Changeset e46ebaa in Klonkt
- Timestamp:
- 08/20/2026 12:54:30 AM (3 weeks ago)
- Branches:
- main
- Children:
- 48481cd
- Parents:
- 598f090
- Files:
-
- 1 added
- 9 edited
-
src/assets/css/style.css (modified) (5 diffs)
-
src/assets/js/mod/read.js (modified) (3 diffs)
-
src/config/database.js (modified) (1 diff)
-
src/routes/admin-sites.js (modified) (3 diffs)
-
src/services/i18n.js (modified) (3 diffs)
-
src/views/pages/admin-site-edit.ejs (modified) (1 diff)
-
src/views/pages/home.ejs (modified) (1 diff)
-
src/views/partials/view-switcher.ejs (modified) (2 diffs)
-
src/views/shell.ejs (modified) (6 diffs)
-
test/feed-alt-view.test.js (added)
Legend:
- Unmodified
- Added
- Removed
-
src/assets/css/style.css
r598f090 re46ebaa 2430 2430 } 2431 2431 2432 /* -------- AUTO (data-feed-view="auto") -------- 2433 Lezen op mobiel, Tijdlijn op desktop. GEEN JavaScript: de CSS kiest, dus geen 2434 resize-afhandeling en geen flikkering waarin je eerst de verkeerde weergave 2435 ziet. De tijdlijn is de terugval, dus boven de grens hoeft hier niets te 2436 staan -- die is vanzelf al zichtbaar. 2437 De :has(.feed-reader)-voorwaarde staat er om dezelfde reden als hierboven: 2438 zonder berichten is er geen leessectie, en dan moet de tijdlijn blijven staan 2439 met "nog niets geschreven" erin. */ 2440 @media (max-width: 767px) { 2441 body.on-home[data-feed-view="auto"] .feed-reader { display: block; } 2442 body.on-home[data-feed-view="auto"]:has(.feed-reader) .feed-timeline, 2443 body.on-home[data-feed-view="auto"]:has(.feed-reader) .feed-grid, 2444 body.on-home[data-feed-view="auto"]:has(.feed-reader) .pagination, 2445 body.on-home[data-feed-view="auto"]:has(.feed-reader) .related-posts, 2446 body.on-home[data-feed-view="auto"]:has(.feed-reader) .article-footer { 2447 display: none !important; 2448 } 2449 } 2450 /* In de auto-stand staan BEIDE knoppen in de HTML en laat de mediaquery er een 2451 zien. Zo verspringt de naam van de knop niet terwijl je ernaar kijkt, en is er 2452 geen JavaScript nodig dat op resize moet luisteren. */ 2453 .view-switch-btn.vs-alleen-mobiel { display: none; } 2454 @media (max-width: 767px) { 2455 .view-switch-btn.vs-alleen-mobiel { display: inline-flex; } 2456 .view-switch-btn.vs-alleen-desktop { display: none; } 2457 } 2458 2432 2459 /* Snappen doet de SCROLLCONTAINER, en dat is het document -- niet .read-stream, 2433 2460 die heeft overflow: visible en scrollt zelf niet. Proximity en geen mandatory: 2434 2461 in een bericht dat langer is dan het scherm moet je rustig door kunnen lezen. */ 2435 html:has(body.on-home[data-feed-view="reader"]) { scroll-snap-type: y proximity; } 2462 html:has(body.on-home[data-feed-view="reader"]) { 2463 scroll-snap-type: y proximity; 2464 /* Vloeiend, niet in een klap. Zonder dit springt een snap er in EEN frame 2465 naartoe en voelt het als een hik in plaats van als meegeven -- Barts 2466 melding (20-8). Geldt ook voor de terug-naar-boven-knop en voor een 2467 ankersprong, en dat is precies wat je wilt. 2468 Uitgezet bij prefers-reduced-motion, onderaan dit blok. */ 2469 scroll-behavior: smooth; 2470 } 2436 2471 /* En de browser niet óók laten compenseren als Load more er berichten bij zet: 2437 2472 Chrome en Firefox schuiven bij invoegen zelf mee (scroll anchoring), en dat … … 2441 2476 2442 2477 .feed-reader .read-post { 2478 /* Snap naar de BOVENkant van een bericht, zacht (proximity op html) en nooit 2479 dwingend (stop: normal). Een korte post vult het scherm, zodat hij niet als 2480 snipper tussen twee lijnen hangt. 2481 De uitzondering staat NIET hier maar in mod/read.js: een bericht dat langer 2482 is dan het scherm krijgt scroll-snap-align:none, anders trekt de browser je 2483 tijdens het lezen terug naar zijn bovenrand en lijkt het of het scrollen 2484 stopt. "Past het op het scherm?" is een vraag over gemeten hoogte, en die 2485 kan CSS niet stellen. */ 2486 /* Je LANDT op de bovenkant van een bericht -- dat is wat "snap to post" 2487 hoort te betekenen. (align:end probeerde ik ook; dan land je op de 2488 ONDERKANT van het vorige bericht, en bij een lang bericht zie je dus zijn 2489 laatste schermvol in plaats van de kop van het volgende.) 2490 Wanneer die bovenkant een snappunt IS, staat in mod/read.js: alleen zolang 2491 je er nog niet voorbij bent. Eenmaal in een bericht snapt er niets meer, 2492 ook niet naar zijn eigen bovenrand -- anders trekt proximity je terug 2493 zodra je een paar regels verder leest. */ 2443 2494 scroll-snap-align: start; 2444 2495 scroll-snap-stop: normal; 2445 min-height: 100svh; 2496 /* GEEN min-height meer. Die rekte elk bericht tot een vol scherm, en bij een 2497 kort bericht leverde dat een halve pagina leegte op onder "Replies and 2498 reactions" -- je zat naar niets te kijken en moest verder scrollen om te 2499 ontdekken dat er nog iets kwam. Robins vondst (20-8), met een 2500 schermafbeelding erbij. 2501 Zonder die hoogte is de stroom gewoon bericht na bericht: je ziet het 2502 volgende meteen onder het vorige beginnen. Het snappen doet nog steeds 2503 zijn werk aan de grens -- dat hangt aan scroll-snap-align en niet aan de 2504 hoogte -- dus de streep lijnt nog altijd uit met de bovenkant van het 2505 volgende bericht. */ 2446 2506 max-width: 46rem; 2447 2507 margin: 0 auto; … … 2461 2521 scrollen om te zien wat het IS. */ 2462 2522 .feed-reader .read-cover { 2463 /* Krimpt om het beeld heen en staat gecentreerd, in plaats van de kolom vol 2464 te trekken. Anders staat een klein plaatje in een veel te grote lijst. */ 2465 width: fit-content; 2466 max-width: 100%; 2467 margin: 0 auto 1.2rem; 2523 /* Volle kolombreedte: de afbeelding erin neemt die breedte, dus de figure 2524 hoeft niet meer om het beeld te krimpen. */ 2525 width: 100%; 2526 margin: 0 0 1.2rem; 2468 2527 border-radius: 10px; 2469 2528 overflow: hidden; … … 2472 2531 .feed-reader .read-cover video { 2473 2532 display: block; 2474 /* GEEN width:100%. Dat blies een cover van 221x165 op tot 505x378 -- ruim 2475 twee keer zo groot als hij is, en dus wazig. De tijdlijn deed dat nooit: 2476 daar zat een cover in een vakje van 120px, dus klein bleef klein. Met 2477 auto + max-width vult een grote afbeelding de kolom en blijft een kleine 2478 op ware grootte staan. 2479 En GEEN object-fit:cover: samen met de hoogtegrens sneed dat de onderkant 2480 eraf (553x464 kwam er als 505x400 uit). Met max-width en max-height houdt 2481 de browser de verhouding zelf en past het beeld HEEL in het vak. */ 2482 width: auto; 2533 /* Neemt de VOLLE kolombreedte, met de verhouding intact (object-fit 2534 weggelaten, dus geen bijsnijden). Een cover die van zichzelf kleiner is 2535 wordt netjes tot de kolom opgeschaald in plaats van als postzegeltje te 2536 blijven staan -- dat was te klein. De hoogtegrens houdt een staande foto 2537 nog binnen de perken zodat titel + eerste alinea boven de vouw blijven; 2538 hij krimpt dan mee in plaats van bijgesneden te worden. */ 2539 display: block; 2540 width: 100%; 2483 2541 height: auto; 2484 max- width: 100%;2485 max-height: 45svh;2542 max-height: 70svh; 2543 object-fit: contain; 2486 2544 /* Een logo met doorzichtige achtergrond zweeft anders op de paginakleur; 2487 2545 dezelfde keuze als bij .post-list-cover in de tijdlijn. */ … … 2506 2564 .feed-reader .read-open:focus-visible { opacity: 1; } 2507 2565 2566 /* Terug naar boven. Rechtsonder, want daar zit de duim -- en boven de tabbalk, 2567 die op mobiel 1050 is en anders overheen zou vallen. Onzichtbaar tot je ver 2568 genoeg bent, zodat hij niet meteen in de weg staat bij het eerste bericht. */ 2569 .read-top { 2570 position: fixed; 2571 right: max(1rem, env(safe-area-inset-right, 0)); 2572 bottom: calc(env(safe-area-inset-bottom, 0) + 4.75rem); 2573 z-index: 1040; 2574 display: grid; 2575 place-items: center; 2576 width: 44px; 2577 height: 44px; /* 44px = de kleinste maat die een duim betrouwbaar raakt */ 2578 border-radius: 999px; 2579 border: 1px solid var(--rule); 2580 background: color-mix(in srgb, var(--paper) 88%, transparent); 2581 backdrop-filter: blur(14px) saturate(140%); 2582 -webkit-backdrop-filter: blur(14px) saturate(140%); 2583 color: var(--ink); 2584 box-shadow: 0 2px 12px color-mix(in srgb, var(--ink) 12%, transparent); 2585 cursor: pointer; 2586 opacity: 0; 2587 pointer-events: none; 2588 transition: opacity .18s ease, transform .18s ease; 2589 transform: translateY(6px); 2590 } 2591 .read-top.is-zichtbaar { opacity: 1; pointer-events: auto; transform: none; } 2592 /* Buiten de leesweergave bestaat hij niet -- home.ejs rendert alle drie de 2593 secties, dus zonder deze regel zweeft hij ook boven Tijdlijn en Grid. */ 2594 body:not([data-feed-view="reader"]) .read-top, 2595 body:not(.on-home) .read-top { display: none; } 2596 /* Op desktop is er geen tabbalk, dus mag hij lager staan. */ 2597 @media (min-width: 768px) { 2598 .read-top { bottom: 1.5rem; right: 1.5rem; } 2599 } 2508 2600 @media (prefers-reduced-motion: reduce) { 2509 html:has(body.on-home[data-feed-view="reader"]) { scroll-snap-type: none; } 2601 .read-top { transition: none; transform: none; } 2602 } 2603 2604 @media (prefers-reduced-motion: reduce) { 2605 html:has(body.on-home[data-feed-view="reader"]) { 2606 scroll-snap-type: none; 2607 scroll-behavior: auto; 2608 } 2510 2609 } 2511 2610 -
src/assets/js/mod/read.js
r598f090 re46ebaa 17 17 * cmd/ctrl-klik hoort de browser zelf af te handelen. 18 18 */ 19 20 /** 21 * Snappen alleen waar het HELPT. 22 * 23 * Elk bericht heeft scroll-snap-align:start en min-height:100svh. Voor een kort 24 * bericht is dat precies goed: het vult het scherm en de volgende klikt netjes 25 * op zijn plek. Voor een LANG bericht is het een val -- je leest naar beneden en 26 * de browser trekt je terug naar de bovenrand, waardoor het scrollen lijkt te 27 * stoppen terwijl je gewoon doorscrollt. Barts melding (20-8). 28 * 29 * "Past dit bericht op het scherm?" is een vraag over gemeten hoogte, en die kan 30 * CSS niet stellen -- vandaar hier. Past het niet, dan gaat de snap eraf en lees 31 * je ononderbroken door tot het volgende bericht wel weer een snappunt is. 32 * 33 * De marge van 4px vangt afrondingsverschillen tussen svh en de echte hoogte; 34 * zonder die speling wipt een bericht dat toevallig exact past heen en weer. 35 */ 36 /** 37 * Wanneer is de bovenkant van een bericht een snappunt? 38 * 39 * Alleen zolang je er nog NIET voorbij bent. Zit de bovenrand op of onder de 40 * bovenkant van het scherm, dan kom je er nog aan en mag hij vangen. Is hij 41 * eenmaal voorbij -- je leest in het bericht -- dan gaat de snap eraf. 42 * 43 * Robins formulering (20-8), en die is preciezer dan wat ik er eerst van maakte: 44 * "in de post zelf mag er nooit gesnapt worden, ook niet bovenin; enkel de 45 * onderkant mag snappen naar de volgende post, dus daar de bovenkant van". 46 * Precies dat: de onderkant van bericht N is de bovenkant van N+1, en die is een 47 * doelwit omdat je hem nadert. De bovenkant van het bericht waar je IN zit is 48 * dat niet meer, want daar ben je voorbij. 49 * 50 * Zonder deze regel trok proximity je terug naar de bovenrand zodra je een paar 51 * regels verder scrolde, en leek het of het scrollen vastliep. 52 * 53 * De 4px is speling voor afronding: tijdens het snappen zelf loopt top naar 0 en 54 * mag hij niet halverwege afhaken. 55 */ 56 const SPELING = 4; 57 58 /** 59 * En snappen doet alleen mee op de weg NAAR BENEDEN (Robin, 20-8). 60 * 61 * Omhoog scroll je om iets terug te zoeken, en dan is elke vangst een hindernis: 62 * je wilt zelf bepalen waar je stopt. Omlaag lees je door, en dan helpt de 63 * grens juist. Dus bij omhoog gaat de snap er overal af. 64 * 65 * De drempel van 2px is er tegen richtingsruis: tijdens een vloeiende snap 66 * schommelt scrollY een fractie, en zonder speling zou de richting dan heen en 67 * weer klappen -- precies midden in de beweging die net soepel moest zijn. 68 */ 69 let laatsteY = 0; 70 let omlaag = true; 71 72 /** 73 * Hoe DICHT bij de grens hij vangt: de onderste VANGZONE pixels van een bericht. 74 * 75 * In pixels, en bewust niet inhoudelijk. Ik probeerde het aan de voet van het 76 * bericht te hangen ("ben je de reacties voorbij"), maar bij een KORT bericht 77 * gaat dat mis: min-height rekt zo'n bericht tot een vol scherm, dus de voet 78 * staat middenin met lege ruimte eronder en de zone begint op een willekeurige 79 * plek. Robin ving dat (20-8) voordat het uitgerold stond. 80 * 81 * Eerst stond dit op 20px en dat was te krap om ooit te vangen. Honderdvijftig 82 * is ruwweg het laatste stukje van een bericht: kom je daarbinnen tot stilstand, 83 * dan trekt hij de streep recht naar de bovenkant van het volgende. Stop je 84 * eerder, dan blijf je gewoon staan. 85 * 86 * Eén getal, dus makkelijk bij te stellen als het onder een duim anders voelt. 87 */ 88 const VANGZONE = 150; 89 90 function ijkEen(art) { 91 const top = art.getBoundingClientRect().top; 92 // Omhoog: nooit. Omlaag: alleen een grens die je NADERT en die binnen de zone 93 // ligt. De ondergrens blijft krap (SPELING): eenmaal voorbij wordt er niet 94 // teruggetrokken, ook niet over een paar pixels. 95 const wil = (!omlaag || top < -SPELING || top > VANGZONE) ? 'none' : ''; 96 // Alleen aanraken als het echt verandert: elke stijlwijziging tijdens een 97 // vloeiende scroll is een kans op een hik, zeker op iOS. 98 if (art.style.scrollSnapAlign !== wil) art.style.scrollSnapAlign = wil; 99 } 100 101 function ijkSnappen() { 102 const y = window.scrollY; 103 if (Math.abs(y - laatsteY) > 2) omlaag = y > laatsteY; 104 laatsteY = y; 105 document.querySelectorAll('.feed-reader .read-post').forEach(ijkEen); 106 } 107 108 /** 109 * Terug naar boven, en bewust NIET window.scrollTo(0). 110 * 111 * In een stroom wil je terug naar het BEGIN VAN DIT BERICHT als je halverwege een 112 * lang stuk zit, en pas daarna naar de kop van de pagina. Twee keer drukken doet 113 * dus twee verschillende dingen -- dat scheelt op mobiel een halve minuut vegen. 114 */ 115 function naarBoven() { 116 const zacht = !window.matchMedia('(prefers-reduced-motion: reduce)').matches; 117 const gedrag = zacht ? 'smooth' : 'auto'; 118 const posts = [...document.querySelectorAll('.feed-reader .read-post')]; 119 const huidig = posts.find((a) => { 120 const r = a.getBoundingClientRect(); 121 return r.top <= 8 && r.bottom > 8; 122 }); 123 // Sta je al bovenaan dit bericht (of bij het eerste), dan naar de paginakop. 124 if (huidig && huidig.getBoundingClientRect().top < -8) { 125 huidig.scrollIntoView({ behavior: gedrag, block: 'start' }); 126 return; 127 } 128 window.scrollTo({ top: 0, behavior: gedrag }); 129 } 130 131 /** De knop verschijnt pas als er iets ONDER je ligt om naar terug te keren. */ 132 function toonKnop(knop) { 133 knop.classList.toggle('is-zichtbaar', window.scrollY > window.innerHeight * 0.6); 134 } 19 135 20 136 let tapX = 0, tapY = 0; … … 37 153 } 38 154 155 let knop = null; 156 let opScroll = null; 157 39 158 export function init() { 40 159 const s = document.getElementById('read-stream'); … … 47 166 s.addEventListener('pointerdown', onPointerDown, { passive: true }); 48 167 s.addEventListener('click', onTap); 168 169 // De knop staat in de HTML (read-top.ejs), zodat hij er ook is zonder deze 170 // module -- dan doet hij niets, maar hij springt niet in beeld bij het laden. 171 knop = document.getElementById('read-top'); 172 if (knop) { 173 knop.onclick = naarBoven; 174 toonKnop(knop); 175 } 176 177 // Eén luisteraar, en losmaken bij een volgende init(): deze module draait bij 178 // ELKE paginawissel en anders stapelen ze op. 179 if (opScroll) { 180 window.removeEventListener('scroll', opScroll); 181 window.removeEventListener('resize', opScroll); 182 } 183 // Eén keer per frame, niet per scroll-event: scroll vuurt tientallen keren per 184 // seconde en we raken hier de stijl van elk bericht aan. 185 let gepland = false; 186 opScroll = () => { 187 if (knop) toonKnop(knop); 188 if (gepland) return; 189 gepland = true; 190 requestAnimationFrame(() => { gepland = false; ijkSnappen(); }); 191 }; 192 window.addEventListener('scroll', opScroll, { passive: true }); 193 window.addEventListener('resize', opScroll, { passive: true }); 194 ijkSnappen(); // ook meteen bij binnenkomst, niet pas bij de eerste scroll 49 195 } 50 196 -
src/config/database.js
r598f090 re46ebaa 200 200 ensureColumn('sites', 'feed_view_default', "TEXT DEFAULT 'grid'"); // timeline | grid 201 201 ensureColumn('sites', 'feed_view_switch', 'INTEGER DEFAULT 1'); // show switcher 202 // Welke TWEEDE weergave deze site aanbiedt naast Grid. 203 // 'reader' Lezen, hele berichten, een per scherm 204 // 'timeline' de chronologische lijst 205 // 'auto' Lezen op mobiel, Tijdlijn op desktop (grens 768px) 206 // Lezen verving Tijdlijn, maar de tijdlijn is nooit weggehaald -- home.ejs 207 // rendert alle drie de secties en de CSS kiest. Dit maakt er weer een keuze 208 // van in plaats van een besluit voor iedereen. 'auto' kost geen JavaScript: 209 // het is een mediaquery, dus geen resize-afhandeling en geen flikkering. 210 ensureColumn('sites', 'feed_alt_view', "TEXT DEFAULT 'reader'"); // reader | timeline | auto 202 211 ensureColumn('sites', 'show_search', 'INTEGER DEFAULT 1'); 203 212 ensureColumn('sites', 'show_archive_link', 'INTEGER DEFAULT 1'); -
src/routes/admin-sites.js
r598f090 re46ebaa 327 327 328 328 const f = req.body; 329 const feedViewDef = f.feed_view_default === 'grid' ? 'grid' : 'timeline'; 329 // Twee vragen, en ze hingen scheef: dit pad schreef 'timeline' terwijl het 330 // AANMAAKpad 'reader' schreef, voor precies dezelfde keuze. Elke site die ooit 331 // is opgeslagen droeg dus 'timeline', en de client vertaalde dat stil terug. 332 // Nu betekent de waarde weer wat er staat. 333 const feedAlt = ['timeline', 'auto'].includes(f.feed_alt_view) ? f.feed_alt_view : 'reader'; 334 const feedViewDef = f.feed_view_default === 'grid' ? 'grid' : feedAlt; 330 335 const profileLinksJson = buildProfileLinks(f); 331 336 … … 366 371 enable_audio_player = ?, 367 372 approve_followers = ?, 368 feed_view_default = ?, feed_view_switch = ?, 373 feed_view_default = ?, feed_view_switch = ?, feed_alt_view = ?, 369 374 show_search = ?, show_archive_link = ?, 370 375 custom_css = ?, custom_head_html = ?, custom_foot_html = ?, … … 390 395 feedViewDef, 391 396 f.feed_view_switch ? 1 : 0, 397 feedAlt, 392 398 f.show_search ? 1 : 0, 393 399 f.show_archive_link ? 1 : 0, -
src/services/i18n.js
r598f090 re46ebaa 33 33 'switch.solo': 'Solo', 34 34 'switch.circle': 'Cirkels', 35 'switch.grid': 'Grid', 'switch.reader': 'Lezen', 35 'switch.grid': 'Grid', 'switch.reader': 'Lezen', 'switch.timeline': 'Tijdlijn', 'read.to_top': 'Terug naar boven', 36 'asite.feed_alt': 'Tweede weergave', 'asite.feed_alt_reader': 'Lezen', 'asite.feed_alt_timeline': 'Tijdlijn', 'asite.feed_alt_auto': 'Lezen op mobiel, Tijdlijn op desktop', 36 37 'switch.reader_solo_only': 'Lezen kan alleen in Solo — in de cirkel staan berichten van anderen', 37 38 'postnav.newer': 'Nieuwer', … … 1048 1049 'switch.solo': 'Solo', 1049 1050 'switch.circle': 'Circles', 1050 'switch.grid': 'Grid', 'switch.reader': 'Reader', 1051 'switch.grid': 'Grid', 'switch.reader': 'Reader', 'switch.timeline': 'Timeline', 'read.to_top': 'Back to top', 1052 'asite.feed_alt': 'Second view', 'asite.feed_alt_reader': 'Reader', 'asite.feed_alt_timeline': 'Timeline', 'asite.feed_alt_auto': 'Reader on mobile, Timeline on desktop', 1051 1053 'switch.reader_solo_only': 'Reader is Solo only — a circle shows other people\u2019s posts', 1052 1054 'postnav.newer': 'Newer', … … 2054 2056 'switch.solo': 'Solo', 2055 2057 'switch.circle': 'Zirkel', 2056 'switch.grid': 'Raster', 'switch.reader': 'Lesen', 2058 'switch.grid': 'Raster', 'switch.reader': 'Lesen', 'switch.timeline': 'Zeitleiste', 'read.to_top': 'Nach oben', 2059 'asite.feed_alt': 'Zweite Ansicht', 'asite.feed_alt_reader': 'Lesen', 'asite.feed_alt_timeline': 'Zeitleiste', 'asite.feed_alt_auto': 'Lesen mobil, Zeitleiste am Desktop', 2057 2060 'switch.reader_solo_only': 'Lesen gibt es nur in Solo — im Zirkel stehen Beitr\u00e4ge anderer', 2058 2061 'postnav.newer': 'Neuer', -
src/views/pages/admin-site-edit.ejs
r598f090 re46ebaa 196 196 <fieldset> 197 197 <legend><%= t('asite.feed_view') %></legend> 198 <%# Twee vragen, en ze zijn niet hetzelfde: WELKE tweede weergave je site 199 aanbiedt naast Grid, en waarin hij opent. %> 200 <label> 201 <span><%= t('asite.feed_alt') %></span> 202 <select name="feed_alt_view"> 203 <% var _a = ['timeline','auto'].indexOf(site.feed_alt_view) >= 0 ? site.feed_alt_view : 'reader'; %> 204 <option value="reader" <%= _a === 'reader' ? 'selected' : '' %>><%= t('asite.feed_alt_reader') %></option> 205 <option value="timeline" <%= _a === 'timeline' ? 'selected' : '' %>><%= t('asite.feed_alt_timeline') %></option> 206 <option value="auto" <%= _a === 'auto' ? 'selected' : '' %>><%= t('asite.feed_alt_auto') %></option> 207 </select> 208 </label> 198 209 <label> 199 210 <span><%= t('asite.feed_default') %></span> 200 211 <select name="feed_view_default"> 201 <option value="reader" <%= site.feed_view_default !== 'grid' ? 'selected' : '' %>><%= t('asite.feed_ reader') %></option>212 <option value="reader" <%= site.feed_view_default !== 'grid' ? 'selected' : '' %>><%= t('asite.feed_alt_' + _a) %></option> 202 213 <option value="grid" <%= site.feed_view_default === 'grid' ? 'selected' : '' %>><%= t('asite.feed_grid') %></option> 203 214 </select> -
src/views/pages/home.ejs
r598f090 re46ebaa 67 67 <% if (typeof readerItems !== 'undefined' && readerItems && readerItems.length) { %> 68 68 <section class="feed-reader" aria-label="<%= t('switch.reader') %>"> 69 <%# Terug naar boven. Staat IN de HTML en niet in JavaScript, zodat hij er ook 70 is als de module niet laadt -- dan doet hij niets, maar hij springt ook niet 71 opeens in beeld. Verschijnt pas als je ver genoeg bent (mod/read.js). %> 72 <button type="button" id="read-top" class="read-top" aria-label="<%= t('read.to_top') %>" title="<%= t('read.to_top') %>"> 73 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg> 74 </button> 69 75 <div class="read-stream" id="read-stream" data-base="<%= _base %>"> 70 76 <% readerItems.forEach(function(it) { %> -
src/views/partials/view-switcher.ejs
r598f090 re46ebaa 23 23 var _leesUit = _bc.indexOf('on-cirkel') >= 0; 24 24 var _gridStandaard = (typeof site !== 'undefined' && site && site.feed_view_default === 'grid'); 25 // Welke tweede weergave biedt deze site aan? Bij 'auto' tekenen we ZE ALLEBEI 26 // en laat de CSS er per viewport een zien -- Lezen op mobiel, Tijdlijn op 27 // desktop. Dat scheelt een resize-afhandeling en een knop die van naam 28 // verspringt terwijl je kijkt. 29 var _alt = (typeof site !== 'undefined' && site && site.feed_alt_view) || 'reader'; 30 if (['reader', 'timeline', 'auto'].indexOf(_alt) < 0) _alt = 'reader'; 31 var _tweede = _alt === 'auto' ? ['reader', 'timeline'] : [_alt]; 32 // De tijdlijn WERKT wel in de cirkel -- dat is er zelfs de natuurlijke vorm. 33 // Alleen Lezen kan daar niet, want dat zijn berichten van anderen. 34 var _dood = function (v) { return v === 'reader' && _leesUit; }; 35 // Staat er GEEN levende tweede knop, dan is Grid het enige dat overblijft en 36 // hoort die aan te staan. Bij een tijdlijn-site is dat in de cirkel niet zo: 37 // daar werkt de tijdlijn gewoon. 38 var _alleenGrid = _tweede.every(_dood); 39 var _icoon = { 40 reader: '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>', 41 timeline: '<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>', 42 }; 25 43 %> 26 44 <div class="view-switcher-wrap"> … … 49 67 <div class="view-switcher" role="tablist" aria-label="Weergave"> 50 68 51 <%# Lezen is de standaardweergave sinds Tijdlijn weg is: hele berichten, 52 één per scherm. De chronologische lijst bestaat nog wel als opmaak 53 (.feed-timeline, ook op de cirkel en het archief), maar is geen KEUZE 54 meer -- daar was Lezen de betere vorm van. %> 55 <button type="button" class="view-switch-btn" data-view="reader" role="tab" 56 <%- _leesUit ? 'disabled aria-disabled="true" tabindex="-1" title="' + t('switch.reader_solo_only') + '"' : '' %> 57 aria-selected="<%= (!_leesUit && !_neutral && !_gridStandaard) ? 'true' : 'false' %>"> 58 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg> 59 <span><%= t('switch.reader') %></span> 69 <%# De tweede weergave naast Grid. Welke dat is, staat in de instellingen 70 van de site (feed_alt_view). Bij 'auto' staan ze er allebei en kiest 71 een mediaquery; vandaar de klasse vs-alleen-*. %> 72 <% _tweede.forEach(function (v) { %> 73 <button type="button" class="view-switch-btn<%= _alt === 'auto' ? ' vs-alleen-' + (v === 'reader' ? 'mobiel' : 'desktop') : '' %>" 74 data-view="<%= v %>" role="tab" 75 <%- _dood(v) ? 'disabled aria-disabled="true" tabindex="-1" title="' + t('switch.reader_solo_only') + '"' : '' %> 76 aria-selected="<%= (!_dood(v) && !_neutral && !_gridStandaard) ? 'true' : 'false' %>"> 77 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><%- _icoon[v] %></svg> 78 <span><%= t('switch.' + v) %></span> 60 79 </button> 80 <% }); %> 61 81 <button type="button" class="view-switch-btn" data-view="grid" role="tab" 62 aria-selected="<%= (!_neutral && (_ leesUit|| _gridStandaard)) ? 'true' : 'false' %>">82 aria-selected="<%= (!_neutral && (_alleenGrid || _gridStandaard)) ? 'true' : 'false' %>"> 63 83 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg> 64 84 <span><%= t('switch.grid') %></span> -
src/views/shell.ejs
r598f090 re46ebaa 222 222 223 223 <!-- v9 stylesheet (full palette system) --> 224 <link rel="stylesheet" href="/assets/css/style.css?v=7 2">224 <link rel="stylesheet" href="/assets/css/style.css?v=77"> 225 225 <script> 226 226 /* iOS safe-area, built by hand. env(safe-area-inset-top) resolves to 0 on this iOS in … … 345 345 </head> 346 346 347 <body class="<%= bodyClass || 'on-home' %> has-bottom-tab" data-js="chrome<%= (typeof pageJs !== 'undefined' && pageJs) ? ' ' + pageJs : '' %>" data-feed-view="<%- _e(safeSite.feed_view_default === 'grid' ? 'grid' : 'reader') %>" data-grid-cols="3" data-site-base="<%- _e((typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '') %>">347 <body class="<%= bodyClass || 'on-home' %> has-bottom-tab" data-js="chrome<%= (typeof pageJs !== 'undefined' && pageJs) ? ' ' + pageJs : '' %>" data-feed-view="<%- _e(safeSite.feed_view_default === 'grid' ? 'grid' : (["timeline","auto"].indexOf(safeSite.feed_alt_view) >= 0 ? safeSite.feed_alt_view : "reader")) %>" data-feed-alt="<%- _e(["timeline","auto"].indexOf(safeSite.feed_alt_view) >= 0 ? safeSite.feed_alt_view : "reader") %>" data-grid-cols="3" data-site-base="<%- _e((typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '') %>"> 348 348 349 349 <% if (typeof isViewer !== 'undefined' && isViewer) { %> … … 505 505 // Eén nummer voor de hele map. Te vaak bumpen kost één download; te weinig 506 506 // bumpen kost een bugfix die nooit aankomt. 507 var MOD_V = 4;507 var MOD_V = 13; 508 508 509 509 // name -> 1 (aan het laden) of de module-namespace (geladen). Een module … … 559 559 // 'pcms-feed-view' en wordt hier nergens geschreven, zodat je thuiskomt in de 560 560 // weergave die je achterliet. 561 var CIRKEL_SLEUTEL = 'pcms-cirkel-view'; 561 562 function opCirkel() { return body.classList.contains('on-cirkel'); } 562 563 … … 565 566 try { 566 567 if (opCirkel()) { 567 body.dataset.feedView = 'grid'; // de enige mogelijkheid daar 568 // Lezen kan hier niet -- dat zijn berichten van ANDEREN, van hun servers, 569 // en "het hele stuk" hebben wij niet in handen. De knop staat er wel maar 570 // dood. Een TIJDLIJN kan hier juist bij uitstek: dat is de natuurlijke 571 // vorm van een cirkel. 572 // 573 // Beschikbaar, niet verplicht. Dit dwong eerst 'timeline' af zodra de 574 // site geen Lezen-site was, en dan kon je in de cirkel geen Grid meer 575 // kiezen. Nu is Grid de landing en is Tijdlijn een keuze ernaast. 576 var altC = body.dataset.feedAlt || 'reader'; 577 var tijdlijnHier = altC === 'timeline' 578 || (altC === 'auto' && window.matchMedia('(min-width: 768px)').matches); 579 var magC = tijdlijnHier ? ['grid', 'timeline'] : ['grid']; 580 // Een EIGEN geheugen, en niet dat van solo. Twee redenen: hier kun je 581 // alleen uit Grid en Tijdlijn kiezen, dus een solo-keuze 'reader' zegt 582 // hier niets; en andersom mag een tuimeling hier je thuisweergave niet 583 // veranderen -- dan kom je thuis in iets anders dan je achterliet. 584 // (Dit geheugen stond er eerder al, verdween toen de cirkel nog maar een 585 // mogelijkheid had, en is nu weer nodig omdat Tijdlijn een echte keuze is.) 586 var vC = null; 587 try { vC = localStorage.getItem(CIRKEL_SLEUTEL); } catch (e) { /* geen opslag */ } 588 body.dataset.feedView = magC.indexOf(vC) >= 0 ? vC : 'grid'; 568 589 return; 569 590 } 570 591 var v = localStorage.getItem('pcms-feed-view'); 571 // 'timeline' bestaat niet meer als keuze, maar staat nog in de localStorage 572 // van iedereen die hier eerder was. Zonder deze vertaling zou zo iemand een 573 // weergave houden die nergens meer een knop heeft: de pil helemaal grijs, en 574 // een feed die niet is wat hij koos. Lezen is waar Tijdlijn in opging. 575 if (v === 'timeline') v = 'reader'; 576 if (v === 'grid' || v === 'reader') body.dataset.feedView = v; 592 // 'timeline' is sinds 20-8 weer een geldige keuze, maar alleen op een site 593 // die hem AANBIEDT. Wie de tijdlijn koos op de ene site en daarna een 594 // Lezen-site bezoekt, moet daar niet op een weergave landen die er geen 595 // knop heeft: dan staat de pil grijs en is de feed niet wat hij koos. 596 // Vandaar de toets tegen wat deze site biedt, in plaats van een vaste 597 // vertaling naar 'reader'. 598 var alt = body.dataset.feedAlt || 'reader'; 599 var mag = alt === 'auto' ? ['grid', 'reader', 'timeline'] : ['grid', alt]; 600 if (mag.indexOf(v) >= 0) body.dataset.feedView = v; 577 601 } catch (e) { /* geen opslag: de server-standaard blijft staan */ } 578 602 } … … 647 671 // veranderen: die moet je terugvinden zoals je hem achterliet. 648 672 try { 649 if (!opCirkel()) localStorage.setItem('pcms-feed-view', view); 673 // De cirkel heeft zijn eigen geheugen, zodat een keuze daar je 674 // thuisweergave niet aanraakt en andersom. 675 if (opCirkel()) localStorage.setItem(CIRKEL_SLEUTEL, view); 676 else localStorage.setItem('pcms-feed-view', view); 650 677 } catch(_) {} 651 678 syncAria();
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)