aboutsummaryrefslogtreecommitdiffstats
path: root/internal/web/templates
diff options
context:
space:
mode:
authorgrm <grm@eyesin.space>2026-09-16 00:00:57 +0300
committergrm <grm@eyesin.space>2026-09-16 00:00:57 +0300
commit839343a3108734a15e331b542a20ed9c77ef582b (patch)
treea6aabc8719c16398bf1ccd874a7efe865b364a9e /internal/web/templates
parent628dcb814ece6335667e2b719049728aa161d8c9 (diff)
downloadblogspace-839343a3108734a15e331b542a20ed9c77ef582b.tar.gz
blogspace-839343a3108734a15e331b542a20ed9c77ef582b.tar.bz2
blogspace-839343a3108734a15e331b542a20ed9c77ef582b.zip
Add a search box module and a search page
Readers had no way to find a post. The new "search" module goes in the header (a bar under the nav) or a side column (a box with a heading) and is a plain GET form to /search, so it works without JavaScript. The results page lists the published posts of every page whose title or Markdown body matches the query — case-insensitive, each word literal, spaces meaning "anything in between", in order — as title, date and a short snippet with the match marked, 20 per page. modules.kind is a CHECK constraint, so a migration widens it; "search" becomes a reserved page slug so the literal route keeps winning over /{page}. moduleHasSettings now takes the module: a header search box has no heading, hence nothing to edit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
Diffstat (limited to 'internal/web/templates')
-rw-r--r--internal/web/templates/blog/search.html23
-rw-r--r--internal/web/templates/dashboard/layout.html2
-rw-r--r--internal/web/templates/dashboard/module_form.html6
-rw-r--r--internal/web/templates/layouts/blog.html6
4 files changed, 34 insertions, 3 deletions
diff --git a/internal/web/templates/blog/search.html b/internal/web/templates/blog/search.html
new file mode 100644
index 0000000..b146f5e
--- /dev/null
+++ b/internal/web/templates/blog/search.html
@@ -0,0 +1,23 @@
+{{define "title"}}{{if .Data.q}}{{tf "Results for “%s”" .Data.q}}{{else}}{{t "Search"}}{{end}} · {{.Blog.Title}}{{end}}
+{{define "content"}}
+<h1 class="page-title">{{if .Data.q}}{{tf "Results for “%s”" .Data.q}}{{else}}{{t "Search"}}{{end}}</h1>
+<div class="search-page">{{template "searchform" .}}</div>
+{{if not .Data.q}}<p class="muted">{{t "Type a word or two to find posts."}}</p>
+{{else if not .Data.hits}}<p class="muted">{{tf "Nothing matched “%s”." .Data.q}}</p>
+{{else}}
+<p class="muted small">{{if eq .Data.total 1}}{{t "One post matches."}}{{else}}{{tf "%d posts match." .Data.total}}{{end}}</p>
+<ul class="search-results">
+{{range .Data.hits}}<li>
+ <a href="/{{.PageSlug}}/{{.Slug}}">{{.Title}}</a>{{if $.Data.theme.ShowDates}} <span class="post-date">{{postdate $.Data.theme.DateFormat .CreatedAt}}</span>{{end}}
+ <p class="snippet">{{.Snippet}}</p>
+</li>
+{{end}}</ul>
+{{if gt .Data.lastPage 1}}
+<div class="pager">
+ {{if gt .Data.pageNum 1}}<a href="/search?q={{.Data.q}}&amp;p={{sub .Data.pageNum 1}}">&larr; {{t "Newer"}}</a>{{end}}
+ <span>{{tf "Page %d of %d" .Data.pageNum .Data.lastPage}}</span>
+ {{if lt .Data.pageNum .Data.lastPage}}<a href="/search?q={{.Data.q}}&amp;p={{add .Data.pageNum 1}}">{{t "Older"}} &rarr;</a>{{end}}
+</div>
+{{end}}
+{{end}}
+{{end}}
diff --git a/internal/web/templates/dashboard/layout.html b/internal/web/templates/dashboard/layout.html
index 48b6778..5c9332a 100644
--- a/internal/web/templates/dashboard/layout.html
+++ b/internal/web/templates/dashboard/layout.html
@@ -102,7 +102,7 @@
</td>
<td>{{t (index $r.Data.names .Kind)}}{{with modsummary .}} <span class="muted">— {{.}}</span>{{end}}
{{if and (eq .Kind "logo") (not $r.Data.theme.Logo)}}<span class="tag off">{{t "no logo chosen"}}</span> <span class="muted small">{{t "pick one on the"}} <a href="/b/{{$r.Blog.Subdomain}}/design#logo">{{t "Design"}}</a> {{t "tab."}}</span>{{end}}</td>
- <td class="nowrap actions">{{if modsettings .Kind}}<a href="{{$u}}/modules/{{.ID}}/edit">{{t "edit"}}</a> {{end}}<a class="danger" href="{{$u}}/modules/{{.ID}}/delete">{{t "remove"}}</a></td>
+ <td class="nowrap actions">{{if modsettings .}}<a href="{{$u}}/modules/{{.ID}}/edit">{{t "edit"}}</a> {{end}}<a class="danger" href="{{$u}}/modules/{{.ID}}/delete">{{t "remove"}}</a></td>
</tr>{{end}}
</table>{{else}}<p class="muted">{{if ne $area "header"}}{{t "Nothing here yet — this area is not shown until you add a module."}}{{else}}{{t "Nothing here yet."}}{{end}}</p>{{end}}
<form method="post" action="{{$u}}/areas/{{$area}}/add" class="addmod">
diff --git a/internal/web/templates/dashboard/module_form.html b/internal/web/templates/dashboard/module_form.html
index 8b28f2e..a0fe95b 100644
--- a/internal/web/templates/dashboard/module_form.html
+++ b/internal/web/templates/dashboard/module_form.html
@@ -5,8 +5,8 @@
<h1>{{.Data.name}} <span class="muted small">{{tf "in the %s" (lower .Data.area)}}</span></h1>
<form method="post">
<input type="hidden" name="_csrf" value="{{.CSRF}}">
- {{if or (eq $m.Kind "recent") (eq $m.Kind "archive") (eq $m.Kind "sitemap") (eq $m.Kind "tags") (eq $m.Kind "tagcloud") (and (eq $m.Kind "html") (or (eq $m.Area "left") (eq $m.Area "right") (eq $m.Area "footer")))}}
- <label>{{t "Heading"}} <span class="muted">({{t "optional"}})</span><br><input name="title" value="{{$m.Title}}" maxlength="120" placeholder="{{if eq $m.Kind "recent"}}{{t "Recent posts"}}{{else if eq $m.Kind "archive"}}{{t "Archive"}}{{else if eq $m.Kind "sitemap"}}{{t "Pages"}}{{else if eq $m.Kind "tags"}}{{t "Tags"}}{{else if eq $m.Kind "tagcloud"}}{{t "Tag cloud"}}{{end}}"></label>
+ {{if or (eq $m.Kind "recent") (eq $m.Kind "archive") (eq $m.Kind "sitemap") (eq $m.Kind "tags") (eq $m.Kind "tagcloud") (eq $m.Kind "search") (and (eq $m.Kind "html") (or (eq $m.Area "left") (eq $m.Area "right") (eq $m.Area "footer")))}}
+ <label>{{t "Heading"}} <span class="muted">({{t "optional"}})</span><br><input name="title" value="{{$m.Title}}" maxlength="120" placeholder="{{if eq $m.Kind "recent"}}{{t "Recent posts"}}{{else if eq $m.Kind "archive"}}{{t "Archive"}}{{else if eq $m.Kind "sitemap"}}{{t "Pages"}}{{else if eq $m.Kind "tags"}}{{t "Tags"}}{{else if eq $m.Kind "tagcloud"}}{{t "Tag cloud"}}{{else if eq $m.Kind "search"}}{{t "Search"}}{{end}}"></label>
{{end}}
{{if eq $m.Kind "recent"}}
<div class="row"><label>{{t "How many posts"}}<br><select name="count">{{range $n := .Data.counts}}<option value="{{$n}}"{{if eq $n $m.Count}} selected{{end}}>{{$n}}</option>{{end}}</select></label></div>
@@ -20,6 +20,8 @@
<p class="muted small">{{t "Your tags with the number of posts in each, most used first. Only published posts count."}}</p>
{{else if eq $m.Kind "tagcloud"}}
<p class="muted small">{{t "Every tag in alphabetical order, bigger the more posts use it."}}</p>
+ {{else if eq $m.Kind "search"}}
+ <p class="muted small">{{t "A box that searches the published posts of every page by title and text; the results open on your blog’s search page. Case does not matter, and several words find posts that contain them in that order, with anything in between."}}</p>
{{else if eq $m.Kind "html"}}
<label for="mod-body">HTML</label>
<textarea id="mod-body" name="body" rows="14" class="mono" spellcheck="false" placeholder="&lt;p&gt;{{t "Hello!"}}&lt;/p&gt;">{{$m.Body}}</textarea>
diff --git a/internal/web/templates/layouts/blog.html b/internal/web/templates/layouts/blog.html
index cf00272..3e854f6 100644
--- a/internal/web/templates/layouts/blog.html
+++ b/internal/web/templates/layouts/blog.html
@@ -76,6 +76,8 @@
{{- else if eq $m.Kind "tagcloud"}}<div class="mod mod-tagcloud">{{template "modtitle" $m}}
<p class="tagcloud">{{range $r.Data.tagcloud}}<a class="tc-{{.Size}}" href="/tag/{{.Slug}}" title="{{.Count}}">{{.Name}}</a> {{else}}<span class="muted">{{t "No tags yet."}}</span>{{end}}</p>
</div>
+{{- else if eq $m.Kind "search"}}{{if eq $m.Area "header"}}<div class="site-search"><div class="wrap search-inner">{{template "searchform" $r}}</div></div>
+{{else}}<div class="mod mod-search">{{template "modtitle" $m}}{{template "searchform" $r}}</div>{{end}}
{{- else if eq $m.Kind "html"}}{{if not $m.Body}}{{else if or (eq $m.Area "above") (eq $m.Area "below")}}<div class="custom-html custom-html-{{$m.Area}}">{{html $m.Body}}</div>
{{else}}<div class="mod mod-html">{{template "modtitle" $m}}{{html $m.Body}}</div>{{end}}
{{- else if eq $m.Kind "rss"}}<p class="mod mod-rss small"><a href="/feed.xml">{{t "RSS feed"}}</a></p>
@@ -85,6 +87,10 @@
</div>
{{- end}}{{end}}
+{{/* The search box: a plain GET form, so it works without JavaScript. .Data.q
+ only exists on the search page, hence the with. */}}
+{{define "searchform"}}<form method="get" action="/search" class="search-form"><input type="search" name="q"{{with .Data.q}} value="{{.}}"{{end}} placeholder="{{t "Search…"}}" size="18"> <button type="submit">{{t "Search"}}</button></form>{{end}}
+
{{define "modtitle"}}{{if .Title}}<h3 class="mod-title">{{.Title}}</h3>{{end}}{{end}}
{{define "menulinks"}}{{range .Data.menu}}<a href="{{.Href}}"{{if and .PageID $.Data.page (eq $.Data.page.ID (deref64 .PageID))}} class="active"{{end}}>{{.Text}}</a>