From fd69b63cdfb59ab90aa70ed546aa656a43408ccf Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 30 Sep 2024 17:00:18 +0100 Subject: [PATCH] Refactored default Tiptap toolbar configuration Removed the `isDefault` property, replaced with property-editor's `defaultData`. --- .../rte/tiptap/extensions/manifests.ts | 2 - .../extensions/tiptap-toolbar-extension.ts | 1 - .../tiptap/extensions/toolbar/manifests.ts | 17 -- ...tiptap-extensions-configuration.element.ts | 39 ++-- ...ui-tiptap-toolbar-configuration.element.ts | 185 +++++++++--------- .../property-editors/tiptap/manifests.ts | 18 ++ 6 files changed, 130 insertions(+), 132 deletions(-) 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 02fdd6dcb8..c5daba1fa6 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 @@ -44,7 +44,6 @@ const umbToolbarExtensions: Array = []; @property({ attribute: false }) config?: UmbPropertyEditorConfigCollection; @state() - private _extensionCategories: ExtensionGroup[] = []; + private _extensionCategories: UmbTiptapExtensionGroup[] = []; @state() - private _extensionConfigs: ExtensionConfig[] = []; + private _extensionConfigs: UmbTiptapExtensionConfig[] = []; protected override async firstUpdated(_changedProperties: PropertyValueMap) { super.firstUpdated(_changedProperties); @@ -69,7 +64,7 @@ export class UmbPropertyEditorUiTiptapExtensionsConfigurationElement if (!this.value) { // The default value is all extensions enabled - this.#value = this._extensionConfigs.map((ext) => ext.alias); + this.value = this._extensionConfigs.map((ext) => ext.alias); this.dispatchEvent(new UmbPropertyValueChangeEvent()); } @@ -88,7 +83,7 @@ export class UmbPropertyEditorUiTiptapExtensionsConfigurationElement // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error - const grouped = Object.groupBy(withSelectedProperty, (item: ExtensionConfig) => item.group || 'Uncategorized'); + const grouped = Object.groupBy(withSelectedProperty, (item: UmbTiptapExtensionConfig) => item.group || 'Uncategorized'); this._extensionCategories = Object.keys(grouped) .sort((a, b) => a.localeCompare(b)) @@ -98,7 +93,7 @@ export class UmbPropertyEditorUiTiptapExtensionsConfigurationElement })); } - #onExtensionClick(item: ExtensionGroupItem) { + #onExtensionClick(item: UmbTiptapExtensionGroupItem) { item.selected = !item.selected; if (!this.value) { @@ -106,9 +101,9 @@ export class UmbPropertyEditorUiTiptapExtensionsConfigurationElement } if (item.selected) { - this.#value = [...this.value, item.alias]; + this.value = [...this.value, item.alias]; } else { - this.#value = this.value.filter((alias) => alias !== item.alias); + this.value = this.value.filter((alias) => alias !== item.alias); } this.requestUpdate('_extensionCategories'); @@ -203,10 +198,10 @@ export class UmbPropertyEditorUiTiptapExtensionsConfigurationElement ]; } -export default UmbPropertyEditorUiTiptapExtensionsConfigurationElement; +export { UmbPropertyEditorUiTiptapExtensionsConfigurationElement as element }; declare global { interface HTMLElementTagNameMap { - 'umb-property-editor-ui-tiptap-extensions-configuration': UmbPropertyEditorUiTiptapExtensionsConfigurationElement; + [elementName]: UmbPropertyEditorUiTiptapExtensionsConfigurationElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/property-editors/property-editor-ui-tiptap-toolbar-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/property-editors/property-editor-ui-tiptap-toolbar-configuration.element.ts index 7e28112c18..19d3a5d9a4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/property-editors/property-editor-ui-tiptap-toolbar-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/property-editors/property-editor-ui-tiptap-toolbar-configuration.element.ts @@ -6,64 +6,52 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbPropertyValueChangeEvent, type UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/property-editor'; import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; -type Extension = { +type UmbTiptapToolbarExtension = { alias: string; label: string; icon: string; }; +const elementName = 'umb-property-editor-ui-tiptap-toolbar-configuration'; -@customElement('umb-property-editor-ui-tiptap-toolbar-configuration') +@customElement(elementName) export class UmbPropertyEditorUiTiptapToolbarConfigurationElement extends UmbLitElement implements UmbPropertyEditorUiElement { - @property({ attribute: false }) - set value(value: UmbTiptapToolbarValue | undefined) { - if (!value) { - this.#useDefault = true; - this.#value = [[[]]]; - return; - } - - // TODO: This can be optimized with cashing; - this.#value = value.map((rows) => rows.map((groups) => [...groups])); - } - - get value(): UmbTiptapToolbarValue { - // TODO: This can be optimized with cashing; - return this.#value.map((rows) => rows.map((groups) => [...groups])); - } - - #useDefault = false; - - #value: UmbTiptapToolbarValue = [[[]]]; - - @state() - _extensions: Extension[] = []; + #inUse: Set = new Set(); #currentDragItem?: { alias: string; fromPos?: [number, number, number]; }; + #lookup?: Map; + + @state() + private _extensions: Array = []; + + @property({ attribute: false }) + set value(value: UmbTiptapToolbarValue | undefined) { + if (!value) { + this.#value = [[[]]]; + } else { + // TODO: This can be optimized with cashing; + this.#value = value ? value.map((rows) => rows.map((groups) => [...groups])) : [[[]]]; + value.forEach((row) => row.forEach((group) => group.forEach((alias) => this.#inUse.add(alias)))); + } + } + get value(): UmbTiptapToolbarValue { + // TODO: This can be optimized with cashing; + return this.#value.map((rows) => rows.map((groups) => [...groups])); + } + #value: UmbTiptapToolbarValue = [[[]]]; + protected override async firstUpdated(_changedProperties: PropertyValueMap) { super.firstUpdated(_changedProperties); this.observe(umbExtensionsRegistry.byType('tiptapToolbarExtension'), (extensions) => { - this._extensions = extensions.map((ext) => { - if (this.#useDefault && ext.meta.isDefault) { - this.#value[0][0].push(ext.alias); - } - return { - alias: ext.alias, - label: ext.meta.label, - icon: ext.meta.icon, - }; - }); - - if (this.#useDefault) { - this.dispatchEvent(new UmbPropertyValueChangeEvent()); - } + this._extensions = extensions.map((ext) => ({ alias: ext.alias, label: ext.meta.label, icon: ext.meta.icon })); + this.#lookup = new Map(this._extensions.map((ext) => [ext.alias, ext])); }); } @@ -121,15 +109,19 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement #insertItem = (alias: string, toPos: [number, number, number]) => { const [rowIndex, groupIndex, itemIndex] = toPos; + // Insert the item into the new position - this.#value[rowIndex][groupIndex].splice(itemIndex, 0, alias); + const inserted = this.#value[rowIndex][groupIndex].splice(itemIndex, 0, alias); + inserted.forEach((alias) => this.#inUse.add(alias)); this.dispatchEvent(new UmbPropertyValueChangeEvent()); }; #removeItem(from: [number, number, number]) { const [rowIndex, groupIndex, itemIndex] = from; - this.#value[rowIndex][groupIndex].splice(itemIndex, 1); + + const removed = this.#value[rowIndex][groupIndex].splice(itemIndex, 1); + removed.forEach((alias) => this.#inUse.delete(alias)); this.dispatchEvent(new UmbPropertyValueChangeEvent()); } @@ -140,12 +132,16 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement }; #removeGroup = (rowIndex: number, groupIndex: number) => { - if (groupIndex === 0) { - // Prevent removing the last group - this.#value[rowIndex][groupIndex] = []; - } else { - this.#value[rowIndex].splice(groupIndex, 1); + if (this.#value[rowIndex].length > groupIndex) { + const removed = this.#value[rowIndex].splice(groupIndex, 1); + removed.forEach((group) => group.forEach((alias) => this.#inUse.delete(alias))); } + + // Prevent leaving an empty group + if (this.#value[rowIndex].length === 0) { + this.#value[rowIndex][groupIndex] = []; + } + this.dispatchEvent(new UmbPropertyValueChangeEvent()); }; @@ -155,26 +151,46 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement }; #removeRow = (rowIndex: number) => { - if (rowIndex === 0) { - // Prevent removing the last row - this.#value[rowIndex] = [[]]; - } else { - this.#value.splice(rowIndex, 1); + if (this.#value.length > rowIndex) { + const removed = this.#value.splice(rowIndex, 1); + removed.forEach((row) => row.forEach((group) => group.forEach((alias) => this.#inUse.delete(alias)))); } + + // Prevent leaving an empty row + if (this.#value.length === 0) { + this.#value[rowIndex] = [[]]; + } + this.dispatchEvent(new UmbPropertyValueChangeEvent()); }; - #renderItem(alias: string, rowIndex: number, groupIndex: number, itemIndex: number) { - const extension = this._extensions.find((ext) => ext.alias === alias); - if (!extension) return nothing; + override render() { return html` -
this.#onDragStart(e, alias, [rowIndex, groupIndex, itemIndex])}> - + ${repeat(this.#value, (row, rowIndex) => this.#renderRow(row, rowIndex))} + this.#addRow(this.#value.length)}> + + Add row + + ${this.#renderExtensions()} + `; + } + + #renderRow(row: string[][], rowIndex: number) { + return html` +
+ ${repeat(row, (group, groupIndex) => this.#renderGroup(group, rowIndex, groupIndex))} + this.#addGroup(rowIndex, row.length)}> + + Add group + + this.#removeRow(rowIndex)}> + +
`; } @@ -188,9 +204,9 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement @drop=${(e: DragEvent) => this.#onDrop(e, [rowIndex, groupIndex, group.length])}> ${group.map((alias, itemIndex) => this.#renderItem(alias, rowIndex, groupIndex, itemIndex))} this.#removeGroup(rowIndex, groupIndex)}> @@ -199,44 +215,33 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement `; } - #renderRow(row: string[][], rowIndex: number) { + #renderItem(alias: string, rowIndex: number, groupIndex: number, itemIndex: number) { + const extension = this.#lookup?.get(alias); + if (!extension) return nothing; return html` -
- ${repeat(row, (group, groupIndex) => this.#renderGroup(group, rowIndex, groupIndex))} - this.#addGroup(rowIndex, row.length)}>+ - this.#removeRow(rowIndex)}> - - +
this.#onDragStart(e, alias, [rowIndex, groupIndex, itemIndex])}> +
`; } - override render() { - return html` - ${repeat(this.#value, (row, rowIndex) => this.#renderRow(row, rowIndex))} - this.#addRow(this.#value.length)}>+ - ${this.#renderExtensions()} - `; - } - #renderExtensions() { - // TODO: Can we avoid using a flat here? or is it okay for performance? return html`
${repeat( - this._extensions.filter((ext) => !this.#value.flat(2).includes(ext.alias)), + this._extensions.filter((ext) => !this.#inUse.has(ext.alias)), (extension) => html`
this.#onDragStart(e, extension.alias)}> + title=${this.localize.string(extension.label)} + @dragstart=${(e: DragEvent) => this.#onDragStart(e, extension.alias)} + @dragend=${this.#onDragEnd}>
`, @@ -315,10 +320,10 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement ]; } -export default UmbPropertyEditorUiTiptapToolbarConfigurationElement; +export { UmbPropertyEditorUiTiptapToolbarConfigurationElement as element }; declare global { interface HTMLElementTagNameMap { - 'umb-property-editor-ui-tiptap-toolbar-configuration': UmbPropertyEditorUiTiptapToolbarConfigurationElement; + [elementName]: UmbPropertyEditorUiTiptapToolbarConfigurationElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/property-editors/tiptap/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/property-editors/tiptap/manifests.ts index 67edb85388..e3adef4605 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/property-editors/tiptap/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/property-editors/tiptap/manifests.ts @@ -50,6 +50,24 @@ export const manifests: Array = [ }, ], defaultData: [ + { + alias: 'toolbar', + value: [ + [ + ['Umb.Tiptap.Toolbar.CodeEditor'], + ['Umb.Tiptap.Toolbar.Bold', 'Umb.Tiptap.Toolbar.Italic', 'Umb.Tiptap.Toolbar.Underline'], + [ + 'Umb.Tiptap.Toolbar.TextAlignLeft', + 'Umb.Tiptap.Toolbar.TextAlignCenter', + 'Umb.Tiptap.Toolbar.TextAlignRight', + ], + ['Umb.Tiptap.Toolbar.BulletList', 'Umb.Tiptap.Toolbar.OrderedList'], + ['Umb.Tiptap.Toolbar.Blockquote', 'Umb.Tiptap.Toolbar.HorizontalRule'], + ['Umb.Tiptap.Toolbar.Link', 'Umb.Tiptap.Toolbar.Unlink'], + ['Umb.Tiptap.Toolbar.MediaPicker', 'Umb.Tiptap.Toolbar.Embed'], + ], + ], + }, { alias: 'maxImageSize', value: 500 }, { alias: 'overlaySize', value: 'medium' }, ],