@gpdoc/filekit 1.23.0 → 1.24.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/package.json +1 -1
- package/src/index.js +86 -4
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -13,6 +13,52 @@ const FRONT_MATTER = /^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/;
|
|
|
13
13
|
const MAX_URL_RESPONSE_BYTES = 10 * 1024 * 1024;
|
|
14
14
|
const URL_TIMEOUT_MS = 15_000;
|
|
15
15
|
|
|
16
|
+
export const GPDOC_PAGE_BREAK = '<div style="page-break-after: always;"></div>';
|
|
17
|
+
|
|
18
|
+
export function resolveSlideDelimiter(metadata = {}) {
|
|
19
|
+
const configured = typeof metadata?.slideDelimiter === 'string'
|
|
20
|
+
? metadata.slideDelimiter.trim()
|
|
21
|
+
: '';
|
|
22
|
+
return configured || GPDOC_PAGE_BREAK;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function splitMarkdownSlides(markdown, delimiter = GPDOC_PAGE_BREAK) {
|
|
26
|
+
// @spec FILE-MODEL-006, FILE-MODEL-007, FILE-MODEL-008, FILE-MODEL-009
|
|
27
|
+
const boundary = typeof delimiter === 'string' && delimiter.trim()
|
|
28
|
+
? delimiter.trim()
|
|
29
|
+
: GPDOC_PAGE_BREAK;
|
|
30
|
+
const slides = [];
|
|
31
|
+
let current = [];
|
|
32
|
+
let fence = null;
|
|
33
|
+
for (const line of String(markdown ?? '').replace(/\r\n/g, '\n').split('\n')) {
|
|
34
|
+
const marker = line.match(/^\s*(`{3,}|~{3,})/u)?.[1] ?? null;
|
|
35
|
+
if (marker) {
|
|
36
|
+
if (!fence) fence = { character: marker[0], length: marker.length };
|
|
37
|
+
else if (marker[0] === fence.character && marker.length >= fence.length) fence = null;
|
|
38
|
+
current.push(line);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (!fence && line.trim() === boundary) {
|
|
42
|
+
slides.push(current.join('\n').trim());
|
|
43
|
+
current = [];
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
current.push(line);
|
|
47
|
+
}
|
|
48
|
+
slides.push(current.join('\n').trim());
|
|
49
|
+
return slides;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function joinMarkdownSlides(slides, delimiter = GPDOC_PAGE_BREAK) {
|
|
53
|
+
// @spec FILE-MODEL-007, FILE-MODEL-008, FILE-MODEL-009
|
|
54
|
+
const boundary = typeof delimiter === 'string' && delimiter.trim()
|
|
55
|
+
? delimiter.trim()
|
|
56
|
+
: GPDOC_PAGE_BREAK;
|
|
57
|
+
return (Array.isArray(slides) ? slides : [slides ?? ''])
|
|
58
|
+
.map((slide) => String(slide ?? '').trim())
|
|
59
|
+
.join(`\n\n${boundary}\n\n`);
|
|
60
|
+
}
|
|
61
|
+
|
|
16
62
|
export class FileKitError extends Error {
|
|
17
63
|
constructor(code, message) {
|
|
18
64
|
super(message);
|
|
@@ -284,8 +330,15 @@ function parseGpdocJson(source) {
|
|
|
284
330
|
if (!isManagedMetadata(json) || !isPlainObject(json.content)) {
|
|
285
331
|
throw new FileKitError('INVALID_GPDOC', 'Managed GPDoc metadata must include id, filename, and a supported filetype.');
|
|
286
332
|
}
|
|
333
|
+
if (json.filetype === 'slides' && Array.isArray(json.content.slides)) {
|
|
334
|
+
return {
|
|
335
|
+
metadata: clone(json),
|
|
336
|
+
body: joinMarkdownSlides(json.content.slides, resolveSlideDelimiter(json)),
|
|
337
|
+
slides: json.content.slides.map((slide) => String(slide ?? '')),
|
|
338
|
+
};
|
|
339
|
+
}
|
|
287
340
|
if (typeof json.content.document !== 'string') {
|
|
288
|
-
throw new FileKitError('INVALID_GPDOC', 'This
|
|
341
|
+
throw new FileKitError('INVALID_GPDOC', 'This GPDoc file does not contain supported document or slide content.');
|
|
289
342
|
}
|
|
290
343
|
return { metadata: clone(json), body: json.content.document };
|
|
291
344
|
}
|
|
@@ -306,11 +359,15 @@ export function readDocument(source, filePath = '') {
|
|
|
306
359
|
}
|
|
307
360
|
if (format === 'json') {
|
|
308
361
|
const parsed = parseGpdocJson(source);
|
|
309
|
-
return { format: 'gpdoc-json', managed: true, filetype: parsed.metadata.filetype, title: parsed.metadata.filename, body: parsed.body, metadata: parsed.metadata, warnings: [] };
|
|
362
|
+
return { format: 'gpdoc-json', managed: true, filetype: parsed.metadata.filetype, title: parsed.metadata.filename, body: parsed.body, slides: parsed.slides, metadata: parsed.metadata, warnings: [] };
|
|
310
363
|
}
|
|
311
364
|
const parsed = parseManagedMarkdown(String(source));
|
|
312
365
|
if (parsed) {
|
|
313
|
-
|
|
366
|
+
const document = { format: 'gpdoc-markdown', managed: true, filetype: parsed.metadata.filetype, title: parsed.metadata.filename, body: parsed.body, metadata: parsed.metadata, warnings: [] };
|
|
367
|
+
if (parsed.metadata.filetype === 'slides') {
|
|
368
|
+
document.slides = splitMarkdownSlides(parsed.body, resolveSlideDelimiter(parsed.metadata));
|
|
369
|
+
}
|
|
370
|
+
return document;
|
|
314
371
|
}
|
|
315
372
|
return { format: 'markdown', managed: false, filetype: 'document', title: filenameTitle(filePath), body: String(source), warnings: [] };
|
|
316
373
|
}
|
|
@@ -327,6 +384,22 @@ export function createManagedDocument(title = 'Untitled', body = '') {
|
|
|
327
384
|
};
|
|
328
385
|
}
|
|
329
386
|
|
|
387
|
+
export function createManagedSlides(title = 'Untitled', slides = ['# Slide 1']) {
|
|
388
|
+
// @spec CLI-074, FILE-MODEL-011
|
|
389
|
+
const now = new Date().toISOString();
|
|
390
|
+
const normalizedSlides = (Array.isArray(slides) && slides.length ? slides : ['# Slide 1'])
|
|
391
|
+
.map((slide) => String(slide ?? ''));
|
|
392
|
+
return {
|
|
393
|
+
id: `file_${randomUUID()}`,
|
|
394
|
+
filename: title,
|
|
395
|
+
filetype: 'slides',
|
|
396
|
+
slideDelimiter: GPDOC_PAGE_BREAK,
|
|
397
|
+
created: now,
|
|
398
|
+
updated: now,
|
|
399
|
+
content: { slides: normalizedSlides },
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
330
403
|
export function serializeManagedMarkdown(metadata, body) {
|
|
331
404
|
// @spec CLI-001, CLI-066
|
|
332
405
|
// Markdown has a separate body. `content.document` is reserved for GPDoc JSON.
|
|
@@ -347,7 +420,16 @@ function markdownToText(markdown) {
|
|
|
347
420
|
export function convertDocument(document, targetFormat) {
|
|
348
421
|
const target = String(targetFormat).toLowerCase();
|
|
349
422
|
const metadata = document.managed ? clone(document.metadata) : createManagedDocument(document.title, document.body);
|
|
350
|
-
|
|
423
|
+
if (document.filetype === 'slides') {
|
|
424
|
+
metadata.slideDelimiter = resolveSlideDelimiter(metadata);
|
|
425
|
+
metadata.content = {
|
|
426
|
+
slides: Array.isArray(document.slides)
|
|
427
|
+
? document.slides.map((slide) => String(slide ?? ''))
|
|
428
|
+
: splitMarkdownSlides(document.body, metadata.slideDelimiter),
|
|
429
|
+
};
|
|
430
|
+
} else {
|
|
431
|
+
metadata.content = { document: document.body };
|
|
432
|
+
}
|
|
351
433
|
if (target === 'gpdoc') return serializeManagedMarkdown(metadata, document.body);
|
|
352
434
|
if (target === 'markdown' || target === 'md') return document.body;
|
|
353
435
|
if (target === 'html') return `<!doctype html>\n<html><head><meta charset="utf-8"><title>${escapeHtml(document.title)}</title></head><body>\n${marked.parse(document.body)}\n</body></html>\n`;
|