Tiptap extension: Embedded Media
This commit is contained in:
54
src/Umbraco.Web.UI.Client/src/external/tiptap/extensions/tiptap-umb-embedded-media.extension.ts
vendored
Normal file
54
src/Umbraco.Web.UI.Client/src/external/tiptap/extensions/tiptap-umb-embedded-media.extension.ts
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
import { mergeAttributes, Node } from '@tiptap/core';
|
||||
|
||||
export const umbEmbeddedMedia = Node.create({
|
||||
name: 'umbEmbeddedMedia',
|
||||
group() {
|
||||
return this.options.inline ? 'inline' : 'block';
|
||||
},
|
||||
inline() {
|
||||
return this.options.inline;
|
||||
},
|
||||
atom: true,
|
||||
marks: '',
|
||||
draggable: true,
|
||||
selectable: true,
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
'data-embed-constrain': { default: false },
|
||||
'data-embed-height': { default: 240 },
|
||||
'data-embed-url': { default: null },
|
||||
'data-embed-width': { default: 360 },
|
||||
markup: { default: null },
|
||||
};
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [{ tag: 'div', class: 'umb-embed-holder', getAttrs: (node) => ({ markup: node.innerHTML }) }];
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
const { markup, ...attrs } = HTMLAttributes;
|
||||
const embed = document.createRange().createContextualFragment(markup);
|
||||
return ['div', mergeAttributes({ class: 'umb-embed-holder' }, attrs), embed];
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
return {
|
||||
setEmbeddedMedia:
|
||||
(options) =>
|
||||
({ commands }) => {
|
||||
const attrs = { markup: options.markup, 'data-embed-url': options.url };
|
||||
return commands.insertContent({ type: this.name, attrs });
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands<ReturnType> {
|
||||
umbEmbeddedMedia: {
|
||||
setEmbeddedMedia: (options: { markup: string; url: string }) => ReturnType;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -24,4 +24,5 @@ export { TextAlign } from '@tiptap/extension-text-align';
|
||||
export { Underline } from '@tiptap/extension-underline';
|
||||
export { Image } from '@tiptap/extension-image';
|
||||
// CUSTOM EXTENSIONS
|
||||
export * from './extensions/tiptap-umb-embedded-media.extension.js';
|
||||
export * from './extensions/tiptap-umb-image.extension.js';
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { UmbOEmbedRepository } from '../repository/oembed.repository.js';
|
||||
import type { UmbEmbeddedMediaModalData, UmbEmbeddedMediaModalValue } from './embedded-media-modal.token.js';
|
||||
import { css, html, unsafeHTML, when, customElement, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { umbFocus } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import type { UUIButtonState, UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
|
||||
@customElement('umb-embedded-media-modal')
|
||||
@@ -27,6 +28,7 @@ export class UmbEmbeddedMediaModalElement extends UmbModalBaseElement<
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
if (this.data?.width) this._width = this.data.width;
|
||||
if (this.data?.height) this._height = this.data.height;
|
||||
if (this.data?.constrain) this.value = { ...this.value, constrain: this.data.constrain };
|
||||
@@ -81,7 +83,7 @@ export class UmbEmbeddedMediaModalElement extends UmbModalBaseElement<
|
||||
<uui-box>
|
||||
<umb-property-layout label=${this.localize.term('general_url')} orientation="vertical">
|
||||
<div slot="editor">
|
||||
<uui-input id="url" .value=${this._url} @input=${this.#onUrlChange} required="true">
|
||||
<uui-input id="url" .value=${this._url} @input=${this.#onUrlChange} required="true" ${umbFocus()}>
|
||||
<uui-button
|
||||
slot="append"
|
||||
look="primary"
|
||||
|
||||
@@ -180,6 +180,32 @@ export class UmbInputTiptapElement extends UmbFormControlMixin<string, typeof Um
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.umb-embed-holder {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.umb-embed-holder > * {
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.umb-embed-holder.ProseMirror-selectednode {
|
||||
outline: 2px solid var(--uui-palette-spanish-pink-light);
|
||||
}
|
||||
|
||||
.umb-embed-holder::before {
|
||||
z-index: 1000;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
content: ' ';
|
||||
}
|
||||
|
||||
.umb-embed-holder.ProseMirror-selectednode::before {
|
||||
background: rgba(0, 0, 0, 0.025);
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -14,6 +14,7 @@ const kinds: Array<UmbExtensionManifestKind> = [
|
||||
},
|
||||
];
|
||||
|
||||
// TODO: [LK] Move each of these to their corresponding packages, e.g. "code-editor", "embedded-media", "media", "multi-url-picker"
|
||||
const umbExtensions: Array<ManifestTiptapExtension | ManifestTiptapExtensionButtonKind> = [
|
||||
{
|
||||
type: 'tiptapExtension',
|
||||
@@ -33,11 +34,11 @@ const umbExtensions: Array<ManifestTiptapExtension | ManifestTiptapExtensionButt
|
||||
kind: 'button',
|
||||
alias: 'Umb.Tiptap.Embed',
|
||||
name: 'Embed Tiptap Extension',
|
||||
api: () => import('./umb/embed.extension.js'),
|
||||
api: () => import('./umb/embedded-media.extension.js'),
|
||||
meta: {
|
||||
alias: 'umb-embed',
|
||||
alias: 'umb-embedded-media',
|
||||
icon: 'icon-embed',
|
||||
label: 'Embed',
|
||||
label: '#general_embed',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { UmbTiptapToolbarElementApiBase } from '../types.js';
|
||||
import type { Editor } from '@umbraco-cms/backoffice/external/tiptap';
|
||||
|
||||
export default class UmbTiptapEmbedExtensionApi extends UmbTiptapToolbarElementApiBase {
|
||||
getTiptapExtensions = () => [];
|
||||
|
||||
override async execute(editor?: Editor) {
|
||||
console.log('umb-embed.execute', editor);
|
||||
// Research: https://github.com/ueberdosis/tiptap/tree/main/packages/extension-youtube
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { UmbTiptapToolbarElementApiBase } from '../types.js';
|
||||
import { umbEmbeddedMedia } from '@umbraco-cms/backoffice/external/tiptap';
|
||||
import { UMB_EMBEDDED_MEDIA_MODAL } from '@umbraco-cms/backoffice/embedded-media';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import type { Editor } from '@umbraco-cms/backoffice/external/tiptap';
|
||||
|
||||
export default class UmbTiptapEmbedExtensionApi extends UmbTiptapToolbarElementApiBase {
|
||||
getTiptapExtensions = () => [umbEmbeddedMedia.configure({ inline: true })];
|
||||
|
||||
override isActive = (editor: Editor) => editor.isActive(umbEmbeddedMedia.name) === true;
|
||||
|
||||
override async execute(editor?: Editor) {
|
||||
const data = {
|
||||
constrain: false,
|
||||
height: 240,
|
||||
width: 360,
|
||||
url: '',
|
||||
};
|
||||
|
||||
const attrs = editor?.getAttributes(umbEmbeddedMedia.name);
|
||||
if (attrs) {
|
||||
data.constrain = attrs['data-embed-constrain'];
|
||||
data.height = attrs['data-embed-height'];
|
||||
data.width = attrs['data-embed-width'];
|
||||
data.url = attrs['data-embed-url'];
|
||||
}
|
||||
|
||||
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
|
||||
const modalHandler = modalManager.open(this, UMB_EMBEDDED_MEDIA_MODAL, { data });
|
||||
|
||||
if (!modalHandler) return;
|
||||
|
||||
const result = await modalHandler.onSubmit().catch(() => undefined);
|
||||
if (!result) return;
|
||||
|
||||
editor?.commands.setEmbeddedMedia(result);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user