This commit is contained in:
Mateusz Gruszczyński committed 2026-09-25 15:31:55 +02:00
1 parent 90e3c399ea
commit 57ad26878b
12 files changed
+477 -31

No files matched your search

+26 -17
View File
@@ -26,7 +26,7 @@ import { bindIdentityDialog, validateCurrentSession } from "@rustpad/auth-ui";
import { bindNoteFiles } from "@rustpad/note-files";
import { currentShareUrl, readEditorState, writeEditorState } from "@rustpad/url-state";
import { toast } from "@rustpad/toast";
import { formatDateTime, formatNumber, formatTime, t, translateSource } from "@rustpad/i18n";
import { formatDateTime, formatNumber, formatTime, getLocale, t, tp, translateSource } from "@rustpad/i18n";
import { getTheme } from "@rustpad/theme";
import { isResourceAccessError } from "@rustpad/security";
import { syncSelectControl } from "@rustpad/select";
@@ -2442,16 +2442,17 @@ export function startNoteEditor(adapter) {
}
function historyRevisionCard(revision, compact = false) {
const snippet = escapeHtml(revision.content.trim().split("\n").slice(0, compact ? 2 : 3).join(" · ").slice(0, compact ? 110 : 150) || "Empty note");
const author = revision.author || "Unknown author";
return `<article class="revision${compact ? " revision--compact" : ""}" data-history-revision="${revision.id}"><span class="revision__marker" style="--owner:${colorFor(author)}"></span><div><div class="revision__meta"><strong>${escapeHtml(author)}</strong><time>${formatDate(revision.created_at)}</time></div><p class="revision__snippet">${snippet}</p><button type="button" data-preview="${revision.id}">Preview</button><button type="button" data-revision="${revision.id}">Restore</button><div class="revision__preview" hidden></div></div></article>`;
const emptyNote = t("editor.historyEmptyNote", {}, "Empty note");
const snippet = escapeHtml(revision.content.trim().split("\n").slice(0, compact ? 2 : 3).join(" · ").slice(0, compact ? 110 : 150) || emptyNote);
const author = revision.author || t("editor.historyUnknownAuthor", {}, "Unknown author");
return `<article class="revision${compact ? " revision--compact" : ""}" data-history-revision="${revision.id}"><span class="revision__marker" style="--owner:${colorFor(author)}"></span><div><div class="revision__meta"><strong>${escapeHtml(author)}</strong><time>${formatDate(revision.created_at)}</time></div><p class="revision__snippet">${snippet}</p><button type="button" data-preview="${revision.id}">${escapeHtml(t("common.preview", {}, "Preview"))}</button><button type="button" data-revision="${revision.id}">${escapeHtml(t("editor.historyRestore", {}, "Restore"))}</button><div class="revision__preview" hidden></div></div></article>`;
}
function renderHistoryListView() {
historyList.classList.remove("history-list--archive");
historyList.innerHTML = historyRevisions.length
? historyRevisions.map(revision => historyRevisionCard(revision)).join("")
: '<p class="empty">No history yet.</p>';
: `<p class="empty">${escapeHtml(t("editor.historyEmpty", {}, "No history yet."))}</p>`;
}
function dayKey(date) {
@@ -2468,8 +2469,8 @@ export function startNoteEditor(adapter) {
.filter(item => item.date);
if (!dated.length) {
historyList.innerHTML = historyRevisions.length
? '<p class="empty">Archive view needs valid revision timestamps.</p>'
: '<p class="empty">No history yet.</p>';
? `<p class="empty">${escapeHtml(t("editor.historyArchiveInvalidDates", {}, "Archive view needs valid revision timestamps."))}</p>`
: `<p class="empty">${escapeHtml(t("editor.historyEmpty", {}, "No history yet."))}</p>`;
return;
}
@@ -2486,13 +2487,18 @@ export function startNoteEditor(adapter) {
});
const maxMonthCount = Math.max(1, ...monthCounts);
const months = Array.from({ length: 12 }, (_, month) => month);
const monthNames = new Intl.DateTimeFormat(undefined, { month: "short" });
const monthNamesLong = new Intl.DateTimeFormat(undefined, { month: "long" });
const locale = getLocale();
const monthNames = new Intl.DateTimeFormat(locale, { month: "short" });
const monthNamesLong = new Intl.DateTimeFormat(locale, { month: "long" });
const weekdayNames = Array.from({ length: 7 }, (_, index) =>
new Intl.DateTimeFormat(locale, { weekday: "narrow" }).format(new Date(2024, 0, 1 + index))
);
const yearButtons = years.map(year => `<button type="button" data-history-year="${year}" class="${year === historyArchiveYear ? "active" : ""}" aria-pressed="${year === historyArchiveYear}">${year}</button>`).join("");
const histogram = months.map(month => {
const count = monthCounts[month];
const height = count ? Math.max(12, Math.round((count / maxMonthCount) * 72)) : 3;
return `<div class="history-archive__bar-cell" title="${escapeHtml(monthNamesLong.format(new Date(historyArchiveYear, month, 1)))}: ${count} revisions"><span class="history-archive__bar" style="--history-bar-height:${height}px"></span><small>${escapeHtml(monthNames.format(new Date(historyArchiveYear, month, 1)))}</small></div>`;
const revisionLabel = tp("editor.historyRevisionCount", count, {}, "{count} revisions");
return `<div class="history-archive__bar-cell" title="${escapeHtml(monthNamesLong.format(new Date(historyArchiveYear, month, 1)))}: ${escapeHtml(revisionLabel)}"><span class="history-archive__bar" style="--history-bar-height:${height}px"></span><small>${escapeHtml(monthNames.format(new Date(historyArchiveYear, month, 1)))}</small></div>`;
}).join("");
const calendars = months.filter(month => monthCounts[month] > 0).map(month => {
@@ -2505,19 +2511,22 @@ export function startNoteEditor(adapter) {
const key = `${historyArchiveYear}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
const revisions = byDay.get(key) || [];
const active = historyArchiveDay === key;
const revisionLabel = tp("editor.historyRevisionCount", revisions.length, {}, "{count} revisions");
days += revisions.length
? `<button type="button" class="history-calendar__day${active ? " active" : ""}" data-history-day="${key}" title="${revisions.length} revision${revisions.length === 1 ? "" : "s"}"><span>${day}</span><small>${revisions.length}</small></button>`
? `<button type="button" class="history-calendar__day${active ? " active" : ""}" data-history-day="${key}" title="${escapeHtml(revisionLabel)}"><span>${day}</span><small>${revisions.length}</small></button>`
: `<span class="history-calendar__day history-calendar__day--empty"><span>${day}</span></span>`;
}
return `<section class="history-calendar__month"><div class="history-calendar__month-head"><strong>${escapeHtml(monthNamesLong.format(first))}</strong><span>${monthCounts[month]} revisions</span></div><div class="history-calendar__weekdays"><span>M</span><span>T</span><span>W</span><span>T</span><span>F</span><span>S</span><span>S</span></div><div class="history-calendar__days">${blanks}${days}</div></section>`;
const monthRevisionLabel = tp("editor.historyRevisionCount", monthCounts[month], {}, "{count} revisions");
const weekdayHtml = weekdayNames.map(name => `<span>${escapeHtml(name)}</span>`).join("");
return `<section class="history-calendar__month"><div class="history-calendar__month-head"><strong>${escapeHtml(monthNamesLong.format(first))}</strong><span>${escapeHtml(monthRevisionLabel)}</span></div><div class="history-calendar__weekdays">${weekdayHtml}</div><div class="history-calendar__days">${blanks}${days}</div></section>`;
}).join("");
const selected = historyArchiveDay && byDay.get(historyArchiveDay);
const selectedHtml = selected?.length
? `<section class="history-archive__selected"><div class="history-archive__selected-head"><strong>${escapeHtml(historyArchiveDay)}</strong><span>${selected.length} saved version${selected.length === 1 ? "" : "s"}</span></div>${selected.map(revision => historyRevisionCard(revision, true)).join("")}</section>`
: '<p class="history-archive__hint">Select a highlighted day to inspect saved versions.</p>';
? `<section class="history-archive__selected"><div class="history-archive__selected-head"><strong>${escapeHtml(historyArchiveDay)}</strong><span>${escapeHtml(tp("editor.historySavedVersionCount", selected.length, {}, "{count} saved versions"))}</span></div>${selected.map(revision => historyRevisionCard(revision, true)).join("")}</section>`
: `<p class="history-archive__hint">${escapeHtml(t("editor.historyArchiveHint", {}, "Select a highlighted day to inspect saved versions."))}</p>`;
historyList.innerHTML = `<div class="history-archive"><div class="history-archive__years" aria-label="Archive year">${yearButtons}</div><div class="history-archive__histogram" aria-label="Revision activity by month">${histogram}</div><div class="history-calendar">${calendars}</div>${selectedHtml}</div>`;
historyList.innerHTML = `<div class="history-archive"><div class="history-archive__years" aria-label="${escapeHtml(t("editor.historyArchiveYear", {}, "Archive year"))}">${yearButtons}</div><div class="history-archive__histogram" aria-label="${escapeHtml(t("editor.historyArchiveActivity", {}, "Revision activity by month"))}">${histogram}</div><div class="history-calendar">${calendars}</div>${selectedHtml}</div>`;
}
function renderHistory() {
@@ -2581,7 +2590,7 @@ export function startNoteEditor(adapter) {
historyPanel.classList.add("open");
historyPanel.setAttribute("aria-hidden", "false");
document.body.classList.add("history-open");
historyList.innerHTML = '<p class="empty">Loading…</p>';
historyList.innerHTML = `<p class="empty">${escapeHtml(t("common.loading", {}, "Loading…"))}</p>`;
try {
historyRevisions = await adapter.loadHistory(accessToken);
historyArchiveYear = null;
@@ -2589,7 +2598,7 @@ export function startNoteEditor(adapter) {
renderHistory();
} catch (e) {
historyList.innerHTML = `<p class="error">${escapeHtml(e.message)}</p>`;
toast.danger(e.message, { title: "Could not load version history" });
toast.danger(e.message, { title: t("editor.historyLoadFailed", {}, "Could not load version history") });
}
});
document.querySelector("#close-history").addEventListener("click", () => {