Просмотр исходного кода

Handle content wrapped in a monospace pre (#14801)

GitOrigin-RevId: 7198e56ea496b8e7496bd637419586019ce56270
Alf Eaton 3 лет назад
Родитель
Сommit
f7e4946056

+ 39 - 4
services/web/frontend/js/features/source-editor/extensions/visual/paste-html.ts

@@ -83,11 +83,33 @@ const removeUnwantedElements = (
   }
 }
 
+const findCodeContainingElement = (documentElement: HTMLElement) => {
+  let result: HTMLElement | null
+
+  // a code element
+  result = documentElement.querySelector<HTMLElement>('code')
+  if (result) {
+    return result
+  }
+
+  // a pre element with "monospace" somewhere in the font family
+  result = documentElement.querySelector<HTMLPreElement>('pre')
+  if (result?.style.fontFamily.includes('monospace')) {
+    return result
+  }
+
+  return null
+}
+
 // return true if the text content of the first <code> element
 // is the same as the text content of the whole document element
-const onlyCode = (documentElement: HTMLElement) =>
-  documentElement.querySelector('code')?.textContent?.trim() ===
-  documentElement.textContent?.trim()
+const onlyCode = (documentElement: HTMLElement) => {
+  const codeElement = findCodeContainingElement(documentElement)
+
+  return (
+    codeElement?.textContent?.trim() === documentElement.textContent?.trim()
+  )
+}
 
 const htmlToLaTeX = (documentElement: HTMLElement) => {
   // remove style elements
@@ -149,12 +171,16 @@ const specialCharacterReplacer = (
   return `${prefix}\\${char}`
 }
 
+const isElementContainingCode = (element: HTMLElement) =>
+  element.tagName === 'CODE' ||
+  (element.tagName === 'PRE' && element.style.fontFamily.includes('monospace'))
+
 const protectSpecialCharacters = (documentElement: HTMLElement) => {
   const walker = document.createTreeWalker(
     documentElement,
     NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,
     node =>
-      isElementNode(node) && node.tagName === 'CODE'
+      isElementNode(node) && isElementContainingCode(node)
         ? NodeFilter.FILTER_REJECT
         : NodeFilter.FILTER_ACCEPT
   )
@@ -593,6 +619,15 @@ const selectors = [
     start: () => `\n\n\\begin{verbatim}\n`,
     end: () => `\n\\end{verbatim}\n\n`,
   }),
+  createSelector({
+    selector: 'pre',
+    match: element =>
+      element.style.fontFamily.includes('monospace') &&
+      element.firstElementChild?.nodeName !== 'CODE' &&
+      hasContent(element),
+    start: () => `\n\n\\begin{verbatim}\n`,
+    end: () => `\n\\end{verbatim}\n\n`,
+  }),
   createSelector({
     selector: '.ol-table-wrap',
     start: () => `\n\n\\begin{table}\n\\centering\n`,

+ 20 - 7
services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-paste-html.spec.tsx

@@ -295,23 +295,20 @@ describe('<CodeMirrorEditor/> paste HTML in Visual mode', function () {
     // TODO: assert that the "Go to page" link has been unescaped
   })
 
-  it('handles a pasted code block', function () {
+  it('handles pasted code in pre blocks', function () {
     mountEditor()
 
-    const data = 'test <pre><code>foo</code></pre> test'
+    const data = `test <pre><code>\\textbf{foo}</code></pre> <pre style="font-family: 'Lucida Console', monospace">\\textbf{foo}</pre> test`
 
     const clipboardData = new DataTransfer()
     clipboardData.setData('text/html', data)
     cy.get('@content').trigger('paste', { clipboardData })
 
-    cy.get('@content').should('have.text', 'test foo test')
-    cy.get('.ol-cm-environment-verbatim').should('have.length', 5)
-
-    cy.get('.cm-line').eq(2).click()
     cy.get('@content').should(
       'have.text',
-      'test \\begin{verbatim}foo\\end{verbatim} test'
+      'test \\textbf{foo} \\textbf{foo} test'
     )
+    cy.get('.ol-cm-environment-verbatim').should('have.length', 10)
   })
 
   it('handles a pasted blockquote', function () {
@@ -415,6 +412,22 @@ describe('<CodeMirrorEditor/> paste HTML in Visual mode', function () {
     cy.get('.ol-cm-environment-verbatim').should('have.length', 0)
   })
 
+  it('use text/plain for a pre element with monospace font', function () {
+    mountEditor()
+
+    const clipboardData = new DataTransfer()
+    clipboardData.setData(
+      'text/html',
+      '<pre style="font-family:Courier,monospace">foo</pre>'
+    )
+    clipboardData.setData('text/plain', 'foo')
+    cy.get('@content').trigger('paste', { clipboardData })
+
+    cy.get('@content').should('have.text', 'foo')
+    cy.get('.ol-cm-command-verb').should('have.length', 0)
+    cy.get('.ol-cm-environment-verbatim').should('have.length', 0)
+  })
+
   it('handles pasted text with formatting', function () {
     mountEditor()