@md-plugins/md-plugin-codeblocks 0.1.0-beta.2 → 0.1.0-beta.20

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
@@ -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 Prism.js for syntax highlighting and allows customization for various use cases.
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 **Prism.js**.
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.
@@ -18,6 +19,8 @@ Install the plugin via your preferred package manager:
18
19
  ```bash
19
20
  # with pnpm:
20
21
  pnpm add @md-plugins/md-plugin-codeblocks
22
+ # with bun:
23
+ bun add @md-plugins/md-plugin-codeblocks
21
24
  # with Yarn:
22
25
  yarn add @md-plugins/md-plugin-codeblocks
23
26
  # with npm:
@@ -38,8 +41,8 @@ md.use(codeblocksPlugin, {
38
41
  copyButtonComponent: '<MarkdownCopyButton',
39
42
  preClass: 'markdown-code',
40
43
  pageScripts: [
41
- "import MarkdownPrerender from 'src/.q-press/components/MarkdownPrerender'",
42
- "import MarkdownCopyButton from 'src/.q-press/components/MarkdownCopyButton.vue'",
44
+ "import MarkdownPrerender from '@/.q-press/components/MarkdownPrerender'",
45
+ "import MarkdownCopyButton from '@/.q-press/components/MarkdownCopyButton.vue'",
43
46
  ],
44
47
  })
45
48
  ```
@@ -99,6 +102,17 @@ console.log('Line 3')
99
102
  ```
100
103
  ````
101
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
+
102
116
  ### Line Highlighting and Annotations
103
117
 
104
118
  ````markup
@@ -177,6 +191,13 @@ pnpm test
177
191
 
178
192
  In case this README falls out of date, please refer to the [documentation](https://md-plugins.netlify.app/md-plugins/codeblocks/overview) for the latest information.
179
193
 
194
+ ## Support
195
+
196
+ If md-plugin-codeblocks is useful in your workflow and you want to support ongoing maintenance:
197
+
198
+ GitHub Sponsors: https://github.com/sponsors/hawkeye64
199
+ PayPal: https://paypal.me/hawkeye64
200
+
180
201
  ## License
181
202
 
182
203
  This project is licensed under the MIT License. See the [LICENSE](LICENSE.md) file for details.
package/dist/index.d.mts CHANGED
@@ -46,7 +46,7 @@ interface CodeblockPluginOptions {
46
46
  */
47
47
  pageScripts?: string[];
48
48
  /**
49
- * Optional Prism languages configuration array. This allows you to override or add custom language definitions.
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 Prism languages configuration array. This allows you to override or add custom language definitions.
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 prism from 'prismjs';
2
- import loadLanguages from 'prismjs/components/index.js';
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,175 @@ 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
+ queryRendering: "line"
81
+ }),
82
+ twoslashOptions: {
83
+ compilerOptions: {
84
+ traceResolution: false
85
+ }
86
+ }
87
+ });
88
+ function preClassTransformer(preClass, maxheight) {
89
+ return {
90
+ name: "md-plugins:pre-class",
91
+ pre(node) {
92
+ addClassToHast(node, preClass);
93
+ if (maxheight !== void 0) {
94
+ const style = typeof node.properties?.style === "string" ? node.properties.style : "";
95
+ node.properties = {
96
+ ...node.properties,
97
+ style: `${style}${style.length > 0 ? ";" : ""}max-height:${maxheight}`
98
+ };
99
+ }
100
+ }
101
+ };
102
+ }
103
+ function codeClassTransformer(codeClass) {
104
+ return {
105
+ name: "md-plugins:code-class",
106
+ code(node) {
107
+ if (codeClass !== void 0 && codeClass.length > 0) {
108
+ addClassToHast(node, codeClass);
109
+ }
110
+ }
111
+ };
112
+ }
113
+ function lineDecorTransformer(lineList) {
114
+ return {
115
+ name: "md-plugins:line-decor",
116
+ code(codeNode) {
117
+ const lines = codeNode.children.filter(
118
+ (child) => child.type === "element" && child.tagName === "span" && lineHasClass(child, "line")
119
+ );
120
+ lines.forEach((line, index) => {
121
+ const target = lineList[index];
122
+ const classList = [...target?.classList ?? []];
123
+ const prefix = target?.prefix ?? [];
124
+ if (this.options.lang === "diff") {
125
+ const first = lineTextContent(line).charAt(0);
126
+ if (first === "+") {
127
+ classList.push("line-add");
128
+ } else if (first === "-") {
129
+ classList.push("line-rem");
130
+ }
131
+ }
132
+ if (classList.length > 0) {
133
+ line.children?.unshift({
134
+ type: "element",
135
+ tagName: "span",
136
+ properties: { class: ["c-line", ...classList] },
137
+ children: []
138
+ });
139
+ }
140
+ if (prefix.length > 0) {
141
+ line.children?.unshift({
142
+ type: "element",
143
+ tagName: "span",
144
+ properties: { class: "c-lpref" },
145
+ children: [{ type: "text", value: prefix.join(" ") }]
146
+ });
147
+ }
148
+ });
149
+ }
150
+ };
151
+ }
152
+ function lineHasClass(line, className) {
153
+ const classValue = line.properties?.class;
154
+ if (typeof classValue === "string") {
155
+ return classValue.split(/\s+/).includes(className);
156
+ }
157
+ if (Array.isArray(classValue)) {
158
+ return classValue.includes(className);
159
+ }
160
+ return false;
161
+ }
162
+ function lineTextContent(line) {
163
+ let acc = "";
164
+ const walk = (node) => {
165
+ if (node.type === "text") {
166
+ acc += node.value ?? "";
167
+ return;
168
+ }
169
+ node.children?.forEach(walk);
170
+ };
171
+ line.children?.forEach(walk);
172
+ return acc;
173
+ }
174
+
11
175
  const defaultLangList = [
12
176
  { name: "markup" },
13
177
  { name: "bash", customCopy: true },
14
178
  { name: "javascript", aliases: "javascript|js" },
15
179
  { name: "typescript", aliases: "typescript|ts" },
180
+ { name: "yaml", aliases: "yaml|yml" },
16
181
  { name: "sass" },
17
182
  { name: "scss" },
18
183
  { name: "css" },
@@ -20,6 +185,7 @@ const defaultLangList = [
20
185
  { name: "xml" },
21
186
  { name: "nginx" },
22
187
  { name: "html" },
188
+ { name: "vue" },
23
189
  { name: "diff" }
24
190
  // special grammars
25
191
  ];
@@ -32,8 +198,8 @@ const DEFAULT_CODEBLOCK_PLUGIN_OPTIONS = {
32
198
  tabPanelTagName: "q-tab-panel",
33
199
  tabPanelTagClass: "q-pa-none",
34
200
  pageScripts: [
35
- "import MarkdownPrerender from 'src/.q-press/components/MarkdownPrerender'",
36
- "import MarkdownCopyButton from 'src/.q-press/components/MarkdownCopyButton.vue'"
201
+ "import MarkdownPrerender from '@/.q-press/components/MarkdownPrerender'",
202
+ "import MarkdownCopyButton from '@/.q-press/components/MarkdownCopyButton.vue'"
37
203
  ],
38
204
  langList: defaultLangList
39
205
  };
@@ -54,7 +220,6 @@ const codeblocksPlugin = (md, options) => {
54
220
  pageScripts = DEFAULT_CODEBLOCK_PLUGIN_OPTIONS.pageScripts,
55
221
  langList = defaultLangList
56
222
  } = resolvedOptions;
57
- loadLanguages(langList.map((l) => l.name));
58
223
  const customCopyLangList = langList.filter((l) => l.customCopy === true).map((l) => l.name);
59
224
  const langMatch = langList.map((l) => l.aliases || l.name).join("|");
60
225
  const definitionLineRE = new RegExp(
@@ -71,11 +236,13 @@ const codeblocksPlugin = (md, options) => {
71
236
  const tabsMatch = line.match(tabsLineRE);
72
237
  if (tabsMatch !== null) {
73
238
  const { lang, attrs, title } = tabsMatch.groups || {};
74
- currentTabName = title?.trim() || `Tab ${list.length + 1}`;
239
+ const bareAttrs = extractBareAttrs(title?.trim() || null);
240
+ currentTabName = bareAttrs.title ?? `Tab ${list.length + 1}`;
75
241
  list.push(currentTabName);
76
242
  tabMap[currentTabName] = {
77
243
  attrs: {
78
244
  ...parseAttrs(attrs?.trim() || null),
245
+ ...bareAttrs.attrs,
79
246
  lang
80
247
  },
81
248
  content: []
@@ -94,6 +261,7 @@ const codeblocksPlugin = (md, options) => {
94
261
  };
95
262
  }
96
263
  const magicCommentList = ["highlight", "rem", "add"];
264
+ const bareAttrList = ["twoslash"];
97
265
  const magicCommentRE = new RegExp(` *\\[\\[! (?<type>(${magicCommentList.join("|")}))\\]\\] *`);
98
266
  const magicCommentGlobalRE = new RegExp(magicCommentRE, "g");
99
267
  function extractCodeLineProps(lines, attrs) {
@@ -152,35 +320,25 @@ const codeblocksPlugin = (md, options) => {
152
320
  }
153
321
  return acc;
154
322
  }
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
323
  function getHighlightedContent(rawContent, attrs) {
163
- const { lang, maxheight } = attrs;
324
+ const { lang, maxheight, twoslash } = attrs;
164
325
  let content = rawContent.trim();
165
326
  const lineList = parseCodeLine(content, attrs);
166
327
  if (lang !== "markup") {
167
328
  content = content.trim().replace(magicCommentGlobalRE, "");
168
329
  }
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
330
  const langProp = customCopyLangList.includes(lang) === true ? ` lang="${lang}"` : "";
180
- return (
181
- // `<pre v-pre class="${preClass}${langClass}"${preAttrs}>` +
182
- `<pre v-pre class="${preClass}"${preAttrs}>` + renderCodeBlock(html, codeClass) + `</pre><${copyButtonComponent}${langProp} />`
183
- );
331
+ return highlighter.codeToHtml(content, {
332
+ lang: normalizeShikiLang(lang),
333
+ ...themeOptions,
334
+ transformers: buildCodeBlockTransformers({
335
+ codeClass,
336
+ lineList,
337
+ maxheight,
338
+ preClass: preClass ?? "markdown-code",
339
+ twoslash: twoslash === true || twoslash === "true"
340
+ })
341
+ }).replace("<pre ", "<pre v-pre ") + `<${copyButtonComponent}${langProp} />`;
184
342
  }
185
343
  function parseAttrs(rawAttrs) {
186
344
  if (rawAttrs === null) return {};
@@ -192,6 +350,27 @@ const codeblocksPlugin = (md, options) => {
192
350
  }
193
351
  return acc;
194
352
  }
353
+ function extractBareAttrs(title) {
354
+ if (title === null) {
355
+ return {
356
+ attrs: {},
357
+ title: null
358
+ };
359
+ }
360
+ const attrs = {};
361
+ const remainingTitle = [];
362
+ for (const chunk of title.split(/\s+/)) {
363
+ if (remainingTitle.length === 0 && bareAttrList.includes(chunk)) {
364
+ attrs[chunk] = true;
365
+ continue;
366
+ }
367
+ remainingTitle.push(chunk);
368
+ }
369
+ return {
370
+ attrs,
371
+ title: remainingTitle.length > 0 ? remainingTitle.join(" ") : null
372
+ };
373
+ }
195
374
  function parseDefinitionLine(token) {
196
375
  const match = token.info.trim().match(definitionLineRE);
197
376
  if (match === null) {
@@ -201,10 +380,12 @@ const codeblocksPlugin = (md, options) => {
201
380
  };
202
381
  }
203
382
  const { lang, attrs, title } = match.groups || {};
383
+ const bareAttrs = extractBareAttrs(title?.trim() || null);
204
384
  const acc = {
205
385
  ...parseAttrs(attrs?.trim() || null),
386
+ ...bareAttrs.attrs,
206
387
  lang,
207
- title: title?.trim() || null
388
+ title: bareAttrs.title
208
389
  };
209
390
  if (acc.lang === "tabs") {
210
391
  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.2",
3
+ "version": "0.1.0-beta.20",
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
- "markdown-it": "^14.1.1",
42
- "prismjs": "^1.30.0",
43
- "@md-plugins/shared": "0.1.0-beta.2"
45
+ "markdown-it": "^14.2.0",
46
+ "@md-plugins/shared": "0.1.0-beta.20"
44
47
  },
45
48
  "peerDependencies": {
46
- "markdown-it": "^14.1.0",
47
- "prismjs": "^1.29.0"
49
+ "markdown-it": "^14.2.0"
48
50
  },
49
51
  "scripts": {
50
52
  "build": "unbuild",