Browse Source

refine engagement and toggle on publications

imwald
Silberengel 1 week ago
parent
commit
18589c30e8
  1. 18
      src/components/Library/LibraryPublicationGrid.tsx
  2. 8
      src/hooks/useLibraryPublications.ts
  3. 2
      src/i18n/locales/de.ts
  4. 2
      src/i18n/locales/en.ts
  5. 71
      src/lib/library-publication-index.test.ts
  6. 202
      src/lib/library-publication-index.ts

18
src/components/Library/LibraryPublicationGrid.tsx

@ -5,7 +5,7 @@ import type { LibraryPublicationEntry } from '@/lib/library-publication-index'
import { isBooklistNip32Label } from '@/lib/nip32-label' import { isBooklistNip32Label } from '@/lib/nip32-label'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { useScreenSize } from '@/providers/ScreenSizeProvider' import { useScreenSize } from '@/providers/ScreenSizeProvider'
import { BookOpen, Highlighter, MessageSquare, Tag } from 'lucide-react' import { BookOpen, Bookmark, Highlighter, MessageSquare, Pin, Tag } from 'lucide-react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
function LabelBadgeIcon({ name }: { name: string }) { function LabelBadgeIcon({ name }: { name: string }) {
@ -23,7 +23,9 @@ function EngagementBadges({ entry }: { entry: LibraryPublicationEntry }) {
otherLabels.length === 0 && otherLabels.length === 0 &&
!entry.hasLabel && !entry.hasLabel &&
!entry.hasComment && !entry.hasComment &&
!entry.hasHighlight !entry.hasHighlight &&
!entry.hasBookmark &&
!entry.hasPin
) { ) {
return null return null
} }
@ -83,6 +85,18 @@ function EngagementBadges({ entry }: { entry: LibraryPublicationEntry }) {
{t('Library badge highlight')} {t('Library badge highlight')}
</span> </span>
)} )}
{entry.hasBookmark && (
<span className="inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-xs text-muted-foreground">
<Bookmark className="size-3" aria-hidden />
{t('Library badge bookmark')}
</span>
)}
{entry.hasPin && (
<span className="inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-xs text-muted-foreground">
<Pin className="size-3" aria-hidden />
{t('Library badge pin')}
</span>
)}
</div> </div>
) )
} }

8
src/hooks/useLibraryPublications.ts

@ -40,7 +40,13 @@ const EMPTY_ENGAGEMENT: PublicationEngagementMaps = {
myHighlightAddresses: new Set(), myHighlightAddresses: new Set(),
myHighlightEventIds: new Set(), myHighlightEventIds: new Set(),
commentAddresses: new Set(), commentAddresses: new Set(),
highlightAddresses: new Set() commentEventIds: new Set(),
highlightAddresses: new Set(),
highlightEventIds: new Set(),
bookmarkAddresses: new Set(),
bookmarkEventIds: new Set(),
pinAddresses: new Set(),
pinEventIds: new Set()
} }
const EMPTY_BOOKLIST_TARGETS = { addresses: new Set<string>(), eventIds: new Set<string>() } const EMPTY_BOOKLIST_TARGETS = { addresses: new Set<string>(), eventIds: new Set<string>() }

2
src/i18n/locales/de.ts

@ -1664,6 +1664,8 @@ export default {
'Library badge label': 'Label', 'Library badge label': 'Label',
'Library badge comment': 'Kommentar', 'Library badge comment': 'Kommentar',
'Library badge highlight': 'Markierung', 'Library badge highlight': 'Markierung',
'Library badge bookmark': 'Lesezeichen',
'Library badge pin': 'Angepinnt',
'Publication version': 'v{{version}}', 'Publication version': 'v{{version}}',
'Publication sections_one': '{{count}} Abschnitt', 'Publication sections_one': '{{count}} Abschnitt',
'Publication sections_other': '{{count}} Abschnitte', 'Publication sections_other': '{{count}} Abschnitte',

2
src/i18n/locales/en.ts

@ -1689,6 +1689,8 @@ export default {
'Library badge my booklist': 'On my booklist', 'Library badge my booklist': 'On my booklist',
'Library badge comment': 'Comment', 'Library badge comment': 'Comment',
'Library badge highlight': 'Highlight', 'Library badge highlight': 'Highlight',
'Library badge bookmark': 'Bookmark',
'Library badge pin': 'Pin',
'Add to my booklist': 'Add to my booklist', 'Add to my booklist': 'Add to my booklist',
'Remove from my booklist': 'Remove from my booklist', 'Remove from my booklist': 'Remove from my booklist',
'Add to my booklist failed': 'Failed to add to booklist', 'Add to my booklist failed': 'Failed to add to booklist',

71
src/lib/library-publication-index.test.ts

@ -39,6 +39,65 @@ function indexEvent(d: string, aTags: string[], id = d.padEnd(64, '0').slice(0,
} }
describe('library-publication-index', () => { describe('library-publication-index', () => {
it('matches comments and highlights by root event id', () => {
const root = indexEvent('book', [`30041:${PK}:intro`])
const indexByAddress = buildIndexByAddress([root])
const comment: Event = {
id: '8'.repeat(64),
kind: ExtendedKind.COMMENT,
pubkey: 'f'.repeat(64),
created_at: 50,
content: 'nice book',
tags: [['e', root.id]],
sig: 'e'.repeat(128)
}
const highlight: Event = {
id: '9'.repeat(64),
kind: kinds.Highlights,
pubkey: 'f'.repeat(64),
created_at: 50,
content: 'quote',
tags: [['e', root.id]],
sig: 'e'.repeat(128)
}
const engagement = buildEngagementMapsFromEvents([], [comment], [highlight])
const engaged = filterEngagedPublications([root], indexByAddress, engagement)
expect(engaged).toHaveLength(1)
expect(engaged[0].hasComment).toBe(true)
expect(engaged[0].hasHighlight).toBe(true)
})
it('matches bookmark and pin lists from any author', () => {
const rootAddr = `30040:${PK}:book`
const root = indexEvent('book', [`30041:${PK}:intro`])
const indexByAddress = buildIndexByAddress([root])
const bookmarkList: Event = {
id: 'b'.repeat(64),
kind: kinds.BookmarkList,
pubkey: 'f'.repeat(64),
created_at: 100,
content: '',
tags: [['a', rootAddr]],
sig: 'd'.repeat(128)
}
const pinList: Event = {
id: 'p'.repeat(64),
kind: 10001,
pubkey: 'e'.repeat(64),
created_at: 100,
content: '',
tags: [['e', root.id]],
sig: 'd'.repeat(128)
}
const engagement = buildEngagementMapsFromEvents([], [], [], undefined, undefined, null, [
bookmarkList
], [pinList])
const engaged = filterEngagedPublications([root], indexByAddress, engagement)
expect(engaged).toHaveLength(1)
expect(engaged[0].hasBookmark).toBe(true)
expect(engaged[0].hasPin).toBe(true)
})
it('matches engagement on nested 30041 addresses', () => { it('matches engagement on nested 30041 addresses', () => {
const leafAddr = `30041:${PK}:chapter-1` const leafAddr = `30041:${PK}:chapter-1`
const childAddr = `30040:${PK}:part-1` const childAddr = `30040:${PK}:part-1`
@ -147,6 +206,8 @@ describe('library-publication-index', () => {
hasMyHighlight: false, hasMyHighlight: false,
hasComment: false, hasComment: false,
hasHighlight: false, hasHighlight: false,
hasBookmark: false,
hasPin: false,
engagementCount: 1 engagementCount: 1
} }
] ]
@ -358,6 +419,8 @@ describe('library-publication-index', () => {
hasMyHighlight: false, hasMyHighlight: false,
hasComment: false, hasComment: false,
hasHighlight: false, hasHighlight: false,
hasBookmark: false,
hasPin: false,
engagementCount: 0 engagementCount: 0
}, },
{ {
@ -370,6 +433,8 @@ describe('library-publication-index', () => {
hasMyHighlight: false, hasMyHighlight: false,
hasComment: false, hasComment: false,
hasHighlight: false, hasHighlight: false,
hasBookmark: false,
hasPin: false,
engagementCount: 0 engagementCount: 0
}, },
{ {
@ -382,6 +447,8 @@ describe('library-publication-index', () => {
hasMyHighlight: false, hasMyHighlight: false,
hasComment: true, hasComment: true,
hasHighlight: false, hasHighlight: false,
hasBookmark: false,
hasPin: false,
engagementCount: 1 engagementCount: 1
}, },
{ {
@ -394,6 +461,8 @@ describe('library-publication-index', () => {
hasMyHighlight: false, hasMyHighlight: false,
hasComment: false, hasComment: false,
hasHighlight: false, hasHighlight: false,
hasBookmark: false,
hasPin: false,
engagementCount: 0 engagementCount: 0
} }
] ]
@ -479,6 +548,8 @@ describe('library-publication-index', () => {
hasMyHighlight: false, hasMyHighlight: false,
hasComment: false, hasComment: false,
hasHighlight: false, hasHighlight: false,
hasBookmark: false,
hasPin: false,
engagementCount: 0 engagementCount: 0
} }
const filtered = filterLibraryPublicationsByUser([entry], viewerPk, { const filtered = filterLibraryPublicationsByUser([entry], viewerPk, {

202
src/lib/library-publication-index.ts

@ -54,6 +54,8 @@ const ENGAGEMENT_FETCH_TIMEOUT_MS = 25_000
const LIBRARY_SEARCH_READING_CACHE_LIMIT = 200 const LIBRARY_SEARCH_READING_CACHE_LIMIT = 200
export const LIBRARY_RELAY_SEARCH_LIMIT = 100 export const LIBRARY_RELAY_SEARCH_LIMIT = 100
const LIBRARY_RELAY_SEARCH_TIMEOUT_MS = 28_000 const LIBRARY_RELAY_SEARCH_TIMEOUT_MS = 28_000
/** NIP-51 pin list (kind 10001). */
const PIN_LIST_KIND = 10001
const QUERY_OPTS = { const QUERY_OPTS = {
globalTimeout: 18_000, globalTimeout: 18_000,
eoseTimeout: 3_000, eoseTimeout: 3_000,
@ -74,7 +76,13 @@ export type PublicationEngagementMaps = {
myHighlightAddresses: Set<string> myHighlightAddresses: Set<string>
myHighlightEventIds: Set<string> myHighlightEventIds: Set<string>
commentAddresses: Set<string> commentAddresses: Set<string>
commentEventIds: Set<string>
highlightAddresses: Set<string> highlightAddresses: Set<string>
highlightEventIds: Set<string>
bookmarkAddresses: Set<string>
bookmarkEventIds: Set<string>
pinAddresses: Set<string>
pinEventIds: Set<string>
} }
export type LibraryPublicationEntry = { export type LibraryPublicationEntry = {
@ -88,6 +96,8 @@ export type LibraryPublicationEntry = {
hasMyHighlight: boolean hasMyHighlight: boolean
hasComment: boolean hasComment: boolean
hasHighlight: boolean hasHighlight: boolean
hasBookmark: boolean
hasPin: boolean
engagementCount: number engagementCount: number
} }
@ -120,9 +130,15 @@ function librarySearchFingerprint(context: LibrarySearchContext): string {
? engagement.labelAddresses.size + ? engagement.labelAddresses.size +
engagement.labelEventIds.size + engagement.labelEventIds.size +
engagement.commentAddresses.size + engagement.commentAddresses.size +
engagement.commentEventIds.size +
engagement.highlightAddresses.size + engagement.highlightAddresses.size +
engagement.highlightEventIds.size +
engagement.booklistAddresses.size + engagement.booklistAddresses.size +
engagement.booklistEventIds.size + engagement.booklistEventIds.size +
engagement.bookmarkAddresses.size +
engagement.bookmarkEventIds.size +
engagement.pinAddresses.size +
engagement.pinEventIds.size +
engagement.myBooklistAddresses.size + engagement.myBooklistAddresses.size +
engagement.myBooklistEventIds.size + engagement.myBooklistEventIds.size +
engagement.myCommentAddresses.size + engagement.myCommentAddresses.size +
@ -344,7 +360,9 @@ export function buildEngagementMapsFromEvents(
highlights: Event[], highlights: Event[],
targetAddresses?: Set<string>, targetAddresses?: Set<string>,
targetEventIds?: Set<string>, targetEventIds?: Set<string>,
viewerPubkey?: string | null viewerPubkey?: string | null,
bookmarkLists: Event[] = [],
pinLists: Event[] = []
): PublicationEngagementMaps { ): PublicationEngagementMaps {
const labelAddresses = new Set<string>() const labelAddresses = new Set<string>()
const labelEventIds = new Set<string>() const labelEventIds = new Set<string>()
@ -359,7 +377,13 @@ export function buildEngagementMapsFromEvents(
const myHighlightAddresses = new Set<string>() const myHighlightAddresses = new Set<string>()
const myHighlightEventIds = new Set<string>() const myHighlightEventIds = new Set<string>()
const commentAddresses = new Set<string>() const commentAddresses = new Set<string>()
const commentEventIds = new Set<string>()
const highlightAddresses = new Set<string>() const highlightAddresses = new Set<string>()
const highlightEventIds = new Set<string>()
const bookmarkAddresses = new Set<string>()
const bookmarkEventIds = new Set<string>()
const pinAddresses = new Set<string>()
const pinEventIds = new Set<string>()
const addressMatches = (addr: string) => !targetAddresses || targetAddresses.has(addr) const addressMatches = (addr: string) => !targetAddresses || targetAddresses.has(addr)
const eventIdMatches = (id: string) => !targetEventIds || targetEventIds.has(id.toLowerCase()) const eventIdMatches = (id: string) => !targetEventIds || targetEventIds.has(id.toLowerCase())
@ -407,8 +431,9 @@ export function buildEngagementMapsFromEvents(
commentAddresses.add(tag[1]) commentAddresses.add(tag[1])
if (isViewerEvent) myCommentAddresses.add(tag[1]) if (isViewerEvent) myCommentAddresses.add(tag[1])
} }
if (tag[0] === 'e' && tag[1] && eventIdMatches(tag[1]) && isViewerEvent) { if (tag[0] === 'e' && tag[1] && eventIdMatches(tag[1])) {
myCommentEventIds.add(tag[1].toLowerCase()) commentEventIds.add(tag[1].toLowerCase())
if (isViewerEvent) myCommentEventIds.add(tag[1].toLowerCase())
} }
} }
} }
@ -420,8 +445,31 @@ export function buildEngagementMapsFromEvents(
highlightAddresses.add(tag[1]) highlightAddresses.add(tag[1])
if (isViewerEvent) myHighlightAddresses.add(tag[1]) if (isViewerEvent) myHighlightAddresses.add(tag[1])
} }
if (tag[0] === 'e' && tag[1] && eventIdMatches(tag[1]) && isViewerEvent) { if (tag[0] === 'e' && tag[1] && eventIdMatches(tag[1])) {
myHighlightEventIds.add(tag[1].toLowerCase()) highlightEventIds.add(tag[1].toLowerCase())
if (isViewerEvent) myHighlightEventIds.add(tag[1].toLowerCase())
}
}
}
for (const ev of bookmarkLists) {
for (const tag of ev.tags) {
if (tag[0] === 'a' && tag[1] && addressMatches(tag[1])) {
bookmarkAddresses.add(tag[1])
}
if (tag[0] === 'e' && tag[1] && eventIdMatches(tag[1])) {
bookmarkEventIds.add(tag[1].toLowerCase())
}
}
}
for (const ev of pinLists) {
for (const tag of ev.tags) {
if (tag[0] === 'a' && tag[1] && addressMatches(tag[1])) {
pinAddresses.add(tag[1])
}
if (tag[0] === 'e' && tag[1] && eventIdMatches(tag[1])) {
pinEventIds.add(tag[1].toLowerCase())
} }
} }
} }
@ -440,7 +488,13 @@ export function buildEngagementMapsFromEvents(
myHighlightAddresses, myHighlightAddresses,
myHighlightEventIds, myHighlightEventIds,
commentAddresses, commentAddresses,
highlightAddresses commentEventIds,
highlightAddresses,
highlightEventIds,
bookmarkAddresses,
bookmarkEventIds,
pinAddresses,
pinEventIds
} }
} }
@ -500,13 +554,34 @@ export async function fetchPublicationEngagementMaps(
const commentWsFilters = addressChunks.map( const commentWsFilters = addressChunks.map(
(chunk): Filter => ({ kinds: [ExtendedKind.COMMENT], '#A': chunk, limit: chunk.length * 12 }) (chunk): Filter => ({ kinds: [ExtendedKind.COMMENT], '#A': chunk, limit: chunk.length * 12 })
) )
const commentEventFilters = eventIdChunks.map(
(chunk): Filter => ({ kinds: [ExtendedKind.COMMENT], '#e': chunk, limit: chunk.length * 12 })
)
const highlightEventFilters = eventIdChunks.map(
(chunk): Filter => ({ kinds: [kinds.Highlights], '#e': chunk, limit: chunk.length * 12 })
)
const bookmarkAddressFilters = addressChunks.map(
(chunk): Filter => ({ kinds: [kinds.BookmarkList], '#a': chunk, limit: chunk.length * 8 })
)
const bookmarkEventFilters = eventIdChunks.map(
(chunk): Filter => ({ kinds: [kinds.BookmarkList], '#e': chunk, limit: chunk.length * 8 })
)
const pinAddressFilters = addressChunks.map(
(chunk): Filter => ({ kinds: [PIN_LIST_KIND], '#a': chunk, limit: chunk.length * 8 })
)
const pinEventFilters = eventIdChunks.map(
(chunk): Filter => ({ kinds: [PIN_LIST_KIND], '#e': chunk, limit: chunk.length * 8 })
)
const highlightPromise = Promise.all([ const highlightPromise = Promise.all([
useWsEngagement && highlightFilters.length > 0 useWsEngagement && highlightFilters.length > 0
? queryService.fetchEvents(wsRelays, highlightFilters, QUERY_OPTS) ? queryService.fetchEvents(wsRelays, highlightFilters, QUERY_OPTS)
: Promise.resolve([] as Event[]), : Promise.resolve([] as Event[]),
useWsEngagement && highlightEventFilters.length > 0
? queryService.fetchEvents(wsRelays, highlightEventFilters, QUERY_OPTS)
: Promise.resolve([] as Event[]),
fetchHttpEngagementByAddresses(httpRelays, kinds.Highlights, '#a', addressChunks) fetchHttpEngagementByAddresses(httpRelays, kinds.Highlights, '#a', addressChunks)
]).then(([scoped, bulk]) => dedupeEventsById([...scoped, ...bulk])) ]).then(([scoped, byEvent, bulk]) => dedupeEventsById([...scoped, ...byEvent, ...bulk]))
const labelPromise = Promise.all([ const labelPromise = Promise.all([
useWsEngagement && labelAddressFilters.length > 0 useWsEngagement && labelAddressFilters.length > 0
@ -522,13 +597,38 @@ export async function fetchPublicationEngagementMaps(
useWsEngagement && commentWsFilters.length > 0 useWsEngagement && commentWsFilters.length > 0
? queryService.fetchEvents(wsRelays, commentWsFilters, QUERY_OPTS) ? queryService.fetchEvents(wsRelays, commentWsFilters, QUERY_OPTS)
: Promise.resolve([] as Event[]), : Promise.resolve([] as Event[]),
useWsEngagement && commentEventFilters.length > 0
? queryService.fetchEvents(wsRelays, commentEventFilters, QUERY_OPTS)
: Promise.resolve([] as Event[]),
fetchHttpEngagementByAddresses(httpRelays, ExtendedKind.COMMENT, '#A', addressChunks) fetchHttpEngagementByAddresses(httpRelays, ExtendedKind.COMMENT, '#A', addressChunks)
]).then(([scoped, bulk]) => dedupeEventsById([...scoped, ...bulk])) ]).then(([scoped, byEvent, bulk]) => dedupeEventsById([...scoped, ...byEvent, ...bulk]))
const bookmarkPromise = Promise.all([
useWsEngagement && bookmarkAddressFilters.length > 0
? queryService.fetchEvents(wsRelays, bookmarkAddressFilters, QUERY_OPTS)
: Promise.resolve([] as Event[]),
useWsEngagement && bookmarkEventFilters.length > 0
? queryService.fetchEvents(wsRelays, bookmarkEventFilters, QUERY_OPTS)
: Promise.resolve([] as Event[]),
fetchHttpEngagementByAddresses(httpRelays, kinds.BookmarkList, '#a', addressChunks)
]).then(([byAddress, byEvent, bulk]) => dedupeEventsById([...byAddress, ...byEvent, ...bulk]))
const [highlights, labels, comments] = await Promise.all([ const pinPromise = Promise.all([
useWsEngagement && pinAddressFilters.length > 0
? queryService.fetchEvents(wsRelays, pinAddressFilters, QUERY_OPTS)
: Promise.resolve([] as Event[]),
useWsEngagement && pinEventFilters.length > 0
? queryService.fetchEvents(wsRelays, pinEventFilters, QUERY_OPTS)
: Promise.resolve([] as Event[]),
fetchHttpEngagementByAddresses(httpRelays, PIN_LIST_KIND, '#a', addressChunks)
]).then(([byAddress, byEvent, bulk]) => dedupeEventsById([...byAddress, ...byEvent, ...bulk]))
const [highlights, labels, comments, bookmarkLists, pinLists] = await Promise.all([
highlightPromise, highlightPromise,
labelPromise, labelPromise,
commentPromise commentPromise,
bookmarkPromise,
pinPromise
]) ])
return buildEngagementMapsFromEvents( return buildEngagementMapsFromEvents(
@ -537,7 +637,9 @@ export async function fetchPublicationEngagementMaps(
dedupeEventsById(highlights), dedupeEventsById(highlights),
targetAddresses, targetAddresses,
targetEventIds, targetEventIds,
options?.viewerPubkey options?.viewerPubkey,
dedupeEventsById(bookmarkLists),
dedupeEventsById(pinLists)
) )
} }
@ -546,14 +648,56 @@ function addressHasEngagement(
eventId: string | undefined, eventId: string | undefined,
maps: PublicationEngagementMaps maps: PublicationEngagementMaps
): { hasLabel: boolean; hasComment: boolean; hasHighlight: boolean } { ): { hasLabel: boolean; hasComment: boolean; hasHighlight: boolean } {
const idLower = eventId?.toLowerCase()
const hasLabel = const hasLabel =
maps.labelAddresses.has(address) || maps.labelAddresses.has(address) || (idLower ? maps.labelEventIds.has(idLower) : false)
(eventId ? maps.labelEventIds.has(eventId.toLowerCase()) : false) const hasComment =
const hasComment = maps.commentAddresses.has(address) maps.commentAddresses.has(address) || (idLower ? maps.commentEventIds.has(idLower) : false)
const hasHighlight = maps.highlightAddresses.has(address) const hasHighlight =
maps.highlightAddresses.has(address) || (idLower ? maps.highlightEventIds.has(idLower) : false)
return { hasLabel, hasComment, hasHighlight } return { hasLabel, hasComment, hasHighlight }
} }
function collectBookmarkPinFlagsForTarget(
address: string,
eventId: string | undefined,
maps: PublicationEngagementMaps
): { hasBookmark: boolean; hasPin: boolean } {
const idLower = eventId?.toLowerCase()
return {
hasBookmark:
maps.bookmarkAddresses.has(address) || (idLower ? maps.bookmarkEventIds.has(idLower) : false),
hasPin: maps.pinAddresses.has(address) || (idLower ? maps.pinEventIds.has(idLower) : false)
}
}
function targetHasPublicationEngagement(
flags: { hasLabel: boolean; hasComment: boolean; hasHighlight: boolean },
booklistFlags: { hasBooklistLabel: boolean },
bookmarkPinFlags: { hasBookmark: boolean; hasPin: boolean }
): boolean {
return (
flags.hasLabel ||
flags.hasComment ||
flags.hasHighlight ||
booklistFlags.hasBooklistLabel ||
bookmarkPinFlags.hasBookmark ||
bookmarkPinFlags.hasPin
)
}
/** True when a library row has any engagement signal from any author. */
export function publicationEntryHasEngagement(entry: LibraryPublicationEntry): boolean {
return (
entry.hasLabel ||
entry.hasBooklistLabel ||
entry.hasComment ||
entry.hasHighlight ||
entry.hasBookmark ||
entry.hasPin
)
}
function collectLabelNamesForTarget( function collectLabelNamesForTarget(
address: string, address: string,
eventId: string | undefined, eventId: string | undefined,
@ -617,6 +761,8 @@ export function buildLibraryPublicationEntry(
let hasLabel = false let hasLabel = false
let hasComment = false let hasComment = false
let hasHighlight = false let hasHighlight = false
let hasBookmark = false
let hasPin = false
let hasBooklistLabel = false let hasBooklistLabel = false
let hasMyBooklistLabel = false let hasMyBooklistLabel = false
let hasMyComment = false let hasMyComment = false
@ -628,6 +774,7 @@ export function buildLibraryPublicationEntry(
const indexed = indexByAddress.get(addr) const indexed = indexByAddress.get(addr)
const flags = addressHasEngagement(addr, indexed?.id, engagement) const flags = addressHasEngagement(addr, indexed?.id, engagement)
const booklistFlags = collectBooklistFlagsForTarget(addr, indexed?.id, engagement) const booklistFlags = collectBooklistFlagsForTarget(addr, indexed?.id, engagement)
const bookmarkPinFlags = collectBookmarkPinFlagsForTarget(addr, indexed?.id, engagement)
const myFlags = collectMyEngagementFlagsForTarget(addr, indexed?.id, engagement) const myFlags = collectMyEngagementFlagsForTarget(addr, indexed?.id, engagement)
if (flags.hasLabel) { if (flags.hasLabel) {
hasLabel = true hasLabel = true
@ -639,15 +786,20 @@ export function buildLibraryPublicationEntry(
if (myFlags.hasMyHighlight) hasMyHighlight = true if (myFlags.hasMyHighlight) hasMyHighlight = true
if (flags.hasComment) hasComment = true if (flags.hasComment) hasComment = true
if (flags.hasHighlight) hasHighlight = true if (flags.hasHighlight) hasHighlight = true
if (flags.hasLabel || flags.hasComment || flags.hasHighlight) engagementCount++ if (bookmarkPinFlags.hasBookmark) hasBookmark = true
if (bookmarkPinFlags.hasPin) hasPin = true
if (targetHasPublicationEngagement(flags, booklistFlags, bookmarkPinFlags)) engagementCount++
} }
const rootFlags = addressHasEngagement(rootAddr ?? '', root.id, engagement) const rootFlags = addressHasEngagement(rootAddr ?? '', root.id, engagement)
const rootBooklistFlags = collectBooklistFlagsForTarget(rootAddr ?? '', root.id, engagement) const rootBooklistFlags = collectBooklistFlagsForTarget(rootAddr ?? '', root.id, engagement)
const rootBookmarkPinFlags = collectBookmarkPinFlagsForTarget(rootAddr ?? '', root.id, engagement)
const rootMyFlags = collectMyEngagementFlagsForTarget(rootAddr ?? '', root.id, engagement) const rootMyFlags = collectMyEngagementFlagsForTarget(rootAddr ?? '', root.id, engagement)
hasLabel = hasLabel || rootFlags.hasLabel hasLabel = hasLabel || rootFlags.hasLabel
hasComment = hasComment || rootFlags.hasComment hasComment = hasComment || rootFlags.hasComment
hasHighlight = hasHighlight || rootFlags.hasHighlight hasHighlight = hasHighlight || rootFlags.hasHighlight
hasBookmark = hasBookmark || rootBookmarkPinFlags.hasBookmark
hasPin = hasPin || rootBookmarkPinFlags.hasPin
hasBooklistLabel = hasBooklistLabel || rootBooklistFlags.hasBooklistLabel hasBooklistLabel = hasBooklistLabel || rootBooklistFlags.hasBooklistLabel
hasMyBooklistLabel = hasMyBooklistLabel || rootBooklistFlags.hasMyBooklistLabel hasMyBooklistLabel = hasMyBooklistLabel || rootBooklistFlags.hasMyBooklistLabel
hasMyComment = hasMyComment || rootMyFlags.hasMyComment hasMyComment = hasMyComment || rootMyFlags.hasMyComment
@ -666,6 +818,8 @@ export function buildLibraryPublicationEntry(
hasMyHighlight, hasMyHighlight,
hasComment, hasComment,
hasHighlight, hasHighlight,
hasBookmark,
hasPin,
engagementCount engagementCount
} }
} }
@ -687,7 +841,7 @@ export function filterEngagedPublications(
): LibraryPublicationEntry[] { ): LibraryPublicationEntry[] {
return getTopLevelIndexEvents(roots) return getTopLevelIndexEvents(roots)
.map((root) => buildLibraryPublicationEntry(root, indexByAddress, engagement)) .map((root) => buildLibraryPublicationEntry(root, indexByAddress, engagement))
.filter((entry) => entry.hasLabel || entry.hasComment || entry.hasHighlight) .filter((entry) => publicationEntryHasEngagement(entry))
} }
export function buildRecentPublicationEntries( export function buildRecentPublicationEntries(
@ -713,7 +867,7 @@ export function computeLibraryFeedRootOrder(
const restRoots: Event[] = [] const restRoots: Event[] = []
for (const root of topLevel) { for (const root of topLevel) {
const entry = buildLibraryPublicationEntry(root, indexByAddress, engagement) const entry = buildLibraryPublicationEntry(root, indexByAddress, engagement)
if (entry.hasLabel || entry.hasComment || entry.hasHighlight) { if (publicationEntryHasEngagement(entry)) {
engagedRoots.push(root) engagedRoots.push(root)
} else { } else {
restRoots.push(root) restRoots.push(root)
@ -787,7 +941,9 @@ export function pickLibraryPublicationEntries(
export function sortLibraryPublications(entries: LibraryPublicationEntry[]): LibraryPublicationEntry[] { export function sortLibraryPublications(entries: LibraryPublicationEntry[]): LibraryPublicationEntry[] {
return [...entries].sort((a, b) => { return [...entries].sort((a, b) => {
if (a.hasLabel !== b.hasLabel) return a.hasLabel ? -1 : 1 const aEngaged = publicationEntryHasEngagement(a)
const bEngaged = publicationEntryHasEngagement(b)
if (aEngaged !== bEngaged) return aEngaged ? -1 : 1
if (a.engagementCount !== b.engagementCount) return b.engagementCount - a.engagementCount if (a.engagementCount !== b.engagementCount) return b.engagementCount - a.engagementCount
return b.event.created_at - a.event.created_at return b.event.created_at - a.event.created_at
}) })
@ -810,7 +966,13 @@ function emptyPublicationEngagementMaps(): PublicationEngagementMaps {
myHighlightAddresses: new Set(), myHighlightAddresses: new Set(),
myHighlightEventIds: new Set(), myHighlightEventIds: new Set(),
commentAddresses: new Set(), commentAddresses: new Set(),
highlightAddresses: new Set() commentEventIds: new Set(),
highlightAddresses: new Set(),
highlightEventIds: new Set(),
bookmarkAddresses: new Set(),
bookmarkEventIds: new Set(),
pinAddresses: new Set(),
pinEventIds: new Set()
} }
} }

Loading…
Cancel
Save