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

Merge pull request #13594 from overleaf/ae-starred-caption

[cm6] Allow an asterisk after CaptionCtrlSeq

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

+ 2 - 1
services/web/frontend/js/features/source-editor/extensions/visual/atomic-decorations.ts

@@ -24,6 +24,7 @@ import { EndWidget } from './visual-widgets/end'
 import {
   getEnvironmentArguments,
   getEnvironmentName,
+  getUnstarredEnvironmentName,
   parseFigureData,
 } from '../../utils/tree-operations/environments'
 import { MathWidget } from './visual-widgets/math'
@@ -224,7 +225,7 @@ export const atomicDecorations = (options: Options) => {
       enter(nodeRef) {
         if (nodeRef.type.is('$Environment')) {
           if (shouldDecorate(state, nodeRef)) {
-            const envName = getEnvironmentName(nodeRef.node, state)
+            const envName = getUnstarredEnvironmentName(nodeRef.node, state)
             const hideInEnvironmentTypes = ['figure', 'table']
             if (envName && hideInEnvironmentTypes.includes(envName)) {
               const beginNode = nodeRef.node.getChild('BeginEnv')

+ 5 - 2
services/web/frontend/js/features/source-editor/extensions/visual/mark-decorations.ts

@@ -6,7 +6,7 @@ import {
 } from '@codemirror/view'
 import { EditorState, Range } from '@codemirror/state'
 import { syntaxTree } from '@codemirror/language'
-import { getEnvironmentName } from '../../utils/tree-operations/environments'
+import { getUnstarredEnvironmentName } from '../../utils/tree-operations/environments'
 import { centeringNodeForEnvironment } from '../../utils/tree-operations/figure'
 import { Tree } from '@lezer/common'
 
@@ -103,7 +103,10 @@ export const markDecorations = ViewPlugin.define(
                 }
               }
             } else if (nodeRef.type.is('$Environment')) {
-              const environmentName = getEnvironmentName(nodeRef.node, state)
+              const environmentName = getUnstarredEnvironmentName(
+                nodeRef.node,
+                state
+              )
 
               switch (environmentName) {
                 case 'abstract':

+ 1 - 1
services/web/frontend/js/features/source-editor/lezer-latex/latex.grammar

@@ -250,7 +250,7 @@ KnownCommand {
         IncludeGraphicsArgument { FilePathArgument }
     } |
     Caption {
-        CaptionCtrlSeq optionalWhitespace? OptionalArgument? TextArgument
+        CaptionCtrlSeq "*"? optionalWhitespace? OptionalArgument? TextArgument
     } |
     Label {
         LabelCtrlSeq optionalWhitespace? LabelArgument

+ 1 - 0
services/web/frontend/js/features/source-editor/lezer-latex/tokens.mjs

@@ -739,6 +739,7 @@ const otherKnownEnvNames = {
   document: DocumentEnvName,
   tikzpicture: TikzPictureEnvName,
   figure: FigureEnvName,
+  'figure*': FigureEnvName,
   subfigure: FigureEnvName,
   enumerate: ListEnvName,
   itemize: ListEnvName,

+ 5 - 0
services/web/frontend/js/features/source-editor/utils/tree-operations/environments.ts

@@ -236,6 +236,11 @@ export function getEnvironmentName(
   return state.sliceDoc(nameNode.from, nameNode.to)
 }
 
+export const getUnstarredEnvironmentName = (
+  node: SyntaxNode | null,
+  state: EditorState
+): string | undefined => getEnvironmentName(node, state)?.replace(/\*$/, '')
+
 export function getEnvironmentArguments(environmentNode: SyntaxNode) {
   return environmentNode.getChild('BeginEnv')?.getChildren('TextArgument')
 }

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

@@ -0,0 +1,72 @@
+import { FC } from 'react'
+import { EditorProviders } from '../../../helpers/editor-providers'
+import CodemirrorEditor from '../../../../../frontend/js/features/source-editor/components/codemirror-editor'
+import { mockScope } from '../helpers/mock-scope'
+
+const Container: FC = ({ children }) => (
+  <div style={{ width: 785, height: 785 }}>{children}</div>
+)
+
+const mountEditor = (content: string) => {
+  const scope = mockScope(content)
+  scope.editor.showVisual = true
+
+  cy.mount(
+    <Container>
+      <EditorProviders scope={scope}>
+        <CodemirrorEditor />
+      </EditorProviders>
+    </Container>
+  )
+
+  // wait for the content to be parsed and revealed
+  cy.get('.cm-content').should('have.css', 'opacity', '1')
+}
+
+describe('<CodeMirrorEditor/> floats', function () {
+  beforeEach(function () {
+    window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
+    cy.interceptEvents()
+    cy.interceptSpelling()
+  })
+
+  afterEach(function () {
+    window.metaAttributesCache.clear()
+  })
+
+  it('decorates a caption', function () {
+    mountEditor('\n\\caption{Foo}\n')
+    cy.get('.cm-line').eq(2).click()
+    cy.get('.cm-content').should('have.text', 'Foo')
+  })
+
+  it('decorates a caption in a figure', function () {
+    mountEditor('\\begin{figure}\n\\caption{Foo}\n\\end{figure}\n')
+    cy.get('.cm-line').eq(3).click()
+    cy.get('.cm-content').should('have.text', 'Foo')
+  })
+
+  it('decorates a caption in a table', function () {
+    mountEditor('\\begin{table}\n\\caption{Foo}\n\\end{table}\n')
+    cy.get('.cm-line').eq(3).click()
+    cy.get('.cm-content').should('have.text', 'Foo')
+  })
+
+  it('decorates a starred caption', function () {
+    mountEditor('\n\\caption*{Foo}\n')
+    cy.get('.cm-line').eq(2).click()
+    cy.get('.cm-content').should('have.text', 'Foo')
+  })
+
+  it('decorates a starred figure', function () {
+    mountEditor('\\begin{figure*}\n\\caption{Foo}\n\\end{figure*}\n')
+    cy.get('.cm-line').eq(3).click()
+    cy.get('.cm-content').should('have.text', 'Foo')
+  })
+
+  it('decorates a starred table', function () {
+    mountEditor('\\begin{table*}\n\\caption{Foo}\n\\end{table*}\n')
+    cy.get('.cm-line').eq(3).click()
+    cy.get('.cm-content').should('have.text', 'Foo')
+  })
+})