v0.2.96
This commit is contained in:
1 parent
b15a6c54ce
commit
d36a4b2451
22 files changed
+926
-114
No files matched your search
+97
-4
@@ -58,6 +58,15 @@ const notesViewKey = `rustpad:workspace:${slug}:notes-view`;
|
||||
let notesView = localStorage.getItem(notesViewKey) === "table" ? "table" : "grid";
|
||||
let notesCache = [];
|
||||
let archivedNotesCache = [];
|
||||
let noteTitleOpenTimer = 0;
|
||||
|
||||
document.addEventListener("pointerdown", event => {
|
||||
if (!noteTitleOpenTimer) return;
|
||||
const target = event.target instanceof Element ? event.target : null;
|
||||
if (target?.closest("[data-note-rename]")) return;
|
||||
clearTimeout(noteTitleOpenTimer);
|
||||
noteTitleOpenTimer = 0;
|
||||
}, true);
|
||||
|
||||
function updateWorkspacePasswordControl() {
|
||||
workspacePasswordForm.hidden = Boolean(info?.protected || !info?.can_set_password);
|
||||
@@ -266,8 +275,6 @@ async function toggleNoteFavorite(button) {
|
||||
{ title: t("favorites.title", {}, "Favorites") },
|
||||
);
|
||||
} catch (error) {
|
||||
// The API client already rechecks the session after a 401.
|
||||
// Only a 403 needs an additional check for an unrelated access restriction.
|
||||
if (error.status === 403) {
|
||||
try {
|
||||
const session = await validateCurrentSession();
|
||||
@@ -296,7 +303,7 @@ function renderNotes(notes = notesCache) {
|
||||
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><a class="note-table-link" href="${escapeHtml(safeAppUrl(note.url))}"><span class="note-list-inline-title${note.can_rename ? " note-list-inline-title--renamable" : ""}" ${note.can_rename ? `data-note-rename="${escapeHtml(note.slug)}" title="${escapeHtml(t("workspace.renameHint", {}, "Double-click to rename"))}"` : ""}>${escapeHtml(note.title)}</span></a></td>
|
||||
<td class="note-author">${escapeHtml(note.created_by || "Unknown")}</td>
|
||||
<td>${Number(note.participant_count) || 0}</td>
|
||||
<td>${Number(note.file_count) || 0} <span class="note-status">(${formatBytes(note.file_size_bytes)})</span></td>
|
||||
@@ -310,13 +317,70 @@ function renderNotes(notes = notesCache) {
|
||||
notesList.innerHTML = notes.map(note => `
|
||||
<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-title"><h3><span class="note-list-inline-title${note.can_rename ? " note-list-inline-title--renamable" : ""}" ${note.can_rename ? `data-note-rename="${escapeHtml(note.slug)}" title="${escapeHtml(t("workspace.renameHint", {}, "Double-click to rename"))}"` : ""}>${escapeHtml(note.title)}</span></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("");
|
||||
}
|
||||
function beginWorkspaceNoteRename(target, note) {
|
||||
if (!target || !note?.can_rename || target.querySelector("input")) return;
|
||||
const previous = note.title;
|
||||
const input = document.createElement("input");
|
||||
input.className = "note-title-inline-input";
|
||||
input.type = "text";
|
||||
input.maxLength = 80;
|
||||
input.value = previous;
|
||||
input.setAttribute("aria-label", t("workspace.renameLabel", {}, "Note name"));
|
||||
target.replaceChildren(input);
|
||||
target.classList.add("note-list-inline-title--editing");
|
||||
let finished = false;
|
||||
|
||||
const restore = title => {
|
||||
if (!target.isConnected) return;
|
||||
target.classList.remove("note-list-inline-title--editing");
|
||||
target.textContent = title;
|
||||
};
|
||||
const finish = async save => {
|
||||
if (finished) return;
|
||||
finished = true;
|
||||
const next = input.value.trim();
|
||||
if (!save) { restore(previous); return; }
|
||||
if (!next) {
|
||||
restore(previous);
|
||||
toast.danger(t("workspace.renameEmpty", {}, "The note name cannot be empty."), { title: t("workspace.renameFailed", {}, "Could not rename note") });
|
||||
return;
|
||||
}
|
||||
if (next === previous) { restore(previous); return; }
|
||||
input.disabled = true;
|
||||
try {
|
||||
const result = await api(`/api/workspaces/${encodeURIComponent(slug)}/notes/${encodeURIComponent(note.slug)}/rename`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ name: next, access_token: accessToken || null }),
|
||||
});
|
||||
note.title = result.title || next;
|
||||
note.updated_at = result.updated_at || note.updated_at;
|
||||
restore(note.title);
|
||||
toast.success(t("workspace.renamed", { title: note.title }, `Renamed to “${note.title}”.`), { title: t("workspace.renamedTitle", {}, "Note renamed") });
|
||||
await openWorkspace();
|
||||
} catch (error) {
|
||||
restore(previous);
|
||||
toast.danger(error.message, { title: t("workspace.renameFailed", {}, "Could not rename note") });
|
||||
}
|
||||
};
|
||||
|
||||
input.addEventListener("click", event => { event.preventDefault(); event.stopPropagation(); });
|
||||
input.addEventListener("dblclick", event => { event.preventDefault(); event.stopPropagation(); });
|
||||
input.addEventListener("keydown", event => {
|
||||
if (event.key === "Enter") { event.preventDefault(); event.stopPropagation(); void finish(true); }
|
||||
else if (event.key === "Escape") { event.preventDefault(); event.stopPropagation(); void finish(false); }
|
||||
});
|
||||
input.addEventListener("blur", () => { void finish(true); }, { once: true });
|
||||
input.focus();
|
||||
input.select();
|
||||
}
|
||||
|
||||
async function showSystemNotFound() {
|
||||
try {
|
||||
const response = await fetch(`${location.pathname.replace(/\/$/, "")}/__not_found__`, {
|
||||
@@ -519,6 +583,35 @@ 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 activeInput = event.target.closest(".note-title-inline-input");
|
||||
if (activeInput) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
const editingTitle = notesList.querySelector(".note-list-inline-title--editing");
|
||||
if (editingTitle) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
const renameTarget = event.target.closest("[data-note-rename]");
|
||||
if (renameTarget) {
|
||||
const note = notesCache.find(item => item.slug === renameTarget.dataset.noteRename);
|
||||
if (note?.can_rename) {
|
||||
event.preventDefault();
|
||||
clearTimeout(noteTitleOpenTimer);
|
||||
if (event.detail >= 2) {
|
||||
noteTitleOpenTimer = 0;
|
||||
beginWorkspaceNoteRename(renameTarget, note);
|
||||
} else {
|
||||
noteTitleOpenTimer = window.setTimeout(() => {
|
||||
noteTitleOpenTimer = 0;
|
||||
location.assign(safeAppUrl(note.url));
|
||||
}, 400);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
const favorite = event.target.closest("[data-favorite-note]");
|
||||
if (favorite) {
|
||||
event.preventDefault();
|
||||
|
||||
Reference in new issue
Block a user