prosemirror-highlight
Advanced tools
Comparing version 0.3.3 to 0.4.0
import { Node } from 'prosemirror-model'; | ||
import { Transaction, Plugin } from 'prosemirror-state'; | ||
import { Decoration, DecorationSet } from 'prosemirror-view'; | ||
import { P as Parser, L as LanguageExtractor } from './types-wUmJTPF3.js'; | ||
import { P as Parser, L as LanguageExtractor } from './types-hA0ujWQ1.js'; | ||
@@ -11,6 +11,3 @@ /** | ||
private cache; | ||
constructor(cache?: Map<number, { | ||
node: Node; | ||
decorations: Decoration[]; | ||
}>); | ||
constructor(cache?: Map<number, [node: Node, decorations: Decoration[]]>); | ||
/** | ||
@@ -20,6 +17,3 @@ * Gets the cache entry at the given doc position, or null if it doesn't exist | ||
*/ | ||
get(pos: number): { | ||
node: Node; | ||
decorations: Decoration[]; | ||
} | undefined; | ||
get(pos: number): [node: Node, decorations: Decoration[]] | undefined; | ||
/** | ||
@@ -46,3 +40,3 @@ * Sets the cache entry at the given position with the give node/decoration | ||
*/ | ||
private remove; | ||
remove(pos: number): void; | ||
/** | ||
@@ -64,2 +58,3 @@ * Invalidates the cache by removing all decoration entries on nodes that have | ||
decorations: DecorationSet; | ||
promises: Promise<void>[]; | ||
} | ||
@@ -66,0 +61,0 @@ /** |
@@ -27,3 +27,3 @@ // src/cache.ts | ||
} | ||
this.cache.set(pos, { node, decorations }); | ||
this.cache.set(pos, [node, decorations]); | ||
} | ||
@@ -59,3 +59,3 @@ /** | ||
const mapping = tr.mapping; | ||
this.cache.forEach(({ node, decorations }, pos) => { | ||
this.cache.forEach(([node, decorations], pos) => { | ||
if (pos < 0) { | ||
@@ -94,3 +94,3 @@ return; | ||
const cache = new DecorationCache(); | ||
const decorations = getDecorationSet( | ||
const [decorations, promises] = calculateDecoration( | ||
instance.doc, | ||
@@ -102,11 +102,13 @@ parser, | ||
); | ||
return { cache, decorations }; | ||
return { cache, decorations, promises }; | ||
}, | ||
apply(tr, data) { | ||
apply: (tr, data) => { | ||
const cache = data.cache.invalidate(tr); | ||
if (!tr.docChanged) { | ||
const refresh = !!tr.getMeta("prosemirror-highlight-refresh"); | ||
if (!tr.docChanged && !refresh) { | ||
const decorations2 = data.decorations.map(tr.mapping, tr.doc); | ||
return { cache, decorations: decorations2 }; | ||
const promises2 = data.promises; | ||
return { cache, decorations: decorations2, promises: promises2 }; | ||
} | ||
const decorations = getDecorationSet( | ||
const [decorations, promises] = calculateDecoration( | ||
tr.doc, | ||
@@ -118,5 +120,34 @@ parser, | ||
); | ||
return { cache, decorations }; | ||
return { cache, decorations, promises }; | ||
} | ||
}, | ||
view: (view) => { | ||
const promises = /* @__PURE__ */ new Set(); | ||
const refresh = () => { | ||
if (promises.size > 0) { | ||
return; | ||
} | ||
const tr = view.state.tr.setMeta("prosemirror-highlight-refresh", true); | ||
view.dispatch(tr); | ||
}; | ||
const check = () => { | ||
var _a; | ||
const state = key.getState(view.state); | ||
for (const promise of (_a = state == null ? void 0 : state.promises) != null ? _a : []) { | ||
promises.add(promise); | ||
promise.then(() => { | ||
promises.delete(promise); | ||
refresh(); | ||
}).catch(() => { | ||
promises.delete(promise); | ||
}); | ||
} | ||
}; | ||
check(); | ||
return { | ||
update: () => { | ||
check(); | ||
} | ||
}; | ||
}, | ||
props: { | ||
@@ -130,4 +161,5 @@ decorations(state) { | ||
} | ||
function getDecorationSet(doc, parser, nodeTypes, languageExtractor, cache) { | ||
function calculateDecoration(doc, parser, nodeTypes, languageExtractor, cache) { | ||
const result = []; | ||
const promises = []; | ||
doc.descendants((node, pos) => { | ||
@@ -141,3 +173,4 @@ if (!node.type.isTextblock) { | ||
if (cached) { | ||
result.push(...cached.decorations); | ||
const [_, decorations] = cached; | ||
result.push(...decorations); | ||
} else { | ||
@@ -149,4 +182,9 @@ const decorations = parser({ | ||
}); | ||
cache.set(pos, node, decorations); | ||
result.push(...decorations); | ||
if (decorations && Array.isArray(decorations)) { | ||
cache.set(pos, node, decorations); | ||
result.push(...decorations); | ||
} else if (decorations instanceof Promise) { | ||
cache.remove(pos); | ||
promises.push(decorations); | ||
} | ||
} | ||
@@ -156,3 +194,3 @@ } | ||
}); | ||
return DecorationSet.create(doc, result); | ||
return [DecorationSet.create(doc, result), promises]; | ||
} | ||
@@ -159,0 +197,0 @@ export { |
import { Root } from 'hast'; | ||
import { P as Parser } from './types-wUmJTPF3.js'; | ||
import { P as Parser } from './types-hA0ujWQ1.js'; | ||
import 'prosemirror-model'; | ||
@@ -4,0 +4,0 @@ import 'prosemirror-view'; |
import { Refractor } from 'refractor/lib/core'; | ||
import { P as Parser } from './types-wUmJTPF3.js'; | ||
import { P as Parser } from './types-hA0ujWQ1.js'; | ||
import 'prosemirror-model'; | ||
@@ -4,0 +4,0 @@ import 'prosemirror-view'; |
import { Highlighter } from 'shiki'; | ||
import { P as Parser } from './types-wUmJTPF3.js'; | ||
import { P as Parser } from './types-hA0ujWQ1.js'; | ||
import 'prosemirror-model'; | ||
@@ -4,0 +4,0 @@ import 'prosemirror-view'; |
import { Highlighter } from 'shikiji'; | ||
import { P as Parser } from './types-wUmJTPF3.js'; | ||
import { P as Parser } from './types-hA0ujWQ1.js'; | ||
import 'prosemirror-model'; | ||
@@ -4,0 +4,0 @@ import 'prosemirror-view'; |
// src/shikiji.ts | ||
import { Decoration } from "prosemirror-view"; | ||
import "shikiji"; | ||
function createParser(highlighter) { | ||
@@ -5,0 +4,0 @@ return function parser({ content, language, pos }) { |
{ | ||
"name": "prosemirror-highlight", | ||
"type": "module", | ||
"version": "0.3.3", | ||
"packageManager": "pnpm@8.12.0", | ||
"version": "0.4.0", | ||
"packageManager": "pnpm@8.13.1", | ||
"description": "A ProseMirror plugin to highlight code blocks", | ||
@@ -64,3 +64,3 @@ "author": "ocavue <ocavue@gmail.com>", | ||
"refractor": "^4.8.1", | ||
"shiki": "^0.14.6", | ||
"shiki": "^0.14.0", | ||
"shikiji": "^0.8.0 || ^0.9.0" | ||
@@ -104,4 +104,4 @@ }, | ||
"@types/hast": "^3.0.3", | ||
"@types/node": "^20.10.4", | ||
"eslint": "^8.55.0", | ||
"@types/node": "^20.10.6", | ||
"eslint": "^8.56.0", | ||
"highlight.js": "^11.9.0", | ||
@@ -116,10 +116,10 @@ "jsdom": "^23.0.1", | ||
"prosemirror-transform": "^1.8.0", | ||
"prosemirror-view": "^1.32.6", | ||
"prosemirror-view": "^1.32.7", | ||
"refractor": "^4.8.1", | ||
"shiki": "^0.14.6", | ||
"shikiji": "^0.8.0", | ||
"shiki": "^0.14.7", | ||
"shikiji": "^0.9.16", | ||
"tsup": "^8.0.1", | ||
"typescript": "^5.3.3", | ||
"vite": "^5.0.7", | ||
"vitest": "^1.0.4" | ||
"vite": "^5.0.10", | ||
"vitest": "^1.1.1" | ||
}, | ||
@@ -126,0 +126,0 @@ "renovate": { |
134
README.md
@@ -11,2 +11,5 @@ # prosemirror-highlight | ||
<details> | ||
<summary>Static loading of a fixed set of languages</summary> | ||
```ts | ||
@@ -18,3 +21,3 @@ import { getHighlighter, setCDN } from 'shiki' | ||
setCDN('https://unpkg.com/shiki@0.14.6/') | ||
setCDN('https://unpkg.com/shiki@0.14.7/') | ||
@@ -29,4 +32,66 @@ const highlighter = await getHighlighter({ | ||
</details> | ||
<details> | ||
<summary>Dynamic loading of arbitrary languages</summary> | ||
```ts | ||
import { getHighlighter, setCDN, type Highlighter, type Lang } from 'shiki' | ||
import { createHighlightPlugin } from 'prosemirror-highlight' | ||
import { createParser, type Parser } from 'prosemirror-highlight/shiki' | ||
setCDN('https://unpkg.com/shiki@0.14.7/') | ||
let highlighterPromise: Promise<void> | undefined | ||
let highlighter: Highlighter | undefined | ||
let parser: Parser | undefined | ||
const loadedLanguages = new Set<string>() | ||
/** | ||
* Lazy load highlighter and highlighter languages. | ||
* | ||
* When the highlighter or the required language is not loaded, it returns a | ||
* promise that resolves when the highlighter or the language is loaded. | ||
* Otherwise, it returns an array of decorations. | ||
*/ | ||
const lazyParser: Parser = (options) => { | ||
if (!highlighterPromise) { | ||
highlighterPromise = getHighlighter({ | ||
themes: ['github-light'], | ||
langs: [], | ||
}).then((h) => { | ||
highlighter = h | ||
}) | ||
return highlighterPromise | ||
} | ||
if (!highlighter) { | ||
return highlighterPromise | ||
} | ||
const language = options.language | ||
if (language && !loadedLanguages.has(language)) { | ||
return highlighter.loadLanguage(language as Lang).then(() => { | ||
loadedLanguages.add(language) | ||
}) | ||
} | ||
if (!parser) { | ||
parser = createParser(highlighter) | ||
} | ||
return parser(options) | ||
} | ||
export const shikiLazyPlugin = createHighlightPlugin({ parser: lazyParser }) | ||
``` | ||
</details> | ||
### With [Shikiji] | ||
<details> | ||
<summary>Static loading of a fixed set of languages</summary> | ||
```ts | ||
@@ -46,4 +111,64 @@ import { getHighlighter } from 'shikiji' | ||
</details> | ||
<details> | ||
<summary>Dynamic loading of arbitrary languages</summary> | ||
```ts | ||
import { getHighlighter, type Highlighter, type BuiltinLanguage } from 'shikiji' | ||
import { createHighlightPlugin, type Parser } from 'prosemirror-highlight' | ||
import { createParser } from 'prosemirror-highlight/shikiji' | ||
let highlighterPromise: Promise<void> | undefined | ||
let highlighter: Highlighter | undefined | ||
let parser: Parser | undefined | ||
const loadedLanguages = new Set<string>() | ||
/** | ||
* Lazy load highlighter and highlighter languages. | ||
* | ||
* When the highlighter or the required language is not loaded, it returns a | ||
* promise that resolves when the highlighter or the language is loaded. | ||
* Otherwise, it returns an array of decorations. | ||
*/ | ||
const lazyParser: Parser = (options) => { | ||
if (!highlighterPromise) { | ||
highlighterPromise = getHighlighter({ | ||
themes: ['vitesse-light'], | ||
langs: [], | ||
}).then((h) => { | ||
highlighter = h | ||
}) | ||
return highlighterPromise | ||
} | ||
if (!highlighter) { | ||
return highlighterPromise | ||
} | ||
const language = options.language | ||
if (language && !loadedLanguages.has(language)) { | ||
return highlighter.loadLanguage(language as BuiltinLanguage).then(() => { | ||
loadedLanguages.add(language) | ||
}) | ||
} | ||
if (!parser) { | ||
parser = createParser(highlighter) | ||
} | ||
return parser(options) | ||
} | ||
export const shikijiLazyPlugin = createHighlightPlugin({ parser: lazyParser }) | ||
``` | ||
</details> | ||
### With [lowlight] (based on [Highlight.js]) | ||
<details> | ||
<summary>Static loading of all languages</summary> | ||
```ts | ||
@@ -62,4 +187,9 @@ import 'highlight.js/styles/default.css' | ||
</details> | ||
### With [refractor] (based on [Prism]) | ||
<details> | ||
<summary>Static loading of all languages</summary> | ||
```ts | ||
@@ -75,2 +205,4 @@ import { refractor } from 'refractor' | ||
</details> | ||
## Online demo | ||
@@ -77,0 +209,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
24544
455
222