Kaynağa Gözat

[cm6] support \emph and \textbf in maketitle (#13703)

* [cm6] support \textbf in maketitle

* support for emph

* added support for \\textbf

* fix overlapping ignored ranges

* fix overlapping fn, added test

* multiple maketitle cases in the same test

* prettier

* simplify from calculation

* added test cases from #13736

GitOrigin-RevId: 00dcf15d6d62903ae30b387a16929e68dde8ca1b
Domagoj Kriskovic 3 yıl önce
ebeveyn
işleme
8ab6239a24

+ 33 - 13
services/web/frontend/js/features/source-editor/extensions/visual/utils/typeset-content.ts

@@ -34,8 +34,6 @@ export function typesetNodeIntoElement(
   const popAncestor = () => ancestorStack.pop()!
   const pushAncestor = (x: HTMLElement) => ancestorStack.push(x)
 
-  // NOTE: Quite hack-ish way to omit closing braces from the output
-  const ignoredRanges: { from: number; to: number }[] = []
   let from = node.from
 
   node.cursor().iterate(
@@ -45,20 +43,21 @@ export function typesetNodeIntoElement(
         ancestor().append(
           document.createTextNode(state.sliceDoc(from, childNode.from))
         )
-        from = ignoredRanges.some(
-          range => range.from <= childNode.from && range.to >= childNode.from
-        )
-          ? childNode.to
-          : childNode.from
+
+        from = childNode.from
       }
       if (isUnknownCommandWithName(childNode, '\\textit', state)) {
         pushAncestor(document.createElement('i'))
-        const argument = childNode.getChild('TextArgument')
-        from = argument?.getChild('LongArg')?.from ?? childNode.to
-        const endBrace = argument?.getChild('CloseBrace')
-        if (endBrace) {
-          ignoredRanges.push(endBrace)
-        }
+        const textArgument = childNode.getChild('TextArgument')
+        from = textArgument?.getChild('LongArg')?.from ?? childNode.to
+      } else if (isUnknownCommandWithName(childNode, '\\textbf', state)) {
+        pushAncestor(document.createElement('b'))
+        const textArgument = childNode.getChild('TextArgument')
+        from = textArgument?.getChild('LongArg')?.from ?? childNode.to
+      } else if (isUnknownCommandWithName(childNode, '\\emph', state)) {
+        pushAncestor(document.createElement('em'))
+        const textArgument = childNode.getChild('TextArgument')
+        from = textArgument?.getChild('LongArg')?.from ?? childNode.to
       } else if (isNewline(childNode, state)) {
         ancestor().appendChild(document.createElement('br'))
         from = childNode.to
@@ -69,6 +68,27 @@ export function typesetNodeIntoElement(
       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)) {
+        const typeSetElement = popAncestor()
+        ancestor().appendChild(typeSetElement)
+        const textArgument = childNode.getChild('TextArgument')
+        const endBrace = textArgument?.getChild('CloseBrace')
+        if (endBrace) {
+          from = endBrace.to
+        }
       }
     }
   )

+ 33 - 0
services/web/test/frontend/features/source-editor/components/codemirror-editor-visual.spec.tsx

@@ -360,6 +360,39 @@ describe('<CodeMirrorEditor/> in Visual mode', function () {
     cy.get('.ol-cm-maketitle')
     cy.get('.ol-cm-title').should('contain.html', 'Document title<br>with')
     cy.get('.ol-cm-author').should('have.text', 'Author')
+
+    cy.get('.ol-cm-preamble-widget').click()
+    const deleteLine =
+      '{command}{leftArrow}{shift}{command}{rightArrow}{backspace}'
+
+    cy.get('@second-line').type(deleteLine)
+    cy.get('@second-line').type(
+      '\\title{{}formatted with \\textit{{}italic} \\textbf{{}bold} \\emph{{}emph}}'
+    )
+    cy.get('.ol-cm-title').should(
+      'contain.html',
+      'formatted with <i>italic</i> <b>bold</b> <em>emph</em>'
+    )
+
+    cy.get('@second-line').type(deleteLine)
+    cy.get('@second-line').type(
+      '\\title{{}title\\\\ \\textbf{{}\\textit{{}\\emph{{}formated}}} \\textit{{}only italic}}'
+    )
+    cy.get('.ol-cm-title').should(
+      'contain.html',
+      'title<br> <b><i><em>formated</em></i></b> <i>only italic</i>'
+    )
+
+    cy.get('@second-line').type(deleteLine)
+    cy.get('@second-line').type('\\title{{}Title with \\& ampersands}')
+    cy.get('.ol-cm-title').should(
+      'contain.html',
+      'Title with \\&amp; ampersands'
+    )
+
+    cy.get('@second-line').type(deleteLine)
+    cy.get('@second-line').type('\\title{{}My \\LaTeX{{}} document}')
+    cy.get('.ol-cm-title').should('contain.html', 'My \\LaTeX{} document')
   })
 
   it('decorates footnotes', function () {