[visual] support for multiple authors in /maketitle (#13713)

* [visual] support for multiple titles in /maketitle

* support \and in author name

* using isMatchWith

* fix isMatchWith check

* fix isMatchWith... again

* add tests

* test author text replace and mouse events

* prettier

* don't handle updating node events

* use the arguments order as before

GitOrigin-RevId: 99b3d1c22a2dc1258a5cdf95738852028895f54a
This commit is contained in:
Domagoj Kriskovic 2023-07-10 15:31:42 +02:00 committed by Copybot
parent 7487f37bc1
commit 9cb0122ea3
3 changed files with 120 additions and 44 deletions

View file

@ -142,11 +142,11 @@ export const atomicDecorations = (options: Options) => {
node: SyntaxNode
content: string
}
author?: {
authors: {
node: SyntaxNode
content: string
}
} = { from: 0, to: 0 }
}[]
} = { from: 0, to: 0, authors: [] }
// find the positions of the title and author in the preamble
tree.iterate({
@ -165,7 +165,7 @@ export const atomicDecorations = (options: Options) => {
const node = nodeRef.node.getChild('TextArgument')
if (node) {
const content = state.sliceDoc(node.from, node.to)
preamble.author = { node, content }
preamble.authors.push({ node, content })
}
}
},

View file

@ -10,10 +10,10 @@ type Preamble = {
node: SyntaxNode
content: string
}
author?: {
authors: {
node: SyntaxNode
content: string
}
}[]
}
export class MakeTitleWidget extends WidgetType {
@ -32,10 +32,7 @@ export class MakeTitleWidget extends WidgetType {
}
eq(widget: MakeTitleWidget) {
return isShallowEqualPreamble(widget.preamble, this.preamble, [
'title',
'author',
])
return isShallowEqualPreamble(widget.preamble, this.preamble)
}
updateDOM(element: HTMLElement, view: EditorView): boolean {
@ -80,26 +77,30 @@ export class MakeTitleWidget extends WidgetType {
})
}
if (this.preamble.author) {
const authorsElement = buildAuthorsElement(
view.state,
this.preamble.author.node
)
authorsElement.addEventListener('mouseup', () => {
if (this.preamble.author) {
selectNode(view, this.preamble.author.node)
}
})
if (this.preamble.authors.length) {
const authorsElement = buildAuthorsElement(this.preamble.authors, view)
element.append(authorsElement)
}
}
}
const isShallowEqualPreamble = (
a: Preamble,
b: Preamble,
fields: Array<keyof Preamble>
) => fields.every(field => a[field]?.content === b[field]?.content)
function isShallowEqualPreamble(a: Preamble, b: Preamble) {
if (a.title?.content !== b.title?.content) {
return false // title changed
}
if (a.authors.length !== b.authors.length) {
return false // number of authors changed
}
for (let i = 0; i < a.authors.length; i++) {
if (a.authors[i].content !== b.authors[i].content) {
return false // author changed
}
}
return true
}
function buildTitleElement(
state: EditorState,
@ -112,28 +113,33 @@ function buildTitleElement(
}
function buildAuthorsElement(
state: EditorState,
argumentNode: SyntaxNode
): HTMLDivElement {
const element = document.createElement('div')
element.classList.add('ol-cm-authors')
authors: { node: SyntaxNode; content: string }[],
view: EditorView
) {
const authorsElement = document.createElement('div')
authorsElement.classList.add('ol-cm-authors')
const content = state.sliceDoc(argumentNode.from + 1, argumentNode.to - 1)
const authors = content.replaceAll(/\s+/g, ' ').split('\\and')
for (const { node, content } of authors) {
const authorContent = content.slice(1, -1) // trimming the braces
const authors = authorContent.replaceAll(/\s+/g, ' ').split('\\and')
for (const authorParts of authors) {
const authorElement = document.createElement('div')
authorElement.classList.add('ol-cm-author')
for (const author of authors) {
const authorElement = document.createElement('div')
authorElement.classList.add('ol-cm-author')
for (const authorInfoItem of authorParts.split('\\\\')) {
const authorLineElement = document.createElement('div')
authorLineElement.classList.add('ol-cm-author-line')
authorLineElement.textContent = authorInfoItem.trim()
authorElement.appendChild(authorLineElement)
for (const authorInfoItem of author.split('\\\\')) {
const authorLineElement = document.createElement('div')
authorLineElement.classList.add('ol-cm-author-line')
authorLineElement.textContent = authorInfoItem.trim()
authorElement.appendChild(authorLineElement)
}
authorElement.addEventListener('mouseup', () => {
selectNode(view, node)
})
authorsElement.append(authorElement)
}
element.append(authorElement)
}
return element
return authorsElement
}

View file

@ -370,8 +370,78 @@ describe('<CodeMirrorEditor/> in Visual mode', function () {
cy.get('@first-line').should('have.text', 'Foo \\footnote{Bar.} ')
})
it('should show document preamble', function () {
cy.get('@first-line').type(
[
'\\author{{}Author}',
'\\title{{}Document title}',
'\\begin{{}document}',
'\\maketitle',
'\\end{{}document}',
'',
].join('{Enter}')
)
cy.get('.ol-cm-preamble-widget').should('have.length', 1)
cy.get('.ol-cm-preamble-widget').click()
cy.get('.ol-cm-preamble-line').eq(0).should('contain', '\\author{Author}')
cy.get('.ol-cm-preamble-line')
.eq(1)
.should('contain', '\\title{Document title}')
cy.get('.ol-cm-preamble-line').eq(2).should('contain', '\\begin{document}')
cy.get('.ol-cm-preamble-line').eq(3).should('not.exist')
})
it('should show multiple authors', function () {
cy.get('@first-line').type(
[
'\\author{{}Author \\and Author2}',
'\\author{{}Author3}',
'\\title{{}Document title}',
'\\begin{{}document}',
'\\maketitle',
'\\end{{}document}',
'',
].join('{Enter}')
)
cy.get('.ol-cm-preamble-widget').should('have.length', 1)
cy.get('.ol-cm-preamble-widget').click()
cy.get('.ol-cm-authors').should('have.length', 1)
cy.get('.ol-cm-authors .ol-cm-author').should('have.length', 3)
})
it('should update authors', function () {
cy.get('@first-line').type(
[
'\\author{{}Author \\and Author2}',
'\\author{{}Author3}',
'\\title{{}Document title}',
'\\begin{{}document}',
'\\maketitle',
'\\end{{}document}',
'',
].join('{Enter}')
)
cy.get('.ol-cm-preamble-widget').should('have.length', 1)
cy.get('.ol-cm-preamble-widget').click()
cy.get('.ol-cm-authors').should('have.length', 1)
cy.get('.ol-cm-author').eq(0).should('contain', 'Author')
cy.get('.ol-cm-author').eq(1).should('contain', 'Author2')
cy.get('.ol-cm-author').eq(2).should('contain', 'Author3')
cy.get('.ol-cm-author').eq(0).click()
cy.get('.ol-cm-preamble-line').eq(0).type('{leftarrow}{backspace}New')
cy.get('.ol-cm-author').eq(1).should('contain', 'AuthorNew')
// update author without changing node from/to coordinates
cy.get('.ol-cm-author').eq(0).click()
cy.get('.ol-cm-preamble-line').eq(0).type('{leftarrow}{shift}{leftarrow}X')
cy.get('.ol-cm-author').eq(1).should('contain', 'AuthorNeX')
})
// TODO: \input
// TODO: Math
// TODO: Abstract
// TODO: Preamble
})