From de5389c41ec1d99cf2a2f6baf95e2c48ee8890b1 Mon Sep 17 00:00:00 2001 From: jiuqingsong Date: Wed, 22 Apr 2026 11:15:51 -0700 Subject: [PATCH] Graduate some experimental features --- .../editorOptions/EditorOptionsPlugin.ts | 7 +-- .../editorOptions/ExperimentalFeatures.tsx | 3 -- .../sidePane/editorOptions/Plugins.tsx | 8 ---- .../command/exportContent/exportContent.ts | 10 ++-- .../createEditorContext.ts | 1 - .../lib/editor/core/DOMHelperImpl.ts | 15 +++--- .../lib/editor/core/createEditorCore.ts | 4 +- .../createEditorContextTest.ts | 7 --- .../test/editor/core/DOMHelperImplTest.ts | 18 +------ .../handlers/handleBlockGroupChildren.ts | 2 +- .../lib/modelToDom/handlers/handleList.ts | 22 +++------ .../lib/modelToDom/handlers/handleListItem.ts | 7 +-- .../handlers/handleBlockGroupChildrenTest.ts | 1 - .../modelToDom/handlers/handleListItemTest.ts | 1 - .../modelToDom/handlers/handleListTest.ts | 10 +--- .../lib/edit/EditOptions.ts | 1 + .../lib/edit/EditPlugin.ts | 26 +--------- .../lib/edit/keyboardEnter.ts | 14 +----- .../test/edit/EditPluginTest.ts | 45 +++++------------- .../edit/inputSteps/handleEnterOnListTest.ts | 2 +- .../test/edit/keyboardEnterTest.ts | 11 ++--- .../lib/context/EditorContext.ts | 1 + .../lib/editor/ExperimentalFeature.ts | 47 ++++++++++--------- .../lib/editor/EditorAdapter.ts | 8 +--- 24 files changed, 76 insertions(+), 195 deletions(-) diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/EditorOptionsPlugin.ts b/demo/scripts/controlsV2/sidePane/editorOptions/EditorOptionsPlugin.ts index 319412e4e6c2..325b199093da 100644 --- a/demo/scripts/controlsV2/sidePane/editorOptions/EditorOptionsPlugin.ts +++ b/demo/scripts/controlsV2/sidePane/editorOptions/EditorOptionsPlugin.ts @@ -70,12 +70,7 @@ const initialState: OptionState = { }, customReplacements: emojiReplacements, disableSideResize: false, - experimentalFeatures: new Set([ - 'HandleEnterKey', - 'CloneIndependentRoot', - 'CacheList', - 'TransformTableBorderColors', - ]), + experimentalFeatures: new Set(['TransformTableBorderColors']), }; export class EditorOptionsPlugin extends SidePanePluginImpl { diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/ExperimentalFeatures.tsx b/demo/scripts/controlsV2/sidePane/editorOptions/ExperimentalFeatures.tsx index dc9aa65739af..7de49ba7833f 100644 --- a/demo/scripts/controlsV2/sidePane/editorOptions/ExperimentalFeatures.tsx +++ b/demo/scripts/controlsV2/sidePane/editorOptions/ExperimentalFeatures.tsx @@ -11,10 +11,7 @@ export class ExperimentalFeatures extends React.Component - {this.renderFeature('HandleEnterKey')} {this.renderFeature('KeepSelectionMarkerWhenEnteringTextNode')} - {this.renderFeature('CloneIndependentRoot')} - {this.renderFeature('CacheList')} {this.renderFeature('TransformTableBorderColors')} ); diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/Plugins.tsx b/demo/scripts/controlsV2/sidePane/editorOptions/Plugins.tsx index 44022a59ccb0..50d9698b3de9 100644 --- a/demo/scripts/controlsV2/sidePane/editorOptions/Plugins.tsx +++ b/demo/scripts/controlsV2/sidePane/editorOptions/Plugins.tsx @@ -99,7 +99,6 @@ abstract class PluginsBase extends Re export class Plugins extends PluginsBase { private allowExcelNoBorderTable = React.createRef(); private handleTabKey = React.createRef(); - private handleEnterKey = React.createRef(); private listMenu = React.createRef(); private tableMenu = React.createRef(); private imageMenu = React.createRef(); @@ -240,13 +239,6 @@ export class Plugins extends PluginsBase { (state, value) => (state.editPluginOptions.handleTabKey.indentParagraph = value) )} - {this.renderCheckBox( - 'Handle Enter Key', - this.handleEnterKey, - this.props.state.editPluginOptions.shouldHandleEnterKey as boolean, - (state, value) => - (state.editPluginOptions.shouldHandleEnterKey = value) - )} )} {this.renderPluginItem( diff --git a/packages/roosterjs-content-model-core/lib/command/exportContent/exportContent.ts b/packages/roosterjs-content-model-core/lib/command/exportContent/exportContent.ts index 47f07595c8a8..2296099c2df2 100644 --- a/packages/roosterjs-content-model-core/lib/command/exportContent/exportContent.ts +++ b/packages/roosterjs-content-model-core/lib/command/exportContent/exportContent.ts @@ -15,7 +15,7 @@ import type { /** * Export HTML content. If there are entities, this will cause EntityOperation event with option = 'replaceTemporaryContent' to get a dehydrated entity * @param editor The editor to get content from - * @param mode Specify HTML to get HTML. This is the default option + * @param mode Specify HTML to get HTML. * @param options @optional Options for Model to DOM conversion */ export function exportContent(editor: IEditor, mode?: 'HTML', options?: ModelToDomOption): string; @@ -24,9 +24,9 @@ export function exportContent(editor: IEditor, mode?: 'HTML', options?: ModelToD * Export HTML content. If there are entities, this will cause EntityOperation event with option = 'replaceTemporaryContent' to get a dehydrated entity. * This is a fast version, it retrieve HTML content directly from editor without going through content model conversion. * @param editor The editor to get content from - * @param mode Specify HTMLFast to get HTML result. + * @param mode Specify HTMLFast to get HTML result. This is the default option */ -export function exportContent(editor: IEditor, mode: 'HTMLFast'): string; +export function exportContent(editor: IEditor, mode?: 'HTMLFast'): string; /** * Export plain text content @@ -52,7 +52,7 @@ export function exportContent(editor: IEditor, mode: 'PlainTextFast'): string; // Once we are confident that 'HTMLFast' is stable, we can fully switch 'HTML' to use the 'HTMLFast' approach export function exportContent( editor: IEditor, - mode: ExportContentMode | 'HTMLFast' = 'HTML', + mode: ExportContentMode | 'HTMLFast' = 'HTMLFast', optionsOrCallbacks?: ModelToDomOption | ModelToTextCallbacks ): string { let model: ContentModelDocument; @@ -70,6 +70,7 @@ export function exportContent( ); case 'HTMLFast': + default: const clonedRoot = editor.getDOMHelper().getClonedRoot(); if (editor.isDarkMode()) { @@ -89,7 +90,6 @@ export function exportContent( return getHTMLFromDOM(editor, clonedRoot); case 'HTML': - default: model = editor.getContentModelCopy('clean'); const doc = editor.getDocument(); diff --git a/packages/roosterjs-content-model-core/lib/coreApi/createEditorContext/createEditorContext.ts b/packages/roosterjs-content-model-core/lib/coreApi/createEditorContext/createEditorContext.ts index 9a2983ae6601..20f38f2c89cc 100644 --- a/packages/roosterjs-content-model-core/lib/coreApi/createEditorContext/createEditorContext.ts +++ b/packages/roosterjs-content-model-core/lib/coreApi/createEditorContext/createEditorContext.ts @@ -17,7 +17,6 @@ export const createEditorContext: CreateEditorContext = (core, saveIndex) => { darkColorHandler: darkColorHandler, addDelimiterForEntity: true, allowCacheElement: true, - allowCacheListItem: !!core.experimentalFeatures?.includes('CacheList'), domIndexer: saveIndex ? cache.domIndexer : undefined, zoomScale: domHelper.calculateZoomScale(), experimentalFeatures: core.experimentalFeatures ?? [], diff --git a/packages/roosterjs-content-model-core/lib/editor/core/DOMHelperImpl.ts b/packages/roosterjs-content-model-core/lib/editor/core/DOMHelperImpl.ts index def7bec62747..016d5a8240b4 100644 --- a/packages/roosterjs-content-model-core/lib/editor/core/DOMHelperImpl.ts +++ b/packages/roosterjs-content-model-core/lib/editor/core/DOMHelperImpl.ts @@ -16,11 +16,14 @@ import type { * @internal */ export interface DOMHelperImplOption { + /** + * @deprecated This is always treated as true now + */ cloneIndependentRoot?: boolean; } class DOMHelperImpl implements DOMHelper { - constructor(private contentDiv: HTMLElement, private options: DOMHelperImplOption) {} + constructor(private contentDiv: HTMLElement, options?: DOMHelperImplOption) {} queryElements(selector: string): HTMLElement[] { return toArray(this.contentDiv.querySelectorAll(selector)) as HTMLElement[]; @@ -123,14 +126,10 @@ class DOMHelperImpl implements DOMHelper { * Get a deep cloned root element */ getClonedRoot(): HTMLElement { - if (this.options.cloneIndependentRoot) { - const doc = this.contentDiv.ownerDocument.implementation.createHTMLDocument(); - const clone = doc.importNode(this.contentDiv, true /*deep*/); + const doc = this.contentDiv.ownerDocument.implementation.createHTMLDocument(); + const clone = doc.importNode(this.contentDiv, true /*deep*/); - return clone; - } else { - return this.contentDiv.cloneNode(true /*deep*/) as HTMLElement; - } + return clone; } /** diff --git a/packages/roosterjs-content-model-core/lib/editor/core/createEditorCore.ts b/packages/roosterjs-content-model-core/lib/editor/core/createEditorCore.ts index a15e5fbf9efb..96398a35276d 100644 --- a/packages/roosterjs-content-model-core/lib/editor/core/createEditorCore.ts +++ b/packages/roosterjs-content-model-core/lib/editor/core/createEditorCore.ts @@ -50,9 +50,7 @@ export function createEditorCore(contentDiv: HTMLDivElement, options: EditorOpti ? options.trustedHTMLHandler : createTrustedHTMLHandler(domCreator), domCreator: domCreator, - domHelper: createDOMHelper(contentDiv, { - cloneIndependentRoot: options.experimentalFeatures?.includes('CloneIndependentRoot'), - }), + domHelper: createDOMHelper(contentDiv), ...getPluginState(corePlugins), disposeErrorHandler: options.disposeErrorHandler, onFixUpModel: options.onFixUpModel, diff --git a/packages/roosterjs-content-model-core/test/coreApi/createEditorContext/createEditorContextTest.ts b/packages/roosterjs-content-model-core/test/coreApi/createEditorContext/createEditorContextTest.ts index 9e97fcc987c8..855e5b8e2364 100644 --- a/packages/roosterjs-content-model-core/test/coreApi/createEditorContext/createEditorContextTest.ts +++ b/packages/roosterjs-content-model-core/test/coreApi/createEditorContext/createEditorContextTest.ts @@ -51,7 +51,6 @@ describe('createEditorContext', () => { experimentalFeatures: [], paragraphMap: mockedParagraphMap, editorViewWidth: 800, - allowCacheListItem: false, }); }); @@ -104,7 +103,6 @@ describe('createEditorContext', () => { experimentalFeatures: [], paragraphMap: mockedParagraphMap, editorViewWidth: 800, - allowCacheListItem: false, }); }); @@ -154,7 +152,6 @@ describe('createEditorContext', () => { experimentalFeatures: [], paragraphMap: mockedParagraphMap, editorViewWidth: 800, - allowCacheListItem: false, }); }); @@ -207,7 +204,6 @@ describe('createEditorContext', () => { experimentalFeatures: [], paragraphMap: mockedParagraphMap, editorViewWidth: 800, - allowCacheListItem: false, }); }); }); @@ -266,7 +262,6 @@ describe('createEditorContext - checkZoomScale', () => { experimentalFeatures: [], paragraphMap: mockedParagraphMap, editorViewWidth: 800, - allowCacheListItem: false, }); }); }); @@ -325,7 +320,6 @@ describe('createEditorContext - checkRootDir', () => { experimentalFeatures: [], paragraphMap: mockedParagraphMap, editorViewWidth: 800, - allowCacheListItem: false, }); }); @@ -347,7 +341,6 @@ describe('createEditorContext - checkRootDir', () => { experimentalFeatures: [], paragraphMap: mockedParagraphMap, editorViewWidth: 800, - allowCacheListItem: false, }); }); }); diff --git a/packages/roosterjs-content-model-core/test/editor/core/DOMHelperImplTest.ts b/packages/roosterjs-content-model-core/test/editor/core/DOMHelperImplTest.ts index 8fa6f84eb29e..86da6ac65ad5 100644 --- a/packages/roosterjs-content-model-core/test/editor/core/DOMHelperImplTest.ts +++ b/packages/roosterjs-content-model-core/test/editor/core/DOMHelperImplTest.ts @@ -354,20 +354,6 @@ describe('DOMHelperImpl', () => { describe('getClonedRoot', () => { it('getClonedRoot', () => { - const mockedClone = 'CLONE' as any; - const cloneSpy = jasmine.createSpy('cloneSpy').and.returnValue(mockedClone); - const mockedDiv: HTMLElement = { - cloneNode: cloneSpy, - } as any; - const domHelper = createDOMHelper(mockedDiv); - - const result = domHelper.getClonedRoot(); - - expect(result).toBe(mockedClone); - expect(cloneSpy).toHaveBeenCalledWith(true); - }); - - it('getClonedRoot, with CloneIndependentRoot on', () => { const mockedClone = 'CLONE' as any; const cloneSpy = jasmine.createSpy('cloneSpy').and.returnValue(mockedClone); const importNodeSpy = jasmine.createSpy('importNodeSpy').and.returnValue(mockedClone); @@ -381,9 +367,7 @@ describe('DOMHelperImpl', () => { }, }, } as any; - const domHelper = createDOMHelper(mockedDiv, { - cloneIndependentRoot: true, - }); + const domHelper = createDOMHelper(mockedDiv); const result = domHelper.getClonedRoot(); diff --git a/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleBlockGroupChildren.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleBlockGroupChildren.ts index 8c459f464e55..a5114412de0f 100644 --- a/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleBlockGroupChildren.ts +++ b/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleBlockGroupChildren.ts @@ -54,7 +54,7 @@ export const handleBlockGroupChildren: ContentModelHandler 0) { + if (nodeStack.length > 0) { // Clear list stack, only run to nodeStack[1] because nodeStack[0] is the parent node for (let i = nodeStack.length - 1; i > 0; i--) { const node = nodeStack.pop()?.refNode ?? null; diff --git a/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleList.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleList.ts index 6ed248c629f6..ae6f190ac7b8 100644 --- a/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleList.ts +++ b/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleList.ts @@ -50,24 +50,18 @@ export const handleList: ContentModelBlockHandler = ( applyMetadata(itemLevel, context.metadataAppliers.listLevel, itemLevel.format, context); } - if ( - context.allowCacheListItem && - parentLevel.refNode && - itemLevel.cachedElement == parentLevel.refNode - ) { + if (parentLevel.refNode && itemLevel.cachedElement == parentLevel.refNode) { // Move refNode to next node since we are reusing this cached element parentLevel.refNode = parentLevel.refNode.nextSibling; } } // Cut off remained list levels that we can't reuse - if (context.allowCacheListItem) { - // Clean up all rest nodes in the reused list levels - for (let i = layer + 1; i < nodeStack.length; i++) { - const stackLevel = nodeStack[i]; + // Clean up all rest nodes in the reused list levels + for (let i = layer + 1; i < nodeStack.length; i++) { + const stackLevel = nodeStack[i]; - cleanUpRestNodes(stackLevel.refNode, context.rewriteFromModel); - } + cleanUpRestNodes(stackLevel.refNode, context.rewriteFromModel); } nodeStack.splice(layer + 1); @@ -83,7 +77,7 @@ export const handleList: ContentModelBlockHandler = ( context.listFormat.currentLevel = layer; - if (context.allowCacheListItem && level.cachedElement) { + if (level.cachedElement) { newList = level.cachedElement; nodeStack[layer].refNode = reuseCachedElement( @@ -112,9 +106,7 @@ export const handleList: ContentModelBlockHandler = ( dataset: { ...level.dataset }, }); - if (context.allowCacheListItem) { - level.cachedElement = newList; - } + level.cachedElement = newList; } applyFormat(newList, context.formatAppliers.listLevelThread, level.format, context); diff --git a/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleListItem.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleListItem.ts index 5f6ca79ce67e..1d6d51e42f43 100644 --- a/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleListItem.ts +++ b/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleListItem.ts @@ -36,7 +36,7 @@ export const handleListItem: ContentModelBlockHandler = ( let li: HTMLLIElement; let isNewlyCreated = false; - if (context.allowCacheListItem && listItem.cachedElement) { + if (listItem.cachedElement) { li = listItem.cachedElement; // Check if the cached LI is used as refNode under another list level, @@ -62,10 +62,7 @@ export const handleListItem: ContentModelBlockHandler = ( // This happens when outdent a list item to cause it has no list level listParent.insertBefore(li, itemRefNode?.parentNode == listParent ? itemRefNode : null); context.rewriteFromModel.addedBlockElements.push(li); - - if (context.allowCacheListItem) { - listItem.cachedElement = li; - } + listItem.cachedElement = li; } if (level) { diff --git a/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleBlockGroupChildrenTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleBlockGroupChildrenTest.ts index 9081f216f454..ed79cbfaa9a5 100644 --- a/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleBlockGroupChildrenTest.ts +++ b/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleBlockGroupChildrenTest.ts @@ -489,7 +489,6 @@ describe('handleBlockGroupChildren', () => { group.blocks.push(listItem, paragraph); context.listFormat.nodeStack = nodeStack; - context.allowCacheListItem = true; expect(parent.outerHTML).toBe('

'); diff --git a/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleListItemTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleListItemTest.ts index a473c59606fb..1d31b5764bf2 100644 --- a/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleListItemTest.ts +++ b/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleListItemTest.ts @@ -465,7 +465,6 @@ describe('handleListItem with cache', () => { listItem: { applierFunction: listItemMetadataApplier }, }, }); - context.allowCacheListItem = true; context.onNodeCreated = onNodeCreatedSpy; spyOn(applyFormat, 'applyFormat').and.callThrough(); }); diff --git a/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleListTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleListTest.ts index 4cf5be44d043..1e82b4a4fcf0 100644 --- a/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleListTest.ts +++ b/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleListTest.ts @@ -740,7 +740,6 @@ describe('handleList with cache', () => { ]); parent.appendChild(cachedUL); - context.allowCacheListItem = true; const newRefNode = handleList(document, parent, listItem, context, cachedUL); @@ -783,7 +782,6 @@ describe('handleList with cache', () => { listItem.levels[0].cachedElement = cachedUL; parent.appendChild(cachedUL); - context.allowCacheListItem = true; const newRefNode = handleList(document, parent, listItem, context, cachedUL); @@ -831,7 +829,6 @@ describe('handleList with cache', () => { listItem.levels[0].cachedElement = cachedUL; parent.appendChild(refNode); - context.allowCacheListItem = true; const newRefNode = handleList(document, parent, listItem, context, refNode); @@ -878,7 +875,6 @@ describe('handleList with cache', () => { listItem.levels[0].cachedElement = cachedUL; parent.appendChild(cachedUL); - context.allowCacheListItem = true; const newRefNode = handleList(document, parent, listItem, context, cachedUL); @@ -929,7 +925,7 @@ describe('handleList with cache', () => { listItem.levels[1].cachedElement = cachedUL; parent.appendChild(cachedOL); cachedOL.appendChild(cachedUL); - context.allowCacheListItem = true; + const newRefNode = handleList(document, parent, listItem, context, cachedUL); expect(parent.outerHTML).toBe('
    '); @@ -984,7 +980,7 @@ describe('handleList with cache', () => { parent.appendChild(refNode); listItem.levels[1].cachedElement = cachedUL; - context.allowCacheListItem = true; + const newRefNode = handleList(document, parent, listItem, context, refNode); expect(parent.outerHTML).toBe('

      '); @@ -1036,7 +1032,6 @@ describe('handleList with cache', () => { parent.appendChild(existingOL1); existingOL1.appendChild(existingLI); existingOL1.appendChild(existingOL2); - context.allowCacheListItem = true; context.listFormat.nodeStack = [ { node: parent, refNode: existingOL1 }, @@ -1090,7 +1085,6 @@ describe('handleList with cache', () => { ]); listItem.levels[0].cachedElement = existingOL1; - context.allowCacheListItem = true; context.listFormat.nodeStack = [ { node: parent, refNode: null }, diff --git a/packages/roosterjs-content-model-plugins/lib/edit/EditOptions.ts b/packages/roosterjs-content-model-plugins/lib/edit/EditOptions.ts index 86f814472a79..e5f85d5ae098 100644 --- a/packages/roosterjs-content-model-plugins/lib/edit/EditOptions.ts +++ b/packages/roosterjs-content-model-plugins/lib/edit/EditOptions.ts @@ -55,6 +55,7 @@ export type EditOptions = { handleExpandedSelectionOnDelete?: boolean; /** + * @deprecated This is always treated as true now * Callback function to determine whether the Rooster should handle the Enter key press. * If the function returns true, the Rooster will handle the Enter key press instead of the browser. * @param editor - The editor instance. diff --git a/packages/roosterjs-content-model-plugins/lib/edit/EditPlugin.ts b/packages/roosterjs-content-model-plugins/lib/edit/EditPlugin.ts index f2b1c8409eb8..6954ed6027c7 100644 --- a/packages/roosterjs-content-model-plugins/lib/edit/EditPlugin.ts +++ b/packages/roosterjs-content-model-plugins/lib/edit/EditPlugin.ts @@ -55,7 +55,6 @@ export class EditPlugin implements EditorPlugin { private disposer: (() => void) | null = null; private shouldHandleNextInputEvent = false; private selectionAfterDelete: DOMSelection | null = null; - private handleNormalEnter: (editor: IEditor) => boolean = () => false; private options: EditOptions & { handleTabKey: Required }; /** @@ -72,23 +71,6 @@ export class EditPlugin implements EditorPlugin { this.options = { ...DefaultOptions, ...options, handleTabKey: tabOptions }; } - private createNormalEnterChecker(result: boolean) { - return result ? () => true : () => false; - } - - private getHandleNormalEnter(editor: IEditor) { - switch (typeof this.options.shouldHandleEnterKey) { - case 'function': - return this.options.shouldHandleEnterKey; - case 'boolean': - return this.createNormalEnterChecker(this.options.shouldHandleEnterKey); - default: - return this.createNormalEnterChecker( - editor.isExperimentalFeatureEnabled('HandleEnterKey') - ); - } - } - /** * Get name of this plugin */ @@ -104,7 +86,6 @@ export class EditPlugin implements EditorPlugin { */ initialize(editor: IEditor) { this.editor = editor; - this.handleNormalEnter = this.getHandleNormalEnter(editor); if (editor.getEnvironment().isAndroid) { this.disposer = this.editor.attachDomEvent({ @@ -224,12 +205,7 @@ export class EditPlugin implements EditorPlugin { !event.rawEvent.isComposing && event.rawEvent.keyCode !== DEAD_KEY ) { - keyboardEnter( - editor, - rawEvent, - this.handleNormalEnter(editor), - this.options.formatsToPreserveOnMerge - ); + keyboardEnter(editor, rawEvent, this.options.formatsToPreserveOnMerge); } break; diff --git a/packages/roosterjs-content-model-plugins/lib/edit/keyboardEnter.ts b/packages/roosterjs-content-model-plugins/lib/edit/keyboardEnter.ts index 08b5c74d5cc0..2ee5581efb6e 100644 --- a/packages/roosterjs-content-model-plugins/lib/edit/keyboardEnter.ts +++ b/packages/roosterjs-content-model-plugins/lib/edit/keyboardEnter.ts @@ -8,7 +8,7 @@ import { normalizeContentModel, runEditSteps, } from 'roosterjs-content-model-dom'; -import type { IEditor, ReadonlyContentModelParagraph } from 'roosterjs-content-model-types'; +import type { IEditor } from 'roosterjs-content-model-types'; /** * @internal @@ -16,7 +16,6 @@ import type { IEditor, ReadonlyContentModelParagraph } from 'roosterjs-content-m export function keyboardEnter( editor: IEditor, rawEvent: KeyboardEvent, - handleNormalEnter: boolean, formatsToPreserveOnMerge: string[] = [] ) { const selection = editor.getDOMSelection(); @@ -36,9 +35,7 @@ export function keyboardEnter( ? [] : [handleAutoLink, handleEnterOnList, deleteEmptyQuote]; - if (handleNormalEnter || handleEnterForEntity(result.insertPoint?.paragraph)) { - steps.push(handleEnterOnParagraph(formatsToPreserveOnMerge)); - } + steps.push(handleEnterOnParagraph(formatsToPreserveOnMerge)); runEditSteps(steps, result); } @@ -64,10 +61,3 @@ export function keyboardEnter( } ); } - -function handleEnterForEntity(paragraph: ReadonlyContentModelParagraph | undefined) { - return ( - paragraph && - (paragraph.isImplicit || paragraph.segments.some(x => x.segmentType == 'Entity')) - ); -} diff --git a/packages/roosterjs-content-model-plugins/test/edit/EditPluginTest.ts b/packages/roosterjs-content-model-plugins/test/edit/EditPluginTest.ts index b687e5969f80..bf88bde843d8 100644 --- a/packages/roosterjs-content-model-plugins/test/edit/EditPluginTest.ts +++ b/packages/roosterjs-content-model-plugins/test/edit/EditPluginTest.ts @@ -20,7 +20,6 @@ describe('EditPlugin', () => { let eventMap: Record; let attachDOMEventSpy: jasmine.Spy; let getEnvironmentSpy: jasmine.Spy; - let isExperimentalFeatureEnabledSpy: jasmine.Spy; beforeEach(() => { attachDOMEventSpy = jasmine @@ -32,9 +31,6 @@ describe('EditPlugin', () => { getEnvironmentSpy = jasmine.createSpy('getEnvironment').and.returnValue({ isAndroid: true, }); - isExperimentalFeatureEnabledSpy = jasmine - .createSpy('isExperimentalFeatureEnabled') - .and.returnValue(false); editor = ({ attachDomEvent: attachDOMEventSpy, @@ -43,7 +39,6 @@ describe('EditPlugin', () => { ({ type: -1, } as any), // Force return invalid range to go through content model code - isExperimentalFeatureEnabled: isExperimentalFeatureEnabledSpy, } as any) as IEditor; }); @@ -122,7 +117,7 @@ describe('EditPlugin', () => { }); it('handleExpandedSelectionOnDelete with options', () => { - plugin = new EditPlugin({ shouldHandleEnterKey: true }); + plugin = new EditPlugin(); const rawEvent = { key: 'Delete' } as any; plugin.initialize(editor); @@ -135,7 +130,6 @@ describe('EditPlugin', () => { expect(keyboardDeleteSpy).toHaveBeenCalledWith(editor, rawEvent, { handleTabKey: DefaultHandleTabOptions, handleExpandedSelectionOnDelete: true, - shouldHandleEnterKey: true, }); }); @@ -191,9 +185,7 @@ describe('EditPlugin', () => { }); it('Tab - custom options with options', () => { - plugin = new EditPlugin({ - shouldHandleEnterKey: true, - }); + plugin = new EditPlugin(); const rawEvent = { key: 'Tab' } as any; plugin.initialize(editor); @@ -248,14 +240,11 @@ describe('EditPlugin', () => { expect(keyboardDeleteSpy).not.toHaveBeenCalled(); expect(keyboardInputSpy).not.toHaveBeenCalled(); - expect(keyboardEnterSpy).toHaveBeenCalledWith(editor, rawEvent, false, undefined); + expect(keyboardEnterSpy).toHaveBeenCalledWith(editor, rawEvent, undefined); expect(keyboardTabSpy).not.toHaveBeenCalled(); }); it('Enter, normal enter enabled with experimental feature', () => { - isExperimentalFeatureEnabledSpy.and.callFake( - (featureName: string) => featureName == 'HandleEnterKey' - ); plugin = new EditPlugin(); const rawEvent = { keyCode: 13, which: 13, key: 'Enter' } as any; const addUndoSnapshotSpy = jasmine.createSpy('addUndoSnapshot'); @@ -271,14 +260,12 @@ describe('EditPlugin', () => { expect(keyboardDeleteSpy).not.toHaveBeenCalled(); expect(keyboardInputSpy).not.toHaveBeenCalled(); - expect(keyboardEnterSpy).toHaveBeenCalledWith(editor, rawEvent, true, undefined); + expect(keyboardEnterSpy).toHaveBeenCalledWith(editor, rawEvent, undefined); expect(keyboardTabSpy).not.toHaveBeenCalled(); }); it('Enter, normal enter enabled', () => { - plugin = new EditPlugin({ - shouldHandleEnterKey: true, - }); + plugin = new EditPlugin(); const rawEvent = { keyCode: 13, which: 13, key: 'Enter' } as any; const addUndoSnapshotSpy = jasmine.createSpy('addUndoSnapshot'); @@ -293,16 +280,12 @@ describe('EditPlugin', () => { expect(keyboardDeleteSpy).not.toHaveBeenCalled(); expect(keyboardInputSpy).not.toHaveBeenCalled(); - expect(keyboardEnterSpy).toHaveBeenCalledWith(editor, rawEvent, true, undefined); + expect(keyboardEnterSpy).toHaveBeenCalledWith(editor, rawEvent, undefined); expect(keyboardTabSpy).not.toHaveBeenCalled(); }); it('Enter, normal enter enabled with callback', () => { - plugin = new EditPlugin({ - shouldHandleEnterKey: _editor => { - return true; - }, - }); + plugin = new EditPlugin(); const rawEvent = { keyCode: 13, which: 13, key: 'Enter' } as any; const addUndoSnapshotSpy = jasmine.createSpy('addUndoSnapshot'); @@ -317,7 +300,7 @@ describe('EditPlugin', () => { expect(keyboardDeleteSpy).not.toHaveBeenCalled(); expect(keyboardInputSpy).not.toHaveBeenCalled(); - expect(keyboardEnterSpy).toHaveBeenCalledWith(editor, rawEvent, true, undefined); + expect(keyboardEnterSpy).toHaveBeenCalledWith(editor, rawEvent, undefined); expect(keyboardTabSpy).not.toHaveBeenCalled(); }); @@ -495,10 +478,6 @@ describe('EditPlugin', () => { beforeEach(() => { keyboardEnterSpy = spyOn(keyboardEnter, 'keyboardEnter'); - // Configure editor to handle Enter key (needed for handleNormalEnter to return true) - isExperimentalFeatureEnabledSpy.and.callFake((feature: string) => { - return feature === 'HandleEnterKey'; - }); }); it('should pass formatsToPreserveOnMerge to keyboardEnter', () => { @@ -523,7 +502,7 @@ describe('EditPlugin', () => { }); expect(keyboardEnterSpy).toHaveBeenCalledTimes(1); - expect(keyboardEnterSpy).toHaveBeenCalledWith(editor, rawEvent, true, [ + expect(keyboardEnterSpy).toHaveBeenCalledWith(editor, rawEvent, [ 'className', 'fontFamily', ]); @@ -550,7 +529,7 @@ describe('EditPlugin', () => { }); expect(keyboardEnterSpy).toHaveBeenCalledTimes(1); - expect(keyboardEnterSpy).toHaveBeenCalledWith(editor, rawEvent, true, undefined); + expect(keyboardEnterSpy).toHaveBeenCalledWith(editor, rawEvent, undefined); }); it('should pass empty formatsToPreserveOnMerge array', () => { @@ -575,7 +554,7 @@ describe('EditPlugin', () => { }); expect(keyboardEnterSpy).toHaveBeenCalledTimes(1); - expect(keyboardEnterSpy).toHaveBeenCalledWith(editor, rawEvent, true, []); + expect(keyboardEnterSpy).toHaveBeenCalledWith(editor, rawEvent, []); }); it('should work with multiple custom format properties', () => { @@ -600,7 +579,7 @@ describe('EditPlugin', () => { }); expect(keyboardEnterSpy).toHaveBeenCalledTimes(1); - expect(keyboardEnterSpy).toHaveBeenCalledWith(editor, rawEvent, true, [ + expect(keyboardEnterSpy).toHaveBeenCalledWith(editor, rawEvent, [ 'className', 'customProp', 'data-testid', diff --git a/packages/roosterjs-content-model-plugins/test/edit/inputSteps/handleEnterOnListTest.ts b/packages/roosterjs-content-model-plugins/test/edit/inputSteps/handleEnterOnListTest.ts index c902d166c354..be234488ffd2 100644 --- a/packages/roosterjs-content-model-plugins/test/edit/inputSteps/handleEnterOnListTest.ts +++ b/packages/roosterjs-content-model-plugins/test/edit/inputSteps/handleEnterOnListTest.ts @@ -1929,7 +1929,7 @@ describe('handleEnterOnList - keyboardEnter', () => { }, }); - keyboardEnter(editor, mockedEvent, true); + keyboardEnter(editor, mockedEvent); }, input, expectedResult, diff --git a/packages/roosterjs-content-model-plugins/test/edit/keyboardEnterTest.ts b/packages/roosterjs-content-model-plugins/test/edit/keyboardEnterTest.ts index 02ced3f372c4..d8a5c9cf8892 100644 --- a/packages/roosterjs-content-model-plugins/test/edit/keyboardEnterTest.ts +++ b/packages/roosterjs-content-model-plugins/test/edit/keyboardEnterTest.ts @@ -61,7 +61,7 @@ describe('keyboardEnter', () => { expect(); }); - keyboardEnter(editor, rawEvent, true); + keyboardEnter(editor, rawEvent); expect(formatContentModelSpy).toHaveBeenCalledTimes(1); expect(input).toEqual(output); @@ -1396,7 +1396,7 @@ describe('keyboardEnter', () => { const runEditStepsSpy = spyOn(runEditSteps, 'runEditSteps'); - keyboardEnter(editor, {} as any, false, []); + keyboardEnter(editor, {} as any, []); expect(runEditStepsSpy).toHaveBeenCalledTimes(2); // Check that the second call to runEditSteps includes steps for handling entity @@ -1434,7 +1434,7 @@ describe('keyboardEnter', () => { const runEditStepsSpy = spyOn(runEditSteps, 'runEditSteps'); - keyboardEnter(editor, {} as any, false, []); + keyboardEnter(editor, {} as any, []); expect(runEditStepsSpy).toHaveBeenCalledTimes(2); expect( @@ -1479,7 +1479,6 @@ describe('keyboardEnter', () => { keyboardEnter( editor, { preventDefault: () => {}, shiftKey: false } as any, - true, formatsToPreserve ); @@ -1515,7 +1514,7 @@ describe('keyboardEnter', () => { callback(model, context); }); - keyboardEnter(editor, { preventDefault: () => {}, shiftKey: false } as any, true, []); + keyboardEnter(editor, { preventDefault: () => {}, shiftKey: false } as any, []); expect(formatContentModelSpy).toHaveBeenCalledTimes(1); }); @@ -1549,7 +1548,7 @@ describe('keyboardEnter', () => { callback(model, context); }); - keyboardEnter(editor, { preventDefault: () => {}, shiftKey: false } as any, true); + keyboardEnter(editor, { preventDefault: () => {}, shiftKey: false } as any); expect(formatContentModelSpy).toHaveBeenCalledTimes(1); }); diff --git a/packages/roosterjs-content-model-types/lib/context/EditorContext.ts b/packages/roosterjs-content-model-types/lib/context/EditorContext.ts index 4f7752faa4bb..ba11488e89bc 100644 --- a/packages/roosterjs-content-model-types/lib/context/EditorContext.ts +++ b/packages/roosterjs-content-model-types/lib/context/EditorContext.ts @@ -50,6 +50,7 @@ export interface EditorContext { allowCacheElement?: boolean; /** + * @deprecated This is now always be treated as true * Whether to allow caching list item elements separately. */ allowCacheListItem?: boolean; diff --git a/packages/roosterjs-content-model-types/lib/editor/ExperimentalFeature.ts b/packages/roosterjs-content-model-types/lib/editor/ExperimentalFeature.ts index 3120182d7ba9..4b4e7f3be7a2 100644 --- a/packages/roosterjs-content-model-types/lib/editor/ExperimentalFeature.ts +++ b/packages/roosterjs-content-model-types/lib/editor/ExperimentalFeature.ts @@ -18,15 +18,13 @@ export type GraduatedExperimentalFeature = * Prevent default browser behavior for copy/cut event, * and set the clipboard data with custom implementation. */ - | 'CustomCopyCut'; + | 'CustomCopyCut' -/** - * Predefined experiment features - * By default these features are not enabled. To enable them, pass the feature name into EditorOptions.experimentalFeatures - * when create editor - */ -export type ExperimentalFeature = - | GraduatedExperimentalFeature + /** + * @deprecated + * Export editor content as HTML using HTMLFast option + */ + | 'ExportHTMLFast' /** * @deprecated Please use the shouldHandleEnterKey option of the EditPlugin Options @@ -35,28 +33,33 @@ export type ExperimentalFeature = | 'HandleEnterKey' /** - * For CJK keyboard input on mobile, if the user toggles bold/italic/underline on an empty div, - * the pending format will be applied on the selection marker. When typing text, the selection moves to the text node and the - * selection marker may be recreated during reconciliation, potentially losing its original formatting. This feature ensures - * the original formatting of the selection marker is kept to match the pending format. - */ - | 'KeepSelectionMarkerWhenEnteringTextNode' - - /** - * Export editor content as HTML using HTMLFast option - */ - | 'ExportHTMLFast' - - /** + * @deprecated * Get cloned root element from an independent HTML document instead of current document. * So any operation to the cloned root won't trigger network request for resources like images */ | 'CloneIndependentRoot' /** + * @deprecated * Allow caching list item elements. */ - | 'CacheList' + | 'CacheList'; + +/** + * Predefined experiment features + * By default these features are not enabled. To enable them, pass the feature name into EditorOptions.experimentalFeatures + * when create editor + */ +export type ExperimentalFeature = + | GraduatedExperimentalFeature + + /** + * For CJK keyboard input on mobile, if the user toggles bold/italic/underline on an empty div, + * the pending format will be applied on the selection marker. When typing text, the selection moves to the text node and the + * selection marker may be recreated during reconciliation, potentially losing its original formatting. This feature ensures + * the original formatting of the selection marker is kept to match the pending format. + */ + | 'KeepSelectionMarkerWhenEnteringTextNode' /** * Transform the table border colors when switching from light to dark mode diff --git a/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts b/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts index 91b64831958a..45b3f2618371 100644 --- a/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts +++ b/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts @@ -354,13 +354,7 @@ export class EditorAdapter extends Editor implements ILegacyEditor { switch (exportMode) { case 'HTML': - return this.isExperimentalFeatureEnabled('ExportHTMLFast') - ? exportContent(this, 'HTMLFast') - : exportContent( - this, - 'HTML', - this.getCore().environment.modelToDomSettings.customized - ); + return exportContent(this, 'HTMLFast'); case 'PlainText': return exportContent(this, 'PlainText');