Skip to content

Commit

Permalink
Content Model Cache improvement - Step 4: Port "readonly" functions (#…
Browse files Browse the repository at this point in the history
…2643)

* Readonly types (3rd try

* Improve

* fix build

* Improve

* improve

* Improve

* Add shallow mutable type

* improve

* Improve

* improve

* improve

* add test

* Readonly types step 2

* Readonly types step 3

* Readonly type step 4

* add test

* Improve

* improve

* improve

* improve

* improve

* Improve

* improve

* fix test

* improve
  • Loading branch information
JiuqingSong committed May 22, 2024
1 parent ecdb478 commit bad75d9
Show file tree
Hide file tree
Showing 18 changed files with 154 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { ApiPaneProps } from '../ApiPaneProps';
import { insertEntity } from 'roosterjs-content-model-api';
import { trustedHTMLHandler } from '../../../../utils/trustedHTMLHandler';
import {
ContentModelBlockGroup,
ContentModelEntity,
InsertEntityOptions,
ReadonlyContentModelBlockGroup,
} from 'roosterjs-content-model-types';

const styles = require('./InsertEntityPane.scss');
Expand Down Expand Up @@ -155,7 +155,7 @@ export default class InsertEntityPane extends React.Component<ApiPaneProps, Inse
};
}

function findAllEntities(group: ContentModelBlockGroup, result: ContentModelEntity[]) {
function findAllEntities(group: ReadonlyContentModelBlockGroup, result: ContentModelEntity[]) {
group.blocks.forEach(block => {
switch (block.blockType) {
case 'BlockGroup':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,22 @@ import { findListItemsInSameThread } from './findListItemsInSameThread';
import {
getAutoListStyleType,
getClosestAncestorBlockGroupIndex,
getListMetadata,
getOrderedListNumberStr,
updateListMetadata,
} from 'roosterjs-content-model-dom';
import type {
AnnounceData,
ContentModelBlockGroup,
ContentModelListItem,
ReadonlyContentModelBlockGroup,
ReadonlyContentModelListItem,
} from 'roosterjs-content-model-types';

/**
* Get announce data for list item
* @param path Content model path that include the list item
* @returns Announce data of current list item if any, or null
*/
export function getListAnnounceData(path: ContentModelBlockGroup[]): AnnounceData | null {
export function getListAnnounceData(path: ReadonlyContentModelBlockGroup[]): AnnounceData | null {
const index = getClosestAncestorBlockGroupIndex(path, ['ListItem'], ['TableCell']);

if (index >= 0) {
Expand All @@ -27,7 +28,7 @@ export function getListAnnounceData(path: ContentModelBlockGroup[]): AnnounceDat
return null;
} else if (level.listType == 'OL') {
const listNumber = getListNumber(path, listItem);
const metadata = updateListMetadata(level);
const metadata = getListMetadata(level);
const listStyle = getAutoListStyleType(
'OL',
metadata ?? {},
Expand All @@ -51,7 +52,10 @@ export function getListAnnounceData(path: ContentModelBlockGroup[]): AnnounceDat
}
}

function getListNumber(path: ContentModelBlockGroup[], listItem: ContentModelListItem) {
function getListNumber(
path: ReadonlyContentModelBlockGroup[],
listItem: ReadonlyContentModelListItem
) {
const items = findListItemsInSameThread(path[path.length - 1], listItem);
let listNumber = 0;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { ContentModelTableRow } from 'roosterjs-content-model-types';
import type { ReadonlyContentModelTableRow } from 'roosterjs-content-model-types';

/**
* @internal
*/
export function canMergeCells(
rows: ContentModelTableRow[],
rows: ReadonlyContentModelTableRow[],
firstRow: number,
firstCol: number,
lastRow: number,
Expand Down Expand Up @@ -40,7 +40,11 @@ export function canMergeCells(
return noSpanAbove && noSpanLeft && noDifferentBelowSpan && noDifferentRightSpan;
}

function getBelowSpanCount(rows: ContentModelTableRow[], rowIndex: number, colIndex: number) {
function getBelowSpanCount(
rows: ReadonlyContentModelTableRow[],
rowIndex: number,
colIndex: number
) {
let spanCount = 0;

for (let row = rowIndex + 1; row < rows.length; row++) {
Expand All @@ -54,7 +58,11 @@ function getBelowSpanCount(rows: ContentModelTableRow[], rowIndex: number, colIn
return spanCount;
}

function getRightSpanCount(rows: ContentModelTableRow[], rowIndex: number, colIndex: number) {
function getRightSpanCount(
rows: ReadonlyContentModelTableRow[],
rowIndex: number,
colIndex: number
) {
let spanCount = 0;

for (let col = colIndex + 1; col < rows[rowIndex]?.cells.length; col++) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { formatImageWithContentModel } from '../utils/formatImageWithContentModel';
import { readFile, updateImageMetadata } from 'roosterjs-content-model-dom';
import { getImageMetadata, readFile } from 'roosterjs-content-model-dom';
import type { ContentModelImage, IEditor } from 'roosterjs-content-model-types';

/**
Expand All @@ -14,7 +14,7 @@ export function changeImage(editor: IEditor, file: File) {
readFile(file, dataUrl => {
if (dataUrl && !editor.isDisposed() && selection?.type === 'image') {
formatImageWithContentModel(editor, 'changeImage', (image: ContentModelImage) => {
const originalSrc = updateImageMetadata(image)?.src ?? '';
const originalSrc = getImageMetadata(image)?.src ?? '';
const previousSrc = image.src;

image.src = dataUrl;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {
ContentModelSegmentFormat,
IEditor,
MergeModelOption,
ReadonlyContentModelDocument,
} from 'roosterjs-content-model-types';

const EmptySegmentFormat: Required<ContentModelSegmentFormat> = {
Expand All @@ -38,7 +39,7 @@ const CloneOption: CloneModelOptions = {
/**
* @internal
*/
export function cloneModelForPaste(model: ContentModelDocument) {
export function cloneModelForPaste(model: ReadonlyContentModelDocument) {
return cloneModel(model, CloneOption);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { ChangedEntity, ContentModelBlockGroup } from 'roosterjs-content-model-types';
import type { ChangedEntity, ReadonlyContentModelBlockGroup } from 'roosterjs-content-model-types';

/**
* @internal
*/
export function findAllEntities(group: ContentModelBlockGroup, entities: ChangedEntity[]) {
export function findAllEntities(group: ReadonlyContentModelBlockGroup, entities: ChangedEntity[]) {
group.blocks.forEach(block => {
switch (block.blockType) {
case 'BlockGroup':
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type {
ContentModelBlock,
ShallowMutableContentModelBlock,
ShallowMutableContentModelBlockGroup,
} from 'roosterjs-content-model-types';

Expand All @@ -8,6 +8,9 @@ import type {
* @param group The block group to add block into
* @param block The block to add
*/
export function addBlock(group: ShallowMutableContentModelBlockGroup, block: ContentModelBlock) {
export function addBlock(
group: ShallowMutableContentModelBlockGroup,
block: ShallowMutableContentModelBlock
) {
group.blocks.push(block);
}
32 changes: 16 additions & 16 deletions packages/roosterjs-content-model-dom/lib/modelApi/common/isEmpty.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type {
ContentModelBlock,
ContentModelBlockGroup,
ContentModelSegment,
ReadonlyContentModelBlock,
ReadonlyContentModelBlockGroup,
ReadonlyContentModelSegment,
} from 'roosterjs-content-model-types';

/**
* @internal
*/
export function isBlockEmpty(block: ContentModelBlock): boolean {
export function isBlockEmpty(block: ReadonlyContentModelBlock): boolean {
switch (block.blockType) {
case 'Paragraph':
return block.segments.length == 0;
Expand All @@ -29,7 +29,7 @@ export function isBlockEmpty(block: ContentModelBlock): boolean {
/**
* @internal
*/
export function isBlockGroupEmpty(group: ContentModelBlockGroup): boolean {
export function isBlockGroupEmpty(group: ReadonlyContentModelBlockGroup): boolean {
switch (group.blockGroupType) {
case 'FormatContainer':
// Format Container of DIV is a container for style, so we always treat it as not empty
Expand All @@ -51,7 +51,7 @@ export function isBlockGroupEmpty(group: ContentModelBlockGroup): boolean {
/**
* @internal
*/
export function isSegmentEmpty(segment: ContentModelSegment): boolean {
export function isSegmentEmpty(segment: ReadonlyContentModelSegment): boolean {
switch (segment.segmentType) {
case 'Text':
return !segment.text;
Expand All @@ -69,7 +69,7 @@ export function isSegmentEmpty(segment: ContentModelSegment): boolean {
* @returns true if the model is empty.
*/
export function isEmpty(
model: ContentModelBlock | ContentModelBlockGroup | ContentModelSegment
model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment
): boolean {
if (isBlockGroup(model)) {
return isBlockGroupEmpty(model);
Expand All @@ -83,19 +83,19 @@ export function isEmpty(
}

function isSegment(
model: ContentModelBlock | ContentModelBlockGroup | ContentModelSegment
): model is ContentModelSegment {
return typeof (<ContentModelSegment>model).segmentType === 'string';
model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment
): model is ReadonlyContentModelSegment {
return typeof (<ReadonlyContentModelSegment>model).segmentType === 'string';
}

function isBlock(
model: ContentModelBlock | ContentModelBlockGroup | ContentModelSegment
): model is ContentModelBlock {
return typeof (<ContentModelBlock>model).blockType === 'string';
model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment
): model is ReadonlyContentModelBlock {
return typeof (<ReadonlyContentModelBlock>model).blockType === 'string';
}

function isBlockGroup(
model: ContentModelBlock | ContentModelBlockGroup | ContentModelSegment
): model is ContentModelBlockGroup {
return typeof (<ContentModelBlockGroup>model).blockGroupType === 'string';
model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment
): model is ReadonlyContentModelBlockGroup {
return typeof (<ReadonlyContentModelBlockGroup>model).blockGroupType === 'string';
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import type { ContentModelSegment, ContentModelSegmentFormat } from 'roosterjs-content-model-types';
import type {
ContentModelSegmentFormat,
ReadonlyContentModelSegment,
} from 'roosterjs-content-model-types';

/**
* Get the text format of a segment, this function will return only format that is applicable to text
* @param segment The segment to get format from
* @returns
*/
export function getSegmentTextFormat(segment: ContentModelSegment): ContentModelSegmentFormat {
export function getSegmentTextFormat(
segment: ReadonlyContentModelSegment
): ContentModelSegmentFormat {
const { fontFamily, fontSize, textColor, backgroundColor, letterSpacing, lineHeight } =
segment?.format ?? {};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { extractBorderValues } from '../../domUtils/style/borderValues';
import { getClosestAncestorBlockGroupIndex } from './getClosestAncestorBlockGroupIndex';
import { getTableMetadata } from '../metadata/updateTableMetadata';
import { isBold } from '../../domUtils/style/isBold';
import { iterateSelections } from '../selection/iterateSelections';
import { parseValueWithUnit } from '../../formatHandlers/utils/parseValueWithUnit';
import { updateTableMetadata } from '../metadata/updateTableMetadata';
import type {
ContentModelFormatState,
ContentModelBlock,
ContentModelBlockGroup,
ContentModelDocument,
ContentModelFormatContainer,
ContentModelImage,
ContentModelListItem,
ContentModelParagraph,
ContentModelSegmentFormat,
TableSelectionContext,
ReadonlyContentModelBlockGroup,
ReadonlyContentModelBlock,
ReadonlyContentModelImage,
ReadonlyTableSelectionContext,
ReadonlyContentModelParagraph,
ReadonlyContentModelFormatContainer,
ReadonlyContentModelListItem,
ReadonlyContentModelDocument,
} from 'roosterjs-content-model-types';

/**
Expand All @@ -24,12 +24,12 @@ import type {
* @param formatState Existing format state object, used for receiving the result
*/
export function retrieveModelFormatState(
model: ContentModelDocument,
model: ReadonlyContentModelDocument,
pendingFormat: ContentModelSegmentFormat | null,
formatState: ContentModelFormatState
) {
let firstTableContext: TableSelectionContext | undefined;
let firstBlock: ContentModelBlock | undefined;
let firstTableContext: ReadonlyTableSelectionContext | undefined;
let firstBlock: ReadonlyContentModelBlock | undefined;
let isFirst = true;
let isFirstImage = true;
let isFirstSegment = true;
Expand All @@ -56,10 +56,11 @@ export function retrieveModelFormatState(
// Segment formats
segments?.forEach(segment => {
if (isFirstSegment || segment.segmentType != 'SelectionMarker') {
const modelFormat = Object.assign({}, model.format);
delete modelFormat?.italic;
delete modelFormat?.underline;
delete modelFormat?.fontWeight;
const modelFormat = { ...model.format };

delete modelFormat.italic;
delete modelFormat.underline;
delete modelFormat.fontWeight;

retrieveSegmentFormat(
formatState,
Expand Down Expand Up @@ -165,7 +166,7 @@ function retrieveSegmentFormat(

function retrieveParagraphFormat(
result: ContentModelFormatState,
paragraph: ContentModelParagraph,
paragraph: ReadonlyContentModelParagraph,
isFirst: boolean
) {
const headingLevel = parseInt((paragraph.decorator?.tagName || '').substring(1));
Expand All @@ -180,14 +181,14 @@ function retrieveParagraphFormat(

function retrieveStructureFormat(
result: ContentModelFormatState,
path: ContentModelBlockGroup[],
path: ReadonlyContentModelBlockGroup[],
isFirst: boolean
) {
const listItemIndex = getClosestAncestorBlockGroupIndex(path, ['ListItem'], []);
const containerIndex = getClosestAncestorBlockGroupIndex(path, ['FormatContainer'], []);

if (listItemIndex >= 0) {
const listItem = path[listItemIndex] as ContentModelListItem;
const listItem = path[listItemIndex] as ReadonlyContentModelListItem;
const listType = listItem?.levels[listItem.levels.length - 1]?.listType;

mergeValue(result, 'isBullet', listType == 'UL', isFirst);
Expand All @@ -198,13 +199,16 @@ function retrieveStructureFormat(
result,
'isBlockQuote',
containerIndex >= 0 &&
(path[containerIndex] as ContentModelFormatContainer)?.tagName == 'blockquote',
(path[containerIndex] as ReadonlyContentModelFormatContainer)?.tagName == 'blockquote',
isFirst
);
}

function retrieveTableFormat(tableContext: TableSelectionContext, result: ContentModelFormatState) {
const tableFormat = updateTableMetadata(tableContext.table);
function retrieveTableFormat(
tableContext: ReadonlyTableSelectionContext,
result: ContentModelFormatState
) {
const tableFormat = getTableMetadata(tableContext.table);

result.isInTable = true;
result.tableHasHeader = tableContext.table.rows.some(row =>
Expand All @@ -216,7 +220,7 @@ function retrieveTableFormat(tableContext: TableSelectionContext, result: Conten
}
}

function retrieveImageFormat(image: ContentModelImage, result: ContentModelFormatState) {
function retrieveImageFormat(image: ReadonlyContentModelImage, result: ContentModelFormatState) {
const { format } = image;
const borderKey = 'borderTop';
const extractedBorder = extractBorderValues(format[borderKey]);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { hasSelectionInBlockGroup } from '../selection/hasSelectionInBlockGroup';
import type { ContentModelTable, TableSelectionCoordinates } from 'roosterjs-content-model-types';
import type {
ReadonlyContentModelTable,
TableSelectionCoordinates,
} from 'roosterjs-content-model-types';

/**
* Get selection coordinates of a table. If there is no selection, return null
* @param table The table model to get selection from
*/
export function getSelectedCells(table: ContentModelTable): TableSelectionCoordinates | null {
export function getSelectedCells(
table: ReadonlyContentModelTable
): TableSelectionCoordinates | null {
let firstRow = -1;
let firstColumn = -1;
let lastRow = -1;
Expand Down
Loading

0 comments on commit bad75d9

Please sign in to comment.