UNPKG

roosterjs

Version:

A simple facade for all roosterjs code

1,052 lines (1,033 loc) 119 kB
var roosterjsAdapter; /******/ (() => { // webpackBootstrap /******/ "use strict"; /******/ var __webpack_modules__ = ({ /***/ "./packages/roosterjs-editor-adapter/lib/corePlugins/BridgePlugin.ts": /*!***************************************************************************!*\ !*** ./packages/roosterjs-editor-adapter/lib/corePlugins/BridgePlugin.ts ***! \***************************************************************************/ /***/ ((__unused_webpack_module, exports, __webpack_require__) => { Object.defineProperty(exports, "__esModule", ({ value: true })); exports.createSizeTransformer = exports.BridgePlugin = void 0; var tslib_1 = __webpack_require__(/*! tslib */ "./node_modules/tslib/tslib.es6.mjs"); var roosterjs_content_model_dom_1 = __webpack_require__(/*! roosterjs-content-model-dom */ "roosterjs-content-model-dom"); var DarkColorHandlerImpl_1 = __webpack_require__(/*! ../editor/DarkColorHandlerImpl */ "./packages/roosterjs-editor-adapter/lib/editor/DarkColorHandlerImpl.ts"); var EditPlugin_1 = __webpack_require__(/*! ./EditPlugin */ "./packages/roosterjs-editor-adapter/lib/corePlugins/EditPlugin.ts"); var IgnoredPluginNames_1 = __webpack_require__(/*! ../editor/IgnoredPluginNames */ "./packages/roosterjs-editor-adapter/lib/editor/IgnoredPluginNames.ts"); var eventConverter_1 = __webpack_require__(/*! ../editor/utils/eventConverter */ "./packages/roosterjs-editor-adapter/lib/editor/utils/eventConverter.ts"); var ExclusivelyHandleEventPluginKey = '__ExclusivelyHandleEventPlugin'; var OldEventKey = '__OldEventFromNewEvent'; /** * @internal * Act as a bridge between Standalone editor and Content Model editor, translate Standalone editor event type to legacy event type */ var BridgePlugin = /** @class */ (function () { function BridgePlugin(onInitialize, legacyPlugins, experimentalFeatures) { if (legacyPlugins === void 0) { legacyPlugins = []; } if (experimentalFeatures === void 0) { experimentalFeatures = []; } this.onInitialize = onInitialize; this.experimentalFeatures = experimentalFeatures; var editPlugin = (0, EditPlugin_1.createEditPlugin)(); this.legacyPlugins = (0, tslib_1.__spreadArray)([ editPlugin ], (0, tslib_1.__read)(legacyPlugins.filter(function (x) { return !!x && IgnoredPluginNames_1.IgnoredPluginNames.indexOf(x.getName()) < 0; })), false); this.edit = editPlugin.getState(); this.contextMenuProviders = this.legacyPlugins.filter(isContextMenuProvider); this.checkExclusivelyHandling = this.legacyPlugins.some(function (plugin) { return plugin.willHandleEventExclusively; }); } /** * Get a friendly name of this plugin */ BridgePlugin.prototype.getName = function () { return 'Bridge'; }; /** * Initialize this plugin. This should only be called from Editor * @param editor Editor instance */ BridgePlugin.prototype.initialize = function (editor) { var outerEditor = this.onInitialize(this.createEditorCore(editor)); this.legacyPlugins.forEach(function (plugin) { plugin.initialize(outerEditor); if (isMixedPlugin(plugin)) { plugin.initializeV9(editor); } }); }; /** * Dispose this plugin */ BridgePlugin.prototype.dispose = function () { for (var i = this.legacyPlugins.length - 1; i >= 0; i--) { var plugin = this.legacyPlugins[i]; plugin.dispose(); } }; BridgePlugin.prototype.willHandleEventExclusively = function (event) { return this.checkExclusivelyHandling && !!this.cacheGetExclusivelyHandlePlugin(event); }; BridgePlugin.prototype.onPluginEvent = function (event) { var _this = this; var oldEvent = this.cacheGetOldEvent(event); var exclusivelyHandleEventPlugin = this.cacheGetExclusivelyHandlePlugin(event); if (exclusivelyHandleEventPlugin) { this.handleEvent(exclusivelyHandleEventPlugin, oldEvent, event); } else { this.legacyPlugins.forEach(function (plugin) { return _this.handleEvent(plugin, oldEvent, event); }); } if (oldEvent) { Object.assign(event, (0, eventConverter_1.oldEventToNewEvent)(oldEvent, event)); } }; /** * A callback to return context menu items * @param target Target node that triggered a ContextMenu event * @returns An array of context menu items, or null means no items needed */ BridgePlugin.prototype.getContextMenuItems = function (target) { var allItems = []; this.contextMenuProviders.forEach(function (provider) { var _a; var items = (_a = provider.getContextMenuItems(target)) !== null && _a !== void 0 ? _a : []; if ((items === null || items === void 0 ? void 0 : items.length) > 0) { if (allItems.length > 0) { allItems.push(null); } allItems.push.apply(allItems, (0, tslib_1.__spreadArray)([], (0, tslib_1.__read)(items), false)); } }); return allItems; }; BridgePlugin.prototype.cacheGetExclusivelyHandlePlugin = function (event) { var _this = this; return (0, roosterjs_content_model_dom_1.cacheGetEventData)(event, ExclusivelyHandleEventPluginKey, function (event) { var _a, _b; var oldEvent = _this.cacheGetOldEvent(event); for (var i = 0; i < _this.legacyPlugins.length; i++) { var plugin = _this.legacyPlugins[i]; if (oldEvent && ((_a = plugin.willHandleEventExclusively) === null || _a === void 0 ? void 0 : _a.call(plugin, oldEvent))) { return plugin; } if (isMixedPlugin(plugin) && ((_b = plugin.willHandleEventExclusivelyV9) === null || _b === void 0 ? void 0 : _b.call(plugin, event))) { return plugin; } } return null; }); }; BridgePlugin.prototype.cacheGetOldEvent = function (event) { return (0, roosterjs_content_model_dom_1.cacheGetEventData)(event, OldEventKey, eventConverter_1.newEventToOldEvent); }; BridgePlugin.prototype.createEditorCore = function (editor) { var _a; return { customData: {}, experimentalFeatures: (_a = this.experimentalFeatures) !== null && _a !== void 0 ? _a : [], sizeTransformer: createSizeTransformer(editor), darkColorHandler: (0, DarkColorHandlerImpl_1.createDarkColorHandler)(editor.getColorManager()), edit: this.edit, contextMenuProviders: this.contextMenuProviders, }; }; BridgePlugin.prototype.handleEvent = function (plugin, oldEvent, newEvent) { var _a; if (oldEvent && plugin.onPluginEvent) { plugin.onPluginEvent(oldEvent); } if (isMixedPlugin(plugin)) { (_a = plugin.onPluginEventV9) === null || _a === void 0 ? void 0 : _a.call(plugin, newEvent); } }; return BridgePlugin; }()); exports.BridgePlugin = BridgePlugin; /** * @internal Export for test only. This function is only used for compatibility from older build */ function createSizeTransformer(editor) { return function (size) { return size / editor.getDOMHelper().calculateZoomScale(); }; } exports.createSizeTransformer = createSizeTransformer; function isContextMenuProvider(source) { var _a; return !!((_a = source) === null || _a === void 0 ? void 0 : _a.getContextMenuItems); } function isMixedPlugin(plugin) { return !!plugin.initializeV9; } /***/ }), /***/ "./packages/roosterjs-editor-adapter/lib/corePlugins/EditPlugin.ts": /*!*************************************************************************!*\ !*** ./packages/roosterjs-editor-adapter/lib/corePlugins/EditPlugin.ts ***! \*************************************************************************/ /***/ ((__unused_webpack_module, exports, __webpack_require__) => { Object.defineProperty(exports, "__esModule", ({ value: true })); exports.createEditPlugin = void 0; var roosterjs_editor_dom_1 = __webpack_require__(/*! roosterjs-editor-dom */ "roosterjs-editor-dom"); /** * Edit Component helps handle Content edit features */ var EditPlugin = /** @class */ (function () { /** * Construct a new instance of EditPlugin * @param options The editor options */ function EditPlugin() { this.editor = null; this.state = { features: {}, }; } /** * Get a friendly name of this plugin */ EditPlugin.prototype.getName = function () { return 'Edit'; }; /** * Initialize this plugin. This should only be called from Editor * @param editor Editor instance */ EditPlugin.prototype.initialize = function (editor) { this.editor = editor; }; /** * Dispose this plugin */ EditPlugin.prototype.dispose = function () { this.editor = null; }; /** * Get plugin state object */ EditPlugin.prototype.getState = function () { return this.state; }; /** * Handle events triggered from editor * @param event PluginEvent object */ EditPlugin.prototype.onPluginEvent = function (event) { var _a; var hasFunctionKey = false; var features = null; var ctrlOrMeta = false; var isKeyDownEvent = event.eventType == 0 /* KeyDown */; if (isKeyDownEvent) { var rawEvent = event.rawEvent; var range = (_a = this.editor) === null || _a === void 0 ? void 0 : _a.getSelectionRange(); ctrlOrMeta = (0, roosterjs_editor_dom_1.isCtrlOrMetaPressed)(rawEvent); hasFunctionKey = ctrlOrMeta || rawEvent.altKey; features = this.state.features[rawEvent.which] || (range && !range.collapsed && this.state.features[258 /* RANGE */]); } else if (event.eventType == 7 /* ContentChanged */) { features = this.state.features[257 /* CONTENTCHANGED */]; } for (var i = 0; features && i < (features === null || features === void 0 ? void 0 : features.length); i++) { var feature = features[i]; if ((feature.allowFunctionKeys || !hasFunctionKey) && this.editor && feature.shouldHandleEvent(event, this.editor, ctrlOrMeta)) { feature.handleEvent(event, this.editor); if (isKeyDownEvent) { event.handledByEditFeature = true; } break; } } }; return EditPlugin; }()); /** * @internal * Create a new instance of EditPlugin. */ function createEditPlugin() { return new EditPlugin(); } exports.createEditPlugin = createEditPlugin; /***/ }), /***/ "./packages/roosterjs-editor-adapter/lib/editor/DarkColorHandlerImpl.ts": /*!******************************************************************************!*\ !*** ./packages/roosterjs-editor-adapter/lib/editor/DarkColorHandlerImpl.ts ***! \******************************************************************************/ /***/ ((__unused_webpack_module, exports, __webpack_require__) => { Object.defineProperty(exports, "__esModule", ({ value: true })); exports.createDarkColorHandler = void 0; var roosterjs_content_model_dom_1 = __webpack_require__(/*! roosterjs-content-model-dom */ "roosterjs-content-model-dom"); var VARIABLE_REGEX = /^\s*var\(\s*(\-\-[a-zA-Z0-9\-_]+)\s*(?:,\s*(.*))?\)\s*$/; var VARIABLE_PREFIX = 'var('; var COLOR_VAR_PREFIX = 'darkColor'; var DarkColorHandlerImpl = /** @class */ (function () { function DarkColorHandlerImpl(innerHandler) { this.innerHandler = innerHandler; } /** * Get a copy of known colors * @returns */ DarkColorHandlerImpl.prototype.getKnownColorsCopy = function () { return Object.values(this.innerHandler.knownColors); }; /** * Given a light mode color value and an optional dark mode color value, register this color * so that editor can handle it, then return the CSS color value for current color mode. * @param lightModeColor Light mode color value * @param isDarkMode Whether current color mode is dark mode * @param darkModeColor Optional dark mode color value. If not passed, we will calculate one. */ DarkColorHandlerImpl.prototype.registerColor = function (lightModeColor, isDarkMode, darkModeColor) { var parsedColor = this.parseColorValue(lightModeColor); var colorKey; if (parsedColor) { lightModeColor = parsedColor.lightModeColor; darkModeColor = parsedColor.darkModeColor || darkModeColor; colorKey = parsedColor.key; } if (isDarkMode && lightModeColor) { colorKey = colorKey || "--" + COLOR_VAR_PREFIX + "_" + lightModeColor.replace(/[^\d\w]/g, '_'); this.innerHandler.updateKnownColor(isDarkMode, colorKey, { lightModeColor: lightModeColor, darkModeColor: darkModeColor || this.innerHandler.getDarkColor(lightModeColor), }); return "var(" + colorKey + ", " + lightModeColor + ")"; } else { return lightModeColor; } }; /** * Reset known color record, clean up registered color variables. */ DarkColorHandlerImpl.prototype.reset = function () { this.innerHandler.reset(); }; /** * Parse an existing color value, if it is in variable-based color format, extract color key, * light color and query related dark color if any * @param color The color string to parse * @param isInDarkMode Whether current content is in dark mode. When set to true, if the color value is not in dark var format, * we will treat is as a dark mode color and try to find a matched dark mode color. */ DarkColorHandlerImpl.prototype.parseColorValue = function (color, isInDarkMode) { var _a; var key; var lightModeColor = ''; var darkModeColor; if (color) { var match = color.startsWith(VARIABLE_PREFIX) ? VARIABLE_REGEX.exec(color) : null; if (match) { if (match[2]) { key = match[1]; lightModeColor = match[2]; darkModeColor = (_a = this.innerHandler.knownColors[key]) === null || _a === void 0 ? void 0 : _a.darkModeColor; } else { lightModeColor = ''; } } else if (isInDarkMode) { // If editor is in dark mode but the color is not in dark color format, it is possible the color was inserted from external code // without any light color info. So we first try to see if there is a known dark color can match this color, and use its related // light color as light mode color. Otherwise we need to drop this color to avoid show "white on white" content. lightModeColor = this.findLightColorFromDarkColor(color) || ''; if (lightModeColor) { darkModeColor = color; } } else { lightModeColor = color; } } return { key: key, lightModeColor: lightModeColor, darkModeColor: darkModeColor }; }; /** * Find related light mode color from dark mode color. * @param darkColor The existing dark color */ DarkColorHandlerImpl.prototype.findLightColorFromDarkColor = function (darkColor) { var rgbSearch = (0, roosterjs_content_model_dom_1.parseColor)(darkColor); var knownColors = this.innerHandler.knownColors; if (rgbSearch) { var key = (0, roosterjs_content_model_dom_1.getObjectKeys)(knownColors).find(function (key) { var rgbCurrent = (0, roosterjs_content_model_dom_1.parseColor)(knownColors[key].darkModeColor); return (rgbCurrent && rgbCurrent[0] == rgbSearch[0] && rgbCurrent[1] == rgbSearch[1] && rgbCurrent[2] == rgbSearch[2]); }); if (key) { return knownColors[key].lightModeColor; } } return null; }; /** * Transform element color, from dark to light or from light to dark * @param element The element to transform color * @param fromDarkMode Whether this is transforming color from dark mode * @param toDarkMode Whether this is transforming color to dark mode */ DarkColorHandlerImpl.prototype.transformElementColor = function (element, fromDarkMode, toDarkMode) { var textColor = (0, roosterjs_content_model_dom_1.getColor)(element, false /*isBackground*/, !toDarkMode, this.innerHandler); var backColor = (0, roosterjs_content_model_dom_1.getColor)(element, true /*isBackground*/, !toDarkMode, this.innerHandler); (0, roosterjs_content_model_dom_1.setColor)(element, textColor, false /*isBackground*/, toDarkMode, this.innerHandler); (0, roosterjs_content_model_dom_1.setColor)(element, backColor, true /*isBackground*/, toDarkMode, this.innerHandler); }; return DarkColorHandlerImpl; }()); /** * @internal */ function createDarkColorHandler(innerHandler) { return new DarkColorHandlerImpl(innerHandler); } exports.createDarkColorHandler = createDarkColorHandler; /***/ }), /***/ "./packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts": /*!***********************************************************************!*\ !*** ./packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts ***! \***********************************************************************/ /***/ ((__unused_webpack_module, exports, __webpack_require__) => { var _a; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.EditorAdapter = void 0; var tslib_1 = __webpack_require__(/*! tslib */ "./node_modules/tslib/tslib.es6.mjs"); var BridgePlugin_1 = __webpack_require__(/*! ../corePlugins/BridgePlugin */ "./packages/roosterjs-editor-adapter/lib/corePlugins/BridgePlugin.ts"); var buildRangeEx_1 = __webpack_require__(/*! ./utils/buildRangeEx */ "./packages/roosterjs-editor-adapter/lib/editor/utils/buildRangeEx.ts"); var insertNode_1 = __webpack_require__(/*! ./utils/insertNode */ "./packages/roosterjs-editor-adapter/lib/editor/utils/insertNode.ts"); var roosterjs_content_model_dom_1 = __webpack_require__(/*! roosterjs-content-model-dom */ "roosterjs-content-model-dom"); var eventConverter_1 = __webpack_require__(/*! ./utils/eventConverter */ "./packages/roosterjs-editor-adapter/lib/editor/utils/eventConverter.ts"); var roosterjs_content_model_core_1 = __webpack_require__(/*! roosterjs-content-model-core */ "roosterjs-content-model-dom"); var selectionConverter_1 = __webpack_require__(/*! ./utils/selectionConverter */ "./packages/roosterjs-editor-adapter/lib/editor/utils/selectionConverter.ts"); var roosterjs_editor_dom_1 = __webpack_require__(/*! roosterjs-editor-dom */ "roosterjs-editor-dom"); var GetContentModeMap = (_a = {}, _a[0 /* CleanHTML */] = 'HTML', _a[3 /* PlainText */] = 'PlainText', _a[4 /* PlainTextFast */] = 'PlainTextFast', _a[1 /* RawHTMLOnly */] = 'HTML', _a[2 /* RawHTMLWithSelection */] = 'HTML', _a); /** * Editor for Content Model. */ var EditorAdapter = /** @class */ (function (_super) { (0, tslib_1.__extends)(EditorAdapter, _super); /** * Creates an instance of Editor * @param contentDiv The DIV HTML element which will be the container element of editor * @param options An optional options object to customize the editor */ function EditorAdapter(contentDiv, options) { if (options === void 0) { options = {}; } var _a, _b; var _this = this; var bridgePlugin = new BridgePlugin_1.BridgePlugin(function (core) { _this.contentModelEditorCore = core; return _this; }, options.legacyPlugins, options.experimentalFeatures); var plugins = (0, tslib_1.__spreadArray)([bridgePlugin], (0, tslib_1.__read)(((_a = options.plugins) !== null && _a !== void 0 ? _a : [])), false); var initContent = (_b = options.initialContent) !== null && _b !== void 0 ? _b : contentDiv.innerHTML; var initialModel = initContent && !options.initialModel ? (0, roosterjs_content_model_core_1.createModelFromHtml)(initContent, options.defaultDomToModelOptions, options.trustedHTMLHandler, options.defaultSegmentFormat) : options.initialModel; var standaloneEditorOptions = (0, tslib_1.__assign)((0, tslib_1.__assign)({}, options), { plugins: plugins, initialModel: initialModel }); _this = _super.call(this, contentDiv, standaloneEditorOptions) || this; return _this; } /** * Dispose this editor, dispose all plugins and custom data */ EditorAdapter.prototype.dispose = function () { _super.prototype.dispose.call(this); var core = this.contentModelEditorCore; if (core) { (0, roosterjs_content_model_dom_1.getObjectKeys)(core.customData).forEach(function (key) { var data = core.customData[key]; if (data && data.disposer) { data.disposer(data.value); } delete core.customData[key]; }); this.contentModelEditorCore = undefined; } }; /** * Get whether this editor is disposed * @returns True if editor is disposed, otherwise false */ EditorAdapter.prototype.isDisposed = function () { return _super.prototype.isDisposed.call(this) || !this.contentModelEditorCore; }; /** * Insert node into editor * @param node The node to insert * @param option Insert options. Default value is: * position: ContentPosition.SelectionStart * updateCursor: true * replaceSelection: true * insertOnNewLine: false * @returns true if node is inserted. Otherwise false */ EditorAdapter.prototype.insertNode = function (node, option) { var _a; if (node) { option = option || { position: 3 /* SelectionStart */, insertOnNewLine: false, updateCursor: true, replaceSelection: true, insertToRegionRoot: false, }; var physicalRoot = this.getCore().physicalRoot; if (option.updateCursor) { this.focus(); } if (option.position == 4 /* Outside */) { (_a = physicalRoot.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(node, physicalRoot.nextSibling); } else { if (this.isDarkMode()) { (0, roosterjs_content_model_dom_1.transformColor)(node, true /*includeSelf*/, 'lightToDark', this.getColorManager()); } var selection = (0, insertNode_1.insertNode)(physicalRoot, this.getDOMSelection(), node, option); if (selection && option.updateCursor) { this.setDOMSelection(selection); } } return true; } else { return false; } }; /** * Delete a node from editor content * @param node The node to delete * @returns true if node is deleted. Otherwise false */ EditorAdapter.prototype.deleteNode = function (node) { // Only remove the node when it falls within editor if (node && this.contains(node) && node.parentNode) { node.parentNode.removeChild(node); return true; } return false; }; /** * Replace a node in editor content with another node * @param existingNode The existing node to be replaced * @param toNode node to replace to * @param transformColorForDarkMode (optional) Whether to transform new node to dark mode. Default is false * @returns true if node is replaced. Otherwise false */ EditorAdapter.prototype.replaceNode = function (existingNode, toNode, transformColorForDarkMode) { var _a; var core = this.getCore(); // Only replace the node when it falls within editor if (this.contains(existingNode) && toNode) { if (core.lifecycle.isDarkMode && transformColorForDarkMode) { this.transformToDarkColor(toNode, 0 /* LightToDark */); } (_a = existingNode.parentNode) === null || _a === void 0 ? void 0 : _a.replaceChild(toNode, existingNode); return true; } return false; }; /** * Get BlockElement at given node * @param node The node to create InlineElement * @returns The BlockElement result */ EditorAdapter.prototype.getBlockElementAtNode = function (node) { return (0, roosterjs_editor_dom_1.getBlockElementAtNode)(this.getCore().logicalRoot, node); }; EditorAdapter.prototype.contains = function (arg) { if (!arg) { return false; } return (0, roosterjs_editor_dom_1.contains)(this.getCore().logicalRoot, arg); }; EditorAdapter.prototype.queryElements = function (selector, scopeOrCallback, callback) { if (scopeOrCallback === void 0) { scopeOrCallback = 0 /* Body */; } var core = this.getCore(); var result = []; var scope = scopeOrCallback instanceof Function ? 0 /* Body */ : scopeOrCallback; callback = scopeOrCallback instanceof Function ? scopeOrCallback : callback; var selectionEx = scope == 0 /* Body */ ? null : this.getSelectionRangeEx(); if (selectionEx) { selectionEx.ranges.forEach(function (range) { result.push.apply(result, (0, tslib_1.__spreadArray)([], (0, tslib_1.__read)((0, roosterjs_editor_dom_1.queryElements)(core.logicalRoot, selector, callback, scope, range)), false)); }); } else { return (0, roosterjs_editor_dom_1.queryElements)(core.logicalRoot, selector, callback, scope, undefined /* range */); } return result; }; /** * Collapse nodes within the given start and end nodes to their common ancestor node, * split parent nodes if necessary * @param start The start node * @param end The end node * @param canSplitParent True to allow split parent node there are nodes before start or after end under the same parent * and the returned nodes will be all nodes from start through end after splitting * False to disallow split parent * @returns When canSplitParent is true, returns all node from start through end after splitting, * otherwise just return start and end */ EditorAdapter.prototype.collapseNodes = function (start, end, canSplitParent) { return (0, roosterjs_editor_dom_1.collapseNodes)(this.getCore().physicalRoot, start, end, canSplitParent); }; //#endregion //#region Content API /** * Check whether the editor contains any visible content * @param trim Whether trim the content string before check. Default is false * @returns True if there's no visible content, otherwise false */ EditorAdapter.prototype.isEmpty = function (trim) { return (0, roosterjs_editor_dom_1.isNodeEmpty)(this.getCore().physicalRoot, trim); }; /** * Get current editor content as HTML string * @param mode specify what kind of HTML content to retrieve * @returns HTML string representing current editor content */ EditorAdapter.prototype.getContent = function (mode) { var _a; if (mode === void 0) { mode = 0 /* CleanHTML */; } var exportMode = (_a = GetContentModeMap[mode]) !== null && _a !== void 0 ? _a : 'HTML'; switch (exportMode) { case 'HTML': return (0, roosterjs_content_model_core_1.exportContent)(this, 'HTML', this.getCore().environment.modelToDomSettings.customized); case 'PlainText': return (0, roosterjs_content_model_core_1.exportContent)(this, 'PlainText'); case 'PlainTextFast': return (0, roosterjs_content_model_core_1.exportContent)(this, 'PlainTextFast'); } }; /** * Set HTML content to this editor. All existing content will be replaced. A ContentChanged event will be triggered * @param content HTML content to set in * @param triggerContentChangedEvent True to trigger a ContentChanged event. Default value is true */ EditorAdapter.prototype.setContent = function (content, triggerContentChangedEvent) { if (triggerContentChangedEvent === void 0) { triggerContentChangedEvent = true; } var core = this.getCore(); var physicalRoot = core.physicalRoot, api = core.api, trustedHTMLHandler = core.trustedHTMLHandler, lifecycle = core.lifecycle, darkColorHandler = core.darkColorHandler; api.triggerEvent(core, { eventType: 'beforeSetContent', newContent: content, }, true /*broadcast*/); var newModel = (0, roosterjs_content_model_core_1.createModelFromHtml)(content, core.environment.domToModelSettings.customized, trustedHTMLHandler, core.format.defaultFormat); api.setContentModel(core, newModel); if (triggerContentChangedEvent) { api.triggerEvent(core, { eventType: 'contentChanged', source: "SetContent" /* SetContent */, }, false /*broadcast*/); } else if (lifecycle.isDarkMode) { (0, roosterjs_content_model_dom_1.transformColor)(physicalRoot, false /*includeSelf*/, 'lightToDark', darkColorHandler); } }; /** * Insert HTML content into editor * @param HTML content to insert * @param option Insert options. Default value is: * position: ContentPosition.SelectionStart * updateCursor: true * replaceSelection: true * insertOnNewLine: false */ EditorAdapter.prototype.insertContent = function (content, option) { var _a; if (content) { var doc = this.getDocument(); var body = (_a = this.getCore().domCreator.htmlToDOM(content)) === null || _a === void 0 ? void 0 : _a.body; var allNodes = (body === null || body === void 0 ? void 0 : body.childNodes) ? (0, roosterjs_editor_dom_1.toArray)(body.childNodes) : []; // If it is to insert on new line, and there are more than one node in the collection, wrap all nodes with // a parent DIV before calling insertNode on each top level sub node. Otherwise, every sub node may get wrapped // separately to show up on its own line if (option && option.insertOnNewLine && allNodes.length > 1) { allNodes = [(0, roosterjs_editor_dom_1.wrap)(allNodes)]; } var fragment_1 = doc.createDocumentFragment(); allNodes.forEach(function (node) { return fragment_1.appendChild(node); }); this.insertNode(fragment_1, option); } }; /** * Delete selected content */ EditorAdapter.prototype.deleteSelectedContent = function () { var range = this.getSelectionRange(); if (range && !range.collapsed) { return (0, roosterjs_editor_dom_1.deleteSelectedContent)(this.getCore().physicalRoot, range); } return null; }; /** * Paste into editor using a clipboardData object * @param clipboardData Clipboard data retrieved from clipboard * @param pasteAsText Force pasting as plain text. Default value is false * @param applyCurrentStyle True if apply format of current selection to the pasted content, * false to keep original format. Default value is false. When pasteAsText is true, this parameter is ignored * @param pasteAsImage: When set to true, if the clipboardData contains a imageDataUri will paste the image to the editor */ EditorAdapter.prototype.paste = function (clipboardData, pasteAsText, applyCurrentFormat, pasteAsImage) { if (pasteAsText === void 0) { pasteAsText = false; } if (applyCurrentFormat === void 0) { applyCurrentFormat = false; } if (pasteAsImage === void 0) { pasteAsImage = false; } (0, roosterjs_content_model_core_1.paste)(this, clipboardData, pasteAsText ? 'asPlainText' : applyCurrentFormat ? 'mergeFormat' : pasteAsImage ? 'asImage' : 'normal'); }; //#endregion //#region Focus and Selection /** * Get current selection range from Editor. * It does a live pull on the selection, if nothing retrieved, return whatever we have in cache. * @param tryGetFromCache Set to true to retrieve the selection range from cache if editor doesn't own the focus now. * Default value is true * @returns current selection range, or null if editor never got focus before */ EditorAdapter.prototype.getSelectionRange = function (tryGetFromCache) { if (tryGetFromCache === void 0) { tryGetFromCache = true; } var selection = this.getDOMSelection(); return (selection === null || selection === void 0 ? void 0 : selection.type) == 'range' ? selection.range : null; }; /** * Get current selection range from Editor. * It does a live pull on the selection, if nothing retrieved, return whatever we have in cache. * @param tryGetFromCache Set to true to retrieve the selection range from cache if editor doesn't own the focus now. * Default value is true * @returns current selection range, or null if editor never got focus before */ EditorAdapter.prototype.getSelectionRangeEx = function () { var selection = this.getDOMSelection(); return (0, selectionConverter_1.convertDomSelectionToRangeEx)(selection); }; /** * Get current selection in a serializable format * It does a live pull on the selection, if nothing retrieved, return whatever we have in cache. * @returns current selection path, or null if editor never got focus before */ EditorAdapter.prototype.getSelectionPath = function () { var range = this.getSelectionRange(); return range && (0, roosterjs_editor_dom_1.getSelectionPath)(this.getCore().physicalRoot, range); }; EditorAdapter.prototype.select = function (arg1, arg2, arg3, arg4) { var core = this.getCore(); var rangeEx = (0, buildRangeEx_1.buildRangeEx)(core.physicalRoot, arg1, arg2, arg3, arg4); var selection = (0, selectionConverter_1.convertRangeExToDomSelection)(rangeEx); this.setDOMSelection(selection); return true; }; /** * Get current focused position. Return null if editor doesn't have focus at this time. */ EditorAdapter.prototype.getFocusedPosition = function () { var _a; var sel = (_a = this.getDocument().defaultView) === null || _a === void 0 ? void 0 : _a.getSelection(); if ((sel === null || sel === void 0 ? void 0 : sel.focusNode) && this.contains(sel.focusNode)) { return new roosterjs_editor_dom_1.Position(sel.focusNode, sel.focusOffset); } var range = this.getSelectionRange(); if (range) { return roosterjs_editor_dom_1.Position.getStart(range); } return null; }; /** * Get an HTML element from current cursor position. * When expectedTags is not specified, return value is the current node (if it is HTML element) * or its parent node (if current node is a Text node). * When expectedTags is specified, return value is the first ancestor of current node which has * one of the expected tags. * If no element found within editor by the given tag, return null. * @param selector Optional, an HTML selector to find HTML element with. * @param startFrom Start search from this node. If not specified, start from current focused position * @param event Optional, if specified, editor will try to get cached result from the event object first. * If it is not cached before, query from DOM and cache the result into the event object */ EditorAdapter.prototype.getElementAtCursor = function (selector, startFrom, event) { var _this = this; var _a; event = startFrom ? undefined : event; // Only use cache when startFrom is not specified, for different start position can have different result return ((_a = (0, roosterjs_editor_dom_1.cacheGetEventData)(event !== null && event !== void 0 ? event : null, 'GET_ELEMENT_AT_CURSOR_' + selector, function () { if (!startFrom) { var position = _this.getFocusedPosition(); startFrom = position === null || position === void 0 ? void 0 : position.node; } return (startFrom && _this.getDOMHelper().findClosestElementAncestor(startFrom, selector)); })) !== null && _a !== void 0 ? _a : null); }; /** * Check if this position is at beginning of the editor. * This will return true if all nodes between the beginning of target node and the position are empty. * @param position The position to check * @returns True if position is at beginning of the editor, otherwise false */ EditorAdapter.prototype.isPositionAtBeginning = function (position) { return (0, roosterjs_editor_dom_1.isPositionAtBeginningOf)(position, this.getCore().logicalRoot); }; /** * Get impacted regions from selection */ EditorAdapter.prototype.getSelectedRegions = function (type) { if (type === void 0) { type = 0 /* Table */; } var selection = this.getSelectionRangeEx(); var result = []; var logicalRoot = this.getCore().logicalRoot; selection.ranges.forEach(function (range) { result.push.apply(result, (0, tslib_1.__spreadArray)([], (0, tslib_1.__read)((range ? (0, roosterjs_editor_dom_1.getRegionsFromRange)(logicalRoot, range, type) : [])), false)); }); return result.filter(function (value, index, self) { return self.indexOf(value) === index; }); }; //#endregion //#region EVENT API EditorAdapter.prototype.addDomEventHandler = function (nameOrMap, handler) { var _a; var eventsMap = typeof nameOrMap == 'string' ? (_a = {}, _a[nameOrMap] = handler, _a) : nameOrMap; var eventsMapResult = {}; (0, roosterjs_content_model_dom_1.getObjectKeys)(eventsMap).forEach(function (key) { var handlerObj = eventsMap[key]; var result = { pluginEventType: null, beforeDispatch: null, }; if (typeof handlerObj === 'number') { result.pluginEventType = eventConverter_1.OldEventTypeToNewEventType[handlerObj]; } else if (typeof handlerObj === 'function') { result.beforeDispatch = handlerObj; } else if (typeof handlerObj === 'object') { var record = handlerObj; result = { beforeDispatch: record.beforeDispatch, pluginEventType: typeof record.pluginEventType == 'number' ? eventConverter_1.OldEventTypeToNewEventType[record.pluginEventType] : undefined, }; } eventsMapResult[key] = result; }); return this.attachDomEvent(eventsMapResult); }; /** * Trigger an event to be dispatched to all plugins * @param eventType Type of the event * @param data data of the event with given type, this is the rest part of PluginEvent with the given type * @param broadcast indicates if the event needs to be dispatched to all plugins * True means to all, false means to allow exclusive handling from one plugin unless no one wants that * @returns the event object which is really passed into plugins. Some plugin may modify the event object so * the result of this function provides a chance to read the modified result */ EditorAdapter.prototype.triggerPluginEvent = function (eventType, data, broadcast) { var _a; if (broadcast === void 0) { broadcast = false; } var oldEvent = (0, tslib_1.__assign)({ eventType: eventType }, data); var newEvent = (0, eventConverter_1.oldEventToNewEvent)(oldEvent); var core = this.getCore(); if (newEvent) { core.api.triggerEvent(core, newEvent, broadcast); return ((_a = (0, eventConverter_1.newEventToOldEvent)(newEvent, oldEvent)) !== null && _a !== void 0 ? _a : oldEvent); } else { return oldEvent; } }; /** * Trigger a ContentChangedEvent * @param source Source of this event, by default is 'SetContent' * @param data additional data for this event */ EditorAdapter.prototype.triggerContentChangedEvent = function (source, data) { if (source === void 0) { source = "SetContent" /* SetContent */; } this.triggerPluginEvent(7 /* ContentChanged */, { source: source, data: data, }); }; //#endregion //#region Undo API /** * Undo last edit operation */ EditorAdapter.prototype.undo = function () { (0, roosterjs_content_model_core_1.undo)(this); }; /** * Redo next edit operation */ EditorAdapter.prototype.redo = function () { (0, roosterjs_content_model_core_1.redo)(this); }; /** * Add undo snapshot, and execute a format callback function, then add another undo snapshot, then trigger * ContentChangedEvent with given change source. * If this function is called nested, undo snapshot will only be added in the outside one * @param callback The callback function to perform formatting, returns a data object which will be used as * the data field in ContentChangedEvent if changeSource is not null. * @param changeSource The change source to use when fire ContentChangedEvent. When the value is not null, * a ContentChangedEvent will be fired with change source equal to this value * @param canUndoByBackspace True if this action can be undone when user press Backspace key (aka Auto Complete). */ EditorAdapter.prototype.addUndoSnapshot = function (callback, changeSource, canUndoByBackspace, additionalData) { var _a, _b; var core = this.getCore(); var undoState = core.undo; var isNested = undoState.isNested; var data; if (!isNested) { undoState.isNested = true; // When there is getEntityState, it means this is triggered by an entity change. // So if HTML content is not changed (hasNewContent is false), no need to add another snapshot before change if (core.undo.snapshotsManager.hasNewContent || !(additionalData === null || additionalData === void 0 ? void 0 : additionalData.getEntityState) || !callback) { core.api.addUndoSnapshot(core, !!canUndoByBackspace, (_a = additionalData === null || additionalData === void 0 ? void 0 : additionalData.getEntityState) === null || _a === void 0 ? void 0 : _a.call(additionalData)); } } try { if (callback) { var selection = core.api.getDOMSelection(core); var range = (selection === null || selection === void 0 ? void 0 : selection.type) == 'range' ? selection.range : null; data = callback(range && roosterjs_editor_dom_1.Position.getStart(range).normalize(), range && roosterjs_editor_dom_1.Position.getEnd(range).normalize()); if (!isNested) { var entityStates = (_b = additionalData === null || additionalData === void 0 ? void 0 : additionalData.getEntityState) === null || _b === void 0 ? void 0 : _b.call(additionalData); core.api.addUndoSnapshot(core, false /*isAutoCompleteSnapshot*/, entityStates); } } } finally { if (!isNested) { undoState.isNested = false; } } if (callback && changeSource) { var event_1 = { eventType: 7 /* ContentChanged */, source: changeSource, data: data, additionalData: additionalData, }; this.triggerPluginEvent(7 /* ContentChanged */, event_1, true /*broadcast*/); } if (canUndoByBackspace) { var selection = core.api.getDOMSelection(core); if ((selection === null || selection === void 0 ? void 0 : selection.type) == 'range') { core.undo.snapshotsManager.hasNewContent = false; core.undo.autoCompleteInsertPoint = { node: selection.range.startContainer, offset: selection.range.startOffset, }; } } }; /** * Whether there is an available undo/redo snapshot */ EditorAdapter.prototype.getUndoState = function () { var snapshotsManager = this.getCore().undo.snapshotsManager; return { canUndo: snapshotsManager.hasNewContent || snapshotsManager.canMove(-1 /*previousSnapshot*/), canRedo: snapshotsManager.canMove(1 /*nextSnapshot*/), }; }; //#endregion //#region Misc /** * Get custom data related to this editor * @param key Key of the custom data * @param getter Getter function. If custom data for the given key doesn't exist, * call this function to get one and store it if it is specified. Otherwise return undefined * @param disposer An optional disposer function to dispose this custom data when * dispose editor. */ EditorAdapter.prototype.getCustomData = function (key, getter, disposer) { var core = this.getContentModelEditorCore(); return (core.customData[key] = core.customData[key] || { value: getter ? getter() : undefined, disposer: disposer, }).value; }; /** * Get default format of this editor * @returns Default format object of this editor */ EditorAdapter.prototype.getDefaultFormat = function () { var format = this.getCore().format.defaultFormat; return { bold: (0, roosterjs_content_model_dom_1.isBold)(format.fontWeight), italic: format.italic, underline: format.underline, fontFamily: format.fontFamily, fontSize: format.fontSize, textColor: format.textColor, backgroundColor: format.backgroundColor, }; }; /** * Get a content traverser for the whole editor * @param startNode The node to start from. If not passed, it will start from the beginning of the body */ EditorAdapter.prototype.getBodyTraverser = function (startNode) { return roosterjs_editor_dom_1.ContentTraverser.createBodyTraverser(this.getCore().logicalRoot, startNode); }; /** * Get a content traverser for current selection * @returns A content traverser, or null if editor never got focus before */ EditorAdapter.prototype.getSelectionTraverser = function (range) { var _a; range = (_a = range !== null && range !== void 0 ? range : this.getSelectionRange()) !== null && _a !== void 0 ? _a : undefined; return range ? roosterjs_editor_dom_1.ContentTraverser.createSelectionTraverser(this.getCore().logicalRoot, range) : null; }; /** * Get a content traverser for current block element start from specified position * @param startFrom Start position of the traverser. Default value is ContentPosition.SelectionStart * @returns A content traverser, or null if editor never got focus before */ EditorAdapter.prototype.getBlockTraverser = function (startFrom) { if (startFrom === void 0) { startFrom = 3 /* SelectionStart */; } var range = this.getSelectionRange(); return range ? roosterjs_editor_dom_1.ContentTraverser.createBlockTraverser(this.getCore().logicalRoot, range, startFrom) : null; }; /** * Get a text traverser of current selection * @param event Optional, if specified, editor will try to get cached result from the event object first. * If it is not cached before, query from DOM and cache the result into the event object * @returns A content traverser, or null if editor never got focus before */ EditorAdapter.prototype.getContentSearcherOfCursor = function (event) { var _this = this; return (0, roosterjs_editor_dom_1.cacheGetEventData)(event !== null && event !== void 0 ? event : null, 'ContentSearcher', function () { var range = _this.getSelectionRange(); return (range && new roosterjs_editor_dom_1.PositionContentSearcher(_this.getCore().logicalRoot, roosterjs_editor_dom_1.Position.getStart(range))); }); }; /** * Run a callback function asynchronously * @param callback The callback function to run * @returns a function to cancel this async run */ EditorAdapter.