diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts index 651f386b2f..111ea1a4a2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts @@ -5,12 +5,15 @@ import type { UmbTiptapToolbarRowViewModel, } from '../types.js'; import type { UmbTiptapToolbarValue } from '../../../components/types.js'; +import type { UmbTiptapToolbarGroupConfigurationElement } from './tiptap-toolbar-group-configuration.element.js'; import { customElement, css, html, property, repeat, state, when, nothing } from '@umbraco-cms/backoffice/external/lit'; import { debounce } from '@umbraco-cms/backoffice/utils'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property'; import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/property-editor'; +import './tiptap-toolbar-group-configuration.element.js'; + @customElement('umb-property-editor-ui-tiptap-toolbar-configuration') export class UmbPropertyEditorUiTiptapToolbarConfigurationElement extends UmbLitElement @@ -74,7 +77,14 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement this.#initialized = true; } - #onClick(item: UmbTiptapToolbarExtension) { + #onChangeToolbarGroup(event: CustomEvent & { target: UmbTiptapToolbarGroupConfigurationElement }) { + event.stopPropagation(); + const element = event.target; + const aliases = element.items.map((item) => item.alias); + this.#context.updateToolbarItem(aliases, [element.rowIndex, element.groupIndex]); + } + + #onClickAvailableItem(item: UmbTiptapToolbarExtension) { const lastRow = (this.#value?.length ?? 1) - 1; const lastGroup = (this.#value?.[lastRow].length ?? 1) - 1; const lastItem = this.#value?.[lastRow][lastGroup].length ?? 0; @@ -128,6 +138,11 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement this.#debouncedFilter(query); } + #onRemoveToolbarItem(event: CustomEvent) { + const { groupIndex, index, rowIndex } = event.detail; + this.#context?.removeToolbarItem([rowIndex, groupIndex, index]); + } + override render() { return html`${this.#renderDesigner()} ${this.#renderAvailableItems()}`; } @@ -173,7 +188,7 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement label=${label} look=${forbidden ? 'placeholder' : 'outline'} ?disabled=${forbidden || inUse} - @click=${() => this.#onClick(item)} + @click=${() => this.#onClickAvailableItem(item)} @dragstart=${(e: DragEvent) => this.#onDragStart(e, item.alias)} @dragend=${this.#onDragEnd}>
@@ -242,19 +257,22 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement #renderGroup(group?: UmbTiptapToolbarGroupViewModel, rowIndex = 0, groupIndex = 0) { if (!group) return nothing; const showActionBar = this._toolbar[rowIndex].data.length > 1 && group.data.length === 0; + const items: UmbTiptapToolbarExtension[] = group!.data + .map((alias) => this.#context?.getExtensionByAlias(alias)) + .filter((item): item is UmbTiptapToolbarExtension => !!item); return html`
this.#onDrop(e, [rowIndex, groupIndex, group.data.length - 1])}> -
- ${when( - group?.data.length === 0, - () => html`Empty`, - () => html`${group!.data.map((alias, idx) => this.#renderItem(alias, rowIndex, groupIndex, idx))}`, - )} -
+ + ${when( showActionBar, () => html` @@ -276,62 +294,6 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement `; } - #renderItem(alias: string, rowIndex = 0, groupIndex = 0, itemIndex = 0) { - const item = this.#context?.getExtensionByAlias(alias); - if (!item) return nothing; - - const forbidden = !this.#context?.isExtensionEnabled(item.alias); - const label = this.localize.string(item.label); - - switch (item.kind) { - case 'styleMenu': - case 'menu': - return html` - this.#context.removeToolbarItem([rowIndex, groupIndex, itemIndex])} - @dragend=${this.#onDragEnd} - @dragstart=${(e: DragEvent) => this.#onDragStart(e, alias, [rowIndex, groupIndex, itemIndex])}> -
- ${label} -
- -
- `; - - case 'button': - default: - return html` - this.#context.removeToolbarItem([rowIndex, groupIndex, itemIndex])} - @dragend=${this.#onDragEnd} - @dragstart=${(e: DragEvent) => this.#onDragStart(e, alias, [rowIndex, groupIndex, itemIndex])}> -
- ${when( - item.icon, - () => html``, - () => html`${label}`, - )} -
-
- `; - } - } - static override readonly styles = [ css` :host { @@ -468,45 +430,6 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement opacity: 1; } } - - .items { - display: flex; - flex-direction: row; - flex-wrap: wrap; - gap: var(--uui-size-1); - - uui-button { - --uui-button-font-weight: normal; - - &[draggable='true'], - &[draggable='true'] > .inner { - cursor: move; - } - - &[disabled], - &[disabled] > .inner { - cursor: not-allowed; - } - - &.forbidden { - --color: var(--uui-color-danger); - --color-standalone: var(--uui-color-danger-standalone); - --color-emphasis: var(--uui-color-danger-emphasis); - --color-contrast: var(--uui-color-danger); - --uui-button-contrast-disabled: var(--uui-color-danger); - --uui-button-border-color-disabled: var(--uui-color-danger); - } - - div { - display: flex; - gap: var(--uui-size-1); - } - - uui-symbol-expand { - margin-left: var(--uui-size-space-2); - } - } - } } } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/tiptap-toolbar-group-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/tiptap-toolbar-group-configuration.element.ts new file mode 100644 index 0000000000..0ec24d6fd6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/tiptap-toolbar-group-configuration.element.ts @@ -0,0 +1,177 @@ +import { UmbTiptapToolbarConfigurationContext } from '../contexts/tiptap-toolbar-configuration.context.js'; +import type { UmbTiptapToolbarExtension } from '../types.js'; +import { css, customElement, html, property, repeat, when } from '@umbraco-cms/backoffice/external/lit'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UmbSorterController, UmbSorterResolvePlacementAsGrid } from '@umbraco-cms/backoffice/sorter'; + +@customElement('umb-tiptap-toolbar-group-configuration') +export class UmbTiptapToolbarGroupConfigurationElement< + TiptapToolbarItem extends UmbTiptapToolbarExtension = UmbTiptapToolbarExtension, +> extends UmbLitElement { + #sorter = new UmbSorterController(this, { + getUniqueOfElement: (element) => element.getAttribute('tiptap-toolbar-alias'), + getUniqueOfModel: (modelEntry) => modelEntry.alias!, + itemSelector: 'uui-button', + identifier: 'umb-tiptap-toolbar-sorter', + containerSelector: '.items', + resolvePlacement: UmbSorterResolvePlacementAsGrid, + onContainerChange: ({ item, model }) => { + this.dispatchEvent(new CustomEvent('container-change', { detail: { item, model } })); + }, + onChange: ({ model }) => { + this.#items = model; + this.requestUpdate(); + this.dispatchEvent(new UmbChangeEvent()); + }, + }); + + #context = new UmbTiptapToolbarConfigurationContext(this); + + @property({ type: Array, attribute: false }) + public set items(value: Array | undefined) { + this.#items = (value ?? []).filter((item, index, self) => self.findIndex((x) => x.alias === item.alias) === index); + this.#sorter.setModel(this.#items); + } + public get items(): Array { + return this.#items; + } + #items: Array = []; + + @property({ type: Number }) + rowIndex = 0; + + @property({ type: Number }) + groupIndex = 0; + + #onRequestRemove(item: TiptapToolbarItem, index = 0) { + this.items = this.items.filter((x) => x.alias !== item.alias); + const rowIndex = this.rowIndex; + const groupIndex = this.groupIndex; + this.dispatchEvent( + new CustomEvent('remove', { detail: { rowIndex, groupIndex, index }, bubbles: true, composed: true }), + ); + } + + override render() { + return html` +
+ ${when( + this.items?.length === 0, + () => html`Empty`, + () => + repeat( + this.items, + (item) => item.alias, + (item, index) => this.#renderItem(item, index), + ), + )} +
+ `; + } + + #renderItem(item: TiptapToolbarItem, index = 0) { + const label = this.localize.string(item.label); + const forbidden = !this.#context?.isExtensionEnabled(item.alias); + + switch (item.kind) { + case 'styleMenu': + case 'menu': + return html` + this.#onRequestRemove(item, index)}> +
+ ${label} +
+ +
+ `; + + case 'button': + default: + return html` + this.#onRequestRemove(item, index)}> +
+ ${when( + item.icon, + () => html``, + () => html`${label}`, + )} +
+
+ `; + } + } + + static override styles = [ + css` + .items { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: var(--uui-size-1); + + uui-button { + --uui-button-font-weight: normal; + + &[draggable='true'], + &[draggable='true'] > .inner { + cursor: move; + } + + &[disabled], + &[disabled] > .inner { + cursor: not-allowed; + } + + &.forbidden { + --color: var(--uui-color-danger); + --color-standalone: var(--uui-color-danger-standalone); + --color-emphasis: var(--uui-color-danger-emphasis); + --color-contrast: var(--uui-color-danger); + --uui-button-contrast-disabled: var(--uui-color-danger); + --uui-button-border-color-disabled: var(--uui-color-danger); + } + + div { + display: flex; + gap: var(--uui-size-1); + } + + uui-symbol-expand { + margin-left: var(--uui-size-space-2); + } + } + } + + uui-button[look='outline'] { + --uui-button-background-color-hover: var(--uui-color-surface); + } + `, + ]; +} + +export default UmbTiptapToolbarGroupConfigurationElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-tiptap-toolbar-group-configuration': UmbTiptapToolbarGroupConfigurationElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/contexts/tiptap-toolbar-configuration.context.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/contexts/tiptap-toolbar-configuration.context.ts index 646c6c32d9..c758839d3d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/contexts/tiptap-toolbar-configuration.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/contexts/tiptap-toolbar-configuration.context.ts @@ -240,6 +240,27 @@ export class UmbTiptapToolbarConfigurationContext extends UmbContextBase { this.#toolbar.setValue(toolbar); } + public updateToolbarItem(aliases: Array, to: [number, number]) { + const toolbar = [...this.#toolbar.getValue()]; + const [rowIndex, groupIndex] = to; + + const newToolbar = toolbar.map((row, rIdx) => { + if (rIdx !== rowIndex) return row; + return { + ...row, + data: row.data.map((group, gIdx) => { + if (gIdx !== groupIndex) return group; + return { + ...group, + data: [...aliases], + }; + }), + }; + }); + + this.#toolbar.setValue(newToolbar); + } + public updateToolbarRow(rowIndex: number, groups: Array) { const toolbar = [...this.#toolbar.getValue()];