@glw907/cairn-cms 0.37.1 → 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 +71 -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/LoginPage.svelte +16 -4
- package/dist/components/LoginPage.svelte.d.ts +3 -1
- 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/diagnostics/conditions.js +16 -0
- package/dist/email.d.ts +20 -1
- package/dist/email.js +25 -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/auth-routes.d.ts +16 -3
- package/dist/sveltekit/auth-routes.js +47 -28
- package/dist/sveltekit/content-routes.d.ts +22 -1
- package/dist/sveltekit/content-routes.js +312 -72
- package/dist/sveltekit/index.d.ts +1 -1
- 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/LoginPage.svelte +16 -4
- 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/diagnostics/conditions.ts +16 -0
- package/src/lib/email.ts +31 -1
- package/src/lib/github/branches.ts +83 -0
- package/src/lib/log/events.ts +3 -0
- package/src/lib/sveltekit/auth-routes.ts +59 -29
- package/src/lib/sveltekit/content-routes.ts +391 -73
- package/src/lib/sveltekit/index.ts +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type FormatKind } from './markdown-format.js';
|
|
1
2
|
interface Props {
|
|
2
3
|
/** The markdown source; bindable so the parent reads edits back. */
|
|
3
4
|
value: string;
|
|
@@ -7,16 +8,21 @@ interface Props {
|
|
|
7
8
|
registerInsert?: (insert: (text: string) => void) => void;
|
|
8
9
|
/** Receives a `(href, title) => void` that inserts an inline link; the link picker calls it. */
|
|
9
10
|
registerInsertLink?: (insert: (href: string, title: string) => void) => void;
|
|
11
|
+
/** Receives a `() => string` returning the selected text; the web link dialog reads it. */
|
|
12
|
+
registerGetSelection?: (get: () => string) => void;
|
|
13
|
+
/** Receives a `(kind) => void` that transforms the current selection; the host's toolbar calls it. */
|
|
14
|
+
registerFormat?: (format: (kind: FormatKind) => void) => void;
|
|
10
15
|
/** Generic CodeMirror completion sources wired into the editor; the link autocomplete is one. The
|
|
11
16
|
* type is referenced inline so no static `@codemirror/*` import sits in this client-only file. */
|
|
12
17
|
completionSources?: import('@codemirror/autocomplete').CompletionSource[];
|
|
13
18
|
}
|
|
14
19
|
/**
|
|
15
20
|
* The `MarkdownEditor` seam (spec §6, seam 5): a thin wrapper over CodeMirror 6 exposing a bindable
|
|
16
|
-
* value and
|
|
21
|
+
* value and cursor-edit callbacks. CodeMirror is client-only, so it mounts after the component does
|
|
17
22
|
* through a dynamic import; until then a plain textarea carries the value so the form still submits, and
|
|
18
|
-
* the hidden field mirrors the value throughout. The
|
|
19
|
-
*
|
|
23
|
+
* the hidden field mirrors the value throughout. The host owns the toolbar and the card chrome, driving
|
|
24
|
+
* selection transforms through the registerFormat seam; the design-accurate preview lives in EditPage
|
|
25
|
+
* through the adapter's render. Swapping the editor stays a one-file change.
|
|
20
26
|
*/
|
|
21
27
|
declare const MarkdownEditor: import("svelte").Component<Props, {}, "value">;
|
|
22
28
|
type MarkdownEditor = ReturnType<typeof MarkdownEditor>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component
|
|
3
|
+
The Markdown cheat sheet, opened from the edit page's editor footer. A one-screen table pairs
|
|
4
|
+
each piece of syntax with what it makes, and a closing note explains the ::: layout blocks.
|
|
5
|
+
Built on a native <dialog>, the DeleteDialog recipe; the host drives it through the exported
|
|
6
|
+
open(), so the component renders no trigger of its own.
|
|
7
|
+
-->
|
|
8
|
+
<script lang="ts">
|
|
9
|
+
let dialog = $state<HTMLDialogElement | null>(null);
|
|
10
|
+
|
|
11
|
+
/** Open the cheat sheet. The trigger lives in the host (the edit page's editor footer). */
|
|
12
|
+
export function open() {
|
|
13
|
+
dialog?.showModal();
|
|
14
|
+
}
|
|
15
|
+
function close() {
|
|
16
|
+
dialog?.close();
|
|
17
|
+
}
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<dialog class="modal" aria-labelledby="cairn-markdown-help-title" bind:this={dialog}>
|
|
21
|
+
<div class="modal-box">
|
|
22
|
+
<div class="mb-3 flex items-center justify-between">
|
|
23
|
+
<h2 id="cairn-markdown-help-title" class="text-base font-semibold">Markdown help</h2>
|
|
24
|
+
<button type="button" class="btn btn-ghost btn-sm" aria-label="Close" onclick={close}>✕</button>
|
|
25
|
+
</div>
|
|
26
|
+
<table class="table table-sm">
|
|
27
|
+
<thead>
|
|
28
|
+
<tr>
|
|
29
|
+
<th class="text-[0.6875rem] font-semibold uppercase tracking-[0.08em] text-[var(--color-muted)]">Type this</th>
|
|
30
|
+
<th class="text-[0.6875rem] font-semibold uppercase tracking-[0.08em] text-[var(--color-muted)]">What it makes</th>
|
|
31
|
+
</tr>
|
|
32
|
+
</thead>
|
|
33
|
+
<tbody>
|
|
34
|
+
<tr><td><code>## Heading</code></td><td>A heading</td></tr>
|
|
35
|
+
<tr><td><code>### Heading</code></td><td>A smaller heading</td></tr>
|
|
36
|
+
<tr><td><code>**bold**</code></td><td>Bold text</td></tr>
|
|
37
|
+
<tr><td><code>*italic*</code></td><td>Italic text</td></tr>
|
|
38
|
+
<tr><td><code>~~text~~</code></td><td>Crossed-out text</td></tr>
|
|
39
|
+
<tr><td><code>[text](url)</code></td><td>A link</td></tr>
|
|
40
|
+
<tr><td><code>[[page-name]]</code></td><td>A link to one of your pages</td></tr>
|
|
41
|
+
<tr><td><code>- item</code></td><td>A bulleted list</td></tr>
|
|
42
|
+
<tr><td><code>1. item</code></td><td>A numbered list</td></tr>
|
|
43
|
+
<tr><td><code>- [ ] item</code></td><td>A checklist</td></tr>
|
|
44
|
+
<tr><td><code>> quote</code></td><td>A quote</td></tr>
|
|
45
|
+
<tr><td><code>`code`</code></td><td>Code</td></tr>
|
|
46
|
+
<tr><td>Table</td><td>The Table button in the toolbar inserts one</td></tr>
|
|
47
|
+
<tr><td><code>---</code></td><td>A horizontal rule</td></tr>
|
|
48
|
+
</tbody>
|
|
49
|
+
</table>
|
|
50
|
+
<p class="mt-3 text-sm">
|
|
51
|
+
Lines starting with <code>:::</code> are layout blocks. Edit the text inside them and leave
|
|
52
|
+
the <code>:::</code> lines alone.
|
|
53
|
+
</p>
|
|
54
|
+
</div>
|
|
55
|
+
<form method="dialog" class="modal-backdrop">
|
|
56
|
+
<button tabindex="-1" aria-label="Close">close</button>
|
|
57
|
+
</form>
|
|
58
|
+
</dialog>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Markdown cheat sheet, opened from the edit page's editor footer. A one-screen table pairs
|
|
3
|
+
* each piece of syntax with what it makes, and a closing note explains the ::: layout blocks.
|
|
4
|
+
* Built on a native <dialog>, the DeleteDialog recipe; the host drives it through the exported
|
|
5
|
+
* open(), so the component renders no trigger of its own.
|
|
6
|
+
*/
|
|
7
|
+
declare const MarkdownHelpDialog: import("svelte").Component<Record<string, never>, {
|
|
8
|
+
open: () => void;
|
|
9
|
+
}, "">;
|
|
10
|
+
type MarkdownHelpDialog = ReturnType<typeof MarkdownHelpDialog>;
|
|
11
|
+
export default MarkdownHelpDialog;
|
|
@@ -17,9 +17,15 @@ DeleteDialog a11y conventions.
|
|
|
17
17
|
label: string;
|
|
18
18
|
/** The current slug, prefilled into the input. */
|
|
19
19
|
slug: string;
|
|
20
|
+
/** Render the built-in Change URL trigger. False mounts only the dialog, for a host that
|
|
21
|
+
* supplies its own trigger and opens the dialog through the exported open(). */
|
|
22
|
+
trigger?: boolean;
|
|
23
|
+
/** Called when the rename form submits, before the document navigates. The edit page uses it
|
|
24
|
+
* to stand down its leave guard while the POST is in flight. */
|
|
25
|
+
onsubmitting?: () => void;
|
|
20
26
|
}
|
|
21
27
|
|
|
22
|
-
let { conceptId, id, label, slug }: Props = $props();
|
|
28
|
+
let { conceptId, id, label, slug, trigger = true, onsubmitting }: Props = $props();
|
|
23
29
|
|
|
24
30
|
let dialog = $state<HTMLDialogElement | null>(null);
|
|
25
31
|
let slugInput = $state<HTMLInputElement | null>(null);
|
|
@@ -27,7 +33,8 @@ DeleteDialog a11y conventions.
|
|
|
27
33
|
// current slug each time the dialog opens without capturing only the initial prop value.
|
|
28
34
|
let nextSlug = $state('');
|
|
29
35
|
|
|
30
|
-
|
|
36
|
+
/** Open the dialog with a fresh prefill. Exported so a trigger={false} host can drive it. */
|
|
37
|
+
export function open() {
|
|
31
38
|
nextSlug = slug;
|
|
32
39
|
dialog?.showModal();
|
|
33
40
|
// showModal() lands focus on the first focusable element (the header Close button), so move
|
|
@@ -43,7 +50,9 @@ DeleteDialog a11y conventions.
|
|
|
43
50
|
}
|
|
44
51
|
</script>
|
|
45
52
|
|
|
46
|
-
|
|
53
|
+
{#if trigger}
|
|
54
|
+
<button type="button" class="btn btn-sm btn-ghost" aria-haspopup="dialog" onclick={open}>Change URL</button>
|
|
55
|
+
{/if}
|
|
47
56
|
|
|
48
57
|
<dialog class="modal" aria-labelledby="cairn-rename-dialog-title" bind:this={dialog}>
|
|
49
58
|
<div class="modal-box">
|
|
@@ -51,7 +60,7 @@ DeleteDialog a11y conventions.
|
|
|
51
60
|
<h2 id="cairn-rename-dialog-title" class="text-base font-semibold">Change this {label.toLowerCase()} URL</h2>
|
|
52
61
|
<button type="button" class="btn btn-ghost btn-sm" aria-label="Close" onclick={close}>✕</button>
|
|
53
62
|
</div>
|
|
54
|
-
<form method="POST" action="?/rename" class="flex flex-col gap-3">
|
|
63
|
+
<form method="POST" action="?/rename" class="flex flex-col gap-3" onsubmit={() => onsubmitting?.()}>
|
|
55
64
|
<CsrfField />
|
|
56
65
|
<input type="hidden" name="concept" value={conceptId} />
|
|
57
66
|
<input type="hidden" name="id" value={id} />
|
|
@@ -7,6 +7,12 @@ interface Props {
|
|
|
7
7
|
label: string;
|
|
8
8
|
/** The current slug, prefilled into the input. */
|
|
9
9
|
slug: string;
|
|
10
|
+
/** Render the built-in Change URL trigger. False mounts only the dialog, for a host that
|
|
11
|
+
* supplies its own trigger and opens the dialog through the exported open(). */
|
|
12
|
+
trigger?: boolean;
|
|
13
|
+
/** Called when the rename form submits, before the document navigates. The edit page uses it
|
|
14
|
+
* to stand down its leave guard while the POST is in flight. */
|
|
15
|
+
onsubmitting?: () => void;
|
|
10
16
|
}
|
|
11
17
|
/**
|
|
12
18
|
* The Change URL control and its modal. The author edits the URL slug; on submit the ?/rename action
|
|
@@ -14,6 +20,8 @@ interface Props {
|
|
|
14
20
|
* dated post keeps its date; only the slug changes. Built on a native <dialog>, following the
|
|
15
21
|
* DeleteDialog a11y conventions.
|
|
16
22
|
*/
|
|
17
|
-
declare const RenameDialog: import("svelte").Component<Props, {
|
|
23
|
+
declare const RenameDialog: import("svelte").Component<Props, {
|
|
24
|
+
open: () => void;
|
|
25
|
+
}, "">;
|
|
18
26
|
type RenameDialog = ReturnType<typeof RenameDialog>;
|
|
19
27
|
export default RenameDialog;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component
|
|
3
|
+
The Web link control and its modal: the way to link out to an ordinary web address, beside the
|
|
4
|
+
Link to page picker that handles internal targets. Two fields, the address and an optional display
|
|
5
|
+
text; when the editor holds a selection it arrives as the default text, and the insert seam wraps
|
|
6
|
+
that selection either way. Built on a native <dialog>, following the LinkPicker a11y conventions,
|
|
7
|
+
and opened by the host's Ctrl/Cmd+K shortcut through the exported open().
|
|
8
|
+
-->
|
|
9
|
+
<script lang="ts">
|
|
10
|
+
interface Props {
|
|
11
|
+
/** Insert an inline link at the editor cursor; the editor's registerInsertLink seam. */
|
|
12
|
+
insert: (href: string, title: string) => void;
|
|
13
|
+
/** Read the editor's current selection, for the Text field's default. */
|
|
14
|
+
selection?: () => string;
|
|
15
|
+
/** Disable the trigger; the host sets it while Preview shows. */
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
/** Render the built-in Web link trigger. False mounts only the dialog, for a host that
|
|
18
|
+
* supplies its own trigger and opens the dialog through the exported open(). */
|
|
19
|
+
trigger?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let { insert, selection, disabled = false, trigger = true }: Props = $props();
|
|
23
|
+
|
|
24
|
+
let dialog = $state<HTMLDialogElement | null>(null);
|
|
25
|
+
let hrefInput = $state<HTMLInputElement | null>(null);
|
|
26
|
+
let href = $state('');
|
|
27
|
+
let text = $state('');
|
|
28
|
+
|
|
29
|
+
/** Open the dialog with fresh fields; the edit page's Ctrl/Cmd+K shortcut calls it too. */
|
|
30
|
+
export function open() {
|
|
31
|
+
href = '';
|
|
32
|
+
text = selection?.() ?? '';
|
|
33
|
+
dialog?.showModal();
|
|
34
|
+
// showModal() lands focus on the first focusable element (the header Close button), so move
|
|
35
|
+
// it to the address input the dialog exists for (WCAG 2.4.3). A microtask defers past the
|
|
36
|
+
// dialog's own focus handling, the RenameDialog recipe.
|
|
37
|
+
queueMicrotask(() => hrefInput?.focus());
|
|
38
|
+
}
|
|
39
|
+
function close() {
|
|
40
|
+
dialog?.close();
|
|
41
|
+
}
|
|
42
|
+
function submit(e: SubmitEvent) {
|
|
43
|
+
e.preventDefault();
|
|
44
|
+
// With no text and no selection the address itself becomes the display text, so the link
|
|
45
|
+
// never renders as an invisible pair of brackets.
|
|
46
|
+
insert(href, text.trim() || href);
|
|
47
|
+
close();
|
|
48
|
+
}
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
{#if trigger}
|
|
52
|
+
<button
|
|
53
|
+
type="button"
|
|
54
|
+
class="btn btn-sm btn-ghost"
|
|
55
|
+
aria-haspopup="dialog"
|
|
56
|
+
aria-label="Web link (Ctrl+K)"
|
|
57
|
+
title="Web link (Ctrl+K)"
|
|
58
|
+
{disabled}
|
|
59
|
+
onclick={open}
|
|
60
|
+
>
|
|
61
|
+
Web link
|
|
62
|
+
</button>
|
|
63
|
+
{/if}
|
|
64
|
+
|
|
65
|
+
<dialog class="modal" aria-labelledby="cairn-web-link-dialog-title" bind:this={dialog}>
|
|
66
|
+
<div class="modal-box">
|
|
67
|
+
<div class="mb-3 flex items-center justify-between">
|
|
68
|
+
<h2 id="cairn-web-link-dialog-title" class="text-base font-semibold">Add a web link</h2>
|
|
69
|
+
<button type="button" class="btn btn-ghost btn-sm" aria-label="Close" onclick={close}>✕</button>
|
|
70
|
+
</div>
|
|
71
|
+
<form onsubmit={submit} class="flex flex-col gap-3">
|
|
72
|
+
<label class="flex flex-col gap-1">
|
|
73
|
+
<span class="text-sm font-medium">Web address</span>
|
|
74
|
+
<input class="input w-full" type="url" required placeholder="https://…" bind:value={href} bind:this={hrefInput} />
|
|
75
|
+
</label>
|
|
76
|
+
<label class="flex flex-col gap-1">
|
|
77
|
+
<span class="text-sm font-medium">Text</span>
|
|
78
|
+
<input class="input w-full" placeholder="What the link says" bind:value={text} />
|
|
79
|
+
</label>
|
|
80
|
+
<div class="flex justify-end gap-2">
|
|
81
|
+
<button type="button" class="btn btn-sm" onclick={close}>Cancel</button>
|
|
82
|
+
<button type="submit" class="btn btn-sm btn-primary">Add link</button>
|
|
83
|
+
</div>
|
|
84
|
+
</form>
|
|
85
|
+
</div>
|
|
86
|
+
<form method="dialog" class="modal-backdrop">
|
|
87
|
+
<button tabindex="-1" aria-label="Close">close</button>
|
|
88
|
+
</form>
|
|
89
|
+
</dialog>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** Insert an inline link at the editor cursor; the editor's registerInsertLink seam. */
|
|
3
|
+
insert: (href: string, title: string) => void;
|
|
4
|
+
/** Read the editor's current selection, for the Text field's default. */
|
|
5
|
+
selection?: () => string;
|
|
6
|
+
/** Disable the trigger; the host sets it while Preview shows. */
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
/** Render the built-in Web link trigger. False mounts only the dialog, for a host that
|
|
9
|
+
* supplies its own trigger and opens the dialog through the exported open(). */
|
|
10
|
+
trigger?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The Web link control and its modal: the way to link out to an ordinary web address, beside the
|
|
14
|
+
* Link to page picker that handles internal targets. Two fields, the address and an optional display
|
|
15
|
+
* text; when the editor holds a selection it arrives as the default text, and the insert seam wraps
|
|
16
|
+
* that selection either way. Built on a native <dialog>, following the LinkPicker a11y conventions,
|
|
17
|
+
* and opened by the host's Ctrl/Cmd+K shortcut through the exported open().
|
|
18
|
+
*/
|
|
19
|
+
declare const WebLinkDialog: import("svelte").Component<Props, {
|
|
20
|
+
open: () => void;
|
|
21
|
+
}, "">;
|
|
22
|
+
type WebLinkDialog = ReturnType<typeof WebLinkDialog>;
|
|
23
|
+
export default WebLinkDialog;
|