mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
[visual] Handle Enter in an empty list item at the end of a nested list (#18034)
GitOrigin-RevId: 4b3f24c7cf18837ba87859340991d9bb2532560a
This commit is contained in:
parent
ce00af7838
commit
45ee8aca93
3 changed files with 116 additions and 19 deletions
|
@ -0,0 +1,13 @@
|
|||
import { EditorState } from '@codemirror/state'
|
||||
import {
|
||||
getIndentation,
|
||||
IndentContext,
|
||||
indentString,
|
||||
} from '@codemirror/language'
|
||||
|
||||
export const createListItem = (state: EditorState, pos: number) => {
|
||||
const cx = new IndentContext(state)
|
||||
const columns = getIndentation(cx, pos) ?? 0
|
||||
const indent = indentString(state, columns)
|
||||
return `${indent}\\item `
|
||||
}
|
|
@ -1,16 +1,17 @@
|
|||
import { keymap } from '@codemirror/view'
|
||||
import { EditorSelection, Prec } from '@codemirror/state'
|
||||
import { ancestorNodeOfType } from '../../utils/tree-query'
|
||||
import {
|
||||
getIndentation,
|
||||
IndentContext,
|
||||
indentString,
|
||||
} from '@codemirror/language'
|
||||
ChangeSpec,
|
||||
EditorSelection,
|
||||
Prec,
|
||||
SelectionRange,
|
||||
} from '@codemirror/state'
|
||||
import { ancestorNodeOfType } from '../../utils/tree-query'
|
||||
import {
|
||||
cursorIsAtStartOfListItem,
|
||||
indentDecrease,
|
||||
indentIncrease,
|
||||
} from '../toolbar/commands'
|
||||
import { createListItem } from '@/features/source-editor/extensions/visual/utils/list-item'
|
||||
|
||||
/**
|
||||
* A keymap which provides behaviours for the visual editor,
|
||||
|
@ -39,29 +40,53 @@ export const visualKeymap = Prec.highest(
|
|||
if (line.text.trim() === '\\item') {
|
||||
// no content on this line
|
||||
|
||||
// delete this line
|
||||
const changes = state.changes({
|
||||
// outside the end of the current list
|
||||
const pos = listNode.to + 1
|
||||
|
||||
// delete the current line
|
||||
const deleteCurrentLine = {
|
||||
from: line.from,
|
||||
to: line.to + 1,
|
||||
insert: '',
|
||||
})
|
||||
}
|
||||
|
||||
// the start of the line after the list environment
|
||||
const range = EditorSelection.cursor(endLine.to + 1).map(
|
||||
changes
|
||||
)
|
||||
const changes: ChangeSpec[] = [deleteCurrentLine]
|
||||
|
||||
// the new cursor position
|
||||
let range: SelectionRange
|
||||
|
||||
// if this is a nested list, insert a new empty list item after this list
|
||||
if (
|
||||
listNode.parent?.parent?.parent?.parent?.type.is(
|
||||
'ListEnvironment'
|
||||
)
|
||||
) {
|
||||
const newListItem = createListItem(state, pos)
|
||||
|
||||
changes.push({
|
||||
from: pos,
|
||||
insert: newListItem + '\n',
|
||||
})
|
||||
|
||||
// place the cursor at the end of the new list item
|
||||
range = EditorSelection.cursor(pos + newListItem.length)
|
||||
} else {
|
||||
// place the cursor outside the end of the current list
|
||||
range = EditorSelection.cursor(pos)
|
||||
}
|
||||
|
||||
handled = true
|
||||
|
||||
return { changes, range }
|
||||
return {
|
||||
changes,
|
||||
range: range.map(state.changes(deleteCurrentLine)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create a new list item
|
||||
const cx = new IndentContext(state)
|
||||
const columns = getIndentation(cx, from) ?? 0
|
||||
const indent = indentString(state, columns)
|
||||
const insert = `\n${indent}\\item `
|
||||
// handle a list item that isn't at the end of a list
|
||||
|
||||
const insert = '\n' + createListItem(state, from)
|
||||
|
||||
const countWhitespaceAfterPosition = (pos: number) => {
|
||||
const line = state.doc.lineAt(pos)
|
||||
|
|
|
@ -226,4 +226,63 @@ describe('<CodeMirrorEditor/> lists in Rich Text mode', function () {
|
|||
|
||||
cy.get('.cm-content').should('have.text', [' foo', ' bazbar'].join(''))
|
||||
})
|
||||
|
||||
it('handles Enter in an empty list item at the end of a top-level list', function () {
|
||||
const content = [
|
||||
'\\begin{itemize}',
|
||||
'\\item foo',
|
||||
'\\item ',
|
||||
'\\end{itemize}',
|
||||
'',
|
||||
].join('\n')
|
||||
mountEditor(content)
|
||||
|
||||
cy.get('.cm-line').eq(2).click()
|
||||
cy.focused().type('{enter}')
|
||||
|
||||
cy.get('.cm-content').should('have.text', [' foo'].join(''))
|
||||
})
|
||||
|
||||
it('handles Enter in an empty list item at the end of a nested list', function () {
|
||||
const content = [
|
||||
'\\begin{itemize}',
|
||||
'\\item foo bar',
|
||||
'\\begin{itemize}',
|
||||
'\\item baz',
|
||||
'\\item ',
|
||||
'\\end{itemize}',
|
||||
'\\end{itemize}',
|
||||
].join('\n')
|
||||
mountEditor(content)
|
||||
|
||||
cy.get('.cm-line').eq(3).click()
|
||||
cy.focused().type('{enter}')
|
||||
|
||||
cy.get('.cm-content').should(
|
||||
'have.text',
|
||||
[' foo', ' bar', ' baz', ' '].join('')
|
||||
)
|
||||
})
|
||||
|
||||
it('handles Enter in an empty list item at the end of a nested list with subsequent items', function () {
|
||||
const content = [
|
||||
'\\begin{itemize}',
|
||||
'\\item foo bar',
|
||||
'\\begin{itemize}',
|
||||
'\\item baz',
|
||||
'\\item ',
|
||||
'\\end{itemize}',
|
||||
'\\item test',
|
||||
'\\end{itemize}',
|
||||
].join('\n')
|
||||
mountEditor(content)
|
||||
|
||||
cy.get('.cm-line').eq(3).click()
|
||||
cy.focused().type('{enter}')
|
||||
|
||||
cy.get('.cm-content').should(
|
||||
'have.text',
|
||||
[' foo', ' bar', ' baz', ' ', ' test'].join('')
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue