17 changed files with 408 additions and 41 deletions
@ -0,0 +1,200 @@ |
|||||||
|
import { NDKRelaySet, type NDKEvent, NDKSubscription } 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 { type IssueFull, full_defaults } from '$lib/components/issues/type' |
||||||
|
import { proposal_status_kinds, proposal_status_open } from '$lib/kinds' |
||||||
|
import { awaitSelectedRepoCollection } from './repo' |
||||||
|
import { |
||||||
|
extractIssueDescription, |
||||||
|
extractIssueTitle, |
||||||
|
} from '$lib/components/events/content/utils' |
||||||
|
import { goto } from '$app/navigation' |
||||||
|
import { selectRepoFromCollection } from '$lib/components/repo/utils' |
||||||
|
|
||||||
|
export const selected_issue_full: Writable<IssueFull> = writable({ |
||||||
|
...full_defaults, |
||||||
|
}) |
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
let selected_issue_repo_id: string = '' |
||||||
|
let selected_issue_id: string = '' |
||||||
|
let issue_summary_author_unsubsriber: Unsubscriber | undefined |
||||||
|
|
||||||
|
export const selected_issue_replies: Writable<NDKEvent[]> = writable([]) |
||||||
|
|
||||||
|
let selected_issue_status_date = 0 |
||||||
|
|
||||||
|
let sub: NDKSubscription |
||||||
|
|
||||||
|
let sub_replies: NDKSubscription |
||||||
|
|
||||||
|
export const ensureIssueFull = (repo_identifier: string, issue_id: string) => { |
||||||
|
if (selected_issue_id == issue_id) return |
||||||
|
if (issue_id == '') { |
||||||
|
selected_issue_full.set({ ...full_defaults }) |
||||||
|
selected_issue_replies.set([]) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if (sub) sub.stop() |
||||||
|
if (sub_replies) sub_replies.stop() |
||||||
|
|
||||||
|
selected_issue_repo_id = repo_identifier |
||||||
|
selected_issue_id = issue_id |
||||||
|
selected_issue_status_date = 0 |
||||||
|
selected_issue_replies.set([]) |
||||||
|
|
||||||
|
selected_issue_full.set({ |
||||||
|
...full_defaults, |
||||||
|
summary: { |
||||||
|
...full_defaults.summary, |
||||||
|
id: issue_id, |
||||||
|
repo_identifier: repo_identifier, |
||||||
|
loading: true, |
||||||
|
}, |
||||||
|
loading: true, |
||||||
|
}) |
||||||
|
if (issue_summary_author_unsubsriber) issue_summary_author_unsubsriber() |
||||||
|
issue_summary_author_unsubsriber = undefined |
||||||
|
|
||||||
|
new Promise(async (r) => { |
||||||
|
const repo_collection = await awaitSelectedRepoCollection(repo_identifier) |
||||||
|
const repo = selectRepoFromCollection(repo_collection) |
||||||
|
const relays_to_use = |
||||||
|
repo && repo.relays.length > 3 |
||||||
|
? repo.relays |
||||||
|
: [...base_relays].concat(repo ? repo.relays : []) |
||||||
|
|
||||||
|
sub = ndk.subscribe( |
||||||
|
{ |
||||||
|
ids: [issue_id], |
||||||
|
limit: 50, |
||||||
|
}, |
||||||
|
{ |
||||||
|
closeOnEose: true, |
||||||
|
}, |
||||||
|
NDKRelaySet.fromRelayUrls(relays_to_use, ndk) |
||||||
|
) |
||||||
|
|
||||||
|
sub.on('event', (event: NDKEvent) => { |
||||||
|
try { |
||||||
|
if (event.id == issue_id) { |
||||||
|
const event_repo_id = event.tagValue('a')?.split(':')[2] |
||||||
|
if (event_repo_id && event_repo_id !== repo_identifier) { |
||||||
|
goto(`/repo/${encodeURIComponent(event_repo_id)}/issue/${issue_id}`) |
||||||
|
} |
||||||
|
selected_issue_full.update((full) => { |
||||||
|
return { |
||||||
|
...full, |
||||||
|
issue_event: event, |
||||||
|
summary: { |
||||||
|
...full.summary, |
||||||
|
title: extractIssueTitle(event.content), |
||||||
|
descritpion: extractIssueDescription(event.content), |
||||||
|
created_at: event.created_at, |
||||||
|
comments: 0, |
||||||
|
author: { |
||||||
|
hexpubkey: event.pubkey, |
||||||
|
loading: true, |
||||||
|
npub: '', |
||||||
|
}, |
||||||
|
loading: false, |
||||||
|
}, |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
issue_summary_author_unsubsriber = ensureUser(event.pubkey).subscribe( |
||||||
|
(u: User) => { |
||||||
|
selected_issue_full.update((full) => { |
||||||
|
return { |
||||||
|
...full, |
||||||
|
summary: { |
||||||
|
...full.summary, |
||||||
|
author: |
||||||
|
event.pubkey == u.hexpubkey ? u : full.summary.author, |
||||||
|
}, |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
) |
||||||
|
} |
||||||
|
} catch {} |
||||||
|
}) |
||||||
|
|
||||||
|
sub.on('eose', () => { |
||||||
|
selected_issue_full.update((full) => { |
||||||
|
const updated = { |
||||||
|
...full, |
||||||
|
summary: { |
||||||
|
...full.summary, |
||||||
|
loading: false, |
||||||
|
}, |
||||||
|
} |
||||||
|
if (full.loading === false) { |
||||||
|
r({ ...updated }) |
||||||
|
} |
||||||
|
return updated |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
sub_replies = ndk.subscribe( |
||||||
|
{ |
||||||
|
'#e': [issue_id], |
||||||
|
}, |
||||||
|
{ |
||||||
|
closeOnEose: false, |
||||||
|
}, |
||||||
|
NDKRelaySet.fromRelayUrls(relays_to_use, ndk) |
||||||
|
) |
||||||
|
|
||||||
|
const process_replies = (event: NDKEvent) => { |
||||||
|
if ( |
||||||
|
event.kind && |
||||||
|
proposal_status_kinds.includes(event.kind) && |
||||||
|
event.created_at && |
||||||
|
selected_issue_status_date < event.created_at |
||||||
|
) { |
||||||
|
selected_issue_status_date = event.created_at |
||||||
|
selected_issue_full.update((full) => { |
||||||
|
return { |
||||||
|
...full, |
||||||
|
summary: { |
||||||
|
...full.summary, |
||||||
|
status: event.kind, |
||||||
|
// this wont be 0 as we are ensuring it is not undefined above
|
||||||
|
status_date: event.created_at || 0, |
||||||
|
}, |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
selected_issue_replies.update((replies) => { |
||||||
|
return [...replies, event].sort( |
||||||
|
(a, b) => (a.created_at || 0) - (b.created_at || 0) |
||||||
|
) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
sub_replies.on('event', (event: NDKEvent) => { |
||||||
|
process_replies(event) |
||||||
|
}) |
||||||
|
|
||||||
|
sub_replies.on('eose', () => { |
||||||
|
selected_issue_full.update((full) => { |
||||||
|
const updated = { |
||||||
|
...full, |
||||||
|
summary: { |
||||||
|
...full.summary, |
||||||
|
status: full.summary.status || proposal_status_open, |
||||||
|
}, |
||||||
|
loading: false, |
||||||
|
} |
||||||
|
if (full.summary.loading === false) { |
||||||
|
r({ ...updated }) |
||||||
|
} |
||||||
|
return updated |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
@ -0,0 +1,94 @@ |
|||||||
|
<script lang="ts"> |
||||||
|
import { |
||||||
|
ensureSelectedRepoCollection, |
||||||
|
selected_repo_collection, |
||||||
|
selected_repo_event, |
||||||
|
} from '$lib/stores/repo' |
||||||
|
import { |
||||||
|
ensureIssueFull, |
||||||
|
selected_issue_full, |
||||||
|
selected_issue_replies, |
||||||
|
} from '$lib/stores/Issue' |
||||||
|
import RepoHeader from '$lib/components/repo/RepoHeader.svelte' |
||||||
|
import Thread from '$lib/wrappers/Thread.svelte' |
||||||
|
import Container from '$lib/components/Container.svelte' |
||||||
|
import ParsedContent from '$lib/components/events/content/ParsedContent.svelte' |
||||||
|
import ComposeReply from '$lib/wrappers/ComposeReply.svelte' |
||||||
|
import { patch_kind } from '$lib/kinds' |
||||||
|
import Patch from '$lib/components/events/content/Patch.svelte' |
||||||
|
import ProposalHeader from '$lib/components/proposals/ProposalHeader.svelte' |
||||||
|
import ProposalDetails from '$lib/components/proposals/ProposalDetails.svelte' |
||||||
|
|
||||||
|
export let data: { |
||||||
|
repo_id: string |
||||||
|
issue_id: string |
||||||
|
} |
||||||
|
|
||||||
|
let repo_id = data.repo_id |
||||||
|
let issue_id = data.issue_id |
||||||
|
|
||||||
|
ensureSelectedRepoCollection(repo_id) |
||||||
|
ensureIssueFull(repo_id, issue_id) |
||||||
|
|
||||||
|
let repo_error = false |
||||||
|
let issue_error = false |
||||||
|
$: { |
||||||
|
repo_error = |
||||||
|
!$selected_repo_collection.loading && |
||||||
|
$selected_repo_event.name.length === 0 |
||||||
|
issue_error = |
||||||
|
!$selected_issue_full.summary.loading && |
||||||
|
$selected_issue_full.summary.created_at === 0 |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
{#if !repo_error} |
||||||
|
<RepoHeader {...$selected_repo_event} /> |
||||||
|
{/if} |
||||||
|
|
||||||
|
{#if issue_error} |
||||||
|
<Container> |
||||||
|
<div role="alert" class="alert alert-error m-auto mt-6 w-full max-w-xs"> |
||||||
|
<svg |
||||||
|
xmlns="http://www.w3.org/2000/svg" |
||||||
|
class="h-6 w-6 shrink-0 stroke-current" |
||||||
|
fill="none" |
||||||
|
viewBox="0 0 24 24" |
||||||
|
><path |
||||||
|
stroke-linecap="round" |
||||||
|
stroke-linejoin="round" |
||||||
|
stroke-width="2" |
||||||
|
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" |
||||||
|
/></svg |
||||||
|
> |
||||||
|
<span>Error! cannot find Issue {repo_error ? 'or repo ' : ''}event</span> |
||||||
|
</div> |
||||||
|
</Container> |
||||||
|
{:else} |
||||||
|
<ProposalHeader {...$selected_issue_full.summary} /> |
||||||
|
<Container> |
||||||
|
<div class="mx-auto max-w-6xl md:flex"> |
||||||
|
<div class="md:mr-2 md:w-2/3"> |
||||||
|
<div class="max-w-4xl"> |
||||||
|
<div class="my-3"> |
||||||
|
<ParsedContent content={$selected_issue_full.summary.descritpion} /> |
||||||
|
</div> |
||||||
|
{#each $selected_issue_replies as event} |
||||||
|
<Thread type="issue" {event} replies={[]} /> |
||||||
|
{/each} |
||||||
|
<div class="my-3"> |
||||||
|
<ComposeReply type="issue" /> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="prose ml-2 hidden w-1/3 md:flex"> |
||||||
|
<ProposalDetails |
||||||
|
type="issue" |
||||||
|
summary={$selected_issue_full.summary} |
||||||
|
labels={$selected_issue_full.labels} |
||||||
|
loading={$selected_issue_full.loading} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</Container> |
||||||
|
{/if} |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
export const load = ({ |
||||||
|
params, |
||||||
|
}: { |
||||||
|
params: { issue_id: string; repo_id: string } |
||||||
|
}) => { |
||||||
|
return { |
||||||
|
repo_id: decodeURIComponent(params.repo_id), |
||||||
|
issue_id: params.issue_id, |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue