possibly first working draft
This commit is contained in:
@ -1,100 +1,160 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"donniemarko/internal/service"
|
||||
"donniemarko/internal/render"
|
||||
"donniemarko/internal/note"
|
||||
"donniemarko/internal/render"
|
||||
"donniemarko/internal/service"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
notesService *service.NotesService
|
||||
templates *render.TemplateManager
|
||||
notesService *service.NotesService
|
||||
templates *render.TemplateManager
|
||||
mux *http.ServeMux
|
||||
}
|
||||
|
||||
func NewHandler(ns *service.NotesService, tm *render.TemplateManager) *Handler {
|
||||
return &Handler{
|
||||
notesService: ns,
|
||||
templates: tm,
|
||||
}
|
||||
return &Handler{
|
||||
notesService: ns,
|
||||
templates: tm,
|
||||
mux: http.NewServeMux(),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Path
|
||||
|
||||
// Handle root and note list
|
||||
if path == "/" {
|
||||
h.handleRoot(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle individual notes
|
||||
if strings.HasPrefix(path, "/notes/") {
|
||||
h.handleNotes(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle 404 for other paths
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
|
||||
// ViewState is built per-request, not shared
|
||||
type ViewState struct {
|
||||
Notes []*note.Note
|
||||
CurrentNote *note.Note
|
||||
SortBy string
|
||||
SearchTerm string
|
||||
ActiveHash string
|
||||
Notes []*note.Note
|
||||
RenderedNote template.HTML
|
||||
SortBy string
|
||||
SearchTerm string
|
||||
LastActive string
|
||||
}
|
||||
|
||||
func (h *Handler) handleRoot(w http.ResponseWriter, r *http.Request) {
|
||||
// Build view state from query params
|
||||
state, err := h.buildViewState(r)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Render with state
|
||||
h.templates.Render(w, "index", state)
|
||||
// Build view state from query params
|
||||
state, err := h.buildViewState(r)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Render with state
|
||||
h.templates.Render(w, "index", state)
|
||||
}
|
||||
|
||||
func (h *Handler) buildViewState(r *http.Request) (*ViewState, error) {
|
||||
query := r.URL.Query()
|
||||
|
||||
// Extract params
|
||||
sortBy := query.Get("sort")
|
||||
if sortBy == "" {
|
||||
sortBy = "recent"
|
||||
}
|
||||
|
||||
searchTerm := query.Get("search")
|
||||
|
||||
// Get notes from service
|
||||
var notes []*note.Note
|
||||
|
||||
if searchTerm != "" {
|
||||
notes = h.notesService.Search(searchTerm)
|
||||
} else {
|
||||
notes = h.notesService.GetNotes()
|
||||
}
|
||||
|
||||
// Apply sorting
|
||||
switch sortBy {
|
||||
case "recent":
|
||||
service.SortByDate(notes)
|
||||
case "alpha":
|
||||
service.SortByTitle(notes)
|
||||
}
|
||||
|
||||
return &ViewState{
|
||||
Notes: notes,
|
||||
SortBy: sortBy,
|
||||
SearchTerm: searchTerm,
|
||||
}, nil
|
||||
query := r.URL.Query()
|
||||
|
||||
// Extract params
|
||||
sortBy := query.Get("sort")
|
||||
if sortBy == "" {
|
||||
sortBy = "recent"
|
||||
}
|
||||
|
||||
searchTerm := query.Get("search")
|
||||
|
||||
// Get notes from service
|
||||
var notes []*note.Note
|
||||
var err error
|
||||
|
||||
if searchTerm != "" {
|
||||
opts := service.QueryOptions{
|
||||
SearchTerm: searchTerm,
|
||||
SortBy: sortBy,
|
||||
}
|
||||
notes, err = h.notesService.QueryNotes(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
notes = h.notesService.GetNotes()
|
||||
|
||||
// Apply sorting
|
||||
switch sortBy {
|
||||
case "recent":
|
||||
service.SortByDate(notes)
|
||||
case "oldest":
|
||||
service.SortByDateAsc(notes)
|
||||
case "alpha":
|
||||
service.SortByTitle(notes)
|
||||
case "ralpha":
|
||||
service.SortByTitleAsc(notes)
|
||||
default:
|
||||
service.SortByDate(notes)
|
||||
}
|
||||
}
|
||||
|
||||
return &ViewState{
|
||||
Notes: notes,
|
||||
SortBy: sortBy,
|
||||
SearchTerm: searchTerm,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *Handler) SetupRoutes() {
|
||||
// Set the handler as the main handler for http.DefaultServeMux
|
||||
http.Handle("/", h)
|
||||
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("internal/web/static"))))
|
||||
}
|
||||
|
||||
func extractHash(path string) string {
|
||||
// Extract hash from /notes/{hash}
|
||||
parts := strings.Split(strings.Trim(path, "/"), "/")
|
||||
if len(parts) < 2 || parts[0] != "notes" {
|
||||
return ""
|
||||
}
|
||||
return parts[1]
|
||||
}
|
||||
|
||||
func (h *Handler) handleNotes(w http.ResponseWriter, r *http.Request) {
|
||||
// Build base state
|
||||
state, err := h.buildViewState(r)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract hash from URL
|
||||
hash := extractHash(r.URL.Path)
|
||||
|
||||
// Get specific note
|
||||
note, err := h.notesService.GetNoteByHash(hash)
|
||||
if err != nil {
|
||||
http.Error(w, "Note not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// Add to state
|
||||
state.CurrentNote = note
|
||||
state.ActiveHash = hash
|
||||
|
||||
h.templates.Render(w, "note", state)
|
||||
// Build base state
|
||||
state, err := h.buildViewState(r)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract hash from URL
|
||||
hash := extractHash(r.URL.Path)
|
||||
|
||||
// Get specific note
|
||||
note, err := h.notesService.GetNoteByHash(hash)
|
||||
if err != nil {
|
||||
http.Error(w, "Note not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// Convert markdown to HTML
|
||||
htmlContent, err := render.RenderMarkdown([]byte(note.Content))
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to render markdown", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Add to state
|
||||
state.RenderedNote = htmlContent
|
||||
state.LastActive = hash
|
||||
|
||||
h.templates.Render(w, "index", state)
|
||||
}
|
||||
|
||||
@ -259,4 +259,13 @@
|
||||
margin: 1em 0;
|
||||
border-bottom: dotted 2px var(--heading1);
|
||||
}
|
||||
|
||||
.note-title {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.note-title a {
|
||||
padding-left: 1em;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
{{ define "base" }}
|
||||
<!doctype html>
|
||||
<html class="no-js" lang="en">
|
||||
<head>
|
||||
@ -9,4 +10,5 @@
|
||||
<body>
|
||||
{{ template "content" . }}
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
{{ end }}
|
||||
@ -3,6 +3,6 @@
|
||||
{{ template "noteList" . }}
|
||||
{{/* Markdown notes rendering area */}}
|
||||
<main>
|
||||
{{ .Note }}
|
||||
{{ .RenderedNote }}
|
||||
</main>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
@ -24,15 +24,15 @@
|
||||
{{ define "renderSearch" }}
|
||||
{{ if ne .SearchTerm "" }}<h2>Matching results for query '{{ .SearchTerm }}'</h2>{{ end }}
|
||||
<ul class="search-results">
|
||||
{{ range .Nodes }}
|
||||
{{ if .IsEnd }}
|
||||
<li {{ if eq .Hash $.LastActive }}class="active-note"{{ end }}>
|
||||
<input type="checkbox"/>
|
||||
<a href="/notes/{{ .Hash }}" data-hash="{{ .Hash }}">{{ .Title }}</a>
|
||||
<span class="last-modified">{{ .LastModified }}</span>
|
||||
</li>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ range .Notes }}
|
||||
<li {{ if eq .ID $.LastActive }}class="active-note"{{ end }}>
|
||||
<div class="note-title">
|
||||
<input type="checkbox"/>
|
||||
<a href="/notes/{{ .ID }}" data-hash="{{ .ID }}">{{ if ge (len .Title) 30 }}{{printf "%.30s" .Title }}[...]{{ else }} {{ .Title }}{{ end }}</a>
|
||||
</div>
|
||||
<span class="last-modified">{{ .GetUpdateDateRep }}</span>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
{{ end }}
|
||||
|
||||
@ -52,4 +52,4 @@
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</ul>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
Reference in New Issue
Block a user