Skip to content

Commit

Permalink
Improve
Browse files Browse the repository at this point in the history
  • Loading branch information
JiuqingSong committed Sep 20, 2023
1 parent 5afa996 commit a3ec520
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 43 deletions.
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
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,46 +1,96 @@
import { createSelectionMarker, createText, isNodeOfType } from 'roosterjs-content-model-dom';
import { NodeType, SelectionRangeEx, SelectionRangeTypes } from 'roosterjs-editor-types';
import { setSelection } from '../../modelApi/selection/setSelection';
import {
NodeType,
SelectionRangeEx,
SelectionRangeTypes,
TableSelection,
} from 'roosterjs-editor-types';
import {
ContentModelDocument,
ContentModelDomIndexer,
ContentModelParagraph,
ContentModelSegment,
ContentModelSelectionMarker,
ContentModelTable,
ContentModelTableRow,
ContentModelText,
Selectable,
} from 'roosterjs-content-model-types';

interface SegmentIndexItem {
interface SegmentItem {
paragraph: ContentModelParagraph;
segments: ContentModelSegment[];
}

interface IndexedNode extends Node {
segmentIndex: SegmentIndexItem;
interface TableItem {
tableRows: ContentModelTableRow[];
}

interface IndexedSegmentNode extends Node {
__roosterjsContentModel: SegmentItem;
}

interface IndexedTableElement extends HTMLTableElement {
__roosterjsContentModel: TableItem;
}

const UnSelectedCoordinates: TableSelection = {
firstCell: {
x: -1,
y: -1,
},
lastCell: {
x: -1,
y: -1,
},
};

function isIndexedSegment(node: Node): node is IndexedSegmentNode {
const { paragraph, segments } = (node as IndexedSegmentNode).__roosterjsContentModel ?? {};

return (
paragraph &&
paragraph.blockType == 'Paragraph' &&
Array.isArray(paragraph.segments) &&
Array.isArray(segments) &&
segments.length > 0
);
}

function onSegment(node: Node, paragraph: ContentModelParagraph, segment: ContentModelSegment[]) {
const indexedText = node as IndexedNode;
indexedText.segmentIndex = {
function isIndexedTable(element: HTMLTableElement): element is IndexedTableElement {
const { tableRows } = (element as IndexedTableElement).__roosterjsContentModel ?? {};

return Array.isArray(tableRows) && tableRows.every(row => Array.isArray(row.cells));
}

function onSegment(
segmentNode: Node,
paragraph: ContentModelParagraph,
segment: ContentModelSegment[]
) {
const indexedText = segmentNode as IndexedSegmentNode;
indexedText.__roosterjsContentModel = {
paragraph,
segments: segment,
};
}

function onParagraph(node: Node) {
function onParagraph(paragraphElement: HTMLElement) {
let previousText: Text | null = null;

for (let child = node.firstChild; child; child = child.nextSibling) {
for (let child = paragraphElement.firstChild; child; child = child.nextSibling) {
if (isNodeOfType(child, NodeType.Text)) {
if (!previousText) {
previousText = child;
} else {
const item = isIndexedNode(previousText) ? previousText.segmentIndex : undefined;
const item = isIndexedSegment(previousText)
? previousText.__roosterjsContentModel
: undefined;

if (item && isIndexedNode(child)) {
item.segments = item.segments.concat(child.segmentIndex.segments);
child.segmentIndex.segments = [];
if (item && isIndexedSegment(child)) {
item.segments = item.segments.concat(child.__roosterjsContentModel.segments);
child.__roosterjsContentModel.segments = [];
}
}
} else if (isNodeOfType(child, NodeType.Element)) {
Expand All @@ -53,6 +103,11 @@ function onParagraph(node: Node) {
}
}

function onTable(tableElement: HTMLTableElement, table: ContentModelTable) {
const indexedTable = tableElement as IndexedTableElement;
indexedTable.__roosterjsContentModel = { tableRows: table.rows };
}

function reconcileSelection(
model: ContentModelDocument,
oldRangeEx: SelectionRangeEx | undefined,
Expand All @@ -66,7 +121,7 @@ function reconcileSelection(
range?.collapsed &&
isNodeOfType(range.startContainer, NodeType.Text)
) {
if (isIndexedNode(range.startContainer)) {
if (isIndexedSegment(range.startContainer)) {
reconcileTextSelection(range.startContainer);
}
} else {
Expand All @@ -76,8 +131,8 @@ function reconcileSelection(

switch (newRangeEx.type) {
case SelectionRangeTypes.ImageSelection:
const imageModel = isIndexedNode(newRangeEx.image)
? newRangeEx.image.segmentIndex.segments[0]
const imageModel = isIndexedSegment(newRangeEx.image)
? newRangeEx.image.__roosterjsContentModel.segments[0]
: null;

if (imageModel?.segmentType == 'Image') {
Expand All @@ -90,6 +145,20 @@ function reconcileSelection(
break;

case SelectionRangeTypes.TableSelection:
const rows = isIndexedTable(newRangeEx.table)
? newRangeEx.table.__roosterjsContentModel.tableRows
: null;
const { firstCell, lastCell } = newRangeEx.coordinates ?? UnSelectedCoordinates;

rows?.forEach((row, rowIndex) => {
row.cells.forEach((cell, colIndex) => {
cell.isSelected =
rowIndex >= firstCell.y &&
rowIndex <= lastCell.y &&
colIndex >= firstCell.x &&
colIndex <= lastCell.x;
});
});
// Cannot handle table selection for now, so just return false and create a new model
return false;

Expand All @@ -111,7 +180,7 @@ function reconcileSelection(
isNodeOfType(startContainer, NodeType.Text)
) {
return (
isIndexedNode(startContainer) &&
isIndexedSegment(startContainer) &&
!!reconcileTextSelection(startContainer, startOffset, endOffset)
);
} else {
Expand All @@ -135,7 +204,7 @@ function reconcileSelection(

function reconcileNodeSelection(node: Node, offset: number): Selectable | undefined {
if (isNodeOfType(node, NodeType.Text)) {
return isIndexedNode(node) ? reconcileTextSelection(node, offset) : undefined;
return isIndexedSegment(node) ? reconcileTextSelection(node, offset) : undefined;
} else if (offset >= node.childNodes.length) {
return insertMarker(node.lastChild, true /*isAfter*/);
} else {
Expand All @@ -146,8 +215,8 @@ function reconcileNodeSelection(node: Node, offset: number): Selectable | undefi
function insertMarker(node: Node | null, isAfter: boolean): Selectable | undefined {
let marker: ContentModelSelectionMarker | undefined;

if (node && isIndexedNode(node)) {
const { paragraph, segments } = node.segmentIndex;
if (node && isIndexedSegment(node)) {
const { paragraph, segments } = node.__roosterjsContentModel;
const index = paragraph.segments.indexOf(segments[0]);

if (index >= 0) {
Expand All @@ -164,8 +233,12 @@ function insertMarker(node: Node | null, isAfter: boolean): Selectable | undefin
return marker;
}

function reconcileTextSelection(textNode: IndexedNode, startOffset?: number, endOffset?: number) {
const { paragraph, segments } = textNode.segmentIndex;
function reconcileTextSelection(
textNode: IndexedSegmentNode,
startOffset?: number,
endOffset?: number
) {
const { paragraph, segments } = textNode.__roosterjsContentModel;
const first = segments[0];
const last = segments[segments.length - 1];
let selectable: Selectable | undefined;
Expand Down Expand Up @@ -239,7 +312,7 @@ function reconcileTextSelection(textNode: IndexedNode, startOffset?: number, end
paragraph.segments.splice(firstIndex, lastIndex - firstIndex + 1, ...newSegments);
}

textNode.segmentIndex = {
textNode.__roosterjsContentModel = {
paragraph,
segments: textSegments,
};
Expand All @@ -250,24 +323,13 @@ function reconcileTextSelection(textNode: IndexedNode, startOffset?: number, end
return selectable;
}

function isIndexedNode(node: Node): node is IndexedNode {
const { paragraph, segments } = (node as IndexedNode).segmentIndex ?? {};

return (
paragraph &&
paragraph.blockType == 'Paragraph' &&
Array.isArray(paragraph.segments) &&
Array.isArray(segments) &&
segments.length > 0
);
}

/**
* @internal
* Implementation of ContentModelDomIndexer
*/
export const contentModelDomIndexer: ContentModelDomIndexer = {
onSegment,
onParagraph,
onTable,
reconcileSelection,
};
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,15 @@ function setSelectionToTable(
start: Selectable | null,
end: Selectable | null
): boolean {
const notFoundCo: Coordinates = { x: -1, y: -1 };
const startCo = findCell(table, start);
const endCo = end ? findCell(table, end) : startCo;
const { x: firstX, y: firstY } = startCo ?? notFoundCo;
const { x: lastX, y: lastY } = (end ? findCell(table, end) : startCo) ?? notFoundCo;

if (!isInSelection && startCo && endCo) {
if (!isInSelection) {
for (let row = 0; row < table.rows.length; row++) {
for (let col = 0; col < table.rows[row].cells.length; col++) {
const isSelected =
row >= startCo.y && row <= endCo.y && col >= startCo.x && col <= endCo.x;
const isSelected = row >= firstY && row <= lastY && col >= firstX && col <= lastX;

setIsSelected(table.rows[row].cells[col], isSelected);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ContentModelDocument } from '../group/ContentModelDocument';
import { ContentModelParagraph } from '../block/ContentModelParagraph';
import { ContentModelSegment } from '../segment/ContentModelSegment';
import { ContentModelTable } from '../block/ContentModelTable';
import { SelectionRangeEx } from 'roosterjs-editor-types';

/**
Expand All @@ -10,21 +11,27 @@ import { SelectionRangeEx } from 'roosterjs-editor-types';
export interface ContentModelDomIndexer {
/**
* Invoked when processing a segment
* @param node The new DOM node for this segment
* @param segmentNode The new DOM node for this segment
* @param paragraph Parent paragraph of this segment
* @param segments The source segments
*/
onSegment: (
node: Node,
segmentNode: Node,
paragraph: ContentModelParagraph,
segments: ContentModelSegment[]
) => void;

/**
* Invoked when new paragraph node is created in DOM tree
* @param node The new DOM node for this paragraph
* @param paragraphElement The new DOM node for this paragraph
*/
onParagraph: (node: Node) => void;
onParagraph: (paragraphElement: HTMLElement) => void;

/**
* Invoked when new table node is created in DOM tree
* @param tableElement The new DOM node for this table
*/
onTable: (tableElement: HTMLTableElement, tableModel: ContentModelTable) => void;

/**
* When document content or selection is changed by user, we need to use this function to update the content model
Expand Down

0 comments on commit a3ec520

Please sign in to comment.