@kernhq/module-quire 0.7.4 → 0.9.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/README.md +93 -55
- package/dist/{server → client}/formula.d.ts +6 -0
- package/dist/client/formula.d.ts.map +1 -0
- package/dist/{server → client}/formula.js +6 -0
- package/dist/client/formula.js.map +1 -0
- package/dist/contract/properties.d.ts +26 -0
- package/dist/contract/properties.d.ts.map +1 -1
- package/dist/contract/properties.js +24 -0
- package/dist/contract/properties.js.map +1 -1
- package/dist/contract/router.d.ts +334 -0
- package/dist/contract/router.d.ts.map +1 -1
- package/dist/contract/router.js +43 -1
- package/dist/contract/router.js.map +1 -1
- package/dist/server/_impl.d.ts +341 -0
- package/dist/server/_impl.d.ts.map +1 -1
- package/dist/server/_impl.js +79 -10
- package/dist/server/_impl.js.map +1 -1
- package/dist/server/document.d.ts +13 -0
- package/dist/server/document.d.ts.map +1 -0
- package/dist/server/document.js +58 -0
- package/dist/server/document.js.map +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +26 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/render.d.ts +53 -0
- package/dist/server/render.d.ts.map +1 -0
- package/dist/server/render.js +358 -0
- package/dist/server/render.js.map +1 -0
- package/dist/server/services/databases.d.ts +73 -1
- package/dist/server/services/databases.d.ts.map +1 -1
- package/dist/server/services/databases.js +167 -10
- package/dist/server/services/databases.js.map +1 -1
- package/dist/server/services/pages.d.ts.map +1 -1
- package/dist/server/services/pages.js +11 -1
- package/dist/server/services/pages.js.map +1 -1
- package/dist/server/services/query.d.ts.map +1 -1
- package/dist/server/services/query.js +93 -37
- package/dist/server/services/query.js.map +1 -1
- package/dist/server/services/versions.d.ts.map +1 -1
- package/dist/server/services/versions.js +13 -1
- package/dist/server/services/versions.js.map +1 -1
- package/migrations/0006_rank_collation.sql +14 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +12 -9
- package/src/client/components/PageEditor.svelte +7 -0
- package/src/client/components/PageTreeRow.svelte +1 -1
- package/src/client/core-api.ts +38 -0
- package/src/client/database/BoardView.svelte +354 -0
- package/src/client/database/CalendarView.svelte +346 -0
- package/src/client/database/DatabaseView.svelte +785 -0
- package/src/client/database/FilterMenu.svelte +281 -0
- package/src/client/database/FilterValue.svelte +176 -0
- package/src/client/database/GalleryView.svelte +177 -0
- package/src/client/database/ListView.svelte +108 -0
- package/src/client/database/OptionChip.svelte +38 -0
- package/src/client/database/PropertyDialog.svelte +460 -0
- package/src/client/database/PropertyMenu.svelte +162 -0
- package/src/client/database/RowPanel.svelte +157 -0
- package/src/client/database/SortMenu.svelte +199 -0
- package/src/client/database/TableView.svelte +388 -0
- package/src/client/database/ViewDialog.svelte +229 -0
- package/src/client/database/cells/Cell.svelte +103 -0
- package/src/client/database/cells/CheckboxCell.svelte +33 -0
- package/src/client/database/cells/ComputedCell.svelte +100 -0
- package/src/client/database/cells/DateCell.svelte +97 -0
- package/src/client/database/cells/LinkCell.svelte +143 -0
- package/src/client/database/cells/NumberCell.svelte +131 -0
- package/src/client/database/cells/PersonCell.svelte +116 -0
- package/src/client/database/cells/RelationCell.svelte +222 -0
- package/src/client/database/cells/SelectCell.svelte +133 -0
- package/src/client/database/cells/TextCell.svelte +85 -0
- package/src/client/database/colours.ts +27 -0
- package/src/client/database/property-types.test.ts +64 -0
- package/src/client/database/property-types.ts +294 -0
- package/src/client/database/view-config.test.ts +148 -0
- package/src/client/database/view-config.ts +102 -0
- package/src/client/formula.test.ts +139 -0
- package/src/client/formula.ts +430 -0
- package/src/client/i18n.ts +1176 -0
- package/src/client/index.ts +31 -0
- package/src/client/mock.ts +511 -1
- package/src/client/pages/PageView.svelte +47 -17
- package/src/client/pages/SpacePage.svelte +8 -2
- package/src/client/query.ts +17 -4
- package/src/contract/properties.ts +28 -0
- package/src/contract/router.ts +46 -0
- package/dist/server/formula.d.ts.map +0 -1
- package/dist/server/formula.js.map +0 -1
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A page, drawn outside the browser.
|
|
3
|
+
*
|
|
4
|
+
* Everything that is not the editor needs this: publishing a page to a reader, exporting it,
|
|
5
|
+
* putting a snippet in a search result, mailing a digest. None of those has a DOM, so none of them
|
|
6
|
+
* could see a page at all before — the prose lives in a Yjs document, and the only thing that knew
|
|
7
|
+
* how to draw it was Tiptap.
|
|
8
|
+
*
|
|
9
|
+
* The rule this file exists to keep is the one in `@kernhq/ui`'s `schema.ts`: **the read side must
|
|
10
|
+
* be able to draw everything the writer can produce.** A node the editor emits and this file has no
|
|
11
|
+
* case for does not render as something plain — it vanishes, and the writer finds out from a reader.
|
|
12
|
+
* `render.test.ts` compares the dispatch tables below against `PAGE_DOC_NODES` and `PAGE_DOC_MARKS`
|
|
13
|
+
* in both directions, so a node with no renderer fails, and so does a renderer for a node that no
|
|
14
|
+
* longer exists.
|
|
15
|
+
*
|
|
16
|
+
* The output is safe to hand to `{@html}` or to write into a response. Every piece of text and
|
|
17
|
+
* every attribute value is escaped, anything that reaches a URL goes through `safeHref`, and
|
|
18
|
+
* everything numeric is parsed as a number rather than interpolated.
|
|
19
|
+
*/
|
|
20
|
+
const ESCAPES = {
|
|
21
|
+
'&': '&',
|
|
22
|
+
'<': '<',
|
|
23
|
+
'>': '>',
|
|
24
|
+
'"': '"',
|
|
25
|
+
"'": ''',
|
|
26
|
+
};
|
|
27
|
+
/** Both quote characters, so an escaped value is safe in an attribute however it is quoted. */
|
|
28
|
+
export const escapeHtml = (value) => value.replace(/[&<>"']/g, (c) => ESCAPES[c]);
|
|
29
|
+
const ALLOWED_PROTOCOLS = new Set(['http:', 'https:', 'mailto:']);
|
|
30
|
+
/**
|
|
31
|
+
* The only hrefs that reach the page.
|
|
32
|
+
*
|
|
33
|
+
* Stricter than the tracker's client renderer, deliberately. That one is `/^(https?:|mailto:|\/)/i`,
|
|
34
|
+
* which accepts `//evil.example` — a protocol-relative href inherits the page's scheme and leaves
|
|
35
|
+
* the site entirely, so it is an off-site link wearing the costume of a local one.
|
|
36
|
+
*
|
|
37
|
+
* The cleaning step is not cosmetic either. A browser strips tab, newline and carriage return from
|
|
38
|
+
* anywhere in a URL and trims leading control characters before deciding what protocol it is, so
|
|
39
|
+
* `java	script:alert(1)` navigates to `javascript:`. This has to reach the same verdict the
|
|
40
|
+
* browser will, or the check is reading a different string from the one that runs.
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* What the URL parser throws away before it looks at a URL, done by hand.
|
|
44
|
+
*
|
|
45
|
+
* Character codes rather than a regex, because the characters in question are control characters:
|
|
46
|
+
* a regex holding them is unreadable, and Biome refuses one on sight. Two rules, and they are not
|
|
47
|
+
* the same rule — everything up to and including a space is stripped from *the ends*, and tab,
|
|
48
|
+
* line feed and carriage return are removed from *anywhere*.
|
|
49
|
+
*/
|
|
50
|
+
function stripUrlNoise(raw) {
|
|
51
|
+
let start = 0;
|
|
52
|
+
let end = raw.length;
|
|
53
|
+
while (start < end && raw.charCodeAt(start) <= 0x20)
|
|
54
|
+
start++;
|
|
55
|
+
while (end > start && raw.charCodeAt(end - 1) <= 0x20)
|
|
56
|
+
end--;
|
|
57
|
+
let out = '';
|
|
58
|
+
for (let i = start; i < end; i++) {
|
|
59
|
+
const code = raw.charCodeAt(i);
|
|
60
|
+
if (code === 0x09 || code === 0x0a || code === 0x0d)
|
|
61
|
+
continue;
|
|
62
|
+
out += raw[i];
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
export function safeHref(href) {
|
|
67
|
+
if (typeof href !== 'string')
|
|
68
|
+
return null;
|
|
69
|
+
const cleaned = stripUrlNoise(href);
|
|
70
|
+
if (!cleaned)
|
|
71
|
+
return null;
|
|
72
|
+
// A fragment stays on this page — that is what a table of contents links to.
|
|
73
|
+
if (cleaned.startsWith('#'))
|
|
74
|
+
return cleaned;
|
|
75
|
+
if (cleaned.startsWith('//'))
|
|
76
|
+
return null;
|
|
77
|
+
// A root-relative path is a link inside the app, e.g. `/quire/ENG/<id>`.
|
|
78
|
+
if (cleaned.startsWith('/'))
|
|
79
|
+
return cleaned;
|
|
80
|
+
try {
|
|
81
|
+
return ALLOWED_PROTOCOLS.has(new URL(cleaned).protocol) ? cleaned : null;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// Anything that is not a URL at all, including a bare relative path we cannot resolve.
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** An attribute we generated ourselves is still checked: it arrived as document JSON. */
|
|
89
|
+
const ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
|
|
90
|
+
const LANGUAGE_PATTERN = /^[a-z0-9#+.-]{1,24}$/i;
|
|
91
|
+
const safeId = (value) => typeof value === 'string' && ID_PATTERN.test(value) ? value : null;
|
|
92
|
+
/** A whole number in a sane range, or nothing. Never the string that was in the document. */
|
|
93
|
+
function safeInt(value, min, max) {
|
|
94
|
+
const n = typeof value === 'number' ? value : Number.parseInt(String(value ?? ''), 10);
|
|
95
|
+
if (!Number.isFinite(n) || !Number.isInteger(n) || n < min || n > max)
|
|
96
|
+
return null;
|
|
97
|
+
return n;
|
|
98
|
+
}
|
|
99
|
+
const attr = (name, value) => value === null || value === undefined || value === '' ? '' : ` ${name}="${escapeHtml(String(value))}"`;
|
|
100
|
+
/** `id` when the writer's editor stamped one, so a heading can be linked to. */
|
|
101
|
+
const idAttr = (node) => attr('id', safeId(node.attrs?.id));
|
|
102
|
+
/** Every character in the node, ignoring marks. What a `<pre>` wants. */
|
|
103
|
+
function textOf(node) {
|
|
104
|
+
if (typeof node.text === 'string')
|
|
105
|
+
return node.text;
|
|
106
|
+
return (node.content ?? []).map(textOf).join('');
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The mark table.
|
|
110
|
+
*
|
|
111
|
+
* A mark this does not know is dropped and its text kept, which is the correct failure: the
|
|
112
|
+
* sentence still reads, it just is not bold.
|
|
113
|
+
*/
|
|
114
|
+
export const MARK_RENDERERS = {
|
|
115
|
+
bold: (html) => `<strong>${html}</strong>`,
|
|
116
|
+
italic: (html) => `<em>${html}</em>`,
|
|
117
|
+
strike: (html) => `<s>${html}</s>`,
|
|
118
|
+
underline: (html) => `<u>${html}</u>`,
|
|
119
|
+
code: (html) => `<code>${html}</code>`,
|
|
120
|
+
highlight: (html) => `<mark class="kern-highlight">${html}</mark>`,
|
|
121
|
+
link: (html, attrs) => {
|
|
122
|
+
const href = safeHref(attrs?.href);
|
|
123
|
+
// No `<a>` at all rather than a dead one: a link that cannot navigate must not look like it can.
|
|
124
|
+
if (!href)
|
|
125
|
+
return html;
|
|
126
|
+
return `<a href="${escapeHtml(href)}" rel="noreferrer noopener" target="_blank">${html}</a>`;
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
function renderMarks(html, marks) {
|
|
130
|
+
let out = html;
|
|
131
|
+
for (const mark of marks ?? []) {
|
|
132
|
+
const render = MARK_RENDERERS[mark?.type ?? ''];
|
|
133
|
+
if (render)
|
|
134
|
+
out = render(out, mark.attrs);
|
|
135
|
+
}
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
/** `<td>` and `<th>` differ only in the tag, so the attributes are written once. */
|
|
139
|
+
function cell(tag, node, children) {
|
|
140
|
+
const colspan = safeInt(node.attrs?.colspan, 1, 1000);
|
|
141
|
+
const rowspan = safeInt(node.attrs?.rowspan, 1, 1000);
|
|
142
|
+
const widths = Array.isArray(node.attrs?.colwidth)
|
|
143
|
+
? node.attrs.colwidth.map((w) => safeInt(w, 1, 10000)).filter((w) => w !== null)
|
|
144
|
+
: [];
|
|
145
|
+
const align = ['start', 'center', 'end'].includes(String(node.attrs?.align))
|
|
146
|
+
? String(node.attrs?.align)
|
|
147
|
+
: null;
|
|
148
|
+
return [
|
|
149
|
+
`<${tag}`,
|
|
150
|
+
idAttr(node),
|
|
151
|
+
colspan && colspan > 1 ? attr('colspan', colspan) : '',
|
|
152
|
+
rowspan && rowspan > 1 ? attr('rowspan', rowspan) : '',
|
|
153
|
+
widths.length ? attr('data-colwidth', widths.join(',')) : '',
|
|
154
|
+
attr('data-align', align),
|
|
155
|
+
`>${children}</${tag}>`,
|
|
156
|
+
].join('');
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* The node table.
|
|
160
|
+
*
|
|
161
|
+
* A `Record` rather than a `switch`, so the test can compare *this object* against the schema's
|
|
162
|
+
* node list. A switch would force the test to restate its cases, and a test that restates what it
|
|
163
|
+
* checks is a second copy that drifts rather than a check.
|
|
164
|
+
*
|
|
165
|
+
* `doc` and `text` are not here; they are the two the walker handles itself.
|
|
166
|
+
*/
|
|
167
|
+
export const NODE_RENDERERS = {
|
|
168
|
+
// An empty paragraph is a deliberate blank line, and `<p></p>` collapses to nothing.
|
|
169
|
+
paragraph: (node, children) => `<p${idAttr(node)}>${children || '<br>'}</p>`,
|
|
170
|
+
heading: (node, children) => {
|
|
171
|
+
const level = safeInt(node.attrs?.level, 1, 6) ?? 1;
|
|
172
|
+
return `<h${level}${idAttr(node)}>${children}</h${level}>`;
|
|
173
|
+
},
|
|
174
|
+
bulletList: (node, children) => `<ul${idAttr(node)}>${children}</ul>`,
|
|
175
|
+
// `start` and `type`, or a list that begins at 5 silently restarts at 1.
|
|
176
|
+
orderedList: (node, children) => {
|
|
177
|
+
const start = safeInt(node.attrs?.start, 1, 1_000_000);
|
|
178
|
+
const type = ['a', 'A', 'i', 'I', '1'].includes(String(node.attrs?.type))
|
|
179
|
+
? String(node.attrs?.type)
|
|
180
|
+
: null;
|
|
181
|
+
return `<ol${idAttr(node)}${start && start !== 1 ? attr('start', start) : ''}${attr('type', type)}>${children}</ol>`;
|
|
182
|
+
},
|
|
183
|
+
listItem: (node, children) => `<li${idAttr(node)}>${children}</li>`,
|
|
184
|
+
taskList: (node, children) => `<ul${idAttr(node)} class="kern-tasks" data-type="taskList">${children}</ul>`,
|
|
185
|
+
/*
|
|
186
|
+
* Mirrors what Tiptap's own TaskItem renders, so a page does not shift between the editor and the
|
|
187
|
+
* published copy — with `disabled` added. A checkbox a reader can click that changes nothing is
|
|
188
|
+
* worse than one that plainly cannot be clicked.
|
|
189
|
+
*/
|
|
190
|
+
taskItem: (node, children) => {
|
|
191
|
+
const checked = node.attrs?.checked === true;
|
|
192
|
+
return [
|
|
193
|
+
`<li${idAttr(node)} data-checked="${checked}" data-type="taskItem">`,
|
|
194
|
+
`<label><input type="checkbox" disabled${checked ? ' checked' : ''}><span></span></label>`,
|
|
195
|
+
`<div>${children}</div></li>`,
|
|
196
|
+
].join('');
|
|
197
|
+
},
|
|
198
|
+
blockquote: (node, children) => `<blockquote${idAttr(node)}>${children}</blockquote>`,
|
|
199
|
+
/*
|
|
200
|
+
* The text, not the rendered children. A code block's content is text and nothing else, and
|
|
201
|
+
* rendering its children would let a `code` mark inside it emit a nested `<code>`.
|
|
202
|
+
*/
|
|
203
|
+
codeBlock: (node) => {
|
|
204
|
+
const language = typeof node.attrs?.language === 'string' && LANGUAGE_PATTERN.test(node.attrs.language)
|
|
205
|
+
? node.attrs.language
|
|
206
|
+
: null;
|
|
207
|
+
const className = language ? ` class="language-${escapeHtml(language)}"` : '';
|
|
208
|
+
return `<pre${idAttr(node)}><code${className}>${escapeHtml(textOf(node))}</code></pre>`;
|
|
209
|
+
},
|
|
210
|
+
horizontalRule: (node) => `<hr${idAttr(node)}>`,
|
|
211
|
+
hardBreak: () => '<br>',
|
|
212
|
+
/*
|
|
213
|
+
* A picture is stored by file id — see the note on the node in `page-schema.ts`. Without a
|
|
214
|
+
* resolver there is nothing honest to emit, so nothing is: a broken-image icon tells a reader the
|
|
215
|
+
* page is damaged when it is the render that is under-configured.
|
|
216
|
+
*/
|
|
217
|
+
image: (node, _children, options) => {
|
|
218
|
+
const fileId = typeof node.attrs?.fileId === 'string' ? node.attrs.fileId : null;
|
|
219
|
+
const resolved = fileId ? (options.fileSrc?.(fileId) ?? null) : null;
|
|
220
|
+
const src = safeHref(resolved ?? node.attrs?.src);
|
|
221
|
+
if (!src)
|
|
222
|
+
return '';
|
|
223
|
+
const alt = typeof node.attrs?.alt === 'string' ? node.attrs.alt : '';
|
|
224
|
+
return [
|
|
225
|
+
`<img${idAttr(node)} src="${escapeHtml(src)}" alt="${escapeHtml(alt)}"`,
|
|
226
|
+
attr('title', typeof node.attrs?.title === 'string' ? node.attrs.title : null),
|
|
227
|
+
attr('width', safeInt(node.attrs?.width, 1, 20000)),
|
|
228
|
+
attr('height', safeInt(node.attrs?.height, 1, 20000)),
|
|
229
|
+
' loading="lazy">',
|
|
230
|
+
].join('');
|
|
231
|
+
},
|
|
232
|
+
/*
|
|
233
|
+
* The wrapper is not decoration. A table is the one block that cannot be made narrower, so it
|
|
234
|
+
* gets its own scroller — otherwise a wide one pushes the whole page sideways, which is exactly
|
|
235
|
+
* the defect shell's e2e sweep exists to catch, and it is worst in Persian.
|
|
236
|
+
*/
|
|
237
|
+
table: (node, children) => `<div class="kern-table-wrap"><table${idAttr(node)} class="kern-table"><tbody>${children}</tbody></table></div>`,
|
|
238
|
+
tableRow: (node, children) => `<tr${idAttr(node)}>${children}</tr>`,
|
|
239
|
+
tableCell: (node, children) => cell('td', node, children),
|
|
240
|
+
tableHeader: (node, children) => cell('th', node, children),
|
|
241
|
+
details: (node, children) => `<details${idAttr(node)} class="kern-toggle">${children}</details>`,
|
|
242
|
+
detailsSummary: (node, children) => `<summary${idAttr(node)}>${children}</summary>`,
|
|
243
|
+
detailsContent: (node, children) => `<div${idAttr(node)}>${children}</div>`,
|
|
244
|
+
/*
|
|
245
|
+
* Byte-identical to what the Callout node renders in the editor — that pairing is written down in
|
|
246
|
+
* `nodes/callout.ts` and this is the other half of it. The tone is narrowed to the closed set
|
|
247
|
+
* rather than trusted, because it ends up inside an attribute selector.
|
|
248
|
+
*/
|
|
249
|
+
callout: (node, children) => {
|
|
250
|
+
const tone = ['info', 'note', 'success', 'warning', 'danger'].includes(String(node.attrs?.tone))
|
|
251
|
+
? String(node.attrs?.tone)
|
|
252
|
+
: 'info';
|
|
253
|
+
return `<aside${idAttr(node)} class="kern-callout" data-callout="${tone}">${children}</aside>`;
|
|
254
|
+
},
|
|
255
|
+
/*
|
|
256
|
+
* A mention is an inline leaf: it has no children, so a case that rendered `children` would render
|
|
257
|
+
* nothing and the mention would simply disappear.
|
|
258
|
+
*/
|
|
259
|
+
mention: (node) => {
|
|
260
|
+
const label = String(node.attrs?.label ?? '');
|
|
261
|
+
const id = String(node.attrs?.id ?? '');
|
|
262
|
+
return `<span class="kern-mention" data-type="mention" data-id="${escapeHtml(id)}">@${escapeHtml(label)}</span>`;
|
|
263
|
+
},
|
|
264
|
+
pageMention: (node, _children, options) => {
|
|
265
|
+
const label = String(node.attrs?.label ?? '');
|
|
266
|
+
const id = String(node.attrs?.id ?? '');
|
|
267
|
+
const href = id ? safeHref(options.pageHref?.(id)) : null;
|
|
268
|
+
const body = escapeHtml(label || 'Untitled');
|
|
269
|
+
const data = ` data-type="pageMention" data-id="${escapeHtml(id)}"`;
|
|
270
|
+
// No resolver, or a page we cannot address: still readable, just not clickable.
|
|
271
|
+
if (!href)
|
|
272
|
+
return `<span class="kern-page-mention"${data}>${body}</span>`;
|
|
273
|
+
return `<a class="kern-page-mention" href="${escapeHtml(href)}"${data}>${body}</a>`;
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
function renderNode(node, options) {
|
|
277
|
+
if (node.type === 'text')
|
|
278
|
+
return renderMarks(escapeHtml(node.text ?? ''), node.marks);
|
|
279
|
+
const render = NODE_RENDERERS[node.type ?? ''];
|
|
280
|
+
const children = (node.content ?? []).map((child) => renderNode(child, options)).join('');
|
|
281
|
+
/*
|
|
282
|
+
* An unknown node keeps its children rather than dropping them. This should be unreachable — the
|
|
283
|
+
* test makes sure every node in the schema has a case — but a document written by a newer image
|
|
284
|
+
* and read by an older one is a real situation during a rolling deploy, and losing a paragraph
|
|
285
|
+
* because its wrapper is from next week is not an acceptable way to handle it.
|
|
286
|
+
*/
|
|
287
|
+
return render ? render(node, children, options) : children;
|
|
288
|
+
}
|
|
289
|
+
/** Sanitised HTML for a stored page. An empty or missing document renders as an empty string. */
|
|
290
|
+
export function renderPageDoc(doc, options = {}) {
|
|
291
|
+
if (!doc || !Array.isArray(doc.content))
|
|
292
|
+
return '';
|
|
293
|
+
return doc.content.map((node) => renderNode(node, options)).join('');
|
|
294
|
+
}
|
|
295
|
+
/** Nodes whose children are separate blocks, so their text needs a line between each. */
|
|
296
|
+
const BLOCK_PARENTS = new Set([
|
|
297
|
+
'blockquote',
|
|
298
|
+
'bulletList',
|
|
299
|
+
'callout',
|
|
300
|
+
'details',
|
|
301
|
+
'detailsContent',
|
|
302
|
+
'doc',
|
|
303
|
+
'listItem',
|
|
304
|
+
'orderedList',
|
|
305
|
+
'table',
|
|
306
|
+
'taskItem',
|
|
307
|
+
'taskList',
|
|
308
|
+
]);
|
|
309
|
+
/** What `collab`'s own flatten caps at; the same number, so the two agree about a very long page. */
|
|
310
|
+
const TEXT_LIMIT = 100_000;
|
|
311
|
+
/**
|
|
312
|
+
* The same document as plain text, for the search index and for previews.
|
|
313
|
+
*
|
|
314
|
+
* This exists because the flatten it replaces is wrong in a way nobody could see. `collab`'s
|
|
315
|
+
* `extractText` walks the Yjs tree and calls `toString()` on each `Y.XmlText`, which renders marks
|
|
316
|
+
* *as markup* — so a page containing one link put
|
|
317
|
+
* `<link class="null" href="…" rel="noreferrer noopener" target="_blank">` into the search body,
|
|
318
|
+
* and every page in the workspace matched a search for "noopener".
|
|
319
|
+
*/
|
|
320
|
+
export function textFromPageDoc(doc) {
|
|
321
|
+
if (!doc || !Array.isArray(doc.content))
|
|
322
|
+
return '';
|
|
323
|
+
const walk = (node) => {
|
|
324
|
+
if (typeof node.text === 'string')
|
|
325
|
+
return node.text;
|
|
326
|
+
switch (node.type) {
|
|
327
|
+
case 'hardBreak':
|
|
328
|
+
return '\n';
|
|
329
|
+
// The literal that produced it, so an edit round-trips instead of deleting who was mentioned.
|
|
330
|
+
case 'mention':
|
|
331
|
+
return `@${String(node.attrs?.label ?? '')}`;
|
|
332
|
+
case 'pageMention':
|
|
333
|
+
return String(node.attrs?.label ?? '');
|
|
334
|
+
// A picture contributes its alt text, the only part of it anybody can search for.
|
|
335
|
+
case 'image':
|
|
336
|
+
return typeof node.attrs?.alt === 'string' ? node.attrs.alt : '';
|
|
337
|
+
// Cells are joined with a tab, or two columns run together into one nonsense word.
|
|
338
|
+
case 'tableRow':
|
|
339
|
+
return (node.content ?? []).map(walk).join('\t');
|
|
340
|
+
default:
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
/*
|
|
344
|
+
* A toggle contributes its summary *and* what it hides, and a code block contributes its
|
|
345
|
+
* commands. Collapsed is a display choice, and a runbook's commands are exactly the thing
|
|
346
|
+
* people search for — so neither is skipped.
|
|
347
|
+
*/
|
|
348
|
+
const separator = BLOCK_PARENTS.has(node.type ?? '') ? '\n' : '';
|
|
349
|
+
return (node.content ?? []).map(walk).join(separator);
|
|
350
|
+
};
|
|
351
|
+
return (doc.content ?? [])
|
|
352
|
+
.map(walk)
|
|
353
|
+
.join('\n')
|
|
354
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
355
|
+
.trim()
|
|
356
|
+
.slice(0, TEXT_LIMIT);
|
|
357
|
+
}
|
|
358
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/server/render.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,16 +1,45 @@
|
|
|
1
1
|
import type { Principal } from '@kernhq/contracts';
|
|
2
2
|
import { type Kernel, type Tx } from '@kernhq/kernel';
|
|
3
|
-
import type { Database, Property, Row, View, ViewConfig } from '../../contract/index.js';
|
|
3
|
+
import type { Database, DatabaseRef, Property, Row, RowRef, View, ViewConfig } from '../../contract/index.js';
|
|
4
4
|
import { pages, properties, views } from '../schema.js';
|
|
5
5
|
import type { QuireAccess } from './access.js';
|
|
6
6
|
type PropertyRow = typeof properties.$inferSelect;
|
|
7
7
|
type ViewRow = typeof views.$inferSelect;
|
|
8
8
|
type PageRow = typeof pages.$inferSelect;
|
|
9
9
|
export declare const toProperty: (r: PropertyRow) => Property;
|
|
10
|
+
/**
|
|
11
|
+
* `config` is **parsed**, never cast.
|
|
12
|
+
*
|
|
13
|
+
* The column is one jsonb blob written by whatever the client last sent, so a cast promises
|
|
14
|
+
* `filterMode: 'and'` and hands the caller `undefined`. Every default in `ViewConfig` exists to be
|
|
15
|
+
* relied on — parsing here is what makes the type the client is given true.
|
|
16
|
+
*/
|
|
10
17
|
export declare const toView: (r: ViewRow) => View;
|
|
11
18
|
export declare const toRow: (p: PageRow) => Row;
|
|
12
19
|
export declare function quireDatabases(kernel: Kernel, access: QuireAccess): {
|
|
13
20
|
get(tx: Tx, workspaceId: string, databaseId: string): Promise<Database>;
|
|
21
|
+
/** Every database in a space, named. What a relation or a rollup is allowed to point at. */
|
|
22
|
+
list(tx: Tx, workspaceId: string, spaceId: string): Promise<DatabaseRef[]>;
|
|
23
|
+
/**
|
|
24
|
+
* The database a page *is*, or null.
|
|
25
|
+
*
|
|
26
|
+
* `pages.database_id` points the other way — it says which database a row belongs to — so this
|
|
27
|
+
* is the only way a `database` page can find what to draw. Null rather than a refusal: a page
|
|
28
|
+
* whose kind says database but which has none yet is a state the interface has to render.
|
|
29
|
+
*/
|
|
30
|
+
forPage(tx: Tx, workspaceId: string, pageId: string): Promise<Database | null>;
|
|
31
|
+
/**
|
|
32
|
+
* Rows by name — what a relation cell draws and what it searches.
|
|
33
|
+
*
|
|
34
|
+
* `ids` and `query` are OR-ed rather than AND-ed on purpose: a cell asks for the rows it holds
|
|
35
|
+
* *and* the rows matching what is being typed in one call, and a cell that loses the chips it
|
|
36
|
+
* already had the moment somebody types is the bug this shape avoids.
|
|
37
|
+
*/
|
|
38
|
+
lookup(tx: Tx, workspaceId: string, databaseId: string, opts: {
|
|
39
|
+
query: string;
|
|
40
|
+
ids: string[];
|
|
41
|
+
limit: number;
|
|
42
|
+
}): Promise<RowRef[]>;
|
|
14
43
|
/**
|
|
15
44
|
* Create a database on a page.
|
|
16
45
|
*
|
|
@@ -153,6 +182,41 @@ export declare function quireDatabases(kernel: Kernel, access: QuireAccess): {
|
|
|
153
182
|
position: string;
|
|
154
183
|
hidden: boolean;
|
|
155
184
|
}>;
|
|
185
|
+
/**
|
|
186
|
+
* Put a column behind another one.
|
|
187
|
+
*
|
|
188
|
+
* The rank is minted here, between the two neighbours, exactly as `pages.move` does it — the
|
|
189
|
+
* contract exposes `afterId` and never a raw position, because a client that writes its own
|
|
190
|
+
* fractional index writes one somebody else is already using.
|
|
191
|
+
*/
|
|
192
|
+
moveProperty(tx: Tx, workspaceId: string, propertyId: string, afterId: string | null): Promise<{
|
|
193
|
+
id: string;
|
|
194
|
+
databaseId: string;
|
|
195
|
+
key: string;
|
|
196
|
+
name: string;
|
|
197
|
+
type: "number" | "date" | "email" | "text" | "select" | "multi_select" | "status" | "person" | "files" | "checkbox" | "url" | "phone" | "relation" | "rollup" | "formula" | "created_time" | "created_by" | "edited_time" | "edited_by";
|
|
198
|
+
config: {
|
|
199
|
+
options?: {
|
|
200
|
+
id: string;
|
|
201
|
+
label: string;
|
|
202
|
+
colour: string;
|
|
203
|
+
group?: "todo" | "doing" | "done" | undefined;
|
|
204
|
+
}[] | undefined;
|
|
205
|
+
format?: string | undefined;
|
|
206
|
+
precision?: number | undefined;
|
|
207
|
+
includeTime?: boolean | undefined;
|
|
208
|
+
isRange?: boolean | undefined;
|
|
209
|
+
relationDatabaseId?: string | undefined;
|
|
210
|
+
relationPropertyId?: string | undefined;
|
|
211
|
+
rollupRelationPropertyId?: string | undefined;
|
|
212
|
+
rollupTargetPropertyId?: string | undefined;
|
|
213
|
+
rollupFunction?: "count" | "count_values" | "count_unique" | "sum" | "average" | "min" | "max" | "range" | "show_original" | "checked" | "unchecked" | "percent_checked" | undefined;
|
|
214
|
+
expression?: string | undefined;
|
|
215
|
+
multiple?: boolean | undefined;
|
|
216
|
+
};
|
|
217
|
+
position: string;
|
|
218
|
+
hidden: boolean;
|
|
219
|
+
}>;
|
|
156
220
|
removeProperty(tx: Tx, workspaceId: string, propertyId: string): Promise<{
|
|
157
221
|
id: string;
|
|
158
222
|
databaseId: string;
|
|
@@ -237,6 +301,14 @@ export declare function quireDatabases(kernel: Kernel, access: QuireAccess): {
|
|
|
237
301
|
position: string;
|
|
238
302
|
isDefault: boolean;
|
|
239
303
|
}>;
|
|
304
|
+
/**
|
|
305
|
+
* Delete a view.
|
|
306
|
+
*
|
|
307
|
+
* Refused when it is the **last** one, not when it is the default — those are different rules,
|
|
308
|
+
* and the first tab of every database is the default, so refusing the default meant the first
|
|
309
|
+
* tab could never be deleted however many others existed. When the default goes, the next view
|
|
310
|
+
* by position takes over; a database with views and no default renders nothing at all.
|
|
311
|
+
*/
|
|
240
312
|
removeView(tx: Tx, workspaceId: string, viewId: string): Promise<{
|
|
241
313
|
id: string;
|
|
242
314
|
databaseId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"databases.d.ts","sourceRoot":"","sources":["../../../src/server/services/databases.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAa,KAAK,MAAM,EAAE,KAAK,EAAE,EAAU,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"databases.d.ts","sourceRoot":"","sources":["../../../src/server/services/databases.ts"],"names":[],"mappings":"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"}
|