prosemirror-highlight 0.13.1 → 0.15.0

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/README.md CHANGED
@@ -32,14 +32,40 @@ export const shikiPlugin = createHighlightPlugin({ parser })
32
32
  ```ts
33
33
  import { createHighlightPlugin } from 'prosemirror-highlight'
34
34
  import { createParser, type Parser } from 'prosemirror-highlight/shiki'
35
+ import type { Decoration } from 'prosemirror-view'
35
36
  import {
36
- getSingletonHighlighter,
37
+ createHighlighter,
37
38
  type BuiltinLanguage,
38
39
  type Highlighter,
39
40
  } from 'shiki'
40
41
 
41
42
  let highlighter: Highlighter | undefined
43
+ let highlighterPromise: Promise<void> | undefined
42
44
  let parser: Parser | undefined
45
+ const loadedLanguages = new Set<string>()
46
+
47
+ function loadHighlighter(): Promise<void> {
48
+ if (!highlighterPromise) {
49
+ highlighterPromise = createHighlighter({
50
+ themes: ['github-light', 'github-dark', 'github-dark-dimmed'],
51
+ langs: [],
52
+ }).then((h) => {
53
+ highlighter = h
54
+ })
55
+ }
56
+ return highlighterPromise
57
+ }
58
+
59
+ async function loadLanguage(
60
+ highlighter: Highlighter,
61
+ language: string,
62
+ ): Promise<void> {
63
+ try {
64
+ return await highlighter.loadLanguage(language as BuiltinLanguage)
65
+ } finally {
66
+ loadedLanguages.add(language)
67
+ }
68
+ }
43
69
 
44
70
  /**
45
71
  * Lazy load highlighter and highlighter languages.
@@ -48,23 +74,25 @@ let parser: Parser | undefined
48
74
  * promise that resolves when the highlighter or the language is loaded.
49
75
  * Otherwise, it returns an array of decorations.
50
76
  */
51
- const lazyParser: Parser = (options) => {
77
+ const lazyParser: Parser = (options): Promise<void> | Decoration[] => {
52
78
  if (!highlighter) {
53
- return getSingletonHighlighter({
54
- themes: ['github-light'],
55
- langs: [],
56
- }).then((h) => {
57
- highlighter = h
58
- })
79
+ return loadHighlighter()
59
80
  }
60
81
 
61
- const language = options.language as BuiltinLanguage
62
- if (language && !highlighter.getLoadedLanguages().includes(language)) {
63
- return highlighter.loadLanguage(language)
82
+ const language = options.language
83
+ if (language && !loadedLanguages.has(language)) {
84
+ return loadLanguage(highlighter, language)
64
85
  }
65
86
 
66
87
  if (!parser) {
67
- parser = createParser(highlighter)
88
+ parser = createParser(highlighter, {
89
+ themes: {
90
+ light: 'github-light',
91
+ dark: 'github-dark',
92
+ dim: 'github-dark-dimmed',
93
+ },
94
+ defaultColor: 'dim',
95
+ })
68
96
  }
69
97
 
70
98
  return parser(options)
@@ -158,6 +186,25 @@ export const sugarHighPlugin = createHighlightPlugin({ parser })
158
186
 
159
187
  </details>
160
188
 
189
+ ## Line Numbers
190
+
191
+ You can add line numbers to code blocks by wrapping any parser with `withLineNumbers`:
192
+
193
+ ```ts
194
+ import { createHighlightPlugin, withLineNumbers } from 'prosemirror-highlight'
195
+ import { createParser } from 'prosemirror-highlight/shiki'
196
+ import { getSingletonHighlighter } from 'shiki'
197
+
198
+ const highlighter = await getSingletonHighlighter({
199
+ themes: ['github-light'],
200
+ langs: ['javascript', 'typescript', 'python'],
201
+ })
202
+ const parser = withLineNumbers(createParser(highlighter))
203
+ export const shikiPlugin = createHighlightPlugin({ parser })
204
+ ```
205
+
206
+ This inserts `<span class="line-number">` elements with class name `line-number` at the start of each line.
207
+
161
208
  ## Online demo
162
209
 
163
210
  [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/ocavue/prosemirror-highlight?file=playground%2Fmain.ts)
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as Parser, t as LanguageExtractor } from "./types-DCbyVqHc.js";
1
+ import { n as Parser, r as ParserOptions, t as LanguageExtractor } from "./types-jXJYKXGQ.js";
2
2
  import { Plugin, Transaction } from "prosemirror-state";
3
3
  import { Decoration, DecorationSet } from "prosemirror-view";
4
4
  import { Node } from "prosemirror-model";
@@ -53,7 +53,7 @@ declare class DecorationCache {
53
53
  */
54
54
  interface HighlightPluginState {
55
55
  cache: DecorationCache;
56
- decorations: DecorationSet;
56
+ decorations: DecorationSet | undefined;
57
57
  promises: Promise<void>[];
58
58
  }
59
59
  /**
@@ -83,5 +83,14 @@ declare function createHighlightPlugin({
83
83
  languageExtractor?: LanguageExtractor;
84
84
  }): Plugin<HighlightPluginState>;
85
85
  //#endregion
86
- export { DecorationCache, type HighlightPluginState, type LanguageExtractor, type Parser, createHighlightPlugin };
86
+ //#region src/line-number.d.ts
87
+ /**
88
+ * Returns a new parser that adds line numbers to the parsed decorations.
89
+ *
90
+ * Line numbers are added as `<span class="line-number">` elements with
91
+ * the line number as the text content.
92
+ */
93
+ declare function withLineNumbers(parser: Parser): Parser;
94
+ //#endregion
95
+ export { DecorationCache, type HighlightPluginState, type LanguageExtractor, type Parser, type ParserOptions, createHighlightPlugin, withLineNumbers };
87
96
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/cache.ts","../src/plugin.ts"],"mappings":";;;;;;;;;cAOa,eAAA;EAAA,QACH,KAAA;cAGN,KAAA,GAAQ,GAAA,UAAa,IAAA,EAAM,IAAA,EAAiB,WAAA,EAAa,UAAA;EAA9B;;;;EAS7B,GAAA,CAAI,GAAA,YAAW,IAAA,EAAA,IAAA,EAAA,WAAA,EAAA,UAAA;EAWQ;;;;;;;EAAvB,GAAA,CAAI,GAAA,UAAa,IAAA,EAAM,IAAA,EAAiB,WAAA,EAAa,UAAA;EApB3C;;;;;;;;EAAA,QAoCF,OAAA;EA3BO;;;;EAyCf,MAAA,CAAO,GAAA;EA9BgB;;;;;;;EAyCvB,UAAA,CAAW,EAAA,EAAI,WAAA,GAAc,eAAA;AAAA;;;;;AAjE/B;UCGiB,oBAAA;EACf,KAAA,EAAO,eAAA;EACP,WAAA,EAAa,aAAA;EACb,QAAA,EAAU,OAAA;AAAA;;;;;iBAOI,qBAAA,CAAA;EACd,MAAA;EACA,SAAA;EACA;AAAA;EDiD6B;;;;EC3C7B,MAAA,EAAQ,MAAA;EDlBE;;;;;ECyBV,SAAA;EDhBA;;;;ECsBA,iBAAA,GAAoB,iBAAA;AAAA,IAClB,MAAA,CAAO,oBAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/cache.ts","../src/plugin.ts","../src/line-number.ts"],"mappings":";;;;;;;;;cAOa,eAAA;EAAA,QACH,KAAA;cAGN,KAAA,GAAQ,GAAA,UAAa,IAAA,EAAM,IAAA,EAAiB,WAAA,EAAa,UAAA;EAA9B;;;;EAS7B,GAAA,CAAI,GAAA,YAAW,IAAA,EAAA,IAAA,EAAA,WAAA,EAAA,UAAA;EAWQ;;;;;;;EAAvB,GAAA,CAAI,GAAA,UAAa,IAAA,EAAM,IAAA,EAAiB,WAAA,EAAa,UAAA;EApB3C;;;;;;;;EAAA,QAoCF,OAAA;EA3BO;;;;EAyCf,MAAA,CAAO,GAAA;EA9BgB;;;;;;;EAyCvB,UAAA,CAAW,EAAA,EAAI,WAAA,GAAc,eAAA;AAAA;;;;;AAjE/B;UCGiB,oBAAA;EACf,KAAA,EAAO,eAAA;EACP,WAAA,EAAa,aAAA;EACb,QAAA,EAAU,OAAA;AAAA;;;;;iBAOI,qBAAA,CAAA;EACd,MAAA;EACA,SAAA;EACA;AAAA;EDiD6B;;;;EC3C7B,MAAA,EAAQ,MAAA;EDlBE;;;;;ECyBV,SAAA;EDhBA;;;;ECsBA,iBAAA,GAAoB,iBAAA;AAAA,IAClB,MAAA,CAAO,oBAAA;;;;;;;ADpCX;;iBEGgB,eAAA,CAAgB,MAAA,EAAQ,MAAA,GAAS,MAAA"}
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Plugin, PluginKey } from "prosemirror-state";
2
- import { DecorationSet } from "prosemirror-view";
2
+ import { Decoration, DecorationSet } from "prosemirror-view";
3
3
 
4
4
  //#region src/cache.ts
5
5
  /**
@@ -97,7 +97,7 @@ function createHighlightPlugin({ parser, nodeTypes = ["code_block", "codeBlock"]
97
97
  const refresh = !!tr.getMeta("prosemirror-highlight-refresh");
98
98
  if (!tr.docChanged && !refresh) return {
99
99
  cache,
100
- decorations: data.decorations.map(tr.mapping, tr.doc),
100
+ decorations: data.decorations?.map(tr.mapping, tr.doc),
101
101
  promises: data.promises
102
102
  };
103
103
  const [decorations, promises] = calculateDecoration(tr.doc, parser, nodeTypes, languageExtractor, cache);
@@ -122,7 +122,8 @@ function createHighlightPlugin({ parser, nodeTypes = ["code_block", "codeBlock"]
122
122
  promise.then(() => {
123
123
  promises.delete(promise);
124
124
  refresh();
125
- }).catch(() => {
125
+ }).catch((error) => {
126
+ console.error("[prosemirror-highlight] Error resolving parser:", error);
126
127
  promises.delete(promise);
127
128
  });
128
129
  }
@@ -138,37 +139,84 @@ function createHighlightPlugin({ parser, nodeTypes = ["code_block", "codeBlock"]
138
139
  });
139
140
  }
140
141
  function calculateDecoration(doc, parser, nodeTypes, languageExtractor, cache) {
141
- const result = [];
142
+ const allDecorations = [];
142
143
  const promises = [];
143
- doc.descendants((node, pos) => {
144
- if (!node.type.isTextblock) return true;
145
- if (nodeTypes.includes(node.type.name)) {
144
+ const nodes = collectCodeBlocks(doc, nodeTypes);
145
+ try {
146
+ for (const [node, pos] of nodes) {
146
147
  const language = languageExtractor(node);
147
148
  const cached = cache.get(pos);
148
149
  if (cached) {
149
150
  const [_, decorations] = cached;
150
- result.push(...decorations);
151
+ if (decorations.length > 0) allDecorations.push(decorations);
151
152
  } else {
152
- const decorations = parser({
153
+ const parsed = parser({
153
154
  content: node.textContent,
154
155
  language: language || void 0,
155
156
  pos,
156
157
  size: node.nodeSize
157
158
  });
158
- if (decorations && Array.isArray(decorations)) {
159
- cache.set(pos, node, decorations);
160
- result.push(...decorations);
161
- } else if (decorations instanceof Promise) {
159
+ if (parsed && Array.isArray(parsed)) {
160
+ cache.set(pos, node, parsed);
161
+ if (parsed.length > 0) allDecorations.push(parsed);
162
+ } else if (parsed instanceof Promise) {
162
163
  cache.remove(pos);
163
- promises.push(decorations);
164
- }
164
+ promises.push(parsed);
165
+ } else console.error(`[prosemirror-highlight] Invalid parser result:`, parsed);
165
166
  }
166
167
  }
167
- return false;
168
+ } catch (error) {
169
+ console.error(`[prosemirror-highlight] Error parsing code blocks:`, error);
170
+ }
171
+ return [allDecorations.length > 0 ? DecorationSet.create(doc, allDecorations.flat()) : void 0, promises];
172
+ }
173
+ function collectCodeBlocks(doc, nodeTypes) {
174
+ const nodes = [];
175
+ doc.descendants((node, pos) => {
176
+ if (node.type.isTextblock && nodeTypes.includes(node.type.name)) {
177
+ nodes.push([node, pos]);
178
+ return false;
179
+ }
168
180
  });
169
- return [DecorationSet.create(doc, result), promises];
181
+ return nodes;
182
+ }
183
+
184
+ //#endregion
185
+ //#region src/line-number.ts
186
+ /**
187
+ * Returns a new parser that adds line numbers to the parsed decorations.
188
+ *
189
+ * Line numbers are added as `<span class="line-number">` elements with
190
+ * the line number as the text content.
191
+ */
192
+ function withLineNumbers(parser) {
193
+ return function parserWithLineNumbers(options) {
194
+ const parsed = parser(options);
195
+ if (parsed && Array.isArray(parsed)) {
196
+ const { pos, content } = options;
197
+ const start = pos + 1;
198
+ const lineStarts = [start];
199
+ for (const match of content.matchAll(/(?:\r?\n)/g)) lineStarts.push(start + match.index + match[0].length);
200
+ const decorations = [];
201
+ for (const [index, lineStart] of lineStarts.entries()) decorations.push(createLineStartWidget(lineStart, index + 1));
202
+ return [...decorations, ...parsed];
203
+ }
204
+ return parsed;
205
+ };
206
+ }
207
+ function createLineStartWidget(from, lineNumber) {
208
+ return Decoration.widget(from, () => createLineStartSpan(lineNumber), {
209
+ key: "line-number",
210
+ side: -20
211
+ });
212
+ }
213
+ function createLineStartSpan(lineNumber) {
214
+ const span = document.createElement("span");
215
+ span.className = "line-number";
216
+ span.textContent = lineNumber.toString();
217
+ return span;
170
218
  }
171
219
 
172
220
  //#endregion
173
- export { DecorationCache, createHighlightPlugin };
221
+ export { DecorationCache, createHighlightPlugin, withLineNumbers };
174
222
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/cache.ts","../src/plugin.ts"],"sourcesContent":["import type { Node as ProseMirrorNode } from 'prosemirror-model'\nimport type { Transaction } from 'prosemirror-state'\nimport type { Decoration } from 'prosemirror-view'\n\n/**\n * Represents a cache of doc positions to the node and decorations at that position\n */\nexport class DecorationCache {\n private cache: Map<number, [node: ProseMirrorNode, decorations: Decoration[]]>\n\n constructor(\n cache?: Map<number, [node: ProseMirrorNode, decorations: Decoration[]]>,\n ) {\n this.cache = new Map(cache)\n }\n\n /**\n * Gets the cache entry at the given doc position, or null if it doesn't exist\n * @param pos The doc position of the node you want the cache for\n */\n get(pos: number) {\n return this.cache.get(pos)\n }\n\n /**\n * Sets the cache entry at the given position with the give node/decoration\n * values\n * @param pos The doc position of the node to set the cache for\n * @param node The node to place in cache\n * @param decorations The decorations to place in cache\n */\n set(pos: number, node: ProseMirrorNode, decorations: Decoration[]): void {\n if (pos < 0) {\n return\n }\n\n this.cache.set(pos, [node, decorations])\n }\n\n /**\n * Removes the value at the oldPos (if it exists) and sets the new position to\n * the given values\n * @param oldPos The old node position to overwrite\n * @param newPos The new node position to set the cache for\n * @param node The new node to place in cache\n * @param decorations The new decorations to place in cache\n */\n private replace(\n oldPos: number,\n newPos: number,\n node: ProseMirrorNode,\n decorations: Decoration[],\n ): void {\n this.remove(oldPos)\n this.set(newPos, node, decorations)\n }\n\n /**\n * Removes the cache entry at the given position\n * @param pos The doc position to remove from cache\n */\n remove(pos: number): void {\n this.cache.delete(pos)\n }\n\n /**\n * Invalidates the cache by removing all decoration entries on nodes that have\n * changed, updating the positions of the nodes that haven't and removing all\n * the entries that have been deleted; NOTE: this does not affect the current\n * cache, but returns an entirely new one\n * @param tr A transaction to map the current cache to\n */\n invalidate(tr: Transaction): DecorationCache {\n const returnCache = new DecorationCache(this.cache)\n const mapping = tr.mapping\n\n this.cache.forEach(([node, decorations], pos) => {\n if (pos < 0) {\n return\n }\n\n const result = mapping.mapResult(pos)\n const mappedNode = tr.doc.nodeAt(result.pos)\n\n if (result.deleted || !mappedNode?.eq(node)) {\n returnCache.remove(pos)\n } else if (pos !== result.pos) {\n // update the decorations' from/to values to match the new node position\n const updatedDecorations = decorations\n .map((d): Decoration | null => {\n // @ts-expect-error: internal api\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call\n return d.map(mapping, 0, 0) as Decoration | null\n })\n .filter((d): d is Decoration => d != null)\n returnCache.replace(pos, result.pos, mappedNode, updatedDecorations)\n }\n })\n\n return returnCache\n }\n}\n","import type { Node as ProseMirrorNode } from 'prosemirror-model'\nimport { Plugin, PluginKey } from 'prosemirror-state'\nimport { type Decoration, DecorationSet } from 'prosemirror-view'\n\nimport { DecorationCache } from './cache'\nimport type { LanguageExtractor, Parser } from './types'\n\n/**\n * Describes the current state of the highlightPlugin\n */\nexport interface HighlightPluginState {\n cache: DecorationCache\n decorations: DecorationSet\n promises: Promise<void>[]\n}\n\n/**\n * Creates a plugin that highlights the contents of all nodes (via Decorations)\n * with a type passed in blockTypes\n */\nexport function createHighlightPlugin({\n parser,\n nodeTypes = ['code_block', 'codeBlock'],\n languageExtractor = (node) => node.attrs.language as string | undefined,\n}: {\n /**\n * A function that returns an array of decorations for the given node text\n * content, language, and position.\n */\n parser: Parser\n\n /**\n * An array containing all the node type name to target for highlighting.\n *\n * @default ['code_block', 'codeBlock']\n */\n nodeTypes?: string[]\n\n /**\n * A function that returns the language string to use when highlighting that\n * node. By default, it returns `node.attrs.language`.\n */\n languageExtractor?: LanguageExtractor\n}): Plugin<HighlightPluginState> {\n const key = new PluginKey<HighlightPluginState>('prosemirror-highlight')\n\n return new Plugin<HighlightPluginState>({\n key,\n state: {\n init(_, instance) {\n const cache = new DecorationCache()\n const [decorations, promises] = calculateDecoration(\n instance.doc,\n parser,\n nodeTypes,\n languageExtractor,\n cache,\n )\n\n return { cache, decorations, promises }\n },\n apply: (tr, data) => {\n const cache = data.cache.invalidate(tr)\n const refresh = !!tr.getMeta('prosemirror-highlight-refresh')\n\n if (!tr.docChanged && !refresh) {\n const decorations = data.decorations.map(tr.mapping, tr.doc)\n const promises = data.promises\n return { cache, decorations, promises }\n }\n\n const [decorations, promises] = calculateDecoration(\n tr.doc,\n parser,\n nodeTypes,\n languageExtractor,\n cache,\n )\n return { cache, decorations, promises }\n },\n },\n view: (view) => {\n const promises = new Set<Promise<void>>()\n\n // Refresh the decorations when all promises resolve\n const refresh = () => {\n if (promises.size > 0) {\n return\n }\n const tr = view.state.tr.setMeta('prosemirror-highlight-refresh', true)\n view.dispatch(tr)\n }\n\n const check = () => {\n const state = key.getState(view.state)\n\n for (const promise of state?.promises ?? []) {\n promises.add(promise)\n promise\n .then(() => {\n promises.delete(promise)\n refresh()\n })\n .catch(() => {\n promises.delete(promise)\n })\n }\n }\n\n check()\n\n return {\n update: () => {\n check()\n },\n }\n },\n props: {\n decorations(this, state) {\n return this.getState(state)?.decorations\n },\n },\n })\n}\n\nfunction calculateDecoration(\n doc: ProseMirrorNode,\n parser: Parser,\n nodeTypes: string[],\n languageExtractor: LanguageExtractor,\n cache: DecorationCache,\n) {\n const result: Decoration[] = []\n const promises: Promise<void>[] = []\n\n doc.descendants((node, pos) => {\n if (!node.type.isTextblock) {\n return true\n }\n\n if (nodeTypes.includes(node.type.name)) {\n const language = languageExtractor(node)\n const cached = cache.get(pos)\n\n if (cached) {\n const [_, decorations] = cached\n result.push(...decorations)\n } else {\n const decorations = parser({\n content: node.textContent,\n language: language || undefined,\n pos,\n size: node.nodeSize,\n })\n\n if (decorations && Array.isArray(decorations)) {\n cache.set(pos, node, decorations)\n result.push(...decorations)\n } else if (decorations instanceof Promise) {\n cache.remove(pos)\n promises.push(decorations)\n }\n }\n }\n return false\n })\n\n return [DecorationSet.create(doc, result), promises] as const\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/cache.ts","../src/plugin.ts","../src/line-number.ts"],"sourcesContent":["import type { Node as ProseMirrorNode } from 'prosemirror-model'\nimport type { Transaction } from 'prosemirror-state'\nimport type { Decoration } from 'prosemirror-view'\n\n/**\n * Represents a cache of doc positions to the node and decorations at that position\n */\nexport class DecorationCache {\n private cache: Map<number, [node: ProseMirrorNode, decorations: Decoration[]]>\n\n constructor(\n cache?: Map<number, [node: ProseMirrorNode, decorations: Decoration[]]>,\n ) {\n this.cache = new Map(cache)\n }\n\n /**\n * Gets the cache entry at the given doc position, or null if it doesn't exist\n * @param pos The doc position of the node you want the cache for\n */\n get(pos: number) {\n return this.cache.get(pos)\n }\n\n /**\n * Sets the cache entry at the given position with the give node/decoration\n * values\n * @param pos The doc position of the node to set the cache for\n * @param node The node to place in cache\n * @param decorations The decorations to place in cache\n */\n set(pos: number, node: ProseMirrorNode, decorations: Decoration[]): void {\n if (pos < 0) {\n return\n }\n\n this.cache.set(pos, [node, decorations])\n }\n\n /**\n * Removes the value at the oldPos (if it exists) and sets the new position to\n * the given values\n * @param oldPos The old node position to overwrite\n * @param newPos The new node position to set the cache for\n * @param node The new node to place in cache\n * @param decorations The new decorations to place in cache\n */\n private replace(\n oldPos: number,\n newPos: number,\n node: ProseMirrorNode,\n decorations: Decoration[],\n ): void {\n this.remove(oldPos)\n this.set(newPos, node, decorations)\n }\n\n /**\n * Removes the cache entry at the given position\n * @param pos The doc position to remove from cache\n */\n remove(pos: number): void {\n this.cache.delete(pos)\n }\n\n /**\n * Invalidates the cache by removing all decoration entries on nodes that have\n * changed, updating the positions of the nodes that haven't and removing all\n * the entries that have been deleted; NOTE: this does not affect the current\n * cache, but returns an entirely new one\n * @param tr A transaction to map the current cache to\n */\n invalidate(tr: Transaction): DecorationCache {\n const returnCache = new DecorationCache(this.cache)\n const mapping = tr.mapping\n\n this.cache.forEach(([node, decorations], pos) => {\n if (pos < 0) {\n return\n }\n\n const result = mapping.mapResult(pos)\n const mappedNode = tr.doc.nodeAt(result.pos)\n\n if (result.deleted || !mappedNode?.eq(node)) {\n returnCache.remove(pos)\n } else if (pos !== result.pos) {\n // update the decorations' from/to values to match the new node position\n const updatedDecorations = decorations\n .map((d): Decoration | null => {\n // @ts-expect-error: internal api\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call\n return d.map(mapping, 0, 0) as Decoration | null\n })\n .filter((d): d is Decoration => d != null)\n returnCache.replace(pos, result.pos, mappedNode, updatedDecorations)\n }\n })\n\n return returnCache\n }\n}\n","import type { Node as ProseMirrorNode } from 'prosemirror-model'\nimport { Plugin, PluginKey } from 'prosemirror-state'\nimport { type Decoration, DecorationSet } from 'prosemirror-view'\n\nimport { DecorationCache } from './cache'\nimport type { LanguageExtractor, Parser } from './types'\n\n/**\n * Describes the current state of the highlightPlugin\n */\nexport interface HighlightPluginState {\n cache: DecorationCache\n decorations: DecorationSet | undefined\n promises: Promise<void>[]\n}\n\n/**\n * Creates a plugin that highlights the contents of all nodes (via Decorations)\n * with a type passed in blockTypes\n */\nexport function createHighlightPlugin({\n parser,\n nodeTypes = ['code_block', 'codeBlock'],\n languageExtractor = (node) => node.attrs.language as string | undefined,\n}: {\n /**\n * A function that returns an array of decorations for the given node text\n * content, language, and position.\n */\n parser: Parser\n\n /**\n * An array containing all the node type name to target for highlighting.\n *\n * @default ['code_block', 'codeBlock']\n */\n nodeTypes?: string[]\n\n /**\n * A function that returns the language string to use when highlighting that\n * node. By default, it returns `node.attrs.language`.\n */\n languageExtractor?: LanguageExtractor\n}): Plugin<HighlightPluginState> {\n const key = new PluginKey<HighlightPluginState>('prosemirror-highlight')\n\n return new Plugin<HighlightPluginState>({\n key,\n state: {\n init(_, instance) {\n const cache = new DecorationCache()\n const [decorations, promises] = calculateDecoration(\n instance.doc,\n parser,\n nodeTypes,\n languageExtractor,\n cache,\n )\n\n return { cache, decorations, promises }\n },\n apply: (tr, data) => {\n const cache = data.cache.invalidate(tr)\n const refresh = !!tr.getMeta('prosemirror-highlight-refresh')\n\n if (!tr.docChanged && !refresh) {\n const decorations = data.decorations?.map(tr.mapping, tr.doc)\n const promises = data.promises\n return { cache, decorations, promises }\n }\n\n const [decorations, promises] = calculateDecoration(\n tr.doc,\n parser,\n nodeTypes,\n languageExtractor,\n cache,\n )\n return { cache, decorations, promises }\n },\n },\n view: (view) => {\n const promises = new Set<Promise<void>>()\n\n // Refresh the decorations when all promises resolve\n const refresh = () => {\n if (promises.size > 0) {\n return\n }\n const tr = view.state.tr.setMeta('prosemirror-highlight-refresh', true)\n view.dispatch(tr)\n }\n\n const check = () => {\n const state = key.getState(view.state)\n\n for (const promise of state?.promises ?? []) {\n promises.add(promise)\n promise\n .then(() => {\n promises.delete(promise)\n refresh()\n })\n .catch((error) => {\n console.error(\n '[prosemirror-highlight] Error resolving parser:',\n error,\n )\n promises.delete(promise)\n })\n }\n }\n\n check()\n\n return {\n update: () => {\n check()\n },\n }\n },\n props: {\n decorations(this, state) {\n return this.getState(state)?.decorations\n },\n },\n })\n}\n\nfunction calculateDecoration(\n doc: ProseMirrorNode,\n parser: Parser,\n nodeTypes: string[],\n languageExtractor: LanguageExtractor,\n cache: DecorationCache,\n): [DecorationSet | undefined, Promise<void>[]] {\n const allDecorations: Decoration[][] = []\n const promises: Promise<void>[] = []\n const nodes = collectCodeBlocks(doc, nodeTypes)\n\n try {\n for (const [node, pos] of nodes) {\n const language = languageExtractor(node)\n const cached = cache.get(pos)\n\n if (cached) {\n const [_, decorations] = cached\n if (decorations.length > 0) {\n allDecorations.push(decorations)\n }\n } else {\n const parsed = parser({\n content: node.textContent,\n language: language || undefined,\n pos,\n size: node.nodeSize,\n })\n if (parsed && Array.isArray(parsed)) {\n cache.set(pos, node, parsed)\n if (parsed.length > 0) {\n allDecorations.push(parsed)\n }\n } else if (parsed instanceof Promise) {\n cache.remove(pos)\n promises.push(parsed)\n } else {\n console.error(\n `[prosemirror-highlight] Invalid parser result:`,\n parsed,\n )\n }\n }\n }\n } catch (error) {\n console.error(`[prosemirror-highlight] Error parsing code blocks:`, error)\n }\n\n const decorationSet =\n allDecorations.length > 0\n ? DecorationSet.create(doc, allDecorations.flat())\n : undefined\n return [decorationSet, promises]\n}\n\nfunction collectCodeBlocks(\n doc: ProseMirrorNode,\n nodeTypes: string[],\n): Array<[node: ProseMirrorNode, pos: number]> {\n const nodes: Array<[node: ProseMirrorNode, pos: number]> = []\n doc.descendants((node, pos) => {\n if (node.type.isTextblock && nodeTypes.includes(node.type.name)) {\n nodes.push([node, pos])\n return false\n }\n })\n return nodes\n}\n","import { Decoration } from 'prosemirror-view'\n\nimport type { Parser, ParserOptions } from './types'\n\n/**\n * Returns a new parser that adds line numbers to the parsed decorations.\n *\n * Line numbers are added as `<span class=\"line-number\">` elements with\n * the line number as the text content.\n */\nexport function withLineNumbers(parser: Parser): Parser {\n return function parserWithLineNumbers(options: ParserOptions) {\n const parsed = parser(options)\n if (parsed && Array.isArray(parsed)) {\n const { pos, content } = options\n const start = pos + 1\n const lineStarts = [start]\n for (const match of content.matchAll(/(?:\\r?\\n)/g)) {\n lineStarts.push(start + match.index + match[0].length)\n }\n const decorations: Decoration[] = []\n for (const [index, lineStart] of lineStarts.entries()) {\n decorations.push(createLineStartWidget(lineStart, index + 1))\n }\n return [...decorations, ...parsed]\n }\n return parsed\n }\n}\n\nexport function createLineStartWidget(\n from: number,\n lineNumber: number,\n): Decoration {\n return Decoration.widget(from, () => createLineStartSpan(lineNumber), {\n key: 'line-number',\n side: -20,\n })\n}\n\nfunction createLineStartSpan(lineNumber: number) {\n const span = document.createElement('span')\n span.className = 'line-number'\n span.textContent = lineNumber.toString()\n return span\n}\n"],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { n as Parser, r as ParserOptions } from "./types-jXJYKXGQ.js";
2
+ import { Highlighter } from "@lezer/highlight";
3
+ import { Tree } from "@lezer/common";
4
+
5
+ //#region src/lezer.d.ts
6
+ declare function createParser({
7
+ parse,
8
+ highlighter
9
+ }: {
10
+ parse: (options: ParserOptions) => Tree | undefined;
11
+ highlighter: Highlighter | readonly Highlighter[];
12
+ }): Parser;
13
+ //#endregion
14
+ export { type Parser, type ParserOptions, createParser };
15
+ //# sourceMappingURL=lezer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lezer.d.ts","names":[],"sources":["../src/lezer.ts"],"mappings":";;;;;iBASgB,YAAA,CAAA;EACd,KAAA;EACA;AAAA;EAEA,KAAA,GAAQ,OAAA,EAAS,aAAA,KAAkB,IAAA;EACnC,WAAA,EAAa,WAAA,YAAuB,WAAA;AAAA,IAClC,MAAA"}
package/dist/lezer.js ADDED
@@ -0,0 +1,20 @@
1
+ import { Decoration } from "prosemirror-view";
2
+ import { highlightTree } from "@lezer/highlight";
3
+
4
+ //#region src/lezer.ts
5
+ function createParser({ parse, highlighter }) {
6
+ return function lezerParser(options) {
7
+ const tree = parse(options);
8
+ if (!tree) return [];
9
+ const decorations = [];
10
+ const offset = options.pos + 1;
11
+ highlightTree(tree, highlighter, (from, to, classes) => {
12
+ if (classes && from < to) decorations.push(Decoration.inline(offset + from, offset + to, { class: classes }));
13
+ });
14
+ return decorations;
15
+ };
16
+ }
17
+
18
+ //#endregion
19
+ export { createParser };
20
+ //# sourceMappingURL=lezer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lezer.js","names":[],"sources":["../src/lezer.ts"],"sourcesContent":["import type { Tree } from '@lezer/common'\nimport type { Highlighter } from '@lezer/highlight'\nimport { highlightTree } from '@lezer/highlight'\nimport { Decoration } from 'prosemirror-view'\n\nimport type { Parser, ParserOptions } from './types'\n\nexport type { Parser, ParserOptions }\n\nexport function createParser({\n parse,\n highlighter,\n}: {\n parse: (options: ParserOptions) => Tree | undefined\n highlighter: Highlighter | readonly Highlighter[]\n}): Parser {\n return function lezerParser(options) {\n const tree = parse(options)\n\n if (!tree) {\n return []\n }\n\n const decorations: Decoration[] = []\n const offset = options.pos + 1\n highlightTree(tree, highlighter, (from, to, classes) => {\n if (classes && from < to) {\n decorations.push(\n Decoration.inline(offset + from, offset + to, { class: classes }),\n )\n }\n })\n\n return decorations\n }\n}\n"],"mappings":";;;;AASA,SAAgB,aAAa,EAC3B,OACA,eAIS;AACT,QAAO,SAAS,YAAY,SAAS;EACnC,MAAM,OAAO,MAAM,QAAQ;AAE3B,MAAI,CAAC,KACH,QAAO,EAAE;EAGX,MAAM,cAA4B,EAAE;EACpC,MAAM,SAAS,QAAQ,MAAM;AAC7B,gBAAc,MAAM,cAAc,MAAM,IAAI,YAAY;AACtD,OAAI,WAAW,OAAO,GACpB,aAAY,KACV,WAAW,OAAO,SAAS,MAAM,SAAS,IAAI,EAAE,OAAO,SAAS,CAAC,CAClE;IAEH;AAEF,SAAO"}
@@ -1,4 +1,4 @@
1
- import { n as Parser } from "./types-DCbyVqHc.js";
1
+ import { n as Parser } from "./types-jXJYKXGQ.js";
2
2
  import { Root } from "hast";
3
3
 
4
4
  //#region src/lowlight.d.ts
@@ -1,4 +1,4 @@
1
- import { n as Parser } from "./types-DCbyVqHc.js";
1
+ import { n as Parser } from "./types-jXJYKXGQ.js";
2
2
  import { Refractor } from "refractor/core";
3
3
 
4
4
  //#region src/refractor.d.ts
package/dist/shiki.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as Parser } from "./types-DCbyVqHc.js";
1
+ import { n as Parser } from "./types-jXJYKXGQ.js";
2
2
  import { CodeToTokensOptions, HighlighterGeneric } from "@shikijs/types";
3
3
 
4
4
  //#region src/shiki.d.ts
package/dist/shiki.js CHANGED
@@ -30,7 +30,7 @@ function createParser(highlighter, options) {
30
30
  };
31
31
  }
32
32
  /**
33
- * Copied from https://github.com/shikijs/shiki/blob/f76a371dbc2752cba341023df00ebfe9b66cb3f6/packages/core/src/utils.ts#L213
33
+ * Copied from https://github.com/shikijs/shiki/blob/v3.22.0/packages/core/src/utils/tokens.ts#L151
34
34
  *
35
35
  * Copy instead of import it from `shiki` to avoid importing the `shiki` package in this file.
36
36
  */
package/dist/shiki.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"shiki.js","names":[],"sources":["../src/shiki.ts"],"sourcesContent":["import type { CodeToTokensOptions, HighlighterGeneric } from '@shikijs/types'\nimport { Decoration } from 'prosemirror-view'\n\nimport type { Parser } from './types'\n\nexport type { Parser }\n\nexport function createParser<\n Language extends string = string,\n Theme extends string = string,\n>(\n highlighter: HighlighterGeneric<Language, Theme>,\n options?: CodeToTokensOptions<Language, Theme>,\n): Parser {\n return function parser({ content, language, pos, size }) {\n const decorations: Decoration[] = []\n\n const { tokens, fg, bg, rootStyle } = highlighter.codeToTokens(content, {\n lang: language as Language | undefined,\n\n // Use provided options for themes or just use first loaded theme\n ...(options ?? {\n theme: highlighter.getLoadedThemes()[0],\n }),\n })\n\n const style =\n rootStyle ||\n (fg && bg\n ? `--prosemirror-highlight:${fg};--prosemirror-highlight-bg:${bg}`\n : '')\n\n if (style) {\n const decoration = Decoration.node(pos, pos + size, { style })\n decorations.push(decoration)\n }\n\n let from = pos + 1\n\n for (const line of tokens) {\n for (const token of line) {\n const to = from + token.content.length\n\n const decoration = Decoration.inline(from, to, {\n // When using `options.themes` the `htmlStyle` field will be set, otherwise `color` will be set\n style: stringifyTokenStyle(\n token.htmlStyle ?? `color: ${token.color}`,\n ),\n class: 'shiki',\n })\n\n decorations.push(decoration)\n\n from = to\n }\n\n from += 1\n }\n\n return decorations\n }\n}\n\n/**\n * Copied from https://github.com/shikijs/shiki/blob/f76a371dbc2752cba341023df00ebfe9b66cb3f6/packages/core/src/utils.ts#L213\n *\n * Copy instead of import it from `shiki` to avoid importing the `shiki` package in this file.\n */\nfunction stringifyTokenStyle(token: string | Record<string, string>): string {\n if (typeof token === 'string') return token\n return Object.entries(token)\n .map(([key, value]) => `${key}:${value}`)\n .join(';')\n}\n"],"mappings":";;;AAOA,SAAgB,aAId,aACA,SACQ;AACR,QAAO,SAAS,OAAO,EAAE,SAAS,UAAU,KAAK,QAAQ;EACvD,MAAM,cAA4B,EAAE;EAEpC,MAAM,EAAE,QAAQ,IAAI,IAAI,cAAc,YAAY,aAAa,SAAS;GACtE,MAAM;GAGN,GAAI,WAAW,EACb,OAAO,YAAY,iBAAiB,CAAC,IACtC;GACF,CAAC;EAEF,MAAM,QACJ,cACC,MAAM,KACH,2BAA2B,GAAG,8BAA8B,OAC5D;AAEN,MAAI,OAAO;GACT,MAAM,aAAa,WAAW,KAAK,KAAK,MAAM,MAAM,EAAE,OAAO,CAAC;AAC9D,eAAY,KAAK,WAAW;;EAG9B,IAAI,OAAO,MAAM;AAEjB,OAAK,MAAM,QAAQ,QAAQ;AACzB,QAAK,MAAM,SAAS,MAAM;IACxB,MAAM,KAAK,OAAO,MAAM,QAAQ;IAEhC,MAAM,aAAa,WAAW,OAAO,MAAM,IAAI;KAE7C,OAAO,oBACL,MAAM,aAAa,UAAU,MAAM,QACpC;KACD,OAAO;KACR,CAAC;AAEF,gBAAY,KAAK,WAAW;AAE5B,WAAO;;AAGT,WAAQ;;AAGV,SAAO;;;;;;;;AASX,SAAS,oBAAoB,OAAgD;AAC3E,KAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAO,OAAO,QAAQ,MAAM,CACzB,KAAK,CAAC,KAAK,WAAW,GAAG,IAAI,GAAG,QAAQ,CACxC,KAAK,IAAI"}
1
+ {"version":3,"file":"shiki.js","names":[],"sources":["../src/shiki.ts"],"sourcesContent":["import type { CodeToTokensOptions, HighlighterGeneric } from '@shikijs/types'\nimport { Decoration } from 'prosemirror-view'\n\nimport type { Parser } from './types'\n\nexport type { Parser }\n\nexport function createParser<\n Language extends string = string,\n Theme extends string = string,\n>(\n highlighter: HighlighterGeneric<Language, Theme>,\n options?: CodeToTokensOptions<Language, Theme>,\n): Parser {\n return function parser({ content, language, pos, size }) {\n const decorations: Decoration[] = []\n\n const { tokens, fg, bg, rootStyle } = highlighter.codeToTokens(content, {\n lang: language as Language | undefined,\n\n // Use provided options for themes or just use first loaded theme\n ...(options ?? {\n theme: highlighter.getLoadedThemes()[0],\n }),\n })\n\n const style =\n rootStyle ||\n (fg && bg\n ? `--prosemirror-highlight:${fg};--prosemirror-highlight-bg:${bg}`\n : '')\n\n if (style) {\n const decoration = Decoration.node(pos, pos + size, { style })\n decorations.push(decoration)\n }\n\n let from = pos + 1\n\n for (const line of tokens) {\n for (const token of line) {\n const to = from + token.content.length\n\n const decoration = Decoration.inline(from, to, {\n // When using `options.themes` the `htmlStyle` field will be set, otherwise `color` will be set\n style: stringifyTokenStyle(\n token.htmlStyle ?? `color: ${token.color}`,\n ),\n class: 'shiki',\n })\n\n decorations.push(decoration)\n\n from = to\n }\n\n from += 1\n }\n\n return decorations\n }\n}\n\n/**\n * Copied from https://github.com/shikijs/shiki/blob/v3.22.0/packages/core/src/utils/tokens.ts#L151\n *\n * Copy instead of import it from `shiki` to avoid importing the `shiki` package in this file.\n */\nfunction stringifyTokenStyle(token: string | Record<string, string>): string {\n if (typeof token === 'string') return token\n return Object.entries(token)\n .map(([key, value]) => `${key}:${value}`)\n .join(';')\n}\n"],"mappings":";;;AAOA,SAAgB,aAId,aACA,SACQ;AACR,QAAO,SAAS,OAAO,EAAE,SAAS,UAAU,KAAK,QAAQ;EACvD,MAAM,cAA4B,EAAE;EAEpC,MAAM,EAAE,QAAQ,IAAI,IAAI,cAAc,YAAY,aAAa,SAAS;GACtE,MAAM;GAGN,GAAI,WAAW,EACb,OAAO,YAAY,iBAAiB,CAAC,IACtC;GACF,CAAC;EAEF,MAAM,QACJ,cACC,MAAM,KACH,2BAA2B,GAAG,8BAA8B,OAC5D;AAEN,MAAI,OAAO;GACT,MAAM,aAAa,WAAW,KAAK,KAAK,MAAM,MAAM,EAAE,OAAO,CAAC;AAC9D,eAAY,KAAK,WAAW;;EAG9B,IAAI,OAAO,MAAM;AAEjB,OAAK,MAAM,QAAQ,QAAQ;AACzB,QAAK,MAAM,SAAS,MAAM;IACxB,MAAM,KAAK,OAAO,MAAM,QAAQ;IAEhC,MAAM,aAAa,WAAW,OAAO,MAAM,IAAI;KAE7C,OAAO,oBACL,MAAM,aAAa,UAAU,MAAM,QACpC;KACD,OAAO;KACR,CAAC;AAEF,gBAAY,KAAK,WAAW;AAE5B,WAAO;;AAGT,WAAQ;;AAGV,SAAO;;;;;;;;AASX,SAAS,oBAAoB,OAAgD;AAC3E,KAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAO,OAAO,QAAQ,MAAM,CACzB,KAAK,CAAC,KAAK,WAAW,GAAG,IAAI,GAAG,QAAQ,CACxC,KAAK,IAAI"}
@@ -1,4 +1,4 @@
1
- import { n as Parser } from "./types-DCbyVqHc.js";
1
+ import { n as Parser } from "./types-jXJYKXGQ.js";
2
2
 
3
3
  //#region src/sugar-high.d.ts
4
4
  declare function createParser(): Parser;
@@ -2,13 +2,7 @@ import { Decoration } from "prosemirror-view";
2
2
  import { Node } from "prosemirror-model";
3
3
 
4
4
  //#region src/types.d.ts
5
- /**
6
- * A function that parses the text content of a code block node and returns an
7
- * array of ProseMirror decorations. If the underlying syntax highlighter is
8
- * still loading, you can return a promise that will be resolved when the
9
- * highlighter is ready.
10
- */
11
- type Parser = (options: {
5
+ interface ParserOptions {
12
6
  /**
13
7
  * The text content of the code block node.
14
8
  */
@@ -25,11 +19,18 @@ type Parser = (options: {
25
19
  * The size of the code block node.
26
20
  */
27
21
  size: number;
28
- }) => Decoration[] | Promise<void>;
22
+ }
23
+ /**
24
+ * A function that parses the text content of a code block node and returns an
25
+ * array of ProseMirror decorations. If the underlying syntax highlighter is
26
+ * still loading, you can return a promise that will be resolved when the
27
+ * highlighter is ready.
28
+ */
29
+ type Parser = (options: ParserOptions) => Decoration[] | Promise<void>;
29
30
  /**
30
31
  * A function that extracts the language of a code block node.
31
32
  */
32
33
  type LanguageExtractor = (node: Node) => string | undefined;
33
34
  //#endregion
34
- export { Parser as n, LanguageExtractor as t };
35
- //# sourceMappingURL=types-DCbyVqHc.d.ts.map
35
+ export { Parser as n, ParserOptions as r, LanguageExtractor as t };
36
+ //# sourceMappingURL=types-jXJYKXGQ.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-jXJYKXGQ.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;UAGiB,aAAA;;AAAjB;;EAIE,OAAA;EAJ4B;;;EAS5B,GAAA;EAUA;;;EALA,QAAA;EAcgB;;;EAThB,IAAA;AAAA;;;;;;;KASU,MAAA,IAAU,OAAA,EAAS,aAAA,KAAkB,UAAA,KAAe,OAAA;;AAKhE;;KAAY,iBAAA,IAAqB,IAAA,EAAM,IAAA"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "prosemirror-highlight",
3
3
  "type": "module",
4
- "version": "0.13.1",
4
+ "version": "0.15.0",
5
5
  "description": "A ProseMirror plugin to highlight code blocks",
6
6
  "author": "ocavue <ocavue@gmail.com>",
7
7
  "license": "MIT",
@@ -19,7 +19,8 @@
19
19
  "shiki",
20
20
  "refractor",
21
21
  "lowlight",
22
- "prism"
22
+ "prism",
23
+ "lezer"
23
24
  ],
24
25
  "sideEffects": false,
25
26
  "main": "./dist/index.js",
@@ -30,6 +31,10 @@
30
31
  "types": "./dist/index.d.ts",
31
32
  "default": "./dist/index.js"
32
33
  },
34
+ "./lezer": {
35
+ "types": "./dist/lezer.d.ts",
36
+ "default": "./dist/lezer.js"
37
+ },
33
38
  "./lowlight": {
34
39
  "types": "./dist/lowlight.d.ts",
35
40
  "default": "./dist/lowlight.js"
@@ -51,6 +56,8 @@
51
56
  "dist"
52
57
  ],
53
58
  "peerDependencies": {
59
+ "@lezer/common": "^1.0.0",
60
+ "@lezer/highlight": "^1.0.0",
54
61
  "@shikijs/types": "^1.29.2 || ^2.0.0 || ^3.0.0",
55
62
  "@types/hast": "^3.0.0",
56
63
  "highlight.js": "^11.9.0",
@@ -63,6 +70,12 @@
63
70
  "sugar-high": "^0.6.1 || ^0.7.0 || ^0.8.0 || ^0.9.0"
64
71
  },
65
72
  "peerDependenciesMeta": {
73
+ "@lezer/common": {
74
+ "optional": true
75
+ },
76
+ "@lezer/highlight": {
77
+ "optional": true
78
+ },
66
79
  "@shikijs/types": {
67
80
  "optional": true
68
81
  },
@@ -95,30 +108,35 @@
95
108
  }
96
109
  },
97
110
  "devDependencies": {
111
+ "@lezer/common": "^1.5.1",
112
+ "@lezer/css": "^1.3.0",
113
+ "@lezer/highlight": "^1.2.3",
114
+ "@lezer/javascript": "^1.5.4",
98
115
  "@ocavue/eslint-config": "^3.11.2",
99
116
  "@ocavue/tsconfig": "^0.6.3",
100
- "@shikijs/types": "^3.13.0",
117
+ "@shikijs/types": "^3.22.0",
101
118
  "@types/hast": "^3.0.4",
102
119
  "@types/node": "^24.0.0",
120
+ "diffable-html-snapshot": "^0.2.0",
103
121
  "eslint": "^9.39.2",
104
122
  "highlight.js": "^11.11.1",
105
123
  "jsdom": "^28.0.0",
106
124
  "lowlight": "^3.3.0",
107
125
  "pkg-pr-new": "^0.0.63",
108
- "prettier": "^3.8.0",
126
+ "prettier": "^3.8.1",
109
127
  "prosemirror-example-setup": "^1.2.3",
110
- "prosemirror-model": "^1.25.3",
128
+ "prosemirror-model": "^1.25.4",
111
129
  "prosemirror-schema-basic": "^1.2.4",
112
- "prosemirror-state": "^1.4.3",
113
- "prosemirror-transform": "^1.10.4",
114
- "prosemirror-view": "^1.41.2",
130
+ "prosemirror-state": "^1.4.4",
131
+ "prosemirror-transform": "^1.11.0",
132
+ "prosemirror-view": "^1.41.6",
115
133
  "refractor": "^5.0.0",
116
- "shiki": "^3.13.0",
117
- "sugar-high": "^0.9.3",
134
+ "shiki": "^3.22.0",
135
+ "sugar-high": "^0.9.5",
118
136
  "tsdown": "^0.20.3",
119
137
  "typescript": "^5.9.3",
120
- "vite": "^7.3.0",
121
- "vitest": "^4.0.0"
138
+ "vite": "^7.3.1",
139
+ "vitest": "^4.0.18"
122
140
  },
123
141
  "scripts": {
124
142
  "dev": "vite",
@@ -1 +0,0 @@
1
- {"version":3,"file":"types-DCbyVqHc.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;AASA;;;;KAAY,MAAA,IAAU,OAAA;EASpB;;;EALA,OAAA;EAgBI;;;EAXJ,GAAA;EAgBU;;;EAXV,QAAA;EAWoD;;;EANpD,IAAA;AAAA,MACI,UAAA,KAAe,OAAA;;;;KAKT,iBAAA,IAAqB,IAAA,EAAM,IAAA"}