@ncukondo/slide-generation 0.1.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/CHANGELOG.md +56 -0
- package/LICENSE +21 -0
- package/README.md +248 -0
- package/README_ja.md +248 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +2994 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +999 -0
- package/dist/index.js +1469 -0
- package/dist/index.js.map +1 -0
- package/icons/custom/.gitkeep +3 -0
- package/icons/registry.yaml +77 -0
- package/package.json +84 -0
- package/templates/basic/bullet-list.yaml +39 -0
- package/templates/basic/numbered-list.yaml +39 -0
- package/templates/basic/section.yaml +26 -0
- package/templates/basic/title.yaml +46 -0
- package/templates/data/comparison-table.yaml +107 -0
- package/templates/data/table.yaml +94 -0
- package/templates/diagrams/cycle-diagram.yaml +71 -0
- package/templates/diagrams/flow-chart.yaml +141 -0
- package/templates/diagrams/hierarchy.yaml +117 -0
- package/templates/diagrams/matrix.yaml +163 -0
- package/templates/diagrams/timeline.yaml +167 -0
- package/templates/layouts/gallery.yaml +94 -0
- package/templates/layouts/image-text.yaml +105 -0
- package/templates/layouts/three-column.yaml +101 -0
- package/templates/layouts/two-column.yaml +85 -0
- package/templates/special/bibliography.yaml +132 -0
- package/templates/special/code-block.yaml +79 -0
- package/templates/special/custom.yaml +45 -0
- package/templates/special/quote.yaml +76 -0
- package/themes/default.css +122 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1469 @@
|
|
|
1
|
+
// src/core/parser.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { parse as parseYaml } from "yaml";
|
|
4
|
+
import { readFile } from "fs/promises";
|
|
5
|
+
var referencesConfigSchema = z.object({
|
|
6
|
+
enabled: z.boolean().default(true),
|
|
7
|
+
style: z.string().default("author-year-pmid")
|
|
8
|
+
});
|
|
9
|
+
var metaSchema = z.object({
|
|
10
|
+
title: z.string(),
|
|
11
|
+
author: z.string().optional(),
|
|
12
|
+
date: z.string().optional(),
|
|
13
|
+
theme: z.string().default("default"),
|
|
14
|
+
references: referencesConfigSchema.optional()
|
|
15
|
+
});
|
|
16
|
+
var slideSchema = z.object({
|
|
17
|
+
template: z.string(),
|
|
18
|
+
content: z.record(z.unknown()).default({}),
|
|
19
|
+
class: z.string().optional(),
|
|
20
|
+
notes: z.string().optional(),
|
|
21
|
+
raw: z.string().optional()
|
|
22
|
+
});
|
|
23
|
+
var presentationSchema = z.object({
|
|
24
|
+
meta: metaSchema,
|
|
25
|
+
slides: z.array(slideSchema).default([])
|
|
26
|
+
});
|
|
27
|
+
var ParseError = class extends Error {
|
|
28
|
+
constructor(message, details) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.details = details;
|
|
31
|
+
this.name = "ParseError";
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
var ValidationError = class extends Error {
|
|
35
|
+
constructor(message, details) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.details = details;
|
|
38
|
+
this.name = "ValidationError";
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
var Parser = class {
|
|
42
|
+
parse(yamlContent) {
|
|
43
|
+
let rawData;
|
|
44
|
+
try {
|
|
45
|
+
rawData = parseYaml(yamlContent);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
throw new ParseError("Failed to parse YAML", error);
|
|
48
|
+
}
|
|
49
|
+
const result = presentationSchema.safeParse(rawData);
|
|
50
|
+
if (!result.success) {
|
|
51
|
+
throw new ValidationError(
|
|
52
|
+
"Schema validation failed",
|
|
53
|
+
result.error.format()
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
return result.data;
|
|
57
|
+
}
|
|
58
|
+
async parseFile(filePath) {
|
|
59
|
+
let content;
|
|
60
|
+
try {
|
|
61
|
+
content = await readFile(filePath, "utf-8");
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (error.code === "ENOENT") {
|
|
64
|
+
throw new ParseError(`File not found: ${filePath}`);
|
|
65
|
+
}
|
|
66
|
+
throw new ParseError(`Failed to read file: ${filePath}`, error);
|
|
67
|
+
}
|
|
68
|
+
return this.parse(content);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// src/core/transformer.ts
|
|
73
|
+
var ICON_PLACEHOLDER_PREFIX = "___ICON_PLACEHOLDER_";
|
|
74
|
+
var ICON_PLACEHOLDER_SUFFIX = "___";
|
|
75
|
+
var REFS_CITE_PLACEHOLDER_PREFIX = "___REFS_CITE_PLACEHOLDER_";
|
|
76
|
+
var REFS_CITE_PLACEHOLDER_SUFFIX = "___";
|
|
77
|
+
var REFS_EXPAND_PLACEHOLDER_PREFIX = "___REFS_EXPAND_PLACEHOLDER_";
|
|
78
|
+
var REFS_EXPAND_PLACEHOLDER_SUFFIX = "___";
|
|
79
|
+
var TransformError = class extends Error {
|
|
80
|
+
constructor(message, slide, details) {
|
|
81
|
+
super(message);
|
|
82
|
+
this.slide = slide;
|
|
83
|
+
this.details = details;
|
|
84
|
+
this.name = "TransformError";
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
var Transformer = class {
|
|
88
|
+
constructor(templateEngine, templateLoader, iconResolver, citationFormatter) {
|
|
89
|
+
this.templateEngine = templateEngine;
|
|
90
|
+
this.templateLoader = templateLoader;
|
|
91
|
+
this.iconResolver = iconResolver;
|
|
92
|
+
this.citationFormatter = citationFormatter;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Transform a single slide using its template
|
|
96
|
+
*/
|
|
97
|
+
async transform(slide, context) {
|
|
98
|
+
if (slide.template === "raw") {
|
|
99
|
+
return slide.raw ?? "";
|
|
100
|
+
}
|
|
101
|
+
const template = this.templateLoader.get(slide.template);
|
|
102
|
+
if (!template) {
|
|
103
|
+
throw new TransformError(
|
|
104
|
+
`Template "${slide.template}" not found`,
|
|
105
|
+
slide
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
const validationResult = this.templateLoader.validateContent(
|
|
109
|
+
slide.template,
|
|
110
|
+
slide.content
|
|
111
|
+
);
|
|
112
|
+
if (!validationResult.valid) {
|
|
113
|
+
throw new TransformError(
|
|
114
|
+
`Slide content validation failed: ${validationResult.errors?.join(", ")}`,
|
|
115
|
+
slide,
|
|
116
|
+
validationResult.errors
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
const pending = {
|
|
120
|
+
icons: /* @__PURE__ */ new Map(),
|
|
121
|
+
cites: /* @__PURE__ */ new Map(),
|
|
122
|
+
expands: /* @__PURE__ */ new Map()
|
|
123
|
+
};
|
|
124
|
+
const templateContext = this.buildTemplateContext(slide, context, pending);
|
|
125
|
+
let output = this.templateEngine.render(template.output, templateContext);
|
|
126
|
+
output = await this.resolvePlaceholders(output, pending);
|
|
127
|
+
if (slide.class) {
|
|
128
|
+
output = `<!-- _class: ${slide.class} -->
|
|
129
|
+
${output}`;
|
|
130
|
+
}
|
|
131
|
+
return output.trim();
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Transform all slides in a presentation
|
|
135
|
+
*/
|
|
136
|
+
async transformAll(presentation) {
|
|
137
|
+
const results = [];
|
|
138
|
+
const totalSlides = presentation.slides.length;
|
|
139
|
+
for (let i = 0; i < presentation.slides.length; i++) {
|
|
140
|
+
const slide = presentation.slides[i];
|
|
141
|
+
const context = {
|
|
142
|
+
meta: presentation.meta,
|
|
143
|
+
slideIndex: i,
|
|
144
|
+
totalSlides
|
|
145
|
+
};
|
|
146
|
+
const transformed = await this.transform(slide, context);
|
|
147
|
+
results.push(transformed);
|
|
148
|
+
}
|
|
149
|
+
return results;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Build the full template context with helpers that collect async operations
|
|
153
|
+
*/
|
|
154
|
+
buildTemplateContext(slide, context, pending) {
|
|
155
|
+
let iconCounter = 0;
|
|
156
|
+
let citeCounter = 0;
|
|
157
|
+
let expandCounter = 0;
|
|
158
|
+
const icons = {
|
|
159
|
+
render: (name, options) => {
|
|
160
|
+
const id = `${iconCounter++}`;
|
|
161
|
+
const placeholder = `${ICON_PLACEHOLDER_PREFIX}${id}${ICON_PLACEHOLDER_SUFFIX}`;
|
|
162
|
+
pending.icons.set(id, { name, options });
|
|
163
|
+
return placeholder;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
const refs = {
|
|
167
|
+
cite: (id) => {
|
|
168
|
+
const counterId = `${citeCounter++}`;
|
|
169
|
+
const placeholder = `${REFS_CITE_PLACEHOLDER_PREFIX}${counterId}${REFS_CITE_PLACEHOLDER_SUFFIX}`;
|
|
170
|
+
pending.cites.set(counterId, id);
|
|
171
|
+
return placeholder;
|
|
172
|
+
},
|
|
173
|
+
expand: (text) => {
|
|
174
|
+
const counterId = `${expandCounter++}`;
|
|
175
|
+
const placeholder = `${REFS_EXPAND_PLACEHOLDER_PREFIX}${counterId}${REFS_EXPAND_PLACEHOLDER_SUFFIX}`;
|
|
176
|
+
pending.expands.set(counterId, text);
|
|
177
|
+
return placeholder;
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
return {
|
|
181
|
+
content: slide.content,
|
|
182
|
+
meta: {
|
|
183
|
+
title: context.meta.title,
|
|
184
|
+
author: context.meta.author,
|
|
185
|
+
theme: context.meta.theme
|
|
186
|
+
},
|
|
187
|
+
slide: {
|
|
188
|
+
index: context.slideIndex,
|
|
189
|
+
total: context.totalSlides
|
|
190
|
+
},
|
|
191
|
+
icons,
|
|
192
|
+
refs
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Resolve all placeholders by executing async operations
|
|
197
|
+
*/
|
|
198
|
+
async resolvePlaceholders(output, pending) {
|
|
199
|
+
const iconResults = /* @__PURE__ */ new Map();
|
|
200
|
+
for (const [id, { name, options }] of pending.icons) {
|
|
201
|
+
const rendered = await this.iconResolver.render(
|
|
202
|
+
name,
|
|
203
|
+
options
|
|
204
|
+
);
|
|
205
|
+
iconResults.set(id, rendered);
|
|
206
|
+
}
|
|
207
|
+
const citeResults = /* @__PURE__ */ new Map();
|
|
208
|
+
for (const [counterId, id] of pending.cites) {
|
|
209
|
+
const formatted = await this.citationFormatter.formatInline(id);
|
|
210
|
+
citeResults.set(counterId, formatted);
|
|
211
|
+
}
|
|
212
|
+
const expandResults = /* @__PURE__ */ new Map();
|
|
213
|
+
for (const [counterId, text] of pending.expands) {
|
|
214
|
+
const expanded = await this.citationFormatter.expandCitations(text);
|
|
215
|
+
expandResults.set(counterId, expanded);
|
|
216
|
+
}
|
|
217
|
+
let result = output;
|
|
218
|
+
for (const [id, rendered] of iconResults) {
|
|
219
|
+
const placeholder = `${ICON_PLACEHOLDER_PREFIX}${id}${ICON_PLACEHOLDER_SUFFIX}`;
|
|
220
|
+
result = result.replace(placeholder, rendered);
|
|
221
|
+
}
|
|
222
|
+
for (const [counterId, formatted] of citeResults) {
|
|
223
|
+
const placeholder = `${REFS_CITE_PLACEHOLDER_PREFIX}${counterId}${REFS_CITE_PLACEHOLDER_SUFFIX}`;
|
|
224
|
+
result = result.replace(placeholder, formatted);
|
|
225
|
+
}
|
|
226
|
+
for (const [counterId, expanded] of expandResults) {
|
|
227
|
+
const placeholder = `${REFS_EXPAND_PLACEHOLDER_PREFIX}${counterId}${REFS_EXPAND_PLACEHOLDER_SUFFIX}`;
|
|
228
|
+
result = result.replace(placeholder, expanded);
|
|
229
|
+
}
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// src/core/renderer.ts
|
|
235
|
+
var Renderer = class {
|
|
236
|
+
/**
|
|
237
|
+
* Render slides and metadata into final Marp markdown
|
|
238
|
+
*/
|
|
239
|
+
render(slides, meta, options) {
|
|
240
|
+
const frontMatter = this.renderFrontMatter(meta, options);
|
|
241
|
+
const slidesContent = this.joinSlides(slides, options?.notes);
|
|
242
|
+
if (slides.length === 0) {
|
|
243
|
+
return frontMatter;
|
|
244
|
+
}
|
|
245
|
+
return `${frontMatter}
|
|
246
|
+
|
|
247
|
+
${slidesContent}`;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Render the YAML front matter block
|
|
251
|
+
*/
|
|
252
|
+
renderFrontMatter(meta, options) {
|
|
253
|
+
const lines = ["---", "marp: true"];
|
|
254
|
+
lines.push(`title: ${meta.title}`);
|
|
255
|
+
if (meta.author) {
|
|
256
|
+
lines.push(`author: ${meta.author}`);
|
|
257
|
+
}
|
|
258
|
+
if (meta.date) {
|
|
259
|
+
lines.push(`date: ${meta.date}`);
|
|
260
|
+
}
|
|
261
|
+
const includeTheme = options?.includeTheme ?? true;
|
|
262
|
+
if (includeTheme && meta.theme) {
|
|
263
|
+
lines.push(`theme: ${meta.theme}`);
|
|
264
|
+
}
|
|
265
|
+
if (options?.additionalFrontMatter) {
|
|
266
|
+
for (const [key, value] of Object.entries(options.additionalFrontMatter)) {
|
|
267
|
+
lines.push(`${key}: ${this.formatFrontMatterValue(value)}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
lines.push("---");
|
|
271
|
+
return lines.join("\n");
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Format a front matter value for YAML
|
|
275
|
+
*/
|
|
276
|
+
formatFrontMatterValue(value) {
|
|
277
|
+
if (typeof value === "boolean") {
|
|
278
|
+
return value ? "true" : "false";
|
|
279
|
+
}
|
|
280
|
+
if (typeof value === "number") {
|
|
281
|
+
return String(value);
|
|
282
|
+
}
|
|
283
|
+
if (typeof value === "string") {
|
|
284
|
+
if (/[:#[\]{}|>]/.test(value)) {
|
|
285
|
+
return `"${value.replace(/"/g, '\\"')}"`;
|
|
286
|
+
}
|
|
287
|
+
return value;
|
|
288
|
+
}
|
|
289
|
+
return String(value);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Join slides with Marp slide separator
|
|
293
|
+
*/
|
|
294
|
+
joinSlides(slides, notes) {
|
|
295
|
+
const parts = [];
|
|
296
|
+
for (let i = 0; i < slides.length; i++) {
|
|
297
|
+
let slideContent = slides[i];
|
|
298
|
+
const note = notes?.[i];
|
|
299
|
+
if (note && note.trim()) {
|
|
300
|
+
slideContent = `${slideContent}
|
|
301
|
+
|
|
302
|
+
${this.renderSpeakerNotes(note)}`;
|
|
303
|
+
}
|
|
304
|
+
parts.push(slideContent);
|
|
305
|
+
}
|
|
306
|
+
return parts.map((slide) => `---
|
|
307
|
+
|
|
308
|
+
${slide}`).join("\n\n");
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Render speaker notes as HTML comment
|
|
312
|
+
*/
|
|
313
|
+
renderSpeakerNotes(notes) {
|
|
314
|
+
return `<!--
|
|
315
|
+
${notes}
|
|
316
|
+
-->`;
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// src/core/pipeline.ts
|
|
321
|
+
import { writeFile } from "fs/promises";
|
|
322
|
+
|
|
323
|
+
// src/templates/engine.ts
|
|
324
|
+
import nunjucks from "nunjucks";
|
|
325
|
+
var TemplateEngine = class {
|
|
326
|
+
env;
|
|
327
|
+
constructor() {
|
|
328
|
+
this.env = new nunjucks.Environment(null, {
|
|
329
|
+
autoescape: false,
|
|
330
|
+
// HTML output for Marp
|
|
331
|
+
throwOnUndefined: false
|
|
332
|
+
});
|
|
333
|
+
this.registerFilters();
|
|
334
|
+
this.registerGlobals();
|
|
335
|
+
}
|
|
336
|
+
render(template, context) {
|
|
337
|
+
return this.env.renderString(template, context);
|
|
338
|
+
}
|
|
339
|
+
registerFilters() {
|
|
340
|
+
}
|
|
341
|
+
registerGlobals() {
|
|
342
|
+
const icons = {
|
|
343
|
+
render: (name, options) => {
|
|
344
|
+
const size = options?.["size"] ?? "24px";
|
|
345
|
+
const color = options?.["color"] ?? "currentColor";
|
|
346
|
+
return `<span class="icon icon-${name}" style="font-size: ${size}; color: ${color};">[${name}]</span>`;
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
this.env.addGlobal("icons", icons);
|
|
350
|
+
const refs = {
|
|
351
|
+
cite: (id) => {
|
|
352
|
+
const cleanId = id.replace("@", "");
|
|
353
|
+
return `(${cleanId})`;
|
|
354
|
+
},
|
|
355
|
+
expand: (text) => {
|
|
356
|
+
return text.replace(/\[@(\w+)\]/g, "($1)");
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
this.env.addGlobal("refs", refs);
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
// src/templates/loader.ts
|
|
364
|
+
import { z as z3 } from "zod";
|
|
365
|
+
import * as yaml from "yaml";
|
|
366
|
+
import * as fs from "fs/promises";
|
|
367
|
+
import * as path from "path";
|
|
368
|
+
|
|
369
|
+
// src/templates/validators.ts
|
|
370
|
+
import { z as z2 } from "zod";
|
|
371
|
+
function jsonSchemaToZod(schema) {
|
|
372
|
+
if (schema.oneOf && schema.oneOf.length > 0) {
|
|
373
|
+
const schemas = schema.oneOf.map((s) => jsonSchemaToZod(s));
|
|
374
|
+
if (schemas.length === 1) {
|
|
375
|
+
return schemas[0];
|
|
376
|
+
}
|
|
377
|
+
return z2.union(schemas);
|
|
378
|
+
}
|
|
379
|
+
const type = schema.type ?? "object";
|
|
380
|
+
switch (type) {
|
|
381
|
+
case "string": {
|
|
382
|
+
if (schema.enum && schema.enum.length > 0) {
|
|
383
|
+
const enumValues = schema.enum;
|
|
384
|
+
return z2.enum(enumValues);
|
|
385
|
+
}
|
|
386
|
+
let zodSchema = z2.string();
|
|
387
|
+
if (schema.pattern) {
|
|
388
|
+
zodSchema = zodSchema.regex(new RegExp(schema.pattern));
|
|
389
|
+
}
|
|
390
|
+
return zodSchema;
|
|
391
|
+
}
|
|
392
|
+
case "number":
|
|
393
|
+
return z2.number();
|
|
394
|
+
case "integer":
|
|
395
|
+
return z2.number().int();
|
|
396
|
+
case "boolean":
|
|
397
|
+
return z2.boolean();
|
|
398
|
+
case "array": {
|
|
399
|
+
const itemSchema = schema.items ? jsonSchemaToZod(schema.items) : z2.unknown();
|
|
400
|
+
let arraySchema = z2.array(itemSchema);
|
|
401
|
+
if (schema.minItems !== void 0) {
|
|
402
|
+
arraySchema = arraySchema.min(schema.minItems);
|
|
403
|
+
}
|
|
404
|
+
if (schema.maxItems !== void 0) {
|
|
405
|
+
arraySchema = arraySchema.max(schema.maxItems);
|
|
406
|
+
}
|
|
407
|
+
return arraySchema;
|
|
408
|
+
}
|
|
409
|
+
case "object": {
|
|
410
|
+
if (!schema.properties) {
|
|
411
|
+
return z2.record(z2.unknown());
|
|
412
|
+
}
|
|
413
|
+
const shape = {};
|
|
414
|
+
const required = new Set(schema.required ?? []);
|
|
415
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
416
|
+
const propZod = jsonSchemaToZod(propSchema);
|
|
417
|
+
shape[key] = required.has(key) ? propZod : propZod.optional();
|
|
418
|
+
}
|
|
419
|
+
return z2.object(shape).passthrough();
|
|
420
|
+
}
|
|
421
|
+
default:
|
|
422
|
+
return z2.unknown();
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
function validateWithJsonSchema(schema, content) {
|
|
426
|
+
const zodSchema = jsonSchemaToZod(schema);
|
|
427
|
+
const result = zodSchema.safeParse(content);
|
|
428
|
+
if (result.success) {
|
|
429
|
+
return { valid: true, errors: [] };
|
|
430
|
+
}
|
|
431
|
+
const errors = result.error.errors.map((issue) => {
|
|
432
|
+
const path3 = issue.path.join(".");
|
|
433
|
+
return path3 ? `${path3}: ${issue.message}` : issue.message;
|
|
434
|
+
});
|
|
435
|
+
return { valid: false, errors };
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// src/templates/loader.ts
|
|
439
|
+
var jsonSchemaSchema = z3.record(z3.unknown());
|
|
440
|
+
var templateDefSchema = z3.object({
|
|
441
|
+
name: z3.string().min(1, "Template name is required"),
|
|
442
|
+
description: z3.string(),
|
|
443
|
+
category: z3.string(),
|
|
444
|
+
schema: jsonSchemaSchema,
|
|
445
|
+
example: z3.record(z3.unknown()).optional(),
|
|
446
|
+
output: z3.string().min(1, "Template output is required"),
|
|
447
|
+
css: z3.string().optional()
|
|
448
|
+
});
|
|
449
|
+
var TemplateLoader = class {
|
|
450
|
+
templates;
|
|
451
|
+
constructor() {
|
|
452
|
+
this.templates = /* @__PURE__ */ new Map();
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Load a template from a YAML string
|
|
456
|
+
*/
|
|
457
|
+
async loadFromString(yamlContent) {
|
|
458
|
+
const parsed = yaml.parse(yamlContent);
|
|
459
|
+
const result = templateDefSchema.safeParse(parsed);
|
|
460
|
+
if (!result.success) {
|
|
461
|
+
const errors = result.error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ");
|
|
462
|
+
throw new Error(`Invalid template definition: ${errors}`);
|
|
463
|
+
}
|
|
464
|
+
this.templates.set(result.data.name, result.data);
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Load a template from a file
|
|
468
|
+
*/
|
|
469
|
+
async loadFromFile(filePath) {
|
|
470
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
471
|
+
await this.loadFromString(content);
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Load all templates from a directory (recursively)
|
|
475
|
+
*/
|
|
476
|
+
async loadBuiltIn(directory) {
|
|
477
|
+
await this.loadDirectory(directory);
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Load custom templates from a directory (can override built-in)
|
|
481
|
+
*/
|
|
482
|
+
async loadCustom(directory) {
|
|
483
|
+
await this.loadDirectory(directory);
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Internal method to load templates from a directory
|
|
487
|
+
*/
|
|
488
|
+
async loadDirectory(directory) {
|
|
489
|
+
const entries = await fs.readdir(directory, { withFileTypes: true });
|
|
490
|
+
for (const entry of entries) {
|
|
491
|
+
const fullPath = path.join(directory, entry.name);
|
|
492
|
+
if (entry.isDirectory()) {
|
|
493
|
+
await this.loadDirectory(fullPath);
|
|
494
|
+
} else if (entry.isFile() && (entry.name.endsWith(".yaml") || entry.name.endsWith(".yml"))) {
|
|
495
|
+
await this.loadFromFile(fullPath);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Get a template by name
|
|
501
|
+
*/
|
|
502
|
+
get(name) {
|
|
503
|
+
return this.templates.get(name);
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* List all loaded templates
|
|
507
|
+
*/
|
|
508
|
+
list() {
|
|
509
|
+
return Array.from(this.templates.values());
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* List templates filtered by category
|
|
513
|
+
*/
|
|
514
|
+
listByCategory(category) {
|
|
515
|
+
return this.list().filter((t) => t.category === category);
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Validate content against a template's schema
|
|
519
|
+
*/
|
|
520
|
+
validateContent(templateName, content) {
|
|
521
|
+
const template = this.get(templateName);
|
|
522
|
+
if (!template) {
|
|
523
|
+
return {
|
|
524
|
+
valid: false,
|
|
525
|
+
errors: [`Template "${templateName}" not found`]
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
return validateWithJsonSchema(template.schema, content);
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
// src/icons/registry.ts
|
|
533
|
+
import * as fs2 from "fs/promises";
|
|
534
|
+
import { parse as parseYaml2 } from "yaml";
|
|
535
|
+
|
|
536
|
+
// src/icons/schema.ts
|
|
537
|
+
import { z as z4 } from "zod";
|
|
538
|
+
var iconSourceTypeSchema = z4.enum([
|
|
539
|
+
"web-font",
|
|
540
|
+
"svg-inline",
|
|
541
|
+
"svg-sprite",
|
|
542
|
+
"local-svg"
|
|
543
|
+
]);
|
|
544
|
+
var iconSourceSchema = z4.object({
|
|
545
|
+
name: z4.string(),
|
|
546
|
+
type: iconSourceTypeSchema,
|
|
547
|
+
prefix: z4.string(),
|
|
548
|
+
url: z4.string().optional(),
|
|
549
|
+
path: z4.string().optional(),
|
|
550
|
+
render: z4.string().optional()
|
|
551
|
+
});
|
|
552
|
+
var iconDefaultsSchema = z4.object({
|
|
553
|
+
size: z4.string().default("24px"),
|
|
554
|
+
color: z4.string().default("currentColor")
|
|
555
|
+
});
|
|
556
|
+
var iconRegistrySchema = z4.object({
|
|
557
|
+
sources: z4.array(iconSourceSchema),
|
|
558
|
+
aliases: z4.record(z4.string()).default({}),
|
|
559
|
+
colors: z4.record(z4.string()).optional(),
|
|
560
|
+
defaults: iconDefaultsSchema.default({})
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
// src/icons/registry.ts
|
|
564
|
+
var IconRegistryLoader = class {
|
|
565
|
+
registry = null;
|
|
566
|
+
sourcesByPrefix = /* @__PURE__ */ new Map();
|
|
567
|
+
aliasMap = /* @__PURE__ */ new Map();
|
|
568
|
+
colorMap = /* @__PURE__ */ new Map();
|
|
569
|
+
/**
|
|
570
|
+
* Load registry from YAML file
|
|
571
|
+
*/
|
|
572
|
+
async load(configPath) {
|
|
573
|
+
const content = await fs2.readFile(configPath, "utf-8");
|
|
574
|
+
const parsed = parseYaml2(content);
|
|
575
|
+
const validated = iconRegistrySchema.parse(parsed);
|
|
576
|
+
this.registry = validated;
|
|
577
|
+
this.buildMaps();
|
|
578
|
+
return validated;
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Resolve an alias to its icon reference
|
|
582
|
+
* @returns The resolved icon reference or the original name if not an alias
|
|
583
|
+
*/
|
|
584
|
+
resolveAlias(nameOrAlias) {
|
|
585
|
+
return this.aliasMap.get(nameOrAlias) ?? nameOrAlias;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Get icon source by prefix
|
|
589
|
+
*/
|
|
590
|
+
getSource(prefix) {
|
|
591
|
+
return this.sourcesByPrefix.get(prefix);
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Parse an icon reference string (e.g., "mi:home" or "iconify:mdi:account")
|
|
595
|
+
* @returns Parsed reference or null if invalid format
|
|
596
|
+
*/
|
|
597
|
+
parseIconReference(reference) {
|
|
598
|
+
const colonIndex = reference.indexOf(":");
|
|
599
|
+
if (colonIndex === -1) {
|
|
600
|
+
return null;
|
|
601
|
+
}
|
|
602
|
+
const prefix = reference.substring(0, colonIndex);
|
|
603
|
+
const name = reference.substring(colonIndex + 1);
|
|
604
|
+
return { prefix, name };
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Get registry defaults
|
|
608
|
+
*/
|
|
609
|
+
getDefaults() {
|
|
610
|
+
if (!this.registry) {
|
|
611
|
+
return { size: "24px", color: "currentColor" };
|
|
612
|
+
}
|
|
613
|
+
return this.registry.defaults;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Get color by name from color palette
|
|
617
|
+
*/
|
|
618
|
+
getColor(name) {
|
|
619
|
+
return this.colorMap.get(name);
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Get all sources
|
|
623
|
+
*/
|
|
624
|
+
getSources() {
|
|
625
|
+
return this.registry?.sources ?? [];
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Get all aliases
|
|
629
|
+
*/
|
|
630
|
+
getAliases() {
|
|
631
|
+
return this.registry?.aliases ?? {};
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Check if registry is loaded
|
|
635
|
+
*/
|
|
636
|
+
isLoaded() {
|
|
637
|
+
return this.registry !== null;
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Build internal lookup maps from registry
|
|
641
|
+
*/
|
|
642
|
+
buildMaps() {
|
|
643
|
+
this.sourcesByPrefix.clear();
|
|
644
|
+
this.aliasMap.clear();
|
|
645
|
+
this.colorMap.clear();
|
|
646
|
+
if (!this.registry) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
for (const source of this.registry.sources) {
|
|
650
|
+
this.sourcesByPrefix.set(source.prefix, source);
|
|
651
|
+
}
|
|
652
|
+
for (const [alias, target] of Object.entries(this.registry.aliases)) {
|
|
653
|
+
this.aliasMap.set(alias, target);
|
|
654
|
+
}
|
|
655
|
+
if (this.registry.colors) {
|
|
656
|
+
for (const [name, color] of Object.entries(this.registry.colors)) {
|
|
657
|
+
this.colorMap.set(name, color);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
// src/icons/resolver.ts
|
|
664
|
+
import * as fs3 from "fs/promises";
|
|
665
|
+
import * as path2 from "path";
|
|
666
|
+
import nunjucks2 from "nunjucks";
|
|
667
|
+
var IconResolver = class {
|
|
668
|
+
constructor(registry) {
|
|
669
|
+
this.registry = registry;
|
|
670
|
+
this.nunjucksEnv = new nunjucks2.Environment(null, {
|
|
671
|
+
autoescape: false
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
nunjucksEnv;
|
|
675
|
+
/**
|
|
676
|
+
* Render an icon by name or alias
|
|
677
|
+
*/
|
|
678
|
+
async render(nameOrAlias, options) {
|
|
679
|
+
const resolved = this.registry.resolveAlias(nameOrAlias);
|
|
680
|
+
const parsed = this.registry.parseIconReference(resolved);
|
|
681
|
+
if (!parsed) {
|
|
682
|
+
throw new Error(
|
|
683
|
+
`Invalid icon reference format: "${resolved}". Expected format: "prefix:name"`
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
const source = this.registry.getSource(parsed.prefix);
|
|
687
|
+
if (!source) {
|
|
688
|
+
throw new Error(`Unknown icon source prefix: "${parsed.prefix}"`);
|
|
689
|
+
}
|
|
690
|
+
const defaults = this.registry.getDefaults();
|
|
691
|
+
const mergedOptions = {
|
|
692
|
+
size: options?.size ?? defaults.size,
|
|
693
|
+
color: options?.color ?? defaults.color,
|
|
694
|
+
...options?.class !== void 0 ? { class: options.class } : {}
|
|
695
|
+
};
|
|
696
|
+
switch (source.type) {
|
|
697
|
+
case "web-font":
|
|
698
|
+
return this.renderWebFont(source, parsed.name, mergedOptions);
|
|
699
|
+
case "local-svg":
|
|
700
|
+
return await this.renderLocalSvg(source, parsed.name, mergedOptions);
|
|
701
|
+
case "svg-inline":
|
|
702
|
+
return await this.renderSvgInline(source, parsed.name, mergedOptions);
|
|
703
|
+
case "svg-sprite":
|
|
704
|
+
return this.renderSvgSprite(source, parsed.name, mergedOptions);
|
|
705
|
+
default:
|
|
706
|
+
throw new Error(`Unsupported icon source type: "${source.type}"`);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Render a web-font icon
|
|
711
|
+
*/
|
|
712
|
+
renderWebFont(source, name, options) {
|
|
713
|
+
const style = this.buildStyle(options);
|
|
714
|
+
const className = this.buildClassName(name, options);
|
|
715
|
+
if (source.render) {
|
|
716
|
+
return this.nunjucksEnv.renderString(source.render, {
|
|
717
|
+
name,
|
|
718
|
+
style,
|
|
719
|
+
class: className,
|
|
720
|
+
size: options.size,
|
|
721
|
+
color: options.color
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
return `<span class="${className}" style="${style}">${name}</span>`;
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Render a local SVG icon
|
|
728
|
+
*/
|
|
729
|
+
async renderLocalSvg(source, name, options) {
|
|
730
|
+
if (!source.path) {
|
|
731
|
+
throw new Error(`Local SVG source "${source.name}" has no path defined`);
|
|
732
|
+
}
|
|
733
|
+
const svgPath = path2.join(source.path, `${name}.svg`);
|
|
734
|
+
try {
|
|
735
|
+
const svgContent = await fs3.readFile(svgPath, "utf-8");
|
|
736
|
+
return this.processSvg(svgContent, name, options);
|
|
737
|
+
} catch (error) {
|
|
738
|
+
if (error.code === "ENOENT") {
|
|
739
|
+
throw new Error(`Icon file not found: ${svgPath}`);
|
|
740
|
+
}
|
|
741
|
+
throw error;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Render an SVG from external URL (placeholder without cache)
|
|
746
|
+
*/
|
|
747
|
+
async renderSvgInline(source, name, options) {
|
|
748
|
+
const className = this.buildClassName(name, options);
|
|
749
|
+
const style = this.buildStyle(options);
|
|
750
|
+
return `<span class="${className}" style="${style}" data-icon-source="${source.name}" data-icon-name="${name}">[${name}]</span>`;
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Render an SVG sprite reference
|
|
754
|
+
*/
|
|
755
|
+
renderSvgSprite(source, name, options) {
|
|
756
|
+
const className = this.buildClassName(name, options);
|
|
757
|
+
const size = options.size ?? "24px";
|
|
758
|
+
const color = options.color ?? "currentColor";
|
|
759
|
+
const spriteUrl = source.url ?? "";
|
|
760
|
+
return `<svg class="${className}" width="${size}" height="${size}" fill="${color}">
|
|
761
|
+
<use xlink:href="${spriteUrl}#${name}"/>
|
|
762
|
+
</svg>`;
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Process SVG content and apply options
|
|
766
|
+
*/
|
|
767
|
+
processSvg(svgContent, name, options) {
|
|
768
|
+
const className = this.buildClassName(name, options);
|
|
769
|
+
const size = options.size ?? "24px";
|
|
770
|
+
const color = options.color ?? "currentColor";
|
|
771
|
+
let processed = svgContent.trim();
|
|
772
|
+
if (processed.includes("class=")) {
|
|
773
|
+
processed = processed.replace(/class="[^"]*"/, `class="${className}"`);
|
|
774
|
+
} else {
|
|
775
|
+
processed = processed.replace("<svg", `<svg class="${className}"`);
|
|
776
|
+
}
|
|
777
|
+
if (processed.includes("width=")) {
|
|
778
|
+
processed = processed.replace(/width="[^"]*"/, `width="${size}"`);
|
|
779
|
+
} else {
|
|
780
|
+
processed = processed.replace("<svg", `<svg width="${size}"`);
|
|
781
|
+
}
|
|
782
|
+
if (processed.includes("height=")) {
|
|
783
|
+
processed = processed.replace(/height="[^"]*"/, `height="${size}"`);
|
|
784
|
+
} else {
|
|
785
|
+
processed = processed.replace("<svg", `<svg height="${size}"`);
|
|
786
|
+
}
|
|
787
|
+
if (color !== "currentColor") {
|
|
788
|
+
processed = processed.replace(/fill="currentColor"/g, `fill="${color}"`);
|
|
789
|
+
}
|
|
790
|
+
return processed;
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Build CSS style string
|
|
794
|
+
*/
|
|
795
|
+
buildStyle(options) {
|
|
796
|
+
const styles = [];
|
|
797
|
+
if (options.size) {
|
|
798
|
+
styles.push(`font-size: ${options.size}`);
|
|
799
|
+
}
|
|
800
|
+
if (options.color) {
|
|
801
|
+
styles.push(`color: ${options.color}`);
|
|
802
|
+
}
|
|
803
|
+
return styles.join("; ");
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Build class name string
|
|
807
|
+
*/
|
|
808
|
+
buildClassName(name, options) {
|
|
809
|
+
const classes = ["icon", `icon-${name}`];
|
|
810
|
+
if (options.class) {
|
|
811
|
+
classes.push(options.class);
|
|
812
|
+
}
|
|
813
|
+
return classes.join(" ");
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
|
|
817
|
+
// src/references/manager.ts
|
|
818
|
+
import { exec } from "child_process";
|
|
819
|
+
var ReferenceManagerError = class extends Error {
|
|
820
|
+
constructor(message, cause) {
|
|
821
|
+
super(message);
|
|
822
|
+
this.cause = cause;
|
|
823
|
+
this.name = "ReferenceManagerError";
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
var ReferenceManager = class {
|
|
827
|
+
constructor(command = "ref") {
|
|
828
|
+
this.command = command;
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Check if reference-manager CLI is available
|
|
832
|
+
*/
|
|
833
|
+
async isAvailable() {
|
|
834
|
+
try {
|
|
835
|
+
await this.execCommand(`${this.command} --version`);
|
|
836
|
+
return true;
|
|
837
|
+
} catch {
|
|
838
|
+
return false;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Get all references from the library
|
|
843
|
+
*/
|
|
844
|
+
async getAll() {
|
|
845
|
+
const result = await this.execCommand(`${this.command} list --format json`);
|
|
846
|
+
return this.parseJSON(result);
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Get a single reference by ID
|
|
850
|
+
*/
|
|
851
|
+
async getById(id) {
|
|
852
|
+
const result = await this.execCommand(
|
|
853
|
+
`${this.command} list --id ${id} --format json`
|
|
854
|
+
);
|
|
855
|
+
const items = this.parseJSON(result);
|
|
856
|
+
return items[0] || null;
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Get multiple references by IDs
|
|
860
|
+
*/
|
|
861
|
+
async getByIds(ids) {
|
|
862
|
+
if (ids.length === 0) {
|
|
863
|
+
return /* @__PURE__ */ new Map();
|
|
864
|
+
}
|
|
865
|
+
const result = await this.execCommand(`${this.command} list --format json`);
|
|
866
|
+
const allItems = this.parseJSON(result);
|
|
867
|
+
const idSet = new Set(ids);
|
|
868
|
+
const map = /* @__PURE__ */ new Map();
|
|
869
|
+
for (const item of allItems) {
|
|
870
|
+
if (idSet.has(item.id)) {
|
|
871
|
+
map.set(item.id, item);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
return map;
|
|
875
|
+
}
|
|
876
|
+
execCommand(cmd) {
|
|
877
|
+
return new Promise((resolve, reject) => {
|
|
878
|
+
exec(cmd, (error, stdout) => {
|
|
879
|
+
if (error) {
|
|
880
|
+
reject(
|
|
881
|
+
new ReferenceManagerError(`Failed to execute: ${cmd}`, error)
|
|
882
|
+
);
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
resolve(stdout.toString());
|
|
886
|
+
});
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
parseJSON(data) {
|
|
890
|
+
try {
|
|
891
|
+
return JSON.parse(data);
|
|
892
|
+
} catch (error) {
|
|
893
|
+
throw new ReferenceManagerError(
|
|
894
|
+
"Failed to parse reference-manager output as JSON",
|
|
895
|
+
error
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
};
|
|
900
|
+
|
|
901
|
+
// src/references/extractor.ts
|
|
902
|
+
var SINGLE_CITATION_PATTERN = /@([\w-]+)(?:,\s*([^;\]]+))?/g;
|
|
903
|
+
var CITATION_BRACKET_PATTERN = /\[(@[\w-]+(?:,\s*[^;\]]+)?(?:;\s*@[\w-]+(?:,\s*[^;\]]+)?)*)\]/g;
|
|
904
|
+
var SOURCE_CITATION_PATTERN = /^@([\w-]+)$/;
|
|
905
|
+
var CitationExtractor = class {
|
|
906
|
+
/**
|
|
907
|
+
* Extract citations from a text string
|
|
908
|
+
*/
|
|
909
|
+
extract(text) {
|
|
910
|
+
const citations = [];
|
|
911
|
+
let bracketMatch;
|
|
912
|
+
CITATION_BRACKET_PATTERN.lastIndex = 0;
|
|
913
|
+
while ((bracketMatch = CITATION_BRACKET_PATTERN.exec(text)) !== null) {
|
|
914
|
+
const bracketStart = bracketMatch.index;
|
|
915
|
+
const bracketContent = bracketMatch[1];
|
|
916
|
+
SINGLE_CITATION_PATTERN.lastIndex = 0;
|
|
917
|
+
let singleMatch;
|
|
918
|
+
while ((singleMatch = SINGLE_CITATION_PATTERN.exec(bracketContent)) !== null) {
|
|
919
|
+
const id = singleMatch[1];
|
|
920
|
+
const locator = singleMatch[2]?.trim();
|
|
921
|
+
citations.push({
|
|
922
|
+
id,
|
|
923
|
+
locator: locator ?? void 0,
|
|
924
|
+
position: {
|
|
925
|
+
start: bracketStart,
|
|
926
|
+
end: bracketStart + bracketMatch[0].length
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
return citations;
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Extract citations from a slide's content
|
|
935
|
+
*/
|
|
936
|
+
extractFromSlide(slide) {
|
|
937
|
+
const allCitations = [];
|
|
938
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
939
|
+
const extractFromValue = (value) => {
|
|
940
|
+
if (typeof value === "string") {
|
|
941
|
+
const sourceMatch = SOURCE_CITATION_PATTERN.exec(value);
|
|
942
|
+
if (sourceMatch && sourceMatch[1]) {
|
|
943
|
+
const id = sourceMatch[1];
|
|
944
|
+
if (!seenIds.has(id)) {
|
|
945
|
+
seenIds.add(id);
|
|
946
|
+
allCitations.push({
|
|
947
|
+
id,
|
|
948
|
+
locator: void 0,
|
|
949
|
+
position: { start: 0, end: value.length }
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
const citations = this.extract(value);
|
|
955
|
+
for (const citation of citations) {
|
|
956
|
+
if (!seenIds.has(citation.id)) {
|
|
957
|
+
seenIds.add(citation.id);
|
|
958
|
+
allCitations.push(citation);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
} else if (Array.isArray(value)) {
|
|
962
|
+
for (const item of value) {
|
|
963
|
+
extractFromValue(item);
|
|
964
|
+
}
|
|
965
|
+
} else if (value && typeof value === "object") {
|
|
966
|
+
for (const v of Object.values(value)) {
|
|
967
|
+
extractFromValue(v);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
};
|
|
971
|
+
extractFromValue(slide.content);
|
|
972
|
+
if (slide.notes) {
|
|
973
|
+
const notesCitations = this.extract(slide.notes);
|
|
974
|
+
for (const citation of notesCitations) {
|
|
975
|
+
if (!seenIds.has(citation.id)) {
|
|
976
|
+
seenIds.add(citation.id);
|
|
977
|
+
allCitations.push(citation);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
return allCitations;
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* Extract citations from all slides in a presentation
|
|
985
|
+
*/
|
|
986
|
+
extractFromPresentation(presentation) {
|
|
987
|
+
const allCitations = [];
|
|
988
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
989
|
+
for (const slide of presentation.slides) {
|
|
990
|
+
const slideCitations = this.extractFromSlide(slide);
|
|
991
|
+
for (const citation of slideCitations) {
|
|
992
|
+
if (!seenIds.has(citation.id)) {
|
|
993
|
+
seenIds.add(citation.id);
|
|
994
|
+
allCitations.push(citation);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
return allCitations;
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Get unique citation IDs in order of first appearance
|
|
1002
|
+
*/
|
|
1003
|
+
getUniqueIds(citations) {
|
|
1004
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1005
|
+
const uniqueIds = [];
|
|
1006
|
+
for (const citation of citations) {
|
|
1007
|
+
if (!seen.has(citation.id)) {
|
|
1008
|
+
seen.add(citation.id);
|
|
1009
|
+
uniqueIds.push(citation.id);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
return uniqueIds;
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
|
|
1016
|
+
// src/references/formatter.ts
|
|
1017
|
+
var DEFAULT_CONFIG = {
|
|
1018
|
+
author: {
|
|
1019
|
+
maxAuthors: 2,
|
|
1020
|
+
etAl: "et al.",
|
|
1021
|
+
etAlJa: "\u307B\u304B",
|
|
1022
|
+
separatorJa: "\u30FB"
|
|
1023
|
+
},
|
|
1024
|
+
inline: {
|
|
1025
|
+
authorSep: ", ",
|
|
1026
|
+
identifierSep: "; ",
|
|
1027
|
+
multiSep: "), ("
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
var JAPANESE_PATTERN = /[\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF]/;
|
|
1031
|
+
var CITATION_BRACKET_PATTERN2 = /\[(@[\w-]+(?:,\s*[^;\]]+)?(?:;\s*@[\w-]+(?:,\s*[^;\]]+)?)*)\]/g;
|
|
1032
|
+
var SINGLE_CITATION_PATTERN2 = /@([\w-]+)(?:,\s*([^;\]]+))?/g;
|
|
1033
|
+
var CitationFormatter = class {
|
|
1034
|
+
constructor(manager, config) {
|
|
1035
|
+
this.manager = manager;
|
|
1036
|
+
this.config = {
|
|
1037
|
+
author: { ...DEFAULT_CONFIG.author, ...config?.author },
|
|
1038
|
+
inline: { ...DEFAULT_CONFIG.inline, ...config?.inline }
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
config;
|
|
1042
|
+
/**
|
|
1043
|
+
* Format an inline citation
|
|
1044
|
+
* e.g., "(Smith et al., 2024; PMID: 12345678)"
|
|
1045
|
+
*/
|
|
1046
|
+
async formatInline(id) {
|
|
1047
|
+
const item = await this.manager.getById(id);
|
|
1048
|
+
if (!item) {
|
|
1049
|
+
return `[${id}]`;
|
|
1050
|
+
}
|
|
1051
|
+
return this.formatInlineItem(item);
|
|
1052
|
+
}
|
|
1053
|
+
/**
|
|
1054
|
+
* Format a full bibliography citation
|
|
1055
|
+
*/
|
|
1056
|
+
async formatFull(id) {
|
|
1057
|
+
const item = await this.manager.getById(id);
|
|
1058
|
+
if (!item) {
|
|
1059
|
+
return `[${id}]`;
|
|
1060
|
+
}
|
|
1061
|
+
return this.formatFullItem(item);
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Expand all citations in text
|
|
1065
|
+
* e.g., "[@smith2024]" -> "(Smith et al., 2024; PMID: 12345678)"
|
|
1066
|
+
*/
|
|
1067
|
+
async expandCitations(text) {
|
|
1068
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1069
|
+
CITATION_BRACKET_PATTERN2.lastIndex = 0;
|
|
1070
|
+
let match;
|
|
1071
|
+
while ((match = CITATION_BRACKET_PATTERN2.exec(text)) !== null) {
|
|
1072
|
+
const content = match[1];
|
|
1073
|
+
SINGLE_CITATION_PATTERN2.lastIndex = 0;
|
|
1074
|
+
let singleMatch;
|
|
1075
|
+
while ((singleMatch = SINGLE_CITATION_PATTERN2.exec(content)) !== null) {
|
|
1076
|
+
ids.add(singleMatch[1]);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
const items = await this.manager.getByIds([...ids]);
|
|
1080
|
+
CITATION_BRACKET_PATTERN2.lastIndex = 0;
|
|
1081
|
+
let result = text;
|
|
1082
|
+
const matches = [];
|
|
1083
|
+
CITATION_BRACKET_PATTERN2.lastIndex = 0;
|
|
1084
|
+
while ((match = CITATION_BRACKET_PATTERN2.exec(text)) !== null) {
|
|
1085
|
+
const content = match[1];
|
|
1086
|
+
const replacements = [];
|
|
1087
|
+
SINGLE_CITATION_PATTERN2.lastIndex = 0;
|
|
1088
|
+
let singleMatch;
|
|
1089
|
+
while ((singleMatch = SINGLE_CITATION_PATTERN2.exec(content)) !== null) {
|
|
1090
|
+
const id = singleMatch[1];
|
|
1091
|
+
const item = items.get(id);
|
|
1092
|
+
if (item) {
|
|
1093
|
+
replacements.push(this.formatInlineItem(item));
|
|
1094
|
+
} else {
|
|
1095
|
+
replacements.push(`[${id}]`);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
matches.push({
|
|
1099
|
+
start: match.index,
|
|
1100
|
+
end: match.index + match[0].length,
|
|
1101
|
+
replacement: replacements.join(", ")
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
for (const m of [...matches].reverse()) {
|
|
1105
|
+
result = result.slice(0, m.start) + m.replacement + result.slice(m.end);
|
|
1106
|
+
}
|
|
1107
|
+
return result;
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Generate bibliography entries
|
|
1111
|
+
*/
|
|
1112
|
+
async generateBibliography(ids, sort = "citation-order") {
|
|
1113
|
+
if (ids.length === 0) {
|
|
1114
|
+
return [];
|
|
1115
|
+
}
|
|
1116
|
+
const items = await this.manager.getByIds(ids);
|
|
1117
|
+
let sortedItems;
|
|
1118
|
+
if (sort === "citation-order") {
|
|
1119
|
+
sortedItems = ids.map((id) => items.get(id)).filter((item) => item !== void 0);
|
|
1120
|
+
} else if (sort === "author") {
|
|
1121
|
+
sortedItems = [...items.values()].sort((a, b) => {
|
|
1122
|
+
const authorA = this.getFirstAuthorFamily(a);
|
|
1123
|
+
const authorB = this.getFirstAuthorFamily(b);
|
|
1124
|
+
return authorA.localeCompare(authorB);
|
|
1125
|
+
});
|
|
1126
|
+
} else {
|
|
1127
|
+
sortedItems = [...items.values()].sort((a, b) => {
|
|
1128
|
+
const yearA = this.getYear(a);
|
|
1129
|
+
const yearB = this.getYear(b);
|
|
1130
|
+
return yearA - yearB;
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
return sortedItems.map((item) => this.formatFullItem(item));
|
|
1134
|
+
}
|
|
1135
|
+
formatInlineItem(item) {
|
|
1136
|
+
const author = this.formatAuthorInline(item.author);
|
|
1137
|
+
const year = this.getYear(item);
|
|
1138
|
+
const identifier = this.getIdentifier(item);
|
|
1139
|
+
if (identifier) {
|
|
1140
|
+
return `(${author}, ${year}; ${identifier})`;
|
|
1141
|
+
}
|
|
1142
|
+
return `(${author}, ${year})`;
|
|
1143
|
+
}
|
|
1144
|
+
formatFullItem(item) {
|
|
1145
|
+
const parts = [];
|
|
1146
|
+
const isJapanese = this.isJapaneseAuthors(item.author);
|
|
1147
|
+
const authors = this.formatAuthorsFull(item.author, isJapanese);
|
|
1148
|
+
parts.push(authors);
|
|
1149
|
+
const year = this.getYear(item);
|
|
1150
|
+
parts.push(`(${year}).`);
|
|
1151
|
+
if (item.title) {
|
|
1152
|
+
parts.push(`${item.title}.`);
|
|
1153
|
+
}
|
|
1154
|
+
if (item["container-title"]) {
|
|
1155
|
+
const journal = isJapanese ? item["container-title"] : `*${item["container-title"]}*`;
|
|
1156
|
+
let location = "";
|
|
1157
|
+
if (item.volume) {
|
|
1158
|
+
location = item.issue ? `${item.volume}(${item.issue})` : item.volume;
|
|
1159
|
+
}
|
|
1160
|
+
if (item.page) {
|
|
1161
|
+
location = location ? `${location}, ${item.page}` : item.page;
|
|
1162
|
+
}
|
|
1163
|
+
parts.push(location ? `${journal}, ${location}.` : `${journal}.`);
|
|
1164
|
+
}
|
|
1165
|
+
const identifier = this.getIdentifier(item);
|
|
1166
|
+
if (identifier) {
|
|
1167
|
+
parts.push(identifier);
|
|
1168
|
+
}
|
|
1169
|
+
return parts.join(" ");
|
|
1170
|
+
}
|
|
1171
|
+
formatAuthorInline(authors) {
|
|
1172
|
+
if (!authors || authors.length === 0) {
|
|
1173
|
+
return "Unknown";
|
|
1174
|
+
}
|
|
1175
|
+
const isJapanese = this.isJapaneseAuthors(authors);
|
|
1176
|
+
const { etAl, etAlJa, separatorJa } = this.config.author;
|
|
1177
|
+
const firstAuthor = authors[0];
|
|
1178
|
+
if (authors.length === 1) {
|
|
1179
|
+
return firstAuthor.family;
|
|
1180
|
+
}
|
|
1181
|
+
if (authors.length === 2) {
|
|
1182
|
+
const separator = isJapanese ? separatorJa : " & ";
|
|
1183
|
+
return `${firstAuthor.family}${separator}${authors[1].family}`;
|
|
1184
|
+
}
|
|
1185
|
+
const suffix = isJapanese ? etAlJa : ` ${etAl}`;
|
|
1186
|
+
return `${firstAuthor.family}${suffix}`;
|
|
1187
|
+
}
|
|
1188
|
+
formatAuthorsFull(authors, isJapanese) {
|
|
1189
|
+
if (!authors || authors.length === 0) {
|
|
1190
|
+
return "Unknown";
|
|
1191
|
+
}
|
|
1192
|
+
if (isJapanese) {
|
|
1193
|
+
return authors.map((a) => `${a.family}${a.given || ""}`).join(", ");
|
|
1194
|
+
}
|
|
1195
|
+
if (authors.length === 1) {
|
|
1196
|
+
const a = authors[0];
|
|
1197
|
+
const initial = a.given ? `${a.given.charAt(0)}.` : "";
|
|
1198
|
+
return `${a.family}, ${initial}`;
|
|
1199
|
+
}
|
|
1200
|
+
const formatted = authors.map((a, i) => {
|
|
1201
|
+
const initial = a.given ? `${a.given.charAt(0)}.` : "";
|
|
1202
|
+
if (i === authors.length - 1) {
|
|
1203
|
+
return `& ${a.family}, ${initial}`;
|
|
1204
|
+
}
|
|
1205
|
+
return `${a.family}, ${initial}`;
|
|
1206
|
+
});
|
|
1207
|
+
return formatted.join(", ").replace(", &", ", &");
|
|
1208
|
+
}
|
|
1209
|
+
isJapaneseAuthors(authors) {
|
|
1210
|
+
if (!authors || authors.length === 0) {
|
|
1211
|
+
return false;
|
|
1212
|
+
}
|
|
1213
|
+
return JAPANESE_PATTERN.test(authors[0].family);
|
|
1214
|
+
}
|
|
1215
|
+
getFirstAuthorFamily(item) {
|
|
1216
|
+
return item.author?.[0]?.family || "";
|
|
1217
|
+
}
|
|
1218
|
+
getYear(item) {
|
|
1219
|
+
const dateParts = item.issued?.["date-parts"];
|
|
1220
|
+
if (dateParts && dateParts[0] && dateParts[0][0]) {
|
|
1221
|
+
return dateParts[0][0];
|
|
1222
|
+
}
|
|
1223
|
+
return 0;
|
|
1224
|
+
}
|
|
1225
|
+
getIdentifier(item) {
|
|
1226
|
+
if (item.PMID) {
|
|
1227
|
+
return `PMID: ${item.PMID}`;
|
|
1228
|
+
}
|
|
1229
|
+
if (item.DOI) {
|
|
1230
|
+
return `DOI: ${item.DOI}`;
|
|
1231
|
+
}
|
|
1232
|
+
return null;
|
|
1233
|
+
}
|
|
1234
|
+
};
|
|
1235
|
+
|
|
1236
|
+
// src/core/pipeline.ts
|
|
1237
|
+
var PipelineError = class extends Error {
|
|
1238
|
+
constructor(message, stage, details) {
|
|
1239
|
+
super(message);
|
|
1240
|
+
this.stage = stage;
|
|
1241
|
+
this.details = details;
|
|
1242
|
+
this.name = "PipelineError";
|
|
1243
|
+
}
|
|
1244
|
+
};
|
|
1245
|
+
var Pipeline = class {
|
|
1246
|
+
constructor(config) {
|
|
1247
|
+
this.config = config;
|
|
1248
|
+
this.parser = new Parser();
|
|
1249
|
+
this.templateEngine = new TemplateEngine();
|
|
1250
|
+
this.templateLoader = new TemplateLoader();
|
|
1251
|
+
this.iconRegistry = new IconRegistryLoader();
|
|
1252
|
+
this.iconResolver = new IconResolver(this.iconRegistry);
|
|
1253
|
+
this.referenceManager = new ReferenceManager(
|
|
1254
|
+
config.references.connection.command
|
|
1255
|
+
);
|
|
1256
|
+
this.citationExtractor = new CitationExtractor();
|
|
1257
|
+
this.citationFormatter = new CitationFormatter(
|
|
1258
|
+
this.referenceManager,
|
|
1259
|
+
{
|
|
1260
|
+
author: {
|
|
1261
|
+
maxAuthors: config.references.format.maxAuthors,
|
|
1262
|
+
etAl: config.references.format.etAl,
|
|
1263
|
+
etAlJa: config.references.format.etAlJa
|
|
1264
|
+
},
|
|
1265
|
+
inline: {
|
|
1266
|
+
authorSep: config.references.format.authorSep,
|
|
1267
|
+
identifierSep: config.references.format.identifierSep
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
);
|
|
1271
|
+
this.transformer = new Transformer(
|
|
1272
|
+
this.templateEngine,
|
|
1273
|
+
this.templateLoader,
|
|
1274
|
+
this.iconResolver,
|
|
1275
|
+
this.citationFormatter
|
|
1276
|
+
);
|
|
1277
|
+
this.renderer = new Renderer();
|
|
1278
|
+
}
|
|
1279
|
+
parser;
|
|
1280
|
+
templateEngine;
|
|
1281
|
+
templateLoader;
|
|
1282
|
+
iconRegistry;
|
|
1283
|
+
iconResolver;
|
|
1284
|
+
referenceManager;
|
|
1285
|
+
citationExtractor;
|
|
1286
|
+
citationFormatter;
|
|
1287
|
+
transformer;
|
|
1288
|
+
renderer;
|
|
1289
|
+
warnings = [];
|
|
1290
|
+
/**
|
|
1291
|
+
* Run the full conversion pipeline
|
|
1292
|
+
*/
|
|
1293
|
+
async run(inputPath, options) {
|
|
1294
|
+
this.warnings = [];
|
|
1295
|
+
try {
|
|
1296
|
+
const presentation = await this.parseSource(inputPath);
|
|
1297
|
+
const citationIds = this.collectCitations(presentation);
|
|
1298
|
+
await this.resolveReferences(citationIds);
|
|
1299
|
+
const transformedSlides = await this.transformSlides(presentation);
|
|
1300
|
+
const output = this.render(transformedSlides, presentation);
|
|
1301
|
+
if (options?.outputPath) {
|
|
1302
|
+
await writeFile(options.outputPath, output, "utf-8");
|
|
1303
|
+
}
|
|
1304
|
+
return output;
|
|
1305
|
+
} catch (error) {
|
|
1306
|
+
if (error instanceof PipelineError) {
|
|
1307
|
+
throw error;
|
|
1308
|
+
}
|
|
1309
|
+
throw new PipelineError(
|
|
1310
|
+
error instanceof Error ? error.message : "Unknown error",
|
|
1311
|
+
"unknown",
|
|
1312
|
+
error
|
|
1313
|
+
);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Run the full pipeline with detailed result
|
|
1318
|
+
*/
|
|
1319
|
+
async runWithResult(inputPath, options) {
|
|
1320
|
+
this.warnings = [];
|
|
1321
|
+
try {
|
|
1322
|
+
const presentation = await this.parseSource(inputPath);
|
|
1323
|
+
const citationIds = this.collectCitations(presentation);
|
|
1324
|
+
await this.resolveReferences(citationIds);
|
|
1325
|
+
const transformedSlides = await this.transformSlides(presentation);
|
|
1326
|
+
const output = this.render(transformedSlides, presentation);
|
|
1327
|
+
if (options?.outputPath) {
|
|
1328
|
+
await writeFile(options.outputPath, output, "utf-8");
|
|
1329
|
+
}
|
|
1330
|
+
return {
|
|
1331
|
+
output,
|
|
1332
|
+
citations: citationIds,
|
|
1333
|
+
warnings: this.warnings,
|
|
1334
|
+
slideCount: presentation.slides.length
|
|
1335
|
+
};
|
|
1336
|
+
} catch (error) {
|
|
1337
|
+
if (error instanceof PipelineError) {
|
|
1338
|
+
throw error;
|
|
1339
|
+
}
|
|
1340
|
+
throw new PipelineError(
|
|
1341
|
+
error instanceof Error ? error.message : "Unknown error",
|
|
1342
|
+
"unknown",
|
|
1343
|
+
error
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
/**
|
|
1348
|
+
* Initialize the pipeline by loading templates and icon registry
|
|
1349
|
+
*/
|
|
1350
|
+
async initialize() {
|
|
1351
|
+
try {
|
|
1352
|
+
await this.templateLoader.loadBuiltIn(this.config.templates.builtin);
|
|
1353
|
+
if (this.config.templates.custom) {
|
|
1354
|
+
await this.templateLoader.loadCustom(this.config.templates.custom);
|
|
1355
|
+
}
|
|
1356
|
+
await this.iconRegistry.load(this.config.icons.registry);
|
|
1357
|
+
} catch (error) {
|
|
1358
|
+
throw new PipelineError(
|
|
1359
|
+
`Failed to initialize pipeline: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1360
|
+
"initialize",
|
|
1361
|
+
error
|
|
1362
|
+
);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
/**
|
|
1366
|
+
* Get collected warnings
|
|
1367
|
+
*/
|
|
1368
|
+
getWarnings() {
|
|
1369
|
+
return [...this.warnings];
|
|
1370
|
+
}
|
|
1371
|
+
// --- Private Stage Methods ---
|
|
1372
|
+
/**
|
|
1373
|
+
* Stage 1: Parse the YAML source file
|
|
1374
|
+
*/
|
|
1375
|
+
async parseSource(inputPath) {
|
|
1376
|
+
try {
|
|
1377
|
+
return await this.parser.parseFile(inputPath);
|
|
1378
|
+
} catch (error) {
|
|
1379
|
+
throw new PipelineError(
|
|
1380
|
+
`Failed to parse source file: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1381
|
+
"parse",
|
|
1382
|
+
error
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
/**
|
|
1387
|
+
* Stage 2: Collect all citation IDs from the presentation
|
|
1388
|
+
*/
|
|
1389
|
+
collectCitations(presentation) {
|
|
1390
|
+
const citations = this.citationExtractor.extractFromPresentation(presentation);
|
|
1391
|
+
return this.citationExtractor.getUniqueIds(citations);
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Stage 3: Resolve references from the reference manager
|
|
1395
|
+
*/
|
|
1396
|
+
async resolveReferences(ids) {
|
|
1397
|
+
if (!this.config.references.enabled || ids.length === 0) {
|
|
1398
|
+
return /* @__PURE__ */ new Map();
|
|
1399
|
+
}
|
|
1400
|
+
try {
|
|
1401
|
+
const items = await this.referenceManager.getByIds(ids);
|
|
1402
|
+
for (const id of ids) {
|
|
1403
|
+
if (!items.has(id)) {
|
|
1404
|
+
this.warnings.push(`Reference not found: ${id}`);
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
return items;
|
|
1408
|
+
} catch (error) {
|
|
1409
|
+
this.warnings.push(
|
|
1410
|
+
`Failed to resolve references: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1411
|
+
);
|
|
1412
|
+
return /* @__PURE__ */ new Map();
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Stage 4: Transform all slides using templates
|
|
1417
|
+
*/
|
|
1418
|
+
async transformSlides(presentation) {
|
|
1419
|
+
try {
|
|
1420
|
+
return await this.transformer.transformAll(presentation);
|
|
1421
|
+
} catch (error) {
|
|
1422
|
+
throw new PipelineError(
|
|
1423
|
+
`Failed to transform slides: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1424
|
+
"transform",
|
|
1425
|
+
error
|
|
1426
|
+
);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
/**
|
|
1430
|
+
* Stage 5: Render the final Marp markdown
|
|
1431
|
+
*/
|
|
1432
|
+
render(slides, presentation) {
|
|
1433
|
+
try {
|
|
1434
|
+
const notes = presentation.slides.map((slide) => slide.notes);
|
|
1435
|
+
const renderOptions = {
|
|
1436
|
+
includeTheme: true,
|
|
1437
|
+
notes
|
|
1438
|
+
};
|
|
1439
|
+
return this.renderer.render(slides, presentation.meta, renderOptions);
|
|
1440
|
+
} catch (error) {
|
|
1441
|
+
throw new PipelineError(
|
|
1442
|
+
`Failed to render output: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1443
|
+
"render",
|
|
1444
|
+
error
|
|
1445
|
+
);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
};
|
|
1449
|
+
|
|
1450
|
+
// src/index.ts
|
|
1451
|
+
var VERSION = "0.1.0";
|
|
1452
|
+
export {
|
|
1453
|
+
ParseError,
|
|
1454
|
+
Parser,
|
|
1455
|
+
Pipeline,
|
|
1456
|
+
PipelineError,
|
|
1457
|
+
Renderer,
|
|
1458
|
+
TemplateEngine,
|
|
1459
|
+
TemplateLoader,
|
|
1460
|
+
TransformError,
|
|
1461
|
+
Transformer,
|
|
1462
|
+
VERSION,
|
|
1463
|
+
ValidationError,
|
|
1464
|
+
jsonSchemaToZod,
|
|
1465
|
+
presentationSchema,
|
|
1466
|
+
templateDefSchema,
|
|
1467
|
+
validateWithJsonSchema
|
|
1468
|
+
};
|
|
1469
|
+
//# sourceMappingURL=index.js.map
|