diff --git a/nostr/commit-signatures.jsonl b/nostr/commit-signatures.jsonl index ad80533..494674f 100644 --- a/nostr/commit-signatures.jsonl +++ b/nostr/commit-signatures.jsonl @@ -63,3 +63,4 @@ {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771840660,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","bug-fixes"]],"content":"Signed commit: bug-fixes","id":"e96c955f550a94c9c6d1228d2a7e479ced331334aaa4eea84525b362b8484d6e","sig":"1218bd9e449404ccc56c5727e8bdff5db31e37c2053a2d91ba02d214c0988173ba480010e53401661cb439884308a575230a7a12124f8e6d8f058c8a804a42f6"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771845583,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","fix search and relay connections"]],"content":"Signed commit: fix search and relay connections","id":"24db15027960b244eb4c8664a3642c64684ebfef8c200250093dd047cd119e7d","sig":"561d15ae39b3bf7a5b8a67539a5cfa19d53cbaca9f904589ab7cb69e568ddf056d0d83ced4830cdfdc0b386f13c4bab930264a0f6144cbb833b187b5d452c4ae"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771847704,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","bug-fixes"]],"content":"Signed commit: bug-fixes","id":"9566e4e2964d0a7b80cce1889092c4db333f89843b5d68906b3c3c568e4ba57d","sig":"8cf9166c630a8dc21bbc3dfaea4330c80c93bf7bc9e8d5d3be182fb11a3b96ea2e5969f452d3e2b309103b3e7fea8fc1aa6e5908d499d0696e9bfcd3859a8e32"} +{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771849427,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","bug-fixes"]],"content":"Signed commit: bug-fixes","id":"1d4e6ff4059b064d7cdd465d623a606cfcc5d0565681a34f6384463d40cc8c71","sig":"f5fe3547289e994ff1a3b191607e76d778d318ca4538e70253406867ecef214c1be437dca373f9a461c9cf2ca2978a581b54a9d323baeb2c91851e9cc6ffbfd6"} diff --git a/src/lib/styles/repo.css b/src/lib/styles/repo.css index fab4c4d..5709464 100644 --- a/src/lib/styles/repo.css +++ b/src/lib/styles/repo.css @@ -1439,17 +1439,32 @@ span.clone-more { flex-shrink: 0; } +.issues-content, +.prs-content, .patches-content, -.discussions-content { +.discussions-content, +.tags-content, +.docs-content, +.commits-content { padding: 1rem; flex: 1; overflow-y: auto; } +.patch-header, +.issue-header { + display: flex; + align-items: center; + width: 100%; +} + .patch-subject, +.issue-subject, .discussion-title { font-weight: 600; color: var(--text-primary); + width: 100%; + word-wrap: break-word; } .patch-meta, @@ -1481,7 +1496,8 @@ span.clone-more { } .patch-meta-detail, -.discussion-meta-detail { +.discussion-meta-detail, +.commit-meta-detail { display: flex; gap: 1rem; align-items: center; @@ -1489,6 +1505,98 @@ span.clone-more { color: var(--text-secondary); } +.commit-detail { + margin-bottom: 1rem; +} + +.commit-detail-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 1rem; +} + +.commit-detail-header h3 { + margin: 0; + flex: 1; + font-size: 1.25rem; + font-weight: 600; + color: var(--text-primary); +} + +.diff-view { + margin-top: 1.5rem; +} + +.diff-file { + margin-bottom: 1.5rem; + border: 1px solid var(--border-color); + border-radius: 0.375rem; + overflow: hidden; +} + +.diff-file-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem 1rem; + background: var(--bg-secondary); + border-bottom: 1px solid var(--border-color); +} + +.diff-file-name { + font-weight: 500; + color: var(--text-primary); +} + +.diff-stats { + display: flex; + gap: 0.5rem; + font-size: 0.875rem; +} + +.diff-stats .additions { + color: #22c55e; +} + +.diff-stats .deletions { + color: #ef4444; +} + +.diff-content { + margin: 0; + padding: 0.75rem 1rem; + background: var(--bg-primary); + overflow-x: auto; + font-size: 0.875rem; + line-height: 1.5; +} + +.close-button { + padding: 0.25rem 0.5rem; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 0.25rem; + cursor: pointer; + color: var(--text-primary); + font-size: 1.25rem; + line-height: 1; + transition: background 0.2s ease; +} + +.close-button:hover { + background: var(--bg-hover); +} + +.patch-description { + font-style: italic; + color: var(--text-primary); + font-size: 0.875rem; + margin-top: 0.5rem; + padding-top: 0.5rem; + border-top: 1px solid var(--border-color); +} + .patch-body { margin-top: 1rem; } @@ -1531,7 +1639,8 @@ span.clone-more { } .patch-item-button, -.discussion-item-button { +.discussion-item-button, +.issue-item-button { width: 100%; padding: 0.75rem 1rem; text-align: left; @@ -1540,15 +1649,21 @@ span.clone-more { cursor: pointer; color: inherit; transition: background 0.2s ease; + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: flex-start; } .patch-item:not(.selected) .patch-item-button:hover, -.discussion-item:not(.selected) .discussion-item-button:hover { +.discussion-item:not(.selected) .discussion-item-button:hover, +.issue-item:not(.selected) .issue-item-button:hover { background: var(--bg-tertiary); } .patch-item.selected .patch-item-button, -.discussion-item.selected .discussion-item-button { +.discussion-item.selected .discussion-item-button, +.issue-item.selected .issue-item-button { background: var(--accent); color: var(--text-on-accent); } diff --git a/src/routes/repos/[npub]/[repo]/+page.svelte b/src/routes/repos/[npub]/[repo]/+page.svelte index 5aa6039..23746d4 100644 --- a/src/routes/repos/[npub]/[repo]/+page.svelte +++ b/src/routes/repos/[npub]/[repo]/+page.svelte @@ -314,13 +314,16 @@ let announcementEventId = $state(null); // Issues - let issues = $state>([]); + let issues = $state>([]); let loadingIssues = $state(false); let showCreateIssueDialog = $state(false); let newIssueSubject = $state(''); let newIssueContent = $state(''); let newIssueLabels = $state(['']); let updatingIssueStatus = $state>({}); + let selectedIssue = $state(null); + let issueReplies = $state>([]); + let loadingIssueReplies = $state(false); // Pull Requests let prs = $state>([]); @@ -370,7 +373,7 @@ }); // Patches - let patches = $state>([]); + let patches = $state>([]); let loadingPatches = $state(false); let selectedPatch = $state(null); let showCreatePatchDialog = $state(false); @@ -3907,6 +3910,9 @@ } async function viewDiff(commitHash: string) { + // Set selected commit immediately so it shows in the right panel + selectedCommit = commitHash; + showDiff = false; // Start with false, will be set to true when diff loads loadingCommits = true; error = null; try { @@ -3922,7 +3928,6 @@ }); if (response.ok) { diffData = await response.json(); - selectedCommit = commitHash; showDiff = true; } } catch (err) { @@ -4008,8 +4013,14 @@ status: issue.status || 'open', author: issue.pubkey, created_at: issue.created_at, - kind: issue.kind || KIND.ISSUE + kind: issue.kind || KIND.ISSUE, + tags: issue.tags || [] })); + // Auto-select first issue if none selected + if (issues.length > 0 && !selectedIssue) { + selectedIssue = issues[0].id; + loadIssueReplies(issues[0].id); + } } else { // Handle non-OK responses const errorText = await response.text().catch(() => response.statusText); @@ -4039,6 +4050,32 @@ } } + async function loadIssueReplies(issueId: string) { + loadingIssueReplies = true; + try { + const replies = await nostrClient.fetchEvents([ + { + kinds: [KIND.COMMENT], + '#e': [issueId], + limit: 100 + } + ]) as NostrEvent[]; + + issueReplies = replies.map(reply => ({ + id: reply.id, + content: reply.content, + author: reply.pubkey, + created_at: reply.created_at, + tags: reply.tags || [] + })).sort((a, b) => a.created_at - b.created_at); + } catch (err) { + console.error('[Issues] Error loading replies:', err); + issueReplies = []; + } finally { + loadingIssueReplies = false; + } + } + async function createIssue() { if (!newIssueSubject.trim() || !newIssueContent.trim()) { alert('Please enter a subject and content'); @@ -4296,14 +4333,45 @@ }); if (response.ok) { const data = await response.json(); - patches = data.map((patch: { id: string; tags: string[][]; content: string; pubkey: string; created_at: number; kind?: number }) => ({ - id: patch.id, - subject: patch.tags.find((t: string[]) => t[0] === 'subject')?.[1] || 'Untitled', - content: patch.content, - author: patch.pubkey, - created_at: patch.created_at, - kind: patch.kind || KIND.PATCH - })); + patches = data.map((patch: { id: string; tags: string[][]; content: string; pubkey: string; created_at: number; kind?: number }) => { + // Extract subject/title from various sources + let subject = patch.tags.find((t: string[]) => t[0] === 'subject')?.[1]; + const description = patch.tags.find((t: string[]) => t[0] === 'description')?.[1]; + const alt = patch.tags.find((t: string[]) => t[0] === 'alt')?.[1]; + + // If no subject tag, try description or alt + if (!subject) { + if (description) { + subject = description.trim(); + } else if (alt) { + // Remove "git patch: " prefix if present + subject = alt.replace(/^git patch:\s*/i, '').trim(); + } else { + // Try to extract from patch content (git patch format) + const subjectMatch = patch.content.match(/^Subject:\s*\[PATCH[^\]]*\]\s*(.+)$/m); + if (subjectMatch) { + subject = subjectMatch[1].trim(); + } else { + // Try simpler Subject: line + const simpleSubjectMatch = patch.content.match(/^Subject:\s*(.+)$/m); + if (simpleSubjectMatch) { + subject = simpleSubjectMatch[1].trim(); + } + } + } + } + + return { + id: patch.id, + subject: subject || 'Untitled', + content: patch.content, + author: patch.pubkey, + created_at: patch.created_at, + kind: patch.kind || KIND.PATCH, + description: description?.trim(), + tags: patch.tags || [] + }; + }); } } catch (err) { error = err instanceof Error ? err.message : 'Failed to load patches'; @@ -4835,22 +4903,6 @@ Show content - {#if tags.length > 0} -
    - {#each tags as tag} - {@const tagHash = tag.hash || ''} - {#if tagHash} -
  • -
    {tag.name}
    -
    {tagHash.slice(0, 7)}
    - {#if tag.message} -
    {tag.message}
    - {/if} -
  • - {/if} - {/each} -
- {/if} {/if} @@ -4885,34 +4937,21 @@ {:else if issues.length > 0}
    {#each issues as issue} -
  • -
    - - {issue.status} - - {issue.subject} -
    -
    - #{issue.id.slice(0, 7)} - {new Date(issue.created_at * 1000).toLocaleDateString()} - -
    - {#if userPubkeyHex && (isMaintainer || userPubkeyHex === issue.author)} -
    - {#if issue.status === 'open'} - - - {:else if issue.status === 'closed' || issue.status === 'resolved'} - - {/if} +
  • +
  • {/each}
@@ -5286,61 +5325,97 @@ {/if} - {#if activeTab === 'history' && showDiff} -
- -
-
-
-

Diff for commit {selectedCommit?.slice(0, 7)}

- + {#if activeTab === 'history'} +
+
+
- {#each diffData as diff} -
-
- {diff.file} - - +{diff.additions} - -{diff.deletions} - + {#if selectedCommit} + {@const commit = commits.find(c => (c.hash || (c as any).sha) === selectedCommit)} + {#if commit} +
+
+

{commit.message || 'No message'}

+ +
+
+ #{selectedCommit.slice(0, 7)} + {commit.author || 'Unknown'} + {commit.date ? new Date(commit.date).toLocaleString() : 'Unknown date'} +
+ {#if loadingCommits} +
Loading diff...
+ {:else if showDiff && diffData.length > 0} +
+ {#each diffData as diff} +
+
+ {diff.file} + + +{diff.additions} + -{diff.deletions} + +
+
{diff.diff}
+
+ {/each} +
+ {:else if showDiff} +
+

No diff data available

+
+ {:else} +
+

Loading diff...

+
+ {/if}
-
{diff.diff}
+ {/if} + {:else} +
+

Select a commit from the sidebar to view details

- {/each} -
- {:else if activeTab === 'history'} -
- -
-
-

Select a commit to view its diff

+ {/if}
{/if} {#if activeTab === 'tags'} -
- -
-
-

Tags are displayed in the sidebar

+
+
+ +
+ {#if tags.length > 0} +
    + {#each tags as tag} + {@const tagHash = tag.hash || ''} + {#if tagHash} +
  • +
    {tag.name}
    +
    {tagHash.slice(0, 7)}
    + {#if tag.message} +
    {tag.message}
    + {/if} +
  • + {/if} + {/each} +
+ {:else} +
+

No tags found

+
+ {/if}
{/if} @@ -5368,21 +5443,69 @@

No issues found. Create one to get started!

- {:else} - {#each issues as issue} + {:else if selectedIssue} + {@const issue = issues.find(i => i.id === selectedIssue)} + {#if issue}

{issue.subject}

{issue.status} + #{issue.id.slice(0, 7)} Created {new Date(issue.created_at * 1000).toLocaleString()} +
{@html issue.content.replace(/\n/g, '
')}
+ + {#if userPubkeyHex && (isMaintainer || userPubkeyHex === issue.author)} +
+ {#if issue.status === 'open'} + + + {:else if issue.status === 'closed' || issue.status === 'resolved'} + + {/if} +
+ {/if} + +
+

Replies ({issueReplies.length})

+ {#if loadingIssueReplies} +
Loading replies...
+ {:else if issueReplies.length === 0} +
+

No replies yet.

+
+ {:else} + {#each issueReplies as reply} +
+
+ + {new Date(reply.created_at * 1000).toLocaleString()} + +
+
+ {@html reply.content.replace(/\n/g, '
')} +
+
+ {/each} + {/if} +
- {/each} + {/if} + {:else} +
+

Select an issue to view details

+
{/if}
{/if} @@ -5476,6 +5599,9 @@ Created {new Date(patch.created_at * 1000).toLocaleString()}
+ {#if patch.description && patch.description !== patch.subject} +
{patch.description}
+ {/if}
{patch.content}