From 77d019904c9e8eb5c629d996496d49f6560fbd2d Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 22 May 2024 15:11:07 +0200 Subject: [PATCH] feat: add a block picker plugin to the rich text editor --- .../src/packages/block/block-rte/manifests.ts | 3 +- .../block-rte/tiny-mce-plugin/manifests.ts | 17 ++++ .../tiny-mce-block-picker.plugin.ts | 90 +++++++++++++++++++ 3 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/block/block-rte/tiny-mce-plugin/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/block/block-rte/tiny-mce-plugin/tiny-mce-block-picker.plugin.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/manifests.ts index 71c165c901..151385f7c1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/manifests.ts @@ -1,4 +1,5 @@ +import { manifests as tinyMcePluginManifests } from './tiny-mce-plugin/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; -export const manifests: Array = [...workspaceManifests]; +export const manifests: Array = [...tinyMcePluginManifests, ...workspaceManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/tiny-mce-plugin/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/tiny-mce-plugin/manifests.ts new file mode 100644 index 0000000000..b549d60854 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/tiny-mce-plugin/manifests.ts @@ -0,0 +1,17 @@ +export const manifests = [ + { + type: 'tinyMcePlugin', + alias: 'Umb.TinyMcePlugin.BlockPicker', + name: 'Block Picker TinyMCE Plugin', + js: () => import('./tiny-mce-block-picker.plugin.js'), + meta: { + toolbar: [ + { + alias: 'umbblockpicker', + label: '#blockEditor_insertBlock', + icon: 'visualblocks', + }, + ], + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/tiny-mce-plugin/tiny-mce-block-picker.plugin.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/tiny-mce-plugin/tiny-mce-block-picker.plugin.ts new file mode 100644 index 0000000000..f8f11ce8a4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/tiny-mce-plugin/tiny-mce-block-picker.plugin.ts @@ -0,0 +1,90 @@ +import { UMB_BLOCK_RTE_WORKSPACE_MODAL } from '../workspace/index.js'; +import { type TinyMcePluginArguments, UmbTinyMcePluginBase } from '@umbraco-cms/backoffice/tiny-mce'; +import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; +import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api'; + +export default class UmbTinyMceMultiUrlPickerPlugin extends UmbTinyMcePluginBase { + #localize = new UmbLocalizationController(this._host); + + constructor(args: TinyMcePluginArguments) { + super(args); + + args.editor.on('preInit', () => { + args.editor.serializer.addRules('umb-rte-block'); + + /** This checks if the div is a block element*/ + args.editor.serializer.addNodeFilter('umb-rte-block', function (nodes) { + for (let i = 0; i < nodes.length; i++) { + const blockEl = nodes[i]; + /* if the block is set to display inline, checks if its wrapped in a p tag and then unwraps it (removes p tag) */ + if (blockEl.parent && blockEl.parent.name.toUpperCase() === 'P') { + blockEl.parent.unwrap(); + } + } + }); + }); + + args.editor.ui.registry.addToggleButton('umbblockpicker', { + icon: 'visualblocks', + tooltip: this.#localize.term('blockEditor_insertBlock'), + onAction: () => this.showDialog(), + onSetup: function (api) { + const changed = args.editor.selection.selectorChangedWithUnbind( + 'umb-rte-block[data-content-udi], umb-rte-block-inline[data-content-udi]', + (state) => api.setActive(state), + ); + return () => changed.unbind(); + }, + }); + } + + 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; + } + + this.#openBlockPicker(blockUdi); + } + + async #openBlockPicker(blockUdi?: string) { + const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); + const modalHandler = modalManager.open(this, UMB_BLOCK_RTE_WORKSPACE_MODAL, { + data: { + preset: { + blockUdi, + }, + }, + }); + + if (!modalHandler) return; + + const blockPickerData = await modalHandler.onSubmit().catch(() => undefined); + if (!blockPickerData) return; + + for (const block of blockPickerData) { + this.#insertBlockInEditor(block.layout?.contentUdi ?? '', (block.layout as any)?.displayInline ?? false); + } + } + + #insertBlockInEditor(blockContentUdi: string, displayInline = false) { + if (!blockContentUdi) { + return; + } + if (displayInline) { + this.editor.selection.setContent( + '', + ); + } else { + this.editor.selection.setContent( + '', + ); + } + + // angularHelper.safeApply($rootScope, function () { + // editor.dispatch("Change"); + // }); + } +}