From c5e5f1f877aa343eeeead840e099c3f8c15f446e Mon Sep 17 00:00:00 2001
From: Jaggob <37583151+Jaggob@users.noreply.github.com>
Date: Sat, 26 Sep 2026 11:42:44 +0200
Subject: [PATCH 1/6] Let the embed-create page wait for its create, and tell
the host when the outcome is not known
The create is a write, but it went with the default ten-second timeout:
a slow create reported 'network' with "Request timed out." while the
server went on and made the pad and the file, and a second try met the
name it had taken. It now has no client-side time limit, like the
embed page's initialise and recovery; a slow create is not a failed
one.
'network' now means no answer from this app, which is the one case where
the outcome is not known: fetch failed, also while the body streams in,
or a proxy answered in its place, with its 502, 503 or 504 in status.
A gateway's answer counted as 'server' before, which says nothing was
created - true only of this app's own answers, since the server rolls a
failed create back. After 'network' the page and the host's message say,
in a translated sentence, that the pad may have been created anyway and
to look in the folder before trying again. architecture.md says a host
that stops waiting on its own must assume the same.
---
docs/api-reference.md | 3 +-
docs/architecture.md | 3 +-
js/etherpad_nextcloud-embed-create-main.mjs | 2 +-
...herpad_nextcloud-embed-create-main.mjs.map | 2 +-
l10n/de.js | 1 +
l10n/de.json | 1 +
l10n/es.js | 1 +
l10n/es.json | 1 +
l10n/fr.js | 1 +
l10n/fr.json | 1 +
l10n/it.js | 1 +
l10n/it.json | 1 +
lib/Controller/EmbedController.php | 1 +
src/embed-create-main.js | 26 +++++---
templates/embed-create.php | 3 +-
tests/js/embed-create-main.test.js | 65 ++++++++++++++++++-
16 files changed, 97 insertions(+), 16 deletions(-)
diff --git a/docs/api-reference.md b/docs/api-reference.md
index 97bf879a..c89fa58f 100644
--- a/docs/api-reference.md
+++ b/docs/api-reference.md
@@ -490,8 +490,9 @@ solely by the separate external-pad policy, not by these two settings.
- `epnc:host-sync-now`
- `src/embed-create-main.js`
- powers the minimal `/embed/create-by-parent/{parentFolderId}` page.
- - uses same-origin `POST /api/v1/pads/create-by-parent`.
+ - uses same-origin `POST /api/v1/pads/create-by-parent`, without a time limit, as it writes.
- redirects to returned `embed_url` after successful pad creation.
+ - tells the host `epnc:create-succeeded` or `epnc:create-failed`; after `reason: 'network'` the outcome is not known (`docs/architecture.md`, the create flow).
## URL Control in Files App
diff --git a/docs/architecture.md b/docs/architecture.md
index b730965a..394a6caa 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -169,7 +169,8 @@ Primary flow (minimal blank create launcher page):
5. `PadCreateController::createByParent` performs server-side validation of `name`, `accessMode`, and the writable target folder before creating the `.pad` file and binding.
6. Before redirecting, `src/embed-create-main.js` posts the host page one of two structured events so the surrounding UI can react without scraping the iframe DOM:
- `epnc:create-succeeded` — payload `{embed_url, file_id, pad_id, access_mode}`. Fires once on the success path, immediately before the iframe self-redirects to the embed-open URL.
- - `epnc:create-failed` — payload `{reason, status, message}`. Fires on any error. `reason` is one of `'invalid'` (client-side validation), `'conflict'` (HTTP 409 — e.g. duplicate filename), `'server'` (any other 4xx/5xx), or `'network'` (fetch itself failed).
+ - `epnc:create-failed` — payload `{reason, status, message}`. Fires on any error. `reason` is one of `'invalid'` (client-side validation), `'conflict'` (HTTP 409 — e.g. duplicate filename), `'server'` (any other 4xx/5xx this app answered; nothing was created, the server rolls a failed create back), or `'network'` (no answer from this app: fetch failed, or a proxy answered in its place, with its `502`/`503`/`504` in `status`). After `'network'` the pad may have been created anyway, and the message says to look in the folder before trying again.
+ The create has no client-side time limit, as it writes: a slow create is not a failed one, and the page reports nothing until the server or a proxy answers. A host that stops waiting on its own must assume the pad may still be created.
The inline error rendering inside the iframe is unchanged — `postMessage` is purely additive for hosts that want to act on the outcome. Target-origin is `*` because the page doesn't know the host's origin up-front; the `frame-ancestors` allowlist already constrains who can be the parent.
7. On success the launcher redirects itself to the returned `embed_url`, after which the normal embed-open flow takes over.
diff --git a/js/etherpad_nextcloud-embed-create-main.mjs b/js/etherpad_nextcloud-embed-create-main.mjs
index 8530a0f8..e2d47bf4 100644
--- a/js/etherpad_nextcloud-embed-create-main.mjs
+++ b/js/etherpad_nextcloud-embed-create-main.mjs
@@ -1,2 +1,2 @@
-import{i as U,f as A,o as P,D as R}from"./fetch-helpers-Dqr3YYFE.chunk.mjs";(function(){const r=document.getElementById("etherpad-nextcloud-embed-create");if(!(r instanceof HTMLElement))return;const d=Number(r.getAttribute("data-parent-folder-id")||""),c=String(r.getAttribute("data-create-by-parent-url")||"").trim(),b=String(r.getAttribute("data-request-token")||"").trim(),w=String(r.getAttribute("data-l10n-missing-name")||"Pad name is required."),h=String(r.getAttribute("data-l10n-invalid-access-mode")||"Invalid access mode."),y=String(r.getAttribute("data-l10n-incomplete-config")||"Embed configuration is incomplete."),l=r.querySelector("[data-epnc-embed-create-loading]"),m=r.querySelector("[data-epnc-embed-create-error]"),u=r.querySelector("[data-epnc-embed-create-error-message]"),g=()=>P(b),f=(e,n)=>{if(window.parent!==window)try{window.parent.postMessage(Object.assign({type:e},n||{}),"*")}catch{}},S=e=>{l instanceof HTMLElement&&(l.hidden=!0),u instanceof HTMLElement&&(u.textContent=String(e||"Unknown error.")),m instanceof HTMLElement&&(m.hidden=!1)},a=(e,n,o)=>{const t=String(n||"Unknown error.");S(t),f("epnc:create-failed",{reason:e,status:typeof o=="number"?o:null,message:t})},_=()=>{const e=new URL(window.location.href).searchParams;return{name:String(e.get("name")||"").trim(),accessMode:String(e.get("accessMode")||R).trim()}},v=e=>{const n=new URL(String(e||"").trim(),window.location.origin);if(n.origin!==window.location.origin)throw new Error("Invalid embed URL origin.");return n.pathname+n.search+n.hash},L=e=>e===409?"conflict":"server";(async()=>{if(!Number.isFinite(d)||d<=0||c===""){a("invalid",y);return}if(g()===""){a("invalid","CSRF request token is missing.");return}const{name:e,accessMode:n}=_();if(e===""){a("invalid",w);return}if(!U(n)){a("invalid",h);return}const o=new URLSearchParams;o.set("parentFolderId",String(d)),o.set("name",e),o.set("accessMode",n);let t;try{t=await A(c,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded;charset=UTF-8",requesttoken:g()},body:o.toString()})}catch(i){const s=i&&typeof i.status=="number"?i.status:null,E=i instanceof Error?i.message:"Pad creation failed.",M=s===null?"network":L(s);a(M,E,s);return}if(!t||typeof t.embed_url!="string"||t.embed_url.trim()===""){a("server","Pad creation API did not return a valid embed URL.");return}let p;try{p=v(t.embed_url)}catch(i){const s=i instanceof Error?i.message:"Invalid embed URL.";a("server",s);return}f("epnc:create-succeeded",{embed_url:t.embed_url,file_id:typeof t.file_id=="number"?t.file_id:null,pad_id:typeof t.pad_id=="string"?t.pad_id:"",access_mode:typeof t.access_mode=="string"?t.access_mode:""}),window.location.replace(p)})()})();
+import{i as A,f as E,r as T,o as q,D as P}from"./fetch-helpers-Dqr3YYFE.chunk.mjs";(function(){const n=document.getElementById("etherpad-nextcloud-embed-create");if(!(n instanceof HTMLElement))return;const s=Number(n.getAttribute("data-parent-folder-id")||""),c=String(n.getAttribute("data-create-by-parent-url")||"").trim(),b=String(n.getAttribute("data-request-token")||"").trim(),w=String(n.getAttribute("data-l10n-missing-name")||"Pad name is required."),y=String(n.getAttribute("data-l10n-invalid-access-mode")||"Invalid access mode."),h=String(n.getAttribute("data-l10n-incomplete-config")||"Embed configuration is incomplete."),S=String(n.getAttribute("data-l10n-unanswered")||"Nextcloud did not answer. The pad may have been created anyway; look in the folder before you try again."),l=n.querySelector("[data-epnc-embed-create-loading]"),m=n.querySelector("[data-epnc-embed-create-error]"),u=n.querySelector("[data-epnc-embed-create-error-message]"),g=()=>q(b),f=(e,r)=>{if(window.parent!==window)try{window.parent.postMessage(Object.assign({type:e},r||{}),"*")}catch{}},_=e=>{l instanceof HTMLElement&&(l.hidden=!0),u instanceof HTMLElement&&(u.textContent=String(e||"Unknown error.")),m instanceof HTMLElement&&(m.hidden=!1)},i=(e,r,o)=>{const t=String(r||"Unknown error.");_(t),f("epnc:create-failed",{reason:e,status:typeof o=="number"?o:null,message:t})},v=()=>{const e=new URL(window.location.href).searchParams;return{name:String(e.get("name")||"").trim(),accessMode:String(e.get("accessMode")||P).trim()}},L=e=>{const r=new URL(String(e||"").trim(),window.location.origin);if(r.origin!==window.location.origin)throw new Error("Invalid embed URL origin.");return r.pathname+r.search+r.hash},M=e=>e===409?"conflict":"server";(async()=>{if(!Number.isFinite(s)||s<=0||c===""){i("invalid",h);return}if(g()===""){i("invalid","CSRF request token is missing.");return}const{name:e,accessMode:r}=v();if(e===""){i("invalid",w);return}if(!A(r)){i("invalid",y);return}const o=new URLSearchParams;o.set("parentFolderId",String(s)),o.set("name",e),o.set("accessMode",r);let t;try{t=await E(c,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded;charset=UTF-8",requesttoken:g()},body:o.toString()},{timeoutMs:null})}catch(a){const d=a&&typeof a.status=="number"?a.status:null,U=d===null||a&&a.unanswered===!0?"network":M(d);i(U,T(a,S,"Pad creation failed."),d);return}if(!t||typeof t.embed_url!="string"||t.embed_url.trim()===""){i("server","Pad creation API did not return a valid embed URL.");return}let p;try{p=L(t.embed_url)}catch(a){const d=a instanceof Error?a.message:"Invalid embed URL.";i("server",d);return}f("epnc:create-succeeded",{embed_url:t.embed_url,file_id:typeof t.file_id=="number"?t.file_id:null,pad_id:typeof t.pad_id=="string"?t.pad_id:"",access_mode:typeof t.access_mode=="string"?t.access_mode:""}),window.location.replace(p)})()})();
//# sourceMappingURL=etherpad_nextcloud-embed-create-main.mjs.map
diff --git a/js/etherpad_nextcloud-embed-create-main.mjs.map b/js/etherpad_nextcloud-embed-create-main.mjs.map
index 5b8deafd..15b2dfc0 100644
--- a/js/etherpad_nextcloud-embed-create-main.mjs.map
+++ b/js/etherpad_nextcloud-embed-create-main.mjs.map
@@ -1 +1 @@
-{"version":3,"file":"etherpad_nextcloud-embed-create-main.mjs","sources":["../src/embed-create-main.js"],"sourcesContent":["/**\n * SPDX-License-Identifier: AGPL-3.0-or-later\n * Copyright (c) 2026 Jacob Bühler\n */\nimport { DEFAULT_PAD_ACCESS_MODE, isPadAccessMode } from './lib/constants.js'\nimport { ocRequestToken } from './lib/oc-compat.js'\nimport { fetchJsonWithTimeout as fetchJson } from './lib/fetch-helpers.js'\n\n(function () {\n\tconst root = document.getElementById('etherpad-nextcloud-embed-create')\n\tif (!(root instanceof HTMLElement)) {\n\t\treturn\n\t}\n\n\tconst parentFolderId = Number(root.getAttribute('data-parent-folder-id') || '')\n\tconst createByParentUrl = String(root.getAttribute('data-create-by-parent-url') || '').trim()\n\tconst templateRequestToken = String(root.getAttribute('data-request-token') || '').trim()\n\tconst missingNameMessage = String(root.getAttribute('data-l10n-missing-name') || 'Pad name is required.')\n\tconst invalidAccessModeMessage = String(root.getAttribute('data-l10n-invalid-access-mode') || 'Invalid access mode.')\n\tconst incompleteConfigMessage = String(root.getAttribute('data-l10n-incomplete-config') || 'Embed configuration is incomplete.')\n\tconst loadingNode = root.querySelector('[data-epnc-embed-create-loading]')\n\tconst errorNode = root.querySelector('[data-epnc-embed-create-error]')\n\tconst errorMessageNode = root.querySelector('[data-epnc-embed-create-error-message]')\n\n\tconst requestToken = () => ocRequestToken(templateRequestToken)\n\n\t/**\n\t * Post an `epnc:*` event to the host page that's embedding this iframe.\n\t *\n\t * Target-origin is `*` rather than a specific origin because the create\n\t * page doesn't know the host's origin up-front (the host hasn't talked to\n\t * us yet). The actual access control happens at iframe-load time via the\n\t * route's CSP `frame-ancestors` header, which only lists the admin-\n\t * configured `trusted_embed_origins`. Anyone receiving these messages is\n\t * by construction already in that allowlist.\n\t *\n\t * No-ops if we're not actually embedded (window.parent === window).\n\t */\n\tconst postHostMessage = (type, payload) => {\n\t\tif (window.parent === window) {\n\t\t\treturn\n\t\t}\n\t\ttry {\n\t\t\twindow.parent.postMessage(Object.assign({ type }, payload || {}), '*')\n\t\t} catch (e) {\n\t\t\t// Posting can throw on certain cross-origin / cross-process boundaries;\n\t\t\t// inline error rendering is the user-visible fallback, so the message\n\t\t\t// is purely advisory for the host.\n\t\t}\n\t}\n\n\tconst showError = (message) => {\n\t\tif (loadingNode instanceof HTMLElement) {\n\t\t\tloadingNode.hidden = true\n\t\t}\n\t\tif (errorMessageNode instanceof HTMLElement) {\n\t\t\terrorMessageNode.textContent = String(message || 'Unknown error.')\n\t\t}\n\t\tif (errorNode instanceof HTMLElement) {\n\t\t\terrorNode.hidden = false\n\t\t}\n\t}\n\n\t/**\n\t * Emit a structured `epnc:create-failed` event AND render the inline error.\n\t * The message is normalised once so the host's payload and the user-facing\n\t * inline message never drift (an empty/undefined `message` would otherwise\n\t * land in the iframe as \"Unknown error.\" but in the postMessage payload as\n\t * the empty string).\n\t *\n\t * `reason` is a coarse bucket so hosts can branch without parsing the\n\t * HTTP status:\n\t * - 'invalid' — client-side validation failed (missing name, etc.)\n\t * - 'conflict' — backend returned 409 (e.g. duplicate filename)\n\t * - 'server' — any other 4xx / 5xx\n\t * - 'network' — fetch itself failed (offline, CORS, timeout)\n\t */\n\tconst failCreate = (reason, message, status) => {\n\t\tconst normalizedMessage = String(message || 'Unknown error.')\n\t\tshowError(normalizedMessage)\n\t\tpostHostMessage('epnc:create-failed', {\n\t\t\treason,\n\t\t\tstatus: typeof status === 'number' ? status : null,\n\t\t\tmessage: normalizedMessage,\n\t\t})\n\t}\n\n\tconst readLauncherParams = () => {\n\t\tconst params = new URL(window.location.href).searchParams\n\t\treturn {\n\t\t\tname: String(params.get('name') || '').trim(),\n\t\t\taccessMode: String(params.get('accessMode') || DEFAULT_PAD_ACCESS_MODE).trim(),\n\t\t}\n\t}\n\n\tconst normalizeEmbedRedirectUrl = (value) => {\n\t\tconst url = new URL(String(value || '').trim(), window.location.origin)\n\t\tif (url.origin !== window.location.origin) {\n\t\t\tthrow new Error('Invalid embed URL origin.')\n\t\t}\n\t\treturn url.pathname + url.search + url.hash\n\t}\n\n\tconst classifyHttpStatus = (status) => {\n\t\tif (status === 409) {\n\t\t\treturn 'conflict'\n\t\t}\n\t\treturn 'server'\n\t}\n\n\tconst run = async () => {\n\t\tif (!Number.isFinite(parentFolderId) || parentFolderId <= 0 || createByParentUrl === '') {\n\t\t\tfailCreate('invalid', incompleteConfigMessage)\n\t\t\treturn\n\t\t}\n\t\tif (requestToken() === '') {\n\t\t\tfailCreate('invalid', 'CSRF request token is missing.')\n\t\t\treturn\n\t\t}\n\n\t\tconst { name, accessMode } = readLauncherParams()\n\t\tif (name === '') {\n\t\t\tfailCreate('invalid', missingNameMessage)\n\t\t\treturn\n\t\t}\n\t\tif (!isPadAccessMode(accessMode)) {\n\t\t\tfailCreate('invalid', invalidAccessModeMessage)\n\t\t\treturn\n\t\t}\n\n\t\tconst body = new URLSearchParams()\n\t\tbody.set('parentFolderId', String(parentFolderId))\n\t\tbody.set('name', name)\n\t\tbody.set('accessMode', accessMode)\n\n\t\t// Step 1: server-side create. Failures here are either network\n\t\t// (fetch threw — no HTTP status reached us) or server (we got a\n\t\t// status code back, including the 409 on duplicate filename).\n\t\tlet data\n\t\ttry {\n\t\t\tdata = await fetchJson(createByParentUrl, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',\n\t\t\t\t\trequesttoken: requestToken(),\n\t\t\t\t},\n\t\t\t\tbody: body.toString(),\n\t\t\t})\n\t\t} catch (error) {\n\t\t\tconst status = (error && typeof error.status === 'number') ? error.status : null\n\t\t\tconst message = error instanceof Error ? error.message : 'Pad creation failed.'\n\t\t\tconst reason = status === null ? 'network' : classifyHttpStatus(status)\n\t\t\tfailCreate(reason, message, status)\n\t\t\treturn\n\t\t}\n\n\t\t// Step 2: validate the server's response shape *and* the redirect\n\t\t// target before emitting success. A malformed or cross-origin\n\t\t// embed_url is a server-side bug, not a network failure — and we\n\t\t// must not announce success only to then announce failure to the\n\t\t// same host listener, which would leave them with contradictory\n\t\t// signals.\n\t\tif (!data || typeof data.embed_url !== 'string' || data.embed_url.trim() === '') {\n\t\t\tfailCreate('server', 'Pad creation API did not return a valid embed URL.')\n\t\t\treturn\n\t\t}\n\t\tlet redirectTarget\n\t\ttry {\n\t\t\tredirectTarget = normalizeEmbedRedirectUrl(data.embed_url)\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : 'Invalid embed URL.'\n\t\t\tfailCreate('server', message)\n\t\t\treturn\n\t\t}\n\n\t\t// Step 3: announce success once everything is definitively OK, then\n\t\t// navigate. Notify *before* the redirect: once we replace the iframe\n\t\t// location the host loses its handle on this script.\n\t\tpostHostMessage('epnc:create-succeeded', {\n\t\t\tembed_url: data.embed_url,\n\t\t\tfile_id: typeof data.file_id === 'number' ? data.file_id : null,\n\t\t\tpad_id: typeof data.pad_id === 'string' ? data.pad_id : '',\n\t\t\taccess_mode: typeof data.access_mode === 'string' ? data.access_mode : '',\n\t\t})\n\t\twindow.location.replace(redirectTarget)\n\t}\n\n\tvoid run()\n})()\n"],"names":["root","parentFolderId","createByParentUrl","templateRequestToken","missingNameMessage","invalidAccessModeMessage","incompleteConfigMessage","loadingNode","errorNode","errorMessageNode","requestToken","ocRequestToken","postHostMessage","type","payload","showError","message","failCreate","reason","status","normalizedMessage","readLauncherParams","params","DEFAULT_PAD_ACCESS_MODE","normalizeEmbedRedirectUrl","value","url","classifyHttpStatus","name","accessMode","isPadAccessMode","body","data","fetchJson","error","redirectTarget"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"etherpad_nextcloud-embed-create-main.mjs","sources":["../src/embed-create-main.js"],"sourcesContent":["/**\n * SPDX-License-Identifier: AGPL-3.0-or-later\n * Copyright (c) 2026 Jacob Bühler\n */\nimport { DEFAULT_PAD_ACCESS_MODE, isPadAccessMode } from './lib/constants.js'\nimport { ocRequestToken } from './lib/oc-compat.js'\nimport { fetchJsonWithTimeout as fetchJson, requestErrorMessage } from './lib/fetch-helpers.js'\n\n(function () {\n\tconst root = document.getElementById('etherpad-nextcloud-embed-create')\n\tif (!(root instanceof HTMLElement)) {\n\t\treturn\n\t}\n\n\tconst parentFolderId = Number(root.getAttribute('data-parent-folder-id') || '')\n\tconst createByParentUrl = String(root.getAttribute('data-create-by-parent-url') || '').trim()\n\tconst templateRequestToken = String(root.getAttribute('data-request-token') || '').trim()\n\tconst missingNameMessage = String(root.getAttribute('data-l10n-missing-name') || 'Pad name is required.')\n\tconst invalidAccessModeMessage = String(root.getAttribute('data-l10n-invalid-access-mode') || 'Invalid access mode.')\n\tconst incompleteConfigMessage = String(root.getAttribute('data-l10n-incomplete-config') || 'Embed configuration is incomplete.')\n\tconst unansweredMessage = String(root.getAttribute('data-l10n-unanswered') || 'Nextcloud did not answer. The pad may have been created anyway; look in the folder before you try again.')\n\tconst loadingNode = root.querySelector('[data-epnc-embed-create-loading]')\n\tconst errorNode = root.querySelector('[data-epnc-embed-create-error]')\n\tconst errorMessageNode = root.querySelector('[data-epnc-embed-create-error-message]')\n\n\tconst requestToken = () => ocRequestToken(templateRequestToken)\n\n\t/**\n\t * Post an `epnc:*` event to the host page that's embedding this iframe.\n\t *\n\t * Target-origin is `*` rather than a specific origin because the create\n\t * page doesn't know the host's origin up-front (the host hasn't talked to\n\t * us yet). The actual access control happens at iframe-load time via the\n\t * route's CSP `frame-ancestors` header, which only lists the admin-\n\t * configured `trusted_embed_origins`. Anyone receiving these messages is\n\t * by construction already in that allowlist.\n\t *\n\t * No-ops if we're not actually embedded (window.parent === window).\n\t */\n\tconst postHostMessage = (type, payload) => {\n\t\tif (window.parent === window) {\n\t\t\treturn\n\t\t}\n\t\ttry {\n\t\t\twindow.parent.postMessage(Object.assign({ type }, payload || {}), '*')\n\t\t} catch (e) {\n\t\t\t// Posting can throw on certain cross-origin / cross-process boundaries;\n\t\t\t// inline error rendering is the user-visible fallback, so the message\n\t\t\t// is purely advisory for the host.\n\t\t}\n\t}\n\n\tconst showError = (message) => {\n\t\tif (loadingNode instanceof HTMLElement) {\n\t\t\tloadingNode.hidden = true\n\t\t}\n\t\tif (errorMessageNode instanceof HTMLElement) {\n\t\t\terrorMessageNode.textContent = String(message || 'Unknown error.')\n\t\t}\n\t\tif (errorNode instanceof HTMLElement) {\n\t\t\terrorNode.hidden = false\n\t\t}\n\t}\n\n\t/**\n\t * Emit a structured `epnc:create-failed` event AND render the inline error.\n\t * The message is normalised once so the host's payload and the user-facing\n\t * inline message never drift (an empty/undefined `message` would otherwise\n\t * land in the iframe as \"Unknown error.\" but in the postMessage payload as\n\t * the empty string).\n\t *\n\t * `reason` is a coarse bucket so hosts can branch without parsing the\n\t * HTTP status:\n\t * - 'invalid' — client-side validation failed (missing name, etc.)\n\t * - 'conflict' — backend returned 409 (e.g. duplicate filename)\n\t * - 'server' — any other 4xx / 5xx this app answered; nothing was\n\t * created, the server rolls a failed create back\n\t * - 'network' — no answer from this app: fetch failed, or a proxy\n\t * answered in its place (`status` is then its 502, 503 or 504).\n\t * The pad may have been created anyway.\n\t */\n\tconst failCreate = (reason, message, status) => {\n\t\tconst normalizedMessage = String(message || 'Unknown error.')\n\t\tshowError(normalizedMessage)\n\t\tpostHostMessage('epnc:create-failed', {\n\t\t\treason,\n\t\t\tstatus: typeof status === 'number' ? status : null,\n\t\t\tmessage: normalizedMessage,\n\t\t})\n\t}\n\n\tconst readLauncherParams = () => {\n\t\tconst params = new URL(window.location.href).searchParams\n\t\treturn {\n\t\t\tname: String(params.get('name') || '').trim(),\n\t\t\taccessMode: String(params.get('accessMode') || DEFAULT_PAD_ACCESS_MODE).trim(),\n\t\t}\n\t}\n\n\tconst normalizeEmbedRedirectUrl = (value) => {\n\t\tconst url = new URL(String(value || '').trim(), window.location.origin)\n\t\tif (url.origin !== window.location.origin) {\n\t\t\tthrow new Error('Invalid embed URL origin.')\n\t\t}\n\t\treturn url.pathname + url.search + url.hash\n\t}\n\n\tconst classifyHttpStatus = (status) => {\n\t\tif (status === 409) {\n\t\t\treturn 'conflict'\n\t\t}\n\t\treturn 'server'\n\t}\n\n\tconst run = async () => {\n\t\tif (!Number.isFinite(parentFolderId) || parentFolderId <= 0 || createByParentUrl === '') {\n\t\t\tfailCreate('invalid', incompleteConfigMessage)\n\t\t\treturn\n\t\t}\n\t\tif (requestToken() === '') {\n\t\t\tfailCreate('invalid', 'CSRF request token is missing.')\n\t\t\treturn\n\t\t}\n\n\t\tconst { name, accessMode } = readLauncherParams()\n\t\tif (name === '') {\n\t\t\tfailCreate('invalid', missingNameMessage)\n\t\t\treturn\n\t\t}\n\t\tif (!isPadAccessMode(accessMode)) {\n\t\t\tfailCreate('invalid', invalidAccessModeMessage)\n\t\t\treturn\n\t\t}\n\n\t\tconst body = new URLSearchParams()\n\t\tbody.set('parentFolderId', String(parentFolderId))\n\t\tbody.set('name', name)\n\t\tbody.set('accessMode', accessMode)\n\n\t\t// Step 1: server-side create. A write, so it has no time limit (see\n\t\t// fetchJsonWithTimeout()): cut short, it would go on creating with\n\t\t// nobody told, and a second try would meet the file it made. A slow\n\t\t// create is not a failed one.\n\t\tlet data\n\t\ttry {\n\t\t\tdata = await fetchJson(createByParentUrl, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',\n\t\t\t\t\trequesttoken: requestToken(),\n\t\t\t\t},\n\t\t\t\tbody: body.toString(),\n\t\t\t}, { timeoutMs: null })\n\t\t} catch (error) {\n\t\t\tconst status = (error && typeof error.status === 'number') ? error.status : null\n\t\t\t// This app answered, including the 409 on a duplicate name, or\n\t\t\t// nothing came back from it and the outcome is not known.\n\t\t\tconst reason = status === null || (error && error.unanswered === true) ? 'network' : classifyHttpStatus(status)\n\t\t\tfailCreate(reason, requestErrorMessage(error, unansweredMessage, 'Pad creation failed.'), status)\n\t\t\treturn\n\t\t}\n\n\t\t// Step 2: validate the server's response shape *and* the redirect\n\t\t// target before emitting success. A malformed or cross-origin\n\t\t// embed_url is a server-side bug, not a network failure — and we\n\t\t// must not announce success only to then announce failure to the\n\t\t// same host listener, which would leave them with contradictory\n\t\t// signals.\n\t\tif (!data || typeof data.embed_url !== 'string' || data.embed_url.trim() === '') {\n\t\t\tfailCreate('server', 'Pad creation API did not return a valid embed URL.')\n\t\t\treturn\n\t\t}\n\t\tlet redirectTarget\n\t\ttry {\n\t\t\tredirectTarget = normalizeEmbedRedirectUrl(data.embed_url)\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : 'Invalid embed URL.'\n\t\t\tfailCreate('server', message)\n\t\t\treturn\n\t\t}\n\n\t\t// Step 3: announce success once everything is definitively OK, then\n\t\t// navigate. Notify *before* the redirect: once we replace the iframe\n\t\t// location the host loses its handle on this script.\n\t\tpostHostMessage('epnc:create-succeeded', {\n\t\t\tembed_url: data.embed_url,\n\t\t\tfile_id: typeof data.file_id === 'number' ? data.file_id : null,\n\t\t\tpad_id: typeof data.pad_id === 'string' ? data.pad_id : '',\n\t\t\taccess_mode: typeof data.access_mode === 'string' ? data.access_mode : '',\n\t\t})\n\t\twindow.location.replace(redirectTarget)\n\t}\n\n\tvoid run()\n})()\n"],"names":["root","parentFolderId","createByParentUrl","templateRequestToken","missingNameMessage","invalidAccessModeMessage","incompleteConfigMessage","unansweredMessage","loadingNode","errorNode","errorMessageNode","requestToken","ocRequestToken","postHostMessage","type","payload","showError","message","failCreate","reason","status","normalizedMessage","readLauncherParams","params","DEFAULT_PAD_ACCESS_MODE","normalizeEmbedRedirectUrl","value","url","classifyHttpStatus","name","accessMode","isPadAccessMode","body","data","fetchJson","error","requestErrorMessage","redirectTarget"],"mappings":"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"}
\ No newline at end of file
diff --git a/l10n/de.js b/l10n/de.js
index 1ff4feb1..22bafa7b 100644
--- a/l10n/de.js
+++ b/l10n/de.js
@@ -118,6 +118,7 @@ OC.L10N.register(
"New pad": "Neues Pad",
"Nextcloud and Etherpad share no parent domain, so the browser cannot send the session cookie to Etherpad and protected pads will not open. If a proxy exposes Etherpad under the Nextcloud domain, use that address as the base URL. Otherwise, place both services under a shared parent domain or switch protected pads off.": "Nextcloud und Etherpad haben keine gemeinsame übergeordnete Domain. Der Browser kann das Sitzungscookie deshalb nicht an Etherpad senden, und geschützte Pads lassen sich nicht öffnen. Wenn ein Proxy Etherpad unter der Nextcloud-Domain bereitstellt, trage diese Adresse als Basis-URL ein. Andernfalls betreibe beide Dienste unter einer gemeinsamen übergeordneten Domain oder schalte geschützte Pads ab.",
"Nextcloud did not answer. Check your connection and try again.": "Nextcloud hat nicht geantwortet. Prüfe deine Verbindung und versuche es erneut.",
+ "Nextcloud did not answer. The pad may have been created anyway; look in the folder before you try again.": "Nextcloud hat nicht geantwortet. Das Pad wurde vielleicht trotzdem angelegt; sieh im Ordner nach, bevor du es erneut versuchst.",
"Nextcloud runs on {nextcloud_host}, Etherpad on {etherpad_host}.": "Nextcloud läuft auf {nextcloud_host}, Etherpad auf {etherpad_host}.",
"No .pad file selected. Open a .pad file from this shared folder.": "Keine .pad-Datei ausgewählt. Öffne eine .pad-Datei aus diesem freigegebenen Ordner.",
"No shared templates yet.": "Noch keine geteilten Vorlagen.",
diff --git a/l10n/de.json b/l10n/de.json
index f52dfe49..c26f0209 100644
--- a/l10n/de.json
+++ b/l10n/de.json
@@ -117,6 +117,7 @@
"New pad": "Neues Pad",
"Nextcloud and Etherpad share no parent domain, so the browser cannot send the session cookie to Etherpad and protected pads will not open. If a proxy exposes Etherpad under the Nextcloud domain, use that address as the base URL. Otherwise, place both services under a shared parent domain or switch protected pads off.": "Nextcloud und Etherpad haben keine gemeinsame übergeordnete Domain. Der Browser kann das Sitzungscookie deshalb nicht an Etherpad senden, und geschützte Pads lassen sich nicht öffnen. Wenn ein Proxy Etherpad unter der Nextcloud-Domain bereitstellt, trage diese Adresse als Basis-URL ein. Andernfalls betreibe beide Dienste unter einer gemeinsamen übergeordneten Domain oder schalte geschützte Pads ab.",
"Nextcloud did not answer. Check your connection and try again.": "Nextcloud hat nicht geantwortet. Prüfe deine Verbindung und versuche es erneut.",
+ "Nextcloud did not answer. The pad may have been created anyway; look in the folder before you try again.": "Nextcloud hat nicht geantwortet. Das Pad wurde vielleicht trotzdem angelegt; sieh im Ordner nach, bevor du es erneut versuchst.",
"Nextcloud runs on {nextcloud_host}, Etherpad on {etherpad_host}.": "Nextcloud läuft auf {nextcloud_host}, Etherpad auf {etherpad_host}.",
"No .pad file selected. Open a .pad file from this shared folder.": "Keine .pad-Datei ausgewählt. Öffne eine .pad-Datei aus diesem freigegebenen Ordner.",
"No shared templates yet.": "Noch keine geteilten Vorlagen.",
diff --git a/l10n/es.js b/l10n/es.js
index 2759bf81..fcc10b03 100644
--- a/l10n/es.js
+++ b/l10n/es.js
@@ -118,6 +118,7 @@ OC.L10N.register(
"New pad": "Nuevo pad",
"Nextcloud and Etherpad share no parent domain, so the browser cannot send the session cookie to Etherpad and protected pads will not open. If a proxy exposes Etherpad under the Nextcloud domain, use that address as the base URL. Otherwise, place both services under a shared parent domain or switch protected pads off.": "Nextcloud y Etherpad no comparten ningún dominio padre común, así que el navegador no puede enviar la cookie de sesión a Etherpad y los pads protegidos no se abrirán. Si un proxy expone Etherpad bajo el dominio de Nextcloud, usa esa dirección como URL base. En caso contrario, sitúa ambos servicios bajo un dominio padre común o desactiva los pads protegidos.",
"Nextcloud did not answer. Check your connection and try again.": "Nextcloud no ha respondido. Comprueba tu conexión y vuelve a intentarlo.",
+ "Nextcloud did not answer. The pad may have been created anyway; look in the folder before you try again.": "Nextcloud no ha respondido. Puede que el pad se haya creado de todos modos; mira en la carpeta antes de volver a intentarlo.",
"Nextcloud runs on {nextcloud_host}, Etherpad on {etherpad_host}.": "Nextcloud se ejecuta en {nextcloud_host} y Etherpad en {etherpad_host}.",
"No .pad file selected. Open a .pad file from this shared folder.": "No se ha seleccionado ningún archivo .pad. Abre un archivo .pad de esta carpeta compartida.",
"No shared templates yet.": "Todavía no hay plantillas compartidas.",
diff --git a/l10n/es.json b/l10n/es.json
index d65f8604..8342858b 100644
--- a/l10n/es.json
+++ b/l10n/es.json
@@ -117,6 +117,7 @@
"New pad": "Nuevo pad",
"Nextcloud and Etherpad share no parent domain, so the browser cannot send the session cookie to Etherpad and protected pads will not open. If a proxy exposes Etherpad under the Nextcloud domain, use that address as the base URL. Otherwise, place both services under a shared parent domain or switch protected pads off.": "Nextcloud y Etherpad no comparten ningún dominio padre común, así que el navegador no puede enviar la cookie de sesión a Etherpad y los pads protegidos no se abrirán. Si un proxy expone Etherpad bajo el dominio de Nextcloud, usa esa dirección como URL base. En caso contrario, sitúa ambos servicios bajo un dominio padre común o desactiva los pads protegidos.",
"Nextcloud did not answer. Check your connection and try again.": "Nextcloud no ha respondido. Comprueba tu conexión y vuelve a intentarlo.",
+ "Nextcloud did not answer. The pad may have been created anyway; look in the folder before you try again.": "Nextcloud no ha respondido. Puede que el pad se haya creado de todos modos; mira en la carpeta antes de volver a intentarlo.",
"Nextcloud runs on {nextcloud_host}, Etherpad on {etherpad_host}.": "Nextcloud se ejecuta en {nextcloud_host} y Etherpad en {etherpad_host}.",
"No .pad file selected. Open a .pad file from this shared folder.": "No se ha seleccionado ningún archivo .pad. Abre un archivo .pad de esta carpeta compartida.",
"No shared templates yet.": "Todavía no hay plantillas compartidas.",
diff --git a/l10n/fr.js b/l10n/fr.js
index f7ff47fe..d8a77695 100644
--- a/l10n/fr.js
+++ b/l10n/fr.js
@@ -118,6 +118,7 @@ OC.L10N.register(
"New pad": "Nouveau pad",
"Nextcloud and Etherpad share no parent domain, so the browser cannot send the session cookie to Etherpad and protected pads will not open. If a proxy exposes Etherpad under the Nextcloud domain, use that address as the base URL. Otherwise, place both services under a shared parent domain or switch protected pads off.": "Nextcloud et Etherpad ne partagent aucun domaine parent : le navigateur ne peut donc pas envoyer le cookie de session à Etherpad, et les pads protégés ne s'ouvriront pas. Si un proxy expose Etherpad sous le domaine Nextcloud, utilisez cette adresse comme URL de base. Sinon, placez les deux services sous un domaine parent commun ou désactivez les pads protégés.",
"Nextcloud did not answer. Check your connection and try again.": "Nextcloud n'a pas répondu. Vérifiez votre connexion et réessayez.",
+ "Nextcloud did not answer. The pad may have been created anyway; look in the folder before you try again.": "Nextcloud n'a pas répondu. Le pad a peut-être été créé malgré tout ; vérifiez le dossier avant de réessayer.",
"Nextcloud runs on {nextcloud_host}, Etherpad on {etherpad_host}.": "Nextcloud tourne sur {nextcloud_host}, Etherpad sur {etherpad_host}.",
"No .pad file selected. Open a .pad file from this shared folder.": "Aucun fichier .pad sélectionné. Ouvrez un fichier .pad de ce dossier partagé.",
"No shared templates yet.": "Aucun modèle partagé pour le moment.",
diff --git a/l10n/fr.json b/l10n/fr.json
index 65c1ba0c..f3ba7fd0 100644
--- a/l10n/fr.json
+++ b/l10n/fr.json
@@ -117,6 +117,7 @@
"New pad": "Nouveau pad",
"Nextcloud and Etherpad share no parent domain, so the browser cannot send the session cookie to Etherpad and protected pads will not open. If a proxy exposes Etherpad under the Nextcloud domain, use that address as the base URL. Otherwise, place both services under a shared parent domain or switch protected pads off.": "Nextcloud et Etherpad ne partagent aucun domaine parent : le navigateur ne peut donc pas envoyer le cookie de session à Etherpad, et les pads protégés ne s'ouvriront pas. Si un proxy expose Etherpad sous le domaine Nextcloud, utilisez cette adresse comme URL de base. Sinon, placez les deux services sous un domaine parent commun ou désactivez les pads protégés.",
"Nextcloud did not answer. Check your connection and try again.": "Nextcloud n'a pas répondu. Vérifiez votre connexion et réessayez.",
+ "Nextcloud did not answer. The pad may have been created anyway; look in the folder before you try again.": "Nextcloud n'a pas répondu. Le pad a peut-être été créé malgré tout ; vérifiez le dossier avant de réessayer.",
"Nextcloud runs on {nextcloud_host}, Etherpad on {etherpad_host}.": "Nextcloud tourne sur {nextcloud_host}, Etherpad sur {etherpad_host}.",
"No .pad file selected. Open a .pad file from this shared folder.": "Aucun fichier .pad sélectionné. Ouvrez un fichier .pad de ce dossier partagé.",
"No shared templates yet.": "Aucun modèle partagé pour le moment.",
diff --git a/l10n/it.js b/l10n/it.js
index d49f76a9..2809f647 100644
--- a/l10n/it.js
+++ b/l10n/it.js
@@ -118,6 +118,7 @@ OC.L10N.register(
"New pad": "Nuovo pad",
"Nextcloud and Etherpad share no parent domain, so the browser cannot send the session cookie to Etherpad and protected pads will not open. If a proxy exposes Etherpad under the Nextcloud domain, use that address as the base URL. Otherwise, place both services under a shared parent domain or switch protected pads off.": "Nextcloud ed Etherpad non condividono alcun parent domain, quindi il browser non può inviare il cookie di sessione a Etherpad e i pad protetti non si apriranno. Se un proxy espone Etherpad sotto il dominio di Nextcloud, usa quell'indirizzo come URL base. Altrimenti colloca entrambi i servizi sotto un parent domain comune, oppure disattiva i pad protetti.",
"Nextcloud did not answer. Check your connection and try again.": "Nextcloud non ha risposto. Controlla la connessione e riprova.",
+ "Nextcloud did not answer. The pad may have been created anyway; look in the folder before you try again.": "Nextcloud non ha risposto. Il pad potrebbe essere stato creato comunque; controlla la cartella prima di riprovare.",
"Nextcloud runs on {nextcloud_host}, Etherpad on {etherpad_host}.": "Nextcloud è in esecuzione su {nextcloud_host}, Etherpad su {etherpad_host}.",
"No .pad file selected. Open a .pad file from this shared folder.": "Nessun file .pad selezionato. Apri un file .pad da questa cartella condivisa.",
"No shared templates yet.": "Non ci sono ancora modelli condivisi.",
diff --git a/l10n/it.json b/l10n/it.json
index f8218851..60902eb3 100644
--- a/l10n/it.json
+++ b/l10n/it.json
@@ -117,6 +117,7 @@
"New pad": "Nuovo pad",
"Nextcloud and Etherpad share no parent domain, so the browser cannot send the session cookie to Etherpad and protected pads will not open. If a proxy exposes Etherpad under the Nextcloud domain, use that address as the base URL. Otherwise, place both services under a shared parent domain or switch protected pads off.": "Nextcloud ed Etherpad non condividono alcun parent domain, quindi il browser non può inviare il cookie di sessione a Etherpad e i pad protetti non si apriranno. Se un proxy espone Etherpad sotto il dominio di Nextcloud, usa quell'indirizzo come URL base. Altrimenti colloca entrambi i servizi sotto un parent domain comune, oppure disattiva i pad protetti.",
"Nextcloud did not answer. Check your connection and try again.": "Nextcloud non ha risposto. Controlla la connessione e riprova.",
+ "Nextcloud did not answer. The pad may have been created anyway; look in the folder before you try again.": "Nextcloud non ha risposto. Il pad potrebbe essere stato creato comunque; controlla la cartella prima di riprovare.",
"Nextcloud runs on {nextcloud_host}, Etherpad on {etherpad_host}.": "Nextcloud è in esecuzione su {nextcloud_host}, Etherpad su {etherpad_host}.",
"No .pad file selected. Open a .pad file from this shared folder.": "Nessun file .pad selezionato. Apri un file .pad da questa cartella condivisa.",
"No shared templates yet.": "Non ci sono ancora modelli condivisi.",
diff --git a/lib/Controller/EmbedController.php b/lib/Controller/EmbedController.php
index 0ae5c05f..94ebcf27 100644
--- a/lib/Controller/EmbedController.php
+++ b/lib/Controller/EmbedController.php
@@ -114,6 +114,7 @@ function () use ($parentFolderId): array {
'missing_name' => $this->l10n->t('Pad name is required.'),
'invalid_access_mode' => $this->l10n->t('Invalid access mode.'),
'incomplete_config' => $this->l10n->t('Embed configuration is incomplete.'),
+ 'unanswered' => $this->l10n->t('Nextcloud did not answer. The pad may have been created anyway; look in the folder before you try again.'),
],
]),
errorTitle: $this->l10n->t('Could not create pad'),
diff --git a/src/embed-create-main.js b/src/embed-create-main.js
index 9316d79c..70f0e85a 100644
--- a/src/embed-create-main.js
+++ b/src/embed-create-main.js
@@ -4,7 +4,7 @@
*/
import { DEFAULT_PAD_ACCESS_MODE, isPadAccessMode } from './lib/constants.js'
import { ocRequestToken } from './lib/oc-compat.js'
-import { fetchJsonWithTimeout as fetchJson } from './lib/fetch-helpers.js'
+import { fetchJsonWithTimeout as fetchJson, requestErrorMessage } from './lib/fetch-helpers.js'
(function () {
const root = document.getElementById('etherpad-nextcloud-embed-create')
@@ -18,6 +18,7 @@ import { fetchJsonWithTimeout as fetchJson } from './lib/fetch-helpers.js'
const missingNameMessage = String(root.getAttribute('data-l10n-missing-name') || 'Pad name is required.')
const invalidAccessModeMessage = String(root.getAttribute('data-l10n-invalid-access-mode') || 'Invalid access mode.')
const incompleteConfigMessage = String(root.getAttribute('data-l10n-incomplete-config') || 'Embed configuration is incomplete.')
+ const unansweredMessage = String(root.getAttribute('data-l10n-unanswered') || 'Nextcloud did not answer. The pad may have been created anyway; look in the folder before you try again.')
const loadingNode = root.querySelector('[data-epnc-embed-create-loading]')
const errorNode = root.querySelector('[data-epnc-embed-create-error]')
const errorMessageNode = root.querySelector('[data-epnc-embed-create-error-message]')
@@ -72,8 +73,11 @@ import { fetchJsonWithTimeout as fetchJson } from './lib/fetch-helpers.js'
* HTTP status:
* - 'invalid' — client-side validation failed (missing name, etc.)
* - 'conflict' — backend returned 409 (e.g. duplicate filename)
- * - 'server' — any other 4xx / 5xx
- * - 'network' — fetch itself failed (offline, CORS, timeout)
+ * - 'server' — any other 4xx / 5xx this app answered; nothing was
+ * created, the server rolls a failed create back
+ * - 'network' — no answer from this app: fetch failed, or a proxy
+ * answered in its place (`status` is then its 502, 503 or 504).
+ * The pad may have been created anyway.
*/
const failCreate = (reason, message, status) => {
const normalizedMessage = String(message || 'Unknown error.')
@@ -133,9 +137,10 @@ import { fetchJsonWithTimeout as fetchJson } from './lib/fetch-helpers.js'
body.set('name', name)
body.set('accessMode', accessMode)
- // Step 1: server-side create. Failures here are either network
- // (fetch threw — no HTTP status reached us) or server (we got a
- // status code back, including the 409 on duplicate filename).
+ // Step 1: server-side create. A write, so it has no time limit (see
+ // fetchJsonWithTimeout()): cut short, it would go on creating with
+ // nobody told, and a second try would meet the file it made. A slow
+ // create is not a failed one.
let data
try {
data = await fetchJson(createByParentUrl, {
@@ -145,12 +150,13 @@ import { fetchJsonWithTimeout as fetchJson } from './lib/fetch-helpers.js'
requesttoken: requestToken(),
},
body: body.toString(),
- })
+ }, { timeoutMs: null })
} catch (error) {
const status = (error && typeof error.status === 'number') ? error.status : null
- const message = error instanceof Error ? error.message : 'Pad creation failed.'
- const reason = status === null ? 'network' : classifyHttpStatus(status)
- failCreate(reason, message, status)
+ // This app answered, including the 409 on a duplicate name, or
+ // nothing came back from it and the outcome is not known.
+ const reason = status === null || (error && error.unanswered === true) ? 'network' : classifyHttpStatus(status)
+ failCreate(reason, requestErrorMessage(error, unansweredMessage, 'Pad creation failed.'), status)
return
}
diff --git a/templates/embed-create.php b/templates/embed-create.php
index a38bfd58..67079cb8 100644
--- a/templates/embed-create.php
+++ b/templates/embed-create.php
@@ -17,7 +17,8 @@ class="epnc-embed"
data-l10n-error-title=""
data-l10n-missing-name=""
data-l10n-invalid-access-mode=""
- data-l10n-incomplete-config="">
+ data-l10n-incomplete-config=""
+ data-l10n-unanswered="">
@@ -33,6 +34,13 @@ const jsonResponse = (body, ok = true, status = 200) => ({
const errorResponse = (body, status = 400) => jsonResponse(body, false, status)
+/** A proxy's or a maintenance page in place of this app's answer. */
+const pageResponse = (status) => ({
+ ok: false,
+ status,
+ json: () => Promise.reject(new SyntaxError('Unexpected token < in JSON at position 0')),
+})
+
const errorMessageText = () => document.querySelector('[data-epnc-embed-create-error-message]').textContent
const errorPanelHidden = () => document.querySelector('[data-epnc-embed-create-error]').hidden
@@ -197,6 +205,61 @@ describe('embed-create-main', () => {
expect(payload.message).toBe('Network unreachable')
})
+ // No answer, so no knowing whether the pad was made: the page says so.
+ it('says the pad may exist when no answer came', async () => {
+ fetch.mockRejectedValueOnce(new TypeError('Failed to fetch'))
+
+ await importEmbedCreate()
+ await flushAsyncWork()
+
+ const payload = parentPostSpy.mock.calls[0][0]
+ expect(payload).toEqual({ type: 'epnc:create-failed', reason: 'network', status: null, message: 'No answer; look in the folder first.' })
+ expect(errorMessageText()).toBe('No answer; look in the folder first.')
+ })
+
+ /**
+ * A proxy answering in this app's place is no answer from it either,
+ * whatever it sends; its status goes along. This app's own 503 is an
+ * answer: nothing was created.
+ */
+ it.each([
+ ['a proxy whose backend is gone', pageResponse(502), 'network', 502],
+ ['a proxy that gave up waiting', pageResponse(504), 'network', 504],
+ ['a gateway answering JSON of its own', errorResponse({ message: 'An invalid response was received from the upstream server' }, 503), 'network', 503],
+ ['this app, the folder locked', errorResponse({ message: 'Pad file is temporarily locked. Please retry.', retryable: true }, 503), 'server', 503],
+ ])('tells the host whether %s answered', async (_, response, reason, status) => {
+ fetch.mockResolvedValueOnce(response)
+
+ await importEmbedCreate()
+ await flushAsyncWork()
+
+ expect(parentPostSpy.mock.calls[0][0]).toMatchObject({ type: 'epnc:create-failed', reason, status })
+ })
+
+ // A write: cut short, it would go on creating with nobody told.
+ it('waits for a slow create instead of calling it failed', async () => {
+ vi.useFakeTimers()
+ try {
+ let settle
+ fetch.mockImplementationOnce((url, init) => new Promise((resolve, reject) => {
+ init.signal.addEventListener('abort', () => reject(new DOMException('aborted', 'AbortError')))
+ settle = () => resolve(jsonResponse({ embed_url: '/embed/by-id/777', file_id: 777, pad_id: 'g.abc$mypad', access_mode: 'protected' }))
+ }))
+
+ await importEmbedCreate()
+ await vi.advanceTimersByTimeAsync(60_000)
+ expect(parentPostSpy).not.toHaveBeenCalled()
+ settle()
+ await flushAsyncWork()
+
+ expect(parentPostSpy).toHaveBeenCalledOnce()
+ expect(parentPostSpy.mock.calls[0][0].type).toBe('epnc:create-succeeded')
+ expect(locationReplaceSpy).toHaveBeenCalledWith('/embed/by-id/777')
+ } finally {
+ vi.useRealTimers()
+ }
+ })
+
it('posts epnc:create-failed with reason=invalid when launcher params are missing', async () => {
await importEmbedCreate('?accessMode=protected') // no name param
From 42c8eb6ee5279dd60202940590d7b80497b55aee Mon Sep 17 00:00:00 2001
From: Jaggob <37583151+Jaggob@users.noreply.github.com>
Date: Sat, 26 Sep 2026 11:57:14 +0200
Subject: [PATCH 2/6] Tell a 5xx that is not this app's answer by its body, and
keep what 'server' can promise
A 5xx counted as no answer from this app only as a 502, 503 or 504
without retryable. This app answers every error in JSON, so a 5xx whose
body is not JSON came from somewhere else too: a proxy's 524 after a
slow origin, or PHP dying midway, after the create may have written the
file. It is unanswered now, in every client. A 4xx page still counts as
an answer: a proxy refusing a body too large never reached the create.
When the body breaks off after the status line, the status goes along
on the error.
The embed-create page takes a 4xx as refused even when its body broke
off, and anything else without this app's answer as 'network', always
with the sentence to look in the folder. 'server' no longer promises
that nothing was created, since the rollback is best effort and a
success the page cannot use counts there too. The create's own
fallback sentence reaches the page, the template no longer carries a
third copy of the sentence, and a controller test holds that both
embed pages get theirs. isUnanswered() is the one check for the flag.
---
docs/api-reference.md | 2 +-
docs/architecture.md | 4 +-
js/etherpad_nextcloud-embed-create-main.mjs | 2 +-
...herpad_nextcloud-embed-create-main.mjs.map | 2 +-
js/etherpad_nextcloud-embed-main.mjs | 2 +-
js/etherpad_nextcloud-embed-main.mjs.map | 2 +-
js/etherpad_nextcloud-viewer-init.mjs | 2 +-
js/etherpad_nextcloud-viewer-init.mjs.map | 2 +-
js/fetch-helpers-BUxbvlK6.chunk.mjs | 2 +
... fetch-helpers-BUxbvlK6.chunk.mjs.license} | 0
js/fetch-helpers-BUxbvlK6.chunk.mjs.map | 1 +
js/fetch-helpers-Dqr3YYFE.chunk.mjs | 2 -
js/fetch-helpers-Dqr3YYFE.chunk.mjs.map | 1 -
...k.mjs => pad-open-flow-By_FzSKk.chunk.mjs} | 8 ++--
... pad-open-flow-By_FzSKk.chunk.mjs.license} | 0
...p => pad-open-flow-By_FzSKk.chunk.mjs.map} | 2 +-
src/embed-create-main.js | 30 ++++++++-----
src/embed-main.js | 4 +-
src/lib/fetch-helpers.js | 39 +++++++++++++----
src/lib/pad-open-flow.js | 3 +-
src/viewer-main.js | 4 +-
templates/embed-create.php | 2 +-
tests/js/embed-create-main.test.js | 29 ++++++++-----
tests/js/lib/fetch-helpers.test.js | 42 +++++++++++++++----
tests/phpunit/unit/EmbedControllerTest.php | 4 ++
25 files changed, 129 insertions(+), 62 deletions(-)
create mode 100644 js/fetch-helpers-BUxbvlK6.chunk.mjs
rename js/{fetch-helpers-Dqr3YYFE.chunk.mjs.license => fetch-helpers-BUxbvlK6.chunk.mjs.license} (100%)
create mode 100644 js/fetch-helpers-BUxbvlK6.chunk.mjs.map
delete mode 100644 js/fetch-helpers-Dqr3YYFE.chunk.mjs
delete mode 100644 js/fetch-helpers-Dqr3YYFE.chunk.mjs.map
rename js/{pad-open-flow-D6KDQoVq.chunk.mjs => pad-open-flow-By_FzSKk.chunk.mjs} (74%)
rename js/{pad-open-flow-D6KDQoVq.chunk.mjs.license => pad-open-flow-By_FzSKk.chunk.mjs.license} (100%)
rename js/{pad-open-flow-D6KDQoVq.chunk.mjs.map => pad-open-flow-By_FzSKk.chunk.mjs.map} (74%)
diff --git a/docs/api-reference.md b/docs/api-reference.md
index c89fa58f..8b874042 100644
--- a/docs/api-reference.md
+++ b/docs/api-reference.md
@@ -448,7 +448,7 @@ solely by the separate external-pad policy, not by these two settings.
The answers of a public share (`/api/v1/public/...`) carry the codes that can come up there - `waiting_binding` and `pad_too_large` - but not `missing_binding` or `missing_frontmatter`: what a client does on those needs a signed-in user. Their messages are translated, one sentence for each kind of trouble.
- A response without a `code` may still be machine-readable through its HTTP status and other documented fields — a locked `.pad` answers `503` with `retryable: true`, signed in and public alike, for instance, and so does a request this instance's Etherpad could not be reached for. Every `503` of this app carries `retryable: true`, and it never answers `502` or `504`: the clients take any of the three without it for a proxy's or a maintenance page's. A file's row that another request made first (`BindingNotCreatedException`, two initialisations at once, say) answers `400` with `retryable: true` too: the next open finds that request's pad. The create endpoints answer it with their own sentence and neither field. One Etherpad answered and refused answers `400` without it: trying again gives the same answer. What is never a stable identifier is the `message` text.
+ A response without a `code` may still be machine-readable through its HTTP status and other documented fields — a locked `.pad` answers `503` with `retryable: true`, signed in and public alike, for instance, and so does a request this instance's Etherpad could not be reached for. Every `503` of this app carries `retryable: true`, it never answers `502` or `504`, and every error it answers is JSON: the clients take any of the three without `retryable`, and any 5xx that is not JSON, for a proxy's, a maintenance page's or PHP's own. A file's row that another request made first (`BindingNotCreatedException`, two initialisations at once, say) answers `400` with `retryable: true` too: the next open finds that request's pad. The create endpoints answer it with their own sentence and neither field. One Etherpad answered and refused answers `400` without it: trying again gives the same answer. What is never a stable identifier is the `message` text.
## Cookie Behavior (Protected Pads)
diff --git a/docs/architecture.md b/docs/architecture.md
index 394a6caa..21776993 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -169,7 +169,7 @@ Primary flow (minimal blank create launcher page):
5. `PadCreateController::createByParent` performs server-side validation of `name`, `accessMode`, and the writable target folder before creating the `.pad` file and binding.
6. Before redirecting, `src/embed-create-main.js` posts the host page one of two structured events so the surrounding UI can react without scraping the iframe DOM:
- `epnc:create-succeeded` — payload `{embed_url, file_id, pad_id, access_mode}`. Fires once on the success path, immediately before the iframe self-redirects to the embed-open URL.
- - `epnc:create-failed` — payload `{reason, status, message}`. Fires on any error. `reason` is one of `'invalid'` (client-side validation), `'conflict'` (HTTP 409 — e.g. duplicate filename), `'server'` (any other 4xx/5xx this app answered; nothing was created, the server rolls a failed create back), or `'network'` (no answer from this app: fetch failed, or a proxy answered in its place, with its `502`/`503`/`504` in `status`). After `'network'` the pad may have been created anyway, and the message says to look in the folder before trying again.
+ - `epnc:create-failed` — payload `{reason, status, message}`. Fires on any error. `reason` is one of `'invalid'` (client-side validation), `'conflict'` (HTTP 409 — e.g. duplicate filename), `'server'` (this app refused or failed the create with any other 4xx/5xx, rolling a failed create back as far as it can, or answered in a way the page cannot use), or `'network'` (no answer from this app: fetch failed, the answer broke off, or a proxy or PHP itself answered in its place, with what came in `status`). After `'network'` the pad may have been created anyway, and the message says to look in the folder before trying again. A 4xx always counts as refused: nothing was created.
The create has no client-side time limit, as it writes: a slow create is not a failed one, and the page reports nothing until the server or a proxy answers. A host that stops waiting on its own must assume the pad may still be created.
The inline error rendering inside the iframe is unchanged — `postMessage` is purely additive for hosts that want to act on the outcome. Target-origin is `*` because the page doesn't know the host's origin up-front; the `frame-ancestors` allowlist already constrains who can be the parent.
7. On success the launcher redirects itself to the returned `embed_url`, after which the normal embed-open flow takes over.
@@ -306,7 +306,7 @@ Primary flow (native viewer):
- `PadControllerErrorMapper` (signed in) and `PublicViewerControllerErrorMapper` (public shares) answer an error with a translated sentence of their own; `code` and `retryable` come from `ApiErrorCode`, and `docs/api-reference.md` lists the codes. An exception's message is for the log; only a refusal translated where it is thrown, and the reason a pad on another server cannot be linked or read, reach the reader as they are.
- The viewer and the embed page offer "Try again" where the same open may work later (`isRetryableOpenError()` in `src/lib/pad-open-flow.js`); the button runs the whole open again. That is every answer with `retryable` (`docs/api-reference.md` lists the cases); `pad_file_changed`, which an open meets only while initialising the file, after the server undid its part; and any step of the open that got no answer, the initialise too. The second try opens first and finds a pad the first try set up, and one still being set up is safe to meet: the server compares the file before it writes, a file has one binding row, and a pad that lost either race is rolled back.
-- `fetchJsonWithTimeout()` marks a request that got no answer from this app as `unanswered`: its own timeout, a failed network, also while the body streams in, and a `502`, `503` or `504` without `retryable`, since this app never answers 502 or 504 and every 503 of its own carries it; a proxy or Nextcloud in maintenance answers in its place. It says only that, since whether another try is safe depends on the request. A recovery that got no answer is not offered again: the clients open the file instead, which shows the pad if it went through and the recovery card if not. Both clients say "no answer" in a translated sentence of their own rather than the browser's English.
+- `fetchJsonWithTimeout()` marks a request that got no answer from this app as `unanswered`: its own timeout, a failed network, also while the body streams in (the status, if one came, goes along), and a 5xx that is not this app's answer. This app answers every error in JSON, never answers 502 or 504, and every 503 of its own carries `retryable`, so a 5xx whose body is not JSON, or a 502, 503 or 504 without `retryable`, came from a proxy, from Nextcloud in maintenance, or from PHP dying midway. It says only that, since whether another try is safe depends on the request. A recovery that got no answer is not offered again: the clients open the file instead, which shows the pad if it went through and the recovery card if not. Both clients say "no answer" in a translated sentence of their own rather than the browser's English.
- After a click whose button goes away or is disabled with the focus on it, a second try or a recovery, the viewer and the embed page hand the focus to the card's first action, or to its message (`src/lib/hand-focus.js`). Not on the first load. The embed page does it without scrolling, since it sits in another page. The message of each error card is `role="alert"`, so an error is read out when it appears, the first one too.
- This instance's Etherpad not reachable answers `503` with `retryable`; a refusal from it (`EtherpadRefusedException`: a pad or group it does not have, a key it does not take) `400`. A pad on another server (`ExternalPadException`) is neither. Which is which the exceptions say (`EtherpadClientException::isEtherpadUnreachable()`); the answer and the log both go by it.
- Each error answered is logged once, by `ApiErrorLog`; the services under the mappers leave it to it, save the few lines that explain a refusal nothing else would (a name another create has locked, a file a create will not write over, a refused legacy migration):
diff --git a/js/etherpad_nextcloud-embed-create-main.mjs b/js/etherpad_nextcloud-embed-create-main.mjs
index e2d47bf4..41b92616 100644
--- a/js/etherpad_nextcloud-embed-create-main.mjs
+++ b/js/etherpad_nextcloud-embed-create-main.mjs
@@ -1,2 +1,2 @@
-import{i as A,f as E,r as T,o as q,D as P}from"./fetch-helpers-Dqr3YYFE.chunk.mjs";(function(){const n=document.getElementById("etherpad-nextcloud-embed-create");if(!(n instanceof HTMLElement))return;const s=Number(n.getAttribute("data-parent-folder-id")||""),c=String(n.getAttribute("data-create-by-parent-url")||"").trim(),b=String(n.getAttribute("data-request-token")||"").trim(),w=String(n.getAttribute("data-l10n-missing-name")||"Pad name is required."),y=String(n.getAttribute("data-l10n-invalid-access-mode")||"Invalid access mode."),h=String(n.getAttribute("data-l10n-incomplete-config")||"Embed configuration is incomplete."),S=String(n.getAttribute("data-l10n-unanswered")||"Nextcloud did not answer. The pad may have been created anyway; look in the folder before you try again."),l=n.querySelector("[data-epnc-embed-create-loading]"),m=n.querySelector("[data-epnc-embed-create-error]"),u=n.querySelector("[data-epnc-embed-create-error-message]"),g=()=>q(b),f=(e,r)=>{if(window.parent!==window)try{window.parent.postMessage(Object.assign({type:e},r||{}),"*")}catch{}},_=e=>{l instanceof HTMLElement&&(l.hidden=!0),u instanceof HTMLElement&&(u.textContent=String(e||"Unknown error.")),m instanceof HTMLElement&&(m.hidden=!1)},i=(e,r,o)=>{const t=String(r||"Unknown error.");_(t),f("epnc:create-failed",{reason:e,status:typeof o=="number"?o:null,message:t})},v=()=>{const e=new URL(window.location.href).searchParams;return{name:String(e.get("name")||"").trim(),accessMode:String(e.get("accessMode")||P).trim()}},L=e=>{const r=new URL(String(e||"").trim(),window.location.origin);if(r.origin!==window.location.origin)throw new Error("Invalid embed URL origin.");return r.pathname+r.search+r.hash},M=e=>e===409?"conflict":"server";(async()=>{if(!Number.isFinite(s)||s<=0||c===""){i("invalid",h);return}if(g()===""){i("invalid","CSRF request token is missing.");return}const{name:e,accessMode:r}=v();if(e===""){i("invalid",w);return}if(!A(r)){i("invalid",y);return}const o=new URLSearchParams;o.set("parentFolderId",String(s)),o.set("name",e),o.set("accessMode",r);let t;try{t=await E(c,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded;charset=UTF-8",requesttoken:g()},body:o.toString()},{timeoutMs:null})}catch(a){const d=a&&typeof a.status=="number"?a.status:null,U=d===null||a&&a.unanswered===!0?"network":M(d);i(U,T(a,S,"Pad creation failed."),d);return}if(!t||typeof t.embed_url!="string"||t.embed_url.trim()===""){i("server","Pad creation API did not return a valid embed URL.");return}let p;try{p=L(t.embed_url)}catch(a){const d=a instanceof Error?a.message:"Invalid embed URL.";i("server",d);return}f("epnc:create-succeeded",{embed_url:t.embed_url,file_id:typeof t.file_id=="number"?t.file_id:null,pad_id:typeof t.pad_id=="string"?t.pad_id:"",access_mode:typeof t.access_mode=="string"?t.access_mode:""}),window.location.replace(p)})()})();
+import{i as k,f as A,a as p,o as P,D as T}from"./fetch-helpers-BUxbvlK6.chunk.mjs";(function(){const n=document.getElementById("etherpad-nextcloud-embed-create");if(!(n instanceof HTMLElement))return;const d=Number(n.getAttribute("data-parent-folder-id")||""),c=String(n.getAttribute("data-create-by-parent-url")||"").trim(),w=String(n.getAttribute("data-request-token")||"").trim(),y=String(n.getAttribute("data-l10n-missing-name")||"Pad name is required."),h=String(n.getAttribute("data-l10n-invalid-access-mode")||"Invalid access mode."),S=String(n.getAttribute("data-l10n-incomplete-config")||"Embed configuration is incomplete."),_=String(n.getAttribute("data-l10n-unanswered")||"Nextcloud did not answer. The pad may have been created anyway; look in the folder before you try again."),l=n.querySelector("[data-epnc-embed-create-loading]"),m=n.querySelector("[data-epnc-embed-create-error]"),u=n.querySelector("[data-epnc-embed-create-error-message]"),g=()=>P(w),f=(e,r)=>{if(window.parent!==window)try{window.parent.postMessage(Object.assign({type:e},r||{}),"*")}catch{}},v=e=>{l instanceof HTMLElement&&(l.hidden=!0),u instanceof HTMLElement&&(u.textContent=String(e||"Unknown error.")),m instanceof HTMLElement&&(m.hidden=!1)},o=(e,r,s)=>{const t=String(r||"Unknown error.");v(t),f("epnc:create-failed",{reason:e,status:typeof s=="number"?s:null,message:t})},M=()=>{const e=new URL(window.location.href).searchParams;return{name:String(e.get("name")||"").trim(),accessMode:String(e.get("accessMode")||T).trim()}},L=e=>{const r=new URL(String(e||"").trim(),window.location.origin);if(r.origin!==window.location.origin)throw new Error("Invalid embed URL origin.");return r.pathname+r.search+r.hash},E=e=>e===409?"conflict":"server";(async()=>{if(!Number.isFinite(d)||d<=0||c===""){o("invalid",S);return}if(g()===""){o("invalid","CSRF request token is missing.");return}const{name:e,accessMode:r}=M();if(e===""){o("invalid",y);return}if(!k(r)){o("invalid",h);return}const s=new URLSearchParams;s.set("parentFolderId",String(d)),s.set("name",e),s.set("accessMode",r);let t;try{t=await A(c,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded;charset=UTF-8",requesttoken:g()},body:s.toString()},{timeoutMs:null,fallbackMessage:"Pad creation failed."})}catch(a){const i=a&&typeof a.status=="number"?a.status:null;if(!(i!==null&&i>=400&&i<500)&&(i===null||p(a))){o("network",_,i);return}const U=!p(a)&&a instanceof Error&&a.message?a.message:"Pad creation failed.";o(E(i),U,i);return}if(!t||typeof t.embed_url!="string"||t.embed_url.trim()===""){o("server","Pad creation API did not return a valid embed URL.");return}let b;try{b=L(t.embed_url)}catch(a){const i=a instanceof Error?a.message:"Invalid embed URL.";o("server",i);return}f("epnc:create-succeeded",{embed_url:t.embed_url,file_id:typeof t.file_id=="number"?t.file_id:null,pad_id:typeof t.pad_id=="string"?t.pad_id:"",access_mode:typeof t.access_mode=="string"?t.access_mode:""}),window.location.replace(b)})()})();
//# sourceMappingURL=etherpad_nextcloud-embed-create-main.mjs.map
diff --git a/js/etherpad_nextcloud-embed-create-main.mjs.map b/js/etherpad_nextcloud-embed-create-main.mjs.map
index 15b2dfc0..af4d4ba7 100644
--- a/js/etherpad_nextcloud-embed-create-main.mjs.map
+++ b/js/etherpad_nextcloud-embed-create-main.mjs.map
@@ -1 +1 @@
-{"version":3,"file":"etherpad_nextcloud-embed-create-main.mjs","sources":["../src/embed-create-main.js"],"sourcesContent":["/**\n * SPDX-License-Identifier: AGPL-3.0-or-later\n * Copyright (c) 2026 Jacob Bühler\n */\nimport { DEFAULT_PAD_ACCESS_MODE, isPadAccessMode } from './lib/constants.js'\nimport { ocRequestToken } from './lib/oc-compat.js'\nimport { fetchJsonWithTimeout as fetchJson, requestErrorMessage } from './lib/fetch-helpers.js'\n\n(function () {\n\tconst root = document.getElementById('etherpad-nextcloud-embed-create')\n\tif (!(root instanceof HTMLElement)) {\n\t\treturn\n\t}\n\n\tconst parentFolderId = Number(root.getAttribute('data-parent-folder-id') || '')\n\tconst createByParentUrl = String(root.getAttribute('data-create-by-parent-url') || '').trim()\n\tconst templateRequestToken = String(root.getAttribute('data-request-token') || '').trim()\n\tconst missingNameMessage = String(root.getAttribute('data-l10n-missing-name') || 'Pad name is required.')\n\tconst invalidAccessModeMessage = String(root.getAttribute('data-l10n-invalid-access-mode') || 'Invalid access mode.')\n\tconst incompleteConfigMessage = String(root.getAttribute('data-l10n-incomplete-config') || 'Embed configuration is incomplete.')\n\tconst unansweredMessage = String(root.getAttribute('data-l10n-unanswered') || 'Nextcloud did not answer. The pad may have been created anyway; look in the folder before you try again.')\n\tconst loadingNode = root.querySelector('[data-epnc-embed-create-loading]')\n\tconst errorNode = root.querySelector('[data-epnc-embed-create-error]')\n\tconst errorMessageNode = root.querySelector('[data-epnc-embed-create-error-message]')\n\n\tconst requestToken = () => ocRequestToken(templateRequestToken)\n\n\t/**\n\t * Post an `epnc:*` event to the host page that's embedding this iframe.\n\t *\n\t * Target-origin is `*` rather than a specific origin because the create\n\t * page doesn't know the host's origin up-front (the host hasn't talked to\n\t * us yet). The actual access control happens at iframe-load time via the\n\t * route's CSP `frame-ancestors` header, which only lists the admin-\n\t * configured `trusted_embed_origins`. Anyone receiving these messages is\n\t * by construction already in that allowlist.\n\t *\n\t * No-ops if we're not actually embedded (window.parent === window).\n\t */\n\tconst postHostMessage = (type, payload) => {\n\t\tif (window.parent === window) {\n\t\t\treturn\n\t\t}\n\t\ttry {\n\t\t\twindow.parent.postMessage(Object.assign({ type }, payload || {}), '*')\n\t\t} catch (e) {\n\t\t\t// Posting can throw on certain cross-origin / cross-process boundaries;\n\t\t\t// inline error rendering is the user-visible fallback, so the message\n\t\t\t// is purely advisory for the host.\n\t\t}\n\t}\n\n\tconst showError = (message) => {\n\t\tif (loadingNode instanceof HTMLElement) {\n\t\t\tloadingNode.hidden = true\n\t\t}\n\t\tif (errorMessageNode instanceof HTMLElement) {\n\t\t\terrorMessageNode.textContent = String(message || 'Unknown error.')\n\t\t}\n\t\tif (errorNode instanceof HTMLElement) {\n\t\t\terrorNode.hidden = false\n\t\t}\n\t}\n\n\t/**\n\t * Emit a structured `epnc:create-failed` event AND render the inline error.\n\t * The message is normalised once so the host's payload and the user-facing\n\t * inline message never drift (an empty/undefined `message` would otherwise\n\t * land in the iframe as \"Unknown error.\" but in the postMessage payload as\n\t * the empty string).\n\t *\n\t * `reason` is a coarse bucket so hosts can branch without parsing the\n\t * HTTP status:\n\t * - 'invalid' — client-side validation failed (missing name, etc.)\n\t * - 'conflict' — backend returned 409 (e.g. duplicate filename)\n\t * - 'server' — any other 4xx / 5xx this app answered; nothing was\n\t * created, the server rolls a failed create back\n\t * - 'network' — no answer from this app: fetch failed, or a proxy\n\t * answered in its place (`status` is then its 502, 503 or 504).\n\t * The pad may have been created anyway.\n\t */\n\tconst failCreate = (reason, message, status) => {\n\t\tconst normalizedMessage = String(message || 'Unknown error.')\n\t\tshowError(normalizedMessage)\n\t\tpostHostMessage('epnc:create-failed', {\n\t\t\treason,\n\t\t\tstatus: typeof status === 'number' ? status : null,\n\t\t\tmessage: normalizedMessage,\n\t\t})\n\t}\n\n\tconst readLauncherParams = () => {\n\t\tconst params = new URL(window.location.href).searchParams\n\t\treturn {\n\t\t\tname: String(params.get('name') || '').trim(),\n\t\t\taccessMode: String(params.get('accessMode') || DEFAULT_PAD_ACCESS_MODE).trim(),\n\t\t}\n\t}\n\n\tconst normalizeEmbedRedirectUrl = (value) => {\n\t\tconst url = new URL(String(value || '').trim(), window.location.origin)\n\t\tif (url.origin !== window.location.origin) {\n\t\t\tthrow new Error('Invalid embed URL origin.')\n\t\t}\n\t\treturn url.pathname + url.search + url.hash\n\t}\n\n\tconst classifyHttpStatus = (status) => {\n\t\tif (status === 409) {\n\t\t\treturn 'conflict'\n\t\t}\n\t\treturn 'server'\n\t}\n\n\tconst run = async () => {\n\t\tif (!Number.isFinite(parentFolderId) || parentFolderId <= 0 || createByParentUrl === '') {\n\t\t\tfailCreate('invalid', incompleteConfigMessage)\n\t\t\treturn\n\t\t}\n\t\tif (requestToken() === '') {\n\t\t\tfailCreate('invalid', 'CSRF request token is missing.')\n\t\t\treturn\n\t\t}\n\n\t\tconst { name, accessMode } = readLauncherParams()\n\t\tif (name === '') {\n\t\t\tfailCreate('invalid', missingNameMessage)\n\t\t\treturn\n\t\t}\n\t\tif (!isPadAccessMode(accessMode)) {\n\t\t\tfailCreate('invalid', invalidAccessModeMessage)\n\t\t\treturn\n\t\t}\n\n\t\tconst body = new URLSearchParams()\n\t\tbody.set('parentFolderId', String(parentFolderId))\n\t\tbody.set('name', name)\n\t\tbody.set('accessMode', accessMode)\n\n\t\t// Step 1: server-side create. A write, so it has no time limit (see\n\t\t// fetchJsonWithTimeout()): cut short, it would go on creating with\n\t\t// nobody told, and a second try would meet the file it made. A slow\n\t\t// create is not a failed one.\n\t\tlet data\n\t\ttry {\n\t\t\tdata = await fetchJson(createByParentUrl, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',\n\t\t\t\t\trequesttoken: requestToken(),\n\t\t\t\t},\n\t\t\t\tbody: body.toString(),\n\t\t\t}, { timeoutMs: null })\n\t\t} catch (error) {\n\t\t\tconst status = (error && typeof error.status === 'number') ? error.status : null\n\t\t\t// This app answered, including the 409 on a duplicate name, or\n\t\t\t// nothing came back from it and the outcome is not known.\n\t\t\tconst reason = status === null || (error && error.unanswered === true) ? 'network' : classifyHttpStatus(status)\n\t\t\tfailCreate(reason, requestErrorMessage(error, unansweredMessage, 'Pad creation failed.'), status)\n\t\t\treturn\n\t\t}\n\n\t\t// Step 2: validate the server's response shape *and* the redirect\n\t\t// target before emitting success. A malformed or cross-origin\n\t\t// embed_url is a server-side bug, not a network failure — and we\n\t\t// must not announce success only to then announce failure to the\n\t\t// same host listener, which would leave them with contradictory\n\t\t// signals.\n\t\tif (!data || typeof data.embed_url !== 'string' || data.embed_url.trim() === '') {\n\t\t\tfailCreate('server', 'Pad creation API did not return a valid embed URL.')\n\t\t\treturn\n\t\t}\n\t\tlet redirectTarget\n\t\ttry {\n\t\t\tredirectTarget = normalizeEmbedRedirectUrl(data.embed_url)\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : 'Invalid embed URL.'\n\t\t\tfailCreate('server', message)\n\t\t\treturn\n\t\t}\n\n\t\t// Step 3: announce success once everything is definitively OK, then\n\t\t// navigate. Notify *before* the redirect: once we replace the iframe\n\t\t// location the host loses its handle on this script.\n\t\tpostHostMessage('epnc:create-succeeded', {\n\t\t\tembed_url: data.embed_url,\n\t\t\tfile_id: typeof data.file_id === 'number' ? data.file_id : null,\n\t\t\tpad_id: typeof data.pad_id === 'string' ? data.pad_id : '',\n\t\t\taccess_mode: typeof data.access_mode === 'string' ? data.access_mode : '',\n\t\t})\n\t\twindow.location.replace(redirectTarget)\n\t}\n\n\tvoid run()\n})()\n"],"names":["root","parentFolderId","createByParentUrl","templateRequestToken","missingNameMessage","invalidAccessModeMessage","incompleteConfigMessage","unansweredMessage","loadingNode","errorNode","errorMessageNode","requestToken","ocRequestToken","postHostMessage","type","payload","showError","message","failCreate","reason","status","normalizedMessage","readLauncherParams","params","DEFAULT_PAD_ACCESS_MODE","normalizeEmbedRedirectUrl","value","url","classifyHttpStatus","name","accessMode","isPadAccessMode","body","data","fetchJson","error","requestErrorMessage","redirectTarget"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"etherpad_nextcloud-embed-create-main.mjs","sources":["../src/embed-create-main.js"],"sourcesContent":["/**\n * SPDX-License-Identifier: AGPL-3.0-or-later\n * Copyright (c) 2026 Jacob Bühler\n */\nimport { DEFAULT_PAD_ACCESS_MODE, isPadAccessMode } from './lib/constants.js'\nimport { ocRequestToken } from './lib/oc-compat.js'\nimport { fetchJsonWithTimeout as fetchJson, isUnanswered } from './lib/fetch-helpers.js'\n\n(function () {\n\tconst root = document.getElementById('etherpad-nextcloud-embed-create')\n\tif (!(root instanceof HTMLElement)) {\n\t\treturn\n\t}\n\n\tconst parentFolderId = Number(root.getAttribute('data-parent-folder-id') || '')\n\tconst createByParentUrl = String(root.getAttribute('data-create-by-parent-url') || '').trim()\n\tconst templateRequestToken = String(root.getAttribute('data-request-token') || '').trim()\n\tconst missingNameMessage = String(root.getAttribute('data-l10n-missing-name') || 'Pad name is required.')\n\tconst invalidAccessModeMessage = String(root.getAttribute('data-l10n-invalid-access-mode') || 'Invalid access mode.')\n\tconst incompleteConfigMessage = String(root.getAttribute('data-l10n-incomplete-config') || 'Embed configuration is incomplete.')\n\tconst unansweredMessage = String(root.getAttribute('data-l10n-unanswered') || 'Nextcloud did not answer. The pad may have been created anyway; look in the folder before you try again.')\n\tconst loadingNode = root.querySelector('[data-epnc-embed-create-loading]')\n\tconst errorNode = root.querySelector('[data-epnc-embed-create-error]')\n\tconst errorMessageNode = root.querySelector('[data-epnc-embed-create-error-message]')\n\n\tconst requestToken = () => ocRequestToken(templateRequestToken)\n\n\t/**\n\t * Post an `epnc:*` event to the host page that's embedding this iframe.\n\t *\n\t * Target-origin is `*` rather than a specific origin because the create\n\t * page doesn't know the host's origin up-front (the host hasn't talked to\n\t * us yet). The actual access control happens at iframe-load time via the\n\t * route's CSP `frame-ancestors` header, which only lists the admin-\n\t * configured `trusted_embed_origins`. Anyone receiving these messages is\n\t * by construction already in that allowlist.\n\t *\n\t * No-ops if we're not actually embedded (window.parent === window).\n\t */\n\tconst postHostMessage = (type, payload) => {\n\t\tif (window.parent === window) {\n\t\t\treturn\n\t\t}\n\t\ttry {\n\t\t\twindow.parent.postMessage(Object.assign({ type }, payload || {}), '*')\n\t\t} catch (e) {\n\t\t\t// Posting can throw on certain cross-origin / cross-process boundaries;\n\t\t\t// inline error rendering is the user-visible fallback, so the message\n\t\t\t// is purely advisory for the host.\n\t\t}\n\t}\n\n\tconst showError = (message) => {\n\t\tif (loadingNode instanceof HTMLElement) {\n\t\t\tloadingNode.hidden = true\n\t\t}\n\t\tif (errorMessageNode instanceof HTMLElement) {\n\t\t\terrorMessageNode.textContent = String(message || 'Unknown error.')\n\t\t}\n\t\tif (errorNode instanceof HTMLElement) {\n\t\t\terrorNode.hidden = false\n\t\t}\n\t}\n\n\t/**\n\t * Emit a structured `epnc:create-failed` event AND render the inline error.\n\t * The message is normalised once so the host's payload and the user-facing\n\t * inline message never drift (an empty/undefined `message` would otherwise\n\t * land in the iframe as \"Unknown error.\" but in the postMessage payload as\n\t * the empty string).\n\t *\n\t * `reason` is a coarse bucket so hosts can branch without parsing the\n\t * HTTP status:\n\t * - 'invalid' — client-side validation failed (missing name, etc.)\n\t * - 'conflict' — backend returned 409 (e.g. duplicate filename)\n\t * - 'server' — this app refused or failed the create with any other\n\t * 4xx / 5xx (it rolls a failed create back as far as it can), or\n\t * answered in a way this page cannot use\n\t * - 'network' — no answer from this app, so the pad may have been\n\t * created anyway: fetch failed, the answer broke off, or a proxy or\n\t * PHP itself answered in its place (see fetchJsonWithTimeout();\n\t * `status` then carries what came)\n\t */\n\tconst failCreate = (reason, message, status) => {\n\t\tconst normalizedMessage = String(message || 'Unknown error.')\n\t\tshowError(normalizedMessage)\n\t\tpostHostMessage('epnc:create-failed', {\n\t\t\treason,\n\t\t\tstatus: typeof status === 'number' ? status : null,\n\t\t\tmessage: normalizedMessage,\n\t\t})\n\t}\n\n\tconst readLauncherParams = () => {\n\t\tconst params = new URL(window.location.href).searchParams\n\t\treturn {\n\t\t\tname: String(params.get('name') || '').trim(),\n\t\t\taccessMode: String(params.get('accessMode') || DEFAULT_PAD_ACCESS_MODE).trim(),\n\t\t}\n\t}\n\n\tconst normalizeEmbedRedirectUrl = (value) => {\n\t\tconst url = new URL(String(value || '').trim(), window.location.origin)\n\t\tif (url.origin !== window.location.origin) {\n\t\t\tthrow new Error('Invalid embed URL origin.')\n\t\t}\n\t\treturn url.pathname + url.search + url.hash\n\t}\n\n\tconst classifyHttpStatus = (status) => {\n\t\tif (status === 409) {\n\t\t\treturn 'conflict'\n\t\t}\n\t\treturn 'server'\n\t}\n\n\tconst run = async () => {\n\t\tif (!Number.isFinite(parentFolderId) || parentFolderId <= 0 || createByParentUrl === '') {\n\t\t\tfailCreate('invalid', incompleteConfigMessage)\n\t\t\treturn\n\t\t}\n\t\tif (requestToken() === '') {\n\t\t\tfailCreate('invalid', 'CSRF request token is missing.')\n\t\t\treturn\n\t\t}\n\n\t\tconst { name, accessMode } = readLauncherParams()\n\t\tif (name === '') {\n\t\t\tfailCreate('invalid', missingNameMessage)\n\t\t\treturn\n\t\t}\n\t\tif (!isPadAccessMode(accessMode)) {\n\t\t\tfailCreate('invalid', invalidAccessModeMessage)\n\t\t\treturn\n\t\t}\n\n\t\tconst body = new URLSearchParams()\n\t\tbody.set('parentFolderId', String(parentFolderId))\n\t\tbody.set('name', name)\n\t\tbody.set('accessMode', accessMode)\n\n\t\t// Step 1: server-side create. A write, so it has no time limit (see\n\t\t// fetchJsonWithTimeout()): cut short, it would go on creating with\n\t\t// nobody told, and a second try would meet the file it made. A slow\n\t\t// create is not a failed one.\n\t\tlet data\n\t\ttry {\n\t\t\tdata = await fetchJson(createByParentUrl, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',\n\t\t\t\t\trequesttoken: requestToken(),\n\t\t\t\t},\n\t\t\t\tbody: body.toString(),\n\t\t\t}, { timeoutMs: null, fallbackMessage: 'Pad creation failed.' })\n\t\t} catch (error) {\n\t\t\tconst status = (error && typeof error.status === 'number') ? error.status : null\n\t\t\t// A 4xx refuses the create, even if its body then broke off, so\n\t\t\t// nothing was made. Anything else without this app's answer leaves\n\t\t\t// the outcome open.\n\t\t\tconst refused = status !== null && status >= 400 && status < 500\n\t\t\tif (!refused && (status === null || isUnanswered(error))) {\n\t\t\t\tfailCreate('network', unansweredMessage, status)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst message = !isUnanswered(error) && error instanceof Error && error.message ? error.message : 'Pad creation failed.'\n\t\t\tfailCreate(classifyHttpStatus(status), message, status)\n\t\t\treturn\n\t\t}\n\n\t\t// Step 2: validate the server's response shape *and* the redirect\n\t\t// target before emitting success. A malformed or cross-origin\n\t\t// embed_url is a server-side bug, not a network failure — and we\n\t\t// must not announce success only to then announce failure to the\n\t\t// same host listener, which would leave them with contradictory\n\t\t// signals.\n\t\tif (!data || typeof data.embed_url !== 'string' || data.embed_url.trim() === '') {\n\t\t\tfailCreate('server', 'Pad creation API did not return a valid embed URL.')\n\t\t\treturn\n\t\t}\n\t\tlet redirectTarget\n\t\ttry {\n\t\t\tredirectTarget = normalizeEmbedRedirectUrl(data.embed_url)\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : 'Invalid embed URL.'\n\t\t\tfailCreate('server', message)\n\t\t\treturn\n\t\t}\n\n\t\t// Step 3: announce success once everything is definitively OK, then\n\t\t// navigate. Notify *before* the redirect: once we replace the iframe\n\t\t// location the host loses its handle on this script.\n\t\tpostHostMessage('epnc:create-succeeded', {\n\t\t\tembed_url: data.embed_url,\n\t\t\tfile_id: typeof data.file_id === 'number' ? data.file_id : null,\n\t\t\tpad_id: typeof data.pad_id === 'string' ? data.pad_id : '',\n\t\t\taccess_mode: typeof data.access_mode === 'string' ? data.access_mode : '',\n\t\t})\n\t\twindow.location.replace(redirectTarget)\n\t}\n\n\tvoid run()\n})()\n"],"names":["root","parentFolderId","createByParentUrl","templateRequestToken","missingNameMessage","invalidAccessModeMessage","incompleteConfigMessage","unansweredMessage","loadingNode","errorNode","errorMessageNode","requestToken","ocRequestToken","postHostMessage","type","payload","showError","message","failCreate","reason","status","normalizedMessage","readLauncherParams","params","DEFAULT_PAD_ACCESS_MODE","normalizeEmbedRedirectUrl","value","url","classifyHttpStatus","name","accessMode","isPadAccessMode","body","data","fetchJson","error","isUnanswered","redirectTarget"],"mappings":"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"}
\ No newline at end of file
diff --git a/js/etherpad_nextcloud-embed-main.mjs b/js/etherpad_nextcloud-embed-main.mjs
index edfd12ee..a555b97f 100644
--- a/js/etherpad_nextcloud-embed-main.mjs
+++ b/js/etherpad_nextcloud-embed-main.mjs
@@ -1,2 +1,2 @@
-import{o as Ee,f as x,r as Le}from"./fetch-helpers-Dqr3YYFE.chunk.mjs";import{o as _e,s as Ce,c as Se,a as Te,p as Me,i as xe,b as ve,d as He,e as we,l as Ae,h as Ne}from"./pad-open-flow-D6KDQoVq.chunk.mjs";(function(){const R="epnc-embed__recovery-button",v=R+" epnc-embed__recovery-button--primary",a=document.getElementById("etherpad-nextcloud-embed");if(!(a instanceof HTMLElement))return;const g=Number(a.getAttribute("data-file-id")||""),U=String(a.getAttribute("data-open-by-id-url")||"").trim(),O=String(a.getAttribute("data-initialize-by-id-url-template")||"").trim(),P=String(a.getAttribute("data-recover-url-template")||"").trim(),B=String(a.getAttribute("data-find-original-url-template")||"").trim(),K=String(a.getAttribute("data-request-token")||"").trim(),Q=String(a.getAttribute("data-trusted-origins")||"").split(/\s+/).map(e=>e.trim()).filter(Boolean),s=a.querySelector("[data-epnc-embed-loading]"),L=a.querySelector("[data-epnc-embed-error]"),H=a.querySelector("[data-epnc-embed-error-message]"),_=a.querySelector("[data-epnc-embed-error-actions]"),p=a.querySelector("[data-epnc-embed-recovery]"),l=a.querySelector("[data-epnc-embed-recovery-message]"),b=a.querySelector("[data-epnc-embed-recovery-body]"),m=a.querySelector("[data-epnc-embed-recovery-actions]"),u=a.querySelector("[data-epnc-embed-iframe]"),X=String(a.getAttribute("data-l10n-content-empty")||"This pad is still empty.").trim(),j=String(a.getAttribute("data-l10n-content-loading")||"Loading pad content...").trim(),w=String(a.getAttribute("data-l10n-content-error")||"Could not load the pad content.").trim(),Y=String(a.getAttribute("data-l10n-content-no-url")||"The server did not say where to load this pad from.").trim(),D=String(a.getAttribute("data-l10n-content-retry")||"Try again").trim(),z=String(a.getAttribute("data-l10n-content-refresh")||"Refresh").trim(),Z=String(a.getAttribute("data-l10n-content-refreshing")||"Refreshing...").trim(),$=String(a.getAttribute("data-l10n-external-link")||"Open original pad").trim(),ee=String(a.getAttribute("data-l10n-recovery-checking")||"Checking for the original pad...").trim(),te=String(a.getAttribute("data-l10n-recovery-copy-body")||"").trim(),ne=String(a.getAttribute("data-l10n-recovery-orphan-body")||"").trim(),re=String(a.getAttribute("data-l10n-recovery-open-original")||"Open the original .pad file").trim(),G=String(a.getAttribute("data-l10n-recovery-create-new")||"Create new pad from this file").trim(),ae=String(a.getAttribute("data-l10n-recovery-creating")||"Creating new pad...").trim(),oe=String(a.getAttribute("data-l10n-unanswered")||"Nextcloud did not answer. Check your connection and try again.").trim();let A=null;const h=()=>Ee(K),f=we({requestToken:h}),C=(e,t)=>Le(e,oe,t),N=(e,t)=>Ne(e,t,{preventScroll:!0}),S=(e,t,r=R)=>{const n=document.createElement("button");return n.type="button",n.className=r,n.textContent=e,n.addEventListener("click",t),n},y=(e,t=!1,r=!1)=>{E(),H instanceof HTMLElement&&(H.textContent=String(e||"Unknown error.")),_ instanceof HTMLElement&&(_.replaceChildren(),t&&_.appendChild(S(D,()=>{T(!0)},v))),L instanceof HTMLElement&&(L.hidden=!1),r&&N(_,H)},ie=()=>{E(),s instanceof HTMLElement&&(s.classList.remove("epnc-embed__loading--pad-doc"),s.hidden=!1)},ce=e=>{if(!(s instanceof HTMLElement))return null;s.classList.add("epnc-embed__loading--pad-doc"),s.textContent="";const t=document.createElement("div");t.className="epnc-pad-doc";const r=document.createElement("div");r.className="epnc-pad-doc__inner";const n=document.createElement("div");n.className="epnc-pad-doc__toolbar";const o=document.createElement("span");o.className="epnc-pad-doc__toolbar-error",o.hidden=!0,n.appendChild(o);const c=document.createElement("button");if(c.type="button",c.className="button epnc-pad-doc__refresh",c.textContent=z,n.appendChild(c),String(e||"").trim()!==""){const d=document.createElement("a");d.className="button epnc-pad-doc__link",d.href=e,d.target="_blank",d.rel="noopener noreferrer",d.textContent=$,n.appendChild(d)}const i=document.createElement("div");return i.className="epnc-pad-doc__text",i.textContent=j,r.appendChild(n),r.appendChild(i),t.appendChild(r),s.appendChild(t),{body:i,refresh:c,toolbarError:o,loaded:!1}};let k=0;const q=async(e,t)=>{if(!e||!(e.body instanceof HTMLElement))return;const{body:r,refresh:n,toolbarError:o}=e;k+=1;const c=k,i=()=>c===k;e.loaded||(r.className="epnc-pad-doc__text",r.textContent=j),o instanceof HTMLElement&&(o.hidden=!0),n instanceof HTMLButtonElement&&(n.disabled=!0,n.textContent=Z);try{if(t===""){V(e,t,Y,!1);return}const d=await Ae(t);if(!i())return;if(e.loaded=!0,d.isEmpty){r.className="epnc-pad-doc__text",r.textContent=X;return}r.className="epnc-pad-doc__text epnc-pad-doc__text--html",r.innerHTML=d.html}catch(d){if(!i())return;V(e,t,C(d,w))}finally{i()&&n instanceof HTMLButtonElement&&(n.disabled=!1,n.textContent=z)}},V=(e,t,r,n=!0)=>{const{body:o,toolbarError:c}=e;if(e.loaded&&c instanceof HTMLElement){c.textContent=r||w,c.hidden=!1;return}o.className="epnc-pad-doc__text",o.textContent="";const i=document.createElement("p");i.textContent=r||w,o.appendChild(i),n&&o.appendChild(S(D,()=>{q(e,t)},"button primary"))},de=e=>{if(!(u instanceof HTMLIFrameElement)){y("Embed iframe is not available.");return}const t=()=>{u.removeEventListener("load",t),window.setTimeout(()=>{s instanceof HTMLElement&&(s.hidden=!0),u.hidden=!1},100)};u.addEventListener("load",t,{once:!0}),u.src=e},I=(e,t,r,n={})=>{!e||typeof e.postMessage!="function"||e.postMessage(Object.assign({type:r,fileId:g},n),t)},se=e=>!e||e==="null"?!1:e===window.location.origin?!0:Q.includes(e),le=()=>{A||(A=e=>{const t=String(e.origin||"");if(!se(t))return;const r=e.data,n=typeof r=="string"?r:r&&typeof r=="object"&&typeof r.type=="string"?r.type:"";if(n){if(n==="epnc:host-visible"){f.start();return}if(n==="epnc:host-hidden"){f.fireAndForget(!0,!0),f.stop();return}if(n==="epnc:host-before-close"||n==="epnc:host-sync-now"){const o=n!=="epnc:host-sync-now",c=n==="epnc:host-before-close"?"before-close":"sync-now";I(e.source,t,"epnc:sync-flush-started",{reason:c}),f.sync(!0,o).then(i=>{I(e.source,t,"epnc:sync-flush-finished",{reason:c,result:i&&typeof i=="object"?i:{}})}).catch(i=>{I(e.source,t,"epnc:sync-flush-failed",{reason:c,message:i instanceof Error?i.message:"Sync failed."})}),o&&f.stop()}}},window.addEventListener("message",A))},me=async()=>{const e=new URLSearchParams;e.set("fileId",String(g));const t=await x(U,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded;charset=UTF-8",requesttoken:h()},body:e.toString()});return He(t)},pe=async()=>{const e=O.replace("__FILE_ID__",encodeURIComponent(String(g))),t=await x(e,{method:"POST",headers:{requesttoken:h()}},{timeoutMs:null});t&&t.status==="migrated_from_legacy"&&console.info("Legacy Ownpad .pad migrated to managed format on first open.")},E=()=>{s instanceof HTMLElement&&(s.hidden=!0),L instanceof HTMLElement&&(L.hidden=!0),p instanceof HTMLElement&&(p.hidden=!0),u instanceof HTMLIFrameElement&&(u.hidden=!0,u.removeAttribute("src"))},ue=()=>{p instanceof HTMLElement&&(E(),p.hidden=!1,l instanceof HTMLElement&&(l.textContent=ee),b instanceof HTMLElement&&(b.textContent=""),m instanceof HTMLElement&&m.replaceChildren())},fe=(e,t)=>{if(p instanceof HTMLElement&&(E(),p.hidden=!1,l instanceof HTMLElement&&(l.textContent=t),b instanceof HTMLElement&&(b.textContent=te),m instanceof HTMLElement)){const r=document.createElement("a");r.className=v,r.href=e,r.textContent=re,m.replaceChildren(r,S(G,()=>{J()}))}},ge=e=>{p instanceof HTMLElement&&(E(),p.hidden=!1,l instanceof HTMLElement&&(l.textContent=e),b instanceof HTMLElement&&(b.textContent=ne),m instanceof HTMLElement&&m.replaceChildren(S(G,()=>{J()},v)))},W=e=>{m instanceof HTMLElement&&m.querySelectorAll("button").forEach(t=>{t.disabled=e,e?(t.dataset.originalLabel=t.dataset.originalLabel||t.textContent||"",t.textContent=ae):t.dataset.originalLabel&&(t.textContent=t.dataset.originalLabel,delete t.dataset.originalLabel)})},J=async()=>{if(P===""){y("Recovery is not available in this embed.");return}W(!0);const e=P.replace("__FILE_ID__",encodeURIComponent(String(g)));try{await x(e,{method:"POST",headers:{requesttoken:h()}},{timeoutMs:null}),T(!0)}catch(t){if(t&&t.unanswered===!0){T(!0);return}W(!1),l instanceof HTMLElement&&(l.textContent=C(t,"Recovery failed.")),N(m,l)}},be=async()=>{if(B==="")return"";const e=B.replace("__FILE_ID__",encodeURIComponent(String(g)));try{const t=await x(e,{method:"GET"});if(t&&t.found===!0&&typeof t.embed_url=="string")return t.embed_url}catch{}return""},he=async(e,t,r)=>{const n=C(e,"Pad open failed.");ue();const o=await be();r()&&(o!==""?fe(o,n):ge(n),t&&N(m,l))};let F=0;const T=async(e=!1)=>{F+=1;const t=F,r=()=>t===F;if(ie(),!Number.isFinite(g)||g<=0||U===""||O===""){y("Embed configuration is incomplete.");return}if(h()===""){y("CSRF request token is missing.");return}try{const n=await _e({open:me,initialize:pe,stillWanted:r});if(n===null||!r())return;const{syncUrl:o,intervalMs:c}=Ce(n);f.configure({syncUrl:o,intervalMs:c}),f.installLifecycleHandlers(),le(),o!==""&&f.start();const i=Se(n),{isContentView:d,externalUrl:ye}=Te(n);if(d){const M=ce(ye);M!==null&&M.refresh.addEventListener("click",()=>{q(M,i)}),q(M,i);return}de(Me(n))}catch(n){if(!r())return;if(xe(n)){he(n,e,r);return}y(C(n,"Pad open failed."),ve(n),e)}};T()})();
+import{o as Ee,f as x,r as Le,a as _e}from"./fetch-helpers-BUxbvlK6.chunk.mjs";import{o as Ce,s as Se,c as Te,a as Me,p as xe,i as ve,b as He,d as we,e as Ae,l as Ne,h as qe}from"./pad-open-flow-By_FzSKk.chunk.mjs";(function(){const F="epnc-embed__recovery-button",v=F+" epnc-embed__recovery-button--primary",a=document.getElementById("etherpad-nextcloud-embed");if(!(a instanceof HTMLElement))return;const g=Number(a.getAttribute("data-file-id")||""),U=String(a.getAttribute("data-open-by-id-url")||"").trim(),O=String(a.getAttribute("data-initialize-by-id-url-template")||"").trim(),P=String(a.getAttribute("data-recover-url-template")||"").trim(),B=String(a.getAttribute("data-find-original-url-template")||"").trim(),K=String(a.getAttribute("data-request-token")||"").trim(),Q=String(a.getAttribute("data-trusted-origins")||"").split(/\s+/).map(e=>e.trim()).filter(Boolean),s=a.querySelector("[data-epnc-embed-loading]"),L=a.querySelector("[data-epnc-embed-error]"),H=a.querySelector("[data-epnc-embed-error-message]"),_=a.querySelector("[data-epnc-embed-error-actions]"),p=a.querySelector("[data-epnc-embed-recovery]"),l=a.querySelector("[data-epnc-embed-recovery-message]"),b=a.querySelector("[data-epnc-embed-recovery-body]"),m=a.querySelector("[data-epnc-embed-recovery-actions]"),u=a.querySelector("[data-epnc-embed-iframe]"),X=String(a.getAttribute("data-l10n-content-empty")||"This pad is still empty.").trim(),j=String(a.getAttribute("data-l10n-content-loading")||"Loading pad content...").trim(),w=String(a.getAttribute("data-l10n-content-error")||"Could not load the pad content.").trim(),Y=String(a.getAttribute("data-l10n-content-no-url")||"The server did not say where to load this pad from.").trim(),D=String(a.getAttribute("data-l10n-content-retry")||"Try again").trim(),z=String(a.getAttribute("data-l10n-content-refresh")||"Refresh").trim(),Z=String(a.getAttribute("data-l10n-content-refreshing")||"Refreshing...").trim(),$=String(a.getAttribute("data-l10n-external-link")||"Open original pad").trim(),ee=String(a.getAttribute("data-l10n-recovery-checking")||"Checking for the original pad...").trim(),te=String(a.getAttribute("data-l10n-recovery-copy-body")||"").trim(),ne=String(a.getAttribute("data-l10n-recovery-orphan-body")||"").trim(),re=String(a.getAttribute("data-l10n-recovery-open-original")||"Open the original .pad file").trim(),G=String(a.getAttribute("data-l10n-recovery-create-new")||"Create new pad from this file").trim(),ae=String(a.getAttribute("data-l10n-recovery-creating")||"Creating new pad...").trim(),oe=String(a.getAttribute("data-l10n-unanswered")||"Nextcloud did not answer. Check your connection and try again.").trim();let A=null;const h=()=>Ee(K),f=Ae({requestToken:h}),C=(e,t)=>Le(e,oe,t),N=(e,t)=>qe(e,t,{preventScroll:!0}),S=(e,t,r=F)=>{const n=document.createElement("button");return n.type="button",n.className=r,n.textContent=e,n.addEventListener("click",t),n},y=(e,t=!1,r=!1)=>{E(),H instanceof HTMLElement&&(H.textContent=String(e||"Unknown error.")),_ instanceof HTMLElement&&(_.replaceChildren(),t&&_.appendChild(S(D,()=>{T(!0)},v))),L instanceof HTMLElement&&(L.hidden=!1),r&&N(_,H)},ie=()=>{E(),s instanceof HTMLElement&&(s.classList.remove("epnc-embed__loading--pad-doc"),s.hidden=!1)},ce=e=>{if(!(s instanceof HTMLElement))return null;s.classList.add("epnc-embed__loading--pad-doc"),s.textContent="";const t=document.createElement("div");t.className="epnc-pad-doc";const r=document.createElement("div");r.className="epnc-pad-doc__inner";const n=document.createElement("div");n.className="epnc-pad-doc__toolbar";const o=document.createElement("span");o.className="epnc-pad-doc__toolbar-error",o.hidden=!0,n.appendChild(o);const c=document.createElement("button");if(c.type="button",c.className="button epnc-pad-doc__refresh",c.textContent=z,n.appendChild(c),String(e||"").trim()!==""){const d=document.createElement("a");d.className="button epnc-pad-doc__link",d.href=e,d.target="_blank",d.rel="noopener noreferrer",d.textContent=$,n.appendChild(d)}const i=document.createElement("div");return i.className="epnc-pad-doc__text",i.textContent=j,r.appendChild(n),r.appendChild(i),t.appendChild(r),s.appendChild(t),{body:i,refresh:c,toolbarError:o,loaded:!1}};let q=0;const k=async(e,t)=>{if(!e||!(e.body instanceof HTMLElement))return;const{body:r,refresh:n,toolbarError:o}=e;q+=1;const c=q,i=()=>c===q;e.loaded||(r.className="epnc-pad-doc__text",r.textContent=j),o instanceof HTMLElement&&(o.hidden=!0),n instanceof HTMLButtonElement&&(n.disabled=!0,n.textContent=Z);try{if(t===""){V(e,t,Y,!1);return}const d=await Ne(t);if(!i())return;if(e.loaded=!0,d.isEmpty){r.className="epnc-pad-doc__text",r.textContent=X;return}r.className="epnc-pad-doc__text epnc-pad-doc__text--html",r.innerHTML=d.html}catch(d){if(!i())return;V(e,t,C(d,w))}finally{i()&&n instanceof HTMLButtonElement&&(n.disabled=!1,n.textContent=z)}},V=(e,t,r,n=!0)=>{const{body:o,toolbarError:c}=e;if(e.loaded&&c instanceof HTMLElement){c.textContent=r||w,c.hidden=!1;return}o.className="epnc-pad-doc__text",o.textContent="";const i=document.createElement("p");i.textContent=r||w,o.appendChild(i),n&&o.appendChild(S(D,()=>{k(e,t)},"button primary"))},de=e=>{if(!(u instanceof HTMLIFrameElement)){y("Embed iframe is not available.");return}const t=()=>{u.removeEventListener("load",t),window.setTimeout(()=>{s instanceof HTMLElement&&(s.hidden=!0),u.hidden=!1},100)};u.addEventListener("load",t,{once:!0}),u.src=e},I=(e,t,r,n={})=>{!e||typeof e.postMessage!="function"||e.postMessage(Object.assign({type:r,fileId:g},n),t)},se=e=>!e||e==="null"?!1:e===window.location.origin?!0:Q.includes(e),le=()=>{A||(A=e=>{const t=String(e.origin||"");if(!se(t))return;const r=e.data,n=typeof r=="string"?r:r&&typeof r=="object"&&typeof r.type=="string"?r.type:"";if(n){if(n==="epnc:host-visible"){f.start();return}if(n==="epnc:host-hidden"){f.fireAndForget(!0,!0),f.stop();return}if(n==="epnc:host-before-close"||n==="epnc:host-sync-now"){const o=n!=="epnc:host-sync-now",c=n==="epnc:host-before-close"?"before-close":"sync-now";I(e.source,t,"epnc:sync-flush-started",{reason:c}),f.sync(!0,o).then(i=>{I(e.source,t,"epnc:sync-flush-finished",{reason:c,result:i&&typeof i=="object"?i:{}})}).catch(i=>{I(e.source,t,"epnc:sync-flush-failed",{reason:c,message:i instanceof Error?i.message:"Sync failed."})}),o&&f.stop()}}},window.addEventListener("message",A))},me=async()=>{const e=new URLSearchParams;e.set("fileId",String(g));const t=await x(U,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded;charset=UTF-8",requesttoken:h()},body:e.toString()});return we(t)},pe=async()=>{const e=O.replace("__FILE_ID__",encodeURIComponent(String(g))),t=await x(e,{method:"POST",headers:{requesttoken:h()}},{timeoutMs:null});t&&t.status==="migrated_from_legacy"&&console.info("Legacy Ownpad .pad migrated to managed format on first open.")},E=()=>{s instanceof HTMLElement&&(s.hidden=!0),L instanceof HTMLElement&&(L.hidden=!0),p instanceof HTMLElement&&(p.hidden=!0),u instanceof HTMLIFrameElement&&(u.hidden=!0,u.removeAttribute("src"))},ue=()=>{p instanceof HTMLElement&&(E(),p.hidden=!1,l instanceof HTMLElement&&(l.textContent=ee),b instanceof HTMLElement&&(b.textContent=""),m instanceof HTMLElement&&m.replaceChildren())},fe=(e,t)=>{if(p instanceof HTMLElement&&(E(),p.hidden=!1,l instanceof HTMLElement&&(l.textContent=t),b instanceof HTMLElement&&(b.textContent=te),m instanceof HTMLElement)){const r=document.createElement("a");r.className=v,r.href=e,r.textContent=re,m.replaceChildren(r,S(G,()=>{J()}))}},ge=e=>{p instanceof HTMLElement&&(E(),p.hidden=!1,l instanceof HTMLElement&&(l.textContent=e),b instanceof HTMLElement&&(b.textContent=ne),m instanceof HTMLElement&&m.replaceChildren(S(G,()=>{J()},v)))},W=e=>{m instanceof HTMLElement&&m.querySelectorAll("button").forEach(t=>{t.disabled=e,e?(t.dataset.originalLabel=t.dataset.originalLabel||t.textContent||"",t.textContent=ae):t.dataset.originalLabel&&(t.textContent=t.dataset.originalLabel,delete t.dataset.originalLabel)})},J=async()=>{if(P===""){y("Recovery is not available in this embed.");return}W(!0);const e=P.replace("__FILE_ID__",encodeURIComponent(String(g)));try{await x(e,{method:"POST",headers:{requesttoken:h()}},{timeoutMs:null}),T(!0)}catch(t){if(_e(t)){T(!0);return}W(!1),l instanceof HTMLElement&&(l.textContent=C(t,"Recovery failed.")),N(m,l)}},be=async()=>{if(B==="")return"";const e=B.replace("__FILE_ID__",encodeURIComponent(String(g)));try{const t=await x(e,{method:"GET"});if(t&&t.found===!0&&typeof t.embed_url=="string")return t.embed_url}catch{}return""},he=async(e,t,r)=>{const n=C(e,"Pad open failed.");ue();const o=await be();r()&&(o!==""?fe(o,n):ge(n),t&&N(m,l))};let R=0;const T=async(e=!1)=>{R+=1;const t=R,r=()=>t===R;if(ie(),!Number.isFinite(g)||g<=0||U===""||O===""){y("Embed configuration is incomplete.");return}if(h()===""){y("CSRF request token is missing.");return}try{const n=await Ce({open:me,initialize:pe,stillWanted:r});if(n===null||!r())return;const{syncUrl:o,intervalMs:c}=Se(n);f.configure({syncUrl:o,intervalMs:c}),f.installLifecycleHandlers(),le(),o!==""&&f.start();const i=Te(n),{isContentView:d,externalUrl:ye}=Me(n);if(d){const M=ce(ye);M!==null&&M.refresh.addEventListener("click",()=>{k(M,i)}),k(M,i);return}de(xe(n))}catch(n){if(!r())return;if(ve(n)){he(n,e,r);return}y(C(n,"Pad open failed."),He(n),e)}};T()})();
//# sourceMappingURL=etherpad_nextcloud-embed-main.mjs.map
diff --git a/js/etherpad_nextcloud-embed-main.mjs.map b/js/etherpad_nextcloud-embed-main.mjs.map
index 3c14580d..3b9afae7 100644
--- a/js/etherpad_nextcloud-embed-main.mjs.map
+++ b/js/etherpad_nextcloud-embed-main.mjs.map
@@ -1 +1 @@
-{"version":3,"file":"etherpad_nextcloud-embed-main.mjs","sources":["../src/embed-main.js"],"sourcesContent":["/**\n * SPDX-License-Identifier: AGPL-3.0-or-later\n * Copyright (c) 2026 Jacob Bühler\n */\nimport { ocRequestToken } from './lib/oc-compat.js'\nimport { createPadSync } from './lib/pad-sync.js'\nimport { fetchJsonWithTimeout as fetchJson, requestErrorMessage } from './lib/fetch-helpers.js'\nimport { handFocusTo } from './lib/hand-focus.js'\nimport { loadPadContent } from './lib/pad-content.js'\nimport { assertOpenPayload, contentUrlFrom, contentViewFrom, isMissingBindingError, isRetryableOpenError, openWithFrontmatterRecovery, padUrlFrom, syncSettingsFrom } from './lib/pad-open-flow.js'\n\n(function () {\n\tconst IFRAME_REVEAL_DELAY_MS = 100\n\tconst BUTTON_CLASS = 'epnc-embed__recovery-button'\n\tconst PRIMARY_BUTTON_CLASS = BUTTON_CLASS + ' epnc-embed__recovery-button--primary'\n\n\tconst root = document.getElementById('etherpad-nextcloud-embed')\n\tif (!(root instanceof HTMLElement)) {\n\t\treturn\n\t}\n\n\tconst fileId = Number(root.getAttribute('data-file-id') || '')\n\tconst openByIdUrl = String(root.getAttribute('data-open-by-id-url') || '').trim()\n\tconst initializeByIdUrlTemplate = String(root.getAttribute('data-initialize-by-id-url-template') || '').trim()\n\tconst recoverUrlTemplate = String(root.getAttribute('data-recover-url-template') || '').trim()\n\tconst findOriginalUrlTemplate = String(root.getAttribute('data-find-original-url-template') || '').trim()\n\tconst templateRequestToken = String(root.getAttribute('data-request-token') || '').trim()\n\tconst trustedOrigins = String(root.getAttribute('data-trusted-origins') || '')\n\t\t.split(/\\s+/)\n\t\t.map((value) => value.trim())\n\t\t.filter(Boolean)\n\tconst loadingNode = root.querySelector('[data-epnc-embed-loading]')\n\tconst errorNode = root.querySelector('[data-epnc-embed-error]')\n\tconst errorMessageNode = root.querySelector('[data-epnc-embed-error-message]')\n\tconst errorActionsNode = root.querySelector('[data-epnc-embed-error-actions]')\n\tconst recoveryNode = root.querySelector('[data-epnc-embed-recovery]')\n\tconst recoveryMessageNode = root.querySelector('[data-epnc-embed-recovery-message]')\n\tconst recoveryBodyNode = root.querySelector('[data-epnc-embed-recovery-body]')\n\tconst recoveryActionsNode = root.querySelector('[data-epnc-embed-recovery-actions]')\n\tconst iframe = root.querySelector('[data-epnc-embed-iframe]')\n\tconst contentEmptyText = String(root.getAttribute('data-l10n-content-empty') || 'This pad is still empty.').trim()\n\tconst contentLoadingText = String(root.getAttribute('data-l10n-content-loading') || 'Loading pad content...').trim()\n\tconst contentErrorText = String(root.getAttribute('data-l10n-content-error') || 'Could not load the pad content.').trim()\n\tconst contentNoUrlText = String(root.getAttribute('data-l10n-content-no-url') || 'The server did not say where to load this pad from.').trim()\n\tconst contentRetryText = String(root.getAttribute('data-l10n-content-retry') || 'Try again').trim()\n\tconst contentRefreshText = String(root.getAttribute('data-l10n-content-refresh') || 'Refresh').trim()\n\tconst contentRefreshingText = String(root.getAttribute('data-l10n-content-refreshing') || 'Refreshing...').trim()\n\tconst externalLinkText = String(root.getAttribute('data-l10n-external-link') || 'Open original pad').trim()\n\tconst recoveryCheckingText = String(root.getAttribute('data-l10n-recovery-checking') || 'Checking for the original pad...').trim()\n\tconst recoveryCopyBodyText = String(root.getAttribute('data-l10n-recovery-copy-body') || '').trim()\n\tconst recoveryOrphanBodyText = String(root.getAttribute('data-l10n-recovery-orphan-body') || '').trim()\n\tconst recoveryOpenOriginalText = String(root.getAttribute('data-l10n-recovery-open-original') || 'Open the original .pad file').trim()\n\tconst recoveryCreateNewText = String(root.getAttribute('data-l10n-recovery-create-new') || 'Create new pad from this file').trim()\n\tconst recoveryCreatingText = String(root.getAttribute('data-l10n-recovery-creating') || 'Creating new pad...').trim()\n\tconst unansweredText = String(root.getAttribute('data-l10n-unanswered') || 'Nextcloud did not answer. Check your connection and try again.').trim()\n\tlet messageHandler = null\n\n\tconst requestToken = () => ocRequestToken(templateRequestToken)\n\tconst padSync = createPadSync({ requestToken })\n\n\tconst messageOf = (error, fallback) => requestErrorMessage(error, unansweredText, fallback)\n\t// The page sits in another one, which must not scroll to it.\n\tconst handFocusToCard = (actionsNode, messageNode) => handFocusTo(actionsNode, messageNode, { preventScroll: true })\n\n\t/** Every button of this page: the content's retry, the error panel's, the recovery card's. */\n\tconst buildButton = (label, onClick, className = BUTTON_CLASS) => {\n\t\tconst button = document.createElement('button')\n\t\tbutton.type = 'button'\n\t\tbutton.className = className\n\t\tbutton.textContent = label\n\t\tbutton.addEventListener('click', onClick)\n\t\treturn button\n\t}\n\n\t/**\n\t * $canRetry: the open may work later (`isRetryableOpenError`), so the\n\t * panel offers to run it again rather than a dead end. $afterClick: see\n\t * handFocusTo().\n\t */\n\tconst showError = (message, canRetry = false, afterClick = false) => {\n\t\thideAllPanels()\n\t\tif (errorMessageNode instanceof HTMLElement) {\n\t\t\terrorMessageNode.textContent = String(message || 'Unknown error.')\n\t\t}\n\t\tif (errorActionsNode instanceof HTMLElement) {\n\t\t\terrorActionsNode.replaceChildren()\n\t\t\tif (canRetry) {\n\t\t\t\terrorActionsNode.appendChild(buildButton(contentRetryText, () => { void run(true) }, PRIMARY_BUTTON_CLASS))\n\t\t\t}\n\t\t}\n\t\tif (errorNode instanceof HTMLElement) {\n\t\t\terrorNode.hidden = false\n\t\t}\n\t\tif (afterClick) {\n\t\t\thandFocusToCard(errorActionsNode, errorMessageNode)\n\t\t}\n\t}\n\n\t/** Every panel away and the loading state up: where every open starts. */\n\tconst showLoading = () => {\n\t\thideAllPanels()\n\t\tif (loadingNode instanceof HTMLElement) {\n\t\t\tloadingNode.classList.remove('epnc-embed__loading--pad-doc')\n\t\t\tloadingNode.hidden = false\n\t\t}\n\t}\n\n\t/**\n\t * The read-only surface: a small toolbar and a document area that\n\t * `loadContent` fills. Returns the parts the loader redraws, so the\n\t * frame and its button survive a refresh.\n\t */\n\tconst showPadContentView = (url) => {\n\t\tif (!(loadingNode instanceof HTMLElement)) {\n\t\t\treturn null\n\t\t}\n\t\tloadingNode.classList.add('epnc-embed__loading--pad-doc')\n\t\tloadingNode.textContent = ''\n\n\t\tconst surface = document.createElement('div')\n\t\tsurface.className = 'epnc-pad-doc'\n\n\t\tconst inner = document.createElement('div')\n\t\tinner.className = 'epnc-pad-doc__inner'\n\n\t\tconst toolbar = document.createElement('div')\n\t\ttoolbar.className = 'epnc-pad-doc__toolbar'\n\n\t\t// A failed refresh is reported here rather than in place of the pad.\n\t\tconst toolbarError = document.createElement('span')\n\t\ttoolbarError.className = 'epnc-pad-doc__toolbar-error'\n\t\ttoolbarError.hidden = true\n\t\ttoolbar.appendChild(toolbarError)\n\n\t\tconst refresh = document.createElement('button')\n\t\trefresh.type = 'button'\n\t\trefresh.className = 'button epnc-pad-doc__refresh'\n\t\trefresh.textContent = contentRefreshText\n\t\ttoolbar.appendChild(refresh)\n\n\t\t// A read-only share has nothing to link to: the pad it would point\n\t\t// at is the one being withheld.\n\t\tif (String(url || '').trim() !== '') {\n\t\t\tconst link = document.createElement('a')\n\t\t\tlink.className = 'button epnc-pad-doc__link'\n\t\t\tlink.href = url\n\t\t\tlink.target = '_blank'\n\t\t\tlink.rel = 'noopener noreferrer'\n\t\t\tlink.textContent = externalLinkText\n\t\t\ttoolbar.appendChild(link)\n\t\t}\n\n\t\tconst body = document.createElement('div')\n\t\tbody.className = 'epnc-pad-doc__text'\n\t\tbody.textContent = contentLoadingText\n\n\t\tinner.appendChild(toolbar)\n\t\tinner.appendChild(body)\n\t\tsurface.appendChild(inner)\n\t\tloadingNode.appendChild(surface)\n\n\t\t// `loaded`: \"busy\" and \"nothing yet\" are two states, and only the\n\t\t// second may blank the view.\n\t\treturn { body, refresh, toolbarError, loaded: false }\n\t}\n\n\t// So a slower earlier answer cannot land on top of a newer one.\n\tlet contentGeneration = 0\n\n\t/** Each call re-checks access on the server. */\n\tconst loadContent = async (view, contentUrl) => {\n\t\tif (!view || !(view.body instanceof HTMLElement)) {\n\t\t\treturn\n\t\t}\n\t\tconst { body, refresh, toolbarError } = view\n\t\tcontentGeneration += 1\n\t\tconst generation = contentGeneration\n\t\tconst isCurrent = () => generation === contentGeneration\n\n\t\t// Only before the first answer; after that the button carries it.\n\t\tif (!view.loaded) {\n\t\t\tbody.className = 'epnc-pad-doc__text'\n\t\t\tbody.textContent = contentLoadingText\n\t\t}\n\t\tif (toolbarError instanceof HTMLElement) {\n\t\t\ttoolbarError.hidden = true\n\t\t}\n\t\tif (refresh instanceof HTMLButtonElement) {\n\t\t\trefresh.disabled = true\n\t\t\trefresh.textContent = contentRefreshingText\n\t\t}\n\n\t\ttry {\n\t\t\tif (contentUrl === '') {\n\t\t\t\t// Retrying cannot help, so the message says what is actually\n\t\t\t\t// wrong instead of inviting another press.\n\t\t\t\trenderContentError(view, contentUrl, contentNoUrlText, false)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst content = await loadPadContent(contentUrl)\n\t\t\tif (!isCurrent()) return\n\t\t\tview.loaded = true\n\t\t\tif (content.isEmpty) {\n\t\t\t\t// Left blank this would read as a failure nobody reported.\n\t\t\t\tbody.className = 'epnc-pad-doc__text'\n\t\t\t\tbody.textContent = contentEmptyText\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbody.className = 'epnc-pad-doc__text epnc-pad-doc__text--html'\n\t\t\tbody.innerHTML = content.html\n\t\t} catch (error) {\n\t\t\tif (!isCurrent()) return\n\t\t\trenderContentError(view, contentUrl, messageOf(error, contentErrorText))\n\t\t} finally {\n\t\t\tif (isCurrent() && refresh instanceof HTMLButtonElement) {\n\t\t\t\trefresh.disabled = false\n\t\t\t\trefresh.textContent = contentRefreshText\n\t\t\t}\n\t\t}\n\t}\n\n\tconst renderContentError = (view, contentUrl, message, canRetry = true) => {\n\t\tconst { body, toolbarError } = view\n\t\t// Something is on screen: keep it, and say the attempt failed.\n\t\tif (view.loaded && toolbarError instanceof HTMLElement) {\n\t\t\ttoolbarError.textContent = message || contentErrorText\n\t\t\ttoolbarError.hidden = false\n\t\t\treturn\n\t\t}\n\t\tbody.className = 'epnc-pad-doc__text'\n\t\tbody.textContent = ''\n\n\t\tconst text = document.createElement('p')\n\t\ttext.textContent = message || contentErrorText\n\n\t\tbody.appendChild(text)\n\t\tif (canRetry) {\n\t\t\tbody.appendChild(buildButton(contentRetryText, () => { void loadContent(view, contentUrl) }, 'button primary'))\n\t\t}\n\t}\n\n\tconst showIframe = (url) => {\n\t\tif (!(iframe instanceof HTMLIFrameElement)) {\n\t\t\tshowError('Embed iframe is not available.')\n\t\t\treturn\n\t\t}\n\t\t// The loading state the open started from stays up until the pad has loaded.\n\t\tconst revealIframe = () => {\n\t\t\tiframe.removeEventListener('load', revealIframe)\n\t\t\twindow.setTimeout(() => {\n\t\t\t\tif (loadingNode instanceof HTMLElement) {\n\t\t\t\t\tloadingNode.hidden = true\n\t\t\t\t}\n\t\t\t\tiframe.hidden = false\n\t\t\t}, IFRAME_REVEAL_DELAY_MS)\n\t\t}\n\t\tiframe.addEventListener('load', revealIframe, { once: true })\n\t\tiframe.src = url\n\t}\n\n\tconst postHostMessage = (source, origin, type, payload = {}) => {\n\t\t// Replies are only sent from the already origin-validated message handler.\n\t\tif (!source || typeof source.postMessage !== 'function') {\n\t\t\treturn\n\t\t}\n\t\tsource.postMessage(Object.assign({\n\t\t\ttype,\n\t\t\tfileId,\n\t\t}, payload), origin)\n\t}\n\n\tconst isAllowedMessageOrigin = (origin) => {\n\t\tif (!origin || origin === 'null') {\n\t\t\treturn false\n\t\t}\n\t\tif (origin === window.location.origin) {\n\t\t\treturn true\n\t\t}\n\t\treturn trustedOrigins.includes(origin)\n\t}\n\n\tconst installHostMessageHandler = () => {\n\t\tif (messageHandler) {\n\t\t\treturn\n\t\t}\n\t\tmessageHandler = (event) => {\n\t\t\tconst origin = String(event.origin || '')\n\t\t\tif (!isAllowedMessageOrigin(origin)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst payload = event.data\n\t\t\tconst type = typeof payload === 'string'\n\t\t\t\t? payload\n\t\t\t\t: (payload && typeof payload === 'object' && typeof payload.type === 'string' ? payload.type : '')\n\t\t\tif (!type) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (type === 'epnc:host-visible') {\n\t\t\t\tpadSync.start()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (type === 'epnc:host-hidden') {\n\t\t\t\tpadSync.fireAndForget(true, true)\n\t\t\t\tpadSync.stop()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (type === 'epnc:host-before-close' || type === 'epnc:host-sync-now') {\n\t\t\t\tconst keepalive = type !== 'epnc:host-sync-now'\n\t\t\t\tconst reason = type === 'epnc:host-before-close' ? 'before-close' : 'sync-now'\n\t\t\t\tpostHostMessage(event.source, origin, 'epnc:sync-flush-started', {\n\t\t\t\t\treason,\n\t\t\t\t})\n\t\t\t\tvoid padSync.sync(true, keepalive)\n\t\t\t\t\t.then((result) => {\n\t\t\t\t\t\tpostHostMessage(event.source, origin, 'epnc:sync-flush-finished', {\n\t\t\t\t\t\t\treason,\n\t\t\t\t\t\t\tresult: result && typeof result === 'object' ? result : {},\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t\t.catch((error) => {\n\t\t\t\t\t\tpostHostMessage(event.source, origin, 'epnc:sync-flush-failed', {\n\t\t\t\t\t\t\treason,\n\t\t\t\t\t\t\tmessage: error instanceof Error ? error.message : 'Sync failed.',\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\tif (keepalive) {\n\t\t\t\t\tpadSync.stop()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\twindow.addEventListener('message', messageHandler)\n\t}\n\n\tconst openPad = async () => {\n\t\tconst body = new URLSearchParams()\n\t\tbody.set('fileId', String(fileId))\n\t\tconst data = await fetchJson(openByIdUrl, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',\n\t\t\t\trequesttoken: requestToken(),\n\t\t\t},\n\t\t\tbody: body.toString(),\n\t\t})\n\t\treturn assertOpenPayload(data)\n\t}\n\n\tconst initializePad = async () => {\n\t\tconst url = initializeByIdUrlTemplate.replace('__FILE_ID__', encodeURIComponent(String(fileId)))\n\t\t// A write: see fetchJsonWithTimeout() for why it gets no timeout.\n\t\tconst data = await fetchJson(url, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\trequesttoken: requestToken(),\n\t\t\t},\n\t\t}, { timeoutMs: null })\n\t\tif (data && data.status === 'migrated_from_legacy') {\n\t\t\t// Mirror the backend audit-log entry to the browser console; no\n\t\t\t// toast surface is wired up in this app yet.\n\t\t\tconsole.info('Legacy Ownpad .pad migrated to managed format on first open.')\n\t\t}\n\t}\n\n\tconst hideAllPanels = () => {\n\t\tif (loadingNode instanceof HTMLElement) loadingNode.hidden = true\n\t\tif (errorNode instanceof HTMLElement) errorNode.hidden = true\n\t\tif (recoveryNode instanceof HTMLElement) recoveryNode.hidden = true\n\t\tif (iframe instanceof HTMLIFrameElement) {\n\t\t\tiframe.hidden = true\n\t\t\tiframe.removeAttribute('src')\n\t\t}\n\t}\n\n\tconst showRecoveryChecking = () => {\n\t\tif (!(recoveryNode instanceof HTMLElement)) return\n\t\thideAllPanels()\n\t\trecoveryNode.hidden = false\n\t\tif (recoveryMessageNode instanceof HTMLElement) recoveryMessageNode.textContent = recoveryCheckingText\n\t\tif (recoveryBodyNode instanceof HTMLElement) recoveryBodyNode.textContent = ''\n\t\tif (recoveryActionsNode instanceof HTMLElement) recoveryActionsNode.replaceChildren()\n\t}\n\n\tconst showRecoveryWithOriginal = (originalEmbedUrl, errorMessage) => {\n\t\tif (!(recoveryNode instanceof HTMLElement)) return\n\t\thideAllPanels()\n\t\trecoveryNode.hidden = false\n\t\tif (recoveryMessageNode instanceof HTMLElement) recoveryMessageNode.textContent = errorMessage\n\t\tif (recoveryBodyNode instanceof HTMLElement) recoveryBodyNode.textContent = recoveryCopyBodyText\n\t\tif (recoveryActionsNode instanceof HTMLElement) {\n\t\t\tconst openLink = document.createElement('a')\n\t\t\topenLink.className = PRIMARY_BUTTON_CLASS\n\t\t\t// Stay in embed mode: load the original's embed page in the same\n\t\t\t// frame so a host iframe doesn't need to deal with a new tab.\n\t\t\topenLink.href = originalEmbedUrl\n\t\t\topenLink.textContent = recoveryOpenOriginalText\n\t\t\trecoveryActionsNode.replaceChildren(\n\t\t\t\topenLink,\n\t\t\t\tbuildButton(recoveryCreateNewText, () => { void triggerRecovery() }),\n\t\t\t)\n\t\t}\n\t}\n\n\tconst showRecoveryWithoutOriginal = (errorMessage) => {\n\t\tif (!(recoveryNode instanceof HTMLElement)) return\n\t\thideAllPanels()\n\t\trecoveryNode.hidden = false\n\t\tif (recoveryMessageNode instanceof HTMLElement) recoveryMessageNode.textContent = errorMessage\n\t\tif (recoveryBodyNode instanceof HTMLElement) recoveryBodyNode.textContent = recoveryOrphanBodyText\n\t\tif (recoveryActionsNode instanceof HTMLElement) {\n\t\t\trecoveryActionsNode.replaceChildren(\n\t\t\t\tbuildButton(recoveryCreateNewText, () => { void triggerRecovery() }, PRIMARY_BUTTON_CLASS),\n\t\t\t)\n\t\t}\n\t}\n\n\tconst setRecoveryActionsBusy = (busy) => {\n\t\tif (!(recoveryActionsNode instanceof HTMLElement)) return\n\t\tconst buttons = recoveryActionsNode.querySelectorAll('button')\n\t\tbuttons.forEach((node) => {\n\t\t\tnode.disabled = busy\n\t\t\tif (busy) {\n\t\t\t\tnode.dataset.originalLabel = node.dataset.originalLabel || node.textContent || ''\n\t\t\t\tnode.textContent = recoveryCreatingText\n\t\t\t} else if (node.dataset.originalLabel) {\n\t\t\t\tnode.textContent = node.dataset.originalLabel\n\t\t\t\tdelete node.dataset.originalLabel\n\t\t\t}\n\t\t})\n\t}\n\n\tconst triggerRecovery = async () => {\n\t\tif (recoverUrlTemplate === '') {\n\t\t\tshowError('Recovery is not available in this embed.')\n\t\t\treturn\n\t\t}\n\t\tsetRecoveryActionsBusy(true)\n\t\tconst url = recoverUrlTemplate.replace('__FILE_ID__', encodeURIComponent(String(fileId)))\n\t\ttry {\n\t\t\t// A write, like initializePad().\n\t\t\tawait fetchJson(url, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { requesttoken: requestToken() },\n\t\t\t}, { timeoutMs: null })\n\t\t\t// Open again now that the binding exists.\n\t\t\tvoid run(true)\n\t\t} catch (error) {\n\t\t\t// No answer: the pad may be set up by now, and another recovery\n\t\t\t// would meet it. Opening tells, and is safe to repeat.\n\t\t\tif (error && error.unanswered === true) {\n\t\t\t\tvoid run(true)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tsetRecoveryActionsBusy(false)\n\t\t\tif (recoveryMessageNode instanceof HTMLElement) {\n\t\t\t\trecoveryMessageNode.textContent = messageOf(error, 'Recovery failed.')\n\t\t\t}\n\t\t\t// The clicked button lost the focus while it was disabled.\n\t\t\thandFocusToCard(recoveryActionsNode, recoveryMessageNode)\n\t\t}\n\t}\n\n\t/** The original's embed page, or '' when there is none or no answer. */\n\tconst findOriginalEmbedUrl = async () => {\n\t\tif (findOriginalUrlTemplate === '') {\n\t\t\treturn ''\n\t\t}\n\t\tconst lookupUrl = findOriginalUrlTemplate.replace('__FILE_ID__', encodeURIComponent(String(fileId)))\n\t\ttry {\n\t\t\tconst hint = await fetchJson(lookupUrl, { method: 'GET' })\n\t\t\tif (hint && hint.found === true && typeof hint.embed_url === 'string') {\n\t\t\t\treturn hint.embed_url\n\t\t\t}\n\t\t} catch {\n\t\t\t// Silent: the card then offers a new pad only.\n\t\t}\n\t\treturn ''\n\t}\n\n\tconst enterRecoveryFlow = async (initialError, afterClick, isCurrent) => {\n\t\tconst errorMessage = messageOf(initialError, 'Pad open failed.')\n\t\tshowRecoveryChecking()\n\t\tconst originalEmbedUrl = await findOriginalEmbedUrl()\n\t\tif (!isCurrent()) {\n\t\t\treturn\n\t\t}\n\t\tif (originalEmbedUrl !== '') {\n\t\t\tshowRecoveryWithOriginal(originalEmbedUrl, errorMessage)\n\t\t} else {\n\t\t\tshowRecoveryWithoutOriginal(errorMessage)\n\t\t}\n\t\tif (afterClick) {\n\t\t\thandFocusToCard(recoveryActionsNode, recoveryMessageNode)\n\t\t}\n\t}\n\n\t// So an open that answers late cannot undo a newer one.\n\tlet openGeneration = 0\n\n\t/** $afterClick: a second try or a recovery started it; see handFocusTo(). */\n\tconst run = async (afterClick = false) => {\n\t\topenGeneration += 1\n\t\tconst generation = openGeneration\n\t\tconst isCurrent = () => generation === openGeneration\n\t\tshowLoading()\n\t\tif (!Number.isFinite(fileId) || fileId <= 0 || openByIdUrl === '' || initializeByIdUrlTemplate === '') {\n\t\t\tshowError('Embed configuration is incomplete.')\n\t\t\treturn\n\t\t}\n\t\tif (requestToken() === '') {\n\t\t\tshowError('CSRF request token is missing.')\n\t\t\treturn\n\t\t}\n\t\ttry {\n\t\t\tconst data = await openWithFrontmatterRecovery({ open: openPad, initialize: initializePad, stillWanted: isCurrent })\n\t\t\tif (data === null || !isCurrent()) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst { syncUrl, intervalMs } = syncSettingsFrom(data)\n\t\t\tpadSync.configure({ syncUrl, intervalMs })\n\t\t\tpadSync.installLifecycleHandlers()\n\t\t\tinstallHostMessageHandler()\n\t\t\t// No syncing for a viewer: it writes the pad back into the .pad\n\t\t\t// file, which is exactly what a read-only share may not do.\n\t\t\tif (syncUrl !== '') {\n\t\t\t\tpadSync.start()\n\t\t\t}\n\t\t\tconst contentUrl = contentUrlFrom(data)\n\t\t\tconst { isContentView, externalUrl } = contentViewFrom(data)\n\t\t\tif (isContentView) {\n\t\t\t\tconst view = showPadContentView(externalUrl)\n\t\t\t\tif (view !== null) {\n\t\t\t\t\tview.refresh.addEventListener('click', () => { void loadContent(view, contentUrl) })\n\t\t\t\t}\n\t\t\t\tvoid loadContent(view, contentUrl)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tshowIframe(padUrlFrom(data))\n\t\t} catch (error) {\n\t\t\tif (!isCurrent()) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (isMissingBindingError(error)) {\n\t\t\t\tvoid enterRecoveryFlow(error, afterClick, isCurrent)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tshowError(messageOf(error, 'Pad open failed.'), isRetryableOpenError(error), afterClick)\n\t\t}\n\t}\n\n\tvoid run()\n})()\n"],"names":["BUTTON_CLASS","PRIMARY_BUTTON_CLASS","root","fileId","openByIdUrl","initializeByIdUrlTemplate","recoverUrlTemplate","findOriginalUrlTemplate","templateRequestToken","trustedOrigins","value","loadingNode","errorNode","errorMessageNode","errorActionsNode","recoveryNode","recoveryMessageNode","recoveryBodyNode","recoveryActionsNode","iframe","contentEmptyText","contentLoadingText","contentErrorText","contentNoUrlText","contentRetryText","contentRefreshText","contentRefreshingText","externalLinkText","recoveryCheckingText","recoveryCopyBodyText","recoveryOrphanBodyText","recoveryOpenOriginalText","recoveryCreateNewText","recoveryCreatingText","unansweredText","messageHandler","requestToken","ocRequestToken","padSync","createPadSync","messageOf","error","fallback","requestErrorMessage","handFocusToCard","actionsNode","messageNode","handFocusTo","buildButton","label","onClick","className","button","showError","message","canRetry","afterClick","hideAllPanels","run","showLoading","showPadContentView","url","surface","inner","toolbar","toolbarError","refresh","link","body","contentGeneration","loadContent","view","contentUrl","generation","isCurrent","renderContentError","content","loadPadContent","text","showIframe","revealIframe","postHostMessage","source","origin","type","payload","isAllowedMessageOrigin","installHostMessageHandler","event","keepalive","reason","result","openPad","data","fetchJson","assertOpenPayload","initializePad","showRecoveryChecking","showRecoveryWithOriginal","originalEmbedUrl","errorMessage","openLink","triggerRecovery","showRecoveryWithoutOriginal","setRecoveryActionsBusy","busy","node","findOriginalEmbedUrl","lookupUrl","hint","enterRecoveryFlow","initialError","openGeneration","openWithFrontmatterRecovery","syncUrl","intervalMs","syncSettingsFrom","contentUrlFrom","isContentView","externalUrl","contentViewFrom","padUrlFrom","isMissingBindingError","isRetryableOpenError"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"etherpad_nextcloud-embed-main.mjs","sources":["../src/embed-main.js"],"sourcesContent":["/**\n * SPDX-License-Identifier: AGPL-3.0-or-later\n * Copyright (c) 2026 Jacob Bühler\n */\nimport { ocRequestToken } from './lib/oc-compat.js'\nimport { createPadSync } from './lib/pad-sync.js'\nimport { fetchJsonWithTimeout as fetchJson, isUnanswered, requestErrorMessage } from './lib/fetch-helpers.js'\nimport { handFocusTo } from './lib/hand-focus.js'\nimport { loadPadContent } from './lib/pad-content.js'\nimport { assertOpenPayload, contentUrlFrom, contentViewFrom, isMissingBindingError, isRetryableOpenError, openWithFrontmatterRecovery, padUrlFrom, syncSettingsFrom } from './lib/pad-open-flow.js'\n\n(function () {\n\tconst IFRAME_REVEAL_DELAY_MS = 100\n\tconst BUTTON_CLASS = 'epnc-embed__recovery-button'\n\tconst PRIMARY_BUTTON_CLASS = BUTTON_CLASS + ' epnc-embed__recovery-button--primary'\n\n\tconst root = document.getElementById('etherpad-nextcloud-embed')\n\tif (!(root instanceof HTMLElement)) {\n\t\treturn\n\t}\n\n\tconst fileId = Number(root.getAttribute('data-file-id') || '')\n\tconst openByIdUrl = String(root.getAttribute('data-open-by-id-url') || '').trim()\n\tconst initializeByIdUrlTemplate = String(root.getAttribute('data-initialize-by-id-url-template') || '').trim()\n\tconst recoverUrlTemplate = String(root.getAttribute('data-recover-url-template') || '').trim()\n\tconst findOriginalUrlTemplate = String(root.getAttribute('data-find-original-url-template') || '').trim()\n\tconst templateRequestToken = String(root.getAttribute('data-request-token') || '').trim()\n\tconst trustedOrigins = String(root.getAttribute('data-trusted-origins') || '')\n\t\t.split(/\\s+/)\n\t\t.map((value) => value.trim())\n\t\t.filter(Boolean)\n\tconst loadingNode = root.querySelector('[data-epnc-embed-loading]')\n\tconst errorNode = root.querySelector('[data-epnc-embed-error]')\n\tconst errorMessageNode = root.querySelector('[data-epnc-embed-error-message]')\n\tconst errorActionsNode = root.querySelector('[data-epnc-embed-error-actions]')\n\tconst recoveryNode = root.querySelector('[data-epnc-embed-recovery]')\n\tconst recoveryMessageNode = root.querySelector('[data-epnc-embed-recovery-message]')\n\tconst recoveryBodyNode = root.querySelector('[data-epnc-embed-recovery-body]')\n\tconst recoveryActionsNode = root.querySelector('[data-epnc-embed-recovery-actions]')\n\tconst iframe = root.querySelector('[data-epnc-embed-iframe]')\n\tconst contentEmptyText = String(root.getAttribute('data-l10n-content-empty') || 'This pad is still empty.').trim()\n\tconst contentLoadingText = String(root.getAttribute('data-l10n-content-loading') || 'Loading pad content...').trim()\n\tconst contentErrorText = String(root.getAttribute('data-l10n-content-error') || 'Could not load the pad content.').trim()\n\tconst contentNoUrlText = String(root.getAttribute('data-l10n-content-no-url') || 'The server did not say where to load this pad from.').trim()\n\tconst contentRetryText = String(root.getAttribute('data-l10n-content-retry') || 'Try again').trim()\n\tconst contentRefreshText = String(root.getAttribute('data-l10n-content-refresh') || 'Refresh').trim()\n\tconst contentRefreshingText = String(root.getAttribute('data-l10n-content-refreshing') || 'Refreshing...').trim()\n\tconst externalLinkText = String(root.getAttribute('data-l10n-external-link') || 'Open original pad').trim()\n\tconst recoveryCheckingText = String(root.getAttribute('data-l10n-recovery-checking') || 'Checking for the original pad...').trim()\n\tconst recoveryCopyBodyText = String(root.getAttribute('data-l10n-recovery-copy-body') || '').trim()\n\tconst recoveryOrphanBodyText = String(root.getAttribute('data-l10n-recovery-orphan-body') || '').trim()\n\tconst recoveryOpenOriginalText = String(root.getAttribute('data-l10n-recovery-open-original') || 'Open the original .pad file').trim()\n\tconst recoveryCreateNewText = String(root.getAttribute('data-l10n-recovery-create-new') || 'Create new pad from this file').trim()\n\tconst recoveryCreatingText = String(root.getAttribute('data-l10n-recovery-creating') || 'Creating new pad...').trim()\n\tconst unansweredText = String(root.getAttribute('data-l10n-unanswered') || 'Nextcloud did not answer. Check your connection and try again.').trim()\n\tlet messageHandler = null\n\n\tconst requestToken = () => ocRequestToken(templateRequestToken)\n\tconst padSync = createPadSync({ requestToken })\n\n\tconst messageOf = (error, fallback) => requestErrorMessage(error, unansweredText, fallback)\n\t// The page sits in another one, which must not scroll to it.\n\tconst handFocusToCard = (actionsNode, messageNode) => handFocusTo(actionsNode, messageNode, { preventScroll: true })\n\n\t/** Every button of this page: the content's retry, the error panel's, the recovery card's. */\n\tconst buildButton = (label, onClick, className = BUTTON_CLASS) => {\n\t\tconst button = document.createElement('button')\n\t\tbutton.type = 'button'\n\t\tbutton.className = className\n\t\tbutton.textContent = label\n\t\tbutton.addEventListener('click', onClick)\n\t\treturn button\n\t}\n\n\t/**\n\t * $canRetry: the open may work later (`isRetryableOpenError`), so the\n\t * panel offers to run it again rather than a dead end. $afterClick: see\n\t * handFocusTo().\n\t */\n\tconst showError = (message, canRetry = false, afterClick = false) => {\n\t\thideAllPanels()\n\t\tif (errorMessageNode instanceof HTMLElement) {\n\t\t\terrorMessageNode.textContent = String(message || 'Unknown error.')\n\t\t}\n\t\tif (errorActionsNode instanceof HTMLElement) {\n\t\t\terrorActionsNode.replaceChildren()\n\t\t\tif (canRetry) {\n\t\t\t\terrorActionsNode.appendChild(buildButton(contentRetryText, () => { void run(true) }, PRIMARY_BUTTON_CLASS))\n\t\t\t}\n\t\t}\n\t\tif (errorNode instanceof HTMLElement) {\n\t\t\terrorNode.hidden = false\n\t\t}\n\t\tif (afterClick) {\n\t\t\thandFocusToCard(errorActionsNode, errorMessageNode)\n\t\t}\n\t}\n\n\t/** Every panel away and the loading state up: where every open starts. */\n\tconst showLoading = () => {\n\t\thideAllPanels()\n\t\tif (loadingNode instanceof HTMLElement) {\n\t\t\tloadingNode.classList.remove('epnc-embed__loading--pad-doc')\n\t\t\tloadingNode.hidden = false\n\t\t}\n\t}\n\n\t/**\n\t * The read-only surface: a small toolbar and a document area that\n\t * `loadContent` fills. Returns the parts the loader redraws, so the\n\t * frame and its button survive a refresh.\n\t */\n\tconst showPadContentView = (url) => {\n\t\tif (!(loadingNode instanceof HTMLElement)) {\n\t\t\treturn null\n\t\t}\n\t\tloadingNode.classList.add('epnc-embed__loading--pad-doc')\n\t\tloadingNode.textContent = ''\n\n\t\tconst surface = document.createElement('div')\n\t\tsurface.className = 'epnc-pad-doc'\n\n\t\tconst inner = document.createElement('div')\n\t\tinner.className = 'epnc-pad-doc__inner'\n\n\t\tconst toolbar = document.createElement('div')\n\t\ttoolbar.className = 'epnc-pad-doc__toolbar'\n\n\t\t// A failed refresh is reported here rather than in place of the pad.\n\t\tconst toolbarError = document.createElement('span')\n\t\ttoolbarError.className = 'epnc-pad-doc__toolbar-error'\n\t\ttoolbarError.hidden = true\n\t\ttoolbar.appendChild(toolbarError)\n\n\t\tconst refresh = document.createElement('button')\n\t\trefresh.type = 'button'\n\t\trefresh.className = 'button epnc-pad-doc__refresh'\n\t\trefresh.textContent = contentRefreshText\n\t\ttoolbar.appendChild(refresh)\n\n\t\t// A read-only share has nothing to link to: the pad it would point\n\t\t// at is the one being withheld.\n\t\tif (String(url || '').trim() !== '') {\n\t\t\tconst link = document.createElement('a')\n\t\t\tlink.className = 'button epnc-pad-doc__link'\n\t\t\tlink.href = url\n\t\t\tlink.target = '_blank'\n\t\t\tlink.rel = 'noopener noreferrer'\n\t\t\tlink.textContent = externalLinkText\n\t\t\ttoolbar.appendChild(link)\n\t\t}\n\n\t\tconst body = document.createElement('div')\n\t\tbody.className = 'epnc-pad-doc__text'\n\t\tbody.textContent = contentLoadingText\n\n\t\tinner.appendChild(toolbar)\n\t\tinner.appendChild(body)\n\t\tsurface.appendChild(inner)\n\t\tloadingNode.appendChild(surface)\n\n\t\t// `loaded`: \"busy\" and \"nothing yet\" are two states, and only the\n\t\t// second may blank the view.\n\t\treturn { body, refresh, toolbarError, loaded: false }\n\t}\n\n\t// So a slower earlier answer cannot land on top of a newer one.\n\tlet contentGeneration = 0\n\n\t/** Each call re-checks access on the server. */\n\tconst loadContent = async (view, contentUrl) => {\n\t\tif (!view || !(view.body instanceof HTMLElement)) {\n\t\t\treturn\n\t\t}\n\t\tconst { body, refresh, toolbarError } = view\n\t\tcontentGeneration += 1\n\t\tconst generation = contentGeneration\n\t\tconst isCurrent = () => generation === contentGeneration\n\n\t\t// Only before the first answer; after that the button carries it.\n\t\tif (!view.loaded) {\n\t\t\tbody.className = 'epnc-pad-doc__text'\n\t\t\tbody.textContent = contentLoadingText\n\t\t}\n\t\tif (toolbarError instanceof HTMLElement) {\n\t\t\ttoolbarError.hidden = true\n\t\t}\n\t\tif (refresh instanceof HTMLButtonElement) {\n\t\t\trefresh.disabled = true\n\t\t\trefresh.textContent = contentRefreshingText\n\t\t}\n\n\t\ttry {\n\t\t\tif (contentUrl === '') {\n\t\t\t\t// Retrying cannot help, so the message says what is actually\n\t\t\t\t// wrong instead of inviting another press.\n\t\t\t\trenderContentError(view, contentUrl, contentNoUrlText, false)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst content = await loadPadContent(contentUrl)\n\t\t\tif (!isCurrent()) return\n\t\t\tview.loaded = true\n\t\t\tif (content.isEmpty) {\n\t\t\t\t// Left blank this would read as a failure nobody reported.\n\t\t\t\tbody.className = 'epnc-pad-doc__text'\n\t\t\t\tbody.textContent = contentEmptyText\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbody.className = 'epnc-pad-doc__text epnc-pad-doc__text--html'\n\t\t\tbody.innerHTML = content.html\n\t\t} catch (error) {\n\t\t\tif (!isCurrent()) return\n\t\t\trenderContentError(view, contentUrl, messageOf(error, contentErrorText))\n\t\t} finally {\n\t\t\tif (isCurrent() && refresh instanceof HTMLButtonElement) {\n\t\t\t\trefresh.disabled = false\n\t\t\t\trefresh.textContent = contentRefreshText\n\t\t\t}\n\t\t}\n\t}\n\n\tconst renderContentError = (view, contentUrl, message, canRetry = true) => {\n\t\tconst { body, toolbarError } = view\n\t\t// Something is on screen: keep it, and say the attempt failed.\n\t\tif (view.loaded && toolbarError instanceof HTMLElement) {\n\t\t\ttoolbarError.textContent = message || contentErrorText\n\t\t\ttoolbarError.hidden = false\n\t\t\treturn\n\t\t}\n\t\tbody.className = 'epnc-pad-doc__text'\n\t\tbody.textContent = ''\n\n\t\tconst text = document.createElement('p')\n\t\ttext.textContent = message || contentErrorText\n\n\t\tbody.appendChild(text)\n\t\tif (canRetry) {\n\t\t\tbody.appendChild(buildButton(contentRetryText, () => { void loadContent(view, contentUrl) }, 'button primary'))\n\t\t}\n\t}\n\n\tconst showIframe = (url) => {\n\t\tif (!(iframe instanceof HTMLIFrameElement)) {\n\t\t\tshowError('Embed iframe is not available.')\n\t\t\treturn\n\t\t}\n\t\t// The loading state the open started from stays up until the pad has loaded.\n\t\tconst revealIframe = () => {\n\t\t\tiframe.removeEventListener('load', revealIframe)\n\t\t\twindow.setTimeout(() => {\n\t\t\t\tif (loadingNode instanceof HTMLElement) {\n\t\t\t\t\tloadingNode.hidden = true\n\t\t\t\t}\n\t\t\t\tiframe.hidden = false\n\t\t\t}, IFRAME_REVEAL_DELAY_MS)\n\t\t}\n\t\tiframe.addEventListener('load', revealIframe, { once: true })\n\t\tiframe.src = url\n\t}\n\n\tconst postHostMessage = (source, origin, type, payload = {}) => {\n\t\t// Replies are only sent from the already origin-validated message handler.\n\t\tif (!source || typeof source.postMessage !== 'function') {\n\t\t\treturn\n\t\t}\n\t\tsource.postMessage(Object.assign({\n\t\t\ttype,\n\t\t\tfileId,\n\t\t}, payload), origin)\n\t}\n\n\tconst isAllowedMessageOrigin = (origin) => {\n\t\tif (!origin || origin === 'null') {\n\t\t\treturn false\n\t\t}\n\t\tif (origin === window.location.origin) {\n\t\t\treturn true\n\t\t}\n\t\treturn trustedOrigins.includes(origin)\n\t}\n\n\tconst installHostMessageHandler = () => {\n\t\tif (messageHandler) {\n\t\t\treturn\n\t\t}\n\t\tmessageHandler = (event) => {\n\t\t\tconst origin = String(event.origin || '')\n\t\t\tif (!isAllowedMessageOrigin(origin)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst payload = event.data\n\t\t\tconst type = typeof payload === 'string'\n\t\t\t\t? payload\n\t\t\t\t: (payload && typeof payload === 'object' && typeof payload.type === 'string' ? payload.type : '')\n\t\t\tif (!type) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (type === 'epnc:host-visible') {\n\t\t\t\tpadSync.start()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (type === 'epnc:host-hidden') {\n\t\t\t\tpadSync.fireAndForget(true, true)\n\t\t\t\tpadSync.stop()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (type === 'epnc:host-before-close' || type === 'epnc:host-sync-now') {\n\t\t\t\tconst keepalive = type !== 'epnc:host-sync-now'\n\t\t\t\tconst reason = type === 'epnc:host-before-close' ? 'before-close' : 'sync-now'\n\t\t\t\tpostHostMessage(event.source, origin, 'epnc:sync-flush-started', {\n\t\t\t\t\treason,\n\t\t\t\t})\n\t\t\t\tvoid padSync.sync(true, keepalive)\n\t\t\t\t\t.then((result) => {\n\t\t\t\t\t\tpostHostMessage(event.source, origin, 'epnc:sync-flush-finished', {\n\t\t\t\t\t\t\treason,\n\t\t\t\t\t\t\tresult: result && typeof result === 'object' ? result : {},\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t\t.catch((error) => {\n\t\t\t\t\t\tpostHostMessage(event.source, origin, 'epnc:sync-flush-failed', {\n\t\t\t\t\t\t\treason,\n\t\t\t\t\t\t\tmessage: error instanceof Error ? error.message : 'Sync failed.',\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\tif (keepalive) {\n\t\t\t\t\tpadSync.stop()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\twindow.addEventListener('message', messageHandler)\n\t}\n\n\tconst openPad = async () => {\n\t\tconst body = new URLSearchParams()\n\t\tbody.set('fileId', String(fileId))\n\t\tconst data = await fetchJson(openByIdUrl, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',\n\t\t\t\trequesttoken: requestToken(),\n\t\t\t},\n\t\t\tbody: body.toString(),\n\t\t})\n\t\treturn assertOpenPayload(data)\n\t}\n\n\tconst initializePad = async () => {\n\t\tconst url = initializeByIdUrlTemplate.replace('__FILE_ID__', encodeURIComponent(String(fileId)))\n\t\t// A write: see fetchJsonWithTimeout() for why it gets no timeout.\n\t\tconst data = await fetchJson(url, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\trequesttoken: requestToken(),\n\t\t\t},\n\t\t}, { timeoutMs: null })\n\t\tif (data && data.status === 'migrated_from_legacy') {\n\t\t\t// Mirror the backend audit-log entry to the browser console; no\n\t\t\t// toast surface is wired up in this app yet.\n\t\t\tconsole.info('Legacy Ownpad .pad migrated to managed format on first open.')\n\t\t}\n\t}\n\n\tconst hideAllPanels = () => {\n\t\tif (loadingNode instanceof HTMLElement) loadingNode.hidden = true\n\t\tif (errorNode instanceof HTMLElement) errorNode.hidden = true\n\t\tif (recoveryNode instanceof HTMLElement) recoveryNode.hidden = true\n\t\tif (iframe instanceof HTMLIFrameElement) {\n\t\t\tiframe.hidden = true\n\t\t\tiframe.removeAttribute('src')\n\t\t}\n\t}\n\n\tconst showRecoveryChecking = () => {\n\t\tif (!(recoveryNode instanceof HTMLElement)) return\n\t\thideAllPanels()\n\t\trecoveryNode.hidden = false\n\t\tif (recoveryMessageNode instanceof HTMLElement) recoveryMessageNode.textContent = recoveryCheckingText\n\t\tif (recoveryBodyNode instanceof HTMLElement) recoveryBodyNode.textContent = ''\n\t\tif (recoveryActionsNode instanceof HTMLElement) recoveryActionsNode.replaceChildren()\n\t}\n\n\tconst showRecoveryWithOriginal = (originalEmbedUrl, errorMessage) => {\n\t\tif (!(recoveryNode instanceof HTMLElement)) return\n\t\thideAllPanels()\n\t\trecoveryNode.hidden = false\n\t\tif (recoveryMessageNode instanceof HTMLElement) recoveryMessageNode.textContent = errorMessage\n\t\tif (recoveryBodyNode instanceof HTMLElement) recoveryBodyNode.textContent = recoveryCopyBodyText\n\t\tif (recoveryActionsNode instanceof HTMLElement) {\n\t\t\tconst openLink = document.createElement('a')\n\t\t\topenLink.className = PRIMARY_BUTTON_CLASS\n\t\t\t// Stay in embed mode: load the original's embed page in the same\n\t\t\t// frame so a host iframe doesn't need to deal with a new tab.\n\t\t\topenLink.href = originalEmbedUrl\n\t\t\topenLink.textContent = recoveryOpenOriginalText\n\t\t\trecoveryActionsNode.replaceChildren(\n\t\t\t\topenLink,\n\t\t\t\tbuildButton(recoveryCreateNewText, () => { void triggerRecovery() }),\n\t\t\t)\n\t\t}\n\t}\n\n\tconst showRecoveryWithoutOriginal = (errorMessage) => {\n\t\tif (!(recoveryNode instanceof HTMLElement)) return\n\t\thideAllPanels()\n\t\trecoveryNode.hidden = false\n\t\tif (recoveryMessageNode instanceof HTMLElement) recoveryMessageNode.textContent = errorMessage\n\t\tif (recoveryBodyNode instanceof HTMLElement) recoveryBodyNode.textContent = recoveryOrphanBodyText\n\t\tif (recoveryActionsNode instanceof HTMLElement) {\n\t\t\trecoveryActionsNode.replaceChildren(\n\t\t\t\tbuildButton(recoveryCreateNewText, () => { void triggerRecovery() }, PRIMARY_BUTTON_CLASS),\n\t\t\t)\n\t\t}\n\t}\n\n\tconst setRecoveryActionsBusy = (busy) => {\n\t\tif (!(recoveryActionsNode instanceof HTMLElement)) return\n\t\tconst buttons = recoveryActionsNode.querySelectorAll('button')\n\t\tbuttons.forEach((node) => {\n\t\t\tnode.disabled = busy\n\t\t\tif (busy) {\n\t\t\t\tnode.dataset.originalLabel = node.dataset.originalLabel || node.textContent || ''\n\t\t\t\tnode.textContent = recoveryCreatingText\n\t\t\t} else if (node.dataset.originalLabel) {\n\t\t\t\tnode.textContent = node.dataset.originalLabel\n\t\t\t\tdelete node.dataset.originalLabel\n\t\t\t}\n\t\t})\n\t}\n\n\tconst triggerRecovery = async () => {\n\t\tif (recoverUrlTemplate === '') {\n\t\t\tshowError('Recovery is not available in this embed.')\n\t\t\treturn\n\t\t}\n\t\tsetRecoveryActionsBusy(true)\n\t\tconst url = recoverUrlTemplate.replace('__FILE_ID__', encodeURIComponent(String(fileId)))\n\t\ttry {\n\t\t\t// A write, like initializePad().\n\t\t\tawait fetchJson(url, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { requesttoken: requestToken() },\n\t\t\t}, { timeoutMs: null })\n\t\t\t// Open again now that the binding exists.\n\t\t\tvoid run(true)\n\t\t} catch (error) {\n\t\t\t// No answer: the pad may be set up by now, and another recovery\n\t\t\t// would meet it. Opening tells, and is safe to repeat.\n\t\t\tif (isUnanswered(error)) {\n\t\t\t\tvoid run(true)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tsetRecoveryActionsBusy(false)\n\t\t\tif (recoveryMessageNode instanceof HTMLElement) {\n\t\t\t\trecoveryMessageNode.textContent = messageOf(error, 'Recovery failed.')\n\t\t\t}\n\t\t\t// The clicked button lost the focus while it was disabled.\n\t\t\thandFocusToCard(recoveryActionsNode, recoveryMessageNode)\n\t\t}\n\t}\n\n\t/** The original's embed page, or '' when there is none or no answer. */\n\tconst findOriginalEmbedUrl = async () => {\n\t\tif (findOriginalUrlTemplate === '') {\n\t\t\treturn ''\n\t\t}\n\t\tconst lookupUrl = findOriginalUrlTemplate.replace('__FILE_ID__', encodeURIComponent(String(fileId)))\n\t\ttry {\n\t\t\tconst hint = await fetchJson(lookupUrl, { method: 'GET' })\n\t\t\tif (hint && hint.found === true && typeof hint.embed_url === 'string') {\n\t\t\t\treturn hint.embed_url\n\t\t\t}\n\t\t} catch {\n\t\t\t// Silent: the card then offers a new pad only.\n\t\t}\n\t\treturn ''\n\t}\n\n\tconst enterRecoveryFlow = async (initialError, afterClick, isCurrent) => {\n\t\tconst errorMessage = messageOf(initialError, 'Pad open failed.')\n\t\tshowRecoveryChecking()\n\t\tconst originalEmbedUrl = await findOriginalEmbedUrl()\n\t\tif (!isCurrent()) {\n\t\t\treturn\n\t\t}\n\t\tif (originalEmbedUrl !== '') {\n\t\t\tshowRecoveryWithOriginal(originalEmbedUrl, errorMessage)\n\t\t} else {\n\t\t\tshowRecoveryWithoutOriginal(errorMessage)\n\t\t}\n\t\tif (afterClick) {\n\t\t\thandFocusToCard(recoveryActionsNode, recoveryMessageNode)\n\t\t}\n\t}\n\n\t// So an open that answers late cannot undo a newer one.\n\tlet openGeneration = 0\n\n\t/** $afterClick: a second try or a recovery started it; see handFocusTo(). */\n\tconst run = async (afterClick = false) => {\n\t\topenGeneration += 1\n\t\tconst generation = openGeneration\n\t\tconst isCurrent = () => generation === openGeneration\n\t\tshowLoading()\n\t\tif (!Number.isFinite(fileId) || fileId <= 0 || openByIdUrl === '' || initializeByIdUrlTemplate === '') {\n\t\t\tshowError('Embed configuration is incomplete.')\n\t\t\treturn\n\t\t}\n\t\tif (requestToken() === '') {\n\t\t\tshowError('CSRF request token is missing.')\n\t\t\treturn\n\t\t}\n\t\ttry {\n\t\t\tconst data = await openWithFrontmatterRecovery({ open: openPad, initialize: initializePad, stillWanted: isCurrent })\n\t\t\tif (data === null || !isCurrent()) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst { syncUrl, intervalMs } = syncSettingsFrom(data)\n\t\t\tpadSync.configure({ syncUrl, intervalMs })\n\t\t\tpadSync.installLifecycleHandlers()\n\t\t\tinstallHostMessageHandler()\n\t\t\t// No syncing for a viewer: it writes the pad back into the .pad\n\t\t\t// file, which is exactly what a read-only share may not do.\n\t\t\tif (syncUrl !== '') {\n\t\t\t\tpadSync.start()\n\t\t\t}\n\t\t\tconst contentUrl = contentUrlFrom(data)\n\t\t\tconst { isContentView, externalUrl } = contentViewFrom(data)\n\t\t\tif (isContentView) {\n\t\t\t\tconst view = showPadContentView(externalUrl)\n\t\t\t\tif (view !== null) {\n\t\t\t\t\tview.refresh.addEventListener('click', () => { void loadContent(view, contentUrl) })\n\t\t\t\t}\n\t\t\t\tvoid loadContent(view, contentUrl)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tshowIframe(padUrlFrom(data))\n\t\t} catch (error) {\n\t\t\tif (!isCurrent()) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (isMissingBindingError(error)) {\n\t\t\t\tvoid enterRecoveryFlow(error, afterClick, isCurrent)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tshowError(messageOf(error, 'Pad open failed.'), isRetryableOpenError(error), afterClick)\n\t\t}\n\t}\n\n\tvoid run()\n})()\n"],"names":["BUTTON_CLASS","PRIMARY_BUTTON_CLASS","root","fileId","openByIdUrl","initializeByIdUrlTemplate","recoverUrlTemplate","findOriginalUrlTemplate","templateRequestToken","trustedOrigins","value","loadingNode","errorNode","errorMessageNode","errorActionsNode","recoveryNode","recoveryMessageNode","recoveryBodyNode","recoveryActionsNode","iframe","contentEmptyText","contentLoadingText","contentErrorText","contentNoUrlText","contentRetryText","contentRefreshText","contentRefreshingText","externalLinkText","recoveryCheckingText","recoveryCopyBodyText","recoveryOrphanBodyText","recoveryOpenOriginalText","recoveryCreateNewText","recoveryCreatingText","unansweredText","messageHandler","requestToken","ocRequestToken","padSync","createPadSync","messageOf","error","fallback","requestErrorMessage","handFocusToCard","actionsNode","messageNode","handFocusTo","buildButton","label","onClick","className","button","showError","message","canRetry","afterClick","hideAllPanels","run","showLoading","showPadContentView","url","surface","inner","toolbar","toolbarError","refresh","link","body","contentGeneration","loadContent","view","contentUrl","generation","isCurrent","renderContentError","content","loadPadContent","text","showIframe","revealIframe","postHostMessage","source","origin","type","payload","isAllowedMessageOrigin","installHostMessageHandler","event","keepalive","reason","result","openPad","data","fetchJson","assertOpenPayload","initializePad","showRecoveryChecking","showRecoveryWithOriginal","originalEmbedUrl","errorMessage","openLink","triggerRecovery","showRecoveryWithoutOriginal","setRecoveryActionsBusy","busy","node","isUnanswered","findOriginalEmbedUrl","lookupUrl","hint","enterRecoveryFlow","initialError","openGeneration","openWithFrontmatterRecovery","syncUrl","intervalMs","syncSettingsFrom","contentUrlFrom","isContentView","externalUrl","contentViewFrom","padUrlFrom","isMissingBindingError","isRetryableOpenError"],"mappings":"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"}
\ No newline at end of file
diff --git a/js/etherpad_nextcloud-viewer-init.mjs b/js/etherpad_nextcloud-viewer-init.mjs
index 6c440e6c..27ba6b80 100644
--- a/js/etherpad_nextcloud-viewer-init.mjs
+++ b/js/etherpad_nextcloud-viewer-init.mjs
@@ -1,2 +1,2 @@
-import{a as p,f,o as v,A as u,t as o,r as R,M as k,V as U}from"./fetch-helpers-Dqr3YYFE.chunk.mjs";import{l as E,o as T,s as L,c as A,a as x,p as M,b as q,i as _,e as G,h as N,d as j}from"./pad-open-flow-D6KDQoVq.chunk.mjs";import{p as z,i as m,a as H}from"./urls-Pchbg185.chunk.mjs";function $(t){if(V(t),window._oca_viewer_handlers??(window._oca_viewer_handlers=new Map),window._oca_viewer_handlers.has(t.id)){console.warn(`Handler with id ${t.id} is already registered.`);return}window._oca_viewer_handlers.set(t.id,t)}function V(t){const{id:e,mimes:n,mimesAliases:i,component:a}=t;if(!e||e.trim()===""||typeof e!="string")throw new Error("The handler does not have a valid id");if((!n||!Array.isArray(n))&&!i)throw new Error("Handler needs a valid mime array or mimesAliases");if(!a||typeof a!="object"&&typeof a!="function")throw new Error("The handler does not have a valid component")}const d=new Map,W=50,D=300*1e3,B=async(t,{bypassCache:e=!1}={})=>{const n="path:"+String(t),i=e?null:J(n);if(i!==null)return i;const a=p("/apps/"+u+"/api/v1/pads/resolve")+"?file="+encodeURIComponent(t),c=I(a,{method:"GET",headers:{Accept:"application/json"}},"Pad resolve by path failed.").catch(s=>{throw d.delete(n),s});return Q(n,c),c},K=async t=>{const e=p("/apps/"+u+"/api/v1/pads/find-original/"+encodeURIComponent(String(t)));return I(e,{method:"GET",headers:{Accept:"application/json"}},"Lookup failed.")},Y=async(t,e="")=>{const n=p("/apps/"+u+"/api/v1/pads/recover-from-snapshot/"+encodeURIComponent(String(t))),i=await f(n,{method:"POST",headers:{Accept:"application/json",requesttoken:v()}},{fallbackMessage:"Recovery failed.",timeoutMs:null});return typeof e=="string"&&e!==""&&d.delete("path:"+e),i},J=t=>{const e=d.get(t);return e?Date.now()-e.createdAt>D?(d.delete(t),null):e.request:null},Q=(t,e)=>{if(!d.has(t)&&d.size>=W){const n=d.keys().next().value;n!==void 0&&d.delete(n)}d.set(t,{createdAt:Date.now(),request:e})},I=async(t,e,n)=>f(t,e,{fallbackMessage:n}),C=t=>String(t||"").replace(/&/g,"&").replace(/"/g,""").replace(//g,">"),X=["http:","https:"],Z=t=>{try{const e=new URL(String(t||""));return X.includes(e.protocol)&&e.username===""&&e.password===""}catch{return!1}},tt="default-src 'none'; frame-src http: https:; style-src 'unsafe-inline'",et=t=>{const e=Z(t)?C(t):"";return'
'},w=(t,e)=>R(t,o("Nextcloud did not answer. Check your connection and try again."),e),nt={name:"EtherpadNextcloudViewer",props:{filename:{type:String,required:!1,default:""},basename:{type:String,required:!1,default:""},source:{type:String,required:!1,default:""},fileid:{type:[String,Number],required:!1,default:null},fileId:{type:[String,Number],required:!1,default:null},fileInfo:{type:Object,required:!1,default:null}},data(){return{iframeSrc:"",isLoading:!0,loadError:"",canRecover:!1,canRetryOpen:!1,maybeStaleFileId:!1,recoveryFileId:null,recoveryPath:"",isRecovering:!1,isCheckingOriginal:!1,originalPad:null,externalOpenUrl:"",contentMode:"",contentUrl:"",contentState:"idle",contentError:"",content:{html:"",isEmpty:!1},contentLoaded:!1,contentGeneration:0,resolveGeneration:0}},computed:{sourcePath(){const t=typeof this.source=="string"?this.source.trim():"";return t&&H(t)||""},filePath(){const t=r=>!r||r==="/"?"/":r.startsWith("/")?r:"/"+r,e=(r,l)=>{if(!l)return"";if(l.startsWith("/"))return l;const y=t(r);return y==="/"?"/"+l:y+"/"+l};if(m(this.sourcePath))return this.sourcePath;const n=this.fileInfo&&typeof this.fileInfo=="object"?this.fileInfo:null,i=n&&typeof n.path=="string"?n.path:"";if(m(i))return i.startsWith("/")?i:"/"+i;const a=String(this.filename||this.basename||n&&(n.name||n.basename)||"");if(!a)return"";const c=n&&typeof n.dirname=="string"?n.dirname:"";if(c){const r=e(c,a);if(m(r))return r}const s=new URLSearchParams(window.location.search||"").get("dir")||"/",h=e(s,a);return m(h)?h:"/"+a},openKey(){return`${this.resolvedFileId===null?"":this.resolvedFileId}::${this.filePath}`},resolvedFileId(){const t=[this.fileid,this.fileId,this.fileInfo&&(this.fileInfo.fileid||this.fileInfo.fileId||this.fileInfo.id)];for(const e of t){const n=Number(e);if(Number.isFinite(n)&&n>0)return n}return null}},watch:{openKey:{immediate:!0,handler(){this.resolveOpenUrl()}}},methods:{async fetchOpenPayload(t,e={}){return j(await f(t,Object.assign({method:"GET"},e)))},async initializeMissingFrontmatter(){const t={Accept:"application/json",requesttoken:v()},e={timeoutMs:null,fallbackMessage:"Pad initialization failed."},n=s=>{s&&s.status==="migrated_from_legacy"&&console.info("Legacy Ownpad .pad migrated to managed format on first open.")};if(this.resolvedFileId!==null){const s=p("/apps/"+u+"/api/v1/pads/initialize-by-id/"+encodeURIComponent(String(this.resolvedFileId))),h=await f(s,{method:"POST",headers:t},e);return n(h),h}if(!this.filePath)throw new Error("Pad initialization failed: missing file path.");const i=new URLSearchParams;i.set("file",this.filePath);const a=p("/apps/"+u+"/api/v1/pads/initialize"),c=await f(a,{method:"POST",headers:Object.assign({},t,{"Content-Type":"application/x-www-form-urlencoded;charset=UTF-8"}),body:i.toString()},e);return n(c),c},async resolveRecoveryFileId(t){try{const e=await B(t,{bypassCache:!0}),n=Number(e&&e.file_id);return Number.isFinite(n)&&n>0?n:null}catch{return null}},markLoaded(){this.$emit("update:loaded",!0)},handFocusToErrorCard(){this.$nextTick(()=>{const t=this.$refs.errorCard;t instanceof HTMLElement&&N(t,t.querySelector(".epnc-native-error-message"))})},padSync(){return this._padSync||(this._padSync=G({requestToken:()=>v()})),this._padSync},teardownSync(){this._padSync&&(this._padSync.fireAndForget(!0,!0),this._padSync.stop(),this._padSync.removeLifecycleHandlers())},async resolveOpenUrl(t=!1){const e=++this.resolveGeneration,n=()=>e===this.resolveGeneration;this._openAbort?.abort();const i=typeof AbortController=="function"?new AbortController:null;if(this._openAbort=i,this.isLoading=!0,this.loadError="",this.canRecover=!1,this.canRetryOpen=!1,this.maybeStaleFileId=!1,this.recoveryFileId=null,this.recoveryPath="",this.isCheckingOriginal=!1,this.originalPad=null,this.iframeSrc="",this.externalOpenUrl="",this.contentMode="",this.contentUrl="",this.contentState="idle",this.contentError="",this.content={html:"",isEmpty:!1},this.contentLoaded=!1,this._contentAbort?.abort(),this._padSync&&(this._padSync.stop(),this._padSync.configure({syncUrl:""})),!this.filePath){if(!n())return;this.loadError="No .pad file selected.",this.isLoading=!1;return}const a=this.filePath,c=z(),s=(()=>{if(!c)return"";const r=new URL(p("/apps/"+u+"/api/v1/public/open/"+encodeURIComponent(c)),window.location.origin);return this.resolvedFileId!==null?r.searchParams.set("fileId",String(this.resolvedFileId)):r.searchParams.set("file",a),r.toString()})(),h={"Content-Type":"application/x-www-form-urlencoded;charset=UTF-8",requesttoken:v()};try{const r=await T({open:async()=>{const g=i?i.signal:void 0;if(s)return await this.fetchOpenPayload(s,{signal:g});if(this.resolvedFileId!==null){const S=new URLSearchParams;return S.set("fileId",String(this.resolvedFileId)),await this.fetchOpenPayload(p("/apps/"+u+"/api/v1/pads/open-by-id"),{method:"POST",headers:h,body:S.toString(),signal:g})}const b=new URLSearchParams;return b.set("file",a),await this.fetchOpenPayload(p("/apps/"+u+"/api/v1/pads/open"),{method:"POST",headers:h,body:b.toString(),signal:g})},initialize:()=>this.initializeMissingFrontmatter(),stillWanted:n});if(r===null||!n())return;const{syncUrl:l,intervalMs:y}=L(r);this.padSync().configure({syncUrl:l,intervalMs:y}),this.padSync().installLifecycleHandlers(),l&&this.padSync().start();const P=A(r),{isContentView:F,externalUrl:O}=x(r);if(F){this.externalOpenUrl=O,this.contentUrl=P,this.contentMode="content",this.markLoaded(),this.loadContent();return}this.iframeSrc=M(r),this.markLoaded()}catch(r){if(!n())return;this.loadError=w(r,"Could not load pad."),this.maybeStaleFileId=this.resolvedFileId!==null&&!!r&&r.status===404&&!r.code,this.canRetryOpen=q(r);let l=this.resolvedFileId;if(this.recoveryPath=a,l===null&&!s&&_(r)&&(l=await this.resolveRecoveryFileId(a),!n()))return;this.recoveryFileId=l,this.canRecover=_(r)&&l!==null&&!s,this.canRecover?this.fetchOriginalPadHint(n,t):t&&this.handFocusToErrorCard(),this.markLoaded()}finally{if(!n())return;this.isLoading=!1}},async fetchOriginalPadHint(t,e=!1){if(this.recoveryFileId!==null){this.isCheckingOriginal=!0;try{const n=await K(this.recoveryFileId);if(!t())return;n&&n.found===!0&&typeof n.viewer_url=="string"&&n.viewer_url!==""&&(this.originalPad={viewerUrl:n.viewer_url,path:typeof n.path=="string"?n.path:""})}catch{}finally{t()&&(this.isCheckingOriginal=!1,e&&this.handFocusToErrorCard())}}},async recoverFromSnapshot(){if(!(!this.canRecover||this.isRecovering||this.recoveryFileId===null)){this.isRecovering=!0;try{await Y(this.recoveryFileId,this.recoveryPath),this.loadError="",this.canRecover=!1,await this.resolveOpenUrl(!0)}catch(t){if(t&&t.unanswered===!0){await this.resolveOpenUrl(!0);return}this.isRecovering=!1,this.loadError=w(t,"Could not load pad."),this.handFocusToErrorCard()}finally{this.isRecovering=!1}}},async loadContent(){const t=this.resolveGeneration;this.contentGeneration+=1;const e=this.contentGeneration,n=()=>e===this.contentGeneration&&t===this.resolveGeneration;if(this.contentUrl===""){this.contentState="error",this.contentError=o("The server did not say where to load this pad from.");return}this._contentAbort?.abort();const i=typeof AbortController=="function"?new AbortController:null;this._contentAbort=i,this.contentState="loading",this.contentError="";try{const a=await E(this.contentUrl,{signal:i?.signal});if(!n())return;this.content=a,this.contentLoaded=!0,this.contentState="ready"}catch(a){if(!n()||a&&a.name==="AbortError")return;this.contentState="error",this.contentError=w(a,o("Could not load the pad content."))}},renderContentView(t,e){const n=Array.isArray(e.actions)?e.actions:[],i=this.contentState==="loading";return t("div",{class:"epnc-pad-doc"},[t("div",{class:"epnc-pad-doc__inner"},[t("div",{class:"epnc-pad-doc__toolbar"},[this.contentState==="error"&&this.contentLoaded?t("span",{class:"epnc-pad-doc__toolbar-error"},this.contentError||o("Could not load the pad content.")):null,t("button",{class:"button epnc-pad-doc__refresh",attrs:{type:"button",disabled:i},on:{click:()=>{this.loadContent()}}},i?o("Refreshing..."):o("Refresh")),...n]),this.renderContentBody(t)])])},renderContentBody(t){return this.contentLoaded&&this.contentState!=="ready"?this.renderContentText(t):this.contentState==="error"?t("div",{class:"epnc-pad-doc__text epnc-pad-doc__status"},[t("div",{},this.contentError||o("Could not load the pad content.")),t("button",{class:"button primary",attrs:{type:"button"},on:{click:()=>{this.loadContent()}}},o("Try again"))]):this.contentState!=="ready"?t("div",{class:"epnc-pad-doc__text epnc-pad-doc__status"},o("Loading pad content...")):this.renderContentText(t)},renderContentText(t){return this.content.isEmpty?t("div",{class:"epnc-pad-doc__text epnc-pad-doc__status"},o("This pad is still empty.")):t("div",{class:"epnc-pad-doc__text epnc-pad-doc__text--html",domProps:{innerHTML:this.content.html}})}},beforeDestroy(){this.resolveGeneration+=1,this._openAbort?.abort(),this._contentAbort?.abort(),this.teardownSync()},beforeUnmount(){this.resolveGeneration+=1,this._openAbort?.abort(),this._contentAbort?.abort(),this.teardownSync()},render(t){if(this.loadError){const e=[t("div",{class:"epnc-native-error-title"},o("Could not open pad")),t("div",{class:"epnc-native-error-message",attrs:{role:"alert"}},this.loadError)];return this.maybeStaleFileId&&e.push(t("div",{class:"epnc-native-error-message"},o("This file may have been moved or replaced since the list was loaded. Reload the page and open it again."))),this.canRetryOpen&&e.push(t("button",{class:"button primary epnc-native-error-action",attrs:{type:"button"},on:{click:()=>{this.resolveOpenUrl(!0)}}},o("Try again"))),this.canRecover&&(this.isCheckingOriginal?e.push(t("div",{class:"epnc-native-error-message"},o("Checking for the original pad..."))):this.originalPad?e.push(t("div",{class:"epnc-native-error-message"},o("This file looks like a copy of an existing .pad file in your account. Open the original to keep editing the linked pad, or create a new pad to fork the content stored in this file.")),t("a",{class:"button primary epnc-native-error-action",attrs:{href:this.originalPad.viewerUrl}},o("Open the original .pad file")),t("button",{class:"button epnc-native-error-action",attrs:{type:"button",disabled:this.isRecovering},on:{click:()=>{this.recoverFromSnapshot()}}},this.isRecovering?o("Creating new pad..."):o("Create new pad from this file"))):e.push(t("div",{class:"epnc-native-error-message"},o("We couldn't find a matching pad in this Nextcloud. You can create a new pad from the text stored in this file; from then on, opening this file will load the new pad.")),t("button",{class:"button primary epnc-native-error-action",attrs:{type:"button",disabled:this.isRecovering},on:{click:()=>{this.recoverFromSnapshot()}}},this.isRecovering?o("Creating new pad..."):o("Create new pad from this file")))),t("div",{class:"epnc-native-status epnc-native-status--error"},[t("div",{class:"epnc-native-error-card",ref:"errorCard"},e)])}return this.contentMode==="content"?this.renderContentView(t,this.externalOpenUrl===""?{}:{actions:[t("a",{class:"button epnc-pad-doc__link",attrs:{href:this.externalOpenUrl,target:"_blank",rel:"noopener noreferrer"}},o("Open original pad"))]}):this.isLoading||!this.iframeSrc?t("div",{class:"epnc-native-status"},"Loading pad..."):t("div",{class:"epnc-native-shell"},[t("iframe",{attrs:{srcdoc:et(this.iframeSrc),title:"Etherpad"},on:{load:()=>this.markLoaded(),error:()=>this.markLoaded()},class:"epnc-native-iframe"})])}};$({id:U,mimes:[k],component:nt});
+import{b as p,f,o as v,A as u,t as o,a as R,r as k,M as U,V as E}from"./fetch-helpers-BUxbvlK6.chunk.mjs";import{l as T,o as L,s as A,c as x,a as M,p as q,b as G,i as _,e as N,h as j,d as z}from"./pad-open-flow-By_FzSKk.chunk.mjs";import{p as H,i as m,a as $}from"./urls-Pchbg185.chunk.mjs";function V(t){if(W(t),window._oca_viewer_handlers??(window._oca_viewer_handlers=new Map),window._oca_viewer_handlers.has(t.id)){console.warn(`Handler with id ${t.id} is already registered.`);return}window._oca_viewer_handlers.set(t.id,t)}function W(t){const{id:e,mimes:n,mimesAliases:i,component:a}=t;if(!e||e.trim()===""||typeof e!="string")throw new Error("The handler does not have a valid id");if((!n||!Array.isArray(n))&&!i)throw new Error("Handler needs a valid mime array or mimesAliases");if(!a||typeof a!="object"&&typeof a!="function")throw new Error("The handler does not have a valid component")}const d=new Map,D=50,B=300*1e3,K=async(t,{bypassCache:e=!1}={})=>{const n="path:"+String(t),i=e?null:Q(n);if(i!==null)return i;const a=p("/apps/"+u+"/api/v1/pads/resolve")+"?file="+encodeURIComponent(t),c=I(a,{method:"GET",headers:{Accept:"application/json"}},"Pad resolve by path failed.").catch(s=>{throw d.delete(n),s});return X(n,c),c},Y=async t=>{const e=p("/apps/"+u+"/api/v1/pads/find-original/"+encodeURIComponent(String(t)));return I(e,{method:"GET",headers:{Accept:"application/json"}},"Lookup failed.")},J=async(t,e="")=>{const n=p("/apps/"+u+"/api/v1/pads/recover-from-snapshot/"+encodeURIComponent(String(t))),i=await f(n,{method:"POST",headers:{Accept:"application/json",requesttoken:v()}},{fallbackMessage:"Recovery failed.",timeoutMs:null});return typeof e=="string"&&e!==""&&d.delete("path:"+e),i},Q=t=>{const e=d.get(t);return e?Date.now()-e.createdAt>B?(d.delete(t),null):e.request:null},X=(t,e)=>{if(!d.has(t)&&d.size>=D){const n=d.keys().next().value;n!==void 0&&d.delete(n)}d.set(t,{createdAt:Date.now(),request:e})},I=async(t,e,n)=>f(t,e,{fallbackMessage:n}),C=t=>String(t||"").replace(/&/g,"&").replace(/"/g,""").replace(//g,">"),Z=["http:","https:"],tt=t=>{try{const e=new URL(String(t||""));return Z.includes(e.protocol)&&e.username===""&&e.password===""}catch{return!1}},et="default-src 'none'; frame-src http: https:; style-src 'unsafe-inline'",nt=t=>{const e=tt(t)?C(t):"";return'
'},w=(t,e)=>k(t,o("Nextcloud did not answer. Check your connection and try again."),e),rt={name:"EtherpadNextcloudViewer",props:{filename:{type:String,required:!1,default:""},basename:{type:String,required:!1,default:""},source:{type:String,required:!1,default:""},fileid:{type:[String,Number],required:!1,default:null},fileId:{type:[String,Number],required:!1,default:null},fileInfo:{type:Object,required:!1,default:null}},data(){return{iframeSrc:"",isLoading:!0,loadError:"",canRecover:!1,canRetryOpen:!1,maybeStaleFileId:!1,recoveryFileId:null,recoveryPath:"",isRecovering:!1,isCheckingOriginal:!1,originalPad:null,externalOpenUrl:"",contentMode:"",contentUrl:"",contentState:"idle",contentError:"",content:{html:"",isEmpty:!1},contentLoaded:!1,contentGeneration:0,resolveGeneration:0}},computed:{sourcePath(){const t=typeof this.source=="string"?this.source.trim():"";return t&&$(t)||""},filePath(){const t=r=>!r||r==="/"?"/":r.startsWith("/")?r:"/"+r,e=(r,l)=>{if(!l)return"";if(l.startsWith("/"))return l;const y=t(r);return y==="/"?"/"+l:y+"/"+l};if(m(this.sourcePath))return this.sourcePath;const n=this.fileInfo&&typeof this.fileInfo=="object"?this.fileInfo:null,i=n&&typeof n.path=="string"?n.path:"";if(m(i))return i.startsWith("/")?i:"/"+i;const a=String(this.filename||this.basename||n&&(n.name||n.basename)||"");if(!a)return"";const c=n&&typeof n.dirname=="string"?n.dirname:"";if(c){const r=e(c,a);if(m(r))return r}const s=new URLSearchParams(window.location.search||"").get("dir")||"/",h=e(s,a);return m(h)?h:"/"+a},openKey(){return`${this.resolvedFileId===null?"":this.resolvedFileId}::${this.filePath}`},resolvedFileId(){const t=[this.fileid,this.fileId,this.fileInfo&&(this.fileInfo.fileid||this.fileInfo.fileId||this.fileInfo.id)];for(const e of t){const n=Number(e);if(Number.isFinite(n)&&n>0)return n}return null}},watch:{openKey:{immediate:!0,handler(){this.resolveOpenUrl()}}},methods:{async fetchOpenPayload(t,e={}){return z(await f(t,Object.assign({method:"GET"},e)))},async initializeMissingFrontmatter(){const t={Accept:"application/json",requesttoken:v()},e={timeoutMs:null,fallbackMessage:"Pad initialization failed."},n=s=>{s&&s.status==="migrated_from_legacy"&&console.info("Legacy Ownpad .pad migrated to managed format on first open.")};if(this.resolvedFileId!==null){const s=p("/apps/"+u+"/api/v1/pads/initialize-by-id/"+encodeURIComponent(String(this.resolvedFileId))),h=await f(s,{method:"POST",headers:t},e);return n(h),h}if(!this.filePath)throw new Error("Pad initialization failed: missing file path.");const i=new URLSearchParams;i.set("file",this.filePath);const a=p("/apps/"+u+"/api/v1/pads/initialize"),c=await f(a,{method:"POST",headers:Object.assign({},t,{"Content-Type":"application/x-www-form-urlencoded;charset=UTF-8"}),body:i.toString()},e);return n(c),c},async resolveRecoveryFileId(t){try{const e=await K(t,{bypassCache:!0}),n=Number(e&&e.file_id);return Number.isFinite(n)&&n>0?n:null}catch{return null}},markLoaded(){this.$emit("update:loaded",!0)},handFocusToErrorCard(){this.$nextTick(()=>{const t=this.$refs.errorCard;t instanceof HTMLElement&&j(t,t.querySelector(".epnc-native-error-message"))})},padSync(){return this._padSync||(this._padSync=N({requestToken:()=>v()})),this._padSync},teardownSync(){this._padSync&&(this._padSync.fireAndForget(!0,!0),this._padSync.stop(),this._padSync.removeLifecycleHandlers())},async resolveOpenUrl(t=!1){const e=++this.resolveGeneration,n=()=>e===this.resolveGeneration;this._openAbort?.abort();const i=typeof AbortController=="function"?new AbortController:null;if(this._openAbort=i,this.isLoading=!0,this.loadError="",this.canRecover=!1,this.canRetryOpen=!1,this.maybeStaleFileId=!1,this.recoveryFileId=null,this.recoveryPath="",this.isCheckingOriginal=!1,this.originalPad=null,this.iframeSrc="",this.externalOpenUrl="",this.contentMode="",this.contentUrl="",this.contentState="idle",this.contentError="",this.content={html:"",isEmpty:!1},this.contentLoaded=!1,this._contentAbort?.abort(),this._padSync&&(this._padSync.stop(),this._padSync.configure({syncUrl:""})),!this.filePath){if(!n())return;this.loadError="No .pad file selected.",this.isLoading=!1;return}const a=this.filePath,c=H(),s=(()=>{if(!c)return"";const r=new URL(p("/apps/"+u+"/api/v1/public/open/"+encodeURIComponent(c)),window.location.origin);return this.resolvedFileId!==null?r.searchParams.set("fileId",String(this.resolvedFileId)):r.searchParams.set("file",a),r.toString()})(),h={"Content-Type":"application/x-www-form-urlencoded;charset=UTF-8",requesttoken:v()};try{const r=await L({open:async()=>{const g=i?i.signal:void 0;if(s)return await this.fetchOpenPayload(s,{signal:g});if(this.resolvedFileId!==null){const S=new URLSearchParams;return S.set("fileId",String(this.resolvedFileId)),await this.fetchOpenPayload(p("/apps/"+u+"/api/v1/pads/open-by-id"),{method:"POST",headers:h,body:S.toString(),signal:g})}const b=new URLSearchParams;return b.set("file",a),await this.fetchOpenPayload(p("/apps/"+u+"/api/v1/pads/open"),{method:"POST",headers:h,body:b.toString(),signal:g})},initialize:()=>this.initializeMissingFrontmatter(),stillWanted:n});if(r===null||!n())return;const{syncUrl:l,intervalMs:y}=A(r);this.padSync().configure({syncUrl:l,intervalMs:y}),this.padSync().installLifecycleHandlers(),l&&this.padSync().start();const P=x(r),{isContentView:F,externalUrl:O}=M(r);if(F){this.externalOpenUrl=O,this.contentUrl=P,this.contentMode="content",this.markLoaded(),this.loadContent();return}this.iframeSrc=q(r),this.markLoaded()}catch(r){if(!n())return;this.loadError=w(r,"Could not load pad."),this.maybeStaleFileId=this.resolvedFileId!==null&&!!r&&r.status===404&&!r.code,this.canRetryOpen=G(r);let l=this.resolvedFileId;if(this.recoveryPath=a,l===null&&!s&&_(r)&&(l=await this.resolveRecoveryFileId(a),!n()))return;this.recoveryFileId=l,this.canRecover=_(r)&&l!==null&&!s,this.canRecover?this.fetchOriginalPadHint(n,t):t&&this.handFocusToErrorCard(),this.markLoaded()}finally{if(!n())return;this.isLoading=!1}},async fetchOriginalPadHint(t,e=!1){if(this.recoveryFileId!==null){this.isCheckingOriginal=!0;try{const n=await Y(this.recoveryFileId);if(!t())return;n&&n.found===!0&&typeof n.viewer_url=="string"&&n.viewer_url!==""&&(this.originalPad={viewerUrl:n.viewer_url,path:typeof n.path=="string"?n.path:""})}catch{}finally{t()&&(this.isCheckingOriginal=!1,e&&this.handFocusToErrorCard())}}},async recoverFromSnapshot(){if(!(!this.canRecover||this.isRecovering||this.recoveryFileId===null)){this.isRecovering=!0;try{await J(this.recoveryFileId,this.recoveryPath),this.loadError="",this.canRecover=!1,await this.resolveOpenUrl(!0)}catch(t){if(R(t)){await this.resolveOpenUrl(!0);return}this.isRecovering=!1,this.loadError=w(t,"Could not load pad."),this.handFocusToErrorCard()}finally{this.isRecovering=!1}}},async loadContent(){const t=this.resolveGeneration;this.contentGeneration+=1;const e=this.contentGeneration,n=()=>e===this.contentGeneration&&t===this.resolveGeneration;if(this.contentUrl===""){this.contentState="error",this.contentError=o("The server did not say where to load this pad from.");return}this._contentAbort?.abort();const i=typeof AbortController=="function"?new AbortController:null;this._contentAbort=i,this.contentState="loading",this.contentError="";try{const a=await T(this.contentUrl,{signal:i?.signal});if(!n())return;this.content=a,this.contentLoaded=!0,this.contentState="ready"}catch(a){if(!n()||a&&a.name==="AbortError")return;this.contentState="error",this.contentError=w(a,o("Could not load the pad content."))}},renderContentView(t,e){const n=Array.isArray(e.actions)?e.actions:[],i=this.contentState==="loading";return t("div",{class:"epnc-pad-doc"},[t("div",{class:"epnc-pad-doc__inner"},[t("div",{class:"epnc-pad-doc__toolbar"},[this.contentState==="error"&&this.contentLoaded?t("span",{class:"epnc-pad-doc__toolbar-error"},this.contentError||o("Could not load the pad content.")):null,t("button",{class:"button epnc-pad-doc__refresh",attrs:{type:"button",disabled:i},on:{click:()=>{this.loadContent()}}},i?o("Refreshing..."):o("Refresh")),...n]),this.renderContentBody(t)])])},renderContentBody(t){return this.contentLoaded&&this.contentState!=="ready"?this.renderContentText(t):this.contentState==="error"?t("div",{class:"epnc-pad-doc__text epnc-pad-doc__status"},[t("div",{},this.contentError||o("Could not load the pad content.")),t("button",{class:"button primary",attrs:{type:"button"},on:{click:()=>{this.loadContent()}}},o("Try again"))]):this.contentState!=="ready"?t("div",{class:"epnc-pad-doc__text epnc-pad-doc__status"},o("Loading pad content...")):this.renderContentText(t)},renderContentText(t){return this.content.isEmpty?t("div",{class:"epnc-pad-doc__text epnc-pad-doc__status"},o("This pad is still empty.")):t("div",{class:"epnc-pad-doc__text epnc-pad-doc__text--html",domProps:{innerHTML:this.content.html}})}},beforeDestroy(){this.resolveGeneration+=1,this._openAbort?.abort(),this._contentAbort?.abort(),this.teardownSync()},beforeUnmount(){this.resolveGeneration+=1,this._openAbort?.abort(),this._contentAbort?.abort(),this.teardownSync()},render(t){if(this.loadError){const e=[t("div",{class:"epnc-native-error-title"},o("Could not open pad")),t("div",{class:"epnc-native-error-message",attrs:{role:"alert"}},this.loadError)];return this.maybeStaleFileId&&e.push(t("div",{class:"epnc-native-error-message"},o("This file may have been moved or replaced since the list was loaded. Reload the page and open it again."))),this.canRetryOpen&&e.push(t("button",{class:"button primary epnc-native-error-action",attrs:{type:"button"},on:{click:()=>{this.resolveOpenUrl(!0)}}},o("Try again"))),this.canRecover&&(this.isCheckingOriginal?e.push(t("div",{class:"epnc-native-error-message"},o("Checking for the original pad..."))):this.originalPad?e.push(t("div",{class:"epnc-native-error-message"},o("This file looks like a copy of an existing .pad file in your account. Open the original to keep editing the linked pad, or create a new pad to fork the content stored in this file.")),t("a",{class:"button primary epnc-native-error-action",attrs:{href:this.originalPad.viewerUrl}},o("Open the original .pad file")),t("button",{class:"button epnc-native-error-action",attrs:{type:"button",disabled:this.isRecovering},on:{click:()=>{this.recoverFromSnapshot()}}},this.isRecovering?o("Creating new pad..."):o("Create new pad from this file"))):e.push(t("div",{class:"epnc-native-error-message"},o("We couldn't find a matching pad in this Nextcloud. You can create a new pad from the text stored in this file; from then on, opening this file will load the new pad.")),t("button",{class:"button primary epnc-native-error-action",attrs:{type:"button",disabled:this.isRecovering},on:{click:()=>{this.recoverFromSnapshot()}}},this.isRecovering?o("Creating new pad..."):o("Create new pad from this file")))),t("div",{class:"epnc-native-status epnc-native-status--error"},[t("div",{class:"epnc-native-error-card",ref:"errorCard"},e)])}return this.contentMode==="content"?this.renderContentView(t,this.externalOpenUrl===""?{}:{actions:[t("a",{class:"button epnc-pad-doc__link",attrs:{href:this.externalOpenUrl,target:"_blank",rel:"noopener noreferrer"}},o("Open original pad"))]}):this.isLoading||!this.iframeSrc?t("div",{class:"epnc-native-status"},"Loading pad..."):t("div",{class:"epnc-native-shell"},[t("iframe",{attrs:{srcdoc:nt(this.iframeSrc),title:"Etherpad"},on:{load:()=>this.markLoaded(),error:()=>this.markLoaded()},class:"epnc-native-iframe"})])}};V({id:E,mimes:[U],component:rt});
//# sourceMappingURL=etherpad_nextcloud-viewer-init.mjs.map
diff --git a/js/etherpad_nextcloud-viewer-init.mjs.map b/js/etherpad_nextcloud-viewer-init.mjs.map
index 02d01b94..cb6ee868 100644
--- a/js/etherpad_nextcloud-viewer-init.mjs.map
+++ b/js/etherpad_nextcloud-viewer-init.mjs.map
@@ -1 +1 @@
-{"version":3,"file":"etherpad_nextcloud-viewer-init.mjs","sources":["../node_modules/@nextcloud/viewer/dist/index.js","../src/lib/api-client.js","../src/lib/pad-frame-srcdoc.js","../src/viewer-main.js","../src/viewer-init.js"],"sourcesContent":["/*!\n * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n/**\n * Register a new handler for the viewer.\n * This needs to be called before the viewer is initialized to ensure the handler is available.\n * So this should be called from an initialization script (`OCP\\Util::addInitScript`).\n *\n * @param handler - The handler to register\n * @throws Error if the handler is invalid\n */\nexport function registerHandler(handler) {\n validateHandler(handler);\n window._oca_viewer_handlers ?? (window._oca_viewer_handlers = new Map());\n if (window._oca_viewer_handlers.has(handler.id)) {\n console.warn(`Handler with id ${handler.id} is already registered.`);\n return;\n }\n window._oca_viewer_handlers.set(handler.id, handler);\n}\n/**\n * Validate the handler object.\n *\n * @param handler - The handler to validate\n */\nfunction validateHandler(handler) {\n const { id, mimes, mimesAliases, component } = handler;\n // checking valid handler id\n if (!id || id.trim() === '' || typeof id !== 'string') {\n throw new Error('The handler does not have a valid id');\n }\n // Nothing available to process! Failure\n if ((!mimes || !Array.isArray(mimes)) && !mimesAliases) {\n throw new Error('Handler needs a valid mime array or mimesAliases');\n }\n // checking valid handler component data\n if ((!component || (typeof component !== 'object' && typeof component !== 'function'))) {\n throw new Error('The handler does not have a valid component');\n }\n}\n","/**\n * SPDX-License-Identifier: AGPL-3.0-or-later\n * Copyright (c) 2026 Jacob Bühler\n */\n\n/** Client helpers for pad resolution and snapshot recovery. */\n\nimport { APP_ID } from './constants.js'\nimport { ocGenerateUrl, ocRequestToken } from './oc-compat.js'\nimport { fetchJsonWithTimeout } from './fetch-helpers.js'\n\nconst RESOLVE_CACHE = new Map()\nconst RESOLVE_CACHE_MAX_ENTRIES = 50\nconst RESOLVE_CACHE_TTL_MS = 5 * 60 * 1000\n\n/**\n * Resolve a path to its pad metadata.\n *\n * Bypass the cache before a write. An entry is up to five minutes old, and\n * in five minutes a file can be moved and another `.pad` created at the\n * same path: for a read that is stale, for recovery it would bind a pad to\n * the wrong file.\n */\nexport const apiResolvePadByPath = async (path, { bypassCache = false } = {}) => {\n\tconst cacheKey = 'path:' + String(path)\n\tconst cached = bypassCache ? null : getResolveCache(cacheKey)\n\tif (cached !== null) {\n\t\treturn cached\n\t}\n\tconst url = ocGenerateUrl('/apps/' + APP_ID + '/api/v1/pads/resolve') + '?file=' + encodeURIComponent(path)\n\tconst request = fetchJson(url, {\n\t\tmethod: 'GET',\n\t\theaders: { Accept: 'application/json' },\n\t}, 'Pad resolve by path failed.')\n\t\t.catch((error) => {\n\t\t\tRESOLVE_CACHE.delete(cacheKey)\n\t\t\tthrow error\n\t\t})\n\tsetResolveCache(cacheKey, request)\n\treturn request\n}\n\nexport const apiFindOriginalPad = async (fileId) => {\n\tconst endpoint = ocGenerateUrl('/apps/' + APP_ID + '/api/v1/pads/find-original/' + encodeURIComponent(String(fileId)))\n\treturn fetchJson(endpoint, {\n\t\tmethod: 'GET',\n\t\theaders: { Accept: 'application/json' },\n\t}, 'Lookup failed.')\n}\n\nexport const apiRecoverFromSnapshot = async (fileId, path = '') => {\n\tconst endpoint = ocGenerateUrl('/apps/' + APP_ID + '/api/v1/pads/recover-from-snapshot/' + encodeURIComponent(String(fileId)))\n\t// A client timeout would not stop the server-side provisioning work.\n\tconst result = await fetchJsonWithTimeout(endpoint, {\n\t\tmethod: 'POST',\n\t\theaders: {\n\t\t\tAccept: 'application/json',\n\t\t\trequesttoken: ocRequestToken(),\n\t\t},\n\t}, { fallbackMessage: 'Recovery failed.', timeoutMs: null })\n\t// Only this path: flushing every entry would throw away answers for\n\t// unrelated files the session has already looked up.\n\tif (typeof path === 'string' && path !== '') {\n\t\tRESOLVE_CACHE.delete('path:' + path)\n\t}\n\treturn result\n}\nconst getResolveCache = (cacheKey) => {\n\tconst cached = RESOLVE_CACHE.get(cacheKey)\n\tif (!cached) {\n\t\treturn null\n\t}\n\tif ((Date.now() - cached.createdAt) > RESOLVE_CACHE_TTL_MS) {\n\t\tRESOLVE_CACHE.delete(cacheKey)\n\t\treturn null\n\t}\n\treturn cached.request\n}\n\nconst setResolveCache = (cacheKey, request) => {\n\tif (!RESOLVE_CACHE.has(cacheKey) && RESOLVE_CACHE.size >= RESOLVE_CACHE_MAX_ENTRIES) {\n\t\tconst oldestKey = RESOLVE_CACHE.keys().next().value\n\t\tif (oldestKey !== undefined) {\n\t\t\tRESOLVE_CACHE.delete(oldestKey)\n\t\t}\n\t}\n\tRESOLVE_CACHE.set(cacheKey, {\n\t\tcreatedAt: Date.now(),\n\t\trequest,\n\t})\n}\n\nconst fetchJson = async (url, options, fallbackMessage) =>\n\tfetchJsonWithTimeout(url, options, { fallbackMessage })\n","/**\n * SPDX-License-Identifier: AGPL-3.0-or-later\n * Copyright (c) 2026 Jacob Bühler\n */\n\nconst escapeAttribute = (value) => String(value || '')\n\t.replace(/&/g, '&')\n\t.replace(/\"/g, '"')\n\t.replace(//g, '>')\n\nconst ALLOWED_PAD_URL_SCHEMES = ['http:', 'https:']\n\nconst isSafePadUrl = (url) => {\n\ttry {\n\t\tconst parsed = new URL(String(url || ''))\n\t\treturn ALLOWED_PAD_URL_SCHEMES.includes(parsed.protocol)\n\t\t\t&& parsed.username === ''\n\t\t\t&& parsed.password === ''\n\t} catch {\n\t\treturn false\n\t}\n}\n\nconst SRC_DOC_CSP = \"default-src 'none'; frame-src http: https:; style-src 'unsafe-inline'\"\n\nexport const buildPadFrameSrcdoc = (url) => {\n\tconst safeUrl = isSafePadUrl(url) ? escapeAttribute(url) : ''\n\treturn '
'\n\t+ '
'\n\t+ ''\n\t+ '
'\n}\n","/**\n * SPDX-License-Identifier: AGPL-3.0-or-later\n * Copyright (c) 2026 Jacob Bühler\n */\n\n/** Nextcloud Viewer component for editable pads and read-only pad content. */\n\nimport { APP_ID } from './lib/constants.js'\nimport { apiFindOriginalPad, apiRecoverFromSnapshot, apiResolvePadByPath } from './lib/api-client.js'\nimport { fetchJsonWithTimeout, requestErrorMessage } from './lib/fetch-helpers.js'\nimport { handFocusTo } from './lib/hand-focus.js'\nimport { ocGenerateUrl, ocRequestToken, translate } from './lib/oc-compat.js'\nimport { createPadSync } from './lib/pad-sync.js'\nimport { loadPadContent } from './lib/pad-content.js'\nimport { assertOpenPayload, contentUrlFrom, contentViewFrom, isMissingBindingError, isRetryableOpenError, openWithFrontmatterRecovery, padUrlFrom, syncSettingsFrom } from './lib/pad-open-flow.js'\nimport { buildPadFrameSrcdoc } from './lib/pad-frame-srcdoc.js'\nimport { isPadName, parsePadPathFromDavHref, parsePublicShareTokenFromLocation } from './lib/urls.js'\n\n// When nothing came back: our own sentence, not the browser's English.\nconst messageOf = (error, fallback) => requestErrorMessage(error, translate('Nextcloud did not answer. Check your connection and try again.'), fallback)\n\nconst component = {\n\tname: 'EtherpadNextcloudViewer',\n\tprops: {\n\t\tfilename: { type: String, required: false, default: '' },\n\t\tbasename: { type: String, required: false, default: '' },\n\t\tsource: { type: String, required: false, default: '' },\n\t\tfileid: { type: [String, Number], required: false, default: null },\n\t\tfileId: { type: [String, Number], required: false, default: null },\n\t\tfileInfo: { type: Object, required: false, default: null },\n\t},\n\tdata() {\n\t\treturn {\n\t\t\tiframeSrc: '',\n\t\t\tisLoading: true,\n\t\t\tloadError: '',\n\t\t\tcanRecover: false,\n\t\t\tcanRetryOpen: false,\n\t\t\tmaybeStaleFileId: false,\n\t\t\t// Recovery may resolve this from the path when Viewer supplies no id.\n\t\t\trecoveryFileId: null,\n\t\t\t// Kept with the id so recovery invalidates the matching cache entry.\n\t\t\trecoveryPath: '',\n\t\t\tisRecovering: false,\n\t\t\tisCheckingOriginal: false,\n\t\t\toriginalPad: null,\n\t\t\texternalOpenUrl: '',\n\t\t\tcontentMode: '',\n\t\t\tcontentUrl: '',\n\t\t\tcontentState: 'idle',\n\t\t\tcontentError: '',\n\t\t\tcontent: { html: '', isEmpty: false },\n\t\t\t// A refresh keeps previously loaded content visible.\n\t\t\tcontentLoaded: false,\n\t\t\t// Refreshes supersede each other without superseding the open.\n\t\t\tcontentGeneration: 0,\n\t\t\tresolveGeneration: 0,\n\t\t}\n\t},\n\tcomputed: {\n\t\tsourcePath() {\n\t\t\t// Whitespace inside the DAV URL is encoded; only surrounding noise is trimmed.\n\t\t\tconst value = typeof this.source === 'string' ? this.source.trim() : ''\n\t\t\tif (!value) return ''\n\t\t\treturn parsePadPathFromDavHref(value) || ''\n\t\t},\n\t\tfilePath() {\n\t\t\t// Whitespace is part of the name, not noise: `Notes .pad` and\n\t\t\t// `Notes.pad` are two files, and trimming here opened the wrong one.\n\t\t\tconst normalizeDir = (dir) => {\n\t\t\t\tif (!dir || dir === '/') return '/'\n\t\t\t\treturn dir.startsWith('/') ? dir : ('/' + dir)\n\t\t\t}\n\t\t\tconst joinPath = (dir, name) => {\n\t\t\t\tif (!name) return ''\n\t\t\t\tif (name.startsWith('/')) return name\n\t\t\t\tconst normalizedDir = normalizeDir(dir)\n\t\t\t\treturn normalizedDir === '/' ? '/' + name : normalizedDir + '/' + name\n\t\t\t}\n\t\t\tif (isPadName(this.sourcePath)) return this.sourcePath\n\n\t\t\tconst info = this.fileInfo && typeof this.fileInfo === 'object' ? this.fileInfo : null\n\t\t\tconst infoPath = info && typeof info.path === 'string' ? info.path : ''\n\t\t\tif (isPadName(infoPath)) return infoPath.startsWith('/') ? infoPath : ('/' + infoPath)\n\n\t\t\tconst baseName = String(this.filename || this.basename || (info && (info.name || info.basename)) || '')\n\t\t\tif (!baseName) return ''\n\n\t\t\tconst infoDir = info && typeof info.dirname === 'string' ? info.dirname : ''\n\t\t\tif (infoDir) {\n\t\t\t\tconst combined = joinPath(infoDir, baseName)\n\t\t\t\tif (isPadName(combined)) return combined\n\t\t\t}\n\n\t\t\tconst params = new URLSearchParams(window.location.search || '')\n\t\t\tconst urlDir = params.get('dir') || '/'\n\t\t\tconst fromDir = joinPath(urlDir, baseName)\n\t\t\tif (isPadName(fromDir)) return fromDir\n\t\t\treturn '/' + baseName\n\t\t},\n\t\topenKey() {\n\t\t\treturn `${this.resolvedFileId === null ? '' : this.resolvedFileId}::${this.filePath}`\n\t\t},\n\t\tresolvedFileId() {\n\t\t\tconst candidates = [this.fileid, this.fileId, this.fileInfo && (this.fileInfo.fileid || this.fileInfo.fileId || this.fileInfo.id)]\n\t\t\tfor (const candidate of candidates) {\n\t\t\t\tconst numeric = Number(candidate)\n\t\t\t\tif (Number.isFinite(numeric) && numeric > 0) return numeric\n\t\t\t}\n\t\t\t// Route ids can outlive the item shown after Viewer navigation.\n\t\t\treturn null\n\t\t},\n\t},\n\twatch: {\n\t\t// A file swap changes path and id together; open it only once.\n\t\topenKey: { immediate: true, handler() { void this.resolveOpenUrl() } },\n\t},\n\tmethods: {\n\t\tasync fetchOpenPayload(url, init = {}) {\n\t\t\treturn assertOpenPayload(await fetchJsonWithTimeout(url, Object.assign({ method: 'GET' }, init)))\n\t\t},\n\t\tasync initializeMissingFrontmatter() {\n\t\t\tconst headers = {\n\t\t\t\tAccept: 'application/json',\n\t\t\t\trequesttoken: ocRequestToken(),\n\t\t\t}\n\n\t\t\t// A client timeout would not stop the server-side provisioning work.\n\t\t\tconst initOptions = { timeoutMs: null, fallbackMessage: 'Pad initialization failed.' }\n\n\t\t\tconst announceMigratedStatus = (data) => {\n\t\t\t\tif (data && data.status === 'migrated_from_legacy') {\n\t\t\t\t\tconsole.info('Legacy Ownpad .pad migrated to managed format on first open.')\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (this.resolvedFileId !== null) {\n\t\t\t\tconst url = ocGenerateUrl('/apps/' + APP_ID + '/api/v1/pads/initialize-by-id/' + encodeURIComponent(String(this.resolvedFileId)))\n\t\t\t\tconst data = await fetchJsonWithTimeout(url, { method: 'POST', headers }, initOptions)\n\t\t\t\tannounceMigratedStatus(data)\n\t\t\t\treturn data\n\t\t\t}\n\n\t\t\tif (!this.filePath) {\n\t\t\t\tthrow new Error('Pad initialization failed: missing file path.')\n\t\t\t}\n\n\t\t\tconst body = new URLSearchParams()\n\t\t\tbody.set('file', this.filePath)\n\t\t\tconst url = ocGenerateUrl('/apps/' + APP_ID + '/api/v1/pads/initialize')\n\t\t\tconst data = await fetchJsonWithTimeout(url, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: Object.assign({}, headers, {\n\t\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',\n\t\t\t\t}),\n\t\t\t\tbody: body.toString(),\n\t\t\t}, initOptions)\n\t\t\tannounceMigratedStatus(data)\n\t\t\treturn data\n\t\t},\n\t\t/** Resolve a fresh id for recovery without changing the opened file. */\n\t\tasync resolveRecoveryFileId(openPath) {\n\t\t\ttry {\n\t\t\t\tconst resolved = await apiResolvePadByPath(openPath, { bypassCache: true })\n\t\t\t\tconst id = Number(resolved && resolved.file_id)\n\t\t\t\treturn Number.isFinite(id) && id > 0 ? id : null\n\t\t\t} catch {\n\t\t\t\treturn null\n\t\t\t}\n\t\t},\n\t\tmarkLoaded() {\n\t\t\tthis.$emit('update:loaded', true)\n\t\t},\n\t\t/**\n\t\t * See handFocusTo(). On the next tick, once the card is drawn: call\n\t\t * it after setting what the card shows.\n\t\t */\n\t\thandFocusToErrorCard() {\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tconst card = this.$refs.errorCard\n\t\t\t\tif (card instanceof HTMLElement) {\n\t\t\t\t\thandFocusTo(card, card.querySelector('.epnc-native-error-message'))\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t\t// Keep the sync controller non-reactive and available to the immediate watcher.\n\t\tpadSync() {\n\t\t\tif (!this._padSync) {\n\t\t\t\tthis._padSync = createPadSync({ requestToken: () => ocRequestToken() })\n\t\t\t}\n\t\t\treturn this._padSync\n\t\t},\n\t\t// Do not create a controller solely to tear it down.\n\t\tteardownSync() {\n\t\t\tif (!this._padSync) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis._padSync.fireAndForget(true, true)\n\t\t\tthis._padSync.stop()\n\t\t\tthis._padSync.removeLifecycleHandlers()\n\t\t},\n\t\t/** $afterClick: see handFocusToErrorCard(). */\n\t\tasync resolveOpenUrl(afterClick = false) {\n\t\t\tconst generation = ++this.resolveGeneration\n\t\t\tconst isCurrent = () => generation === this.resolveGeneration\n\t\t\t// Discarding a result is insufficient: a completed request may mint a session.\n\t\t\tthis._openAbort?.abort()\n\t\t\tconst abort = typeof AbortController === 'function' ? new AbortController() : null\n\t\t\tthis._openAbort = abort\n\n\t\t\tthis.isLoading = true\n\t\t\tthis.loadError = ''\n\t\t\tthis.canRecover = false\n\t\t\tthis.canRetryOpen = false\n\t\t\tthis.maybeStaleFileId = false\n\t\t\tthis.recoveryFileId = null\n\t\t\tthis.recoveryPath = ''\n\t\t\tthis.isCheckingOriginal = false\n\t\t\tthis.originalPad = null\n\t\t\tthis.iframeSrc = ''\n\t\t\tthis.externalOpenUrl = ''\n\t\t\tthis.contentMode = ''\n\t\t\tthis.contentUrl = ''\n\t\t\tthis.contentState = 'idle'\n\t\t\tthis.contentError = ''\n\t\t\tthis.content = { html: '', isEmpty: false }\n\t\t\tthis.contentLoaded = false\n\t\t\tthis._contentAbort?.abort()\n\t\t\t// Do not construct a sync controller while resetting viewer state.\n\t\t\tif (this._padSync) {\n\t\t\t\tthis._padSync.stop()\n\t\t\t\tthis._padSync.configure({ syncUrl: '' })\n\t\t\t}\n\n\t\t\tif (!this.filePath) {\n\t\t\t\tif (!isCurrent()) return\n\t\t\t\tthis.loadError = 'No .pad file selected.'\n\t\t\t\tthis.isLoading = false\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Viewer props may change before the watcher starts the next open.\n\t\t\tconst openPath = this.filePath\n\t\t\tconst publicToken = parsePublicShareTokenFromLocation()\n\t\t\tconst byPublicUrl = (() => {\n\t\t\t\tif (!publicToken) return ''\n\t\t\t\tconst url = new URL(ocGenerateUrl('/apps/' + APP_ID + '/api/v1/public/open/' + encodeURIComponent(publicToken)), window.location.origin)\n\t\t\t\t// The public endpoint rejects conflicting id and path locators.\n\t\t\t\tif (this.resolvedFileId !== null) {\n\t\t\t\t\turl.searchParams.set('fileId', String(this.resolvedFileId))\n\t\t\t\t} else {\n\t\t\t\t\turl.searchParams.set('file', openPath)\n\t\t\t\t}\n\t\t\t\treturn url.toString()\n\t\t\t})()\n\t\t\tconst openPostHeaders = {\n\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',\n\t\t\t\trequesttoken: ocRequestToken(),\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst fetchOpenData = async () => {\n\t\t\t\t\t// Never retry a refused id by path: it could identify a different file.\n\t\t\t\t\tconst signal = abort ? abort.signal : undefined\n\t\t\t\t\tif (byPublicUrl) {\n\t\t\t\t\t\treturn await this.fetchOpenPayload(byPublicUrl, { signal })\n\t\t\t\t\t}\n\t\t\t\t\t// One way in, chosen once. An open by id that retries by\n\t\t\t\t\t// path is how a refused id ended up opening whatever the\n\t\t\t\t\t// path pointed at.\n\t\t\t\t\tif (this.resolvedFileId !== null) {\n\t\t\t\t\t\tconst byIdBody = new URLSearchParams()\n\t\t\t\t\t\tbyIdBody.set('fileId', String(this.resolvedFileId))\n\t\t\t\t\t\treturn await this.fetchOpenPayload(\n\t\t\t\t\t\t\tocGenerateUrl('/apps/' + APP_ID + '/api/v1/pads/open-by-id'),\n\t\t\t\t\t\t\t{ method: 'POST', headers: openPostHeaders, body: byIdBody.toString(), signal },\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t\tconst byPathBody = new URLSearchParams()\n\t\t\t\t\tbyPathBody.set('file', openPath)\n\t\t\t\t\treturn await this.fetchOpenPayload(\n\t\t\t\t\t\tocGenerateUrl('/apps/' + APP_ID + '/api/v1/pads/open'),\n\t\t\t\t\t\t{ method: 'POST', headers: openPostHeaders, body: byPathBody.toString(), signal },\n\t\t\t\t\t)\n\t\t\t\t}\n\n\t\t\t\tconst data = await openWithFrontmatterRecovery({\n\t\t\t\t\topen: fetchOpenData,\n\t\t\t\t\tinitialize: () => this.initializeMissingFrontmatter(),\n\t\t\t\t\tstillWanted: isCurrent,\n\t\t\t\t})\n\t\t\t\tif (data === null || !isCurrent()) return\n\n\t\t\t\tconst { syncUrl, intervalMs } = syncSettingsFrom(data)\n\n\t\t\t\tthis.padSync().configure({ syncUrl, intervalMs })\n\t\t\t\tthis.padSync().installLifecycleHandlers()\n\t\t\t\tif (syncUrl) {\n\t\t\t\t\tthis.padSync().start()\n\t\t\t\t}\n\n\t\t\t\tconst contentUrl = contentUrlFrom(data)\n\n\t\t\t\tconst { isContentView, externalUrl } = contentViewFrom(data)\n\t\t\t\tif (isContentView) {\n\t\t\t\t\tthis.externalOpenUrl = externalUrl\n\t\t\t\t\tthis.contentUrl = contentUrl\n\t\t\t\t\tthis.contentMode = 'content'\n\t\t\t\t\tthis.markLoaded()\n\t\t\t\t\t// Draw the content view while its body loads.\n\t\t\t\t\tvoid this.loadContent()\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tthis.iframeSrc = padUrlFrom(data)\n\t\t\t\tthis.markLoaded()\n\t\t\t} catch (error) {\n\t\t\t\tif (!isCurrent()) return\n\t\t\t\tthis.loadError = messageOf(error, 'Could not load pad.')\n\t\t\t\t// The server intentionally does not disclose why this id is unavailable.\n\t\t\t\tthis.maybeStaleFileId = this.resolvedFileId !== null\n\t\t\t\t\t&& Boolean(error) && error.status === 404 && !error.code\n\t\t\t\tthis.canRetryOpen = isRetryableOpenError(error)\n\t\t\t\t// Recovery may resolve only the same path that failed to open.\n\t\t\t\tlet recoveryFileId = this.resolvedFileId\n\t\t\t\tthis.recoveryPath = openPath\n\t\t\t\tif (recoveryFileId === null && !byPublicUrl && isMissingBindingError(error)) {\n\t\t\t\t\trecoveryFileId = await this.resolveRecoveryFileId(openPath)\n\t\t\t\t\t// A late lookup must not attach recovery to a newer Viewer item.\n\t\t\t\t\tif (!isCurrent()) return\n\t\t\t\t}\n\t\t\t\tthis.recoveryFileId = recoveryFileId\n\t\t\t\tthis.canRecover = isMissingBindingError(error)\n\t\t\t\t\t&& recoveryFileId !== null\n\t\t\t\t\t&& !byPublicUrl\n\t\t\t\tif (this.canRecover) {\n\t\t\t\t\tthis.fetchOriginalPadHint(isCurrent, afterClick)\n\t\t\t\t} else if (afterClick) {\n\t\t\t\t\tthis.handFocusToErrorCard()\n\t\t\t\t}\n\t\t\t\tthis.markLoaded()\n\t\t\t} finally {\n\t\t\t\tif (!isCurrent()) return\n\t\t\t\tthis.isLoading = false\n\t\t\t}\n\t\t},\n\t\t/** $afterClick: the focus waits for the card's final actions. */\n\t\tasync fetchOriginalPadHint(isCurrent, afterClick = false) {\n\t\t\tif (this.recoveryFileId === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.isCheckingOriginal = true\n\t\t\ttry {\n\t\t\t\tconst hint = await apiFindOriginalPad(this.recoveryFileId)\n\t\t\t\tif (!isCurrent()) return\n\t\t\t\tif (hint && hint.found === true && typeof hint.viewer_url === 'string' && hint.viewer_url !== '') {\n\t\t\t\t\tthis.originalPad = {\n\t\t\t\t\t\tviewerUrl: hint.viewer_url,\n\t\t\t\t\t\tpath: typeof hint.path === 'string' ? hint.path : '',\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Recovery remains available without an original-file hint.\n\t\t\t} finally {\n\t\t\t\tif (isCurrent()) {\n\t\t\t\t\tthis.isCheckingOriginal = false\n\t\t\t\t\tif (afterClick) {\n\t\t\t\t\t\tthis.handFocusToErrorCard()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tasync recoverFromSnapshot() {\n\t\t\tif (!this.canRecover || this.isRecovering || this.recoveryFileId === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.isRecovering = true\n\t\t\ttry {\n\t\t\t\tawait apiRecoverFromSnapshot(this.recoveryFileId, this.recoveryPath)\n\t\t\t\tthis.loadError = ''\n\t\t\t\tthis.canRecover = false\n\t\t\t\tawait this.resolveOpenUrl(true)\n\t\t\t} catch (error) {\n\t\t\t\t// No answer: the pad may be set up by now, and another\n\t\t\t\t// recovery would meet it. Opening tells, and is safe to repeat.\n\t\t\t\tif (error && error.unanswered === true) {\n\t\t\t\t\tawait this.resolveOpenUrl(true)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// Enabled again before the card is drawn, so the focus lands.\n\t\t\t\tthis.isRecovering = false\n\t\t\t\tthis.loadError = messageOf(error, 'Could not load pad.')\n\t\t\t\t// The clicked button lost the focus while it was disabled.\n\t\t\t\tthis.handFocusToErrorCard()\n\t\t\t} finally {\n\t\t\t\tthis.isRecovering = false\n\t\t\t}\n\t\t},\n\t\t/** Refresh content through an endpoint that re-checks access. */\n\t\tasync loadContent() {\n\t\t\tconst openGeneration = this.resolveGeneration\n\t\t\tthis.contentGeneration += 1\n\t\t\tconst generation = this.contentGeneration\n\t\t\tconst isCurrent = () => generation === this.contentGeneration\n\t\t\t\t&& openGeneration === this.resolveGeneration\n\n\t\t\tif (this.contentUrl === '') {\n\t\t\t\tthis.contentState = 'error'\n\t\t\t\tthis.contentError = translate('The server did not say where to load this pad from.')\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis._contentAbort?.abort()\n\t\t\t// Abort superseded content refreshes.\n\t\t\tconst abort = typeof AbortController === 'function' ? new AbortController() : null\n\t\t\tthis._contentAbort = abort\n\t\t\tthis.contentState = 'loading'\n\t\t\tthis.contentError = ''\n\n\t\t\ttry {\n\t\t\t\tconst content = await loadPadContent(this.contentUrl, { signal: abort?.signal })\n\t\t\t\tif (!isCurrent()) return\n\t\t\t\tthis.content = content\n\t\t\t\tthis.contentLoaded = true\n\t\t\t\tthis.contentState = 'ready'\n\t\t\t} catch (error) {\n\t\t\t\tif (!isCurrent() || (error && error.name === 'AbortError')) return\n\t\t\t\tthis.contentState = 'error'\n\t\t\t\tthis.contentError = messageOf(error, translate('Could not load the pad content.'))\n\t\t\t}\n\t\t},\n\t\trenderContentView(createElement, options) {\n\t\t\tconst extraActions = Array.isArray(options.actions) ? options.actions : []\n\t\t\tconst isBusy = this.contentState === 'loading'\n\n\t\t\treturn createElement('div', { class: 'epnc-pad-doc' }, [\n\t\t\t\tcreateElement('div', { class: 'epnc-pad-doc__inner' }, [\n\t\t\t\t\tcreateElement('div', { class: 'epnc-pad-doc__toolbar' }, [\n\t\t\t\t\t\t(this.contentState === 'error' && this.contentLoaded)\n\t\t\t\t\t\t\t? createElement('span', { class: 'epnc-pad-doc__toolbar-error' },\n\t\t\t\t\t\t\t\tthis.contentError || translate('Could not load the pad content.'))\n\t\t\t\t\t\t\t: null,\n\t\t\t\t\t\tcreateElement('button', {\n\t\t\t\t\t\t\tclass: 'button epnc-pad-doc__refresh',\n\t\t\t\t\t\t\tattrs: { type: 'button', disabled: isBusy },\n\t\t\t\t\t\t\ton: { click: () => { void this.loadContent() } },\n\t\t\t\t\t\t}, isBusy ? translate('Refreshing...') : translate('Refresh')),\n\t\t\t\t\t\t...extraActions,\n\t\t\t\t\t]),\n\t\t\t\t\tthis.renderContentBody(createElement),\n\t\t\t\t]),\n\t\t\t])\n\t\t},\n\t\trenderContentBody(createElement) {\n\t\t\tif (this.contentLoaded && this.contentState !== 'ready') {\n\t\t\t\treturn this.renderContentText(createElement)\n\t\t\t}\n\t\t\tif (this.contentState === 'error') {\n\t\t\t\treturn createElement('div', { class: 'epnc-pad-doc__text epnc-pad-doc__status' }, [\n\t\t\t\t\tcreateElement('div', {},\n\t\t\t\t\t\tthis.contentError || translate('Could not load the pad content.')),\n\t\t\t\t\tcreateElement('button', {\n\t\t\t\t\t\tclass: 'button primary',\n\t\t\t\t\t\tattrs: { type: 'button' },\n\t\t\t\t\t\ton: { click: () => { void this.loadContent() } },\n\t\t\t\t\t}, translate('Try again')),\n\t\t\t\t])\n\t\t\t}\n\t\t\tif (this.contentState !== 'ready') {\n\t\t\t\treturn createElement('div', { class: 'epnc-pad-doc__text epnc-pad-doc__status' }, translate('Loading pad content...'))\n\t\t\t}\n\t\t\treturn this.renderContentText(createElement)\n\t\t},\n\t\trenderContentText(createElement) {\n\t\t\tif (this.content.isEmpty) {\n\t\t\t\treturn createElement('div', { class: 'epnc-pad-doc__text epnc-pad-doc__status' }, translate('This pad is still empty.'))\n\t\t\t}\n\t\t\treturn createElement('div', {\n\t\t\t\tclass: 'epnc-pad-doc__text epnc-pad-doc__text--html',\n\t\t\t\tdomProps: { innerHTML: this.content.html },\n\t\t\t})\n\t\t},\n\t},\n\tbeforeDestroy() {\n\t\tthis.resolveGeneration += 1\n\t\t// Abort work that could otherwise finish after teardown.\n\t\tthis._openAbort?.abort()\n\t\tthis._contentAbort?.abort()\n\t\tthis.teardownSync()\n\t},\n\tbeforeUnmount() {\n\t\tthis.resolveGeneration += 1\n\t\tthis._openAbort?.abort()\n\t\tthis._contentAbort?.abort()\n\t\tthis.teardownSync()\n\t},\n\trender(createElement) {\n\t\tif (this.loadError) {\n\t\t\tconst cardChildren = [\n\t\t\t\tcreateElement('div', { class: 'epnc-native-error-title' }, translate('Could not open pad')),\n\t\t\t\t// Read out when it appears, not only when someone looks.\n\t\t\t\tcreateElement('div', { class: 'epnc-native-error-message', attrs: { role: 'alert' } }, this.loadError),\n\t\t\t]\n\t\t\tif (this.maybeStaleFileId) {\n\t\t\t\tcardChildren.push(\n\t\t\t\t\tcreateElement('div', { class: 'epnc-native-error-message' },\n\t\t\t\t\t\ttranslate('This file may have been moved or replaced since the list was loaded. Reload the page and open it again.')),\n\t\t\t\t)\n\t\t\t}\n\t\t\tif (this.canRetryOpen) {\n\t\t\t\tcardChildren.push(\n\t\t\t\t\tcreateElement('button', {\n\t\t\t\t\t\tclass: 'button primary epnc-native-error-action',\n\t\t\t\t\t\tattrs: { type: 'button' },\n\t\t\t\t\t\ton: { click: () => { void this.resolveOpenUrl(true) } },\n\t\t\t\t\t}, translate('Try again')),\n\t\t\t\t)\n\t\t\t}\n\t\t\tif (this.canRecover) {\n\t\t\t\tif (this.isCheckingOriginal) {\n\t\t\t\t\t// Wait before choosing the primary recovery action.\n\t\t\t\t\tcardChildren.push(\n\t\t\t\t\t\tcreateElement('div', { class: 'epnc-native-error-message' },\n\t\t\t\t\t\t\ttranslate('Checking for the original pad...')),\n\t\t\t\t\t)\n\t\t\t\t} else if (this.originalPad) {\n\t\t\t\t\tcardChildren.push(\n\t\t\t\t\t\tcreateElement('div', { class: 'epnc-native-error-message' },\n\t\t\t\t\t\t\ttranslate('This file looks like a copy of an existing .pad file in your account. Open the original to keep editing the linked pad, or create a new pad to fork the content stored in this file.')),\n\t\t\t\t\t\tcreateElement('a', {\n\t\t\t\t\t\t\tclass: 'button primary epnc-native-error-action',\n\t\t\t\t\t\t\tattrs: { href: this.originalPad.viewerUrl },\n\t\t\t\t\t\t}, translate('Open the original .pad file')),\n\t\t\t\t\t\tcreateElement('button', {\n\t\t\t\t\t\t\tclass: 'button epnc-native-error-action',\n\t\t\t\t\t\t\tattrs: { type: 'button', disabled: this.isRecovering },\n\t\t\t\t\t\t\ton: { click: () => { void this.recoverFromSnapshot() } },\n\t\t\t\t\t\t}, this.isRecovering ? translate('Creating new pad...') : translate('Create new pad from this file')),\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tcardChildren.push(\n\t\t\t\t\t\tcreateElement('div', { class: 'epnc-native-error-message' },\n\t\t\t\t\t\t\ttranslate(\"We couldn't find a matching pad in this Nextcloud. You can create a new pad from the text stored in this file; from then on, opening this file will load the new pad.\")),\n\t\t\t\t\t\tcreateElement('button', {\n\t\t\t\t\t\t\tclass: 'button primary epnc-native-error-action',\n\t\t\t\t\t\t\tattrs: { type: 'button', disabled: this.isRecovering },\n\t\t\t\t\t\t\ton: { click: () => { void this.recoverFromSnapshot() } },\n\t\t\t\t\t\t}, this.isRecovering ? translate('Creating new pad...') : translate('Create new pad from this file')),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn createElement('div', { class: 'epnc-native-status epnc-native-status--error' }, [\n\t\t\t\tcreateElement('div', { class: 'epnc-native-error-card', ref: 'errorCard' }, cardChildren),\n\t\t\t])\n\t\t}\n\t\tif (this.contentMode === 'content') {\n\t\t\treturn this.renderContentView(createElement, this.externalOpenUrl === ''\n\t\t\t\t? {}\n\t\t\t\t: {\n\t\t\t\t\tactions: [\n\t\t\t\t\t\tcreateElement('a', {\n\t\t\t\t\t\t\tclass: 'button epnc-pad-doc__link',\n\t\t\t\t\t\t\tattrs: {\n\t\t\t\t\t\t\t\thref: this.externalOpenUrl,\n\t\t\t\t\t\t\t\ttarget: '_blank',\n\t\t\t\t\t\t\t\trel: 'noopener noreferrer',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}, translate('Open original pad')),\n\t\t\t\t\t],\n\t\t\t\t})\n\t\t}\n\t\tif (this.isLoading || !this.iframeSrc) {\n\t\t\treturn createElement('div', { class: 'epnc-native-status' }, 'Loading pad...')\n\t\t}\n\n\t\treturn createElement('div', { class: 'epnc-native-shell' }, [\n\t\t\t// Nextcloud inspects direct iframe children, so keep this wrapper same-origin.\n\t\t\tcreateElement('iframe', {\n\t\t\t\tattrs: { srcdoc: buildPadFrameSrcdoc(this.iframeSrc), title: 'Etherpad' },\n\t\t\t\t// Etherpad provides its own loading state inside the nested iframe.\n\t\t\t\ton: { load: () => this.markLoaded(), error: () => this.markLoaded() },\n\t\t\t\tclass: 'epnc-native-iframe',\n\t\t\t}),\n\t\t])\n\t},\n}\n\nexport default component\n","/**\n * SPDX-License-Identifier: AGPL-3.0-or-later\n * Copyright (c) 2026 Jacob Bühler\n */\n\n/** Registers the pad MIME handler with the Viewer. */\n\nimport { registerHandler } from '@nextcloud/viewer'\n\nimport { MIME, VIEWER_HANDLER_ID } from './lib/constants.js'\nimport component from './viewer-main.js'\n\n// The options object itself, not a loader for it: the Viewer assigns its\n// Mime mixin onto what it is given and registers it under `component.name`,\n// and a function silently takes neither.\nregisterHandler({ id: VIEWER_HANDLER_ID, mimes: [MIME], component })\n"],"names":["registerHandler","handler","validateHandler","id","mimes","mimesAliases","component","RESOLVE_CACHE","RESOLVE_CACHE_MAX_ENTRIES","RESOLVE_CACHE_TTL_MS","apiResolvePadByPath","path","bypassCache","cacheKey","cached","getResolveCache","url","ocGenerateUrl","APP_ID","request","fetchJson","error","setResolveCache","apiFindOriginalPad","fileId","endpoint","apiRecoverFromSnapshot","result","fetchJsonWithTimeout","ocRequestToken","oldestKey","options","fallbackMessage","escapeAttribute","value","ALLOWED_PAD_URL_SCHEMES","isSafePadUrl","parsed","SRC_DOC_CSP","buildPadFrameSrcdoc","safeUrl","messageOf","fallback","requestErrorMessage","translate","parsePadPathFromDavHref","normalizeDir","dir","joinPath","name","normalizedDir","isPadName","info","infoPath","baseName","infoDir","combined","urlDir","fromDir","candidates","candidate","numeric","init","assertOpenPayload","headers","initOptions","announceMigratedStatus","data","body","openPath","resolved","card","handFocusTo","createPadSync","afterClick","generation","isCurrent","abort","publicToken","parsePublicShareTokenFromLocation","byPublicUrl","openPostHeaders","openWithFrontmatterRecovery","signal","byIdBody","byPathBody","syncUrl","intervalMs","syncSettingsFrom","contentUrl","contentUrlFrom","isContentView","externalUrl","contentViewFrom","padUrlFrom","isRetryableOpenError","recoveryFileId","isMissingBindingError","hint","openGeneration","content","loadPadContent","createElement","extraActions","isBusy","cardChildren","VIEWER_HANDLER_ID","MIME"],"mappings":"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","x_google_ignoreList":[0]}
\ No newline at end of file
+{"version":3,"file":"etherpad_nextcloud-viewer-init.mjs","sources":["../node_modules/@nextcloud/viewer/dist/index.js","../src/lib/api-client.js","../src/lib/pad-frame-srcdoc.js","../src/viewer-main.js","../src/viewer-init.js"],"sourcesContent":["/*!\n * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n/**\n * Register a new handler for the viewer.\n * This needs to be called before the viewer is initialized to ensure the handler is available.\n * So this should be called from an initialization script (`OCP\\Util::addInitScript`).\n *\n * @param handler - The handler to register\n * @throws Error if the handler is invalid\n */\nexport function registerHandler(handler) {\n validateHandler(handler);\n window._oca_viewer_handlers ?? (window._oca_viewer_handlers = new Map());\n if (window._oca_viewer_handlers.has(handler.id)) {\n console.warn(`Handler with id ${handler.id} is already registered.`);\n return;\n }\n window._oca_viewer_handlers.set(handler.id, handler);\n}\n/**\n * Validate the handler object.\n *\n * @param handler - The handler to validate\n */\nfunction validateHandler(handler) {\n const { id, mimes, mimesAliases, component } = handler;\n // checking valid handler id\n if (!id || id.trim() === '' || typeof id !== 'string') {\n throw new Error('The handler does not have a valid id');\n }\n // Nothing available to process! Failure\n if ((!mimes || !Array.isArray(mimes)) && !mimesAliases) {\n throw new Error('Handler needs a valid mime array or mimesAliases');\n }\n // checking valid handler component data\n if ((!component || (typeof component !== 'object' && typeof component !== 'function'))) {\n throw new Error('The handler does not have a valid component');\n }\n}\n","/**\n * SPDX-License-Identifier: AGPL-3.0-or-later\n * Copyright (c) 2026 Jacob Bühler\n */\n\n/** Client helpers for pad resolution and snapshot recovery. */\n\nimport { APP_ID } from './constants.js'\nimport { ocGenerateUrl, ocRequestToken } from './oc-compat.js'\nimport { fetchJsonWithTimeout } from './fetch-helpers.js'\n\nconst RESOLVE_CACHE = new Map()\nconst RESOLVE_CACHE_MAX_ENTRIES = 50\nconst RESOLVE_CACHE_TTL_MS = 5 * 60 * 1000\n\n/**\n * Resolve a path to its pad metadata.\n *\n * Bypass the cache before a write. An entry is up to five minutes old, and\n * in five minutes a file can be moved and another `.pad` created at the\n * same path: for a read that is stale, for recovery it would bind a pad to\n * the wrong file.\n */\nexport const apiResolvePadByPath = async (path, { bypassCache = false } = {}) => {\n\tconst cacheKey = 'path:' + String(path)\n\tconst cached = bypassCache ? null : getResolveCache(cacheKey)\n\tif (cached !== null) {\n\t\treturn cached\n\t}\n\tconst url = ocGenerateUrl('/apps/' + APP_ID + '/api/v1/pads/resolve') + '?file=' + encodeURIComponent(path)\n\tconst request = fetchJson(url, {\n\t\tmethod: 'GET',\n\t\theaders: { Accept: 'application/json' },\n\t}, 'Pad resolve by path failed.')\n\t\t.catch((error) => {\n\t\t\tRESOLVE_CACHE.delete(cacheKey)\n\t\t\tthrow error\n\t\t})\n\tsetResolveCache(cacheKey, request)\n\treturn request\n}\n\nexport const apiFindOriginalPad = async (fileId) => {\n\tconst endpoint = ocGenerateUrl('/apps/' + APP_ID + '/api/v1/pads/find-original/' + encodeURIComponent(String(fileId)))\n\treturn fetchJson(endpoint, {\n\t\tmethod: 'GET',\n\t\theaders: { Accept: 'application/json' },\n\t}, 'Lookup failed.')\n}\n\nexport const apiRecoverFromSnapshot = async (fileId, path = '') => {\n\tconst endpoint = ocGenerateUrl('/apps/' + APP_ID + '/api/v1/pads/recover-from-snapshot/' + encodeURIComponent(String(fileId)))\n\t// A client timeout would not stop the server-side provisioning work.\n\tconst result = await fetchJsonWithTimeout(endpoint, {\n\t\tmethod: 'POST',\n\t\theaders: {\n\t\t\tAccept: 'application/json',\n\t\t\trequesttoken: ocRequestToken(),\n\t\t},\n\t}, { fallbackMessage: 'Recovery failed.', timeoutMs: null })\n\t// Only this path: flushing every entry would throw away answers for\n\t// unrelated files the session has already looked up.\n\tif (typeof path === 'string' && path !== '') {\n\t\tRESOLVE_CACHE.delete('path:' + path)\n\t}\n\treturn result\n}\nconst getResolveCache = (cacheKey) => {\n\tconst cached = RESOLVE_CACHE.get(cacheKey)\n\tif (!cached) {\n\t\treturn null\n\t}\n\tif ((Date.now() - cached.createdAt) > RESOLVE_CACHE_TTL_MS) {\n\t\tRESOLVE_CACHE.delete(cacheKey)\n\t\treturn null\n\t}\n\treturn cached.request\n}\n\nconst setResolveCache = (cacheKey, request) => {\n\tif (!RESOLVE_CACHE.has(cacheKey) && RESOLVE_CACHE.size >= RESOLVE_CACHE_MAX_ENTRIES) {\n\t\tconst oldestKey = RESOLVE_CACHE.keys().next().value\n\t\tif (oldestKey !== undefined) {\n\t\t\tRESOLVE_CACHE.delete(oldestKey)\n\t\t}\n\t}\n\tRESOLVE_CACHE.set(cacheKey, {\n\t\tcreatedAt: Date.now(),\n\t\trequest,\n\t})\n}\n\nconst fetchJson = async (url, options, fallbackMessage) =>\n\tfetchJsonWithTimeout(url, options, { fallbackMessage })\n","/**\n * SPDX-License-Identifier: AGPL-3.0-or-later\n * Copyright (c) 2026 Jacob Bühler\n */\n\nconst escapeAttribute = (value) => String(value || '')\n\t.replace(/&/g, '&')\n\t.replace(/\"/g, '"')\n\t.replace(//g, '>')\n\nconst ALLOWED_PAD_URL_SCHEMES = ['http:', 'https:']\n\nconst isSafePadUrl = (url) => {\n\ttry {\n\t\tconst parsed = new URL(String(url || ''))\n\t\treturn ALLOWED_PAD_URL_SCHEMES.includes(parsed.protocol)\n\t\t\t&& parsed.username === ''\n\t\t\t&& parsed.password === ''\n\t} catch {\n\t\treturn false\n\t}\n}\n\nconst SRC_DOC_CSP = \"default-src 'none'; frame-src http: https:; style-src 'unsafe-inline'\"\n\nexport const buildPadFrameSrcdoc = (url) => {\n\tconst safeUrl = isSafePadUrl(url) ? escapeAttribute(url) : ''\n\treturn '
'\n\t+ '
'\n\t+ ''\n\t+ '
'\n}\n","/**\n * SPDX-License-Identifier: AGPL-3.0-or-later\n * Copyright (c) 2026 Jacob Bühler\n */\n\n/** Nextcloud Viewer component for editable pads and read-only pad content. */\n\nimport { APP_ID } from './lib/constants.js'\nimport { apiFindOriginalPad, apiRecoverFromSnapshot, apiResolvePadByPath } from './lib/api-client.js'\nimport { fetchJsonWithTimeout, isUnanswered, requestErrorMessage } from './lib/fetch-helpers.js'\nimport { handFocusTo } from './lib/hand-focus.js'\nimport { ocGenerateUrl, ocRequestToken, translate } from './lib/oc-compat.js'\nimport { createPadSync } from './lib/pad-sync.js'\nimport { loadPadContent } from './lib/pad-content.js'\nimport { assertOpenPayload, contentUrlFrom, contentViewFrom, isMissingBindingError, isRetryableOpenError, openWithFrontmatterRecovery, padUrlFrom, syncSettingsFrom } from './lib/pad-open-flow.js'\nimport { buildPadFrameSrcdoc } from './lib/pad-frame-srcdoc.js'\nimport { isPadName, parsePadPathFromDavHref, parsePublicShareTokenFromLocation } from './lib/urls.js'\n\n// When nothing came back: our own sentence, not the browser's English.\nconst messageOf = (error, fallback) => requestErrorMessage(error, translate('Nextcloud did not answer. Check your connection and try again.'), fallback)\n\nconst component = {\n\tname: 'EtherpadNextcloudViewer',\n\tprops: {\n\t\tfilename: { type: String, required: false, default: '' },\n\t\tbasename: { type: String, required: false, default: '' },\n\t\tsource: { type: String, required: false, default: '' },\n\t\tfileid: { type: [String, Number], required: false, default: null },\n\t\tfileId: { type: [String, Number], required: false, default: null },\n\t\tfileInfo: { type: Object, required: false, default: null },\n\t},\n\tdata() {\n\t\treturn {\n\t\t\tiframeSrc: '',\n\t\t\tisLoading: true,\n\t\t\tloadError: '',\n\t\t\tcanRecover: false,\n\t\t\tcanRetryOpen: false,\n\t\t\tmaybeStaleFileId: false,\n\t\t\t// Recovery may resolve this from the path when Viewer supplies no id.\n\t\t\trecoveryFileId: null,\n\t\t\t// Kept with the id so recovery invalidates the matching cache entry.\n\t\t\trecoveryPath: '',\n\t\t\tisRecovering: false,\n\t\t\tisCheckingOriginal: false,\n\t\t\toriginalPad: null,\n\t\t\texternalOpenUrl: '',\n\t\t\tcontentMode: '',\n\t\t\tcontentUrl: '',\n\t\t\tcontentState: 'idle',\n\t\t\tcontentError: '',\n\t\t\tcontent: { html: '', isEmpty: false },\n\t\t\t// A refresh keeps previously loaded content visible.\n\t\t\tcontentLoaded: false,\n\t\t\t// Refreshes supersede each other without superseding the open.\n\t\t\tcontentGeneration: 0,\n\t\t\tresolveGeneration: 0,\n\t\t}\n\t},\n\tcomputed: {\n\t\tsourcePath() {\n\t\t\t// Whitespace inside the DAV URL is encoded; only surrounding noise is trimmed.\n\t\t\tconst value = typeof this.source === 'string' ? this.source.trim() : ''\n\t\t\tif (!value) return ''\n\t\t\treturn parsePadPathFromDavHref(value) || ''\n\t\t},\n\t\tfilePath() {\n\t\t\t// Whitespace is part of the name, not noise: `Notes .pad` and\n\t\t\t// `Notes.pad` are two files, and trimming here opened the wrong one.\n\t\t\tconst normalizeDir = (dir) => {\n\t\t\t\tif (!dir || dir === '/') return '/'\n\t\t\t\treturn dir.startsWith('/') ? dir : ('/' + dir)\n\t\t\t}\n\t\t\tconst joinPath = (dir, name) => {\n\t\t\t\tif (!name) return ''\n\t\t\t\tif (name.startsWith('/')) return name\n\t\t\t\tconst normalizedDir = normalizeDir(dir)\n\t\t\t\treturn normalizedDir === '/' ? '/' + name : normalizedDir + '/' + name\n\t\t\t}\n\t\t\tif (isPadName(this.sourcePath)) return this.sourcePath\n\n\t\t\tconst info = this.fileInfo && typeof this.fileInfo === 'object' ? this.fileInfo : null\n\t\t\tconst infoPath = info && typeof info.path === 'string' ? info.path : ''\n\t\t\tif (isPadName(infoPath)) return infoPath.startsWith('/') ? infoPath : ('/' + infoPath)\n\n\t\t\tconst baseName = String(this.filename || this.basename || (info && (info.name || info.basename)) || '')\n\t\t\tif (!baseName) return ''\n\n\t\t\tconst infoDir = info && typeof info.dirname === 'string' ? info.dirname : ''\n\t\t\tif (infoDir) {\n\t\t\t\tconst combined = joinPath(infoDir, baseName)\n\t\t\t\tif (isPadName(combined)) return combined\n\t\t\t}\n\n\t\t\tconst params = new URLSearchParams(window.location.search || '')\n\t\t\tconst urlDir = params.get('dir') || '/'\n\t\t\tconst fromDir = joinPath(urlDir, baseName)\n\t\t\tif (isPadName(fromDir)) return fromDir\n\t\t\treturn '/' + baseName\n\t\t},\n\t\topenKey() {\n\t\t\treturn `${this.resolvedFileId === null ? '' : this.resolvedFileId}::${this.filePath}`\n\t\t},\n\t\tresolvedFileId() {\n\t\t\tconst candidates = [this.fileid, this.fileId, this.fileInfo && (this.fileInfo.fileid || this.fileInfo.fileId || this.fileInfo.id)]\n\t\t\tfor (const candidate of candidates) {\n\t\t\t\tconst numeric = Number(candidate)\n\t\t\t\tif (Number.isFinite(numeric) && numeric > 0) return numeric\n\t\t\t}\n\t\t\t// Route ids can outlive the item shown after Viewer navigation.\n\t\t\treturn null\n\t\t},\n\t},\n\twatch: {\n\t\t// A file swap changes path and id together; open it only once.\n\t\topenKey: { immediate: true, handler() { void this.resolveOpenUrl() } },\n\t},\n\tmethods: {\n\t\tasync fetchOpenPayload(url, init = {}) {\n\t\t\treturn assertOpenPayload(await fetchJsonWithTimeout(url, Object.assign({ method: 'GET' }, init)))\n\t\t},\n\t\tasync initializeMissingFrontmatter() {\n\t\t\tconst headers = {\n\t\t\t\tAccept: 'application/json',\n\t\t\t\trequesttoken: ocRequestToken(),\n\t\t\t}\n\n\t\t\t// A client timeout would not stop the server-side provisioning work.\n\t\t\tconst initOptions = { timeoutMs: null, fallbackMessage: 'Pad initialization failed.' }\n\n\t\t\tconst announceMigratedStatus = (data) => {\n\t\t\t\tif (data && data.status === 'migrated_from_legacy') {\n\t\t\t\t\tconsole.info('Legacy Ownpad .pad migrated to managed format on first open.')\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (this.resolvedFileId !== null) {\n\t\t\t\tconst url = ocGenerateUrl('/apps/' + APP_ID + '/api/v1/pads/initialize-by-id/' + encodeURIComponent(String(this.resolvedFileId)))\n\t\t\t\tconst data = await fetchJsonWithTimeout(url, { method: 'POST', headers }, initOptions)\n\t\t\t\tannounceMigratedStatus(data)\n\t\t\t\treturn data\n\t\t\t}\n\n\t\t\tif (!this.filePath) {\n\t\t\t\tthrow new Error('Pad initialization failed: missing file path.')\n\t\t\t}\n\n\t\t\tconst body = new URLSearchParams()\n\t\t\tbody.set('file', this.filePath)\n\t\t\tconst url = ocGenerateUrl('/apps/' + APP_ID + '/api/v1/pads/initialize')\n\t\t\tconst data = await fetchJsonWithTimeout(url, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: Object.assign({}, headers, {\n\t\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',\n\t\t\t\t}),\n\t\t\t\tbody: body.toString(),\n\t\t\t}, initOptions)\n\t\t\tannounceMigratedStatus(data)\n\t\t\treturn data\n\t\t},\n\t\t/** Resolve a fresh id for recovery without changing the opened file. */\n\t\tasync resolveRecoveryFileId(openPath) {\n\t\t\ttry {\n\t\t\t\tconst resolved = await apiResolvePadByPath(openPath, { bypassCache: true })\n\t\t\t\tconst id = Number(resolved && resolved.file_id)\n\t\t\t\treturn Number.isFinite(id) && id > 0 ? id : null\n\t\t\t} catch {\n\t\t\t\treturn null\n\t\t\t}\n\t\t},\n\t\tmarkLoaded() {\n\t\t\tthis.$emit('update:loaded', true)\n\t\t},\n\t\t/**\n\t\t * See handFocusTo(). On the next tick, once the card is drawn: call\n\t\t * it after setting what the card shows.\n\t\t */\n\t\thandFocusToErrorCard() {\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tconst card = this.$refs.errorCard\n\t\t\t\tif (card instanceof HTMLElement) {\n\t\t\t\t\thandFocusTo(card, card.querySelector('.epnc-native-error-message'))\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t\t// Keep the sync controller non-reactive and available to the immediate watcher.\n\t\tpadSync() {\n\t\t\tif (!this._padSync) {\n\t\t\t\tthis._padSync = createPadSync({ requestToken: () => ocRequestToken() })\n\t\t\t}\n\t\t\treturn this._padSync\n\t\t},\n\t\t// Do not create a controller solely to tear it down.\n\t\tteardownSync() {\n\t\t\tif (!this._padSync) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis._padSync.fireAndForget(true, true)\n\t\t\tthis._padSync.stop()\n\t\t\tthis._padSync.removeLifecycleHandlers()\n\t\t},\n\t\t/** $afterClick: see handFocusToErrorCard(). */\n\t\tasync resolveOpenUrl(afterClick = false) {\n\t\t\tconst generation = ++this.resolveGeneration\n\t\t\tconst isCurrent = () => generation === this.resolveGeneration\n\t\t\t// Discarding a result is insufficient: a completed request may mint a session.\n\t\t\tthis._openAbort?.abort()\n\t\t\tconst abort = typeof AbortController === 'function' ? new AbortController() : null\n\t\t\tthis._openAbort = abort\n\n\t\t\tthis.isLoading = true\n\t\t\tthis.loadError = ''\n\t\t\tthis.canRecover = false\n\t\t\tthis.canRetryOpen = false\n\t\t\tthis.maybeStaleFileId = false\n\t\t\tthis.recoveryFileId = null\n\t\t\tthis.recoveryPath = ''\n\t\t\tthis.isCheckingOriginal = false\n\t\t\tthis.originalPad = null\n\t\t\tthis.iframeSrc = ''\n\t\t\tthis.externalOpenUrl = ''\n\t\t\tthis.contentMode = ''\n\t\t\tthis.contentUrl = ''\n\t\t\tthis.contentState = 'idle'\n\t\t\tthis.contentError = ''\n\t\t\tthis.content = { html: '', isEmpty: false }\n\t\t\tthis.contentLoaded = false\n\t\t\tthis._contentAbort?.abort()\n\t\t\t// Do not construct a sync controller while resetting viewer state.\n\t\t\tif (this._padSync) {\n\t\t\t\tthis._padSync.stop()\n\t\t\t\tthis._padSync.configure({ syncUrl: '' })\n\t\t\t}\n\n\t\t\tif (!this.filePath) {\n\t\t\t\tif (!isCurrent()) return\n\t\t\t\tthis.loadError = 'No .pad file selected.'\n\t\t\t\tthis.isLoading = false\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Viewer props may change before the watcher starts the next open.\n\t\t\tconst openPath = this.filePath\n\t\t\tconst publicToken = parsePublicShareTokenFromLocation()\n\t\t\tconst byPublicUrl = (() => {\n\t\t\t\tif (!publicToken) return ''\n\t\t\t\tconst url = new URL(ocGenerateUrl('/apps/' + APP_ID + '/api/v1/public/open/' + encodeURIComponent(publicToken)), window.location.origin)\n\t\t\t\t// The public endpoint rejects conflicting id and path locators.\n\t\t\t\tif (this.resolvedFileId !== null) {\n\t\t\t\t\turl.searchParams.set('fileId', String(this.resolvedFileId))\n\t\t\t\t} else {\n\t\t\t\t\turl.searchParams.set('file', openPath)\n\t\t\t\t}\n\t\t\t\treturn url.toString()\n\t\t\t})()\n\t\t\tconst openPostHeaders = {\n\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',\n\t\t\t\trequesttoken: ocRequestToken(),\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst fetchOpenData = async () => {\n\t\t\t\t\t// Never retry a refused id by path: it could identify a different file.\n\t\t\t\t\tconst signal = abort ? abort.signal : undefined\n\t\t\t\t\tif (byPublicUrl) {\n\t\t\t\t\t\treturn await this.fetchOpenPayload(byPublicUrl, { signal })\n\t\t\t\t\t}\n\t\t\t\t\t// One way in, chosen once. An open by id that retries by\n\t\t\t\t\t// path is how a refused id ended up opening whatever the\n\t\t\t\t\t// path pointed at.\n\t\t\t\t\tif (this.resolvedFileId !== null) {\n\t\t\t\t\t\tconst byIdBody = new URLSearchParams()\n\t\t\t\t\t\tbyIdBody.set('fileId', String(this.resolvedFileId))\n\t\t\t\t\t\treturn await this.fetchOpenPayload(\n\t\t\t\t\t\t\tocGenerateUrl('/apps/' + APP_ID + '/api/v1/pads/open-by-id'),\n\t\t\t\t\t\t\t{ method: 'POST', headers: openPostHeaders, body: byIdBody.toString(), signal },\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t\tconst byPathBody = new URLSearchParams()\n\t\t\t\t\tbyPathBody.set('file', openPath)\n\t\t\t\t\treturn await this.fetchOpenPayload(\n\t\t\t\t\t\tocGenerateUrl('/apps/' + APP_ID + '/api/v1/pads/open'),\n\t\t\t\t\t\t{ method: 'POST', headers: openPostHeaders, body: byPathBody.toString(), signal },\n\t\t\t\t\t)\n\t\t\t\t}\n\n\t\t\t\tconst data = await openWithFrontmatterRecovery({\n\t\t\t\t\topen: fetchOpenData,\n\t\t\t\t\tinitialize: () => this.initializeMissingFrontmatter(),\n\t\t\t\t\tstillWanted: isCurrent,\n\t\t\t\t})\n\t\t\t\tif (data === null || !isCurrent()) return\n\n\t\t\t\tconst { syncUrl, intervalMs } = syncSettingsFrom(data)\n\n\t\t\t\tthis.padSync().configure({ syncUrl, intervalMs })\n\t\t\t\tthis.padSync().installLifecycleHandlers()\n\t\t\t\tif (syncUrl) {\n\t\t\t\t\tthis.padSync().start()\n\t\t\t\t}\n\n\t\t\t\tconst contentUrl = contentUrlFrom(data)\n\n\t\t\t\tconst { isContentView, externalUrl } = contentViewFrom(data)\n\t\t\t\tif (isContentView) {\n\t\t\t\t\tthis.externalOpenUrl = externalUrl\n\t\t\t\t\tthis.contentUrl = contentUrl\n\t\t\t\t\tthis.contentMode = 'content'\n\t\t\t\t\tthis.markLoaded()\n\t\t\t\t\t// Draw the content view while its body loads.\n\t\t\t\t\tvoid this.loadContent()\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tthis.iframeSrc = padUrlFrom(data)\n\t\t\t\tthis.markLoaded()\n\t\t\t} catch (error) {\n\t\t\t\tif (!isCurrent()) return\n\t\t\t\tthis.loadError = messageOf(error, 'Could not load pad.')\n\t\t\t\t// The server intentionally does not disclose why this id is unavailable.\n\t\t\t\tthis.maybeStaleFileId = this.resolvedFileId !== null\n\t\t\t\t\t&& Boolean(error) && error.status === 404 && !error.code\n\t\t\t\tthis.canRetryOpen = isRetryableOpenError(error)\n\t\t\t\t// Recovery may resolve only the same path that failed to open.\n\t\t\t\tlet recoveryFileId = this.resolvedFileId\n\t\t\t\tthis.recoveryPath = openPath\n\t\t\t\tif (recoveryFileId === null && !byPublicUrl && isMissingBindingError(error)) {\n\t\t\t\t\trecoveryFileId = await this.resolveRecoveryFileId(openPath)\n\t\t\t\t\t// A late lookup must not attach recovery to a newer Viewer item.\n\t\t\t\t\tif (!isCurrent()) return\n\t\t\t\t}\n\t\t\t\tthis.recoveryFileId = recoveryFileId\n\t\t\t\tthis.canRecover = isMissingBindingError(error)\n\t\t\t\t\t&& recoveryFileId !== null\n\t\t\t\t\t&& !byPublicUrl\n\t\t\t\tif (this.canRecover) {\n\t\t\t\t\tthis.fetchOriginalPadHint(isCurrent, afterClick)\n\t\t\t\t} else if (afterClick) {\n\t\t\t\t\tthis.handFocusToErrorCard()\n\t\t\t\t}\n\t\t\t\tthis.markLoaded()\n\t\t\t} finally {\n\t\t\t\tif (!isCurrent()) return\n\t\t\t\tthis.isLoading = false\n\t\t\t}\n\t\t},\n\t\t/** $afterClick: the focus waits for the card's final actions. */\n\t\tasync fetchOriginalPadHint(isCurrent, afterClick = false) {\n\t\t\tif (this.recoveryFileId === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.isCheckingOriginal = true\n\t\t\ttry {\n\t\t\t\tconst hint = await apiFindOriginalPad(this.recoveryFileId)\n\t\t\t\tif (!isCurrent()) return\n\t\t\t\tif (hint && hint.found === true && typeof hint.viewer_url === 'string' && hint.viewer_url !== '') {\n\t\t\t\t\tthis.originalPad = {\n\t\t\t\t\t\tviewerUrl: hint.viewer_url,\n\t\t\t\t\t\tpath: typeof hint.path === 'string' ? hint.path : '',\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Recovery remains available without an original-file hint.\n\t\t\t} finally {\n\t\t\t\tif (isCurrent()) {\n\t\t\t\t\tthis.isCheckingOriginal = false\n\t\t\t\t\tif (afterClick) {\n\t\t\t\t\t\tthis.handFocusToErrorCard()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tasync recoverFromSnapshot() {\n\t\t\tif (!this.canRecover || this.isRecovering || this.recoveryFileId === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.isRecovering = true\n\t\t\ttry {\n\t\t\t\tawait apiRecoverFromSnapshot(this.recoveryFileId, this.recoveryPath)\n\t\t\t\tthis.loadError = ''\n\t\t\t\tthis.canRecover = false\n\t\t\t\tawait this.resolveOpenUrl(true)\n\t\t\t} catch (error) {\n\t\t\t\t// No answer: the pad may be set up by now, and another\n\t\t\t\t// recovery would meet it. Opening tells, and is safe to repeat.\n\t\t\t\tif (isUnanswered(error)) {\n\t\t\t\t\tawait this.resolveOpenUrl(true)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// Enabled again before the card is drawn, so the focus lands.\n\t\t\t\tthis.isRecovering = false\n\t\t\t\tthis.loadError = messageOf(error, 'Could not load pad.')\n\t\t\t\t// The clicked button lost the focus while it was disabled.\n\t\t\t\tthis.handFocusToErrorCard()\n\t\t\t} finally {\n\t\t\t\tthis.isRecovering = false\n\t\t\t}\n\t\t},\n\t\t/** Refresh content through an endpoint that re-checks access. */\n\t\tasync loadContent() {\n\t\t\tconst openGeneration = this.resolveGeneration\n\t\t\tthis.contentGeneration += 1\n\t\t\tconst generation = this.contentGeneration\n\t\t\tconst isCurrent = () => generation === this.contentGeneration\n\t\t\t\t&& openGeneration === this.resolveGeneration\n\n\t\t\tif (this.contentUrl === '') {\n\t\t\t\tthis.contentState = 'error'\n\t\t\t\tthis.contentError = translate('The server did not say where to load this pad from.')\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis._contentAbort?.abort()\n\t\t\t// Abort superseded content refreshes.\n\t\t\tconst abort = typeof AbortController === 'function' ? new AbortController() : null\n\t\t\tthis._contentAbort = abort\n\t\t\tthis.contentState = 'loading'\n\t\t\tthis.contentError = ''\n\n\t\t\ttry {\n\t\t\t\tconst content = await loadPadContent(this.contentUrl, { signal: abort?.signal })\n\t\t\t\tif (!isCurrent()) return\n\t\t\t\tthis.content = content\n\t\t\t\tthis.contentLoaded = true\n\t\t\t\tthis.contentState = 'ready'\n\t\t\t} catch (error) {\n\t\t\t\tif (!isCurrent() || (error && error.name === 'AbortError')) return\n\t\t\t\tthis.contentState = 'error'\n\t\t\t\tthis.contentError = messageOf(error, translate('Could not load the pad content.'))\n\t\t\t}\n\t\t},\n\t\trenderContentView(createElement, options) {\n\t\t\tconst extraActions = Array.isArray(options.actions) ? options.actions : []\n\t\t\tconst isBusy = this.contentState === 'loading'\n\n\t\t\treturn createElement('div', { class: 'epnc-pad-doc' }, [\n\t\t\t\tcreateElement('div', { class: 'epnc-pad-doc__inner' }, [\n\t\t\t\t\tcreateElement('div', { class: 'epnc-pad-doc__toolbar' }, [\n\t\t\t\t\t\t(this.contentState === 'error' && this.contentLoaded)\n\t\t\t\t\t\t\t? createElement('span', { class: 'epnc-pad-doc__toolbar-error' },\n\t\t\t\t\t\t\t\tthis.contentError || translate('Could not load the pad content.'))\n\t\t\t\t\t\t\t: null,\n\t\t\t\t\t\tcreateElement('button', {\n\t\t\t\t\t\t\tclass: 'button epnc-pad-doc__refresh',\n\t\t\t\t\t\t\tattrs: { type: 'button', disabled: isBusy },\n\t\t\t\t\t\t\ton: { click: () => { void this.loadContent() } },\n\t\t\t\t\t\t}, isBusy ? translate('Refreshing...') : translate('Refresh')),\n\t\t\t\t\t\t...extraActions,\n\t\t\t\t\t]),\n\t\t\t\t\tthis.renderContentBody(createElement),\n\t\t\t\t]),\n\t\t\t])\n\t\t},\n\t\trenderContentBody(createElement) {\n\t\t\tif (this.contentLoaded && this.contentState !== 'ready') {\n\t\t\t\treturn this.renderContentText(createElement)\n\t\t\t}\n\t\t\tif (this.contentState === 'error') {\n\t\t\t\treturn createElement('div', { class: 'epnc-pad-doc__text epnc-pad-doc__status' }, [\n\t\t\t\t\tcreateElement('div', {},\n\t\t\t\t\t\tthis.contentError || translate('Could not load the pad content.')),\n\t\t\t\t\tcreateElement('button', {\n\t\t\t\t\t\tclass: 'button primary',\n\t\t\t\t\t\tattrs: { type: 'button' },\n\t\t\t\t\t\ton: { click: () => { void this.loadContent() } },\n\t\t\t\t\t}, translate('Try again')),\n\t\t\t\t])\n\t\t\t}\n\t\t\tif (this.contentState !== 'ready') {\n\t\t\t\treturn createElement('div', { class: 'epnc-pad-doc__text epnc-pad-doc__status' }, translate('Loading pad content...'))\n\t\t\t}\n\t\t\treturn this.renderContentText(createElement)\n\t\t},\n\t\trenderContentText(createElement) {\n\t\t\tif (this.content.isEmpty) {\n\t\t\t\treturn createElement('div', { class: 'epnc-pad-doc__text epnc-pad-doc__status' }, translate('This pad is still empty.'))\n\t\t\t}\n\t\t\treturn createElement('div', {\n\t\t\t\tclass: 'epnc-pad-doc__text epnc-pad-doc__text--html',\n\t\t\t\tdomProps: { innerHTML: this.content.html },\n\t\t\t})\n\t\t},\n\t},\n\tbeforeDestroy() {\n\t\tthis.resolveGeneration += 1\n\t\t// Abort work that could otherwise finish after teardown.\n\t\tthis._openAbort?.abort()\n\t\tthis._contentAbort?.abort()\n\t\tthis.teardownSync()\n\t},\n\tbeforeUnmount() {\n\t\tthis.resolveGeneration += 1\n\t\tthis._openAbort?.abort()\n\t\tthis._contentAbort?.abort()\n\t\tthis.teardownSync()\n\t},\n\trender(createElement) {\n\t\tif (this.loadError) {\n\t\t\tconst cardChildren = [\n\t\t\t\tcreateElement('div', { class: 'epnc-native-error-title' }, translate('Could not open pad')),\n\t\t\t\t// Read out when it appears, not only when someone looks.\n\t\t\t\tcreateElement('div', { class: 'epnc-native-error-message', attrs: { role: 'alert' } }, this.loadError),\n\t\t\t]\n\t\t\tif (this.maybeStaleFileId) {\n\t\t\t\tcardChildren.push(\n\t\t\t\t\tcreateElement('div', { class: 'epnc-native-error-message' },\n\t\t\t\t\t\ttranslate('This file may have been moved or replaced since the list was loaded. Reload the page and open it again.')),\n\t\t\t\t)\n\t\t\t}\n\t\t\tif (this.canRetryOpen) {\n\t\t\t\tcardChildren.push(\n\t\t\t\t\tcreateElement('button', {\n\t\t\t\t\t\tclass: 'button primary epnc-native-error-action',\n\t\t\t\t\t\tattrs: { type: 'button' },\n\t\t\t\t\t\ton: { click: () => { void this.resolveOpenUrl(true) } },\n\t\t\t\t\t}, translate('Try again')),\n\t\t\t\t)\n\t\t\t}\n\t\t\tif (this.canRecover) {\n\t\t\t\tif (this.isCheckingOriginal) {\n\t\t\t\t\t// Wait before choosing the primary recovery action.\n\t\t\t\t\tcardChildren.push(\n\t\t\t\t\t\tcreateElement('div', { class: 'epnc-native-error-message' },\n\t\t\t\t\t\t\ttranslate('Checking for the original pad...')),\n\t\t\t\t\t)\n\t\t\t\t} else if (this.originalPad) {\n\t\t\t\t\tcardChildren.push(\n\t\t\t\t\t\tcreateElement('div', { class: 'epnc-native-error-message' },\n\t\t\t\t\t\t\ttranslate('This file looks like a copy of an existing .pad file in your account. Open the original to keep editing the linked pad, or create a new pad to fork the content stored in this file.')),\n\t\t\t\t\t\tcreateElement('a', {\n\t\t\t\t\t\t\tclass: 'button primary epnc-native-error-action',\n\t\t\t\t\t\t\tattrs: { href: this.originalPad.viewerUrl },\n\t\t\t\t\t\t}, translate('Open the original .pad file')),\n\t\t\t\t\t\tcreateElement('button', {\n\t\t\t\t\t\t\tclass: 'button epnc-native-error-action',\n\t\t\t\t\t\t\tattrs: { type: 'button', disabled: this.isRecovering },\n\t\t\t\t\t\t\ton: { click: () => { void this.recoverFromSnapshot() } },\n\t\t\t\t\t\t}, this.isRecovering ? translate('Creating new pad...') : translate('Create new pad from this file')),\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tcardChildren.push(\n\t\t\t\t\t\tcreateElement('div', { class: 'epnc-native-error-message' },\n\t\t\t\t\t\t\ttranslate(\"We couldn't find a matching pad in this Nextcloud. You can create a new pad from the text stored in this file; from then on, opening this file will load the new pad.\")),\n\t\t\t\t\t\tcreateElement('button', {\n\t\t\t\t\t\t\tclass: 'button primary epnc-native-error-action',\n\t\t\t\t\t\t\tattrs: { type: 'button', disabled: this.isRecovering },\n\t\t\t\t\t\t\ton: { click: () => { void this.recoverFromSnapshot() } },\n\t\t\t\t\t\t}, this.isRecovering ? translate('Creating new pad...') : translate('Create new pad from this file')),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn createElement('div', { class: 'epnc-native-status epnc-native-status--error' }, [\n\t\t\t\tcreateElement('div', { class: 'epnc-native-error-card', ref: 'errorCard' }, cardChildren),\n\t\t\t])\n\t\t}\n\t\tif (this.contentMode === 'content') {\n\t\t\treturn this.renderContentView(createElement, this.externalOpenUrl === ''\n\t\t\t\t? {}\n\t\t\t\t: {\n\t\t\t\t\tactions: [\n\t\t\t\t\t\tcreateElement('a', {\n\t\t\t\t\t\t\tclass: 'button epnc-pad-doc__link',\n\t\t\t\t\t\t\tattrs: {\n\t\t\t\t\t\t\t\thref: this.externalOpenUrl,\n\t\t\t\t\t\t\t\ttarget: '_blank',\n\t\t\t\t\t\t\t\trel: 'noopener noreferrer',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}, translate('Open original pad')),\n\t\t\t\t\t],\n\t\t\t\t})\n\t\t}\n\t\tif (this.isLoading || !this.iframeSrc) {\n\t\t\treturn createElement('div', { class: 'epnc-native-status' }, 'Loading pad...')\n\t\t}\n\n\t\treturn createElement('div', { class: 'epnc-native-shell' }, [\n\t\t\t// Nextcloud inspects direct iframe children, so keep this wrapper same-origin.\n\t\t\tcreateElement('iframe', {\n\t\t\t\tattrs: { srcdoc: buildPadFrameSrcdoc(this.iframeSrc), title: 'Etherpad' },\n\t\t\t\t// Etherpad provides its own loading state inside the nested iframe.\n\t\t\t\ton: { load: () => this.markLoaded(), error: () => this.markLoaded() },\n\t\t\t\tclass: 'epnc-native-iframe',\n\t\t\t}),\n\t\t])\n\t},\n}\n\nexport default component\n","/**\n * SPDX-License-Identifier: AGPL-3.0-or-later\n * Copyright (c) 2026 Jacob Bühler\n */\n\n/** Registers the pad MIME handler with the Viewer. */\n\nimport { registerHandler } from '@nextcloud/viewer'\n\nimport { MIME, VIEWER_HANDLER_ID } from './lib/constants.js'\nimport component from './viewer-main.js'\n\n// The options object itself, not a loader for it: the Viewer assigns its\n// Mime mixin onto what it is given and registers it under `component.name`,\n// and a function silently takes neither.\nregisterHandler({ id: VIEWER_HANDLER_ID, mimes: [MIME], component })\n"],"names":["registerHandler","handler","validateHandler","id","mimes","mimesAliases","component","RESOLVE_CACHE","RESOLVE_CACHE_MAX_ENTRIES","RESOLVE_CACHE_TTL_MS","apiResolvePadByPath","path","bypassCache","cacheKey","cached","getResolveCache","url","ocGenerateUrl","APP_ID","request","fetchJson","error","setResolveCache","apiFindOriginalPad","fileId","endpoint","apiRecoverFromSnapshot","result","fetchJsonWithTimeout","ocRequestToken","oldestKey","options","fallbackMessage","escapeAttribute","value","ALLOWED_PAD_URL_SCHEMES","isSafePadUrl","parsed","SRC_DOC_CSP","buildPadFrameSrcdoc","safeUrl","messageOf","fallback","requestErrorMessage","translate","parsePadPathFromDavHref","normalizeDir","dir","joinPath","name","normalizedDir","isPadName","info","infoPath","baseName","infoDir","combined","urlDir","fromDir","candidates","candidate","numeric","init","assertOpenPayload","headers","initOptions","announceMigratedStatus","data","body","openPath","resolved","card","handFocusTo","createPadSync","afterClick","generation","isCurrent","abort","publicToken","parsePublicShareTokenFromLocation","byPublicUrl","openPostHeaders","openWithFrontmatterRecovery","signal","byIdBody","byPathBody","syncUrl","intervalMs","syncSettingsFrom","contentUrl","contentUrlFrom","isContentView","externalUrl","contentViewFrom","padUrlFrom","isRetryableOpenError","recoveryFileId","isMissingBindingError","hint","isUnanswered","openGeneration","content","loadPadContent","createElement","extraActions","isBusy","cardChildren","VIEWER_HANDLER_ID","MIME"],"mappings":"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","x_google_ignoreList":[0]}
\ No newline at end of file
diff --git a/js/fetch-helpers-BUxbvlK6.chunk.mjs b/js/fetch-helpers-BUxbvlK6.chunk.mjs
new file mode 100644
index 00000000..a9394942
--- /dev/null
+++ b/js/fetch-helpers-BUxbvlK6.chunk.mjs
@@ -0,0 +1,2 @@
+const g="etherpad_nextcloud",E="application/x-etherpad-nextcloud",O="etherpad_nextcloud",h=Object.freeze(["protected","public"]),j="protected",C=t=>h.includes(t),A=t=>window.OC&&typeof window.OC.generateUrl=="function"?window.OC.generateUrl(t):"/index.php"+t,T=(t="")=>{const a=String(t||"").trim();return a!==""?a:String(window.OC&&window.OC.requestToken||"")},M=t=>typeof window.t=="function"?window.t(g,t):t,m=1e4,y=[502,503,504],S=async(t,a={},d={})=>{const{timeoutMs:u=m,fallbackMessage:b="Request failed."}=d,c=new AbortController,w=u===null||u===0?null:window.setTimeout(()=>c.abort(),u),o=a.signal;let l;o&&(o.aborted?c.abort():(l=()=>c.abort(),o.addEventListener("abort",l)));const p=Object.assign({Accept:"application/json"},a.headers||{});let i=null;try{const e=await fetch(t,Object.assign({},a,{credentials:"same-origin",headers:p,signal:c.signal}));i=e.status;let s=!0;const r=await e.json().catch(n=>{if(!(n instanceof SyntaxError))throw n;return s=!1,{}});if(!e.ok){const n=new Error(r&&r.message||b);r&&typeof r.code=="string"&&(n.code=r.code),r&&r.retryable===!0&&(n.retryable=!0);const f=y.includes(e.status)&&n.retryable!==!0;throw e.status>=500&&(!s||f)&&(n.unanswered=!0),n.status=e.status,n}return r}catch(e){if(e&&typeof e=="object"&&"name"in e&&e.name==="AbortError"){if(w===null||o&&o.aborted)throw e;const s=new Error("Request timed out.");throw s.unanswered=!0,i!==null&&(s.status=i),s}throw e instanceof TypeError&&(e.unanswered=!0,i!==null&&(e.status=i)),e}finally{w!==null&&window.clearTimeout(w),l&&o.removeEventListener("abort",l)}},x=t=>!!t&&t.unanswered===!0,_=(t,a,d)=>x(t)?a:t instanceof Error&&t.message?t.message:d;export{g as A,j as D,E as M,O as V,x as a,A as b,S as f,C as i,T as o,_ as r,M as t};
+//# sourceMappingURL=fetch-helpers-BUxbvlK6.chunk.mjs.map
diff --git a/js/fetch-helpers-Dqr3YYFE.chunk.mjs.license b/js/fetch-helpers-BUxbvlK6.chunk.mjs.license
similarity index 100%
rename from js/fetch-helpers-Dqr3YYFE.chunk.mjs.license
rename to js/fetch-helpers-BUxbvlK6.chunk.mjs.license
diff --git a/js/fetch-helpers-BUxbvlK6.chunk.mjs.map b/js/fetch-helpers-BUxbvlK6.chunk.mjs.map
new file mode 100644
index 00000000..051bfbd6
--- /dev/null
+++ b/js/fetch-helpers-BUxbvlK6.chunk.mjs.map
@@ -0,0 +1 @@
+{"version":3,"file":"fetch-helpers-BUxbvlK6.chunk.mjs","sources":["../src/lib/constants.js","../src/lib/oc-compat.js","../src/lib/fetch-helpers.js"],"sourcesContent":["/**\n * SPDX-License-Identifier: AGPL-3.0-or-later\n * Copyright (c) 2026 Jacob Bühler\n */\n\nexport const APP_ID = 'etherpad_nextcloud'\nexport const MIME = 'application/x-etherpad-nextcloud'\nexport const VIEWER_HANDLER_ID = 'etherpad_nextcloud'\n\n/** The kinds of pad this app makes. PadAccessMode.php is the same list. */\nexport const PAD_ACCESS_MODES = Object.freeze(['protected', 'public'])\n\n/** What a caller gets for asking for none. PadCreateController spells it too. */\nexport const DEFAULT_PAD_ACCESS_MODE = 'protected'\n\n/**\n * @param {unknown} value\n * @return {boolean}\n */\nexport const isPadAccessMode = (value) => PAD_ACCESS_MODES.includes(value)\n","/**\n * SPDX-License-Identifier: AGPL-3.0-or-later\n * Copyright (c) 2026 Jacob Bühler\n */\n\nimport { APP_ID } from './constants.js'\n\nexport const ocGenerateUrl = (path) => {\n\tif (window.OC && typeof window.OC.generateUrl === 'function') {\n\t\treturn window.OC.generateUrl(path)\n\t}\n\treturn '/index.php' + path\n}\n\nexport const ocRequestToken = (fallback = '') => {\n\tconst configuredFallback = String(fallback || '').trim()\n\tif (configuredFallback !== '') {\n\t\treturn configuredFallback\n\t}\n\treturn String((window.OC && window.OC.requestToken) || '')\n}\n\nexport const translate = (text) => (typeof window.t === 'function' ? window.t(APP_ID, text) : text)\n","/**\n * SPDX-License-Identifier: AGPL-3.0-or-later\n * Copyright (c) 2026 Jacob Bühler\n */\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 10000\nconst GATEWAY_STATUSES = [502, 503, 504]\n\n/**\n * `init.signal` is chained rather than replaced: the timeout needs a\n * controller of its own, and a caller that brought a signal — a viewer\n * abandoning an open, say — must still be able to cancel. Whichever fires\n * first wins.\n *\n * `timeoutMs: null` waits indefinitely, and is for requests that *write*.\n * Cutting a read short costs a retry; cutting a write short applies the\n * change with nobody left to read the outcome — a recovery that has\n * created its pad but not yet its binding looks unrecovered, and the\n * retry then either collides with the binding it did write or provisions\n * a second pad and orphans the first. Slow is not the same as stuck.\n *\n * An error carries the server's `code` and `retryable` when it sent them,\n * and `unanswered` when nothing came back from this app: our own timeout,\n * a failed network, or a proxy or PHP itself answering in its place. Only\n * that fact: whether the same request is worth another try depends on\n * whether it writes, which the caller knows (`isRetryableOpenError` for an\n * open). `status` goes along whenever one came, also when the body then\n * broke off.\n */\nexport const fetchJsonWithTimeout = async (url, init = {}, options = {}) => {\n\tconst { timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS, fallbackMessage = 'Request failed.' } = options\n\tconst controller = new AbortController()\n\tconst timeoutId = timeoutMs === null || timeoutMs === 0\n\t\t? null\n\t\t: window.setTimeout(() => controller.abort(), timeoutMs)\n\tconst callerSignal = init.signal\n\tlet abortOnCaller\n\tif (callerSignal) {\n\t\tif (callerSignal.aborted) {\n\t\t\tcontroller.abort()\n\t\t} else {\n\t\t\tabortOnCaller = () => controller.abort()\n\t\t\tcallerSignal.addEventListener('abort', abortOnCaller)\n\t\t}\n\t}\n\tconst headers = Object.assign({ Accept: 'application/json' }, init.headers || {})\n\tlet status = null\n\ttry {\n\t\tconst response = await fetch(url, Object.assign({}, init, {\n\t\t\tcredentials: 'same-origin',\n\t\t\theaders,\n\t\t\tsignal: controller.signal,\n\t\t}))\n\t\tstatus = response.status\n\t\t// Only a body that is not JSON counts as none. A timeout or a failed\n\t\t// network while it streams in is no answer, and is handled below.\n\t\tlet isJson = true\n\t\tconst data = await response.json().catch((error) => {\n\t\t\tif (!(error instanceof SyntaxError)) {\n\t\t\t\tthrow error\n\t\t\t}\n\t\t\tisJson = false\n\t\t\treturn {}\n\t\t})\n\t\tif (!response.ok) {\n\t\t\tconst error = new Error((data && data.message) || fallbackMessage)\n\t\t\tif (data && typeof data.code === 'string') {\n\t\t\t\terror.code = data.code\n\t\t\t}\n\t\t\t// The server's word that the same request may succeed later (the\n\t\t\t// cases are in docs/api-reference.md).\n\t\t\tif (data && data.retryable === true) {\n\t\t\t\terror.retryable = true\n\t\t\t}\n\t\t\t// This app answers every error in JSON, never with 502 or 504,\n\t\t\t// and every 503 of its own carries retryable (docs/api-reference.md).\n\t\t\t// A 5xx that is no such answer came from a proxy with its backend\n\t\t\t// gone, from Nextcloud in maintenance, or from PHP dying midway.\n\t\t\tconst isGateway = GATEWAY_STATUSES.includes(response.status) && error.retryable !== true\n\t\t\tif (response.status >= 500 && (!isJson || isGateway)) {\n\t\t\t\terror.unanswered = true\n\t\t\t}\n\t\t\terror.status = response.status\n\t\t\tthrow error\n\t\t}\n\t\treturn data\n\t} catch (error) {\n\t\tif (error && typeof error === 'object' && 'name' in error && error.name === 'AbortError') {\n\t\t\t// A caller's own abort is not a timeout. No caller distinguishes\n\t\t\t// them today — both recognise a superseded request by the\n\t\t\t// generation guard instead — but rewriting a deliberate abort as\n\t\t\t// \"Request timed out.\" would be a lie the moment one does.\n\t\t\tif (timeoutId === null || (callerSignal && callerSignal.aborted)) {\n\t\t\t\tthrow error\n\t\t\t}\n\t\t\tconst timedOut = new Error('Request timed out.')\n\t\t\ttimedOut.unanswered = true\n\t\t\tif (status !== null) {\n\t\t\t\ttimedOut.status = status\n\t\t\t}\n\t\t\tthrow timedOut\n\t\t}\n\t\t// fetch() and reading the body reject with a TypeError when the\n\t\t// network fails.\n\t\tif (error instanceof TypeError) {\n\t\t\terror.unanswered = true\n\t\t\tif (status !== null) {\n\t\t\t\terror.status = status\n\t\t\t}\n\t\t}\n\t\tthrow error\n\t} finally {\n\t\tif (timeoutId !== null) {\n\t\t\twindow.clearTimeout(timeoutId)\n\t\t}\n\t\tif (abortOnCaller) {\n\t\t\tcallerSignal.removeEventListener('abort', abortOnCaller)\n\t\t}\n\t}\n}\n\n/**\n * Whether nothing came back from this app (see fetchJsonWithTimeout()).\n *\n * @param {unknown} error\n * @return {boolean}\n */\nexport const isUnanswered = (error) => Boolean(error) && error.unanswered === true\n\n/**\n * What to show for a failed request: the server's sentence, or, when\n * nothing came back, the caller's translated `unansweredText` rather than\n * the browser's own English (\"Failed to fetch\", \"Load failed\", ...).\n *\n * @param {unknown} error\n * @param {string} unansweredText\n * @param {string} fallbackText\n * @return {string}\n */\nexport const requestErrorMessage = (error, unansweredText, fallbackText) => {\n\tif (isUnanswered(error)) {\n\t\treturn unansweredText\n\t}\n\treturn error instanceof Error && error.message ? error.message : fallbackText\n}\n"],"names":["APP_ID","MIME","VIEWER_HANDLER_ID","PAD_ACCESS_MODES","DEFAULT_PAD_ACCESS_MODE","isPadAccessMode","value","ocGenerateUrl","path","ocRequestToken","fallback","configuredFallback","translate","text","DEFAULT_REQUEST_TIMEOUT_MS","GATEWAY_STATUSES","fetchJsonWithTimeout","url","init","options","timeoutMs","fallbackMessage","controller","timeoutId","callerSignal","abortOnCaller","headers","status","response","isJson","data","error","isGateway","timedOut","isUnanswered","requestErrorMessage","unansweredText","fallbackText"],"mappings":"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"}
\ No newline at end of file
diff --git a/js/fetch-helpers-Dqr3YYFE.chunk.mjs b/js/fetch-helpers-Dqr3YYFE.chunk.mjs
deleted file mode 100644
index 71c7bddd..00000000
--- a/js/fetch-helpers-Dqr3YYFE.chunk.mjs
+++ /dev/null
@@ -1,2 +0,0 @@
-const p="etherpad_nextcloud",h="application/x-etherpad-nextcloud",m="etherpad_nextcloud",b=Object.freeze(["protected","public"]),y="protected",E=e=>b.includes(e),O=e=>window.OC&&typeof window.OC.generateUrl=="function"?window.OC.generateUrl(e):"/index.php"+e,x=(e="")=>{const a=String(e||"").trim();return a!==""?a:String(window.OC&&window.OC.requestToken||"")},C=e=>typeof window.t=="function"?window.t(p,e):e,f=1e4,g=[502,503,504],j=async(e,a={},c={})=>{const{timeoutMs:d=f,fallbackMessage:w="Request failed."}=c,s=new AbortController,l=d===null||d===0?null:window.setTimeout(()=>s.abort(),d),o=a.signal;let i;o&&(o.aborted?s.abort():(i=()=>s.abort(),o.addEventListener("abort",i)));const u=Object.assign({Accept:"application/json"},a.headers||{});try{const t=await fetch(e,Object.assign({},a,{credentials:"same-origin",headers:u,signal:s.signal})),n=await t.json().catch(r=>{if(!(r instanceof SyntaxError))throw r;return{}});if(!t.ok){const r=new Error(n&&n.message||w);throw n&&typeof n.code=="string"&&(r.code=n.code),n&&n.retryable===!0&&(r.retryable=!0),g.includes(t.status)&&r.retryable!==!0&&(r.unanswered=!0),r.status=t.status,r}return n}catch(t){if(t&&typeof t=="object"&&"name"in t&&t.name==="AbortError"){if(l===null||o&&o.aborted)throw t;const n=new Error("Request timed out.");throw n.unanswered=!0,n}throw t instanceof TypeError&&(t.unanswered=!0),t}finally{l!==null&&window.clearTimeout(l),i&&o.removeEventListener("abort",i)}},A=(e,a,c)=>e&&e.unanswered===!0?a:e instanceof Error&&e.message?e.message:c;export{p as A,y as D,h as M,m as V,O as a,j as f,E as i,x as o,A as r,C as t};
-//# sourceMappingURL=fetch-helpers-Dqr3YYFE.chunk.mjs.map
diff --git a/js/fetch-helpers-Dqr3YYFE.chunk.mjs.map b/js/fetch-helpers-Dqr3YYFE.chunk.mjs.map
deleted file mode 100644
index 75252cbf..00000000
--- a/js/fetch-helpers-Dqr3YYFE.chunk.mjs.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"fetch-helpers-Dqr3YYFE.chunk.mjs","sources":["../src/lib/constants.js","../src/lib/oc-compat.js","../src/lib/fetch-helpers.js"],"sourcesContent":["/**\n * SPDX-License-Identifier: AGPL-3.0-or-later\n * Copyright (c) 2026 Jacob Bühler\n */\n\nexport const APP_ID = 'etherpad_nextcloud'\nexport const MIME = 'application/x-etherpad-nextcloud'\nexport const VIEWER_HANDLER_ID = 'etherpad_nextcloud'\n\n/** The kinds of pad this app makes. PadAccessMode.php is the same list. */\nexport const PAD_ACCESS_MODES = Object.freeze(['protected', 'public'])\n\n/** What a caller gets for asking for none. PadCreateController spells it too. */\nexport const DEFAULT_PAD_ACCESS_MODE = 'protected'\n\n/**\n * @param {unknown} value\n * @return {boolean}\n */\nexport const isPadAccessMode = (value) => PAD_ACCESS_MODES.includes(value)\n","/**\n * SPDX-License-Identifier: AGPL-3.0-or-later\n * Copyright (c) 2026 Jacob Bühler\n */\n\nimport { APP_ID } from './constants.js'\n\nexport const ocGenerateUrl = (path) => {\n\tif (window.OC && typeof window.OC.generateUrl === 'function') {\n\t\treturn window.OC.generateUrl(path)\n\t}\n\treturn '/index.php' + path\n}\n\nexport const ocRequestToken = (fallback = '') => {\n\tconst configuredFallback = String(fallback || '').trim()\n\tif (configuredFallback !== '') {\n\t\treturn configuredFallback\n\t}\n\treturn String((window.OC && window.OC.requestToken) || '')\n}\n\nexport const translate = (text) => (typeof window.t === 'function' ? window.t(APP_ID, text) : text)\n","/**\n * SPDX-License-Identifier: AGPL-3.0-or-later\n * Copyright (c) 2026 Jacob Bühler\n */\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 10000\nconst GATEWAY_STATUSES = [502, 503, 504]\n\n/**\n * `init.signal` is chained rather than replaced: the timeout needs a\n * controller of its own, and a caller that brought a signal — a viewer\n * abandoning an open, say — must still be able to cancel. Whichever fires\n * first wins.\n *\n * `timeoutMs: null` waits indefinitely, and is for requests that *write*.\n * Cutting a read short costs a retry; cutting a write short applies the\n * change with nobody left to read the outcome — a recovery that has\n * created its pad but not yet its binding looks unrecovered, and the\n * retry then either collides with the binding it did write or provisions\n * a second pad and orphans the first. Slow is not the same as stuck.\n *\n * An error carries the server's `code` and `retryable` when it sent them,\n * and `unanswered` when nothing came back from this app: our own timeout,\n * a failed network, or a gateway answering in its place. Only that fact:\n * whether the same request is worth another try depends on whether it\n * writes, which the caller knows (`isRetryableOpenError` for an open).\n */\nexport const fetchJsonWithTimeout = async (url, init = {}, options = {}) => {\n\tconst { timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS, fallbackMessage = 'Request failed.' } = options\n\tconst controller = new AbortController()\n\tconst timeoutId = timeoutMs === null || timeoutMs === 0\n\t\t? null\n\t\t: window.setTimeout(() => controller.abort(), timeoutMs)\n\tconst callerSignal = init.signal\n\tlet abortOnCaller\n\tif (callerSignal) {\n\t\tif (callerSignal.aborted) {\n\t\t\tcontroller.abort()\n\t\t} else {\n\t\t\tabortOnCaller = () => controller.abort()\n\t\t\tcallerSignal.addEventListener('abort', abortOnCaller)\n\t\t}\n\t}\n\tconst headers = Object.assign({ Accept: 'application/json' }, init.headers || {})\n\ttry {\n\t\tconst response = await fetch(url, Object.assign({}, init, {\n\t\t\tcredentials: 'same-origin',\n\t\t\theaders,\n\t\t\tsignal: controller.signal,\n\t\t}))\n\t\t// Only a body that is not JSON counts as none. A timeout or a failed\n\t\t// network while it streams in is no answer, and is handled below.\n\t\tconst data = await response.json().catch((error) => {\n\t\t\tif (!(error instanceof SyntaxError)) {\n\t\t\t\tthrow error\n\t\t\t}\n\t\t\treturn {}\n\t\t})\n\t\tif (!response.ok) {\n\t\t\tconst error = new Error((data && data.message) || fallbackMessage)\n\t\t\tif (data && typeof data.code === 'string') {\n\t\t\t\terror.code = data.code\n\t\t\t}\n\t\t\t// The server's word that the same request may succeed later (the\n\t\t\t// cases are in docs/api-reference.md).\n\t\t\tif (data && data.retryable === true) {\n\t\t\t\terror.retryable = true\n\t\t\t}\n\t\t\t// This app never answers 502 or 504, and every 503 of its own\n\t\t\t// carries retryable (docs/api-reference.md). Without it, a proxy\n\t\t\t// with its backend gone or Nextcloud in maintenance answered in\n\t\t\t// its place, with a page or with JSON of its own.\n\t\t\tif (GATEWAY_STATUSES.includes(response.status) && error.retryable !== true) {\n\t\t\t\terror.unanswered = true\n\t\t\t}\n\t\t\terror.status = response.status\n\t\t\tthrow error\n\t\t}\n\t\treturn data\n\t} catch (error) {\n\t\tif (error && typeof error === 'object' && 'name' in error && error.name === 'AbortError') {\n\t\t\t// A caller's own abort is not a timeout. No caller distinguishes\n\t\t\t// them today — both recognise a superseded request by the\n\t\t\t// generation guard instead — but rewriting a deliberate abort as\n\t\t\t// \"Request timed out.\" would be a lie the moment one does.\n\t\t\tif (timeoutId === null || (callerSignal && callerSignal.aborted)) {\n\t\t\t\tthrow error\n\t\t\t}\n\t\t\tconst timedOut = new Error('Request timed out.')\n\t\t\ttimedOut.unanswered = true\n\t\t\tthrow timedOut\n\t\t}\n\t\t// fetch() and reading the body reject with a TypeError when the\n\t\t// network fails.\n\t\tif (error instanceof TypeError) {\n\t\t\terror.unanswered = true\n\t\t}\n\t\tthrow error\n\t} finally {\n\t\tif (timeoutId !== null) {\n\t\t\twindow.clearTimeout(timeoutId)\n\t\t}\n\t\tif (abortOnCaller) {\n\t\t\tcallerSignal.removeEventListener('abort', abortOnCaller)\n\t\t}\n\t}\n}\n\n/**\n * What to show for a failed request: the server's sentence, or, when\n * nothing came back, the caller's translated `unansweredText` rather than\n * the browser's own English (\"Failed to fetch\", \"Load failed\", ...).\n *\n * @param {unknown} error\n * @param {string} unansweredText\n * @param {string} fallbackText\n * @return {string}\n */\nexport const requestErrorMessage = (error, unansweredText, fallbackText) => {\n\tif (error && error.unanswered === true) {\n\t\treturn unansweredText\n\t}\n\treturn error instanceof Error && error.message ? error.message : fallbackText\n}\n"],"names":["APP_ID","MIME","VIEWER_HANDLER_ID","PAD_ACCESS_MODES","DEFAULT_PAD_ACCESS_MODE","isPadAccessMode","value","ocGenerateUrl","path","ocRequestToken","fallback","configuredFallback","translate","text","DEFAULT_REQUEST_TIMEOUT_MS","GATEWAY_STATUSES","fetchJsonWithTimeout","url","init","options","timeoutMs","fallbackMessage","controller","timeoutId","callerSignal","abortOnCaller","headers","response","data","error","timedOut","requestErrorMessage","unansweredText","fallbackText"],"mappings":"MAKaA,EAAS,qBACTC,EAAO,mCACPC,EAAoB,qBAGpBC,EAAmB,OAAO,OAAO,CAAC,YAAa,QAAQ,CAAC,EAGxDC,EAA0B,YAM1BC,EAAmBC,GAAUH,EAAiB,SAASG,CAAK,ECZ5DC,EAAiBC,GACzB,OAAO,IAAM,OAAO,OAAO,GAAG,aAAgB,WAC1C,OAAO,GAAG,YAAYA,CAAI,EAE3B,aAAeA,EAGVC,EAAiB,CAACC,EAAW,KAAO,CAChD,MAAMC,EAAqB,OAAOD,GAAY,EAAE,EAAE,KAAI,EACtD,OAAIC,IAAuB,GACnBA,EAED,OAAQ,OAAO,IAAM,OAAO,GAAG,cAAiB,EAAE,CAC1D,EAEaC,EAAaC,GAAU,OAAO,OAAO,GAAM,WAAa,OAAO,EAAEb,EAAQa,CAAI,EAAIA,ECjBxFC,EAA6B,IAC7BC,EAAmB,CAAC,IAAK,IAAK,GAAG,EAqB1BC,EAAuB,MAAOC,EAAKC,EAAO,CAAA,EAAIC,EAAU,CAAA,IAAO,CAC3E,KAAM,CAAE,UAAAC,EAAYN,EAA4B,gBAAAO,EAAkB,iBAAiB,EAAKF,EAClFG,EAAa,IAAI,gBACjBC,EAAYH,IAAc,MAAQA,IAAc,EACnD,KACA,OAAO,WAAW,IAAME,EAAW,MAAK,EAAIF,CAAS,EAClDI,EAAeN,EAAK,OAC1B,IAAIO,EACAD,IACCA,EAAa,QAChBF,EAAW,MAAK,GAEhBG,EAAgB,IAAMH,EAAW,MAAK,EACtCE,EAAa,iBAAiB,QAASC,CAAa,IAGtD,MAAMC,EAAU,OAAO,OAAO,CAAE,OAAQ,kBAAkB,EAAIR,EAAK,SAAW,CAAA,CAAE,EAChF,GAAI,CACH,MAAMS,EAAW,MAAM,MAAMV,EAAK,OAAO,OAAO,CAAA,EAAIC,EAAM,CACzD,YAAa,cACb,QAAAQ,EACA,OAAQJ,EAAW,MACtB,CAAG,CAAC,EAGIM,EAAO,MAAMD,EAAS,KAAI,EAAG,MAAOE,GAAU,CACnD,GAAI,EAAEA,aAAiB,aACtB,MAAMA,EAEP,MAAO,CAAA,CACR,CAAC,EACD,GAAI,CAACF,EAAS,GAAI,CACjB,MAAME,EAAQ,IAAI,MAAOD,GAAQA,EAAK,SAAYP,CAAe,EACjE,MAAIO,GAAQ,OAAOA,EAAK,MAAS,WAChCC,EAAM,KAAOD,EAAK,MAIfA,GAAQA,EAAK,YAAc,KAC9BC,EAAM,UAAY,IAMfd,EAAiB,SAASY,EAAS,MAAM,GAAKE,EAAM,YAAc,KACrEA,EAAM,WAAa,IAEpBA,EAAM,OAASF,EAAS,OAClBE,CACP,CACA,OAAOD,CACR,OAASC,EAAO,CACf,GAAIA,GAAS,OAAOA,GAAU,UAAY,SAAUA,GAASA,EAAM,OAAS,aAAc,CAKzF,GAAIN,IAAc,MAASC,GAAgBA,EAAa,QACvD,MAAMK,EAEP,MAAMC,EAAW,IAAI,MAAM,oBAAoB,EAC/C,MAAAA,EAAS,WAAa,GAChBA,CACP,CAGA,MAAID,aAAiB,YACpBA,EAAM,WAAa,IAEdA,CACP,QAAA,CACKN,IAAc,MACjB,OAAO,aAAaA,CAAS,EAE1BE,GACHD,EAAa,oBAAoB,QAASC,CAAa,CAEzD,CACD,EAYaM,EAAsB,CAACF,EAAOG,EAAgBC,IACtDJ,GAASA,EAAM,aAAe,GAC1BG,EAEDH,aAAiB,OAASA,EAAM,QAAUA,EAAM,QAAUI"}
\ No newline at end of file
diff --git a/js/pad-open-flow-D6KDQoVq.chunk.mjs b/js/pad-open-flow-By_FzSKk.chunk.mjs
similarity index 74%
rename from js/pad-open-flow-D6KDQoVq.chunk.mjs
rename to js/pad-open-flow-By_FzSKk.chunk.mjs
index 32bd8448..e1d60699 100644
--- a/js/pad-open-flow-D6KDQoVq.chunk.mjs
+++ b/js/pad-open-flow-By_FzSKk.chunk.mjs
@@ -1,4 +1,4 @@
-import{f as jn}from"./fetch-helpers-Dqr3YYFE.chunk.mjs";const Wn=12e4;function Lo({requestToken:t,fetchImpl:r}={}){const a=typeof r=="function"?r:(...S)=>window.fetch(...S),l=typeof t=="function"?t:()=>"";let u="",g=Wn,b=null,Y=!1,E=!1,G=!1,q=null,L=null,O=null;const je=({syncUrl:S,intervalMs:M}={})=>{typeof S=="string"&&(u=S),Number.isFinite(M)&&M>0&&(g=M)},Z=()=>{q!==null&&(window.clearInterval(q),q=null)},be=()=>{!u||q!==null||(q=window.setInterval(()=>{document.visibilityState==="visible"&&le(!1,!1)},g))},le=(S,M)=>{I(S,M).catch(()=>{})},I=async(S,M)=>{if(!u)return{status:"disabled"};if(b)return S&&!Y?(E=!0,G=G||!!M,b.catch(()=>{}).then(()=>I(!0,G))):b;Y=!!S;const J=(async()=>{const ee=S?u+(u.includes("?")?"&":"?")+"force=1":u,v=await a(ee,{method:"POST",credentials:"same-origin",headers:{Accept:"application/json",requesttoken:l()},keepalive:!!M}),z=await v.json().catch(()=>({}));if(!v.ok)throw new Error(z&&z.message||"Sync request failed.");return z})();b=J;let U,F=null;try{U=await J}catch(ee){F=ee}finally{b===J&&(b=null),Y=!1}const ce=E,Q=G;if(E=!1,G=!1,ce)return I(!0,Q);if(F instanceof Error)throw F;return U};return{configure:je,start:be,stop:Z,sync:I,fireAndForget:le,installLifecycleHandlers:()=>{L||O||(L=()=>{if(document.visibilityState==="hidden"){le(!0,!0),Z();return}be()},O=()=>{le(!0,!0),Z()},document.addEventListener("visibilitychange",L),window.addEventListener("pagehide",O))},removeLifecycleHandlers:()=>{L&&(document.removeEventListener("visibilitychange",L),L=null),O&&(window.removeEventListener("pagehide",O),O=null)}}}const Io=(t,r,{preventScroll:a=!1}={})=>{const l=(t instanceof HTMLElement?t.querySelector("a, button"):null)||r;l instanceof HTMLElement&&(l===r&&(l.tabIndex=-1),l.focus({preventScroll:a}))};function Gt(t,r){(r==null||r>t.length)&&(r=t.length);for(var a=0,l=Array(r);a
2?a-2:0),u=2;u1?r-1:0),l=1;l"u"?null:T(BigInt.prototype.toString),Zt=typeof Symbol>"u"?null:T(Symbol.prototype.toString),D=T(Object.prototype.hasOwnProperty),_e=T(Object.prototype.toString),N=T(RegExp.prototype.test),ie=no(TypeError);function T(t){return function(r){r instanceof RegExp&&(r.lastIndex=0);for(var a=arguments.length,l=new Array(a>1?a-1:0),u=1;u2&&arguments[2]!==void 0?arguments[2]:Ne;if(qt&&qt(t,null),!ye(r))return t;let l=r.length;for(;l--;){let u=r[l];if(typeof u=="string"){const g=a(u);g!==u&&(Vn(r)||(r[l]=g),u=g)}t[u]=!0}return t}function oo(t){for(let r=0;r/g),uo=A(/\${[\w\W]*/g),fo=A(/^data-[\-\w.\u00B7-\uFFFF]+$/),mo=A(/^aria-[\-\w]+$/),nn=A(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),po=A(/^(?:\w+script|data):/i),ho=A(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),go=A(/^html$/i),yo=A(/^[a-z][.\w]*(-[.\w]+)+$/i),on=A(/<[/\w!]/g),rn=A(/<[/\w]/g),bo=A(/<\/no(script|embed|frames)/i),To=A(/\/>/i),k={element:1,attribute:2,text:3,cdataSection:4,entityReference:5,entityNode:6,processingInstruction:7,comment:8,document:9,documentType:10,documentFragment:11,notation:12},sn=["style","script","xmp","iframe","noembed","noframes","plaintext","noscript"],wo=w(m({},sn)),Ao=(function(){const t={};return ae(sn,r=>{t[r]=A(new RegExp(""+r+"(?=[\\t\\n\\f\\r />])","i"))}),w(t)})(),Eo=function(){return typeof window>"u"?null:window},So=function(t,r){if(typeof t!="object"||typeof t.createPolicy!="function")return null;let a=null;const l="data-tt-policy-suffix";r&&r.hasAttribute(l)&&(a=r.getAttribute(l));const u="dompurify"+(a?"#"+a:"");try{return t.createPolicy(u,{createHTML(g){return g},createScriptURL(g){return g}})}catch{return console.warn("TrustedTypes policy "+u+" could not be created."),null}},an=function(){return{afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]}},K=function(t,r,a,l){return D(t,r)&&ye(t[r])?m(l.base?R(l.base):{},t[r],l.transform):a},st=function(t,r,a){const l=D(t,r)?t[r]:void 0;return l&&typeof l=="object"?R(l):a()};function un(){let t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:Eo();const r=e=>un(e);if(r.version="3.4.15",r.removed=[],!t||!t.document||t.document.nodeType!==k.document||!t.Element)return r.isSupported=!1,r;let a=t.document;const l=a,u=l.currentScript;t.DocumentFragment;const g=t.HTMLTemplateElement,b=t.Node,Y=t.Element,E=t.NodeFilter;t.NamedNodeMap===void 0&&(t.NamedNodeMap||t.MozNamedAttrMap),t.HTMLFormElement;const q=t.DOMParser,L=t.trustedTypes,O=Y.prototype,je=C(O,"cloneNode"),Z=C(O,"remove"),be=C(O,"removeAttributeNode"),le=C(O,"nextSibling"),I=C(O,"childNodes"),S=C(O,"parentNode"),M=C(O,"shadowRoot"),J=C(O,"attributes"),U=b&&b.prototype?C(b.prototype,"nodeType"):null,F=b&&b.prototype?C(b.prototype,"nodeName"):null,ce=b&&b.prototype?C(b.prototype,"ownerDocument"):null,Q=function(e){return U?U(e):e.nodeType},ee=function(e){return F?F(e):e.nodeName};if(typeof g=="function"){const e=a.createElement("template");e.content&&e.content.ownerDocument&&(a=e.content.ownerDocument)}let v,z="",We,mt=!1,Te=0;const pt=function(){if(Te>0)throw ie('A configured TRUSTED_TYPES_POLICY callback (createHTML or createScriptURL) must not call DOMPurify.sanitize, as that causes infinite recursion. Do not pass a policy whose callbacks wrap DOMPurify as TRUSTED_TYPES_POLICY; see the "DOMPurify and Trusted Types" section of the README.')},se=function(e){pt(),Te++;try{return v.createHTML(e)}finally{Te--}},pn=function(e){pt(),Te++;try{return v.createScriptURL(e)}finally{Te--}},dn=function(){return mt||(We=So(L,u),mt=!0),We},Oe=a,Be=Oe.implementation,dt=Oe.createNodeIterator,hn=Oe.createDocumentFragment,gn=Oe.getElementsByTagName,yn=l.importNode;let p=an();r.isSupported=typeof ln=="function"&&typeof S=="function"&&Be&&Be.createHTMLDocument!==void 0;const bn=co,Tn=so,wn=uo,An=fo,En=mo,Sn=po,ht=ho,vn=yo;let gt=nn,d=null;const Ye=m({},[...Jt,...it,...at,...lt,...Qt]);let h=null;const Ge=m({},[...en,...ct,...tn,...He]);let H=Object.seal(ge(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),we=null,yt=null;const X=Object.seal(ge(null,{tagCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeCheck:{writable:!0,configurable:!1,enumerable:!0,value:null}}));let bt=!0,qe=!0,Tt=!1,wt=!0,V=!1,te=!0,ne=!1,Xe=!1,xe=null,De=null,Ve=!1,ue=!1,ke=!1,Re=!1,At=!0,Et=!1;const St="user-content-";let $e=!0,Ke=!1,fe={},me=null;const vt=m({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","selectedcontent","style","svg","template","thead","title","video","xmp"]);let _t=null;const Nt=m({},["audio","video","img","source","image","track"]);let Ot=null;const xt=m({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Ce="http://www.w3.org/1998/Math/MathML",Le="http://www.w3.org/2000/svg",j="http://www.w3.org/1999/xhtml";let pe=j,Ze=!1,Je=null;const _n=m({},[Ce,Le,j],rt),Dt=w(["mi","mo","mn","ms","mtext"]);let Qe=m({},Dt);const kt=w(["annotation-xml"]);let et=m({},kt);const Nn=m({},["title","style","font","a","script"]);let Ae=null;const On=["application/xhtml+xml","text/html"],xn="text/html";let y=null,de=null;const Dn=a.createElement("form"),Rt=function(e){return e instanceof RegExp||e instanceof Function},tt=function(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};if(de&&de===e)return;(!e||typeof e!="object")&&(e={}),e=R(e),Ae=On.indexOf(e.PARSER_MEDIA_TYPE)===-1?xn:e.PARSER_MEDIA_TYPE,y=Ae==="application/xhtml+xml"?rt:Ne,d=K(e,"ALLOWED_TAGS",Ye,{transform:y}),h=K(e,"ALLOWED_ATTR",Ge,{transform:y}),Je=K(e,"ALLOWED_NAMESPACES",_n,{transform:rt}),Ot=K(e,"ADD_URI_SAFE_ATTR",xt,{transform:y,base:xt}),_t=K(e,"ADD_DATA_URI_TAGS",Nt,{transform:y,base:Nt}),me=K(e,"FORBID_CONTENTS",vt,{transform:y}),we=K(e,"FORBID_TAGS",R({}),{transform:y}),yt=K(e,"FORBID_ATTR",R({}),{transform:y}),fe=D(e,"USE_PROFILES")?e.USE_PROFILES&&typeof e.USE_PROFILES=="object"?R(e.USE_PROFILES):e.USE_PROFILES:!1,bt=e.ALLOW_ARIA_ATTR!==!1,qe=e.ALLOW_DATA_ATTR!==!1,Tt=e.ALLOW_UNKNOWN_PROTOCOLS||!1,wt=e.ALLOW_SELF_CLOSE_IN_ATTR!==!1,V=e.SAFE_FOR_TEMPLATES||!1,te=e.SAFE_FOR_XML!==!1,ne=e.WHOLE_DOCUMENT||!1,ue=e.RETURN_DOM||!1,ke=e.RETURN_DOM_FRAGMENT||!1,Re=e.RETURN_TRUSTED_TYPE||!1,Ve=e.FORCE_BODY||!1,At=e.SANITIZE_DOM!==!1,Et=e.SANITIZE_NAMED_PROPS||!1,$e=e.KEEP_CONTENT!==!1,Ke=e.IN_PLACE||!1,gt=io(e.ALLOWED_URI_REGEXP)?e.ALLOWED_URI_REGEXP:nn,pe=typeof e.NAMESPACE=="string"?e.NAMESPACE:j,Qe=st(e,"MATHML_TEXT_INTEGRATION_POINTS",()=>m({},Dt)),et=st(e,"HTML_INTEGRATION_POINTS",()=>m({},kt));const n=st(e,"CUSTOM_ELEMENT_HANDLING",()=>ge(null));if(H=ge(null),D(n,"tagNameCheck")&&Rt(n.tagNameCheck)&&(H.tagNameCheck=n.tagNameCheck),D(n,"attributeNameCheck")&&Rt(n.attributeNameCheck)&&(H.attributeNameCheck=n.attributeNameCheck),D(n,"allowCustomizedBuiltInElements")&&typeof n.allowCustomizedBuiltInElements=="boolean"&&(H.allowCustomizedBuiltInElements=n.allowCustomizedBuiltInElements),A(H),V&&(qe=!1),ke&&(ue=!0),fe&&(d=m({},Qt),h=ge(null),fe.html===!0&&(m(d,Jt),m(h,en)),fe.svg===!0&&(m(d,it),m(h,ct),m(h,He)),fe.svgFilters===!0&&(m(d,at),m(h,ct),m(h,He)),fe.mathMl===!0&&(m(d,lt),m(h,tn),m(h,He))),X.tagCheck=null,X.attributeCheck=null,D(e,"ADD_TAGS")&&(typeof e.ADD_TAGS=="function"?X.tagCheck=e.ADD_TAGS:ye(e.ADD_TAGS)&&(d===Ye&&(d=R(d)),m(d,e.ADD_TAGS,y))),D(e,"ADD_ATTR")&&(typeof e.ADD_ATTR=="function"?X.attributeCheck=e.ADD_ATTR:ye(e.ADD_ATTR)&&(h===Ge&&(h=R(h)),m(h,e.ADD_ATTR,y))),D(e,"ADD_FORBID_CONTENTS")&&ye(e.ADD_FORBID_CONTENTS)&&(me===vt&&(me=R(me)),m(me,e.ADD_FORBID_CONTENTS,y)),$e&&(d["#text"]=!0),ne&&m(d,["html","head","body"]),d.table&&(m(d,["tbody"]),delete we.tbody),e.TRUSTED_TYPES_POLICY){if(typeof e.TRUSTED_TYPES_POLICY.createHTML!="function")throw ie('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if(typeof e.TRUSTED_TYPES_POLICY.createScriptURL!="function")throw ie('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');const o=v;v=e.TRUSTED_TYPES_POLICY;try{z=se("")}catch(i){throw v=o,i}}else e.TRUSTED_TYPES_POLICY===null?(v=void 0,z=""):(v===void 0&&(v=dn()),v&&typeof z=="string"&&(z=se("")));w&&w(e),de=e},Ct=m({},[...it,...at,...ao]),Lt=m({},[...lt,...lo]),kn=function(e,n,o){return n.namespaceURI===j?e==="svg":n.namespaceURI===Ce?e==="svg"&&(o==="annotation-xml"||Qe[o]):!!Ct[e]},Rn=function(e,n,o){return n.namespaceURI===j?e==="math":n.namespaceURI===Le?e==="math"&&et[o]:!!Lt[e]},Cn=function(e,n,o){return n.namespaceURI===Le&&!et[o]||n.namespaceURI===Ce&&!Qe[o]?!1:!Lt[e]&&(Nn[e]||!Ct[e])},Ln=function(e){let n=S(e);(!n||!n.tagName)&&(n={namespaceURI:pe,tagName:"template"});const o=Ne(e.tagName),i=Ne(n.tagName);return Je[e.namespaceURI]?e.namespaceURI===Le?kn(o,n,i):e.namespaceURI===Ce?Rn(o,n,i):e.namespaceURI===j?Cn(o,n,i):!!(Ae==="application/xhtml+xml"&&Je[e.namespaceURI]):!1},oe=function(e){Se(r.removed,{element:e});try{S(e).removeChild(e)}catch{if(Z(e),!S(e))throw ie("a node selected for removal could not be detached from its tree and cannot be safely returned; refusing to sanitize in place")}},It=function(e,n,o){try{be(e,n)}catch{try{e.removeAttribute(o)}catch{}}},Ie=function(e){Me(e);const n=I(e);if(n){const i=[];ae(n,c=>{Se(i,c)}),ae(i,c=>{try{Z(c)}catch{}})}const o=J(e);if(o)for(let i=o.length-1;i>=0;--i){const c=o[i],s=c&&c.name;typeof s=="string"&&It(e,c,s)}},re=function(e,n,o){if(!o)try{o=n.getAttributeNode(e)}catch{o=null}Se(r.removed,{attribute:o||null,from:n});try{o?be(n,o):n.removeAttribute(e)}catch{try{n.removeAttribute(e)}catch{}}if(e==="is")if(ue||ke)try{oe(n)}catch{}else try{n.setAttribute(e,"")}catch{}},In=function(e){const n=J(e);if(n)for(let o=n.length-1;o>=0;--o){const i=n[o],c=i&&i.name;typeof c!="string"||h[y(c)]||It(e,i,c)}},Me=function(e){const n=[e];for(;n.length>0;){const o=n.pop();Q(o)===k.element&&In(o);const i=I(o);if(i)for(let c=i.length-1;c>=0;--c)n.push(i[c])}},Mt=function(e,n){return te?e==="patchsrc"?!0:e==="for"&&n!=="label"&&n!=="output":!1},Mn=function(e){if(!te)return;const n=[e];for(;n.length>0;){const o=n.pop(),i=Q(o);if(i===k.processingInstruction||i===k.comment&&N(rn,o.data)){try{Z(o)}catch{}continue}if(i===k.element){const s=o,f=y(ee(o));try{s.hasAttribute&&s.hasAttribute("patchsrc")&&s.removeAttribute("patchsrc"),s.hasAttribute&&s.hasAttribute("for")&&Mt("for",f)&&s.removeAttribute("for")}catch{}}const c=I(o);if(c)for(let s=c.length-1;s>=0;--s)n.push(c[s])}},zt=function(e){let n=null,o=null;if(Ve)e=""+e;else{const s=Vt(e,/^[\r\n\t ]+/);o=s&&s[0]}Ae==="application/xhtml+xml"&&pe===j&&(e=''+e+"");const i=v?se(e):e;if(pe===j)try{n=new q().parseFromString(i,Ae)}catch{}if(!n||!n.documentElement){n=Be.createDocument(pe,"template",null);try{n.documentElement.innerHTML=Ze?z:i}catch{}}const c=n.body||n.documentElement;return e&&o&&c.insertBefore(a.createTextNode(o),c.childNodes[0]||null),pe===j?gn.call(n,ne?"html":"body")[0]:ne?n.documentElement:c},Pt=function(e){const n=ce?ce(e):e.ownerDocument;return dt.call(n||e,e,E.SHOW_ELEMENT|E.SHOW_COMMENT|E.SHOW_TEXT|E.SHOW_PROCESSING_INSTRUCTION|E.SHOW_CDATA_SECTION,null)},ze=function(e){return e=ve(e,bn," "),e=ve(e,Tn," "),e=ve(e,wn," "),e},nt=function(e){var n;e.normalize();const o=ce?ce(e):e.ownerDocument,i=dt.call(o||e,e,E.SHOW_TEXT|E.SHOW_COMMENT|E.SHOW_CDATA_SECTION|E.SHOW_PROCESSING_INSTRUCTION,null);let c=i.nextNode();for(;c;)c.data=ze(c.data),c=i.nextNode();const s=(n=e.querySelectorAll)===null||n===void 0?void 0:n.call(e,"template");s&&ae(s,f=>{he(f.content)&&nt(f.content)})},Pe=function(e){const n=F?F(e):null;return typeof n!="string"||y(n)!=="form"?!1:typeof e.nodeName!="string"||typeof e.textContent!="string"||typeof e.removeChild!="function"||e.attributes!==J(e)||typeof e.removeAttribute!="function"||typeof e.removeAttributeNode!="function"||typeof e.getAttributeNode!="function"||typeof e.setAttribute!="function"||typeof e.namespaceURI!="string"||typeof e.insertBefore!="function"||typeof e.hasChildNodes!="function"||e.nodeType!==U(e)||e.childNodes!==I(e)},he=function(e){if(!U||typeof e!="object"||e===null)return!1;try{return U(e)===k.documentFragment}catch{return!1}},Ee=function(e){if(!U||typeof e!="object"||e===null)return!1;try{return typeof U(e)=="number"}catch{return!1}};function W(e,n,o){e.length!==0&&ae(e,i=>{i.call(r,n,o,de)})}const zn=function(e,n){return!!(te&&e.hasChildNodes()&&!Ee(e.firstElementChild)&&N(on,e.textContent)&&N(on,e.innerHTML)||te&&e.namespaceURI===j&&wo[n]&&(Ee(e.firstElementChild)||typeof e.textContent=="string"&&N(Ao[n],e.textContent))||e.nodeType===k.processingInstruction||te&&e.nodeType===k.comment&&N(rn,e.data))},Ue=function(e,n){if(e instanceof RegExp)return N(e,n);if(e instanceof Function){for(var o=arguments.length,i=new Array(o>2?o-2:0),c=2;c=0;--f){const B=e===o?je(c[f],!0):c[f];i.insertBefore(B,le(e))}}}return oe(e),!0},Ut=function(e,n,o,i){return e.length===0?n:n===o||n===i?R(n):n},Ft=function(e,n){return e===n||S(e)!==null?!1:(Ke&&Me(e),!0)},Ht=function(e,n){if(W(p.beforeSanitizeElements,e,null),Ft(e,n))return!0;if(Pe(e))return oe(e),!0;const o=y(ee(e));if(d=Ut(p.uponSanitizeElement,d,Ye,xe),W(p.uponSanitizeElement,e,{tagName:o,allowedTags:d}),Ft(e,n))return!0;if(zn(e,o))return oe(e),!0;if(we[o]||!(X.tagCheck instanceof Function&&X.tagCheck(o))&&!d[o]){const i=Pn(e,o,n);return i===!1&&W(p.afterSanitizeElements,e,null),i}if(Q(e)===k.element&&!Ln(e)||(o==="noscript"||o==="noembed"||o==="noframes")&&N(bo,e.innerHTML))return oe(e),!0;if(V&&e.nodeType===k.text){const i=ze(e.textContent);e.textContent!==i&&(Se(r.removed,{element:e.cloneNode()}),e.textContent=i)}return W(p.afterSanitizeElements,e,null),!1},jt=function(e,n,o){if(yt[n]||Mt(n,e)||At&&(n==="id"||n==="name")&&(o in a||o in Dn))return!1;const i=h[n]||X.attributeCheck instanceof Function&&X.attributeCheck(n,e);return qe&&N(An,n)||bt&&N(En,n)?!0:i?Ot[n]||N(gt,ve(o,ht,""))||(n==="src"||n==="xlink:href"||n==="href")&&e!=="script"&&$t(o,"data:")===0&&_t[e]||Tt&&!N(Sn,ve(o,ht,""))?!0:!o:Wt(e)&&Ue(H.tagNameCheck,e)&&Ue(H.attributeNameCheck,n,e)||n==="is"&&H.allowCustomizedBuiltInElements&&Ue(H.tagNameCheck,o)},Un=m({},["annotation-xml","color-profile","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","missing-glyph"]),Wt=function(e){return!Un[Ne(e)]&&N(vn,e)},Fn=function(e,n,o,i){if(v&&typeof L=="object"&&typeof L.getAttributeType=="function"&&!o)switch(L.getAttributeType(e,n)){case"TrustedHTML":return se(i);case"TrustedScriptURL":return pn(i)}return i},Hn=function(e,n,o,i){try{return o?e.setAttributeNS(o,n,i):e.setAttribute(n,i),Pe(e)?(oe(e),!1):!0}catch{return re(n,e),!1}},Bt=function(e){W(p.beforeSanitizeAttributes,e,null);const n=e.attributes;if(!n||Pe(e))return;h=Ut(p.uponSanitizeAttribute,h,Ge,De);const o={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:h,forceKeepAttr:void 0};let i=n.length;const c=y(e.nodeName);for(;i--;){const s=n[i],f=s.name,B=s.namespaceURI,$=s.value,_=y(f),P=$;let x=f==="value"?P:Qn(P),Yt=!1;if(o.attrName=_,o.attrValue=x,o.keepAttr=!0,o.forceKeepAttr=void 0,W(p.uponSanitizeAttribute,e,o),x=o.attrValue,Et&&(_==="id"||_==="name")&&$t(x,St)!==0&&(re(f,e,s),x=St+x,Yt=!0),te&&N(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i,x)){re(f,e,s);continue}if(_==="attributename"&&Vt(x,"href")){re(f,e,s);continue}if(!o.forceKeepAttr){if(!o.keepAttr){re(f,e,s);continue}if(!wt&&N(To,x)){re(f,e,s);continue}if(V&&(x=ze(x)),!jt(c,_,x)){re(f,e,s);continue}x=Fn(c,_,B,x),x!==P&&Hn(e,f,B,x)&&Yt&&Xt(r.removed)}}W(p.afterSanitizeAttributes,e,null)},Fe=function(e){let n=null;const o=Pt(e);for(W(p.beforeSanitizeShadowDOM,e,null);n=o.nextNode();)if(W(p.uponSanitizeShadowNode,n,null),Ht(n,e),Bt(n),he(n.content)&&Fe(n.content),Q(n)===k.element){const i=M(n);he(i)&&(ot(i),Fe(i))}W(p.afterSanitizeShadowDOM,e,null)},ot=function(e){const n=[{node:e,shadow:null}];for(;n.length>0;){const o=n.pop();if(o.shadow){Fe(o.shadow);continue}const i=o.node,c=Q(i)===k.element,s=I(i);if(s)for(let f=s.length-1;f>=0;--f)n.push({node:s[f],shadow:null});if(c){const f=F?F(i):null;if(typeof f=="string"&&y(f)==="template"){const B=i.content;he(B)&&n.push({node:B,shadow:null})}}if(c){const f=M(i);he(f)&&n.push({node:null,shadow:f},{node:f,shadow:null})}}};return r.sanitize=function(e){let n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},o=null,i=null,c=null,s=null;if(Ze=!e,Ze&&(e=""),typeof e!="string"&&!Ee(e)&&(e=ro(e),typeof e!="string"))throw ie("dirty is not a string, aborting");if(!r.isSupported)return e;Xe?(d=xe,h=De):tt(n),(p.uponSanitizeElement.length>0||p.uponSanitizeAttribute.length>0)&&(d=R(d)),p.uponSanitizeAttribute.length>0&&(h=R(h)),r.removed=[];const f=Ke&&typeof e!="string"&&Ee(e);if(f){Mn(e);const _=ee(e);if(typeof _=="string"){const P=y(_);if(!d[P]||we[P])throw Ie(e),ie("root node is forbidden and cannot be sanitized in-place")}if(Pe(e))throw Ie(e),ie("root node is clobbered and cannot be sanitized in-place");try{ot(e)}catch(P){throw Ie(e),P}}else if(Ee(e))o=zt(""),i=o.ownerDocument.importNode(e,!0),i.nodeType===k.element&&i.nodeName==="BODY"||i.nodeName==="HTML"?o=i:o.appendChild(i),ot(o);else{if(!ue&&!V&&!ne&&e.indexOf("<")===-1)return v&&Re?se(e):e;if(o=zt(e),!o)return ue?null:Re?z:""}o&&Ve&&oe(o.firstChild);const B=f?e:o;try{const _=Pt(B);for(;c=_.nextNode();)Ht(c,B),Bt(c),he(c.content)&&Fe(c.content)}catch(_){throw f&&(Ie(e),ae(r.removed,P=>{P.element&&Me(P.element)})),_}if(f)return ae(r.removed,_=>{_.element&&Me(_.element)}),V&&nt(e),e;if(ue){if(V&&nt(o),ke)for(s=hn.call(o.ownerDocument);o.firstChild;)s.appendChild(o.firstChild);else s=o;return(h.shadowroot||h.shadowrootmode)&&(s=yn.call(l,s,!0)),s}let $=ne?o.outerHTML:o.innerHTML;return ne&&d["!doctype"]&&o.ownerDocument&&o.ownerDocument.doctype&&o.ownerDocument.doctype.name&&N(go,o.ownerDocument.doctype.name)&&($="
-`+$),V&&($=ze($)),v&&Re?se($):$},r.setConfig=function(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};tt(e),Xe=!0,xe=d,De=h},r.clearConfig=function(){de=null,Xe=!1,xe=null,De=null,v=We,z=""},r.isValidAttribute=function(e,n,o){de||tt({});const i=y(e),c=y(n);return jt(i,c,o)},r.addHook=function(e,n){typeof n=="function"&&D(p,e)&&Se(p[e],n)},r.removeHook=function(e,n){if(D(p,e)){if(n!==void 0){const o=Zn(p[e],n);return o===-1?void 0:Jn(p[e],o,1)[0]}return Xt(p[e])}},r.removeHooks=function(e){D(p,e)&&(p[e]=[])},r.removeAllHooks=function(){p=an()},r}var fn=un();const vo=["p","br","ul","ol","li","h1","h2","h3","h4","h5","h6","strong","b","em","i","u","s","del","blockquote","pre","code","a"],_o=/^(?:https?|mailto):/i;fn.addHook("afterSanitizeAttributes",t=>{if(t.tagName==="A"){if(!t.hasAttribute("href")){t.replaceWith(...t.childNodes);return}t.setAttribute("target","_blank"),t.setAttribute("rel","noopener noreferrer")}});function No(t){return fn.sanitize(String(t??""),{ALLOWED_TAGS:vo,ALLOWED_ATTR:["href"],ALLOWED_URI_REGEXP:_o,ALLOW_DATA_ATTR:!1})}const Oo=25e3,Mo=async(t,{signal:r}={})=>{const a=await jn(t,{method:"GET",signal:r},{timeoutMs:Oo,fallbackMessage:"Could not load the pad content."});if(!a||typeof a!="object"||typeof a.html!="string"||typeof a.is_empty!="boolean")throw new Error("Could not load the pad content.");const l=No(a.html);return{html:l,isEmpty:a.is_empty||l.trim()===""}},xo=5e3,Do=2147483647,ko=12e4,Ro=t=>!!t&&t.code==="missing_frontmatter",zo=t=>!!t&&t.code==="missing_binding",Po=t=>!!t&&(t.retryable===!0||t.code==="pad_file_changed"||t.unanswered===!0),Uo=t=>{if(!t||t.is_readonly_view!==!0&&mn(t)==="")throw new Error("Pad open API did not return a valid URL.");return t},mn=t=>t&&typeof t.url=="string"?t.url.trim():"",Fo=async({open:t,initialize:r,stillWanted:a=()=>!0})=>{try{return await t()}catch(l){if(!Ro(l))throw l;return await r(),a()?await t():null}},Ho=t=>{const r=Number(t&&t.sync_interval_seconds);return{syncUrl:t&&typeof t.sync_url=="string"?t.sync_url.trim():"",intervalMs:Number.isFinite(r)&&r>0?Math.min(Do,Math.max(xo,r*1e3)):ko}},jo=t=>t&&typeof t.content_url=="string"?t.content_url.trim():"",Wo=t=>{const r=!!(t&&t.is_readonly_view===!0),a=mn(t),l=!!(t&&t.is_external===!0)&&a!=="";return{isContentView:r||l,externalUrl:l&&!r?a:""}};export{Wo as a,Po as b,jo as c,Uo as d,Lo as e,Io as h,zo as i,Mo as l,Fo as o,mn as p,Ho as s};
-//# sourceMappingURL=pad-open-flow-D6KDQoVq.chunk.mjs.map
+import{f as jn,a as Wn}from"./fetch-helpers-BUxbvlK6.chunk.mjs";const Bn=12e4;function Io({requestToken:t,fetchImpl:r}={}){const a=typeof r=="function"?r:(...S)=>window.fetch(...S),l=typeof t=="function"?t:()=>"";let u="",g=Bn,b=null,Y=!1,E=!1,G=!1,q=null,L=null,O=null;const je=({syncUrl:S,intervalMs:M}={})=>{typeof S=="string"&&(u=S),Number.isFinite(M)&&M>0&&(g=M)},Z=()=>{q!==null&&(window.clearInterval(q),q=null)},be=()=>{!u||q!==null||(q=window.setInterval(()=>{document.visibilityState==="visible"&&le(!1,!1)},g))},le=(S,M)=>{I(S,M).catch(()=>{})},I=async(S,M)=>{if(!u)return{status:"disabled"};if(b)return S&&!Y?(E=!0,G=G||!!M,b.catch(()=>{}).then(()=>I(!0,G))):b;Y=!!S;const J=(async()=>{const ee=S?u+(u.includes("?")?"&":"?")+"force=1":u,v=await a(ee,{method:"POST",credentials:"same-origin",headers:{Accept:"application/json",requesttoken:l()},keepalive:!!M}),z=await v.json().catch(()=>({}));if(!v.ok)throw new Error(z&&z.message||"Sync request failed.");return z})();b=J;let U,F=null;try{U=await J}catch(ee){F=ee}finally{b===J&&(b=null),Y=!1}const ce=E,Q=G;if(E=!1,G=!1,ce)return I(!0,Q);if(F instanceof Error)throw F;return U};return{configure:je,start:be,stop:Z,sync:I,fireAndForget:le,installLifecycleHandlers:()=>{L||O||(L=()=>{if(document.visibilityState==="hidden"){le(!0,!0),Z();return}be()},O=()=>{le(!0,!0),Z()},document.addEventListener("visibilitychange",L),window.addEventListener("pagehide",O))},removeLifecycleHandlers:()=>{L&&(document.removeEventListener("visibilitychange",L),L=null),O&&(window.removeEventListener("pagehide",O),O=null)}}}const Mo=(t,r,{preventScroll:a=!1}={})=>{const l=(t instanceof HTMLElement?t.querySelector("a, button"):null)||r;l instanceof HTMLElement&&(l===r&&(l.tabIndex=-1),l.focus({preventScroll:a}))};function Gt(t,r){(r==null||r>t.length)&&(r=t.length);for(var a=0,l=Array(r);a2?a-2:0),u=2;u1?r-1:0),l=1;l"u"?null:T(BigInt.prototype.toString),Zt=typeof Symbol>"u"?null:T(Symbol.prototype.toString),D=T(Object.prototype.hasOwnProperty),_e=T(Object.prototype.toString),N=T(RegExp.prototype.test),ie=oo(TypeError);function T(t){return function(r){r instanceof RegExp&&(r.lastIndex=0);for(var a=arguments.length,l=new Array(a>1?a-1:0),u=1;u2&&arguments[2]!==void 0?arguments[2]:Ne;if(qt&&qt(t,null),!ye(r))return t;let l=r.length;for(;l--;){let u=r[l];if(typeof u=="string"){const g=a(u);g!==u&&($n(r)||(r[l]=g),u=g)}t[u]=!0}return t}function ro(t){for(let r=0;r/g),fo=A(/\${[\w\W]*/g),mo=A(/^data-[\-\w.\u00B7-\uFFFF]+$/),po=A(/^aria-[\-\w]+$/),nn=A(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),ho=A(/^(?:\w+script|data):/i),go=A(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),yo=A(/^html$/i),bo=A(/^[a-z][.\w]*(-[.\w]+)+$/i),on=A(/<[/\w!]/g),rn=A(/<[/\w]/g),To=A(/<\/no(script|embed|frames)/i),wo=A(/\/>/i),k={element:1,attribute:2,text:3,cdataSection:4,entityReference:5,entityNode:6,processingInstruction:7,comment:8,document:9,documentType:10,documentFragment:11,notation:12},sn=["style","script","xmp","iframe","noembed","noframes","plaintext","noscript"],Ao=w(m({},sn)),Eo=(function(){const t={};return ae(sn,r=>{t[r]=A(new RegExp(""+r+"(?=[\\t\\n\\f\\r />])","i"))}),w(t)})(),So=function(){return typeof window>"u"?null:window},vo=function(t,r){if(typeof t!="object"||typeof t.createPolicy!="function")return null;let a=null;const l="data-tt-policy-suffix";r&&r.hasAttribute(l)&&(a=r.getAttribute(l));const u="dompurify"+(a?"#"+a:"");try{return t.createPolicy(u,{createHTML(g){return g},createScriptURL(g){return g}})}catch{return console.warn("TrustedTypes policy "+u+" could not be created."),null}},an=function(){return{afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]}},K=function(t,r,a,l){return D(t,r)&&ye(t[r])?m(l.base?R(l.base):{},t[r],l.transform):a},st=function(t,r,a){const l=D(t,r)?t[r]:void 0;return l&&typeof l=="object"?R(l):a()};function un(){let t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:So();const r=e=>un(e);if(r.version="3.4.15",r.removed=[],!t||!t.document||t.document.nodeType!==k.document||!t.Element)return r.isSupported=!1,r;let a=t.document;const l=a,u=l.currentScript;t.DocumentFragment;const g=t.HTMLTemplateElement,b=t.Node,Y=t.Element,E=t.NodeFilter;t.NamedNodeMap===void 0&&(t.NamedNodeMap||t.MozNamedAttrMap),t.HTMLFormElement;const q=t.DOMParser,L=t.trustedTypes,O=Y.prototype,je=C(O,"cloneNode"),Z=C(O,"remove"),be=C(O,"removeAttributeNode"),le=C(O,"nextSibling"),I=C(O,"childNodes"),S=C(O,"parentNode"),M=C(O,"shadowRoot"),J=C(O,"attributes"),U=b&&b.prototype?C(b.prototype,"nodeType"):null,F=b&&b.prototype?C(b.prototype,"nodeName"):null,ce=b&&b.prototype?C(b.prototype,"ownerDocument"):null,Q=function(e){return U?U(e):e.nodeType},ee=function(e){return F?F(e):e.nodeName};if(typeof g=="function"){const e=a.createElement("template");e.content&&e.content.ownerDocument&&(a=e.content.ownerDocument)}let v,z="",We,mt=!1,Te=0;const pt=function(){if(Te>0)throw ie('A configured TRUSTED_TYPES_POLICY callback (createHTML or createScriptURL) must not call DOMPurify.sanitize, as that causes infinite recursion. Do not pass a policy whose callbacks wrap DOMPurify as TRUSTED_TYPES_POLICY; see the "DOMPurify and Trusted Types" section of the README.')},se=function(e){pt(),Te++;try{return v.createHTML(e)}finally{Te--}},pn=function(e){pt(),Te++;try{return v.createScriptURL(e)}finally{Te--}},dn=function(){return mt||(We=vo(L,u),mt=!0),We},Oe=a,Be=Oe.implementation,dt=Oe.createNodeIterator,hn=Oe.createDocumentFragment,gn=Oe.getElementsByTagName,yn=l.importNode;let p=an();r.isSupported=typeof ln=="function"&&typeof S=="function"&&Be&&Be.createHTMLDocument!==void 0;const bn=so,Tn=uo,wn=fo,An=mo,En=po,Sn=ho,ht=go,vn=bo;let gt=nn,d=null;const Ye=m({},[...Jt,...it,...at,...lt,...Qt]);let h=null;const Ge=m({},[...en,...ct,...tn,...He]);let H=Object.seal(ge(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),we=null,yt=null;const V=Object.seal(ge(null,{tagCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeCheck:{writable:!0,configurable:!1,enumerable:!0,value:null}}));let bt=!0,qe=!0,Tt=!1,wt=!0,X=!1,te=!0,ne=!1,Ve=!1,xe=null,De=null,Xe=!1,ue=!1,ke=!1,Re=!1,At=!0,Et=!1;const St="user-content-";let $e=!0,Ke=!1,fe={},me=null;const vt=m({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","selectedcontent","style","svg","template","thead","title","video","xmp"]);let _t=null;const Nt=m({},["audio","video","img","source","image","track"]);let Ot=null;const xt=m({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Ce="http://www.w3.org/1998/Math/MathML",Le="http://www.w3.org/2000/svg",j="http://www.w3.org/1999/xhtml";let pe=j,Ze=!1,Je=null;const _n=m({},[Ce,Le,j],rt),Dt=w(["mi","mo","mn","ms","mtext"]);let Qe=m({},Dt);const kt=w(["annotation-xml"]);let et=m({},kt);const Nn=m({},["title","style","font","a","script"]);let Ae=null;const On=["application/xhtml+xml","text/html"],xn="text/html";let y=null,de=null;const Dn=a.createElement("form"),Rt=function(e){return e instanceof RegExp||e instanceof Function},tt=function(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};if(de&&de===e)return;(!e||typeof e!="object")&&(e={}),e=R(e),Ae=On.indexOf(e.PARSER_MEDIA_TYPE)===-1?xn:e.PARSER_MEDIA_TYPE,y=Ae==="application/xhtml+xml"?rt:Ne,d=K(e,"ALLOWED_TAGS",Ye,{transform:y}),h=K(e,"ALLOWED_ATTR",Ge,{transform:y}),Je=K(e,"ALLOWED_NAMESPACES",_n,{transform:rt}),Ot=K(e,"ADD_URI_SAFE_ATTR",xt,{transform:y,base:xt}),_t=K(e,"ADD_DATA_URI_TAGS",Nt,{transform:y,base:Nt}),me=K(e,"FORBID_CONTENTS",vt,{transform:y}),we=K(e,"FORBID_TAGS",R({}),{transform:y}),yt=K(e,"FORBID_ATTR",R({}),{transform:y}),fe=D(e,"USE_PROFILES")?e.USE_PROFILES&&typeof e.USE_PROFILES=="object"?R(e.USE_PROFILES):e.USE_PROFILES:!1,bt=e.ALLOW_ARIA_ATTR!==!1,qe=e.ALLOW_DATA_ATTR!==!1,Tt=e.ALLOW_UNKNOWN_PROTOCOLS||!1,wt=e.ALLOW_SELF_CLOSE_IN_ATTR!==!1,X=e.SAFE_FOR_TEMPLATES||!1,te=e.SAFE_FOR_XML!==!1,ne=e.WHOLE_DOCUMENT||!1,ue=e.RETURN_DOM||!1,ke=e.RETURN_DOM_FRAGMENT||!1,Re=e.RETURN_TRUSTED_TYPE||!1,Xe=e.FORCE_BODY||!1,At=e.SANITIZE_DOM!==!1,Et=e.SANITIZE_NAMED_PROPS||!1,$e=e.KEEP_CONTENT!==!1,Ke=e.IN_PLACE||!1,gt=ao(e.ALLOWED_URI_REGEXP)?e.ALLOWED_URI_REGEXP:nn,pe=typeof e.NAMESPACE=="string"?e.NAMESPACE:j,Qe=st(e,"MATHML_TEXT_INTEGRATION_POINTS",()=>m({},Dt)),et=st(e,"HTML_INTEGRATION_POINTS",()=>m({},kt));const n=st(e,"CUSTOM_ELEMENT_HANDLING",()=>ge(null));if(H=ge(null),D(n,"tagNameCheck")&&Rt(n.tagNameCheck)&&(H.tagNameCheck=n.tagNameCheck),D(n,"attributeNameCheck")&&Rt(n.attributeNameCheck)&&(H.attributeNameCheck=n.attributeNameCheck),D(n,"allowCustomizedBuiltInElements")&&typeof n.allowCustomizedBuiltInElements=="boolean"&&(H.allowCustomizedBuiltInElements=n.allowCustomizedBuiltInElements),A(H),X&&(qe=!1),ke&&(ue=!0),fe&&(d=m({},Qt),h=ge(null),fe.html===!0&&(m(d,Jt),m(h,en)),fe.svg===!0&&(m(d,it),m(h,ct),m(h,He)),fe.svgFilters===!0&&(m(d,at),m(h,ct),m(h,He)),fe.mathMl===!0&&(m(d,lt),m(h,tn),m(h,He))),V.tagCheck=null,V.attributeCheck=null,D(e,"ADD_TAGS")&&(typeof e.ADD_TAGS=="function"?V.tagCheck=e.ADD_TAGS:ye(e.ADD_TAGS)&&(d===Ye&&(d=R(d)),m(d,e.ADD_TAGS,y))),D(e,"ADD_ATTR")&&(typeof e.ADD_ATTR=="function"?V.attributeCheck=e.ADD_ATTR:ye(e.ADD_ATTR)&&(h===Ge&&(h=R(h)),m(h,e.ADD_ATTR,y))),D(e,"ADD_FORBID_CONTENTS")&&ye(e.ADD_FORBID_CONTENTS)&&(me===vt&&(me=R(me)),m(me,e.ADD_FORBID_CONTENTS,y)),$e&&(d["#text"]=!0),ne&&m(d,["html","head","body"]),d.table&&(m(d,["tbody"]),delete we.tbody),e.TRUSTED_TYPES_POLICY){if(typeof e.TRUSTED_TYPES_POLICY.createHTML!="function")throw ie('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if(typeof e.TRUSTED_TYPES_POLICY.createScriptURL!="function")throw ie('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');const o=v;v=e.TRUSTED_TYPES_POLICY;try{z=se("")}catch(i){throw v=o,i}}else e.TRUSTED_TYPES_POLICY===null?(v=void 0,z=""):(v===void 0&&(v=dn()),v&&typeof z=="string"&&(z=se("")));w&&w(e),de=e},Ct=m({},[...it,...at,...lo]),Lt=m({},[...lt,...co]),kn=function(e,n,o){return n.namespaceURI===j?e==="svg":n.namespaceURI===Ce?e==="svg"&&(o==="annotation-xml"||Qe[o]):!!Ct[e]},Rn=function(e,n,o){return n.namespaceURI===j?e==="math":n.namespaceURI===Le?e==="math"&&et[o]:!!Lt[e]},Cn=function(e,n,o){return n.namespaceURI===Le&&!et[o]||n.namespaceURI===Ce&&!Qe[o]?!1:!Lt[e]&&(Nn[e]||!Ct[e])},Ln=function(e){let n=S(e);(!n||!n.tagName)&&(n={namespaceURI:pe,tagName:"template"});const o=Ne(e.tagName),i=Ne(n.tagName);return Je[e.namespaceURI]?e.namespaceURI===Le?kn(o,n,i):e.namespaceURI===Ce?Rn(o,n,i):e.namespaceURI===j?Cn(o,n,i):!!(Ae==="application/xhtml+xml"&&Je[e.namespaceURI]):!1},oe=function(e){Se(r.removed,{element:e});try{S(e).removeChild(e)}catch{if(Z(e),!S(e))throw ie("a node selected for removal could not be detached from its tree and cannot be safely returned; refusing to sanitize in place")}},It=function(e,n,o){try{be(e,n)}catch{try{e.removeAttribute(o)}catch{}}},Ie=function(e){Me(e);const n=I(e);if(n){const i=[];ae(n,c=>{Se(i,c)}),ae(i,c=>{try{Z(c)}catch{}})}const o=J(e);if(o)for(let i=o.length-1;i>=0;--i){const c=o[i],s=c&&c.name;typeof s=="string"&&It(e,c,s)}},re=function(e,n,o){if(!o)try{o=n.getAttributeNode(e)}catch{o=null}Se(r.removed,{attribute:o||null,from:n});try{o?be(n,o):n.removeAttribute(e)}catch{try{n.removeAttribute(e)}catch{}}if(e==="is")if(ue||ke)try{oe(n)}catch{}else try{n.setAttribute(e,"")}catch{}},In=function(e){const n=J(e);if(n)for(let o=n.length-1;o>=0;--o){const i=n[o],c=i&&i.name;typeof c!="string"||h[y(c)]||It(e,i,c)}},Me=function(e){const n=[e];for(;n.length>0;){const o=n.pop();Q(o)===k.element&&In(o);const i=I(o);if(i)for(let c=i.length-1;c>=0;--c)n.push(i[c])}},Mt=function(e,n){return te?e==="patchsrc"?!0:e==="for"&&n!=="label"&&n!=="output":!1},Mn=function(e){if(!te)return;const n=[e];for(;n.length>0;){const o=n.pop(),i=Q(o);if(i===k.processingInstruction||i===k.comment&&N(rn,o.data)){try{Z(o)}catch{}continue}if(i===k.element){const s=o,f=y(ee(o));try{s.hasAttribute&&s.hasAttribute("patchsrc")&&s.removeAttribute("patchsrc"),s.hasAttribute&&s.hasAttribute("for")&&Mt("for",f)&&s.removeAttribute("for")}catch{}}const c=I(o);if(c)for(let s=c.length-1;s>=0;--s)n.push(c[s])}},zt=function(e){let n=null,o=null;if(Xe)e=""+e;else{const s=Xt(e,/^[\r\n\t ]+/);o=s&&s[0]}Ae==="application/xhtml+xml"&&pe===j&&(e=''+e+"");const i=v?se(e):e;if(pe===j)try{n=new q().parseFromString(i,Ae)}catch{}if(!n||!n.documentElement){n=Be.createDocument(pe,"template",null);try{n.documentElement.innerHTML=Ze?z:i}catch{}}const c=n.body||n.documentElement;return e&&o&&c.insertBefore(a.createTextNode(o),c.childNodes[0]||null),pe===j?gn.call(n,ne?"html":"body")[0]:ne?n.documentElement:c},Pt=function(e){const n=ce?ce(e):e.ownerDocument;return dt.call(n||e,e,E.SHOW_ELEMENT|E.SHOW_COMMENT|E.SHOW_TEXT|E.SHOW_PROCESSING_INSTRUCTION|E.SHOW_CDATA_SECTION,null)},ze=function(e){return e=ve(e,bn," "),e=ve(e,Tn," "),e=ve(e,wn," "),e},nt=function(e){var n;e.normalize();const o=ce?ce(e):e.ownerDocument,i=dt.call(o||e,e,E.SHOW_TEXT|E.SHOW_COMMENT|E.SHOW_CDATA_SECTION|E.SHOW_PROCESSING_INSTRUCTION,null);let c=i.nextNode();for(;c;)c.data=ze(c.data),c=i.nextNode();const s=(n=e.querySelectorAll)===null||n===void 0?void 0:n.call(e,"template");s&&ae(s,f=>{he(f.content)&&nt(f.content)})},Pe=function(e){const n=F?F(e):null;return typeof n!="string"||y(n)!=="form"?!1:typeof e.nodeName!="string"||typeof e.textContent!="string"||typeof e.removeChild!="function"||e.attributes!==J(e)||typeof e.removeAttribute!="function"||typeof e.removeAttributeNode!="function"||typeof e.getAttributeNode!="function"||typeof e.setAttribute!="function"||typeof e.namespaceURI!="string"||typeof e.insertBefore!="function"||typeof e.hasChildNodes!="function"||e.nodeType!==U(e)||e.childNodes!==I(e)},he=function(e){if(!U||typeof e!="object"||e===null)return!1;try{return U(e)===k.documentFragment}catch{return!1}},Ee=function(e){if(!U||typeof e!="object"||e===null)return!1;try{return typeof U(e)=="number"}catch{return!1}};function W(e,n,o){e.length!==0&&ae(e,i=>{i.call(r,n,o,de)})}const zn=function(e,n){return!!(te&&e.hasChildNodes()&&!Ee(e.firstElementChild)&&N(on,e.textContent)&&N(on,e.innerHTML)||te&&e.namespaceURI===j&&Ao[n]&&(Ee(e.firstElementChild)||typeof e.textContent=="string"&&N(Eo[n],e.textContent))||e.nodeType===k.processingInstruction||te&&e.nodeType===k.comment&&N(rn,e.data))},Ue=function(e,n){if(e instanceof RegExp)return N(e,n);if(e instanceof Function){for(var o=arguments.length,i=new Array(o>2?o-2:0),c=2;c=0;--f){const B=e===o?je(c[f],!0):c[f];i.insertBefore(B,le(e))}}}return oe(e),!0},Ut=function(e,n,o,i){return e.length===0?n:n===o||n===i?R(n):n},Ft=function(e,n){return e===n||S(e)!==null?!1:(Ke&&Me(e),!0)},Ht=function(e,n){if(W(p.beforeSanitizeElements,e,null),Ft(e,n))return!0;if(Pe(e))return oe(e),!0;const o=y(ee(e));if(d=Ut(p.uponSanitizeElement,d,Ye,xe),W(p.uponSanitizeElement,e,{tagName:o,allowedTags:d}),Ft(e,n))return!0;if(zn(e,o))return oe(e),!0;if(we[o]||!(V.tagCheck instanceof Function&&V.tagCheck(o))&&!d[o]){const i=Pn(e,o,n);return i===!1&&W(p.afterSanitizeElements,e,null),i}if(Q(e)===k.element&&!Ln(e)||(o==="noscript"||o==="noembed"||o==="noframes")&&N(To,e.innerHTML))return oe(e),!0;if(X&&e.nodeType===k.text){const i=ze(e.textContent);e.textContent!==i&&(Se(r.removed,{element:e.cloneNode()}),e.textContent=i)}return W(p.afterSanitizeElements,e,null),!1},jt=function(e,n,o){if(yt[n]||Mt(n,e)||At&&(n==="id"||n==="name")&&(o in a||o in Dn))return!1;const i=h[n]||V.attributeCheck instanceof Function&&V.attributeCheck(n,e);return qe&&N(An,n)||bt&&N(En,n)?!0:i?Ot[n]||N(gt,ve(o,ht,""))||(n==="src"||n==="xlink:href"||n==="href")&&e!=="script"&&$t(o,"data:")===0&&_t[e]||Tt&&!N(Sn,ve(o,ht,""))?!0:!o:Wt(e)&&Ue(H.tagNameCheck,e)&&Ue(H.attributeNameCheck,n,e)||n==="is"&&H.allowCustomizedBuiltInElements&&Ue(H.tagNameCheck,o)},Un=m({},["annotation-xml","color-profile","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","missing-glyph"]),Wt=function(e){return!Un[Ne(e)]&&N(vn,e)},Fn=function(e,n,o,i){if(v&&typeof L=="object"&&typeof L.getAttributeType=="function"&&!o)switch(L.getAttributeType(e,n)){case"TrustedHTML":return se(i);case"TrustedScriptURL":return pn(i)}return i},Hn=function(e,n,o,i){try{return o?e.setAttributeNS(o,n,i):e.setAttribute(n,i),Pe(e)?(oe(e),!1):!0}catch{return re(n,e),!1}},Bt=function(e){W(p.beforeSanitizeAttributes,e,null);const n=e.attributes;if(!n||Pe(e))return;h=Ut(p.uponSanitizeAttribute,h,Ge,De);const o={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:h,forceKeepAttr:void 0};let i=n.length;const c=y(e.nodeName);for(;i--;){const s=n[i],f=s.name,B=s.namespaceURI,$=s.value,_=y(f),P=$;let x=f==="value"?P:eo(P),Yt=!1;if(o.attrName=_,o.attrValue=x,o.keepAttr=!0,o.forceKeepAttr=void 0,W(p.uponSanitizeAttribute,e,o),x=o.attrValue,Et&&(_==="id"||_==="name")&&$t(x,St)!==0&&(re(f,e,s),x=St+x,Yt=!0),te&&N(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i,x)){re(f,e,s);continue}if(_==="attributename"&&Xt(x,"href")){re(f,e,s);continue}if(!o.forceKeepAttr){if(!o.keepAttr){re(f,e,s);continue}if(!wt&&N(wo,x)){re(f,e,s);continue}if(X&&(x=ze(x)),!jt(c,_,x)){re(f,e,s);continue}x=Fn(c,_,B,x),x!==P&&Hn(e,f,B,x)&&Yt&&Vt(r.removed)}}W(p.afterSanitizeAttributes,e,null)},Fe=function(e){let n=null;const o=Pt(e);for(W(p.beforeSanitizeShadowDOM,e,null);n=o.nextNode();)if(W(p.uponSanitizeShadowNode,n,null),Ht(n,e),Bt(n),he(n.content)&&Fe(n.content),Q(n)===k.element){const i=M(n);he(i)&&(ot(i),Fe(i))}W(p.afterSanitizeShadowDOM,e,null)},ot=function(e){const n=[{node:e,shadow:null}];for(;n.length>0;){const o=n.pop();if(o.shadow){Fe(o.shadow);continue}const i=o.node,c=Q(i)===k.element,s=I(i);if(s)for(let f=s.length-1;f>=0;--f)n.push({node:s[f],shadow:null});if(c){const f=F?F(i):null;if(typeof f=="string"&&y(f)==="template"){const B=i.content;he(B)&&n.push({node:B,shadow:null})}}if(c){const f=M(i);he(f)&&n.push({node:null,shadow:f},{node:f,shadow:null})}}};return r.sanitize=function(e){let n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},o=null,i=null,c=null,s=null;if(Ze=!e,Ze&&(e=""),typeof e!="string"&&!Ee(e)&&(e=io(e),typeof e!="string"))throw ie("dirty is not a string, aborting");if(!r.isSupported)return e;Ve?(d=xe,h=De):tt(n),(p.uponSanitizeElement.length>0||p.uponSanitizeAttribute.length>0)&&(d=R(d)),p.uponSanitizeAttribute.length>0&&(h=R(h)),r.removed=[];const f=Ke&&typeof e!="string"&&Ee(e);if(f){Mn(e);const _=ee(e);if(typeof _=="string"){const P=y(_);if(!d[P]||we[P])throw Ie(e),ie("root node is forbidden and cannot be sanitized in-place")}if(Pe(e))throw Ie(e),ie("root node is clobbered and cannot be sanitized in-place");try{ot(e)}catch(P){throw Ie(e),P}}else if(Ee(e))o=zt(""),i=o.ownerDocument.importNode(e,!0),i.nodeType===k.element&&i.nodeName==="BODY"||i.nodeName==="HTML"?o=i:o.appendChild(i),ot(o);else{if(!ue&&!X&&!ne&&e.indexOf("<")===-1)return v&&Re?se(e):e;if(o=zt(e),!o)return ue?null:Re?z:""}o&&Xe&&oe(o.firstChild);const B=f?e:o;try{const _=Pt(B);for(;c=_.nextNode();)Ht(c,B),Bt(c),he(c.content)&&Fe(c.content)}catch(_){throw f&&(Ie(e),ae(r.removed,P=>{P.element&&Me(P.element)})),_}if(f)return ae(r.removed,_=>{_.element&&Me(_.element)}),X&&nt(e),e;if(ue){if(X&&nt(o),ke)for(s=hn.call(o.ownerDocument);o.firstChild;)s.appendChild(o.firstChild);else s=o;return(h.shadowroot||h.shadowrootmode)&&(s=yn.call(l,s,!0)),s}let $=ne?o.outerHTML:o.innerHTML;return ne&&d["!doctype"]&&o.ownerDocument&&o.ownerDocument.doctype&&o.ownerDocument.doctype.name&&N(yo,o.ownerDocument.doctype.name)&&($="
+`+$),X&&($=ze($)),v&&Re?se($):$},r.setConfig=function(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};tt(e),Ve=!0,xe=d,De=h},r.clearConfig=function(){de=null,Ve=!1,xe=null,De=null,v=We,z=""},r.isValidAttribute=function(e,n,o){de||tt({});const i=y(e),c=y(n);return jt(i,c,o)},r.addHook=function(e,n){typeof n=="function"&&D(p,e)&&Se(p[e],n)},r.removeHook=function(e,n){if(D(p,e)){if(n!==void 0){const o=Jn(p[e],n);return o===-1?void 0:Qn(p[e],o,1)[0]}return Vt(p[e])}},r.removeHooks=function(e){D(p,e)&&(p[e]=[])},r.removeAllHooks=function(){p=an()},r}var fn=un();const _o=["p","br","ul","ol","li","h1","h2","h3","h4","h5","h6","strong","b","em","i","u","s","del","blockquote","pre","code","a"],No=/^(?:https?|mailto):/i;fn.addHook("afterSanitizeAttributes",t=>{if(t.tagName==="A"){if(!t.hasAttribute("href")){t.replaceWith(...t.childNodes);return}t.setAttribute("target","_blank"),t.setAttribute("rel","noopener noreferrer")}});function Oo(t){return fn.sanitize(String(t??""),{ALLOWED_TAGS:_o,ALLOWED_ATTR:["href"],ALLOWED_URI_REGEXP:No,ALLOW_DATA_ATTR:!1})}const xo=25e3,zo=async(t,{signal:r}={})=>{const a=await jn(t,{method:"GET",signal:r},{timeoutMs:xo,fallbackMessage:"Could not load the pad content."});if(!a||typeof a!="object"||typeof a.html!="string"||typeof a.is_empty!="boolean")throw new Error("Could not load the pad content.");const l=Oo(a.html);return{html:l,isEmpty:a.is_empty||l.trim()===""}},Do=5e3,ko=2147483647,Ro=12e4,Co=t=>!!t&&t.code==="missing_frontmatter",Po=t=>!!t&&t.code==="missing_binding",Uo=t=>!!t&&(t.retryable===!0||t.code==="pad_file_changed"||Wn(t)),Fo=t=>{if(!t||t.is_readonly_view!==!0&&mn(t)==="")throw new Error("Pad open API did not return a valid URL.");return t},mn=t=>t&&typeof t.url=="string"?t.url.trim():"",Ho=async({open:t,initialize:r,stillWanted:a=()=>!0})=>{try{return await t()}catch(l){if(!Co(l))throw l;return await r(),a()?await t():null}},jo=t=>{const r=Number(t&&t.sync_interval_seconds);return{syncUrl:t&&typeof t.sync_url=="string"?t.sync_url.trim():"",intervalMs:Number.isFinite(r)&&r>0?Math.min(ko,Math.max(Do,r*1e3)):Ro}},Wo=t=>t&&typeof t.content_url=="string"?t.content_url.trim():"",Bo=t=>{const r=!!(t&&t.is_readonly_view===!0),a=mn(t),l=!!(t&&t.is_external===!0)&&a!=="";return{isContentView:r||l,externalUrl:l&&!r?a:""}};export{Bo as a,Uo as b,Wo as c,Fo as d,Io as e,Mo as h,Po as i,zo as l,Ho as o,mn as p,jo as s};
+//# sourceMappingURL=pad-open-flow-By_FzSKk.chunk.mjs.map
diff --git a/js/pad-open-flow-D6KDQoVq.chunk.mjs.license b/js/pad-open-flow-By_FzSKk.chunk.mjs.license
similarity index 100%
rename from js/pad-open-flow-D6KDQoVq.chunk.mjs.license
rename to js/pad-open-flow-By_FzSKk.chunk.mjs.license
diff --git a/js/pad-open-flow-D6KDQoVq.chunk.mjs.map b/js/pad-open-flow-By_FzSKk.chunk.mjs.map
similarity index 74%
rename from js/pad-open-flow-D6KDQoVq.chunk.mjs.map
rename to js/pad-open-flow-By_FzSKk.chunk.mjs.map
index 4c325d74..aadf725a 100644
--- a/js/pad-open-flow-D6KDQoVq.chunk.mjs.map
+++ b/js/pad-open-flow-By_FzSKk.chunk.mjs.map
@@ -1 +1 @@
-{"version":3,"file":"pad-open-flow-D6KDQoVq.chunk.mjs","sources":["../src/lib/pad-sync.js","../src/lib/hand-focus.js","../node_modules/dompurify/dist/purify.es.mjs","../src/lib/sanitize-html.js","../src/lib/pad-content.js","../src/lib/pad-open-flow.js"],"sourcesContent":["/**\n * SPDX-License-Identifier: AGPL-3.0-or-later\n * Copyright (c) 2026 Jacob Bühler\n */\n\n/**\n * Shared pad-sync loop for the viewer and embed entrypoints.\n *\n * Owns the periodic background sync, forced/keepalive flushes, and the\n * visibility/pagehide lifecycle wiring. Forced syncs coalesce: while one\n * request is in flight a second forced flush is not started concurrently but\n * remembered and replayed once, so two forced flushes can never overlap (this\n * is the embed behavior, now shared — the viewer previously used a plain\n * in-flight boolean that allowed overlap).\n *\n * The caller supplies the request-token getter (the viewer and embed read it\n * from different places) and may inject a `fetchImpl` for tests.\n */\n\nexport const DEFAULT_SYNC_INTERVAL_MS = 120000\n\nexport function createPadSync({ requestToken, fetchImpl } = {}) {\n\tconst doFetch = typeof fetchImpl === 'function' ? fetchImpl : (...args) => window.fetch(...args)\n\tconst getToken = typeof requestToken === 'function' ? requestToken : () => ''\n\n\tlet syncUrl = ''\n\tlet intervalMs = DEFAULT_SYNC_INTERVAL_MS\n\tlet syncPromise = null\n\tlet activeSyncForce = false\n\tlet pendingForcedSync = false\n\tlet pendingForcedKeepalive = false\n\tlet timerId = null\n\tlet visibilityHandler = null\n\tlet pageHideHandler = null\n\n\tconst configure = ({ syncUrl: url, intervalMs: ms } = {}) => {\n\t\tif (typeof url === 'string') {\n\t\t\tsyncUrl = url\n\t\t}\n\t\tif (Number.isFinite(ms) && ms > 0) {\n\t\t\tintervalMs = ms\n\t\t}\n\t}\n\n\tconst stop = () => {\n\t\tif (timerId !== null) {\n\t\t\twindow.clearInterval(timerId)\n\t\t\ttimerId = null\n\t\t}\n\t}\n\n\tconst start = () => {\n\t\tif (!syncUrl || timerId !== null) {\n\t\t\treturn\n\t\t}\n\t\ttimerId = window.setInterval(() => {\n\t\t\tif (document.visibilityState === 'visible') {\n\t\t\t\tfireAndForget(false, false)\n\t\t\t}\n\t\t}, intervalMs)\n\t}\n\n\tconst fireAndForget = (force, keepalive) => {\n\t\tvoid sync(force, keepalive).catch(() => {})\n\t}\n\n\tconst sync = async (force, keepalive) => {\n\t\tif (!syncUrl) {\n\t\t\treturn { status: 'disabled' }\n\t\t}\n\t\tif (syncPromise) {\n\t\t\t// A request is already running. A forced flush that arrives while a\n\t\t\t// non-forced sync is in flight is coalesced into a single replay.\n\t\t\tif (force && !activeSyncForce) {\n\t\t\t\tpendingForcedSync = true\n\t\t\t\tpendingForcedKeepalive = pendingForcedKeepalive || Boolean(keepalive)\n\t\t\t\treturn syncPromise.catch(() => undefined).then(() => sync(true, pendingForcedKeepalive))\n\t\t\t}\n\t\t\treturn syncPromise\n\t\t}\n\t\tactiveSyncForce = Boolean(force)\n\t\tconst currentPromise = (async () => {\n\t\t\tconst url = force ? (syncUrl + (syncUrl.includes('?') ? '&' : '?') + 'force=1') : syncUrl\n\t\t\tconst response = await doFetch(url, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\tcredentials: 'same-origin',\n\t\t\t\theaders: {\n\t\t\t\t\tAccept: 'application/json',\n\t\t\t\t\trequesttoken: getToken(),\n\t\t\t\t},\n\t\t\t\tkeepalive: Boolean(keepalive),\n\t\t\t})\n\t\t\tconst data = await response.json().catch(() => ({}))\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow new Error((data && data.message) || 'Sync request failed.')\n\t\t\t}\n\t\t\treturn data\n\t\t})()\n\t\tsyncPromise = currentPromise\n\t\tlet result\n\t\tlet syncError = null\n\t\ttry {\n\t\t\tresult = await currentPromise\n\t\t} catch (error) {\n\t\t\tsyncError = error\n\t\t} finally {\n\t\t\tif (syncPromise === currentPromise) {\n\t\t\t\tsyncPromise = null\n\t\t\t}\n\t\t\tactiveSyncForce = false\n\t\t}\n\t\tconst rerunForcedSync = pendingForcedSync\n\t\tconst rerunKeepalive = pendingForcedKeepalive\n\t\tpendingForcedSync = false\n\t\tpendingForcedKeepalive = false\n\t\tif (rerunForcedSync) {\n\t\t\treturn sync(true, rerunKeepalive)\n\t\t}\n\t\tif (syncError instanceof Error) {\n\t\t\tthrow syncError\n\t\t}\n\t\treturn result\n\t}\n\n\tconst installLifecycleHandlers = () => {\n\t\tif (visibilityHandler || pageHideHandler) {\n\t\t\treturn\n\t\t}\n\t\tvisibilityHandler = () => {\n\t\t\tif (document.visibilityState === 'hidden') {\n\t\t\t\tfireAndForget(true, true)\n\t\t\t\tstop()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tstart()\n\t\t}\n\t\tpageHideHandler = () => {\n\t\t\tfireAndForget(true, true)\n\t\t\tstop()\n\t\t}\n\t\tdocument.addEventListener('visibilitychange', visibilityHandler)\n\t\twindow.addEventListener('pagehide', pageHideHandler)\n\t}\n\n\tconst removeLifecycleHandlers = () => {\n\t\tif (visibilityHandler) {\n\t\t\tdocument.removeEventListener('visibilitychange', visibilityHandler)\n\t\t\tvisibilityHandler = null\n\t\t}\n\t\tif (pageHideHandler) {\n\t\t\twindow.removeEventListener('pagehide', pageHideHandler)\n\t\t\tpageHideHandler = null\n\t\t}\n\t}\n\n\treturn {\n\t\tconfigure,\n\t\tstart,\n\t\tstop,\n\t\tsync,\n\t\tfireAndForget,\n\t\tinstallLifecycleHandlers,\n\t\tremoveLifecycleHandlers,\n\t}\n}\n","/**\n * SPDX-License-Identifier: AGPL-3.0-or-later\n * Copyright (c) 2026 Jacob Bühler\n */\n\n/**\n * After a click whose button went away with the focus on it - a second\n * try, a recovery - the first action in `actionsScope`, or else the\n * message, takes the focus, so a keyboard or screen reader keeps its\n * place (docs/architecture.md, \"Errors of the API\").\n *\n * @param {Element|null|undefined} actionsScope where the new actions are\n * @param {Element|null|undefined} messageNode what to focus without one\n * @param {{preventScroll?: boolean}} [options] the embed page sits in\n * another page, which must not scroll to it\n */\nexport const handFocusTo = (actionsScope, messageNode, { preventScroll = false } = {}) => {\n\tconst action = actionsScope instanceof HTMLElement ? actionsScope.querySelector('a, button') : null\n\tconst target = action || messageNode\n\tif (!(target instanceof HTMLElement)) {\n\t\treturn\n\t}\n\tif (target === messageNode) {\n\t\t// A paragraph takes the focus only with a tabindex.\n\t\ttarget.tabIndex = -1\n\t}\n\ttarget.focus({ preventScroll })\n}\n","/*! @license DOMPurify 3.4.15 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.15/LICENSE */\n\nfunction _arrayLikeToArray(r, a) {\n (null == a || a > r.length) && (a = r.length);\n for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];\n return n;\n}\nfunction _arrayWithHoles(r) {\n if (Array.isArray(r)) return r;\n}\nfunction _iterableToArrayLimit(r, l) {\n var t = null == r ? null : \"undefined\" != typeof Symbol && r[Symbol.iterator] || r[\"@@iterator\"];\n if (null != t) {\n var e,\n n,\n i,\n u,\n a = [],\n f = true,\n o = false;\n try {\n if (i = (t = t.call(r)).next, 0 === l) ; else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);\n } catch (r) {\n o = true, n = r;\n } finally {\n try {\n if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;\n } finally {\n if (o) throw n;\n }\n }\n return a;\n }\n}\nfunction _nonIterableRest() {\n throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\");\n}\nfunction _slicedToArray(r, e) {\n return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();\n}\nfunction _unsupportedIterableToArray(r, a) {\n if (r) {\n if (\"string\" == typeof r) return _arrayLikeToArray(r, a);\n var t = {}.toString.call(r).slice(8, -1);\n return \"Object\" === t && r.constructor && (t = r.constructor.name), \"Map\" === t || \"Set\" === t ? Array.from(r) : \"Arguments\" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;\n }\n}\n\nconst entries = Object.entries,\n setPrototypeOf = Object.setPrototypeOf,\n isFrozen = Object.isFrozen,\n getPrototypeOf = Object.getPrototypeOf,\n getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;\nlet freeze = Object.freeze,\n seal = Object.seal,\n create = Object.create; // eslint-disable-line import/no-mutable-exports\nlet _ref = typeof Reflect !== 'undefined' && Reflect,\n apply = _ref.apply,\n construct = _ref.construct;\nif (!freeze) {\n freeze = function freeze(x) {\n return x;\n };\n}\nif (!seal) {\n seal = function seal(x) {\n return x;\n };\n}\nif (!apply) {\n apply = function apply(func, thisArg) {\n for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {\n args[_key - 2] = arguments[_key];\n }\n return func.apply(thisArg, args);\n };\n}\nif (!construct) {\n construct = function construct(Func) {\n for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {\n args[_key2 - 1] = arguments[_key2];\n }\n return new Func(...args);\n };\n}\nconst arrayForEach = unapply(Array.prototype.forEach);\nconst arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);\nconst arrayPop = unapply(Array.prototype.pop);\nconst arrayPush = unapply(Array.prototype.push);\nconst arraySplice = unapply(Array.prototype.splice);\nconst arrayIsArray = Array.isArray;\nconst stringToLowerCase = unapply(String.prototype.toLowerCase);\nconst stringToString = unapply(String.prototype.toString);\nconst stringMatch = unapply(String.prototype.match);\nconst stringReplace = unapply(String.prototype.replace);\nconst stringIndexOf = unapply(String.prototype.indexOf);\nconst stringTrim = unapply(String.prototype.trim);\nconst numberToString = unapply(Number.prototype.toString);\nconst booleanToString = unapply(Boolean.prototype.toString);\nconst bigintToString = typeof BigInt === 'undefined' ? null : unapply(BigInt.prototype.toString);\nconst symbolToString = typeof Symbol === 'undefined' ? null : unapply(Symbol.prototype.toString);\nconst objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);\nconst objectToString = unapply(Object.prototype.toString);\nconst regExpTest = unapply(RegExp.prototype.test);\nconst typeErrorCreate = unconstruct(TypeError);\n/**\n * Creates a new function that calls the given function with a specified thisArg and arguments.\n *\n * @param func - The function to be wrapped and called.\n * @returns A new function that calls the given function with a specified thisArg and arguments.\n */\nfunction unapply(func) {\n return function (thisArg) {\n if (thisArg instanceof RegExp) {\n thisArg.lastIndex = 0;\n }\n for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {\n args[_key3 - 1] = arguments[_key3];\n }\n return apply(func, thisArg, args);\n };\n}\n/**\n * Creates a new function that constructs an instance of the given constructor function with the provided arguments.\n *\n * @param func - The constructor function to be wrapped and called.\n * @returns A new function that constructs an instance of the given constructor function with the provided arguments.\n */\nfunction unconstruct(Func) {\n return function () {\n for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {\n args[_key4] = arguments[_key4];\n }\n return construct(Func, args);\n };\n}\n/**\n * Add properties to a lookup table\n *\n * @param set - The set to which elements will be added.\n * @param array - The array containing elements to be added to the set.\n * @param transformCaseFunc - An optional function to transform the case of each element before adding to the set.\n * @returns The modified set with added elements.\n */\nfunction addToSet(set, array) {\n let transformCaseFunc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : stringToLowerCase;\n if (setPrototypeOf) {\n // Make 'in' and truthy checks like Boolean(set.constructor)\n // independent of any properties defined on Object.prototype.\n // Prevent prototype setters from intercepting set as a this value.\n setPrototypeOf(set, null);\n }\n if (!arrayIsArray(array)) {\n return set;\n }\n let l = array.length;\n while (l--) {\n let element = array[l];\n if (typeof element === 'string') {\n const lcElement = transformCaseFunc(element);\n if (lcElement !== element) {\n // Config presets (e.g. tags.js, attrs.js) are immutable.\n if (!isFrozen(array)) {\n array[l] = lcElement;\n }\n element = lcElement;\n }\n }\n set[element] = true;\n }\n return set;\n}\n/**\n * Clean up an array to harden against CSPP\n *\n * @param array - The array to be cleaned.\n * @returns The cleaned version of the array\n */\nfunction cleanArray(array) {\n for (let index = 0; index < array.length; index++) {\n const isPropertyExist = objectHasOwnProperty(array, index);\n if (!isPropertyExist) {\n array[index] = null;\n }\n }\n return array;\n}\n/**\n * Shallow clone an object\n *\n * @param object - The object to be cloned.\n * @returns A new object that copies the original.\n */\nfunction clone(object) {\n const newObject = create(null);\n for (const _ref2 of entries(object)) {\n var _ref3 = _slicedToArray(_ref2, 2);\n const property = _ref3[0];\n const value = _ref3[1];\n const isPropertyExist = objectHasOwnProperty(object, property);\n if (isPropertyExist) {\n if (arrayIsArray(value)) {\n newObject[property] = cleanArray(value);\n } else if (value && typeof value === 'object' && value.constructor === Object) {\n newObject[property] = clone(value);\n } else {\n newObject[property] = value;\n }\n }\n }\n return newObject;\n}\n/**\n * Convert non-node values into strings without depending on direct property access.\n *\n * @param value - The value to stringify.\n * @returns A string representation of the provided value.\n */\nfunction stringifyValue(value) {\n switch (typeof value) {\n case 'string':\n {\n return value;\n }\n case 'number':\n {\n return numberToString(value);\n }\n case 'boolean':\n {\n return booleanToString(value);\n }\n case 'bigint':\n {\n return bigintToString ? bigintToString(value) : '0';\n }\n case 'symbol':\n {\n return symbolToString ? symbolToString(value) : 'Symbol()';\n }\n case 'undefined':\n {\n return objectToString(value);\n }\n case 'function':\n case 'object':\n {\n if (value === null) {\n return objectToString(value);\n }\n const valueAsRecord = value;\n const valueToString = lookupGetter(valueAsRecord, 'toString');\n if (typeof valueToString === 'function') {\n const stringified = valueToString(valueAsRecord);\n return typeof stringified === 'string' ? stringified : objectToString(stringified);\n }\n return objectToString(value);\n }\n default:\n {\n return objectToString(value);\n }\n }\n}\n/**\n * This method automatically checks if the prop is function or getter and behaves accordingly.\n *\n * @param object - The object to look up the getter function in its prototype chain.\n * @param prop - The property name for which to find the getter function.\n * @returns The getter function found in the prototype chain or a fallback function.\n */\nfunction lookupGetter(object, prop) {\n while (object !== null) {\n const desc = getOwnPropertyDescriptor(object, prop);\n if (desc) {\n if (desc.get) {\n return unapply(desc.get);\n }\n if (typeof desc.value === 'function') {\n return unapply(desc.value);\n }\n }\n object = getPrototypeOf(object);\n }\n function fallbackValue() {\n return null;\n }\n return fallbackValue;\n}\nfunction isRegex(value) {\n try {\n regExpTest(value, '');\n return true;\n } catch (_unused) {\n return false;\n }\n}\n\nconst html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'search', 'section', 'select', 'shadow', 'slot', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']);\nconst svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'enterkeyhint', 'exportparts', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'inputmode', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'part', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);\nconst svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']);\n// List of SVG elements that are disallowed by default.\n// We still need to know them so that we can do namespace\n// checks properly in case one wants to add them to\n// allow-list.\nconst svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignobject', 'hatch', 'hatchpath', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'missing-glyph', 'script', 'set', 'solidcolor', 'unknown', 'use']);\nconst mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover', 'mprescripts']);\n// Similarly to SVG, we want to know all MathML elements,\n// even those that we disallow by default.\nconst mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);\nconst text = freeze(['#text']);\n\nconst html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'command', 'commandfor', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'exportparts', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inert', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'part', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'slot', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns']);\nconst svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dominant-baseline', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'mask-type', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'pointer-events', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-orientation', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'vector-effect', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);\nconst mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnalign', 'columnlines', 'columnspacing', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lquote', 'lspace', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);\nconst xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);\n\nconst MUSTACHE_EXPR = seal(/{{[\\w\\W]*|^[\\w\\W]*}}/g);\nconst ERB_EXPR = seal(/<%[\\w\\W]*|^[\\w\\W]*%>/g);\nconst TMPLIT_EXPR = seal(/\\${[\\w\\W]*/g);\nconst DATA_ATTR = seal(/^data-[\\-\\w.\\u00B7-\\uFFFF]+$/); // eslint-disable-line no-useless-escape\nconst ARIA_ATTR = seal(/^aria-[\\-\\w]+$/); // eslint-disable-line no-useless-escape\nconst IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))/i // eslint-disable-line no-useless-escape\n);\nconst IS_SCRIPT_OR_DATA = seal(/^(?:\\w+script|data):/i);\nconst ATTR_WHITESPACE = seal(/[\\u0000-\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u2029\\u205F\\u3000]/g // eslint-disable-line no-control-regex\n);\nconst DOCTYPE_NAME = seal(/^html$/i);\nconst CUSTOM_ELEMENT = seal(/^[a-z][.\\w]*(-[.\\w]+)+$/i);\n// Markup-significant character probes used by _sanitizeElements.\n// Shared module-level instances are safe despite the sticky /g flags:\n// unapply() resets lastIndex for RegExp receivers before every call.\nconst ELEMENT_MARKUP_PROBE = seal(/<[/\\w!]/g);\nconst COMMENT_MARKUP_PROBE = seal(/<[/\\w]/g);\nconst FALLBACK_TAG_CLOSE = seal(/<\\/no(script|embed|frames)/i);\nconst SELF_CLOSING_TAG = seal(/\\/>/i);\n\n// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType\nconst NODE_TYPE = {\n element: 1,\n attribute: 2,\n text: 3,\n cdataSection: 4,\n entityReference: 5,\n // Deprecated\n entityNode: 6,\n // Deprecated\n processingInstruction: 7,\n comment: 8,\n document: 9,\n documentType: 10,\n documentFragment: 11,\n notation: 12 // Deprecated\n};\n/* HTML-namespace elements whose child text nodes are serialized *literally*\n (unescaped) by the HTML fragment-serialization algorithm. Two reparse-mXSS\n shapes ride on that literal serialization:\n (a) an element child - a tree the HTML parser can never build, but the DOM\n API and an XML/XHTML parse can - after which a ``-bearing text\n sibling breaks the element open on reparse; and\n (b) text-only content that already carries the element's OWN end tag, e.g.\n `
` built as a node, which the literal\n serializer emits verbatim for the HTML parser to re-open.\n Shape (a) is handled by the firstElementChild branch in _isUnsafeNode; shape\n (b) by the LITERAL_TEXT_CLOSE probe. Both read textContent (the raw-serialized\n form for these elements) rather than innerHTML, because an XML/XHTML working\n document serializes innerHTML with `<` escaped, which silently blinds the\n innerHTML-based probes (rule 1's second probe and FALLBACK_TAG_CLOSE) there.\n `script` is never allow-listed, but is kept here so the guard matches the\n serializer's own literal-text list exactly. */\nconst LITERAL_TEXT_ELEMENT_NAMES = ['style', 'script', 'xmp', 'iframe', 'noembed', 'noframes', 'plaintext', 'noscript'];\nconst LITERAL_TEXT_ELEMENTS = freeze(addToSet({}, LITERAL_TEXT_ELEMENT_NAMES));\n/* Per-element end-tag matcher. On an HTML reparse the ONLY token that\n terminates a literal-text element's raw content is its own end tag; a foreign\n literal-text close (e.g. `` sitting inside `
` built as a node, which the literal\n serializer emits verbatim for the HTML parser to re-open.\n Shape (a) is handled by the firstElementChild branch in _isUnsafeNode; shape\n (b) by the LITERAL_TEXT_CLOSE probe. Both read textContent (the raw-serialized\n form for these elements) rather than innerHTML, because an XML/XHTML working\n document serializes innerHTML with `<` escaped, which silently blinds the\n innerHTML-based probes (rule 1's second probe and FALLBACK_TAG_CLOSE) there.\n `script` is never allow-listed, but is kept here so the guard matches the\n serializer's own literal-text list exactly. */\nconst LITERAL_TEXT_ELEMENT_NAMES = ['style', 'script', 'xmp', 'iframe', 'noembed', 'noframes', 'plaintext', 'noscript'];\nconst LITERAL_TEXT_ELEMENTS = freeze(addToSet({}, LITERAL_TEXT_ELEMENT_NAMES));\n/* Per-element end-tag matcher. On an HTML reparse the ONLY token that\n terminates a literal-text element's raw content is its own end tag; a foreign\n literal-text close (e.g. `` sitting inside `