diff --git a/docs/api-reference.md b/docs/api-reference.md index 97bf879a..8f43b8c5 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 error this app answers is JSON, it never answers `502` or `504`, and every `503` of its own carries `retryable: true`; what the clients make of a 5xx that is none of these is in `docs/architecture.md` ("Errors of the API"). 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) @@ -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..7f009b5d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -169,7 +169,14 @@ 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, code, retryable}`. Fires on any error. `reason` is one of: + - `'invalid'`: client-side validation failed; nothing was sent. + - `'conflict'`: a `409`, a name taken or a file that changed while its pad was set up; `code` tells them apart. + - `'server'`: refused with any other 4xx, by this app or by a proxy before it, so nothing was created; or failed by this app with a 5xx of its own, which it rolls back as far as it can; or answered in a way the page cannot use. + - `'network'`: no answer from this app (see "Errors of the API"), so the pad may have been created anyway. `status` carries what came, if anything, and the message says to look in the folder before trying again. + + `code` and `retryable` are the server's (`docs/api-reference.md`), `null` and `false` without them: `retryable` says the same create may work later, a locked folder say. + 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. @@ -305,7 +312,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 cannot be this app's answer by what `docs/api-reference.md` says of its errors: one whose body is not JSON, or a 502, 503 or 504 without `retryable`. A proxy, Nextcloud in maintenance or PHP dying midway sent it. 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 8530a0f8..6d270e8b 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 U,f as k,a as T,o as q,D as P}from"./fetch-helpers-BUxbvlK6.chunk.mjs";(function(){const n=document.getElementById("etherpad-nextcloud-embed-create");if(!(n instanceof HTMLElement))return;const c=Number(n.getAttribute("data-parent-folder-id")||""),l=String(n.getAttribute("data-create-by-parent-url")||"").trim(),h=String(n.getAttribute("data-request-token")||"").trim(),S=String(n.getAttribute("data-l10n-missing-name")||"Pad name is required."),_=String(n.getAttribute("data-l10n-invalid-access-mode")||"Invalid access mode."),v=String(n.getAttribute("data-l10n-incomplete-config")||"Embed configuration is incomplete."),M=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."),u=String(n.getAttribute("data-l10n-failed")||"Pad creation failed."),m=n.querySelector("[data-epnc-embed-create-loading]"),g=n.querySelector("[data-epnc-embed-create-error]"),f=n.querySelector("[data-epnc-embed-create-error-message]"),b=()=>q(h),p=(t,a)=>{if(window.parent!==window)try{window.parent.postMessage(Object.assign({type:t},a||{}),"*")}catch{}},L=t=>{m instanceof HTMLElement&&(m.hidden=!0),f instanceof HTMLElement&&(f.textContent=String(t||"Unknown error.")),g instanceof HTMLElement&&(g.hidden=!1)},o=(t,a,d,e=null)=>{const s=String(a||"Unknown error.");L(s),p("epnc:create-failed",{reason:t,status:typeof d=="number"?d:null,message:s,code:e&&typeof e.code=="string"?e.code:null,retryable:!!e&&e.retryable===!0})},A=()=>{const t=new URL(window.location.href).searchParams;return{name:String(t.get("name")||"").trim(),accessMode:String(t.get("accessMode")||P).trim()}},E=t=>{const a=new URL(String(t||"").trim(),window.location.origin);if(a.origin!==window.location.origin)throw new Error("Invalid embed URL origin.");return a.pathname+a.search+a.hash};(async()=>{if(!Number.isFinite(c)||c<=0||l===""){o("invalid",v);return}if(b()===""){o("invalid","CSRF request token is missing.");return}const{name:t,accessMode:a}=A();if(t===""){o("invalid",S);return}if(!U(a)){o("invalid",_);return}const d=new URLSearchParams;d.set("parentFolderId",String(c)),d.set("name",t),d.set("accessMode",a);let e;try{e=await k(l,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded;charset=UTF-8",requesttoken:b()},body:d.toString()},{timeoutMs:null,fallbackMessage:u})}catch(r){const i=r&&typeof r.status=="number"?r.status:null,w=T(r),y=!w&&r instanceof Error&&r.message?r.message:u;if(i!==null&&i>=400&&i<500){o(i===409?"conflict":"server",y,i,r);return}if(i===null||w){o("network",M,i);return}o("server",y,i,r);return}if(!e||typeof e.embed_url!="string"||e.embed_url.trim()===""){o("server","Pad creation API did not return a valid embed URL.");return}let s;try{s=E(e.embed_url)}catch(r){const i=r instanceof Error?r.message:"Invalid embed URL.";o("server",i);return}p("epnc:create-succeeded",{embed_url:e.embed_url,file_id:typeof e.file_id=="number"?e.file_id:null,pad_id:typeof e.pad_id=="string"?e.pad_id:"",access_mode:typeof e.access_mode=="string"?e.access_mode:""}),window.location.replace(s)})()})(); //# 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..1b792f4d 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, 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 failedMessage = String(root.getAttribute('data-l10n-failed') || 'Pad creation failed.')\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 * The host contract is in docs/architecture.md (the create flow);\n\t * `reason` in short:\n\t * - 'invalid' — client-side validation failed\n\t * - 'conflict' — a 409\n\t * - 'server' — refused with another 4xx, or this app's own 5xx\n\t * - 'network' — no answer from this app; the pad may exist\n\t * `code` and `retryable` are the server's, `null` and `false` without.\n\t */\n\tconst failCreate = (reason, message, status, answer = null) => {\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\tcode: answer && typeof answer.code === 'string' ? answer.code : null,\n\t\t\tretryable: Boolean(answer) && answer.retryable === true,\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 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: failedMessage })\n\t\t} catch (error) {\n\t\t\tconst status = (error && typeof error.status === 'number') ? error.status : null\n\t\t\tconst unanswered = isUnanswered(error)\n\t\t\tconst message = !unanswered && error instanceof Error && error.message ? error.message : failedMessage\n\t\t\t// Refused, even if the body then broke off: nothing was made.\n\t\t\tif (status !== null && status >= 400 && status < 500) {\n\t\t\t\tfailCreate(status === 409 ? 'conflict' : 'server', message, status, error)\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// No answer from this app: whether the pad was made is not known.\n\t\t\tif (status === null || unanswered) {\n\t\t\t\tfailCreate('network', unansweredMessage, status)\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// This app's own 5xx, rolled back as far as it could.\n\t\t\tfailCreate('server', message, status, error)\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","failedMessage","loadingNode","errorNode","errorMessageNode","requestToken","ocRequestToken","postHostMessage","type","payload","showError","message","failCreate","reason","status","answer","normalizedMessage","readLauncherParams","params","DEFAULT_PAD_ACCESS_MODE","normalizeEmbedRedirectUrl","value","url","name","accessMode","isPadAccessMode","body","data","fetchJson","error","unanswered","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":"4RAYO,SAASA,EAAgBC,EAAS,CAGrC,GAFAC,EAAgBD,CAAO,EACvB,OAAO,uBAAyB,OAAO,qBAAuB,IAAI,KAC9D,OAAO,qBAAqB,IAAIA,EAAQ,EAAE,EAAG,CAC7C,QAAQ,KAAK,mBAAmBA,EAAQ,EAAE,yBAAyB,EACnE,MACJ,CACA,OAAO,qBAAqB,IAAIA,EAAQ,GAAIA,CAAO,CACvD,CAMA,SAASC,EAAgBD,EAAS,CAC9B,KAAM,CAAE,GAAAE,EAAI,MAAAC,EAAO,aAAAC,EAAc,UAAAC,CAAS,EAAKL,EAE/C,GAAI,CAACE,GAAMA,EAAG,KAAI,IAAO,IAAM,OAAOA,GAAO,SACzC,MAAM,IAAI,MAAM,sCAAsC,EAG1D,IAAK,CAACC,GAAS,CAAC,MAAM,QAAQA,CAAK,IAAM,CAACC,EACtC,MAAM,IAAI,MAAM,kDAAkD,EAGtE,GAAK,CAACC,GAAc,OAAOA,GAAc,UAAY,OAAOA,GAAc,WACtE,MAAM,IAAI,MAAM,6CAA6C,CAErE,CC7BA,MAAMC,EAAgB,IAAI,IACpBC,EAA4B,GAC5BC,EAAuB,IAAS,IAUzBC,EAAsB,MAAOC,EAAM,CAAE,YAAAC,EAAc,EAAK,EAAK,CAAA,IAAO,CAChF,MAAMC,EAAW,QAAU,OAAOF,CAAI,EAChCG,EAASF,EAAc,KAAOG,EAAgBF,CAAQ,EAC5D,GAAIC,IAAW,KACd,OAAOA,EAER,MAAME,EAAMC,EAAc,SAAWC,EAAS,sBAAsB,EAAI,SAAW,mBAAmBP,CAAI,EACpGQ,EAAUC,EAAUJ,EAAK,CAC9B,OAAQ,MACR,QAAS,CAAE,OAAQ,kBAAkB,CACvC,EAAI,6BAA6B,EAC9B,MAAOK,GAAU,CACjB,MAAAd,EAAc,OAAOM,CAAQ,EACvBQ,CACP,CAAC,EACF,OAAAC,EAAgBT,EAAUM,CAAO,EAC1BA,CACR,EAEaI,EAAqB,MAAOC,GAAW,CACnD,MAAMC,EAAWR,EAAc,SAAWC,EAAS,8BAAgC,mBAAmB,OAAOM,CAAM,CAAC,CAAC,EACrH,OAAOJ,EAAUK,EAAU,CAC1B,OAAQ,MACR,QAAS,CAAE,OAAQ,kBAAkB,CACvC,EAAI,gBAAgB,CACpB,EAEaC,EAAyB,MAAOF,EAAQb,EAAO,KAAO,CAClE,MAAMc,EAAWR,EAAc,SAAWC,EAAS,sCAAwC,mBAAmB,OAAOM,CAAM,CAAC,CAAC,EAEvHG,EAAS,MAAMC,EAAqBH,EAAU,CACnD,OAAQ,OACR,QAAS,CACR,OAAQ,mBACR,aAAcI,EAAc,CAC/B,CACA,EAAI,CAAE,gBAAiB,mBAAoB,UAAW,IAAI,CAAE,EAG3D,OAAI,OAAOlB,GAAS,UAAYA,IAAS,IACxCJ,EAAc,OAAO,QAAUI,CAAI,EAE7BgB,CACR,EACMZ,EAAmBF,GAAa,CACrC,MAAMC,EAASP,EAAc,IAAIM,CAAQ,EACzC,OAAKC,EAGA,KAAK,IAAG,EAAKA,EAAO,UAAaL,GACrCF,EAAc,OAAOM,CAAQ,EACtB,MAEDC,EAAO,QANN,IAOT,EAEMQ,EAAkB,CAACT,EAAUM,IAAY,CAC9C,GAAI,CAACZ,EAAc,IAAIM,CAAQ,GAAKN,EAAc,MAAQC,EAA2B,CACpF,MAAMsB,EAAYvB,EAAc,KAAI,EAAG,KAAI,EAAG,MAC1CuB,IAAc,QACjBvB,EAAc,OAAOuB,CAAS,CAEhC,CACAvB,EAAc,IAAIM,EAAU,CAC3B,UAAW,KAAK,IAAG,EACnB,QAAAM,CACF,CAAE,CACF,EAEMC,EAAY,MAAOJ,EAAKe,EAASC,IACtCJ,EAAqBZ,EAAKe,EAAS,CAAE,gBAAAC,CAAe,CAAE,ECxFjDC,EAAmBC,GAAU,OAAOA,GAAS,EAAE,EACnD,QAAQ,KAAM,OAAO,EACrB,QAAQ,KAAM,QAAQ,EACtB,QAAQ,KAAM,MAAM,EACpB,QAAQ,KAAM,MAAM,EAEhBC,EAA0B,CAAC,QAAS,QAAQ,EAE5CC,EAAgBpB,GAAQ,CAC7B,GAAI,CACH,MAAMqB,EAAS,IAAI,IAAI,OAAOrB,GAAO,EAAE,CAAC,EACxC,OAAOmB,EAAwB,SAASE,EAAO,QAAQ,GACnDA,EAAO,WAAa,IACpBA,EAAO,WAAa,EACzB,MAAQ,CACP,MAAO,EACR,CACD,EAEMC,GAAc,wEAEPC,GAAuBvB,GAAQ,CAC3C,MAAMwB,EAAUJ,EAAapB,CAAG,EAAIiB,EAAgBjB,CAAG,EAAI,GAC3D,MAAO,wGACoDiB,EAAgBK,EAAW,EAAI,6IAEzDE,EAAU,4CAC5C,ECbMC,EAAY,CAACpB,EAAOqB,IAAaC,EAAoBtB,EAAOuB,EAAU,gEAAgE,EAAGF,CAAQ,EAEjJpC,GAAY,CACjB,KAAM,0BACN,MAAO,CACN,SAAU,CAAE,KAAM,OAAQ,SAAU,GAAO,QAAS,EAAE,EACtD,SAAU,CAAE,KAAM,OAAQ,SAAU,GAAO,QAAS,EAAE,EACtD,OAAQ,CAAE,KAAM,OAAQ,SAAU,GAAO,QAAS,EAAE,EACpD,OAAQ,CAAE,KAAM,CAAC,OAAQ,MAAM,EAAG,SAAU,GAAO,QAAS,IAAI,EAChE,OAAQ,CAAE,KAAM,CAAC,OAAQ,MAAM,EAAG,SAAU,GAAO,QAAS,IAAI,EAChE,SAAU,CAAE,KAAM,OAAQ,SAAU,GAAO,QAAS,IAAI,CAC1D,EACC,MAAO,CACN,MAAO,CACN,UAAW,GACX,UAAW,GACX,UAAW,GACX,WAAY,GACZ,aAAc,GACd,iBAAkB,GAElB,eAAgB,KAEhB,aAAc,GACd,aAAc,GACd,mBAAoB,GACpB,YAAa,KACb,gBAAiB,GACjB,YAAa,GACb,WAAY,GACZ,aAAc,OACd,aAAc,GACd,QAAS,CAAE,KAAM,GAAI,QAAS,EAAK,EAEnC,cAAe,GAEf,kBAAmB,EACnB,kBAAmB,CACtB,CACC,EACA,SAAU,CACT,YAAa,CAEZ,MAAM4B,EAAQ,OAAO,KAAK,QAAW,SAAW,KAAK,OAAO,OAAS,GACrE,OAAKA,GACEW,EAAwBX,CAAK,GAAK,EAC1C,EACA,UAAW,CAGV,MAAMY,EAAgBC,GACjB,CAACA,GAAOA,IAAQ,IAAY,IACzBA,EAAI,WAAW,GAAG,EAAIA,EAAO,IAAMA,EAErCC,EAAW,CAACD,EAAKE,IAAS,CAC/B,GAAI,CAACA,EAAM,MAAO,GAClB,GAAIA,EAAK,WAAW,GAAG,EAAG,OAAOA,EACjC,MAAMC,EAAgBJ,EAAaC,CAAG,EACtC,OAAOG,IAAkB,IAAM,IAAMD,EAAOC,EAAgB,IAAMD,CACnE,EACA,GAAIE,EAAU,KAAK,UAAU,EAAG,OAAO,KAAK,WAE5C,MAAMC,EAAO,KAAK,UAAY,OAAO,KAAK,UAAa,SAAW,KAAK,SAAW,KAC5EC,EAAWD,GAAQ,OAAOA,EAAK,MAAS,SAAWA,EAAK,KAAO,GACrE,GAAID,EAAUE,CAAQ,EAAG,OAAOA,EAAS,WAAW,GAAG,EAAIA,EAAY,IAAMA,EAE7E,MAAMC,EAAW,OAAO,KAAK,UAAY,KAAK,UAAaF,IAASA,EAAK,MAAQA,EAAK,WAAc,EAAE,EACtG,GAAI,CAACE,EAAU,MAAO,GAEtB,MAAMC,EAAUH,GAAQ,OAAOA,EAAK,SAAY,SAAWA,EAAK,QAAU,GAC1E,GAAIG,EAAS,CACZ,MAAMC,EAAWR,EAASO,EAASD,CAAQ,EAC3C,GAAIH,EAAUK,CAAQ,EAAG,OAAOA,CACjC,CAGA,MAAMC,EADS,IAAI,gBAAgB,OAAO,SAAS,QAAU,EAAE,EACzC,IAAI,KAAK,GAAK,IAC9BC,EAAUV,EAASS,EAAQH,CAAQ,EACzC,OAAIH,EAAUO,CAAO,EAAUA,EACxB,IAAMJ,CACd,EACA,SAAU,CACT,MAAO,GAAG,KAAK,iBAAmB,KAAO,GAAK,KAAK,cAAc,KAAK,KAAK,QAAQ,EACpF,EACA,gBAAiB,CAChB,MAAMK,EAAa,CAAC,KAAK,OAAQ,KAAK,OAAQ,KAAK,WAAa,KAAK,SAAS,QAAU,KAAK,SAAS,QAAU,KAAK,SAAS,GAAG,EACjI,UAAWC,KAAaD,EAAY,CACnC,MAAME,EAAU,OAAOD,CAAS,EAChC,GAAI,OAAO,SAASC,CAAO,GAAKA,EAAU,EAAG,OAAOA,CACrD,CAEA,OAAO,IACR,CACF,EACC,MAAO,CAEN,QAAS,CAAE,UAAW,GAAM,SAAU,CAAO,KAAK,eAAc,CAAG,CAAC,CACtE,EACC,QAAS,CACR,MAAM,iBAAiB7C,EAAK8C,EAAO,GAAI,CACtC,OAAOC,EAAkB,MAAMnC,EAAqBZ,EAAK,OAAO,OAAO,CAAE,OAAQ,OAAS8C,CAAI,CAAC,CAAC,CACjG,EACA,MAAM,8BAA+B,CACpC,MAAME,EAAU,CACf,OAAQ,mBACR,aAAcnC,EAAc,CAChC,EAGSoC,EAAc,CAAE,UAAW,KAAM,gBAAiB,4BAA4B,EAE9EC,EAA0BC,GAAS,CACpCA,GAAQA,EAAK,SAAW,wBAC3B,QAAQ,KAAK,8DAA8D,CAE7E,EAEA,GAAI,KAAK,iBAAmB,KAAM,CACjC,MAAMnD,EAAMC,EAAc,SAAWC,EAAS,iCAAmC,mBAAmB,OAAO,KAAK,cAAc,CAAC,CAAC,EAC1HiD,EAAO,MAAMvC,EAAqBZ,EAAK,CAAE,OAAQ,OAAQ,QAAAgD,CAAO,EAAIC,CAAW,EACrF,OAAAC,EAAuBC,CAAI,EACpBA,CACR,CAEA,GAAI,CAAC,KAAK,SACT,MAAM,IAAI,MAAM,+CAA+C,EAGhE,MAAMC,EAAO,IAAI,gBACjBA,EAAK,IAAI,OAAQ,KAAK,QAAQ,EAC9B,MAAMpD,EAAMC,EAAc,SAAWC,EAAS,yBAAyB,EACjEiD,EAAO,MAAMvC,EAAqBZ,EAAK,CAC5C,OAAQ,OACR,QAAS,OAAO,OAAO,CAAA,EAAIgD,EAAS,CACnC,eAAgB,iDACrB,CAAK,EACD,KAAMI,EAAK,SAAQ,CACvB,EAAMH,CAAW,EACd,OAAAC,EAAuBC,CAAI,EACpBA,CACR,EAEA,MAAM,sBAAsBE,EAAU,CACrC,GAAI,CACH,MAAMC,EAAW,MAAM5D,EAAoB2D,EAAU,CAAE,YAAa,EAAI,CAAE,EACpElE,EAAK,OAAOmE,GAAYA,EAAS,OAAO,EAC9C,OAAO,OAAO,SAASnE,CAAE,GAAKA,EAAK,EAAIA,EAAK,IAC7C,MAAQ,CACP,OAAO,IACR,CACD,EACA,YAAa,CACZ,KAAK,MAAM,gBAAiB,EAAI,CACjC,EAKA,sBAAuB,CACtB,KAAK,UAAU,IAAM,CACpB,MAAMoE,EAAO,KAAK,MAAM,UACpBA,aAAgB,aACnBC,EAAYD,EAAMA,EAAK,cAAc,4BAA4B,CAAC,CAEpE,CAAC,CACF,EAEA,SAAU,CACT,OAAK,KAAK,WACT,KAAK,SAAWE,EAAc,CAAE,aAAc,IAAM5C,EAAc,CAAE,CAAE,GAEhE,KAAK,QACb,EAEA,cAAe,CACT,KAAK,WAGV,KAAK,SAAS,cAAc,GAAM,EAAI,EACtC,KAAK,SAAS,KAAI,EAClB,KAAK,SAAS,wBAAuB,EACtC,EAEA,MAAM,eAAe6C,EAAa,GAAO,CACxC,MAAMC,EAAa,EAAE,KAAK,kBACpBC,EAAY,IAAMD,IAAe,KAAK,kBAE5C,KAAK,YAAY,MAAK,EACtB,MAAME,EAAQ,OAAO,iBAAoB,WAAa,IAAI,gBAAoB,KA2B9E,GA1BA,KAAK,WAAaA,EAElB,KAAK,UAAY,GACjB,KAAK,UAAY,GACjB,KAAK,WAAa,GAClB,KAAK,aAAe,GACpB,KAAK,iBAAmB,GACxB,KAAK,eAAiB,KACtB,KAAK,aAAe,GACpB,KAAK,mBAAqB,GAC1B,KAAK,YAAc,KACnB,KAAK,UAAY,GACjB,KAAK,gBAAkB,GACvB,KAAK,YAAc,GACnB,KAAK,WAAa,GAClB,KAAK,aAAe,OACpB,KAAK,aAAe,GACpB,KAAK,QAAU,CAAE,KAAM,GAAI,QAAS,EAAK,EACzC,KAAK,cAAgB,GACrB,KAAK,eAAe,MAAK,EAErB,KAAK,WACR,KAAK,SAAS,KAAI,EAClB,KAAK,SAAS,UAAU,CAAE,QAAS,EAAE,CAAE,GAGpC,CAAC,KAAK,SAAU,CACnB,GAAI,CAACD,EAAS,EAAI,OAClB,KAAK,UAAY,yBACjB,KAAK,UAAY,GACjB,MACD,CAGA,MAAMP,EAAW,KAAK,SAChBS,EAAcC,EAAiC,EAC/CC,GAAe,IAAM,CAC1B,GAAI,CAACF,EAAa,MAAO,GACzB,MAAM9D,EAAM,IAAI,IAAIC,EAAc,SAAWC,EAAS,uBAAyB,mBAAmB4D,CAAW,CAAC,EAAG,OAAO,SAAS,MAAM,EAEvI,OAAI,KAAK,iBAAmB,KAC3B9D,EAAI,aAAa,IAAI,SAAU,OAAO,KAAK,cAAc,CAAC,EAE1DA,EAAI,aAAa,IAAI,OAAQqD,CAAQ,EAE/BrD,EAAI,SAAQ,CACpB,GAAC,EACKiE,EAAkB,CACvB,eAAgB,kDAChB,aAAcpD,EAAc,CAChC,EAEG,GAAI,CA0BH,MAAMsC,EAAO,MAAMe,EAA4B,CAC9C,KA1BqB,SAAY,CAEjC,MAAMC,EAASN,EAAQA,EAAM,OAAS,OACtC,GAAIG,EACH,OAAO,MAAM,KAAK,iBAAiBA,EAAa,CAAE,OAAAG,CAAM,CAAE,EAK3D,GAAI,KAAK,iBAAmB,KAAM,CACjC,MAAMC,EAAW,IAAI,gBACrB,OAAAA,EAAS,IAAI,SAAU,OAAO,KAAK,cAAc,CAAC,EAC3C,MAAM,KAAK,iBACjBnE,EAAc,SAAWC,EAAS,yBAAyB,EAC3D,CAAE,OAAQ,OAAQ,QAAS+D,EAAiB,KAAMG,EAAS,SAAQ,EAAI,OAAAD,CAAM,CACpF,CACK,CACA,MAAME,EAAa,IAAI,gBACvB,OAAAA,EAAW,IAAI,OAAQhB,CAAQ,EACxB,MAAM,KAAK,iBACjBpD,EAAc,SAAWC,EAAS,mBAAmB,EACrD,CAAE,OAAQ,OAAQ,QAAS+D,EAAiB,KAAMI,EAAW,SAAQ,EAAI,OAAAF,CAAM,CACrF,CACI,EAIC,WAAY,IAAM,KAAK,6BAA4B,EACnD,YAAaP,CAClB,CAAK,EACD,GAAIT,IAAS,MAAQ,CAACS,IAAa,OAEnC,KAAM,CAAE,QAAAU,EAAS,WAAAC,CAAU,EAAKC,EAAiBrB,CAAI,EAErD,KAAK,QAAO,EAAG,UAAU,CAAE,QAAAmB,EAAS,WAAAC,CAAU,CAAE,EAChD,KAAK,QAAO,EAAG,yBAAwB,EACnCD,GACH,KAAK,QAAO,EAAG,MAAK,EAGrB,MAAMG,EAAaC,EAAevB,CAAI,EAEhC,CAAE,cAAAwB,EAAe,YAAAC,CAAW,EAAKC,EAAgB1B,CAAI,EAC3D,GAAIwB,EAAe,CAClB,KAAK,gBAAkBC,EACvB,KAAK,WAAaH,EAClB,KAAK,YAAc,UACnB,KAAK,WAAU,EAEV,KAAK,YAAW,EACrB,MACD,CAEA,KAAK,UAAYK,EAAW3B,CAAI,EAChC,KAAK,WAAU,CAChB,OAAS9C,EAAO,CACf,GAAI,CAACuD,EAAS,EAAI,OAClB,KAAK,UAAYnC,EAAUpB,EAAO,qBAAqB,EAEvD,KAAK,iBAAmB,KAAK,iBAAmB,MAC5C,CAAA,CAAQA,GAAUA,EAAM,SAAW,KAAO,CAACA,EAAM,KACrD,KAAK,aAAe0E,EAAqB1E,CAAK,EAE9C,IAAI2E,EAAiB,KAAK,eAE1B,GADA,KAAK,aAAe3B,EAChB2B,IAAmB,MAAQ,CAAChB,GAAeiB,EAAsB5E,CAAK,IACzE2E,EAAiB,MAAM,KAAK,sBAAsB3B,CAAQ,EAEtD,CAACO,EAAS,GAAI,OAEnB,KAAK,eAAiBoB,EACtB,KAAK,WAAaC,EAAsB5E,CAAK,GACzC2E,IAAmB,MACnB,CAAChB,EACD,KAAK,WACR,KAAK,qBAAqBJ,EAAWF,CAAU,EACrCA,GACV,KAAK,qBAAoB,EAE1B,KAAK,WAAU,CAChB,QAAA,CACC,GAAI,CAACE,EAAS,EAAI,OAClB,KAAK,UAAY,EAClB,CACD,EAEA,MAAM,qBAAqBA,EAAWF,EAAa,GAAO,CACzD,GAAI,KAAK,iBAAmB,KAG5B,CAAA,KAAK,mBAAqB,GAC1B,GAAI,CACH,MAAMwB,EAAO,MAAM3E,EAAmB,KAAK,cAAc,EACzD,GAAI,CAACqD,EAAS,EAAI,OACdsB,GAAQA,EAAK,QAAU,IAAQ,OAAOA,EAAK,YAAe,UAAYA,EAAK,aAAe,KAC7F,KAAK,YAAc,CAClB,UAAWA,EAAK,WAChB,KAAM,OAAOA,EAAK,MAAS,SAAWA,EAAK,KAAO,EACxD,EAEG,MAAQ,CAER,QAAA,CACKtB,EAAS,IACZ,KAAK,mBAAqB,GACtBF,GACH,KAAK,qBAAoB,EAG5B,CAAA,CACD,EACA,MAAM,qBAAsB,CAC3B,GAAI,EAAA,CAAC,KAAK,YAAc,KAAK,cAAgB,KAAK,iBAAmB,MAGrE,CAAA,KAAK,aAAe,GACpB,GAAI,CACH,MAAMhD,EAAuB,KAAK,eAAgB,KAAK,YAAY,EACnE,KAAK,UAAY,GACjB,KAAK,WAAa,GAClB,MAAM,KAAK,eAAe,EAAI,CAC/B,OAASL,EAAO,CAGf,GAAIA,GAASA,EAAM,aAAe,GAAM,CACvC,MAAM,KAAK,eAAe,EAAI,EAC9B,MACD,CAEA,KAAK,aAAe,GACpB,KAAK,UAAYoB,EAAUpB,EAAO,qBAAqB,EAEvD,KAAK,qBAAoB,CAC1B,QAAA,CACC,KAAK,aAAe,EACrB,EACD,EAEA,MAAM,aAAc,CACnB,MAAM8E,EAAiB,KAAK,kBAC5B,KAAK,mBAAqB,EAC1B,MAAMxB,EAAa,KAAK,kBAClBC,EAAY,IAAMD,IAAe,KAAK,mBACxCwB,IAAmB,KAAK,kBAE5B,GAAI,KAAK,aAAe,GAAI,CAC3B,KAAK,aAAe,QACpB,KAAK,aAAevD,EAAU,qDAAqD,EACnF,MACD,CAEA,KAAK,eAAe,MAAK,EAEzB,MAAMiC,EAAQ,OAAO,iBAAoB,WAAa,IAAI,gBAAoB,KAC9E,KAAK,cAAgBA,EACrB,KAAK,aAAe,UACpB,KAAK,aAAe,GAEpB,GAAI,CACH,MAAMuB,EAAU,MAAMC,EAAe,KAAK,WAAY,CAAE,OAAQxB,GAAO,MAAM,CAAE,EAC/E,GAAI,CAACD,EAAS,EAAI,OAClB,KAAK,QAAUwB,EACf,KAAK,cAAgB,GACrB,KAAK,aAAe,OACrB,OAAS/E,EAAO,CACf,GAAI,CAACuD,EAAS,GAAOvD,GAASA,EAAM,OAAS,aAAe,OAC5D,KAAK,aAAe,QACpB,KAAK,aAAeoB,EAAUpB,EAAOuB,EAAU,iCAAiC,CAAC,CAClF,CACD,EACA,kBAAkB0D,EAAevE,EAAS,CACzC,MAAMwE,EAAe,MAAM,QAAQxE,EAAQ,OAAO,EAAIA,EAAQ,QAAU,CAAA,EAClEyE,EAAS,KAAK,eAAiB,UAErC,OAAOF,EAAc,MAAO,CAAE,MAAO,cAAc,EAAI,CACtDA,EAAc,MAAO,CAAE,MAAO,qBAAqB,EAAI,CACtDA,EAAc,MAAO,CAAE,MAAO,uBAAuB,EAAI,CACvD,KAAK,eAAiB,SAAW,KAAK,cACpCA,EAAc,OAAQ,CAAE,MAAO,6BAA6B,EAC7D,KAAK,cAAgB1D,EAAU,iCAAiC,CAAC,EAChE,KACH0D,EAAc,SAAU,CACvB,MAAO,+BACP,MAAO,CAAE,KAAM,SAAU,SAAUE,CAAM,EACzC,GAAI,CAAE,MAAO,IAAM,CAAO,KAAK,YAAW,CAAG,CAAC,CACrD,EAASA,EAAS5D,EAAU,eAAe,EAAIA,EAAU,SAAS,CAAC,EAC7D,GAAG2D,CACT,CAAM,EACD,KAAK,kBAAkBD,CAAa,CACzC,CAAK,CACL,CAAI,CACF,EACA,kBAAkBA,EAAe,CAChC,OAAI,KAAK,eAAiB,KAAK,eAAiB,QACxC,KAAK,kBAAkBA,CAAa,EAExC,KAAK,eAAiB,QAClBA,EAAc,MAAO,CAAE,MAAO,yCAAyC,EAAI,CACjFA,EAAc,MAAO,CAAA,EACpB,KAAK,cAAgB1D,EAAU,iCAAiC,CAAC,EAClE0D,EAAc,SAAU,CACvB,MAAO,iBACP,MAAO,CAAE,KAAM,QAAQ,EACvB,GAAI,CAAE,MAAO,IAAM,CAAO,KAAK,YAAW,CAAG,CAAC,CACpD,EAAQ1D,EAAU,WAAW,CAAC,CAC9B,CAAK,EAEE,KAAK,eAAiB,QAClB0D,EAAc,MAAO,CAAE,MAAO,yCAAyC,EAAI1D,EAAU,wBAAwB,CAAC,EAE/G,KAAK,kBAAkB0D,CAAa,CAC5C,EACA,kBAAkBA,EAAe,CAChC,OAAI,KAAK,QAAQ,QACTA,EAAc,MAAO,CAAE,MAAO,yCAAyC,EAAI1D,EAAU,0BAA0B,CAAC,EAEjH0D,EAAc,MAAO,CAC3B,MAAO,8CACP,SAAU,CAAE,UAAW,KAAK,QAAQ,IAAI,CAC5C,CAAI,CACF,CACF,EACC,eAAgB,CACf,KAAK,mBAAqB,EAE1B,KAAK,YAAY,MAAK,EACtB,KAAK,eAAe,MAAK,EACzB,KAAK,aAAY,CAClB,EACA,eAAgB,CACf,KAAK,mBAAqB,EAC1B,KAAK,YAAY,MAAK,EACtB,KAAK,eAAe,MAAK,EACzB,KAAK,aAAY,CAClB,EACA,OAAOA,EAAe,CACrB,GAAI,KAAK,UAAW,CACnB,MAAMG,EAAe,CACpBH,EAAc,MAAO,CAAE,MAAO,yBAAyB,EAAI1D,EAAU,oBAAoB,CAAC,EAE1F0D,EAAc,MAAO,CAAE,MAAO,4BAA6B,MAAO,CAAE,KAAM,OAAO,GAAM,KAAK,SAAS,CACzG,EACG,OAAI,KAAK,kBACRG,EAAa,KACZH,EAAc,MAAO,CAAE,MAAO,2BAA2B,EACxD1D,EAAU,yGAAyG,CAAC,CAC1H,EAEO,KAAK,cACR6D,EAAa,KACZH,EAAc,SAAU,CACvB,MAAO,0CACP,MAAO,CAAE,KAAM,QAAQ,EACvB,GAAI,CAAE,MAAO,IAAM,CAAO,KAAK,eAAe,EAAI,CAAE,CAAC,CAC3D,EAAQ1D,EAAU,WAAW,CAAC,CAC9B,EAEO,KAAK,aACJ,KAAK,mBAER6D,EAAa,KACZH,EAAc,MAAO,CAAE,MAAO,2BAA2B,EACxD1D,EAAU,kCAAkC,CAAC,CACpD,EACe,KAAK,YACf6D,EAAa,KACZH,EAAc,MAAO,CAAE,MAAO,2BAA2B,EACxD1D,EAAU,sLAAsL,CAAC,EAClM0D,EAAc,IAAK,CAClB,MAAO,0CACP,MAAO,CAAE,KAAM,KAAK,YAAY,SAAS,CAChD,EAAS1D,EAAU,6BAA6B,CAAC,EAC3C0D,EAAc,SAAU,CACvB,MAAO,kCACP,MAAO,CAAE,KAAM,SAAU,SAAU,KAAK,YAAY,EACpD,GAAI,CAAE,MAAO,IAAM,CAAO,KAAK,oBAAmB,CAAG,CAAC,CAC7D,EAAS,KAAK,aAAe1D,EAAU,qBAAqB,EAAIA,EAAU,+BAA+B,CAAC,CAC1G,EAEK6D,EAAa,KACZH,EAAc,MAAO,CAAE,MAAO,2BAA2B,EACxD1D,EAAU,uKAAuK,CAAC,EACnL0D,EAAc,SAAU,CACvB,MAAO,0CACP,MAAO,CAAE,KAAM,SAAU,SAAU,KAAK,YAAY,EACpD,GAAI,CAAE,MAAO,IAAM,CAAO,KAAK,oBAAmB,CAAG,CAAC,CAC7D,EAAS,KAAK,aAAe1D,EAAU,qBAAqB,EAAIA,EAAU,+BAA+B,CAAC,CAC1G,GAGU0D,EAAc,MAAO,CAAE,MAAO,8CAA8C,EAAI,CACtFA,EAAc,MAAO,CAAE,MAAO,yBAA0B,IAAK,WAAW,EAAIG,CAAY,CAC5F,CAAI,CACF,CACA,OAAI,KAAK,cAAgB,UACjB,KAAK,kBAAkBH,EAAe,KAAK,kBAAoB,GACnE,CAAA,EACA,CACD,QAAS,CACRA,EAAc,IAAK,CAClB,MAAO,4BACP,MAAO,CACN,KAAM,KAAK,gBACX,OAAQ,SACR,IAAK,qBACb,CACA,EAAS1D,EAAU,mBAAmB,CAAC,CACvC,CACA,CAAK,EAEC,KAAK,WAAa,CAAC,KAAK,UACpB0D,EAAc,MAAO,CAAE,MAAO,oBAAoB,EAAI,gBAAgB,EAGvEA,EAAc,MAAO,CAAE,MAAO,mBAAmB,EAAI,CAE3DA,EAAc,SAAU,CACvB,MAAO,CAAE,OAAQ/D,GAAoB,KAAK,SAAS,EAAG,MAAO,UAAU,EAEvE,GAAI,CAAE,KAAM,IAAM,KAAK,WAAU,EAAI,MAAO,IAAM,KAAK,YAAY,EACnE,MAAO,oBACX,CAAI,CACJ,CAAG,CACF,CACD,EC1jBAvC,EAAgB,CAAE,GAAI0G,EAAmB,MAAO,CAACC,CAAI,EAAG,UAAArG,EAAS,CAAE","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..967d6528 --- /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 * `status` whenever one came, and `unanswered` when nothing came back from\n * this app (docs/architecture.md, \"Errors of the API\", says which failures\n * count). Only that fact: whether the same request is worth another try\n * depends on whether it writes, which the caller knows\n * (`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\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// A 5xx that cannot be this app's (docs/architecture.md, \"Errors of the API\").\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