@caretcms/core 0.2.0 → 0.4.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 +195 -12
- package/dist/index.d.ts +26 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +113 -12
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +35 -3
- package/dist/loader.js.map +1 -1
- package/dist/markdown/paragraphs.d.ts +14 -0
- package/dist/markdown/paragraphs.d.ts.map +1 -0
- package/dist/markdown/paragraphs.js +61 -0
- package/dist/markdown/paragraphs.js.map +1 -0
- package/dist/markdown/remark.d.ts.map +1 -1
- package/dist/markdown/remark.js +1 -0
- package/dist/markdown/remark.js.map +1 -1
- package/dist/markdown/satteri.d.ts.map +1 -1
- package/dist/markdown/satteri.js +35 -9
- package/dist/markdown/satteri.js.map +1 -1
- package/dist/markdown/stamp.d.ts +0 -9
- package/dist/markdown/stamp.d.ts.map +1 -1
- package/dist/markdown/stamp.js +6 -0
- package/dist/markdown/stamp.js.map +1 -1
- package/dist/providers/deployment/github.d.ts +27 -0
- package/dist/providers/deployment/github.d.ts.map +1 -0
- package/dist/providers/deployment/github.js +180 -0
- package/dist/providers/deployment/github.js.map +1 -0
- package/dist/providers/deployment/simulated.d.ts +10 -0
- package/dist/providers/deployment/simulated.d.ts.map +1 -0
- package/dist/providers/deployment/simulated.js +29 -0
- package/dist/providers/deployment/simulated.js.map +1 -0
- package/dist/runtime/auth/session.d.ts +1 -0
- package/dist/runtime/auth/session.d.ts.map +1 -1
- package/dist/runtime/auth/session.js +16 -0
- package/dist/runtime/auth/session.js.map +1 -1
- package/dist/runtime/authorization.d.ts +11 -0
- package/dist/runtime/authorization.d.ts.map +1 -0
- package/dist/runtime/authorization.js +29 -0
- package/dist/runtime/authorization.js.map +1 -0
- package/dist/runtime/collection-policy.d.ts +11 -0
- package/dist/runtime/collection-policy.d.ts.map +1 -0
- package/dist/runtime/collection-policy.js +44 -0
- package/dist/runtime/collection-policy.js.map +1 -0
- package/dist/runtime/config.d.ts +3 -0
- package/dist/runtime/config.d.ts.map +1 -1
- package/dist/runtime/config.js +4 -1
- package/dist/runtime/config.js.map +1 -1
- package/dist/runtime/content-errors.d.ts +9 -0
- package/dist/runtime/content-errors.d.ts.map +1 -0
- package/dist/runtime/content-errors.js +17 -0
- package/dist/runtime/content-errors.js.map +1 -0
- package/dist/runtime/content.d.ts.map +1 -1
- package/dist/runtime/content.js +13 -4
- package/dist/runtime/content.js.map +1 -1
- package/dist/runtime/deployment-status.d.ts +14 -0
- package/dist/runtime/deployment-status.d.ts.map +1 -0
- package/dist/runtime/deployment-status.js +57 -0
- package/dist/runtime/deployment-status.js.map +1 -0
- package/dist/runtime/draft-state.d.ts +16 -0
- package/dist/runtime/draft-state.d.ts.map +1 -0
- package/dist/runtime/draft-state.js +29 -0
- package/dist/runtime/draft-state.js.map +1 -0
- package/dist/runtime/i18n.d.ts +97 -0
- package/dist/runtime/i18n.d.ts.map +1 -0
- package/dist/runtime/i18n.js +190 -0
- package/dist/runtime/i18n.js.map +1 -0
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/middleware.d.ts.map +1 -1
- package/dist/runtime/middleware.js +61 -4
- package/dist/runtime/middleware.js.map +1 -1
- package/dist/runtime/mutations/contracts.d.ts +12 -0
- package/dist/runtime/mutations/contracts.d.ts.map +1 -1
- package/dist/runtime/mutations/contracts.js +54 -4
- package/dist/runtime/mutations/contracts.js.map +1 -1
- package/dist/runtime/mutations/engine.d.ts +2 -0
- package/dist/runtime/mutations/engine.d.ts.map +1 -1
- package/dist/runtime/mutations/engine.js +220 -57
- package/dist/runtime/mutations/engine.js.map +1 -1
- package/dist/runtime/providers.d.ts +4 -1
- package/dist/runtime/providers.d.ts.map +1 -1
- package/dist/runtime/providers.js +18 -1
- package/dist/runtime/providers.js.map +1 -1
- package/dist/runtime/publish-recovery.d.ts +32 -0
- package/dist/runtime/publish-recovery.d.ts.map +1 -0
- package/dist/runtime/publish-recovery.js +98 -0
- package/dist/runtime/publish-recovery.js.map +1 -0
- package/dist/runtime/publish.d.ts +10 -4
- package/dist/runtime/publish.d.ts.map +1 -1
- package/dist/runtime/publish.js +105 -63
- package/dist/runtime/publish.js.map +1 -1
- package/dist/runtime/rebuild-webhook.d.ts +4 -0
- package/dist/runtime/rebuild-webhook.d.ts.map +1 -1
- package/dist/runtime/rebuild-webhook.js +12 -2
- package/dist/runtime/rebuild-webhook.js.map +1 -1
- package/dist/runtime/request-context.d.ts +7 -1
- package/dist/runtime/request-context.d.ts.map +1 -1
- package/dist/runtime/request-context.js +7 -1
- package/dist/runtime/request-context.js.map +1 -1
- package/dist/runtime/rewrite.d.ts.map +1 -1
- package/dist/runtime/rewrite.js +28 -2
- package/dist/runtime/rewrite.js.map +1 -1
- package/dist/runtime/rich-allowlist.js +1 -1
- package/dist/runtime/rich-allowlist.js.map +1 -1
- package/dist/runtime/routes/admin-entry.d.ts.map +1 -1
- package/dist/runtime/routes/admin-entry.js +129 -21
- package/dist/runtime/routes/admin-entry.js.map +1 -1
- package/dist/runtime/routes/admin.d.ts.map +1 -1
- package/dist/runtime/routes/admin.js +28 -14
- package/dist/runtime/routes/admin.js.map +1 -1
- package/dist/runtime/routes/auth-login.d.ts.map +1 -1
- package/dist/runtime/routes/auth-login.js +4 -0
- package/dist/runtime/routes/auth-login.js.map +1 -1
- package/dist/runtime/routes/auth-logout.d.ts.map +1 -1
- package/dist/runtime/routes/auth-logout.js +7 -2
- package/dist/runtime/routes/auth-logout.js.map +1 -1
- package/dist/runtime/routes/auth-session.d.ts.map +1 -1
- package/dist/runtime/routes/auth-session.js +10 -2
- package/dist/runtime/routes/auth-session.js.map +1 -1
- package/dist/runtime/routes/collection-list.d.ts.map +1 -1
- package/dist/runtime/routes/collection-list.js +213 -66
- package/dist/runtime/routes/collection-list.js.map +1 -1
- package/dist/runtime/routes/deployment.d.ts +5 -0
- package/dist/runtime/routes/deployment.d.ts.map +1 -0
- package/dist/runtime/routes/deployment.js +38 -0
- package/dist/runtime/routes/deployment.js.map +1 -0
- package/dist/runtime/routes/draft.d.ts.map +1 -1
- package/dist/runtime/routes/draft.js +10 -3
- package/dist/runtime/routes/draft.js.map +1 -1
- package/dist/runtime/routes/entries.d.ts.map +1 -1
- package/dist/runtime/routes/entries.js +87 -43
- package/dist/runtime/routes/entries.js.map +1 -1
- package/dist/runtime/routes/history.d.ts.map +1 -1
- package/dist/runtime/routes/history.js +40 -21
- package/dist/runtime/routes/history.js.map +1 -1
- package/dist/runtime/routes/mutate.d.ts.map +1 -1
- package/dist/runtime/routes/mutate.js +4 -1
- package/dist/runtime/routes/mutate.js.map +1 -1
- package/dist/runtime/routes/publish.d.ts.map +1 -1
- package/dist/runtime/routes/publish.js +127 -33
- package/dist/runtime/routes/publish.js.map +1 -1
- package/dist/runtime/routes/schema.d.ts.map +1 -1
- package/dist/runtime/routes/schema.js +11 -2
- package/dist/runtime/routes/schema.js.map +1 -1
- package/dist/runtime/routes/studio-home.d.ts.map +1 -1
- package/dist/runtime/routes/studio-home.js +41 -20
- package/dist/runtime/routes/studio-home.js.map +1 -1
- package/dist/runtime/routes/upload.d.ts.map +1 -1
- package/dist/runtime/routes/upload.js +3 -0
- package/dist/runtime/routes/upload.js.map +1 -1
- package/dist/runtime/schema-registry.d.ts +8 -1
- package/dist/runtime/schema-registry.d.ts.map +1 -1
- package/dist/runtime/schema-registry.js +49 -3
- package/dist/runtime/schema-registry.js.map +1 -1
- package/dist/runtime/storage/entry-commit.d.ts +8 -0
- package/dist/runtime/storage/entry-commit.d.ts.map +1 -0
- package/dist/runtime/storage/entry-commit.js +45 -0
- package/dist/runtime/storage/entry-commit.js.map +1 -0
- package/dist/runtime/storage/filesystem-adapter.d.ts +5 -0
- package/dist/runtime/storage/filesystem-adapter.d.ts.map +1 -1
- package/dist/runtime/storage/filesystem-adapter.js +12 -3
- package/dist/runtime/storage/filesystem-adapter.js.map +1 -1
- package/dist/runtime/storage/frontmatter-codec.d.ts +5 -23
- package/dist/runtime/storage/frontmatter-codec.d.ts.map +1 -1
- package/dist/runtime/storage/frontmatter-codec.js +156 -13
- package/dist/runtime/storage/frontmatter-codec.js.map +1 -1
- package/dist/runtime/storage/in-memory-adapter.d.ts +7 -0
- package/dist/runtime/storage/in-memory-adapter.d.ts.map +1 -1
- package/dist/runtime/storage/in-memory-adapter.js +6 -0
- package/dist/runtime/storage/in-memory-adapter.js.map +1 -1
- package/dist/runtime/storage/markdown-adapter.d.ts.map +1 -1
- package/dist/runtime/storage/markdown-adapter.js +18 -20
- package/dist/runtime/storage/markdown-adapter.js.map +1 -1
- package/dist/runtime/storage/session-overlay-adapter.d.ts +12 -4
- package/dist/runtime/storage/session-overlay-adapter.d.ts.map +1 -1
- package/dist/runtime/storage/session-overlay-adapter.js +100 -13
- package/dist/runtime/storage/session-overlay-adapter.js.map +1 -1
- package/dist/runtime/storage/sidecar-meta-store.d.ts +5 -0
- package/dist/runtime/storage/sidecar-meta-store.d.ts.map +1 -1
- package/dist/runtime/storage/sidecar-meta-store.js +48 -5
- package/dist/runtime/storage/sidecar-meta-store.js.map +1 -1
- package/dist/runtime/themes/preset-tokens.js +4 -4
- package/dist/runtime/themes/preset-tokens.js.map +1 -1
- package/dist/runtime/utils.d.ts.map +1 -1
- package/dist/runtime/utils.js +3 -4
- package/dist/runtime/utils.js.map +1 -1
- package/dist/runtime/views/studio-layout.d.ts +3 -0
- package/dist/runtime/views/studio-layout.d.ts.map +1 -1
- package/dist/runtime/views/studio-layout.js +36 -13
- package/dist/runtime/views/studio-layout.js.map +1 -1
- package/dist/schema-utils.d.ts +7 -0
- package/dist/schema-utils.d.ts.map +1 -1
- package/dist/schema-utils.js +143 -0
- package/dist/schema-utils.js.map +1 -1
- package/dist/types.d.ts +165 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +12 -3
- package/static/cms/admin-entry.css +144 -13
- package/static/cms/admin-entry.js +539 -586
- package/static/cms/dev-toolbar/app.js +78 -19
- package/static/cms/editor/config.js +35 -15
- package/static/cms/editor/content-map.css +8 -1
- package/static/cms/editor/content-map.js +62 -23
- package/static/cms/editor/deployment-status.js +66 -0
- package/static/cms/editor/draft-client.js +87 -0
- package/static/cms/editor/guards.js +20 -0
- package/static/cms/editor/helpers.js +30 -1
- package/static/cms/editor/highlight.js +1 -0
- package/static/cms/editor/image-edit.js +44 -1
- package/static/cms/editor/image-utils.js +23 -7
- package/static/cms/editor/link-follow.js +122 -41
- package/static/cms/editor/link-ui.css +10 -1
- package/static/cms/editor/linkify.js +2 -0
- package/static/cms/editor/md-block-edit.js +140 -37
- package/static/cms/editor/md-paragraphs.js +98 -0
- package/static/cms/editor/md-serialize.js +1 -1
- package/static/cms/editor/panel.js +53 -7
- package/static/cms/editor/preview-mode.js +44 -0
- package/static/cms/editor/publish-result.js +100 -0
- package/static/cms/editor/rich-toolbar.js +58 -15
- package/static/cms/editor/sanitize.js +6 -2
- package/static/cms/editor/save-queue.js +75 -17
- package/static/cms/editor/section-controls/activation.js +3 -0
- package/static/cms/editor/section-controls/api.js +25 -7
- package/static/cms/editor/section-controls/index.js +60 -8
- package/static/cms/editor/section-controls/model.js +39 -4
- package/static/cms/editor/section-controls/mutations.js +15 -0
- package/static/cms/editor/section-controls/page-context.js +9 -0
- package/static/cms/editor/section-controls/pickers.js +53 -2
- package/static/cms/editor/section-controls/reorder.js +23 -0
- package/static/cms/editor/section-controls/selection.js +6 -0
- package/static/cms/editor/section-controls/spacing-drag.js +16 -0
- package/static/cms/editor/section-controls/ui-elements.js +7 -0
- package/static/cms/editor/section-controls/utils.js +7 -0
- package/static/cms/editor/section-controls.css +18 -3
- package/static/cms/editor/security.js +13 -2
- package/static/cms/editor/stega-hydrate.js +17 -4
- package/static/cms/editor/sync.js +475 -56
- package/static/cms/editor/text-edit.js +37 -3
- package/static/cms/editor/text-link-interactions.js +59 -17
- package/static/cms/editor/toast.js +23 -9
- package/static/cms/editor/tokens-and-text.css +9 -0
- package/static/cms/editor/toolbar.css +5 -0
- package/static/cms/editor/toolbar.js +91 -101
- package/static/cms/editor.css +1 -1
- package/static/cms/editor.js +1 -0
- package/static/cms/studio/entry-loader.js +102 -0
- package/static/cms/studio/field-model.js +110 -0
- package/static/cms/studio/fields.js +319 -0
- package/static/cms/studio/history-client.js +74 -0
- package/static/cms/studio/mutation-client.js +74 -0
- package/static/cms/studio/sync-client.js +107 -0
- package/static/cms/studio/upload-client.js +109 -0
- package/static/cms/studio-tokens.css +4 -4
- package/static/cms/studio.css +24 -0
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
import { escapeHtml } from './linkify.js';
|
|
2
2
|
|
|
3
|
+
/** @typedef {HTMLElement & { _caretSkipSave?: boolean }} EditableElement */
|
|
4
|
+
/** @typedef {{ dirtyEls: Set<Element>, linkPopupEl: HTMLElement | null }} TextEditorState */
|
|
5
|
+
/** @typedef {(message: string, kind: 'success' | 'error') => unknown} ShowToast */
|
|
6
|
+
/** @typedef {(url: string | null) => void} LinkApplyCallback */
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {object} options
|
|
10
|
+
* @param {TextEditorState} options.state
|
|
11
|
+
* @param {(text: string) => string} options.clientLinkify
|
|
12
|
+
* @param {ShowToast} options.showToast
|
|
13
|
+
* @param {WeakMap<HTMLElement, string>} options.snapshots
|
|
14
|
+
*/
|
|
3
15
|
export function createTextLinkInteractions({
|
|
4
16
|
state,
|
|
5
17
|
clientLinkify,
|
|
6
18
|
showToast,
|
|
7
19
|
snapshots,
|
|
8
20
|
}) {
|
|
21
|
+
/** @type {HTMLDivElement | null} */
|
|
9
22
|
let activePopover = null;
|
|
23
|
+
/** @type {HTMLDivElement | null} */
|
|
10
24
|
let activeLinkHint = null;
|
|
11
25
|
|
|
12
26
|
function removeLinkHint() {
|
|
@@ -24,6 +38,11 @@ export function createTextLinkInteractions({
|
|
|
24
38
|
state.linkPopupEl = null;
|
|
25
39
|
}
|
|
26
40
|
|
|
41
|
+
/**
|
|
42
|
+
* @param {DOMRect | DOMRectReadOnly} rect
|
|
43
|
+
* @param {string} initialUrl
|
|
44
|
+
* @param {LinkApplyCallback} onApply
|
|
45
|
+
*/
|
|
27
46
|
function showLinkPopup(rect, initialUrl, onApply) {
|
|
28
47
|
dismissLinkPopup();
|
|
29
48
|
|
|
@@ -59,9 +78,15 @@ export function createTextLinkInteractions({
|
|
|
59
78
|
popover.style.left = `${left}px`;
|
|
60
79
|
popover.style.top = `${top}px`;
|
|
61
80
|
|
|
62
|
-
const input =
|
|
63
|
-
|
|
64
|
-
|
|
81
|
+
const input = /** @type {HTMLInputElement} */ (
|
|
82
|
+
popover.querySelector('.cms-link-popover-input')
|
|
83
|
+
);
|
|
84
|
+
const applyBtn = /** @type {HTMLButtonElement} */ (
|
|
85
|
+
popover.querySelector('.cms-link-popover-apply')
|
|
86
|
+
);
|
|
87
|
+
const cancelBtn = /** @type {HTMLButtonElement} */ (
|
|
88
|
+
popover.querySelector('.cms-link-popover-cancel')
|
|
89
|
+
);
|
|
65
90
|
|
|
66
91
|
requestAnimationFrame(() => input.focus());
|
|
67
92
|
if (initialUrl) input.select();
|
|
@@ -80,7 +105,7 @@ export function createTextLinkInteractions({
|
|
|
80
105
|
applyBtn.addEventListener('click', apply);
|
|
81
106
|
cancelBtn.addEventListener('click', cancel);
|
|
82
107
|
|
|
83
|
-
input.addEventListener('keydown', (e) => {
|
|
108
|
+
input.addEventListener('keydown', (/** @type {KeyboardEvent} */ e) => {
|
|
84
109
|
if (e.key === 'Enter') {
|
|
85
110
|
e.preventDefault();
|
|
86
111
|
apply();
|
|
@@ -92,8 +117,9 @@ export function createTextLinkInteractions({
|
|
|
92
117
|
});
|
|
93
118
|
|
|
94
119
|
setTimeout(() => {
|
|
120
|
+
/** @param {MouseEvent} e */
|
|
95
121
|
function onDocClick(e) {
|
|
96
|
-
if (!popover.contains(e.target)) {
|
|
122
|
+
if (!(e.target instanceof Node) || !popover.contains(e.target)) {
|
|
97
123
|
document.removeEventListener('click', onDocClick, true);
|
|
98
124
|
cancel();
|
|
99
125
|
}
|
|
@@ -102,6 +128,7 @@ export function createTextLinkInteractions({
|
|
|
102
128
|
}, 0);
|
|
103
129
|
}
|
|
104
130
|
|
|
131
|
+
/** @param {HTMLElement} el */
|
|
105
132
|
function showLinkHint(el) {
|
|
106
133
|
removeLinkHint();
|
|
107
134
|
const hint = document.createElement('div');
|
|
@@ -115,8 +142,23 @@ export function createTextLinkInteractions({
|
|
|
115
142
|
hint.style.left = `${rect.right - hint.offsetWidth}px`;
|
|
116
143
|
}
|
|
117
144
|
|
|
145
|
+
/**
|
|
146
|
+
* @param {Range} range
|
|
147
|
+
* @returns {boolean}
|
|
148
|
+
*/
|
|
149
|
+
function restoreSelection(range) {
|
|
150
|
+
const selection = window.getSelection();
|
|
151
|
+
if (!selection) return false;
|
|
152
|
+
selection.removeAllRanges();
|
|
153
|
+
selection.addRange(range);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** @param {KeyboardEvent} e */
|
|
118
158
|
function onKeydown(e) {
|
|
119
|
-
const focused = document.activeElement
|
|
159
|
+
const focused = document.activeElement instanceof HTMLElement
|
|
160
|
+
? /** @type {EditableElement} */ (document.activeElement)
|
|
161
|
+
: null;
|
|
120
162
|
|
|
121
163
|
// Ctrl+S / Cmd+S — save focused element by triggering blur save
|
|
122
164
|
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
|
|
@@ -148,15 +190,14 @@ export function createTextLinkInteractions({
|
|
|
148
190
|
: anchorNode instanceof Element ? anchorNode.closest('a') : null;
|
|
149
191
|
const initialUrl = existingLink?.getAttribute('href') || '';
|
|
150
192
|
|
|
151
|
-
state.linkPopupEl = focused;
|
|
152
|
-
|
|
153
193
|
showLinkPopup(selRect, initialUrl, (url) => {
|
|
154
194
|
focused.focus();
|
|
155
195
|
|
|
156
196
|
if (url) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
197
|
+
if (!restoreSelection(range)) {
|
|
198
|
+
state.linkPopupEl = null;
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
160
201
|
|
|
161
202
|
if (existingLink) {
|
|
162
203
|
existingLink.setAttribute('href', url);
|
|
@@ -179,12 +220,13 @@ export function createTextLinkInteractions({
|
|
|
179
220
|
|
|
180
221
|
state.dirtyEls.add(focused);
|
|
181
222
|
} else if (url === '' && existingLink) {
|
|
182
|
-
document.execCommand('unlink'
|
|
223
|
+
document.execCommand('unlink');
|
|
183
224
|
state.dirtyEls.add(focused);
|
|
184
225
|
}
|
|
185
226
|
|
|
186
227
|
state.linkPopupEl = null;
|
|
187
228
|
});
|
|
229
|
+
state.linkPopupEl = focused;
|
|
188
230
|
return;
|
|
189
231
|
}
|
|
190
232
|
|
|
@@ -198,21 +240,21 @@ export function createTextLinkInteractions({
|
|
|
198
240
|
|
|
199
241
|
const range = sel.getRangeAt(0).cloneRange();
|
|
200
242
|
const selRect = range.getBoundingClientRect();
|
|
201
|
-
state.linkPopupEl = focused;
|
|
202
|
-
|
|
203
243
|
showLinkPopup(selRect, '', (url) => {
|
|
204
244
|
focused.focus();
|
|
205
245
|
|
|
206
246
|
if (url) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
247
|
+
if (!restoreSelection(range)) {
|
|
248
|
+
state.linkPopupEl = null;
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
210
251
|
document.execCommand('insertText', false, `[${selectedText}](${url})`);
|
|
211
252
|
state.dirtyEls.add(focused);
|
|
212
253
|
}
|
|
213
254
|
|
|
214
255
|
state.linkPopupEl = null;
|
|
215
256
|
});
|
|
257
|
+
state.linkPopupEl = focused;
|
|
216
258
|
}
|
|
217
259
|
return;
|
|
218
260
|
}
|
|
@@ -5,6 +5,13 @@ const ERROR_SVG =
|
|
|
5
5
|
const WARNING_SVG =
|
|
6
6
|
'<svg class="cms-toast-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>';
|
|
7
7
|
|
|
8
|
+
/** @typedef {'success' | 'error' | 'warning'} ToastType */
|
|
9
|
+
/** @typedef {{ label: string, primary?: boolean, onClick: () => void | Promise<void> }} ToastAction */
|
|
10
|
+
/** @typedef {{ message: string, sticky?: boolean, actions?: ToastAction[], role?: string }} ToastOptions */
|
|
11
|
+
/** @typedef {{ message: string, onKeepMine: () => void | Promise<void>, onLoadLatest: () => void | Promise<void>, keepLabel?: string, latestLabel?: string }} ConflictOptions */
|
|
12
|
+
/** @typedef {((message: string, type?: 'success' | 'error') => HTMLDivElement) & { conflict: (options: ConflictOptions) => HTMLDivElement }} ToastController */
|
|
13
|
+
|
|
14
|
+
/** @param {ToastType} type @returns {string} */
|
|
8
15
|
function iconFor(type) {
|
|
9
16
|
if (type === 'success') return SUCCESS_SVG;
|
|
10
17
|
if (type === 'warning') return WARNING_SVG;
|
|
@@ -24,9 +31,13 @@ function iconFor(type) {
|
|
|
24
31
|
* server-provided error string can't inject markup into the editor chrome.
|
|
25
32
|
* - The container is an aria-live region; errors/conflicts are role="alert".
|
|
26
33
|
*/
|
|
34
|
+
/** @returns {ToastController} */
|
|
27
35
|
export function createToast() {
|
|
36
|
+
/** @type {HTMLDivElement | null} */
|
|
28
37
|
let container = null;
|
|
38
|
+
const dismissed = new WeakSet();
|
|
29
39
|
|
|
40
|
+
/** @returns {HTMLDivElement} */
|
|
30
41
|
function ensureContainer() {
|
|
31
42
|
if (container && document.body.contains(container)) return container;
|
|
32
43
|
container = document.createElement('div');
|
|
@@ -36,13 +47,15 @@ export function createToast() {
|
|
|
36
47
|
return container;
|
|
37
48
|
}
|
|
38
49
|
|
|
50
|
+
/** @param {HTMLDivElement} toast */
|
|
39
51
|
function dismiss(toast) {
|
|
40
|
-
if (toast
|
|
41
|
-
toast
|
|
52
|
+
if (dismissed.has(toast)) return;
|
|
53
|
+
dismissed.add(toast);
|
|
42
54
|
toast.classList.add('cms-toast-out');
|
|
43
55
|
setTimeout(() => toast.remove(), 300);
|
|
44
56
|
}
|
|
45
57
|
|
|
58
|
+
/** @param {ToastType} type @param {ToastOptions} options @returns {HTMLDivElement} */
|
|
46
59
|
function render(type, { message, sticky, actions, role }) {
|
|
47
60
|
const stack = ensureContainer();
|
|
48
61
|
|
|
@@ -72,11 +85,11 @@ export function createToast() {
|
|
|
72
85
|
btn.textContent = action.label;
|
|
73
86
|
btn.addEventListener('click', () => {
|
|
74
87
|
dismiss(toast);
|
|
75
|
-
|
|
76
|
-
action.onClick()
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
88
|
+
Promise.resolve()
|
|
89
|
+
.then(() => action.onClick())
|
|
90
|
+
.catch(() => {
|
|
91
|
+
/* action errors shouldn't break the toast */
|
|
92
|
+
});
|
|
80
93
|
});
|
|
81
94
|
group.appendChild(btn);
|
|
82
95
|
}
|
|
@@ -96,14 +109,15 @@ export function createToast() {
|
|
|
96
109
|
return toast;
|
|
97
110
|
}
|
|
98
111
|
|
|
99
|
-
|
|
112
|
+
/** @type {ToastController} */
|
|
113
|
+
const showToast = (message, type) => {
|
|
100
114
|
const t = type === 'error' ? 'error' : type || 'success';
|
|
101
115
|
return render(t, {
|
|
102
116
|
message,
|
|
103
117
|
sticky: t === 'error',
|
|
104
118
|
role: t === 'error' ? 'alert' : undefined,
|
|
105
119
|
});
|
|
106
|
-
}
|
|
120
|
+
};
|
|
107
121
|
|
|
108
122
|
// Two-choice save-conflict prompt. Sticky so the user's edit is never silently
|
|
109
123
|
// discarded — they explicitly choose to keep theirs or load the latest.
|
|
@@ -72,6 +72,15 @@
|
|
|
72
72
|
box-shadow: 0 0 0 4px var(--cms-blue-dim);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/* Briefly confirms that the paired Studio field was located. Save feedback is
|
|
76
|
+
green, so linked selection keeps the editor's blue navigation language. */
|
|
77
|
+
.cms-linked-selection {
|
|
78
|
+
outline: 2px solid var(--cms-blue) !important;
|
|
79
|
+
outline-offset: 4px;
|
|
80
|
+
box-shadow: 0 0 0 4px var(--cms-blue-dim);
|
|
81
|
+
transition: outline-color 0.35s ease, box-shadow 0.35s ease;
|
|
82
|
+
}
|
|
83
|
+
|
|
75
84
|
/* ---------------------------------------------------------------------------
|
|
76
85
|
Unpublished body draft
|
|
77
86
|
The server rewrite tags blocks whose prose comes from an unpublished draft
|
|
@@ -1,39 +1,34 @@
|
|
|
1
|
+
import { isPolicyDraftMode } from './config.js';
|
|
1
2
|
import { toggleHighlight } from './highlight.js';
|
|
2
3
|
import { buildCmsUrl, isStaticDelivery } from './config.js';
|
|
4
|
+
import { createDeploymentStatusPoller } from './deployment-status.js';
|
|
5
|
+
import { presentPublishResult } from './publish-result.js';
|
|
6
|
+
import { createDraftClient } from './draft-client.js';
|
|
7
|
+
import { createPreviewModeController } from './preview-mode.js';
|
|
3
8
|
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
function setPreviewCookie(on) {
|
|
11
|
-
document.cookie = on
|
|
12
|
-
? `${PREVIEW_COOKIE}=1; path=/; SameSite=Lax`
|
|
13
|
-
: `${PREVIEW_COOKIE}=; path=/; Max-Age=0; SameSite=Lax`;
|
|
14
|
-
}
|
|
9
|
+
const previewMode = createPreviewModeController({
|
|
10
|
+
isStaticDelivery,
|
|
11
|
+
isPolicyDraftMode,
|
|
12
|
+
documentRef: document,
|
|
13
|
+
reload: () => window.location.reload(),
|
|
14
|
+
});
|
|
15
15
|
|
|
16
16
|
/** Static delivery always drafts — turn preview on once so saves stay unpublished. */
|
|
17
17
|
export function ensureStaticPreviewMode() {
|
|
18
|
-
|
|
19
|
-
if (previewActive()) return false;
|
|
20
|
-
setPreviewCookie(true);
|
|
21
|
-
window.location.reload();
|
|
22
|
-
return true;
|
|
18
|
+
return previewMode.ensureStaticPreviewMode();
|
|
23
19
|
}
|
|
24
20
|
|
|
25
21
|
/** Align preview cookie with delivery mode (static on, server off). */
|
|
26
22
|
export function normalizePreviewForDelivery() {
|
|
27
|
-
|
|
28
|
-
if (previewActive()) {
|
|
29
|
-
setPreviewCookie(false);
|
|
30
|
-
window.location.reload();
|
|
31
|
-
return true;
|
|
32
|
-
}
|
|
33
|
-
return false;
|
|
23
|
+
return previewMode.normalizePreviewForDelivery();
|
|
34
24
|
}
|
|
35
25
|
|
|
26
|
+
/** @typedef {{ href: string, label: string, isActive: boolean }} ToolbarNavLink */
|
|
27
|
+
/** @typedef {'saving' | 'idle' | 'error'} ToolbarStatusState */
|
|
28
|
+
|
|
29
|
+
/** @param {string} pagePath @returns {ToolbarNavLink[]} */
|
|
36
30
|
function getToolbarNavLinks(pagePath) {
|
|
31
|
+
/** @type {ToolbarNavLink[]} */
|
|
37
32
|
const links = [];
|
|
38
33
|
const headerNav = document.querySelector('#main-header nav');
|
|
39
34
|
if (!headerNav) return links;
|
|
@@ -49,13 +44,14 @@ function getToolbarNavLinks(pagePath) {
|
|
|
49
44
|
return links;
|
|
50
45
|
}
|
|
51
46
|
|
|
47
|
+
/** @param {boolean} staticDelivery */
|
|
52
48
|
function renderDraftControls(staticDelivery) {
|
|
53
49
|
// Static delivery is always drafting, so the controls stay visible. Server
|
|
54
50
|
// delivery saves field edits straight to the site, but inline body-block
|
|
55
51
|
// edits are ALWAYS drafts (they splice into source files only at publish), so
|
|
56
52
|
// the controls render here too — hidden until at least one draft is pending
|
|
57
53
|
// (toggled by refreshDraftControls) so the toolbar stays quiet otherwise.
|
|
58
|
-
const publishTitle = staticDelivery
|
|
54
|
+
const publishTitle = isPolicyDraftMode() ? 'Publish private drafts to shared content' : staticDelivery
|
|
59
55
|
? 'Publish drafts; static visitors update after rebuild and deploy'
|
|
60
56
|
: 'Publish your body-block drafts into the source files';
|
|
61
57
|
return `
|
|
@@ -72,6 +68,7 @@ function renderDraftControls(staticDelivery) {
|
|
|
72
68
|
</span>`;
|
|
73
69
|
}
|
|
74
70
|
|
|
71
|
+
/** @param {ToolbarNavLink[]} navLinks @param {boolean} staticDelivery */
|
|
75
72
|
function renderToolbar(navLinks, staticDelivery) {
|
|
76
73
|
const navLinksHtml = navLinks
|
|
77
74
|
.map(
|
|
@@ -80,8 +77,8 @@ function renderToolbar(navLinks, staticDelivery) {
|
|
|
80
77
|
)
|
|
81
78
|
.join('');
|
|
82
79
|
|
|
83
|
-
const badgeLabel = staticDelivery ? 'Draft' : 'Live';
|
|
84
|
-
const badgeHint = staticDelivery
|
|
80
|
+
const badgeLabel = staticDelivery || isPolicyDraftMode() ? 'Draft' : 'Live';
|
|
81
|
+
const badgeHint = isPolicyDraftMode() ? 'Changes save to your private draft; publishing requires permission' : staticDelivery
|
|
85
82
|
? 'Draft preview; visitors update after publish, rebuild, and deploy'
|
|
86
83
|
: 'Changes save directly to your site';
|
|
87
84
|
|
|
@@ -104,6 +101,7 @@ function renderToolbar(navLinks, staticDelivery) {
|
|
|
104
101
|
${navLinksHtml ? `<div class="cms-toolbar-nav">${navLinksHtml}</div>` : ''}
|
|
105
102
|
<div class="cms-toolbar-right">
|
|
106
103
|
${renderDraftControls(staticDelivery)}
|
|
104
|
+
<button type="button" class="cms-retry-rebuild-btn" hidden>Retry deploy</button>
|
|
107
105
|
<button type="button" class="cms-studio-btn" title="Toggle Content Studio panel">
|
|
108
106
|
<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>
|
|
109
107
|
Studio
|
|
@@ -130,6 +128,13 @@ function renderToolbar(navLinks, staticDelivery) {
|
|
|
130
128
|
return toolbar;
|
|
131
129
|
}
|
|
132
130
|
|
|
131
|
+
/**
|
|
132
|
+
* @param {{
|
|
133
|
+
* showToast: (message: string, type: 'success' | 'error') => void,
|
|
134
|
+
* clearDirty: () => void,
|
|
135
|
+
* onLogout: () => void | Promise<void>,
|
|
136
|
+
* }} options
|
|
137
|
+
*/
|
|
133
138
|
export function mountToolbar({ showToast, clearDirty, onLogout }) {
|
|
134
139
|
const staticDelivery = isStaticDelivery();
|
|
135
140
|
const navLinks = getToolbarNavLinks(window.location.pathname);
|
|
@@ -138,6 +143,7 @@ export function mountToolbar({ showToast, clearDirty, onLogout }) {
|
|
|
138
143
|
const statusDot = toolbar.querySelector('.cms-status-dot');
|
|
139
144
|
const statusText = toolbar.querySelector('.cms-status-text');
|
|
140
145
|
|
|
146
|
+
/** @param {ToolbarStatusState} state @param {string} text */
|
|
141
147
|
function setStatus(state, text) {
|
|
142
148
|
if (statusDot) {
|
|
143
149
|
statusDot.className = `cms-status-dot cms-status-${state}`;
|
|
@@ -147,29 +153,36 @@ export function mountToolbar({ showToast, clearDirty, onLogout }) {
|
|
|
147
153
|
}
|
|
148
154
|
}
|
|
149
155
|
|
|
150
|
-
const
|
|
156
|
+
const deploymentStatus = createDeploymentStatusPoller({
|
|
157
|
+
fetchStatus: () => fetch(buildCmsUrl('/deployment'), {
|
|
158
|
+
credentials: 'same-origin',
|
|
159
|
+
headers: { 'x-caret-request': '1' },
|
|
160
|
+
}),
|
|
161
|
+
setStatus,
|
|
162
|
+
setBuildId(buildId) {
|
|
163
|
+
const statusGroup = /** @type {HTMLElement | null} */ (toolbar.querySelector('.cms-status-group'));
|
|
164
|
+
if (statusGroup) {
|
|
165
|
+
statusGroup.title = buildId ? `Build ${buildId}` : '';
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
const draftClient = createDraftClient({ buildUrl: path => buildCmsUrl(path) });
|
|
170
|
+
|
|
171
|
+
const highlightBtn = /** @type {HTMLButtonElement | null} */ (toolbar.querySelector('.cms-highlight-btn'));
|
|
151
172
|
highlightBtn?.addEventListener('click', () =>
|
|
152
173
|
toggleHighlight(highlightBtn, showToast),
|
|
153
174
|
);
|
|
154
175
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
method,
|
|
158
|
-
headers: { 'Content-Type': 'application/json', 'x-caret-request': '1' },
|
|
159
|
-
credentials: 'same-origin',
|
|
160
|
-
body: method === 'POST' ? '{}' : undefined,
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
|
|
176
|
+
let canPublishDrafts = true;
|
|
177
|
+
const retryRebuildButton = /** @type {HTMLButtonElement | null} */ (toolbar.querySelector('.cms-retry-rebuild-btn'));
|
|
164
178
|
async function fetchDraftCount() {
|
|
165
179
|
try {
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (!
|
|
171
|
-
|
|
172
|
-
return typeof data.count === 'number' ? data.count : 0;
|
|
180
|
+
const data = await draftClient.getState();
|
|
181
|
+
canPublishDrafts = data.canPublish;
|
|
182
|
+
const publishButton = /** @type {HTMLButtonElement | null} */ (toolbar.querySelector('.cms-publish-btn'));
|
|
183
|
+
if (publishButton) publishButton.hidden = !data.canPublish;
|
|
184
|
+
if (retryRebuildButton) retryRebuildButton.hidden = !data.retryRebuild || !data.canPublish;
|
|
185
|
+
return data.count;
|
|
173
186
|
} catch {
|
|
174
187
|
return 0;
|
|
175
188
|
}
|
|
@@ -178,60 +191,25 @@ export function mountToolbar({ showToast, clearDirty, onLogout }) {
|
|
|
178
191
|
async function publishDrafts() {
|
|
179
192
|
setStatus('saving', 'Publishing…');
|
|
180
193
|
try {
|
|
181
|
-
const
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
);
|
|
194
|
+
const presentation = presentPublishResult(await draftClient.publish());
|
|
195
|
+
if (retryRebuildButton) retryRebuildButton.hidden = !presentation.retryAvailable;
|
|
196
|
+
if (presentation.status) {
|
|
197
|
+
setStatus(presentation.status.state, presentation.status.text);
|
|
221
198
|
}
|
|
222
|
-
|
|
223
|
-
|
|
199
|
+
showToast(presentation.toast.message, presentation.toast.kind);
|
|
200
|
+
if (presentation.reload) window.location.reload();
|
|
201
|
+
return presentation.completed;
|
|
224
202
|
} catch {
|
|
225
203
|
setStatus('error', 'Publish failed');
|
|
226
|
-
showToast('Publish
|
|
204
|
+
showToast('Publication could not finish. Some content may already be published. Retry Publish to recover.', 'error');
|
|
227
205
|
return false;
|
|
228
206
|
}
|
|
229
207
|
}
|
|
230
208
|
|
|
231
209
|
toolbar.querySelector('.cms-exit-btn')?.addEventListener('click', async () => {
|
|
232
|
-
if (staticDelivery) {
|
|
210
|
+
if (staticDelivery || isPolicyDraftMode()) {
|
|
233
211
|
const count = await fetchDraftCount();
|
|
234
|
-
if (count > 0) {
|
|
212
|
+
if (count > 0 && canPublishDrafts) {
|
|
235
213
|
const goLive = window.confirm(
|
|
236
214
|
`You have ${count} unpublished change${count === 1 ? '' : 's'}. Publish before signing out?`,
|
|
237
215
|
);
|
|
@@ -244,10 +222,9 @@ export function mountToolbar({ showToast, clearDirty, onLogout }) {
|
|
|
244
222
|
);
|
|
245
223
|
if (!discard) return;
|
|
246
224
|
try {
|
|
247
|
-
|
|
248
|
-
if (!res.ok) throw new Error('discard failed');
|
|
225
|
+
await draftClient.discard();
|
|
249
226
|
} catch {
|
|
250
|
-
showToast('
|
|
227
|
+
showToast('Some drafts need publish recovery. Retry Publish before discarding.', 'error');
|
|
251
228
|
return;
|
|
252
229
|
}
|
|
253
230
|
}
|
|
@@ -257,7 +234,7 @@ export function mountToolbar({ showToast, clearDirty, onLogout }) {
|
|
|
257
234
|
await onLogout();
|
|
258
235
|
});
|
|
259
236
|
|
|
260
|
-
const publishBtn = toolbar.querySelector('.cms-publish-btn');
|
|
237
|
+
const publishBtn = /** @type {HTMLButtonElement | null} */ (toolbar.querySelector('.cms-publish-btn'));
|
|
261
238
|
publishBtn?.addEventListener('click', async () => {
|
|
262
239
|
if (
|
|
263
240
|
!window.confirm(
|
|
@@ -274,8 +251,7 @@ export function mountToolbar({ showToast, clearDirty, onLogout }) {
|
|
|
274
251
|
toolbar.querySelector('.cms-discard-btn')?.addEventListener('click', async () => {
|
|
275
252
|
if (!window.confirm('Discard all your draft changes? This cannot be undone.')) return;
|
|
276
253
|
try {
|
|
277
|
-
|
|
278
|
-
if (!res.ok) throw new Error('discard failed');
|
|
254
|
+
await draftClient.discard();
|
|
279
255
|
showToast('Draft discarded', 'success');
|
|
280
256
|
window.location.reload();
|
|
281
257
|
} catch {
|
|
@@ -287,15 +263,29 @@ export function mountToolbar({ showToast, clearDirty, onLogout }) {
|
|
|
287
263
|
// edits save directly (no draft), so the count reflects pending body blocks.
|
|
288
264
|
// Refresh on mount and whenever a body block is drafted; publish/discard
|
|
289
265
|
// reload the page, so mount re-evaluates from scratch.
|
|
290
|
-
const draftControls = toolbar.querySelector('.cms-draft-controls');
|
|
266
|
+
const draftControls = /** @type {HTMLElement | null} */ (toolbar.querySelector('.cms-draft-controls'));
|
|
291
267
|
async function refreshDraftControls() {
|
|
292
|
-
|
|
293
|
-
draftControls.hidden =
|
|
294
|
-
}
|
|
295
|
-
if (!staticDelivery) {
|
|
296
|
-
refreshDraftControls();
|
|
297
|
-
window.addEventListener('cms:draftSaved', refreshDraftControls);
|
|
268
|
+
const count = await fetchDraftCount();
|
|
269
|
+
if (draftControls) draftControls.hidden = !staticDelivery && !isPolicyDraftMode() && count === 0;
|
|
298
270
|
}
|
|
271
|
+
refreshDraftControls();
|
|
272
|
+
window.addEventListener('cms:draftSaved', refreshDraftControls);
|
|
273
|
+
retryRebuildButton?.addEventListener('click', async () => {
|
|
274
|
+
retryRebuildButton.disabled = true;
|
|
275
|
+
setStatus('saving', 'Retrying deploy…');
|
|
276
|
+
try {
|
|
277
|
+
const data = await draftClient.retryDeployment();
|
|
278
|
+
retryRebuildButton.hidden = true;
|
|
279
|
+
setStatus(data.deploymentTracked ? 'saving' : 'idle', data.deploymentTracked ? 'Deploying…' : 'Deploy requested');
|
|
280
|
+
showToast('Deploy requested. Your published content is unchanged.', 'success');
|
|
281
|
+
if (data.deploymentTracked) deploymentStatus.schedule();
|
|
282
|
+
} catch {
|
|
283
|
+
setStatus('error', 'Published, deploy failed');
|
|
284
|
+
showToast('The deploy hook failed. Check its configuration and retry.', 'error');
|
|
285
|
+
} finally { retryRebuildButton.disabled = false; }
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
void deploymentStatus.refresh();
|
|
299
289
|
|
|
300
290
|
const studioButton = toolbar.querySelector('.cms-studio-btn');
|
|
301
291
|
const mapButton = toolbar.querySelector('.cms-map-btn');
|
package/static/cms/editor.css
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* ========================================================================
|
|
2
2
|
CMS Editor Styles — only loaded when isEditor = true
|
|
3
3
|
======================================================================== */
|
|
4
|
-
@import "./editor/tokens-and-text.css";
|
|
4
|
+
@import "./editor/tokens-and-text.css?v=studio-upstream-20260819";
|
|
5
5
|
@import "./editor/image.css";
|
|
6
6
|
@import "./editor/section-controls.css";
|
|
7
7
|
@import "./editor/toolbar.css";
|
package/static/cms/editor.js
CHANGED
|
@@ -36,6 +36,7 @@ function redirectToEditorLogin() {
|
|
|
36
36
|
function boot() {
|
|
37
37
|
if (normalizePreviewForDelivery()) return;
|
|
38
38
|
|
|
39
|
+
/** @type {{ dirtyEls: Set<Element>, linkPopupEl: HTMLElement | null }} */
|
|
39
40
|
const state = {
|
|
40
41
|
// Every element with unsaved edits. A Set (not a single element) so a save
|
|
41
42
|
// completing on field A can't clear the dirty flag for field B the user has
|