@8ux-co/eelzap 0.10.0 → 0.10.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/boot/local.ts","../src/preview/site/load.ts","../src/preview/site/shortcut.ts","../src/preview/site/boot.ts","../src/preview/page-bridge.ts","../src/preview/sanitize.ts","../src/preview/values.ts","../src/preview/runtime.ts","../src/preview/live.ts"],"sourcesContent":["/**\n * Development origins: `localhost`, `127.0.0.1`, `[::1]` and any `*.localhost`\n * name (which browsers resolve to the machine itself and treat as secure\n * contexts), over http OR https, any port: a site in development runs on\n * either.\n *\n * Shared by the boot (`./boot.ts`, which every visitor runs, so one regex and\n * no more) and the overlay (`../preview/live.ts`), so the two never disagree\n * about what counts as local.\n */\nexport const LOCAL_ORIGIN =\n /^https?:\\/\\/(?:(?:[a-z\\d-]+\\.)*localhost|127\\.0\\.0\\.1|\\[::1\\])(?::\\d+)?$/i\n\n/** True for a development hostname (no scheme, no port). */\nexport const isLocalHost = (hostname: string): boolean => LOCAL_ORIGIN.test(`http://${hostname}`)\n","import type { ChunkModule, ChunkName } from './config'\n\n/**\n * The npm build's chunk loader: a dynamic `import()`, which the site's bundler\n * (and this package's own ESM build, `tsup` with splitting) turns into a\n * separate file fetched only when called. The script-tag build swaps this\n * module for `load-cdn.ts` at bundle time (`scripts/bundle-config.mjs`).\n */\nexport function loadChunk(name: ChunkName, _base?: string | null): Promise<ChunkModule> {\n return name === 'signin' ? import('./signin') : import('./suggest')\n}\n","/**\n * The keyboard shortcut that opens «Editar» and «Comentar» on the live site\n * (zap-cms-v2 §3.4, owner 2026-10-05): Shift plus one letter, `Z` by default.\n * It replaced «Shift twice», which fired while people typed capitals.\n *\n * Shared by the boot every visitor runs (`src/boot/boot.ts`, under 1 KB) and\n * the overlay core (`./boot.ts`), so the two can never disagree about what\n * counts. No directive, no imports.\n *\n * It never fires while the person is typing: focus in an input, a textarea, a\n * select or anything editable, or a key that is part of an IME composition\n * (`isComposing`, or the legacy `keyCode` 229 some browsers send instead).\n */\n\n/** The letter pressed with Shift when a site sets none. */\nexport const DEFAULT_SHORTCUT = 'Z'\n\n/**\n * Where typing happens: the target itself or anything it sits in. Any\n * `contenteditable`, even `false`, counts: a few bytes of the boot cheaper,\n * and a missed shortcut there costs nothing.\n */\nconst TYPING = 'input,textarea,select,[contenteditable]'\n\nexport function isShortcut(event: KeyboardEvent, key: string): boolean {\n // The real target, inside an open shadow root too (a site's own components).\n const target = (event.composedPath?.()[0] ?? event.target) as Element | null\n return (\n event.shiftKey &&\n !(event.ctrlKey || event.metaKey || event.altKey || event.repeat || event.isComposing) &&\n // Some browsers report a key inside an IME composition only as keyCode 229.\n event.keyCode !== 229 &&\n event.key?.toLowerCase() === key.toLowerCase() &&\n !target?.closest?.(TYPING)\n )\n}\n","import {\n PENDING_STORAGE_KEY,\n SIGNIN_CHANNEL,\n siteClientId,\n STATE_PREFIX,\n type CallbackParams,\n type ChunkHandle,\n type ChunkModule,\n type ChunkName,\n type SiteContext,\n type StartOptions,\n} from './config'\nimport { loadChunk } from './load'\nimport { DEFAULT_SHORTCUT, isShortcut } from './shortcut'\nimport { readToken } from './token'\n\n/**\n * The core's part of Live suggestion mode (zap-cms-v2 §3.4, ADR 041), for a\n * page that is NOT framed by Zap. It draws nothing and loads nothing until one\n * of these holds:\n *\n * 1. **This page is the OAuth callback**: `{origin}/?code&state&iss` with a\n * `state` this client minted and `iss` = the compiled-in issuer. In the\n * sign-in popup the code goes to the opener over\n * `BroadcastChannel('eel-zap')` and the popup closes; after a full-page\n * sign-in (the popup was blocked) the `signin` chunk finishes here. Either\n * way the code leaves the address bar at once.\n * 2. **This tab holds a live token** for this site: the `suggest` chunk.\n * 3. **The person asks**: `?zap` in the URL, or the shortcut (Shift Z by\n * default, never while typing in a field or composing, `shortcut.ts`): the\n * `signin` chunk, which shows the launcher.\n *\n * Ordinary visitors meet none of these, so they get one `keydown` listener\n * and nothing else. Whatever the callback carries is checked again by the\n * `signin` chunk (state, issuer, the PKCE verifier only the opener holds);\n * this file only decides whether a page looks like ours.\n */\n\nexport interface SiteModeOptions {\n win: Window\n siteId: string\n zapOrigin: string\n authOrigin: string\n locale?: 'es' | 'en'\n /** The letter pressed with Shift to open the launcher; false turns the shortcut off. */\n shortcut?: string | false\n /** Script-tag build: the core's own URL, the chunks are its siblings. */\n chunkBase?: string | null\n /** Open at once: the boot already saw the trigger (the shortcut). */\n open?: boolean\n}\n\n/** The callback params when this page is one of ours, else null. */\nexport function readCallback(win: Window, authOrigin: string): CallbackParams | null {\n const { pathname, search } = win.location\n if (pathname !== '/' || !search) return null\n const params = new URLSearchParams(search)\n const code = params.get('code')\n const state = params.get('state')\n const iss = params.get('iss')\n if (!code || !state?.startsWith(STATE_PREFIX) || iss !== authOrigin) return null\n return { code, state, iss }\n}\n\nfunction pendingRedirect(win: Window): boolean {\n try {\n return JSON.parse(win.sessionStorage.getItem(PENDING_STORAGE_KEY) ?? '{}').mode === 'redirect'\n } catch {\n return false\n }\n}\n\nfunction pageLocale(doc: Document): 'es' | 'en' {\n return /^en\\b/i.test(doc.documentElement.lang) ? 'en' : 'es'\n}\n\nexport function startSiteMode(options: SiteModeOptions): () => void {\n const { win, siteId } = options\n const doc = win.document\n const key = options.shortcut === undefined ? DEFAULT_SHORTCUT : options.shortcut\n let current: ChunkHandle | null = null\n let opening = false\n\n const ctx: SiteContext = {\n win,\n doc,\n siteId,\n zapOrigin: options.zapOrigin,\n authOrigin: options.authOrigin,\n clientId: siteClientId(options.zapOrigin, siteId),\n redirectUri: `${win.location.origin}/`,\n locale: options.locale ?? pageLocale(doc),\n shortcut: key,\n async open(name: ChunkName, startOptions?: StartOptions) {\n opening = true\n try {\n const module: ChunkModule = await loadChunk(name, options.chunkBase)\n current?.destroy()\n current = module.start(ctx, startOptions)\n } catch (error) {\n console.warn('eelzap: suggestions failed', error)\n } finally {\n opening = false\n }\n },\n close() {\n current?.destroy()\n current = null\n },\n }\n\n const callback = readCallback(win, options.authOrigin)\n if (callback) {\n // The code and state must not stay in the address bar or the history.\n win.history.replaceState(win.history.state, '', '/')\n if (pendingRedirect(win)) {\n void ctx.open('signin', { callback })\n } else {\n try {\n const channel = new BroadcastChannel(SIGNIN_CHANNEL)\n channel.postMessage({ type: 'eelzap:callback', ...callback })\n channel.close()\n } finally {\n win.close()\n }\n }\n return ctx.close\n }\n\n const launch = () => {\n if (current || opening) return\n void ctx.open(readToken(win, siteId) ? 'suggest' : 'signin', { reason: 'trigger' })\n }\n\n if (readToken(win, siteId)) void ctx.open('suggest')\n else if (options.open || new URLSearchParams(win.location.search).has('zap')) launch()\n\n const onKey = (event: KeyboardEvent) => {\n if (key && isShortcut(event, key)) launch()\n }\n win.addEventListener('keydown', onKey, true)\n\n return () => {\n win.removeEventListener('keydown', onKey, true)\n ctx.close()\n }\n}\n","import {\n emptyDropStats,\n envelope,\n MAX_MESSAGE_BYTES,\n messageBytes,\n parseEditorMessage,\n type DropReason,\n type DropStats,\n type EditorMessage,\n type PageMessages,\n type PageMessageType,\n} from './protocol'\n\n/**\n * The PAGE side of the bridge (§2.6 \"Origins\").\n *\n * A message from the editor is accepted only when ALL hold:\n *\n * 1. `event.source === parent` — the window that framed us, nothing else\n * (another frame, a popup, the page itself posting to itself).\n * 2. `event.origin` is one of `editorOrigins` (the Zap origins the client\n * trusts). `'null'` (an opaque origin) is never accepted.\n * 3. It parses as a v1 envelope of a known editor to page type, under 256 KB.\n * 4. Its `session` matches. Before `zap:hello` the session is unknown, and\n * nothing but a hello is accepted; the first valid hello fixes it. After\n * that, every message must carry it; a second hello with another session\n * is refused.\n *\n * Every drop is counted in `stats` by reason; nothing throws.\n *\n * Neither side ever posts with `'*'`. The page answers the origin that said\n * hello (before it, each accepted candidate; the browser drops a message whose\n * target is not the parent's real origin). The editor posts only to the exact\n * page origin that said `zap:ready` (`editor.ts`).\n */\n\nexport interface MessageTargetLike {\n postMessage(message: unknown, targetOrigin: string): void\n}\n\nexport interface PageBridgeOptions {\n win: Window\n /** `window.parent` in production; injected in specs. */\n parent: MessageTargetLike\n editorOrigins: readonly string[]\n /**\n * Where to post BEFORE the hello names the editor's origin. Defaults\n * to `editorOrigins`. Every entry must itself be an accepted origin; the\n * browser drops a message whose target is not the parent's real origin.\n */\n preHelloTargets?(): readonly string[]\n onMessage(message: EditorMessage): void\n onDrop?(reason: DropReason): void\n}\n\nexport interface PageBridge {\n post<K extends PageMessageType>(type: K, payload: PageMessages[K]): boolean\n readonly session: string | null\n readonly stats: DropStats\n destroy(): void\n}\n\nexport function createPageBridge(options: PageBridgeOptions): PageBridge {\n const stats = emptyDropStats()\n let session: string | null = null\n let helloOrigin: string | null = null\n const origins = options.editorOrigins.filter((o) => typeof o === 'string' && o !== '')\n\n const isAccepted = (origin: string) =>\n typeof origin === 'string' && origin !== 'null' && origins.includes(origin)\n\n const drop = (reason: DropReason) => {\n stats[reason]++\n options.onDrop?.(reason)\n }\n\n const listener = (event: MessageEvent) => {\n if (event.source !== (options.parent as unknown)) return drop('wrong-source')\n if (!isAccepted(event.origin)) return drop('wrong-origin')\n const parsed = parseEditorMessage(event.data)\n if (!parsed.ok) return drop(parsed.reason)\n const message = parsed.message\n\n if (message.type === 'zap:hello') {\n if (message.payload.session !== message.session) return drop('invalid-payload')\n if (session !== null && message.session !== session) return drop('wrong-session')\n if (session === null) session = message.session\n helloOrigin = event.origin\n } else if (session === null || message.session !== session || helloOrigin === null) {\n // Nothing but a hello is accepted before the hello.\n return drop('wrong-session')\n }\n options.onMessage(message)\n }\n\n options.win.addEventListener('message', listener)\n\n return {\n post(type, payload) {\n const message = envelope(type, session ?? '', payload)\n if (messageBytes(message) > MAX_MESSAGE_BYTES) return false\n // Never '*': after the hello we answer the origin that said hello;\n // before it, each allowed origin (a non-matching target is dropped by\n // the browser, so only the real parent receives it).\n const targets = helloOrigin\n ? [helloOrigin]\n : (options.preHelloTargets?.() ?? origins).filter(isAccepted)\n for (const origin of targets) options.parent.postMessage(message, origin)\n return targets.length > 0\n },\n get session() {\n return session\n },\n stats,\n destroy() {\n options.win.removeEventListener('message', listener)\n },\n }\n}\n","/**\n * The allowlist sanitiser for rich-text substitution (§2.4 \"Draft values\",\n * §7.2 \"Rich text substitution\").\n *\n * ## Why this is not \"the delivery API's allowlist\"\n *\n * The spec says to sanitise \"with the same allowlist the delivery API\n * applies\". The delivery API applies NONE: `apps/zap/src/lib/cms/rich-text-media.ts`\n * strips `<script>…</script>` and nothing else, on purpose (its header: an\n * allowlist would drop `data-media-id` and change bytes, flipping every\n * delivery ETag). The suite's real allowlist is `@eel/ui-web`'s\n * `sanitizeRichTextHtml`, a regex sanitiser for Swarm/Wrap editor HTML whose\n * `src` rule admits only the suite's own file route — Zap rich text points at\n * R2 media URLs, so reusing it verbatim would blank every image.\n *\n * So the allowlist lives HERE, as the one source of truth for HTML that Zap\n * puts into a customer page: the tags and attributes Zap's rich-text editor\n * emits (Tiptap StarterKit + link, underline, text-align, the media image\n * with `data-media-id`), nothing else. Constants are exported so a server-side\n * twin, if one is ever needed, imports them rather than copying.\n *\n * ## DOM-based, not regex\n *\n * The page is a real browser, so the HTML is parsed by the browser's own\n * parser into an INERT `<template>` (no script runs, no image loads, no\n * handler fires while parsing) and the resulting tree is walked: disallowed\n * elements are dropped with their content (script-like) or unwrapped\n * (formatting-like), every attribute not on the list is removed, and URL\n * attributes are re-parsed with `URL` so `java\\tscript:` and friends are\n * judged by the scheme the browser would actually use.\n */\n\n/** Elements kept as they are. Everything else is unwrapped or dropped. */\nexport const RICH_TEXT_ALLOWED_TAGS: ReadonlySet<string> = new Set([\n 'p',\n 'br',\n 'strong',\n 'b',\n 'em',\n 'i',\n 'u',\n 's',\n 'code',\n 'pre',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'ul',\n 'ol',\n 'li',\n 'blockquote',\n 'hr',\n 'a',\n 'span',\n 'img',\n])\n\n/** Elements removed WITH their content: executable, embedding or form markup. */\nexport const RICH_TEXT_DROPPED_TAGS: ReadonlySet<string> = new Set([\n 'script',\n 'style',\n 'template',\n 'iframe',\n 'frame',\n 'frameset',\n 'object',\n 'embed',\n 'applet',\n 'noscript',\n 'svg',\n 'math',\n 'form',\n 'input',\n 'button',\n 'select',\n 'textarea',\n 'link',\n 'meta',\n 'base',\n 'title',\n 'head',\n])\n\n/** Attributes kept, per element (`*` applies to every allowed element). */\nexport const RICH_TEXT_ALLOWED_ATTRS: Readonly<Record<string, ReadonlySet<string>>> = {\n '*': new Set(['title', 'style']),\n a: new Set(['href', 'target', 'rel']),\n img: new Set(['src', 'alt', 'width', 'height', 'data-media-id']),\n}\n\n/** `style` survives only as text alignment (the TextAlign extension). */\nconst ALLOWED_STYLE = /^\\s*text-align\\s*:\\s*(left|right|center|justify)\\s*;?\\s*$/i\nconst LINK_SCHEMES = new Set(['http:', 'https:', 'mailto:', 'tel:'])\nconst IMAGE_SCHEMES = new Set(['http:', 'https:'])\nconst DIMENSION = /^\\d{1,5}$/\nconst MEDIA_ID = /^[A-Za-z0-9-]{1,64}$/\n\n/**\n * Is `value` a URL on one of `schemes` once the browser parses it? Relative\n * URLs resolve against the document's base (the customer page), which is\n * always http(s).\n */\nexport function isSafeUrl(value: string, schemes: ReadonlySet<string>, base?: string): boolean {\n try {\n const url = new URL(value, base ?? 'https://base.invalid/')\n return schemes.has(url.protocol)\n } catch {\n return false\n }\n}\n\n/** A link target the page may carry: http(s), mailto or tel. */\nexport function isSafeLinkUrl(value: string, base?: string): boolean {\n return isSafeUrl(value, LINK_SCHEMES, base)\n}\n\nexport function isSafeImageUrl(value: string, base?: string): boolean {\n return isSafeUrl(value, IMAGE_SCHEMES, base)\n}\n\n/** Every candidate URL in a `srcset` must be a safe image URL. */\nexport function isSafeSrcset(value: string, base?: string): boolean {\n const candidates = value.split(',').map((part) => part.trim().split(/\\s+/)[0] ?? '')\n return candidates.length > 0 && candidates.every((url) => url !== '' && isSafeImageUrl(url, base))\n}\n\nfunction cleanAttributes(element: Element, tag: string, base: string | undefined): void {\n const allowed = RICH_TEXT_ALLOWED_ATTRS[tag]\n const global = RICH_TEXT_ALLOWED_ATTRS['*']!\n for (const attribute of Array.from(element.attributes)) {\n const name = attribute.name.toLowerCase()\n const value = attribute.value\n let keep = global.has(name) || (allowed?.has(name) ?? false)\n if (keep && name === 'style') keep = ALLOWED_STYLE.test(value)\n if (keep && name === 'href') keep = isSafeUrl(value, LINK_SCHEMES, base)\n if (keep && name === 'src') keep = isSafeImageUrl(value, base)\n if (keep && name === 'target') keep = value === '_blank'\n if (keep && (name === 'width' || name === 'height')) keep = DIMENSION.test(value)\n if (keep && name === 'data-media-id') keep = MEDIA_ID.test(value)\n if (!keep) element.removeAttribute(attribute.name)\n }\n if (tag === 'a' && element.getAttribute('target') === '_blank') {\n element.setAttribute('rel', 'noopener noreferrer')\n } else if (tag === 'a') {\n element.removeAttribute('rel')\n }\n}\n\nfunction walk(parent: ParentNode, base: string | undefined): void {\n for (const node of Array.from(parent.childNodes)) {\n if (node.nodeType === 3 /* TEXT */) continue\n if (node.nodeType !== 1 /* ELEMENT */) {\n // Comments, processing instructions, CDATA: never kept.\n node.parentNode?.removeChild(node)\n continue\n }\n const element = node as Element\n const tag = element.localName.toLowerCase()\n const foreign = element.namespaceURI !== 'http://www.w3.org/1999/xhtml'\n if (foreign || RICH_TEXT_DROPPED_TAGS.has(tag)) {\n element.remove()\n continue\n }\n walk(element, base)\n if (!RICH_TEXT_ALLOWED_TAGS.has(tag)) {\n // Unknown formatting (div, section, font, …): keep the text, drop the tag.\n element.replaceWith(...Array.from(element.childNodes))\n continue\n }\n cleanAttributes(element, tag, base)\n // An image whose src did not survive is dropped, not left broken.\n if (tag === 'img' && !element.hasAttribute('src')) element.remove()\n }\n}\n\n/**\n * Sanitise `html` into a fragment owned by `doc`, ready for\n * `element.replaceChildren(fragment)`. Never sets `innerHTML` on a live node.\n */\nexport function sanitizeRichText(\n html: string,\n doc: Document,\n base: string | undefined = doc.baseURI,\n): DocumentFragment {\n const template = doc.createElement('template')\n template.innerHTML = html\n const content = template.content\n walk(content, base)\n return content\n}\n\n/** The sanitised HTML as a string (for tests and for comparisons). */\nexport function sanitizeRichTextToString(html: string, doc: Document): string {\n const holder = doc.createElement('div')\n holder.appendChild(sanitizeRichText(html, doc))\n return holder.innerHTML\n}\n\n/** Plain text of a rich-text value, for an element that did not opt into HTML. */\nexport function richTextToText(html: string, doc: Document): string {\n // Through the sanitiser first, so the text of a dropped <script> or <style>\n // never shows up as page copy.\n return sanitizeRichText(html, doc).textContent ?? ''\n}\n","import { splitStega } from '../stega'\nimport type { PreviewValue, ValuesPayload } from './protocol'\nimport {\n isSafeImageUrl,\n isSafeLinkUrl,\n isSafeSrcset,\n richTextToText,\n sanitizeRichText,\n} from './sanitize'\nimport type { TagIndex, TaggedElement } from './tags'\n\n/**\n * Draft value substitution (`zap:values`, §2.4 \"Draft values\").\n *\n * What is touched, and nothing else:\n *\n * - text (`string`, `number`, `boolean`, `{ text }`) → `textContent`; on an\n * `<img>` it becomes `alt` (an image tagged with an alt-text field).\n * - `{ image }` → `src`, `srcset` and `alt` of the element when it is an\n * `<img>`, else of its first descendant `<img>`; `<source>` siblings in a\n * `<picture>` lose `srcset` so the browser actually shows the new `src`.\n * URLs must be http(s) (relative resolves against the page).\n * - `{ html }` → sanitised children (never `innerHTML` on the live node), ONLY\n * when the element carries `data-zap-html`; otherwise its plain text.\n * - `{ url }` (a URL field) and `{ email }` (an EMAIL field, as `mailto:`) →\n * the `href` of the element when it is an `<a>`, else of its first\n * descendant `<a>`; the link's label stays the site's. With no link, the\n * address is the text. Only http(s), mailto and tel links. (An image field\n * is `{ image }`, above: its `src`, never its text.)\n * - `null` → empty text; an image keeps what it had.\n *\n * Never an attribute other than `href`, `src`, `srcset` and `alt`, never a\n * style, never an event handler.\n *\n * An element found by a stega marker (`source: 'stega'`) gets text only, and\n * only in the text node that carries the marker, which keeps it: the rest of\n * the element (other text, other fields, markup) is the site's. Rich text and\n * images found that way are left to the site's `onValues` handler.\n *\n * ## Formatting is the editor's job\n *\n * Numbers, dates and enum labels arrive already formatted (`PreviewValue`\n * says why): the browser core has no locale data, no field types and a size\n * budget, and the editor already has the suite's formatters.\n *\n * ## Re-application\n *\n * The last value per field is remembered, so when the site re-renders a tagged\n * node (hydration, client routing) `reapply()` writes it again. An element is\n * rewritten only when it differs from what we would write, so our own writes\n * — which the mutation observer also sees — settle after one pass.\n */\n\ninterface Desired {\n locale: string\n value: PreviewValue\n}\n\nexport class ValueApplier {\n private desired = new Map<string, Desired>()\n private appliedHtml = new WeakMap<Element, { source: string; output: string }>()\n\n constructor(\n private readonly index: TagIndex,\n private readonly doc: Document,\n ) {}\n\n /** Remember and apply a patch. Returns how many elements were written. */\n apply(payload: ValuesPayload): number {\n let written = 0\n for (const [fieldKey, value] of Object.entries(payload.patch)) {\n this.desired.set(`${payload.recordRef}#${fieldKey}`, { locale: payload.locale, value })\n for (const tagged of this.index.byField(payload.recordRef, fieldKey)) {\n if (this.write(tagged, payload.locale, value)) written++\n }\n }\n return written\n }\n\n /** Write every remembered value again where the page drifted from it. */\n reapply(): number {\n let written = 0\n for (const tagged of this.index.elements) {\n const desired = this.desired.get(tagged.tag)\n if (desired && this.write(tagged, desired.locale, desired.value)) written++\n }\n return written\n }\n\n private write(tagged: TaggedElement, locale: string, value: PreviewValue): boolean {\n // An element pinned to another locale shows that locale's copy; leave it.\n if (tagged.locale && tagged.locale !== locale) return false\n const element = tagged.element\n\n if (tagged.textNode) {\n const text = plainText(value)\n if (text === null) return false\n const node = tagged.textNode\n // The node is the field's own text; keep the whitespace a template put before it.\n const split = splitStega(node.data)\n const next = /^\\s*/.exec(split.text)![0] + text + split.markers\n if (node.data === next) return false\n node.data = next\n return true\n }\n\n if (value !== null && typeof value === 'object' && 'image' in value) {\n return writeImage(element, value.image, this.doc.baseURI)\n }\n\n if (value !== null && typeof value === 'object' && 'html' in value) {\n if (tagged.html) return this.writeHtml(element, value.html)\n return writeText(element, richTextToText(value.html, this.doc))\n }\n\n if (value !== null && typeof value === 'object' && ('url' in value || 'email' in value)) {\n return writeLink(element, value, this.doc.baseURI)\n }\n\n const text = plainText(value) ?? ''\n if (isImg(element)) return setAttr(element, 'alt', text)\n return writeText(element, text)\n }\n\n private writeHtml(element: Element, html: string): boolean {\n // Unchanged when the source is the one we last wrote AND the node still\n // holds exactly our output (the site did not re-render it since).\n const last = this.appliedHtml.get(element)\n if (last && last.source === html && last.output === element.innerHTML) return false\n element.replaceChildren(sanitizeRichText(html, this.doc))\n this.appliedHtml.set(element, { source: html, output: element.innerHTML })\n return true\n }\n}\n\n/** A value as plain text, or null for the shapes that are not text (html, image). */\nfunction plainText(value: PreviewValue): string | null {\n if (value === null || typeof value !== 'object') return value === null ? '' : String(value)\n const v = value as { text?: string; url?: string; email?: string }\n return v.text ?? v.url ?? v.email ?? null\n}\n\n/** A URL or EMAIL value: the `href` of the link, never its label. */\nfunction writeLink(element: Element, value: { url: string } | { email: string }, base: string) {\n const email = 'email' in value\n const address = email ? value.email : value.url\n const link = element.localName === 'a' ? element : element.querySelector('a')\n if (!link) return writeText(element, address)\n const href = email && address ? `mailto:${address}` : address\n return isSafeLinkUrl(href, base) && setAttr(link, 'href', href)\n}\n\nfunction isImg(element: Element): element is HTMLImageElement {\n return element.localName === 'img'\n}\n\nfunction writeText(element: Element, text: string): boolean {\n if (element.textContent === text) return false\n element.textContent = text\n return true\n}\n\nfunction setAttr(element: Element, name: string, value: string): boolean {\n if (element.getAttribute(name) === value) return false\n element.setAttribute(name, value)\n return true\n}\n\nfunction writeImage(\n element: Element,\n image: { src?: string | null; srcset?: string | null; alt?: string | null },\n base: string,\n): boolean {\n const img = isImg(element) ? element : element.querySelector('img')\n if (!img) return false\n let changed = false\n if (image.src && isSafeImageUrl(image.src, base)) {\n changed = setAttr(img, 'src', image.src) || changed\n const picture = img.parentElement?.localName === 'picture' ? img.parentElement : null\n if (picture) {\n for (const source of Array.from(picture.children)) {\n if (source.localName === 'source' && source.hasAttribute('srcset')) {\n source.removeAttribute('srcset')\n changed = true\n }\n }\n }\n if (image.srcset === undefined || image.srcset === null) {\n // A stale srcset would win over the new src.\n if (img.hasAttribute('srcset')) {\n img.removeAttribute('srcset')\n changed = true\n }\n }\n }\n if (image.srcset && isSafeSrcset(image.srcset, base)) {\n changed = setAttr(img, 'srcset', image.srcset) || changed\n }\n if (typeof image.alt === 'string') changed = setAttr(img, 'alt', image.alt) || changed\n return changed\n}\n","import { buildAnchor, findAnchorElement } from './anchor'\nimport { Overlay } from './overlay'\nimport { createPageBridge, type MessageTargetLike, type PageBridge } from './page-bridge'\nimport { CAPABILITIES, PAGE_UNLOAD_ERROR, type EditorMessage, type TagSummary } from './protocol'\nimport { observeTags, TagIndex, type TagProblem } from './tags'\nimport { ValueApplier } from './values'\n\n/**\n * The page runtime: index, overlay, values and bridge, wired together. The\n * Live client (`live.ts`) boots it inside the customer's own page, framed by\n * Zap's editor by its real URL (§2.7).\n */\n\nexport const CLIENT_VERSION = '0.10.0'\n\nexport interface RuntimeOptions {\n win?: Window\n doc?: Document\n parent?: MessageTargetLike\n /** Rescan throttle. Default 250 ms. */\n throttleMs?: number\n}\n\nexport interface PageRuntime {\n readonly bridge: PageBridge\n readonly index: TagIndex\n readonly overlay: Overlay\n /** Force a rescan + report now (specs, and after a navigation). */\n refresh(): void\n destroy(): void\n}\n\nconst MAX_PROBLEM_REPORTS = 20\n\n/**\n * What one runtime needs beyond the DOM: who it talks to and how the page\n * behaves. The Live client builds it from the origins it trusts (`live.ts`).\n */\nexport interface RuntimeConfig {\n editorOrigins: readonly string[]\n /** Where `zap:ready` goes before the hello names the editor. */\n preHelloTargets?(): readonly string[]\n /** The site's draft-mode route path, announced in `zap:ready`. */\n draftRoute?: string | null\n /** The page's real URL (`location.href`; it moves on client-side routing). */\n pageUrl(): string\n /**\n * Nothing is added to the page (no overlay element, no listeners) until\n * a verified `zap:hello`, so a page framed by anyone else stays untouched (§7.3 \"Overlay as an attack surface on public pages\").\n */\n lazyOverlay?: boolean\n /**\n * A last say on a message the bridge accepted (the hello must name this\n * site). False ignores it, before the runtime touches the page.\n */\n accept?(message: EditorMessage): boolean\n /** Called after the runtime handled an editor message itself. */\n onMessage?(message: EditorMessage): void\n}\n\nexport function startRuntime(config: RuntimeConfig, options: RuntimeOptions = {}): PageRuntime {\n const win = options.win ?? window\n const doc = options.doc ?? win.document\n const parent = options.parent ?? (win.parent as MessageTargetLike)\n const index = new TagIndex(doc)\n const values = new ValueApplier(index, doc)\n let lastReport = ''\n const reportedProblems = new Set<string>()\n let stopObserving: () => void = () => {}\n\n const overlay = new Overlay({\n doc,\n win,\n index,\n onInspect: (tagged) =>\n bridge.post('zap:click', { recordRef: tagged.recordRef, fieldKey: tagged.fieldKey }),\n onSelect: (elements) =>\n bridge.post('zap:select', {\n anchors: elements.map((element) => buildAnchor(element, index, config.pageUrl(), doc, win)),\n }),\n onSpot: (anchor) => bridge.post('zap:spot', { ...anchor, pageUrl: config.pageUrl() }),\n onPin: (pin) => bridge.post('zap:pin', pin),\n // Zap's one comment toggle: a click on the page or a wrapper is a spot.\n spotOnLarge: true,\n })\n\n const bridge = createPageBridge({\n win,\n parent,\n editorOrigins: config.editorOrigins,\n preHelloTargets: config.preHelloTargets,\n onMessage: (message) => {\n if (config.accept && !config.accept(message)) return\n handle(message)\n config.onMessage?.(message)\n },\n })\n\n function handle(message: EditorMessage): void {\n switch (message.type) {\n case 'zap:hello': {\n const hello = message.payload\n overlay.mount()\n overlay.setLabels(hello.labels)\n overlay.setTheme(hello.theme)\n if (hello.zoom !== undefined) overlay.setZoom(hello.zoom)\n overlay.setMode(hello.mode)\n // The editor may have missed the load-time report (it was not\n // listening yet), so the hello is answered with the current tags.\n report(true)\n return\n }\n case 'zap:values':\n values.apply(message.payload)\n return\n case 'zap:focus-field':\n overlay.focusField(\n index.byField(message.payload.recordRef, message.payload.fieldKey).map((t) => t.element),\n )\n return\n case 'zap:mode':\n overlay.setMode(message.payload)\n return\n case 'zap:zoom':\n overlay.setZoom(message.payload.zoom)\n return\n case 'zap:highlight': {\n const found = message.payload.anchors\n .map((anchor) => findAnchorElement(anchor, index, doc, win))\n .filter((element): element is Element => element !== null)\n overlay.highlight(found)\n return\n }\n case 'zap:pins': {\n const pins = []\n for (const { id, n, spot, dom } of message.payload.pins) {\n // A dom pin whose element is gone is not drawn (like a highlight).\n const element = dom ? findAnchorElement(dom, index, doc, win) : null\n if (spot) pins.push({ id, n, spot })\n else if (element) pins.push({ id, n, element })\n }\n overlay.setPins(pins)\n return\n }\n }\n }\n\n function report(force = false): void {\n const summary: TagSummary[] = index.summary()\n const key = JSON.stringify(summary)\n if (force || key !== lastReport) {\n lastReport = key\n bridge.post('zap:tags', summary)\n }\n reportProblems(index.problems)\n }\n\n function reportProblems(problems: TagProblem[]): void {\n for (const problem of problems) {\n const key = `${problem.attribute}=${problem.value}`\n if (reportedProblems.has(key) || reportedProblems.size >= MAX_PROBLEM_REPORTS) continue\n reportedProblems.add(key)\n bridge.post('zap:error', { code: 'TAG_INVALID', detail: key.slice(0, 500) })\n }\n }\n\n // A client-side route change (pushState) moves the page without a load; it\n // shows up as DOM mutations, so the rescan notices the new URL and tells the\n // editor. A real navigation loads a new document, which says ready again.\n let lastUrl = config.pageUrl()\n function refresh(): void {\n index.scan()\n values.reapply()\n report()\n const url = config.pageUrl()\n if (url !== lastUrl) {\n lastUrl = url\n bridge.post('zap:navigate', { url })\n }\n }\n\n // Listeners go in NOW, at script start (or, with `lazyOverlay`, the overlay's\n // at the verified hello). When the frame navigates, the editor hears of it\n // before the next document can receive anything (see PAGE_UNLOAD_ERROR).\n const onPageHide = () => {\n bridge.post('zap:error', { code: PAGE_UNLOAD_ERROR })\n }\n\n if (!config.lazyOverlay) overlay.install()\n win.addEventListener('pagehide', onPageHide)\n\n function boot(): void {\n if (!config.lazyOverlay) overlay.mount()\n index.scan()\n stopObserving = observeTags(doc.documentElement, refresh, {\n throttleMs: options.throttleMs,\n ignore: (node) => {\n const host = overlay.hostElement\n return !!host && (node === host || host.contains(node))\n },\n })\n bridge.post('zap:ready', {\n version: CLIENT_VERSION,\n capabilities: [...CAPABILITIES],\n pageUrl: config.pageUrl(),\n draftRoute: config.draftRoute ?? null,\n })\n report(true)\n }\n\n if (doc.readyState === 'loading') {\n doc.addEventListener('DOMContentLoaded', boot, { once: true })\n } else {\n boot()\n }\n\n return {\n bridge,\n index,\n overlay,\n refresh,\n destroy() {\n stopObserving()\n win.removeEventListener('pagehide', onPageHide)\n overlay.destroy()\n bridge.destroy()\n },\n }\n}\n","import {\n isDraftRoutePath,\n isPreviewTokenShape,\n type EditorMessage,\n type PreviewValue,\n type RecordRef,\n type ValuesPayload,\n} from './protocol'\nimport { isLocalHost } from '../boot/local'\nimport { NEST_PRODUCTION_ORIGIN } from './site/config'\nimport { startSiteMode } from './site/boot'\nimport { startRuntime, type PageRuntime, type RuntimeOptions } from './runtime'\n\n/**\n * The Live client («En vivo», `.docs/proposals/zap-cms-v2.md` §2.5–2.7): the\n * page runtime (`runtime.ts`: tag index, overlay, value substitution, bridge),\n * booted by the customer's site itself, which Zap's editor frames by its real\n * URL.\n *\n * ```ts\n * import { initZap, onValues, zapAttrs } from '@8ux-co/eelzap/preview'\n *\n * initZap({ siteKey: 'verdeorigen' })\n * <h1 {...zapAttrs(post, 'title')}>{post.content.title}</h1>\n * onValues(post, (patch) => …) // optional: values the site computes from\n * ```\n *\n * ## Who the client listens to (§2.6 \"Origins\")\n *\n * A message from the editor is accepted only when ALL hold, and every other\n * message is dropped and counted (`page-bridge.ts`):\n *\n * 1. `event.source === window.parent`;\n * 2. `event.origin` is the production editor (`https://zap.eel.software`)\n * or the development origin opted into below. Zap's own preview\n * deployments are not editors: no deployment-specific origin is compiled\n * into a public bundle;\n * 3. it parses as a v1 envelope under 256 KB;\n * 4. after the first `zap:hello`, its `session` matches.\n *\n * And the hello itself must name THIS site (`siteKey`); a hello for another\n * site is ignored, and nothing is drawn or stored for it.\n *\n * ## Development: `zapOrigin`\n *\n * `zapOrigin` is the only way to trust another origin, and it is honoured only\n * for a local Zap (`localhost`, `127.0.0.1`, `[::1]` or a `*.localhost` name,\n * over http or https, any port) AND only while the page itself runs on one of\n * those hosts. The boot sets it to the Zap it loaded this file from when that\n * is a local Zap framing a local page, so the site need not pass it. A\n * production page never trusts a local editor, even when the option is left in\n * the site's code; any other value is ignored with a console warning. The\n * production editor needs no option.\n *\n * ## Not framed by Zap, nothing happens\n *\n * Outside a frame the client does nothing at all. Inside a frame it posts one\n * `zap:ready` to the candidate parent origin (and the browser drops it unless\n * the parent really is that Zap origin), and adds nothing to the page — no\n * element, no listener beyond `message` — until a verified hello arrives.\n */\n\n/** The production editor. Always trusted. */\nexport const ZAP_PRODUCTION_ORIGIN = 'https://zap.eel.software'\n\n/** The header the cookieless fallback sends the preview token in (§2.5). */\nexport const ZAP_PREVIEW_TOKEN_HEADER = 'x-zap-preview-token'\n/** Where the client keeps it, per tab and per frame. */\nexport const PREVIEW_TOKEN_STORAGE_KEY = 'eelzap:preview-token'\n\nfunction parseUrl(value: string): URL | null {\n try {\n return new URL(value)\n } catch {\n return null\n }\n}\n\n/**\n * The extra origin `zapOrigin` adds, or null. See \"Development\" above: a\n * local Zap, from a local page, nothing else.\n */\nexport function developmentZapOrigin(\n zapOrigin: string | undefined,\n pageHostname: string,\n): string | null {\n if (!zapOrigin) return null\n const url = parseUrl(zapOrigin)\n if (!url || (url.protocol !== 'http:' && url.protocol !== 'https:')) return null\n if (url.origin === ZAP_PRODUCTION_ORIGIN) return null\n if (!isLocalHost(url.hostname) || !isLocalHost(pageHostname)) return null\n return url.origin\n}\n\n// ── Record references ───────────────────────────────────────────────────────\n\nexport { recordRefsOf, zapAttrs, type EntryLike } from '../refs'\nimport { recordRefsOf, type EntryLike } from '../refs'\nexport { cleanStega } from '../stega'\n\n// ── Values ──────────────────────────────────────────────────────────────────\n\nexport type ValuesHandler = (patch: Record<string, PreviewValue>, payload: ValuesPayload) => void\n\ninterface Subscription {\n refs: RecordRef[]\n handler: ValuesHandler\n}\n\nconst subscriptions = new Set<Subscription>()\n\n/**\n * Call `handler` with each `zap:values` patch for `entry` (unsaved keystrokes\n * from the editor), for sites that compute things from values. The tagged\n * elements are updated anyway; this is for everything else. Values arrive\n * formatted for display, exactly as the overlay writes them. Returns an\n * unsubscribe function. Works before or after `initZap`.\n */\nexport function onValues(entry: EntryLike, handler: ValuesHandler): () => void {\n return subscribeRefs(recordRefsOf(entry), handler)\n}\n\n/** `onValues` for references already resolved (the React hook). */\nexport function subscribeRefs(refs: readonly RecordRef[], handler: ValuesHandler): () => void {\n const subscription: Subscription = { refs: [...refs], handler }\n subscriptions.add(subscription)\n return () => {\n subscriptions.delete(subscription)\n }\n}\n\n/**\n * The window event every `zap:values` patch is also announced with, so code\n * bundled apart from this client (`useZapLiveUpdates` and `onValues` in\n * `@8ux-co/eelzap/react`, which never bundle the overlay) hears it.\n */\nexport const VALUES_EVENT = 'eelzap:values'\n\nfunction dispatchValues(payload: ValuesPayload, win?: Window): void {\n win?.dispatchEvent(new CustomEvent(VALUES_EVENT, { detail: payload }))\n for (const subscription of [...subscriptions]) {\n if (!subscription.refs.includes(payload.recordRef)) continue\n try {\n subscription.handler(payload.patch, payload)\n } catch (error) {\n // A site handler that throws must not take the bridge down with it.\n console.error('eelzap: onValues handler threw', error)\n }\n }\n}\n\n// ── Preview token (cookieless fallback) ─────────────────────────────────────\n\nfunction sessionStore(win: Window | undefined): Storage | null {\n try {\n return win?.sessionStorage ?? null\n } catch {\n return null\n }\n}\n\n/**\n * The preview token the editor handed this frame, or null. For the\n * cookieless fallback: send it to your own server with `previewHeaders()`,\n * where `getPreviewToken` from `@8ux-co/eelzap/next` reads it.\n */\nexport function getPreviewToken(win: Window | undefined = globalWindow()): string | null {\n try {\n const value = sessionStore(win)?.getItem(PREVIEW_TOKEN_STORAGE_KEY) ?? null\n return isPreviewTokenShape(value) ? value : null\n } catch {\n return null\n }\n}\n\n/** `{ 'x-zap-preview-token': token }` while this frame has one, else `{}`. */\nexport function previewHeaders(win?: Window): Record<string, string> {\n const token = getPreviewToken(win ?? globalWindow())\n return token ? { [ZAP_PREVIEW_TOKEN_HEADER]: token } : {}\n}\n\nfunction forgetPreviewToken(win: Window): void {\n try {\n sessionStore(win)?.removeItem(PREVIEW_TOKEN_STORAGE_KEY)\n } catch {\n // Nothing stored.\n }\n}\n\nfunction globalWindow(): Window | undefined {\n return typeof window === 'undefined' ? undefined : window\n}\n\n// ── initZap ─────────────────────────────────────────────────────────────────\n\nexport interface InitZapOptions {\n /** The Zap site's key (Configuración del sitio). A hello for another site is ignored. */\n siteKey: string\n /**\n * The Zap site's id. Turns on Live suggestion mode outside Zap's frame\n * (§3.4): it names the site's OAuth client. Without it the client only\n * talks to Zap's editor.\n */\n siteId?: string\n /** A local Zap, for development only (see \"Development\" above). */\n zapOrigin?: string\n /** A local Nest (the OAuth issuer), for development only, under the same rule as `zapOrigin`. */\n authOrigin?: string\n /** The letter pressed with Shift to open «Editar» and «Comentar». Default `Z`; false turns it off. */\n shortcut?: string | false\n /** The language of the suggestion UI; default from `<html lang>`, Spanish otherwise. */\n locale?: 'es' | 'en'\n /** Script-tag build only: the core's own URL, so its chunks load from beside it. */\n chunkBase?: string | null\n /**\n * The site's draft-mode route, a path such as `/api/zap-preview`, announced\n * to the editor in `zap:ready` (§2.5). Anything that is not a plain path is\n * dropped (`isDraftRoutePath`).\n */\n draftRoute?: string | null\n /** The boot saw the shortcut: open the site tools at once. */\n open?: boolean\n}\n\nexport interface ZapLive {\n /** Framed and listening; false outside a frame or after `destroy`. */\n readonly active: boolean\n /** The underlying runtime, null when inactive (tests and debugging). */\n readonly runtime: PageRuntime | null\n destroy(): void\n}\n\nlet current: ZapLive | null = null\n\nconst INERT: ZapLive = {\n active: false,\n runtime: null,\n destroy() {},\n}\n\n/**\n * Start the Live client. Call once per page, as early as convenient; a second\n * call returns the first one's handle (a script tag and a bundled import on\n * the same page do not start two clients).\n */\nexport function initZap(options: InitZapOptions, runtimeOptions: RuntimeOptions = {}): ZapLive {\n if (current?.active) return current\n const win = runtimeOptions.win ?? globalWindow()\n if (!win) return INERT\n const siteKey = typeof options?.siteKey === 'string' ? options.siteKey.trim() : ''\n if (!siteKey || siteKey.length > 128) {\n console.warn('eelzap: no site key')\n return INERT\n }\n const parent = runtimeOptions.parent ?? (win.parent as Window | null)\n const dev = developmentZapOrigin(options.zapOrigin, win.location.hostname)\n if (options.zapOrigin && !dev && safeOrigin(options.zapOrigin) !== ZAP_PRODUCTION_ORIGIN) {\n console.warn('eelzap: zapOrigin is for a local Zap on a local page')\n }\n if (!parent || (parent as unknown) === win) return startSuggestions(options, win, dev)\n\n const exact = dev ? [ZAP_PRODUCTION_ORIGIN, dev] : [ZAP_PRODUCTION_ORIGIN]\n\n let helloOk = false\n const runtime = startRuntime(\n {\n editorOrigins: exact,\n preHelloTargets: () => {\n const candidate = parentOriginHint(win)\n return candidate && exact.includes(candidate) ? [candidate] : exact\n },\n draftRoute: isDraftRoutePath(options.draftRoute) ? options.draftRoute : null,\n pageUrl: () => win.location.href,\n lazyOverlay: true,\n accept: (message) => {\n if (message.type === 'zap:hello') {\n helloOk = message.payload.siteKey === siteKey\n return helloOk\n }\n return helloOk\n },\n onMessage: (message: EditorMessage) => {\n if (message.type === 'zap:hello') {\n // A hello without a token (drafts off, or a new session) forgets the\n // last one: a stale token must not keep serving drafts.\n try {\n const token = message.payload.previewToken\n if (token) sessionStore(win)?.setItem(PREVIEW_TOKEN_STORAGE_KEY, token)\n else forgetPreviewToken(win)\n } catch {\n // Storage blocked: the draft-mode cookie is the only path left.\n }\n } else if (message.type === 'zap:values') {\n dispatchValues(message.payload, win)\n }\n },\n },\n { ...runtimeOptions, win, parent: parent as unknown as RuntimeOptions['parent'] },\n )\n\n const handle: ZapLive = {\n get active() {\n return current === handle\n },\n runtime,\n destroy() {\n runtime.destroy()\n forgetPreviewToken(win)\n if (current === handle) current = null\n },\n }\n current = handle\n return handle\n}\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\nlet stopSuggestions: (() => void) | null = null\n\n/**\n * Not framed: Live suggestion mode (§3.4, `site/boot.ts`), when the site\n * passed its id. Zap is production or the local dev opt-in, never a preview\n * deployment; Nest likewise (`authOrigin` follows the `zapOrigin` rule).\n */\nfunction startSuggestions(options: InitZapOptions, win: Window, devZap: string | null): ZapLive {\n const siteId = typeof options.siteId === 'string' ? options.siteId.trim() : ''\n if (stopSuggestions || !UUID_RE.test(siteId)) return INERT\n const local = isLocalHost(win.location.hostname)\n const nest = parseUrl(options.authOrigin ?? '')\n const devNest = local && nest && isLocalHost(nest.hostname) ? nest.origin : null\n const stop = startSiteMode({\n win,\n siteId,\n zapOrigin: devZap ?? ZAP_PRODUCTION_ORIGIN,\n authOrigin: devNest ?? NEST_PRODUCTION_ORIGIN,\n shortcut: options.shortcut,\n locale: options.locale,\n chunkBase: options.chunkBase,\n open: options.open,\n })\n stopSuggestions = stop\n return {\n active: false,\n runtime: null,\n destroy() {\n stop()\n if (stopSuggestions === stop) stopSuggestions = null\n },\n }\n}\n\nfunction safeOrigin(value: string): string {\n return parseUrl(value)?.origin ?? ''\n}\n\n/**\n * Who framed us, as far as the page can tell before the hello: Chrome and\n * Safari expose `location.ancestorOrigins`; otherwise the referrer, which the\n * editor sends as its origin (`referrerpolicy=\"origin\"`). Only a hint: the\n * browser delivers `zap:ready` only if the parent really is that origin.\n */\nfunction parentOriginHint(win: Window): string | null {\n const ancestors = (win.location as Location & { ancestorOrigins?: DOMStringList }).ancestorOrigins\n if (ancestors && ancestors.length > 0) return ancestors[0] ?? null\n const referrer = win.document.referrer\n return referrer ? safeOrigin(referrer) || null : null\n}\n\n/** Specs only: forget the page's client and every `onValues` subscription. */\nexport function __resetLiveForTests(): void {\n current?.destroy()\n current = null\n stopSuggestions?.()\n stopSuggestions = null\n subscriptions.clear()\n}\n"],"mappings":"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","names":["current","text"]}
1
+ {"version":3,"sources":["../src/boot/local.ts","../src/preview/site/load.ts","../src/preview/site/boot.ts","../src/preview/page-bridge.ts","../src/preview/sanitize.ts","../src/preview/values.ts","../src/preview/runtime.ts","../src/preview/live.ts"],"sourcesContent":["/**\n * Development origins: `localhost`, `127.0.0.1`, `[::1]` and any `*.localhost`\n * name (which browsers resolve to the machine itself and treat as secure\n * contexts), over http OR https, any port: a site in development runs on\n * either.\n *\n * Shared by the boot (`./boot.ts`, which every visitor runs, so one regex and\n * no more) and the overlay (`../preview/live.ts`), so the two never disagree\n * about what counts as local.\n */\nexport const LOCAL_ORIGIN =\n /^https?:\\/\\/(?:(?:[a-z\\d-]+\\.)*localhost|127\\.0\\.0\\.1|\\[::1\\])(?::\\d+)?$/i\n\n/** True for a development hostname (no scheme, no port). */\nexport const isLocalHost = (hostname: string): boolean => LOCAL_ORIGIN.test(`http://${hostname}`)\n","import type { ChunkModule, ChunkName } from './config'\n\n/**\n * The npm build's chunk loader: a dynamic `import()`, which the site's bundler\n * (and this package's own ESM build, `tsup` with splitting) turns into a\n * separate file fetched only when called. The script-tag build swaps this\n * module for `load-cdn.ts` at bundle time (`scripts/bundle-config.mjs`).\n */\nexport function loadChunk(name: ChunkName, _base?: string | null): Promise<ChunkModule> {\n return name === 'signin' ? import('./signin') : import('./suggest')\n}\n","import {\n PENDING_STORAGE_KEY,\n SIGNIN_CHANNEL,\n siteClientId,\n STATE_PREFIX,\n type CallbackParams,\n type ChunkHandle,\n type ChunkModule,\n type ChunkName,\n type SiteContext,\n type StartOptions,\n} from './config'\nimport { loadChunk } from './load'\nimport { DEFAULT_SHORTCUT, isShortcut } from './shortcut'\nimport { readSession } from './token'\n\n/**\n * The core's part of Live suggestion mode (zap-cms-v2 §3.4, ADR 041), for a\n * page that is NOT framed by Zap. It draws nothing and loads nothing until one\n * of these holds:\n *\n * 1. **This page is the OAuth callback**: `{origin}/?code&state&iss` with a\n * `state` this client minted and `iss` = the compiled-in issuer. In the\n * sign-in popup the code goes to the opener over\n * `BroadcastChannel('eel-zap')` and the popup closes; after a full-page\n * sign-in (the popup was blocked) the `signin` chunk finishes here. Either\n * way the code leaves the address bar at once.\n * 2. **This tab holds a live token** for this site (or a refresh token that\n * can renew it, `session.ts`): the `suggest` chunk.\n * 3. **The person asks**: `?zap` in the URL, or the shortcut (Shift Z by\n * default, never while typing in a field or composing, `shortcut.ts`): the\n * `signin` chunk, which shows the launcher.\n *\n * Ordinary visitors meet none of these, so they get one `keydown` listener\n * and nothing else. Whatever the callback carries is checked again by the\n * `signin` chunk (state, issuer, the PKCE verifier only the opener holds);\n * this file only decides whether a page looks like ours.\n */\n\nexport interface SiteModeOptions {\n win: Window\n siteId: string\n zapOrigin: string\n authOrigin: string\n locale?: 'es' | 'en'\n /** The letter pressed with Shift to open the launcher; false turns the shortcut off. */\n shortcut?: string | false\n /** Script-tag build: the core's own URL, the chunks are its siblings. */\n chunkBase?: string | null\n /** Open at once: the boot already saw the trigger (the shortcut). */\n open?: boolean\n /** The site's draft-mode route, for «Editar» on an untagged page (`draft-session.ts`). */\n draftRoute?: string | null\n}\n\n/** The callback params when this page is one of ours, else null. */\nexport function readCallback(win: Window, authOrigin: string): CallbackParams | null {\n const { pathname, search } = win.location\n if (pathname !== '/' || !search) return null\n const params = new URLSearchParams(search)\n const code = params.get('code')\n const state = params.get('state')\n const iss = params.get('iss')\n if (!code || !state?.startsWith(STATE_PREFIX) || iss !== authOrigin) return null\n return { code, state, iss }\n}\n\nfunction pendingRedirect(win: Window): boolean {\n try {\n return JSON.parse(win.sessionStorage.getItem(PENDING_STORAGE_KEY) ?? '{}').mode === 'redirect'\n } catch {\n return false\n }\n}\n\nfunction pageLocale(doc: Document): 'es' | 'en' {\n return /^en\\b/i.test(doc.documentElement.lang) ? 'en' : 'es'\n}\n\nexport function startSiteMode(options: SiteModeOptions): () => void {\n const { win, siteId } = options\n const doc = win.document\n const key = options.shortcut === undefined ? DEFAULT_SHORTCUT : options.shortcut\n let current: ChunkHandle | null = null\n let opening = false\n\n const ctx: SiteContext = {\n win,\n doc,\n siteId,\n zapOrigin: options.zapOrigin,\n authOrigin: options.authOrigin,\n clientId: siteClientId(options.zapOrigin, siteId),\n redirectUri: `${win.location.origin}/`,\n locale: options.locale ?? pageLocale(doc),\n shortcut: key,\n draftRoute: options.draftRoute ?? null,\n async open(name: ChunkName, startOptions?: StartOptions) {\n opening = true\n try {\n const module: ChunkModule = await loadChunk(name, options.chunkBase)\n current?.destroy()\n current = module.start(ctx, startOptions)\n } catch (error) {\n console.warn('eelzap: suggestions failed', error)\n } finally {\n opening = false\n }\n },\n close() {\n current?.destroy()\n current = null\n },\n }\n\n const callback = readCallback(win, options.authOrigin)\n if (callback) {\n // The code and state must not stay in the address bar or the history.\n win.history.replaceState(win.history.state, '', '/')\n if (pendingRedirect(win)) {\n void ctx.open('signin', { callback })\n } else {\n try {\n const channel = new BroadcastChannel(SIGNIN_CHANNEL)\n channel.postMessage({ type: 'eelzap:callback', ...callback })\n channel.close()\n } finally {\n win.close()\n }\n }\n return ctx.close\n }\n\n const launch = () => {\n if (current || opening) return\n void ctx.open(readSession(win, siteId) ? 'suggest' : 'signin', { reason: 'trigger' })\n }\n\n if (readSession(win, siteId)) void ctx.open('suggest')\n else if (options.open || new URLSearchParams(win.location.search).has('zap')) launch()\n\n const onKey = (event: KeyboardEvent) => {\n if (key && isShortcut(event, key)) launch()\n }\n win.addEventListener('keydown', onKey, true)\n\n return () => {\n win.removeEventListener('keydown', onKey, true)\n ctx.close()\n }\n}\n","import {\n emptyDropStats,\n envelope,\n MAX_MESSAGE_BYTES,\n messageBytes,\n parseEditorMessage,\n type DropReason,\n type DropStats,\n type EditorMessage,\n type PageMessages,\n type PageMessageType,\n} from './protocol'\n\n/**\n * The PAGE side of the bridge (§2.6 \"Origins\").\n *\n * A message from the editor is accepted only when ALL hold:\n *\n * 1. `event.source === parent` — the window that framed us, nothing else\n * (another frame, a popup, the page itself posting to itself).\n * 2. `event.origin` is one of `editorOrigins` (the Zap origins the client\n * trusts). `'null'` (an opaque origin) is never accepted.\n * 3. It parses as a v1 envelope of a known editor to page type, under 256 KB.\n * 4. Its `session` matches. Before `zap:hello` the session is unknown, and\n * nothing but a hello is accepted; the first valid hello fixes it. After\n * that, every message must carry it; a second hello with another session\n * is refused.\n *\n * Every drop is counted in `stats` by reason; nothing throws.\n *\n * Neither side ever posts with `'*'`. The page answers the origin that said\n * hello (before it, each accepted candidate; the browser drops a message whose\n * target is not the parent's real origin). The editor posts only to the exact\n * page origin that said `zap:ready` (`editor.ts`).\n */\n\nexport interface MessageTargetLike {\n postMessage(message: unknown, targetOrigin: string): void\n}\n\nexport interface PageBridgeOptions {\n win: Window\n /** `window.parent` in production; injected in specs. */\n parent: MessageTargetLike\n editorOrigins: readonly string[]\n /**\n * Where to post BEFORE the hello names the editor's origin. Defaults\n * to `editorOrigins`. Every entry must itself be an accepted origin; the\n * browser drops a message whose target is not the parent's real origin.\n */\n preHelloTargets?(): readonly string[]\n onMessage(message: EditorMessage): void\n onDrop?(reason: DropReason): void\n}\n\nexport interface PageBridge {\n post<K extends PageMessageType>(type: K, payload: PageMessages[K]): boolean\n readonly session: string | null\n readonly stats: DropStats\n destroy(): void\n}\n\nexport function createPageBridge(options: PageBridgeOptions): PageBridge {\n const stats = emptyDropStats()\n let session: string | null = null\n let helloOrigin: string | null = null\n const origins = options.editorOrigins.filter((o) => typeof o === 'string' && o !== '')\n\n const isAccepted = (origin: string) =>\n typeof origin === 'string' && origin !== 'null' && origins.includes(origin)\n\n const drop = (reason: DropReason) => {\n stats[reason]++\n options.onDrop?.(reason)\n }\n\n const listener = (event: MessageEvent) => {\n if (event.source !== (options.parent as unknown)) return drop('wrong-source')\n if (!isAccepted(event.origin)) return drop('wrong-origin')\n const parsed = parseEditorMessage(event.data)\n if (!parsed.ok) return drop(parsed.reason)\n const message = parsed.message\n\n if (message.type === 'zap:hello') {\n if (message.payload.session !== message.session) return drop('invalid-payload')\n if (session !== null && message.session !== session) return drop('wrong-session')\n if (session === null) session = message.session\n helloOrigin = event.origin\n } else if (session === null || message.session !== session || helloOrigin === null) {\n // Nothing but a hello is accepted before the hello.\n return drop('wrong-session')\n }\n options.onMessage(message)\n }\n\n options.win.addEventListener('message', listener)\n\n return {\n post(type, payload) {\n const message = envelope(type, session ?? '', payload)\n if (messageBytes(message) > MAX_MESSAGE_BYTES) return false\n // Never '*': after the hello we answer the origin that said hello;\n // before it, each allowed origin (a non-matching target is dropped by\n // the browser, so only the real parent receives it).\n const targets = helloOrigin\n ? [helloOrigin]\n : (options.preHelloTargets?.() ?? origins).filter(isAccepted)\n for (const origin of targets) options.parent.postMessage(message, origin)\n return targets.length > 0\n },\n get session() {\n return session\n },\n stats,\n destroy() {\n options.win.removeEventListener('message', listener)\n },\n }\n}\n","/**\n * The allowlist sanitiser for rich-text substitution (§2.4 \"Draft values\",\n * §7.2 \"Rich text substitution\").\n *\n * ## Why this is not \"the delivery API's allowlist\"\n *\n * The spec says to sanitise \"with the same allowlist the delivery API\n * applies\". The delivery API applies NONE: `apps/zap/src/lib/cms/rich-text-media.ts`\n * strips `<script>…</script>` and nothing else, on purpose (its header: an\n * allowlist would drop `data-media-id` and change bytes, flipping every\n * delivery ETag). The suite's real allowlist is `@eel/ui-web`'s\n * `sanitizeRichTextHtml`, a regex sanitiser for Swarm/Wrap editor HTML whose\n * `src` rule admits only the suite's own file route — Zap rich text points at\n * R2 media URLs, so reusing it verbatim would blank every image.\n *\n * So the allowlist lives HERE, as the one source of truth for HTML that Zap\n * puts into a customer page: the tags and attributes Zap's rich-text editor\n * emits (Tiptap StarterKit + link, underline, text-align, the media image\n * with `data-media-id`), nothing else. Constants are exported so a server-side\n * twin, if one is ever needed, imports them rather than copying.\n *\n * ## DOM-based, not regex\n *\n * The page is a real browser, so the HTML is parsed by the browser's own\n * parser into an INERT `<template>` (no script runs, no image loads, no\n * handler fires while parsing) and the resulting tree is walked: disallowed\n * elements are dropped with their content (script-like) or unwrapped\n * (formatting-like), every attribute not on the list is removed, and URL\n * attributes are re-parsed with `URL` so `java\\tscript:` and friends are\n * judged by the scheme the browser would actually use.\n */\n\n/** Elements kept as they are. Everything else is unwrapped or dropped. */\nexport const RICH_TEXT_ALLOWED_TAGS: ReadonlySet<string> = new Set([\n 'p',\n 'br',\n 'strong',\n 'b',\n 'em',\n 'i',\n 'u',\n 's',\n 'code',\n 'pre',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'ul',\n 'ol',\n 'li',\n 'blockquote',\n 'hr',\n 'a',\n 'span',\n 'img',\n])\n\n/** Elements removed WITH their content: executable, embedding or form markup. */\nexport const RICH_TEXT_DROPPED_TAGS: ReadonlySet<string> = new Set([\n 'script',\n 'style',\n 'template',\n 'iframe',\n 'frame',\n 'frameset',\n 'object',\n 'embed',\n 'applet',\n 'noscript',\n 'svg',\n 'math',\n 'form',\n 'input',\n 'button',\n 'select',\n 'textarea',\n 'link',\n 'meta',\n 'base',\n 'title',\n 'head',\n])\n\n/** Attributes kept, per element (`*` applies to every allowed element). */\nexport const RICH_TEXT_ALLOWED_ATTRS: Readonly<Record<string, ReadonlySet<string>>> = {\n '*': new Set(['title', 'style']),\n a: new Set(['href', 'target', 'rel']),\n img: new Set(['src', 'alt', 'width', 'height', 'data-media-id']),\n}\n\n/** `style` survives only as text alignment (the TextAlign extension). */\nconst ALLOWED_STYLE = /^\\s*text-align\\s*:\\s*(left|right|center|justify)\\s*;?\\s*$/i\nconst LINK_SCHEMES = new Set(['http:', 'https:', 'mailto:', 'tel:'])\nconst IMAGE_SCHEMES = new Set(['http:', 'https:'])\nconst DIMENSION = /^\\d{1,5}$/\nconst MEDIA_ID = /^[A-Za-z0-9-]{1,64}$/\n\n/**\n * Is `value` a URL on one of `schemes` once the browser parses it? Relative\n * URLs resolve against the document's base (the customer page), which is\n * always http(s).\n */\nexport function isSafeUrl(value: string, schemes: ReadonlySet<string>, base?: string): boolean {\n try {\n const url = new URL(value, base ?? 'https://base.invalid/')\n return schemes.has(url.protocol)\n } catch {\n return false\n }\n}\n\n/** A link target the page may carry: http(s), mailto or tel. */\nexport function isSafeLinkUrl(value: string, base?: string): boolean {\n return isSafeUrl(value, LINK_SCHEMES, base)\n}\n\nexport function isSafeImageUrl(value: string, base?: string): boolean {\n return isSafeUrl(value, IMAGE_SCHEMES, base)\n}\n\n/** Every candidate URL in a `srcset` must be a safe image URL. */\nexport function isSafeSrcset(value: string, base?: string): boolean {\n const candidates = value.split(',').map((part) => part.trim().split(/\\s+/)[0] ?? '')\n return candidates.length > 0 && candidates.every((url) => url !== '' && isSafeImageUrl(url, base))\n}\n\nfunction cleanAttributes(element: Element, tag: string, base: string | undefined): void {\n const allowed = RICH_TEXT_ALLOWED_ATTRS[tag]\n const global = RICH_TEXT_ALLOWED_ATTRS['*']!\n for (const attribute of Array.from(element.attributes)) {\n const name = attribute.name.toLowerCase()\n const value = attribute.value\n let keep = global.has(name) || (allowed?.has(name) ?? false)\n if (keep && name === 'style') keep = ALLOWED_STYLE.test(value)\n if (keep && name === 'href') keep = isSafeUrl(value, LINK_SCHEMES, base)\n if (keep && name === 'src') keep = isSafeImageUrl(value, base)\n if (keep && name === 'target') keep = value === '_blank'\n if (keep && (name === 'width' || name === 'height')) keep = DIMENSION.test(value)\n if (keep && name === 'data-media-id') keep = MEDIA_ID.test(value)\n if (!keep) element.removeAttribute(attribute.name)\n }\n if (tag === 'a' && element.getAttribute('target') === '_blank') {\n element.setAttribute('rel', 'noopener noreferrer')\n } else if (tag === 'a') {\n element.removeAttribute('rel')\n }\n}\n\nfunction walk(parent: ParentNode, base: string | undefined): void {\n for (const node of Array.from(parent.childNodes)) {\n if (node.nodeType === 3 /* TEXT */) continue\n if (node.nodeType !== 1 /* ELEMENT */) {\n // Comments, processing instructions, CDATA: never kept.\n node.parentNode?.removeChild(node)\n continue\n }\n const element = node as Element\n const tag = element.localName.toLowerCase()\n const foreign = element.namespaceURI !== 'http://www.w3.org/1999/xhtml'\n if (foreign || RICH_TEXT_DROPPED_TAGS.has(tag)) {\n element.remove()\n continue\n }\n walk(element, base)\n if (!RICH_TEXT_ALLOWED_TAGS.has(tag)) {\n // Unknown formatting (div, section, font, …): keep the text, drop the tag.\n element.replaceWith(...Array.from(element.childNodes))\n continue\n }\n cleanAttributes(element, tag, base)\n // An image whose src did not survive is dropped, not left broken.\n if (tag === 'img' && !element.hasAttribute('src')) element.remove()\n }\n}\n\n/**\n * Sanitise `html` into a fragment owned by `doc`, ready for\n * `element.replaceChildren(fragment)`. Never sets `innerHTML` on a live node.\n */\nexport function sanitizeRichText(\n html: string,\n doc: Document,\n base: string | undefined = doc.baseURI,\n): DocumentFragment {\n const template = doc.createElement('template')\n template.innerHTML = html\n const content = template.content\n walk(content, base)\n return content\n}\n\n/** The sanitised HTML as a string (for tests and for comparisons). */\nexport function sanitizeRichTextToString(html: string, doc: Document): string {\n const holder = doc.createElement('div')\n holder.appendChild(sanitizeRichText(html, doc))\n return holder.innerHTML\n}\n\n/** Plain text of a rich-text value, for an element that did not opt into HTML. */\nexport function richTextToText(html: string, doc: Document): string {\n // Through the sanitiser first, so the text of a dropped <script> or <style>\n // never shows up as page copy.\n return sanitizeRichText(html, doc).textContent ?? ''\n}\n","import { cleanStega, splitStega } from '../stega'\nimport type { PreviewValue, ValuesPayload } from './protocol'\nimport {\n isSafeImageUrl,\n isSafeLinkUrl,\n isSafeSrcset,\n richTextToText,\n sanitizeRichText,\n} from './sanitize'\nimport type { TagIndex, TaggedElement } from './tags'\n\n/**\n * Draft value substitution (`zap:values`, §2.4 \"Draft values\").\n *\n * What is touched, and nothing else:\n *\n * - text (`string`, `number`, `boolean`, `{ text }`) → `textContent`; on an\n * `<img>` it becomes `alt` (an image tagged with an alt-text field).\n * - `{ image }` → `src`, `srcset` and `alt` of the element when it is an\n * `<img>`, else of its first descendant `<img>`; `<source>` siblings in a\n * `<picture>` lose `srcset` so the browser actually shows the new `src`.\n * URLs must be http(s) (relative resolves against the page).\n * - `{ html }` → sanitised children (never `innerHTML` on the live node), ONLY\n * when the element carries `data-zap-html`; otherwise its plain text.\n * - `{ url }` (a URL field) and `{ email }` (an EMAIL field, as `mailto:`) →\n * the `href` of the element when it is an `<a>`, else of its first\n * descendant `<a>`; the link's label stays the site's. With no link, the\n * address is the text. Only http(s), mailto and tel links. (An image field\n * is `{ image }`, above: its `src`, never its text.)\n * - `null` → empty text; an image keeps what it had.\n *\n * Never an attribute other than `href`, `src`, `srcset` and `alt`, never a\n * style, never an event handler.\n *\n * An element found by a stega marker (`source: 'stega'`) gets text only, and\n * only in the text node that carries the marker, which keeps it: the rest of\n * the element (other text, other fields, markup) is the site's. Rich text and\n * images found that way are left to the site's `onValues` handler.\n *\n * ## Formatting is the editor's job\n *\n * Numbers, dates and enum labels arrive already formatted (`PreviewValue`\n * says why): the browser core has no locale data, no field types and a size\n * budget, and the editor already has the suite's formatters.\n *\n * ## Re-application\n *\n * The last value per field is remembered, so when the site re-renders a tagged\n * node (hydration, client routing) `reapply()` writes it again. An element is\n * rewritten only when it differs from what we would write, so our own writes\n * — which the mutation observer also sees — settle after one pass.\n */\n\ninterface Desired {\n locale: string\n value: PreviewValue\n}\n\nexport class ValueApplier {\n private desired = new Map<string, Desired>()\n private appliedHtml = new WeakMap<Element, { source: string; output: string }>()\n\n constructor(\n private readonly index: TagIndex,\n private readonly doc: Document,\n ) {}\n\n /** Remember and apply a patch. Returns how many elements were written. */\n apply(payload: ValuesPayload): number {\n let written = 0\n for (const [fieldKey, raw] of Object.entries(payload.patch)) {\n // Never a marker from a value: a stega text node keeps ITS markers and\n // appends the value, so a value carrying markers (text copied from a\n // preview into the form) would add more on every pass, and each new\n // marker is one more tagged entry at the next scan — the index and the\n // node grow without bound until the tab runs out of memory.\n const value = cleanStega(raw)\n this.desired.set(`${payload.recordRef}#${fieldKey}`, { locale: payload.locale, value })\n for (const tagged of this.index.byField(payload.recordRef, fieldKey)) {\n if (this.write(tagged, payload.locale, value)) written++\n }\n }\n return written\n }\n\n /** Write every remembered value again where the page drifted from it. */\n reapply(): number {\n let written = 0\n for (const tagged of this.index.elements) {\n const desired = this.desired.get(tagged.tag)\n if (desired && this.write(tagged, desired.locale, desired.value)) written++\n }\n return written\n }\n\n private write(tagged: TaggedElement, locale: string, value: PreviewValue): boolean {\n // An element pinned to another locale shows that locale's copy; leave it.\n if (tagged.locale && tagged.locale !== locale) return false\n const element = tagged.element\n\n if (tagged.textNode) {\n const text = plainText(value)\n if (text === null) return false\n const node = tagged.textNode\n // The node is the field's own text; keep the whitespace a template put before it.\n const split = splitStega(node.data)\n const next = /^\\s*/.exec(split.text)![0] + text + split.markers\n if (node.data === next) return false\n node.data = next\n return true\n }\n\n if (value !== null && typeof value === 'object' && 'image' in value) {\n return writeImage(element, value.image, this.doc.baseURI)\n }\n\n if (value !== null && typeof value === 'object' && 'html' in value) {\n if (tagged.html) return this.writeHtml(element, value.html)\n return writeText(element, richTextToText(value.html, this.doc))\n }\n\n if (value !== null && typeof value === 'object' && ('url' in value || 'email' in value)) {\n return writeLink(element, value, this.doc.baseURI)\n }\n\n const text = plainText(value) ?? ''\n if (isImg(element)) return setAttr(element, 'alt', text)\n return writeText(element, text)\n }\n\n private writeHtml(element: Element, html: string): boolean {\n // Unchanged when the source is the one we last wrote AND the node still\n // holds exactly our output (the site did not re-render it since).\n const last = this.appliedHtml.get(element)\n if (last && last.source === html && last.output === element.innerHTML) return false\n element.replaceChildren(sanitizeRichText(html, this.doc))\n this.appliedHtml.set(element, { source: html, output: element.innerHTML })\n return true\n }\n}\n\n/** A value as plain text, or null for the shapes that are not text (html, image). */\nfunction plainText(value: PreviewValue): string | null {\n if (value === null || typeof value !== 'object') return value === null ? '' : String(value)\n const v = value as { text?: string; url?: string; email?: string }\n return v.text ?? v.url ?? v.email ?? null\n}\n\n/** A URL or EMAIL value: the `href` of the link, never its label. */\nfunction writeLink(element: Element, value: { url: string } | { email: string }, base: string) {\n const email = 'email' in value\n const address = email ? value.email : value.url\n const link = element.localName === 'a' ? element : element.querySelector('a')\n if (!link) return writeText(element, address)\n const href = email && address ? `mailto:${address}` : address\n return isSafeLinkUrl(href, base) && setAttr(link, 'href', href)\n}\n\nfunction isImg(element: Element): element is HTMLImageElement {\n return element.localName === 'img'\n}\n\nfunction writeText(element: Element, text: string): boolean {\n if (element.textContent === text) return false\n element.textContent = text\n return true\n}\n\nfunction setAttr(element: Element, name: string, value: string): boolean {\n if (element.getAttribute(name) === value) return false\n element.setAttribute(name, value)\n return true\n}\n\nfunction writeImage(\n element: Element,\n image: { src?: string | null; srcset?: string | null; alt?: string | null },\n base: string,\n): boolean {\n const img = isImg(element) ? element : element.querySelector('img')\n if (!img) return false\n let changed = false\n if (image.src && isSafeImageUrl(image.src, base)) {\n changed = setAttr(img, 'src', image.src) || changed\n const picture = img.parentElement?.localName === 'picture' ? img.parentElement : null\n if (picture) {\n for (const source of Array.from(picture.children)) {\n if (source.localName === 'source' && source.hasAttribute('srcset')) {\n source.removeAttribute('srcset')\n changed = true\n }\n }\n }\n if (image.srcset === undefined || image.srcset === null) {\n // A stale srcset would win over the new src.\n if (img.hasAttribute('srcset')) {\n img.removeAttribute('srcset')\n changed = true\n }\n }\n }\n if (image.srcset && isSafeSrcset(image.srcset, base)) {\n changed = setAttr(img, 'srcset', image.srcset) || changed\n }\n if (typeof image.alt === 'string') changed = setAttr(img, 'alt', image.alt) || changed\n return changed\n}\n","import { buildAnchor, findAnchorElement } from './anchor'\nimport { Overlay } from './overlay'\nimport { createPageBridge, type MessageTargetLike, type PageBridge } from './page-bridge'\nimport {\n CAPABILITIES,\n PAGE_UNLOAD_ERROR,\n type EditorMessage,\n type PausedPayload,\n type TagSummary,\n} from './protocol'\nimport { observeTags, TagIndex, tagGuard, type TagProblem } from './tags'\nimport { ValueApplier } from './values'\n\n/**\n * The page runtime: index, overlay, values and bridge, wired together. The\n * Live client (`live.ts`) boots it inside the customer's own page, framed by\n * Zap's editor by its real URL (§2.7).\n */\n\nexport const CLIENT_VERSION = '0.10.0'\n\nexport interface RuntimeOptions {\n win?: Window\n doc?: Document\n parent?: MessageTargetLike\n /** Rescan throttle. Default 250 ms. */\n throttleMs?: number\n}\n\nexport interface PageRuntime {\n readonly bridge: PageBridge\n readonly index: TagIndex\n readonly overlay: Overlay\n /** Force a rescan + report now (specs, and after a navigation); nothing once paused. */\n refresh(): void\n destroy(): void\n}\n\nconst MAX_PROBLEM_REPORTS = 20\n\n/**\n * What one runtime needs beyond the DOM: who it talks to and how the page\n * behaves. The Live client builds it from the origins it trusts (`live.ts`).\n */\nexport interface RuntimeConfig {\n editorOrigins: readonly string[]\n /** Where `zap:ready` goes before the hello names the editor. */\n preHelloTargets?(): readonly string[]\n /** The site's draft-mode route path, announced in `zap:ready`. */\n draftRoute?: string | null\n /** The page's real URL (`location.href`; it moves on client-side routing). */\n pageUrl(): string\n /**\n * Nothing is added to the page (no overlay element, no listeners) until\n * a verified `zap:hello`, so a page framed by anyone else stays untouched (§7.3 \"Overlay as an attack surface on public pages\").\n */\n lazyOverlay?: boolean\n /**\n * A last say on a message the bridge accepted (the hello must name this\n * site). False ignores it, before the runtime touches the page.\n */\n accept?(message: EditorMessage): boolean\n /** Called after the runtime handled an editor message itself. */\n onMessage?(message: EditorMessage): void\n}\n\nexport function startRuntime(config: RuntimeConfig, options: RuntimeOptions = {}): PageRuntime {\n const win = options.win ?? window\n const doc = options.doc ?? win.document\n const parent = options.parent ?? (win.parent as MessageTargetLike)\n const index = new TagIndex(doc)\n const values = new ValueApplier(index, doc)\n let lastReport = ''\n const reportedProblems = new Set<string>()\n let stopObserving: () => void = () => {}\n // The runaway guard (`tagGuard`): once paused, nothing is scanned or written.\n const guard = tagGuard()\n let paused: PausedPayload | null = null\n /**\n * `localStorage['eelzap:debug']` set (any value) on the site's origin:\n * `console.debug('[zap:fields]', …)` for every tags report and its cause,\n * and every editor message dropped (a wrong session drops them all).\n */\n let debug = false\n try {\n debug = !!win.localStorage.getItem('eelzap:debug')\n } catch {\n // Blocked storage: no logs.\n }\n const log = (...args: unknown[]) => debug && console.debug('[zap:fields]', ...args)\n\n const overlay = new Overlay({\n doc,\n win,\n index,\n onInspect: (tagged) => {\n const { x, y, width: w, height: h } = tagged.element.getBoundingClientRect()\n bridge.post('zap:click', {\n recordRef: tagged.recordRef,\n fieldKey: tagged.fieldKey,\n rect: { x, y, w, h },\n })\n },\n onSelect: (elements) =>\n bridge.post('zap:select', {\n anchors: elements.map((element) => buildAnchor(element, index, config.pageUrl(), doc, win)),\n }),\n onSpot: (anchor) => bridge.post('zap:spot', { ...anchor, pageUrl: config.pageUrl() }),\n onPin: (pin) => bridge.post('zap:pin', pin),\n // Zap's one comment toggle: a click on the page or a wrapper is a spot.\n spotOnLarge: true,\n })\n\n const bridge = createPageBridge({\n win,\n parent,\n editorOrigins: config.editorOrigins,\n preHelloTargets: config.preHelloTargets,\n // Any window may post to the page: only drops from the editor are news.\n onDrop: (reason) => reason !== 'wrong-source' && log('drop', reason),\n onMessage: (message) => {\n if (config.accept && !config.accept(message)) return\n handle(message)\n config.onMessage?.(message)\n },\n })\n\n function handle(message: EditorMessage): void {\n switch (message.type) {\n case 'zap:hello': {\n const hello = message.payload\n greeted = true\n overlay.mount()\n overlay.setLabels(hello.labels, hello.foreign)\n overlay.setTheme(hello.theme)\n if (hello.zoom !== undefined) overlay.setZoom(hello.zoom)\n overlay.setMode(hello.mode)\n // The editor may have missed the load-time report (it was not\n // listening yet), so the hello is answered with the current tags.\n report('hello', true)\n // A pause before the hello never reached the editor.\n if (paused) bridge.post('zap:paused', paused)\n return\n }\n case 'zap:values':\n if (!paused) values.apply(message.payload)\n return\n case 'zap:focus-field':\n overlay.focusField(\n index.byField(message.payload.recordRef, message.payload.fieldKey).map((t) => t.element),\n )\n return\n case 'zap:mode':\n overlay.setMode(message.payload)\n return\n case 'zap:zoom':\n overlay.setZoom(message.payload.zoom)\n return\n case 'zap:refresh':\n log('refresh')\n win.location.reload()\n return\n case 'zap:highlight': {\n const found = message.payload.anchors\n .map((anchor) => findAnchorElement(anchor, index, doc, win))\n .filter((element): element is Element => element !== null)\n overlay.highlight(found)\n return\n }\n case 'zap:pins': {\n const pins = []\n for (const { id, n, spot, dom } of message.payload.pins) {\n // A dom pin whose element is gone is not drawn (like a highlight).\n const element = dom ? findAnchorElement(dom, index, doc, win) : null\n if (spot) pins.push({ id, n, spot })\n else if (element) pins.push({ id, n, element })\n }\n overlay.setPins(pins)\n return\n }\n }\n }\n\n function report(why: string, force = false): void {\n const summary: TagSummary[] = index.summary()\n const key = JSON.stringify(summary)\n if (force || key !== lastReport) {\n lastReport = key\n bridge.post('zap:tags', summary)\n log('tags', why, summary.length, config.pageUrl(), bridge.session)\n }\n reportProblems(index.problems)\n }\n\n function reportProblems(problems: TagProblem[]): void {\n for (const problem of problems) {\n const key = `${problem.attribute}=${problem.value}`\n if (reportedProblems.has(key) || reportedProblems.size >= MAX_PROBLEM_REPORTS) continue\n reportedProblems.add(key)\n bridge.post('zap:error', { code: 'TAG_INVALID', detail: key.slice(0, 500) })\n }\n }\n\n // A client-side route change (pushState) moves the page without a load; it\n // shows up as DOM mutations, so the rescan notices the new URL and tells the\n // editor. A real navigation loads a new document, which says ready again.\n let lastUrl = config.pageUrl()\n /** Rescan; false once the guard paused the page (it stops observing, writes nothing more). */\n function scan(page: boolean): boolean {\n if (paused) return false\n index.scan()\n const count = index.elements.length\n const reason = guard(count, page)\n if (!reason) return true\n paused = { reason, count }\n stopObserving()\n console.warn('eelzap: preview paused', paused)\n bridge.post('zap:paused', paused)\n return false\n }\n\n function refresh(page = true): void {\n if (!scan(page)) return\n values.reapply()\n report(page ? 'mutation' : 'reapply')\n const url = config.pageUrl()\n if (url !== lastUrl) {\n lastUrl = url\n bridge.post('zap:navigate', { url })\n }\n }\n\n // Listeners go in NOW, at script start (or, with `lazyOverlay`, the overlay's\n // at the verified hello). When the frame navigates, the editor hears of it\n // before the next document can receive anything (see PAGE_UNLOAD_ERROR).\n const onPageHide = () => {\n bridge.post('zap:error', { code: PAGE_UNLOAD_ERROR })\n }\n // A document restored from the back-forward cache never loads again: it\n // says ready again, and the editor answers with its hello.\n const onPageShow = (event: PageTransitionEvent) => {\n if (event.persisted) {\n log('pageshow')\n announce()\n }\n }\n /** Whether a hello arrived: until then, `zap:ready` is said again. */\n let greeted = false\n function announce(): void {\n log('ready', config.pageUrl())\n bridge.post('zap:ready', {\n version: CLIENT_VERSION,\n capabilities: [...CAPABILITIES],\n pageUrl: config.pageUrl(),\n draftRoute: config.draftRoute ?? null,\n })\n }\n\n if (!config.lazyOverlay) overlay.install()\n win.addEventListener('pagehide', onPageHide)\n win.addEventListener('pageshow', onPageShow)\n\n function boot(): void {\n if (!config.lazyOverlay) overlay.mount()\n if (scan(true)) {\n stopObserving = observeTags(doc.documentElement, refresh, {\n throttleMs: options.throttleMs,\n ignore: (node) => {\n const host = overlay.hostElement\n return !!host && (node === host || host.contains(node))\n },\n })\n }\n announce()\n report('ready', true)\n // An editor whose listener mounted after this ready (a soft navigation in\n // Zap) never answers it: say it again until a hello comes.\n for (const ms of [500, 1500, 4000]) win.setTimeout(() => greeted || announce(), ms)\n }\n\n if (doc.readyState === 'loading') {\n doc.addEventListener('DOMContentLoaded', boot, { once: true })\n } else {\n boot()\n }\n\n return {\n bridge,\n index,\n overlay,\n refresh,\n destroy() {\n stopObserving()\n win.removeEventListener('pagehide', onPageHide)\n win.removeEventListener('pageshow', onPageShow)\n overlay.destroy()\n bridge.destroy()\n },\n }\n}\n","import {\n isDraftRoutePath,\n isPreviewTokenShape,\n type EditorMessage,\n type PreviewValue,\n type RecordRef,\n type ValuesPayload,\n} from './protocol'\nimport { isLocalHost } from '../boot/local'\nimport { NEST_PRODUCTION_ORIGIN } from './site/config'\nimport { startSiteMode } from './site/boot'\nimport { startRuntime, type PageRuntime, type RuntimeOptions } from './runtime'\n\n/**\n * The Live client («En vivo», `.docs/proposals/zap-cms-v2.md` §2.5–2.7): the\n * page runtime (`runtime.ts`: tag index, overlay, value substitution, bridge),\n * booted by the customer's site itself, which Zap's editor frames by its real\n * URL.\n *\n * ```ts\n * import { initZap, onValues, zapAttrs } from '@8ux-co/eelzap/preview'\n *\n * initZap({ siteKey: 'verdeorigen' })\n * <h1 {...zapAttrs(post, 'title')}>{post.content.title}</h1>\n * onValues(post, (patch) => …) // optional: values the site computes from\n * ```\n *\n * ## Who the client listens to (§2.6 \"Origins\")\n *\n * A message from the editor is accepted only when ALL hold, and every other\n * message is dropped and counted (`page-bridge.ts`):\n *\n * 1. `event.source === window.parent`;\n * 2. `event.origin` is the production editor (`https://zap.eel.software`)\n * or the development origin opted into below. Zap's own preview\n * deployments are not editors: no deployment-specific origin is compiled\n * into a public bundle;\n * 3. it parses as a v1 envelope under 256 KB;\n * 4. after the first `zap:hello`, its `session` matches.\n *\n * And the hello itself must name THIS site (`siteKey`); a hello for another\n * site is ignored, and nothing is drawn or stored for it.\n *\n * ## Development: `zapOrigin`\n *\n * `zapOrigin` is the only way to trust another origin, and it is honoured only\n * for a local Zap (`localhost`, `127.0.0.1`, `[::1]` or a `*.localhost` name,\n * over http or https, any port) AND only while the page itself runs on one of\n * those hosts. The boot sets it to the Zap it loaded this file from when that\n * is a local Zap framing a local page, so the site need not pass it. A\n * production page never trusts a local editor, even when the option is left in\n * the site's code; any other value is ignored with a console warning. The\n * production editor needs no option.\n *\n * ## Not framed by Zap, nothing happens\n *\n * Outside a frame the client does nothing at all. Inside a frame it posts one\n * `zap:ready` to the candidate parent origin (and the browser drops it unless\n * the parent really is that Zap origin), and adds nothing to the page — no\n * element, no listener beyond `message` — until a verified hello arrives.\n */\n\n/** The production editor. Always trusted. */\nexport const ZAP_PRODUCTION_ORIGIN = 'https://zap.eel.software'\n\n/** The header the cookieless fallback sends the preview token in (§2.5). */\nexport const ZAP_PREVIEW_TOKEN_HEADER = 'x-zap-preview-token'\n/** Where the client keeps it, per tab and per frame. */\nexport const PREVIEW_TOKEN_STORAGE_KEY = 'eelzap:preview-token'\n\nfunction parseUrl(value: string): URL | null {\n try {\n return new URL(value)\n } catch {\n return null\n }\n}\n\n/**\n * The extra origin `zapOrigin` adds, or null. See \"Development\" above: a\n * local Zap, from a local page, nothing else.\n */\nexport function developmentZapOrigin(\n zapOrigin: string | undefined,\n pageHostname: string,\n): string | null {\n if (!zapOrigin) return null\n const url = parseUrl(zapOrigin)\n if (!url || (url.protocol !== 'http:' && url.protocol !== 'https:')) return null\n if (url.origin === ZAP_PRODUCTION_ORIGIN) return null\n if (!isLocalHost(url.hostname) || !isLocalHost(pageHostname)) return null\n return url.origin\n}\n\n// ── Record references ───────────────────────────────────────────────────────\n\nexport { recordRefsOf, zapAttrs, type EntryLike } from '../refs'\nimport { recordRefsOf, type EntryLike } from '../refs'\nexport { cleanStega } from '../stega'\n\n// ── Values ──────────────────────────────────────────────────────────────────\n\nexport type ValuesHandler = (patch: Record<string, PreviewValue>, payload: ValuesPayload) => void\n\ninterface Subscription {\n refs: RecordRef[]\n handler: ValuesHandler\n}\n\nconst subscriptions = new Set<Subscription>()\n\n/**\n * Call `handler` with each `zap:values` patch for `entry` (unsaved keystrokes\n * from the editor), for sites that compute things from values. The tagged\n * elements are updated anyway; this is for everything else. Values arrive\n * formatted for display, exactly as the overlay writes them. Returns an\n * unsubscribe function. Works before or after `initZap`.\n */\nexport function onValues(entry: EntryLike, handler: ValuesHandler): () => void {\n return subscribeRefs(recordRefsOf(entry), handler)\n}\n\n/** `onValues` for references already resolved (the React hook). */\nexport function subscribeRefs(refs: readonly RecordRef[], handler: ValuesHandler): () => void {\n const subscription: Subscription = { refs: [...refs], handler }\n subscriptions.add(subscription)\n return () => {\n subscriptions.delete(subscription)\n }\n}\n\n/**\n * The window event every `zap:values` patch is also announced with, so code\n * bundled apart from this client (`useZapLiveUpdates` and `onValues` in\n * `@8ux-co/eelzap/react`, which never bundle the overlay) hears it.\n */\nexport const VALUES_EVENT = 'eelzap:values'\n\nfunction dispatchValues(payload: ValuesPayload, win?: Window): void {\n win?.dispatchEvent(new CustomEvent(VALUES_EVENT, { detail: payload }))\n for (const subscription of [...subscriptions]) {\n if (!subscription.refs.includes(payload.recordRef)) continue\n try {\n subscription.handler(payload.patch, payload)\n } catch (error) {\n // A site handler that throws must not take the bridge down with it.\n console.error('eelzap: onValues handler threw', error)\n }\n }\n}\n\n// ── Preview token (cookieless fallback) ─────────────────────────────────────\n\nfunction sessionStore(win: Window | undefined): Storage | null {\n try {\n return win?.sessionStorage ?? null\n } catch {\n return null\n }\n}\n\n/**\n * The preview token the editor handed this frame, or null. For the\n * cookieless fallback: send it to your own server with `previewHeaders()`,\n * where `getPreviewToken` from `@8ux-co/eelzap/next` reads it.\n */\nexport function getPreviewToken(win: Window | undefined = globalWindow()): string | null {\n try {\n const value = sessionStore(win)?.getItem(PREVIEW_TOKEN_STORAGE_KEY) ?? null\n return isPreviewTokenShape(value) ? value : null\n } catch {\n return null\n }\n}\n\n/** `{ 'x-zap-preview-token': token }` while this frame has one, else `{}`. */\nexport function previewHeaders(win?: Window): Record<string, string> {\n const token = getPreviewToken(win ?? globalWindow())\n return token ? { [ZAP_PREVIEW_TOKEN_HEADER]: token } : {}\n}\n\nfunction forgetPreviewToken(win: Window): void {\n try {\n sessionStore(win)?.removeItem(PREVIEW_TOKEN_STORAGE_KEY)\n } catch {\n // Nothing stored.\n }\n}\n\nfunction globalWindow(): Window | undefined {\n return typeof window === 'undefined' ? undefined : window\n}\n\n// ── initZap ─────────────────────────────────────────────────────────────────\n\nexport interface InitZapOptions {\n /** The Zap site's key (Configuración del sitio). A hello for another site is ignored. */\n siteKey: string\n /**\n * The Zap site's id. Turns on Live suggestion mode outside Zap's frame\n * (§3.4): it names the site's OAuth client. Without it the client only\n * talks to Zap's editor.\n */\n siteId?: string\n /** A local Zap, for development only (see \"Development\" above). */\n zapOrigin?: string\n /** A local Nest (the OAuth issuer), for development only, under the same rule as `zapOrigin`. */\n authOrigin?: string\n /** The letter pressed with Shift to open «Editar» and «Comentar». Default `Z`; false turns it off. */\n shortcut?: string | false\n /** The language of the suggestion UI; default from `<html lang>`, Spanish otherwise. */\n locale?: 'es' | 'en'\n /** Script-tag build only: the core's own URL, so its chunks load from beside it. */\n chunkBase?: string | null\n /**\n * The site's draft-mode route, a path such as `/api/zap-preview`, announced\n * to the editor in `zap:ready` (§2.5). Anything that is not a plain path is\n * dropped (`isDraftRoutePath`).\n */\n draftRoute?: string | null\n /** The boot saw the shortcut: open the site tools at once. */\n open?: boolean\n}\n\nexport interface ZapLive {\n /** Framed and listening; false outside a frame or after `destroy`. */\n readonly active: boolean\n /** The underlying runtime, null when inactive (tests and debugging). */\n readonly runtime: PageRuntime | null\n destroy(): void\n}\n\nlet current: ZapLive | null = null\n\nconst INERT: ZapLive = {\n active: false,\n runtime: null,\n destroy() {},\n}\n\n/**\n * Start the Live client. Call once per page, as early as convenient; a second\n * call returns the first one's handle (a script tag and a bundled import on\n * the same page do not start two clients).\n */\nexport function initZap(options: InitZapOptions, runtimeOptions: RuntimeOptions = {}): ZapLive {\n if (current?.active) return current\n const win = runtimeOptions.win ?? globalWindow()\n if (!win) return INERT\n const siteKey = typeof options?.siteKey === 'string' ? options.siteKey.trim() : ''\n if (!siteKey || siteKey.length > 128) {\n console.warn('eelzap: no site key')\n return INERT\n }\n const parent = runtimeOptions.parent ?? (win.parent as Window | null)\n const dev = developmentZapOrigin(options.zapOrigin, win.location.hostname)\n if (options.zapOrigin && !dev && safeOrigin(options.zapOrigin) !== ZAP_PRODUCTION_ORIGIN) {\n console.warn('eelzap: zapOrigin is for a local Zap on a local page')\n }\n if (!parent || (parent as unknown) === win) return startSuggestions(options, win, dev)\n\n const exact = dev ? [ZAP_PRODUCTION_ORIGIN, dev] : [ZAP_PRODUCTION_ORIGIN]\n\n let helloOk = false\n const draftRoute = isDraftRoutePath(options.draftRoute) ? options.draftRoute : null\n const runtime = startRuntime(\n {\n editorOrigins: exact,\n preHelloTargets: () => {\n const candidate = parentOriginHint(win)\n return candidate && exact.includes(candidate) ? [candidate] : exact\n },\n draftRoute,\n pageUrl: () => win.location.href,\n lazyOverlay: true,\n accept: (message) => {\n if (message.type === 'zap:hello') {\n helloOk = message.payload.siteKey === siteKey\n return helloOk\n }\n return helloOk\n },\n onMessage: (message: EditorMessage) => {\n if (message.type === 'zap:hello') {\n // A hello without a token (drafts off, or a new session) forgets the\n // last one: a stale token must not keep serving drafts.\n try {\n const token = message.payload.previewToken\n const store = sessionStore(win)\n const last = store?.getItem(PREVIEW_TOKEN_STORAGE_KEY)\n if (token) store?.setItem(PREVIEW_TOKEN_STORAGE_KEY, token)\n else forgetPreviewToken(win)\n // The editor renewed the draft session ahead of its end: the new\n // token goes through the site's draft route in the background,\n // which sets the draft cookies again without navigating, so the\n // next reload still reads drafts. The redirect is not followed.\n if (token && last && last !== token && draftRoute) {\n const query = new URLSearchParams({ token, path: win.location.pathname })\n void win\n .fetch(`${draftRoute}?${query}`, { credentials: 'same-origin', redirect: 'manual' })\n .catch(() => {})\n }\n } catch {\n // Storage blocked: the draft-mode cookie is the only path left.\n }\n } else if (message.type === 'zap:values') {\n dispatchValues(message.payload, win)\n }\n },\n },\n { ...runtimeOptions, win, parent: parent as unknown as RuntimeOptions['parent'] },\n )\n\n const handle: ZapLive = {\n get active() {\n return current === handle\n },\n runtime,\n destroy() {\n runtime.destroy()\n forgetPreviewToken(win)\n if (current === handle) current = null\n },\n }\n current = handle\n return handle\n}\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\nlet stopSuggestions: (() => void) | null = null\n\n/**\n * Not framed: Live suggestion mode (§3.4, `site/boot.ts`), when the site\n * passed its id. Zap is production or the local dev opt-in, never a preview\n * deployment; Nest likewise (`authOrigin` follows the `zapOrigin` rule).\n */\nfunction startSuggestions(options: InitZapOptions, win: Window, devZap: string | null): ZapLive {\n const siteId = typeof options.siteId === 'string' ? options.siteId.trim() : ''\n if (stopSuggestions || !UUID_RE.test(siteId)) return INERT\n const local = isLocalHost(win.location.hostname)\n const nest = parseUrl(options.authOrigin ?? '')\n const devNest = local && nest && isLocalHost(nest.hostname) ? nest.origin : null\n const stop = startSiteMode({\n win,\n siteId,\n zapOrigin: devZap ?? ZAP_PRODUCTION_ORIGIN,\n authOrigin: devNest ?? NEST_PRODUCTION_ORIGIN,\n shortcut: options.shortcut,\n locale: options.locale,\n chunkBase: options.chunkBase,\n open: options.open,\n draftRoute: isDraftRoutePath(options.draftRoute) ? options.draftRoute : null,\n })\n stopSuggestions = stop\n return {\n active: false,\n runtime: null,\n destroy() {\n stop()\n if (stopSuggestions === stop) stopSuggestions = null\n },\n }\n}\n\nfunction safeOrigin(value: string): string {\n return parseUrl(value)?.origin ?? ''\n}\n\n/**\n * Who framed us, as far as the page can tell before the hello: Chrome and\n * Safari expose `location.ancestorOrigins`; otherwise the referrer, which the\n * editor sends as its origin (`referrerpolicy=\"origin\"`). Only a hint: the\n * browser delivers `zap:ready` only if the parent really is that origin.\n */\nfunction parentOriginHint(win: Window): string | null {\n const ancestors = (win.location as Location & { ancestorOrigins?: DOMStringList }).ancestorOrigins\n if (ancestors && ancestors.length > 0) return ancestors[0] ?? null\n const referrer = win.document.referrer\n return referrer ? safeOrigin(referrer) || null : null\n}\n\n/** Specs only: forget the page's client and every `onValues` subscription. */\nexport function __resetLiveForTests(): void {\n current?.destroy()\n current = null\n stopSuggestions?.()\n stopSuggestions = null\n subscriptions.clear()\n}\n"],"mappings":"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","names":["current","text"]}
package/dist/react.js CHANGED
@@ -48,7 +48,7 @@ function zapAttrs(entry, fieldKey) {
48
48
 
49
49
  // src/preview/site/shortcut.ts
50
50
  var DEFAULT_SHORTCUT = "Z";
51
- var TYPING = "input,textarea,select,[contenteditable]";
51
+ var TYPING = 'input,textarea,select,[contenteditable]:not([contenteditable="false"])';
52
52
  function isShortcut(event, key) {
53
53
  const target = event.composedPath?.()[0] ?? event.target;
54
54
  return event.shiftKey && !(event.ctrlKey || event.metaKey || event.altKey || event.repeat || event.isComposing) && // Some browsers report a key inside an IME composition only as keyCode 229.
@@ -59,8 +59,8 @@ function isShortcut(event, key) {
59
59
  var LOCAL_ORIGIN = /^https?:\/\/(?:(?:[a-z\d-]+\.)*localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/i;
60
60
 
61
61
  // src/boot/release.ts
62
- var PREVIEW_RELEASE_PATH = "/js/preview/preview.v1.a5d4edd65a63f670.js";
63
- var PREVIEW_RELEASE_INTEGRITY = "sha384-BndA3d22q6r2xdtC8CsXM2jiTzs8BqpU2W5xNMSWbjM6wlbdx9Le3SGYxIXh+04C";
62
+ var PREVIEW_RELEASE_PATH = "/js/preview/preview.v1.d596b252d8afff70.js";
63
+ var PREVIEW_RELEASE_INTEGRITY = "sha384-LeIaGcZk+fQNnNJs65j+955Ez72VLAP5yuHd+J0vLqdkuJX9nQi4axLNf94zCoNX";
64
64
 
65
65
  // src/boot/boot.ts
66
66
  var BOOT_ATTRIBUTES = {
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react.ts","../src/refs.ts","../src/preview/site/shortcut.ts","../src/boot/local.ts","../src/boot/release.ts","../src/boot/boot.ts","../src/boot/component.ts"],"sourcesContent":["import * as React from 'react'\n\nimport { recordRefsOf, type EntryLike, type RecordRef } from './refs'\nimport type { PreviewValue, ValuesPayload } from './preview/protocol'\n\nexport { bootZapPreview, type ZapBootOptions } from './boot/boot'\nexport { ZapPreview, type ZapPreviewProps } from './boot/component'\nexport { recordRefsOf, zapAttrs, type EntryLike } from './refs'\n\n/**\n * `@8ux-co/eelzap/react` (zap-cms-v2 §2.5, §2.7): the preview boot for React\n * sites (`ZapPreview`, `bootZapPreview`) and the hook below. Nothing here\n * bundles the overlay: it loads from the CDN in a preview session, and\n * announces each `zap:values` patch with a window event (`eelzap:values`),\n * which `onValues` and `useZapLiveUpdates` listen to.\n *\n * `useZapLiveUpdates(entry)` returns `entry` with the editor's unsaved values\n * merged in while the page is framed by Zap, and `entry` itself (the same\n * object) otherwise, the shape of Contentful's `useContentfulLiveUpdates`:\n *\n * ```tsx\n * const post = useZapLiveUpdates(props.post)\n * return <h1 {...zapAttrs(post, 'title')}>{post.content.title}</h1>\n * ```\n *\n * The entry is a delivery API item or document (its `content` gets the\n * values) or any object keyed by field (the object itself gets them). Values\n * arrive formatted for display, as the overlay writes them: text, a URL and an\n * email as a string, rich text as its sanitised HTML, an image as\n * `{ url, alt }` merged into what the field held. The boot must run somewhere on the page (`ZapPreview`);\n * without it, or outside Zap, the hook never changes anything.\n */\n\ntype Patch = Record<string, PreviewValue>\n\n/** One display value as a plain field value, merged into what the field held. */\nexport function toFieldValue(value: PreviewValue, previous: unknown): unknown {\n if (value === null || typeof value !== 'object') return value\n if ('text' in value) return value.text\n if ('html' in value) return value.html\n if ('url' in value) return value.url\n if ('email' in value) return value.email\n const image = value.image\n const base =\n previous && typeof previous === 'object' && !Array.isArray(previous)\n ? (previous as Record<string, unknown>)\n : {}\n const next: Record<string, unknown> = { ...base }\n if (image.src !== undefined) next.url = image.src\n if (image.alt !== undefined) next.alt = image.alt\n return next\n}\n\n/** `entry` with `patch` merged into its `content` (or into itself). Pure. */\nexport function mergeLiveValues<T>(entry: T, patch: Patch): T {\n const keys = Object.keys(patch)\n if (keys.length === 0 || !entry || typeof entry !== 'object') return entry\n const record = entry as Record<string, unknown>\n const content = record.content\n const target =\n content && typeof content === 'object' && !Array.isArray(content)\n ? (content as Record<string, unknown>)\n : record\n const merged: Record<string, unknown> = { ...target }\n for (const key of keys) merged[key] = toFieldValue(patch[key] as PreviewValue, target[key])\n return (target === record ? merged : { ...record, content: merged }) as T\n}\n\n/** The window event the overlay announces every `zap:values` patch with. */\nexport const VALUES_EVENT = 'eelzap:values'\n\n/** A values event's detail, checked enough to merge it safely. */\nfunction readValues(event: Event): ValuesPayload | null {\n const detail = (event as CustomEvent<unknown>).detail as Partial<ValuesPayload> | null\n if (!detail || typeof detail.recordRef !== 'string') return null\n const patch = detail.patch\n if (!patch || typeof patch !== 'object' || Array.isArray(patch)) return null\n return detail as ValuesPayload\n}\n\n/** `onValues` for references already resolved. */\nexport function subscribeRefs(\n refs: readonly RecordRef[],\n handler: (patch: Patch, payload: ValuesPayload) => void,\n win: Window | undefined = typeof window === 'undefined' ? undefined : window,\n): () => void {\n if (!win) return () => {}\n const listener = (event: Event) => {\n const payload = readValues(event)\n if (!payload || !refs.includes(payload.recordRef)) return\n try {\n handler(payload.patch, payload)\n } catch (error) {\n // A site handler that throws must not take the others down with it.\n console.error('eelzap: onValues handler failed', error)\n }\n }\n win.addEventListener(VALUES_EVENT, listener)\n return () => win.removeEventListener(VALUES_EVENT, listener)\n}\n\n/**\n * Call `handler` with each unsaved-values patch for `entry` while the page is\n * in Zap's preview, for sites that compute things from values (excerpts,\n * lists, counts). Framework-free despite living here. Returns an unsubscribe\n * function; outside a preview it never fires.\n */\nexport function onValues(\n entry: EntryLike,\n handler: (patch: Patch, payload: ValuesPayload) => void,\n): () => void {\n return subscribeRefs(recordRefsOf(entry), handler)\n}\n\nexport function useZapLiveUpdates<T extends EntryLike>(entry: T): T {\n // Every reference counts: the editor names the record by whichever one the\n // page tagged (the default slug, or a localized one).\n const refsKey = recordRefsOf(entry).join('|')\n const [state, setState] = React.useState<{ refsKey: string; patch: Patch }>({\n refsKey,\n patch: {},\n })\n\n React.useEffect(() => {\n if (!refsKey) return\n return subscribeRefs(refsKey.split('|'), (patch) => {\n setState((previous) => ({\n refsKey,\n patch: previous.refsKey === refsKey ? { ...previous.patch, ...patch } : { ...patch },\n }))\n })\n // `refsKey` stands for the entry's identity: new data for the same entry\n // keeps the unsaved values on top of it.\n }, [refsKey])\n\n const patch = state.refsKey === refsKey ? state.patch : null\n return React.useMemo(() => (patch ? mergeLiveValues(entry, patch) : entry), [entry, patch])\n}\n","/**\n * The record and field grammar of tags (`.docs/proposals/zap-cms-v2.md` §2.4),\n * in a module of its own so the light entries (`./fields`, `./react`, the\n * boot) can name records without pulling in the preview protocol.\n * No imports, no side effects.\n */\n\n/**\n * A record reference in its canonical string form: `collectionKey/slug` for an\n * entry, `doc:documentKey` for a document (the tag grammar, §2.4).\n */\nexport type RecordRef = string\n\n/** Zap's field key pattern. */\nexport const FIELD_KEY_RE = /^[a-z][a-z0-9_]{0,127}$/\n/** Entry and document references; keys and slugs are `[a-z0-9]+(?:-[a-z0-9]+)*`. */\nconst ENTRY_REF_RE = /^([a-z0-9]+(?:-[a-z0-9]+)*)\\/([a-z0-9]+(?:-[a-z0-9]+)*)$/\nconst DOC_REF_RE = /^doc:([a-z0-9]+(?:-[a-z0-9]+)*)$/\nconst MAX_REF_LENGTH = 300\n\nexport type ParsedRecordRef =\n | { kind: 'entry'; collection: string; slug: string }\n | { kind: 'document'; key: string }\n\nexport function parseRecordRef(value: unknown): ParsedRecordRef | null {\n if (typeof value !== 'string' || value.length > MAX_REF_LENGTH) return null\n const doc = DOC_REF_RE.exec(value)\n if (doc) return { kind: 'document', key: doc[1]! }\n const entry = ENTRY_REF_RE.exec(value)\n if (entry) return { kind: 'entry', collection: entry[1]!, slug: entry[2]! }\n return null\n}\n\nexport function isRecordRef(value: unknown): value is RecordRef {\n return parseRecordRef(value) !== null\n}\n\n/** `zpt_` + 32 bytes in base64url, as Zap mints it (zap-cms-v2 §3.5). */\nexport const PREVIEW_TOKEN_RE = /^zpt_[A-Za-z0-9_-]{43}$/\n\n/** A preview token in the shape Zap mints; says nothing about whether it is live. */\nexport function isPreviewTokenShape(value: unknown): value is string {\n return typeof value === 'string' && PREVIEW_TOKEN_RE.test(value)\n}\n\nexport function isFieldKey(value: unknown): value is string {\n return typeof value === 'string' && FIELD_KEY_RE.test(value)\n}\n\n/**\n * What `zapAttrs` and `onValues` accept: a record reference string\n * (`blog/hola`, `doc:home`) or a delivery API entry (an item carries\n * `collection.key` and `slug`, a document `key`).\n */\nexport type EntryLike =\n | RecordRef\n | {\n slug?: string | null\n key?: string | null\n collection?: { key?: string | null } | null\n localizedSlugs?: Record<string, string | null | undefined> | null\n }\n\n/** Every reference an entry answers to (default slug first, then localized ones). */\nexport function recordRefsOf(entry: EntryLike | null | undefined): RecordRef[] {\n if (!entry) return []\n if (typeof entry === 'string') return isRecordRef(entry) ? [entry] : []\n const refs: RecordRef[] = []\n const add = (ref: string) => {\n if (isRecordRef(ref) && !refs.includes(ref)) refs.push(ref)\n }\n const collection = entry.collection?.key\n if (collection) {\n if (entry.slug) add(`${collection}/${entry.slug}`)\n for (const slug of Object.values(entry.localizedSlugs ?? {})) {\n if (slug) add(`${collection}/${slug}`)\n }\n } else if (entry.key) {\n add(`doc:${entry.key}`)\n }\n return refs\n}\n\n/**\n * The tag for one field, as attributes to spread on the element that renders\n * it: `{ 'data-zap': 'blog/hola#title' }`. Empty when the entry or the field\n * key is not one Zap can name, so a bad call never breaks the page.\n */\nexport function zapAttrs(entry: EntryLike, fieldKey: string): { 'data-zap'?: string } {\n const [ref] = recordRefsOf(entry)\n if (!ref || !isFieldKey(fieldKey)) return {}\n return { 'data-zap': `${ref}#${fieldKey}` }\n}\n","/**\n * The keyboard shortcut that opens «Editar» and «Comentar» on the live site\n * (zap-cms-v2 §3.4, owner 2026-10-05): Shift plus one letter, `Z` by default.\n * It replaced «Shift twice», which fired while people typed capitals.\n *\n * Shared by the boot every visitor runs (`src/boot/boot.ts`, under 1 KB) and\n * the overlay core (`./boot.ts`), so the two can never disagree about what\n * counts. No directive, no imports.\n *\n * It never fires while the person is typing: focus in an input, a textarea, a\n * select or anything editable, or a key that is part of an IME composition\n * (`isComposing`, or the legacy `keyCode` 229 some browsers send instead).\n */\n\n/** The letter pressed with Shift when a site sets none. */\nexport const DEFAULT_SHORTCUT = 'Z'\n\n/**\n * Where typing happens: the target itself or anything it sits in. Any\n * `contenteditable`, even `false`, counts: a few bytes of the boot cheaper,\n * and a missed shortcut there costs nothing.\n */\nconst TYPING = 'input,textarea,select,[contenteditable]'\n\nexport function isShortcut(event: KeyboardEvent, key: string): boolean {\n // The real target, inside an open shadow root too (a site's own components).\n const target = (event.composedPath?.()[0] ?? event.target) as Element | null\n return (\n event.shiftKey &&\n !(event.ctrlKey || event.metaKey || event.altKey || event.repeat || event.isComposing) &&\n // Some browsers report a key inside an IME composition only as keyCode 229.\n event.keyCode !== 229 &&\n event.key?.toLowerCase() === key.toLowerCase() &&\n !target?.closest?.(TYPING)\n )\n}\n","/**\n * Development origins: `localhost`, `127.0.0.1`, `[::1]` and any `*.localhost`\n * name (which browsers resolve to the machine itself and treat as secure\n * contexts), over http OR https, any port: a site in development runs on\n * either.\n *\n * Shared by the boot (`./boot.ts`, which every visitor runs, so one regex and\n * no more) and the overlay (`../preview/live.ts`), so the two never disagree\n * about what counts as local.\n */\nexport const LOCAL_ORIGIN =\n /^https?:\\/\\/(?:(?:[a-z\\d-]+\\.)*localhost|127\\.0\\.0\\.1|\\[::1\\])(?::\\d+)?$/i\n\n/** True for a development hostname (no scheme, no port). */\nexport const isLocalHost = (hostname: string): boolean => LOCAL_ORIGIN.test(`http://${hostname}`)\n","// GENERATED by packages/zap-sdk/scripts/release-cdn.mjs. Committed: the CDN\n// release of `./preview` the boot loads, compiled into the npm build and the\n// script-tag boot. The boot joins the path to the Zap origin it trusts (the\n// framing editor, the configured Zap, or production); the SRI hash pins the\n// bytes. apps/zap live-client.spec.ts checks it names a released file.\n\nexport const PREVIEW_RELEASE_PATH = '/js/preview/preview.v1.a5d4edd65a63f670.js'\n\nexport const PREVIEW_RELEASE_INTEGRITY =\n 'sha384-BndA3d22q6r2xdtC8CsXM2jiTzs8BqpU2W5xNMSWbjM6wlbdx9Le3SGYxIXh+04C'\n","import { DEFAULT_SHORTCUT, isShortcut } from '../preview/site/shortcut'\nimport { LOCAL_ORIGIN } from './local'\nimport { PREVIEW_RELEASE_INTEGRITY, PREVIEW_RELEASE_PATH } from './release'\n\n/**\n * The boot (`.docs/proposals/zap-cms-v2.md` §2.7 \"What a visitor pays\",\n * §7.5): the only Zap code an ordinary visitor runs. Under 1 KB gzip, and it\n * makes NO request unless a preview session is present:\n *\n * 1. the server says a draft-mode session is active (`preview: true`);\n * 2. the page is framed by a Zap origin: framed, and, where the browser tells\n * (`location.ancestorOrigins`, Chrome and Safari), the parent is the\n * production editor or a local Zap seen from a local page (Zap's own\n * preview deployments are not editors) (`./local.ts`: localhost, 127.0.0.1,\n * [::1] or *.localhost, over http or https); where it cannot tell\n * (Firefox), any frame counts and the overlay itself refuses every editor\n * that is not Zap;\n * and with a site id (suggestion mode, §3.4):\n * 3. the URL carries `?zap`;\n * 4. this page is the OAuth callback of a sign-in this client started\n * (`/?code&state=zap1.…`);\n * 5. this tab already holds a suggestion session or a pending sign-in\n * (`sessionStorage`);\n * 6. the shortcut is pressed: Shift Z by default, never while the person is\n * typing in a field or composing with an IME (`preview/site/shortcut.ts`).\n *\n * Only then does it add one `<script>` for `./preview`: the release path\n * compiled in (`./release.ts`) with its SRI hash, on a Zap origin. The origin\n * is the editor that framed the page when the browser names it and it is a\n * Zap origin (so a local Zap serves its own overlay), else `zapOrigin` when it\n * is a Zap origin by the same rule (a local Zap from a local page), else\n * production. Never a URL from the page or the query string, and the SRI\n * hash means only the released bytes run, whichever Zap serves them.\n *\n * One script per document: a second boot on the same page (React StrictMode\n * runs every effect twice, a remount, a script tag beside the component) finds\n * the first one by its SRI hash and adds nothing. It reads the parent's\n * origin, the URL, sessionStorage keys and key presses; it writes nothing and\n * draws nothing.\n */\n\nexport interface ZapBootOptions {\n /** The Zap site's key. */\n siteKey: string\n /** The Zap site's id: turns on suggestion mode on the live site (§3.4). */\n siteId?: string\n /** The site's draft-mode route path, announced to the editor (§2.5). */\n draftRoute?: string | null\n /** The server saw an active draft-mode session (Next: `draftMode().isEnabled`). */\n preview?: boolean\n /** The letter pressed with Shift to open «Editar» and «Comentar»; default `Z`, false for none. */\n shortcut?: string | false\n /**\n * The Zap the site works against (`EELZAP_ORIGIN`): the overlay loads from\n * it and trusts it. Honoured for a local Zap from a local page\n * (development) and for production; anything else is ignored.\n */\n zapOrigin?: string\n /** A local Nest, under the same rule. */\n authOrigin?: string\n}\n\n/**\n * The string options and the `data-*` attribute each travels in to the\n * overlay's script (and, in the script-tag boot, comes from).\n */\nexport const BOOT_ATTRIBUTES = {\n siteKey: 'site',\n siteId: 'site-id',\n draftRoute: 'zap-draft-route',\n shortcut: 'zap-shortcut',\n zapOrigin: 'zap-origin',\n authOrigin: 'auth-origin',\n} as const\n\nconst ZAP = 'https://zap.eel.software'\n\n/** A Zap origin, as seen from `page`: see the list above. */\nfunction isZap(origin: string, page: string): boolean {\n return origin === ZAP || (LOCAL_ORIGIN.test(origin) && LOCAL_ORIGIN.test(page))\n}\n\n/**\n * The parent's origin: undefined when not framed, '' when framed but the\n * browser does not say by whom (Firefox).\n */\nfunction parentOrigin(win: Window): string | undefined {\n if (win.parent === win) return undefined\n const ancestors = (win.location as Location & { ancestorOrigins?: DOMStringList }).ancestorOrigins\n return ancestors?.[0] ?? ''\n}\n\nfunction suggesting(win: Window): boolean {\n const query = new URLSearchParams(win.location.search)\n if (query.has('zap')) return true\n if (\n win.location.pathname === '/' &&\n query.has('code') &&\n /^zap1\\./.test(query.get('state') ?? '')\n )\n return true\n try {\n const store = win.sessionStorage\n return !!(store.getItem('eelzap:site-token') || store.getItem('eelzap:signin'))\n } catch {\n return false\n }\n}\n\n/** Start the boot. Returns a function that removes its one listener. */\nexport function bootZapPreview(options: ZapBootOptions, win: Window = window): () => void {\n const page = win.location.origin\n const parent = parentOrigin(win)\n // The framing editor when it is Zap, else '' (not framed, unnamed, or not Zap).\n const editor = parent && isZap(parent, page) ? parent : ''\n // Where the overlay comes from (see above): that editor, the configured\n // Zap (an exact origin, as `EELZAP_ORIGIN` holds it), or production.\n const zap = editor || (isZap(options.zapOrigin ?? '', page) ? options.zapOrigin! : ZAP)\n let loaded = false\n const load = (open?: boolean) => {\n if (loaded || !PREVIEW_RELEASE_PATH) return\n loaded = true\n stop()\n const doc = win.document\n if (doc.querySelector(`script[integrity=\"${PREVIEW_RELEASE_INTEGRITY}\"]`)) return\n const script = doc.createElement('script')\n script.src = zap + PREVIEW_RELEASE_PATH\n script.setAttribute('integrity', PREVIEW_RELEASE_INTEGRITY)\n script.setAttribute('crossorigin', 'anonymous')\n let key: keyof typeof BOOT_ATTRIBUTES\n for (key in BOOT_ATTRIBUTES) {\n const value = key === 'shortcut' && options.shortcut === false ? 'off' : options[key]\n if (typeof value === 'string') script.setAttribute(`data-${BOOT_ATTRIBUTES[key]}`, value)\n }\n // The overlay trusts the Zap it came from (a local one only from a local page).\n script.setAttribute('data-zap-origin', zap)\n if (open) script.setAttribute('data-zap-open', '')\n doc.head.appendChild(script)\n }\n\n const key = options.shortcut === undefined ? DEFAULT_SHORTCUT : options.shortcut\n const onKey = (event: KeyboardEvent) => {\n if (key && isShortcut(event, key)) load(true)\n }\n const stop = () => win.removeEventListener('keydown', onKey, true)\n\n if (!options.siteKey) return stop\n if (options.preview || parent === '' || editor) load()\n else if (options.siteId) {\n if (suggesting(win)) load()\n else if (key) win.addEventListener('keydown', onKey, true)\n }\n return stop\n}\n","import { createElement, Fragment, useEffect } from 'react'\n\nimport { bootZapPreview, type ZapBootOptions } from './boot'\n\nexport type ZapPreviewProps = ZapBootOptions\n\n/**\n * The preview boot as a React component (`@8ux-co/eelzap/react` and, with a\n * default draft route, `@8ux-co/eelzap/next`). Renders nothing; after mount it\n * runs `bootZapPreview`, which loads the overlay only in a preview session.\n *\n * ```tsx\n * <ZapPreview siteKey=\"verdeorigen\" siteId=\"…uuid…\" />\n * ```\n */\nexport function ZapPreview(props: ZapPreviewProps) {\n const { siteKey, siteId, draftRoute, preview, shortcut, zapOrigin, authOrigin } = props\n useEffect(\n () => bootZapPreview({ siteKey, siteId, draftRoute, preview, shortcut, zapOrigin, authOrigin }),\n [siteKey, siteId, draftRoute, preview, shortcut, zapOrigin, authOrigin],\n )\n return createElement(Fragment)\n}\n"],"mappings":"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","names":["key","patch"]}
1
+ {"version":3,"sources":["../src/react.ts","../src/refs.ts","../src/preview/site/shortcut.ts","../src/boot/local.ts","../src/boot/release.ts","../src/boot/boot.ts","../src/boot/component.ts"],"sourcesContent":["import * as React from 'react'\n\nimport { recordRefsOf, type EntryLike, type RecordRef } from './refs'\nimport type { PreviewValue, ValuesPayload } from './preview/protocol'\n\nexport { bootZapPreview, type ZapBootOptions } from './boot/boot'\nexport { ZapPreview, type ZapPreviewProps } from './boot/component'\nexport { recordRefsOf, zapAttrs, type EntryLike } from './refs'\n\n/**\n * `@8ux-co/eelzap/react` (zap-cms-v2 §2.5, §2.7): the preview boot for React\n * sites (`ZapPreview`, `bootZapPreview`) and the hook below. Nothing here\n * bundles the overlay: it loads from the CDN in a preview session, and\n * announces each `zap:values` patch with a window event (`eelzap:values`),\n * which `onValues` and `useZapLiveUpdates` listen to.\n *\n * `useZapLiveUpdates(entry)` returns `entry` with the editor's unsaved values\n * merged in while the page is framed by Zap, and `entry` itself (the same\n * object) otherwise, the shape of Contentful's `useContentfulLiveUpdates`:\n *\n * ```tsx\n * const post = useZapLiveUpdates(props.post)\n * return <h1 {...zapAttrs(post, 'title')}>{post.content.title}</h1>\n * ```\n *\n * The entry is a delivery API item or document (its `content` gets the\n * values) or any object keyed by field (the object itself gets them). Values\n * arrive formatted for display, as the overlay writes them: text, a URL and an\n * email as a string, rich text as its sanitised HTML, an image as\n * `{ url, alt }` merged into what the field held. The boot must run somewhere on the page (`ZapPreview`);\n * without it, or outside Zap, the hook never changes anything.\n */\n\ntype Patch = Record<string, PreviewValue>\n\n/** One display value as a plain field value, merged into what the field held. */\nexport function toFieldValue(value: PreviewValue, previous: unknown): unknown {\n if (value === null || typeof value !== 'object') return value\n if ('text' in value) return value.text\n if ('html' in value) return value.html\n if ('url' in value) return value.url\n if ('email' in value) return value.email\n const image = value.image\n const base =\n previous && typeof previous === 'object' && !Array.isArray(previous)\n ? (previous as Record<string, unknown>)\n : {}\n const next: Record<string, unknown> = { ...base }\n if (image.src !== undefined) next.url = image.src\n if (image.alt !== undefined) next.alt = image.alt\n return next\n}\n\n/** `entry` with `patch` merged into its `content` (or into itself). Pure. */\nexport function mergeLiveValues<T>(entry: T, patch: Patch): T {\n const keys = Object.keys(patch)\n if (keys.length === 0 || !entry || typeof entry !== 'object') return entry\n const record = entry as Record<string, unknown>\n const content = record.content\n const target =\n content && typeof content === 'object' && !Array.isArray(content)\n ? (content as Record<string, unknown>)\n : record\n const merged: Record<string, unknown> = { ...target }\n for (const key of keys) merged[key] = toFieldValue(patch[key] as PreviewValue, target[key])\n return (target === record ? merged : { ...record, content: merged }) as T\n}\n\n/** The window event the overlay announces every `zap:values` patch with. */\nexport const VALUES_EVENT = 'eelzap:values'\n\n/** A values event's detail, checked enough to merge it safely. */\nfunction readValues(event: Event): ValuesPayload | null {\n const detail = (event as CustomEvent<unknown>).detail as Partial<ValuesPayload> | null\n if (!detail || typeof detail.recordRef !== 'string') return null\n const patch = detail.patch\n if (!patch || typeof patch !== 'object' || Array.isArray(patch)) return null\n return detail as ValuesPayload\n}\n\n/** `onValues` for references already resolved. */\nexport function subscribeRefs(\n refs: readonly RecordRef[],\n handler: (patch: Patch, payload: ValuesPayload) => void,\n win: Window | undefined = typeof window === 'undefined' ? undefined : window,\n): () => void {\n if (!win) return () => {}\n const listener = (event: Event) => {\n const payload = readValues(event)\n if (!payload || !refs.includes(payload.recordRef)) return\n try {\n handler(payload.patch, payload)\n } catch (error) {\n // A site handler that throws must not take the others down with it.\n console.error('eelzap: onValues handler failed', error)\n }\n }\n win.addEventListener(VALUES_EVENT, listener)\n return () => win.removeEventListener(VALUES_EVENT, listener)\n}\n\n/**\n * Call `handler` with each unsaved-values patch for `entry` while the page is\n * in Zap's preview, for sites that compute things from values (excerpts,\n * lists, counts). Framework-free despite living here. Returns an unsubscribe\n * function; outside a preview it never fires.\n */\nexport function onValues(\n entry: EntryLike,\n handler: (patch: Patch, payload: ValuesPayload) => void,\n): () => void {\n return subscribeRefs(recordRefsOf(entry), handler)\n}\n\nexport function useZapLiveUpdates<T extends EntryLike>(entry: T): T {\n // Every reference counts: the editor names the record by whichever one the\n // page tagged (the default slug, or a localized one).\n const refsKey = recordRefsOf(entry).join('|')\n const [state, setState] = React.useState<{ refsKey: string; patch: Patch }>({\n refsKey,\n patch: {},\n })\n\n React.useEffect(() => {\n if (!refsKey) return\n return subscribeRefs(refsKey.split('|'), (patch) => {\n setState((previous) => ({\n refsKey,\n patch: previous.refsKey === refsKey ? { ...previous.patch, ...patch } : { ...patch },\n }))\n })\n // `refsKey` stands for the entry's identity: new data for the same entry\n // keeps the unsaved values on top of it.\n }, [refsKey])\n\n const patch = state.refsKey === refsKey ? state.patch : null\n return React.useMemo(() => (patch ? mergeLiveValues(entry, patch) : entry), [entry, patch])\n}\n","/**\n * The record and field grammar of tags (`.docs/proposals/zap-cms-v2.md` §2.4),\n * in a module of its own so the light entries (`./fields`, `./react`, the\n * boot) can name records without pulling in the preview protocol.\n * No imports, no side effects.\n */\n\n/**\n * A record reference in its canonical string form: `collectionKey/slug` for an\n * entry, `doc:documentKey` for a document (the tag grammar, §2.4).\n */\nexport type RecordRef = string\n\n/** Zap's field key pattern. */\nexport const FIELD_KEY_RE = /^[a-z][a-z0-9_]{0,127}$/\n/** Entry and document references; keys and slugs are `[a-z0-9]+(?:-[a-z0-9]+)*`. */\nconst ENTRY_REF_RE = /^([a-z0-9]+(?:-[a-z0-9]+)*)\\/([a-z0-9]+(?:-[a-z0-9]+)*)$/\nconst DOC_REF_RE = /^doc:([a-z0-9]+(?:-[a-z0-9]+)*)$/\nconst MAX_REF_LENGTH = 300\n\nexport type ParsedRecordRef =\n | { kind: 'entry'; collection: string; slug: string }\n | { kind: 'document'; key: string }\n\nexport function parseRecordRef(value: unknown): ParsedRecordRef | null {\n if (typeof value !== 'string' || value.length > MAX_REF_LENGTH) return null\n const doc = DOC_REF_RE.exec(value)\n if (doc) return { kind: 'document', key: doc[1]! }\n const entry = ENTRY_REF_RE.exec(value)\n if (entry) return { kind: 'entry', collection: entry[1]!, slug: entry[2]! }\n return null\n}\n\nexport function isRecordRef(value: unknown): value is RecordRef {\n return parseRecordRef(value) !== null\n}\n\n/** `zpt_` + 32 bytes in base64url, as Zap mints it (zap-cms-v2 §3.5). */\nexport const PREVIEW_TOKEN_RE = /^zpt_[A-Za-z0-9_-]{43}$/\n\n/** A preview token in the shape Zap mints; says nothing about whether it is live. */\nexport function isPreviewTokenShape(value: unknown): value is string {\n return typeof value === 'string' && PREVIEW_TOKEN_RE.test(value)\n}\n\nexport function isFieldKey(value: unknown): value is string {\n return typeof value === 'string' && FIELD_KEY_RE.test(value)\n}\n\n/**\n * What `zapAttrs` and `onValues` accept: a record reference string\n * (`blog/hola`, `doc:home`) or a delivery API entry (an item carries\n * `collection.key` and `slug`, a document `key`).\n */\nexport type EntryLike =\n | RecordRef\n | {\n slug?: string | null\n key?: string | null\n collection?: { key?: string | null } | null\n localizedSlugs?: Record<string, string | null | undefined> | null\n }\n\n/** Every reference an entry answers to (default slug first, then localized ones). */\nexport function recordRefsOf(entry: EntryLike | null | undefined): RecordRef[] {\n if (!entry) return []\n if (typeof entry === 'string') return isRecordRef(entry) ? [entry] : []\n const refs: RecordRef[] = []\n const add = (ref: string) => {\n if (isRecordRef(ref) && !refs.includes(ref)) refs.push(ref)\n }\n const collection = entry.collection?.key\n if (collection) {\n if (entry.slug) add(`${collection}/${entry.slug}`)\n for (const slug of Object.values(entry.localizedSlugs ?? {})) {\n if (slug) add(`${collection}/${slug}`)\n }\n } else if (entry.key) {\n add(`doc:${entry.key}`)\n }\n return refs\n}\n\n/**\n * The tag for one field, as attributes to spread on the element that renders\n * it: `{ 'data-zap': 'blog/hola#title' }`. Empty when the entry or the field\n * key is not one Zap can name, so a bad call never breaks the page.\n */\nexport function zapAttrs(entry: EntryLike, fieldKey: string): { 'data-zap'?: string } {\n const [ref] = recordRefsOf(entry)\n if (!ref || !isFieldKey(fieldKey)) return {}\n return { 'data-zap': `${ref}#${fieldKey}` }\n}\n","/**\n * The keyboard shortcut that opens «Editar» and «Comentar» on the live site\n * (zap-cms-v2 §3.4, owner 2026-10-05): Shift plus one letter, `Z` by default.\n * It replaced «Shift twice», which fired while people typed capitals.\n *\n * Shared by the boot every visitor runs (`src/boot/boot.ts`, under 1 KB) and\n * the overlay core (`./boot.ts`), so the two can never disagree about what\n * counts. No directive, no imports.\n *\n * It never fires while the person is typing: focus in an input, a textarea, a\n * select or anything editable, or a key that is part of an IME composition\n * (`isComposing`, or the legacy `keyCode` 229 some browsers send instead).\n */\n\n/** The letter pressed with Shift when a site sets none. */\nexport const DEFAULT_SHORTCUT = 'Z'\n\n/**\n * Where typing happens: the target itself or anything it sits in. A\n * `contenteditable=\"false\"` region is not typing, so the shortcut still works\n * there; a field being edited on the live site is, so Shift+Z types a capital.\n */\nconst TYPING = 'input,textarea,select,[contenteditable]:not([contenteditable=\"false\"])'\n\nexport function isShortcut(event: KeyboardEvent, key: string): boolean {\n // The real target, inside an open shadow root too (a site's own components).\n const target = (event.composedPath?.()[0] ?? event.target) as Element | null\n return (\n event.shiftKey &&\n !(event.ctrlKey || event.metaKey || event.altKey || event.repeat || event.isComposing) &&\n // Some browsers report a key inside an IME composition only as keyCode 229.\n event.keyCode !== 229 &&\n event.key?.toLowerCase() === key.toLowerCase() &&\n !target?.closest?.(TYPING)\n )\n}\n","/**\n * Development origins: `localhost`, `127.0.0.1`, `[::1]` and any `*.localhost`\n * name (which browsers resolve to the machine itself and treat as secure\n * contexts), over http OR https, any port: a site in development runs on\n * either.\n *\n * Shared by the boot (`./boot.ts`, which every visitor runs, so one regex and\n * no more) and the overlay (`../preview/live.ts`), so the two never disagree\n * about what counts as local.\n */\nexport const LOCAL_ORIGIN =\n /^https?:\\/\\/(?:(?:[a-z\\d-]+\\.)*localhost|127\\.0\\.0\\.1|\\[::1\\])(?::\\d+)?$/i\n\n/** True for a development hostname (no scheme, no port). */\nexport const isLocalHost = (hostname: string): boolean => LOCAL_ORIGIN.test(`http://${hostname}`)\n","// GENERATED by packages/zap-sdk/scripts/release-cdn.mjs. Committed: the CDN\n// release of `./preview` the boot loads, compiled into the npm build and the\n// script-tag boot. The boot joins the path to the Zap origin it trusts (the\n// framing editor, the configured Zap, or production); the SRI hash pins the\n// bytes. apps/zap live-client.spec.ts checks it names a released file.\n\nexport const PREVIEW_RELEASE_PATH = '/js/preview/preview.v1.d596b252d8afff70.js'\n\nexport const PREVIEW_RELEASE_INTEGRITY =\n 'sha384-LeIaGcZk+fQNnNJs65j+955Ez72VLAP5yuHd+J0vLqdkuJX9nQi4axLNf94zCoNX'\n","import { DEFAULT_SHORTCUT, isShortcut } from '../preview/site/shortcut'\nimport { LOCAL_ORIGIN } from './local'\nimport { PREVIEW_RELEASE_INTEGRITY, PREVIEW_RELEASE_PATH } from './release'\n\n/**\n * The boot (`.docs/proposals/zap-cms-v2.md` §2.7 \"What a visitor pays\",\n * §7.5): the only Zap code an ordinary visitor runs. Under 1 KB gzip, and it\n * makes NO request unless a preview session is present:\n *\n * 1. the server says a draft-mode session is active (`preview: true`);\n * 2. the page is framed by a Zap origin: framed, and, where the browser tells\n * (`location.ancestorOrigins`, Chrome and Safari), the parent is the\n * production editor or a local Zap seen from a local page (Zap's own\n * preview deployments are not editors) (`./local.ts`: localhost, 127.0.0.1,\n * [::1] or *.localhost, over http or https); where it cannot tell\n * (Firefox), any frame counts and the overlay itself refuses every editor\n * that is not Zap;\n * and with a site id (suggestion mode, §3.4):\n * 3. the URL carries `?zap`;\n * 4. this page is the OAuth callback of a sign-in this client started\n * (`/?code&state=zap1.…`);\n * 5. this tab already holds a suggestion session or a pending sign-in\n * (`sessionStorage`);\n * 6. the shortcut is pressed: Shift Z by default, never while the person is\n * typing in a field or composing with an IME (`preview/site/shortcut.ts`).\n *\n * Only then does it add one `<script>` for `./preview`: the release path\n * compiled in (`./release.ts`) with its SRI hash, on a Zap origin. The origin\n * is the editor that framed the page when the browser names it and it is a\n * Zap origin (so a local Zap serves its own overlay), else `zapOrigin` when it\n * is a Zap origin by the same rule (a local Zap from a local page), else\n * production. Never a URL from the page or the query string, and the SRI\n * hash means only the released bytes run, whichever Zap serves them.\n *\n * One script per document: a second boot on the same page (React StrictMode\n * runs every effect twice, a remount, a script tag beside the component) finds\n * the first one by its SRI hash and adds nothing. It reads the parent's\n * origin, the URL, sessionStorage keys and key presses; it writes nothing and\n * draws nothing.\n */\n\nexport interface ZapBootOptions {\n /** The Zap site's key. */\n siteKey: string\n /** The Zap site's id: turns on suggestion mode on the live site (§3.4). */\n siteId?: string\n /** The site's draft-mode route path, announced to the editor (§2.5). */\n draftRoute?: string | null\n /** The server saw an active draft-mode session (Next: `draftMode().isEnabled`). */\n preview?: boolean\n /** The letter pressed with Shift to open «Editar» and «Comentar»; default `Z`, false for none. */\n shortcut?: string | false\n /**\n * The Zap the site works against (`EELZAP_ORIGIN`): the overlay loads from\n * it and trusts it. Honoured for a local Zap from a local page\n * (development) and for production; anything else is ignored.\n */\n zapOrigin?: string\n /** A local Nest, under the same rule. */\n authOrigin?: string\n}\n\n/**\n * The string options and the `data-*` attribute each travels in to the\n * overlay's script (and, in the script-tag boot, comes from).\n */\nexport const BOOT_ATTRIBUTES = {\n siteKey: 'site',\n siteId: 'site-id',\n draftRoute: 'zap-draft-route',\n shortcut: 'zap-shortcut',\n zapOrigin: 'zap-origin',\n authOrigin: 'auth-origin',\n} as const\n\nconst ZAP = 'https://zap.eel.software'\n\n/** A Zap origin, as seen from `page`: see the list above. */\nfunction isZap(origin: string, page: string): boolean {\n return origin === ZAP || (LOCAL_ORIGIN.test(origin) && LOCAL_ORIGIN.test(page))\n}\n\n/**\n * The parent's origin: undefined when not framed, '' when framed but the\n * browser does not say by whom (Firefox).\n */\nfunction parentOrigin(win: Window): string | undefined {\n if (win.parent === win) return undefined\n const ancestors = (win.location as Location & { ancestorOrigins?: DOMStringList }).ancestorOrigins\n return ancestors?.[0] ?? ''\n}\n\nfunction suggesting(win: Window): boolean {\n const query = new URLSearchParams(win.location.search)\n if (query.has('zap')) return true\n if (\n win.location.pathname === '/' &&\n query.has('code') &&\n /^zap1\\./.test(query.get('state') ?? '')\n )\n return true\n try {\n const store = win.sessionStorage\n return !!(store.getItem('eelzap:site-token') || store.getItem('eelzap:signin'))\n } catch {\n return false\n }\n}\n\n/** Start the boot. Returns a function that removes its one listener. */\nexport function bootZapPreview(options: ZapBootOptions, win: Window = window): () => void {\n const page = win.location.origin\n const parent = parentOrigin(win)\n // The framing editor when it is Zap, else '' (not framed, unnamed, or not Zap).\n const editor = parent && isZap(parent, page) ? parent : ''\n // Where the overlay comes from (see above): that editor, the configured\n // Zap (an exact origin, as `EELZAP_ORIGIN` holds it), or production.\n const zap = editor || (isZap(options.zapOrigin ?? '', page) ? options.zapOrigin! : ZAP)\n let loaded = false\n const load = (open?: boolean) => {\n if (loaded || !PREVIEW_RELEASE_PATH) return\n loaded = true\n stop()\n const doc = win.document\n if (doc.querySelector(`script[integrity=\"${PREVIEW_RELEASE_INTEGRITY}\"]`)) return\n const script = doc.createElement('script')\n script.src = zap + PREVIEW_RELEASE_PATH\n script.setAttribute('integrity', PREVIEW_RELEASE_INTEGRITY)\n script.setAttribute('crossorigin', 'anonymous')\n let key: keyof typeof BOOT_ATTRIBUTES\n for (key in BOOT_ATTRIBUTES) {\n const value = key === 'shortcut' && options.shortcut === false ? 'off' : options[key]\n if (typeof value === 'string') script.setAttribute(`data-${BOOT_ATTRIBUTES[key]}`, value)\n }\n // The overlay trusts the Zap it came from (a local one only from a local page).\n script.setAttribute('data-zap-origin', zap)\n if (open) script.setAttribute('data-zap-open', '')\n doc.head.appendChild(script)\n }\n\n const key = options.shortcut === undefined ? DEFAULT_SHORTCUT : options.shortcut\n const onKey = (event: KeyboardEvent) => {\n if (key && isShortcut(event, key)) load(true)\n }\n const stop = () => win.removeEventListener('keydown', onKey, true)\n\n if (!options.siteKey) return stop\n if (options.preview || parent === '' || editor) load()\n else if (options.siteId) {\n if (suggesting(win)) load()\n else if (key) win.addEventListener('keydown', onKey, true)\n }\n return stop\n}\n","import { createElement, Fragment, useEffect } from 'react'\n\nimport { bootZapPreview, type ZapBootOptions } from './boot'\n\nexport type ZapPreviewProps = ZapBootOptions\n\n/**\n * The preview boot as a React component (`@8ux-co/eelzap/react` and, with a\n * default draft route, `@8ux-co/eelzap/next`). Renders nothing; after mount it\n * runs `bootZapPreview`, which loads the overlay only in a preview session.\n *\n * ```tsx\n * <ZapPreview siteKey=\"verdeorigen\" siteId=\"…uuid…\" />\n * ```\n */\nexport function ZapPreview(props: ZapPreviewProps) {\n const { siteKey, siteId, draftRoute, preview, shortcut, zapOrigin, authOrigin } = props\n useEffect(\n () => bootZapPreview({ siteKey, siteId, draftRoute, preview, shortcut, zapOrigin, authOrigin }),\n [siteKey, siteId, draftRoute, preview, shortcut, zapOrigin, authOrigin],\n )\n return createElement(Fragment)\n}\n"],"mappings":"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","names":["key","patch"]}