Browse Source

feat: intelligently load repo events

choose repo events based from earliest_unique_commit and indentifier
based on:
 - number of mentions (issues and root patches)
  - most recent created_at

identify repo events for the same repository based on identical identifiers
and use of earliest_unique_commit

use stores for collections of repo events
master
DanConwayDev 2 years ago
parent
commit
2c2ee926eb
No known key found for this signature in database
GPG Key ID: 68E15486D73F75E1
  1. 4
      src/lib/components/RepoSummaryCard.svelte
  2. 4
      src/lib/components/ReposSummaryList.svelte
  3. 3
      src/lib/components/proposals/ProposalsListItem.svelte
  4. 14
      src/lib/components/proposals/StatusSelector.svelte
  5. 11
      src/lib/components/repo/RepoDetails.svelte
  6. 12
      src/lib/components/repo/RepoHeader.svelte
  7. 38
      src/lib/components/repo/type.ts
  8. 161
      src/lib/components/repo/utils.spec.ts
  9. 17
      src/lib/components/repo/utils.ts
  10. 34
      src/lib/components/repo/vectors.ts
  11. 29
      src/lib/stores/Proposal.ts
  12. 41
      src/lib/stores/Proposals.ts
  13. 2
      src/lib/stores/ndk.ts
  14. 156
      src/lib/stores/repo.ts
  15. 313
      src/lib/stores/repos.ts
  16. 17
      src/lib/wrappers/Compose.svelte
  17. 0
      src/lib/wrappers/RecentProposals.svelte
  18. 9
      src/lib/wrappers/RepoDetails.svelte
  19. 151
      src/lib/wrappers/ReposRecent.svelte
  20. 20
      src/routes/repo/[repo_id]/+page.svelte
  21. 14
      src/routes/repo/[repo_id]/proposal/[proposal_id]/+page.svelte
  22. 121
      src/routes/repo/[repo_id]/proposal/[proposal_id]/page.svelte

4
src/lib/components/RepoSummaryCard.svelte

@ -2,7 +2,7 @@
import { summary_defaults } from './repo/type' import { summary_defaults } from './repo/type'
import UserHeader from './users/UserHeader.svelte' import UserHeader from './users/UserHeader.svelte'
export let { name, description, repo_id, maintainers, loading } = export let { name, description, identifier, maintainers, loading } =
summary_defaults summary_defaults
let short_name: string let short_name: string
$: { $: {
@ -24,7 +24,7 @@
{:else} {:else}
<a <a
class="link-primary break-words" class="link-primary break-words"
href="/repo/{encodeURIComponent(repo_id)}">{short_name}</a href="/repo/{encodeURIComponent(identifier)}">{short_name}</a
> >
{#if short_descrption.length > 0} {#if short_descrption.length > 0}
<p class="text-muted break-words pb-1 text-sm"> <p class="text-muted break-words pb-1 text-sm">

4
src/lib/components/ReposSummaryList.svelte

@ -17,8 +17,8 @@
<p class="prose">None</p> <p class="prose">None</p>
{:else} {:else}
<div class=""> <div class="">
{#each repos as { name, description, repo_id, maintainers }} {#each repos as { name, description, identifier, maintainers }}
<RepoSummaryCard {name} {description} {repo_id} {maintainers} /> <RepoSummaryCard {name} {description} {identifier} {maintainers} />
{/each} {/each}
{#if loading} {#if loading}
<RepoSummaryCard loading={true} /> <RepoSummaryCard loading={true} />

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

@ -17,10 +17,12 @@
dayjs.extend(relativeTime) dayjs.extend(relativeTime)
export let { export let {
title, title,
descritpion,
id, id,
repo_id, repo_id,
comments, comments,
status, status,
status_date,
author, author,
created_at, created_at,
loading, loading,
@ -74,6 +76,7 @@
<a <a
href="/repo/{repo_id}/proposal/{id}" href="/repo/{repo_id}/proposal/{id}"
class="ml-3 grow overflow-hidden text-xs text-neutral-content" class="ml-3 grow overflow-hidden text-xs text-neutral-content"
class:pointer-events-none={loading}
> >
{#if loading} {#if loading}
<div class="skeleton h-5 w-60 flex-none pt-1"></div> <div class="skeleton h-5 w-60 flex-none pt-1"></div>

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

@ -13,7 +13,10 @@
statusKindtoText, statusKindtoText,
} from '$lib/kinds' } from '$lib/kinds'
import { getUserRelays, logged_in_user } from '$lib/stores/users' import { getUserRelays, logged_in_user } from '$lib/stores/users'
import { selected_repo } from '$lib/stores/repo' import {
selected_repo_collection,
selected_repo_event,
} from '$lib/stores/repo'
import Status from '$lib/components/proposals/Status.svelte' import Status from '$lib/components/proposals/Status.svelte'
export let status: number | undefined = undefined export let status: number | undefined = undefined
@ -25,7 +28,8 @@
let edit_mode = false let edit_mode = false
$: { $: {
edit_mode = edit_mode =
$logged_in_user !== undefined && repo_id === $selected_repo.repo_id $logged_in_user !== undefined &&
repo_id === $selected_repo_collection.identifier
} }
async function changeStatus(new_status_kind: number) { async function changeStatus(new_status_kind: number) {
@ -42,10 +46,10 @@
.forEach((revision) => { .forEach((revision) => {
event.tags.push(['e', revision.id, 'mention']) event.tags.push(['e', revision.id, 'mention'])
}) })
if ($selected_repo.unique_commit) if ($selected_repo_event.unique_commit)
event.tags.push(['r', $selected_repo.unique_commit]) event.tags.push(['r', $selected_repo_event.unique_commit])
loading = true loading = true
let relays = [...$selected_repo.relays] let relays = [...$selected_repo_event.relays]
try { try {
event.sign() event.sign()
} catch { } catch {

11
src/lib/components/repo/RepoDetails.svelte

@ -1,17 +1,22 @@
<script lang="ts"> <script lang="ts">
import UserHeader from '$lib/components/users/UserHeader.svelte' import UserHeader from '$lib/components/users/UserHeader.svelte'
import { defaults } from './type' import { event_defaults } from './type'
export let { export let {
repo_id, event_id,
identifier,
unique_commit,
name, name,
description, description,
clone, clone,
web,
tags, tags,
maintainers, maintainers,
relays, relays,
referenced_by,
created_at,
loading, loading,
} = defaults } = event_defaults
$: short_descrption = $: short_descrption =
description.length > 500 ? description.slice(0, 450) + '...' : description description.length > 500 ? description.slice(0, 450) + '...' : description
</script> </script>

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

@ -1,18 +1,22 @@
<script lang="ts"> <script lang="ts">
import Container from '../Container.svelte' import Container from '../Container.svelte'
import { defaults } from './type' import { event_defaults } from './type'
export let { export let {
repo_id, event_id,
identifier,
unique_commit, unique_commit,
name, name,
description, description,
clone, clone,
web,
tags, tags,
maintainers, maintainers,
relays, relays,
referenced_by,
created_at,
loading, loading,
} = defaults } = event_defaults
let short_name: string let short_name: string
$: { $: {
if (name.length > 45) short_name = name.slice(0, 45) + '...' if (name.length > 45) short_name = name.slice(0, 45) + '...'
@ -29,7 +33,7 @@
</div> </div>
{:else} {:else}
<a <a
href={`/repo/${repo_id}`} href={`/repo/${identifier}`}
class="strong btn btn-ghost mb-0 mt-0 break-words px-3 text-sm" class="strong btn btn-ghost mb-0 mt-0 break-words px-3 text-sm"
>{short_name}</a >{short_name}</a
> >

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

@ -1,39 +1,65 @@
import { defaults as user_defaults, type User } from '../users/type' import { defaults as user_defaults, type User } from '../users/type'
export interface Repo { export interface RepoEvent {
repo_id: string event_id: string
identifier: string
unique_commit: string | undefined unique_commit: string | undefined
name: string name: string
description: string description: string
clone: string clone: string
web: string[]
tags: string[] tags: string[]
maintainers: User[] maintainers: User[]
relays: string[] relays: string[]
referenced_by: string[]
created_at: number
loading: boolean loading: boolean
} }
export const defaults: Repo = { export const event_defaults: RepoEvent = {
repo_id: '', event_id: '',
identifier: '',
unique_commit: '', unique_commit: '',
name: '', name: '',
description: '', description: '',
clone: '', clone: '',
web: [],
tags: [], tags: [],
maintainers: [], maintainers: [],
relays: [], relays: [],
referenced_by: [],
created_at: 0,
loading: true,
}
export interface RepoCollection {
selected_event_id: string
unique_commit: string
identifier: string
events: RepoEvent[]
loading: boolean
}
export const collection_defaults: RepoCollection = {
identifier: '',
unique_commit: '',
selected_event_id: '',
events: [],
loading: true, loading: true,
} }
export interface RepoSummary { export interface RepoSummary {
name: string name: string
description: string description: string
repo_id: string identifier: string
unique_commit: string | undefined
maintainers: User[] maintainers: User[]
loading?: boolean loading?: boolean
created_at: number created_at: number
} }
export const summary_defaults: RepoSummary = { export const summary_defaults: RepoSummary = {
name: '', name: '',
repo_id: '', identifier: '',
unique_commit: undefined,
description: '', description: '',
maintainers: [{ ...user_defaults }], maintainers: [{ ...user_defaults }],
loading: false, loading: false,

161
src/lib/components/repo/utils.spec.ts

@ -0,0 +1,161 @@
import { describe, expect, test } from 'vitest'
import { selectRepoFromCollection } from './utils'
import {
collection_defaults,
event_defaults,
type RepoCollection,
type RepoEvent,
} from './type'
const repo_event: RepoEvent = {
...event_defaults,
event_id: '123',
unique_commit: 'abc123',
identifier: 'abc',
created_at: 10,
}
describe('getSelectedRepo', () => {
describe('selected_event_id is default (empty string)', () => {
test('if no events returns undefined', () => {
expect(
selectRepoFromCollection({
...collection_defaults,
selected_event_id: 'b',
} as RepoCollection)
).toBeUndefined()
})
test('if no event with id returns undefined', () => {
expect(
selectRepoFromCollection({
...collection_defaults,
selected_event_id: 'b',
events: [
{
...repo_event,
event_id: 'a',
created_at: 1,
referenced_by: ['d', 'e'],
},
],
} as RepoCollection)
).toBeUndefined()
})
test('returns event with selected id', () => {
const preferable_event = {
...repo_event,
event_id: 'a',
created_at: 1,
referenced_by: ['d', 'e'],
}
expect(
selectRepoFromCollection({
...collection_defaults,
selected_event_id: preferable_event.event_id,
events: [
preferable_event,
{
...repo_event,
event_id: 'b',
created_at: 2,
referenced_by: ['x', 'y', 'z'],
},
{
...repo_event,
event_id: 'c',
created_at: 3,
},
],
} as RepoCollection)
).toEqual(preferable_event)
})
})
describe('selected_event_id is default (empty string)', () => {
test('if no events returns undefined', () => {
expect(
selectRepoFromCollection({
...collection_defaults,
} as RepoCollection)
).toBeUndefined()
})
test('if referenced_by is undefined (still loading), treat its as no references', () => {
const preferable_event = {
...repo_event,
event_id: 'c',
referenced_by: ['d', 'e'],
created_at: 2,
}
expect(
selectRepoFromCollection({
...collection_defaults,
events: [
{
...repo_event,
event_id: 'a',
created_at: 1,
},
{
...repo_event,
event_id: 'b',
created_at: 3,
},
preferable_event,
],
} as RepoCollection)
).toEqual(preferable_event)
})
test('if no references to either event return youngest', () => {
const preferable_event = {
...repo_event,
event_id: 'b',
created_at: 3,
}
expect(
selectRepoFromCollection({
...collection_defaults,
events: [
{
...repo_event,
event_id: 'a',
created_at: 1,
},
preferable_event,
{
...repo_event,
event_id: 'c',
created_at: 2,
},
],
} as RepoCollection)
).toEqual(preferable_event)
})
test('returns most referenced event', () => {
const preferable_event = {
...repo_event,
event_id: 'b',
created_at: 2,
referenced_by: ['x', 'y', 'z'],
}
expect(
selectRepoFromCollection({
...collection_defaults,
events: [
{
...repo_event,
event_id: 'a',
created_at: 1,
referenced_by: ['d', 'e'],
},
preferable_event,
{
...repo_event,
event_id: 'c',
created_at: 3,
},
],
} as RepoCollection)
).toEqual(preferable_event)
})
})
})

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

@ -0,0 +1,17 @@
import type { RepoCollection, RepoEvent } from './type'
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]
}

34
src/lib/components/repo/vectors.ts

@ -1,5 +1,5 @@
import { UserVectors, withName } from '../users/vectors' import { UserVectors, withName } from '../users/vectors'
import type { Repo, RepoSummary } from './type' import type { RepoEvent, RepoSummary } from './type'
export const RepoSummaryCardArgsVectors = { export const RepoSummaryCardArgsVectors = {
Short: { Short: {
@ -33,8 +33,8 @@ export const RepoSummaryCardArgsVectors = {
], ],
} as RepoSummary, } as RepoSummary,
} }
const base: Repo = { const base: RepoEvent = {
repo_id: '9ee507fc4357d7ee16a5d8901bedcd103f23c17d', identifier: '9ee507fc4357d7ee16a5d8901bedcd103f23c17d',
unique_commit: '9ee507fc4357d7ee16a5d8901bedcd103f23c17d', unique_commit: '9ee507fc4357d7ee16a5d8901bedcd103f23c17d',
name: 'Short Name', name: 'Short Name',
description: 'short description', description: 'short description',
@ -50,23 +50,23 @@ const base: Repo = {
} }
export const RepoDetailsArgsVectors = { export const RepoDetailsArgsVectors = {
Short: { ...base } as Repo, Short: { ...base } as RepoEvent,
Long: { Long: {
...base, ...base,
name: 'Long Name that goes on and on and on and on and on and on and on and on and on', name: 'Long Name that goes on and on and on and on and on and on and on and on and on',
description: description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis quis nisl eget turpis congue molestie. Nulla vitae purus nec augue accumsan facilisis sed sed ligula. Vestibulum sed risus lacinia risus lacinia molestie. Ut lorem quam, consequat eget tempus in, rhoncus vel nunc. Duis efficitur a leo vel sodales. Nam id fermentum lacus. Etiam nec placerat velit. Praesent ac consectetur est. Aenean iaculis commodo enim.\n Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis quis nisl eget turpis congue molestie.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis quis nisl eget turpis congue molestie. Nulla vitae purus nec augue accumsan facilisis sed sed ligula. Vestibulum sed risus lacinia risus lacinia molestie. Ut lorem quam, consequat eget tempus in, rhoncus vel nunc. Duis efficitur a leo vel sodales. Nam id fermentum lacus. Etiam nec placerat velit. Praesent ac consectetur est. Aenean iaculis commodo enim.\n Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis quis nisl eget turpis congue molestie.',
} as Repo, } as RepoEvent,
LongNoSpaces: { LongNoSpaces: {
...base, ...base,
name: 'LongNameLongNameLongNameLongNameLongNameLongNameLongNameLongName', name: 'LongNameLongNameLongNameLongNameLongNameLongNameLongNameLongName',
description: description:
'LoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsum', 'LoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsum',
} as Repo, } as RepoEvent,
NoNameOrDescription: { ...base, name: '', description: '' } as Repo, NoNameOrDescription: { ...base, name: '', description: '' } as RepoEvent,
NoDescription: { ...base, description: '' } as Repo, NoDescription: { ...base, description: '' } as RepoEvent,
NoTags: { ...base, tags: [] } as Repo, NoTags: { ...base, tags: [] } as RepoEvent,
NoGitServer: { ...base, clone: '' } as Repo, NoGitServer: { ...base, clone: '' } as RepoEvent,
MaintainersOneProfileNotLoaded: { MaintainersOneProfileNotLoaded: {
...base, ...base,
maintainers: [ maintainers: [
@ -74,7 +74,7 @@ export const RepoDetailsArgsVectors = {
{ ...UserVectors.loading }, { ...UserVectors.loading },
{ ...base.maintainers[2] }, { ...base.maintainers[2] },
], ],
} as Repo, } as RepoEvent,
MaintainersOneProfileDisplayNameWithoutName: { MaintainersOneProfileDisplayNameWithoutName: {
...base, ...base,
maintainers: [ maintainers: [
@ -82,7 +82,7 @@ export const RepoDetailsArgsVectors = {
{ ...UserVectors.display_name_only }, { ...UserVectors.display_name_only },
{ ...base.maintainers[2] }, { ...base.maintainers[2] },
], ],
} as Repo, } as RepoEvent,
MaintainersOneProfileNameAndDisplayNamePresent: { MaintainersOneProfileNameAndDisplayNamePresent: {
...base, ...base,
maintainers: [ maintainers: [
@ -90,7 +90,7 @@ export const RepoDetailsArgsVectors = {
{ ...UserVectors.display_name_and_name }, { ...UserVectors.display_name_and_name },
{ ...base.maintainers[2] }, { ...base.maintainers[2] },
], ],
} as Repo, } as RepoEvent,
MaintainersOneProfileNoNameOrDisplayNameBeingPresent: { MaintainersOneProfileNoNameOrDisplayNameBeingPresent: {
...base, ...base,
maintainers: [ maintainers: [
@ -98,8 +98,8 @@ export const RepoDetailsArgsVectors = {
{ ...UserVectors.no_profile }, { ...UserVectors.no_profile },
{ ...base.maintainers[2] }, { ...base.maintainers[2] },
], ],
} as Repo, } as RepoEvent,
NoMaintainers: { ...base, maintainers: [] } as Repo, NoMaintainers: { ...base, maintainers: [] } as RepoEvent,
NoRelays: { ...base, relays: [] } as Repo, NoRelays: { ...base, relays: [] } as RepoEvent,
NoMaintainersOrRelays: { ...base, maintainers: [], relays: [] } as Repo, NoMaintainersOrRelays: { ...base, maintainers: [], relays: [] } as RepoEvent,
} }

29
src/lib/stores/Proposal.ts

@ -1,6 +1,6 @@
import { NDKRelaySet, type NDKEvent, NDKSubscription } from '@nostr-dev-kit/ndk' import { NDKRelaySet, type NDKEvent, NDKSubscription } from '@nostr-dev-kit/ndk'
import { writable, type Unsubscriber, type Writable } from 'svelte/store' import { writable, type Unsubscriber, type Writable } from 'svelte/store'
import { ndk } from './ndk' import { base_relays, ndk } from './ndk'
import type { User } from '$lib/components/users/type' import type { User } from '$lib/components/users/type'
import { ensureUser } from './users' import { ensureUser } from './users'
import { import {
@ -8,9 +8,10 @@ import {
full_defaults, full_defaults,
} from '$lib/components/proposals/type' } from '$lib/components/proposals/type'
import { proposal_status_kinds, proposal_status_open } from '$lib/kinds' import { proposal_status_kinds, proposal_status_open } from '$lib/kinds'
import { ensureSelectedRepo } from './repo' import { awaitSelectedRepoCollection } from './repo'
import { extractPatchMessage } from '$lib/components/events/content/utils' import { extractPatchMessage } from '$lib/components/events/content/utils'
import { goto } from '$app/navigation' import { goto } from '$app/navigation'
import { selectRepoFromCollection } from '$lib/components/repo/utils'
export const selected_proposal_full: Writable<ProposalFull> = writable({ export const selected_proposal_full: Writable<ProposalFull> = writable({
...full_defaults, ...full_defaults,
@ -58,8 +59,16 @@ export const ensureProposalFull = (repo_id: string, proposal_id: string) => {
if (proposal_summary_author_unsubsriber) proposal_summary_author_unsubsriber() if (proposal_summary_author_unsubsriber) proposal_summary_author_unsubsriber()
proposal_summary_author_unsubsriber = undefined proposal_summary_author_unsubsriber = undefined
new Promise(async (r) => { new Promise(async (r, reject) => {
const repo = await ensureSelectedRepo(repo_id) const repo_collection = await awaitSelectedRepoCollection(repo_id)
const repo = selectRepoFromCollection(repo_collection)
if (!repo) {
return reject()
}
const relays_to_use =
repo.relays.length > 3
? repo.relays
: [...base_relays].concat(repo.relays)
sub = ndk.subscribe( sub = ndk.subscribe(
{ {
@ -69,9 +78,7 @@ export const ensureProposalFull = (repo_id: string, proposal_id: string) => {
{ {
closeOnEose: true, closeOnEose: true,
}, },
repo.relays.length > 0 NDKRelaySet.fromRelayUrls(relays_to_use, ndk)
? NDKRelaySet.fromRelayUrls(repo.relays, ndk)
: undefined
) )
sub.on('event', (event: NDKEvent) => { sub.on('event', (event: NDKEvent) => {
@ -148,9 +155,7 @@ export const ensureProposalFull = (repo_id: string, proposal_id: string) => {
{ {
closeOnEose: false, closeOnEose: false,
}, },
repo.relays.length > 0 NDKRelaySet.fromRelayUrls(relays_to_use, ndk)
? NDKRelaySet.fromRelayUrls(repo.relays, ndk)
: undefined
) )
const process_replies = (event: NDKEvent) => { const process_replies = (event: NDKEvent) => {
@ -187,9 +192,7 @@ export const ensureProposalFull = (repo_id: string, proposal_id: string) => {
{ {
closeOnEose: true, closeOnEose: true,
}, },
repo.relays.length > 0 NDKRelaySet.fromRelayUrls(relays_to_use, ndk)
? NDKRelaySet.fromRelayUrls(repo.relays, ndk)
: undefined
) )
sub_revision_replies.on('event', (event: NDKEvent) => { sub_revision_replies.on('event', (event: NDKEvent) => {
process_replies(event) process_replies(event)

41
src/lib/stores/Proposals.ts

@ -5,20 +5,21 @@ import {
type NDKFilter, type NDKFilter,
} from '@nostr-dev-kit/ndk' } from '@nostr-dev-kit/ndk'
import { writable, type Unsubscriber, type Writable } from 'svelte/store' import { writable, type Unsubscriber, type Writable } from 'svelte/store'
import { ndk } from './ndk' import { base_relays, ndk } from './ndk'
import { summary_defaults } from '$lib/components/proposals/type' import { summary_defaults } from '$lib/components/proposals/type'
import type { User } from '$lib/components/users/type' import type { User } from '$lib/components/users/type'
import { ensureUser } from './users' import { ensureUser } from './users'
import type { ProposalSummaries } from '$lib/components/proposals/type' import type { ProposalSummaries } from '$lib/components/proposals/type'
import { ensureSelectedRepo } from './repo' import { awaitSelectedRepoCollection } from './repo'
import { import {
patch_kind, patch_kind,
proposal_status_kinds, proposal_status_kinds,
proposal_status_open, proposal_status_open,
repo_kind, repo_kind,
} from '$lib/kinds' } from '$lib/kinds'
import type { Repo } from '$lib/components/repo/type' import type { RepoEvent } from '$lib/components/repo/type'
import { extractPatchMessage } from '$lib/components/events/content/utils' import { extractPatchMessage } from '$lib/components/events/content/utils'
import { selectRepoFromCollection } from '$lib/components/repo/utils'
export const proposal_summaries: Writable<ProposalSummaries> = writable({ export const proposal_summaries: Writable<ProposalSummaries> = writable({
id: '', id: '',
@ -47,14 +48,31 @@ export const ensureProposalSummaries = async (repo_id: string) => {
selected_repo_id = repo_id selected_repo_id = repo_id
const repo = await ensureSelectedRepo(repo_id) setTimeout(() => {
proposal_summaries.update((summaries) => {
return {
...summaries,
loading: false,
}
})
}, 6000)
const repo_collection = await awaitSelectedRepoCollection(repo_id)
const repo = selectRepoFromCollection(repo_collection)
if (!repo) {
return
}
const relays_to_use =
repo.relays.length > 3 ? repo.relays : [...base_relays].concat(repo.relays)
const without_root_tag = !repo.unique_commit const without_root_tag = !repo.unique_commit
const filter_with_root: NDKFilter = { const filter_with_root: NDKFilter = {
kinds: [patch_kind], kinds: [patch_kind],
'#a': repo.maintainers.map( '#a': repo.maintainers.map(
(m) => `${repo_kind}:${m.hexpubkey}:${repo.repo_id}` (m) => `${repo_kind}:${m.hexpubkey}:${repo.identifier}`
), ),
'#t': ['root'], '#t': ['root'],
limit: 50, limit: 50,
@ -63,7 +81,7 @@ export const ensureProposalSummaries = async (repo_id: string) => {
const filter_without_root: NDKFilter = { const filter_without_root: NDKFilter = {
kinds: [patch_kind], kinds: [patch_kind],
'#a': repo.maintainers.map( '#a': repo.maintainers.map(
(m) => `${repo_kind}:${m.hexpubkey}:${repo.repo_id}` (m) => `${repo_kind}:${m.hexpubkey}:${repo.identifier}`
), ),
limit: 50, limit: 50,
} }
@ -73,9 +91,7 @@ export const ensureProposalSummaries = async (repo_id: string) => {
{ {
closeOnEose: true, closeOnEose: true,
}, },
repo.relays.length > 0 NDKRelaySet.fromRelayUrls(relays_to_use, ndk)
? NDKRelaySet.fromRelayUrls(repo.relays, ndk)
: undefined
) )
sub.on('event', (event: NDKEvent) => { sub.on('event', (event: NDKEvent) => {
@ -145,8 +161,11 @@ let sub_statuses: NDKSubscription
function getAndUpdateProposalStatus( function getAndUpdateProposalStatus(
proposals: ProposalSummaries, proposals: ProposalSummaries,
repo: Repo repo: RepoEvent
): void { ): void {
const relays_to_use =
repo.relays.length > 3 ? repo.relays : [...base_relays].concat(repo.relays)
if (sub_statuses) sub_statuses.stop() if (sub_statuses) sub_statuses.stop()
sub_statuses = ndk.subscribe( sub_statuses = ndk.subscribe(
{ {
@ -156,7 +175,7 @@ function getAndUpdateProposalStatus(
{ {
closeOnEose: true, closeOnEose: true,
}, },
NDKRelaySet.fromRelayUrls(repo.relays, ndk) NDKRelaySet.fromRelayUrls(relays_to_use, ndk)
) )
sub_statuses.on('event', (event: NDKEvent) => { sub_statuses.on('event', (event: NDKEvent) => {
const tagged_proposal_event = event.tagValue('e') const tagged_proposal_event = event.tagValue('e')

2
src/lib/stores/ndk.ts

@ -19,4 +19,4 @@ export const ndk = new NDKSvelte({
explicitRelayUrls: [...base_relays], explicitRelayUrls: [...base_relays],
}) })
ndk.connect() ndk.connect(5000)

156
src/lib/stores/repo.ts

@ -1,117 +1,61 @@
import { NDKEvent, NDKRelaySet, NDKSubscription } from '@nostr-dev-kit/ndk'
import { writable, type Unsubscriber, type Writable } from 'svelte/store' import { writable, type Unsubscriber, type Writable } from 'svelte/store'
import { base_relays, ndk } from './ndk' import type { RepoCollection, RepoEvent } from '$lib/components/repo/type'
import type { Repo } from '$lib/components/repo/type' import { collection_defaults, event_defaults } from '$lib/components/repo/type'
import { defaults } from '$lib/components/repo/type' import { ensureRepoCollection } from './repos'
import type { User } from '$lib/components/users/type' import { selectRepoFromCollection } from '$lib/components/repo/utils'
import { ensureUser } from './users'
import { repo_kind } from '$lib/kinds'
export const selected_repo: Writable<Repo> = writable({ ...defaults }) export const selected_repo_collection: Writable<RepoCollection> = writable({
let selected_repo_id: string = '' ...collection_defaults,
})
let maintainers_unsubscribers: Unsubscriber[] = [] export const selected_repo_event: Writable<RepoEvent> = writable({
...event_defaults,
})
let sub: NDKSubscription selected_repo_collection.subscribe((collection) => {
export const ensureSelectedRepo = async (repo_id: string): Promise<Repo> => { const selected_from_collection = selectRepoFromCollection(collection)
if (selected_repo_id == repo_id) { if (selected_from_collection)
return new Promise((r) => { selected_repo_event.set({ ...selected_from_collection })
const unsubscriber = selected_repo.subscribe((repo) => { })
if (repo.repo_id === repo_id && !repo.loading) {
setTimeout(() => {
unsubscriber()
}, 5)
r({ ...repo })
}
})
})
}
selected_repo_id = repo_id
if (sub) sub.stop() let selected_repo_unique_commit_or_identifier: string = ''
sub = ndk.subscribe(
{
kinds: [repo_kind],
'#d': [repo_id],
limit: 1,
},
{
closeOnEose: false,
},
NDKRelaySet.fromRelayUrls(base_relays, ndk)
)
return new Promise((r) => { let selected_unsubscriber: Unsubscriber
sub.on('event', (event: NDKEvent) => {
try { export const ensureSelectedRepoCollection = (
if (event.kind == repo_kind && event.tagValue('d') == repo_id) { unique_commit_or_identifier: string
const maintainers = [ ): Writable<RepoCollection> => {
{ if (
hexpubkey: event.pubkey, selected_repo_unique_commit_or_identifier !== unique_commit_or_identifier
loading: true, ) {
npub: '', selected_repo_unique_commit_or_identifier = unique_commit_or_identifier
} as User, if (selected_unsubscriber) selected_unsubscriber()
] selected_unsubscriber = ensureRepoCollection(
event.getMatchingTags('maintainers').forEach((t: string[]) => { unique_commit_or_identifier
t.forEach((v, i) => { ).subscribe((repo_collection) => {
if (i > 0 && v !== maintainers[0].hexpubkey) { selected_repo_collection.set({ ...repo_collection })
maintainers.push({
hexpubkey: v,
loading: true,
npub: '',
} as User)
}
})
})
const relays: string[] = []
event.getMatchingTags('relays').forEach((t: string[]) => {
t.forEach((v, i) => {
if (i > 0) {
relays.push(v)
}
})
})
selected_repo.set({
loading: false,
repo_id: event.replaceableDTag(),
unique_commit: event.tagValue('r') || undefined,
name: event.tagValue('name') || '',
description: event.tagValue('description') || '',
clone: event.tagValue('clone') || '',
tags: event.getMatchingTags('t').map((t) => t[1]) || [],
maintainers,
relays,
})
const old_unsubscribers = maintainers_unsubscribers
maintainers_unsubscribers = maintainers.map((m: User) => {
return ensureUser(m.hexpubkey).subscribe((u: User) => {
selected_repo.update((repo) => {
return {
...repo,
maintainers: repo.maintainers.map((m) => {
if (m.hexpubkey == u.hexpubkey) return { ...u }
else return { ...m }
}),
}
})
})
})
old_unsubscribers.forEach((unsubscriber) => unsubscriber())
}
} catch {}
}) })
}
return selected_repo_collection
}
sub.on('eose', () => { export const awaitSelectedRepoCollection = async (
selected_repo.update((repo) => { unique_commit_or_identifier: string
r({ ): Promise<RepoCollection> => {
...repo, return new Promise((r) => {
loading: false, const unsubscriber = ensureSelectedRepoCollection(
}) unique_commit_or_identifier
return { ).subscribe((repo_collection) => {
...repo, if (
loading: false, selected_repo_unique_commit_or_identifier ==
} unique_commit_or_identifier &&
}) !repo_collection.loading
) {
setTimeout(() => {
unsubscriber()
}, 5)
r({ ...repo_collection })
}
}) })
}) })
} }

313
src/lib/stores/repos.ts

@ -0,0 +1,313 @@
import {
collection_defaults,
type RepoCollection,
type RepoEvent,
type RepoSummary,
} from '$lib/components/repo/type'
import { NDKRelaySet, type NDKFilter, NDKEvent } from '@nostr-dev-kit/ndk'
import { writable, type Writable } from 'svelte/store'
import { base_relays, ndk } from './ndk'
import { repo_kind } from '$lib/kinds'
import type { User } from '$lib/components/users/type'
import { ensureUser } from './users'
import { selectRepoFromCollection } from '$lib/components/repo/utils'
export const repos: {
[unique_commit_or_identifier: string]: Writable<RepoCollection>
} = {}
export const ensureRepoCollection = (
unique_commit_or_identifier: string
): Writable<RepoCollection> => {
if (!repos[unique_commit_or_identifier]) {
let base: RepoCollection = {
...collection_defaults,
}
if (unique_commit_or_identifier.length === 40) {
base = { ...base, unique_commit: unique_commit_or_identifier }
} else {
base = { ...base, identifier: unique_commit_or_identifier }
}
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,
}
const sub = ndk.subscribe(
filter,
{
groupable: true,
// default 100
groupableDelay: 200,
closeOnEose: true,
},
NDKRelaySet.fromRelayUrls(base_relays, ndk)
)
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,
events: [...repo_collection.events, repo_event as RepoEvent],
}
})
const relays_to_use =
repo_event.relays.length < 3
? repo_event.relays
: [...base_relays].concat(repo_event.relays)
// get references
const ref_sub = ndk.subscribe(
{
'#a': [
`${repo_kind}:${repo_event.maintainers[0].hexpubkey}:${repo_event.identifier}`,
],
limit: 10,
},
{
groupable: true,
// default 100
groupableDelay: 200,
closeOnEose: true,
},
NDKRelaySet.fromRelayUrls(relays_to_use, ndk)
)
ref_sub.on('event', (ref_event: NDKEvent) => {
repos[unique_commit_or_identifier].update((repo_collection) => {
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
}),
],
}
})
})
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)
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,
}
})
})
// load maintainers - we will subscribe later to prevent too many updates
repo_event.maintainers.forEach((m) => ensureUser(m.hexpubkey))
}
})
sub.on('eose', () => {
// still awaiting reference_by at this point
repos[unique_commit_or_identifier].update((repo_collection) => {
// subscribe to maintainers
const hexpubkeys = repo_collection.events.flatMap((repo_event) =>
repo_event.maintainers.map((m) => m.hexpubkey)
)
hexpubkeys.forEach((hexpubkey) => {
ensureUser(hexpubkey).subscribe((u) => {
repos[unique_commit_or_identifier].update((repo_collection) => ({
...repo_collection,
events: [
...repo_collection.events.map((repo_event) => ({
...repo_event,
maintainers: [
...repo_event.maintainers.map((m) => ({
...(m.hexpubkey === u.hexpubkey ? u : m),
})),
],
})),
],
}))
})
})
return {
...repo_collection,
loading: false,
}
})
})
}
setTimeout(() => {
repos[unique_commit_or_identifier].update((collection) => {
return {
...collection,
events: collection.events.map((e) => ({ ...e, loading: false })),
loading: false,
}
})
}, 5000)
return repos[unique_commit_or_identifier]
}
export const eventToRepoEvent = (event: NDKEvent): RepoEvent | undefined => {
if (event.kind !== repo_kind) return undefined
const maintainers = [
{
hexpubkey: event.pubkey,
loading: true,
npub: '',
} as User,
]
event.getMatchingTags('maintainers').forEach((t: string[]) => {
t.forEach((v, i) => {
if (i > 0 && v !== maintainers[0].hexpubkey) {
maintainers.push({
hexpubkey: v,
loading: true,
npub: '',
} as User)
}
})
})
const relays: string[] = []
event.getMatchingTags('relays').forEach((t: string[]) => {
t.forEach((v, i) => {
if (i > 0) {
relays.push(v)
}
})
})
const web: string[] = []
event.getMatchingTags('web').forEach((t: string[]) => {
t.forEach((v, i) => {
if (i > 0) {
relays.push(v)
}
})
})
return {
event_id: event.id,
identifier: event.replaceableDTag(),
unique_commit: event.tagValue('r') || undefined,
name: event.tagValue('name') || '',
description: event.tagValue('description') || '',
clone: event.tagValue('clone') || '',
web,
tags: event.getMatchingTags('t').map((t) => t[1]) || [],
maintainers,
relays,
referenced_by: [],
created_at: event.created_at || 0,
loading: true, // loading until references fetched
}
}
export const repoCollectionToSummary = (
collection: RepoCollection
): RepoSummary | undefined => {
const selected = selectRepoFromCollection(collection)
if (!selected) return undefined
return {
name: selected.name,
identifier: selected.identifier,
unique_commit: selected.unique_commit,
description: selected.description,
maintainers: selected.maintainers,
loading: collection.loading,
created_at: selected.created_at,
} 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),
}
}
)
}
}
)
})
}

17
src/lib/wrappers/Compose.svelte

@ -3,7 +3,10 @@
import { NDKEvent, NDKRelaySet } from '@nostr-dev-kit/ndk' import { NDKEvent, NDKRelaySet } from '@nostr-dev-kit/ndk'
import { reply_kind, repo_kind } from '$lib/kinds' import { reply_kind, repo_kind } from '$lib/kinds'
import { getUserRelays, logged_in_user } from '$lib/stores/users' import { getUserRelays, logged_in_user } from '$lib/stores/users'
import { selected_repo } from '$lib/stores/repo' import {
selected_repo_collection,
selected_repo_event,
} from '$lib/stores/repo'
import Compose from '$lib/components/events/Compose.svelte' import Compose from '$lib/components/events/Compose.svelte'
import { selected_proposal_full } from '$lib/stores/Proposal' import { selected_proposal_full } from '$lib/stores/Proposal'
@ -16,7 +19,7 @@
let submitted = false let submitted = false
let edit_mode = false let edit_mode = false
$: { $: {
repo_id = $selected_repo.repo_id repo_id = $selected_repo_collection.identifier
proposal_id = $selected_proposal_full.summary.id proposal_id = $selected_proposal_full.summary.id
edit_mode = repo_id.length > 0 && proposal_id.length > 0 && !submitted edit_mode = repo_id.length > 0 && proposal_id.length > 0 && !submitted
@ -30,20 +33,20 @@
if (reply_to_event_id.length > 0) { if (reply_to_event_id.length > 0) {
event.tags.push(['e', reply_to_event_id, 'reply']) event.tags.push(['e', reply_to_event_id, 'reply'])
} }
if ($selected_repo.unique_commit) { if ($selected_repo_event.unique_commit) {
event.tags.push(['r', $selected_repo.unique_commit]) event.tags.push(['r', $selected_repo_event.unique_commit])
} }
event.tags.push([ event.tags.push([
'a', 'a',
`${repo_kind}:${$selected_repo.maintainers[0].hexpubkey}:${repo_id}`, `${repo_kind}:${$selected_repo_event.maintainers[0].hexpubkey}:${repo_id}`,
]) ])
$selected_repo.maintainers.forEach((m) => $selected_repo_event.maintainers.forEach((m) =>
event.tags.push(['p', m.hexpubkey]) event.tags.push(['p', m.hexpubkey])
) )
// TODO nip-10 reply chain p tags // TODO nip-10 reply chain p tags
event.content = content event.content = content
submitting = true submitting = true
let relays = [...$selected_repo.relays] let relays = [...$selected_repo_event.relays]
try { try {
event.sign() event.sign()
} catch { } catch {

0
src/lib/wrappers/RecentProposals.svelte

9
src/lib/wrappers/RepoDetails.svelte

@ -1,10 +1,13 @@
<script lang="ts"> <script lang="ts">
import RepoDetails from '$lib/components/repo/RepoDetails.svelte' import RepoDetails from '$lib/components/repo/RepoDetails.svelte'
import { ensureSelectedRepo, selected_repo } from '$lib/stores/repo' import {
ensureSelectedRepoCollection,
selected_repo_event,
} from '$lib/stores/repo'
export let repo_id = '' export let repo_id = ''
ensureSelectedRepo(repo_id) ensureSelectedRepoCollection(repo_id)
</script> </script>
<RepoDetails {...$selected_repo} /> <RepoDetails {...$selected_repo_event} />

151
src/lib/wrappers/ReposRecent.svelte

@ -1,114 +1,85 @@
<script lang="ts"> <script lang="ts">
import ReposSummaryList from '$lib/components/ReposSummaryList.svelte' import ReposSummaryList from '$lib/components/ReposSummaryList.svelte'
import type { RepoSummary } from '$lib/components/repo/type' import type { RepoEvent, RepoSummary } from '$lib/components/repo/type'
import type { User } from '$lib/components/users/type'
import { repo_kind } from '$lib/kinds' import { repo_kind } from '$lib/kinds'
import { ndk } from '$lib/stores/ndk' import { ndk } from '$lib/stores/ndk'
import { ensureUser } from '$lib/stores/users' import {
ensureRepoCollection,
eventToRepoEvent,
repoCollectionToSummary,
} from '$lib/stores/repos'
import type { NDKEvent } from '@nostr-dev-kit/ndk' import type { NDKEvent } from '@nostr-dev-kit/ndk'
import { onDestroy } from 'svelte' import { onDestroy } from 'svelte'
import type { Unsubscriber } from 'svelte/store' import { writable, type Writable } from 'svelte/store'
export let limit: number = 50 export let limit: number = 100
let repos: Writable<RepoSummary[]> = writable([])
let repos: RepoSummary[] = []
let loading: boolean = true let loading: boolean = true
let sub = ndk.subscribe({ let sub = ndk.subscribe({
kinds: [repo_kind], kinds: [repo_kind],
limit, limit,
}) })
let maintainers_unsubscribers: Unsubscriber[] = [] let events: RepoEvent[] = []
sub.on('event', (event: NDKEvent) => { sub.on('event', (event: NDKEvent) => {
if (repos.length < limit) { let repo_event = eventToRepoEvent(event)
try { if (repo_event) events.push(repo_event)
if (event.kind == repo_kind) {
const maintainers = [
{
hexpubkey: event.pubkey,
loading: true,
npub: '',
} as User,
]
event.getMatchingTags('maintainers').forEach((t: string[]) => {
t.forEach((v, i) => {
if (i > 0 && v !== maintainers[0].hexpubkey) {
maintainers.push({
hexpubkey: v,
loading: true,
npub: '',
} as User)
}
})
})
// not duplicate name
if (!repos.some((r) => r.repo_id == event.replaceableDTag())) {
repos = [
...repos,
{
name: event.tagValue('name') || '',
description: event.tagValue('description') || '',
repo_id: event.replaceableDTag(),
maintainers,
created_at: event.created_at || 0,
},
]
} else {
// duplicate name
repos = [
...repos.map((r) => {
if (event.created_at && r.repo_id == event.replaceableDTag()) {
let new_maintainers = maintainers.filter(
(m) =>
!r.maintainers.some((o) => o.hexpubkey == m.hexpubkey)
)
return {
name:
r.created_at < event.created_at
? event.tagValue('name') || r.name
: r.name,
description:
r.created_at < event.created_at
? event.tagValue('description') || r.description
: r.description,
repo_id: r.repo_id,
maintainers: [...r.maintainers, ...new_maintainers],
created_at:
r.created_at < event.created_at
? event.created_at
: r.created_at,
}
} else return { ...r }
}),
]
}
// get maintainers profile
maintainers.forEach((m) => {
maintainers_unsubscribers.push(
ensureUser(m.hexpubkey).subscribe((u: User) => {
repos = repos.map((r) => {
return {
...r,
maintainers: r.maintainers.map((m) => {
if (m.hexpubkey == u.hexpubkey) return { ...u }
else return { ...m }
}),
}
})
})
)
})
}
} catch {}
} else if (loading == true) loading = false
}) })
sub.on('eose', () => { sub.on('eose', () => {
let unique_commits = [
...new Set(events.map((e) => e.unique_commit).filter((s) => !!s)),
] as string[]
let 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) => {
let summary = repoCollectionToSummary(repo_collection)
if (!summary) return
repos.update((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 }
}),
]
}
// if not duplicate - add summary
else if (summary) return [...repos, summary]
return [...repos]
})
})
})
if (loading == true) loading = false if (loading == true) loading = false
}) })
onDestroy(() => { onDestroy(() => {
maintainers_unsubscribers.forEach((unsubscriber) => unsubscriber())
sub.stop() sub.stop()
}) })
</script> </script>
<ReposSummaryList title="Latest Repositories" {repos} {loading} /> <ReposSummaryList title="Latest Repositories" repos={$repos} {loading} />

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

@ -1,18 +1,24 @@
<script lang="ts"> <script lang="ts">
import RepoDetails from '$lib/wrappers/RepoDetails.svelte' import RepoDetails from '$lib/wrappers/RepoDetails.svelte'
import OpenProposals from '$lib/wrappers/OpenProposals.svelte' import OpenProposals from '$lib/wrappers/OpenProposals.svelte'
import { ensureSelectedRepo, selected_repo } from '$lib/stores/repo' import {
ensureSelectedRepoCollection,
selected_repo_collection,
selected_repo_event,
} from '$lib/stores/repo'
import RepoHeader from '$lib/components/repo/RepoHeader.svelte' import RepoHeader from '$lib/components/repo/RepoHeader.svelte'
import Container from '$lib/components/Container.svelte' import Container from '$lib/components/Container.svelte'
export let data: { repo_id: string } export let data: { repo_id: string }
let repo_id = data.repo_id let identifier = data.repo_id
ensureSelectedRepo(repo_id) ensureSelectedRepoCollection(identifier)
let repo_error = false let repo_error = false
$: { $: {
repo_error = !$selected_repo.loading && $selected_repo.name.length === 0 repo_error =
!$selected_repo_collection.loading &&
$selected_repo_event.name.length === 0
} }
</script> </script>
@ -35,15 +41,15 @@
</div> </div>
</Container> </Container>
{:else} {:else}
<RepoHeader {...$selected_repo} /> <RepoHeader {...$selected_repo_event} />
<Container> <Container>
<div class="mt-2 md:flex"> <div class="mt-2 md:flex">
<div class="md:mr-2 md:w-2/3"> <div class="md:mr-2 md:w-2/3">
<OpenProposals {repo_id} /> <OpenProposals repo_id={identifier} />
</div> </div>
<div class="prose ml-2 hidden w-1/3 md:flex"> <div class="prose ml-2 hidden w-1/3 md:flex">
<RepoDetails {repo_id} /> <RepoDetails repo_id={identifier} />
</div> </div>
</div> </div>
</Container> </Container>

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

@ -1,5 +1,9 @@
<script lang="ts"> <script lang="ts">
import { ensureSelectedRepo, selected_repo } from '$lib/stores/repo' import {
ensureSelectedRepoCollection,
selected_repo_collection,
selected_repo_event,
} from '$lib/stores/repo'
import { import {
ensureProposalFull, ensureProposalFull,
selected_proposal_full, selected_proposal_full,
@ -23,13 +27,15 @@
let repo_id = data.repo_id let repo_id = data.repo_id
let proposal_id = data.proposal_id let proposal_id = data.proposal_id
ensureSelectedRepo(repo_id) ensureSelectedRepoCollection(repo_id)
ensureProposalFull(repo_id, proposal_id) ensureProposalFull(repo_id, proposal_id)
let repo_error = false let repo_error = false
let proposal_error = false let proposal_error = false
$: { $: {
repo_error = !$selected_repo.loading && $selected_repo.name.length === 0 repo_error =
!$selected_repo_collection.loading &&
$selected_repo_event.name.length === 0
proposal_error = proposal_error =
!$selected_proposal_full.summary.loading && !$selected_proposal_full.summary.loading &&
$selected_proposal_full.summary.created_at === 0 $selected_proposal_full.summary.created_at === 0
@ -37,7 +43,7 @@
</script> </script>
{#if !repo_error} {#if !repo_error}
<RepoHeader {...$selected_repo} /> <RepoHeader {...$selected_repo_event} />
{/if} {/if}
{#if proposal_error} {#if proposal_error}

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

@ -1,121 +0,0 @@
<script lang="ts">
import { ensureSelectedRepo, selected_repo } 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 RepoHeader from '$lib/components/repo/RepoHeader.svelte'
import Thread from '$lib/wrappers/Thread.svelte'
import ProposalDetails from '$lib/components/proposals/ProposalDetails.svelte'
import Container from '$lib/components/Container.svelte'
import ParsedContent from '$lib/components/events/content/ParsedContent.svelte'
import Compose from '$lib/wrappers/Compose.svelte'
import { patch_kind } from '$lib/kinds'
import Patch from '$lib/components/events/content/Patch.svelte'
export let data: {
repo_id: string
proposal_id: string
}
let repo_id = data.repo_id
let proposal_id = data.proposal_id
ensureSelectedRepo(repo_id)
ensureProposalFull(repo_id, proposal_id)
let repo_error = false
let proposal_error = false
$: {
repo_error = !$selected_repo.loading && $selected_repo.name.length === 0
proposal_error =
!$selected_proposal_full.summary.loading &&
$selected_proposal_full.summary.created_at === 0
}
</script>
{#if !repo_error}
<RepoHeader {...$selected_repo} />
{/if}
{#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="md:flex">
<div class="md:mr-2 md:w-2/3">
<div role="alert" class="alert mt-3">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="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
>
<div>
<h3 class="prose mb-2 text-sm font-bold">
view proposal in local git repository
</h3>
<p class="prose 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="prose my-3">
{#if $selected_proposal_full.proposal_event && $selected_proposal_full.proposal_event.kind === patch_kind}
<Patch
content={$selected_proposal_full.proposal_event.content}
tags={$selected_proposal_full.proposal_event.tags}
/>
{:else}
<ParsedContent
content={$selected_proposal_full.summary.descritpion}
/>
{/if}
</div>
{#each $selected_proposal_replies as event}
<Thread {event} replies={[]} />
{/each}
<div class="my-3">
<Compose />
</div>
</div>
<div class="prose ml-2 hidden w-1/3 md:flex">
<ProposalDetails
summary={$selected_proposal_full.summary}
labels={$selected_proposal_full.labels}
loading={$selected_proposal_full.loading}
/>
</div>
</div>
</Container>
{/if}
Loading…
Cancel
Save