@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
|
@@ -22,298 +22,171 @@ editor.focusEditor() (the selection is intact, since opening only blurred the ed
|
|
|
22
22
|
narrow breakpoint it falls back to a full-height bottom sheet (the admin design system's modal-sizing
|
|
23
23
|
rule). The CSRF token is read from the admin context.
|
|
24
24
|
-->
|
|
25
|
-
<script lang="ts">
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
25
|
+
<script lang="ts">import { getContext, tick } from "svelte";
|
|
26
|
+
import { CSRF_CONTEXT_KEY } from "./csrf-context.js";
|
|
27
|
+
import MediaCaptureCard from "./MediaCaptureCard.svelte";
|
|
28
|
+
import MediaPicker, {} from "./MediaPicker.svelte";
|
|
29
|
+
import {
|
|
30
|
+
ingestFile,
|
|
31
|
+
buildUploadRequest,
|
|
32
|
+
sendUpload,
|
|
33
|
+
ingestFailureKind,
|
|
34
|
+
failureCard
|
|
35
|
+
} from "./client-ingest.js";
|
|
36
|
+
import { deserialize } from "$app/forms";
|
|
37
|
+
import { uploadOutcome } from "./media-upload-outcome.js";
|
|
38
|
+
let { conceptId, id, library, editor, onuploaded, trigger = false } = $props();
|
|
39
|
+
const csrf = getContext(CSRF_CONTEXT_KEY);
|
|
40
|
+
let view = $state(null);
|
|
41
|
+
let captureFile = $state(null);
|
|
42
|
+
let status = $state({ kind: "idle" });
|
|
43
|
+
let anchor = $state(null);
|
|
44
|
+
let panel = $state(null);
|
|
45
|
+
let fileInput = $state(null);
|
|
46
|
+
let retryButton = $state(null);
|
|
47
|
+
let expiredCloseButton = $state(null);
|
|
48
|
+
let reusedDoneButton = $state(null);
|
|
49
|
+
$effect(() => {
|
|
50
|
+
const kind = status.kind;
|
|
51
|
+
if (kind !== "failed" && kind !== "expired" && kind !== "reused") return;
|
|
52
|
+
void tick().then(() => {
|
|
53
|
+
if (kind === "failed") retryButton?.focus();
|
|
54
|
+
else if (kind === "expired") expiredCloseButton?.focus();
|
|
55
|
+
else reusedDoneButton?.focus();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
export function open(signal, file) {
|
|
59
|
+
anchor = editor.caretCoords();
|
|
60
|
+
status = { kind: "idle" };
|
|
61
|
+
if (signal === "capture" && file) {
|
|
62
|
+
captureFile = file;
|
|
63
|
+
view = "capture";
|
|
64
|
+
} else {
|
|
65
|
+
captureFile = null;
|
|
66
|
+
view = "chooser";
|
|
53
67
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
placeholders: ImagePlaceholderApi;
|
|
68
|
-
insertImage: (alt: string, ref: string) => void;
|
|
69
|
-
};
|
|
70
|
-
/** Called with the server-owned record on a successful upload; the host appends it to its records
|
|
71
|
-
* state and merges it into the library so the source decoration resolves the new reference. */
|
|
72
|
-
onuploaded: (record: MediaEntry) => void;
|
|
73
|
-
/** Render the built-in trigger button. False (the default) mounts headless; the host opens the
|
|
74
|
-
* popover through the exported open(). */
|
|
75
|
-
trigger?: boolean;
|
|
68
|
+
void tick().then(() => panel?.focus());
|
|
69
|
+
}
|
|
70
|
+
function close() {
|
|
71
|
+
view = null;
|
|
72
|
+
captureFile = null;
|
|
73
|
+
status = { kind: "idle" };
|
|
74
|
+
editor.focusEditor();
|
|
75
|
+
}
|
|
76
|
+
function onKeydown(e) {
|
|
77
|
+
if (e.key === "Escape") {
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
close();
|
|
80
|
+
return;
|
|
76
81
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
82
|
+
if (e.key !== "Tab" || !panel) return;
|
|
83
|
+
const focusable = panel.querySelectorAll(
|
|
84
|
+
'a[href], button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'
|
|
85
|
+
);
|
|
86
|
+
if (focusable.length === 0) return;
|
|
87
|
+
const first = focusable[0];
|
|
88
|
+
const last = focusable[focusable.length - 1];
|
|
89
|
+
const activeEl = document.activeElement;
|
|
90
|
+
if (e.shiftKey && (activeEl === first || activeEl === panel)) {
|
|
91
|
+
e.preventDefault();
|
|
92
|
+
last.focus();
|
|
93
|
+
} else if (!e.shiftKey && activeEl === last) {
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
first.focus();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function onPick(sel) {
|
|
99
|
+
editor.insertImage(sel.alt, sel.ref);
|
|
100
|
+
close();
|
|
101
|
+
}
|
|
102
|
+
function onChosenFile(e) {
|
|
103
|
+
const input = e.currentTarget;
|
|
104
|
+
const file = input.files?.[0];
|
|
105
|
+
if (file) {
|
|
106
|
+
captureFile = file;
|
|
107
|
+
view = "capture";
|
|
108
|
+
status = { kind: "idle" };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function runUpload(record) {
|
|
112
|
+
const objectUrl = URL.createObjectURL(record.file);
|
|
113
|
+
const pid = editor.placeholders.begin(objectUrl);
|
|
114
|
+
view = null;
|
|
115
|
+
captureFile = null;
|
|
116
|
+
const discardPlaceholder = () => {
|
|
117
|
+
editor.placeholders.cancel(pid);
|
|
118
|
+
URL.revokeObjectURL(objectUrl);
|
|
119
|
+
};
|
|
120
|
+
const fail = (card) => {
|
|
121
|
+
discardPlaceholder();
|
|
122
|
+
status = { kind: "failed", card, retry: () => void runUpload(record) };
|
|
123
|
+
};
|
|
124
|
+
const expire = () => {
|
|
125
|
+
discardPlaceholder();
|
|
126
|
+
status = { kind: "expired" };
|
|
127
|
+
};
|
|
128
|
+
const genericCard = () => fail({ status: "failed", kind: "generic", message: GENERIC_FAILURE_MESSAGE });
|
|
129
|
+
editor.placeholders.progress(pid, 0.4);
|
|
130
|
+
let ingested;
|
|
131
|
+
try {
|
|
132
|
+
ingested = await ingestFile(record.file);
|
|
133
|
+
} catch (err) {
|
|
134
|
+
fail(failureCard(ingestFailureKind(err)));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
editor.placeholders.progress(pid, 0.85);
|
|
138
|
+
const { url, init } = buildUploadRequest({
|
|
139
|
+
conceptId,
|
|
140
|
+
id,
|
|
141
|
+
bytes: ingested.blob,
|
|
142
|
+
contentType: ingested.contentType,
|
|
143
|
+
csrf: csrf?.() ?? "",
|
|
144
|
+
filename: record.file.name,
|
|
145
|
+
alt: record.alt,
|
|
146
|
+
displayName: record.displayName,
|
|
147
|
+
width: ingested.width,
|
|
148
|
+
height: ingested.height
|
|
127
149
|
});
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
export function open(signal: 'chooser' | 'capture', file?: File): void {
|
|
135
|
-
anchor = editor.caretCoords();
|
|
136
|
-
status = { kind: 'idle' };
|
|
137
|
-
if (signal === 'capture' && file) {
|
|
138
|
-
captureFile = file;
|
|
139
|
-
view = 'capture';
|
|
140
|
-
} else {
|
|
141
|
-
captureFile = null;
|
|
142
|
-
view = 'chooser';
|
|
143
|
-
}
|
|
144
|
-
// Move focus into the panel once it renders.
|
|
145
|
-
void tick().then(() => panel?.focus());
|
|
150
|
+
let res;
|
|
151
|
+
try {
|
|
152
|
+
res = await sendUpload(url, init);
|
|
153
|
+
} catch (err) {
|
|
154
|
+
fail(failureCard(ingestFailureKind(err)));
|
|
155
|
+
return;
|
|
146
156
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
captureFile = null;
|
|
151
|
-
status = { kind: 'idle' };
|
|
152
|
-
editor.focusEditor();
|
|
157
|
+
if (res.type === "opaqueredirect" || res.status === 0) {
|
|
158
|
+
expire();
|
|
159
|
+
return;
|
|
153
160
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
if (e.key !== 'Tab' || !panel) return;
|
|
163
|
-
const focusable = panel.querySelectorAll<HTMLElement>(
|
|
164
|
-
'a[href], button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])',
|
|
165
|
-
);
|
|
166
|
-
if (focusable.length === 0) return;
|
|
167
|
-
const first = focusable[0];
|
|
168
|
-
const last = focusable[focusable.length - 1];
|
|
169
|
-
const activeEl = document.activeElement;
|
|
170
|
-
if (e.shiftKey && (activeEl === first || activeEl === panel)) {
|
|
171
|
-
e.preventDefault();
|
|
172
|
-
last.focus();
|
|
173
|
-
} else if (!e.shiftKey && activeEl === last) {
|
|
174
|
-
e.preventDefault();
|
|
175
|
-
first.focus();
|
|
176
|
-
}
|
|
161
|
+
let outcome;
|
|
162
|
+
try {
|
|
163
|
+
outcome = uploadOutcome(deserialize(await res.text()));
|
|
164
|
+
} catch {
|
|
165
|
+
genericCard();
|
|
166
|
+
return;
|
|
177
167
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
function onPick(sel: MediaSelection) {
|
|
182
|
-
editor.insertImage(sel.alt, sel.ref);
|
|
183
|
-
close();
|
|
168
|
+
if (outcome.kind === "session-expired") {
|
|
169
|
+
expire();
|
|
170
|
+
return;
|
|
184
171
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const input = e.currentTarget as HTMLInputElement;
|
|
190
|
-
const file = input.files?.[0];
|
|
191
|
-
if (file) {
|
|
192
|
-
captureFile = file;
|
|
193
|
-
view = 'capture';
|
|
194
|
-
status = { kind: 'idle' };
|
|
195
|
-
}
|
|
172
|
+
if (outcome.kind === "failed") {
|
|
173
|
+
if (outcome.failure === "generic") genericCard();
|
|
174
|
+
else fail(failureCard(outcome.failure));
|
|
175
|
+
return;
|
|
196
176
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
// Close the byte-capture view now; the placeholder carries the progress in the source.
|
|
205
|
-
view = null;
|
|
206
|
-
captureFile = null;
|
|
207
|
-
|
|
208
|
-
// Drop the in-flight placeholder, leaving the source exactly as it was, and free the object URL.
|
|
209
|
-
// Both unsuccessful paths (a typed failure, an expired session) end here before setting status.
|
|
210
|
-
const discardPlaceholder = () => {
|
|
211
|
-
editor.placeholders.cancel(pid);
|
|
212
|
-
URL.revokeObjectURL(objectUrl);
|
|
213
|
-
};
|
|
214
|
-
const fail = (card: FailureCard) => {
|
|
215
|
-
discardPlaceholder();
|
|
216
|
-
status = { kind: 'failed', card, retry: () => void runUpload(record) };
|
|
217
|
-
};
|
|
218
|
-
const expire = () => {
|
|
219
|
-
discardPlaceholder();
|
|
220
|
-
status = { kind: 'expired' };
|
|
221
|
-
};
|
|
222
|
-
// The author-facing card for an envelope-only generic refusal (a deserialize throw or an
|
|
223
|
-
// operational reason with no author-actionable specifics).
|
|
224
|
-
const genericCard = () => fail({ status: 'failed', kind: 'generic', message: GENERIC_FAILURE_MESSAGE });
|
|
225
|
-
|
|
226
|
-
// Stage progress, not real byte counts: fetch cannot report upload bytes, so the bar reads the
|
|
227
|
-
// ingest/upload LIFECYCLE (begin ~0.1 set by the field, ingesting ~0.4, uploading ~0.85, resolve
|
|
228
|
-
// clears it). Honest stage progress, never a fabricated timer.
|
|
229
|
-
editor.placeholders.progress(pid, 0.4);
|
|
230
|
-
let ingested: Awaited<ReturnType<typeof ingestFile>>;
|
|
231
|
-
try {
|
|
232
|
-
ingested = await ingestFile(record.file);
|
|
233
|
-
} catch (err) {
|
|
234
|
-
fail(failureCard(ingestFailureKind(err)));
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
editor.placeholders.progress(pid, 0.85);
|
|
239
|
-
const { url, init } = buildUploadRequest({
|
|
240
|
-
conceptId,
|
|
241
|
-
id,
|
|
242
|
-
bytes: ingested.blob,
|
|
243
|
-
contentType: ingested.contentType,
|
|
244
|
-
csrf: csrf?.() ?? '',
|
|
245
|
-
filename: record.file.name,
|
|
246
|
-
alt: record.alt,
|
|
247
|
-
displayName: record.displayName,
|
|
248
|
-
width: ingested.width,
|
|
249
|
-
height: ingested.height,
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
let res: Response;
|
|
253
|
-
try {
|
|
254
|
-
res = await sendUpload(url, init);
|
|
255
|
-
} catch (err) {
|
|
256
|
-
fail(failureCard(ingestFailureKind(err)));
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// The guard's expired-session 303 under redirect: 'manual' surfaces as an opaque, status-0
|
|
261
|
-
// response; treat it as session-expired before parsing a body that is not there.
|
|
262
|
-
if (res.type === 'opaqueredirect' || res.status === 0) {
|
|
263
|
-
expire();
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// deserialize returns the generic ActionResult; the upload action's success data is an
|
|
268
|
-
// UploadResult and its failure data carries an error string, so the result matches UploadEnvelope.
|
|
269
|
-
// The cast names that known shape for the pure mapper (the redirect/status-0 case is handled above).
|
|
270
|
-
// An unexpected server response (a 500/502/504 from a worker crash, OOM, or an edge timeout) is an
|
|
271
|
-
// HTML error page, not a devalue-encoded result, so deserialize throws. Catch it and route the
|
|
272
|
-
// throw through fail() with the generic card, so the placeholder cancels and a Retry is offered.
|
|
273
|
-
let outcome: ReturnType<typeof uploadOutcome>;
|
|
274
|
-
try {
|
|
275
|
-
outcome = uploadOutcome(deserialize(await res.text()) as UploadEnvelope);
|
|
276
|
-
} catch {
|
|
277
|
-
genericCard();
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
if (outcome.kind === 'session-expired') {
|
|
281
|
-
expire();
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
if (outcome.kind === 'failed') {
|
|
285
|
-
// An ingest-taxonomy kind reuses failureCard's own message; the envelope-only `generic` kind
|
|
286
|
-
// carries its own plain message. Either way the card shows the message with a Retry.
|
|
287
|
-
if (outcome.failure === 'generic') genericCard();
|
|
288
|
-
else fail(failureCard(outcome.failure));
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// Success: swap the placeholder for the committed reference in one transaction, hand the record
|
|
293
|
-
// up, and close. A dedup reuse still inserts the existing reference (the decision) and briefly
|
|
294
|
-
// notes it.
|
|
295
|
-
editor.placeholders.resolveTo(pid, record.alt, outcome.reference);
|
|
296
|
-
onuploaded(outcome.record);
|
|
297
|
-
URL.revokeObjectURL(objectUrl);
|
|
298
|
-
if (outcome.reused) {
|
|
299
|
-
status = { kind: 'reused' };
|
|
300
|
-
} else {
|
|
301
|
-
close();
|
|
302
|
-
}
|
|
177
|
+
editor.placeholders.resolveTo(pid, record.alt, outcome.reference);
|
|
178
|
+
onuploaded(outcome.record);
|
|
179
|
+
URL.revokeObjectURL(objectUrl);
|
|
180
|
+
if (outcome.reused) {
|
|
181
|
+
status = { kind: "reused" };
|
|
182
|
+
} else {
|
|
183
|
+
close();
|
|
303
184
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
// The popover's anchored position: just below the caret line, clamped into the viewport. A null
|
|
311
|
-
// anchor centers it. The full-height sheet at the narrow breakpoint is the CSS fallback.
|
|
312
|
-
const positionStyle = $derived(
|
|
313
|
-
anchor
|
|
314
|
-
? `left: ${Math.max(8, Math.min(anchor.left, (typeof window !== 'undefined' ? window.innerWidth : 1024) - 360))}px; top: ${anchor.bottom + 6}px;`
|
|
315
|
-
: 'left: 50%; top: 4rem; transform: translateX(-50%);',
|
|
316
|
-
);
|
|
185
|
+
}
|
|
186
|
+
const GENERIC_FAILURE_MESSAGE = "The upload could not be completed. Please try again.";
|
|
187
|
+
const positionStyle = $derived(
|
|
188
|
+
anchor ? `left: ${Math.max(8, Math.min(anchor.left, (typeof window !== "undefined" ? window.innerWidth : 1024) - 360))}px; top: ${anchor.bottom + 6}px;` : "left: 50%; top: 4rem; transform: translateX(-50%);"
|
|
189
|
+
);
|
|
317
190
|
</script>
|
|
318
191
|
|
|
319
192
|
{#if trigger}
|