@kernhq/module-quire 0.13.0 → 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.
Files changed (101) hide show
  1. package/dist/contract/models.d.ts +185 -0
  2. package/dist/contract/models.d.ts.map +1 -1
  3. package/dist/contract/models.js +133 -0
  4. package/dist/contract/models.js.map +1 -1
  5. package/dist/contract/permissions.d.ts +14 -0
  6. package/dist/contract/permissions.d.ts.map +1 -1
  7. package/dist/contract/permissions.js +82 -0
  8. package/dist/contract/permissions.js.map +1 -1
  9. package/dist/contract/properties.d.ts +4 -4
  10. package/dist/contract/router.d.ts +542 -15
  11. package/dist/contract/router.d.ts.map +1 -1
  12. package/dist/contract/router.js +155 -8
  13. package/dist/contract/router.js.map +1 -1
  14. package/dist/server/_impl.d.ts +526 -15
  15. package/dist/server/_impl.d.ts.map +1 -1
  16. package/dist/server/_impl.js +166 -23
  17. package/dist/server/_impl.js.map +1 -1
  18. package/dist/server/export/html.d.ts +67 -0
  19. package/dist/server/export/html.d.ts.map +1 -0
  20. package/dist/server/export/html.js +206 -0
  21. package/dist/server/export/html.js.map +1 -0
  22. package/dist/server/export/markdown.d.ts +51 -0
  23. package/dist/server/export/markdown.d.ts.map +1 -0
  24. package/dist/server/export/markdown.js +312 -0
  25. package/dist/server/export/markdown.js.map +1 -0
  26. package/dist/server/export/pdf.d.ts +20 -0
  27. package/dist/server/export/pdf.d.ts.map +1 -0
  28. package/dist/server/export/pdf.js +91 -0
  29. package/dist/server/export/pdf.js.map +1 -0
  30. package/dist/server/export/zip.d.ts +31 -0
  31. package/dist/server/export/zip.d.ts.map +1 -0
  32. package/dist/server/export/zip.js +158 -0
  33. package/dist/server/export/zip.js.map +1 -0
  34. package/dist/server/import/csv.d.ts +77 -0
  35. package/dist/server/import/csv.d.ts.map +1 -0
  36. package/dist/server/import/csv.js +263 -0
  37. package/dist/server/import/csv.js.map +1 -0
  38. package/dist/server/import/html.d.ts +52 -0
  39. package/dist/server/import/html.d.ts.map +1 -0
  40. package/dist/server/import/html.js +472 -0
  41. package/dist/server/import/html.js.map +1 -0
  42. package/dist/server/import/markdown.d.ts +63 -0
  43. package/dist/server/import/markdown.d.ts.map +1 -0
  44. package/dist/server/import/markdown.js +692 -0
  45. package/dist/server/import/markdown.js.map +1 -0
  46. package/dist/server/import/plan.d.ts +70 -0
  47. package/dist/server/import/plan.d.ts.map +1 -0
  48. package/dist/server/import/plan.js +761 -0
  49. package/dist/server/import/plan.js.map +1 -0
  50. package/dist/server/import/ydoc.d.ts +35 -0
  51. package/dist/server/import/ydoc.d.ts.map +1 -0
  52. package/dist/server/import/ydoc.js +91 -0
  53. package/dist/server/import/ydoc.js.map +1 -0
  54. package/dist/server/import/zip.d.ts +63 -0
  55. package/dist/server/import/zip.d.ts.map +1 -0
  56. package/dist/server/import/zip.js +308 -0
  57. package/dist/server/import/zip.js.map +1 -0
  58. package/dist/server/index.d.ts.map +1 -1
  59. package/dist/server/index.js +3 -1
  60. package/dist/server/index.js.map +1 -1
  61. package/dist/server/schema.d.ts +445 -1
  62. package/dist/server/schema.d.ts.map +1 -1
  63. package/dist/server/schema.js +146 -0
  64. package/dist/server/schema.js.map +1 -1
  65. package/dist/server/services/databases.d.ts +5 -5
  66. package/dist/server/services/export.d.ts +176 -0
  67. package/dist/server/services/export.d.ts.map +1 -0
  68. package/dist/server/services/export.js +822 -0
  69. package/dist/server/services/export.js.map +1 -0
  70. package/dist/server/services/import.d.ts +109 -0
  71. package/dist/server/services/import.d.ts.map +1 -0
  72. package/dist/server/services/import.js +570 -0
  73. package/dist/server/services/import.js.map +1 -0
  74. package/dist/server/services/index.d.ts +28 -1
  75. package/dist/server/services/index.d.ts.map +1 -1
  76. package/dist/server/services/index.js +62 -1
  77. package/dist/server/services/index.js.map +1 -1
  78. package/dist/server/services/publications.d.ts +34 -1
  79. package/dist/server/services/publications.d.ts.map +1 -1
  80. package/dist/server/services/publications.js +62 -3
  81. package/dist/server/services/publications.js.map +1 -1
  82. package/dist/server/services/versions.d.ts +1 -1
  83. package/migrations/0010_transfers.sql +154 -0
  84. package/migrations/meta/_journal.json +7 -0
  85. package/package.json +1 -1
  86. package/src/client/components/ExportDialog.svelte +685 -0
  87. package/src/client/components/ImportDialog.svelte +702 -0
  88. package/src/client/components/ImportReport.svelte +310 -0
  89. package/src/client/components/SidebarSpaces.svelte +79 -0
  90. package/src/client/i18n.ts +614 -0
  91. package/src/client/index.ts +32 -0
  92. package/src/client/mock.ts +318 -0
  93. package/src/client/module.ts +36 -0
  94. package/src/client/pages/PageView.svelte +36 -0
  95. package/src/client/pages/TransfersPage.svelte +570 -0
  96. package/src/client/permissions.ts +11 -0
  97. package/src/client/query.ts +23 -0
  98. package/src/client/transfers.ts +142 -0
  99. package/src/contract/models.ts +152 -0
  100. package/src/contract/permissions.ts +84 -0
  101. package/src/contract/router.ts +170 -7
@@ -0,0 +1,263 @@
1
+ /** Enough rows for the guess to mean something without reading a whole spreadsheet twice. */
2
+ const SAMPLE = 500;
3
+ /** Above this a column of distinct strings is prose, not a set of choices. */
4
+ const MAX_OPTIONS = 40;
5
+ /** A choice longer than this is a sentence somebody typed, not a label. */
6
+ const MAX_OPTION_LENGTH = 60;
7
+ /**
8
+ * RFC 4180, with the two departures every real file needs.
9
+ *
10
+ * A quoted field may hold newlines and `""` for a literal quote — that part is the specification. The
11
+ * departures are that a lone `\r` or a `\r\n` both end a record (Excel writes one, Notion the other)
12
+ * and that a stray quote inside an unquoted field is a character rather than an error, because the
13
+ * alternative is refusing a file every other reader opens.
14
+ */
15
+ export function parseCsv(source) {
16
+ const rows = [];
17
+ let row = [];
18
+ let field = '';
19
+ let quoted = false;
20
+ let sawField = false;
21
+ const endField = () => {
22
+ row.push(field);
23
+ field = '';
24
+ sawField = false;
25
+ };
26
+ const endRow = () => {
27
+ endField();
28
+ rows.push(row);
29
+ row = [];
30
+ };
31
+ for (let i = 0; i < source.length; i++) {
32
+ const ch = source[i];
33
+ if (quoted) {
34
+ if (ch !== '"') {
35
+ field += ch;
36
+ continue;
37
+ }
38
+ if (source[i + 1] === '"') {
39
+ field += '"';
40
+ i++;
41
+ continue;
42
+ }
43
+ quoted = false;
44
+ continue;
45
+ }
46
+ if (ch === '"' && !sawField) {
47
+ quoted = true;
48
+ sawField = true;
49
+ continue;
50
+ }
51
+ if (ch === ',') {
52
+ endField();
53
+ continue;
54
+ }
55
+ if (ch === '\r') {
56
+ if (source[i + 1] === '\n')
57
+ i++;
58
+ endRow();
59
+ continue;
60
+ }
61
+ if (ch === '\n') {
62
+ endRow();
63
+ continue;
64
+ }
65
+ field += ch;
66
+ sawField = true;
67
+ }
68
+ // A file ending in a newline has already closed its last row; anything else is still open.
69
+ if (field.length > 0 || row.length > 0)
70
+ endRow();
71
+ // A trailing empty record is what a file ending in a newline leaves behind, and it is not a row.
72
+ while (rows.length > 0 && rows.at(-1).every((value) => value.trim() === ''))
73
+ rows.pop();
74
+ return rows;
75
+ }
76
+ const TRUE_WORDS = new Set(['yes', 'true', 'checked', 'done', 'y', '✓', '✔']);
77
+ const FALSE_WORDS = new Set(['no', 'false', 'unchecked', 'n', '✗', '✘']);
78
+ const RE_NUMBER = /^-?\d{1,3}(?:,\d{3})*(?:\.\d+)?$|^-?\d*\.?\d+$/;
79
+ const RE_CURRENCY = /^\s*([$£€¥₹﷼])\s*/;
80
+ const RE_EMAIL = /^[^\s@]+@[^\s@.]+\.[^\s@]+$/;
81
+ const RE_ISO_DATE = /^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
82
+ const RE_LONG_DATE = /^[A-Za-z]{3,9}\s+\d{1,2},?\s+\d{4}(?:\s+\d{1,2}:\d{2}\s*(?:[AaPp][Mm])?)?$/;
83
+ const RE_SLASH_DATE = /^\d{1,2}\/\d{1,2}\/\d{4}$/;
84
+ /** A value nobody filled in. Notion writes an empty cell; a spreadsheet sometimes writes a dash. */
85
+ const isEmpty = (value) => value.trim() === '' || value.trim() === '-';
86
+ /** `2026-01-05`, `January 5, 2026`, `05/01/2026` — as the ISO string a date cell stores. */
87
+ export function parseDateValue(raw) {
88
+ const value = raw.trim();
89
+ if (RE_ISO_DATE.test(value)) {
90
+ const at = new Date(value.length === 10 ? `${value}T00:00:00Z` : value.replace(' ', 'T'));
91
+ return Number.isNaN(at.getTime()) ? null : value.length === 10 ? value : at.toISOString();
92
+ }
93
+ if (RE_LONG_DATE.test(value) || RE_SLASH_DATE.test(value)) {
94
+ const at = new Date(value);
95
+ if (Number.isNaN(at.getTime()))
96
+ return null;
97
+ // Date-only input has no time to keep, and a `date` cell draws the first ten characters.
98
+ return /\d{1,2}:\d{2}/.test(value) ? at.toISOString() : at.toISOString().slice(0, 10);
99
+ }
100
+ return null;
101
+ }
102
+ /** `1,234.50`, `$99`, `12%` — as the number a numeric sort and a rollup can use. */
103
+ export function parseNumberValue(raw) {
104
+ let value = raw.trim();
105
+ const currency = RE_CURRENCY.exec(value)?.[1] ?? null;
106
+ if (currency)
107
+ value = value.replace(RE_CURRENCY, '');
108
+ const percent = value.endsWith('%');
109
+ if (percent)
110
+ value = value.slice(0, -1).trim();
111
+ if (!RE_NUMBER.test(value))
112
+ return null;
113
+ const parsed = Number(value.replaceAll(',', ''));
114
+ return Number.isFinite(parsed) ? { value: parsed, percent, currency } : null;
115
+ }
116
+ /** An option id derived from its label, so the same choice keeps the same id across two imports. */
117
+ function optionId(label, taken) {
118
+ const base = label
119
+ .toLowerCase()
120
+ .normalize('NFKD')
121
+ .replace(/[^\p{Letter}\p{Number}]+/gu, '-')
122
+ .replace(/^-+|-+$/g, '')
123
+ .slice(0, 48) || 'option';
124
+ let id = base;
125
+ for (let n = 2; taken.has(id); n++)
126
+ id = `${base}-${n}`;
127
+ taken.add(id);
128
+ return id;
129
+ }
130
+ /**
131
+ * The palette a select column's choices are coloured from.
132
+ *
133
+ * Cycled rather than random: two imports of the same file produce the same colours, and a person
134
+ * re-running an import after fixing one row does not find their board repainted.
135
+ */
136
+ const OPTION_COLOURS = ['slate', 'accent', 'success', 'warning', 'danger', 'info'];
137
+ /**
138
+ * One column's type, from its values.
139
+ *
140
+ * The order is the whole algorithm: checkbox before number (`1` and `0` are both), number before
141
+ * date (a bare year is a number), url and email before select (a column of six addresses is not six
142
+ * choices), multi-select before select (a comma is the only thing that separates them), and select
143
+ * before text (the fallback that is always true).
144
+ */
145
+ export function guessColumn(name, rawValues) {
146
+ const values = rawValues.slice(0, SAMPLE).filter((value) => !isEmpty(value));
147
+ const total = values.length;
148
+ const from = `from ${total} of ${rawValues.length} value${rawValues.length === 1 ? '' : 's'}`;
149
+ const plain = (type, what, config = {}) => ({
150
+ name,
151
+ type,
152
+ config,
153
+ note: `${name}: read as ${what}, ${from}`,
154
+ });
155
+ if (total === 0)
156
+ return {
157
+ name,
158
+ type: 'text',
159
+ config: {},
160
+ note: `${name}: read as text — the column has no values to judge`,
161
+ };
162
+ const lower = values.map((value) => value.trim().toLowerCase());
163
+ if (lower.every((value) => TRUE_WORDS.has(value) || FALSE_WORDS.has(value)))
164
+ return plain('checkbox', 'a checkbox');
165
+ const numbers = values.map(parseNumberValue);
166
+ if (numbers.every((n) => n !== null)) {
167
+ const percent = numbers.every((n) => n.percent);
168
+ const currency = numbers.find((n) => n.currency)?.currency ?? null;
169
+ const decimals = values.map((value) => (value.split('.')[1] ?? '').replace(/[^\d]/g, '').length);
170
+ const config = {
171
+ format: percent ? 'percent' : currency ? 'currency' : 'plain',
172
+ precision: Math.min(8, Math.max(...decimals, 0)),
173
+ };
174
+ return plain('number', percent ? 'a percentage' : currency ? 'an amount of money' : 'a number', config);
175
+ }
176
+ if (values.every((value) => parseDateValue(value) !== null)) {
177
+ const withTime = values.some((value) => /\d{1,2}:\d{2}/.test(value));
178
+ return plain('date', withTime ? 'a date and time' : 'a date', { includeTime: withTime });
179
+ }
180
+ if (values.every((value) => /^https?:\/\/\S+$/i.test(value.trim())))
181
+ return plain('url', 'a link');
182
+ if (values.every((value) => RE_EMAIL.test(value.trim())))
183
+ return plain('email', 'an email address');
184
+ const options = (labels) => {
185
+ const taken = new Set();
186
+ const ids = new Map();
187
+ const out = [];
188
+ labels.forEach((label, index) => {
189
+ const id = optionId(label, taken);
190
+ ids.set(label, id);
191
+ out.push({ id, label, colour: OPTION_COLOURS[index % OPTION_COLOURS.length] });
192
+ });
193
+ return { options: out, ids };
194
+ };
195
+ // A multi-select is a select whose cells hold more than one choice, so it is only worth guessing
196
+ // when at least one cell actually does — otherwise every select with a comma in one label becomes
197
+ // one, and the column silently stops matching an `equals` filter.
198
+ if (values.some((value) => value.includes(','))) {
199
+ const parts = [...new Set(values.flatMap((value) => splitChoices(value)))];
200
+ if (parts.length > 0 && parts.length <= MAX_OPTIONS && parts.every((p) => p.length <= MAX_OPTION_LENGTH))
201
+ return {
202
+ name,
203
+ type: 'multi_select',
204
+ config: { options: options(parts).options },
205
+ note: `${name}: read as a multi-select with ${parts.length} choices, ${from}`,
206
+ };
207
+ }
208
+ const distinct = [...new Set(values.map((value) => value.trim()))];
209
+ if (distinct.length <= MAX_OPTIONS &&
210
+ distinct.length < total &&
211
+ distinct.every((value) => value.length <= MAX_OPTION_LENGTH))
212
+ return {
213
+ name,
214
+ type: 'select',
215
+ config: { options: options(distinct).options },
216
+ note: `${name}: read as a select with ${distinct.length} choices, ${from}`,
217
+ };
218
+ return plain('text', 'text');
219
+ }
220
+ /** `Design, Urgent` → `['Design', 'Urgent']`, which is how every exporter writes a multi-select. */
221
+ export const splitChoices = (value) => value
222
+ .split(',')
223
+ .map((part) => part.trim())
224
+ .filter((part) => part.length > 0);
225
+ /**
226
+ * One cell, as the value the column's type stores.
227
+ *
228
+ * Null means "leave the key out of `props`" rather than "write null": an absent key is what every
229
+ * other writer in this module produces for an empty cell, and a null would make a `not_contains`
230
+ * filter behave differently for an imported row than for one somebody typed.
231
+ *
232
+ * `select` stores a **scalar** option id and `multi_select` an **array** of them, and the difference
233
+ * is load-bearing rather than cosmetic: `query.ts` compares a select as text through `props->>'key'`,
234
+ * so an array there stringifies to `["done"]` and matches no filter anybody can write. The cell
235
+ * component reads either shape, which is exactly why this is easy to get wrong and invisible on screen.
236
+ */
237
+ export function coerceValue(column, raw) {
238
+ if (isEmpty(raw))
239
+ return null;
240
+ const value = raw.trim();
241
+ switch (column.type) {
242
+ case 'checkbox':
243
+ return TRUE_WORDS.has(value.toLowerCase());
244
+ case 'number':
245
+ return parseNumberValue(value)?.value ?? null;
246
+ case 'date':
247
+ return parseDateValue(value);
248
+ case 'select': {
249
+ const option = (column.config.options ?? []).find((o) => o.label === value);
250
+ return option ? option.id : null;
251
+ }
252
+ case 'multi_select': {
253
+ const wanted = splitChoices(value);
254
+ const ids = wanted
255
+ .map((label) => (column.config.options ?? []).find((o) => o.label === label)?.id)
256
+ .filter((id) => id !== undefined);
257
+ return ids.length > 0 ? ids : null;
258
+ }
259
+ default:
260
+ return value;
261
+ }
262
+ }
263
+ //# sourceMappingURL=csv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csv.js","sourceRoot":"","sources":["../../../src/server/import/csv.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * HTML to a page document, for the one source that has no Markdown: a Confluence space export.
3
+ *
4
+ * Confluence's "Export space → HTML" writes one already-rendered `.html` per page, an `index.html`
5
+ * listing them, and an `attachments/` tree. There is no storage format to read — the wiki markup and
6
+ * the `<ac:…>` macros are gone by the time the export is written — so the rendered HTML *is* the
7
+ * document, and reading it is the only route in that does not ask a customer to install a plugin.
8
+ *
9
+ * **A tag soup parser, deliberately, and not a DOM.** No `jsdom`, no `parse5`: this reads one
10
+ * bounded, machine-written dialect, and the whole job is a tokeniser plus a stack that closes
11
+ * implied tags. Nothing here is executed, nothing is fetched, and no attribute reaches a page
12
+ * unfiltered — a `src` and an `href` come out as raw strings for `plan.ts` to resolve and everything
13
+ * else is dropped, so a `<script>`, an `onclick` or a `javascript:` href has nowhere to land. That is
14
+ * the property that matters, because the input is a file somebody uploaded.
15
+ *
16
+ * Confluence's own furniture is recognised where it maps onto something Quire has — an information
17
+ * macro is a callout, a `syntaxhighlighter` block is a code block, an inline task list is a task list
18
+ * — and ignored where it does not. What it cannot map, it keeps as prose rather than dropping, which
19
+ * is the same rule the Markdown reader follows and for the same reason.
20
+ */
21
+ import type { PageDoc } from '@kernhq/ui/editor/page-doc';
22
+ export interface HtmlElement {
23
+ tag: string;
24
+ attrs: Record<string, string>;
25
+ children: HtmlNode[];
26
+ }
27
+ export type HtmlNode = HtmlElement | {
28
+ text: string;
29
+ };
30
+ /** `&amp;`, `&#8212;`, `&#x2014;` — everything else is left exactly as it was written. */
31
+ export declare function decodeEntities(value: string): string;
32
+ /**
33
+ * A document as a tree.
34
+ *
35
+ * Comments, doctypes and processing instructions are skipped rather than kept: none of them can
36
+ * become a node, and a comment holding `<div>` would otherwise open one.
37
+ */
38
+ export declare function parseHtml(source: string): HtmlElement;
39
+ /** The first element matching a predicate, depth first. */
40
+ export declare function findElement(node: HtmlNode, match: (el: HtmlElement) => boolean): HtmlElement | null;
41
+ /** Every class on an element, lower-cased — Confluence's markup is identified entirely by class. */
42
+ export declare const classesOf: (el: HtmlElement) => string[];
43
+ /** All the text under a node, with runs of whitespace collapsed. What a title needs and no more. */
44
+ export declare function textContent(node: HtmlNode): string;
45
+ /**
46
+ * An HTML fragment as a page document.
47
+ *
48
+ * Always a `doc`: an element holding nothing is an empty page, which is a correct reading of an empty
49
+ * page rather than a failure.
50
+ */
51
+ export declare function htmlToPageDoc(node: HtmlNode): PageDoc;
52
+ //# sourceMappingURL=html.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../../src/server/import/html.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,OAAO,EAA4B,MAAM,4BAA4B,CAAA;AAMnF,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,QAAQ,EAAE,QAAQ,EAAE,CAAA;CACrB;AACD,MAAM,MAAM,QAAQ,GAAG,WAAW,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AA6ErD,0FAA0F;AAC1F,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAapD;AAaD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAyErD;AAED,2DAA2D;AAC3D,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,OAAO,GAAG,WAAW,GAAG,IAAI,CAQnG;AAED,oGAAoG;AACpG,eAAO,MAAM,SAAS,GAAI,IAAI,WAAW,KAAG,MAAM,EAAuD,CAAA;AAEzG,oGAAoG;AACpG,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAGlD;AAkSD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAErD"}