Skip to content

Commit

Permalink
Merge branch 'master' into u/jisong/insertentity
Browse files Browse the repository at this point in the history
  • Loading branch information
JiuqingSong authored Aug 1, 2023
2 parents a48807b + 79c9c0e commit 85689ad
Show file tree
Hide file tree
Showing 25 changed files with 297 additions and 418 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ const initialState: BuildInPluginState = {
forcePreserveRatio: false,
experimentalFeatures: [
ExperimentalFeatures.AutoFormatList,
ExperimentalFeatures.ReusableContentModel,
ExperimentalFeatures.EditWithContentModel,
ExperimentalFeatures.InlineEntityReadOnlyDelimiters,
ExperimentalFeatures.ContentModelPaste,
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ const FeatureNames: Partial<Record<ExperimentalFeatures, string>> = {
'Trigger formatting by a especial characters. Ex: (A), 1. i).',
[ExperimentalFeatures.ReuseAllAncestorListElements]:
"Reuse ancestor list elements even if they don't match the types from the list item.",
[ExperimentalFeatures.ReusableContentModel]:
'Reuse existing DOM structure if possible when convert Content Model back to DOM tree',
[ExperimentalFeatures.EditWithContentModel]: 'Handle keyboard edit event with Content Model',
[ExperimentalFeatures.DeleteTableWithBackspace]:
'Delete a table selected with the table selector pressing Backspace key',
[ExperimentalFeatures.InlineEntityReadOnlyDelimiters]:
Expand Down
1 change: 1 addition & 0 deletions karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ module.exports = function (config) {
rootDir: rootPath,
declaration: false,
strict: false,
downlevelIteration: true,
paths: {
'*': [
'*',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export default class ContentModelEditor
cacheContentModel(model: ContentModelDocument | null) {
const core = this.getCore();

if (core.reuseModel && !core.lifecycle.shadowEditFragment) {
if (!core.lifecycle.shadowEditFragment) {
core.cachedModel = model || undefined;
core.cachedRangeEx = undefined;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
* @param option The option to customize the behavior of DOM to Content Model conversion
*/
export const createContentModel: CreateContentModel = (core, option) => {
let cachedModel = core.reuseModel ? core.cachedModel : null;
let cachedModel = core.cachedModel;

if (cachedModel && core.lifecycle.shadowEditFragment) {
// When in shadow edit, use a cloned model so we won't pollute the cached one
Expand All @@ -38,10 +38,6 @@ function internalCreateContentModel(
...option?.processorOverride,
};

if (!core.reuseModel) {
context.disableCacheElement = true;
}

return domToContentModel(
core.contentDiv,
context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,7 @@ export const createContentModelEditorCore: CoreCreator<
new ContentModelEditPlugin(),
],
corePluginOverride: {
typeInContainer: isFeatureEnabled(
options.experimentalFeatures,
ExperimentalFeatures.EditWithContentModel
)
? new ContentModelTypeInContainerPlugin()
: undefined,
typeInContainer: new ContentModelTypeInContainerPlugin(),
copyPaste: isFeatureEnabled(
options.experimentalFeatures,
ExperimentalFeatures.ContentModelPaste
Expand Down Expand Up @@ -82,10 +77,6 @@ function promoteContentModelInfo(

cmCore.defaultDomToModelOptions = options.defaultDomToModelOptions || {};
cmCore.defaultModelToDomOptions = options.defaultModelToDomOptions || {};
cmCore.reuseModel = isFeatureEnabled(
experimentalFeatures,
ExperimentalFeatures.ReusableContentModel
);
cmCore.addDelimiterForEntity = isFeatureEnabled(
experimentalFeatures,
ExperimentalFeatures.InlineEntityReadOnlyDelimiters
Expand All @@ -96,17 +87,8 @@ function promoteCoreApi(cmCore: ContentModelEditorCore) {
cmCore.api.createEditorContext = createEditorContext;
cmCore.api.createContentModel = createContentModel;
cmCore.api.setContentModel = setContentModel;
cmCore.api.switchShadowEdit = switchShadowEdit;
cmCore.api.getSelectionRangeEx = getSelectionRangeEx;

if (
isFeatureEnabled(
cmCore.lifecycle.experimentalFeatures,
ExperimentalFeatures.ReusableContentModel
)
) {
// Only use Content Model shadow edit when reuse model is enabled because it relies on cached model for the original model
cmCore.api.switchShadowEdit = switchShadowEdit;
}
cmCore.originalApi.createEditorContext = createEditorContext;
cmCore.originalApi.createContentModel = createContentModel;
cmCore.originalApi.setContentModel = setContentModel;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { isNodeOfType, normalizeContentModel } from 'roosterjs-content-model-dom
import {
EditorPlugin,
EntityOperationEvent,
ExperimentalFeatures,
IEditor,
Keys,
NodePosition,
Expand Down Expand Up @@ -40,7 +39,6 @@ const ProcessKey = 'Process';
export default class ContentModelEditPlugin implements EditorPlugin {
private editor: IContentModelEditor | null = null;
private triggeredEntityEvents: EntityOperationEvent[] = [];
private editWithContentModel = false;
private hasDefaultFormat = false;

/**
Expand All @@ -59,9 +57,6 @@ export default class ContentModelEditPlugin implements EditorPlugin {
initialize(editor: IEditor) {
// TODO: Later we may need a different interface for Content Model editor plugin
this.editor = editor as IContentModelEditor;
this.editWithContentModel = this.editor.isFeatureEnabled(
ExperimentalFeatures.EditWithContentModel
);

const defaultFormat = this.editor.getContentModelDefaultFormat();
this.hasDefaultFormat =
Expand Down Expand Up @@ -117,10 +112,10 @@ export default class ContentModelEditPlugin implements EditorPlugin {
const rawEvent = event.rawEvent;
const which = rawEvent.which;

if (!this.editWithContentModel || rawEvent.defaultPrevented) {
if (rawEvent.defaultPrevented || event.handledByEditFeature) {
// Other plugins already handled this event, so it is most likely content is already changed, we need to clear cached content model
editor.cacheContentModel(null /*model*/);
} else if (!rawEvent.defaultPrevented && !event.handledByEditFeature) {
} else {
// TODO: Consider use ContentEditFeature and need to hide other conflict features that are not based on Content Model
switch (which) {
case Keys.BACKSPACE:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export { default as setListStartNumber } from './publicApi/list/setListStartNumb
export { default as hasSelectionInBlock } from './publicApi/selection/hasSelectionInBlock';
export { default as hasSelectionInSegment } from './publicApi/selection/hasSelectionInSegment';
export { default as hasSelectionInBlockGroup } from './publicApi/selection/hasSelectionInBlockGroup';
export { default as getSelectedSegments } from './publicApi/selection/getSelectedSegments';
export { default as setIndentation } from './publicApi/block/setIndentation';
export { default as setAlignment } from './publicApi/block/setAlignment';
export { default as setDirection } from './publicApi/block/setDirection';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,6 @@ export function getSelectedSegmentsAndParagraphs(
return result;
}

/**
* @internal
*/
export function getSelectedSegments(
model: ContentModelDocument,
includingFormatHolder: boolean
): ContentModelSegment[] {
return getSelectedSegmentsAndParagraphs(model, includingFormatHolder).map(x => x[0]);
}

/**
* @internal
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import getSelectedSegments from '../selection/getSelectedSegments';
import { adjustSegmentSelection } from '../../modelApi/selection/adjustSegmentSelection';
import { adjustWordSelection } from '../../modelApi/selection/adjustWordSelection';
import { formatWithContentModel } from '../utils/formatWithContentModel';
import { getSelectedSegments } from '../../modelApi/selection/collectSelections';
import { IContentModelEditor } from '../../publicTypes/IContentModelEditor';
import { setSelection } from '../../modelApi/selection/setSelection';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import getSelectedSegments from '../selection/getSelectedSegments';
import { ChangeSource } from 'roosterjs-editor-types';
import { ContentModelLink } from 'roosterjs-content-model-types';
import { formatWithContentModel } from '../utils/formatWithContentModel';
import { getOnDeleteEntityCallback } from '../../editor/utils/handleKeyboardEventCommon';
import { getPendingFormat } from '../../modelApi/format/pendingFormat';
import { getSelectedSegments } from '../../modelApi/selection/collectSelections';
import { HtmlSanitizer, matchLink } from 'roosterjs-editor-dom';
import { IContentModelEditor } from '../../publicTypes/IContentModelEditor';
import { mergeModel } from '../../modelApi/common/mergeModel';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import getSelectedSegments from '../selection/getSelectedSegments';
import { adjustSegmentSelection } from '../../modelApi/selection/adjustSegmentSelection';
import { formatWithContentModel } from '../utils/formatWithContentModel';
import { getSelectedSegments } from '../../modelApi/selection/collectSelections';
import { IContentModelEditor } from '../../publicTypes/IContentModelEditor';

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ContentModelDocument, ContentModelSegment } from 'roosterjs-content-model-types';
import { getSelectedSegmentsAndParagraphs } from '../../modelApi/selection/collectSelections';

/**
* Get selected segments from a content model
*/
export default function getSelectedSegments(
model: ContentModelDocument,
includingFormatHolder: boolean
): ContentModelSegment[] {
return getSelectedSegmentsAndParagraphs(model, includingFormatHolder).map(x => x[0]);
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import ContentModelBeforePasteEvent from '../../publicTypes/event/ContentModelBeforePasteEvent';
import { BeforePasteEvent, NodePosition } from 'roosterjs-editor-types';
import { ContentModelBlockFormat, FormatParser } from 'roosterjs-content-model-types';
import { domToContentModel } from 'roosterjs-content-model-dom';
import { formatWithContentModel } from './formatWithContentModel';
import { getOnDeleteEntityCallback } from '../../editor/utils/handleKeyboardEventCommon';
import { IContentModelEditor } from '../../publicTypes/IContentModelEditor';
import { mergeModel } from '../../modelApi/common/mergeModel';
import { NodePosition } from 'roosterjs-editor-types';
import ContentModelBeforePasteEvent, {
ContentModelBeforePasteEventData,
} from '../../publicTypes/event/ContentModelBeforePasteEvent';
import {
createDefaultHtmlSanitizerOptions,
getPasteType,
Expand Down Expand Up @@ -45,32 +47,32 @@ export default function paste(
clipboardData.snapshotBeforePaste = editor.getContent(GetContentMode.RawHTMLWithSelection);
}

const event = createBeforePasteEvent(
const eventData = createBeforePasteEventData(
editor,
clipboardData,
getPasteType(pasteAsText, applyCurrentFormat, pasteAsImage)
);

const fragment = createFragmentFromClipboardData(
const { pluginEvent, fragment } = triggerPluginEventAndCreatePasteFragment(
editor,
clipboardData,
null /* position */,
pasteAsText,
pasteAsImage,
event
eventData
);

const pasteModel = domToContentModel(fragment, {
...event.domToModelOption,
...pluginEvent.domToModelOption,
disableCacheElement: true,
additionalFormatParsers: {
...event.domToModelOption.additionalFormatParsers,
...pluginEvent.domToModelOption.additionalFormatParsers,
block: [
...(event.domToModelOption.additionalFormatParsers?.block || []),
...(pluginEvent.domToModelOption.additionalFormatParsers?.block || []),
...(applyCurrentFormat ? [blockElementParser] : []),
],
listLevel: [
...(event.domToModelOption.additionalFormatParsers?.listLevel || []),
...(pluginEvent.domToModelOption.additionalFormatParsers?.listLevel || []),
...(applyCurrentFormat ? [blockElementParser] : []),
],
},
Expand All @@ -81,12 +83,16 @@ export default function paste(
editor,
'Paste',
model => {
mergeModel(model, pasteModel, getOnDeleteEntityCallback(editor), {
mergeFormat: applyCurrentFormat ? 'keepSourceEmphasisFormat' : 'none',
mergeTable:
pasteModel.blocks.length === 1 &&
pasteModel.blocks[0].blockType === 'Table',
});
if (pluginEvent.customizedMerge) {
pluginEvent.customizedMerge(model, pasteModel);
} else {
mergeModel(model, pasteModel, getOnDeleteEntityCallback(editor), {
mergeFormat: applyCurrentFormat ? 'keepSourceEmphasisFormat' : 'none',
mergeTable:
pasteModel.blocks.length === 1 &&
pasteModel.blocks[0].blockType === 'Table',
});
}
return true;
},
{
Expand All @@ -97,18 +103,17 @@ export default function paste(
}
}

function createBeforePasteEvent(
function createBeforePasteEventData(
editor: IContentModelEditor,
clipboardData: ClipboardData,
pasteType: PasteType
): ContentModelBeforePasteEvent {
): ContentModelBeforePasteEventData {
const options = createDefaultHtmlSanitizerOptions();

// Remove "caret-color" style generated by Safari to make sure caret shows in right color after paste
options.cssStyleCallbacks['caret-color'] = () => false;

return {
eventType: PluginEventType.BeforePaste,
clipboardData,
fragment: editor.getDocument().createDocumentFragment(),
sanitizingOption: options,
Expand All @@ -120,14 +125,23 @@ function createBeforePasteEvent(
};
}

function createFragmentFromClipboardData(
/**
* This function is used to create a BeforePasteEvent object after trigger the event, so other plugins can modify the event object
* This function will also create a DocumentFragment for paste.
*/
function triggerPluginEventAndCreatePasteFragment(
editor: IContentModelEditor,
clipboardData: ClipboardData,
position: NodePosition | null,
pasteAsText: boolean,
pasteAsImage: boolean,
event: BeforePasteEvent
) {
eventData: ContentModelBeforePasteEventData
): { pluginEvent: ContentModelBeforePasteEvent; fragment: DocumentFragment } {
const event = {
eventType: PluginEventType.BeforePaste,
...eventData,
} as ContentModelBeforePasteEvent;

const { fragment } = event;
const { rawHtml, text, imageDataUri } = clipboardData;
const trustedHTMLHandler = editor.getTrustedHTMLHandler();
Expand All @@ -151,12 +165,16 @@ function createFragmentFromClipboardData(
}

// Step 4: Trigger BeforePasteEvent so that plugins can do proper change before paste
editor.triggerPluginEvent(PluginEventType.BeforePaste, event, true /* broadcast */);
const pluginEvent = editor.triggerPluginEvent(
PluginEventType.BeforePaste,
eventData,
true /* broadcast */
) as ContentModelBeforePasteEvent;

// Step 5. Sanitize the fragment before paste to make sure the content is safe
sanitizePasteContent(event, position);

return fragment;
return { fragment, pluginEvent };
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,6 @@ export interface ContentModelEditorCore extends EditorCore {
*/
defaultModelToDomOptions: ModelToDomOption;

/**
* Whether reuse Content Model is allowed
*/
reuseModel: boolean;

/**
* Whether adding delimiter for entity is allowed
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DomToModelOption } from 'roosterjs-content-model-types';
import { ContentModelDocument, DomToModelOption } from 'roosterjs-content-model-types';
import {
BeforePasteEvent,
BeforePasteEventData,
Expand All @@ -13,6 +13,10 @@ export interface ContentModelBeforePasteEventData extends BeforePasteEventData {
* domToModel Options to use when creating the content model from the paste fragment
*/
domToModelOption: Partial<DomToModelOption>;
/**
* customizedMerge Customized merge function to use when merging the paste fragment into the editor
*/
customizedMerge?: (target: ContentModelDocument, source: ContentModelDocument) => void;
}

/**
Expand Down
Loading

0 comments on commit 85689ad

Please sign in to comment.