Browse Source

major refactor to improve handling of repo events

stop automatic grouping of repo events by identifer and
earliest unique commit into a single collection.

allow selection of different repos that have an
identifier collision by prioritising pubkey:identifier
over identifer

show repos grouped by name
master
DanConwayDev 2 years ago
parent
commit
79a691726a
No known key found for this signature in database
GPG Key ID: 68E15486D73F75E1
  1. 7
      src/lib/components/RepoSummaryCard.svelte
  2. 90
      src/lib/components/ReposSummaryList.svelte
  3. 8
      src/lib/components/issues/type.ts
  4. 1
      src/lib/components/proposals/ProposalDetails.svelte
  5. 8
      src/lib/components/proposals/ProposalHeader.svelte
  6. 17
      src/lib/components/proposals/ProposalsListItem.svelte
  7. 10
      src/lib/components/proposals/StatusSelector.svelte
  8. 8
      src/lib/components/proposals/type.ts
  9. 6
      src/lib/components/repo/RepoHeader.svelte
  10. 48
      src/lib/components/repo/type.ts
  11. 53
      src/lib/components/repo/utils.ts
  12. 15
      src/lib/stores/Issue.ts
  13. 26
      src/lib/stores/Issues.ts
  14. 47
      src/lib/stores/Proposal.ts
  15. 38
      src/lib/stores/Proposals.ts
  16. 56
      src/lib/stores/ReposIdentifier.ts
  17. 78
      src/lib/stores/ReposPubkey.ts
  18. 55
      src/lib/stores/ReposRecent.ts
  19. 63
      src/lib/stores/repo.ts
  20. 459
      src/lib/stores/repos.ts
  21. 2
      src/lib/wrappers/ComposeIssue.svelte
  22. 9
      src/lib/wrappers/ComposeReply.svelte
  23. 4
      src/lib/wrappers/RepoDetails.svelte
  24. 9
      src/lib/wrappers/RepoMenu.svelte
  25. 38
      src/lib/wrappers/RepoPageWrapper.svelte
  26. 17
      src/lib/wrappers/ReposRecent.svelte
  27. 2
      src/routes/+page.svelte
  28. 24
      src/routes/about/+page.svelte
  29. 122
      src/routes/e/[nostr_ref]/+page.svelte
  30. 5
      src/routes/e/[nostr_ref]/+page.ts
  31. 12
      src/routes/ngit/+page.svelte
  32. 19
      src/routes/p/[npub]/+page.svelte
  33. 8
      src/routes/r/[repo_naddr]/+page.svelte
  34. 7
      src/routes/r/[repo_naddr]/+page.ts
  35. 8
      src/routes/r/[repo_naddr]/issues/+page.svelte
  36. 5
      src/routes/r/[repo_naddr]/issues/+page.ts
  37. 60
      src/routes/r/[repo_naddr]/issues/[issue_nip19]/+page.svelte
  38. 10
      src/routes/r/[repo_naddr]/issues/[issue_nip19]/+page.ts
  39. 36
      src/routes/r/[repo_naddr]/issues/new/+page.svelte
  40. 5
      src/routes/r/[repo_naddr]/issues/new/+page.ts
  41. 7
      src/routes/r/[repo_naddr]/proposals/+page.svelte
  42. 5
      src/routes/r/[repo_naddr]/proposals/+page.ts
  43. 107
      src/routes/r/[repo_naddr]/proposals/[proposal_nip19]/+page.svelte
  44. 10
      src/routes/r/[repo_naddr]/proposals/[proposal_nip19]/+page.ts
  45. 7
      src/routes/repo/[repo_id]/+page.ts
  46. 10
      src/routes/repo/[repo_id]/issue/[issue_id]/+page.ts
  47. 5
      src/routes/repo/[repo_id]/issues/+page.ts
  48. 5
      src/routes/repo/[repo_id]/issues/new/+page.ts
  49. 122
      src/routes/repo/[repo_id]/proposal/[proposal_id]/+page.svelte
  50. 10
      src/routes/repo/[repo_id]/proposal/[proposal_id]/+page.ts
  51. 5
      src/routes/repo/[repo_id]/proposals/+page.ts
  52. 20
      src/routes/repo/[repo_identifier]/+page.svelte
  53. 7
      src/routes/repo/[repo_identifier]/+page.ts
  54. 5
      src/routes/repo/[repo_identifier]/issue/[event_id]/+page.ts
  55. 5
      src/routes/repo/[repo_identifier]/issues/+page.ts
  56. 5
      src/routes/repo/[repo_identifier]/proposal/[event_id]/+page.ts
  57. 5
      src/routes/repo/[repo_identifier]/proposals/+page.ts

7
src/lib/components/RepoSummaryCard.svelte

@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
import { summary_defaults } from './repo/type'
import UserHeader from './users/UserHeader.svelte'
export let { name, description, identifier, maintainers, loading } =
export let { name, description, identifier, maintainers, naddr, loading } =
summary_defaults
let short_name: string
$: {
@ -25,10 +25,7 @@ @@ -25,10 +25,7 @@
<div class="skeleton mb-2 h-5 w-40"></div>
<div class="w-100 skeleton h-4"></div>
{:else}
<a
class="link-primary break-words"
href="/repo/{encodeURIComponent(identifier)}">{short_name}</a
>
<a class="link-primary break-words" href="/r/{naddr}">{short_name}</a>
{#if short_descrption.length > 0}
<p class="text-muted break-words pb-1 text-sm">
{short_descrption}

90
src/lib/components/ReposSummaryList.svelte

@ -5,6 +5,27 @@ @@ -5,6 +5,27 @@
export let title: string = ''
export let repos: RepoSummary[] = []
export let loading: boolean = false
export let group_by: 'name' | 'identifier' | undefined = undefined
let grouped_repos: RepoSummary[][] = []
let selected_group: string | undefined = undefined
$: {
grouped_repos = []
repos.forEach((collection) => {
if (!group_by) {
grouped_repos.push([collection])
return
}
const added_to_group = grouped_repos.some((group, i) => {
if (group.some((c) => c[group_by] === collection[group_by])) {
grouped_repos[i].push(collection)
return true
}
return false
})
if (!added_to_group) grouped_repos.push([collection])
})
}
</script>
<div class="min-width">
@ -17,8 +38,46 @@ @@ -17,8 +38,46 @@
<p class="prose">None</p>
{:else}
<div class="grid gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
{#each repos as { name, description, identifier, maintainers }}
<RepoSummaryCard {name} {description} {identifier} {maintainers} />
{#each grouped_repos as group}
{#if group.length === 0}
<RepoSummaryCard loading={true} />
{:else if group.length === 1}
{#each group as { name, description, identifier, maintainers, naddr }}
<RepoSummaryCard
{name}
{description}
{identifier}
{maintainers}
{naddr}
/>
{/each}
{:else if group_by}
<div class="stack">
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="flex min-h-28 cursor-pointer items-center rounded-lg border border-base-400 bg-base-200 p-4 hover:bg-base-300"
on:click={() => {
selected_group = group[0][group_by]
}}
>
<div class="m-auto text-center">
<div class="">{group[0][group_by]}</div>
<div class=" text-sm opacity-50">{group.length} Items</div>
</div>
</div>
{#each group as { name, description, identifier, maintainers, naddr }}
<div class="rounded-lg border border-base-400">
<RepoSummaryCard
{name}
{description}
{identifier}
{maintainers}
{naddr}
/>
</div>
{/each}
</div>
{/if}
{/each}
{#if loading}
<RepoSummaryCard loading={true} />
@ -30,3 +89,30 @@ @@ -30,3 +89,30 @@
</div>
{/if}
</div>
{#if selected_group}
<div class="modal modal-open">
<div class="modal-box max-w-full text-wrap text-xs">
<div class="prose max-w-full">
<h3 class="mb-3 max-w-full text-center">
{group_by}: "{selected_group}"
</h3>
</div>
<div class="grid gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
{#each repos.filter((summary) => group_by && summary[group_by] === selected_group) as { name, description, identifier, maintainers, naddr }}
<RepoSummaryCard
{name}
{description}
{identifier}
{maintainers}
{naddr}
/>
{/each}
</div>
<div class="modal-action">
<button class="btn btn-sm" on:click={() => (selected_group = undefined)}
>Close</button
>
</div>
</div>
</div>
{/if}

8
src/lib/components/issues/type.ts

@ -7,7 +7,7 @@ export interface IssueSummary { @@ -7,7 +7,7 @@ export interface IssueSummary {
type: 'issue'
title: string
descritpion: string
repo_identifier: string
repo_a: string
id: string
comments: number
status: undefined | number
@ -21,7 +21,7 @@ export const summary_defaults: IssueSummary = { @@ -21,7 +21,7 @@ export const summary_defaults: IssueSummary = {
type: 'issue',
title: '',
descritpion: '',
repo_identifier: '',
repo_a: '',
id: '',
comments: 0,
status: undefined,
@ -32,13 +32,13 @@ export const summary_defaults: IssueSummary = { @@ -32,13 +32,13 @@ export const summary_defaults: IssueSummary = {
}
export interface IssueSummaries {
id: string | undefined
repo_a: string | undefined
summaries: IssueSummary[]
loading: boolean
}
export const summaries_defaults: IssueSummaries = {
id: '',
repo_a: '',
summaries: [],
loading: true,
}

1
src/lib/components/proposals/ProposalDetails.svelte

@ -31,7 +31,6 @@ @@ -31,7 +31,6 @@
<StatusSelector
{type}
status={summary.status}
repo_identifier={summary.repo_identifier}
proposal_or_issue_id={summary.id}
/>
{/if}

8
src/lib/components/proposals/ProposalHeader.svelte

@ -23,7 +23,6 @@ @@ -23,7 +23,6 @@
title,
descritpion,
id,
repo_identifier,
comments,
status,
status_date,
@ -86,12 +85,7 @@ @@ -86,12 +85,7 @@
{#if !$logged_in_user}
<Status {type} {status} edit_mode={false} />
{:else}
<StatusSelector
{type}
{status}
{repo_identifier}
proposal_or_issue_id={id}
/>{/if}
<StatusSelector {type} {status} proposal_or_issue_id={id} />{/if}
</div>
<div class="mr-3 inline align-middle">
opened {created_at_ago}

17
src/lib/components/proposals/ProposalsListItem.svelte

@ -14,6 +14,8 @@ @@ -14,6 +14,8 @@
proposal_status_open,
} from '$lib/kinds'
import { issue_icon_path } from '../issues/icons'
import { aToNaddr, extractAReference } from '../repo/utils'
import { nip19 } from 'nostr-tools'
dayjs.extend(relativeTime)
export let type: 'issue' | 'proposal' = 'proposal'
@ -22,7 +24,7 @@ @@ -22,7 +24,7 @@
title,
descritpion,
id,
repo_identifier,
repo_a,
comments,
status,
status_date,
@ -39,6 +41,15 @@ @@ -39,6 +41,15 @@
else short_title = title
created_at_ago = created_at ? dayjs(created_at * 1000).fromNow() : ''
}
let repo_naddr = ''
let repo_identifier = ''
$: {
if (repo_a.length > 0) {
repo_naddr = aToNaddr(repo_a) || ''
let a_ref = extractAReference(repo_a)
repo_identifier = a_ref ? a_ref.identifier : ''
}
}
</script>
<li class="flex p-2 pt-4 {!loading ? 'cursor-pointer hover:bg-base-200' : ''}">
@ -97,7 +108,7 @@ @@ -97,7 +108,7 @@
</svg>
{/if}
<a
href="/repo/{repo_identifier}/{type}/{id}"
href="/r/{repo_naddr}/{type}s/{nip19.noteEncode(id) || ''}"
class="ml-3 grow overflow-hidden text-xs text-neutral-content"
class:pointer-events-none={loading}
>
@ -134,7 +145,7 @@ @@ -134,7 +145,7 @@
</li>
{#if show_repo && repo_identifier.length > 0}
<li class="ml-3 inline">
<a class="link-primary z-10" href="/repo/{repo_identifier}">
<a class="link-primary z-10" href="/r/{repo_naddr}">
{repo_identifier}
</a>
</li>

10
src/lib/components/proposals/StatusSelector.svelte

@ -13,24 +13,18 @@ @@ -13,24 +13,18 @@
statusKindtoText,
} from '$lib/kinds'
import { getUserRelays, logged_in_user } from '$lib/stores/users'
import {
selected_repo_collection,
selected_repo_event,
} from '$lib/stores/repo'
import { selected_repo_event } from '$lib/stores/repo'
import Status from '$lib/components/proposals/Status.svelte'
export let status: number | undefined = undefined
export let type: 'proposal' | 'issue' = 'proposal'
export let repo_identifier: string = ''
export let proposal_or_issue_id: string = ''
let loading = false
let edit_mode = false
$: {
edit_mode =
$logged_in_user !== undefined &&
repo_identifier === $selected_repo_collection.identifier
edit_mode = $logged_in_user !== undefined
}
async function changeStatus(new_status_kind: number) {

8
src/lib/components/proposals/type.ts

@ -7,7 +7,7 @@ export interface ProposalSummary { @@ -7,7 +7,7 @@ export interface ProposalSummary {
type: 'proposal'
title: string
descritpion: string
repo_identifier: string
repo_a: string
id: string
comments: number
status: undefined | number
@ -21,7 +21,7 @@ export const summary_defaults: ProposalSummary = { @@ -21,7 +21,7 @@ export const summary_defaults: ProposalSummary = {
type: 'proposal',
title: '',
descritpion: '',
repo_identifier: '',
repo_a: '',
id: '',
comments: 0,
status: undefined,
@ -32,13 +32,13 @@ export const summary_defaults: ProposalSummary = { @@ -32,13 +32,13 @@ export const summary_defaults: ProposalSummary = {
}
export interface ProposalSummaries {
id: string | undefined
repo_a: string | undefined
summaries: ProposalSummary[]
loading: boolean
}
export const summaries_defaults: ProposalSummaries = {
id: '',
repo_a: '',
summaries: [],
loading: true,
}

6
src/lib/components/repo/RepoHeader.svelte

@ -6,8 +6,10 @@ @@ -6,8 +6,10 @@
export let {
event_id,
identifier,
naddr,
unique_commit,
name,
author,
description,
clone,
web,
@ -38,11 +40,11 @@ @@ -38,11 +40,11 @@
</div>
{:else}
<a
href={`/repo/${identifier}`}
href={`/r/${naddr}`}
class="strong btn btn-ghost mb-0 mt-0 break-words px-3 text-sm"
>{short_name}</a
>
{/if}
<RepoMenu {identifier} {selected_tab} />
<RepoMenu {selected_tab} />
</Container>
</div>

48
src/lib/components/repo/type.ts

@ -7,6 +7,7 @@ import { @@ -7,6 +7,7 @@ import {
export interface RepoEventBase {
event_id: string
naddr: string
author: string // pubkey
identifier: string
unique_commit: string | undefined
name: string
@ -17,6 +18,8 @@ export interface RepoEventBase { @@ -17,6 +18,8 @@ export interface RepoEventBase {
maintainers: string | User[]
relays: string[]
referenced_by: string[]
// this is unreliable as relays dont return youngest first
most_recent_reference_timestamp: number
created_at: number
loading: boolean
}
@ -31,6 +34,7 @@ export interface RepoEventWithMaintainersMetadata extends RepoEventBase { @@ -31,6 +34,7 @@ export interface RepoEventWithMaintainersMetadata extends RepoEventBase {
export const event_defaults: RepoEvent = {
event_id: '',
naddr: '',
author: '',
identifier: '',
unique_commit: '',
name: '',
@ -41,22 +45,32 @@ export const event_defaults: RepoEvent = { @@ -41,22 +45,32 @@ export const event_defaults: RepoEvent = {
maintainers: [],
relays: [],
referenced_by: [],
most_recent_reference_timestamp: 0,
created_at: 0,
loading: true,
}
export interface RepoCollection {
selected_event_id: string
unique_commit: string
identifier: string
export interface RepoCollectionBase {
selected_a: string // <kind>:<pubkeyhex>:<identifier>
most_recent_index: number
maintainers: string | User[]
events: RepoEvent[]
loading: boolean
}
export interface RepoCollection extends RepoCollectionBase {
maintainers: string[]
}
export interface RepoCollectionWithMaintainersMetadata
extends RepoCollectionBase {
maintainers: UserObject[]
}
export const collection_defaults: RepoCollection = {
identifier: '',
unique_commit: '',
selected_event_id: '',
selected_a: '',
most_recent_index: -1,
maintainers: [],
events: [],
loading: true,
}
@ -65,19 +79,39 @@ export interface RepoSummary { @@ -65,19 +79,39 @@ export interface RepoSummary {
name: string
description: string
identifier: string
naddr: string
unique_commit: string | undefined
maintainers: User[]
loading?: boolean
created_at: number
most_recent_reference_timestamp: number
}
export const summary_defaults: RepoSummary = {
name: '',
identifier: '',
naddr: '',
unique_commit: undefined,
description: '',
maintainers: [{ ...user_defaults }],
loading: false,
created_at: 0,
most_recent_reference_timestamp: 0,
}
export interface SelectedPubkeyRepoCollections {
pubkey: string
collections: RepoCollection[]
}
export interface RepoDIdentiferCollection {
d: string
events: RepoEvent[]
loading: boolean
}
export interface RepoRecentCollection {
events: RepoEvent[]
loading: boolean
}
export type RepoPage = 'about' | 'issues' | 'proposals'

53
src/lib/components/repo/utils.ts

@ -1,19 +1,12 @@ @@ -1,19 +1,12 @@
import type { AddressPointer } from 'nostr-tools/lib/types/nip19'
import type { RepoCollection, RepoEvent } from './type'
import { nip19 } from 'nostr-tools'
import { repo_kind } from '$lib/kinds'
export const selectRepoFromCollection = (
collection: RepoCollection
): RepoEvent | undefined => {
if (collection.selected_event_id && collection.selected_event_id.length > 0)
return collection.events.find(
(e) => e.event_id === collection.selected_event_id
)
return [...collection.events].sort((a, b) => {
const a_ref = a.referenced_by ? a.referenced_by.length : 0
const b_ref = b.referenced_by ? b.referenced_by.length : 0
if (a_ref === b_ref) return b.created_at - a.created_at
return b_ref - a_ref
})[0]
return collection.events[collection.most_recent_index]
}
/** most servers will produce a CORS error so a proxy should be used */
@ -70,3 +63,41 @@ const extractRepoAddress = (clone_string: string): string => { @@ -70,3 +63,41 @@ const extractRepoAddress = (clone_string: string): string => {
s = s.replace(':', '/')
return s
}
const naddrToPointer = (s: string): AddressPointer | undefined => {
const decoded = nip19.decode(s)
if (
typeof decoded.data === 'string' ||
!Object.keys(decoded.data).includes('identifier')
)
return undefined
return decoded.data as AddressPointer
}
export const extractAReference = (a: string): AddressPointer | undefined => {
if (a.split(':').length !== 3) return undefined
const [k, pubkey, identifier] = a.split(':')
return { kind: Number(k), pubkey, identifier }
}
export const naddrToRepoA = (s: string): string | undefined => {
const pointer = naddrToPointer(s)
if (pointer && pointer.kind === repo_kind)
return `${repo_kind}:${pointer.pubkey}:${pointer.identifier}`
return undefined
}
export const aToNaddr = (
a: string | AddressPointer
): `naddr1${string}` | undefined => {
const a_ref = typeof a === 'string' ? extractAReference(a) : a
if (!a_ref) return undefined
return nip19.naddrEncode(a_ref)
}
export const neventOrNoteToHexId = (s: string): string | undefined => {
const decoded = nip19.decode(s)
if (decoded.type === 'note') return decoded.data
else if (decoded.type === 'nevent') return decoded.data.id
return undefined
}

15
src/lib/stores/Issue.ts

@ -8,7 +8,6 @@ import { @@ -8,7 +8,6 @@ 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({
@ -16,7 +15,7 @@ export const selected_issue_full: Writable<IssueFull> = writable({ @@ -16,7 +15,7 @@ export const selected_issue_full: Writable<IssueFull> = writable({
})
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let selected_issue_repo_id: string = ''
let selected_issue_repo_a: string = ''
let selected_issue_id: string = ''
export const selected_issue_replies: Writable<NDKEvent[]> = writable([])
@ -29,7 +28,7 @@ let sub_replies: NDKSubscription @@ -29,7 +28,7 @@ let sub_replies: NDKSubscription
const sub_replies_to_replies: NDKSubscription[] = []
export const ensureIssueFull = (repo_identifier: string, issue_id: string) => {
export const ensureIssueFull = (repo_a: string, issue_id: string) => {
if (selected_issue_id == issue_id) return
if (issue_id == '') {
selected_issue_full.set({ ...full_defaults })
@ -41,7 +40,7 @@ export const ensureIssueFull = (repo_identifier: string, issue_id: string) => { @@ -41,7 +40,7 @@ export const ensureIssueFull = (repo_identifier: string, issue_id: string) => {
if (sub_replies) sub_replies.stop()
sub_replies_to_replies.forEach((sub) => sub.stop())
selected_issue_repo_id = repo_identifier
selected_issue_repo_a = repo_a
selected_issue_id = issue_id
selected_issue_status_date = 0
selected_issue_replies.set([])
@ -51,14 +50,14 @@ export const ensureIssueFull = (repo_identifier: string, issue_id: string) => { @@ -51,14 +50,14 @@ export const ensureIssueFull = (repo_identifier: string, issue_id: string) => {
summary: {
...full_defaults.summary,
id: issue_id,
repo_identifier: repo_identifier,
repo_a,
loading: true,
},
loading: true,
})
new Promise(async (r) => {
const repo_collection = await awaitSelectedRepoCollection(repo_identifier)
const repo_collection = await awaitSelectedRepoCollection(repo_a)
const repo = selectRepoFromCollection(repo_collection)
const relays_to_use =
repo && repo.relays.length > 3
@ -79,10 +78,6 @@ export const ensureIssueFull = (repo_identifier: string, issue_id: string) => { @@ -79,10 +78,6 @@ export const ensureIssueFull = (repo_identifier: string, issue_id: string) => {
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,

26
src/lib/stores/Issues.ts

@ -24,27 +24,27 @@ import { @@ -24,27 +24,27 @@ import {
} from '$lib/components/issues/type'
export const issue_summaries: Writable<IssueSummaries> = writable({
id: '',
repo_a: '',
summaries: [],
loading: false,
})
let selected_repo_id: string | undefined = ''
let selected_repo_a: string | undefined = ''
let sub: NDKSubscription
export const ensureIssueSummaries = async (repo_id: string | undefined) => {
if (selected_repo_id == repo_id) return
export const ensureIssueSummaries = async (repo_a: string | undefined) => {
if (selected_repo_a == repo_a) return
issue_summaries.set({
id: repo_id,
repo_a,
summaries: [],
loading: repo_id !== '',
loading: repo_a !== '',
})
if (sub) sub.stop()
if (sub_statuses) sub_statuses.stop()
selected_repo_id = repo_id
selected_repo_a = repo_a
setTimeout(() => {
issue_summaries.update((summaries) => {
@ -61,8 +61,8 @@ export const ensureIssueSummaries = async (repo_id: string | undefined) => { @@ -61,8 +61,8 @@ export const ensureIssueSummaries = async (repo_id: string | undefined) => {
limit: 100,
}
if (repo_id) {
const repo_collection = await awaitSelectedRepoCollection(repo_id)
if (repo_a) {
const repo_collection = await awaitSelectedRepoCollection(repo_a)
const repo = selectRepoFromCollection(repo_collection)
if (!repo) {
@ -93,7 +93,7 @@ export const ensureIssueSummaries = async (repo_id: string | undefined) => { @@ -93,7 +93,7 @@ export const ensureIssueSummaries = async (repo_id: string | undefined) => {
sub.on('event', (event: NDKEvent) => {
try {
if (event.kind == issue_kind) {
if (!extractRepoIdentiferFromIssueEvent(event) && !repo_id) {
if (!extractRepoIdentiferFromIssueEvent(event) && !repo_a) {
// link to issue will not work as it requires an identifier
return
}
@ -105,8 +105,8 @@ export const ensureIssueSummaries = async (repo_id: string | undefined) => { @@ -105,8 +105,8 @@ export const ensureIssueSummaries = async (repo_id: string | undefined) => {
{
...summary_defaults,
id: event.id,
repo_identifier:
extractRepoIdentiferFromIssueEvent(event) || repo_id || '',
repo_a:
extractRepoIdentiferFromIssueEvent(event) || repo_a || '',
title: extractIssueTitle(event.content),
descritpion: extractIssueDescription(event.content),
created_at: event.created_at,
@ -199,5 +199,5 @@ export const extractRepoIdentiferFromIssueEvent = ( @@ -199,5 +199,5 @@ export const extractRepoIdentiferFromIssueEvent = (
if (!value) return undefined
const split = value.split(':')
if (split.length < 3) return undefined
return split[2]
return value
}

47
src/lib/stores/Proposal.ts

@ -1,8 +1,6 @@ @@ -1,8 +1,6 @@
import { NDKRelaySet, type NDKEvent, NDKSubscription } from '@nostr-dev-kit/ndk'
import { writable, type Unsubscriber, type Writable } from 'svelte/store'
import { writable, 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 ProposalFull,
full_defaults,
@ -10,7 +8,6 @@ import { @@ -10,7 +8,6 @@ import {
import { proposal_status_kinds, proposal_status_open } from '$lib/kinds'
import { awaitSelectedRepoCollection } from './repo'
import { extractPatchMessage } from '$lib/components/events/content/utils'
import { goto } from '$app/navigation'
import { selectRepoFromCollection } from '$lib/components/repo/utils'
export const selected_proposal_full: Writable<ProposalFull> = writable({
@ -18,9 +15,8 @@ export const selected_proposal_full: Writable<ProposalFull> = writable({ @@ -18,9 +15,8 @@ export const selected_proposal_full: Writable<ProposalFull> = writable({
})
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let selected_proposal_repo_id: string = ''
let selected_proposal_repo_a: string = ''
let selected_proposal_id: string = ''
let proposal_summary_author_unsubsriber: Unsubscriber | undefined
export const selected_proposal_replies: Writable<NDKEvent[]> = writable([])
@ -32,10 +28,7 @@ let sub_replies: NDKSubscription @@ -32,10 +28,7 @@ let sub_replies: NDKSubscription
const sub_replies_to_replies: NDKSubscription[] = []
export const ensureProposalFull = (
repo_identifier: string,
proposal_id: string
) => {
export const ensureProposalFull = (repo_a: string, proposal_id: string) => {
if (selected_proposal_id == proposal_id) return
if (proposal_id == '') {
selected_proposal_full.set({ ...full_defaults })
@ -47,7 +40,7 @@ export const ensureProposalFull = ( @@ -47,7 +40,7 @@ export const ensureProposalFull = (
if (sub_replies) sub_replies.stop()
sub_replies_to_replies.forEach((sub) => sub.stop())
selected_proposal_repo_id = repo_identifier
selected_proposal_repo_a = repo_a
selected_proposal_id = proposal_id
selected_proposal_status_date = 0
selected_proposal_replies.set([])
@ -57,16 +50,14 @@ export const ensureProposalFull = ( @@ -57,16 +50,14 @@ export const ensureProposalFull = (
summary: {
...full_defaults.summary,
id: proposal_id,
repo_identifier: repo_identifier,
repo_a,
loading: true,
},
loading: true,
})
if (proposal_summary_author_unsubsriber) proposal_summary_author_unsubsriber()
proposal_summary_author_unsubsriber = undefined
new Promise(async (r) => {
const repo_collection = await awaitSelectedRepoCollection(repo_identifier)
const repo_collection = await awaitSelectedRepoCollection(repo_a)
const repo = selectRepoFromCollection(repo_collection)
const relays_to_use =
repo && repo.relays.length > 3
@ -87,12 +78,6 @@ export const ensureProposalFull = ( @@ -87,12 +78,6 @@ export const ensureProposalFull = (
sub.on('event', (event: NDKEvent) => {
try {
if (event.id == proposal_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)}/proposal/${proposal_id}`
)
}
selected_proposal_full.update((full) => {
return {
...full,
@ -108,29 +93,11 @@ export const ensureProposalFull = ( @@ -108,29 +93,11 @@ export const ensureProposalFull = (
descritpion: event.tagValue('description') || '',
created_at: event.created_at,
comments: 0,
author: {
hexpubkey: event.pubkey,
loading: true,
npub: '',
},
author: event.pubkey,
loading: false,
},
}
})
proposal_summary_author_unsubsriber = ensureUser(
event.pubkey
).subscribe((u: User) => {
selected_proposal_full.update((full) => {
return {
...full,
summary: {
...full.summary,
author: event.pubkey == u.hexpubkey ? u : full.summary.author,
},
}
})
})
}
} catch {}
})

38
src/lib/stores/Proposals.ts

@ -20,27 +20,27 @@ import { selectRepoFromCollection } from '$lib/components/repo/utils' @@ -20,27 +20,27 @@ import { selectRepoFromCollection } from '$lib/components/repo/utils'
import { returnRepoCollection } from './repos'
export const proposal_summaries: Writable<ProposalSummaries> = writable({
id: '',
repo_a: '',
summaries: [],
loading: false,
})
let selected_repo_id: string | undefined = ''
let selected_a: string | undefined = ''
let sub: NDKSubscription
export const ensureProposalSummaries = async (repo_id: string | undefined) => {
if (selected_repo_id == repo_id) return
export const ensureProposalSummaries = async (repo_a: string | undefined) => {
if (selected_a == repo_a) return
proposal_summaries.set({
id: repo_id,
repo_a,
summaries: [],
loading: repo_id !== '',
loading: repo_a !== '',
})
if (sub) sub.stop()
if (sub_statuses) sub_statuses.stop()
selected_repo_id = repo_id
selected_a = repo_a
setTimeout(() => {
proposal_summaries.update((summaries) => {
@ -57,8 +57,8 @@ export const ensureProposalSummaries = async (repo_id: string | undefined) => { @@ -57,8 +57,8 @@ export const ensureProposalSummaries = async (repo_id: string | undefined) => {
limit: 100,
}
if (repo_id) {
const repo_collection = await awaitSelectedRepoCollection(repo_id)
if (repo_a) {
const repo_collection = await awaitSelectedRepoCollection(repo_a)
const repo = selectRepoFromCollection(repo_collection)
if (!repo) {
@ -108,12 +108,10 @@ export const ensureProposalSummaries = async (repo_id: string | undefined) => { @@ -108,12 +108,10 @@ export const ensureProposalSummaries = async (repo_id: string | undefined) => {
event.content.length > 0 &&
!event.tags.some((t) => t.length > 1 && t[1] === 'revision-root')
) {
if (!extractRepoIdentiferFromProposalEvent(event) && !repo_id) {
if (!extractRepoAFromProposalEvent(event) && !repo_a) {
// link to proposal will not work as it requires an identifier
return
}
const repo_identifier =
extractRepoIdentiferFromProposalEvent(event) || repo_id || ''
proposal_summaries.update((proposals) => {
return {
@ -123,7 +121,7 @@ export const ensureProposalSummaries = async (repo_id: string | undefined) => { @@ -123,7 +121,7 @@ export const ensureProposalSummaries = async (repo_id: string | undefined) => {
{
...summary_defaults,
id: event.id,
repo_identifier,
repo_a: extractRepoAFromProposalEvent(event) || repo_a || '',
title: (
event.tagValue('name') ||
event.tagValue('description') ||
@ -141,9 +139,13 @@ export const ensureProposalSummaries = async (repo_id: string | undefined) => { @@ -141,9 +139,13 @@ export const ensureProposalSummaries = async (repo_id: string | undefined) => {
})
// filter out non root proposals if repo event supports nip34+ features
if (!repo_id && repo_identifier.length > 0) {
const repo_collection = await returnRepoCollection(repo_identifier)
if (selected_repo_id === repo_id && repo_collection.unique_commit) {
if (repo_a && repo_a.length > 0) {
const repo_collection = await returnRepoCollection(repo_a)
if (
selected_a === repo_a &&
repo_collection.events[repo_collection.most_recent_index]
.unique_commit
) {
proposal_summaries.update((proposals) => {
return {
...proposals,
@ -238,12 +240,12 @@ function getAndUpdateProposalStatus( @@ -238,12 +240,12 @@ function getAndUpdateProposalStatus(
})
}
export const extractRepoIdentiferFromProposalEvent = (
export const extractRepoAFromProposalEvent = (
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]
return value
}

56
src/lib/stores/ReposIdentifier.ts

@ -0,0 +1,56 @@ @@ -0,0 +1,56 @@
import type { RepoDIdentiferCollection } from '$lib/components/repo/type'
import { writable, type Writable } from 'svelte/store'
import { ensureRepo, eventToRepoEvent } from './repos'
import { base_relays, ndk } from './ndk'
import { repo_kind } from '$lib/kinds'
import { NDKEvent, NDKRelaySet } from '@nostr-dev-kit/ndk'
export const repos_identifer: {
[d: string]: Writable<RepoDIdentiferCollection>
} = {}
export const ensureIdentifierRepoCollection = (
identifier: string
): Writable<RepoDIdentiferCollection> => {
if (!Object.keys(repos_identifer).includes(identifier)) {
repos_identifer[identifier] = writable({
d: '',
events: [],
loading: true,
})
const sub = ndk.subscribe(
{ kinds: [repo_kind], '#d': [identifier] },
{ closeOnEose: true },
NDKRelaySet.fromRelayUrls(base_relays, ndk)
)
sub.on('event', (event: NDKEvent) => {
const repo_event = eventToRepoEvent(event)
if (repo_event && repo_event.identifier === identifier) {
ensureRepo(event).subscribe((repo_event) => {
repos_identifer[identifier].update((collection) => {
let events = collection.events
let exists = false
events.map((e) => {
if (e.author === repo_event.author) {
exists = true
return repo_event
} else return e
})
if (!exists) events = [...events, repo_event]
return {
...collection,
events,
}
})
})
}
})
sub.on('eose', () => {
repos_identifer[identifier].update((collection) => ({
...collection,
loading: false,
}))
})
}
return repos_identifer[identifier]
}

78
src/lib/stores/ReposPubkey.ts

@ -0,0 +1,78 @@ @@ -0,0 +1,78 @@
import type { SelectedPubkeyRepoCollections } from '$lib/components/repo/type'
import { get, writable, type Unsubscriber, type Writable } from 'svelte/store'
import { ensureRepoCollection, eventToRepoEvent } from './repos'
import { base_relays, ndk } from './ndk'
import { repo_kind } from '$lib/kinds'
import { NDKEvent, NDKRelaySet } from '@nostr-dev-kit/ndk'
import { extractAReference } from '$lib/components/repo/utils'
export const selected_npub_repo_collections: Writable<SelectedPubkeyRepoCollections> =
writable({
pubkey: '',
collections: [],
})
const unsubscribers: Unsubscriber[] = []
export const ensureSelectedPubkeyRepoCollection = (
pubkey: string
): Writable<SelectedPubkeyRepoCollections> => {
const collections = get(selected_npub_repo_collections)
if (collections.pubkey === pubkey) return selected_npub_repo_collections
// TODO call unsubscribers
selected_npub_repo_collections.set({
pubkey,
collections: [],
})
const sub = ndk.subscribe(
{ kinds: [repo_kind], authors: [pubkey] },
{ closeOnEose: true },
NDKRelaySet.fromRelayUrls(base_relays, ndk)
)
const identifiers: string[] = []
sub.on('event', (event: NDKEvent) => {
const repo_event = eventToRepoEvent(event)
if (
repo_event &&
repo_event.author === pubkey &&
!identifiers.includes(repo_event.identifier)
)
identifiers.push(repo_event.identifier)
})
sub.on('eose', () => {
identifiers.forEach((identifier) => {
unsubscribers.push(
ensureRepoCollection(`${repo_kind}:${pubkey}:${identifier}`).subscribe(
(c) => {
if (!c.maintainers.includes(pubkey)) return
selected_npub_repo_collections.update((selected_collections) => {
if (selected_collections.pubkey !== pubkey)
return { ...selected_collections }
let collection_in_selected_collections = false
const collections = selected_collections.collections.map(
(old_c) => {
const ref = extractAReference(old_c.selected_a)
if (ref && ref.identifier === identifier) {
collection_in_selected_collections = true
return {
...c,
}
}
return { ...old_c }
}
)
if (!collection_in_selected_collections) collections.push(c)
return {
...selected_collections,
collections,
}
})
}
)
)
})
})
return selected_npub_repo_collections
}

55
src/lib/stores/ReposRecent.ts

@ -0,0 +1,55 @@ @@ -0,0 +1,55 @@
import type { RepoRecentCollection } from '$lib/components/repo/type'
import { writable, type Writable } from 'svelte/store'
import { ensureRepo, eventToRepoEvent } from './repos'
import { base_relays, ndk } from './ndk'
import { repo_kind } from '$lib/kinds'
import { NDKEvent, NDKRelaySet } from '@nostr-dev-kit/ndk'
export const recent_repos: Writable<RepoRecentCollection> = writable({
events: [],
loading: true,
})
let started = false
export const ensureRecentRepos = (): Writable<RepoRecentCollection> => {
if (started) return recent_repos
started = true
const sub = ndk.subscribe(
{ kinds: [repo_kind] },
{ closeOnEose: true },
NDKRelaySet.fromRelayUrls(base_relays, ndk)
)
sub.on('event', (event: NDKEvent) => {
const repo_event = eventToRepoEvent(event)
if (repo_event) {
ensureRepo(event).subscribe((repo_event) => {
recent_repos.update((collection) => {
let events = collection.events
let exists = false
events.map((e) => {
if (
e.author === repo_event.author &&
e.identifier === repo_event.identifier
) {
exists = true
return repo_event
} else return e
})
if (!exists) events = [...events, repo_event]
return {
...collection,
events,
}
})
})
}
})
sub.on('eose', () => {
recent_repos.update((collection) => ({
...collection,
loading: false,
}))
})
return recent_repos
}

63
src/lib/stores/repo.ts

@ -30,52 +30,46 @@ selected_repo_collection.subscribe((collection) => { @@ -30,52 +30,46 @@ selected_repo_collection.subscribe((collection) => {
selected_repo_event.set({ ...selected_from_collection })
})
let selected_repo_unique_commit_or_identifier: string = ''
let selected_repo_a: string = ''
let selected_unsubscriber: Unsubscriber
export const ensureSelectedRepoCollection = (
unique_commit_or_identifier: string
a: string
): Writable<RepoCollection> => {
if (
selected_repo_unique_commit_or_identifier !== unique_commit_or_identifier
) {
if (selected_repo_a !== a) {
let loading = true
selected_repo_unique_commit_or_identifier = unique_commit_or_identifier
selected_repo_a = a
if (selected_unsubscriber) selected_unsubscriber()
selected_unsubscriber = ensureRepoCollection(
unique_commit_or_identifier
).subscribe((repo_collection) => {
selected_repo_collection.set({ ...repo_collection })
if (loading && !repo_collection.loading) {
loading = false
const repo_event = selectRepoFromCollection(repo_collection)
if (repo_event)
ensureRepoReadme(repo_event.clone, repo_collection.identifier)
selected_unsubscriber = ensureRepoCollection(a).subscribe(
(repo_collection) => {
selected_repo_collection.set({ ...repo_collection })
if (loading && !repo_collection.loading) {
loading = false
const repo_event = selectRepoFromCollection(repo_collection)
if (repo_event)
ensureRepoReadme(repo_event.clone, repo_collection.selected_a)
}
}
})
)
}
return selected_repo_collection
}
export const awaitSelectedRepoCollection = async (
unique_commit_or_identifier: string
a: string
): Promise<RepoCollection> => {
return new Promise((r) => {
const unsubscriber = ensureSelectedRepoCollection(
unique_commit_or_identifier
).subscribe((repo_collection) => {
if (
selected_repo_unique_commit_or_identifier ==
unique_commit_or_identifier &&
!repo_collection.loading
) {
setTimeout(() => {
if (unsubscriber) unsubscriber()
}, 5)
r({ ...repo_collection })
const unsubscriber = ensureSelectedRepoCollection(a).subscribe(
(repo_collection) => {
if (selected_repo_a === a && !repo_collection.loading) {
setTimeout(() => {
if (unsubscriber) unsubscriber()
}, 5)
r({ ...repo_collection })
}
}
})
)
})
}
@ -83,19 +77,14 @@ export const selected_repo_readme: Writable<RepoReadme> = writable({ @@ -83,19 +77,14 @@ export const selected_repo_readme: Writable<RepoReadme> = writable({
...readme_defaults,
})
const ensureRepoReadme = async (
clone: string[],
unique_commit_or_identifier: string
): Promise<void> => {
const ensureRepoReadme = async (clone: string[], a: string): Promise<void> => {
selected_repo_readme.set({ ...readme_defaults })
/** update writable unless selected readme has changed */
const update = (md: string | undefined = undefined): void => {
const latest_collection = get(selected_repo_collection)
if (
[latest_collection.identifier, latest_collection.unique_commit].includes(
unique_commit_or_identifier
)
[latest_collection.selected_a, latest_collection.selected_a].includes(a)
) {
selected_repo_readme.set({
md: md || '',

459
src/lib/stores/repos.ts

@ -1,64 +1,54 @@ @@ -1,64 +1,54 @@
import {
event_defaults,
collection_defaults,
type RepoCollection,
type RepoEvent,
type RepoSummary,
} from '$lib/components/repo/type'
import { NDKRelaySet, type NDKFilter, NDKEvent } from '@nostr-dev-kit/ndk'
import { NDKRelaySet, NDKEvent } from '@nostr-dev-kit/ndk'
import { get, writable, type Writable } from 'svelte/store'
import { base_relays, ndk } from './ndk'
import { repo_kind } from '$lib/kinds'
import { selectRepoFromCollection } from '$lib/components/repo/utils'
import { selected_repo_collection } from './repo'
import {
extractAReference,
selectRepoFromCollection,
} from '$lib/components/repo/utils'
import { nip19 } from 'nostr-tools'
export const repos: {
[unique_commit_or_identifier: string]: Writable<RepoCollection>
[a: string]: Writable<RepoEvent>
} = {}
export const returnRepoCollection = async (
unique_commit_or_identifier: string
): Promise<RepoCollection> => {
return new Promise((r) => {
const unsubscriber = ensureRepoCollection(
unique_commit_or_identifier
).subscribe((c) => {
if (!c.loading) {
setTimeout(() => {
if (unsubscriber) unsubscriber()
}, 5)
r(c)
}
})
})
}
export const repo_collections: {
[a: string]: Writable<RepoCollection>
} = {}
export const ensureRepoCollection = (
unique_commit_or_identifier: string
): Writable<RepoCollection> => {
if (!repos[unique_commit_or_identifier]) {
let base: RepoCollection = {
...collection_defaults,
export const ensureRepo = (a: string | NDKEvent): Writable<RepoEvent> => {
if (typeof a !== 'string') {
const repo_event = eventToRepoEvent(a)
if (repo_event) {
const a = repoEventToARef(repo_event)
repos[a] = writable({ ...repo_event, loading: true })
fetchReferencedBy(repo_event)
return repos[a]
}
if (unique_commit_or_identifier.length === 40) {
base = { ...base, unique_commit: unique_commit_or_identifier }
} else {
base = { ...base, identifier: unique_commit_or_identifier }
return repos['']
}
if (!repos[a]) {
const base: RepoEvent = {
...event_defaults,
}
repos[unique_commit_or_identifier] = writable(base)
const filter: NDKFilter = base.unique_commit
? {
kinds: [repo_kind],
'#r': [base.unique_commit],
limit: 100,
}
: {
kinds: [repo_kind],
'#d': [base.identifier],
limit: 100,
}
repos[a] = writable(base)
const a_ref = extractAReference(a)
if (!a_ref) return repos[a]
const { pubkey, identifier } = a_ref
const sub = ndk.subscribe(
filter,
{ kinds: [repo_kind], '#d': [identifier], authors: [pubkey] },
{
groupable: true,
// default 100
@ -69,36 +59,122 @@ export const ensureRepoCollection = ( @@ -69,36 +59,122 @@ export const ensureRepoCollection = (
)
sub.on('event', (event: NDKEvent) => {
const repo_event = eventToRepoEvent(event)
if (repo_event) {
const collection_for_unique_commit =
unique_commit_or_identifier.length === 40
// get repo events with same identifer but no unique_commit as
// the assumption is that they will be the same repo
if (collection_for_unique_commit) {
ensureRepoCollection(repo_event.identifier)
// we will process them just before we turn loading to true
}
repos[unique_commit_or_identifier].update((repo_collection) => {
return {
...repo_collection,
unique_commit:
repo_collection.unique_commit.length > 0
? repo_collection.unique_commit
: repo_event.unique_commit || '',
events: [...repo_collection.events, repo_event as RepoEvent],
}
})
fetchReferencedBy(
repo_event,
unique_commit_or_identifier,
collection_for_unique_commit
if (
identifier === repo_event.identifier &&
pubkey === repo_event.author
)
repos[a].update(() => {
return {
...repo_event,
}
})
fetchReferencedBy(repo_event)
// TODO fetch stargazers
}
})
sub.on('eose', () => {
// still awaiting reference_by at this point
repos[unique_commit_or_identifier].update((repo_collection) => {
repos[a].update((repo_event) => {
return {
...repo_event,
loading: false,
}
})
})
}
setTimeout(() => {
repos[a].update((repo_event) => {
return {
...repo_event,
loading: false,
}
})
}, 5000)
return repos[a]
}
export const returnRepo = async (a: string): Promise<RepoEvent> => {
return new Promise((r) => {
const unsubscriber = ensureRepo(a).subscribe((c) => {
if (!c.loading) {
setTimeout(() => {
if (unsubscriber) unsubscriber()
}, 5)
r(c)
}
})
})
}
export const ensureRepoCollection = (a: string): Writable<RepoCollection> => {
if (!repo_collections[a]) {
const base: RepoCollection = {
...collection_defaults,
selected_a: a,
}
repo_collections[a] = writable(base)
const a_ref = extractAReference(a)
if (!a_ref) return repo_collections[a]
const { pubkey, identifier } = a_ref
returnRepo(a).then(async (repo_event) => {
if (get(repo_collections[a]).events.length > 0) return
repo_collections[a].update((collection) => {
return {
...collection,
events: [repo_event],
maintainers: repo_event.maintainers,
most_recent_index: 0,
}
})
const new_maintainers: string[] = []
const addMaintainers = async (m: string) => {
const m_repo_event = await returnRepo(`${repo_kind}:${m}:${identifier}`)
repo_collections[a].update((collection) => {
m_repo_event.maintainers.forEach((m) => {
if (
![pubkey, ...collection.maintainers, ...new_maintainers].includes(
m
)
)
new_maintainers.push(m)
})
const events = [...collection.events, m_repo_event]
const most_recent = events.sort(
(a, b) => b.created_at - a.created_at
)[0]
return {
...collection,
events,
most_recent_index: events.findIndex(
(e) => e.author === most_recent.author
),
maintainers: [...collection.maintainers, ...new_maintainers],
}
})
}
// add maintainer events
await Promise.all(
repo_event.maintainers
.filter((m) => m !== pubkey)
.map((m) => addMaintainers(m))
)
// also add maintainers included in their maintainer events
while (new_maintainers.length > 0) {
await Promise.all(new_maintainers.map((m) => addMaintainers(m)))
}
repo_collections[a].update((repo_collection) => {
return {
...repo_collection,
loading: false,
@ -107,22 +183,35 @@ export const ensureRepoCollection = ( @@ -107,22 +183,35 @@ export const ensureRepoCollection = (
})
}
setTimeout(() => {
repos[unique_commit_or_identifier].update((collection) => {
repo_collections[a].update((repo_collection) => {
return {
...collection,
events: collection.events.map((e) => ({ ...e, loading: false })),
...repo_collection,
loading: false,
}
})
}, 5000)
return repos[unique_commit_or_identifier]
return repo_collections[a]
}
export const returnRepoCollection = async (
a: string
): Promise<RepoCollection> => {
return new Promise((r) => {
const unsubscriber = ensureRepoCollection(a).subscribe((c) => {
if (!c.loading) {
setTimeout(() => {
if (unsubscriber) unsubscriber()
}, 5)
r(c)
}
})
})
}
const fetchReferencedBy = (
repo_event: RepoEvent,
unique_commit_or_identifier: string,
collection_for_unique_commit: boolean
) => {
const repoEventToARef = (repo_event: RepoEvent): string =>
`${repo_kind}:${repo_event.author}:${repo_event.identifier}`
const fetchReferencedBy = (repo_event: RepoEvent) => {
const relays_to_use =
repo_event.relays.length < 3
? repo_event.relays
@ -130,66 +219,39 @@ const fetchReferencedBy = ( @@ -130,66 +219,39 @@ const fetchReferencedBy = (
const ref_sub = ndk.subscribe(
{
'#a': [
`${repo_kind}:${repo_event.maintainers[0]}:${repo_event.identifier}`,
],
'#a': [repoEventToARef(repo_event)],
limit: 10,
},
{
groupable: true,
// default 100
groupableDelay: 200,
closeOnEose: !get(selected_repo_collection)
.events.map((e) => e.identifier)
.includes(repo_event.identifier),
closeOnEose: true,
},
NDKRelaySet.fromRelayUrls(relays_to_use, ndk)
)
ref_sub.on('event', (ref_event: NDKEvent) => {
repos[unique_commit_or_identifier].update((repo_collection) => {
repos[repoEventToARef(repo_event)].update((repo_event) => {
return {
...repo_collection,
events: [
...repo_collection.events.map((latest_ref_event) => {
if (latest_ref_event.event_id === repo_event.event_id) {
return {
...latest_ref_event,
referenced_by: latest_ref_event.referenced_by
? [...latest_ref_event.referenced_by, ref_event.id]
: [ref_event.id],
}
}
return latest_ref_event
}),
],
...repo_event,
referenced_by: repo_event.referenced_by.includes(ref_event.id)
? [...repo_event.referenced_by]
: [...repo_event.referenced_by, ref_event.id],
most_recent_reference_timestamp:
ref_event.created_at &&
repo_event.most_recent_reference_timestamp < ref_event.created_at
? ref_event.created_at
: repo_event.most_recent_reference_timestamp,
}
})
})
ref_sub.on('eose', () => {
repos[unique_commit_or_identifier].update((repo_collection) => {
const events = [
...repo_collection.events.map((latest_ref_event) => {
if (latest_ref_event.event_id === repo_event.event_id) {
return {
...latest_ref_event,
// finished loading repo_event as we have all referenced_by events
loading: false,
}
}
return latest_ref_event
}),
]
const still_loading_events_in_collection = events.some((e) => e.loading)
if (collection_for_unique_commit && !still_loading_events_in_collection)
addEventsWithMatchingIdentifiers(events)
ref_sub.on('eose', () => {
repos[repoEventToARef(repo_event)].update((repo_event) => {
return {
...repo_collection,
events,
loading:
still_loading_events_in_collection ||
// for uninque_commit loading will complete after extra identifer events are added
collection_for_unique_commit,
...repo_event,
// finished loading repo_event as we have all referenced_by events
loading: false,
}
})
})
@ -202,7 +264,10 @@ export const eventToRepoEvent = (event: NDKEvent): RepoEvent | undefined => { @@ -202,7 +264,10 @@ export const eventToRepoEvent = (event: NDKEvent): RepoEvent | undefined => {
event.getMatchingTags('maintainers').forEach((t: string[]) => {
t.forEach((v, i) => {
if (i > 0 && v !== maintainers[0]) {
maintainers.push(v)
try {
nip19.npubEncode(v) // will throw if invalid hex pubkey
maintainers.push(v)
} catch {}
}
})
})
@ -233,6 +298,7 @@ export const eventToRepoEvent = (event: NDKEvent): RepoEvent | undefined => { @@ -233,6 +298,7 @@ export const eventToRepoEvent = (event: NDKEvent): RepoEvent | undefined => {
return {
event_id: event.id,
naddr: event.encode(),
author: event.pubkey,
identifier: event.replaceableDTag(),
unique_commit: event.tagValue('r') || undefined,
name: event.tagValue('name') || '',
@ -243,6 +309,7 @@ export const eventToRepoEvent = (event: NDKEvent): RepoEvent | undefined => { @@ -243,6 +309,7 @@ export const eventToRepoEvent = (event: NDKEvent): RepoEvent | undefined => {
maintainers,
relays,
referenced_by: [],
most_recent_reference_timestamp: event.created_at || 0,
created_at: event.created_at || 0,
loading: true, // loading until references fetched
}
@ -256,147 +323,29 @@ export const repoCollectionToSummary = ( @@ -256,147 +323,29 @@ export const repoCollectionToSummary = (
return {
name: selected.name,
identifier: selected.identifier,
naddr: selected.naddr,
unique_commit: selected.unique_commit,
description: selected.description,
maintainers: selected.maintainers,
loading: collection.loading,
created_at: selected.created_at,
most_recent_reference_timestamp: Math.max.apply(
0,
collection.events.map((e) => e.most_recent_reference_timestamp)
),
} as RepoSummary
}
/** to be called once all existing events have been found. this
* function is useful if we assume events with the same
* identifier reference the same repository */
const addEventsWithMatchingIdentifiers = (exisiting_events: RepoEvent[]) => {
// add events with same identifier but no unique_commit
exisiting_events
// filter out duplicate identifiers
.filter(
(e, i) =>
exisiting_events.findIndex((v) => v.identifier == e.identifier) === i
)
// subscribe to each identifier
.forEach((repo_event) => {
ensureRepoCollection(repo_event.identifier).subscribe(
(identiifer_collection) => {
// if extra event(s)
if (
identiifer_collection.events.some(
(identifier_repo) =>
!exisiting_events.some(
(e) => e.event_id === identifier_repo.event_id
)
)
) {
// add identifier events
repos[repo_event.unique_commit as string].update(
(repo_collection) => {
const events = [
...repo_collection.events,
...identiifer_collection.events
.filter(
(identifier_repo) =>
!repo_collection.events.some(
(e) => e.event_id === identifier_repo.event_id
)
)
.map((e) => ({ ...e })),
]
return {
...repo_collection,
events,
// if all RepoEvents are loaded, the collection is too
loading: events.some((e) => e.loading),
}
}
)
}
}
)
})
}
export const recent_repo_summaries: Writable<RepoSummary[]> = writable([])
export const recent_repo_summaries_loading = writable(false)
let began_fetching_repo_events = false
export const ensureRecentReposEvents = () => {
if (began_fetching_repo_events) return
began_fetching_repo_events = true
recent_repo_summaries_loading.set(true)
const sub = ndk.subscribe(
{
kinds: [repo_kind],
limit: 100,
},
{
closeOnEose: false,
}
)
const events: RepoEvent[] = []
sub.on('event', (event: NDKEvent) => {
const repo_event = eventToRepoEvent(event)
if (repo_event) events.push(repo_event)
})
sub.on('eose', () => {
const unique_commits = [
...new Set(events.map((e) => e.unique_commit).filter((s) => !!s)),
] as string[]
const identifers_not_linked_to_unique_commit = [
...new Set(events.map((e) => e.identifier)),
].filter(
(identifier) =>
!events.some((e) => e.identifier == identifier && e.unique_commit)
)
unique_commits
.concat(identifers_not_linked_to_unique_commit)
.forEach((c) => {
ensureRepoCollection(c).subscribe((repo_collection) => {
const summary = repoCollectionToSummary(repo_collection)
if (!summary) return
recent_repo_summaries.update((repos) => {
if (
summary.identifier === 'dotfiles' &&
repos.some((repo) => repo.identifier === 'dotfiles')
)
return [...repos]
// if duplicate
if (
repos.some(
(repo) =>
(repo.unique_commit &&
repo.unique_commit === repo_collection.unique_commit) ||
(!repo.unique_commit &&
repo.identifier === repo_collection.identifier)
)
) {
return [
// update summary
...repos.map((repo) => {
if (
summary &&
((repo.unique_commit &&
repo.unique_commit === repo_collection.unique_commit) ||
(!repo.unique_commit &&
repo.identifier === repo_collection.identifier))
)
return summary
return { ...repo }
}),
].sort((a, b) => b.created_at - a.created_at)
}
// if not duplicate - add summary
else if (summary) {
console.log(
`${summary.identifier} ${summary.unique_commit} col ${repo_collection.unique_commit}`
)
return [...repos, summary]
}
return [...repos]
})
})
})
recent_repo_summaries_loading.set(false)
})
export const repoEventToSummary = (event: RepoEvent): RepoSummary => {
return {
name: event.name,
identifier: event.identifier,
naddr: event.naddr,
unique_commit: event.unique_commit,
description: event.description,
maintainers: event.maintainers,
loading: event.loading,
created_at: event.created_at,
most_recent_reference_timestamp: event.most_recent_reference_timestamp,
} as RepoSummary
}

2
src/lib/wrappers/ComposeIssue.svelte

@ -61,7 +61,7 @@ @@ -61,7 +61,7 @@
submitting = false
submitted = true
setTimeout(() => {
goto(`/repo/${repo_event.identifier}/issue/${event.id}`)
goto(`/r/${repo_event.identifier}/issues/${event.id}`)
}, 2000)
} catch {}
}

9
src/lib/wrappers/ComposeReply.svelte

@ -24,7 +24,7 @@ @@ -24,7 +24,7 @@
let submitted = false
let edit_mode = false
$: {
repo_identifier = $selected_repo_collection.identifier
repo_identifier = $selected_repo_event.identifier
selected_issue_full
selected_proposal_or_issue =
type === 'proposal' ? $selected_proposal_full : $selected_issue_full
@ -66,10 +66,9 @@ @@ -66,10 +66,9 @@
if ($selected_repo_event.unique_commit) {
new_event.tags.push(['r', $selected_repo_event.unique_commit])
}
new_event.tags.push([
'a',
`${repo_kind}:${$selected_repo_event.maintainers[0]}:${repo_identifier}`,
])
$selected_repo_collection.maintainers.forEach((m) => {
new_event.tags.push(['a', `${repo_kind}:${m}:${repo_identifier}`])
})
let parent_event_user_relay = user_relays[event.pubkey]
? get(user_relays[event.pubkey]).ndk_relays?.writeRelayUrls[0]
: undefined

4
src/lib/wrappers/RepoDetails.svelte

@ -5,9 +5,9 @@ @@ -5,9 +5,9 @@
selected_repo_event,
} from '$lib/stores/repo'
export let repo_id = ''
export let a = ''
ensureSelectedRepoCollection(repo_id)
ensureSelectedRepoCollection(a)
</script>
<RepoDetails {...$selected_repo_event} />

9
src/lib/wrappers/RepoMenu.svelte

@ -5,17 +5,16 @@ @@ -5,17 +5,16 @@
import { proposal_status_open } from '$lib/kinds'
import { issue_summaries } from '$lib/stores/Issues'
import { proposal_summaries } from '$lib/stores/Proposals'
import { selected_repo_readme } from '$lib/stores/repo'
import { selected_repo_event, selected_repo_readme } from '$lib/stores/repo'
export let selected_tab: RepoPage = 'about'
export let identifier = ''
</script>
<div class="flex border-b border-base-400">
<div role="tablist" class="tabs tabs-bordered flex-none">
{#if !$selected_repo_readme.failed}
<a
href={`/repo/${identifier}`}
href={`/r/${$selected_repo_event.naddr}`}
class="tab"
class:tab-active={selected_tab === 'about'}
>
@ -23,7 +22,7 @@ @@ -23,7 +22,7 @@
</a>
{/if}
<a
href={`/repo/${identifier}/proposals`}
href={`/r/${$selected_repo_event.naddr}/proposals`}
class="tab"
class:tab-active={selected_tab === 'proposals'}
>
@ -44,7 +43,7 @@ @@ -44,7 +43,7 @@
{/if}
</a>
<a
href={`/repo/${identifier}/issues`}
href={`/r/${$selected_repo_event.naddr}/issues`}
class="tab"
class:tab-active={selected_tab === 'issues'}
>

38
src/lib/wrappers/RepoPageWrapper.svelte

@ -10,32 +10,36 @@ @@ -10,32 +10,36 @@
import { ensureProposalSummaries } from '$lib/stores/Proposals'
import { ensureIssueSummaries } from '$lib/stores/Issues'
import type { RepoPage } from '$lib/components/repo/type'
import { naddrToRepoA } from '$lib/components/repo/utils'
export let identifier = ''
export let repo_naddr = ''
export let selected_tab: RepoPage = 'about'
export let with_side_bar = true
export let show_details_on_mobile = false
ensureSelectedRepoCollection(identifier)
ensureProposalSummaries(identifier)
ensureIssueSummaries(identifier)
let invalid_naddr = false
let a = ''
let repo_error = false
$: {
const a_result = naddrToRepoA(repo_naddr)
if (a_result) {
a = a_result
invalid_naddr = false
ensureSelectedRepoCollection(a)
ensureProposalSummaries(a)
ensureIssueSummaries(a)
} else {
invalid_naddr = true
}
}
let waited_5_secs = false
setTimeout(() => {
waited_5_secs = true
}, 5000)
$: {
repo_error =
!$selected_repo_collection.loading &&
waited_5_secs &&
$selected_repo_event.name.length === 0
}
</script>
{#if repo_error}
{#if invalid_naddr || (waited_5_secs && $selected_repo_collection.loading && $selected_repo_event.name.length)}
<Container>
<div role="alert" class="alert alert-error m-auto mt-6 w-full max-w-xs">
<svg
@ -50,7 +54,11 @@ @@ -50,7 +54,11 @@
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 repository event</span>
{#if invalid_naddr}
<span>Error! invalid naddr in url: {repo_naddr}</span>
{:else}
<span>Error! cannot find repository event: {repo_naddr}</span>
{/if}
</div>
</Container>
{:else}
@ -69,7 +77,7 @@ @@ -69,7 +77,7 @@
<h4 class="">Repository Details</h4>
</div>
<div class="prose my-3 px-6 md:ml-2 md:px-0">
<RepoDetails repo_id={identifier} />
<RepoDetails {a} />
</div>
</div>
</div>

17
src/lib/wrappers/ReposRecent.svelte

@ -1,16 +1,17 @@ @@ -1,16 +1,17 @@
<script lang="ts">
import ReposSummaryList from '$lib/components/ReposSummaryList.svelte'
import {
ensureRecentReposEvents,
recent_repo_summaries,
recent_repo_summaries_loading,
} from '$lib/stores/repos'
import { summary_defaults } from '$lib/components/repo/type'
import { ensureRecentRepos, recent_repos } from '$lib/stores/ReposRecent'
import { repoEventToSummary } from '$lib/stores/repos'
ensureRecentReposEvents()
ensureRecentRepos()
</script>
<ReposSummaryList
title="Latest Repositories"
repos={$recent_repo_summaries}
loading={$recent_repo_summaries_loading}
repos={$recent_repos.events.map(
(c) => repoEventToSummary(c) || { ...summary_defaults }
)}
group_by="name"
loading={$recent_repos.loading}
/>

2
src/routes/+page.svelte

@ -79,7 +79,7 @@ @@ -79,7 +79,7 @@
</h3>
</div>
<div class="prose">
to host the authoratitive code. eg. Gitea, Github, Gitlab,
to host the authoritative code. eg. Gitea, Github, Gitlab,
BitBucket...
</div>
</div>

24
src/routes/about/+page.svelte

@ -44,14 +44,20 @@ @@ -44,14 +44,20 @@
<div>
<h4 class="my-1 font-bold">please provide feedback</h4>
<p class="mb-0 text-sm">
via an <a class="link-secondary" href="/repo/ngit">ngit issue</a>, a
<a class="link-secondary" href="/repo/gitworkshop"
via an <a
class="link-secondary"
href="/r/naddr1qqzxuemfwsqs6amnwvaz7tmwdaejumr0dspzpgqgmmc409hm4xsdd74sf68a2uyf9pwel4g9mfdg8l5244t6x4jdqvzqqqrhnym0k2qj"
>ngit issue</a
>, a
<a
class="link-secondary"
href="/r/naddr1qq9kw6t5wahhy6mndphhqqgkwaehxw309aex2mrp0yhxummnw3ezucnpdejqyg9qpr00z4uklw56p4h6kp8gl4ts3y59m874qhd94ql732k40g6kf5psgqqqw7vs2nfsd9"
>gitworkshop.dev issue</a
>
or directly to
<a
class="link-primary"
href="https://njump.me/nprofile1qy88wumn8ghj7mn0wvhxcmmv9uq3vamnwvaz7tmsw4e8qmr9wfjkccte9e3k7mf0qqs2qzx779ted7af5rt04vzw3l2hpzfgtk0a2pw6t2plaz4d2734vng80y96x"
href="/p/nprofile1qy88wumn8ghj7mn0wvhxcmmv9uq3vamnwvaz7tmsw4e8qmr9wfjkccte9e3k7mf0qqs2qzx779ted7af5rt04vzw3l2hpzfgtk0a2pw6t2plaz4d2734vng80y96x"
>DanConwayDev</a
> on nostr
</p>
@ -283,14 +289,20 @@ @@ -283,14 +289,20 @@
<div>
<h4 class="my-1 font-bold">please provide feedback</h4>
<p class="mb-0 text-sm">
via an <a class="link-secondary" href="/repo/ngit">ngit issue</a>, a
<a class="link-secondary" href="/repo/gitworkshop"
via an <a
class="link-secondary"
href="/r/naddr1qqzxuemfwsqs6amnwvaz7tmwdaejumr0dspzpgqgmmc409hm4xsdd74sf68a2uyf9pwel4g9mfdg8l5244t6x4jdqvzqqqrhnym0k2qj"
>ngit issue</a
>, a
<a
class="link-secondary"
href="/r/naddr1qq9kw6t5wahhy6mndphhqqgkwaehxw309aex2mrp0yhxummnw3ezucnpdejqyg9qpr00z4uklw56p4h6kp8gl4ts3y59m874qhd94ql732k40g6kf5psgqqqw7vs2nfsd9"
>gitworkshop.dev issue</a
>
or directly to
<a
class="link-primary"
href="https://njump.me/nprofile1qy88wumn8ghj7mn0wvhxcmmv9uq3vamnwvaz7tmsw4e8qmr9wfjkccte9e3k7mf0qqs2qzx779ted7af5rt04vzw3l2hpzfgtk0a2pw6t2plaz4d2734vng80y96x"
href="/p/nprofile1qy88wumn8ghj7mn0wvhxcmmv9uq3vamnwvaz7tmsw4e8qmr9wfjkccte9e3k7mf0qqs2qzx779ted7af5rt04vzw3l2hpzfgtk0a2pw6t2plaz4d2734vng80y96x"
>DanConwayDev</a
> on nostr
</p>

122
src/routes/e/[nostr_ref]/+page.svelte

@ -0,0 +1,122 @@ @@ -0,0 +1,122 @@
<script lang="ts">
import { nip19 } from 'nostr-tools'
import Container from '$lib/components/Container.svelte'
import { goto } from '$app/navigation'
import { issue_kind, patch_kind, repo_kind } from '$lib/kinds'
import { base_relays, ndk } from '$lib/stores/ndk'
import { NDKEvent, NDKRelaySet } from '@nostr-dev-kit/ndk'
import { aToNaddr } from '$lib/components/repo/utils'
import { ensureIssueFull } from '$lib/stores/Issue'
import { ensureProposalFull } from '$lib/stores/Proposal'
export let data: { nostr_ref: string }
let error = false
let error_msg =
'reference in URL is not a repository, proposal, issue or npub reference'
let waited = false
const showError = (msg?: string) => {
if (msg) error_msg = msg
error = true
waited = true
}
let lookupEvent = (id: string) => {
let sub = ndk.subscribe(
{
ids: [id],
limit: 100,
},
{
closeOnEose: false,
},
NDKRelaySet.fromRelayUrls(base_relays, ndk)
)
sub.on('event', (event: NDKEvent) => {
try {
if (event.id == id) {
let a = event.tagValue('a')
if (!a) {
showError(
'found event but it contains an invalid "a" tag reference'
)
} else {
if (event.kind === issue_kind) {
ensureIssueFull(a, id)
goto(`/r/${aToNaddr(a)}/issues/${nip19.noteEncode(id)}`)
} else if (event.kind === patch_kind) {
ensureProposalFull(a, id)
goto(`/r/${aToNaddr(a)}/proposals/${nip19.noteEncode(id)}`)
} else {
showError()
}
}
}
} catch {}
})
sub.on('eose', () => {
showError('cannot find event')
})
}
$: {
try {
let decoded = nip19.decode(data.nostr_ref)
if (decoded.type === 'npub' || decoded.type === 'nprofile')
goto(`/p/${data.nostr_ref}`)
else if (decoded.type === 'naddr' && decoded.data.kind === repo_kind) {
goto(`/r/${data.nostr_ref}`)
} else if (decoded.type === 'nrelay' || decoded.type === 'nsec') {
showError()
} else if (typeof decoded.data === 'string') {
lookupEvent(decoded.data)
} else if (
(decoded.type === 'nevent' || decoded.type === 'note') &&
// doesnt have a confirmed kind of something other than issue or patch
!(
decoded.data.kind &&
[patch_kind, issue_kind].includes(decoded.data.kind)
)
) {
lookupEvent(decoded.data.id)
} else {
showError()
}
} catch {
try {
nip19.noteEncode(data.nostr_ref) // will throw if invalid event id
lookupEvent(data.nostr_ref)
} catch {
showError()
}
}
}
</script>
{#if error && waited}
<Container>
<div
role="alert"
class="wrap alert alert-error m-auto mt-6 w-full max-w-lg"
>
<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! {error_msg}: {data.nostr_ref}</span>
</div>
</Container>
{:else}
<Container>loading...</Container>
{/if}

5
src/routes/e/[nostr_ref]/+page.ts

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
export const load = ({ params }: { params: { nostr_ref: string } }) => {
return {
nostr_ref: params.nostr_ref,
}
}

12
src/routes/ngit/+page.svelte

@ -36,14 +36,20 @@ @@ -36,14 +36,20 @@
<div>
<h4 class="my-1 font-bold">please provide feedback</h4>
<p class="mb-0 text-sm">
via an <a class="link-secondary" href="/repo/ngit">ngit issue</a>, a
<a class="link-secondary" href="/repo/gitworkshop"
via an <a
class="link-secondary"
href="/r/naddr1qqzxuemfwsqs6amnwvaz7tmwdaejumr0dspzpgqgmmc409hm4xsdd74sf68a2uyf9pwel4g9mfdg8l5244t6x4jdqvzqqqrhnym0k2qj"
>ngit issue</a
>, a
<a
class="link-secondary"
href="/r/naddr1qq9kw6t5wahhy6mndphhqqgkwaehxw309aex2mrp0yhxummnw3ezucnpdejqyg9qpr00z4uklw56p4h6kp8gl4ts3y59m874qhd94ql732k40g6kf5psgqqqw7vs2nfsd9"
>gitworkshop.dev issue</a
>
or directly to
<a
class="link-primary"
href="https://njump.me/nprofile1qy88wumn8ghj7mn0wvhxcmmv9uq3vamnwvaz7tmsw4e8qmr9wfjkccte9e3k7mf0qqs2qzx779ted7af5rt04vzw3l2hpzfgtk0a2pw6t2plaz4d2734vng80y96x"
href="/p/nprofile1qy88wumn8ghj7mn0wvhxcmmv9uq3vamnwvaz7tmsw4e8qmr9wfjkccte9e3k7mf0qqs2qzx779ted7af5rt04vzw3l2hpzfgtk0a2pw6t2plaz4d2734vng80y96x"
>DanConwayDev</a
> on nostr
</p>

19
src/routes/p/[npub]/+page.svelte

@ -2,12 +2,13 @@ @@ -2,12 +2,13 @@
import { nip19 } from 'nostr-tools'
import Container from '$lib/components/Container.svelte'
import ReposSummaryList from '$lib/components/ReposSummaryList.svelte'
import {
ensureRecentReposEvents,
recent_repo_summaries,
recent_repo_summaries_loading,
} from '$lib/stores/repos'
import UserHeader from '$lib/components/users/UserHeader.svelte'
import {
ensureSelectedPubkeyRepoCollection,
selected_npub_repo_collections,
} from '$lib/stores/ReposPubkey'
import { repoCollectionToSummary } from '$lib/stores/repos'
import { summary_defaults } from '$lib/components/repo/type'
export let data: { npub: string }
@ -19,11 +20,11 @@ @@ -19,11 +20,11 @@
if (decoded.type === 'npub') pubkey = decoded.data
else if (decoded.type === 'nprofile') pubkey = decoded.data.pubkey
else error = true
if (pubkey) ensureSelectedPubkeyRepoCollection(pubkey)
} catch {
error = true
}
}
ensureRecentReposEvents()
</script>
{#if error}
@ -56,10 +57,10 @@ @@ -56,10 +57,10 @@
<div class="divider"></div>
<ReposSummaryList
title="Repositories"
repos={$recent_repo_summaries.filter(
(summary) => pubkey && summary.maintainers.includes(pubkey)
repos={$selected_npub_repo_collections.collections.map(
(c) => repoCollectionToSummary(c) || { ...summary_defaults }
)}
loading={$recent_repo_summaries_loading}
loading={false}
/>
</div>
</Container>

8
src/routes/repo/[repo_id]/+page.svelte → src/routes/r/[repo_naddr]/+page.svelte

@ -4,16 +4,16 @@ @@ -4,16 +4,16 @@
import RepoPageWrapper from '$lib/wrappers/RepoPageWrapper.svelte'
import { goto } from '$app/navigation'
export let data: { repo_id: string }
let identifier = data.repo_id
export let data: { repo_naddr: string }
let repo_naddr = data.repo_naddr
$: {
if ($selected_repo_readme.failed === true)
goto(`/repo/${identifier}/proposals`)
goto(`/r/${repo_naddr}/proposals`)
}
</script>
<RepoPageWrapper {identifier} selected_tab="about" show_details_on_mobile>
<RepoPageWrapper {repo_naddr} selected_tab="about" show_details_on_mobile>
<div class="my-3 rounded-lg border border-base-400">
<div class="border-b border-base-400 bg-base-300 px-6 py-3">
<h4 class="">README.md</h4>

7
src/routes/r/[repo_naddr]/+page.ts

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
export const load = ({ params }: { params: { repo_naddr: string } }) => {
return {
repo_naddr: params.repo_naddr,
}
}
export const ssr = false

8
src/routes/repo/[repo_id]/issues/+page.svelte → src/routes/r/[repo_naddr]/issues/+page.svelte

@ -10,14 +10,14 @@ @@ -10,14 +10,14 @@
import { issue_summaries } from '$lib/stores/Issues'
import RepoPageWrapper from '$lib/wrappers/RepoPageWrapper.svelte'
export let data: { repo_id: string }
let identifier = data.repo_id
export let data: { repo_naddr: string }
let repo_naddr = data.repo_naddr
let status: number = proposal_status_open
let filtered: IssueSummary[] = []
$: filtered = $issue_summaries.summaries.filter((s) => s.status === status)
</script>
<RepoPageWrapper {identifier} selected_tab="issues">
<RepoPageWrapper {repo_naddr} selected_tab="issues">
<div class="mt-2 rounded-tr-lg border border-base-400">
<div class="flex rounded-r-lg bg-slate-900">
<div class="flex-none">
@ -67,7 +67,7 @@ @@ -67,7 +67,7 @@
<div class="flex-none">
<a
class="btn btn-success btn-sm h-full text-base-400"
href="/repo/{identifier}/issues/new"
href={`/r/${repo_naddr}/issues/new`}
>
create issue
</a>

5
src/routes/r/[repo_naddr]/issues/+page.ts

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
export const load = ({ params }: { params: { repo_naddr: string } }) => {
return {
repo_naddr: params.repo_naddr,
}
}

60
src/routes/repo/[repo_id]/issue/[issue_id]/+page.svelte → src/routes/r/[repo_naddr]/issues/[issue_nip19]/+page.svelte

@ -1,9 +1,4 @@ @@ -1,9 +1,4 @@
<script lang="ts">
import {
ensureSelectedRepoCollection,
selected_repo_collection,
selected_repo_event,
} from '$lib/stores/repo'
import {
ensureIssueFull,
selected_issue_full,
@ -14,36 +9,51 @@ @@ -14,36 +9,51 @@
import ProposalHeader from '$lib/components/proposals/ProposalHeader.svelte'
import ProposalDetails from '$lib/components/proposals/ProposalDetails.svelte'
import RepoPageWrapper from '$lib/wrappers/RepoPageWrapper.svelte'
import { naddrToRepoA, neventOrNoteToHexId } from '$lib/components/repo/utils'
export let data: {
repo_id: string
issue_id: string
repo_naddr: string
issue_nip19: string
}
let repo_naddr = data.repo_naddr
let a = ''
$: {
const a_result = naddrToRepoA(repo_naddr)
if (a_result) a = a_result
}
let repo_id = data.repo_id
let issue_id = data.issue_id
let issue_nip19 = data.issue_nip19
let issue_id = ''
let invalid_issue_ref = false
$: {
const issue_nip19_result = neventOrNoteToHexId(issue_nip19)
ensureSelectedRepoCollection(repo_id)
ensureIssueFull(repo_id, issue_id)
if (issue_nip19_result) {
issue_id = issue_nip19_result
invalid_issue_ref = false
ensureIssueFull(a, issue_id)
} else {
invalid_issue_ref = true
}
}
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
}
let waited_5_secs = false
setTimeout(() => {
waited_5_secs = true
}, 5000)
</script>
<RepoPageWrapper
identifier={repo_id}
with_side_bar={false}
selected_tab="issues"
>
{#if issue_error}
<RepoPageWrapper {repo_naddr} with_side_bar={false} selected_tab="issues">
{#if invalid_issue_ref || (waited_5_secs && issue_error)}
<Container>
<div role="alert" class="alert alert-error m-auto mt-6 w-full max-w-xs">
<svg
@ -58,8 +68,14 @@ @@ -58,8 +68,14 @@
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
>
{#if invalid_issue_ref}<span
>Error! invalid Issue reference: {issue_id} '{issue_nip19}'</span
>
{:else}
<span
>Error! cannot find Issue {repo_error ? 'or repo ' : ''}event</span
>
{/if}
</div>
</Container>
{:else}

10
src/routes/r/[repo_naddr]/issues/[issue_nip19]/+page.ts

@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
export const load = ({
params,
}: {
params: { issue_nip19: string; repo_naddr: string }
}) => {
return {
repo_naddr: decodeURIComponent(params.repo_naddr),
issue_nip19: params.issue_nip19,
}
}

36
src/routes/repo/[repo_id]/issues/new/+page.svelte → src/routes/r/[repo_naddr]/issues/new/+page.svelte

@ -8,27 +8,31 @@ @@ -8,27 +8,31 @@
import RepoHeader from '$lib/components/repo/RepoHeader.svelte'
import Container from '$lib/components/Container.svelte'
import ComposeIssue from '$lib/wrappers/ComposeIssue.svelte'
import { naddrToRepoA } from '$lib/components/repo/utils'
export let data: { repo_id: string }
let identifier = data.repo_id
export let data: { repo_naddr: string }
let repo_naddr = data.repo_naddr
let invalid_naddr = false
let a = ''
ensureSelectedRepoCollection(identifier)
let repo_error = false
$: {
const a_result = naddrToRepoA(repo_naddr)
if (a_result) {
a = a_result
invalid_naddr = false
ensureSelectedRepoCollection(a)
} else {
invalid_naddr = true
}
}
let waited_5_secs = false
setTimeout(() => {
waited_5_secs = true
}, 5000)
$: {
repo_error =
!$selected_repo_collection.loading &&
waited_5_secs &&
$selected_repo_event.name.length === 0
}
</script>
{#if repo_error}
{#if invalid_naddr || (waited_5_secs && $selected_repo_collection.loading && $selected_repo_event.name.length)}
<Container>
<div role="alert" class="alert alert-error m-auto mt-6 w-full max-w-xs">
<svg
@ -43,7 +47,11 @@ @@ -43,7 +47,11 @@
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 repository event</span>
{#if invalid_naddr}
<span>Error! invalid naddr in url: {repo_naddr}</span>
{:else}
<span>Error! cannot find repository event: {repo_naddr}</span>
{/if}
</div>
</Container>
{:else}
@ -56,7 +64,7 @@ @@ -56,7 +64,7 @@
<ComposeIssue repo_event={$selected_repo_event} />
</div>
<div class="prose ml-2 hidden w-1/3 lg:flex">
<RepoDetails repo_id={identifier} />
<RepoDetails {a} />
</div>
</div>
</Container>

5
src/routes/r/[repo_naddr]/issues/new/+page.ts

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
export const load = ({ params }: { params: { repo_naddr: string } }) => {
return {
repo_naddr: params.repo_naddr,
}
}

7
src/routes/repo/[repo_id]/proposals/+page.svelte → src/routes/r/[repo_naddr]/proposals/+page.svelte

@ -11,14 +11,15 @@ @@ -11,14 +11,15 @@
import { proposal_summaries } from '$lib/stores/Proposals'
import RepoPageWrapper from '$lib/wrappers/RepoPageWrapper.svelte'
export let data: { repo_id: string }
let identifier = data.repo_id
export let data: { repo_naddr: string }
let repo_naddr = data.repo_naddr
let status: number = proposal_status_open
let filtered: ProposalSummary[] = []
$: filtered = $proposal_summaries.summaries.filter((s) => s.status === status)
</script>
<RepoPageWrapper {identifier} selected_tab="proposals">
<RepoPageWrapper {repo_naddr} selected_tab="proposals">
<div class="mt-2 border border-base-400">
<div class="flex bg-slate-900">
<div class="tabs tabs-lifted tabs-xs flex-none p-2">

5
src/routes/r/[repo_naddr]/proposals/+page.ts

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
export const load = ({ params }: { params: { repo_naddr: string } }) => {
return {
repo_naddr: params.repo_naddr,
}
}

107
src/routes/r/[repo_naddr]/proposals/[proposal_nip19]/+page.svelte

@ -0,0 +1,107 @@ @@ -0,0 +1,107 @@
<script lang="ts">
import {
ensureProposalFull,
selected_proposal_full,
selected_proposal_replies,
} from '$lib/stores/Proposal'
import Thread from '$lib/wrappers/Thread.svelte'
import Container from '$lib/components/Container.svelte'
import ProposalHeader from '$lib/components/proposals/ProposalHeader.svelte'
import ProposalDetails from '$lib/components/proposals/ProposalDetails.svelte'
import RepoPageWrapper from '$lib/wrappers/RepoPageWrapper.svelte'
import { naddrToRepoA, neventOrNoteToHexId } from '$lib/components/repo/utils'
export let data: {
repo_naddr: string
proposal_nip19: string
}
let repo_naddr = data.repo_naddr
let a = ''
$: {
const a_result = naddrToRepoA(repo_naddr)
if (a_result) a = a_result
}
let proposal_nip19 = data.proposal_nip19
let proposal_id = ''
let invalid_proposal_ref = false
$: {
const proposal_nip19_result = neventOrNoteToHexId(proposal_nip19)
if (proposal_nip19_result) {
proposal_id = proposal_nip19_result
invalid_proposal_ref = false
ensureProposalFull(a, proposal_id)
} else {
invalid_proposal_ref = true
}
}
let repo_error = false
let proposal_error = false
$: {
proposal_error =
!$selected_proposal_full.summary.loading &&
$selected_proposal_full.summary.created_at === 0
}
let waited_5_secs = false
setTimeout(() => {
waited_5_secs = true
}, 5000)
</script>
<RepoPageWrapper {repo_naddr} with_side_bar={false} selected_tab="proposals">
{#if invalid_proposal_ref || (waited_5_secs && proposal_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
>
{#if invalid_proposal_ref}<span
>Error! invalid Issue reference: {proposal_id} '{proposal_nip19}'</span
>
{:else}
<span
>Error! cannot find Issue {repo_error ? 'or repo ' : ''}event</span
>
{/if}
</div>
</Container>
{:else}
<ProposalHeader {...$selected_proposal_full.summary} />
<Container>
<div class="mx-auto max-w-6xl lg:flex">
<div class="md:mr-2 lg:w-2/3">
<div class="max-w-4xl">
{#if $selected_proposal_full.proposal_event}
<Thread
type="proposal"
event={$selected_proposal_full.proposal_event}
replies={$selected_proposal_replies}
/>
{/if}
</div>
</div>
<div class="prose ml-2 hidden w-1/3 lg:flex">
<ProposalDetails
type="proposal"
summary={$selected_proposal_full.summary}
labels={$selected_proposal_full.labels}
loading={$selected_proposal_full.loading}
/>
</div>
</div>
</Container>
{/if}
</RepoPageWrapper>

10
src/routes/r/[repo_naddr]/proposals/[proposal_nip19]/+page.ts

@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
export const load = ({
params,
}: {
params: { proposal_nip19: string; repo_naddr: string }
}) => {
return {
repo_naddr: decodeURIComponent(params.repo_naddr),
proposal_nip19: params.proposal_nip19,
}
}

7
src/routes/repo/[repo_id]/+page.ts

@ -1,7 +0,0 @@ @@ -1,7 +0,0 @@
export const load = ({ params }: { params: { repo_id: string } }) => {
return {
repo_id: decodeURIComponent(params.repo_id),
}
}
export const ssr = false

10
src/routes/repo/[repo_id]/issue/[issue_id]/+page.ts

@ -1,10 +0,0 @@ @@ -1,10 +0,0 @@
export const load = ({
params,
}: {
params: { issue_id: string; repo_id: string }
}) => {
return {
repo_id: decodeURIComponent(params.repo_id),
issue_id: params.issue_id,
}
}

5
src/routes/repo/[repo_id]/issues/+page.ts

@ -1,5 +0,0 @@ @@ -1,5 +0,0 @@
export const load = ({ params }: { params: { repo_id: string } }) => {
return {
repo_id: decodeURIComponent(params.repo_id),
}
}

5
src/routes/repo/[repo_id]/issues/new/+page.ts

@ -1,5 +0,0 @@ @@ -1,5 +0,0 @@
export const load = ({ params }: { params: { repo_id: string } }) => {
return {
repo_id: decodeURIComponent(params.repo_id),
}
}

122
src/routes/repo/[repo_id]/proposal/[proposal_id]/+page.svelte

@ -1,122 +0,0 @@ @@ -1,122 +0,0 @@
<script lang="ts">
import {
ensureSelectedRepoCollection,
selected_repo_collection,
selected_repo_event,
} from '$lib/stores/repo'
import {
ensureProposalFull,
selected_proposal_full,
selected_proposal_replies,
} from '$lib/stores/Proposal'
import ProposalHeader from '$lib/components/proposals/ProposalHeader.svelte'
import Thread from '$lib/wrappers/Thread.svelte'
import ProposalDetails from '$lib/components/proposals/ProposalDetails.svelte'
import Container from '$lib/components/Container.svelte'
import RepoPageWrapper from '$lib/wrappers/RepoPageWrapper.svelte'
export let data: {
repo_id: string
proposal_id: string
}
let repo_id = data.repo_id
let proposal_id = data.proposal_id
ensureSelectedRepoCollection(repo_id)
ensureProposalFull(repo_id, proposal_id)
let repo_error = false
let proposal_error = false
$: {
repo_error =
!$selected_repo_collection.loading &&
$selected_repo_event.name.length === 0
proposal_error =
!$selected_proposal_full.summary.loading &&
$selected_proposal_full.summary.created_at === 0
}
</script>
<RepoPageWrapper
identifier={repo_id}
with_side_bar={false}
selected_tab="proposals"
>
{#if proposal_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 Proposal {repo_error ? 'or repo ' : ''}event</span
>
</div>
</Container>
{:else}
<ProposalHeader {...$selected_proposal_full.summary} />
<Container>
<div class="mx-auto max-w-6xl lg:flex">
<div class="lg:w-2/3 xl:mr-2">
<div class="max-w-4xl">
{#if $selected_proposal_full.proposal_event}
<Thread
type="proposal"
event={$selected_proposal_full.proposal_event}
replies={$selected_proposal_replies}
/>
{/if}
</div>
</div>
<div class="prose ml-2 hidden w-1/3 lg:block">
<div role="alert" class="alert mt-3">
<div class="text-center">
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="inline h-6 w-6 shrink-0 stroke-info"
><path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
></path></svg
>
<h3 class="prose mx-1 inline text-sm font-bold">
view in local git repository
</h3>
</div>
<p class="mx-0 mb-1 mt-2 text-xs">
<a href="/ngit">install ngit</a>, run
<span class="rounded bg-neutral p-1 font-mono"
><span class="py-3">ngit list</span></span
> from the local repository and select the proposal title
</p>
</div>
</div>
<div class="block">
<ProposalDetails
type="proposal"
summary={$selected_proposal_full.summary}
labels={$selected_proposal_full.labels}
loading={$selected_proposal_full.loading}
/>
</div>
</div>
</div>
</Container>
{/if}
</RepoPageWrapper>

10
src/routes/repo/[repo_id]/proposal/[proposal_id]/+page.ts

@ -1,10 +0,0 @@ @@ -1,10 +0,0 @@
export const load = ({
params,
}: {
params: { proposal_id: string; repo_id: string }
}) => {
return {
repo_id: decodeURIComponent(params.repo_id),
proposal_id: params.proposal_id,
}
}

5
src/routes/repo/[repo_id]/proposals/+page.ts

@ -1,5 +0,0 @@ @@ -1,5 +0,0 @@
export const load = ({ params }: { params: { repo_id: string } }) => {
return {
repo_id: decodeURIComponent(params.repo_id),
}
}

20
src/routes/repo/[repo_identifier]/+page.svelte

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
<script lang="ts">
import Container from '$lib/components/Container.svelte'
import ReposSummaryList from '$lib/components/ReposSummaryList.svelte'
import { ensureIdentifierRepoCollection } from '$lib/stores/ReposIdentifier'
import { repoEventToSummary } from '$lib/stores/repos'
export let data: { repo_identifier: string }
let collection = ensureIdentifierRepoCollection(data.repo_identifier || '')
</script>
<Container>
<div class="m-5">
<ReposSummaryList
title={`repositories for '${data.repo_identifier}'`}
repos={$collection.events.map(repoEventToSummary)}
loading={$collection.loading}
/>
</div>
</Container>

7
src/routes/repo/[repo_identifier]/+page.ts

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
export const load = ({ params }: { params: { repo_identifier: string } }) => {
return {
repo_identifier: params.repo_identifier,
}
}
export const ssr = false

5
src/routes/repo/[repo_identifier]/issue/[event_id]/+page.ts

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
import { redirect } from '@sveltejs/kit'
export const load = ({ params }: { params: { event_id: string } }) => {
throw redirect(301, `/e/${params.event_id}`)
}

5
src/routes/repo/[repo_identifier]/issues/+page.ts

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
import { redirect } from '@sveltejs/kit'
export const load = ({ params }: { params: { repo_identifier: string } }) => {
throw redirect(301, `/repo/${params.repo_identifier}`)
}

5
src/routes/repo/[repo_identifier]/proposal/[event_id]/+page.ts

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
import { redirect } from '@sveltejs/kit'
export const load = ({ params }: { params: { event_id: string } }) => {
throw redirect(301, `/e/${params.event_id}`)
}

5
src/routes/repo/[repo_identifier]/proposals/+page.ts

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
import { redirect } from '@sveltejs/kit'
export const load = ({ params }: { params: { repo_identifier: string } }) => {
throw redirect(301, `/repo/${params.repo_identifier}`)
}
Loading…
Cancel
Save