| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- import { EditorView, showTooltip, Tooltip, ViewPlugin } from '@codemirror/view'
- import {
- Compartment,
- EditorState,
- Extension,
- StateEffect,
- StateField,
- TransactionSpec,
- } from '@codemirror/state'
- import { loadMathJax } from '../../mathjax/load-mathjax'
- import { descendantsOfNodeWithType } from '../utils/tree-query'
- import {
- MathContainer,
- mathAncestorNode,
- parseMathContainer,
- } from '../utils/tree-operations/math'
- import { documentCommands } from '../languages/latex/document-commands'
- import { debugConsole } from '@/utils/debugging'
- import { nodeHasError } from '../utils/tree-operations/common'
- import { documentEnvironments } from '../languages/latex/document-environments'
- import { repositionAllTooltips } from './tooltips-reposition'
- import { closeAllContextMenusEffect } from '../utils/close-all-context-menus-effect'
- const HIDE_TOOLTIP_EVENT = 'editor:hideMathTooltip'
- export const mathPreview = (enabled: boolean): Extension => {
- return mathPreviewConf.of(
- enabled ? [mathPreviewTheme, mathPreviewStateField] : [mathPreviewTheme]
- )
- }
- export const hideTooltipEffect = StateEffect.define<null>()
- const mathPreviewConf = new Compartment()
- export const setMathPreview = (enabled: boolean): TransactionSpec => ({
- effects: mathPreviewConf.reconfigure(enabled ? mathPreviewStateField : []),
- })
- export const mathPreviewStateField = StateField.define<{
- tooltip: Tooltip | null
- hide: boolean
- }>({
- create: buildInitialState,
- update(state, tr) {
- for (const effect of tr.effects) {
- if (effect.is(hideTooltipEffect)) {
- return { tooltip: null, hide: true }
- }
- if (effect.is(closeAllContextMenusEffect)) {
- return { tooltip: null, hide: state.hide }
- }
- }
- if (tr.docChanged || tr.selection) {
- const mathContainer = getMathContainer(tr.state)
- if (mathContainer) {
- if (state.hide) {
- return { tooltip: null, hide: true }
- } else {
- const mathContent = buildTooltipContent(tr.state, mathContainer)
- return {
- tooltip: buildTooltip(mathContainer, mathContent),
- hide: false,
- }
- }
- }
- return { tooltip: null, hide: false }
- }
- return state
- },
- provide: field => [
- showTooltip.compute([field], state => state.field(field).tooltip),
- ViewPlugin.define(view => {
- const hideTooltip = () => {
- view.dispatch({
- effects: hideTooltipEffect.of(null),
- })
- }
- window.addEventListener(HIDE_TOOLTIP_EVENT, hideTooltip)
- return {
- destroy() {
- window.removeEventListener(HIDE_TOOLTIP_EVENT, hideTooltip)
- },
- }
- }),
- ],
- })
- function buildInitialState(state: EditorState) {
- const mathContainer = getMathContainer(state)
- if (mathContainer) {
- const mathContent = buildTooltipContent(state, mathContainer)
- return {
- tooltip: buildTooltip(mathContainer, mathContent),
- mathContent,
- hide: false,
- }
- }
- return { tooltip: null, hide: false, mathContent: null }
- }
- const renderMath = async (
- content: string,
- displayMode: boolean,
- element: HTMLElement,
- definitions: string
- ) => {
- const MathJax = await loadMathJax()
- MathJax.texReset([0]) // equation numbering is disabled, but this is still needed
- try {
- await MathJax.tex2svgPromise(definitions)
- } catch {
- // ignore errors thrown during parsing command definitions
- }
- const math = await MathJax.tex2svgPromise(content, {
- ...MathJax.getMetricsFor(element),
- display: displayMode,
- })
- element.textContent = ''
- element.append(math)
- }
- function buildTooltip(
- mathContainer: MathContainer,
- mathContent: HTMLDivElement | null
- ): Tooltip | null {
- if (!mathContent || !mathContainer) {
- return null
- }
- return {
- pos: mathContainer.pos,
- above: true,
- strictSide: true,
- arrow: false,
- create() {
- const dom = document.createElement('div')
- dom.classList.add('ol-cm-math-tooltip-container')
- const innerElt = document.createElement('div')
- innerElt.classList.add('ol-cm-math-tooltip')
- innerElt.id = 'ol-cm-math-tooltip'
- innerElt.appendChild(mathContent)
- dom.appendChild(innerElt)
- return { dom, overlap: true, offset: { x: 0, y: 8 } }
- },
- }
- }
- const getMathContainer = (state: EditorState) => {
- const range = state.selection.main
- if (!range.empty) {
- return null
- }
- // if anywhere inside Math, find the whole Math node
- const ancestorNode = mathAncestorNode(state, range.from)
- if (!ancestorNode) return null
- const [node] = descendantsOfNodeWithType(ancestorNode, 'Math', 'Math')
- if (!node) return null
- if (nodeHasError(ancestorNode)) return null
- return parseMathContainer(state, node, ancestorNode)
- }
- const buildTooltipContent = (
- state: EditorState,
- math: MathContainer | null
- ): HTMLDivElement | null => {
- if (!math || !math.content.length) return null
- const element = document.createElement('div')
- element.style.opacity = '0'
- element.textContent = math.content
- let definitions = ''
- const environmentState = state.field(documentEnvironments, false)
- if (environmentState?.items) {
- for (const environment of environmentState.items) {
- if (environment.type === 'definition') {
- definitions += `${environment.raw}\n`
- }
- }
- }
- const commandState = state.field(documentCommands, false)
- if (commandState?.items) {
- for (const command of commandState.items) {
- if (command.type === 'definition' && command.raw) {
- definitions += `${command.raw}\n`
- }
- }
- }
- renderMath(math.content, math.displayMode, element, definitions)
- .then(() => {
- element.style.opacity = '1'
- repositionAllTooltips()
- })
- .catch(error => {
- debugConsole.error(error)
- })
- return element
- }
- /**
- * Styles for the preview tooltip
- */
- const mathPreviewTheme = EditorView.baseTheme({
- '&light .ol-cm-math-tooltip': {
- boxShadow: '0px 2px 4px 0px #1e253029',
- border: '1px solid #e7e9ee !important',
- backgroundColor: 'white !important',
- },
- '&dark .ol-cm-math-tooltip': {
- boxShadow: '0px 2px 4px 0px #1e253029',
- border: '1px solid #2f3a4c !important',
- backgroundColor: '#1b222c !important',
- },
- })
|