Changeset 79bba6e in Klonkt
- Timestamp:
- 06/17/2026 12:29:16 AM (3 months ago)
- Branches:
- main
- Children:
- cbd1761
- Parents:
- 44cf44d
- Location:
- src
- Files:
-
- 5 edited
-
routes/prutter.js (modified) (1 diff)
-
views/pages/hub-home.ejs (modified) (1 diff)
-
views/pages/prutter-conversation.ejs (modified) (4 diffs)
-
views/partials/chrome.ejs (modified) (1 diff)
-
views/shell.ejs (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
src/routes/prutter.js
r44cf44d r79bba6e 112 112 renderPage(req, res, 'pages/prutter-conversation', { 113 113 pageTitle: 'Prutter — ' + (other?.username || ''), 114 bodyClass: 'on-special', 114 // on-chat → full-height chat-view (geen artiest-profielkop, alleen de thread 115 // scrollt). Zie chrome.ejs (_headerless) + de page-CSS hieronder. 116 bodyClass: 'on-special on-chat', 115 117 conversation: conv, 116 118 other, -
src/views/pages/hub-home.ejs
r44cf44d r79bba6e 1 1 <div class="hub<%= hub.heroImage ? ' hub--img' : '' %>"> 2 2 3 <%# ── Minimale nav (alleen auth) — over de hero, geen brand/switcher ── %> 4 <nav class="hub-nav"> 5 <div class="hub-nav-inner container"> 6 <% if (user) { %> 7 <% if (typeof canSeeBeheer !== 'undefined' && canSeeBeheer) { %> 8 <a class="hub-nav-link" href="/admin" 9 hx-get="/admin?partial=1" hx-target="#pcms-main" hx-swap="innerHTML" 10 hx-push-url="/admin" hx-indicator="#pcms-loading">Beheer</a> 11 <% } %> 12 <a class="hub-nav-link" href="/account" 13 hx-get="/account?partial=1" hx-target="#pcms-main" hx-swap="innerHTML" 14 hx-push-url="/account" hx-indicator="#pcms-loading"><%= user.username %></a> 15 <a class="hub-nav-link" href="/auth/logout">Uitloggen</a> 16 <% } else { %> 17 <a class="hub-nav-link" href="/auth/login">Inloggen</a> 18 <% } %> 19 </div> 20 </nav> 21 22 <%# ── HERO (label) — de pagina begint hier, geen header erboven ────── %> 3 <%# ── HERO (label) — de mini topnav-balk staat erboven (chrome.ejs); 4 de hero zelf is de "grote" header van de hub-home ──────────────── %> 23 5 <header class="hub-hero<%= hub.heroImage ? ' has-img' : '' %>"<% if (hub.heroImage) { %> style="--hero-img:url('<%= hub.heroImage %>'); --hero-ov:<%= ((hub.heroOverlay == null ? 45 : hub.heroOverlay)/100).toFixed(2) %>"<% } %>> 24 6 <div class="hub-hero-inner container"> -
src/views/pages/prutter-conversation.ejs
r44cf44d r79bba6e 1 <div class=" containerprutter-conv-page" data-conversation-id="<%= conversation.id %>" data-me="<%= user.id %>">1 <div class="prutter-conv-page" data-conversation-id="<%= conversation.id %>" data-me="<%= user.id %>"> 2 2 <header class="prutter-conv-header"> 3 < p class="prutter-back"><a href="<%= siteUrlBase %>/prutter">← Inbox</a></p>4 <div class="prutter-conv-headline">5 <div class="prutter-conv-avatar prutter-conv-avatar--lg">6 <% if (other && other.avatar_url) { %>7 <img src="<%= other.avatar_url %>" alt="">8 < % } else { %>9 <span><%= (other && other.username || '?').charAt(0).toUpperCase() %></span>10 < % } %>11 < /div>12 <div>13 <h1><%= other ? other.username : 'Unknown' %></h1>14 <% if (other) { %>15 <p class="prutter-conv-sub"><a href="/users/<%= encodeURIComponent(other.username) %>">View profile</a></p>16 < % } %>17 < /div>3 <a class="prutter-back" href="<%= siteUrlBase %>/prutter" aria-label="Terug naar inbox" title="Inbox"> 4 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg> 5 </a> 6 <div class="prutter-conv-avatar"> 7 <% if (other && other.avatar_url) { %> 8 <img src="<%= other.avatar_url %>" alt=""> 9 <% } else { %> 10 <span><%= (other && other.username || '?').charAt(0).toUpperCase() %></span> 11 <% } %> 12 </div> 13 <div class="prutter-conv-id"> 14 <span class="prutter-conv-name"><%= other ? other.username : 'Onbekend' %></span> 15 <% if (other) { %> 16 <a class="prutter-conv-sub" href="/users/<%= encodeURIComponent(other.username) %>">Bekijk profiel</a> 17 <% } %> 18 18 </div> 19 19 </header> … … 35 35 hx-swap="beforeend" 36 36 hx-on::after-request="this.querySelector('textarea').value=''; window.__prutterScroll && window.__prutterScroll();"> 37 <textarea name="content" rows=" 2" maxlength="2000" placeholder="Type a message…" required></textarea>38 <button type="submit" class="btn btn-primary">S end</button>37 <textarea name="content" rows="1" maxlength="2000" placeholder="Bericht…" required></textarea> 38 <button type="submit" class="btn btn-primary">Stuur</button> 39 39 </form> 40 40 </div> … … 87 87 88 88 <style> 89 .prutter-conv-page { max-width: 720px; margin: 2rem auto; padding: 0 1rem; display: flex; flex-direction: column; gap: 1rem; } 90 .prutter-back { margin: 0; } 91 .prutter-back a { color: var(--accent); text-decoration: none; } 92 93 .prutter-conv-headline { 94 display: flex; align-items: center; gap: 0.85rem; 95 padding-bottom: 1rem; border-bottom: 1px solid var(--rule); 96 } 97 .prutter-conv-headline h1 { 98 font-family: var(--font-display, serif); 99 font-size: 1.5rem; margin: 0; 100 } 101 .prutter-conv-sub { margin: 0; font-size: 0.85rem; color: var(--ink-muted, var(--ink-soft)); } 102 .prutter-conv-sub a { color: var(--accent); } 103 104 .prutter-conv-avatar--lg { width: 56px; height: 56px; } 105 .prutter-conv-avatar--lg span { font-size: 1.2rem; } 106 89 /* ── Full-height chat-layout (mobile-first) ────────────────────────────── 90 De pagina is FIXED gepind tussen de topnav (desktop) en de onderste balken 91 (bottom-tab + audiospeler), zodat ALLEEN de thread scrollt — niet de hele 92 pagina. De offsets volgen de body-classes has-bottom-tab/has-audio-player, 93 dus de composer valt nooit achter een balk. */ 94 .prutter-conv-page { 95 position: fixed; 96 left: 0; right: 0; 97 top: 0; /* mobiel: masthead is verborgen → vanaf de top */ 98 bottom: 0; 99 max-width: 760px; margin: 0 auto; 100 display: flex; flex-direction: column; 101 padding: 0.5rem 0.75rem; 102 z-index: 1; 103 } 104 /* Desktop: de sticky masthead staat erboven → begin eronder. */ 105 @media (min-width: 768px) { 106 .prutter-conv-page { top: 64px; padding: 0.75rem 1rem; } 107 } 108 /* Onderste vrije ruimte = de balken die de viewport-bodem overlappen. */ 109 @media (max-width: 767px) { 110 body.has-bottom-tab .prutter-conv-page { bottom: calc(56px + env(safe-area-inset-bottom, 0)); } 111 } 112 body.has-audio-player .prutter-conv-page { bottom: calc(76px + env(safe-area-inset-bottom, 0)); } 113 @media (max-width: 767px) { 114 body.has-bottom-tab.has-audio-player .prutter-conv-page { bottom: calc(56px + 76px + env(safe-area-inset-bottom, 0)); } 115 } 116 117 /* Compacte chat-kop: terug-pijl + avatar + naam op één regel. */ 118 .prutter-conv-header { 119 flex: 0 0 auto; 120 display: flex; align-items: center; gap: 0.65rem; 121 padding-bottom: 0.6rem; margin-bottom: 0.5rem; 122 border-bottom: 1px solid var(--rule); 123 } 124 .prutter-back { 125 flex: 0 0 auto; 126 display: inline-flex; align-items: center; justify-content: center; 127 width: 40px; height: 40px; border-radius: 8px; 128 color: var(--ink); text-decoration: none; 129 } 130 .prutter-back:hover { color: var(--accent); background: var(--paper-2); } 131 .prutter-back svg { width: 20px; height: 20px; } 132 .prutter-conv-avatar { 133 flex: 0 0 auto; 134 width: 40px; height: 40px; border-radius: 50%; 135 overflow: hidden; background: var(--paper-2); 136 display: inline-flex; align-items: center; justify-content: center; 137 color: var(--accent); font-weight: 700; 138 } 139 .prutter-conv-avatar img { width: 100%; height: 100%; object-fit: cover; } 140 .prutter-conv-id { display: flex; flex-direction: column; min-width: 0; line-height: 1.2; } 141 .prutter-conv-name { 142 font-family: var(--font-display, serif); font-size: 1.15rem; color: var(--ink); 143 white-space: nowrap; overflow: hidden; text-overflow: ellipsis; 144 } 145 .prutter-conv-sub { font-size: 0.78rem; color: var(--ink-muted, var(--ink-soft)); text-decoration: none; } 146 .prutter-conv-sub:hover { color: var(--accent); } 147 148 /* De ENIGE scroller. min-height:0 is cruciaal in een flex-kolom, anders 149 groeit de lijst i.p.v. te scrollen. */ 107 150 .prutter-thread { 108 list-style: none; padding: 0; margin: 0; 109 display: flex; flex-direction: column; gap: 0.5rem; 110 background: var(--paper-2); 111 border: 1px solid var(--rule); 112 border-radius: 8px; 113 padding: 1rem; 114 height: 60vh; 115 overflow-y: auto; 116 } 117 118 .prutter-msg { display: flex; flex-direction: column; max-width: 80%; } 151 flex: 1 1 auto; min-height: 0; 152 list-style: none; margin: 0; 153 display: flex; flex-direction: column; gap: 0.4rem; 154 padding: 0.25rem 0.1rem 0.5rem; 155 overflow-y: auto; overscroll-behavior: contain; 156 -webkit-overflow-scrolling: touch; 157 } 158 159 .prutter-msg { display: flex; flex-direction: column; max-width: 82%; } 119 160 .prutter-msg-bubble { 120 161 padding: 0.55rem 0.85rem; … … 148 189 149 190 .prutter-composer { 191 flex: 0 0 auto; 150 192 display: flex; gap: 0.5rem; align-items: flex-end; 151 padding-bottom: env(safe-area-inset-bottom, 0); 193 padding-top: 0.6rem; margin-top: 0.25rem; 194 border-top: 1px solid var(--rule); 152 195 } 153 196 .prutter-composer textarea { 154 197 flex: 1; 155 padding: 0.6 rem 0.85rem;198 padding: 0.65rem 0.85rem; 156 199 border: 1px solid var(--rule); 157 border-radius: 8px;200 border-radius: 12px; 158 201 background: var(--paper); 159 202 color: var(--ink); 160 203 font-family: inherit; 161 font-size: 0.95rem; 162 resize: vertical; 163 min-height: 44px; 164 } 204 font-size: 1rem; /* ≥16px → iOS zoomt niet in bij focus */ 205 line-height: 1.35; 206 resize: none; 207 min-height: 44px; max-height: 40vh; 208 } 209 .prutter-composer .btn { flex: 0 0 auto; min-height: 44px; } 165 210 </style> -
src/views/partials/chrome.ejs
r44cf44d r79bba6e 8 8 // 9 9 // Wordt zowel door shell.ejs (oob niet gezet) als door render.js bij partials 10 // (oob=true) gerenderd. bareChrome-logica staat hier zodat beide identiek zijn. 11 var _isAdmin = typeof bodyClass === 'string' && bodyClass.indexOf('on-admin') >= 0; 12 var bareChrome = (typeof bodyClass === 'string' && (bodyClass.indexOf('on-hub') >= 0 || bodyClass.indexOf('on-auth') >= 0)) 10 // (oob=true) gerenderd. De logica staat hier zodat beide identiek zijn. 11 // 12 // _isAuth → focus-scherm (in/uitloggen): helemaal geen chrome. 13 // _headerless → hub-homepagina én de Prutter-chat: WÉL de mini topnav-balk, 14 // GEEN profielkop (hub: hero = header; chat: full-height view). 15 // anders → topnav + profielkop (+ view-switcher buiten admin). 16 var _isAdmin = typeof bodyClass === 'string' && bodyClass.indexOf('on-admin') >= 0; 17 var _isAuth = typeof bodyClass === 'string' && bodyClass.indexOf('on-auth') >= 0; 18 var _isChat = typeof bodyClass === 'string' && bodyClass.indexOf('on-chat') >= 0; 19 var hubLanding = (typeof bodyClass === 'string' && bodyClass.indexOf('on-hub') >= 0) 13 20 || ((typeof tenancy !== 'undefined' && tenancy === 'hub') && !siteUrlBase && !_isAdmin); 21 var _headerless = hubLanding || _isChat; 14 22 %> 15 23 <div id="pcms-chrome"<% if (typeof oob !== 'undefined' && oob) { %> hx-swap-oob="true"<% } %>> 16 <% if (! bareChrome) { %>24 <% if (!_isAuth) { %> 17 25 <%- include('topnav') %> 18 <%- include('profile-header') %> 19 <% if (!_isAdmin) { %><%- include('view-switcher') %><% } %> 26 <% if (!_headerless) { %> 27 <%- include('profile-header') %> 28 <% if (!_isAdmin) { %><%- include('view-switcher') %><% } %> 29 <% } %> 20 30 <% } %> 21 31 </div> -
src/views/shell.ejs
r44cf44d r79bba6e 419 419 </script> 420 420 421 <!-- Globale link-boost: alle interne navigatie-links lopen via htmx in #pcms-main, 422 zodat de audioplayer (los in document.body) blijft spelen i.p.v. te verspringen 423 bij een full page-load. Werkt overal — Beheer, Account, posts, sites — zonder 424 elke link los htmx te maken. Links die écht een volledige load nodig hebben 425 (uitloggen/auth, downloads, feeds, media, assets, bestanden) worden overgeslagen, 426 net als links die al hun eigen hx-* hebben. --> 427 <script> 428 (function () { 429 if (!window.htmx) return; 430 // Back/forward moet her-ophalen, zodat de OOB-site-chrome (kop) meekomt met de 431 // herstelde pagina — htmx' snapshot-cache zou anders een stale kop tonen. 432 try { window.htmx.config.historyCacheSize = 0; } catch (_) {} 433 434 function fullLoad(a, url) { 435 if (a.hasAttribute('download') || a.hasAttribute('data-full-load')) return true; 436 if (a.hasAttribute('hx-get') || a.hasAttribute('hx-post') || a.hasAttribute('hx-boost')) return true; 437 if (a.target && a.target !== '_self') return true; 438 if (a.getAttribute('rel') === 'external') return true; 439 var p = url.pathname; 440 // Sessie/security/oauth → volledige navigatie (cookies, redirects). 441 if (/^\/(logout|login|auth|oauth)(?:\/|$)/.test(p)) return true; 442 // Feeds, PWA, service-worker, statics, media-streams, downloads. 443 if (/^\/(feed|atom|sitemap|manifest|robots|sw\.js|assets|media|audio|uploads)(?:\/|\.|$)/.test(p)) return true; 444 if (/\.[a-z0-9]{2,5}$/i.test(p)) return true; // bestandsextensie → laat de browser 't halen 445 return false; 446 } 447 448 var lastPath = location.pathname + location.search; 449 450 document.addEventListener('click', function (e) { 451 if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return; 452 var a = e.target.closest('a[href]'); 453 if (!a) return; 454 var href = a.getAttribute('href'); 455 if (!href || href.charAt(0) === '#') return; 456 var url; try { url = new URL(a.href, location.href); } catch (_) { return; } 457 if (url.origin !== location.origin) return; 458 if (fullLoad(a, url)) return; 459 e.preventDefault(); 460 var dest = url.pathname + url.search; 461 if (dest !== lastPath) history.pushState({ b: 1 }, '', dest); 462 lastPath = dest; 463 window.htmx.ajax('GET', dest, { target: '#pcms-main', swap: 'innerHTML' }); 464 try { window.scrollTo(0, 0); } catch (_) {} 465 }); 466 467 window.addEventListener('popstate', function () { 468 var here = location.pathname + location.search; 469 if (here === lastPath) return; 470 lastPath = here; 471 window.htmx.ajax('GET', here, { target: '#pcms-main', swap: 'innerHTML' }); 472 }); 473 })(); 474 </script> 475 421 476 <!-- PWA install prompt — show button when browser fires beforeinstallprompt --> 422 477 <script>
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)