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

FHL: Port TableCellSelection plugin #2506

Merged
merged 22 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 0 additions & 2 deletions demo/scripts/controlsV2/plugins/createLegacyPlugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
CustomReplace,
HyperLink,
ImageEdit,
TableCellSelection,
} from 'roosterjs-editor-plugins';
import {
LegacyPluginList,
Expand Down Expand Up @@ -33,7 +32,6 @@ export function createLegacyPlugins(initState: OptionState): LegacyEditorPlugin[
applyChangesOnMouseUp: initState.applyChangesOnMouseUp,
})
: null,
tableCellSelection: pluginList.tableCellSelection ? new TableCellSelection() : null,
customReplace: pluginList.customReplace ? new CustomReplace() : null,
announce: pluginList.announce ? new Announce(getDefaultStringsMap()) : null,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { FocusZone, FocusZoneDirection } from '@fluentui/react/lib/FocusZone';
import { insertTable } from 'roosterjs-content-model-api';
import { isNodeOfType } from 'roosterjs-content-model-dom';
import { mergeStyleSets } from '@fluentui/react/lib/Styling';
import { TableCellCoordinate } from 'roosterjs-content-model-types';
import type { RibbonButton } from '../type/RibbonButton';
import type { InsertTableButtonStringKey } from '../type/RibbonButtonStringKeys';
import type { IContextualMenuItem } from '@fluentui/react/lib/ContextualMenu';
Expand Down Expand Up @@ -57,7 +58,7 @@ export const insertTableButton: RibbonButton<InsertTableButtonStringKey> = {
},
};

function parseKey(key: string): { row: number; col: number } {
function parseKey(key: string): TableCellCoordinate {
const [row, col] = key.split(',');
return {
row: parseInt(row),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ const initialState: OptionState = {
contentEdit: false,
hyperlink: false,
imageEdit: false,
tableCellSelection: true,
customReplace: false,
announce: false,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ export interface LegacyPluginList {
contentEdit: boolean;
hyperlink: boolean;
imageEdit: boolean;
tableCellSelection: boolean;
customReplace: boolean;
announce: boolean;
}
Expand Down
1 change: 0 additions & 1 deletion demo/scripts/controlsV2/sidePane/editorOptions/Plugins.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,6 @@ export class LegacyPlugins extends PluginsBase<keyof LegacyPluginList> {
)
)}
{this.renderPluginItem('customReplace', 'Custom Replace Plugin (autocomplete)')}
{this.renderPluginItem('tableCellSelection', 'Table Cell Selection')}
{this.renderPluginItem('announce', 'Announce')}
</tbody>
</table>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
EditPluginCode,
ImageEditCode,
PastePluginCode,
TableCellSelectionCode,
TableEditPluginCode,
ShortcutPluginCode,
} from './SimplePluginCode';
Expand Down Expand Up @@ -58,7 +57,6 @@ export class LegacyPluginCode extends PluginsCodeBase {
pluginList.hyperlink && new HyperLinkCode(state.linkTitle),
pluginList.imageEdit && new ImageEditCode(),
pluginList.customReplace && new CustomReplaceCode(),
pluginList.tableCellSelection && new TableCellSelectionCode(),
];

super(plugins);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,3 @@ export class CustomReplaceCode extends SimplePluginCode {
super('CustomReplace', 'roosterjs');
}
}

export class TableCellSelectionCode extends SimplePluginCode {
constructor() {
super('TableCellSelection', 'roosterjs');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,13 @@ function getNewSelection(core: EditorCore): DOMSelection | null {
const selection = core.logicalRoot.ownerDocument.defaultView?.getSelection();
const range = selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : null;

return range && core.logicalRoot.contains(range.commonAncestorContainer)
return selection && range && core.logicalRoot.contains(range.commonAncestorContainer)
? {
type: 'range',
range,
isReverted: isSelectionReverted(selection),
isReverted:
selection.focusNode != range.endContainer ||
selection.focusOffset != range.endOffset,
}
: null;
}

function isSelectionReverted(selection: Selection | null | undefined): boolean {
if (selection && selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
return (
!range.collapsed &&
selection.focusNode != range.endContainer &&
selection.focusOffset != range.endOffset
);
}

return false;
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { addRangeToSelection } from './addRangeToSelection';
import { ensureUniqueId } from '../setEditorStyle/ensureUniqueId';
import { isNodeOfType, toArray } from 'roosterjs-content-model-dom';
import { parseTableCells } from '../../publicApi/domUtils/tableCellUtils';
import {
findLastedCoInMergedCell,
findTableCellElement,
parseTableCells,
} from '../../publicApi/domUtils/tableCellUtils';
import type {
ParsedTable,
SelectionChangedEvent,
SetDOMSelection,
TableSelection,
TableCellCoordinate,
} from 'roosterjs-content-model-types';

const DOM_SELECTION_CSS_KEY = '_DOMSelection';
Expand Down Expand Up @@ -49,11 +54,47 @@ export const setDOMSelection: SetDOMSelection = (core, selection, skipSelectionC
setRangeSelection(doc, image);
break;
case 'table':
const { table, firstColumn, firstRow } = selection;
const tableSelectors = buildTableSelectors(
ensureUniqueId(table, TABLE_ID),
selection
);
const { table, firstColumn, firstRow, lastColumn, lastRow } = selection;
const parsedTable = parseTableCells(selection.table);
let firstCell = {
row: Math.min(firstRow, lastRow),
col: Math.min(firstColumn, lastColumn),
cell: <HTMLTableCellElement | null>null,
};
let lastCell = {
row: Math.max(firstRow, lastRow),
col: Math.max(firstColumn, lastColumn),
};

firstCell = findTableCellElement(parsedTable, firstCell) || firstCell;
lastCell = findLastedCoInMergedCell(parsedTable, lastCell) || lastCell;

if (
isNaN(firstCell.row) ||
isNaN(firstCell.col) ||
isNaN(lastCell.row) ||
isNaN(lastCell.col)
) {
return;
}

selection = {
type: 'table',
table,
firstRow: firstCell.row,
firstColumn: firstCell.col,
lastRow: lastCell.row,
lastColumn: lastCell.col,
};

const tableId = ensureUniqueId(table, TABLE_ID);
const tableSelectors =
firstCell.row == 0 &&
firstCell.col == 0 &&
lastCell.row == parsedTable.length - 1 &&
lastCell.col == (parsedTable[lastCell.row]?.length ?? 0) - 1
? [`#${tableId}`, `#${tableId} *`]
: handleTableSelected(parsedTable, tableId, table, firstCell, lastCell);

core.selection.selection = selection;
core.api.setEditorStyle(
Expand All @@ -64,7 +105,12 @@ export const setDOMSelection: SetDOMSelection = (core, selection, skipSelectionC
);
core.api.setEditorStyle(core, HIDE_CURSOR_CSS_KEY, CARET_CSS_RULE);

setRangeSelection(doc, table.rows[firstRow]?.cells[firstColumn]);
const nodeToSelect = firstCell.cell?.firstElementChild || firstCell.cell;

if (nodeToSelect) {
setRangeSelection(doc, (nodeToSelect as HTMLElement) || undefined);
}

break;
case 'range':
addRangeToSelection(doc, selection.range, selection.isReverted);
Expand All @@ -90,25 +136,13 @@ export const setDOMSelection: SetDOMSelection = (core, selection, skipSelectionC
}
};

function buildTableSelectors(tableId: string, selection: TableSelection): string[] {
const { firstColumn, firstRow, lastColumn, lastRow } = selection;
const cells = parseTableCells(selection.table);
const isAllTableSelected =
firstRow == 0 &&
firstColumn == 0 &&
lastRow == cells.length - 1 &&
lastColumn == (cells[lastRow]?.length ?? 0) - 1;
return isAllTableSelected
? [`#${tableId}`, `#${tableId} *`]
: handleTableSelected(tableId, selection, cells);
}

function handleTableSelected(
parsedTable: ParsedTable,
tableId: string,
selection: TableSelection,
cells: (HTMLTableCellElement | null)[][]
table: HTMLTableElement,
firstCell: TableCellCoordinate,
lastCell: TableCellCoordinate
) {
const { firstRow, firstColumn, lastRow, lastColumn, table } = selection;
const selectors: string[] = [];

// Get whether table has thead, tbody or tfoot, then Set the start and end of each of the table children,
Expand All @@ -132,7 +166,7 @@ function handleTableSelected(
return result;
});

cells.forEach((row, rowIndex) => {
parsedTable.forEach((row, rowIndex) => {
let tdCount = 0;

//Get current TBODY/THEAD/TFOOT
Expand All @@ -146,14 +180,14 @@ function handleTableSelected(
for (let cellIndex = 0; cellIndex < row.length; cellIndex++) {
const cell = row[cellIndex];

if (cell) {
if (typeof cell == 'object') {
tdCount++;

if (
rowIndex >= firstRow &&
rowIndex <= lastRow &&
cellIndex >= firstColumn &&
cellIndex <= lastColumn
rowIndex >= firstCell.row &&
rowIndex <= lastCell.row &&
cellIndex >= firstCell.col &&
cellIndex <= lastCell.col
) {
const selector = `#${tableId}${middleElSelector} tr:nth-child(${currentRow})>${cell.tagName}:nth-child(${tdCount})`;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { isCharacterValue } from '../../publicApi/domUtils/eventUtils';
import { iterateSelections } from '../../publicApi/selection/iterateSelections';
import { normalizePos } from '../../publicApi/domUtils/normalizePos';
import {
addDelimiters,
createBr,
Expand Down Expand Up @@ -132,14 +133,10 @@ function getFocusedElement(
let node: Node | null = isReverted ? range.startContainer : range.endContainer;
let offset = isReverted ? range.startOffset : range.endOffset;

while (node?.lastChild) {
if (offset == node.childNodes.length) {
node = node.lastChild;
offset = node.childNodes.length;
} else {
node = node.childNodes[offset];
offset = 0;
}
if (node) {
const pos = normalizePos(node, offset);
node = pos.node;
offset = pos.offset;
}

if (!isNodeOfType(node, 'ELEMENT_NODE')) {
Expand Down
Loading
Loading