feat: add profile language preferences and refine toast, dropdown and history UI
This commit is contained in:
+15
-11
@@ -15,6 +15,7 @@ import { copyText } from "@rustpad/clipboard";
|
||||
import { lineFromHash, lineLink } from "@rustpad/line-links";
|
||||
import { alignPreviewLineNumbers, renderMarkdown, setMarkdownFiles } from "@rustpad/markdown";
|
||||
import { toast } from "@rustpad/toast";
|
||||
import { formatDateTime, t } from "@rustpad/i18n";
|
||||
import { getTheme } from "@rustpad/theme";
|
||||
import { loadHighlight, loadMediaPlayer, loadMermaid } from "@rustpad/vendor-libs";
|
||||
|
||||
@@ -43,7 +44,7 @@ async function renderMermaid() {
|
||||
if (!node.isConnected || !content.contains(node) || !node.parentNode) return;
|
||||
const message = document.createElement("p");
|
||||
message.className = "error mermaid-error";
|
||||
message.textContent = "Failed to load Mermaid.";
|
||||
message.textContent = t("public.mermaidFailed", {}, "Failed to load Mermaid.");
|
||||
node.parentNode.insertBefore(message, node);
|
||||
});
|
||||
}
|
||||
@@ -53,7 +54,7 @@ async function renderMediaPlayers() { const nodes = content.querySelectorAll("[d
|
||||
function lockPublicContent(allowTaskUpdates) {
|
||||
content.querySelectorAll('[contenteditable]').forEach(node => node.removeAttribute('contenteditable'));
|
||||
content.querySelectorAll('.preview-editable').forEach(node => node.classList.remove('preview-editable'));
|
||||
content.querySelectorAll('.task-checkbox').forEach(box => { box.disabled = !allowTaskUpdates; box.title = allowTaskUpdates ? 'Update this task' : 'Task updates are disabled by the owner'; });
|
||||
content.querySelectorAll('.task-checkbox').forEach(box => { box.disabled = !allowTaskUpdates; box.title = allowTaskUpdates ? t("public.task.update", {}, "Update this task") : t("public.task.disabled", {}, "Task updates are disabled by the owner"); });
|
||||
}
|
||||
function publicAnchorTarget(hash) {
|
||||
const rawId = String(hash || "").replace(/^#/, "");
|
||||
@@ -154,8 +155,8 @@ function installPublicPermalinks() {
|
||||
anchor.dataset.line = String(line);
|
||||
anchor.dataset.linkKind = heading ? "heading" : "line";
|
||||
anchor.textContent = String(line);
|
||||
anchor.setAttribute("aria-label", heading ? `Copy link to heading on line ${line}` : `Copy link to line ${line}`);
|
||||
anchor.title = heading ? `Copy link to this heading (line ${line})` : `Copy link to line ${line}`;
|
||||
anchor.setAttribute("aria-label", heading ? t("public.copyHeadingAria", { line }, `Copy link to heading on line ${line}`) : t("public.copyLineAria", { line }, `Copy link to line ${line}`));
|
||||
anchor.title = heading ? t("public.copyHeadingTitle", { line }, `Copy link to this heading (line ${line})`) : t("public.copyLineAria", { line }, `Copy link to line ${line}`);
|
||||
target.append(anchor);
|
||||
});
|
||||
}
|
||||
@@ -179,7 +180,9 @@ async function initialize() {
|
||||
if (passwordDialog.open) passwordDialog.close();
|
||||
passwordError.textContent = "";
|
||||
document.querySelector("#public-title").textContent = page.title;
|
||||
document.querySelector("#public-meta").textContent = `Updated: ${new Date(page.updated_at).toLocaleString("en-US")}${page.allow_task_updates ? " · tasks can be updated" : ""}`;
|
||||
document.querySelector("#public-meta").textContent = page.allow_task_updates
|
||||
? t("public.updatedTasks", { date: formatDateTime(page.updated_at) }, `Updated: ${formatDateTime(page.updated_at)} · tasks can be updated`)
|
||||
: t("public.updated", { date: formatDateTime(page.updated_at) }, `Updated: ${formatDateTime(page.updated_at)}`);
|
||||
document.title = `${page.title} · RustPad`;
|
||||
setMarkdownFiles(page.files || []);
|
||||
content.innerHTML = renderMarkdown(page.content);
|
||||
@@ -191,7 +194,7 @@ async function initialize() {
|
||||
} catch (error) {
|
||||
publicAnchorSettleCleanup?.();
|
||||
if (error.status === 401 || error.status === 403) {
|
||||
passwordError.textContent = error.status === 403 ? "Sign in with an authorized account or enter the resource password." : "Enter the correct password.";
|
||||
passwordError.textContent = error.status === 403 ? t("public.passwordAuthorized", {}, "Sign in with an authorized account or enter the resource password.") : t("public.passwordCorrect", {}, "Enter the correct password.");
|
||||
if (!passwordDialog.open) passwordDialog.showModal();
|
||||
passwordInput.focus();
|
||||
return;
|
||||
@@ -201,6 +204,7 @@ async function initialize() {
|
||||
message.className = "error";
|
||||
message.textContent = String(error.message);
|
||||
content.append(message);
|
||||
toast.danger(error.message, { title: "Could not load published page" });
|
||||
}
|
||||
}
|
||||
content.addEventListener("click", async event => {
|
||||
@@ -216,16 +220,16 @@ content.addEventListener("click", async event => {
|
||||
await copyText(href);
|
||||
link.classList.add("is-copied");
|
||||
setTimeout(() => link.classList.remove("is-copied"), 900);
|
||||
toast(link.dataset.linkKind === "heading" ? "Heading link copied" : `Link to line ${link.dataset.line} copied`);
|
||||
} catch (error) { toast(error.message); }
|
||||
toast.success(link.dataset.linkKind === "heading" ? "Heading link copied to the clipboard." : `Link to line ${link.dataset.line} copied to the clipboard.`, { title: "Link copied" });
|
||||
} catch (error) { toast.danger(error.message, { title: "Could not copy link" }); }
|
||||
});
|
||||
window.addEventListener("hashchange", () => { publicAnchorSettleCleanup?.(); scrollToPublicAnchor(location.hash, "smooth"); });
|
||||
content.addEventListener("change", async event => { const box = event.target.closest(".task-checkbox"); if (!box || box.disabled) return; const previous = !box.checked; box.disabled = true; try { const page = await api(`/api/public/${encodeURIComponent(token)}/tasks`, { method: "POST", headers: pageHeaders(), body: JSON.stringify({ source_line: Number(box.dataset.sourceLine), checked: box.checked }) }); document.querySelector("#public-meta").textContent = `Updated: ${new Date(page.updated_at).toLocaleString("en-US")} · tasks can be updated`; toast("Task saved"); } catch (error) { box.checked = previous; toast(error.message); } finally { box.disabled = false; } });
|
||||
content.addEventListener("change", async event => { const box = event.target.closest(".task-checkbox"); if (!box || box.disabled) return; const previous = !box.checked; box.disabled = true; try { const page = await api(`/api/public/${encodeURIComponent(token)}/tasks`, { method: "POST", headers: pageHeaders(), body: JSON.stringify({ source_line: Number(box.dataset.sourceLine), checked: box.checked }) }); document.querySelector("#public-meta").textContent = t("public.updatedTasks", { date: formatDateTime(page.updated_at) }, `Updated: ${formatDateTime(page.updated_at)} · tasks can be updated`); toast.success(box.checked ? "Task marked as complete." : "Task marked as incomplete.", { title: "Task updated" }); } catch (error) { box.checked = previous; toast.danger(error.message, { title: "Could not update task" }); } finally { box.disabled = false; } });
|
||||
lineNumbersToggle.addEventListener("change", () => { document.body.classList.toggle("hide-preview-line-numbers", !lineNumbersToggle.checked); });
|
||||
lineLinksToggle.addEventListener("change", () => { document.body.classList.toggle("line-links-enabled", lineLinksToggle.checked); });
|
||||
fullWidthToggle.addEventListener("change", () => setFullWidth(fullWidthToggle.checked));
|
||||
window.addEventListener("resize", () => requestAnimationFrame(() => alignPreviewLineNumbers(content)));
|
||||
passwordForm.addEventListener("submit", async event => { event.preventDefault(); pagePassword = passwordInput.value; await initialize(); });
|
||||
passwordForm.addEventListener("submit", async event => { event.preventDefault(); pagePassword = passwordInput.value; await initialize(); if (!passwordDialog.open) toast.success("The published page has been unlocked.", { title: "Page unlocked" }); else toast.danger(passwordError.textContent || "Enter the correct password.", { title: "Could not unlock page" }); });
|
||||
passwordDialog.addEventListener("cancel", event => event.preventDefault());
|
||||
document.querySelector("#copy-public-link").addEventListener("click", async () => { try { await copyText(location.href); toast("Link copied"); } catch (error) { toast(error.message); } });
|
||||
document.querySelector("#copy-public-link").addEventListener("click", async () => { try { await copyText(location.href); toast.success("Published page link copied to the clipboard.", { title: "Link copied" }); } catch (error) { toast.danger(error.message, { title: "Could not copy page link" }); } });
|
||||
initialize();
|
||||
|
||||
Reference in New Issue
Block a user