@reslide-dev/mdx 0.0.1 → 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.
Files changed (3) hide show
  1. package/dist/index.d.mts +74 -3274
  2. package/dist/index.mjs +171 -81
  3. package/package.json +28 -27
package/dist/index.mjs CHANGED
@@ -1,7 +1,12 @@
1
- import remarkFrontmatter from "remark-frontmatter";
2
1
  import { visit } from "unist-util-visit";
3
2
  import { compile } from "@mdx-js/mdx";
3
+ import rehypeShiki from "@shikijs/rehype";
4
+ import { transformerNotationHighlight } from "@shikijs/transformers";
5
+ import rehypeKatex from "rehype-katex";
4
6
  import remarkDirective from "remark-directive";
7
+ import remarkFrontmatter from "remark-frontmatter";
8
+ import remarkGfm from "remark-gfm";
9
+ import remarkMath from "remark-math";
5
10
  //#region src/remark-slides.ts
6
11
  function parseYamlString(value) {
7
12
  const options = {};
@@ -40,11 +45,10 @@ function tryExtractOptionsFromNodes(nodes) {
40
45
  * Remark plugin that splits MDX content at `---` (thematic breaks)
41
46
  * into `<Slide>` components wrapped in a `<Deck>`.
42
47
  *
43
- * Internally enables remark-frontmatter for the first YAML block.
48
+ * Requires remark-frontmatter to be added before this plugin in the pipeline.
44
49
  * Subsequent slide frontmatters are detected via setext heading pattern.
45
50
  */
46
51
  function remarkSlides() {
47
- this.use(remarkFrontmatter, ["yaml"]);
48
52
  return (tree) => {
49
53
  const slides = [];
50
54
  let current = [];
@@ -62,7 +66,6 @@ function remarkSlides() {
62
66
  const content = slideContent.slice(contentStart);
63
67
  if (content.length === 0 && Object.keys(options).length === 0) continue;
64
68
  const clickCount = countClickDirectives(content);
65
- const slideIndex = slideElements.length;
66
69
  const attrs = [];
67
70
  if (options.layout) attrs.push({
68
71
  type: "mdxJsxAttribute",
@@ -79,14 +82,6 @@ function remarkSlides() {
79
82
  type: "mdxJsxFlowElement",
80
83
  name: "ClickSteps",
81
84
  attributes: [{
82
- type: "mdxJsxAttribute",
83
- name: "slideIndex",
84
- value: {
85
- type: "mdxJsxAttributeValueExpression",
86
- value: String(slideIndex),
87
- data: { estree: createNumericExpression(slideIndex) }
88
- }
89
- }, {
90
85
  type: "mdxJsxAttribute",
91
86
  name: "count",
92
87
  value: {
@@ -142,94 +137,127 @@ function createNumericExpression(value) {
142
137
  * Remark plugin that converts `::click` directives (from remark-directive)
143
138
  * into `<Click>` MDX JSX components.
144
139
  *
145
- * Supports both leaf (`::click`) and container (`::: click ... :::`) forms.
140
+ * For leaf directives (`::click`), all subsequent sibling nodes until the
141
+ * next `::click` or `---` are gathered as children of the `<Click>`.
142
+ *
143
+ * For container directives (`::: click ... :::`), children are wrapped directly.
144
+ *
146
145
  * Auto-increments the `at` attribute for sequential click steps.
147
146
  */
148
147
  function remarkClick() {
149
148
  return (tree) => {
150
- let stepCounter = 0;
151
- visit(tree, (node, index, parent) => {
152
- if (node.type !== "leafDirective" && node.type !== "containerDirective") return;
153
- if (node.name !== "click") return;
154
- if (index == null || !parent) return;
155
- stepCounter++;
156
- const attrs = [{
157
- type: "mdxJsxAttribute",
158
- name: "at",
159
- value: {
160
- type: "mdxJsxAttributeValueExpression",
161
- value: String(stepCounter),
162
- data: { estree: {
163
- type: "Program",
164
- sourceType: "module",
165
- body: [{
166
- type: "ExpressionStatement",
167
- expression: {
168
- type: "Literal",
169
- value: stepCounter,
170
- raw: String(stepCounter)
171
- }
172
- }]
173
- } }
174
- }
175
- }];
176
- if (node.type === "leafDirective") {
177
- const siblings = parent.children;
178
- const gathered = [];
179
- let i = index + 1;
180
- while (i < siblings.length) {
181
- const sibling = siblings[i];
182
- if ((sibling.type === "leafDirective" || sibling.type === "containerDirective") && sibling.name === "click") break;
183
- if (sibling.type === "thematicBreak") break;
184
- gathered.push(siblings[i]);
185
- i++;
186
- }
187
- if (gathered.length > 0) siblings.splice(index + 1, gathered.length);
188
- parent.children[index] = {
189
- type: "mdxJsxFlowElement",
190
- name: "Click",
191
- attributes: attrs,
192
- children: gathered
193
- };
194
- return;
195
- }
196
- parent.children[index] = {
149
+ processNode(tree);
150
+ };
151
+ }
152
+ function isClickDirective(node) {
153
+ return (node.type === "leafDirective" || node.type === "containerDirective") && "name" in node && node.name === "click";
154
+ }
155
+ function processNode(node) {
156
+ for (const child of node.children) if ("children" in child && Array.isArray(child.children)) processNode(child);
157
+ const newChildren = [];
158
+ let stepCounter = 0;
159
+ let i = 0;
160
+ while (i < node.children.length) {
161
+ const child = node.children[i];
162
+ if (!isClickDirective(child)) {
163
+ newChildren.push(child);
164
+ i++;
165
+ continue;
166
+ }
167
+ stepCounter++;
168
+ if (child.type === "containerDirective") {
169
+ newChildren.push({
197
170
  type: "mdxJsxFlowElement",
198
171
  name: "Click",
199
- attributes: attrs,
200
- children: node.children
201
- };
172
+ attributes: [createAtAttribute(stepCounter)],
173
+ children: "children" in child ? child.children : []
174
+ });
175
+ i++;
176
+ continue;
177
+ }
178
+ const gathered = [];
179
+ i++;
180
+ while (i < node.children.length) {
181
+ const sibling = node.children[i];
182
+ if (isClickDirective(sibling)) break;
183
+ if (sibling.type === "thematicBreak") break;
184
+ gathered.push(sibling);
185
+ i++;
186
+ }
187
+ newChildren.push({
188
+ type: "mdxJsxFlowElement",
189
+ name: "Click",
190
+ attributes: [createAtAttribute(stepCounter)],
191
+ children: gathered
202
192
  });
193
+ }
194
+ node.children = newChildren;
195
+ }
196
+ function createAtAttribute(step) {
197
+ return {
198
+ type: "mdxJsxAttribute",
199
+ name: "at",
200
+ value: {
201
+ type: "mdxJsxAttributeValueExpression",
202
+ value: String(step),
203
+ data: { estree: {
204
+ type: "Program",
205
+ sourceType: "module",
206
+ body: [{
207
+ type: "ExpressionStatement",
208
+ expression: {
209
+ type: "Literal",
210
+ value: step,
211
+ raw: String(step)
212
+ }
213
+ }]
214
+ } }
215
+ }
203
216
  };
204
217
  }
205
218
  //#endregion
206
219
  //#region src/remark-mark.ts
220
+ const MARK_TYPES = [
221
+ "highlight",
222
+ "underline",
223
+ "circle"
224
+ ];
207
225
  /**
208
- * Remark plugin that converts `:mark[text]{.style}` directives
209
- * into `<Mark>` MDX JSX components.
226
+ * Remark plugin that converts mark directives into `<Mark>` MDX JSX components.
210
227
  *
211
- * Supports styles: highlight, underline, circle
212
- * Supports color via additional class: .orange, .red, .blue, etc.
228
+ * Supports two syntax forms (both avoid `{}` for MDX compatibility):
213
229
  *
214
- * Example: `:mark[important text]{.highlight.orange}`
215
- * Produces: `<Mark type="highlight" color="orange">important text</Mark>`
230
+ * 1. Type-based directives — the directive name IS the mark type:
231
+ * `:highlight[text]` → `<Mark type="highlight">text</Mark>`
232
+ * `:underline[text]` → `<Mark type="underline">text</Mark>`
233
+ * `:circle[text]` → `<Mark type="circle">text</Mark>`
234
+ *
235
+ * 2. Type + color via hyphen:
236
+ * `:highlight-yellow[text]` → `<Mark type="highlight" color="yellow">`
237
+ * `:underline-blue[text]` → `<Mark type="underline" color="blue">`
238
+ * `:circle-red[text]` → `<Mark type="circle" color="red">`
239
+ *
240
+ * 3. Legacy `:mark[text]{.type.color}` syntax (works in non-MDX contexts):
241
+ * `:mark[important]{.highlight.orange}` → `<Mark type="highlight" color="orange">`
216
242
  */
217
243
  function remarkMark() {
218
244
  return (tree) => {
219
245
  visit(tree, (node, index, parent) => {
220
246
  if (node.type !== "textDirective") return;
221
- if (node.name !== "mark") return;
222
247
  if (index == null || !parent) return;
223
- const classes = (node.attributes?.class ?? "").split(/\s+/).filter(Boolean);
224
- const markTypes = [
225
- "highlight",
226
- "underline",
227
- "circle"
228
- ];
229
- let type = "highlight";
248
+ let type;
230
249
  let color;
231
- for (const cls of classes) if (markTypes.includes(cls)) type = cls;
232
- else if (cls) color = cls;
250
+ if (node.name === "mark") {
251
+ const classes = (node.attributes?.class ?? "").split(/\s+/).filter(Boolean);
252
+ for (const cls of classes) if (MARK_TYPES.includes(cls)) type = cls;
253
+ else if (cls) color = cls;
254
+ type ??= "highlight";
255
+ } else {
256
+ const parsed = parseDirectiveName(node.name);
257
+ if (!parsed) return;
258
+ type = parsed.type;
259
+ color = parsed.color;
260
+ }
233
261
  const attrs = [{
234
262
  type: "mdxJsxAttribute",
235
263
  name: "type",
@@ -249,6 +277,57 @@ function remarkMark() {
249
277
  });
250
278
  };
251
279
  }
280
+ function parseDirectiveName(name) {
281
+ if (MARK_TYPES.includes(name)) return { type: name };
282
+ for (const markType of MARK_TYPES) if (name.startsWith(markType + "-")) {
283
+ const color = name.slice(markType.length + 1);
284
+ if (color) return {
285
+ type: markType,
286
+ color
287
+ };
288
+ }
289
+ return null;
290
+ }
291
+ //#endregion
292
+ //#region src/remark-directive-fallback.ts
293
+ function isDirectiveNode(node) {
294
+ if (typeof node !== "object" || node === null) return false;
295
+ const n = node;
296
+ return n.type === "textDirective" || n.type === "leafDirective";
297
+ }
298
+ function directiveToText(node) {
299
+ let text = `${node.type === "leafDirective" ? "::" : ":"}${node.name}`;
300
+ if (node.children.length > 0) {
301
+ const content = node.children.map((c) => c.value ?? "").join("");
302
+ if (content) text += `[${content}]`;
303
+ }
304
+ if (node.attributes && Object.keys(node.attributes).length > 0) {
305
+ const attrs = Object.entries(node.attributes).map(([k, v]) => v ? `${k}="${v}"` : k).join(" ");
306
+ text += `{${attrs}}`;
307
+ }
308
+ return text;
309
+ }
310
+ /**
311
+ * Remark plugin that converts remaining (unprocessed) directive nodes
312
+ * back to plain text. This prevents `remark-directive` from eating
313
+ * colon-prefixed patterns like time notations (13:00), port numbers
314
+ * (localhost:3000), or unknown directives.
315
+ *
316
+ * Should be placed **after** `remarkClick` and `remarkMark` in the
317
+ * plugin pipeline so that it only touches leftovers.
318
+ */
319
+ function remarkDirectiveFallback() {
320
+ return (tree) => {
321
+ visit(tree, (node, index, parent) => {
322
+ if (!isDirectiveNode(node)) return;
323
+ if (!parent || typeof index !== "number") return;
324
+ parent.children[index] = {
325
+ type: "text",
326
+ value: directiveToText(node)
327
+ };
328
+ });
329
+ };
330
+ }
252
331
  //#endregion
253
332
  //#region src/compile.ts
254
333
  /**
@@ -279,12 +358,23 @@ async function compileMdxSlides(source, options) {
279
358
  outputFormat: "function-body",
280
359
  remarkPlugins: [
281
360
  remarkDirective,
361
+ remarkGfm,
362
+ [remarkFrontmatter, ["yaml"]],
363
+ remarkMath,
282
364
  remarkSlides,
283
365
  remarkClick,
284
366
  remarkMark,
367
+ remarkDirectiveFallback,
285
368
  ...options?.remarkPlugins ?? []
286
369
  ],
287
- rehypePlugins: options?.rehypePlugins ?? [],
370
+ rehypePlugins: [
371
+ rehypeKatex,
372
+ [rehypeShiki, {
373
+ theme: "github-dark",
374
+ transformers: [transformerNotationHighlight()]
375
+ }],
376
+ ...options?.rehypePlugins ?? []
377
+ ],
288
378
  providerImportSource: void 0
289
379
  });
290
380
  return {
@@ -307,4 +397,4 @@ function countSlides(source) {
307
397
  return Math.max(1, parts.filter((p) => p.trim().length > 0).length);
308
398
  }
309
399
  //#endregion
310
- export { compileMdxSlides, parseSlideMetadata, remarkClick, remarkMark, remarkSlides };
400
+ export { compileMdxSlides, parseSlideMetadata, remarkClick, remarkDirectiveFallback, remarkMark, remarkSlides };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reslide-dev/mdx",
3
- "version": "0.0.1",
3
+ "version": "0.2.0",
4
4
  "description": "Remark/rehype plugins for reslide MDX preprocessing",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -11,39 +11,40 @@
11
11
  ".": "./dist/index.mjs",
12
12
  "./package.json": "./package.json"
13
13
  },
14
- "scripts": {
15
- "build": "vp pack",
16
- "dev": "vp pack --watch",
17
- "test": "vp test"
14
+ "publishConfig": {
15
+ "access": "public"
18
16
  },
19
17
  "dependencies": {
20
- "@mdx-js/mdx": "^3.1.1",
21
- "remark-frontmatter": "^5.0.0",
22
- "unist-util-visit": "^5.0.0"
18
+ "@mdx-js/mdx": "3.1.1",
19
+ "@shikijs/rehype": "4.0.2",
20
+ "@shikijs/transformers": "4.0.2",
21
+ "rehype-katex": "7.0.1",
22
+ "remark-frontmatter": "5.0.0",
23
+ "remark-gfm": "4.0.1",
24
+ "remark-math": "6.0.0",
25
+ "unist-util-visit": "5.1.0"
23
26
  },
24
27
  "devDependencies": {
25
- "@types/mdast": "catalog:",
26
- "@types/unist": "catalog:",
27
- "remark": "^15.0.0",
28
- "remark-directive": "^3.0.0",
29
- "remark-frontmatter": "catalog:",
30
- "remark-mdx": "^3.1.0",
31
- "unified": "catalog:",
32
- "vite-plus": "catalog:",
33
- "vitest": "catalog:"
28
+ "@types/mdast": "4.0.4",
29
+ "@types/unist": "3.0.3",
30
+ "remark": "15.0.1",
31
+ "remark-directive": "4.0.0",
32
+ "remark-frontmatter": "5.0.0",
33
+ "remark-mdx": "3.1.1",
34
+ "unified": "11.0.5",
35
+ "vite-plus": "latest",
36
+ "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
34
37
  },
35
38
  "peerDependencies": {
36
- "remark-directive": "^3.0.0"
37
- },
38
- "publishConfig": {
39
- "access": "public"
39
+ "remark-directive": "4.0.0"
40
40
  },
41
41
  "inlinedDependencies": {
42
42
  "@types/mdast": "4.0.4",
43
- "@types/unist": "3.0.3",
44
- "trough": "2.2.0",
45
- "unified": "11.0.5",
46
- "vfile": "6.0.3",
47
- "vfile-message": "4.0.3"
43
+ "@types/unist": "3.0.3"
44
+ },
45
+ "scripts": {
46
+ "build": "vp pack",
47
+ "dev": "vp pack --watch",
48
+ "test": "vp test"
48
49
  }
49
- }
50
+ }