diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json index 4fae5c0ec7..f8dd3ed1b4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json @@ -233,6 +233,10 @@ "name": "icon-block", "file": "ban.svg" }, + { + "name": "icon-blockquote", + "file": "text-quote.svg" + }, { "_name": "icon-blueprint", "____file": "blueprint.svg" @@ -245,6 +249,10 @@ "name": "icon-boat-shipping", "file": "ship.svg" }, + { + "name": "icon-bold", + "file": "bold.svg" + }, { "_name": "icon-bomb", "____file": "bomb.svg" @@ -797,6 +805,10 @@ "_name": "icon-eject", "____file": "eject.svg" }, + { + "name": "icon-embed", + "file": "monitor-play.svg" + }, { "name": "icon-employee", "file": "user.svg", @@ -1071,6 +1083,18 @@ "_name": "icon-hd", "____file": "hd.svg" }, + { + "name": "icon-heading-1", + "file": "heading-1.svg" + }, + { + "name": "icon-heading-2", + "file": "heading-2.svg" + }, + { + "name": "icon-heading-3", + "file": "heading-3.svg" + }, { "name": "icon-headphones", "file": "headphones.svg" @@ -1104,6 +1128,10 @@ "name": "icon-home", "file": "house.svg" }, + { + "name": "icon-horizontal-rule", + "file": "separator-horizontal.svg" + }, { "name": "icon-hourglass", "file": "hourglass.svg" @@ -1153,6 +1181,10 @@ "file": "smartphone.svg", "legacy": true }, + { + "name": "icon-italic", + "file": "italic.svg" + }, { "name": "icon-item-arrangement", "file": "table-properties.svg", @@ -2128,6 +2160,10 @@ "file": "square-activity.svg", "legacy": true }, + { + "name": "icon-strikethrough", + "file": "strikethrough.svg" + }, { "name": "icon-sunny", "file": "sun.svg" @@ -2178,6 +2214,22 @@ "name": "icon-terminal", "file": "square-terminal.svg" }, + { + "name": "icon-text-align-center", + "file": "align-center.svg" + }, + { + "name": "icon-text-align-justify", + "file": "align-justify.svg" + }, + { + "name": "icon-text-align-left", + "file": "align-left.svg" + }, + { + "name": "icon-text-align-right", + "file": "align-right.svg" + }, { "name": "icon-theater", "file": "drama.svg" @@ -2308,6 +2360,10 @@ "name": "icon-undo", "file": "undo-2.svg" }, + { + "name": "icon-underline", + "file": "underline.svg" + }, { "name": "icon-unlocked", "file": "lock-open.svg" diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons.ts index ff42d07bc6..acc2b0e7c6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons.ts @@ -159,6 +159,10 @@ name: "icon-block", path: () => import("./icons/icon-block.js"), },{ +name: "icon-blockquote", + +path: () => import("./icons/icon-blockquote.js"), +},{ name: "icon-bluetooth", path: () => import("./icons/icon-bluetooth.js"), @@ -167,6 +171,10 @@ name: "icon-boat-shipping", path: () => import("./icons/icon-boat-shipping.js"), },{ +name: "icon-bold", + +path: () => import("./icons/icon-bold.js"), +},{ name: "icon-bones", path: () => import("./icons/icon-bones.js"), @@ -627,6 +635,10 @@ name: "icon-edit", path: () => import("./icons/icon-edit.js"), },{ +name: "icon-embed", + +path: () => import("./icons/icon-embed.js"), +},{ name: "icon-employee", legacy: true, path: () => import("./icons/icon-employee.js"), @@ -855,6 +867,18 @@ name: "icon-hard-drive", legacy: true, path: () => import("./icons/icon-hard-drive.js"), },{ +name: "icon-heading-1", + +path: () => import("./icons/icon-heading-1.js"), +},{ +name: "icon-heading-2", + +path: () => import("./icons/icon-heading-2.js"), +},{ +name: "icon-heading-3", + +path: () => import("./icons/icon-heading-3.js"), +},{ name: "icon-headphones", path: () => import("./icons/icon-headphones.js"), @@ -887,6 +911,10 @@ name: "icon-home", path: () => import("./icons/icon-home.js"), },{ +name: "icon-horizontal-rule", + +path: () => import("./icons/icon-horizontal-rule.js"), +},{ name: "icon-hourglass", path: () => import("./icons/icon-hourglass.js"), @@ -927,6 +955,10 @@ name: "icon-iphone", legacy: true, path: () => import("./icons/icon-iphone.js"), },{ +name: "icon-italic", + +path: () => import("./icons/icon-italic.js"), +},{ name: "icon-item-arrangement", legacy: true, path: () => import("./icons/icon-item-arrangement.js"), @@ -1803,6 +1835,10 @@ name: "icon-stream", legacy: true, path: () => import("./icons/icon-stream.js"), },{ +name: "icon-strikethrough", + +path: () => import("./icons/icon-strikethrough.js"), +},{ name: "icon-sunny", path: () => import("./icons/icon-sunny.js"), @@ -1851,6 +1887,22 @@ name: "icon-terminal", path: () => import("./icons/icon-terminal.js"), },{ +name: "icon-text-align-center", + +path: () => import("./icons/icon-text-align-center.js"), +},{ +name: "icon-text-align-justify", + +path: () => import("./icons/icon-text-align-justify.js"), +},{ +name: "icon-text-align-left", + +path: () => import("./icons/icon-text-align-left.js"), +},{ +name: "icon-text-align-right", + +path: () => import("./icons/icon-text-align-right.js"), +},{ name: "icon-theater", path: () => import("./icons/icon-theater.js"), @@ -1967,6 +2019,10 @@ name: "icon-undo", path: () => import("./icons/icon-undo.js"), },{ +name: "icon-underline", + +path: () => import("./icons/icon-underline.js"), +},{ name: "icon-unlocked", path: () => import("./icons/icon-unlocked.js"), diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-blockquote.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-blockquote.ts new file mode 100644 index 0000000000..7d4802defc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-blockquote.ts @@ -0,0 +1,17 @@ +export default ` + + + + + + +`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-bold.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-bold.ts new file mode 100644 index 0000000000..6b4b1d986b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-bold.ts @@ -0,0 +1,14 @@ +export default ` + + + +`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-embed.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-embed.ts new file mode 100644 index 0000000000..22fdef036f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-embed.ts @@ -0,0 +1,17 @@ +export default ` + + + + + + +`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-heading-1.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-heading-1.ts new file mode 100644 index 0000000000..20fe0c5dd1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-heading-1.ts @@ -0,0 +1,17 @@ +export default ` + + + + + + +`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-heading-2.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-heading-2.ts new file mode 100644 index 0000000000..f062a49ca9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-heading-2.ts @@ -0,0 +1,17 @@ +export default ` + + + + + + +`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-heading-3.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-heading-3.ts new file mode 100644 index 0000000000..cb237bb4a2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-heading-3.ts @@ -0,0 +1,18 @@ +export default ` + + + + + + + +`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-horizontal-rule.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-horizontal-rule.ts new file mode 100644 index 0000000000..424f13bb2f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-horizontal-rule.ts @@ -0,0 +1,16 @@ +export default ` + + + + + +`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-italic.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-italic.ts new file mode 100644 index 0000000000..d70979bfe6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-italic.ts @@ -0,0 +1,16 @@ +export default ` + + + + + +`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-strikethrough.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-strikethrough.ts new file mode 100644 index 0000000000..9af35d7e81 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-strikethrough.ts @@ -0,0 +1,16 @@ +export default ` + + + + + +`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-text-align-center.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-text-align-center.ts new file mode 100644 index 0000000000..5d985fd584 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-text-align-center.ts @@ -0,0 +1,16 @@ +export default ` + + + + + +`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-text-align-justify.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-text-align-justify.ts new file mode 100644 index 0000000000..7279356fc8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-text-align-justify.ts @@ -0,0 +1,16 @@ +export default ` + + + + + +`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-text-align-left.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-text-align-left.ts new file mode 100644 index 0000000000..43f4ebf794 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-text-align-left.ts @@ -0,0 +1,16 @@ +export default ` + + + + + +`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-text-align-right.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-text-align-right.ts new file mode 100644 index 0000000000..a03f55eec2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-text-align-right.ts @@ -0,0 +1,16 @@ +export default ` + + + + + +`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-underline.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-underline.ts new file mode 100644 index 0000000000..84f133257c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-underline.ts @@ -0,0 +1,15 @@ +export default ` + + + + +`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/components/input-tiptap/icons.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/components/input-tiptap/icons.ts deleted file mode 100644 index 2676404411..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/components/input-tiptap/icons.ts +++ /dev/null @@ -1,268 +0,0 @@ -import { html } from '@umbraco-cms/backoffice/external/lit'; - -const iconSize = '16px'; -export const bold = html` - -`; - -export const italic = html` - - - -`; - -export const underline = html` - - -`; - -export const strikethrough = html` - - - -`; -export const heading1 = html` - - - - -`; -export const heading2 = html` - - - - -`; -export const heading3 = html` - - - - - -`; -export const blockquote = html` - - - - -`; -export const code = html` - - -`; -export const bulletList = html` - - - - - - -`; -export const orderedList = html` - - - - - - -`; -export const horizontalRule = html` - - - -`; -export const alignLeft = html` - - - -`; -export const alignCenter = html` - - - -`; -export const alignRight = html` - - - -`; -export const alignJustify = html` - - - -`; - -export const link = html` - - -`; diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/components/input-tiptap/input-tiptap.element.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/components/input-tiptap/input-tiptap.element.ts index 2041b8bcec..0be12259f8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/components/input-tiptap/input-tiptap.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/components/input-tiptap/input-tiptap.element.ts @@ -3,28 +3,14 @@ import { css, customElement, html, property, state, when } from '@umbraco-cms/ba import { loadManifestApi } from '@umbraco-cms/backoffice/extension-api'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { - Blockquote, - Bold, - BulletList, - Code, - CodeBlock, Document, Dropcursor, Editor, Gapcursor, HardBreak, - Heading, History, - HorizontalRule, - Italic, - Link, - ListItem, - OrderedList, Paragraph, - Strike, Text, - TextAlign, - Underline, } from '@umbraco-cms/backoffice/external/tiptap'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @@ -34,7 +20,9 @@ import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/ import './tiptap-fixed-menu.element.js'; import './tiptap-hover-menu.element.js'; -@customElement('umb-input-tiptap') +const elementName = 'umb-input-tiptap'; + +@customElement(elementName) export class UmbInputTiptapElement extends UmbFormControlMixin(UmbLitElement) { #requiredExtensions = [Document, Dropcursor, Gapcursor, HardBreak, History, Paragraph, Text]; @@ -88,26 +76,7 @@ export class UmbInputTiptapElement extends UmbFormControlMixin { this.value = editor.getHTML(); @@ -142,6 +111,14 @@ export class UmbInputTiptapElement extends UmbFormControlMixin code) { - background-color: var(--uui-color-surface-alt); - padding: var(--uui-size-space-1) var(--uui-size-space-2); - border-radius: calc(var(--uui-border-radius) * 2); - } + code:not(pre > code) { + background-color: var(--uui-color-surface-alt); + padding: var(--uui-size-space-1) var(--uui-size-space-2); + border-radius: calc(var(--uui-border-radius) * 2); + } - #editor code { - font-family: 'Roboto Mono', monospace; - background: none; - color: inherit; - font-size: 0.8rem; - padding: 0; - } - .tiptap { - height: 100%; - width: 100%; - outline: none; - white-space: pre-wrap; - min-width: 0; - } - #editor p, - #editor h1, - #editor h2, - #editor h3 { - margin-top: 0; - margin-bottom: 0.5em; + code { + font-family: 'Roboto Mono', monospace; + background: none; + color: inherit; + font-size: 0.8rem; + padding: 0; + } + + h1, + h2, + h3, + p { + margin-top: 0; + margin-bottom: 0.5em; + } } `, ]; } -export default UmbInputTiptapElement; - declare global { interface HTMLElementTagNameMap { - 'umb-input-tiptap': UmbInputTiptapElement; + [elementName]: UmbInputTiptapElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/components/input-tiptap/tiptap-fixed-menu.element.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/components/input-tiptap/tiptap-fixed-menu.element.ts index 122c1d4c8b..5b879b5c4a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/components/input-tiptap/tiptap-fixed-menu.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/components/input-tiptap/tiptap-fixed-menu.element.ts @@ -1,144 +1,15 @@ -import type { UmbTiptapToolbarButton } from '../../extensions/types.js'; import type { ManifestTiptapExtension } from '../../extensions/tiptap-extension.js'; -import * as icons from './icons.js'; -import { css, customElement, html, property, state, when } from '@umbraco-cms/backoffice/external/lit'; +import { css, customElement, html, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; -@customElement('umb-tiptap-fixed-menu') +const elementName = 'umb-tiptap-fixed-menu'; + +@customElement(elementName) export class UmbTiptapFixedMenuElement extends UmbLitElement { @property({ type: Boolean, reflect: true }) readonly = false; - @state() - actions: Array = [ - // TODO: I don't think we need a paragraph button. It's the default state. - // { - // name: 'paragraph', - // icon: html` - // - // - // `, - // command: (editor) => editor?.chain().focus().setParagraph().run(), - // }, - { - name: 'bold', - icon: icons.bold, - isActive: (editor) => editor?.isActive('bold'), - command: (editor) => editor?.chain().focus().toggleBold().run(), - }, - { - name: 'italic', - icon: icons.italic, - isActive: (editor) => editor?.isActive('italic'), - command: (editor) => editor?.chain().focus().toggleItalic().run(), - }, - { - name: 'underline', - icon: icons.underline, - isActive: (editor) => editor?.isActive('underline'), - command: (editor) => editor?.chain().focus().toggleUnderline().run(), - }, - { - name: 'strikethrough', - icon: icons.strikethrough, - isActive: (editor) => editor?.isActive('strike'), - command: (editor) => editor?.chain().focus().toggleStrike().run(), - }, - { - name: 'h1', - icon: icons.heading1, - isActive: (editor) => editor?.isActive('heading', { level: 1 }), - command: (editor) => editor?.chain().focus().toggleHeading({ level: 1 }).run(), - }, - { - name: 'h2', - icon: icons.heading2, - isActive: (editor) => editor?.isActive('heading', { level: 2 }), - command: (editor) => editor?.chain().focus().toggleHeading({ level: 2 }).run(), - }, - { - name: 'h3', - icon: icons.heading3, - isActive: (editor) => editor?.isActive('heading', { level: 3 }), - command: (editor) => editor?.chain().focus().toggleHeading({ level: 3 }).run(), - }, - { - name: 'blockquote', - icon: icons.blockquote, - isActive: (editor) => editor?.isActive('blockquote'), - command: (editor) => editor?.chain().focus().toggleBlockquote().run(), - }, - { - name: 'code', - icon: icons.code, - isActive: (editor) => editor?.isActive('codeBlock'), - command: (editor) => editor?.chain().focus().toggleCodeBlock().run(), - }, - { - name: 'bullet-list', - icon: icons.bulletList, - isActive: (editor) => editor?.isActive('bulletList'), - command: (editor) => editor?.chain().focus().toggleBulletList().run(), - }, - { - name: 'ordered-list', - icon: icons.orderedList, - isActive: (editor) => editor?.isActive('orderedList'), - command: (editor) => editor?.chain().focus().toggleOrderedList().run(), - }, - { - name: 'horizontal-rule', - icon: icons.horizontalRule, - isActive: (editor) => editor?.isActive('horizontalRule'), - command: (editor) => editor?.chain().focus().setHorizontalRule().run(), - }, - { - name: 'align-left', - icon: icons.alignLeft, - isActive: (editor) => editor?.isActive({ textAlign: 'left' }), - command: (editor) => editor?.chain().focus().setTextAlign('left').run(), - }, - { - name: 'align-center', - icon: icons.alignCenter, - isActive: (editor) => editor?.isActive({ textAlign: 'center' }), - command: (editor) => editor?.chain().focus().setTextAlign('center').run(), - }, - { - name: 'align-right', - icon: icons.alignRight, - isActive: (editor) => editor?.isActive({ textAlign: 'right' }), - command: (editor) => editor?.chain().focus().setTextAlign('right').run(), - }, - { - name: 'align-justify', - icon: icons.alignJustify, - isActive: (editor) => editor?.isActive({ textAlign: 'justify' }), - command: (editor) => editor?.chain().focus().setTextAlign('justify').run(), - }, - { - name: 'link', - icon: icons.link, - isActive: (editor) => editor?.isActive('link'), - command: () => { - const text = prompt('Enter the text'); - const url = prompt('Enter the URL'); - - if (url && text && this.editor) { - const { from } = this.editor.state.selection; - this.editor - .chain() - .focus() - .insertContent(text) - .setTextSelection({ from: from, to: from + text.length }) - .setLink({ href: url, target: '_blank' }) - .run(); - } - }, - }, - ]; - @property({ attribute: false }) set editor(value) { const oldValue = this.#editor; @@ -146,35 +17,14 @@ export class UmbTiptapFixedMenuElement extends UmbLitElement { return; } this.#editor = value; - this.#editor?.on('selectionUpdate', this.#onUpdate); - this.#editor?.on('update', this.#onUpdate); - // todo add listener for commands } get editor() { return this.#editor; } #editor?: Editor; - #onUpdate = () => { - this.requestUpdate(); - }; - override render() { return html` - ${this.actions.map( - (action) => html` - - `, - )} !!ext.kind || !!ext.element} @@ -187,10 +37,13 @@ export class UmbTiptapFixedMenuElement extends UmbLitElement { :host { border-radius: var(--uui-border-radius); border: 1px solid var(--uui-color-border); + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; background-color: var(--uui-color-surface); color: var(--color-text); display: grid; grid-template-columns: repeat(auto-fill, minmax(24px, 1fr)); + gap: 4px; position: sticky; top: -25px; left: 0px; @@ -202,42 +55,11 @@ export class UmbTiptapFixedMenuElement extends UmbLitElement { pointer-events: none; background-color: var(--uui-color-surface-alt); } - - button { - color: var(--uui-color-interactive); - width: 24px; - height: 24px; - padding: 4px; - border: none; - background: none; - cursor: pointer; - margin: 0; - border-radius: 4px; - box-sizing: border-box; - } - - button:hover { - color: var(--uui-color-interactive-emphasis); - background-color: var(--uui-color-surface-alt); - } - - button.active { - background-color: var(--uui-color-selected); - color: var(--uui-color-selected-contrast); - } - button.active:hover { - background-color: var(--uui-color-selected-emphasis); - } - - button img { - width: 100%; - height: 100%; - } `; } declare global { interface HTMLElementTagNameMap { - 'umb-tiptap-fixed-menu': UmbTiptapFixedMenuElement; + [elementName]: UmbTiptapFixedMenuElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/components/input-tiptap/tiptap-hover-menu.element.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/components/input-tiptap/tiptap-hover-menu.element.ts index 28c9feb62e..30f1458cb7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/components/input-tiptap/tiptap-hover-menu.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/components/input-tiptap/tiptap-hover-menu.element.ts @@ -24,7 +24,6 @@ export class UmbTiptapHoverMenuElement extends LitElement { } #onUpdate = () => { - console.log('LINK ACTIVE'); if (this.editor?.isActive('link')) { // show the popover this.showPopover(); @@ -34,7 +33,6 @@ export class UmbTiptapHoverMenuElement extends LitElement { }; override render() { - console.log('RENDER HOVER MENU'); return html``; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/components/toolbar/tiptap-toolbar-button.element.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/components/toolbar/tiptap-toolbar-button.element.ts new file mode 100644 index 0000000000..fc3c7219bd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/components/toolbar/tiptap-toolbar-button.element.ts @@ -0,0 +1,66 @@ +import type { ManifestTiptapExtensionButtonKind } from '../../extensions/tiptap-extension.js'; +import type { UmbTiptapToolbarElementApi } from '../../extensions/types.js'; +import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; +import { customElement, html, ifDefined, state, when } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; + +const elementName = 'umb-tiptap-toolbar-button'; + +@customElement(elementName) +export class UmbTiptapToolbarButtonElement extends UmbLitElement { + public api?: UmbTiptapToolbarElementApi; + public editor?: Editor; + public manifest?: ManifestTiptapExtensionButtonKind; + + @state() + private _isActive = false; + + override connectedCallback() { + super.connectedCallback(); + + if (this.editor) { + this.editor.on('selectionUpdate', this.#onEditorUpdate); + this.editor.on('update', this.#onEditorUpdate); + } + } + + override disconnectedCallback() { + super.disconnectedCallback(); + + if (this.editor) { + this.editor.off('selectionUpdate', this.#onEditorUpdate); + this.editor.off('update', this.#onEditorUpdate); + } + } + + #onEditorUpdate = () => { + if (this.api && this.editor && this.manifest) { + this._isActive = this.api.isActive(this.editor); + } + }; + + override render() { + return html` + this.api?.execute(this.editor)}> + ${when( + this.manifest?.meta.icon, + () => html``, + () => html`${this.manifest?.meta.label}`, + )} + + `; + } +} + +export { UmbTiptapToolbarButtonElement as element }; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: UmbTiptapToolbarButtonElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/blockquote.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/blockquote.extension.ts new file mode 100644 index 0000000000..d1ecfdfe19 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/blockquote.extension.ts @@ -0,0 +1,11 @@ +import { UmbTiptapToolbarElementApiBase } from '../types.js'; +import { Blockquote } from '@umbraco-cms/backoffice/external/tiptap'; +import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; + +export default class UmbTiptapBlockquoteExtensionApi extends UmbTiptapToolbarElementApiBase { + getTiptapExtensions = () => [Blockquote]; + + override execute(editor?: Editor) { + editor?.chain().focus().toggleBlockquote().run(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/bold.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/bold.extension.ts new file mode 100644 index 0000000000..e65d167551 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/bold.extension.ts @@ -0,0 +1,11 @@ +import { UmbTiptapToolbarElementApiBase } from '../types.js'; +import { Bold } from '@umbraco-cms/backoffice/external/tiptap'; +import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; + +export default class UmbTiptapBoldExtensionApi extends UmbTiptapToolbarElementApiBase { + getTiptapExtensions = () => [Bold]; + + override execute(editor?: Editor) { + editor?.chain().focus().toggleBold().run(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/bullet-list.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/bullet-list.extension.ts new file mode 100644 index 0000000000..8dd956279c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/bullet-list.extension.ts @@ -0,0 +1,11 @@ +import { UmbTiptapToolbarElementApiBase } from '../types.js'; +import { BulletList, ListItem } from '@umbraco-cms/backoffice/external/tiptap'; +import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; + +export default class UmbTiptapBulletListExtensionApi extends UmbTiptapToolbarElementApiBase { + getTiptapExtensions = () => [BulletList, ListItem]; + + override execute(editor?: Editor) { + editor?.chain().focus().toggleBulletList().run(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/code-block.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/code-block.extension.ts new file mode 100644 index 0000000000..7cfa5861b2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/code-block.extension.ts @@ -0,0 +1,12 @@ +import { UmbTiptapToolbarElementApiBase } from '../types.js'; +import { Code, CodeBlock } from '@umbraco-cms/backoffice/external/tiptap'; +import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; + +export default class UmbTiptapCodeBlockExtensionApi extends UmbTiptapToolbarElementApiBase { + getTiptapExtensions = () => [Code, CodeBlock]; + + override execute(editor?: Editor) { + // editor.chain().focus().toggleCode().run(); + editor?.chain().focus().toggleCodeBlock().run(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/heading1.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/heading1.extension.ts new file mode 100644 index 0000000000..7543e321fb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/heading1.extension.ts @@ -0,0 +1,15 @@ +import { UmbTiptapToolbarElementApiBase } from '../types.js'; +import { Heading } from '@umbraco-cms/backoffice/external/tiptap'; +import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; + +export default class UmbTiptapHeading1ExtensionApi extends UmbTiptapToolbarElementApiBase { + getTiptapExtensions = () => [Heading]; + + override isActive(editor?: Editor) { + return editor?.isActive('heading', { level: 1 }) === true; + } + + override execute(editor?: Editor) { + editor?.chain().focus().toggleHeading({ level: 1 }).run(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/heading2.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/heading2.extension.ts new file mode 100644 index 0000000000..3edcf7b57a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/heading2.extension.ts @@ -0,0 +1,15 @@ +import { UmbTiptapToolbarElementApiBase } from '../types.js'; +import { Heading } from '@umbraco-cms/backoffice/external/tiptap'; +import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; + +export default class UmbTiptapHeading2ExtensionApi extends UmbTiptapToolbarElementApiBase { + getTiptapExtensions = () => [Heading]; + + override isActive(editor?: Editor) { + return editor?.isActive('heading', { level: 2 }) === true; + } + + override execute(editor?: Editor) { + editor?.chain().focus().toggleHeading({ level: 2 }).run(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/heading3.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/heading3.extension.ts new file mode 100644 index 0000000000..9def84dc2c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/heading3.extension.ts @@ -0,0 +1,15 @@ +import { UmbTiptapToolbarElementApiBase } from '../types.js'; +import { Heading } from '@umbraco-cms/backoffice/external/tiptap'; +import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; + +export default class UmbTiptapHeading3ExtensionApi extends UmbTiptapToolbarElementApiBase { + getTiptapExtensions = () => [Heading]; + + override isActive(editor?: Editor) { + return editor?.isActive('heading', { level: 3 }) === true; + } + + override execute(editor?: Editor) { + editor?.chain().focus().toggleHeading({ level: 3 }).run(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/horizontal-rule.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/horizontal-rule.extension.ts new file mode 100644 index 0000000000..0219f45673 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/horizontal-rule.extension.ts @@ -0,0 +1,11 @@ +import { UmbTiptapToolbarElementApiBase } from '../types.js'; +import { HorizontalRule } from '@umbraco-cms/backoffice/external/tiptap'; +import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; + +export default class UmbTiptapHorizontalRuleExtensionApi extends UmbTiptapToolbarElementApiBase { + getTiptapExtensions = () => [HorizontalRule]; + + override execute(editor?: Editor) { + editor?.chain().focus().setHorizontalRule().run(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/image.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/image.extension.ts new file mode 100644 index 0000000000..bf8b9956e9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/image.extension.ts @@ -0,0 +1,8 @@ +import { UmbTiptapToolbarElementApiBase } from '../types.js'; +import { UmbImage } from '@umbraco-cms/backoffice/external/tiptap'; + +export default class UmbTiptapImageExtensionApi extends UmbTiptapToolbarElementApiBase { + getTiptapExtensions() { + return [UmbImage.configure({ inline: true })]; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/italic.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/italic.extension.ts new file mode 100644 index 0000000000..ff122f81e4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/italic.extension.ts @@ -0,0 +1,11 @@ +import { UmbTiptapToolbarElementApiBase } from '../types.js'; +import { Italic } from '@umbraco-cms/backoffice/external/tiptap'; +import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; + +export default class UmbTiptapItalicExtensionApi extends UmbTiptapToolbarElementApiBase { + getTiptapExtensions = () => [Italic]; + + override execute(editor?: Editor) { + editor?.chain().focus().toggleItalic().run(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/manifests.ts new file mode 100644 index 0000000000..9ef58cd081 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/manifests.ts @@ -0,0 +1,221 @@ +import type { ManifestTiptapExtension, ManifestTiptapExtensionButtonKind } from '../tiptap-extension.js'; + +export const manifests: Array = [ + { + type: 'tiptapExtension', + kind: 'button', + alias: 'Umb.Tiptap.Blockquote', + name: 'Blockquote Tiptap Extension', + api: () => import('./blockquote.extension.js'), + weight: 995, + meta: { + alias: 'blockquote', + icon: 'icon-blockquote', + label: 'Blockquote', + }, + }, + { + type: 'tiptapExtension', + kind: 'button', + alias: 'Umb.Tiptap.Bold', + name: 'Bold Tiptap Extension', + api: () => import('./bold.extension.js'), + weight: 999, + meta: { + alias: 'bold', + icon: 'icon-bold', + label: 'Bold', + }, + }, + { + type: 'tiptapExtension', + kind: 'button', + alias: 'Umb.Tiptap.BulletList', + name: 'Bullet List Tiptap Extension', + api: () => import('./bullet-list.extension.js'), + weight: 993, + meta: { + alias: 'bulletList', + icon: 'icon-bulleted-list', + label: 'Bullet List', + }, + }, + { + type: 'tiptapExtension', + kind: 'button', + alias: 'Umb.Tiptap.CodeBlock', + name: 'Code Block Tiptap Extension', + api: () => import('./code-block.extension.js'), + weight: 994, + meta: { + alias: 'codeBlock', + icon: 'icon-code', + label: 'Code Block', + }, + }, + { + type: 'tiptapExtension', + kind: 'button', + alias: 'Umb.Tiptap.Heading1', + name: 'Heading 1 Tiptap Extension', + api: () => import('./heading1.extension.js'), + weight: 949, + meta: { + alias: 'heading1', + icon: 'icon-heading-1', + label: 'Heading 1', + }, + }, + { + type: 'tiptapExtension', + kind: 'button', + alias: 'Umb.Tiptap.Heading2', + name: 'Heading 2 Tiptap Extension', + api: () => import('./heading2.extension.js'), + weight: 948, + meta: { + alias: 'heading2', + icon: 'icon-heading-2', + label: 'Heading 2', + }, + }, + { + type: 'tiptapExtension', + kind: 'button', + alias: 'Umb.Tiptap.Heading3', + name: 'Heading 3 Tiptap Extension', + api: () => import('./heading3.extension.js'), + weight: 947, + meta: { + alias: 'heading3', + icon: 'icon-heading-3', + label: 'Heading 3', + }, + }, + { + type: 'tiptapExtension', + kind: 'button', + alias: 'Umb.Tiptap.HorizontalRule', + name: 'Horizontal Rule Tiptap Extension', + api: () => import('./horizontal-rule.extension.js'), + weight: 991, + meta: { + alias: 'horizontalRule', + icon: 'icon-horizontal-rule', + label: 'Horizontal Rule', + }, + }, + { + type: 'tiptapExtension', + alias: 'Umb.Tiptap.Image', + name: 'Image Tiptap Extension', + api: () => import('./image.extension.js'), + meta: { + alias: 'image', + }, + }, + { + type: 'tiptapExtension', + kind: 'button', + alias: 'Umb.Tiptap.Italic', + name: 'Italic Tiptap Extension', + api: () => import('./italic.extension.js'), + weight: 998, + meta: { + alias: 'italic', + icon: 'icon-italic', + label: 'Italic', + }, + }, + { + type: 'tiptapExtension', + kind: 'button', + alias: 'Umb.Tiptap.OrderedList', + name: 'Ordered List Tiptap Extension', + api: () => import('./ordered-list.extension.js'), + weight: 992, + meta: { + alias: 'orderedList', + icon: 'icon-ordered-list', + label: 'Ordered List', + }, + }, + { + type: 'tiptapExtension', + kind: 'button', + alias: 'Umb.Tiptap.Strike', + name: 'Strike Tiptap Extension', + api: () => import('./strike.extension.js'), + weight: 996, + meta: { + alias: 'strike', + icon: 'icon-strikethrough', + label: 'Strike', + }, + }, + { + type: 'tiptapExtension', + kind: 'button', + alias: 'Umb.Tiptap.TextAlignCenter', + name: 'Text Align Center Tiptap Extension', + api: () => import('./text-align-center.extension.js'), + weight: 918, + meta: { + alias: 'text-align-center', + icon: 'icon-text-align-center', + label: 'Text Align Center', + }, + }, + { + type: 'tiptapExtension', + kind: 'button', + alias: 'Umb.Tiptap.TextAlignJustify', + name: 'Text Align Justify Tiptap Extension', + api: () => import('./text-align-justify.extension.js'), + weight: 916, + meta: { + alias: 'text-align-justify', + icon: 'icon-text-align-justify', + label: 'Text Align Justify', + }, + }, + { + type: 'tiptapExtension', + kind: 'button', + alias: 'Umb.Tiptap.TextAlignLeft', + name: 'Text Align Left Tiptap Extension', + api: () => import('./text-align-left.extension.js'), + weight: 919, + meta: { + alias: 'text-align-left', + icon: 'icon-text-align-left', + label: 'Text Align Left', + }, + }, + { + type: 'tiptapExtension', + kind: 'button', + alias: 'Umb.Tiptap.TextAlignRight', + name: 'Text Align Right Tiptap Extension', + api: () => import('./text-align-right.extension.js'), + weight: 917, + meta: { + alias: 'text-align-right', + icon: 'icon-text-align-right', + label: 'Text Align Right', + }, + }, + { + type: 'tiptapExtension', + kind: 'button', + alias: 'Umb.Tiptap.Underline', + name: 'Underline Tiptap Extension', + api: () => import('./underline.extension.js'), + weight: 997, + meta: { + alias: 'underline', + icon: 'icon-underline', + label: 'Underline', + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/ordered-list.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/ordered-list.extension.ts new file mode 100644 index 0000000000..471d794e1c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/ordered-list.extension.ts @@ -0,0 +1,11 @@ +import { UmbTiptapToolbarElementApiBase } from '../types.js'; +import { OrderedList, ListItem } from '@umbraco-cms/backoffice/external/tiptap'; +import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; + +export default class UmbTiptapOrderedListExtensionApi extends UmbTiptapToolbarElementApiBase { + getTiptapExtensions = () => [OrderedList, ListItem]; + + override execute(editor?: Editor) { + editor?.chain().focus().toggleOrderedList().run(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/strike.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/strike.extension.ts new file mode 100644 index 0000000000..b073c12dd9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/strike.extension.ts @@ -0,0 +1,11 @@ +import { UmbTiptapToolbarElementApiBase } from '../types.js'; +import { Strike } from '@umbraco-cms/backoffice/external/tiptap'; +import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; + +export default class UmbTiptapStrikeExtensionApi extends UmbTiptapToolbarElementApiBase { + getTiptapExtensions = () => [Strike]; + + override execute(editor?: Editor) { + editor?.chain().focus().toggleStrike().run(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/text-align-center.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/text-align-center.extension.ts new file mode 100644 index 0000000000..fa9c90855c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/text-align-center.extension.ts @@ -0,0 +1,19 @@ +import { UmbTiptapToolbarElementApiBase } from '../types.js'; +import { TextAlign } from '@umbraco-cms/backoffice/external/tiptap'; +import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; + +export default class UmbTiptapTextAlignCenterExtensionApi extends UmbTiptapToolbarElementApiBase { + getTiptapExtensions = () => [ + TextAlign.configure({ + types: ['heading', 'paragraph', 'blockquote', 'orderedList', 'bulletList', 'codeBlock'], + }), + ]; + + override isActive(editor?: Editor) { + return editor?.isActive({ textAlign: 'center' }) === true; + } + + override execute(editor?: Editor) { + editor?.chain().focus().setTextAlign('center').run(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/text-align-justify.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/text-align-justify.extension.ts new file mode 100644 index 0000000000..03e197654f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/text-align-justify.extension.ts @@ -0,0 +1,19 @@ +import { UmbTiptapToolbarElementApiBase } from '../types.js'; +import { TextAlign } from '@umbraco-cms/backoffice/external/tiptap'; +import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; + +export default class UmbTiptapTextAlignJustifyExtensionApi extends UmbTiptapToolbarElementApiBase { + getTiptapExtensions = () => [ + TextAlign.configure({ + types: ['heading', 'paragraph', 'blockquote', 'orderedList', 'bulletList', 'codeBlock'], + }), + ]; + + override isActive(editor?: Editor) { + return editor?.isActive({ textAlign: 'justify' }) === true; + } + + override execute(editor?: Editor) { + editor?.chain().focus().setTextAlign('justify').run(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/text-align-left.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/text-align-left.extension.ts new file mode 100644 index 0000000000..2f35da46d2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/text-align-left.extension.ts @@ -0,0 +1,19 @@ +import { UmbTiptapToolbarElementApiBase } from '../types.js'; +import { TextAlign } from '@umbraco-cms/backoffice/external/tiptap'; +import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; + +export default class UmbTiptapTextAlignLeftExtensionApi extends UmbTiptapToolbarElementApiBase { + getTiptapExtensions = () => [ + TextAlign.configure({ + types: ['heading', 'paragraph', 'blockquote', 'orderedList', 'bulletList', 'codeBlock'], + }), + ]; + + override isActive(editor?: Editor) { + return editor?.isActive({ textAlign: 'left' }) === true; + } + + override execute(editor?: Editor) { + editor?.chain().focus().setTextAlign('left').run(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/text-align-right.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/text-align-right.extension.ts new file mode 100644 index 0000000000..62de9a54f9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/text-align-right.extension.ts @@ -0,0 +1,19 @@ +import { UmbTiptapToolbarElementApiBase } from '../types.js'; +import { TextAlign } from '@umbraco-cms/backoffice/external/tiptap'; +import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; + +export default class UmbTiptapTextAlignRightExtensionApi extends UmbTiptapToolbarElementApiBase { + getTiptapExtensions = () => [ + TextAlign.configure({ + types: ['heading', 'paragraph', 'blockquote', 'orderedList', 'bulletList', 'codeBlock'], + }), + ]; + + override isActive(editor?: Editor) { + return editor?.isActive({ textAlign: 'right' }) === true; + } + + override execute(editor?: Editor) { + editor?.chain().focus().setTextAlign('right').run(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/underline.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/underline.extension.ts new file mode 100644 index 0000000000..4e1bac6d6a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/underline.extension.ts @@ -0,0 +1,11 @@ +import { UmbTiptapToolbarElementApiBase } from '../types.js'; +import { Underline } from '@umbraco-cms/backoffice/external/tiptap'; +import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; + +export default class UmbTiptapUnderlineExtensionApi extends UmbTiptapToolbarElementApiBase { + getTiptapExtensions = () => [Underline]; + + override execute(editor?: Editor) { + editor?.chain().focus().toggleUnderline().run(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/manifests.ts index 8afb03e629..4b9394020d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/manifests.ts @@ -1,4 +1,5 @@ -import type { ManifestTiptapExtension } from './tiptap-extension.js'; +import type { ManifestTiptapExtension, ManifestTiptapExtensionButtonKind } from './tiptap-extension.js'; +import { manifests as core } from './core/manifests.js'; import type { ManifestTypes, UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; const kinds: Array = [ @@ -8,33 +9,63 @@ const kinds: Array = [ matchKind: 'button', matchType: 'tiptapExtension', manifest: { - element: () => import('./tiptap-toolbar-button.element.js'), + element: () => import('../components/toolbar/tiptap-toolbar-button.element.js'), }, }, ]; -const extensions: Array = [ +const umbExtensions: Array = [ { type: 'tiptapExtension', - alias: 'Umb.Tiptap.Image', - name: 'Image Tiptap Extension', + kind: 'button', + alias: 'Umb.Tiptap.CodeEditor', + name: 'Code Editor Tiptap Extension', + api: () => import('./umb/code-editor.extension.js'), weight: 1000, - api: () => import('./tiptap-image.extension.js'), - meta: {}, + meta: { + alias: 'umb-code-editor', + icon: 'icon-code', + label: '#general_viewSourceCode', + }, + }, + { + type: 'tiptapExtension', + kind: 'button', + alias: 'Umb.Tiptap.Embed', + name: 'Embed Tiptap Extension', + api: () => import('./umb/embed.extension.js'), + meta: { + alias: 'umb-embed', + icon: 'icon-embed', + label: 'Embed', + }, }, { type: 'tiptapExtension', kind: 'button', alias: 'Umb.Tiptap.MediaPicker', name: 'Media Picker Tiptap Extension', - weight: 900, - api: () => import('./tiptap-mediapicker.extension.js'), + api: () => import('./umb/mediapicker.extension.js'), meta: { alias: 'umb-media', icon: 'icon-picture', label: 'Media picker', }, }, + { + type: 'tiptapExtension', + kind: 'button', + alias: 'Umb.Tiptap.UrlPicker', + name: 'URL Picker Tiptap Extension', + api: () => import('./umb/urlpicker.extension.js'), + meta: { + alias: 'umb-link', + icon: 'icon-link', + label: 'URL picker', + }, + }, ]; +const extensions: Array = [...core, ...umbExtensions]; + export const manifests: Array = [...kinds, ...extensions]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/tiptap-extension.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/tiptap-extension.ts index 40c9fd6186..5ecb61da5d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/tiptap-extension.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/tiptap-extension.ts @@ -8,8 +8,9 @@ export interface ManifestTiptapExtension this.api?.execute(this.editor)}> - ${when( - this.manifest?.meta.icon, - () => html``, - () => html`${this.manifest?.meta.label}`, - )} - - `; - } -} - -export { UmbTiptapToolbarButtonElement as element }; - -declare global { - interface HTMLElementTagNameMap { - [elementName]: UmbTiptapToolbarButtonElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/types.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/types.ts index 5a2d19ccd4..949d73f056 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/types.ts @@ -1,23 +1,36 @@ +import type { ManifestTiptapExtension } from './tiptap-extension.js'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import type { Editor, Extension, Mark, Node } from '@umbraco-cms/backoffice/external/tiptap'; -import type { TemplateResult } from '@umbraco-cms/backoffice/external/lit'; import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -export abstract class UmbTiptapExtensionApi extends UmbControllerBase implements UmbApi { +export interface UmbTiptapExtensionApi extends UmbApi { + getTiptapExtensions(): Array; +} + +export abstract class UmbTiptapExtensionApiBase extends UmbControllerBase implements UmbApi { + public manifest?: ManifestTiptapExtension; + constructor(host: UmbControllerHost) { super(host); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - execute(editor?: Editor) {} - abstract getTiptapExtensions(): Array; } -export interface UmbTiptapToolbarButton { - name: string; - icon: string | TemplateResult; - isActive: (editor?: Editor) => boolean | undefined; - command: (editor?: Editor) => boolean | undefined | void | Promise | Promise | Promise; +export interface UmbTiptapToolbarElementApi extends UmbTiptapExtensionApi { + execute(editor?: Editor): void; + isActive(editor?: Editor): boolean; +} + +export abstract class UmbTiptapToolbarElementApiBase + extends UmbTiptapExtensionApiBase + implements UmbTiptapToolbarElementApi +{ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public execute(editor?: Editor) {} + + public isActive(editor?: Editor) { + return editor && this.manifest?.meta.alias ? editor?.isActive(this.manifest.meta.alias) : false; + } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/umb/code-editor.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/umb/code-editor.extension.ts new file mode 100644 index 0000000000..be55c65dbe --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/umb/code-editor.extension.ts @@ -0,0 +1,28 @@ +import { UmbTiptapToolbarElementApiBase } from '../types.js'; +import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; +import { UMB_CODE_EDITOR_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; + +export default class UmbTiptapCodeEditorExtensionApi extends UmbTiptapToolbarElementApiBase { + getTiptapExtensions = () => []; + + override async execute(editor?: Editor) { + console.log('umb-code-editor.execute', editor); + if (!editor) return; + + const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); + const modal = modalManager.open(this, UMB_CODE_EDITOR_MODAL, { + data: { + headline: 'Edit source code', + content: editor?.getHTML() ?? '', + language: 'html', + }, + }); + + if (!modal) return; + + const data = await modal.onSubmit().catch(() => undefined); + if (!data) return; + + editor?.commands.setContent(data.content, true); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/umb/embed.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/umb/embed.extension.ts new file mode 100644 index 0000000000..f09ecfd084 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/umb/embed.extension.ts @@ -0,0 +1,11 @@ +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 + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/tiptap-mediapicker.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/umb/mediapicker.extension.ts similarity index 84% rename from src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/tiptap-mediapicker.extension.ts rename to src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/umb/mediapicker.extension.ts index 7d96091761..6947d772b8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/tiptap-mediapicker.extension.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/umb/mediapicker.extension.ts @@ -1,14 +1,14 @@ -import { UmbTiptapExtensionApi } from './types.js'; +import { UmbTiptapToolbarElementApiBase } from '../types.js'; import { mergeAttributes, Node } from '@umbraco-cms/backoffice/external/tiptap'; import { UMB_MEDIA_PICKER_MODAL } from '@umbraco-cms/backoffice/media'; import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; -export default class UmbTiptapMediaPickerPlugin extends UmbTiptapExtensionApi { +export default class UmbTiptapMediaPickerExtensionApi extends UmbTiptapToolbarElementApiBase { getTiptapExtensions() { return [ Node.create({ - name: 'umbMediaPicker', + name: 'umb-media', priority: 1000, group: 'block', marks: '', @@ -33,7 +33,9 @@ export default class UmbTiptapMediaPickerPlugin extends UmbTiptapExtensionApi { ]; } - //isActive: (editor?: Editor) => editor?.isActive('umbMediaPicker') || editor?.isActive('image'), + override isActive(editor?: Editor) { + return editor?.isActive('umb-media') === true || editor?.isActive('image') === true; + } override async execute(editor?: Editor) { console.log('umb-media.execute', editor); diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/umb/urlpicker.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/umb/urlpicker.extension.ts new file mode 100644 index 0000000000..7bedab735b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/umb/urlpicker.extension.ts @@ -0,0 +1,27 @@ +import { UmbTiptapToolbarElementApiBase } from '../types.js'; +import { Link } from '@umbraco-cms/backoffice/external/tiptap'; +import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; + +export default class UmbTiptapUrlPickerExtensionApi extends UmbTiptapToolbarElementApiBase { + getTiptapExtensions() { + return [Link.extend({ openOnClick: false })]; + } + + override async execute(editor?: Editor) { + console.log('umb-link.execute', editor); + + const text = prompt('Enter the text'); + const url = prompt('Enter the URL'); + + if (url && text && editor) { + const { from } = editor.state.selection; + editor + .chain() + .focus() + .insertContent(text) + .setTextSelection({ from: from, to: from + text.length }) + .setLink({ href: url, target: '_blank' }) + .run(); + } + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/property-editors/tiptap/property-editor-ui-tiptap.element.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/property-editors/tiptap/property-editor-ui-tiptap.element.ts index 60dedc1240..d1dee314ae 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/property-editors/tiptap/property-editor-ui-tiptap.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/property-editors/tiptap/property-editor-ui-tiptap.element.ts @@ -1,18 +1,13 @@ -import type UmbInputTiptapElement from '../../components/input-tiptap/input-tiptap.element.js'; +import type { UmbInputTiptapElement } from '../../components/input-tiptap/input-tiptap.element.js'; import { customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbBlockRteEntriesContext, UmbBlockRteManagerContext } from '@umbraco-cms/backoffice/block-rte'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { - UmbPropertyValueChangeEvent, - type UmbPropertyEditorConfigCollection, -} from '@umbraco-cms/backoffice/property-editor'; +import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; +import type { UmbBlockRteLayoutModel } from '@umbraco-cms/backoffice/block-rte'; +import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; import '../../components/input-tiptap/input-tiptap.element.js'; -import { - UmbBlockRteEntriesContext, - UmbBlockRteManagerContext, - type UmbBlockRteLayoutModel, -} from '@umbraco-cms/backoffice/block-rte'; import type { UmbBlockValueType } from '@umbraco-cms/backoffice/block'; // Look at Tiny for correct types @@ -23,11 +18,13 @@ export interface UmbRichTextEditorValueType { const UMB_BLOCK_RTE_BLOCK_LAYOUT_ALIAS = 'Umbraco.RichText'; +const elementName = 'umb-property-editor-ui-tiptap'; + /** * @element umb-property-editor-ui-tiptap */ -@customElement('umb-property-editor-ui-tiptap') -export class UmbPropertyEditorUITiptapElement extends UmbLitElement implements UmbPropertyEditorUiElement { +@customElement(elementName) +export class UmbPropertyEditorUiTiptapElement extends UmbLitElement implements UmbPropertyEditorUiElement { // public set config(config: UmbPropertyEditorConfigCollection | undefined) { this._config = config; @@ -110,7 +107,7 @@ export class UmbPropertyEditorUITiptapElement extends UmbLitElement implements U } #onChange(event: CustomEvent & { target: UmbInputTiptapElement }) { - const value = event.target.value as string; + const value = event.target.value; this._latestMarkup = value; // TODO: Validate blocks @@ -148,10 +145,10 @@ export class UmbPropertyEditorUITiptapElement extends UmbLitElement implements U } } -export default UmbPropertyEditorUITiptapElement; +export { UmbPropertyEditorUiTiptapElement as element }; declare global { interface HTMLElementTagNameMap { - 'umb-property-editor-ui-tiptap': UmbPropertyEditorUITiptapElement; + [elementName]: UmbPropertyEditorUiTiptapElement; } }