import { installGlobalDiagnostics, logInfo } from "./logger.js"; installGlobalDiagnostics(); import { bindIdentityDialog, handleAccountConfirmationToken, handleResetToken, logoutCurrentSession, validateCurrentSession } from "./auth-ui.js"; import { getAuthToken } from "@rustpad/session"; import { api } from "@rustpad/api"; function slugify(value, fallback) { return value.toLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || fallback; } function bindPreview(inputId, previewId, countId, prefix, fallback) { const input = document.querySelector(inputId); const preview = document.querySelector(previewId); const count = document.querySelector(countId); const update = () => { count.textContent = `${input.value.length}/80`; preview.textContent = `${prefix}${slugify(input.value, fallback)}`; }; input.addEventListener("input", update); update(); } function setBusy(button, busy, idleText, busyText) { button.disabled = busy; button.textContent = busy ? busyText : idleText; } bindPreview("#pad-name", "#pad-slug-preview", "#pad-name-count", "/p/", "note"); bindPreview("#workspace-name", "#workspace-slug-preview", "#workspace-name-count", "/w/", "workspace"); document.querySelectorAll(".password-toggle").forEach((button) => { button.addEventListener("click", () => { const input = document.getElementById(button.dataset.target); const show = input.type === "password"; input.type = show ? "text" : "password"; button.textContent = show ? "Hide" : "Show"; }); }); document.querySelector("#pad-form").addEventListener("submit", async (event) => { event.preventDefault(); const name = document.querySelector("#pad-name"); const password = document.querySelector("#pad-password"); const button = document.querySelector("#pad-button"); const error = document.querySelector("#pad-error"); error.textContent = ""; setBusy(button, true, "Create note", "Creating…"); try { const payload = { name: name.value.trim() }; if (password.value) payload.password = password.value; const result = await api("/api/pads", { method: "POST", headers: authHeaders(), body: JSON.stringify(payload) }); if (password.value) sessionStorage.setItem(`rustpad:pad:${result.slug}:password`, password.value); window.location.assign(`${result.url}?view=split&mode=markdown`); } catch (requestError) { error.textContent = requestError.message; } finally { setBusy(button, false, "Create note", "Creating…"); } }); document.querySelector("#workspace-form").addEventListener("submit", async (event) => { event.preventDefault(); const name = document.querySelector("#workspace-name"); const password = document.querySelector("#workspace-password"); const button = document.querySelector("#workspace-button"); const error = document.querySelector("#workspace-error"); error.textContent = ""; setBusy(button, true, "Create workspace", "Creating…"); try { const payload = { name: name.value.trim() }; if (password.value) payload.password = password.value; const result = await api("/api/workspaces", { method: "POST", headers: authHeaders(), body: JSON.stringify(payload) }); if (password.value) sessionStorage.setItem(`rustpad:workspace:${result.slug}:password`, password.value); window.location.assign(result.url); } catch (requestError) { error.textContent = requestError.message; } finally { setBusy(button, false, "Create workspace", "Creating…"); } }); handleAccountConfirmationToken(); handleResetToken(); const identityDialog = document.querySelector("#identity-dialog"); const guestAccount = document.querySelector("#footer-account-guest"); const userAccount = document.querySelector("#footer-account-user"); const userLabel = document.querySelector("#footer-user-label"); const registerLink = document.querySelector("#footer-register"); const registrationEnabled = document.body.dataset.registrationEnabled === "true"; const resourcesDialog = document.querySelector("#resources-dialog"); const resourcesList = document.querySelector("#resources-list"); const resourcesError = document.querySelector("#resources-error"); function authHeaders() { const token = getAuthToken(); return token ? { Authorization: `Bearer ${token}` } : {}; } async function loadResources() { resourcesError.textContent = ""; resourcesList.innerHTML = "

Loading…

"; try { const data = await api("/api/auth/resources", { headers: authHeaders() }); const items = [...data.workspaces.map(item => ({...item, kind:"workspace", url:`/w/${item.slug}`})), ...data.pads.map(item => ({...item, kind:"pad", url:`/p/${item.slug}`}))]; resourcesList.innerHTML = items.length ? "" : "

No assigned items yet.

"; for (const item of items) { const row = document.createElement("article"); row.className = "resource-row"; row.innerHTML = `
${item.title}${item.kind === "workspace" ? "Workspace" : "Note"}${item.protected ? " · password protected" : ""}
`; const inline = row.querySelector("[data-inline]"); const closeInline = () => { inline.hidden = true; inline.innerHTML = ""; }; const setInlineMessage = (text, type = "") => { const message = inline.querySelector("[data-inline-message]"); if (!message) return; message.className = `form-message resource-inline-message ${type}`.trim(); message.textContent = text; }; row.querySelector("[data-password]").addEventListener("click", () => { inline.hidden = false; inline.innerHTML = `

Leave empty to remove password protection.

`; const form = inline.querySelector("form"); const input = form.querySelector("input"); form.querySelector("[data-cancel]").addEventListener("click", closeInline); form.addEventListener("submit", async event => { event.preventDefault(); const password = input.value; if (password && password.length < 8) { setInlineMessage("Password must contain at least 8 characters.", "error"); return; } const submit = form.querySelector('[type="submit"]'); submit.disabled = true; setInlineMessage(""); try { await api("/api/auth/resources", { method:"PUT", headers:authHeaders(), body:JSON.stringify({kind:item.kind, slug:item.slug, password}) }); await loadResources(); } catch(e) { setInlineMessage(e.message, "error"); submit.disabled = false; } }); input.focus(); }); row.querySelector("[data-delete]").addEventListener("click", () => { inline.hidden = false; inline.innerHTML = `

Delete “${item.title}” permanently?

`; inline.querySelector("[data-cancel]").addEventListener("click", closeInline); inline.querySelector("[data-confirm-delete]").addEventListener("click", async event => { event.currentTarget.disabled = true; setInlineMessage(""); try { await api("/api/auth/resources", { method:"DELETE", headers:authHeaders(), body:JSON.stringify({kind:item.kind, slug:item.slug}) }); await loadResources(); } catch(e) { setInlineMessage(e.message, "error"); event.currentTarget.disabled = false; } }); }); resourcesList.append(row); } } catch(e) { resourcesList.innerHTML=""; resourcesError.textContent=e.message; } } function renderAccount(session) { guestAccount.hidden = Boolean(session); userAccount.hidden = !session; if (session) userLabel.textContent = `Signed in as ${session.nickname}`; registerLink.hidden = !registrationEnabled; } if (identityDialog) { const authDialog = bindIdentityDialog({ dialog: identityDialog, onIdentity: async (_nickname, session) => renderAccount(session), }); document.querySelector("#footer-login")?.addEventListener("click", () => { authDialog.setMode("login"); identityDialog.showModal(); }); registerLink?.addEventListener("click", () => { authDialog.setMode("register"); identityDialog.showModal(); }); document.querySelector("#footer-resources")?.addEventListener("click", async () => { resourcesDialog.showModal(); await loadResources(); }); document.querySelector("#close-resources")?.addEventListener("click", () => resourcesDialog.close()); resourcesDialog?.addEventListener("click", (event) => { if (event.target === resourcesDialog) resourcesDialog.close(); }); document.querySelector("#footer-logout")?.addEventListener("click", async () => { await logoutCurrentSession(); renderAccount(null); }); renderAccount(null); validateCurrentSession().then(renderAccount); }