@kernhq/module-quire 0.13.1 → 0.14.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/contract/models.d.ts +185 -0
- package/dist/contract/models.d.ts.map +1 -1
- package/dist/contract/models.js +133 -0
- package/dist/contract/models.js.map +1 -1
- package/dist/contract/permissions.d.ts +14 -0
- package/dist/contract/permissions.d.ts.map +1 -1
- package/dist/contract/permissions.js +82 -0
- package/dist/contract/permissions.js.map +1 -1
- package/dist/contract/properties.d.ts +4 -4
- package/dist/contract/router.d.ts +535 -8
- package/dist/contract/router.d.ts.map +1 -1
- package/dist/contract/router.js +133 -1
- package/dist/contract/router.js.map +1 -1
- package/dist/server/_impl.d.ts +519 -8
- package/dist/server/_impl.d.ts.map +1 -1
- package/dist/server/_impl.js +128 -0
- package/dist/server/_impl.js.map +1 -1
- package/dist/server/export/html.d.ts +67 -0
- package/dist/server/export/html.d.ts.map +1 -0
- package/dist/server/export/html.js +206 -0
- package/dist/server/export/html.js.map +1 -0
- package/dist/server/export/markdown.d.ts +51 -0
- package/dist/server/export/markdown.d.ts.map +1 -0
- package/dist/server/export/markdown.js +312 -0
- package/dist/server/export/markdown.js.map +1 -0
- package/dist/server/export/pdf.d.ts +20 -0
- package/dist/server/export/pdf.d.ts.map +1 -0
- package/dist/server/export/pdf.js +91 -0
- package/dist/server/export/pdf.js.map +1 -0
- package/dist/server/export/zip.d.ts +31 -0
- package/dist/server/export/zip.d.ts.map +1 -0
- package/dist/server/export/zip.js +158 -0
- package/dist/server/export/zip.js.map +1 -0
- package/dist/server/import/csv.d.ts +77 -0
- package/dist/server/import/csv.d.ts.map +1 -0
- package/dist/server/import/csv.js +263 -0
- package/dist/server/import/csv.js.map +1 -0
- package/dist/server/import/html.d.ts +52 -0
- package/dist/server/import/html.d.ts.map +1 -0
- package/dist/server/import/html.js +472 -0
- package/dist/server/import/html.js.map +1 -0
- package/dist/server/import/markdown.d.ts +63 -0
- package/dist/server/import/markdown.d.ts.map +1 -0
- package/dist/server/import/markdown.js +692 -0
- package/dist/server/import/markdown.js.map +1 -0
- package/dist/server/import/plan.d.ts +70 -0
- package/dist/server/import/plan.d.ts.map +1 -0
- package/dist/server/import/plan.js +761 -0
- package/dist/server/import/plan.js.map +1 -0
- package/dist/server/import/ydoc.d.ts +35 -0
- package/dist/server/import/ydoc.d.ts.map +1 -0
- package/dist/server/import/ydoc.js +91 -0
- package/dist/server/import/ydoc.js.map +1 -0
- package/dist/server/import/zip.d.ts +63 -0
- package/dist/server/import/zip.d.ts.map +1 -0
- package/dist/server/import/zip.js +308 -0
- package/dist/server/import/zip.js.map +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/schema.d.ts +445 -1
- package/dist/server/schema.d.ts.map +1 -1
- package/dist/server/schema.js +146 -0
- package/dist/server/schema.js.map +1 -1
- package/dist/server/services/databases.d.ts +5 -5
- package/dist/server/services/export.d.ts +176 -0
- package/dist/server/services/export.d.ts.map +1 -0
- package/dist/server/services/export.js +822 -0
- package/dist/server/services/export.js.map +1 -0
- package/dist/server/services/import.d.ts +109 -0
- package/dist/server/services/import.d.ts.map +1 -0
- package/dist/server/services/import.js +570 -0
- package/dist/server/services/import.js.map +1 -0
- package/dist/server/services/index.d.ts +26 -1
- package/dist/server/services/index.d.ts.map +1 -1
- package/dist/server/services/index.js +60 -1
- package/dist/server/services/index.js.map +1 -1
- package/dist/server/services/versions.d.ts +1 -1
- package/migrations/0010_transfers.sql +154 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/src/client/components/ExportDialog.svelte +685 -0
- package/src/client/components/ImportDialog.svelte +702 -0
- package/src/client/components/ImportReport.svelte +310 -0
- package/src/client/components/SidebarSpaces.svelte +79 -0
- package/src/client/i18n.ts +614 -0
- package/src/client/index.ts +32 -0
- package/src/client/mock.ts +318 -0
- package/src/client/module.ts +36 -0
- package/src/client/pages/PageView.svelte +36 -0
- package/src/client/pages/TransfersPage.svelte +570 -0
- package/src/client/permissions.ts +11 -0
- package/src/client/query.ts +23 -0
- package/src/client/transfers.ts +142 -0
- package/src/contract/models.ts +152 -0
- package/src/contract/permissions.ts +84 -0
- package/src/contract/router.ts +147 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A page as an HTML file somebody can open with no Kern behind it.
|
|
3
|
+
*
|
|
4
|
+
* The body comes from `renderPageDoc` — the same static renderer the public site uses — so an export
|
|
5
|
+
* and a published page are the same drawing of the same stored version. What this file adds is
|
|
6
|
+
* everything a *file* needs and a page inside an application does not: a document element, a
|
|
7
|
+
* character set, a title, and the styles, inlined.
|
|
8
|
+
*
|
|
9
|
+
* **The stylesheet is written here rather than taken from `@kernhq/ui`, and that is deliberate.**
|
|
10
|
+
* `prose.css` in that package is 480 lines referencing 31 `--kern-*` tokens defined in a second
|
|
11
|
+
* stylesheet that itself references a palette — so "inline the prose styles" is really "inline the
|
|
12
|
+
* whole design system", and an exported file would carry a copy of it that goes stale the moment the
|
|
13
|
+
* palette moves. What a reader of an exported page needs is smaller and does not change: the ink and
|
|
14
|
+
* paper colours, a measure they can read, and a rule for every block the renderer can emit. It is
|
|
15
|
+
* written flat, with no custom properties to resolve, because a file that is opened in a mail client
|
|
16
|
+
* or pasted into a wiki is not guaranteed a cascade.
|
|
17
|
+
*
|
|
18
|
+
* It has to answer for the same things a Kern screen answers for. Dark mode is `prefers-color-scheme`
|
|
19
|
+
* rather than a class, because a file has no shell to set one. `dir` comes from the document element
|
|
20
|
+
* so a Persian or Arabic page reads right-to-left, and every rule that could have been `left` or
|
|
21
|
+
* `right` is a logical property — `margin-inline-start`, `border-inline-start`, `text-align: start` —
|
|
22
|
+
* so nothing has to be mirrored. A table is the one block that cannot be made narrower, so it keeps
|
|
23
|
+
* the same `overflow-x` scroller the application gives it rather than pushing the page sideways.
|
|
24
|
+
*/
|
|
25
|
+
import { escapeHtml } from '../render.js';
|
|
26
|
+
/**
|
|
27
|
+
* What an exported page is dressed in.
|
|
28
|
+
*
|
|
29
|
+
* Deliberately not a copy of `prose.css`: the same shapes, in the values a standalone file can rely
|
|
30
|
+
* on. Every colour pair here was chosen against the surface it sits on — body ink on paper is
|
|
31
|
+
* #2c2a26 on #fdfcfa in light and #e8e4dc on #171614 in dark, and the muted ink is a colour rather
|
|
32
|
+
* than an opacity, because `opacity` fades text against whatever is behind it and a "muted" row at
|
|
33
|
+
* 0.5 is a row nobody can read.
|
|
34
|
+
*
|
|
35
|
+
* The contrast is arithmetic and was computed rather than judged: every foreground below was
|
|
36
|
+
* measured against the surface it actually sits on — including each callout tint, the highlight and
|
|
37
|
+
* the code ground, in both themes — and the worst pair is the muted trail at **5.33:1**, which
|
|
38
|
+
* clears AA for body text. Re-measure before changing a value rather than adjusting it by eye.
|
|
39
|
+
*/
|
|
40
|
+
export const EXPORT_STYLES = `
|
|
41
|
+
:root { color-scheme: light dark; }
|
|
42
|
+
* { box-sizing: border-box; }
|
|
43
|
+
html { -webkit-text-size-adjust: 100%; }
|
|
44
|
+
body {
|
|
45
|
+
margin: 0;
|
|
46
|
+
padding: 40px 24px 96px;
|
|
47
|
+
background: #fdfcfa;
|
|
48
|
+
color: #2c2a26;
|
|
49
|
+
font: 16px/1.7 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Noto Sans", sans-serif;
|
|
50
|
+
}
|
|
51
|
+
main { margin: 0 auto; max-width: 74ch; }
|
|
52
|
+
.kern-page + .kern-page { margin-top: 72px; border-top: 1px solid #e5e0d6; padding-top: 40px; }
|
|
53
|
+
.kern-page-title { margin: 0 0 4px; font-size: 32px; line-height: 1.25; font-weight: 650; }
|
|
54
|
+
.kern-page-trail { margin: 0 0 28px; font-size: 13px; color: #6f6959; }
|
|
55
|
+
.kern-prose > :first-child { margin-top: 0; }
|
|
56
|
+
.kern-prose > :last-child { margin-bottom: 0; }
|
|
57
|
+
.kern-prose p { margin: 0 0 16px; }
|
|
58
|
+
.kern-prose h1, .kern-prose h2, .kern-prose h3,
|
|
59
|
+
.kern-prose h4, .kern-prose h5, .kern-prose h6 {
|
|
60
|
+
margin: 32px 0 12px; line-height: 1.3; font-weight: 650; scroll-margin-top: 16px;
|
|
61
|
+
}
|
|
62
|
+
.kern-prose h1 { font-size: 28px; }
|
|
63
|
+
.kern-prose h2 { font-size: 23px; }
|
|
64
|
+
.kern-prose h3 { font-size: 19px; }
|
|
65
|
+
.kern-prose h4 { font-size: 17px; }
|
|
66
|
+
.kern-prose h5 { font-size: 15px; }
|
|
67
|
+
.kern-prose h6 { font-size: 14px; color: #6f6959; text-transform: uppercase; letter-spacing: 0.04em; }
|
|
68
|
+
.kern-prose a { color: #1c5f7a; text-underline-offset: 2px; }
|
|
69
|
+
.kern-prose ul, .kern-prose ol { margin: 0 0 16px; padding-inline-start: 26px; }
|
|
70
|
+
.kern-prose li { margin: 4px 0; }
|
|
71
|
+
.kern-prose li > p { margin: 0; }
|
|
72
|
+
.kern-prose blockquote {
|
|
73
|
+
margin: 0 0 16px; padding-block: 2px; padding-inline: 16px 0;
|
|
74
|
+
border-inline-start: 3px solid #d8d2c4; color: #4a463d;
|
|
75
|
+
}
|
|
76
|
+
.kern-prose code {
|
|
77
|
+
font: 0.9em/1.5 ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
78
|
+
background: #f1eee6; border: 1px solid #e5e0d6; border-radius: 4px; padding: 1px 4px;
|
|
79
|
+
}
|
|
80
|
+
.kern-prose pre {
|
|
81
|
+
margin: 0 0 16px; padding: 14px 16px; overflow-x: auto;
|
|
82
|
+
background: #f6f3ec; border: 1px solid #e5e0d6; border-radius: 8px;
|
|
83
|
+
}
|
|
84
|
+
.kern-prose pre code { background: none; border: 0; padding: 0; font-size: 13px; }
|
|
85
|
+
.kern-prose hr { margin: 28px 0; border: 0; border-top: 1px solid #e5e0d6; }
|
|
86
|
+
.kern-prose img { max-width: 100%; height: auto; border-radius: 6px; }
|
|
87
|
+
.kern-prose mark.kern-highlight { background: #fbeaa8; color: inherit; padding: 0 2px; border-radius: 3px; }
|
|
88
|
+
.kern-table-wrap { margin: 0 0 16px; overflow-x: auto; }
|
|
89
|
+
.kern-prose table.kern-table { border-collapse: collapse; width: 100%; font-size: 15px; }
|
|
90
|
+
.kern-prose table.kern-table td, .kern-prose table.kern-table th {
|
|
91
|
+
border: 1px solid #e5e0d6; padding: 7px 10px; text-align: start; vertical-align: top;
|
|
92
|
+
}
|
|
93
|
+
.kern-prose table.kern-table th { background: #f6f3ec; font-weight: 620; }
|
|
94
|
+
.kern-prose table.kern-table [data-align="center"] { text-align: center; }
|
|
95
|
+
.kern-prose table.kern-table [data-align="end"] { text-align: end; }
|
|
96
|
+
.kern-prose ul.kern-tasks { list-style: none; padding-inline-start: 2px; }
|
|
97
|
+
.kern-prose ul.kern-tasks li { display: flex; gap: 8px; align-items: baseline; }
|
|
98
|
+
.kern-prose ul.kern-tasks li > div { flex: 1; min-width: 0; }
|
|
99
|
+
.kern-prose ul.kern-tasks li > div > p { margin: 0; }
|
|
100
|
+
.kern-prose aside.kern-callout {
|
|
101
|
+
margin: 0 0 16px; padding: 12px 16px; border-radius: 8px;
|
|
102
|
+
border: 1px solid #d8d2c4; border-inline-start-width: 3px; background: #f6f3ec;
|
|
103
|
+
}
|
|
104
|
+
.kern-prose aside.kern-callout > :last-child { margin-bottom: 0; }
|
|
105
|
+
.kern-prose aside.kern-callout[data-callout="info"] { border-color: #a9cbd8; background: #eef6f9; }
|
|
106
|
+
.kern-prose aside.kern-callout[data-callout="success"] { border-color: #a9d3b4; background: #eef8f1; }
|
|
107
|
+
.kern-prose aside.kern-callout[data-callout="warning"] { border-color: #e0cb96; background: #fbf5e6; }
|
|
108
|
+
.kern-prose aside.kern-callout[data-callout="danger"] { border-color: #e0aaa4; background: #fbefee; }
|
|
109
|
+
.kern-prose details.kern-toggle {
|
|
110
|
+
margin: 0 0 16px; padding: 10px 14px; border: 1px solid #e5e0d6; border-radius: 8px;
|
|
111
|
+
}
|
|
112
|
+
.kern-prose details.kern-toggle > summary { cursor: pointer; font-weight: 600; }
|
|
113
|
+
.kern-prose .kern-mention, .kern-prose .kern-page-mention {
|
|
114
|
+
border-radius: 4px; padding: 0 3px; background: #eef2f7; color: #2b4a63;
|
|
115
|
+
}
|
|
116
|
+
@media print {
|
|
117
|
+
body { padding: 0; background: #fff; color: #000; }
|
|
118
|
+
.kern-page { break-after: page; }
|
|
119
|
+
.kern-page:last-child { break-after: auto; }
|
|
120
|
+
.kern-page + .kern-page { margin-top: 0; border-top: 0; padding-top: 0; }
|
|
121
|
+
.kern-prose a { color: inherit; }
|
|
122
|
+
.kern-table-wrap { overflow-x: visible; }
|
|
123
|
+
}
|
|
124
|
+
@media (prefers-color-scheme: dark) {
|
|
125
|
+
body { background: #171614; color: #e8e4dc; }
|
|
126
|
+
.kern-page + .kern-page { border-top-color: #35322c; }
|
|
127
|
+
.kern-page-trail, .kern-prose h6 { color: #9a9384; }
|
|
128
|
+
.kern-prose a { color: #7fc2dd; }
|
|
129
|
+
.kern-prose blockquote { border-inline-start-color: #3f3b33; color: #c6c1b6; }
|
|
130
|
+
.kern-prose code { background: #23211d; border-color: #35322c; }
|
|
131
|
+
.kern-prose pre { background: #1e1d1a; border-color: #35322c; }
|
|
132
|
+
.kern-prose hr { border-top-color: #35322c; }
|
|
133
|
+
.kern-prose mark.kern-highlight { background: #6a5a1e; color: #f4efdd; }
|
|
134
|
+
.kern-prose table.kern-table td, .kern-prose table.kern-table th { border-color: #35322c; }
|
|
135
|
+
.kern-prose table.kern-table th { background: #1e1d1a; }
|
|
136
|
+
.kern-prose aside.kern-callout { border-color: #3f3b33; background: #1e1d1a; }
|
|
137
|
+
.kern-prose aside.kern-callout[data-callout="info"] { border-color: #2d5a6d; background: #16232a; }
|
|
138
|
+
.kern-prose aside.kern-callout[data-callout="success"] { border-color: #2f5c3d; background: #16241b; }
|
|
139
|
+
.kern-prose aside.kern-callout[data-callout="warning"] { border-color: #6a5622; background: #241f13; }
|
|
140
|
+
.kern-prose aside.kern-callout[data-callout="danger"] { border-color: #6d3a34; background: #261917; }
|
|
141
|
+
.kern-prose details.kern-toggle { border-color: #35322c; }
|
|
142
|
+
.kern-prose .kern-mention, .kern-prose .kern-page-mention { background: #22303a; color: #b7d4e4; }
|
|
143
|
+
}
|
|
144
|
+
`.trim();
|
|
145
|
+
export function exportLinks() {
|
|
146
|
+
const byTarget = new Map();
|
|
147
|
+
const byToken = new Map();
|
|
148
|
+
return {
|
|
149
|
+
to(target) {
|
|
150
|
+
const existing = byTarget.get(target);
|
|
151
|
+
if (existing)
|
|
152
|
+
return existing;
|
|
153
|
+
const token = `https://asset.invalid/${byToken.size}`;
|
|
154
|
+
byTarget.set(target, token);
|
|
155
|
+
byToken.set(token, target);
|
|
156
|
+
return token;
|
|
157
|
+
},
|
|
158
|
+
resolve(html) {
|
|
159
|
+
let out = html;
|
|
160
|
+
for (const [token, target] of byToken)
|
|
161
|
+
out = out.replaceAll(token, target);
|
|
162
|
+
return out;
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
const section = (page) => [
|
|
167
|
+
`<article class="kern-page" id="p-${escapeHtml(page.id)}">`,
|
|
168
|
+
`<h1 class="kern-page-title">${escapeHtml(page.title || 'Untitled')}</h1>`,
|
|
169
|
+
page.trail.length > 0
|
|
170
|
+
? `<p class="kern-page-trail">${page.trail.map((part) => escapeHtml(part)).join(' / ')}</p>`
|
|
171
|
+
: '',
|
|
172
|
+
`<div class="kern-prose">${page.html}</div>`,
|
|
173
|
+
'</article>',
|
|
174
|
+
].join('\n');
|
|
175
|
+
/**
|
|
176
|
+
* One HTML file holding one page or many.
|
|
177
|
+
*
|
|
178
|
+
* `pages` is a list rather than a page because the PDF path needs every page of a subtree in one
|
|
179
|
+
* document — Chromium is handed a document and produces a PDF, so one round trip per page would be
|
|
180
|
+
* one PDF per page and nothing to join them with. The zip path passes a list of one.
|
|
181
|
+
*
|
|
182
|
+
* `dir` is the writing direction of the *content*, not of the reader's locale: an exported Persian
|
|
183
|
+
* handbook is right-to-left wherever it is opened.
|
|
184
|
+
*/
|
|
185
|
+
export function exportedHtmlDocument(opts) {
|
|
186
|
+
const dir = opts.dir ?? 'auto';
|
|
187
|
+
return `<!doctype html>
|
|
188
|
+
<html lang="${escapeHtml(opts.lang ?? 'en')}" dir="${dir}">
|
|
189
|
+
<head>
|
|
190
|
+
<meta charset="utf-8">
|
|
191
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
192
|
+
<meta name="generator" content="Kern Quire">
|
|
193
|
+
<title>${escapeHtml(opts.title || 'Untitled')}</title>
|
|
194
|
+
<style>
|
|
195
|
+
${EXPORT_STYLES}
|
|
196
|
+
</style>
|
|
197
|
+
</head>
|
|
198
|
+
<body>
|
|
199
|
+
<main>
|
|
200
|
+
${opts.pages.map(section).join('\n')}
|
|
201
|
+
</main>
|
|
202
|
+
</body>
|
|
203
|
+
</html>
|
|
204
|
+
`;
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=html.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.js","sourceRoot":"","sources":["../../../src/server/export/html.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEzC;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwG5B,CAAC,IAAI,EAAE,CAAA;AA4BR,MAAM,UAAU,WAAW;IACzB,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAA;IAC1C,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAA;IACzC,OAAO;QACL,EAAE,CAAC,MAAM;YACP,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;YACrC,IAAI,QAAQ;gBAAE,OAAO,QAAQ,CAAA;YAC7B,MAAM,KAAK,GAAG,yBAAyB,OAAO,CAAC,IAAI,EAAE,CAAA;YACrD,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;YAC3B,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;YAC1B,OAAO,KAAK,CAAA;QACd,CAAC;QACD,OAAO,CAAC,IAAI;YACV,IAAI,GAAG,GAAG,IAAI,CAAA;YACd,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,OAAO;gBAAE,GAAG,GAAG,GAAG,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;YAC1E,OAAO,GAAG,CAAA;QACZ,CAAC;KACF,CAAA;AACH,CAAC;AAYD,MAAM,OAAO,GAAG,CAAC,IAAkB,EAAU,EAAE,CAC7C;IACE,oCAAoC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI;IAC3D,+BAA+B,UAAU,CAAC,IAAI,CAAC,KAAK,IAAI,UAAU,CAAC,OAAO;IAC1E,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;QACnB,CAAC,CAAC,8BAA8B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM;QAC5F,CAAC,CAAC,EAAE;IACN,2BAA2B,IAAI,CAAC,IAAI,QAAQ;IAC5C,YAAY;CACb,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAEd;;;;;;;;;GASG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAKpC;IACC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,MAAM,CAAA;IAC9B,OAAO;cACK,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,GAAG;;;;;SAK/C,UAAU,CAAC,IAAI,CAAC,KAAK,IAAI,UAAU,CAAC;;EAE3C,aAAa;;;;;EAKb,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;;;;CAInC,CAAA;AACD,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A page as Markdown, written from the page document rather than from HTML.
|
|
3
|
+
*
|
|
4
|
+
* Two writers, one source. `renderPageDoc` next door draws the same document as HTML; this one draws
|
|
5
|
+
* it as Markdown, and both read the stored `PageDoc`. Converting the HTML instead would have been
|
|
6
|
+
* less code and would have thrown away everything a heading, a task and a table know about
|
|
7
|
+
* themselves — an `<h2>` is a heading, but a `<ul class="kern-tasks">` full of disabled checkboxes is
|
|
8
|
+
* not a task list to anything that reads it back.
|
|
9
|
+
*
|
|
10
|
+
* **Round-tripping is the requirement, not prettiness.** A heading, a list, a table, a code block, a
|
|
11
|
+
* callout and a task list have to come back as themselves when the file is imported again, so every
|
|
12
|
+
* one of them is written in the dialect an importer can recognise without guessing:
|
|
13
|
+
*
|
|
14
|
+
* - tables and task lists are GitHub-flavoured Markdown, because that is what every reader
|
|
15
|
+
* implements and what an import will parse;
|
|
16
|
+
* - a callout is a blockquote with a GitHub alert marker — `> [!WARNING]` — carrying Quire's own
|
|
17
|
+
* tone name, so it re-reads as the same callout here and degrades to an ordinary blockquote
|
|
18
|
+
* anywhere else. A `<div data-callout>` would round-trip too and would be a raw HTML block in
|
|
19
|
+
* every other reader;
|
|
20
|
+
* - underline and highlight have no Markdown at all and are written as `<u>` and `<mark>`, which
|
|
21
|
+
* is inline HTML a Markdown parser passes through rather than a syntax it will mangle.
|
|
22
|
+
*
|
|
23
|
+
* `PAGE_DOC_NODES` is the list this has to cover, the same list `render.ts` is held to, and
|
|
24
|
+
* `export.int.test.ts` compares this file's table against it in both directions. A node with no
|
|
25
|
+
* writer here does not degrade quietly — it would lose somebody's table.
|
|
26
|
+
*/
|
|
27
|
+
import type { PageDoc } from '@kernhq/ui/editor/page-doc';
|
|
28
|
+
export interface MarkdownOptions {
|
|
29
|
+
/**
|
|
30
|
+
* Where the picture with this id sits *relative to the file being written* — `media/diagram.png`.
|
|
31
|
+
*
|
|
32
|
+
* Never a signed storage URL. A presigned GET is the object's key, so it carries the tenant's
|
|
33
|
+
* workspace uuid and the file's uuid, and it stops working an hour after it is minted: writing one
|
|
34
|
+
* into a file somebody keeps is both a leak and a broken picture. A picture with no answer here is
|
|
35
|
+
* dropped, exactly as the HTML renderer drops it.
|
|
36
|
+
*/
|
|
37
|
+
fileSrc?: (fileId: string) => string | null;
|
|
38
|
+
/** Where another exported page sits relative to this one. Without one, a mention stays as text. */
|
|
39
|
+
pageHref?: (pageId: string) => string | null;
|
|
40
|
+
}
|
|
41
|
+
/** Every node type this file can write, for the test that compares it against `PAGE_DOC_NODES`. */
|
|
42
|
+
export declare const MARKDOWN_NODES: readonly string[];
|
|
43
|
+
/**
|
|
44
|
+
* A stored page as Markdown. An empty or unreadable document is an empty string, never a throw.
|
|
45
|
+
*
|
|
46
|
+
* The title is not written here. A page's title is a column, not a node in its document, so whether
|
|
47
|
+
* it becomes an `# H1` at the top is a decision about the file — and the file writer next door makes
|
|
48
|
+
* it, once, for both formats.
|
|
49
|
+
*/
|
|
50
|
+
export declare function pageDocToMarkdown(doc: PageDoc | null | undefined, options?: MarkdownOptions): string;
|
|
51
|
+
//# sourceMappingURL=markdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../../src/server/export/markdown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,KAAK,EAAE,OAAO,EAA4B,MAAM,4BAA4B,CAAA;AAEnF,MAAM,WAAW,eAAe;IAC9B;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAA;IAC3C,mGAAmG;IACnG,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAA;CAC7C;AAsUD,mGAAmG;AACnG,eAAO,MAAM,cAAc,EAAE,SAAS,MAAM,EAE3C,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,GAAG,SAAS,EAAE,OAAO,GAAE,eAAoB,GAAG,MAAM,CAKxG"}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a Markdown reader would otherwise take as syntax.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately not every character CommonMark lists. Escaping `.`, `+`, `(` and `)` everywhere turns
|
|
5
|
+
* an ordinary sentence into a thicket of backslashes, and they are only meaningful at the start of a
|
|
6
|
+
* line or inside a link — both of which are handled where they arise. What is escaped here is what
|
|
7
|
+
* changes the meaning of a sentence wherever it appears.
|
|
8
|
+
*/
|
|
9
|
+
const escapeInline = (text) => text.replace(/([\\`*_[\]<>|~])/g, '\\$1');
|
|
10
|
+
/**
|
|
11
|
+
* A line that would start a block if it were left alone — a paragraph beginning `- ` or `# `.
|
|
12
|
+
*
|
|
13
|
+
* The ordered-list case is separate because the escape goes in a different place. `\1. ` is not an
|
|
14
|
+
* escape at all: a backslash before anything that is not punctuation is a literal backslash, so it
|
|
15
|
+
* would print one and still start a list. `1\. ` is the one that works.
|
|
16
|
+
*/
|
|
17
|
+
const escapeLineStart = (line) => line
|
|
18
|
+
.replace(/^(\s*)(>|#{1,6}(?=\s)|[-*+](?=\s))/, '$1\\$2')
|
|
19
|
+
.replace(/^(\s*)(\d{1,9})([.)])(?=\s)/, '$1$2\\$3');
|
|
20
|
+
/** Every character in the node, ignoring marks. What a fence and a `<pre>` want. */
|
|
21
|
+
function textOf(node) {
|
|
22
|
+
if (typeof node.text === 'string')
|
|
23
|
+
return node.text;
|
|
24
|
+
return (node.content ?? []).map(textOf).join('');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The marks, applied so the result parses.
|
|
28
|
+
*
|
|
29
|
+
* Order matters and is not alphabetical. `code` goes on first because a code span's content is
|
|
30
|
+
* literal — `**bold**` inside backticks is four asterisks and a word — and `link` goes on last
|
|
31
|
+
* because a link's label may hold everything else and nothing may hold a link.
|
|
32
|
+
*/
|
|
33
|
+
function applyMarks(text, marks) {
|
|
34
|
+
const has = (type) => (marks ?? []).some((m) => m?.type === type);
|
|
35
|
+
let out = has('code') ? codeSpan(text) : escapeInline(text);
|
|
36
|
+
if (has('highlight'))
|
|
37
|
+
out = `<mark>${out}</mark>`;
|
|
38
|
+
if (has('strike'))
|
|
39
|
+
out = `~~${out}~~`;
|
|
40
|
+
if (has('underline'))
|
|
41
|
+
out = `<u>${out}</u>`;
|
|
42
|
+
if (has('italic'))
|
|
43
|
+
out = `*${out}*`;
|
|
44
|
+
if (has('bold'))
|
|
45
|
+
out = `**${out}**`;
|
|
46
|
+
const link = (marks ?? []).find((m) => m?.type === 'link');
|
|
47
|
+
if (link) {
|
|
48
|
+
const href = typeof link.attrs?.href === 'string' ? link.attrs.href : '';
|
|
49
|
+
if (href)
|
|
50
|
+
out = `[${out}](${encodeLinkTarget(href)})`;
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
/** A code span whose fence is longer than any run of backticks inside it, so it closes where it should. */
|
|
55
|
+
function codeSpan(text) {
|
|
56
|
+
const longest = Math.max(0, ...[...text.matchAll(/`+/g)].map((m) => m[0].length));
|
|
57
|
+
const fence = '`'.repeat(longest + 1);
|
|
58
|
+
const pad = text.startsWith('`') || text.endsWith('`') ? ' ' : '';
|
|
59
|
+
return `${fence}${pad}${text}${pad}${fence}`;
|
|
60
|
+
}
|
|
61
|
+
/** Parentheses and whitespace are what break an inline link target; angle brackets fix both. */
|
|
62
|
+
const encodeLinkTarget = (href) => /[\s()<>]/.test(href) ? `<${href.replaceAll('>', '%3E')}>` : href;
|
|
63
|
+
function inline(nodes, options) {
|
|
64
|
+
return (nodes ?? [])
|
|
65
|
+
.map((node) => {
|
|
66
|
+
if (typeof node.text === 'string')
|
|
67
|
+
return applyMarks(node.text, node.marks);
|
|
68
|
+
const writer = INLINE_WRITERS[node.type ?? ''];
|
|
69
|
+
if (writer)
|
|
70
|
+
return writer(node, options);
|
|
71
|
+
// An inline node with no writer keeps its text rather than vanishing mid-sentence.
|
|
72
|
+
return escapeInline(textOf(node));
|
|
73
|
+
})
|
|
74
|
+
.join('');
|
|
75
|
+
}
|
|
76
|
+
const INLINE_WRITERS = {
|
|
77
|
+
// Two trailing spaces is the line break every Markdown reader implements; a backslash is CommonMark
|
|
78
|
+
// only, and an importer that is not CommonMark would keep the backslash as a character.
|
|
79
|
+
hardBreak: () => ' \n',
|
|
80
|
+
text: (node) => escapeInline(node.text ?? ''),
|
|
81
|
+
/**
|
|
82
|
+
* A person, as `@Ada` — **and this one does not come back.**
|
|
83
|
+
*
|
|
84
|
+
* Stated here because it is the one construct in the writer that is knowingly lossy, and a reader
|
|
85
|
+
* of this table would otherwise assume the pairing with `import/markdown.ts` is total. A page
|
|
86
|
+
* mention resolves against a path in the same archive, so the reader can rebuild it; a person
|
|
87
|
+
* resolves against a *user id*, which is a fact about the instance the export came from. The two
|
|
88
|
+
* spellings that would carry it are both worse than the loss: putting the id in the file makes an
|
|
89
|
+
* export identify people, and importing it would mint a mention of a stranger's id in a workspace
|
|
90
|
+
* that never had that person. So this writes what a mention reads as — the name — and it re-imports
|
|
91
|
+
* as prose. There is no report row for it either, because by then it is a run of text and nothing
|
|
92
|
+
* can tell it from one somebody typed.
|
|
93
|
+
*/
|
|
94
|
+
mention: (node) => `@${escapeInline(String(node.attrs?.label ?? ''))}`,
|
|
95
|
+
pageMention: (node, options) => {
|
|
96
|
+
const label = escapeInline(String(node.attrs?.label ?? '') || 'Untitled');
|
|
97
|
+
const id = typeof node.attrs?.id === 'string' ? node.attrs.id : '';
|
|
98
|
+
const href = id ? (options.pageHref?.(id) ?? null) : null;
|
|
99
|
+
// A page that is not in this export cannot be linked to, and a dead link is worse than a name.
|
|
100
|
+
return href ? `[${label}](${encodeLinkTarget(href)})` : label;
|
|
101
|
+
},
|
|
102
|
+
image: (node, options) => {
|
|
103
|
+
const fileId = typeof node.attrs?.fileId === 'string' ? node.attrs.fileId : null;
|
|
104
|
+
const resolved = fileId ? (options.fileSrc?.(fileId) ?? null) : null;
|
|
105
|
+
const src = resolved ?? (typeof node.attrs?.src === 'string' ? node.attrs.src : null);
|
|
106
|
+
if (!src)
|
|
107
|
+
return '';
|
|
108
|
+
const alt = escapeInline(typeof node.attrs?.alt === 'string' ? node.attrs.alt : '');
|
|
109
|
+
const title = typeof node.attrs?.title === 'string' && node.attrs.title ? node.attrs.title : null;
|
|
110
|
+
return `}${title ? ` "${title.replaceAll('"', '\\"')}"` : ''})`;
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Prefix every line, including the blank ones — which is what makes a nested blockquote hold.
|
|
115
|
+
*
|
|
116
|
+
* A blank line gets the prefix trimmed (`>` rather than `> `) and a line with content does not, so
|
|
117
|
+
* the two trailing spaces that mean "line break" survive. Trimming every line would delete them and
|
|
118
|
+
* silently join two lines of an address.
|
|
119
|
+
*/
|
|
120
|
+
const prefixLines = (text, first, rest = first) => text
|
|
121
|
+
.split('\n')
|
|
122
|
+
.map((line, i) => line.length === 0 ? (i === 0 ? first : rest).trimEnd() : `${i === 0 ? first : rest}${line}`)
|
|
123
|
+
.join('\n');
|
|
124
|
+
/** Blocks, separated by the blank line that keeps them separate blocks. */
|
|
125
|
+
function blocks(nodes, options) {
|
|
126
|
+
return (nodes ?? [])
|
|
127
|
+
.map((node) => block(node, options))
|
|
128
|
+
.filter((text) => text.length > 0)
|
|
129
|
+
.join('\n\n');
|
|
130
|
+
}
|
|
131
|
+
function block(node, options) {
|
|
132
|
+
const writer = BLOCK_WRITERS[node.type ?? ''];
|
|
133
|
+
if (writer)
|
|
134
|
+
return writer(node, options);
|
|
135
|
+
// Unreachable while the test below holds, and correct if a newer image writes a node this one has
|
|
136
|
+
// never heard of: keep the children rather than losing a paragraph to an unknown wrapper.
|
|
137
|
+
return blocks(node.content, options);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* A list, whose marker depends on the list and whose continuation depends on the marker.
|
|
141
|
+
*
|
|
142
|
+
* Every line after the first is indented by the marker's own width, or the reader ends the item and
|
|
143
|
+
* starts a paragraph — which is how a two-paragraph bullet becomes a bullet and an orphan.
|
|
144
|
+
*/
|
|
145
|
+
function list(node, options, markerFor) {
|
|
146
|
+
return (node.content ?? [])
|
|
147
|
+
.map((item, index) => {
|
|
148
|
+
const marker = markerFor(index);
|
|
149
|
+
const body = blocks(item.content, options);
|
|
150
|
+
// An empty bullet is still a bullet somebody typed, so the marker survives on its own.
|
|
151
|
+
return body ? prefixLines(body, marker, ' '.repeat(marker.length)) : marker.trimEnd();
|
|
152
|
+
})
|
|
153
|
+
.join('\n');
|
|
154
|
+
}
|
|
155
|
+
/** `info` | `note` | `success` | `warning` | `danger`, upper-cased into a GitHub alert marker. */
|
|
156
|
+
const CALLOUT_TONES = new Set(['info', 'note', 'success', 'warning', 'danger']);
|
|
157
|
+
/**
|
|
158
|
+
* Escape the pipes a cell still has, and only those.
|
|
159
|
+
*
|
|
160
|
+
* `escapeInline` already escaped every pipe in ordinary prose, so a blind `replaceAll` would double
|
|
161
|
+
* them — `Pipe \| here` becomes `Pipe \\| here`, which renders as a backslash and *then* ends the
|
|
162
|
+
* cell. What is left unescaped is a pipe inside a code span, where escaping is deliberately not
|
|
163
|
+
* applied — and GFM says a table cell escapes those too, code span or not. So this walks the string,
|
|
164
|
+
* steps over anything already escaped, and escapes what remains.
|
|
165
|
+
*/
|
|
166
|
+
function escapePipes(text) {
|
|
167
|
+
let out = '';
|
|
168
|
+
for (let i = 0; i < text.length; i++) {
|
|
169
|
+
const ch = text[i];
|
|
170
|
+
if (ch === '\\' && i + 1 < text.length) {
|
|
171
|
+
out += ch + text[i + 1];
|
|
172
|
+
i++;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
out += ch === '|' ? '\\|' : ch;
|
|
176
|
+
}
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* One cell of a GFM table.
|
|
181
|
+
*
|
|
182
|
+
* A cell is inline only — the format has no way to hold a second paragraph — so a paragraph break
|
|
183
|
+
* becomes `<br><br>`. Losing the block structure inside a cell is the format's limit, and saying so
|
|
184
|
+
* here is better than a table that stops being a table at the first newline.
|
|
185
|
+
*
|
|
186
|
+
* The whitespace before each newline goes with it, and that is not tidiness. A hard break is written
|
|
187
|
+
* ` \n` — two spaces are what makes it a break in prose — so leaving them in front of the `<br>`
|
|
188
|
+
* hands the importer a cell whose text ends in two spaces it never had. They are invisible in every
|
|
189
|
+
* rendering and they *accumulate*: three export-import cycles turned `a` into `a` and six spaces.
|
|
190
|
+
*/
|
|
191
|
+
const tableCell = (cell, options) => escapePipes((blocks(cell.content, options) || '')
|
|
192
|
+
.replace(/[ \t]*\n{2,}/g, '<br><br>')
|
|
193
|
+
.replace(/[ \t]*\n/g, '<br>')
|
|
194
|
+
.trim());
|
|
195
|
+
function table(node, options) {
|
|
196
|
+
const rows = (node.content ?? []).filter((row) => (row.content ?? []).length > 0);
|
|
197
|
+
if (rows.length === 0)
|
|
198
|
+
return '';
|
|
199
|
+
const width = Math.max(...rows.map((row) => (row.content ?? []).length));
|
|
200
|
+
const cellsOf = (row) => {
|
|
201
|
+
const cells = (row.content ?? []).map((cell) => tableCell(cell, options));
|
|
202
|
+
while (cells.length < width)
|
|
203
|
+
cells.push('');
|
|
204
|
+
return cells;
|
|
205
|
+
};
|
|
206
|
+
/*
|
|
207
|
+
* GFM has no table without a header row, so a table whose first row is ordinary cells still
|
|
208
|
+
* contributes one — its own first row. Emitting an empty header instead would put a blank band at
|
|
209
|
+
* the top of every table Quire exports, and shift every row's meaning by one on the way back in.
|
|
210
|
+
*/
|
|
211
|
+
const [head, ...body] = rows;
|
|
212
|
+
const line = (cells) => `| ${cells.join(' | ')} |`;
|
|
213
|
+
return [
|
|
214
|
+
line(cellsOf(head)),
|
|
215
|
+
line(Array.from({ length: width }, () => '---')),
|
|
216
|
+
...body.map((row) => line(cellsOf(row))),
|
|
217
|
+
].join('\n');
|
|
218
|
+
}
|
|
219
|
+
const BLOCK_WRITERS = {
|
|
220
|
+
paragraph: (node, options) => escapeLineStart(inline(node.content, options)),
|
|
221
|
+
heading: (node, options) => {
|
|
222
|
+
const raw = Number(node.attrs?.level);
|
|
223
|
+
const level = Number.isInteger(raw) && raw >= 1 && raw <= 6 ? raw : 1;
|
|
224
|
+
return `${'#'.repeat(level)} ${inline(node.content, options)}`.trimEnd();
|
|
225
|
+
},
|
|
226
|
+
bulletList: (node, options) => list(node, options, () => '- '),
|
|
227
|
+
orderedList: (node, options) => {
|
|
228
|
+
const raw = Number(node.attrs?.start);
|
|
229
|
+
const start = Number.isInteger(raw) && raw > 0 ? raw : 1;
|
|
230
|
+
return list(node, options, (index) => `${start + index}. `);
|
|
231
|
+
},
|
|
232
|
+
listItem: (node, options) => blocks(node.content, options),
|
|
233
|
+
taskList: (node, options) => (node.content ?? [])
|
|
234
|
+
.map((item) => {
|
|
235
|
+
const marker = `- [${item.attrs?.checked === true ? 'x' : ' '}] `;
|
|
236
|
+
const body = blocks(item.content, options);
|
|
237
|
+
return body ? prefixLines(body, marker, ' '.repeat(marker.length)) : marker.trimEnd();
|
|
238
|
+
})
|
|
239
|
+
.join('\n'),
|
|
240
|
+
taskItem: (node, options) => blocks(node.content, options),
|
|
241
|
+
blockquote: (node, options) => prefixLines(blocks(node.content, options), '> '),
|
|
242
|
+
/**
|
|
243
|
+
* A GitHub alert: a blockquote whose first line names the tone.
|
|
244
|
+
*
|
|
245
|
+
* The tone is Quire's own word rather than GitHub's nearest equivalent, because the round trip
|
|
246
|
+
* matters more than the rendering on one website. `[!SUCCESS]` is not one of GitHub's five, so it
|
|
247
|
+
* renders there as a plain blockquote with a line of text at the top — which is a smaller loss
|
|
248
|
+
* than mapping `success` onto `TIP` and importing it back as the wrong colour.
|
|
249
|
+
*/
|
|
250
|
+
callout: (node, options) => {
|
|
251
|
+
const tone = String(node.attrs?.tone ?? 'info');
|
|
252
|
+
const marker = CALLOUT_TONES.has(tone) ? tone : 'info';
|
|
253
|
+
return prefixLines(`[!${marker.toUpperCase()}]\n${blocks(node.content, options)}`, '> ');
|
|
254
|
+
},
|
|
255
|
+
codeBlock: (node) => {
|
|
256
|
+
const language = typeof node.attrs?.language === 'string' && /^[a-z0-9#+.-]{1,24}$/i.test(node.attrs.language)
|
|
257
|
+
? node.attrs.language
|
|
258
|
+
: '';
|
|
259
|
+
const body = textOf(node);
|
|
260
|
+
// Longer than any run of backticks in the body, or a snippet about Markdown closes its own fence.
|
|
261
|
+
const longest = Math.max(2, ...[...body.matchAll(/^`{3,}/gm)].map((m) => m[0].length));
|
|
262
|
+
const fence = '`'.repeat(longest + 1);
|
|
263
|
+
return `${fence}${language}\n${body}\n${fence}`;
|
|
264
|
+
},
|
|
265
|
+
horizontalRule: () => '---',
|
|
266
|
+
table,
|
|
267
|
+
// A row or a cell reached on its own is inside a table that has already written it; reaching one
|
|
268
|
+
// here would mean a malformed document, and its text is better kept than dropped.
|
|
269
|
+
tableRow: (node, options) => blocks(node.content, options),
|
|
270
|
+
tableCell: (node, options) => blocks(node.content, options),
|
|
271
|
+
tableHeader: (node, options) => blocks(node.content, options),
|
|
272
|
+
/**
|
|
273
|
+
* A toggle, as the HTML block Markdown has no syntax for.
|
|
274
|
+
*
|
|
275
|
+
* The blank lines around the body are load-bearing: without them a Markdown reader treats
|
|
276
|
+
* everything between the tags as raw HTML and stops parsing the prose inside, so a bulleted list
|
|
277
|
+
* inside a toggle arrives as literal hyphens.
|
|
278
|
+
*/
|
|
279
|
+
details: (node, options) => {
|
|
280
|
+
const summary = (node.content ?? []).find((child) => child.type === 'detailsSummary');
|
|
281
|
+
const rest = (node.content ?? []).filter((child) => child.type !== 'detailsSummary');
|
|
282
|
+
const title = summary ? inline(summary.content, options) : '';
|
|
283
|
+
return `<details>\n<summary>${title}</summary>\n\n${blocks(rest, options)}\n\n</details>`;
|
|
284
|
+
},
|
|
285
|
+
detailsSummary: (node, options) => inline(node.content, options),
|
|
286
|
+
detailsContent: (node, options) => blocks(node.content, options),
|
|
287
|
+
// Blocks in name only: both are inline nodes that can also stand alone as a whole block.
|
|
288
|
+
image: (node, options) => INLINE_WRITERS.image(node, options),
|
|
289
|
+
hardBreak: () => '',
|
|
290
|
+
mention: (node, options) => INLINE_WRITERS.mention(node, options),
|
|
291
|
+
pageMention: (node, options) => INLINE_WRITERS.pageMention(node, options),
|
|
292
|
+
text: (node) => escapeInline(node.text ?? ''),
|
|
293
|
+
};
|
|
294
|
+
/** Every node type this file can write, for the test that compares it against `PAGE_DOC_NODES`. */
|
|
295
|
+
export const MARKDOWN_NODES = [
|
|
296
|
+
...new Set(['doc', ...Object.keys(BLOCK_WRITERS), ...Object.keys(INLINE_WRITERS)]),
|
|
297
|
+
];
|
|
298
|
+
/**
|
|
299
|
+
* A stored page as Markdown. An empty or unreadable document is an empty string, never a throw.
|
|
300
|
+
*
|
|
301
|
+
* The title is not written here. A page's title is a column, not a node in its document, so whether
|
|
302
|
+
* it becomes an `# H1` at the top is a decision about the file — and the file writer next door makes
|
|
303
|
+
* it, once, for both formats.
|
|
304
|
+
*/
|
|
305
|
+
export function pageDocToMarkdown(doc, options = {}) {
|
|
306
|
+
if (!doc || !Array.isArray(doc.content))
|
|
307
|
+
return '';
|
|
308
|
+
return `${blocks(doc.content, options)
|
|
309
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
310
|
+
.trim()}\n`;
|
|
311
|
+
}
|
|
312
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../../src/server/export/markdown.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Where the compose stack puts it. `dev/compose.yml` publishes 3500 on the host. */
|
|
2
|
+
export declare const DEFAULT_GOTENBERG_URL = "http://gotenberg:3000";
|
|
3
|
+
export declare const gotenbergUrl: () => string;
|
|
4
|
+
export interface PdfOptions {
|
|
5
|
+
/** overrides `GOTENBERG_URL`; the export job does not pass one, the test does */
|
|
6
|
+
url?: string;
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
/** what Chromium prints in the footer's page numbers, and nothing else */
|
|
9
|
+
title?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A complete PDF, or a throw.
|
|
13
|
+
*
|
|
14
|
+
* The HTML has to be self-contained. Gotenberg's Chromium fetches whatever the document references,
|
|
15
|
+
* from inside its own container — so a presigned storage URL in an `<img src>` is both a network
|
|
16
|
+
* dependency this has no business having and a workspace uuid handed to a process that did not need
|
|
17
|
+
* it. Pictures reach here as `data:` URIs, put there by the caller.
|
|
18
|
+
*/
|
|
19
|
+
export declare function htmlToPdf(html: string, options?: PdfOptions): Promise<Buffer>;
|
|
20
|
+
//# sourceMappingURL=pdf.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pdf.d.ts","sourceRoot":"","sources":["../../../src/server/export/pdf.ts"],"names":[],"mappings":"AA2BA,qFAAqF;AACrF,eAAO,MAAM,qBAAqB,0BAA0B,CAAA;AAE5D,eAAO,MAAM,YAAY,QAAO,MAAoE,CAAA;AAQpG,MAAM,WAAW,UAAU;IACzB,iFAAiF;IACjF,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;;;;;;GAOG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,UAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CA2DvF"}
|