From 7e20d41c4c7c7cd482b26054ff7d97f80c380b65 Mon Sep 17 00:00:00 2001 From: Alf Eaton Date: Tue, 4 Jul 2023 09:15:18 +0100 Subject: [PATCH] Remove list items when list is toggled off (#13580) GitOrigin-RevId: 19c63b26798fcf3c8e631090c614f0a693d4f071 --- .../source-editor/extensions/toolbar/lists.ts | 55 +++--- .../utils/tree-operations/ancestors.ts | 8 +- .../codemirror-editor-visual-toolbar.spec.tsx | 160 ++++++++++++++++++ 3 files changed, 198 insertions(+), 25 deletions(-) diff --git a/services/web/frontend/js/features/source-editor/extensions/toolbar/lists.ts b/services/web/frontend/js/features/source-editor/extensions/toolbar/lists.ts index b2aac520ca..73bd285f8d 100644 --- a/services/web/frontend/js/features/source-editor/extensions/toolbar/lists.ts +++ b/services/web/frontend/js/features/source-editor/extensions/toolbar/lists.ts @@ -229,39 +229,52 @@ const toggleListForRange = ( // toggle list off const changeSpec: ChangeSpec[] = [ { - from: emptyBeginLine ? beginLine.from - 1 : beginEnvNode.from, - to: emptyBeginLine ? beginLine.to : beginEnvNode.to, + from: emptyBeginLine ? beginLine.from : beginEnvNode.from, + to: emptyBeginLine + ? Math.min(beginLine.to + 1, view.state.doc.length) + : beginEnvNode.to, insert: '', }, { - from: emptyEndLine ? endLine.from : endEnvNode.from, - to: emptyEndLine ? endLine.to + 1 : endEnvNode.to, + from: emptyEndLine + ? Math.max(endLine.from - 1, 0) + : endEnvNode.from, + to: emptyEndLine ? endLine.to : endEnvNode.to, insert: '', }, ] - const commandNodes = descendantsOfNodeWithType( + // items that aren't within nested list environments + const itemNodes = descendantsOfNodeWithType( ancestorNode, - 'Item' - ).filter( - commandNode => - view.state.sliceDoc(commandNode.from, commandNode.to) === '\\item' + 'Item', + ListEnvironment ) - if (commandNodes.length > 0) { - // whether the command is the only content on this line, apart from whitespace - const emptyLineBeforeItem = /^\s*\\item\{/.test(beginLine.text) + if (itemNodes.length > 0) { + const indentUnit = getIndentUnit(view.state) - const indentUnit = emptyLineBeforeItem - ? getIndentUnit(view.state) - : 0 - - for (const commandNode of commandNodes) { - changeSpec.push({ - from: commandNode.from - indentUnit, - to: commandNode.to + 1, + for (const itemNode of itemNodes) { + const change: ChangeSpec = { + from: itemNode.from, + to: itemNode.to, insert: '', - }) + } + + const line = view.state.doc.lineAt(itemNode.from) + + const lineBeforeCommand = view.state.sliceDoc( + line.from, + itemNode.from + ) + + // if the line before the command is empty, remove one unit of indentation + if (lineBeforeCommand.trim().length === 0) { + const indentation = getIndentation(view.state, itemNode.from) + change.from -= Math.min(indentation ?? 0, indentUnit) + } + + changeSpec.push(change) } } diff --git a/services/web/frontend/js/features/source-editor/utils/tree-operations/ancestors.ts b/services/web/frontend/js/features/source-editor/utils/tree-operations/ancestors.ts index 67236f6434..141296461e 100644 --- a/services/web/frontend/js/features/source-editor/utils/tree-operations/ancestors.ts +++ b/services/web/frontend/js/features/source-editor/utils/tree-operations/ancestors.ts @@ -138,16 +138,16 @@ export const lastAncestorAtEndPosition = ( export const descendantsOfNodeWithType = ( node: SyntaxNode, type: string | number, - nested = false + leaveType?: string | number ): SyntaxNode[] => { const children: SyntaxNode[] = [] node.cursor().iterate(nodeRef => { if (nodeRef.type.is(type)) { children.push(nodeRef.node) - if (!nested) { - return false - } + } + if (leaveType && nodeRef.type.is(leaveType) && nodeRef.node !== node) { + return false } }) diff --git a/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-toolbar.spec.tsx b/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-toolbar.spec.tsx index 7df4d4368c..30152cb7c5 100644 --- a/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-toolbar.spec.tsx +++ b/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-toolbar.spec.tsx @@ -147,4 +147,164 @@ describe(' toolbar in Rich Text mode', function () { cy.get('.cm-line').eq(0).type('ing') cy.get('.cm-line').eq(0).should('have.text', ' testing') }) + + it('should toggle between list types', function () { + mountEditor('test') + selectAll() + + clickToolbarButton('Numbered List') + + cy.get('.cm-content').should( + 'have.text', + [ + // + '\\begin{enumerate}', + ' test', + '\\end{enumerate}', + ].join('') + ) + + cy.get('.cm-line').eq(1).click() + + clickToolbarButton('Bullet List') + + cy.get('.cm-line').eq(1).click() + + cy.get('.cm-content').should( + 'have.text', + [ + // + '\\begin{itemize}', + ' test', + '\\end{itemize}', + ].join('') + ) + }) + + it('should remove a list', function () { + mountEditor('test') + selectAll() + + clickToolbarButton('Numbered List') + + cy.get('.cm-content').should( + 'have.text', + [ + // + '\\begin{enumerate}', + ' test', + '\\end{enumerate}', + ].join('') + ) + + cy.get('.cm-line').eq(0).click() + + clickToolbarButton('Numbered List') + + cy.get('.cm-line').eq(0).click() + + cy.get('.cm-content').should('have.text', 'test') + }) + + it('should not remove a parent list', function () { + mountEditor('test\ntest') + selectAll() + + clickToolbarButton('Numbered List') + + cy.get('.cm-content').should( + 'have.text', + [ + // + '\\begin{enumerate}', + ' test', + ' test', + '\\end{enumerate}', + ].join('') + ) + + cy.get('.cm-line').eq(2).click() + + clickToolbarButton('Increase Indent') + + cy.get('.cm-content').should( + 'have.text', + [ + // + '\\begin{enumerate}', + ' test', + ' \\begin{enumerate}', + ' test', + ' \\end{enumerate}', + '\\end{enumerate}', + ].join('') + ) + + cy.get('.cm-line').eq(2).click() + + clickToolbarButton('Numbered List') + + cy.get('.cm-content').should( + 'have.text', + [ + // + '\\begin{enumerate}', + ' test', + ' test', + '\\end{enumerate}', + ].join('') + ) + }) + + it('should not remove a nested list', function () { + mountEditor('test\ntest') + selectAll() + + clickToolbarButton('Numbered List') + + cy.get('.cm-content').should( + 'have.text', + [ + // + '\\begin{enumerate}', + ' test', + ' test', + '\\end{enumerate}', + ].join('') + ) + + cy.get('.cm-line').eq(2).click() + + clickToolbarButton('Increase Indent') + + cy.get('.cm-content').should( + 'have.text', + [ + // + '\\begin{enumerate}', + ' test', + ' \\begin{enumerate}', + ' test', + ' \\end{enumerate}', + '\\end{enumerate}', + ].join('') + ) + + cy.get('.cm-line').eq(1).click() + + clickToolbarButton('Numbered List') + + cy.get('.cm-line').eq(1).click() + + cy.get('.cm-content').should( + 'have.text', + [ + // + 'test', + ' \\begin{enumerate}', + ' test', + ' \\end{enumerate}', + ].join('') + ) + }) })