Skip to content

Commit

Permalink
optimize
Browse files Browse the repository at this point in the history
  • Loading branch information
JiuqingSong committed Sep 25, 2024
1 parent e3bc1f3 commit 50d7834
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 51 deletions.
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { applyFormat } from '../utils/applyFormat';
import { getObjectKeys } from '../../domUtils/getObjectKeys';
import { handleSegments } from './handleSegments';
import { optimize } from '../optimizers/optimize';
import { reuseCachedElement } from '../../domUtils/reuseCachedElement';
import { stackFormat } from '../utils/stackFormat';
import { unwrap } from '../../domUtils/unwrap';
import type {
ContentModelBlockHandler,
ContentModelParagraph,
ModelToDomContext,
} from 'roosterjs-content-model-types';

const DefaultParagraphTag = 'div';
Expand All @@ -16,16 +16,19 @@ const DefaultParagraphTag = 'div';
* @internal
*/
export const handleParagraph: ContentModelBlockHandler<ContentModelParagraph> = (
doc: Document,
parent: Node,
paragraph: ContentModelParagraph,
context: ModelToDomContext,
refNode: Node | null
doc,
parent,
paragraph,
context,
refNode
) => {
let container = context.allowCacheElement ? paragraph.cachedElement : undefined;
let cachedElement = context.allowCacheElement ? paragraph.cachedElement : undefined;

Check failure on line 25 in packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleParagraph.ts

View workflow job for this annotation

GitHub Actions / build

'cachedElement' is never reassigned. Use 'const' instead

if (container && paragraph.segments.every(x => x.segmentType != 'General' && !x.isSelected)) {
refNode = reuseCachedElement(parent, container, refNode);
if (
cachedElement &&
paragraph.segments.every(x => x.segmentType != 'General' && !x.isSelected)
) {
refNode = reuseCachedElement(parent, cachedElement, refNode);
} else {
stackFormat(context, paragraph.decorator?.tagName || null, () => {
const needParagraphWrapper =
Expand All @@ -39,8 +42,9 @@ export const handleParagraph: ContentModelBlockHandler<ContentModelParagraph> =
...paragraph.segmentFormat,
}
: {};

container = doc.createElement(paragraph.decorator?.tagName || DefaultParagraphTag);
const container = doc.createElement(
paragraph.decorator?.tagName || DefaultParagraphTag
);

parent.insertBefore(container, refNode);

Expand All @@ -49,42 +53,10 @@ export const handleParagraph: ContentModelBlockHandler<ContentModelParagraph> =
segment: null,
};

const handleSegments = () => {
const parent = container;

if (parent) {
const firstSegment = paragraph.segments[0];

if (firstSegment?.segmentType == 'SelectionMarker') {
// Make sure there is a segment created before selection marker.
// If selection marker is the first selected segment in a paragraph, create a dummy text node,
// so after rewrite, the regularSelection object can have a valid segment object set to the text node.
context.modelHandlers.text(
doc,
parent,
{
...firstSegment,
segmentType: 'Text',
text: '',
},
context,
[]
);
}

paragraph.segments.forEach(segment => {
const newSegments: Node[] = [];
context.modelHandlers.segment(doc, parent, segment, context, newSegments);

newSegments.forEach(node => {
context.domIndexer?.onSegment(node, paragraph, [segment]);
});
});
}
};

if (needParagraphWrapper) {
stackFormat(context, formatOnWrapper, handleSegments);
stackFormat(context, formatOnWrapper, () => {
handleSegments(doc, container, paragraph, context);
});

applyFormat(container, context.formatAppliers.block, paragraph.format, context);
applyFormat(container, context.formatAppliers.container, paragraph.format, context);
Expand All @@ -95,10 +67,10 @@ export const handleParagraph: ContentModelBlockHandler<ContentModelParagraph> =
context
);
} else {
handleSegments();
handleSegments(doc, container, paragraph, context);
}

optimize(container);
// optimize(container);

// It is possible the next sibling node is changed during processing child segments
// e.g. When this paragraph is an implicit paragraph and it contains an inline entity segment
Expand All @@ -119,7 +91,6 @@ export const handleParagraph: ContentModelBlockHandler<ContentModelParagraph> =
}
} else {
unwrap(container);
container = undefined;
}
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { areSameFormats } from '../../domToModel/utils/areSameFormats';
import type {
ContentModelCode,
ContentModelLink,
ContentModelSegment,
ContentModelSegmentHandler,
ContentModelText,
ModelToDomTextContext,
ModelToDomTextContextItem,
} from 'roosterjs-content-model-types';

/**
Expand All @@ -24,7 +30,15 @@ export const handleSegment: ContentModelSegmentHandler<ContentModelSegment> = (

switch (segment.segmentType) {
case 'Text':
context.modelHandlers.text(doc, parent, segment, context, segmentNodes);
let txtToReuse: Text | null = tryReuseTextNode(context.textContext, segment);

Check failure on line 33 in packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleSegment.ts

View workflow job for this annotation

GitHub Actions / build

'txtToReuse' is never reassigned. Use 'const' instead

if (txtToReuse) {
txtToReuse.nodeValue += segment.text;
// Handle selection
// Update index
} else {
context.modelHandlers.text(doc, parent, segment, context, segmentNodes);
}
break;

case 'Br':
Expand All @@ -44,6 +58,10 @@ export const handleSegment: ContentModelSegmentHandler<ContentModelSegment> = (
break;
}

if (segment.segmentType != 'Text') {
delete context.textContext;
}

// If end position is not set, or it is not finalized, and current segment is still in selection, set end position
// If there is other selection, we will overwrite regularSelection.end when we process that segment
if (segment.isSelected && regularSelection.start) {
Expand All @@ -52,3 +70,45 @@ export const handleSegment: ContentModelSegmentHandler<ContentModelSegment> = (
};
}
};

function tryReuseTextNode(
textContext: ModelToDomTextContextItem | undefined,
segment: ContentModelText
): Text | null {
if (!textContext) {
return null;
}

const { lastSegment, lastTextNode } = textContext;

return !areSameFormats(lastSegment.format, segment.format) ||
!areSameLinks(lastSegment.link, segment.link) ||
!areSameCodes(lastSegment.code, segment.code)
? null
: lastTextNode;
}

function areSameLinks(
link1: ContentModelLink | undefined,
link2: ContentModelLink | undefined
): boolean {
if (link1 && link2) {
return (
areSameFormats(link1.format, link2.format) &&
areSameFormats(link1.dataset, link2.dataset)
);
} else {
return !(link1 || link2);
}
}

function areSameCodes(
code1: ContentModelCode | undefined,
code2: ContentModelCode | undefined
): boolean {
if (code1 && code2) {
return areSameFormats(code1.format, code2.format);
} else {
return !(code1 || code2);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { ContentModelParagraph, ModelToDomContext } from 'roosterjs-content-model-types';

/**
* @internal
*/
export function handleSegments(
doc: Document,
parent: Node,
paragraph: ContentModelParagraph,
context: ModelToDomContext
) {
const firstSegment = paragraph.segments[0];

if (firstSegment?.segmentType == 'SelectionMarker') {
// Make sure there is a segment created before selection marker.
// If selection marker is the first selected segment in a paragraph, create a dummy text node,
// so after rewrite, the regularSelection object can have a valid segment object set to the text node.
context.modelHandlers.text(
doc,
parent,
{
...firstSegment,
segmentType: 'Text',
text: '',
},
context,
[]
);
}

paragraph.segments.forEach(segment => {
const newSegments: Node[] = [];
context.modelHandlers.segment(doc, parent, segment, context, newSegments);

newSegments.forEach(node => {
context.domIndexer?.onSegment(node, paragraph, [segment]);
});
});

delete context.textContext;
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const handleText: ContentModelSegmentHandler<ContentModelText> = (
element.appendChild(txt);

context.formatAppliers.text.forEach(applier => applier(segment.format, txt, context));
context.textContext = { lastSegment: segment, lastTextNode: txt };

handleSegmentCommon(doc, txt, element, segment, context, segmentNodes);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { EditorContext } from './EditorContext';
import type { ModelToDomFormatContext } from './ModelToDomFormatContext';
import type { ModelToDomSelectionContext } from './ModelToDomSelectionContext';
import type { ModelToDomSettings } from './ModelToDomSettings';
import type { ModelToDomTextContext } from './ModelToDomTextContext';

/**
* Context of Model to DOM conversion, used for generate HTML DOM tree according to current context
Expand All @@ -10,4 +11,5 @@ export interface ModelToDomContext
extends EditorContext,
ModelToDomSelectionContext,
ModelToDomFormatContext,
ModelToDomSettings {}
ModelToDomSettings,
ModelToDomTextContext {}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ export interface ModelToDomBlockAndSegmentNode {
* Segment node of this position. When provided, it represents the position right after this node
*/
segment: Node | null;

/**
*
*/
textOffset?: number;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { ContentModelText } from '../contentModel/segment/ContentModelText';

/**
*
*/
export interface ModelToDomTextContextItem {
/**
*
*/
lastSegment: ContentModelText;

/**
*
*/
lastTextNode: Text;
}

/**
*
*/
export interface ModelToDomTextContext {
/**
*
*/
textContext?: ModelToDomTextContextItem;
}
1 change: 1 addition & 0 deletions packages/roosterjs-content-model-types/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ export {
DomToModelDecoratorContext,
DomToModelListFormat,
} from './context/DomToModelFormatContext';
export { ModelToDomTextContext, ModelToDomTextContextItem } from './context/ModelToDomTextContext';
export { ModelToDomContext } from './context/ModelToDomContext';
export {
ModelToDomBlockAndSegmentNode,
Expand Down

0 comments on commit 50d7834

Please sign in to comment.