favourited notes
This commit is contained in:
+39
-3
@@ -109,6 +109,7 @@ const registerLink = document.querySelector("#footer-register");
|
||||
const registrationEnabled = document.body.dataset.registrationEnabled === "true";
|
||||
const resourcesDialog = document.querySelector("#resources-dialog");
|
||||
const resourcesList = document.querySelector("#resources-list");
|
||||
const favoritesList = document.querySelector("#favorites-list");
|
||||
const resourcesError = document.querySelector("#resources-error");
|
||||
const resourcesSearch = document.querySelector("#resources-search");
|
||||
const resourcesPerPage = document.querySelector("#resources-per-page");
|
||||
@@ -174,11 +175,46 @@ document.addEventListener("click", event => {
|
||||
document.addEventListener("keydown", event => {
|
||||
if (event.key === "Escape") closeResourcePasswordMenus();
|
||||
});
|
||||
|
||||
function renderFavorites(items) {
|
||||
if (!favoritesList) return;
|
||||
favoritesList.innerHTML = items.length ? "" : `<p>${escapeHtml(t("favorites.empty", {}, "No favorites yet."))}</p>`;
|
||||
for (const item of items) {
|
||||
const row = document.createElement("article");
|
||||
row.className = "resource-row resource-row--favorite";
|
||||
const workspace = item.workspace_title
|
||||
? ` · ${t("common.workspace", {}, "Workspace")}: ${item.workspace_title}`
|
||||
: "";
|
||||
row.innerHTML = `<div class="resource-copy"><div class="resource-title-line"><a href="${escapeHtml(safeAppUrl(item.url))}" title="${escapeHtml(item.title)}">${escapeHtml(item.title)}</a></div><small>${escapeHtml(t("common.note", {}, "Note"))}${escapeHtml(workspace)}</small></div><button class="resource-favorite-remove" type="button" data-unfavorite title="${escapeHtml(t("favorites.remove", {}, "Remove from favorites"))}" aria-label="${escapeHtml(t("favorites.remove", {}, "Remove from favorites"))}">★</button>`;
|
||||
row.querySelector("[data-unfavorite]")?.addEventListener("click", async event => {
|
||||
const button = event.currentTarget;
|
||||
button.disabled = true;
|
||||
try {
|
||||
const target = { kind: item.kind, slug: item.slug };
|
||||
if (item.workspace_slug) target.workspace_slug = item.workspace_slug;
|
||||
await api("/api/auth/favorites", { method: "DELETE", headers: authHeaders(), body: JSON.stringify(target) });
|
||||
toast.success(t("favorites.removed", {}, "Removed from favorites."), { title: t("favorites.title", {}, "Favorites") });
|
||||
await loadResources();
|
||||
} catch (error) {
|
||||
button.disabled = false;
|
||||
resourcesError.textContent = error.message;
|
||||
toast.danger(error.message, { title: t("favorites.updateFailed", {}, "Could not update favorites") });
|
||||
}
|
||||
});
|
||||
favoritesList.append(row);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadResources() {
|
||||
resourcesError.textContent = ""; resourcesList.innerHTML = "<p>Loading…</p>";
|
||||
resourcesError.textContent = ""; resourcesList.innerHTML = "<p>Loading…</p>"; if (favoritesList) favoritesList.innerHTML = "<p>Loading…</p>";
|
||||
try {
|
||||
const params = new URLSearchParams({ q: resourcesSearch.value.trim(), page: String(resourcesPage), per_page: resourcesPerPage.value });
|
||||
const data = await api(`/api/auth/resources?${params}`, { headers: authHeaders() });
|
||||
const favoriteParams = new URLSearchParams({ q: resourcesSearch.value.trim() });
|
||||
const [data, favorites] = await Promise.all([
|
||||
api(`/api/auth/resources?${params}`, { headers: authHeaders() }),
|
||||
api(`/api/auth/favorites?${favoriteParams}`, { headers: authHeaders() }),
|
||||
]);
|
||||
renderFavorites(favorites.items || []);
|
||||
resourcesPage = data.pagination.page;
|
||||
const items = data.items.map(item => ({ ...item, url: item.kind === "workspace" ? `/w/${item.slug}` : `/p/${item.slug}` }));
|
||||
resourcesList.innerHTML = items.length ? "" : "<p>No assigned items yet.</p>";
|
||||
@@ -395,7 +431,7 @@ async function loadResources() {
|
||||
resourcesList.append(row);
|
||||
}
|
||||
renderResourcesPagination(data.pagination);
|
||||
} catch (e) { resourcesList.innerHTML = ""; resourcesPagination.innerHTML = ""; resourcesError.textContent = e.message; toast.danger(e.message, { title: "Could not load your items" }); }
|
||||
} catch (e) { resourcesList.innerHTML = ""; if (favoritesList) favoritesList.innerHTML = ""; resourcesPagination.innerHTML = ""; resourcesError.textContent = e.message; toast.danger(e.message, { title: "Could not load your items" }); }
|
||||
}
|
||||
|
||||
resourcesSearch?.addEventListener("input", () => { clearTimeout(resourcesSearchTimer); resourcesSearchTimer = setTimeout(() => { resourcesPage = 1; loadResources(); }, 250); });
|
||||
|
||||
@@ -22,6 +22,7 @@ export function createPadAdapter() {
|
||||
|
||||
return {
|
||||
access: { kind: "pad", key: slug },
|
||||
favorite: { kind: "pad", slug },
|
||||
passwordScope: "note",
|
||||
addressSelector: "#document-url",
|
||||
title: info => `${info.title} · RustPad`,
|
||||
@@ -68,6 +69,7 @@ export function createWorkspaceNoteAdapter() {
|
||||
|
||||
return {
|
||||
access: { kind: "workspace", key: workspaceSlug },
|
||||
favorite: { kind: "note", slug: noteSlug, workspace_slug: workspaceSlug },
|
||||
passwordScope: "workspace",
|
||||
addressSelector: "#document-url",
|
||||
title: info => `${info.title} · ${info.workspace_title}`,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
import { installGlobalDiagnostics, logInfo } from "@rustpad/logger";
|
||||
import { api } from "@rustpad/api";
|
||||
installGlobalDiagnostics();
|
||||
|
||||
import { applyAuthorshipEdit, authorshipOwners, lineAuthors, mapSelectionThroughEdit, parseAuthorship, renderAuthorshipLayer, replaceAuthorshipOwner, serializeAuthorship, syncAuthorshipLayer } from "@rustpad/authorship";
|
||||
@@ -36,7 +37,7 @@ export function startNoteEditor(adapter) {
|
||||
const editor = document.querySelector("#editor"), preview = document.querySelector("#preview"), editorWorkspace = document.querySelector("#editor-workspace"), gutter = document.querySelector("#line-gutter"), ownerLabels = document.querySelector("#owner-labels"), authorshipLayer = document.querySelector("#authorship-layer");
|
||||
const modeToggle = document.querySelector("#mode-toggle"), toolbarCollapseToggle = document.querySelector("#toolbar-collapse-toggle"), navbarCollapseToggle = document.querySelector("#navbar-collapse-toggle"), passwordDialog = document.querySelector("#password-dialog"), identityDialog = document.querySelector("#identity-dialog");
|
||||
const accessLevel = document.querySelector("#access-level"), roomDetails = document.querySelector("#room-details"), roomUsers = document.querySelector("#room-users"), roomCount = document.querySelector("#room-count"), socketLatency = document.querySelector("#socket-latency"), mobileConnectionDetails = document.querySelector("#mobile-connection-details"), chatMessages = document.querySelector("#chat-messages"), chatForm = document.querySelector("#chat-form"), chatInput = document.querySelector("#chat-input"), chatUnread = document.querySelector("#chat-unread"), mobileChatUnread = document.querySelector("#mobile-chat-unread"), connectionNotice = document.querySelector("#connection-notice"), connectionNoticeTitle = document.querySelector("#connection-notice-title"), connectionNoticeMessage = document.querySelector("#connection-notice-message");
|
||||
const saveState = document.querySelector("#save-state");
|
||||
const saveState = document.querySelector("#save-state"), favoriteToggle = document.querySelector("#favorite-toggle");
|
||||
editor.readOnly = true;
|
||||
let unreadChat = 0;
|
||||
const compactToggle = document.querySelector("#compact-toggle"), lineLinksToggle = document.querySelector("#line-links-toggle"), authorshipColorsToggle = document.querySelector("#authorship-colors-toggle"), authorshipColorsLabel = document.querySelector("#authorship-colors-label"), publicPageEnabled = document.querySelector("#public-page-enabled"), publicTaskUpdates = document.querySelector("#public-task-updates"), unprotectPublicPage = document.querySelector("#unprotect-public-page"), participantBadges = document.querySelector("#participant-badges"), fontFamily = document.querySelector("#font-family"), fontSize = document.querySelector("#font-size"), currentUser = document.querySelector("#current-user"), userColorPicker = document.querySelector("#user-color-picker"), mobileColorPicker = document.querySelector("#mobile-color-picker"), useGlobalColorButton = document.querySelector("#use-global-color");
|
||||
@@ -49,7 +50,7 @@ export function startNoteEditor(adapter) {
|
||||
? crypto.randomUUID().replaceAll("-", "")
|
||||
: `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`;
|
||||
const collaboration = new CollaborationSession(collaborationClientId);
|
||||
let accessToken = shareToken || getAccessToken(adapter.access.kind, adapter.access.key), password = "", nickname = getNickname(), info, socket, saveTimer, applyingRemote = false, applyingHistory = false, resourceUnlocked = false, uiState = readEditorState(), authorship = parseAuthorship("", "[]"), previousContent = "", globalColor = "", noteColor = "", presenceUsers = [], authorshipMode = "simple", authorshipColorsEnabled = true, lastRevealedLineHash = "", flushRequested = false;
|
||||
let accessToken = shareToken || getAccessToken(adapter.access.kind, adapter.access.key), password = "", nickname = getNickname(), info, socket, saveTimer, applyingRemote = false, applyingHistory = false, resourceUnlocked = false, uiState = readEditorState(), authorship = parseAuthorship("", "[]"), previousContent = "", globalColor = "", noteColor = "", presenceUsers = [], authorshipMode = "simple", authorshipColorsEnabled = true, lastRevealedLineHash = "", flushRequested = false, accountSession = null;
|
||||
let editorSettingsSaveTimer, editorSettingsSaveInFlight = false, pendingPersonalSettingsSave = false, pendingAuthorshipSettingsSave = false, connectionNoticeTimer = 0, connectionWasInterrupted = false;
|
||||
let pendingPreviewViewport = null;
|
||||
const editHistory = {
|
||||
@@ -225,6 +226,41 @@ export function startNoteEditor(adapter) {
|
||||
: {};
|
||||
}
|
||||
function accountHeaders() { return {}; }
|
||||
function favoriteParams() {
|
||||
const target = adapter.favorite;
|
||||
if (!target) return "";
|
||||
const params = new URLSearchParams({ kind: target.kind, slug: target.slug });
|
||||
if (target.workspace_slug) params.set("workspace_slug", target.workspace_slug);
|
||||
return params.toString();
|
||||
}
|
||||
function renderFavoriteButton(active) {
|
||||
if (!favoriteToggle) return;
|
||||
const favorite = Boolean(active);
|
||||
favoriteToggle.hidden = !accountSession || !adapter.favorite;
|
||||
favoriteToggle.setAttribute("aria-pressed", String(favorite));
|
||||
favoriteToggle.querySelector("span").textContent = favorite ? "★" : "☆";
|
||||
const label = favorite
|
||||
? t("favorites.remove", {}, "Remove from favorites")
|
||||
: t("favorites.add", {}, "Add to favorites");
|
||||
favoriteToggle.title = label;
|
||||
favoriteToggle.setAttribute("aria-label", label);
|
||||
}
|
||||
async function syncFavoriteButton() {
|
||||
if (!favoriteToggle || !adapter.favorite || !accountSession) {
|
||||
renderFavoriteButton(false);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const status = await api(`/api/auth/favorites/status?${favoriteParams()}`);
|
||||
renderFavoriteButton(Boolean(status.favorite));
|
||||
} catch (error) {
|
||||
if (error.status === 401 || error.status === 403 || error.status === 404) {
|
||||
favoriteToggle.hidden = true;
|
||||
return;
|
||||
}
|
||||
renderFavoriteButton(false);
|
||||
}
|
||||
}
|
||||
async function loadNoteInfo() {
|
||||
info = await adapter.loadInfo(sessionHeaders());
|
||||
const documentTitle = document.querySelector("#document-title");
|
||||
@@ -1574,7 +1610,7 @@ export function startNoteEditor(adapter) {
|
||||
});
|
||||
socket.connect();
|
||||
}
|
||||
bindIdentityDialog({ dialog: identityDialog, onIdentity: async value => { nickname = value; accessToken = shareToken || getAccessToken(adapter.access.kind, adapter.access.key); identityDialog.close(); updateCurrentUser(); await loadNoteInfo(); if (info.protected && info.access_level === "none") passwordDialog.showModal(); else { loadFiles(); connect(); } } });
|
||||
bindIdentityDialog({ dialog: identityDialog, onIdentity: async (value, session) => { nickname = value; accountSession = session || await validateCurrentSession(); accessToken = shareToken || getAccessToken(adapter.access.kind, adapter.access.key); identityDialog.close(); updateCurrentUser(); await loadNoteInfo(); await syncFavoriteButton(); if (info.protected && info.access_level === "none") passwordDialog.showModal(); else { loadFiles(); connect(); } } });
|
||||
identityDialog.addEventListener("close", () => { if (!nickname) queueMicrotask(() => { if (!identityDialog.open) identityDialog.showModal(); }); });
|
||||
async function showSystemNotFound() {
|
||||
try {
|
||||
@@ -1594,8 +1630,8 @@ export function startNoteEditor(adapter) {
|
||||
async function initialize() {
|
||||
applyUi();
|
||||
try {
|
||||
const session = await validateCurrentSession();
|
||||
nickname = session?.nickname || getNickname();
|
||||
accountSession = await validateCurrentSession();
|
||||
nickname = accountSession?.nickname || getNickname();
|
||||
if (!nickname) {
|
||||
if (!identityDialog.open) identityDialog.showModal();
|
||||
return;
|
||||
@@ -1608,6 +1644,7 @@ export function startNoteEditor(adapter) {
|
||||
adapter.configureView?.(info);
|
||||
applyUi({ write: true, replace: true });
|
||||
updateCurrentUser();
|
||||
await syncFavoriteButton();
|
||||
if (info.protected && info.access_level === "none") passwordDialog.showModal();
|
||||
else { loadFiles(); connect(); }
|
||||
} catch (e) {
|
||||
@@ -1943,6 +1980,37 @@ export function startNoteEditor(adapter) {
|
||||
event.preventDefault();
|
||||
copyCurrentLink();
|
||||
});
|
||||
favoriteToggle?.addEventListener("click", async () => {
|
||||
if (!accountSession || !adapter.favorite) return;
|
||||
const currentlyFavorite = favoriteToggle.getAttribute("aria-pressed") === "true";
|
||||
favoriteToggle.disabled = true;
|
||||
try {
|
||||
const result = await api("/api/auth/favorites", {
|
||||
method: currentlyFavorite ? "DELETE" : "PUT",
|
||||
body: JSON.stringify(adapter.favorite),
|
||||
});
|
||||
renderFavoriteButton(Boolean(result.favorite));
|
||||
toast.success(
|
||||
result.favorite
|
||||
? t("favorites.added", {}, "Added to favorites.")
|
||||
: t("favorites.removed", {}, "Removed from favorites."),
|
||||
{ title: t("favorites.title", {}, "Favorites") },
|
||||
);
|
||||
} catch (error) {
|
||||
toast.danger(error.message, { title: t("favorites.updateFailed", {}, "Could not update favorites") });
|
||||
await syncFavoriteButton();
|
||||
} finally {
|
||||
favoriteToggle.disabled = false;
|
||||
}
|
||||
});
|
||||
window.addEventListener("rustpad:session-change", event => {
|
||||
accountSession = event.detail?.session || null;
|
||||
syncFavoriteButton();
|
||||
});
|
||||
window.addEventListener("rustpad:session-expired", () => {
|
||||
accountSession = null;
|
||||
renderFavoriteButton(false);
|
||||
});
|
||||
|
||||
let pendingPreviewFormatRange = null;
|
||||
document.querySelectorAll("[data-format]").forEach(button => {
|
||||
@@ -2161,6 +2229,7 @@ export function startNoteEditor(adapter) {
|
||||
password = "";
|
||||
setPagePasswordInput.value = "";
|
||||
await loadNoteInfo();
|
||||
await syncFavoriteButton();
|
||||
resourceUnlocked = false;
|
||||
socket?.stop();
|
||||
loadFiles();
|
||||
@@ -2258,7 +2327,7 @@ export function startNoteEditor(adapter) {
|
||||
document.querySelector("#open-password")?.focus();
|
||||
}
|
||||
});
|
||||
document.querySelector("#password-form").addEventListener("submit", async e => { e.preventDefault(); try { password = document.querySelector("#open-password").value; const result = await adapter.requestAccess(password); setAccessToken(adapter.access.kind, adapter.access.key, result.granted); accessToken = getAccessToken(adapter.access.kind, adapter.access.key); password = ""; document.querySelector("#open-password").value = ""; document.querySelector("#password-error").textContent = ""; await loadNoteInfo(); loadFiles(); connect(); toast.success("Editing access has been unlocked.", { title: "Note unlocked" }); } catch (error) { document.querySelector("#password-error").textContent = error.message; toast.danger(error.message, { title: "Could not unlock note" }); } });
|
||||
document.querySelector("#password-form").addEventListener("submit", async e => { e.preventDefault(); try { password = document.querySelector("#open-password").value; const result = await adapter.requestAccess(password); setAccessToken(adapter.access.kind, adapter.access.key, result.granted); accessToken = getAccessToken(adapter.access.kind, adapter.access.key); password = ""; document.querySelector("#open-password").value = ""; document.querySelector("#password-error").textContent = ""; await loadNoteInfo(); await syncFavoriteButton(); loadFiles(); connect(); toast.success("Editing access has been unlocked.", { title: "Note unlocked" }); } catch (error) { document.querySelector("#password-error").textContent = error.message; toast.danger(error.message, { title: "Could not unlock note" }); } });
|
||||
const historyPanel = document.querySelector("#history-panel"); document.querySelector("#history-button").addEventListener("click", async () => { if (info?.protected && !resourceUnlocked) { if (!passwordDialog.open) passwordDialog.showModal(); document.querySelector("#open-password")?.focus(); return; } historyPanel.classList.add("open"); historyPanel.setAttribute("aria-hidden", "false"); document.body.classList.add("history-open"); const list = document.querySelector("#history-list"); list.innerHTML = '<p class="empty">Loading…</p>'; try { const revisions = await adapter.loadHistory(accessToken); list.innerHTML = revisions.length ? revisions.map((r, i) => { const snippet = escapeHtml(r.content.trim().split("\n").slice(0, 3).join(" · ").slice(0, 150) || "Empty note"); const author = r.author || "Unknown author"; return `<article class="revision"><span class="revision__marker" style="--owner:${colorFor(author)}"></span><div><div class="revision__meta"><strong>${escapeHtml(author)}</strong><time>${formatDate(r.created_at)}</time></div><p class="revision__snippet">${snippet}</p><button data-preview="${r.id}">Preview</button><button data-revision="${r.id}">Restore</button><div class="revision__preview" id="preview-${r.id}" hidden></div></div></article>`; }).join("") : '<p class="empty">No history yet.</p>'; for (const r of revisions) { list.querySelector(`[data-preview="${r.id}"]`)?.addEventListener("click", () => { const el = list.querySelector(`#preview-${r.id}`); el.hidden = !el.hidden; el.textContent = r.content; }); list.querySelector(`[data-revision="${r.id}"]`)?.addEventListener("click", async () => { await adapter.restoreRevision(r.id, accessToken); toast.success("The selected revision is now the current version.", { title: "Version restored" }); }); } } catch (e) { list.innerHTML = `<p class="error">${escapeHtml(e.message)}</p>`; toast.danger(e.message, { title: "Could not load version history" }); } }); document.querySelector("#close-history").addEventListener("click", () => { historyPanel.classList.remove("open"); historyPanel.setAttribute("aria-hidden", "true"); document.body.classList.remove("history-open"); });
|
||||
|
||||
const deleteNoteButton = document.querySelector("#delete-note"); if (deleteNoteButton && adapter.deleteNote) deleteNoteButton.addEventListener("click", async () => { try { await adapter.deleteNote(info, accessToken); } catch (error) { toast.danger(error.message, { title: "Could not delete note" }); } });
|
||||
|
||||
+74
-5
@@ -12,12 +12,12 @@ installGlobalDiagnostics();
|
||||
|
||||
import { api } from "@rustpad/api";
|
||||
import { copyText } from "@rustpad/clipboard";
|
||||
import { getNickname, getAccessToken, getAuthToken, getGuestId, setAccessToken } from "@rustpad/session";
|
||||
import { getNickname, getAccessToken, getGuestId, setAccessToken } from "@rustpad/session";
|
||||
import { bindIdentityDialog, validateCurrentSession } from "@rustpad/auth-ui";
|
||||
import { askConfirm } from "@rustpad/modal";
|
||||
import { isResourceAccessError, safeAppUrl } from "@rustpad/security";
|
||||
import { consumeQueuedToast, toast } from "@rustpad/toast";
|
||||
import { formatDateTime, formatNumber } from "@rustpad/i18n";
|
||||
import { formatDateTime, formatNumber, t } from "@rustpad/i18n";
|
||||
|
||||
consumeQueuedToast();
|
||||
|
||||
@@ -27,6 +27,7 @@ let info;
|
||||
const shareToken = new URLSearchParams(location.search).get("share");
|
||||
let accessToken = shareToken || getAccessToken("workspace", slug);
|
||||
let nickname = getNickname();
|
||||
let accountSession = null;
|
||||
getGuestId();
|
||||
const workspaceWatchClientId = `workspace_watch_${crypto.randomUUID()}`;
|
||||
let workspaceWatchSocket;
|
||||
@@ -177,6 +178,57 @@ function deleteButton(note, inline = false) {
|
||||
: "Read-write access is required to delete this note";
|
||||
return `<button class="${classes}" data-delete-note="${escapeHtml(note.slug)}" data-note-title="${escapeHtml(note.title)}" ${disabled ? "disabled" : ""} title="${escapeHtml(reason)}">Delete</button>`;
|
||||
}
|
||||
function favoriteButton(note, inline = false) {
|
||||
if (!accountSession) return "";
|
||||
const active = Boolean(note.favorite);
|
||||
const label = active
|
||||
? t("favorites.remove", {}, "Remove from favorites")
|
||||
: t("favorites.add", {}, "Add to favorites");
|
||||
return `<button class="note-favorite-button${inline ? " note-favorite-button--inline" : ""}" type="button" data-favorite-note="${escapeHtml(note.slug)}" aria-pressed="${active}" title="${escapeHtml(label)}" aria-label="${escapeHtml(label)}"><span aria-hidden="true">${active ? "★" : "☆"}</span></button>`;
|
||||
}
|
||||
function updateFavoriteButton(button, active) {
|
||||
const favorite = Boolean(active);
|
||||
const label = favorite
|
||||
? t("favorites.remove", {}, "Remove from favorites")
|
||||
: t("favorites.add", {}, "Add to favorites");
|
||||
button.setAttribute("aria-pressed", String(favorite));
|
||||
button.title = label;
|
||||
button.setAttribute("aria-label", label);
|
||||
const icon = button.querySelector("span");
|
||||
if (icon) icon.textContent = favorite ? "★" : "☆";
|
||||
}
|
||||
async function toggleNoteFavorite(button) {
|
||||
if (!accountSession) return;
|
||||
const note = notesCache.find(item => item.slug === button.dataset.favoriteNote);
|
||||
if (!note) return;
|
||||
const current = Boolean(note.favorite);
|
||||
button.disabled = true;
|
||||
try {
|
||||
const result = await api("/api/auth/favorites", {
|
||||
method: current ? "DELETE" : "PUT",
|
||||
body: JSON.stringify({ kind: "note", slug: note.slug, workspace_slug: slug }),
|
||||
});
|
||||
note.favorite = Boolean(result.favorite);
|
||||
document.querySelectorAll(`[data-favorite-note="${CSS.escape(note.slug)}"]`).forEach(target => updateFavoriteButton(target, note.favorite));
|
||||
toast.success(
|
||||
note.favorite
|
||||
? t("favorites.added", {}, "Added to favorites.")
|
||||
: t("favorites.removed", {}, "Removed from favorites."),
|
||||
{ title: t("favorites.title", {}, "Favorites") },
|
||||
);
|
||||
} catch (error) {
|
||||
if (error.status === 401 || error.status === 403) {
|
||||
const session = await validateCurrentSession();
|
||||
if (!session) {
|
||||
accountSession = null;
|
||||
renderNotes();
|
||||
}
|
||||
}
|
||||
toast.danger(error.message, { title: t("favorites.updateFailed", {}, "Could not update favorites") });
|
||||
} finally {
|
||||
if (button.isConnected) button.disabled = false;
|
||||
}
|
||||
}
|
||||
function renderNotes(notes = notesCache) {
|
||||
notesCache = notes;
|
||||
setNotesView(notesView);
|
||||
@@ -185,8 +237,10 @@ function renderNotes(notes = notesCache) {
|
||||
return;
|
||||
}
|
||||
if (notesView === "table") {
|
||||
notesList.innerHTML = `<div class="notes-table-scroll"><table><thead><tr><th>Name</th><th>Created by</th><th>Participants</th><th>Files</th><th>Revisions</th><th>Status</th><th>Updated</th><th class="notes-table-actions">Actions</th></tr></thead><tbody>${notes.map(note => `
|
||||
const favoriteHeader = accountSession ? '<th class="notes-table-favorite"><span class="sr-only">Favorites</span>★</th>' : "";
|
||||
notesList.innerHTML = `<div class="notes-table-scroll"><table><thead><tr>${favoriteHeader}<th>Name</th><th>Created by</th><th>Participants</th><th>Files</th><th>Revisions</th><th>Status</th><th>Updated</th><th class="notes-table-actions">Actions</th></tr></thead><tbody>${notes.map(note => `
|
||||
<tr>
|
||||
${accountSession ? `<td class="notes-table-favorite">${favoriteButton(note, true)}</td>` : ""}
|
||||
<td><a class="note-table-link" href="${escapeHtml(safeAppUrl(note.url))}">${escapeHtml(note.title)}</a></td>
|
||||
<td class="note-author">${escapeHtml(note.created_by || "Unknown")}</td>
|
||||
<td>${Number(note.participant_count) || 0}</td>
|
||||
@@ -199,11 +253,12 @@ function renderNotes(notes = notesCache) {
|
||||
return;
|
||||
}
|
||||
notesList.innerHTML = notes.map(note => `
|
||||
<article class="note-card-wrap">
|
||||
<article class="note-card-wrap${accountSession ? " note-card-wrap--favorite-enabled" : ""}">
|
||||
<a class="note-card" href="${escapeHtml(safeAppUrl(note.url))}">
|
||||
<div class="note-card-title"><h3>${escapeHtml(note.title)}</h3>${note.protected ? '<span class="protect-badge">Protected</span>' : ''}</div>
|
||||
<div class="note-card-meta"><span>Created by: ${escapeHtml(note.created_by || "Unknown")}</span>${noteStats(note)}<span>Updated: ${formatDate(note.updated_at)}</span></div>
|
||||
</a>
|
||||
${accountSession ? `<div class="note-card-favorite">${favoriteButton(note)}</div>` : ""}
|
||||
${deleteButton(note)}
|
||||
</article>`).join("");
|
||||
}
|
||||
@@ -323,6 +378,12 @@ document.querySelector("#note-form").addEventListener("submit", async e => {
|
||||
} catch (err) { error.textContent = err.message; toast.danger(err.message, { title: "Could not create note" }); }
|
||||
});
|
||||
notesList.addEventListener("click", async event => {
|
||||
const favorite = event.target.closest("[data-favorite-note]");
|
||||
if (favorite) {
|
||||
event.preventDefault();
|
||||
await toggleNoteFavorite(favorite);
|
||||
return;
|
||||
}
|
||||
const button = event.target.closest("[data-delete-note]");
|
||||
if (!button) return;
|
||||
const title = button.dataset.noteTitle;
|
||||
@@ -350,6 +411,7 @@ document.querySelector("#copy-workspace-link").addEventListener("click", async (
|
||||
});
|
||||
async function startAuthorizedWorkspace() {
|
||||
const session = await validateCurrentSession();
|
||||
accountSession = session;
|
||||
nickname = session?.nickname || getNickname();
|
||||
|
||||
if (!nickname) {
|
||||
@@ -365,8 +427,9 @@ async function startAuthorizedWorkspace() {
|
||||
|
||||
bindIdentityDialog({
|
||||
dialog: identityDialog,
|
||||
onIdentity: async value => {
|
||||
onIdentity: async (value, session) => {
|
||||
nickname = value;
|
||||
accountSession = session;
|
||||
accessToken = shareToken || getAccessToken("workspace", slug);
|
||||
workspaceContent.hidden = false;
|
||||
await init();
|
||||
@@ -386,6 +449,12 @@ dialog.addEventListener("cancel", event => {
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("rustpad:session-change", event => {
|
||||
if (event.detail?.session) return;
|
||||
if (!accountSession) return;
|
||||
accountSession = null;
|
||||
renderNotes();
|
||||
});
|
||||
window.addEventListener("beforeunload", stopWorkspaceWatch);
|
||||
|
||||
setNotesView(notesView);
|
||||
|
||||
Reference in New Issue
Block a user