implement manager for rte property editor

This commit is contained in:
Niels Lyngsø
2024-05-26 10:48:27 +02:00
parent 7864fe57f0
commit 8935b68705
13 changed files with 287 additions and 21 deletions

View File

@@ -1 +1,2 @@
export * from './block-list-entries.context-token.js';
export * from './block-list-entry.context-token.js';

View File

@@ -0,0 +1,5 @@
import type { UmbBlockRteEntriesContext } from './block-rte-entries.context.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
// TODO: Make discriminator method for this:
export const UMB_BLOCK_RTE_ENTRIES_CONTEXT = new UmbContextToken<UmbBlockRteEntriesContext>('UmbBlockEntriesContext');

View File

@@ -0,0 +1,121 @@
import type { UmbBlockDataType } from '../../block/index.js';
import { UMB_BLOCK_CATALOGUE_MODAL, UmbBlockEntriesContext } from '../../block/index.js';
import type { UmbBlockRteWorkspaceData } from '../index.js';
import type { UmbBlockRteLayoutModel, UmbBlockRteTypeModel } from '../types.js';
import { UMB_BLOCK_RTE_MANAGER_CONTEXT } from './block-rte-manager.context.js';
import { UmbBooleanState } from '@umbraco-cms/backoffice/observable-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
export class UmbBlockRteEntriesContext extends UmbBlockEntriesContext<
typeof UMB_BLOCK_RTE_MANAGER_CONTEXT,
typeof UMB_BLOCK_RTE_MANAGER_CONTEXT.TYPE,
UmbBlockRteTypeModel,
UmbBlockRteLayoutModel
> {
//
#catalogueModal: UmbModalRouteRegistrationController<typeof UMB_BLOCK_CATALOGUE_MODAL.DATA, undefined>;
// We will just say its always allowed for RTE for now: [NL]
public readonly canCreate = new UmbBooleanState(true).asObservable();
constructor(host: UmbControllerHost) {
super(host, UMB_BLOCK_RTE_MANAGER_CONTEXT);
this.#catalogueModal = new UmbModalRouteRegistrationController(this, UMB_BLOCK_CATALOGUE_MODAL)
.addUniquePaths(['propertyAlias', 'variantId'])
.addAdditionalPath(':view/:index')
.onSetup((routingInfo) => {
// Idea: Maybe on setup should be async, so it can retrieve the values when needed? [NL]
const index = routingInfo.index ? parseInt(routingInfo.index) : -1;
return {
data: {
blocks: this._manager?.getBlockTypes() ?? [],
blockGroups: [],
openClipboard: routingInfo.view === 'clipboard',
blockOriginData: { index: index },
},
};
})
.observeRouteBuilder((routeBuilder) => {
this._catalogueRouteBuilderState.setValue(routeBuilder);
});
}
protected _gotBlockManager() {
if (!this._manager) return;
this.observe(
this._manager.layouts,
(layouts) => {
this._layoutEntries.setValue(layouts);
},
'observeParentLayouts',
);
this.observe(
this.layoutEntries,
(layouts) => {
this._manager?.setLayouts(layouts);
},
'observeThisLayouts',
);
this.observe(
this._manager.propertyAlias,
(alias) => {
this.#catalogueModal.setUniquePathValue('propertyAlias', alias ?? 'null');
},
'observePropertyAlias',
);
this.observe(
this._manager.variantId,
(variantId) => {
if (variantId) {
this.#catalogueModal.setUniquePathValue('variantId', variantId.toString());
}
},
'observePropertyAlias',
);
}
getPathForCreateBlock(index: number) {
return this._catalogueRouteBuilderState.getValue()?.({ view: 'create', index: index });
}
getPathForClipboard(index: number) {
return this._catalogueRouteBuilderState.getValue()?.({ view: 'clipboard', index: index });
}
async setLayouts(layouts: Array<UmbBlockRteLayoutModel>) {
await this._retrieveManager;
this._manager?.setLayouts(layouts);
}
async create(
contentElementTypeKey: string,
partialLayoutEntry?: Omit<UmbBlockRteLayoutModel, 'contentUdi'>,
modalData?: UmbBlockRteWorkspaceData,
) {
await this._retrieveManager;
return this._manager?.create(contentElementTypeKey, partialLayoutEntry, modalData);
}
// insert Block?
async insert(
layoutEntry: UmbBlockRteLayoutModel,
content: UmbBlockDataType,
settings: UmbBlockDataType | undefined,
modalData: UmbBlockRteWorkspaceData,
) {
await this._retrieveManager;
return this._manager?.insert(layoutEntry, content, settings, modalData) ?? false;
}
// create Block?
async delete(contentUdi: string) {
// TODO: Loop through children and delete them as well?
await super.delete(contentUdi);
}
}

View File

@@ -0,0 +1,5 @@
import type { UmbBlockRteEntryContext } from './block-rte-entry.context.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
// TODO: Make discriminator method for this:
export const UMB_BLOCK_RTE_ENTRY_CONTEXT = new UmbContextToken<UmbBlockRteEntryContext>('UmbBlockEntryContext');

View File

@@ -0,0 +1,26 @@
import { UMB_BLOCK_RTE_MANAGER_CONTEXT } from './block-rte-manager.context.js';
import { UMB_BLOCK_RTE_ENTRIES_CONTEXT } from './block-rte-entries.context-token.js';
import { UmbBlockEntryContext } from '@umbraco-cms/backoffice/block';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export class UmbBlockRteEntryContext extends UmbBlockEntryContext<
typeof UMB_BLOCK_RTE_MANAGER_CONTEXT,
typeof UMB_BLOCK_RTE_MANAGER_CONTEXT.TYPE,
typeof UMB_BLOCK_RTE_ENTRIES_CONTEXT,
typeof UMB_BLOCK_RTE_ENTRIES_CONTEXT.TYPE
> {
readonly forceHideContentEditorInOverlay = this._blockType.asObservablePart(
(x) => !!x?.forceHideContentEditorInOverlay,
);
readonly showContentEdit = this.forceHideContentEditorInOverlay;
constructor(host: UmbControllerHost) {
super(host, UMB_BLOCK_RTE_MANAGER_CONTEXT, UMB_BLOCK_RTE_ENTRIES_CONTEXT);
}
_gotManager() {}
_gotEntries() {}
_gotContentType() {}
}

View File

@@ -0,0 +1,47 @@
import type { UmbBlockRteLayoutModel, UmbBlockRteTypeModel } from '../types.js';
import type { UmbBlockRteWorkspaceData } from '../index.js';
import type { UmbBlockDataType } from '../../block/types.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import { UmbBooleanState } from '@umbraco-cms/backoffice/observable-api';
import { UmbBlockManagerContext } from '@umbraco-cms/backoffice/block';
/**
* A implementation of the Block Manager specifically for the Rich Text Editor.
*/
export class UmbBlockRteManagerContext<
BlockLayoutType extends UmbBlockRteLayoutModel = UmbBlockRteLayoutModel,
> extends UmbBlockManagerContext<UmbBlockRteTypeModel, BlockLayoutType> {
//
#inlineEditingMode = new UmbBooleanState(undefined);
readonly inlineEditingMode = this.#inlineEditingMode.asObservable();
setInlineEditingMode(inlineEditingMode: boolean | undefined) {
this.#inlineEditingMode.setValue(inlineEditingMode ?? false);
}
create(
contentElementTypeKey: string,
partialLayoutEntry?: Omit<BlockLayoutType, 'contentUdi'>,
modalData?: UmbBlockRteWorkspaceData,
) {
return super.createBlockData(contentElementTypeKey, partialLayoutEntry);
}
insert(
layoutEntry: BlockLayoutType,
content: UmbBlockDataType,
settings: UmbBlockDataType | undefined,
modalData: UmbBlockRteWorkspaceData,
) {
this._layouts.appendOneAt(layoutEntry, modalData.originData.index ?? -1);
this.insertBlockData(layoutEntry, content, settings, modalData);
return true;
}
}
// TODO: Make discriminator method for this:
export const UMB_BLOCK_RTE_MANAGER_CONTEXT = new UmbContextToken<UmbBlockRteManagerContext, UmbBlockRteManagerContext>(
'UmbBlockManagerContext',
);

View File

@@ -0,0 +1,5 @@
export * from './block-rte-entries.context-token.js';
export * from './block-rte-entries.context.js';
export * from './block-rte-entry.context-token.js';
export * from './block-rte-entry.context.js';
export * from './block-rte-manager.context.js';

View File

@@ -1,2 +1,3 @@
export * from './context/index.js';
export * from './workspace/index.js';
export * from './types.js';

View File

@@ -40,16 +40,20 @@ export default class UmbTinyMceMultiUrlPickerPlugin extends UmbTinyMcePluginBase
async showDialog() {
const blockEl = this.editor.selection.getNode();
let blockUdi: string | undefined;
if (blockEl.nodeName === 'UMB-RTE-BLOCK' || blockEl.nodeName === 'UMB-RTE-BLOCK-INLINE') {
blockUdi = blockEl.getAttribute('data-content-udi') ?? undefined;
const blockUdi = blockEl.getAttribute('data-content-udi') ?? undefined;
if (blockUdi) {
this.#editBlock(blockUdi);
return;
}
}
this.#openBlockPicker(blockUdi);
// If no block is selected, open the block picker:
this.#createBlock();
}
async #openBlockPicker(blockUdi?: string) {
async #editBlock(blockUdi?: string) {
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modalHandler = modalManager.open(this, UMB_BLOCK_RTE_WORKSPACE_MODAL, {
data: {
@@ -69,6 +73,8 @@ export default class UmbTinyMceMultiUrlPickerPlugin extends UmbTinyMcePluginBase
}
}
#createBlock() {}
#insertBlockInEditor(blockContentUdi: string, displayInline = false) {
if (!blockContentUdi) {
return;

View File

@@ -1,5 +1,9 @@
import type { UmbBlockTypeBaseModel } from '../block-type/index.js';
import type { UmbBlockLayoutBaseModel, UmbBlockValueType } from '@umbraco-cms/backoffice/block';
export const UMB_BLOCK_RTE_TYPE = 'block-rte-type';
export interface UmbBlockRteTypeModel extends UmbBlockTypeBaseModel {}
export interface UmbBlockRteLayoutModel extends UmbBlockLayoutBaseModel {}
export interface UmbBlockRteValueModel extends UmbBlockValueType<UmbBlockRteLayoutModel> {}

View File

@@ -6,14 +6,14 @@ import type {
import type { UmbWorkspaceModalData } from '@umbraco-cms/backoffice/modal';
import { UmbModalToken } from '@umbraco-cms/backoffice/modal';
export interface UmbBlockRTEWorkspaceData
export interface UmbBlockRteWorkspaceData
extends UmbBlockWorkspaceData<{
index: number;
}> {}
export type UmbBlockRTEWorkspaceValue = Array<UmbBlockViewPropsType<UmbBlockLayoutBaseModel>>;
export type UmbBlockRteWorkspaceValue = Array<UmbBlockViewPropsType<UmbBlockLayoutBaseModel>>;
export const UMB_BLOCK_RTE_WORKSPACE_MODAL = new UmbModalToken<UmbBlockRTEWorkspaceData, UmbBlockRTEWorkspaceValue>(
export const UMB_BLOCK_RTE_WORKSPACE_MODAL = new UmbModalToken<UmbBlockRteWorkspaceData, UmbBlockRteWorkspaceValue>(
'Umb.Modal.Workspace',
{
modal: {
@@ -23,4 +23,4 @@ export const UMB_BLOCK_RTE_WORKSPACE_MODAL = new UmbModalToken<UmbBlockRTEWorksp
data: { entityType: 'block', preset: {}, originData: { index: -1 } },
// Recast the type, so the entityType data prop is not required:
},
) as UmbModalToken<Omit<UmbWorkspaceModalData, 'entityType'>, UmbBlockRTEWorkspaceValue>;
) as UmbModalToken<Omit<UmbWorkspaceModalData, 'entityType'>, UmbBlockRteWorkspaceValue>;

View File

@@ -9,12 +9,15 @@ export interface UmbBlockDataType {
[key: string]: unknown;
}
export interface UmbBlockValueType<BlockLayoutType extends UmbBlockLayoutBaseModel> {
layout: { [key: string]: Array<BlockLayoutType> | undefined };
export interface UmbBlockDataBaseValueType {
contentData: Array<UmbBlockDataType>;
settingsData: Array<UmbBlockDataType>;
}
export interface UmbBlockValueType<BlockLayoutType extends UmbBlockLayoutBaseModel> extends UmbBlockDataBaseValueType {
layout: { [key: string]: Array<BlockLayoutType> | undefined };
}
export interface UmbBlockViewUrlsPropType {
editContent?: string;
editSettings?: string;

View File

@@ -1,15 +1,16 @@
import { customElement, html, property } from '@umbraco-cms/backoffice/external/lit';
import { customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor';
import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
import '../../components/input-tiny-mce/input-tiny-mce.element.js';
import { UmbBlockRteEntriesContext, UmbBlockRteManagerContext } from '@umbraco-cms/backoffice/block-rte';
import type { UmbBlockDataBaseValueType } from '@umbraco-cms/backoffice/block';
type RichTextEditorValue = {
blocks: object;
export interface UmbRichTextEditorValueType extends UmbBlockDataBaseValueType {
markup: string;
};
}
/**
* @element umb-property-editor-ui-tiny-mce
@@ -18,11 +19,20 @@ type RichTextEditorValue = {
export class UmbPropertyEditorUITinyMceElement extends UmbLitElement implements UmbPropertyEditorUiElement {
#configuration?: UmbPropertyEditorConfigCollection;
@property({ type: Object })
value?: RichTextEditorValue = {
blocks: {},
markup: '',
};
@property({ attribute: false })
public set value(value: UmbRichTextEditorValueType | undefined) {
const buildUpValue: Partial<UmbRichTextEditorValueType> = value ? { ...value } : {};
buildUpValue.markup ??= '';
buildUpValue.contentData ??= [];
buildUpValue.settingsData ??= [];
this._value = buildUpValue as UmbRichTextEditorValueType;
this.#managerContext.setContents(buildUpValue.contentData);
this.#managerContext.setSettings(buildUpValue.settingsData);
}
public get value(): UmbRichTextEditorValueType {
return this._value;
}
@property({ attribute: false })
public set config(config: UmbPropertyEditorConfigCollection | undefined) {
@@ -32,9 +42,41 @@ export class UmbPropertyEditorUITinyMceElement extends UmbLitElement implements
return this.#configuration;
}
@state()
private _value: UmbRichTextEditorValueType = {
markup: '',
contentData: [],
settingsData: [],
};
#managerContext = new UmbBlockRteManagerContext(this);
#entriesContext = new UmbBlockRteEntriesContext(this);
constructor() {
super();
this.observe(this.#entriesContext.layoutEntries, (layouts) => {
// Update manager:
this.#managerContext.setLayouts(layouts);
});
this.observe(this.#managerContext.contents, (contents) => {
this._value = { ...this._value, contentData: contents };
this.#fireChangeEvent();
});
this.observe(this.#managerContext.settings, (settings) => {
this._value = { ...this._value, settingsData: settings };
this.#fireChangeEvent();
});
}
#fireChangeEvent() {
this.dispatchEvent(new UmbPropertyValueChangeEvent());
}
#onChange(event: InputEvent & { target: HTMLInputElement }) {
this.value = {
blocks: {},
...this._value,
markup: event.target.value,
};
this.dispatchEvent(new UmbPropertyValueChangeEvent());
@@ -44,7 +86,7 @@ export class UmbPropertyEditorUITinyMceElement extends UmbLitElement implements
return html`
<umb-input-tiny-mce
.configuration=${this.#configuration}
.value=${this.value?.markup ?? ''}
.value=${this._value?.markup ?? ''}
@change=${this.#onChange}>
</umb-input-tiny-mce>
`;