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 { keymap } from '@codemirror/view'
|
||||||
import { EditorSelection, Prec } from '@codemirror/state'
|
|
||||||
import { ancestorNodeOfType } from '../../utils/tree-query'
|
|
||||||
import {
|
import {
|
||||||
getIndentation,
|
ChangeSpec,
|
||||||
IndentContext,
|
EditorSelection,
|
||||||
indentString,
|
Prec,
|
||||||
} from '@codemirror/language'
|
SelectionRange,
|
||||||
|
} from '@codemirror/state'
|
||||||
|
import { ancestorNodeOfType } from '../../utils/tree-query'
|
||||||
import {
|
import {
|
||||||
cursorIsAtStartOfListItem,
|
cursorIsAtStartOfListItem,
|
||||||
indentDecrease,
|
indentDecrease,
|
||||||
indentIncrease,
|
indentIncrease,
|
||||||
} from '../toolbar/commands'
|
} from '../toolbar/commands'
|
||||||
|
import { createListItem } from '@/features/source-editor/extensions/visual/utils/list-item'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A keymap which provides behaviours for the visual editor,
|
* A keymap which provides behaviours for the visual editor,
|
||||||
|
@ -39,29 +40,53 @@ export const visualKeymap = Prec.highest(
|
||||||
if (line.text.trim() === '\\item') {
|
if (line.text.trim() === '\\item') {
|
||||||
// no content on this line
|
// no content on this line
|
||||||
|
|
||||||
// delete this line
|
// outside the end of the current list
|
||||||
const changes = state.changes({
|
const pos = listNode.to + 1
|
||||||
|
|
||||||
|
// delete the current line
|
||||||
|
const deleteCurrentLine = {
|
||||||
from: line.from,
|
from: line.from,
|
||||||
to: line.to + 1,
|
to: line.to + 1,
|
||||||
insert: '',
|
insert: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
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',
|
||||||
})
|
})
|
||||||
|
|
||||||
// the start of the line after the list environment
|
// place the cursor at the end of the new list item
|
||||||
const range = EditorSelection.cursor(endLine.to + 1).map(
|
range = EditorSelection.cursor(pos + newListItem.length)
|
||||||
changes
|
} else {
|
||||||
)
|
// place the cursor outside the end of the current list
|
||||||
|
range = EditorSelection.cursor(pos)
|
||||||
|
}
|
||||||
|
|
||||||
handled = true
|
handled = true
|
||||||
|
|
||||||
return { changes, range }
|
return {
|
||||||
|
changes,
|
||||||
|
range: range.map(state.changes(deleteCurrentLine)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a new list item
|
// handle a list item that isn't at the end of a list
|
||||||
const cx = new IndentContext(state)
|
|
||||||
const columns = getIndentation(cx, from) ?? 0
|
const insert = '\n' + createListItem(state, from)
|
||||||
const indent = indentString(state, columns)
|
|
||||||
const insert = `\n${indent}\\item `
|
|
||||||
|
|
||||||
const countWhitespaceAfterPosition = (pos: number) => {
|
const countWhitespaceAfterPosition = (pos: number) => {
|
||||||
const line = state.doc.lineAt(pos)
|
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(''))
|
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