summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKen D'Ambrosio <ken@jots.org>2026-06-18 21:58:04 +0000
committerKen D'Ambrosio <ken@jots.org>2026-06-18 21:58:04 +0000
commitc5ec42b2f42e285a95610f1b9b9efba4beef3d7e (patch)
tree549fdff3243113484a840bf0bc631c65eda4fddc
parent462e0081cc9062beb29530e84d15edf2003802db (diff)
Rename global search to "Album Search" and group results by album
Photo search was returning every individual matching photo, which was overkill for what's meant to be a quick way to find an album. /search now groups matches by album (cover thumbnail + match count) instead of listing each photo, and is relabeled "Album Search" throughout the UI. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-rw-r--r--app.rb23
-rw-r--r--public/css/style.css5
-rw-r--r--views/layout.erb2
-rw-r--r--views/search.erb33
4 files changed, 30 insertions, 33 deletions
diff --git a/app.rb b/app.rb
index 7d6489a..c5f533b 100644
--- a/app.rb
+++ b/app.rb
@@ -556,15 +556,26 @@ end
get '/search' do
q = params[:q].to_s.strip
- @title = q.empty? ? 'Search' : "Search: #{q}"
+ @title = q.empty? ? 'Album Search' : "Album Search: #{q}"
@search_query = q
unless q.empty?
- ast = search_parse(q)
- index = search_index
- all = index.select { |d| (admin? || d[:visible]) && eval_search(ast, d[:text]) }
- @total = all.length
- @results = all.first(300)
+ ast = search_parse(q)
+ index = search_index
+ matched = index.select { |d| (admin? || d[:visible]) && eval_search(ast, d[:text]) }
+
+ by_dir = matched.each_with_object({}) { |d, h| (h[d[:dir_rel]] ||= []) << d }
+ @results = by_dir.map do |dir_rel, docs|
+ dir = dir_rel.empty? ? MEDIA_ROOT : File.join(MEDIA_ROOT, dir_rel)
+ data = load_album(dir)
+ {
+ dir_rel: dir_rel,
+ title: data['title'] || (dir_rel.empty? ? '(root)' : File.basename(dir_rel)),
+ cover: album_cover(dir, data),
+ count: docs.length,
+ }
+ end
+ @total = @results.length
end
erb :search
diff --git a/public/css/style.css b/public/css/style.css
index 09de0b3..03d869f 100644
--- a/public/css/style.css
+++ b/public/css/style.css
@@ -389,11 +389,6 @@ legend { padding: 0 8px; color: var(--text-dim); font-size: .85rem; }
.search-input:focus { border-color: var(--accent); outline: none; }
.search-hint { font-size: .8rem; color: var(--text-dim); margin: 0 0 18px; }
.search-hint code { background: var(--bg3); padding: 1px 5px; border-radius: 3px; font-size: .85em; }
-.search-card .card-meta { padding: 6px 8px 8px; }
-.search-album-path { font-size: .72rem; color: var(--text-dim); margin-top: 3px;
- overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.search-album-path a { color: var(--text-dim); }
-.search-album-path a:hover { color: var(--accent); }
/* ── Responsive ────────────────────────────────────────────────────────── */
@media (max-width: 600px) {
diff --git a/views/layout.erb b/views/layout.erb
index 6a85482..17a5983 100644
--- a/views/layout.erb
+++ b/views/layout.erb
@@ -22,7 +22,7 @@
<header class="site-header">
<a href="/browse/" class="site-logo">Albumen</a>
<form action="/search" method="get" class="header-search">
- <input type="search" name="q" placeholder="Search…" autocomplete="off"
+ <input type="search" name="q" placeholder="Album search…" autocomplete="off"
value="<%= defined?(@search_query) ? ERB::Util.html_escape(@search_query.to_s) : '' %>"
class="header-search-input">
</form>
diff --git a/views/search.erb b/views/search.erb
index 1010284..00c9553 100644
--- a/views/search.erb
+++ b/views/search.erb
@@ -1,5 +1,5 @@
<div class="album-header">
- <h1>Search</h1>
+ <h1>Album Search</h1>
<form action="/search" method="get" class="search-form">
<input type="search" name="q" value="<%= ERB::Util.html_escape(@search_query.to_s) %>"
placeholder="e.g. ken 2004 OR carol AND NOT vacation"
@@ -16,34 +16,25 @@
<% if @search_query && !@search_query.empty? %>
<% if @results %>
<p class="update-hint">
- <%= @total %> result<%= @total == 1 ? '' : 's' %>
- <% if @total > @results.length %>(showing first <%= @results.length %>)<% end %>
+ <%= @total %> album<%= @total == 1 ? '' : 's' %> matched
</p>
<% if @results.empty? %>
- <p class="empty-album">No photos matched "<%= ERB::Util.html_escape(@search_query) %>".</p>
+ <p class="empty-album">No albums matched "<%= ERB::Util.html_escape(@search_query) %>".</p>
<% else %>
<div class="grid">
<% @results.each do |r| %>
<% album_url = r[:dir_rel].empty? ? '/browse/' : "/browse/#{ERB::Util.html_escape(r[:dir_rel])}" %>
- <a href="<%= album_url %>?photo=<%= ERB::Util.url_encode(r[:filename]) %>"
- class="card search-card" style="text-decoration:none">
+ <% cover_rel = r[:dir_rel].empty? ? r[:cover] : "#{r[:dir_rel]}/#{r[:cover]}" %>
+ <a href="<%= album_url %>" class="card album-card">
<div class="thumb-wrap">
- <img src="/thumb/<%= ERB::Util.html_escape(r[:rel]) %>" loading="lazy"
- alt="<%= ERB::Util.html_escape(r[:filename]) %>">
- </div>
- <div class="card-meta">
- <div class="filename"><%= ERB::Util.html_escape(r[:filename]) %></div>
- <% if r[:taken_at] %>
- <div class="card-caption"><%= ERB::Util.html_escape(r[:taken_at][0..9]) %></div>
- <% end %>
- <% unless r[:people].empty? %>
- <div class="card-caption"><%= ERB::Util.html_escape(r[:people].join(', ')) %></div>
+ <% if r[:cover] %>
+ <img src="/thumb/<%= ERB::Util.html_escape(cover_rel) %>" alt="<%= ERB::Util.html_escape(r[:title]) %>" loading="lazy">
+ <% else %>
+ <div class="thumb-placeholder">📁</div>
<% end %>
- <div class="search-album-path">
- <a href="<%= album_url %>" onclick="event.stopPropagation()">
- <%= ERB::Util.html_escape(r[:dir_rel].empty? ? '(root)' : r[:dir_rel]) %>
- </a>
- </div>
+ </div>
+ <div class="album-label">
+ <%= ERB::Util.html_escape(r[:title]) %><span class="album-count"><%= r[:count] %></span>
</div>
</a>
<% end %>