Merge pull request 'digest email: replace reply/reaction counts with posting relay URL' (#27) from mailship-ihb-digest-email-replace-reply-reaction-coun-281 into main
Some checks are pending
Docker / docker (push) Waiting to run
CI / checks (push) Successful in 38s

Reviewed-on: #27
Reviewed-by: matt <matt@lorentz.is>
This commit is contained in:
matt 2026-09-22 14:58:00 +00:00
commit 27bb4a5342
4 changed files with 12 additions and 22 deletions

View file

@ -24,8 +24,7 @@ const sample = {
Icon: 'https://i.pravatar.cc/150?img=32', Icon: 'https://i.pravatar.cc/150?img=32',
Name: 'carol', Name: 'carol',
Content: '<p>Does anyone know how relay-based groups work?</p>', Content: '<p>Does anyone know how relay-based groups work?</p>',
Replies: 5, RelayUrl: 'relay.damus.io',
Reactions: 8,
}, },
{ {
Link: 'https://app.flotilla.social/spaces/nos.lol/community?at=1700000000', Link: 'https://app.flotilla.social/spaces/nos.lol/community?at=1700000000',
@ -34,8 +33,7 @@ const sample = {
Name: 'bob', Name: 'bob',
Content: Content:
'<p>Excited to share our new community space on Flotilla! <a href="https://flotilla.social">Check it out</a>.</p>', '<p>Excited to share our new community space on Flotilla! <a href="https://flotilla.social">Check it out</a>.</p>',
Replies: 14, RelayUrl: 'nos.lol',
Reactions: 32,
}, },
], ],
unsubscribeUrl: 'https://app.flotilla.social/unsubscribe?token=abc123', unsubscribeUrl: 'https://app.flotilla.social/unsubscribe?token=abc123',

View file

@ -1,6 +1,5 @@
import { neventEncode, decode } from 'nostr-tools/nip19' import { neventEncode, decode } from 'nostr-tools/nip19'
import { import {
spec,
sortBy, sortBy,
groupBy, groupBy,
displayList, displayList,
@ -10,7 +9,6 @@ import { parse, truncate, renderAsHtml } from '@welshman/content'
import { import {
TrustedEvent, TrustedEvent,
normalizeRelayUrl, normalizeRelayUrl,
getParentId,
NOTE, NOTE,
COMMENT, COMMENT,
REACTION, REACTION,
@ -50,23 +48,23 @@ export class Digest {
const getEventVariables = (event: TrustedEvent) => { const getEventVariables = (event: TrustedEvent) => {
const parsed = truncate(parse(event), { minLength: 400, maxLength: 800, mediaLength: 50 }) const parsed = truncate(parse(event), { minLength: 400, maxLength: 800, mediaLength: 50 })
const relayUrl = data.relayByEventId.get(event.id)
return { return {
Link: buildLink(event, handler, data.relayByEventId.get(event.id)), Link: buildLink(event, handler, relayUrl),
Timestamp: formatter.format(secondsToDate(event.created_at)), Timestamp: formatter.format(secondsToDate(event.created_at)),
Icon: profilesByPubkey.get().get(event.pubkey)?.picture, Icon: profilesByPubkey.get().get(event.pubkey)?.picture,
Name: displayProfileByPubkey(event.pubkey), Name: displayProfileByPubkey(event.pubkey),
Content: renderAsHtml(parsed, { createElement, renderEntity }).toString(), Content: renderAsHtml(parsed, { createElement, renderEntity }).toString(),
Replies: RelayUrl: relayUrl
repliesByParentId.get(event.id)?.filter((e) => [COMMENT, NOTE].includes(e.kind)) ? normalizeRelayUrl(relayUrl).replace(/^wss:\/\//, '').replace(/\/$/, '')
?.length || 0, : '',
Reactions: repliesByParentId.get(event.id)?.filter(spec({ kind: REACTION }))?.length || 0,
} }
} }
const { events, context } = data const { events } = data
const formatter = getFormatter() const formatter = getFormatter()
const handler = await this.loadHandler() const handler = await this.loadHandler()
const repliesByParentId = groupBy(getParentId, context)
const eventsByPubkey = groupBy((e) => e.pubkey, events) const eventsByPubkey = groupBy((e) => e.pubkey, events)
const userProfile = profilesByPubkey.get().get(this.sub.pubkey) const userProfile = profilesByPubkey.get().get(this.sub.pubkey)
const sorted = sortBy((e) => e.created_at, events).slice(0, 100) const sorted = sortBy((e) => e.created_at, events).slice(0, 100)

View file

@ -64,10 +64,7 @@
<div class="event-content">{{{Content}}}</div> <div class="event-content">{{{Content}}}</div>
<div class="event-stats"> <div class="event-stats">
<span class="stat-item"> <span class="stat-item">
{{Replies}} replies Posted to {{RelayUrl}}
</span>
<span class="stat-item">
{{Reactions}} reactions
</span> </span>
</div> </div>
</div> </div>

View file

@ -1,6 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest' import { describe, it, expect, vi, beforeEach } from 'vitest'
import type { TrustedEvent } from '@welshman/util' import type { TrustedEvent } from '@welshman/util'
import { spec } from '@welshman/lib'
// ── Shared state accessible from both vi.mock factories and test body ────── // ── Shared state accessible from both vi.mock factories and test body ──────
const mockRepo = vi.hoisted(() => { const mockRepo = vi.hoisted(() => {
@ -159,9 +158,7 @@ describe('digest reply/reaction stats', () => {
expect(latest.length).toBeGreaterThanOrEqual(1) expect(latest.length).toBeGreaterThanOrEqual(1)
const parentEntry = latest[0] const parentEntry = latest[0]
// When context includes reply/reaction events loaded from the repository, // RelayUrl should be the relay domain stripped of protocol and trailing slash
// Replies should be >= 1 and Reactions >= 1 expect(parentEntry.RelayUrl).toBe('relay.example.com')
expect(parentEntry.Replies).toBeGreaterThanOrEqual(1)
expect(parentEntry.Reactions).toBeGreaterThanOrEqual(1)
}) })
}) })