@kernhq/module-quire 0.13.1 → 0.15.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 +394 -0
- package/dist/contract/models.d.ts.map +1 -1
- package/dist/contract/models.js +284 -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 +114 -0
- package/dist/contract/permissions.js.map +1 -1
- package/dist/contract/properties.d.ts +7 -7
- package/dist/contract/router.d.ts +1124 -96
- package/dist/contract/router.d.ts.map +1 -1
- package/dist/contract/router.js +343 -1
- package/dist/contract/router.js.map +1 -1
- package/dist/server/_impl.d.ts +983 -1168
- package/dist/server/_impl.d.ts.map +1 -1
- package/dist/server/_impl.js +308 -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 +351 -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/render.d.ts +158 -0
- package/dist/server/render.d.ts.map +1 -1
- package/dist/server/render.js +240 -0
- package/dist/server/render.js.map +1 -1
- package/dist/server/schema.d.ts +704 -1
- package/dist/server/schema.d.ts.map +1 -1
- package/dist/server/schema.js +254 -1
- package/dist/server/schema.js.map +1 -1
- package/dist/server/services/databases.d.ts +10 -10
- 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 +32 -1
- package/dist/server/services/index.d.ts.map +1 -1
- package/dist/server/services/index.js +87 -4
- package/dist/server/services/index.js.map +1 -1
- package/dist/server/services/macros.d.ts +83 -0
- package/dist/server/services/macros.d.ts.map +1 -0
- package/dist/server/services/macros.js +488 -0
- package/dist/server/services/macros.js.map +1 -0
- package/dist/server/services/publications.d.ts +2 -1
- package/dist/server/services/publications.d.ts.map +1 -1
- package/dist/server/services/publications.js +43 -4
- package/dist/server/services/publications.js.map +1 -1
- package/dist/server/services/templates.d.ts +135 -0
- package/dist/server/services/templates.d.ts.map +1 -0
- package/dist/server/services/templates.js +897 -0
- package/dist/server/services/templates.js.map +1 -0
- package/dist/server/services/versions.d.ts +13 -1
- package/dist/server/services/versions.d.ts.map +1 -1
- package/dist/server/services/versions.js +3 -1
- package/dist/server/services/versions.js.map +1 -1
- package/migrations/0010_transfers.sql +154 -0
- package/migrations/0011_templates.sql +157 -0
- package/migrations/meta/_journal.json +14 -0
- package/package.json +5 -5
- 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/NewSpaceDialog.svelte +77 -8
- package/src/client/components/PageEditor.svelte +80 -0
- package/src/client/components/PagePicker.svelte +264 -0
- package/src/client/components/SaveAsTemplateDialog.svelte +502 -0
- package/src/client/components/SidebarSpaces.svelte +112 -1
- package/src/client/components/TemplatePicker.svelte +437 -0
- package/src/client/i18n.ts +941 -0
- package/src/client/index.ts +51 -0
- package/src/client/mock.ts +592 -0
- package/src/client/module.ts +36 -0
- package/src/client/pages/PageView.svelte +86 -0
- package/src/client/pages/SpacePage.svelte +20 -4
- package/src/client/pages/TransfersPage.svelte +570 -0
- package/src/client/permissions.ts +11 -0
- package/src/client/query.ts +40 -0
- package/src/client/transfers.ts +142 -0
- package/src/contract/models.ts +343 -0
- package/src/contract/permissions.ts +117 -0
- package/src/contract/router.ts +375 -0
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import { PAGE_MACRO_MAX_DEPTH, PAGE_MACRO_MAX_ROWS, } from '@kernhq/ui/editor/page-doc';
|
|
2
|
+
import { and, asc, desc, eq, inArray, isNull, sql } from 'drizzle-orm';
|
|
3
|
+
import { pageDocFromBase64, pageDocFromState } from '../document.js';
|
|
4
|
+
import { macroKey, macrosIn, renderPageDoc, textFromPageDoc, } from '../render.js';
|
|
5
|
+
import { pages, pageVersions } from '../schema.js';
|
|
6
|
+
import { documentNameOf } from './pages.js';
|
|
7
|
+
/**
|
|
8
|
+
* How much one macro may ask for.
|
|
9
|
+
*
|
|
10
|
+
* The document decides how much work a request does, so these are enforced here as well as in the
|
|
11
|
+
* editor: a `depth` of 900 in a page somebody pasted in is a recursive query somebody pasted in.
|
|
12
|
+
* The editor's own clamps are the same constants, out of `page-doc.ts` — the one file in
|
|
13
|
+
* `@kernhq/ui` that imports nothing and is safe to load in a backend process.
|
|
14
|
+
*/
|
|
15
|
+
const MAX_DEPTH = PAGE_MACRO_MAX_DEPTH;
|
|
16
|
+
const MAX_ROWS = PAGE_MACRO_MAX_ROWS;
|
|
17
|
+
/**
|
|
18
|
+
* How many rows a "recently updated" macro reads before filtering.
|
|
19
|
+
*
|
|
20
|
+
* It has to over-fetch, because the filter runs after the sort: the newest twenty-five pages in a
|
|
21
|
+
* space are not the newest twenty-five *this reader may see*. Four times the ceiling is the
|
|
22
|
+
* compromise — a reader denied three quarters of a space still fills the list, and the query stays
|
|
23
|
+
* one bounded index scan rather than a walk of the whole space. A reader denied more than that gets
|
|
24
|
+
* a short list, which is honest: the alternative is paging until the list is full, which is a
|
|
25
|
+
* query whose cost is set by how much somebody is not allowed to see.
|
|
26
|
+
*/
|
|
27
|
+
const RECENT_CANDIDATES = MAX_ROWS * 4;
|
|
28
|
+
/** How long an excerpt drawn beside a title may be, before it stops being a hint and becomes prose. */
|
|
29
|
+
const EXCERPT_CHARS = 200;
|
|
30
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
31
|
+
/**
|
|
32
|
+
* A page id out of a document, or null.
|
|
33
|
+
*
|
|
34
|
+
* The same narrowing `macroPageId` applies in the editor, restated for the same reason the status
|
|
35
|
+
* tones are: this file must not load the extension. A value that is not a uuid reaches a
|
|
36
|
+
* `= $1::uuid` and turns a macro nobody can see into a 500 on the page that holds it.
|
|
37
|
+
*/
|
|
38
|
+
const pageIdAttr = (value) => typeof value === 'string' && UUID_RE.test(value) ? value.toLowerCase() : null;
|
|
39
|
+
/** A whole number inside a range, or the fallback — never the string that was in the document. */
|
|
40
|
+
function countAttr(value, min, max, fallback) {
|
|
41
|
+
const n = typeof value === 'number' ? value : Number.parseInt(String(value ?? ''), 10);
|
|
42
|
+
if (!Number.isFinite(n) || !Number.isInteger(n) || n < min || n > max)
|
|
43
|
+
return fallback;
|
|
44
|
+
return n;
|
|
45
|
+
}
|
|
46
|
+
const flagAttr = (value) => value === true || value === 'true';
|
|
47
|
+
const CHILDREN_SORTS = ['position', 'title', 'updated'];
|
|
48
|
+
const sortAttr = (value) => CHILDREN_SORTS.includes(String(value)) ? value : 'position';
|
|
49
|
+
const RECENT_SCOPES = ['space', 'subtree'];
|
|
50
|
+
const scopeAttr = (value) => RECENT_SCOPES.includes(String(value)) ? value : 'space';
|
|
51
|
+
/** Trim flattened prose to something that sits under a title without becoming the page. */
|
|
52
|
+
function excerptOf(text) {
|
|
53
|
+
const clean = text.replace(/\s+/g, ' ').trim();
|
|
54
|
+
if (!clean)
|
|
55
|
+
return null;
|
|
56
|
+
if (clean.length <= EXCERPT_CHARS)
|
|
57
|
+
return clean;
|
|
58
|
+
// Cut at a space so the hint does not end mid-word; fall back to a hard cut for a long token.
|
|
59
|
+
const cut = clean.slice(0, EXCERPT_CHARS);
|
|
60
|
+
const space = cut.lastIndexOf(' ');
|
|
61
|
+
return `${(space > EXCERPT_CHARS / 2 ? cut.slice(0, space) : cut).trimEnd()}…`;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The first `excerpt` node in a document, flattened to text.
|
|
65
|
+
*
|
|
66
|
+
* One page, one excerpt: a second is ignored, because "the excerpt" would otherwise mean whichever
|
|
67
|
+
* one the walk happened to reach first, and that is a page whose macro changes meaning when
|
|
68
|
+
* somebody edits a paragraph far away from it. Depth-bounded for the reason `templates.ts` gives —
|
|
69
|
+
* `doc` is whatever was written to a JSONB column, and an adversarially nested document walked
|
|
70
|
+
* recursively is a stack overflow, which in Node is a process rather than an exception.
|
|
71
|
+
*/
|
|
72
|
+
function markedExcerpt(doc, depth = 0) {
|
|
73
|
+
if (!doc || depth > MAX_DEPTH * 4)
|
|
74
|
+
return null;
|
|
75
|
+
for (const node of (doc.content ?? [])) {
|
|
76
|
+
if (node.type === 'excerpt') {
|
|
77
|
+
const text = textFromPageDoc({ type: 'doc', content: node.content ?? [] });
|
|
78
|
+
const clean = text.replace(/\s+/g, ' ').trim();
|
|
79
|
+
if (clean)
|
|
80
|
+
return clean;
|
|
81
|
+
}
|
|
82
|
+
const nested = markedExcerpt({ type: 'doc', content: node.content ?? [] }, depth + 1);
|
|
83
|
+
if (nested)
|
|
84
|
+
return nested;
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
export function quireMacros(kernel, access) {
|
|
89
|
+
/**
|
|
90
|
+
* A page's prose, as a document — the live one first.
|
|
91
|
+
*
|
|
92
|
+
* Same order as `templates.ts`'s: what the person looking at the page can see, then the newest
|
|
93
|
+
* stored version for a page the collab service has forgotten or never held. An include that drew
|
|
94
|
+
* last week's published copy while the source page says something else is the kind of wrong that
|
|
95
|
+
* takes a day to notice.
|
|
96
|
+
*/
|
|
97
|
+
async function docOfPage(tx, workspaceId, pageId) {
|
|
98
|
+
const live = await kernel
|
|
99
|
+
.call('collab.document.state', {
|
|
100
|
+
name: documentNameOf({ workspaceId, id: pageId }),
|
|
101
|
+
})
|
|
102
|
+
.catch(() => null);
|
|
103
|
+
const fromLive = pageDocFromBase64(live?.state ?? null);
|
|
104
|
+
if (fromLive)
|
|
105
|
+
return fromLive;
|
|
106
|
+
const [version] = await tx
|
|
107
|
+
.select({ state: pageVersions.state })
|
|
108
|
+
.from(pageVersions)
|
|
109
|
+
.where(and(eq(pageVersions.workspaceId, workspaceId), eq(pageVersions.pageId, pageId)))
|
|
110
|
+
.orderBy(desc(pageVersions.createdAt))
|
|
111
|
+
.limit(1);
|
|
112
|
+
return version ? pageDocFromState(version.state) : null;
|
|
113
|
+
}
|
|
114
|
+
const selection = {
|
|
115
|
+
id: pages.id,
|
|
116
|
+
parentId: pages.parentId,
|
|
117
|
+
spaceId: pages.spaceId,
|
|
118
|
+
title: pages.title,
|
|
119
|
+
icon: pages.icon,
|
|
120
|
+
text: pages.text,
|
|
121
|
+
updatedAt: pages.updatedAt,
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* The pages a macro may consider at all, before anybody is asked about them.
|
|
125
|
+
*
|
|
126
|
+
* Trashed, archived, non-`page` and database rows are excluded here rather than in each macro,
|
|
127
|
+
* because every one of them wants the same answer and the one that forgot would be the leak.
|
|
128
|
+
* A database row is a page, which is exactly why it has to be named: a children macro on a page
|
|
129
|
+
* that happens to hold a database would otherwise draw four thousand rows as a table of contents.
|
|
130
|
+
*/
|
|
131
|
+
const drawable = (workspaceId) => and(eq(pages.workspaceId, workspaceId), eq(pages.kind, 'page'), isNull(pages.databaseId), isNull(pages.deletedAt), isNull(pages.archivedAt));
|
|
132
|
+
/**
|
|
133
|
+
* May this audience be shown this page?
|
|
134
|
+
*
|
|
135
|
+
* The one question this file exists to ask, and the only place either audience is interpreted.
|
|
136
|
+
* A publication is a set membership — no principal exists to ask, and the walk that built the set
|
|
137
|
+
* already applied every rule about what public means. A reader is a page-scoped permission check
|
|
138
|
+
* whose parent chain carries every ancestor, so a restriction set on a section applies to
|
|
139
|
+
* everything under it without having been copied down.
|
|
140
|
+
*/
|
|
141
|
+
async function visible(workspaceId, audience, page) {
|
|
142
|
+
if (audience.kind === 'publication')
|
|
143
|
+
return audience.pageIds.has(page.id);
|
|
144
|
+
return access.canPage(audience.principal, 'quire.page.view', workspaceId, {
|
|
145
|
+
pageId: page.id,
|
|
146
|
+
spaceId: page.spaceId,
|
|
147
|
+
ancestorIds: page.ancestorIds,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/** A visible page as the renderer wants it. Nothing here is reached for a page that failed the gate. */
|
|
151
|
+
function refOf(page, ctx, opts) {
|
|
152
|
+
return {
|
|
153
|
+
id: page.id,
|
|
154
|
+
title: page.title,
|
|
155
|
+
icon: page.icon,
|
|
156
|
+
href: ctx.pageHref?.(page.id) ?? null,
|
|
157
|
+
updated: opts.updated ? formatDate(page.updatedAt, ctx.locale) : null,
|
|
158
|
+
excerpt: opts.excerpt ? excerptOf(page.text) : null,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* A date the reader can read, formatted here because the renderer does no date arithmetic.
|
|
163
|
+
*
|
|
164
|
+
* Through `Intl`, so a Persian workspace gets Persian digits and an Arabic one an Arabic calendar
|
|
165
|
+
* without this file knowing anything about either — the same bargain `templates.ts` makes for a
|
|
166
|
+
* template's `{{date}}`.
|
|
167
|
+
*/
|
|
168
|
+
function formatDate(value, locale) {
|
|
169
|
+
if (!(value instanceof Date) || Number.isNaN(value.getTime()))
|
|
170
|
+
return null;
|
|
171
|
+
try {
|
|
172
|
+
return new Intl.DateTimeFormat(locale || 'en', { dateStyle: 'medium' }).format(value);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return new Intl.DateTimeFormat('en', { dateStyle: 'medium' }).format(value);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/** The chain above one page, so its children can be asked about without a query per level. */
|
|
179
|
+
async function scopeChain(tx, workspaceId, pageId) {
|
|
180
|
+
const scope = await access.scopeOf(tx, workspaceId, pageId).catch(() => null);
|
|
181
|
+
return scope ? scope.ancestorIds : [];
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* The visible pages under one page, level by level, pruning as it goes.
|
|
185
|
+
*
|
|
186
|
+
* Breadth-first and one query per level rather than one recursive query, because the permission
|
|
187
|
+
* question is asked *between* levels: a page that fails the gate is never expanded, so its
|
|
188
|
+
* children are not read from the database at all. That is what makes this a prune rather than a
|
|
189
|
+
* filter, and it is also why the loop is bounded by `depth` — the number of queries is the depth
|
|
190
|
+
* the macro asked for, which is at most `MAX_DEPTH`.
|
|
191
|
+
*/
|
|
192
|
+
async function childrenTree(tx, workspaceId, audience, ctx, root, depth, sort, showExcerpt) {
|
|
193
|
+
const order = sort === 'title' ? asc(pages.title) : sort === 'updated' ? desc(pages.updatedAt) : asc(pages.position);
|
|
194
|
+
const top = [];
|
|
195
|
+
// The frontier carries each surviving page's own ancestor chain, which its children inherit.
|
|
196
|
+
let frontier = [
|
|
197
|
+
{
|
|
198
|
+
ref: { id: root.id, title: '', icon: null, href: null, updated: null, excerpt: null },
|
|
199
|
+
id: root.id,
|
|
200
|
+
ancestorIds: root.ancestorIds,
|
|
201
|
+
},
|
|
202
|
+
];
|
|
203
|
+
let drawn = 0;
|
|
204
|
+
for (let level = 0; level < depth && frontier.length > 0 && drawn < MAX_ROWS; level++) {
|
|
205
|
+
const parentIds = frontier.map((entry) => entry.id);
|
|
206
|
+
const rows = await tx
|
|
207
|
+
.select(selection)
|
|
208
|
+
.from(pages)
|
|
209
|
+
.where(and(drawable(workspaceId), inArray(pages.parentId, parentIds)))
|
|
210
|
+
.orderBy(order)
|
|
211
|
+
.limit(MAX_ROWS * 4);
|
|
212
|
+
const next = [];
|
|
213
|
+
for (const entry of frontier) {
|
|
214
|
+
for (const row of rows) {
|
|
215
|
+
if (row.parentId !== entry.id)
|
|
216
|
+
continue;
|
|
217
|
+
if (drawn >= MAX_ROWS)
|
|
218
|
+
break;
|
|
219
|
+
const scoped = { ...row, ancestorIds: [entry.id, ...entry.ancestorIds] };
|
|
220
|
+
if (!(await visible(workspaceId, audience, scoped)))
|
|
221
|
+
continue;
|
|
222
|
+
const ref = refOf(row, ctx, { excerpt: showExcerpt, updated: false });
|
|
223
|
+
drawn++;
|
|
224
|
+
if (level === 0)
|
|
225
|
+
top.push(ref);
|
|
226
|
+
else {
|
|
227
|
+
entry.ref.children ??= [];
|
|
228
|
+
entry.ref.children.push(ref);
|
|
229
|
+
}
|
|
230
|
+
next.push({ ref, id: row.id, ancestorIds: scoped.ancestorIds });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
frontier = next;
|
|
234
|
+
}
|
|
235
|
+
return top;
|
|
236
|
+
}
|
|
237
|
+
/** The most recently changed pages an audience may see, in a space or under one page. */
|
|
238
|
+
async function recentlyUpdated(tx, workspaceId, audience, ctx, home, scope, limit) {
|
|
239
|
+
/*
|
|
240
|
+
* A subtree is resolved with one recursive query rather than level by level, because unlike the
|
|
241
|
+
* children macro this list is ordered by time across the whole subtree — there is no level to
|
|
242
|
+
* stop at. The prune is then applied to the result: a page whose ancestor chain contains
|
|
243
|
+
* something the audience cannot see is dropped along with anything under it, which the chain
|
|
244
|
+
* carried on each row makes a check rather than a second walk.
|
|
245
|
+
*/
|
|
246
|
+
let rows;
|
|
247
|
+
let chains = new Map();
|
|
248
|
+
if (scope === 'subtree') {
|
|
249
|
+
const res = await tx.execute(sql `
|
|
250
|
+
with recursive tree as (
|
|
251
|
+
select p.id, p.parent_id, p.space_id, p.title, p.icon, p.text, p.updated_at,
|
|
252
|
+
array[]::uuid[] as path, 0 as depth
|
|
253
|
+
from mod_quire.pages p
|
|
254
|
+
where p.workspace_id = ${workspaceId}::uuid
|
|
255
|
+
and p.parent_id = ${home.id}::uuid
|
|
256
|
+
and p.kind = 'page' and p.database_id is null
|
|
257
|
+
and p.deleted_at is null and p.archived_at is null
|
|
258
|
+
union all
|
|
259
|
+
select c.id, c.parent_id, c.space_id, c.title, c.icon, c.text, c.updated_at,
|
|
260
|
+
tree.path || tree.id, tree.depth + 1
|
|
261
|
+
from mod_quire.pages c
|
|
262
|
+
join tree on c.parent_id = tree.id
|
|
263
|
+
where c.workspace_id = ${workspaceId}::uuid
|
|
264
|
+
and c.kind = 'page' and c.database_id is null
|
|
265
|
+
and c.deleted_at is null and c.archived_at is null
|
|
266
|
+
and tree.depth < ${MAX_DEPTH}
|
|
267
|
+
) cycle id set looped using cyclepath
|
|
268
|
+
select id, parent_id, space_id, title, icon, text, updated_at, path
|
|
269
|
+
from tree
|
|
270
|
+
order by updated_at desc
|
|
271
|
+
limit ${RECENT_CANDIDATES}
|
|
272
|
+
`);
|
|
273
|
+
rows = res.rows.map((row) => ({
|
|
274
|
+
id: row.id,
|
|
275
|
+
parentId: row.parent_id,
|
|
276
|
+
spaceId: row.space_id,
|
|
277
|
+
title: row.title,
|
|
278
|
+
icon: row.icon,
|
|
279
|
+
text: row.text,
|
|
280
|
+
updatedAt: row.updated_at instanceof Date ? row.updated_at : new Date(row.updated_at),
|
|
281
|
+
}));
|
|
282
|
+
chains = new Map(res.rows.map((row) => [row.id, [...(row.path ?? [])].reverse().concat(home.id, home.ancestorIds)]));
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
rows = await tx
|
|
286
|
+
.select(selection)
|
|
287
|
+
.from(pages)
|
|
288
|
+
.where(and(drawable(workspaceId), eq(pages.spaceId, home.spaceId)))
|
|
289
|
+
.orderBy(desc(pages.updatedAt))
|
|
290
|
+
.limit(RECENT_CANDIDATES);
|
|
291
|
+
}
|
|
292
|
+
const out = [];
|
|
293
|
+
for (const row of rows) {
|
|
294
|
+
if (out.length >= limit)
|
|
295
|
+
break;
|
|
296
|
+
/*
|
|
297
|
+
* A space-scoped list has no walk to carry a chain, so each surviving row is asked about with
|
|
298
|
+
* its own ancestors. `scopeOf` is one recursive query per row, which is why the candidate set
|
|
299
|
+
* is bounded — and why the subtree branch, which already knows every chain, does not pay it.
|
|
300
|
+
*/
|
|
301
|
+
const ancestorIds = chains.get(row.id) ?? (await scopeChain(tx, workspaceId, row.id));
|
|
302
|
+
if (!(await visible(workspaceId, audience, { ...row, ancestorIds })))
|
|
303
|
+
continue;
|
|
304
|
+
out.push(refOf(row, ctx, { excerpt: false, updated: true }));
|
|
305
|
+
}
|
|
306
|
+
return out;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Who has written on this page.
|
|
310
|
+
*
|
|
311
|
+
* **A reader only.** Every other public response in this module is scrubbed of anything
|
|
312
|
+
* identifying a person — see the note at the top of `publications.ts` — and a page's authors are
|
|
313
|
+
* the customer's staff rather than the page's content. Somebody who wants a byline on a published
|
|
314
|
+
* page writes one. That decision is made by having no publication branch at all rather than by
|
|
315
|
+
* returning an empty list from one, so a later edit cannot turn it back on by accident.
|
|
316
|
+
*/
|
|
317
|
+
async function contributors(tx, workspaceId, audience, home, limit) {
|
|
318
|
+
if (audience.kind !== 'reader')
|
|
319
|
+
return null;
|
|
320
|
+
if (!(await visible(workspaceId, audience, home)))
|
|
321
|
+
return null;
|
|
322
|
+
const [row] = await tx
|
|
323
|
+
.select({ createdBy: pages.createdBy, updatedBy: pages.updatedBy })
|
|
324
|
+
.from(pages)
|
|
325
|
+
.where(and(eq(pages.workspaceId, workspaceId), eq(pages.id, home.id)))
|
|
326
|
+
.limit(1);
|
|
327
|
+
// Newest first, so a long-lived page credits whoever has been working on it lately.
|
|
328
|
+
const versions = await tx
|
|
329
|
+
.select({ authorId: pageVersions.authorId })
|
|
330
|
+
.from(pageVersions)
|
|
331
|
+
.where(and(eq(pageVersions.workspaceId, workspaceId), eq(pageVersions.pageId, home.id)))
|
|
332
|
+
.orderBy(desc(pageVersions.createdAt))
|
|
333
|
+
.limit(RECENT_CANDIDATES);
|
|
334
|
+
const ordered = [];
|
|
335
|
+
const seen = new Set();
|
|
336
|
+
for (const id of [row?.updatedBy, ...versions.map((v) => v.authorId), row?.createdBy]) {
|
|
337
|
+
if (!id || seen.has(id))
|
|
338
|
+
continue;
|
|
339
|
+
seen.add(id);
|
|
340
|
+
ordered.push(id);
|
|
341
|
+
if (ordered.length >= limit)
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
if (ordered.length === 0)
|
|
345
|
+
return null;
|
|
346
|
+
/*
|
|
347
|
+
* Names from core, which is the only thing that has them. A lookup that fails draws no
|
|
348
|
+
* contributors rather than a list of ids: an id is not a name, and printing one on somebody's
|
|
349
|
+
* page is both useless and a string a reader can try somewhere else.
|
|
350
|
+
*/
|
|
351
|
+
const people = await kernel
|
|
352
|
+
.call('core.users.list', { ids: ordered })
|
|
353
|
+
.catch(() => null);
|
|
354
|
+
if (!people)
|
|
355
|
+
return null;
|
|
356
|
+
const byId = new Map(people.map((person) => [person.id, person.name]));
|
|
357
|
+
const named = ordered
|
|
358
|
+
.map((id) => byId.get(id))
|
|
359
|
+
.filter((name) => typeof name === 'string' && name.trim().length > 0)
|
|
360
|
+
.map((name) => ({ name }));
|
|
361
|
+
return named.length > 0 ? { kind: 'people', people: named } : null;
|
|
362
|
+
}
|
|
363
|
+
/** The page a macro points at, with its chain, or null when it is not drawable at all. */
|
|
364
|
+
async function targetPage(tx, workspaceId, pageId) {
|
|
365
|
+
const [row] = await tx
|
|
366
|
+
.select(selection)
|
|
367
|
+
.from(pages)
|
|
368
|
+
.where(and(drawable(workspaceId), eq(pages.id, pageId)))
|
|
369
|
+
.limit(1);
|
|
370
|
+
if (!row)
|
|
371
|
+
return null;
|
|
372
|
+
return { ...row, ancestorIds: await scopeChain(tx, workspaceId, row.id) };
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
/**
|
|
376
|
+
* Answer every reading macro in a document, for one audience, in one pass.
|
|
377
|
+
*
|
|
378
|
+
* Returns the synchronous lookup `renderPageDoc` takes. A macro this could not answer — a page
|
|
379
|
+
* that was deleted, a page the audience may not see, a lookup that failed — is simply absent
|
|
380
|
+
* from the map, and an absent answer is the empty frame. There is no path through this function
|
|
381
|
+
* that returns a page the audience failed the gate on, and no way to express one: `visible` is
|
|
382
|
+
* the only thing that builds a `MacroPageRef`'s input, and every caller of `refOf` has just
|
|
383
|
+
* awaited it.
|
|
384
|
+
*/
|
|
385
|
+
async resolve(tx, workspaceId, doc, audience, ctx) {
|
|
386
|
+
const nodes = macrosIn(doc);
|
|
387
|
+
if (nodes.length === 0)
|
|
388
|
+
return () => null;
|
|
389
|
+
/*
|
|
390
|
+
* The page the document is on, which four of the five macros need — as the default target,
|
|
391
|
+
* as the space a recent list is drawn from, and as the page a byline is about. Without one
|
|
392
|
+
* (a preview, an import) only a macro carrying an explicit page id can resolve, and the rest
|
|
393
|
+
* draw empty frames.
|
|
394
|
+
*/
|
|
395
|
+
const home = ctx.pageId ? await targetPage(tx, workspaceId, ctx.pageId) : null;
|
|
396
|
+
const answers = new Map();
|
|
397
|
+
for (const node of nodes) {
|
|
398
|
+
const content = await resolveOne(tx, workspaceId, audience, ctx, home, node);
|
|
399
|
+
if (content)
|
|
400
|
+
answers.set(macroKey(node), content);
|
|
401
|
+
}
|
|
402
|
+
return (node) => answers.get(macroKey(node)) ?? null;
|
|
403
|
+
},
|
|
404
|
+
};
|
|
405
|
+
async function resolveOne(tx, workspaceId, audience, ctx, home, node) {
|
|
406
|
+
const attrs = node.attrs ?? {};
|
|
407
|
+
/*
|
|
408
|
+
* `pageId` null means "the page this macro is on" — what almost every use of a children macro
|
|
409
|
+
* wants, and what keeps it working after the page is copied into a template or duplicated.
|
|
410
|
+
*/
|
|
411
|
+
const named = pageIdAttr(attrs.pageId);
|
|
412
|
+
switch (node.type) {
|
|
413
|
+
case 'pageChildren': {
|
|
414
|
+
const root = named ? await targetPage(tx, workspaceId, named) : home;
|
|
415
|
+
if (!root)
|
|
416
|
+
return null;
|
|
417
|
+
/*
|
|
418
|
+
* The root of a children list is asked about too. Pointing a macro at a page the reader may
|
|
419
|
+
* not see must not list that page's children — the parent is the permission boundary its
|
|
420
|
+
* children inherit, so skipping this check would make the macro a way around it.
|
|
421
|
+
*/
|
|
422
|
+
if (!(await visible(workspaceId, audience, root)))
|
|
423
|
+
return null;
|
|
424
|
+
const pages_ = await childrenTree(tx, workspaceId, audience, ctx, root, countAttr(attrs.depth, 1, MAX_DEPTH, 1), sortAttr(attrs.sort), flagAttr(attrs.showExcerpt));
|
|
425
|
+
return { kind: 'pages', pages: pages_ };
|
|
426
|
+
}
|
|
427
|
+
case 'recentlyUpdated': {
|
|
428
|
+
const anchor = named ? await targetPage(tx, workspaceId, named) : home;
|
|
429
|
+
if (!anchor)
|
|
430
|
+
return null;
|
|
431
|
+
if (!(await visible(workspaceId, audience, anchor)))
|
|
432
|
+
return null;
|
|
433
|
+
const rows = await recentlyUpdated(tx, workspaceId, audience, ctx, anchor, scopeAttr(attrs.scope), countAttr(attrs.limit, 1, MAX_ROWS, 10));
|
|
434
|
+
return { kind: 'pages', pages: rows };
|
|
435
|
+
}
|
|
436
|
+
case 'excerptInclude': {
|
|
437
|
+
const target = named ? await targetPage(tx, workspaceId, named) : null;
|
|
438
|
+
if (!target)
|
|
439
|
+
return null;
|
|
440
|
+
if (!(await visible(workspaceId, audience, target)))
|
|
441
|
+
return null;
|
|
442
|
+
/*
|
|
443
|
+
* The marked excerpt where the source page has one, its opening prose where it does not —
|
|
444
|
+
* plain text either way. Lifting markup out of one document into another is how a half-open
|
|
445
|
+
* tag becomes the reader's problem on a page they did not write.
|
|
446
|
+
*/
|
|
447
|
+
const marked = markedExcerpt(await docOfPage(tx, workspaceId, target.id));
|
|
448
|
+
const text = marked ?? excerptOf(target.text);
|
|
449
|
+
if (!text)
|
|
450
|
+
return null;
|
|
451
|
+
const ref = refOf(target, ctx, { excerpt: false, updated: false });
|
|
452
|
+
return { kind: 'page', page: { ...ref, excerpt: text }, html: '' };
|
|
453
|
+
}
|
|
454
|
+
case 'includePage': {
|
|
455
|
+
const target = named ? await targetPage(tx, workspaceId, named) : null;
|
|
456
|
+
if (!target)
|
|
457
|
+
return null;
|
|
458
|
+
if (!(await visible(workspaceId, audience, target)))
|
|
459
|
+
return null;
|
|
460
|
+
const doc = await docOfPage(tx, workspaceId, target.id);
|
|
461
|
+
if (!doc)
|
|
462
|
+
return null;
|
|
463
|
+
/*
|
|
464
|
+
* **The included page is rendered with no resolver of its own**, so its macros draw empty
|
|
465
|
+
* frames. Two reasons, and the first is sufficient: a page that includes a page that
|
|
466
|
+
* includes the first is a cycle, and a cycle here is a request that never returns. The
|
|
467
|
+
* second is that a macro one level down would be resolved against an audience that reached
|
|
468
|
+
* it through two pages' permissions, which is a question nobody asked and this file would
|
|
469
|
+
* have to invent an answer to. One level of transclusion, stated rather than discovered.
|
|
470
|
+
*
|
|
471
|
+
* The HTML this produces is the only string the renderer emits unescaped, and this is the
|
|
472
|
+
* only place that produces it — by calling the same `renderPageDoc` that escapes everything
|
|
473
|
+
* else. See the note on `MacroContent`.
|
|
474
|
+
*/
|
|
475
|
+
const html = renderPageDoc(doc, { pageHref: ctx.pageHref });
|
|
476
|
+
return { kind: 'page', page: refOf(target, ctx, { excerpt: false, updated: false }), html };
|
|
477
|
+
}
|
|
478
|
+
case 'contributors': {
|
|
479
|
+
if (!home)
|
|
480
|
+
return null;
|
|
481
|
+
return contributors(tx, workspaceId, audience, home, countAttr(attrs.limit, 1, MAX_ROWS, 10));
|
|
482
|
+
}
|
|
483
|
+
default:
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
//# sourceMappingURL=macros.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"macros.js","sourceRoot":"","sources":["../../../src/server/services/macros.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ import { type Kernel, type Tx } from '@kernhq/kernel';
|
|
|
3
3
|
import type { Publication } from '../../contract/index.js';
|
|
4
4
|
import { publications } from '../schema.js';
|
|
5
5
|
import type { QuireAccess } from './access.js';
|
|
6
|
+
import type { QuireMacros } from './macros.js';
|
|
6
7
|
/**
|
|
7
8
|
* A public URL names its workspace by slug or by id; every query needs the id.
|
|
8
9
|
*
|
|
@@ -74,7 +75,7 @@ export declare function publicHtml(html: string, basePath: string, resolve: {
|
|
|
74
75
|
}): string;
|
|
75
76
|
/** A plain-text window around the first match, for a search result. */
|
|
76
77
|
export declare function snippetAround(text: string, query: string): string;
|
|
77
|
-
export declare function quirePublications(kernel: Kernel, access: QuireAccess, versions: QuireVersions): {
|
|
78
|
+
export declare function quirePublications(kernel: Kernel, access: QuireAccess, versions: QuireVersions, macroServices: QuireMacros): {
|
|
78
79
|
/**
|
|
79
80
|
* The anonymous path's transaction: the workspace set, and nothing writable.
|
|
80
81
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publications.d.ts","sourceRoot":"","sources":["../../../src/server/services/publications.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAa,KAAK,MAAM,EAAE,KAAK,EAAE,EAAE,MAAM,gBAAgB,CAAA;AAEhE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;
|
|
1
|
+
{"version":3,"file":"publications.d.ts","sourceRoot":"","sources":["../../../src/server/services/publications.ts"],"names":[],"mappings":"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"}
|
|
@@ -32,7 +32,8 @@ import { promisify } from 'node:util';
|
|
|
32
32
|
import { KernError } from '@kernhq/kernel';
|
|
33
33
|
import { and, desc, eq, inArray, sql } from 'drizzle-orm';
|
|
34
34
|
import { PUBLIC_ASSET_SEGMENT } from '../../contract/index.js';
|
|
35
|
-
import {
|
|
35
|
+
import { pageDocFromState } from '../document.js';
|
|
36
|
+
import { escapeHtml, hasReadingMacro } from '../render.js';
|
|
36
37
|
import { pages, pageVersions, publications } from '../schema.js';
|
|
37
38
|
/** A workspace segment is either its uuid or its slug; anything else is not an address. */
|
|
38
39
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -303,7 +304,12 @@ export function snippetAround(text, query) {
|
|
|
303
304
|
}
|
|
304
305
|
/** `%`, `_` and `\` mean something to `ILIKE`, so a reader searching for `50%` gets `50%`. */
|
|
305
306
|
const escapeLike = (value) => value.replace(/[\\%_]/g, (c) => `\\${c}`);
|
|
306
|
-
export function quirePublications(kernel, access, versions
|
|
307
|
+
export function quirePublications(kernel, access, versions,
|
|
308
|
+
/*
|
|
309
|
+
* Only ever asked to resolve for a `publication` audience — there is no principal on this path and
|
|
310
|
+
* nothing here can construct one. See the note in `html`.
|
|
311
|
+
*/
|
|
312
|
+
macroServices) {
|
|
307
313
|
/**
|
|
308
314
|
* The associated data an unlock token is sealed with.
|
|
309
315
|
*
|
|
@@ -619,7 +625,7 @@ export function quirePublications(kernel, access, versions) {
|
|
|
619
625
|
/** The pinned version's stored HTML, scrubbed for the internet. */
|
|
620
626
|
async html(tx, workspaceId, node, nodes, basePath) {
|
|
621
627
|
const [row] = await tx
|
|
622
|
-
.select({ html: pageVersions.html })
|
|
628
|
+
.select({ html: pageVersions.html, state: pageVersions.state })
|
|
623
629
|
.from(pageVersions)
|
|
624
630
|
.where(and(eq(pageVersions.workspaceId, workspaceId), eq(pageVersions.id, node.version_id)))
|
|
625
631
|
.limit(1);
|
|
@@ -634,7 +640,40 @@ export function quirePublications(kernel, access, versions) {
|
|
|
634
640
|
if (!row || row.html === null)
|
|
635
641
|
throw new KernError('NOT_FOUND', 'There is no published page at this address');
|
|
636
642
|
const pathById = new Map(nodes.map((n) => [n.id, n.path]));
|
|
637
|
-
|
|
643
|
+
/*
|
|
644
|
+
* A macro that reads other pages is resolved **here, per read** — never at publish time.
|
|
645
|
+
*
|
|
646
|
+
* The stored HTML draws those five as empty frames on purpose. Resolving them into
|
|
647
|
+
* `page_versions.html` would freeze a list of titles into a row that is then served for ever:
|
|
648
|
+
* unpublish a page, exclude it from the site, move it out of the publication, and the stored
|
|
649
|
+
* copy of its parent keeps naming it. `publicHtml` below would strip the dead *link* and leave
|
|
650
|
+
* the *title*, which is the leak stated at the top of `macros.ts` — a private page's title on
|
|
651
|
+
* a public site.
|
|
652
|
+
*
|
|
653
|
+
* So the version is re-rendered against this publication's own reachable set, which is exactly
|
|
654
|
+
* `nodes`: the walk has already applied every rule about what public means, and this file does
|
|
655
|
+
* not get a second opinion about it. Only for a page that has such a macro — `hasReadingMacro`
|
|
656
|
+
* is a string search over HTML we already have, so an ordinary page still costs one row read.
|
|
657
|
+
*/
|
|
658
|
+
let html = row.html;
|
|
659
|
+
if (hasReadingMacro(html)) {
|
|
660
|
+
const macros = await macroServices.resolve(tx, workspaceId, pageDocFromState(row.state), { kind: 'publication', pageIds: new Set(nodes.map((n) => n.id)) }, {
|
|
661
|
+
pageId: node.id,
|
|
662
|
+
/*
|
|
663
|
+
* An in-app address, deliberately, even though this render is for the internet.
|
|
664
|
+
*
|
|
665
|
+
* `publicHtml` below turns every `/quire/<space>/<id>` into this site's own path, or
|
|
666
|
+
* drops the href when the id is not public — one pass, over every internal link on the
|
|
667
|
+
* page. Handing a macro the *public* path here instead would produce the one href that
|
|
668
|
+
* pass does not recognise, so it would go out without `basePath` in front of it. The
|
|
669
|
+
* space segment is not read by that pattern and there is nothing here to fill it with,
|
|
670
|
+
* so it is a placeholder; the id is what carries the meaning.
|
|
671
|
+
*/
|
|
672
|
+
pageHref: (id) => (pathById.has(id) ? `/quire/-/${id}` : null),
|
|
673
|
+
});
|
|
674
|
+
html = await versions.html(tx, workspaceId, row.state, { pictures: 'referenced', macros });
|
|
675
|
+
}
|
|
676
|
+
return publicHtml(html, basePath, {
|
|
638
677
|
pagePath: (id) => pathById.get(id) ?? null,
|
|
639
678
|
// Minted per read rather than stored, so the envelope can be re-keyed and so nothing
|
|
640
679
|
// durable in the database is a capability. `basePath` already ends in a slash.
|