8 changed files with 355 additions and 12 deletions
@ -0,0 +1,60 @@ |
|||||||
|
import type { User } from '../users/type' |
||||||
|
import { defaults as user_defaults } from '../users/type' |
||||||
|
import type { Event } from '../events/type' |
||||||
|
import type { NDKEvent } from '@nostr-dev-kit/ndk' |
||||||
|
|
||||||
|
export interface IssueSummary { |
||||||
|
type: 'issue' |
||||||
|
title: string |
||||||
|
descritpion: string |
||||||
|
repo_identifier: string |
||||||
|
id: string |
||||||
|
comments: number |
||||||
|
status: undefined | number |
||||||
|
status_date: number |
||||||
|
author: User |
||||||
|
created_at: number | undefined |
||||||
|
loading: boolean |
||||||
|
} |
||||||
|
|
||||||
|
export const summary_defaults: IssueSummary = { |
||||||
|
type: 'issue', |
||||||
|
title: '', |
||||||
|
descritpion: '', |
||||||
|
repo_identifier: '', |
||||||
|
id: '', |
||||||
|
comments: 0, |
||||||
|
status: undefined, |
||||||
|
status_date: 0, |
||||||
|
author: { ...user_defaults }, |
||||||
|
created_at: 0, |
||||||
|
loading: true, |
||||||
|
} |
||||||
|
|
||||||
|
export interface IssueSummaries { |
||||||
|
id: string | undefined |
||||||
|
summaries: IssueSummary[] |
||||||
|
loading: boolean |
||||||
|
} |
||||||
|
|
||||||
|
export const summaries_defaults: IssueSummaries = { |
||||||
|
id: '', |
||||||
|
summaries: [], |
||||||
|
loading: true, |
||||||
|
} |
||||||
|
|
||||||
|
export interface IssueFull { |
||||||
|
summary: IssueSummary |
||||||
|
issue_event: NDKEvent | undefined |
||||||
|
labels: string[] |
||||||
|
events: Event[] |
||||||
|
loading: boolean |
||||||
|
} |
||||||
|
|
||||||
|
export const full_defaults: IssueFull = { |
||||||
|
summary: { ...summary_defaults }, |
||||||
|
issue_event: undefined, |
||||||
|
labels: [], |
||||||
|
events: [], |
||||||
|
loading: true, |
||||||
|
} |
||||||
@ -0,0 +1,231 @@ |
|||||||
|
import { |
||||||
|
NDKRelaySet, |
||||||
|
type NDKEvent, |
||||||
|
NDKSubscription, |
||||||
|
type NDKFilter, |
||||||
|
} from '@nostr-dev-kit/ndk' |
||||||
|
import { writable, type Unsubscriber, type Writable } from 'svelte/store' |
||||||
|
import { base_relays, ndk } from './ndk' |
||||||
|
import type { User } from '$lib/components/users/type' |
||||||
|
import { ensureUser } from './users' |
||||||
|
import { awaitSelectedRepoCollection } from './repo' |
||||||
|
import { |
||||||
|
issue_kind, |
||||||
|
proposal_status_kinds, |
||||||
|
proposal_status_open, |
||||||
|
repo_kind, |
||||||
|
} from '$lib/kinds' |
||||||
|
import { extractPatchMessage } from '$lib/components/events/content/utils' |
||||||
|
import { selectRepoFromCollection } from '$lib/components/repo/utils' |
||||||
|
import { |
||||||
|
summary_defaults, |
||||||
|
type IssueSummaries, |
||||||
|
} from '$lib/components/issues/type' |
||||||
|
|
||||||
|
export const issue_summaries: Writable<IssueSummaries> = writable({ |
||||||
|
id: '', |
||||||
|
summaries: [], |
||||||
|
loading: false, |
||||||
|
}) |
||||||
|
|
||||||
|
let selected_repo_id: string | undefined = '' |
||||||
|
|
||||||
|
let authors_unsubscribers: Unsubscriber[] = [] |
||||||
|
|
||||||
|
let sub: NDKSubscription |
||||||
|
|
||||||
|
export const ensureIssueSummaries = async (repo_id: string | undefined) => { |
||||||
|
if (selected_repo_id == repo_id) return |
||||||
|
issue_summaries.set({ |
||||||
|
id: repo_id, |
||||||
|
summaries: [], |
||||||
|
loading: repo_id !== '', |
||||||
|
}) |
||||||
|
|
||||||
|
if (sub) sub.stop() |
||||||
|
if (sub_statuses) sub_statuses.stop() |
||||||
|
authors_unsubscribers.forEach((u) => u()) |
||||||
|
authors_unsubscribers = [] |
||||||
|
|
||||||
|
selected_repo_id = repo_id |
||||||
|
|
||||||
|
setTimeout(() => { |
||||||
|
issue_summaries.update((summaries) => { |
||||||
|
return { |
||||||
|
...summaries, |
||||||
|
loading: false, |
||||||
|
} |
||||||
|
}) |
||||||
|
}, 6000) |
||||||
|
|
||||||
|
let relays_to_use = [...base_relays] |
||||||
|
let filter: NDKFilter = { |
||||||
|
kinds: [issue_kind], |
||||||
|
limit: 50, |
||||||
|
} |
||||||
|
|
||||||
|
if (repo_id) { |
||||||
|
const repo_collection = await awaitSelectedRepoCollection(repo_id) |
||||||
|
|
||||||
|
const repo = selectRepoFromCollection(repo_collection) |
||||||
|
if (!repo) { |
||||||
|
// TODO: display error info bar
|
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
relays_to_use = |
||||||
|
repo.relays.length > 3 |
||||||
|
? repo.relays |
||||||
|
: [...base_relays].concat(repo.relays) |
||||||
|
|
||||||
|
filter = { |
||||||
|
kinds: [issue_kind], |
||||||
|
'#a': repo.maintainers.map( |
||||||
|
(m) => `${repo_kind}:${m.hexpubkey}:${repo.identifier}` |
||||||
|
), |
||||||
|
limit: 50, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
sub = ndk.subscribe( |
||||||
|
filter, |
||||||
|
{ |
||||||
|
closeOnEose: true, |
||||||
|
}, |
||||||
|
NDKRelaySet.fromRelayUrls(relays_to_use, ndk) |
||||||
|
) |
||||||
|
|
||||||
|
sub.on('event', (event: NDKEvent) => { |
||||||
|
try { |
||||||
|
if (event.kind == issue_kind) { |
||||||
|
if (!extractRepoIdentiferFromIssueEvent(event) && !repo_id) { |
||||||
|
// link to issue will not work as it requires an identifier
|
||||||
|
return |
||||||
|
} |
||||||
|
issue_summaries.update((issues) => { |
||||||
|
return { |
||||||
|
...issues, |
||||||
|
summaries: [ |
||||||
|
...issues.summaries, |
||||||
|
{ |
||||||
|
...summary_defaults, |
||||||
|
id: event.id, |
||||||
|
repo_identifier: |
||||||
|
extractRepoIdentiferFromIssueEvent(event) || repo_id || '', |
||||||
|
title: ( |
||||||
|
event.tagValue('name') || |
||||||
|
event.tagValue('description') || |
||||||
|
extractPatchMessage(event.content) || |
||||||
|
'' |
||||||
|
).split('\n')[0], |
||||||
|
descritpion: event.tagValue('description') || '', |
||||||
|
created_at: event.created_at, |
||||||
|
comments: 0, |
||||||
|
author: { |
||||||
|
hexpubkey: event.pubkey, |
||||||
|
loading: true, |
||||||
|
npub: '', |
||||||
|
}, |
||||||
|
loading: false, |
||||||
|
}, |
||||||
|
].sort((a, b) => (b.created_at || 0) - (a.created_at || 0)), |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
authors_unsubscribers.push( |
||||||
|
ensureUser(event.pubkey).subscribe((u: User) => { |
||||||
|
issue_summaries.update((issues) => { |
||||||
|
return { |
||||||
|
...issues, |
||||||
|
summaries: issues.summaries.map((o) => ({ |
||||||
|
...o, |
||||||
|
author: event.pubkey === o.author.hexpubkey ? u : o.author, |
||||||
|
})), |
||||||
|
} |
||||||
|
}) |
||||||
|
}) |
||||||
|
) |
||||||
|
} catch {} |
||||||
|
}) |
||||||
|
sub.on('eose', () => { |
||||||
|
issue_summaries.update((issues) => { |
||||||
|
getAndUpdateIssueStatus(issues, relays_to_use) |
||||||
|
return { |
||||||
|
...issues, |
||||||
|
loading: false, |
||||||
|
} |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
let sub_statuses: NDKSubscription |
||||||
|
|
||||||
|
function getAndUpdateIssueStatus( |
||||||
|
issues: IssueSummaries, |
||||||
|
relays: string[] |
||||||
|
): void { |
||||||
|
if (sub_statuses) sub_statuses.stop() |
||||||
|
sub_statuses = ndk.subscribe( |
||||||
|
{ |
||||||
|
kinds: proposal_status_kinds, |
||||||
|
'#e': issues.summaries.map((issue) => issue.id), |
||||||
|
}, |
||||||
|
{ |
||||||
|
closeOnEose: true, |
||||||
|
}, |
||||||
|
NDKRelaySet.fromRelayUrls(relays, ndk) |
||||||
|
) |
||||||
|
sub_statuses.on('event', (event: NDKEvent) => { |
||||||
|
const tagged_issue_event = event.tagValue('e') |
||||||
|
if ( |
||||||
|
event.kind && |
||||||
|
proposal_status_kinds.includes(event.kind) && |
||||||
|
tagged_issue_event && |
||||||
|
event.created_at |
||||||
|
) { |
||||||
|
issue_summaries.update((issues) => { |
||||||
|
return { |
||||||
|
...issues, |
||||||
|
summaries: issues.summaries.map((o) => { |
||||||
|
if ( |
||||||
|
o.id === tagged_issue_event && |
||||||
|
event.created_at && |
||||||
|
o.status_date < event.created_at |
||||||
|
) { |
||||||
|
return { |
||||||
|
...o, |
||||||
|
status: event.kind as number, |
||||||
|
status_date: event.created_at, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return o |
||||||
|
}), |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
sub_statuses.on('eose', () => { |
||||||
|
issue_summaries.update((issues) => { |
||||||
|
return { |
||||||
|
...issues, |
||||||
|
summaries: issues.summaries.map((o) => ({ |
||||||
|
...o, |
||||||
|
status: o.status || proposal_status_open, |
||||||
|
})), |
||||||
|
} |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
export const extractRepoIdentiferFromIssueEvent = ( |
||||||
|
event: NDKEvent |
||||||
|
): string | undefined => { |
||||||
|
const value = event.tagValue('a') |
||||||
|
if (!value) return undefined |
||||||
|
const split = value.split(':') |
||||||
|
if (split.length < 3) return undefined |
||||||
|
return split[2] |
||||||
|
} |
||||||
Loading…
Reference in new issue