latex-cite-editor 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +103 -0
- package/dist/bibtex-BV8HliUE.d.cts +13 -0
- package/dist/bibtex-BV8HliUE.d.ts +13 -0
- package/dist/chunk-5AWY4EOD.js +125 -0
- package/dist/chunk-5AWY4EOD.js.map +1 -0
- package/dist/index.cjs +400 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +62 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.js +252 -0
- package/dist/index.js.map +1 -0
- package/dist/react.cjs +298 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +37 -0
- package/dist/react.d.ts +37 -0
- package/dist/react.js +158 -0
- package/dist/react.js.map +1 -0
- package/package.json +76 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Reinan Br.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
<div align='center'>
|
|
2
|
+
|
|
3
|
+
<h1>latex-cite-editor</h1>
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/latex-cite-editor)
|
|
6
|
+
[](https://www.npmjs.com/package/latex-cite-editor)
|
|
7
|
+
[](https://www.npmjs.com/package/latex-cite-editor)
|
|
8
|
+
[](https://www.npmjs.com/package/latex-cite-editor)
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
|
|
11
|
+
<p>A CodeMirror 6 based React editor for Markdown articles with LaTeX-flavored extras: <code>\cite{key}</code> citations resolved against a BibTeX (<code>.bib</code>) file, autocomplete for citation keys, LaTeX-like commands (<code>\textbf</code>, <code>\section</code>, <code>\footnote</code>, ...), and math symbols inside <code>$...$</code>/<code>$$...$$</code>.</p>
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<hr>
|
|
16
|
+
|
|
17
|
+
## Table of contents
|
|
18
|
+
|
|
19
|
+
- [Why](#why)
|
|
20
|
+
- [Installation](#installation)
|
|
21
|
+
- [Quickstart](#quickstart)
|
|
22
|
+
- [API](#api)
|
|
23
|
+
- [Recommended host CSS](#recommended-host-css)
|
|
24
|
+
- [License](#license)
|
|
25
|
+
|
|
26
|
+
<hr>
|
|
27
|
+
|
|
28
|
+
## Why
|
|
29
|
+
|
|
30
|
+
Writing a technical/academic article in Markdown usually means citations degrade to a manually
|
|
31
|
+
numbered plain-text list, with no autocomplete and no link between an in-text reference and its
|
|
32
|
+
entry. `latex-cite-editor` borrows the one LaTeX/BibTeX convention worth keeping — `\cite{key}`
|
|
33
|
+
resolved against a `.bib` file — and wires it into a real editor: type `\cite{` and get a
|
|
34
|
+
live-filtered dropdown of your bibliography, sourced from whatever `.bib` text you feed it.
|
|
35
|
+
|
|
36
|
+
It does **not** compile real LaTeX or produce PDFs — it's a Markdown editing experience (built on
|
|
37
|
+
CodeMirror 6) borrowing LaTeX authoring conventions that a physics/CS/math author already knows.
|
|
38
|
+
Bring your own Markdown renderer (`marked`, `remark`, ...); this package only resolves
|
|
39
|
+
`\cite{...}` into numbered, linked HTML markers and hands you back a ready-to-inject bibliography
|
|
40
|
+
block.
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install latex-cite-editor
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Requires React 18+.
|
|
49
|
+
|
|
50
|
+
## Quickstart
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
import { useMemo, useState } from 'react';
|
|
54
|
+
import { parseBibtex, resolveCitations } from 'latex-cite-editor';
|
|
55
|
+
import { CiteEditor } from 'latex-cite-editor/react';
|
|
56
|
+
|
|
57
|
+
function Editor() {
|
|
58
|
+
const [content, setContent] = useState('');
|
|
59
|
+
const [bibText, setBibText] = useState('');
|
|
60
|
+
const bibEntries = useMemo(() => parseBibtex(bibText), [bibText]);
|
|
61
|
+
|
|
62
|
+
return <CiteEditor value={content} onChange={setContent} bibEntries={bibEntries} />;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// When rendering the article:
|
|
66
|
+
const { text, bibliographyHtml } = resolveCitations(markdownSource, bibEntries);
|
|
67
|
+
const html = renderMarkdown(text) + bibliographyHtml; // plug into your own Markdown renderer
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
See [`examples/basic-usage.tsx`](examples/basic-usage.tsx) for a minimal editor + live preview, and
|
|
71
|
+
[`examples/article-editor-with-toolbar.tsx`](examples/article-editor-with-toolbar.tsx) for the
|
|
72
|
+
fuller pattern — a title/bibliography/content form with a formatting toolbar built entirely on
|
|
73
|
+
`CiteEditorHandle` (no raw textarea/DOM access).
|
|
74
|
+
|
|
75
|
+
## API
|
|
76
|
+
|
|
77
|
+
- `parseBibtex(source: string): BibEntry[]` — parses `.bib` text (handles nested braces in field values, e.g. `title = {The {Higgs} Boson}`).
|
|
78
|
+
- `resolveCitations(text: string, entries: BibEntry[]): ResolvedCitations` — replaces every `\cite{key[,key2]}` with numbered, linked markers (ordered by first appearance, like LaTeX + natbib's numeric style) and returns an HTML bibliography block. If an entry has a `url`, `link`, or `doi` field, its bibliography line is wrapped in a link to that address (checked in that order).
|
|
79
|
+
- `extractCiteKeys(text: string): string[]` / `formatEntry(entry: BibEntry): string` / `resolveEntryUrl(entry: BibEntry): string | undefined` — lower-level building blocks. `formatEntry` already runs author/title/journal through `cleanLatexText`.
|
|
80
|
+
- `cleanLatexText(value: string): string` — converts LaTeX accent macros (`{\'e}`, `{\^o}`, `{\c c}`, `{\v c}`, `{\ss}`, ...) as exported by Google Scholar/reference managers into real Unicode (`é`, `ô`, `ç`, `č`, `ß`, ...), and strips leftover `{}` grouping braces.
|
|
81
|
+
- `<CiteEditor />` (from `latex-cite-editor/react`, a `'use client'` module) — the editor component. Props: `value`, `onChange`, `bibEntries`, `fontSize`, `placeholder`, `minHeight`, `className`.
|
|
82
|
+
- `CiteEditorHandle` (from `latex-cite-editor/react`, via `ref`) — imperative API for toolbars: `focus()`, `getSelection()`, `wrapSelection(before, after, placeholder?)`, `insertAtLineStart(prefix)`, `insertText(text)`, `duplicateCurrentLine()`, `openSearch()`, and the raw CodeMirror `view`.
|
|
83
|
+
|
|
84
|
+
> `<CiteEditor />` lives on a separate `/react` subpath (with its own `'use client'` directive) so that the root entry — `parseBibtex`, `resolveCitations`, and friends — stays free of React/CodeMirror and safe to import from a React Server Component (e.g. to resolve citations at render time on the server).
|
|
85
|
+
- `citationCompletionSource`, `latexCommandCompletionSource`, `latexHighlightPlugin`, `latexHighlightTheme` — exported separately in case you're composing your own CodeMirror extension list instead of using `<CiteEditor />`.
|
|
86
|
+
|
|
87
|
+
## Recommended host CSS
|
|
88
|
+
|
|
89
|
+
The rendered bibliography/citation markers use plain classes so you can style them with your own
|
|
90
|
+
design system:
|
|
91
|
+
|
|
92
|
+
```css
|
|
93
|
+
.citation-ref a { text-decoration: none; }
|
|
94
|
+
.citation-ref.citation-missing { color: #ef4444; }
|
|
95
|
+
.bibliography ol { list-style: decimal; padding-left: 1.5rem; }
|
|
96
|
+
.bibliography li { margin-bottom: 0.5rem; }
|
|
97
|
+
.bibliography .citation-link { text-decoration: underline; }
|
|
98
|
+
.citation-backref { text-decoration: none; opacity: 0.6; }
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
MIT © [Reinan Br](LICENSE)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
interface BibEntry {
|
|
2
|
+
key: string;
|
|
3
|
+
type: string;
|
|
4
|
+
fields: Record<string, string>;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Hand-rolled parser instead of a regex split: BibTeX field values commonly
|
|
8
|
+
* contain nested braces (e.g. `title = {The {Higgs} Boson}`), which a single
|
|
9
|
+
* regex cannot balance correctly.
|
|
10
|
+
*/
|
|
11
|
+
declare function parseBibtex(source: string): BibEntry[];
|
|
12
|
+
|
|
13
|
+
export { type BibEntry as B, parseBibtex as p };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
interface BibEntry {
|
|
2
|
+
key: string;
|
|
3
|
+
type: string;
|
|
4
|
+
fields: Record<string, string>;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Hand-rolled parser instead of a regex split: BibTeX field values commonly
|
|
8
|
+
* contain nested braces (e.g. `title = {The {Higgs} Boson}`), which a single
|
|
9
|
+
* regex cannot balance correctly.
|
|
10
|
+
*/
|
|
11
|
+
declare function parseBibtex(source: string): BibEntry[];
|
|
12
|
+
|
|
13
|
+
export { type BibEntry as B, parseBibtex as p };
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// src/autocomplete.ts
|
|
2
|
+
var LATEX_COMMANDS = [
|
|
3
|
+
{ label: "\\cite{}", apply: "\\cite{}", type: "keyword", detail: "citar refer\xEAncia do .bib", boost: 2 },
|
|
4
|
+
{ label: "\\textbf{}", apply: "\\textbf{}", type: "keyword", detail: "negrito" },
|
|
5
|
+
{ label: "\\textit{}", apply: "\\textit{}", type: "keyword", detail: "it\xE1lico" },
|
|
6
|
+
{ label: "\\section{}", apply: "\\section{}", type: "keyword", detail: "se\xE7\xE3o" },
|
|
7
|
+
{ label: "\\subsection{}", apply: "\\subsection{}", type: "keyword", detail: "subse\xE7\xE3o" },
|
|
8
|
+
{ label: "\\label{}", apply: "\\label{}", type: "keyword", detail: "r\xF3tulo para refer\xEAncia cruzada" },
|
|
9
|
+
{ label: "\\ref{}", apply: "\\ref{}", type: "keyword", detail: "refer\xEAncia cruzada a um \\label" },
|
|
10
|
+
{ label: "\\footnote{}", apply: "\\footnote{}", type: "keyword", detail: "nota de rodap\xE9" }
|
|
11
|
+
];
|
|
12
|
+
var MATH_SYMBOLS = [
|
|
13
|
+
"\\alpha",
|
|
14
|
+
"\\beta",
|
|
15
|
+
"\\gamma",
|
|
16
|
+
"\\delta",
|
|
17
|
+
"\\theta",
|
|
18
|
+
"\\lambda",
|
|
19
|
+
"\\pi",
|
|
20
|
+
"\\sigma",
|
|
21
|
+
"\\omega",
|
|
22
|
+
"\\sum",
|
|
23
|
+
"\\int",
|
|
24
|
+
"\\prod",
|
|
25
|
+
"\\infty",
|
|
26
|
+
"\\partial",
|
|
27
|
+
"\\nabla",
|
|
28
|
+
"\\leq",
|
|
29
|
+
"\\geq",
|
|
30
|
+
"\\neq",
|
|
31
|
+
"\\approx",
|
|
32
|
+
"\\times",
|
|
33
|
+
"\\cdot"
|
|
34
|
+
].map((label) => ({ label, type: "constant" })).concat([
|
|
35
|
+
{ label: "\\sqrt{}", apply: "\\sqrt{}", type: "constant" },
|
|
36
|
+
{ label: "\\frac{}{}", apply: "\\frac{}{}", type: "constant" }
|
|
37
|
+
]);
|
|
38
|
+
function citationDetail(entry) {
|
|
39
|
+
const firstAuthor = entry.fields.author?.split(/\s+and\s+/)[0]?.split(",")[0]?.trim();
|
|
40
|
+
const year = entry.fields.year;
|
|
41
|
+
const who = firstAuthor ?? "autor desconhecido";
|
|
42
|
+
return year ? `${who} (${year})` : who;
|
|
43
|
+
}
|
|
44
|
+
function citationCompletionSource(getEntries) {
|
|
45
|
+
return (context) => {
|
|
46
|
+
const match = context.matchBefore(/\\cite\{[^}]*/);
|
|
47
|
+
if (!match) return null;
|
|
48
|
+
const braceIdx = match.text.indexOf("{");
|
|
49
|
+
const from = match.from + braceIdx + 1;
|
|
50
|
+
const entries = getEntries();
|
|
51
|
+
if (entries.length === 0) return null;
|
|
52
|
+
return {
|
|
53
|
+
from,
|
|
54
|
+
options: entries.map((entry) => ({
|
|
55
|
+
label: entry.key,
|
|
56
|
+
detail: citationDetail(entry),
|
|
57
|
+
info: entry.fields.title ? entry.fields.title.replace(/[{}]/g, "") : void 0,
|
|
58
|
+
type: "text"
|
|
59
|
+
})),
|
|
60
|
+
validFor: /^[^}]*$/
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function isInsideMath(textBeforeCursor) {
|
|
65
|
+
const lastParagraphBreak = textBeforeCursor.lastIndexOf("\n\n");
|
|
66
|
+
const scope = lastParagraphBreak === -1 ? textBeforeCursor : textBeforeCursor.slice(lastParagraphBreak);
|
|
67
|
+
const dollarCount = (scope.match(/\$/g) || []).length;
|
|
68
|
+
return dollarCount % 2 === 1;
|
|
69
|
+
}
|
|
70
|
+
function latexCommandCompletionSource(context) {
|
|
71
|
+
const word = context.matchBefore(/\\[a-zA-Z]*/);
|
|
72
|
+
if (!word || word.from === word.to && !context.explicit) return null;
|
|
73
|
+
const inMath = isInsideMath(context.state.sliceDoc(0, word.from));
|
|
74
|
+
const options = inMath ? [...MATH_SYMBOLS, ...LATEX_COMMANDS] : LATEX_COMMANDS;
|
|
75
|
+
return {
|
|
76
|
+
from: word.from,
|
|
77
|
+
options,
|
|
78
|
+
validFor: /^\\[a-zA-Z]*$/
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/latexDecorations.ts
|
|
83
|
+
import { Decoration, EditorView, MatchDecorator, ViewPlugin } from "@codemirror/view";
|
|
84
|
+
var LATEX_TOKEN_RE = /\\cite\{[^}]*\}|\\[a-zA-Z]+(?:\{[^}]*\})?|\$\$[^$]*\$\$|\$[^$\n]+\$/g;
|
|
85
|
+
function classify(token) {
|
|
86
|
+
if (token.startsWith("\\cite{")) return "cm-latex-cite";
|
|
87
|
+
if (token.startsWith("$$")) return "cm-latex-math-block";
|
|
88
|
+
if (token.startsWith("$")) return "cm-latex-math-inline";
|
|
89
|
+
return "cm-latex-command";
|
|
90
|
+
}
|
|
91
|
+
var matcher = new MatchDecorator({
|
|
92
|
+
regexp: LATEX_TOKEN_RE,
|
|
93
|
+
decoration: (match) => Decoration.mark({ class: classify(match[0]) })
|
|
94
|
+
});
|
|
95
|
+
var latexHighlightPlugin = ViewPlugin.define(
|
|
96
|
+
(view) => ({
|
|
97
|
+
decorations: matcher.createDeco(view),
|
|
98
|
+
update(update) {
|
|
99
|
+
this.decorations = matcher.updateDeco(update, this.decorations);
|
|
100
|
+
}
|
|
101
|
+
}),
|
|
102
|
+
{
|
|
103
|
+
decorations: (instance) => instance.decorations
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
var latexHighlightTheme = EditorView.baseTheme({
|
|
107
|
+
".cm-latex-cite": {
|
|
108
|
+
color: "#0ea5e9",
|
|
109
|
+
fontWeight: "600"
|
|
110
|
+
},
|
|
111
|
+
".cm-latex-command": {
|
|
112
|
+
color: "#a855f7"
|
|
113
|
+
},
|
|
114
|
+
".cm-latex-math-inline, .cm-latex-math-block": {
|
|
115
|
+
color: "#f59e0b"
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
export {
|
|
120
|
+
citationCompletionSource,
|
|
121
|
+
latexCommandCompletionSource,
|
|
122
|
+
latexHighlightPlugin,
|
|
123
|
+
latexHighlightTheme
|
|
124
|
+
};
|
|
125
|
+
//# sourceMappingURL=chunk-5AWY4EOD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/autocomplete.ts","../src/latexDecorations.ts"],"sourcesContent":["import type { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete';\nimport type { BibEntry } from './bibtex';\n\nconst LATEX_COMMANDS: Completion[] = [\n { label: '\\\\cite{}', apply: '\\\\cite{}', type: 'keyword', detail: 'citar referência do .bib', boost: 2 },\n { label: '\\\\textbf{}', apply: '\\\\textbf{}', type: 'keyword', detail: 'negrito' },\n { label: '\\\\textit{}', apply: '\\\\textit{}', type: 'keyword', detail: 'itálico' },\n { label: '\\\\section{}', apply: '\\\\section{}', type: 'keyword', detail: 'seção' },\n { label: '\\\\subsection{}', apply: '\\\\subsection{}', type: 'keyword', detail: 'subseção' },\n { label: '\\\\label{}', apply: '\\\\label{}', type: 'keyword', detail: 'rótulo para referência cruzada' },\n { label: '\\\\ref{}', apply: '\\\\ref{}', type: 'keyword', detail: 'referência cruzada a um \\\\label' },\n { label: '\\\\footnote{}', apply: '\\\\footnote{}', type: 'keyword', detail: 'nota de rodapé' },\n];\n\nconst MATH_SYMBOLS: Completion[] = [\n '\\\\alpha', '\\\\beta', '\\\\gamma', '\\\\delta', '\\\\theta', '\\\\lambda', '\\\\pi', '\\\\sigma', '\\\\omega',\n '\\\\sum', '\\\\int', '\\\\prod', '\\\\infty', '\\\\partial', '\\\\nabla',\n '\\\\leq', '\\\\geq', '\\\\neq', '\\\\approx', '\\\\times', '\\\\cdot',\n].map((label): Completion => ({ label, type: 'constant' })).concat([\n { label: '\\\\sqrt{}', apply: '\\\\sqrt{}', type: 'constant' },\n { label: '\\\\frac{}{}', apply: '\\\\frac{}{}', type: 'constant' },\n]);\n\nfunction citationDetail(entry: BibEntry): string {\n const firstAuthor = entry.fields.author?.split(/\\s+and\\s+/)[0]?.split(',')[0]?.trim();\n const year = entry.fields.year;\n const who = firstAuthor ?? 'autor desconhecido';\n return year ? `${who} (${year})` : who;\n}\n\n/** Autocompletes bib keys right after `\\cite{`, sourced live from the current .bib entries. */\nexport function citationCompletionSource(getEntries: () => BibEntry[]) {\n return (context: CompletionContext): CompletionResult | null => {\n const match = context.matchBefore(/\\\\cite\\{[^}]*/);\n if (!match) return null;\n const braceIdx = match.text.indexOf('{');\n const from = match.from + braceIdx + 1;\n const entries = getEntries();\n if (entries.length === 0) return null;\n\n return {\n from,\n options: entries.map((entry) => ({\n label: entry.key,\n detail: citationDetail(entry),\n info: entry.fields.title ? entry.fields.title.replace(/[{}]/g, '') : undefined,\n type: 'text',\n })),\n validFor: /^[^}]*$/,\n };\n };\n}\n\nfunction isInsideMath(textBeforeCursor: string): boolean {\n const lastParagraphBreak = textBeforeCursor.lastIndexOf('\\n\\n');\n const scope = lastParagraphBreak === -1 ? textBeforeCursor : textBeforeCursor.slice(lastParagraphBreak);\n const dollarCount = (scope.match(/\\$/g) || []).length;\n return dollarCount % 2 === 1;\n}\n\n/** Autocompletes LaTeX-like commands (\\cite, \\textbf, ...) and, inside $...$, math symbols. */\nexport function latexCommandCompletionSource(context: CompletionContext): CompletionResult | null {\n const word = context.matchBefore(/\\\\[a-zA-Z]*/);\n if (!word || (word.from === word.to && !context.explicit)) return null;\n\n const inMath = isInsideMath(context.state.sliceDoc(0, word.from));\n const options = inMath ? [...MATH_SYMBOLS, ...LATEX_COMMANDS] : LATEX_COMMANDS;\n\n return {\n from: word.from,\n options,\n validFor: /^\\\\[a-zA-Z]*$/,\n };\n}\n","import { Decoration, EditorView, MatchDecorator, ViewPlugin, type DecorationSet } from '@codemirror/view';\n\nconst LATEX_TOKEN_RE = /\\\\cite\\{[^}]*\\}|\\\\[a-zA-Z]+(?:\\{[^}]*\\})?|\\$\\$[^$]*\\$\\$|\\$[^$\\n]+\\$/g;\n\nfunction classify(token: string): string {\n if (token.startsWith('\\\\cite{')) return 'cm-latex-cite';\n if (token.startsWith('$$')) return 'cm-latex-math-block';\n if (token.startsWith('$')) return 'cm-latex-math-inline';\n return 'cm-latex-command';\n}\n\nconst matcher = new MatchDecorator({\n regexp: LATEX_TOKEN_RE,\n decoration: (match) => Decoration.mark({ class: classify(match[0]) }),\n});\n\n/** Regex-based decorations that highlight \\cite{}, other \\commands, and $...$/$$...$$ math spans. */\nexport const latexHighlightPlugin = ViewPlugin.define(\n (view) => ({\n decorations: matcher.createDeco(view),\n update(update) {\n this.decorations = matcher.updateDeco(update, this.decorations);\n },\n }),\n {\n decorations: (instance) => instance.decorations as DecorationSet,\n }\n);\n\nexport const latexHighlightTheme = EditorView.baseTheme({\n '.cm-latex-cite': {\n color: '#0ea5e9',\n fontWeight: '600',\n },\n '.cm-latex-command': {\n color: '#a855f7',\n },\n '.cm-latex-math-inline, .cm-latex-math-block': {\n color: '#f59e0b',\n },\n});\n"],"mappings":"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","names":[]}
|