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 Cache improvement - Step 5: Port roosterjs-content-model-dom package #2648

Merged
merged 60 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
7cc5a1b
Readonly types (3rd try
JiuqingSong May 13, 2024
3ff0779
Improve
JiuqingSong May 14, 2024
950a4ae
fix build
JiuqingSong May 14, 2024
2574fe2
Improve
JiuqingSong May 14, 2024
0180d29
Merge branch 'master' into u/jisong/readonlytype0513
JiuqingSong May 14, 2024
47b21f3
improve
JiuqingSong May 14, 2024
bd6b0cb
Improve
JiuqingSong May 14, 2024
cf3ea78
Add shallow mutable type
JiuqingSong May 14, 2024
e14e506
improve
JiuqingSong May 14, 2024
9a2d449
Improve
JiuqingSong May 14, 2024
719f5ed
improve
JiuqingSong May 14, 2024
caf7ef1
improve
JiuqingSong May 15, 2024
29495db
Merge branch 'master' into u/jisong/readonlytype0513
JiuqingSong May 16, 2024
0230a26
add test
JiuqingSong May 16, 2024
46fbe45
Readonly types step 2
JiuqingSong May 16, 2024
5f9b88b
Readonly types step 3
JiuqingSong May 16, 2024
4fa3138
Merge branch 'u/jisong/readonlytypes_step_2' into u/jisong/readonlyty…
JiuqingSong May 16, 2024
ce2e31a
Readonly type step 4
JiuqingSong May 16, 2024
2afcf38
add test
JiuqingSong May 16, 2024
b2a3f6c
Merge branch 'u/jisong/readonlytypes_step_2' into u/jisong/readonlyty…
JiuqingSong May 16, 2024
adb26a0
Improve
JiuqingSong May 16, 2024
b77e4b0
Merge branch 'u/jisong/readonlytypes_step_2' into u/jisong/readonlyty…
JiuqingSong May 16, 2024
85b50f2
improve
JiuqingSong May 17, 2024
e91e1a8
Merge branch 'master' into u/jisong/readonlytype0513
JiuqingSong May 17, 2024
6dbd78b
improve
JiuqingSong May 17, 2024
d0ee665
Merge branch 'u/jisong/readonlytype0513' into u/jisong/readonlytypes_…
JiuqingSong May 17, 2024
6d7d041
Merge branch 'u/jisong/readonlytype0513' into u/jisong/readonlytypes_…
JiuqingSong May 17, 2024
6c35a70
Merge branch 'u/jisong/readonlytypes_step_2' into u/jisong/readonlyty…
JiuqingSong May 17, 2024
1e157b1
Merge branch 'u/jisong/readonlytypes_step_3' into u/jisong/readonlyty…
JiuqingSong May 17, 2024
c92c7db
Readonly types step 5: dom package
JiuqingSong May 17, 2024
719c917
add change
JiuqingSong May 17, 2024
3ec1ec0
Merge branch 'master' into u/jisong/readonlytypes_step_5
JiuqingSong May 18, 2024
f659ca9
Merge branch 'master' into u/jisong/readonlytypes_step_2
JiuqingSong May 19, 2024
acceb1d
improve
JiuqingSong May 19, 2024
d5b159d
Merge branch 'u/jisong/readonlytypes_step_2' into u/jisong/readonlyty…
JiuqingSong May 19, 2024
93b0247
Merge branch 'u/jisong/readonlytypes_step_4' into u/jisong/readonlyty…
JiuqingSong May 19, 2024
a69738f
Merge branch 'master' into u/jisong/readonlytype0513
JiuqingSong May 20, 2024
b4ab134
improve
JiuqingSong May 20, 2024
dea9f7f
Merge branch 'u/jisong/readonlytype0513' into u/jisong/readonlytypes_…
JiuqingSong May 20, 2024
6d9fe1e
Merge branch 'u/jisong/readonlytype0513' into u/jisong/readonlytypes_…
JiuqingSong May 20, 2024
124559c
Merge branch 'u/jisong/readonlytypes_step_2' into u/jisong/readonlyty…
JiuqingSong May 20, 2024
df68a71
Merge branch 'u/jisong/readonlytypes_step_3' into u/jisong/readonlyty…
JiuqingSong May 20, 2024
9301264
Merge branch 'u/jisong/readonlytypes_step_4' into u/jisong/readonlyty…
JiuqingSong May 20, 2024
f745036
Merge branch 'master' into u/jisong/readonlytypes_step_2
JiuqingSong May 20, 2024
806a1d3
Improve
JiuqingSong May 20, 2024
fa84daa
Merge branch 'master' into u/jisong/readonlytypes_step_3
JiuqingSong May 20, 2024
de8ca0d
improve
JiuqingSong May 20, 2024
149b315
Merge branch 'u/jisong/readonlytypes_step_2' into u/jisong/readonlyty…
JiuqingSong May 20, 2024
1de55bb
Merge branch 'u/jisong/readonlytypes_step_3' into u/jisong/readonlyty…
JiuqingSong May 20, 2024
588864e
Merge branch 'u/jisong/readonlytypes_step_4' into u/jisong/readonlyty…
JiuqingSong May 20, 2024
7a6b43f
fix test
JiuqingSong May 20, 2024
bd9661d
Merge branch 'u/jisong/readonlytypes_step_2' into u/jisong/readonlyty…
JiuqingSong May 20, 2024
cc17fdb
Merge branch 'u/jisong/readonlytypes_step_4' into u/jisong/readonlyty…
JiuqingSong May 20, 2024
62e491f
Improve
JiuqingSong May 20, 2024
829e6ca
fix build
JiuqingSong May 20, 2024
5e72761
improve
JiuqingSong May 20, 2024
3f36928
Merge branch 'u/jisong/readonlytypes_step_2' into u/jisong/readonlyty…
JiuqingSong May 20, 2024
a8cc329
Merge branch 'u/jisong/readonlytypes_step_4' into u/jisong/readonlyty…
JiuqingSong May 20, 2024
10fcdd2
'master' into u/jisong/readonlytypes_step_5
JiuqingSong May 22, 2024
6b72d19
Merge branch 'master' into u/jisong/readonlytypes_step_5
JiuqingSong May 23, 2024
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 @@ -4,17 +4,19 @@ import {
createListLevel,
getOperationalBlocks,
isBlockGroupOfType,
mutateBlock,
parseValueWithUnit,
updateListMetadata,
} from 'roosterjs-content-model-dom';
import type {
ContentModelBlock,
ContentModelBlockFormat,
ContentModelBlockGroup,
ContentModelDocument,
ContentModelListItem,
ContentModelListLevel,
FormatContentModelContext,
ReadonlyContentModelBlock,
ReadonlyContentModelBlockGroup,
ReadonlyContentModelDocument,
ReadonlyContentModelListItem,
} from 'roosterjs-content-model-types';

const IndentStepInPixel = 40;
Expand All @@ -26,7 +28,7 @@ const IndentStepInPixel = 40;
* Set indentation for selected list items or paragraphs
*/
export function setModelIndentation(
model: ContentModelDocument,
model: ReadonlyContentModelDocument,
indentation: 'indent' | 'outdent',
length: number = IndentStepInPixel,
context?: FormatContentModelContext
Expand All @@ -37,7 +39,7 @@ export function setModelIndentation(
['TableCell']
);
const isIndent = indentation == 'indent';
const modifiedBlocks: ContentModelBlock[] = [];
const modifiedBlocks: ReadonlyContentModelBlock[] = [];

paragraphOrListItem.forEach(({ block, parent, path }) => {
if (isBlockGroupOfType<ContentModelListItem>(block, 'ListItem')) {
Expand Down Expand Up @@ -89,12 +91,12 @@ export function setModelIndentation(
}
}
} else if (block) {
let currentBlock: ContentModelBlock = block;
let currentParent: ContentModelBlockGroup = parent;
let currentBlock: ReadonlyContentModelBlock = block;
let currentParent: ReadonlyContentModelBlockGroup = parent;

while (currentParent && modifiedBlocks.indexOf(currentBlock) < 0) {
const index = path.indexOf(currentParent);
const { format } = currentBlock;
const { format } = mutateBlock(currentBlock);
const newValue = calculateMarginValue(format, isIndent, length);

if (newValue !== null) {
Expand Down Expand Up @@ -124,7 +126,7 @@ export function setModelIndentation(
return paragraphOrListItem.length > 0;
}

function isSelected(listItem: ContentModelListItem) {
function isSelected(listItem: ReadonlyContentModelListItem) {
return listItem.blocks.some(block => {
if (block.blockType == 'Paragraph') {
return block.segments.some(segment => segment.isSelected);
Expand All @@ -137,9 +139,9 @@ function isSelected(listItem: ContentModelListItem) {
* Otherwise, the margin of the first item will be changed, and the sub list will be created, creating a unintentional margin difference between the list items.
*/
function isMultilevelSelection(
model: ContentModelDocument,
listItem: ContentModelListItem,
parent: ContentModelBlockGroup
model: ReadonlyContentModelDocument,
listItem: ReadonlyContentModelListItem,
parent: ReadonlyContentModelBlockGroup
) {
const listIndex = parent.blocks.indexOf(listItem);
for (let i = listIndex - 1; i >= 0; i--) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@ import {
deleteSelection,
getClosestAncestorBlockGroupIndex,
setSelection,
mutateBlock,
} from 'roosterjs-content-model-dom';
import type {
ContentModelBlock,
ContentModelBlockGroup,
ContentModelDocument,
ContentModelEntity,
ContentModelParagraph,
FormatContentModelContext,
InsertEntityPosition,
InsertPoint,
ReadonlyContentModelBlock,
ShallowMutableContentModelBlock,
ShallowMutableContentModelBlockGroup,
ShallowMutableContentModelParagraph,
} from 'roosterjs-content-model-types';

/**
Expand All @@ -30,7 +32,7 @@ export function insertEntityModel(
context?: FormatContentModelContext,
insertPointOverride?: InsertPoint
) {
let blockParent: ContentModelBlockGroup | undefined;
let blockParent: ShallowMutableContentModelBlockGroup | undefined;
let blockIndex = -1;
let insertPoint: InsertPoint | null;

Expand All @@ -57,9 +59,10 @@ export function insertEntityModel(
position == 'root'
? getClosestAncestorBlockGroupIndex(path, ['TableCell', 'Document'])
: 0;
blockParent = path[pathIndex];
blockParent = mutateBlock(path[pathIndex]);

const child = path[pathIndex - 1];
const directChild: ContentModelBlock =
const directChild: ReadonlyContentModelBlock =
child?.blockGroupType == 'FormatContainer' ||
child?.blockGroupType == 'General' ||
child?.blockGroupType == 'ListItem'
Expand All @@ -71,16 +74,16 @@ export function insertEntityModel(
}

if (blockIndex >= 0 && blockParent) {
const blocksToInsert: ContentModelBlock[] = [];
let nextParagraph: ContentModelParagraph | undefined;
const blocksToInsert: ShallowMutableContentModelBlock[] = [];
let nextParagraph: ShallowMutableContentModelParagraph | undefined;

if (isBlock) {
const nextBlock = blockParent.blocks[blockIndex];

blocksToInsert.push(entityModel);

if (nextBlock?.blockType == 'Paragraph') {
nextParagraph = nextBlock;
nextParagraph = mutateBlock(nextBlock);
} else if (!nextBlock || nextBlock.blockType == 'Entity' || focusAfterEntity) {
nextParagraph = createParagraph(false /*isImplicit*/, {}, model.format);
nextParagraph.segments.push(createBr(model.format));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,6 @@ describe('adjustImageSelection', () => {
format: {},
src: 'img2',
dataset: {},
isSelectedAsImageSelection: false,
},
{
segmentType: 'Text',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,6 @@ describe('adjustLinkSelection', () => {
link: link,
dataset: {},
isSelected: true,
isSelectedAsImageSelection: false,
},
{
segmentType: 'Text',
Expand Down Expand Up @@ -228,7 +227,6 @@ describe('adjustLinkSelection', () => {
link: link,
dataset: {},
isSelected: true,
isSelectedAsImageSelection: false,
},
{
segmentType: 'Text',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ describe('removeLink', () => {
dataset: {},
format: {},
isSelected: true,
isSelectedAsImageSelection: false,
},
],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import {
getClosestAncestorBlockGroupIndex,
hasSelectionInBlock,
hasSelectionInBlockGroup,
mutateBlock,
} from 'roosterjs-content-model-dom';
import type {
ContentModelBlock,
DeleteSelectionContext,
DeleteSelectionStep,
ReadonlyContentModelBlock,
} from 'roosterjs-content-model-types';

function isEmptyBlock(block: ContentModelBlock | undefined): boolean {
function isEmptyBlock(block: ReadonlyContentModelBlock | undefined): boolean {
if (block && block.blockType == 'Paragraph') {
return block.segments.every(
segment => segment.segmentType !== 'SelectionMarker' && segment.segmentType == 'Br'
Expand Down Expand Up @@ -53,7 +54,7 @@ export const deleteEmptyList: DeleteSelectionStep = (context: DeleteSelectionCon
nextBlock &&
isEmptyBlock(nextBlock)
) {
item.levels = [];
mutateBlock(item).levels = [];
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { ContentModelBlock } from 'roosterjs-content-model-types';
import { mutateBlock } from '../common/mutate';
import type { ReadonlyContentModelBlock } from 'roosterjs-content-model-types';

/**
* For a given block, if it is a paragraph, set it to be not-implicit
* @param block The block to check
*/
export function setParagraphNotImplicit(block: ContentModelBlock) {
export function setParagraphNotImplicit(block: ReadonlyContentModelBlock) {
if (block.blockType == 'Paragraph' && block.isImplicit) {
block.isImplicit = false;
mutateBlock(block).isImplicit = false;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { isBlockEmpty } from './isEmpty';
import { mutateBlock } from './mutate';
import { normalizeParagraph } from './normalizeParagraph';
import { unwrapBlock } from './unwrapBlock';
import type { ContentModelBlockGroup } from 'roosterjs-content-model-types';
import type { ReadonlyContentModelBlockGroup } from 'roosterjs-content-model-types';

/**
* For a given content model, normalize it to make the model be consistent.
Expand All @@ -12,7 +13,7 @@ import type { ContentModelBlockGroup } from 'roosterjs-content-model-types';
* - For an empty block, remove it
* @param group The root level block group of content model to normalize
*/
export function normalizeContentModel(group: ContentModelBlockGroup) {
export function normalizeContentModel(group: ReadonlyContentModelBlockGroup) {
for (let i = group.blocks.length - 1; i >= 0; i--) {
const block = group.blocks[i];

Expand Down Expand Up @@ -40,7 +41,7 @@ export function normalizeContentModel(group: ContentModelBlockGroup) {
}

if (isBlockEmpty(block)) {
group.blocks.splice(i, 1);
mutateBlock(group).blocks.splice(i, 1);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@ import { areSameFormats } from '../../domToModel/utils/areSameFormats';
import { createBr } from '../creators/createBr';
import { isSegmentEmpty } from './isEmpty';
import { isWhiteSpacePreserved } from '../../domUtils/isWhiteSpacePreserved';
import { mutateBlock, mutateSegment } from './mutate';
import { normalizeAllSegments } from './normalizeSegment';
import type {
ContentModelParagraph,
ContentModelSegment,
ContentModelSegmentFormat,
ReadonlyContentModelParagraph,
ReadonlyContentModelSegment,
} from 'roosterjs-content-model-types';

/**
* @param paragraph The paragraph to normalize
* Normalize a paragraph. If it is empty, add a BR segment to make sure it can insert content
*/
export function normalizeParagraph(paragraph: ContentModelParagraph) {
export function normalizeParagraph(paragraph: ReadonlyContentModelParagraph) {
const segments = paragraph.segments;

if (!paragraph.isImplicit && segments.length > 0) {
Expand All @@ -24,7 +25,7 @@ export function normalizeParagraph(paragraph: ContentModelParagraph) {
last.segmentType == 'SelectionMarker' &&
(!secondLast || secondLast.segmentType == 'Br')
) {
segments.push(createBr(last.format));
mutateBlock(paragraph).segments.push(createBr(last.format));
} else if (segments.length > 1 && segments[segments.length - 1].segmentType == 'Br') {
const noMarkerSegments = segments.filter(x => x.segmentType != 'SelectionMarker');

Expand All @@ -34,7 +35,7 @@ export function normalizeParagraph(paragraph: ContentModelParagraph) {
noMarkerSegments.length > 1 &&
noMarkerSegments[noMarkerSegments.length - 2].segmentType != 'Br'
) {
segments.pop();
mutateBlock(paragraph).segments.pop();
}
}
}
Expand All @@ -50,20 +51,21 @@ export function normalizeParagraph(paragraph: ContentModelParagraph) {
moveUpSegmentFormat(paragraph);
}

function removeEmptySegments(block: ContentModelParagraph) {
function removeEmptySegments(block: ReadonlyContentModelParagraph) {
for (let j = block.segments.length - 1; j >= 0; j--) {
if (isSegmentEmpty(block.segments[j])) {
block.segments.splice(j, 1);
mutateBlock(block).segments.splice(j, 1);
}
}
}

function removeEmptyLinks(paragraph: ContentModelParagraph) {
function removeEmptyLinks(paragraph: ReadonlyContentModelParagraph) {
const marker = paragraph.segments.find(x => x.segmentType == 'SelectionMarker');
if (marker) {
const markerIndex = paragraph.segments.indexOf(marker);
const prev = paragraph.segments[markerIndex - 1];
const next = paragraph.segments[markerIndex + 1];

if (
(prev &&
!prev.link &&
Expand All @@ -76,7 +78,9 @@ function removeEmptyLinks(paragraph: ContentModelParagraph) {
!next.link &&
areSameFormats(next.format, marker.format))
) {
delete marker.link;
mutateSegment(paragraph, marker, mutableMarker => {
delete mutableMarker.link;
});
}
}
}
Expand All @@ -85,7 +89,7 @@ type FormatsToMoveUp = 'fontFamily' | 'fontSize' | 'textColor';
const formatsToMoveUp: FormatsToMoveUp[] = ['fontFamily', 'fontSize', 'textColor'];

// When all segments are sharing the same segment format (font name, size and color), we can move its format to paragraph
function moveUpSegmentFormat(paragraph: ContentModelParagraph) {
function moveUpSegmentFormat(paragraph: ReadonlyContentModelParagraph) {
if (!paragraph.decorator) {
const segments = paragraph.segments.filter(x => x.segmentType != 'SelectionMarker');
const target = paragraph.segmentFormat || {};
Expand All @@ -96,13 +100,13 @@ function moveUpSegmentFormat(paragraph: ContentModelParagraph) {
});

if (changed) {
paragraph.segmentFormat = target;
mutateBlock(paragraph).segmentFormat = target;
}
}
}

function internalMoveUpSegmentFormat(
segments: ContentModelSegment[],
segments: ReadonlyContentModelSegment[],
target: ContentModelSegmentFormat,
formatKey: FormatsToMoveUp
): boolean {
Expand Down
Loading
Loading