@glw907/cairn-cms 0.38.0 → 0.40.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/CHANGELOG.md +60 -0
- package/README.md +6 -5
- package/dist/components/AdminLayout.svelte +53 -0
- package/dist/components/ComponentInsertDialog.svelte +27 -13
- package/dist/components/ComponentInsertDialog.svelte.d.ts +13 -2
- package/dist/components/ConceptList.svelte +13 -3
- package/dist/components/DeleteDialog.svelte +18 -7
- package/dist/components/DeleteDialog.svelte.d.ts +11 -1
- package/dist/components/EditPage.svelte +575 -70
- package/dist/components/EditPage.svelte.d.ts +8 -1
- package/dist/components/EditorToolbar.svelte +202 -29
- package/dist/components/EditorToolbar.svelte.d.ts +12 -4
- package/dist/components/LinkPicker.svelte +14 -6
- package/dist/components/LinkPicker.svelte.d.ts +9 -2
- package/dist/components/MarkdownEditor.svelte +80 -34
- package/dist/components/MarkdownEditor.svelte.d.ts +9 -3
- package/dist/components/MarkdownHelpDialog.svelte +58 -0
- package/dist/components/MarkdownHelpDialog.svelte.d.ts +11 -0
- package/dist/components/RenameDialog.svelte +13 -4
- package/dist/components/RenameDialog.svelte.d.ts +9 -1
- package/dist/components/WebLinkDialog.svelte +89 -0
- package/dist/components/WebLinkDialog.svelte.d.ts +23 -0
- package/dist/components/cairn-admin.css +353 -4
- package/dist/components/editor-highlight.d.ts +9 -0
- package/dist/components/editor-highlight.js +62 -0
- package/dist/components/markdown-directives.d.ts +7 -0
- package/dist/components/markdown-directives.js +22 -0
- package/dist/components/markdown-format.d.ts +1 -1
- package/dist/components/markdown-format.js +91 -12
- package/dist/content/pending.d.ts +9 -0
- package/dist/content/pending.js +24 -0
- package/dist/github/branches.d.ts +11 -0
- package/dist/github/branches.js +75 -0
- package/dist/log/events.d.ts +1 -1
- package/dist/sveltekit/content-routes.d.ts +22 -1
- package/dist/sveltekit/content-routes.js +312 -72
- package/package.json +3 -2
- package/src/lib/components/AdminLayout.svelte +53 -0
- package/src/lib/components/ComponentInsertDialog.svelte +27 -13
- package/src/lib/components/ConceptList.svelte +13 -3
- package/src/lib/components/DeleteDialog.svelte +18 -7
- package/src/lib/components/EditPage.svelte +575 -70
- package/src/lib/components/EditorToolbar.svelte +202 -29
- package/src/lib/components/LinkPicker.svelte +14 -6
- package/src/lib/components/MarkdownEditor.svelte +80 -34
- package/src/lib/components/MarkdownHelpDialog.svelte +58 -0
- package/src/lib/components/RenameDialog.svelte +13 -4
- package/src/lib/components/WebLinkDialog.svelte +89 -0
- package/src/lib/components/cairn-admin.css +26 -4
- package/src/lib/components/editor-highlight.ts +67 -0
- package/src/lib/components/markdown-directives.ts +23 -0
- package/src/lib/components/markdown-format.ts +118 -13
- package/src/lib/content/pending.ts +24 -0
- package/src/lib/github/branches.ts +83 -0
- package/src/lib/log/events.ts +3 -0
- package/src/lib/sveltekit/content-routes.ts +391 -73
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Remark-directive detection for the editor's machinery highlighting (spec: directive syntax is
|
|
2
|
+
// styled distinctly so an editor can tell component scaffolding from prose). Pure functions; the
|
|
3
|
+
// CodeMirror decoration plugin wraps them.
|
|
4
|
+
const FENCE = /^\s{0,3}:::+\s*[\w-]*\s*(\{[^}]*\})?\s*$/;
|
|
5
|
+
const LEAF = /^\s{0,3}::[\w-]+(\[[^\]]*\])?(\{[^}]*\})?\s*$/;
|
|
6
|
+
const INLINE = /(?<![:\w]):[\w-]+\[[^\]]*\](\{[^}]*\})?/g;
|
|
7
|
+
/** Classify a whole line as a container fence, a leaf directive, or neither. */
|
|
8
|
+
export function directiveLineKind(line) {
|
|
9
|
+
if (FENCE.test(line))
|
|
10
|
+
return 'fence';
|
|
11
|
+
if (LEAF.test(line))
|
|
12
|
+
return 'leaf';
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
/** Inline directive ranges (`:name[...]{...}`) within a line of text. */
|
|
16
|
+
export function findInlineDirectives(text) {
|
|
17
|
+
const out = [];
|
|
18
|
+
for (const m of text.matchAll(INLINE)) {
|
|
19
|
+
out.push({ from: m.index, to: m.index + m[0].length });
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type FormatKind = 'bold' | 'italic' | 'code' | '
|
|
1
|
+
export type FormatKind = 'bold' | 'italic' | 'code' | 'strike' | 'h2' | 'h3' | 'quote' | 'ul' | 'ol' | 'task' | 'codeblock' | 'hr' | 'table' | 'link';
|
|
2
2
|
export interface FormatResult {
|
|
3
3
|
doc: string;
|
|
4
4
|
from: number;
|
|
@@ -8,13 +8,81 @@ import remarkParse from 'remark-parse';
|
|
|
8
8
|
import remarkGfm from 'remark-gfm';
|
|
9
9
|
import { visit } from 'unist-util-visit';
|
|
10
10
|
import { escapeLinkText } from '../content/links.js';
|
|
11
|
-
const WRAP = { bold: '**', italic: '_', code: '`' };
|
|
12
|
-
|
|
11
|
+
const WRAP = { bold: '**', italic: '_', code: '`', strike: '~~' };
|
|
12
|
+
/**
|
|
13
|
+
* Per-kind line-prefix behavior. `prefix` builds the marker for the line's 0-based index (only ol
|
|
14
|
+
* varies by line). `exact` matches a line already carrying this kind's own marker; when every
|
|
15
|
+
* selected line matches, the format toggles off. `strip` matches a competing marker to replace
|
|
16
|
+
* before prefixing, so h2 on an h3 line swaps the level instead of stacking. Quote and ul keep
|
|
17
|
+
* their original add-only behavior, so they carry neither regex.
|
|
18
|
+
*/
|
|
19
|
+
const LINE = {
|
|
20
|
+
h2: { prefix: () => '## ', exact: /^## /, strip: /^#{1,6} / },
|
|
21
|
+
h3: { prefix: () => '### ', exact: /^### /, strip: /^#{1,6} / },
|
|
22
|
+
quote: { prefix: () => '> ' },
|
|
23
|
+
ul: { prefix: () => '- ' },
|
|
24
|
+
ol: { prefix: (i) => `${i + 1}. `, exact: /^\d+\. /, strip: /^\d+\. / },
|
|
25
|
+
task: { prefix: () => '- [ ] ', exact: /^- \[[ xX]\] /, strip: /^- \[[ xX]\] / },
|
|
26
|
+
};
|
|
27
|
+
const TABLE_GRID = '| Column 1 | Column 2 |\n| -------- | -------- |\n| | |\n| | |';
|
|
28
|
+
/** Wrap the selection in `marker`, or unwrap when the markers are already there (inside or just
|
|
29
|
+
* outside the selection). The returned range covers the text without its markers either way. */
|
|
30
|
+
function toggleWrap(doc, from, to, marker) {
|
|
31
|
+
const m = marker.length;
|
|
32
|
+
const sel = doc.slice(from, to);
|
|
33
|
+
if (sel.length >= 2 * m && sel.startsWith(marker) && sel.endsWith(marker)) {
|
|
34
|
+
const inner = sel.slice(m, sel.length - m);
|
|
35
|
+
return { doc: doc.slice(0, from) + inner + doc.slice(to), from, to: to - 2 * m };
|
|
36
|
+
}
|
|
37
|
+
if (from >= m && doc.slice(from - m, from) === marker && doc.slice(to, to + m) === marker) {
|
|
38
|
+
return { doc: doc.slice(0, from - m) + sel + doc.slice(to + m), from: from - m, to: to - m };
|
|
39
|
+
}
|
|
40
|
+
const next = doc.slice(0, from) + marker + sel + marker + doc.slice(to);
|
|
41
|
+
return { doc: next, from: from + m, to: to + m };
|
|
42
|
+
}
|
|
43
|
+
/** Apply a line-prefix kind to every selected line. When the kind toggles and every line already
|
|
44
|
+
* carries its marker, the markers come off; otherwise competing markers are replaced and each
|
|
45
|
+
* line gains the kind's prefix. The selection shifts with the first line's edit and stretches
|
|
46
|
+
* by the total length change, the same mechanics the original single-prefix version had. */
|
|
47
|
+
function applyLinePrefix(doc, from, to, kind) {
|
|
48
|
+
const { prefix, exact, strip } = LINE[kind];
|
|
49
|
+
const lineStart = doc.lastIndexOf('\n', from - 1) + 1; // 0 when the selection is on the first line
|
|
50
|
+
const lines = doc.slice(lineStart, to).split('\n');
|
|
51
|
+
const next = exact && lines.every((line) => exact.test(line))
|
|
52
|
+
? lines.map((line) => line.replace(exact, ''))
|
|
53
|
+
: lines.map((line, i) => prefix(i) + (strip ? line.replace(strip, '') : line));
|
|
54
|
+
const region = next.join('\n');
|
|
55
|
+
const firstDelta = next[0].length - lines[0].length;
|
|
56
|
+
const totalDelta = region.length - (to - lineStart);
|
|
57
|
+
return {
|
|
58
|
+
doc: doc.slice(0, lineStart) + region + doc.slice(to),
|
|
59
|
+
from: Math.max(lineStart, from + firstDelta),
|
|
60
|
+
to: to + totalDelta,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** Fence the selected lines in triple backticks on their own lines, or remove the fences when the
|
|
64
|
+
* lines just above and below the selection already are fences. */
|
|
65
|
+
function toggleCodeFence(doc, from, to) {
|
|
66
|
+
const lineStart = doc.lastIndexOf('\n', from - 1) + 1;
|
|
67
|
+
const lineEndRaw = doc.indexOf('\n', to);
|
|
68
|
+
const lineEnd = lineEndRaw === -1 ? doc.length : lineEndRaw;
|
|
69
|
+
const prevStart = lineStart > 0 ? doc.lastIndexOf('\n', lineStart - 2) + 1 : -1;
|
|
70
|
+
const prevLine = prevStart >= 0 ? doc.slice(prevStart, lineStart - 1) : null;
|
|
71
|
+
const nextEndRaw = lineEnd < doc.length ? doc.indexOf('\n', lineEnd + 1) : -1;
|
|
72
|
+
const nextEnd = nextEndRaw === -1 ? doc.length : nextEndRaw;
|
|
73
|
+
const nextLine = lineEnd < doc.length ? doc.slice(lineEnd + 1, nextEnd) : null;
|
|
74
|
+
if (prevLine === '```' && nextLine === '```') {
|
|
75
|
+
const removedBefore = lineStart - prevStart; // the opening fence line and its newline
|
|
76
|
+
const next = doc.slice(0, prevStart) + doc.slice(lineStart, lineEnd) + doc.slice(nextEnd);
|
|
77
|
+
return { doc: next, from: from - removedBefore, to: to - removedBefore };
|
|
78
|
+
}
|
|
79
|
+
const open = '```\n';
|
|
80
|
+
const next = doc.slice(0, lineStart) + open + doc.slice(lineStart, lineEnd) + '\n```' + doc.slice(lineEnd);
|
|
81
|
+
return { doc: next, from: from + open.length, to: to + open.length };
|
|
82
|
+
}
|
|
13
83
|
export function applyMarkdownFormat(doc, from, to, kind) {
|
|
14
|
-
if (kind === 'bold' || kind === 'italic' || kind === 'code') {
|
|
15
|
-
|
|
16
|
-
const next = doc.slice(0, from) + marker + doc.slice(from, to) + marker + doc.slice(to);
|
|
17
|
-
return { doc: next, from: from + marker.length, to: to + marker.length };
|
|
84
|
+
if (kind === 'bold' || kind === 'italic' || kind === 'code' || kind === 'strike') {
|
|
85
|
+
return toggleWrap(doc, from, to, WRAP[kind]);
|
|
18
86
|
}
|
|
19
87
|
if (kind === 'link') {
|
|
20
88
|
const text = doc.slice(from, to);
|
|
@@ -24,12 +92,23 @@ export function applyMarkdownFormat(doc, from, to, kind) {
|
|
|
24
92
|
const urlStart = from + lead.length;
|
|
25
93
|
return { doc: doc.slice(0, from) + inserted + doc.slice(to), from: urlStart, to: urlStart + placeholder.length };
|
|
26
94
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
95
|
+
if (kind === 'codeblock')
|
|
96
|
+
return toggleCodeFence(doc, from, to);
|
|
97
|
+
if (kind === 'hr') {
|
|
98
|
+
const inserted = '\n\n---\n\n';
|
|
99
|
+
const at = from + inserted.length;
|
|
100
|
+
return { doc: doc.slice(0, from) + inserted + doc.slice(to), from: at, to: at };
|
|
101
|
+
}
|
|
102
|
+
if (kind === 'table') {
|
|
103
|
+
const inserted = `\n\n${TABLE_GRID}\n\n`;
|
|
104
|
+
const cellStart = from + inserted.indexOf('Column 1');
|
|
105
|
+
return {
|
|
106
|
+
doc: doc.slice(0, from) + inserted + doc.slice(to),
|
|
107
|
+
from: cellStart,
|
|
108
|
+
to: cellStart + 'Column 1'.length,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return applyLinePrefix(doc, from, to, kind);
|
|
33
112
|
}
|
|
34
113
|
/**
|
|
35
114
|
* Insert an inline markdown link at the selection. With a non-empty selection the selected text
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Every pending branch sits under this prefix; one matching-refs call lists them all. */
|
|
2
|
+
export declare const PENDING_PREFIX = "cairn/";
|
|
3
|
+
/** The branch name holding an entry's pending edits. */
|
|
4
|
+
export declare function pendingBranch(concept: string, id: string): string;
|
|
5
|
+
/** Parse a branch name or fully qualified ref back to its entry, or null for any other ref. */
|
|
6
|
+
export declare function parsePendingBranch(ref: string): {
|
|
7
|
+
concept: string;
|
|
8
|
+
id: string;
|
|
9
|
+
} | null;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// The pending-branch codec (publish-workflow spec): a pending entry lives on
|
|
2
|
+
// `cairn/<conceptKey>/<id>`, and the ref's existence is the only pending state. Concept ids and
|
|
3
|
+
// entry ids are slug-safe, so the name needs no escaping; the parser is the codec's inverse.
|
|
4
|
+
/** Every pending branch sits under this prefix; one matching-refs call lists them all. */
|
|
5
|
+
export const PENDING_PREFIX = 'cairn/';
|
|
6
|
+
/** The branch name holding an entry's pending edits. */
|
|
7
|
+
export function pendingBranch(concept, id) {
|
|
8
|
+
return `${PENDING_PREFIX}${concept}/${id}`;
|
|
9
|
+
}
|
|
10
|
+
/** Parse a branch name or fully qualified ref back to its entry, or null for any other ref. */
|
|
11
|
+
export function parsePendingBranch(ref) {
|
|
12
|
+
const name = ref.startsWith('refs/heads/') ? ref.slice('refs/heads/'.length) : ref;
|
|
13
|
+
if (!name.startsWith(PENDING_PREFIX))
|
|
14
|
+
return null;
|
|
15
|
+
const rest = name.slice(PENDING_PREFIX.length);
|
|
16
|
+
const slash = rest.indexOf('/');
|
|
17
|
+
if (slash <= 0)
|
|
18
|
+
return null;
|
|
19
|
+
const concept = rest.slice(0, slash);
|
|
20
|
+
const id = rest.slice(slash + 1);
|
|
21
|
+
if (!id || id.includes('/'))
|
|
22
|
+
return null;
|
|
23
|
+
return { concept, id };
|
|
24
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RepoRef } from './types.js';
|
|
2
|
+
/** The head commit sha of a branch, or null when the branch does not exist. */
|
|
3
|
+
export declare function branchHeadSha(repo: RepoRef, branch: string, token: string): Promise<string | null>;
|
|
4
|
+
/** Create `branch` pointing at `fromSha`. Throws on any failure including an existing ref. */
|
|
5
|
+
export declare function createBranch(repo: RepoRef, branch: string, fromSha: string, token: string): Promise<void>;
|
|
6
|
+
/** Delete `branch`. A 404 (already gone) is success: the desired state holds. */
|
|
7
|
+
export declare function deleteBranch(repo: RepoRef, branch: string, token: string): Promise<void>;
|
|
8
|
+
/** Branch names under `prefix`, sorted. The matching-refs API paginates at 30 by default, so a
|
|
9
|
+
* site with 31+ pending entries would silently truncate; request the 100-per-page maximum and
|
|
10
|
+
* follow the Link rel="next" chain until exhausted. */
|
|
11
|
+
export declare function listBranches(repo: RepoRef, prefix: string, token: string): Promise<string[]>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const API = 'https://api.github.com';
|
|
2
|
+
function headers(token) {
|
|
3
|
+
return {
|
|
4
|
+
Accept: 'application/vnd.github+json',
|
|
5
|
+
'User-Agent': 'cairn-cms',
|
|
6
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
7
|
+
Authorization: `Bearer ${token}`,
|
|
8
|
+
'Content-Type': 'application/json',
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function gitUrl(repo, suffix) {
|
|
12
|
+
return `${API}/repos/${repo.owner}/${repo.repo}/git/${suffix}`;
|
|
13
|
+
}
|
|
14
|
+
/** The head commit sha of a branch, or null when the branch does not exist. */
|
|
15
|
+
export async function branchHeadSha(repo, branch, token) {
|
|
16
|
+
const res = await fetch(gitUrl(repo, `ref/heads/${encodeURIComponent(branch)}`), { headers: headers(token) });
|
|
17
|
+
// The 404 probe is a hot path (every editLoad); drain the body so the connection frees
|
|
18
|
+
// immediately instead of pinning one of workerd's six until GC.
|
|
19
|
+
if (res.status === 404) {
|
|
20
|
+
await res.body?.cancel();
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
if (!res.ok)
|
|
24
|
+
throw new Error(`GitHub ref ${branch} failed: ${res.status} ${await res.text()}`);
|
|
25
|
+
return (await res.json()).object.sha;
|
|
26
|
+
}
|
|
27
|
+
/** Create `branch` pointing at `fromSha`. Throws on any failure including an existing ref. */
|
|
28
|
+
export async function createBranch(repo, branch, fromSha, token) {
|
|
29
|
+
const res = await fetch(gitUrl(repo, 'refs'), {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
headers: headers(token),
|
|
32
|
+
body: JSON.stringify({ ref: `refs/heads/${branch}`, sha: fromSha }),
|
|
33
|
+
});
|
|
34
|
+
if (!res.ok)
|
|
35
|
+
throw new Error(`GitHub branch create ${branch} failed: ${res.status} ${await res.text()}`);
|
|
36
|
+
await res.body?.cancel();
|
|
37
|
+
}
|
|
38
|
+
/** Delete `branch`. A 404 (already gone) is success: the desired state holds. */
|
|
39
|
+
export async function deleteBranch(repo, branch, token) {
|
|
40
|
+
const res = await fetch(gitUrl(repo, `refs/heads/${encodeURIComponent(branch)}`), {
|
|
41
|
+
method: 'DELETE',
|
|
42
|
+
headers: headers(token),
|
|
43
|
+
});
|
|
44
|
+
if (!res.ok && res.status !== 404) {
|
|
45
|
+
throw new Error(`GitHub branch delete ${branch} failed: ${res.status} ${await res.text()}`);
|
|
46
|
+
}
|
|
47
|
+
await res.body?.cancel();
|
|
48
|
+
}
|
|
49
|
+
/** The rel="next" URL from a GitHub Link header, or null on the last page. */
|
|
50
|
+
function nextPageUrl(link) {
|
|
51
|
+
if (!link)
|
|
52
|
+
return null;
|
|
53
|
+
for (const part of link.split(',')) {
|
|
54
|
+
const match = part.match(/<([^>]+)>\s*;\s*rel="next"/);
|
|
55
|
+
if (match)
|
|
56
|
+
return match[1];
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
/** Branch names under `prefix`, sorted. The matching-refs API paginates at 30 by default, so a
|
|
61
|
+
* site with 31+ pending entries would silently truncate; request the 100-per-page maximum and
|
|
62
|
+
* follow the Link rel="next" chain until exhausted. */
|
|
63
|
+
export async function listBranches(repo, prefix, token) {
|
|
64
|
+
const names = [];
|
|
65
|
+
let url = `${gitUrl(repo, `matching-refs/heads/${prefix}`)}?per_page=100`;
|
|
66
|
+
while (url) {
|
|
67
|
+
const res = await fetch(url, { headers: headers(token) });
|
|
68
|
+
if (!res.ok)
|
|
69
|
+
throw new Error(`GitHub matching-refs ${prefix} failed: ${res.status} ${await res.text()}`);
|
|
70
|
+
const refs = (await res.json());
|
|
71
|
+
names.push(...refs.map((r) => r.ref.replace(/^refs\/heads\//, '')));
|
|
72
|
+
url = nextPageUrl(res.headers.get('Link'));
|
|
73
|
+
}
|
|
74
|
+
return names;
|
|
75
|
+
}
|
package/dist/log/events.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export type CairnLogEvent = 'auth.link.requested' | 'auth.link.send_failed' | 'auth.token.minted' | 'auth.token.confirmed' | 'auth.session.created' | 'auth.session.destroyed' | 'commit.succeeded' | 'commit.failed' | 'guard.rejected';
|
|
1
|
+
export type CairnLogEvent = 'auth.link.requested' | 'auth.link.send_failed' | 'auth.token.minted' | 'auth.token.confirmed' | 'auth.session.created' | 'auth.session.destroyed' | 'commit.succeeded' | 'commit.failed' | 'entry.published' | 'entry.discarded' | 'publish.failed' | 'guard.rejected';
|
|
@@ -29,6 +29,12 @@ export interface LayoutData {
|
|
|
29
29
|
collapsedNav: string[];
|
|
30
30
|
/** The session's CSRF double-submit token, rendered as a hidden field in every admin form. */
|
|
31
31
|
csrf: string;
|
|
32
|
+
/** Every entry with unpublished edits (a `cairn/` ref), for the topbar's publish-all action.
|
|
33
|
+
* Null when GitHub is unreachable, so the topbar hides the action rather than lying. */
|
|
34
|
+
pendingEntries: {
|
|
35
|
+
concept: string;
|
|
36
|
+
id: string;
|
|
37
|
+
}[] | null;
|
|
32
38
|
}
|
|
33
39
|
/** One row in a concept's list view. */
|
|
34
40
|
export interface EntrySummary {
|
|
@@ -36,6 +42,8 @@ export interface EntrySummary {
|
|
|
36
42
|
title: string;
|
|
37
43
|
date: string | null;
|
|
38
44
|
draft: boolean;
|
|
45
|
+
/** Publish state derived from the ref set: live as-is, live with pending edits, or branch-only. */
|
|
46
|
+
status: 'published' | 'edited' | 'new';
|
|
39
47
|
}
|
|
40
48
|
/** The concept list view's data. */
|
|
41
49
|
export interface ListData {
|
|
@@ -48,6 +56,8 @@ export interface ListData {
|
|
|
48
56
|
error: string | null;
|
|
49
57
|
/** A create-form bounce error read from `?error`. */
|
|
50
58
|
formError: string | null;
|
|
59
|
+
/** The entry count from a publish-all redirect (`?publishedAll=`), for the list page's flash. */
|
|
60
|
+
publishedAll: number | null;
|
|
51
61
|
}
|
|
52
62
|
/** The editor's data. `frontmatter` holds form-ready values (dates already `YYYY-MM-DD`). */
|
|
53
63
|
export interface EditData {
|
|
@@ -69,6 +79,14 @@ export interface EditData {
|
|
|
69
79
|
linkTargets: LinkTarget[];
|
|
70
80
|
/** The entries that link to this one, for the delete guard. Empty when nothing links here. */
|
|
71
81
|
inboundLinks: InboundLink[];
|
|
82
|
+
/** True when the entry has a pending branch, so the body above came from that branch. */
|
|
83
|
+
pending: boolean;
|
|
84
|
+
/** True when the entry file exists on the default branch (the live site shows it). */
|
|
85
|
+
published: boolean;
|
|
86
|
+
/** True after a publish redirect (`?published=1`), for the confirmation strip. */
|
|
87
|
+
publishedFlash: boolean;
|
|
88
|
+
/** True after a discard redirect (`?discarded=1`), for the confirmation strip. */
|
|
89
|
+
discardedFlash: boolean;
|
|
72
90
|
}
|
|
73
91
|
/** The structural event the content routes read; a real SvelteKit RequestEvent satisfies it. */
|
|
74
92
|
export interface ContentEvent {
|
|
@@ -91,12 +109,15 @@ export interface ContentRoutesDeps {
|
|
|
91
109
|
mintToken?: (env: GithubKeyEnv) => Promise<string>;
|
|
92
110
|
}
|
|
93
111
|
export declare function createContentRoutes(runtime: CairnRuntime, deps?: ContentRoutesDeps): {
|
|
94
|
-
layoutLoad: (event: ContentEvent) => LayoutData
|
|
112
|
+
layoutLoad: (event: ContentEvent) => Promise<LayoutData>;
|
|
95
113
|
indexRedirect: () => never;
|
|
96
114
|
listLoad: (event: ContentEvent) => Promise<ListData>;
|
|
97
115
|
createAction: (event: ContentEvent) => Promise<never>;
|
|
98
116
|
editLoad: (event: ContentEvent) => Promise<EditData>;
|
|
99
117
|
saveAction: (event: ContentEvent) => Promise<ReturnType<typeof fail> | never>;
|
|
118
|
+
publishAction: (event: ContentEvent) => Promise<ReturnType<typeof fail> | never>;
|
|
119
|
+
publishAllAction: (event: ContentEvent) => Promise<never>;
|
|
120
|
+
discardAction: (event: ContentEvent) => Promise<never>;
|
|
100
121
|
deleteAction: (event: ContentEvent) => Promise<ReturnType<typeof fail> | never>;
|
|
101
122
|
listDeleteAction: (event: ContentEvent) => Promise<ReturnType<typeof fail> | never>;
|
|
102
123
|
renameAction: (event: ContentEvent) => Promise<ReturnType<typeof fail> | never>;
|