Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/microsoft/roosterjs into …
Browse files Browse the repository at this point in the history
…u/juliaroldi/set-alignment-content-model
  • Loading branch information
juliaroldi committed Mar 17, 2023
2 parents 3d29eed + b4d4550 commit a6cc71c
Show file tree
Hide file tree
Showing 49 changed files with 564 additions and 272 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,43 +12,38 @@ export const handleBlock: ContentModelBlockHandler<ContentModelBlock> = (
context: ModelToDomContext,
refNode: Node | null
) => {
function callHandler<T extends ContentModelBlock>(
block: T,
handler: ContentModelBlockHandler<T>
) {
handler(doc, parent, block, context, refNode);
}

const handlers = context.modelHandlers;

switch (block.blockType) {
case 'Table':
callHandler(block, handlers.table);
refNode = handlers.table(doc, parent, block, context, refNode);
break;
case 'Paragraph':
callHandler(block, handlers.paragraph);
refNode = handlers.paragraph(doc, parent, block, context, refNode);
break;
case 'Entity':
callHandler(block, handlers.entity);
refNode = handlers.entity(doc, parent, block, context, refNode);
break;
case 'Divider':
callHandler(block, handlers.divider);
refNode = handlers.divider(doc, parent, block, context, refNode);
break;
case 'BlockGroup':
switch (block.blockGroupType) {
case 'General':
callHandler(block, handlers.general);
refNode = handlers.general(doc, parent, block, context, refNode);
break;

case 'Quote':
callHandler(block, handlers.quote);
refNode = handlers.quote(doc, parent, block, context, refNode);
break;

case 'ListItem':
callHandler(block, handlers.listItem);
refNode = handlers.listItem(doc, parent, block, context, refNode);
break;
}

break;
}

return refNode;
};
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import { ContentModelBlock } from '../../publicTypes/block/ContentModelBlock';
import { ContentModelBlockGroup } from '../../publicTypes/group/ContentModelBlockGroup';
import { ContentModelBlockWithCache } from '../../publicTypes/block/ContentModelBlockWithCache';
import { ContentModelHandler } from '../../publicTypes/context/ContentModelHandler';
import { getEntityFromElement } from 'roosterjs-editor-dom';
import { isNodeOfType } from '../../domUtils/isNodeOfType';
import { ModelToDomContext } from '../../publicTypes/context/ModelToDomContext';
import { NodeType } from 'roosterjs-editor-types';

/**
* @internal
Expand Down Expand Up @@ -34,65 +29,17 @@ export const handleBlockGroupChildren: ContentModelHandler<ContentModelBlockGrou
listFormat.nodeStack = [];
}

const element = getCachedElement(childBlock);

// Check if there is cached element and if we can reuse it
if (element) {
if (element.parentNode == parent) {
// Remove nodes before the one we are hitting since they don't appear in Content Model at this position.
// But we don't want to touch entity since it would better to keep entity at its place unless it is removed
// In that case we will remove it after we have handled all other nodes
while (refNode && refNode != element && !isEntity(refNode)) {
refNode = remove(refNode);
}

if (refNode && refNode == element) {
refNode = refNode.nextSibling;
} else {
parent.insertBefore(element, refNode);
}
} else {
parent.insertBefore(element, refNode);
}

// No need to add entity delimiter here since entity delimiter is only for inline entity, but here we only handle block entity.

if (childBlock.blockType == 'BlockGroup') {
context.modelHandlers.blockGroupChildren(doc, element, childBlock, context);
}
} else {
context.modelHandlers.block(doc, parent, childBlock, context, refNode);
}
refNode = context.modelHandlers.block(doc, parent, childBlock, context, refNode);
});

// Remove all rest node if any since they don't appear in content model
while (refNode) {
refNode = remove(refNode);
const next = refNode.nextSibling;

refNode.parentNode?.removeChild(refNode);
refNode = next;
}
} finally {
listFormat.nodeStack = nodeStack;
}
};

function remove(node: Node) {
const next = node.nextSibling;
node.parentNode?.removeChild(node);

return next;
}

function isEntity(node: Node) {
return isNodeOfType(node, NodeType.Element) && !!getEntityFromElement(node);
}

function getCachedElement(block: ContentModelBlock): HTMLElement | undefined {
if ((block as ContentModelBlockWithCache).cachedElement) {
return (block as ContentModelBlockWithCache).cachedElement;
} else if (block.blockType == 'Entity') {
return block.wrapper;
} else if (block.blockType == 'BlockGroup' && block.blockGroupType == 'General') {
return block.element;
} else {
return undefined;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { applyFormat } from '../utils/applyFormat';
import { ContentModelBlockHandler } from '../../publicTypes/context/ContentModelHandler';
import { ContentModelDivider } from '../../publicTypes/block/ContentModelDivider';
import { ModelToDomContext } from '../../publicTypes/context/ModelToDomContext';
import { reuseCachedElement } from '../utils/reuseCachedElement';

/**
* @internal
Expand All @@ -13,10 +14,18 @@ export const handleDivider: ContentModelBlockHandler<ContentModelDivider> = (
context: ModelToDomContext,
refNode: Node | null
) => {
const element = doc.createElement(divider.tagName);
const element = divider.cachedElement;

divider.cachedElement = element;
parent.insertBefore(element, refNode);
if (element) {
refNode = reuseCachedElement(parent, element, refNode);
} else {
const element = doc.createElement(divider.tagName);

applyFormat(element, context.formatAppliers.divider, divider.format, context);
divider.cachedElement = element;
parent.insertBefore(element, refNode);

applyFormat(element, context.formatAppliers.divider, divider.format, context);
}

return refNode;
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ContentModelBlockHandler } from '../../publicTypes/context/ContentModel
import { ContentModelEntity } from '../../publicTypes/entity/ContentModelEntity';
import { Entity } from 'roosterjs-editor-types';
import { ModelToDomContext } from '../../publicTypes/context/ModelToDomContext';
import { reuseCachedElement } from '../utils/reuseCachedElement';
import {
addDelimiters,
commitEntity,
Expand Down Expand Up @@ -38,7 +39,7 @@ export const handleEntity: ContentModelBlockHandler<ContentModelEntity> = (
commitEntity(wrapper, entity.type, entity.isReadonly, entity.id);
}

parent.insertBefore(wrapper, refNode);
refNode = reuseCachedElement(parent, wrapper, refNode);

if (isInlineEntity && getObjectKeys(format).length > 0) {
const span = wrap(wrapper, 'span');
Expand All @@ -47,6 +48,10 @@ export const handleEntity: ContentModelBlockHandler<ContentModelEntity> = (
}

if (context.addDelimiterForEntity && isInlineEntity && isReadonly) {
addDelimiters(wrapper);
const [after] = addDelimiters(wrapper);

context.regularSelection.current.segment = after;
}

return refNode;
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ContentModelGeneralSegment } from '../../publicTypes/segment/ContentMod
import { isNodeOfType } from '../../domUtils/isNodeOfType';
import { ModelToDomContext } from '../../publicTypes/context/ModelToDomContext';
import { NodeType } from 'roosterjs-editor-types';
import { reuseCachedElement } from '../utils/reuseCachedElement';

/**
* @internal
Expand All @@ -16,9 +17,16 @@ export const handleGeneralModel: ContentModelBlockHandler<ContentModelGeneralBlo
context: ModelToDomContext,
refNode: Node | null
) => {
const element = group.element.cloneNode();
let element: Node = group.element;

parent.insertBefore(element, refNode);
if (refNode && element.parentNode == parent) {
refNode = reuseCachedElement(parent, element, refNode);
} else {
element = element.cloneNode();
group.element = element as HTMLElement;

parent.insertBefore(element, refNode);
}

if (isGeneralSegment(group) && isNodeOfType(element, NodeType.Element)) {
if (!group.element.firstChild) {
Expand All @@ -31,6 +39,8 @@ export const handleGeneralModel: ContentModelBlockHandler<ContentModelGeneralBlo
}

context.modelHandlers.blockGroupChildren(doc, element, group, context);

return refNode;
};

function isGeneralSegment(block: ContentModelGeneralBlock): block is ContentModelGeneralSegment {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ export const handleList: ContentModelBlockHandler<ContentModelListItem> = (

nodeStack.push({ node: newList, ...level });
}

return refNode;
};

function handleMetadata(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const handleListItem: ContentModelBlockHandler<ContentModelListItem> = (
context: ModelToDomContext,
refNode: Node | null
) => {
context.modelHandlers.list(doc, parent, listItem, context, refNode);
refNode = context.modelHandlers.list(doc, parent, listItem, context, refNode);

const { nodeStack } = context.listFormat;

Expand All @@ -42,4 +42,6 @@ export const handleListItem: ContentModelBlockHandler<ContentModelListItem> = (

unwrap(li);
}

return refNode;
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ContentModelParagraph } from '../../publicTypes/block/ContentModelParag
import { CreateElementData } from 'roosterjs-editor-types';
import { getObjectKeys, unwrap, wrap } from 'roosterjs-editor-dom';
import { ModelToDomContext } from '../../publicTypes/context/ModelToDomContext';
import { reuseCachedElement } from '../utils/reuseCachedElement';
import { stackFormat } from '../utils/stackFormat';

const DefaultParagraphTag = 'div';
Expand All @@ -22,51 +23,59 @@ export const handleParagraph: ContentModelBlockHandler<ContentModelParagraph> =
context: ModelToDomContext,
refNode: Node | null
) => {
stackFormat(context, paragraph.decorator?.tagName || null, () => {
const needParagraphWrapper =
!paragraph.isImplicit ||
!!paragraph.decorator ||
(getObjectKeys(paragraph.format).length > 0 &&
paragraph.segments.some(segment => segment.segmentType != 'SelectionMarker'));
const element = paragraph.cachedElement;

let container = doc.createElement(paragraph.decorator?.tagName || DefaultParagraphTag);
if (element) {
refNode = reuseCachedElement(parent, element, refNode);
} else {
stackFormat(context, paragraph.decorator?.tagName || null, () => {
const needParagraphWrapper =
!paragraph.isImplicit ||
!!paragraph.decorator ||
(getObjectKeys(paragraph.format).length > 0 &&
paragraph.segments.some(segment => segment.segmentType != 'SelectionMarker'));

parent.insertBefore(container, refNode);
let container = doc.createElement(paragraph.decorator?.tagName || DefaultParagraphTag);

if (needParagraphWrapper) {
applyFormat(container, context.formatAppliers.block, paragraph.format, context);
}
parent.insertBefore(container, refNode);

if (paragraph.decorator) {
applyFormat(
container,
context.formatAppliers.segmentOnBlock,
paragraph.decorator.format,
context
);
}
if (needParagraphWrapper) {
applyFormat(container, context.formatAppliers.block, paragraph.format, context);
}

let pre: HTMLElement | undefined;
if (paragraph.decorator) {
applyFormat(
container,
context.formatAppliers.segmentOnBlock,
paragraph.decorator.format,
context
);
}

// Need some special handling for PRE tag in order to cache the correct element.
// TODO: Consider use decorator to handle PRE tag
if (paragraph.format.whiteSpace == 'pre') {
pre = wrap(container, Pre);
}
let pre: HTMLElement | undefined;

context.regularSelection.current = {
block: needParagraphWrapper ? container : container.parentNode,
segment: null,
};
// Need some special handling for PRE tag in order to cache the correct element.
// TODO: Consider use decorator to handle PRE tag
if (paragraph.format.whiteSpace == 'pre') {
pre = wrap(container, Pre);
}

paragraph.segments.forEach(segment => {
context.modelHandlers.segment(doc, container, segment, context);
context.regularSelection.current = {
block: needParagraphWrapper ? container : container.parentNode,
segment: null,
};

paragraph.segments.forEach(segment => {
context.modelHandlers.segment(doc, container, segment, context);
});

if (needParagraphWrapper) {
paragraph.cachedElement = pre || container;
} else {
unwrap(container);
}
});
}

if (needParagraphWrapper) {
paragraph.cachedElement = pre || container;
} else {
unwrap(container);
}
});
return refNode;
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ContentModelBlockHandler } from '../../publicTypes/context/ContentModel
import { ContentModelQuote } from '../../publicTypes/group/ContentModelQuote';
import { isBlockGroupEmpty } from '../../modelApi/common/isEmpty';
import { ModelToDomContext } from '../../publicTypes/context/ModelToDomContext';
import { reuseCachedElement } from '../utils/reuseCachedElement';
import { stackFormat } from '../utils/stackFormat';

const QuoteTagName = 'blockquote';
Expand All @@ -17,7 +18,13 @@ export const handleQuote: ContentModelBlockHandler<ContentModelQuote> = (
context: ModelToDomContext,
refNode: Node | null
) => {
if (!isBlockGroupEmpty(quote)) {
let element = quote.cachedElement;

if (element) {
refNode = reuseCachedElement(parent, element, refNode);

context.modelHandlers.blockGroupChildren(doc, element, quote, context);
} else if (!isBlockGroupEmpty(quote)) {
const blockQuote = doc.createElement(QuoteTagName);

quote.cachedElement = blockQuote;
Expand All @@ -35,4 +42,6 @@ export const handleQuote: ContentModelBlockHandler<ContentModelQuote> = (

context.modelHandlers.blockGroupChildren(doc, blockQuote, quote, context);
}

return refNode;
};
Loading

0 comments on commit a6cc71c

Please sign in to comment.