@glw907/cairn-cms 0.60.0 → 0.62.1
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 +82 -0
- package/dist/components/AdminLayout.svelte +152 -229
- package/dist/components/CairnAdmin.svelte +13 -42
- package/dist/components/CairnLogo.svelte +1 -6
- package/dist/components/CairnMediaLibrary.svelte +821 -1210
- package/dist/components/CairnTidySettings.svelte +194 -261
- package/dist/components/CairnTidySettings.svelte.d.ts +1 -1
- package/dist/components/ComponentForm.svelte +110 -185
- package/dist/components/ComponentInsertDialog.svelte +163 -283
- package/dist/components/ConceptList.svelte +111 -191
- package/dist/components/ConfirmPage.svelte +5 -12
- package/dist/components/CsrfField.svelte +5 -11
- package/dist/components/DeleteDialog.svelte +15 -42
- package/dist/components/EditPage.svelte +781 -1205
- package/dist/components/EditorToolbar.svelte +108 -170
- package/dist/components/HelpHome.svelte +824 -0
- package/dist/components/HelpHome.svelte.d.ts +22 -0
- package/dist/components/IconPicker.svelte +23 -53
- package/dist/components/LinkPicker.svelte +34 -58
- package/dist/components/LoginPage.svelte +14 -27
- package/dist/components/ManageEditors.svelte +3 -15
- package/dist/components/MarkdownEditor.svelte +689 -957
- package/dist/components/MarkdownHelpDialog.svelte +12 -27
- package/dist/components/MediaCaptureCard.svelte +18 -57
- package/dist/components/MediaFigureControl.svelte +32 -71
- package/dist/components/MediaHeroField.svelte +210 -329
- package/dist/components/MediaInsertPopover.svelte +156 -283
- package/dist/components/MediaPicker.svelte +67 -131
- package/dist/components/NavTree.svelte +46 -78
- package/dist/components/RenameDialog.svelte +16 -43
- package/dist/components/ShortcutsDialog.svelte +9 -13
- package/dist/components/ShortcutsGrid.svelte +1 -2
- package/dist/components/TidyReview.svelte +140 -248
- package/dist/components/WebLinkDialog.svelte +19 -40
- package/dist/components/cairn-admin.css +4 -0
- package/dist/components/client-ingest.d.ts +16 -8
- package/dist/components/client-ingest.js +12 -6
- package/dist/components/editor-media.js +16 -8
- package/dist/components/editor-placeholder.d.ts +4 -2
- package/dist/components/editor-tidy.d.ts +24 -12
- package/dist/components/editor-tidy.js +8 -4
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/components/link-completion.d.ts +12 -6
- package/dist/components/link-completion.js +12 -6
- package/dist/components/markdown-directives.d.ts +9 -6
- package/dist/components/markdown-directives.js +9 -6
- package/dist/components/markdown-format.d.ts +7 -2
- package/dist/components/markdown-format.js +59 -28
- package/dist/components/markdown-reference.d.ts +8 -0
- package/dist/components/markdown-reference.js +22 -0
- package/dist/components/media-upload-outcome.d.ts +12 -6
- package/dist/components/objective-errors.d.ts +8 -4
- package/dist/components/objective-errors.js +8 -4
- package/dist/components/preview-doc.d.ts +4 -2
- package/dist/components/preview-doc.js +4 -2
- package/dist/components/spellcheck.d.ts +57 -29
- package/dist/components/spellcheck.js +50 -20
- package/dist/components/tidy-categorize.d.ts +20 -10
- package/dist/components/tidy-categorize.js +16 -8
- package/dist/components/tidy-validate.d.ts +12 -6
- package/dist/components/tidy-validate.js +20 -10
- package/dist/components/topbar-context.d.ts +4 -2
- package/dist/content/advisories.d.ts +51 -0
- package/dist/content/advisories.js +79 -0
- package/dist/content/compose.d.ts +4 -2
- package/dist/content/compose.js +1 -0
- package/dist/content/excerpt.js +4 -2
- package/dist/content/getting-started.d.ts +18 -0
- package/dist/content/getting-started.js +12 -0
- package/dist/content/links.d.ts +16 -8
- package/dist/content/links.js +12 -6
- package/dist/content/manifest.d.ts +36 -18
- package/dist/content/manifest.js +32 -16
- package/dist/content/media-refs.d.ts +4 -2
- package/dist/content/media-refs.js +4 -2
- package/dist/content/media-rewrite.d.ts +8 -4
- package/dist/content/media-rewrite.js +76 -38
- package/dist/content/schema.d.ts +20 -10
- package/dist/content/site-dictionary.d.ts +4 -2
- package/dist/content/site-dictionary.js +8 -4
- package/dist/content/types.d.ts +97 -42
- package/dist/delivery/CairnHead.svelte +8 -11
- package/dist/delivery/content-index.d.ts +16 -8
- package/dist/delivery/feeds.js +4 -2
- package/dist/delivery/json-ld.d.ts +3 -0
- package/dist/delivery/json-ld.js +3 -0
- package/dist/delivery/manifest.d.ts +4 -2
- package/dist/delivery/manifest.js +4 -2
- package/dist/delivery/public-routes.d.ts +12 -6
- package/dist/delivery/public-routes.js +4 -2
- package/dist/delivery/seo-fields.d.ts +12 -6
- package/dist/delivery/seo-fields.js +8 -4
- package/dist/delivery/site-indexes.d.ts +4 -2
- package/dist/delivery/site-resolver.d.ts +4 -2
- package/dist/delivery/site-resolver.js +4 -2
- package/dist/doctor/cloudflare-api.d.ts +6 -0
- package/dist/doctor/cloudflare-api.js +6 -0
- package/dist/doctor/index.d.ts +12 -6
- package/dist/doctor/report.d.ts +3 -0
- package/dist/doctor/report.js +3 -0
- package/dist/doctor/run.d.ts +3 -0
- package/dist/doctor/run.js +3 -0
- package/dist/doctor/types.d.ts +10 -2
- package/dist/doctor/types.js +6 -0
- package/dist/doctor/wrangler-config.d.ts +7 -2
- package/dist/doctor/wrangler-config.js +3 -0
- package/dist/email.d.ts +4 -2
- package/dist/env.d.ts +0 -3
- package/dist/env.js +0 -3
- package/dist/github/branches.d.ts +4 -2
- package/dist/github/branches.js +4 -2
- package/dist/github/signing.d.ts +1 -1
- package/dist/github/signing.js +2 -2
- package/dist/log/events.d.ts +1 -1
- package/dist/media/bulk-delete-plan.d.ts +8 -4
- package/dist/media/config.d.ts +12 -6
- package/dist/media/config.js +16 -8
- package/dist/media/delivery-bucket.d.ts +4 -2
- package/dist/media/library-entry.d.ts +4 -2
- package/dist/media/library-entry.js +4 -2
- package/dist/media/manifest.d.ts +29 -15
- package/dist/media/manifest.js +29 -16
- package/dist/media/naming.d.ts +12 -6
- package/dist/media/naming.js +24 -12
- package/dist/media/orphan-scan.d.ts +4 -2
- package/dist/media/reconcile.d.ts +21 -11
- package/dist/media/reconcile.js +12 -6
- package/dist/media/reference.d.ts +8 -4
- package/dist/media/reference.js +12 -6
- package/dist/media/rewrite-plan.d.ts +12 -6
- package/dist/media/sniff.d.ts +4 -2
- package/dist/media/sniff.js +28 -14
- package/dist/media/store.d.ts +16 -8
- package/dist/media/store.js +4 -2
- package/dist/media/transform-url.d.ts +12 -6
- package/dist/media/transform-url.js +8 -4
- package/dist/media/usage.d.ts +8 -4
- package/dist/nav/site-config.d.ts +16 -8
- package/dist/render/component-grammar.d.ts +23 -10
- package/dist/render/component-grammar.js +19 -8
- package/dist/render/component-insert.d.ts +8 -4
- package/dist/render/component-insert.js +4 -2
- package/dist/render/component-reference.d.ts +4 -2
- package/dist/render/component-reference.js +4 -2
- package/dist/render/component-validate.d.ts +3 -0
- package/dist/render/component-validate.js +3 -0
- package/dist/render/glyph.d.ts +4 -2
- package/dist/render/glyph.js +4 -2
- package/dist/render/pipeline.d.ts +20 -10
- package/dist/render/pipeline.js +4 -2
- package/dist/render/registry.d.ts +40 -20
- package/dist/render/registry.js +16 -8
- package/dist/render/rehype-dispatch.d.ts +22 -8
- package/dist/render/rehype-dispatch.js +22 -8
- package/dist/render/remark-directives.d.ts +3 -0
- package/dist/render/remark-directives.js +3 -0
- package/dist/render/remark-figure.d.ts +4 -2
- package/dist/render/remark-figure.js +4 -2
- package/dist/render/resolve-links.d.ts +4 -2
- package/dist/render/resolve-links.js +4 -2
- package/dist/render/resolve-media.d.ts +16 -8
- package/dist/render/resolve-media.js +12 -6
- package/dist/sveltekit/admin-dispatch.d.ts +2 -0
- package/dist/sveltekit/admin-dispatch.js +9 -3
- package/dist/sveltekit/auth-routes.d.ts +3 -0
- package/dist/sveltekit/auth-routes.js +3 -0
- package/dist/sveltekit/cairn-admin.d.ts +16 -5
- package/dist/sveltekit/cairn-admin.js +26 -10
- package/dist/sveltekit/content-routes.d.ts +191 -86
- package/dist/sveltekit/content-routes.js +295 -107
- package/dist/sveltekit/editors-routes.d.ts +3 -0
- package/dist/sveltekit/editors-routes.js +3 -0
- package/dist/sveltekit/guard.d.ts +4 -2
- package/dist/sveltekit/guard.js +4 -2
- package/dist/sveltekit/https-required-page.d.ts +1 -1
- package/dist/sveltekit/https-required-page.js +1 -1
- package/dist/sveltekit/index.d.ts +1 -1
- package/dist/sveltekit/media-route.d.ts +1 -2
- package/dist/sveltekit/media-route.js +13 -8
- package/dist/sveltekit/nav-routes.d.ts +7 -2
- package/dist/sveltekit/nav-routes.js +3 -0
- package/dist/sveltekit/types.d.ts +4 -2
- package/dist/vite/index.d.ts +32 -16
- package/dist/vite/index.js +52 -26
- package/dist/vite/resolve-root.d.ts +8 -4
- package/dist/vite/resolve-root.js +4 -2
- package/package.json +8 -2
- package/src/lib/components/AdminLayout.svelte +22 -0
- package/src/lib/components/CairnAdmin.svelte +3 -0
- package/src/lib/components/CairnTidySettings.svelte +2 -2
- package/src/lib/components/ComponentForm.svelte +0 -1
- package/src/lib/components/EditPage.svelte +133 -41
- package/src/lib/components/HelpHome.svelte +850 -0
- package/src/lib/components/MarkdownHelpDialog.svelte +4 -15
- package/src/lib/components/client-ingest.ts +20 -10
- package/src/lib/components/editor-media.ts +20 -10
- package/src/lib/components/editor-placeholder.ts +12 -6
- package/src/lib/components/editor-tidy.ts +28 -14
- package/src/lib/components/index.ts +1 -0
- package/src/lib/components/link-completion.ts +12 -6
- package/src/lib/components/markdown-directives.ts +13 -8
- package/src/lib/components/markdown-format.ts +63 -30
- package/src/lib/components/markdown-reference.ts +30 -0
- package/src/lib/components/media-upload-outcome.ts +12 -6
- package/src/lib/components/objective-errors.ts +16 -8
- package/src/lib/components/preview-doc.ts +4 -2
- package/src/lib/components/spellcheck.ts +92 -40
- package/src/lib/components/tidy-categorize.ts +28 -14
- package/src/lib/components/tidy-validate.ts +28 -14
- package/src/lib/components/topbar-context.ts +4 -2
- package/src/lib/content/advisories.ts +141 -0
- package/src/lib/content/compose.ts +5 -2
- package/src/lib/content/excerpt.ts +4 -2
- package/src/lib/content/getting-started.ts +31 -0
- package/src/lib/content/links.ts +16 -8
- package/src/lib/content/manifest.ts +36 -18
- package/src/lib/content/media-refs.ts +4 -2
- package/src/lib/content/media-rewrite.ts +100 -50
- package/src/lib/content/schema.ts +20 -10
- package/src/lib/content/site-dictionary.ts +8 -4
- package/src/lib/content/types.ts +97 -42
- package/src/lib/delivery/content-index.ts +16 -8
- package/src/lib/delivery/feeds.ts +4 -2
- package/src/lib/delivery/json-ld.ts +3 -0
- package/src/lib/delivery/manifest.ts +4 -2
- package/src/lib/delivery/public-routes.ts +16 -8
- package/src/lib/delivery/seo-fields.ts +12 -6
- package/src/lib/delivery/site-indexes.ts +4 -2
- package/src/lib/delivery/site-resolver.ts +4 -2
- package/src/lib/doctor/cloudflare-api.ts +6 -0
- package/src/lib/doctor/index.ts +12 -6
- package/src/lib/doctor/report.ts +3 -0
- package/src/lib/doctor/run.ts +3 -0
- package/src/lib/doctor/types.ts +10 -2
- package/src/lib/doctor/wrangler-config.ts +7 -2
- package/src/lib/email.ts +4 -2
- package/src/lib/env.ts +0 -3
- package/src/lib/github/branches.ts +4 -2
- package/src/lib/github/signing.ts +2 -2
- package/src/lib/log/events.ts +1 -0
- package/src/lib/media/bulk-delete-plan.ts +8 -4
- package/src/lib/media/config.ts +24 -12
- package/src/lib/media/delivery-bucket.ts +4 -2
- package/src/lib/media/library-entry.ts +4 -2
- package/src/lib/media/manifest.ts +33 -18
- package/src/lib/media/naming.ts +24 -12
- package/src/lib/media/orphan-scan.ts +4 -2
- package/src/lib/media/reconcile.ts +21 -11
- package/src/lib/media/reference.ts +12 -6
- package/src/lib/media/rewrite-plan.ts +12 -6
- package/src/lib/media/sniff.ts +28 -14
- package/src/lib/media/store.ts +16 -8
- package/src/lib/media/transform-url.ts +12 -6
- package/src/lib/media/usage.ts +8 -4
- package/src/lib/nav/site-config.ts +16 -8
- package/src/lib/render/component-grammar.ts +23 -10
- package/src/lib/render/component-insert.ts +8 -4
- package/src/lib/render/component-reference.ts +4 -2
- package/src/lib/render/component-validate.ts +3 -0
- package/src/lib/render/glyph.ts +4 -2
- package/src/lib/render/pipeline.ts +20 -10
- package/src/lib/render/registry.ts +44 -22
- package/src/lib/render/rehype-dispatch.ts +22 -8
- package/src/lib/render/remark-directives.ts +3 -0
- package/src/lib/render/remark-figure.ts +4 -2
- package/src/lib/render/resolve-links.ts +4 -2
- package/src/lib/render/resolve-media.ts +16 -8
- package/src/lib/sveltekit/admin-dispatch.ts +10 -4
- package/src/lib/sveltekit/auth-routes.ts +3 -0
- package/src/lib/sveltekit/cairn-admin.ts +37 -15
- package/src/lib/sveltekit/content-routes.ts +492 -197
- package/src/lib/sveltekit/editors-routes.ts +3 -0
- package/src/lib/sveltekit/guard.ts +4 -2
- package/src/lib/sveltekit/https-required-page.ts +1 -1
- package/src/lib/sveltekit/index.ts +3 -0
- package/src/lib/sveltekit/media-route.ts +13 -8
- package/src/lib/sveltekit/nav-routes.ts +7 -2
- package/src/lib/sveltekit/types.ts +4 -2
- package/src/lib/vite/index.ts +60 -30
- package/src/lib/vite/resolve-root.ts +8 -4
|
@@ -10,6 +10,9 @@ const EMAIL_RE = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
|
|
|
10
10
|
function parseRole(value) {
|
|
11
11
|
return value === 'owner' ? 'owner' : 'editor';
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
*
|
|
15
|
+
*/
|
|
13
16
|
export function createEditorRoutes() {
|
|
14
17
|
/** GET /admin/editors. Owner-only. Returns the allowlist and the acting owner's email. */
|
|
15
18
|
async function editorsLoad(event) {
|
|
@@ -2,9 +2,11 @@ import type { Editor } from '../auth/types.js';
|
|
|
2
2
|
import type { HandleInput, RequestContext } from './types.js';
|
|
3
3
|
/** The SvelteKit `Handle` that guards `/admin/**` and hardens admin responses. */
|
|
4
4
|
export declare function createAuthGuard(): ({ event, resolve }: HandleInput) => Promise<Response>;
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* For a protected load/action: the session the guard already resolved, or a login redirect.
|
|
6
7
|
* The parameter is the minimal structural need (just `locals`), so every engine event shape
|
|
7
|
-
* (RequestContext, the content routes' ContentEvent) and a real RequestEvent all satisfy it.
|
|
8
|
+
* (RequestContext, the content routes' ContentEvent) and a real RequestEvent all satisfy it.
|
|
9
|
+
*/
|
|
8
10
|
export declare function requireSession(event: {
|
|
9
11
|
locals: {
|
|
10
12
|
editor?: Editor | null;
|
package/dist/sveltekit/guard.js
CHANGED
|
@@ -87,9 +87,11 @@ export function createAuthGuard() {
|
|
|
87
87
|
return response;
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
|
-
/**
|
|
90
|
+
/**
|
|
91
|
+
* For a protected load/action: the session the guard already resolved, or a login redirect.
|
|
91
92
|
* The parameter is the minimal structural need (just `locals`), so every engine event shape
|
|
92
|
-
* (RequestContext, the content routes' ContentEvent) and a real RequestEvent all satisfy it.
|
|
93
|
+
* (RequestContext, the content routes' ContentEvent) and a real RequestEvent all satisfy it.
|
|
94
|
+
*/
|
|
93
95
|
export function requireSession(event) {
|
|
94
96
|
const editor = event.locals.editor;
|
|
95
97
|
if (!editor)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Render the full HTML document for the HTTPS-required page.
|
|
3
|
-
* @param httpsUrl The same request rebuilt over https, offered as the one-click recovery link.
|
|
3
|
+
* @param httpsUrl - The same request rebuilt over https, offered as the one-click recovery link.
|
|
4
4
|
*/
|
|
5
5
|
export declare function httpsRequiredPage(httpsUrl: string): string;
|
|
@@ -8,7 +8,7 @@ import { escapeHtml } from '../escape.js';
|
|
|
8
8
|
import { renderStaticAdminPage } from './static-admin-page.js';
|
|
9
9
|
/**
|
|
10
10
|
* Render the full HTML document for the HTTPS-required page.
|
|
11
|
-
* @param httpsUrl The same request rebuilt over https, offered as the one-click recovery link.
|
|
11
|
+
* @param httpsUrl - The same request rebuilt over https, offered as the one-click recovery link.
|
|
12
12
|
*/
|
|
13
13
|
export function httpsRequiredPage(httpsUrl) {
|
|
14
14
|
const href = escapeHtml(httpsUrl);
|
|
@@ -3,7 +3,7 @@ export { createAuthRoutes, type AuthRoutesConfig, type RequestResult } from './a
|
|
|
3
3
|
export { createEditorRoutes } from './editors-routes.js';
|
|
4
4
|
export { createContentRoutes } from './content-routes.js';
|
|
5
5
|
export { createMediaRoute } from './media-route.js';
|
|
6
|
-
export type { NavConcept, LayoutData, EntrySummary, ListData, EditData, MediaUsageInfo, MediaLibraryData, ContentEvent, ContentRoutesDeps, SaveFailure, DeleteRefusal, RenameFailure, MediaDeleteRefusal, MediaUpdateFailure, MediaReplaceFailure, MediaAltPropagateFailure, MediaBulkFailure, ContentFormFailure, UploadResult, } from './content-routes.js';
|
|
6
|
+
export type { NavConcept, LayoutData, EntrySummary, ListData, EditData, AdvisoryNotice, AdvisoryAction, HelpData, MediaUsageInfo, MediaLibraryData, ContentEvent, ContentRoutesDeps, SaveFailure, DeleteRefusal, RenameFailure, MediaDeleteRefusal, MediaUpdateFailure, MediaReplaceFailure, MediaAltPropagateFailure, MediaBulkFailure, ContentFormFailure, UploadResult, } from './content-routes.js';
|
|
7
7
|
export { createNavRoutes } from './nav-routes.js';
|
|
8
8
|
export type { NavLoadData, NavPageOption, NavRoutesDeps } from './nav-routes.js';
|
|
9
9
|
export { parseAdminPath, type AdminView } from './admin-dispatch.js';
|
|
@@ -6,7 +6,6 @@ import type { ResolvedAssetConfig } from '../media/config.js';
|
|
|
6
6
|
* The handler validates the hash and extension before any R2 call, derives the object key from the
|
|
7
7
|
* validated values only (never trusting the URL's fan-out), guards the Cloudflare Images self-loop,
|
|
8
8
|
* and sets the security headers on every served response.
|
|
9
|
-
*
|
|
10
|
-
* @param resolved the adapter's resolved media config; when media is off the handler always 404s.
|
|
9
|
+
* @param resolved - the adapter's resolved media config; when media is off the handler always 404s.
|
|
11
10
|
*/
|
|
12
11
|
export declare function createMediaRoute(resolved: ResolvedAssetConfig): RequestHandler;
|
|
@@ -4,12 +4,16 @@ import { r2Key } from '../media/naming.js';
|
|
|
4
4
|
import { log } from '../log/index.js';
|
|
5
5
|
/** A 16-character lowercase hex content-hash prefix, validated before any R2 lookup. */
|
|
6
6
|
const HASH_RE = /^[0-9a-f]{16}$/;
|
|
7
|
-
/**
|
|
8
|
-
*
|
|
7
|
+
/**
|
|
8
|
+
* The closed delivery extension allow-list. A filename ext outside this set is a 404 with no R2
|
|
9
|
+
* read, so the route can never serve a type it cannot vouch for.
|
|
10
|
+
*/
|
|
9
11
|
const DELIVERY_EXTS = new Set(['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif']);
|
|
10
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* The load-bearing XSS control: set on every non-404 response, so a served object can never run as
|
|
11
14
|
* active content. `Content-Type` comes from the stored, server-validated metadata via
|
|
12
|
-
* `writeHttpMetadata`; these override or add to it.
|
|
15
|
+
* `writeHttpMetadata`; these override or add to it.
|
|
16
|
+
*/
|
|
13
17
|
function applySecurityHeaders(headers, etag) {
|
|
14
18
|
headers.set('X-Content-Type-Options', 'nosniff');
|
|
15
19
|
headers.set('Content-Disposition', 'inline');
|
|
@@ -22,8 +26,10 @@ function applySecurityHeaders(headers, etag) {
|
|
|
22
26
|
if (!headers.has('Content-Type'))
|
|
23
27
|
headers.set('Content-Type', 'application/octet-stream');
|
|
24
28
|
}
|
|
25
|
-
/**
|
|
26
|
-
*
|
|
29
|
+
/**
|
|
30
|
+
* True when the returned object carries a body (a full or ranged read), narrowing it to the body
|
|
31
|
+
* variant. R2 returns a body-less object on an `If-None-Match` hit.
|
|
32
|
+
*/
|
|
27
33
|
function hasBody(obj) {
|
|
28
34
|
return 'body' in obj && obj.body != null;
|
|
29
35
|
}
|
|
@@ -33,8 +39,7 @@ function hasBody(obj) {
|
|
|
33
39
|
* The handler validates the hash and extension before any R2 call, derives the object key from the
|
|
34
40
|
* validated values only (never trusting the URL's fan-out), guards the Cloudflare Images self-loop,
|
|
35
41
|
* and sets the security headers on every served response.
|
|
36
|
-
*
|
|
37
|
-
* @param resolved the adapter's resolved media config; when media is off the handler always 404s.
|
|
42
|
+
* @param resolved - the adapter's resolved media config; when media is off the handler always 404s.
|
|
38
43
|
*/
|
|
39
44
|
export function createMediaRoute(resolved) {
|
|
40
45
|
return async (event) => {
|
|
@@ -21,10 +21,15 @@ export interface NavLoadData {
|
|
|
21
21
|
}
|
|
22
22
|
/** Injectable dependencies; tests stub the token mint to avoid signing a real key. */
|
|
23
23
|
export interface NavRoutesDeps {
|
|
24
|
-
/**
|
|
25
|
-
*
|
|
24
|
+
/**
|
|
25
|
+
* Mint a GitHub App installation token from the Worker env. Defaults to the real signer.
|
|
26
|
+
* A bare string works too; the routes await whatever comes back.
|
|
27
|
+
*/
|
|
26
28
|
mintToken?: (env: GithubKeyEnv) => string | Promise<string>;
|
|
27
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
*
|
|
32
|
+
*/
|
|
28
33
|
export declare function createNavRoutes(runtime: CairnRuntime, deps?: NavRoutesDeps): {
|
|
29
34
|
navLoad: (event: ContentEvent) => Promise<NavLoadData>;
|
|
30
35
|
navSave: (event: ContentEvent) => Promise<never>;
|
|
@@ -9,6 +9,9 @@ import { isConflict } from '../github/types.js';
|
|
|
9
9
|
import { log } from '../log/index.js';
|
|
10
10
|
import { parseSiteConfig, extractMenu, validateNavTree, setMenu } from '../nav/site-config.js';
|
|
11
11
|
import { requireSession } from './guard.js';
|
|
12
|
+
/**
|
|
13
|
+
*
|
|
14
|
+
*/
|
|
12
15
|
export function createNavRoutes(runtime, deps = {}) {
|
|
13
16
|
const mintToken = deps.mintToken ?? ((env) => cachedInstallationToken(appCredentials(runtime.backend, env)));
|
|
14
17
|
/** List page-like concepts (routable, not dated) for the URL picker. Best-effort per concept. */
|
|
@@ -23,9 +23,11 @@ export interface PlatformContext<Env> {
|
|
|
23
23
|
waitUntil(promise: Promise<unknown>): void;
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* The structural core every engine event type extends, parameterized by the Worker env the
|
|
27
28
|
* surface reads. Each shared field is defined once here; the extensions add only what their
|
|
28
|
-
* surface needs (cookies, params, setHeaders).
|
|
29
|
+
* surface needs (cookies, params, setHeaders).
|
|
30
|
+
*/
|
|
29
31
|
export interface EventBase<Env> {
|
|
30
32
|
url: URL;
|
|
31
33
|
request: Request;
|
package/dist/vite/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Plugin, PluginOption } from 'vite';
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
2
|
+
/**
|
|
3
|
+
* Options for {@link cairnManifest}. Paths are app-root-absolute (the form `import.meta.glob` wants),
|
|
4
|
+
* so they match the build's own resolution.
|
|
5
|
+
*/
|
|
4
6
|
export interface CairnManifestOptions {
|
|
5
7
|
/** The module exporting the `cairn` adapter and the parsed `siteConfig`, app-root-absolute. */
|
|
6
8
|
configModule: string;
|
|
@@ -9,28 +11,38 @@ export interface CairnManifestOptions {
|
|
|
9
11
|
/** The committed manifest path, app-root-absolute. Defaults to `/src/content/.cairn/index.json`. */
|
|
10
12
|
manifestPath?: string;
|
|
11
13
|
}
|
|
12
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Flatten the consumer's plugins option and drop the cairnManifest plugin at any nesting depth, so
|
|
13
16
|
* the nested verify server can never re-enter its buildStart. Vite supports (and flattens) nested
|
|
14
17
|
* plugin arrays, and findCairnOptions recurses into them, so a flat single-level filter would miss a
|
|
15
18
|
* cairnManifest nested inside a shared preset's sub-array and let it survive into the nested server.
|
|
16
|
-
* This mirrors findCairnOptions's recursion. Falsy slots pass through, which Vite tolerates.
|
|
19
|
+
* This mirrors findCairnOptions's recursion. Falsy slots pass through, which Vite tolerates.
|
|
20
|
+
*/
|
|
17
21
|
export declare function stripCairnManifest(plugins: PluginOption | PluginOption[]): PluginOption[];
|
|
18
|
-
/**
|
|
19
|
-
*
|
|
22
|
+
/**
|
|
23
|
+
* Verify the committed manifest against the corpus from a Vite context, throwing on drift. The bin
|
|
24
|
+
* and the plugin share this; the spike proved it runs cleanly inside the consumer's config.
|
|
25
|
+
*/
|
|
20
26
|
export declare function verifyManifestFromVite(opts: CairnManifestOptions, root: string): Promise<void>;
|
|
21
|
-
/**
|
|
22
|
-
*
|
|
27
|
+
/**
|
|
28
|
+
* Regenerate the serialized manifest from the corpus in a Vite context, sharing the build's
|
|
29
|
+
* resolution. The cairn-manifest bin (a later task) will call this and write the result.
|
|
30
|
+
*/
|
|
23
31
|
export declare function buildManifestFromVite(opts: CairnManifestOptions, root: string): Promise<string>;
|
|
24
|
-
/**
|
|
25
|
-
*
|
|
32
|
+
/**
|
|
33
|
+
* The cairnManifest plugin. It serves the verify virtual module to the app graph and, in
|
|
34
|
+
* buildStart, evaluates it through a nested Vite SSR load so a manifest drift fails the build.
|
|
35
|
+
*/
|
|
26
36
|
export declare function cairnManifest(opts: CairnManifestOptions): Plugin;
|
|
27
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* Regenerate the committed manifest from the consumer's corpus and write it to the configured
|
|
28
39
|
* manifestPath. It searches for the consumer's Vite config from `cwd`, derives the authoritative
|
|
29
40
|
* Vite root from the loaded config (so a configured `root` or a non-root cwd resolves correctly),
|
|
30
41
|
* reads the cairnManifest plugin's options off the instance, evaluates the write-mode virtual
|
|
31
42
|
* module through the build's own resolution, and writes the serialized manifest under the Vite
|
|
32
43
|
* root. The cairn-manifest bin calls this; it is exported so the write logic is testable apart
|
|
33
|
-
* from the CLI shell.
|
|
44
|
+
* from the CLI shell.
|
|
45
|
+
*/
|
|
34
46
|
export declare function writeManifest(cwd?: string): Promise<void>;
|
|
35
47
|
/** The repo and sender facts cairn-doctor derives off the consumer's adapter. */
|
|
36
48
|
export interface AdapterFacts {
|
|
@@ -40,14 +52,18 @@ export interface AdapterFacts {
|
|
|
40
52
|
repo?: string;
|
|
41
53
|
/** `cairn.sender.from`. */
|
|
42
54
|
from?: string;
|
|
43
|
-
/**
|
|
44
|
-
*
|
|
55
|
+
/**
|
|
56
|
+
* `cairn.assets.bucketBinding`, the media R2 binding name; undefined when the adapter declares no
|
|
57
|
+
* assets. The doctor's conditional media-bucket check reads it.
|
|
58
|
+
*/
|
|
45
59
|
mediaBucketBinding?: string;
|
|
46
60
|
}
|
|
47
|
-
/**
|
|
61
|
+
/**
|
|
62
|
+
* Read `{ owner, repo, from }` off the consumer's adapter by evaluating a tiny virtual module
|
|
48
63
|
* through the consumer's own Vite resolution, the same machinery the cairn-manifest bin uses.
|
|
49
64
|
* cairn-doctor calls this to fill inputs the operator did not pass. Derivation is best-effort:
|
|
50
65
|
* any failure (no Vite config, no cairnManifest plugin, a config module that throws) returns
|
|
51
66
|
* null, so the doctor degrades to flags instead of crashing. This runs only on the bin path,
|
|
52
|
-
* never in a Worker.
|
|
67
|
+
* never in a Worker.
|
|
68
|
+
*/
|
|
53
69
|
export declare function readAdapterFacts(cwd?: string): Promise<AdapterFacts | null>;
|
package/dist/vite/index.js
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { writeFile, mkdir } from 'node:fs/promises';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
3
|
import { resolveViteRoot } from './resolve-root.js';
|
|
4
|
-
/**
|
|
5
|
-
*
|
|
4
|
+
/**
|
|
5
|
+
* The key the cairnManifest plugin stashes its options under, so the write path can read them off the
|
|
6
|
+
* plugin instance in the consumer's loaded config without re-parsing the config file.
|
|
7
|
+
*/
|
|
6
8
|
const CAIRN_OPTIONS = Symbol.for('cairn-cms.manifest-options');
|
|
7
9
|
const VIRTUAL_ID = 'virtual:cairn-manifest';
|
|
8
10
|
const RESOLVED_ID = '\0' + VIRTUAL_ID;
|
|
9
11
|
/** The default committed manifest path, app-root-absolute. */
|
|
10
12
|
const DEFAULT_MANIFEST_PATH = '/src/content/.cairn/index.json';
|
|
11
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Build the virtual module source. In verify mode it throws on drift; in write mode it exports the
|
|
12
15
|
* serialized manifest as `result`. The module runs in the app graph, so its `import.meta.glob`,
|
|
13
|
-
* package, and `?raw` resolution is the build's own.
|
|
16
|
+
* package, and `?raw` resolution is the build's own.
|
|
17
|
+
*/
|
|
14
18
|
function virtualSource(opts, mode) {
|
|
15
19
|
const manifestPath = opts.manifestPath ?? DEFAULT_MANIFEST_PATH;
|
|
16
20
|
const globEntries = Object.entries(opts.content)
|
|
@@ -31,11 +35,13 @@ const built = buildSiteManifest(cairn, siteConfig, globs);
|
|
|
31
35
|
export const result = ${resultExpr};
|
|
32
36
|
`;
|
|
33
37
|
}
|
|
34
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Evaluate a virtual module source inside the consumer's own Vite resolution, then return the
|
|
35
40
|
* module's `result`. It reuses the consumer's loaded config (so `$lib`, the config module,
|
|
36
41
|
* `import.meta.glob`, and `?raw` resolve exactly as the build does) and strips the cairnManifest
|
|
37
42
|
* plugin from the nested server's plugin list, so its buildStart never recurses. This runs at
|
|
38
|
-
* build time and in the bins, never in the request lifecycle.
|
|
43
|
+
* build time and in the bins, never in the request lifecycle.
|
|
44
|
+
*/
|
|
39
45
|
async function evalVirtual(source, root) {
|
|
40
46
|
const { createServer, loadConfigFromFile } = await import('vite');
|
|
41
47
|
// Load the consumer's real Vite config so the nested server inherits SvelteKit's resolution
|
|
@@ -60,17 +66,21 @@ async function evalVirtual(source, root) {
|
|
|
60
66
|
await server.close();
|
|
61
67
|
}
|
|
62
68
|
}
|
|
63
|
-
/**
|
|
69
|
+
/**
|
|
70
|
+
* True for any plugin object whose name is the cairnManifest plugin, so the nested server drops it
|
|
64
71
|
* and cannot recurse into another buildStart. The consumer's plugin list may nest arrays and hold
|
|
65
|
-
* falsy slots, so guard the shape.
|
|
72
|
+
* falsy slots, so guard the shape.
|
|
73
|
+
*/
|
|
66
74
|
function isCairnManifestPlugin(p) {
|
|
67
75
|
return !!p && typeof p === 'object' && 'name' in p && p.name === 'cairn-manifest';
|
|
68
76
|
}
|
|
69
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* Flatten the consumer's plugins option and drop the cairnManifest plugin at any nesting depth, so
|
|
70
79
|
* the nested verify server can never re-enter its buildStart. Vite supports (and flattens) nested
|
|
71
80
|
* plugin arrays, and findCairnOptions recurses into them, so a flat single-level filter would miss a
|
|
72
81
|
* cairnManifest nested inside a shared preset's sub-array and let it survive into the nested server.
|
|
73
|
-
* This mirrors findCairnOptions's recursion. Falsy slots pass through, which Vite tolerates.
|
|
82
|
+
* This mirrors findCairnOptions's recursion. Falsy slots pass through, which Vite tolerates.
|
|
83
|
+
*/
|
|
74
84
|
export function stripCairnManifest(plugins) {
|
|
75
85
|
if (Array.isArray(plugins))
|
|
76
86
|
return plugins.flatMap(stripCairnManifest);
|
|
@@ -78,18 +88,24 @@ export function stripCairnManifest(plugins) {
|
|
|
78
88
|
return [];
|
|
79
89
|
return [plugins];
|
|
80
90
|
}
|
|
81
|
-
/**
|
|
82
|
-
*
|
|
91
|
+
/**
|
|
92
|
+
* Verify the committed manifest against the corpus from a Vite context, throwing on drift. The bin
|
|
93
|
+
* and the plugin share this; the spike proved it runs cleanly inside the consumer's config.
|
|
94
|
+
*/
|
|
83
95
|
export async function verifyManifestFromVite(opts, root) {
|
|
84
96
|
await evalVirtual(virtualSource(opts, 'verify'), root);
|
|
85
97
|
}
|
|
86
|
-
/**
|
|
87
|
-
*
|
|
98
|
+
/**
|
|
99
|
+
* Regenerate the serialized manifest from the corpus in a Vite context, sharing the build's
|
|
100
|
+
* resolution. The cairn-manifest bin (a later task) will call this and write the result.
|
|
101
|
+
*/
|
|
88
102
|
export async function buildManifestFromVite(opts, root) {
|
|
89
103
|
return evalVirtual(virtualSource(opts, 'write'), root);
|
|
90
104
|
}
|
|
91
|
-
/**
|
|
92
|
-
*
|
|
105
|
+
/**
|
|
106
|
+
* The cairnManifest plugin. It serves the verify virtual module to the app graph and, in
|
|
107
|
+
* buildStart, evaluates it through a nested Vite SSR load so a manifest drift fails the build.
|
|
108
|
+
*/
|
|
93
109
|
export function cairnManifest(opts) {
|
|
94
110
|
let root = process.cwd();
|
|
95
111
|
const plugin = {
|
|
@@ -121,13 +137,15 @@ export function cairnManifest(opts) {
|
|
|
121
137
|
plugin[CAIRN_OPTIONS] = opts;
|
|
122
138
|
return plugin;
|
|
123
139
|
}
|
|
124
|
-
/**
|
|
140
|
+
/**
|
|
141
|
+
* Regenerate the committed manifest from the consumer's corpus and write it to the configured
|
|
125
142
|
* manifestPath. It searches for the consumer's Vite config from `cwd`, derives the authoritative
|
|
126
143
|
* Vite root from the loaded config (so a configured `root` or a non-root cwd resolves correctly),
|
|
127
144
|
* reads the cairnManifest plugin's options off the instance, evaluates the write-mode virtual
|
|
128
145
|
* module through the build's own resolution, and writes the serialized manifest under the Vite
|
|
129
146
|
* root. The cairn-manifest bin calls this; it is exported so the write logic is testable apart
|
|
130
|
-
* from the CLI shell.
|
|
147
|
+
* from the CLI shell.
|
|
148
|
+
*/
|
|
131
149
|
export async function writeManifest(cwd = process.cwd()) {
|
|
132
150
|
const { loadConfigFromFile } = await import('vite');
|
|
133
151
|
const loaded = await loadConfigFromFile({ command: 'build', mode: 'production' }, undefined, cwd);
|
|
@@ -147,8 +165,10 @@ export async function writeManifest(cwd = process.cwd()) {
|
|
|
147
165
|
await mkdir(dirname(outPath), { recursive: true });
|
|
148
166
|
await writeFile(outPath, serialized);
|
|
149
167
|
}
|
|
150
|
-
/**
|
|
151
|
-
*
|
|
168
|
+
/**
|
|
169
|
+
* Walk a Vite plugins option (which may nest arrays, hold falsy slots, or be a thenable) and return
|
|
170
|
+
* the stashed cairnManifest options from the first matching plugin, or null if there is none.
|
|
171
|
+
*/
|
|
152
172
|
function findCairnOptions(plugins) {
|
|
153
173
|
if (!plugins)
|
|
154
174
|
return null;
|
|
@@ -165,8 +185,10 @@ function findCairnOptions(plugins) {
|
|
|
165
185
|
}
|
|
166
186
|
return null;
|
|
167
187
|
}
|
|
168
|
-
/**
|
|
169
|
-
*
|
|
188
|
+
/**
|
|
189
|
+
* A minimal plugin that serves only the given virtual module source, for the nested SSR load. It
|
|
190
|
+
* carries no buildStart, so the nested server never recurses into the verify.
|
|
191
|
+
*/
|
|
170
192
|
function cairnVirtualOnly(source) {
|
|
171
193
|
return {
|
|
172
194
|
name: 'cairn-manifest-virtual',
|
|
@@ -180,10 +202,12 @@ function cairnVirtualOnly(source) {
|
|
|
180
202
|
},
|
|
181
203
|
};
|
|
182
204
|
}
|
|
183
|
-
/**
|
|
205
|
+
/**
|
|
206
|
+
* Build the virtual module that reads only the adapter facts the doctor derives. It imports the
|
|
184
207
|
* configured config module and exports the string-typed `owner`, `repo`, `from`, and the media
|
|
185
208
|
* `bucketBinding` as JSON, so nothing else of the adapter (least of all a secret) crosses the
|
|
186
|
-
* boundary.
|
|
209
|
+
* boundary.
|
|
210
|
+
*/
|
|
187
211
|
function adapterFactsSource(opts) {
|
|
188
212
|
return `
|
|
189
213
|
import { cairn } from ${JSON.stringify(opts.configModule)};
|
|
@@ -198,12 +222,14 @@ if (typeof assets.bucketBinding === 'string') facts.mediaBucketBinding = assets.
|
|
|
198
222
|
export const result = JSON.stringify(facts);
|
|
199
223
|
`;
|
|
200
224
|
}
|
|
201
|
-
/**
|
|
225
|
+
/**
|
|
226
|
+
* Read `{ owner, repo, from }` off the consumer's adapter by evaluating a tiny virtual module
|
|
202
227
|
* through the consumer's own Vite resolution, the same machinery the cairn-manifest bin uses.
|
|
203
228
|
* cairn-doctor calls this to fill inputs the operator did not pass. Derivation is best-effort:
|
|
204
229
|
* any failure (no Vite config, no cairnManifest plugin, a config module that throws) returns
|
|
205
230
|
* null, so the doctor degrades to flags instead of crashing. This runs only on the bin path,
|
|
206
|
-
* never in a Worker.
|
|
231
|
+
* never in a Worker.
|
|
232
|
+
*/
|
|
207
233
|
export async function readAdapterFacts(cwd = process.cwd()) {
|
|
208
234
|
try {
|
|
209
235
|
const { loadConfigFromFile } = await import('vite');
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
1
|
+
/**
|
|
2
|
+
* The shape of `loadConfigFromFile`'s result that the root derivation reads: the config file's own
|
|
3
|
+
* path and its `root` field. Typed structurally so the helper is testable without a real load.
|
|
4
|
+
*/
|
|
3
5
|
export interface LoadedViteConfig {
|
|
4
6
|
/** The resolved path of the config file Vite loaded. */
|
|
5
7
|
path: string;
|
|
@@ -8,9 +10,11 @@ export interface LoadedViteConfig {
|
|
|
8
10
|
root?: string;
|
|
9
11
|
};
|
|
10
12
|
}
|
|
11
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* The authoritative Vite root for the manifest bin, derived from the loaded config the way Vite
|
|
12
15
|
* resolves a relative `root`: against the config file's own directory, not cwd. An absolute `root`
|
|
13
16
|
* stands as given, and no `root` falls back to `cwd` (the directory the bin was run from). This
|
|
14
17
|
* separates the config-search dir (cwd) from the Vite root, so a non-root cwd or a config that
|
|
15
|
-
* sets `root` reads and writes the manifest under the real app root.
|
|
18
|
+
* sets `root` reads and writes the manifest under the real app root.
|
|
19
|
+
*/
|
|
16
20
|
export declare function resolveViteRoot(loaded: LoadedViteConfig, cwd: string): string;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
// The manifest bin's root derivation, split out so it is unit-testable without widening the public
|
|
2
2
|
// /vite surface (only src/lib/vite/index.ts is the package subpath; this sibling is internal).
|
|
3
3
|
import { dirname, isAbsolute, resolve } from 'node:path';
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* The authoritative Vite root for the manifest bin, derived from the loaded config the way Vite
|
|
5
6
|
* resolves a relative `root`: against the config file's own directory, not cwd. An absolute `root`
|
|
6
7
|
* stands as given, and no `root` falls back to `cwd` (the directory the bin was run from). This
|
|
7
8
|
* separates the config-search dir (cwd) from the Vite root, so a non-root cwd or a config that
|
|
8
|
-
* sets `root` reads and writes the manifest under the real app root.
|
|
9
|
+
* sets `root` reads and writes the manifest under the real app root.
|
|
10
|
+
*/
|
|
9
11
|
export function resolveViteRoot(loaded, cwd) {
|
|
10
12
|
const root = loaded.config.root;
|
|
11
13
|
if (!root)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glw907/cairn-cms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.62.1",
|
|
4
4
|
"description": "Embedded, magic-link, GitHub-committing CMS for SvelteKit/Cloudflare sites.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"markdown"
|
|
27
27
|
],
|
|
28
28
|
"scripts": {
|
|
29
|
-
"package": "svelte-package && node scripts/build-admin-css.mjs && chmod +x dist/vite/bin.js dist/doctor/bin.js",
|
|
29
|
+
"package": "svelte-package && node scripts/build-admin-css.mjs && node scripts/transpile-dist-svelte.mjs && chmod +x dist/vite/bin.js dist/doctor/bin.js",
|
|
30
30
|
"check:package": "npm run package && publint --strict && attw --pack . --ignore-rules no-resolution cjs-resolves-to-esm internal-resolution-error",
|
|
31
31
|
"check:reference": "npm run package && node scripts/reference-coverage.mjs",
|
|
32
32
|
"check:reference:signatures": "npm run package && node scripts/check-reference-signatures.mjs",
|
|
@@ -34,6 +34,8 @@
|
|
|
34
34
|
"check:docs": "node scripts/docs-links.mjs",
|
|
35
35
|
"check:version": "node scripts/check-version.mjs",
|
|
36
36
|
"check:prose": "node scripts/check-admin-prose.mjs",
|
|
37
|
+
"lint": "eslint src/lib",
|
|
38
|
+
"check:comments": "bash scripts/check-comments.sh",
|
|
37
39
|
"prepare": "npm run package",
|
|
38
40
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
|
39
41
|
"test": "vitest run",
|
|
@@ -158,6 +160,9 @@
|
|
|
158
160
|
"@vitest/browser": "^4.1.7",
|
|
159
161
|
"@vitest/browser-playwright": "^4.1.7",
|
|
160
162
|
"daisyui": "^5.5.23",
|
|
163
|
+
"eslint": "^9.39.4",
|
|
164
|
+
"eslint-plugin-jsdoc": "^63.0.7",
|
|
165
|
+
"eslint-plugin-tsdoc": "^0.5.2",
|
|
161
166
|
"lightningcss": "^1.32.0",
|
|
162
167
|
"playwright": "^1.60.0",
|
|
163
168
|
"postcss": "^8.5.15",
|
|
@@ -167,6 +172,7 @@
|
|
|
167
172
|
"svelte-check": "^4",
|
|
168
173
|
"tailwindcss": "^4.3.0",
|
|
169
174
|
"typescript": "^6.0.3",
|
|
175
|
+
"typescript-eslint": "^8.62.0",
|
|
170
176
|
"vite": "^8.0",
|
|
171
177
|
"vitest": "^4.1",
|
|
172
178
|
"vitest-browser-svelte": "^2.1.1",
|
|
@@ -23,6 +23,7 @@ identical on every host regardless of the site's own theme.
|
|
|
23
23
|
import ImageIcon from '@lucide/svelte/icons/image';
|
|
24
24
|
import BlocksIcon from '@lucide/svelte/icons/blocks';
|
|
25
25
|
import ExternalLinkIcon from '@lucide/svelte/icons/external-link';
|
|
26
|
+
import HelpCircleIcon from '@lucide/svelte/icons/circle-help';
|
|
26
27
|
import './cairn-admin.css';
|
|
27
28
|
|
|
28
29
|
interface Props {
|
|
@@ -186,6 +187,7 @@ identical on every host regardless of the site's own theme.
|
|
|
186
187
|
|
|
187
188
|
const paletteCommands = $derived<Command[]>([
|
|
188
189
|
...coreItems.map((item) => ({ label: item.label, icon: item.icon, href: item.href })),
|
|
190
|
+
{ label: 'Help', icon: HelpCircleIcon, href: '/admin/help' },
|
|
189
191
|
{ label: 'View the live site', icon: ExternalLinkIcon, href: '/', external: true },
|
|
190
192
|
theme === 'cairn-admin'
|
|
191
193
|
? { label: 'Switch to dark mode', icon: MoonIcon, action: toggleTheme }
|
|
@@ -478,6 +480,26 @@ identical on every host regardless of the site's own theme.
|
|
|
478
480
|
{/each}
|
|
479
481
|
</div>
|
|
480
482
|
|
|
483
|
+
<!-- Help is a standing utility destination, pinned at the foot of the nav and set apart from
|
|
484
|
+
the content concepts by a top hairline. It is always present, labeled in plain text, and
|
|
485
|
+
styled as a peer of the nav items above it. -->
|
|
486
|
+
<div class="flex-none border-t border-[var(--cairn-card-border)] px-2 py-2">
|
|
487
|
+
<ul class="menu menu-sm w-full gap-0.5 p-0">
|
|
488
|
+
<li>
|
|
489
|
+
<a
|
|
490
|
+
href="/admin/help"
|
|
491
|
+
class={isActive('/admin/help')
|
|
492
|
+
? 'bg-primary/10 font-semibold text-primary'
|
|
493
|
+
: 'font-medium text-[var(--color-subtle)]'}
|
|
494
|
+
aria-current={isActive('/admin/help') ? 'page' : undefined}
|
|
495
|
+
>
|
|
496
|
+
<HelpCircleIcon class="h-4 w-4" aria-hidden="true" />
|
|
497
|
+
Help
|
|
498
|
+
</a>
|
|
499
|
+
</li>
|
|
500
|
+
</ul>
|
|
501
|
+
</div>
|
|
502
|
+
|
|
481
503
|
<div class="flex-none border-t border-[var(--cairn-card-border)] px-5 py-4">
|
|
482
504
|
<div class="flex items-center gap-3">
|
|
483
505
|
<div class="avatar avatar-placeholder">
|
|
@@ -15,6 +15,7 @@ mount inside `AdminLayout`. No styling or wrapper elements of its own.
|
|
|
15
15
|
import NavTree from './NavTree.svelte';
|
|
16
16
|
import CairnMediaLibrary from './CairnMediaLibrary.svelte';
|
|
17
17
|
import CairnTidySettings from './CairnTidySettings.svelte';
|
|
18
|
+
import HelpHome from './HelpHome.svelte';
|
|
18
19
|
import type { AdminData } from '../sveltekit/cairn-admin.js';
|
|
19
20
|
import type { ContentFormFailure } from '../sveltekit/content-routes.js';
|
|
20
21
|
import type { ComponentRegistry } from '../render/registry.js';
|
|
@@ -72,6 +73,8 @@ mount inside `AdminLayout`. No styling or wrapper elements of its own.
|
|
|
72
73
|
<CairnMediaLibrary data={data.page} {form} />
|
|
73
74
|
{:else if data.view === 'settings'}
|
|
74
75
|
<CairnTidySettings data={data.page} />
|
|
76
|
+
{:else if data.view === 'help'}
|
|
77
|
+
<HelpHome data={data.page} />
|
|
75
78
|
{/if}
|
|
76
79
|
</AdminLayout>
|
|
77
80
|
{/if}
|
|
@@ -8,7 +8,7 @@ Two tiers with a truthful visibility gate:
|
|
|
8
8
|
that tidy is enabled, a key is configured, and which model runs, but cannot edit any of it. The
|
|
9
9
|
literal deploy-time tokens sit in a marked "For your developer" sub-block.
|
|
10
10
|
- The EDITOR tier (the per-convention config) renders ONLY when tidy is enabled AND the key is
|
|
11
|
-
present (`data.enabled`). When tidy is not enabled, the editor tier is
|
|
11
|
+
present (`data.enabled`). When tidy is not enabled, the editor tier is ABSENT, replaced
|
|
12
12
|
by an honest labelled gate region with a read-only "what your developer needs to do" checklist and
|
|
13
13
|
a "spellcheck still works" reassurance. No teasing disabled controls sit in the tab order.
|
|
14
14
|
|
|
@@ -515,7 +515,7 @@ home), diffable and shared across editors.
|
|
|
515
515
|
</div>
|
|
516
516
|
</form>
|
|
517
517
|
{:else}
|
|
518
|
-
<!-- THE VISIBILITY GATE: tidy NOT enabled by the developer. The convention list is
|
|
518
|
+
<!-- THE VISIBILITY GATE: tidy NOT enabled by the developer. The convention list is
|
|
519
519
|
absent, not disabled. One honest labelled region names the deploy-time task and who does it,
|
|
520
520
|
with no disabled controls in the tab order. -->
|
|
521
521
|
<div role="region" aria-label="Tidy is not set up" class="mt-6 flex flex-col items-center gap-3 rounded-2xl border border-[var(--cairn-card-border)] bg-base-100 p-10 text-center shadow-[var(--cairn-shadow)]">
|
|
@@ -56,7 +56,6 @@ binds out its live `values` and `incomplete` so the dialog can render that previ
|
|
|
56
56
|
});
|
|
57
57
|
|
|
58
58
|
const attributes = $derived(def.attributes ?? []);
|
|
59
|
-
// Non-repeatable slots render here; the repeatable list is handled separately.
|
|
60
59
|
const flatSlots = $derived((def.slots ?? []).filter((s) => s.kind !== 'repeatable'));
|
|
61
60
|
const repeatableSlots = $derived((def.slots ?? []).filter((s) => s.kind === 'repeatable'));
|
|
62
61
|
|