@bendyline/squisq-formats 1.3.0 → 1.4.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/chunk-6M7Z25LA.js +46 -0
- package/dist/chunk-6M7Z25LA.js.map +1 -0
- package/dist/{chunk-S3Y7H2BK.js → chunk-APSIAEXL.js} +9 -8
- package/dist/chunk-APSIAEXL.js.map +1 -0
- package/dist/{chunk-UDS45KUJ.js → chunk-BJQIJ4S4.js} +36 -52
- package/dist/chunk-BJQIJ4S4.js.map +1 -0
- package/dist/{chunk-33YRFXZZ.js → chunk-CRTC6DEX.js} +259 -13
- package/dist/chunk-CRTC6DEX.js.map +1 -0
- package/dist/chunk-FMOIGA5T.js +201 -0
- package/dist/chunk-FMOIGA5T.js.map +1 -0
- package/dist/{chunk-VN2KEOYB.js → chunk-I2GFTEOV.js} +321 -91
- package/dist/chunk-I2GFTEOV.js.map +1 -0
- package/dist/chunk-JBNVE2GV.js +116 -0
- package/dist/chunk-JBNVE2GV.js.map +1 -0
- package/dist/chunk-L53YIGYS.js +141 -0
- package/dist/chunk-L53YIGYS.js.map +1 -0
- package/dist/{chunk-ERZ627GR.js → chunk-ON232HJR.js} +11 -26
- package/dist/chunk-ON232HJR.js.map +1 -0
- package/dist/chunk-PN52A5AA.js +32 -0
- package/dist/chunk-PN52A5AA.js.map +1 -0
- package/dist/{chunk-7DEUGIOO.js → chunk-QSE7EWE7.js} +12 -87
- package/dist/chunk-QSE7EWE7.js.map +1 -0
- package/dist/container/index.d.ts +7 -5
- package/dist/container/index.js +39 -70
- package/dist/container/index.js.map +1 -1
- package/dist/csv/index.d.ts +37 -0
- package/dist/csv/index.js +13 -0
- package/dist/csv/index.js.map +1 -0
- package/dist/docx/index.d.ts +128 -15
- package/dist/docx/index.js +19 -22
- package/dist/docx/index.js.map +1 -1
- package/dist/epub/index.d.ts +66 -11
- package/dist/epub/index.js +11 -18
- package/dist/epub/index.js.map +1 -1
- package/dist/html/index.d.ts +315 -15
- package/dist/html/index.js +32 -165
- package/dist/html/index.js.map +1 -1
- package/dist/index.d.ts +10 -38
- package/dist/index.js +80 -37
- package/dist/index.js.map +1 -1
- package/dist/ooxml/index.d.ts +353 -14
- package/dist/ooxml/index.js +116 -22
- package/dist/ooxml/index.js.map +1 -1
- package/dist/pdf/index.d.ts +143 -17
- package/dist/pdf/index.js +16 -25
- package/dist/pdf/index.js.map +1 -1
- package/dist/pptx/index.d.ts +98 -21
- package/dist/pptx/index.js +18 -34
- package/dist/pptx/index.js.map +1 -1
- package/dist/xlsx/index.d.ts +29 -25
- package/dist/xlsx/index.js +13 -44
- package/dist/xlsx/index.js.map +1 -1
- package/package.json +7 -2
- package/src/__tests__/csvImport.test.ts +51 -0
- package/src/__tests__/exportThemeReconciliation.test.ts +87 -0
- package/src/__tests__/htmlImport.test.ts +57 -0
- package/src/__tests__/plainHtml.test.ts +31 -0
- package/src/__tests__/pptxExport.test.ts +138 -0
- package/src/__tests__/pptxImport.test.ts +70 -0
- package/src/__tests__/roundTripMatrix.fixtures.ts +86 -0
- package/src/__tests__/roundTripMatrix.helpers.ts +154 -0
- package/src/__tests__/roundTripMatrix.test.ts +136 -0
- package/src/__tests__/xlsxImport.test.ts +80 -0
- package/src/csv/index.ts +165 -0
- package/src/docx/export.ts +27 -44
- package/src/docx/import.ts +1 -2
- package/src/epub/export.ts +10 -27
- package/src/html/import.ts +297 -0
- package/src/html/index.ts +4 -0
- package/src/html/plainHtml.ts +66 -16
- package/src/index.ts +10 -3
- package/src/ooxml/namespaces.ts +3 -0
- package/src/pdf/export.ts +10 -5
- package/src/pdf/import.ts +1 -1
- package/src/pptx/export.ts +225 -85
- package/src/pptx/import.ts +156 -0
- package/src/pptx/index.ts +10 -28
- package/src/shared/inlineRuns.ts +99 -0
- package/src/shared/text.ts +41 -0
- package/src/xlsx/import.ts +159 -0
- package/src/xlsx/index.ts +11 -28
- package/dist/__tests__/container.test.d.ts +0 -8
- package/dist/__tests__/container.test.d.ts.map +0 -1
- package/dist/__tests__/container.test.js +0 -147
- package/dist/__tests__/container.test.js.map +0 -1
- package/dist/__tests__/docxExport.test.d.ts +0 -8
- package/dist/__tests__/docxExport.test.d.ts.map +0 -1
- package/dist/__tests__/docxExport.test.js +0 -383
- package/dist/__tests__/docxExport.test.js.map +0 -1
- package/dist/__tests__/docxImport.test.d.ts +0 -8
- package/dist/__tests__/docxImport.test.d.ts.map +0 -1
- package/dist/__tests__/docxImport.test.js +0 -309
- package/dist/__tests__/docxImport.test.js.map +0 -1
- package/dist/__tests__/epub.test.d.ts +0 -8
- package/dist/__tests__/epub.test.d.ts.map +0 -1
- package/dist/__tests__/epub.test.js +0 -472
- package/dist/__tests__/epub.test.js.map +0 -1
- package/dist/__tests__/html.test.d.ts +0 -8
- package/dist/__tests__/html.test.d.ts.map +0 -1
- package/dist/__tests__/html.test.js +0 -317
- package/dist/__tests__/html.test.js.map +0 -1
- package/dist/__tests__/ooxml.test.d.ts +0 -5
- package/dist/__tests__/ooxml.test.d.ts.map +0 -1
- package/dist/__tests__/ooxml.test.js +0 -152
- package/dist/__tests__/ooxml.test.js.map +0 -1
- package/dist/__tests__/pdfExport.test.d.ts +0 -8
- package/dist/__tests__/pdfExport.test.d.ts.map +0 -1
- package/dist/__tests__/pdfExport.test.js +0 -275
- package/dist/__tests__/pdfExport.test.js.map +0 -1
- package/dist/__tests__/pdfImport.test.d.ts +0 -11
- package/dist/__tests__/pdfImport.test.d.ts.map +0 -1
- package/dist/__tests__/pdfImport.test.js +0 -254
- package/dist/__tests__/pdfImport.test.js.map +0 -1
- package/dist/__tests__/plainHtml.test.d.ts +0 -5
- package/dist/__tests__/plainHtml.test.d.ts.map +0 -1
- package/dist/__tests__/plainHtml.test.js +0 -327
- package/dist/__tests__/plainHtml.test.js.map +0 -1
- package/dist/__tests__/plainHtmlBundle.test.d.ts +0 -11
- package/dist/__tests__/plainHtmlBundle.test.d.ts.map +0 -1
- package/dist/__tests__/plainHtmlBundle.test.js +0 -210
- package/dist/__tests__/plainHtmlBundle.test.js.map +0 -1
- package/dist/__tests__/roundTrip.test.d.ts +0 -7
- package/dist/__tests__/roundTrip.test.d.ts.map +0 -1
- package/dist/__tests__/roundTrip.test.js +0 -173
- package/dist/__tests__/roundTrip.test.js.map +0 -1
- package/dist/chunk-33YRFXZZ.js.map +0 -1
- package/dist/chunk-67KIJHV2.js +0 -21
- package/dist/chunk-67KIJHV2.js.map +0 -1
- package/dist/chunk-7DEUGIOO.js.map +0 -1
- package/dist/chunk-ERZ627GR.js.map +0 -1
- package/dist/chunk-S3Y7H2BK.js.map +0 -1
- package/dist/chunk-UDS45KUJ.js.map +0 -1
- package/dist/chunk-VN2KEOYB.js.map +0 -1
- package/dist/chunk-YN5HFCEW.js +0 -120
- package/dist/chunk-YN5HFCEW.js.map +0 -1
- package/dist/container/index.d.ts.map +0 -1
- package/dist/docx/export.d.ts +0 -69
- package/dist/docx/export.d.ts.map +0 -1
- package/dist/docx/export.js +0 -985
- package/dist/docx/export.js.map +0 -1
- package/dist/docx/import.d.ts +0 -65
- package/dist/docx/import.d.ts.map +0 -1
- package/dist/docx/import.js +0 -779
- package/dist/docx/import.js.map +0 -1
- package/dist/docx/index.d.ts.map +0 -1
- package/dist/docx/styles.d.ts +0 -68
- package/dist/docx/styles.d.ts.map +0 -1
- package/dist/docx/styles.js +0 -125
- package/dist/docx/styles.js.map +0 -1
- package/dist/epub/export.d.ts +0 -72
- package/dist/epub/export.d.ts.map +0 -1
- package/dist/epub/export.js +0 -698
- package/dist/epub/export.js.map +0 -1
- package/dist/epub/index.d.ts.map +0 -1
- package/dist/html/docsHtmlBundle.d.ts +0 -53
- package/dist/html/docsHtmlBundle.d.ts.map +0 -1
- package/dist/html/docsHtmlBundle.js +0 -299
- package/dist/html/docsHtmlBundle.js.map +0 -1
- package/dist/html/htmlTemplate.d.ts +0 -70
- package/dist/html/htmlTemplate.d.ts.map +0 -1
- package/dist/html/htmlTemplate.js +0 -282
- package/dist/html/htmlTemplate.js.map +0 -1
- package/dist/html/imageUtils.d.ts +0 -29
- package/dist/html/imageUtils.d.ts.map +0 -1
- package/dist/html/imageUtils.js +0 -63
- package/dist/html/imageUtils.js.map +0 -1
- package/dist/html/index.d.ts.map +0 -1
- package/dist/html/plainHtml.d.ts +0 -79
- package/dist/html/plainHtml.d.ts.map +0 -1
- package/dist/html/plainHtml.js +0 -614
- package/dist/html/plainHtml.js.map +0 -1
- package/dist/html/plainHtmlBundle.d.ts +0 -93
- package/dist/html/plainHtmlBundle.d.ts.map +0 -1
- package/dist/html/plainHtmlBundle.js +0 -357
- package/dist/html/plainHtmlBundle.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/ooxml/index.d.ts.map +0 -1
- package/dist/ooxml/namespaces.d.ts +0 -77
- package/dist/ooxml/namespaces.d.ts.map +0 -1
- package/dist/ooxml/namespaces.js +0 -104
- package/dist/ooxml/namespaces.js.map +0 -1
- package/dist/ooxml/reader.d.ts +0 -52
- package/dist/ooxml/reader.d.ts.map +0 -1
- package/dist/ooxml/reader.js +0 -190
- package/dist/ooxml/reader.js.map +0 -1
- package/dist/ooxml/types.d.ts +0 -82
- package/dist/ooxml/types.d.ts.map +0 -1
- package/dist/ooxml/types.js +0 -9
- package/dist/ooxml/types.js.map +0 -1
- package/dist/ooxml/writer.d.ts +0 -71
- package/dist/ooxml/writer.d.ts.map +0 -1
- package/dist/ooxml/writer.js +0 -200
- package/dist/ooxml/writer.js.map +0 -1
- package/dist/ooxml/xmlUtils.d.ts +0 -76
- package/dist/ooxml/xmlUtils.d.ts.map +0 -1
- package/dist/ooxml/xmlUtils.js +0 -108
- package/dist/ooxml/xmlUtils.js.map +0 -1
- package/dist/pdf/export.d.ts +0 -56
- package/dist/pdf/export.d.ts.map +0 -1
- package/dist/pdf/export.js +0 -768
- package/dist/pdf/export.js.map +0 -1
- package/dist/pdf/import.d.ts +0 -94
- package/dist/pdf/import.d.ts.map +0 -1
- package/dist/pdf/import.js +0 -796
- package/dist/pdf/import.js.map +0 -1
- package/dist/pdf/index.d.ts.map +0 -1
- package/dist/pdf/styles.d.ts +0 -142
- package/dist/pdf/styles.d.ts.map +0 -1
- package/dist/pdf/styles.js +0 -145
- package/dist/pdf/styles.js.map +0 -1
- package/dist/pptx/export.d.ts +0 -75
- package/dist/pptx/export.d.ts.map +0 -1
- package/dist/pptx/export.js +0 -724
- package/dist/pptx/export.js.map +0 -1
- package/dist/pptx/index.d.ts.map +0 -1
- package/dist/pptx/styles.d.ts +0 -54
- package/dist/pptx/styles.d.ts.map +0 -1
- package/dist/pptx/styles.js +0 -73
- package/dist/pptx/styles.js.map +0 -1
- package/dist/pptx/templates.d.ts +0 -24
- package/dist/pptx/templates.d.ts.map +0 -1
- package/dist/pptx/templates.js +0 -158
- package/dist/pptx/templates.js.map +0 -1
- package/dist/xlsx/index.d.ts.map +0 -1
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML Import Module — @bendyline/squisq-formats/html
|
|
3
|
+
*
|
|
4
|
+
* Converts an HTML document (or fragment) into a squisq `MarkdownDocument`.
|
|
5
|
+
* Built on the existing `parseHtmlToNodes` (parse5-backed) + `sanitizeHtmlNodes`
|
|
6
|
+
* from `@bendyline/squisq/markdown`, so no new dependency is introduced.
|
|
7
|
+
*
|
|
8
|
+
* The primary consumer is email: HTML mail bodies are hostile input, so the
|
|
9
|
+
* parse is sanitized by default (scripts/styles/event handlers/dangerous URLs
|
|
10
|
+
* stripped) before the tree is walked into markdown nodes.
|
|
11
|
+
*
|
|
12
|
+
* Two entry points mirror the docx/pdf importers:
|
|
13
|
+
* - `htmlToMarkdownDoc(data, options?)` → `MarkdownDocument`
|
|
14
|
+
* - `htmlToMarkdown(html, options?)` → markdown string (convenience for the
|
|
15
|
+
* string-in/string-out path email body conversion needs)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
type MarkdownBlockNode,
|
|
20
|
+
type MarkdownDocument,
|
|
21
|
+
type MarkdownInlineNode,
|
|
22
|
+
type MarkdownListItem,
|
|
23
|
+
type MarkdownTableCell,
|
|
24
|
+
type MarkdownTableRow,
|
|
25
|
+
type HtmlElement,
|
|
26
|
+
type HtmlNode,
|
|
27
|
+
parseHtmlToNodes,
|
|
28
|
+
sanitizeHtmlNodes,
|
|
29
|
+
stringifyMarkdown,
|
|
30
|
+
} from '@bendyline/squisq/markdown';
|
|
31
|
+
|
|
32
|
+
export interface HtmlImportOptions {
|
|
33
|
+
/**
|
|
34
|
+
* Strip scripts / styles / event handlers / dangerous URLs before walking
|
|
35
|
+
* the tree. Default `true` — HTML email is untrusted; only disable for
|
|
36
|
+
* trusted input where you want raw fidelity.
|
|
37
|
+
*/
|
|
38
|
+
sanitize?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── tag classification ──────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
const HEADINGS: Record<string, 1 | 2 | 3 | 4 | 5 | 6> = {
|
|
44
|
+
h1: 1,
|
|
45
|
+
h2: 2,
|
|
46
|
+
h3: 3,
|
|
47
|
+
h4: 4,
|
|
48
|
+
h5: 5,
|
|
49
|
+
h6: 6,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Containers with no semantic block meaning — unwrap and walk their children.
|
|
53
|
+
const TRANSPARENT_BLOCK = new Set([
|
|
54
|
+
'div',
|
|
55
|
+
'section',
|
|
56
|
+
'article',
|
|
57
|
+
'header',
|
|
58
|
+
'footer',
|
|
59
|
+
'main',
|
|
60
|
+
'aside',
|
|
61
|
+
'nav',
|
|
62
|
+
'figure',
|
|
63
|
+
'figcaption',
|
|
64
|
+
'form',
|
|
65
|
+
'fieldset',
|
|
66
|
+
'body',
|
|
67
|
+
'html',
|
|
68
|
+
'center',
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
// Elements that carry no content we want (and whose text must not leak).
|
|
72
|
+
const DROP = new Set(['script', 'style', 'head', 'title', 'noscript', 'template', 'svg']);
|
|
73
|
+
|
|
74
|
+
const INLINE_STRONG = new Set(['strong', 'b']);
|
|
75
|
+
const INLINE_EM = new Set(['em', 'i']);
|
|
76
|
+
const INLINE_DEL = new Set(['del', 's', 'strike']);
|
|
77
|
+
const INLINE_TRANSPARENT = new Set(['span', 'font', 'abbr', 'mark', 'small', 'sub', 'sup', 'u']);
|
|
78
|
+
|
|
79
|
+
const isElement = (n: HtmlNode): n is HtmlElement => n.type === 'htmlElement';
|
|
80
|
+
const isText = (n: HtmlNode): n is { type: 'htmlText'; value: string } => n.type === 'htmlText';
|
|
81
|
+
|
|
82
|
+
const collapseWs = (s: string): string => s.replace(/\s+/g, ' ');
|
|
83
|
+
|
|
84
|
+
// ── inline conversion ───────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
function inlinesFromNodes(nodes: HtmlNode[]): MarkdownInlineNode[] {
|
|
87
|
+
const out: MarkdownInlineNode[] = [];
|
|
88
|
+
for (const node of nodes) {
|
|
89
|
+
if (isText(node)) {
|
|
90
|
+
const value = collapseWs(node.value);
|
|
91
|
+
if (value) out.push({ type: 'text', value });
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (!isElement(node)) continue; // comment
|
|
95
|
+
const tag = node.tagName.toLowerCase();
|
|
96
|
+
if (DROP.has(tag)) continue;
|
|
97
|
+
|
|
98
|
+
if (tag === 'br') {
|
|
99
|
+
out.push({ type: 'break' });
|
|
100
|
+
} else if (INLINE_STRONG.has(tag)) {
|
|
101
|
+
out.push({ type: 'strong', children: inlinesFromNodes(node.children) });
|
|
102
|
+
} else if (INLINE_EM.has(tag)) {
|
|
103
|
+
out.push({ type: 'emphasis', children: inlinesFromNodes(node.children) });
|
|
104
|
+
} else if (INLINE_DEL.has(tag)) {
|
|
105
|
+
out.push({ type: 'delete', children: inlinesFromNodes(node.children) });
|
|
106
|
+
} else if (tag === 'code' || tag === 'kbd' || tag === 'samp' || tag === 'tt') {
|
|
107
|
+
out.push({ type: 'inlineCode', value: textContent(node) });
|
|
108
|
+
} else if (tag === 'a') {
|
|
109
|
+
const url = node.attributes.href ?? '';
|
|
110
|
+
const children = inlinesFromNodes(node.children);
|
|
111
|
+
out.push({
|
|
112
|
+
type: 'link',
|
|
113
|
+
url,
|
|
114
|
+
children: children.length > 0 ? children : [{ type: 'text', value: url }],
|
|
115
|
+
});
|
|
116
|
+
} else if (tag === 'img') {
|
|
117
|
+
const url = node.attributes.src ?? '';
|
|
118
|
+
const alt = node.attributes.alt;
|
|
119
|
+
if (url) out.push({ type: 'image', url, ...(alt ? { alt } : {}) });
|
|
120
|
+
} else {
|
|
121
|
+
// Unknown / transparent inline (span, font, …): flatten children.
|
|
122
|
+
out.push(...inlinesFromNodes(node.children));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Flatten an element's descendant text (for code blocks / inline code). */
|
|
129
|
+
function textContent(node: HtmlNode): string {
|
|
130
|
+
if (isText(node)) return node.value;
|
|
131
|
+
if (isElement(node)) return node.children.map(textContent).join('');
|
|
132
|
+
return '';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const onlyWhitespace = (inlines: MarkdownInlineNode[]): boolean =>
|
|
136
|
+
inlines.every((n) => n.type === 'text' && n.value.trim() === '');
|
|
137
|
+
|
|
138
|
+
// ── block conversion ────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
function blocksFromNodes(nodes: HtmlNode[]): MarkdownBlockNode[] {
|
|
141
|
+
const out: MarkdownBlockNode[] = [];
|
|
142
|
+
let inlineBuffer: HtmlNode[] = [];
|
|
143
|
+
|
|
144
|
+
const flush = () => {
|
|
145
|
+
if (inlineBuffer.length === 0) return;
|
|
146
|
+
const inlines = inlinesFromNodes(inlineBuffer);
|
|
147
|
+
inlineBuffer = [];
|
|
148
|
+
if (inlines.length > 0 && !onlyWhitespace(inlines)) {
|
|
149
|
+
out.push({ type: 'paragraph', children: inlines });
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
for (const node of nodes) {
|
|
154
|
+
if (isText(node)) {
|
|
155
|
+
inlineBuffer.push(node);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (!isElement(node)) continue;
|
|
159
|
+
const tag = node.tagName.toLowerCase();
|
|
160
|
+
if (DROP.has(tag)) continue;
|
|
161
|
+
|
|
162
|
+
const block = blockForElement(node, tag);
|
|
163
|
+
if (block === 'inline') {
|
|
164
|
+
inlineBuffer.push(node);
|
|
165
|
+
} else if (block) {
|
|
166
|
+
flush();
|
|
167
|
+
out.push(...block);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
flush();
|
|
171
|
+
return out;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Returns the block node(s) for a block element, `'inline'` for inline ones. */
|
|
175
|
+
function blockForElement(node: HtmlElement, tag: string): MarkdownBlockNode[] | 'inline' | null {
|
|
176
|
+
if (tag in HEADINGS) {
|
|
177
|
+
const children = inlinesFromNodes(node.children);
|
|
178
|
+
return [{ type: 'heading', depth: HEADINGS[tag]!, children }];
|
|
179
|
+
}
|
|
180
|
+
if (tag === 'p') {
|
|
181
|
+
const children = inlinesFromNodes(node.children);
|
|
182
|
+
return children.length > 0 && !onlyWhitespace(children)
|
|
183
|
+
? [{ type: 'paragraph', children }]
|
|
184
|
+
: [];
|
|
185
|
+
}
|
|
186
|
+
if (tag === 'br') return 'inline';
|
|
187
|
+
if (tag === 'hr') return [{ type: 'thematicBreak' }];
|
|
188
|
+
if (tag === 'blockquote') {
|
|
189
|
+
return [{ type: 'blockquote', children: blocksFromNodes(node.children) }];
|
|
190
|
+
}
|
|
191
|
+
if (tag === 'pre') {
|
|
192
|
+
return [{ type: 'code', value: stripTrailingNewline(textContent(node)) }];
|
|
193
|
+
}
|
|
194
|
+
if (tag === 'ul' || tag === 'ol') {
|
|
195
|
+
return [listFromElement(node, tag === 'ol')];
|
|
196
|
+
}
|
|
197
|
+
if (tag === 'table') {
|
|
198
|
+
const table = tableFromElement(node);
|
|
199
|
+
return table ? [table] : [];
|
|
200
|
+
}
|
|
201
|
+
if (TRANSPARENT_BLOCK.has(tag)) {
|
|
202
|
+
return blocksFromNodes(node.children);
|
|
203
|
+
}
|
|
204
|
+
if (
|
|
205
|
+
INLINE_STRONG.has(tag) ||
|
|
206
|
+
INLINE_EM.has(tag) ||
|
|
207
|
+
INLINE_DEL.has(tag) ||
|
|
208
|
+
INLINE_TRANSPARENT.has(tag) ||
|
|
209
|
+
tag === 'a' ||
|
|
210
|
+
tag === 'img' ||
|
|
211
|
+
tag === 'code' ||
|
|
212
|
+
tag === 'kbd' ||
|
|
213
|
+
tag === 'samp'
|
|
214
|
+
) {
|
|
215
|
+
return 'inline';
|
|
216
|
+
}
|
|
217
|
+
// Unknown element: treat as a transparent container so its content survives.
|
|
218
|
+
return blocksFromNodes(node.children);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function listFromElement(node: HtmlElement, ordered: boolean): MarkdownBlockNode {
|
|
222
|
+
const items: MarkdownListItem[] = [];
|
|
223
|
+
for (const child of node.children) {
|
|
224
|
+
if (isElement(child) && child.tagName.toLowerCase() === 'li') {
|
|
225
|
+
const blocks = blocksFromNodes(child.children);
|
|
226
|
+
items.push({ type: 'listItem', children: blocks });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const startAttr = node.attributes.start;
|
|
230
|
+
const start = ordered && startAttr ? Number.parseInt(startAttr, 10) : undefined;
|
|
231
|
+
return {
|
|
232
|
+
type: 'list',
|
|
233
|
+
ordered,
|
|
234
|
+
...(start !== undefined && Number.isFinite(start) ? { start } : {}),
|
|
235
|
+
children: items,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function tableFromElement(node: HtmlElement): MarkdownBlockNode | null {
|
|
240
|
+
const rows: MarkdownTableRow[] = [];
|
|
241
|
+
const collectRows = (n: HtmlElement) => {
|
|
242
|
+
for (const child of n.children) {
|
|
243
|
+
if (!isElement(child)) continue;
|
|
244
|
+
const t = child.tagName.toLowerCase();
|
|
245
|
+
if (t === 'tr') {
|
|
246
|
+
const cells: MarkdownTableCell[] = [];
|
|
247
|
+
for (const cell of child.children) {
|
|
248
|
+
if (
|
|
249
|
+
isElement(cell) &&
|
|
250
|
+
(cell.tagName.toLowerCase() === 'td' || cell.tagName.toLowerCase() === 'th')
|
|
251
|
+
) {
|
|
252
|
+
cells.push({ type: 'tableCell', children: inlinesFromNodes(cell.children) });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (cells.length > 0) rows.push({ type: 'tableRow', children: cells });
|
|
256
|
+
} else if (t === 'thead' || t === 'tbody' || t === 'tfoot') {
|
|
257
|
+
collectRows(child);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
collectRows(node);
|
|
262
|
+
if (rows.length === 0) return null;
|
|
263
|
+
return { type: 'table', children: rows };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const stripTrailingNewline = (s: string): string => s.replace(/\n+$/, '');
|
|
267
|
+
|
|
268
|
+
// ── public API ──────────────────────────────────────────────────────
|
|
269
|
+
|
|
270
|
+
function toHtmlString(data: ArrayBuffer | Uint8Array | string): string {
|
|
271
|
+
if (typeof data === 'string') return data;
|
|
272
|
+
const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
273
|
+
return new TextDecoder('utf-8').decode(bytes);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Parse HTML into a squisq `MarkdownDocument`. */
|
|
277
|
+
export function htmlToMarkdownDocSync(
|
|
278
|
+
html: string,
|
|
279
|
+
options: HtmlImportOptions = {},
|
|
280
|
+
): MarkdownDocument {
|
|
281
|
+
let nodes = parseHtmlToNodes(html);
|
|
282
|
+
if (options.sanitize !== false) nodes = sanitizeHtmlNodes(nodes);
|
|
283
|
+
return { type: 'document', children: blocksFromNodes(nodes) };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** Async, ArrayBuffer-accepting entry mirroring docx/pdf importers. */
|
|
287
|
+
export async function htmlToMarkdownDoc(
|
|
288
|
+
data: ArrayBuffer | Uint8Array | string,
|
|
289
|
+
options: HtmlImportOptions = {},
|
|
290
|
+
): Promise<MarkdownDocument> {
|
|
291
|
+
return htmlToMarkdownDocSync(toHtmlString(data), options);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Convenience string→string conversion (used for email HTML bodies). */
|
|
295
|
+
export function htmlToMarkdown(html: string, options: HtmlImportOptions = {}): string {
|
|
296
|
+
return stringifyMarkdown(htmlToMarkdownDocSync(html, options));
|
|
297
|
+
}
|
package/src/html/index.ts
CHANGED
|
@@ -195,3 +195,7 @@ export { markdownDocsToPlainHtmlBundle, collectLinkRefs } from './plainHtmlBundl
|
|
|
195
195
|
export type { PlainHtmlBundleOptions } from './plainHtmlBundle.js';
|
|
196
196
|
export { markdownDocsToHtmlBundle } from './docsHtmlBundle.js';
|
|
197
197
|
export type { HtmlBundleOptions } from './docsHtmlBundle.js';
|
|
198
|
+
|
|
199
|
+
// ── Import (HTML → MarkdownDocument) ────────────────────────────────
|
|
200
|
+
export { htmlToMarkdown, htmlToMarkdownDoc, htmlToMarkdownDocSync } from './import.js';
|
|
201
|
+
export type { HtmlImportOptions } from './import.js';
|
package/src/html/plainHtml.ts
CHANGED
|
@@ -14,9 +14,18 @@
|
|
|
14
14
|
* blob URLs (live preview) or data URIs (single-file export).
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import
|
|
17
|
+
import {
|
|
18
|
+
sanitizeHtmlNodes,
|
|
19
|
+
sanitizeUrl,
|
|
20
|
+
type HtmlPolicy,
|
|
21
|
+
type MarkdownDocument,
|
|
22
|
+
type MarkdownNode,
|
|
23
|
+
type HtmlNode,
|
|
24
|
+
readFrontmatterThemeId,
|
|
25
|
+
} from '@bendyline/squisq/markdown';
|
|
18
26
|
import type { Theme } from '@bendyline/squisq/schemas';
|
|
19
|
-
import { resolveFontFamily, buildGoogleFontsUrl
|
|
27
|
+
import { resolveFontFamily, buildGoogleFontsUrl } from '@bendyline/squisq/schemas';
|
|
28
|
+
import { resolveThemeForDoc } from '@bendyline/squisq/doc';
|
|
20
29
|
|
|
21
30
|
// ── Public Types ───────────────────────────────────────────────────
|
|
22
31
|
|
|
@@ -71,6 +80,11 @@ export interface PlainHtmlExportOptions {
|
|
|
71
80
|
* option is not provided.
|
|
72
81
|
*/
|
|
73
82
|
iconsCss?: string;
|
|
83
|
+
/**
|
|
84
|
+
* Raw HTML policy. Defaults to `sanitize`, which removes unsafe tags,
|
|
85
|
+
* event handlers, and executable URL schemes before emitting HTML.
|
|
86
|
+
*/
|
|
87
|
+
htmlPolicy?: HtmlPolicy;
|
|
74
88
|
}
|
|
75
89
|
|
|
76
90
|
/**
|
|
@@ -81,6 +95,7 @@ export interface PlainHtmlExportOptions {
|
|
|
81
95
|
interface RenderCtx {
|
|
82
96
|
images?: Map<string, string>;
|
|
83
97
|
links?: Map<string, string>;
|
|
98
|
+
htmlPolicy: HtmlPolicy;
|
|
84
99
|
}
|
|
85
100
|
|
|
86
101
|
// ── Public API ─────────────────────────────────────────────────────
|
|
@@ -97,19 +112,20 @@ export function markdownDocToPlainHtml(
|
|
|
97
112
|
doc: MarkdownDocument,
|
|
98
113
|
options: PlainHtmlExportOptions = {},
|
|
99
114
|
): string {
|
|
100
|
-
const { title = 'Document', images, links, themeId, iconsCss } = options;
|
|
115
|
+
const { title = 'Document', images, links, themeId, iconsCss, htmlPolicy = 'sanitize' } = options;
|
|
101
116
|
// Fall back chain for theme: explicit `theme` → explicit `themeId`
|
|
102
117
|
// option → doc frontmatter `themeId`. Hosts whose export dialog
|
|
103
118
|
// tracks themes by id can pass `themeId` straight through; authored
|
|
104
119
|
// docs with `themeId: warm-earth` in frontmatter get styled
|
|
105
120
|
// automatically when neither is supplied.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const
|
|
121
|
+
// Resolution is doc-scoped via `resolveThemeForDoc`, so an inline
|
|
122
|
+
// `squisq-custom-themes` id resolves here with no global registration —
|
|
123
|
+
// and `readFrontmatterThemeId` picks up the editor's canonical
|
|
124
|
+
// `squisq-theme` key. Stays undefined when nothing selects a theme so
|
|
125
|
+
// un-themed exports render unstyled (unchanged behavior).
|
|
126
|
+
const resolveId = themeId ?? readFrontmatterThemeId(doc.frontmatter);
|
|
127
|
+
const theme = options.theme ?? (resolveId ? resolveThemeForDoc(doc, resolveId) : undefined);
|
|
128
|
+
const ctx: RenderCtx = { images, links, htmlPolicy };
|
|
113
129
|
const body = renderTopLevel(doc.children, ctx);
|
|
114
130
|
const fontsLink = theme ? renderFontsLink(theme) : '';
|
|
115
131
|
// Resolve how to load FontAwesome — only when the doc actually uses
|
|
@@ -258,7 +274,9 @@ interface FeaturedImage {
|
|
|
258
274
|
|
|
259
275
|
function renderFeatureImage(img: FeaturedImage, ctx: RenderCtx | undefined): string {
|
|
260
276
|
const resolved = ctx?.images?.get(img.src) ?? img.src;
|
|
261
|
-
const
|
|
277
|
+
const safeSrc = sanitizeUrl(resolved, 'media');
|
|
278
|
+
if (!safeSrc) return '<div class="squisq-feature__media squisq-feature__media--empty"></div>';
|
|
279
|
+
const attrs = [`src="${escapeAttr(safeSrc)}"`, `alt="${escapeAttr(img.alt)}"`];
|
|
262
280
|
// Emit `width` / `height` attributes only when the source HTML had
|
|
263
281
|
// them. The CSS rules then know to honor those values rather than
|
|
264
282
|
// stretching the image to fill the column.
|
|
@@ -604,12 +622,16 @@ function nodeToHtml(node: MarkdownNode | undefined | null, ctx?: RenderCtx): str
|
|
|
604
622
|
case 'link': {
|
|
605
623
|
const original = node.url ?? '';
|
|
606
624
|
const rewritten = ctx?.links?.get(original) ?? original;
|
|
607
|
-
|
|
625
|
+
const safeHref = sanitizeUrl(rewritten, 'link');
|
|
626
|
+
if (!safeHref) return childrenToHtml(node, ctx);
|
|
627
|
+
return `<a href="${escapeAttr(safeHref)}">${childrenToHtml(node, ctx)}</a>`;
|
|
608
628
|
}
|
|
609
629
|
case 'image': {
|
|
610
630
|
const original = node.url ?? '';
|
|
611
631
|
const resolved = ctx?.images?.get(original) ?? original;
|
|
612
|
-
|
|
632
|
+
const safeSrc = sanitizeUrl(resolved, 'media');
|
|
633
|
+
if (!safeSrc) return escapeHtml(node.alt ?? '');
|
|
634
|
+
return `<img src="${escapeAttr(safeSrc)}" alt="${escapeAttr(node.alt ?? '')}" />`;
|
|
613
635
|
}
|
|
614
636
|
case 'thematicBreak':
|
|
615
637
|
return '<hr />';
|
|
@@ -626,11 +648,12 @@ function nodeToHtml(node: MarkdownNode | undefined | null, ctx?: RenderCtx): str
|
|
|
626
648
|
}
|
|
627
649
|
case 'htmlBlock':
|
|
628
650
|
case 'htmlInline':
|
|
651
|
+
if (resolveHtmlPolicy(ctx) === 'strip') return '';
|
|
629
652
|
// Resized images and other authored HTML survive the round-trip
|
|
630
653
|
// as parsed `htmlChildren` — rewriting `<img src>` through the
|
|
631
654
|
// image map keeps the preview consistent with the markdown-image
|
|
632
655
|
// path. Other tags pass through unmodified.
|
|
633
|
-
return htmlChildrenToHtml(node.htmlChildren, ctx);
|
|
656
|
+
return htmlChildrenToHtml(resolveHtmlNodes(node.htmlChildren, ctx), ctx);
|
|
634
657
|
default: {
|
|
635
658
|
// Unknown / unhandled node — recurse into children if any so we
|
|
636
659
|
// don't drop content (e.g. directives, footnotes).
|
|
@@ -682,18 +705,28 @@ function tableToHtml(
|
|
|
682
705
|
return parts.join('');
|
|
683
706
|
}
|
|
684
707
|
|
|
708
|
+
function resolveHtmlPolicy(ctx: RenderCtx | undefined): HtmlPolicy {
|
|
709
|
+
return ctx?.htmlPolicy ?? 'sanitize';
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function resolveHtmlNodes(nodes: HtmlNode[] | undefined, ctx: RenderCtx | undefined): HtmlNode[] {
|
|
713
|
+
if (!nodes || nodes.length === 0) return [];
|
|
714
|
+
return resolveHtmlPolicy(ctx) === 'trusted' ? nodes : sanitizeHtmlNodes(nodes);
|
|
715
|
+
}
|
|
716
|
+
|
|
685
717
|
function htmlChildrenToHtml(nodes: HtmlNode[] | undefined, ctx?: RenderCtx): string {
|
|
686
718
|
if (!nodes || nodes.length === 0) return '';
|
|
719
|
+
const trusted = resolveHtmlPolicy(ctx) === 'trusted';
|
|
687
720
|
const out: string[] = [];
|
|
688
721
|
for (const node of nodes) {
|
|
689
722
|
if (node.type === 'htmlText') {
|
|
690
723
|
// HtmlText already represents authored HTML — emit verbatim so
|
|
691
724
|
// entity references the user wrote (e.g. `&`) survive.
|
|
692
|
-
out.push(node.value);
|
|
725
|
+
out.push(trusted ? node.value : escapeHtml(node.value));
|
|
693
726
|
continue;
|
|
694
727
|
}
|
|
695
728
|
if (node.type === 'htmlComment') {
|
|
696
|
-
out.push(`<!--${node.value}-->`);
|
|
729
|
+
if (trusted) out.push(`<!--${node.value}-->`);
|
|
697
730
|
continue;
|
|
698
731
|
}
|
|
699
732
|
// htmlElement
|
|
@@ -713,6 +746,23 @@ function htmlChildrenToHtml(nodes: HtmlNode[] | undefined, ctx?: RenderCtx): str
|
|
|
713
746
|
if ((tag === 'video' || tag === 'audio') && typeof attrs.poster === 'string') {
|
|
714
747
|
attrs.poster = ctx?.images?.get(attrs.poster) ?? attrs.poster;
|
|
715
748
|
}
|
|
749
|
+
if (!trusted) {
|
|
750
|
+
if (typeof attrs.src === 'string') {
|
|
751
|
+
const safeSrc = sanitizeUrl(attrs.src, 'media');
|
|
752
|
+
if (!safeSrc) delete attrs.src;
|
|
753
|
+
else attrs.src = safeSrc;
|
|
754
|
+
}
|
|
755
|
+
if (typeof attrs.poster === 'string') {
|
|
756
|
+
const safePoster = sanitizeUrl(attrs.poster, 'media');
|
|
757
|
+
if (!safePoster) delete attrs.poster;
|
|
758
|
+
else attrs.poster = safePoster;
|
|
759
|
+
}
|
|
760
|
+
if (typeof attrs.href === 'string') {
|
|
761
|
+
const safeHref = sanitizeUrl(attrs.href, 'link');
|
|
762
|
+
if (!safeHref) delete attrs.href;
|
|
763
|
+
else attrs.href = safeHref;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
716
766
|
const attrStr = Object.entries(attrs)
|
|
717
767
|
.map(([k, v]) => ` ${k}="${escapeAttr(v)}"`)
|
|
718
768
|
.join('');
|
package/src/index.ts
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* Supported formats:
|
|
8
8
|
* - **DOCX** — Microsoft Word (import + export) ✅
|
|
9
9
|
* - **PDF** — Portable Document Format (import + export) ✅
|
|
10
|
-
* - **PPTX** — Microsoft PowerPoint (export ✅, import
|
|
11
|
-
* - **XLSX** — Microsoft Excel (planned)
|
|
10
|
+
* - **PPTX** — Microsoft PowerPoint (export ✅, import ✅)
|
|
11
|
+
* - **XLSX** — Microsoft Excel (import ✅, export planned)
|
|
12
|
+
* - **CSV** — Comma-separated values (import ✅, export ✅)
|
|
12
13
|
*
|
|
13
14
|
* All converters run in the browser — no server or native binaries required.
|
|
14
15
|
* The shared `ooxml/` subpath export provides reusable OOXML infrastructure.
|
|
@@ -32,10 +33,14 @@ export type { DocxExportOptions, DocxImportOptions } from './docx/index.js';
|
|
|
32
33
|
export { markdownDocToPptx, docToPptx, pptxToMarkdownDoc, pptxToDoc } from './pptx/index.js';
|
|
33
34
|
export type { PptxExportOptions, PptxImportOptions } from './pptx/index.js';
|
|
34
35
|
|
|
35
|
-
// XLSX (stub)
|
|
36
|
+
// XLSX (import implemented, export stub)
|
|
36
37
|
export { markdownDocToXlsx, docToXlsx, xlsxToMarkdownDoc, xlsxToDoc } from './xlsx/index.js';
|
|
37
38
|
export type { XlsxExportOptions, XlsxImportOptions } from './xlsx/index.js';
|
|
38
39
|
|
|
40
|
+
// CSV (import + export)
|
|
41
|
+
export { csvToMarkdownDoc, csvToDoc, markdownDocToCsv, parseCsv } from './csv/index.js';
|
|
42
|
+
export type { CsvImportOptions, CsvExportOptions } from './csv/index.js';
|
|
43
|
+
|
|
39
44
|
// PDF (fully implemented)
|
|
40
45
|
export {
|
|
41
46
|
markdownDocToPdf,
|
|
@@ -49,6 +54,8 @@ export type { PdfExportOptions, PdfImportOptions } from './pdf/index.js';
|
|
|
49
54
|
// HTML (fully implemented)
|
|
50
55
|
export { docToHtml, docToHtmlZip, collectImagePaths } from './html/index.js';
|
|
51
56
|
export type { HtmlExportOptions, HtmlZipExportOptions } from './html/index.js';
|
|
57
|
+
export { htmlToMarkdown, htmlToMarkdownDoc, htmlToMarkdownDocSync } from './html/index.js';
|
|
58
|
+
export type { HtmlImportOptions } from './html/index.js';
|
|
52
59
|
|
|
53
60
|
// EPUB (export)
|
|
54
61
|
export { markdownDocToEpub, docToEpub } from './epub/index.js';
|
package/src/ooxml/namespaces.ts
CHANGED
|
@@ -77,6 +77,9 @@ export const NS_WML = 'http://schemas.openxmlformats.org/wordprocessingml/2006/m
|
|
|
77
77
|
/** PresentationML main namespace (p:) */
|
|
78
78
|
export const NS_PML = 'http://schemas.openxmlformats.org/presentationml/2006/main';
|
|
79
79
|
|
|
80
|
+
/** PowerPoint 2010 extension namespace (p14:) */
|
|
81
|
+
export const NS_PML_2010 = 'http://schemas.microsoft.com/office/powerpoint/2010/main';
|
|
82
|
+
|
|
80
83
|
// ============================================
|
|
81
84
|
// SpreadsheetML (XLSX)
|
|
82
85
|
// ============================================
|
package/src/pdf/export.ts
CHANGED
|
@@ -22,8 +22,7 @@
|
|
|
22
22
|
import { PDFDocument, StandardFonts, rgb, PDFFont, PDFPage } from 'pdf-lib';
|
|
23
23
|
|
|
24
24
|
import type { Doc } from '@bendyline/squisq/schemas';
|
|
25
|
-
import {
|
|
26
|
-
import { docToMarkdown } from '@bendyline/squisq/doc';
|
|
25
|
+
import { docToMarkdown, resolveThemeForDoc } from '@bendyline/squisq/doc';
|
|
27
26
|
import type {
|
|
28
27
|
MarkdownDocument,
|
|
29
28
|
MarkdownBlockNode,
|
|
@@ -51,6 +50,7 @@ import type {
|
|
|
51
50
|
MarkdownInlineMath,
|
|
52
51
|
MarkdownFootnoteReference,
|
|
53
52
|
} from '@bendyline/squisq/markdown';
|
|
53
|
+
import { readFrontmatterThemeId } from '@bendyline/squisq/markdown';
|
|
54
54
|
|
|
55
55
|
import {
|
|
56
56
|
PAGE_WIDTH_LETTER,
|
|
@@ -127,7 +127,7 @@ export async function markdownDocToPdf(
|
|
|
127
127
|
pdfDoc.setCreationDate(new Date());
|
|
128
128
|
pdfDoc.setModificationDate(new Date());
|
|
129
129
|
|
|
130
|
-
const ctx = await createExportContext(pdfDoc, options);
|
|
130
|
+
const ctx = await createExportContext(pdfDoc, options, doc);
|
|
131
131
|
|
|
132
132
|
renderBlocks(doc.children, ctx, 0);
|
|
133
133
|
|
|
@@ -190,6 +190,7 @@ interface ExportContext {
|
|
|
190
190
|
async function createExportContext(
|
|
191
191
|
pdfDoc: PDFDocument,
|
|
192
192
|
options: PdfExportOptions,
|
|
193
|
+
doc: MarkdownDocument,
|
|
193
194
|
): Promise<ExportContext> {
|
|
194
195
|
const [regular, bold, italic, boldItalic, mono, monoBold] = await Promise.all([
|
|
195
196
|
pdfDoc.embedFont(StandardFonts.Helvetica),
|
|
@@ -213,8 +214,12 @@ async function createExportContext(
|
|
|
213
214
|
let colorHeading = COLOR_HEADING;
|
|
214
215
|
let colorLink = COLOR_LINK;
|
|
215
216
|
|
|
216
|
-
|
|
217
|
-
|
|
217
|
+
// Honor an explicit themeId, else the doc's frontmatter theme (`squisq-theme`
|
|
218
|
+
// / legacy) — mirroring PPTX/DOCX/HTML so every format themes consistently,
|
|
219
|
+
// including inline custom themes (resolved doc-scoped).
|
|
220
|
+
const themeId = options.themeId ?? readFrontmatterThemeId(doc.frontmatter);
|
|
221
|
+
if (themeId) {
|
|
222
|
+
const theme = resolveThemeForDoc(doc, themeId);
|
|
218
223
|
if (theme.colors) {
|
|
219
224
|
colorText = hexToRgb(theme.colors.text) ?? COLOR_TEXT;
|
|
220
225
|
colorHeading = hexToRgb(theme.colors.primary) ?? COLOR_HEADING;
|
package/src/pdf/import.ts
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
import type { Doc } from '@bendyline/squisq/schemas';
|
|
24
24
|
import { markdownToDoc } from '@bendyline/squisq/doc';
|
|
25
|
+
import { stringifyMarkdown } from '@bendyline/squisq/markdown';
|
|
25
26
|
import type {
|
|
26
27
|
MarkdownDocument,
|
|
27
28
|
MarkdownBlockNode,
|
|
@@ -169,7 +170,6 @@ export async function pdfToContainer(
|
|
|
169
170
|
|
|
170
171
|
const markdownDoc: MarkdownDocument = { type: 'document', children: blocks };
|
|
171
172
|
|
|
172
|
-
const { stringifyMarkdown } = await import('@bendyline/squisq/markdown');
|
|
173
173
|
const markdown = stringifyMarkdown(markdownDoc);
|
|
174
174
|
|
|
175
175
|
const container = new MemoryContentContainer();
|