source: Klonkt/src/views/pages/search.ejs@ 86fa2d2

main
Last change on this file since 86fa2d2 was 097da22, checked in by roboburr <roboburr@…>, 3 months ago

feat(zoeken): live results while typing + search in events & pages

  • /search/suggest JSON endpoint; overlay shows live (debounced) grouped results while typing, with "all results →" to the full page.
  • Sources expanded: in addition to posts + tracks now also calendar events (shows: city/location/country/note, if the calendar is enabled) and site pages (Agenda/Downloads/Links/Press Kit/Archive, only those available). Full search page shows those sections too. NL/EN/DE.
  • Overlay placeholder now translated (was hardcoded NL).

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

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