Skip to content

Commit

Permalink
Merge branch 'master' into u/juliaroldi/auto-format-demo-site
Browse files Browse the repository at this point in the history
  • Loading branch information
juliaroldi authored Apr 2, 2024
2 parents a5967fb + f362e01 commit 13543b9
Show file tree
Hide file tree
Showing 8 changed files with 1,808 additions and 13 deletions.
1 change: 1 addition & 0 deletions packages/roosterjs-content-model-api/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export { toggleCode } from './publicApi/segment/toggleCode';
export { insertEntity } from './publicApi/entity/insertEntity';
export { insertTableRow } from './modelApi/table/insertTableRow';
export { insertTableColumn } from './modelApi/table/insertTableColumn';
export { clearSelectedCells } from './modelApi/table/clearSelectedCells';

export { formatTableWithContentModel } from './publicApi/utils/formatTableWithContentModel';
export { formatImageWithContentModel } from './publicApi/utils/formatImageWithContentModel';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type { ContentModelTable, TableSelectionCoordinates } from 'roosterjs-con

/**
* Clear selection of a table.
* @internal
* @param table The table model where the selection is to be cleared
* @param sel The selection coordinates to be cleared
*/
Expand Down
47 changes: 35 additions & 12 deletions packages/roosterjs-content-model-plugins/lib/edit/keyboardTab.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { getOperationalBlocks, isBlockGroupOfType } from 'roosterjs-content-model-dom';
import { handleTabOnList } from './tabUtils/handleTabOnList';
import { handleTabOnParagraph } from './tabUtils/handleTabOnParagraph';
import { handleTabOnTable } from './tabUtils/handleTabOnTable';
import { handleTabOnTableCell } from './tabUtils/handleTabOnTableCell';
import { setModelIndentation } from 'roosterjs-content-model-api';
import type {
ContentModelDocument,
ContentModelListItem,
ContentModelTableCell,
IEditor,
} from 'roosterjs-content-model-types';

Expand All @@ -14,31 +17,51 @@ import type {
export function keyboardTab(editor: IEditor, rawEvent: KeyboardEvent) {
const selection = editor.getDOMSelection();

if (selection?.type == 'range') {
editor.formatContentModel(
model => {
return handleTab(model, rawEvent);
},
{
apiName: 'handleTabKey',
}
);
switch (selection?.type) {
case 'range':
editor.formatContentModel(
model => {
return handleTab(model, rawEvent);
},
{
apiName: 'handleTabKey',
}
);

return true;
return true;
case 'table':
editor.formatContentModel(
model => {
return handleTabOnTable(model, rawEvent);
},
{
apiName: 'handleTabKey',
}
);
return true;
}
}

/**
* If multiple blocks are selected, indent or outdent the selected blocks with setModelIndentation.
* If only one block is selected, call handleTabOnParagraph or handleTabOnList to handle the tab key.
* If only one block is selected:
* - If it is a table cell, call handleTabOnTableCell to handle the tab key.
* - If it is a paragraph, call handleTabOnParagraph to handle the tab key.
* - If it is a list item, call handleTabOnList to handle the tab key.
*/
function handleTab(model: ContentModelDocument, rawEvent: KeyboardEvent) {
const blocks = getOperationalBlocks<ContentModelListItem>(model, ['ListItem'], ['TableCell']);
const blocks = getOperationalBlocks<ContentModelListItem | ContentModelTableCell>(
model,
['ListItem', 'TableCell'],
[]
);
const block = blocks.length > 0 ? blocks[0].block : undefined;
if (blocks.length > 1) {
setModelIndentation(model, rawEvent.shiftKey ? 'outdent' : 'indent');
rawEvent.preventDefault();
return true;
} else if (isBlockGroupOfType<ContentModelTableCell>(block, 'TableCell')) {
return handleTabOnTableCell(model, block, rawEvent);
} else if (block?.blockType === 'Paragraph') {
return handleTabOnParagraph(model, block, rawEvent);
} else if (isBlockGroupOfType<ContentModelListItem>(block, 'ListItem')) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { getFirstSelectedTable } from 'roosterjs-content-model-dom';
import { setModelIndentation } from 'roosterjs-content-model-api';
import type { ContentModelDocument, ContentModelTable } from 'roosterjs-content-model-types';

/**
* When the whole table is selected, indent or outdent the whole table with setModelIndentation.
* @internal
*/
export function handleTabOnTable(model: ContentModelDocument, rawEvent: KeyboardEvent) {
const tableModel = getFirstSelectedTable(model)[0];
if (tableModel && isWholeTableSelected(tableModel)) {
setModelIndentation(model, rawEvent.shiftKey ? 'outdent' : 'indent');
rawEvent.preventDefault();
return true;
}
return false;
}

function isWholeTableSelected(tableModel: ContentModelTable) {
return (
tableModel.rows[0]?.cells[0]?.isSelected &&
tableModel.rows[tableModel.rows.length - 1]?.cells[tableModel.widths.length - 1]?.isSelected
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { clearSelectedCells, insertTableRow } from 'roosterjs-content-model-api';
import {
createSelectionMarker,
getFirstSelectedTable,
normalizeTable,
setParagraphNotImplicit,
setSelection,
} from 'roosterjs-content-model-dom';
import type { ContentModelDocument, ContentModelTableCell } from 'roosterjs-content-model-types';

/**
* When the cursor is on the last cell of a table, add new row and focus first new cell.
* @internal
*/
export function handleTabOnTableCell(
model: ContentModelDocument,
cell: ContentModelTableCell,
rawEvent: KeyboardEvent
) {
const tableModel = getFirstSelectedTable(model)[0];
// Check if cursor is on last cell of the table
if (
!rawEvent.shiftKey &&
tableModel &&
tableModel.rows[tableModel.rows.length - 1]?.cells[tableModel.widths.length - 1] === cell
) {
insertTableRow(tableModel, 'insertBelow');

// Clear Table selection
clearSelectedCells(tableModel, {
firstRow: tableModel.rows.length - 1,
firstColumn: 0,
lastRow: tableModel.rows.length - 1,
lastColumn: tableModel.widths.length - 1,
});
normalizeTable(tableModel, model.format);

// Add selection marker to the first cell of the new row
const markerParagraph = tableModel.rows[tableModel.rows.length - 1]?.cells[0]?.blocks[0];
if (markerParagraph.blockType == 'Paragraph') {
const marker = createSelectionMarker(model.format);

markerParagraph.segments.unshift(marker);
setParagraphNotImplicit(markerParagraph);
setSelection(tableModel.rows[tableModel.rows.length - 1].cells[0], marker);
}

rawEvent.preventDefault();
return true;
}

return false;
}
148 changes: 148 additions & 0 deletions packages/roosterjs-content-model-plugins/test/edit/keyboardTabTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,154 @@ describe('keyboardTab', () => {
runTest(model, undefined, false, true);
});

it('tab on the start first item on the list while list is in table cell', () => {
const model: ContentModelDocument = {
blockGroupType: 'Document',
blocks: [
{
blockType: 'Table',
rows: [
{
height: 20,
format: {},
cells: [
{
blockGroupType: 'TableCell',
blocks: [
{
blockType: 'BlockGroup',
blockGroupType: 'ListItem',
blocks: [
{
blockType: 'Paragraph',
segments: [
{
segmentType: 'SelectionMarker',
isSelected: true,
format: {},
},
{
segmentType: 'Text',
text: 'test',
format: {},
},
],
format: {},
isImplicit: true,
},
],
levels: [
{
listType: 'OL',
format: {
marginTop: '0px',
marginBottom: '0px',
listStyleType: 'decimal',
},
dataset: {
editingInfo: '{"orderedStyleType":1}',
},
},
],
formatHolder: {
segmentType: 'SelectionMarker',
isSelected: false,
format: {},
},
format: {},
},
{
blockType: 'BlockGroup',
blockGroupType: 'ListItem',
blocks: [
{
blockType: 'Paragraph',
segments: [
{
segmentType: 'Text',
text: 'test',
format: {},
},
],
format: {},
isImplicit: true,
},
],
levels: [
{
listType: 'OL',
format: {
marginTop: '0px',
marginBottom: '0px',
listStyleType: 'decimal',
},
dataset: {
editingInfo: '{"orderedStyleType":1}',
},
},
],
formatHolder: {
segmentType: 'SelectionMarker',
isSelected: false,
format: {},
},
format: {},
},
{
blockType: 'BlockGroup',
blockGroupType: 'ListItem',
blocks: [
{
blockType: 'Paragraph',
segments: [
{
segmentType: 'Text',
text: 'test',
format: {},
},
],
format: {},
isImplicit: true,
},
],
levels: [
{
listType: 'OL',
format: {
marginTop: '0px',
marginBottom: '0px',
listStyleType: 'decimal',
},
dataset: {
editingInfo: '{"orderedStyleType":1}',
},
},
],
formatHolder: {
segmentType: 'SelectionMarker',
isSelected: false,
format: {},
},
format: {},
},
],
format: {},
dataset: {},
},
],
},
],
format: {},
widths: [20],
dataset: {},
},
],
format: {},
};

runTest(model, 'indent', false, true);
});

it('shift tab on empty list item', () => {
const model: ContentModelDocument = {
blockGroupType: 'Document',
Expand Down
Loading

0 comments on commit 13543b9

Please sign in to comment.