diff --git a/frontend/app.js b/frontend/app.js index 49846d8..fe3c95e 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -3269,6 +3269,16 @@ const shareBtn = el("button", { class: "btn-action", title: "Partager ce document" }, [icon("share-2", 14), document.createTextNode("Partager")]); shareBtn.addEventListener("click", () => openShareDialog(data.vault, data.path)); + // Bookmark button + const bookmarkBtn = el("button", { class: "btn-action", title: "Ajouter/Retirer des bookmarks" }, [icon("bookmark-plus", 14), document.createTextNode("Bookmark")]); + bookmarkBtn.addEventListener("click", async () => { + try { + const res = await api("/api/bookmarks/toggle", { method: "POST", body: JSON.stringify({ vault: data.vault, path: data.path, title: data.title }) }); + showToast(res.bookmarked ? "Ajouté aux bookmarks" : "Retiré des bookmarks", "success"); + if (typeof DashboardBookmarkWidget !== "undefined") DashboardBookmarkWidget.load(); + } catch (err) { showToast("Erreur: " + err.message, "error"); } + }); + // Frontmatter — Accent Card let fmSection = null; if (data.frontmatter && Object.keys(data.frontmatter).length > 0) { @@ -3309,7 +3319,7 @@ // Assemble area.innerHTML = ""; area.appendChild(breadcrumb); - area.appendChild(el("div", { class: "file-header" }, [el("div", { class: "file-title" }, [document.createTextNode(data.title)]), tagsDiv, el("div", { class: "file-actions" }, [copyBtn, sourceBtn, downloadBtn, editBtn, openNewWindowBtn, tocBtn, shareBtn])])); + area.appendChild(el("div", { class: "file-header" }, [el("div", { class: "file-title" }, [document.createTextNode(data.title)]), tagsDiv, el("div", { class: "file-actions" }, [copyBtn, sourceBtn, downloadBtn, editBtn, openNewWindowBtn, tocBtn, shareBtn, bookmarkBtn])])); if (fmSection) area.appendChild(fmSection); area.appendChild(mdDiv); area.appendChild(rawDiv); @@ -4540,32 +4550,90 @@ })); } - // ── Share Dialog ── + // ── Share Dialog (professional) ── async function openShareDialog(vault, path) { + // First check if already shared + let existingShare = null; try { - const share = await api(`/api/share/${encodeURIComponent(vault)}`, { method: "POST", body: JSON.stringify({ path }) }); - const url = window.location.origin + share.url; - const div = document.createElement("div"); - div.className = "share-dialog-overlay"; - div.innerHTML = ` -
`; - document.body.appendChild(div); - div.querySelector(".share-copy-btn").addEventListener("click", async () => { - await navigator.clipboard.writeText(url); - showToast("Lien copié !", "success"); - div.remove(); - }); + const shares = await api("/api/shares"); + existingShare = shares.find(s => s.vault === vault && s.path === path); + } catch (e) { /* ignore */ } + + const div = document.createElement("div"); + div.className = "share-dialog-overlay"; + + const renderContent = () => { + if (existingShare) { + const url = window.location.origin + existingShare.url; + const expiresInfo = existingShare.expires_at + ? `Expire le ${new Date(existingShare.expires_at).toLocaleDateString("fr-FR")}
` + : 'Sans expiration
'; + div.innerHTML = ` + `; + div.querySelector(".share-copy-btn").addEventListener("click", async () => { + await navigator.clipboard.writeText(url); + showToast("Lien copié !", "success"); + div.remove(); + }); + div.querySelector(".share-revoke-btn").addEventListener("click", async () => { + try { + await api(`/api/share/${existingShare.id}`, { method: "DELETE" }); + showToast("Partage révoqué", "success"); + existingShare = null; + renderContent(); + } catch (err) { showToast("Erreur: " + err.message, "error"); } + }); + } else { + div.innerHTML = ` + `; + div.querySelector(".share-create-btn").addEventListener("click", async () => { + try { + const expiry = document.getElementById("share-expiry")?.value; + const share = await api(`/api/share/${encodeURIComponent(vault)}`, { + method: "POST", + body: JSON.stringify({ path, expires_in_hours: expiry ? parseInt(expiry) : null }), + }); + existingShare = share; + renderContent(); + showToast("Lien créé !", "success"); + } catch (err) { showToast("Erreur: " + err.message, "error"); } + }); + } div.querySelector(".share-close-btn").addEventListener("click", () => div.remove()); div.addEventListener("click", (e) => { if (e.target === div) div.remove(); }); - } catch (err) { showToast("Erreur: " + err.message, "error"); } + }; + + renderContent(); + document.body.appendChild(div); } function renderConfigFilters() { @@ -6471,6 +6539,8 @@ } else if (type === 'file') { this._addItem('edit', 'Renommer', () => this._renameItem(), isReadonly); this._addItem('trash-2', 'Supprimer', () => this._deleteFile(), isReadonly); + this._addSeparator(); + this._addItem('bookmark-plus', 'Ajouter aux bookmarks', () => this._toggleBookmark(), false); } this._menu.classList.add('active'); @@ -6582,6 +6652,19 @@ showToast('Erreur lors de la copie', 'error'); } document.body.removeChild(textarea); + }, + + async _toggleBookmark() { + try { + const data = await api("/api/bookmarks/toggle", { + method: "POST", + body: JSON.stringify({ vault: this._targetVault, path: this._targetPath, title: this._targetPath.split("/").pop() }), + }); + showToast(data.bookmarked ? "Ajouté aux bookmarks" : "Retiré des bookmarks", "success"); + if (typeof DashboardBookmarkWidget !== "undefined" && DashboardBookmarkWidget.load) { + DashboardBookmarkWidget.load(); + } + } catch (err) { showToast("Erreur: " + err.message, "error"); } } }; diff --git a/frontend/style.css b/frontend/style.css index 2fbf0ce..df087b1 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -5744,6 +5744,24 @@ body.popup-mode .content-area { cursor: pointer; font-size: 0.85rem; } +.share-create-btn { + padding: 6px 16px; + background: var(--accent); + color: #fff; + border: none; + border-radius: 6px; + cursor: pointer; + font-size: 0.85rem; +} +.share-revoke-btn { + padding: 6px 16px; + background: none; + color: var(--text-error); + border: 1px solid var(--text-error); + border-radius: 6px; + cursor: pointer; + font-size: 0.85rem; +} /* ── Enhanced Search Bar Toggles ── */ .search-actions .tog {