{safeTitle || 'Repository'} {#if hasImage && safeImage} {/if} {#if hasBanner && safeBanner} {/if} {#if hasBanner && safeBanner} {:else if hasImage && safeImage} {/if}
{#if state.metadata.banner && typeof state.metadata.banner === 'string' && state.metadata.banner.trim()}
{ if (typeof window !== 'undefined') { console.error('[Repo Images] Failed to load banner:', state.metadata.banner); const target = e.target as HTMLImageElement; if (target) target.style.display = 'none'; } }} />
{/if} {#if repoOwnerPubkeyDerived} { if (typeof state.ui.showRepoMenu !== 'undefined') state.ui.showRepoMenu = !state.ui.showRepoMenu; }} showMenu={state.ui.showRepoMenu || false} userPubkey={state.user.pubkey || null} isBookmarked={state.bookmark.isBookmarked || false} loadingBookmark={state.loading.bookmark || false} onToggleBookmark={safeToggleBookmark} onFork={safeForkRepository} forking={state.fork.forking || false} onCloneToServer={safeCloneRepository} cloning={state.clone.cloning || false} checkingCloneStatus={state.clone.checking || false} onCreateIssue={() => { state.openDialog = 'createIssue'; }} onCreatePR={() => { state.openDialog = 'createPR'; }} onCreatePatch={() => { state.openDialog = 'createPatch'; }} onCreateBranch={async () => { if (!state.user.pubkey || !state.maintainers.isMaintainer || needsClone) return; try { const settings = await settingsStore.getSettings(); state.forms.branch.defaultName = settings.defaultBranch || 'master'; } catch { state.forms.branch.defaultName = 'master'; } // Preset the default branch name in the input field state.forms.branch.name = state.forms.branch.defaultName; state.forms.branch.from = null; // Reset from branch selection state.openDialog = 'createBranch'; }} onSettings={() => goto(`/signup?npub=${state.npub}&repo=${state.repo}`)} onGenerateVerification={repoOwnerPubkeyDerived && state.user.pubkeyHex === repoOwnerPubkeyDerived && state.verification.status?.verified !== true ? generateAnnouncementFileForRepo : undefined} onDeleteAnnouncement={repoOwnerPubkeyDerived && state.user.pubkeyHex === repoOwnerPubkeyDerived ? deleteAnnouncement : undefined} deletingAnnouncement={state.creating.announcement} hasUnlimitedAccess={hasUnlimitedAccess($userStore.userLevel)} needsClone={needsClone} allMaintainers={state.maintainers.all} onCopyEventId={copyEventId} onRemoveFromServer={repoOwnerPubkeyDerived && state.user.pubkeyHex === repoOwnerPubkeyDerived && state.clone.isCloned ? safeRemoveRepoFromServer : undefined} /> {/if} {#if repoWebsite || (repoCloneUrls && repoCloneUrls.length > 0) || repoLanguage || (repoTopics && repoTopics.length > 0) || state.fork.info?.isFork} {/if}
{#if state.clone.isCloned === false && (canUseApiFallback || state.clone.apiFallbackAvailable === null)}
{/if} {#if state.error}
Error: {state.error}
{#if state.error.includes('not cloned locally') && hasUnlimitedAccess($userStore.userLevel)}
{/if}
{/if} {#if repoOwnerPubkeyDerived}
{#if state.ui.activeTab === 'files'} { state.files.currentPath = path; loadFiles(path); }} onNavigateBack={handleBack} onContentChange={(content) => { state.files.editedContent = content; state.files.hasChanges = content !== state.files.content; }} isMaintainer={state.maintainers.isMaintainer} showFilePreview={state.preview.file.showPreview} fileHtml={state.preview.file.html} highlightedFileContent={state.preview.file.highlightedContent} isImageFile={state.preview.file.isImage} imageUrl={state.preview.file.imageUrl} wordWrap={state.ui.wordWrap} {supportsPreview} onSave={() => { if (!state.user.pubkey || !state.maintainers.isMaintainer || needsClone) return; state.openDialog = 'commit'; }} onTogglePreview={() => { state.preview.file.showPreview = !state.preview.file.showPreview; // When switching to raw mode, ensure syntax highlighting is applied if (!state.preview.file.showPreview && state.files.content && state.files.currentFile) { const ext = state.files.currentFile.split('.').pop() || ''; // Only apply if we don't already have highlighted content if (!state.preview.file.highlightedContent || state.preview.file.highlightedContent.trim() === '') { applySyntaxHighlighting(state.files.content, ext).catch(err => console.error('Error applying syntax highlighting:', err)); } } }} onCopyFileContent={copyFileContent} onDownloadFile={downloadFile} copyingFile={state.preview.copying} saving={state.saving} needsClone={needsClone} {cloneTooltip} branches={state.git.branches} currentBranch={state.git.currentBranch} defaultBranch={state.git.defaultBranch} onBranchChange={(branch) => { state.git.currentBranch = branch; handleBranchChangeDirect(branch); }} userPubkey={state.user.pubkey} activeTab={state.ui.activeTab} {tabs} onTabChange={(tab: string) => { state.ui.activeTab = tab as typeof state.ui.activeTab; // Update URL query parameter without page reload const url = new URL($page.url); if (tab === 'docs') { url.searchParams.delete('tab'); } else { url.searchParams.set('tab', tab); } goto(url.pathname + url.search, { replaceState: true, noScroll: true }); }} onCreateFile={() => { if (!state.user.pubkey || !state.maintainers.isMaintainer || needsClone) return; state.openDialog = 'createFile'; }} onApplySyntaxHighlighting={applySyntaxHighlighting} onRenderPreview={async (content: string, ext: string) => { return new Promise((resolve) => { const branch = state.git.currentBranch || state.git.defaultBranch || null; renderFileAsHtmlUtil(content, ext, state.files.currentFile, (html: string) => { resolve(html); }, state.npub, state.repo, branch); }); }} npub={state.npub} repo={state.repo} /> {/if} {#if state.ui.activeTab === 'history'} { state.git.selectedCommit = hash; viewDiff(hash); }} onVerify={async (hash) => { state.git.verifyingCommits.add(hash); try { // Trigger verification logic - find the commit and verify const commit = state.git.commits.find((c: { hash?: string; sha?: string }) => (c.hash || c.sha) === hash); if (commit) { await verifyCommit(hash); } } finally { state.git.verifyingCommits.delete(hash); } }} verifyingCommits={state.git.verifyingCommits} showDiff={state.git.showDiff} diffData={state.git.diffData} activeTab={state.ui.activeTab} {tabs} onTabChange={(tab: string) => { state.ui.activeTab = tab as typeof state.ui.activeTab; // Update URL query parameter without page reload const url = new URL($page.url); if (tab === 'docs') { url.searchParams.delete('tab'); } else { url.searchParams.set('tab', tab); } goto(url.pathname + url.search, { replaceState: true, noScroll: true }); }} /> {/if} state.git.selectedTag = tagName} onTabChange={(tab: string) => { state.ui.activeTab = tab as typeof state.ui.activeTab; // Update URL without page reload const url = new URL($page.url); if (tab === 'docs') { url.searchParams.delete('tab'); } else { url.searchParams.set('tab', tab); } goto(url.pathname + url.search, { replaceState: true, noScroll: true }); }} onToggleMobilePanel={() => state.ui.showLeftPanelOnMobile = !state.ui.showLeftPanelOnMobile} onCreateTag={() => state.openDialog = 'createTag'} onCreateRelease={(tagName, tagHash) => { state.forms.release.tagName = tagName; state.forms.release.tagHash = tagHash; // Pre-fill download URL with full URL if (typeof window !== 'undefined') { const origin = window.location.origin; state.forms.release.downloadUrl = `${origin}/api/repos/${state.npub}/${state.repo}/archive?ref=${encodeURIComponent(tagName)}&format=zip`; } else { state.forms.release.downloadUrl = `/api/repos/${state.npub}/${state.repo}/archive?ref=${encodeURIComponent(tagName)}&format=zip`; } state.openDialog = 'createRelease'; }} onLoadTags={loadTags} /> {#if state.ui.activeTab === 'code-search'} {/if} {#if state.ui.activeTab === 'issues'} { state.selected.issue = id; loadIssueReplies(id); }} onStatusUpdate={async (id, status) => { // Find issue and update status const issue = state.issues.find((i: { id: string }) => i.id === id); if (issue) { await updateIssueStatus(id, issue.author, status as 'open' | 'closed' | 'resolved' | 'draft'); await loadIssues(); } }} issueReplies={state.issueReplies} loadingReplies={state.loading.issueReplies} activeTab={state.ui.activeTab} {tabs} onTabChange={(tab: string) => { state.ui.activeTab = tab as typeof state.ui.activeTab; // Update URL query parameter without page reload const url = new URL($page.url); if (tab === 'docs') { url.searchParams.delete('tab'); } else { url.searchParams.set('tab', tab); } goto(url.pathname + url.search, { replaceState: true, noScroll: true }); }} onCreate={() => { state.openDialog = 'createIssue'; }} userPubkey={state.user.pubkey || null} /> {/if} {#if state.ui.activeTab === 'prs'} { state.selected.pr = id; }} onStatusUpdate={async (id, status) => { // Find PR and update status - similar to updateIssueStatus const pr = state.prs.find((p: { id: string }) => p.id === id); if (pr && state.user.pubkeyHex) { // Check if user is maintainer or PR author const isAuthor = state.user.pubkeyHex === pr.author; if (!state.maintainers.isMaintainer && !isAuthor) { alert('Only repository maintainers or PR authors can update PR status'); return; } try { const response = await fetch(`/api/repos/${state.npub}/${state.repo}/pull-requests/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prAuthor: pr.author, status }) }); if (!response.ok) { const data = await response.json(); throw new Error(data.state.error || 'Failed to update PR status'); } await loadPRs(); } catch (err) { state.error = err instanceof Error ? err.message : 'Failed to update PR status'; console.error('Error updating PR status:', err); } } }} activeTab={state.ui.activeTab} {tabs} onTabChange={(tab: string) => { state.ui.activeTab = tab as typeof state.ui.activeTab; // Update URL query parameter without page reload const url = new URL($page.url); if (tab === 'docs') { url.searchParams.delete('tab'); } else { url.searchParams.set('tab', tab); } goto(url.pathname + url.search, { replaceState: true, noScroll: true }); }} onCreate={() => { state.openDialog = 'createPR'; }} userPubkey={state.user.pubkey || null} /> {/if} {#if state.ui.activeTab === 'patches'} { state.selected.patch = id; }} onStatusUpdate={async (id, status) => { const patch = state.patches.find((p: { id: string }) => p.id === id); if (patch) { await updatePatchStatus(id, patch.author, status); } }} onApply={async (id) => { applying[id] = true; try { const patch = state.patches.find((p: { id: string }) => p.id === id); if (!patch) { throw new Error('Patch not found'); } if (!state.user.pubkey || !state.maintainers.isMaintainer || needsClone) { alert('Only maintainers can apply patches'); return; } // Check if repo is empty (no branches) if (state.git.branches.length === 0) { alert('Cannot apply patch to an empty repository. Please create a branch first.'); return; } if (!confirm('Apply this patch to the repository? This will create a commit with the patch changes.')) { return; } const authorEmail = await getUserEmail(); const authorName = await getUserName(); // Use current branch, default branch, or first branch (repo is not empty at this point) const branch = state.git.currentBranch || state.git.defaultBranch || (state.git.branches.length > 0 ? (typeof state.git.branches[0] === 'string' ? state.git.branches[0] : state.git.branches[0].name) : null); if (!branch) { alert('No branch available to apply patch to. Please create a branch first.'); return; } const response = await fetch(`/api/repos/${state.npub}/${state.repo}/patches/${id}/apply`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...buildApiHeaders() }, body: JSON.stringify({ message: `Apply patch ${id.slice(0, 8)}: ${patch.subject}`, authorName, authorEmail, branch }) }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.message || 'Failed to apply patch'); } await loadPatches(); alert('Patch applied successfully!'); } catch (err) { state.error = err instanceof Error ? err.message : 'Failed to apply patch'; console.error('Error applying patch:', err); } finally { applying[id] = false; } }} {applying} activeTab={state.ui.activeTab} {tabs} onTabChange={(tab: string) => { state.ui.activeTab = tab as typeof state.ui.activeTab; // Update URL query parameter without page reload const url = new URL($page.url); if (tab === 'docs') { url.searchParams.delete('tab'); } else { url.searchParams.set('tab', tab); } goto(url.pathname + url.search, { replaceState: true, noScroll: true }); }} onCreate={() => { state.openDialog = 'createPatch'; }} userPubkey={state.user.pubkey || null} /> {/if} {#if state.ui.activeTab === 'discussions'} { state.ui.activeTab = tab as typeof state.ui.activeTab; // Update URL query parameter without page reload const url = new URL($page.url); if (tab === 'docs') { url.searchParams.delete('tab'); } else { url.searchParams.set('tab', tab); } goto(url.pathname + url.search, { replaceState: true, noScroll: true }); }} /> {/if} {#if state.ui.activeTab === 'docs' || (!['files', 'issues', 'prs', 'patches', 'discussions', 'history', 'tags', 'code-search'].includes(state.ui.activeTab))} { state.ui.activeTab = tab as typeof state.ui.activeTab; // Update URL query parameter without page reload const url = new URL($page.url); if (tab === 'docs') { url.searchParams.delete('tab'); } else { url.searchParams.set('tab', tab); } goto(url.pathname + url.search, { replaceState: true, noScroll: true }); }} isMaintainer={state.maintainers.isMaintainer} reloadTrigger={docsReloadTrigger} onCreateDocumentation={() => { if (!state.user.pubkey || !state.maintainers.isMaintainer) return; state.openDialog = 'createDocumentation'; }} /> {/if} {#if state.ui.activeTab === 'code-search'}
{#if state.files.list.length === 0 && !state.loading.main}

This repo is empty and contains no files.

{:else}
e.key === 'Enter' && performCodeSearch()} class="code-search-input" />
{#if state.loading.codeSearch}

Searching...

{:else if state.codeSearch.results.length > 0}

Found {state.codeSearch.results.length} result{state.codeSearch.results.length !== 1 ? 's' : ''}

{#each state.codeSearch.results as result}
{result.file} Line {result.line} {#if state.codeSearch.scope === 'all' && 'repo' in result} {result.repo || state.npub}/{result.repo || state.repo} {/if}
{result.content}
{/each}
{:else if state.codeSearch.query.trim() && !state.loading.codeSearch}
{#if state.error}

Error: {state.error}

{:else}

No results found

{/if}
{/if} {/if}
{/if}
{/if}
state.openDialog = null} /> state.openDialog = null} /> state.openDialog = null} /> state.openDialog = null} /> state.openDialog = null} /> state.openDialog = null} /> state.openDialog = null} /> state.openDialog = null} /> state.openDialog = null} /> state.openDialog = null} /> state.openDialog = null} /> state.openDialog = null} /> state.openDialog = null} /> state.openDialog = null} /> state.openDialog = null} />