From 1ce4715595fbb3cdac3728c624dd3e693e40325e Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 19 Dec 2023 09:10:19 +0100 Subject: [PATCH 01/23] add a 'meta' module to extract information from package.json --- src/Umbraco.Web.UI.Client/package.json | 1 + src/Umbraco.Web.UI.Client/src/packages/core/meta/index.ts | 7 +++++++ src/Umbraco.Web.UI.Client/tsconfig.json | 1 + src/Umbraco.Web.UI.Client/web-test-runner.config.mjs | 1 + 4 files changed, 10 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/meta/index.ts diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index f3df7fa2e0..fee7ff6e6f 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -33,6 +33,7 @@ "./localization": "./dist-cms/packages/core/localization/index.js", "./macro": "./dist-cms/packages/core/macro/index.js", "./menu": "./dist-cms/packages/core/menu/index.js", + "./meta": "./dist-cms/packages/core/meta/index.js", "./modal": "./dist-cms/packages/core/modal/index.js", "./notification": "./dist-cms/packages/core/notification/index.js", "./picker-input": "./dist-cms/packages/core/picker-input/index.js", diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/meta/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/meta/index.ts new file mode 100644 index 0000000000..ccf2b69ee1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/meta/index.ts @@ -0,0 +1,7 @@ +import packageJson from '../../../../package.json'; + +export const umbMeta = { + name: 'Bellissima', + packageName: packageJson.name, + packageVersion: packageJson.version, +}; diff --git a/src/Umbraco.Web.UI.Client/tsconfig.json b/src/Umbraco.Web.UI.Client/tsconfig.json index 3747bafb49..45cc717711 100644 --- a/src/Umbraco.Web.UI.Client/tsconfig.json +++ b/src/Umbraco.Web.UI.Client/tsconfig.json @@ -67,6 +67,7 @@ "@umbraco-cms/backoffice/localization": ["src/packages/core/localization"], "@umbraco-cms/backoffice/macro": ["src/packages/core/macro"], "@umbraco-cms/backoffice/menu": ["src/packages/core/menu"], + "@umbraco-cms/backoffice/meta": ["src/packages/core/meta"], "@umbraco-cms/backoffice/modal": ["src/packages/core/modal"], "@umbraco-cms/backoffice/notification": ["src/packages/core/notification"], "@umbraco-cms/backoffice/picker-input": ["src/packages/core/picker-input"], diff --git a/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs b/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs index 91b9dc9e89..fd823603ba 100644 --- a/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs +++ b/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs @@ -73,6 +73,7 @@ export default { '@umbraco-cms/backoffice/localization': './src/packages/core/localization/index.ts', '@umbraco-cms/backoffice/macro': './src/packages/core/macro/index.ts', '@umbraco-cms/backoffice/menu': './src/packages/core/menu/index.ts', + '@umbraco-cms/backoffice/meta': './src/packages/core/meta/index.ts', '@umbraco-cms/backoffice/modal': './src/packages/core/modal/index.ts', '@umbraco-cms/backoffice/notification': './src/packages/core/notification/index.ts', '@umbraco-cms/backoffice/picker-input': './src/packages/core/picker-input/index.ts', From 390b095231e92e49bde831ba430f63ad9b6449f4 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 19 Dec 2023 09:10:27 +0100 Subject: [PATCH 02/23] add a 'meta' module to extract information from package.json --- src/Umbraco.Web.UI.Client/src/packages/core/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/index.ts index 21661c1e53..d16bd52640 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/index.ts @@ -29,6 +29,7 @@ export * from './extension-registry/index.js'; export * from './id/index.js'; export * from './macro/index.js'; export * from './menu/index.js'; +export * from './meta/index.js'; export * from './modal/index.js'; export * from './notification/index.js'; export * from './picker-input/index.js'; From 8eede4fe869ccb1a00d5790f61fa832423ce149b Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 19 Dec 2023 09:11:13 +0100 Subject: [PATCH 03/23] use packageVersion for tinymce cache_suffix --- .../input-tiny-mce/input-tiny-mce.element.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts index d43c6320db..9521c37128 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts @@ -1,3 +1,4 @@ +import { umbMeta } from '@umbraco-cms/backoffice/meta'; import { defaultExtendedValidElements, defaultFallbackConfig, defaultStyleFormats } from './input-tiny-mce.defaults.js'; import { pastePreProcessHandler, uploadImageHandler } from './input-tiny-mce.handlers.js'; import { availableLanguages } from './input-tiny-mce.languages.js'; @@ -117,17 +118,6 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) { const rules: any[] = []; stylesheetPath.forEach((path) => { - //TODO => Legacy path? - /** - * if (val.indexOf(Umbraco.Sys.ServerVariables.umbracoSettings.cssPath + "/") === 0) { - // current format (full path to stylesheet) - stylesheets.push(val); - } - else { - // legacy format (stylesheet name only) - must prefix with stylesheet folder and postfix with ".css" - stylesheets.push(Umbraco.Sys.ServerVariables.umbracoSettings.cssPath + "/" + val + ".css"); - } - */ this.#stylesheetRepository?.getStylesheetRules(path).then(({ data }) => { data?.rules?.forEach((rule) => { const r: { @@ -193,7 +183,7 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) { autoresize_bottom_margin: 10, body_class: 'umb-rte', //see https://www.tiny.cloud/docs/tinymce/6/editor-important-options/#cache_suffix - cache_suffix: '?umb__rnd=' + window.Umbraco?.Sys.ServerVariables.application.cacheBuster, // TODO: Cache buster + cache_suffix: `?umb__rnd=${umbMeta.packageVersion}`, contextMenu: false, inline_boundaries_selector: 'a[href],code,.mce-annotation,.umb-embed-holder,.umb-macro-holder', menubar: false, From 586f6b9ae17f149f3589772e6964a337d48fd845 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 19 Dec 2023 09:19:08 +0100 Subject: [PATCH 04/23] clean imports --- .../core/components/input-tiny-mce/input-tiny-mce.element.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts index 9521c37128..edd15a2716 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts @@ -1,8 +1,8 @@ -import { umbMeta } from '@umbraco-cms/backoffice/meta'; import { defaultExtendedValidElements, defaultFallbackConfig, defaultStyleFormats } from './input-tiny-mce.defaults.js'; import { pastePreProcessHandler, uploadImageHandler } from './input-tiny-mce.handlers.js'; import { availableLanguages } from './input-tiny-mce.languages.js'; import { uriAttributeSanitizer } from './input-tiny-mce.sanitizer.js'; +import { umbMeta } from '@umbraco-cms/backoffice/meta'; import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; import { type Editor, @@ -12,7 +12,7 @@ import { } from '@umbraco-cms/backoffice/external/tinymce'; import { UMB_CURRENT_USER_CONTEXT, UmbCurrentUser } from '@umbraco-cms/backoffice/current-user'; import { TinyMcePluginArguments, UmbTinyMcePluginBase } from '@umbraco-cms/backoffice/components'; -import { ClassConstructor, hasDefaultExport, loadManifestApi } from '@umbraco-cms/backoffice/extension-api'; +import { loadManifestApi } from '@umbraco-cms/backoffice/extension-api'; import { ManifestTinyMcePlugin, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { PropertyValueMap, @@ -30,7 +30,6 @@ import { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/prope import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app'; import { UmbStylesheetRepository } from '@umbraco-cms/backoffice/stylesheet'; -// TODO => integrate macro picker, update stylesheet fetch when backend CLI exists (ref tinymce.service.js in existing backoffice) @customElement('umb-input-tiny-mce') export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) { @property({ attribute: false }) From de08bae418713a1c71914adc8e4d63c7ea35a35c Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 19 Dec 2023 09:23:40 +0100 Subject: [PATCH 05/23] rename packageVersion to clientVersion --- .../core/components/input-tiny-mce/input-tiny-mce.element.ts | 2 +- src/Umbraco.Web.UI.Client/src/packages/core/meta/index.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts index edd15a2716..55cead9f0e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts @@ -182,7 +182,7 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) { autoresize_bottom_margin: 10, body_class: 'umb-rte', //see https://www.tiny.cloud/docs/tinymce/6/editor-important-options/#cache_suffix - cache_suffix: `?umb__rnd=${umbMeta.packageVersion}`, + cache_suffix: `?umb__rnd=${umbMeta.clientVersion}`, contextMenu: false, inline_boundaries_selector: 'a[href],code,.mce-annotation,.umb-embed-holder,.umb-macro-holder', menubar: false, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/meta/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/meta/index.ts index ccf2b69ee1..90b526bc17 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/meta/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/meta/index.ts @@ -2,6 +2,6 @@ import packageJson from '../../../../package.json'; export const umbMeta = { name: 'Bellissima', - packageName: packageJson.name, - packageVersion: packageJson.version, + clientName: packageJson.name, + clientVersion: packageJson.version, }; From 1b9f3a90780f45fe08ba5eccbd888ca1af2d7060 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 19 Dec 2023 15:49:01 +0100 Subject: [PATCH 06/23] revamp loose binding between tinymce raweditoroptions and our data type configuration by checking if configuration options exist first --- .../input-tiny-mce/input-tiny-mce.defaults.ts | 61 ++++----- .../input-tiny-mce/input-tiny-mce.element.ts | 120 +++++++----------- .../input-tiny-mce/input-tiny-mce.handlers.ts | 44 ------- .../plugins/tiny-mce-mediapicker.plugin.ts | 58 +++++++++ 4 files changed, 133 insertions(+), 150 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.defaults.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.defaults.ts index f93ee1290d..579cbf0b6d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.defaults.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.defaults.ts @@ -1,35 +1,15 @@ import type { RawEditorOptions } from '@umbraco-cms/backoffice/external/tinymce'; -export type TinyStyleSheet = RawEditorOptions['style_formats']; - -export const defaultStyleFormats: TinyStyleSheet = [ - { - title: 'Headers', - items: [ - { title: 'Page header', block: 'h2' }, - { title: 'Section header', block: 'h3' }, - { title: 'Paragraph header', block: 'h4' }, - ], - }, - { - title: 'Blocks', - items: [{ title: 'Normal', block: 'p' }], - }, - { - title: 'Containers', - items: [ - { title: 'Quote', block: 'blockquote' }, - { title: 'Code', block: 'code' }, - ], - }, -]; - //These are absolutely required in order for the macros to render inline //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce -export const defaultExtendedValidElements = - '@[id|class|style],-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'; - +//so we don't have to specify all the normal elements again export const defaultFallbackConfig: RawEditorOptions = { + plugins: ['anchor', 'charmap', 'table', 'lists', 'advlist', 'autolink', 'directionality', 'searchreplace'], + valid_elements: + '+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],-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', toolbar: [ 'styles', 'bold', @@ -46,7 +26,30 @@ export const defaultFallbackConfig: RawEditorOptions = { 'umbmacro', 'umbembeddialog', ], - mode: 'classic', - stylesheets: [], + style_formats: [ + { + title: 'Headers', + items: [ + { title: 'Page header', block: 'h2' }, + { title: 'Section header', block: 'h3' }, + { title: 'Paragraph header', block: 'h4' }, + ], + }, + { + title: 'Blocks', + items: [{ title: 'Normal', block: 'p' }], + }, + { + title: 'Containers', + items: [ + { title: 'Quote', block: 'blockquote' }, + { title: 'Code', block: 'code' }, + ], + }, + ], + /** + * @description The maximum image size in pixels that can be inserted into the editor. + * @remarks This is registered and used by the UmbMediaPicker plugin + */ maxImageSize: 500, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts index 55cead9f0e..a2ee44ed9e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts @@ -1,15 +1,10 @@ -import { defaultExtendedValidElements, defaultFallbackConfig, defaultStyleFormats } from './input-tiny-mce.defaults.js'; -import { pastePreProcessHandler, uploadImageHandler } from './input-tiny-mce.handlers.js'; +import { defaultFallbackConfig } from './input-tiny-mce.defaults.js'; +import { pastePreProcessHandler } from './input-tiny-mce.handlers.js'; import { availableLanguages } from './input-tiny-mce.languages.js'; import { uriAttributeSanitizer } from './input-tiny-mce.sanitizer.js'; import { umbMeta } from '@umbraco-cms/backoffice/meta'; import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; -import { - type Editor, - type EditorEvent, - type RawEditorOptions, - renderEditor, -} from '@umbraco-cms/backoffice/external/tinymce'; +import { type Editor, type RawEditorOptions, renderEditor } from '@umbraco-cms/backoffice/external/tinymce'; import { UMB_CURRENT_USER_CONTEXT, UmbCurrentUser } from '@umbraco-cms/backoffice/current-user'; import { TinyMcePluginArguments, UmbTinyMcePluginBase } from '@umbraco-cms/backoffice/components'; import { loadManifestApi } from '@umbraco-cms/backoffice/extension-api'; @@ -158,23 +153,47 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) { } async #setTinyConfig() { - // create an object by merging the configuration onto the fallback config - // TODO: Seems like a too tight coupling between DataTypeConfigCollection and TinyMceConfig, I would love it begin more explicit what we take from DataTypeConfigCollection and parse on, but I understand that this gives some flexibility. Is this flexibility on purpose? - const configurationOptions: Record = { - ...defaultFallbackConfig, - ...(this.configuration ? this.configuration?.toObject() : {}), - }; + const dimensions = this.configuration?.getValueByAlias<{ width?: number; height?: number }>('dimensions'); // Map the stylesheets with server url - const stylesheets = configurationOptions.stylesheets.map( - (stylesheetPath: string) => `${this.#serverUrl}/css/${stylesheetPath.replace(/\\/g, '/')}`, - ); - const styleFormats = await this.getFormatStyles(configurationOptions.stylesheets); + const stylesheets = + this.configuration + ?.getValueByAlias('stylesheets') + ?.map((stylesheetPath: string) => `${this.#serverUrl}/css/${stylesheetPath.replace(/\\/g, '/')}`) ?? []; + const styleFormats = await this.getFormatStyles(stylesheets); + + // create an object by merging the configuration onto the fallback config + const configurationOptions: RawEditorOptions = { + ...defaultFallbackConfig, + height: dimensions?.height, + width: dimensions?.width, + content_css: stylesheets, + style_formats: styleFormats, + }; // no auto resize when a fixed height is set - if (!configurationOptions.dimensions?.height) { - configurationOptions.plugins ??= []; - configurationOptions.plugins.splice(configurationOptions.plugins.indexOf('autoresize'), 1); + if (!configurationOptions.height) { + if (Array.isArray(configurationOptions.plugins) && configurationOptions.plugins.includes('autoresize')) { + configurationOptions.plugins.splice(configurationOptions.plugins.indexOf('autoresize'), 1); + } + } + + // set the configured toolbar if any + const toolbar = this.configuration?.getValueByAlias('toolbar'); + if (toolbar) { + configurationOptions.toolbar = toolbar.join(' '); + } + + // set the configured inline mode + const mode = this.configuration?.getValueByAlias('mode'); + if (mode?.toLocaleLowerCase() === 'inline') { + configurationOptions.inline = true; + } + + // set the maximum image size + const maxImageSize = this.configuration?.getValueByAlias('maxImageSize'); + if (maxImageSize !== undefined) { + configurationOptions.maxImageSize = maxImageSize; } // set the default values that will not be modified via configuration @@ -193,36 +212,12 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) { statusbar: false, setup: (editor) => this.#editorSetup(editor), target: this._editorElement, - }; + paste_data_images: false, - // extend with configuration values - this._tinyConfig = { - ...this._tinyConfig, - content_css: stylesheets, - style_formats: styleFormats || defaultStyleFormats, - extended_valid_elements: defaultExtendedValidElements, - height: configurationOptions.height ?? 500, - invalid_elements: configurationOptions.invalidElements, - plugins: configurationOptions.plugins.map((x: any) => x.name), - toolbar: configurationOptions.toolbar.join(' '), - valid_elements: configurationOptions.validElements, - width: configurationOptions.width, + // Extend with configuration options + ...configurationOptions, }; - // Need to check if we are allowed to UPLOAD images - // This is done by checking if the insert image toolbar button is available - if (this.#isMediaPickerEnabled()) { - this._tinyConfig = { - ...this._tinyConfig, - // Update the TinyMCE Config object to allow pasting - images_upload_handler: uploadImageHandler, - automatic_uploads: false, - images_replace_blob_uris: false, - // This allows images to be pasted in & stored as Base64 until they get uploaded to server - paste_data_images: true, - }; - } - this.#setLanguage(); if (this.#editorRef) { @@ -282,7 +277,6 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) { editor.on('Change', () => this.#onChange(editor.getContent())); editor.on('Dirty', () => this.#onChange(editor.getContent())); editor.on('Keyup', () => this.#onChange(editor.getContent())); - editor.on('SetContent', () => this.#mediaHelper.uploadBlobImages(editor)); editor.on('focus', () => this.dispatchEvent(new CustomEvent('umb-rte-focus', { composed: true, bubbles: true }))); @@ -297,22 +291,6 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) { }); editor.on('init', () => editor.setContent(this.value?.toString() ?? '')); - - // If we can not find the insert image/media toolbar button - // Then we need to add an event listener to the editor - // That will update native browser drag & drop events - // To update the icon to show you can NOT drop something into the editor - if (this._tinyConfig.toolbar && !this.#isMediaPickerEnabled()) { - // Wire up the event listener - editor.on('dragstart dragend dragover draggesture dragdrop drop drag', (e: EditorEvent) => { - e.preventDefault(); - if (e.dataTransfer) { - e.dataTransfer.effectAllowed = 'none'; - e.dataTransfer.dropEffect = 'none'; - } - e.stopPropagation(); - }); - } } #onInit(editor: Editor) { @@ -326,21 +304,9 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) { this.dispatchEvent(new CustomEvent('change')); } - #isMediaPickerEnabled() { - const toolbar = this._tinyConfig.toolbar; - if (Array.isArray(toolbar) && (toolbar as string[]).includes('umbmediapicker')) { - return true; - } else if (typeof toolbar === 'string' && toolbar.includes('umbmediapicker')) { - return true; - } - - return false; - } - /** * Nothing rendered by default - TinyMCE initialisation creates * a target div and binds the RTE to that element - * @returns */ render() { return html`
`; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.handlers.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.handlers.ts index 9c647e1eea..4cfbc3c67a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.handlers.ts @@ -8,47 +8,3 @@ export const pastePreProcessHandler: RawEditorOptions['paste_preprocess'] = (_ed // convert i to em args.content = args.content.replace(/<\s*i([^>]*)>(.*?)<\s*\/\s*i([^>]*)>/g, '$2'); }; - -export const uploadImageHandler: RawEditorOptions['images_upload_handler'] = (blobInfo, progress) => { - return new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - xhr.open('POST', window.Umbraco?.Sys.ServerVariables.umbracoUrls.tinyMceApiBaseUrl + 'UploadImage'); - - xhr.onloadstart = () => - document.dispatchEvent(new CustomEvent('rte.file.uploading', { composed: true, bubbles: true })); - - xhr.onloadend = () => - document.dispatchEvent(new CustomEvent('rte.file.uploaded', { composed: true, bubbles: true })); - - xhr.upload.onprogress = (e) => progress((e.loaded / e.total) * 100); - - xhr.onerror = () => reject('Image upload failed due to a XHR Transport error. Code: ' + xhr.status); - - xhr.onload = () => { - if (xhr.status < 200 || xhr.status >= 300) { - reject('HTTP Error: ' + xhr.status); - return; - } - - const json = JSON.parse(xhr.responseText); - - if (!json || typeof json.tmpLocation !== 'string') { - reject('Invalid JSON: ' + xhr.responseText); - return; - } - - // Put temp location into localstorage (used to update the img with data-tmpimg later on) - localStorage.set(`tinymce__${blobInfo.blobUri()}`, json.tmpLocation); - - // We set the img src url to be the same as we started - // The Blob URI is stored in TinyMce's cache - // so the img still shows in the editor - resolve(blobInfo.blobUri()); - }; - - const formData = new FormData(); - formData.append('file', blobInfo.blob(), blobInfo.filename()); - - xhr.send(formData); - }); -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts index fe58231a3c..432fca76fe 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts @@ -6,6 +6,7 @@ import { UMB_MODAL_MANAGER_CONTEXT_TOKEN, } from '@umbraco-cms/backoffice/modal'; import { UMB_CURRENT_USER_CONTEXT, UmbCurrentUser } from '@umbraco-cms/backoffice/current-user'; +import { RawEditorOptions } from '@umbraco-cms/backoffice/external/tinymce'; interface MediaPickerTargetData { altText?: string; @@ -52,6 +53,18 @@ export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase { //stateSelector: 'img[data-udi]', TODO => Investigate where stateselector has gone, or if it is still needed onAction: () => this.#onAction(), }); + + // Register global options for the editor + this.editor.options.register('maxImageSize', { processor: 'number', default: 500 }); + + // Adjust Editor settings to allow pasting images + // but only if the umbmediapicker button is present + const toolbar = this.configuration?.getValueByAlias('toolbar'); + if (toolbar?.includes('umbmediapicker')) { + this.editor.options.set('paste_data_images', true); + this.editor.options.set('automatic_uploads', true); + this.editor.options.set('images_upload_handler', uploadImageHandler); + } } /* @@ -186,3 +199,48 @@ export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase { }); } } + +const uploadImageHandler: RawEditorOptions['images_upload_handler'] = (blobInfo, progress) => { + return new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + // FIXME: This must use the new TemporaryFileResource + xhr.open('POST', window.Umbraco?.Sys.ServerVariables.umbracoUrls.tinyMceApiBaseUrl + 'UploadImage'); + + xhr.onloadstart = () => + document.dispatchEvent(new CustomEvent('rte.file.uploading', { composed: true, bubbles: true })); + + xhr.onloadend = () => + document.dispatchEvent(new CustomEvent('rte.file.uploaded', { composed: true, bubbles: true })); + + xhr.upload.onprogress = (e) => progress((e.loaded / e.total) * 100); + + xhr.onerror = () => reject('Image upload failed due to a XHR Transport error. Code: ' + xhr.status); + + xhr.onload = () => { + if (xhr.status < 200 || xhr.status >= 300) { + reject('HTTP Error: ' + xhr.status); + return; + } + + const json = JSON.parse(xhr.responseText); + + if (!json || typeof json.tmpLocation !== 'string') { + reject('Invalid JSON: ' + xhr.responseText); + return; + } + + // Put temp location into localstorage (used to update the img with data-tmpimg later on) + localStorage.set(`tinymce__${blobInfo.blobUri()}`, json.tmpLocation); + + // We set the img src url to be the same as we started + // The Blob URI is stored in TinyMce's cache + // so the img still shows in the editor + resolve(blobInfo.blobUri()); + }; + + const formData = new FormData(); + formData.append('file', blobInfo.blob(), blobInfo.filename()); + + xhr.send(formData); + }); +}; From 32102b8ecb4f47923f3385ce151c1943430985a6 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 19 Dec 2023 16:24:13 +0100 Subject: [PATCH 07/23] use UmbTemporaryFileRepository to upload files --- .../plugins/tiny-mce-mediapicker.plugin.ts | 80 ++++++++----------- 1 file changed, 34 insertions(+), 46 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts index 432fca76fe..886c1fd1d4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts @@ -7,6 +7,8 @@ import { } from '@umbraco-cms/backoffice/modal'; import { UMB_CURRENT_USER_CONTEXT, UmbCurrentUser } from '@umbraco-cms/backoffice/current-user'; import { RawEditorOptions } from '@umbraco-cms/backoffice/external/tinymce'; +import { UmbTemporaryFileRepository } from '@umbraco-cms/backoffice/temporary-file'; +import { UmbId } from '@umbraco-cms/backoffice/id'; interface MediaPickerTargetData { altText?: string; @@ -30,11 +32,13 @@ export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase { #currentUser?: UmbCurrentUser; #modalContext?: UmbModalManagerContext; #currentUserContext?: typeof UMB_CURRENT_USER_CONTEXT.TYPE; + #temporaryFileRepository; constructor(args: TinyMcePluginArguments) { super(args); this.#mediaHelper = new UmbMediaHelper(); + this.#temporaryFileRepository = new UmbTemporaryFileRepository(args.host); this.consumeContext(UMB_MODAL_MANAGER_CONTEXT_TOKEN, (modalContext) => { this.#modalContext = modalContext; @@ -63,7 +67,7 @@ export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase { if (toolbar?.includes('umbmediapicker')) { this.editor.options.set('paste_data_images', true); this.editor.options.set('automatic_uploads', true); - this.editor.options.set('images_upload_handler', uploadImageHandler); + this.editor.options.set('images_upload_handler', this.#uploadImageHandler); } } @@ -198,49 +202,33 @@ export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase { } }); } + + #uploadImageHandler: RawEditorOptions['images_upload_handler'] = (blobInfo, progress) => { + return new Promise((resolve, reject) => { + // Fetch does not support progress, so we need to fake it. + progress(0); + + const id = UmbId.new(); + const blobUri = blobInfo.blobUri(); + const fileBlob = blobInfo.blob(); + const file = new File([fileBlob], blobInfo.filename(), { type: fileBlob.type }); + + progress(50); + + this.#temporaryFileRepository + .upload(id, file) + .then((response) => { + if (response.error) { + reject(response.error); + } else { + // Put temp location into localstorage (used to update the img with data-tmpimg later on) + sessionStorage.setItem(`tinymce__${blobUri}`, id); + + resolve(blobUri); + } + }) + .catch(reject) + .finally(() => progress(100)); + }); + }; } - -const uploadImageHandler: RawEditorOptions['images_upload_handler'] = (blobInfo, progress) => { - return new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - // FIXME: This must use the new TemporaryFileResource - xhr.open('POST', window.Umbraco?.Sys.ServerVariables.umbracoUrls.tinyMceApiBaseUrl + 'UploadImage'); - - xhr.onloadstart = () => - document.dispatchEvent(new CustomEvent('rte.file.uploading', { composed: true, bubbles: true })); - - xhr.onloadend = () => - document.dispatchEvent(new CustomEvent('rte.file.uploaded', { composed: true, bubbles: true })); - - xhr.upload.onprogress = (e) => progress((e.loaded / e.total) * 100); - - xhr.onerror = () => reject('Image upload failed due to a XHR Transport error. Code: ' + xhr.status); - - xhr.onload = () => { - if (xhr.status < 200 || xhr.status >= 300) { - reject('HTTP Error: ' + xhr.status); - return; - } - - const json = JSON.parse(xhr.responseText); - - if (!json || typeof json.tmpLocation !== 'string') { - reject('Invalid JSON: ' + xhr.responseText); - return; - } - - // Put temp location into localstorage (used to update the img with data-tmpimg later on) - localStorage.set(`tinymce__${blobInfo.blobUri()}`, json.tmpLocation); - - // We set the img src url to be the same as we started - // The Blob URI is stored in TinyMce's cache - // so the img still shows in the editor - resolve(blobInfo.blobUri()); - }; - - const formData = new FormData(); - formData.append('file', blobInfo.blob(), blobInfo.filename()); - - xhr.send(formData); - }); -}; From 74549521390eceb52316653713a143d451c7a1bb Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 8 Jan 2024 15:15:25 +0000 Subject: [PATCH 08/23] property-editor-ui-color-picker: sets value --- .../core/components/input-color/input-color.element.ts | 5 ++++- .../color-picker/property-editor-ui-color-picker.element.ts | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-color/input-color.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-color/input-color.element.ts index e978447dfb..ad5836627f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-color/input-color.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-color/input-color.element.ts @@ -31,7 +31,10 @@ export class UmbInputColorElement extends FormControlMixin(UmbLitElement) { render() { return html` - ${this._renderColors()} + ${this._renderColors()} `; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/color-picker/property-editor-ui-color-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/color-picker/property-editor-ui-color-picker.element.ts index 530fbdcfbf..bf54d82db6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/color-picker/property-editor-ui-color-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/color-picker/property-editor-ui-color-picker.element.ts @@ -35,6 +35,7 @@ export class UmbPropertyEditorUIColorPickerElement extends UmbLitElement impleme render() { return html``; } From 593dfa73e550a58845d73def7c658a3af09afbe2 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 9 Jan 2024 10:57:12 +0100 Subject: [PATCH 09/23] option is registered by plugin --- .../core/components/input-tiny-mce/input-tiny-mce.element.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts index a2ee44ed9e..8e90591ffd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts @@ -252,9 +252,6 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) { #editorSetup(editor: Editor) { editor.suffix = '.min'; - // register custom option maxImageSize - editor.options.register('maxImageSize', { processor: 'number', default: defaultFallbackConfig.maxImageSize }); - // instantiate plugins - these are already loaded in this.#loadPlugins // to ensure they are available before setting up the editor. // Plugins require a reference to the current editor as a param, so can not From 68637e17b84ef8cb3a468d8b4b408ec05c8903d6 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 9 Jan 2024 10:57:22 +0100 Subject: [PATCH 10/23] use UmbLocalizeController to get current language --- .../input-tiny-mce/input-tiny-mce.element.ts | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts index 8e90591ffd..adc9ac0c18 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts @@ -5,7 +5,6 @@ import { uriAttributeSanitizer } from './input-tiny-mce.sanitizer.js'; import { umbMeta } from '@umbraco-cms/backoffice/meta'; import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; import { type Editor, type RawEditorOptions, renderEditor } from '@umbraco-cms/backoffice/external/tinymce'; -import { UMB_CURRENT_USER_CONTEXT, UmbCurrentUser } from '@umbraco-cms/backoffice/current-user'; import { TinyMcePluginArguments, UmbTinyMcePluginBase } from '@umbraco-cms/backoffice/components'; import { loadManifestApi } from '@umbraco-cms/backoffice/extension-api'; import { ManifestTinyMcePlugin, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; @@ -24,6 +23,7 @@ import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app'; import { UmbStylesheetRepository } from '@umbraco-cms/backoffice/stylesheet'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; @customElement('umb-input-tiny-mce') export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) { @@ -34,8 +34,6 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) { private _tinyConfig: RawEditorOptions = {}; #mediaHelper = new UmbMediaHelper(); - #currentUser?: UmbCurrentUser; - #currentUserContext?: typeof UMB_CURRENT_USER_CONTEXT.TYPE; #plugins: Array UmbTinyMcePluginBase> = []; #editorRef?: Editor | null = null; #stylesheetRepository?: UmbStylesheetRepository; @@ -56,19 +54,6 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) { }); this.#stylesheetRepository = new UmbStylesheetRepository(this); - - // TODO => this breaks tests, removing for now will ignore user language - // and fall back to tinymce default language - // this.consumeContext(UMB_AUTH, (instance) => { - // this.#auth = instance; - // this.#observeCurrentUser(); - // }); - } - - async #observeCurrentUser() { - if (!this.#currentUserContext) return; - - this.observe(this.#currentUserContext.currentUser, (currentUser) => (this.#currentUser = currentUser)); } protected async firstUpdated(_changedProperties: PropertyValueMap | Map): Promise { @@ -231,7 +216,7 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) { /** * Sets the language to use for TinyMCE */ #setLanguage() { - const localeId = this.#currentUser?.languageIsoCode; + const localeId = this.localize.lang(); //try matching the language using full locale format let languageMatch = availableLanguages.find((x) => localeId?.localeCompare(x) === 0); @@ -297,8 +282,8 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) { } #onChange(value: string) { - super.value = value; - this.dispatchEvent(new CustomEvent('change')); + this.value = value; + this.dispatchEvent(new UmbChangeEvent()); } /** From 1cc237671efe42008d82b4da8ec4f16da2839c6c Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 9 Jan 2024 11:23:59 +0100 Subject: [PATCH 11/23] use object url to persist blob in memory --- .../tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts index 886c1fd1d4..6d3ed6fefa 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts @@ -209,7 +209,6 @@ export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase { progress(0); const id = UmbId.new(); - const blobUri = blobInfo.blobUri(); const fileBlob = blobInfo.blob(); const file = new File([fileBlob], blobInfo.filename(), { type: fileBlob.type }); @@ -220,12 +219,13 @@ export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase { .then((response) => { if (response.error) { reject(response.error); - } else { - // Put temp location into localstorage (used to update the img with data-tmpimg later on) - sessionStorage.setItem(`tinymce__${blobUri}`, id); - - resolve(blobUri); + return; } + + // Put temp location into localstorage (used to update the img with data-tmpimg later on) + const blobUri = window.URL.createObjectURL(fileBlob); + sessionStorage.setItem(`tinymce__${blobUri}`, id); + resolve(blobUri); }) .catch(reject) .finally(() => progress(100)); From 48546b323e369fe43806de197f178b7a91e2b4b2 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 9 Jan 2024 11:41:26 +0100 Subject: [PATCH 12/23] replace automatic_upload with our own to be able to locate images afterwards --- .../plugins/tiny-mce-mediapicker.plugin.ts | 61 ++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts index 6d3ed6fefa..a1960177a8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts @@ -66,8 +66,67 @@ export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase { const toolbar = this.configuration?.getValueByAlias('toolbar'); if (toolbar?.includes('umbmediapicker')) { this.editor.options.set('paste_data_images', true); - this.editor.options.set('automatic_uploads', true); + this.editor.options.set('automatic_uploads', false); this.editor.options.set('images_upload_handler', this.#uploadImageHandler); + + // Listen for SetContent to update images + this.editor.on('SetContent', async (e) => { + const content = e.content; + console.log('🚀 ~ UmbTinyMceMediaPickerPlugin ~ this.editor.on ~ content:', content); + + // Upload BLOB images (dragged/pasted ones) + // find src attribute where value starts with `blob:` + // search is case-insensitive and allows single or double quotes + if (content.search(/src=["']blob:.*?["']/gi) !== -1) { + const data = await this.editor.uploadImages(); + + // Once all images have been uploaded + for (const item of data) { + // Skip items that failed upload + if (item.status === false) { + return; + } + + // Select img element + const img = item.element; + + // Get img src + const imgSrc = img.getAttribute('src'); + const tmpLocation = sessionStorage.getItem(`tinymce__${imgSrc}`); + + // Select the img & add new attr which we can search for + // When its being persisted in RTE property editor + // To create a media item & delete this tmp one etc + this.editor.dom.setAttrib(img, 'data-tmpimg', tmpLocation); + + // Resize the image to the max size configured + // NOTE: no imagesrc passed into func as the src is blob://... + // We will append ImageResizing Querystrings on perist to DB with node save + this.#mediaHelper.sizeImageInEditor(args.editor, img); + } + + // Get all img where src starts with blob: AND does NOT have a data=tmpimg attribute + // This is most likely seen as a duplicate image that has already been uploaded + // editor.uploadImages() does not give us any indiciation that the image been uploaded already + const blobImageWithNoTmpImgAttribute = args.editor.dom.select("img[src^='blob:']:not([data-tmpimg])"); + + //For each of these selected items + blobImageWithNoTmpImgAttribute.forEach((imageElement) => { + const blobSrcUri = args.editor.dom.getAttrib(imageElement, 'src'); + + // Find the same image uploaded (Should be in LocalStorage) + // May already exist in the editor as duplicate image + // OR added to the RTE, deleted & re-added again + // So lets fetch the tempurl out of localstorage for that blob URI item + const tmpLocation = sessionStorage.getItem(`tinymce__${blobSrcUri}`); + + if (tmpLocation) { + this.#mediaHelper.sizeImageInEditor(this.editor, imageElement); + args.editor.dom.setAttrib(imageElement, 'data-tmpimg', tmpLocation); + } + }); + } + }); } } From a885cfb2063c2c69d8077b7c1fb37b872d0ea603 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 9 Jan 2024 11:46:50 +0100 Subject: [PATCH 13/23] update value of rich text editor --- .../property-editor-ui-tiny-mce.element.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/property-editor-ui-tiny-mce.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/property-editor-ui-tiny-mce.element.ts index 2c32a2eade..3bffb054c0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/property-editor-ui-tiny-mce.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/property-editor-ui-tiny-mce.element.ts @@ -4,6 +4,11 @@ import { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-re import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; +type RichTextEditorValue = { + blocks: object; + markup: string; +}; + /** * @element umb-property-editor-ui-tiny-mce */ @@ -11,8 +16,11 @@ import { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/prope export class UmbPropertyEditorUITinyMceElement extends UmbLitElement implements UmbPropertyEditorUiElement { #configuration?: UmbPropertyEditorConfigCollection; - @property({ type: String }) - value = ''; + @property({ type: Object }) + value: RichTextEditorValue = { + blocks: {}, + markup: '', + }; @property({ attribute: false }) public set config(config: UmbPropertyEditorConfigCollection | undefined) { @@ -20,7 +28,10 @@ export class UmbPropertyEditorUITinyMceElement extends UmbLitElement implements } #onChange(event: InputEvent) { - this.value = (event.target as HTMLInputElement).value; + this.value = { + blocks: {}, + markup: (event.target as HTMLInputElement).value, + }; this.dispatchEvent(new CustomEvent('property-value-change')); } @@ -28,7 +39,7 @@ export class UmbPropertyEditorUITinyMceElement extends UmbLitElement implements return html``; + .value=${this.value.markup}>`; } static styles = [UmbTextStyles]; From 6c24c2556a30852304f2f6ed35ef707aed4c4942 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 9 Jan 2024 11:59:04 +0100 Subject: [PATCH 14/23] add onSetup to toggle media picker --- .../uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts index a1960177a8..7487b73377 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts @@ -51,11 +51,16 @@ export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase { // this.#observeCurrentUser(); // }); - this.editor.ui.registry.addButton('umbmediapicker', { + this.editor.ui.registry.addToggleButton('umbmediapicker', { icon: 'image', tooltip: 'Media Picker', - //stateSelector: 'img[data-udi]', TODO => Investigate where stateselector has gone, or if it is still needed onAction: () => this.#onAction(), + onSetup: (api) => { + const changed = this.editor.selection.selectorChangedWithUnbind('img[data-udi]', (state) => + api.setActive(state), + ); + return () => changed; + }, }); // Register global options for the editor From 62dc85e8c1a340d7dc171b8d54ce28dfa5347fed Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 9 Jan 2024 12:13:02 +0100 Subject: [PATCH 15/23] simplify logic --- .../uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts index 7487b73377..84e80bacc0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts @@ -55,12 +55,8 @@ export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase { icon: 'image', tooltip: 'Media Picker', onAction: () => this.#onAction(), - onSetup: (api) => { - const changed = this.editor.selection.selectorChangedWithUnbind('img[data-udi]', (state) => - api.setActive(state), - ); - return () => changed; - }, + onSetup: (api) => () => + this.editor.selection.selectorChangedWithUnbind('img[data-udi]', (state) => api.setActive(state)), }); // Register global options for the editor @@ -77,7 +73,6 @@ export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase { // Listen for SetContent to update images this.editor.on('SetContent', async (e) => { const content = e.content; - console.log('🚀 ~ UmbTinyMceMediaPickerPlugin ~ this.editor.on ~ content:', content); // Upload BLOB images (dragged/pasted ones) // find src attribute where value starts with `blob:` From 5638d5e40f9e41b199115a6eb58066f55e0db65a Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 9 Jan 2024 13:48:59 +0100 Subject: [PATCH 16/23] allow pasting base64 images --- .../uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts index 84e80bacc0..76e0d56323 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts @@ -69,6 +69,8 @@ export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase { this.editor.options.set('paste_data_images', true); this.editor.options.set('automatic_uploads', false); this.editor.options.set('images_upload_handler', this.#uploadImageHandler); + // This allows images to be pasted in & stored as Base64 until they get uploaded to server + this.editor.options.set('images_replace_blob_uris', true); // Listen for SetContent to update images this.editor.on('SetContent', async (e) => { From 9e9e68fae2f9681ba72275bbb8368b357444a3bf Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 9 Jan 2024 13:54:25 +0100 Subject: [PATCH 17/23] use existing code to upload blob images --- .../plugins/tiny-mce-mediapicker.plugin.ts | 54 +------------------ .../src/shared/utils/media-helper.service.ts | 25 +++------ 2 files changed, 8 insertions(+), 71 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts index 76e0d56323..095c2ed6d2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts @@ -76,58 +76,8 @@ export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase { this.editor.on('SetContent', async (e) => { const content = e.content; - // Upload BLOB images (dragged/pasted ones) - // find src attribute where value starts with `blob:` - // search is case-insensitive and allows single or double quotes - if (content.search(/src=["']blob:.*?["']/gi) !== -1) { - const data = await this.editor.uploadImages(); - - // Once all images have been uploaded - for (const item of data) { - // Skip items that failed upload - if (item.status === false) { - return; - } - - // Select img element - const img = item.element; - - // Get img src - const imgSrc = img.getAttribute('src'); - const tmpLocation = sessionStorage.getItem(`tinymce__${imgSrc}`); - - // Select the img & add new attr which we can search for - // When its being persisted in RTE property editor - // To create a media item & delete this tmp one etc - this.editor.dom.setAttrib(img, 'data-tmpimg', tmpLocation); - - // Resize the image to the max size configured - // NOTE: no imagesrc passed into func as the src is blob://... - // We will append ImageResizing Querystrings on perist to DB with node save - this.#mediaHelper.sizeImageInEditor(args.editor, img); - } - - // Get all img where src starts with blob: AND does NOT have a data=tmpimg attribute - // This is most likely seen as a duplicate image that has already been uploaded - // editor.uploadImages() does not give us any indiciation that the image been uploaded already - const blobImageWithNoTmpImgAttribute = args.editor.dom.select("img[src^='blob:']:not([data-tmpimg])"); - - //For each of these selected items - blobImageWithNoTmpImgAttribute.forEach((imageElement) => { - const blobSrcUri = args.editor.dom.getAttrib(imageElement, 'src'); - - // Find the same image uploaded (Should be in LocalStorage) - // May already exist in the editor as duplicate image - // OR added to the RTE, deleted & re-added again - // So lets fetch the tempurl out of localstorage for that blob URI item - const tmpLocation = sessionStorage.getItem(`tinymce__${blobSrcUri}`); - - if (tmpLocation) { - this.#mediaHelper.sizeImageInEditor(this.editor, imageElement); - args.editor.dom.setAttrib(imageElement, 'data-tmpimg', tmpLocation); - } - }); - } + // Handle images that are pasted in + this.#mediaHelper.uploadBlobImages(this.editor, content); }); } } diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/media-helper.service.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/media-helper.service.ts index b7a4d14010..cb508c2dbe 100644 --- a/src/Umbraco.Web.UI.Client/src/shared/utils/media-helper.service.ts +++ b/src/Umbraco.Web.UI.Client/src/shared/utils/media-helper.service.ts @@ -94,8 +94,8 @@ export class UmbMediaHelper { * * @param editor */ - async uploadBlobImages(editor: Editor) { - const content = editor.getContent(); + async uploadBlobImages(editor: Editor, newContent?: string) { + const content = newContent ?? editor.getContent(); // Upload BLOB images (dragged/pasted ones) // find src attribute where value starts with `blob:` @@ -114,7 +114,7 @@ export class UmbMediaHelper { // Get img src const imgSrc = img.getAttribute('src'); - const tmpLocation = localStorage.get(`tinymce__${imgSrc}`); + const tmpLocation = sessionStorage.getItem(`tinymce__${imgSrc}`); // Select the img & add new attr which we can search for // When its being persisted in RTE property editor @@ -136,30 +136,17 @@ export class UmbMediaHelper { blobImageWithNoTmpImgAttribute.forEach((imageElement) => { const blobSrcUri = editor.dom.getAttrib(imageElement, 'src'); - // Find the same image uploaded (Should be in LocalStorage) + // Find the same image uploaded (Should be in SessionStorage) // May already exist in the editor as duplicate image // OR added to the RTE, deleted & re-added again - // So lets fetch the tempurl out of localstorage for that blob URI item - - const tmpLocation = localStorage.get(`tinymce__${blobSrcUri}`); + // So lets fetch the tempurl out of sessionStorage for that blob URI item + const tmpLocation = sessionStorage.getItem(`tinymce__${blobSrcUri}`); if (tmpLocation) { this.sizeImageInEditor(editor, imageElement); editor.dom.setAttrib(imageElement, 'data-tmpimg', tmpLocation); } }); } - - if (window.Umbraco?.Sys.ServerVariables.umbracoSettings.sanitizeTinyMce) { - /** prevent injecting arbitrary JavaScript execution in on-attributes. */ - const allNodes = Array.from(editor.dom.doc.getElementsByTagName('*')); - allNodes.forEach((node) => { - for (let i = 0; i < node.attributes.length; i++) { - if (node.attributes[i].name.startsWith('on')) { - node.removeAttribute(node.attributes[i].name); - } - } - }); - } } /** From 31eefa55703f1ed06dd66720ed4c72ff38041970 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 9 Jan 2024 13:57:18 +0100 Subject: [PATCH 18/23] add logic to sanitize all nodes to remove on-handlers --- .../input-tiny-mce/input-tiny-mce.element.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts index adc9ac0c18..a429861425 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts @@ -272,6 +272,22 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) { this.#onChange(editor.getContent()); }); + editor.on('SetContent', (e) => { + /** + * Prevent injecting arbitrary JavaScript execution in on-attributes. + * + * TODO: This used to be toggleable through server variables with window.Umbraco?.Sys.ServerVariables.umbracoSettings.sanitizeTinyMce + */ + const allNodes = Array.from(editor.dom.doc.getElementsByTagName('*')); + allNodes.forEach((node) => { + for (let i = 0; i < node.attributes.length; i++) { + if (node.attributes[i].name.startsWith('on')) { + node.removeAttribute(node.attributes[i].name); + } + } + }); + }); + editor.on('init', () => editor.setContent(this.value?.toString() ?? '')); } From 04a1bc7c8d15d9728f10930d8c2242b176a2a7b1 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 9 Jan 2024 14:00:40 +0100 Subject: [PATCH 19/23] re-implement legacy custom events for file uploading --- .../uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts index 095c2ed6d2..7b9e0e4ed1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts @@ -225,6 +225,8 @@ export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase { progress(50); + document.dispatchEvent(new CustomEvent('rte.file.uploading', { composed: true, bubbles: true })); + this.#temporaryFileRepository .upload(id, file) .then((response) => { @@ -239,7 +241,10 @@ export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase { resolve(blobUri); }) .catch(reject) - .finally(() => progress(100)); + .finally(() => { + progress(100); + document.dispatchEvent(new CustomEvent('rte.file.uploaded', { composed: true, bubbles: true })); + }); }); }; } From 01b685a58a0ff113d5e0b1d5a245fdf81e960cea Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 9 Jan 2024 14:03:52 +0100 Subject: [PATCH 20/23] fix docs --- .../src/shared/utils/media-helper.service.ts | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/media-helper.service.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/media-helper.service.ts index cb508c2dbe..b90178f579 100644 --- a/src/Umbraco.Web.UI.Client/src/shared/utils/media-helper.service.ts +++ b/src/Umbraco.Web.UI.Client/src/shared/utils/media-helper.service.ts @@ -6,10 +6,7 @@ import type { Editor, EditorEvent } from '@umbraco-cms/backoffice/external/tinym export class UmbMediaHelper { /** - * - * @param editor - * @param imageDomElement - * @param imgUrl + * Sizes an image in the editor */ async sizeImageInEditor(editor: Editor, imageDomElement: HTMLElement, imgUrl?: string) { const size = editor.dom.getSize(imageDomElement); @@ -36,11 +33,7 @@ export class UmbMediaHelper { } /** - * - * @param maxSize - * @param width - * @param height - * @returns + * Scales an image to the max size */ scaleToMaxSize(maxSize: number, width: number, height: number) { const retval = { width, height }; @@ -73,10 +66,7 @@ export class UmbMediaHelper { } /** - * - * @param imagePath - * @param options - * @returns + * Returns the URL of the processed image */ async getProcessedImageUrl(imagePath: string, options: any) { if (!options) { @@ -91,8 +81,7 @@ export class UmbMediaHelper { } /** - * - * @param editor + * Uploads blob images to the server */ async uploadBlobImages(editor: Editor, newContent?: string) { const content = newContent ?? editor.getContent(); @@ -150,9 +139,7 @@ export class UmbMediaHelper { } /** - * - * @param e - * @returns + * Handles the resize event */ async onResize( e: EditorEvent<{ From e795f39f1c152b4b408499881b1f02969e4cce8a Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 10 Jan 2024 14:27:25 +0100 Subject: [PATCH 21/23] make sure to unbind properly onSetup --- .../uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts index 7b9e0e4ed1..d1969701c5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts @@ -55,8 +55,12 @@ export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase { icon: 'image', tooltip: 'Media Picker', onAction: () => this.#onAction(), - onSetup: (api) => () => - this.editor.selection.selectorChangedWithUnbind('img[data-udi]', (state) => api.setActive(state)), + onSetup: (api) => { + const changed = this.editor.selection.selectorChangedWithUnbind('img[data-udi]', (state) => + api.setActive(state), + ); + return () => changed.unbind(); + }, }); // Register global options for the editor From 8d01a5dc65bbe5cde70a4560825ae9c6810b773d Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 11 Jan 2024 08:53:41 +0000 Subject: [PATCH 22/23] Initial value (on a new document) is `undefined` Made the `value` nullable. --- .../uis/tiny-mce/property-editor-ui-tiny-mce.element.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/property-editor-ui-tiny-mce.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/property-editor-ui-tiny-mce.element.ts index 3bffb054c0..641d384499 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/property-editor-ui-tiny-mce.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/property-editor-ui-tiny-mce.element.ts @@ -17,7 +17,7 @@ export class UmbPropertyEditorUITinyMceElement extends UmbLitElement implements #configuration?: UmbPropertyEditorConfigCollection; @property({ type: Object }) - value: RichTextEditorValue = { + value?: RichTextEditorValue = { blocks: {}, markup: '', }; @@ -39,7 +39,7 @@ export class UmbPropertyEditorUITinyMceElement extends UmbLitElement implements return html``; + .value=${this.value?.markup ?? ''}>`; } static styles = [UmbTextStyles]; From 7baf4f72ac9a2e3812b6a8130e9711706f4c8bf9 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 11 Jan 2024 08:54:19 +0000 Subject: [PATCH 23/23] Updated the `value` data structure the story --- .../uis/tiny-mce/property-editor-ui-tiny-mce.stories.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/property-editor-ui-tiny-mce.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/property-editor-ui-tiny-mce.stories.ts index 14e5bbd6b5..b7671b7cde 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/property-editor-ui-tiny-mce.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/property-editor-ui-tiny-mce.stories.ts @@ -80,7 +80,9 @@ const meta: Meta = { id: 'umb-property-editor-ui-tiny-mce', args: { config: undefined, - value: ` + value: { + blocks: {}, + markup: `

TinyMCE

I am a default value for the TinyMCE text editor story.

@@ -92,6 +94,7 @@ const meta: Meta = { Umbraco documentation

`, + }, }, };