Changeset e46ebaa in Klonkt


Ignore:
Timestamp:
08/20/2026 12:54:30 AM (3 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
48481cd
Parents:
598f090
Message:

Lezen: snappen dat je laat lezen, en geen lege schermen meer

Barts meldingen van 20 augustus, in een stuk of zes rondes bijgesteld. De
uitkomst is korter dan de weg ernaartoe, en die weg is het waard om vast te
leggen omdat ik er twee keer overheen ben geschoten.

SNAPPEN. De regel is nu in drie zinnen te zeggen:

omhoog scrollen nooit snappen -- je zoekt iets terug en bepaalt zelf

waar je stopt

omlaag, ver weg vrij scrollen
omlaag, laatste
150px van een
bericht vangt, en je landt op de bovenkant van het volgende

Er is geen top-zone: de bovenkant van het bericht waar je IN zit is geen
doelwit meer. Dat was de oorspronkelijke klacht -- je las een paar regels
verder en proximity trok je terug naar de bovenrand, waardoor het leek of
het scrollen vastliep. De grens die wel vangt is de onderkant van je huidige
bericht, en dat is hetzelfde punt als de bovenkant van het volgende.

Onderweg twee doodlopende wegen, allebei door Robin gecorrigeerd:

  • Eerst zette ik de snap UIT voor berichten langer dan het scherm. Op een telefoon is bijna elk bericht net iets langer dan de viewport, dus daar verdween het snappen overal: op 812px hoog kregen berichten van 1009 en zelfs 839 al geen snap meer.
  • Toen probeerde ik de vangzone aan de VOET van het bericht te hangen ("ben je de reacties voorbij"). Bij een kort bericht staat die voet middenin de lege ruimte die min-height maakte, dus de zone begon op een willekeurige plek. Gelukkig ving Robin dat voordat het uitgerold stond.

Het moest dus een getal in pixels zijn, en 150 is dat getal.

GEEN LEGE SCHERMEN MEER. min-height:100svh rekte elk bericht op tot een vol
scherm. Bij een kort bericht gaf dat een halve pagina leegte onder "Replies
and reactions" -- je keek naar niets en moest doorscrollen om te ontdekken
dat er nog iets kwam. Weg dus: berichten hebben hun eigen hoogte (gemeten:
360/1009/839/250 waar het eerst 888/888/888/888 was) en de stroom is gewoon
bericht na bericht. Het snappen hangt aan scroll-snap-align en niet aan die
hoogte, dus dat werkt onveranderd.

VLOEIEND: scroll-behavior:smooth, want zonder dat springt een snap er in een
frame naartoe en voelt het als een hik. Uit bij prefers-reduced-motion.

TERUG NAAR BOVEN, rechtsonder, in twee stappen: eerst naar het begin van DIT
bericht, daarna pas naar de kop van de pagina. Die knop is de enige die naar
een bovenkant mag springen, en hij heeft het snappen daar niet voor nodig --
scrollIntoView stuurt zelf. Nagemeten, juist omdat omhoog-snappen uit staat:
1706 -> 1206, exact de bovenkant.

COVERS nemen de volle kolombreedte met de verhouding intact. De ronde ervoor
haalde het opblazen eruit maar liet kleine afbeeldingen op ware grootte
staan, en dat was als postzegel in een brede kolom te klein.

DE CIRKEL. Lezen blijft daar dood (berichten van anderen), maar een tijdlijn
werkt er juist bij uitstek. Die dwong ik eerst af zodra de site geen
Lezen-site was, en dan kon je in de cirkel geen Grid meer kiezen. Nu is Grid
de landing en staat Tijdlijn ernaast: bij feed_alt_view=timeline altijd, bij
auto op desktop. De cirkel krijgt zijn eigen geheugen terug, met een eigen
sleutel zodat een keuze daar je thuisweergave niet aanraakt.

style.css naar v77 en MOD_V naar 13.

Suite 1165/1165.

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

Files:
1 added
9 edited

Legend:

Unmodified
Added
Removed
  • src/assets/css/style.css

    r598f090 re46ebaa  
    24302430}
    24312431
     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
    24322459/* Snappen doet de SCROLLCONTAINER, en dat is het document -- niet .read-stream,
    24332460   die heeft overflow: visible en scrollt zelf niet. Proximity en geen mandatory:
    24342461   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; }
     2462html: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}
    24362471/* En de browser niet óók laten compenseren als Load more er berichten bij zet:
    24372472   Chrome en Firefox schuiven bij invoegen zelf mee (scroll anchoring), en dat
     
    24412476
    24422477.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. */
    24432494    scroll-snap-align: start;
    24442495    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. */
    24462506    max-width: 46rem;
    24472507    margin: 0 auto;
     
    24612521   scrollen om te zien wat het IS. */
    24622522.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;
    24682527    border-radius: 10px;
    24692528    overflow: hidden;
     
    24722531.feed-reader .read-cover video {
    24732532    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%;
    24832541    height: auto;
    2484     max-width: 100%;
    2485     max-height: 45svh;
     2542    max-height: 70svh;
     2543    object-fit: contain;
    24862544    /* Een logo met doorzichtige achtergrond zweeft anders op de paginakleur;
    24872545       dezelfde keuze als bij .post-list-cover in de tijdlijn. */
     
    25062564.feed-reader .read-open:focus-visible { opacity: 1; }
    25072565
     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. */
     2594body:not([data-feed-view="reader"]) .read-top,
     2595body: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}
    25082600@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    }
    25102609}
    25112610
  • src/assets/js/mod/read.js

    r598f090 re46ebaa  
    1717 * cmd/ctrl-klik hoort de browser zelf af te handelen.
    1818 */
     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 */
     56const 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 */
     69let laatsteY = 0;
     70let 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 */
     88const VANGZONE = 150;
     89
     90function 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
     101function 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 */
     115function 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. */
     132function toonKnop(knop) {
     133  knop.classList.toggle('is-zichtbaar', window.scrollY > window.innerHeight * 0.6);
     134}
    19135
    20136let tapX = 0, tapY = 0;
     
    37153}
    38154
     155let knop = null;
     156let opScroll = null;
     157
    39158export function init() {
    40159  const s = document.getElementById('read-stream');
     
    47166  s.addEventListener('pointerdown', onPointerDown, { passive: true });
    48167  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
    49195}
    50196
  • src/config/database.js

    r598f090 re46ebaa  
    200200  ensureColumn('sites', 'feed_view_default', "TEXT DEFAULT 'grid'"); // timeline | grid
    201201  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
    202211  ensureColumn('sites', 'show_search',     'INTEGER DEFAULT 1');
    203212  ensureColumn('sites', 'show_archive_link', 'INTEGER DEFAULT 1');
  • src/routes/admin-sites.js

    r598f090 re46ebaa  
    327327
    328328  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;
    330335  const profileLinksJson = buildProfileLinks(f);
    331336
     
    366371      enable_audio_player = ?,
    367372      approve_followers = ?,
    368       feed_view_default = ?, feed_view_switch = ?,
     373      feed_view_default = ?, feed_view_switch = ?, feed_alt_view = ?,
    369374      show_search = ?, show_archive_link = ?,
    370375      custom_css = ?, custom_head_html = ?, custom_foot_html = ?,
     
    390395    feedViewDef,
    391396    f.feed_view_switch ? 1 : 0,
     397    feedAlt,
    392398    f.show_search ? 1 : 0,
    393399    f.show_archive_link ? 1 : 0,
  • src/services/i18n.js

    r598f090 re46ebaa  
    3333    'switch.solo': 'Solo',
    3434    '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',
    3637    'switch.reader_solo_only': 'Lezen kan alleen in Solo — in de cirkel staan berichten van anderen',
    3738    'postnav.newer': 'Nieuwer',
     
    10481049    'switch.solo': 'Solo',
    10491050    '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',
    10511053    'switch.reader_solo_only': 'Reader is Solo only — a circle shows other people\u2019s posts',
    10521054    'postnav.newer': 'Newer',
     
    20542056    'switch.solo': 'Solo',
    20552057    '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',
    20572060    'switch.reader_solo_only': 'Lesen gibt es nur in Solo — im Zirkel stehen Beitr\u00e4ge anderer',
    20582061    'postnav.newer': 'Neuer',
  • src/views/pages/admin-site-edit.ejs

    r598f090 re46ebaa  
    196196    <fieldset>
    197197      <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>
    198209      <label>
    199210        <span><%= t('asite.feed_default') %></span>
    200211        <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>
    202213          <option value="grid"     <%= site.feed_view_default === 'grid' ? 'selected' : '' %>><%= t('asite.feed_grid') %></option>
    203214        </select>
  • src/views/pages/home.ejs

    r598f090 re46ebaa  
    6767<% if (typeof readerItems !== 'undefined' && readerItems && readerItems.length) { %>
    6868<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>
    6975  <div class="read-stream" id="read-stream" data-base="<%= _base %>">
    7076    <% readerItems.forEach(function(it) { %>
  • src/views/partials/view-switcher.ejs

    r598f090 re46ebaa  
    2323  var _leesUit = _bc.indexOf('on-cirkel') >= 0;
    2424  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  };
    2543%>
    2644<div class="view-switcher-wrap">
     
    4967    <div class="view-switcher" role="tablist" aria-label="Weergave">
    5068
    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>
    6079      </button>
     80      <% }); %>
    6181      <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' %>">
    6383        <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>
    6484        <span><%= t('switch.grid') %></span>
  • src/views/shell.ejs

    r598f090 re46ebaa  
    222222
    223223<!-- v9 stylesheet (full palette system) -->
    224 <link rel="stylesheet" href="/assets/css/style.css?v=72">
     224<link rel="stylesheet" href="/assets/css/style.css?v=77">
    225225<script>
    226226/* iOS safe-area, built by hand. env(safe-area-inset-top) resolves to 0 on this iOS in
     
    345345</head>
    346346
    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 : '') %>">
    348348
    349349<% if (typeof isViewer !== 'undefined' && isViewer) { %>
     
    505505  // Eén nummer voor de hele map. Te vaak bumpen kost één download; te weinig
    506506  // bumpen kost een bugfix die nooit aankomt.
    507   var MOD_V = 4;
     507  var MOD_V = 13;
    508508
    509509  // name -> 1 (aan het laden) of de module-namespace (geladen). Een module
     
    559559  // 'pcms-feed-view' en wordt hier nergens geschreven, zodat je thuiskomt in de
    560560  // weergave die je achterliet.
     561  var CIRKEL_SLEUTEL = 'pcms-cirkel-view';
    561562  function opCirkel() { return body.classList.contains('on-cirkel'); }
    562563
     
    565566    try {
    566567      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';
    568589        return;
    569590      }
    570591      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;
    577601    } catch (e) { /* geen opslag: de server-standaard blijft staan */ }
    578602  }
     
    647671      // veranderen: die moet je terugvinden zoals je hem achterliet.
    648672      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);
    650677      } catch(_) {}
    651678      syncAria();
Note: See TracChangeset for help on using the changeset viewer.