Merge remote-tracking branch 'origin/main' into cron-minutely
This commit is contained in:
commit
81f5602ce4
5 changed files with 48 additions and 22 deletions
36
.forgejo/workflows/docker-publish.yml
Normal file
36
.forgejo/workflows/docker-publish.yml
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Forgejo Action — build + publish Docker image to Forgejo container registry on merge to main
|
||||||
|
---
|
||||||
|
name: Docker
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker:
|
||||||
|
runs-on: docker-builder
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Log in to Forgejo Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: forgejo.lorentz.is
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
forgejo.lorentz.is/matt/mailship:${{ github.sha }}
|
||||||
|
forgejo.lorentz.is/matt/mailship:latest
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
Loading…
Reference in a new issue