diff --git a/src/Umbraco.Web.UI.Client/.gitignore b/src/Umbraco.Web.UI.Client/.gitignore index d333aae063..90f12bb2db 100644 --- a/src/Umbraco.Web.UI.Client/.gitignore +++ b/src/Umbraco.Web.UI.Client/.gitignore @@ -51,7 +51,6 @@ custom-elements.json # https://github.com/runem/web-component-analyzer#vscode # https://github.com/microsoft/vscode-custom-data vscode-html-custom-data.json -public/tinymce/* # Vite runtime files vite.config.ts.timestamp-*.mjs diff --git a/src/Umbraco.Web.UI.Client/.vscode/settings.json b/src/Umbraco.Web.UI.Client/.vscode/settings.json index 8fc1bce8e6..be94428edf 100644 --- a/src/Umbraco.Web.UI.Client/.vscode/settings.json +++ b/src/Umbraco.Web.UI.Client/.vscode/settings.json @@ -21,7 +21,6 @@ "stylesheet", "svgs", "templating", - "tinymce", "tiptap", "umbraco", "Uncategorized", diff --git a/src/Umbraco.Web.UI.Client/eslint.config.js b/src/Umbraco.Web.UI.Client/eslint.config.js index d5c5d55530..8aed0c6fcb 100644 --- a/src/Umbraco.Web.UI.Client/eslint.config.js +++ b/src/Umbraco.Web.UI.Client/eslint.config.js @@ -69,7 +69,7 @@ export default [ 'local-rules/exported-string-constant-naming': [ 'error', { - excludedFileNames: ['umbraco-package', 'input-tiny-mce.defaults'], // TODO: what to do about the tiny mce defaults? + excludedFileNames: ['umbraco-package'], }, ], '@typescript-eslint/no-non-null-assertion': 'off', diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 53ab077d46..03e0d8873f 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -37,8 +37,6 @@ "marked": "^15.0.7", "monaco-editor": "^0.52.2", "rxjs": "^7.8.1", - "tinymce": "^6.8.5", - "tinymce-i18n": "^24.12.30", "uuid": "^11.0.5" }, "devDependencies": { @@ -4430,10 +4428,6 @@ "resolved": "src/packages/templating", "link": true }, - "node_modules/@umbraco-backoffice/tiny-mce": { - "resolved": "src/packages/tiny-mce", - "link": true - }, "node_modules/@umbraco-backoffice/tiptap": { "resolved": "src/packages/tiptap", "link": true @@ -15882,18 +15876,6 @@ "dev": true, "license": "MIT" }, - "node_modules/tinymce": { - "version": "6.8.5", - "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-6.8.5.tgz", - "integrity": "sha512-qAL/FxL7cwZHj4BfaF818zeJJizK9jU5IQzTcSLL4Rj5MaJdiVblEj7aDr80VCV1w9h4Lak9hlnALhq/kVtN1g==", - "license": "MIT" - }, - "node_modules/tinymce-i18n": { - "version": "24.12.30", - "resolved": "https://registry.npmjs.org/tinymce-i18n/-/tinymce-i18n-24.12.30.tgz", - "integrity": "sha512-OOtJfr9plrXT5fuvCEXJ56QFKyFPCaaVcalj0UgJGv2AK8PNWhDVqzPef6MPlBkvVA1qgrZb7ZvfJC63wmkWjg==", - "license": "MIT" - }, "node_modules/tinyrainbow": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", @@ -17685,9 +17667,6 @@ "src/packages/templating": { "name": "@umbraco-backoffice/templating" }, - "src/packages/tiny-mce": { - "name": "@umbraco-backoffice/tiny-mce" - }, "src/packages/tiptap": { "name": "@umbraco-backoffice/tiptap" }, diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 37ff284dd7..dd899bb926 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -100,7 +100,6 @@ "./template": "./dist-cms/packages/templating/templates/index.js", "./temporary-file": "./dist-cms/packages/core/temporary-file/index.js", "./themes": "./dist-cms/packages/core/themes/index.js", - "./tiny-mce": "./dist-cms/packages/tiny-mce/index.js", "./tiptap": "./dist-cms/packages/tiptap/index.js", "./translation": "./dist-cms/packages/translation/index.js", "./tree": "./dist-cms/packages/core/tree/index.js", @@ -124,7 +123,6 @@ "./external/openid": "./dist-cms/external/openid/index.js", "./external/router-slot": "./dist-cms/external/router-slot/index.js", "./external/rxjs": "./dist-cms/external/rxjs/index.js", - "./external/tinymce": "./dist-cms/external/tinymce/index.js", "./external/tiptap": "./dist-cms/external/tiptap/index.js", "./external/uui": "./dist-cms/external/uui/index.js", "./external/uuid": "./dist-cms/external/uuid/index.js" @@ -227,8 +225,6 @@ "marked": "^15.0.7", "monaco-editor": "^0.52.2", "rxjs": "^7.8.1", - "tinymce": "^6.8.5", - "tinymce-i18n": "^24.12.30", "uuid": "^11.0.5" }, "devDependencies": { diff --git a/src/Umbraco.Web.UI.Client/public-assets/App_Plugins/tinyMcePlugin.js b/src/Umbraco.Web.UI.Client/public-assets/App_Plugins/tinyMcePlugin.js deleted file mode 100644 index f7a4a549b1..0000000000 --- a/src/Umbraco.Web.UI.Client/public-assets/App_Plugins/tinyMcePlugin.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class UmbTinyMceMockPlugin { - /** - * @param {TinyMcePluginArguments} args - */ - constructor(args) { - // Add your plugin code here - console.log('editor initialized', args) - } -} diff --git a/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts b/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts index b918aa3860..fb0294e5bb 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts @@ -42,7 +42,6 @@ const CORE_PACKAGES = [ import('../../packages/tags/umbraco-package.js'), import('../../packages/telemetry/umbraco-package.js'), import('../../packages/templating/umbraco-package.js'), - import('../../packages/tiny-mce/umbraco-package.js'), import('../../packages/tiptap/umbraco-package.js'), import('../../packages/translation/umbraco-package.js'), import('../../packages/ufm/umbraco-package.js'), diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts index ea30988212..e732990380 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts @@ -879,6 +879,7 @@ export default { orderBy: 'Order by', password: 'Password', path: 'Path', + pixels: 'pixels', pleasewait: 'One moment please...', previous: 'Previous', properties: 'Properties', diff --git a/src/Umbraco.Web.UI.Client/src/css/rte-content.css b/src/Umbraco.Web.UI.Client/src/css/rte-content.css index f6ec3cbabe..681920b919 100644 --- a/src/Umbraco.Web.UI.Client/src/css/rte-content.css +++ b/src/Umbraco.Web.UI.Client/src/css/rte-content.css @@ -1,37 +1,4 @@ -/* TinyMCE specific styles */ -#tinymce { - .umb-embed-holder { - position: relative; - } - - .umb-embed-holder > * { - user-select: none; - pointer-events: none; - } - - .umb-embed-holder[data-mce-selected] { - outline: 2px solid var(--uui-palette-spanish-pink-light); - } - - .umb-embed-holder::before { - z-index: 1000; - width: 100%; - height: 100%; - position: absolute; - content: ' '; - } - - .umb-embed-holder[data-mce-selected]::before { - background: rgba(0, 0, 0, 0.025); - } - - *[data-mce-selected='inline-boundary'] { - background: rgba(0, 0, 0, 0.025); - outline: 2px solid var(--uui-palette-spanish-pink-light); - } -} - -/* General styles (that apply to both TinyMCE and Tiptap RTEs) */ +/* General RTEs styles */ .umb-macro-holder { border: 3px dotted red; diff --git a/src/Umbraco.Web.UI.Client/src/external/tinymce/index.ts b/src/Umbraco.Web.UI.Client/src/external/tinymce/index.ts deleted file mode 100644 index 3d2a1e8fd6..0000000000 --- a/src/Umbraco.Web.UI.Client/src/external/tinymce/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* eslint local-rules/enforce-umbraco-external-imports: 0 */ -/** - * TinyMce is a CommonJS module, but in order to make @web/test-runner happy - * we need to load it as a default and then manually register it in the browser - * as a global variable, so that we can find it and use it in our tests. - * We are also loading the default icons, so that we can use them outside of a TinyMce instance. - */ -import 'tinymce'; -import 'tinymce/icons/default/icons.js'; -import type { RawEditorOptions, TinyMCE } from 'tinymce'; - -export const tinymce = window.tinymce; -export type * from 'tinymce'; - -export const defaultConfig: RawEditorOptions = { - base_url: '/umbraco/backoffice/tinymce', -}; - -/* Initialize TinyMCE */ -export function renderEditor(userConfig?: RawEditorOptions) { - const config = { ...defaultConfig, ...userConfig }; - return tinymce.init(config); -} - -// Declare a global variable to hold the TinyMCE instance -declare global { - interface Window { - /** - * @TJS-ignore - */ - tinymce: TinyMCE; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts index 0567c880a5..c317402c4c 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts @@ -1088,101 +1088,6 @@ export const data: Array = [ { alias: 'overlaySize', value: 'medium' }, ], }, - { - name: 'Rich Text Editor (TinyMCE)', - id: 'dt-richTextEditorTinyMce', - parent: null, - editorAlias: 'Umbraco.RichText', - editorUiAlias: 'Umb.PropertyEditorUi.TinyMCE', - hasChildren: false, - isFolder: false, - isDeletable: true, - canIgnoreStartNodes: false, - values: [ - { - alias: 'hideLabel', - value: true, - }, - { alias: 'dimensions', value: { height: 500 } }, - { alias: 'maxImageSize', value: 500 }, - { alias: 'ignoreUserStartNodes', value: false }, - { - alias: 'validElements', - value: - '+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,video[*],audio[*],picture[*],source[*],canvas[*]', - }, - { alias: 'invalidElements', value: 'font' }, - { alias: 'stylesheets', value: ['/rte-styles.css'] }, - { - alias: 'toolbar', - value: [ - 'sourcecode', - 'undo', - 'redo', - 'styles', - 'fontfamily', - 'fontsize', - 'forecolor', - 'backcolor', - 'blockquote', - 'removeformat', - 'bold', - 'italic', - 'underline', - 'strikethrough', - 'alignleft', - 'aligncenter', - 'alignright', - 'alignjustify', - 'bullist', - 'numlist', - 'outdent', - 'indent', - 'link', - 'unlink', - 'anchor', - 'hr', - 'subscript', - 'superscript', - 'charmap', - 'rtl', - 'ltr', - 'table', - 'umbmediapicker', - 'umbembeddialog', - ], - }, - { - alias: 'plugins', - value: [ - { - name: 'anchor', - }, - { - name: 'charmap', - }, - { - name: 'table', - }, - { - name: 'lists', - }, - { - name: 'advlist', - }, - { - name: 'autolink', - }, - { - name: 'directionality', - }, - { - name: 'searchreplace', - }, - ], - }, - ], - }, { name: 'Label', id: 'dt-label', diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.data.ts index 540384e57c..6029b2059d 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.data.ts @@ -105,30 +105,6 @@ export const data: Array = [ labelOnTop: false, }, }, - { - id: '1', - container: { - id: 'all-properties-group-key', - }, - alias: 'richTextEditorTinyMce', - name: 'Rich Text editor (TinyMce)', - description: 'Some description to test with a long description.', - dataType: { - id: 'dt-richTextEditorTinyMce', - }, - variesByCulture: false, - variesBySegment: false, - sortOrder: 0, - validation: { - mandatory: true, - mandatoryMessage: null, - regEx: null, - regExMessage: null, - }, - appearance: { - labelOnTop: false, - }, - }, { id: '2', container: { @@ -1802,31 +1778,6 @@ export const data: Array = [ labelOnTop: false, }, }, - { - id: '2dd0d4d2-cda8-4ac2-affd-a69fc10382b1', - container: { id: 'the-simplest-document-type-id-container' }, - alias: 'tinymce', - name: 'TinyMCE', - description: ` -This is to test the default configuration of the TinyMCE editor. - -Search for **dt-richTextEditorTinyMce** in the codebase to find the configuration and add configuration values. - -**NB!** If this throws an error in console, make sure that \`@umbraco-cms/backoffice/block-rte\` is available in the importmap.`, - dataType: { id: 'dt-richTextEditorTinyMce' }, - variesByCulture: false, - variesBySegment: false, - sortOrder: 0, - validation: { - mandatory: false, - mandatoryMessage: null, - regEx: null, - regExMessage: null, - }, - appearance: { - labelOnTop: false, - }, - }, ], containers: [ { diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.data.ts index 93c3322f44..7b3b190c43 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.data.ts @@ -948,25 +948,6 @@ export const data: Array = [ `, }, }, - { - alias: 'tinymce', - editorAlias: 'Umb.PropertyEditorUi.TinyMCE', - culture: null, - segment: null, - value: { - blocks: undefined, - markup: ` -

- Some value for the RTE with an external link and an internal link foo foo -

-
Macro alias: TestMacro
-

- Installer illustration -

-

End of test content

- `, - }, - }, ], }, { diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/stylesheet/stylesheet.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/stylesheet/stylesheet.data.ts index 14b53b5bc3..8494e13e0f 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/stylesheet/stylesheet.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/stylesheet/stylesheet.data.ts @@ -18,7 +18,7 @@ export const data: Array = [ content: ` /** RTE Stylesheet */ -#editor, #tinymce { +#editor { background-color: pink; font-size: 1.5rem; } diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/manifests.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/manifests.handlers.ts index 69bcdc54d0..d37caf6bb0 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/manifests.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/manifests.handlers.ts @@ -45,18 +45,6 @@ const privateManifests: PackageManifestResponse = [ propertyEditorSchema: 'Umbraco.TextBox', }, }, - { - type: 'tinyMcePlugin', - alias: 'My.TinyMcePlugin.Custom', - name: 'My Custom TinyMce Plugin', - js: '/App_Plugins/tinyMcePlugin.js', - meta: { - config: { - plugins: ['wordcount'], - statusbar: true, - }, - }, - }, ], }, { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/index.ts index 4a12b45770..5b67cd5326 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/index.ts @@ -5,7 +5,6 @@ export * from './reference/index.js'; export * from './repository/index.js'; export * from './search/index.js'; export * from './url/index.js'; -export * from './utils/index.js'; export { UmbMediaAuditLogRepository } from './audit-log/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/utils/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/utils/index.ts deleted file mode 100644 index 0b3e67c41e..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/utils/index.ts +++ /dev/null @@ -1,132 +0,0 @@ -// TODO => this is NOT a full reimplementation of the existing media helper service, currently -// contains only functions referenced by the TinyMCE editor -// TODO: This should not be done in this way, we need to split this into separate defined helper methods. This is also very specific to TinyMCE, so should be named that way. [NL] - -import { getProcessedImageUrl } from '@umbraco-cms/backoffice/utils'; -import type { Editor } from '@umbraco-cms/backoffice/external/tinymce'; - -/** - * Sizes an image in the editor - * @param editor - * @param imageDomElement - * @param imgUrl - */ -export async function sizeImageInEditor(editor: Editor, imageDomElement: HTMLElement, imgUrl?: string) { - const size = editor.dom.getSize(imageDomElement); - const maxImageSize = editor.options.get('maxImageSize'); - - if (maxImageSize && maxImageSize > 0) { - const newSize = scaleToMaxSize(maxImageSize, size.w, size.h); - - editor.dom.setAttribs(imageDomElement, { width: Math.round(newSize.width), height: Math.round(newSize.height) }); - - // Images inserted via Media Picker will have a URL we can use for ImageResizer QueryStrings - // Images pasted/dragged in are not persisted to media until saved & thus will need to be added - if (imgUrl) { - const resizedImgUrl = await getProcessedImageUrl(imgUrl, { - width: newSize.width, - height: newSize.height, - }); - - editor.dom.setAttrib(imageDomElement, 'data-mce-src', resizedImgUrl); - } - - editor.execCommand('mceAutoResize', false); - } -} - -/** - * Scales an image to the max size - * @param maxSize - * @param width - * @param height - */ -export function scaleToMaxSize(maxSize: number, width: number, height: number) { - const retval = { width, height }; - - const maxWidth = maxSize; // Max width for the image - const maxHeight = maxSize; // Max height for the image - let ratio = 0; // Used for aspect ratio - - // Check if the current width is larger than the max - if (width > maxWidth) { - ratio = maxWidth / width; // get ratio for scaling image - - retval.width = maxWidth; - retval.height = height * ratio; - - height = height * ratio; // Reset height to match scaled image - width = width * ratio; // Reset width to match scaled image - } - - // Check if current height is larger than max - if (height > maxHeight) { - ratio = maxHeight / height; // get ratio for scaling image - - retval.height = maxHeight; - retval.width = width * ratio; - width = width * ratio; // Reset width to match scaled image - } - - return retval; -} - -/** - * Uploads blob images to the server - * @param editor - * @param newContent - */ -export async function uploadBlobImages(editor: Editor, newContent?: string) { - const content = newContent ?? editor.getContent(); - - // 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 editor.uploadImages(); - // Once all images have been uploaded - data.forEach((item) => { - // 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 - 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 - sizeImageInEditor(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 = editor.dom.select('img[src^="blob:"]:not([data-tmpimg])'); - - //For each of these selected items - blobImageWithNoTmpImgAttribute.forEach((imageElement) => { - const blobSrcUri = editor.dom.getAttrib(imageElement, 'src'); - - // 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 sessionStorage for that blob URI item - const tmpLocation = sessionStorage.getItem(`tinymce__${blobSrcUri}`); - if (tmpLocation) { - sizeImageInEditor(editor, imageElement); - editor.dom.setAttrib(imageElement, 'data-tmpimg', tmpLocation); - } - }); - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/manifests.ts index b95810b145..345ac8994e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/manifests.ts @@ -1,11 +1,9 @@ import { manifests as modalManifests } from './link-picker-modal/manifests.js'; import { manifests as monacoMarkdownEditorManifests } from './monaco-markdown-editor-action/manifests.js'; import { manifests as propertyEditorManifests } from './property-editor/manifests.js'; -import { manifests as tinyMceManifests } from './tiny-mce-plugin/manifests.js'; export const manifests: Array = [ ...modalManifests, ...monacoMarkdownEditorManifests, ...propertyEditorManifests, - ...tinyMceManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/tiny-mce-plugin/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/tiny-mce-plugin/manifests.ts deleted file mode 100644 index 8d7455f5e5..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/tiny-mce-plugin/manifests.ts +++ /dev/null @@ -1,22 +0,0 @@ -export const manifests = [ - { - type: 'tinyMcePlugin', - alias: 'Umb.TinyMcePlugin.MultiUrlPicker', - name: 'Multi Url Picker TinyMCE Plugin', - js: () => import('./tiny-mce-multi-url-picker.plugin.js'), - meta: { - toolbar: [ - { - alias: 'link', - label: 'Insert/Edit link', - icon: 'link', - }, - { - alias: 'unlink', - label: 'Remove link', - icon: 'unlink', - }, - ], - }, - }, -]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/tiny-mce-plugin/tiny-mce-multi-url-picker.plugin.ts b/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/tiny-mce-plugin/tiny-mce-multi-url-picker.plugin.ts deleted file mode 100644 index 308c76fee3..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/tiny-mce-plugin/tiny-mce-multi-url-picker.plugin.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { UMB_LINK_PICKER_MODAL } from '../link-picker-modal/link-picker-modal.token.js'; -import type { UmbLinkPickerLink, UmbLinkPickerLinkType } from '../link-picker-modal/types.js'; -import type { UmbLinkPickerModalValue } from '../link-picker-modal/link-picker-modal.token.js'; -import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api'; -import { UmbTinyMcePluginBase } from '@umbraco-cms/backoffice/tiny-mce'; -import { umbOpenModal } from '@umbraco-cms/backoffice/modal'; -import type { TinyMcePluginArguments } from '@umbraco-cms/backoffice/tiny-mce'; - -type AnchorElementAttributes = { - 'data-anchor'?: string | null; - href?: string | null; - target?: string | null; - text?: string; - title?: string | null; - type?: UmbLinkPickerLinkType; - rel?: string | null; -}; - -export default class UmbTinyMceMultiUrlPickerPlugin extends UmbTinyMcePluginBase { - #linkPickerData?: UmbLinkPickerModalValue; - - #anchorElement?: HTMLAnchorElement; - - constructor(args: TinyMcePluginArguments) { - super(args); - - const localize = new UmbLocalizationController(args.host); - - this.editor.ui.registry.addToggleButton('link', { - icon: 'link', - tooltip: localize.term('general_addEditLink'), - onAction: () => this.showDialog(), - onSetup: (api) => { - const changed = this.editor.selection.selectorChangedWithUnbind('a', (state) => api.setActive(state)); - return () => changed.unbind(); - }, - }); - - this.editor.ui.registry.addToggleButton('unlink', { - icon: 'unlink', - tooltip: localize.term('general_removeLink'), - onAction: () => args.editor.execCommand('unlink'), - onSetup: (api) => { - const changed = this.editor.selection.selectorChangedWithUnbind('a', (state) => api.setActive(state)); - return () => changed.unbind(); - }, - }); - } - - async showDialog() { - const selectedElm = this.editor.selection.getNode(); - this.#anchorElement = this.editor.dom.getParent(selectedElm, 'a[href]') as HTMLAnchorElement; - - if (!this.#anchorElement) { - this.#openLinkPicker({ name: this.editor.selection.getContent({ format: 'text' }) }); - return; - } - - let url = this.#anchorElement.getAttribute('href') ?? this.#anchorElement.href ?? ''; - - const queryString = this.#anchorElement.getAttribute('data-anchor') ?? ''; - if (queryString && url.endsWith(queryString)) { - url = url.substring(0, url.indexOf(queryString)); - } - - const currentTarget: UmbLinkPickerLink = { - name: this.#anchorElement.title || this.#anchorElement.textContent, - target: this.#anchorElement.target, - queryString: queryString, - type: (this.#anchorElement.type as UmbLinkPickerLinkType) ?? 'external', - unique: url.includes('localLink:') ? url.substring(url.indexOf(':') + 1, url.indexOf('}')) : null, - url: url, - }; - - this.#openLinkPicker(currentTarget); - } - - async #openLinkPicker(currentTarget?: UmbLinkPickerLink) { - const linkPickerData = await umbOpenModal(this, UMB_LINK_PICKER_MODAL, { - data: { - config: {}, - index: null, - isNew: currentTarget?.url === undefined, - }, - value: { - link: currentTarget ?? {}, - }, - }).catch(() => undefined); - - if (!linkPickerData) return; - - // TODO: This is a workaround for the issue where the link picker modal is returning a frozen object, and we need to extract the link into smaller parts to avoid the frozen object issue. - this.#linkPickerData = { link: { ...linkPickerData.link } }; - - this.#updateLink(); - } - - #createElemAttributes() { - const link = this.#linkPickerData!.link; - - const anchor: AnchorElementAttributes = { - href: link.url ?? '', - title: link.name ?? link.url ?? '', - target: link.target, - type: link.type ?? 'external', - rel: link.target === '_blank' ? 'noopener' : null, - }; - - if (link.queryString) { - anchor['data-anchor'] = link.queryString; - - if (link.queryString.startsWith('?')) { - anchor.href += !anchor.href ? '/' + link.queryString : link.queryString; - } else if (link.queryString.startsWith('#')) { - anchor.href += link.queryString; - } - } - - return anchor; - } - - #insertLink() { - if (this.#anchorElement) { - this.editor.dom.setAttribs(this.#anchorElement, this.#createElemAttributes()); - this.editor.selection.select(this.#anchorElement); - this.editor.execCommand('mceEndTyping'); - return; - } - - // If there is no selected content, we can't insert a link - // as TinyMCE needs selected content for this, so instead we - // create a new dom element and insert it, using the chosen - // link name as the content. - - if (this.editor.selection.getContent() !== '') { - this.editor.execCommand('CreateLink', false, this.#createElemAttributes()); - return; - } - - // Using the target url as a fallback, as href might be confusing with a local link - const linkContent = - typeof this.#linkPickerData?.link.name !== 'undefined' && this.#linkPickerData?.link.name !== '' - ? this.#linkPickerData?.link.name - : this.#linkPickerData?.link.url; - - // only insert if link has content - if (linkContent) { - const domElement = this.editor.dom.createHTML('a', this.#createElemAttributes(), linkContent); - this.editor.execCommand('mceInsertContent', false, domElement); - } - } - - #updateLink() { - // if an anchor exists, check that it is appropriately prefixed - if ( - this.#linkPickerData?.link.queryString && - !this.#linkPickerData.link.queryString.startsWith('?') && - !this.#linkPickerData.link.queryString.startsWith('#') - ) { - this.#linkPickerData.link.queryString = - (this.#linkPickerData.link.queryString.startsWith('=') ? '#' : '?') + this.#linkPickerData.link.queryString; - } - - // the href might be an external url, so check the value for an anchor/qs - // href has the anchor re-appended later, hence the reset here to avoid duplicating the anchor - if (this.#linkPickerData && !this.#linkPickerData?.link.queryString) { - const urlParts = this.#linkPickerData?.link.url?.split(/(#|\?)/); - if (urlParts?.length === 3) { - this.#linkPickerData.link.url = urlParts[0]; - this.#linkPickerData.link.queryString = urlParts[1] + urlParts[2]; - } - } - - if ( - !this.#linkPickerData?.link.url && - !this.#linkPickerData?.link.queryString && - !this.#linkPickerData?.link.unique - ) { - this.editor.execCommand('unlink'); - return; - } - - //if we have an id, it must be a locallink:id - if (this.#linkPickerData?.link.unique) { - this.#linkPickerData.link.url = '/{localLink:' + this.#linkPickerData.link.unique + '}'; - this.#insertLink(); - return; - } - - if (!this.#linkPickerData?.link.url) { - this.#linkPickerData.link.url = ''; - } - - // Is email and not //user@domain.com and protocol (e.g. mailto:, sip:) is not specified - if ( - this.#linkPickerData?.link.url.includes('@') && - !this.#linkPickerData.link.url.includes('//') && - !this.#linkPickerData.link.url.includes(':') - ) { - // assume it's a mailto link - this.#linkPickerData.link.url = 'mailto:' + this.#linkPickerData?.link.url; - this.#insertLink(); - return; - } - - // Is www. prefixed - if (/^\s*www\./i.test(this.#linkPickerData?.link.url)) { - this.#linkPickerData.link.url = 'http://' + this.#linkPickerData.link.url; - } - - this.#insertLink(); - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/dimensions/manifest.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/dimensions/manifest.ts new file mode 100644 index 0000000000..eb759e39fb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/dimensions/manifest.ts @@ -0,0 +1,11 @@ +export const manifest: UmbExtensionManifest = { + type: 'propertyEditorUi', + alias: 'Umb.PropertyEditorUI.Dimensions', + name: 'Dimensions Property Editor UI', + element: () => import('./property-editor-ui-dimensions.element.js'), + meta: { + label: 'Dimensions', + icon: 'icon-fullscreen-alt', + group: 'common', + }, +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/dimensions/property-editor-ui-tiny-mce-dimensions-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/dimensions/property-editor-ui-dimensions.element.ts similarity index 68% rename from src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/dimensions/property-editor-ui-tiny-mce-dimensions-configuration.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/property-editors/dimensions/property-editor-ui-dimensions.element.ts index 893938217d..a665865f74 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/dimensions/property-editor-ui-tiny-mce-dimensions-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/dimensions/property-editor-ui-dimensions.element.ts @@ -1,14 +1,14 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { customElement, html, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; /** - * @element umb-property-editor-ui-tiny-mce-dimensions-configuration + * @element umb-property-editor-ui-dimensions */ -@customElement('umb-property-editor-ui-tiny-mce-dimensions-configuration') -export class UmbPropertyEditorUITinyMceDimensionsConfigurationElement extends UmbLitElement { +@customElement('umb-property-editor-ui-dimensions') +export class UmbPropertyEditorUIDimensionsElement extends UmbLitElement { @property({ type: Object }) value: { width?: number; height?: number } = {}; @@ -22,29 +22,33 @@ export class UmbPropertyEditorUITinyMceDimensionsConfigurationElement extends Um } override render() { - return html` - x + .value=${this.value?.width?.toString() ?? ''}> + + × - pixels`; + .value=${this.value?.height?.toString() ?? ''}> + + pixels + `; } static override readonly styles = [UmbTextStyles]; } -export default UmbPropertyEditorUITinyMceDimensionsConfigurationElement; +export { UmbPropertyEditorUIDimensionsElement as element }; declare global { interface HTMLElementTagNameMap { - 'umb-property-editor-ui-tiny-mce-dimensions-configuration': UmbPropertyEditorUITinyMceDimensionsConfigurationElement; + 'umb-property-editor-ui-dimensions': UmbPropertyEditorUIDimensionsElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/dimensions/property-editor-ui-dimensions.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/dimensions/property-editor-ui-dimensions.stories.ts new file mode 100644 index 0000000000..b48b92542f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/dimensions/property-editor-ui-dimensions.stories.ts @@ -0,0 +1,15 @@ +import type { UmbPropertyEditorUIDimensionsElement } from './property-editor-ui-dimensions.element.js'; +import type { Meta, StoryFn } from '@storybook/web-components'; +import { html } from '@umbraco-cms/backoffice/external/lit'; + +import './property-editor-ui-dimensions.element.js'; + +export default { + title: 'Property Editor UIs/Dimensions', + component: 'umb-property-editor-ui-dimensions', + id: 'umb-property-editor-ui-dimensions', +} as Meta; + +export const AAAOverview: StoryFn = () => + html` `; +AAAOverview.storyName = 'Overview'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/manifests.ts index 4ded362fc7..092f9d72b7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/manifests.ts @@ -1,5 +1,6 @@ import { manifest as acceptedType } from './accepted-types/manifests.js'; import { manifest as colorEditor } from './color-swatches-editor/manifests.js'; +import { manifest as dimensions } from './dimensions/manifest.js'; import { manifest as numberRange } from './number-range/manifests.js'; import { manifest as orderDirection } from './order-direction/manifests.js'; import { manifest as overlaySize } from './overlay-size/manifests.js'; @@ -41,6 +42,7 @@ export const manifests: Array = [ ...contentPickerManifests, acceptedType, colorEditor, + dimensions, numberRange, orderDirection, overlaySize, diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/property-editors/stylesheet-picker/property-editor-ui-stylesheet-picker.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/property-editors/stylesheet-picker/property-editor-ui-stylesheet-picker.stories.ts index 8363c00a0c..70a4bdc7f9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/property-editors/stylesheet-picker/property-editor-ui-stylesheet-picker.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/property-editors/stylesheet-picker/property-editor-ui-stylesheet-picker.stories.ts @@ -1,10 +1,10 @@ import { umbDataTypeMockDb } from '../../../../../mocks/data/data-type/data-type.db.js'; import { html } from '@umbraco-cms/backoffice/external/lit'; import type { Meta } from '@storybook/web-components'; - -import './property-editor-ui-tiny-mce-stylesheets-configuration.element.js'; import type { UmbDataTypeDetailModel } from '@umbraco-cms/backoffice/data-type'; +import './property-editor-ui-stylesheet-picker.element.js'; + const dataTypeData = umbDataTypeMockDb.read('dt-richTextEditor') as unknown as UmbDataTypeDetailModel; export default { diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/index.ts deleted file mode 100644 index b1439b57ca..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './input-tiny-mce/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/input-tiny-mce/index.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/input-tiny-mce/index.ts deleted file mode 100644 index cc2f0fa312..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/input-tiny-mce/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './input-tiny-mce.defaults.js'; -export * from './input-tiny-mce.element.js'; -export * from './input-tiny-mce.languages.js'; -export * from './input-tiny-mce.sanitizer.js'; -export * from './input-tiny-mce.handlers.js'; -export * from './tiny-mce-plugin.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/input-tiny-mce/input-tiny-mce.defaults.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/input-tiny-mce/input-tiny-mce.defaults.ts deleted file mode 100644 index a9c7b6255b..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/input-tiny-mce/input-tiny-mce.defaults.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { UMB_CONTEXT_REQUEST_EVENT_TYPE, type UmbContextRequestEvent } from '@umbraco-cms/backoffice/context-api'; -import type { RawEditorOptions } from '@umbraco-cms/backoffice/external/tinymce'; -import { UUIIconRequestEvent } from '@umbraco-cms/backoffice/external/uui'; - -//export const UMB_BLOCK_ENTRY_WEB_COMPONENTS_ABSOLUTE_PATH = '/umbraco/backoffice/packages/block/block-rte/index.js'; -export const UMB_BLOCK_ENTRY_WEB_COMPONENTS_ABSOLUTE_PATH = '@umbraco-cms/backoffice/block-rte'; - -//we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce -//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[*],-code', - invalid_elements: 'font', - extended_valid_elements: - '@[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', - 'bold', - 'italic', - 'alignleft', - 'aligncenter', - 'alignright', - 'bullist', - 'numlist', - 'outdent', - 'indent', - 'link', - 'umbmediapicker', - 'umbembeddialog', - ], - - init_instance_callback: function (editor) { - // The following code is the context api proxy. [NL] - // It re-dispatches the context api request event to the origin target of this modal, in other words the element that initiated the modal. [NL] - editor.dom.doc.addEventListener(UMB_CONTEXT_REQUEST_EVENT_TYPE, ((event: UmbContextRequestEvent) => { - if (!editor.iframeElement) return; - - event.stopImmediatePropagation(); - editor.iframeElement.dispatchEvent(event.clone()); - }) as EventListener); - - // Proxy for retrieving icons from outside the iframe [NL] - editor.dom.doc.addEventListener(UUIIconRequestEvent.ICON_REQUEST, ((event: UUIIconRequestEvent) => { - if (!editor.iframeElement) return; - - const newEvent = new UUIIconRequestEvent(UUIIconRequestEvent.ICON_REQUEST, { - detail: event.detail, - }); - editor.iframeElement.dispatchEvent(newEvent); - if (newEvent.icon !== null) { - event.acceptRequest(newEvent.icon); - } - }) as EventListener); - - // Transfer our import-map to the iframe: [NL] - const importMapTag = document.head.querySelector('script[type="importmap"]'); - if (importMapTag) { - const importMap = document.createElement('script'); - importMap.type = 'importmap'; - importMap.text = importMapTag.innerHTML; - editor.dom.doc.head.appendChild(importMap); - } - - // Transfer our stylesheets to the iframe: [NL] - const stylesheetTags = document.head.querySelectorAll('link[rel="stylesheet"]'); - stylesheetTags.forEach((stylesheetTag) => { - const stylesheet = document.createElement('link'); - stylesheet.rel = 'stylesheet'; - stylesheet.href = stylesheetTag.href; - editor.dom.doc.head.appendChild(stylesheet); - }); - - editor.dom.doc.addEventListener('click', (e: MouseEvent) => { - // If we try to open link in a new tab, then we want to skip skip: - //if ((isWindows && e.ctrlKey) || (!isWindows && e.metaKey)) return; - - const composedPaths = 'composedPath' in e ? e.composedPath() : null; - - // Find the target by using the composed path to get the element through the shadow boundaries. - // Notice the difference here compared to RouterSlots implementation [NL] - const $anchor: HTMLAnchorElement = - (composedPaths?.find( - ($elem) => $elem instanceof HTMLAnchorElement || ($elem as any).tagName === 'A', - ) as HTMLAnchorElement) ?? (e.target as HTMLAnchorElement); - - // Abort if the event is not about the anchor tag or the anchor tag has the attribute [data-router-slot]="disabled" - if ( - $anchor == null || - !($anchor instanceof HTMLAnchorElement || ($anchor as any).tagName === 'A') || - $anchor.dataset['routerSlot'] === 'disabled' - ) { - return; - } - - // Abort if the anchor tag is not inside a block element - const isInsideBlockElement = - composedPaths?.some( - ($elem) => ($elem as any).tagName === 'UMB-RTE-BLOCK' || ($elem as any).tagName === 'UMB-RTE-BLOCK-INLINE', - ) ?? false; - - if (!isInsideBlockElement) { - return; - } - - // Remove the origin from the start of the HREF to get the path - const path = $anchor.pathname + $anchor.search + $anchor.hash; - - // Prevent the default behavior - e.preventDefault(); - - // Change the history! - window.history.pushState(null, '', path); - }); - - // Load backoffice JS so we can get the umb-rte-block component registered inside the iframe [NL] - const script = document.createElement('script'); - script.type = 'text/javascript'; - script.setAttribute('type', 'module'); - - script.text = `import "@umbraco-cms/backoffice/extension-registry";`; - script.text = `import "${UMB_BLOCK_ENTRY_WEB_COMPONENTS_ABSOLUTE_PATH}";`; - editor.dom.doc.head.appendChild(script); - }, - - style_formats: [ - { - title: 'Headers', - items: [ - { title: 'Page header', block: 'h2' }, - { title: 'Section header', block: 'h3' }, - { title: 'Paragraph header', block: 'h4' }, - ], - }, - { - title: 'Blocks', - items: [{ title: 'Paragraph', 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/tiny-mce/components/input-tiny-mce/input-tiny-mce.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/input-tiny-mce/input-tiny-mce.element.ts deleted file mode 100644 index 41739b5f9d..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/input-tiny-mce/input-tiny-mce.element.ts +++ /dev/null @@ -1,407 +0,0 @@ -import { availableLanguages } from './input-tiny-mce.languages.js'; -import { defaultFallbackConfig } from './input-tiny-mce.defaults.js'; -import { pastePreProcessHandler } from './input-tiny-mce.handlers.js'; -import { uriAttributeSanitizer } from './input-tiny-mce.sanitizer.js'; -import type { UmbTinyMcePluginBase } from './tiny-mce-plugin.js'; -import { css, customElement, html, property, query } from '@umbraco-cms/backoffice/external/lit'; -import { loadManifestApi } from '@umbraco-cms/backoffice/extension-api'; -import { getProcessedImageUrl, umbDeepMerge } from '@umbraco-cms/backoffice/utils'; -import { renderEditor } from '@umbraco-cms/backoffice/external/tinymce'; -import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; -import { ImageCropModeModel } from '@umbraco-cms/backoffice/external/backend-api'; -import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; -import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbStylesheetDetailRepository, UmbStylesheetRuleManager } from '@umbraco-cms/backoffice/stylesheet'; -import { UUIFormControlMixin } from '@umbraco-cms/backoffice/external/uui'; -import type { ClassConstructor } from '@umbraco-cms/backoffice/extension-api'; -import type { EditorEvent, Editor, RawEditorOptions } from '@umbraco-cms/backoffice/external/tinymce'; -import type { ManifestTinyMcePlugin } from '@umbraco-cms/backoffice/tiny-mce'; -import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; - -/** - * Handles the resize event - * @param e - */ -async function onResize( - e: EditorEvent<{ - target: HTMLElement; - width: number; - height: number; - origin: string; - }>, -) { - const srcAttr = e.target.getAttribute('src'); - - if (!srcAttr) { - return; - } - - const path = srcAttr.split('?')[0]; - const resizedPath = await getProcessedImageUrl(path, { - width: e.width, - height: e.height, - mode: ImageCropModeModel.MAX, - }); - - e.target.setAttribute('data-mce-src', resizedPath); -} - -@customElement('umb-input-tiny-mce') -export class UmbInputTinyMceElement extends UUIFormControlMixin(UmbLitElement, '') { - @property({ attribute: false }) - configuration?: UmbPropertyEditorConfigCollection; - - #plugins: Array | undefined> = []; - #editorRef?: Editor | null = null; - readonly #stylesheetRepository = new UmbStylesheetDetailRepository(this); - readonly #umbStylesheetRuleManager = new UmbStylesheetRuleManager(); - - protected override getFormElement() { - return this._editorElement?.querySelector('iframe') ?? undefined; - } - - override set value(newValue: FormDataEntryValue | FormData) { - const newContent = typeof newValue === 'string' ? newValue : ''; - super.value = newContent; - - if (this.#editorRef && this.#editorRef.getContent() != newContent) { - this.#editorRef.setContent(newContent); - } - } - - override get value(): FormDataEntryValue | FormData { - return super.value; - } - - /** - * Sets the input to readonly mode, meaning value cannot be changed but still able to read and select its content. - * @type {boolean} - * @attr - * @default - */ - @property({ type: Boolean, reflect: true }) - public get readonly() { - return this.#readonly; - } - public set readonly(value) { - this.#readonly = value; - const editor = this.getEditor(); - const mode = value ? 'readonly' : 'design'; - editor?.mode.set(mode); - } - #readonly = false; - - @query('.editor', true) - private readonly _editorElement?: HTMLElement; - - getEditor() { - return this.#editorRef; - } - - override firstUpdated() { - this.#loadEditor(); - } - - async #loadEditor() { - this.observe(umbExtensionsRegistry.byType('tinyMcePlugin'), async (manifests) => { - this.#plugins.length = 0; - this.#plugins = await this.#loadPlugins(manifests); - - let config: RawEditorOptions = {}; - manifests.forEach((manifest) => { - if (manifest.meta?.config) { - config = umbDeepMerge(manifest.meta.config, config); - } - }); - - this.#setTinyConfig(config); - }); - } - - override disconnectedCallback() { - super.disconnectedCallback(); - - this.#editorRef?.destroy(); - } - - /** - * Load all custom plugins - need to split loading and instantiating as these - * need the editor instance as a ctor argument. If we load them in the editor - * setup method, the asynchronous nature means the editor is loaded before - * the plugins are ready and so are not associated with the editor. - * @param manifests - */ - async #loadPlugins(manifests: Array) { - const promises = []; - for (const manifest of manifests) { - if (manifest.js) { - promises.push(await loadManifestApi(manifest.js)); - } - if (manifest.api) { - promises.push(await loadManifestApi(manifest.api)); - } - } - return promises; - } - - async getFormatStyles(stylesheetPaths: Array) { - if (!stylesheetPaths) return []; - const formatStyles: any[] = []; - - const promises = stylesheetPaths.map((path) => this.#stylesheetRepository?.requestByUnique(path)); - const stylesheetResponses = await Promise.all(promises); - - stylesheetResponses.forEach(({ data }) => { - if (!data?.content) return; - - const rulesFromContent = this.#umbStylesheetRuleManager.extractRules(data.content); - - rulesFromContent.forEach((rule) => { - const r: { - title?: string; - inline?: string; - classes?: string; - attributes?: Record; - block?: string; - } = { - title: rule.name, - }; - - if (!rule.selector) return; - - if (rule.selector.startsWith('.')) { - r.inline = 'span'; - r.classes = rule.selector.substring(1); - } else if (rule.selector.startsWith('#')) { - r.inline = 'span'; - r.attributes = { id: rule.selector.substring(1) }; - } else if (rule.selector.includes('.')) { - const [block, ...classes] = rule.selector.split('.'); - r.block = block; - r.classes = classes.join(' ').replace(/\./g, ' '); - } else if (rule.selector.includes('#')) { - const [block, id] = rule.selector.split('#'); - r.block = block; - r.classes = id; - } else { - r.block = rule.selector; - } - - formatStyles.push(r); - }); - }); - - return formatStyles; - } - - async #setTinyConfig(additionalConfig?: RawEditorOptions) { - const dimensions = this.configuration?.getValueByAlias<{ width?: number; height?: number }>('dimensions'); - - const stylesheetPaths = this.configuration?.getValueByAlias('stylesheets') ?? []; - const styleFormats = await this.getFormatStyles(stylesheetPaths); - - // Map the stylesheets with server url - const stylesheets = - stylesheetPaths?.map((stylesheetPath: string) => `/css${stylesheetPath.replace(/\\/g, '/')}`) ?? []; - - stylesheets.push('/umbraco/backoffice/css/rte-content.css'); - - // create an object by merging the configuration onto the fallback config - const configurationOptions: RawEditorOptions = { - ...defaultFallbackConfig, - height: dimensions?.height ?? defaultFallbackConfig.height, - width: dimensions?.width ?? defaultFallbackConfig.width, - content_css: stylesheets.length ? stylesheets : defaultFallbackConfig.content_css, - style_formats: styleFormats.length ? styleFormats : defaultFallbackConfig.style_formats, - }; - - // no auto resize when a fixed height is set - 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, otherwise false - const toolbar = this.configuration?.getValueByAlias('toolbar'); - if (toolbar?.length) { - configurationOptions.toolbar = toolbar.join(' '); - } else { - configurationOptions.toolbar = false; - } - - // 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) { - configurationOptions.maxImageSize = maxImageSize; - } - - // set the default values that will not be modified via configuration - let config: RawEditorOptions = { - autoresize_bottom_margin: 10, - body_class: 'umb-rte', - contextMenu: false, - inline_boundaries_selector: 'a[href],code,.mce-annotation,.umb-embed-holder,.umb-macro-holder', - menubar: false, - paste_remove_styles_if_webkit: true, - paste_preprocess: pastePreProcessHandler, - relative_urls: false, - resize: false, - statusbar: false, - setup: (editor) => this.#editorSetup(editor), - target: this._editorElement, - paste_data_images: false, - language: this.#getLanguage(), - promotion: false, - convert_unsafe_embeds: true, // [JOV] Workaround for CVE-2024-29881 - readonly: this.#readonly, - - // Extend with configuration options - ...configurationOptions, - }; - - // Extend with additional configuration options - if (additionalConfig) { - config = umbDeepMerge(additionalConfig, config); - } - - this.#editorRef?.destroy(); - - const editors = await renderEditor(config).catch((error) => { - console.error('Failed to render TinyMCE', error); - return []; - }); - this.#editorRef = editors.pop(); - } - - /** - * Gets the language to use for TinyMCE - */ - #getLanguage() { - const localeId = this.localize.lang(); - //try matching the language using full locale format - let languageMatch = availableLanguages.find((x) => localeId?.localeCompare(x) === 0); - - //if no matches, try matching using only the language - if (!languageMatch) { - const localeParts = localeId?.split('_'); - if (localeParts) { - languageMatch = availableLanguages.find((x) => x === localeParts[0]); - } - } - - return languageMatch; - } - - #editorSetup(editor: Editor) { - editor.suffix = '.min'; - - // define keyboard shortcuts - editor.addShortcut('Ctrl+S', '', () => - this.dispatchEvent(new CustomEvent('rte.shortcut.save', { composed: true, bubbles: true })), - ); - - editor.addShortcut('Ctrl+P', '', () => - this.dispatchEvent(new CustomEvent('rte.shortcut.saveAndPublish', { composed: true, bubbles: true })), - ); - - // bind editor events - editor.on('init', () => this.#onInit(editor)); - editor.on('Change', () => this.#onChange(editor.getContent())); - editor.on('Dirty', () => this.#onChange(editor.getContent())); - editor.on('Keyup', () => this.#onChange(editor.getContent())); - - editor.on('focus', () => this.dispatchEvent(new CustomEvent('umb-rte-focus', { composed: true, bubbles: true }))); - - editor.on('blur', () => { - this.#onChange(editor.getContent()); - this.dispatchEvent(new CustomEvent('umb-rte-blur', { composed: true, bubbles: true })); - }); - - editor.on('ObjectResized', (e) => { - onResize(e); - this.#onChange(editor.getContent()); - }); - - editor.on('SetContent', () => { - /** - * Prevent injecting arbitrary JavaScript execution in on-attributes. - * - */ - const allNodes = Array.from(editor.dom.doc.getElementsByTagName('*')); - allNodes.forEach((node) => { - for (const attr of node.attributes) { - if (attr.name.startsWith('on')) { - node.removeAttribute(attr.name); - } - } - }); - }); - - // instantiate plugins to ensure they are available before setting up the editor. - // Plugins require a reference to the current editor as a param, so can not - // be instantiated until we have an editor - for (const plugin of this.#plugins) { - if (plugin) { - // [v15]: This might be improved by changing to `createExtensionApi` and avoiding the `#loadPlugins` method altogether, but that would require a breaking change - // because that function sends the UmbControllerHost as the first argument, which is not the case here. - new plugin({ host: this, editor }); - } - } - } - - #onInit(editor: Editor) { - //enable browser based spell checking - editor.getBody().setAttribute('spellcheck', 'true'); - uriAttributeSanitizer(editor); - editor.setContent(typeof this.value === 'string' ? this.value : ''); - } - - #onChange(value: string) { - if (this.value === value) return; - this.value = value; - this.dispatchEvent(new UmbChangeEvent()); - } - - /** - * Nothing rendered by default - TinyMCE initialization creates - * a target div and binds the RTE to that element - */ - override render() { - return html`
`; - } - - static override readonly styles = [ - css` - .tox-tinymce { - position: relative; - min-height: 100px; - border-radius: 0; - border: var(--uui-input-border-width, 1px) solid var(--uui-input-border-color, var(--uui-color-border, #d8d7d9)); - } - - .tox-tinymce-fullscreen { - position: absolute; - } - - /* FIXME: Remove this workaround when https://github.com/tinymce/tinymce/issues/6431 has been fixed */ - .tox .tox-collection__item-label { - line-height: 1 !important; - } - `, - ]; -} - -export default UmbInputTinyMceElement; - -declare global { - interface HTMLElementTagNameMap { - 'umb-input-tiny-mce': UmbInputTinyMceElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/input-tiny-mce/input-tiny-mce.handlers.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/input-tiny-mce/input-tiny-mce.handlers.ts deleted file mode 100644 index 4cfbc3c67a..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/input-tiny-mce/input-tiny-mce.handlers.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { RawEditorOptions } from '@umbraco-cms/backoffice/external/tinymce'; - -export const pastePreProcessHandler: RawEditorOptions['paste_preprocess'] = (_editor, args) => { - // Remove spans - args.content = args.content.replace(/<\s*span[^>]*>(.*?)<\s*\/\s*span>/g, '$1'); - // Convert b to strong. - args.content = args.content.replace(/<\s*b([^>]*)>(.*?)<\s*\/\s*b([^>]*)>/g, '$2'); - // convert i to em - args.content = args.content.replace(/<\s*i([^>]*)>(.*?)<\s*\/\s*i([^>]*)>/g, '$2'); -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/input-tiny-mce/input-tiny-mce.languages.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/input-tiny-mce/input-tiny-mce.languages.ts deleted file mode 100644 index 641e46f33c..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/input-tiny-mce/input-tiny-mce.languages.ts +++ /dev/null @@ -1,78 +0,0 @@ -export const availableLanguages = [ - 'ar', - 'ar_SA', - 'hy', - 'az', - 'eu', - 'be', - 'bn_BD', - 'bs', - 'bg_BG', - 'ca', - 'zh_CN', - 'zh_TW', - 'hr', - 'cs', - 'da', - 'dv', - 'nl', - 'en_CA', - 'en_GB', - 'et', - 'fo', - 'fi', - 'fr_FR', - 'gd', - 'gl', - 'ka_GE', - 'de', - 'de_AT', - 'el', - 'he_IL', - 'hi_IN', - 'hu_HU', - 'is_IS', - 'id', - 'it', - 'ja', - 'kab', - 'kk', - 'km_KH', - 'ko_KR', - 'ku', - 'ku_IQ', - 'lv', - 'lt', - 'lb', - 'ml', - 'ml_IN', - 'mn_MN', - 'nb_NO', - 'fa', - 'fa_IR', - 'pl', - 'pt_BR', - 'pt_PT', - 'ro', - 'ru', - 'sr', - 'si_LK', - 'sk', - 'sl_SI', - 'es', - 'es_MX', - 'sv_SE', - 'tg', - 'ta', - 'ta_IN', - 'tt', - 'th_TH', - 'tr', - 'tr_TR', - 'ug', - 'uk', - 'uk_UA', - 'vi', - 'vi_VN', - 'cy', -]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/input-tiny-mce/input-tiny-mce.sanitizer.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/input-tiny-mce/input-tiny-mce.sanitizer.ts deleted file mode 100644 index 9d07f73680..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/input-tiny-mce/input-tiny-mce.sanitizer.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { Editor } from '@umbraco-cms/backoffice/external/tinymce'; - -/** - * Setup sanitization for preventing injecting arbitrary JavaScript execution in attributes: - * https://github.com/advisories/GHSA-w7jx-j77m-wp65 - * https://github.com/advisories/GHSA-5vm8-hhgr-jcjp - * @param editor - */ -export const uriAttributeSanitizer = (editor: Editor) => { - const uriAttributesToSanitize = ['src', 'href', 'data', 'background', 'action', 'formaction', 'poster', 'xlink:href']; - - const parseUri = (function () { - // Encapsulated JS logic. - const safeSvgDataUrlElements = ['img', 'video']; - const scriptUriRegExp = /((java|vb)script|mhtml):/i; - // eslint-disable-next-line no-control-regex - const trimRegExp = /[\s\u0000-\u001F]+/g; - - const isInvalidUri = (uri: string, tagName: string) => { - if (/^data:image\//i.test(uri)) { - return safeSvgDataUrlElements.indexOf(tagName) !== -1 && /^data:image\/svg\+xml/i.test(uri); - } else { - return /^data:/i.test(uri); - } - }; - - return function parseUri(uri: string, tagName: string) { - uri = uri.replace(trimRegExp, ''); - uri = decodeURIComponent(uri); - - if (scriptUriRegExp.test(uri)) { - return; - } - - if (isInvalidUri(uri, tagName)) { - return; - } - - return uri; - }; - })(); - - editor.serializer.addAttributeFilter('uriAttributesToSanitize', function (nodes) { - nodes.forEach(function (node) { - if (!node.attributes) return; - for (const attr of node.attributes) { - const attrName = attr.name.toLowerCase(); - if (uriAttributesToSanitize.indexOf(attrName) !== -1) { - attr.value = parseUri(attr.value, node.name) ?? ''; - } - } - }); - }); -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/input-tiny-mce/tiny-mce-plugin.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/input-tiny-mce/tiny-mce-plugin.ts deleted file mode 100644 index c7da513d31..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/components/input-tiny-mce/tiny-mce-plugin.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { UmbInputTinyMceElement } from './input-tiny-mce.element.js'; -import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; -import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; -import type { Editor } from '@umbraco-cms/backoffice/external/tinymce'; -import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; - -export class UmbTinyMcePluginBase extends UmbControllerBase implements UmbApi { - editor: Editor; - configuration?: UmbPropertyEditorConfigCollection; - - constructor(arg: TinyMcePluginArguments) { - super(arg.host); - this.editor = arg.editor; - this.configuration = arg.host.configuration; - } -} - -export type TinyMcePluginArguments = { - host: UmbInputTinyMceElement; - editor: Editor; -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/constants.ts deleted file mode 100644 index 411ed6911b..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './property-editors/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/index.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/index.ts deleted file mode 100644 index 3c7345719d..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './constants.js'; -export * from './components/index.js'; -export type * from './plugins/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/manifests.ts deleted file mode 100644 index 0c978efd63..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/manifests.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { manifests as propertyEditors } from './property-editors/manifests.js'; -import { manifests as plugins } from './plugins/manifests.js'; - -export const manifests: Array = [...propertyEditors, ...plugins]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/package.json b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/package.json deleted file mode 100644 index 6362dc5347..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@umbraco-backoffice/tiny-mce", - "private": true, - "type": "module", - "scripts": { - "build": "vite build" - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/plugins/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/plugins/manifests.ts deleted file mode 100644 index 33397db3ae..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/plugins/manifests.ts +++ /dev/null @@ -1,64 +0,0 @@ -import type { ManifestTinyMcePlugin } from './tinymce-plugin.extension.js'; - -export const manifests: Array = [ - { - type: 'tinyMcePlugin', - alias: 'Umb.TinyMcePlugin.CodeEditor', - name: 'Code Editor TinyMCE Plugin', - js: () => import('./tiny-mce-code-editor.plugin.js'), - meta: { - toolbar: [ - { - alias: 'sourcecode', - label: 'Source code editor', - icon: 'sourcecode', - }, - ], - }, - }, - { - type: 'tinyMcePlugin', - alias: 'Umb.TinyMcePlugin.MediaPicker', - name: 'Media Picker TinyMCE Plugin', - js: () => import('./tiny-mce-mediapicker.plugin.js'), - meta: { - toolbar: [ - { - alias: 'umbmediapicker', - label: 'Image', - icon: 'image', - }, - ], - }, - }, - { - type: 'tinyMcePlugin', - alias: 'Umb.TinyMcePlugin.EmbeddedMedia', - name: 'Embedded Media TinyMCE Plugin', - js: () => import('./tiny-mce-embeddedmedia.plugin.js'), - meta: { - toolbar: [ - { - alias: 'umbembeddialog', - label: 'Embed', - icon: 'embed', - }, - ], - }, - }, - { - 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/tiny-mce/plugins/tiny-mce-block-picker.plugin.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/plugins/tiny-mce-block-picker.plugin.ts deleted file mode 100644 index 527eb94504..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/plugins/tiny-mce-block-picker.plugin.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { UMB_BLOCK_RTE_DATA_CONTENT_KEY } from '@umbraco-cms/backoffice/rte'; -import { type TinyMcePluginArguments, UmbTinyMcePluginBase } from '@umbraco-cms/backoffice/tiny-mce'; -import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api'; -import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/block-type'; -import type { Editor } from '@umbraco-cms/backoffice/external/tinymce'; -import type { UmbBlockDataModel } from '@umbraco-cms/backoffice/block'; -import { - UMB_BLOCK_RTE_ENTRIES_CONTEXT, - UMB_BLOCK_RTE_MANAGER_CONTEXT, - type UmbBlockRteLayoutModel, -} from '@umbraco-cms/backoffice/block-rte'; - -export default class UmbTinyMceMultiUrlPickerPlugin extends UmbTinyMcePluginBase { - readonly #localize = new UmbLocalizationController(this._host); - readonly #editor: Editor; - #blocks?: Array; - #entriesContext?: typeof UMB_BLOCK_RTE_ENTRIES_CONTEXT.TYPE; - - constructor(args: TinyMcePluginArguments) { - super(args); - - this.#editor = args.editor; - - 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-key], umb-rte-block-inline[data-content-key]', - (state) => api.setActive(state), - ); - return () => changed.unbind(); - }, - }); - - this.consumeContext(UMB_BLOCK_RTE_MANAGER_CONTEXT, (context) => { - this.observe( - context.blockTypes, - (blockTypes) => { - this.#blocks = blockTypes; - }, - 'blockType', - ); - - this.observe( - context.contents, - (contents) => { - this.#updateBlocks(contents, context.getLayouts()); - }, - 'contents', - ); - }); - this.consumeContext(UMB_BLOCK_RTE_ENTRIES_CONTEXT, (context) => { - this.#entriesContext = context; - }); - } - - async showDialog() { - //const blockEl = this.editor.selection.getNode(); - - /*if (blockEl.nodeName === 'UMB-RTE-BLOCK' || blockEl.nodeName === 'UMB-RTE-BLOCK-INLINE') { - const blockkey = blockEl.getAttribute('data-content-key') ?? undefined; - if (blockkey) { - // TODO: Missing a solution to edit a block from this scope. [NL] - this.#editBlock(blockkey); - return; - } - }*/ - - // If no block is selected, open the block picker: - this.#createBlock(); - } - - #createBlock() { - if (!this.#entriesContext) { - console.error('[Block Picker] No entries context available.'); - return; - } - - let createPath: string | undefined = undefined; - - if (this.#blocks?.length === 1) { - const elementKey = this.#blocks[0].contentElementTypeKey; - createPath = this.#entriesContext.getPathForCreateBlock() + 'modal/umb-modal-workspace/create/' + elementKey; - } else { - createPath = this.#entriesContext.getPathForCreateBlock(); - } - - if (createPath) { - window.history.pushState({}, '', createPath); - } - } - - #updateBlocks(blocks: UmbBlockDataModel[], layouts: Array) { - const editor = this.#editor; - if (!editor?.dom) return; - - const existingBlocks = editor.dom - .select('umb-rte-block, umb-rte-block-inline') - .map((x) => x.getAttribute(UMB_BLOCK_RTE_DATA_CONTENT_KEY)); - const newBlocks = blocks.filter((x) => !existingBlocks.find((contentKey) => contentKey === x.key)); - - newBlocks.forEach((block) => { - // Find layout for block - const layout = layouts.find((x) => x.contentKey === block.key); - const inline = layout?.displayInline ?? false; - - let blockTag = 'umb-rte-block'; - - if (inline) { - blockTag = 'umb-rte-block-inline'; - } - - const blockEl = `<${blockTag} ${UMB_BLOCK_RTE_DATA_CONTENT_KEY}="${block.key}">`; - - editor.selection.setContent(blockEl); - editor.setDirty(true); - editor.dispatch('Change'); - }); - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/plugins/tiny-mce-code-editor.plugin.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/plugins/tiny-mce-code-editor.plugin.ts deleted file mode 100644 index b6609ade8e..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/plugins/tiny-mce-code-editor.plugin.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { type TinyMcePluginArguments, UmbTinyMcePluginBase } from '../components/input-tiny-mce/tiny-mce-plugin.js'; -import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api'; -import { UMB_CODE_EDITOR_MODAL } from '@umbraco-cms/backoffice/code-editor'; -import { umbOpenModal } from '@umbraco-cms/backoffice/modal'; - -export default class UmbTinyMceCodeEditorPlugin extends UmbTinyMcePluginBase { - constructor(args: TinyMcePluginArguments) { - super(args); - const localize = new UmbLocalizationController(args.host); - - this.editor.ui.registry.addButton('sourcecode', { - icon: 'sourcecode', - tooltip: localize.term('general_viewSourceCode'), - onAction: () => this.#showCodeEditor(), - }); - } - - async #showCodeEditor() { - const value = await umbOpenModal(this, UMB_CODE_EDITOR_MODAL, { - data: { - headline: 'Edit source code', - content: this.editor.getContent() ?? '', - language: 'html', - }, - }).catch(() => undefined); - - if (!value) { - return; - } - - if (!value.content) { - this.editor.resetContent(); - } else { - this.editor.setContent(value.content.toString()); - } - - this.editor.dispatch('Change'); - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/plugins/tiny-mce-embeddedmedia.plugin.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/plugins/tiny-mce-embeddedmedia.plugin.ts deleted file mode 100644 index 75e16fa29a..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/plugins/tiny-mce-embeddedmedia.plugin.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { type TinyMcePluginArguments, UmbTinyMcePluginBase } from '../components/input-tiny-mce/tiny-mce-plugin.js'; -import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api'; -import type { UmbEmbeddedMediaModalData, UmbEmbeddedMediaModalValue } from '@umbraco-cms/backoffice/embedded-media'; -import { umbOpenModal } from '@umbraco-cms/backoffice/modal'; -import { UMB_EMBEDDED_MEDIA_MODAL } from '@umbraco-cms/backoffice/embedded-media'; - -export default class UmbTinyMceEmbeddedMediaPlugin extends UmbTinyMcePluginBase { - constructor(args: TinyMcePluginArguments) { - super(args); - const localize = new UmbLocalizationController(args.host); - - this.editor.ui.registry.addToggleButton('umbembeddialog', { - icon: 'embed', - tooltip: localize.term('general_embed'), - onAction: () => this.#onAction(), - onSetup: function (api) { - const changed = args.editor.selection.selectorChangedWithUnbind('div.umb-embed-holder', (state) => - api.setActive(state), - ); - return () => changed.unbind(); - }, - }); - } - - #onAction() { - // Get the selected element - // Check nodename is a DIV and the claslist contains 'umb-embed-holder' - const selectedElm = this.editor.selection.getNode(); - - let modify: UmbEmbeddedMediaModalData = { - width: 360, - height: 240, - }; - - if (selectedElm.nodeName.toUpperCase() === 'DIV' && selectedElm.classList.contains('umb-embed-holder')) { - // See if we can go and get the attributes - const url = this.editor.dom.getAttrib(selectedElm, 'data-embed-url'); - const embedWidth = this.editor.dom.getAttrib(selectedElm, 'data-embed-width'); - const embedHeight = this.editor.dom.getAttrib(selectedElm, 'data-embed-height'); - const constrain = this.editor.dom.getAttrib(selectedElm, 'data-embed-constrain') === 'true'; - - modify = { - url, - constrain, - width: parseInt(embedWidth) || modify.width, - height: parseInt(embedHeight) || modify.height, - }; - } - - this.#showModal(selectedElm, modify); - } - - #insertInEditor(embed: UmbEmbeddedMediaModalValue, activeElement: HTMLElement) { - // Wrap HTML preview content here in a DIV with non-editable class of .mceNonEditable - // This turns it into a selectable/cutable block to move about - - const wrapper = this.editor.dom.create( - 'div', - { - class: 'mceNonEditable umb-embed-holder', - 'data-embed-url': embed.url ?? '', - 'data-embed-height': embed.height!, - 'data-embed-width': embed.width!, - 'data-embed-constrain': embed.constrain ?? false, - contenteditable: false, - }, - embed.markup, - ); - - // Only replace if activeElement is an Embed element. - if (activeElement?.nodeName.toUpperCase() === 'DIV' && activeElement.classList.contains('umb-embed-holder')) { - activeElement.replaceWith(wrapper); // directly replaces the html node - } else { - this.editor.selection.setNode(wrapper); - } - } - - async #showModal(selectedElm: HTMLElement, embeddedMediaModalData: UmbEmbeddedMediaModalData) { - const result = await umbOpenModal(this, UMB_EMBEDDED_MEDIA_MODAL, { data: embeddedMediaModalData }).catch( - () => undefined, - ); - - if (!result) return; - - this.#insertInEditor(result, selectedElm); - this.editor.dispatch('Change'); - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts deleted file mode 100644 index 7c6e354bdf..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts +++ /dev/null @@ -1,264 +0,0 @@ -import { type TinyMcePluginArguments, UmbTinyMcePluginBase } from '../components/input-tiny-mce/tiny-mce-plugin.js'; -import { getGuidFromUdi } from '@umbraco-cms/backoffice/utils'; -import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; -import type { RawEditorOptions } from '@umbraco-cms/backoffice/external/tinymce'; -import { UmbTemporaryFileRepository } from '@umbraco-cms/backoffice/temporary-file'; -import { UmbId } from '@umbraco-cms/backoffice/id'; -import { - sizeImageInEditor, - uploadBlobImages, - UMB_MEDIA_PICKER_MODAL, - UMB_MEDIA_CAPTION_ALT_TEXT_MODAL, -} from '@umbraco-cms/backoffice/media'; -import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api'; - -interface MediaPickerTargetData { - altText?: string; - url?: string; - caption?: string; - udi?: string; - id?: string; - tmpimg?: string; -} - -interface MediaPickerResultData { - id?: string; - src?: string; - alt?: string; - 'data-udi'?: string; - 'data-caption'?: string; -} - -export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase { - #modalManager?: typeof UMB_MODAL_MANAGER_CONTEXT.TYPE; - readonly #temporaryFileRepository; - - constructor(args: TinyMcePluginArguments) { - super(args); - const localize = new UmbLocalizationController(args.host); - - this.#temporaryFileRepository = new UmbTemporaryFileRepository(args.host); - - this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => { - this.#modalManager = instance; - }); - - this.editor.ui.registry.addToggleButton('umbmediapicker', { - icon: 'image', - tooltip: localize.term('general_mediaPicker'), - onAction: () => this.#onAction(), - onSetup: (api) => { - const changed = this.editor.selection.selectorChangedWithUnbind('img[data-udi]', (state) => - api.setActive(state), - ); - return () => changed.unbind(); - }, - }); - - // 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', 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) => { - const content = e.content; - - // Handle images that are pasted in - uploadBlobImages(this.editor, content); - }); - } - } - - /* - async #observeCurrentUser() { - if (!this.#currentUserContext) return; - - this.observe(this.#currentUserContext.currentUser, (currentUser) => (this.#currentUser = currentUser)); - } - */ - - async #onAction() { - const selectedElm = this.editor.selection.getNode(); - let currentTarget: MediaPickerTargetData = {}; - - if (selectedElm.nodeName === 'IMG') { - const img = selectedElm as HTMLImageElement; - const hasUdi = img.hasAttribute('data-udi'); - const hasDataTmpImg = img.hasAttribute('data-tmpimg'); - - currentTarget = { - altText: img.alt, - url: img.src, - caption: img.dataset.caption, - }; - - if (hasUdi) { - currentTarget['udi'] = img.dataset.udi; - } else { - currentTarget['id'] = img.getAttribute('rel') ?? undefined; - } - - if (hasDataTmpImg) { - currentTarget['tmpimg'] = img.dataset.tmpimg; - } - } - - this.#showMediaPicker(currentTarget); - } - - async #showMediaPicker(currentTarget: MediaPickerTargetData) { - /* - let startNodeId; - let startNodeIsVirtual; - - if (!this.configuration?.getByAlias('startNodeId')) { - if (this.configuration?.getValueByAlias('ignoreUserStartNodes') === true) { - startNodeId = -1; - startNodeIsVirtual = true; - } else { - startNodeId = this.#currentUser?.mediaStartNodeIds?.length !== 1 ? -1 : this.#currentUser?.mediaStartNodeIds[0]; - startNodeIsVirtual = this.#currentUser?.mediaStartNodeIds?.length !== 1; - } - } - */ - - const modalHandler = this.#modalManager?.open(this, UMB_MEDIA_PICKER_MODAL, { - data: { - multiple: false, - //startNodeIsVirtual, - }, - value: { - selection: currentTarget.udi ? [getGuidFromUdi(currentTarget.udi)] : [], - }, - }); - - if (!modalHandler) return; - - const { selection } = await modalHandler.onSubmit().catch(() => ({ selection: undefined })); - if (!selection?.length) return; - - this.#showMediaCaptionAltText(selection[0], currentTarget); - this.editor.dispatch('Change'); - } - - async #showMediaCaptionAltText(mediaUnique: string | null, currentTarget: MediaPickerTargetData) { - if (!mediaUnique) return; - - const modalHandler = this.#modalManager?.open(this, UMB_MEDIA_CAPTION_ALT_TEXT_MODAL, { - data: { mediaUnique }, - value: { - url: '', - altText: currentTarget.altText, - caption: currentTarget.caption, - }, - }); - - const mediaData = await modalHandler?.onSubmit().catch(() => null); - if (!mediaData) return; - - const media: MediaPickerTargetData = { - altText: mediaData?.altText, - caption: mediaData?.caption, - url: mediaData?.url, - udi: 'umb://media/' + mediaUnique?.replace(/-/g, ''), - }; - - this.#insertInEditor(media); - } - - async #insertInEditor(media: MediaPickerTargetData) { - if (!media) return; - - // We need to create a NEW DOM element to insert - // setting an attribute of ID to __mcenew, so we can gather a reference to the node, to be able to update its size accordingly to the size of the image. - const img: MediaPickerResultData = { - alt: media.altText, - src: media.url ? media.url : 'nothing.jpg', - id: '__mcenew', - 'data-udi': media.udi, - 'data-caption': media.caption, - }; - const newImage = this.editor.dom.createHTML('img', img as Record); - const parentElement = this.editor.selection.getNode().parentElement; - - if (img['data-caption'] && parentElement) { - const figCaption = this.editor.dom.createHTML('figcaption', {}, img['data-caption']); - const combined = newImage + figCaption; - - if (parentElement.nodeName !== 'FIGURE') { - const fragment = this.editor.dom.createHTML('figure', {}, combined); - this.editor.selection.setContent(fragment); - } else { - parentElement.innerHTML = combined; - } - } else if (parentElement?.nodeName === 'FIGURE' && parentElement.parentElement) { - //if caption is removed, remove the figure element - parentElement.parentElement.innerHTML = newImage; - } else { - this.editor.selection.setContent(newImage); - } - - // Using settimeout to wait for a DoM-render, so we can find the new element by ID. - setTimeout(() => { - const imgElm = this.editor.dom.get('__mcenew') as HTMLImageElement; - if (!imgElm) return; - - this.editor.dom.setAttrib(imgElm, 'id', null); - - // When image is loaded we are ready to call sizeImageInEditor. - const onImageLoaded = () => { - sizeImageInEditor(this.editor, imgElm, img.src); - this.editor.dispatch('Change'); - }; - - // Check if image already is loaded. - if (imgElm.complete === true) { - onImageLoaded(); - } else { - imgElm.onload = onImageLoaded; - } - }); - } - - readonly #uploadImageHandler: RawEditorOptions['images_upload_handler'] = (blobInfo, progress) => { - return new Promise((resolve, reject) => { - progress(0); - - const id = UmbId.new(); - const fileBlob = blobInfo.blob(); - const file = new File([fileBlob], blobInfo.filename(), { type: fileBlob.type }); - - document.dispatchEvent(new CustomEvent('rte.file.uploading', { composed: true, bubbles: true })); - - this.#temporaryFileRepository - .upload(id, file, (evt) => { - progress((evt.loaded / evt.total) * 100); - }) - .then((response) => { - if (response.error) { - reject(response.error); - 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); - document.dispatchEvent(new CustomEvent('rte.file.uploaded', { composed: true, bubbles: true })); - }); - }); - }; -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/plugins/tinymce-plugin.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/plugins/tinymce-plugin.extension.ts deleted file mode 100644 index 2506a66597..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/plugins/tinymce-plugin.extension.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { UmbTinyMcePluginBase } from '@umbraco-cms/backoffice/tiny-mce'; -import type { ManifestApi } from '@umbraco-cms/backoffice/extension-api'; -import type { RawEditorOptions } from '@umbraco-cms/backoffice/external/tinymce'; - -export interface MetaTinyMcePlugin { - /** - * If the plugin adds toolbar buttons, this property can be used to configure the buttons. - * This configuration will be used on the Rich Text Editor configuration page. - */ - toolbar?: Array<{ - /** - * The alias of the toolbar button that will be configured in the TinyMCE editor. - * @see [TinyMCE Toolbar](https://www.tiny.cloud/docs/tinymce/6/toolbar-configuration-options/) for more information. - */ - alias: string; - - /** - * The label of the option shown on the Rich Text Editor configuration page. - */ - label: string; - - /** - * The icon shown on the Rich Text Editor configuration page. The icon has to be a part of TinyMCE's icon set. - * @optional - * @see [TinyMCE Icon Set](https://www.tiny.cloud/docs/tinymce/6/editor-icon-identifiers/) for available default icons. - */ - icon?: string; - }>; - - /** - * @title Sets the default configuration for the TinyMCE editor. - * @description This configuration will be used when the editor is initialized. See the [TinyMCE Configuration](https://www.tiny.cloud/docs/configure/) for more information. - * @optional - * @TJS-type object - * @examples [ - * { - * "plugins": "wordcount", - * "statusbar": true - * } - * ] - */ - config?: RawEditorOptions; -} - -/** - * The manifest for a TinyMCE plugin. - * The plugin will be loaded into the TinyMCE editor. - * A plugin can add things like buttons, menu items, context menu items, etc. through the TinyMCE API. - * A plugin can also add custom commands to the editor. - * A plugin can also modify the behavior of the editor. - * @see [TinyMCE Plugin](https://www.tiny.cloud/docs/tinymce/6/apis/tinymce.plugin/) for more information. - */ -export interface ManifestTinyMcePlugin extends ManifestApi { - type: 'tinyMcePlugin'; - meta?: MetaTinyMcePlugin; -} - -declare global { - interface UmbExtensionManifestMap { - umbTinyMcePlugin: ManifestTinyMcePlugin; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/plugins/types.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/plugins/types.ts deleted file mode 100644 index dd9208e9d0..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/plugins/types.ts +++ /dev/null @@ -1 +0,0 @@ -export type * from './tinymce-plugin.extension.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/block/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/block/manifests.ts deleted file mode 100644 index 7a8bb13a83..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/block/manifests.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { ManifestPropertyEditorUi } from '@umbraco-cms/backoffice/property-editor'; - -export const manifest: ManifestPropertyEditorUi = { - type: 'propertyEditorUi', - alias: 'Umb.PropertyEditorUi.BlockRteTypeConfiguration', - name: 'Block Rte Type Configuration Property Editor UI', - js: () => import('./property-editor-ui-block-rte-type-configuration.element.js'), - meta: { - label: 'Block Rte Type Configuration', - icon: 'icon-autofill', - group: 'common', - }, -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/block/property-editor-ui-block-rte-type-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/block/property-editor-ui-block-rte-type-configuration.element.ts deleted file mode 100644 index 008a595ced..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/block/property-editor-ui-block-rte-type-configuration.element.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { customElement, html, property, state, nothing } from '@umbraco-cms/backoffice/external/lit'; -import { UmbInputBlockTypeElement } from '@umbraco-cms/backoffice/block-type'; -import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; -import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; -import { UMB_BLOCK_RTE_TYPE } from '@umbraco-cms/backoffice/block-rte'; -import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace'; -import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/block-type'; -import type { - UmbPropertyEditorUiElement, - UmbPropertyEditorConfigCollection, -} from '@umbraco-cms/backoffice/property-editor'; - -/** - * @element umb-property-editor-ui-block-rte-type-configuration - */ -@customElement('umb-property-editor-ui-block-rte-type-configuration') -export class UmbPropertyEditorUIBlockRteBlockConfigurationElement - extends UmbLitElement - implements UmbPropertyEditorUiElement -{ - readonly #blockTypeWorkspaceModalRegistration?: UmbModalRouteRegistrationController< - typeof UMB_WORKSPACE_MODAL.DATA, - typeof UMB_WORKSPACE_MODAL.VALUE - >; - - @property({ attribute: false }) - value: UmbBlockTypeBaseModel[] = []; - - @property({ type: Object, attribute: false }) - public config?: UmbPropertyEditorConfigCollection; - - @state() - private _workspacePath?: string; - - constructor() { - super(); - this.#blockTypeWorkspaceModalRegistration?.destroy(); - - this.#blockTypeWorkspaceModalRegistration = new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL) - .addAdditionalPath(UMB_BLOCK_RTE_TYPE) - .onSetup(() => { - return { data: { entityType: UMB_BLOCK_RTE_TYPE, preset: {} }, modal: { size: 'large' } }; - }) - .observeRouteBuilder((routeBuilder) => { - const newpath = routeBuilder({}); - this._workspacePath = newpath; - }); - } - - #onCreate(e: CustomEvent) { - const selectedElementType = e.detail.contentElementTypeKey; - if (selectedElementType) { - this.#blockTypeWorkspaceModalRegistration?.open({}, 'create/' + selectedElementType + '/null'); - } - } - #onChange(e: CustomEvent) { - e.stopPropagation(); - this.value = (e.target as UmbInputBlockTypeElement).value; - this.dispatchEvent(new UmbChangeEvent()); - } - - override render() { - return UmbInputBlockTypeElement - ? html`` - : nothing; - } -} - -export default UmbPropertyEditorUIBlockRteBlockConfigurationElement; - -declare global { - interface HTMLElementTagNameMap { - 'umb-property-editor-ui-block-rte-type-configuration': UmbPropertyEditorUIBlockRteBlockConfigurationElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/block/property-editor-ui-block-rte-type-configuration.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/block/property-editor-ui-block-rte-type-configuration.stories.ts deleted file mode 100644 index 770cff8af8..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/block/property-editor-ui-block-rte-type-configuration.stories.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { UmbPropertyEditorUIBlockRteBlockConfigurationElement } from './property-editor-ui-block-rte-type-configuration.element.js'; -import type { Meta, StoryFn } from '@storybook/web-components'; -import { html } from '@umbraco-cms/backoffice/external/lit'; - -import './property-editor-ui-block-rte-type-configuration.element.js'; - -export default { - title: 'Property Editor UIs/Block Rte Block Configuration', - component: 'umb-property-editor-ui-block-rte-type-configuration', - id: 'umb-property-editor-ui-block-rte-type-configuration', -} as Meta; - -export const AAAOverview: StoryFn = () => - html``; -AAAOverview.storyName = 'Overview'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/block/property-editor-ui-block-rte-type-configuration.test.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/block/property-editor-ui-block-rte-type-configuration.test.ts deleted file mode 100644 index 227b20c4f6..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/block/property-editor-ui-block-rte-type-configuration.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { UmbPropertyEditorUIBlockRteBlockConfigurationElement } from './property-editor-ui-block-rte-type-configuration.element.js'; -import { expect, fixture, html } from '@open-wc/testing'; -import { type UmbTestRunnerWindow, defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; - -describe('UmbPropertyEditorUIBlockListBlockConfigurationElement', () => { - let element: UmbPropertyEditorUIBlockRteBlockConfigurationElement; - - beforeEach(async () => { - element = await fixture(html` - - `); - }); - - it('is defined with its own instance', () => { - expect(element).to.be.instanceOf(UmbPropertyEditorUIBlockRteBlockConfigurationElement); - }); - - if ((window as UmbTestRunnerWindow).__UMBRACO_TEST_RUN_A11Y_TEST) { - it('passes the a11y audit', async () => { - await expect(element).shadowDom.to.be.accessible(defaultA11yConfig); - }); - } -}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/constants.ts deleted file mode 100644 index ead5bb69a2..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './tiny-mce/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/dimensions/property-editor-ui-tiny-mce-dimensions-configuration.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/dimensions/property-editor-ui-tiny-mce-dimensions-configuration.stories.ts deleted file mode 100644 index c8cc12f8f8..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/dimensions/property-editor-ui-tiny-mce-dimensions-configuration.stories.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { umbDataTypeMockDb } from '../../../../mocks/data/data-type/data-type.db.js'; -import type { Meta } from '@storybook/web-components'; -import './property-editor-ui-tiny-mce-dimensions-configuration.element.js'; -import { html } from '@umbraco-cms/backoffice/external/lit'; -import type { UmbDataTypeDetailModel } from '@umbraco-cms/backoffice/data-type'; - -const dataTypeData = umbDataTypeMockDb.read('dt-richTextEditor') as unknown as UmbDataTypeDetailModel; - -export default { - title: 'Property Editor UIs/Tiny Mce Dimensions Configuration', - component: 'umb-property-eDitor-ui-tiny-mce-dimensions-configuration', - id: 'umb-property-editor-ui-tiny-mce-dimensions-configuration', -} as Meta; - -export const AAAOverview = ({ value }: any) => - html``; -AAAOverview.storyName = 'Overview'; -AAAOverview.args = { - value: dataTypeData?.values?.find((x) => x.alias === 'dimensions')?.value, -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/dimensions/property-editor-ui-tiny-mce-dimensions-configuration.test.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/dimensions/property-editor-ui-tiny-mce-dimensions-configuration.test.ts deleted file mode 100644 index 2373058ed1..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/dimensions/property-editor-ui-tiny-mce-dimensions-configuration.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { UmbPropertyEditorUITinyMceDimensionsConfigurationElement } from './property-editor-ui-tiny-mce-dimensions-configuration.element.js'; -import { expect, fixture, html } from '@open-wc/testing'; -import { type UmbTestRunnerWindow, defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; - -describe('UmbPropertyEditorUITinyMceDimensionsConfigurationElement', () => { - let element: UmbPropertyEditorUITinyMceDimensionsConfigurationElement; - - beforeEach(async () => { - element = await fixture(html` - - `); - }); - - it('is defined with its own instance', () => { - expect(element).to.be.instanceOf(UmbPropertyEditorUITinyMceDimensionsConfigurationElement); - }); - - if ((window as UmbTestRunnerWindow).__UMBRACO_TEST_RUN_A11Y_TEST) { - it('passes the a11y audit', async () => { - await expect(element).shadowDom.to.be.accessible(defaultA11yConfig); - }); - } -}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/manifests.ts deleted file mode 100644 index cc57f8b61b..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/manifests.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { manifest as blockRteTypeManifest } from './block/manifests.js'; -import { manifests as tinyMceManifest } from './tiny-mce/manifests.js'; - -export const manifests: Array = [ - ...tinyMceManifest, - blockRteTypeManifest, - { - type: 'propertyEditorUi', - alias: 'Umb.PropertyEditorUI.TinyMCE.ToolbarConfiguration', - name: 'TinyMCE Toolbar Property Editor UI', - element: () => import('./toolbar/property-editor-ui-tiny-mce-toolbar-configuration.element.js'), - meta: { - label: 'TinyMCE Toolbar Configuration', - icon: 'icon-autofill', - group: 'common', - }, - }, - { - type: 'propertyEditorUi', - alias: 'Umb.PropertyEditorUI.TinyMCE.StylesheetsConfiguration', - name: 'TinyMCE Stylesheets Property Editor UI', - element: () => import('./stylesheets/property-editor-ui-tiny-mce-stylesheets-configuration.element.js'), - meta: { - label: 'TinyMCE Stylesheets Configuration', - icon: 'icon-autofill', - group: 'common', - }, - }, - { - type: 'propertyEditorUi', - alias: 'Umb.PropertyEditorUI.TinyMCE.DimensionsConfiguration', - name: 'TinyMCE Dimensions Property Editor UI', - element: () => import('./dimensions/property-editor-ui-tiny-mce-dimensions-configuration.element.js'), - meta: { - label: 'TinyMCE Dimensions Configuration', - icon: 'icon-autofill', - group: 'common', - }, - }, - { - type: 'propertyEditorUi', - alias: 'Umb.PropertyEditorUI.TinyMCE.MaxImageSizeConfiguration', - name: 'TinyMCE Max Image Size Property Editor UI', - element: () => import('./max-image-size/property-editor-ui-tiny-mce-maximagesize.element.js'), - meta: { - label: 'TinyMCE Max Image Size Configuration', - icon: 'icon-autofill', - group: 'common', - }, - }, -]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/max-image-size/property-editor-ui-tiny-mce-maximagesize.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/max-image-size/property-editor-ui-tiny-mce-maximagesize.element.ts deleted file mode 100644 index 01b3f1656a..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/max-image-size/property-editor-ui-tiny-mce-maximagesize.element.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { customElement, html, property } from '@umbraco-cms/backoffice/external/lit'; -import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/property-editor'; -import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; -import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; - -/** - * @element umb-property-editor-ui-tiny-mce-maximagesize - */ -@customElement('umb-property-editor-ui-tiny-mce-maximagesize') -export class UmbPropertyEditorUITinyMceMaxImageSizeElement extends UmbLitElement implements UmbPropertyEditorUiElement { - @property({ type: Number }) - value: number = 0; - - #onChange(e: UUIInputEvent) { - this.value = Number(e.target.value as string); - this.dispatchEvent(new UmbChangeEvent()); - } - - override render() { - return html` - - - `; - } -} - -export default UmbPropertyEditorUITinyMceMaxImageSizeElement; - -declare global { - interface HTMLElementTagNameMap { - 'umb-property-editor-ui-tiny-mce-maximagesize': UmbPropertyEditorUITinyMceMaxImageSizeElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/max-image-size/property-editor-ui-tiny-mce-maximagesize.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/max-image-size/property-editor-ui-tiny-mce-maximagesize.stories.ts deleted file mode 100644 index 714136611c..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/max-image-size/property-editor-ui-tiny-mce-maximagesize.stories.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { umbDataTypeMockDb } from '../../../../mocks/data/data-type/data-type.db.js'; -import type { Meta } from '@storybook/web-components'; -import { html } from '@umbraco-cms/backoffice/external/lit'; -import './property-editor-ui-tiny-mce-maximagesize.element.js'; -import type { UmbDataTypeDetailModel } from '@umbraco-cms/backoffice/data-type'; - -const dataTypeData = umbDataTypeMockDb.read('dt-richTextEditor') as unknown as UmbDataTypeDetailModel; - -export default { - title: 'Property Editor UIs/Tiny Mce Max Image Size', - component: 'umb-property-editor-ui-tiny-mce-maximagesize', - id: 'umb-property-editor-ui-tiny-mce-maximagesize', -} as Meta; - -export const AAAOverview = ({ value }: any) => - html``; -AAAOverview.storyName = 'Overview'; -AAAOverview.args = { - value: dataTypeData?.values?.find((x) => x.alias === 'maxImageSize')?.value, -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/max-image-size/property-editor-ui-tiny-mce-maximagesize.test.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/max-image-size/property-editor-ui-tiny-mce-maximagesize.test.ts deleted file mode 100644 index 2edf9abbf9..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/max-image-size/property-editor-ui-tiny-mce-maximagesize.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { UmbPropertyEditorUITinyMceMaxImageSizeElement } from './property-editor-ui-tiny-mce-maximagesize.element.js'; -import { expect, fixture, html } from '@open-wc/testing'; -import { type UmbTestRunnerWindow, defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; - -describe('UmbPropertyEditorUITinyMceMaxImSizeElement', () => { - let element: UmbPropertyEditorUITinyMceMaxImageSizeElement; - - beforeEach(async () => { - element = await fixture(html` - - `); - }); - - it('is defined with its own instance', () => { - expect(element).to.be.instanceOf(UmbPropertyEditorUITinyMceMaxImageSizeElement); - }); - - if ((window as UmbTestRunnerWindow).__UMBRACO_TEST_RUN_A11Y_TEST) { - it('passes the a11y audit', async () => { - await expect(element).shadowDom.to.be.accessible(defaultA11yConfig); - }); - } -}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/stylesheets/property-editor-ui-tiny-mce-stylesheets-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/stylesheets/property-editor-ui-tiny-mce-stylesheets-configuration.element.ts deleted file mode 100644 index b0df41b3a6..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/stylesheets/property-editor-ui-tiny-mce-stylesheets-configuration.element.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { customElement, html, property } from '@umbraco-cms/backoffice/external/lit'; -import { UmbDeprecation } from '@umbraco-cms/backoffice/utils'; -import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbServerFilePathUniqueSerializer } from '@umbraco-cms/backoffice/server-file-system'; -import type { - UmbPropertyEditorConfigCollection, - UmbPropertyEditorUiElement, -} from '@umbraco-cms/backoffice/property-editor'; -import type { UmbStylesheetInputElement } from '@umbraco-cms/backoffice/stylesheet'; -import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; - -/** - * @element umb-property-editor-ui-tiny-mce-stylesheets-configuration - */ -@customElement('umb-property-editor-ui-tiny-mce-stylesheets-configuration') -export class UmbPropertyEditorUITinyMceStylesheetsConfigurationElement - extends UmbLitElement - implements UmbPropertyEditorUiElement -{ - readonly #serverFilePathUniqueSerializer = new UmbServerFilePathUniqueSerializer(); - - @property({ type: Array }) - public set value(value: Array) { - if (!value) return; - this.#value = value.map((unique) => this.#serverFilePathUniqueSerializer.toUnique(unique)); - } - public get value(): Array { - if (!this.#value) return []; - return this.#value.map((unique) => this.#serverFilePathUniqueSerializer.toServerPath(unique)) as string[]; - } - #value: Array = []; - - @property({ type: Object, attribute: false }) - public config?: UmbPropertyEditorConfigCollection; - - #onChange(event: CustomEvent) { - const target = event.target as UmbStylesheetInputElement; - this.#value = target.selection ?? []; - this.dispatchEvent(new UmbChangeEvent()); - } - - constructor() { - super(); - new UmbDeprecation({ - deprecated: 'umb-property-editor-ui-tiny-mce-stylesheets-configuration', - removeInVersion: '16.0.0', - solution: - "Use `` instead, or the 'Umb.PropertyEditorUi.StylesheetPicker' manifest.", - }).warn(); - } - - override render() { - return html``; - } -} - -export default UmbPropertyEditorUITinyMceStylesheetsConfigurationElement; - -declare global { - interface HTMLElementTagNameMap { - 'umb-property-editor-ui-tiny-mce-stylesheets-configuration': UmbPropertyEditorUITinyMceStylesheetsConfigurationElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/stylesheets/property-editor-ui-tiny-mce-stylesheets-configuration.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/stylesheets/property-editor-ui-tiny-mce-stylesheets-configuration.stories.ts deleted file mode 100644 index 1341c768a7..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/stylesheets/property-editor-ui-tiny-mce-stylesheets-configuration.stories.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { umbDataTypeMockDb } from '../../../../mocks/data/data-type/data-type.db.js'; -import type { Meta } from '@storybook/web-components'; -import { html } from '@umbraco-cms/backoffice/external/lit'; - -import './property-editor-ui-tiny-mce-stylesheets-configuration.element.js'; -import type { UmbDataTypeDetailModel } from '@umbraco-cms/backoffice/data-type'; - -const dataTypeData = umbDataTypeMockDb.read('dt-richTextEditor') as unknown as UmbDataTypeDetailModel; - -export default { - title: 'Property Editor UIs/Tiny Mce Stylesheets Configuration', - component: 'umb-property-editor-ui-tiny-mce-stylesheets-configuration', - id: 'umb-property-editor-ui-tiny-mce-stylesheets-configuration', -} as Meta; - -export const AAAOverview = ({ value }: any) => - html``; -AAAOverview.storyName = 'Overview'; -AAAOverview.args = { - value: dataTypeData?.values?.find((x) => x.alias === 'stylesheets')?.value ?? [], -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/stylesheets/property-editor-ui-tiny-mce-stylesheets-configuration.test.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/stylesheets/property-editor-ui-tiny-mce-stylesheets-configuration.test.ts deleted file mode 100644 index 7cd2cc71d6..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/stylesheets/property-editor-ui-tiny-mce-stylesheets-configuration.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { UmbPropertyEditorUITinyMceStylesheetsConfigurationElement } from './property-editor-ui-tiny-mce-stylesheets-configuration.element.js'; -import { expect, fixture, html } from '@open-wc/testing'; -import { type UmbTestRunnerWindow, defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; - -describe('UmbPropertyEditorUITinyMceStylesheetsConfigurationElement', () => { - let element: UmbPropertyEditorUITinyMceStylesheetsConfigurationElement; - - beforeEach(async () => { - element = await fixture(html` - - `); - }); - - it('is defined with its own instance', () => { - expect(element).to.be.instanceOf(UmbPropertyEditorUITinyMceStylesheetsConfigurationElement); - }); - - if ((window as UmbTestRunnerWindow).__UMBRACO_TEST_RUN_A11Y_TEST) { - it('passes the a11y audit', async () => { - await expect(element).shadowDom.to.be.accessible(defaultA11yConfig); - }); - } -}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/tiny-mce/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/tiny-mce/constants.ts deleted file mode 100644 index 67f1af3d74..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/tiny-mce/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const UMB_BLOCK_RTE_PROPERTY_EDITOR_UI_ALIAS = 'Umb.PropertyEditorUi.TinyMCE'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/tiny-mce/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/tiny-mce/manifests.ts deleted file mode 100644 index b3c14d87c3..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/tiny-mce/manifests.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { UMB_BLOCK_RTE_PROPERTY_EDITOR_UI_ALIAS } from './constants.js'; -import { UMB_BLOCK_RTE_PROPERTY_EDITOR_SCHEMA_ALIAS } from '@umbraco-cms/backoffice/rte'; - -export const manifests: Array = [ - { - type: 'propertyEditorUi', - alias: UMB_BLOCK_RTE_PROPERTY_EDITOR_UI_ALIAS, - name: 'Rich Text Editor Property Editor UI', - element: () => import('./property-editor-ui-tiny-mce.element.js'), - meta: { - label: 'Rich Text Editor [TinyMCE]', - propertyEditorSchemaAlias: UMB_BLOCK_RTE_PROPERTY_EDITOR_SCHEMA_ALIAS, - icon: 'icon-browser-window', - group: 'richContent', - supportsReadOnly: true, - settings: { - properties: [ - { - alias: 'toolbar', - label: 'Toolbar', - description: 'Pick the toolbar options that should be available when editing', - propertyEditorUiAlias: 'Umb.PropertyEditorUI.TinyMCE.ToolbarConfiguration', - weight: 10, - config: [ - { - alias: 'toolbar', - value: [ - { alias: 'undo', label: 'Undo', icon: 'undo' }, - { alias: 'redo', label: 'Redo', icon: 'redo' }, - { alias: 'cut', label: 'Cut', icon: 'cut' }, - { alias: 'copy', label: 'Copy', icon: 'copy' }, - { alias: 'paste', label: 'Paste', icon: 'paste' }, - { alias: 'styles', label: 'Style select', icon: 'permanent-pen' }, - { alias: 'fontname', label: 'Font select', icon: 'text-color' }, - { alias: 'fontsize', label: 'Font size', icon: 'text-color' }, - { alias: 'forecolor', label: 'Text color', icon: 'text-color' }, - { alias: 'backcolor', label: 'Background color', icon: 'highlight-bg-color' }, - { alias: 'blockquote', label: 'Blockquote', icon: 'quote' }, - { alias: 'formatblock', label: 'Format block', icon: 'format' }, - { alias: 'removeformat', label: 'Remove format', icon: 'remove-formatting' }, - { alias: 'bold', label: 'Bold', icon: 'bold' }, - { alias: 'italic', label: 'Italic', icon: 'italic' }, - { alias: 'underline', label: 'Underline', icon: 'underline' }, - { alias: 'strikethrough', label: 'Strikethrough', icon: 'strike-through' }, - { alias: 'alignleft', label: 'Align left', icon: 'align-left' }, - { alias: 'aligncenter', label: 'Align center', icon: 'align-center' }, - { alias: 'alignright', label: 'Align right', icon: 'align-right' }, - { alias: 'alignjustify', label: 'Align justify', icon: 'align-justify' }, - { alias: 'bullist', label: 'Bullet list', icon: 'unordered-list' }, - { alias: 'numlist', label: 'Numbered list', icon: 'ordered-list' }, - { alias: 'outdent', label: 'Outdent', icon: 'outdent' }, - { alias: 'indent', label: 'Indent', icon: 'indent' }, - { alias: 'anchor', label: 'Anchor', icon: 'bookmark' }, - { alias: 'table', label: 'Table', icon: 'table' }, - { alias: 'hr', label: 'Horizontal rule', icon: 'horizontal-rule' }, - { alias: 'subscript', label: 'Subscript', icon: 'subscript' }, - { alias: 'superscript', label: 'Superscript', icon: 'superscript' }, - { alias: 'charmap', label: 'Character map', icon: 'insert-character' }, - { alias: 'rtl', label: 'Right to left', icon: 'rtl' }, - { alias: 'ltr', label: 'Left to right', icon: 'ltr' }, - ], - }, - ], - }, - { - alias: 'stylesheets', - label: '#treeHeaders_stylesheets', - description: 'Pick the stylesheets whose editor styles should be available when editing', - propertyEditorUiAlias: 'Umb.PropertyEditorUi.StylesheetPicker', - weight: 20, - }, - { - alias: 'dimensions', - label: '#general_dimensions', - description: 'Set the editor dimensions', - propertyEditorUiAlias: 'Umb.PropertyEditorUI.TinyMCE.DimensionsConfiguration', - weight: 30, - }, - { - alias: 'maxImageSize', - label: '#rte_config_maxImageSize', - description: '{#rte_config_maxImageSize_description}', - propertyEditorUiAlias: 'Umb.PropertyEditorUI.TinyMCE.MaxImageSizeConfiguration', - weight: 40, - }, - { - alias: 'mode', - label: 'Mode', - description: 'Select the mode for the editor', - propertyEditorUiAlias: 'Umb.PropertyEditorUi.RadioButtonList', - config: [{ alias: 'items', value: ['Classic', 'Inline'] }], - weight: 50, - }, - { - alias: 'overlaySize', - label: '#rte_config_overlaySize', - description: '{#rte_config_overlaySize_description}', - propertyEditorUiAlias: 'Umb.PropertyEditorUi.OverlaySize', - weight: 81, - }, - { - alias: 'hideLabel', - label: 'Hide Label', - propertyEditorUiAlias: 'Umb.PropertyEditorUi.Toggle', - weight: 82, - }, - ], - defaultData: [ - { - alias: 'toolbar', - value: [ - 'styles', - 'bold', - 'italic', - 'alignleft', - 'aligncenter', - 'alignright', - 'bullist', - 'numlist', - 'outdent', - 'indent', - 'sourcecode', - 'link', - 'umbmediapicker', - 'umbembeddialog', - ], - }, - { alias: 'mode', value: 'Classic' }, - { alias: 'maxImageSize', value: 500 }, - ], - }, - }, - }, -]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/tiny-mce/property-editor-ui-tiny-mce.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/tiny-mce/property-editor-ui-tiny-mce.element.ts deleted file mode 100644 index aa63a33930..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/tiny-mce/property-editor-ui-tiny-mce.element.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { UmbInputTinyMceElement } from '../../components/input-tiny-mce/input-tiny-mce.element.js'; -import { customElement, html } from '@umbraco-cms/backoffice/external/lit'; -import { UmbPropertyEditorUiRteElementBase } from '@umbraco-cms/backoffice/rte'; - -import '../../components/input-tiny-mce/input-tiny-mce.element.js'; - -/** - * @element umb-property-editor-ui-tiny-mce - */ -@customElement('umb-property-editor-ui-tiny-mce') -export class UmbPropertyEditorUITinyMceElement extends UmbPropertyEditorUiRteElementBase { - #onChange(event: CustomEvent & { target: UmbInputTinyMceElement }) { - const markup = typeof event.target.value === 'string' ? event.target.value : ''; - - // If we don't get any markup clear the property editor value. - if (markup === '') { - this.value = undefined; - this._fireChangeEvent(); - return; - } - - // Remove unused Blocks of Blocks Layout. Leaving only the Blocks that are present in Markup. - const usedContentKeys: string[] = []; - - // Regex matching all block elements in the markup, and extracting the content key. It's the same as the one used on the backend. - const regex = new RegExp( - /(?:)?<\/umb-rte-block(?:-inline)?>/gi, - ); - let blockElement: RegExpExecArray | null; - while ((blockElement = regex.exec(markup)) !== null) { - if (blockElement.groups?.key) { - usedContentKeys.push(blockElement.groups.key); - } - } - - if (this.value) { - this.value = { - ...this.value, - markup: markup, - }; - } else { - this.value = { - markup: markup, - blocks: { - layout: {}, - contentData: [], - settingsData: [], - expose: [], - }, - }; - } - - // lets run this one after we set the value, to make sure we don't reset the value. - this._filterUnusedBlocks(usedContentKeys); - - this._fireChangeEvent(); - } - - override render() { - return html` - - - `; - } -} - -export default UmbPropertyEditorUITinyMceElement; - -declare global { - interface HTMLElementTagNameMap { - 'umb-property-editor-ui-tiny-mce': UmbPropertyEditorUITinyMceElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/tiny-mce/property-editor-ui-tiny-mce.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/tiny-mce/property-editor-ui-tiny-mce.stories.ts deleted file mode 100644 index a5019f808c..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/tiny-mce/property-editor-ui-tiny-mce.stories.ts +++ /dev/null @@ -1,114 +0,0 @@ -import type { UmbPropertyEditorUITinyMceElement } from './property-editor-ui-tiny-mce.element.js'; -import type { Meta, StoryObj } from '@storybook/web-components'; -import { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; - -import './property-editor-ui-tiny-mce.element.js'; - -const config = new UmbPropertyEditorConfigCollection([ - { - alias: 'hideLabel', - value: true, - }, - { alias: 'dimensions', value: { height: 500 } }, - { alias: 'maxImageSize', value: 500 }, - { alias: 'ignoreUserStartNodes', value: false }, - { - alias: 'validElements', - value: - '+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,video[*],audio[*],picture[*],source[*],canvas[*]', - }, - { alias: 'invalidElements', value: 'font' }, - { - alias: 'toolbar', - value: [ - 'sourcecode', - 'undo', - 'redo', - 'styles', - 'bold', - 'italic', - 'alignleft', - 'aligncenter', - 'alignright', - 'bullist', - 'numlist', - 'outdent', - 'indent', - 'link', - 'unlink', - 'anchor', - 'table', - 'umbmediapicker', - 'umbembeddialog', - ], - }, - { - alias: 'plugins', - value: [ - { - name: 'anchor', - }, - { - name: 'charmap', - }, - { - name: 'table', - }, - { - name: 'lists', - }, - { - name: 'advlist', - }, - { - name: 'autolink', - }, - { - name: 'directionality', - }, - { - name: 'searchreplace', - }, - ], - }, -]); - -const meta: Meta = { - title: 'Property Editor UIs/Tiny Mce', - component: 'umb-property-editor-ui-tiny-mce', - id: 'umb-property-editor-ui-tiny-mce', - args: { - config: undefined, - value: { - blocks: { - layout: {}, - contentData: [], - settingsData: [], - expose: [], - }, - markup: ` -

TinyMCE

-

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

-

- TinyMCE documentation -

-

- TinyMCE quick start guide -

- Umbraco documentation -

- `, - }, - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; - -export const DefaultConfig: Story = { - args: { - config, - }, -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/tiny-mce/property-editor-ui-tiny-mce.test.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/tiny-mce/property-editor-ui-tiny-mce.test.ts deleted file mode 100644 index 362b9e369a..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/tiny-mce/property-editor-ui-tiny-mce.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { UmbPropertyEditorUITinyMceElement } from './property-editor-ui-tiny-mce.element.js'; -import { expect, fixture, html } from '@open-wc/testing'; -import { type UmbTestRunnerWindow, defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; - -describe('UmbPropertyEditorUITinyMceElement', () => { - let element: UmbPropertyEditorUITinyMceElement; - - beforeEach(async () => { - element = await fixture(html` `); - }); - - it('is defined with its own instance', () => { - expect(element).to.be.instanceOf(UmbPropertyEditorUITinyMceElement); - }); - - if ((window as UmbTestRunnerWindow).__UMBRACO_TEST_RUN_A11Y_TEST) { - it('passes the a11y audit', async () => { - await expect(element).shadowDom.to.be.accessible(defaultA11yConfig); - }); - } -}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/toolbar/property-editor-ui-tiny-mce-toolbar-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/toolbar/property-editor-ui-tiny-mce-toolbar-configuration.element.ts deleted file mode 100644 index f5c1d1e227..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/toolbar/property-editor-ui-tiny-mce-toolbar-configuration.element.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { css, customElement, html, property, state, repeat } from '@umbraco-cms/backoffice/external/lit'; -import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; -import { tinymce } from '@umbraco-cms/backoffice/external/tinymce'; -import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; -import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; -import type { - UmbPropertyEditorUiElement, - UmbPropertyEditorConfigCollection, -} from '@umbraco-cms/backoffice/property-editor'; -import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; - -const tinyIconSet = tinymce.IconManager.get('default'); - -type ToolbarConfig = { - alias: string; - label: string; - icon?: string; - selected: boolean; -}; - -/** - * @element umb-property-editor-ui-tiny-mce-toolbar-configuration - */ -@customElement('umb-property-editor-ui-tiny-mce-toolbar-configuration') -export class UmbPropertyEditorUITinyMceToolbarConfigurationElement - extends UmbLitElement - implements UmbPropertyEditorUiElement -{ - @property({ attribute: false }) - set value(value: string | string[] | null) { - if (!value) return; - - if (typeof value === 'string') { - this.#selectedValues = value.split(',').filter((x) => x.length > 0); - } else if (Array.isArray(value)) { - this.#selectedValues = value; - } else { - this.#selectedValues = []; - return; - } - - // Migrations - if (this.#selectedValues.includes('ace')) { - this.#selectedValues = this.#selectedValues.filter((v) => v !== 'ace'); - this.#selectedValues.push('sourcecode'); - } - - this._toolbarConfig.forEach((v) => { - v.selected = this.#selectedValues.includes(v.alias); - }); - } - get value(): string[] { - return this.#selectedValues; - } - - @property({ attribute: false }) - config?: UmbPropertyEditorConfigCollection; - - @state() - private readonly _toolbarConfig: ToolbarConfig[] = []; - - #selectedValues: string[] = []; - - protected override async firstUpdated(_changedProperties: PropertyValueMap) { - super.firstUpdated(_changedProperties); - - this.config?.getValueByAlias('toolbar')?.forEach((v) => { - this._toolbarConfig.push({ - ...v, - selected: this.value.includes(v.alias), - }); - }); - - await this.getToolbarPlugins(); - - this.requestUpdate('_toolbarConfig'); - } - - private async getToolbarPlugins(): Promise { - // Get all the toolbar plugins - const plugin$ = umbExtensionsRegistry.byType('tinyMcePlugin'); - - const plugins = await firstValueFrom(plugin$); - - plugins.forEach((p) => { - // If the plugin has a toolbar, add it to the config - if (p.meta?.toolbar) { - p.meta.toolbar.forEach((t: any) => { - this._toolbarConfig.push({ - alias: t.alias, - label: this.localize.string(t.label), - icon: t.icon ?? 'icon-autofill', - selected: this.value.includes(t.alias), - }); - }); - } - }); - } - - private onChange(event: CustomEvent) { - const checkbox = event.target as HTMLInputElement; - const alias = checkbox.value; - - const value = this._toolbarConfig - .filter((t) => (t.alias !== alias && t.selected) || (t.alias === alias && checkbox.checked)) - .map((v) => v.alias); - - this.value = value; - - this.dispatchEvent(new UmbChangeEvent()); - } - - override render() { - return html`
    - ${repeat( - this._toolbarConfig, - (v) => v.alias, - (v) => - html`
  • - - - ${v.label} - -
  • `, - )} -
`; - } - - static override readonly styles = [ - UmbTextStyles, - css` - ul { - list-style: none; - padding: 0; - margin: 0; - - uui-icon { - width: 1.5em; - height: 1.5em; - margin-right: 5px; - } - } - `, - ]; -} - -export default UmbPropertyEditorUITinyMceToolbarConfigurationElement; - -declare global { - interface HTMLElementTagNameMap { - 'umb-property-editor-ui-tiny-mce-toolbar-configuration': UmbPropertyEditorUITinyMceToolbarConfigurationElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/toolbar/property-editor-ui-tiny-mce-toolbar-configuration.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/toolbar/property-editor-ui-tiny-mce-toolbar-configuration.stories.ts deleted file mode 100644 index 29f8543717..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/toolbar/property-editor-ui-tiny-mce-toolbar-configuration.stories.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { umbDataTypeMockDb } from '../../../../mocks/data/data-type/data-type.db.js'; -import type { Meta } from '@storybook/web-components'; -import { html } from '@umbraco-cms/backoffice/external/lit'; - -import './property-editor-ui-tiny-mce-toolbar-configuration.element.js'; -import type { UmbDataTypeDetailModel } from '@umbraco-cms/backoffice/data-type'; - -const dataTypeData = umbDataTypeMockDb.read('dt-richTextEditor') as unknown as UmbDataTypeDetailModel; - -export default { - title: 'Property Editor UIs/Tiny Mce Toolbar Configuration', - component: 'umb-property-editor-ui-tiny-mce-toolbar-configuration', - id: 'umb-property-editor-ui-tiny-mce-toolbar-configuration', -} as Meta; - -export const AAAOverview = ({ value }: any) => - html``; - -AAAOverview.storyName = 'Overview'; - -AAAOverview.args = { - value: dataTypeData?.values?.find((x) => x.alias === 'toolbar')?.value ?? [], -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/toolbar/property-editor-ui-tiny-mce-toolbar-configuration.test.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/toolbar/property-editor-ui-tiny-mce-toolbar-configuration.test.ts deleted file mode 100644 index a9c977a699..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/property-editors/toolbar/property-editor-ui-tiny-mce-toolbar-configuration.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { UmbPropertyEditorUITinyMceToolbarConfigurationElement } from './property-editor-ui-tiny-mce-toolbar-configuration.element.js'; -import { expect, fixture, html } from '@open-wc/testing'; -import { type UmbTestRunnerWindow, defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; - -describe('UmbPropertyEditorUITinyMceToolbarConfigurationElement', () => { - let element: UmbPropertyEditorUITinyMceToolbarConfigurationElement; - - beforeEach(async () => { - element = await fixture(html` - - `); - }); - - it('is defined with its own instance', () => { - expect(element).to.be.instanceOf(UmbPropertyEditorUITinyMceToolbarConfigurationElement); - }); - - if ((window as UmbTestRunnerWindow).__UMBRACO_TEST_RUN_A11Y_TEST) { - it('passes the a11y audit', async () => { - await expect(element).shadowDom.to.be.accessible(defaultA11yConfig); - }); - } -}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/umbraco-package.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/umbraco-package.ts deleted file mode 100644 index 691ff65f0c..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/umbraco-package.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const name = 'Umbraco.Core.TinyMCE'; -export const extensions = [ - { - name: 'TinyMCE Bundle', - alias: 'Umb.Bundle.TinyMCE', - type: 'bundle', - js: () => import('./manifests.js'), - }, -]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/vite.config.ts b/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/vite.config.ts deleted file mode 100644 index 0385c52efa..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/tiny-mce/vite.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { defineConfig } from 'vite'; -import { rmSync } from 'fs'; -import { getDefaultConfig } from '../../vite-config-base'; - -const dist = '../../../dist-cms/packages/tiny-mce'; - -// delete the unbundled dist folder -rmSync(dist, { recursive: true, force: true }); - -export default defineConfig({ - ...getDefaultConfig({ - dist, - }), -}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-statusbar-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-statusbar-configuration.element.ts index 14a7be8f35..246c0d6680 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-statusbar-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-statusbar-configuration.element.ts @@ -199,7 +199,7 @@ export class UmbPropertyEditorUiTiptapStatusbarConfigurationElement
${when( area?.data.length === 0, - () => html`Empty`, + () => html`Empty`, () => html`${area!.data.map((alias, idx) => this.#renderItem(alias, areaIndex, idx))}`, )}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts index 820ff25915..a91e7ea652 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/components/property-editor-ui-tiptap-toolbar-configuration.element.ts @@ -34,7 +34,7 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement set value(value: UmbTiptapToolbarValue | undefined) { if (!value) value = [[[]]]; if (value === this.#value) return; - this.#value = this.#context.migrateTinyMceToolbar(value); + this.#value = this.#context.isValidToolbarValue(value) ? value : [[[]]]; } get value(): UmbTiptapToolbarValue | undefined { return this.#context.cloneToolbarValue(this.#value); @@ -246,7 +246,7 @@ export class UmbPropertyEditorUiTiptapToolbarConfigurationElement
${when( group?.data.length === 0, - () => html`Empty`, + () => html`Empty`, () => html`${group!.data.map((alias, idx) => this.#renderItem(alias, rowIndex, groupIndex, idx))}`, )}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/contexts/tiptap-toolbar-configuration.context.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/contexts/tiptap-toolbar-configuration.context.ts index a068ee7162..8620fadef5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/contexts/tiptap-toolbar-configuration.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/contexts/tiptap-toolbar-configuration.context.ts @@ -28,50 +28,6 @@ export class UmbTiptapToolbarConfigurationContext extends UmbContextBase([], (x) => x.unique); public readonly toolbar = this.#toolbar.asObservable(); - /** @deprecated This will be removed in Umbraco 16. */ - #tinyMceToolbarMapping: Record = { - undo: 'Umb.Tiptap.Toolbar.Undo', - redo: 'Umb.Tiptap.Toolbar.Redo', - cut: null, - copy: null, - paste: null, - styles: 'Umb.Tiptap.Toolbar.StyleSelect', - fontname: 'Umb.Tiptap.Toolbar.FontFamily', - fontfamily: 'Umb.Tiptap.Toolbar.FontFamily', - fontsize: 'Umb.Tiptap.Toolbar.FontSize', - forecolor: 'Umb.Tiptap.Toolbar.TextColorForeground', - backcolor: 'Umb.Tiptap.Toolbar.TextColorBackground', - blockquote: 'Umb.Tiptap.Toolbar.Blockquote', - formatblock: null, - removeformat: 'Umb.Tiptap.Toolbar.ClearFormatting', - bold: 'Umb.Tiptap.Toolbar.Bold', - italic: 'Umb.Tiptap.Toolbar.Italic', - underline: 'Umb.Tiptap.Toolbar.Underline', - strikethrough: 'Umb.Tiptap.Toolbar.Strike', - alignleft: 'Umb.Tiptap.Toolbar.TextAlignLeft', - aligncenter: 'Umb.Tiptap.Toolbar.TextAlignCenter', - alignright: 'Umb.Tiptap.Toolbar.TextAlignRight', - alignjustify: 'Umb.Tiptap.Toolbar.TextAlignJustify', - bullist: 'Umb.Tiptap.Toolbar.BulletList', - numlist: 'Umb.Tiptap.Toolbar.OrderedList', - outdent: 'Umb.Tiptap.Toolbar.TextOutdent', - indent: 'Umb.Tiptap.Toolbar.TextIndent', - anchor: 'Umb.Tiptap.Toolbar.Anchor', - table: 'Umb.Tiptap.Toolbar.Table', - hr: 'Umb.Tiptap.Toolbar.HorizontalRule', - subscript: 'Umb.Tiptap.Toolbar.Subscript', - superscript: 'Umb.Tiptap.Toolbar.Superscript', - charmap: 'Umb.Tiptap.Toolbar.CharacterMap', - rtl: 'Umb.Tiptap.Toolbar.TextDirectionRtl', - ltr: 'Umb.Tiptap.Toolbar.TextDirectionLtr', - link: 'Umb.Tiptap.Toolbar.Link', - unlink: 'Umb.Tiptap.Toolbar.Unlink', - sourcecode: 'Umb.Tiptap.Toolbar.SourceEditor', - umbmediapicker: 'Umb.Tiptap.Toolbar.MediaPicker', - umbembeddialog: 'Umb.Tiptap.Toolbar.EmbeddedMedia', - umbblockpicker: 'Umb.Tiptap.Toolbar.BlockPicker', - }; - constructor(host: UmbControllerHost) { super(host, UMB_TIPTAP_TOOLBAR_CONFIGURATION_CONTEXT); @@ -184,28 +140,6 @@ export class UmbTiptapToolbarConfigurationContext extends UmbContextBase | null} value - The value to migrate. - * @returns {UmbTiptapToolbarValue} The migrated value. - * @deprecated This will be removed in Umbraco 16. - */ - public migrateTinyMceToolbar(value?: UmbTiptapToolbarValue | Array | null): UmbTiptapToolbarValue { - if (this.isValidToolbarValue(value)) return value; - - const items: Array = []; - - if (Array.isArray(value) && value.length > 0 && typeof value[0] === 'string') { - for (const alias of value) { - const mapping = this.#tinyMceToolbarMapping[alias]; - if (mapping) { - items.push(mapping); - } - } - } - - return [[items]]; - } - public moveToolbarItem(from: [number, number, number], to: [number, number, number]) { const [fromRowIndex, fromGroupIndex, fromItemIndex] = from; const [toRowIndex, toGroupIndex, toItemIndex] = to; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/manifests.ts index a795207657..0dc7c51319 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/manifests.ts @@ -51,14 +51,15 @@ _Drag and drop the available actions onto the statusbar areas._`, alias: 'dimensions', label: '#general_dimensions', description: '{#tiptap_config_dimensions_description}', - propertyEditorUiAlias: 'Umb.PropertyEditorUI.TinyMCE.DimensionsConfiguration', + propertyEditorUiAlias: 'Umb.PropertyEditorUI.Dimensions', weight: 30, }, { alias: 'maxImageSize', label: '#rte_config_maxImageSize', description: '{#rte_config_maxImageSize_description}', - propertyEditorUiAlias: 'Umb.PropertyEditorUI.TinyMCE.MaxImageSizeConfiguration', + propertyEditorUiAlias: 'Umb.PropertyEditorUi.Integer', + config: [{ alias: 'min', value: 0 }], weight: 40, }, { diff --git a/src/Umbraco.Web.UI.Client/src/rollup.config.js b/src/Umbraco.Web.UI.Client/src/rollup.config.js index f4a313b69c..ece64bc411 100644 --- a/src/Umbraco.Web.UI.Client/src/rollup.config.js +++ b/src/Umbraco.Web.UI.Client/src/rollup.config.js @@ -39,16 +39,6 @@ console.log('--- Copying UUI Fonts ---'); cpSync('./node_modules/@umbraco-ui/uui-css/assets/fonts', `${DIST_DIRECTORY}/assets/fonts`, { recursive: true }); console.log('--- Copying src UUI Fonts done ---'); -// Copy TinyMCE -console.log('--- Copying TinyMCE ---'); -cpSync('./node_modules/tinymce', `${DIST_DIRECTORY}/tinymce`, { recursive: true }); -console.log('--- Copying TinyMCE done ---'); - -// Copy TinyMCE i18n -console.log('--- Copying TinyMCE i18n ---'); -cpSync('./node_modules/tinymce-i18n/langs6', `${DIST_DIRECTORY}/tinymce/langs`, { recursive: true }); -console.log('--- Copying TinyMCE i18n done ---'); - // Copy monaco-editor console.log('--- Copying monaco-editor ---'); cpSync('./node_modules/monaco-editor/esm/vs/editor/editor.worker.js', `${DIST_DIRECTORY}/monaco-editor/vs/editor/editor.worker.js`); diff --git a/src/Umbraco.Web.UI.Client/tsconfig.json b/src/Umbraco.Web.UI.Client/tsconfig.json index 4470ea1776..b648af791a 100644 --- a/src/Umbraco.Web.UI.Client/tsconfig.json +++ b/src/Umbraco.Web.UI.Client/tsconfig.json @@ -129,7 +129,6 @@ DON'T EDIT THIS FILE DIRECTLY. It is generated by /devops/tsconfig/index.js "@umbraco-cms/backoffice/template": ["./src/packages/templating/templates/index.ts"], "@umbraco-cms/backoffice/temporary-file": ["./src/packages/core/temporary-file/index.ts"], "@umbraco-cms/backoffice/themes": ["./src/packages/core/themes/index.ts"], - "@umbraco-cms/backoffice/tiny-mce": ["./src/packages/tiny-mce/index.ts"], "@umbraco-cms/backoffice/tiptap": ["./src/packages/tiptap/index.ts"], "@umbraco-cms/backoffice/translation": ["./src/packages/translation/index.ts"], "@umbraco-cms/backoffice/tree": ["./src/packages/core/tree/index.ts"], @@ -153,7 +152,6 @@ DON'T EDIT THIS FILE DIRECTLY. It is generated by /devops/tsconfig/index.js "@umbraco-cms/backoffice/external/openid": ["./src/external/openid/index.ts"], "@umbraco-cms/backoffice/external/router-slot": ["./src/external/router-slot/index.ts"], "@umbraco-cms/backoffice/external/rxjs": ["./src/external/rxjs/index.ts"], - "@umbraco-cms/backoffice/external/tinymce": ["./src/external/tinymce/index.ts"], "@umbraco-cms/backoffice/external/tiptap": ["./src/external/tiptap/index.ts"], "@umbraco-cms/backoffice/external/uui": ["./src/external/uui/index.ts"], "@umbraco-cms/backoffice/external/uuid": ["./src/external/uuid/index.ts"] diff --git a/src/Umbraco.Web.UI.Client/vite.config.ts b/src/Umbraco.Web.UI.Client/vite.config.ts index c57b7aa65d..72104c6283 100644 --- a/src/Umbraco.Web.UI.Client/vite.config.ts +++ b/src/Umbraco.Web.UI.Client/vite.config.ts @@ -21,14 +21,6 @@ export const plugins: PluginOption[] = [ src: 'src/assets/*', dest: 'umbraco/backoffice/assets', }, - { - src: 'node_modules/tinymce/**/*', - dest: 'umbraco/backoffice/tinymce', - }, - { - src: 'node_modules/tinymce-i18n/langs6/**/*', - dest: 'umbraco/backoffice/tinymce/langs', - }, { src: 'node_modules/msw/lib/iife/**/*', dest: 'umbraco/backoffice/msw', diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TinyMCE.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TinyMCE.spec.ts deleted file mode 100644 index a6257ce9b8..0000000000 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/TinyMCE.spec.ts +++ /dev/null @@ -1,258 +0,0 @@ -import {ConstantHelper, NotificationConstantHelper, test} from "@umbraco/playwright-testhelpers"; -import {expect} from "@playwright/test"; - -const tinyMCEName = 'TestTinyMCE'; - -test.beforeEach(async ({umbracoUi, umbracoApi}) => { - await umbracoApi.dataType.ensureNameNotExists(tinyMCEName); - await umbracoUi.goToBackOffice(); - await umbracoUi.dataType.goToSettingsTreeItem('Data Types'); -}); - -test.afterEach(async ({umbracoApi}) => { - await umbracoApi.dataType.ensureNameNotExists(tinyMCEName); -}); - -test('can create a rich text editor with tinyMCE', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { - // Arrange - const tinyMCELocatorName = 'Rich Text Editor [TinyMCE]'; - const tinyMCEFilterKeyword = 'Rich Text Editor'; - const tinyMCEAlias = 'Umbraco.RichText'; - const tinyMCEUiAlias = 'Umb.PropertyEditorUi.TinyMCE'; - const toolbarValue = [ - "styles", - "bold", - "italic", - "alignleft", - "aligncenter", - "alignright", - "bullist", - "numlist", - "outdent", - "indent", - "sourcecode", - "link", - "umbmediapicker", - "umbembeddialog" - ]; - - // Act - await umbracoUi.dataType.clickActionsMenuAtRoot(); - await umbracoUi.dataType.clickActionsMenuCreateButton(); - await umbracoUi.dataType.clickDataTypeButton(); - await umbracoUi.dataType.enterDataTypeName(tinyMCEName); - await umbracoUi.dataType.clickSelectAPropertyEditorButton(); - await umbracoUi.dataType.selectAPropertyEditor(tinyMCELocatorName, tinyMCEFilterKeyword); - await umbracoUi.dataType.clickSaveButton(); - - // Assert - await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); - expect(await umbracoApi.dataType.doesNameExist(tinyMCEName)).toBeTruthy(); - // Verify the default configuration - await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.tinyMCESettings); - await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.tinyMCESettings); - await umbracoUi.dataType.doesPropertyEditorHaveAlias(tinyMCEAlias); - await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(tinyMCEUiAlias); - const dataTypeData = await umbracoApi.dataType.getByName(tinyMCEName); - expect(dataTypeData.editorAlias).toBe(tinyMCEAlias); - expect(dataTypeData.editorUiAlias).toBe(tinyMCEUiAlias); - expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'maxImageSize', 500)).toBeTruthy(); - expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'mode', 'Classic')).toBeTruthy(); - expect(await umbracoApi.dataType.doesTinyMCEToolbarHaveItems(tinyMCEName, toolbarValue)).toBeTruthy(); -}); - -test('can rename a rich text editor with tinyMCE', async ({umbracoApi, umbracoUi}) => { - // Arrange - const wrongName = 'tinyMCETest'; - await umbracoApi.dataType.createDefaultTinyMCEDataType(wrongName); - - // Act - await umbracoUi.dataType.goToDataType(wrongName); - await umbracoUi.dataType.enterDataTypeName(tinyMCEName); - await umbracoUi.dataType.clickSaveButton(); - - // Assert - await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - expect(await umbracoApi.dataType.doesNameExist(tinyMCEName)).toBeTruthy(); - expect(await umbracoApi.dataType.doesNameExist(wrongName)).toBeFalsy(); -}); - -test('can delete a rich text editor with tinyMCE', async ({umbracoApi, umbracoUi}) => { - // Arrange - await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName); - - // Act - await umbracoUi.dataType.clickRootFolderCaretButton(); - await umbracoUi.dataType.clickActionsMenuForDataType(tinyMCEName); - await umbracoUi.dataType.clickDeleteAndConfirmButton(); - - // Assert - await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); - expect(await umbracoApi.dataType.doesNameExist(tinyMCEName)).toBeFalsy(); - await umbracoUi.dataType.isTreeItemVisible(tinyMCEName, false); -}); - -test('can enable toolbar options', async ({umbracoApi, umbracoUi}) => { - // Arrange - const toolbarValues = ["undo", "redo", "cut"]; - await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName); - await umbracoUi.dataType.goToDataType(tinyMCEName); - const toolbarItemCount = await umbracoApi.dataType.getTinyMCEToolbarItemsCount(tinyMCEName); - - // Act - await umbracoUi.dataType.clickToolbarOptionByValue(toolbarValues); - await umbracoUi.dataType.clickSaveButton(); - - // Assert - await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - const expectedToolbarItems = toolbarItemCount + toolbarValues.length; - expect(await umbracoApi.dataType.doesTinyMCEToolbarItemsMatchCount(tinyMCEName, expectedToolbarItems)).toBeTruthy(); - expect(await umbracoApi.dataType.doesTinyMCEToolbarHaveItems(tinyMCEName, toolbarValues)).toBeTruthy(); -}); - -test('can add stylesheet', async ({umbracoApi, umbracoUi}) => { - // Arrange - const stylesheetName = 'StylesheetForDataType.css'; - const stylesheetPath = await umbracoApi.stylesheet.createDefaultStylesheet(stylesheetName); - await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName); - await umbracoUi.dataType.goToDataType(tinyMCEName); - - // Act - await umbracoUi.dataType.addStylesheet(stylesheetName); - await umbracoUi.dataType.clickSaveButton(); - - // Assert - await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'stylesheets', [stylesheetPath])).toBeTruthy(); - - // Clean - await umbracoApi.stylesheet.ensureNameNotExists(stylesheetName); -}); - -test('can add dimensions', async ({umbracoApi, umbracoUi}) => { - // Arrange - const width = 100; - const height = 10; - await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName); - await umbracoUi.dataType.goToDataType(tinyMCEName); - - // Act - await umbracoUi.dataType.enterDimensionsValue(width.toString(), height.toString()); - await umbracoUi.dataType.clickSaveButton(); - - // Assert - await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - expect(await umbracoApi.dataType.doesRTEHaveDimensions(tinyMCEName, width, height)).toBeTruthy(); -}); - -test('can update maximum size for inserted images', async ({umbracoApi, umbracoUi}) => { - // Arrange - const maximumSize = 300; - await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName); - await umbracoUi.dataType.goToDataType(tinyMCEName); - - // Act - await umbracoUi.dataType.enterMaximumSizeForImages(maximumSize.toString()); - await umbracoUi.dataType.clickSaveButton(); - - // Assert - await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'maxImageSize', maximumSize)).toBeTruthy(); -}); - -test('can enable inline editing mode', async ({umbracoApi, umbracoUi}) => { - // Arrange - const mode = 'Inline'; - await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName); - await umbracoUi.dataType.goToDataType(tinyMCEName); - - // Act - await umbracoUi.dataType.clickInlineRadioButton(); - await umbracoUi.dataType.clickSaveButton(); - - // Assert - await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'mode', mode)).toBeTruthy(); -}); - -test('can add an available block', async ({umbracoApi, umbracoUi}) => { - // Arrange - const elementTypeName = 'TestElementType'; - const elementTypeId = await umbracoApi.documentType.createEmptyElementType(elementTypeName); - - await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName); - await umbracoUi.dataType.goToDataType(tinyMCEName); - - // Act - await umbracoUi.dataType.addAvailableBlocks(elementTypeName); - await umbracoUi.dataType.clickSaveButton(); - - // Assert - await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - expect(await umbracoApi.dataType.doesRTEContainBlocks(tinyMCEName, [elementTypeId])).toBeTruthy(); - - // Clean - await umbracoApi.documentType.ensureNameNotExists(elementTypeName); -}); - -test('can select overlay size', async ({umbracoApi, umbracoUi}) => { - // Arrange - const overlaySizeValue = 'large'; - await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName); - await umbracoUi.dataType.goToDataType(tinyMCEName); - - // Act - await umbracoUi.dataType.chooseOverlaySizeByValue(overlaySizeValue); - await umbracoUi.dataType.clickSaveButton(); - - // Assert - await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'overlaySize', overlaySizeValue)).toBeTruthy(); -}); - -test('can enable hide label', async ({umbracoApi, umbracoUi}) => { - // Arrange - await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName); - await umbracoUi.dataType.goToDataType(tinyMCEName); - - // Act - await umbracoUi.dataType.clickHideLabelToggle(); - await umbracoUi.dataType.clickSaveButton(); - - // Assert - await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'hideLabel', true)).toBeTruthy(); -}); - -test('can add image upload folder', async ({umbracoApi, umbracoUi}) => { - // Arrange - const mediaFolderName = 'TestMediaFolder'; - const mediaFolderId = await umbracoApi.media.createDefaultMediaFolder(mediaFolderName); - await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName); - await umbracoUi.dataType.goToDataType(tinyMCEName); - - // Act - await umbracoUi.dataType.addImageUploadFolder(mediaFolderName); - await umbracoUi.dataType.clickSaveButton(); - - // Assert - await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'mediaParentId', mediaFolderId)).toBeTruthy(); - - // Clean - await umbracoApi.media.ensureNameNotExists(mediaFolderName); -}); - -test('can enable ignore user start nodes', async ({umbracoApi, umbracoUi}) => { - // Arrange - await umbracoApi.dataType.createDefaultTinyMCEDataType(tinyMCEName); - await umbracoUi.dataType.goToDataType(tinyMCEName); - - // Act - await umbracoUi.dataType.clickIgnoreUserStartNodesToggle(); - await umbracoUi.dataType.clickSaveButton(); - - // Assert - await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - expect(await umbracoApi.dataType.doesDataTypeHaveValue(tinyMCEName, 'ignoreUserStartNodes', true)).toBeTruthy(); -}); \ No newline at end of file