@caretcms/core 0.1.1 → 0.2.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/README.md +101 -18
- package/dist/browser-runtime.d.ts +16 -0
- package/dist/browser-runtime.d.ts.map +1 -1
- package/dist/browser-runtime.js +8 -3
- package/dist/browser-runtime.js.map +1 -1
- package/dist/contracts.d.ts +1 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +1 -0
- package/dist/contracts.js.map +1 -1
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +239 -13
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +17 -6
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +33 -14
- package/dist/loader.js.map +1 -1
- package/dist/markdown/block-context.d.ts +21 -0
- package/dist/markdown/block-context.d.ts.map +1 -0
- package/dist/markdown/block-context.js +27 -0
- package/dist/markdown/block-context.js.map +1 -0
- package/dist/markdown/canonical-body.d.ts +30 -0
- package/dist/markdown/canonical-body.d.ts.map +1 -0
- package/dist/markdown/canonical-body.js +54 -0
- package/dist/markdown/canonical-body.js.map +1 -0
- package/dist/markdown/contracts.d.ts +66 -0
- package/dist/markdown/contracts.d.ts.map +1 -0
- package/dist/markdown/contracts.js +102 -0
- package/dist/markdown/contracts.js.map +1 -0
- package/dist/markdown/html-to-snodes.d.ts +26 -0
- package/dist/markdown/html-to-snodes.d.ts.map +1 -0
- package/dist/markdown/html-to-snodes.js +118 -0
- package/dist/markdown/html-to-snodes.js.map +1 -0
- package/dist/markdown/remark.d.ts +35 -0
- package/dist/markdown/remark.d.ts.map +1 -0
- package/dist/markdown/remark.js +66 -0
- package/dist/markdown/remark.js.map +1 -0
- package/dist/markdown/satteri.d.ts +19 -0
- package/dist/markdown/satteri.d.ts.map +1 -0
- package/dist/markdown/satteri.js +120 -0
- package/dist/markdown/satteri.js.map +1 -0
- package/dist/markdown/serialize.d.ts +60 -0
- package/dist/markdown/serialize.d.ts.map +1 -0
- package/dist/markdown/serialize.js +264 -0
- package/dist/markdown/serialize.js.map +1 -0
- package/dist/markdown/splice.d.ts +32 -0
- package/dist/markdown/splice.d.ts.map +1 -0
- package/dist/markdown/splice.js +44 -0
- package/dist/markdown/splice.js.map +1 -0
- package/dist/markdown/stamp.d.ts +68 -0
- package/dist/markdown/stamp.d.ts.map +1 -0
- package/dist/markdown/stamp.js +131 -0
- package/dist/markdown/stamp.js.map +1 -0
- package/dist/runtime/auth/rate-limiter.d.ts.map +1 -1
- package/dist/runtime/auth/rate-limiter.js +5 -0
- package/dist/runtime/auth/rate-limiter.js.map +1 -1
- package/dist/runtime/auth/session.d.ts +2 -3
- package/dist/runtime/auth/session.d.ts.map +1 -1
- package/dist/runtime/auth/session.js +52 -21
- package/dist/runtime/auth/session.js.map +1 -1
- package/dist/runtime/content.d.ts.map +1 -1
- package/dist/runtime/content.js +4 -2
- package/dist/runtime/content.js.map +1 -1
- package/dist/runtime/middleware.d.ts.map +1 -1
- package/dist/runtime/middleware.js +21 -4
- package/dist/runtime/middleware.js.map +1 -1
- package/dist/runtime/mutations/contracts.d.ts +25 -1
- package/dist/runtime/mutations/contracts.d.ts.map +1 -1
- package/dist/runtime/mutations/contracts.js +67 -0
- package/dist/runtime/mutations/contracts.js.map +1 -1
- package/dist/runtime/mutations/engine.d.ts +6 -1
- package/dist/runtime/mutations/engine.d.ts.map +1 -1
- package/dist/runtime/mutations/engine.js +151 -42
- package/dist/runtime/mutations/engine.js.map +1 -1
- package/dist/runtime/providers.d.ts +9 -0
- package/dist/runtime/providers.d.ts.map +1 -1
- package/dist/runtime/providers.js +6 -0
- package/dist/runtime/providers.js.map +1 -1
- package/dist/runtime/publish.d.ts +15 -1
- package/dist/runtime/publish.d.ts.map +1 -1
- package/dist/runtime/publish.js +79 -4
- package/dist/runtime/publish.js.map +1 -1
- package/dist/runtime/rebuild-webhook.d.ts +29 -0
- package/dist/runtime/rebuild-webhook.d.ts.map +1 -0
- package/dist/runtime/rebuild-webhook.js +37 -0
- package/dist/runtime/rebuild-webhook.js.map +1 -0
- package/dist/runtime/request-context.d.ts +9 -1
- package/dist/runtime/request-context.d.ts.map +1 -1
- package/dist/runtime/request-context.js.map +1 -1
- package/dist/runtime/rewrite.d.ts +18 -0
- package/dist/runtime/rewrite.d.ts.map +1 -1
- package/dist/runtime/rewrite.js +118 -4
- package/dist/runtime/rewrite.js.map +1 -1
- package/dist/runtime/rich-allowlist.d.ts +3 -2
- package/dist/runtime/rich-allowlist.d.ts.map +1 -1
- package/dist/runtime/rich-allowlist.js +14 -4
- package/dist/runtime/rich-allowlist.js.map +1 -1
- package/dist/runtime/routes/_helpers.d.ts +7 -0
- package/dist/runtime/routes/_helpers.d.ts.map +1 -1
- package/dist/runtime/routes/_helpers.js +34 -1
- package/dist/runtime/routes/_helpers.js.map +1 -1
- package/dist/runtime/routes/admin-entry.d.ts +1 -0
- package/dist/runtime/routes/admin-entry.d.ts.map +1 -1
- package/dist/runtime/routes/admin-entry.js +3 -2
- package/dist/runtime/routes/admin-entry.js.map +1 -1
- package/dist/runtime/routes/admin.d.ts +1 -0
- package/dist/runtime/routes/admin.d.ts.map +1 -1
- package/dist/runtime/routes/admin.js +20 -13
- package/dist/runtime/routes/admin.js.map +1 -1
- package/dist/runtime/routes/auth-login.d.ts +1 -0
- package/dist/runtime/routes/auth-login.d.ts.map +1 -1
- package/dist/runtime/routes/auth-login.js +1 -0
- package/dist/runtime/routes/auth-login.js.map +1 -1
- package/dist/runtime/routes/auth-logout.d.ts +1 -0
- package/dist/runtime/routes/auth-logout.d.ts.map +1 -1
- package/dist/runtime/routes/auth-logout.js +1 -0
- package/dist/runtime/routes/auth-logout.js.map +1 -1
- package/dist/runtime/routes/auth-session.d.ts +1 -0
- package/dist/runtime/routes/auth-session.d.ts.map +1 -1
- package/dist/runtime/routes/auth-session.js +1 -0
- package/dist/runtime/routes/auth-session.js.map +1 -1
- package/dist/runtime/routes/collection-list.d.ts +1 -0
- package/dist/runtime/routes/collection-list.d.ts.map +1 -1
- package/dist/runtime/routes/collection-list.js +3 -2
- package/dist/runtime/routes/collection-list.js.map +1 -1
- package/dist/runtime/routes/collections-metadata.d.ts +1 -0
- package/dist/runtime/routes/collections-metadata.d.ts.map +1 -1
- package/dist/runtime/routes/collections-metadata.js +3 -2
- package/dist/runtime/routes/collections-metadata.js.map +1 -1
- package/dist/runtime/routes/draft.d.ts +5 -0
- package/dist/runtime/routes/draft.d.ts.map +1 -1
- package/dist/runtime/routes/draft.js +18 -1
- package/dist/runtime/routes/draft.js.map +1 -1
- package/dist/runtime/routes/editor-assets.d.ts +1 -0
- package/dist/runtime/routes/editor-assets.d.ts.map +1 -1
- package/dist/runtime/routes/editor-assets.js +1 -0
- package/dist/runtime/routes/editor-assets.js.map +1 -1
- package/dist/runtime/routes/entries.d.ts +1 -0
- package/dist/runtime/routes/entries.d.ts.map +1 -1
- package/dist/runtime/routes/entries.js +6 -4
- package/dist/runtime/routes/entries.js.map +1 -1
- package/dist/runtime/routes/history.d.ts +1 -0
- package/dist/runtime/routes/history.d.ts.map +1 -1
- package/dist/runtime/routes/history.js +50 -24
- package/dist/runtime/routes/history.js.map +1 -1
- package/dist/runtime/routes/mutate.d.ts +1 -0
- package/dist/runtime/routes/mutate.d.ts.map +1 -1
- package/dist/runtime/routes/mutate.js +42 -7
- package/dist/runtime/routes/mutate.js.map +1 -1
- package/dist/runtime/routes/publish.d.ts +1 -0
- package/dist/runtime/routes/publish.d.ts.map +1 -1
- package/dist/runtime/routes/publish.js +15 -3
- package/dist/runtime/routes/publish.js.map +1 -1
- package/dist/runtime/routes/schema.d.ts +1 -0
- package/dist/runtime/routes/schema.d.ts.map +1 -1
- package/dist/runtime/routes/schema.js +7 -3
- package/dist/runtime/routes/schema.js.map +1 -1
- package/dist/runtime/routes/studio-home.d.ts +1 -0
- package/dist/runtime/routes/studio-home.d.ts.map +1 -1
- package/dist/runtime/routes/studio-home.js +10 -9
- package/dist/runtime/routes/studio-home.js.map +1 -1
- package/dist/runtime/routes/theme-stylesheet.d.ts +1 -0
- package/dist/runtime/routes/theme-stylesheet.d.ts.map +1 -1
- package/dist/runtime/routes/theme-stylesheet.js +12 -1
- package/dist/runtime/routes/theme-stylesheet.js.map +1 -1
- package/dist/runtime/routes/upload.d.ts +1 -0
- package/dist/runtime/routes/upload.d.ts.map +1 -1
- package/dist/runtime/routes/upload.js +1 -0
- package/dist/runtime/routes/upload.js.map +1 -1
- package/dist/runtime/static-bake.d.ts +20 -0
- package/dist/runtime/static-bake.d.ts.map +1 -0
- package/dist/runtime/static-bake.js +58 -0
- package/dist/runtime/static-bake.js.map +1 -0
- package/dist/runtime/storage/atomic-write.d.ts.map +1 -1
- package/dist/runtime/storage/atomic-write.js +8 -3
- package/dist/runtime/storage/atomic-write.js.map +1 -1
- package/dist/runtime/storage/frontmatter-codec.d.ts.map +1 -1
- package/dist/runtime/storage/frontmatter-codec.js +8 -0
- package/dist/runtime/storage/frontmatter-codec.js.map +1 -1
- package/dist/runtime/storage/markdown-adapter.d.ts +19 -0
- package/dist/runtime/storage/markdown-adapter.d.ts.map +1 -1
- package/dist/runtime/storage/markdown-adapter.js +41 -0
- package/dist/runtime/storage/markdown-adapter.js.map +1 -1
- package/dist/runtime/storage/session-overlay-adapter.d.ts +8 -0
- package/dist/runtime/storage/session-overlay-adapter.d.ts.map +1 -1
- package/dist/runtime/storage/session-overlay-adapter.js +24 -2
- package/dist/runtime/storage/session-overlay-adapter.js.map +1 -1
- package/dist/runtime/utils.d.ts +10 -0
- package/dist/runtime/utils.d.ts.map +1 -1
- package/dist/runtime/utils.js +16 -0
- package/dist/runtime/utils.js.map +1 -1
- package/dist/runtime/views/studio-layout.d.ts.map +1 -1
- package/dist/runtime/views/studio-layout.js +11 -10
- package/dist/runtime/views/studio-layout.js.map +1 -1
- package/dist/types.d.ts +40 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/static/cms/admin-entry.js +36 -2
- package/static/cms/dev-toolbar/app.js +3 -1
- package/static/cms/editor/config.js +10 -0
- package/static/cms/editor/guards.js +1 -1
- package/static/cms/editor/helpers.js +5 -1
- package/static/cms/editor/highlight.js +4 -6
- package/static/cms/editor/image-edit.js +41 -11
- package/static/cms/editor/image.css +7 -1
- package/static/cms/editor/md-block-edit.js +176 -0
- package/static/cms/editor/md-serialize.js +301 -0
- package/static/cms/editor/panel.css +69 -25
- package/static/cms/editor/panel.js +39 -0
- package/static/cms/editor/rich-toolbar.js +4 -3
- package/static/cms/editor/sanitize.js +10 -5
- package/static/cms/editor/sync.js +1 -1
- package/static/cms/editor/text-edit.js +68 -17
- package/static/cms/editor/text-link-interactions.js +4 -4
- package/static/cms/editor/toast.css +114 -2
- package/static/cms/editor/toast.js +121 -22
- package/static/cms/editor/tokens-and-text.css +64 -3
- package/static/cms/editor/toolbar.css +76 -8
- package/static/cms/editor/toolbar.js +173 -64
- package/static/cms/editor.js +16 -3
- package/static/cms/studio-login.css +70 -0
- package/static/cms/studio.css +80 -13
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { toggleHighlight } from './highlight.js';
|
|
2
|
-
import { buildCmsUrl } from './config.js';
|
|
2
|
+
import { buildCmsUrl, isStaticDelivery } from './config.js';
|
|
3
3
|
|
|
4
4
|
const PREVIEW_COOKIE = 'caret_preview';
|
|
5
5
|
|
|
@@ -13,6 +13,26 @@ function setPreviewCookie(on) {
|
|
|
13
13
|
: `${PREVIEW_COOKIE}=; path=/; Max-Age=0; SameSite=Lax`;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/** Static delivery always drafts — turn preview on once so saves stay unpublished. */
|
|
17
|
+
export function ensureStaticPreviewMode() {
|
|
18
|
+
if (!isStaticDelivery()) return false;
|
|
19
|
+
if (previewActive()) return false;
|
|
20
|
+
setPreviewCookie(true);
|
|
21
|
+
window.location.reload();
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Align preview cookie with delivery mode (static on, server off). */
|
|
26
|
+
export function normalizePreviewForDelivery() {
|
|
27
|
+
if (isStaticDelivery()) return ensureStaticPreviewMode();
|
|
28
|
+
if (previewActive()) {
|
|
29
|
+
setPreviewCookie(false);
|
|
30
|
+
window.location.reload();
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
16
36
|
function getToolbarNavLinks(pagePath) {
|
|
17
37
|
const links = [];
|
|
18
38
|
const headerNav = document.querySelector('#main-header nav');
|
|
@@ -29,7 +49,30 @@ function getToolbarNavLinks(pagePath) {
|
|
|
29
49
|
return links;
|
|
30
50
|
}
|
|
31
51
|
|
|
32
|
-
function
|
|
52
|
+
function renderDraftControls(staticDelivery) {
|
|
53
|
+
// Static delivery is always drafting, so the controls stay visible. Server
|
|
54
|
+
// delivery saves field edits straight to the site, but inline body-block
|
|
55
|
+
// edits are ALWAYS drafts (they splice into source files only at publish), so
|
|
56
|
+
// the controls render here too — hidden until at least one draft is pending
|
|
57
|
+
// (toggled by refreshDraftControls) so the toolbar stays quiet otherwise.
|
|
58
|
+
const publishTitle = staticDelivery
|
|
59
|
+
? 'Publish drafts; static visitors update after rebuild and deploy'
|
|
60
|
+
: 'Publish your body-block drafts into the source files';
|
|
61
|
+
return `
|
|
62
|
+
<span class="cms-draft-controls"${staticDelivery ? '' : ' hidden'}>
|
|
63
|
+
<button class="cms-publish-btn cms-go-live-btn" type="button" title="${publishTitle}">
|
|
64
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19V5"/><polyline points="5 12 12 5 19 12"/></svg>
|
|
65
|
+
<span class="cms-go-live-label">Publish</span>
|
|
66
|
+
</button>
|
|
67
|
+
<button class="cms-discard-btn" type="button" title="Discard your draft changes">
|
|
68
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
|
69
|
+
Discard
|
|
70
|
+
</button>
|
|
71
|
+
<div class="cms-toolbar-divider"></div>
|
|
72
|
+
</span>`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function renderToolbar(navLinks, staticDelivery) {
|
|
33
76
|
const navLinksHtml = navLinks
|
|
34
77
|
.map(
|
|
35
78
|
({ href, label, isActive }) =>
|
|
@@ -37,36 +80,30 @@ function renderToolbar(navLinks) {
|
|
|
37
80
|
)
|
|
38
81
|
.join('');
|
|
39
82
|
|
|
83
|
+
const badgeLabel = staticDelivery ? 'Draft' : 'Live';
|
|
84
|
+
const badgeHint = staticDelivery
|
|
85
|
+
? 'Draft preview; visitors update after publish, rebuild, and deploy'
|
|
86
|
+
: 'Changes save directly to your site';
|
|
87
|
+
|
|
40
88
|
const toolbar = document.createElement('div');
|
|
41
89
|
toolbar.className = 'cms-toolbar';
|
|
90
|
+
toolbar.dataset.delivery = staticDelivery ? 'static' : 'server';
|
|
42
91
|
toolbar.innerHTML = `
|
|
43
92
|
<div class="cms-toolbar-inner">
|
|
44
93
|
<div class="cms-toolbar-left">
|
|
45
|
-
<div class="cms-toolbar-badge">
|
|
94
|
+
<div class="cms-toolbar-badge" title="${badgeHint}">
|
|
46
95
|
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2L2 9l10 13 10-13L12 2zm0 3.84L18.26 9 12 17.65 5.74 9 12 5.84z"/></svg>
|
|
47
|
-
<span class="cms-toolbar-badge-text"
|
|
96
|
+
<span class="cms-toolbar-badge-text">${badgeLabel}</span>
|
|
48
97
|
</div>
|
|
49
98
|
<div class="cms-toolbar-divider"></div>
|
|
50
|
-
<div class="cms-status-group">
|
|
51
|
-
<span class="cms-status-dot cms-status-idle"></span>
|
|
99
|
+
<div class="cms-status-group" role="status" aria-live="polite">
|
|
100
|
+
<span class="cms-status-dot cms-status-idle" aria-hidden="true"></span>
|
|
52
101
|
<span class="cms-status-text">Ready</span>
|
|
53
102
|
</div>
|
|
54
103
|
</div>
|
|
55
104
|
${navLinksHtml ? `<div class="cms-toolbar-nav">${navLinksHtml}</div>` : ''}
|
|
56
105
|
<div class="cms-toolbar-right">
|
|
57
|
-
|
|
58
|
-
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
|
|
59
|
-
Preview
|
|
60
|
-
</button>
|
|
61
|
-
<button class="cms-publish-btn" type="button" title="Publish your draft changes to the live site" hidden>
|
|
62
|
-
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19V5"/><polyline points="5 12 12 5 19 12"/></svg>
|
|
63
|
-
Publish
|
|
64
|
-
</button>
|
|
65
|
-
<button class="cms-discard-btn" type="button" title="Discard your draft changes" hidden>
|
|
66
|
-
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
|
67
|
-
Discard
|
|
68
|
-
</button>
|
|
69
|
-
<div class="cms-toolbar-divider"></div>
|
|
106
|
+
${renderDraftControls(staticDelivery)}
|
|
70
107
|
<button type="button" class="cms-studio-btn" title="Toggle Content Studio panel">
|
|
71
108
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="9" y1="9" x2="21" y2="9"/></svg>
|
|
72
109
|
Studio
|
|
@@ -79,12 +116,12 @@ function renderToolbar(navLinks) {
|
|
|
79
116
|
<div class="cms-toolbar-divider"></div>
|
|
80
117
|
<button class="cms-highlight-btn" type="button" title="Show all editable regions">
|
|
81
118
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
|
|
82
|
-
Show
|
|
119
|
+
<span class="cms-highlight-label">Show all</span>
|
|
83
120
|
</button>
|
|
84
121
|
<div class="cms-toolbar-divider"></div>
|
|
85
|
-
<button class="cms-exit-btn" type="button">
|
|
122
|
+
<button class="cms-exit-btn" type="button" title="Sign out">
|
|
86
123
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
|
|
87
|
-
|
|
124
|
+
Sign out
|
|
88
125
|
</button>
|
|
89
126
|
</div>
|
|
90
127
|
</div>
|
|
@@ -94,8 +131,9 @@ function renderToolbar(navLinks) {
|
|
|
94
131
|
}
|
|
95
132
|
|
|
96
133
|
export function mountToolbar({ showToast, clearDirty, onLogout }) {
|
|
134
|
+
const staticDelivery = isStaticDelivery();
|
|
97
135
|
const navLinks = getToolbarNavLinks(window.location.pathname);
|
|
98
|
-
const toolbar = renderToolbar(navLinks);
|
|
136
|
+
const toolbar = renderToolbar(navLinks, staticDelivery);
|
|
99
137
|
|
|
100
138
|
const statusDot = toolbar.querySelector('.cms-status-dot');
|
|
101
139
|
const statusText = toolbar.querySelector('.cms-status-text');
|
|
@@ -114,40 +152,6 @@ export function mountToolbar({ showToast, clearDirty, onLogout }) {
|
|
|
114
152
|
toggleHighlight(highlightBtn, showToast),
|
|
115
153
|
);
|
|
116
154
|
|
|
117
|
-
toolbar.querySelector('.cms-exit-btn')?.addEventListener('click', async () => {
|
|
118
|
-
clearDirty();
|
|
119
|
-
await onLogout();
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
// --- Draft preview + publish/discard ---
|
|
123
|
-
const previewBtn = toolbar.querySelector('.cms-preview-btn');
|
|
124
|
-
const publishBtn = toolbar.querySelector('.cms-publish-btn');
|
|
125
|
-
const discardBtn = toolbar.querySelector('.cms-discard-btn');
|
|
126
|
-
const badgeText = toolbar.querySelector('.cms-toolbar-badge-text');
|
|
127
|
-
|
|
128
|
-
function reflectPreview() {
|
|
129
|
-
const on = previewActive();
|
|
130
|
-
previewBtn?.classList.toggle('cms-preview-btn-active', on);
|
|
131
|
-
if (publishBtn) publishBtn.hidden = !on;
|
|
132
|
-
if (discardBtn) discardBtn.hidden = !on;
|
|
133
|
-
if (badgeText) badgeText.textContent = on ? 'Draft' : 'Editor';
|
|
134
|
-
const label = previewBtn
|
|
135
|
-
&& Array.from(previewBtn.childNodes).find((n) => n.nodeType === 3 && n.textContent?.trim());
|
|
136
|
-
if (label) label.textContent = on ? ' Previewing' : ' Preview';
|
|
137
|
-
}
|
|
138
|
-
reflectPreview();
|
|
139
|
-
|
|
140
|
-
previewBtn?.addEventListener('click', () => {
|
|
141
|
-
const turningOn = !previewActive();
|
|
142
|
-
setPreviewCookie(turningOn);
|
|
143
|
-
showToast(
|
|
144
|
-
turningOn ? 'Draft preview on — your edits stay unpublished' : 'Draft preview off',
|
|
145
|
-
'success',
|
|
146
|
-
);
|
|
147
|
-
// Reload so the server installs (or drops) the per-editor draft overlay.
|
|
148
|
-
window.location.reload();
|
|
149
|
-
});
|
|
150
|
-
|
|
151
155
|
async function draftRequest(method, path) {
|
|
152
156
|
return fetch(buildCmsUrl(path), {
|
|
153
157
|
method,
|
|
@@ -157,30 +161,121 @@ export function mountToolbar({ showToast, clearDirty, onLogout }) {
|
|
|
157
161
|
});
|
|
158
162
|
}
|
|
159
163
|
|
|
160
|
-
|
|
161
|
-
|
|
164
|
+
async function fetchDraftCount() {
|
|
165
|
+
try {
|
|
166
|
+
const res = await fetch(buildCmsUrl('/draft'), {
|
|
167
|
+
credentials: 'same-origin',
|
|
168
|
+
headers: { 'x-caret-request': '1' },
|
|
169
|
+
});
|
|
170
|
+
if (!res.ok) return 0;
|
|
171
|
+
const data = await res.json();
|
|
172
|
+
return typeof data.count === 'number' ? data.count : 0;
|
|
173
|
+
} catch {
|
|
174
|
+
return 0;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function publishDrafts() {
|
|
162
179
|
setStatus('saving', 'Publishing…');
|
|
163
180
|
try {
|
|
164
181
|
const res = await draftRequest('POST', '/publish');
|
|
165
182
|
if (!res.ok) throw new Error('publish failed');
|
|
166
183
|
const data = await res.json();
|
|
167
184
|
const n = Array.isArray(data.published) ? data.published.length : 0;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
185
|
+
const conflicts = Array.isArray(data.conflicts) ? data.conflicts.length : 0;
|
|
186
|
+
if (conflicts > 0) {
|
|
187
|
+
// Stale body drafts: preserved server-side, NOT published. Reload so
|
|
188
|
+
// fresh stamps arrive and the editor can re-apply them.
|
|
189
|
+
setStatus('error', 'Some drafts conflicted');
|
|
190
|
+
showToast(
|
|
191
|
+
n > 0
|
|
192
|
+
? `Published ${n} change(s); ${conflicts} draft(s) conflicted with newer content — reloading so you can re-apply.`
|
|
193
|
+
: `${conflicts} draft(s) conflicted with newer content — reloading so you can re-apply.`,
|
|
194
|
+
'error',
|
|
195
|
+
);
|
|
196
|
+
window.location.reload();
|
|
197
|
+
return n > 0;
|
|
198
|
+
}
|
|
199
|
+
if (data.rebuild?.triggered && data.rebuild.ok === false) {
|
|
200
|
+
setStatus('error', 'Published, deploy failed');
|
|
201
|
+
showToast(
|
|
202
|
+
'Changes are published, but the deploy webhook failed. Check your CI settings.',
|
|
203
|
+
'error',
|
|
204
|
+
);
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
if (data.rebuild?.triggered) {
|
|
208
|
+
showToast(
|
|
209
|
+
n > 0
|
|
210
|
+
? `Published ${n} change(s) — rebuild started`
|
|
211
|
+
: 'Your site is rebuilding',
|
|
212
|
+
'success',
|
|
213
|
+
);
|
|
214
|
+
} else {
|
|
215
|
+
showToast(
|
|
216
|
+
n > 0
|
|
217
|
+
? `Published ${n} change(s) — rebuild and deploy to update visitors`
|
|
218
|
+
: 'Already up to date',
|
|
219
|
+
'success',
|
|
220
|
+
);
|
|
221
|
+
}
|
|
171
222
|
window.location.reload();
|
|
223
|
+
return true;
|
|
172
224
|
} catch {
|
|
173
225
|
setStatus('error', 'Publish failed');
|
|
174
226
|
showToast('Publish failed', 'error');
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
toolbar.querySelector('.cms-exit-btn')?.addEventListener('click', async () => {
|
|
232
|
+
if (staticDelivery) {
|
|
233
|
+
const count = await fetchDraftCount();
|
|
234
|
+
if (count > 0) {
|
|
235
|
+
const goLive = window.confirm(
|
|
236
|
+
`You have ${count} unpublished change${count === 1 ? '' : 's'}. Publish before signing out?`,
|
|
237
|
+
);
|
|
238
|
+
if (goLive) {
|
|
239
|
+
await publishDrafts();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const discard = window.confirm(
|
|
243
|
+
'Discard unpublished changes and sign out?',
|
|
244
|
+
);
|
|
245
|
+
if (!discard) return;
|
|
246
|
+
try {
|
|
247
|
+
const res = await draftRequest('DELETE', '/draft');
|
|
248
|
+
if (!res.ok) throw new Error('discard failed');
|
|
249
|
+
} catch {
|
|
250
|
+
showToast('Could not discard drafts', 'error');
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
175
254
|
}
|
|
255
|
+
|
|
256
|
+
clearDirty();
|
|
257
|
+
await onLogout();
|
|
176
258
|
});
|
|
177
259
|
|
|
178
|
-
|
|
260
|
+
const publishBtn = toolbar.querySelector('.cms-publish-btn');
|
|
261
|
+
publishBtn?.addEventListener('click', async () => {
|
|
262
|
+
if (
|
|
263
|
+
!window.confirm(
|
|
264
|
+
staticDelivery
|
|
265
|
+
? 'Publish all draft changes? Static visitors update after rebuild and deploy.'
|
|
266
|
+
: 'Publish all your draft changes to the live site?',
|
|
267
|
+
)
|
|
268
|
+
) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
await publishDrafts();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
toolbar.querySelector('.cms-discard-btn')?.addEventListener('click', async () => {
|
|
179
275
|
if (!window.confirm('Discard all your draft changes? This cannot be undone.')) return;
|
|
180
276
|
try {
|
|
181
277
|
const res = await draftRequest('DELETE', '/draft');
|
|
182
278
|
if (!res.ok) throw new Error('discard failed');
|
|
183
|
-
setPreviewCookie(false);
|
|
184
279
|
showToast('Draft discarded', 'success');
|
|
185
280
|
window.location.reload();
|
|
186
281
|
} catch {
|
|
@@ -188,6 +283,20 @@ export function mountToolbar({ showToast, clearDirty, onLogout }) {
|
|
|
188
283
|
}
|
|
189
284
|
});
|
|
190
285
|
|
|
286
|
+
// Server delivery hides the draft controls until a body draft exists. Field
|
|
287
|
+
// edits save directly (no draft), so the count reflects pending body blocks.
|
|
288
|
+
// Refresh on mount and whenever a body block is drafted; publish/discard
|
|
289
|
+
// reload the page, so mount re-evaluates from scratch.
|
|
290
|
+
const draftControls = toolbar.querySelector('.cms-draft-controls');
|
|
291
|
+
async function refreshDraftControls() {
|
|
292
|
+
if (staticDelivery || !draftControls) return;
|
|
293
|
+
draftControls.hidden = (await fetchDraftCount()) === 0;
|
|
294
|
+
}
|
|
295
|
+
if (!staticDelivery) {
|
|
296
|
+
refreshDraftControls();
|
|
297
|
+
window.addEventListener('cms:draftSaved', refreshDraftControls);
|
|
298
|
+
}
|
|
299
|
+
|
|
191
300
|
const studioButton = toolbar.querySelector('.cms-studio-btn');
|
|
192
301
|
const mapButton = toolbar.querySelector('.cms-map-btn');
|
|
193
302
|
return {
|
package/static/cms/editor.js
CHANGED
|
@@ -16,7 +16,7 @@ import { createSaveField } from './editor/save-queue.js';
|
|
|
16
16
|
import { compressImage, updateEmblaCarousel } from './editor/image-utils.js';
|
|
17
17
|
import { createToast } from './editor/toast.js';
|
|
18
18
|
import { mountEditorGuards } from './editor/guards.js';
|
|
19
|
-
import { mountToolbar } from './editor/toolbar.js';
|
|
19
|
+
import { mountToolbar, normalizePreviewForDelivery } from './editor/toolbar.js';
|
|
20
20
|
import { mountStudioPanel } from './editor/panel.js';
|
|
21
21
|
import { mountStudioSync } from './editor/sync.js';
|
|
22
22
|
import { mountTextEditors } from './editor/text-edit.js';
|
|
@@ -24,6 +24,7 @@ import { mountImageEditors } from './editor/image-edit.js';
|
|
|
24
24
|
import { mountSectionControls } from './editor/section-controls.js';
|
|
25
25
|
import { mountContentMap } from './editor/content-map.js';
|
|
26
26
|
import { mountRichToolbar } from './editor/rich-toolbar.js';
|
|
27
|
+
import { mountMdBlockEditors } from './editor/md-block-edit.js';
|
|
27
28
|
import { mountLinkFollowAffordances } from './editor/link-follow.js';
|
|
28
29
|
import { revealAndFade } from './editor/highlight.js';
|
|
29
30
|
import { hydrateStega } from './editor/stega-hydrate.js';
|
|
@@ -33,8 +34,13 @@ function redirectToEditorLogin() {
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
function boot() {
|
|
37
|
+
if (normalizePreviewForDelivery()) return;
|
|
38
|
+
|
|
36
39
|
const state = {
|
|
37
|
-
|
|
40
|
+
// Every element with unsaved edits. A Set (not a single element) so a save
|
|
41
|
+
// completing on field A can't clear the dirty flag for field B the user has
|
|
42
|
+
// since moved to — that would let the beforeunload guard miss B's edits.
|
|
43
|
+
dirtyEls: new Set(),
|
|
38
44
|
linkPopupEl: null,
|
|
39
45
|
};
|
|
40
46
|
|
|
@@ -50,7 +56,7 @@ function boot() {
|
|
|
50
56
|
const { studioButton, mapButton, setStatus } = mountToolbar({
|
|
51
57
|
showToast,
|
|
52
58
|
clearDirty: () => {
|
|
53
|
-
state.
|
|
59
|
+
state.dirtyEls.clear();
|
|
54
60
|
},
|
|
55
61
|
onLogout: async () => {
|
|
56
62
|
await fetch(buildCmsUrl('/auth/logout'), { method: 'POST' });
|
|
@@ -92,6 +98,13 @@ function boot() {
|
|
|
92
98
|
richToolbarMounted = true;
|
|
93
99
|
}
|
|
94
100
|
|
|
101
|
+
mountMdBlockEditors({
|
|
102
|
+
state,
|
|
103
|
+
flash,
|
|
104
|
+
showToast,
|
|
105
|
+
onUnauthorized: redirectToEditorLogin,
|
|
106
|
+
});
|
|
107
|
+
|
|
95
108
|
mountImageEditors({
|
|
96
109
|
parseCaretAttr,
|
|
97
110
|
flash,
|
|
@@ -298,3 +298,73 @@
|
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
.studio-login-footer a:hover { opacity: 0.7; }
|
|
301
|
+
|
|
302
|
+
/* Setup helper (no password / dev password) */
|
|
303
|
+
.studio-login-setup {
|
|
304
|
+
margin-top: 1.25rem;
|
|
305
|
+
padding: 1rem 1.1rem;
|
|
306
|
+
border: 1px solid rgba(127, 127, 127, 0.28);
|
|
307
|
+
border-radius: 10px;
|
|
308
|
+
text-align: left;
|
|
309
|
+
font-size: 0.85rem;
|
|
310
|
+
line-height: 1.55;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.studio-login-setup--dev {
|
|
314
|
+
margin-top: 1rem;
|
|
315
|
+
padding: 0.8rem 1rem;
|
|
316
|
+
border-style: dashed;
|
|
317
|
+
font-size: 0.82rem;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.studio-login-setup-title {
|
|
321
|
+
margin: 0 0 0.6rem;
|
|
322
|
+
font-weight: 600;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.studio-login-setup ol {
|
|
326
|
+
margin: 0;
|
|
327
|
+
padding-left: 1.2rem;
|
|
328
|
+
display: grid;
|
|
329
|
+
gap: 0.45rem;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.studio-login-setup-snippet-row {
|
|
333
|
+
display: flex;
|
|
334
|
+
gap: 0.4rem;
|
|
335
|
+
align-items: center;
|
|
336
|
+
margin-top: 0.35rem;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.studio-login-setup-snippet {
|
|
340
|
+
flex: 1;
|
|
341
|
+
padding: 0.4rem 0.55rem;
|
|
342
|
+
border-radius: 6px;
|
|
343
|
+
background: rgba(0, 0, 0, 0.28);
|
|
344
|
+
overflow: auto;
|
|
345
|
+
white-space: nowrap;
|
|
346
|
+
font-family: var(--studio-font-mono);
|
|
347
|
+
font-size: 0.85em;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.studio-login-setup-copy {
|
|
351
|
+
appearance: none;
|
|
352
|
+
cursor: pointer;
|
|
353
|
+
border: 1px solid currentColor;
|
|
354
|
+
background: transparent;
|
|
355
|
+
color: inherit;
|
|
356
|
+
border-radius: 6px;
|
|
357
|
+
padding: 0.4rem 0.65rem;
|
|
358
|
+
font-family: var(--studio-font);
|
|
359
|
+
font-size: 0.85em;
|
|
360
|
+
font-weight: 500;
|
|
361
|
+
line-height: 1.2;
|
|
362
|
+
flex-shrink: 0;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.studio-login-setup-copy:hover { opacity: 0.85; }
|
|
366
|
+
|
|
367
|
+
.studio-login-setup-link {
|
|
368
|
+
display: inline-block;
|
|
369
|
+
margin-top: 0.7rem;
|
|
370
|
+
}
|
package/static/cms/studio.css
CHANGED
|
@@ -55,7 +55,6 @@ a {
|
|
|
55
55
|
.studio-header {
|
|
56
56
|
display: flex;
|
|
57
57
|
align-items: center;
|
|
58
|
-
justify-content: space-between;
|
|
59
58
|
gap: var(--space-theme-md);
|
|
60
59
|
padding: var(--space-theme-md) var(--space-theme-lg);
|
|
61
60
|
border-bottom: 1px solid var(--studio-border);
|
|
@@ -65,6 +64,29 @@ a {
|
|
|
65
64
|
z-index: 10;
|
|
66
65
|
}
|
|
67
66
|
|
|
67
|
+
.studio-breadcrumb {
|
|
68
|
+
flex: 1;
|
|
69
|
+
min-width: 0;
|
|
70
|
+
display: flex;
|
|
71
|
+
align-items: center;
|
|
72
|
+
gap: var(--space-theme-sm);
|
|
73
|
+
font-family: var(--studio-font);
|
|
74
|
+
font-size: 11px;
|
|
75
|
+
font-weight: 500;
|
|
76
|
+
line-height: 1.2;
|
|
77
|
+
letter-spacing: 0.08em;
|
|
78
|
+
text-transform: uppercase;
|
|
79
|
+
color: var(--studio-text-dim);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.studio-header-nav {
|
|
83
|
+
display: flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
gap: var(--space-theme-xs);
|
|
86
|
+
margin-left: auto;
|
|
87
|
+
flex-shrink: 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
68
90
|
.studio-brand {
|
|
69
91
|
display: flex;
|
|
70
92
|
align-items: center;
|
|
@@ -73,24 +95,18 @@ a {
|
|
|
73
95
|
font-size: 1rem;
|
|
74
96
|
letter-spacing: 0.02em;
|
|
75
97
|
color: var(--studio-text);
|
|
98
|
+
transition: color 0.15s;
|
|
99
|
+
flex-shrink: 0;
|
|
76
100
|
}
|
|
77
101
|
|
|
102
|
+
.studio-brand:hover { color: var(--studio-text-muted); }
|
|
103
|
+
|
|
78
104
|
.studio-brand-mark {
|
|
79
105
|
width: 24px;
|
|
80
106
|
height: 24px;
|
|
81
107
|
color: var(--studio-accent);
|
|
82
108
|
}
|
|
83
109
|
|
|
84
|
-
.studio-breadcrumb {
|
|
85
|
-
display: flex;
|
|
86
|
-
align-items: center;
|
|
87
|
-
gap: var(--space-theme-sm);
|
|
88
|
-
font-size: 11px;
|
|
89
|
-
letter-spacing: 0.08em;
|
|
90
|
-
text-transform: uppercase;
|
|
91
|
-
color: var(--studio-text-dim);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
110
|
.studio-breadcrumb a:hover { color: var(--studio-text-muted); }
|
|
95
111
|
.studio-breadcrumb-sep { opacity: 0.4; }
|
|
96
112
|
.studio-breadcrumb-current { color: var(--studio-text-muted); }
|
|
@@ -101,6 +117,37 @@ a {
|
|
|
101
117
|
overflow-y: auto;
|
|
102
118
|
}
|
|
103
119
|
|
|
120
|
+
.studio-home {
|
|
121
|
+
max-width: 64rem;
|
|
122
|
+
margin: 0 auto;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.studio-page-intro {
|
|
126
|
+
text-align: center;
|
|
127
|
+
margin: 0 0 2.5rem;
|
|
128
|
+
font-size: 0.85rem;
|
|
129
|
+
color: var(--studio-text-dim);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.studio-home-grid {
|
|
133
|
+
display: grid;
|
|
134
|
+
gap: 1rem;
|
|
135
|
+
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@media (max-width: 640px) {
|
|
139
|
+
.studio-header {
|
|
140
|
+
flex-wrap: wrap;
|
|
141
|
+
padding: var(--space-theme-sm) var(--space-theme-md);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.studio-breadcrumb {
|
|
145
|
+
order: 3;
|
|
146
|
+
flex-basis: 100%;
|
|
147
|
+
padding-top: var(--space-theme-xs);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
104
151
|
/* ─── Drop zone ─────────────────────────────────────────────────────── */
|
|
105
152
|
|
|
106
153
|
.studio-dropzone {
|
|
@@ -259,9 +306,14 @@ textarea.studio-input {
|
|
|
259
306
|
/* ─── Header nav links ──────────────────────────────────────────────── */
|
|
260
307
|
|
|
261
308
|
.studio-nav-link {
|
|
309
|
+
display: inline-flex;
|
|
310
|
+
align-items: center;
|
|
262
311
|
padding: 4px 12px;
|
|
263
|
-
font-
|
|
264
|
-
|
|
312
|
+
font-family: var(--studio-font);
|
|
313
|
+
font-size: 11px;
|
|
314
|
+
font-weight: 500;
|
|
315
|
+
line-height: 1.2;
|
|
316
|
+
letter-spacing: 0.08em;
|
|
265
317
|
text-transform: uppercase;
|
|
266
318
|
text-decoration: none;
|
|
267
319
|
color: var(--studio-text-dim);
|
|
@@ -269,6 +321,21 @@ textarea.studio-input {
|
|
|
269
321
|
border-radius: var(--radius-theme-xs);
|
|
270
322
|
}
|
|
271
323
|
|
|
324
|
+
button.studio-nav-link {
|
|
325
|
+
appearance: none;
|
|
326
|
+
border: none;
|
|
327
|
+
cursor: pointer;
|
|
328
|
+
background: transparent;
|
|
329
|
+
margin: 0;
|
|
330
|
+
font-family: var(--studio-font);
|
|
331
|
+
font-size: 11px;
|
|
332
|
+
font-weight: 500;
|
|
333
|
+
line-height: 1.2;
|
|
334
|
+
letter-spacing: 0.08em;
|
|
335
|
+
text-transform: uppercase;
|
|
336
|
+
color: var(--studio-text-dim);
|
|
337
|
+
}
|
|
338
|
+
|
|
272
339
|
.studio-nav-link:hover {
|
|
273
340
|
color: var(--studio-text-muted);
|
|
274
341
|
background: var(--color-theme-surface-muted);
|