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

Migrate extension to `@overleaf/codemirror-tree-view` (#15769)

GitOrigin-RevId: 8355158040923a55dafc7b1c3f566f7e7703cb02
Alf Eaton 2 лет назад
Родитель
Сommit
cdd9ff9ed0

+ 20 - 0
package-lock.json

@@ -7637,6 +7637,17 @@
       "resolved": "services/clsi-perf",
       "link": true
     },
+    "node_modules/@overleaf/codemirror-tree-view": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/@overleaf/codemirror-tree-view/-/codemirror-tree-view-0.1.3.tgz",
+      "integrity": "sha512-/ysOnX+ovObqj0uR78tumQtK/y0qFwbawcCGxT9JDeyJPgfPrK3PYTIvoZ1SgmSxaXpOPkds7aL+4Hv6VWZqSw==",
+      "dev": true,
+      "peerDependencies": {
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.0.0"
+      }
+    },
     "node_modules/@overleaf/contacts": {
       "resolved": "services/contacts",
       "link": true
@@ -39635,6 +39646,7 @@
         "@opentelemetry/sdk-trace-base": "^1.15.2",
         "@opentelemetry/sdk-trace-web": "^1.15.2",
         "@opentelemetry/semantic-conventions": "^1.15.2",
+        "@overleaf/codemirror-tree-view": "^0.1.3",
         "@overleaf/ranges-tracker": "*",
         "@overleaf/stream-utils": "*",
         "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11",
@@ -46393,6 +46405,13 @@
         }
       }
     },
+    "@overleaf/codemirror-tree-view": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/@overleaf/codemirror-tree-view/-/codemirror-tree-view-0.1.3.tgz",
+      "integrity": "sha512-/ysOnX+ovObqj0uR78tumQtK/y0qFwbawcCGxT9JDeyJPgfPrK3PYTIvoZ1SgmSxaXpOPkds7aL+4Hv6VWZqSw==",
+      "dev": true,
+      "requires": {}
+    },
     "@overleaf/contacts": {
       "version": "file:services/contacts",
       "requires": {
@@ -47778,6 +47797,7 @@
         "@opentelemetry/sdk-trace-web": "^1.15.2",
         "@opentelemetry/semantic-conventions": "^1.15.2",
         "@overleaf/access-token-encryptor": "*",
+        "@overleaf/codemirror-tree-view": "^0.1.3",
         "@overleaf/fetch-utils": "*",
         "@overleaf/logger": "*",
         "@overleaf/metrics": "*",

+ 8 - 243
services/web/frontend/js/features/source-editor/languages/latex/codemirror-dev-tools.ts

@@ -1,22 +1,8 @@
-import {
-  Annotation,
-  Compartment,
-  EditorSelection,
-  EditorState,
-  StateEffect,
-  StateField,
-  Transaction,
-} from '@codemirror/state'
-import {
-  Decoration,
-  DecorationSet,
-  EditorView,
-  ViewPlugin,
-} from '@codemirror/view'
-import { syntaxTree } from '@codemirror/language'
 import { toggleVisualEffect } from '../../extensions/visual/visual'
-import { hasLanguageLoadedEffect } from '../../extensions/language'
 import customLocalStorage from '../../../../infrastructure/local-storage'
+import { Compartment } from '@codemirror/state'
+import { EditorView, ViewPlugin } from '@codemirror/view'
+import { treeView } from '@overleaf/codemirror-tree-view'
 
 // to enable: window.localStorage.setItem('cm6-dev-tools', '"on"')
 // to disable: window.localStorage.removeItem('cm6-dev-tools')
@@ -90,233 +76,12 @@ const toggleDevTools = () => {
   }
 }
 
-const createExtension = () =>
-  isActive() ? [devToolsView, highlightSelectedNode, devToolsTheme] : []
-
-const devToolsTheme = EditorView.baseTheme({
-  '.ol-cm-dev-tools-container': {
-    padding: '8px 8px 0',
-    backgroundColor: '#222',
-    color: '#eee',
-    fontSize: '13px',
-    flexShrink: '0',
-    fontFamily: '"SF Mono", monospace',
-    height: 'calc(100% - 32px)',
-    overflow: 'auto',
-    position: 'absolute',
+const treeViewTheme = EditorView.baseTheme({
+  // note: duplicate selector to ensure extension theme styles are overriden
+  '.cm-tree-view-container.cm-tree-view-container': {
     top: '32px',
-    right: 0,
-    width: '50%',
-  },
-  '.ol-cm-dev-tools-item': {
-    cursor: 'pointer',
-    borderTop: '2px solid transparent',
-    borderBottom: '2px solid transparent',
-    scrollMargin: '2em',
-  },
-  '.ol-cm-selected-node-highlight': {
-    backgroundColor: 'yellow',
-  },
-  '.ol-cm-dev-tools-covered-item': {
-    backgroundColor: 'rgba(255, 255, 0, 0.2)',
-  },
-  '.ol-cm-dev-tools-selected-item': {
-    backgroundColor: 'rgba(255, 255, 0, 0.5)',
-    color: '#000',
-  },
-  '.ol-cm-dev-tools-cursor-before': {
-    borderTopColor: 'rgba(255, 255, 0, 1)',
-    '& + .ol-cm-dev-tools-cursor-before': {
-      borderTopColor: 'transparent',
-    },
-  },
-  '.ol-cm-dev-tools-positions': {
-    position: 'sticky',
-    bottom: '0',
-    backgroundColor: 'inherit',
-    display: 'flex',
-    flexDirection: 'column',
-    alignItems: 'center',
-    justifyContent: 'flex-end',
+    minHeight: 'unset',
   },
-  '.ol-cm-dev-tools-position': {
-    padding: '4px 0',
-  },
-})
-
-const fromDevTools = Annotation.define()
-
-const transactionIsFromDevTools = (tr: Transaction) =>
-  tr.annotation(fromDevTools)
-
-const devToolsView = ViewPlugin.define(view => {
-  const scroller = document.querySelector<HTMLDivElement>('.cm-scroller')
-
-  if (!scroller) {
-    return {}
-  }
-
-  const container = document.createElement('div')
-  container.classList.add('ol-cm-dev-tools-container')
-  scroller.after(container)
-  scroller.style.width = '50%'
-
-  const highlightNodeRange = (from: number, to: number) => {
-    view.dispatch({
-      effects: [selectedNodeEffect.of({ from, to })],
-    })
-  }
-
-  const selectNodeRange = (from: number, to: number) => {
-    view.dispatch({
-      annotations: [fromDevTools.of(true)],
-      selection: EditorSelection.single(from, to),
-      effects: EditorView.scrollIntoView(from, { y: 'center' }),
-    })
-    view.focus()
-  }
-
-  buildPanel(view.state, container, highlightNodeRange, selectNodeRange, true)
-
-  return {
-    update(update) {
-      if (
-        update.docChanged ||
-        update.selectionSet ||
-        hasLanguageLoadedEffect(update)
-      ) {
-        const scroll = !update.transactions.some(transactionIsFromDevTools)
-        buildPanel(
-          update.state,
-          container,
-          highlightNodeRange,
-          selectNodeRange,
-          scroll
-        )
-      }
-    },
-    destroy() {
-      container.remove()
-      scroller.style.width = 'unset'
-    },
-  }
 })
 
-const buildPanel = (
-  state: EditorState,
-  container: HTMLDivElement,
-  highlightNodeRange: (from: number, to: number) => void,
-  selectNodeRange: (from: number, to: number) => void,
-  scroll: boolean
-) => {
-  container.textContent = '' // clear
-
-  const tree = syntaxTree(state)
-  const { selection } = state
-  let itemToCenter: HTMLDivElement
-
-  let depth = 0
-  tree.iterate({
-    enter(nodeRef) {
-      const { from, to, name } = nodeRef
-
-      const element = document.createElement('div')
-      element.classList.add('ol-cm-dev-tools-item')
-      element.style.paddingLeft = `${depth * 16}px`
-      element.textContent = name
-
-      element.addEventListener('mouseover', () => {
-        highlightNodeRange(from, to)
-      })
-
-      element.addEventListener('click', () => {
-        selectNodeRange(from, to)
-      })
-
-      container.append(element)
-
-      for (const range of selection.ranges) {
-        // completely covered by selection
-        if (range.from <= from && range.to >= to) {
-          element.classList.add('ol-cm-dev-tools-selected-item')
-          itemToCenter = element
-        } else if (
-          (range.from > from && range.from < to) ||
-          (range.to > from && range.to < to)
-        ) {
-          element.classList.add('ol-cm-dev-tools-covered-item')
-          itemToCenter = element
-        }
-
-        if (range.head === from) {
-          element.classList.add('ol-cm-dev-tools-cursor-before')
-          itemToCenter = element
-        }
-      }
-      depth++
-    },
-    leave(node) {
-      depth--
-    },
-  })
-
-  const positions = document.createElement('div')
-  positions.classList.add('ol-cm-dev-tools-positions')
-  container.append(positions)
-
-  for (const range of state.selection.ranges) {
-    const line = state.doc.lineAt(range.head)
-    const column = range.head - line.from + 1
-    const position = document.createElement('div')
-    position.classList.add('ol-cm-dev-tools-position')
-    position.textContent = `line ${line.number}, col ${column}, pos ${range.head}`
-    positions.append(position)
-  }
-
-  if (scroll && itemToCenter!) {
-    window.setTimeout(() => {
-      itemToCenter.scrollIntoView({
-        block: 'center',
-        inline: 'center',
-      })
-    })
-  }
-}
-
-const selectedNodeEffect = StateEffect.define<{
-  from: number
-  to: number
-} | null>()
-
-const highlightSelectedNode = StateField.define<DecorationSet>({
-  create() {
-    return Decoration.none
-  },
-  update(value, tr) {
-    if (tr.selection) {
-      value = Decoration.none
-    }
-    for (const effect of tr.effects) {
-      if (effect.is(selectedNodeEffect)) {
-        if (effect.value) {
-          const { from, to } = effect.value
-
-          // TODO: widget decoration if no range to decorate?
-          if (to > from) {
-            value = Decoration.set([
-              Decoration.mark({
-                class: 'ol-cm-selected-node-highlight',
-              }).range(from, to),
-            ])
-          }
-        } else {
-          value = Decoration.none
-        }
-      }
-    }
-    return value
-  },
-  provide(f) {
-    return EditorView.decorations.from(f)
-  },
-})
+const createExtension = () => (isActive() ? [treeView, treeViewTheme] : [])

+ 1 - 0
services/web/package.json

@@ -198,6 +198,7 @@
     "@opentelemetry/sdk-trace-base": "^1.15.2",
     "@opentelemetry/sdk-trace-web": "^1.15.2",
     "@opentelemetry/semantic-conventions": "^1.15.2",
+    "@overleaf/codemirror-tree-view": "^0.1.3",
     "@overleaf/ranges-tracker": "*",
     "@overleaf/stream-utils": "*",
     "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11",