@md-plugins/md-plugin-codeblocks 0.1.0-beta.17 → 0.1.0-beta.19
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 +14 -2
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +206 -27
- package/package.json +8 -6
package/README.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# md-plugin-codeblocks
|
|
2
2
|
|
|
3
|
-
A **Markdown-It** plugin that enhances code block rendering by providing syntax highlighting, line numbering, and support for advanced features like tabbed code blocks. It integrates with
|
|
3
|
+
A **Markdown-It** plugin that enhances code block rendering by providing syntax highlighting, line numbering, and support for advanced features like tabbed code blocks. It integrates with Shiki for syntax highlighting and allows customization for various use cases.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **Syntax Highlighting**: Automatically highlights code blocks using **
|
|
7
|
+
- **Syntax Highlighting**: Automatically highlights code blocks using **Shiki**.
|
|
8
|
+
- **TwoSlash Hovers**: Opt in to TypeScript-powered hover and query output for richer examples.
|
|
8
9
|
- **Line Numbering**: Optionally adds line numbers to code blocks.
|
|
9
10
|
- **Magic Comments**: Supports special comments like `[[! highlight]]`, `[[! add]]`, and `[[! rem]]` for inline code annotations.
|
|
10
11
|
- **Tabbed Code Blocks**: Enables the creation of tabbed code blocks for multi-language or multi-file examples.
|
|
@@ -101,6 +102,17 @@ console.log('Line 3')
|
|
|
101
102
|
```
|
|
102
103
|
````
|
|
103
104
|
|
|
105
|
+
### TwoSlash Type Hovers
|
|
106
|
+
|
|
107
|
+
Add the `twoslash` attribute to TypeScript or JavaScript examples when you want inferred type information, compiler diagnostics, or `^?` query output.
|
|
108
|
+
|
|
109
|
+
````markup
|
|
110
|
+
```ts [twoslash]
|
|
111
|
+
const count = 1
|
|
112
|
+
// ^?
|
|
113
|
+
```
|
|
114
|
+
````
|
|
115
|
+
|
|
104
116
|
### Line Highlighting and Annotations
|
|
105
117
|
|
|
106
118
|
````markup
|
package/dist/index.d.mts
CHANGED
|
@@ -46,7 +46,7 @@ interface CodeblockPluginOptions {
|
|
|
46
46
|
*/
|
|
47
47
|
pageScripts?: string[];
|
|
48
48
|
/**
|
|
49
|
-
* Optional
|
|
49
|
+
* Optional Shiki languages configuration array. This allows you to override or add custom language definitions.
|
|
50
50
|
* Each item can have a `name`, optional `aliases`, and `customCopy` boolean.
|
|
51
51
|
*/
|
|
52
52
|
langList?: Lang[];
|
package/dist/index.d.ts
CHANGED
|
@@ -46,7 +46,7 @@ interface CodeblockPluginOptions {
|
|
|
46
46
|
*/
|
|
47
47
|
pageScripts?: string[];
|
|
48
48
|
/**
|
|
49
|
-
* Optional
|
|
49
|
+
* Optional Shiki languages configuration array. This allows you to override or add custom language definitions.
|
|
50
50
|
* Each item can have a `name`, optional `aliases`, and `customCopy` boolean.
|
|
51
51
|
*/
|
|
52
52
|
langList?: Lang[];
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { addClassToHast } from 'shiki/core';
|
|
2
|
+
import { createHighlighter } from 'shiki';
|
|
3
|
+
import { transformerNotationHighlight, transformerNotationDiff, transformerNotationFocus, transformerNotationErrorLevel, transformerNotationWordHighlight } from '@shikijs/transformers';
|
|
3
4
|
|
|
4
5
|
function resolvePluginOptions(options, key, defaults) {
|
|
5
6
|
if (options && typeof options === "object" && key in options) {
|
|
@@ -8,11 +9,173 @@ function resolvePluginOptions(options, key, defaults) {
|
|
|
8
9
|
return { ...defaults, ...options };
|
|
9
10
|
}
|
|
10
11
|
|
|
12
|
+
const shikiLangs = [
|
|
13
|
+
"bash",
|
|
14
|
+
"css",
|
|
15
|
+
"diff",
|
|
16
|
+
"html",
|
|
17
|
+
"javascript",
|
|
18
|
+
"json",
|
|
19
|
+
"nginx",
|
|
20
|
+
"sass",
|
|
21
|
+
"scss",
|
|
22
|
+
"typescript",
|
|
23
|
+
"vue",
|
|
24
|
+
"xml",
|
|
25
|
+
"yaml"
|
|
26
|
+
];
|
|
27
|
+
const shikiLangAlias = {
|
|
28
|
+
js: "javascript",
|
|
29
|
+
markup: "html",
|
|
30
|
+
shell: "bash",
|
|
31
|
+
sh: "bash",
|
|
32
|
+
ts: "typescript",
|
|
33
|
+
yml: "yaml"
|
|
34
|
+
};
|
|
35
|
+
const highlighter = await createHighlighter({
|
|
36
|
+
themes: ["github-light", "github-dark"],
|
|
37
|
+
langs: shikiLangs,
|
|
38
|
+
langAlias: shikiLangAlias
|
|
39
|
+
});
|
|
40
|
+
const debugEnvValue = process.env.DEBUG;
|
|
41
|
+
delete process.env.DEBUG;
|
|
42
|
+
const twoslashModule = await import('@shikijs/twoslash').finally(() => {
|
|
43
|
+
if (debugEnvValue !== void 0) {
|
|
44
|
+
process.env.DEBUG = debugEnvValue;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
const { rendererRich, transformerTwoslash } = twoslashModule;
|
|
48
|
+
const supportedLangSet = new Set(highlighter.getLoadedLanguages());
|
|
49
|
+
const themeOptions = {
|
|
50
|
+
themes: {
|
|
51
|
+
light: "github-light",
|
|
52
|
+
dark: "github-dark"
|
|
53
|
+
},
|
|
54
|
+
defaultColor: false
|
|
55
|
+
};
|
|
56
|
+
function normalizeShikiLang(lang) {
|
|
57
|
+
return supportedLangSet.has(lang) ? lang : "text";
|
|
58
|
+
}
|
|
59
|
+
function buildCodeBlockTransformers({
|
|
60
|
+
codeClass,
|
|
61
|
+
lineList,
|
|
62
|
+
maxheight,
|
|
63
|
+
preClass,
|
|
64
|
+
twoslash
|
|
65
|
+
}) {
|
|
66
|
+
return [
|
|
67
|
+
preClassTransformer(preClass, maxheight),
|
|
68
|
+
codeClassTransformer(codeClass),
|
|
69
|
+
...twoslash === true ? [twoslashTransformer] : [],
|
|
70
|
+
transformerNotationHighlight(),
|
|
71
|
+
transformerNotationDiff(),
|
|
72
|
+
transformerNotationFocus(),
|
|
73
|
+
transformerNotationErrorLevel(),
|
|
74
|
+
transformerNotationWordHighlight(),
|
|
75
|
+
lineDecorTransformer(lineList)
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
const twoslashTransformer = transformerTwoslash({
|
|
79
|
+
renderer: rendererRich(),
|
|
80
|
+
twoslashOptions: {
|
|
81
|
+
compilerOptions: {
|
|
82
|
+
traceResolution: false
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
function preClassTransformer(preClass, maxheight) {
|
|
87
|
+
return {
|
|
88
|
+
name: "md-plugins:pre-class",
|
|
89
|
+
pre(node) {
|
|
90
|
+
addClassToHast(node, preClass);
|
|
91
|
+
if (maxheight !== void 0) {
|
|
92
|
+
const style = typeof node.properties?.style === "string" ? node.properties.style : "";
|
|
93
|
+
node.properties = {
|
|
94
|
+
...node.properties,
|
|
95
|
+
style: `${style}${style.length > 0 ? ";" : ""}max-height:${maxheight}`
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function codeClassTransformer(codeClass) {
|
|
102
|
+
return {
|
|
103
|
+
name: "md-plugins:code-class",
|
|
104
|
+
code(node) {
|
|
105
|
+
if (codeClass !== void 0 && codeClass.length > 0) {
|
|
106
|
+
addClassToHast(node, codeClass);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function lineDecorTransformer(lineList) {
|
|
112
|
+
return {
|
|
113
|
+
name: "md-plugins:line-decor",
|
|
114
|
+
code(codeNode) {
|
|
115
|
+
const lines = codeNode.children.filter(
|
|
116
|
+
(child) => child.type === "element" && child.tagName === "span" && lineHasClass(child, "line")
|
|
117
|
+
);
|
|
118
|
+
lines.forEach((line, index) => {
|
|
119
|
+
const target = lineList[index];
|
|
120
|
+
const classList = [...target?.classList ?? []];
|
|
121
|
+
const prefix = target?.prefix ?? [];
|
|
122
|
+
if (this.options.lang === "diff") {
|
|
123
|
+
const first = lineTextContent(line).charAt(0);
|
|
124
|
+
if (first === "+") {
|
|
125
|
+
classList.push("line-add");
|
|
126
|
+
} else if (first === "-") {
|
|
127
|
+
classList.push("line-rem");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (classList.length > 0) {
|
|
131
|
+
line.children?.unshift({
|
|
132
|
+
type: "element",
|
|
133
|
+
tagName: "span",
|
|
134
|
+
properties: { class: ["c-line", ...classList] },
|
|
135
|
+
children: []
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (prefix.length > 0) {
|
|
139
|
+
line.children?.unshift({
|
|
140
|
+
type: "element",
|
|
141
|
+
tagName: "span",
|
|
142
|
+
properties: { class: "c-lpref" },
|
|
143
|
+
children: [{ type: "text", value: prefix.join(" ") }]
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function lineHasClass(line, className) {
|
|
151
|
+
const classValue = line.properties?.class;
|
|
152
|
+
if (typeof classValue === "string") {
|
|
153
|
+
return classValue.split(/\s+/).includes(className);
|
|
154
|
+
}
|
|
155
|
+
if (Array.isArray(classValue)) {
|
|
156
|
+
return classValue.includes(className);
|
|
157
|
+
}
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
function lineTextContent(line) {
|
|
161
|
+
let acc = "";
|
|
162
|
+
const walk = (node) => {
|
|
163
|
+
if (node.type === "text") {
|
|
164
|
+
acc += node.value ?? "";
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
node.children?.forEach(walk);
|
|
168
|
+
};
|
|
169
|
+
line.children?.forEach(walk);
|
|
170
|
+
return acc;
|
|
171
|
+
}
|
|
172
|
+
|
|
11
173
|
const defaultLangList = [
|
|
12
174
|
{ name: "markup" },
|
|
13
175
|
{ name: "bash", customCopy: true },
|
|
14
176
|
{ name: "javascript", aliases: "javascript|js" },
|
|
15
177
|
{ name: "typescript", aliases: "typescript|ts" },
|
|
178
|
+
{ name: "yaml", aliases: "yaml|yml" },
|
|
16
179
|
{ name: "sass" },
|
|
17
180
|
{ name: "scss" },
|
|
18
181
|
{ name: "css" },
|
|
@@ -20,6 +183,7 @@ const defaultLangList = [
|
|
|
20
183
|
{ name: "xml" },
|
|
21
184
|
{ name: "nginx" },
|
|
22
185
|
{ name: "html" },
|
|
186
|
+
{ name: "vue" },
|
|
23
187
|
{ name: "diff" }
|
|
24
188
|
// special grammars
|
|
25
189
|
];
|
|
@@ -54,7 +218,6 @@ const codeblocksPlugin = (md, options) => {
|
|
|
54
218
|
pageScripts = DEFAULT_CODEBLOCK_PLUGIN_OPTIONS.pageScripts,
|
|
55
219
|
langList = defaultLangList
|
|
56
220
|
} = resolvedOptions;
|
|
57
|
-
loadLanguages(langList.map((l) => l.name));
|
|
58
221
|
const customCopyLangList = langList.filter((l) => l.customCopy === true).map((l) => l.name);
|
|
59
222
|
const langMatch = langList.map((l) => l.aliases || l.name).join("|");
|
|
60
223
|
const definitionLineRE = new RegExp(
|
|
@@ -71,11 +234,13 @@ const codeblocksPlugin = (md, options) => {
|
|
|
71
234
|
const tabsMatch = line.match(tabsLineRE);
|
|
72
235
|
if (tabsMatch !== null) {
|
|
73
236
|
const { lang, attrs, title } = tabsMatch.groups || {};
|
|
74
|
-
|
|
237
|
+
const bareAttrs = extractBareAttrs(title?.trim() || null);
|
|
238
|
+
currentTabName = bareAttrs.title ?? `Tab ${list.length + 1}`;
|
|
75
239
|
list.push(currentTabName);
|
|
76
240
|
tabMap[currentTabName] = {
|
|
77
241
|
attrs: {
|
|
78
242
|
...parseAttrs(attrs?.trim() || null),
|
|
243
|
+
...bareAttrs.attrs,
|
|
79
244
|
lang
|
|
80
245
|
},
|
|
81
246
|
content: []
|
|
@@ -94,6 +259,7 @@ const codeblocksPlugin = (md, options) => {
|
|
|
94
259
|
};
|
|
95
260
|
}
|
|
96
261
|
const magicCommentList = ["highlight", "rem", "add"];
|
|
262
|
+
const bareAttrList = ["twoslash"];
|
|
97
263
|
const magicCommentRE = new RegExp(` *\\[\\[! (?<type>(${magicCommentList.join("|")}))\\]\\] *`);
|
|
98
264
|
const magicCommentGlobalRE = new RegExp(magicCommentRE, "g");
|
|
99
265
|
function extractCodeLineProps(lines, attrs) {
|
|
@@ -152,35 +318,25 @@ const codeblocksPlugin = (md, options) => {
|
|
|
152
318
|
}
|
|
153
319
|
return acc;
|
|
154
320
|
}
|
|
155
|
-
function renderCodeBlock(html, codeClass2) {
|
|
156
|
-
return `<code${codeClass2 ? ` class="${codeClass2}"` : ""}>${html}</code>`;
|
|
157
|
-
}
|
|
158
|
-
function getPrismHighlightedContent(rawContent, lang) {
|
|
159
|
-
const content = rawContent.trim();
|
|
160
|
-
return prism.highlight(content, prism.languages[lang], lang);
|
|
161
|
-
}
|
|
162
321
|
function getHighlightedContent(rawContent, attrs) {
|
|
163
|
-
const { lang, maxheight } = attrs;
|
|
322
|
+
const { lang, maxheight, twoslash } = attrs;
|
|
164
323
|
let content = rawContent.trim();
|
|
165
324
|
const lineList = parseCodeLine(content, attrs);
|
|
166
325
|
if (lang !== "markup") {
|
|
167
326
|
content = content.trim().replace(magicCommentGlobalRE, "");
|
|
168
327
|
}
|
|
169
|
-
const html = getPrismHighlightedContent(content, lang).split("\n").map((line, lineIndex) => {
|
|
170
|
-
const target = lineList[lineIndex];
|
|
171
|
-
if (target === void 0) return line;
|
|
172
|
-
let lineHtml = "";
|
|
173
|
-
lineHtml += target.classList.length !== 0 ? `<span class="c-line ${target.classList.join(" ")}"></span>` : "";
|
|
174
|
-
lineHtml += target.prefix.length !== 0 ? `<span class="c-lpref">${target.prefix.join(" ")}</span>` : "";
|
|
175
|
-
lineHtml += line;
|
|
176
|
-
return lineHtml;
|
|
177
|
-
}).join("\n");
|
|
178
|
-
const preAttrs = maxheight !== void 0 ? ` style="max-height:${maxheight}"` : "";
|
|
179
328
|
const langProp = customCopyLangList.includes(lang) === true ? ` lang="${lang}"` : "";
|
|
180
|
-
return (
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
329
|
+
return highlighter.codeToHtml(content, {
|
|
330
|
+
lang: normalizeShikiLang(lang),
|
|
331
|
+
...themeOptions,
|
|
332
|
+
transformers: buildCodeBlockTransformers({
|
|
333
|
+
codeClass,
|
|
334
|
+
lineList,
|
|
335
|
+
maxheight,
|
|
336
|
+
preClass: preClass ?? "markdown-code",
|
|
337
|
+
twoslash: twoslash === true || twoslash === "true"
|
|
338
|
+
})
|
|
339
|
+
}).replace("<pre ", "<pre v-pre ") + `<${copyButtonComponent}${langProp} />`;
|
|
184
340
|
}
|
|
185
341
|
function parseAttrs(rawAttrs) {
|
|
186
342
|
if (rawAttrs === null) return {};
|
|
@@ -192,6 +348,27 @@ const codeblocksPlugin = (md, options) => {
|
|
|
192
348
|
}
|
|
193
349
|
return acc;
|
|
194
350
|
}
|
|
351
|
+
function extractBareAttrs(title) {
|
|
352
|
+
if (title === null) {
|
|
353
|
+
return {
|
|
354
|
+
attrs: {},
|
|
355
|
+
title: null
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
const attrs = {};
|
|
359
|
+
const remainingTitle = [];
|
|
360
|
+
for (const chunk of title.split(/\s+/)) {
|
|
361
|
+
if (remainingTitle.length === 0 && bareAttrList.includes(chunk)) {
|
|
362
|
+
attrs[chunk] = true;
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
remainingTitle.push(chunk);
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
attrs,
|
|
369
|
+
title: remainingTitle.length > 0 ? remainingTitle.join(" ") : null
|
|
370
|
+
};
|
|
371
|
+
}
|
|
195
372
|
function parseDefinitionLine(token) {
|
|
196
373
|
const match = token.info.trim().match(definitionLineRE);
|
|
197
374
|
if (match === null) {
|
|
@@ -201,10 +378,12 @@ const codeblocksPlugin = (md, options) => {
|
|
|
201
378
|
};
|
|
202
379
|
}
|
|
203
380
|
const { lang, attrs, title } = match.groups || {};
|
|
381
|
+
const bareAttrs = extractBareAttrs(title?.trim() || null);
|
|
204
382
|
const acc = {
|
|
205
383
|
...parseAttrs(attrs?.trim() || null),
|
|
384
|
+
...bareAttrs.attrs,
|
|
206
385
|
lang,
|
|
207
|
-
title: title
|
|
386
|
+
title: bareAttrs.title
|
|
208
387
|
};
|
|
209
388
|
if (acc.lang === "tabs") {
|
|
210
389
|
acc.tabs = extractTabs(token.content);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@md-plugins/md-plugin-codeblocks",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.19",
|
|
4
4
|
"description": "A markdown-it plugin for code blocks.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown-it",
|
|
@@ -35,16 +35,18 @@
|
|
|
35
35
|
"publishConfig": {
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@shikijs/transformers": "^4.1.0",
|
|
40
|
+
"@shikijs/twoslash": "^4.1.0",
|
|
41
|
+
"shiki": "^4.1.0"
|
|
42
|
+
},
|
|
38
43
|
"devDependencies": {
|
|
39
44
|
"@types/markdown-it": "^14.1.2",
|
|
40
|
-
"@types/prismjs": "^1.26.6",
|
|
41
45
|
"markdown-it": "^14.2.0",
|
|
42
|
-
"
|
|
43
|
-
"@md-plugins/shared": "0.1.0-beta.17"
|
|
46
|
+
"@md-plugins/shared": "0.1.0-beta.19"
|
|
44
47
|
},
|
|
45
48
|
"peerDependencies": {
|
|
46
|
-
"markdown-it": "^14.2.0"
|
|
47
|
-
"prismjs": "^1.29.0"
|
|
49
|
+
"markdown-it": "^14.2.0"
|
|
48
50
|
},
|
|
49
51
|
"scripts": {
|
|
50
52
|
"build": "unbuild",
|