Changeset 834bcc3 in Klonkt for src/views/partials
- Timestamp:
- 06/23/2026 06:14:27 PM (3 months ago)
- Branches:
- main
- Children:
- d774679
- Parents:
- bb42dfb
- Location:
- src/views/partials
- Files:
-
- 7 edited
-
bottom-tab.ejs (modified) (4 diffs)
-
footer.ejs (modified) (4 diffs)
-
like-button.ejs (modified) (1 diff)
-
post-nav.ejs (modified) (2 diffs)
-
shared-styles.ejs (modified) (4 diffs)
-
topnav.ejs (modified) (8 diffs)
-
track-editor.ejs (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/views/partials/bottom-tab.ejs
rbb42dfb r834bcc3 15 15 const _ownProfile = user ? ('/users/' + user.username) : null; 16 16 const _isHub = (typeof tenancy !== 'undefined' && tenancy === 'hub'); 17 // Home gaat in hub-modus ALTIJD naar de hub-root (/), niet naar de sub-home van18 // een artiest. In solo/circle is _siteUrlBase leeg, dus dat blijft'/'.17 // Home in hub mode ALWAYS goes to the hub root (/), not to an artist's sub-home. 18 // In solo/circle _siteUrlBase is empty, so it stays '/'. 19 19 const _homeHref = (_isHub ? '' : _siteUrlBase) + '/'; 20 20 … … 27 27 28 28 let _active = null; 29 // In hub is Home alleen actief op de échte hub-root, niet op een artiest-sub-home.29 // In hub, Home is only active on the real hub root, not on an artist sub-home. 30 30 if (_isHub ? (_path === '/') : (_p === '/' || _p === '')) _active = 'home'; 31 31 else if (_p.indexOf('/search') === 0 || _p.indexOf('/tag/') === 0 || _p.indexOf('/type/') === 0) _active = 'search'; … … 38 38 39 39 <!-- Home --> 40 <%# htmx -navigatie (geen full reload): de persistente audioplayer in document.body41 overleeft dan, dus de muziek verspringt niet bij het navigeren. Op mobiel is de42 top-balk toch verborgen, dus een context-reset via full-load is hier niet nodig. %>40 <%# htmx navigation (no full reload): the persistent audio player in document.body 41 survives, so music does not skip when navigating. On mobile the top bar is hidden 42 anyway, so a context reset via full-load is not needed here. %> 43 43 <a class="bottom-tab-item<%= _active === 'home' ? ' is-active' : '' %>" 44 44 href="<%= _homeHref %>" … … 308 308 309 309 <script> 310 // Bottom-tab zoekknop → opent #search-overlay. Via event-delegatie + de overlay311 // ELKE klik opnieuw opzoeken: de overlay zit in de chrome die bij htmx-navigatie312 // OOB vervangen wordt, dus een één-keer-opgevangen referentie verschaalt313 // ( knop deed daarna niets). Guard tegen dubbel-wiren.310 // Bottom-tab search button → opens #search-overlay. Using event delegation + looking 311 // up the overlay on EVERY click: the overlay lives in the chrome that is OOB-replaced 312 // during htmx navigation, so a once-captured reference goes stale 313 // (button did nothing afterwards). Guard against double-wiring. 314 314 (function() { 315 315 if (window.__btSearchWired) return; -
src/views/partials/footer.ejs
rbb42dfb r834bcc3 13 13 <span>© <%= new Date().getFullYear() %> <%= (site && site.title) || 'Klonkt' %></span> 14 14 </div> 15 <%# Install-app knop — in de footer-rij (midden), bespaart een hele extra rij.16 JS detect eert platform & opent een modal met PWA-install-stappen. %>15 <%# Install-app button — placed in the footer row (centre), saves a whole extra row. 16 JS detects the platform & opens a modal with PWA install steps. %> 17 17 <button type="button" class="install-app-btn install-app-btn--footer" data-pcms-install-app> 18 18 <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true"><path d="M5 20h14v-2H5v2zm7-18L5.33 9h3.84v6h5.66V9h3.84L12 2z" fill="currentColor"/></svg> … … 114 114 } 115 115 .footer-lang:hover { border-color: var(--accent); } 116 /* Open dropdown-opties leesbaar (anders wit-op-wit opdark mode). */116 /* Make open dropdown options readable (otherwise white-on-white in dark mode). */ 117 117 .footer-lang option { background: var(--paper); color: var(--ink); } 118 /* O p desktop staat de taalkeuze in de header-nav → footer-versie verbergen. */118 /* On desktop the language picker lives in the header nav → hide the footer version. */ 119 119 @media (min-width: 768px) { .footer-lang { display: none; } } 120 120 .footer-version { color: inherit; text-decoration: none; } … … 144 144 } 145 145 146 /* Compact e install-knop ín de footer-rij (midden) i.p.v. een grote losse CTA147 op een eigen regel — bespaart verticale ruimte. */146 /* Compact install button inside the footer row (centre) instead of a large standalone 147 CTA on its own line — saves vertical space. */ 148 148 .install-app-btn--footer { 149 149 flex: 0 0 auto; 150 width: auto !important; /* override de mobile .install-app-btn width:100% */150 width: auto !important; /* override the mobile .install-app-btn width:100% */ 151 151 padding: 0.42rem 0.95rem; 152 152 font-size: 0.82rem; … … 154 154 } 155 155 .install-app-btn--footer svg { width: 15px; height: 15px; } 156 /* O p smal scherm: laat de drie items netjes centreren als ze wrappen. */156 /* On narrow screens: let the three items centre neatly when they wrap. */ 157 157 @media (max-width: 600px) { 158 158 .site-footer-inner { justify-content: center; text-align: center; } -
src/views/partials/like-button.ejs
rbb42dfb r834bcc3 1 <%# Like -/favorietknop voor een post. Ingelogd → htmx-toggle (POST /posts/:id/like,2 swap outerHTML met deze partial terug). Niet ingelogd → link naarlogin.3 Verwacht: post (met.id), likedByMe, likeCount, loggedIn, loginNext. %>1 <%# Like/favourite button for a post. Logged in → htmx toggle (POST /posts/:id/like, 2 swaps outerHTML back with this partial). Not logged in → link to login. 3 Expects: post (with .id), likedByMe, likeCount, loggedIn, loginNext. %> 4 4 <% 5 5 var _liked = (typeof likedByMe !== 'undefined' && likedByMe); -
src/views/partials/post-nav.ejs
rbb42dfb r834bcc3 1 <%# Post -navigatie (Newer links / Older rechts) — gedeeld door post.ejs én2 fan-gate.ejs. Verwacht newerPost + olderPost in scope. Ontbreekt een buur,3 dan een uitgegrijsde placeholder zodat elke post dezelfde structuur houdt. %>1 <%# Post navigation (Newer link left / Older right) — shared by post.ejs and 2 fan-gate.ejs. Expects newerPost + olderPost in scope. If a neighbour is missing, 3 a greyed-out placeholder keeps every post in the same two-card structure. %> 4 4 <% if ((typeof newerPost !== 'undefined' && newerPost) || (typeof olderPost !== 'undefined' && olderPost)) { %> 5 5 <nav class="post-nav post-nav--top"> … … 35 35 <style> 36 36 .post-nav { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-top: 3rem; padding-top: 2rem; border-top: 1px solid var(--rule); } 37 /* Boven de post: geen lijn/gap erboven, maar een scheidingslijn ERONDER (naar de content). */37 /* Above the post: no line/gap above it, but a dividing line BELOW (toward the content). */ 38 38 .post-nav--top { margin-top: 0; padding-top: 0; border-top: 0; margin-bottom: 2rem; padding-bottom: 2rem; border-bottom: 1px solid var(--rule); } 39 39 .post-nav-prev, .post-nav-next { display: flex; flex-direction: column; padding: 1rem; background: var(--paper-2); border-radius: 6px; text-decoration: none; color: var(--ink); } 40 /* Lege plek (geen nieuwere/oudere post): zelfde kaart, uitgegrijsd + niet klikbaar,41 zodat elke post dezelfde twee-kaarten-structuur houdt. */40 /* Empty slot (no newer/older post): same card, greyed out + not clickable, 41 so every post keeps the same two-card structure. */ 42 42 .post-nav-prev.is-empty, .post-nav-next.is-empty { opacity: 0.4; cursor: default; } 43 43 .post-nav-prev.is-empty .post-nav-title, .post-nav-next.is-empty .post-nav-title { font-weight: 400; font-style: italic; color: var(--ink-muted); } -
src/views/partials/shared-styles.ejs
rbb42dfb r834bcc3 6 6 border-bottom: 1px solid var(--rule); 7 7 background: var(--paper); 8 /* Geen extra margin-top: de pill wordt zo gecentreerd tussen de profielkop-9 streep (boven) en de eigen border (onder). De gelijke lucht boven/onder de10 pill komt uit de symmetrische container-padding hieronder. De margin-bottom11 (1.5rem in style.css) blijft lucht naar de feed. */8 /* No extra margin-top: this centres the pill between the profile-header 9 rule (above) and the own border (below). Equal space above/below the 10 pill comes from the symmetric container padding below. The margin-bottom 11 (1.5rem in style.css) keeps breathing room toward the feed. */ 12 12 margin-top: 0; 13 13 } 14 14 .view-switcher-wrap .container { 15 15 display: flex; align-items: center; justify-content: center; gap: 0.75rem; 16 /* Symmetri sche padding → even veel lucht boven als onder de pill (≈8px),17 gecentreerd tussen de twee lijnen. */16 /* Symmetric padding → equal space above and below the pill (≈8px), 17 centred between the two dividing lines. */ 18 18 padding: 0.5rem 1rem; 19 19 max-width: 1200px; margin: 0 auto; … … 36 36 } 37 37 .view-switch-btn:hover { color: var(--ink); } 38 /* Acti ef = wit (lichte achtergrond, donkere tekst). Via aria-selected (element-39 attribu ut, door syncAria gezet + op htmx:afterSettle) → htmx-robuust. Op niet-40 feed -pagina's zet syncAria aria-selected=false → beide grijs. */38 /* Active = white (light background, dark text). Via aria-selected (element 39 attribute, set by syncAria + on htmx:afterSettle) → htmx-robust. On non- 40 feed pages syncAria sets aria-selected=false → both grey. */ 41 41 .view-switch-btn[aria-selected="true"] { background: var(--ink); color: var(--paper); border-color: var(--ink); } 42 42 43 /* Agenda = een EIGEN pill (los van Tijdlijn/Grid, zodat de feed-toggle nooit44 verstoord raakt). Zelfde look als de view-switcher-pill. Zichtbaar in alle modi. */45 .agenda-pill { display: inline-flex; align-items: center; } /* geen box — losse icoon+tekst link */43 /* Agenda = its OWN pill (separate from Timeline/Grid, so the feed-toggle is never 44 disturbed). Same look as the view-switcher pill. Visible in all modes. */ 45 .agenda-pill { display: inline-flex; align-items: center; } /* no box — standalone icon+text link */ 46 46 .vs-agenda { 47 47 display: inline-flex; align-items: center; gap: 0.4rem; … … 53 53 } 54 54 .vs-agenda:hover { color: var(--ink); } 55 /* Acti ef (op de agendapagina) = helder/wit. Element-class (in de OOB-gerenderde56 chrome) i.p.v. body-class-CSS → werkt betrouwbaar ook na htmx-navigatie. */55 /* Active (on the agenda page) = bright/white. Element-class (in the OOB-rendered 56 chrome) rather than body-class CSS → stays reliable after htmx navigation. */ 57 57 .vs-agenda.is-active { color: var(--ink); font-weight: 600; } 58 58 59 /* Mobi el: Agenda op een eigen rij bovenaan, en Solo/Cirkel + Tijdlijn/Grid samen60 o p de rij eronder (samen ~330px → past binnen een telefoon, i.p.v. de oude61 onevenwichtige split Agenda+Solo / Tijdlijn). */59 /* Mobile: Agenda on its own row at the top, Solo/Circle + Timeline/Grid together 60 on the row below (together ~330px → fits within a phone, instead of the old 61 unbalanced split Agenda+Solo / Timeline). */ 62 62 @media (max-width: 520px) { 63 63 .view-switcher-wrap .agenda-pill { flex: 0 0 100%; justify-content: center; } … … 92 92 .on-admin .view-switcher-wrap { display: none; } 93 93 94 /* O p een losse post is er geen feed om te schakelen, dus geen knop hoort "actief"95 ( gekleurd) te ogen. De actieve staat zit in style.css opbody[data-feed-view]96 / body[data-grid-cols] (background:var(--ink)); we overr ulen die SPECIFIEKER97 via body.on-post zodat de knoppen neutraal/grijs worden — maar wél klikbaar98 blijven (de klik-handler navigeert dan naar de feed in die weergave). De99 body-class wisselt client-side bij HTMX-nav, dus dit werkt ook zonder reload. */94 /* On a standalone post there is no feed to switch, so no button should look "active" 95 (coloured). The active state lives in style.css on body[data-feed-view] 96 / body[data-grid-cols] (background:var(--ink)); we override that MORE SPECIFICALLY 97 via body.on-post so the buttons become neutral/grey — but remain clickable 98 (the click-handler then navigates to the feed in that view). The 99 body-class switches client-side on HTMX-nav, so this works without a reload too. */ 100 100 body.on-post[data-feed-view="timeline"] .view-switch-btn[data-view="timeline"], 101 101 body.on-post[data-feed-view="grid"] .view-switch-btn[data-view="grid"], -
src/views/partials/topnav.ejs
rbb42dfb r834bcc3 27 27 <a href="<%= _siteUrlBase %>/" class="nav-link nav-link-admin"><%= t('nav.back_to_site') %></a> 28 28 <% } else if (typeof tenancy !== 'undefined' && tenancy === 'hub') { %> 29 <%# Hub: de brand gaat naar de hub-home via HTMX. De chrome wordt in hub-modus30 altijd gerenderd en via body.on-hub (CSS) verborgen op de landing, dushtmx31 kan schakelen zonder full-reload → de audioplayer blijft leven. %>29 <%# Hub: the brand navigates to the hub home via HTMX. The chrome is always 30 rendered in hub mode and hidden on the landing via body.on-hub (CSS), so htmx 31 can switch without a full reload → the audio player stays alive. %> 32 32 <a class="nav-hub-home" href="/" aria-label="Home" title="Home" 33 33 hx-get="/?partial=1" hx-target="#pcms-main" hx-swap="innerHTML" hx-push-url="/" hx-indicator="#pcms-loading"> … … 207 207 <script> 208 208 (function() { 209 // EÉN keer bedraden. Deze chrome (topnav) wordt bij htmx-navigatie out-of-band210 // opnieuw ingevoegd → zonder deze guard zou dit script bij elke navigatie EXTRA211 // listeners stapelen, waardoor de thema-toggle 2× (of vaker) vuurde = geen netto212 // wisseling ("toggle werkt niet meer"). Alles hieronder is event-delegatie op213 // b ody/document, dus het werkt ook voor knoppen die ná deze run (OOB) verschijnen.209 // Wired ONCE. This chrome (topnav) is re-inserted out-of-band during htmx navigation 210 // → without this guard the script would stack EXTRA listeners on every navigation, 211 // causing the theme toggle to fire 2× (or more) = no net change ("toggle stops working"). 212 // Everything below uses event delegation on body/document, so it also works for 213 // buttons that appear after this run (OOB). 214 214 if (window.__pcmsChromeWired) return; 215 215 window.__pcmsChromeWired = true; … … 229 229 if (e.target.closest('#search-toggle')) { openSearch(); return; } 230 230 if (e.target.closest('#search-close')) { closeSearch(); return; } 231 // Open dropdowns (gebruikersmenu + taalkeuze) sluiten bij klik erbuiten.231 // Close open dropdowns (user menu + language picker) on click outside. 232 232 document.querySelectorAll('.user-menu[open], .lang-menu[open]').forEach(function(d) { 233 233 if (!d.contains(e.target)) d.removeAttribute('open'); … … 239 239 }); 240 240 241 // ── Live result aten tijdens typen───────────────────────────────241 // ── Live results while typing ─────────────────────────────────── 242 242 var _ST = { 243 243 posts: '<%= t('search.section_posts') %>', tracks: '<%= t('search.section_tracks') %>', … … 280 280 }, 200); 281 281 }); 282 // Klik op een suggestie → overlay sluiten (de link/boost doet de navigatie).282 // Click on a suggestion → close the overlay (the link/boost handles navigation). 283 283 document.addEventListener('click', function(e) { 284 284 if (e.target.closest && e.target.closest('#search-suggest a')) { var o = overlay(); if (o) o.hidden = true; } … … 313 313 } 314 314 315 /* Hub-home brand is WÉL zichtbaar — de thuisknop op artiest-/hub-pagina's. */315 /* Hub-home brand IS visible — the home button on artist/hub pages. */ 316 316 .masthead-brand .nav-hub-home { 317 317 display: inline-flex; align-items: center; justify-content: center; … … 436 436 min-width: 220px; 437 437 padding: .35rem; 438 /* Frosted glass: transpar ant + zware blur zodat het frosted effect duidelijk is. */438 /* Frosted glass: transparent + heavy blur so the frosted effect is clearly visible. */ 439 439 background: color-mix(in srgb, var(--paper) 60%, transparent); 440 440 backdrop-filter: blur(34px) saturate(190%); … … 565 565 .ss-all { display: block; margin-top: .35rem; padding: .55rem .6rem; border-top: 1px solid var(--rule); color: var(--accent); font-weight: 600; text-decoration: none; font-size: .9rem; } 566 566 .ss-all:hover { text-decoration: underline; } 567 /* Meldingen-bel + ongelezen-badge */567 /* Notification bell + unread badge */ 568 568 .nav-notif { position: relative; } 569 569 .notif-badge { -
src/views/partials/track-editor.ejs
rbb42dfb r834bcc3 195 195 } 196 196 197 /* ── Credit -veld met ©-invoegknop──────────────────────────── */197 /* ── Credit field with © insert button ──────────────────────────── */ 198 198 .te-credit-row { display: flex; gap: 0.4rem; align-items: stretch; } 199 199 .te-credit-row input { flex: 1; min-width: 0; } … … 638 638 } 639 639 640 // ── ©-teken invoegen in het credit-veld ─────────────────640 // ── Insert © symbol into the credit field ───────────────── 641 641 const copyrBtn = $('#te-credit-copyr'); 642 642 if (copyrBtn) { … … 693 693 }); 694 694 695 // ── Auto-du ur───────────────────────────────────────────696 // De server bepaalt de duur al automatisch bij upload. Dit is de vangnet/697 // UX-laag: opent een admin een bestaande track zónder duur, dan lezen we 'm698 // hier uit de audio-metadata en vullen het veld — zodat je nooit seconden699 // hoeft te typen. Bestaande waarde wordt nooit overschreven. We halen de700 // bytes via dezelfde header-gate als de speler (X-Audio-Player).695 // ── Auto-duration ─────────────────────────────────────────── 696 // The server already determines duration automatically on upload. This is the 697 // fallback/UX layer: if an admin opens an existing track without a duration, 698 // we read it from the audio metadata and fill the field — so you never need 699 // to type seconds manually. An existing value is never overwritten. We fetch 700 // the bytes via the same header gate as the player (X-Audio-Player). 701 701 (async function autoDuration() { 702 702 const durEl = $('#te-duration'); 703 703 if (!durEl || !track.stream_url) return; 704 if (durEl.value && Number(durEl.value) > 0) return; // al ingevuld → met rust laten704 if (durEl.value && Number(durEl.value) > 0) return; // already filled → leave it alone 705 705 let objUrl = null; 706 706 try {
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)