Merge branch 'v15/feature/tiptap' into v15/feature/rte/tiptap/toolbar-manifest-tweaks

This commit is contained in:
Jacob Overgaard
2024-09-30 15:09:58 +02:00
committed by GitHub
11 changed files with 159 additions and 68 deletions

View File

@@ -95,7 +95,7 @@ export abstract class UmbBlockEntriesContext<
layoutEntry: BlockLayoutType,
content: UmbBlockDataModel,
settings: UmbBlockDataModel | undefined,
originData: UmbBlockWorkspaceOriginData,
originData: BlockOriginData,
): Promise<boolean>;
//edit?
//editSettings

View File

@@ -91,6 +91,9 @@ export abstract class UmbBlockManagerContext<
setContents(contents: Array<UmbBlockDataModel>) {
this.#contents.setValue(contents);
}
getContents() {
return this.#contents.value;
}
setSettings(settings: Array<UmbBlockDataModel>) {
this.#settings.setValue(settings);
}

View File

@@ -12,8 +12,6 @@ import {
type UmbBlockRteTypeModel,
} from '@umbraco-cms/backoffice/block-rte';
import { UMB_PROPERTY_CONTEXT, UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property';
import { observeMultiple } from '@umbraco-cms/backoffice/observable-api';
import { debounceTime } from '@umbraco-cms/backoffice/external/rxjs';
export abstract class UmbRteBaseElement extends UmbLitElement implements UmbPropertyEditorUiElement {
public set config(config: UmbPropertyEditorConfigCollection | undefined) {
@@ -27,13 +25,6 @@ export abstract class UmbRteBaseElement extends UmbLitElement implements UmbProp
this.#managerContext.setEditorConfiguration(config);
}
/**
* Sets the input to readonly mode, meaning value cannot be changed but still able to read and select its content.
* @default false
*/
@property({ type: Boolean, reflect: true })
readonly = false;
@property({
attribute: false,
type: Object,
@@ -45,6 +36,7 @@ export abstract class UmbRteBaseElement extends UmbLitElement implements UmbProp
const buildUpValue: Partial<UmbPropertyEditorUiValueType> = value ? { ...value } : {};
buildUpValue.markup ??= '';
buildUpValue.blocks ??= { layout: {}, contentData: [], settingsData: [], expose: [] };
buildUpValue.blocks.layout ??= {};
buildUpValue.blocks.contentData ??= [];
buildUpValue.blocks.settingsData ??= [];
buildUpValue.blocks.expose ??= [];
@@ -64,6 +56,13 @@ export abstract class UmbRteBaseElement extends UmbLitElement implements UmbProp
return this._value;
}
/**
* Sets the input to readonly mode, meaning value cannot be changed but still able to read and select its content.
* @default false
*/
@property({ type: Boolean, reflect: true })
readonly = false;
@state()
protected _config?: UmbPropertyEditorConfigCollection;
@@ -119,7 +118,34 @@ export abstract class UmbRteBaseElement extends UmbLitElement implements UmbProp
'observePropertyAlias',
);
this.observe(
this.observe(this.#entriesContext.layoutEntries, (layouts) => {
// Update manager:
this.#managerContext.setLayouts(layouts);
});
// Observe the value of the property and update the editor value.
this.observe(this.#managerContext.layouts, (layouts) => {
this._value = {
...this._value,
blocks: { ...this._value.blocks, layout: { [UMB_BLOCK_RTE_PROPERTY_EDITOR_SCHEMA_ALIAS]: layouts } },
};
this._fireChangeEvent();
});
this.observe(this.#managerContext.contents, (contents) => {
this._value = { ...this._value, blocks: { ...this._value.blocks, contentData: contents } };
this._fireChangeEvent();
});
this.observe(this.#managerContext.settings, (settings) => {
this._value = { ...this._value, blocks: { ...this._value.blocks, settingsData: settings } };
this._fireChangeEvent();
});
this.observe(this.#managerContext.exposes, (exposes) => {
this._value = { ...this._value, blocks: { ...this._value.blocks, expose: exposes } };
this._fireChangeEvent();
});
// The above could potentially be replaced with a single observeMultiple call, but it is not done for now to avoid potential issues with the order of the updates.
/*this.observe(
observeMultiple([
this.#managerContext.layouts,
this.#managerContext.contents,
@@ -140,7 +166,7 @@ export abstract class UmbRteBaseElement extends UmbLitElement implements UmbProp
this._fireChangeEvent();
},
'motherObserver',
);
);*/
});
this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, (context) => {
this.#managerContext.setVariantId(context.getVariantId());
@@ -153,6 +179,10 @@ export abstract class UmbRteBaseElement extends UmbLitElement implements UmbProp
}
protected _filterUnusedBlocks(usedContentKeys: (string | null)[]) {
const unusedBlockContents = this.#managerContext.getContents().filter((x) => usedContentKeys.indexOf(x.key) === -1);
unusedBlockContents.forEach((blockContent) => {
this.#managerContext.removeOneContent(blockContent.key);
});
const unusedBlocks = this.#managerContext.getLayouts().filter((x) => usedContentKeys.indexOf(x.contentKey) === -1);
unusedBlocks.forEach((blockLayout) => {
this.#managerContext.removeOneLayout(blockLayout.contentKey);

View File

@@ -13,7 +13,7 @@ export const defaultFallbackConfig: RawEditorOptions = {
'+a[id|style|rel|data-id|data-udi|rev|charset|hreflang|dir|lang|tabindex|accesskey|type|name|href|target|title|class|onfocus|onblur|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup],-strong/-b[class|style],-em/-i[class|style],-strike[class|style],-s[class|style],-u[class|style],#p[id|style|dir|class|align],-ol[class|reversed|start|style|type],-ul[class|style],-li[class|style],br[class],img[id|dir|lang|longdesc|usemap|style|class|src|onmouseover|onmouseout|border|alt=|title|hspace|vspace|width|height|align|umbracoorgwidth|umbracoorgheight|onresize|onresizestart|onresizeend|rel|data-id],-sub[style|class],-sup[style|class],-blockquote[dir|style|class],-table[border=0|cellspacing|cellpadding|width|height|class|align|summary|style|dir|id|lang|bgcolor|background|bordercolor],-tr[id|lang|dir|class|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor],tbody[id|class],thead[id|class],tfoot[id|class],#td[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor|scope],-th[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|scope],caption[id|lang|dir|class|style],-div[id|dir|class|align|style],-span[class|align|style],-pre[class|align|style],address[class|align|style],-h1[id|dir|class|align|style],-h2[id|dir|class|align|style],-h3[id|dir|class|align|style],-h4[id|dir|class|align|style],-h5[id|dir|class|align|style],-h6[id|style|dir|class|align|style],hr[class|style],small[class|style],dd[id|class|title|style|dir|lang],dl[id|class|title|style|dir|lang],dt[id|class|title|style|dir|lang],object[class|id|width|height|codebase|*],param[name|value|_value|class],embed[type|width|height|src|class|*],map[name|class],area[shape|coords|href|alt|target|class],bdo[class],button[class],iframe[*],figure,figcaption,cite,video[*],audio[*],picture[*],source[*],canvas[*]',
invalid_elements: 'font',
extended_valid_elements:
'@[id|class|style],+umb-rte-block[!data-content-udi],+umb-rte-block-inline[!data-content-udi],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style],-h1[id|dir|class|align|style],-h2[id|dir|class|align|style],-h3[id|dir|class|align|style],-h4[id|dir|class|align|style],-h5[id|dir|class|align|style],-h6[id|style|dir|class|align],span[id|class|style|lang],figure,figcaption',
'@[id|class|style],+umb-rte-block[!data-content-key],+umb-rte-block-inline[!data-content-key],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style],-h1[id|dir|class|align|style],-h2[id|dir|class|align|style],-h3[id|dir|class|align|style],-h4[id|dir|class|align|style],-h5[id|dir|class|align|style],-h6[id|style|dir|class|align],span[id|class|style|lang],figure,figcaption',
custom_elements: 'umb-rte-block,~umb-rte-block-inline',
toolbar: [
'styles',

View File

@@ -114,7 +114,8 @@ export default class UmbTinyMceMultiUrlPickerPlugin extends UmbTinyMcePluginBase
const blockEl = `<${blockTag} ${UMB_BLOCK_RTE_DATA_CONTENT_KEY}="${block.key}"></${blockTag}>`;
editor.insertContent(blockEl);
editor.selection.setContent(blockEl);
editor.setDirty(true);
});
}
}

View File

@@ -1,4 +1,4 @@
import type { UmbTiptapExtensionApi } from '../../extensions/types.js';
import type { UmbTiptapExtensionApi, UmbTiptapToolbarValue } from '../../extensions/types.js';
import { css, customElement, html, property, state, when } from '@umbraco-cms/backoffice/external/lit';
import { loadManifestApi } from '@umbraco-cms/backoffice/extension-api';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
@@ -75,7 +75,7 @@ export class UmbInputTiptapElement extends UmbFormControlMixin<string, typeof Um
private _editor!: Editor;
@state()
_toolbar: string[][][] = [[[]]];
_toolbar: UmbTiptapToolbarValue = [[[]]];
protected override async firstUpdated() {
await Promise.all([await this.#loadExtensions(), await this.#loadEditor()]);
@@ -105,12 +105,11 @@ export class UmbInputTiptapElement extends UmbFormControlMixin<string, typeof Um
const element = this.shadowRoot?.querySelector('#editor');
if (!element) return;
const maxWidth = this.configuration?.getValueByAlias<number>('maxWidth');
const maxHeight = this.configuration?.getValueByAlias<number>('maxHeight');
if (maxWidth) this.setAttribute('style', `max-width: ${maxWidth}px;`);
if (maxHeight) element.setAttribute('style', `max-height: ${maxHeight}px;`);
const dimensions = this.configuration?.getValueByAlias<{ width?: number; height?: number }>('dimensions');
if (dimensions?.width) this.setAttribute('style', `max-width: ${dimensions.width}px;`);
if (dimensions?.height) element.setAttribute('style', `max-height: ${dimensions.height}px;`);
this._toolbar = this.configuration?.getValueByAlias<string[][][]>('toolbar') ?? [[[]]];
this._toolbar = this.configuration?.getValueByAlias<UmbTiptapToolbarValue>('toolbar') ?? [[[]]];
const extensions = this._extensions
.map((ext) => ext.getTiptapExtensions({ configuration: this.configuration }))
@@ -264,6 +263,8 @@ export class UmbInputTiptapElement extends UmbFormControlMixin<string, typeof Um
.umb-embed-holder.ProseMirror-selectednode::before {
background: rgba(0, 0, 0, 0.025);
}
/* Table-specific styling */
.tableWrapper {
margin: 1.5rem 0;
@@ -318,7 +319,6 @@ export class UmbInputTiptapElement extends UmbFormControlMixin<string, typeof Um
width: 3px;
}
}
}
.resize-cursor {
cursor: ew-resize;

View File

@@ -1,5 +1,7 @@
import type { ManifestTiptapToolbarExtension } from '../../extensions/tiptap-toolbar-extension.js';
import { css, customElement, html, property } from '@umbraco-cms/backoffice/external/lit';
import type { UmbTiptapToolbarValue } from '../../extensions/types.js';
import { css, customElement, html, map, property, state } from '@umbraco-cms/backoffice/external/lit';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { UmbExtensionsElementAndApiInitializer } from '@umbraco-cms/backoffice/extension-api';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type { Editor } from '@umbraco-cms/backoffice/external/tiptap';
import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
@@ -10,6 +12,12 @@ const elementName = 'umb-tiptap-fixed-menu';
@customElement(elementName)
export class UmbTiptapFixedMenuElement extends UmbLitElement {
#attached = false;
#extensionsController?: UmbExtensionsElementAndApiInitializer;
@state()
private _lookup?: Map<string, unknown>;
@property({ type: Boolean, reflect: true })
readonly = false;
@@ -20,21 +28,61 @@ export class UmbTiptapFixedMenuElement extends UmbLitElement {
configuration?: UmbPropertyEditorConfigCollection;
@property({ attribute: false })
toolbar: string[][][] = [[[]]];
toolbar: UmbTiptapToolbarValue = [[[]]];
override connectedCallback(): void {
super.connectedCallback();
this.#attached = true;
this.#observeExtensions();
}
override disconnectedCallback(): void {
this.#attached = false;
this.#extensionsController?.destroy();
this.#extensionsController = undefined;
super.disconnectedCallback();
}
#observeExtensions(): void {
if (!this.#attached) return;
this.#extensionsController?.destroy();
this.#extensionsController = new UmbExtensionsElementAndApiInitializer(
this,
umbExtensionsRegistry,
'tiptapToolbarExtension',
[],
(manifest) => this.toolbar.flat(2).includes(manifest.alias),
(extensionControllers) => {
this._lookup = new Map(extensionControllers.map((ext) => [ext.alias, ext.component]));
},
);
this.#extensionsController.apiProperties = { configuration: this.configuration };
this.#extensionsController.elementProperties = { editor: this.editor, configuration: this.configuration };
}
override render() {
return html`
<umb-extension-with-api-slot
type="tiptapToolbarExtension"
.filter=${(ext: ManifestTiptapToolbarExtension) =>
this.toolbar.flat(2).includes(ext.alias) && (!!ext.kind || !!ext.element)}
.elementProps=${{ editor: this.editor, configuration: this.configuration }}
.apiProps=${{ configuration: this.configuration }}>
</umb-extension-with-api-slot>
`;
return html`${map(this.toolbar, (row, rowIndex) =>
map(
row,
(group, groupIndex) =>
html`${map(group, (alias, aliasIndex) => {
const newRow = rowIndex !== 0 && groupIndex === 0 && aliasIndex === 0;
return html`<div class="item" ?data-new-row=${newRow} style="${newRow ? 'grid-column: 1 / span 3' : ''}">
${this._lookup?.get(alias)}
</div>`;
})}
<div class="separator"></div> `,
),
)} `;
}
static override readonly styles = css`
:host([readonly]) {
pointer-events: none;
background-color: var(--uui-color-surface-alt);
}
:host {
border-radius: var(--uui-border-radius);
border: 1px solid var(--uui-color-border);
@@ -43,20 +91,29 @@ export class UmbTiptapFixedMenuElement extends UmbLitElement {
background-color: var(--uui-color-surface);
color: var(--color-text);
display: grid;
grid-template-columns: repeat(auto-fill, minmax(24px, 1fr));
gap: var(--uui-size-space-1);
grid-template-columns: repeat(auto-fill, 10px);
grid-auto-flow: row;
position: sticky;
top: -25px;
left: 0px;
right: 0px;
padding: var(--uui-size-space-3);
align-items: center;
z-index: 9999999;
}
:host([readonly]) {
pointer-events: none;
background-color: var(--uui-color-surface-alt);
.item {
grid-column: span 3;
}
.separator {
background-color: var(--uui-color-border);
width: 1px;
place-self: center;
height: 22px;
}
.separator:last-child,
.separator:has(+ [data-new-row]) {
display: none;
}
`;
}

View File

@@ -98,3 +98,5 @@ export abstract class UmbTiptapToolbarElementApiBase extends UmbControllerBase i
return editor && this.manifest?.meta.alias ? editor?.isActive(this.manifest.meta.alias) : false;
}
}
export type UmbTiptapToolbarValue = Array<Array<Array<string>>>;

View File

@@ -4,6 +4,7 @@ import { UMB_LINK_PICKER_MODAL } from '@umbraco-cms/backoffice/multi-url-picker'
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import type { Editor } from '@umbraco-cms/backoffice/external/tiptap';
import type { UmbLinkPickerLink } from '@umbraco-cms/backoffice/multi-url-picker';
import type { UUIModalSidebarSize } from '@umbraco-cms/backoffice/external/uui';
export default class UmbTiptapLinkExtensionApi extends UmbTiptapToolbarElementApiBase {
override async execute(editor?: Editor) {
@@ -12,8 +13,10 @@ export default class UmbTiptapLinkExtensionApi extends UmbTiptapToolbarElementAp
const data = { config: {}, index: null };
const value = { link };
const overlaySize = this.configuration?.getValueByAlias<UUIModalSidebarSize>('overlaySize') ?? 'small';
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modalHandler = modalManager.open(this, UMB_LINK_PICKER_MODAL, { data, value });
const modalHandler = modalManager.open(this, UMB_LINK_PICKER_MODAL, { data, value, modal: { size: overlaySize } });
if (!modalHandler) return;

View File

@@ -1,17 +1,10 @@
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import {
customElement,
css,
html,
property,
state,
repeat,
nothing,
type PropertyValueMap,
} from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type { UmbTiptapToolbarValue } from '../extensions/types.js';
import { customElement, css, html, property, state, repeat, nothing } from '@umbraco-cms/backoffice/external/lit';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
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 = {
alias: string;
@@ -25,7 +18,7 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement
implements UmbPropertyEditorUiElement
{
@property({ attribute: false })
set value(value: string[][][] | undefined) {
set value(value: UmbTiptapToolbarValue | undefined) {
if (!value) {
this.#useDefault = true;
this.#value = [[[]]];
@@ -36,14 +29,14 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement
this.#value = value.map((rows) => rows.map((groups) => [...groups]));
}
get value(): string[][][] {
get value(): UmbTiptapToolbarValue {
// TODO: This can be optimized with cashing;
return this.#value.map((rows) => rows.map((groups) => [...groups]));
}
#useDefault = false;
#value: string[][][] = [[[]]];
#value: UmbTiptapToolbarValue = [[[]]];
@state()
_extensions: Extension[] = [];

View File

@@ -28,19 +28,12 @@ export const manifests: Array<ManifestPropertyEditorUi> = [
weight: 10,
},
{
alias: 'maxWidth',
label: 'Maximum width',
description: 'Editor maximum width',
propertyEditorUiAlias: 'Umb.PropertyEditorUi.Integer',
alias: 'dimensions',
label: 'Dimensions',
description: 'Set the maximum width and height of the editor',
propertyEditorUiAlias: 'Umb.PropertyEditorUI.TinyMCE.DimensionsConfiguration',
weight: 20,
},
{
alias: 'maxHeight',
label: 'Maximum height',
description: 'Editor maximum height',
propertyEditorUiAlias: 'Umb.PropertyEditorUi.Integer',
weight: 30,
},
{
alias: 'maxImageSize',
label: 'Maximum size for inserted images',
@@ -49,8 +42,17 @@ export const manifests: Array<ManifestPropertyEditorUi> = [
weight: 40,
config: [{ alias: 'min', value: 0 }],
},
{
alias: 'overlaySize',
label: 'Overlay Size',
description: 'Select the width of the overlay (link picker)',
propertyEditorUiAlias: 'Umb.PropertyEditorUi.OverlaySize',
weight: 50,
},
],
defaultData: [
{ alias: 'overlaySize', value: 'medium' },
],
defaultData: [],
},
},
},