roosterjs
Version:
A simple facade for all roosterjs code
1,052 lines (1,033 loc) • 119 kB
JavaScript
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.