From 21a88fd2063f6633f559afde6e72d0585dc6d6c5 Mon Sep 17 00:00:00 2001 From: Jonas Date: Sat, 4 Jul 2026 20:15:44 +0200 Subject: [PATCH] fix(Table): allow changing list indention inside tables Fixes: #8744 Signed-off-by: Jonas --- playwright/e2e/table-tab-navigation.spec.ts | 50 ++++++++++++++++++++ playwright/support/sections/EditorSection.ts | 2 +- src/nodes/Table/Table.js | 49 +++++++++++++++++-- 3 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 playwright/e2e/table-tab-navigation.spec.ts diff --git a/playwright/e2e/table-tab-navigation.spec.ts b/playwright/e2e/table-tab-navigation.spec.ts new file mode 100644 index 00000000000..3792c65dcd0 --- /dev/null +++ b/playwright/e2e/table-tab-navigation.spec.ts @@ -0,0 +1,50 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { expect, mergeTests } from '@playwright/test' +import { test as editorTest } from '../support/fixtures/editor' +import { test as uploadFileTest } from '../support/fixtures/upload-file' + +const test = mergeTests(editorTest, uploadFileTest) + +test.describe('Tab navigation inside table cell', () => { + test('indents list item inside table cell', async ({ editor, open }) => { + await open() + await editor.getMenu('Table').click() + + const firstCell = editor.content.locator('table td').first() + await firstCell.click() + await editor.type('- one') + await editor.content.press('Enter') + await editor.type('two') + + await editor.content.press('Tab') + + const nested = firstCell.locator('ul > li').first().locator('ul > li') + await expect(nested).toHaveText('two') + + await editor.content.press('Shift+Tab') + await expect(firstCell.locator('> ul > li')).toHaveCount(2) + await expect(firstCell.locator('> ul > li > ul')).toHaveCount(0) + }) + + test('navigates cells when caret is not inside a list', async ({ + editor, + open, + }) => { + await open() + await editor.getMenu('Table').click() + + const header = editor.content.locator('table th').first() + await header.click() + await editor.type('h1') + await editor.content.press('Tab') + await editor.type('h2') + + const headers = editor.content.locator('table th') + await expect(headers.nth(0)).toContainText('h1') + await expect(headers.nth(0)).toContainText('h1') + }) +}) diff --git a/playwright/support/sections/EditorSection.ts b/playwright/support/sections/EditorSection.ts index c4fcb9770d6..9ec489a7431 100644 --- a/playwright/support/sections/EditorSection.ts +++ b/playwright/support/sections/EditorSection.ts @@ -47,7 +47,7 @@ export class EditorSection { } public getMenu(name: string): Locator { - return this.el.getByRole('button', { name }) + return this.menubar.getByRole('button', { name }) } public getMenuItem(name: string): Locator { diff --git a/src/nodes/Table/Table.js b/src/nodes/Table/Table.js index 46702e75e9b..b58bff18a85 100644 --- a/src/nodes/Table/Table.js +++ b/src/nodes/Table/Table.js @@ -72,6 +72,21 @@ function findSameCellInNextRow($cell) { } } +/** + * Return the node type name if the selection sits inside a listItem/taskItem. + * + * @param {object} selection - the editor selection + */ +function findListItemAtSelection(selection) { + const { $from } = selection + for (let depth = $from.depth; depth > 0; depth--) { + const name = $from.node(depth).type.name + if (name === 'listItem' || name === 'taskItem') { + return name + } + } +} + export default Table.extend({ content: 'tableCaption? tableHeadRow tableRow*', @@ -319,11 +334,39 @@ export default Table.extend({ ...this.parent(), /** * inside a table cell - * Jump to next cell or outside table if already in last cell + * When inside a list, indent the list item. Otherwise jump to + * the next cell (or out of the table if in the last cell). */ - Tab: () => + Tab: () => { + const listItemName = findListItemAtSelection( + this.editor.state.selection, + ) + if ( + listItemName + && this.editor.commands.sinkListItem(listItemName) + ) { + return true + } this.editor.commands.goToNextCell() - || this.editor.commands.leaveTable(), + || this.editor.commands.leaveTable() + }, + /** + * inside a table cell + * When inside a list, indent the list item. Otherwise jump to + * the next cell (or out of the table if in the last cell). + */ + 'Shift-Tab': () => { + const listItemName = findListItemAtSelection( + this.editor.state.selection, + ) + if ( + listItemName + && this.editor.commands.liftListItem(listItemName) + ) { + return true + } + this.editor.commands.goToPreviousCell() + }, } }, })