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(' ')
|
.join(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
const listDepth = (
|
const listDepth = (element: HTMLElement): number =>
|
||||||
element: HTMLOListElement | HTMLUListElement | HTMLLIElement
|
Math.max(0, matchingParents(element, 'ul,ol').length)
|
||||||
): number => Math.max(0, matchingParents(element, 'ul,ol').length - 1)
|
|
||||||
|
|
||||||
const listIndent = (
|
const indentUnit = ' ' // TODO: replace hard-coded indent unit?
|
||||||
element: HTMLOListElement | HTMLUListElement | HTMLLIElement
|
|
||||||
): string => '\t'.repeat(listDepth(element))
|
const listIndent = (element: HTMLElement | null): string =>
|
||||||
|
element ? indentUnit.repeat(listDepth(element)) : ''
|
||||||
|
|
||||||
type ElementSelector<T extends string, E extends HTMLElement = HTMLElement> = {
|
type ElementSelector<T extends string, E extends HTMLElement = HTMLElement> = {
|
||||||
selector: T
|
selector: T
|
||||||
|
@ -610,6 +610,37 @@ const startMultirow = (element: HTMLTableCellElement): string => {
|
||||||
return `\\multirow{${rowspan}}{*}{`
|
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 = [
|
const selectors = [
|
||||||
createSelector({
|
createSelector({
|
||||||
selector: 'b',
|
selector: 'b',
|
||||||
|
@ -826,18 +857,28 @@ const selectors = [
|
||||||
createSelector({
|
createSelector({
|
||||||
// selector: 'ul:has(> li:nth-child(2))', // only select lists with at least 2 items (once Firefox supports :has())
|
// selector: 'ul:has(> li:nth-child(2))', // only select lists with at least 2 items (once Firefox supports :has())
|
||||||
selector: 'ul',
|
selector: 'ul',
|
||||||
start: element => `\n\n${listIndent(element)}\\begin{itemize}`,
|
start: element => {
|
||||||
end: element => `\n${listIndent(element)}\\end{itemize}\n`,
|
return `${listPrefix(element)}${listIndent(element)}\\begin{itemize}`
|
||||||
|
},
|
||||||
|
end: element => {
|
||||||
|
return `\n${listIndent(element)}\\end{itemize}${listSuffix(element)}`
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
createSelector({
|
createSelector({
|
||||||
// selector: 'ol:has(> li:nth-child(2))', // only select lists with at least 2 items (once Firefox supports :has())
|
// selector: 'ol:has(> li:nth-child(2))', // only select lists with at least 2 items (once Firefox supports :has())
|
||||||
selector: 'ol',
|
selector: 'ol',
|
||||||
start: element => `\n\n${listIndent(element)}\\begin{enumerate}`,
|
start: element => {
|
||||||
end: element => `\n${listIndent(element)}\\end{enumerate}\n`,
|
return `${listPrefix(element)}${listIndent(element)}\\begin{enumerate}`
|
||||||
|
},
|
||||||
|
end: element => {
|
||||||
|
return `\n${listIndent(element)}\\end{enumerate}${listSuffix(element)}`
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
createSelector({
|
createSelector({
|
||||||
selector: 'li',
|
selector: 'li',
|
||||||
start: element => `\n${listIndent(element)}\t\\item `,
|
start: element => {
|
||||||
|
return `\n${listIndent(element.parentElement)}${indentUnit}\\item `
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
createSelector({
|
createSelector({
|
||||||
selector: 'p',
|
selector: 'p',
|
||||||
|
|
|
@ -69,6 +69,36 @@ describe('<CodeMirrorEditor/> paste HTML in Visual mode', function () {
|
||||||
cy.get('.ol-cm-item').should('have.length', 2)
|
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 () {
|
it('removes a solitary item from a list', function () {
|
||||||
mountEditor()
|
mountEditor()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue