|
|
|
@ -33,6 +33,8 @@ |
|
|
|
let issues = $state<NostrEvent[]>([]); |
|
|
|
let issues = $state<NostrEvent[]>([]); |
|
|
|
let issueComments = $state<Map<string, NostrEvent[]>>(new Map()); |
|
|
|
let issueComments = $state<Map<string, NostrEvent[]>>(new Map()); |
|
|
|
let issueStatuses = $state<Map<string, NostrEvent>>(new Map()); |
|
|
|
let issueStatuses = $state<Map<string, NostrEvent>>(new Map()); |
|
|
|
|
|
|
|
let loadingIssues = $state(false); |
|
|
|
|
|
|
|
let loadingIssueData = $state(false); // Statuses, comments, profiles |
|
|
|
let documentationEvents = $state<Map<string, NostrEvent>>(new Map()); |
|
|
|
let documentationEvents = $state<Map<string, NostrEvent>>(new Map()); |
|
|
|
let changingStatus = $state<Map<string, boolean>>(new Map()); // Track which issues are having status changed |
|
|
|
let changingStatus = $state<Map<string, boolean>>(new Map()); // Track which issues are having status changed |
|
|
|
let statusFilter = $state<string | null>(null); // Filter issues by status: null = all, 'open', 'resolved', 'closed', 'draft' |
|
|
|
let statusFilter = $state<string | null>(null); // Filter issues by status: null = all, 'open', 'resolved', 'closed', 'draft' |
|
|
|
@ -115,11 +117,9 @@ |
|
|
|
break; // Success, stop trying other URLs |
|
|
|
break; // Success, stop trying other URLs |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
} catch (error) { |
|
|
|
// Failed to fetch git repo |
|
|
|
// Failed to fetch git repo - continue to next URL |
|
|
|
// Continue to next URL |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
} catch (error) { |
|
|
|
// Failed to load git repo |
|
|
|
// Failed to load git repo |
|
|
|
@ -279,6 +279,7 @@ |
|
|
|
async function loadIssues() { |
|
|
|
async function loadIssues() { |
|
|
|
if (!repoEvent) return; |
|
|
|
if (!repoEvent) return; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
loadingIssues = true; |
|
|
|
try { |
|
|
|
try { |
|
|
|
const gitUrls = extractGitUrls(repoEvent); |
|
|
|
const gitUrls = extractGitUrls(repoEvent); |
|
|
|
const relays = relayManager.getProfileReadRelays(); |
|
|
|
const relays = relayManager.getProfileReadRelays(); |
|
|
|
@ -301,10 +302,16 @@ |
|
|
|
filters.push({ '#r': gitUrls, kinds: [KIND.ISSUE], limit: 100 }); |
|
|
|
filters.push({ '#r': gitUrls, kinds: [KIND.ISSUE], limit: 100 }); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Batch fetch all issues in parallel |
|
|
|
// Search for issues by the repo author (issues might be created by repo maintainers) |
|
|
|
|
|
|
|
filters.push({ authors: [repoEvent.pubkey], kinds: [KIND.ISSUE], limit: 100 }); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Batch fetch all issues in parallel with cache-first strategy |
|
|
|
const issueEventsArrays = await Promise.all( |
|
|
|
const issueEventsArrays = await Promise.all( |
|
|
|
filters.map(filter => |
|
|
|
filters.map(filter => |
|
|
|
nostrClient.fetchEvents([filter], relays, { useCache: true, cacheResults: true }) |
|
|
|
nostrClient.fetchEvents([filter], relays, { |
|
|
|
|
|
|
|
useCache: 'cache-first', // Prioritize cache for faster loading |
|
|
|
|
|
|
|
cacheResults: true |
|
|
|
|
|
|
|
}) |
|
|
|
) |
|
|
|
) |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
@ -317,16 +324,24 @@ |
|
|
|
// Deduplicate and sort |
|
|
|
// Deduplicate and sort |
|
|
|
const uniqueIssues = Array.from(new Map(issueEvents.map(e => [e.id, e])).values()); |
|
|
|
const uniqueIssues = Array.from(new Map(issueEvents.map(e => [e.id, e])).values()); |
|
|
|
issues = uniqueIssues.sort((a, b) => b.created_at - a.created_at); |
|
|
|
issues = uniqueIssues.sort((a, b) => b.created_at - a.created_at); |
|
|
|
|
|
|
|
loadingIssues = false; // Issues are loaded, show them immediately |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Load statuses, comments, and profiles in background (don't wait) |
|
|
|
// Batch load statuses, comments, and profiles |
|
|
|
// This allows the UI to show issues immediately |
|
|
|
await Promise.all([ |
|
|
|
loadingIssueData = true; |
|
|
|
|
|
|
|
Promise.all([ |
|
|
|
loadIssueStatuses(), |
|
|
|
loadIssueStatuses(), |
|
|
|
loadIssueComments(), |
|
|
|
loadIssueComments(), |
|
|
|
loadAllProfiles() |
|
|
|
loadAllProfiles() |
|
|
|
]); |
|
|
|
]).finally(() => { |
|
|
|
|
|
|
|
loadingIssueData = false; |
|
|
|
|
|
|
|
}).catch(() => { |
|
|
|
|
|
|
|
// Background loading errors are non-critical |
|
|
|
|
|
|
|
loadingIssueData = false; |
|
|
|
|
|
|
|
}); |
|
|
|
} catch (error) { |
|
|
|
} catch (error) { |
|
|
|
// Failed to load issues |
|
|
|
// Failed to load issues |
|
|
|
|
|
|
|
loadingIssues = false; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -346,7 +361,7 @@ |
|
|
|
limit: 200 |
|
|
|
limit: 200 |
|
|
|
}], |
|
|
|
}], |
|
|
|
relays, |
|
|
|
relays, |
|
|
|
{ useCache: true, cacheResults: true } |
|
|
|
{ useCache: 'cache-first', cacheResults: true } // Prioritize cache |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -551,10 +566,11 @@ |
|
|
|
const relays = relayManager.getCommentReadRelays(); |
|
|
|
const relays = relayManager.getCommentReadRelays(); |
|
|
|
|
|
|
|
|
|
|
|
// Batch fetch all comments for all issues |
|
|
|
// Batch fetch all comments for all issues |
|
|
|
|
|
|
|
// Use cache-first to load comments faster |
|
|
|
const comments = await nostrClient.fetchEvents( |
|
|
|
const comments = await nostrClient.fetchEvents( |
|
|
|
[{ '#e': issueIds, kinds: [KIND.COMMENT], limit: 500 }], |
|
|
|
[{ '#e': issueIds, kinds: [KIND.COMMENT], limit: 500 }], |
|
|
|
relays, |
|
|
|
relays, |
|
|
|
{ useCache: true, cacheResults: true } |
|
|
|
{ useCache: 'cache-first', cacheResults: true } // Prioritize cache |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
// Group comments by issue ID |
|
|
|
// Group comments by issue ID |
|
|
|
@ -1287,7 +1303,11 @@ |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
{:else if activeTab === 'issues'} |
|
|
|
{:else if activeTab === 'issues'} |
|
|
|
<div class="issues-tab"> |
|
|
|
<div class="issues-tab"> |
|
|
|
{#if issues.length > 0} |
|
|
|
{#if loadingIssues} |
|
|
|
|
|
|
|
<div class="loading-state"> |
|
|
|
|
|
|
|
<p class="text-fog-text dark:text-fog-dark-text">Loading issues...</p> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
{:else if issues.length > 0} |
|
|
|
<div class="issues-filter"> |
|
|
|
<div class="issues-filter"> |
|
|
|
<label for="status-filter" class="filter-label">Filter by status:</label> |
|
|
|
<label for="status-filter" class="filter-label">Filter by status:</label> |
|
|
|
<select |
|
|
|
<select |
|
|
|
@ -1310,6 +1330,9 @@ |
|
|
|
{:else} |
|
|
|
{:else} |
|
|
|
{issues.length} {issues.length === 1 ? 'issue' : 'issues'} |
|
|
|
{issues.length} {issues.length === 1 ? 'issue' : 'issues'} |
|
|
|
{/if} |
|
|
|
{/if} |
|
|
|
|
|
|
|
{#if loadingIssueData} |
|
|
|
|
|
|
|
<span class="loading-indicator"> (loading details...)</span> |
|
|
|
|
|
|
|
{/if} |
|
|
|
</span> |
|
|
|
</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="issues-list"> |
|
|
|
<div class="issues-list"> |
|
|
|
@ -1601,17 +1624,22 @@ |
|
|
|
padding: 0.125rem 0.25rem; |
|
|
|
padding: 0.125rem 0.25rem; |
|
|
|
border-radius: 0.25rem; |
|
|
|
border-radius: 0.25rem; |
|
|
|
font-family: monospace; |
|
|
|
font-family: monospace; |
|
|
|
|
|
|
|
color: var(--fog-text, #1f2937); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
:global(.dark) .readme-container :global(code) { |
|
|
|
:global(.dark) .readme-container :global(code) { |
|
|
|
background: var(--fog-dark-highlight, #475569); |
|
|
|
background: var(--fog-dark-highlight, #475569); |
|
|
|
|
|
|
|
color: var(--fog-dark-text, #f9fafb); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Code blocks with highlight.js are styled by vs2015 theme */ |
|
|
|
|
|
|
|
/* Pre blocks for non-highlighted code */ |
|
|
|
.readme-container :global(pre) { |
|
|
|
.readme-container :global(pre) { |
|
|
|
background: var(--fog-highlight, #f3f4f6); |
|
|
|
|
|
|
|
padding: 1rem; |
|
|
|
padding: 1rem; |
|
|
|
border-radius: 0.5rem; |
|
|
|
border-radius: 0.5rem; |
|
|
|
overflow-x: auto; |
|
|
|
overflow-x: auto; |
|
|
|
|
|
|
|
/* Background will be overridden by vs2015 theme for hljs code blocks */ |
|
|
|
|
|
|
|
background: var(--fog-highlight, #f3f4f6); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
:global(.dark) .readme-container :global(pre) { |
|
|
|
:global(.dark) .readme-container :global(pre) { |
|
|
|
|