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) {
|
func servePDF(w http.ResponseWriter, r *http.Request) {
|
||||||
hash := chi.URLParam(r, "hash")
|
hash := chi.URLParam(r, "hash")
|
||||||
|
download := r.URL.Query().Get("download") == "1"
|
||||||
|
|
||||||
// Try PDF first, then TXT
|
// Try PDF first, then TXT
|
||||||
for _, ext := range []string{".pdf", ".txt"} {
|
for _, ext := range []string{".pdf", ".txt"} {
|
||||||
|
|
@ -285,6 +286,13 @@ func servePDF(w http.ResponseWriter, r *http.Request) {
|
||||||
} else {
|
} else {
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
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)
|
http.ServeFile(w, r, path)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -293,6 +301,13 @@ func servePDF(w http.ResponseWriter, r *http.Request) {
|
||||||
// Try without extension
|
// Try without extension
|
||||||
path := filepath.Join(storeDir, hash)
|
path := filepath.Join(storeDir, hash)
|
||||||
if _, err := os.Stat(path); err == nil {
|
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)
|
http.ServeFile(w, r, path)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -300,6 +315,12 @@ func servePDF(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Error(w, "File not found", http.StatusNotFound)
|
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
|
// API handlers
|
||||||
|
|
||||||
func apiSearchHandler(w http.ResponseWriter, r *http.Request) {
|
func apiSearchHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
Share
|
Share
|
||||||
</button>
|
</button>
|
||||||
{{if .Document.PDFPath}}
|
{{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">
|
<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>
|
<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>
|
</svg>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue