@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.
- package/dist/index.d.mts +74 -3274
- package/dist/index.mjs +171 -81
- 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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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:
|
|
200
|
-
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
|
|
209
|
-
* into `<Mark>` MDX JSX components.
|
|
226
|
+
* Remark plugin that converts mark directives into `<Mark>` MDX JSX components.
|
|
210
227
|
*
|
|
211
|
-
* Supports
|
|
212
|
-
* Supports color via additional class: .orange, .red, .blue, etc.
|
|
228
|
+
* Supports two syntax forms (both avoid `{}` for MDX compatibility):
|
|
213
229
|
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
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
|
-
|
|
224
|
-
const markTypes = [
|
|
225
|
-
"highlight",
|
|
226
|
-
"underline",
|
|
227
|
-
"circle"
|
|
228
|
-
];
|
|
229
|
-
let type = "highlight";
|
|
248
|
+
let type;
|
|
230
249
|
let color;
|
|
231
|
-
|
|
232
|
-
|
|
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:
|
|
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
|
|
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
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"dev": "vp pack --watch",
|
|
17
|
-
"test": "vp test"
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
18
16
|
},
|
|
19
17
|
"dependencies": {
|
|
20
|
-
"@mdx-js/mdx": "
|
|
21
|
-
"
|
|
22
|
-
"
|
|
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": "
|
|
26
|
-
"@types/unist": "
|
|
27
|
-
"remark": "
|
|
28
|
-
"remark-directive": "
|
|
29
|
-
"remark-frontmatter": "
|
|
30
|
-
"remark-mdx": "
|
|
31
|
-
"unified": "
|
|
32
|
-
"vite-plus": "
|
|
33
|
-
"vitest": "
|
|
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": "
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
"
|
|
47
|
-
"
|
|
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
|
+
}
|