@domternal/extension-code-block-lowlight 0.2.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/LICENSE +21 -0
- package/README.md +53 -0
- package/dist/index.cjs +225 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +76 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.js +220 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Domternal contributors
|
|
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,53 @@
|
|
|
1
|
+
# @domternal/extension-code-block-lowlight
|
|
2
|
+
|
|
3
|
+
Syntax-highlighted code blocks for the Domternal editor, powered by [lowlight](https://github.com/wooorm/lowlight) (highlight.js).
|
|
4
|
+
|
|
5
|
+
Part of the [Domternal](https://github.com/domternal/domternal) toolkit. Full docs at [domternal.dev](https://domternal.dev).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @domternal/core @domternal/extension-code-block-lowlight lowlight
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { Editor, StarterKit } from '@domternal/core';
|
|
17
|
+
import { CodeBlockLowlight } from '@domternal/extension-code-block-lowlight';
|
|
18
|
+
import { createLowlight, common } from 'lowlight';
|
|
19
|
+
|
|
20
|
+
const lowlight = createLowlight(common);
|
|
21
|
+
|
|
22
|
+
const editor = new Editor({
|
|
23
|
+
element: document.getElementById('editor')!,
|
|
24
|
+
extensions: [
|
|
25
|
+
StarterKit.configure({ codeBlock: false }), // disable the default CodeBlock
|
|
26
|
+
CodeBlockLowlight.configure({ lowlight }),
|
|
27
|
+
],
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Options
|
|
32
|
+
|
|
33
|
+
| Option | Default | Description |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| `lowlight` | (required) | A lowlight instance created with `createLowlight()` |
|
|
36
|
+
| `defaultLanguage` | `null` | Default language when none is specified |
|
|
37
|
+
| `autoDetect` | `true` | Auto-detect language when none is specified |
|
|
38
|
+
| `tabIndentation` | `true` | Tab key inserts spaces inside code blocks |
|
|
39
|
+
| `tabSize` | `2` | Number of spaces per tab |
|
|
40
|
+
|
|
41
|
+
### Server-Side Rendering
|
|
42
|
+
|
|
43
|
+
Use `generateHighlightedHTML` to produce syntax-highlighted HTML on the server:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { generateHighlightedHTML } from '@domternal/extension-code-block-lowlight';
|
|
47
|
+
|
|
48
|
+
const html = generateHighlightedHTML(jsonContent, { lowlight });
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
[MIT](https://github.com/domternal/domternal/blob/main/LICENSE)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@domternal/core');
|
|
4
|
+
var state = require('@domternal/pm/state');
|
|
5
|
+
var view = require('@domternal/pm/view');
|
|
6
|
+
var hastUtilToHtml = require('hast-util-to-html');
|
|
7
|
+
|
|
8
|
+
// src/CodeBlockLowlight.ts
|
|
9
|
+
function flattenNodes(nodes, classes = []) {
|
|
10
|
+
return nodes.flatMap((node) => {
|
|
11
|
+
if (node.type === "element") {
|
|
12
|
+
const childClasses = [...classes, ...node.properties?.className ?? []];
|
|
13
|
+
return flattenNodes(node.children, childClasses);
|
|
14
|
+
}
|
|
15
|
+
return [{ text: node.value, classes }];
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
function decorateCodeBlock(node, pos, lowlight, defaultLanguage, autoDetect) {
|
|
19
|
+
const language = node.attrs["language"] ?? defaultLanguage;
|
|
20
|
+
const text = node.textContent;
|
|
21
|
+
if (!text) return [];
|
|
22
|
+
let result;
|
|
23
|
+
if (language && lowlight.registered(language)) {
|
|
24
|
+
result = lowlight.highlight(language, text);
|
|
25
|
+
} else if (autoDetect) {
|
|
26
|
+
result = lowlight.highlightAuto(text);
|
|
27
|
+
} else {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
const tokens = flattenNodes(result.children);
|
|
31
|
+
const decorations = [];
|
|
32
|
+
let offset = pos + 1;
|
|
33
|
+
for (const token of tokens) {
|
|
34
|
+
if (token.classes.length > 0) {
|
|
35
|
+
decorations.push(
|
|
36
|
+
view.Decoration.inline(offset, offset + token.text.length, {
|
|
37
|
+
class: token.classes.join(" ")
|
|
38
|
+
})
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
offset += token.text.length;
|
|
42
|
+
}
|
|
43
|
+
return decorations;
|
|
44
|
+
}
|
|
45
|
+
function decorateAll(doc, options) {
|
|
46
|
+
const decorations = [];
|
|
47
|
+
doc.descendants((node, pos) => {
|
|
48
|
+
if (node.type.name === options.name) {
|
|
49
|
+
decorations.push(
|
|
50
|
+
...decorateCodeBlock(node, pos, options.lowlight, options.defaultLanguage, options.autoDetect)
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
return view.DecorationSet.create(doc, decorations);
|
|
55
|
+
}
|
|
56
|
+
var lowlightPluginKey = new state.PluginKey("lowlight");
|
|
57
|
+
function lowlightPlugin(options) {
|
|
58
|
+
const { lowlight } = options;
|
|
59
|
+
if (!lowlight) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
'[@domternal/extension-code-block-lowlight] The "lowlight" option is required. Provide a lowlight instance: CodeBlockLowlight.configure({ lowlight: createLowlight(common) })'
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
const validated = { ...options, lowlight };
|
|
65
|
+
return new state.Plugin({
|
|
66
|
+
key: lowlightPluginKey,
|
|
67
|
+
state: {
|
|
68
|
+
init(_config, state) {
|
|
69
|
+
return decorateAll(state.doc, validated);
|
|
70
|
+
},
|
|
71
|
+
apply(tr, decorationSet, _oldState, newState) {
|
|
72
|
+
if (!tr.docChanged) return decorationSet;
|
|
73
|
+
const changedRanges = [];
|
|
74
|
+
for (let i = 0; i < tr.steps.length; i++) {
|
|
75
|
+
const map = tr.mapping.maps[i];
|
|
76
|
+
if (!map) continue;
|
|
77
|
+
map.forEach((oldStart, oldEnd) => {
|
|
78
|
+
const from = tr.mapping.slice(i).map(oldStart, -1);
|
|
79
|
+
const to = tr.mapping.slice(i).map(oldEnd, 1);
|
|
80
|
+
changedRanges.push({ from, to });
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
let updated = decorationSet.map(tr.mapping, tr.doc);
|
|
84
|
+
const affected = [];
|
|
85
|
+
newState.doc.descendants((node, pos) => {
|
|
86
|
+
if (node.type.name !== validated.name) return;
|
|
87
|
+
const end = pos + node.nodeSize;
|
|
88
|
+
if (changedRanges.some((r) => r.from <= end && r.to >= pos)) {
|
|
89
|
+
affected.push({ node, pos });
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
if (affected.length === 0) return updated;
|
|
93
|
+
for (const block of affected) {
|
|
94
|
+
const end = block.pos + block.node.nodeSize;
|
|
95
|
+
const stale = updated.find(block.pos, end);
|
|
96
|
+
updated = updated.remove(stale);
|
|
97
|
+
const fresh = decorateCodeBlock(
|
|
98
|
+
block.node,
|
|
99
|
+
block.pos,
|
|
100
|
+
validated.lowlight,
|
|
101
|
+
validated.defaultLanguage,
|
|
102
|
+
validated.autoDetect
|
|
103
|
+
);
|
|
104
|
+
updated = updated.add(newState.doc, fresh);
|
|
105
|
+
}
|
|
106
|
+
return updated;
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
props: {
|
|
110
|
+
decorations(state) {
|
|
111
|
+
return lowlightPluginKey.getState(state);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/CodeBlockLowlight.ts
|
|
118
|
+
var CodeBlockLowlight = core.CodeBlock.extend({
|
|
119
|
+
addOptions() {
|
|
120
|
+
return {
|
|
121
|
+
...core.CodeBlock.options,
|
|
122
|
+
lowlight: null,
|
|
123
|
+
defaultLanguage: null,
|
|
124
|
+
autoDetect: true,
|
|
125
|
+
tabIndentation: true,
|
|
126
|
+
tabSize: 2
|
|
127
|
+
};
|
|
128
|
+
},
|
|
129
|
+
addStorage() {
|
|
130
|
+
return {
|
|
131
|
+
listLanguages: () => {
|
|
132
|
+
return this.options.lowlight ? this.options.lowlight.listLanguages() : [];
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
addKeyboardShortcuts() {
|
|
137
|
+
const parentShortcuts = core.CodeBlock.config.addKeyboardShortcuts?.call(this) ?? {};
|
|
138
|
+
if (!this.options.tabIndentation) return parentShortcuts;
|
|
139
|
+
const spaces = " ".repeat(this.options.tabSize);
|
|
140
|
+
return {
|
|
141
|
+
...parentShortcuts,
|
|
142
|
+
Tab: () => {
|
|
143
|
+
if (!this.editor) return false;
|
|
144
|
+
const { state } = this.editor;
|
|
145
|
+
if (state.selection.$head.parent.type.name !== this.name) return false;
|
|
146
|
+
return this.editor.commands["insertText"]?.(spaces) ?? false;
|
|
147
|
+
},
|
|
148
|
+
"Shift-Tab": () => {
|
|
149
|
+
if (!this.editor) return false;
|
|
150
|
+
const { state } = this.editor;
|
|
151
|
+
const { $head } = state.selection;
|
|
152
|
+
if ($head.parent.type.name !== this.name) return false;
|
|
153
|
+
const textBefore = $head.parent.textBetween(0, $head.parentOffset);
|
|
154
|
+
const lastNewline = textBefore.lastIndexOf("\n");
|
|
155
|
+
const lineStart = lastNewline + 1;
|
|
156
|
+
const lineText = textBefore.slice(lineStart);
|
|
157
|
+
const leadingSpaces = /^ */.exec(lineText)?.[0]?.length ?? 0;
|
|
158
|
+
const spacesToRemove = Math.min(leadingSpaces, this.options.tabSize);
|
|
159
|
+
if (spacesToRemove === 0) return false;
|
|
160
|
+
const deleteFrom = $head.start() + lineStart;
|
|
161
|
+
const { tr } = state;
|
|
162
|
+
tr.delete(deleteFrom, deleteFrom + spacesToRemove);
|
|
163
|
+
this.editor.view.dispatch(tr);
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
},
|
|
168
|
+
addProseMirrorPlugins() {
|
|
169
|
+
return [
|
|
170
|
+
...core.CodeBlock.config.addProseMirrorPlugins?.call(this) ?? [],
|
|
171
|
+
lowlightPlugin({
|
|
172
|
+
name: this.name,
|
|
173
|
+
lowlight: this.options.lowlight,
|
|
174
|
+
defaultLanguage: this.options.defaultLanguage,
|
|
175
|
+
autoDetect: this.options.autoDetect
|
|
176
|
+
})
|
|
177
|
+
];
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
function generateHighlightedHTML(content, extensions, lowlight, options = {}) {
|
|
181
|
+
const htmlOptions = {};
|
|
182
|
+
if (options.document !== void 0) {
|
|
183
|
+
htmlOptions.document = options.document;
|
|
184
|
+
}
|
|
185
|
+
const html = core.generateHTML(content, extensions, htmlOptions);
|
|
186
|
+
return html.replace(
|
|
187
|
+
/<pre([^>]*)><code(?:\s+class="language-([^"]*)")?>([\s\S]*?)<\/code><\/pre>/g,
|
|
188
|
+
(_match, preAttrs, language, code) => {
|
|
189
|
+
const decoded = code.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'");
|
|
190
|
+
const lang = language ?? options.defaultLanguage ?? null;
|
|
191
|
+
let highlighted;
|
|
192
|
+
if (lang && lowlight.registered(lang)) {
|
|
193
|
+
const result = lowlight.highlight(lang, decoded);
|
|
194
|
+
highlighted = hastUtilToHtml.toHtml(result);
|
|
195
|
+
} else if (options.autoDetect && decoded.length > 0) {
|
|
196
|
+
const result = lowlight.highlightAuto(decoded);
|
|
197
|
+
highlighted = hastUtilToHtml.toHtml(result);
|
|
198
|
+
} else {
|
|
199
|
+
const codeClass2 = language ? ` class="language-${language}"` : "";
|
|
200
|
+
return `<pre${preAttrs}><code${codeClass2}>${code}</code></pre>`;
|
|
201
|
+
}
|
|
202
|
+
const codeClass = language ? ` class="language-${language}"` : "";
|
|
203
|
+
return `<pre${preAttrs}><code${codeClass}>${highlighted}</code></pre>`;
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
function createCodeHighlighter(lowlight, options = {}) {
|
|
208
|
+
return (code, language) => {
|
|
209
|
+
const lang = language ?? options.defaultLanguage ?? null;
|
|
210
|
+
if (lang && lowlight.registered(lang)) {
|
|
211
|
+
return hastUtilToHtml.toHtml(lowlight.highlight(lang, code));
|
|
212
|
+
}
|
|
213
|
+
if (options.autoDetect && code.length > 0) {
|
|
214
|
+
return hastUtilToHtml.toHtml(lowlight.highlightAuto(code));
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
exports.CodeBlockLowlight = CodeBlockLowlight;
|
|
221
|
+
exports.createCodeHighlighter = createCodeHighlighter;
|
|
222
|
+
exports.generateHighlightedHTML = generateHighlightedHTML;
|
|
223
|
+
exports.lowlightPluginKey = lowlightPluginKey;
|
|
224
|
+
//# sourceMappingURL=index.cjs.map
|
|
225
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lowlightPlugin.ts","../src/CodeBlockLowlight.ts","../src/generateHighlightedHTML.ts","../src/createCodeHighlighter.ts"],"names":["Decoration","DecorationSet","PluginKey","Plugin","CodeBlock","generateHTML","toHtml","codeClass"],"mappings":"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","file":"index.cjs","sourcesContent":["import { Plugin, PluginKey } from '@domternal/pm/state';\nimport type { EditorState, Transaction } from '@domternal/pm/state';\nimport { Decoration, DecorationSet } from '@domternal/pm/view';\nimport type { Node as PMNode } from '@domternal/pm/model';\nimport type { createLowlight } from 'lowlight';\n\n/** The lowlight instance type (return type of createLowlight) */\nexport type Lowlight = ReturnType<typeof createLowlight>;\n\n// Minimal hast types (lowlight output)\ninterface HastText {\n readonly type: 'text';\n readonly value: string;\n}\n\ninterface HastElement {\n readonly type: 'element';\n readonly properties?: { readonly className?: readonly string[] };\n readonly children: readonly HastNode[];\n}\n\ntype HastNode = HastText | HastElement;\n\nexport interface LowlightPluginOptions {\n name: string;\n lowlight: Lowlight | null;\n defaultLanguage: string | null;\n autoDetect: boolean;\n}\n\n/** Internal type after validation — lowlight guaranteed non-null */\ninterface ValidatedOptions extends LowlightPluginOptions {\n lowlight: Lowlight;\n}\n\ninterface Token {\n text: string;\n classes: string[];\n}\n\n/** Flatten hast tree into a list of text tokens with their CSS classes */\nfunction flattenNodes(nodes: readonly HastNode[], classes: string[] = []): Token[] {\n return nodes.flatMap((node): Token[] => {\n if (node.type === 'element') {\n const childClasses = [...classes, ...(node.properties?.className ?? [])];\n return flattenNodes(node.children, childClasses);\n }\n return [{ text: node.value, classes }];\n });\n}\n\n/** Create inline decorations for a single code block */\nfunction decorateCodeBlock(\n node: PMNode,\n pos: number,\n lowlight: Lowlight,\n defaultLanguage: string | null,\n autoDetect: boolean,\n): Decoration[] {\n const language = (node.attrs['language'] as string | null) ?? defaultLanguage;\n const text = node.textContent;\n if (!text) return [];\n\n let result;\n if (language && lowlight.registered(language)) {\n result = lowlight.highlight(language, text);\n } else if (autoDetect) {\n result = lowlight.highlightAuto(text);\n } else {\n return [];\n }\n\n const tokens = flattenNodes(result.children as HastNode[]);\n const decorations: Decoration[] = [];\n let offset = pos + 1; // +1 to skip the node's opening token\n\n for (const token of tokens) {\n if (token.classes.length > 0) {\n decorations.push(\n Decoration.inline(offset, offset + token.text.length, {\n class: token.classes.join(' '),\n }),\n );\n }\n offset += token.text.length;\n }\n\n return decorations;\n}\n\n/** Build decorations for all code blocks in the document */\nfunction decorateAll(doc: PMNode, options: ValidatedOptions): DecorationSet {\n const decorations: Decoration[] = [];\n doc.descendants((node, pos) => {\n if (node.type.name === options.name) {\n decorations.push(\n ...decorateCodeBlock(node, pos, options.lowlight, options.defaultLanguage, options.autoDetect),\n );\n }\n });\n return DecorationSet.create(doc, decorations);\n}\n\nexport const lowlightPluginKey = new PluginKey('lowlight');\n\nexport function lowlightPlugin(options: LowlightPluginOptions): Plugin {\n const { lowlight } = options;\n if (!lowlight) {\n throw new Error(\n '[@domternal/extension-code-block-lowlight] The \"lowlight\" option is required. ' +\n 'Provide a lowlight instance: CodeBlockLowlight.configure({ lowlight: createLowlight(common) })',\n );\n }\n\n // After validation, lowlight is guaranteed non-null\n const validated = { ...options, lowlight };\n\n return new Plugin({\n key: lowlightPluginKey,\n\n state: {\n init(_config: unknown, state: EditorState) {\n return decorateAll(state.doc, validated);\n },\n\n apply(tr: Transaction, decorationSet: DecorationSet, _oldState: EditorState, newState: EditorState) {\n if (!tr.docChanged) return decorationSet;\n\n // Compute changed ranges in final document coordinates\n // Uses oldStart/oldEnd from each step map, mapped through the full\n // remaining mapping to get final positions.\n const changedRanges: { from: number; to: number }[] = [];\n for (let i = 0; i < tr.steps.length; i++) {\n const map = tr.mapping.maps[i];\n if (!map) continue;\n map.forEach((oldStart: number, oldEnd: number) => {\n const from = tr.mapping.slice(i).map(oldStart, -1);\n const to = tr.mapping.slice(i).map(oldEnd, 1);\n changedRanges.push({ from, to });\n });\n }\n\n // Map existing decorations to new positions\n let updated = decorationSet.map(tr.mapping, tr.doc);\n\n // Find code blocks that overlap with changed ranges\n const affected: { node: PMNode; pos: number }[] = [];\n newState.doc.descendants((node, pos) => {\n if (node.type.name !== validated.name) return;\n const end = pos + node.nodeSize;\n if (changedRanges.some((r) => r.from <= end && r.to >= pos)) {\n affected.push({ node, pos });\n }\n });\n\n if (affected.length === 0) return updated;\n\n // Re-highlight only affected code blocks\n for (const block of affected) {\n const end = block.pos + block.node.nodeSize;\n const stale = updated.find(block.pos, end);\n updated = updated.remove(stale);\n\n const fresh = decorateCodeBlock(\n block.node, block.pos,\n validated.lowlight, validated.defaultLanguage, validated.autoDetect,\n );\n updated = updated.add(newState.doc, fresh);\n }\n\n return updated;\n },\n },\n\n props: {\n decorations(state: EditorState) {\n return lowlightPluginKey.getState(state) as DecorationSet | undefined;\n },\n },\n });\n}\n","import { CodeBlock } from '@domternal/core';\nimport type { CodeBlockOptions } from '@domternal/core';\nimport { lowlightPlugin } from './lowlightPlugin.js';\nimport type { Lowlight } from './lowlightPlugin.js';\n\nexport interface CodeBlockLowlightOptions extends CodeBlockOptions {\n /** The lowlight instance (required). Create with createLowlight(). Null before configure(). */\n lowlight: Lowlight | null;\n /** Default language when none is specified. @default null */\n defaultLanguage: string | null;\n /** Auto-detect language when none specified. @default true */\n autoDetect: boolean;\n /** Tab key inserts spaces in code blocks. @default true */\n tabIndentation: boolean;\n /** Number of spaces per tab. @default 2 */\n tabSize: number;\n}\n\nexport interface CodeBlockLowlightStorage {\n /** Returns list of registered language names */\n listLanguages: () => string[];\n}\n\nexport const CodeBlockLowlight = CodeBlock.extend<CodeBlockLowlightOptions, CodeBlockLowlightStorage>({\n addOptions() {\n return {\n ...CodeBlock.options,\n lowlight: null as unknown as Lowlight,\n defaultLanguage: null,\n autoDetect: true,\n tabIndentation: true,\n tabSize: 2,\n };\n },\n\n addStorage() {\n return {\n listLanguages: (): string[] => {\n return this.options.lowlight ? this.options.lowlight.listLanguages() : [];\n },\n };\n },\n\n addKeyboardShortcuts() {\n const parentShortcuts = CodeBlock.config.addKeyboardShortcuts?.call(this) ?? {};\n\n if (!this.options.tabIndentation) return parentShortcuts;\n\n const spaces = ' '.repeat(this.options.tabSize);\n\n return {\n ...parentShortcuts,\n Tab: () => {\n if (!this.editor) return false;\n const { state } = this.editor;\n if (state.selection.$head.parent.type.name !== this.name) return false;\n return this.editor.commands['insertText']?.(spaces) ?? false;\n },\n 'Shift-Tab': () => {\n if (!this.editor) return false;\n const { state } = this.editor;\n const { $head } = state.selection;\n if ($head.parent.type.name !== this.name) return false;\n\n const textBefore = $head.parent.textBetween(0, $head.parentOffset);\n const lastNewline = textBefore.lastIndexOf('\\n');\n const lineStart = lastNewline + 1;\n const lineText = textBefore.slice(lineStart);\n const leadingSpaces = (/^ */).exec(lineText)?.[0]?.length ?? 0;\n const spacesToRemove = Math.min(leadingSpaces, this.options.tabSize);\n\n if (spacesToRemove === 0) return false;\n\n const deleteFrom = $head.start() + lineStart;\n const { tr } = state;\n tr.delete(deleteFrom, deleteFrom + spacesToRemove);\n this.editor.view.dispatch(tr);\n return true;\n },\n };\n },\n\n addProseMirrorPlugins() {\n return [\n ...(CodeBlock.config.addProseMirrorPlugins?.call(this) ?? []),\n lowlightPlugin({\n name: this.name,\n lowlight: this.options.lowlight,\n defaultLanguage: this.options.defaultLanguage,\n autoDetect: this.options.autoDetect,\n }),\n ];\n },\n});\n","import type { AnyExtension, JSONContent, GenerateHTMLOptions } from '@domternal/core';\nimport { generateHTML } from '@domternal/core';\nimport type { Lowlight } from './lowlightPlugin.js';\nimport { toHtml } from 'hast-util-to-html';\n\nexport interface GenerateHighlightedHTMLOptions {\n /** Default language for code blocks without a language. */\n defaultLanguage?: string;\n /** Auto-detect language when none specified. @default false */\n autoDetect?: boolean;\n /** Custom document implementation for generateHTML. */\n document?: Document;\n}\n\n/**\n * Generate HTML with syntax-highlighted code blocks.\n *\n * Unlike generateHTML(), this applies lowlight highlighting to code blocks,\n * producing `<span class=\"hljs-keyword\">` etc. inside `<code>` elements.\n *\n * @example\n * ```ts\n * import { generateHighlightedHTML } from '@domternal/extension-code-block-lowlight';\n * import { createLowlight, common } from 'lowlight';\n *\n * const lowlight = createLowlight(common);\n * const html = generateHighlightedHTML(json, extensions, lowlight);\n * ```\n */\nexport function generateHighlightedHTML(\n content: JSONContent,\n extensions: AnyExtension[],\n lowlight: Lowlight,\n options: GenerateHighlightedHTMLOptions = {},\n): string {\n const htmlOptions: GenerateHTMLOptions = {};\n if (options.document !== undefined) {\n htmlOptions.document = options.document;\n }\n\n const html = generateHTML(content, extensions, htmlOptions);\n\n return html.replace(\n /<pre([^>]*)><code(?:\\s+class=\"language-([^\"]*)\")?>([\\s\\S]*?)<\\/code><\\/pre>/g,\n (_match, preAttrs: string, language: string | undefined, code: string) => {\n // Unescape HTML entities in code content\n const decoded = code\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\");\n\n const lang = language ?? options.defaultLanguage ?? null;\n let highlighted: string;\n\n if (lang && lowlight.registered(lang)) {\n const result = lowlight.highlight(lang, decoded);\n highlighted = toHtml(result);\n } else if (options.autoDetect && decoded.length > 0) {\n const result = lowlight.highlightAuto(decoded);\n highlighted = toHtml(result);\n } else {\n const codeClass = language ? ` class=\"language-${language}\"` : '';\n return `<pre${preAttrs}><code${codeClass}>${code}</code></pre>`;\n }\n\n const codeClass = language ? ` class=\"language-${language}\"` : '';\n return `<pre${preAttrs}><code${codeClass}>${highlighted}</code></pre>`;\n },\n );\n}\n","import type { Lowlight } from './lowlightPlugin.js';\nimport { toHtml } from 'hast-util-to-html';\n\nexport interface CreateCodeHighlighterOptions {\n /** Default language when none specified on the code block. */\n defaultLanguage?: string;\n /** Auto-detect language when none specified. @default false */\n autoDetect?: boolean;\n}\n\n/**\n * Creates a `codeHighlighter` callback for use with `inlineStyles()`.\n *\n * @example\n * ```ts\n * import { createLowlight, common } from 'lowlight';\n * import { createCodeHighlighter } from '@domternal/extension-code-block-lowlight';\n * import { inlineStyles } from '@domternal/core';\n *\n * const lowlight = createLowlight(common);\n * const styled = inlineStyles(html, {\n * codeHighlighter: createCodeHighlighter(lowlight),\n * });\n * ```\n */\nexport function createCodeHighlighter(\n lowlight: Lowlight,\n options: CreateCodeHighlighterOptions = {},\n): (code: string, language: string | null) => string | null {\n return (code: string, language: string | null): string | null => {\n const lang = language ?? options.defaultLanguage ?? null;\n\n if (lang && lowlight.registered(lang)) {\n return toHtml(lowlight.highlight(lang, code));\n }\n\n if (options.autoDetect && code.length > 0) {\n return toHtml(lowlight.highlightAuto(code));\n }\n\n return null;\n };\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as _domternal_core from '@domternal/core';
|
|
2
|
+
import { CodeBlockOptions, JSONContent, AnyExtension } from '@domternal/core';
|
|
3
|
+
import { PluginKey } from '@domternal/pm/state';
|
|
4
|
+
import { createLowlight } from 'lowlight';
|
|
5
|
+
|
|
6
|
+
/** The lowlight instance type (return type of createLowlight) */
|
|
7
|
+
type Lowlight = ReturnType<typeof createLowlight>;
|
|
8
|
+
declare const lowlightPluginKey: PluginKey<any>;
|
|
9
|
+
|
|
10
|
+
interface CodeBlockLowlightOptions extends CodeBlockOptions {
|
|
11
|
+
/** The lowlight instance (required). Create with createLowlight(). Null before configure(). */
|
|
12
|
+
lowlight: Lowlight | null;
|
|
13
|
+
/** Default language when none is specified. @default null */
|
|
14
|
+
defaultLanguage: string | null;
|
|
15
|
+
/** Auto-detect language when none specified. @default true */
|
|
16
|
+
autoDetect: boolean;
|
|
17
|
+
/** Tab key inserts spaces in code blocks. @default true */
|
|
18
|
+
tabIndentation: boolean;
|
|
19
|
+
/** Number of spaces per tab. @default 2 */
|
|
20
|
+
tabSize: number;
|
|
21
|
+
}
|
|
22
|
+
interface CodeBlockLowlightStorage {
|
|
23
|
+
/** Returns list of registered language names */
|
|
24
|
+
listLanguages: () => string[];
|
|
25
|
+
}
|
|
26
|
+
declare const CodeBlockLowlight: _domternal_core.Node<CodeBlockLowlightOptions, CodeBlockLowlightStorage>;
|
|
27
|
+
|
|
28
|
+
interface GenerateHighlightedHTMLOptions {
|
|
29
|
+
/** Default language for code blocks without a language. */
|
|
30
|
+
defaultLanguage?: string;
|
|
31
|
+
/** Auto-detect language when none specified. @default false */
|
|
32
|
+
autoDetect?: boolean;
|
|
33
|
+
/** Custom document implementation for generateHTML. */
|
|
34
|
+
document?: Document;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Generate HTML with syntax-highlighted code blocks.
|
|
38
|
+
*
|
|
39
|
+
* Unlike generateHTML(), this applies lowlight highlighting to code blocks,
|
|
40
|
+
* producing `<span class="hljs-keyword">` etc. inside `<code>` elements.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* import { generateHighlightedHTML } from '@domternal/extension-code-block-lowlight';
|
|
45
|
+
* import { createLowlight, common } from 'lowlight';
|
|
46
|
+
*
|
|
47
|
+
* const lowlight = createLowlight(common);
|
|
48
|
+
* const html = generateHighlightedHTML(json, extensions, lowlight);
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
declare function generateHighlightedHTML(content: JSONContent, extensions: AnyExtension[], lowlight: Lowlight, options?: GenerateHighlightedHTMLOptions): string;
|
|
52
|
+
|
|
53
|
+
interface CreateCodeHighlighterOptions {
|
|
54
|
+
/** Default language when none specified on the code block. */
|
|
55
|
+
defaultLanguage?: string;
|
|
56
|
+
/** Auto-detect language when none specified. @default false */
|
|
57
|
+
autoDetect?: boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Creates a `codeHighlighter` callback for use with `inlineStyles()`.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* import { createLowlight, common } from 'lowlight';
|
|
65
|
+
* import { createCodeHighlighter } from '@domternal/extension-code-block-lowlight';
|
|
66
|
+
* import { inlineStyles } from '@domternal/core';
|
|
67
|
+
*
|
|
68
|
+
* const lowlight = createLowlight(common);
|
|
69
|
+
* const styled = inlineStyles(html, {
|
|
70
|
+
* codeHighlighter: createCodeHighlighter(lowlight),
|
|
71
|
+
* });
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
declare function createCodeHighlighter(lowlight: Lowlight, options?: CreateCodeHighlighterOptions): (code: string, language: string | null) => string | null;
|
|
75
|
+
|
|
76
|
+
export { CodeBlockLowlight, type CodeBlockLowlightOptions, type CodeBlockLowlightStorage, type CreateCodeHighlighterOptions, type GenerateHighlightedHTMLOptions, type Lowlight, createCodeHighlighter, generateHighlightedHTML, lowlightPluginKey };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as _domternal_core from '@domternal/core';
|
|
2
|
+
import { CodeBlockOptions, JSONContent, AnyExtension } from '@domternal/core';
|
|
3
|
+
import { PluginKey } from '@domternal/pm/state';
|
|
4
|
+
import { createLowlight } from 'lowlight';
|
|
5
|
+
|
|
6
|
+
/** The lowlight instance type (return type of createLowlight) */
|
|
7
|
+
type Lowlight = ReturnType<typeof createLowlight>;
|
|
8
|
+
declare const lowlightPluginKey: PluginKey<any>;
|
|
9
|
+
|
|
10
|
+
interface CodeBlockLowlightOptions extends CodeBlockOptions {
|
|
11
|
+
/** The lowlight instance (required). Create with createLowlight(). Null before configure(). */
|
|
12
|
+
lowlight: Lowlight | null;
|
|
13
|
+
/** Default language when none is specified. @default null */
|
|
14
|
+
defaultLanguage: string | null;
|
|
15
|
+
/** Auto-detect language when none specified. @default true */
|
|
16
|
+
autoDetect: boolean;
|
|
17
|
+
/** Tab key inserts spaces in code blocks. @default true */
|
|
18
|
+
tabIndentation: boolean;
|
|
19
|
+
/** Number of spaces per tab. @default 2 */
|
|
20
|
+
tabSize: number;
|
|
21
|
+
}
|
|
22
|
+
interface CodeBlockLowlightStorage {
|
|
23
|
+
/** Returns list of registered language names */
|
|
24
|
+
listLanguages: () => string[];
|
|
25
|
+
}
|
|
26
|
+
declare const CodeBlockLowlight: _domternal_core.Node<CodeBlockLowlightOptions, CodeBlockLowlightStorage>;
|
|
27
|
+
|
|
28
|
+
interface GenerateHighlightedHTMLOptions {
|
|
29
|
+
/** Default language for code blocks without a language. */
|
|
30
|
+
defaultLanguage?: string;
|
|
31
|
+
/** Auto-detect language when none specified. @default false */
|
|
32
|
+
autoDetect?: boolean;
|
|
33
|
+
/** Custom document implementation for generateHTML. */
|
|
34
|
+
document?: Document;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Generate HTML with syntax-highlighted code blocks.
|
|
38
|
+
*
|
|
39
|
+
* Unlike generateHTML(), this applies lowlight highlighting to code blocks,
|
|
40
|
+
* producing `<span class="hljs-keyword">` etc. inside `<code>` elements.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* import { generateHighlightedHTML } from '@domternal/extension-code-block-lowlight';
|
|
45
|
+
* import { createLowlight, common } from 'lowlight';
|
|
46
|
+
*
|
|
47
|
+
* const lowlight = createLowlight(common);
|
|
48
|
+
* const html = generateHighlightedHTML(json, extensions, lowlight);
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
declare function generateHighlightedHTML(content: JSONContent, extensions: AnyExtension[], lowlight: Lowlight, options?: GenerateHighlightedHTMLOptions): string;
|
|
52
|
+
|
|
53
|
+
interface CreateCodeHighlighterOptions {
|
|
54
|
+
/** Default language when none specified on the code block. */
|
|
55
|
+
defaultLanguage?: string;
|
|
56
|
+
/** Auto-detect language when none specified. @default false */
|
|
57
|
+
autoDetect?: boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Creates a `codeHighlighter` callback for use with `inlineStyles()`.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* import { createLowlight, common } from 'lowlight';
|
|
65
|
+
* import { createCodeHighlighter } from '@domternal/extension-code-block-lowlight';
|
|
66
|
+
* import { inlineStyles } from '@domternal/core';
|
|
67
|
+
*
|
|
68
|
+
* const lowlight = createLowlight(common);
|
|
69
|
+
* const styled = inlineStyles(html, {
|
|
70
|
+
* codeHighlighter: createCodeHighlighter(lowlight),
|
|
71
|
+
* });
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
declare function createCodeHighlighter(lowlight: Lowlight, options?: CreateCodeHighlighterOptions): (code: string, language: string | null) => string | null;
|
|
75
|
+
|
|
76
|
+
export { CodeBlockLowlight, type CodeBlockLowlightOptions, type CodeBlockLowlightStorage, type CreateCodeHighlighterOptions, type GenerateHighlightedHTMLOptions, type Lowlight, createCodeHighlighter, generateHighlightedHTML, lowlightPluginKey };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { CodeBlock, generateHTML } from '@domternal/core';
|
|
2
|
+
import { PluginKey, Plugin } from '@domternal/pm/state';
|
|
3
|
+
import { Decoration, DecorationSet } from '@domternal/pm/view';
|
|
4
|
+
import { toHtml } from 'hast-util-to-html';
|
|
5
|
+
|
|
6
|
+
// src/CodeBlockLowlight.ts
|
|
7
|
+
function flattenNodes(nodes, classes = []) {
|
|
8
|
+
return nodes.flatMap((node) => {
|
|
9
|
+
if (node.type === "element") {
|
|
10
|
+
const childClasses = [...classes, ...node.properties?.className ?? []];
|
|
11
|
+
return flattenNodes(node.children, childClasses);
|
|
12
|
+
}
|
|
13
|
+
return [{ text: node.value, classes }];
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function decorateCodeBlock(node, pos, lowlight, defaultLanguage, autoDetect) {
|
|
17
|
+
const language = node.attrs["language"] ?? defaultLanguage;
|
|
18
|
+
const text = node.textContent;
|
|
19
|
+
if (!text) return [];
|
|
20
|
+
let result;
|
|
21
|
+
if (language && lowlight.registered(language)) {
|
|
22
|
+
result = lowlight.highlight(language, text);
|
|
23
|
+
} else if (autoDetect) {
|
|
24
|
+
result = lowlight.highlightAuto(text);
|
|
25
|
+
} else {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
const tokens = flattenNodes(result.children);
|
|
29
|
+
const decorations = [];
|
|
30
|
+
let offset = pos + 1;
|
|
31
|
+
for (const token of tokens) {
|
|
32
|
+
if (token.classes.length > 0) {
|
|
33
|
+
decorations.push(
|
|
34
|
+
Decoration.inline(offset, offset + token.text.length, {
|
|
35
|
+
class: token.classes.join(" ")
|
|
36
|
+
})
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
offset += token.text.length;
|
|
40
|
+
}
|
|
41
|
+
return decorations;
|
|
42
|
+
}
|
|
43
|
+
function decorateAll(doc, options) {
|
|
44
|
+
const decorations = [];
|
|
45
|
+
doc.descendants((node, pos) => {
|
|
46
|
+
if (node.type.name === options.name) {
|
|
47
|
+
decorations.push(
|
|
48
|
+
...decorateCodeBlock(node, pos, options.lowlight, options.defaultLanguage, options.autoDetect)
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
return DecorationSet.create(doc, decorations);
|
|
53
|
+
}
|
|
54
|
+
var lowlightPluginKey = new PluginKey("lowlight");
|
|
55
|
+
function lowlightPlugin(options) {
|
|
56
|
+
const { lowlight } = options;
|
|
57
|
+
if (!lowlight) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
'[@domternal/extension-code-block-lowlight] The "lowlight" option is required. Provide a lowlight instance: CodeBlockLowlight.configure({ lowlight: createLowlight(common) })'
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
const validated = { ...options, lowlight };
|
|
63
|
+
return new Plugin({
|
|
64
|
+
key: lowlightPluginKey,
|
|
65
|
+
state: {
|
|
66
|
+
init(_config, state) {
|
|
67
|
+
return decorateAll(state.doc, validated);
|
|
68
|
+
},
|
|
69
|
+
apply(tr, decorationSet, _oldState, newState) {
|
|
70
|
+
if (!tr.docChanged) return decorationSet;
|
|
71
|
+
const changedRanges = [];
|
|
72
|
+
for (let i = 0; i < tr.steps.length; i++) {
|
|
73
|
+
const map = tr.mapping.maps[i];
|
|
74
|
+
if (!map) continue;
|
|
75
|
+
map.forEach((oldStart, oldEnd) => {
|
|
76
|
+
const from = tr.mapping.slice(i).map(oldStart, -1);
|
|
77
|
+
const to = tr.mapping.slice(i).map(oldEnd, 1);
|
|
78
|
+
changedRanges.push({ from, to });
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
let updated = decorationSet.map(tr.mapping, tr.doc);
|
|
82
|
+
const affected = [];
|
|
83
|
+
newState.doc.descendants((node, pos) => {
|
|
84
|
+
if (node.type.name !== validated.name) return;
|
|
85
|
+
const end = pos + node.nodeSize;
|
|
86
|
+
if (changedRanges.some((r) => r.from <= end && r.to >= pos)) {
|
|
87
|
+
affected.push({ node, pos });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
if (affected.length === 0) return updated;
|
|
91
|
+
for (const block of affected) {
|
|
92
|
+
const end = block.pos + block.node.nodeSize;
|
|
93
|
+
const stale = updated.find(block.pos, end);
|
|
94
|
+
updated = updated.remove(stale);
|
|
95
|
+
const fresh = decorateCodeBlock(
|
|
96
|
+
block.node,
|
|
97
|
+
block.pos,
|
|
98
|
+
validated.lowlight,
|
|
99
|
+
validated.defaultLanguage,
|
|
100
|
+
validated.autoDetect
|
|
101
|
+
);
|
|
102
|
+
updated = updated.add(newState.doc, fresh);
|
|
103
|
+
}
|
|
104
|
+
return updated;
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
props: {
|
|
108
|
+
decorations(state) {
|
|
109
|
+
return lowlightPluginKey.getState(state);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/CodeBlockLowlight.ts
|
|
116
|
+
var CodeBlockLowlight = CodeBlock.extend({
|
|
117
|
+
addOptions() {
|
|
118
|
+
return {
|
|
119
|
+
...CodeBlock.options,
|
|
120
|
+
lowlight: null,
|
|
121
|
+
defaultLanguage: null,
|
|
122
|
+
autoDetect: true,
|
|
123
|
+
tabIndentation: true,
|
|
124
|
+
tabSize: 2
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
addStorage() {
|
|
128
|
+
return {
|
|
129
|
+
listLanguages: () => {
|
|
130
|
+
return this.options.lowlight ? this.options.lowlight.listLanguages() : [];
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
},
|
|
134
|
+
addKeyboardShortcuts() {
|
|
135
|
+
const parentShortcuts = CodeBlock.config.addKeyboardShortcuts?.call(this) ?? {};
|
|
136
|
+
if (!this.options.tabIndentation) return parentShortcuts;
|
|
137
|
+
const spaces = " ".repeat(this.options.tabSize);
|
|
138
|
+
return {
|
|
139
|
+
...parentShortcuts,
|
|
140
|
+
Tab: () => {
|
|
141
|
+
if (!this.editor) return false;
|
|
142
|
+
const { state } = this.editor;
|
|
143
|
+
if (state.selection.$head.parent.type.name !== this.name) return false;
|
|
144
|
+
return this.editor.commands["insertText"]?.(spaces) ?? false;
|
|
145
|
+
},
|
|
146
|
+
"Shift-Tab": () => {
|
|
147
|
+
if (!this.editor) return false;
|
|
148
|
+
const { state } = this.editor;
|
|
149
|
+
const { $head } = state.selection;
|
|
150
|
+
if ($head.parent.type.name !== this.name) return false;
|
|
151
|
+
const textBefore = $head.parent.textBetween(0, $head.parentOffset);
|
|
152
|
+
const lastNewline = textBefore.lastIndexOf("\n");
|
|
153
|
+
const lineStart = lastNewline + 1;
|
|
154
|
+
const lineText = textBefore.slice(lineStart);
|
|
155
|
+
const leadingSpaces = /^ */.exec(lineText)?.[0]?.length ?? 0;
|
|
156
|
+
const spacesToRemove = Math.min(leadingSpaces, this.options.tabSize);
|
|
157
|
+
if (spacesToRemove === 0) return false;
|
|
158
|
+
const deleteFrom = $head.start() + lineStart;
|
|
159
|
+
const { tr } = state;
|
|
160
|
+
tr.delete(deleteFrom, deleteFrom + spacesToRemove);
|
|
161
|
+
this.editor.view.dispatch(tr);
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
},
|
|
166
|
+
addProseMirrorPlugins() {
|
|
167
|
+
return [
|
|
168
|
+
...CodeBlock.config.addProseMirrorPlugins?.call(this) ?? [],
|
|
169
|
+
lowlightPlugin({
|
|
170
|
+
name: this.name,
|
|
171
|
+
lowlight: this.options.lowlight,
|
|
172
|
+
defaultLanguage: this.options.defaultLanguage,
|
|
173
|
+
autoDetect: this.options.autoDetect
|
|
174
|
+
})
|
|
175
|
+
];
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
function generateHighlightedHTML(content, extensions, lowlight, options = {}) {
|
|
179
|
+
const htmlOptions = {};
|
|
180
|
+
if (options.document !== void 0) {
|
|
181
|
+
htmlOptions.document = options.document;
|
|
182
|
+
}
|
|
183
|
+
const html = generateHTML(content, extensions, htmlOptions);
|
|
184
|
+
return html.replace(
|
|
185
|
+
/<pre([^>]*)><code(?:\s+class="language-([^"]*)")?>([\s\S]*?)<\/code><\/pre>/g,
|
|
186
|
+
(_match, preAttrs, language, code) => {
|
|
187
|
+
const decoded = code.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'");
|
|
188
|
+
const lang = language ?? options.defaultLanguage ?? null;
|
|
189
|
+
let highlighted;
|
|
190
|
+
if (lang && lowlight.registered(lang)) {
|
|
191
|
+
const result = lowlight.highlight(lang, decoded);
|
|
192
|
+
highlighted = toHtml(result);
|
|
193
|
+
} else if (options.autoDetect && decoded.length > 0) {
|
|
194
|
+
const result = lowlight.highlightAuto(decoded);
|
|
195
|
+
highlighted = toHtml(result);
|
|
196
|
+
} else {
|
|
197
|
+
const codeClass2 = language ? ` class="language-${language}"` : "";
|
|
198
|
+
return `<pre${preAttrs}><code${codeClass2}>${code}</code></pre>`;
|
|
199
|
+
}
|
|
200
|
+
const codeClass = language ? ` class="language-${language}"` : "";
|
|
201
|
+
return `<pre${preAttrs}><code${codeClass}>${highlighted}</code></pre>`;
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
function createCodeHighlighter(lowlight, options = {}) {
|
|
206
|
+
return (code, language) => {
|
|
207
|
+
const lang = language ?? options.defaultLanguage ?? null;
|
|
208
|
+
if (lang && lowlight.registered(lang)) {
|
|
209
|
+
return toHtml(lowlight.highlight(lang, code));
|
|
210
|
+
}
|
|
211
|
+
if (options.autoDetect && code.length > 0) {
|
|
212
|
+
return toHtml(lowlight.highlightAuto(code));
|
|
213
|
+
}
|
|
214
|
+
return null;
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export { CodeBlockLowlight, createCodeHighlighter, generateHighlightedHTML, lowlightPluginKey };
|
|
219
|
+
//# sourceMappingURL=index.js.map
|
|
220
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lowlightPlugin.ts","../src/CodeBlockLowlight.ts","../src/generateHighlightedHTML.ts","../src/createCodeHighlighter.ts"],"names":["codeClass","toHtml"],"mappings":"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","file":"index.js","sourcesContent":["import { Plugin, PluginKey } from '@domternal/pm/state';\nimport type { EditorState, Transaction } from '@domternal/pm/state';\nimport { Decoration, DecorationSet } from '@domternal/pm/view';\nimport type { Node as PMNode } from '@domternal/pm/model';\nimport type { createLowlight } from 'lowlight';\n\n/** The lowlight instance type (return type of createLowlight) */\nexport type Lowlight = ReturnType<typeof createLowlight>;\n\n// Minimal hast types (lowlight output)\ninterface HastText {\n readonly type: 'text';\n readonly value: string;\n}\n\ninterface HastElement {\n readonly type: 'element';\n readonly properties?: { readonly className?: readonly string[] };\n readonly children: readonly HastNode[];\n}\n\ntype HastNode = HastText | HastElement;\n\nexport interface LowlightPluginOptions {\n name: string;\n lowlight: Lowlight | null;\n defaultLanguage: string | null;\n autoDetect: boolean;\n}\n\n/** Internal type after validation — lowlight guaranteed non-null */\ninterface ValidatedOptions extends LowlightPluginOptions {\n lowlight: Lowlight;\n}\n\ninterface Token {\n text: string;\n classes: string[];\n}\n\n/** Flatten hast tree into a list of text tokens with their CSS classes */\nfunction flattenNodes(nodes: readonly HastNode[], classes: string[] = []): Token[] {\n return nodes.flatMap((node): Token[] => {\n if (node.type === 'element') {\n const childClasses = [...classes, ...(node.properties?.className ?? [])];\n return flattenNodes(node.children, childClasses);\n }\n return [{ text: node.value, classes }];\n });\n}\n\n/** Create inline decorations for a single code block */\nfunction decorateCodeBlock(\n node: PMNode,\n pos: number,\n lowlight: Lowlight,\n defaultLanguage: string | null,\n autoDetect: boolean,\n): Decoration[] {\n const language = (node.attrs['language'] as string | null) ?? defaultLanguage;\n const text = node.textContent;\n if (!text) return [];\n\n let result;\n if (language && lowlight.registered(language)) {\n result = lowlight.highlight(language, text);\n } else if (autoDetect) {\n result = lowlight.highlightAuto(text);\n } else {\n return [];\n }\n\n const tokens = flattenNodes(result.children as HastNode[]);\n const decorations: Decoration[] = [];\n let offset = pos + 1; // +1 to skip the node's opening token\n\n for (const token of tokens) {\n if (token.classes.length > 0) {\n decorations.push(\n Decoration.inline(offset, offset + token.text.length, {\n class: token.classes.join(' '),\n }),\n );\n }\n offset += token.text.length;\n }\n\n return decorations;\n}\n\n/** Build decorations for all code blocks in the document */\nfunction decorateAll(doc: PMNode, options: ValidatedOptions): DecorationSet {\n const decorations: Decoration[] = [];\n doc.descendants((node, pos) => {\n if (node.type.name === options.name) {\n decorations.push(\n ...decorateCodeBlock(node, pos, options.lowlight, options.defaultLanguage, options.autoDetect),\n );\n }\n });\n return DecorationSet.create(doc, decorations);\n}\n\nexport const lowlightPluginKey = new PluginKey('lowlight');\n\nexport function lowlightPlugin(options: LowlightPluginOptions): Plugin {\n const { lowlight } = options;\n if (!lowlight) {\n throw new Error(\n '[@domternal/extension-code-block-lowlight] The \"lowlight\" option is required. ' +\n 'Provide a lowlight instance: CodeBlockLowlight.configure({ lowlight: createLowlight(common) })',\n );\n }\n\n // After validation, lowlight is guaranteed non-null\n const validated = { ...options, lowlight };\n\n return new Plugin({\n key: lowlightPluginKey,\n\n state: {\n init(_config: unknown, state: EditorState) {\n return decorateAll(state.doc, validated);\n },\n\n apply(tr: Transaction, decorationSet: DecorationSet, _oldState: EditorState, newState: EditorState) {\n if (!tr.docChanged) return decorationSet;\n\n // Compute changed ranges in final document coordinates\n // Uses oldStart/oldEnd from each step map, mapped through the full\n // remaining mapping to get final positions.\n const changedRanges: { from: number; to: number }[] = [];\n for (let i = 0; i < tr.steps.length; i++) {\n const map = tr.mapping.maps[i];\n if (!map) continue;\n map.forEach((oldStart: number, oldEnd: number) => {\n const from = tr.mapping.slice(i).map(oldStart, -1);\n const to = tr.mapping.slice(i).map(oldEnd, 1);\n changedRanges.push({ from, to });\n });\n }\n\n // Map existing decorations to new positions\n let updated = decorationSet.map(tr.mapping, tr.doc);\n\n // Find code blocks that overlap with changed ranges\n const affected: { node: PMNode; pos: number }[] = [];\n newState.doc.descendants((node, pos) => {\n if (node.type.name !== validated.name) return;\n const end = pos + node.nodeSize;\n if (changedRanges.some((r) => r.from <= end && r.to >= pos)) {\n affected.push({ node, pos });\n }\n });\n\n if (affected.length === 0) return updated;\n\n // Re-highlight only affected code blocks\n for (const block of affected) {\n const end = block.pos + block.node.nodeSize;\n const stale = updated.find(block.pos, end);\n updated = updated.remove(stale);\n\n const fresh = decorateCodeBlock(\n block.node, block.pos,\n validated.lowlight, validated.defaultLanguage, validated.autoDetect,\n );\n updated = updated.add(newState.doc, fresh);\n }\n\n return updated;\n },\n },\n\n props: {\n decorations(state: EditorState) {\n return lowlightPluginKey.getState(state) as DecorationSet | undefined;\n },\n },\n });\n}\n","import { CodeBlock } from '@domternal/core';\nimport type { CodeBlockOptions } from '@domternal/core';\nimport { lowlightPlugin } from './lowlightPlugin.js';\nimport type { Lowlight } from './lowlightPlugin.js';\n\nexport interface CodeBlockLowlightOptions extends CodeBlockOptions {\n /** The lowlight instance (required). Create with createLowlight(). Null before configure(). */\n lowlight: Lowlight | null;\n /** Default language when none is specified. @default null */\n defaultLanguage: string | null;\n /** Auto-detect language when none specified. @default true */\n autoDetect: boolean;\n /** Tab key inserts spaces in code blocks. @default true */\n tabIndentation: boolean;\n /** Number of spaces per tab. @default 2 */\n tabSize: number;\n}\n\nexport interface CodeBlockLowlightStorage {\n /** Returns list of registered language names */\n listLanguages: () => string[];\n}\n\nexport const CodeBlockLowlight = CodeBlock.extend<CodeBlockLowlightOptions, CodeBlockLowlightStorage>({\n addOptions() {\n return {\n ...CodeBlock.options,\n lowlight: null as unknown as Lowlight,\n defaultLanguage: null,\n autoDetect: true,\n tabIndentation: true,\n tabSize: 2,\n };\n },\n\n addStorage() {\n return {\n listLanguages: (): string[] => {\n return this.options.lowlight ? this.options.lowlight.listLanguages() : [];\n },\n };\n },\n\n addKeyboardShortcuts() {\n const parentShortcuts = CodeBlock.config.addKeyboardShortcuts?.call(this) ?? {};\n\n if (!this.options.tabIndentation) return parentShortcuts;\n\n const spaces = ' '.repeat(this.options.tabSize);\n\n return {\n ...parentShortcuts,\n Tab: () => {\n if (!this.editor) return false;\n const { state } = this.editor;\n if (state.selection.$head.parent.type.name !== this.name) return false;\n return this.editor.commands['insertText']?.(spaces) ?? false;\n },\n 'Shift-Tab': () => {\n if (!this.editor) return false;\n const { state } = this.editor;\n const { $head } = state.selection;\n if ($head.parent.type.name !== this.name) return false;\n\n const textBefore = $head.parent.textBetween(0, $head.parentOffset);\n const lastNewline = textBefore.lastIndexOf('\\n');\n const lineStart = lastNewline + 1;\n const lineText = textBefore.slice(lineStart);\n const leadingSpaces = (/^ */).exec(lineText)?.[0]?.length ?? 0;\n const spacesToRemove = Math.min(leadingSpaces, this.options.tabSize);\n\n if (spacesToRemove === 0) return false;\n\n const deleteFrom = $head.start() + lineStart;\n const { tr } = state;\n tr.delete(deleteFrom, deleteFrom + spacesToRemove);\n this.editor.view.dispatch(tr);\n return true;\n },\n };\n },\n\n addProseMirrorPlugins() {\n return [\n ...(CodeBlock.config.addProseMirrorPlugins?.call(this) ?? []),\n lowlightPlugin({\n name: this.name,\n lowlight: this.options.lowlight,\n defaultLanguage: this.options.defaultLanguage,\n autoDetect: this.options.autoDetect,\n }),\n ];\n },\n});\n","import type { AnyExtension, JSONContent, GenerateHTMLOptions } from '@domternal/core';\nimport { generateHTML } from '@domternal/core';\nimport type { Lowlight } from './lowlightPlugin.js';\nimport { toHtml } from 'hast-util-to-html';\n\nexport interface GenerateHighlightedHTMLOptions {\n /** Default language for code blocks without a language. */\n defaultLanguage?: string;\n /** Auto-detect language when none specified. @default false */\n autoDetect?: boolean;\n /** Custom document implementation for generateHTML. */\n document?: Document;\n}\n\n/**\n * Generate HTML with syntax-highlighted code blocks.\n *\n * Unlike generateHTML(), this applies lowlight highlighting to code blocks,\n * producing `<span class=\"hljs-keyword\">` etc. inside `<code>` elements.\n *\n * @example\n * ```ts\n * import { generateHighlightedHTML } from '@domternal/extension-code-block-lowlight';\n * import { createLowlight, common } from 'lowlight';\n *\n * const lowlight = createLowlight(common);\n * const html = generateHighlightedHTML(json, extensions, lowlight);\n * ```\n */\nexport function generateHighlightedHTML(\n content: JSONContent,\n extensions: AnyExtension[],\n lowlight: Lowlight,\n options: GenerateHighlightedHTMLOptions = {},\n): string {\n const htmlOptions: GenerateHTMLOptions = {};\n if (options.document !== undefined) {\n htmlOptions.document = options.document;\n }\n\n const html = generateHTML(content, extensions, htmlOptions);\n\n return html.replace(\n /<pre([^>]*)><code(?:\\s+class=\"language-([^\"]*)\")?>([\\s\\S]*?)<\\/code><\\/pre>/g,\n (_match, preAttrs: string, language: string | undefined, code: string) => {\n // Unescape HTML entities in code content\n const decoded = code\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\");\n\n const lang = language ?? options.defaultLanguage ?? null;\n let highlighted: string;\n\n if (lang && lowlight.registered(lang)) {\n const result = lowlight.highlight(lang, decoded);\n highlighted = toHtml(result);\n } else if (options.autoDetect && decoded.length > 0) {\n const result = lowlight.highlightAuto(decoded);\n highlighted = toHtml(result);\n } else {\n const codeClass = language ? ` class=\"language-${language}\"` : '';\n return `<pre${preAttrs}><code${codeClass}>${code}</code></pre>`;\n }\n\n const codeClass = language ? ` class=\"language-${language}\"` : '';\n return `<pre${preAttrs}><code${codeClass}>${highlighted}</code></pre>`;\n },\n );\n}\n","import type { Lowlight } from './lowlightPlugin.js';\nimport { toHtml } from 'hast-util-to-html';\n\nexport interface CreateCodeHighlighterOptions {\n /** Default language when none specified on the code block. */\n defaultLanguage?: string;\n /** Auto-detect language when none specified. @default false */\n autoDetect?: boolean;\n}\n\n/**\n * Creates a `codeHighlighter` callback for use with `inlineStyles()`.\n *\n * @example\n * ```ts\n * import { createLowlight, common } from 'lowlight';\n * import { createCodeHighlighter } from '@domternal/extension-code-block-lowlight';\n * import { inlineStyles } from '@domternal/core';\n *\n * const lowlight = createLowlight(common);\n * const styled = inlineStyles(html, {\n * codeHighlighter: createCodeHighlighter(lowlight),\n * });\n * ```\n */\nexport function createCodeHighlighter(\n lowlight: Lowlight,\n options: CreateCodeHighlighterOptions = {},\n): (code: string, language: string | null) => string | null {\n return (code: string, language: string | null): string | null => {\n const lang = language ?? options.defaultLanguage ?? null;\n\n if (lang && lowlight.registered(lang)) {\n return toHtml(lowlight.highlight(lang, code));\n }\n\n if (options.autoDetect && code.length > 0) {\n return toHtml(lowlight.highlightAuto(code));\n }\n\n return null;\n };\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@domternal/extension-code-block-lowlight",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Code block with syntax highlighting via lowlight for Domternal editor",
|
|
5
|
+
"author": "https://github.com/ThomasNowHere",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=20"
|
|
11
|
+
},
|
|
12
|
+
"main": "./dist/index.cjs",
|
|
13
|
+
"module": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"import": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"require": {
|
|
22
|
+
"types": "./dist/index.d.cts",
|
|
23
|
+
"default": "./dist/index.cjs"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsup --clean",
|
|
32
|
+
"dev": "tsup --watch",
|
|
33
|
+
"test": "vitest run",
|
|
34
|
+
"test:watch": "vitest",
|
|
35
|
+
"lint": "eslint src",
|
|
36
|
+
"typecheck": "tsc --noEmit",
|
|
37
|
+
"prepublishOnly": "node -e \"const p=JSON.parse(require('fs').readFileSync('package.json','utf8'));delete p.devDependencies;if(p.dependencies){for(const[k,v]of Object.entries(p.dependencies)){if(v==='workspace:*')p.dependencies[k]='>=0.2.0'}}require('fs').writeFileSync('package.json',JSON.stringify(p,null,2)+'\\n')\"",
|
|
38
|
+
"postpublish": "git checkout package.json"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"hast-util-to-html": "^9.0.0"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"@domternal/core": ">=0.2.0",
|
|
45
|
+
"@domternal/pm": ">=0.2.0",
|
|
46
|
+
"lowlight": "^3.0.0"
|
|
47
|
+
},
|
|
48
|
+
"keywords": [
|
|
49
|
+
"prosemirror",
|
|
50
|
+
"editor",
|
|
51
|
+
"code-block",
|
|
52
|
+
"syntax-highlighting",
|
|
53
|
+
"lowlight",
|
|
54
|
+
"domternal"
|
|
55
|
+
],
|
|
56
|
+
"repository": {
|
|
57
|
+
"type": "git",
|
|
58
|
+
"url": "git+https://github.com/domternal/domternal.git",
|
|
59
|
+
"directory": "packages/extension-code-block-lowlight"
|
|
60
|
+
},
|
|
61
|
+
"bugs": {
|
|
62
|
+
"url": "https://github.com/domternal/domternal/issues"
|
|
63
|
+
},
|
|
64
|
+
"homepage": "https://domternal.dev"
|
|
65
|
+
}
|