import { installGlobalDiagnostics, logInfo } from "@rustpad/logger"; installGlobalDiagnostics(); import { api } from "@rustpad/api"; import { copyText } from "@rustpad/clipboard"; import { getNickname, getAccessToken, getAuthToken, setAccessToken } from "@rustpad/session"; import { askConfirm } from "@rustpad/modal"; const parts = location.pathname.split("/").filter(Boolean); const slug = parts[1]; let info; const shareToken = new URLSearchParams(location.search).get("share"); let accessToken = shareToken || getAuthToken() || getAccessToken("workspace", slug); if (shareToken) setAccessToken("workspace", slug, shareToken); const dialog = document.querySelector("#password-dialog"); const notesList = document.querySelector("#notes-list"); const notesViewKey = `rustpad:workspace:${slug}:notes-view`; let notesView = localStorage.getItem(notesViewKey) === "table" ? "table" : "grid"; let notesCache = []; function toast(text) { const el = document.querySelector("#toast"); el.textContent = text; el.classList.add("visible"); setTimeout(() => el.classList.remove("visible"), 1600); } function escapeHtml(v) { const e = document.createElement("div"); e.textContent = v; return e.innerHTML; } function formatDate(value) { if (value == null || value === "") return "—"; let raw = String(value).trim(); if (/^\d+$/.test(raw)) { const number = Number(raw); raw = raw.length <= 10 ? number * 1000 : number; } else if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?$/.test(raw)) raw = raw.replace(" ", "T") + "Z"; const date = new Date(raw); return Number.isNaN(date.getTime()) ? "—" : date.toLocaleString("pl-PL"); } function setNotesView(view) { notesView = view === "table" ? "table" : "grid"; localStorage.setItem(notesViewKey, notesView); notesList.classList.toggle("notes-grid", notesView === "grid"); notesList.classList.toggle("notes-table", notesView === "table"); document.querySelectorAll("[data-notes-view]").forEach(button => { const active = button.dataset.notesView === notesView; button.classList.toggle("active", active); button.setAttribute("aria-pressed", String(active)); }); } function deleteButton(note, inline = false) { const disabled = note.protected; const classes = `note-delete-button${inline ? " note-delete-button--inline" : ""}`; const reason = disabled ? "Protected notes cannot be deleted" : `Delete ${note.title}`; return ``; } function renderNotes(notes = notesCache) { notesCache = notes; setNotesView(notesView); if (!notes.length) { notesList.innerHTML = '

No notes yet.

'; return; } if (notesView === "table") { notesList.innerHTML = `
${notes.map(note => ` `).join("")}
NameCreated byStatusUpdatedActions
${escapeHtml(note.title)} ${escapeHtml(note.created_by || "Unknown")} ${note.protected ? 'Protected' : 'Unprotected'} ${formatDate(note.updated_at)} ${deleteButton(note, true)}
`; return; } notesList.innerHTML = notes.map(note => `

${escapeHtml(note.title)}

${note.protected ? 'Protected' : ''}
Created by: ${escapeHtml(note.created_by || "Unknown")}Updated: ${formatDate(note.updated_at)}
${deleteButton(note)}
`).join(""); } async function openWorkspace() { try { const data = await api(`/api/workspaces/${encodeURIComponent(slug)}/open`, { method: "POST", body: JSON.stringify({ access_token: accessToken || null }) }); info = data.workspace; document.querySelector("#workspace-title").textContent = info.title; document.querySelector("#workspace-url").textContent = location.pathname; document.title = `${info.title} · RustPad`; notesCache = data.notes; renderNotes(); if (dialog.open) dialog.close(); } catch (e) { if (info?.protected || e.message.toLowerCase().includes("password")) { document.querySelector("#password-error").textContent = e.message; if (!dialog.open) dialog.showModal(); } else if (e.status === 403) { location.replace("/errors/private-workspace"); } else document.querySelector("#workspace-error").textContent = e.message; } } async function init() { try { info = await api(`/api/workspaces/${encodeURIComponent(slug)}`); document.querySelector("#workspace-title").textContent = info.title; document.querySelector("#workspace-url").textContent = location.pathname; if (info.protected && !accessToken) dialog.showModal(); else openWorkspace(); } catch (e) { if (e.status === 403) location.replace("/errors/private-workspace"); else document.querySelector("#workspace-error").textContent = e.message; } } document.querySelector("#password-form").addEventListener("submit", async e => { e.preventDefault(); try { const password = document.querySelector("#open-password").value; const result = await api("/api/access-token", { method: "POST", body: JSON.stringify({ kind: "workspace", slug, password }) }); accessToken = result.access_token; setAccessToken("workspace", slug, accessToken); document.querySelector("#open-password").value = ""; document.querySelector("#password-error").textContent = ""; openWorkspace(); } catch (error) { document.querySelector("#password-error").textContent = error.message; } }); document.querySelector("#new-note-button").addEventListener("click", () => document.querySelector("#note-dialog").showModal()); document.querySelector("#cancel-note").addEventListener("click", () => document.querySelector("#note-dialog").close()); document.querySelector("#note-form").addEventListener("submit", async e => { e.preventDefault(); const error = document.querySelector("#note-error"); error.textContent = ""; try { const note = await api(`/api/workspaces/${encodeURIComponent(slug)}/notes`, { method: "POST", body: JSON.stringify({ name: document.querySelector("#note-name").value, access_token: accessToken || null, protect: document.querySelector("#note-protect").checked, created_by: getNickname() || null }) }); location.assign(`${note.url}?view=split&mode=markdown`); } catch (err) { error.textContent = err.message; } }); notesList.addEventListener("click", async event => { const button = event.target.closest("[data-delete-note]"); if (!button) return; const title = button.dataset.noteTitle; if (!await askConfirm(`Delete note “${title}”? This cannot be undone.`, { title: "Delete note", confirmText: "Delete", danger: true })) return; button.disabled = true; try { await api(`/api/workspaces/${encodeURIComponent(slug)}/notes/${encodeURIComponent(button.dataset.deleteNote)}`, { method: "DELETE", body: JSON.stringify({ access_token: accessToken || null }) }); toast("Note deleted"); await openWorkspace(); } catch (error) { toast(error.message); button.disabled = false; } }); document.querySelectorAll("[data-notes-view]").forEach(button => button.addEventListener("click", () => { if (button.dataset.notesView === notesView) return; notesView = button.dataset.notesView; renderNotes(); })); document.querySelector("#copy-workspace-link").addEventListener("click", async () => { try { await copyText(new URL(location.pathname, location.origin).href); toast("Link copied"); } catch (e) { toast(e.message); } }); setNotesView(notesView); init();