Add download link with pretty filename from document title
- servePDF now supports ?download=1 query param - Looks up document title and uses it as the Content-Disposition filename - Download button on document page triggers actual download (not tab open) - Added sanitizeFilename helper for safe Content-Disposition values
This commit is contained in:
parent
1b49dac87f
commit
f59c12e25c
21
main.go
21
main.go
|
|
@ -275,6 +275,7 @@ func searchHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
func servePDF(w http.ResponseWriter, r *http.Request) {
|
||||
hash := chi.URLParam(r, "hash")
|
||||
download := r.URL.Query().Get("download") == "1"
|
||||
|
||||
// Try PDF first, then TXT
|
||||
for _, ext := range []string{".pdf", ".txt"} {
|
||||
|
|
@ -285,6 +286,13 @@ func servePDF(w http.ResponseWriter, r *http.Request) {
|
|||
} else {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
}
|
||||
if download {
|
||||
filename := hash + ext
|
||||
if doc, err := GetDocument(hash); err == nil && doc.Title != "" {
|
||||
filename = sanitizeFilename(doc.Title) + ext
|
||||
}
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename))
|
||||
}
|
||||
http.ServeFile(w, r, path)
|
||||
return
|
||||
}
|
||||
|
|
@ -293,6 +301,13 @@ func servePDF(w http.ResponseWriter, r *http.Request) {
|
|||
// Try without extension
|
||||
path := filepath.Join(storeDir, hash)
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
if download {
|
||||
filename := hash
|
||||
if doc, err := GetDocument(hash); err == nil && doc.Title != "" {
|
||||
filename = sanitizeFilename(doc.Title)
|
||||
}
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename))
|
||||
}
|
||||
http.ServeFile(w, r, path)
|
||||
return
|
||||
}
|
||||
|
|
@ -300,6 +315,12 @@ func servePDF(w http.ResponseWriter, r *http.Request) {
|
|||
http.Error(w, "File not found", http.StatusNotFound)
|
||||
}
|
||||
|
||||
// sanitizeFilename removes characters unsafe for use in Content-Disposition filenames.
|
||||
func sanitizeFilename(name string) string {
|
||||
replacer := strings.NewReplacer(`"`, "'", "/", "-", "\\", "-", "\n", " ", "\r", "")
|
||||
return replacer.Replace(name)
|
||||
}
|
||||
|
||||
// API handlers
|
||||
|
||||
func apiSearchHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
Share
|
||||
</button>
|
||||
{{if .Document.PDFPath}}
|
||||
<a href="/pdf/{{.Document.ID}}" target="_blank" class="inline-flex items-center px-3 py-2 bg-brand-600 hover:bg-brand-700 text-white text-sm font-medium rounded-lg transition-all">
|
||||
<a href="/pdf/{{.Document.ID}}?download=1" class="inline-flex items-center px-3 py-2 bg-brand-600 hover:bg-brand-700 text-white text-sm font-medium rounded-lg transition-all">
|
||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
|
|
|
|||
Loading…
Reference in New Issue