@glw907/cairn-cms 0.34.0 → 0.35.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 +19 -0
- package/dist/auth/crypto.d.ts +4 -0
- package/dist/auth/crypto.js +10 -0
- package/dist/components/AdminLayout.svelte +8 -1
- package/dist/components/ConceptList.svelte +3 -0
- package/dist/components/ConfirmPage.svelte +4 -2
- package/dist/components/ConfirmPage.svelte.d.ts +2 -1
- package/dist/components/CsrfField.svelte +20 -0
- package/dist/components/CsrfField.svelte.d.ts +12 -0
- package/dist/components/DeleteDialog.svelte +2 -0
- package/dist/components/EditPage.svelte +2 -0
- package/dist/components/LoginPage.svelte +4 -2
- package/dist/components/LoginPage.svelte.d.ts +2 -1
- package/dist/components/ManageEditors.svelte +4 -0
- package/dist/components/NavTree.svelte +2 -0
- package/dist/components/RenameDialog.svelte +3 -0
- package/dist/components/csrf-context.d.ts +2 -0
- package/dist/components/csrf-context.js +2 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/sveltekit/auth-routes.d.ts +2 -0
- package/dist/sveltekit/auth-routes.js +10 -3
- package/dist/sveltekit/content-routes.d.ts +6 -4
- package/dist/sveltekit/content-routes.js +2 -0
- package/dist/sveltekit/csrf-required-page.d.ts +2 -0
- package/dist/sveltekit/csrf-required-page.js +25 -0
- package/dist/sveltekit/csrf.d.ts +18 -0
- package/dist/sveltekit/csrf.js +60 -0
- package/dist/sveltekit/guard.js +30 -6
- package/dist/sveltekit/https-required-page.js +10 -191
- package/dist/sveltekit/static-admin-page.d.ts +11 -0
- package/dist/sveltekit/static-admin-page.js +195 -0
- package/package.json +1 -1
- package/src/lib/auth/crypto.ts +13 -0
- package/src/lib/components/AdminLayout.svelte +8 -1
- package/src/lib/components/ConceptList.svelte +3 -0
- package/src/lib/components/ConfirmPage.svelte +4 -2
- package/src/lib/components/CsrfField.svelte +20 -0
- package/src/lib/components/DeleteDialog.svelte +2 -0
- package/src/lib/components/EditPage.svelte +2 -0
- package/src/lib/components/LoginPage.svelte +4 -2
- package/src/lib/components/ManageEditors.svelte +4 -0
- package/src/lib/components/NavTree.svelte +2 -0
- package/src/lib/components/RenameDialog.svelte +3 -0
- package/src/lib/components/csrf-context.ts +2 -0
- package/src/lib/components/index.ts +1 -0
- package/src/lib/sveltekit/auth-routes.ts +12 -5
- package/src/lib/sveltekit/content-routes.ts +8 -2
- package/src/lib/sveltekit/csrf-required-page.ts +26 -0
- package/src/lib/sveltekit/csrf.ts +61 -0
- package/src/lib/sveltekit/guard.ts +37 -6
- package/src/lib/sveltekit/https-required-page.ts +10 -194
- package/src/lib/sveltekit/static-admin-page.ts +200 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// The shared shell for cairn's edge-served admin pages (HTTPS-required, CSRF-failed). Each is a
|
|
2
|
+
// self-contained document with inlined Warm Stone tokens for both colour schemes and the system
|
|
3
|
+
// font stack, served raw before SvelteKit renders. The cairn glyph is the same public-domain
|
|
4
|
+
// Temaki mark the admin chrome uses. See docs/internal/admin-design-system.md.
|
|
5
|
+
|
|
6
|
+
/** Escape a string for safe interpolation into HTML text and double-quoted attributes. */
|
|
7
|
+
export function escapeHtml(value: string): string {
|
|
8
|
+
return value
|
|
9
|
+
.replace(/&/g, '&')
|
|
10
|
+
.replace(/</g, '<')
|
|
11
|
+
.replace(/>/g, '>')
|
|
12
|
+
.replace(/"/g, '"');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// The cairn stone-stack glyph (Temaki, CC0), drawn in currentColor like CairnLogo.svelte.
|
|
16
|
+
const CAIRN_GLYPH =
|
|
17
|
+
'<path d="M6.28 14C5.56 14 1 13.89 1 12.91C1 11.46 2.16 11.07 3.2 10.81C4.36 10.51 13.18 9.77 ' +
|
|
18
|
+
'13.76 10.07C14.46 10.43 13.52 12.49 12.44 12.77C11.28 13.07 10.21 14 8.48 14C7.05 14 9.69 14 ' +
|
|
19
|
+
'6.28 14ZM6.92 4.5C6.67 4.5 5 4.43 5 3.88C5 3.07 5.75 2.51 5.96 2.35C6.36 2.03 6.32 1.62 6.54 ' +
|
|
20
|
+
'1.27C6.84 0.79 7.61 0.5 7.88 0.5C8.1 0.5 8.75 0.9 9.23 1.42C9.45 1.66 10 2.77 10 3.12C10 4.22 ' +
|
|
21
|
+
'9.36 4.5 8.85 4.5C8.33 4.5 8.15 4.5 6.92 4.5ZM3.68 8.22C3 7.73 3.67 6.86 4.57 6.21C5.38 5.63 ' +
|
|
22
|
+
'5.92 5.96 6.79 5.7C8.33 5.24 9.02 5.72 9.02 5.72L10.9 6.82C12.03 7.63 10.99 7.67 10.38 8.56C9.79 ' +
|
|
23
|
+
'9.42 8.18 9.11 7.42 9.33C6.78 9.53 5.75 9.71 4.62 8.9L3.68 8.22Z"/>';
|
|
24
|
+
|
|
25
|
+
// The verbatim rule set lifted from the original <style> block: the `:root` light tokens, the
|
|
26
|
+
// `@media (prefers-color-scheme: dark)` block, and every rule through `.foot`. It already covers
|
|
27
|
+
// every class both pages use (brand, eyebrow, cta, fix, path, foot).
|
|
28
|
+
const SHARED_STYLE = `:root {
|
|
29
|
+
color-scheme: light;
|
|
30
|
+
--bg: oklch(96.5% 0.006 75);
|
|
31
|
+
--glow: oklch(52% 0.2 293 / 0.06);
|
|
32
|
+
--panel: oklch(99% 0.004 75);
|
|
33
|
+
--recessed: oklch(95% 0.008 75);
|
|
34
|
+
--ink: oklch(26% 0.014 75);
|
|
35
|
+
--muted: oklch(48% 0.01 75);
|
|
36
|
+
--subtle: oklch(42% 0.01 75);
|
|
37
|
+
--primary: oklch(52% 0.2 293);
|
|
38
|
+
--primary-content: oklch(98% 0.012 293);
|
|
39
|
+
--border: oklch(93% 0.008 75);
|
|
40
|
+
--shadow: 0 1px 2px oklch(28% 0.02 75 / 0.05), 0 18px 40px -12px oklch(28% 0.02 75 / 0.16);
|
|
41
|
+
--radius-box: 1rem;
|
|
42
|
+
--radius-field: 0.625rem;
|
|
43
|
+
--font: 'Figtree Variable', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
44
|
+
}
|
|
45
|
+
@media (prefers-color-scheme: dark) {
|
|
46
|
+
:root {
|
|
47
|
+
color-scheme: dark;
|
|
48
|
+
--bg: oklch(15.5% 0.009 75);
|
|
49
|
+
--glow: oklch(68% 0.18 293 / 0.1);
|
|
50
|
+
--panel: oklch(24% 0.01 75);
|
|
51
|
+
--recessed: oklch(20% 0.01 75);
|
|
52
|
+
--ink: oklch(93% 0.006 75);
|
|
53
|
+
--muted: oklch(72% 0.01 75);
|
|
54
|
+
--subtle: oklch(80% 0.008 75);
|
|
55
|
+
--primary: oklch(68% 0.18 293);
|
|
56
|
+
--primary-content: oklch(20% 0.04 293);
|
|
57
|
+
--border: oklch(30% 0.014 75);
|
|
58
|
+
--shadow: 0 1px 2px oklch(0% 0 0 / 0.35), 0 18px 40px -12px oklch(0% 0 0 / 0.55);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
* { box-sizing: border-box; }
|
|
62
|
+
body {
|
|
63
|
+
margin: 0;
|
|
64
|
+
min-height: 100vh;
|
|
65
|
+
display: flex;
|
|
66
|
+
align-items: center;
|
|
67
|
+
justify-content: center;
|
|
68
|
+
padding: 1.5rem;
|
|
69
|
+
font-family: var(--font);
|
|
70
|
+
color: var(--ink);
|
|
71
|
+
background-color: var(--bg);
|
|
72
|
+
background-image: radial-gradient(80rem 50rem at 50% -20%, var(--glow), transparent 60%);
|
|
73
|
+
-webkit-font-smoothing: antialiased;
|
|
74
|
+
-moz-osx-font-smoothing: grayscale;
|
|
75
|
+
line-height: 1.55;
|
|
76
|
+
}
|
|
77
|
+
main {
|
|
78
|
+
width: 100%;
|
|
79
|
+
max-width: 30rem;
|
|
80
|
+
background: var(--panel);
|
|
81
|
+
border: 1px solid var(--border);
|
|
82
|
+
border-radius: var(--radius-box);
|
|
83
|
+
box-shadow: var(--shadow);
|
|
84
|
+
padding: 2.25rem;
|
|
85
|
+
}
|
|
86
|
+
.brand { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 1.75rem; }
|
|
87
|
+
.brand .tile {
|
|
88
|
+
display: grid;
|
|
89
|
+
place-items: center;
|
|
90
|
+
width: 2rem;
|
|
91
|
+
height: 2rem;
|
|
92
|
+
border-radius: 0.75rem;
|
|
93
|
+
background: var(--primary);
|
|
94
|
+
color: var(--primary-content);
|
|
95
|
+
box-shadow: 0 1px 2px oklch(0% 0 0 / 0.12);
|
|
96
|
+
}
|
|
97
|
+
.brand .tile svg { width: 1.25rem; height: 1.25rem; }
|
|
98
|
+
.brand .word {
|
|
99
|
+
font-weight: 700;
|
|
100
|
+
font-size: 1.25rem;
|
|
101
|
+
letter-spacing: -0.01em;
|
|
102
|
+
}
|
|
103
|
+
.eyebrow {
|
|
104
|
+
display: inline-flex;
|
|
105
|
+
align-items: center;
|
|
106
|
+
gap: 0.4rem;
|
|
107
|
+
font-size: 0.6875rem;
|
|
108
|
+
font-weight: 600;
|
|
109
|
+
text-transform: uppercase;
|
|
110
|
+
letter-spacing: 0.08em;
|
|
111
|
+
color: var(--muted);
|
|
112
|
+
margin-bottom: 0.6rem;
|
|
113
|
+
}
|
|
114
|
+
.eyebrow svg { width: 0.85rem; height: 0.85rem; }
|
|
115
|
+
h1 {
|
|
116
|
+
margin: 0 0 0.75rem;
|
|
117
|
+
font-size: 1.6rem;
|
|
118
|
+
font-weight: 800;
|
|
119
|
+
letter-spacing: -0.02em;
|
|
120
|
+
line-height: 1.15;
|
|
121
|
+
}
|
|
122
|
+
p { margin: 0 0 1rem; color: var(--subtle); }
|
|
123
|
+
.cta {
|
|
124
|
+
display: inline-flex;
|
|
125
|
+
align-items: center;
|
|
126
|
+
gap: 0.5rem;
|
|
127
|
+
margin: 0.25rem 0 0.5rem;
|
|
128
|
+
padding: 0.7rem 1.15rem;
|
|
129
|
+
border-radius: var(--radius-field);
|
|
130
|
+
background: var(--primary);
|
|
131
|
+
color: var(--primary-content);
|
|
132
|
+
font-weight: 600;
|
|
133
|
+
font-size: 0.95rem;
|
|
134
|
+
text-decoration: none;
|
|
135
|
+
box-shadow: 0 4px 14px -4px oklch(52% 0.2 293 / 0.5);
|
|
136
|
+
transition: transform 0.12s ease, box-shadow 0.12s ease;
|
|
137
|
+
}
|
|
138
|
+
.cta:hover { transform: translateY(-1px); box-shadow: 0 8px 20px -6px oklch(52% 0.2 293 / 0.55); }
|
|
139
|
+
.cta:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
|
|
140
|
+
.cta svg { width: 1rem; height: 1rem; }
|
|
141
|
+
.fix {
|
|
142
|
+
margin-top: 1.75rem;
|
|
143
|
+
padding: 1.1rem 1.2rem;
|
|
144
|
+
background: var(--recessed);
|
|
145
|
+
border: 1px solid var(--border);
|
|
146
|
+
border-radius: var(--radius-field);
|
|
147
|
+
}
|
|
148
|
+
.fix h2 {
|
|
149
|
+
margin: 0 0 0.5rem;
|
|
150
|
+
font-size: 0.8125rem;
|
|
151
|
+
font-weight: 700;
|
|
152
|
+
letter-spacing: 0.01em;
|
|
153
|
+
}
|
|
154
|
+
.fix p { margin: 0 0 0.65rem; font-size: 0.875rem; }
|
|
155
|
+
.fix p:last-child { margin-bottom: 0; }
|
|
156
|
+
.path {
|
|
157
|
+
display: block;
|
|
158
|
+
font-size: 0.8125rem;
|
|
159
|
+
font-weight: 600;
|
|
160
|
+
color: var(--ink);
|
|
161
|
+
letter-spacing: 0.01em;
|
|
162
|
+
margin: 0 0 0.65rem;
|
|
163
|
+
}
|
|
164
|
+
.path .arrow { color: var(--muted); padding: 0 0.35rem; font-weight: 400; }
|
|
165
|
+
.foot {
|
|
166
|
+
margin-top: 1.75rem;
|
|
167
|
+
text-align: center;
|
|
168
|
+
font-size: 0.75rem;
|
|
169
|
+
color: var(--muted);
|
|
170
|
+
}`;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Render a full self-contained admin page document. The caller supplies trusted inner HTML
|
|
174
|
+
* (eyebrow, heading, copy, CTA); the helper owns the head, the inlined style, the brand tile,
|
|
175
|
+
* and the footer.
|
|
176
|
+
*/
|
|
177
|
+
export function renderStaticAdminPage(opts: { title: string; innerHtml: string }): string {
|
|
178
|
+
return `<!doctype html>
|
|
179
|
+
<html lang="en">
|
|
180
|
+
<head>
|
|
181
|
+
<meta charset="utf-8" />
|
|
182
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
183
|
+
<meta name="robots" content="noindex, nofollow" />
|
|
184
|
+
<title>${escapeHtml(opts.title)}</title>
|
|
185
|
+
<style>
|
|
186
|
+
${SHARED_STYLE}
|
|
187
|
+
</style>
|
|
188
|
+
</head>
|
|
189
|
+
<body>
|
|
190
|
+
<main>
|
|
191
|
+
<div class="brand">
|
|
192
|
+
<span class="tile"><svg viewBox="0 0 15 15" fill="currentColor" aria-hidden="true">${CAIRN_GLYPH}</svg></span>
|
|
193
|
+
<span class="word">Cairn</span>
|
|
194
|
+
</div>
|
|
195
|
+
${opts.innerHtml}
|
|
196
|
+
<p class="foot">Powered by Cairn</p>
|
|
197
|
+
</main>
|
|
198
|
+
</body>
|
|
199
|
+
</html>`;
|
|
200
|
+
}
|