import { api } from "@rustpad/api"; import { copyText } from "@rustpad/clipboard"; import { prepareImageFile } from "@rustpad/image-upload"; import { askConfirm } from "@rustpad/modal"; import { getAuthToken } from "@rustpad/session"; import { safeAppUrl } from "@rustpad/security"; function escapeHtml(value) { return String(value).replace(/[&<>"']/g, character => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[character])); } function formatBytes(value) { const bytes = Number(value) || 0; if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } function formatDate(value) { const date = new Date(value); return Number.isNaN(date.getTime()) ? "" : date.toLocaleString("pl-PL"); } export function bindNoteFiles({ editor, endpoints, getAccessToken, canDelete, toast }) { const dialog = document.querySelector("#files-dialog"); const list = document.querySelector("#files-list"); const input = document.querySelector("#file-input"); const footer = document.querySelector("#footer-files"); async function loadFiles({ open = false } = {}) { try { const files = await api(endpoints.list, { method: "PUT", body: JSON.stringify({ access_token: getAccessToken() || null }) }); const totalSize = files.reduce((sum, file) => sum + (Number(file.size_bytes) || 0), 0); footer.textContent = `${files.length} ${files.length === 1 ? "file" : "files"} · ${formatBytes(totalSize)}`; list.innerHTML = files.length ? files.map(file => `
${escapeHtml(file.filename)}
${formatBytes(file.size_bytes)} · ${escapeHtml(file.mime_type)}${file.created_at ? ` · ${formatDate(file.created_at)}` : ""} · ${file.is_attached ? "in note" : "removed from content"}
${canDelete() ? `` : ""}
`).join("") : '

No files uploaded.

'; if (open && !dialog.open) dialog.showModal(); } catch (error) { if (open) toast(error.message); } } document.querySelector("#upload-button").addEventListener("click", () => input.click()); input.addEventListener("change", async event => { let file = event.target.files[0]; if (!file) return; if (file.type.startsWith("image/")) { file = await prepareImageFile(file); if (!file) { input.value = ""; return; } } const form = new FormData(); form.append("access_token", getAccessToken() || ""); form.append("file", file); try { const result = await api(endpoints.upload, { method: "POST", body: form, headers: {} }); const fileUrl = safeAppUrl(result.url); const text = file.type.startsWith("image/") ? `![${file.name}](${fileUrl})` : `[${file.name}](${fileUrl})`; editor.setRangeText(text, editor.selectionStart, editor.selectionEnd, "end"); editor.dispatchEvent(new Event("input", { bubbles: true })); toast("File uploaded"); await loadFiles(); } catch (error) { toast(error.message); } input.value = ""; }); document.querySelector("#files-button").addEventListener("click", () => loadFiles({ open: true })); footer.addEventListener("click", () => loadFiles({ open: true })); document.querySelector("#close-files").addEventListener("click", () => dialog.close()); list.addEventListener("click", async event => { const addButton = event.target.closest("[data-add-file-to-note]"); if (addButton) { const relative = safeAppUrl(addButton.dataset.url); const text = addButton.dataset.mime?.startsWith("image/") ? `![${addButton.dataset.name}](${relative})` : `[${addButton.dataset.name}](${relative})`; editor.setRangeText(text, editor.selectionStart, editor.selectionEnd, "end"); editor.dispatchEvent(new Event("input", { bubbles: true })); toast("Added to note"); return; } const showButton = event.target.closest("[data-show-file-code]"); if (showButton) { const panel = showButton.closest(".file-row").querySelector(".file-code"); const output = panel.querySelector("textarea"); const absolute = new URL(safeAppUrl(showButton.dataset.url), location.origin).href; let text = absolute; if (showButton.dataset.showFileCode === "markdown") { const relative = safeAppUrl(showButton.dataset.url); text = showButton.dataset.mime?.startsWith("image/") ? `![${showButton.dataset.name}](${relative})` : `[${showButton.dataset.name}](${relative})`; } output.value = text; panel.hidden = false; output.focus(); output.select(); return; } const copyButton = event.target.closest("[data-copy-generated]"); if (copyButton) { try { await copyText(copyButton.closest(".file-code").querySelector("textarea").value); toast("Copied"); } catch (error) { toast(error.message); } return; } const deleteButton = event.target.closest("[data-delete-file]"); if (!deleteButton) return; if (!await askConfirm(`Delete file "${deleteButton.dataset.fileName}" permanently?`, { title: "Delete file", confirmText: "Delete", danger: true })) return; try { await api(endpoints.remove(deleteButton.dataset.deleteFile), { method: "DELETE", headers: getAuthToken() ? { Authorization: `Bearer ${getAuthToken()}` } : {}, body: JSON.stringify({ access_token: getAccessToken() || null }) }); toast("File deleted"); await loadFiles(); } catch (error) { toast(error.message); } }); return { loadFiles }; }