feat: add profile language preferences and refine toast, dropdown and history UI
This commit is contained in:
+18
-11
@@ -16,7 +16,10 @@ import { getNickname, getAccessToken, getAuthToken, getGuestId, setAccessToken }
|
||||
import { bindIdentityDialog, validateCurrentSession } from "@rustpad/auth-ui";
|
||||
import { askConfirm } from "@rustpad/modal";
|
||||
import { isResourceAccessError, safeAppUrl } from "@rustpad/security";
|
||||
import { toast } from "@rustpad/toast";
|
||||
import { consumeQueuedToast, toast } from "@rustpad/toast";
|
||||
import { formatDateTime, formatNumber } from "@rustpad/i18n";
|
||||
|
||||
consumeQueuedToast();
|
||||
|
||||
const parts = location.pathname.split("/").filter(Boolean);
|
||||
const slug = parts[1];
|
||||
@@ -135,12 +138,12 @@ function connectWorkspaceWatch() {
|
||||
function escapeHtml(v) { const e = document.createElement("div"); e.textContent = v; return e.innerHTML; }
|
||||
function formatBytes(value) {
|
||||
const bytes = Math.max(0, Number(value) || 0);
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
if (bytes < 1024) return `${formatNumber(bytes)} B`;
|
||||
const units = ["KB", "MB", "GB", "TB"];
|
||||
let amount = bytes;
|
||||
let unit = -1;
|
||||
do { amount /= 1024; unit++; } while (amount >= 1024 && unit < units.length - 1);
|
||||
return `${amount >= 10 ? amount.toFixed(0) : amount.toFixed(1)} ${units[unit]}`;
|
||||
return `${formatNumber(amount, { maximumFractionDigits: amount >= 10 ? 0 : 1 })} ${units[unit]}`;
|
||||
}
|
||||
function noteStats(note) {
|
||||
return `<span>Participants: ${Number(note.participant_count) || 0}</span><span>Files: ${Number(note.file_count) || 0} (${formatBytes(note.file_size_bytes)})</span><span>Revisions: ${Number(note.revision_count) || 0}</span>`;
|
||||
@@ -151,7 +154,7 @@ function formatDate(value) {
|
||||
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");
|
||||
return Number.isNaN(date.getTime()) ? "—" : formatDateTime(date);
|
||||
}
|
||||
function setNotesView(view) {
|
||||
notesView = view === "table" ? "table" : "grid";
|
||||
@@ -226,6 +229,7 @@ async function openWorkspace() {
|
||||
const params = new URLSearchParams({ q: notesSearch.value.trim(), page: String(notesPage), per_page: notesPerPage.value });
|
||||
const data = await api(`/api/workspaces/${encodeURIComponent(slug)}/open?${params}`, { method: "POST", body: JSON.stringify({ access_token: accessToken || null }) });
|
||||
info = data.workspace;
|
||||
document.querySelector("#workspace-title").removeAttribute("data-i18n");
|
||||
document.querySelector("#workspace-title").textContent = info.title;
|
||||
document.querySelector("#workspace-url").textContent = location.pathname;
|
||||
document.title = `${info.title} · RustPad`;
|
||||
@@ -250,6 +254,7 @@ async function init() {
|
||||
try {
|
||||
const headers = accessToken && accessToken !== "cookie" ? { Authorization: `Bearer ${accessToken}` } : {};
|
||||
info = await api(`/api/workspaces/${encodeURIComponent(slug)}`, { headers });
|
||||
document.querySelector("#workspace-title").removeAttribute("data-i18n");
|
||||
document.querySelector("#workspace-title").textContent = info.title;
|
||||
document.querySelector("#workspace-url").textContent = location.pathname;
|
||||
updateWorkspacePasswordControl();
|
||||
@@ -270,7 +275,8 @@ document.querySelector("#password-form").addEventListener("submit", async e => {
|
||||
document.querySelector("#open-password").value = "";
|
||||
document.querySelector("#password-error").textContent = "";
|
||||
await openWorkspace();
|
||||
} catch (error) { document.querySelector("#password-error").textContent = error.message; }
|
||||
toast.success("Workspace access has been unlocked.", { title: "Workspace unlocked" });
|
||||
} catch (error) { document.querySelector("#password-error").textContent = error.message; toast.danger(error.message, { title: "Could not unlock workspace" }); }
|
||||
});
|
||||
workspacePasswordForm.addEventListener("submit", async event => {
|
||||
event.preventDefault();
|
||||
@@ -294,10 +300,11 @@ workspacePasswordForm.addEventListener("submit", async event => {
|
||||
setAccessToken("workspace", slug, result.granted);
|
||||
accessToken = getAccessToken("workspace", slug);
|
||||
workspacePasswordInput.value = "";
|
||||
toast("Workspace password set");
|
||||
toast.success("Password protection is now enabled for this workspace.", { title: "Workspace protected" });
|
||||
await openWorkspace();
|
||||
} catch (error) {
|
||||
workspacePasswordError.textContent = error.message;
|
||||
toast.danger(error.message, { title: "Could not set workspace password" });
|
||||
} finally {
|
||||
submit.disabled = false;
|
||||
}
|
||||
@@ -313,7 +320,7 @@ document.querySelector("#note-form").addEventListener("submit", async e => {
|
||||
body: JSON.stringify({ name: document.querySelector("#note-name").value, access_token: accessToken || null, protect: document.querySelector("#note-protect").checked, created_by: nickname || null })
|
||||
});
|
||||
location.assign(safeAppUrl(note.url));
|
||||
} catch (err) { error.textContent = err.message; }
|
||||
} catch (err) { error.textContent = err.message; toast.danger(err.message, { title: "Could not create note" }); }
|
||||
});
|
||||
notesList.addEventListener("click", async event => {
|
||||
const button = event.target.closest("[data-delete-note]");
|
||||
@@ -325,9 +332,9 @@ notesList.addEventListener("click", async event => {
|
||||
await api(`/api/workspaces/${encodeURIComponent(slug)}/notes/${encodeURIComponent(button.dataset.deleteNote)}`, {
|
||||
method: "DELETE", body: JSON.stringify({ access_token: accessToken || null })
|
||||
});
|
||||
toast("Note deleted");
|
||||
toast.success(`The note "${title}" was deleted.`, { title: "Note deleted" });
|
||||
await openWorkspace();
|
||||
} catch (error) { toast(error.message); button.disabled = false; }
|
||||
} catch (error) { toast.danger(error.message, { title: "Could not delete note" }); button.disabled = false; }
|
||||
});
|
||||
document.querySelectorAll("[data-notes-view]").forEach(button => button.addEventListener("click", () => {
|
||||
if (button.dataset.notesView === notesView) return;
|
||||
@@ -338,8 +345,8 @@ notesSearch.addEventListener("input", () => { clearTimeout(notesSearchTimer); no
|
||||
notesPerPage.addEventListener("change", () => { notesPage = 1; openWorkspace(); });
|
||||
notesPagination.addEventListener("click", event => { const button = event.target.closest("[data-page]"); if (!button || button.disabled) return; notesPage = Number(button.dataset.page) || 1; openWorkspace(); });
|
||||
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); }
|
||||
try { await copyText(new URL(location.pathname, location.origin).href); toast.success("Workspace link copied to the clipboard.", { title: "Link copied" }); }
|
||||
catch (e) { toast.danger(e.message, { title: "Could not copy workspace link" }); }
|
||||
});
|
||||
async function startAuthorizedWorkspace() {
|
||||
const session = await validateCurrentSession();
|
||||
|
||||
Reference in New Issue
Block a user