Changes in src/assets/js/guardian.js [d56d471:1ff801d] in Klonkt
- File:
-
- 1 edited
-
src/assets/js/guardian.js (modified) (10 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/assets/js/guardian.js
rd56d471 r1ff801d 49 49 // child's panel. 50 50 var HELP_TOP = 5; 51 52 /** 53 * Wacht deze hulpvraag nog op iemand? 54 * 55 * EEN plek, met opzet. Dit werd eerst op twee plekken los beslist -- de lijst 56 * en de teller op de wardregel -- en toen de lijst gefilterd werd bleef die 57 * teller een afgehandelde vraag meetellen. Een derde plek komt er vast, en dan 58 * hoort hij hier langs te komen. 59 * 60 * Bij TWIJFEL open: alles wat geen expliciete afsluiting draagt telt als 61 * wachtend. De omgekeerde fout -- iets als afgehandeld tonen dat het niet is -- 62 * is hier de gevaarlijke. 63 */ 64 function helpOpen(h) { return !h || !h.state || h.state.open; } 51 65 52 66 function helpCard(h) { … … 72 86 card.appendChild(a); 73 87 } 88 card.appendChild(helpState(h)); 74 89 return card; 90 } 91 92 /** Hoe lang geleden, grof. Een oppik vervalt niet maar hoort wel te verouderen. */ 93 function ago(ms) { 94 var u = Math.floor(ms / 3600000); 95 if (u < 1) return T.help_just_now || 'just now'; 96 if (u < 24) return (T.help_hours || '{n}h ago').replace('{n}', u); 97 return (T.help_days || '{n}d ago').replace('{n}', Math.floor(u / 24)); 98 } 99 100 /** 101 * De gedeelde staat van een hulpvraag (shaer-lgo): wie er al op af is, of het 102 * is afgesloten, en de twee knoppen. 103 * 104 * De faalstand hier is NIET veilig: 'iedereen denkt dat het geregeld is' is 105 * gevaarlijker dan geen markering. Dus bij twijfel toont dit OPEN, en een oude 106 * oppik verkleurt in plaats van te verdwijnen -- anders ziet een hulpvraag er 107 * onaangeroerd uit terwijl er iemand mee bezig is. 108 */ 109 function helpState(h) { 110 var st = h.state || { open: true, pickedUpBy: [], handled: null, ageMs: null }; 111 var box = el('div', 'g-help-state'); 112 113 if (st.handled) { 114 var wie = st.handled.handle || handleOf(st.handled.uri); 115 box.appendChild(el('div', 'g-help-done', (T.help_handled_by || 'Handled by {who}').replace('{who}', wie))); 116 // Geen terugdraaiknop: leeft de vraag nog, dan wordt hij opnieuw gesteld. 117 box.appendChild(el('p', 'small g-empty', T.help_handled_note || '')); 118 return box; 119 } 120 121 (st.pickedUpBy || []).forEach(function (p) { 122 var line = (T.help_picked_by || '{who} is looking into this').replace('{who}', p.handle || handleOf(p.uri)); 123 box.appendChild(el('div', 'g-help-pick', line)); 124 }); 125 // Oud, maar niet weg. Dit is het verschil tussen 'er is iemand mee bezig' en 126 // 'er was ooit iemand mee bezig'. 127 if (st.ageMs != null && st.ageMs > 3600000) { 128 box.appendChild(el('div', 'g-help-age small', ago(st.ageMs))); 129 } 130 131 var row = el('div', 'row'); 132 var mij = (st.pickedUpBy || []).some(function (p) { return p.uri === S.me; }); 133 if (!mij) { 134 var pick = el('button', 'small', T.help_pick || 'I am on it'); 135 pick.addEventListener('click', function () { markHelp(h, 'pickup', pick); }); 136 row.appendChild(pick); 137 } 138 var done = el('button', 'quiet small', T.help_close || 'Mark handled'); 139 // Een stevige bevestiging, en nooit een window.confirm: dat verstopt een 140 // uitleg achter een OK die mensen wegklikken. Zelfde lijn als het loslaten 141 // van een ward. 142 done.addEventListener('click', function () { 143 done.hidden = true; 144 var ask = el('div', 'g-confirm'); 145 ask.appendChild(el('p', 'small', T.help_close_ask || '')); 146 var ja = el('button', 'small', T.help_close_yes || 'Yes, handled'); 147 ja.addEventListener('click', function () { markHelp(h, 'handled', ja); }); 148 var nee = el('button', 'quiet small', T.release_no || 'No'); 149 nee.addEventListener('click', function () { ask.remove(); done.hidden = false; }); 150 ask.appendChild(ja); ask.appendChild(nee); 151 box.appendChild(ask); 152 }); 153 row.appendChild(done); 154 box.appendChild(row); 155 return box; 156 } 157 158 function markHelp(h, kind, btn) { 159 if (btn) btn.disabled = true; 160 fetch('/guardian/api/help/' + kind, { 161 method: 'POST', headers: { 'Content-Type': 'application/json' }, 162 body: JSON.stringify({ note: h.object_uri, ward: h.actor_uri, site: S.site }), 163 }).then(refresh).catch(function () { if (btn) btn.disabled = false; }); 75 164 } 76 165 … … 78 167 var list = document.getElementById('help-list'); 79 168 list.textContent = ''; 80 var help = S.help || []; 81 help.slice(0, HELP_TOP).forEach(function (h) { list.appendChild(helpCard(h)); }); 82 if (help.length > HELP_TOP) { 83 list.appendChild(el('p', 'g-sec-sub', '+ ' + (help.length - HELP_TOP) + ' — ' + (T.panel_help || ''))); 84 } 169 var alle = S.help || []; 170 // Afgehandeld hoort niet meer in de lijst die om aandacht vraagt, en niet 171 // meer in de teller: anders blijft het cijfer alarm slaan voor iets dat af 172 // is, en neemt een gesloten vraag de ruimte in van een open vraag. 173 var open = alle.filter(helpOpen); 174 var klaar = alle.filter(function (h) { return !helpOpen(h); }); 175 176 open.slice(0, HELP_TOP).forEach(function (h) { list.appendChild(helpCard(h)); }); 177 if (open.length > HELP_TOP) { 178 list.appendChild(el('p', 'g-sec-sub', '+ ' + (open.length - HELP_TOP) + ' — ' + (T.panel_help || ''))); 179 } 180 181 // WEG is niet hetzelfde als AF. Een afgehandelde hulpvraag blijft bestaan -- 182 // er wordt in dit systeem niets herschreven -- maar hij hoort achter een 183 // klik, niet voor je neus. De volledige geschiedenis per kind staat sowieso 184 // in het paneel van dat kind. 185 if (klaar.length) { 186 var doos = el('div', 'g-help-archive'); 187 doos.hidden = true; 188 klaar.forEach(function (h) { doos.appendChild(helpCard(h)); }); 189 var knop = el('button', 'quiet small', (T.help_archive || '{n} handled').replace('{n}', klaar.length)); 190 knop.addEventListener('click', function () { 191 doos.hidden = !doos.hidden; 192 knop.textContent = doos.hidden 193 ? (T.help_archive || '{n} handled').replace('{n}', klaar.length) 194 : (T.help_archive_hide || 'hide'); 195 }); 196 list.appendChild(knop); 197 list.appendChild(doos); 198 } 199 85 200 var badge = document.getElementById('help-count'); 86 badge.textContent = help.length; badge.hidden = help.length === 0; 87 show('help-empty', help.length === 0); 201 badge.textContent = open.length; badge.hidden = open.length === 0; 202 // "Geen hulpverzoeken. Mooi zo." mag ook staan als er wel een archief is: 203 // er wacht dan immers niets. 204 show('help-empty', open.length === 0); 88 205 } 89 206 … … 351 468 } 352 469 470 /** Het woord dat we voor een gate gebruiken; onbekend valt terug op de naam. */ 471 function gateLabel(feature) { 472 return T['gate_' + feature.replace('shaer:', '')] || feature.replace('shaer:', ''); 473 } 474 475 /** 476 * Een rij in het gate-paneel. Toont WAT er geldt, van welk SOORT het is, welke 477 * drempel er hoort en wat er loopt -- en pas daarna een knop, als er iets te 478 * verzetten valt. 479 * 480 * Het soort staat erbij omdat de gates niet hetzelfde werken: een stand is aan 481 * of uit, volgverzoeken zijn een stroom beslissingen, en een overdracht is 482 * onomkeerbaar zodra het kind hem gebruikt. Vier rijen met dezelfde schakelaar 483 * zouden dat verschil wegpoetsen. 484 */ 485 function gateRow(w, g) { 486 var row = el('div', 'g-gate'); 487 var head = el('div', 'g-gate-head'); 488 head.appendChild(el('span', 'g-gate-name', gateLabel(g.feature))); 489 head.appendChild(el('span', 'g-gate-kind', T['gate_kind_' + g.kind] || g.kind)); 490 row.appendChild(head); 491 492 // De stand. NULL is onbekend en dat is iets anders dan uit: bij een ward op 493 // een andere server wordt die kolom daar bijgehouden. 494 var stand = g.value === null || g.value === undefined 495 ? (T.gate_unknown || 'unknown') 496 : (g.value ? (T.prop_on || 'on') : (T.prop_off || 'off')); 497 var meta = el('div', 'g-gate-meta small'); 498 meta.appendChild(el('span', null, stand)); 499 if (g.threshold) { 500 meta.appendChild(el('span', null, (T.gate_threshold || '{need} of {of} guardians') 501 .replace('{need}', g.threshold.need).replace('{of}', g.threshold.of))); 502 } else { 503 // Geen drempel verzinnen die we niet kennen. 504 meta.appendChild(el('span', 'g-dim', T.gate_threshold_unknown || '')); 505 } 506 if (!g.reversible) meta.appendChild(el('span', 'g-gate-warn', T.gate_irreversible || '')); 507 if (g.waiting) { 508 meta.appendChild(el('span', 'g-gate-wait', (T.gate_waiting || '{n} waiting').replace('{n}', g.waiting))); 509 } 510 row.appendChild(meta); 511 512 if (g.proposal) { 513 row.appendChild(el('p', 'small g-prop g-prop-' + g.proposal.status, 514 (T.prop_line || 'Proposal {what} {value}: {status}') 515 .replace('{what}', gateLabel(g.feature)) 516 .replace('{value}', g.proposal.value ? (T.prop_on || 'on') : (T.prop_off || 'off')) 517 .replace('{status}', T['prop_st_' + g.proposal.status] || g.proposal.status))); 518 } 519 if (g.blockedBy) { 520 row.appendChild(el('p', 'small g-empty', (T.gate_blocked || 'needs {what} first') 521 .replace('{what}', gateLabel(g.blockedBy)))); 522 } 523 if (g.adjustable) { 524 row.appendChild(gateButton(w, g.feature, g.value, T.gate_propose || T.embeds_propose, 525 T.prop_on || 'on', T.prop_off || 'off')); 526 } 527 return row; 528 } 529 353 530 function wardPanel(w) { 354 531 var uri = w.other_uri; … … 358 535 var set = el('div', 'g-panel-sec'); 359 536 set.appendChild(el('h3', null, T.settings_title || 'Settings')); 360 var setRow = el('div', 'row'); 361 setRow.appendChild(gateButton(w, 'shaer:externalEmbeds', w.embeds, T.embeds_propose, T.embeds_on, T.embeds_off)); 362 // The heavier sibling (5.6): seeing that a video exists is one decision, 363 // letting a third party's player run inside the app is another. Hidden 364 // only when previews are known-OFF: for a ward on another server the 365 // value is unknown, and unknown is not off. Hiding it there meant a 366 // guardian elsewhere could never even propose playback, which is how a 367 // whole proposal round went into the wrong gate. The ward's server 368 // enforces play-needs-previews at serve time regardless. 369 if (w.embeds !== false) { 370 setRow.appendChild(gateButton(w, 'shaer:externalPlayback', w.playback, T.play_propose, T.play_on, T.play_off)); 371 } 372 set.appendChild(setRow); 373 // What this guardian proposed and how it stands (5.6). This used to be a 374 // button caption that vanished on refresh, so a running decision was 375 // invisible: you could not tell "waiting", "done" and "expired" apart. 376 (w.proposals || []).forEach(function (p) { 377 var what = p.feature === 'shaer:externalPlayback' ? (T.prop_play || 'playback') : (T.prop_embeds || 'link previews'); 378 var line = (T.prop_line || 'Proposal {what} {value}: {status}') 379 .replace('{what}', what) 380 .replace('{value}', p.value ? (T.prop_on || 'on') : (T.prop_off || 'off')) 381 .replace('{status}', T['prop_st_' + p.status] || p.status); 382 set.appendChild(el('p', 'small g-prop g-prop-' + p.status, line)); 383 }); 537 // Een rij per gate, uit de catalogus van de server (shaer-ahy.1). Losse 538 // knoppen lieten een guardian zelf uitzoeken wat er allemaal geldt, en wat 539 // niet verstelbaar is stond nergens -- terwijl dat de helft van het antwoord 540 // is op "wat mag dit kind". 541 (w.gates || []).forEach(function (g) { set.appendChild(gateRow(w, g)); }); 542 // Terugval voor een server die de catalogus nog niet stuurt: dan de twee 543 // knoppen zoals ze waren, zodat een oudere Klonkt niet met een leeg vak zit. 544 if (!(w.gates || []).length) { 545 var setRow = el('div', 'row'); 546 setRow.appendChild(gateButton(w, 'shaer:externalEmbeds', w.embeds, T.embeds_propose, T.embeds_on, T.embeds_off)); 547 if (w.embeds !== false) { 548 setRow.appendChild(gateButton(w, 'shaer:externalPlayback', w.playback, T.play_propose, T.play_on, T.play_off)); 549 } 550 set.appendChild(setRow); 551 (w.proposals || []).forEach(function (p) { 552 var what = p.feature === 'shaer:externalPlayback' ? (T.prop_play || 'playback') : (T.prop_embeds || 'link previews'); 553 set.appendChild(el('p', 'small g-prop g-prop-' + p.status, (T.prop_line || 'Proposal {what} {value}: {status}') 554 .replace('{what}', what).replace('{value}', p.value ? (T.prop_on || 'on') : (T.prop_off || 'off')) 555 .replace('{status}', T['prop_st_' + p.status] || p.status))); 556 }); 557 } 384 558 panel.appendChild(set); 385 559 … … 420 594 T.panel_follow_empty || '', followCard); 421 595 596 // Hier juist de VOLLEDIGE geschiedenis van dit kind -- dat is waar dit 597 // paneel voor is. Wel wat nog wacht bovenaan, want dat is waar je naar zoekt 598 // als je het opent. 422 599 sectionInto(panel, T.panel_help || 'Calls for help', 423 (S.help || []).filter(function (h) { return h.actor_uri === uri; }), 600 (S.help || []).filter(function (h) { return h.actor_uri === uri; }) 601 .sort(function (a, b) { return (helpOpen(b) ? 1 : 0) - (helpOpen(a) ? 1 : 0); }), 424 602 T.panel_help_empty || '', helpCard); 425 603 … … 453 631 actRow.appendChild(rel); 454 632 act.appendChild(actRow); 455 panel.appendChild(act); 633 // Bovenaan, niet onderaan: zwaaien en loslaten zijn de dingen die je DOET. 634 // De rest van het paneel is lezen -- instellingen, wie er nog meer op let, 635 // wat er binnenkwam. Wie het paneel opent om iets te doen hoorde eerst langs 636 // vijf secties te scrollen. 637 panel.insertBefore(act, panel.firstChild); 456 638 return panel; 457 639 } … … 467 649 row.appendChild(el('span', 'who grow', handleOf(uri, w.other_handle))); 468 650 // Counts on the row: whatever is waiting must be visible with the panel shut. 469 var nHelp = (S.help || []).filter(function (h) { return h.actor_uri === uri; }).length; 651 // Alleen wat nog WACHT, zoals het commentaar hierboven al zei: een 652 // afgehandelde vraag hoorde de boei niet te laten staan. 653 var nHelp = (S.help || []).filter(function (h) { return h.actor_uri === uri && helpOpen(h); }).length; 470 654 var nFollow = FOLLOWS.filter(function (f) { return f.wardUri === uri; }).length; 471 655 if (nHelp) row.appendChild(el('span', 'tag help', '🛟 ' + nHelp)); … … 553 737 card.appendChild(head); 554 738 var body = el('div', 'feed-body'); 739 // body_html is de gedeelde note-body-partial, serverside gerenderd: opmaak, 740 // media, quote-kaart en embed, precies als in de Krant en in Berichten. 741 // Valt terug op de kale content voor een client uit de cache. 742 var html = p.body_html || p.content || ''; 555 743 if (p.cw) { 744 // De content warning blijft van de PWA zelf: note-body versluiert alleen 745 // bij nsfw, en een ward-post met alleen een cw hoort hier dicht te staan. 556 746 var d = document.createElement('details'); 557 747 var sum = document.createElement('summary'); sum.textContent = p.cw; d.appendChild(sum); 558 var inner = el('div'); inner.innerHTML = p.content || ''; d.appendChild(inner);748 var inner = el('div'); inner.innerHTML = html; d.appendChild(inner); 559 749 body.appendChild(d); 560 750 } else { 561 body.innerHTML = p.content || ''; // server-sanitized HTML (same as Berichten)751 body.innerHTML = html; // server-sanitized HTML (same as Berichten) 562 752 } 563 753 card.appendChild(body); … … 700 890 701 891 renderAll(); pushState(); loadFeed(); loadFollowReqs(); 702 setInterval(refresh, 45000); // live-ish while open 892 893 // De push die de melding brengt is meteen het teken dat de staat veranderd is. 894 // Daarmee hoeft er geen tweede, open verbinding bij: hetzelfde kanaal doet het 895 // werk, en het werkt ook als de app dicht is. 896 if ('serviceWorker' in navigator) { 897 navigator.serviceWorker.addEventListener('message', function (e) { 898 if (e.data && e.data.klonkt === 'push') refresh(); 899 }); 900 } 901 // Het tikje blijft als vangnet -- niet elke verandering geeft een melding, en 902 // niet iedereen heeft meldingen aanstaan. Maar niet tikken terwijl niemand 903 // kijkt: dat waren verzoeken voor een tabblad op de achtergrond. Bij terugkomen 904 // meteen een keer, want dan is de kans op nieuws het grootst. 905 setInterval(function () { if (!document.hidden) refresh(); }, 45000); 906 document.addEventListener('visibilitychange', function () { if (!document.hidden) refresh(); }); 703 907 } catch (e) { 704 908 fatal((e && e.message) || String(e));
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)