@obelism/content 0.1.1 → 0.3.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
@@ -62,6 +62,7 @@ const onlyOnSite = ()=>Promise.reject(new Error('Save and publish in the visual
62
62
  // What every bridge without a feature does for it.
63
63
  const INERT_METHODS = {
64
64
  prompt: inertPrompt,
65
+ createPage: ()=>Promise.reject(new Error('New pages are made on the site itself')),
65
66
  undo: ()=>{},
66
67
  save: onlyOnSite,
67
68
  publish: onlyOnSite,
@@ -304,11 +305,19 @@ const DEFAULT_PROMPT_LABELS = {
304
305
  unsaved: 'Unsaved changes',
305
306
  signOut: 'Exit editing',
306
307
  viewingDrafts: 'Viewing drafts',
307
- selectHint: 'Click a block to change only that block'
308
+ selectHint: 'Click a block to change only that block',
309
+ newPage: 'New page',
310
+ newPageCancel: 'Edit this page instead',
311
+ newPagePlaceholder: 'Describe the new page, e.g. “a pricing page for agencies”',
312
+ path: 'Path',
313
+ pathPlaceholder: '/pricing',
314
+ creating: 'Writing the page…',
315
+ created: 'Opening the new page…'
308
316
  };
309
317
  // Icons (Lucide), drawn in the text colour.
310
318
  const ICONS = {
311
319
  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"/>',
320
+ newPage: '<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"/><path d="M9 15h6"/><path d="M12 18v-6"/>',
312
321
  block: '<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M7 8h10"/><path d="M7 12h6"/>',
313
322
  undo: '<path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H11"/>',
314
323
  close: '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',
@@ -413,6 +422,20 @@ textarea {
413
422
  font-size: 16px; line-height: 1.6; color: rgb(var(--theme-foreground-rgb));
414
423
  }
415
424
  textarea::placeholder { color: rgb(var(--theme-foreground-muted-rgb)); }
425
+ /* New page: where it goes, above what it is for. */
426
+ .path {
427
+ display: flex; align-items: center; gap: 10px; padding: 16px 22px 0;
428
+ color: rgb(var(--theme-foreground-muted-rgb));
429
+ }
430
+ .path input {
431
+ flex: 1; min-width: 0; height: 34px; padding: 0 14px; outline: 0;
432
+ border: 1px solid rgb(var(--theme-border-rgb) / .25); border-radius: var(--border-radius-pill);
433
+ background: rgb(var(--theme-background-actions-rgb) / .45); color: rgb(var(--theme-foreground-rgb));
434
+ transition: border-color .25s var(--ease-roll);
435
+ }
436
+ .path input:focus-visible { border-color: rgb(var(--theme-accent-rgb)); }
437
+ .path input::placeholder { color: rgb(var(--theme-foreground-muted-rgb)); }
438
+ .path input:disabled { opacity: .5; }
416
439
  textarea:disabled { opacity: .5; }
417
440
  .toolbar { display: flex; align-items: center; gap: 6px; padding: 8px 12px 12px; }
418
441
  .spacer { flex: 1; }
@@ -490,12 +513,13 @@ const mountPromptBox = (doc, bridge, options = {})=>{
490
513
  mode: 'open'
491
514
  });
492
515
  const site = bridge.mode === 'site';
516
+ const pillText = element(doc, 'span', {
517
+ textContent: labels.open
518
+ });
493
519
  const pill = element(doc, 'button', {
494
520
  className: 'pill',
495
521
  type: 'button'
496
- }, element(doc, 'span', {
497
- textContent: labels.open
498
- }), // The Obelism mark.
522
+ }, pillText, // The Obelism mark.
499
523
  element(doc, 'span', {
500
524
  className: 'mark',
501
525
  textContent: '⁜'
@@ -538,6 +562,34 @@ const mountPromptBox = (doc, bridge, options = {})=>{
538
562
  className: 'action chip',
539
563
  type: 'button'
540
564
  }, icon(doc, 'undo'), labels.undo);
565
+ // New page (on the site, for people who may add pages): the scope becomes
566
+ // the new page, with a way back, and a path field shows above the prompt.
567
+ const newPage = element(doc, 'button', {
568
+ className: 'action chip',
569
+ type: 'button'
570
+ }, icon(doc, 'newPage'), labels.newPage);
571
+ const cancelNew = element(doc, 'button', {
572
+ type: 'button',
573
+ title: labels.newPageCancel
574
+ });
575
+ cancelNew.setAttribute('aria-label', labels.newPageCancel);
576
+ cancelNew.appendChild(icon(doc, 'close'));
577
+ const newScope = element(doc, 'span', {
578
+ className: 'chip scope'
579
+ }, icon(doc, 'newPage'), element(doc, 'span', {
580
+ textContent: labels.newPage
581
+ }), cancelNew);
582
+ const pathInput = element(doc, 'input', {
583
+ type: 'text',
584
+ placeholder: labels.pathPlaceholder,
585
+ autocomplete: 'off',
586
+ spellcheck: false
587
+ });
588
+ const pathRow = element(doc, 'label', {
589
+ className: 'path'
590
+ }, element(doc, 'span', {
591
+ textContent: labels.path
592
+ }), pathInput);
541
593
  const close = element(doc, 'button', {
542
594
  className: 'close icon-button',
543
595
  type: 'button',
@@ -581,9 +633,9 @@ const mountPromptBox = (doc, bridge, options = {})=>{
581
633
  }), signOut, save, publish);
582
634
  const panel = element(doc, 'form', {
583
635
  className: 'panel'
584
- }, status, input, element(doc, 'div', {
636
+ }, status, pathRow, input, element(doc, 'div', {
585
637
  className: 'toolbar'
586
- }, scope, undo, element(doc, 'span', {
638
+ }, scope, newScope, undo, newPage, element(doc, 'span', {
587
639
  className: 'spacer'
588
640
  }), close, element(doc, 'span', {
589
641
  className: 'divider'
@@ -597,6 +649,7 @@ const mountPromptBox = (doc, bridge, options = {})=>{
597
649
  textContent: CSS
598
650
  }), root);
599
651
  let open = false;
652
+ let creating = false;
600
653
  let message = null;
601
654
  const render = (state)=>{
602
655
  const available = state.features.includes('prompt');
@@ -606,24 +659,40 @@ const mountPromptBox = (doc, bridge, options = {})=>{
606
659
  pill.hidden = open || !available;
607
660
  viewing.hidden = available || !site;
608
661
  panel.hidden = !open || !available;
662
+ const canCreate = site && state.features.includes('create');
663
+ // A page of the site that isn't an entry (no `editable(entry)`, like a
664
+ // hand-written /about) has nothing to edit: only New page.
665
+ const pageless = site && bridge.entryId === null;
666
+ const onlyNew = pageless && canCreate;
667
+ if (!canCreate) creating = false;
668
+ if (onlyNew) creating = true;
669
+ pillText.textContent = onlyNew ? labels.newPage : labels.open;
670
+ cancelNew.hidden = onlyNew;
671
+ scope.hidden = creating;
672
+ newScope.hidden = !creating;
673
+ newPage.hidden = creating || !canCreate;
674
+ newPage.disabled = busy;
675
+ pathRow.hidden = !creating;
676
+ pathInput.disabled = busy;
677
+ input.placeholder = creating ? labels.newPagePlaceholder : labels.placeholder;
609
678
  scopeText.textContent = state.selectedUid ? labels.scopeBlock : labels.scopeEntry;
610
679
  clearBlock.hidden = !state.selectedUid;
611
680
  pageIcon.hidden = !!state.selectedUid;
612
681
  blockIcon.hidden = !state.selectedUid;
613
- undo.hidden = !state.canUndo;
682
+ undo.hidden = creating || !state.canUndo;
614
683
  undo.disabled = busy;
615
- send.disabled = busy || input.value.trim() === '';
684
+ send.disabled = busy || input.value.trim() === '' || creating && pathInput.value.trim() === '';
616
685
  input.disabled = busy;
617
- save.hidden = !state.features.includes('save');
686
+ save.hidden = !state.features.includes('save') || pageless;
618
687
  save.disabled = busy || !state.unsaved;
619
688
  save.textContent = state.saving ? labels.saving : labels.save;
620
- publish.hidden = !state.features.includes('publish');
689
+ publish.hidden = !state.features.includes('publish') || pageless;
621
690
  publish.disabled = busy;
622
691
  publish.textContent = state.publishing ? labels.publishing : labels.publish;
623
692
  signOut.hidden = !site;
624
693
  footer.hidden = !site;
625
694
  unsaved.hidden = !state.unsaved;
626
- const text = state.prompting ? labels.working : message?.text ?? (site && !state.selectedUid ? labels.selectHint : '');
695
+ const text = state.prompting ? creating ? labels.creating : labels.working : message?.text ?? (site && !creating && !state.selectedUid ? labels.selectHint : '');
627
696
  status.textContent = text;
628
697
  status.hidden = !text;
629
698
  status.className = `status${state.prompting ? ' working' : ''}${!busy && message?.error ? ' error' : ''}`;
@@ -638,11 +707,22 @@ const mountPromptBox = (doc, bridge, options = {})=>{
638
707
  if (!prompt || bridge.getState().prompting) return;
639
708
  message = null;
640
709
  try {
641
- const { summary } = await bridge.prompt(prompt);
642
- message = {
643
- text: summary,
644
- error: false
645
- };
710
+ if (creating) {
711
+ const path = pathInput.value.trim();
712
+ if (!path) return pathInput.focus();
713
+ // The bridge opens the new page; this shows until it does.
714
+ await bridge.createPage(path, prompt);
715
+ message = {
716
+ text: labels.created,
717
+ error: false
718
+ };
719
+ } else {
720
+ const { summary } = await bridge.prompt(prompt);
721
+ message = {
722
+ text: summary,
723
+ error: false
724
+ };
725
+ }
646
726
  input.value = '';
647
727
  grow();
648
728
  } catch (error) {
@@ -656,8 +736,16 @@ const mountPromptBox = (doc, bridge, options = {})=>{
656
736
  };
657
737
  const setOpen = (next)=>{
658
738
  open = next;
659
- if (site) bridge.setSelecting(open);
660
739
  refresh();
740
+ if (site) bridge.setSelecting(open && !creating);
741
+ };
742
+ const setCreating = (next)=>{
743
+ creating = next;
744
+ message = null;
745
+ bridge.setSelecting(open && !creating);
746
+ if (creating) bridge.select(null);
747
+ refresh();
748
+ (creating ? pathInput : input).focus();
661
749
  };
662
750
  const store = async (action)=>{
663
751
  message = null;
@@ -677,7 +765,7 @@ const mountPromptBox = (doc, bridge, options = {})=>{
677
765
  };
678
766
  pill.addEventListener('click', ()=>{
679
767
  setOpen(true);
680
- input.focus();
768
+ (creating ? pathInput : input).focus();
681
769
  });
682
770
  close.addEventListener('click', ()=>setOpen(false));
683
771
  save.addEventListener('click', ()=>void store('save'));
@@ -685,6 +773,17 @@ const mountPromptBox = (doc, bridge, options = {})=>{
685
773
  signOut.addEventListener('click', ()=>bridge.signOut());
686
774
  pillSignOut.addEventListener('click', ()=>bridge.signOut());
687
775
  clearBlock.addEventListener('click', ()=>bridge.select(null));
776
+ newPage.addEventListener('click', ()=>setCreating(true));
777
+ cancelNew.addEventListener('click', ()=>setCreating(false));
778
+ pathInput.addEventListener('input', refresh);
779
+ pathInput.addEventListener('keydown', (event)=>{
780
+ if (event.key === 'Enter' && !event.isComposing) {
781
+ event.preventDefault();
782
+ input.focus();
783
+ } else if (event.key === 'Escape') {
784
+ setOpen(false);
785
+ }
786
+ });
688
787
  undo.addEventListener('click', ()=>{
689
788
  message = null;
690
789
  bridge.undo();
@@ -738,7 +837,7 @@ const readSiteEditing = (doc)=>{
738
837
  return null;
739
838
  };
740
839
  const same = (a, b)=>JSON.stringify(a) === JSON.stringify(b);
741
- const createSiteBridge = (win, basePath)=>{
840
+ const createSiteBridge = (win, basePath, { pagePath = (path)=>`/${path}` } = {})=>{
742
841
  const doc = win.document;
743
842
  const emitter = createEmitter();
744
843
  const cleanup = [];
@@ -901,6 +1000,39 @@ const createSiteBridge = (win, basePath)=>{
901
1000
  });
902
1001
  }
903
1002
  },
1003
+ async createPage (path, prompt) {
1004
+ const text = prompt.trim();
1005
+ if (!text) throw new Error('Say what the page is for');
1006
+ if (!path.trim()) throw new Error('Say where the page goes, like /pricing');
1007
+ if (!has('create')) throw new Error('You may not add pages here');
1008
+ // Opening the new page would lose them.
1009
+ if (state.unsaved) {
1010
+ throw new Error('Save or undo your changes before adding a page');
1011
+ }
1012
+ setState({
1013
+ prompting: true
1014
+ });
1015
+ try {
1016
+ const created = await api('entries', {
1017
+ path,
1018
+ prompt: text,
1019
+ locale: locale(),
1020
+ from: pageEntryId()
1021
+ });
1022
+ const url = pagePath(created.path);
1023
+ win.location.assign(url);
1024
+ return {
1025
+ summary: created.summary,
1026
+ entryId: created.entryId,
1027
+ path: created.path,
1028
+ url
1029
+ };
1030
+ } finally{
1031
+ setState({
1032
+ prompting: false
1033
+ });
1034
+ }
1035
+ },
904
1036
  undo () {
905
1037
  const last = history.pop();
906
1038
  const entry = last && working.get(last.entryId);
@@ -1027,7 +1159,7 @@ const peekContentBridge = ()=>typeof window === 'undefined' ? null : window[KEY]
1027
1159
  // if (bridge.isEditing) bridge.on('update', ({ entry }) => render(entry))
1028
1160
  //
1029
1161
  // The first call's options win: there is one bridge per page.
1030
- const getContentBridge = ({ url, prompt = true } = {})=>{
1162
+ const getContentBridge = ({ url, prompt = true, pagePath } = {})=>{
1031
1163
  if (typeof window === 'undefined') return SERVER_BRIDGE;
1032
1164
  const win = window;
1033
1165
  if (win[KEY]) return win[KEY];
@@ -1039,7 +1171,9 @@ const getContentBridge = ({ url, prompt = true } = {})=>{
1039
1171
  // Not in the visual editor, but signed in to edit through the site's
1040
1172
  // own `/cms/login` (@obelism/content/site).
1041
1173
  const sitePath = bridge.isEditing || win.parent !== win ? null : readSiteEditing(win.document);
1042
- if (sitePath) bridge = createSiteBridge(win, sitePath);
1174
+ if (sitePath) bridge = createSiteBridge(win, sitePath, {
1175
+ pagePath
1176
+ });
1043
1177
  if (bridge.isEditing && prompt) {
1044
1178
  const unmount = mountPromptBox(win.document, bridge, prompt === true ? {} : prompt);
1045
1179
  const destroy = bridge.destroy;
@@ -1053,4 +1187,4 @@ const getContentBridge = ({ url, prompt = true } = {})=>{
1053
1187
  return bridge;
1054
1188
  };
1055
1189
 
1056
- export { DEFAULT_PROMPT_LABELS, INITIAL_STATE, getContentBridge, peekContentBridge };
1190
+ export { DEFAULT_PROMPT_LABELS, INITIAL_STATE, getContentBridge, mountPromptBox, peekContentBridge };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as RichTextFormat, E as Entry, a as EntryList, M as ModelFile, A as Asset, b as EntryStub, c as RichTextNode } from './types-pa96z6po.js';
2
- export { B as Block, C as ContentTypeDefinition, d as ContentTypeKind, e as Environment, F as FieldDefinition, f as FieldType, g as RichTextDocument, h as RichTextMark } from './types-pa96z6po.js';
1
+ import { R as RichTextFormat, E as Entry, a as EntryList, M as ModelFile, A as Asset, B as Block, I as ImproveBlock, b as EntryStub, c as RichTextNode } from './types-nyvor49r.js';
2
+ export { C as ContentTypeDefinition, d as ContentTypeKind, e as Environment, F as FieldDefinition, f as FieldType, g as RichTextDocument, h as RichTextMark } from './types-nyvor49r.js';
3
3
 
4
4
  type ContentClientOptions = {
5
5
  token: string;
@@ -66,6 +66,10 @@ type ImageTransform = {
66
66
  declare const imageUrl: (asset: Pick<Asset, "url"> | string, transform?: ImageTransform) => string;
67
67
  declare const imageSrcSet: (asset: Pick<Asset, "url"> | string, widths: number[], transform?: Omit<ImageTransform, "w">) => string;
68
68
 
69
+ declare const IMPROVE_BLOCK = "_improve";
70
+ declare const isImproveBlock: (block: Block) => block is ImproveBlock;
71
+ declare const pickImproveVariant: <TBlock extends Block>(block: ImproveBlock<TBlock>, option: string | null | undefined, fallback?: string) => TBlock[];
72
+
69
73
  declare const PREVIEW_PARAMS: {
70
74
  readonly preview: "cms_preview";
71
75
  readonly entry: "cms_entry";
@@ -88,6 +92,6 @@ declare const richTextToHtml: (doc: RichTextNode | null | undefined, { renderEnt
88
92
  declare const headingLevel: (node: RichTextNode) => 1 | 2 | 3 | 4 | 5 | 6;
89
93
  declare const richTextToPlainText: (doc: RichTextNode | null | undefined) => string;
90
94
 
91
- export { Asset, ContentApiError, ContentClient, DEFAULT_URL, ENTRY_ATTRIBUTE, Entry, EntryList, EntryStub, ModelFile, PREVIEW_PARAMS, RichTextFormat, RichTextNode, SAFE_HREF, UID_ATTRIBUTE, editable, escapeHtml, headingLevel, imageSrcSet, imageUrl, readPreviewParams, richTextToHtml, richTextToPlainText };
95
+ export { Asset, Block, ContentApiError, ContentClient, DEFAULT_URL, ENTRY_ATTRIBUTE, Entry, EntryList, EntryStub, IMPROVE_BLOCK, ImproveBlock, ModelFile, PREVIEW_PARAMS, RichTextFormat, RichTextNode, SAFE_HREF, UID_ATTRIBUTE, editable, escapeHtml, headingLevel, imageSrcSet, imageUrl, isImproveBlock, pickImproveVariant, readPreviewParams, richTextToHtml, richTextToPlainText };
92
96
  export type { ContentClientOptions, EntriesQuery, ImageTransform, PreviewParams, RequestOptions, RichTextHtmlOptions };
93
97
  //# 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/preview.ts","../src/richText.ts"],"mappings":";;;AACA,KAAK,oBAAoB;AACzB;AACA;AACA;AACA;AACA,eAAe,cAAc;AAC7B;AACA,WAAW,WAAW;AACtB,mBAAmB,KAAK;AACxB;AACA,KAAK,cAAc;AACnB;AACA;AACA,eAAe,cAAc;AAC7B,WAAW,WAAW;AACtB;AACA,KAAK,YAAY,GAAG,cAAc;AAClC;AACA;AACA;AACA;AACA;AACA,cAAc,aAAa;AAC3B;AACA;AACA,2FAA2F,oBAAoB;AAC/G,6BAA6B,KAAK,GAAG,KAAK,0BAA0B,cAAc,KAAK,OAAO;AAC9F,8BAA8B,KAAK,GAAG,KAAK,wBAAwB,cAAc,KAAK,OAAO;AAC7F,gCAAgC,KAAK,GAAG,KAAK,yDAAyD,YAAY,KAAK,OAAO,CAAC,SAAS;AACxI,iCAAiC,KAAK,GAAG,KAAK,UAAU,IAAI,CAAC,YAAY,YAAY,cAAc;AACnG,gCAAgC,IAAI,CAAC,cAAc,cAAc,OAAO,CAAC,SAAS;AAClF;;AChCA,cAAc,WAAW;;ACAzB,cAAc,aAAa;AAC3B,cAAc,eAAe;AAC7B,KAAK,QAAQ;AACb;AACA;AACA;AACA,cAAc,QAAQ;AACtB;AACA;AACA;AACA;AACA,yBAAyB,QAAQ;;ACXjC,cAAc,eAAe,SAAS,KAAK;AAC3C;AACA;AACA;AACA;AACA;;ACJA,KAAK,cAAc;AACnB;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,QAAQ,UAAU,IAAI,CAAC,KAAK,+BAA+B,cAAc;AACvF,cAAc,WAAW,UAAU,IAAI,CAAC,KAAK,iDAAiD,IAAI,CAAC,cAAc;;ACTjH,cAAc,cAAc;AAC5B;AACA;AACA;AACA;AACA,KAAK,aAAa;AAClB;AACA;AACA;AACA,KAAK,iBAAiB,GAAG,GAAG,GAAG,eAAe,YAAY,MAAM;AAChE,cAAc,iBAAiB,UAAU,iBAAiB,KAAK,aAAa;;ACT5E,cAAc,SAAS,EAAE,MAAM;AAC/B,cAAc,UAAU;AACxB,KAAK,mBAAmB;AACxB,0BAA0B,KAAK,GAAG,SAAS,QAAQ,YAAY;AAC/D,0BAA0B,KAAK,QAAQ,YAAY;AACnD;AACA,cAAc,cAAc,QAAQ,YAAY,oDAAoD,mBAAmB;AACvH,cAAc,YAAY,SAAS,YAAY;AAC/C,cAAc,mBAAmB,QAAQ,YAAY;;","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/preview.ts","../src/richText.ts"],"mappings":";;;AACA,KAAK,oBAAoB;AACzB;AACA;AACA;AACA;AACA,eAAe,cAAc;AAC7B;AACA,WAAW,WAAW;AACtB,mBAAmB,KAAK;AACxB;AACA,KAAK,cAAc;AACnB;AACA;AACA,eAAe,cAAc;AAC7B,WAAW,WAAW;AACtB;AACA,KAAK,YAAY,GAAG,cAAc;AAClC;AACA;AACA;AACA;AACA;AACA,cAAc,aAAa;AAC3B;AACA;AACA,2FAA2F,oBAAoB;AAC/G,6BAA6B,KAAK,GAAG,KAAK,0BAA0B,cAAc,KAAK,OAAO;AAC9F,8BAA8B,KAAK,GAAG,KAAK,wBAAwB,cAAc,KAAK,OAAO;AAC7F,gCAAgC,KAAK,GAAG,KAAK,yDAAyD,YAAY,KAAK,OAAO,CAAC,SAAS;AACxI,iCAAiC,KAAK,GAAG,KAAK,UAAU,IAAI,CAAC,YAAY,YAAY,cAAc;AACnG,gCAAgC,IAAI,CAAC,cAAc,cAAc,OAAO,CAAC,SAAS;AAClF;;AChCA,cAAc,WAAW;;ACAzB,cAAc,aAAa;AAC3B,cAAc,eAAe;AAC7B,KAAK,QAAQ;AACb;AACA;AACA;AACA,cAAc,QAAQ;AACtB;AACA;AACA;AACA;AACA,yBAAyB,QAAQ;;ACXjC,cAAc,eAAe,SAAS,KAAK;AAC3C;AACA;AACA;AACA;AACA;;ACJA,KAAK,cAAc;AACnB;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,QAAQ,UAAU,IAAI,CAAC,KAAK,+BAA+B,cAAc;AACvF,cAAc,WAAW,UAAU,IAAI,CAAC,KAAK,iDAAiD,IAAI,CAAC,cAAc;;ACRjH,cAAc,aAAa;AAC3B,cAAc,cAAc,UAAU,KAAK,cAAc,YAAY;AACrE,cAAc,kBAAkB,kBAAkB,KAAK,SAAS,YAAY;;ACH5E,cAAc,cAAc;AAC5B;AACA;AACA;AACA;AACA,KAAK,aAAa;AAClB;AACA;AACA;AACA,KAAK,iBAAiB,GAAG,GAAG,GAAG,eAAe,YAAY,MAAM;AAChE,cAAc,iBAAiB,UAAU,iBAAiB,KAAK,aAAa;;ACT5E,cAAc,SAAS,EAAE,MAAM;AAC/B,cAAc,UAAU;AACxB,KAAK,mBAAmB;AACxB,0BAA0B,KAAK,GAAG,SAAS,QAAQ,YAAY;AAC/D,0BAA0B,KAAK,QAAQ,YAAY;AACnD;AACA,cAAc,cAAc,QAAQ,YAAY,oDAAoD,mBAAmB;AACvH,cAAc,YAAY,SAAS,YAAY;AAC/C,cAAc,mBAAmB,QAAQ,YAAY;;","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as RichTextFormat, E as Entry, a as EntryList, M as ModelFile, A as Asset, b as EntryStub, c as RichTextNode } from './types-pa96z6po.js';
2
- export { B as Block, C as ContentTypeDefinition, d as ContentTypeKind, e as Environment, F as FieldDefinition, f as FieldType, g as RichTextDocument, h as RichTextMark } from './types-pa96z6po.js';
1
+ import { R as RichTextFormat, E as Entry, a as EntryList, M as ModelFile, A as Asset, B as Block, I as ImproveBlock, b as EntryStub, c as RichTextNode } from './types-nyvor49r.js';
2
+ export { C as ContentTypeDefinition, d as ContentTypeKind, e as Environment, F as FieldDefinition, f as FieldType, g as RichTextDocument, h as RichTextMark } from './types-nyvor49r.js';
3
3
 
4
4
  type ContentClientOptions = {
5
5
  token: string;
@@ -66,6 +66,10 @@ type ImageTransform = {
66
66
  declare const imageUrl: (asset: Pick<Asset, "url"> | string, transform?: ImageTransform) => string;
67
67
  declare const imageSrcSet: (asset: Pick<Asset, "url"> | string, widths: number[], transform?: Omit<ImageTransform, "w">) => string;
68
68
 
69
+ declare const IMPROVE_BLOCK = "_improve";
70
+ declare const isImproveBlock: (block: Block) => block is ImproveBlock;
71
+ declare const pickImproveVariant: <TBlock extends Block>(block: ImproveBlock<TBlock>, option: string | null | undefined, fallback?: string) => TBlock[];
72
+
69
73
  declare const PREVIEW_PARAMS: {
70
74
  readonly preview: "cms_preview";
71
75
  readonly entry: "cms_entry";
@@ -88,6 +92,6 @@ declare const richTextToHtml: (doc: RichTextNode | null | undefined, { renderEnt
88
92
  declare const headingLevel: (node: RichTextNode) => 1 | 2 | 3 | 4 | 5 | 6;
89
93
  declare const richTextToPlainText: (doc: RichTextNode | null | undefined) => string;
90
94
 
91
- export { Asset, ContentApiError, ContentClient, DEFAULT_URL, ENTRY_ATTRIBUTE, Entry, EntryList, EntryStub, ModelFile, PREVIEW_PARAMS, RichTextFormat, RichTextNode, SAFE_HREF, UID_ATTRIBUTE, editable, escapeHtml, headingLevel, imageSrcSet, imageUrl, readPreviewParams, richTextToHtml, richTextToPlainText };
95
+ export { Asset, Block, ContentApiError, ContentClient, DEFAULT_URL, ENTRY_ATTRIBUTE, Entry, EntryList, EntryStub, IMPROVE_BLOCK, ImproveBlock, ModelFile, PREVIEW_PARAMS, RichTextFormat, RichTextNode, SAFE_HREF, UID_ATTRIBUTE, editable, escapeHtml, headingLevel, imageSrcSet, imageUrl, isImproveBlock, pickImproveVariant, readPreviewParams, richTextToHtml, richTextToPlainText };
92
96
  export type { ContentClientOptions, EntriesQuery, ImageTransform, PreviewParams, RequestOptions, RichTextHtmlOptions };
93
97
  //# 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/preview.ts","../src/richText.ts"],"mappings":";;;AACA,KAAK,oBAAoB;AACzB;AACA;AACA;AACA;AACA,eAAe,cAAc;AAC7B;AACA,WAAW,WAAW;AACtB,mBAAmB,KAAK;AACxB;AACA,KAAK,cAAc;AACnB;AACA;AACA,eAAe,cAAc;AAC7B,WAAW,WAAW;AACtB;AACA,KAAK,YAAY,GAAG,cAAc;AAClC;AACA;AACA;AACA;AACA;AACA,cAAc,aAAa;AAC3B;AACA;AACA,2FAA2F,oBAAoB;AAC/G,6BAA6B,KAAK,GAAG,KAAK,0BAA0B,cAAc,KAAK,OAAO;AAC9F,8BAA8B,KAAK,GAAG,KAAK,wBAAwB,cAAc,KAAK,OAAO;AAC7F,gCAAgC,KAAK,GAAG,KAAK,yDAAyD,YAAY,KAAK,OAAO,CAAC,SAAS;AACxI,iCAAiC,KAAK,GAAG,KAAK,UAAU,IAAI,CAAC,YAAY,YAAY,cAAc;AACnG,gCAAgC,IAAI,CAAC,cAAc,cAAc,OAAO,CAAC,SAAS;AAClF;;AChCA,cAAc,WAAW;;ACAzB,cAAc,aAAa;AAC3B,cAAc,eAAe;AAC7B,KAAK,QAAQ;AACb;AACA;AACA;AACA,cAAc,QAAQ;AACtB;AACA;AACA;AACA;AACA,yBAAyB,QAAQ;;ACXjC,cAAc,eAAe,SAAS,KAAK;AAC3C;AACA;AACA;AACA;AACA;;ACJA,KAAK,cAAc;AACnB;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,QAAQ,UAAU,IAAI,CAAC,KAAK,+BAA+B,cAAc;AACvF,cAAc,WAAW,UAAU,IAAI,CAAC,KAAK,iDAAiD,IAAI,CAAC,cAAc;;ACTjH,cAAc,cAAc;AAC5B;AACA;AACA;AACA;AACA,KAAK,aAAa;AAClB;AACA;AACA;AACA,KAAK,iBAAiB,GAAG,GAAG,GAAG,eAAe,YAAY,MAAM;AAChE,cAAc,iBAAiB,UAAU,iBAAiB,KAAK,aAAa;;ACT5E,cAAc,SAAS,EAAE,MAAM;AAC/B,cAAc,UAAU;AACxB,KAAK,mBAAmB;AACxB,0BAA0B,KAAK,GAAG,SAAS,QAAQ,YAAY;AAC/D,0BAA0B,KAAK,QAAQ,YAAY;AACnD;AACA,cAAc,cAAc,QAAQ,YAAY,oDAAoD,mBAAmB;AACvH,cAAc,YAAY,SAAS,YAAY;AAC/C,cAAc,mBAAmB,QAAQ,YAAY;;","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/preview.ts","../src/richText.ts"],"mappings":";;;AACA,KAAK,oBAAoB;AACzB;AACA;AACA;AACA;AACA,eAAe,cAAc;AAC7B;AACA,WAAW,WAAW;AACtB,mBAAmB,KAAK;AACxB;AACA,KAAK,cAAc;AACnB;AACA;AACA,eAAe,cAAc;AAC7B,WAAW,WAAW;AACtB;AACA,KAAK,YAAY,GAAG,cAAc;AAClC;AACA;AACA;AACA;AACA;AACA,cAAc,aAAa;AAC3B;AACA;AACA,2FAA2F,oBAAoB;AAC/G,6BAA6B,KAAK,GAAG,KAAK,0BAA0B,cAAc,KAAK,OAAO;AAC9F,8BAA8B,KAAK,GAAG,KAAK,wBAAwB,cAAc,KAAK,OAAO;AAC7F,gCAAgC,KAAK,GAAG,KAAK,yDAAyD,YAAY,KAAK,OAAO,CAAC,SAAS;AACxI,iCAAiC,KAAK,GAAG,KAAK,UAAU,IAAI,CAAC,YAAY,YAAY,cAAc;AACnG,gCAAgC,IAAI,CAAC,cAAc,cAAc,OAAO,CAAC,SAAS;AAClF;;AChCA,cAAc,WAAW;;ACAzB,cAAc,aAAa;AAC3B,cAAc,eAAe;AAC7B,KAAK,QAAQ;AACb;AACA;AACA;AACA,cAAc,QAAQ;AACtB;AACA;AACA;AACA;AACA,yBAAyB,QAAQ;;ACXjC,cAAc,eAAe,SAAS,KAAK;AAC3C;AACA;AACA;AACA;AACA;;ACJA,KAAK,cAAc;AACnB;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,QAAQ,UAAU,IAAI,CAAC,KAAK,+BAA+B,cAAc;AACvF,cAAc,WAAW,UAAU,IAAI,CAAC,KAAK,iDAAiD,IAAI,CAAC,cAAc;;ACRjH,cAAc,aAAa;AAC3B,cAAc,cAAc,UAAU,KAAK,cAAc,YAAY;AACrE,cAAc,kBAAkB,kBAAkB,KAAK,SAAS,YAAY;;ACH5E,cAAc,cAAc;AAC5B;AACA;AACA;AACA;AACA,KAAK,aAAa;AAClB;AACA;AACA;AACA,KAAK,iBAAiB,GAAG,GAAG,GAAG,eAAe,YAAY,MAAM;AAChE,cAAc,iBAAiB,UAAU,iBAAiB,KAAK,aAAa;;ACT5E,cAAc,SAAS,EAAE,MAAM;AAC/B,cAAc,UAAU;AACxB,KAAK,mBAAmB;AACxB,0BAA0B,KAAK,GAAG,SAAS,QAAQ,YAAY;AAC/D,0BAA0B,KAAK,QAAQ,YAAY;AACnD;AACA,cAAc,cAAc,QAAQ,YAAY,oDAAoD,mBAAmB;AACvH,cAAc,YAAY,SAAS,YAAY;AAC/C,cAAc,mBAAmB,QAAQ,YAAY;;","names":[]}
package/dist/index.js CHANGED
@@ -171,6 +171,20 @@ const imageSrcSet = (asset, widths, transform = {})=>widths.map((w)=>`${imageUrl
171
171
  w
172
172
  })} ${w}w`).join(', ');
173
173
 
174
+ const IMPROVE_BLOCK = '_improve';
175
+ const isImproveBlock = (block)=>block._type === IMPROVE_BLOCK;
176
+ // The blocks to render for an improve block: the variant of `option`, what
177
+ // improve's SDK returned for the visitor (getTestValue / getFlagValue). It
178
+ // is null when the test or flag isn't active or the visitor isn't in it;
179
+ // then, or for an option the block has no variant for, the `fallback`
180
+ // option's variant, else the first one (usually the control).
181
+ const pickImproveVariant = (block, option, fallback)=>{
182
+ const variants = block.variants ?? {};
183
+ if (option && variants[option]) return variants[option];
184
+ if (fallback && variants[fallback]) return variants[fallback];
185
+ return Object.values(variants)[0] ?? [];
186
+ };
187
+
174
188
  // Links are only rendered for these; anything else (`javascript:` …) keeps its
175
189
  // text without the link. The same rule the CMS validates with.
176
190
  const SAFE_HREF = /^(https?:\/\/|mailto:|tel:|#|\/(?!\/))/i;
@@ -291,10 +305,13 @@ exports.editable = preview.editable;
291
305
  exports.readPreviewParams = preview.readPreviewParams;
292
306
  exports.ContentApiError = ContentApiError;
293
307
  exports.ContentClient = ContentClient;
308
+ exports.IMPROVE_BLOCK = IMPROVE_BLOCK;
294
309
  exports.SAFE_HREF = SAFE_HREF;
295
310
  exports.escapeHtml = escapeHtml;
296
311
  exports.headingLevel = headingLevel;
297
312
  exports.imageSrcSet = imageSrcSet;
298
313
  exports.imageUrl = imageUrl;
314
+ exports.isImproveBlock = isImproveBlock;
315
+ exports.pickImproveVariant = pickImproveVariant;
299
316
  exports.richTextToHtml = richTextToHtml;
300
317
  exports.richTextToPlainText = richTextToPlainText;
package/dist/index.mjs CHANGED
@@ -169,6 +169,20 @@ const imageSrcSet = (asset, widths, transform = {})=>widths.map((w)=>`${imageUrl
169
169
  w
170
170
  })} ${w}w`).join(', ');
171
171
 
172
+ const IMPROVE_BLOCK = '_improve';
173
+ const isImproveBlock = (block)=>block._type === IMPROVE_BLOCK;
174
+ // The blocks to render for an improve block: the variant of `option`, what
175
+ // improve's SDK returned for the visitor (getTestValue / getFlagValue). It
176
+ // is null when the test or flag isn't active or the visitor isn't in it;
177
+ // then, or for an option the block has no variant for, the `fallback`
178
+ // option's variant, else the first one (usually the control).
179
+ const pickImproveVariant = (block, option, fallback)=>{
180
+ const variants = block.variants ?? {};
181
+ if (option && variants[option]) return variants[option];
182
+ if (fallback && variants[fallback]) return variants[fallback];
183
+ return Object.values(variants)[0] ?? [];
184
+ };
185
+
172
186
  // Links are only rendered for these; anything else (`javascript:` …) keeps its
173
187
  // text without the link. The same rule the CMS validates with.
174
188
  const SAFE_HREF = /^(https?:\/\/|mailto:|tel:|#|\/(?!\/))/i;
@@ -281,4 +295,4 @@ const richTextToPlainText = (doc)=>{
281
295
  return parts.join('').replace(/\n+/g, '\n').trim();
282
296
  };
283
297
 
284
- export { ContentApiError, ContentClient, DEFAULT_URL, SAFE_HREF, escapeHtml, headingLevel, imageSrcSet, imageUrl, richTextToHtml, richTextToPlainText };
298
+ export { ContentApiError, ContentClient, DEFAULT_URL, IMPROVE_BLOCK, SAFE_HREF, escapeHtml, headingLevel, imageSrcSet, imageUrl, isImproveBlock, pickImproveVariant, richTextToHtml, richTextToPlainText };
package/dist/site.js CHANGED
@@ -86,6 +86,10 @@ const PROXY_ROUTES = [
86
86
  method: 'GET',
87
87
  pattern: /^entries\/[\w-]+$/
88
88
  },
89
+ {
90
+ method: 'POST',
91
+ pattern: /^entries$/
92
+ },
89
93
  {
90
94
  method: 'POST',
91
95
  pattern: /^entries\/[\w-]+\/(preview|prompt|save|publish)$/
package/dist/site.mjs CHANGED
@@ -84,6 +84,10 @@ const PROXY_ROUTES = [
84
84
  method: 'GET',
85
85
  pattern: /^entries\/[\w-]+$/
86
86
  },
87
+ {
88
+ method: 'POST',
89
+ pattern: /^entries$/
90
+ },
87
91
  {
88
92
  method: 'POST',
89
93
  pattern: /^entries\/[\w-]+\/(preview|prompt|save|publish)$/
@@ -30,6 +30,11 @@ type Block<TFields extends Record<string, unknown> = Record<string, unknown>, TT
30
30
  _uid: string;
31
31
  _type: TType;
32
32
  } & TFields;
33
+ type ImproveBlock<TBlock extends Block = Block> = Block<{
34
+ kind: 'test' | 'flag';
35
+ slug: string;
36
+ variants: Record<string, TBlock[]>;
37
+ }, '_improve'>;
33
38
  type RichTextMark = {
34
39
  type: 'bold' | 'italic' | 'underline' | 'strike' | 'code' | 'link' | string;
35
40
  attrs?: Record<string, unknown>;
@@ -116,5 +121,5 @@ type ModelFile = {
116
121
  contentTypes: ContentTypeDefinition[];
117
122
  };
118
123
 
119
- export type { Asset as A, Block as B, ContentTypeDefinition as C, Entry as E, FieldDefinition as F, ModelFile as M, RichTextFormat as R, EntryList as a, EntryStub as b, RichTextNode as c, ContentTypeKind as d, Environment as e, FieldType as f, RichTextDocument as g, RichTextMark as h };
120
- //# sourceMappingURL=types-pa96z6po.d.ts.map
124
+ export type { Asset as A, Block as B, ContentTypeDefinition as C, Entry as E, FieldDefinition as F, ImproveBlock as I, ModelFile as M, RichTextFormat as R, EntryList as a, EntryStub as b, RichTextNode as c, ContentTypeKind as d, Environment as e, FieldType as f, RichTextDocument as g, RichTextMark as h };
125
+ //# sourceMappingURL=types-nyvor49r.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-nyvor49r.d.ts","sources":["../src/types.ts"],"mappings":"AAAA,KAAK,WAAW;AAChB,KAAK,cAAc;AACnB,KAAK,KAAK;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,MAAM;AAC1B,kBAAkB,MAAM;AACxB;AACA,KAAK,KAAK,iBAAiB,MAAM,oBAAoB,MAAM;AAC3D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,SAAS;AACd;AACA;AACA;AACA,KAAK,KAAK,iBAAiB,MAAM,oBAAoB,MAAM;AAC3D;AACA;AACA;AACA,KAAK,YAAY,gBAAgB,KAAK,GAAG,KAAK,IAAI,KAAK;AACvD;AACA;AACA,cAAc,MAAM;AACpB;AACA,KAAK,YAAY;AACjB;AACA,YAAY,MAAM;AAClB;AACA,KAAK,YAAY;AACjB;AACA,YAAY,MAAM;AAClB,cAAc,YAAY;AAC1B;AACA,YAAY,YAAY;AACxB;AACA,KAAK,gBAAgB,GAAG,YAAY;AACpC;AACA;AACA,KAAK,SAAS,gBAAgB,KAAK,GAAG,KAAK;AAC3C;AACA;AACA;AACA;AACA;AACA,KAAK,eAAe;AACpB,KAAK,SAAS;AACd;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,eAAe,GAAG,SAAS;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,SAAS,GAAG,eAAe;AAChC,KAAK,qBAAqB;AAC1B;AACA;AACA,UAAU,eAAe;AACzB;AACA,YAAY,eAAe;AAC3B;AACA,KAAK,SAAS;AACd;AACA;AACA,kBAAkB,qBAAqB;AACvC;;","names":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@obelism/content",
3
3
  "description": "Obelism Content SDK: the delivery API client, the visual editor bridge with prompt editing, editing on your own site, and the content model CLI",
4
- "version": "0.1.1",
4
+ "version": "0.3.0",
5
5
  "keywords": [
6
6
  "cms",
7
7
  "headless-cms",
@@ -65,7 +65,7 @@
65
65
  }
66
66
  },
67
67
  "scripts": {
68
- "build": "bunchee",
68
+ "build": "bunchee && rollup --config rollup.bridge.config.mjs",
69
69
  "dev": "bunchee --watch --no-clean",
70
70
  "test": "vitest",
71
71
  "test:coverage": "vitest run --coverage",
@@ -77,12 +77,15 @@
77
77
  },
78
78
  "devDependencies": {
79
79
  "@obelism/content-typescript-config": "^0.0.0",
80
+ "@rollup/plugin-node-resolve": "^16.0.3",
80
81
  "@types/node": "^24.0.0",
81
82
  "@typescript/typescript6": "^6.0.2",
82
83
  "bunchee": "^7.0.2",
83
84
  "jsdom": "^30.1.2",
84
85
  "npm-run-all2": "^9.0.2",
85
86
  "oxlint": "^1.88.0",
87
+ "rollup": "^4.64.7",
88
+ "rollup-plugin-swc3": "^0.12.1",
86
89
  "typescript": "^7.0.2",
87
90
  "vitest": "^5.0.4"
88
91
  },
@@ -1 +0,0 @@
1
- {"version":3,"file":"types-pa96z6po.d.ts","sources":["../src/types.ts"],"mappings":"AAAA,KAAK,WAAW;AAChB,KAAK,cAAc;AACnB,KAAK,KAAK;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,MAAM;AAC1B,kBAAkB,MAAM;AACxB;AACA,KAAK,KAAK,iBAAiB,MAAM,oBAAoB,MAAM;AAC3D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,SAAS;AACd;AACA;AACA;AACA,KAAK,KAAK,iBAAiB,MAAM,oBAAoB,MAAM;AAC3D;AACA;AACA;AACA,KAAK,YAAY;AACjB;AACA,YAAY,MAAM;AAClB;AACA,KAAK,YAAY;AACjB;AACA,YAAY,MAAM;AAClB,cAAc,YAAY;AAC1B;AACA,YAAY,YAAY;AACxB;AACA,KAAK,gBAAgB,GAAG,YAAY;AACpC;AACA;AACA,KAAK,SAAS,gBAAgB,KAAK,GAAG,KAAK;AAC3C;AACA;AACA;AACA;AACA;AACA,KAAK,eAAe;AACpB,KAAK,SAAS;AACd;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,eAAe,GAAG,SAAS;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,SAAS,GAAG,eAAe;AAChC,KAAK,qBAAqB;AAC1B;AACA;AACA,UAAU,eAAe;AACzB;AACA,YAAY,eAAe;AAC3B;AACA,KAAK,SAAS;AACd;AACA;AACA,kBAAkB,qBAAqB;AACvC;;","names":[]}