Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 156 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@
"@typescript-eslint/parser": "^6.5.0",
"@web/dev-server-esbuild": "^0.4.1",
"@web/dev-server-import-maps": "^0.1.1",
"@web/dev-server-rollup": "^0.6.0",
"@web/test-runner": "^0.17.0",
"@web/test-runner-playwright": "^0.10.1",
"babel-loader": "^9.1.3",
Expand Down
13 changes: 7 additions & 6 deletions src/external/tinymce/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,27 @@
*
* TODO: Load the plugins that we want to use in the editor.
*/
import * as tiny from 'tinymce';
import tinymce, { type RawEditorOptions } from 'tinymce';

// Declare a global variable to hold the TinyMCE instance
declare global {
interface Window {
tinymce: typeof tiny.default;
tinymce: typeof tinymce;
}
}

// Load default icons making them available to everyone
import 'tinymce/icons/default/icons.js';

const defaultConfig: tiny.RawEditorOptions = {
const defaultConfig: RawEditorOptions = {
base_url: '/umbraco/backoffice/tinymce',
};

/* Initialize TinyMCE */
export function renderEditor(userConfig?: tiny.RawEditorOptions) {
export function renderEditor(userConfig?: RawEditorOptions) {
const config = { ...defaultConfig, ...userConfig };
return window.tinymce.init(config);
return tinymce.init(config);
}

export { tiny as tinymce };
export { tinymce };
export type { RawEditorOptions, AstNode, EditorEvent, Editor } from 'tinymce';
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { tinymce } from '@umbraco-cms/backoffice/external/tinymce';
import type { RawEditorOptions } from '@umbraco-cms/backoffice/external/tinymce';

export type TinyStyleSheet = tinymce.RawEditorOptions['style_formats'];
export type TinyStyleSheet = RawEditorOptions['style_formats'];

export const defaultStyleFormats: TinyStyleSheet = [
{
Expand Down Expand Up @@ -29,7 +29,7 @@ export const defaultStyleFormats: TinyStyleSheet = [
export const defaultExtendedValidElements =
'@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style],-h1[id|dir|class|align|style],-h2[id|dir|class|align|style],-h3[id|dir|class|align|style],-h4[id|dir|class|align|style],-h5[id|dir|class|align|style],-h6[id|style|dir|class|align],span[id|class|style|lang],figure,figcaption';

export const defaultFallbackConfig: tinymce.RawEditorOptions = {
export const defaultFallbackConfig: RawEditorOptions = {
toolbar: [
'styles',
'bold',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { pastePreProcessHandler, uploadImageHandler } from './input-tiny-mce.han
import { availableLanguages } from './input-tiny-mce.languages.js';
import { uriAttributeSanitizer } from './input-tiny-mce.sanitizer.js';
import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui';
import { renderEditor, type tinymce } from '@umbraco-cms/backoffice/external/tinymce';
import {
type Editor,
type EditorEvent,
type RawEditorOptions,
renderEditor,
} from '@umbraco-cms/backoffice/external/tinymce';
import { UMB_AUTH_CONTEXT, UmbLoggedInUser } from '@umbraco-cms/backoffice/auth';
import { TinyMcePluginArguments, UmbTinyMcePluginBase } from '@umbraco-cms/backoffice/components';
import { ClassConstructor, hasDefaultExport, loadExtension } from '@umbraco-cms/backoffice/extension-api';
Expand Down Expand Up @@ -31,13 +36,13 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) {
configuration?: UmbPropertyEditorConfigCollection;

@state()
private _tinyConfig: tinymce.RawEditorOptions = {};
private _tinyConfig: RawEditorOptions = {};

#mediaHelper = new UmbMediaHelper();
#currentUser?: UmbLoggedInUser;
#auth?: typeof UMB_AUTH_CONTEXT.TYPE;
#plugins: Array<new (args: TinyMcePluginArguments) => UmbTinyMcePluginBase> = [];
#editorRef?: tinymce.Editor | null = null;
#editorRef?: Editor | null = null;
#stylesheetRepository?: UmbStylesheetRepository;
#serverUrl?: string;

Expand Down Expand Up @@ -256,7 +261,7 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) {
}
}

#editorSetup(editor: tinymce.Editor) {
#editorSetup(editor: Editor) {
editor.suffix = '.min';

// register custom option maxImageSize
Expand Down Expand Up @@ -306,7 +311,7 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) {
// To update the icon to show you can NOT drop something into the editor
if (this._tinyConfig.toolbar && !this.#isMediaPickerEnabled()) {
// Wire up the event listener
editor.on('dragstart dragend dragover draggesture dragdrop drop drag', (e: tinymce.EditorEvent<InputEvent>) => {
editor.on('dragstart dragend dragover draggesture dragdrop drop drag', (e: EditorEvent<InputEvent>) => {
e.preventDefault();
if (e.dataTransfer) {
e.dataTransfer.effectAllowed = 'none';
Expand All @@ -317,7 +322,7 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) {
}
}

#onInit(editor: tinymce.Editor) {
#onInit(editor: Editor) {
//enable browser based spell checking
editor.getBody().setAttribute('spellcheck', 'true');
uriAttributeSanitizer(editor);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { tinymce } from '@umbraco-cms/backoffice/external/tinymce';
import type { RawEditorOptions } from '@umbraco-cms/backoffice/external/tinymce';

export const pastePreProcessHandler: tinymce.RawEditorOptions['paste_preprocess'] = (_editor, args) => {
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.
Expand All @@ -9,7 +9,7 @@ export const pastePreProcessHandler: tinymce.RawEditorOptions['paste_preprocess'
args.content = args.content.replace(/<\s*i([^>]*)>(.*?)<\s*\/\s*i([^>]*)>/g, '<em$1>$2</em$3>');
};

export const uploadImageHandler: tinymce.RawEditorOptions['images_upload_handler'] = (blobInfo, progress) => {
export const uploadImageHandler: RawEditorOptions['images_upload_handler'] = (blobInfo, progress) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', window.Umbraco?.Sys.ServerVariables.umbracoUrls.tinyMceApiBaseUrl + 'UploadImage');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { tinymce } from '@umbraco-cms/backoffice/external/tinymce';
import type { AstNode, 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
*/
export const uriAttributeSanitizer = (editor: tinymce.Editor) => {
export const uriAttributeSanitizer = (editor: Editor) => {
const uriAttributesToSanitize = ['src', 'href', 'data', 'background', 'action', 'formaction', 'poster', 'xlink:href'];

const parseUri = (function () {
Expand Down Expand Up @@ -46,8 +46,8 @@ export const uriAttributeSanitizer = (editor: tinymce.Editor) => {

if (window.Umbraco?.Sys.ServerVariables.umbracoSettings.sanitizeTinyMce) {
uriAttributesToSanitize.forEach((attribute) => {
editor.serializer.addAttributeFilter(attribute, (nodes: tinymce.AstNode[]) => {
nodes.forEach((node: tinymce.AstNode) => {
editor.serializer.addAttributeFilter(attribute, (nodes: AstNode[]) => {
nodes.forEach((node: AstNode) => {
node.attributes?.forEach((attr) => {
if (uriAttributesToSanitize.includes(attr.name.toLowerCase())) {
attr.value = parseUri(attr.value, node.name) ?? '';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { UmbInputTinyMceElement } from '@umbraco-cms/backoffice/components';
import type { tinymce } from '@umbraco-cms/backoffice/external/tinymce';
import type { Editor } from '@umbraco-cms/backoffice/external/tinymce';
import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';

export class UmbTinyMcePluginBase {
host: UmbInputTinyMceElement;
editor: tinymce.Editor;
editor: Editor;
configuration?: UmbPropertyEditorConfigCollection;

constructor(arg: TinyMcePluginArguments) {
Expand All @@ -16,5 +16,5 @@ export class UmbTinyMcePluginBase {

export interface TinyMcePluginArguments {
host: UmbInputTinyMceElement;
editor: tinymce.Editor;
editor: Editor;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs';
import { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
import { tinymce } from '@umbraco-cms/backoffice/external/tinymce';

const tinyIconSet = tinymce.default?.IconManager.get('default');
const tinyIconSet = tinymce.IconManager.get('default');

type ToolbarConfig = {
alias: string;
Expand Down
Loading