roosterjs-content-model-core 9.5.0 → 9.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/lib/command/paste/mergePasteContent.js +1 -14
  2. package/lib/command/paste/mergePasteContent.js.map +1 -1
  3. package/lib/coreApi/setContentModel/setContentModel.js +1 -2
  4. package/lib/coreApi/setContentModel/setContentModel.js.map +1 -1
  5. package/lib/corePlugin/cache/CachePlugin.js +17 -6
  6. package/lib/corePlugin/cache/CachePlugin.js.map +1 -1
  7. package/lib/corePlugin/cache/domIndexerImpl.d.ts +31 -2
  8. package/lib/corePlugin/cache/domIndexerImpl.js +115 -5
  9. package/lib/corePlugin/cache/domIndexerImpl.js.map +1 -1
  10. package/lib/corePlugin/cache/textMutationObserver.d.ts +2 -2
  11. package/lib/corePlugin/cache/textMutationObserver.js +59 -10
  12. package/lib/corePlugin/cache/textMutationObserver.js.map +1 -1
  13. package/lib-amd/command/paste/mergePasteContent.js +1 -14
  14. package/lib-amd/command/paste/mergePasteContent.js.map +1 -1
  15. package/lib-amd/coreApi/setContentModel/setContentModel.js +1 -2
  16. package/lib-amd/coreApi/setContentModel/setContentModel.js.map +1 -1
  17. package/lib-amd/corePlugin/cache/CachePlugin.js +17 -6
  18. package/lib-amd/corePlugin/cache/CachePlugin.js.map +1 -1
  19. package/lib-amd/corePlugin/cache/domIndexerImpl.d.ts +31 -2
  20. package/lib-amd/corePlugin/cache/domIndexerImpl.js +115 -5
  21. package/lib-amd/corePlugin/cache/domIndexerImpl.js.map +1 -1
  22. package/lib-amd/corePlugin/cache/textMutationObserver.d.ts +2 -2
  23. package/lib-amd/corePlugin/cache/textMutationObserver.js +59 -10
  24. package/lib-amd/corePlugin/cache/textMutationObserver.js.map +1 -1
  25. package/lib-mjs/command/paste/mergePasteContent.js +1 -14
  26. package/lib-mjs/command/paste/mergePasteContent.js.map +1 -1
  27. package/lib-mjs/coreApi/setContentModel/setContentModel.js +1 -2
  28. package/lib-mjs/coreApi/setContentModel/setContentModel.js.map +1 -1
  29. package/lib-mjs/corePlugin/cache/CachePlugin.js +17 -6
  30. package/lib-mjs/corePlugin/cache/CachePlugin.js.map +1 -1
  31. package/lib-mjs/corePlugin/cache/domIndexerImpl.d.ts +31 -2
  32. package/lib-mjs/corePlugin/cache/domIndexerImpl.js +117 -7
  33. package/lib-mjs/corePlugin/cache/domIndexerImpl.js.map +1 -1
  34. package/lib-mjs/corePlugin/cache/textMutationObserver.d.ts +2 -2
  35. package/lib-mjs/corePlugin/cache/textMutationObserver.js +59 -10
  36. package/lib-mjs/corePlugin/cache/textMutationObserver.js.map +1 -1
  37. package/package.json +3 -3
@@ -4,19 +4,6 @@ exports.mergePasteContent = exports.cloneModelForPaste = void 0;
4
4
  var tslib_1 = require("tslib");
5
5
  var createDomToModelContextForSanitizing_1 = require("../createModelFromHtml/createDomToModelContextForSanitizing");
6
6
  var roosterjs_content_model_dom_1 = require("roosterjs-content-model-dom");
7
- var EmptySegmentFormat = {
8
- backgroundColor: '',
9
- fontFamily: '',
10
- fontSize: '',
11
- fontWeight: '',
12
- italic: false,
13
- letterSpacing: '',
14
- lineHeight: '',
15
- strikethrough: false,
16
- superOrSubScriptSequence: '',
17
- textColor: '',
18
- underline: false,
19
- };
20
7
  var CloneOption = {
21
8
  includeCachedElement: function (node, type) { return (type == 'cache' ? undefined : node); },
22
9
  };
@@ -51,7 +38,7 @@ function mergePasteContent(editor, eventResult, clipboardData) {
51
38
  ? customizedMerge(model, pasteModel)
52
39
  : (0, roosterjs_content_model_dom_1.mergeModel)(model, pasteModel, context, mergeOption);
53
40
  if (insertPoint) {
54
- context.newPendingFormat = (0, tslib_1.__assign)((0, tslib_1.__assign)((0, tslib_1.__assign)({}, EmptySegmentFormat), model.format), insertPoint.marker.format);
41
+ context.newPendingFormat = (0, tslib_1.__assign)((0, tslib_1.__assign)((0, tslib_1.__assign)({}, roosterjs_content_model_dom_1.EmptySegmentFormat), model.format), insertPoint.marker.format);
55
42
  }
56
43
  return true;
57
44
  }, {
@@ -1 +1 @@
1
- {"version":3,"file":"mergePasteContent.js","sourceRoot":"","sources":["../../../../../packages/roosterjs-content-model-core/lib/command/paste/mergePasteContent.ts"],"names":[],"mappings":"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","sourcesContent":["import { createDomToModelContextForSanitizing } from '../createModelFromHtml/createDomToModelContextForSanitizing';\nimport {\n ChangeSource,\n cloneModel,\n domToContentModel,\n getSegmentTextFormat,\n getSelectedSegments,\n mergeModel,\n} from 'roosterjs-content-model-dom';\nimport type {\n BeforePasteEvent,\n ClipboardData,\n CloneModelOptions,\n ContentModelDocument,\n ContentModelSegmentFormat,\n IEditor,\n MergeModelOption,\n ReadonlyContentModelDocument,\n} from 'roosterjs-content-model-types';\n\nconst EmptySegmentFormat: Required<ContentModelSegmentFormat> = {\n backgroundColor: '',\n fontFamily: '',\n fontSize: '',\n fontWeight: '',\n italic: false,\n letterSpacing: '',\n lineHeight: '',\n strikethrough: false,\n superOrSubScriptSequence: '',\n textColor: '',\n underline: false,\n};\n\nconst CloneOption: CloneModelOptions = {\n includeCachedElement: (node, type) => (type == 'cache' ? undefined : node),\n};\n\n/**\n * @internal\n */\nexport function cloneModelForPaste(model: ReadonlyContentModelDocument) {\n return cloneModel(model, CloneOption);\n}\n\n/**\n * @internal\n */\nexport function mergePasteContent(\n editor: IEditor,\n eventResult: BeforePasteEvent,\n clipboardData: ClipboardData\n) {\n const { fragment, domToModelOption, customizedMerge, pasteType } = eventResult;\n\n editor.formatContentModel(\n (model, context) => {\n if (clipboardData.modelBeforePaste) {\n const clonedModel = cloneModelForPaste(clipboardData.modelBeforePaste);\n model.blocks = clonedModel.blocks;\n }\n\n const selectedSegment = getSelectedSegments(model, true /*includeFormatHolder*/)[0];\n const domToModelContext = createDomToModelContextForSanitizing(\n editor.getDocument(),\n undefined /*defaultFormat*/,\n editor.getEnvironment().domToModelSettings.customized,\n domToModelOption\n );\n\n domToModelContext.segmentFormat = selectedSegment\n ? getSegmentTextFormat(selectedSegment)\n : {};\n\n const pasteModel = domToContentModel(fragment, domToModelContext);\n const mergeOption: MergeModelOption = {\n mergeFormat: pasteType == 'mergeFormat' ? 'keepSourceEmphasisFormat' : 'none',\n mergeTable: shouldMergeTable(pasteModel),\n };\n\n const insertPoint = customizedMerge\n ? customizedMerge(model, pasteModel)\n : mergeModel(model, pasteModel, context, mergeOption);\n\n if (insertPoint) {\n context.newPendingFormat = {\n ...EmptySegmentFormat,\n ...model.format,\n ...insertPoint.marker.format,\n };\n }\n\n return true;\n },\n {\n changeSource: ChangeSource.Paste,\n getChangeData: () => clipboardData,\n scrollCaretIntoView: true,\n apiName: 'paste',\n }\n );\n}\n\nfunction shouldMergeTable(pasteModel: ContentModelDocument): boolean | undefined {\n // If model contains a table and a paragraph element after the table with a single BR segment, remove the Paragraph after the table\n if (\n pasteModel.blocks.length == 2 &&\n pasteModel.blocks[0].blockType === 'Table' &&\n pasteModel.blocks[1].blockType === 'Paragraph' &&\n pasteModel.blocks[1].segments.length === 1 &&\n pasteModel.blocks[1].segments[0].segmentType === 'Br'\n ) {\n pasteModel.blocks.splice(1);\n }\n // Only merge table when the document contain a single table.\n return pasteModel.blocks.length === 1 && pasteModel.blocks[0].blockType === 'Table';\n}\n"]}
1
+ {"version":3,"file":"mergePasteContent.js","sourceRoot":"","sources":["../../../../../packages/roosterjs-content-model-core/lib/command/paste/mergePasteContent.ts"],"names":[],"mappings":"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","sourcesContent":["import { createDomToModelContextForSanitizing } from '../createModelFromHtml/createDomToModelContextForSanitizing';\nimport {\n ChangeSource,\n EmptySegmentFormat,\n cloneModel,\n domToContentModel,\n getSegmentTextFormat,\n getSelectedSegments,\n mergeModel,\n} from 'roosterjs-content-model-dom';\nimport type {\n BeforePasteEvent,\n ClipboardData,\n CloneModelOptions,\n ContentModelDocument,\n IEditor,\n MergeModelOption,\n ReadonlyContentModelDocument,\n} from 'roosterjs-content-model-types';\n\nconst CloneOption: CloneModelOptions = {\n includeCachedElement: (node, type) => (type == 'cache' ? undefined : node),\n};\n\n/**\n * @internal\n */\nexport function cloneModelForPaste(model: ReadonlyContentModelDocument) {\n return cloneModel(model, CloneOption);\n}\n\n/**\n * @internal\n */\nexport function mergePasteContent(\n editor: IEditor,\n eventResult: BeforePasteEvent,\n clipboardData: ClipboardData\n) {\n const { fragment, domToModelOption, customizedMerge, pasteType } = eventResult;\n\n editor.formatContentModel(\n (model, context) => {\n if (clipboardData.modelBeforePaste) {\n const clonedModel = cloneModelForPaste(clipboardData.modelBeforePaste);\n model.blocks = clonedModel.blocks;\n }\n\n const selectedSegment = getSelectedSegments(model, true /*includeFormatHolder*/)[0];\n const domToModelContext = createDomToModelContextForSanitizing(\n editor.getDocument(),\n undefined /*defaultFormat*/,\n editor.getEnvironment().domToModelSettings.customized,\n domToModelOption\n );\n\n domToModelContext.segmentFormat = selectedSegment\n ? getSegmentTextFormat(selectedSegment)\n : {};\n\n const pasteModel = domToContentModel(fragment, domToModelContext);\n const mergeOption: MergeModelOption = {\n mergeFormat: pasteType == 'mergeFormat' ? 'keepSourceEmphasisFormat' : 'none',\n mergeTable: shouldMergeTable(pasteModel),\n };\n\n const insertPoint = customizedMerge\n ? customizedMerge(model, pasteModel)\n : mergeModel(model, pasteModel, context, mergeOption);\n\n if (insertPoint) {\n context.newPendingFormat = {\n ...EmptySegmentFormat,\n ...model.format,\n ...insertPoint.marker.format,\n };\n }\n\n return true;\n },\n {\n changeSource: ChangeSource.Paste,\n getChangeData: () => clipboardData,\n scrollCaretIntoView: true,\n apiName: 'paste',\n }\n );\n}\n\nfunction shouldMergeTable(pasteModel: ContentModelDocument): boolean | undefined {\n // If model contains a table and a paragraph element after the table with a single BR segment, remove the Paragraph after the table\n if (\n pasteModel.blocks.length == 2 &&\n pasteModel.blocks[0].blockType === 'Table' &&\n pasteModel.blocks[1].blockType === 'Paragraph' &&\n pasteModel.blocks[1].segments.length === 1 &&\n pasteModel.blocks[1].segments[0].segmentType === 'Br'\n ) {\n pasteModel.blocks.splice(1);\n }\n // Only merge table when the document contain a single table.\n return pasteModel.blocks.length === 1 && pasteModel.blocks[0].blockType === 'Table';\n}\n"]}
@@ -27,8 +27,7 @@ var setContentModel = function (core, model, option, onNodeCreated) {
27
27
  core.selection.selection = selection;
28
28
  }
29
29
  // Clear pending mutations since we will use our latest model object to replace existing cache
30
- (_a = core.cache.textMutationObserver) === null || _a === void 0 ? void 0 : _a.flushMutations();
31
- core.cache.cachedModel = model;
30
+ (_a = core.cache.textMutationObserver) === null || _a === void 0 ? void 0 : _a.flushMutations(model);
32
31
  }
33
32
  return selection;
34
33
  };
@@ -1 +1 @@
1
- {"version":3,"file":"setContentModel.js","sourceRoot":"","sources":["../../../../../packages/roosterjs-content-model-core/lib/coreApi/setContentModel/setContentModel.ts"],"names":[],"mappings":";;;AAAA,sFAAqF;AACrF,2EAIqC;AAGrC;;;;;;GAMG;AACI,IAAM,eAAe,GAAoB,UAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa;;IAC/E,IAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,mBAAmB,CAAC,IAAI,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;IAC7E,IAAM,iBAAiB,GAAG,MAAM;QAC5B,CAAC,CAAC,IAAA,qDAAuB,EACnB,aAAa,EACb,IAAI,CAAC,WAAW,CAAC,kBAAkB,CAAC,OAAO,EAC3C,IAAI,CAAC,WAAW,CAAC,kBAAkB,CAAC,UAAU,EAC9C,MAAM,CACT;QACH,CAAC,CAAC,IAAA,+DAAiC,EAC7B,IAAI,CAAC,WAAW,CAAC,kBAAkB,CAAC,UAAU,EAC9C,aAAa,CAChB,CAAC;IAER,iBAAiB,CAAC,aAAa,GAAG,aAAa,CAAC;IAEhD,IAAM,SAAS,GAAG,IAAA,+CAAiB,EAC/B,IAAI,CAAC,WAAW,CAAC,aAAa,EAC9B,IAAI,CAAC,WAAW,EAChB,KAAK,EACL,iBAAiB,CACpB,CAAC;IAEF,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,kBAAkB,EAAE;QACpC,IAAA,6CAAqB,EAAC,IAAI,CAAC,KAAK,EAAE,SAAS,IAAI,SAAS,CAAC,CAAC;QAE1D,IAAI,CAAC,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,eAAe,CAAA,IAAI,SAAS,EAAE;YACvC,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;SAC7C;aAAM;YACH,IAAI,CAAC,SAAS,CAAC,SAAS,GAAG,SAAS,CAAC;SACxC;QAED,8FAA8F;QAC9F,MAAA,IAAI,CAAC,KAAK,CAAC,oBAAoB,0CAAE,cAAc,EAAE,CAAC;QAClD,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,KAAK,CAAC;KAClC;IAED,OAAO,SAAS,CAAC;AACrB,CAAC,CAAC;AAtCW,QAAA,eAAe,mBAsC1B","sourcesContent":["import { updateCachedSelection } from '../../corePlugin/cache/updateCachedSelection';\nimport {\n contentModelToDom,\n createModelToDomContext,\n createModelToDomContextWithConfig,\n} from 'roosterjs-content-model-dom';\nimport type { SetContentModel } from 'roosterjs-content-model-types';\n\n/**\n * @internal\n * Set content with content model\n * @param core The editor core object\n * @param model The content model to set\n * @param option Additional options to customize the behavior of Content Model to DOM conversion\n */\nexport const setContentModel: SetContentModel = (core, model, option, onNodeCreated) => {\n const editorContext = core.api.createEditorContext(core, true /*saveIndex*/);\n const modelToDomContext = option\n ? createModelToDomContext(\n editorContext,\n core.environment.modelToDomSettings.builtIn,\n core.environment.modelToDomSettings.customized,\n option\n )\n : createModelToDomContextWithConfig(\n core.environment.modelToDomSettings.calculated,\n editorContext\n );\n\n modelToDomContext.onNodeCreated = onNodeCreated;\n\n const selection = contentModelToDom(\n core.logicalRoot.ownerDocument,\n core.logicalRoot,\n model,\n modelToDomContext\n );\n\n if (!core.lifecycle.shadowEditFragment) {\n updateCachedSelection(core.cache, selection || undefined);\n\n if (!option?.ignoreSelection && selection) {\n core.api.setDOMSelection(core, selection);\n } else {\n core.selection.selection = selection;\n }\n\n // Clear pending mutations since we will use our latest model object to replace existing cache\n core.cache.textMutationObserver?.flushMutations();\n core.cache.cachedModel = model;\n }\n\n return selection;\n};\n"]}
1
+ {"version":3,"file":"setContentModel.js","sourceRoot":"","sources":["../../../../../packages/roosterjs-content-model-core/lib/coreApi/setContentModel/setContentModel.ts"],"names":[],"mappings":";;;AAAA,sFAAqF;AACrF,2EAIqC;AAGrC;;;;;;GAMG;AACI,IAAM,eAAe,GAAoB,UAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa;;IAC/E,IAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,mBAAmB,CAAC,IAAI,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;IAC7E,IAAM,iBAAiB,GAAG,MAAM;QAC5B,CAAC,CAAC,IAAA,qDAAuB,EACnB,aAAa,EACb,IAAI,CAAC,WAAW,CAAC,kBAAkB,CAAC,OAAO,EAC3C,IAAI,CAAC,WAAW,CAAC,kBAAkB,CAAC,UAAU,EAC9C,MAAM,CACT;QACH,CAAC,CAAC,IAAA,+DAAiC,EAC7B,IAAI,CAAC,WAAW,CAAC,kBAAkB,CAAC,UAAU,EAC9C,aAAa,CAChB,CAAC;IAER,iBAAiB,CAAC,aAAa,GAAG,aAAa,CAAC;IAEhD,IAAM,SAAS,GAAG,IAAA,+CAAiB,EAC/B,IAAI,CAAC,WAAW,CAAC,aAAa,EAC9B,IAAI,CAAC,WAAW,EAChB,KAAK,EACL,iBAAiB,CACpB,CAAC;IAEF,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,kBAAkB,EAAE;QACpC,IAAA,6CAAqB,EAAC,IAAI,CAAC,KAAK,EAAE,SAAS,IAAI,SAAS,CAAC,CAAC;QAE1D,IAAI,CAAC,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,eAAe,CAAA,IAAI,SAAS,EAAE;YACvC,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;SAC7C;aAAM;YACH,IAAI,CAAC,SAAS,CAAC,SAAS,GAAG,SAAS,CAAC;SACxC;QAED,8FAA8F;QAC9F,MAAA,IAAI,CAAC,KAAK,CAAC,oBAAoB,0CAAE,cAAc,CAAC,KAAK,CAAC,CAAC;KAC1D;IAED,OAAO,SAAS,CAAC;AACrB,CAAC,CAAC;AArCW,QAAA,eAAe,mBAqC1B","sourcesContent":["import { updateCachedSelection } from '../../corePlugin/cache/updateCachedSelection';\nimport {\n contentModelToDom,\n createModelToDomContext,\n createModelToDomContextWithConfig,\n} from 'roosterjs-content-model-dom';\nimport type { SetContentModel } from 'roosterjs-content-model-types';\n\n/**\n * @internal\n * Set content with content model\n * @param core The editor core object\n * @param model The content model to set\n * @param option Additional options to customize the behavior of Content Model to DOM conversion\n */\nexport const setContentModel: SetContentModel = (core, model, option, onNodeCreated) => {\n const editorContext = core.api.createEditorContext(core, true /*saveIndex*/);\n const modelToDomContext = option\n ? createModelToDomContext(\n editorContext,\n core.environment.modelToDomSettings.builtIn,\n core.environment.modelToDomSettings.customized,\n option\n )\n : createModelToDomContextWithConfig(\n core.environment.modelToDomSettings.calculated,\n editorContext\n );\n\n modelToDomContext.onNodeCreated = onNodeCreated;\n\n const selection = contentModelToDom(\n core.logicalRoot.ownerDocument,\n core.logicalRoot,\n model,\n modelToDomContext\n );\n\n if (!core.lifecycle.shadowEditFragment) {\n updateCachedSelection(core.cache, selection || undefined);\n\n if (!option?.ignoreSelection && selection) {\n core.api.setDOMSelection(core, selection);\n } else {\n core.selection.selection = selection;\n }\n\n // Clear pending mutations since we will use our latest model object to replace existing cache\n core.cache.textMutationObserver?.flushMutations(model);\n }\n\n return selection;\n};\n"]}
@@ -27,19 +27,30 @@ var CachePlugin = /** @class */ (function () {
27
27
  }
28
28
  }
29
29
  };
30
+ this.onSkipMutation = function (newModel) {
31
+ var _a;
32
+ if (!((_a = _this.editor) === null || _a === void 0 ? void 0 : _a.isInShadowEdit())) {
33
+ _this.state.cachedModel = newModel;
34
+ _this.state.cachedSelection = undefined;
35
+ }
36
+ };
30
37
  this.onNativeSelectionChange = function () {
31
38
  var _a;
32
39
  if ((_a = _this.editor) === null || _a === void 0 ? void 0 : _a.hasFocus()) {
33
40
  _this.updateCachedModel(_this.editor);
34
41
  }
35
42
  };
36
- this.state = option.disableCache
37
- ? {}
38
- : {
39
- domIndexer: new domIndexerImpl_1.DomIndexerImpl(option.experimentalFeatures &&
40
- option.experimentalFeatures.indexOf('PersistCache') >= 0),
41
- textMutationObserver: (0, textMutationObserver_1.createTextMutationObserver)(contentDiv, this.onMutation),
43
+ if (option.disableCache) {
44
+ this.state = {};
45
+ }
46
+ else {
47
+ var domIndexer = new domIndexerImpl_1.DomIndexerImpl(option.experimentalFeatures &&
48
+ option.experimentalFeatures.indexOf('PersistCache') >= 0);
49
+ this.state = {
50
+ domIndexer: domIndexer,
51
+ textMutationObserver: (0, textMutationObserver_1.createTextMutationObserver)(contentDiv, domIndexer, this.onMutation, this.onSkipMutation),
42
52
  };
53
+ }
43
54
  }
44
55
  /**
45
56
  * Get name of this plugin
@@ -1 +1 @@
1
- {"version":3,"file":"CachePlugin.js","sourceRoot":"","sources":["../../../../../packages/roosterjs-content-model-core/lib/corePlugin/cache/CachePlugin.ts"],"names":[],"mappings":"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","sourcesContent":["import { areSameSelection } from './areSameSelection';\nimport { createTextMutationObserver } from './textMutationObserver';\nimport { DomIndexerImpl } from './domIndexerImpl';\nimport { updateCachedSelection } from './updateCachedSelection';\nimport type {\n CachePluginState,\n IEditor,\n PluginEvent,\n PluginWithState,\n EditorOptions,\n} from 'roosterjs-content-model-types';\n\n/**\n * ContentModel cache plugin manages cached Content Model, and refresh the cache when necessary\n */\nclass CachePlugin implements PluginWithState<CachePluginState> {\n private editor: IEditor | null = null;\n private state: CachePluginState;\n\n /**\n * Construct a new instance of CachePlugin class\n * @param option The editor option\n * @param contentDiv The editor content DIV\n */\n constructor(option: EditorOptions, contentDiv: HTMLDivElement) {\n this.state = option.disableCache\n ? {}\n : {\n domIndexer: new DomIndexerImpl(\n option.experimentalFeatures &&\n option.experimentalFeatures.indexOf('PersistCache') >= 0\n ),\n textMutationObserver: createTextMutationObserver(contentDiv, this.onMutation),\n };\n }\n\n /**\n * Get name of this plugin\n */\n getName() {\n return 'Cache';\n }\n\n /**\n * The first method that editor will call to a plugin when editor is initializing.\n * It will pass in the editor instance, plugin should take this chance to save the\n * editor reference so that it can call to any editor method or format API later.\n * @param editor The editor object\n */\n initialize(editor: IEditor) {\n this.editor = editor;\n this.editor.getDocument().addEventListener('selectionchange', this.onNativeSelectionChange);\n\n this.state.textMutationObserver?.startObserving();\n }\n\n /**\n * The last method that editor will call to a plugin before it is disposed.\n * Plugin can take this chance to clear the reference to editor. After this method is\n * called, plugin should not call to any editor method since it will result in error.\n */\n dispose() {\n this.state.textMutationObserver?.stopObserving();\n\n if (this.editor) {\n this.editor\n .getDocument()\n .removeEventListener('selectionchange', this.onNativeSelectionChange);\n this.editor = null;\n }\n }\n\n /**\n * Get plugin state object\n */\n getState(): CachePluginState {\n return this.state;\n }\n\n /**\n * Core method for a plugin. Once an event happens in editor, editor will call this\n * method of each plugin to handle the event as long as the event is not handled\n * exclusively by another plugin.\n * @param event The event to handle:\n */\n onPluginEvent(event: PluginEvent) {\n if (!this.editor) {\n return;\n }\n\n switch (event.eventType) {\n case 'keyDown':\n case 'input':\n if (!this.state.textMutationObserver) {\n // When updating cache is not enabled, need to clear the cache to make sure other plugins can get an up-to-date content model\n this.invalidateCache();\n }\n break;\n\n case 'selectionChanged':\n this.updateCachedModel(this.editor);\n break;\n\n case 'contentChanged':\n const { contentModel, selection } = event;\n\n if (contentModel && this.state.domIndexer) {\n this.state.cachedModel = contentModel;\n updateCachedSelection(this.state, selection);\n } else {\n this.invalidateCache();\n }\n\n break;\n }\n }\n\n private onMutation = (isTextChangeOnly: boolean) => {\n if (this.editor) {\n if (isTextChangeOnly) {\n this.updateCachedModel(this.editor, true /*forceUpdate*/);\n } else {\n this.invalidateCache();\n }\n }\n };\n\n private onNativeSelectionChange = () => {\n if (this.editor?.hasFocus()) {\n this.updateCachedModel(this.editor);\n }\n };\n\n private invalidateCache() {\n if (!this.editor?.isInShadowEdit()) {\n this.state.cachedModel = undefined;\n this.state.cachedSelection = undefined;\n }\n }\n\n private updateCachedModel(editor: IEditor, forceUpdate?: boolean) {\n const cachedSelection = this.state.cachedSelection;\n this.state.cachedSelection = undefined; // Clear it to force getDOMSelection() retrieve the latest selection range\n\n const newRangeEx = editor.getDOMSelection() || undefined;\n const model = this.state.cachedModel;\n const isSelectionChanged =\n forceUpdate ||\n !cachedSelection ||\n !newRangeEx ||\n !areSameSelection(newRangeEx, cachedSelection);\n\n if (isSelectionChanged) {\n if (\n !model ||\n !newRangeEx ||\n !this.state.domIndexer?.reconcileSelection(model, newRangeEx, cachedSelection)\n ) {\n this.invalidateCache();\n } else {\n updateCachedSelection(this.state, newRangeEx);\n }\n } else {\n this.state.cachedSelection = cachedSelection;\n }\n }\n}\n\n/**\n * @internal\n * Create a new instance of CachePlugin class.\n * @param option The editor option\n * @param contentDiv The editor content DIV\n */\nexport function createCachePlugin(\n option: EditorOptions,\n contentDiv: HTMLDivElement\n): PluginWithState<CachePluginState> {\n return new CachePlugin(option, contentDiv);\n}\n"]}
1
+ {"version":3,"file":"CachePlugin.js","sourceRoot":"","sources":["../../../../../packages/roosterjs-content-model-core/lib/corePlugin/cache/CachePlugin.ts"],"names":[],"mappings":"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","sourcesContent":["import { areSameSelection } from './areSameSelection';\nimport { createTextMutationObserver } from './textMutationObserver';\nimport { DomIndexerImpl } from './domIndexerImpl';\nimport { updateCachedSelection } from './updateCachedSelection';\nimport type {\n CachePluginState,\n IEditor,\n PluginEvent,\n PluginWithState,\n EditorOptions,\n ContentModelDocument,\n} from 'roosterjs-content-model-types';\n\n/**\n * ContentModel cache plugin manages cached Content Model, and refresh the cache when necessary\n */\nclass CachePlugin implements PluginWithState<CachePluginState> {\n private editor: IEditor | null = null;\n private state: CachePluginState;\n\n /**\n * Construct a new instance of CachePlugin class\n * @param option The editor option\n * @param contentDiv The editor content DIV\n */\n constructor(option: EditorOptions, contentDiv: HTMLDivElement) {\n if (option.disableCache) {\n this.state = {};\n } else {\n const domIndexer = new DomIndexerImpl(\n option.experimentalFeatures &&\n option.experimentalFeatures.indexOf('PersistCache') >= 0\n );\n\n this.state = {\n domIndexer: domIndexer,\n textMutationObserver: createTextMutationObserver(\n contentDiv,\n domIndexer,\n this.onMutation,\n this.onSkipMutation\n ),\n };\n }\n }\n\n /**\n * Get name of this plugin\n */\n getName() {\n return 'Cache';\n }\n\n /**\n * The first method that editor will call to a plugin when editor is initializing.\n * It will pass in the editor instance, plugin should take this chance to save the\n * editor reference so that it can call to any editor method or format API later.\n * @param editor The editor object\n */\n initialize(editor: IEditor) {\n this.editor = editor;\n this.editor.getDocument().addEventListener('selectionchange', this.onNativeSelectionChange);\n\n this.state.textMutationObserver?.startObserving();\n }\n\n /**\n * The last method that editor will call to a plugin before it is disposed.\n * Plugin can take this chance to clear the reference to editor. After this method is\n * called, plugin should not call to any editor method since it will result in error.\n */\n dispose() {\n this.state.textMutationObserver?.stopObserving();\n\n if (this.editor) {\n this.editor\n .getDocument()\n .removeEventListener('selectionchange', this.onNativeSelectionChange);\n this.editor = null;\n }\n }\n\n /**\n * Get plugin state object\n */\n getState(): CachePluginState {\n return this.state;\n }\n\n /**\n * Core method for a plugin. Once an event happens in editor, editor will call this\n * method of each plugin to handle the event as long as the event is not handled\n * exclusively by another plugin.\n * @param event The event to handle:\n */\n onPluginEvent(event: PluginEvent) {\n if (!this.editor) {\n return;\n }\n\n switch (event.eventType) {\n case 'keyDown':\n case 'input':\n if (!this.state.textMutationObserver) {\n // When updating cache is not enabled, need to clear the cache to make sure other plugins can get an up-to-date content model\n this.invalidateCache();\n }\n break;\n\n case 'selectionChanged':\n this.updateCachedModel(this.editor);\n break;\n\n case 'contentChanged':\n const { contentModel, selection } = event;\n\n if (contentModel && this.state.domIndexer) {\n this.state.cachedModel = contentModel;\n updateCachedSelection(this.state, selection);\n } else {\n this.invalidateCache();\n }\n\n break;\n }\n }\n\n private onMutation = (isTextChangeOnly: boolean) => {\n if (this.editor) {\n if (isTextChangeOnly) {\n this.updateCachedModel(this.editor, true /*forceUpdate*/);\n } else {\n this.invalidateCache();\n }\n }\n };\n\n private onSkipMutation = (newModel: ContentModelDocument) => {\n if (!this.editor?.isInShadowEdit()) {\n this.state.cachedModel = newModel;\n this.state.cachedSelection = undefined;\n }\n };\n\n private onNativeSelectionChange = () => {\n if (this.editor?.hasFocus()) {\n this.updateCachedModel(this.editor);\n }\n };\n\n private invalidateCache() {\n if (!this.editor?.isInShadowEdit()) {\n this.state.cachedModel = undefined;\n this.state.cachedSelection = undefined;\n }\n }\n\n private updateCachedModel(editor: IEditor, forceUpdate?: boolean) {\n const cachedSelection = this.state.cachedSelection;\n this.state.cachedSelection = undefined; // Clear it to force getDOMSelection() retrieve the latest selection range\n\n const newRangeEx = editor.getDOMSelection() || undefined;\n const model = this.state.cachedModel;\n const isSelectionChanged =\n forceUpdate ||\n !cachedSelection ||\n !newRangeEx ||\n !areSameSelection(newRangeEx, cachedSelection);\n\n if (isSelectionChanged) {\n if (\n !model ||\n !newRangeEx ||\n !this.state.domIndexer?.reconcileSelection(model, newRangeEx, cachedSelection)\n ) {\n this.invalidateCache();\n } else {\n updateCachedSelection(this.state, newRangeEx);\n }\n } else {\n this.state.cachedSelection = cachedSelection;\n }\n }\n}\n\n/**\n * @internal\n * Create a new instance of CachePlugin class.\n * @param option The editor option\n * @param contentDiv The editor content DIV\n */\nexport function createCachePlugin(\n option: EditorOptions,\n contentDiv: HTMLDivElement\n): PluginWithState<CachePluginState> {\n return new CachePlugin(option, contentDiv);\n}\n"]}
@@ -1,17 +1,46 @@
1
- import type { CacheSelection, ContentModelDocument, ContentModelParagraph, ContentModelSegment, ContentModelTable, DomIndexer, DOMSelection } from 'roosterjs-content-model-types';
1
+ import type { CacheSelection, ContentModelDocument, ContentModelParagraph, ContentModelSegment, ContentModelTable, ContentModelTableRow, DomIndexer, DOMSelection } from 'roosterjs-content-model-types';
2
+ /**
3
+ * @internal Export for test only
4
+ */
5
+ export interface SegmentItem {
6
+ paragraph: ContentModelParagraph;
7
+ segments: ContentModelSegment[];
8
+ }
9
+ /**
10
+ * @internal Export for test only
11
+ */
12
+ export interface TableItem {
13
+ tableRows: ContentModelTableRow[];
14
+ }
15
+ /**
16
+ * @internal Export for test only
17
+ */
18
+ export interface IndexedSegmentNode extends Node {
19
+ __roosterjsContentModel: SegmentItem;
20
+ }
21
+ /**
22
+ * @internal Export for test only
23
+ */
24
+ export interface IndexedTableElement extends HTMLTableElement {
25
+ __roosterjsContentModel: TableItem;
26
+ }
2
27
  /**
3
28
  * @internal
4
29
  * Implementation of DomIndexer
5
30
  */
6
31
  export declare class DomIndexerImpl implements DomIndexer {
7
- readonly persistCache?: boolean | undefined;
32
+ private readonly persistCache?;
8
33
  constructor(persistCache?: boolean | undefined);
9
34
  onSegment(segmentNode: Node, paragraph: ContentModelParagraph, segment: ContentModelSegment[]): void;
10
35
  onParagraph(paragraphElement: HTMLElement): void;
11
36
  onTable(tableElement: HTMLTableElement, table: ContentModelTable): void;
12
37
  reconcileSelection(model: ContentModelDocument, newSelection: DOMSelection, oldSelection?: CacheSelection): boolean;
38
+ reconcileChildList(addedNodes: ArrayLike<Node>, removedNodes: ArrayLike<Node>): boolean;
13
39
  private isCollapsed;
14
40
  private reconcileNodeSelection;
15
41
  private insertMarker;
16
42
  private reconcileTextSelection;
43
+ private reconcileAddedNode;
44
+ private reconcileRemovedNode;
45
+ private indexNode;
17
46
  }
@@ -11,6 +11,9 @@ function isIndexedSegment(node) {
11
11
  Array.isArray(paragraph.segments) &&
12
12
  Array.isArray(segments));
13
13
  }
14
+ function getIndexedSegmentItem(node) {
15
+ return node && isIndexedSegment(node) ? node.__roosterjsContentModel : null;
16
+ }
14
17
  /**
15
18
  * @internal
16
19
  * Implementation of DomIndexer
@@ -34,9 +37,7 @@ var DomIndexerImpl = /** @class */ (function () {
34
37
  previousText = child;
35
38
  }
36
39
  else {
37
- var item = isIndexedSegment(previousText)
38
- ? previousText.__roosterjsContentModel
39
- : undefined;
40
+ var item = getIndexedSegmentItem(previousText);
40
41
  if (item && isIndexedSegment(child)) {
41
42
  item.segments = item.segments.concat(child.__roosterjsContentModel.segments);
42
43
  child.__roosterjsContentModel.segments = [];
@@ -109,6 +110,32 @@ var DomIndexerImpl = /** @class */ (function () {
109
110
  }
110
111
  return false;
111
112
  };
113
+ DomIndexerImpl.prototype.reconcileChildList = function (addedNodes, removedNodes) {
114
+ if (!this.persistCache) {
115
+ return false;
116
+ }
117
+ var canHandle = true;
118
+ var context = {
119
+ segIndex: -1,
120
+ };
121
+ // First process added nodes
122
+ var addedNode = addedNodes[0];
123
+ if (addedNodes.length == 1 && (0, roosterjs_content_model_dom_1.isNodeOfType)(addedNode, 'TEXT_NODE')) {
124
+ canHandle = this.reconcileAddedNode(addedNode, context);
125
+ }
126
+ else if (addedNodes.length > 0) {
127
+ canHandle = false;
128
+ }
129
+ // Second, process removed nodes
130
+ var removedNode = removedNodes[0];
131
+ if (canHandle && removedNodes.length == 1) {
132
+ canHandle = this.reconcileRemovedNode(removedNode, context);
133
+ }
134
+ else if (removedNodes.length > 0) {
135
+ canHandle = false;
136
+ }
137
+ return canHandle && !context.pendingTextNode;
138
+ };
112
139
  DomIndexerImpl.prototype.isCollapsed = function (selection) {
113
140
  var start = selection.start, end = selection.end;
114
141
  return start.node == end.node && start.offset == end.offset;
@@ -126,8 +153,9 @@ var DomIndexerImpl = /** @class */ (function () {
126
153
  };
127
154
  DomIndexerImpl.prototype.insertMarker = function (node, isAfter) {
128
155
  var marker;
129
- if (node && isIndexedSegment(node)) {
130
- var _a = node.__roosterjsContentModel, paragraph = _a.paragraph, segments = _a.segments;
156
+ var segmentItem = node && getIndexedSegmentItem(node);
157
+ if (segmentItem) {
158
+ var paragraph = segmentItem.paragraph, segments = segmentItem.segments;
131
159
  var index = paragraph.segments.indexOf(segments[0]);
132
160
  if (index >= 0) {
133
161
  var formatSegment = (!isAfter && paragraph.segments[index - 1]) || paragraph.segments[index];
@@ -197,6 +225,88 @@ var DomIndexerImpl = /** @class */ (function () {
197
225
  }
198
226
  return selectable;
199
227
  };
228
+ DomIndexerImpl.prototype.reconcileAddedNode = function (node, context) {
229
+ var segmentItem = null;
230
+ var index = -1;
231
+ var existingSegment;
232
+ var previousSibling = node.previousSibling, nextSibling = node.nextSibling;
233
+ if ((segmentItem = getIndexedSegmentItem(previousSibling)) &&
234
+ (existingSegment = segmentItem.segments[segmentItem.segments.length - 1]) &&
235
+ (index = segmentItem.paragraph.segments.indexOf(existingSegment)) >= 0) {
236
+ // When we can find indexed segment before current one, use it as the insert index
237
+ this.indexNode(segmentItem.paragraph, index + 1, node, existingSegment.format);
238
+ }
239
+ else if ((segmentItem = getIndexedSegmentItem(nextSibling)) &&
240
+ (existingSegment = segmentItem.segments[0]) &&
241
+ (index = segmentItem.paragraph.segments.indexOf(existingSegment)) >= 0) {
242
+ // When we can find indexed segment after current one, use it as the insert index
243
+ this.indexNode(segmentItem.paragraph, index, node, existingSegment.format);
244
+ }
245
+ else if (context.paragraph && context.segIndex >= 0) {
246
+ // When there is indexed paragraph from removed nodes, we can use it as the insert index
247
+ this.indexNode(context.paragraph, context.segIndex, node, context.format);
248
+ }
249
+ else if (context.pendingTextNode === undefined) {
250
+ // When we can't find the insert index, set current node as pending node
251
+ // so later we can pick it up when we have enough info when processing removed node
252
+ // Only do this when pendingTextNode is undefined. If it is null it means there was already a pending node before
253
+ // and in that case we should return false since we can't handle two pending text node
254
+ context.pendingTextNode = node;
255
+ }
256
+ else {
257
+ return false;
258
+ }
259
+ return true;
260
+ };
261
+ DomIndexerImpl.prototype.reconcileRemovedNode = function (node, context) {
262
+ var segmentItem = null;
263
+ var removingSegment;
264
+ if (context.segIndex < 0 &&
265
+ !context.paragraph && // No previous removed segment or related paragraph found, and
266
+ (segmentItem = getIndexedSegmentItem(node)) && // The removed node is indexed, and
267
+ (removingSegment = segmentItem.segments[0]) // There is at least one related segment
268
+ ) {
269
+ // Now we can remove the indexed segment from the paragraph, and remember it, later we may need to use it
270
+ context.format = removingSegment.format;
271
+ context.paragraph = segmentItem.paragraph;
272
+ context.segIndex = segmentItem.paragraph.segments.indexOf(segmentItem.segments[0]);
273
+ if (context.segIndex < 0) {
274
+ // Indexed segment is not under paragraph, something wrong happens, we cannot keep handling
275
+ return false;
276
+ }
277
+ for (var i = 0; i < segmentItem.segments.length; i++) {
278
+ var index = segmentItem.paragraph.segments.indexOf(segmentItem.segments[i]);
279
+ if (index >= 0) {
280
+ segmentItem.paragraph.segments.splice(index, 1);
281
+ }
282
+ }
283
+ if (context.pendingTextNode) {
284
+ // If we have pending text node added but not indexed, do it now
285
+ this.indexNode(context.paragraph, context.segIndex, context.pendingTextNode, segmentItem.segments[0].format);
286
+ // Set to null since we have processed it.
287
+ // Next time we see a pending node we know we have already processed one so it is a situation we cannot handle
288
+ context.pendingTextNode = null;
289
+ }
290
+ return true;
291
+ }
292
+ else {
293
+ return false;
294
+ }
295
+ };
296
+ DomIndexerImpl.prototype.indexNode = function (paragraph, index, textNode, format) {
297
+ var _a;
298
+ var copiedFormat = format ? (0, tslib_1.__assign)({}, format) : undefined;
299
+ if (copiedFormat) {
300
+ (0, roosterjs_content_model_dom_1.getObjectKeys)(copiedFormat).forEach(function (key) {
301
+ if (roosterjs_content_model_dom_1.EmptySegmentFormat[key] === undefined) {
302
+ delete copiedFormat[key];
303
+ }
304
+ });
305
+ }
306
+ var text = (0, roosterjs_content_model_dom_1.createText)((_a = textNode.textContent) !== null && _a !== void 0 ? _a : '', copiedFormat);
307
+ paragraph.segments.splice(index, 0, text);
308
+ this.onSegment(textNode, paragraph, [text]);
309
+ };
200
310
  return DomIndexerImpl;
201
311
  }());
202
312
  exports.DomIndexerImpl = DomIndexerImpl;
@@ -1 +1 @@
1
- {"version":3,"file":"domIndexerImpl.js","sourceRoot":"","sources":["../../../../../packages/roosterjs-content-model-core/lib/corePlugin/cache/domIndexerImpl.ts"],"names":[],"mappings":";;;;AAAA,2EAKqC;AAiCrC,SAAS,gBAAgB,CAAC,IAAU;;IAC1B,IAAA,KAA0B,MAAC,IAA2B,CAAC,uBAAuB,mCAAI,EAAE,EAAlF,SAAS,eAAA,EAAE,QAAQ,cAA+D,CAAC;IAE3F,OAAO,CACH,SAAS;QACT,SAAS,CAAC,SAAS,IAAI,WAAW;QAClC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC;QACjC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAC1B,CAAC;AACN,CAAC;AAED;;;GAGG;AACH;IACI,wBAA4B,YAAsB;QAAtB,iBAAY,GAAZ,YAAY,CAAU;IAAG,CAAC;IAEtD,kCAAS,GAAT,UAAU,WAAiB,EAAE,SAAgC,EAAE,OAA8B;QACzF,IAAM,WAAW,GAAG,WAAiC,CAAC;QACtD,WAAW,CAAC,uBAAuB,GAAG;YAClC,SAAS,WAAA;YACT,QAAQ,EAAE,OAAO;SACpB,CAAC;IACN,CAAC;IAED,oCAAW,GAAX,UAAY,gBAA6B;QACrC,IAAI,YAAY,GAAgB,IAAI,CAAC;QAErC,KAAK,IAAI,KAAK,GAAG,gBAAgB,CAAC,UAAU,EAAE,KAAK,EAAE,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE;YAC5E,IAAI,IAAA,0CAAY,EAAC,KAAK,EAAE,WAAW,CAAC,EAAE;gBAClC,IAAI,CAAC,YAAY,EAAE;oBACf,YAAY,GAAG,KAAK,CAAC;iBACxB;qBAAM;oBACH,IAAM,IAAI,GAAG,gBAAgB,CAAC,YAAY,CAAC;wBACvC,CAAC,CAAC,YAAY,CAAC,uBAAuB;wBACtC,CAAC,CAAC,SAAS,CAAC;oBAEhB,IAAI,IAAI,IAAI,gBAAgB,CAAC,KAAK,CAAC,EAAE;wBACjC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAChC,KAAK,CAAC,uBAAuB,CAAC,QAAQ,CACzC,CAAC;wBACF,KAAK,CAAC,uBAAuB,CAAC,QAAQ,GAAG,EAAE,CAAC;qBAC/C;iBACJ;aACJ;iBAAM,IAAI,IAAA,0CAAY,EAAC,KAAK,EAAE,cAAc,CAAC,EAAE;gBAC5C,YAAY,GAAG,IAAI,CAAC;gBAEpB,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;aAC3B;iBAAM;gBACH,YAAY,GAAG,IAAI,CAAC;aACvB;SACJ;IACL,CAAC;IAED,gCAAO,GAAP,UAAQ,YAA8B,EAAE,KAAwB;QAC5D,IAAM,YAAY,GAAG,YAAmC,CAAC;QACzD,YAAY,CAAC,uBAAuB,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;IACrE,CAAC;IAED,2CAAkB,GAAlB,UACI,KAA2B,EAC3B,YAA0B,EAC1B,YAA6B;QAE7B,IAAI,YAAY,EAAE;YACd,IACI,YAAY,CAAC,IAAI,IAAI,OAAO;gBAC5B,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC;gBAC9B,IAAA,0CAAY,EAAC,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,WAAW,CAAC,EACpD;gBACE,IAAI,gBAAgB,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE;oBAC3C,IAAI,CAAC,sBAAsB,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;iBACxD;aACJ;iBAAM;gBACH,IAAA,0CAAY,EAAC,KAAK,CAAC,CAAC;aACvB;SACJ;QAED,QAAQ,YAAY,CAAC,IAAI,EAAE;YACvB,KAAK,OAAO,CAAC;YACb,KAAK,OAAO;gBACR,uHAAuH;gBACvH,OAAO,KAAK,CAAC;YAEjB,KAAK,OAAO;gBACR,IAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,CAAC;gBACpC,IAAI,QAAQ,EAAE;oBAEN,IAAA,cAAc,GAKd,QAAQ,eALM,EACd,WAAW,GAIX,QAAQ,YAJG,EACX,YAAY,GAGZ,QAAQ,aAHI,EACZ,SAAS,GAET,QAAQ,UAFC,EACT,SAAS,GACT,QAAQ,UADC,CACA;oBAEb,OAAO,KAAK,CAAC,yBAAyB,CAAC;oBAEvC,IAAI,SAAS,EAAE;wBACX,OAAO,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;qBACrE;yBAAM,IACH,cAAc,IAAI,YAAY;wBAC9B,IAAA,0CAAY,EAAC,cAAc,EAAE,WAAW,CAAC,EAC3C;wBACE,IAAI,YAAY,CAAC,UAAU,EAAE;4BACzB,KAAK,CAAC,yBAAyB,GAAG,IAAI,CAAC;yBAC1C;wBAED,OAAO,CACH,gBAAgB,CAAC,cAAc,CAAC;4BAChC,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,cAAc,EAAE,WAAW,EAAE,SAAS,CAAC,CACxE,CAAC;qBACL;yBAAM;wBACH,IAAM,OAAO,GAAG,IAAI,CAAC,sBAAsB,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;wBACzE,IAAM,OAAO,GAAG,IAAI,CAAC,sBAAsB,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;wBAErE,IAAI,OAAO,IAAI,OAAO,EAAE;4BACpB,IAAI,YAAY,CAAC,UAAU,EAAE;gCACzB,KAAK,CAAC,yBAAyB,GAAG,IAAI,CAAC;6BAC1C;4BAED,IAAA,0CAAY,EAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;4BACtC,OAAO,IAAI,CAAC;yBACf;6BAAM;4BACH,OAAO,KAAK,CAAC;yBAChB;qBACJ;iBACJ;gBAED,MAAM;SACb;QAED,OAAO,KAAK,CAAC;IACjB,CAAC;IAEO,oCAAW,GAAnB,UAAoB,SAAiC;QACzC,IAAA,KAAK,GAAU,SAAS,MAAnB,EAAE,GAAG,GAAK,SAAS,IAAd,CAAe;QAEjC,OAAO,KAAK,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,KAAK,CAAC,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC;IAChE,CAAC;IAEO,+CAAsB,GAA9B,UAA+B,IAAU,EAAE,MAAc;QACrD,IAAI,IAAA,0CAAY,EAAC,IAAI,EAAE,WAAW,CAAC,EAAE;YACjC,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;SACzF;aAAM,IAAI,MAAM,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE;YACzC,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;SAC9D;aAAM;YACH,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;SACxE;IACL,CAAC;IAEO,qCAAY,GAApB,UAAqB,IAAiB,EAAE,OAAgB;QACpD,IAAI,MAA+C,CAAC;QAEpD,IAAI,IAAI,IAAI,gBAAgB,CAAC,IAAI,CAAC,EAAE;YAC1B,IAAA,KAA0B,IAAI,CAAC,uBAAuB,EAApD,SAAS,eAAA,EAAE,QAAQ,cAAiC,CAAC;YAC7D,IAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;YAEtD,IAAI,KAAK,IAAI,CAAC,EAAE;gBACZ,IAAM,aAAa,GACf,CAAC,CAAC,OAAO,IAAI,SAAS,CAAC,QAAQ,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;gBAC7E,MAAM,GAAG,IAAA,mDAAqB,EAAC,aAAa,CAAC,MAAM,CAAC,CAAC;gBAErD,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;aACrE;SACJ;QAED,OAAO,MAAM,CAAC;IAClB,CAAC;IAEO,+CAAsB,GAA9B,UACI,QAA4B,EAC5B,WAAoB,EACpB,SAAkB;;QAEZ,IAAA,KAA0B,QAAQ,CAAC,uBAAuB,EAAxD,SAAS,eAAA,EAAE,QAAQ,cAAqC,CAAC;QACjE,IAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;QAC1B,IAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAC3C,IAAI,UAAkC,CAAC;QAEvC,IAAI,CAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,WAAW,KAAI,MAAM,IAAI,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,WAAW,KAAI,MAAM,EAAE;YAC7D,IAAM,WAAW,GAA0B,EAAE,CAAC;YAC9C,IAAM,GAAG,GAAG,QAAQ,CAAC,SAAS,IAAI,EAAE,CAAC;YACrC,IAAM,YAAY,GAAuB,EAAE,CAAC;YAE5C,IAAI,WAAW,KAAK,SAAS,EAAE;gBAC3B,KAAK,CAAC,IAAI,GAAG,GAAG,CAAC;gBACjB,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACxB,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;aAC5B;iBAAM;gBACH,IAAI,WAAW,GAAG,CAAC,EAAE;oBACjB,KAAK,CAAC,IAAI,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;oBAC3C,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;oBACxB,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;iBAC5B;gBAED,IAAI,SAAS,KAAK,SAAS,EAAE;oBACzB,IAAM,MAAM,GAAG,IAAA,mDAAqB,EAAC,KAAK,CAAC,MAAM,CAAC,CAAC;oBACnD,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;oBAEzB,UAAU,GAAG,MAAM,CAAC;oBACpB,SAAS,GAAG,WAAW,CAAC;iBAC3B;qBAAM,IAAI,SAAS,GAAG,WAAW,EAAE;oBAChC,IAAM,MAAM,GAAG,IAAA,wCAAU,EACrB,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE,SAAS,CAAC,EACrC,KAAK,CAAC,MAAM,EACZ,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,IAAI,CACb,CAAC;oBAEF,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC;oBACzB,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;oBACzB,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;oBAC1B,UAAU,GAAG,MAAM,CAAC;iBACvB;gBAED,IAAI,SAAS,GAAG,GAAG,CAAC,MAAM,EAAE;oBACxB,IAAM,OAAO,GAAG,IAAA,wCAAU,EACtB,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,EACxB,KAAK,CAAC,MAAM,EACZ,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,IAAI,CACb,CAAC;oBACF,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;oBAC1B,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;iBAC9B;aACJ;YAED,IAAI,UAAU,GAAG,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACnD,IAAI,SAAS,GAAG,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAEjD,IAAI,UAAU,IAAI,CAAC,IAAI,SAAS,IAAI,CAAC,EAAE;gBACnC,OACI,UAAU,GAAG,CAAC;oBACd,SAAS,CAAC,QAAQ,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,WAAW,IAAI,iBAAiB,EACrE;oBACE,UAAU,EAAE,CAAC;iBAChB;gBAED,OACI,SAAS,GAAG,SAAS,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC;oBACzC,SAAS,CAAC,QAAQ,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,WAAW,IAAI,iBAAiB,EACpE;oBACE,SAAS,EAAE,CAAC;iBACf;gBAED,CAAA,KAAA,SAAS,CAAC,QAAQ,CAAA,CAAC,MAAM,uCAAC,UAAU,EAAE,SAAS,GAAG,UAAU,GAAG,CAAC,uBAAK,WAAW,WAAE;aACrF;YAED,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;YAElD,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE;gBACpB,OAAO,SAAS,CAAC,aAAa,CAAC;aAClC;SACJ;QAED,OAAO,UAAU,CAAC;IACtB,CAAC;IACL,qBAAC;AAAD,CAAC,AAnPD,IAmPC;AAnPY,wCAAc","sourcesContent":["import {\n createSelectionMarker,\n createText,\n isNodeOfType,\n setSelection,\n} from 'roosterjs-content-model-dom';\nimport type {\n CacheSelection,\n ContentModelDocument,\n ContentModelParagraph,\n ContentModelSegment,\n ContentModelSelectionMarker,\n ContentModelTable,\n ContentModelTableRow,\n ContentModelText,\n DomIndexer,\n DOMSelection,\n RangeSelectionForCache,\n Selectable,\n} from 'roosterjs-content-model-types';\n\ninterface SegmentItem {\n paragraph: ContentModelParagraph;\n segments: ContentModelSegment[];\n}\n\ninterface TableItem {\n tableRows: ContentModelTableRow[];\n}\n\ninterface IndexedSegmentNode extends Node {\n __roosterjsContentModel: SegmentItem;\n}\n\ninterface IndexedTableElement extends HTMLTableElement {\n __roosterjsContentModel: TableItem;\n}\n\nfunction isIndexedSegment(node: Node): node is IndexedSegmentNode {\n const { paragraph, segments } = (node as IndexedSegmentNode).__roosterjsContentModel ?? {};\n\n return (\n paragraph &&\n paragraph.blockType == 'Paragraph' &&\n Array.isArray(paragraph.segments) &&\n Array.isArray(segments)\n );\n}\n\n/**\n * @internal\n * Implementation of DomIndexer\n */\nexport class DomIndexerImpl implements DomIndexer {\n constructor(public readonly persistCache?: boolean) {}\n\n onSegment(segmentNode: Node, paragraph: ContentModelParagraph, segment: ContentModelSegment[]) {\n const indexedText = segmentNode as IndexedSegmentNode;\n indexedText.__roosterjsContentModel = {\n paragraph,\n segments: segment,\n };\n }\n\n onParagraph(paragraphElement: HTMLElement) {\n let previousText: Text | null = null;\n\n for (let child = paragraphElement.firstChild; child; child = child.nextSibling) {\n if (isNodeOfType(child, 'TEXT_NODE')) {\n if (!previousText) {\n previousText = child;\n } else {\n const item = isIndexedSegment(previousText)\n ? previousText.__roosterjsContentModel\n : undefined;\n\n if (item && isIndexedSegment(child)) {\n item.segments = item.segments.concat(\n child.__roosterjsContentModel.segments\n );\n child.__roosterjsContentModel.segments = [];\n }\n }\n } else if (isNodeOfType(child, 'ELEMENT_NODE')) {\n previousText = null;\n\n this.onParagraph(child);\n } else {\n previousText = null;\n }\n }\n }\n\n onTable(tableElement: HTMLTableElement, table: ContentModelTable) {\n const indexedTable = tableElement as IndexedTableElement;\n indexedTable.__roosterjsContentModel = { tableRows: table.rows };\n }\n\n reconcileSelection(\n model: ContentModelDocument,\n newSelection: DOMSelection,\n oldSelection?: CacheSelection\n ): boolean {\n if (oldSelection) {\n if (\n oldSelection.type == 'range' &&\n this.isCollapsed(oldSelection) &&\n isNodeOfType(oldSelection.start.node, 'TEXT_NODE')\n ) {\n if (isIndexedSegment(oldSelection.start.node)) {\n this.reconcileTextSelection(oldSelection.start.node);\n }\n } else {\n setSelection(model);\n }\n }\n\n switch (newSelection.type) {\n case 'image':\n case 'table':\n // For image and table selection, we just clear the cached model since during selecting the element id might be changed\n return false;\n\n case 'range':\n const newRange = newSelection.range;\n if (newRange) {\n const {\n startContainer,\n startOffset,\n endContainer,\n endOffset,\n collapsed,\n } = newRange;\n\n delete model.hasRevertedRangeSelection;\n\n if (collapsed) {\n return !!this.reconcileNodeSelection(startContainer, startOffset);\n } else if (\n startContainer == endContainer &&\n isNodeOfType(startContainer, 'TEXT_NODE')\n ) {\n if (newSelection.isReverted) {\n model.hasRevertedRangeSelection = true;\n }\n\n return (\n isIndexedSegment(startContainer) &&\n !!this.reconcileTextSelection(startContainer, startOffset, endOffset)\n );\n } else {\n const marker1 = this.reconcileNodeSelection(startContainer, startOffset);\n const marker2 = this.reconcileNodeSelection(endContainer, endOffset);\n\n if (marker1 && marker2) {\n if (newSelection.isReverted) {\n model.hasRevertedRangeSelection = true;\n }\n\n setSelection(model, marker1, marker2);\n return true;\n } else {\n return false;\n }\n }\n }\n\n break;\n }\n\n return false;\n }\n\n private isCollapsed(selection: RangeSelectionForCache): boolean {\n const { start, end } = selection;\n\n return start.node == end.node && start.offset == end.offset;\n }\n\n private reconcileNodeSelection(node: Node, offset: number): Selectable | undefined {\n if (isNodeOfType(node, 'TEXT_NODE')) {\n return isIndexedSegment(node) ? this.reconcileTextSelection(node, offset) : undefined;\n } else if (offset >= node.childNodes.length) {\n return this.insertMarker(node.lastChild, true /*isAfter*/);\n } else {\n return this.insertMarker(node.childNodes[offset], false /*isAfter*/);\n }\n }\n\n private insertMarker(node: Node | null, isAfter: boolean): Selectable | undefined {\n let marker: ContentModelSelectionMarker | undefined;\n\n if (node && isIndexedSegment(node)) {\n const { paragraph, segments } = node.__roosterjsContentModel;\n const index = paragraph.segments.indexOf(segments[0]);\n\n if (index >= 0) {\n const formatSegment =\n (!isAfter && paragraph.segments[index - 1]) || paragraph.segments[index];\n marker = createSelectionMarker(formatSegment.format);\n\n paragraph.segments.splice(isAfter ? index + 1 : index, 0, marker);\n }\n }\n\n return marker;\n }\n\n private reconcileTextSelection(\n textNode: IndexedSegmentNode,\n startOffset?: number,\n endOffset?: number\n ) {\n const { paragraph, segments } = textNode.__roosterjsContentModel;\n const first = segments[0];\n const last = segments[segments.length - 1];\n let selectable: Selectable | undefined;\n\n if (first?.segmentType == 'Text' && last?.segmentType == 'Text') {\n const newSegments: ContentModelSegment[] = [];\n const txt = textNode.nodeValue || '';\n const textSegments: ContentModelText[] = [];\n\n if (startOffset === undefined) {\n first.text = txt;\n newSegments.push(first);\n textSegments.push(first);\n } else {\n if (startOffset > 0) {\n first.text = txt.substring(0, startOffset);\n newSegments.push(first);\n textSegments.push(first);\n }\n\n if (endOffset === undefined) {\n const marker = createSelectionMarker(first.format);\n newSegments.push(marker);\n\n selectable = marker;\n endOffset = startOffset;\n } else if (endOffset > startOffset) {\n const middle = createText(\n txt.substring(startOffset, endOffset),\n first.format,\n first.link,\n first.code\n );\n\n middle.isSelected = true;\n newSegments.push(middle);\n textSegments.push(middle);\n selectable = middle;\n }\n\n if (endOffset < txt.length) {\n const newLast = createText(\n txt.substring(endOffset),\n first.format,\n first.link,\n first.code\n );\n newSegments.push(newLast);\n textSegments.push(newLast);\n }\n }\n\n let firstIndex = paragraph.segments.indexOf(first);\n let lastIndex = paragraph.segments.indexOf(last);\n\n if (firstIndex >= 0 && lastIndex >= 0) {\n while (\n firstIndex > 0 &&\n paragraph.segments[firstIndex - 1].segmentType == 'SelectionMarker'\n ) {\n firstIndex--;\n }\n\n while (\n lastIndex < paragraph.segments.length - 1 &&\n paragraph.segments[lastIndex + 1].segmentType == 'SelectionMarker'\n ) {\n lastIndex++;\n }\n\n paragraph.segments.splice(firstIndex, lastIndex - firstIndex + 1, ...newSegments);\n }\n\n this.onSegment(textNode, paragraph, textSegments);\n\n if (!this.persistCache) {\n delete paragraph.cachedElement;\n }\n }\n\n return selectable;\n }\n}\n"]}
1
+ {"version":3,"file":"domIndexerImpl.js","sourceRoot":"","sources":["../../../../../packages/roosterjs-content-model-core/lib/corePlugin/cache/domIndexerImpl.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n EmptySegmentFormat,\n createSelectionMarker,\n createText,\n getObjectKeys,\n isNodeOfType,\n setSelection,\n} from 'roosterjs-content-model-dom';\nimport type {\n CacheSelection,\n ContentModelDocument,\n ContentModelParagraph,\n ContentModelSegment,\n ContentModelSegmentFormat,\n ContentModelSelectionMarker,\n ContentModelTable,\n ContentModelTableRow,\n ContentModelText,\n DomIndexer,\n DOMSelection,\n RangeSelectionForCache,\n Selectable,\n} from 'roosterjs-content-model-types';\n\n/**\n * @internal Export for test only\n */\nexport interface SegmentItem {\n paragraph: ContentModelParagraph;\n segments: ContentModelSegment[];\n}\n\n/**\n * @internal Export for test only\n */\nexport interface TableItem {\n tableRows: ContentModelTableRow[];\n}\n\n/**\n * @internal Export for test only\n */\nexport interface IndexedSegmentNode extends Node {\n __roosterjsContentModel: SegmentItem;\n}\n\n/**\n * @internal Export for test only\n */\nexport interface IndexedTableElement extends HTMLTableElement {\n __roosterjsContentModel: TableItem;\n}\n\n/**\n * Context object used by DomIndexer when reconcile mutations with child list\n */\ninterface ReconcileChildListContext {\n /**\n * Index of segment in current paragraph\n */\n segIndex: number;\n\n /**\n * The current paragraph that we are handling\n */\n paragraph?: ContentModelParagraph;\n\n /**\n * Text node that is added from mutation but has not been handled. This can happen when we first see an added node then later we see a removed one.\n * e.g. Type text in an empty paragraph (&lt;div&gt;&lt;br&gt;&lt;/div&gt;), so a text node will be added and &lt;BR&gt; will be removed.\n * Set to a valid text node means we need to handle it later. If it is finally not handled, that means we need to clear cache\n * Set to undefined (initial value) means no pending text node is hit yet (valid case)\n * Set to null means there was a pending text node which is already handled, so if we see another pending text node,\n * we should clear cache since we don't know how to handle it\n */\n pendingTextNode?: Text | null;\n\n /**\n * Format of the removed segment, this will be used as the format for newly created segment\n */\n format?: ContentModelSegmentFormat;\n}\n\nfunction isIndexedSegment(node: Node): node is IndexedSegmentNode {\n const { paragraph, segments } = (node as IndexedSegmentNode).__roosterjsContentModel ?? {};\n\n return (\n paragraph &&\n paragraph.blockType == 'Paragraph' &&\n Array.isArray(paragraph.segments) &&\n Array.isArray(segments)\n );\n}\n\nfunction getIndexedSegmentItem(node: Node | null): SegmentItem | null {\n return node && isIndexedSegment(node) ? node.__roosterjsContentModel : null;\n}\n\n/**\n * @internal\n * Implementation of DomIndexer\n */\nexport class DomIndexerImpl implements DomIndexer {\n constructor(private readonly persistCache?: boolean) {}\n\n onSegment(segmentNode: Node, paragraph: ContentModelParagraph, segment: ContentModelSegment[]) {\n const indexedText = segmentNode as IndexedSegmentNode;\n indexedText.__roosterjsContentModel = {\n paragraph,\n segments: segment,\n };\n }\n\n onParagraph(paragraphElement: HTMLElement) {\n let previousText: Text | null = null;\n\n for (let child = paragraphElement.firstChild; child; child = child.nextSibling) {\n if (isNodeOfType(child, 'TEXT_NODE')) {\n if (!previousText) {\n previousText = child;\n } else {\n const item = getIndexedSegmentItem(previousText);\n\n if (item && isIndexedSegment(child)) {\n item.segments = item.segments.concat(\n child.__roosterjsContentModel.segments\n );\n child.__roosterjsContentModel.segments = [];\n }\n }\n } else if (isNodeOfType(child, 'ELEMENT_NODE')) {\n previousText = null;\n\n this.onParagraph(child);\n } else {\n previousText = null;\n }\n }\n }\n\n onTable(tableElement: HTMLTableElement, table: ContentModelTable) {\n const indexedTable = tableElement as IndexedTableElement;\n indexedTable.__roosterjsContentModel = { tableRows: table.rows };\n }\n\n reconcileSelection(\n model: ContentModelDocument,\n newSelection: DOMSelection,\n oldSelection?: CacheSelection\n ): boolean {\n if (oldSelection) {\n if (\n oldSelection.type == 'range' &&\n this.isCollapsed(oldSelection) &&\n isNodeOfType(oldSelection.start.node, 'TEXT_NODE')\n ) {\n if (isIndexedSegment(oldSelection.start.node)) {\n this.reconcileTextSelection(oldSelection.start.node);\n }\n } else {\n setSelection(model);\n }\n }\n\n switch (newSelection.type) {\n case 'image':\n case 'table':\n // For image and table selection, we just clear the cached model since during selecting the element id might be changed\n return false;\n\n case 'range':\n const newRange = newSelection.range;\n if (newRange) {\n const {\n startContainer,\n startOffset,\n endContainer,\n endOffset,\n collapsed,\n } = newRange;\n\n delete model.hasRevertedRangeSelection;\n\n if (collapsed) {\n return !!this.reconcileNodeSelection(startContainer, startOffset);\n } else if (\n startContainer == endContainer &&\n isNodeOfType(startContainer, 'TEXT_NODE')\n ) {\n if (newSelection.isReverted) {\n model.hasRevertedRangeSelection = true;\n }\n\n return (\n isIndexedSegment(startContainer) &&\n !!this.reconcileTextSelection(startContainer, startOffset, endOffset)\n );\n } else {\n const marker1 = this.reconcileNodeSelection(startContainer, startOffset);\n const marker2 = this.reconcileNodeSelection(endContainer, endOffset);\n\n if (marker1 && marker2) {\n if (newSelection.isReverted) {\n model.hasRevertedRangeSelection = true;\n }\n\n setSelection(model, marker1, marker2);\n return true;\n } else {\n return false;\n }\n }\n }\n\n break;\n }\n\n return false;\n }\n\n reconcileChildList(addedNodes: ArrayLike<Node>, removedNodes: ArrayLike<Node>): boolean {\n if (!this.persistCache) {\n return false;\n }\n\n let canHandle = true;\n const context: ReconcileChildListContext = {\n segIndex: -1,\n };\n\n // First process added nodes\n const addedNode = addedNodes[0];\n\n if (addedNodes.length == 1 && isNodeOfType(addedNode, 'TEXT_NODE')) {\n canHandle = this.reconcileAddedNode(addedNode, context);\n } else if (addedNodes.length > 0) {\n canHandle = false;\n }\n\n // Second, process removed nodes\n const removedNode = removedNodes[0];\n\n if (canHandle && removedNodes.length == 1) {\n canHandle = this.reconcileRemovedNode(removedNode, context);\n } else if (removedNodes.length > 0) {\n canHandle = false;\n }\n\n return canHandle && !context.pendingTextNode;\n }\n\n private isCollapsed(selection: RangeSelectionForCache): boolean {\n const { start, end } = selection;\n\n return start.node == end.node && start.offset == end.offset;\n }\n\n private reconcileNodeSelection(node: Node, offset: number): Selectable | undefined {\n if (isNodeOfType(node, 'TEXT_NODE')) {\n return isIndexedSegment(node) ? this.reconcileTextSelection(node, offset) : undefined;\n } else if (offset >= node.childNodes.length) {\n return this.insertMarker(node.lastChild, true /*isAfter*/);\n } else {\n return this.insertMarker(node.childNodes[offset], false /*isAfter*/);\n }\n }\n\n private insertMarker(node: Node | null, isAfter: boolean): Selectable | undefined {\n let marker: ContentModelSelectionMarker | undefined;\n const segmentItem = node && getIndexedSegmentItem(node);\n\n if (segmentItem) {\n const { paragraph, segments } = segmentItem;\n const index = paragraph.segments.indexOf(segments[0]);\n\n if (index >= 0) {\n const formatSegment =\n (!isAfter && paragraph.segments[index - 1]) || paragraph.segments[index];\n marker = createSelectionMarker(formatSegment.format);\n\n paragraph.segments.splice(isAfter ? index + 1 : index, 0, marker);\n }\n }\n\n return marker;\n }\n\n private reconcileTextSelection(\n textNode: IndexedSegmentNode,\n startOffset?: number,\n endOffset?: number\n ) {\n const { paragraph, segments } = textNode.__roosterjsContentModel;\n const first = segments[0];\n const last = segments[segments.length - 1];\n let selectable: Selectable | undefined;\n\n if (first?.segmentType == 'Text' && last?.segmentType == 'Text') {\n const newSegments: ContentModelSegment[] = [];\n const txt = textNode.nodeValue || '';\n const textSegments: ContentModelText[] = [];\n\n if (startOffset === undefined) {\n first.text = txt;\n newSegments.push(first);\n textSegments.push(first);\n } else {\n if (startOffset > 0) {\n first.text = txt.substring(0, startOffset);\n newSegments.push(first);\n textSegments.push(first);\n }\n\n if (endOffset === undefined) {\n const marker = createSelectionMarker(first.format);\n newSegments.push(marker);\n\n selectable = marker;\n endOffset = startOffset;\n } else if (endOffset > startOffset) {\n const middle = createText(\n txt.substring(startOffset, endOffset),\n first.format,\n first.link,\n first.code\n );\n\n middle.isSelected = true;\n newSegments.push(middle);\n textSegments.push(middle);\n selectable = middle;\n }\n\n if (endOffset < txt.length) {\n const newLast = createText(\n txt.substring(endOffset),\n first.format,\n first.link,\n first.code\n );\n newSegments.push(newLast);\n textSegments.push(newLast);\n }\n }\n\n let firstIndex = paragraph.segments.indexOf(first);\n let lastIndex = paragraph.segments.indexOf(last);\n\n if (firstIndex >= 0 && lastIndex >= 0) {\n while (\n firstIndex > 0 &&\n paragraph.segments[firstIndex - 1].segmentType == 'SelectionMarker'\n ) {\n firstIndex--;\n }\n\n while (\n lastIndex < paragraph.segments.length - 1 &&\n paragraph.segments[lastIndex + 1].segmentType == 'SelectionMarker'\n ) {\n lastIndex++;\n }\n\n paragraph.segments.splice(firstIndex, lastIndex - firstIndex + 1, ...newSegments);\n }\n\n this.onSegment(textNode, paragraph, textSegments);\n\n if (!this.persistCache) {\n delete paragraph.cachedElement;\n }\n }\n\n return selectable;\n }\n\n private reconcileAddedNode(node: Text, context: ReconcileChildListContext): boolean {\n let segmentItem: SegmentItem | null = null;\n let index = -1;\n let existingSegment: ContentModelSegment;\n const { previousSibling, nextSibling } = node;\n\n if (\n (segmentItem = getIndexedSegmentItem(previousSibling)) &&\n (existingSegment = segmentItem.segments[segmentItem.segments.length - 1]) &&\n (index = segmentItem.paragraph.segments.indexOf(existingSegment)) >= 0\n ) {\n // When we can find indexed segment before current one, use it as the insert index\n this.indexNode(segmentItem.paragraph, index + 1, node, existingSegment.format);\n } else if (\n (segmentItem = getIndexedSegmentItem(nextSibling)) &&\n (existingSegment = segmentItem.segments[0]) &&\n (index = segmentItem.paragraph.segments.indexOf(existingSegment)) >= 0\n ) {\n // When we can find indexed segment after current one, use it as the insert index\n this.indexNode(segmentItem.paragraph, index, node, existingSegment.format);\n } else if (context.paragraph && context.segIndex >= 0) {\n // When there is indexed paragraph from removed nodes, we can use it as the insert index\n this.indexNode(context.paragraph, context.segIndex, node, context.format);\n } else if (context.pendingTextNode === undefined) {\n // When we can't find the insert index, set current node as pending node\n // so later we can pick it up when we have enough info when processing removed node\n // Only do this when pendingTextNode is undefined. If it is null it means there was already a pending node before\n // and in that case we should return false since we can't handle two pending text node\n context.pendingTextNode = node;\n } else {\n return false;\n }\n\n return true;\n }\n\n private reconcileRemovedNode(node: Node, context: ReconcileChildListContext): boolean {\n let segmentItem: SegmentItem | null = null;\n let removingSegment: ContentModelSegment;\n\n if (\n context.segIndex < 0 &&\n !context.paragraph && // No previous removed segment or related paragraph found, and\n (segmentItem = getIndexedSegmentItem(node)) && // The removed node is indexed, and\n (removingSegment = segmentItem.segments[0]) // There is at least one related segment\n ) {\n // Now we can remove the indexed segment from the paragraph, and remember it, later we may need to use it\n context.format = removingSegment.format;\n context.paragraph = segmentItem.paragraph;\n context.segIndex = segmentItem.paragraph.segments.indexOf(segmentItem.segments[0]);\n\n if (context.segIndex < 0) {\n // Indexed segment is not under paragraph, something wrong happens, we cannot keep handling\n return false;\n }\n\n for (let i = 0; i < segmentItem.segments.length; i++) {\n const index = segmentItem.paragraph.segments.indexOf(segmentItem.segments[i]);\n\n if (index >= 0) {\n segmentItem.paragraph.segments.splice(index, 1);\n }\n }\n\n if (context.pendingTextNode) {\n // If we have pending text node added but not indexed, do it now\n this.indexNode(\n context.paragraph,\n context.segIndex,\n context.pendingTextNode,\n segmentItem.segments[0].format\n );\n\n // Set to null since we have processed it.\n // Next time we see a pending node we know we have already processed one so it is a situation we cannot handle\n context.pendingTextNode = null;\n }\n\n return true;\n } else {\n return false;\n }\n }\n\n private indexNode(\n paragraph: ContentModelParagraph,\n index: number,\n textNode: Text,\n format?: ContentModelSegmentFormat\n ) {\n const copiedFormat = format ? { ...format } : undefined;\n\n if (copiedFormat) {\n getObjectKeys(copiedFormat).forEach(key => {\n if (EmptySegmentFormat[key] === undefined) {\n delete copiedFormat[key];\n }\n });\n }\n\n const text = createText(textNode.textContent ?? '', copiedFormat);\n\n paragraph.segments.splice(index, 0, text);\n this.onSegment(textNode, paragraph, [text]);\n }\n}\n"]}
@@ -1,5 +1,5 @@
1
- import type { TextMutationObserver } from 'roosterjs-content-model-types';
1
+ import type { ContentModelDocument, DomIndexer, TextMutationObserver } from 'roosterjs-content-model-types';
2
2
  /**
3
3
  * @internal
4
4
  */
5
- export declare function createTextMutationObserver(contentDiv: HTMLDivElement, onMutation: (isTextChangeOnly: boolean) => void): TextMutationObserver;
5
+ export declare function createTextMutationObserver(contentDiv: HTMLDivElement, domIndexer: DomIndexer, onMutation: (isTextChangeOnly: boolean) => void, onSkipMutation: (newModel: ContentModelDocument) => void): TextMutationObserver;
@@ -2,16 +2,60 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createTextMutationObserver = void 0;
4
4
  var TextMutationObserverImpl = /** @class */ (function () {
5
- function TextMutationObserverImpl(contentDiv, onMutation) {
5
+ function TextMutationObserverImpl(contentDiv, domIndexer, onMutation, onSkipMutation) {
6
6
  var _this = this;
7
7
  this.contentDiv = contentDiv;
8
+ this.domIndexer = domIndexer;
8
9
  this.onMutation = onMutation;
10
+ this.onSkipMutation = onSkipMutation;
9
11
  this.onMutationInternal = function (mutations) {
10
- var _a;
11
- var firstTarget = (_a = mutations[0]) === null || _a === void 0 ? void 0 : _a.target;
12
- if (firstTarget) {
13
- var isTextChangeOnly = mutations.every(function (mutation) { return mutation.type == 'characterData' && mutation.target == firstTarget; });
14
- _this.onMutation(isTextChangeOnly);
12
+ var canHandle = true;
13
+ var firstTarget = null;
14
+ var lastTextChangeNode = null;
15
+ var addedNodes = [];
16
+ var removedNodes = [];
17
+ var reconcileText = false;
18
+ for (var i = 0; i < mutations.length && canHandle; i++) {
19
+ var mutation = mutations[i];
20
+ switch (mutation.type) {
21
+ case 'attributes':
22
+ if (mutation.target != _this.contentDiv) {
23
+ // We cannot handle attributes changes on editor content for now
24
+ canHandle = false;
25
+ }
26
+ break;
27
+ case 'characterData':
28
+ if (lastTextChangeNode && lastTextChangeNode != mutation.target) {
29
+ // Multiple text nodes got changed, we don't know how to handle it
30
+ canHandle = false;
31
+ }
32
+ else {
33
+ lastTextChangeNode = mutation.target;
34
+ reconcileText = true;
35
+ }
36
+ break;
37
+ case 'childList':
38
+ if (!firstTarget) {
39
+ firstTarget = mutation.target;
40
+ }
41
+ else if (firstTarget != mutation.target) {
42
+ canHandle = false;
43
+ }
44
+ if (canHandle) {
45
+ addedNodes = addedNodes.concat(Array.from(mutation.addedNodes));
46
+ removedNodes = removedNodes.concat(Array.from(mutation.removedNodes));
47
+ }
48
+ break;
49
+ }
50
+ }
51
+ if (canHandle && (addedNodes.length > 0 || removedNodes.length > 0)) {
52
+ canHandle = _this.domIndexer.reconcileChildList(addedNodes, removedNodes);
53
+ }
54
+ if (canHandle && reconcileText) {
55
+ _this.onMutation(true /*textOnly*/);
56
+ }
57
+ else if (!canHandle) {
58
+ _this.onMutation(false /*textOnly*/);
15
59
  }
16
60
  };
17
61
  this.observer = new MutationObserver(this.onMutationInternal);
@@ -27,17 +71,22 @@ var TextMutationObserverImpl = /** @class */ (function () {
27
71
  TextMutationObserverImpl.prototype.stopObserving = function () {
28
72
  this.observer.disconnect();
29
73
  };
30
- TextMutationObserverImpl.prototype.flushMutations = function () {
74
+ TextMutationObserverImpl.prototype.flushMutations = function (model) {
31
75
  var mutations = this.observer.takeRecords();
32
- this.onMutationInternal(mutations);
76
+ if (model) {
77
+ this.onSkipMutation(model);
78
+ }
79
+ else {
80
+ this.onMutationInternal(mutations);
81
+ }
33
82
  };
34
83
  return TextMutationObserverImpl;
35
84
  }());
36
85
  /**
37
86
  * @internal
38
87
  */
39
- function createTextMutationObserver(contentDiv, onMutation) {
40
- return new TextMutationObserverImpl(contentDiv, onMutation);
88
+ function createTextMutationObserver(contentDiv, domIndexer, onMutation, onSkipMutation) {
89
+ return new TextMutationObserverImpl(contentDiv, domIndexer, onMutation, onSkipMutation);
41
90
  }
42
91
  exports.createTextMutationObserver = createTextMutationObserver;
43
92
  //# sourceMappingURL=textMutationObserver.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"textMutationObserver.js","sourceRoot":"","sources":["../../../../../packages/roosterjs-content-model-core/lib/corePlugin/cache/textMutationObserver.ts"],"names":[],"mappings":";;;AAEA;IAGI,kCACY,UAA0B,EAC1B,UAA+C;QAF3D,iBAKC;QAJW,eAAU,GAAV,UAAU,CAAgB;QAC1B,eAAU,GAAV,UAAU,CAAqC;QAwBnD,uBAAkB,GAAG,UAAC,SAA2B;;YACrD,IAAM,WAAW,GAAG,MAAA,SAAS,CAAC,CAAC,CAAC,0CAAE,MAAM,CAAC;YAEzC,IAAI,WAAW,EAAE;gBACb,IAAM,gBAAgB,GAAG,SAAS,CAAC,KAAK,CACpC,UAAA,QAAQ,IAAI,OAAA,QAAQ,CAAC,IAAI,IAAI,eAAe,IAAI,QAAQ,CAAC,MAAM,IAAI,WAAW,EAAlE,CAAkE,CACjF,CAAC;gBAEF,KAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;aACrC;QACL,CAAC,CAAC;QAhCE,IAAI,CAAC,QAAQ,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAClE,CAAC;IAED,iDAAc,GAAd;QACI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE;YACnC,OAAO,EAAE,IAAI;YACb,SAAS,EAAE,IAAI;YACf,UAAU,EAAE,IAAI;YAChB,aAAa,EAAE,IAAI;SACtB,CAAC,CAAC;IACP,CAAC;IAED,gDAAa,GAAb;QACI,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;IAC/B,CAAC;IAED,iDAAc,GAAd;QACI,IAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;QAE9C,IAAI,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC;IACvC,CAAC;IAaL,+BAAC;AAAD,CAAC,AAxCD,IAwCC;AAED;;GAEG;AACH,SAAgB,0BAA0B,CACtC,UAA0B,EAC1B,UAA+C;IAE/C,OAAO,IAAI,wBAAwB,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;AAChE,CAAC;AALD,gEAKC","sourcesContent":["import type { TextMutationObserver } from 'roosterjs-content-model-types';\n\nclass TextMutationObserverImpl implements TextMutationObserver {\n private observer: MutationObserver;\n\n constructor(\n private contentDiv: HTMLDivElement,\n private onMutation: (isTextChangeOnly: boolean) => void\n ) {\n this.observer = new MutationObserver(this.onMutationInternal);\n }\n\n startObserving() {\n this.observer.observe(this.contentDiv, {\n subtree: true,\n childList: true,\n attributes: true,\n characterData: true,\n });\n }\n\n stopObserving() {\n this.observer.disconnect();\n }\n\n flushMutations() {\n const mutations = this.observer.takeRecords();\n\n this.onMutationInternal(mutations);\n }\n\n private onMutationInternal = (mutations: MutationRecord[]) => {\n const firstTarget = mutations[0]?.target;\n\n if (firstTarget) {\n const isTextChangeOnly = mutations.every(\n mutation => mutation.type == 'characterData' && mutation.target == firstTarget\n );\n\n this.onMutation(isTextChangeOnly);\n }\n };\n}\n\n/**\n * @internal\n */\nexport function createTextMutationObserver(\n contentDiv: HTMLDivElement,\n onMutation: (isTextChangeOnly: boolean) => void\n): TextMutationObserver {\n return new TextMutationObserverImpl(contentDiv, onMutation);\n}\n"]}
1
+ {"version":3,"file":"textMutationObserver.js","sourceRoot":"","sources":["../../../../../packages/roosterjs-content-model-core/lib/corePlugin/cache/textMutationObserver.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ContentModelDocument,\n DomIndexer,\n TextMutationObserver,\n} from 'roosterjs-content-model-types';\n\nclass TextMutationObserverImpl implements TextMutationObserver {\n private observer: MutationObserver;\n\n constructor(\n private contentDiv: HTMLDivElement,\n private domIndexer: DomIndexer,\n private onMutation: (isTextChangeOnly: boolean) => void,\n private onSkipMutation: (newModel: ContentModelDocument) => void\n ) {\n this.observer = new MutationObserver(this.onMutationInternal);\n }\n\n startObserving() {\n this.observer.observe(this.contentDiv, {\n subtree: true,\n childList: true,\n attributes: true,\n characterData: true,\n });\n }\n\n stopObserving() {\n this.observer.disconnect();\n }\n\n flushMutations(model: ContentModelDocument) {\n const mutations = this.observer.takeRecords();\n\n if (model) {\n this.onSkipMutation(model);\n } else {\n this.onMutationInternal(mutations);\n }\n }\n\n private onMutationInternal = (mutations: MutationRecord[]) => {\n let canHandle = true;\n let firstTarget: Node | null = null;\n let lastTextChangeNode: Node | null = null;\n let addedNodes: Node[] = [];\n let removedNodes: Node[] = [];\n let reconcileText = false;\n\n for (let i = 0; i < mutations.length && canHandle; i++) {\n const mutation = mutations[i];\n\n switch (mutation.type) {\n case 'attributes':\n if (mutation.target != this.contentDiv) {\n // We cannot handle attributes changes on editor content for now\n canHandle = false;\n }\n break;\n\n case 'characterData':\n if (lastTextChangeNode && lastTextChangeNode != mutation.target) {\n // Multiple text nodes got changed, we don't know how to handle it\n canHandle = false;\n } else {\n lastTextChangeNode = mutation.target;\n reconcileText = true;\n }\n break;\n\n case 'childList':\n if (!firstTarget) {\n firstTarget = mutation.target;\n } else if (firstTarget != mutation.target) {\n canHandle = false;\n }\n\n if (canHandle) {\n addedNodes = addedNodes.concat(Array.from(mutation.addedNodes));\n removedNodes = removedNodes.concat(Array.from(mutation.removedNodes));\n }\n\n break;\n }\n }\n\n if (canHandle && (addedNodes.length > 0 || removedNodes.length > 0)) {\n canHandle = this.domIndexer.reconcileChildList(addedNodes, removedNodes);\n }\n\n if (canHandle && reconcileText) {\n this.onMutation(true /*textOnly*/);\n } else if (!canHandle) {\n this.onMutation(false /*textOnly*/);\n }\n };\n}\n\n/**\n * @internal\n */\nexport function createTextMutationObserver(\n contentDiv: HTMLDivElement,\n domIndexer: DomIndexer,\n onMutation: (isTextChangeOnly: boolean) => void,\n onSkipMutation: (newModel: ContentModelDocument) => void\n): TextMutationObserver {\n return new TextMutationObserverImpl(contentDiv, domIndexer, onMutation, onSkipMutation);\n}\n"]}