source: Klonkt/src/assets/js/mod/read.js@ 7421147

main
Last change on this file since 7421147 was 7421147, checked in by Bart <bart@…>, 3 weeks ago

Leesweergave: de twee zinnen die het script plaatst, zijn nu vertaalbaar

'Je bent bij het begin van het archief.' en de laadfout stonden hard in
mod/read.js -- terwijl read.exit in dezelfde commit wél in drie talen ging. Een
Engelse of Duitse lezer kreeg Nederlands aan het eind van het archief. Ze komen
nu van data-i18n op de stroom, dezelfde afspraak als de zoekteksten van de
topnav (mod/chrome.js).

En drie klassenamen die in het Nederlands waren blijven steken naast negen
Engelse: read-chrome-weg, read-laden en read-eind heten nu read-chrome-hidden,
read-loading en read-end.

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

  • Property mode set to 100644
File size: 5.8 KB
Line 
1/**
2 * De leesweergave: een stroom van hele berichten, één per beeld.
3 *
4 * NIET de truc van bartoverkamp.nl. Daar springt de pagina bij de rand naar de
5 * andere kant en wordt het nieuwe artikel "vloeiend" in beeld gezet -- een naad
6 * die verborgen wordt. Hier groeit de stroom in de richting waarin je leest:
7 * bij de onderrand komt de oudere buur eronder te staan, bij de bovenrand de
8 * nieuwere erboven. De browser snapt naar de berichtgrenzen (CSS, proximity).
9 * Er is dan geen naad, dus ook niets te verbergen.
10 *
11 * Elk artikel draagt zijn eigen buren in data-attributen. Wat er nog te halen
12 * valt staat dus altijd op het stuk dat er al is, en deze module hoeft geen
13 * lijst bij te houden die uit de pas kan lopen.
14 */
15
16// Hoeveel schermen vooruit we alvast halen. Eén is genoeg: je leest niet
17// sneller dan je scrolt, en meer betekent alleen meer verkeer.
18const MARGE = '1px 0px 100% 0px';
19
20// De teksten die dit script plaatst. Ze staan in data-i18n op de stroom, want
21// een zin die hier staat, staat in één taal -- en dat is nooit de taal van de
22// lezer. Zelfde afspraak als de topnav (mod/chrome.js).
23let woorden = {};
24function woord(sleutel) { return woorden[sleutel] || ''; }
25
26const stroom = () => document.getElementById('read-stream');
27const geladen = new Set(); // slugs die al in de stroom staan
28let bezigOnder = false;
29let bezigBoven = false;
30let waarnemer = null;
31let laatsteY = 0;
32
33function chrome(verbergen) {
34 document.body.classList.toggle('read-chrome-hidden', !!verbergen);
35}
36
37/** Het artikel dat nu het meest in beeld is; die bepaalt titel en URL. */
38function inBeeld() {
39 const posts = [...document.querySelectorAll('.read-post')];
40 const midden = window.innerHeight / 2;
41 return posts.find((p) => {
42 const r = p.getBoundingClientRect();
43 return r.top <= midden && r.bottom >= midden;
44 }) || posts[0];
45}
46
47/** Titel en adres volgen wat je leest. replaceState: je bladert, je stapelt niet. */
48function volgAdres() {
49 const p = inBeeld();
50 if (!p) return;
51 const slug = p.dataset.slug;
52 const basis = p.dataset.base || '';
53 if (location.pathname === `${basis}/read/${slug}`) return;
54 history.replaceState(null, '', `${basis}/read/${slug}`);
55 if (p.dataset.title) document.title = p.dataset.title;
56}
57
58async function haal(slug, basis) {
59 const r = await fetch(`${basis}/read/${encodeURIComponent(slug)}?partial=1`, {
60 headers: { 'HX-Request': 'true' },
61 credentials: 'same-origin',
62 });
63 if (!r.ok) throw new Error('HTTP ' + r.status);
64 const doos = document.createElement('div');
65 doos.innerHTML = await r.text();
66 const art = doos.querySelector('.read-post');
67 if (!art) throw new Error('geen bericht in het antwoord');
68 return art;
69}
70
71/** Onderaan aanvullen: gewoon erbij zetten, de scrollpositie verandert niet. */
72async function vulAan() {
73 const s = stroom();
74 if (!s || bezigOnder) return;
75 const laatste = s.querySelector('.read-post:last-of-type');
76 const slug = laatste && laatste.dataset.older;
77 if (!slug) return klaar(s);
78 if (geladen.has(slug)) return;
79 bezigOnder = true;
80 const wacht = melding(s, 'read-loading', '…');
81 try {
82 const art = await haal(slug, laatste.dataset.base || '');
83 geladen.add(slug);
84 wacht.remove();
85 s.appendChild(art);
86 kijk();
87 } catch (e) {
88 wacht.textContent = woord('load_error');
89 console.warn('[read] onderaan:', e && e.message);
90 } finally { bezigOnder = false; }
91}
92
93/**
94 * Bovenaan aanvullen. Dit is het enige plek waar we de scrollpositie WEL
95 * moeten bijstellen: er komt hoogte bij bóven het beeld, en zonder correctie
96 * schuift alles wat je las naar beneden weg. We meten de hoogte na het invoegen
97 * en tellen die bij de scrollpositie op -- geen animatie, geen sprong.
98 */
99async function vulAanBoven() {
100 const s = stroom();
101 if (!s || bezigBoven) return;
102 const eerste = s.querySelector('.read-post');
103 const slug = eerste && eerste.dataset.newer;
104 if (!slug || geladen.has(slug)) return;
105 bezigBoven = true;
106 try {
107 const art = await haal(slug, eerste.dataset.base || '');
108 geladen.add(slug);
109 const voor = s.scrollHeight;
110 s.insertBefore(art, eerste);
111 const erbij = s.scrollHeight - voor;
112 window.scrollBy(0, erbij);
113 kijk();
114 } catch (e) {
115 console.warn('[read] bovenaan:', e && e.message);
116 } finally { bezigBoven = false; }
117}
118
119function melding(s, klasse, tekst) {
120 const d = document.createElement('div');
121 d.className = klasse;
122 d.textContent = tekst;
123 s.appendChild(d);
124 return d;
125}
126
127function klaar(s) {
128 if (s.querySelector('.read-end')) return;
129 melding(s, 'read-end', woord('end'));
130}
131
132/** (Her)richt de waarnemer op de huidige eerste en laatste post. */
133function kijk() {
134 if (waarnemer) waarnemer.disconnect();
135 waarnemer = new IntersectionObserver((entries) => {
136 for (const e of entries) {
137 if (!e.isIntersecting) continue;
138 if (e.target.matches('.read-post:last-of-type')) vulAan();
139 else if (e.target.matches('.read-post:first-of-type')) vulAanBoven();
140 }
141 }, { rootMargin: MARGE });
142 const eerste = stroom() && stroom().querySelector('.read-post');
143 const laatste = stroom() && stroom().querySelector('.read-post:last-of-type');
144 if (eerste) waarnemer.observe(eerste);
145 if (laatste && laatste !== eerste) waarnemer.observe(laatste);
146}
147
148function bijScroll() {
149 const y = window.scrollY || 0;
150 if (y < 8) chrome(false);
151 else if (y > laatsteY + 4) chrome(true);
152 else if (y < laatsteY - 24) chrome(false);
153 laatsteY = y;
154 volgAdres();
155}
156
157export function init() {
158 const s = stroom();
159 if (!s) return;
160 try { woorden = JSON.parse(s.getAttribute('data-i18n') || '{}'); } catch (e) { woorden = {}; }
161 s.querySelectorAll('.read-post').forEach((p) => geladen.add(p.dataset.slug));
162 laatsteY = window.scrollY || 0;
163 window.addEventListener('scroll', bijScroll, { passive: true });
164 window.addEventListener('resize', kijk, { passive: true });
165 kijk();
166}
167
168export default { init };
Note: See TracBrowser for help on using the repository browser.