@obelism/content 0.17.0 → 0.19.1

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 } from './preview-h7rwp06v.mjs';
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';
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
 
@@ -227,6 +227,7 @@ const onlyWhileEditingSite = ()=>Promise.reject(new Error('Only while signed in
227
227
  const INERT_METHODS = {
228
228
  prompt: inertPrompt,
229
229
  createPage: ()=>Promise.reject(new Error('New pages are made on the site itself')),
230
+ createProductPage: ()=>Promise.reject(new Error('Product pages are made on the site itself')),
230
231
  view: onlyWhileEditingSite,
231
232
  applyJson: onlyWhileEditingSite,
232
233
  openForm: (_container, options = {})=>{
@@ -284,6 +285,7 @@ const createBridge = (win, cmsOrigin)=>{
284
285
  mode: isEditing ? 'editor' : null,
285
286
  entryId: params.get(PREVIEW_PARAMS.entry),
286
287
  environment: params.get(PREVIEW_PARAMS.environment),
288
+ product: null,
287
289
  on: emitter.on,
288
290
  getState: ()=>state,
289
291
  ...INERT_METHODS,
@@ -476,6 +478,7 @@ const wrapScriptBridge = (script, params)=>{
476
478
  mode: script.isEditing ? 'editor' : null,
477
479
  entryId: script.entryId,
478
480
  environment: params.get(PREVIEW_PARAMS.environment),
481
+ product: null,
479
482
  on: (event, listener)=>{
480
483
  if (event === 'update' && !forwardingUpdates) {
481
484
  forwardingUpdates = true;
@@ -535,8 +538,24 @@ const DEFAULT_PROMPT_LABELS = {
535
538
  pagesEmpty: 'No pages match',
536
539
  pageCurrent: 'This page',
537
540
  pageDraft: 'Draft',
538
- pageChanged: 'Changed'
541
+ pageChanged: 'Changed',
542
+ addProductPage: 'Add a product page',
543
+ addingProductPage: 'Making the product page…',
544
+ addedProductPage: 'Opening the product page…',
545
+ productPageHint: 'This product has no page yet. Add one for what the store doesn’t say.'
539
546
  };
547
+ const SIDE_KEY = 'obelism-content-prompt-side';
548
+ // How far the pointer moves before it drags the box, not clicks it.
549
+ const DRAG_THRESHOLD_PX = 6;
550
+ // How far ahead a flick carries the box: its speed for this long.
551
+ const FLICK_MS = 150;
552
+ // …at most this far, so one coarse pointer event isn't a flick across the
553
+ // screen…
554
+ const FLICK_MAX_PX = 120;
555
+ // …and none when the pointer stood still this long before letting go.
556
+ const FLICK_STALE_MS = 80;
557
+ // Its distance from the side of the screen, as in the CSS.
558
+ const EDGE_PX = 16;
540
559
  // Icons (Lucide), drawn in the text colour.
541
560
  const ICONS = {
542
561
  page: '<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/>',
@@ -549,6 +568,7 @@ const ICONS = {
549
568
  chevrons: '<path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/>',
550
569
  search: '<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>',
551
570
  check: '<path d="M20 6 9 17l-5-5"/>',
571
+ mark: '<path d="M12 3v18M3 12h18"/><g fill="currentColor" stroke="none"><circle cx="7" cy="7" r="1.75"/><circle cx="17" cy="7" r="1.75"/><circle cx="7" cy="17" r="1.75"/><circle cx="17" cy="17" r="1.75"/></g>',
552
572
  send: '<path d="M3.714 3.048a.498.498 0 0 0-.683.627l2.843 7.627a2 2 0 0 1 0 1.396l-2.842 7.627a.498.498 0 0 0 .682.627l18-8.5a.5.5 0 0 0 0-.904z"/><path d="M6 12h16"/>'
553
573
  };
554
574
  // Obelism's design tokens (obelism.studio/design.md), under their own names:
@@ -574,10 +594,20 @@ const CSS = `
574
594
  --font-inter: 'InterVariable', 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
575
595
  Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif;
576
596
  position: fixed; left: 50%; bottom: 20px; transform: translateX(-50%);
597
+ transition: translate .45s cubic-bezier(0.34, 1.3, 0.64, 1);
577
598
  z-index: 2147483647; width: min(640px, calc(100vw - 32px)); color-scheme: light dark;
578
599
  font: 400 14px/1.4 var(--font-inter); color: rgb(var(--theme-foreground-rgb));
579
600
  -webkit-font-smoothing: antialiased;
580
601
  }
602
+ /* Dragged to a side of the screen, it stays there, springing into place. */
603
+ .root[data-side='left'] { left: 16px; transform: none; }
604
+ .root[data-side='right'] { left: auto; right: 16px; transform: none; }
605
+ .root[data-side='left'] .bar { justify-content: flex-start; }
606
+ .root[data-side='right'] .bar { justify-content: flex-end; }
607
+ .root.dragging { transition: none; user-select: none; -webkit-user-select: none; }
608
+ .root.dragging, .root.dragging * { cursor: grabbing !important; }
609
+ .toolbar, .footer, .status { cursor: grab; touch-action: none; }
610
+ .pill { touch-action: none; }
581
611
  @media (prefers-color-scheme: dark) {
582
612
  .root {
583
613
  --theme-foreground-rgb: var(--color-white-rgb);
@@ -613,7 +643,8 @@ button:focus-visible { outline: 2px solid rgb(var(--theme-accent-rgb)); outline-
613
643
  }
614
644
  button.pill:hover { color: rgb(var(--theme-foreground-rgb)); transform: translateY(-2px); }
615
645
  .pill > span:first-child { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
616
- .mark { color: rgb(var(--theme-accent-rgb)); font-size: 22px; line-height: 1; }
646
+ .mark { color: rgb(var(--theme-accent-rgb)); }
647
+ .mark svg { width: 20px; height: 20px; }
617
648
  .pill.compact { width: auto; height: 40px; padding: 0 18px; font-size: 14px; gap: 4px; }
618
649
 
619
650
  /* Open: a floating card, the text on top and a row of pill buttons below. */
@@ -631,7 +662,7 @@ button.pill:hover { color: rgb(var(--theme-foreground-rgb)); transform: translat
631
662
  @keyframes rise { from { opacity: 0; transform: translateY(8px) scale(.98); } }
632
663
  @keyframes spin { to { transform: rotate(1turn); } }
633
664
  @media (prefers-reduced-motion: reduce) {
634
- .panel { animation: none; }
665
+ .root, .panel { animation: none; transition: none; }
635
666
  .pill { transition: none; }
636
667
  }
637
668
  .status {
@@ -783,6 +814,11 @@ const icon = (doc, name)=>{
783
814
  span.innerHTML = `<svg viewBox="0 0 24 24">${ICONS[name]}</svg>`;
784
815
  return span;
785
816
  };
817
+ const mark = (doc)=>{
818
+ const span = icon(doc, 'mark');
819
+ span.classList.add('mark');
820
+ return span;
821
+ };
786
822
  // Mounts the box on the page; returns a function that removes it.
787
823
  const mountPromptBox = (doc, bridge, options = {})=>{
788
824
  const labels = {
@@ -801,11 +837,8 @@ const mountPromptBox = (doc, bridge, options = {})=>{
801
837
  const pill = element(doc, 'button', {
802
838
  className: 'pill',
803
839
  type: 'button'
804
- }, pillText, // The Obelism mark.
805
- element(doc, 'span', {
806
- className: 'mark',
807
- textContent: '⁜'
808
- }));
840
+ }, pillText, // The Obelism mark (⁜), drawn: the glyph sits low in most fonts.
841
+ mark(doc));
809
842
  // Someone signed in who may only read: a note that drafts show, and a
810
843
  // way out.
811
844
  const viewing = element(doc, 'span', {
@@ -870,6 +903,12 @@ const mountPromptBox = (doc, bridge, options = {})=>{
870
903
  className: 'action chip',
871
904
  type: 'button'
872
905
  }, icon(doc, 'newPage'), labels.newPage);
906
+ // A product the site marks (editableProduct) without a page of its own:
907
+ // one click makes its product page, which the page then shows.
908
+ const addProduct = element(doc, 'button', {
909
+ className: 'action chip product',
910
+ type: 'button'
911
+ }, icon(doc, 'newPage'), labels.addProductPage);
873
912
  const cancelNew = element(doc, 'button', {
874
913
  type: 'button',
875
914
  title: labels.newPageCancel
@@ -919,7 +958,7 @@ const mountPromptBox = (doc, bridge, options = {})=>{
919
958
  });
920
959
  const toolbar = element(doc, 'div', {
921
960
  className: 'toolbar'
922
- }, scope, newScope, modes, undo, newPage, element(doc, 'span', {
961
+ }, scope, newScope, modes, undo, addProduct, newPage, element(doc, 'span', {
923
962
  className: 'spacer'
924
963
  }), minimize, close, divider, send);
925
964
  // The pages (on the site): a chip naming this page, opening a search
@@ -998,6 +1037,95 @@ const mountPromptBox = (doc, bridge, options = {})=>{
998
1037
  shadow.append(element(doc, 'style', {
999
1038
  textContent: CSS
1000
1039
  }), root);
1040
+ // Dragged sideways, the box moves to the left, the middle or the right
1041
+ // of the screen, whichever third it is let go (or flicked) in; this
1042
+ // browser remembers where. From the pill, or the open box's bars, not its
1043
+ // fields and buttons.
1044
+ const win = doc.defaultView;
1045
+ const readSide = ()=>{
1046
+ try {
1047
+ const side = win?.localStorage.getItem(SIDE_KEY);
1048
+ return side === 'left' || side === 'right' ? side : 'center';
1049
+ } catch {
1050
+ return 'center';
1051
+ }
1052
+ };
1053
+ const placeAt = (side)=>{
1054
+ if (side === 'center') delete root.dataset.side;
1055
+ else root.dataset.side = side;
1056
+ };
1057
+ placeAt(readSide());
1058
+ let drag = null;
1059
+ // A drag that ends on the pill isn't a click on it.
1060
+ let dragged = false;
1061
+ root.addEventListener('pointerdown', (event)=>{
1062
+ dragged = false;
1063
+ if (event.button !== 0 || !event.isPrimary) return;
1064
+ const target = event.target;
1065
+ const control = target.closest('button, input, textarea, iframe, a, label, .pages, .form');
1066
+ if (control && control !== pill) return;
1067
+ const { left, right } = root.getBoundingClientRect();
1068
+ drag = {
1069
+ id: event.pointerId,
1070
+ startX: event.clientX,
1071
+ x: event.clientX,
1072
+ velocity: 0,
1073
+ time: event.timeStamp,
1074
+ moved: false,
1075
+ min: Math.min(0, EDGE_PX - left),
1076
+ max: Math.max(0, (win?.innerWidth ?? 0) - EDGE_PX - right)
1077
+ };
1078
+ });
1079
+ root.addEventListener('pointermove', (event)=>{
1080
+ if (!drag || event.pointerId !== drag.id) return;
1081
+ const dx = event.clientX - drag.startX;
1082
+ if (!drag.moved) {
1083
+ if (Math.abs(dx) < DRAG_THRESHOLD_PX) return;
1084
+ drag.moved = true;
1085
+ root.classList.add('dragging');
1086
+ try {
1087
+ root.setPointerCapture(event.pointerId);
1088
+ } catch {}
1089
+ }
1090
+ const elapsed = event.timeStamp - drag.time;
1091
+ if (elapsed > 0) drag.velocity = (event.clientX - drag.x) / elapsed;
1092
+ drag.x = event.clientX;
1093
+ drag.time = event.timeStamp;
1094
+ // Past the edge, a third as far.
1095
+ const clamped = Math.min(Math.max(dx, drag.min), drag.max);
1096
+ root.style.translate = `${clamped + (dx - clamped) / 3}px 0`;
1097
+ });
1098
+ const drop = (event)=>{
1099
+ if (!drag || event.pointerId !== drag.id) return;
1100
+ const { moved, x, velocity, time } = drag;
1101
+ drag = null;
1102
+ if (!moved) return;
1103
+ dragged = true;
1104
+ // Springs from where it was let go to its new place.
1105
+ const before = root.getBoundingClientRect().left;
1106
+ const width = win?.innerWidth ?? 0;
1107
+ const flick = event.timeStamp - time > FLICK_STALE_MS ? 0 : Math.min(Math.max(velocity * FLICK_MS, -FLICK_MAX_PX), FLICK_MAX_PX);
1108
+ const projected = x + flick;
1109
+ const side = projected < width / 3 ? 'left' : projected > width * 2 / 3 ? 'right' : 'center';
1110
+ placeAt(side);
1111
+ root.style.translate = '';
1112
+ const after = root.getBoundingClientRect().left;
1113
+ root.style.translate = `${before - after}px 0`;
1114
+ void root.offsetWidth;
1115
+ root.classList.remove('dragging');
1116
+ root.style.translate = '';
1117
+ try {
1118
+ win?.localStorage.setItem(SIDE_KEY, side);
1119
+ } catch {}
1120
+ };
1121
+ root.addEventListener('pointerup', drop);
1122
+ root.addEventListener('pointercancel', drop);
1123
+ root.addEventListener('click', (event)=>{
1124
+ if (!dragged) return;
1125
+ dragged = false;
1126
+ event.preventDefault();
1127
+ event.stopPropagation();
1128
+ }, true);
1001
1129
  let open = false;
1002
1130
  let minimized = false;
1003
1131
  let creating = false;
@@ -1030,7 +1158,9 @@ const mountPromptBox = (doc, bridge, options = {})=>{
1030
1158
  // A page of the site that isn't an entry (no `editable(entry)`, like a
1031
1159
  // hand-written /about) has nothing to edit: only New page.
1032
1160
  const pageless = site && bridge.entryId === null;
1033
- const onlyNew = pageless && canCreate;
1161
+ // …unless it shows a product: then Add a product page first.
1162
+ const productHere = pageless && canCreate && bridge.product !== null;
1163
+ const onlyNew = pageless && canCreate && !productHere;
1034
1164
  if (!canCreate) creating = false;
1035
1165
  if (onlyNew) creating = true;
1036
1166
  if (creating || pageless || !site) form = false;
@@ -1038,16 +1168,19 @@ const mountPromptBox = (doc, bridge, options = {})=>{
1038
1168
  const showForm = open && available && form;
1039
1169
  if (showForm && !formHandle) mountForm();
1040
1170
  else if (!showForm && formHandle) unmountForm();
1041
- pillText.textContent = onlyNew ? labels.newPage : labels.open;
1171
+ const offerProduct = productHere && !creating;
1172
+ pillText.textContent = offerProduct ? labels.addProductPage : onlyNew ? labels.newPage : labels.open;
1042
1173
  cancelNew.hidden = onlyNew;
1043
- scope.hidden = creating;
1174
+ scope.hidden = creating || offerProduct;
1044
1175
  newScope.hidden = !creating;
1176
+ addProduct.hidden = !offerProduct;
1177
+ addProduct.disabled = busy;
1045
1178
  newPage.hidden = creating || form || !canCreate;
1046
1179
  newPage.disabled = busy;
1047
1180
  if (minimized) picking = false;
1048
1181
  pathRow.hidden = !creating || picking || minimized;
1049
1182
  pathInput.disabled = busy;
1050
- input.hidden = form || picking || minimized;
1183
+ input.hidden = form || picking || minimized || offerProduct;
1051
1184
  formBox.hidden = !form || picking || minimized;
1052
1185
  formBox.classList.toggle('loading', formLoading);
1053
1186
  toolbar.hidden = picking;
@@ -1067,8 +1200,8 @@ const mountPromptBox = (doc, bridge, options = {})=>{
1067
1200
  undo.hidden = creating || !state.canUndo;
1068
1201
  undo.disabled = busy;
1069
1202
  // The form applies edits as they are made.
1070
- send.hidden = form || minimized;
1071
- divider.hidden = form || minimized;
1203
+ send.hidden = form || minimized || offerProduct;
1204
+ divider.hidden = form || minimized || offerProduct;
1072
1205
  const toggleLabel = minimized ? labels.expand : labels.minimize;
1073
1206
  minimize.title = toggleLabel;
1074
1207
  minimize.setAttribute('aria-label', toggleLabel);
@@ -1091,7 +1224,7 @@ const mountPromptBox = (doc, bridge, options = {})=>{
1091
1224
  pageChipText.textContent = pages?.find(({ entryId })=>entryId === bridge.entryId)?.name ?? labels.pages;
1092
1225
  const loadingForm = form && formLoading && !picking;
1093
1226
  const working = state.prompting || loadingForm;
1094
- const note = state.prompting ? creating ? labels.creating : labels.working : loadingForm ? labels.formLoading : message?.text ?? (site && !creating && !picking && !state.selectedUid ? labels.selectHint : '');
1227
+ const note = state.prompting ? creating ? labels.creating : offerProduct ? labels.addingProductPage : labels.working : loadingForm ? labels.formLoading : message?.text ?? (offerProduct ? labels.productPageHint : site && !creating && !picking && !state.selectedUid ? labels.selectHint : '');
1095
1228
  status.textContent = note;
1096
1229
  status.hidden = !note || minimized;
1097
1230
  status.className = `status${working ? ' working' : ''}${!busy && !loadingForm && message?.error ? ' error' : ''}`;
@@ -1295,12 +1428,35 @@ const mountPromptBox = (doc, bridge, options = {})=>{
1295
1428
  refresh();
1296
1429
  if (!minimized) (form ? minimize : input).focus();
1297
1430
  });
1431
+ // Any other button of the minimized box expands it first (capture, so
1432
+ // its own handler then sees the box open), e.g. the pages or New page.
1433
+ panel.addEventListener('click', (event)=>{
1434
+ if (!minimized) return;
1435
+ const button = event.target.closest('button');
1436
+ if (!button || button === minimize || button === close) return;
1437
+ minimized = false;
1438
+ refresh();
1439
+ }, true);
1298
1440
  save.addEventListener('click', ()=>void store('save'));
1299
1441
  publish.addEventListener('click', ()=>void store('publish'));
1300
1442
  signOut.addEventListener('click', ()=>bridge.signOut());
1301
1443
  pillSignOut.addEventListener('click', ()=>bridge.signOut());
1302
1444
  clearBlock.addEventListener('click', ()=>bridge.select(null));
1303
1445
  newPage.addEventListener('click', ()=>setCreating(true));
1446
+ addProduct.addEventListener('click', async ()=>{
1447
+ message = null;
1448
+ try {
1449
+ // The bridge reloads the page; this shows until it does.
1450
+ await bridge.createProductPage();
1451
+ message = {
1452
+ text: labels.addedProductPage,
1453
+ error: false
1454
+ };
1455
+ } catch (error) {
1456
+ message = failure(error);
1457
+ }
1458
+ refresh();
1459
+ });
1304
1460
  cancelNew.addEventListener('click', ()=>setCreating(false));
1305
1461
  promptMode.addEventListener('click', ()=>setForm(false));
1306
1462
  formMode.addEventListener('click', ()=>setForm(true));
@@ -1372,13 +1528,15 @@ const mountPromptBox = (doc, bridge, options = {})=>{
1372
1528
  // Messages are only exchanged with the URL's origin, and only with the
1373
1529
  // iframe's window:
1374
1530
  // - form → bridge: { source: 'obelism-content-form', type: 'ready' |
1375
- // 'change' | 'error' | 'height', content?, message?, height? } —
1376
- // `change` carries the whole entry content after an edit, `height` the
1377
- // form's own height in px, which the iframe takes (up to 60vh)
1531
+ // 'loaded' | 'change' | 'error' | 'height', loading?, content?, message?,
1532
+ // height? } — `ready` with `loading: true` when the form still loads the
1533
+ // entry after it and says `loaded` once it shows (a CMS from before says
1534
+ // only `ready`), `change` carries the whole entry content after an edit,
1535
+ // `height` the form's own height in px, which the iframe takes (up to 60vh)
1378
1536
  // - bridge → form: { source: 'obelism-content-bridge', type: 'content',
1379
1537
  // entryId, content, locale, selectedUid }
1380
1538
  const FORM_SOURCE = 'obelism-content-form';
1381
- // How long the form may take to say it is ready.
1539
+ // How long the form may take to show.
1382
1540
  const FORM_READY_TIMEOUT_MS = 30_000;
1383
1541
  const isObject = (value)=>!!value && typeof value === 'object' && !Array.isArray(value);
1384
1542
  // Mounts the form at `url` in `container`. The URL is single use: mount a
@@ -1396,6 +1554,7 @@ const mountFormFrame = (win, container, url, title, handlers)=>{
1396
1554
  iframe.title = title;
1397
1555
  iframe.src = url;
1398
1556
  let ready = false;
1557
+ let shown = false;
1399
1558
  let latest = null;
1400
1559
  const post = ()=>{
1401
1560
  if (!ready || !latest) return;
@@ -1406,8 +1565,13 @@ const mountFormFrame = (win, container, url, title, handlers)=>{
1406
1565
  }, origin);
1407
1566
  };
1408
1567
  const timer = setTimeout(()=>{
1409
- if (!ready) handlers.onError('The form did not open');
1568
+ if (!shown) handlers.onError('The form did not open');
1410
1569
  }, FORM_READY_TIMEOUT_MS);
1570
+ const show = ()=>{
1571
+ shown = true;
1572
+ clearTimeout(timer);
1573
+ handlers.onReady();
1574
+ };
1411
1575
  const onMessage = (event)=>{
1412
1576
  if (event.origin !== origin || !iframe.contentWindow || event.source !== iframe.contentWindow) {
1413
1577
  return;
@@ -1417,9 +1581,12 @@ const mountFormFrame = (win, container, url, title, handlers)=>{
1417
1581
  if (data.type === 'ready') {
1418
1582
  // Again after the form reloads itself: it gets the content again.
1419
1583
  ready = true;
1420
- clearTimeout(timer);
1421
- handlers.onReady();
1422
1584
  post();
1585
+ // Until the form loaded the entry, the box's own loading state
1586
+ // stands in for the form's.
1587
+ if (data.loading !== true) show();
1588
+ } else if (data.type === 'loaded') {
1589
+ if (ready) show();
1423
1590
  } else if (data.type === 'change' && isObject(data.content)) {
1424
1591
  handlers.onChange(data.content, typeof data.entryId === 'string' ? data.entryId : undefined);
1425
1592
  } else if (data.type === 'height') {
@@ -1481,7 +1648,7 @@ const FORM_BURST_MS = 1000;
1481
1648
  // How long the page waits for the form's edits to settle before it shows
1482
1649
  // them.
1483
1650
  const FORM_RENDER_MS = 300;
1484
- const createSiteBridge = (win, basePath, { pagePath = (path)=>`/${path}` } = {})=>{
1651
+ const createSiteBridge = (win, basePath, { pagePath = (path)=>`/${path}`, productPath: ownProductPath } = {})=>{
1485
1652
  const doc = win.document;
1486
1653
  const emitter = createEmitter();
1487
1654
  const cleanup = [];
@@ -1491,6 +1658,7 @@ const createSiteBridge = (win, basePath, { pagePath = (path)=>`/${path}` } = {})
1491
1658
  // Entries changed here since the last publish.
1492
1659
  const touched = new Set();
1493
1660
  let session = null;
1661
+ const productPath = (handle, locale)=>ownProductPath ? ownProductPath(handle, locale) : (session?.productPath ?? '/products/{handle}').replaceAll('{handle}', encodeURIComponent(handle));
1494
1662
  // The open form's, if any; and when the form last changed content, for
1495
1663
  // undo steps (-Infinity: the next edit starts a new step).
1496
1664
  let closeForm = null;
@@ -1535,6 +1703,16 @@ const createSiteBridge = (win, basePath, { pagePath = (path)=>`/${path}` } = {})
1535
1703
  return data;
1536
1704
  };
1537
1705
  const pageEntryId = ()=>doc.querySelector(`[${ENTRY_ATTRIBUTE}]`)?.getAttribute(ENTRY_ATTRIBUTE) ?? null;
1706
+ // The product the page shows (editableProduct), if it marks one.
1707
+ const pageProduct = ()=>{
1708
+ const element = doc.querySelector(`[${PRODUCT_ATTRIBUTE}]`);
1709
+ const id = element?.getAttribute(PRODUCT_ATTRIBUTE);
1710
+ const handle = element?.getAttribute(PRODUCT_HANDLE_ATTRIBUTE);
1711
+ return id && handle ? {
1712
+ id,
1713
+ handle
1714
+ } : null;
1715
+ };
1538
1716
  const entryFor = (uid)=>{
1539
1717
  const block = uid ? doc.querySelector(blockSelector(uid)) : null;
1540
1718
  return block?.closest(`[${ENTRY_ATTRIBUTE}]`)?.getAttribute(ENTRY_ATTRIBUTE) ?? pageEntryId();
@@ -1642,6 +1820,9 @@ const createSiteBridge = (win, basePath, { pagePath = (path)=>`/${path}` } = {})
1642
1820
  get entryId () {
1643
1821
  return pageEntryId();
1644
1822
  },
1823
+ get product () {
1824
+ return pageProduct();
1825
+ },
1645
1826
  environment: null,
1646
1827
  on: emitter.on,
1647
1828
  getState: ()=>state,
@@ -1711,6 +1892,34 @@ const createSiteBridge = (win, basePath, { pagePath = (path)=>`/${path}` } = {})
1711
1892
  });
1712
1893
  }
1713
1894
  },
1895
+ async createProductPage (product = pageProduct() ?? undefined) {
1896
+ if (!product) {
1897
+ throw new Error('Nothing on this page is marked as a product: add editableProduct(product)');
1898
+ }
1899
+ if (!has('create')) throw new Error('You may not add pages here');
1900
+ if (state.unsaved) {
1901
+ throw new Error('Save or undo your changes before adding a page');
1902
+ }
1903
+ setState({
1904
+ prompting: true
1905
+ });
1906
+ try {
1907
+ const made = await api('product-pages', {
1908
+ product: {
1909
+ id: product.id,
1910
+ handle: product.handle
1911
+ }
1912
+ });
1913
+ // The site's product route finds its page now (drafts, while
1914
+ // editing): show it.
1915
+ win.location.reload();
1916
+ return made;
1917
+ } finally{
1918
+ setState({
1919
+ prompting: false
1920
+ });
1921
+ }
1922
+ },
1714
1923
  async view (options = {}) {
1715
1924
  const { uid, entryId } = target(options.uid);
1716
1925
  const entry = await load(entryId);
@@ -1864,7 +2073,8 @@ const createSiteBridge = (win, basePath, { pagePath = (path)=>`/${path}` } = {})
1864
2073
  contentType: page.contentType,
1865
2074
  status: page.status,
1866
2075
  path,
1867
- url: pagePath(path, code)
2076
+ url: page.product ? productPath(page.product.handle, code) : pagePath(path, code),
2077
+ product: page.product ?? null
1868
2078
  }
1869
2079
  ];
1870
2080
  });
@@ -2009,7 +2219,7 @@ const peekContentBridge = ()=>typeof window === 'undefined' ? null : window[KEY]
2009
2219
  // if (bridge.isEditing) bridge.on('update', ({ entry }) => render(entry))
2010
2220
  //
2011
2221
  // The first call's options win: there is one bridge per page.
2012
- const getContentBridge = ({ url, prompt = true, pagePath } = {})=>{
2222
+ const getContentBridge = ({ url, prompt = true, pagePath, productPath } = {})=>{
2013
2223
  if (typeof window === 'undefined') return SERVER_BRIDGE;
2014
2224
  const win = window;
2015
2225
  if (win[KEY]) return win[KEY];
@@ -2022,7 +2232,8 @@ const getContentBridge = ({ url, prompt = true, pagePath } = {})=>{
2022
2232
  // own `/cms/login` (@obelism/content/site).
2023
2233
  const sitePath = bridge.isEditing || win.parent !== win ? null : readSiteEditing(win.document);
2024
2234
  if (sitePath) bridge = createSiteBridge(win, sitePath, {
2025
- pagePath
2235
+ pagePath,
2236
+ productPath
2026
2237
  });
2027
2238
  if (bridge.isEditing && prompt) {
2028
2239
  const unmount = mountPromptBox(win.document, bridge, prompt === true ? {} : prompt);
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as RichTextFormat, E as Entry, a as RedirectList, b as Redirect, c as EntryList, M as ModelFile, A as Asset, B as Block, I as ImproveBlock, L as Link, S as ShopifyItem, d as EntryStub, e as RichTextNode } from './types-e5nvgudo.js';
2
- export { C as ContentTypeDefinition, f as ContentTypeKind, g as Environment, F as FieldDefinition, h as FieldType, i as LinkTarget, j as RichTextDocument, k as RichTextMark } from './types-e5nvgudo.js';
1
+ import { R as RichTextFormat, E as Entry, a as RedirectList, b as Redirect, c as EntryList, M as ModelFile, A as Asset, B as Block, I as ImproveBlock, S as ShopifyItem, L as LinkTarget, d as Link, e as EntryStub, f as RichTextNode } from './types-mtjco3a5.js';
2
+ export { C as ContentTypeDefinition, g as ContentTypeKind, h as Environment, F as FieldDefinition, i as FieldType, j as RichTextDocument, k as RichTextMark } from './types-mtjco3a5.js';
3
3
 
4
4
  type ContentClientOptions = {
5
5
  token: string;
@@ -60,6 +60,8 @@ declare const DEFAULT_URL = "https://content.obelism.studio";
60
60
 
61
61
  declare const UID_ATTRIBUTE = "data-cms-uid";
62
62
  declare const ENTRY_ATTRIBUTE = "data-cms-entry";
63
+ declare const PRODUCT_ATTRIBUTE = "data-cms-product";
64
+ declare const PRODUCT_HANDLE_ATTRIBUTE = "data-cms-product-handle";
63
65
  type Editable = {
64
66
  'data-cms-uid'?: string;
65
67
  'data-cms-entry'?: string;
@@ -70,6 +72,14 @@ declare const editable: (target: {
70
72
  id: string;
71
73
  fields: unknown;
72
74
  } | null | undefined) => Editable;
75
+ type EditableProduct = {
76
+ 'data-cms-product'?: string;
77
+ 'data-cms-product-handle'?: string;
78
+ };
79
+ declare const editableProduct: (product: {
80
+ id: string;
81
+ handle: string;
82
+ } | null | undefined) => EditableProduct;
73
83
 
74
84
  declare class ContentApiError extends Error {
75
85
  readonly status: number;
@@ -98,8 +108,9 @@ declare const pickImproveVariant: <TBlock extends Block>(block: ImproveBlock<TBl
98
108
 
99
109
  type LinkHrefOptions = {
100
110
  url?: ((path: string) => string) | undefined;
111
+ product?: ((product: ShopifyItem, page: LinkTarget | null) => string) | undefined;
101
112
  };
102
- declare const linkHref: (link: Link | null | undefined, { url }?: LinkHrefOptions) => string | null;
113
+ declare const linkHref: (link: Link | null | undefined, { url, product, }?: LinkHrefOptions) => string | null;
103
114
 
104
115
  declare const PREVIEW_PARAMS: {
105
116
  readonly preview: "cms_preview";
@@ -184,6 +195,6 @@ type SitemapOptions = LocalizedUrlOptions & {
184
195
  };
185
196
  declare const sitemapXml: (entries: Iterable<Page>, { exclude, ...options }: SitemapOptions) => string;
186
197
 
187
- export { Asset, Block, ContentApiError, ContentClient, DEFAULT_URL, ENTRY_ATTRIBUTE, Entry, EntryList, EntryStub, IMPROVE_BLOCK, ImproveBlock, Link, ModelFile, PREVIEW_PARAMS, Redirect, RedirectList, RichTextFormat, RichTextNode, SAFE_HREF, SHOPIFY_STOREFRONT_API_VERSION, ShopifyItem, ShopifyStorefrontError, UID_ATTRIBUTE, alternateLinks, editable, escapeHtml, fetchShopifyCollection, fetchShopifyProducts, headingLevel, imageSrcSet, imageUrl, isImproveBlock, linkHref, pickImproveVariant, readPreviewParams, richTextToHtml, richTextToPlainText, sitemapXml };
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 };
188
199
  export type { AlternateLink, ContentClientOptions, EntriesQuery, ImageTransform, LinkHrefOptions, LocalizedUrlOptions, PageLookup, PreviewParams, RedirectsQuery, RequestOptions, RichTextHtmlOptions, ShopifyCollection, ShopifyImage, ShopifyMoney, ShopifyProduct, ShopifyStorefrontOptions, SitemapOptions };
189
200
  //# 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
@@ -1,5 +1,5 @@
1
- import { R as RichTextFormat, E as Entry, a as RedirectList, b as Redirect, c as EntryList, M as ModelFile, A as Asset, B as Block, I as ImproveBlock, L as Link, S as ShopifyItem, d as EntryStub, e as RichTextNode } from './types-e5nvgudo.js';
2
- export { C as ContentTypeDefinition, f as ContentTypeKind, g as Environment, F as FieldDefinition, h as FieldType, i as LinkTarget, j as RichTextDocument, k as RichTextMark } from './types-e5nvgudo.js';
1
+ import { R as RichTextFormat, E as Entry, a as RedirectList, b as Redirect, c as EntryList, M as ModelFile, A as Asset, B as Block, I as ImproveBlock, S as ShopifyItem, L as LinkTarget, d as Link, e as EntryStub, f as RichTextNode } from './types-mtjco3a5.js';
2
+ export { C as ContentTypeDefinition, g as ContentTypeKind, h as Environment, F as FieldDefinition, i as FieldType, j as RichTextDocument, k as RichTextMark } from './types-mtjco3a5.js';
3
3
 
4
4
  type ContentClientOptions = {
5
5
  token: string;
@@ -60,6 +60,8 @@ declare const DEFAULT_URL = "https://content.obelism.studio";
60
60
 
61
61
  declare const UID_ATTRIBUTE = "data-cms-uid";
62
62
  declare const ENTRY_ATTRIBUTE = "data-cms-entry";
63
+ declare const PRODUCT_ATTRIBUTE = "data-cms-product";
64
+ declare const PRODUCT_HANDLE_ATTRIBUTE = "data-cms-product-handle";
63
65
  type Editable = {
64
66
  'data-cms-uid'?: string;
65
67
  'data-cms-entry'?: string;
@@ -70,6 +72,14 @@ declare const editable: (target: {
70
72
  id: string;
71
73
  fields: unknown;
72
74
  } | null | undefined) => Editable;
75
+ type EditableProduct = {
76
+ 'data-cms-product'?: string;
77
+ 'data-cms-product-handle'?: string;
78
+ };
79
+ declare const editableProduct: (product: {
80
+ id: string;
81
+ handle: string;
82
+ } | null | undefined) => EditableProduct;
73
83
 
74
84
  declare class ContentApiError extends Error {
75
85
  readonly status: number;
@@ -98,8 +108,9 @@ declare const pickImproveVariant: <TBlock extends Block>(block: ImproveBlock<TBl
98
108
 
99
109
  type LinkHrefOptions = {
100
110
  url?: ((path: string) => string) | undefined;
111
+ product?: ((product: ShopifyItem, page: LinkTarget | null) => string) | undefined;
101
112
  };
102
- declare const linkHref: (link: Link | null | undefined, { url }?: LinkHrefOptions) => string | null;
113
+ declare const linkHref: (link: Link | null | undefined, { url, product, }?: LinkHrefOptions) => string | null;
103
114
 
104
115
  declare const PREVIEW_PARAMS: {
105
116
  readonly preview: "cms_preview";
@@ -184,6 +195,6 @@ type SitemapOptions = LocalizedUrlOptions & {
184
195
  };
185
196
  declare const sitemapXml: (entries: Iterable<Page>, { exclude, ...options }: SitemapOptions) => string;
186
197
 
187
- export { Asset, Block, ContentApiError, ContentClient, DEFAULT_URL, ENTRY_ATTRIBUTE, Entry, EntryList, EntryStub, IMPROVE_BLOCK, ImproveBlock, Link, ModelFile, PREVIEW_PARAMS, Redirect, RedirectList, RichTextFormat, RichTextNode, SAFE_HREF, SHOPIFY_STOREFRONT_API_VERSION, ShopifyItem, ShopifyStorefrontError, UID_ATTRIBUTE, alternateLinks, editable, escapeHtml, fetchShopifyCollection, fetchShopifyProducts, headingLevel, imageSrcSet, imageUrl, isImproveBlock, linkHref, pickImproveVariant, readPreviewParams, richTextToHtml, richTextToPlainText, sitemapXml };
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 };
188
199
  export type { AlternateLink, ContentClientOptions, EntriesQuery, ImageTransform, LinkHrefOptions, LocalizedUrlOptions, PageLookup, PreviewParams, RedirectsQuery, RequestOptions, RichTextHtmlOptions, ShopifyCollection, ShopifyImage, ShopifyMoney, ShopifyProduct, ShopifyStorefrontOptions, SitemapOptions };
189
200
  //# 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":[]}