use vanilla initialiser - allows setting editor mode

update editor config with mode selection
styles
This commit is contained in:
Nathan Woulfe
2023-03-08 15:44:07 +10:00
parent f3afbdbbce
commit f42e91d8a1
3 changed files with 143 additions and 43 deletions

View File

@@ -1,9 +1,9 @@
import { html } from 'lit';
import { css, html } from 'lit';
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { customElement, property, state } from 'lit/decorators.js';
import { ifDefined } from 'lit-html/directives/if-defined.js';
import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins';
import { AstNode, Editor, EditorEvent, TinyMCE } from 'tinymce';
import { AstNode, Editor, EditorEvent } from 'tinymce';
import { firstValueFrom } from 'rxjs';
import { UmbMediaHelper } from '../../property-editors/uis/tiny-mce/media-helper.service';
import {
@@ -17,22 +17,93 @@ import type { UserDetails } from '@umbraco-cms/models';
import { DataTypePropertyModel } from '@umbraco-cms/backend-api';
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
import { ManifestTinyMcePlugin } from 'libs/extensions-registry/tinymce-plugin.model';
/// TINY MCE
import '../../../../../public-assets/tiny-mce/tinymce.min.js';
import '@tinymce/tinymce-webcomponent';
declare global {
interface Window {
tinyConfig: { [key: string]: string | number | boolean | object | (() => void) };
tinymce: TinyMCE;
//tinyConfig: { [key: string]: string | number | boolean | object | (() => void) };
tinymce: any;
Umbraco: any;
}
}
enum EditorMode {
'classic',
'inline',
'distraction-free', // ?? probably not needed - not exposed in current backoffice
}
@customElement('umb-input-tiny-mce')
export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) {
static styles = [UUITextStyles];
static styles = [
UUITextStyles,
css`
#editor {
position: relative;
min-height: 100px;
}
.umb-macro-holder {
border: 3px dotted var(---uui-palette-spanish-pink-light);
padding: 7px;
display: block;
margin: 3px;
}
/* TODO => alternate background loader
.umb-macro-holder.loading {
background: url(assets/img/loader.gif) right no-repeat;
background-size: 18px;
background-position-x: 99%;
} */
.embeditem {
position: relative;
}
.embeditem > * {
user-select: none;
pointer-events: none;
}
.embeditem[data-mce-selected] {
outline: 2px solid var(---uui-palette-spanish-pink-light);
}
.embeditem::before {
z-index: 1000;
width: 100%;
height: 100%;
position: absolute;
content: ' ';
}
.embeditem[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);
}
.tox-tinymce-aux {
z-index: 9000;
}
.tox-tinymce-inline {
z-index: 900;
}
.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;
}
`,
];
@property()
configuration: Array<DataTypePropertyModel> = [];
@@ -169,13 +240,11 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) {
'umbmacro',
'umbembeddialog',
],
mode: 'classic',
stylesheets: [],
maxImageSize: 500,
};
// @property({ type: String })
// private _contentStyle: string = contentUiSkinCss.toString() + '\n' + contentCss.toString();
#currentUserStore?: UmbCurrentUserStore;
modalContext!: UmbModalContext;
#mediaHelper = new UmbMediaHelper(this);
@@ -209,9 +278,12 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) {
connectedCallback() {
super.connectedCallback();
this._configObject = this.configuration
? Object.fromEntries(this.configuration.map((x) => [x.alias, x.value]))
: this.#fallbackConfig;
// create an object by merging the configuration onto the fallback config
Object.assign(
this._configObject,
this.#fallbackConfig,
this.configuration ? Object.fromEntries(this.configuration.map((x) => [x.alias, x.value])) : {}
);
// no auto resize when a fixed height is set
if (!this._configObject.dimensions?.height) {
@@ -222,9 +294,13 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) {
}
// TODO => setup runs before rendering, here we can add any custom plugins
async #setTinyConfig() {
#setTinyConfig() {
const target = document.createElement('div');
target.id = 'editor';
this.shadowRoot?.appendChild(target);
// set the default values that will not be modified via configuration
window.tinyConfig = {
const tinyConfig: { [key: string]: any } = {
autoresize_bottom_margin: 10,
base_url: '/public-assets/tiny-mce',
body_class: 'umb-rte',
@@ -237,34 +313,52 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) {
paste_preprocess: (_: Editor, args: { content: string }) => this.#cleanupPasteData(args),
relative_urls: false,
resize: false,
target,
statusbar: false,
setup: (editor: Editor) => this.#editorSetup(editor),
};
// TODO => set editor mode from configuration?
const plugins: Array<string> = this._configObject.plugins.map((x: any) => x.name);
const toolbar = this._configObject.toolbar.join(' ');
// extend with configuration values
Object.assign(window.tinyConfig, {
Object.assign(tinyConfig, {
content_css: this._configObject.stylesheets.join(','),
extended_valid_elements: this.#extendedValidElements,
height: ifDefined(this._configObject.dimensions?.height),
invalid_elements: this._configObject.invalidElements,
plugins: this._configObject.plugins.map((x: any) => x.name),
quickbars_insert_toolbar: toolbar,
quickbars_selection_toolbar: toolbar,
plugins,
style_formats: this._styleFormats,
toolbar,
valid_elements: this._configObject.validElements,
width: ifDefined(this._configObject.dimensions?.width),
});
// configure correct toolbars based on editor mode
const editorMode = this._configObject.mode?.toLowerCase();
if (editorMode === EditorMode[EditorMode.classic]) {
Object.assign(tinyConfig, {
toolbar,
});
} else if (editorMode === EditorMode[EditorMode.inline]) {
Object.assign(tinyConfig, {
inline: true,
toolbar,
});
tinyConfig.plugins.push('quickbars');
} else if (editorMode === EditorMode[EditorMode['distraction-free']]) {
Object.assign(tinyConfig, {
inline: true,
quickbars_insert_toolbar: toolbar,
quickbars_selection_toolbar: toolbar,
});
}
// Need to check if we are allowed to UPLOAD images
// This is done by checking if the insert image toolbar button is available
if (this.#isMediaPickerEnabled()) {
Object.assign(window.tinyConfig, {
Object.assign(tinyConfig, {
// Update the TinyMCE Config object to allow pasting
images_upload_handler: this.#mediaHelper.uploadImageHandler,
automatic_uploads: false,
@@ -273,6 +367,8 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) {
paste_data_images: true,
});
}
window.tinymce.init(tinyConfig);
}
#cleanupPasteData(args: { content: string }) {
@@ -302,8 +398,9 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) {
return languageMatch ?? this.#defaultLanguage;
}
async #editorSetup(editor: Editor) {
#editorSetup(editor: Editor) {
editor.suffix = '.min';
// register custom option maxImageSize
editor.options.register('maxImageSize', { processor: 'number', default: this.#fallbackConfig.maxImageSize });
@@ -311,19 +408,19 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) {
// these receive the default args below, but can also
// provide their own args. Generally though, the additional
// args would be managed in the plugin
const pluginArgs: TinyMcePluginArguments = {
editor: editor,
modalContext: this.modalContext,
configuration: this.configuration,
currentUser: this.currentUser,
mediaHelper: this.#mediaHelper,
};
(async () => {
const pluginArgs: TinyMcePluginArguments = {
editor,
modalContext: this.modalContext,
configuration: this.configuration,
currentUser: this.currentUser,
mediaHelper: this.#mediaHelper,
};
const observable = umbExtensionsRegistry
?.extensionsOfType('tinyMcePlugin');
const plugins = (await firstValueFrom(observable)) as ManifestTinyMcePlugin[];
plugins.forEach(p => new p.meta.api(pluginArgs, p.meta.args));
const observable = umbExtensionsRegistry?.extensionsOfType('tinyMcePlugin');
const plugins = (await firstValueFrom(observable)) as ManifestTinyMcePlugin[];
plugins.forEach((p) => new p.meta.api(pluginArgs, p.meta.args));
})();
// define keyboard shortcuts
editor.addShortcut('Ctrl+S', '', () => this.dispatchEvent(new CustomEvent('rte.shortcut.save')));
@@ -336,8 +433,7 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) {
editor.on('Keyup', () => this.#onChange(editor.getContent()));
editor.on('SetContent', () => this.#mediaHelper.uploadBlobImages(editor));
editor.on('focus', () =>
this.dispatchEvent(new CustomEvent('umb-rte-focus', { composed: true, bubbles: true })));
editor.on('focus', () => this.dispatchEvent(new CustomEvent('umb-rte-focus', { composed: true, bubbles: true })));
editor.on('blur', () => {
this.#onChange(editor.getContent());
@@ -349,6 +445,8 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) {
this.#onChange(editor.getContent());
});
editor.on('init', () => editor.setContent(this.value.toString()));
// If we can not find the insert image/media toolbar button
// Then we need to add an event listener to the editor
// That will update native browser drag & drop events
@@ -447,7 +545,7 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) {
}
render() {
return html` <tinymce-editor config="tinyConfig">${this.value}</tinymce-editor>`;
return html``;
}
}

View File

@@ -4,11 +4,11 @@ import { manifests as propertyActionManifests } from './property-actions/manifes
import { manifests as propertyEditorModelManifests } from './property-editors/models/manifests';
import { manifests as propertyEditorUIManifests } from './property-editors/uis/manifests';
import { manifests as collectionViewManifests } from './collection/views/manifests';
import { manifests as tinyMcePluginManifests } from './property-editors/uis/tiny-mce/plugins/manifests';
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
import { ManifestTypes } from '@umbraco-cms/extensions-registry';
import { manifests as tinyMcePluginManifests } from './property-editors/uis/tiny-mce/plugins/manifests';
const registerExtensions = (manifests: Array<ManifestTypes>) => {
manifests.forEach((manifest) => {

View File

@@ -89,9 +89,11 @@ export class UmbMediaHelper {
return imagePath;
}
// TODO => use backend cli when available
const result = await fetch('/umbraco/management/api/v1/images/GetProcessedImageUrl');
const url = await result.json() as string;
return result as any;
return url;
}
/**