Add notebook delete, fix button styling, rename Share button

- Add delete notebook handler with ownership check and CASCADE delete
- Rename "Create Share Link" to "Share"
- Fix action button heights: use inline-flex + align-items for
  consistent sizing across <a> and <button> elements

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 09:43:57 -07:00
parent aeb12d9f50
commit 651eabe995
4 changed files with 25 additions and 3 deletions

View File

@@ -248,6 +248,23 @@ func (ws *WebServer) handleCreateShare(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, fmt.Sprintf("/notebooks/%d", id), http.StatusFound) http.Redirect(w, r, fmt.Sprintf("/notebooks/%d", id), http.StatusFound)
} }
func (ws *WebServer) handleDeleteNotebook(w http.ResponseWriter, r *http.Request) {
userID := r.Context().Value(userIDKey).(int64)
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
// Verify ownership before deleting.
var ownerID int64
err := ws.db.QueryRow("SELECT user_id FROM notebooks WHERE id = ?", id).Scan(&ownerID)
if err != nil || ownerID != userID {
http.Error(w, "Not found", http.StatusNotFound)
return
}
// CASCADE deletes pages, strokes, and share_links.
_, _ = ws.db.Exec("DELETE FROM notebooks WHERE id = ?", id)
http.Redirect(w, r, "/notebooks", http.StatusFound)
}
func (ws *WebServer) handleRevokeShare(w http.ResponseWriter, r *http.Request) { func (ws *WebServer) handleRevokeShare(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
token := r.FormValue("token") token := r.FormValue("token")

View File

@@ -112,6 +112,7 @@ func Start(cfg Config) (*http.Server, error) {
r.Get("/notebooks/{id}/pages/{num}/svg", ws.handlePageSVG) r.Get("/notebooks/{id}/pages/{num}/svg", ws.handlePageSVG)
r.Get("/notebooks/{id}/pages/{num}/jpg", ws.handlePageJPG) r.Get("/notebooks/{id}/pages/{num}/jpg", ws.handlePageJPG)
r.Get("/notebooks/{id}/pdf", ws.handleNotebookPDF) r.Get("/notebooks/{id}/pdf", ws.handleNotebookPDF)
r.Post("/notebooks/{id}/delete", ws.handleDeleteNotebook)
r.Post("/notebooks/{id}/share", ws.handleCreateShare) r.Post("/notebooks/{id}/share", ws.handleCreateShare)
r.Post("/notebooks/{id}/share/revoke", ws.handleRevokeShare) r.Post("/notebooks/{id}/share/revoke", ws.handleRevokeShare)
r.Get("/logout", ws.handleLogout) r.Get("/logout", ws.handleLogout)

View File

@@ -18,9 +18,9 @@
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 1rem; } .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 1rem; }
.page-thumb { border: 1px solid #ccc; background: #fff; aspect-ratio: 0.773; display: flex; align-items: center; justify-content: center; } .page-thumb { border: 1px solid #ccc; background: #fff; aspect-ratio: 0.773; display: flex; align-items: center; justify-content: center; }
.page-thumb img { width: 100%; height: 100%; object-fit: contain; } .page-thumb img { width: 100%; height: 100%; object-fit: contain; }
.btn { display: inline-block; padding: 0.5rem 1rem; border: 1px solid #111; border-radius: 4px; text-decoration: none; color: #111; background: #fff; cursor: pointer; } .btn { display: inline-flex; align-items: center; padding: 0.5rem 1rem; border: 1px solid #111; border-radius: 4px; text-decoration: none; color: #111; background: #fff; cursor: pointer; font: inherit; font-size: 1rem; line-height: 1.5; box-sizing: border-box; }
.btn:hover { background: #f0f0f0; } .btn:hover { background: #f0f0f0; }
.actions { display: flex; gap: 0.5rem; margin: 1rem 0; } .actions { display: flex; align-items: center; gap: 0.5rem; margin: 1rem 0; }
input[type="text"], input[type="password"] { padding: 0.5rem; border: 1px solid #ccc; border-radius: 4px; width: 100%; max-width: 300px; } input[type="text"], input[type="password"] { padding: 0.5rem; border: 1px solid #ccc; border-radius: 4px; width: 100%; max-width: 300px; }
label { display: block; margin-bottom: 0.25rem; font-weight: bold; } label { display: block; margin-bottom: 0.25rem; font-weight: bold; }
.form-group { margin-bottom: 1rem; } .form-group { margin-bottom: 1rem; }

View File

@@ -6,7 +6,11 @@
<a href="{{.PDFLink}}" class="btn">Download PDF</a> <a href="{{.PDFLink}}" class="btn">Download PDF</a>
{{if not .Shared}} {{if not .Shared}}
<form method="POST" action="/notebooks/{{.ID}}/share" style="display:inline;"> <form method="POST" action="/notebooks/{{.ID}}/share" style="display:inline;">
<button type="submit" class="btn">Create Share Link</button> <button type="submit" class="btn">Share</button>
</form>
<form method="POST" action="/notebooks/{{.ID}}/delete" style="display:inline;"
onsubmit="return confirm('Delete this notebook from the server?');">
<button type="submit" class="btn" style="color: #c62828; border-color: #c62828;">Delete</button>
</form> </form>
{{end}} {{end}}
</div> </div>