@caretcms/core 0.1.0 → 0.1.2

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.
Files changed (243) hide show
  1. package/README.md +182 -6
  2. package/dist/browser-runtime.d.ts +16 -0
  3. package/dist/browser-runtime.d.ts.map +1 -1
  4. package/dist/browser-runtime.js +42 -23
  5. package/dist/browser-runtime.js.map +1 -1
  6. package/dist/contracts.d.ts +14 -0
  7. package/dist/contracts.d.ts.map +1 -0
  8. package/dist/contracts.js +14 -0
  9. package/dist/contracts.js.map +1 -0
  10. package/dist/editable.d.ts +28 -0
  11. package/dist/editable.d.ts.map +1 -0
  12. package/dist/editable.js +122 -0
  13. package/dist/editable.js.map +1 -0
  14. package/dist/index.d.ts +53 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +274 -4
  17. package/dist/index.js.map +1 -1
  18. package/dist/loader.d.ts.map +1 -1
  19. package/dist/loader.js +45 -3
  20. package/dist/loader.js.map +1 -1
  21. package/dist/providers/storage/markdown.d.ts +7 -0
  22. package/dist/providers/storage/markdown.d.ts.map +1 -0
  23. package/dist/providers/storage/markdown.js +6 -0
  24. package/dist/providers/storage/markdown.js.map +1 -0
  25. package/dist/runtime/auth/cookie-utils.d.ts +1 -0
  26. package/dist/runtime/auth/cookie-utils.d.ts.map +1 -1
  27. package/dist/runtime/auth/cookie-utils.js +1 -1
  28. package/dist/runtime/auth/cookie-utils.js.map +1 -1
  29. package/dist/runtime/auth/rate-limiter.d.ts.map +1 -1
  30. package/dist/runtime/auth/rate-limiter.js +19 -13
  31. package/dist/runtime/auth/rate-limiter.js.map +1 -1
  32. package/dist/runtime/auth/session.d.ts +8 -0
  33. package/dist/runtime/auth/session.d.ts.map +1 -1
  34. package/dist/runtime/auth/session.js +72 -11
  35. package/dist/runtime/auth/session.js.map +1 -1
  36. package/dist/runtime/config.d.ts +1 -0
  37. package/dist/runtime/config.d.ts.map +1 -1
  38. package/dist/runtime/config.js +2 -1
  39. package/dist/runtime/config.js.map +1 -1
  40. package/dist/runtime/git-journal.d.ts +19 -0
  41. package/dist/runtime/git-journal.d.ts.map +1 -0
  42. package/dist/runtime/git-journal.js +64 -0
  43. package/dist/runtime/git-journal.js.map +1 -0
  44. package/dist/runtime/index.d.ts +1 -1
  45. package/dist/runtime/index.d.ts.map +1 -1
  46. package/dist/runtime/index.js +1 -1
  47. package/dist/runtime/index.js.map +1 -1
  48. package/dist/runtime/middleware.d.ts.map +1 -1
  49. package/dist/runtime/middleware.js +125 -8
  50. package/dist/runtime/middleware.js.map +1 -1
  51. package/dist/runtime/mutations/contracts.d.ts.map +1 -1
  52. package/dist/runtime/mutations/contracts.js +7 -4
  53. package/dist/runtime/mutations/contracts.js.map +1 -1
  54. package/dist/runtime/mutations/engine.d.ts +7 -0
  55. package/dist/runtime/mutations/engine.d.ts.map +1 -1
  56. package/dist/runtime/mutations/engine.js +30 -17
  57. package/dist/runtime/mutations/engine.js.map +1 -1
  58. package/dist/runtime/providers.d.ts +16 -0
  59. package/dist/runtime/providers.d.ts.map +1 -1
  60. package/dist/runtime/providers.js +16 -0
  61. package/dist/runtime/providers.js.map +1 -1
  62. package/dist/runtime/publish.d.ts +40 -0
  63. package/dist/runtime/publish.d.ts.map +1 -0
  64. package/dist/runtime/publish.js +75 -0
  65. package/dist/runtime/publish.js.map +1 -0
  66. package/dist/runtime/rebuild-webhook.d.ts +29 -0
  67. package/dist/runtime/rebuild-webhook.d.ts.map +1 -0
  68. package/dist/runtime/rebuild-webhook.js +37 -0
  69. package/dist/runtime/rebuild-webhook.js.map +1 -0
  70. package/dist/runtime/request-context.d.ts +19 -0
  71. package/dist/runtime/request-context.d.ts.map +1 -1
  72. package/dist/runtime/request-context.js.map +1 -1
  73. package/dist/runtime/rewrite.d.ts +29 -1
  74. package/dist/runtime/rewrite.d.ts.map +1 -1
  75. package/dist/runtime/rewrite.js +30 -16
  76. package/dist/runtime/rewrite.js.map +1 -1
  77. package/dist/runtime/rich-allowlist.d.ts +29 -0
  78. package/dist/runtime/rich-allowlist.d.ts.map +1 -0
  79. package/dist/runtime/rich-allowlist.js +49 -0
  80. package/dist/runtime/rich-allowlist.js.map +1 -0
  81. package/dist/runtime/routes/_helpers.d.ts +33 -0
  82. package/dist/runtime/routes/_helpers.d.ts.map +1 -1
  83. package/dist/runtime/routes/_helpers.js +117 -1
  84. package/dist/runtime/routes/_helpers.js.map +1 -1
  85. package/dist/runtime/routes/admin-entry.d.ts +1 -0
  86. package/dist/runtime/routes/admin-entry.d.ts.map +1 -1
  87. package/dist/runtime/routes/admin-entry.js +3 -2
  88. package/dist/runtime/routes/admin-entry.js.map +1 -1
  89. package/dist/runtime/routes/admin.d.ts +1 -0
  90. package/dist/runtime/routes/admin.d.ts.map +1 -1
  91. package/dist/runtime/routes/admin.js +61 -9
  92. package/dist/runtime/routes/admin.js.map +1 -1
  93. package/dist/runtime/routes/auth-login.d.ts +1 -0
  94. package/dist/runtime/routes/auth-login.d.ts.map +1 -1
  95. package/dist/runtime/routes/auth-login.js +26 -8
  96. package/dist/runtime/routes/auth-login.js.map +1 -1
  97. package/dist/runtime/routes/auth-logout.d.ts +1 -0
  98. package/dist/runtime/routes/auth-logout.d.ts.map +1 -1
  99. package/dist/runtime/routes/auth-logout.js +5 -0
  100. package/dist/runtime/routes/auth-logout.js.map +1 -1
  101. package/dist/runtime/routes/auth-session.d.ts +1 -0
  102. package/dist/runtime/routes/auth-session.d.ts.map +1 -1
  103. package/dist/runtime/routes/auth-session.js +1 -0
  104. package/dist/runtime/routes/auth-session.js.map +1 -1
  105. package/dist/runtime/routes/collection-list.d.ts +1 -0
  106. package/dist/runtime/routes/collection-list.d.ts.map +1 -1
  107. package/dist/runtime/routes/collection-list.js +3 -2
  108. package/dist/runtime/routes/collection-list.js.map +1 -1
  109. package/dist/runtime/routes/collections-metadata.d.ts +1 -0
  110. package/dist/runtime/routes/collections-metadata.d.ts.map +1 -1
  111. package/dist/runtime/routes/collections-metadata.js +7 -4
  112. package/dist/runtime/routes/collections-metadata.js.map +1 -1
  113. package/dist/runtime/routes/draft.d.ts +13 -0
  114. package/dist/runtime/routes/draft.d.ts.map +1 -0
  115. package/dist/runtime/routes/draft.js +55 -0
  116. package/dist/runtime/routes/draft.js.map +1 -0
  117. package/dist/runtime/routes/editor-assets.d.ts +1 -0
  118. package/dist/runtime/routes/editor-assets.d.ts.map +1 -1
  119. package/dist/runtime/routes/editor-assets.js +1 -0
  120. package/dist/runtime/routes/editor-assets.js.map +1 -1
  121. package/dist/runtime/routes/entries.d.ts +1 -0
  122. package/dist/runtime/routes/entries.d.ts.map +1 -1
  123. package/dist/runtime/routes/entries.js +3 -2
  124. package/dist/runtime/routes/entries.js.map +1 -1
  125. package/dist/runtime/routes/history.d.ts +1 -0
  126. package/dist/runtime/routes/history.d.ts.map +1 -1
  127. package/dist/runtime/routes/history.js +24 -10
  128. package/dist/runtime/routes/history.js.map +1 -1
  129. package/dist/runtime/routes/mutate.d.ts +1 -0
  130. package/dist/runtime/routes/mutate.d.ts.map +1 -1
  131. package/dist/runtime/routes/mutate.js +7 -10
  132. package/dist/runtime/routes/mutate.js.map +1 -1
  133. package/dist/runtime/routes/publish.d.ts +10 -0
  134. package/dist/runtime/routes/publish.d.ts.map +1 -0
  135. package/dist/runtime/routes/publish.js +75 -0
  136. package/dist/runtime/routes/publish.js.map +1 -0
  137. package/dist/runtime/routes/schema.d.ts +1 -0
  138. package/dist/runtime/routes/schema.d.ts.map +1 -1
  139. package/dist/runtime/routes/schema.js +7 -4
  140. package/dist/runtime/routes/schema.js.map +1 -1
  141. package/dist/runtime/routes/studio-home.d.ts +1 -0
  142. package/dist/runtime/routes/studio-home.d.ts.map +1 -1
  143. package/dist/runtime/routes/studio-home.js +10 -9
  144. package/dist/runtime/routes/studio-home.js.map +1 -1
  145. package/dist/runtime/routes/theme-stylesheet.d.ts +1 -0
  146. package/dist/runtime/routes/theme-stylesheet.d.ts.map +1 -1
  147. package/dist/runtime/routes/theme-stylesheet.js +1 -0
  148. package/dist/runtime/routes/theme-stylesheet.js.map +1 -1
  149. package/dist/runtime/routes/upload.d.ts +1 -0
  150. package/dist/runtime/routes/upload.d.ts.map +1 -1
  151. package/dist/runtime/routes/upload.js +17 -3
  152. package/dist/runtime/routes/upload.js.map +1 -1
  153. package/dist/runtime/sanitize-html.d.ts +8 -2
  154. package/dist/runtime/sanitize-html.d.ts.map +1 -1
  155. package/dist/runtime/sanitize-html.js +57 -38
  156. package/dist/runtime/sanitize-html.js.map +1 -1
  157. package/dist/runtime/static-bake.d.ts +20 -0
  158. package/dist/runtime/static-bake.d.ts.map +1 -0
  159. package/dist/runtime/static-bake.js +58 -0
  160. package/dist/runtime/static-bake.js.map +1 -0
  161. package/dist/runtime/stega.d.ts +38 -0
  162. package/dist/runtime/stega.d.ts.map +1 -0
  163. package/dist/runtime/stega.js +98 -0
  164. package/dist/runtime/stega.js.map +1 -0
  165. package/dist/runtime/storage/atomic-write.d.ts +22 -0
  166. package/dist/runtime/storage/atomic-write.d.ts.map +1 -0
  167. package/dist/runtime/storage/atomic-write.js +44 -0
  168. package/dist/runtime/storage/atomic-write.js.map +1 -0
  169. package/dist/runtime/storage/filesystem-adapter.d.ts +14 -11
  170. package/dist/runtime/storage/filesystem-adapter.d.ts.map +1 -1
  171. package/dist/runtime/storage/filesystem-adapter.js +71 -201
  172. package/dist/runtime/storage/filesystem-adapter.js.map +1 -1
  173. package/dist/runtime/storage/frontmatter-codec.d.ts +47 -0
  174. package/dist/runtime/storage/frontmatter-codec.d.ts.map +1 -0
  175. package/dist/runtime/storage/frontmatter-codec.js +523 -0
  176. package/dist/runtime/storage/frontmatter-codec.js.map +1 -0
  177. package/dist/runtime/storage/fs-runtime.d.ts +8 -0
  178. package/dist/runtime/storage/fs-runtime.d.ts.map +1 -0
  179. package/dist/runtime/storage/fs-runtime.js +40 -0
  180. package/dist/runtime/storage/fs-runtime.js.map +1 -0
  181. package/dist/runtime/storage/id-contracts.d.ts +22 -0
  182. package/dist/runtime/storage/id-contracts.d.ts.map +1 -0
  183. package/dist/runtime/storage/id-contracts.js +27 -0
  184. package/dist/runtime/storage/id-contracts.js.map +1 -0
  185. package/dist/runtime/storage/image-validation.d.ts +10 -0
  186. package/dist/runtime/storage/image-validation.d.ts.map +1 -1
  187. package/dist/runtime/storage/image-validation.js +17 -4
  188. package/dist/runtime/storage/image-validation.js.map +1 -1
  189. package/dist/runtime/storage/in-memory-adapter.d.ts +4 -0
  190. package/dist/runtime/storage/in-memory-adapter.d.ts.map +1 -1
  191. package/dist/runtime/storage/in-memory-adapter.js +11 -0
  192. package/dist/runtime/storage/in-memory-adapter.js.map +1 -1
  193. package/dist/runtime/storage/markdown-adapter.d.ts +63 -0
  194. package/dist/runtime/storage/markdown-adapter.d.ts.map +1 -0
  195. package/dist/runtime/storage/markdown-adapter.js +227 -0
  196. package/dist/runtime/storage/markdown-adapter.js.map +1 -0
  197. package/dist/runtime/storage/quota-upload-handler.d.ts +2 -0
  198. package/dist/runtime/storage/quota-upload-handler.d.ts.map +1 -1
  199. package/dist/runtime/storage/quota-upload-handler.js +34 -8
  200. package/dist/runtime/storage/quota-upload-handler.js.map +1 -1
  201. package/dist/runtime/storage/session-overlay-adapter.d.ts +3 -0
  202. package/dist/runtime/storage/session-overlay-adapter.d.ts.map +1 -1
  203. package/dist/runtime/storage/session-overlay-adapter.js +3 -1
  204. package/dist/runtime/storage/session-overlay-adapter.js.map +1 -1
  205. package/dist/runtime/storage/sidecar-meta-store.d.ts +51 -0
  206. package/dist/runtime/storage/sidecar-meta-store.d.ts.map +1 -0
  207. package/dist/runtime/storage/sidecar-meta-store.js +209 -0
  208. package/dist/runtime/storage/sidecar-meta-store.js.map +1 -0
  209. package/dist/runtime/themes/preset-tokens.d.ts +0 -1
  210. package/dist/runtime/themes/preset-tokens.d.ts.map +1 -1
  211. package/dist/runtime/themes/preset-tokens.js +1 -1
  212. package/dist/runtime/themes/preset-tokens.js.map +1 -1
  213. package/dist/runtime/utils.d.ts +9 -1
  214. package/dist/runtime/utils.d.ts.map +1 -1
  215. package/dist/runtime/utils.js +21 -1
  216. package/dist/runtime/utils.js.map +1 -1
  217. package/dist/runtime/views/studio-layout.d.ts.map +1 -1
  218. package/dist/runtime/views/studio-layout.js +11 -10
  219. package/dist/runtime/views/studio-layout.js.map +1 -1
  220. package/dist/schema-utils.d.ts.map +1 -1
  221. package/dist/schema-utils.js +1 -0
  222. package/dist/schema-utils.js.map +1 -1
  223. package/dist/types.d.ts +17 -0
  224. package/dist/types.d.ts.map +1 -1
  225. package/package.json +9 -1
  226. package/static/cms/dev-toolbar/app.js +265 -0
  227. package/static/cms/editor/config.js +10 -0
  228. package/static/cms/editor/helpers.js +5 -1
  229. package/static/cms/editor/highlight.js +44 -0
  230. package/static/cms/editor/sanitize.js +56 -9
  231. package/static/cms/editor/stega-hydrate.js +80 -0
  232. package/static/cms/editor/text-edit.js +22 -0
  233. package/static/cms/editor/tokens-and-text.css +1 -1
  234. package/static/cms/editor/toolbar.css +101 -4
  235. package/static/cms/editor/toolbar.js +171 -21
  236. package/static/cms/editor.js +18 -1
  237. package/static/cms/signin-hint.css +74 -0
  238. package/static/cms/studio-login.css +70 -0
  239. package/static/cms/studio.css +80 -13
  240. package/dist/runtime/schema/template.d.ts +0 -15
  241. package/dist/runtime/schema/template.d.ts.map +0 -1
  242. package/dist/runtime/schema/template.js +0 -38
  243. package/dist/runtime/schema/template.js.map +0 -1
@@ -78,6 +78,45 @@
78
78
  display: flex;
79
79
  align-items: center;
80
80
  gap: 12px;
81
+ min-width: 0;
82
+ }
83
+
84
+ @media (max-width: 768px) {
85
+ .cms-toolbar-inner {
86
+ padding: 0 12px;
87
+ gap: 8px;
88
+ }
89
+
90
+ .cms-toolbar-left {
91
+ gap: 8px;
92
+ flex-shrink: 0;
93
+ }
94
+
95
+ .cms-status-text {
96
+ display: none;
97
+ }
98
+
99
+ .cms-toolbar-right {
100
+ gap: 6px;
101
+ overflow-x: auto;
102
+ -webkit-overflow-scrolling: touch;
103
+ scrollbar-width: none;
104
+ flex-shrink: 1;
105
+ padding-left: 4px;
106
+ }
107
+
108
+ .cms-toolbar-right::-webkit-scrollbar {
109
+ display: none;
110
+ }
111
+
112
+ .cms-toolbar-divider {
113
+ display: none;
114
+ }
115
+
116
+ .cms-toolbar button {
117
+ padding: 6px 10px;
118
+ flex-shrink: 0;
119
+ }
81
120
  }
82
121
 
83
122
  /* Shared base for every toolbar button so they can't drift visually. Individual
@@ -88,10 +127,10 @@
88
127
  gap: 6px;
89
128
  padding: 6px 14px;
90
129
  font-family: var(--cms-font);
91
- font-size: 10px;
130
+ font-size: 11px;
92
131
  font-weight: 500;
93
132
  line-height: 1.2;
94
- letter-spacing: 0.12em;
133
+ letter-spacing: 0.08em;
95
134
  text-transform: uppercase;
96
135
  background: transparent;
97
136
  color: rgba(255, 255, 255, 0.5);
@@ -175,6 +214,52 @@
175
214
  color: rgba(59, 130, 246, 0.9);
176
215
  }
177
216
 
217
+ /* The base `.cms-toolbar button` sets `display`, which overrides the default
218
+ [hidden] behavior — restore it so hidden draft actions actually disappear. */
219
+ .cms-toolbar button[hidden] {
220
+ display: none;
221
+ }
222
+
223
+ /* Draft preview active — amber, signalling "unpublished" */
224
+ .cms-toolbar button.cms-preview-btn-active {
225
+ background: rgba(234, 179, 8, 0.12);
226
+ border-color: rgba(234, 179, 8, 0.4);
227
+ color: var(--cms-yellow);
228
+ }
229
+
230
+ /* Go live — primary action on static delivery */
231
+ .cms-toolbar button.cms-go-live-btn {
232
+ background: rgba(34, 197, 94, 0.12);
233
+ border-color: rgba(34, 197, 94, 0.3);
234
+ color: var(--cms-green);
235
+ }
236
+
237
+ .cms-toolbar button.cms-go-live-btn:hover {
238
+ background: rgba(34, 197, 94, 0.25);
239
+ border-color: rgba(34, 197, 94, 0.5);
240
+ color: var(--cms-green);
241
+ }
242
+
243
+ /* Publish — green (server delivery when preview is manually enabled) */
244
+ .cms-toolbar button.cms-publish-btn {
245
+ background: rgba(34, 197, 94, 0.12);
246
+ border-color: rgba(34, 197, 94, 0.3);
247
+ color: var(--cms-green);
248
+ }
249
+
250
+ .cms-toolbar button.cms-publish-btn:hover {
251
+ background: rgba(34, 197, 94, 0.25);
252
+ border-color: rgba(34, 197, 94, 0.5);
253
+ color: var(--cms-green);
254
+ }
255
+
256
+ /* Discard — red on hover */
257
+ .cms-toolbar button.cms-discard-btn:hover {
258
+ background: rgba(248, 113, 113, 0.08);
259
+ border-color: rgba(248, 113, 113, 0.3);
260
+ color: var(--cms-red);
261
+ }
262
+
178
263
  /* Highlight all mode — show every editable region */
179
264
  .cms-highlight-all [data-caret].cms-editable {
180
265
  outline-color: var(--cms-blue-hover);
@@ -186,6 +271,15 @@
186
271
  outline-offset: 4px;
187
272
  }
188
273
 
274
+ /* The welcome reveal still flashes editable regions, but without animating the
275
+ transition — motion is suppressed, the information is not. */
276
+ @media (prefers-reduced-motion: reduce) {
277
+ .cms-highlight-all [data-caret].cms-editable,
278
+ .cms-highlight-all img[data-caret] {
279
+ transition: none;
280
+ }
281
+ }
282
+
189
283
  /* ---------------------------------------------------------------------------
190
284
  Toolbar — page navigation links
191
285
  --------------------------------------------------------------------------- */
@@ -204,8 +298,11 @@
204
298
 
205
299
  .cms-nav-link {
206
300
  padding: 5px 14px;
207
- font-size: 10px;
208
- letter-spacing: 0.1em;
301
+ font-family: var(--cms-font);
302
+ font-size: 11px;
303
+ font-weight: 500;
304
+ line-height: 1.2;
305
+ letter-spacing: 0.08em;
209
306
  text-transform: uppercase;
210
307
  text-decoration: none;
211
308
  color: rgba(255, 255, 255, 0.35);
@@ -1,3 +1,38 @@
1
+ import { toggleHighlight } from './highlight.js';
2
+ import { buildCmsUrl, isStaticDelivery } from './config.js';
3
+
4
+ const PREVIEW_COOKIE = 'caret_preview';
5
+
6
+ function previewActive() {
7
+ return document.cookie.split('; ').some((c) => c === `${PREVIEW_COOKIE}=1`);
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
+ }
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
+
1
36
  function getToolbarNavLinks(pagePath) {
2
37
  const links = [];
3
38
  const headerNav = document.querySelector('#main-header nav');
@@ -14,7 +49,22 @@ function getToolbarNavLinks(pagePath) {
14
49
  return links;
15
50
  }
16
51
 
17
- function renderToolbar(navLinks) {
52
+ function renderDraftControls(staticDelivery) {
53
+ if (!staticDelivery) return '';
54
+
55
+ return `
56
+ <button class="cms-publish-btn cms-go-live-btn" type="button" title="Push your draft changes to the live site">
57
+ <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>
58
+ <span class="cms-go-live-label">Go live</span>
59
+ </button>
60
+ <button class="cms-discard-btn" type="button" title="Discard your draft changes">
61
+ <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>
62
+ Discard
63
+ </button>
64
+ <div class="cms-toolbar-divider"></div>`;
65
+ }
66
+
67
+ function renderToolbar(navLinks, staticDelivery) {
18
68
  const navLinksHtml = navLinks
19
69
  .map(
20
70
  ({ href, label, isActive }) =>
@@ -22,14 +72,20 @@ function renderToolbar(navLinks) {
22
72
  )
23
73
  .join('');
24
74
 
75
+ const badgeLabel = staticDelivery ? 'Draft' : 'Live';
76
+ const badgeHint = staticDelivery
77
+ ? 'Not on your site until you go live'
78
+ : 'Changes save directly to your site';
79
+
25
80
  const toolbar = document.createElement('div');
26
81
  toolbar.className = 'cms-toolbar';
82
+ toolbar.dataset.delivery = staticDelivery ? 'static' : 'server';
27
83
  toolbar.innerHTML = `
28
84
  <div class="cms-toolbar-inner">
29
85
  <div class="cms-toolbar-left">
30
- <div class="cms-toolbar-badge">
86
+ <div class="cms-toolbar-badge" title="${badgeHint}">
31
87
  <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>
32
- <span class="cms-toolbar-badge-text">Editor</span>
88
+ <span class="cms-toolbar-badge-text">${badgeLabel}</span>
33
89
  </div>
34
90
  <div class="cms-toolbar-divider"></div>
35
91
  <div class="cms-status-group">
@@ -39,6 +95,7 @@ function renderToolbar(navLinks) {
39
95
  </div>
40
96
  ${navLinksHtml ? `<div class="cms-toolbar-nav">${navLinksHtml}</div>` : ''}
41
97
  <div class="cms-toolbar-right">
98
+ ${renderDraftControls(staticDelivery)}
42
99
  <button type="button" class="cms-studio-btn" title="Toggle Content Studio panel">
43
100
  <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>
44
101
  Studio
@@ -51,12 +108,12 @@ function renderToolbar(navLinks) {
51
108
  <div class="cms-toolbar-divider"></div>
52
109
  <button class="cms-highlight-btn" type="button" title="Show all editable regions">
53
110
  <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>
54
- Show All
111
+ <span class="cms-highlight-label">Show all</span>
55
112
  </button>
56
113
  <div class="cms-toolbar-divider"></div>
57
- <button class="cms-exit-btn" type="button">
114
+ <button class="cms-exit-btn" type="button" title="Sign out">
58
115
  <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>
59
- Log Out
116
+ Sign out
60
117
  </button>
61
118
  </div>
62
119
  </div>
@@ -66,8 +123,9 @@ function renderToolbar(navLinks) {
66
123
  }
67
124
 
68
125
  export function mountToolbar({ showToast, clearDirty, onLogout }) {
126
+ const staticDelivery = isStaticDelivery();
69
127
  const navLinks = getToolbarNavLinks(window.location.pathname);
70
- const toolbar = renderToolbar(navLinks);
128
+ const toolbar = renderToolbar(navLinks, staticDelivery);
71
129
 
72
130
  const statusDot = toolbar.querySelector('.cms-status-dot');
73
131
  const statusText = toolbar.querySelector('.cms-status-text');
@@ -81,31 +139,123 @@ export function mountToolbar({ showToast, clearDirty, onLogout }) {
81
139
  }
82
140
  }
83
141
 
84
- let highlightActive = false;
85
142
  const highlightBtn = toolbar.querySelector('.cms-highlight-btn');
143
+ highlightBtn?.addEventListener('click', () =>
144
+ toggleHighlight(highlightBtn, showToast),
145
+ );
86
146
 
87
- highlightBtn?.addEventListener('click', () => {
88
- highlightActive = !highlightActive;
89
- document.body.classList.toggle('cms-highlight-all', highlightActive);
90
- highlightBtn.classList.toggle('cms-highlight-btn-active', highlightActive);
147
+ async function draftRequest(method, path) {
148
+ return fetch(buildCmsUrl(path), {
149
+ method,
150
+ headers: { 'Content-Type': 'application/json', 'x-caret-request': '1' },
151
+ credentials: 'same-origin',
152
+ body: method === 'POST' ? '{}' : undefined,
153
+ });
154
+ }
91
155
 
92
- const textNode = Array.from(highlightBtn.childNodes).find(
93
- (n) => n.nodeType === 3 && n.textContent?.trim(),
94
- );
156
+ async function fetchDraftCount() {
157
+ try {
158
+ const res = await fetch(buildCmsUrl('/draft'), {
159
+ credentials: 'same-origin',
160
+ headers: { 'x-caret-request': '1' },
161
+ });
162
+ if (!res.ok) return 0;
163
+ const data = await res.json();
164
+ return typeof data.count === 'number' ? data.count : 0;
165
+ } catch {
166
+ return 0;
167
+ }
168
+ }
95
169
 
96
- if (highlightActive) {
97
- if (textNode) textNode.textContent = ' Hide All';
98
- showToast('Showing all editable regions', 'success');
99
- } else {
100
- if (textNode) textNode.textContent = ' Show All';
170
+ async function publishDrafts() {
171
+ setStatus('saving', 'Going live…');
172
+ try {
173
+ const res = await draftRequest('POST', '/publish');
174
+ if (!res.ok) throw new Error('publish failed');
175
+ const data = await res.json();
176
+ const n = Array.isArray(data.published) ? data.published.length : 0;
177
+ if (data.rebuild?.triggered && data.rebuild.ok === false) {
178
+ setStatus('error', 'Live, but deploy failed');
179
+ showToast(
180
+ 'Changes are saved, but the deploy webhook failed. Check your CI settings.',
181
+ 'error',
182
+ );
183
+ return false;
184
+ }
185
+ if (data.rebuild?.triggered) {
186
+ showToast(
187
+ n > 0
188
+ ? `Went live with ${n} change(s) — your site is rebuilding`
189
+ : 'Your site is rebuilding',
190
+ 'success',
191
+ );
192
+ } else {
193
+ showToast(n > 0 ? `Went live with ${n} change(s)` : 'Already up to date', 'success');
194
+ }
195
+ window.location.reload();
196
+ return true;
197
+ } catch {
198
+ setStatus('error', 'Go live failed');
199
+ showToast('Go live failed', 'error');
200
+ return false;
101
201
  }
102
- });
202
+ }
103
203
 
104
204
  toolbar.querySelector('.cms-exit-btn')?.addEventListener('click', async () => {
205
+ if (staticDelivery) {
206
+ const count = await fetchDraftCount();
207
+ if (count > 0) {
208
+ const goLive = window.confirm(
209
+ `You have ${count} unpublished change${count === 1 ? '' : 's'}. Go live before signing out?`,
210
+ );
211
+ if (goLive) {
212
+ await publishDrafts();
213
+ return;
214
+ }
215
+ const discard = window.confirm(
216
+ 'Discard unpublished changes and sign out?',
217
+ );
218
+ if (!discard) return;
219
+ try {
220
+ const res = await draftRequest('DELETE', '/draft');
221
+ if (!res.ok) throw new Error('discard failed');
222
+ } catch {
223
+ showToast('Could not discard drafts', 'error');
224
+ return;
225
+ }
226
+ }
227
+ }
228
+
105
229
  clearDirty();
106
230
  await onLogout();
107
231
  });
108
232
 
233
+ const publishBtn = toolbar.querySelector('.cms-publish-btn');
234
+ publishBtn?.addEventListener('click', async () => {
235
+ if (
236
+ !window.confirm(
237
+ staticDelivery
238
+ ? 'Push all your draft changes to the live site?'
239
+ : 'Publish all your draft changes to the live site?',
240
+ )
241
+ ) {
242
+ return;
243
+ }
244
+ await publishDrafts();
245
+ });
246
+
247
+ toolbar.querySelector('.cms-discard-btn')?.addEventListener('click', async () => {
248
+ if (!window.confirm('Discard all your draft changes? This cannot be undone.')) return;
249
+ try {
250
+ const res = await draftRequest('DELETE', '/draft');
251
+ if (!res.ok) throw new Error('discard failed');
252
+ showToast('Draft discarded', 'success');
253
+ window.location.reload();
254
+ } catch {
255
+ showToast('Discard failed', 'error');
256
+ }
257
+ });
258
+
109
259
  const studioButton = toolbar.querySelector('.cms-studio-btn');
110
260
  const mapButton = toolbar.querySelector('.cms-map-btn');
111
261
  return {
@@ -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';
@@ -25,17 +25,25 @@ 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
27
  import { mountLinkFollowAffordances } from './editor/link-follow.js';
28
+ import { revealAndFade } from './editor/highlight.js';
29
+ import { hydrateStega } from './editor/stega-hydrate.js';
28
30
 
29
31
  function redirectToEditorLogin() {
30
32
  window.location.href = getEditorLoginUrl(window.location.href);
31
33
  }
32
34
 
33
35
  function boot() {
36
+ if (normalizePreviewForDelivery()) return;
37
+
34
38
  const state = {
35
39
  dirtyEl: null,
36
40
  linkPopupEl: null,
37
41
  };
38
42
 
43
+ // Promote stega-tagged (loader-fed) content into data-caret bindings before
44
+ // any scanner runs, so prop/component/loop content is click-to-edit too.
45
+ hydrateStega();
46
+
39
47
  mountEditorGuards(state);
40
48
  mountLinkFollowAffordances();
41
49
 
@@ -107,6 +115,15 @@ function boot() {
107
115
  });
108
116
 
109
117
  mountInlineEditors();
118
+
119
+ // First load after login: flash every editable region once, then fade out.
120
+ // The flag is set by the login form just before it redirects here.
121
+ try {
122
+ if (sessionStorage.getItem('caret:welcome')) {
123
+ sessionStorage.removeItem('caret:welcome');
124
+ revealAndFade(2500);
125
+ }
126
+ } catch (e) {}
110
127
  }
111
128
 
112
129
  if (document.readyState === 'loading') {
@@ -0,0 +1,74 @@
1
+ /*
2
+ * The authenticated empty-state hint: shown by the middleware on a live page
3
+ * when an editor is signed in but the page has zero `data-caret` bindings — the
4
+ * silent dead-end this replaces (you land on a normal page with no sign the
5
+ * editor noticed you). Self-contained and isolated (`all: initial` on the host)
6
+ * so it can't be restyled by — or restyle — the host page, and so it needs no
7
+ * inline styles (CSP `style-src 'self'` covers this same-origin file).
8
+ */
9
+ .caret-signin-hint {
10
+ all: initial;
11
+ position: fixed;
12
+ bottom: 16px;
13
+ left: 16px;
14
+ z-index: 2147483000;
15
+ box-sizing: border-box;
16
+ display: flex;
17
+ align-items: center;
18
+ gap: 10px;
19
+ max-width: min(92vw, 400px);
20
+ padding: 10px 14px;
21
+ font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
22
+ font-size: 13px;
23
+ line-height: 1.45;
24
+ color: #e5e7eb;
25
+ background: #111827;
26
+ border: 1px solid #374151;
27
+ border-radius: 10px;
28
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28);
29
+ }
30
+
31
+ .caret-signin-hint__dot {
32
+ flex: 0 0 auto;
33
+ width: 8px;
34
+ height: 8px;
35
+ border-radius: 50%;
36
+ background: #34d399;
37
+ box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.18);
38
+ }
39
+
40
+ .caret-signin-hint__text {
41
+ color: inherit;
42
+ font: inherit;
43
+ }
44
+
45
+ .caret-signin-hint code {
46
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
47
+ font-size: 0.92em;
48
+ color: #f9fafb;
49
+ background: #1f2937;
50
+ padding: 1px 5px;
51
+ border-radius: 5px;
52
+ }
53
+
54
+ .caret-signin-hint a {
55
+ color: #93c5fd;
56
+ font: inherit;
57
+ text-decoration: underline;
58
+ white-space: nowrap;
59
+ }
60
+
61
+ @media (prefers-color-scheme: light) {
62
+ .caret-signin-hint {
63
+ color: #1f2937;
64
+ background: #ffffff;
65
+ border-color: #e5e7eb;
66
+ }
67
+ .caret-signin-hint code {
68
+ color: #111827;
69
+ background: #f3f4f6;
70
+ }
71
+ .caret-signin-hint a {
72
+ color: #2563eb;
73
+ }
74
+ }
@@ -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
+ }