import { Arr, Fun, Type } from '@ephox/katamari'; import * as EditorContent from '../content/EditorContent'; import * as Deprecations from '../Deprecations'; import * as NodeType from '../dom/NodeType'; import * as EditorRemove from '../EditorRemove'; import { BlobInfoImagePair } from '../file/ImageScanner'; import * as EditorFocus from '../focus/EditorFocus'; import * as Render from '../init/Render'; import { NodeChange } from '../NodeChange'; import { normalizeOptions } from '../options/NormalizeOptions'; import SelectionOverrides from '../SelectionOverrides'; import { UndoManager } from '../undo/UndoManagerTypes'; import Quirks from '../util/Quirks'; import * as VisualAids from '../view/VisualAids'; import AddOnManager from './AddOnManager'; import Annotator from './Annotator'; import * as Commands from './commands/Commands'; import DOMUtils from './dom/DOMUtils'; import { EventUtilsCallback } from './dom/EventUtils'; import ScriptLoader from './dom/ScriptLoader'; import EditorSelection from './dom/Selection'; import DomSerializer from './dom/Serializer'; import EditorCommands, { ExecCommandArgs, EditorCommandCallback } from './EditorCommands'; import EditorManager from './EditorManager'; import EditorObservable from './EditorObservable'; import { BuiltInOptionType, BuiltInOptionTypeMap, Options as EditorOptions, create as createOptions } from './EditorOptions'; import EditorUpload, { UploadResult } from './EditorUpload'; import Env from './Env'; import { SaveContentEvent } from './EventTypes'; import Formatter from './Formatter'; import DomParser from './html/DomParser'; import AstNode from './html/Node'; import Schema from './html/Schema'; import { create as createMode, EditorMode } from './Mode'; import { Model } from './ModelManager'; import NotificationManager from './NotificationManager'; import * as Options from './Options'; import { NormalizedEditorOptions, RawEditorOptions } from './OptionTypes'; import PluginManager, { Plugin } from './PluginManager'; import Shortcuts from './Shortcuts'; import { Theme } from './ThemeManager'; import { registry } from './ui/Registry'; import { EditorUi } from './ui/Ui'; import EventDispatcher, { NativeEventMap } from './util/EventDispatcher'; import I18n, { TranslatedString, Untranslated } from './util/I18n'; import Tools from './util/Tools'; import URI from './util/URI'; import WindowManager from './WindowManager'; /** * This class contains the core logic for a TinyMCE editor. * * @class tinymce.Editor * @mixes tinymce.util.Observable * @example * // Add a class to all paragraphs in the editor. * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass'); * * // Gets the current editors selection as text * tinymce.activeEditor.selection.getContent({ format: 'text' }); * * // Creates a new editor instance * const ed = new tinymce.Editor('textareaid', { * some_setting: 1 * }, tinymce.EditorManager); * * ed.render(); */ export interface EditorConstructor { readonly prototype: Editor; new (id: string, options: RawEditorOptions, editorManager: EditorManager): Editor; } // Shorten these names const DOM = DOMUtils.DOM; const extend = Tools.extend, each = Tools.each; /** * Include Editor API docs. * * @include ../../../../../tools/docs/tinymce.Editor.js */ class Editor implements EditorObservable { public documentBaseUrl: string; public baseUri: URI; /** * Editor instance id, normally the same as the div/textarea that was replaced. * * @property id * @type String */ public id: string; /** * Name/Value object containing plugin instances. * * @property plugins * @type Object * @example * // Execute a method inside a plugin directly * tinymce.activeEditor.plugins.someplugin.someMethod(); */ public plugins: Record = {}; /** * URI object to document configured for the TinyMCE instance. * * @property documentBaseURI * @type tinymce.util.URI * @example * // Get relative URL from the location of document_base_url * tinymce.activeEditor.documentBaseURI.toRelative('/somedir/somefile.htm'); * * // Get absolute URL from the location of document_base_url * tinymce.activeEditor.documentBaseURI.toAbsolute('somefile.htm'); */ public documentBaseURI: URI; /** * URI object to current document that holds the TinyMCE editor instance. * * @property baseURI * @type tinymce.util.URI * @example * // Get relative URL from the location of the API * tinymce.activeEditor.baseURI.toRelative('/somedir/somefile.htm'); * * // Get absolute URL from the location of the API * tinymce.activeEditor.baseURI.toAbsolute('somefile.htm'); */ public baseURI: URI; /** * Array with CSS files to load into the iframe. * * @property contentCSS * @type Array */ public contentCSS: string[] = []; /** * Array of CSS styles to add to head of document when the editor loads. * * @property contentStyles * @type Array */ public contentStyles: string[] = []; /** * Editor ui components * * @property ui * @type tinymce.editor.ui.Ui */ public ui: EditorUi; /** * Editor mode API * * @property mode * @type tinymce.EditorMode */ public mode: EditorMode; /** * Editor options API * * @property options * @type tinymce.EditorOptions */ public options: EditorOptions; /** * Editor upload API * * @property editorUpload * @type tinymce.EditorUpload */ public editorUpload!: EditorUpload; public shortcuts: Shortcuts; public loadedCSS: Record = {}; public editorCommands: EditorCommands; public suffix: string; public editorManager: EditorManager; public hidden: boolean; public inline: boolean; public hasVisual: boolean; public isNotDirty: boolean = false; // Arguments set later, for example by InitContentBody.ts // Note that these may technically be undefined up until PreInit (or similar) has fired, // however the types are aimed at an initialised editor for ease of use. public annotator!: Annotator; public bodyElement: HTMLElement | undefined; public bookmark: any; // Note: Intentionally any so as to not expose Optional public composing: boolean = false; public container!: HTMLElement; public contentAreaContainer!: HTMLElement; public contentDocument!: Document; public contentWindow!: Window; public delegates: Record> | undefined; public destroyed: boolean = false; public dom!: DOMUtils; public editorContainer!: HTMLElement; public eventRoot: Element | undefined; public formatter!: Formatter; public formElement: HTMLElement | undefined; public formEventDelegate: ((e: Event) => void) | undefined; public hasHiddenInput: boolean = false; public iframeElement: HTMLIFrameElement | null = null; public iframeHTML: string | undefined; public initialized: boolean = false; public notificationManager!: NotificationManager; public orgDisplay!: string; public orgVisibility: string | undefined; public parser!: DomParser; public quirks!: Quirks; public readonly: boolean = false; public removed: boolean = false; public schema!: Schema; public selection!: EditorSelection; public serializer!: DomSerializer; public startContent: string = ''; public targetElm!: HTMLElement; public theme!: Theme; public model!: Model; public undoManager!: UndoManager; public windowManager!: WindowManager; public _beforeUnload: (() => void) | undefined; public _eventDispatcher: EventDispatcher | undefined; public _nodeChangeDispatcher!: NodeChange; public _pendingNativeEvents: string[] = []; public _selectionOverrides!: SelectionOverrides; public _skinLoaded: boolean = false; // EditorObservable patches public bindPendingEventDelegates!: EditorObservable['bindPendingEventDelegates']; public toggleNativeEvent!: EditorObservable['toggleNativeEvent']; public unbindAllNativeEvents!: EditorObservable['unbindAllNativeEvents']; public fire!: EditorObservable['fire']; public dispatch!: EditorObservable['dispatch']; public on!: EditorObservable['on']; public off!: EditorObservable['off']; public once!: EditorObservable['once']; public hasEventListeners!: EditorObservable['hasEventListeners']; /** * Constructs a editor instance by id. * * @constructor * @method Editor * @param {String} id Unique id for the editor. * @param {Object} options Options for the editor. * @param {tinymce.EditorManager} editorManager EditorManager instance. */ public constructor(id: string, options: RawEditorOptions, editorManager: EditorManager) { this.editorManager = editorManager; this.documentBaseUrl = editorManager.documentBaseURL; // Patch in the EditorObservable functions extend(this, EditorObservable); const self = this; this.id = id; this.hidden = false; const normalizedOptions = normalizeOptions(editorManager.defaultOptions, options); this.options = createOptions(self, normalizedOptions); Options.register(self); const getOption = this.options.get; if (getOption('deprecation_warnings')) { Deprecations.logWarnings(options, normalizedOptions); } const suffix = getOption('suffix'); if (suffix) { editorManager.suffix = suffix; } this.suffix = editorManager.suffix; const baseUrl = getOption('base_url'); if (baseUrl) { editorManager._setBaseUrl(baseUrl); } this.baseUri = editorManager.baseURI; const referrerPolicy = Options.getReferrerPolicy(self); if (referrerPolicy) { ScriptLoader.ScriptLoader._setReferrerPolicy(referrerPolicy); DOMUtils.DOM.styleSheetLoader._setReferrerPolicy(referrerPolicy); } const contentCssCors = Options.hasContentCssCors(self); if (Type.isNonNullable(contentCssCors)) { DOMUtils.DOM.styleSheetLoader._setContentCssCors(contentCssCors); } AddOnManager.languageLoad = getOption('language_load'); AddOnManager.baseURL = editorManager.baseURL; this.setDirty(false); this.documentBaseURI = new URI(Options.getDocumentBaseUrl(self), { base_uri: this.baseUri }); this.baseURI = this.baseUri; this.inline = Options.isInline(self); this.hasVisual = Options.isVisualAidsEnabled(self); this.shortcuts = new Shortcuts(this); this.editorCommands = new EditorCommands(this); Commands.registerCommands(this); const cacheSuffix = getOption('cache_suffix'); if (cacheSuffix) { Env.cacheSuffix = cacheSuffix.replace(/^[\?\&]+/, ''); } this.ui = { registry: registry(), styleSheetLoader: undefined as any, show: Fun.noop, hide: Fun.noop, setEnabled: Fun.noop, isEnabled: Fun.always }; this.mode = createMode(self); // Call setup editorManager.dispatch('SetupEditor', { editor: this }); const setupCallback = Options.getSetupCallback(self); if (Type.isFunction(setupCallback)) { setupCallback.call(self, self); } } /** * Renders the editor/adds it to the page. * * @method render */ public render(): void { Render.render(this); } /** * Focuses/activates the editor. This will set this editor as the activeEditor in the tinymce collection * it will also place DOM focus inside the editor. * * @method focus * @param {Boolean} skipFocus Skip DOM focus. Just set is as the active editor. */ public focus(skipFocus?: boolean): void { this.execCommand('mceFocus', false, skipFocus); } /** * Returns true/false if the editor has real keyboard focus. * * @method hasFocus * @return {Boolean} Current focus state of the editor. */ public hasFocus(): boolean { return EditorFocus.hasFocus(this); } /** * Translates the specified string by replacing variables with language pack items it will also check if there is * a key matching the input. * * @method translate * @param {String} text String to translate by the language pack data. * @return {String} Translated string. */ public translate(text: Untranslated): TranslatedString { return I18n.translate(text); } /** * Returns a configuration parameter by name. *
* Deprecated in TinyMCE 6.0 and has been marked for removal in TinyMCE 7.0. Use the editor.options.get API instead. * * @method getParam * @param {String} name Configuration parameter to retrieve. * @param {String} defaultVal Optional default value to return. * @param {String} type Optional type parameter. * @return {String} Configuration parameter value or default value. * @deprecated Use editor.options.get instead * @example * // Returns a specific config value from the currently active editor * const someval = tinymce.activeEditor.getParam('myvalue'); * * // Returns a specific config value from a specific editor instance by id * const someval2 = tinymce.get('my_editor').getParam('myvalue'); */ public getParam (name: string, defaultVal: BuiltInOptionTypeMap[K], type: K): BuiltInOptionTypeMap[K]; public getParam (name: K, defaultVal?: NormalizedEditorOptions[K], type?: BuiltInOptionType): NormalizedEditorOptions[K]; public getParam (name: string, defaultVal: T, type?: BuiltInOptionType): T; public getParam(name: string, defaultVal?: any, type?: BuiltInOptionType): any { const options = this.options; // To keep the legacy API we need to register the option if it's not already been registered if (!options.isRegistered(name)) { if (Type.isNonNullable(type)) { options.register(name, { processor: type, default: defaultVal }); } else { options.register(name, { processor: Fun.always, default: defaultVal }); } } // Attempt to use the passed default value if nothing has been set already return !options.isSet(name) && !Type.isUndefined(defaultVal) ? defaultVal : options.get(name); } /** * Checks that the plugin is in the editor configuration and can optionally check if the plugin has been loaded. * * @method hasPlugin * @param {String} name The name of the plugin, as specified for the TinyMCE `plugins` option. * @param {Boolean} loaded If `true`, will also check that the plugin has been loaded. * @return {Boolean} If `loaded` is `true`, returns `true` if the plugin is in the configuration and has been loaded. If `loaded` is `false`, returns `true` if the plugin is in the configuration, regardless of plugin load status. * @example * // Returns `true` if the Comments plugin is in the editor configuration and has loaded successfully: * tinymce.activeEditor.hasPlugin('tinycomments', true); * // Returns `true` if the Table plugin is in the editor configuration, regardless of whether or not it loads: * tinymce.activeEditor.hasPlugin('table'); */ public hasPlugin(name: string, loaded?: boolean): boolean { const hasPlugin = Arr.contains(Options.getPlugins(this), name); if (hasPlugin) { return loaded ? PluginManager.get(name) !== undefined : true; } else { return false; } } /** * Dispatches out a onNodeChange event to all observers. This method should be called when you * need to update the UI states or element path etc. * * @method nodeChanged * @param {Object} args Optional args to pass to NodeChange event handlers. */ public nodeChanged(args?: any): void { this._nodeChangeDispatcher.nodeChanged(args); } /** * Adds a custom command to the editor. This function can also be used to override existing commands. * The command that you add can be executed with execCommand. * * @method addCommand * @param {String} name Command name to add/override. * @param {Function} callback Function to execute when the command occurs. * @param {Object} scope Optional scope to execute the function in. * @example * // Adds a custom command that later can be executed using execCommand * tinymce.init({ * ... * * setup: (ed) => { * // Register example command * ed.addCommand('mycommand', (ui, v) => { * ed.windowManager.alert('Hello world!! Selection: ' + ed.selection.getContent({ format: 'text' })); * }); * } * }); */ public addCommand(name: string, callback: EditorCommandCallback, scope: S): void; public addCommand(name: string, callback: EditorCommandCallback): void; public addCommand(name: string, callback: EditorCommandCallback, scope?: object): void { /** * Callback function that gets called when a command is executed. * * @callback addCommandCallback * @param {Boolean} ui Display UI state true/false. * @param {Object} value Optional value for command. * @return {Boolean} True/false state if the command was handled or not. */ this.editorCommands.addCommand(name, callback, scope); } /** * Adds a custom query state command to the editor. This function can also be used to override existing commands. * The command that you add can be executed with queryCommandState function. * * @method addQueryStateHandler * @param {String} name Command name to add/override. * @param {Function} callback Function to execute when the command state retrieval occurs. * @param {Object} scope Optional scope to execute the function in. */ public addQueryStateHandler(name: string, callback: (this: S) => boolean, scope?: S): void; public addQueryStateHandler(name: string, callback: (this: Editor) => boolean): void; public addQueryStateHandler(name: string, callback: (this: any) => boolean, scope?: any): void { /** * Callback function that gets called when a queryCommandState is executed. * * @callback addQueryStateHandlerCallback * @return {Boolean} True/false state if the command is enabled or not like is it bold. */ this.editorCommands.addQueryStateHandler(name, callback, scope); } /** * Adds a custom query value command to the editor. This function can also be used to override existing commands. * The command that you add can be executed with queryCommandValue function. * * @method addQueryValueHandler * @param {String} name Command name to add/override. * @param {Function} callback Function to execute when the command value retrieval occurs. * @param {Object} scope Optional scope to execute the function in. */ public addQueryValueHandler(name: string, callback: (this: S) => string, scope: S): void; public addQueryValueHandler(name: string, callback: (this: Editor) => string): void; public addQueryValueHandler(name: string, callback: (this: any) => string, scope?: any): void { /** * Callback function that gets called when a queryCommandValue is executed. * * @callback addQueryValueHandlerCallback * @return {Object} Value of the command or undefined. */ this.editorCommands.addQueryValueHandler(name, callback, scope); } /** * Adds a keyboard shortcut for some command or function. * * @method addShortcut * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o. * @param {String} desc Text description for the command. * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed. * @param {Object} scope Optional scope to execute the function in. * @return {Boolean} true/false state if the shortcut was added or not. * @example * editor.addShortcut('ctrl+a', 'description of the shortcut', () => {}); * editor.addShortcut('ctrl+alt+a', 'description of the shortcut', () => {}); * // "meta" maps to Command on Mac and Ctrl on PC * editor.addShortcut('meta+a', 'description of the shortcut', () => {}); * // "access" maps to Control+Option on Mac and shift+alt on PC * editor.addShortcut('access+a', 'description of the shortcut', () => {}); * * editor.addShortcut('meta+access+c', 'Opens the code editor dialog.', () => { * editor.execCommand('mceCodeEditor'); * }); * * editor.addShortcut('meta+shift+32', 'Inserts "Hello, World!" for meta+shift+space', () => { * editor.execCommand('mceInsertContent', false, 'Hello, World!'); * }); */ public addShortcut(pattern: string, desc: string, cmdFunc: string | [string, boolean, any] | (() => void), scope?: any): void { this.shortcuts.add(pattern, desc, cmdFunc, scope); } /** * Executes a registered command on the current instance. A list of available commands can be found in * the tinymce command identifiers documentation. * * @method execCommand * @param {String} cmd Command name to execute, for example mceLink or Bold. * @param {Boolean} ui Specifies if a UI (dialog) should be presented or not. * @param {Object/Array/String/Number/Boolean} value Optional command value, this can be anything. * @param {Object} args Optional arguments object. * @return {Boolean} true or false if the command was supported or not. */ public execCommand(cmd: string, ui?: boolean, value?: any, args?: ExecCommandArgs): boolean { return this.editorCommands.execCommand(cmd, ui, value, args); } /** * Returns a command specific state, for example if bold is enabled or not. * * @method queryCommandState * @param {String} cmd Command to query state from. * @return {Boolean} Command specific state, for example if bold is enabled or not. */ public queryCommandState(cmd: string): boolean { return this.editorCommands.queryCommandState(cmd); } /** * Returns a command specific value, for example the current font size. * * @method queryCommandValue * @param {String} cmd Command to query value from. * @return {String} Command value, for example the current font size or an empty string (`""`) if the query command is not found. */ public queryCommandValue(cmd: string): string { return this.editorCommands.queryCommandValue(cmd); } /** * Returns true/false if the command is supported or not. * * @method queryCommandSupported * @param {String} cmd Command that we check support for. * @return {Boolean} true/false if the command is supported or not. */ public queryCommandSupported(cmd: string): boolean { return this.editorCommands.queryCommandSupported(cmd); } /** * Shows the editor and hides any textarea/div that the editor is supposed to replace. * * @method show */ public show(): void { const self = this; if (self.hidden) { self.hidden = false; if (self.inline) { self.getBody().contentEditable = 'true'; } else { DOM.show(self.getContainer()); DOM.hide(self.id); } self.load(); self.dispatch('show'); } } /** * Hides the editor and shows any textarea/div that the editor is supposed to replace. * * @method hide */ public hide(): void { const self = this; if (!self.hidden) { // We must save before we hide so Safari doesn't crash self.save(); if (self.inline) { self.getBody().contentEditable = 'false'; // Make sure the editor gets blurred if (self === self.editorManager.focusedEditor) { self.editorManager.focusedEditor = null; } } else { DOM.hide(self.getContainer()); DOM.setStyle(self.id, 'display', self.orgDisplay); } self.hidden = true; self.dispatch('hide'); } } /** * Returns true/false if the editor is hidden or not. * * @method isHidden * @return {Boolean} True/false if the editor is hidden or not. */ public isHidden(): boolean { return this.hidden; } /** * Sets the progress state, this will display a throbber/progess for the editor. * This is ideal for asynchronous operations like an AJAX save call. * * @method setProgressState * @param {Boolean} state Boolean state if the progress should be shown or hidden. * @param {Number} time Optional time to wait before the progress gets shown. * @return {Boolean} Same as the input state. * @example * // Show progress for the active editor * tinymce.activeEditor.setProgressState(true); * * // Hide progress for the active editor * tinymce.activeEditor.setProgressState(false); * * // Show progress after 3 seconds * tinymce.activeEditor.setProgressState(true, 3000); */ public setProgressState(state: boolean, time?: number): void { this.dispatch('ProgressState', { state, time }); } /** * Loads contents from the textarea, input or other element that got converted into an editor instance. * This method will move the contents from that textarea, input or other element into the editor by using setContent * so all events etc that method has will get dispatched as well. * * @method load * @param {Object} args Optional content object, this gets passed around through the whole load process. * @return {String} HTML string that got set into the editor. */ public load(args: Partial = {}): string { const self = this; const elm = self.getElement(); if (self.removed) { return ''; } if (elm) { const loadArgs: Partial & { load: boolean } = { ...args, load: true }; const value = NodeType.isTextareaOrInput(elm) ? elm.value : elm.innerHTML; const html = self.setContent(value, loadArgs); if (!loadArgs.no_events) { self.dispatch('LoadContent', { ...loadArgs, element: elm }); } return html; } else { return ''; } } /** * Saves the contents from an editor out to the textarea or div element that got converted into an editor instance. * This method will move the HTML contents from the editor into that textarea or div by getContent * so all events etc that method has will get dispatched as well. * * @method save * @param {Object} args Optional content object, this gets passed around through the whole save process. * @return {String} HTML string that got set into the textarea/div. */ public save(args: Partial = {}): string { const self = this; let elm: HTMLElement | null = self.getElement(); if (!elm || !self.initialized || self.removed) { return ''; } const getArgs: Partial = { ...args, save: true, element: elm }; let html = self.getContent(getArgs); const saveArgs = { ...getArgs, content: html } as SaveContentEvent; if (!saveArgs.no_events) { self.dispatch('SaveContent', saveArgs); } // Always run this internal event if (saveArgs.format === 'raw') { self.dispatch('RawSaveContent', saveArgs); } html = saveArgs.content; if (!NodeType.isTextareaOrInput(elm)) { if (args.is_removing || !self.inline) { elm.innerHTML = html; } // Update hidden form element const form = DOM.getParent(self.id, 'form'); if (form) { each(form.elements, (elm) => { if ((elm as any).name === self.id) { (elm as any).value = html; return false; } else { return true; } }); } } else { elm.value = html; } saveArgs.element = getArgs.element = elm = null; if (saveArgs.set_dirty !== false) { self.setDirty(false); } return html; } /** * Sets the specified content to the editor instance, this will cleanup the content before it gets set using * the different cleanup rules options. *
* Note: The content return value was deprecated in TinyMCE 6.0 and has been marked for removal in TinyMCE 7.0. * * @method setContent * @param {String} content Content to set to editor, normally HTML contents but can be other formats as well. * @param {Object} args Optional content object, this gets passed around through the whole set process. * @return {String} HTML string that got set into the editor. * @example * // Sets the HTML contents of the activeEditor editor * tinymce.activeEditor.setContent('some html'); * * // Sets the content of a specific editor (my_editor in this example) * tinymce.get('my_editor').setContent(data); * * // Sets the content of the activeEditor editor using the specified format * tinymce.activeEditor.setContent('

Some html

', { format: 'html' }); */ public setContent(content: string, args?: Partial): string; public setContent(content: AstNode, args?: Partial): AstNode; public setContent(content: EditorContent.Content, args?: Partial): EditorContent.Content; public setContent(content: EditorContent.Content, args?: Partial): EditorContent.Content { return EditorContent.setContent(this, content, args); } /** * Gets the content from the editor instance, this will cleanup the content before it gets returned using * the different cleanup rules options. * * @method getContent * @param {Object} args Optional content object, this gets passed around through the whole get process. * @return {String} Cleaned content string, normally HTML contents. * @example * // Get the HTML contents of the currently active editor * console.debug(tinymce.activeEditor.getContent()); * * // Get the contents of the currently active editor as plain text * tinymce.activeEditor.getContent({ format: 'text' }); * * // Get content of a specific editor: * tinymce.get('content id').getContent() */ public getContent(args: { format: 'tree' } & Partial): AstNode; public getContent(args?: Partial): string; public getContent(args?: Partial): EditorContent.Content { return EditorContent.getContent(this, args); } /** * Inserts content at caret position. * * @method insertContent * @param {String} content Content to insert. * @param {Object} args Optional args to pass to insert call. */ public insertContent(content: string, args?: any): void { if (args) { content = extend({ content }, args); } this.execCommand('mceInsertContent', false, content); } /** * Resets the editors content, undo/redo history and dirty state. If `initialContent` isn't specified, then * the editor is reset back to the initial start content. * * @method resetContent * @param {String} initialContent An optional string to use as the initial content of the editor. */ public resetContent(initialContent?: string): void { // Set the editor content if (initialContent === undefined) { // editor.startContent is generated by using the `raw` format, so we should set it the same way EditorContent.setContent(this, this.startContent, { format: 'raw' }); } else { EditorContent.setContent(this, initialContent); } // Update the editor/undo manager state this.undoManager.reset(); this.setDirty(false); // Fire a node change event this.nodeChanged(); } /** * Returns true/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents. * * The dirty state is automatically set to `true` when the user modifies editor content after initialization or the * last `editor.save()` call. This includes changes made using undo or redo. * * @method isDirty * @return {Boolean} True/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents. * @example * if (tinymce.activeEditor.isDirty()) { * alert("You must save your contents."); * } */ public isDirty(): boolean { return !this.isNotDirty; } /** * Explicitly sets the dirty state. This will fire the dirty event if the editor dirty state is changed from false to true * by invoking this method. * * @method setDirty * @param {Boolean} state True/false if the editor is considered dirty. * @example * const ajaxSave = () => { * const editor = tinymce.get('elm1'); * * // Save contents using some XHR call * alert(editor.getContent()); * * editor.setDirty(false); // Force not dirty state * } */ public setDirty(state: boolean): void { const oldState = !this.isNotDirty; this.isNotDirty = !state; if (state && state !== oldState) { this.dispatch('dirty'); } } /** * Returns the container element of the editor. The container element includes * all the elements added to the page for the editor. Such as UI, iframe, etc. * * @method getContainer * @return {Element} HTML DOM element for the editor container. */ public getContainer(): HTMLElement { const self = this; if (!self.container) { self.container = self.editorContainer || DOM.get(self.id + '_parent') as HTMLElement; } return self.container; } /** * Returns the content area container element of the editor. This element * holds the iframe or the editable element. * * @method getContentAreaContainer * @return {Element} HTML DOM element for the editor area container. */ public getContentAreaContainer(): HTMLElement { return this.contentAreaContainer; } /** * Returns the target element/textarea that got replaced with a TinyMCE editor instance. * * @method getElement * @return {Element} HTML DOM element for the replaced element. */ public getElement(): HTMLElement { if (!this.targetElm) { this.targetElm = DOM.get(this.id) as HTMLElement; } return this.targetElm; } /** * Returns the iframes window object. * * @method getWin * @return {Window} Iframe DOM window object. */ public getWin(): Window { const self = this; if (!self.contentWindow) { const elm = self.iframeElement; if (elm) { self.contentWindow = elm.contentWindow as Window; } } return self.contentWindow; } /** * Returns the iframes document object. * * @method getDoc * @return {Document} Iframe DOM document object. */ public getDoc(): Document { const self = this; if (!self.contentDocument) { const win = self.getWin(); if (win) { self.contentDocument = win.document; } } return self.contentDocument; } /** * Returns the root element of the editable area. * For a non-inline iframe-based editor, returns the iframe's body element. * * @method getBody * @return {Element} The root element of the editable area. */ public getBody(): HTMLElement { const doc = this.getDoc(); return this.bodyElement ?? doc?.body ?? null; } /** * URL converter function this gets executed each time a user adds an img, a or * any other element that has a URL in it. This will be called both by the DOM and HTML * manipulation functions. * * @method convertURL * @param {String} url URL to convert. * @param {String} name Attribute name src, href etc. * @param {String/HTMLElement} elm Tag name or HTML DOM element depending on HTML or DOM insert. * @return {String} Converted URL string. */ public convertURL(url: string, name: string, elm?: string | Element): string { const self = this, getOption = self.options.get; // Use callback instead const urlConverterCallback = Options.getUrlConverterCallback(self); if (Type.isFunction(urlConverterCallback)) { return urlConverterCallback.call(self, url, elm, true, name); } // Don't convert link href since that's the CSS files that gets loaded into the editor also skip local file URLs if ( !getOption('convert_urls') || elm === 'link' || (Type.isObject(elm) && (elm as HTMLElement).nodeName === 'LINK') || url.indexOf('file:') === 0 || url.length === 0 ) { return url; } // Convert to relative if (getOption('relative_urls')) { return self.documentBaseURI.toRelative(url); } // Convert to absolute url = self.documentBaseURI.toAbsolute(url, getOption('remove_script_host')); return url; } /** * Adds visual aids for tables, anchors, etc so they can be more easily edited inside the editor. * * @method addVisual * @param {Element} elm Optional root element to loop though to find tables, etc that needs the visual aid. */ public addVisual(elm?: HTMLElement): void { VisualAids.addVisual(this, elm); } /** * Removes the editor from the dom and tinymce collection. * * @method remove */ public remove(): void { EditorRemove.remove(this); } /** * Destroys the editor instance by removing all events, element references or other resources * that could leak memory. This method will be called automatically when the page is unloaded * but you can also call it directly if you know what you are doing. * * @method destroy * @param {Boolean} automatic Optional state if the destroy is an automatic destroy or user called one. */ public destroy(automatic?: boolean): void { EditorRemove.destroy(this, automatic); } /** * Uploads all data uri/blob uri images in the editor contents to server. * * @method uploadImages * @return {Promise} Promise instance with images and status for each image. */ public uploadImages(): Promise { return this.editorUpload.uploadImages(); } // Internal functions public _scanForImages(): Promise { return this.editorUpload.scanForImages(); } } export default Editor;