Skip to content

Commit

Permalink
Content Model: Move default format logic into ContentModelFormatPlugin (
Browse files Browse the repository at this point in the history
#2099)

* Content Model Customization refactor

* fix build

* improve

* Content Model Customization refactor 2: Add default config

* fix build

* Content Model: Persist cache 1

* fix build

* improve

* Content Model: Cache 2

* Fix test

* Fix build

* improve

* Improve

* improve

* Improve

* fix test

* Do not restore cached selection when call select

* Content Model: Add model into ContentChangedEvent

* fix build

* add demo site page

* Improve

* Content Model: pass out segment nodes

* cache 8

* fix build

* fix test

* Improve

* improve

* fix test

* Improve

* fix test

* Improve "checkDependency"

* Content Model: Clear table selection fix

* fix build

* Content Model: Improve adjustWordSelection

* add test

* add test

* fix test

* Graduate InlineEntityReadOnlyDelimiters

* fix build

* fix test

* Refactor format plugin

* fix test

* Improve

* fix build
  • Loading branch information
JiuqingSong authored Sep 28, 2023
1 parent be655c1 commit 24e280c
Show file tree
Hide file tree
Showing 16 changed files with 233 additions and 263 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import type {
import type { SelectionRangeEx } from 'roosterjs-editor-types';
import type {
ContentModelDocument,
ContentModelSegmentFormat,
DomToModelOption,
ModelToDomOption,
OnNodeCreated,
Expand Down Expand Up @@ -66,15 +65,4 @@ export default class ContentModelEditor

return core.api.setContentModel(core, model, option, onNodeCreated);
}

/**
* Get default format as ContentModelSegmentFormat.
* This is a replacement of IEditor.getDefaultFormat for Content Model.
* @returns The default format
*/
getContentModelDefaultFormat(): ContentModelSegmentFormat {
const core = this.getCore();

return core.defaultFormat;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import type { EditorContext } from 'roosterjs-content-model-types';
* Create a EditorContext object used by ContentModel API
*/
export const createEditorContext: CreateEditorContext = core => {
const { lifecycle, defaultFormat, darkColorHandler, contentDiv, cache } = core;
const { lifecycle, format, darkColorHandler, contentDiv, cache } = core;

const context: EditorContext = {
isDarkMode: lifecycle.isDarkMode,
defaultFormat: defaultFormat,
defaultFormat: format.defaultFormat,
darkColorHandler: darkColorHandler,
addDelimiterForEntity: true,
allowCacheElement: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import applyPendingFormat from '../../publicApi/format/applyPendingFormat';
import { canApplyPendingFormat, clearPendingFormat } from '../../modelApi/format/pendingFormat';
import { getObjectKeys, isCharacterValue } from 'roosterjs-editor-dom';
import { Keys, PluginEventType } from 'roosterjs-editor-types';
import type { EditorPlugin, IEditor, PluginEvent } from 'roosterjs-editor-types';
import type { IContentModelEditor } from '../../publicTypes/IContentModelEditor';
import type { IEditor, PluginEvent, PluginWithState } from 'roosterjs-editor-types';
import type { ContentModelFormatPluginState } from '../../publicTypes/pluginState/ContentModelFormatPluginState';

// During IME input, KeyDown event will have "Process" as key
const ProcessKey = 'Process';
Expand All @@ -14,10 +15,19 @@ const ProcessKey = 'Process';
* This includes:
* 1. Handle pending format changes when selection is collapsed
*/
export default class ContentModelFormatPlugin implements EditorPlugin {
export default class ContentModelFormatPlugin
implements PluginWithState<ContentModelFormatPluginState> {
private editor: IContentModelEditor | null = null;
private hasDefaultFormat = false;

/**
* Construct a new instance of ContentModelEditPlugin class
* @param state State of this plugin
*/
constructor(private state: ContentModelFormatPluginState) {
// TODO: Remove tempState parameter once we have standalone Content Model editor
}

/**
* Get name of this plugin
*/
Expand All @@ -34,11 +44,10 @@ export default class ContentModelFormatPlugin implements EditorPlugin {
initialize(editor: IEditor) {
// TODO: Later we may need a different interface for Content Model editor plugin
this.editor = editor as IContentModelEditor;

const defaultFormat = this.editor.getContentModelDefaultFormat();
this.hasDefaultFormat =
getObjectKeys(defaultFormat).filter(x => typeof defaultFormat[x] !== 'undefined')
.length > 0;
getObjectKeys(this.state.defaultFormat).filter(
x => typeof this.state.defaultFormat[x] !== 'undefined'
).length > 0;
}

/**
Expand All @@ -50,6 +59,13 @@ export default class ContentModelFormatPlugin implements EditorPlugin {
this.editor = null;
}

/**
* Get plugin state object
*/
getState(): ContentModelFormatPluginState {
return this.state;
}

/**
* Core method for a plugin. Once an event happens in editor, editor will call this
* method of each plugin to handle the event as long as the event is not handled
Expand Down Expand Up @@ -81,7 +97,7 @@ export default class ContentModelFormatPlugin implements EditorPlugin {
this.hasDefaultFormat &&
(isCharacterValue(event.rawEvent) || event.rawEvent.key == ProcessKey)
) {
applyDefaultFormat(this.editor);
applyDefaultFormat(this.editor, this.state.defaultFormat);
}

break;
Expand All @@ -108,6 +124,6 @@ export default class ContentModelFormatPlugin implements EditorPlugin {
* Create a new instance of ContentModelFormatPlugin.
* This is mostly for unit test
*/
export function createContentModelFormatPlugin() {
return new ContentModelFormatPlugin();
export function createContentModelFormatPlugin(state: ContentModelFormatPluginState) {
return new ContentModelFormatPlugin(state);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import ContentModelTypeInContainerPlugin from './corePlugins/ContentModelTypeInC
import { contentModelDomIndexer } from './utils/contentModelDomIndexer';
import { createContentModel } from './coreApi/createContentModel';
import { createContentModelCachePlugin } from './corePlugins/ContentModelCachePlugin';
import { createContentModelEditPlugin } from './plugins/ContentModelEditPlugin';
import { createContentModelFormatPlugin } from './plugins/ContentModelFormatPlugin';
import { createContentModelEditPlugin } from './corePlugins/ContentModelEditPlugin';
import { createContentModelFormatPlugin } from './corePlugins/ContentModelFormatPlugin';
import { createDomToModelConfig, createModelToDomConfig } from 'roosterjs-content-model-dom';
import { createEditorContext } from './coreApi/createEditorContext';
import { createEditorCore, isFeatureEnabled } from 'roosterjs-editor-core';
Expand All @@ -16,7 +16,6 @@ import { tablePreProcessor } from './overrides/tablePreProcessor';
import type { ContentModelEditorCore } from '../publicTypes/ContentModelEditorCore';
import type { ContentModelEditorOptions } from '../publicTypes/IContentModelEditor';
import type { ContentModelPluginState } from '../publicTypes/pluginState/ContentModelPluginState';
import type { ContentModelSegmentFormat } from 'roosterjs-content-model-types';
import type { CoreCreator, EditorCore } from 'roosterjs-editor-types';

/**
Expand All @@ -26,25 +25,13 @@ export const createContentModelEditorCore: CoreCreator<
ContentModelEditorCore,
ContentModelEditorOptions
> = (contentDiv, options) => {
const pluginState: ContentModelPluginState = {
cache: {
domIndexer: isFeatureEnabled(
options.experimentalFeatures,
ExperimentalFeatures.ReusableContentModelV2
)
? contentModelDomIndexer
: undefined,
},
copyPaste: {
allowedCustomPasteType: options.allowedCustomPasteType || [],
},
};
const pluginState = getPluginState(options);
const modifiedOptions: ContentModelEditorOptions = {
...options,
plugins: [
createContentModelCachePlugin(pluginState.cache),
...(options.plugins || []),
createContentModelFormatPlugin(),
createContentModelFormatPlugin(pluginState.format),
createContentModelEditPlugin(),
],
corePluginOverride: {
Expand Down Expand Up @@ -74,7 +61,6 @@ export function promoteToContentModelEditorCore(
const cmCore = core as ContentModelEditorCore;

promoteCorePluginState(cmCore, pluginState);
promoteDefaultFormat(cmCore);
promoteContentModelInfo(cmCore, options);
promoteCoreApi(cmCore);
}
Expand All @@ -86,11 +72,6 @@ function promoteCorePluginState(
Object.assign(cmCore, pluginState);
}

function promoteDefaultFormat(cmCore: ContentModelEditorCore) {
cmCore.lifecycle.defaultFormat = cmCore.lifecycle.defaultFormat || {};
cmCore.defaultFormat = getDefaultSegmentFormat(cmCore);
}

function promoteContentModelInfo(
cmCore: ContentModelEditorCore,
options: ContentModelEditorOptions
Expand Down Expand Up @@ -119,17 +100,31 @@ function promoteCoreApi(cmCore: ContentModelEditorCore) {
cmCore.originalApi.setContentModel = setContentModel;
}

function getDefaultSegmentFormat(core: EditorCore): ContentModelSegmentFormat {
const format = core.lifecycle.defaultFormat ?? {};

function getPluginState(options: ContentModelEditorOptions): ContentModelPluginState {
const format = options.defaultFormat || {};
return {
fontWeight: format.bold ? 'bold' : undefined,
italic: format.italic || undefined,
underline: format.underline || undefined,
fontFamily: format.fontFamily || undefined,
fontSize: format.fontSize || undefined,
textColor: format.textColors?.lightModeColor || format.textColor || undefined,
backgroundColor:
format.backgroundColors?.lightModeColor || format.backgroundColor || undefined,
cache: {
domIndexer: isFeatureEnabled(
options.experimentalFeatures,
ExperimentalFeatures.ReusableContentModelV2
)
? contentModelDomIndexer
: undefined,
},
copyPaste: {
allowedCustomPasteType: options.allowedCustomPasteType || [],
},
format: {
defaultFormat: {
fontWeight: format.bold ? 'bold' : undefined,
italic: format.italic || undefined,
underline: format.underline || undefined,
fontFamily: format.fontFamily || undefined,
fontSize: format.fontSize || undefined,
textColor: format.textColors?.lightModeColor || format.textColor || undefined,
backgroundColor:
format.backgroundColors?.lightModeColor || format.backgroundColor || undefined,
},
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,10 @@ export { default as keyboardDelete } from './publicApi/editing/keyboardDelete';

export { default as ContentModelEditor } from './editor/ContentModelEditor';
export { default as isContentModelEditor } from './editor/isContentModelEditor';
export { default as ContentModelFormatPlugin } from './editor/plugins/ContentModelFormatPlugin';
export { default as ContentModelEditPlugin } from './editor/plugins/ContentModelEditPlugin';
export { default as ContentModelPastePlugin } from './editor/plugins/PastePlugin/ContentModelPastePlugin';

export { default as ContentModelFormatPlugin } from './editor/corePlugins/ContentModelFormatPlugin';
export { default as ContentModelEditPlugin } from './editor/corePlugins/ContentModelEditPlugin';
export { default as ContentModelTypeInContainerPlugin } from './editor/corePlugins/ContentModelTypeInContainerPlugin';
export { default as ContentModelCopyPastePlugin } from './editor/corePlugins/ContentModelCopyPastePlugin';
export { default as ContentModelCachePlugin } from './editor/corePlugins/ContentModelCachePlugin';
Expand All @@ -103,3 +104,4 @@ export { updateTableMetadata } from './domUtils/metadata/updateTableMetadata';

export { ContentModelCachePluginState } from './publicTypes/pluginState/ContentModelCachePluginState';
export { ContentModelPluginState } from './publicTypes/pluginState/ContentModelPluginState';
export { ContentModelFormatPluginState } from './publicTypes/pluginState/ContentModelFormatPluginState';
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@ import type { NodePosition } from 'roosterjs-editor-types';
* @internal
* When necessary, set default format as current pending format so it will be applied when Input event is fired
* @param editor The Content Model Editor
* @param defaultFormat The default segment format to apply
*/
export default function applyDefaultFormat(editor: IContentModelEditor) {
export default function applyDefaultFormat(
editor: IContentModelEditor,
defaultFormat: ContentModelSegmentFormat
) {
const rangeEx = editor.getSelectionRangeEx();
const range = rangeEx?.type == SelectionRangeTypes.Normal ? rangeEx.ranges[0] : null;
const startPos = range ? Position.getStart(range) : null;
Expand Down Expand Up @@ -64,10 +68,10 @@ export default function applyDefaultFormat(editor: IContentModelEditor) {
const previousBlock = blocks[blockIndex - 1];

if (previousBlock?.blockType != 'Paragraph') {
internalApplyDefaultFormat(editor, marker.format, startPos);
internalApplyDefaultFormat(editor, defaultFormat, marker.format, startPos);
}
} else if (paragraph.segments.every(x => x.segmentType != 'Text')) {
internalApplyDefaultFormat(editor, marker.format, startPos);
internalApplyDefaultFormat(editor, defaultFormat, marker.format, startPos);
}

// We didn't do any change but just apply default format to pending format, so no need to write back
Expand All @@ -80,11 +84,11 @@ export default function applyDefaultFormat(editor: IContentModelEditor) {

function internalApplyDefaultFormat(
editor: IContentModelEditor,
defaultFormat: ContentModelSegmentFormat,
currentFormat: ContentModelSegmentFormat,
startPos: NodePosition
) {
const pendingFormat = getPendingFormat(editor) || {};
const defaultFormat = editor.getContentModelDefaultFormat();
const newFormat: ContentModelSegmentFormat = {
...defaultFormat,
...pendingFormat,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { ContentModelPluginState } from './pluginState/ContentModelPluginSt
import type { CoreApiMap, EditorCore, SelectionRangeEx } from 'roosterjs-editor-types';
import type {
ContentModelDocument,
ContentModelSegmentFormat,
DomToModelOption,
DomToModelSettings,
EditorContext,
Expand Down Expand Up @@ -84,11 +83,6 @@ export interface ContentModelEditorCore extends EditorCore, ContentModelPluginSt
*/
readonly originalApi: ContentModelCoreApiMap;

/**
* Default format used by Content Model. This is calculated from lifecycle.defaultFormat
*/
defaultFormat: ContentModelSegmentFormat;

/**
* Default DOM to Content Model options
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { EditorOptions, IEditor, SelectionRangeEx } from 'roosterjs-editor-types';
import type {
ContentModelDocument,
ContentModelSegmentFormat,
DomToModelOption,
ModelToDomOption,
OnNodeCreated,
Expand Down Expand Up @@ -35,13 +34,6 @@ export interface IContentModelEditor extends IEditor {
option?: ModelToDomOption,
onNodeCreated?: OnNodeCreated
): SelectionRangeEx | null;

/**
* Get default format as ContentModelSegmentFormat.
* This is a replacement of IEditor.getDefaultFormat for Content Model.
* @returns The default format
*/
getContentModelDefaultFormat(): ContentModelSegmentFormat;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { ContentModelSegmentFormat } from 'roosterjs-content-model-types';

/**
* Plugin state for ContentModelFormatPlugin
*/
export interface ContentModelFormatPluginState {
/**
* Default format of this editor
*/
defaultFormat: ContentModelSegmentFormat;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ContentModelCachePluginState } from './ContentModelCachePluginState';
import type { CopyPastePluginState } from 'roosterjs-editor-types';
import type { ContentModelCachePluginState } from './ContentModelCachePluginState';
import type { ContentModelFormatPluginState } from './ContentModelFormatPluginState';

/**
* Temporary core plugin state for Content Model editor
Expand All @@ -15,4 +16,9 @@ export interface ContentModelPluginState {
* Plugin state for ContentModelCopyPastePlugin
*/
copyPaste: CopyPastePluginState;

/**
* Plugin state for ContentModelFormatPlugin
*/
format: ContentModelFormatPluginState;
}
Original file line number Diff line number Diff line change
Expand Up @@ -247,27 +247,4 @@ describe('ContentModelEditor', () => {

expect(div.style.fontFamily).toBe('Arial');
});

it('getContentModelDefaultFormat', () => {
const div = document.createElement('div');
const editor = new ContentModelEditor(div, {
defaultFormat: {
fontFamily: 'Tahoma',
fontSize: '20pt',
},
});
const format = editor.getContentModelDefaultFormat();

editor.dispose();

expect(format).toEqual({
fontWeight: undefined,
italic: undefined,
underline: undefined,
fontFamily: 'Tahoma',
fontSize: '20pt',
textColor: undefined,
backgroundColor: undefined,
});
});
});
Loading

0 comments on commit 24e280c

Please sign in to comment.