mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
[visual] Improve handling of pasted lists (#14912)
GitOrigin-RevId: 15e91ef6807433c5fb0a9bedbd5fea42ac35a5f0
This commit is contained in:
parent
a2ff44d7d4
commit
cb297e342a
2 changed files with 82 additions and 11 deletions
|
@ -522,13 +522,13 @@ const tabular = (element: HTMLTableElement) => {
|
|||
.join(' ')
|
||||
}
|
||||
|
||||
const listDepth = (
|
||||
element: HTMLOListElement | HTMLUListElement | HTMLLIElement
|
||||
): number => Math.max(0, matchingParents(element, 'ul,ol').length - 1)
|
||||
const listDepth = (element: HTMLElement): number =>
|
||||
Math.max(0, matchingParents(element, 'ul,ol').length)
|
||||
|
||||
const listIndent = (
|
||||
element: HTMLOListElement | HTMLUListElement | HTMLLIElement
|
||||
): string => '\t'.repeat(listDepth(element))
|
||||
const indentUnit = ' ' // TODO: replace hard-coded indent unit?
|
||||
|
||||
const listIndent = (element: HTMLElement | null): string =>
|
||||
element ? indentUnit.repeat(listDepth(element)) : ''
|
||||
|
||||
type ElementSelector<T extends string, E extends HTMLElement = HTMLElement> = {
|
||||
selector: T
|
||||
|
@ -610,6 +610,37 @@ const startMultirow = (element: HTMLTableCellElement): string => {
|
|||
return `\\multirow{${rowspan}}{*}{`
|
||||
}
|
||||
|
||||
const listPrefix = (element: HTMLOListElement | HTMLUListElement) => {
|
||||
if (isListOrListItemElement(element.parentElement)) {
|
||||
// within a list = newline
|
||||
return '\n'
|
||||
}
|
||||
// outside a list = double newline
|
||||
return '\n\n'
|
||||
}
|
||||
|
||||
const listSuffix = (element: HTMLOListElement | HTMLUListElement) => {
|
||||
if (listDepth(element) === 0) {
|
||||
// a top-level list => newline
|
||||
return '\n'
|
||||
} else {
|
||||
// a nested list => no extra newline
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
const isListElement = (
|
||||
element: Element | null
|
||||
): element is HTMLOListElement | HTMLUListElement =>
|
||||
element !== null && listNodeNames.includes(element.nodeName)
|
||||
|
||||
const isListOrListItemElement = (
|
||||
element: Element | null
|
||||
): element is HTMLOListElement | HTMLUListElement =>
|
||||
element !== null && (isListElement(element) || element.nodeName === 'LI')
|
||||
|
||||
const listNodeNames = ['OL', 'UL']
|
||||
|
||||
const selectors = [
|
||||
createSelector({
|
||||
selector: 'b',
|
||||
|
@ -826,18 +857,28 @@ const selectors = [
|
|||
createSelector({
|
||||
// selector: 'ul:has(> li:nth-child(2))', // only select lists with at least 2 items (once Firefox supports :has())
|
||||
selector: 'ul',
|
||||
start: element => `\n\n${listIndent(element)}\\begin{itemize}`,
|
||||
end: element => `\n${listIndent(element)}\\end{itemize}\n`,
|
||||
start: element => {
|
||||
return `${listPrefix(element)}${listIndent(element)}\\begin{itemize}`
|
||||
},
|
||||
end: element => {
|
||||
return `\n${listIndent(element)}\\end{itemize}${listSuffix(element)}`
|
||||
},
|
||||
}),
|
||||
createSelector({
|
||||
// selector: 'ol:has(> li:nth-child(2))', // only select lists with at least 2 items (once Firefox supports :has())
|
||||
selector: 'ol',
|
||||
start: element => `\n\n${listIndent(element)}\\begin{enumerate}`,
|
||||
end: element => `\n${listIndent(element)}\\end{enumerate}\n`,
|
||||
start: element => {
|
||||
return `${listPrefix(element)}${listIndent(element)}\\begin{enumerate}`
|
||||
},
|
||||
end: element => {
|
||||
return `\n${listIndent(element)}\\end{enumerate}${listSuffix(element)}`
|
||||
},
|
||||
}),
|
||||
createSelector({
|
||||
selector: 'li',
|
||||
start: element => `\n${listIndent(element)}\t\\item `,
|
||||
start: element => {
|
||||
return `\n${listIndent(element.parentElement)}${indentUnit}\\item `
|
||||
},
|
||||
}),
|
||||
createSelector({
|
||||
selector: 'p',
|
||||
|
|
|
@ -69,6 +69,36 @@ describe('<CodeMirrorEditor/> paste HTML in Visual mode', function () {
|
|||
cy.get('.ol-cm-item').should('have.length', 2)
|
||||
})
|
||||
|
||||
it('handles a pasted nested bullet list', function () {
|
||||
mountEditor()
|
||||
|
||||
const data =
|
||||
'<ul><li>foo</li><li><ul><li>bar</li><li>baz</li></ul></li></ul>'
|
||||
|
||||
const clipboardData = new DataTransfer()
|
||||
clipboardData.setData('text/html', data)
|
||||
cy.get('@content').trigger('paste', { clipboardData })
|
||||
|
||||
cy.get('@content').should('have.text', ' foo bar baz')
|
||||
cy.get('.ol-cm-item').should('have.length', 4)
|
||||
cy.get('.cm-line').should('have.length', 6)
|
||||
})
|
||||
|
||||
it('handles a pasted nested numbered list', function () {
|
||||
mountEditor()
|
||||
|
||||
const data =
|
||||
'<ol><li>foo</li><li><ol><li>bar</li><li>baz</li></ol></li></ol>'
|
||||
|
||||
const clipboardData = new DataTransfer()
|
||||
clipboardData.setData('text/html', data)
|
||||
cy.get('@content').trigger('paste', { clipboardData })
|
||||
|
||||
cy.get('@content').should('have.text', ' foo bar baz')
|
||||
cy.get('.ol-cm-item').should('have.length', 4)
|
||||
cy.get('.cm-line').should('have.length', 6)
|
||||
})
|
||||
|
||||
it('removes a solitary item from a list', function () {
|
||||
mountEditor()
|
||||
|
||||
|
|
Loading…
Reference in a new issue