mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Migrate extension to @overleaf/codemirror-tree-view
(#15769)
GitOrigin-RevId: 8355158040923a55dafc7b1c3f566f7e7703cb02
This commit is contained in:
parent
bf6257af80
commit
cdd9ff9ed0
3 changed files with 29 additions and 243 deletions
20
package-lock.json
generated
20
package-lock.json
generated
|
@ -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": "*",
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
'.ol-cm-dev-tools-position': {
|
||||
padding: '4px 0',
|
||||
minHeight: 'unset',
|
||||
},
|
||||
})
|
||||
|
||||
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] : [])
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in a new issue