135 lines
3.6 KiB
Go
135 lines
3.6 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"html/template"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var quickAddTmpl = template.Must(template.ParseFS(assets, "assets/quickadd.html"))
|
|
|
|
type quickAddData struct {
|
|
To, URL, Title string
|
|
}
|
|
|
|
// handleQuickAdd serves the bookmarklet popup at /quickadd.
|
|
func (h *handler) handleQuickAdd(w http.ResponseWriter, r *http.Request) {
|
|
if !h.checkAuth(w, r) {
|
|
return
|
|
}
|
|
if r.Method != http.MethodGet {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
q := r.URL.Query()
|
|
to := strings.TrimSpace(q.Get("to"))
|
|
if to == "" {
|
|
http.Error(w, "missing to", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if !strings.HasPrefix(to, "/") {
|
|
to = "/" + to
|
|
}
|
|
data := quickAddData{
|
|
To: to,
|
|
URL: q.Get("url"),
|
|
Title: q.Get("title"),
|
|
}
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
if err := quickAddTmpl.Execute(w, data); err != nil {
|
|
log.Printf("quickadd template: %v", err)
|
|
}
|
|
}
|
|
|
|
// handleAppend appends one link entry to index.md at fsPath. Creates the
|
|
// folder and index.md if missing. Body is form-encoded with `url` (required),
|
|
// `title` and `comment` (both optional).
|
|
func (h *handler) handleAppend(w http.ResponseWriter, r *http.Request, urlPath, fsPath string) {
|
|
if err := r.ParseForm(); err != nil {
|
|
http.Error(w, "bad request", http.StatusBadRequest)
|
|
return
|
|
}
|
|
rawURL := strings.TrimSpace(r.FormValue("url"))
|
|
title := strings.TrimSpace(r.FormValue("title"))
|
|
comment := strings.TrimSpace(r.FormValue("comment"))
|
|
if rawURL == "" {
|
|
http.Error(w, "missing url", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if title == "" {
|
|
title = rawURL
|
|
}
|
|
|
|
entry := formatAppendEntry(title, rawURL, comment, time.Now())
|
|
|
|
_, statErr := os.Stat(fsPath)
|
|
newlyCreated := os.IsNotExist(statErr)
|
|
if err := os.MkdirAll(fsPath, 0755); err != nil {
|
|
http.Error(w, "mkdir failed: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
indexPath := filepath.Join(fsPath, "index.md")
|
|
existing, err := os.ReadFile(indexPath)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
http.Error(w, "read failed: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if len(existing) > 0 {
|
|
buf.Write(existing)
|
|
if existing[len(existing)-1] != '\n' {
|
|
buf.WriteByte('\n')
|
|
}
|
|
}
|
|
buf.WriteString(entry)
|
|
|
|
if err := os.WriteFile(indexPath, buf.Bytes(), 0644); err != nil {
|
|
http.Error(w, "write failed: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if newlyCreated {
|
|
if rel, err := filepath.Rel(h.root, fsPath); err == nil {
|
|
folderIndexAdd(filepath.ToSlash(rel))
|
|
}
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
// formatAppendEntry builds a CommonMark multi-line list item: a link with a
|
|
// continuation-indented timestamp line and an optional comment line. Lines
|
|
// after the first share an indent so goldmark folds them into one paragraph.
|
|
func formatAppendEntry(title, rawURL, comment string, ts time.Time) string {
|
|
var b strings.Builder
|
|
b.WriteString("- [")
|
|
b.WriteString(escapeLinkLabel(title))
|
|
b.WriteString("](")
|
|
b.WriteString(rawURL)
|
|
b.WriteString(")</br>")
|
|
b.WriteString(ts.Format("2006-01-02 15:04"))
|
|
b.WriteString("</br>")
|
|
if comment != "" {
|
|
b.WriteString(" ")
|
|
b.WriteString(comment)
|
|
b.WriteByte('\n')
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
// escapeLinkLabel backslash-escapes the brackets that would otherwise close
|
|
// the markdown link label early. The label text is rendered verbatim, so we
|
|
// keep all other characters as-is.
|
|
func escapeLinkLabel(s string) string {
|
|
s = strings.ReplaceAll(s, `\`, `\\`)
|
|
s = strings.ReplaceAll(s, `[`, `\[`)
|
|
s = strings.ReplaceAll(s, `]`, `\]`)
|
|
return s
|
|
}
|