diff --git a/backend/main.py b/backend/main.py index cceed80..8b090aa 100644 --- a/backend/main.py +++ b/backend/main.py @@ -994,6 +994,26 @@ async def api_toggle_bookmark(req: BookmarkToggleRequest, current_user=Depends(r raise HTTPException(status_code=403, detail="Access denied to vault") is_now_bookmarked = toggle_bookmark(username, req.vault, req.path, req.title) + + # Update the file's YAML frontmatter: favoris: true/false + vault_data = get_vault_data(req.vault) + if vault_data: + file_path = _resolve_safe_path(Path(vault_data["path"]), req.path) + if file_path.exists() and file_path.suffix == ".md": + try: + raw = file_path.read_text(encoding="utf-8", errors="replace") + post = frontmatter.loads(raw) + if is_now_bookmarked: + post.metadata["favoris"] = True + elif "favoris" in post.metadata: + del post.metadata["favoris"] + new_raw = frontmatter.dumps(post) + _backup_file(file_path, req.vault, req.path) + file_path.write_text(new_raw, encoding="utf-8") + await update_single_file(req.vault, str(file_path)) + except Exception as e: + logger.warning(f"Failed to update favoris metadata on {req.vault}/{req.path}: {e}") + return {"bookmarked": is_now_bookmarked} @@ -2721,13 +2741,36 @@ async def api_share_create( body: dict = Body(...), current_user=Depends(require_auth), ): - """Create a public share link for a document.""" + """Create a public share link for a document. + + Also sets ``publish: true`` in the file's YAML frontmatter so the + frontend can visually indicate the file is publicly shared. + """ if not check_vault_access(vault_name, current_user): raise HTTPException(403, f"Accès refusé à la vault '{vault_name}'") path = body.get("path", "") expires = body.get("expires_in_hours") share = create_share(vault_name, path, current_user["username"], expires) share["url"] = f"/s/{share['token']}" + + # Set publish: true in the file's frontmatter + vault_data = get_vault_data(vault_name) + if vault_data: + file_path = _resolve_safe_path(Path(vault_data["path"]), path) + if file_path.exists() and file_path.suffix == ".md": + try: + raw = file_path.read_text(encoding="utf-8", errors="replace") + post = frontmatter.loads(raw) + if not post.metadata.get("publish"): + post.metadata["publish"] = True + new_raw = frontmatter.dumps(post) + _backup_file(file_path, vault_name, path) + file_path.write_text(new_raw, encoding="utf-8") + await update_single_file(vault_name, str(file_path)) + logger.info(f"Set publish:true on {vault_name}/{path}") + except Exception as e: + logger.warning(f"Failed to set publish metadata on {vault_name}/{path}: {e}") + return share diff --git a/frontend/app.js b/frontend/app.js index 4fb0b9e..1fdc10e 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -3269,8 +3269,18 @@ RightSidebarManager.toggle(); }); - // Share button - const shareBtn = el("button", { class: "btn-action", title: "Partager ce document" }, [icon("share-2", 14), document.createTextNode("Partager")]); + // Share button — check if already shared + const shareBtn = el("button", { class: "btn-action btn-share", title: "Partager ce document" }, [icon("share-2", 14), document.createTextNode("Partager")]); + // Check if already shared and color the button + (async () => { + try { + const shares = await api("/api/shares"); + if (shares.some(s => s.vault === data.vault && s.path === data.path)) { + shareBtn.classList.add("shared"); + shareBtn.title = "Document partagé — cliquer pour gérer"; + } + } catch (e) { /* ignore */ } + })(); shareBtn.addEventListener("click", () => openShareDialog(data.vault, data.path)); // Bookmark button @@ -3399,6 +3409,59 @@ } }; + // ── Dashboard Shared Widget ── + const DashboardSharedWidget = { + async load() { + const grid = document.getElementById("dashboard-shared-grid"); + const empty = document.getElementById("dashboard-shared-empty"); + if (!grid) return; + try { + const shares = await api("/api/shares"); + if (!shares.length) { if (empty) empty.style.display = ""; grid.innerHTML = ""; return; } + if (empty) empty.style.display = "none"; + grid.innerHTML = shares.map(s => ` +
+ `).join(""); + lucide.createIcons(); + grid.querySelectorAll(".shared-copy-btn").forEach(b => b.addEventListener("click", async (e) => { + e.stopPropagation(); + const url = b.dataset.url; + try { await navigator.clipboard.writeText(url); } catch { const ta = document.createElement("textarea"); ta.value=url; ta.style.position="fixed"; ta.style.left="-9999px"; document.body.appendChild(ta); ta.select(); document.execCommand("copy"); document.body.removeChild(ta); } + showToast("Lien copié !", "success"); + })); + grid.querySelectorAll(".shared-open-btn").forEach(b => b.addEventListener("click", (e) => { + e.stopPropagation(); + const card = b.closest(".shared-card"); + if (card) TabManager.openPreview(card.dataset.vault, card.dataset.path); + })); + grid.querySelectorAll(".shared-revoke-btn").forEach(b => b.addEventListener("click", async (e) => { + e.stopPropagation(); + await api(`/api/share/${b.dataset.id}`, { method: "DELETE" }); + showToast("Partage révoqué", "success"); + this.load(); + })); + grid.querySelectorAll(".shared-card").forEach(card => card.addEventListener("click", () => { + TabManager.openPreview(card.dataset.vault, card.dataset.path); + })); + } catch (err) { if (empty) empty.style.display = ""; } + } + }; + // ── Dashboard Conflicts Widget ── const DashboardConflictsWidget = { async load() { @@ -4586,7 +4649,15 @@ `; div.querySelector(".share-copy-btn").addEventListener("click", async () => { - await navigator.clipboard.writeText(url); + try { + await navigator.clipboard.writeText(url); + } catch (e) { + // Fallback for non-HTTPS contexts + const ta = document.createElement("textarea"); + ta.value = url; ta.style.position = "fixed"; ta.style.left = "-9999px"; + document.body.appendChild(ta); ta.select(); document.execCommand("copy"); + document.body.removeChild(ta); + } showToast("Lien copié !", "success"); div.remove(); }); @@ -5813,6 +5884,9 @@ + @@ -5856,6 +5930,16 @@Ouvrez un fichier pour le voir apparaître ici
+ + + `; // Re-initialize widgets and dashboard tabs @@ -5889,6 +5973,9 @@ if (typeof DashboardBookmarkWidget !== "undefined") { DashboardBookmarkWidget.load(selectedContextVault); } + if (typeof DashboardSharedWidget !== "undefined") { + DashboardSharedWidget.load(); + } } function showLoading() { diff --git a/frontend/index.html b/frontend/index.html index 984f4a3..d6d8760 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -382,6 +382,9 @@ + @@ -425,6 +428,16 @@Ouvrez un fichier pour le voir apparaître ici
+ + + diff --git a/frontend/style.css b/frontend/style.css index df087b1..1589bc2 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -5830,11 +5830,62 @@ body.popup-mode .content-area { .search-replace-btn.destructive { border-color: var(--text-error); color: var(--text-error); } /* ── Active search result highlight ── */ -.search-result-item.search-result-active { - background: var(--accent-bg, rgba(99, 102, 241, 0.08)); - border-left: 3px solid var(--accent); - padding-left: calc(var(--result-padding, 12px) - 3px); +.btn-share.shared { + color: #059669; + border-color: #059669; + background: rgba(5, 150, 105, 0.08); } +.btn-share.shared:hover { + background: rgba(5, 150, 105, 0.15); +} + +/* ── Shared Cards ── */ +.shared-card { + padding: 10px 14px; + background: var(--bg-card, var(--bg-secondary)); + border-radius: 6px; + border: 1px solid var(--border); + cursor: pointer; + transition: background 0.15s; + display: flex; + flex-direction: column; + gap: 6px; +} +.shared-card:hover { background: var(--bg-hover); } +.shared-card-header { + display: flex; + align-items: center; + gap: 8px; +} +.shared-card-title { font-weight: 500; font-size: 0.85rem; } +.shared-card-vault { + font-size: 0.65rem; + color: var(--accent); + background: var(--accent-bg, rgba(99,102,241,0.1)); + padding: 1px 6px; + border-radius: 3px; + margin-left: auto; +} +.shared-card-meta { + font-size: 0.7rem; + color: var(--text-muted); + display: flex; + gap: 12px; +} +.shared-card-actions { + display: flex; + gap: 6px; +} +.shared-card-actions button { + font-size: 0.7rem; + padding: 2px 8px; + border: 1px solid var(--border); + border-radius: 4px; + background: var(--bg-primary); + color: var(--text-primary); + cursor: pointer; +} +.shared-card-actions button:hover { background: var(--bg-hover); } /* ── Dashboard Tab Navigation ── */ .dashboard-tabs {