@obelism/content 0.19.2 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bridge.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { P as PREVIEW_PARAMS, U as UID_ATTRIBUTE, E as ENTRY_ATTRIBUTE, a as PRODUCT_ATTRIBUTE, b as PRODUCT_HANDLE_ATTRIBUTE } from './preview-jcmxxj92.mjs';
1
+ import { P as PREVIEW_PARAMS, U as UID_ATTRIBUTE, E as ENTRY_ATTRIBUTE, C as COLLECTION_ATTRIBUTE, a as COLLECTION_HANDLE_ATTRIBUTE, b as PRODUCT_ATTRIBUTE, c as PRODUCT_HANDLE_ATTRIBUTE } from './preview-fu3qtgfx.mjs';
2
2
  import { SITE_EDITING_COOKIE, SITE_REQUEST_HEADER } from './site.mjs';
3
3
  import { n as normalizeUrl, D as DEFAULT_URL } from './config-c6qktlz6.mjs';
4
4
 
@@ -10,7 +10,7 @@ import { n as normalizeUrl, D as DEFAULT_URL } from './config-c6qktlz6.mjs';
10
10
  //
11
11
  // Sites tag a block's clickable element with `data-numbers-id={block._uid}`
12
12
  // and load Obelism numbers' script; the CMS reads the rates from numbers
13
- // (the CMS's content/docs/guides/visual-editor.mdx, "Click rates").
13
+ // (the CMS's content/docs/guides/canvas.mdx, "Click rates").
14
14
  const DEFAULT_STATS_LABELS = {
15
15
  clicked: '{rate}% clicked',
16
16
  title: '{clickers} of {viewers} visitors of its page clicked it in the last 30 days ({clicks} clicks)'
@@ -163,6 +163,91 @@ const mountStatsOverlay = (win, { selector, onSelect, labels: labelOverrides })=
163
163
  };
164
164
  };
165
165
 
166
+ // Content's canvas shows a whole page in a frame as tall as the page, so the
167
+ // frame is the page's height, not the screen's. Sizes in viewport-height
168
+ // units (`vh`, `svh`, `lvh`, `dvh`) would follow it: a `100vh` hero makes
169
+ // the page taller, the canvas makes the frame taller, and the hero grows
170
+ // again. The editor sends the height the frame stands for (`viewport`), and
171
+ // this rewrites those units in the page's stylesheets as pixels of that
172
+ // height. Stylesheets from another origin can't be read and keep theirs.
173
+ const UNIT = /(-?\d*\.?\d+)(?:s|l|d)?vh\b/g;
174
+ // A value with its viewport-height units as pixels of `height`, or null when
175
+ // it has none.
176
+ const freezeValue = (value, height)=>{
177
+ UNIT.lastIndex = 0;
178
+ if (!UNIT.test(value)) return null;
179
+ return value.replace(UNIT, (_, amount)=>`${Math.round(Number(amount) * height / 100 * 100) / 100}px`);
180
+ };
181
+ const freezeStyle = (style, height)=>{
182
+ for(let index = 0; index < style.length; index++){
183
+ const property = style.item(index);
184
+ const frozen = freezeValue(style.getPropertyValue(property), height);
185
+ if (frozen !== null) {
186
+ style.setProperty(property, frozen, style.getPropertyPriority(property));
187
+ }
188
+ }
189
+ };
190
+ // Style rules, and those inside @media, @supports and @layer blocks.
191
+ const freezeRules = (rules, height)=>{
192
+ for (const rule of Array.from(rules)){
193
+ const { style, cssRules } = rule;
194
+ if (style) freezeStyle(style, height);
195
+ if (cssRules) freezeRules(cssRules, height);
196
+ }
197
+ };
198
+ const createViewportUnits = (doc)=>{
199
+ const done = new WeakSet();
200
+ let height = null;
201
+ let observer = null;
202
+ let scheduled = false;
203
+ const freezeSheets = ()=>{
204
+ if (height === null) return;
205
+ for (const sheet of Array.from(doc.styleSheets)){
206
+ if (done.has(sheet)) continue;
207
+ let rules;
208
+ try {
209
+ rules = sheet.cssRules;
210
+ } catch {
211
+ continue;
212
+ }
213
+ done.add(sheet);
214
+ freezeRules(rules, height);
215
+ }
216
+ doc.querySelectorAll('[style*="vh"]').forEach((element)=>{
217
+ freezeStyle(element.style, height);
218
+ });
219
+ };
220
+ return {
221
+ freeze (next) {
222
+ // Once: the frame stands for one screen size until it reloads.
223
+ if (height !== null || !(next > 0)) return;
224
+ height = next;
225
+ freezeSheets();
226
+ const View = doc.defaultView?.MutationObserver;
227
+ if (!View) return;
228
+ // Once per frame, however much the page changes in it.
229
+ observer = new View(()=>{
230
+ if (scheduled) return;
231
+ scheduled = true;
232
+ const run = ()=>{
233
+ scheduled = false;
234
+ freezeSheets();
235
+ };
236
+ if (doc.defaultView?.requestAnimationFrame) {
237
+ doc.defaultView.requestAnimationFrame(run);
238
+ } else run();
239
+ });
240
+ observer.observe(doc.documentElement, {
241
+ childList: true,
242
+ subtree: true
243
+ });
244
+ },
245
+ destroy () {
246
+ observer?.disconnect();
247
+ }
248
+ };
249
+ };
250
+
166
251
  // The site's half of the visual editor (the CMS's public/bridge.js, as a
167
252
  // module). Inside the editor's iframe it outlines blocks, tells the editor
168
253
  // which one was clicked, hands the editor's live, unsaved entry to listeners
@@ -171,10 +256,10 @@ const mountStatsOverlay = (win, { selector, onSelect, labels: labelOverrides })=
171
256
  // Messages are only exchanged with the CMS origin, and only with the parent
172
257
  // window:
173
258
  // - site → editor: { source: 'obelism-content-bridge', type: 'ready' |
174
- // 'select' | 'prompt' | 'undo', uid?, id?, prompt? }
259
+ // 'size' | 'select' | 'prompt' | 'undo', uid?, id?, prompt?, height? }
175
260
  // - editor → site: { source: 'obelism-content-editor', type: 'connect' |
176
261
  // 'update' | 'select' | 'saved' | 'published' | 'features' |
177
- // 'promptResult' | 'promptState' | 'stats', … }
262
+ // 'promptResult' | 'promptState' | 'stats' | 'viewport', … }
178
263
  const BRIDGE_SOURCE = 'obelism-content-bridge';
179
264
  const EDITOR_SOURCE = 'obelism-content-editor';
180
265
  // How long a prompt may take before it counts as failed.
@@ -228,6 +313,7 @@ const INERT_METHODS = {
228
313
  prompt: inertPrompt,
229
314
  createPage: ()=>Promise.reject(new Error('New pages are made on the site itself')),
230
315
  createProductPage: ()=>Promise.reject(new Error('Product pages are made on the site itself')),
316
+ createCollectionPage: ()=>Promise.reject(new Error('Collection pages are made on the site itself')),
231
317
  view: onlyWhileEditingSite,
232
318
  applyJson: onlyWhileEditingSite,
233
319
  openForm: (_container, options = {})=>{
@@ -286,6 +372,7 @@ const createBridge = (win, cmsOrigin)=>{
286
372
  entryId: params.get(PREVIEW_PARAMS.entry),
287
373
  environment: params.get(PREVIEW_PARAMS.environment),
288
374
  product: null,
375
+ collection: null,
289
376
  on: emitter.on,
290
377
  getState: ()=>state,
291
378
  ...INERT_METHODS,
@@ -340,9 +427,32 @@ const createBridge = (win, cmsOrigin)=>{
340
427
  };
341
428
  doc.addEventListener('click', onClick, true);
342
429
  cleanup.push(()=>doc.removeEventListener('click', onClick, true));
343
- const ready = ()=>send('ready', {
344
- url: win.location.href
430
+ // The page's height, which an editor on another origin can't read: the
431
+ // canvas makes its frame as tall as the page. With `ready`, and as `size`
432
+ // whenever it changes.
433
+ const pageHeight = ()=>Math.ceil(Math.max(doc.documentElement.scrollHeight, doc.body?.scrollHeight ?? 0));
434
+ let sentHeight = 0;
435
+ const ready = ()=>{
436
+ sentHeight = pageHeight();
437
+ send('ready', {
438
+ url: win.location.href,
439
+ height: sentHeight
345
440
  });
441
+ };
442
+ const Observer = win.ResizeObserver;
443
+ if (typeof Observer === 'function') {
444
+ const observer = new Observer(()=>{
445
+ const height = pageHeight();
446
+ if (height === sentHeight) return;
447
+ sentHeight = height;
448
+ send('size', {
449
+ height
450
+ });
451
+ });
452
+ observer.observe(doc.documentElement);
453
+ if (doc.body) observer.observe(doc.body);
454
+ cleanup.push(()=>observer.disconnect());
455
+ }
346
456
  // Prompts waiting for the editor's answer, by id.
347
457
  const pending = new Map();
348
458
  let nextId = 0;
@@ -406,6 +516,8 @@ const createBridge = (win, cmsOrigin)=>{
406
516
  settle(id)?.reject(new Error('The bridge was destroyed'));
407
517
  }
408
518
  });
519
+ const viewportUnits = createViewportUnits(doc);
520
+ cleanup.push(()=>viewportUnits.destroy());
409
521
  const onMessage = (event)=>{
410
522
  if (event.origin !== cmsOrigin || event.source !== win.parent) return;
411
523
  const data = event.data;
@@ -438,6 +550,10 @@ const createBridge = (win, cmsOrigin)=>{
438
550
  statsVisible: data.visible !== false
439
551
  });
440
552
  showStats();
553
+ } else if (data.type === 'viewport' && typeof data.height === 'number') {
554
+ // The canvas: the frame is as tall as the page (`size`), so `vh`
555
+ // sizes are fixed at the screen height it stands for.
556
+ viewportUnits.freeze(data.height);
441
557
  } else if (data.type === 'promptState') {
442
558
  setState({
443
559
  canUndo: data.canUndo === true
@@ -479,6 +595,7 @@ const wrapScriptBridge = (script, params)=>{
479
595
  entryId: script.entryId,
480
596
  environment: params.get(PREVIEW_PARAMS.environment),
481
597
  product: null,
598
+ collection: null,
482
599
  on: (event, listener)=>{
483
600
  if (event === 'update' && !forwardingUpdates) {
484
601
  forwardingUpdates = true;
@@ -507,6 +624,7 @@ const DEFAULT_PROMPT_LABELS = {
507
624
  minimize: 'Minimize, to see the page',
508
625
  expand: 'Expand',
509
626
  scopeEntry: 'Whole page',
627
+ scopeNoPage: 'Whole page',
510
628
  scopeBlock: 'Selected block',
511
629
  clearBlock: 'Edit the whole page instead',
512
630
  working: 'Working on it…',
@@ -542,7 +660,11 @@ const DEFAULT_PROMPT_LABELS = {
542
660
  addProductPage: 'Add a product page',
543
661
  addingProductPage: 'Making the product page…',
544
662
  addedProductPage: 'Opening the product page…',
545
- productPageHint: 'This product has no page yet. Add one for what the store doesn’t say.'
663
+ productPageHint: 'This product has no page yet. Add one for what the store doesn’t say.',
664
+ addCollectionPage: 'Add a collection page',
665
+ addingCollectionPage: 'Making the collection page…',
666
+ addedCollectionPage: 'Opening the collection page…',
667
+ collectionPageHint: 'This collection has no page yet. Add one for what goes around its products.'
546
668
  };
547
669
  const SIDE_KEY = 'obelism-content-prompt-side';
548
670
  // How far the pointer moves before it drags the box, not clicks it.
@@ -632,7 +754,7 @@ button:disabled { cursor: not-allowed; }
632
754
  button:focus-visible { outline: 2px solid rgb(var(--theme-accent-rgb)); outline-offset: 2px; }
633
755
 
634
756
  /* Closed: one floating pill that reads like an empty field, with the mark. */
635
- .bar { display: flex; justify-content: center; }
757
+ .bar { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 8px; }
636
758
  .pill {
637
759
  display: flex; align-items: center; gap: 12px; width: min(560px, 100%); height: 56px;
638
760
  padding: 0 22px 0 26px; border-radius: var(--border-radius-pill);
@@ -763,7 +885,7 @@ button.chip:disabled { opacity: .5; }
763
885
 
764
886
  /* On the site: storing the draft, and the way out. */
765
887
  .footer {
766
- display: flex; align-items: center; gap: 8px; padding: 10px 12px 10px 22px;
888
+ display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 10px 12px 10px 22px;
767
889
  border-top: 1px solid rgb(var(--theme-border-rgb) / .2);
768
890
  background: rgb(var(--theme-background-actions-rgb) / .45);
769
891
  }
@@ -921,8 +1043,9 @@ const mountPromptBox = (doc, bridge, options = {})=>{
921
1043
  className: 'action chip',
922
1044
  type: 'button'
923
1045
  }, icon(doc, 'newPage'), labels.newPage);
924
- // A product the site marks (editableProduct) without a page of its own:
925
- // one click makes its product page, which the page then shows.
1046
+ // A product (collection) the site marks (editableProduct,
1047
+ // editableCollection) without a page of its own: one click makes its
1048
+ // page, which the page then shows.
926
1049
  const addProduct = element(doc, 'button', {
927
1050
  className: 'action chip product',
928
1051
  type: 'button'
@@ -1017,6 +1140,16 @@ const mountPromptBox = (doc, bridge, options = {})=>{
1017
1140
  }, element(doc, 'label', {
1018
1141
  className: 'search'
1019
1142
  }, icon(doc, 'search'), search), optionList, pagesNote);
1143
+ // The page's own controls (`actions`), in the footer while open and next
1144
+ // to the pill while closed.
1145
+ const actionsInFooter = element(doc, 'slot');
1146
+ actionsInFooter.name = 'actions-footer';
1147
+ const actionsInBar = element(doc, 'slot');
1148
+ actionsInBar.name = 'actions-bar';
1149
+ if (options.actions) {
1150
+ options.actions.slot = 'actions-bar';
1151
+ host.appendChild(options.actions);
1152
+ }
1020
1153
  const unsaved = element(doc, 'span', {
1021
1154
  className: 'unsaved'
1022
1155
  });
@@ -1043,7 +1176,7 @@ const mountPromptBox = (doc, bridge, options = {})=>{
1043
1176
  className: 'footer'
1044
1177
  }, pageChip, unsaved, element(doc, 'span', {
1045
1178
  className: 'spacer'
1046
- }), signOut, save, publish);
1179
+ }), actionsInFooter, signOut, save, publish);
1047
1180
  const panel = element(doc, 'form', {
1048
1181
  className: 'panel'
1049
1182
  }, status, pathRow, input, formBox, toolbar, pagesSection, footer);
@@ -1051,7 +1184,7 @@ const mountPromptBox = (doc, bridge, options = {})=>{
1051
1184
  className: 'root'
1052
1185
  }, element(doc, 'div', {
1053
1186
  className: 'bar'
1054
- }, pill, viewing), panel);
1187
+ }, pill, viewing, actionsInBar), panel);
1055
1188
  shadow.append(element(doc, 'style', {
1056
1189
  textContent: CSS
1057
1190
  }), root);
@@ -1080,7 +1213,7 @@ const mountPromptBox = (doc, bridge, options = {})=>{
1080
1213
  dragged = false;
1081
1214
  if (event.button !== 0 || !event.isPrimary) return;
1082
1215
  const target = event.target;
1083
- const control = target.closest('button, input, textarea, iframe, a, label, .pages, .form');
1216
+ const control = target.closest('button, input, textarea, select, iframe, a, label, .pages, .form');
1084
1217
  if (control && control !== pill) return;
1085
1218
  const { left, right } = root.getBoundingClientRect();
1086
1219
  drag = {
@@ -1176,8 +1309,20 @@ const mountPromptBox = (doc, bridge, options = {})=>{
1176
1309
  // A page of the site that isn't an entry (no `editable(entry)`, like a
1177
1310
  // hand-written /about) has nothing to edit: only New page.
1178
1311
  const pageless = site && bridge.entryId === null;
1179
- // …unless it shows a product: then Add a product page first.
1180
- const productHere = pageless && canCreate && bridge.product !== null;
1312
+ // …unless it shows a product (collection): then Add a product
1313
+ // (collection) page first.
1314
+ const shopHere = !pageless || !canCreate ? null : bridge.product ? 'product' : bridge.collection ? 'collection' : null;
1315
+ const productHere = shopHere !== null;
1316
+ const shopCopy = shopHere === 'collection' ? {
1317
+ add: labels.addCollectionPage,
1318
+ adding: labels.addingCollectionPage,
1319
+ hint: labels.collectionPageHint
1320
+ } : {
1321
+ add: labels.addProductPage,
1322
+ adding: labels.addingProductPage,
1323
+ hint: labels.productPageHint
1324
+ };
1325
+ addProduct.lastChild.textContent = shopCopy.add;
1181
1326
  const onlyNew = pageless && canCreate && !productHere;
1182
1327
  if (!canCreate) creating = false;
1183
1328
  if (onlyNew) creating = true;
@@ -1187,7 +1332,7 @@ const mountPromptBox = (doc, bridge, options = {})=>{
1187
1332
  if (showForm && !formHandle) mountForm();
1188
1333
  else if (!showForm && formHandle) unmountForm();
1189
1334
  const offerProduct = productHere && !creating;
1190
- pillText.textContent = offerProduct ? labels.addProductPage : onlyNew ? labels.newPage : labels.open;
1335
+ pillText.textContent = offerProduct ? shopCopy.add : onlyNew ? labels.newPage : labels.open;
1191
1336
  cancelNew.hidden = onlyNew;
1192
1337
  scope.hidden = creating || offerProduct;
1193
1338
  newScope.hidden = !creating;
@@ -1205,7 +1350,7 @@ const mountPromptBox = (doc, bridge, options = {})=>{
1205
1350
  toolbar.hidden = picking;
1206
1351
  pagesSection.hidden = !picking;
1207
1352
  input.placeholder = creating ? labels.newPagePlaceholder : labels.placeholder;
1208
- scopeText.textContent = state.selectedUid ? labels.scopeBlock : labels.scopeEntry;
1353
+ scopeText.textContent = state.selectedUid ? labels.scopeBlock : pageless ? labels.scopeNoPage : labels.scopeEntry;
1209
1354
  clearBlock.hidden = !state.selectedUid;
1210
1355
  pageIcon.hidden = !!state.selectedUid;
1211
1356
  blockIcon.hidden = !state.selectedUid;
@@ -1235,7 +1380,11 @@ const mountPromptBox = (doc, bridge, options = {})=>{
1235
1380
  publish.hidden = !state.features.includes('publish') || pageless;
1236
1381
  publish.disabled = busy;
1237
1382
  publish.textContent = state.publishing ? labels.publishing : labels.publish;
1238
- signOut.hidden = !site;
1383
+ signOut.hidden = !site || options.signOut === false;
1384
+ pillSignOut.hidden = options.signOut === false;
1385
+ if (options.actions) {
1386
+ options.actions.slot = open && available ? 'actions-footer' : 'actions-bar';
1387
+ }
1239
1388
  footer.hidden = !site;
1240
1389
  unsaved.hidden = !state.unsaved;
1241
1390
  pageChip.hidden = !site;
@@ -1244,7 +1393,7 @@ const mountPromptBox = (doc, bridge, options = {})=>{
1244
1393
  const loadingForm = form && formLoading && !picking;
1245
1394
  const working = state.prompting;
1246
1395
  // A form opening says so in its own place.
1247
- const note = state.prompting ? creating ? labels.creating : offerProduct ? labels.addingProductPage : labels.working : loadingForm ? '' : message?.text ?? (offerProduct ? labels.productPageHint : site && !creating && !picking && !state.selectedUid ? labels.selectHint : '');
1396
+ const note = state.prompting ? creating ? labels.creating : offerProduct ? shopCopy.adding : labels.working : loadingForm ? '' : message?.text ?? (offerProduct ? shopCopy.hint : site && !creating && !picking && !state.selectedUid ? labels.selectHint : '');
1248
1397
  status.textContent = note;
1249
1398
  status.hidden = !note || minimized;
1250
1399
  status.className = `status${working ? ' working' : ''}${!busy && !loadingForm && message?.error ? ' error' : ''}`;
@@ -1467,11 +1616,19 @@ const mountPromptBox = (doc, bridge, options = {})=>{
1467
1616
  message = null;
1468
1617
  try {
1469
1618
  // The bridge reloads the page; this shows until it does.
1470
- await bridge.createProductPage();
1471
- message = {
1472
- text: labels.addedProductPage,
1473
- error: false
1474
- };
1619
+ if (bridge.product) {
1620
+ await bridge.createProductPage();
1621
+ message = {
1622
+ text: labels.addedProductPage,
1623
+ error: false
1624
+ };
1625
+ } else {
1626
+ await bridge.createCollectionPage();
1627
+ message = {
1628
+ text: labels.addedCollectionPage,
1629
+ error: false
1630
+ };
1631
+ }
1475
1632
  } catch (error) {
1476
1633
  message = failure(error);
1477
1634
  }
@@ -1668,7 +1825,7 @@ const FORM_BURST_MS = 1000;
1668
1825
  // How long the page waits for the form's edits to settle before it shows
1669
1826
  // them.
1670
1827
  const FORM_RENDER_MS = 300;
1671
- const createSiteBridge = (win, basePath, { pagePath = (path)=>`/${path}`, productPath: ownProductPath } = {})=>{
1828
+ const createSiteBridge = (win, basePath, { pagePath = (path)=>`/${path}`, productPath: ownProductPath, collectionPath: ownCollectionPath } = {})=>{
1672
1829
  const doc = win.document;
1673
1830
  const emitter = createEmitter();
1674
1831
  const cleanup = [];
@@ -1679,6 +1836,7 @@ const createSiteBridge = (win, basePath, { pagePath = (path)=>`/${path}`, produc
1679
1836
  const touched = new Set();
1680
1837
  let session = null;
1681
1838
  const productPath = (handle, locale)=>ownProductPath ? ownProductPath(handle, locale) : (session?.productPath ?? '/products/{handle}').replaceAll('{handle}', encodeURIComponent(handle));
1839
+ const collectionPath = (handle, locale)=>ownCollectionPath ? ownCollectionPath(handle, locale) : (session?.collectionPath ?? '/collections/{handle}').replaceAll('{handle}', encodeURIComponent(handle));
1682
1840
  // The open form's, if any; and when the form last changed content, for
1683
1841
  // undo steps (-Infinity: the next edit starts a new step).
1684
1842
  let closeForm = null;
@@ -1733,6 +1891,16 @@ const createSiteBridge = (win, basePath, { pagePath = (path)=>`/${path}`, produc
1733
1891
  handle
1734
1892
  } : null;
1735
1893
  };
1894
+ // The collection the page shows (editableCollection), if it marks one.
1895
+ const pageCollection = ()=>{
1896
+ const element = doc.querySelector(`[${COLLECTION_ATTRIBUTE}]`);
1897
+ const id = element?.getAttribute(COLLECTION_ATTRIBUTE);
1898
+ const handle = element?.getAttribute(COLLECTION_HANDLE_ATTRIBUTE);
1899
+ return id && handle ? {
1900
+ id,
1901
+ handle
1902
+ } : null;
1903
+ };
1736
1904
  const entryFor = (uid)=>{
1737
1905
  const block = uid ? doc.querySelector(blockSelector(uid)) : null;
1738
1906
  return block?.closest(`[${ENTRY_ATTRIBUTE}]`)?.getAttribute(ENTRY_ATTRIBUTE) ?? pageEntryId();
@@ -1843,6 +2011,9 @@ const createSiteBridge = (win, basePath, { pagePath = (path)=>`/${path}`, produc
1843
2011
  get product () {
1844
2012
  return pageProduct();
1845
2013
  },
2014
+ get collection () {
2015
+ return pageCollection();
2016
+ },
1846
2017
  environment: null,
1847
2018
  on: emitter.on,
1848
2019
  getState: ()=>state,
@@ -1940,6 +2111,34 @@ const createSiteBridge = (win, basePath, { pagePath = (path)=>`/${path}`, produc
1940
2111
  });
1941
2112
  }
1942
2113
  },
2114
+ async createCollectionPage (collection = pageCollection() ?? undefined) {
2115
+ if (!collection) {
2116
+ throw new Error('Nothing on this page is marked as a collection: add editableCollection(collection)');
2117
+ }
2118
+ if (!has('create')) throw new Error('You may not add pages here');
2119
+ if (state.unsaved) {
2120
+ throw new Error('Save or undo your changes before adding a page');
2121
+ }
2122
+ setState({
2123
+ prompting: true
2124
+ });
2125
+ try {
2126
+ const made = await api('collection-pages', {
2127
+ collection: {
2128
+ id: collection.id,
2129
+ handle: collection.handle
2130
+ }
2131
+ });
2132
+ // The site's collection route finds its page now (drafts, while
2133
+ // editing): show it.
2134
+ win.location.reload();
2135
+ return made;
2136
+ } finally{
2137
+ setState({
2138
+ prompting: false
2139
+ });
2140
+ }
2141
+ },
1943
2142
  async view (options = {}) {
1944
2143
  const { uid, entryId } = target(options.uid);
1945
2144
  const entry = await load(entryId);
@@ -2093,8 +2292,9 @@ const createSiteBridge = (win, basePath, { pagePath = (path)=>`/${path}`, produc
2093
2292
  contentType: page.contentType,
2094
2293
  status: page.status,
2095
2294
  path,
2096
- url: page.product ? productPath(page.product.handle, code) : pagePath(path, code),
2097
- product: page.product ?? null
2295
+ url: page.product ? productPath(page.product.handle, code) : page.collection ? collectionPath(page.collection.handle, code) : pagePath(path, code),
2296
+ product: page.product ?? null,
2297
+ collection: page.collection ?? null
2098
2298
  }
2099
2299
  ];
2100
2300
  });
@@ -2239,7 +2439,7 @@ const peekContentBridge = ()=>typeof window === 'undefined' ? null : window[KEY]
2239
2439
  // if (bridge.isEditing) bridge.on('update', ({ entry }) => render(entry))
2240
2440
  //
2241
2441
  // The first call's options win: there is one bridge per page.
2242
- const getContentBridge = ({ url, prompt = true, pagePath, productPath } = {})=>{
2442
+ const getContentBridge = ({ url, prompt = true, pagePath, productPath, collectionPath } = {})=>{
2243
2443
  if (typeof window === 'undefined') return SERVER_BRIDGE;
2244
2444
  const win = window;
2245
2445
  if (win[KEY]) return win[KEY];
@@ -2253,12 +2453,26 @@ const getContentBridge = ({ url, prompt = true, pagePath, productPath } = {})=>{
2253
2453
  const sitePath = bridge.isEditing || win.parent !== win ? null : readSiteEditing(win.document);
2254
2454
  if (sitePath) bridge = createSiteBridge(win, sitePath, {
2255
2455
  pagePath,
2256
- productPath
2456
+ productPath,
2457
+ collectionPath
2257
2458
  });
2258
2459
  if (bridge.isEditing && prompt) {
2259
- const unmount = mountPromptBox(win.document, bridge, prompt === true ? {} : prompt);
2460
+ const doc = win.document;
2461
+ let unmount = ()=>{};
2462
+ let destroyed = false;
2463
+ const mount = ()=>{
2464
+ if (destroyed) return;
2465
+ unmount = mountPromptBox(doc, bridge, prompt === true ? {} : prompt);
2466
+ };
2467
+ // A script tag in <head> runs before there is a body to mount into.
2468
+ if (doc.body) mount();
2469
+ else doc.addEventListener('DOMContentLoaded', mount, {
2470
+ once: true
2471
+ });
2260
2472
  const destroy = bridge.destroy;
2261
2473
  bridge.destroy = ()=>{
2474
+ destroyed = true;
2475
+ doc.removeEventListener('DOMContentLoaded', mount);
2262
2476
  unmount();
2263
2477
  destroy();
2264
2478
  };
package/dist/index.d.mts CHANGED
@@ -80,6 +80,14 @@ declare const editableProduct: (product: {
80
80
  id: string;
81
81
  handle: string;
82
82
  } | null | undefined) => EditableProduct;
83
+ type EditableCollection = {
84
+ 'data-cms-collection'?: string;
85
+ 'data-cms-collection-handle'?: string;
86
+ };
87
+ declare const editableCollection: (collection: {
88
+ id: string;
89
+ handle: string;
90
+ } | null | undefined) => EditableCollection;
83
91
 
84
92
  declare class ContentApiError extends Error {
85
93
  readonly status: number;
@@ -195,6 +203,6 @@ type SitemapOptions = LocalizedUrlOptions & {
195
203
  };
196
204
  declare const sitemapXml: (entries: Iterable<Page>, { exclude, ...options }: SitemapOptions) => string;
197
205
 
198
- export { Asset, Block, ContentApiError, ContentClient, DEFAULT_URL, ENTRY_ATTRIBUTE, Entry, EntryList, EntryStub, IMPROVE_BLOCK, ImproveBlock, Link, LinkTarget, ModelFile, PREVIEW_PARAMS, PRODUCT_ATTRIBUTE, PRODUCT_HANDLE_ATTRIBUTE, Redirect, RedirectList, RichTextFormat, RichTextNode, SAFE_HREF, SHOPIFY_STOREFRONT_API_VERSION, ShopifyItem, ShopifyStorefrontError, UID_ATTRIBUTE, alternateLinks, editable, editableProduct, escapeHtml, fetchShopifyCollection, fetchShopifyProducts, headingLevel, imageSrcSet, imageUrl, isImproveBlock, linkHref, pickImproveVariant, readPreviewParams, richTextToHtml, richTextToPlainText, sitemapXml };
206
+ export { Asset, Block, ContentApiError, ContentClient, DEFAULT_URL, ENTRY_ATTRIBUTE, Entry, EntryList, EntryStub, IMPROVE_BLOCK, ImproveBlock, Link, LinkTarget, ModelFile, PREVIEW_PARAMS, PRODUCT_ATTRIBUTE, PRODUCT_HANDLE_ATTRIBUTE, Redirect, RedirectList, RichTextFormat, RichTextNode, SAFE_HREF, SHOPIFY_STOREFRONT_API_VERSION, ShopifyItem, ShopifyStorefrontError, UID_ATTRIBUTE, alternateLinks, editable, editableCollection, editableProduct, escapeHtml, fetchShopifyCollection, fetchShopifyProducts, headingLevel, imageSrcSet, imageUrl, isImproveBlock, linkHref, pickImproveVariant, readPreviewParams, richTextToHtml, richTextToPlainText, sitemapXml };
199
207
  export type { AlternateLink, ContentClientOptions, EntriesQuery, ImageTransform, LinkHrefOptions, LocalizedUrlOptions, PageLookup, PreviewParams, RedirectsQuery, RequestOptions, RichTextHtmlOptions, ShopifyCollection, ShopifyImage, ShopifyMoney, ShopifyProduct, ShopifyStorefrontOptions, SitemapOptions };
200
208
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","sources":["../src/client.ts","../src/config.ts","../src/editable.ts","../src/errors.ts","../src/images.ts","../src/improve.ts","../src/links.ts","../src/preview.ts","../src/shopify.ts","../src/richText.ts","../src/sitemap.ts"],"mappings":"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","names":[]}
1
+ {"version":3,"file":"index.d.mts","sources":["../src/client.ts","../src/config.ts","../src/editable.ts","../src/errors.ts","../src/images.ts","../src/improve.ts","../src/links.ts","../src/preview.ts","../src/shopify.ts","../src/richText.ts","../src/sitemap.ts"],"mappings":"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","names":[]}
package/dist/index.d.ts CHANGED
@@ -80,6 +80,14 @@ declare const editableProduct: (product: {
80
80
  id: string;
81
81
  handle: string;
82
82
  } | null | undefined) => EditableProduct;
83
+ type EditableCollection = {
84
+ 'data-cms-collection'?: string;
85
+ 'data-cms-collection-handle'?: string;
86
+ };
87
+ declare const editableCollection: (collection: {
88
+ id: string;
89
+ handle: string;
90
+ } | null | undefined) => EditableCollection;
83
91
 
84
92
  declare class ContentApiError extends Error {
85
93
  readonly status: number;
@@ -195,6 +203,6 @@ type SitemapOptions = LocalizedUrlOptions & {
195
203
  };
196
204
  declare const sitemapXml: (entries: Iterable<Page>, { exclude, ...options }: SitemapOptions) => string;
197
205
 
198
- export { Asset, Block, ContentApiError, ContentClient, DEFAULT_URL, ENTRY_ATTRIBUTE, Entry, EntryList, EntryStub, IMPROVE_BLOCK, ImproveBlock, Link, LinkTarget, ModelFile, PREVIEW_PARAMS, PRODUCT_ATTRIBUTE, PRODUCT_HANDLE_ATTRIBUTE, Redirect, RedirectList, RichTextFormat, RichTextNode, SAFE_HREF, SHOPIFY_STOREFRONT_API_VERSION, ShopifyItem, ShopifyStorefrontError, UID_ATTRIBUTE, alternateLinks, editable, editableProduct, escapeHtml, fetchShopifyCollection, fetchShopifyProducts, headingLevel, imageSrcSet, imageUrl, isImproveBlock, linkHref, pickImproveVariant, readPreviewParams, richTextToHtml, richTextToPlainText, sitemapXml };
206
+ export { Asset, Block, ContentApiError, ContentClient, DEFAULT_URL, ENTRY_ATTRIBUTE, Entry, EntryList, EntryStub, IMPROVE_BLOCK, ImproveBlock, Link, LinkTarget, ModelFile, PREVIEW_PARAMS, PRODUCT_ATTRIBUTE, PRODUCT_HANDLE_ATTRIBUTE, Redirect, RedirectList, RichTextFormat, RichTextNode, SAFE_HREF, SHOPIFY_STOREFRONT_API_VERSION, ShopifyItem, ShopifyStorefrontError, UID_ATTRIBUTE, alternateLinks, editable, editableCollection, editableProduct, escapeHtml, fetchShopifyCollection, fetchShopifyProducts, headingLevel, imageSrcSet, imageUrl, isImproveBlock, linkHref, pickImproveVariant, readPreviewParams, richTextToHtml, richTextToPlainText, sitemapXml };
199
207
  export type { AlternateLink, ContentClientOptions, EntriesQuery, ImageTransform, LinkHrefOptions, LocalizedUrlOptions, PageLookup, PreviewParams, RedirectsQuery, RequestOptions, RichTextHtmlOptions, ShopifyCollection, ShopifyImage, ShopifyMoney, ShopifyProduct, ShopifyStorefrontOptions, SitemapOptions };
200
208
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sources":["../src/client.ts","../src/config.ts","../src/editable.ts","../src/errors.ts","../src/images.ts","../src/improve.ts","../src/links.ts","../src/preview.ts","../src/shopify.ts","../src/richText.ts","../src/sitemap.ts"],"mappings":"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","names":[]}
1
+ {"version":3,"file":"index.d.ts","sources":["../src/client.ts","../src/config.ts","../src/editable.ts","../src/errors.ts","../src/images.ts","../src/improve.ts","../src/links.ts","../src/preview.ts","../src/shopify.ts","../src/richText.ts","../src/sitemap.ts"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, '__esModule', { value: true });
2
2
 
3
3
  var config = require('./config-ci2hxmas.js');
4
- var preview = require('./preview-ijk3ixwb.js');
4
+ var preview = require('./preview-ooq8w3jv.js');
5
5
 
6
6
  // A response from the API that wasn't a success. `message` is the API's own
7
7
  // `error` text when it sent one.
@@ -513,6 +513,7 @@ exports.PRODUCT_ATTRIBUTE = preview.PRODUCT_ATTRIBUTE;
513
513
  exports.PRODUCT_HANDLE_ATTRIBUTE = preview.PRODUCT_HANDLE_ATTRIBUTE;
514
514
  exports.UID_ATTRIBUTE = preview.UID_ATTRIBUTE;
515
515
  exports.editable = preview.editable;
516
+ exports.editableCollection = preview.editableCollection;
516
517
  exports.editableProduct = preview.editableProduct;
517
518
  exports.readPreviewParams = preview.readPreviewParams;
518
519
  exports.ContentApiError = ContentApiError;
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { n as normalizeUrl, a as DEFAULT_TIMEOUT, b as DELIVERY_PATH, D as DEFAULT_URL } from './config-c6qktlz6.mjs';
2
- export { E as ENTRY_ATTRIBUTE, P as PREVIEW_PARAMS, a as PRODUCT_ATTRIBUTE, b as PRODUCT_HANDLE_ATTRIBUTE, U as UID_ATTRIBUTE, e as editable, c as editableProduct, r as readPreviewParams } from './preview-jcmxxj92.mjs';
2
+ export { E as ENTRY_ATTRIBUTE, P as PREVIEW_PARAMS, b as PRODUCT_ATTRIBUTE, c as PRODUCT_HANDLE_ATTRIBUTE, U as UID_ATTRIBUTE, e as editable, d as editableCollection, f as editableProduct, r as readPreviewParams } from './preview-fu3qtgfx.mjs';
3
3
 
4
4
  // A response from the API that wasn't a success. `message` is the API's own
5
5
  // `error` text when it sent one.
@@ -2,6 +2,8 @@ const UID_ATTRIBUTE = 'data-cms-uid';
2
2
  const ENTRY_ATTRIBUTE = 'data-cms-entry';
3
3
  const PRODUCT_ATTRIBUTE = 'data-cms-product';
4
4
  const PRODUCT_HANDLE_ATTRIBUTE = 'data-cms-product-handle';
5
+ const COLLECTION_ATTRIBUTE = 'data-cms-collection';
6
+ const COLLECTION_HANDLE_ATTRIBUTE = 'data-cms-collection-handle';
5
7
  // The attribute that lets editors select a block on the page, in the visual
6
8
  // editor and on the site. Spread it on the block's outermost element:
7
9
  //
@@ -36,6 +38,18 @@ const editableProduct = (product)=>product?.id ? {
36
38
  'data-cms-product': product.id,
37
39
  'data-cms-product-handle': product.handle
38
40
  } : {};
41
+ // The Shopify collection a page shows, on a collection route: editing on the
42
+ // site then offers "Add a collection page" when the page has no collection
43
+ // page yet (no `editable(entry)`), and makes one for this collection.
44
+ //
45
+ // <main {...editableCollection(collection)} {...editable(page)}>…</main>
46
+ //
47
+ // `collection` is the store's (its GID and handle), from the Storefront API
48
+ // or a Shopify field.
49
+ const editableCollection = (collection)=>collection?.id ? {
50
+ 'data-cms-collection': collection.id,
51
+ 'data-cms-collection-handle': collection.handle
52
+ } : {};
39
53
 
40
54
  // The visual editor loads the site in an iframe at the environment's preview
41
55
  // URL plus the entry's path, with these parameters added. `cms_token` is a
@@ -84,4 +98,4 @@ const readPreviewParams = (input)=>{
84
98
  };
85
99
  };
86
100
 
87
- export { ENTRY_ATTRIBUTE as E, PREVIEW_PARAMS as P, UID_ATTRIBUTE as U, PRODUCT_ATTRIBUTE as a, PRODUCT_HANDLE_ATTRIBUTE as b, editableProduct as c, editable as e, readPreviewParams as r };
101
+ export { COLLECTION_ATTRIBUTE as C, ENTRY_ATTRIBUTE as E, PREVIEW_PARAMS as P, UID_ATTRIBUTE as U, COLLECTION_HANDLE_ATTRIBUTE as a, PRODUCT_ATTRIBUTE as b, PRODUCT_HANDLE_ATTRIBUTE as c, editableCollection as d, editable as e, editableProduct as f, readPreviewParams as r };