Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Content Model: Advanced cache #2083

Merged
merged 72 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from 69 commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
130623d
Content Model Customization refactor
JiuqingSong Sep 7, 2023
809cdfb
fix build
JiuqingSong Sep 7, 2023
fde4550
improve
JiuqingSong Sep 7, 2023
acee729
Merge branch 'master' into u/jisong/config3
JiuqingSong Sep 8, 2023
1057dce
Merge branch 'master' into u/jisong/config3
JiuqingSong Sep 8, 2023
14dec36
Merge branch 'master' into u/jisong/config3
JiuqingSong Sep 11, 2023
93c9250
Content Model Customization refactor 2: Add default config
JiuqingSong Sep 11, 2023
1435264
fix build
JiuqingSong Sep 11, 2023
4f154ac
Merge branch 'u/jisong/config3' into u/jisong/config4
JiuqingSong Sep 11, 2023
bc8682f
Content Model: Persist cache 1
JiuqingSong Sep 12, 2023
2eeaddd
fix build
JiuqingSong Sep 12, 2023
8bba06c
improve
JiuqingSong Sep 12, 2023
ccaa7df
Content Model: Cache 2
JiuqingSong Sep 12, 2023
3f5b460
Fix test
JiuqingSong Sep 13, 2023
144e12b
Fix build
JiuqingSong Sep 13, 2023
3bd3c4e
improve
JiuqingSong Sep 13, 2023
929edfb
Improve
JiuqingSong Sep 14, 2023
ca805d6
improve
JiuqingSong Sep 14, 2023
0f33bb3
Improve
JiuqingSong Sep 14, 2023
d4bdd6c
fix test
JiuqingSong Sep 14, 2023
053d4e3
Merge branch 'master' into u/jisong/config3
JiuqingSong Sep 15, 2023
ceb6a31
Merge branch 'u/jisong/config3' into u/jisong/config4
JiuqingSong Sep 15, 2023
47f0939
Merge branch 'u/jisong/config4' into u/jisong/cache2
JiuqingSong Sep 15, 2023
089b5ee
Merge branch 'master' into u/jisong/config4
JiuqingSong Sep 18, 2023
f7595c2
Merge branch 'u/jisong/config4' into u/jisong/cache2
JiuqingSong Sep 18, 2023
29e4f49
Do not restore cached selection when call select
JiuqingSong Sep 18, 2023
be750d3
Content Model: Add model into ContentChangedEvent
JiuqingSong Sep 18, 2023
6d9708f
fix build
JiuqingSong Sep 18, 2023
0e5f504
Merge branch 'master' into u/jisong/cmcontentchanged
JiuqingSong Sep 18, 2023
80a541f
Merge branch 'master' into u/jisong/select
JiuqingSong Sep 18, 2023
d96d200
add demo site page
JiuqingSong Sep 18, 2023
345e724
Merge branch 'u/jisong/cmcontentchanged' of https://github.com/micros…
JiuqingSong Sep 18, 2023
93761f3
Improve
JiuqingSong Sep 18, 2023
cf5aaf5
Merge branch 'master' into u/jisong/cache2
JiuqingSong Sep 18, 2023
12e011d
Merge branch 'u/jisong/cmcontentchanged' into u/jisong/cache2
JiuqingSong Sep 18, 2023
f406287
Merge branch 'u/jisong/select' into u/jisong/cache2
JiuqingSong Sep 18, 2023
827c699
Content Model: pass out segment nodes
JiuqingSong Sep 19, 2023
5084520
Merge branch 'u/jisong/segmentnodes' into u/jisong/cache6
JiuqingSong Sep 19, 2023
f179daa
cache 8
JiuqingSong Sep 19, 2023
d994051
Merge branch 'master' into u/jisong/select
JiuqingSong Sep 19, 2023
7bbab84
Merge branch 'master' into u/jisong/cmcontentchanged
JiuqingSong Sep 19, 2023
c4568a6
Merge branch 'master' into u/jisong/select
JiuqingSong Sep 19, 2023
7185b21
Merge branch 'master' into u/jisong/cmcontentchanged
JiuqingSong Sep 19, 2023
391a0e9
fix build
JiuqingSong Sep 19, 2023
03381fe
fix test
JiuqingSong Sep 19, 2023
5430f57
Improve
JiuqingSong Sep 19, 2023
14a1062
improve
JiuqingSong Sep 20, 2023
5afa996
fix test
JiuqingSong Sep 20, 2023
a3ec520
Improve
JiuqingSong Sep 20, 2023
a1866ea
fix test
JiuqingSong Sep 20, 2023
2edc202
Merge branch 'master' into u/jisong/cache8
JiuqingSong Sep 20, 2023
020c3e3
Merge branch 'u/jisong/select' into test
JiuqingSong Sep 20, 2023
d8fbc78
Improve "checkDependency"
JiuqingSong Sep 20, 2023
4aadb3d
Content Model: Clear table selection fix
JiuqingSong Sep 20, 2023
a7da588
fix build
JiuqingSong Sep 20, 2023
311a01c
Content Model: Improve adjustWordSelection
JiuqingSong Sep 20, 2023
868b66f
Merge branch 'u/jisong/checkdepimprove' into test
JiuqingSong Sep 20, 2023
9c3d0f4
Merge branch 'u/jisong/setselectiong' into test
JiuqingSong Sep 20, 2023
984487e
Merge branch 'u/jisong/adjustwordselection' into test
JiuqingSong Sep 20, 2023
59e034b
Merge branch 'test' into u/jisong/cache8
JiuqingSong Sep 20, 2023
03bee32
Merge branch 'master' into u/jisong/cache8
JiuqingSong Sep 20, 2023
82a065f
add test
JiuqingSong Sep 20, 2023
9e12a64
add test
JiuqingSong Sep 21, 2023
768823c
Merge branch 'master' into u/jisong/cache8
JiuqingSong Sep 21, 2023
6c6b8c9
Merge branch 'master' into u/jisong/cache8
JiuqingSong Sep 21, 2023
24d816b
Merge branch 'master' into u/jisong/cache8
JiuqingSong Sep 22, 2023
01f610d
fix test
JiuqingSong Sep 22, 2023
d734d7a
Merge branch 'master' into u/jisong/cache8
JiuqingSong Sep 22, 2023
7bede7c
Merge branch 'master' into u/jisong/cache8
JiuqingSong Sep 25, 2023
f3e4064
Improve
JiuqingSong Sep 25, 2023
ad33b7c
retrigger check step
JiuqingSong Sep 25, 2023
48fa981
retrigger check step
JiuqingSong Sep 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ const initialState: BuildInPluginState = {
linkTitle: 'Ctrl+Click to follow the link:' + UrlPlaceholder,
watermarkText: 'Type content here ...',
forcePreserveRatio: false,
experimentalFeatures: [ExperimentalFeatures.ContentModelPaste],
experimentalFeatures: [
ExperimentalFeatures.ContentModelPaste,
ExperimentalFeatures.ReusableContentModelV2,
],
isRtl: false,
tableFeaturesContainerSelector: '#' + 'EditorContainer',
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const FeatureNames: Partial<Record<ExperimentalFeatures, string>> = {
'Delete a table selected with the table selector pressing Backspace key',
[ExperimentalFeatures.ContentModelPaste]: 'Paste with content model',
[ExperimentalFeatures.DisableListChain]: 'Disable list chain functionality',
[ExperimentalFeatures.ReusableContentModelV2]:
'Reuse existing DOM structure if possible, and update the model when content or selection is changed',
};

export default class ContentModelExperimentalFeaturesPane extends React.Component<
Expand All @@ -36,7 +38,7 @@ export default class ContentModelExperimentalFeaturesPane extends React.Componen
id={name}
onChange={() => this.onClick(name)}
/>
<label htmlFor={name}>{FeatureNames[name]}</label>
<label htmlFor={name}>{name + ': ' + FeatureNames[name]}</label>
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ export const brProcessor: ElementProcessor<HTMLBRElement> = (group, element, con
br.isSelected = true;
}

addSegment(group, br, context.blockFormat);
const paragraph = addSegment(group, br, context.blockFormat);
context.domIndexer?.onSegment(element, paragraph, [br]);
};
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ export const entityProcessor: ElementProcessor<HTMLElement> = (group, element, c
if (isBlockEntity) {
addBlock(group, entityModel);
} else {
addSegment(group, entityModel);
const paragraph = addSegment(group, entityModel);
context.domIndexer?.onSegment(element, paragraph, [entityModel]);
}
}
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ const generalSegmentProcessor: ElementProcessor<HTMLElement> = (group, element,
const isSelectedBefore = context.isInSelection;

addDecorators(segment, context);
addSegment(group, segment);
const paragraph = addSegment(group, segment);
context.domIndexer?.onSegment(element, paragraph, [segment]);

stackFormat(
context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const imageProcessor: ElementProcessor<HTMLImageElement> = (group, elemen
image.isSelected = true;
}

addSegment(group, image);
const paragraph = addSegment(group, image);
context.domIndexer?.onSegment(element, paragraph, [image]);
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ export const tableProcessor: ElementProcessor<HTMLTableElement> = (
table.cachedElement = tableElement;
}

context.domIndexer?.onTable(tableElement, table);

parseFormat(tableElement, context.formatParsers.table, table.format, context);
parseFormat(tableElement, context.formatParsers.tableBorder, table.format, context);
parseFormat(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { addDecorators } from '../../modelApi/common/addDecorators';
import { addSegment } from '../../modelApi/common/addSegment';
import { addSelectionMarker } from '../utils/addSelectionMarker';
import { areSameFormats } from '../utils/areSameFormats';
import { createText } from '../../modelApi/creators/createText';
import { ensureParagraph } from '../../modelApi/common/ensureParagraph';
import { getRegularSelectionOffsets } from '../utils/getRegularSelectionOffsets';
import { hasSpacesOnly } from '../../modelApi/common/hasSpacesOnly';
import {
ContentModelBlockGroup,
ContentModelParagraph,
ContentModelText,
DomToModelContext,
ElementProcessor,
} from 'roosterjs-content-model-types';
Expand All @@ -21,9 +23,12 @@ export const textProcessor: ElementProcessor<Text> = (
) => {
let txt = textNode.nodeValue || '';
let [txtStartOffset, txtEndOffset] = getRegularSelectionOffsets(context, textNode);
const segments: (ContentModelText | undefined)[] = [];
const paragraph = ensureParagraph(group, context.blockFormat);

if (txtStartOffset >= 0) {
addTextSegment(group, txt.substring(0, txtStartOffset), context);
const subText = txt.substring(0, txtStartOffset);
segments.push(addTextSegment(group, subText, paragraph, context));
context.isInSelection = true;

addSelectionMarker(group, context);
Expand All @@ -33,7 +38,8 @@ export const textProcessor: ElementProcessor<Text> = (
}

if (txtEndOffset >= 0) {
addTextSegment(group, txt.substring(0, txtEndOffset), context);
const subText = txt.substring(0, txtEndOffset);
segments.push(addTextSegment(group, subText, paragraph, context));

if (context.rangeEx && !context.rangeEx.areAllCollapsed) {
addSelectionMarker(group, context);
Expand All @@ -43,32 +49,32 @@ export const textProcessor: ElementProcessor<Text> = (
txt = txt.substring(txtEndOffset);
}

addTextSegment(group, txt, context);
segments.push(addTextSegment(group, txt, paragraph, context));
context.domIndexer?.onSegment(
textNode,
paragraph,
segments.filter((x): x is ContentModelText => !!x)
);
};

// When we see these values of white-space style, need to preserve spaces and line-breaks and let browser handle it for us.
const WhiteSpaceValuesNeedToHandle = ['pre', 'pre-wrap', 'pre-line', 'break-spaces'];

function addTextSegment(group: ContentModelBlockGroup, text: string, context: DomToModelContext) {
if (text) {
const lastBlock = group.blocks[group.blocks.length - 1];
const paragraph = lastBlock?.blockType == 'Paragraph' ? lastBlock : null;
const lastSegment = paragraph?.segments[paragraph.segments.length - 1];
function addTextSegment(
group: ContentModelBlockGroup,
text: string,
paragraph: ContentModelParagraph,
context: DomToModelContext
): ContentModelText | undefined {
let textModel: ContentModelText | undefined;

if (text) {
if (
lastSegment?.segmentType == 'Text' &&
!!lastSegment.isSelected == !!context.isInSelection &&
areSameFormats(lastSegment.format, context.segmentFormat) &&
areSameFormats(lastSegment.link || {}, context.link.format || {}) &&
areSameFormats(lastSegment.code || {}, context.code.format || {})
) {
lastSegment.text += text;
} else if (
!hasSpacesOnly(text) ||
paragraph?.segments.length! > 0 ||
(paragraph?.segments.length ?? 0) > 0 ||
WhiteSpaceValuesNeedToHandle.indexOf(paragraph?.format.whiteSpace || '') >= 0
) {
const textModel = createText(text, context.segmentFormat);
textModel = createText(text, context.segmentFormat);

if (context.isInSelection) {
textModel.isSelected = true;
Expand All @@ -79,4 +85,6 @@ function addTextSegment(group: ContentModelBlockGroup, text: string, context: Do
addSegment(group, textModel, context.blockFormat);
}
}

return textModel;
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export { createListLevel } from './modelApi/creators/createListLevel';
export { addBlock } from './modelApi/common/addBlock';
export { addCode } from './modelApi/common/addDecorators';
export { addLink } from './modelApi/common/addDecorators';
export { ensureParagraph } from './modelApi/common/ensureParagraph';

export { normalizeContentModel } from './modelApi/common/normalizeContentModel';
export { isGeneralSegment } from './modelApi/common/isGeneralSegment';
export { unwrapBlock } from './modelApi/common/unwrapBlock';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { addBlock } from './addBlock';
import { createParagraph } from '../creators/createParagraph';
import { ensureParagraph } from './ensureParagraph';
import type {
ContentModelBlockFormat,
ContentModelBlockGroup,
Expand All @@ -12,22 +11,14 @@ import type {
* @param group The parent block group of the paragraph to add segment into
* @param newSegment The segment to add
* @param blockFormat The block format used for creating a new paragraph when need
* @returns The parent paragraph where the segment is added to
*/
export function addSegment(
group: ContentModelBlockGroup,
newSegment: ContentModelSegment,
blockFormat?: ContentModelBlockFormat
) {
const lastBlock = group.blocks[group.blocks.length - 1];
let paragraph: ContentModelParagraph;

if (lastBlock?.blockType == 'Paragraph') {
paragraph = lastBlock;
} else {
paragraph = createParagraph(true, blockFormat);
addBlock(group, paragraph);
}

): ContentModelParagraph {
const paragraph = ensureParagraph(group, blockFormat);
const lastSegment = paragraph.segments[paragraph.segments.length - 1];

if (newSegment.segmentType == 'SelectionMarker') {
Expand All @@ -41,4 +32,6 @@ export function addSegment(

paragraph.segments.push(newSegment);
}

return paragraph;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { addBlock } from './addBlock';
import { createParagraph } from '../creators/createParagraph';
import {
ContentModelBlockFormat,
ContentModelBlockGroup,
ContentModelParagraph,
} from 'roosterjs-content-model-types';

/**
* Ensure there is a Paragraph that can insert segments in a Content Model Block Group
* @param group The parent block group of the target paragraph
* @param blockFormat Format of the paragraph. This is only used if we need to create a new paragraph
*/
export function ensureParagraph(
group: ContentModelBlockGroup,
blockFormat?: ContentModelBlockFormat
): ContentModelParagraph {
const lastBlock = group.blocks[group.blocks.length - 1];

if (lastBlock?.blockType == 'Paragraph') {
return lastBlock;
} else {
const paragraph = createParagraph(true, blockFormat);
addBlock(group, paragraph);

return paragraph;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ export const handleParagraph: ContentModelBlockHandler<ContentModelParagraph> =
paragraph.segments.forEach(segment => {
const newSegments: Node[] = [];
context.modelHandlers.segment(doc, parent, segment, context, newSegments);

newSegments.forEach(node => {
context.domIndexer?.onSegment(node, paragraph, [segment]);
});
});
}
};
Expand Down Expand Up @@ -103,6 +107,11 @@ export const handleParagraph: ContentModelBlockHandler<ContentModelParagraph> =
// to make sure the value is correct.
refNode = container.nextSibling;

if (container) {
context.onNodeCreated?.(paragraph, container);
context.domIndexer?.onParagraph(container);
}

if (needParagraphWrapper) {
if (context.allowCacheElement) {
paragraph.cachedElement = container;
Expand All @@ -114,9 +123,5 @@ export const handleParagraph: ContentModelBlockHandler<ContentModelParagraph> =
});
}

if (container) {
context.onNodeCreated?.(paragraph, container);
}

return refNode;
};
Original file line number Diff line number Diff line change
Expand Up @@ -145,5 +145,7 @@ export const handleTable: ContentModelBlockHandler<ContentModelTable> = (
}
}

context.domIndexer?.onTable(tableNode, table);

return refNode;
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { brProcessor } from '../../../lib/domToModel/processors/brProcessor';
import { createContentModelDocument } from '../../../lib/modelApi/creators/createContentModelDocument';
import { createDomToModelContext } from '../../../lib/domToModel/context/createDomToModelContext';
import { DomToModelContext } from 'roosterjs-content-model-types';
import {
ContentModelBr,
ContentModelDomIndexer,
ContentModelParagraph,
DomToModelContext,
} from 'roosterjs-content-model-types';

describe('brProcessor', () => {
let context: DomToModelContext;
Expand Down Expand Up @@ -59,4 +64,37 @@ describe('brProcessor', () => {
],
});
});

it('Br with domIndexer', () => {
const doc = createContentModelDocument();
const br = document.createElement('br');
const onSegmentSpy = jasmine.createSpy('onSegment');
const domIndexer: ContentModelDomIndexer = {
onParagraph: null!,
onSegment: onSegmentSpy,
onTable: null!,
reconcileSelection: null!,
};

context.domIndexer = domIndexer;

brProcessor(doc, br, context);

const brModel: ContentModelBr = {
segmentType: 'Br',
format: {},
};
const paragraphModel: ContentModelParagraph = {
blockType: 'Paragraph',
isImplicit: true,
segments: [brModel],
format: {},
};

expect(doc).toEqual({
blockGroupType: 'Document',
blocks: [paragraphModel],
});
expect(onSegmentSpy).toHaveBeenCalledWith(br, paragraphModel, [brModel]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,8 @@ describe('childProcessor', () => {
isSelected: true,
format: {},
},
{ segmentType: 'Text', text: 'test2test3', format: {} },
{ segmentType: 'Text', text: 'test2', format: {} },
{ segmentType: 'Text', text: 'test3', format: {} },
],
isImplicit: true,
format: {},
Expand Down
Loading