v0.2.85
This commit is contained in:
1 parent
90e3c399ea
commit
57ad26878b
12 files changed
+477
-31
No files matched your search
@@ -1901,6 +1901,18 @@ dialog::backdrop {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.workspace-top__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.workspace-top__actions [hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.notes-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
@@ -3638,6 +3650,68 @@ dialog::backdrop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.archived-notes-dialog {
|
||||
width: min(620px, calc(100% - 24px));
|
||||
}
|
||||
|
||||
.archived-notes-panel {
|
||||
grid-template-rows: auto minmax(0, 1fr);
|
||||
max-height: min(78dvh, 720px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.archived-notes-panel__header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.archived-notes-panel__header h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.archived-notes-panel__header .dialog-copy {
|
||||
margin: 5px 0 0;
|
||||
}
|
||||
|
||||
.archived-notes-list {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
.archived-note-card {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--surface-card);
|
||||
}
|
||||
|
||||
.archived-note-card__copy {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.archived-note-card__copy strong {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.archived-note-card__copy span,
|
||||
.archived-note-card__copy small {
|
||||
color: var(--muted);
|
||||
font-size: .74rem;
|
||||
}
|
||||
|
||||
.notes-view-switch {
|
||||
display: inline-flex;
|
||||
padding: 3px;
|
||||
@@ -3777,6 +3851,21 @@ dialog::backdrop {
|
||||
@media (max-width: 700px) {
|
||||
.workspace-top {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.workspace-top__actions {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.archived-note-card {
|
||||
grid-template-columns: 1fr;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.archived-note-card .secondary-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.workspace-password-card {
|
||||
|
||||
+3
-3
@@ -272,9 +272,9 @@
|
||||
<p>Author, time, and version preview</p>
|
||||
</div><button id="close-history" class="icon-button">×</button>
|
||||
</div>
|
||||
<div class="history-view-switch" role="group" aria-label="History view">
|
||||
<button type="button" data-history-view="list" class="active" aria-pressed="true">List</button>
|
||||
<button type="button" data-history-view="archive" aria-pressed="false">Archive</button>
|
||||
<div class="history-view-switch" role="group" aria-label="History view" data-i18n-aria-label="editor.historyView">
|
||||
<button type="button" data-history-view="list" class="active" aria-pressed="true" data-i18n="editor.historyListView">List</button>
|
||||
<button type="button" data-history-view="archive" aria-pressed="false" data-i18n="editor.historyArchiveView">Archive</button>
|
||||
</div>
|
||||
<div id="history-list" class="history-list"></div>
|
||||
</aside>
|
||||
|
||||
+26
-17
@@ -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", () => {
|
||||
|
||||
+97
-5
@@ -46,20 +46,31 @@ const workspacePasswordForm = document.querySelector("#workspace-password-form")
|
||||
const workspacePasswordInput = document.querySelector("#workspace-set-password");
|
||||
const workspacePasswordError = document.querySelector("#workspace-password-error");
|
||||
const workspaceSettingsForm = document.querySelector("#workspace-settings-form");
|
||||
const workspaceSettingsButton = document.querySelector("#workspace-settings-button");
|
||||
const archiveDeletedNotesToggle = document.querySelector("#archive-deleted-notes");
|
||||
const workspaceSettingsStatus = document.querySelector("#workspace-settings-status");
|
||||
const archivedNotesButton = document.querySelector("#archived-notes-button");
|
||||
const archivedNotesDialog = document.querySelector("#archived-notes-dialog");
|
||||
const archivedNotesList = document.querySelector("#archived-notes-list");
|
||||
let notesPage = 1;
|
||||
let notesSearchTimer;
|
||||
const notesViewKey = `rustpad:workspace:${slug}:notes-view`;
|
||||
let notesView = localStorage.getItem(notesViewKey) === "table" ? "table" : "grid";
|
||||
let notesCache = [];
|
||||
let archivedNotesCache = [];
|
||||
|
||||
function updateWorkspacePasswordControl() {
|
||||
workspacePasswordForm.hidden = Boolean(info?.protected || !info?.can_set_password);
|
||||
}
|
||||
|
||||
function updateWorkspaceSettingsControl() {
|
||||
workspaceSettingsForm.hidden = !info?.can_manage_settings;
|
||||
const canManage = Boolean(info?.can_manage_settings);
|
||||
workspaceSettingsButton.hidden = !canManage;
|
||||
archivedNotesButton.hidden = !canManage;
|
||||
if (!canManage) {
|
||||
workspaceSettingsForm.hidden = true;
|
||||
workspaceSettingsButton.setAttribute("aria-expanded", "false");
|
||||
}
|
||||
archiveDeletedNotesToggle.checked = Boolean(info?.archive_deleted_notes);
|
||||
}
|
||||
|
||||
@@ -165,6 +176,36 @@ function formatDate(value) {
|
||||
const date = new Date(raw);
|
||||
return Number.isNaN(date.getTime()) ? "—" : formatDateTime(date);
|
||||
}
|
||||
|
||||
function renderArchivedNotes(notes) {
|
||||
archivedNotesCache = Array.isArray(notes) ? notes : [];
|
||||
if (!archivedNotesCache.length) {
|
||||
archivedNotesList.innerHTML = `<p class="empty">${escapeHtml(t("workspace.archive.empty", {}, "No archived notes."))}</p>`;
|
||||
return;
|
||||
}
|
||||
archivedNotesList.innerHTML = archivedNotesCache.map(note => {
|
||||
const author = note.created_by || t("workspace.unknown", {}, "Unknown");
|
||||
return `<article class="archived-note-card">
|
||||
<div class="archived-note-card__copy">
|
||||
<strong>${escapeHtml(note.title)}</strong>
|
||||
<span>${escapeHtml(t("workspace.archive.archivedAt", { date: formatDate(note.deleted_at) }, `Archived: ${formatDate(note.deleted_at)}`))}</span>
|
||||
<small>${escapeHtml(t("workspace.archive.createdBy", { author }, `Created by: ${author}`))}</small>
|
||||
</div>
|
||||
<button class="secondary-button inline-button" type="button" data-restore-archived-note="${escapeHtml(note.slug)}">${escapeHtml(t("workspace.archive.restore", {}, "Restore"))}</button>
|
||||
</article>`;
|
||||
}).join("");
|
||||
}
|
||||
|
||||
async function loadArchivedNotes() {
|
||||
archivedNotesList.innerHTML = `<p class="empty">${escapeHtml(t("common.loading", {}, "Loading…"))}</p>`;
|
||||
try {
|
||||
const notes = await api(`/api/workspaces/${encodeURIComponent(slug)}/archived-notes`);
|
||||
renderArchivedNotes(notes);
|
||||
} catch (error) {
|
||||
archivedNotesList.innerHTML = `<p class="form-message error">${escapeHtml(error.message)}</p>`;
|
||||
toast.danger(error.message, { title: t("workspace.archive.loadFailed", {}, "Could not load archived notes") });
|
||||
}
|
||||
}
|
||||
function setNotesView(view) {
|
||||
notesView = view === "table" ? "table" : "grid";
|
||||
localStorage.setItem(notesViewKey, notesView);
|
||||
@@ -374,6 +415,52 @@ workspacePasswordForm.addEventListener("submit", async event => {
|
||||
submit.disabled = false;
|
||||
}
|
||||
});
|
||||
workspaceSettingsButton.addEventListener("click", () => {
|
||||
if (workspaceSettingsButton.hidden) return;
|
||||
const willOpen = workspaceSettingsForm.hidden;
|
||||
workspaceSettingsForm.hidden = !willOpen;
|
||||
workspaceSettingsButton.setAttribute("aria-expanded", String(willOpen));
|
||||
if (willOpen) {
|
||||
workspaceSettingsStatus.textContent = "";
|
||||
archiveDeletedNotesToggle.checked = Boolean(info?.archive_deleted_notes);
|
||||
}
|
||||
});
|
||||
archivedNotesButton.addEventListener("click", () => {
|
||||
if (!archivedNotesDialog.open) archivedNotesDialog.showModal();
|
||||
void loadArchivedNotes();
|
||||
});
|
||||
document.querySelector("#close-archived-notes").addEventListener("click", () => archivedNotesDialog.close());
|
||||
archivedNotesDialog.addEventListener("click", event => {
|
||||
if (event.target === archivedNotesDialog) archivedNotesDialog.close();
|
||||
});
|
||||
archivedNotesList.addEventListener("click", async event => {
|
||||
const button = event.target.closest("[data-restore-archived-note]");
|
||||
if (!button) return;
|
||||
const noteSlug = button.dataset.restoreArchivedNote;
|
||||
const archivedNote = archivedNotesCache.find(note => note.slug === noteSlug);
|
||||
const title = archivedNote?.title || noteSlug;
|
||||
const confirmed = await askConfirm(
|
||||
t("workspace.archive.restoreAsk", { title }, `Restore note “${title}” to this workspace?`),
|
||||
{
|
||||
title: t("workspace.archive.restoreTitle", {}, "Restore archived note"),
|
||||
confirmText: t("workspace.archive.restore", {}, "Restore"),
|
||||
},
|
||||
);
|
||||
if (!confirmed) return;
|
||||
button.disabled = true;
|
||||
try {
|
||||
await api(`/api/workspaces/${encodeURIComponent(slug)}/archived-notes/${encodeURIComponent(noteSlug)}/restore`, { method: "POST" });
|
||||
toast.success(
|
||||
t("workspace.archive.restored", { title }, `The note "${title}" was restored.`),
|
||||
{ title: t("workspace.archive.restoredTitle", {}, "Note restored") },
|
||||
);
|
||||
await Promise.all([loadArchivedNotes(), openWorkspace()]);
|
||||
} catch (error) {
|
||||
toast.danger(error.message, { title: t("workspace.archive.restoreFailed", {}, "Could not restore note") });
|
||||
if (button.isConnected) button.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
workspaceSettingsForm.addEventListener("submit", async event => {
|
||||
event.preventDefault();
|
||||
workspaceSettingsStatus.textContent = "";
|
||||
@@ -387,12 +474,17 @@ workspaceSettingsForm.addEventListener("submit", async event => {
|
||||
info = { ...info, ...updated };
|
||||
updateWorkspaceSettingsControl();
|
||||
workspaceSettingsStatus.textContent = info.archive_deleted_notes
|
||||
? "Deleted notes will be archived in the database."
|
||||
: "Deleted notes will be permanently removed.";
|
||||
toast.success("Workspace deletion policy saved.", { title: "Workspace settings" });
|
||||
? t("workspace.settings.archiveEnabled", {}, "Deleted notes will be archived in the database.")
|
||||
: t("workspace.settings.archiveDisabled", {}, "Deleted notes will be permanently removed.");
|
||||
workspaceSettingsForm.hidden = true;
|
||||
workspaceSettingsButton.setAttribute("aria-expanded", "false");
|
||||
toast.success(
|
||||
t("workspace.settings.saved", {}, "Workspace deletion policy saved."),
|
||||
{ title: t("workspace.settings.title", {}, "Workspace settings") },
|
||||
);
|
||||
} catch (error) {
|
||||
workspaceSettingsStatus.textContent = error.message;
|
||||
toast.danger(error.message, { title: "Could not save workspace settings" });
|
||||
toast.danger(error.message, { title: t("workspace.settings.saveFailed", {}, "Could not save workspace settings") });
|
||||
} finally {
|
||||
submit.disabled = false;
|
||||
}
|
||||
|
||||
+21
-4
@@ -32,7 +32,12 @@
|
||||
<div>
|
||||
<h2>Notes</h2>
|
||||
<p>Select a note or create a new one.</p>
|
||||
</div><button id="new-note-button" class="primary-button inline-button">New note</button>
|
||||
</div>
|
||||
<div class="workspace-top__actions">
|
||||
<button id="archived-notes-button" class="secondary-button inline-button" type="button" data-i18n="workspace.archive.title" hidden>Archived notes</button>
|
||||
<button id="workspace-settings-button" class="secondary-button inline-button" type="button" aria-expanded="false" data-i18n="workspace.settings.title" hidden>Workspace settings</button>
|
||||
<button id="new-note-button" class="primary-button inline-button" type="button">New note</button>
|
||||
</div>
|
||||
</section>
|
||||
<form id="workspace-password-form" class="workspace-password-card" hidden>
|
||||
<div class="workspace-password-card__copy"><strong>Protect workspace</strong><small>One password secures the
|
||||
@@ -44,10 +49,10 @@
|
||||
</form>
|
||||
<form id="workspace-settings-form" class="workspace-settings-card" hidden>
|
||||
<div class="workspace-settings-card__copy">
|
||||
<strong>Deleted notes</strong>
|
||||
<small>Workspace-wide setting. Archived notes disappear from the workspace but remain in the database with their history and files.</small>
|
||||
<strong data-i18n="workspace.settings.deletedNotes">Deleted notes</strong>
|
||||
<small data-i18n="workspace.settings.copy">Workspace-wide setting. Archived notes disappear from the workspace but remain in the database with their history and files.</small>
|
||||
</div>
|
||||
<label class="workspace-archive-toggle"><input id="archive-deleted-notes" type="checkbox"> Archive deleted notes instead of permanently deleting them</label>
|
||||
<label class="workspace-archive-toggle"><input id="archive-deleted-notes" type="checkbox"> <span data-i18n="workspace.settings.archiveToggle">Archive deleted notes instead of permanently deleting them</span></label>
|
||||
<button class="secondary-button" type="submit">Save</button>
|
||||
<small id="workspace-settings-status" class="form-message" role="status"></small>
|
||||
</form>
|
||||
@@ -68,6 +73,18 @@
|
||||
<section id="notes-list" class="notes-grid" aria-live="polite"></section>
|
||||
<nav id="notes-pagination" class="pagination" aria-label="Notes pagination"></nav>
|
||||
</main>
|
||||
<dialog id="archived-notes-dialog" class="archived-notes-dialog">
|
||||
<section class="dialog-panel archived-notes-panel">
|
||||
<div class="archived-notes-panel__header">
|
||||
<div>
|
||||
<h2 data-i18n="workspace.archive.title">Archived notes</h2>
|
||||
<p class="dialog-copy" data-i18n="workspace.archive.copy">Restore notes that were archived instead of permanently deleted.</p>
|
||||
</div>
|
||||
<button id="close-archived-notes" class="icon-button" type="button" aria-label="Close dialog">×</button>
|
||||
</div>
|
||||
<div id="archived-notes-list" class="archived-notes-list" aria-live="polite"></div>
|
||||
</section>
|
||||
</dialog>
|
||||
<dialog id="identity-dialog">
|
||||
<form id="identity-form" autocomplete="on" class="dialog-panel identity-panel">
|
||||
<h2>Identify yourself</h2>
|
||||
|
||||
Reference in new issue
Block a user