@obelism/content 0.7.0 → 0.9.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/README.md CHANGED
@@ -22,6 +22,11 @@ const content = new ContentClient({
22
22
  })
23
23
 
24
24
  await content.getPage('blog/hello') // Entry | null
25
+ await content.getPage('blog/hallo', { locale: 'nl' }) // looked up in nl's paths
26
+ await content.findPage('blog/old') // { entry } | { redirect: { path } } | null
27
+ await content.getRedirects({ locale: 'nl' }) // { items: [{ locale, from, to }], total, limit, skip }
28
+ for await (const redirect of content.getAllRedirects()) {
29
+ }
25
30
  await content.getEntry('entry_…') // Entry | null
26
31
  await content.getEntries({ contentType: 'post', limit: 20, skip: 0 }) // { items, total, limit, skip }
27
32
  for await (const post of content.getAllEntries({ contentType: 'post' })) {
@@ -39,7 +44,17 @@ await content.getContentModel() // the model file
39
44
  ```
40
45
 
41
46
  Each method takes `{ locale, depth, richText, init }` to override the
42
- defaults. Missing pages and entries are `null`; any other failure throws a
47
+ defaults.
48
+
49
+ Pages and folders can have a slug per locale. `getPage` looks the path up
50
+ in the requested locale's paths, `entry.path` is the path in that locale and
51
+ `entry.paths` the path in every locale (`{ en: 'about-us', nl: 'over-ons' }`),
52
+ for hreflang links and language switchers. The locale is never part of a
53
+ path: whether it is `/nl/over-ons` or `nl.example.com/over-ons` is your
54
+ routing. A page that moved keeps its old path as a redirect in each locale:
55
+ `findPage` answers it with `{ redirect: { path } }`, so your route can send
56
+ a `301` instead of a 404, and `getAllRedirects` lists them for redirect rules
57
+ built ahead of time. Missing pages and entries are `null`; any other failure throws a
43
58
  `ContentApiError` with `status` and the API's `message`. Type your content
44
59
  with generics: `content.getPage<Entry<{ title: string }>>('home')`.
45
60
 
@@ -131,11 +146,12 @@ bridge.undo()
131
146
  On a site signed in to edit (`@obelism/content/site`), the box also has **New
132
147
  page**: a path (`/pricing`, or under an existing page) and what the page is
133
148
  for. The CMS writes the page like the one the editor is on and stores it as a
134
- draft, and the bridge opens it at `/` + its path. When your routes put CMS
135
- pages elsewhere, say where:
149
+ draft, and the bridge opens it at `/` + its path. The path is the one in
150
+ the page's language (`<html lang>`). When your routes put CMS pages
151
+ elsewhere, say where:
136
152
 
137
153
  ```ts
138
- getContentBridge({ pagePath: (path) => `/en/${path}` })
154
+ getContentBridge({ pagePath: (path, locale) => `/${locale}/${path}` })
139
155
  await bridge.createPage('/pricing', 'Our plans, for agencies') // or your own UI
140
156
  ```
141
157
 
@@ -196,7 +212,7 @@ const event = await verifyWebhook(request, process.env.CONTENT_WEBHOOK_SECRET!)
196
212
 
197
213
  `WebhookEvent` is a union on `type`: `entry.published`, `entry.unpublished`,
198
214
  `entry.deleted` and `entry.moved` (`data.entry`: `{ id, contentType, name,
199
- path }`, plus `previousPath` when moved), `asset.updated` (`data.asset`,
215
+ path, paths }`, plus `previousPath` and `previousPaths` when moved), `asset.updated` (`data.asset`,
200
216
  `data.entries`), `model.changed` (`data.contentTypes`: `{ apiId, action }`),
201
217
  `locales.changed` (`data.defaultLocale`, `data.locales`) and `webhook.test`.
202
218
 
@@ -225,7 +241,27 @@ bridge.on('change', ({ stats, statsVisible }) => …) // stats[uid].rate, 0–1
225
241
  ## Helpers
226
242
 
227
243
  - `richTextToHtml(doc, { renderEntry?, renderAsset? })`, `richTextToPlainText(doc)`
228
- - `imageUrl(asset, { w, h, fit, format, q })`, `imageSrcSet(asset, [480, 960])`
244
+ - `imageUrl(asset, { w, h, fit, format, q, focus })`, `imageSrcSet(asset, [480, 960])`:
245
+ with `fit: 'cover'` or `'crop'` the crop keeps the asset's `focalPoint` (set
246
+ in the CMS under Media) in view, or the `focus` you pass (`{ x, y }` or
247
+ `'auto'`)
248
+ - `alternateLinks(entry, { url, defaultLocale })`: every locale's URL of a page
249
+ (`entry.paths`), plus `x-default`, for `<link rel="alternate" hreflang>`
250
+ - `sitemapXml(entries, { url, defaultLocale, exclude? })`: a sitemap with one
251
+ `<url>` per page and locale and its alternates; pages with "Hide from search
252
+ engines" (`fields.noIndex`) are left out. `url(path, locale)` decides where
253
+ a locale's path lives on your site:
254
+
255
+ ```ts
256
+ const pages = []
257
+ for await (const page of content.getAllEntries({ contentType: 'page' })) {
258
+ pages.push(page)
259
+ }
260
+ const xml = sitemapXml(pages, {
261
+ url: (path, locale) => `https://example.com/${locale}/${path}`,
262
+ defaultLocale: 'en',
263
+ })
264
+ ```
229
265
 
230
266
  ## A/B tests and feature flags
231
267
 
@@ -25,7 +25,8 @@ const exampleAsset = (mimeTypes)=>{
25
25
  width: image ? 1600 : null,
26
26
  height: image ? 900 : null,
27
27
  title: null,
28
- alt: image ? 'An example image' : null
28
+ alt: image ? 'An example image' : null,
29
+ focalPoint: null
29
30
  };
30
31
  };
31
32
  const exampleRichText = (name)=>({
package/dist/bridge.d.mts CHANGED
@@ -112,7 +112,7 @@ declare const mountPromptBox: (doc: Document, bridge: ContentBridge, options?: P
112
112
  type BridgeOptions = {
113
113
  url?: string | undefined;
114
114
  prompt?: boolean | PromptBoxOptions | undefined;
115
- pagePath?: ((path: string) => string) | undefined;
115
+ pagePath?: ((path: string, locale: string) => string) | undefined;
116
116
  };
117
117
  declare const peekContentBridge: () => ContentBridge | null;
118
118
  declare const getContentBridge: ({ url, prompt, pagePath, }?: BridgeOptions) => ContentBridge;
package/dist/bridge.d.ts CHANGED
@@ -112,7 +112,7 @@ declare const mountPromptBox: (doc: Document, bridge: ContentBridge, options?: P
112
112
  type BridgeOptions = {
113
113
  url?: string | undefined;
114
114
  prompt?: boolean | PromptBoxOptions | undefined;
115
- pagePath?: ((path: string) => string) | undefined;
115
+ pagePath?: ((path: string, locale: string) => string) | undefined;
116
116
  };
117
117
  declare const peekContentBridge: () => ContentBridge | null;
118
118
  declare const getContentBridge: ({ url, prompt, pagePath, }?: BridgeOptions) => ContentBridge;
@@ -179,4 +179,4 @@ button.chip:disabled { opacity: .5; }
179
179
  background: rgb(var(--theme-accent-rgb)); margin-right: 8px;
180
180
  }
181
181
  [hidden] { display: none !important; }
182
- `,x=(e,t,r={},...n)=>{let o=e.createElement(t);for(let t of(Object.assign(o,r),n))o.appendChild("string"==typeof t?e.createTextNode(t):t);return o},w=(e,t)=>{let r=x(e,"span",{className:"icon"});return r.setAttribute("aria-hidden","true"),r.innerHTML=`<svg viewBox="0 0 24 24">${v[t]}</svg>`,r},k=(e,t,r={})=>{let n={...f,...r.labels},o=x(e,"div");o.setAttribute("data-obelism-content-prompt","");let a=o.attachShadow({mode:"open"}),i="site"===t.mode,s=x(e,"span",{textContent:n.open}),l=x(e,"button",{className:"pill",type:"button"},s,x(e,"span",{className:"mark",textContent:"⁜"})),d=x(e,"span",{className:"pill compact"},`${n.viewingDrafts} \xb7 `),c=x(e,"button",{className:"link",type:"button",textContent:n.signOut});d.appendChild(c);let p=x(e,"p",{className:"status"});p.setAttribute("role","status");let g=x(e,"textarea",{placeholder:n.placeholder,rows:1});g.setAttribute("aria-label",n.open);let u=w(e,"page"),h=w(e,"block"),b=x(e,"span"),m=x(e,"button",{type:"button",title:n.clearBlock});m.setAttribute("aria-label",n.clearBlock),m.appendChild(w(e,"close"));let v=x(e,"span",{className:"chip scope"},u,h,b,m),k=x(e,"button",{className:"action chip",type:"button"},w(e,"undo"),n.undo),E=x(e,"button",{className:"action chip",type:"button"},w(e,"newPage"),n.newPage),S=x(e,"button",{type:"button",title:n.newPageCancel});S.setAttribute("aria-label",n.newPageCancel),S.appendChild(w(e,"close"));let C=x(e,"span",{className:"chip scope"},w(e,"newPage"),x(e,"span",{textContent:n.newPage}),S),N=x(e,"input",{type:"text",placeholder:n.pathPlaceholder,autocomplete:"off",spellcheck:!1}),L=x(e,"label",{className:"path"},x(e,"span",{textContent:n.path}),N),U=x(e,"button",{className:"close icon-button",type:"button",title:n.close},w(e,"close"));U.setAttribute("aria-label",n.close);let P=x(e,"button",{className:"action primary icon-button",type:"submit"},x(e,"span",{className:"visually-hidden",textContent:n.send}),w(e,"send"));P.title=n.send;let $=x(e,"span",{className:"unsaved"});$.appendChild(x(e,"span",{className:"dot"})),$.appendChild(e.createTextNode(n.unsaved));let A=x(e,"button",{className:"link",type:"button",textContent:n.signOut}),O=x(e,"button",{className:"action chip",type:"button",textContent:n.save}),I=x(e,"button",{className:"action chip",type:"button",textContent:n.publish}),M=x(e,"div",{className:"footer"},$,x(e,"span",{className:"spacer"}),A,O,I),j=x(e,"form",{className:"panel"},p,L,g,x(e,"div",{className:"toolbar"},v,C,k,E,x(e,"span",{className:"spacer"}),U,x(e,"span",{className:"divider"}),P),M),z=x(e,"div",{className:"root"},x(e,"div",{className:"bar"},l,d),j);a.append(x(e,"style",{textContent:y}),z);let R=!1,B=!1,D=null,T=e=>{let r=e.features.includes("prompt"),o=e.saving||e.publishing,a=e.prompting||o;z.hidden=!r&&!i,l.hidden=R||!r,d.hidden=r||!i,j.hidden=!R||!r;let c=i&&e.features.includes("create"),f=i&&null===t.entryId,y=f&&c;c||(B=!1),y&&(B=!0),s.textContent=y?n.newPage:n.open,S.hidden=y,v.hidden=B,C.hidden=!B,E.hidden=B||!c,E.disabled=a,L.hidden=!B,N.disabled=a,g.placeholder=B?n.newPagePlaceholder:n.placeholder,b.textContent=e.selectedUid?n.scopeBlock:n.scopeEntry,m.hidden=!e.selectedUid,u.hidden=!!e.selectedUid,h.hidden=!e.selectedUid,k.hidden=B||!e.canUndo,k.disabled=a,P.disabled=a||""===g.value.trim()||B&&""===N.value.trim(),g.disabled=a,O.hidden=!e.features.includes("save")||f,O.disabled=a||!e.unsaved,O.textContent=e.saving?n.saving:n.save,I.hidden=!e.features.includes("publish")||f,I.disabled=a,I.textContent=e.publishing?n.publishing:n.publish,A.hidden=!i,M.hidden=!i,$.hidden=!e.unsaved;let x=e.prompting?B?n.creating:n.working:D?.text??(!i||B||e.selectedUid?"":n.selectHint);p.textContent=x,p.hidden=!x,p.className=`status${e.prompting?" working":""}${!a&&D?.error?" error":""}`},q=()=>T(t.getState()),V=()=>{g.style.height="auto",g.style.height=`${g.scrollHeight}px`},H=async()=>{let e=g.value.trim();if(e&&!t.getState().prompting){D=null;try{if(B){let r=N.value.trim();if(!r)return N.focus();await t.createPage(r,e),D={text:n.created,error:!1}}else{let{summary:r}=await t.prompt(e);D={text:r,error:!1}}g.value="",V()}catch(e){D={text:e instanceof Error?e.message:String(e),error:!0}}q(),g.focus()}},F=e=>{R=e,q(),i&&t.setSelecting(R&&!B)},Y=e=>{B=e,D=null,t.setSelecting(R&&!B),B&&t.select(null),q(),(B?N:g).focus()},_=async e=>{D=null;try{await t[e](),D={text:"save"===e?n.saved:n.published,error:!1}}catch(e){D={text:e instanceof Error?e.message:String(e),error:!0}}q()};l.addEventListener("click",()=>{F(!0),(B?N:g).focus()}),U.addEventListener("click",()=>F(!1)),O.addEventListener("click",()=>void _("save")),I.addEventListener("click",()=>void _("publish")),A.addEventListener("click",()=>t.signOut()),c.addEventListener("click",()=>t.signOut()),m.addEventListener("click",()=>t.select(null)),E.addEventListener("click",()=>Y(!0)),S.addEventListener("click",()=>Y(!1)),N.addEventListener("input",q),N.addEventListener("keydown",e=>{"Enter"!==e.key||e.isComposing?"Escape"===e.key&&F(!1):(e.preventDefault(),g.focus())}),k.addEventListener("click",()=>{D=null,t.undo()}),j.addEventListener("submit",e=>{e.preventDefault(),H()}),g.addEventListener("input",()=>{V(),q()}),g.addEventListener("keydown",e=>{"Enter"!==e.key||e.shiftKey||e.isComposing?"Escape"===e.key&&F(!1):(e.preventDefault(),H())});let W=t.on("change",T);return q(),e.body.appendChild(o),()=>{W(),o.remove()}},E="https://content.obelism.studio",S=(e,t)=>JSON.stringify(e)===JSON.stringify(t),C=Symbol.for("@obelism/content/bridge"),N=m({isEditing:!1,entryId:null,on:()=>{}},new URLSearchParams),L=({url:e,prompt:l=!0,pagePath:g}={})=>{let f;if("u"<typeof window)return N;let v=window;if(v[C])return v[C];if(v.ObelismBridge)f=m(v.ObelismBridge,new URLSearchParams(v.location.search));else{let m=(f=((e,r)=>{let l=new URLSearchParams(e.location.search),g=e.parent!==e&&l.has("cms_preview"),m=p(),f=[],v={...c,selecting:!0},y=e=>{v={...v,...e},m.emit("change",v)},x={isEditing:g,mode:g?"editor":null,entryId:l.get("cms_entry"),environment:l.get(n),on:m.on,getState:()=>v,...u,destroy:()=>{f.splice(0).forEach(e=>e())}};if(!g)return x;let w=e.document,k=(t,n={})=>{e.parent.postMessage({source:"obelism-content-bridge",type:t,...n},r)},E=w.createElement("style");E.textContent=d(),w.head.appendChild(E),f.push(()=>E.remove());let S=(e,t)=>{if(w.querySelectorAll("[data-cms-selected]").forEach(e=>{e.removeAttribute("data-cms-selected")}),v.selectedUid!==(e??null)&&y({selectedUid:e??null}),!e)return;let r=w.querySelector(b(e));r&&(r.setAttribute("data-cms-selected",""),t&&r.scrollIntoView({behavior:"smooth",block:"center"}))},C=e=>{let r=e.target,n=r?.closest?.(`[${t}]`);if(!n)return;e.preventDefault(),e.stopPropagation();let o=n.getAttribute(t);S(o,!1),k("select",{uid:o})};w.addEventListener("click",C,!0),f.push(()=>w.removeEventListener("click",C,!0));let N=()=>k("ready",{url:e.location.href}),L=new Map,U=0,P=e=>{let t=L.get(e);return t?(clearTimeout(t.timer),L.delete(e),0===L.size&&y({prompting:!1}),t):null};x.prompt=(e,t={})=>{let r=e.trim();if(!r)return Promise.reject(Error("Say what to change"));if(!v.features.includes("prompt"))return Promise.reject(Error("The editor does not offer prompts here"));let n=`prompt_${++U}`,o=void 0===t.uid?v.selectedUid:t.uid;return new Promise((e,t)=>{let a=setTimeout(()=>{P(n)?.reject(Error("The editor did not answer in time"))},12e4);L.set(n,{resolve:e,reject:t,timer:a}),y({prompting:!0}),k("prompt",{id:n,prompt:r,uid:o})})},x.undo=()=>k("undo"),x.select=e=>{S(e,!1),k("select",{uid:e})};let $=null;f.push(()=>$?.destroy()),f.push(()=>{for(let e of L.keys())P(e)?.reject(Error("The bridge was destroyed"))});let A=t=>{if(t.origin!==r||t.source!==e.parent)return;let n=t.data;if(n&&"obelism-content-editor"===n.source){if("connect"===n.type)N();else if("update"===n.type&&n.entry)m.emit("update",{entry:n.entry,selectedUid:n.selectedUid??null}),e.requestAnimationFrame(()=>S(n.selectedUid,!1)),$?.refresh();else if("select"===n.type)S(n.uid,!0);else if("saved"===n.type||"published"===n.type)m.emit(n.type,{}),0===m.listeners.update.size&&e.location.reload();else if("features"===n.type&&Array.isArray(n.features))y({features:n.features.filter(e=>"string"==typeof e)});else if("stats"===n.type)y({stats:(e=>{if(!e||"object"!=typeof e||Array.isArray(e))return{};let t={};for(let[r,n]of Object.entries(e)){let{clicks:e,clickers:o,viewers:a,rate:i}=n??{};h(e)&&h(o)&&h(a)&&h(i)&&(t[r]={clicks:e,clickers:o,viewers:a,rate:i})}return t})(n.stats),statsVisible:!1!==n.visible}),($||Object.keys(v.stats).length)&&($??($=((e,{selector:t,onSelect:r,labels:n})=>{let l=e.document,d={...o,...n},c=l.createElement("div");c.setAttribute("data-obelism-stats","");let p=c.attachShadow({mode:"open"}),g=l.createElement("style");g.textContent=i;let u=l.createElement("div");u.className="layer",p.append(g,u),l.body.appendChild(c);let h={},b=!1,m=null,f=()=>{if(m=null,u.replaceChildren(),u.hidden=!b,!b)return;let n=e.innerHeight;for(let[o,i]of Object.entries(h)){let c=l.querySelector(t(o));if(!c)continue;let p=c.getBoundingClientRect();if(!p.width&&!p.height||p.bottom<0||p.top>n)continue;let g=l.createElement("button");g.type="button",g.className="badge",g.dataset.uid=o,g.textContent=a(d.clicked,{rate:Math.round(100*i.rate)}),g.title=a(d.title,{clickers:i.clickers,viewers:i.viewers,clicks:i.clicks});let{place:h,top:b,left:m}=s(p,e.innerWidth);g.dataset.place=h,g.style.top=`${b}px`,g.style.left=`${m}px`,g.addEventListener("click",()=>r(o)),u.appendChild(g)}},v=()=>{null===m&&b&&(m=e.requestAnimationFrame(f))};e.addEventListener("scroll",v,{capture:!0,passive:!0}),e.addEventListener("resize",v);let y=e.MutationObserver,x="function"==typeof y?new y(v):null;return x?.observe(l.body,{childList:!0,subtree:!0}),{update(t,r){h=t,b=r,null!==m&&(e.cancelAnimationFrame?.(m),m=null),f()},refresh:v,destroy(){null!==m&&e.cancelAnimationFrame?.(m),e.removeEventListener("scroll",v,{capture:!0}),e.removeEventListener("resize",v),x?.disconnect(),c.remove()}}})(e,{selector:b,onSelect:e=>x.select(e)})),$.update(v.stats,v.statsVisible));else if("promptState"===n.type)y({canUndo:!0===n.canUndo});else if("promptResult"===n.type&&"string"==typeof n.id){let e=P(n.id);if(!e)return;!0===n.ok?e.resolve({summary:"string"==typeof n.summary?n.summary:""}):e.reject(Error("string"==typeof n.error?n.error:"The prompt failed"))}}};return e.addEventListener("message",A),f.push(()=>e.removeEventListener("message",A)),"loading"===w.readyState?(w.addEventListener("DOMContentLoaded",N,{once:!0}),f.push(()=>w.removeEventListener("DOMContentLoaded",N))):N(),x})(v,new URL(e||E).origin)).isEditing||v.parent!==v?null:(e=>{for(let t of e.cookie.split(";")){let[e,...r]=t.trim().split("=");if("obelism_content_editing"===e)try{let e=decodeURIComponent(r.join("="));return e.startsWith("/")&&!e.startsWith("//")?e:null}catch{break}}return null})(v.document);if(m&&(f=((e,n,{pagePath:o=e=>`/${e}`}={})=>{let a=e.document,i=p(),s=[],l=new Map,g=[],u=new Set,h=null,m=c,f=e=>{m={...m,...e},i.emit("change",m)},v=()=>f({unsaved:[...l.values()].some(({content:e,saved:t})=>!S(e,t)),canUndo:g.length>0}),y=async(t,r)=>{let o=await e.fetch(`${n}/api/${t}`,void 0===r?{credentials:"same-origin"}:{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json","x-obelism-content":"1"},body:JSON.stringify(r)}),a=await o.json().catch(()=>({}));if(401===o.status)throw f({features:[]}),Error("You were signed out. Sign in again to edit.");if(!o.ok)throw Error("string"==typeof a.error?a.error:`Obelism Content answered ${o.status}`);return a},x=()=>a.querySelector(`[${r}]`)?.getAttribute(r)??null,w=()=>{if(!h)return;let e=h.space.locales.map(({code:e})=>e),t=a.documentElement.lang;if(e.includes(t))return t;let r=t.split("-")[0]??"";return e.includes(r)?r:h.space.defaultLocale},k=async e=>{let t=l.get(e);if(t)return t;let r=await y(`entries/${encodeURIComponent(e)}`),n={content:r.content,saved:r.content,revision:r.draftRevision};return l.set(e,n),n},E=e=>{a.querySelectorAll("[data-cms-selected]").forEach(e=>{e.removeAttribute("data-cms-selected")}),m.selectedUid!==e&&f({selectedUid:e}),e&&a.querySelector(b(e))?.setAttribute("data-cms-selected","")},C=async t=>{if(0===i.listeners.update.size)return;let r=l.get(t)?.content,{entry:n}=await y(`entries/${encodeURIComponent(t)}/preview`,{content:r,locale:w()});i.emit("update",{entry:n,selectedUid:m.selectedUid}),e.requestAnimationFrame(()=>E(m.selectedUid))},N=t=>{i.emit(t,{}),0===i.listeners.update.size&&e.location.reload()},L=async()=>{for(let[e,t]of l){if(S(t.content,t.saved))continue;let{draftRevision:r}=await y(`entries/${encodeURIComponent(e)}/save`,{content:t.content,baseRevision:t.revision});t.saved=t.content,t.revision=r,u.add(e)}g.length=0,v()},U={isEditing:!0,mode:"site",get entryId(){return x()},environment:null,on:i.on,getState:()=>m,async prompt(e,t={}){let n,o=e.trim();if(!o)throw Error("Say what to change");if(!m.features.includes("prompt"))throw Error("You may not edit content here");let i=void 0===t.uid?m.selectedUid:t.uid,s=(n=i?a.querySelector(b(i)):null,n?.closest(`[${r}]`)?.getAttribute(r)??x());if(!s)throw Error("Nothing on this page is marked as an entry: add editable(entry)");f({prompting:!0});try{let e=await k(s),t=await y(`entries/${encodeURIComponent(s)}/prompt`,{prompt:o,content:e.content,locale:w(),uid:i});return g.push({entryId:s,content:e.content}),e.content=t.content,v(),await C(s),{summary:t.summary}}finally{f({prompting:!1})}},async createPage(t,r){let n=r.trim();if(!n)throw Error("Say what the page is for");if(!t.trim())throw Error("Say where the page goes, like /pricing");if(!m.features.includes("create"))throw Error("You may not add pages here");if(m.unsaved)throw Error("Save or undo your changes before adding a page");f({prompting:!0});try{let r=await y("entries",{path:t,prompt:n,locale:w(),from:x()}),a=o(r.path);return e.location.assign(a),{summary:r.summary,entryId:r.entryId,path:r.path,url:a}}finally{f({prompting:!1})}},undo(){let e=g.pop(),t=e&&l.get(e.entryId);e&&t&&(t.content=e.content,v(),C(e.entryId).catch(e=>console.error("[@obelism/content bridge]",e)))},async save(){if(!m.features.includes("save"))throw Error("You may not edit content here");f({saving:!0});try{await L()}finally{f({saving:!1})}N("saved")},async publish(){if(!m.features.includes("publish"))throw Error("You may not publish here");f({publishing:!0});try{await L();let e=x(),t=u.size>0?[...u]:e?[e]:[];if(0===t.length)throw Error("Nothing on this page is marked as an entry: add editable(entry)");for(let e of t)await y(`entries/${encodeURIComponent(e)}/publish`,{}),u.delete(e)}finally{f({publishing:!1})}N("published")},signOut(){if(m.unsaved&&!e.confirm("Sign out and lose the changes you have not saved?"))return;l.clear(),v();let t=e.location.pathname+e.location.search;e.location.href=`${n}/logout?return_to=${encodeURIComponent(t)}`},select(e){E(e)},setSelecting(e){e?a.documentElement.setAttribute("data-cms-selecting",""):a.documentElement.removeAttribute("data-cms-selecting"),f({selecting:e})},destroy(){s.splice(0).forEach(e=>e())}},P=a.createElement("style");P.textContent=d("html[data-cms-selecting] "),a.head.appendChild(P),s.push(()=>P.remove()),s.push(()=>a.documentElement.removeAttribute("data-cms-selecting"));let $=e=>{if(!m.selecting)return;let r=e.target,n=r?.closest?.(`[${t}]`);n&&(e.preventDefault(),e.stopPropagation(),E(n.getAttribute(t)))};a.addEventListener("click",$,!0),s.push(()=>a.removeEventListener("click",$,!0));let A=e=>{m.unsaved&&(e.preventDefault(),e.returnValue="")};return e.addEventListener("beforeunload",A),s.push(()=>e.removeEventListener("beforeunload",A)),y("session").then(e=>{h=e,f({features:e.features})}).catch(e=>console.error("[@obelism/content bridge]",e)),U})(v,m,{pagePath:g})),f.isEditing&&l){let e=k(v.document,f,!0===l?{}:l),t=f.destroy;f.destroy=()=>{e(),t()}}}return v[C]=f,f};(e=window).ObelismBridge=L((e=>{let t=e?.dataset.url,r=t||E;if(!t&&e?.src){let t=new URL(e.src);"/bridge.js"===t.pathname&&(r=t.origin)}return{url:r,prompt:e?.dataset.prompt!=="false"}})(document.currentScript)),e.ObelismContent={getContentBridge:L,mountPromptBox:k}}();
182
+ `,x=(e,t,r={},...n)=>{let o=e.createElement(t);for(let t of(Object.assign(o,r),n))o.appendChild("string"==typeof t?e.createTextNode(t):t);return o},w=(e,t)=>{let r=x(e,"span",{className:"icon"});return r.setAttribute("aria-hidden","true"),r.innerHTML=`<svg viewBox="0 0 24 24">${v[t]}</svg>`,r},k=(e,t,r={})=>{let n={...f,...r.labels},o=x(e,"div");o.setAttribute("data-obelism-content-prompt","");let a=o.attachShadow({mode:"open"}),i="site"===t.mode,s=x(e,"span",{textContent:n.open}),l=x(e,"button",{className:"pill",type:"button"},s,x(e,"span",{className:"mark",textContent:"⁜"})),d=x(e,"span",{className:"pill compact"},`${n.viewingDrafts} \xb7 `),c=x(e,"button",{className:"link",type:"button",textContent:n.signOut});d.appendChild(c);let p=x(e,"p",{className:"status"});p.setAttribute("role","status");let g=x(e,"textarea",{placeholder:n.placeholder,rows:1});g.setAttribute("aria-label",n.open);let u=w(e,"page"),h=w(e,"block"),b=x(e,"span"),m=x(e,"button",{type:"button",title:n.clearBlock});m.setAttribute("aria-label",n.clearBlock),m.appendChild(w(e,"close"));let v=x(e,"span",{className:"chip scope"},u,h,b,m),k=x(e,"button",{className:"action chip",type:"button"},w(e,"undo"),n.undo),E=x(e,"button",{className:"action chip",type:"button"},w(e,"newPage"),n.newPage),S=x(e,"button",{type:"button",title:n.newPageCancel});S.setAttribute("aria-label",n.newPageCancel),S.appendChild(w(e,"close"));let C=x(e,"span",{className:"chip scope"},w(e,"newPage"),x(e,"span",{textContent:n.newPage}),S),N=x(e,"input",{type:"text",placeholder:n.pathPlaceholder,autocomplete:"off",spellcheck:!1}),L=x(e,"label",{className:"path"},x(e,"span",{textContent:n.path}),N),U=x(e,"button",{className:"close icon-button",type:"button",title:n.close},w(e,"close"));U.setAttribute("aria-label",n.close);let P=x(e,"button",{className:"action primary icon-button",type:"submit"},x(e,"span",{className:"visually-hidden",textContent:n.send}),w(e,"send"));P.title=n.send;let $=x(e,"span",{className:"unsaved"});$.appendChild(x(e,"span",{className:"dot"})),$.appendChild(e.createTextNode(n.unsaved));let A=x(e,"button",{className:"link",type:"button",textContent:n.signOut}),O=x(e,"button",{className:"action chip",type:"button",textContent:n.save}),I=x(e,"button",{className:"action chip",type:"button",textContent:n.publish}),M=x(e,"div",{className:"footer"},$,x(e,"span",{className:"spacer"}),A,O,I),j=x(e,"form",{className:"panel"},p,L,g,x(e,"div",{className:"toolbar"},v,C,k,E,x(e,"span",{className:"spacer"}),U,x(e,"span",{className:"divider"}),P),M),z=x(e,"div",{className:"root"},x(e,"div",{className:"bar"},l,d),j);a.append(x(e,"style",{textContent:y}),z);let R=!1,B=!1,D=null,T=e=>{let r=e.features.includes("prompt"),o=e.saving||e.publishing,a=e.prompting||o;z.hidden=!r&&!i,l.hidden=R||!r,d.hidden=r||!i,j.hidden=!R||!r;let c=i&&e.features.includes("create"),f=i&&null===t.entryId,y=f&&c;c||(B=!1),y&&(B=!0),s.textContent=y?n.newPage:n.open,S.hidden=y,v.hidden=B,C.hidden=!B,E.hidden=B||!c,E.disabled=a,L.hidden=!B,N.disabled=a,g.placeholder=B?n.newPagePlaceholder:n.placeholder,b.textContent=e.selectedUid?n.scopeBlock:n.scopeEntry,m.hidden=!e.selectedUid,u.hidden=!!e.selectedUid,h.hidden=!e.selectedUid,k.hidden=B||!e.canUndo,k.disabled=a,P.disabled=a||""===g.value.trim()||B&&""===N.value.trim(),g.disabled=a,O.hidden=!e.features.includes("save")||f,O.disabled=a||!e.unsaved,O.textContent=e.saving?n.saving:n.save,I.hidden=!e.features.includes("publish")||f,I.disabled=a,I.textContent=e.publishing?n.publishing:n.publish,A.hidden=!i,M.hidden=!i,$.hidden=!e.unsaved;let x=e.prompting?B?n.creating:n.working:D?.text??(!i||B||e.selectedUid?"":n.selectHint);p.textContent=x,p.hidden=!x,p.className=`status${e.prompting?" working":""}${!a&&D?.error?" error":""}`},q=()=>T(t.getState()),V=()=>{g.style.height="auto",g.style.height=`${g.scrollHeight}px`},H=async()=>{let e=g.value.trim();if(e&&!t.getState().prompting){D=null;try{if(B){let r=N.value.trim();if(!r)return N.focus();await t.createPage(r,e),D={text:n.created,error:!1}}else{let{summary:r}=await t.prompt(e);D={text:r,error:!1}}g.value="",V()}catch(e){D={text:e instanceof Error?e.message:String(e),error:!0}}q(),g.focus()}},F=e=>{R=e,q(),i&&t.setSelecting(R&&!B)},Y=e=>{B=e,D=null,t.setSelecting(R&&!B),B&&t.select(null),q(),(B?N:g).focus()},_=async e=>{D=null;try{await t[e](),D={text:"save"===e?n.saved:n.published,error:!1}}catch(e){D={text:e instanceof Error?e.message:String(e),error:!0}}q()};l.addEventListener("click",()=>{F(!0),(B?N:g).focus()}),U.addEventListener("click",()=>F(!1)),O.addEventListener("click",()=>void _("save")),I.addEventListener("click",()=>void _("publish")),A.addEventListener("click",()=>t.signOut()),c.addEventListener("click",()=>t.signOut()),m.addEventListener("click",()=>t.select(null)),E.addEventListener("click",()=>Y(!0)),S.addEventListener("click",()=>Y(!1)),N.addEventListener("input",q),N.addEventListener("keydown",e=>{"Enter"!==e.key||e.isComposing?"Escape"===e.key&&F(!1):(e.preventDefault(),g.focus())}),k.addEventListener("click",()=>{D=null,t.undo()}),j.addEventListener("submit",e=>{e.preventDefault(),H()}),g.addEventListener("input",()=>{V(),q()}),g.addEventListener("keydown",e=>{"Enter"!==e.key||e.shiftKey||e.isComposing?"Escape"===e.key&&F(!1):(e.preventDefault(),H())});let W=t.on("change",T);return q(),e.body.appendChild(o),()=>{W(),o.remove()}},E="https://content.obelism.studio",S=(e,t)=>JSON.stringify(e)===JSON.stringify(t),C=Symbol.for("@obelism/content/bridge"),N=m({isEditing:!1,entryId:null,on:()=>{}},new URLSearchParams),L=({url:e,prompt:l=!0,pagePath:g}={})=>{let f;if("u"<typeof window)return N;let v=window;if(v[C])return v[C];if(v.ObelismBridge)f=m(v.ObelismBridge,new URLSearchParams(v.location.search));else{let m=(f=((e,r)=>{let l=new URLSearchParams(e.location.search),g=e.parent!==e&&l.has("cms_preview"),m=p(),f=[],v={...c,selecting:!0},y=e=>{v={...v,...e},m.emit("change",v)},x={isEditing:g,mode:g?"editor":null,entryId:l.get("cms_entry"),environment:l.get(n),on:m.on,getState:()=>v,...u,destroy:()=>{f.splice(0).forEach(e=>e())}};if(!g)return x;let w=e.document,k=(t,n={})=>{e.parent.postMessage({source:"obelism-content-bridge",type:t,...n},r)},E=w.createElement("style");E.textContent=d(),w.head.appendChild(E),f.push(()=>E.remove());let S=(e,t)=>{if(w.querySelectorAll("[data-cms-selected]").forEach(e=>{e.removeAttribute("data-cms-selected")}),v.selectedUid!==(e??null)&&y({selectedUid:e??null}),!e)return;let r=w.querySelector(b(e));r&&(r.setAttribute("data-cms-selected",""),t&&r.scrollIntoView({behavior:"smooth",block:"center"}))},C=e=>{let r=e.target,n=r?.closest?.(`[${t}]`);if(!n)return;e.preventDefault(),e.stopPropagation();let o=n.getAttribute(t);S(o,!1),k("select",{uid:o})};w.addEventListener("click",C,!0),f.push(()=>w.removeEventListener("click",C,!0));let N=()=>k("ready",{url:e.location.href}),L=new Map,U=0,P=e=>{let t=L.get(e);return t?(clearTimeout(t.timer),L.delete(e),0===L.size&&y({prompting:!1}),t):null};x.prompt=(e,t={})=>{let r=e.trim();if(!r)return Promise.reject(Error("Say what to change"));if(!v.features.includes("prompt"))return Promise.reject(Error("The editor does not offer prompts here"));let n=`prompt_${++U}`,o=void 0===t.uid?v.selectedUid:t.uid;return new Promise((e,t)=>{let a=setTimeout(()=>{P(n)?.reject(Error("The editor did not answer in time"))},12e4);L.set(n,{resolve:e,reject:t,timer:a}),y({prompting:!0}),k("prompt",{id:n,prompt:r,uid:o})})},x.undo=()=>k("undo"),x.select=e=>{S(e,!1),k("select",{uid:e})};let $=null;f.push(()=>$?.destroy()),f.push(()=>{for(let e of L.keys())P(e)?.reject(Error("The bridge was destroyed"))});let A=t=>{if(t.origin!==r||t.source!==e.parent)return;let n=t.data;if(n&&"obelism-content-editor"===n.source){if("connect"===n.type)N();else if("update"===n.type&&n.entry)m.emit("update",{entry:n.entry,selectedUid:n.selectedUid??null}),e.requestAnimationFrame(()=>S(n.selectedUid,!1)),$?.refresh();else if("select"===n.type)S(n.uid,!0);else if("saved"===n.type||"published"===n.type)m.emit(n.type,{}),0===m.listeners.update.size&&e.location.reload();else if("features"===n.type&&Array.isArray(n.features))y({features:n.features.filter(e=>"string"==typeof e)});else if("stats"===n.type)y({stats:(e=>{if(!e||"object"!=typeof e||Array.isArray(e))return{};let t={};for(let[r,n]of Object.entries(e)){let{clicks:e,clickers:o,viewers:a,rate:i}=n??{};h(e)&&h(o)&&h(a)&&h(i)&&(t[r]={clicks:e,clickers:o,viewers:a,rate:i})}return t})(n.stats),statsVisible:!1!==n.visible}),($||Object.keys(v.stats).length)&&($??($=((e,{selector:t,onSelect:r,labels:n})=>{let l=e.document,d={...o,...n},c=l.createElement("div");c.setAttribute("data-obelism-stats","");let p=c.attachShadow({mode:"open"}),g=l.createElement("style");g.textContent=i;let u=l.createElement("div");u.className="layer",p.append(g,u),l.body.appendChild(c);let h={},b=!1,m=null,f=()=>{if(m=null,u.replaceChildren(),u.hidden=!b,!b)return;let n=e.innerHeight;for(let[o,i]of Object.entries(h)){let c=l.querySelector(t(o));if(!c)continue;let p=c.getBoundingClientRect();if(!p.width&&!p.height||p.bottom<0||p.top>n)continue;let g=l.createElement("button");g.type="button",g.className="badge",g.dataset.uid=o,g.textContent=a(d.clicked,{rate:Math.round(100*i.rate)}),g.title=a(d.title,{clickers:i.clickers,viewers:i.viewers,clicks:i.clicks});let{place:h,top:b,left:m}=s(p,e.innerWidth);g.dataset.place=h,g.style.top=`${b}px`,g.style.left=`${m}px`,g.addEventListener("click",()=>r(o)),u.appendChild(g)}},v=()=>{null===m&&b&&(m=e.requestAnimationFrame(f))};e.addEventListener("scroll",v,{capture:!0,passive:!0}),e.addEventListener("resize",v);let y=e.MutationObserver,x="function"==typeof y?new y(v):null;return x?.observe(l.body,{childList:!0,subtree:!0}),{update(t,r){h=t,b=r,null!==m&&(e.cancelAnimationFrame?.(m),m=null),f()},refresh:v,destroy(){null!==m&&e.cancelAnimationFrame?.(m),e.removeEventListener("scroll",v,{capture:!0}),e.removeEventListener("resize",v),x?.disconnect(),c.remove()}}})(e,{selector:b,onSelect:e=>x.select(e)})),$.update(v.stats,v.statsVisible));else if("promptState"===n.type)y({canUndo:!0===n.canUndo});else if("promptResult"===n.type&&"string"==typeof n.id){let e=P(n.id);if(!e)return;!0===n.ok?e.resolve({summary:"string"==typeof n.summary?n.summary:""}):e.reject(Error("string"==typeof n.error?n.error:"The prompt failed"))}}};return e.addEventListener("message",A),f.push(()=>e.removeEventListener("message",A)),"loading"===w.readyState?(w.addEventListener("DOMContentLoaded",N,{once:!0}),f.push(()=>w.removeEventListener("DOMContentLoaded",N))):N(),x})(v,new URL(e||E).origin)).isEditing||v.parent!==v?null:(e=>{for(let t of e.cookie.split(";")){let[e,...r]=t.trim().split("=");if("obelism_content_editing"===e)try{let e=decodeURIComponent(r.join("="));return e.startsWith("/")&&!e.startsWith("//")?e:null}catch{break}}return null})(v.document);if(m&&(f=((e,n,{pagePath:o=e=>`/${e}`}={})=>{let a=e.document,i=p(),s=[],l=new Map,g=[],u=new Set,h=null,m=c,f=e=>{m={...m,...e},i.emit("change",m)},v=()=>f({unsaved:[...l.values()].some(({content:e,saved:t})=>!S(e,t)),canUndo:g.length>0}),y=async(t,r)=>{let o=await e.fetch(`${n}/api/${t}`,void 0===r?{credentials:"same-origin"}:{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json","x-obelism-content":"1"},body:JSON.stringify(r)}),a=await o.json().catch(()=>({}));if(401===o.status)throw f({features:[]}),Error("You were signed out. Sign in again to edit.");if(!o.ok)throw Error("string"==typeof a.error?a.error:`Obelism Content answered ${o.status}`);return a},x=()=>a.querySelector(`[${r}]`)?.getAttribute(r)??null,w=()=>{if(!h)return;let e=h.space.locales.map(({code:e})=>e),t=a.documentElement.lang;if(e.includes(t))return t;let r=t.split("-")[0]??"";return e.includes(r)?r:h.space.defaultLocale},k=async e=>{let t=l.get(e);if(t)return t;let r=await y(`entries/${encodeURIComponent(e)}`),n={content:r.content,saved:r.content,revision:r.draftRevision};return l.set(e,n),n},E=e=>{a.querySelectorAll("[data-cms-selected]").forEach(e=>{e.removeAttribute("data-cms-selected")}),m.selectedUid!==e&&f({selectedUid:e}),e&&a.querySelector(b(e))?.setAttribute("data-cms-selected","")},C=async t=>{if(0===i.listeners.update.size)return;let r=l.get(t)?.content,{entry:n}=await y(`entries/${encodeURIComponent(t)}/preview`,{content:r,locale:w()});i.emit("update",{entry:n,selectedUid:m.selectedUid}),e.requestAnimationFrame(()=>E(m.selectedUid))},N=t=>{i.emit(t,{}),0===i.listeners.update.size&&e.location.reload()},L=async()=>{for(let[e,t]of l){if(S(t.content,t.saved))continue;let{draftRevision:r}=await y(`entries/${encodeURIComponent(e)}/save`,{content:t.content,baseRevision:t.revision});t.saved=t.content,t.revision=r,u.add(e)}g.length=0,v()},U={isEditing:!0,mode:"site",get entryId(){return x()},environment:null,on:i.on,getState:()=>m,async prompt(e,t={}){let n,o=e.trim();if(!o)throw Error("Say what to change");if(!m.features.includes("prompt"))throw Error("You may not edit content here");let i=void 0===t.uid?m.selectedUid:t.uid,s=(n=i?a.querySelector(b(i)):null,n?.closest(`[${r}]`)?.getAttribute(r)??x());if(!s)throw Error("Nothing on this page is marked as an entry: add editable(entry)");f({prompting:!0});try{let e=await k(s),t=await y(`entries/${encodeURIComponent(s)}/prompt`,{prompt:o,content:e.content,locale:w(),uid:i});return g.push({entryId:s,content:e.content}),e.content=t.content,v(),await C(s),{summary:t.summary}}finally{f({prompting:!1})}},async createPage(t,r){let n=r.trim();if(!n)throw Error("Say what the page is for");if(!t.trim())throw Error("Say where the page goes, like /pricing");if(!m.features.includes("create"))throw Error("You may not add pages here");if(m.unsaved)throw Error("Save or undo your changes before adding a page");f({prompting:!0});try{let r=await y("entries",{path:t,prompt:n,locale:w(),from:x()}),a=o(r.path,w()??"");return e.location.assign(a),{summary:r.summary,entryId:r.entryId,path:r.path,url:a}}finally{f({prompting:!1})}},undo(){let e=g.pop(),t=e&&l.get(e.entryId);e&&t&&(t.content=e.content,v(),C(e.entryId).catch(e=>console.error("[@obelism/content bridge]",e)))},async save(){if(!m.features.includes("save"))throw Error("You may not edit content here");f({saving:!0});try{await L()}finally{f({saving:!1})}N("saved")},async publish(){if(!m.features.includes("publish"))throw Error("You may not publish here");f({publishing:!0});try{await L();let e=x(),t=u.size>0?[...u]:e?[e]:[];if(0===t.length)throw Error("Nothing on this page is marked as an entry: add editable(entry)");for(let e of t)await y(`entries/${encodeURIComponent(e)}/publish`,{}),u.delete(e)}finally{f({publishing:!1})}N("published")},signOut(){if(m.unsaved&&!e.confirm("Sign out and lose the changes you have not saved?"))return;l.clear(),v();let t=e.location.pathname+e.location.search;e.location.href=`${n}/logout?return_to=${encodeURIComponent(t)}`},select(e){E(e)},setSelecting(e){e?a.documentElement.setAttribute("data-cms-selecting",""):a.documentElement.removeAttribute("data-cms-selecting"),f({selecting:e})},destroy(){s.splice(0).forEach(e=>e())}},P=a.createElement("style");P.textContent=d("html[data-cms-selecting] "),a.head.appendChild(P),s.push(()=>P.remove()),s.push(()=>a.documentElement.removeAttribute("data-cms-selecting"));let $=e=>{if(!m.selecting)return;let r=e.target,n=r?.closest?.(`[${t}]`);n&&(e.preventDefault(),e.stopPropagation(),E(n.getAttribute(t)))};a.addEventListener("click",$,!0),s.push(()=>a.removeEventListener("click",$,!0));let A=e=>{m.unsaved&&(e.preventDefault(),e.returnValue="")};return e.addEventListener("beforeunload",A),s.push(()=>e.removeEventListener("beforeunload",A)),y("session").then(e=>{h=e,f({features:e.features})}).catch(e=>console.error("[@obelism/content bridge]",e)),U})(v,m,{pagePath:g})),f.isEditing&&l){let e=k(v.document,f,!0===l?{}:l),t=f.destroy;f.destroy=()=>{e(),t()}}}return v[C]=f,f};(e=window).ObelismBridge=L((e=>{let t=e?.dataset.url,r=t||E;if(!t&&e?.src){let t=new URL(e.src);"/bridge.js"===t.pathname&&(r=t.origin)}return{url:r,prompt:e?.dataset.prompt!=="false"}})(document.currentScript)),e.ObelismContent={getContentBridge:L,mountPromptBox:k}}();
package/dist/bridge.js CHANGED
@@ -1219,7 +1219,7 @@ const createSiteBridge = (win, basePath, { pagePath = (path)=>`/${path}` } = {})
1219
1219
  locale: locale(),
1220
1220
  from: pageEntryId()
1221
1221
  });
1222
- const url = pagePath(created.path);
1222
+ const url = pagePath(created.path, locale() ?? '');
1223
1223
  win.location.assign(url);
1224
1224
  return {
1225
1225
  summary: created.summary,
package/dist/bridge.mjs CHANGED
@@ -1217,7 +1217,7 @@ const createSiteBridge = (win, basePath, { pagePath = (path)=>`/${path}` } = {})
1217
1217
  locale: locale(),
1218
1218
  from: pageEntryId()
1219
1219
  });
1220
- const url = pagePath(created.path);
1220
+ const url = pagePath(created.path, locale() ?? '');
1221
1221
  win.location.assign(url);
1222
1222
  return {
1223
1223
  summary: created.summary,
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 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';
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, d as EntryStub, e as RichTextNode } from './types-fxvuz29w.js';
2
+ export { C as ContentTypeDefinition, f as ContentTypeKind, g as Environment, F as FieldDefinition, h as FieldType, i as RichTextDocument, j as RichTextMark } from './types-fxvuz29w.js';
3
3
 
4
4
  type ContentClientOptions = {
5
5
  token: string;
@@ -26,11 +26,29 @@ type EntriesQuery = RequestOptions & {
26
26
  limit?: number | undefined;
27
27
  skip?: number | undefined;
28
28
  };
29
+ type RedirectsQuery = {
30
+ locale?: string | undefined;
31
+ limit?: number | undefined;
32
+ skip?: number | undefined;
33
+ init?: RequestInit | undefined;
34
+ };
35
+ type PageLookup<TEntry extends Entry = Entry> = {
36
+ entry: TEntry;
37
+ redirect?: undefined;
38
+ } | {
39
+ entry?: undefined;
40
+ redirect: {
41
+ path: string;
42
+ };
43
+ };
29
44
  declare class ContentClient {
30
45
  #private;
31
46
  readonly url: string;
32
47
  constructor({ token, url, locale, depth, richText, timeout, init, fetch: fetchImpl, }: ContentClientOptions);
33
48
  getPage: <TEntry extends Entry = Entry>(path: string, options?: RequestOptions) => Promise<TEntry | null>;
49
+ findPage: <TEntry extends Entry = Entry>(path: string, options?: RequestOptions) => Promise<PageLookup<TEntry> | null>;
50
+ getRedirects: ({ locale, limit, skip, init, }?: RedirectsQuery) => Promise<RedirectList>;
51
+ getAllRedirects(query?: Omit<RedirectsQuery, 'skip'>): AsyncGenerator<Redirect, void, undefined>;
34
52
  getEntry: <TEntry extends Entry = Entry>(id: string, options?: RequestOptions) => Promise<TEntry | null>;
35
53
  getEntries: <TEntry extends Entry = Entry>({ contentType, parentPath, fields, order, limit, skip, ...options }?: EntriesQuery) => Promise<EntryList<TEntry>>;
36
54
  getFirstEntry: <TEntry extends Entry = Entry>(query?: Omit<EntriesQuery, "limit" | "skip">) => Promise<TEntry | null>;
@@ -66,9 +84,13 @@ type ImageTransform = {
66
84
  fit?: 'scale-down' | 'contain' | 'cover' | 'crop' | 'pad' | undefined;
67
85
  format?: 'auto' | 'avif' | 'webp' | 'jpeg' | 'png' | undefined;
68
86
  q?: number | undefined;
87
+ focus?: {
88
+ x: number;
89
+ y: number;
90
+ } | 'auto' | undefined;
69
91
  };
70
- declare const imageUrl: (asset: Pick<Asset, "url"> | string, transform?: ImageTransform) => string;
71
- declare const imageSrcSet: (asset: Pick<Asset, "url"> | string, widths: number[], transform?: Omit<ImageTransform, "w">) => string;
92
+ declare const imageUrl: (asset: (Pick<Asset, "url"> & Partial<Pick<Asset, "focalPoint">>) | string, transform?: ImageTransform) => string;
93
+ declare const imageSrcSet: (asset: (Pick<Asset, "url"> & Partial<Pick<Asset, "focalPoint">>) | string, widths: number[], transform?: Omit<ImageTransform, "w">) => string;
72
94
 
73
95
  declare const IMPROVE_BLOCK = "_improve";
74
96
  declare const isImproveBlock: (block: Block) => block is ImproveBlock;
@@ -98,6 +120,21 @@ declare const richTextToHtml: (doc: RichTextNode | null | undefined, { renderEnt
98
120
  declare const headingLevel: (node: RichTextNode) => 1 | 2 | 3 | 4 | 5 | 6;
99
121
  declare const richTextToPlainText: (doc: RichTextNode | null | undefined) => string;
100
122
 
101
- 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 };
102
- export type { ContentClientOptions, EntriesQuery, ImageTransform, PreviewParams, RequestOptions, RichTextHtmlOptions };
123
+ type LocalizedUrlOptions = {
124
+ url: (path: string, locale: string) => string;
125
+ defaultLocale?: string | undefined;
126
+ };
127
+ type AlternateLink = {
128
+ hreflang: string;
129
+ href: string;
130
+ };
131
+ type Page = Pick<Entry, 'paths'> & Partial<Pick<Entry, 'updatedAt' | 'fields'>>;
132
+ declare const alternateLinks: (entry: Pick<Entry, "paths">, { url, defaultLocale }: LocalizedUrlOptions) => AlternateLink[];
133
+ type SitemapOptions = LocalizedUrlOptions & {
134
+ exclude?: ((entry: Page) => boolean) | undefined;
135
+ };
136
+ declare const sitemapXml: (entries: Iterable<Page>, { exclude, ...options }: SitemapOptions) => string;
137
+
138
+ export { Asset, Block, ContentApiError, ContentClient, DEFAULT_URL, ENTRY_ATTRIBUTE, Entry, EntryList, EntryStub, IMPROVE_BLOCK, ImproveBlock, ModelFile, PREVIEW_PARAMS, Redirect, RedirectList, RichTextFormat, RichTextNode, SAFE_HREF, UID_ATTRIBUTE, alternateLinks, editable, escapeHtml, headingLevel, imageSrcSet, imageUrl, isImproveBlock, pickImproveVariant, readPreviewParams, richTextToHtml, richTextToPlainText, sitemapXml };
139
+ export type { AlternateLink, ContentClientOptions, EntriesQuery, ImageTransform, LocalizedUrlOptions, PageLookup, PreviewParams, RedirectsQuery, RequestOptions, RichTextHtmlOptions, SitemapOptions };
103
140
  //# 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/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,gBAAgB;AACrB,KAAK,YAAY,GAAG,cAAc;AAClC;AACA;AACA,aAAa,MAAM,SAAS,gBAAgB,YAAY,gBAAgB;AACxE;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,wEAAwE,YAAY,KAAK,OAAO,CAAC,SAAS;AACvJ,mCAAmC,KAAK,GAAG,KAAK,UAAU,IAAI,CAAC,YAAY,wBAAwB,OAAO;AAC1G,iCAAiC,KAAK,GAAG,KAAK,UAAU,IAAI,CAAC,YAAY,YAAY,cAAc;AACnG,gCAAgC,IAAI,CAAC,cAAc,cAAc,OAAO,CAAC,SAAS;AAClF;;ACpCA,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;AACA,KAAK,aAAa;AAClB;AACA;AACA;AACA;AACA,KAAK,iBAAiB,GAAG,GAAG,GAAG,eAAe,YAAY,MAAM;AAChE,cAAc,iBAAiB,UAAU,iBAAiB,KAAK,aAAa;;ACX5E,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","../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 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';
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, d as EntryStub, e as RichTextNode } from './types-fxvuz29w.js';
2
+ export { C as ContentTypeDefinition, f as ContentTypeKind, g as Environment, F as FieldDefinition, h as FieldType, i as RichTextDocument, j as RichTextMark } from './types-fxvuz29w.js';
3
3
 
4
4
  type ContentClientOptions = {
5
5
  token: string;
@@ -26,11 +26,29 @@ type EntriesQuery = RequestOptions & {
26
26
  limit?: number | undefined;
27
27
  skip?: number | undefined;
28
28
  };
29
+ type RedirectsQuery = {
30
+ locale?: string | undefined;
31
+ limit?: number | undefined;
32
+ skip?: number | undefined;
33
+ init?: RequestInit | undefined;
34
+ };
35
+ type PageLookup<TEntry extends Entry = Entry> = {
36
+ entry: TEntry;
37
+ redirect?: undefined;
38
+ } | {
39
+ entry?: undefined;
40
+ redirect: {
41
+ path: string;
42
+ };
43
+ };
29
44
  declare class ContentClient {
30
45
  #private;
31
46
  readonly url: string;
32
47
  constructor({ token, url, locale, depth, richText, timeout, init, fetch: fetchImpl, }: ContentClientOptions);
33
48
  getPage: <TEntry extends Entry = Entry>(path: string, options?: RequestOptions) => Promise<TEntry | null>;
49
+ findPage: <TEntry extends Entry = Entry>(path: string, options?: RequestOptions) => Promise<PageLookup<TEntry> | null>;
50
+ getRedirects: ({ locale, limit, skip, init, }?: RedirectsQuery) => Promise<RedirectList>;
51
+ getAllRedirects(query?: Omit<RedirectsQuery, 'skip'>): AsyncGenerator<Redirect, void, undefined>;
34
52
  getEntry: <TEntry extends Entry = Entry>(id: string, options?: RequestOptions) => Promise<TEntry | null>;
35
53
  getEntries: <TEntry extends Entry = Entry>({ contentType, parentPath, fields, order, limit, skip, ...options }?: EntriesQuery) => Promise<EntryList<TEntry>>;
36
54
  getFirstEntry: <TEntry extends Entry = Entry>(query?: Omit<EntriesQuery, "limit" | "skip">) => Promise<TEntry | null>;
@@ -66,9 +84,13 @@ type ImageTransform = {
66
84
  fit?: 'scale-down' | 'contain' | 'cover' | 'crop' | 'pad' | undefined;
67
85
  format?: 'auto' | 'avif' | 'webp' | 'jpeg' | 'png' | undefined;
68
86
  q?: number | undefined;
87
+ focus?: {
88
+ x: number;
89
+ y: number;
90
+ } | 'auto' | undefined;
69
91
  };
70
- declare const imageUrl: (asset: Pick<Asset, "url"> | string, transform?: ImageTransform) => string;
71
- declare const imageSrcSet: (asset: Pick<Asset, "url"> | string, widths: number[], transform?: Omit<ImageTransform, "w">) => string;
92
+ declare const imageUrl: (asset: (Pick<Asset, "url"> & Partial<Pick<Asset, "focalPoint">>) | string, transform?: ImageTransform) => string;
93
+ declare const imageSrcSet: (asset: (Pick<Asset, "url"> & Partial<Pick<Asset, "focalPoint">>) | string, widths: number[], transform?: Omit<ImageTransform, "w">) => string;
72
94
 
73
95
  declare const IMPROVE_BLOCK = "_improve";
74
96
  declare const isImproveBlock: (block: Block) => block is ImproveBlock;
@@ -98,6 +120,21 @@ declare const richTextToHtml: (doc: RichTextNode | null | undefined, { renderEnt
98
120
  declare const headingLevel: (node: RichTextNode) => 1 | 2 | 3 | 4 | 5 | 6;
99
121
  declare const richTextToPlainText: (doc: RichTextNode | null | undefined) => string;
100
122
 
101
- 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 };
102
- export type { ContentClientOptions, EntriesQuery, ImageTransform, PreviewParams, RequestOptions, RichTextHtmlOptions };
123
+ type LocalizedUrlOptions = {
124
+ url: (path: string, locale: string) => string;
125
+ defaultLocale?: string | undefined;
126
+ };
127
+ type AlternateLink = {
128
+ hreflang: string;
129
+ href: string;
130
+ };
131
+ type Page = Pick<Entry, 'paths'> & Partial<Pick<Entry, 'updatedAt' | 'fields'>>;
132
+ declare const alternateLinks: (entry: Pick<Entry, "paths">, { url, defaultLocale }: LocalizedUrlOptions) => AlternateLink[];
133
+ type SitemapOptions = LocalizedUrlOptions & {
134
+ exclude?: ((entry: Page) => boolean) | undefined;
135
+ };
136
+ declare const sitemapXml: (entries: Iterable<Page>, { exclude, ...options }: SitemapOptions) => string;
137
+
138
+ export { Asset, Block, ContentApiError, ContentClient, DEFAULT_URL, ENTRY_ATTRIBUTE, Entry, EntryList, EntryStub, IMPROVE_BLOCK, ImproveBlock, ModelFile, PREVIEW_PARAMS, Redirect, RedirectList, RichTextFormat, RichTextNode, SAFE_HREF, UID_ATTRIBUTE, alternateLinks, editable, escapeHtml, headingLevel, imageSrcSet, imageUrl, isImproveBlock, pickImproveVariant, readPreviewParams, richTextToHtml, richTextToPlainText, sitemapXml };
139
+ export type { AlternateLink, ContentClientOptions, EntriesQuery, ImageTransform, LocalizedUrlOptions, PageLookup, PreviewParams, RedirectsQuery, RequestOptions, RichTextHtmlOptions, SitemapOptions };
103
140
  //# 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/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,gBAAgB;AACrB,KAAK,YAAY,GAAG,cAAc;AAClC;AACA;AACA,aAAa,MAAM,SAAS,gBAAgB,YAAY,gBAAgB;AACxE;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,wEAAwE,YAAY,KAAK,OAAO,CAAC,SAAS;AACvJ,mCAAmC,KAAK,GAAG,KAAK,UAAU,IAAI,CAAC,YAAY,wBAAwB,OAAO;AAC1G,iCAAiC,KAAK,GAAG,KAAK,UAAU,IAAI,CAAC,YAAY,YAAY,cAAc;AACnG,gCAAgC,IAAI,CAAC,cAAc,cAAc,OAAO,CAAC,SAAS;AAClF;;ACpCA,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;AACA,KAAK,aAAa;AAClB;AACA;AACA;AACA;AACA,KAAK,iBAAiB,GAAG,GAAG,GAAG,eAAe,YAAY,MAAM;AAChE,cAAc,iBAAiB,UAAU,iBAAiB,KAAK,aAAa;;ACX5E,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","../src/sitemap.ts"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -42,17 +42,45 @@ class ContentClient {
42
42
  #fetch;
43
43
  constructor({ token, url, locale, depth, richText, timeout, init, fetch: fetchImpl }){
44
44
  // The page at a path (`blog/hello`), or null when there is none. A site's
45
- // catch-all route asks for this.
46
- this.getPage = async (path, options = {})=>{
45
+ // catch-all route asks for this. The path is looked up in `locale`'s
46
+ // paths (the default locale's when left out or `*`): pages can have a
47
+ // slug per locale.
48
+ this.getPage = async (path, options = {})=>(await this.findPage(path, options))?.entry ?? null;
49
+ // Like getPage, but a path a page moved away from answers where it is
50
+ // now, so the site can send a 301 there instead of a 404.
51
+ //
52
+ // const found = await content.findPage(path, { locale })
53
+ // if (found?.redirect) return redirect(`/${locale}/${found.redirect.path}`, 301)
54
+ // if (!found) return notFound()
55
+ this.findPage = async (path, options = {})=>{
47
56
  const trimmed = trimSlashes(path);
48
57
  if (!trimmed) throw new Error('getPage needs a path');
49
58
  const result = await this.#request('/pages', {
50
59
  path: trimmed
51
60
  }, options, {
52
- notFound: true
61
+ notFound: 'body'
53
62
  });
54
- return result?.entry ?? null;
63
+ if (result && 'entry' in result) return {
64
+ entry: result.entry
65
+ };
66
+ const to = result?.redirect?.path;
67
+ return typeof to === 'string' ? {
68
+ redirect: {
69
+ path: to
70
+ }
71
+ } : null;
55
72
  };
73
+ // One page of redirects: every path a page moved away from, per locale,
74
+ // with where it is now. For redirect rules built ahead of time.
75
+ this.getRedirects = ({ locale, limit, skip, init } = {})=>this.#request('/redirects', {
76
+ limit,
77
+ skip
78
+ }, {
79
+ locale,
80
+ init
81
+ }, {
82
+ defaults: false
83
+ });
56
84
  // An entry by id, or null when there is none (or it isn't published, for a
57
85
  // delivery token).
58
86
  this.getEntry = async (id, options = {})=>{
@@ -93,6 +121,21 @@ class ContentClient {
93
121
  this.#init = init;
94
122
  this.#fetch = fetchImpl ?? ((...args)=>fetch(...args));
95
123
  }
124
+ // Every redirect, fetching pages of `limit` (default 1000) as it goes.
125
+ async *getAllRedirects(query = {}) {
126
+ const limit = query.limit ?? 1000;
127
+ let skip = 0;
128
+ while(true){
129
+ const page = await this.getRedirects({
130
+ ...query,
131
+ limit,
132
+ skip
133
+ });
134
+ yield* page.items;
135
+ skip += page.items.length;
136
+ if (page.items.length === 0 || skip >= page.total) return;
137
+ }
138
+ }
96
139
  // Every entry matching the query, fetching pages of `limit` (default 100)
97
140
  // as it goes.
98
141
  //
@@ -111,7 +154,8 @@ class ContentClient {
111
154
  if (page.items.length === 0 || skip >= page.total) return;
112
155
  }
113
156
  }
114
- // null for a 404 when `notFound` is set; throws ContentApiError otherwise.
157
+ // For a 404, null when `notFound` is set, or the body as is for 'body';
158
+ // throws ContentApiError otherwise.
115
159
  async #request(path, query, { init, ...options }, { notFound = false, defaults = true } = {}) {
116
160
  // Checked here rather than in the constructor, so a module creating a
117
161
  // client still loads (and a build still runs) without the variable.
@@ -149,7 +193,7 @@ class ContentClient {
149
193
  timeout
150
194
  ]) : timeout
151
195
  });
152
- if (notFound && response.status === 404) return null;
196
+ if (notFound === true && response.status === 404) return null;
153
197
  const text = await response.text();
154
198
  let body = text;
155
199
  try {
@@ -157,6 +201,9 @@ class ContentClient {
157
201
  } catch {
158
202
  // Not JSON: keep the text for the error.
159
203
  }
204
+ if (notFound === 'body' && response.status === 404) {
205
+ return typeof body === 'object' && body !== null ? body : null;
206
+ }
160
207
  if (!response.ok) throw new ContentApiError(response.status, url, body);
161
208
  return body;
162
209
  }
@@ -171,16 +218,25 @@ const KEYS = [
171
218
  'format',
172
219
  'q'
173
220
  ];
221
+ const CROPS = new Set([
222
+ 'cover',
223
+ 'crop'
224
+ ]);
174
225
  // An asset's URL with a resize / re-encode query. PNG, JPEG, GIF, WebP and
175
226
  // AVIF are transformed; other files ignore the query.
176
227
  //
177
228
  // imageUrl(asset, { w: 800 })
229
+ // imageUrl(asset, { w: 800, h: 800, fit: 'cover' }) // around its focal point
178
230
  const imageUrl = (asset, transform = {})=>{
179
231
  const url = new URL(typeof asset === 'string' ? asset : asset.url);
180
232
  for (const key of KEYS){
181
233
  const value = transform[key];
182
234
  if (value !== undefined) url.searchParams.set(key, String(value));
183
235
  }
236
+ const focus = transform.focus ?? (typeof asset === 'string' ? undefined : asset.focalPoint ?? undefined);
237
+ if (focus && CROPS.has(transform.fit ?? '')) {
238
+ url.searchParams.set('focus', focus === 'auto' ? 'auto' : `${focus.x},${focus.y}`);
239
+ }
184
240
  return url.toString();
185
241
  };
186
242
  // A `srcset` with one candidate per width.
@@ -319,6 +375,53 @@ const richTextToPlainText = (doc)=>{
319
375
  return parts.join('').replace(/\n+/g, '\n').trim();
320
376
  };
321
377
 
378
+ // Every locale's URL of a page, for `<link rel="alternate" hreflang>` tags
379
+ // and language switchers. Empty for data entries.
380
+ const alternateLinks = (entry, { url, defaultLocale })=>{
381
+ const links = Object.entries(entry.paths).map(([locale, path])=>({
382
+ hreflang: locale,
383
+ href: url(path, locale)
384
+ }));
385
+ const fallback = defaultLocale ? entry.paths[defaultLocale] : undefined;
386
+ if (defaultLocale && fallback !== undefined) {
387
+ links.push({
388
+ hreflang: 'x-default',
389
+ href: url(fallback, defaultLocale)
390
+ });
391
+ }
392
+ return links;
393
+ };
394
+ const escapeXml = (text)=>text.replace(/[<>&'"]/g, (char)=>({
395
+ '<': '&lt;',
396
+ '>': '&gt;',
397
+ '&': '&amp;',
398
+ "'": '&apos;',
399
+ '"': '&quot;'
400
+ })[char]);
401
+ const hidden = (entry)=>entry.fields?.noIndex === true;
402
+ // A sitemap.xml with one `<url>` per page and locale, each listing the page
403
+ // in every locale (`xhtml:link`), and `lastmod` from `updatedAt`. Pages
404
+ // without paths (data entries) are skipped. Fetch the entries with a
405
+ // delivery token: drafts don't belong in a sitemap.
406
+ const sitemapXml = (entries, { exclude = hidden, ...options })=>{
407
+ const urls = [];
408
+ for (const entry of entries){
409
+ if (exclude(entry)) continue;
410
+ const links = alternateLinks(entry, options);
411
+ const alternates = links.map(({ hreflang, href })=>`<xhtml:link rel="alternate" hreflang="${escapeXml(hreflang)}" href="${escapeXml(href)}"/>`).join('');
412
+ for (const { hreflang, href } of links){
413
+ if (hreflang === 'x-default') continue;
414
+ urls.push(`<url><loc>${escapeXml(href)}</loc>${entry.updatedAt ? `<lastmod>${escapeXml(entry.updatedAt)}</lastmod>` : ''}${links.length > 1 ? alternates : ''}</url>`);
415
+ }
416
+ }
417
+ return [
418
+ '<?xml version="1.0" encoding="UTF-8"?>',
419
+ '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">',
420
+ ...urls,
421
+ '</urlset>'
422
+ ].join('\n');
423
+ };
424
+
322
425
  exports.DEFAULT_URL = config.DEFAULT_URL;
323
426
  exports.ENTRY_ATTRIBUTE = preview.ENTRY_ATTRIBUTE;
324
427
  exports.PREVIEW_PARAMS = preview.PREVIEW_PARAMS;
@@ -329,6 +432,7 @@ exports.ContentApiError = ContentApiError;
329
432
  exports.ContentClient = ContentClient;
330
433
  exports.IMPROVE_BLOCK = IMPROVE_BLOCK;
331
434
  exports.SAFE_HREF = SAFE_HREF;
435
+ exports.alternateLinks = alternateLinks;
332
436
  exports.escapeHtml = escapeHtml;
333
437
  exports.headingLevel = headingLevel;
334
438
  exports.imageSrcSet = imageSrcSet;
@@ -337,3 +441,4 @@ exports.isImproveBlock = isImproveBlock;
337
441
  exports.pickImproveVariant = pickImproveVariant;
338
442
  exports.richTextToHtml = richTextToHtml;
339
443
  exports.richTextToPlainText = richTextToPlainText;
444
+ exports.sitemapXml = sitemapXml;
package/dist/index.mjs CHANGED
@@ -40,17 +40,45 @@ class ContentClient {
40
40
  #fetch;
41
41
  constructor({ token, url, locale, depth, richText, timeout, init, fetch: fetchImpl }){
42
42
  // The page at a path (`blog/hello`), or null when there is none. A site's
43
- // catch-all route asks for this.
44
- this.getPage = async (path, options = {})=>{
43
+ // catch-all route asks for this. The path is looked up in `locale`'s
44
+ // paths (the default locale's when left out or `*`): pages can have a
45
+ // slug per locale.
46
+ this.getPage = async (path, options = {})=>(await this.findPage(path, options))?.entry ?? null;
47
+ // Like getPage, but a path a page moved away from answers where it is
48
+ // now, so the site can send a 301 there instead of a 404.
49
+ //
50
+ // const found = await content.findPage(path, { locale })
51
+ // if (found?.redirect) return redirect(`/${locale}/${found.redirect.path}`, 301)
52
+ // if (!found) return notFound()
53
+ this.findPage = async (path, options = {})=>{
45
54
  const trimmed = trimSlashes(path);
46
55
  if (!trimmed) throw new Error('getPage needs a path');
47
56
  const result = await this.#request('/pages', {
48
57
  path: trimmed
49
58
  }, options, {
50
- notFound: true
59
+ notFound: 'body'
51
60
  });
52
- return result?.entry ?? null;
61
+ if (result && 'entry' in result) return {
62
+ entry: result.entry
63
+ };
64
+ const to = result?.redirect?.path;
65
+ return typeof to === 'string' ? {
66
+ redirect: {
67
+ path: to
68
+ }
69
+ } : null;
53
70
  };
71
+ // One page of redirects: every path a page moved away from, per locale,
72
+ // with where it is now. For redirect rules built ahead of time.
73
+ this.getRedirects = ({ locale, limit, skip, init } = {})=>this.#request('/redirects', {
74
+ limit,
75
+ skip
76
+ }, {
77
+ locale,
78
+ init
79
+ }, {
80
+ defaults: false
81
+ });
54
82
  // An entry by id, or null when there is none (or it isn't published, for a
55
83
  // delivery token).
56
84
  this.getEntry = async (id, options = {})=>{
@@ -91,6 +119,21 @@ class ContentClient {
91
119
  this.#init = init;
92
120
  this.#fetch = fetchImpl ?? ((...args)=>fetch(...args));
93
121
  }
122
+ // Every redirect, fetching pages of `limit` (default 1000) as it goes.
123
+ async *getAllRedirects(query = {}) {
124
+ const limit = query.limit ?? 1000;
125
+ let skip = 0;
126
+ while(true){
127
+ const page = await this.getRedirects({
128
+ ...query,
129
+ limit,
130
+ skip
131
+ });
132
+ yield* page.items;
133
+ skip += page.items.length;
134
+ if (page.items.length === 0 || skip >= page.total) return;
135
+ }
136
+ }
94
137
  // Every entry matching the query, fetching pages of `limit` (default 100)
95
138
  // as it goes.
96
139
  //
@@ -109,7 +152,8 @@ class ContentClient {
109
152
  if (page.items.length === 0 || skip >= page.total) return;
110
153
  }
111
154
  }
112
- // null for a 404 when `notFound` is set; throws ContentApiError otherwise.
155
+ // For a 404, null when `notFound` is set, or the body as is for 'body';
156
+ // throws ContentApiError otherwise.
113
157
  async #request(path, query, { init, ...options }, { notFound = false, defaults = true } = {}) {
114
158
  // Checked here rather than in the constructor, so a module creating a
115
159
  // client still loads (and a build still runs) without the variable.
@@ -147,7 +191,7 @@ class ContentClient {
147
191
  timeout
148
192
  ]) : timeout
149
193
  });
150
- if (notFound && response.status === 404) return null;
194
+ if (notFound === true && response.status === 404) return null;
151
195
  const text = await response.text();
152
196
  let body = text;
153
197
  try {
@@ -155,6 +199,9 @@ class ContentClient {
155
199
  } catch {
156
200
  // Not JSON: keep the text for the error.
157
201
  }
202
+ if (notFound === 'body' && response.status === 404) {
203
+ return typeof body === 'object' && body !== null ? body : null;
204
+ }
158
205
  if (!response.ok) throw new ContentApiError(response.status, url, body);
159
206
  return body;
160
207
  }
@@ -169,16 +216,25 @@ const KEYS = [
169
216
  'format',
170
217
  'q'
171
218
  ];
219
+ const CROPS = new Set([
220
+ 'cover',
221
+ 'crop'
222
+ ]);
172
223
  // An asset's URL with a resize / re-encode query. PNG, JPEG, GIF, WebP and
173
224
  // AVIF are transformed; other files ignore the query.
174
225
  //
175
226
  // imageUrl(asset, { w: 800 })
227
+ // imageUrl(asset, { w: 800, h: 800, fit: 'cover' }) // around its focal point
176
228
  const imageUrl = (asset, transform = {})=>{
177
229
  const url = new URL(typeof asset === 'string' ? asset : asset.url);
178
230
  for (const key of KEYS){
179
231
  const value = transform[key];
180
232
  if (value !== undefined) url.searchParams.set(key, String(value));
181
233
  }
234
+ const focus = transform.focus ?? (typeof asset === 'string' ? undefined : asset.focalPoint ?? undefined);
235
+ if (focus && CROPS.has(transform.fit ?? '')) {
236
+ url.searchParams.set('focus', focus === 'auto' ? 'auto' : `${focus.x},${focus.y}`);
237
+ }
182
238
  return url.toString();
183
239
  };
184
240
  // A `srcset` with one candidate per width.
@@ -317,4 +373,51 @@ const richTextToPlainText = (doc)=>{
317
373
  return parts.join('').replace(/\n+/g, '\n').trim();
318
374
  };
319
375
 
320
- export { ContentApiError, ContentClient, DEFAULT_URL, IMPROVE_BLOCK, SAFE_HREF, escapeHtml, headingLevel, imageSrcSet, imageUrl, isImproveBlock, pickImproveVariant, richTextToHtml, richTextToPlainText };
376
+ // Every locale's URL of a page, for `<link rel="alternate" hreflang>` tags
377
+ // and language switchers. Empty for data entries.
378
+ const alternateLinks = (entry, { url, defaultLocale })=>{
379
+ const links = Object.entries(entry.paths).map(([locale, path])=>({
380
+ hreflang: locale,
381
+ href: url(path, locale)
382
+ }));
383
+ const fallback = defaultLocale ? entry.paths[defaultLocale] : undefined;
384
+ if (defaultLocale && fallback !== undefined) {
385
+ links.push({
386
+ hreflang: 'x-default',
387
+ href: url(fallback, defaultLocale)
388
+ });
389
+ }
390
+ return links;
391
+ };
392
+ const escapeXml = (text)=>text.replace(/[<>&'"]/g, (char)=>({
393
+ '<': '&lt;',
394
+ '>': '&gt;',
395
+ '&': '&amp;',
396
+ "'": '&apos;',
397
+ '"': '&quot;'
398
+ })[char]);
399
+ const hidden = (entry)=>entry.fields?.noIndex === true;
400
+ // A sitemap.xml with one `<url>` per page and locale, each listing the page
401
+ // in every locale (`xhtml:link`), and `lastmod` from `updatedAt`. Pages
402
+ // without paths (data entries) are skipped. Fetch the entries with a
403
+ // delivery token: drafts don't belong in a sitemap.
404
+ const sitemapXml = (entries, { exclude = hidden, ...options })=>{
405
+ const urls = [];
406
+ for (const entry of entries){
407
+ if (exclude(entry)) continue;
408
+ const links = alternateLinks(entry, options);
409
+ const alternates = links.map(({ hreflang, href })=>`<xhtml:link rel="alternate" hreflang="${escapeXml(hreflang)}" href="${escapeXml(href)}"/>`).join('');
410
+ for (const { hreflang, href } of links){
411
+ if (hreflang === 'x-default') continue;
412
+ urls.push(`<url><loc>${escapeXml(href)}</loc>${entry.updatedAt ? `<lastmod>${escapeXml(entry.updatedAt)}</lastmod>` : ''}${links.length > 1 ? alternates : ''}</url>`);
413
+ }
414
+ }
415
+ return [
416
+ '<?xml version="1.0" encoding="UTF-8"?>',
417
+ '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">',
418
+ ...urls,
419
+ '</urlset>'
420
+ ].join('\n');
421
+ };
422
+
423
+ export { ContentApiError, ContentClient, DEFAULT_URL, IMPROVE_BLOCK, SAFE_HREF, alternateLinks, escapeHtml, headingLevel, imageSrcSet, imageUrl, isImproveBlock, pickImproveVariant, richTextToHtml, richTextToPlainText, sitemapXml };
@@ -10,6 +10,10 @@ type Asset = {
10
10
  height: number | null;
11
11
  title: string | Record<string, string> | null;
12
12
  alt: string | Record<string, string> | null;
13
+ focalPoint: {
14
+ x: number;
15
+ y: number;
16
+ } | null;
13
17
  };
14
18
  type Entry<TFields extends Record<string, unknown> = Record<string, unknown>, TContentType extends string = string> = {
15
19
  id: string;
@@ -17,6 +21,7 @@ type Entry<TFields extends Record<string, unknown> = Record<string, unknown>, TC
17
21
  name: string;
18
22
  slug: string | null;
19
23
  path: string | null;
24
+ paths: Record<string, string>;
20
25
  publishedAt: string | null;
21
26
  firstPublishedAt: string | null;
22
27
  updatedAt: string;
@@ -55,6 +60,17 @@ type EntryList<TEntry extends Entry = Entry> = {
55
60
  limit: number;
56
61
  skip: number;
57
62
  };
63
+ type Redirect = {
64
+ locale: string;
65
+ from: string;
66
+ to: string;
67
+ };
68
+ type RedirectList = {
69
+ items: Redirect[];
70
+ total: number;
71
+ limit: number;
72
+ skip: number;
73
+ };
58
74
  type ContentTypeKind = 'page' | 'data' | 'block';
59
75
  type BaseField = {
60
76
  apiId: string;
@@ -121,5 +137,5 @@ type ModelFile = {
121
137
  contentTypes: ContentTypeDefinition[];
122
138
  };
123
139
 
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
140
+ 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, RedirectList as a, Redirect as b, EntryList as c, EntryStub as d, RichTextNode as e, ContentTypeKind as f, Environment as g, FieldType as h, RichTextDocument as i, RichTextMark as j };
141
+ //# sourceMappingURL=types-fxvuz29w.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-fxvuz29w.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;AACA;AACA;AACA;AACA,KAAK,KAAK,iBAAiB,MAAM,oBAAoB,MAAM;AAC3D;AACA;AACA;AACA;AACA;AACA,WAAW,MAAM;AACjB;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,QAAQ;AACb;AACA;AACA;AACA;AACA,KAAK,YAAY;AACjB,WAAW,QAAQ;AACnB;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":[]}
@@ -5,6 +5,7 @@ type WebhookEntry = {
5
5
  contentType: string;
6
6
  name: string;
7
7
  path: string | null;
8
+ paths: Record<string, string>;
8
9
  };
9
10
  type WebhookEventOf<TType extends string, TData> = {
10
11
  type: TType;
@@ -25,6 +26,7 @@ type EntryDeletedEvent = WebhookEventOf<'entry.deleted', {
25
26
  type EntryMovedEvent = WebhookEventOf<'entry.moved', {
26
27
  entry: WebhookEntry;
27
28
  previousPath: string | null;
29
+ previousPaths: Record<string, string>;
28
30
  }>;
29
31
  type AssetUpdatedEvent = WebhookEventOf<'asset.updated', {
30
32
  asset: {
@@ -1 +1 @@
1
- {"version":3,"file":"webhooks.d.mts","sources":["../src/webhooks.ts"],"mappings":";;AACA,KAAK,YAAY;AACjB;AACA;AACA;AACA;AACA;AACA,KAAK,cAAc;AACnB;AACA;AACA;AACA,iBAAiB,WAAW;AAC5B;AACA;AACA,KAAK,mBAAmB,GAAG,cAAc;AACzC,WAAW,YAAY;AACvB;AACA,KAAK,qBAAqB,GAAG,cAAc;AAC3C,WAAW,YAAY;AACvB;AACA,KAAK,iBAAiB,GAAG,cAAc;AACvC,WAAW,YAAY;AACvB;AACA,KAAK,eAAe,GAAG,cAAc;AACrC,WAAW,YAAY;AACvB;AACA;AACA,KAAK,iBAAiB,GAAG,cAAc;AACvC;AACA;AACA;AACA;AACA,aAAa,YAAY;AACzB;AACA,KAAK,iBAAiB,GAAG,cAAc;AACvC;AACA;AACA;AACA;AACA;AACA,KAAK,mBAAmB,GAAG,cAAc;AACzC;AACA;AACA;AACA,KAAK,gBAAgB,GAAG,cAAc,iBAAiB,MAAM;AAC7D,KAAK,YAAY,GAAG,mBAAmB,GAAG,qBAAqB,GAAG,iBAAiB,GAAG,eAAe,GAAG,iBAAiB,GAAG,iBAAiB,GAAG,mBAAmB,GAAG,gBAAgB;AACtL,KAAK,gBAAgB,GAAG,YAAY;AACpC,cAAc,wBAAwB,SAAS,KAAK;AACpD;AACA;AACA,KAAK,cAAc;AACnB;AACA,aAAa,OAAO,GAAG,MAAM,0CAA0C,QAAQ;AAC/E;AACA,KAAK,oBAAoB;AACzB;AACA;AACA;AACA,cAAc,aAAa,YAAY,OAAO,GAAG,cAAc,wCAAwC,oBAAoB,KAAK,OAAO,CAAC,YAAY;AACpJ,KAAK,qBAAqB;AAC1B;AACA,qBAAqB,YAAY;AACjC;AACA;AACA;AACA;AACA,cAAc,oBAAoB,mCAAmC,qBAAqB,eAAe,OAAO,KAAK,OAAO,CAAC,QAAQ","names":[]}
1
+ {"version":3,"file":"webhooks.d.mts","sources":["../src/webhooks.ts"],"mappings":";;AACA,KAAK,YAAY;AACjB;AACA;AACA;AACA;AACA,WAAW,MAAM;AACjB;AACA,KAAK,cAAc;AACnB;AACA;AACA;AACA,iBAAiB,WAAW;AAC5B;AACA;AACA,KAAK,mBAAmB,GAAG,cAAc;AACzC,WAAW,YAAY;AACvB;AACA,KAAK,qBAAqB,GAAG,cAAc;AAC3C,WAAW,YAAY;AACvB;AACA,KAAK,iBAAiB,GAAG,cAAc;AACvC,WAAW,YAAY;AACvB;AACA,KAAK,eAAe,GAAG,cAAc;AACrC,WAAW,YAAY;AACvB;AACA,mBAAmB,MAAM;AACzB;AACA,KAAK,iBAAiB,GAAG,cAAc;AACvC;AACA;AACA;AACA;AACA,aAAa,YAAY;AACzB;AACA,KAAK,iBAAiB,GAAG,cAAc;AACvC;AACA;AACA;AACA;AACA;AACA,KAAK,mBAAmB,GAAG,cAAc;AACzC;AACA;AACA;AACA,KAAK,gBAAgB,GAAG,cAAc,iBAAiB,MAAM;AAC7D,KAAK,YAAY,GAAG,mBAAmB,GAAG,qBAAqB,GAAG,iBAAiB,GAAG,eAAe,GAAG,iBAAiB,GAAG,iBAAiB,GAAG,mBAAmB,GAAG,gBAAgB;AACtL,KAAK,gBAAgB,GAAG,YAAY;AACpC,cAAc,wBAAwB,SAAS,KAAK;AACpD;AACA;AACA,KAAK,cAAc;AACnB;AACA,aAAa,OAAO,GAAG,MAAM,0CAA0C,QAAQ;AAC/E;AACA,KAAK,oBAAoB;AACzB;AACA;AACA;AACA,cAAc,aAAa,YAAY,OAAO,GAAG,cAAc,wCAAwC,oBAAoB,KAAK,OAAO,CAAC,YAAY;AACpJ,KAAK,qBAAqB;AAC1B;AACA,qBAAqB,YAAY;AACjC;AACA;AACA;AACA;AACA,cAAc,oBAAoB,mCAAmC,qBAAqB,eAAe,OAAO,KAAK,OAAO,CAAC,QAAQ","names":[]}
@@ -5,6 +5,7 @@ type WebhookEntry = {
5
5
  contentType: string;
6
6
  name: string;
7
7
  path: string | null;
8
+ paths: Record<string, string>;
8
9
  };
9
10
  type WebhookEventOf<TType extends string, TData> = {
10
11
  type: TType;
@@ -25,6 +26,7 @@ type EntryDeletedEvent = WebhookEventOf<'entry.deleted', {
25
26
  type EntryMovedEvent = WebhookEventOf<'entry.moved', {
26
27
  entry: WebhookEntry;
27
28
  previousPath: string | null;
29
+ previousPaths: Record<string, string>;
28
30
  }>;
29
31
  type AssetUpdatedEvent = WebhookEventOf<'asset.updated', {
30
32
  asset: {
@@ -1 +1 @@
1
- {"version":3,"file":"webhooks.d.ts","sources":["../src/webhooks.ts"],"mappings":";;AACA,KAAK,YAAY;AACjB;AACA;AACA;AACA;AACA;AACA,KAAK,cAAc;AACnB;AACA;AACA;AACA,iBAAiB,WAAW;AAC5B;AACA;AACA,KAAK,mBAAmB,GAAG,cAAc;AACzC,WAAW,YAAY;AACvB;AACA,KAAK,qBAAqB,GAAG,cAAc;AAC3C,WAAW,YAAY;AACvB;AACA,KAAK,iBAAiB,GAAG,cAAc;AACvC,WAAW,YAAY;AACvB;AACA,KAAK,eAAe,GAAG,cAAc;AACrC,WAAW,YAAY;AACvB;AACA;AACA,KAAK,iBAAiB,GAAG,cAAc;AACvC;AACA;AACA;AACA;AACA,aAAa,YAAY;AACzB;AACA,KAAK,iBAAiB,GAAG,cAAc;AACvC;AACA;AACA;AACA;AACA;AACA,KAAK,mBAAmB,GAAG,cAAc;AACzC;AACA;AACA;AACA,KAAK,gBAAgB,GAAG,cAAc,iBAAiB,MAAM;AAC7D,KAAK,YAAY,GAAG,mBAAmB,GAAG,qBAAqB,GAAG,iBAAiB,GAAG,eAAe,GAAG,iBAAiB,GAAG,iBAAiB,GAAG,mBAAmB,GAAG,gBAAgB;AACtL,KAAK,gBAAgB,GAAG,YAAY;AACpC,cAAc,wBAAwB,SAAS,KAAK;AACpD;AACA;AACA,KAAK,cAAc;AACnB;AACA,aAAa,OAAO,GAAG,MAAM,0CAA0C,QAAQ;AAC/E;AACA,KAAK,oBAAoB;AACzB;AACA;AACA;AACA,cAAc,aAAa,YAAY,OAAO,GAAG,cAAc,wCAAwC,oBAAoB,KAAK,OAAO,CAAC,YAAY;AACpJ,KAAK,qBAAqB;AAC1B;AACA,qBAAqB,YAAY;AACjC;AACA;AACA;AACA;AACA,cAAc,oBAAoB,mCAAmC,qBAAqB,eAAe,OAAO,KAAK,OAAO,CAAC,QAAQ","names":[]}
1
+ {"version":3,"file":"webhooks.d.ts","sources":["../src/webhooks.ts"],"mappings":";;AACA,KAAK,YAAY;AACjB;AACA;AACA;AACA;AACA,WAAW,MAAM;AACjB;AACA,KAAK,cAAc;AACnB;AACA;AACA;AACA,iBAAiB,WAAW;AAC5B;AACA;AACA,KAAK,mBAAmB,GAAG,cAAc;AACzC,WAAW,YAAY;AACvB;AACA,KAAK,qBAAqB,GAAG,cAAc;AAC3C,WAAW,YAAY;AACvB;AACA,KAAK,iBAAiB,GAAG,cAAc;AACvC,WAAW,YAAY;AACvB;AACA,KAAK,eAAe,GAAG,cAAc;AACrC,WAAW,YAAY;AACvB;AACA,mBAAmB,MAAM;AACzB;AACA,KAAK,iBAAiB,GAAG,cAAc;AACvC;AACA;AACA;AACA;AACA,aAAa,YAAY;AACzB;AACA,KAAK,iBAAiB,GAAG,cAAc;AACvC;AACA;AACA;AACA;AACA;AACA,KAAK,mBAAmB,GAAG,cAAc;AACzC;AACA;AACA;AACA,KAAK,gBAAgB,GAAG,cAAc,iBAAiB,MAAM;AAC7D,KAAK,YAAY,GAAG,mBAAmB,GAAG,qBAAqB,GAAG,iBAAiB,GAAG,eAAe,GAAG,iBAAiB,GAAG,iBAAiB,GAAG,mBAAmB,GAAG,gBAAgB;AACtL,KAAK,gBAAgB,GAAG,YAAY;AACpC,cAAc,wBAAwB,SAAS,KAAK;AACpD;AACA;AACA,KAAK,cAAc;AACnB;AACA,aAAa,OAAO,GAAG,MAAM,0CAA0C,QAAQ;AAC/E;AACA,KAAK,oBAAoB;AACzB;AACA;AACA;AACA,cAAc,aAAa,YAAY,OAAO,GAAG,cAAc,wCAAwC,oBAAoB,KAAK,OAAO,CAAC,YAAY;AACpJ,KAAK,qBAAqB;AAC1B;AACA,qBAAqB,YAAY;AACjC;AACA;AACA;AACA;AACA,cAAc,oBAAoB,mCAAmC,qBAAqB,eAAe,OAAO,KAAK,OAAO,CAAC,QAAQ","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, webhook verification, and the content model CLI",
4
- "version": "0.7.0",
4
+ "version": "0.9.0",
5
5
  "keywords": [
6
6
  "cms",
7
7
  "headless-cms",
@@ -1 +0,0 @@
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":[]}