@407dev/cms-astro 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 (212) hide show
  1. package/README.md +69 -0
  2. package/dist/chunk-2TKIQIOE.js +118 -0
  3. package/dist/chunk-2TKIQIOE.js.map +1 -0
  4. package/dist/chunk-53OQ5RXJ.js +912 -0
  5. package/dist/chunk-53OQ5RXJ.js.map +1 -0
  6. package/dist/chunk-56OMNQLH.js +1013 -0
  7. package/dist/chunk-56OMNQLH.js.map +1 -0
  8. package/dist/chunk-5AOOMQ7K.js +1031 -0
  9. package/dist/chunk-5AOOMQ7K.js.map +1 -0
  10. package/dist/chunk-5XRUCFDC.js +743 -0
  11. package/dist/chunk-5XRUCFDC.js.map +1 -0
  12. package/dist/chunk-62ZHCOLZ.js +25 -0
  13. package/dist/chunk-62ZHCOLZ.js.map +1 -0
  14. package/dist/chunk-6AZLRSYA.js +126 -0
  15. package/dist/chunk-6AZLRSYA.js.map +1 -0
  16. package/dist/chunk-6L3ZTCFB.js +14 -0
  17. package/dist/chunk-6L3ZTCFB.js.map +1 -0
  18. package/dist/chunk-6O7ZNNDT.js +697 -0
  19. package/dist/chunk-6O7ZNNDT.js.map +1 -0
  20. package/dist/chunk-6PQIXTMK.js +697 -0
  21. package/dist/chunk-6PQIXTMK.js.map +1 -0
  22. package/dist/chunk-7AW6M7QW.js +1013 -0
  23. package/dist/chunk-7AW6M7QW.js.map +1 -0
  24. package/dist/chunk-7E3XJLKX.js +73 -0
  25. package/dist/chunk-7E3XJLKX.js.map +1 -0
  26. package/dist/chunk-7LM54OTA.js +1031 -0
  27. package/dist/chunk-7LM54OTA.js.map +1 -0
  28. package/dist/chunk-7LMUFCWX.js +126 -0
  29. package/dist/chunk-7LMUFCWX.js.map +1 -0
  30. package/dist/chunk-A3OGL4OX.js +706 -0
  31. package/dist/chunk-A3OGL4OX.js.map +1 -0
  32. package/dist/chunk-AO4UAWOV.js +1030 -0
  33. package/dist/chunk-AO4UAWOV.js.map +1 -0
  34. package/dist/chunk-B4YYLS7Y.js +14 -0
  35. package/dist/chunk-B4YYLS7Y.js.map +1 -0
  36. package/dist/chunk-BDFVGXM2.js +697 -0
  37. package/dist/chunk-BDFVGXM2.js.map +1 -0
  38. package/dist/chunk-CXM7NZVX.js +1031 -0
  39. package/dist/chunk-CXM7NZVX.js.map +1 -0
  40. package/dist/chunk-DMLOBKYE.js +96 -0
  41. package/dist/chunk-DMLOBKYE.js.map +1 -0
  42. package/dist/chunk-DXJSYGS6.js +743 -0
  43. package/dist/chunk-DXJSYGS6.js.map +1 -0
  44. package/dist/chunk-EFUSAQZM.js +72 -0
  45. package/dist/chunk-EFUSAQZM.js.map +1 -0
  46. package/dist/chunk-F7KDGPBN.js +1013 -0
  47. package/dist/chunk-F7KDGPBN.js.map +1 -0
  48. package/dist/chunk-FBSQT45X.js +126 -0
  49. package/dist/chunk-FBSQT45X.js.map +1 -0
  50. package/dist/chunk-FEPDOMVH.js +697 -0
  51. package/dist/chunk-FEPDOMVH.js.map +1 -0
  52. package/dist/chunk-FQDOL2QW.js +25 -0
  53. package/dist/chunk-FQDOL2QW.js.map +1 -0
  54. package/dist/chunk-GB3LGQJM.js +109 -0
  55. package/dist/chunk-GB3LGQJM.js.map +1 -0
  56. package/dist/chunk-HCNP6R3P.js +736 -0
  57. package/dist/chunk-HCNP6R3P.js.map +1 -0
  58. package/dist/chunk-J6WEJDRI.js +743 -0
  59. package/dist/chunk-J6WEJDRI.js.map +1 -0
  60. package/dist/chunk-JHEAPNJ2.js +117 -0
  61. package/dist/chunk-JHEAPNJ2.js.map +1 -0
  62. package/dist/chunk-JOJDJ6GW.js +25 -0
  63. package/dist/chunk-JOJDJ6GW.js.map +1 -0
  64. package/dist/chunk-KTDV6WYB.js +202 -0
  65. package/dist/chunk-KTDV6WYB.js.map +1 -0
  66. package/dist/chunk-KZWJBKPC.js +734 -0
  67. package/dist/chunk-KZWJBKPC.js.map +1 -0
  68. package/dist/chunk-LEAQBGL6.js +109 -0
  69. package/dist/chunk-LEAQBGL6.js.map +1 -0
  70. package/dist/chunk-LGZJL2Q7.js +697 -0
  71. package/dist/chunk-LGZJL2Q7.js.map +1 -0
  72. package/dist/chunk-LQPBU46R.js +109 -0
  73. package/dist/chunk-LQPBU46R.js.map +1 -0
  74. package/dist/chunk-M55NNUTY.js +1013 -0
  75. package/dist/chunk-M55NNUTY.js.map +1 -0
  76. package/dist/chunk-M6NCYCC3.js +1013 -0
  77. package/dist/chunk-M6NCYCC3.js.map +1 -0
  78. package/dist/chunk-MGN7OZAU.js +109 -0
  79. package/dist/chunk-MGN7OZAU.js.map +1 -0
  80. package/dist/chunk-MGYZOX4K.js +1013 -0
  81. package/dist/chunk-MGYZOX4K.js.map +1 -0
  82. package/dist/chunk-MY4IEKJS.js +1031 -0
  83. package/dist/chunk-MY4IEKJS.js.map +1 -0
  84. package/dist/chunk-NCQRGAB4.js +705 -0
  85. package/dist/chunk-NCQRGAB4.js.map +1 -0
  86. package/dist/chunk-NI4NHXI4.js +697 -0
  87. package/dist/chunk-NI4NHXI4.js.map +1 -0
  88. package/dist/chunk-NPI5N7AU.js +14 -0
  89. package/dist/chunk-NPI5N7AU.js.map +1 -0
  90. package/dist/chunk-QN4CPMJF.js +126 -0
  91. package/dist/chunk-QN4CPMJF.js.map +1 -0
  92. package/dist/chunk-QYUVAKMC.js +1013 -0
  93. package/dist/chunk-QYUVAKMC.js.map +1 -0
  94. package/dist/chunk-RAAT6PDT.js +25 -0
  95. package/dist/chunk-RAAT6PDT.js.map +1 -0
  96. package/dist/chunk-RDPO5VPS.js +1013 -0
  97. package/dist/chunk-RDPO5VPS.js.map +1 -0
  98. package/dist/chunk-RQHIW2PF.js +25 -0
  99. package/dist/chunk-RQHIW2PF.js.map +1 -0
  100. package/dist/chunk-RREVHA52.js +722 -0
  101. package/dist/chunk-RREVHA52.js.map +1 -0
  102. package/dist/chunk-S7PT4PZN.js +25 -0
  103. package/dist/chunk-S7PT4PZN.js.map +1 -0
  104. package/dist/chunk-SUXV5M32.js +109 -0
  105. package/dist/chunk-SUXV5M32.js.map +1 -0
  106. package/dist/chunk-SZEORUY6.js +696 -0
  107. package/dist/chunk-SZEORUY6.js.map +1 -0
  108. package/dist/chunk-T7NGGWGY.js +25 -0
  109. package/dist/chunk-T7NGGWGY.js.map +1 -0
  110. package/dist/chunk-UEVWP5VE.js +696 -0
  111. package/dist/chunk-UEVWP5VE.js.map +1 -0
  112. package/dist/chunk-UJNKNP7U.js +748 -0
  113. package/dist/chunk-UJNKNP7U.js.map +1 -0
  114. package/dist/chunk-V4XVFWFI.js +1013 -0
  115. package/dist/chunk-V4XVFWFI.js.map +1 -0
  116. package/dist/chunk-VJU2CIKZ.js +729 -0
  117. package/dist/chunk-VJU2CIKZ.js.map +1 -0
  118. package/dist/chunk-VRQUP5L3.js +202 -0
  119. package/dist/chunk-VRQUP5L3.js.map +1 -0
  120. package/dist/chunk-WBJ5E7CL.js +742 -0
  121. package/dist/chunk-WBJ5E7CL.js.map +1 -0
  122. package/dist/chunk-XJPVYMZM.js +24 -0
  123. package/dist/chunk-XJPVYMZM.js.map +1 -0
  124. package/dist/chunk-Y3Q65C7R.js +14 -0
  125. package/dist/chunk-Y3Q65C7R.js.map +1 -0
  126. package/dist/chunk-YHEGFPTR.js +1030 -0
  127. package/dist/chunk-YHEGFPTR.js.map +1 -0
  128. package/dist/chunk-ZDLBN46C.js +697 -0
  129. package/dist/chunk-ZDLBN46C.js.map +1 -0
  130. package/dist/dev/index.d.ts +122 -0
  131. package/dist/dev/index.js +662 -0
  132. package/dist/dev/index.js.map +1 -0
  133. package/dist/fields/collections.d.ts +18 -0
  134. package/dist/fields/collections.js +37 -0
  135. package/dist/fields/collections.js.map +1 -0
  136. package/dist/index.d.ts +235 -0
  137. package/dist/index.js +956 -0
  138. package/dist/index.js.map +1 -0
  139. package/dist/middleware/index.d.ts +8 -0
  140. package/dist/middleware/index.js +13 -0
  141. package/dist/middleware/index.js.map +1 -0
  142. package/dist/preview-BEkLMD6N.d.ts +23 -0
  143. package/dist/preview-D9yewK8x.d.ts +24 -0
  144. package/dist/preview-DJEA4fj7.d.ts +23 -0
  145. package/dist/preview-DeD1Jn7U.d.ts +23 -0
  146. package/dist/toolbar/app.d.ts +28 -0
  147. package/dist/toolbar/app.js +1176 -0
  148. package/dist/toolbar/app.js.map +1 -0
  149. package/dist/types-15jfCQKb.d.ts +141 -0
  150. package/dist/types-BREdL9Gx.d.ts +140 -0
  151. package/dist/types-C6sUZNa2.d.ts +104 -0
  152. package/dist/types-CEYS5Fzc.d.ts +112 -0
  153. package/dist/types-DpeHpobR.d.ts +104 -0
  154. package/dist/types-NaKJe6Pg.d.ts +140 -0
  155. package/package.json +90 -0
  156. package/src/CmsHead.astro +25 -0
  157. package/src/CmsImage.astro +62 -0
  158. package/src/CmsVideo.astro +185 -0
  159. package/src/RichText.astro +32 -0
  160. package/src/client.test.ts +227 -0
  161. package/src/client.ts +164 -0
  162. package/src/dev/config.test.ts +258 -0
  163. package/src/dev/config.ts +319 -0
  164. package/src/dev/index.ts +5 -0
  165. package/src/dev/preview-token.ts +39 -0
  166. package/src/dev/routes.test.ts +354 -0
  167. package/src/dev/routes.ts +245 -0
  168. package/src/dev/token.test.ts +280 -0
  169. package/src/dev/token.ts +201 -0
  170. package/src/dev/verifier.test.ts +63 -0
  171. package/src/dev/verifier.ts +161 -0
  172. package/src/fields/attrs.ts +24 -0
  173. package/src/fields/collection.ts +72 -0
  174. package/src/fields/collections.ts +35 -0
  175. package/src/fields/entryScope.test.ts +62 -0
  176. package/src/fields/entryScope.ts +240 -0
  177. package/src/fields/group.ts +51 -0
  178. package/src/fields/helpers.test.ts +51 -0
  179. package/src/fields/helpers.ts +214 -0
  180. package/src/fields/index.ts +46 -0
  181. package/src/fields/scope.ts +21 -0
  182. package/src/fields/types.ts +119 -0
  183. package/src/head.test.ts +74 -0
  184. package/src/head.ts +123 -0
  185. package/src/image.test.ts +42 -0
  186. package/src/image.ts +64 -0
  187. package/src/index.test.ts +76 -0
  188. package/src/index.ts +13 -0
  189. package/src/integration.ts +218 -0
  190. package/src/jwt.ts +131 -0
  191. package/src/middleware/index.ts +4 -0
  192. package/src/middleware/preview.test.ts +184 -0
  193. package/src/middleware/preview.ts +206 -0
  194. package/src/preview/bridge-script.test.ts +683 -0
  195. package/src/preview/bridge-script.ts +763 -0
  196. package/src/preview/origins.ts +29 -0
  197. package/src/preview.ts +49 -0
  198. package/src/richtext/RichTextNode.astro +130 -0
  199. package/src/richtext/render.ts +248 -0
  200. package/src/richtext.test.ts +139 -0
  201. package/src/runtime/bundle.ts +28 -0
  202. package/src/runtime/store.ts +130 -0
  203. package/src/siteFiles.test.ts +93 -0
  204. package/src/siteFiles.ts +111 -0
  205. package/src/toolbar/app.test.ts +191 -0
  206. package/src/toolbar/app.ts +1091 -0
  207. package/src/toolbar/icon.ts +5 -0
  208. package/src/toolbar/widgets.test.ts +153 -0
  209. package/src/toolbar/widgets.ts +365 -0
  210. package/src/types.test.ts +154 -0
  211. package/src/types.ts +163 -0
  212. package/src/version.ts +3 -0
@@ -0,0 +1,763 @@
1
+ import { chromeTokens } from '@407dev/design-tokens/tokens';
2
+ import { resolveEditorOrigins } from './origins';
3
+
4
+ export const BRIDGE_PROTOCOL_VERSION = 1;
5
+
6
+ export interface BridgeScriptOptions {
7
+ editorOrigins?: string[];
8
+ }
9
+
10
+ export function buildBridgeScript(options: BridgeScriptOptions = {}): string {
11
+ const allowedOrigins = resolveEditorOrigins(options.editorOrigins);
12
+ const allowedOriginsJson = JSON.stringify(allowedOrigins);
13
+
14
+ return `(function() {
15
+ if (typeof window === 'undefined') return;
16
+ if (window.__CMS_BRIDGE_INITIALIZED__) return;
17
+ window.__CMS_BRIDGE_INITIALIZED__ = true;
18
+
19
+ var PROTOCOL_VERSION = ${BRIDGE_PROTOCOL_VERSION};
20
+ var allowedOrigins = ${allowedOriginsJson};
21
+ var connectedOrigin = null;
22
+ var connectedSource = null;
23
+ var activeKey = null;
24
+ var watchedAnchors = [];
25
+ var rafScheduled = false;
26
+ var isPatchingDom = false;
27
+ var debounceRescanTimer = null;
28
+
29
+ function normalizeOrigin(raw) {
30
+ if (!raw || typeof raw !== 'string') return null;
31
+ var trimmed = raw.trim();
32
+ if (!trimmed) return null;
33
+ try {
34
+ var withScheme = /^[a-zA-Z][a-zA-Z0-9+.-]*:\\/\\//.test(trimmed) ? trimmed : 'https://' + trimmed;
35
+ var url = new URL(withScheme);
36
+ if (!url.protocol || !url.host) return null;
37
+ return (url.protocol + '//' + url.host).toLowerCase();
38
+ } catch (e) {
39
+ return null;
40
+ }
41
+ }
42
+
43
+ var normalizedAllowed = allowedOrigins.map(normalizeOrigin).filter(Boolean);
44
+
45
+ function isOriginAllowed(origin) {
46
+ var norm = normalizeOrigin(origin);
47
+ if (!norm) return false;
48
+ return normalizedAllowed.indexOf(norm) !== -1;
49
+ }
50
+
51
+ function postToEditor(msg) {
52
+ if (!connectedSource || !connectedOrigin) return;
53
+ try {
54
+ connectedSource.postMessage(msg, connectedOrigin);
55
+ } catch (err) {
56
+ // Failed to post
57
+ }
58
+ }
59
+
60
+ // Inject Bridge Styles
61
+ var isEmbedded = false;
62
+ try {
63
+ isEmbedded = window.self !== window.top || window.parent !== window;
64
+ } catch (_e) {
65
+ isEmbedded = true;
66
+ }
67
+
68
+ var toolbarTag = 'astro-' + 'dev-toolbar';
69
+
70
+ function ensureBridgeStyles() {
71
+ if (typeof document === 'undefined' || !document || typeof document.getElementById !== 'function') return;
72
+ if (document.getElementById('cms-bridge-styles')) return;
73
+ var style = document.createElement('style');
74
+ style.id = 'cms-bridge-styles';
75
+ style.textContent = [
76
+ isEmbedded ? toolbarTag + ' { display: none !important; }' : '',
77
+ 'html[data-cms-comment-mode],',
78
+ 'html[data-cms-comment-mode] * {',
79
+ ' cursor: crosshair !important;',
80
+ '}',
81
+ 'html[data-cms-edit-mode] [data-cms-field],',
82
+ 'html[data-cms-edit-mode] [data-cms-entry-field] {',
83
+ ' cursor: pointer !important;',
84
+ ' outline: 1px dashed rgba(99, 102, 241, 0.5) !important;',
85
+ ' outline-offset: 2px !important;',
86
+ ' transition: outline 0.15s ease-in-out !important;',
87
+ '}',
88
+ 'html[data-cms-edit-mode] [data-cms-field]:hover,',
89
+ 'html[data-cms-edit-mode] [data-cms-entry-field]:hover {',
90
+ ' outline: 2px solid ${chromeTokens.primary} !important;',
91
+ ' outline-offset: 2px !important;',
92
+ '}',
93
+ 'html[data-cms-edit-mode] [data-cms-group] {',
94
+ ' outline: 1px dashed rgba(16, 185, 129, 0.4) !important;',
95
+ ' outline-offset: 3px !important;',
96
+ '}',
97
+ 'html[data-cms-edit-mode] [data-cms-group]:hover {',
98
+ ' outline: 2px solid ${chromeTokens.success} !important;',
99
+ ' outline-offset: 3px !important;',
100
+ '}'
101
+ ].filter(Boolean).join('\\n');
102
+
103
+ var target = document.head || document.documentElement;
104
+ if (target) {
105
+ target.appendChild(style);
106
+ }
107
+ }
108
+
109
+ function hideDevToolbar() {
110
+ if (!isEmbedded) return;
111
+ ensureBridgeStyles();
112
+ if (typeof document === 'undefined' || !document || typeof document.querySelector !== 'function') return;
113
+ try {
114
+ var tb = document.querySelector(toolbarTag);
115
+ if (tb && tb instanceof HTMLElement) {
116
+ tb.style.setProperty('display', 'none', 'important');
117
+ }
118
+ } catch (_e) {}
119
+ }
120
+
121
+ ensureBridgeStyles();
122
+ hideDevToolbar();
123
+
124
+ function inferFieldKind(el) {
125
+ if (!el) return 'unknown';
126
+ var tag = el.tagName ? el.tagName.toLowerCase() : '';
127
+ if (tag === 'img') return 'image';
128
+ if (tag === 'video') return 'video';
129
+ var explicitKind = el.getAttribute('data-cms-kind');
130
+ if (explicitKind) return explicitKind;
131
+ if (el.classList.contains('rich-body') || el.classList.contains('cms-richtext') || el.getAttribute('data-cms-richtext') === 'true') {
132
+ return 'richtext';
133
+ }
134
+ return 'text';
135
+ }
136
+
137
+ function getElementRect(el) {
138
+ var r = el.getBoundingClientRect();
139
+ return {
140
+ top: r.top,
141
+ left: r.left,
142
+ width: r.width,
143
+ height: r.height
144
+ };
145
+ }
146
+
147
+ function parseGroupAttr(raw) {
148
+ if (!raw) return undefined;
149
+ var idx = raw.indexOf(':');
150
+ if (idx === -1) return { group: raw, scope: '' };
151
+ return {
152
+ group: raw.slice(0, idx),
153
+ scope: raw.slice(idx + 1)
154
+ };
155
+ }
156
+
157
+ function scanRenderedFields() {
158
+ var fieldEls = document.querySelectorAll('[data-cms-field], [data-cms-entry-field]');
159
+ var fieldMap = {};
160
+
161
+ for (var i = 0; i < fieldEls.length; i++) {
162
+ var el = fieldEls[i];
163
+ var key = el.getAttribute('data-cms-field') || el.getAttribute('data-cms-entry-field') || '';
164
+ if (!key) continue;
165
+
166
+ var isEntry = el.hasAttribute('data-cms-entry-field');
167
+ var groupEl = el.closest('[data-cms-group]');
168
+ var collectionEl = el.closest('[data-cms-collection]');
169
+ var groupName = undefined;
170
+ if (groupEl) {
171
+ var g = parseGroupAttr(groupEl.getAttribute('data-cms-group'));
172
+ if (g) groupName = g.group;
173
+ }
174
+
175
+ var entryRef = undefined;
176
+ if (isEntry || collectionEl) {
177
+ entryRef = {
178
+ collection: collectionEl ? collectionEl.getAttribute('data-cms-collection') || '' : '',
179
+ slug: collectionEl ? collectionEl.getAttribute('data-cms-entry') || '' : '',
180
+ path: isEntry ? key : key
181
+ };
182
+ }
183
+
184
+ var uniqueId = (isEntry && entryRef ? entryRef.collection + ':' + entryRef.slug + ':' : '') + key;
185
+
186
+ if (!fieldMap[uniqueId]) {
187
+ fieldMap[uniqueId] = {
188
+ key: key,
189
+ kind: inferFieldKind(el),
190
+ group: groupName,
191
+ entryRef: entryRef,
192
+ instances: 1
193
+ };
194
+ } else {
195
+ fieldMap[uniqueId].instances++;
196
+ }
197
+ }
198
+
199
+ var fields = [];
200
+ for (var k in fieldMap) {
201
+ if (Object.prototype.hasOwnProperty.call(fieldMap, k)) {
202
+ fields.push(fieldMap[k]);
203
+ }
204
+ }
205
+
206
+ window.__CMS_FIELDS__ = fields;
207
+ window.__CMS_RESCAN__ = scanRenderedFields;
208
+
209
+ try {
210
+ document.dispatchEvent(new CustomEvent('cms:fields-updated', { detail: fields }));
211
+ } catch (_e) {}
212
+
213
+ postToEditor({
214
+ source: 'cms-preview',
215
+ v: PROTOCOL_VERSION,
216
+ type: 'rendered-fields',
217
+ fields: fields
218
+ });
219
+ }
220
+
221
+ function debouncedRescan() {
222
+ if (isPatchingDom) return;
223
+ if (debounceRescanTimer) clearTimeout(debounceRescanTimer);
224
+ debounceRescanTimer = setTimeout(function() {
225
+ scanRenderedFields();
226
+ scheduleRectsUpdate();
227
+ }, 150);
228
+ }
229
+
230
+ var currentObserver = null;
231
+ function setupMutationObserver() {
232
+ if (typeof MutationObserver === 'undefined' || !document.body) return;
233
+ if (currentObserver) {
234
+ try {
235
+ currentObserver.disconnect();
236
+ } catch (_e) {}
237
+ }
238
+ currentObserver = new MutationObserver(function(mutations) {
239
+ if (isPatchingDom) return;
240
+ hideDevToolbar();
241
+ debouncedRescan();
242
+ });
243
+ currentObserver.observe(document.body, {
244
+ childList: true,
245
+ subtree: true,
246
+ attributeFilter: ['data-cms-field', 'data-cms-entry-field', 'data-cms-group', 'data-cms-collection', 'data-cms-entry']
247
+ });
248
+ }
249
+
250
+ function buildStableSelector(target) {
251
+ if (!target || !target.tagName) return '';
252
+ if (target === document.documentElement) return 'html';
253
+ if (target === document.body) return 'body';
254
+
255
+ var parts = [];
256
+ var curr = target;
257
+ var maxLevels = 8;
258
+
259
+ while (curr && curr !== document.documentElement && parts.length < maxLevels) {
260
+ if (curr === document.body) {
261
+ parts.unshift('body');
262
+ break;
263
+ }
264
+ var id = curr.getAttribute('id');
265
+ if (id && id.trim()) {
266
+ var escapedId = (typeof CSS !== 'undefined' && CSS.escape) ? CSS.escape(id.trim()) : id.trim();
267
+ parts.unshift('#' + escapedId);
268
+ break;
269
+ }
270
+ var tag = curr.tagName ? curr.tagName.toLowerCase() : '';
271
+ if (!tag) break;
272
+ var count = 1;
273
+ var sib = curr.previousElementSibling;
274
+ while (sib) {
275
+ if (sib.tagName && sib.tagName.toLowerCase() === tag) {
276
+ count++;
277
+ }
278
+ sib = sib.previousElementSibling;
279
+ }
280
+ parts.unshift(tag + ':nth-of-type(' + count + ')');
281
+ curr = curr.parentElement;
282
+ }
283
+
284
+ return parts.join(' > ');
285
+ }
286
+
287
+ function buildElementLabel(el) {
288
+ if (!el || !el.tagName) return 'element';
289
+
290
+ var ariaLabel = el.getAttribute('aria-label');
291
+ if (ariaLabel && ariaLabel.trim()) {
292
+ return ariaLabel.trim();
293
+ }
294
+
295
+ var id = el.getAttribute('id');
296
+ if (id && id.trim()) {
297
+ return '#' + id.trim();
298
+ }
299
+
300
+ var tag = el.tagName.toLowerCase();
301
+
302
+ if (tag === 'nav') return 'Navigation';
303
+ if (tag === 'header') return 'Header';
304
+ if (tag === 'footer') return 'Footer';
305
+ if (tag === 'aside') return 'Sidebar';
306
+ if (tag === 'form') return 'Form';
307
+
308
+ if (tag === 'img') {
309
+ var alt = el.getAttribute('alt');
310
+ if (alt && alt.trim()) return alt.trim();
311
+ return 'img';
312
+ }
313
+
314
+ if (tag === 'a' || tag === 'button') {
315
+ var text = (el.textContent || '').trim().replace(/\\s+/g, ' ');
316
+ if (text) {
317
+ return text.length > 40 ? text.slice(0, 37) + '...' : text;
318
+ }
319
+ return tag;
320
+ }
321
+
322
+ return tag || 'element';
323
+ }
324
+
325
+ function buildCandidateEntry(targetEl, clientX, clientY) {
326
+ if (!targetEl || !targetEl.tagName) return null;
327
+ var selector = buildStableSelector(targetEl);
328
+ if (!selector) return null;
329
+ var all = Array.prototype.slice.call(document.querySelectorAll(selector));
330
+ var instance = Math.max(0, all.indexOf(targetEl));
331
+ var hitRect = targetEl.getBoundingClientRect();
332
+ var offsetX = typeof clientX === 'number' ? Math.round(clientX - hitRect.left) : 0;
333
+ var offsetY = typeof clientY === 'number' ? Math.round(clientY - hitRect.top) : 0;
334
+ return {
335
+ selector: selector,
336
+ instance: instance,
337
+ kind: 'element',
338
+ label: buildElementLabel(targetEl),
339
+ offsetX: offsetX,
340
+ offsetY: offsetY,
341
+ rect: getElementRect(targetEl)
342
+ };
343
+ }
344
+
345
+ function buildAnchorChain(el, clientX, clientY) {
346
+ var chain = [];
347
+ var curr = el;
348
+ while (curr && curr !== document.documentElement && curr !== document.body) {
349
+ var hit = curr.closest('[data-cms-field], [data-cms-entry-field], [data-cms-group], [data-cms-collection][data-cms-entry]');
350
+ if (!hit) break;
351
+
352
+ var selector = '';
353
+ var kind = 'unknown';
354
+ var label = '';
355
+
356
+ if (hit.hasAttribute('data-cms-field')) {
357
+ var fieldKey = hit.getAttribute('data-cms-field') || '';
358
+ selector = '[data-cms-field="' + fieldKey + '"]';
359
+ kind = inferFieldKind(hit);
360
+ label = fieldKey;
361
+ } else if (hit.hasAttribute('data-cms-entry-field')) {
362
+ var entryFieldKey = hit.getAttribute('data-cms-entry-field') || '';
363
+ selector = '[data-cms-entry-field="' + entryFieldKey + '"]';
364
+ kind = inferFieldKind(hit);
365
+ label = entryFieldKey;
366
+ } else if (hit.hasAttribute('data-cms-group')) {
367
+ var groupAttr = hit.getAttribute('data-cms-group') || '';
368
+ selector = '[data-cms-group="' + groupAttr + '"]';
369
+ kind = 'group';
370
+ var parsed = parseGroupAttr(groupAttr);
371
+ label = parsed ? parsed.group : groupAttr;
372
+ } else if (hit.hasAttribute('data-cms-collection') && hit.hasAttribute('data-cms-entry')) {
373
+ var col = hit.getAttribute('data-cms-collection') || '';
374
+ var ent = hit.getAttribute('data-cms-entry') || '';
375
+ selector = '[data-cms-collection="' + col + '"][data-cms-entry="' + ent + '"]';
376
+ kind = 'entry';
377
+ label = col + '/' + ent;
378
+ }
379
+
380
+ if (selector) {
381
+ var all = Array.prototype.slice.call(document.querySelectorAll(selector));
382
+ var instance = Math.max(0, all.indexOf(hit));
383
+ var hitRect = hit.getBoundingClientRect();
384
+ var offsetX = typeof clientX === 'number' ? Math.round(clientX - hitRect.left) : 0;
385
+ var offsetY = typeof clientY === 'number' ? Math.round(clientY - hitRect.top) : 0;
386
+ chain.push({
387
+ selector: selector,
388
+ instance: instance,
389
+ kind: kind,
390
+ label: label,
391
+ offsetX: offsetX,
392
+ offsetY: offsetY,
393
+ rect: getElementRect(hit)
394
+ });
395
+ }
396
+
397
+ curr = hit.parentElement;
398
+ }
399
+
400
+ if (chain.length > 0) {
401
+ return chain;
402
+ }
403
+
404
+ if (!el || !el.tagName) return chain;
405
+
406
+ var interactiveEl = el.closest ? el.closest('a, button') : null;
407
+ var selfEl = interactiveEl || (el.closest ? el.closest('a, button, nav, header, footer, aside, main, section, article, form, img, svg, [role]') : null) || el;
408
+ var landmarkEl = selfEl.closest ? selfEl.closest('nav, header, footer, aside, main, section, article, form, [role="navigation"], [role="banner"], [role="contentinfo"]') : null;
409
+
410
+ var selfEntry = buildCandidateEntry(selfEl, clientX, clientY);
411
+ if (selfEntry) {
412
+ chain.push(selfEntry);
413
+ }
414
+
415
+ if (landmarkEl && landmarkEl !== selfEl) {
416
+ var landmarkEntry = buildCandidateEntry(landmarkEl, clientX, clientY);
417
+ if (landmarkEntry) {
418
+ chain.push(landmarkEntry);
419
+ }
420
+ }
421
+
422
+ return chain;
423
+ }
424
+
425
+ function resolveAnchorEl(anchor) {
426
+ if (!anchor || !anchor.selector) return null;
427
+ try {
428
+ var els = document.querySelectorAll(anchor.selector);
429
+ if (!els || els.length === 0) return null;
430
+ var inst = typeof anchor.instance === 'number' ? anchor.instance : 0;
431
+ if (inst >= 0 && inst < els.length) return els[inst];
432
+ return els[0];
433
+ } catch (_e) {
434
+ return null;
435
+ }
436
+ }
437
+
438
+ function scheduleRectsUpdate() {
439
+ if (rafScheduled) return;
440
+ rafScheduled = true;
441
+ requestAnimationFrame(function() {
442
+ rafScheduled = false;
443
+ var rects = {};
444
+ if (activeKey) {
445
+ var el = document.querySelector('[data-cms-field="' + activeKey + '"], [data-cms-entry-field="' + activeKey + '"]');
446
+ if (el) {
447
+ rects[activeKey] = getElementRect(el);
448
+ }
449
+ }
450
+ postToEditor({
451
+ source: 'cms-preview',
452
+ v: PROTOCOL_VERSION,
453
+ type: 'rects-update',
454
+ rects: rects,
455
+ scrollY: window.scrollY || window.pageYOffset || 0
456
+ });
457
+
458
+ if (watchedAnchors && watchedAnchors.length > 0) {
459
+ var anchorRects = {};
460
+ for (var a = 0; a < watchedAnchors.length; a++) {
461
+ var wAnchor = watchedAnchors[a];
462
+ var resolvedEl = resolveAnchorEl(wAnchor);
463
+ if (resolvedEl) {
464
+ anchorRects[wAnchor.id] = getElementRect(resolvedEl);
465
+ } else {
466
+ anchorRects[wAnchor.id] = null;
467
+ }
468
+ }
469
+ postToEditor({
470
+ source: 'cms-preview',
471
+ v: PROTOCOL_VERSION,
472
+ type: 'anchor-rects',
473
+ rects: anchorRects
474
+ });
475
+ }
476
+ });
477
+ }
478
+
479
+ // Click handler
480
+ document.addEventListener('click', function(e) {
481
+ if (document.documentElement.hasAttribute('data-cms-comment-mode')) {
482
+ e.preventDefault();
483
+ e.stopPropagation();
484
+ var target = e.target;
485
+ if (!target || !(target instanceof Element)) return;
486
+
487
+ var docWidth = Math.max(
488
+ document.documentElement.scrollWidth || 0,
489
+ document.body ? document.body.scrollWidth : 0,
490
+ window.innerWidth || 0
491
+ );
492
+ var docHeight = Math.max(
493
+ document.documentElement.scrollHeight || 0,
494
+ document.body ? document.body.scrollHeight : 0,
495
+ window.innerHeight || 0
496
+ );
497
+ var pageX = e.pageX !== undefined ? e.pageX : (e.clientX + (window.scrollX || window.pageXOffset || 0));
498
+ var pageY = e.pageY !== undefined ? e.pageY : (e.clientY + (window.scrollY || window.pageYOffset || 0));
499
+ var pageXPct = docWidth > 0 ? Math.round((pageX / docWidth) * 10000) / 100 : 0;
500
+ var pageYPct = docHeight > 0 ? Math.round((pageY / docHeight) * 10000) / 100 : 0;
501
+
502
+ var chain = buildAnchorChain(target, e.clientX, e.clientY);
503
+ var clickRect = chain.length > 0 ? chain[0].rect : {
504
+ top: e.clientY,
505
+ left: e.clientX,
506
+ width: 0,
507
+ height: 0
508
+ };
509
+
510
+ postToEditor({
511
+ source: 'cms-preview',
512
+ v: PROTOCOL_VERSION,
513
+ type: 'comment-click',
514
+ chain: chain,
515
+ point: { pageXPct: pageXPct, pageYPct: pageYPct },
516
+ rect: clickRect
517
+ });
518
+ return;
519
+ }
520
+
521
+ if (!document.documentElement.hasAttribute('data-cms-edit-mode')) return;
522
+ var target = e.target;
523
+ if (!target || !(target instanceof Element)) return;
524
+
525
+ var fieldEl = target.closest('[data-cms-field], [data-cms-entry-field]');
526
+ var groupEl = target.closest('[data-cms-group]');
527
+
528
+ if (fieldEl) {
529
+ e.preventDefault();
530
+ e.stopPropagation();
531
+
532
+ var isEntry = fieldEl.hasAttribute('data-cms-entry-field');
533
+ var key = fieldEl.getAttribute('data-cms-field') || fieldEl.getAttribute('data-cms-entry-field') || '';
534
+ var kind = inferFieldKind(fieldEl);
535
+ var rect = getElementRect(fieldEl);
536
+ activeKey = key;
537
+
538
+ var selector = isEntry ? '[data-cms-entry-field="' + key + '"]' : '[data-cms-field="' + key + '"]';
539
+ var all = Array.prototype.slice.call(document.querySelectorAll(selector));
540
+ var instance = Math.max(0, all.indexOf(fieldEl));
541
+
542
+ var collectionEl = fieldEl.closest('[data-cms-collection]');
543
+ var entryRef = (isEntry || collectionEl) ? {
544
+ collection: collectionEl ? collectionEl.getAttribute('data-cms-collection') || '' : '',
545
+ slug: collectionEl ? collectionEl.getAttribute('data-cms-entry') || '' : '',
546
+ path: isEntry ? key : key
547
+ } : undefined;
548
+
549
+ var groupPath = groupEl ? parseGroupAttr(groupEl.getAttribute('data-cms-group')) : undefined;
550
+
551
+ try {
552
+ document.dispatchEvent(new CustomEvent('cms:field-selected', {
553
+ detail: {
554
+ key: key,
555
+ kind: kind,
556
+ entryRef: entryRef,
557
+ groupPath: groupPath
558
+ }
559
+ }));
560
+ } catch (_e) {}
561
+
562
+ postToEditor({
563
+ source: 'cms-preview',
564
+ v: PROTOCOL_VERSION,
565
+ type: 'field-click',
566
+ key: key,
567
+ kind: kind,
568
+ rect: rect,
569
+ instance: instance,
570
+ entryRef: entryRef,
571
+ groupPath: groupPath
572
+ });
573
+ return;
574
+ }
575
+
576
+ if (groupEl) {
577
+ e.preventDefault();
578
+ e.stopPropagation();
579
+
580
+ var g = parseGroupAttr(groupEl.getAttribute('data-cms-group'));
581
+ if (g) {
582
+ var gRect = getElementRect(groupEl);
583
+ postToEditor({
584
+ source: 'cms-preview',
585
+ v: PROTOCOL_VERSION,
586
+ type: 'group-click',
587
+ group: g.group,
588
+ scope: g.scope,
589
+ rect: gRect
590
+ });
591
+ }
592
+ }
593
+ }, true);
594
+
595
+ // Message handler
596
+ window.addEventListener('message', function(e) {
597
+ var data = e.data;
598
+ if (!data || typeof data !== 'object') return;
599
+ if (data.source !== 'cms-editor') return;
600
+
601
+ if (data.type === 'hello') {
602
+ if (!isOriginAllowed(e.origin)) return;
603
+ connectedOrigin = e.origin;
604
+ connectedSource = e.source;
605
+
606
+ postToEditor({
607
+ source: 'cms-preview',
608
+ v: PROTOCOL_VERSION,
609
+ type: 'ready',
610
+ protocolVersion: PROTOCOL_VERSION,
611
+ capabilities: ['comments']
612
+ });
613
+
614
+ scanRenderedFields();
615
+ return;
616
+ }
617
+
618
+ if (!connectedOrigin || e.origin !== connectedOrigin) return;
619
+
620
+ if (data.type === 'highlight-mode') {
621
+ if (data.on) {
622
+ document.documentElement.setAttribute('data-cms-edit-mode', 'true');
623
+ } else {
624
+ document.documentElement.removeAttribute('data-cms-edit-mode');
625
+ }
626
+ return;
627
+ }
628
+
629
+ if (data.type === 'comment-mode') {
630
+ if (data.on) {
631
+ document.documentElement.setAttribute('data-cms-comment-mode', 'true');
632
+ } else {
633
+ document.documentElement.removeAttribute('data-cms-comment-mode');
634
+ }
635
+ return;
636
+ }
637
+
638
+ if (data.type === 'watch-anchors') {
639
+ watchedAnchors = Array.isArray(data.anchors) ? data.anchors : [];
640
+ scheduleRectsUpdate();
641
+ return;
642
+ }
643
+
644
+ if (data.type === 'scroll-to-anchor') {
645
+ var sId = data.id;
646
+ var targetItem = null;
647
+ for (var si = 0; si < watchedAnchors.length; si++) {
648
+ if (watchedAnchors[si].id === sId) {
649
+ targetItem = watchedAnchors[si];
650
+ break;
651
+ }
652
+ }
653
+ if (targetItem) {
654
+ var elToScroll = resolveAnchorEl(targetItem);
655
+ if (elToScroll) {
656
+ elToScroll.scrollIntoView({ behavior: 'smooth', block: 'center' });
657
+ scheduleRectsUpdate();
658
+ }
659
+ }
660
+ return;
661
+ }
662
+
663
+ if (data.type === 'patch') {
664
+ isPatchingDom = true;
665
+ var patchKey = data.key;
666
+ var patchKind = data.kind;
667
+ var patchVal = data.value;
668
+ var patchEntry = data.entryRef;
669
+
670
+ var patchSelector;
671
+ var fPath = patchEntry && patchEntry.path ? patchEntry.path : patchKey;
672
+ if (patchEntry && (patchEntry.collection || patchEntry.slug)) {
673
+ var colAttr = patchEntry.collection ? '[data-cms-collection="' + patchEntry.collection + '"]' : '';
674
+ var entryAttr = patchEntry.slug ? '[data-cms-entry="' + patchEntry.slug + '"]' : '';
675
+ var scopeSel = colAttr + entryAttr;
676
+ patchSelector = scopeSel + ' [data-cms-entry-field="' + fPath + '"], ' +
677
+ scopeSel + '[data-cms-entry-field="' + fPath + '"]';
678
+ } else if (patchEntry) {
679
+ patchSelector = '[data-cms-entry-field="' + fPath + '"]';
680
+ } else {
681
+ patchSelector = '[data-cms-field="' + patchKey + '"]';
682
+ }
683
+
684
+ var matchingEls = document.querySelectorAll(patchSelector);
685
+ for (var j = 0; j < matchingEls.length; j++) {
686
+ var mEl = matchingEls[j];
687
+ var isStructured = patchKind === 'geo.area' || patchKind === 'geoArea' ||
688
+ patchKind === 'address' || patchKind === 'address.list' ||
689
+ patchKind === 'media.videoList' || patchKind === 'videoList' ||
690
+ mEl.getAttribute('data-cms-kind') === 'geo.area' ||
691
+ mEl.getAttribute('data-cms-kind') === 'address.list' ||
692
+ mEl.getAttribute('data-cms-kind') === 'media.videoList';
693
+
694
+ if (patchKind === 'image' || mEl.tagName.toLowerCase() === 'img') {
695
+ if (patchVal && typeof patchVal === 'object') {
696
+ if (patchVal.url) mEl.setAttribute('src', patchVal.url);
697
+ if (patchVal.alt !== undefined) mEl.setAttribute('alt', patchVal.alt);
698
+ } else if (typeof patchVal === 'string') {
699
+ mEl.setAttribute('src', patchVal);
700
+ }
701
+ } else if (patchKind === 'video' || mEl.tagName.toLowerCase() === 'video') {
702
+ if (patchVal && typeof patchVal === 'object') {
703
+ if (patchVal.url) mEl.setAttribute('src', patchVal.url);
704
+ if (patchVal.poster !== undefined) mEl.setAttribute('poster', patchVal.poster);
705
+ } else if (typeof patchVal === 'string') {
706
+ mEl.setAttribute('src', patchVal);
707
+ }
708
+ if (typeof mEl.load === 'function') mEl.load();
709
+ } else if (isStructured || (patchVal && typeof patchVal === 'object')) {
710
+ // Structured non-image field values (e.g. videoList, geoArea, address) cannot be rendered
711
+ // via simple textContent replacement without producing [object Object].
712
+ // Skip the text write and leave server-rendered markup intact; structured fields refresh via reload.
713
+ } else {
714
+ mEl.textContent = patchVal !== null && patchVal !== undefined ? String(patchVal) : '';
715
+ }
716
+ }
717
+
718
+ isPatchingDom = false;
719
+ scanRenderedFields();
720
+ scheduleRectsUpdate();
721
+ return;
722
+ }
723
+
724
+ if (data.type === 'scroll-to') {
725
+ var scrollKey = data.key;
726
+ var scrollInstance = typeof data.instance === 'number' ? data.instance : 0;
727
+ var scrollEls = document.querySelectorAll('[data-cms-field="' + scrollKey + '"], [data-cms-entry-field="' + scrollKey + '"]');
728
+ if (scrollEls.length > scrollInstance) {
729
+ scrollEls[scrollInstance].scrollIntoView({ behavior: 'smooth', block: 'center' });
730
+ activeKey = scrollKey;
731
+ scheduleRectsUpdate();
732
+ }
733
+ return;
734
+ }
735
+
736
+ if (data.type === 'reload') {
737
+ window.location.reload();
738
+ return;
739
+ }
740
+ });
741
+
742
+ function handlePageTransition() {
743
+ hideDevToolbar();
744
+ scanRenderedFields();
745
+ setupMutationObserver();
746
+ }
747
+
748
+ window.addEventListener('scroll', scheduleRectsUpdate, { passive: true });
749
+ window.addEventListener('resize', scheduleRectsUpdate, { passive: true });
750
+
751
+ document.addEventListener('astro:page-load', handlePageTransition);
752
+ document.addEventListener('astro:after-swap', handlePageTransition);
753
+ window.addEventListener('popstate', handlePageTransition);
754
+
755
+ if (document.readyState === 'loading') {
756
+ document.addEventListener('DOMContentLoaded', function() {
757
+ handlePageTransition();
758
+ });
759
+ } else {
760
+ handlePageTransition();
761
+ }
762
+ })();`;
763
+ }