Changeset 1720482 in Klonkt for src/views/pages/search.ejs


Ignore:
Timestamp:
06/20/2026 02:33:25 AM (3 months ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
5f47671
Parents:
1ee967d
Message:

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@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/views/pages/search.ejs

    r1ee967d r1720482  
    11<% const _base = (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : ''; %>
     2<% const _tracks = (typeof tracks !== 'undefined' && tracks) ? tracks : []; %>
    23<div class="container search-page">
    3   <h1>Search</h1>
     4  <h1>Zoeken</h1>
    45
    56  <form method="get" action="<%= _base %>/search" class="search-page-form">
     
    89      name="q"
    910      value="<%= query %>"
    10       placeholder="Search posts…"
     11      placeholder="Zoek posts en nummers…"
    1112      autocomplete="off"
    1213      autofocus
    13       aria-label="Search query">
    14     <button type="submit" class="btn btn-primary">Search</button>
     14      aria-label="Zoekopdracht">
     15    <button type="submit" class="btn btn-primary">Zoek</button>
    1516  </form>
    1617
    1718  <% if (queryError) { %>
    18     <p class="search-error">Couldn't run that query. Try a simpler term.</p>
     19    <p class="search-error">Kon die zoekopdracht niet uitvoeren. Probeer een eenvoudiger term.</p>
    1920  <% } %>
    2021
    2122  <% if (query && !queryError) { %>
    2223    <p class="search-meta">
    23       <%= total %> result<%= total === 1 ? '' : 's' %> for &ldquo;<%= query %>&rdquo;
     24      <%= total %> resulta<%= total === 1 ? 'at' : 'ten' %> voor &ldquo;<%= query %>&rdquo;
    2425    </p>
    2526  <% } %>
    2627
     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
    2765  <% if (results && results.length) { %>
     66    <% if (_tracks.length) { %><h2 class="search-section-title">Posts</h2><% } %>
    2867    <ol class="search-results">
    2968      <% results.forEach(function(r) { %>
    3069        <li class="search-result">
    31           <h2 class="search-result-title">
    32             <a href="/<%= r.slug %>"
    33                hx-get="/<%= r.slug %>?partial=1"
     70          <h3 class="search-result-title">
     71            <a href="<%= _base %>/<%= r.slug %>"
     72               hx-get="<%= _base %>/<%= r.slug %>?partial=1"
    3473               hx-target="#pcms-main"
    3574               hx-swap="innerHTML"
    36                hx-push-url="/<%= r.slug %>"
     75               hx-push-url="<%= _base %>/<%= r.slug %>"
    3776               hx-indicator="#pcms-loading">
    38               <%= r.title || '(untitled)' %>
     77              <%= r.title || '(zonder titel)' %>
    3978            </a>
    40           </h2>
     79          </h3>
    4180          <p class="search-result-meta">
    4281            <%= r.author_username %>
     
    4786      <% }); %>
    4887    </ol>
    49   <% } else if (query && !queryError) { %>
    50     <p class="search-empty">No posts found.</p>
     88  <% } %>
     89
     90  <% if (query && !queryError && !results.length && !_tracks.length) { %>
     91    <p class="search-empty">Niets gevonden.</p>
    5192  <% } %>
    5293</div>
     
    80121  margin: 0 0 1rem;
    81122}
     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}
    82131.search-error {
    83132  background: rgba(200, 60, 60, 0.15);
     
    93142}
    94143
     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
    95173.search-results {
    96174  list-style: none;
    97175  margin: 0;
    98176  padding: 0;
    99   counter-reset: search-result;
    100177}
    101178.search-result {
Note: See TracChangeset for help on using the changeset viewer.