Changeset 5218863 in Klonkt for src/routes/ward.js


Ignore:
Timestamp:
08/12/2026 08:25:10 PM (3 months ago)
Author:
Robin <roboburr@…>
Children:
4bb93aa
Parents:
084b046
Message:

De Ward PWA: het gesprek over de hemel, en de zwaai-hand

Plak 4. Een tik op een gezicht opent nu echt iets.

HET GESPREK is een overlay OVER de levende hemel, geen aparte pagina:
de hemel eronder blijft dezelfde draaiende instantie, en het paneel is
doorschijnend met blur, zoals de glazen bubbels in de app. Dezelfde
bron als de C2S-lezing die Shaer gebruikt (conversationHistory):
inkomend de ap_mentions, uitgaand de eigen directe notes. Openen is
lezen: markRead neemt het maximum, dus een oud toestel kan nooit iets
weer ongelezen maken. Verversen is pollen om de acht seconden zolang
het gesprek open staat; het ritme van een gesprek is geen milliseconde,
en een socket kan later zonder iets te slopen.

DE ZWAAI-HAND hangt in dezelfde hoek als de boei en ze wisselen elkaar
af met de stand: met de kaarten verdwijnt de boei, op de hemel hangt de
hand. Slepen op een gezicht opent het paneel met de drie antwoorden, en
de tekst die je kiest IS het bericht. De treffer is sky.pick(): exact
dezelfde rekensom als een tik, dus mikken gedraagt zich overal gelijk.

Changed files:
src/routes/ward.js

  • /api/thread: beide kanten, cursor, markRead bij openen
  • /api/message: direct bericht of zwaai (shaer:wave reist mee)

src/assets/js/ward-sky.js

  • pick() als publieke mikfunctie

src/assets/js/ward.js

  • thread-overlay met poll, verstuurvak, Escape sluit
  • de hand: slepen, mikken, paneel, versturen

src/views/pages/ward.ejs

  • thread, hand en zwaai-paneel in de schil

src/assets/css/ward.css

  • doorschijnend gesprek, bubbels, hand, paneel

src/services/i18n.js

  • tien sleutels in nl, en, de

test/ward-pwa.test.js

  • het gesprek draagt beide kanten en de zwaai-vlag overleeft; versturen eist een echte ontvanger en echte tekst

remarks: 897 tests groen. Nog niet geport: de (+) voor een nieuw
gesprek, Friends/Orbit-panelen, de composer met media, de mini-speler
en de themakleur; beads volgen. Slepen met een echte vinger is aan
Robin op dev.

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/routes/ward.js

    r084b046 r5218863  
    101101      toCards: i18nT(L, 'ward.to_cards'),
    102102      skyEmpty: i18nT(L, 'ward.sky_empty'),
     103      handHint: i18nT(L, 'ward.hand_hint'),
     104      close: i18nT(L, 'ward.close'),
     105      writeHere: i18nT(L, 'ward.write_here'),
     106      send: i18nT(L, 'ward.send'),
     107      wave: i18nT(L, 'ward.wave'),
     108      waveCall: i18nT(L, 'ward.wave_call'),
     109      waveThanks: i18nT(L, 'ward.wave_thanks'),
     110      notNow: i18nT(L, 'ward.not_now'),
     111      waveTo: i18nT(L, 'ward.wave_to'),
    103112    },
    104113  };
    … …  
    254263});
    255264
     265/**
     266 * Eén gesprek, beide kanten, nieuwste eerst, met een cursor voor "meer".
     267 *
     268 * Dezelfde bron als de C2S-lezing die Shaer gebruikt (conversationHistory):
     269 * inkomend zijn de ap_mentions, uitgaand de eigen directe notes. De body komt
     270 * af van de server, net als in de feed; de client hoeft alleen bubbels te
     271 * tekenen en te weten welke kant elk bericht op wijst.
     272 */
     273router.get('/api/thread', requireAuth, (req, res) => {
     274  const site = siteForUser(req);
     275  if (!site) return res.status(404).json({ error: 'no_site' });
     276  const other = String(req.query.with || '');
     277  if (!/^https?:\/\//i.test(other)) return res.status(400).json({ error: 'with_must_be_actor_uri' });
     278  const L = resolveLang(req);
     279  const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '');
     280
     281  const page = AP.conversationHistory(site.slug, other, {
     282    before: req.query.before ? String(req.query.before) : null,
     283    limit: req.query.limit,
     284  });
     285  const inRefs = page.rows.filter((r) => r.direction === 'in').map((r) => r.ref);
     286  const inMap = new Map(AP.messageRowsByUri(site.slug, inRefs).map((m) => [m.object_uri, m]));
     287
     288  const items = page.rows.map((r) => {
     289    if (r.direction === 'in') {
     290      const m = inMap.get(r.ref);
     291      if (!m) return null;
     292      return {
     293        dir: 'in', id: m.object_uri,
     294        whenText: formatDateTime(m.published || m.created_at),
     295        bodyHtml: renderNoteBody(m, L),
     296        wave: !!m.wave, help: !!m.help_request,
     297      };
     298    }
     299    const n = AP.getOutboxNote(base, r.ref);
     300    if (!n) return null;
     301    return {
     302      dir: 'out', id: n.id,
     303      whenText: formatDateTime(n.published),
     304      // Eigen notes zijn door onszelf gebouwd en gesaniteerd bij het maken.
     305      bodyHtml: n.content || '',
     306      wave: n['shaer:wave'] === true,
     307    };
     308  }).filter(Boolean);
     309
     310  // Openen is lezen. De markering is optellend (maximum), dus een oud toestel
     311  // kan nooit iets weer ongelezen maken.
     312  const nieuwsteIn = items.find((i) => i.dir === 'in');
     313  if (nieuwsteIn) { try { AP.markRead(site.slug, nieuwsteIn.id); } catch { /* lezen faalt stil */ } }
     314
     315  res.json({ items, more: page.more, oldest: page.oldest });
     316});
     317
     318/**
     319 * Een bericht in een gesprek, of een zwaai (§5, shaer:wave).
     320 *
     321 * Dezelfde draad als de boei (deliverDirectNote): een direct bericht aan één
     322 * persoon, nooit een post, nooit boostbaar. De wave-vlag reist mee zodat de
     323 * andere kant hem als zwaai toont en niet als kale tekst.
     324 */
     325router.post('/api/message', requireAuth, express.json({ limit: '8kb' }), async (req, res) => {
     326  const site = siteForUser(req);
     327  if (!site) return res.status(404).json({ error: 'no_site' });
     328  const to = String(req.body?.to || '').trim();
     329  const text = String(req.body?.text || '').trim().slice(0, 2000);
     330  if (!/^https?:\/\//i.test(to)) return res.status(400).json({ error: 'to_must_be_actor_uri' });
     331  if (!text) return res.status(400).json({ error: 'empty' });
     332
     333  const sent = await Guardianship.deliverDirectNote(site, {
     334    recipients: [to],
     335    text,
     336    language: site.language || 'nl',
     337    wave: req.body?.wave === true,
     338  }).catch(() => null);
     339  if (!sent) return res.status(502).json({ error: 'not_delivered' });
     340  res.json({ ok: true });
     341});
     342
    256343function pwaAsset(rel, type) {
    257344  return (req, res) => {
Note: See TracChangeset for help on using the changeset viewer.