From c21b8e31fd5d894b6aa77fb53eef58251e93c47f Mon Sep 17 00:00:00 2001 From: Domagoj Kriskovic Date: Tue, 18 Jul 2023 12:24:18 +0200 Subject: [PATCH] [visual] suport for \texttt command in maketitle (#13824) GitOrigin-RevId: 712eb8fb4fc0f2cbc5cd3c2e39ff6b8af39c0a49 --- .../visual/utils/typeset-content.ts | 44 ++++++++++++------- .../visual/visual-widgets/maketitle.ts | 38 +++++++++------- .../codemirror-editor-visual.spec.tsx | 38 ++++++++++++++++ 3 files changed, 87 insertions(+), 33 deletions(-) diff --git a/services/web/frontend/js/features/source-editor/extensions/visual/utils/typeset-content.ts b/services/web/frontend/js/features/source-editor/extensions/visual/utils/typeset-content.ts index 913c045b83..4e0e5efab1 100644 --- a/services/web/frontend/js/features/source-editor/extensions/visual/utils/typeset-content.ts +++ b/services/web/frontend/js/features/source-editor/extensions/visual/utils/typeset-content.ts @@ -58,6 +58,26 @@ export function typesetNodeIntoElement( pushAncestor(document.createElement('em')) const textArgument = childNode.getChild('TextArgument') from = textArgument?.getChild('LongArg')?.from ?? childNode.to + } else if (isUnknownCommandWithName(childNode, '\\texttt', state)) { + const spanElement = document.createElement('span') + spanElement.classList.add('ol-cm-command-texttt') + pushAncestor(spanElement) + const textArgument = childNode.getChild('TextArgument') + from = textArgument?.getChild('LongArg')?.from ?? childNode.to + } else if (isUnknownCommandWithName(childNode, '\\and', state)) { + const spanElement = document.createElement('span') + spanElement.classList.add('ol-cm-command-and') + pushAncestor(spanElement) + const textArgument = childNode.getChild('TextArgument') + from = textArgument?.getChild('LongArg')?.from ?? childNode.to + } else if ( + isUnknownCommandWithName(childNode, '\\corref', state) || + isUnknownCommandWithName(childNode, '\\fnref', state) || + isUnknownCommandWithName(childNode, '\\thanks', state) + ) { + // ignoring these commands + from = childNode.to + return false } else if (isNewline(childNode, state)) { ancestor().appendChild(document.createElement('br')) from = childNode.to @@ -65,23 +85,13 @@ export function typesetNodeIntoElement( }, function leave(childNodeRef) { const childNode = childNodeRef.node - if (isUnknownCommandWithName(childNode, '\\textit', state)) { - const typeSetElement = popAncestor() - ancestor().appendChild(typeSetElement) - const textArgument = childNode.getChild('TextArgument') - const endBrace = textArgument?.getChild('CloseBrace') - if (endBrace) { - from = endBrace.to - } - } else if (isUnknownCommandWithName(childNode, '\\textbf', state)) { - const typeSetElement = popAncestor() - ancestor().appendChild(typeSetElement) - const textArgument = childNode.getChild('TextArgument') - const endBrace = textArgument?.getChild('CloseBrace') - if (endBrace) { - from = endBrace.to - } - } else if (isUnknownCommandWithName(childNode, '\\emph', state)) { + if ( + isUnknownCommandWithName(childNode, '\\and', state) || + isUnknownCommandWithName(childNode, '\\textit', state) || + isUnknownCommandWithName(childNode, '\\textbf', state) || + isUnknownCommandWithName(childNode, '\\emph', state) || + isUnknownCommandWithName(childNode, '\\texttt', state) + ) { const typeSetElement = popAncestor() ancestor().appendChild(typeSetElement) const textArgument = childNode.getChild('TextArgument') diff --git a/services/web/frontend/js/features/source-editor/extensions/visual/visual-widgets/maketitle.ts b/services/web/frontend/js/features/source-editor/extensions/visual/visual-widgets/maketitle.ts index d121659727..4d6c480ac6 100644 --- a/services/web/frontend/js/features/source-editor/extensions/visual/visual-widgets/maketitle.ts +++ b/services/web/frontend/js/features/source-editor/extensions/visual/visual-widgets/maketitle.ts @@ -119,26 +119,32 @@ function buildAuthorsElement( const authorsElement = document.createElement('div') authorsElement.classList.add('ol-cm-authors') - 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 { node } of authors) { + const typesettedAuthors = document.createElement('div') + typesetNodeIntoElement(node, typesettedAuthors, view.state) - for (const author of authors) { - const authorElement = document.createElement('div') - authorElement.classList.add('ol-cm-author') + let currentAuthor = document.createElement('div') + currentAuthor.classList.add('ol-cm-author') + authorsElement.append(currentAuthor) - 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) + while (typesettedAuthors.firstChild) { + const child = typesettedAuthors.firstChild + if ( + child instanceof HTMLElement && + child.classList.contains('ol-cm-command-and') + ) { + currentAuthor = document.createElement('div') + currentAuthor.classList.add('ol-cm-author') + authorsElement.append(currentAuthor) + child.remove() + } else { + currentAuthor.append(child) } - - authorElement.addEventListener('mouseup', () => { - selectNode(view, node) - }) - authorsElement.append(authorElement) } + + currentAuthor.addEventListener('mouseup', () => { + selectNode(view, node) + }) } return authorsElement diff --git a/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual.spec.tsx b/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual.spec.tsx index 2b8c59d450..c26afdf8ba 100644 --- a/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual.spec.tsx +++ b/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual.spec.tsx @@ -419,6 +419,7 @@ describe(' in Visual mode', function () { const deleteLine = '{command}{leftArrow}{shift}{command}{rightArrow}{backspace}' + // italic, bold and emph cy.get('@second-line').type(deleteLine) cy.get('@second-line').type( '\\title{{}formatted with \\textit{{}italic} \\textbf{{}bold} \\emph{{}emph}}' @@ -437,6 +438,24 @@ describe(' in Visual mode', function () { 'title
formated only italic' ) + // texttt command + cy.get('@second-line').type(deleteLine) + cy.get('@second-line').type('\\title{{}title with \\texttt{{}command}}') + cy.get('.ol-cm-title').should( + 'contain.html', + 'title with command' + ) + + cy.get('@second-line').type(deleteLine) + cy.get('@second-line').type( + '\\title{{}title with \\texttt{{}\\textbf{{}command}}}' + ) + cy.get('.ol-cm-title').should( + 'contain.html', + 'title with command' + ) + + // unsupported commands cy.get('@second-line').type(deleteLine) cy.get('@second-line').type('\\title{{}Title with \\& ampersands}') cy.get('.ol-cm-title').should( @@ -528,6 +547,25 @@ describe(' in Visual mode', function () { cy.get('.ol-cm-author').eq(1).should('contain', 'AuthorNeX') }) + it('should ignore some commands in author', function () { + cy.get('@first-line').type( + [ + '\\author{{}Author with \\corref{{}cor1} and \\fnref{{}label2} in the name}', + '\\title{{}Document title}', + '\\begin{{}document}', + '\\maketitle', + '\\end{{}document}', + '', + ].join('{Enter}') + ) + + cy.get('.ol-cm-authors').should('have.length', 1) + cy.get('.ol-cm-author').should( + 'contain.html', + 'Author with and in the name' + ) + }) + describe('handling of special characters', function () { it('decorates a tilde with a non-breaking space', function () { cy.get('@first-line').type('Test~test')