Refactored default Tiptap toolbar configuration

Removed the `isDefault` property, replaced with property-editor's `defaultData`.
This commit is contained in:
leekelleher
2024-09-30 17:00:18 +01:00
parent 1e63126c82
commit fd69b63cdf
6 changed files with 130 additions and 132 deletions

View File

@@ -44,7 +44,6 @@ const umbToolbarExtensions: Array<ManifestTiptapToolbarExtension | ManifestTipta
alias: 'umbLink',
icon: 'icon-link',
label: '#defaultdialogs_urlLinkPicker',
isDefault: true,
},
},
{
@@ -58,7 +57,6 @@ const umbToolbarExtensions: Array<ManifestTiptapToolbarExtension | ManifestTipta
alias: 'umbMedia',
icon: 'icon-picture',
label: 'Media picker',
isDefault: true,
},
},
{

View File

@@ -13,7 +13,6 @@ export interface MetaTiptapToolbarExtension {
alias: string;
icon: string;
label: string;
isDefault?: boolean;
}
export interface ManifestTiptapToolbarExtensionButtonKind<

View File

@@ -15,7 +15,6 @@ export const manifests: Array<ManifestTiptapToolbarExtension | ManifestTiptapToo
alias: 'blockquote',
icon: 'icon-blockquote',
label: 'Blockquote',
isDefault: true,
},
},
{
@@ -29,7 +28,6 @@ export const manifests: Array<ManifestTiptapToolbarExtension | ManifestTiptapToo
alias: 'bold',
icon: 'icon-bold',
label: 'Bold',
isDefault: true,
},
},
{
@@ -43,7 +41,6 @@ export const manifests: Array<ManifestTiptapToolbarExtension | ManifestTiptapToo
alias: 'codeBlock',
icon: 'icon-code',
label: 'Code Block',
isDefault: true,
},
},
{
@@ -57,7 +54,6 @@ export const manifests: Array<ManifestTiptapToolbarExtension | ManifestTiptapToo
alias: 'bulletList',
icon: 'icon-bulleted-list',
label: 'Bullet List',
isDefault: true,
},
},
{
@@ -71,7 +67,6 @@ export const manifests: Array<ManifestTiptapToolbarExtension | ManifestTiptapToo
alias: 'orderedList',
icon: 'icon-ordered-list',
label: 'Ordered List',
isDefault: true,
},
},
{
@@ -99,7 +94,6 @@ export const manifests: Array<ManifestTiptapToolbarExtension | ManifestTiptapToo
alias: 'strike',
icon: 'icon-strikethrough',
label: 'Strike',
isDefault: true,
},
},
{
@@ -113,7 +107,6 @@ export const manifests: Array<ManifestTiptapToolbarExtension | ManifestTiptapToo
alias: 'subscript',
icon: 'icon-subscript',
label: 'Subscript',
isDefault: true,
},
},
{
@@ -127,7 +120,6 @@ export const manifests: Array<ManifestTiptapToolbarExtension | ManifestTiptapToo
alias: 'superscript',
icon: 'icon-superscript',
label: 'Superscript',
isDefault: true,
},
},
{
@@ -154,7 +146,6 @@ export const manifests: Array<ManifestTiptapToolbarExtension | ManifestTiptapToo
alias: 'heading1',
icon: 'icon-heading-1',
label: 'Heading 1',
isDefault: true,
},
},
{
@@ -168,7 +159,6 @@ export const manifests: Array<ManifestTiptapToolbarExtension | ManifestTiptapToo
alias: 'heading2',
icon: 'icon-heading-2',
label: 'Heading 2',
isDefault: true,
},
},
{
@@ -182,7 +172,6 @@ export const manifests: Array<ManifestTiptapToolbarExtension | ManifestTiptapToo
alias: 'heading3',
icon: 'icon-heading-3',
label: 'Heading 3',
isDefault: true,
},
},
{
@@ -209,7 +198,6 @@ export const manifests: Array<ManifestTiptapToolbarExtension | ManifestTiptapToo
alias: 'italic',
icon: 'icon-italic',
label: 'Italic',
isDefault: true,
},
},
{
@@ -223,7 +211,6 @@ export const manifests: Array<ManifestTiptapToolbarExtension | ManifestTiptapToo
alias: 'text-align-center',
icon: 'icon-text-align-center',
label: 'Text Align Center',
isDefault: true,
},
},
{
@@ -250,7 +237,6 @@ export const manifests: Array<ManifestTiptapToolbarExtension | ManifestTiptapToo
alias: 'text-align-left',
icon: 'icon-text-align-left',
label: 'Text Align Left',
isDefault: true,
},
},
{
@@ -264,7 +250,6 @@ export const manifests: Array<ManifestTiptapToolbarExtension | ManifestTiptapToo
alias: 'text-align-right',
icon: 'icon-text-align-right',
label: 'Text Align Right',
isDefault: true,
},
},
{
@@ -278,7 +263,6 @@ export const manifests: Array<ManifestTiptapToolbarExtension | ManifestTiptapToo
alias: 'underline',
icon: 'icon-underline',
label: 'Underline',
isDefault: true,
},
},
{
@@ -307,7 +291,6 @@ export const manifests: Array<ManifestTiptapToolbarExtension | ManifestTiptapToo
alias: 'unlink',
icon: 'icon-unlink',
label: 'Unlink',
isDefault: true,
},
},
];

View File

@@ -9,48 +9,43 @@ import {
type UmbPropertyEditorUiElement,
} from '@umbraco-cms/backoffice/property-editor';
type ExtensionConfig = {
type UmbTiptapExtensionConfig = {
alias: string;
label: string;
icon?: string;
group: string;
};
type ExtensionGroupItem = {
type UmbTiptapExtensionGroupItem = {
alias: string;
label: string;
icon?: string;
selected: boolean;
};
type ExtensionGroup = {
type UmbTiptapExtensionGroup = {
group: string;
extensions: ExtensionGroupItem[];
extensions: UmbTiptapExtensionGroupItem[];
};
@customElement('umb-property-editor-ui-tiptap-extensions-configuration')
const elementName = 'umb-property-editor-ui-tiptap-extensions-configuration';
@customElement(elementName)
export class UmbPropertyEditorUiTiptapExtensionsConfigurationElement
extends UmbLitElement
implements UmbPropertyEditorUiElement
{
@property({ attribute: false })
set value(value: string[] | undefined) {
this.#value = value;
}
get value(): string[] | undefined {
return this.#value;
}
#value?: string[] = [];
value?: Array<string> = [];
@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<unknown>) {
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;
}
}

View File

@@ -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<string> = new Set();
#currentDragItem?: {
alias: string;
fromPos?: [number, number, number];
};
#lookup?: Map<string, UmbTiptapToolbarExtension>;
@state()
private _extensions: Array<UmbTiptapToolbarExtension> = [];
@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<unknown>) {
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`
<div
title=${extension.label}
class="item"
draggable="true"
@dragend=${this.#onDragEnd}
@dragstart=${(e: DragEvent) => this.#onDragStart(e, alias, [rowIndex, groupIndex, itemIndex])}>
<umb-icon name=${extension.icon ?? ''}></umb-icon>
${repeat(this.#value, (row, rowIndex) => this.#renderRow(row, rowIndex))}
<uui-button look="secondary" @click=${() => this.#addRow(this.#value.length)}>
<uui-icon name="add"></uui-icon>
<span>Add row</span>
</uui-button>
${this.#renderExtensions()}
`;
}
#renderRow(row: string[][], rowIndex: number) {
return html`
<div class="row">
${repeat(row, (group, groupIndex) => this.#renderGroup(group, rowIndex, groupIndex))}
<uui-button look="secondary" @click=${() => this.#addGroup(rowIndex, row.length)}>
<uui-icon name="add"></uui-icon>
<span>Add group</span>
</uui-button>
<uui-button
compact
color="danger"
look="primary"
class="remove-row-button ${rowIndex === 0 && row.length === 1 && row[0].length === 0 ? 'hidden' : undefined}"
@click=${() => this.#removeRow(rowIndex)}>
<umb-icon name="icon-trash"></umb-icon>
</uui-button>
</div>
`;
}
@@ -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))}
<uui-button
look="primary"
color="danger"
compact
color="danger"
look="primary"
class="remove-group-button ${groupIndex === 0 && group.length === 0 ? 'hidden' : undefined}"
@click=${() => this.#removeGroup(rowIndex, groupIndex)}>
<umb-icon name="icon-trash"></umb-icon>
@@ -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`
<div class="row">
${repeat(row, (group, groupIndex) => this.#renderGroup(group, rowIndex, groupIndex))}
<uui-button look="secondary" @click=${() => this.#addGroup(rowIndex, row.length)}>+</uui-button>
<uui-button
look="primary"
color="danger"
compact
class="remove-row-button ${rowIndex === 0 && row.length === 1 && row[0].length === 0 ? 'hidden' : undefined}"
@click=${() => this.#removeRow(rowIndex)}>
<umb-icon name="icon-trash"></umb-icon>
</uui-button>
<div
title=${extension.label}
class="item"
draggable="true"
@dragend=${this.#onDragEnd}
@dragstart=${(e: DragEvent) => this.#onDragStart(e, alias, [rowIndex, groupIndex, itemIndex])}>
<umb-icon name=${extension.icon ?? ''}></umb-icon>
</div>
`;
}
override render() {
return html`
${repeat(this.#value, (row, rowIndex) => this.#renderRow(row, rowIndex))}
<uui-button look="secondary" @click=${() => this.#addRow(this.#value.length)}>+</uui-button>
${this.#renderExtensions()}
`;
}
#renderExtensions() {
// TODO: Can we avoid using a flat here? or is it okay for performance?
return html`
<div class="extensions" dropzone="move" @drop=${this.#onDrop} @dragover=${this.#onDragOver}>
${repeat(
this._extensions.filter((ext) => !this.#value.flat(2).includes(ext.alias)),
this._extensions.filter((ext) => !this.#inUse.has(ext.alias)),
(extension) => html`
<div
title=${extension.label}
class="item"
draggable="true"
@dragend=${this.#onDragEnd}
@dragstart=${(e: DragEvent) => this.#onDragStart(e, extension.alias)}>
title=${this.localize.string(extension.label)}
@dragstart=${(e: DragEvent) => this.#onDragStart(e, extension.alias)}
@dragend=${this.#onDragEnd}>
<umb-icon name=${extension.icon ?? ''}></umb-icon>
</div>
`,
@@ -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;
}
}

View File

@@ -50,6 +50,24 @@ export const manifests: Array<ManifestPropertyEditorUi> = [
},
],
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' },
],