diff --git a/backend/main.py b/backend/main.py
index fb29520..ae94726 100644
--- a/backend/main.py
+++ b/backend/main.py
@@ -2790,6 +2790,50 @@ async def api_share_revoke(share_id: str, current_user=Depends(require_auth)):
return {"status": "revoked"}
+@app.get("/s/{token}/pdf")
+async def public_share_pdf(token: str):
+ """Download shared document as PDF — no authentication required."""
+ share = get_share_by_token(token)
+ if not share:
+ raise HTTPException(404, "Share not found or expired")
+ vault_data = get_vault_data(share["vault"])
+ if not vault_data:
+ raise HTTPException(404, "Vault not found")
+ vault_root = Path(vault_data["path"])
+ file_path = _resolve_safe_path(vault_root, share["path"])
+ if not file_path.exists():
+ raise HTTPException(404, "File not found")
+ try:
+ raw = file_path.read_text(encoding="utf-8", errors="replace")
+ except Exception:
+ raise HTTPException(500, "Cannot read file")
+ record_access(token)
+ raw = redact_file_content(raw, str(file_path))
+ post = parse_markdown_file(raw)
+ html = _render_markdown(post.content, share["vault"], file_path)
+ title = post.metadata.get("title", file_path.stem)
+
+ # Build a full HTML page for PDF rendering
+ full_html = f"""
{title}
+{title}
{html}"""
+
+ filename = f"{title}.pdf"
+ return HTMLResponse(
+ content=full_html,
+ media_type="text/html",
+ headers={
+ "Content-Disposition": f'attachment; filename="{filename}"',
+ "X-PDF-Download": "true",
+ },
+ )
+
+
@app.get("/s/{token}")
async def public_share_view(token: str):
"""Public share view — no authentication required."""
@@ -2868,7 +2912,7 @@ body{{font-family:system-ui,-apple-system,sans-serif;background:var(--bg);color:
function toggleTheme(){{var t=document.documentElement;var isDark=t.dataset.theme==="dark";t.dataset.theme=isDark?"light":"dark";document.getElementById("theme-icon-dark").style.display=isDark?"none":"";document.getElementById("theme-icon-light").style.display=isDark?"":"none";localStorage.setItem("obsigate-share-theme",t.dataset.theme)}}
(function(){{var s=localStorage.getItem("obsigate-share-theme");if(!s)s="dark";document.documentElement.dataset.theme=s;var isDark=s==="dark";document.getElementById("theme-icon-dark").style.display=isDark?"":"none";document.getElementById("theme-icon-light").style.display=isDark?"none":""}})();
function exportMD(){{var t=document.getElementById("content").innerText;var b=new Blob([t],{{type:"text/markdown"}});var a=document.createElement("a");a.href=URL.createObjectURL(b);a.download="{title}.md";a.click()}}
-function exportPDF(){{document.title="{title}";window.print()}}
+function exportPDF(){{var w=window.open("","_blank","width=800,height=600");w.document.write("{title}{title}
"+document.getElementById("content").innerHTML+"");w.document.close();w.focus();setTimeout(function(){{w.print();w.close()}},500)}}