@mapled/vanilla 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -9
- package/dist/mapled.js +17 -17
- package/dist/mapled.js.sha384 +1 -1
- package/dist/markdown.js +6 -1
- package/dist/runtime.d.ts +6 -1
- package/dist/runtime.js +133 -28
- package/dist/url.js +9 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<html lang="en">
|
|
14
14
|
<head>
|
|
15
15
|
<title data-mapled-bind="home.headline">Studio Nord</title>
|
|
16
|
-
<script src="https://cdn.jsdelivr.net/npm/@mapled/vanilla@0.1
|
|
16
|
+
<script src="https://cdn.jsdelivr.net/npm/@mapled/vanilla@0.2.1/dist/mapled.js" data-mapled-key="mk_live_…" defer></script>
|
|
17
17
|
</head>
|
|
18
18
|
<body>
|
|
19
19
|
<h1 data-mapled-bind="home.headline">Websites, built by hand</h1>
|
|
@@ -87,7 +87,7 @@ The key is the project's **delivery key** (Mapled: Integrations → Your site).
|
|
|
87
87
|
</article>
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
`post.html?slug=hello-world` shows the post whose slug field is `hello-world`. `data-mapled-slug="about-us"` names the record in the page instead. Tell Mapled the page's address as `/post.html?slug=[slug]` in the site manifest (with a `route_param` binding on the slug field)
|
|
90
|
+
`post.html?slug=hello-world` shows the post whose slug field is `hello-world`. `data-mapled-slug="about-us"` names the record in the page instead. Tell Mapled the page's address as `/post.html?slug=[slug]` in the site manifest (with a `route_param` binding on the slug field) — `npx @mapled/cli scan --write` writes it from the attributes — and **Preview** in the record's editor opens the right page.
|
|
91
91
|
|
|
92
92
|
### Forms
|
|
93
93
|
|
|
@@ -113,7 +113,7 @@ The key is the project's **delivery key** (Mapled: Integrations → Your site).
|
|
|
113
113
|
| `missing` | No such single, record or collection in the release — or nothing in the address to find a record by | The HTML you wrote |
|
|
114
114
|
| `empty` | The record is there, a value at this path isn't: an editor left the field empty — or the key is misspelled | Nothing: the text is emptied, a picture is hidden, an attribute is removed (`alt` becomes `""`) |
|
|
115
115
|
| `failed` | The read didn't come back | The HTML you wrote |
|
|
116
|
-
| `unsafe-url` | An address that isn't `http:`, `https:` — or `mailto:`, `tel:` in a link — or that carries credentials | The HTML you wrote |
|
|
116
|
+
| `unsafe-url` | An address that isn't `http:`, `https:` — or `mailto:`, `tel:` in a link — or that carries credentials; a value that can't go into a link template | The HTML you wrote |
|
|
117
117
|
| `type` | A value that has no text: a group bound as text, a word where a date goes | The HTML you wrote |
|
|
118
118
|
| `refused-element` | `<script>`, `<style>`, `<iframe>`, `<object>`, `<embed>`, `<base>`, `<link>`, `<meta>`, `<template>`, `<noscript>` are never written into | The HTML you wrote |
|
|
119
119
|
| `unsupported` | An attribute Mapled doesn't read — `data-mapled-bind-style`, a misspelled name — or a list without a `<template>` | The HTML you wrote |
|
|
@@ -129,11 +129,11 @@ The key is the project's **delivery key** (Mapled: Integrations → Your site).
|
|
|
129
129
|
</script>
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
-
`state` is `ready`, `unpublished` (nothing is published yet) or `fallback` (Mapled couldn't be reached); the same word is on `<html data-mapled-state="…">`, `loading` before the first fill — a hook for CSS, set by the script only, so a page whose script never loaded hides nothing. `mapled:update` follows every later fill. In the console, `Mapled.page.report` is the last report and `Mapled.page.refresh()` fills again — call it after your own script adds elements with attributes. Every fill starts from the HTML you wrote, so a value that stops fitting gives its place back to your fallback, not to the previous release.
|
|
132
|
+
`state` is `ready`, `unpublished` (nothing is published yet) or `fallback` (Mapled couldn't be reached — or preview just ended and what is published is on its way); the same word is on `<html data-mapled-state="…">`, `loading` before the first fill — a hook for CSS, set by the script only, so a page whose script never loaded hides nothing. `mapled:update` follows every later fill. In the console, `Mapled.page.report` is the last report and `Mapled.page.refresh()` fills again — call it after your own script adds elements with attributes. Every fill starts from the HTML you wrote, so a value that stops fitting gives its place back to your fallback, not to the previous release.
|
|
133
133
|
|
|
134
134
|
## Preview
|
|
135
135
|
|
|
136
|
-
Nothing to set up in the page. **Preview** in Mapled opens the page with a one-time token — `https://your-site/post.html?slug=hello&mapled-preview=mpv_…` — and the script, before its first read, takes the token out of the address bar, trades it for a draft session and keeps the session for the tab. The page shows drafts until the tab closes or the session ends, and `<html>` carries `data-mapled-preview` meanwhile:
|
|
136
|
+
Nothing to set up in the page. **Preview** in Mapled opens the page with a one-time token — `https://your-site/post.html?slug=hello&mapled-preview=mpv_…` — and the script, before its first read, takes the token out of the address bar, trades it for a draft session and keeps the session for the tab. The page shows drafts until the tab closes or the session ends, and `<html>` carries `data-mapled-preview` meanwhile; a form posted from that tab is filed as a test submission — not counted, nobody notified, no workflow started:
|
|
137
137
|
|
|
138
138
|
```html
|
|
139
139
|
<!-- preview-bar.html -->
|
|
@@ -143,11 +143,11 @@ Nothing to set up in the page. **Preview** in Mapled opens the page with a one-t
|
|
|
143
143
|
</style>
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
-
Mapled has to know where the site lives and that it renders in the browser: the site manifest's `framework` is `"plain-html"` (your AI agent's `push_site_manifest`, or `npx @mapled/cli bindings push`) — or the project was created as plain HTML — and **Production domain** in Project settings holds the site's public address (an agent sets it with `set_site_url`). `npx @mapled/cli doctor` checks all of it, the script tag's version included.
|
|
146
|
+
Mapled has to know where the site lives and that it renders in the browser: the site manifest's `framework` is `"plain-html"` (your AI agent's `push_site_manifest`, or `npx @mapled/cli scan --write` and `npx @mapled/cli bindings push` — the scan reads the pages' attributes into the manifest) — or the project was created as plain HTML — and **Production domain** in Project settings holds the site's public address (an agent sets it with `set_site_url`). `npx @mapled/cli doctor` checks all of it, the script tag's version included.
|
|
147
147
|
|
|
148
148
|
## Live updates
|
|
149
149
|
|
|
150
|
-
A publish shows when the page is loaded again. To follow publishes while a page is open, add `data-mapled-live` to the script tag — `data-mapled-live="30"` for the seconds between checks (60 by default, 15 at least). A check is a request of the delivery key's budget (1,200 a minute), made only while the tab is visible; a quiet one costs next to nothing.
|
|
150
|
+
A publish shows when the page is loaded again. To follow publishes while a page is open, add `data-mapled-live` to the script tag — `data-mapled-live="30"` for the seconds between checks (60 by default, 15 at least). A check is a request of the delivery key's budget (1,200 a minute), made only while the tab is visible; a quiet one costs next to nothing. The page knows the release it filled from, and the watch starts there: a publish that landed between the fill and the first check, or while a tab opened in the background waited to be shown, fills the page at once — and a page left as written because Mapled was out of reach fills itself from the first release the watch hears.
|
|
151
151
|
|
|
152
152
|
## What content can and can't do to the page
|
|
153
153
|
|
|
@@ -172,13 +172,13 @@ const report = await page.ready;
|
|
|
172
172
|
if (report.state === "fallback") console.info("Showing the page as written.");
|
|
173
173
|
```
|
|
174
174
|
|
|
175
|
-
`mount({ key, apiUrl?, root?, live?, fetch? })` answers `{ ready, report, refresh(), endPreview(), stop(), client }` — `client` is the [`@mapled/client`](https://www.npmjs.com/package/@mapled/client) the page reads through, for reads of your own. The script tag does the same from its attributes (`data-mapled-key`, `data-mapled-api`, `data-mapled-live`) and leaves the result at `Mapled.page`; a tag without a key only defines `Mapled.mount`. `VERSION` is the package's version, `Mapled.version` in a page.
|
|
175
|
+
`mount({ key, apiUrl?, locale?, root?, live?, fetch? })` answers `{ ready, report, refresh(), endPreview(), stop(), client }` — `client` is the [`@mapled/client`](https://www.npmjs.com/package/@mapled/client) the page reads through, for reads of your own. The script tag does the same from its attributes (`data-mapled-key`, `data-mapled-api`, `data-mapled-live`, `data-mapled-locale`) and leaves the result at `Mapled.page`; a tag without a key only defines `Mapled.mount`. `VERSION` is the package's version, `Mapled.version` in a page.
|
|
176
176
|
|
|
177
177
|
## Every attribute
|
|
178
178
|
|
|
179
179
|
| Attribute | On | Says |
|
|
180
180
|
|---|---|---|
|
|
181
|
-
| `data-mapled-key`, `data-mapled-api`, `data-mapled-live` | the script tag | the delivery key; another API origin; follow publishes |
|
|
181
|
+
| `data-mapled-key`, `data-mapled-api`, `data-mapled-live`, `data-mapled-locale` | the script tag | the delivery key; another API origin; follow publishes; read in one of the project's languages (`data-mapled-locale="ru"` — a translated field shows in it, or in the first language of the project's fallback chain that has a value) |
|
|
182
182
|
| `data-mapled-bind` | an element | the path whose value fills it |
|
|
183
183
|
| `data-mapled-format` | with `data-mapled-bind` | `text` (default), `markdown`, `date`, `datetime`, `number` |
|
|
184
184
|
| `data-mapled-width` | an `<img>` | the width of the picture to ask for, 16–4096 |
|
package/dist/mapled.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
/*! @mapled/vanilla 0.1
|
|
2
|
-
"use strict";(()=>{var pt=Object.defineProperty;var mt=(e,t,n)=>t in e?pt(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var ue=(e,t,n)=>mt(e,typeof t!="symbol"?t+"":t,n);var ce="mapled-preview",Q="mapled:preview",gt=/^mpv_[A-Za-z0-9]{16,96}$/,Le=/^mps_[A-Za-z0-9]{16,96}$/,Me=new WeakMap,ht=4e3;function Ue(){if(typeof window>"u"||typeof document>"u")return null;try{let e=window.sessionStorage,t=Me.get(window);return t||Me.set(window,t={memory:null,exchanging:null}),{location:window.location,history:window.history,sessionStorage:e,page:t}}catch{return null}}function yt(e){try{let t=e.getItem(Q);if(!t)return null;let n=JSON.parse(t),r=typeof n?.expiresAt=="string"?Date.parse(n.expiresAt):NaN;return typeof n?.draftToken!="string"||!Le.test(n.draftToken)||!(r>Date.now())?(e.removeItem(Q),null):{draftToken:n.draftToken,expiresAt:n.expiresAt}}catch{return null}}function F(e){let t=yt(e.sessionStorage);if(t)return t;let n=e.page.memory;return n&&Date.parse(n.expiresAt)>Date.now()?n:(e.page.memory=null,null)}function ee(){let e=Ue();if(e){e.page.memory=null;try{e.sessionStorage.removeItem(Q)}catch{}}}function Ne(e={}){let t=Ue();if(!t)return Promise.resolve(null);let n;try{n=new URL(t.location.href)}catch{return Promise.resolve(F(t))}let r=n.searchParams.get(ce);if(r===null)return t.page.exchanging?t.page.exchanging.done:Promise.resolve(F(t));n.searchParams.delete(ce);try{t.history.replaceState(t.history.state,"",`${n.pathname}${n.search}${n.hash}`)}catch{}if(!gt.test(r))return Promise.resolve(F(t));if(t.page.exchanging?.token===r)return t.page.exchanging.done;let s=(e.apiUrl??"https://api.mapled.io").replace(/\/+$/,""),l=(e.appUrl??"https://app.mapled.io").replace(/\/+$/,""),u=e.fetch??fetch,i=(async()=>{try{let o=await u(`${s}/v1/delivery/preview/exchange`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({token:r}),cache:"no-store"});if(o.ok){let a=await o.json();if(typeof a.draftToken!="string"||typeof a.expiresAt!="string"||!Le.test(a.draftToken))return F(t);let d={draftToken:a.draftToken,expiresAt:a.expiresAt};t.page.memory=d;try{t.sessionStorage.setItem(Q,JSON.stringify(d))}catch{}return d}if(o.status===401){let a=await o.json().catch(()=>null),d=a?.error?.projectId;if(a?.error?.code==="PREVIEW_EXPIRED"&&typeof d=="string"&&/^[0-9a-f-]{8,64}$/i.test(d))return t.location.assign(`${l}/preview/expired?project=${encodeURIComponent(d)}`),await new Promise(m=>setTimeout(m,ht)),F(t)}return F(t)}catch{return F(t)}})(),f={token:r,done:i};return t.page.exchanging=f,i.then(()=>{t.page.exchanging===f&&(t.page.exchanging=null)}),i}async function Ie(e){return(await Ne(e))?.draftToken??null}function bt(e){if(!e||typeof e!="object")return;let{release:t,publishedAt:n}=e;if(t===null)return null;if(!(!Number.isInteger(t)||t<1||typeof n!="string"))return{release:t,publishedAt:n}}function Fe(e){let t=typeof e.key=="string"&&e.key.length>0;if(!t&&!e.endpoint)throw new Error("Mapled: watchRelease needs an endpoint or a delivery key.");let n=t?`${(e.apiUrl??"https://api.mapled.io").replace(/\/+$/,"")}/v1/delivery/release`:e.endpoint,r=t?{accept:"application/json","x-mapled-key":e.key}:{accept:"application/json"},s=(t?Math.max(15,e.interval??60):Math.max(2,e.interval??10))*1e3,l=e.fetch??fetch,u=typeof document>"u"?null:document,i,f=0,o=!1,a,d=null,m=()=>!u||u.visibilityState!=="hidden",w=()=>{o||!m()||(a=setTimeout(v,Math.min(s*2**f,3e5)))};async function v(){if(o||d)return;let O=new AbortController;d=O;let T=null;try{let M=await l(n,{cache:"no-cache",headers:r,signal:O.signal}),$=M.ok?bt(await M.json()):void 0;if(o)return;$===void 0?f+=1:(f=0,i===void 0?i=$?$.release:null:$&&$.release>(i??0)&&(T={next:$,previous:i},i=$.release))}catch{if(o)return;f+=1}finally{d===O&&(d=null)}w(),T&&e.onPublish(T.next,T.previous)}let x=()=>{clearTimeout(a),m()&&v()};return u?.addEventListener("visibilitychange",x),m()&&v(),()=>{o=!0,clearTimeout(a),d?.abort(),u?.removeEventListener("visibilitychange",x)}}function De(e){let t={};return e.forEach((n,r)=>{typeof n!="string"||r in t||(t[r]=n)}),t}async function de(e,t,n){if(!n?.key)throw new Error("Mapled: a delivery key is required.");let r=(n.apiUrl??"https://api.mapled.io").replace(/\/+$/,""),s=typeof HTMLFormElement<"u"&&t instanceof HTMLFormElement?De(new FormData(t)):typeof FormData<"u"&&t instanceof FormData?De(t):{...t},l=n.page??(typeof window<"u"&&typeof document<"u"?window.location.pathname:void 0),u={...s};n.consent&&(u._consent=n.consent),l&&u._page===void 0&&(u._page=l),n.test&&(u._test=!0);let i;try{i=await(n.fetch??fetch)(`${r}/v1/delivery/forms/${encodeURIComponent(e)}`,{method:"POST",headers:{"content-type":"application/json","x-mapled-key":n.key},body:JSON.stringify(u)})}catch{return{ok:!1,status:0,code:"NETWORK",message:"Mapled couldn't be reached. Try again in a minute."}}if(i.ok)return{ok:!0};let f=`The submission wasn't accepted (${i.status}).`,o;try{let a=await i.json();a.error?.message&&(f=a.error.message),typeof a.error?.code=="string"&&(o=a.error.code)}catch{}return{ok:!1,status:i.status,code:o,message:f}}var fe=/^[a-z0-9][a-z0-9-]{0,63}$/,Ke=new Set;function wt(e){if(typeof process<"u")return;let t=e?.aliases;if(!(!t||typeof t!="object")){for(let[n,r]of Object.entries(t))if(!(!r||typeof r!="object"||!fe.test(n)))for(let[s,l]of Object.entries(r)){if(typeof l!="string"||!fe.test(s)||!fe.test(l))continue;let u=`${n}.${s}`;Ke.has(u)||(Ke.add(u),console.warn(`[mapled] \u201C${s}\u201D in \u201C${n}\u201D was renamed to \u201C${l}\u201D. The old key keeps working until its alias is removed in Mapled \u2014 use \u201C${l}\u201D instead.`))}}}function Be(e,t={}){let n=(t.apiUrl??"https://api.mapled.io").replace(/\/+$/,""),r=new URLSearchParams;t.width&&r.set("w",String(t.width)),t.height&&r.set("h",String(t.height)),t.fit&&r.set("fit",t.fit),t.format&&r.set("fmt",t.format),t.quality&&r.set("q",String(t.quality));let s=r.toString();return`${n}/files/${encodeURIComponent(e)}${s?`?${s}`:""}`}var D=class extends Error{constructor(n,r,s){super(r);ue(this,"status");ue(this,"code");this.name="MapledError",this.status=n,this.code=s}},_e=e=>Number.isInteger(e)&&e>=1,je=()=>new Error("Mapled: release must be a positive whole number.");function qe(e){let t=(e.apiUrl??"https://api.mapled.io").replace(/\/+$/,"");if(!e.key)throw new Error("Mapled: a delivery key is required.");if(e.release!==void 0&&!_e(e.release))throw je();let n=(o,a)=>(e.fetch??fetch)(o,a);async function r(){return e.draft===null?null:typeof e.draft=="string"?e.draft||null:typeof e.draft=="function"?await e.draft()||null:Ie({apiUrl:t,appUrl:e.appUrl,fetch:e.fetch})}async function s(o){let a=`Mapled request failed (${o.status}).`,d;try{let m=await o.json();m.error?.message&&(a=m.error.message),typeof m.error?.code=="string"&&(d=m.error.code)}catch{}return new D(o.status,a,d)}async function l(o,a,d){let m=await r(),w=d.release??e.release;if(w!==void 0&&!_e(w))throw je();w!==void 0&&!m&&a.set("release",String(w));let v=a.toString(),x=await n(`${t}${o}${v?`?${v}`:""}`,{headers:m?{"x-mapled-key":e.key,"x-mapled-draft":m}:{"x-mapled-key":e.key},...m?{cache:"no-store"}:{},...d.signal?{signal:d.signal}:{}});if(!x.ok)throw await s(x);let O=await x.json();return wt(O),O}let u=(o,a)=>(a.expand?.length&&o.set("expand",a.expand.join(",")),a.fields?.length&&o.set("fields",a.fields.join(",")),o),i=async(o,a)=>{try{return(await l(o,u(new URLSearchParams,a),a)).record}catch(d){if(d instanceof D&&d.status===404&&d.code!=="RELEASE_NOT_FOUND")return null;throw d}};return{async getRecords(o,a={}){let d=new URLSearchParams;for(let[m,w]of Object.entries(a.filter??{}))if(w!==null&&typeof w=="object")for(let[v,x]of Object.entries(w))x!==void 0&&d.set(`filter[${m}][${v}]`,Array.isArray(x)?x.join(","):String(x));else d.set(`filter[${m}]`,String(w));return a.sort&&d.set("sort",a.sort),a.limit!==void 0&&d.set("limit",String(a.limit)),a.offset!==void 0&&d.set("offset",String(a.offset)),u(d,a),l(`/v1/delivery/collections/${encodeURIComponent(o)}/records`,d,a)},getRecord(o,a,d={}){return i(`/v1/delivery/collections/${encodeURIComponent(o)}/records/${encodeURIComponent(a)}`,d)},getRecordBySlug(o,a,d={}){return i(`/v1/delivery/collections/${encodeURIComponent(o)}/records/by-slug/${encodeURIComponent(a)}`,d)},getSingle(o,a={}){return i(`/v1/delivery/singles/${encodeURIComponent(o)}`,a)},async isPreview(){return await r()!==null},watch(o,a={}){return Fe({key:e.key,apiUrl:t,interval:a.interval,onPublish:o,fetch:e.fetch})},async getRelease(o={}){let a=await n(`${t}/v1/delivery/release`,{headers:{"x-mapled-key":e.key},cache:"no-cache",...o.signal?{signal:o.signal}:{}});if(!a.ok)throw await s(a);let d=await a.json();return d.release===null||d.publishedAt===null?null:{release:d.release,publishedAt:d.publishedAt}}}}var kt=/^[a-z0-9][a-z0-9_-]{0,63}$/;function j(e){if(typeof e!="string")return null;let t=e.trim().split(".");if(t.length>8)return null;for(let n of t)if(!kt.test(n))return null;return t}var te=(e,t)=>e!==null&&typeof e=="object"&&!Array.isArray(e)&&Object.prototype.hasOwnProperty.call(e,t)?e[t]:void 0;function pe(e,t){let n=e.data,r=e.expanded;for(let s=0;s<t.length;s++){let l=t[s],u=s<t.length-1?te(r,l):void 0;if(u!==void 0){if(u===null||Array.isArray(u))return;n=te(u,"data"),r=te(u,"expanded");continue}if(n=te(n,l),r=void 0,n===void 0)return}return n}var B=e=>e==null||e===""||Array.isArray(e)&&e.length===0;function me(e){return typeof e=="string"?e:typeof e=="number"&&Number.isFinite(e)?String(e):typeof e=="boolean"?e?"true":"false":Array.isArray(e)&&e.every(t=>typeof t=="string"||typeof t=="number")?e.join(", "):null}var St=new Set(["http:","https:","mailto:","tel:"]),Rt=new Set(["http:","https:"]),At=2048;function N(e,t,n){if(typeof e!="string"||e.length>At)return null;let r;try{r=new URL(e,t)}catch{return null}return!(n==="link"?St:Rt).has(r.protocol)||r.username!==""||r.password!==""?null:r.href}var vt=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,ze=e=>typeof e=="string"&&vt.test(e);function He(e,t,n){let r=[],s=e.replace(/\{([^{}]*)\}/g,(u,i)=>{let f=j(i);if(!f)return r.push({ok:!1,reason:"bad-path",path:i.trim().slice(0,80)}),"";let o=n(f);return typeof o!="string"&&typeof o!="number"||o===""?(r.push({ok:!1,reason:"empty",path:f.join(".")}),""):encodeURIComponent(String(o))});if(r[0])return r[0];let l=N(s,t,"link");return l===null?{ok:!1,reason:"unsafe-url",path:e.slice(0,80)}:{ok:!0,url:l}}var Tt=1e5,Ve=8,Ge=2048,Et="!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~",We={amp:"&",lt:"<",gt:">",quot:'"',apos:"'",nbsp:"\xA0",copy:"\xA9",reg:"\xAE",trade:"\u2122",mdash:"\u2014",ndash:"\u2013",hellip:"\u2026",laquo:"\xAB",raquo:"\xBB"};function Xe(e,t,n){let r=t.createDocumentFragment();if(typeof e!="string"||e==="")return r;let s=e.replace(/\r\n?/g,`
|
|
3
|
-
`).replace(/\t/g," ").replace(/\u0000/g,"\uFFFD"),
|
|
4
|
-
`),r,0),r}function
|
|
5
|
-
`).trim();
|
|
1
|
+
/*! @mapled/vanilla 0.2.1 | MIT | https://mapled.io */
|
|
2
|
+
"use strict";(()=>{var Ct=Object.defineProperty;var Lt=(e,t,n)=>t in e?Ct(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var be=(e,t,n)=>Lt(e,typeof t!="symbol"?t+"":t,n);var we="mapled-preview",ie="mapled:preview",Nt=/^mpv_[A-Za-z0-9]{16,96}$/,Ze=/^mps_[A-Za-z0-9]{16,96}$/,Ve=new WeakMap,Ut=4e3;function ke(){if(typeof window>"u"||typeof document>"u")return null;try{let e=window.sessionStorage,t=Ve.get(window);return t||Ve.set(window,t={memory:null,exchanging:null,epoch:0}),{location:window.location,history:window.history,sessionStorage:e,page:t}}catch{return null}}function It(e){try{let t=e.getItem(ie);if(!t)return null;let n=JSON.parse(t),r=typeof n?.expiresAt=="string"?Date.parse(n.expiresAt):NaN;return typeof n?.draftToken!="string"||!Ze.test(n.draftToken)||!(r>Date.now())?(e.removeItem(ie),null):{draftToken:n.draftToken,expiresAt:n.expiresAt}}catch{return null}}function K(e){let t=It(e.sessionStorage);if(t)return t;let n=e.page.memory;return n&&Date.parse(n.expiresAt)>Date.now()?n:(e.page.memory=null,null)}function B(){let e=ke();return e?K(e):null}function Ye(e){let t;try{t=new URL(e.location.href)}catch{return null}let n=t.searchParams.get(we);if(n===null)return null;t.searchParams.delete(we);try{e.history.replaceState(e.history.state,"",`${t.pathname}${t.search}${t.hash}`)}catch{}return n}function Se(){let e=ke();if(e){e.page.memory=null,e.page.epoch++,e.page.exchanging=null,Ye(e);try{e.sessionStorage.removeItem(ie)}catch{}}}function Je(e={}){let t=ke();if(!t)return Promise.resolve(null);let n=Ye(t);if(n===null)return t.page.exchanging?t.page.exchanging.done:Promise.resolve(K(t));if(!Nt.test(n))return Promise.resolve(K(t));if(t.page.exchanging?.token===n)return t.page.exchanging.done;let r=(e.apiUrl??"https://api.mapled.io").replace(/\/+$/,""),s=(e.appUrl??"https://app.mapled.io").replace(/\/+$/,""),o=e.fetch??fetch,c=++t.page.epoch,a=(async()=>{try{let i=await o(`${r}/v1/delivery/preview/exchange`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({token:n}),cache:"no-store"});if(i.ok){let p=await i.json();if(typeof p.draftToken!="string"||typeof p.expiresAt!="string"||!Ze.test(p.draftToken)||!(Date.parse(p.expiresAt)>Date.now())||t.page.epoch!==c)return K(t);let d={draftToken:p.draftToken,expiresAt:p.expiresAt};t.page.memory=d;try{t.sessionStorage.setItem(ie,JSON.stringify(d))}catch{}return d}if(i.status===401&&t.page.epoch===c){let p=await i.json().catch(()=>null),d=p?.error?.projectId;if(t.page.epoch===c&&p?.error?.code==="PREVIEW_EXPIRED"&&typeof d=="string"&&/^[0-9a-f-]{8,64}$/i.test(d))return t.location.assign(`${s}/preview/expired?project=${encodeURIComponent(d)}`),await new Promise(l=>setTimeout(l,Ut)),K(t)}return K(t)}catch{return K(t)}})(),f={token:n,done:a};return t.page.exchanging=f,a.then(()=>{t.page.exchanging===f&&(t.page.exchanging=null)}),a}async function Qe(e){return(await Je(e))?.draftToken??null}function Ae(e){if(!e||typeof e!="object")return;let{release:t,publishedAt:n}=e;if(t===null)return n===null?null:void 0;if(!(!Number.isInteger(t)||t<1||typeof n!="string"))return{release:t,publishedAt:n}}var Dt=1e4;function Ft(e,t){let n=typeof document>"u"?void 0:globalThis.performance?.timeOrigin;if(n===void 0)return!1;let r=Date.parse(t.get("date")??""),s=Number.isNaN(r)?0:r+(Number(t.get("age"))||0)*1e3-Date.now();return Date.parse(e)>n+s-Dt}function et(e,t){return(n,r)=>{if(e!==void 0)return e;let s=t?.();return s!==void 0?s:Ft(n.publishedAt,r)?null:n.release}}function tt(e,t,n){let r=typeof e.key=="string"&&e.key.length>0;if(!r&&!e.endpoint)throw new Error("Mapled: watchRelease needs an endpoint or a delivery key.");let s=r?`${(e.apiUrl??"https://api.mapled.io").replace(/\/+$/,"")}/v1/delivery/release`:e.endpoint,o=r?{accept:"application/json","x-mapled-key":e.key}:{accept:"application/json"},c=(r?Math.max(15,e.interval??60):Math.max(2,e.interval??10))*1e3,a=e.fetch??fetch,f=typeof document>"u"?null:document,i,p=null,d=0,l=null,g=0,S=!1,x,P=null,b=()=>!f||f.visibilityState!=="hidden",C=()=>{S||!b()||(x=setTimeout(L,Math.min(c*2**g,3e5)))};async function L(){if(S||P)return;let N=new AbortController;P=N;let U=null;try{let G=await a(s,{cache:"no-cache",headers:o,signal:N.signal}),O=G.ok?Ae(await G.json()):void 0;if(S)return;O===void 0?g+=1:(g=0,i===void 0&&(i=O?t(O,G.headers):null),O&&O.release>(p?.release??0)&&(p=O),O&&O.release>(i??0)?(U={next:O,previous:i},i=O.release,d=O.release,l=null):O&&l!==null&&typeof i=="number"&&O.release>=i&&i>d&&(U={next:O,previous:l},d=i,l=null))}catch{if(S)return;g+=1}finally{P===N&&(P=null)}C(),U&&e.onPublish(U.next,U.previous)}let $=()=>{clearTimeout(x),b()&&L()};f?.addEventListener("visibilitychange",$);let z=n?.(N=>{if(S||typeof i!="number"||N>=i||i<=d||(l=l===null?N:Math.min(l,N),p===null||p.release<i))return;d=i;let U=l;l=null,e.onPublish(p,U)});return b()&&L(),()=>{S=!0,clearTimeout(x),P?.abort(),z?.(),f?.removeEventListener("visibilitychange",$)}}function nt(e){let t={};return e.forEach((n,r)=>{typeof n!="string"||r in t||(t[r]=n)}),t}async function ve(e,t,n){if(!n?.key)throw new Error("Mapled: a delivery key is required.");let r=(n.apiUrl??"https://api.mapled.io").replace(/\/+$/,""),s=typeof HTMLFormElement<"u"&&t instanceof HTMLFormElement?nt(new FormData(t)):typeof FormData<"u"&&t instanceof FormData?nt(t):{...t},o=n.page??(typeof window<"u"&&typeof document<"u"?window.location.pathname:void 0),c={...s};n.consent&&(c._consent=n.consent),o&&c._page===void 0&&(c._page=o),n.test&&(c._test=!0);let a=B()?.draftToken,f={"content-type":"application/json","x-mapled-key":n.key};a&&(f["x-mapled-draft"]=a);let i;try{i=await(n.fetch??fetch)(`${r}/v1/delivery/forms/${encodeURIComponent(e)}`,{method:"POST",headers:f,body:JSON.stringify(c)})}catch{return{ok:!1,status:0,code:"NETWORK",message:"Mapled couldn't be reached. Try again in a minute."}}if(i.ok)return{ok:!0};let p=`The submission wasn't accepted (${i.status}).`,d;try{let l=await i.json();l.error?.message&&(p=l.error.message),typeof l.error?.code=="string"&&(d=l.error.code)}catch{}return{ok:!1,status:i.status,code:d,message:p}}var Re=/^[a-z0-9][a-z0-9-]{0,63}$/,rt=new Set;function _t(e){if(typeof process<"u")return;let t=e?.aliases;if(!(!t||typeof t!="object")){for(let[n,r]of Object.entries(t))if(!(!r||typeof r!="object"||!Re.test(n)))for(let[s,o]of Object.entries(r)){if(typeof o!="string"||!Re.test(s)||!Re.test(o))continue;let c=`${n}.${s}`;rt.has(c)||(rt.add(c),console.warn(`[mapled] \u201C${s}\u201D in \u201C${n}\u201D was renamed to \u201C${o}\u201D. The old key keeps working until its alias is removed in Mapled \u2014 use \u201C${o}\u201D instead.`))}}}function it(e,t={}){let n=(t.apiUrl??"https://api.mapled.io").replace(/\/+$/,""),r=new URLSearchParams;t.width&&r.set("w",String(t.width)),t.height&&r.set("h",String(t.height)),t.fit&&r.set("fit",t.fit),t.format&&r.set("fmt",t.format),t.quality&&r.set("q",String(t.quality));let s=r.toString();return`${n}/files/${encodeURIComponent(e)}${s?`?${s}`:""}`}var W=class extends Error{constructor(n,r,s){super(r);be(this,"status");be(this,"code");this.name="MapledError",this.status=n,this.code=s}},st=e=>Number.isInteger(e)&&e>=1,ot=()=>new Error("Mapled: release must be a positive whole number.");function at(e){let t=(e.apiUrl??"https://api.mapled.io").replace(/\/+$/,"");if(!e.key)throw new Error("Mapled: a delivery key is required.");if(e.release!==void 0&&!st(e.release))throw ot();let n=(d,l)=>(e.fetch??fetch)(d,l),r,s=new Set;async function o(){return e.draft===null?null:typeof e.draft=="string"?e.draft||null:typeof e.draft=="function"?await e.draft()||null:Qe({apiUrl:t,appUrl:e.appUrl,fetch:e.fetch})}async function c(d){let l=`Mapled request failed (${d.status}).`,g;try{let S=await d.json();S.error?.message&&(l=S.error.message),typeof S.error?.code=="string"&&(g=S.error.code)}catch{}return new W(d.status,l,g)}async function a(d,l,g){let S=await o(),x=g.release??e.release;if(x!==void 0&&!st(x))throw ot();x!==void 0&&!S&&l.set("release",String(x));let P=g.locale||e.locale;P&&l.set("locale",P);let b=l.toString(),C=await n(`${t}${d}${b?`?${b}`:""}`,{headers:S?{"x-mapled-key":e.key,"x-mapled-draft":S}:{"x-mapled-key":e.key},...S?{cache:"no-store"}:{},...g.signal?{signal:g.signal}:{}});if(!C.ok)throw await c(C);let L=await C.json();if(!S){let $=Number(C.headers.get("x-mapled-release"));if(Number.isInteger($)&&$>=1){r=r===void 0?$:Math.min(r,$);for(let z of s)Promise.resolve().then(()=>z($))}}return _t(L),L}let f=(d,l)=>(l.expand?.length&&d.set("expand",l.expand.join(",")),l.fields?.length&&d.set("fields",l.fields.join(",")),d),i=async(d,l)=>{try{return(await a(d,f(new URLSearchParams,l),l)).record}catch(g){if(g instanceof W&&g.status===404&&g.code!=="RELEASE_NOT_FOUND")return null;throw g}};return{async getRecords(d,l={}){let g=new URLSearchParams;for(let[S,x]of Object.entries(l.filter??{}))if(x!==null&&typeof x=="object")for(let[P,b]of Object.entries(x))b!==void 0&&g.set(`filter[${S}][${P}]`,Array.isArray(b)?b.join(","):String(b));else g.set(`filter[${S}]`,String(x));return l.sort&&g.set("sort",l.sort),l.limit!==void 0&&g.set("limit",String(l.limit)),l.offset!==void 0&&g.set("offset",String(l.offset)),f(g,l),a(`/v1/delivery/collections/${encodeURIComponent(d)}/records`,g,l)},getRecord(d,l,g={}){return i(`/v1/delivery/collections/${encodeURIComponent(d)}/records/${encodeURIComponent(l)}`,g)},getRecordBySlug(d,l,g={}){return i(`/v1/delivery/collections/${encodeURIComponent(d)}/records/by-slug/${encodeURIComponent(l)}`,g)},getSingle(d,l={}){return i(`/v1/delivery/singles/${encodeURIComponent(d)}`,l)},async isPreview(){return await o()!==null},watch(d,l={}){return tt({key:e.key,apiUrl:t,interval:l.interval,onPublish:d,fetch:e.fetch},et(l.from,()=>r),g=>(s.add(g),()=>{s.delete(g)}))},async getRelease(d={}){let l=await n(`${t}/v1/delivery/release`,{headers:{"x-mapled-key":e.key},cache:"no-cache",...d.signal?{signal:d.signal}:{}});if(!l.ok)throw await c(l);let g=Ae(await l.json());if(g===void 0)throw new Error("Mapled: the release pointer didn't come back as one \u2014 check the API origin.");return g}}}var jt=/^[a-z0-9][a-z0-9_-]{0,63}$/;function Z(e){if(typeof e!="string")return null;let t=e.trim().split(".");if(t.length>8)return null;for(let n of t)if(!jt.test(n))return null;return t}var ae=(e,t)=>e!==null&&typeof e=="object"&&!Array.isArray(e)&&Object.prototype.hasOwnProperty.call(e,t)?e[t]:void 0;function Te(e,t){let n=e.data,r=e.expanded;for(let s=0;s<t.length;s++){let o=t[s],c=s<t.length-1?ae(r,o):void 0;if(c!==void 0){if(c===null||Array.isArray(c))return;n=ae(c,"data"),r=ae(c,"expanded");continue}if(n=ae(n,o),r=void 0,n===void 0)return}return n}var Y=e=>e==null||e===""||Array.isArray(e)&&e.length===0;function Ee(e){return typeof e=="string"?e:typeof e=="number"&&Number.isFinite(e)?String(e):typeof e=="boolean"?e?"true":"false":Array.isArray(e)&&e.every(t=>typeof t=="string"||typeof t=="number")?e.join(", "):null}var Kt=new Set(["http:","https:","mailto:","tel:"]),Bt=new Set(["http:","https:"]),Ht=2048;function H(e,t,n){if(typeof e!="string"||e.length>Ht)return null;let r;try{r=new URL(e,t)}catch{return null}return!(n==="link"?Kt:Bt).has(r.protocol)||r.username!==""||r.password!==""?null:r.href}var qt=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,lt=e=>typeof e=="string"&&qt.test(e);function ut(e,t,n){let r=[],s=e.replace(/\{([^{}]*)\}/g,(c,a)=>{let f=Z(a);if(!f)return r.push({ok:!1,reason:"bad-path",path:a.trim().slice(0,80)}),"";let i=n(f);if(typeof i!="string"&&typeof i!="number"||i==="")return r.push({ok:!1,reason:"empty",path:f.join(".")}),"";try{return encodeURIComponent(String(i))}catch{return r.push({ok:!1,reason:"unsafe-url",path:f.join(".")}),""}});if(r[0])return r[0];let o=H(s,t,"link");return o===null?{ok:!1,reason:"unsafe-url",path:e.slice(0,80)}:{ok:!0,url:o}}var zt=1e5,dt=8,ft=2048,Wt="!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~",ct={amp:"&",lt:"<",gt:">",quot:'"',apos:"'",nbsp:"\xA0",copy:"\xA9",reg:"\xAE",trade:"\u2122",mdash:"\u2014",ndash:"\u2013",hellip:"\u2026",laquo:"\xAB",raquo:"\xBB"};function pt(e,t,n){let r=t.createDocumentFragment();if(typeof e!="string"||e==="")return r;let s=e.replace(/\r\n?/g,`
|
|
3
|
+
`).replace(/\t/g," ").replace(/\u0000/g,"\uFFFD"),o={doc:t,base:n};if(s.length>zt){for(let c of s.split(/\n{2,}/))c.trim()!==""&&(M(o,"p",r).textContent=c.trim());return r}return Ce(o,s.split(`
|
|
4
|
+
`),r,0),r}function M(e,t,n){let r=e.doc.createElement(t);return n.appendChild(r),r}var xe=e=>e.trim()==="",Q=e=>e.length-e.trimStart().length;function mt(e){let t=Q(e);if(t>3)return null;let n=0;for(;n<7&&e[t+n]==="#";)n++;if(n===0||n>6)return null;let r=e[t+n];if(r!==void 0&&r!==" ")return null;let s=e.slice(t+n).trim(),o=s.length;for(;o>0&&s[o-1]==="#";)o--;return o<s.length&&(o===0||s[o-1]===" ")&&(s=s.slice(0,o).trim()),{level:n,text:s}}function $e(e){if(Q(e)>3)return!1;let t=e.replace(/ /g,""),n=t[0];if(t.length<3||n!=="-"&&n!=="*"&&n!=="_")return!1;for(let r of t)if(r!==n)return!1;return!0}function Pe(e){let t=/^ {0,3}(`{3,}|~{3,})/.exec(e);if(!t)return null;let n=t[1][0];return n==="`"&&e.slice(t[0].length).includes("`")?null:{mark:n,size:t[1].length}}function J(e){let t=/^( {0,3})([-*+]|\d{1,9}[.)])( +|$)/.exec(e);if(!t)return null;let n=t[2],r=t[3].length,s=n.length>1||/\d/.test(n);return{indent:t[1].length,width:n.length+(r>=1&&r<=4?r:1),ordered:s,start:s?parseInt(n,10):1,rest:e.slice(t[0].length)}}var Me=e=>/^ {0,3}>/.test(e);function Xt(e){if(mt(e)!==null||Pe(e)!==null||Me(e)||$e(e))return!0;let t=J(e);return t!==null&&t.rest!==""&&(!t.ordered||t.start===1)}function Ce(e,t,n,r){if(r>=dt){let o=t.join(`
|
|
5
|
+
`).trim();o!==""&&(M(e,"p",n).textContent=o);return}let s=0;for(;s<t.length;){let o=t[s];if(xe(o)){s++;continue}let c=Pe(o);if(c){let p=[],d=Q(o);for(s++;s<t.length;){let l=t[s],g=Pe(l);if(g&&g.mark===c.mark&&g.size>=c.size&&l.trim().length===g.size)break;p.push(l.slice(Math.min(d,Q(l)))),s++}s++,M(e,"code",M(e,"pre",n)).textContent=p.length>0?`${p.join(`
|
|
6
6
|
`)}
|
|
7
|
-
`:"";continue}let
|
|
8
|
-
`),
|
|
9
|
-
`,
|
|
10
|
-
&`);if(n===-1)return null;let r=e.slice(t+1,n);if(r[0]==="#"){let
|
|
11
|
-
<`);if(
|
|
12
|
-
`)break;if(f==="("&&
|
|
13
|
-
`);)r++;let
|
|
14
|
-
`);)r++}return e[r]!==")"?null:{address:
|
|
15
|
-
`)for(
|
|
16
|
-
`){let
|
|
17
|
-
`,
|
|
18
|
-
<`),a=o===-1?null:t.slice(i+1,o);if(a!==null&&Mt.test(a)){let d=N(a.includes(":")?a:`mailto:${a}`,e.base,"link");if(d!==null){l();let m=P(e,"a",n);m.setAttribute("href",d),m.textContent=a,i=o+1;continue}}}if(f==="&"){let o=$t(t,i);if(o){s+=o.char,i=o.end;continue}}if(f==="*"||f==="_"||f==="~"){let o=H(t,i,f),a=o-i,d=!Se(t[o])&&(f!=="_"||!Ye(t[i-1])),m=f==="~"?a>=2?[["~~",["del"]]]:[]:[...a>=3?[[f.repeat(3),["em","strong"]]]:[],...a>=2?[[f.repeat(2),["strong"]]]:[],[f,["em"]]],w=!1;if(d)for(let[v,x]of m){if(u.has(v))continue;let O=i+v.length,T=Ot(t,O,v);if(T===-1){u.add(v);continue}if(T===O)continue;l();let M=n;for(let $ of x)M=P(e,$,M);re(e,t.slice(O,T),M,r+1),i=T+v.length,w=!0;break}w||(s+=t.slice(i,o),i=o);continue}s+=f,i++}l()}var Lt="http://www.w3.org/1999/xhtml",Ut=new Set(["script","style","iframe","frame","frameset","object","embed","applet","base","link","meta","template","noscript"]),se={href:{on:["a","area"],holds:"link"},src:{on:["img","video","audio","source"],holds:"media"},alt:{on:["img","area"],holds:"text"},title:{on:null,holds:"text"},"aria-label":{on:null,holds:"text"},placeholder:{on:["input","textarea"],holds:"text"},datetime:{on:["time","del","ins"],holds:"text"}},oe=e=>e.namespaceURI===Lt,W=e=>!Ut.has(e.localName.toLowerCase());function Re(e,t){if(!W(e))return"refused-element";let n=me(t);return n===null?"type":(e.textContent=n,null)}function tt(e,t,n){return!W(e)||!oe(e)?"refused-element":typeof t!="string"?"type":(e.replaceChildren(Xe(t,e.ownerDocument,n.base)),null)}function nt(e,t,n){return typeof e!="string"?null:ze(e)?Be(e,{...t.apiUrl?{apiUrl:t.apiUrl}:{},...n?{width:n}:{}}):N(e,t.base,"media")}function rt(e,t,n,r){if(!oe(e)||e.localName!=="img")return"refused-element";if(typeof t!="string")return"type";let s=nt(t,n,r);return s===null?"unsafe-url":(e.removeAttribute("srcset"),e.setAttribute("src",s),null)}function st(e,t,n,r,s){let l=t.toLowerCase(),u=Object.prototype.hasOwnProperty.call(se,l)?se[l]:void 0;if(!u)return"unsupported";if(!W(e)||!oe(e))return"refused-element";if(u.on!==null&&!u.on.includes(e.localName))return"unsupported";if(u.holds==="text"){let f=me(n);return f===null?"type":(e.setAttribute(l,f),null)}if(typeof n!="string")return"type";let i=u.holds==="media"?nt(n,r,e.localName==="img"?s:void 0):N(n,r.base,"link");return i===null?"unsafe-url":(e.setAttribute(l,i),null)}function ot(e,t){return!oe(e)||e.localName!=="a"&&e.localName!=="area"?"unsupported":(e.setAttribute("href",t),null)}function K(e,t){t?e.removeAttribute("hidden"):e.setAttribute("hidden","")}var it=["text","markdown","date","datetime","number"],Nt=/^\d{4}-\d{2}-\d{2}$/,It=/^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;function at(e,t,n){let r=n&&/^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$/.test(n)?n:void 0;try{if(t==="number"){let u=typeof e=="number"?e:typeof e=="string"&&e.trim()!==""?Number(e):NaN;return Number.isFinite(u)?new Intl.NumberFormat(r).format(u):null}if(typeof e!="string"||!It.test(e))return null;let s=new Date(e);if(Number.isNaN(s.getTime()))return null;let l=Nt.test(e);return new Intl.DateTimeFormat(r,{dateStyle:"long",...t==="datetime"&&!l?{timeStyle:"short"}:{},...l?{timeZone:"UTC"}:{}}).format(s)}catch{return null}}var S="data-mapled-",Ft=new Set(["bind","format","width","show","hide","link","list","each","record","slug","slug-param","sort","limit","filter","expand","empty","form","form-state","form-sent","form-failed","consent","end-preview","state","preview","key","api","live"]),ve=/^[a-z0-9][a-z0-9_-]{0,63}$/,Dt=/^mk_[A-Za-z0-9_]{8,128}$/,Kt=4;function U(e){let t=e.getAttribute("id");return`${e.localName}${t&&/^[\w-]{1,40}$/.test(t)?`#${t}`:""}`}var ae=e=>(e??"").split(",").map(t=>t.trim()).filter(t=>/^[a-z0-9][a-z0-9_.-]{0,127}$/.test(t)),lt=e=>Array.from(e.children).find(t=>t.localName==="template")??null;function Ae(e,t){let n=null,r=()=>n??(n={format:"text",attrs:[],expand:[]});for(let i of Array.from(e.attributes)){if(!i.name.startsWith(S))continue;let f=i.name.slice(S.length);if(f==="bind")r().bind=i.value;else if(f.startsWith("bind-")){let o=f.slice(5);Object.prototype.hasOwnProperty.call(se,o)?r().attrs.push([o,i.value]):t.push({path:i.value.slice(0,80),reason:"unsupported",on:`${U(e)}[${i.name.slice(0,60)}]`})}else f==="show"?r().show=i.value:f==="hide"?r().hide=i.value:f==="link"?r().link=i.value:Ft.has(f)||t.push({path:i.value.slice(0,80),reason:"unsupported",on:`${U(e)}[${i.name.slice(0,60)}]`})}if(!n)return null;let s=n,l=e.getAttribute(`${S}format`);l!==null&&(it.includes(l)?s.format=l:t.push({path:l.slice(0,80),reason:"bad-path",on:`${U(e)}[${S}format]`}));let u=parseInt(e.getAttribute(`${S}width`)??"",10);return u>=16&&u<=4096&&(s.width=u),s.expand=ae(e.getAttribute(`${S}expand`)),s}function _t(e,t){let n={},r=e.getAttribute(`${S}sort`);r!==null&&(/^-?[a-z0-9][a-z0-9_-]{0,63}$/.test(r.trim())?n.sort=r.trim():t.push({path:r.slice(0,80),reason:"bad-path",on:`${U(e)}[${S}sort]`}));let s=parseInt(e.getAttribute(`${S}limit`)??"",10);s>=1&&s<=100&&(n.limit=s);let l=e.getAttribute(`${S}filter`);if(l!==null)for(let i of l.split(";")){if(i.trim()==="")continue;let f=i.indexOf("="),o=f===-1?"":i.slice(0,f).trim(),a=f===-1?"":i.slice(f+1).trim();ve.test(o)&&a!==""&&a.length<=200?(n.filter??(n.filter={}))[o]=a:t.push({path:i.trim().slice(0,80),reason:"bad-path",on:`${U(e)}[${S}filter]`})}let u=ae(e.getAttribute(`${S}expand`));return u.length>0&&(n.expand=u),n}function ie(e,t){let n={bound:[],eaches:[],lists:[],records:[],issues:[]},r=s=>{if(s.hasAttributes()){let l=s.getAttribute(`${S}list`),u=s.getAttribute(`${S}record`),i=s.getAttribute(`${S}each`);if(l!==null||u!==null){let o=(l??u??"").trim();if(!t)n.issues.push({path:o.slice(0,80),reason:"unsupported",on:U(s)});else if(!ve.test(o))n.issues.push({path:o.slice(0,80),reason:"bad-path",on:U(s)});else if(l!==null)n.lists.push({el:s,collection:o,options:_t(s,n.issues)});else{let a=s.getAttribute(`${S}slug-param`),d=s.getAttribute(`${S}slug`)??(a===null?null:new URLSearchParams(s.ownerDocument.location?.search??"").get(a)),m=ie(s,!0);for(let v of m.records)n.issues.push({path:v.collection,reason:"unsupported",on:U(v.el)});n.lists.push(...m.lists),n.issues.push(...m.issues);let w=Ae(s,n.issues);w&&m.bound.unshift({el:s,d:w}),n.records.push({el:s,collection:o,slug:d!==null&&d.trim()!==""&&d.length<=200?d.trim():null,expand:ae(s.getAttribute(`${S}expand`)),block:{...m,lists:[],records:[],issues:[]}})}return}if(i!==null){n.eaches.push({el:s,path:i});return}let f=Ae(s,n.issues);f&&n.bound.push({el:s,d:f})}for(let l of Array.from(s.children))r(l)};for(let s of Array.from(e.children))r(s);return n}function ut(e){let t=new Map,n=(r,s=[])=>{let l=j(r)?.[0];if(!l)return;let u=t.get(l)??new Set;for(let i of s)u.add(i);t.set(l,u)};for(let{d:r}of e.bound){n(r.bind,r.expand);for(let[,s]of r.attrs)n(s,r.expand);n(r.show,r.expand),n(r.hide,r.expand);for(let s of(r.link??"").matchAll(/\{([^{}]*)\}/g))n(s[1],r.expand)}for(let r of e.eaches)n(r.path,ae(r.el.getAttribute(`${S}expand`)));return t}function V(e,t){let n=j(t);if(!n)return{status:"bad-path"};if(e.kind==="record")return{status:"value",value:pe(e.source,n)};if(n.length<2)return{status:"bad-path"};let r=e.singles.get(n[0]);return!r||r.status!=="ok"?{status:r?.status==="failed"?"failed":"missing"}:{status:"value",value:pe(r.value,n.slice(1))}}function Te(e){if(typeof e?.key!="string"||!Dt.test(e.key))throw new Error("Mapled: data-mapled-key takes the delivery key (mk_\u2026) \u2014 the one key that is safe in a page. A server key or a secret never goes into HTML.");if(typeof document>"u")throw new Error("Mapled: @mapled/vanilla fills a page \u2014 it runs in a browser. On a server read with @mapled/client.");let t=e.root??document.documentElement,n=t.ownerDocument,r=qe({key:e.key,apiUrl:e.apiUrl,appUrl:e.appUrl,fetch:e.fetch}),s={apiUrl:e.apiUrl,base:n.baseURI},l=new Map,u=c=>{let p=l.get(c);return p||l.set(c,p={attrs:new Map}),p},i=c=>{let p=u(c);p.nodes??(p.nodes=Array.from(c.childNodes))},f=(c,p)=>{let R=u(c);R.attrs.has(p)||R.attrs.set(p,c.getAttribute(p))},o=()=>{for(let[c,p]of l){p.nodes&&c.replaceChildren(...p.nodes);for(let[R,g]of p.attrs)g===null?c.removeAttribute(R):c.setAttribute(R,g)}l.clear()},a=[],d=0,m=(c,p,R)=>a.push({path:(p??"").trim().slice(0,120),reason:R,on:U(c)});function w(c,p,R){if(p.bind!==void 0){let g=V(R,p.bind);if(g.status!=="value")m(c,p.bind,g.status);else if(!W(c))m(c,p.bind,"refused-element");else{let b=c.localName==="img";if(b?(f(c,"src"),f(c,"srcset"),f(c,"hidden")):i(c),B(g.value))b?K(c,!1):c.textContent="",m(c,p.bind,"empty");else{let h;if(b)h=rt(c,g.value,s,p.width);else if(p.format==="markdown")h=tt(c,g.value,s);else if(p.format==="text")h=Re(c,g.value);else{let k=at(g.value,p.format,c.closest("[lang]")?.getAttribute("lang")??void 0);h=k===null?"type":Re(c,k)}h?m(c,p.bind,h):d++}}}for(let[g,b]of p.attrs){let h=V(R,b);if(h.status!=="value"){m(c,b,h.status);continue}if(f(c,g),B(h.value)){g==="alt"?c.setAttribute("alt",""):c.removeAttribute(g),m(c,b,"empty");continue}let k=c.getAttribute(g),A=st(c,g,h.value,s,p.width);A?(k===null&&c.removeAttribute(g),m(c,b,A)):d++}for(let[g,b]of[[p.show,!0],[p.hide,!1]]){if(g===void 0)continue;let h=V(R,g);if(h.status!=="value"){m(c,g,h.status);continue}f(c,"hidden");let k=!B(h.value)&&h.value!==!1;K(c,k===b),d++}if(p.link!==void 0){let g=[],b=He(p.link,s.base,h=>{let k=V(R,h.join("."));if(k.status==="value")return k.value;g.push(k.status)});if(g[0])m(c,p.link,g[0]);else if(!b.ok)m(c,b.path,b.reason);else{f(c,"href");let h=ot(c,b.url);h?m(c,p.link,h):d++}}}function v(c,p,R){let g=lt(c);if(!g){m(c,"template","unsupported");return}i(c);let b=[g];for(let k of Array.from(c.children))k.hasAttribute(`${S}empty`)&&(f(k,"hidden"),K(k,p.length===0),b.push(k));let h=[];for(let k of p){let A=g.content.cloneNode(!0);x(ie(A,!1),{kind:"record",source:k},R+1),h.push(...Array.from(A.childNodes))}c.replaceChildren(...b,...h),d++}function x(c,p,R){a.push(...c.issues);for(let{el:g,d:b}of c.bound)w(g,b,p);for(let g of c.eaches){let b=V(p,g.path);if(b.status!=="value"){m(g.el,g.path,b.status);continue}if(R>=Kt){m(g.el,g.path,"unsupported");continue}let h=b.value,k=Array.isArray(h)?h:B(h)?[]:[h];if(k.some(A=>A===null||typeof A!="object"||Array.isArray(A))){m(g.el,g.path,"type");continue}k.length===0&&m(g.el,g.path,"empty"),v(g.el,k.map(A=>({data:A})),R)}}async function O(c){try{let p=await c();return p===null?{status:"missing"}:{status:"ok",value:p}}catch(p){return p instanceof D&&p.status===404&&p.code!=="RELEASE_NOT_FOUND"?{status:"missing"}:{status:"failed"}}}let T=null,M="";async function $(c){let p=T===null;p&&t.setAttribute(`${S}state`,"loading");let R=await r.isPreview().catch(()=>!1),g=y=>{T=y,t.setAttribute(`${S}state`,y.state),t.toggleAttribute(`${S}preview`,y.preview);let E=y.issues.filter(I=>I.reason!=="empty"),C=E.map(I=>`${I.path} (${I.reason}) on ${I.on}`).join("; ");return C!==M&&(M=C,E.length>0&&console.warn(`[mapled] ${E.length===1?"1 place":`${E.length} places`} couldn't be filled: ${C}`)),n.dispatchEvent(new CustomEvent(p?"mapled:ready":"mapled:update",{detail:y})),y},b=null;if(!R)if(c!==void 0)b=c;else try{let y=await r.getRelease();if(y===null)return g({state:"unpublished",release:null,preview:R,bound:0,issues:[]});b=y.release}catch{return T!==null&&T.state==="ready"?T:g({state:"fallback",release:null,preview:R,bound:0,issues:[]})}let h=b===null?{}:{release:b},k=ie(t,!0),A=Ae(t,k.issues);A&&k.bound.unshift({el:t,d:A});let L=new Map,le=new Map,Y=new Map,J=[];for(let[y,E]of ut(k))J.push(O(()=>r.getSingle(y,{...h,...E.size>0?{expand:[...E]}:{}})).then(C=>L.set(y,C)));for(let y of k.lists){let E=lt(y.el),C=E?[...ut(ie(E.content,!1)).keys()]:[];J.push(O(async()=>(await r.getRecords(y.collection,{...y.options,...h,...C.length>0?{fields:C}:{}})).records).then(I=>le.set(y,I)))}for(let y of k.records){if(y.slug===null){Y.set(y,{status:"missing"});continue}let E=y.slug;J.push(O(()=>r.getRecordBySlug(y.collection,E,{...h,...y.expand.length>0?{expand:y.expand}:{}})).then(C=>Y.set(y,C)))}await Promise.all(J);let Ce=[...L.values(),...le.values(),...Y.values()];if(Ce.length>0&&Ce.every(y=>y.status==="failed"))return T!==null&&T.state==="ready"?T:g({state:"fallback",release:b,preview:R,bound:0,issues:[]});o(),a=[],d=0,x(k,{kind:"page",singles:L},0);for(let y of k.lists){let E=le.get(y);E?.status==="ok"?v(y.el,E.value,0):m(y.el,y.collection,E?.status??"failed")}for(let y of k.records){let E=Y.get(y);E?.status==="ok"?x(y.block,{kind:"record",source:E.value},0):m(y.el,y.collection,E?.status??"failed")}return Pe(),g({state:"ready",release:b,preview:R,bound:d,issues:a})}let Ee=new WeakSet,xe=new WeakSet;function Pe(){for(let c of Array.from(t.querySelectorAll(`form[${S}form]`)))Ee.has(c)||(Ee.add(c),c.addEventListener("submit",p=>{p.preventDefault(),ft(c)}))}async function ft(c){if(c.getAttribute(`${S}form-state`)==="sending")return;let p=(c.getAttribute(`${S}form`)??"").trim(),R=c.querySelector(`[${S}form-sent]`),g=c.querySelector(`[${S}form-failed]`),b=Array.from(c.querySelectorAll("button, input[type=submit]")).filter(L=>!L.disabled);c.setAttribute(`${S}form-state`,"sending");for(let L of b)L.disabled=!0;let h=c.querySelector(`input[type=checkbox][${S}consent]`),k=h?.checked?(h.labels?.[0]?.textContent??h.closest("label")?.textContent??"").trim().slice(0,500):"",A=ve.test(p)?await de(p,c,{key:e.key,apiUrl:e.apiUrl,fetch:e.fetch,...k?{consent:k}:{}}):{ok:!1,status:0,code:"BAD_FORM",message:"This form isn't set up yet."};for(let L of b)L.disabled=!1;c.setAttribute(`${S}form-state`,A.ok?"sent":"failed"),A.ok&&c.reset(),R&&K(R,A.ok),g&&(!A.ok&&(xe.has(g)||(g.textContent??"").trim()==="")&&(xe.add(g),g.textContent=A.message),K(g,!A.ok)),n.dispatchEvent(new CustomEvent("mapled:form",{detail:A.ok?{form:p,ok:!0}:{form:p,ok:!1,status:A.status,code:A.code}}))}Pe();let Oe=async c=>{try{return await $(c)}catch(p){return console.error(`[mapled] ${p instanceof Error?p.message:"The page couldn't be filled."}`),T??{state:"fallback",release:null,preview:!1,bound:0,issues:[]}}},G=Oe(),X=c=>G=G.then(()=>Oe(c)),$e=c=>{c.target?.closest?.(`[${S}end-preview]`)&&(c.preventDefault(),ee(),X())};n.addEventListener("click",$e);let Z=null;if(e.live){let c=typeof e.live=="number"?e.live:void 0;G.then(p=>{!p.preview&&Z===null&&(Z=r.watch(R=>{X(R.release)},{interval:c}))})}return{ready:G,get report(){return T},refresh:()=>X(),endPreview:()=>(ee(),X()),stop:()=>{Z?.(),Z=()=>{},n.removeEventListener("click",$e)},client:r}}var ct="0.1.0";var dt={version:ct,mount:Te,page:null};window.Mapled=dt;var _=document.currentScript;if(_ instanceof HTMLScriptElement&&_.hasAttribute("data-mapled-key")){let e=_.getAttribute("data-mapled-live"),t=e===null?NaN:parseInt(e,10),n=()=>{try{dt.page=Te({key:_.getAttribute("data-mapled-key")??"",..._.getAttribute("data-mapled-api")?{apiUrl:_.getAttribute("data-mapled-api")}:{},...e===null?{}:{live:Number.isFinite(t)?t:!0}})}catch(r){console.error(r instanceof Error?r.message:"Mapled couldn't start.")}};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",n,{once:!0}):n()}})();
|
|
7
|
+
`:"";continue}let a=mt(o);if(a){ue(e,a.text,M(e,`h${a.level}`,n),r),s++;continue}if($e(o)){M(e,"hr",n),s++;continue}if(Me(o)){let p=[];for(;s<t.length&&Me(t[s]);)p.push(t[s].replace(/^ {0,3}> ?/,"")),s++;Ce(e,p,M(e,"blockquote",n),r+1);continue}let f=J(o);if(f){s=Gt(e,t,s,f,n,r);continue}let i=[o.trimStart()];for(s++;s<t.length&&!xe(t[s])&&!Xt(t[s]);)i.push(t[s].trimStart()),s++;ue(e,i.join(`
|
|
8
|
+
`),M(e,"p",n),r)}}function Gt(e,t,n,r,s,o){let c=M(e,r.ordered?"ol":"ul",s);r.ordered&&r.start!==1&&c.setAttribute("start",String(r.start));let a=[],f=!1;for(;n<t.length;){let i=J(t[n]);if(!i||i.ordered!==r.ordered||i.indent!==r.indent||$e(t[n]))break;let p=i.indent+i.width,d=[i.rest];n++;let l=!1;for(;n<t.length;){let g=t[n];if(xe(g)){l=!0,d.push(""),n++;continue}if(Q(g)<p)break;l&&d.some(S=>S!=="")&&!J(g.slice(p))&&(f=!0),l=!1,d.push(g.slice(p)),n++}for(;d.length>0&&d[d.length-1]==="";)d.pop();if(l&&n<t.length){let g=J(t[n]);g&&g.ordered===r.ordered&&g.indent===r.indent&&(f=!0)}a.push({lines:d,el:M(e,"li",c)})}for(let i of a)if(Ce(e,i.lines,i.el,o+1),!f){for(let p of Array.from(i.el.childNodes))if(p.nodeName==="P"){for(;p.firstChild;)i.el.insertBefore(p.firstChild,p);i.el.removeChild(p)}}return n}var Le=e=>e===void 0||e===" "||e===`
|
|
9
|
+
`,gt=e=>e!==void 0&&/[\p{L}\p{N}]/u.test(e);function le(e,t,n,r,s=""){let o=Math.min(e.length,r);for(let c=n;c<o;c++){if(e[c]===t)return c;if(s.includes(e[c]))return-1}return-1}function ee(e,t,n){let r=t;for(;e[r]===n;)r++;return r}function ht(e,t){if(e[t]==="\\")return t+2;if(e[t]==="`"){let n=ee(e,t,"`"),r=yt(e,n,n-t);return r===-1?n:r+(n-t)}return-1}function yt(e,t,n){let r=e.indexOf("`",t);for(;r!==-1;){let s=ee(e,r,"`");if(s-r===n)return r;r=e.indexOf("`",s)}return-1}function Vt(e,t,n){let r=n[0],s=t;for(;s<e.length;){let o=ht(e,s);if(o!==-1){s=o;continue}if(e[s]!==r){s++;continue}let c=ee(e,s,r),a=c-s;if((n.length===1?a===1||a>=3:a>=n.length)&&s>t&&!Le(e[s-1])&&(r!=="_"||!gt(e[c])))return c-n.length;s=c}return-1}function Zt(e,t){let n=le(e,";",t+1,t+34,`
|
|
10
|
+
&`);if(n===-1)return null;let r=e.slice(t+1,n);if(r[0]==="#"){let o=r[1]==="x"||r[1]==="X",c=r.slice(o?2:1);if(!(o?/^[0-9a-fA-F]{1,6}$/:/^[0-9]{1,7}$/).test(c))return null;let a=parseInt(c,o?16:10);return{char:a>0&&a<=1114111&&!(a>=55296&&a<=57343)?String.fromCodePoint(a):"\uFFFD",end:n+1}}let s=Object.prototype.hasOwnProperty.call(ct,r)?ct[r]:void 0;return s===void 0?null:{char:s,end:n+1}}var bt=e=>e.replace(/\\([!-/:-@[-`{-~])/g,"$1");function Yt(e,t){if(e[t]!=="(")return null;let n=Math.min(e.length,t+ft+1024),r=t+1;for(;r<n&&Le(e[r])&&e[r]!==void 0;)r++;let s="";if(e[r]==="<"){let c=le(e,">",r+1,n,`
|
|
11
|
+
<`);if(c===-1)return null;s=e.slice(r+1,c),r=c+1}else{let c=r,a=0;for(;r<n;){let f=e[r];if(f==="\\"&&r+1<n){r+=2;continue}if(f===" "||f===`
|
|
12
|
+
`)break;if(f==="("&&a++,f===")"){if(a===0)break;a--}r++}s=e.slice(c,r)}for(;r<n&&(e[r]===" "||e[r]===`
|
|
13
|
+
`);)r++;let o=e[r];if(o==='"'||o==="'"||o==="("){let c=le(e,o==="("?")":o,r+1,n);if(c===-1)return null;for(r=c+1;r<n&&(e[r]===" "||e[r]===`
|
|
14
|
+
`);)r++}return e[r]!==")"?null:{address:bt(s),end:r+1}}function Oe(e,t,n=!1){let r=t+1;for(;r<e.length;){let o=ht(e,r);if(o!==-1){r=o;continue}if(!n&&e[r]==="!"&&e[r+1]==="["){let c=Oe(e,r+1,!0);if(!c)return null;r=c.end;continue}if(e[r]==="[")return null;if(e[r]==="]")break;r++}if(r>=e.length)return null;let s=Yt(e,r+1);return s?{label:e.slice(t+1,r),address:s.address,end:s.end}:null}var Jt=/^(?:[a-z][a-z0-9+.-]{1,31}:[^\s<>]*|[^\s<>@]+@[^\s<>@]+\.[^\s<>@]+)$/i;function ue(e,t,n,r){if(r>=dt){n.appendChild(e.doc.createTextNode(t));return}let s="",o=()=>{s!==""&&n.appendChild(e.doc.createTextNode(s)),s=""},c=new Set,a=0;for(;a<t.length;){let f=t[a];if(f==="\\"){let i=t[a+1];if(i===`
|
|
15
|
+
`)for(o(),M(e,"br",n),a+=2;t[a]===" ";)a++;else i!==void 0&&Wt.includes(i)?(s+=i,a+=2):(s+=f,a++);continue}if(f===`
|
|
16
|
+
`){let i=s.endsWith(" "),p=s.length;for(;p>0&&s[p-1]===" ";)p--;for(s=s.slice(0,p),i?(o(),M(e,"br",n)):s+=`
|
|
17
|
+
`,a++;t[a]===" ";)a++;continue}if(f==="`"){let i=ee(t,a,"`"),p=yt(t,i,i-a);if(p===-1){s+=t.slice(a,i),a=i;continue}let d=t.slice(i,p).replace(/\n/g," ");d.length>1&&d.startsWith(" ")&&d.endsWith(" ")&&d.trim()!==""&&(d=d.slice(1,-1)),o(),M(e,"code",n).textContent=d,a=p+(i-a);continue}if(f==="!"&&t[a+1]==="["){let i=Oe(t,a+1);if(i){let p=bt(i.label),d=H(i.address.trim(),e.base,"media");if(d===null)s+=p;else{o();let l=M(e,"img",n);l.setAttribute("src",d),l.setAttribute("alt",p),l.setAttribute("loading","lazy")}a=i.end;continue}}if(f==="["){let i=Oe(t,a);if(i){let p=H(i.address.trim(),e.base,"link");o();let d=p===null?n:M(e,"a",n);p!==null&&d.setAttribute("href",p),ue(e,i.label,d,r+1),a=i.end;continue}}if(f==="<"){let i=le(t,">",a+1,a+ft,`
|
|
18
|
+
<`),p=i===-1?null:t.slice(a+1,i);if(p!==null&&Jt.test(p)){let d=H(p.includes(":")?p:`mailto:${p}`,e.base,"link");if(d!==null){o();let l=M(e,"a",n);l.setAttribute("href",d),l.textContent=p,a=i+1;continue}}}if(f==="&"){let i=Zt(t,a);if(i){s+=i.char,a=i.end;continue}}if(f==="*"||f==="_"||f==="~"){let i=ee(t,a,f),p=i-a,d=!Le(t[i])&&(f!=="_"||!gt(t[a-1])),l=f==="~"?p>=2?[["~~",["del"]]]:[]:[...p>=3?[[f.repeat(3),["em","strong"]]]:[],...p>=2?[[f.repeat(2),["strong"]]]:[],[f,["em"]]],g=!1;if(d)for(let[S,x]of l){if(c.has(S))continue;let P=a+S.length,b=Vt(t,P,S);if(b===-1){c.add(S);continue}if(b===P)continue;o();let C=n;for(let L of x)C=M(e,L,C);ue(e,t.slice(P,b),C,r+1),a=b+S.length,g=!0;break}g||(s+=t.slice(a,i),a=i);continue}s+=f,a++}o()}var Qt="http://www.w3.org/1999/xhtml",en=new Set(["script","style","iframe","frame","frameset","object","embed","applet","base","link","meta","template","noscript"]),ce={href:{on:["a","area"],holds:"link"},src:{on:["img","video","audio","source"],holds:"media"},alt:{on:["img","area"],holds:"text"},title:{on:null,holds:"text"},"aria-label":{on:null,holds:"text"},placeholder:{on:["input","textarea"],holds:"text"},datetime:{on:["time","del","ins"],holds:"text"}},de=e=>e.namespaceURI===Qt,te=e=>!en.has(e.localName.toLowerCase());function Ne(e,t){if(!te(e))return"refused-element";let n=Ee(t);return n===null?"type":(e.textContent=n,null)}function wt(e,t,n){return!te(e)||!de(e)?"refused-element":typeof t!="string"?"type":(e.replaceChildren(pt(t,e.ownerDocument,n.base)),null)}function kt(e,t,n){return typeof e!="string"?null:lt(e)?it(e,{...t.apiUrl?{apiUrl:t.apiUrl}:{},...n?{width:n}:{}}):H(e,t.base,"media")}function St(e,t,n,r){if(!de(e)||e.localName!=="img")return"refused-element";if(typeof t!="string")return"type";let s=kt(t,n,r);return s===null?"unsafe-url":(e.removeAttribute("srcset"),e.setAttribute("src",s),null)}function At(e,t,n,r,s){let o=t.toLowerCase(),c=Object.prototype.hasOwnProperty.call(ce,o)?ce[o]:void 0;if(!c)return"unsupported";if(!te(e)||!de(e))return"refused-element";if(c.on!==null&&!c.on.includes(e.localName))return"unsupported";if(c.holds==="text"){let f=Ee(n);return f===null?"type":(e.setAttribute(o,f),null)}if(typeof n!="string")return"type";let a=c.holds==="media"?kt(n,r,e.localName==="img"?s:void 0):H(n,r.base,"link");return a===null?"unsafe-url":(e.setAttribute(o,a),null)}function vt(e,t){return!de(e)||e.localName!=="a"&&e.localName!=="area"?"unsupported":(e.setAttribute("href",t),null)}function X(e,t){t?e.removeAttribute("hidden"):e.setAttribute("hidden","")}var Rt=["text","markdown","date","datetime","number"],tn=/^\d{4}-\d{2}-\d{2}$/,nn=/^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;function Tt(e,t,n){let r=n&&/^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$/.test(n)?n:void 0;try{if(t==="number"){let c=typeof e=="number"?e:typeof e=="string"&&e.trim()!==""?Number(e):NaN;return Number.isFinite(c)?new Intl.NumberFormat(r).format(c):null}if(typeof e!="string"||!nn.test(e))return null;let s=new Date(e);if(Number.isNaN(s.getTime()))return null;let o=tn.test(e);return new Intl.DateTimeFormat(r,{dateStyle:"long",...t==="datetime"&&!o?{timeStyle:"short"}:{},...o?{timeZone:"UTC"}:{}}).format(s)}catch{return null}}var A="data-mapled-",rn=new Set(["bind","format","width","show","hide","link","list","each","record","slug","slug-param","sort","limit","filter","expand","empty","form","form-state","form-sent","form-failed","consent","end-preview","state","preview","key","api","live","locale"]),Ie=/^[a-z0-9][a-z0-9_-]{0,63}$/,sn=/^mk_[A-Za-z0-9_]{8,128}$/,on=4,an=100,ln=2;function re(e,t=0){if(e===null||typeof e!="object"||Array.isArray(e))return!1;let{data:n,expanded:r}=e;if(n===null||typeof n!="object"||Array.isArray(n))return!1;if(r===void 0)return!0;if(r===null||typeof r!="object"||Array.isArray(r))return!1;let s=Object.values(r);return t>=ln?s.length===0:s.every(o=>o===null||(Array.isArray(o)?o.every(c=>re(c,t+1)):re(o,t+1)))}var un=e=>Array.isArray(e)&&e.every(t=>re(t));function _(e){let t=e.getAttribute("id");return`${e.localName}${t&&/^[\w-]{1,40}$/.test(t)?`#${t}`:""}`}var pe=e=>(e??"").split(",").map(t=>t.trim()).filter(t=>/^[a-z0-9][a-z0-9_.-]{0,127}$/.test(t)),Et=e=>Array.from(e.children).find(t=>t.localName==="template")??null;function Ue(e,t){let n=null,r=()=>n??(n={format:"text",attrs:[],expand:[]});for(let a of Array.from(e.attributes)){if(!a.name.startsWith(A))continue;let f=a.name.slice(A.length);if(f==="bind")r().bind=a.value;else if(f.startsWith("bind-")){let i=f.slice(5);Object.prototype.hasOwnProperty.call(ce,i)?r().attrs.push([i,a.value]):t.push({path:a.value.slice(0,80),reason:"unsupported",on:`${_(e)}[${a.name.slice(0,60)}]`})}else f==="show"?r().show=a.value:f==="hide"?r().hide=a.value:f==="link"?r().link=a.value:rn.has(f)||t.push({path:a.value.slice(0,80),reason:"unsupported",on:`${_(e)}[${a.name.slice(0,60)}]`})}if(!n)return null;let s=n,o=e.getAttribute(`${A}format`);o!==null&&(Rt.includes(o)?s.format=o:t.push({path:o.slice(0,80),reason:"bad-path",on:`${_(e)}[${A}format]`}));let c=parseInt(e.getAttribute(`${A}width`)??"",10);return c>=16&&c<=4096&&(s.width=c),s.expand=pe(e.getAttribute(`${A}expand`)),s}function cn(e,t){let n={},r=e.getAttribute(`${A}sort`);r!==null&&(/^-?[a-z0-9][a-z0-9_-]{0,63}$/.test(r.trim())?n.sort=r.trim():t.push({path:r.slice(0,80),reason:"bad-path",on:`${_(e)}[${A}sort]`}));let s=parseInt(e.getAttribute(`${A}limit`)??"",10);s>=1&&s<=100&&(n.limit=s);let o=e.getAttribute(`${A}filter`);if(o!==null)for(let a of o.split(";")){if(a.trim()==="")continue;let f=a.indexOf("="),i=f===-1?"":a.slice(0,f).trim(),p=f===-1?"":a.slice(f+1).trim();Ie.test(i)&&p!==""&&p.length<=200?(n.filter??(n.filter={}))[i]=p:t.push({path:a.trim().slice(0,80),reason:"bad-path",on:`${_(e)}[${A}filter]`})}let c=pe(e.getAttribute(`${A}expand`));return c.length>0&&(n.expand=c),n}function fe(e,t){let n={bound:[],eaches:[],lists:[],records:[],issues:[]},r=s=>{if(s.hasAttributes()){let o=s.getAttribute(`${A}list`),c=s.getAttribute(`${A}record`),a=s.getAttribute(`${A}each`);if(o!==null||c!==null){let i=(o??c??"").trim();if(!t)n.issues.push({path:i.slice(0,80),reason:"unsupported",on:_(s)});else if(!Ie.test(i))n.issues.push({path:i.slice(0,80),reason:"bad-path",on:_(s)});else if(o!==null)n.lists.push({el:s,collection:i,options:cn(s,n.issues)});else{let p=s.getAttribute(`${A}slug-param`),d=s.getAttribute(`${A}slug`)??(p===null?null:new URLSearchParams(s.ownerDocument.location?.search??"").get(p)),l=fe(s,!0);for(let S of l.records)n.issues.push({path:S.collection,reason:"unsupported",on:_(S.el)});n.lists.push(...l.lists),n.issues.push(...l.issues);let g=Ue(s,n.issues);g&&l.bound.unshift({el:s,d:g}),n.records.push({el:s,collection:i,slug:d!==null&&d.trim()!==""&&d.length<=200?d.trim():null,expand:pe(s.getAttribute(`${A}expand`)),block:{...l,lists:[],records:[],issues:[]}})}return}if(a!==null){n.eaches.push({el:s,path:a});return}let f=Ue(s,n.issues);f&&n.bound.push({el:s,d:f})}for(let o of Array.from(s.children))r(o)};for(let s of Array.from(e.children))r(s);return n}function xt(e){let t=new Map,n=(r,s=[])=>{let o=Z(r)?.[0];if(!o)return;let c=t.get(o)??new Set;for(let a of s)c.add(a);t.set(o,c)};for(let{d:r}of e.bound){n(r.bind,r.expand);for(let[,s]of r.attrs)n(s,r.expand);n(r.show,r.expand),n(r.hide,r.expand);for(let s of(r.link??"").matchAll(/\{([^{}]*)\}/g))n(s[1],r.expand)}for(let r of e.eaches)n(r.path,pe(r.el.getAttribute(`${A}expand`)));return t}function ne(e,t){let n=Z(t);if(!n)return{status:"bad-path"};if(e.kind==="record")return{status:"value",value:Te(e.source,n)};if(n.length<2)return{status:"bad-path"};let r=e.singles.get(n[0]);return!r||r.status!=="ok"?{status:r?.status==="failed"?"failed":"missing"}:{status:"value",value:Te(r.value,n.slice(1))}}function De(e){if(typeof e?.key!="string"||!sn.test(e.key))throw new Error("Mapled: data-mapled-key takes the delivery key (mk_\u2026) \u2014 the one key that is safe in a page. A server key or a secret never goes into HTML.");if(typeof document>"u")throw new Error("Mapled: @mapled/vanilla fills a page \u2014 it runs in a browser. On a server read with @mapled/client.");let t=e.root??document.documentElement,n=t.ownerDocument,r=at({key:e.key,apiUrl:e.apiUrl,appUrl:e.appUrl,fetch:e.fetch,...e.locale?{locale:e.locale}:{}}),s={apiUrl:e.apiUrl,base:n.baseURI},o=new Map,c=u=>{let m=o.get(u);return m||o.set(u,m={attrs:new Map}),m},a=u=>{let m=c(u);m.nodes??(m.nodes=Array.from(u.childNodes))},f=(u,m)=>{let v=c(u);v.attrs.has(m)||v.attrs.set(m,u.getAttribute(m))},i=()=>{for(let[u,m]of o){m.nodes&&u.replaceChildren(...m.nodes);for(let[v,h]of m.attrs)h===null?u.removeAttribute(v):u.setAttribute(v,h)}o.clear()},p=[],d=0,l=(u,m,v)=>p.push({path:(m??"").trim().slice(0,120),reason:v,on:_(u)});function g(u,m,v){if(m.bind!==void 0){let h=ne(v,m.bind);if(h.status!=="value")l(u,m.bind,h.status);else if(!te(u))l(u,m.bind,"refused-element");else{let w=u.localName==="img";if(w?(f(u,"src"),f(u,"srcset"),f(u,"hidden")):a(u),Y(h.value))w?X(u,!1):u.textContent="",l(u,m.bind,"empty");else{let k;if(w)k=St(u,h.value,s,m.width);else if(m.format==="markdown")k=wt(u,h.value,s);else if(m.format==="text")k=Ne(u,h.value);else{let R=Tt(h.value,m.format,u.closest("[lang]")?.getAttribute("lang")??void 0);k=R===null?"type":Ne(u,R)}k?l(u,m.bind,k):d++}}}for(let[h,w]of m.attrs){let k=ne(v,w);if(k.status!=="value"){l(u,w,k.status);continue}if(f(u,h),Y(k.value)){h==="alt"?u.setAttribute("alt",""):u.removeAttribute(h),l(u,w,"empty");continue}let R=u.getAttribute(h),T=At(u,h,k.value,s,m.width);T?(R===null&&u.removeAttribute(h),l(u,w,T)):d++}for(let[h,w]of[[m.show,!0],[m.hide,!1]]){if(h===void 0)continue;let k=ne(v,h);if(k.status!=="value"){l(u,h,k.status);continue}f(u,"hidden");let R=!Y(k.value)&&k.value!==!1;X(u,R===w),d++}if(m.link!==void 0){let h=[],w=ut(m.link,s.base,k=>{let R=ne(v,k.join("."));if(R.status==="value")return R.value;h.push(R.status)});if(h[0])l(u,m.link,h[0]);else if(!w.ok)l(u,w.path,w.reason);else{f(u,"href");let k=vt(u,w.url);k?l(u,m.link,k):d++}}}function S(u,m,v){let h=Et(u);if(!h){l(u,"template","unsupported");return}a(u);let w=[h];for(let R of Array.from(u.children))R.hasAttribute(`${A}empty`)&&(f(R,"hidden"),X(R,m.length===0),w.push(R));let k=[];for(let R of m.slice(0,an)){let T=h.content.cloneNode(!0);x(fe(T,!1),{kind:"record",source:R},v+1),k.push(...Array.from(T.childNodes))}u.replaceChildren(...w,...k),d++}function x(u,m,v){p.push(...u.issues);for(let{el:h,d:w}of u.bound)g(h,w,m);for(let h of u.eaches){let w=ne(m,h.path);if(w.status!=="value"){l(h.el,h.path,w.status);continue}if(v>=on){l(h.el,h.path,"unsupported");continue}let k=w.value,R=Array.isArray(k)?k:Y(k)?[]:[k];if(R.some(T=>T===null||typeof T!="object"||Array.isArray(T))){l(h.el,h.path,"type");continue}R.length===0&&l(h.el,h.path,"empty"),S(h.el,R.map(T=>({data:T})),v)}}async function P(u,m){try{let v=await u();return v===null?{status:"missing"}:m(v)?{status:"ok",value:v}:{status:"failed",answered:!0}}catch(v){return v instanceof W&&v.status===404&&v.code!=="RELEASE_NOT_FOUND"?{status:"missing"}:{status:"failed"}}}let b=null,C="",L=null,$,z=!1,N=0,U=-1,G=3,O=typeof e.live=="number"?e.live:void 0;function Fe(){i(),b={state:"fallback",release:null,preview:!1,bound:0,issues:[]},t.setAttribute(`${A}state`,"fallback"),t.removeAttribute(`${A}preview`)}function _e(){N++,clearTimeout($),b?.preview&&Fe()}function Ot(){clearTimeout($);let u=B();if(!u||z)return;let m=Date.parse(u.expiresAt)-Date.now();$=setTimeout(()=>{B()===null&&_e(),ge()},Math.min(Math.max(m,0)+1,2147483647))}async function je(u,m=1){let v=b===null;v&&t.setAttribute(`${A}state`,"loading");let h=N,w=await r.isPreview().catch(()=>!1),k=w?B()?.draftToken??null:null;if(b!==null&&b.preview&&!w&&Fe(),!w&&u!==void 0&&b!==null&&b.state==="ready"&&!b.preview&&b.release!==null&&u<=b.release)return b;let R=y=>{b=y,U=h,t.setAttribute(`${A}state`,y.state),t.toggleAttribute(`${A}preview`,y.preview);let E=y.issues.filter(F=>F.reason!=="empty"),I=E.map(F=>`${F.path} (${F.reason}) on ${F.on}`).join("; ");return I!==C&&(C=I,E.length>0&&console.warn(`[mapled] ${E.length===1?"1 place":`${E.length} places`} couldn't be filled: ${I}`)),n.dispatchEvent(new CustomEvent(v?"mapled:ready":"mapled:update",{detail:y})),y.preview?Ot():(clearTimeout($),e.live&&L===null&&(L=r.watch(F=>{ge(F.release)},{interval:O,from:y.state==="ready"?y.release:null}))),y},T=null;if(!w)if(u!==void 0)T=u;else try{let y=await r.getRelease();if(y===null)return i(),R({state:"unpublished",release:null,preview:w,bound:0,issues:[]});T=y.release}catch{return b!==null&&b.state==="ready"?b:R({state:"fallback",release:null,preview:w,bound:0,issues:[]})}let D=T===null?{}:{release:T},j=fe(t,!0),We=Ue(t,j.issues);We&&j.bound.unshift({el:t,d:We});let he=new Map,ye=new Map,se=new Map,oe=[];for(let[y,E]of xt(j))oe.push(P(()=>r.getSingle(y,{...D,...E.size>0?{expand:[...E]}:{}}),re).then(I=>he.set(y,I)));for(let y of j.lists){let E=Et(y.el),I=E?[...xt(fe(E.content,!1)).keys()]:[];oe.push(P(async()=>(await r.getRecords(y.collection,{...y.options,...D,...I.length>0?{fields:I}:{}})).records,un).then(F=>ye.set(y,F)))}for(let y of j.records){if(y.slug===null){se.set(y,{status:"missing"});continue}let E=y.slug;oe.push(P(()=>r.getRecordBySlug(y.collection,E,{...D,...y.expand.length>0?{expand:y.expand}:{}}),re).then(I=>se.set(y,I)))}await Promise.all(oe);let Xe=await r.isPreview().catch(()=>!1)?B()?.draftToken??null:null;if(h!==N||Xe!==k)return m<G?je(u,m+1):(i(),R({state:"fallback",release:null,preview:Xe!==null,bound:0,issues:[]}));let Ge=[...he.values(),...ye.values(),...se.values()];if(Ge.length>0&&Ge.every(y=>y.status==="failed"&&!y.answered))return b!==null&&b.state==="ready"?b:R({state:"fallback",release:T,preview:w,bound:0,issues:[]});i(),p=[],d=0,x(j,{kind:"page",singles:he},0);for(let y of j.lists){let E=ye.get(y);E?.status==="ok"?S(y.el,E.value,0):l(y.el,y.collection,E?.status??"failed")}for(let y of j.records){let E=se.get(y);E?.status==="ok"?x(y.block,{kind:"record",source:E.value},0):l(y.el,y.collection,E?.status??"failed")}return He(),R({state:"ready",release:T,preview:w,bound:d,issues:p})}let Ke=new WeakSet,Be=new WeakSet;function He(){for(let u of Array.from(t.querySelectorAll(`form[${A}form]`)))Ke.has(u)||(Ke.add(u),u.addEventListener("submit",m=>{m.preventDefault(),$t(u)}))}async function $t(u){if(u.getAttribute(`${A}form-state`)==="sending")return;let m=(u.getAttribute(`${A}form`)??"").trim(),v=u.querySelector(`[${A}form-sent]`),h=u.querySelector(`[${A}form-failed]`),w=Array.from(u.querySelectorAll("button, input[type=submit]")).filter(D=>!D.disabled);u.setAttribute(`${A}form-state`,"sending");for(let D of w)D.disabled=!0;let k=u.querySelector(`input[type=checkbox][${A}consent]`),R=k?.checked?(k.labels?.[0]?.textContent??k.closest("label")?.textContent??"").trim().slice(0,500):"",T=Ie.test(m)?await ve(m,u,{key:e.key,apiUrl:e.apiUrl,fetch:e.fetch,...R?{consent:R}:{}}):{ok:!1,status:0,code:"BAD_FORM",message:"This form isn't set up yet."};for(let D of w)D.disabled=!1;u.setAttribute(`${A}form-state`,T.ok?"sent":"failed"),T.ok&&u.reset(),v&&X(v,T.ok),h&&(!T.ok&&(Be.has(h)||(h.textContent??"").trim()==="")&&(Be.add(h),h.textContent=T.message),X(h,!T.ok)),n.dispatchEvent(new CustomEvent("mapled:form",{detail:T.ok?{form:m,ok:!0}:{form:m,ok:!1,status:T.status,code:T.code}}))}He();let me=async u=>{try{return await je(u)}catch(m){return console.error(`[mapled] ${m instanceof Error?m.message:"The page couldn't be filled."}`),b??{state:"fallback",release:null,preview:!1,bound:0,issues:[]}}},V=me(),ge=u=>V=V.then(()=>me(u)),qe=()=>{Se(),_e();let u=N;return V=V.then(()=>U>=u&&b!==null?b:me())},ze=u=>{u.target?.closest?.(`[${A}end-preview]`)&&(u.preventDefault(),qe())};return n.addEventListener("click",ze),{ready:V,get report(){return b},refresh:()=>ge(),endPreview:qe,stop:()=>{z=!0,L?.(),L=()=>{},clearTimeout($),n.removeEventListener("click",ze)},client:r}}var Pt="0.2.1";var Mt={version:Pt,mount:De,page:null};window.Mapled=Mt;var q=document.currentScript;if(q instanceof HTMLScriptElement&&q.hasAttribute("data-mapled-key")){let e=q.getAttribute("data-mapled-live"),t=e===null?NaN:parseInt(e,10),n=()=>{try{Mt.page=De({key:q.getAttribute("data-mapled-key")??"",...q.getAttribute("data-mapled-api")?{apiUrl:q.getAttribute("data-mapled-api")}:{},...q.getAttribute("data-mapled-locale")?{locale:q.getAttribute("data-mapled-locale")}:{},...e===null?{}:{live:Number.isFinite(t)?t:!0}})}catch(r){console.error(r instanceof Error?r.message:"Mapled couldn't start.")}};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",n,{once:!0}):n()}})();
|
package/dist/mapled.js.sha384
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
sha384-
|
|
1
|
+
sha384-6apTHYxXrozzwFCqNU+70Z9pa/FDnhArNomcKQNWbeKRX4scjFiOc9d7wWKCOvTU
|
package/dist/markdown.js
CHANGED
|
@@ -445,7 +445,12 @@ function inline(ctx, text, parent, depth) {
|
|
|
445
445
|
}
|
|
446
446
|
if (ch === "\n") {
|
|
447
447
|
const hard = buffer.endsWith(" ");
|
|
448
|
-
|
|
448
|
+
// trailing spaces go — read from the end: a pattern anchored there would try every
|
|
449
|
+
// space of a long run inside the line, once per soft break (quadratic, found by review)
|
|
450
|
+
let end = buffer.length;
|
|
451
|
+
while (end > 0 && buffer[end - 1] === " ")
|
|
452
|
+
end--;
|
|
453
|
+
buffer = buffer.slice(0, end);
|
|
449
454
|
if (hard) {
|
|
450
455
|
flush();
|
|
451
456
|
make(ctx, "br", parent);
|
package/dist/runtime.d.ts
CHANGED
|
@@ -44,7 +44,8 @@ export type Issue = {
|
|
|
44
44
|
};
|
|
45
45
|
export type Report = {
|
|
46
46
|
/** ready — filled from Mapled; unpublished — nothing is published yet;
|
|
47
|
-
fallback — Mapled couldn't be reached,
|
|
47
|
+
fallback — the page is as written: Mapled couldn't be reached, or
|
|
48
|
+
preview just ended and what is published is on its way */
|
|
48
49
|
state: "ready" | "unpublished" | "fallback";
|
|
49
50
|
release: number | null;
|
|
50
51
|
preview: boolean;
|
|
@@ -56,6 +57,10 @@ export type MountOptions = {
|
|
|
56
57
|
/** The project's delivery key — the one key that is safe in a page. */
|
|
57
58
|
key: string;
|
|
58
59
|
apiUrl?: string;
|
|
60
|
+
/** Read in this language: a translated field shows in it, or in the
|
|
61
|
+
first language of the project's fallback chain that has a value.
|
|
62
|
+
One of the project's languages (`pt_br` → `pt-BR`). */
|
|
63
|
+
locale?: string;
|
|
59
64
|
/** Mapled's app — where an expired preview link sends the person. */
|
|
60
65
|
appUrl?: string;
|
|
61
66
|
/** Where to look for attributes (default: the whole document). */
|
package/dist/runtime.js
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
Paths start at a single from the document (`home.headline`) and at the
|
|
29
29
|
record inside a list, an each or a record (`title`, `author.name`). */
|
|
30
|
-
import { createClient, endPreview, MapledError, submitForm } from "@mapled/client";
|
|
30
|
+
import { createClient, endPreview, MapledError, previewSession, submitForm } from "@mapled/client";
|
|
31
31
|
import { isEmpty, parsePath, valueAt } from "./paths.js";
|
|
32
32
|
import { ATTRIBUTES, FORMATS, formatted, setVisible, writable, writeAttribute, writeImage, writeLink, writeMarkdown, writeText } from "./sinks.js";
|
|
33
33
|
import { fillTemplate } from "./url.js";
|
|
@@ -41,11 +41,36 @@ const P = "data-mapled-";
|
|
|
41
41
|
/** Every attribute the runtime reads or writes, besides `bind-…`. */
|
|
42
42
|
export const NAMES = new Set([
|
|
43
43
|
"bind", "format", "width", "show", "hide", "link", "list", "each", "record", "slug", "slug-param", "sort", "limit", "filter", "expand", "empty",
|
|
44
|
-
"form", "form-state", "form-sent", "form-failed", "consent", "end-preview", "state", "preview", "key", "api", "live",
|
|
44
|
+
"form", "form-state", "form-sent", "form-failed", "consent", "end-preview", "state", "preview", "key", "api", "live", "locale",
|
|
45
45
|
]);
|
|
46
46
|
const KEY = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
47
47
|
const DELIVERY_KEY = /^mk_[A-Za-z0-9_]{8,128}$/;
|
|
48
48
|
const MAX_EACH_DEPTH = 4;
|
|
49
|
+
/** The most a template repeats: the API's page, and the most items a
|
|
50
|
+
repeatable group holds — an answer that brings more is cut here. */
|
|
51
|
+
const MAX_ITEMS = 100;
|
|
52
|
+
/** How deep `expand` reaches: `author`, `related.author`. */
|
|
53
|
+
const MAX_EXPAND_DEPTH = 2;
|
|
54
|
+
/** A record as Mapled serves one — what every path walks: `data` an
|
|
55
|
+
object; `expanded`, when there, one linked record, a list of them or
|
|
56
|
+
null under each key, no deeper than `expand` can ask. An answer is
|
|
57
|
+
data, and so is its shape: what isn't a record is read as no answer. */
|
|
58
|
+
function isRecord(value, depth = 0) {
|
|
59
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
60
|
+
return false;
|
|
61
|
+
const { data, expanded } = value;
|
|
62
|
+
if (data === null || typeof data !== "object" || Array.isArray(data))
|
|
63
|
+
return false;
|
|
64
|
+
if (expanded === undefined)
|
|
65
|
+
return true;
|
|
66
|
+
if (expanded === null || typeof expanded !== "object" || Array.isArray(expanded))
|
|
67
|
+
return false;
|
|
68
|
+
const linked = Object.values(expanded);
|
|
69
|
+
if (depth >= MAX_EXPAND_DEPTH)
|
|
70
|
+
return linked.length === 0;
|
|
71
|
+
return linked.every((one) => one === null || (Array.isArray(one) ? one.every((r) => isRecord(r, depth + 1)) : isRecord(one, depth + 1)));
|
|
72
|
+
}
|
|
73
|
+
const isRecordList = (value) => Array.isArray(value) && value.every((r) => isRecord(r));
|
|
49
74
|
/** The element as the page names it: `a#cta`. The id is the author's. */
|
|
50
75
|
function nameOf(el) {
|
|
51
76
|
const id = el.getAttribute("id");
|
|
@@ -235,7 +260,13 @@ export function mount(options) {
|
|
|
235
260
|
throw new Error("Mapled: @mapled/vanilla fills a page — it runs in a browser. On a server read with @mapled/client.");
|
|
236
261
|
const root = options.root ?? document.documentElement;
|
|
237
262
|
const doc = root.ownerDocument;
|
|
238
|
-
const client = createClient({
|
|
263
|
+
const client = createClient({
|
|
264
|
+
key: options.key,
|
|
265
|
+
apiUrl: options.apiUrl,
|
|
266
|
+
appUrl: options.appUrl,
|
|
267
|
+
fetch: options.fetch,
|
|
268
|
+
...(options.locale ? { locale: options.locale } : {}),
|
|
269
|
+
});
|
|
239
270
|
const sinks = { apiUrl: options.apiUrl, base: doc.baseURI };
|
|
240
271
|
/** what the author wrote, for everything a run changed: the next run starts from it */
|
|
241
272
|
const originals = new Map();
|
|
@@ -392,7 +423,7 @@ export function mount(options) {
|
|
|
392
423
|
kept.push(child);
|
|
393
424
|
}
|
|
394
425
|
const items = [];
|
|
395
|
-
for (const source of sources) {
|
|
426
|
+
for (const source of sources.slice(0, MAX_ITEMS)) {
|
|
396
427
|
const clone = template.content.cloneNode(true);
|
|
397
428
|
fill(compile(clone, false), { kind: "record", source }, depth + 1);
|
|
398
429
|
items.push(...Array.from(clone.childNodes));
|
|
@@ -426,10 +457,13 @@ export function mount(options) {
|
|
|
426
457
|
repeat(each.el, items.map((item) => ({ data: item })), depth);
|
|
427
458
|
}
|
|
428
459
|
}
|
|
429
|
-
async function read(load) {
|
|
460
|
+
async function read(load, wellFormed) {
|
|
430
461
|
try {
|
|
431
462
|
const value = await load();
|
|
432
|
-
|
|
463
|
+
if (value === null)
|
|
464
|
+
return { status: "missing" };
|
|
465
|
+
// an answer of another shape than Mapled's is no answer: the place keeps the author's HTML
|
|
466
|
+
return wellFormed(value) ? { status: "ok", value } : { status: "failed", answered: true };
|
|
433
467
|
}
|
|
434
468
|
catch (err) {
|
|
435
469
|
// an unknown collection or single is the page's mistake, not an outage
|
|
@@ -438,13 +472,64 @@ export function mount(options) {
|
|
|
438
472
|
}
|
|
439
473
|
let last = null;
|
|
440
474
|
let warned = "";
|
|
441
|
-
|
|
475
|
+
let stopWatching = null;
|
|
476
|
+
let expiry;
|
|
477
|
+
let stopped = false;
|
|
478
|
+
/** moves when preview is left: a fill that began before it commits nothing */
|
|
479
|
+
let generation = 0;
|
|
480
|
+
/** the generation of the last fill that committed: an exit's own fill is skipped when one already has */
|
|
481
|
+
let committed = -1;
|
|
482
|
+
/** how many times a fill starts over when the session changed under it */
|
|
483
|
+
const MAX_ATTEMPTS = 3;
|
|
484
|
+
const interval = typeof options.live === "number" ? options.live : undefined;
|
|
485
|
+
/** Preview is over — the page's own way out, or the session's end: the
|
|
486
|
+
page is as written again, and says so, before anything is asked of
|
|
487
|
+
Mapled; what is published follows when it is in hand. */
|
|
488
|
+
function leave() {
|
|
489
|
+
restore();
|
|
490
|
+
last = { state: "fallback", release: null, preview: false, bound: 0, issues: [] };
|
|
491
|
+
root.setAttribute(`${P}state`, "fallback");
|
|
492
|
+
root.removeAttribute(`${P}preview`);
|
|
493
|
+
}
|
|
494
|
+
/** Preview ends now, not after the fill in flight: the draft goes at
|
|
495
|
+
once, and whatever that fill brings back is dropped. */
|
|
496
|
+
function drop() {
|
|
497
|
+
generation++;
|
|
498
|
+
clearTimeout(expiry);
|
|
499
|
+
if (last?.preview)
|
|
500
|
+
leave();
|
|
501
|
+
}
|
|
502
|
+
/** The session's end is a fill of its own: no draft is shown past it —
|
|
503
|
+
the timer stays set through fills that fail or stall. */
|
|
504
|
+
function watchExpiry() {
|
|
505
|
+
clearTimeout(expiry);
|
|
506
|
+
const session = previewSession();
|
|
507
|
+
if (!session || stopped)
|
|
508
|
+
return;
|
|
509
|
+
const left = Date.parse(session.expiresAt) - Date.now();
|
|
510
|
+
expiry = setTimeout(() => {
|
|
511
|
+
if (previewSession() === null)
|
|
512
|
+
drop();
|
|
513
|
+
void refresh();
|
|
514
|
+
}, Math.min(Math.max(left, 0) + 1, 0x7fffffff));
|
|
515
|
+
}
|
|
516
|
+
async function run(known, attempt = 1) {
|
|
442
517
|
const first = last === null;
|
|
443
518
|
if (first)
|
|
444
519
|
root.setAttribute(`${P}state`, "loading");
|
|
520
|
+
const mine = generation;
|
|
445
521
|
const preview = await client.isPreview().catch(() => false);
|
|
522
|
+
// the session the drafts are read with: an answer read with another one is not this fill's
|
|
523
|
+
const session = preview ? (previewSession()?.draftToken ?? null) : null;
|
|
524
|
+
// a tab that left preview, or whose session ended, shows nothing of the drafts — before anything is asked
|
|
525
|
+
if (last !== null && last.preview && !preview)
|
|
526
|
+
leave();
|
|
527
|
+
// releases only grow (a rollback is a new one): a fill pinned to one the page already shows filled, or an older one, has nothing to add — a page left as written keeps trying
|
|
528
|
+
if (!preview && known !== undefined && last !== null && last.state === "ready" && !last.preview && last.release !== null && known <= last.release)
|
|
529
|
+
return last;
|
|
446
530
|
const finish = (report) => {
|
|
447
531
|
last = report;
|
|
532
|
+
committed = mine;
|
|
448
533
|
root.setAttribute(`${P}state`, report.state);
|
|
449
534
|
root.toggleAttribute(`${P}preview`, report.preview);
|
|
450
535
|
const loud = report.issues.filter((i) => i.reason !== "empty");
|
|
@@ -455,6 +540,16 @@ export function mount(options) {
|
|
|
455
540
|
console.warn(`[mapled] ${loud.length === 1 ? "1 place" : `${loud.length} places`} couldn't be filled: ${text}`);
|
|
456
541
|
}
|
|
457
542
|
doc.dispatchEvent(new CustomEvent(first ? "mapled:ready" : "mapled:update", { detail: report }));
|
|
543
|
+
if (report.preview)
|
|
544
|
+
watchExpiry();
|
|
545
|
+
else {
|
|
546
|
+
clearTimeout(expiry);
|
|
547
|
+
// drafts don't move with releases: a tab watches once it shows what is published — from the release it shows
|
|
548
|
+
// (its reads are pinned to it), so a publish that landed between the fill and the watch's first answer, or
|
|
549
|
+
// while a background tab waited to be shown, is news; a page as written shows no release: the first named is
|
|
550
|
+
if (options.live && stopWatching === null)
|
|
551
|
+
stopWatching = client.watch((next) => void refresh(next.release), { interval, from: report.state === "ready" ? report.release : null });
|
|
552
|
+
}
|
|
458
553
|
return report;
|
|
459
554
|
};
|
|
460
555
|
// drafts have no release; what is published is read pinned to one, so a
|
|
@@ -467,12 +562,15 @@ export function mount(options) {
|
|
|
467
562
|
else {
|
|
468
563
|
try {
|
|
469
564
|
const pointer = await client.getRelease();
|
|
470
|
-
if (pointer === null)
|
|
565
|
+
if (pointer === null) {
|
|
566
|
+
// nothing is published: the page is as written — a draft it showed goes too
|
|
567
|
+
restore();
|
|
471
568
|
return finish({ state: "unpublished", release: null, preview, bound: 0, issues: [] });
|
|
569
|
+
}
|
|
472
570
|
release = pointer.release;
|
|
473
571
|
}
|
|
474
572
|
catch {
|
|
475
|
-
// out of reach: the page stays as it is — as written, or as last filled
|
|
573
|
+
// out of reach: the page stays as it is — as written, or as last filled from what is published
|
|
476
574
|
return last !== null && last.state === "ready" ? last : finish({ state: "fallback", release: null, preview, bound: 0, issues: [] });
|
|
477
575
|
}
|
|
478
576
|
}
|
|
@@ -488,12 +586,12 @@ export function mount(options) {
|
|
|
488
586
|
const records = new Map();
|
|
489
587
|
const reads = [];
|
|
490
588
|
for (const [key, expand] of heads(page)) {
|
|
491
|
-
reads.push(read(() => client.getSingle(key, { ...pin, ...(expand.size > 0 ? { expand: [...expand] } : {}) })).then((r) => singles.set(key, r)));
|
|
589
|
+
reads.push(read(() => client.getSingle(key, { ...pin, ...(expand.size > 0 ? { expand: [...expand] } : {}) }), isRecord).then((r) => singles.set(key, r)));
|
|
492
590
|
}
|
|
493
591
|
for (const list of page.lists) {
|
|
494
592
|
const template = templateOf(list.el);
|
|
495
593
|
const fields = template ? [...heads(compile(template.content, false)).keys()] : [];
|
|
496
|
-
reads.push(read(async () => (await client.getRecords(list.collection, { ...list.options, ...pin, ...(fields.length > 0 ? { fields } : {}) })).records).then((r) => lists.set(list, r)));
|
|
594
|
+
reads.push(read(async () => (await client.getRecords(list.collection, { ...list.options, ...pin, ...(fields.length > 0 ? { fields } : {}) })).records, isRecordList).then((r) => lists.set(list, r)));
|
|
497
595
|
}
|
|
498
596
|
for (const record of page.records) {
|
|
499
597
|
if (record.slug === null) {
|
|
@@ -501,11 +599,22 @@ export function mount(options) {
|
|
|
501
599
|
continue;
|
|
502
600
|
}
|
|
503
601
|
const slug = record.slug;
|
|
504
|
-
reads.push(read(() => client.getRecordBySlug(record.collection, slug, { ...pin, ...(record.expand.length > 0 ? { expand: record.expand } : {}) })).then((r) => records.set(record, r)));
|
|
602
|
+
reads.push(read(() => client.getRecordBySlug(record.collection, slug, { ...pin, ...(record.expand.length > 0 ? { expand: record.expand } : {}) }), isRecord).then((r) => records.set(record, r)));
|
|
505
603
|
}
|
|
506
604
|
await Promise.all(reads);
|
|
605
|
+
// preview was left, or the session ended or was replaced, while the reads were on their way:
|
|
606
|
+
// what they brought is nobody's now — read again, with whatever the tab has
|
|
607
|
+
const now = (await client.isPreview().catch(() => false)) ? (previewSession()?.draftToken ?? null) : null;
|
|
608
|
+
if (mine !== generation || now !== session) {
|
|
609
|
+
if (attempt < MAX_ATTEMPTS)
|
|
610
|
+
return run(known, attempt + 1);
|
|
611
|
+
// the session keeps changing under the reads: show nothing of any of them — the page as written, until the next fill
|
|
612
|
+
restore();
|
|
613
|
+
return finish({ state: "fallback", release: null, preview: now !== null, bound: 0, issues: [] });
|
|
614
|
+
}
|
|
615
|
+
// nothing came back at all: an outage, and the page stays as it is — a wrong-shaped answer is still an answer, reported by its place
|
|
507
616
|
const answers = [...singles.values(), ...lists.values(), ...records.values()];
|
|
508
|
-
if (answers.length > 0 && answers.every((a) => a.status === "failed")) {
|
|
617
|
+
if (answers.length > 0 && answers.every((a) => a.status === "failed" && !a.answered)) {
|
|
509
618
|
return last !== null && last.state === "ready" ? last : finish({ state: "fallback", release, preview, bound: 0, issues: [] });
|
|
510
619
|
}
|
|
511
620
|
// one synchronous pass: back to what the author wrote, then filled
|
|
@@ -590,37 +699,33 @@ export function mount(options) {
|
|
|
590
699
|
};
|
|
591
700
|
let queue = guarded();
|
|
592
701
|
const refresh = (known) => (queue = queue.then(() => guarded(known)));
|
|
702
|
+
/** Preview is left: at once on the page, then one fill — unless a fill that began after the exit already committed. */
|
|
703
|
+
const exit = () => {
|
|
704
|
+
endPreview();
|
|
705
|
+
drop();
|
|
706
|
+
const after = generation;
|
|
707
|
+
return (queue = queue.then(() => (committed >= after && last !== null ? last : guarded())));
|
|
708
|
+
};
|
|
593
709
|
const onClick = (event) => {
|
|
594
710
|
const target = event.target;
|
|
595
711
|
if (!target?.closest?.(`[${P}end-preview]`))
|
|
596
712
|
return;
|
|
597
713
|
event.preventDefault();
|
|
598
|
-
|
|
599
|
-
void refresh();
|
|
714
|
+
void exit();
|
|
600
715
|
};
|
|
601
716
|
doc.addEventListener("click", onClick);
|
|
602
|
-
let stopWatching = null;
|
|
603
|
-
if (options.live) {
|
|
604
|
-
const interval = typeof options.live === "number" ? options.live : undefined;
|
|
605
|
-
// drafts don't move with releases: a tab in preview doesn't watch
|
|
606
|
-
void queue.then((report) => {
|
|
607
|
-
if (!report.preview && stopWatching === null)
|
|
608
|
-
stopWatching = client.watch((next) => void refresh(next.release), { interval });
|
|
609
|
-
});
|
|
610
|
-
}
|
|
611
717
|
return {
|
|
612
718
|
ready: queue,
|
|
613
719
|
get report() {
|
|
614
720
|
return last;
|
|
615
721
|
},
|
|
616
722
|
refresh: () => refresh(),
|
|
617
|
-
endPreview:
|
|
618
|
-
endPreview();
|
|
619
|
-
return refresh();
|
|
620
|
-
},
|
|
723
|
+
endPreview: exit,
|
|
621
724
|
stop: () => {
|
|
725
|
+
stopped = true;
|
|
622
726
|
stopWatching?.();
|
|
623
727
|
stopWatching = () => { };
|
|
728
|
+
clearTimeout(expiry);
|
|
624
729
|
doc.removeEventListener("click", onClick);
|
|
625
730
|
},
|
|
626
731
|
client,
|
package/dist/url.js
CHANGED
|
@@ -49,7 +49,15 @@ export function fillTemplate(template, base, lookup) {
|
|
|
49
49
|
failures.push({ ok: false, reason: "empty", path: segments.join(".") });
|
|
50
50
|
return "";
|
|
51
51
|
}
|
|
52
|
-
|
|
52
|
+
try {
|
|
53
|
+
return encodeURIComponent(String(value));
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// a string that isn't well-formed (a lone surrogate — JSON allows it) can't be encoded:
|
|
57
|
+
// this place is refused, the rest of the page fills
|
|
58
|
+
failures.push({ ok: false, reason: "unsafe-url", path: segments.join(".") });
|
|
59
|
+
return "";
|
|
60
|
+
}
|
|
53
61
|
});
|
|
54
62
|
if (failures[0])
|
|
55
63
|
return failures[0];
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mapled/vanilla",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "The browser runtime of Mapled, the hosted headless CMS for sites built with AI — published content in a plain HTML site: one script tag and data-mapled attributes, no framework and no build step.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://mapled.io",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@mapled/client": "^0.1
|
|
44
|
+
"@mapled/client": "^0.2.1"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"esbuild": "^0.28.2",
|