improvements
This commit is contained in:
+40
-4
@@ -126,6 +126,20 @@ function shareLinkId(tokenHash) { return String(tokenHash || "").slice(0, 12); }
|
||||
function renderResourcesPagination(meta) {
|
||||
resourcesPagination.innerHTML = meta.total ? `<button type="button" data-page="${meta.page - 1}" ${meta.page <= 1 ? "disabled" : ""}>Previous</button><span>Page ${meta.page} of ${meta.total_pages} · ${meta.total} items</span><button type="button" data-page="${meta.page + 1}" ${meta.page >= meta.total_pages ? "disabled" : ""}>Next</button>` : "";
|
||||
}
|
||||
function closeResourcePasswordMenus(except = null) {
|
||||
document.querySelectorAll(".resource-password-menu[open]").forEach(menu => {
|
||||
if (menu !== except) menu.removeAttribute("open");
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("click", event => {
|
||||
const menu = event.target.closest?.(".resource-password-menu");
|
||||
closeResourcePasswordMenus(menu);
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", event => {
|
||||
if (event.key === "Escape") closeResourcePasswordMenus();
|
||||
});
|
||||
async function loadResources() {
|
||||
resourcesError.textContent = ""; resourcesList.innerHTML = "<p>Loading…</p>";
|
||||
try {
|
||||
@@ -140,7 +154,10 @@ async function loadResources() {
|
||||
const sharedLabel = !item.owned ? `<span class="resource-shared-badge">Shared by ${escapeHtml(item.shared_by || "another user")}</span>` : "";
|
||||
const permissionLabel = item.permission === "rw" ? "Read and write" : "Read only";
|
||||
row.classList.toggle("resource-row--shared", !Boolean(item.owned));
|
||||
row.innerHTML = `<div class="resource-main"><div class="resource-copy"><div class="resource-title-line"><a href="${escapeHtml(safeAppUrl(item.url))}">${escapeHtml(item.title)}</a>${sharedLabel}</div><small>${item.kind === "workspace" ? "Workspace" : "Note"}${item.private ? " · private" : ""}${!item.owned ? ` · ${permissionLabel}` : item.protected ? " · password protected" : ""}</small></div><div class="resource-actions">${item.owned ? `<button class="action-button action-button--secondary compact-button" type="button" data-privacy>${item.private ? "Make public" : "Make private"}</button><button class="action-button action-button--primary compact-button" type="button" data-share>Share</button><button class="action-button action-button--secondary compact-button" type="button" data-password>Change password</button><button class="action-button action-button--danger compact-button" type="button" data-delete>Delete</button>` : ""}</div></div><div class="resource-inline" data-inline hidden></div>`;
|
||||
const passwordActions = item.protected
|
||||
? `<button class="resource-password-menu__item" type="button" data-password>Change password</button><button class="resource-password-menu__item resource-password-menu__item--danger" type="button" data-remove-password>Remove password</button>`
|
||||
: `<button class="resource-password-menu__item" type="button" data-password>Set password</button>`;
|
||||
row.innerHTML = `<div class="resource-main"><div class="resource-copy"><div class="resource-title-line"><a href="${escapeHtml(safeAppUrl(item.url))}">${escapeHtml(item.title)}</a>${sharedLabel}</div><small>${item.kind === "workspace" ? "Workspace" : "Note"}${item.private ? " · private" : ""}${!item.owned ? ` · ${permissionLabel}` : item.protected ? " · password protected" : ""}</small></div><div class="resource-actions">${item.owned ? `<button class="action-button action-button--secondary compact-button" type="button" data-privacy>${item.private ? "Make public" : "Make private"}</button><button class="action-button action-button--primary compact-button" type="button" data-share>Share</button><details class="resource-password-menu"><summary class="action-button action-button--secondary compact-button">Password…<span class="resource-password-menu__chevron" aria-hidden="true">▾</span></summary><div class="resource-password-menu__panel">${passwordActions}</div></details><button class="action-button action-button--danger compact-button" type="button" data-delete>Delete</button>` : ""}</div></div><div class="resource-inline" data-inline hidden></div>`;
|
||||
|
||||
const inline = row.querySelector("[data-inline]");
|
||||
const closeInline = () => { inline.hidden = true; inline.innerHTML = ""; };
|
||||
@@ -267,16 +284,17 @@ async function loadResources() {
|
||||
try { await refresh(); } catch (err) { setDialogMessage(err.message, "error"); }
|
||||
});
|
||||
|
||||
row.querySelector("[data-password]")?.addEventListener("click", () => {
|
||||
row.querySelector("[data-password]")?.addEventListener("click", event => {
|
||||
event.currentTarget.closest(".resource-password-menu")?.removeAttribute("open");
|
||||
inline.hidden = false;
|
||||
inline.innerHTML = `<form class="resource-password-form" autocomplete="off"><label>New password<input name="resource-password" type="password" minlength="8" maxlength="128" autocomplete="new-password" data-bwignore="true" placeholder="Minimum 8 characters"></label><p class="resource-inline-help">Leave empty to remove password protection.</p><p class="form-message resource-inline-message" data-inline-message role="status"></p><div class="resource-inline-actions"><button class="primary-button" type="submit">Save</button><button class="secondary-button" type="button" data-cancel>Cancel</button></div></form>`;
|
||||
inline.innerHTML = `<form class="resource-password-form" autocomplete="off"><label>${item.protected ? "New password" : "Password"}<input name="resource-password" type="password" minlength="8" maxlength="128" autocomplete="new-password" data-bwignore="true" placeholder="Minimum 8 characters" required></label><p class="form-message resource-inline-message" data-inline-message role="status"></p><div class="resource-inline-actions"><button class="primary-button" type="submit">Save</button><button class="secondary-button" type="button" data-cancel>Cancel</button></div></form>`;
|
||||
const form = inline.querySelector("form");
|
||||
const input = form.querySelector("input");
|
||||
form.querySelector("[data-cancel]").addEventListener("click", closeInline);
|
||||
form.addEventListener("submit", async event => {
|
||||
event.preventDefault();
|
||||
const password = input.value;
|
||||
if (password && password.length < 8) { setInlineMessage("Password must contain at least 8 characters.", "error"); return; }
|
||||
if (password.length < 8) { setInlineMessage("Password must contain at least 8 characters.", "error"); return; }
|
||||
const submit = form.querySelector('[type="submit"]');
|
||||
submit.disabled = true;
|
||||
setInlineMessage("");
|
||||
@@ -291,6 +309,24 @@ async function loadResources() {
|
||||
input.focus();
|
||||
});
|
||||
|
||||
row.querySelector("[data-remove-password]")?.addEventListener("click", event => {
|
||||
event.currentTarget.closest(".resource-password-menu")?.removeAttribute("open");
|
||||
inline.hidden = false;
|
||||
inline.innerHTML = `<div class="resource-delete-confirm"><p>Remove password protection from “${escapeHtml(item.title)}”?</p><p class="resource-inline-help">Anyone with the public link will be able to open it without a password.</p><p class="form-message resource-inline-message" data-inline-message role="status"></p><div class="resource-inline-actions"><button class="danger-button" type="button" data-confirm-remove-password>Remove password</button><button class="secondary-button" type="button" data-cancel>Cancel</button></div></div>`;
|
||||
inline.querySelector("[data-cancel]").addEventListener("click", closeInline);
|
||||
inline.querySelector("[data-confirm-remove-password]").addEventListener("click", async event => {
|
||||
event.currentTarget.disabled = true;
|
||||
setInlineMessage("");
|
||||
try {
|
||||
await api("/api/auth/resources", { method: "PUT", headers: authHeaders(), body: JSON.stringify({ kind: item.kind, slug: item.slug, password: "" }) });
|
||||
await loadResources();
|
||||
} catch (e) {
|
||||
setInlineMessage(e.message, "error");
|
||||
event.currentTarget.disabled = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
row.querySelector("[data-delete]")?.addEventListener("click", () => {
|
||||
inline.hidden = false;
|
||||
inline.innerHTML = `<div class="resource-delete-confirm"><p>Delete “${escapeHtml(item.title)}” permanently?</p><p class="form-message resource-inline-message" data-inline-message role="status"></p><div class="resource-inline-actions"><button class="danger-button" type="button" data-confirm-delete>Delete</button><button class="secondary-button" type="button" data-cancel>Cancel</button></div></div>`;
|
||||
|
||||
Reference in New Issue
Block a user