source: Klonkt/src/views/pages/search.ejs@ 66cfbaa

main
Last change on this file since 66cfbaa was 1720482, checked in by roboburr <roboburr@…>, 3 months ago

feat(search): tracks searchable + prefix-matching for posts

  • Posts: FTS5 query is now prefix-AND ("astr"* finds "astra") instead of a single strict phrase — nicer search-as-you-type; title/author/content are searched.
  • Tracks: new search on audio_tracks (title/artist/album, LIKE), as playable rows (audio-player picks up data-pcms-track) + "in post →" link to the post/album/playlist the track appears in (in-memory match over the site's published posts).
  • View updated to two-section layout (Tracks / Posts).

Co-Authored-By: Claude <noreply@…>

  • Property mode set to 100644
File size: 6.3 KB
Line 
1<% const _base = (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : ''; %>
2<% const _tracks = (typeof tracks !== 'undefined' && tracks) ? tracks : []; %>
3<div class="container search-page">
4 <h1>Zoeken</h1>
5
6 <form method="get" action="<%= _base %>/search" class="search-page-form">
7 <input
8 type="search"
9 name="q"
10 value="<%= query %>"
11 placeholder="Zoek posts en nummers…"
12 autocomplete="off"
13 autofocus
14 aria-label="Zoekopdracht">
15 <button type="submit" class="btn btn-primary">Zoek</button>
16 </form>
17
18 <% if (queryError) { %>
19 <p class="search-error">Kon die zoekopdracht niet uitvoeren. Probeer een eenvoudiger term.</p>
20 <% } %>
21
22 <% if (query && !queryError) { %>
23 <p class="search-meta">
24 <%= total %> resulta<%= total === 1 ? 'at' : 'ten' %> voor &ldquo;<%= query %>&rdquo;
25 </p>
26 <% } %>
27
28 <% if (_tracks.length) { %>
29 <h2 class="search-section-title">Nummers</h2>
30 <div class="search-tracks">
31 <% _tracks.forEach(function(t) {
32 const tj = JSON.stringify({ id: t.id, url: t.url, title: t.title, artist: t.artist || '', cover: t.cover || '' })
33 .replace(/&/g, '&amp;').replace(/'/g, '&#39;').replace(/</g, '&lt;'); %>
34 <div class="post-audio-track search-track" id="track-<%= t.id %>"
35 data-pcms-track-id="<%= t.id %>" data-pcms-track-url="<%= t.url %>"
36 data-pcms-track='<%- tj %>'>
37 <button type="button" class="pat-play" aria-label="Speel <%= t.title %>">
38 <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M8 4l12 8-12 8z"/></svg>
39 </button>
40 <% if (t.cover) { %>
41 <span class="search-track-cover" style="background-image:url('<%= t.cover %>')" aria-hidden="true"></span>
42 <% } %>
43 <div class="pat-info">
44 <div class="pat-title"><%= t.title %></div>
45 <% if (t.artist || t.album) { %>
46 <div class="pat-artist">
47 <%= t.artist %><% if (t.artist && t.album) { %> &middot; <% } %><%= t.album %>
48 </div>
49 <% } %>
50 </div>
51 <% if (t.postUrl) { %>
52 <a class="search-track-link"
53 href="<%= t.postUrl %>"
54 hx-get="<%= t.postUrl %>?partial=1"
55 hx-target="#pcms-main"
56 hx-swap="innerHTML"
57 hx-push-url="<%= t.postUrl %>"
58 aria-label="Open in post">in post →</a>
59 <% } %>
60 </div>
61 <% }); %>
62 </div>
63 <% } %>
64
65 <% if (results && results.length) { %>
66 <% if (_tracks.length) { %><h2 class="search-section-title">Posts</h2><% } %>
67 <ol class="search-results">
68 <% results.forEach(function(r) { %>
69 <li class="search-result">
70 <h3 class="search-result-title">
71 <a href="<%= _base %>/<%= r.slug %>"
72 hx-get="<%= _base %>/<%= r.slug %>?partial=1"
73 hx-target="#pcms-main"
74 hx-swap="innerHTML"
75 hx-push-url="<%= _base %>/<%= r.slug %>"
76 hx-indicator="#pcms-loading">
77 <%= r.title || '(zonder titel)' %>
78 </a>
79 </h3>
80 <p class="search-result-meta">
81 <%= r.author_username %>
82 <% if (r.published_at) { %> &middot; <%= formatDate(r.published_at) %><% } %>
83 </p>
84 <p class="search-result-snippet"><%- r.snippet %></p>
85 </li>
86 <% }); %>
87 </ol>
88 <% } %>
89
90 <% if (query && !queryError && !results.length && !_tracks.length) { %>
91 <p class="search-empty">Niets gevonden.</p>
92 <% } %>
93</div>
94
95<style>
96.search-page { max-width: 720px; margin: 3rem auto; padding: 0 1rem; }
97.search-page h1 { font-family: var(--font-display, serif); font-size: 2rem; margin: 0 0 1.5rem; }
98
99.search-page-form {
100 display: flex;
101 gap: 0.5rem;
102 margin-bottom: 1.5rem;
103}
104.search-page-form input {
105 flex: 1;
106 padding: 0.6rem 0.9rem;
107 border: 1px solid var(--rule);
108 border-radius: 5px;
109 background: var(--paper-2);
110 color: var(--ink);
111 font-size: 1rem;
112}
113.search-page-form input:focus {
114 outline: none;
115 border-color: var(--accent);
116}
117
118.search-meta {
119 color: var(--ink-muted, var(--ink-soft));
120 font-size: 0.9rem;
121 margin: 0 0 1rem;
122}
123.search-section-title {
124 font-family: var(--font-display, serif);
125 font-size: 1.1rem;
126 margin: 1.75rem 0 0.75rem;
127 color: var(--ink-soft);
128 text-transform: uppercase;
129 letter-spacing: 0.05em;
130}
131.search-error {
132 background: rgba(200, 60, 60, 0.15);
133 color: #c33;
134 padding: 0.75rem 1rem;
135 border-radius: 6px;
136 border: 1px solid rgba(200, 60, 60, 0.3);
137}
138.search-empty {
139 color: var(--ink-muted, var(--ink-soft));
140 text-align: center;
141 margin: 3rem 0;
142}
143
144/* Nummer-resultaten */
145.search-tracks { display: flex; flex-direction: column; gap: 0.25rem; }
146.search-track.post-audio-track {
147 display: flex;
148 align-items: center;
149 gap: 0.75rem;
150 padding: 0.5rem 0.6rem;
151 border-radius: 8px;
152}
153.search-track .search-track-cover {
154 width: 38px; height: 38px;
155 flex: 0 0 38px;
156 border-radius: 5px;
157 background-size: cover;
158 background-position: center;
159 background-color: var(--paper-2);
160}
161.search-track .pat-info { flex: 1; min-width: 0; }
162.search-track .pat-title { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
163.search-track .pat-artist { font-size: 0.82rem; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
164.search-track-link {
165 flex: 0 0 auto;
166 font-size: 0.82rem;
167 color: var(--accent);
168 text-decoration: none;
169 white-space: nowrap;
170}
171.search-track-link:hover { text-decoration: underline; }
172
173.search-results {
174 list-style: none;
175 margin: 0;
176 padding: 0;
177}
178.search-result {
179 padding: 1rem 0;
180 border-bottom: 1px solid var(--rule);
181}
182.search-result:last-child { border-bottom: 0; }
183
184.search-result-title {
185 font-family: var(--font-display, serif);
186 font-size: 1.25rem;
187 margin: 0 0 0.25rem;
188}
189.search-result-title a {
190 color: var(--ink);
191 text-decoration: none;
192}
193.search-result-title a:hover { color: var(--accent); }
194
195.search-result-meta {
196 margin: 0 0 0.5rem;
197 color: var(--ink-muted, var(--ink-soft));
198 font-size: 0.85rem;
199}
200
201.search-result-snippet {
202 margin: 0;
203 color: var(--ink-soft);
204 line-height: 1.5;
205}
206.search-result-snippet mark {
207 background: var(--accent);
208 color: white;
209 padding: 0.05em 0.25em;
210 border-radius: 3px;
211}
212</style>
Note: See TracBrowser for help on using the repository browser.