source: Klonkt/src/assets/js/mod/messages.js@ e64a49b

main
Last change on this file since e64a49b was e64a49b, checked in by roboburr <roboburr@…>, 5 weeks ago

Berichten uit inline script naar een module (shaer-bqr, stap 2)

De enige bevestigde stukke plek. Kwam je hier via een link BINNEN de site, dan
arriveerde het script via htmx met een nonce die het document niet kent, en
weigerde de CSP het (shaer-0i6). Chips, zoeken en het in-/uitklappen deden dan
niets, en de reply-editor laadde niet -- dat was Barts melding.

Een pagina vraagt nu om zijn module met pageJs; de shell zet dat op body[data-js]
en de bootstrap importeert het. De waarde wordt een PAD, dus hij gaat door
/[a-z0-9 -]*$/ voordat hij de locals in mag.

TWEE DINGEN MOESTEN VERANDEREN, en niet omdat de code fout was: een module leeft
anders dan een inline script.

NIETS VASTHOUDEN het inline script pakte .msg-list, #msg-q en .msg-nomatch

een keer bij het inladen. Een module wordt per document maar
EEN keer geimporteerd, dus wie Berichten verlaat en
terugkomt houdt verwijzingen over naar elementen die er niet
meer zijn. Nu per keer opzoeken, en de invoerluisteraar
gedelegeerd in plaats van op het veld zelf.

OPNIEUW INDEXEREN bij binnenkomst op de pagina, niet alleen bij "meer laden".

Anders is de lijst na een navigatie niet geindexeerd en
filtert het zoeken op een lege verzameling.

Dat eerste is precies de val waar de chrome-partials al een comment over hebben
staan ("a once-captured reference goes stale"), en het geldt hier dubbel omdat
een module niet opnieuw draait.

Templates compileren, suite 556/556.

  • Property mode set to 100644
File size: 5.7 KB
Line 
1// Berichten: filteren, zoeken, en gesprekken in- en uitklappen (shaer-bqr).
2//
3// Dit stond inline in pages/messages.ejs. Dat werkte alleen na een volledige
4// laadbeurt: kwam je hier via een link BINNEN de site, dan arriveerde het script
5// via htmx met een nonce die het document niet kent, en weigerde de CSP het
6// (shaer-0i6). Chips, zoeken en het inklappen deden dan niets, en de reply-editor
7// laadde niet -- dat was Barts melding.
8//
9// TWEE DINGEN VERANDERD bij de verhuizing, en ze zijn allebei nodig omdat een
10// module ANDERS leeft dan een inline script:
11//
12// NIETS VASTHOUDEN het inline script pakte .msg-list, #msg-q en .msg-nomatch
13// een keer bij het inladen. Een module wordt per document
14// maar EEN keer geimporteerd, dus als je Berichten verlaat
15// en terugkomt wijzen die naar elementen die er niet meer
16// zijn. Nu wordt er per keer opgezocht.
17// OPNIEUW INDEXEREN bij binnenkomst op deze pagina, niet alleen bij "meer
18// laden". Anders is de lijst na een navigatie niet
19// geindexeerd en filtert het zoeken op niets.
20//
21// Alles gedelegeerd op document, met een slot: de module kan een tweede keer
22// geladen worden en mag dan geen tweede stel luisteraars neerzetten.
23
24(function () {
25 'use strict';
26 if (window.__msgWired) return;
27 window.__msgWired = true;
28
29 var kind = 'all';
30 var items = [];
31
32 // Per keer opzoeken. Zie de kop: vasthouden overleeft een navigatie niet.
33 function list() { return document.querySelector('.msg-list'); }
34 function noMatch() { return document.querySelector('.msg-nomatch'); }
35 function query() { return document.getElementById('msg-q'); }
36
37 function indexItem(li) {
38 // Een draad indexeren op zijn eerste bubbel zou de rest onvindbaar maken,
39 // dus daar nemen we de hele tekst; losse regels blijven gericht geindexeerd.
40 if (li.classList.contains('msg-thread')) {
41 li._search = ((li.getAttribute('data-who') || '') + ' ' + li.textContent).toLowerCase();
42 return;
43 }
44 var body = li.querySelector('.msg-content');
45 var post = li.querySelector('.msg-post');
46 var poll = li.querySelector('.msg-poll');
47 li._search = ((li.getAttribute('data-who') || '') + ' ' +
48 (body ? body.textContent : '') + ' ' + (post ? post.textContent : '') + ' ' +
49 (poll ? poll.textContent : '')).toLowerCase();
50 }
51
52 function reindex() {
53 var l = list();
54 items = l ? Array.prototype.slice.call(l.querySelectorAll(':scope > .msg-item')) : [];
55 items.forEach(function (li) { if (!li._search) indexItem(li); });
56 }
57
58 function apply() {
59 if (!list()) return;
60 var qEl = query();
61 var term = (qEl && qEl.value || '').trim().toLowerCase();
62 var shown = 0;
63 items.forEach(function (li) {
64 var ok = (kind === 'all' || li.getAttribute('data-kind') === kind) &&
65 (!term || li._search.indexOf(term) !== -1);
66 li.style.display = ok ? '' : 'none';
67 // Een treffer die in een dichtgeklapt gesprek zit, laat anders alleen de
68 // naam zien: je zoekt iets, het staat er, en je ziet het niet. Zolang er
69 // gezocht wordt gaat zo'n gesprek open; daarna keert hij terug naar de
70 // stand die de lezer zelf koos.
71 if (li.classList.contains('msg-thread')) {
72 li.classList.toggle('is-search-open', !!term && ok);
73 }
74 if (ok) shown++;
75 });
76 var nm = noMatch();
77 if (nm) nm.hidden = shown !== 0;
78 }
79
80 // In- en uitklappen door op de tegenpartij te tikken. De stand leeft alleen in
81 // deze pagina: standaard uitgeklapt, en na een herlading weer. Dat is bewust --
82 // een gesprek dat je gisteren dichtklapte stilhouden is niet hetzelfde als het
83 // opruimen van je scherm van nu.
84 document.addEventListener('click', function (e) {
85 var who = e.target.closest && e.target.closest('.msg-thread-who'); if (!who) return;
86 var li = who.closest('.msg-thread'); if (!li) return;
87 var collapsed = li.classList.toggle('is-collapsed');
88 who.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
89 });
90
91 document.addEventListener('click', function (e) {
92 var chip = e.target.closest && e.target.closest('.msg-chip'); if (!chip) return;
93 kind = chip.getAttribute('data-show');
94 document.querySelectorAll('.msg-chip').forEach(function (c) { c.classList.toggle('is-on', c === chip); });
95 apply();
96 });
97
98 // Gedelegeerd in plaats van op het invoerveld zelf: dat veld wordt bij een
99 // navigatie vervangen.
100 document.addEventListener('input', function (e) {
101 if (e.target && e.target.id === 'msg-q') apply();
102 });
103
104 document.body.addEventListener('htmx:afterSettle', function (e) {
105 // "Meer laden" voegt rijen toe aan de bestaande lijst; een navigatie brengt
106 // een hele nieuwe lijst. In beide gevallen opnieuw indexeren en de actieve
107 // chip toepassen.
108 if (e.target && (e.target.id === 'msg-list' || e.target.querySelector && e.target.querySelector('.msg-list'))) {
109 reindex();
110 apply();
111 }
112 });
113
114 // De bookmarklet-knop: bouwt zijn href pas in de browser, want hij heeft de
115 // origin nodig.
116 function wireBookmarklet() {
117 var a = document.getElementById('fedi-bm-btn');
118 if (!a || a.__wired) return;
119 a.__wired = true;
120 a.setAttribute('href', "javascript:void(window.open('" + location.origin + "/authorize_interaction?uri='+encodeURIComponent(window.location.href)))");
121 a.addEventListener('click', function (e) {
122 e.preventDefault();
123 a.classList.add('nudge');
124 setTimeout(function () { a.classList.remove('nudge'); }, 600);
125 });
126 }
127
128 function start() { reindex(); apply(); wireBookmarklet(); }
129 start();
130 // Kom je hier via een link binnen de site, dan is de lijst er pas na de swap.
131 document.body.addEventListener('pcmsNav', start);
132})();
Note: See TracBrowser for help on using the repository browser.