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 246fc5f49a..913c045b83 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
@@ -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
+ }
}
}
)
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 f8c298610e..3159f7fbbf 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
@@ -360,6 +360,39 @@ describe(' in Visual mode', function () {
cy.get('.ol-cm-maketitle')
cy.get('.ol-cm-title').should('contain.html', 'Document title
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 italic bold emph'
+ )
+
+ 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
formated only italic'
+ )
+
+ 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 \\& 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 () {