@caretcms/core 0.1.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.
Files changed (249) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +191 -0
  3. package/dist/browser-runtime.d.ts +12 -0
  4. package/dist/browser-runtime.d.ts.map +1 -0
  5. package/dist/browser-runtime.js +298 -0
  6. package/dist/browser-runtime.js.map +1 -0
  7. package/dist/index.d.ts +81 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +343 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/loader.d.ts +61 -0
  12. package/dist/loader.d.ts.map +1 -0
  13. package/dist/loader.js +80 -0
  14. package/dist/loader.js.map +1 -0
  15. package/dist/providers/storage/filesystem.d.ts +7 -0
  16. package/dist/providers/storage/filesystem.d.ts.map +1 -0
  17. package/dist/providers/storage/filesystem.js +6 -0
  18. package/dist/providers/storage/filesystem.js.map +1 -0
  19. package/dist/providers/uploads/local.d.ts +6 -0
  20. package/dist/providers/uploads/local.d.ts.map +1 -0
  21. package/dist/providers/uploads/local.js +6 -0
  22. package/dist/providers/uploads/local.js.map +1 -0
  23. package/dist/runtime/auth/cookie-utils.d.ts +3 -0
  24. package/dist/runtime/auth/cookie-utils.d.ts.map +1 -0
  25. package/dist/runtime/auth/cookie-utils.js +33 -0
  26. package/dist/runtime/auth/cookie-utils.js.map +1 -0
  27. package/dist/runtime/auth/demo-session.d.ts +24 -0
  28. package/dist/runtime/auth/demo-session.d.ts.map +1 -0
  29. package/dist/runtime/auth/demo-session.js +42 -0
  30. package/dist/runtime/auth/demo-session.js.map +1 -0
  31. package/dist/runtime/auth/rate-limiter.d.ts +26 -0
  32. package/dist/runtime/auth/rate-limiter.d.ts.map +1 -0
  33. package/dist/runtime/auth/rate-limiter.js +65 -0
  34. package/dist/runtime/auth/rate-limiter.js.map +1 -0
  35. package/dist/runtime/auth/session.d.ts +14 -0
  36. package/dist/runtime/auth/session.d.ts.map +1 -0
  37. package/dist/runtime/auth/session.js +128 -0
  38. package/dist/runtime/auth/session.js.map +1 -0
  39. package/dist/runtime/bind.d.ts +14 -0
  40. package/dist/runtime/bind.d.ts.map +1 -0
  41. package/dist/runtime/bind.js +19 -0
  42. package/dist/runtime/bind.js.map +1 -0
  43. package/dist/runtime/config.d.ts +20 -0
  44. package/dist/runtime/config.d.ts.map +1 -0
  45. package/dist/runtime/config.js +40 -0
  46. package/dist/runtime/config.js.map +1 -0
  47. package/dist/runtime/content.d.ts +28 -0
  48. package/dist/runtime/content.d.ts.map +1 -0
  49. package/dist/runtime/content.js +37 -0
  50. package/dist/runtime/content.js.map +1 -0
  51. package/dist/runtime/index.d.ts +14 -0
  52. package/dist/runtime/index.d.ts.map +1 -0
  53. package/dist/runtime/index.js +16 -0
  54. package/dist/runtime/index.js.map +1 -0
  55. package/dist/runtime/middleware.d.ts +14 -0
  56. package/dist/runtime/middleware.d.ts.map +1 -0
  57. package/dist/runtime/middleware.js +80 -0
  58. package/dist/runtime/middleware.js.map +1 -0
  59. package/dist/runtime/mutations/contracts.d.ts +91 -0
  60. package/dist/runtime/mutations/contracts.d.ts.map +1 -0
  61. package/dist/runtime/mutations/contracts.js +320 -0
  62. package/dist/runtime/mutations/contracts.js.map +1 -0
  63. package/dist/runtime/mutations/engine.d.ts +23 -0
  64. package/dist/runtime/mutations/engine.d.ts.map +1 -0
  65. package/dist/runtime/mutations/engine.js +255 -0
  66. package/dist/runtime/mutations/engine.js.map +1 -0
  67. package/dist/runtime/providers.d.ts +9 -0
  68. package/dist/runtime/providers.d.ts.map +1 -0
  69. package/dist/runtime/providers.js +44 -0
  70. package/dist/runtime/providers.js.map +1 -0
  71. package/dist/runtime/request-context.d.ts +11 -0
  72. package/dist/runtime/request-context.d.ts.map +1 -0
  73. package/dist/runtime/request-context.js +16 -0
  74. package/dist/runtime/request-context.js.map +1 -0
  75. package/dist/runtime/rewrite.d.ts +13 -0
  76. package/dist/runtime/rewrite.d.ts.map +1 -0
  77. package/dist/runtime/rewrite.js +253 -0
  78. package/dist/runtime/rewrite.js.map +1 -0
  79. package/dist/runtime/routes/_helpers.d.ts +17 -0
  80. package/dist/runtime/routes/_helpers.d.ts.map +1 -0
  81. package/dist/runtime/routes/_helpers.js +38 -0
  82. package/dist/runtime/routes/_helpers.js.map +1 -0
  83. package/dist/runtime/routes/admin-entry.d.ts +3 -0
  84. package/dist/runtime/routes/admin-entry.d.ts.map +1 -0
  85. package/dist/runtime/routes/admin-entry.js +170 -0
  86. package/dist/runtime/routes/admin-entry.js.map +1 -0
  87. package/dist/runtime/routes/admin.d.ts +3 -0
  88. package/dist/runtime/routes/admin.d.ts.map +1 -0
  89. package/dist/runtime/routes/admin.js +189 -0
  90. package/dist/runtime/routes/admin.js.map +1 -0
  91. package/dist/runtime/routes/auth-login.d.ts +4 -0
  92. package/dist/runtime/routes/auth-login.d.ts.map +1 -0
  93. package/dist/runtime/routes/auth-login.js +71 -0
  94. package/dist/runtime/routes/auth-login.js.map +1 -0
  95. package/dist/runtime/routes/auth-logout.d.ts +4 -0
  96. package/dist/runtime/routes/auth-logout.d.ts.map +1 -0
  97. package/dist/runtime/routes/auth-logout.js +47 -0
  98. package/dist/runtime/routes/auth-logout.js.map +1 -0
  99. package/dist/runtime/routes/auth-session.d.ts +3 -0
  100. package/dist/runtime/routes/auth-session.d.ts.map +1 -0
  101. package/dist/runtime/routes/auth-session.js +8 -0
  102. package/dist/runtime/routes/auth-session.js.map +1 -0
  103. package/dist/runtime/routes/collection-list.d.ts +3 -0
  104. package/dist/runtime/routes/collection-list.d.ts.map +1 -0
  105. package/dist/runtime/routes/collection-list.js +420 -0
  106. package/dist/runtime/routes/collection-list.js.map +1 -0
  107. package/dist/runtime/routes/collections-metadata.d.ts +3 -0
  108. package/dist/runtime/routes/collections-metadata.d.ts.map +1 -0
  109. package/dist/runtime/routes/collections-metadata.js +17 -0
  110. package/dist/runtime/routes/collections-metadata.js.map +1 -0
  111. package/dist/runtime/routes/editor-assets.d.ts +3 -0
  112. package/dist/runtime/routes/editor-assets.d.ts.map +1 -0
  113. package/dist/runtime/routes/editor-assets.js +73 -0
  114. package/dist/runtime/routes/editor-assets.js.map +1 -0
  115. package/dist/runtime/routes/entries.d.ts +3 -0
  116. package/dist/runtime/routes/entries.d.ts.map +1 -0
  117. package/dist/runtime/routes/entries.js +96 -0
  118. package/dist/runtime/routes/entries.js.map +1 -0
  119. package/dist/runtime/routes/history.d.ts +4 -0
  120. package/dist/runtime/routes/history.d.ts.map +1 -0
  121. package/dist/runtime/routes/history.js +54 -0
  122. package/dist/runtime/routes/history.js.map +1 -0
  123. package/dist/runtime/routes/mutate.d.ts +4 -0
  124. package/dist/runtime/routes/mutate.d.ts.map +1 -0
  125. package/dist/runtime/routes/mutate.js +38 -0
  126. package/dist/runtime/routes/mutate.js.map +1 -0
  127. package/dist/runtime/routes/schema.d.ts +3 -0
  128. package/dist/runtime/routes/schema.d.ts.map +1 -0
  129. package/dist/runtime/routes/schema.js +58 -0
  130. package/dist/runtime/routes/schema.js.map +1 -0
  131. package/dist/runtime/routes/studio-home.d.ts +3 -0
  132. package/dist/runtime/routes/studio-home.d.ts.map +1 -0
  133. package/dist/runtime/routes/studio-home.js +99 -0
  134. package/dist/runtime/routes/studio-home.js.map +1 -0
  135. package/dist/runtime/routes/theme-stylesheet.d.ts +3 -0
  136. package/dist/runtime/routes/theme-stylesheet.d.ts.map +1 -0
  137. package/dist/runtime/routes/theme-stylesheet.js +32 -0
  138. package/dist/runtime/routes/theme-stylesheet.js.map +1 -0
  139. package/dist/runtime/routes/upload.d.ts +3 -0
  140. package/dist/runtime/routes/upload.d.ts.map +1 -0
  141. package/dist/runtime/routes/upload.js +33 -0
  142. package/dist/runtime/routes/upload.js.map +1 -0
  143. package/dist/runtime/sanitize-html.d.ts +8 -0
  144. package/dist/runtime/sanitize-html.d.ts.map +1 -0
  145. package/dist/runtime/sanitize-html.js +86 -0
  146. package/dist/runtime/sanitize-html.js.map +1 -0
  147. package/dist/runtime/schema/template.d.ts +15 -0
  148. package/dist/runtime/schema/template.d.ts.map +1 -0
  149. package/dist/runtime/schema/template.js +38 -0
  150. package/dist/runtime/schema/template.js.map +1 -0
  151. package/dist/runtime/schema-registry.d.ts +17 -0
  152. package/dist/runtime/schema-registry.d.ts.map +1 -0
  153. package/dist/runtime/schema-registry.js +16 -0
  154. package/dist/runtime/schema-registry.js.map +1 -0
  155. package/dist/runtime/storage/filesystem-adapter.d.ts +36 -0
  156. package/dist/runtime/storage/filesystem-adapter.d.ts.map +1 -0
  157. package/dist/runtime/storage/filesystem-adapter.js +291 -0
  158. package/dist/runtime/storage/filesystem-adapter.js.map +1 -0
  159. package/dist/runtime/storage/filesystem-upload-handler.d.ts +11 -0
  160. package/dist/runtime/storage/filesystem-upload-handler.d.ts.map +1 -0
  161. package/dist/runtime/storage/filesystem-upload-handler.js +20 -0
  162. package/dist/runtime/storage/filesystem-upload-handler.js.map +1 -0
  163. package/dist/runtime/storage/image-validation.d.ts +28 -0
  164. package/dist/runtime/storage/image-validation.d.ts.map +1 -0
  165. package/dist/runtime/storage/image-validation.js +94 -0
  166. package/dist/runtime/storage/image-validation.js.map +1 -0
  167. package/dist/runtime/storage/in-memory-adapter.d.ts +29 -0
  168. package/dist/runtime/storage/in-memory-adapter.d.ts.map +1 -0
  169. package/dist/runtime/storage/in-memory-adapter.js +110 -0
  170. package/dist/runtime/storage/in-memory-adapter.js.map +1 -0
  171. package/dist/runtime/storage/quota-upload-handler.d.ts +27 -0
  172. package/dist/runtime/storage/quota-upload-handler.d.ts.map +1 -0
  173. package/dist/runtime/storage/quota-upload-handler.js +34 -0
  174. package/dist/runtime/storage/quota-upload-handler.js.map +1 -0
  175. package/dist/runtime/storage/session-overlay-adapter.d.ts +33 -0
  176. package/dist/runtime/storage/session-overlay-adapter.d.ts.map +1 -0
  177. package/dist/runtime/storage/session-overlay-adapter.js +121 -0
  178. package/dist/runtime/storage/session-overlay-adapter.js.map +1 -0
  179. package/dist/runtime/themes/preset-tokens.d.ts +17 -0
  180. package/dist/runtime/themes/preset-tokens.d.ts.map +1 -0
  181. package/dist/runtime/themes/preset-tokens.js +47 -0
  182. package/dist/runtime/themes/preset-tokens.js.map +1 -0
  183. package/dist/runtime/utils.d.ts +6 -0
  184. package/dist/runtime/utils.d.ts.map +1 -0
  185. package/dist/runtime/utils.js +29 -0
  186. package/dist/runtime/utils.js.map +1 -0
  187. package/dist/runtime/views/studio-layout.d.ts +25 -0
  188. package/dist/runtime/views/studio-layout.d.ts.map +1 -0
  189. package/dist/runtime/views/studio-layout.js +108 -0
  190. package/dist/runtime/views/studio-layout.js.map +1 -0
  191. package/dist/schema-utils.d.ts +16 -0
  192. package/dist/schema-utils.d.ts.map +1 -0
  193. package/dist/schema-utils.js +62 -0
  194. package/dist/schema-utils.js.map +1 -0
  195. package/dist/types.d.ts +76 -0
  196. package/dist/types.d.ts.map +1 -0
  197. package/dist/types.js +2 -0
  198. package/dist/types.js.map +1 -0
  199. package/editor-runtime.d.ts +6 -0
  200. package/editor-runtime.js +2 -0
  201. package/package.json +88 -0
  202. package/static/cms/admin-entry.css +207 -0
  203. package/static/cms/admin-entry.js +1083 -0
  204. package/static/cms/editor/config.js +118 -0
  205. package/static/cms/editor/content-map.css +141 -0
  206. package/static/cms/editor/content-map.js +172 -0
  207. package/static/cms/editor/guards.js +72 -0
  208. package/static/cms/editor/helpers.js +51 -0
  209. package/static/cms/editor/image-edit.js +151 -0
  210. package/static/cms/editor/image-utils.js +84 -0
  211. package/static/cms/editor/image.css +133 -0
  212. package/static/cms/editor/link-follow.js +144 -0
  213. package/static/cms/editor/link-ui.css +199 -0
  214. package/static/cms/editor/linkify.js +55 -0
  215. package/static/cms/editor/panel.css +91 -0
  216. package/static/cms/editor/panel.js +70 -0
  217. package/static/cms/editor/rich-toolbar.css +67 -0
  218. package/static/cms/editor/rich-toolbar.js +200 -0
  219. package/static/cms/editor/sanitize.js +89 -0
  220. package/static/cms/editor/save-queue.js +166 -0
  221. package/static/cms/editor/section-controls/activation.js +10 -0
  222. package/static/cms/editor/section-controls/api.js +87 -0
  223. package/static/cms/editor/section-controls/constants.js +24 -0
  224. package/static/cms/editor/section-controls/index.js +622 -0
  225. package/static/cms/editor/section-controls/model.js +76 -0
  226. package/static/cms/editor/section-controls/mutations.js +112 -0
  227. package/static/cms/editor/section-controls/page-context.js +34 -0
  228. package/static/cms/editor/section-controls/pickers.js +196 -0
  229. package/static/cms/editor/section-controls/reorder.js +92 -0
  230. package/static/cms/editor/section-controls/selection.js +54 -0
  231. package/static/cms/editor/section-controls/spacing-drag.js +83 -0
  232. package/static/cms/editor/section-controls/ui-elements.js +54 -0
  233. package/static/cms/editor/section-controls/utils.js +35 -0
  234. package/static/cms/editor/section-controls.css +349 -0
  235. package/static/cms/editor/section-controls.js +1 -0
  236. package/static/cms/editor/security.js +48 -0
  237. package/static/cms/editor/sync.js +72 -0
  238. package/static/cms/editor/text-edit.js +151 -0
  239. package/static/cms/editor/text-link-interactions.js +254 -0
  240. package/static/cms/editor/toast.css +50 -0
  241. package/static/cms/editor/toast.js +29 -0
  242. package/static/cms/editor/tokens-and-text.css +93 -0
  243. package/static/cms/editor/toolbar.css +249 -0
  244. package/static/cms/editor/toolbar.js +117 -0
  245. package/static/cms/editor.css +12 -0
  246. package/static/cms/editor.js +116 -0
  247. package/static/cms/studio-login.css +300 -0
  248. package/static/cms/studio-tokens.css +85 -0
  249. package/static/cms/studio.css +437 -0
@@ -0,0 +1,117 @@
1
+ function getToolbarNavLinks(pagePath) {
2
+ const links = [];
3
+ const headerNav = document.querySelector('#main-header nav');
4
+ if (!headerNav) return links;
5
+
6
+ headerNav.querySelectorAll('a[href]').forEach((a) => {
7
+ const href = a.getAttribute('href');
8
+ const label = a.textContent?.trim();
9
+ if (href && label && href.startsWith('/')) {
10
+ const isActive = pagePath === href || (href !== '/' && pagePath.startsWith(href));
11
+ links.push({ href, label, isActive });
12
+ }
13
+ });
14
+ return links;
15
+ }
16
+
17
+ function renderToolbar(navLinks) {
18
+ const navLinksHtml = navLinks
19
+ .map(
20
+ ({ href, label, isActive }) =>
21
+ `<a href="${href}" class="cms-nav-link ${isActive ? 'active' : ''}">${label}</a>`,
22
+ )
23
+ .join('');
24
+
25
+ const toolbar = document.createElement('div');
26
+ toolbar.className = 'cms-toolbar';
27
+ toolbar.innerHTML = `
28
+ <div class="cms-toolbar-inner">
29
+ <div class="cms-toolbar-left">
30
+ <div class="cms-toolbar-badge">
31
+ <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>
33
+ </div>
34
+ <div class="cms-toolbar-divider"></div>
35
+ <div class="cms-status-group">
36
+ <span class="cms-status-dot cms-status-idle"></span>
37
+ <span class="cms-status-text">Ready</span>
38
+ </div>
39
+ </div>
40
+ ${navLinksHtml ? `<div class="cms-toolbar-nav">${navLinksHtml}</div>` : ''}
41
+ <div class="cms-toolbar-right">
42
+ <button type="button" class="cms-studio-btn" title="Toggle Content Studio panel">
43
+ <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
+ Studio
45
+ </button>
46
+ <div class="cms-toolbar-divider"></div>
47
+ <button class="cms-map-btn" type="button" title="Content Map — view all editable bindings">
48
+ <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="M14.5 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
49
+ Map
50
+ </button>
51
+ <div class="cms-toolbar-divider"></div>
52
+ <button class="cms-highlight-btn" type="button" title="Show all editable regions">
53
+ <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
55
+ </button>
56
+ <div class="cms-toolbar-divider"></div>
57
+ <button class="cms-exit-btn" type="button">
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="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
60
+ </button>
61
+ </div>
62
+ </div>
63
+ `;
64
+ document.body.appendChild(toolbar);
65
+ return toolbar;
66
+ }
67
+
68
+ export function mountToolbar({ showToast, clearDirty, onLogout }) {
69
+ const navLinks = getToolbarNavLinks(window.location.pathname);
70
+ const toolbar = renderToolbar(navLinks);
71
+
72
+ const statusDot = toolbar.querySelector('.cms-status-dot');
73
+ const statusText = toolbar.querySelector('.cms-status-text');
74
+
75
+ function setStatus(state, text) {
76
+ if (statusDot) {
77
+ statusDot.className = `cms-status-dot cms-status-${state}`;
78
+ }
79
+ if (statusText && text) {
80
+ statusText.textContent = text;
81
+ }
82
+ }
83
+
84
+ let highlightActive = false;
85
+ const highlightBtn = toolbar.querySelector('.cms-highlight-btn');
86
+
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);
91
+
92
+ const textNode = Array.from(highlightBtn.childNodes).find(
93
+ (n) => n.nodeType === 3 && n.textContent?.trim(),
94
+ );
95
+
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';
101
+ }
102
+ });
103
+
104
+ toolbar.querySelector('.cms-exit-btn')?.addEventListener('click', async () => {
105
+ clearDirty();
106
+ await onLogout();
107
+ });
108
+
109
+ const studioButton = toolbar.querySelector('.cms-studio-btn');
110
+ const mapButton = toolbar.querySelector('.cms-map-btn');
111
+ return {
112
+ toolbar,
113
+ studioButton,
114
+ mapButton,
115
+ setStatus,
116
+ };
117
+ }
@@ -0,0 +1,12 @@
1
+ /* ========================================================================
2
+ CMS Editor Styles — only loaded when isEditor = true
3
+ ======================================================================== */
4
+ @import "./editor/tokens-and-text.css";
5
+ @import "./editor/image.css";
6
+ @import "./editor/section-controls.css";
7
+ @import "./editor/toolbar.css";
8
+ @import "./editor/toast.css";
9
+ @import "./editor/panel.css";
10
+ @import "./editor/link-ui.css";
11
+ @import "./editor/rich-toolbar.css";
12
+ @import "./editor/content-map.css";
@@ -0,0 +1,116 @@
1
+ /**
2
+ * CMS Inline Editor runtime entry.
3
+ *
4
+ * This file is intentionally thin and wires independent controllers:
5
+ * - guards
6
+ * - toolbar
7
+ * - studio panel + iframe sync
8
+ * - text editing
9
+ * - image editing
10
+ * - content map
11
+ */
12
+ import { buildCmsUrl, clearCloudSessionToken, getEditorLoginUrl } from './editor/config.js';
13
+ import { parseCaretAttr, flash } from './editor/helpers.js';
14
+ import { clientLinkify } from './editor/linkify.js';
15
+ import { createSaveField } from './editor/save-queue.js';
16
+ import { compressImage, updateEmblaCarousel } from './editor/image-utils.js';
17
+ import { createToast } from './editor/toast.js';
18
+ import { mountEditorGuards } from './editor/guards.js';
19
+ import { mountToolbar } from './editor/toolbar.js';
20
+ import { mountStudioPanel } from './editor/panel.js';
21
+ import { mountStudioSync } from './editor/sync.js';
22
+ import { mountTextEditors } from './editor/text-edit.js';
23
+ import { mountImageEditors } from './editor/image-edit.js';
24
+ import { mountSectionControls } from './editor/section-controls.js';
25
+ import { mountContentMap } from './editor/content-map.js';
26
+ import { mountRichToolbar } from './editor/rich-toolbar.js';
27
+ import { mountLinkFollowAffordances } from './editor/link-follow.js';
28
+
29
+ function redirectToEditorLogin() {
30
+ window.location.href = getEditorLoginUrl(window.location.href);
31
+ }
32
+
33
+ function boot() {
34
+ const state = {
35
+ dirtyEl: null,
36
+ linkPopupEl: null,
37
+ };
38
+
39
+ mountEditorGuards(state);
40
+ mountLinkFollowAffordances();
41
+
42
+ const showToast = createToast();
43
+
44
+ const { studioButton, mapButton, setStatus } = mountToolbar({
45
+ showToast,
46
+ clearDirty: () => {
47
+ state.dirtyEl = null;
48
+ },
49
+ onLogout: async () => {
50
+ await fetch(buildCmsUrl('/auth/logout'), { method: 'POST' });
51
+ clearCloudSessionToken();
52
+ window.location.href = '/';
53
+ },
54
+ });
55
+
56
+ const saveField = createSaveField({
57
+ setStatus,
58
+ onUnauthorized: redirectToEditorLogin,
59
+ });
60
+
61
+ const { iframe: studioIframe } = mountStudioPanel({ studioButton });
62
+
63
+ mountStudioSync({
64
+ studioIframe,
65
+ state,
66
+ showToast,
67
+ clientLinkify,
68
+ });
69
+
70
+ mountContentMap({ mapButton });
71
+
72
+ let richToolbarMounted = false;
73
+
74
+ const mountInlineEditors = () => {
75
+ const { showLinkPopup } = mountTextEditors({
76
+ state,
77
+ parseCaretAttr,
78
+ clientLinkify,
79
+ saveField,
80
+ flash,
81
+ showToast,
82
+ });
83
+
84
+ if (!richToolbarMounted) {
85
+ mountRichToolbar({ state, showToast, showLinkPopup });
86
+ richToolbarMounted = true;
87
+ }
88
+
89
+ mountImageEditors({
90
+ parseCaretAttr,
91
+ flash,
92
+ compressImage,
93
+ updateEmblaCarousel,
94
+ saveField,
95
+ setStatus,
96
+ showToast,
97
+ onUnauthorized: redirectToEditorLogin,
98
+ });
99
+ };
100
+
101
+ mountSectionControls({
102
+ parseCaretAttr,
103
+ setStatus,
104
+ showToast,
105
+ onUnauthorized: redirectToEditorLogin,
106
+ onCanvasStructureChanged: mountInlineEditors,
107
+ });
108
+
109
+ mountInlineEditors();
110
+ }
111
+
112
+ if (document.readyState === 'loading') {
113
+ document.addEventListener('DOMContentLoaded', boot);
114
+ } else {
115
+ boot();
116
+ }
@@ -0,0 +1,300 @@
1
+ /* ========================================================================
2
+ Studio Login — Decorative Layer
3
+ ------------------------------------------------------------------------
4
+ Loaded only on the login screen. All values derive from semantic tokens
5
+ in studio-tokens.css, so a theme override automatically restyles the
6
+ login card without touching this file.
7
+ ======================================================================== */
8
+
9
+ @import "./studio-tokens.css";
10
+
11
+ .studio-login-page {
12
+ position: relative;
13
+ min-height: 100vh;
14
+ display: flex;
15
+ align-items: center;
16
+ justify-content: center;
17
+ overflow: hidden;
18
+ background: var(--studio-bg);
19
+ font-family: var(--studio-font);
20
+ }
21
+
22
+ /* Background radial gradients */
23
+ .studio-login-bg {
24
+ position: absolute;
25
+ inset: 0;
26
+ pointer-events: none;
27
+ }
28
+
29
+ .studio-login-bg::before,
30
+ .studio-login-bg::after {
31
+ content: "";
32
+ position: absolute;
33
+ inset: 0;
34
+ }
35
+
36
+ .studio-login-bg::before {
37
+ background: radial-gradient(
38
+ ellipse at top right,
39
+ var(--studio-accent-soft) 0%,
40
+ transparent 60%
41
+ );
42
+ }
43
+
44
+ .studio-login-bg::after {
45
+ background: radial-gradient(
46
+ ellipse at bottom left,
47
+ var(--studio-accent-soft) 0%,
48
+ transparent 50%
49
+ );
50
+ opacity: 0.6;
51
+ }
52
+
53
+ /* Subtle decorative grid */
54
+ .studio-login-grid {
55
+ position: absolute;
56
+ inset: 0;
57
+ opacity: 0.015;
58
+ background-image: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M30 0L60 30L30 60L0 30Z' fill='none' stroke='%23D4AF37' stroke-width='0.5'/%3E%3C/svg%3E");
59
+ pointer-events: none;
60
+ }
61
+
62
+ /* Top accent line */
63
+ .studio-login-accent-top {
64
+ position: absolute;
65
+ top: 0;
66
+ left: 0;
67
+ right: 0;
68
+ height: 1px;
69
+ background: linear-gradient(
70
+ to right,
71
+ transparent,
72
+ var(--studio-accent-strong, rgba(59, 130, 246, 0.3)),
73
+ transparent
74
+ );
75
+ }
76
+
77
+ /* Login card */
78
+ .studio-login-card {
79
+ position: relative;
80
+ z-index: 10;
81
+ width: 100%;
82
+ max-width: 28rem;
83
+ margin: 0 1rem;
84
+ opacity: 0;
85
+ transform: translateY(16px);
86
+ animation: studio-card-reveal 0.8s cubic-bezier(0.16, 1, 0.3, 1) 0.2s forwards;
87
+ }
88
+
89
+ /* Card glow */
90
+ .studio-login-card-glow {
91
+ position: absolute;
92
+ inset: -1px;
93
+ background: linear-gradient(
94
+ to bottom,
95
+ var(--studio-accent-strong, rgba(59, 130, 246, 0.2)),
96
+ transparent,
97
+ var(--studio-accent-soft)
98
+ );
99
+ border-radius: var(--radius-theme-xs);
100
+ filter: blur(4px);
101
+ }
102
+
103
+ /* Card body */
104
+ .studio-login-card-body {
105
+ position: relative;
106
+ background: var(--studio-surface);
107
+ border: 1px solid var(--studio-border);
108
+ padding: 3rem;
109
+ }
110
+
111
+ /* Corner accent strokes (8 total, 4 corners) */
112
+ .studio-corner {
113
+ position: absolute;
114
+ background: var(--studio-accent);
115
+ opacity: 0.4;
116
+ }
117
+
118
+ .studio-corner-h { width: 32px; height: 1px; }
119
+ .studio-corner-v { width: 1px; height: 32px; }
120
+
121
+ .studio-corner-tl-h { top: 0; left: 0; }
122
+ .studio-corner-tl-v { top: 0; left: 0; }
123
+ .studio-corner-tr-h { top: 0; right: 0; }
124
+ .studio-corner-tr-v { top: 0; right: 0; }
125
+ .studio-corner-bl-h { bottom: 0; left: 0; }
126
+ .studio-corner-bl-v { bottom: 0; left: 0; }
127
+ .studio-corner-br-h { bottom: 0; right: 0; }
128
+ .studio-corner-br-v { bottom: 0; right: 0; }
129
+
130
+ /* Login header */
131
+ .studio-login-header {
132
+ text-align: center;
133
+ margin-bottom: 2.5rem;
134
+ }
135
+
136
+ .studio-login-mark {
137
+ display: inline-flex;
138
+ align-items: center;
139
+ justify-content: center;
140
+ width: 56px;
141
+ height: 56px;
142
+ margin-bottom: 1.5rem;
143
+ color: var(--studio-accent);
144
+ }
145
+
146
+ .studio-login-mark svg {
147
+ width: 40px;
148
+ height: 40px;
149
+ }
150
+
151
+ .studio-login-title {
152
+ font-family: var(--studio-font-heading);
153
+ font-size: 1.5rem;
154
+ font-weight: 500;
155
+ margin: 0 0 0.5rem;
156
+ letter-spacing: 0.04em;
157
+ color: var(--studio-text);
158
+ }
159
+
160
+ .studio-login-tagline {
161
+ font-size: 11px;
162
+ letter-spacing: 0.2em;
163
+ text-transform: uppercase;
164
+ color: var(--studio-text-dim);
165
+ margin: 0;
166
+ }
167
+
168
+ /* Login form */
169
+ .studio-login-form {
170
+ display: flex;
171
+ flex-direction: column;
172
+ gap: 2rem;
173
+ }
174
+
175
+ .studio-login-field {
176
+ display: flex;
177
+ flex-direction: column;
178
+ gap: 0.75rem;
179
+ }
180
+
181
+ .studio-login-field label {
182
+ font-size: 11px;
183
+ letter-spacing: 0.15em;
184
+ text-transform: uppercase;
185
+ color: var(--studio-text-muted);
186
+ }
187
+
188
+ .studio-login-input-wrap {
189
+ position: relative;
190
+ }
191
+
192
+ .studio-login-input {
193
+ width: 100%;
194
+ padding: 14px 18px;
195
+ background: var(--color-theme-surface-muted);
196
+ border: 1px solid var(--studio-border);
197
+ color: var(--studio-text);
198
+ font-family: var(--studio-font);
199
+ font-size: 14px;
200
+ outline: none;
201
+ transition: border-color 0.2s, background 0.2s;
202
+ }
203
+
204
+ .studio-login-input:focus {
205
+ border-color: var(--studio-accent);
206
+ background: rgba(255, 255, 255, 0.06);
207
+ }
208
+
209
+ .studio-login-input-icon {
210
+ position: absolute;
211
+ right: 16px;
212
+ top: 50%;
213
+ transform: translateY(-50%);
214
+ color: var(--studio-text-dim);
215
+ pointer-events: none;
216
+ width: 16px;
217
+ height: 16px;
218
+ }
219
+
220
+ /* Submit button */
221
+ .studio-login-submit {
222
+ position: relative;
223
+ width: 100%;
224
+ padding: 16px 20px;
225
+ font-size: 12px;
226
+ font-weight: 600;
227
+ letter-spacing: 0.2em;
228
+ text-transform: uppercase;
229
+ background: var(--studio-accent);
230
+ color: var(--studio-on-accent);
231
+ border: none;
232
+ cursor: pointer;
233
+ font-family: var(--studio-font);
234
+ transition: box-shadow 0.5s, opacity 0.2s;
235
+ overflow: hidden;
236
+ }
237
+
238
+ .studio-login-submit:hover:not(:disabled) {
239
+ box-shadow: 0 8px 30px var(--studio-accent-strong, rgba(59, 130, 246, 0.3));
240
+ }
241
+
242
+ .studio-login-submit:disabled {
243
+ opacity: 0.5;
244
+ cursor: not-allowed;
245
+ }
246
+
247
+ .studio-login-submit-content {
248
+ display: flex;
249
+ align-items: center;
250
+ justify-content: center;
251
+ gap: 0.75rem;
252
+ }
253
+
254
+ .studio-login-submit-arrow {
255
+ width: 16px;
256
+ height: 16px;
257
+ transition: transform 0.3s;
258
+ }
259
+
260
+ .studio-login-submit:hover:not(:disabled) .studio-login-submit-arrow {
261
+ transform: translateX(4px);
262
+ }
263
+
264
+ /* Inline error banner */
265
+ .studio-login-error {
266
+ display: flex;
267
+ align-items: center;
268
+ gap: 0.75rem;
269
+ margin-bottom: 2rem;
270
+ padding: 1rem;
271
+ background: rgba(239, 68, 68, 0.08);
272
+ border: 1px solid rgba(239, 68, 68, 0.3);
273
+ color: var(--studio-red);
274
+ font-size: 13px;
275
+ }
276
+
277
+ .studio-login-error[hidden] { display: none; }
278
+
279
+ .studio-login-error svg {
280
+ width: 16px;
281
+ height: 16px;
282
+ flex-shrink: 0;
283
+ }
284
+
285
+ /* Footer */
286
+ .studio-login-footer {
287
+ margin-top: 2rem;
288
+ padding-top: 1.5rem;
289
+ text-align: center;
290
+ border-top: 1px solid var(--studio-border);
291
+ }
292
+
293
+ .studio-login-footer a {
294
+ font-size: 11px;
295
+ letter-spacing: 0.05em;
296
+ color: var(--studio-text-dim);
297
+ transition: opacity 0.2s;
298
+ }
299
+
300
+ .studio-login-footer a:hover { opacity: 0.7; }
@@ -0,0 +1,85 @@
1
+ /* ========================================================================
2
+ Studio Design System — Token Layer
3
+ ------------------------------------------------------------------------
4
+ Two layers, in cascade order:
5
+
6
+ 1. SEMANTIC tokens (--color-theme-*, --space-*, --radius-*, --font-*)
7
+ The contract that themes override. A theme preset only needs to
8
+ redeclare these to fully restyle the studio.
9
+
10
+ 2. STUDIO aliases (--studio-*)
11
+ The names existing studio CSS classes already consume. Each alias
12
+ is derived from a semantic token, so semantic overrides cascade
13
+ automatically.
14
+
15
+ Defaults below = the "Studio" preset (neutral dark + product blue).
16
+ The /api/cms/theme.css endpoint (theme-stylesheet route) re-emits
17
+ the SEMANTIC layer for whichever preset is active, so the aliases
18
+ below never need to change per theme.
19
+ ======================================================================== */
20
+
21
+ :root {
22
+ /* ─── Semantic: Surface ───────────────────────────────────────────── */
23
+ --color-theme-bg-deep: #0c0e12;
24
+ --color-theme-surface: #14171c;
25
+ --color-theme-surface-elevated: #1c2027;
26
+ --color-theme-surface-muted: rgba(255, 255, 255, 0.03);
27
+
28
+ /* ─── Semantic: Border ────────────────────────────────────────────── */
29
+ --color-theme-border-subtle: rgba(255, 255, 255, 0.06);
30
+ --color-theme-border: rgba(255, 255, 255, 0.10);
31
+
32
+ /* ─── Semantic: Accent ────────────────────────────────────────────── */
33
+ --color-theme-accent: #3b82f6;
34
+ --color-theme-accent-soft: rgba(59, 130, 246, 0.12);
35
+ --color-theme-accent-strong: rgba(59, 130, 246, 0.24);
36
+ --color-theme-on-accent: #ffffff;
37
+
38
+ /* ─── Semantic: Text ──────────────────────────────────────────────── */
39
+ --color-theme-text: rgba(255, 255, 255, 0.92);
40
+ --color-theme-text-muted: rgba(255, 255, 255, 0.55);
41
+ --color-theme-text-dim: rgba(255, 255, 255, 0.32);
42
+
43
+ /* ─── Semantic: State ─────────────────────────────────────────────── */
44
+ --color-theme-success: #22c55e;
45
+ --color-theme-danger: #ef4444;
46
+ --color-theme-info: #3b82f6;
47
+ --color-theme-warning: #eab308;
48
+
49
+ /* ─── Semantic: Typography ────────────────────────────────────────── */
50
+ --font-theme-body: Inter, system-ui, -apple-system, "Segoe UI", sans-serif;
51
+ --font-theme-heading: Inter, system-ui, -apple-system, "Segoe UI", sans-serif;
52
+ --font-theme-mono: ui-monospace, SFMono-Regular, "JetBrains Mono", Menlo, monospace;
53
+
54
+ /* ─── Semantic: Scale ─────────────────────────────────────────────── */
55
+ --radius-theme-xs: 2px;
56
+ --radius-theme-sm: 4px;
57
+ --radius-theme-md: 6px;
58
+ --radius-theme-lg: 12px;
59
+ --space-theme-xs: 0.25rem;
60
+ --space-theme-sm: 0.5rem;
61
+ --space-theme-md: 1rem;
62
+ --space-theme-lg: 1.5rem;
63
+ --space-theme-xl: 2rem;
64
+ --space-theme-2xl: 3rem;
65
+
66
+ /* ─── Studio Aliases (derive from semantic — do not hand-edit) ────── */
67
+ --studio-bg: var(--color-theme-bg-deep);
68
+ --studio-surface: var(--color-theme-surface);
69
+ --studio-surface-hover: var(--color-theme-surface-elevated);
70
+ --studio-border: var(--color-theme-border-subtle);
71
+ --studio-border-hover: var(--color-theme-border);
72
+ --studio-accent: var(--color-theme-accent);
73
+ --studio-accent-soft: var(--color-theme-accent-soft);
74
+ --studio-accent-strong: var(--color-theme-accent-strong);
75
+ --studio-on-accent: var(--color-theme-on-accent);
76
+ --studio-text: var(--color-theme-text);
77
+ --studio-text-muted: var(--color-theme-text-muted);
78
+ --studio-text-dim: var(--color-theme-text-dim);
79
+ --studio-green: var(--color-theme-success);
80
+ --studio-red: var(--color-theme-danger);
81
+ --studio-blue: var(--color-theme-info);
82
+ --studio-font: var(--font-theme-body);
83
+ --studio-font-heading: var(--font-theme-heading);
84
+ --studio-font-mono: var(--font-theme-mono);
85
+ }