@glw907/cairn-cms 0.38.0 → 0.41.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 +94 -0
- package/README.md +7 -6
- 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 +22 -3
- package/dist/components/DeleteDialog.svelte +18 -7
- package/dist/components/DeleteDialog.svelte.d.ts +11 -1
- package/dist/components/EditPage.svelte +604 -75
- package/dist/components/EditPage.svelte.d.ts +8 -1
- package/dist/components/EditorToolbar.svelte +206 -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/link-completion.js +10 -3
- 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/diagnostics/conditions.d.ts +8 -1
- package/dist/diagnostics/conditions.js +68 -1
- package/dist/doctor/bin.d.ts +2 -0
- package/dist/doctor/bin.js +44 -0
- package/dist/doctor/check-send.d.ts +3 -0
- package/dist/doctor/check-send.js +43 -0
- package/dist/doctor/checks-cloudflare.d.ts +5 -0
- package/dist/doctor/checks-cloudflare.js +200 -0
- package/dist/doctor/checks-github.d.ts +2 -0
- package/dist/doctor/checks-github.js +57 -0
- package/dist/doctor/checks-local.d.ts +5 -0
- package/dist/doctor/checks-local.js +112 -0
- package/dist/doctor/cloudflare-api.d.ts +7 -0
- package/dist/doctor/cloudflare-api.js +24 -0
- package/dist/doctor/index.d.ts +23 -0
- package/dist/doctor/index.js +68 -0
- package/dist/doctor/report.d.ts +5 -0
- package/dist/doctor/report.js +21 -0
- package/dist/doctor/run.d.ts +8 -0
- package/dist/doctor/run.js +20 -0
- package/dist/doctor/types.d.ts +41 -0
- package/dist/doctor/types.js +10 -0
- package/dist/doctor/wrangler-config.d.ts +12 -0
- package/dist/doctor/wrangler-config.js +125 -0
- package/dist/github/branches.d.ts +11 -0
- package/dist/github/branches.js +75 -0
- package/dist/github/signing.d.ts +3 -1
- package/dist/github/signing.js +13 -5
- package/dist/log/events.d.ts +1 -1
- package/dist/sveltekit/content-routes.d.ts +22 -1
- package/dist/sveltekit/content-routes.js +320 -72
- package/package.json +8 -5
- package/src/lib/components/AdminLayout.svelte +53 -0
- package/src/lib/components/ComponentInsertDialog.svelte +27 -13
- package/src/lib/components/ConceptList.svelte +22 -3
- package/src/lib/components/DeleteDialog.svelte +18 -7
- package/src/lib/components/EditPage.svelte +604 -75
- package/src/lib/components/EditorToolbar.svelte +206 -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/link-completion.ts +10 -3
- 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/diagnostics/conditions.ts +75 -2
- package/src/lib/doctor/bin.ts +45 -0
- package/src/lib/doctor/check-send.ts +43 -0
- package/src/lib/doctor/checks-cloudflare.ts +222 -0
- package/src/lib/doctor/checks-github.ts +63 -0
- package/src/lib/doctor/checks-local.ts +119 -0
- package/src/lib/doctor/cloudflare-api.ts +33 -0
- package/src/lib/doctor/index.ts +93 -0
- package/src/lib/doctor/report.ts +30 -0
- package/src/lib/doctor/run.ts +23 -0
- package/src/lib/doctor/types.ts +52 -0
- package/src/lib/doctor/wrangler-config.ts +142 -0
- package/src/lib/github/branches.ts +83 -0
- package/src/lib/github/signing.ts +13 -6
- package/src/lib/log/events.ts +4 -0
- package/src/lib/sveltekit/content-routes.ts +400 -73
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export async function readWranglerConfig(readFile) {
|
|
2
|
+
const jsonc = await readFile('wrangler.jsonc');
|
|
3
|
+
if (jsonc !== null)
|
|
4
|
+
return factsFromJsonc(jsonc);
|
|
5
|
+
const toml = await readFile('wrangler.toml');
|
|
6
|
+
if (toml !== null)
|
|
7
|
+
return factsFromToml(toml);
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
// Strip // and /* */ comments outside string literals, character by character, so a URL
|
|
11
|
+
// inside a string survives. Trailing commas go by regex afterward; a string containing
|
|
12
|
+
// ",}" would be mangled, an accepted gap in a tolerant reader.
|
|
13
|
+
function stripJsonc(text) {
|
|
14
|
+
let out = '';
|
|
15
|
+
let inString = false;
|
|
16
|
+
let i = 0;
|
|
17
|
+
while (i < text.length) {
|
|
18
|
+
const ch = text[i];
|
|
19
|
+
if (inString) {
|
|
20
|
+
out += ch;
|
|
21
|
+
if (ch === '\\') {
|
|
22
|
+
out += text[i + 1] ?? '';
|
|
23
|
+
i += 2;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (ch === '"')
|
|
27
|
+
inString = false;
|
|
28
|
+
i += 1;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (ch === '"') {
|
|
32
|
+
inString = true;
|
|
33
|
+
out += ch;
|
|
34
|
+
i += 1;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (ch === '/' && text[i + 1] === '/') {
|
|
38
|
+
const end = text.indexOf('\n', i);
|
|
39
|
+
i = end === -1 ? text.length : end;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (ch === '/' && text[i + 1] === '*') {
|
|
43
|
+
const end = text.indexOf('*/', i + 2);
|
|
44
|
+
i = end === -1 ? text.length : end + 2;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
out += ch;
|
|
48
|
+
i += 1;
|
|
49
|
+
}
|
|
50
|
+
return out.replace(/,(\s*[}\]])/g, '$1');
|
|
51
|
+
}
|
|
52
|
+
function factsFromJsonc(text) {
|
|
53
|
+
let config;
|
|
54
|
+
try {
|
|
55
|
+
config = JSON.parse(stripJsonc(text));
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// V8's SyntaxError embeds a source snippet, which would land verbatim in the report;
|
|
59
|
+
// a file that exists but does not parse is a fail with a clean message instead.
|
|
60
|
+
throw new Error('wrangler.jsonc did not parse');
|
|
61
|
+
}
|
|
62
|
+
const sendEmail = Array.isArray(config.send_email) ? config.send_email : [];
|
|
63
|
+
const hasEmailBinding = sendEmail.some((entry) => typeof entry === 'object' && entry !== null && entry.name === 'EMAIL');
|
|
64
|
+
const databases = Array.isArray(config.d1_databases) ? config.d1_databases : [];
|
|
65
|
+
const authDb = databases.find((entry) => typeof entry === 'object' && entry !== null && entry.binding === 'AUTH_DB');
|
|
66
|
+
const observability = config.observability;
|
|
67
|
+
const facts = {
|
|
68
|
+
hasEmailBinding,
|
|
69
|
+
hasAuthDb: authDb !== undefined,
|
|
70
|
+
observabilityEnabled: observability?.enabled === true,
|
|
71
|
+
};
|
|
72
|
+
if (typeof authDb?.database_id === 'string')
|
|
73
|
+
facts.authDbId = authDb.database_id;
|
|
74
|
+
return facts;
|
|
75
|
+
}
|
|
76
|
+
// The toml read is deliberately shallow: line-anchored matching for the three facts, not a
|
|
77
|
+
// TOML parser. The remediation tells the operator exactly what to add, so full fidelity
|
|
78
|
+
// buys nothing here. A table header opens a section; the relevant key lines are matched
|
|
79
|
+
// within it and the d1 table flushes on the next header.
|
|
80
|
+
function factsFromToml(text) {
|
|
81
|
+
const facts = {
|
|
82
|
+
hasEmailBinding: false,
|
|
83
|
+
hasAuthDb: false,
|
|
84
|
+
observabilityEnabled: false,
|
|
85
|
+
};
|
|
86
|
+
let section = '';
|
|
87
|
+
let d1Binding;
|
|
88
|
+
let d1Id;
|
|
89
|
+
const flushD1 = () => {
|
|
90
|
+
if (d1Binding === 'AUTH_DB') {
|
|
91
|
+
facts.hasAuthDb = true;
|
|
92
|
+
if (d1Id !== undefined)
|
|
93
|
+
facts.authDbId = d1Id;
|
|
94
|
+
}
|
|
95
|
+
d1Binding = undefined;
|
|
96
|
+
d1Id = undefined;
|
|
97
|
+
};
|
|
98
|
+
for (const line of text.split('\n')) {
|
|
99
|
+
const header = line.match(/^\s*(\[\[?[\w.]+\]?\])\s*(?:#.*)?$/);
|
|
100
|
+
if (header) {
|
|
101
|
+
flushD1();
|
|
102
|
+
section = header[1];
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const kv = line.match(/^\s*(\w+)\s*=\s*(.+?)\s*$/);
|
|
106
|
+
if (!kv)
|
|
107
|
+
continue;
|
|
108
|
+
const [, key, value] = kv;
|
|
109
|
+
const str = value.match(/^["'](.*)["']/)?.[1];
|
|
110
|
+
if (section === '[[send_email]]' && key === 'name' && str === 'EMAIL') {
|
|
111
|
+
facts.hasEmailBinding = true;
|
|
112
|
+
}
|
|
113
|
+
else if (section === '[[d1_databases]]') {
|
|
114
|
+
if (key === 'binding')
|
|
115
|
+
d1Binding = str;
|
|
116
|
+
if (key === 'database_id')
|
|
117
|
+
d1Id = str;
|
|
118
|
+
}
|
|
119
|
+
else if (section === '[observability]' && key === 'enabled' && value.startsWith('true')) {
|
|
120
|
+
facts.observabilityEnabled = true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
flushD1();
|
|
124
|
+
return facts;
|
|
125
|
+
}
|
|
@@ -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/github/signing.d.ts
CHANGED
|
@@ -9,7 +9,9 @@ export declare function installationToken(creds: AppCredentials): Promise<string
|
|
|
9
9
|
* instead of re-signing and re-calling GitHub on every list and commit. A cold isolate re-mints,
|
|
10
10
|
* which is always safe. This mirrors the default of @octokit/auth-app, which caches installation
|
|
11
11
|
* tokens in memory and returns them until expiry. The TTL stays under GitHub's documented one-hour
|
|
12
|
-
* lifetime, so a fixed margin avoids parsing the API expiry.
|
|
12
|
+
* lifetime, so a fixed margin avoids parsing the API expiry. The cache holds the in-flight
|
|
13
|
+
* promise, not the resolved token, so a cold isolate's parallel loads coalesce into one mint;
|
|
14
|
+
* a rejected mint evicts itself so the next call retries. `mint` and `now` are injected so the
|
|
13
15
|
* cache is testable with no network call and no real clock.
|
|
14
16
|
*/
|
|
15
17
|
export declare function createInstallationTokenCache(mint?: (creds: AppCredentials) => Promise<string>, now?: () => number, ttlMs?: number): (creds: AppCredentials) => Promise<string>;
|
package/dist/github/signing.js
CHANGED
|
@@ -65,18 +65,26 @@ export async function installationToken(creds) {
|
|
|
65
65
|
* instead of re-signing and re-calling GitHub on every list and commit. A cold isolate re-mints,
|
|
66
66
|
* which is always safe. This mirrors the default of @octokit/auth-app, which caches installation
|
|
67
67
|
* tokens in memory and returns them until expiry. The TTL stays under GitHub's documented one-hour
|
|
68
|
-
* lifetime, so a fixed margin avoids parsing the API expiry.
|
|
68
|
+
* lifetime, so a fixed margin avoids parsing the API expiry. The cache holds the in-flight
|
|
69
|
+
* promise, not the resolved token, so a cold isolate's parallel loads coalesce into one mint;
|
|
70
|
+
* a rejected mint evicts itself so the next call retries. `mint` and `now` are injected so the
|
|
69
71
|
* cache is testable with no network call and no real clock.
|
|
70
72
|
*/
|
|
71
73
|
export function createInstallationTokenCache(mint = installationToken, now = () => Date.now(), ttlMs = 55 * 60 * 1000) {
|
|
72
74
|
const cache = new Map();
|
|
73
|
-
return
|
|
75
|
+
return function get(creds) {
|
|
74
76
|
const hit = cache.get(creds.installationId);
|
|
75
77
|
if (hit && hit.expiresAt > now())
|
|
76
78
|
return hit.token;
|
|
77
|
-
const
|
|
78
|
-
cache.set(creds.installationId,
|
|
79
|
-
|
|
79
|
+
const entry = { token: mint(creds), expiresAt: now() + ttlMs };
|
|
80
|
+
cache.set(creds.installationId, entry);
|
|
81
|
+
// Evict only this entry on rejection: a newer entry that replaced it must survive. The
|
|
82
|
+
// caller's await surfaces the rejection itself, so this side handler swallows nothing.
|
|
83
|
+
entry.token.catch(() => {
|
|
84
|
+
if (cache.get(creds.installationId) === entry)
|
|
85
|
+
cache.delete(creds.installationId);
|
|
86
|
+
});
|
|
87
|
+
return entry.token;
|
|
80
88
|
};
|
|
81
89
|
}
|
|
82
90
|
/** The shared installation-token cache, one instance per Worker isolate. */
|
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' | 'github.unreachable' | '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>;
|