Browse Source

refactor 3

Nostr-Signature: a761c789227ef2368eff89f7062fa7889820c4846701667360978cfdad08c3d2 573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc 9d229200ab66d3f4a0a2a21112c9100ee14d0a5d9f8409a35fef36f195f5f73c8ac2344aa1175cc476f650336a5a10ea6ac0076c8ec2cb229fea7d600c5d4399
main
Silberengel 2 weeks ago
parent
commit
2cbb0b5d26
  1. 1
      nostr/commit-signatures.jsonl
  2. 790
      src/routes/repos/[npub]/[repo]/+page.svelte
  3. 132
      src/routes/repos/[npub]/[repo]/components/dialogs/CloneUrlVerificationDialog.svelte
  4. 99
      src/routes/repos/[npub]/[repo]/components/dialogs/CommitDialog.svelte
  5. 96
      src/routes/repos/[npub]/[repo]/components/dialogs/CreateBranchDialog.svelte
  6. 101
      src/routes/repos/[npub]/[repo]/components/dialogs/CreateFileDialog.svelte
  7. 79
      src/routes/repos/[npub]/[repo]/components/dialogs/CreateIssueDialog.svelte
  8. 87
      src/routes/repos/[npub]/[repo]/components/dialogs/CreatePRDialog.svelte
  9. 86
      src/routes/repos/[npub]/[repo]/components/dialogs/CreatePatchDialog.svelte
  10. 96
      src/routes/repos/[npub]/[repo]/components/dialogs/CreateReleaseDialog.svelte
  11. 86
      src/routes/repos/[npub]/[repo]/components/dialogs/CreateTagDialog.svelte
  12. 79
      src/routes/repos/[npub]/[repo]/components/dialogs/CreateThreadDialog.svelte
  13. 67
      src/routes/repos/[npub]/[repo]/components/dialogs/Modal.svelte
  14. 84
      src/routes/repos/[npub]/[repo]/components/dialogs/PatchCommentDialog.svelte
  15. 94
      src/routes/repos/[npub]/[repo]/components/dialogs/PatchHighlightDialog.svelte
  16. 86
      src/routes/repos/[npub]/[repo]/components/dialogs/ReplyDialog.svelte
  17. 117
      src/routes/repos/[npub]/[repo]/components/dialogs/VerificationDialog.svelte

1
nostr/commit-signatures.jsonl

@ -92,3 +92,4 @@
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772087425,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","refactoring 1"]],"content":"Signed commit: refactoring 1","id":"533e9f7acbdd4dc16dbe304245469d57d8d37f0c0cce53b60d99719e2acf4502","sig":"0fad2d7c44f086ceb06ce40ea8cea2d4d002ebe8caec7d78e83483348b1404cfb6256d8d3796ebd9ae6f7866a431ec4a1abe84e417d3e238b9b554b4a32481e4"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772087425,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","refactoring 1"]],"content":"Signed commit: refactoring 1","id":"533e9f7acbdd4dc16dbe304245469d57d8d37f0c0cce53b60d99719e2acf4502","sig":"0fad2d7c44f086ceb06ce40ea8cea2d4d002ebe8caec7d78e83483348b1404cfb6256d8d3796ebd9ae6f7866a431ec4a1abe84e417d3e238b9b554b4a32481e4"}
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772090269,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","refactoring 2"]],"content":"Signed commit: refactoring 2","id":"9375bfe35e0574bc722cad243c22fdf374dcc9016f91f358ff9ddf1d0a03bb50","sig":"10fbbcbc7cab48dfd2340f0c9eceafe558d893789e4838cbe26493e5c339f7a1f015d1cc4af8bfa51d57e9a9da94bb1bb44841305d5ce7cf92db9938985d0459"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772090269,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","refactoring 2"]],"content":"Signed commit: refactoring 2","id":"9375bfe35e0574bc722cad243c22fdf374dcc9016f91f358ff9ddf1d0a03bb50","sig":"10fbbcbc7cab48dfd2340f0c9eceafe558d893789e4838cbe26493e5c339f7a1f015d1cc4af8bfa51d57e9a9da94bb1bb44841305d5ce7cf92db9938985d0459"}
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772104036,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","fix build"]],"content":"Signed commit: fix build","id":"830b91f4efe7d208128a008d44fd3b4352c09af0a83b40ea1fab769f9c8563cf","sig":"49a9772580d5ba1b9b9800bdb53f0f4b55661f6062f9968b18cbbd4983d7a042b477281769488d44b4f43c7bdf627d621d83c16659d3d8d226fb32fe0a450756"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772104036,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","fix build"]],"content":"Signed commit: fix build","id":"830b91f4efe7d208128a008d44fd3b4352c09af0a83b40ea1fab769f9c8563cf","sig":"49a9772580d5ba1b9b9800bdb53f0f4b55661f6062f9968b18cbbd4983d7a042b477281769488d44b4f43c7bdf627d621d83c16659d3d8d226fb32fe0a450756"}
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772105581,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","fdix build"]],"content":"Signed commit: fdix build","id":"aa457cd97e3af5c7e7e6f8938d159f62de2eee27afcf9a9a415192a8b39cd038","sig":"1959bae547fefff3b3fd72e23071e989724ab71f2042bad9cb5a969133045119a068b529df17ded13db96b54372f662760df79a34f1b6072dcabf5d2f003000b"}

790
src/routes/repos/[npub]/[repo]/+page.svelte

@ -17,6 +17,20 @@
import PatchesTab from './components/PatchesTab.svelte'; import PatchesTab from './components/PatchesTab.svelte';
import DocsTab from './components/DocsTab.svelte'; import DocsTab from './components/DocsTab.svelte';
import DiscussionsTab from './components/DiscussionsTab.svelte'; import DiscussionsTab from './components/DiscussionsTab.svelte';
import CreateFileDialog from './components/dialogs/CreateFileDialog.svelte';
import CreateBranchDialog from './components/dialogs/CreateBranchDialog.svelte';
import CreateTagDialog from './components/dialogs/CreateTagDialog.svelte';
import CreateReleaseDialog from './components/dialogs/CreateReleaseDialog.svelte';
import CreateIssueDialog from './components/dialogs/CreateIssueDialog.svelte';
import CreateThreadDialog from './components/dialogs/CreateThreadDialog.svelte';
import ReplyDialog from './components/dialogs/ReplyDialog.svelte';
import CreatePRDialog from './components/dialogs/CreatePRDialog.svelte';
import CreatePatchDialog from './components/dialogs/CreatePatchDialog.svelte';
import PatchHighlightDialog from './components/dialogs/PatchHighlightDialog.svelte';
import PatchCommentDialog from './components/dialogs/PatchCommentDialog.svelte';
import CommitDialog from './components/dialogs/CommitDialog.svelte';
import VerificationDialog from './components/dialogs/VerificationDialog.svelte';
import CloneUrlVerificationDialog from './components/dialogs/CloneUrlVerificationDialog.svelte';
import { downloadRepository as downloadRepoUtil } from './utils/download.js'; import { downloadRepository as downloadRepoUtil } from './utils/download.js';
import { buildApiHeaders } from './utils/api-client.js'; import { buildApiHeaders } from './utils/api-client.js';
import '$lib/styles/repo.css'; import '$lib/styles/repo.css';
@ -5893,675 +5907,113 @@
{/if} {/if}
</main> </main>
<!-- Create File Dialog --> <!-- Dialogs -->
{#if state.openDialog === 'createFile' && state.user.pubkey && state.maintainers.isMaintainer} <CreateFileDialog
<div open={state.openDialog === 'createFile' && !!state.user.pubkey && state.maintainers.isMaintainer}
class="modal-overlay" {state}
role="dialog" {needsClone}
aria-modal="true" {cloneTooltip}
aria-label="Create new file" onCreate={createFile}
onclick={() => state.openDialog = null} onClose={() => state.openDialog = null}
onkeydown={(e) => e.key === 'Escape' && (state.openDialog = null)} />
tabindex="-1"
> <CreateBranchDialog
<!-- svelte-ignore a11y_click_events_have_key_events --> open={state.openDialog === 'createBranch' && !!state.user.pubkey && state.maintainers.isMaintainer}
<!-- svelte-ignore a11y_no_noninteractive_element_interactions --> {state}
<div {needsClone}
class="modal" {cloneTooltip}
role="document" onCreate={createBranch}
onclick={(e) => e.stopPropagation()} onClose={() => state.openDialog = null}
> />
<h3>Create New File</h3>
{#if state.git.branches.length > 0} <CreateTagDialog
<label> open={state.openDialog === 'createTag' && !!state.user.pubkey && state.maintainers.isMaintainer}
Branch: {state}
<select bind:value={state.git.currentBranch} disabled={state.saving}> {needsClone}
{#each state.git.branches as branch} {cloneTooltip}
{@const branchName = typeof branch === 'string' ? branch : branch.name} onCreate={createTag}
<option value={branchName}>{branchName}{#if branchName === state.git.defaultBranch} (default){/if}</option> onClose={() => state.openDialog = null}
{/each} />
</select>
</label> <CreateReleaseDialog
{:else if state.git.currentBranch} open={state.openDialog === 'createRelease' && !!state.user.pubkey && (state.maintainers.isMaintainer || state.user.pubkeyHex === repoOwnerPubkeyDerived) && !!state.clone.isCloned}
<label> {state}
Branch: onCreate={createRelease}
<input type="text" value={state.git.currentBranch} disabled /> onClose={() => state.openDialog = null}
</label> />
{/if}
<label> <CreateIssueDialog
File Name: open={state.openDialog === 'createIssue' && !!state.user.pubkey}
<input type="text" bind:value={state.forms.file.fileName} placeholder="filename.md" /> {state}
</label> onCreate={createIssue}
<label> onClose={() => state.openDialog = null}
Content: />
<textarea bind:value={state.forms.file.content} rows="10" placeholder="File content..."></textarea>
</label> <CreateThreadDialog
<div class="modal-actions"> open={state.openDialog === 'createThread' && !!state.user.pubkey}
<button onclick={() => state.openDialog = null} class="cancel-button">Cancel</button> {state}
<button onCreate={createDiscussionThread}
onclick={createFile} onClose={() => state.openDialog = null}
disabled={!state.forms.file.fileName.trim() || state.saving || needsClone || !state.git.currentBranch} />
class="save-button"
title={needsClone ? cloneTooltip : (!state.git.currentBranch ? 'Please select a branch' : '')} <ReplyDialog
> open={state.openDialog === 'reply' && !!state.user.pubkey && (!!state.discussion.replyingToThread || !!state.discussion.replyingToComment)}
{state.saving ? 'Creating...' : 'Create'} {state}
</button> onCreate={createThreadReply}
</div> onClose={() => state.openDialog = null}
</div> />
</div>
{/if} <CreatePRDialog
open={state.openDialog === 'createPR' && !!state.user.pubkey}
<!-- Create Branch Dialog --> {state}
{#if state.openDialog === 'createBranch' && state.user.pubkey && state.maintainers.isMaintainer} onCreate={createPR}
<div onClose={() => state.openDialog = null}
class="modal-overlay" />
role="dialog"
aria-modal="true" <CreatePatchDialog
aria-label="Create new branch" open={state.openDialog === 'createPatch' && !!state.user.pubkey}
onclick={() => state.openDialog = null} {state}
onkeydown={(e) => e.key === 'Escape' && (state.openDialog = null)} onCreate={createPatch}
tabindex="-1" onClose={() => state.openDialog = null}
> />
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions --> <PatchHighlightDialog
<div open={state.openDialog === 'patchHighlight'}
class="modal" {state}
role="document" onCreate={createPatchHighlight}
onclick={(e) => e.stopPropagation()} onClose={() => state.openDialog = null}
> />
<h3>Create New Branch</h3>
<label> <PatchCommentDialog
Branch Name: open={state.openDialog === 'patchComment'}
<input type="text" bind:value={state.forms.branch.name} placeholder="feature/new-feature" /> {state}
</label> onCreate={createPatchComment}
<label> onClose={() => state.openDialog = null}
From Branch: />
<select bind:value={state.forms.branch.from}>
{#if state.git.branches.length === 0} <CommitDialog
<option value={null}>No state.git.branches - will create initial branch</option> open={state.openDialog === 'commit' && !!state.user.pubkey && state.maintainers.isMaintainer}
{:else} {state}
{#each state.git.branches as branch} {needsClone}
{@const branchName = typeof branch === 'string' ? branch : (branch as { name: string }).name} {cloneTooltip}
{@const isDefaultBranch = branchName === state.forms.branch.defaultName} onCommit={saveFile}
{#if !isDefaultBranch} onClose={() => state.openDialog = null}
<option value={branchName}>{branchName}</option> />
{/if}
{/each} <VerificationDialog
{/if} open={state.openDialog === 'verification' && !!state.verification.fileContent}
</select> {state}
</label> onCopy={copyVerificationToClipboard}
<div class="modal-actions"> onDownload={downloadVerificationFile}
<button onclick={() => state.openDialog = null} class="cancel-button">Cancel</button> onClose={() => state.openDialog = null}
<button />
onclick={createBranch}
disabled={!state.forms.branch.name.trim() || state.saving || needsClone} <CloneUrlVerificationDialog
class="save-button" open={state.openDialog === 'cloneUrlVerification'}
title={needsClone ? cloneTooltip : ''} {state}
> onVerify={verifyCloneUrl}
{state.saving ? 'Creating...' : 'Create Branch'} onClose={() => state.openDialog = null}
</button> />
</div>
</div>
</div>
{/if}
<!-- Create Tag Dialog -->
{#if state.openDialog === 'createTag' && state.user.pubkey && state.maintainers.isMaintainer}
<div
class="modal-overlay"
role="dialog"
aria-modal="true"
aria-label="Create new tag"
onclick={() => state.openDialog = null}
onkeydown={(e) => e.key === 'Escape' && (state.openDialog = null)}
tabindex="-1"
>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<div
class="modal"
role="document"
onclick={(e) => e.stopPropagation()}
>
<h3>Create New Tag</h3>
<label>
Tag Name:
<input type="text" bind:value={state.forms.tag.name} placeholder="v1.0.0" />
</label>
<label>
Reference (commit/branch):
<input type="text" bind:value={state.forms.tag.ref} placeholder="HEAD" />
</label>
<label>
Message (optional, for annotated tag):
<textarea bind:value={state.forms.tag.message} rows="3" placeholder="Tag message..."></textarea>
</label>
<div class="modal-actions">
<button onclick={() => state.openDialog = null} class="cancel-button">Cancel</button>
<button
onclick={createTag}
disabled={!state.forms.tag.name.trim() || state.saving || needsClone}
class="save-button"
title={needsClone ? cloneTooltip : ''}
>
{state.saving ? 'Creating...' : 'Create Tag'}
</button>
</div>
</div>
</div>
{/if}
<!-- Create Release Dialog -->
{#if state.openDialog === 'createRelease' && state.user.pubkey && (state.maintainers.isMaintainer || state.user.pubkeyHex === repoOwnerPubkeyDerived) && state.clone.isCloned}
<div
class="modal-overlay"
role="dialog"
aria-modal="true"
aria-label="Create new release"
onclick={() => state.openDialog = null}
onkeydown={(e) => e.key === 'Escape' && (state.openDialog = null)}
tabindex="-1"
>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<div
class="modal"
role="document"
onclick={(e) => e.stopPropagation()}
>
<h3>Create New Release</h3>
<label>
Tag Name:
<input type="text" bind:value={state.forms.release.tagName} placeholder="v1.0.0" />
</label>
<label>
Tag Hash (commit hash):
<input type="text" bind:value={state.forms.release.tagHash} placeholder="abc1234..." />
</label>
<label>
Release Notes:
<textarea bind:value={state.forms.release.notes} rows="10" placeholder="Release notes in markdown..."></textarea>
</label>
<label>
<input type="checkbox" bind:checked={state.forms.release.isDraft} />
Draft Release
</label>
<label>
<input type="checkbox" bind:checked={state.forms.release.isPrerelease} />
Pre-release
</label>
<div class="modal-actions">
<button onclick={() => state.openDialog = null} class="cancel-button">Cancel</button>
<button
onclick={createRelease}
disabled={!state.forms.release.tagName.trim() || !state.forms.release.tagHash.trim() || state.creating.release}
class="save-button"
>
{state.creating.release ? 'Creating...' : 'Create Release'}
</button>
</div>
</div>
</div>
{/if}
<!-- Create Issue Dialog -->
{#if state.openDialog === 'createIssue' && state.user.pubkey}
<div
class="modal-overlay"
role="dialog"
aria-modal="true"
aria-label="Create new issue"
onclick={() => state.openDialog = null}
onkeydown={(e) => e.key === 'Escape' && (state.openDialog = null)}
tabindex="-1"
>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<div
class="modal"
role="document"
onclick={(e) => e.stopPropagation()}
>
<h3>Create New Issue</h3>
<label>
Subject:
<input type="text" bind:value={state.forms.issue.subject} placeholder="Issue title..." />
</label>
<label>
Description:
<textarea bind:value={state.forms.issue.content} rows="10" placeholder="Describe the issue..."></textarea>
</label>
<div class="modal-actions">
<button onclick={() => state.openDialog = null} class="cancel-button">Cancel</button>
<button onclick={createIssue} disabled={!state.forms.issue.subject.trim() || !state.forms.issue.content.trim() || state.saving} class="save-button">
{state.saving ? 'Creating...' : 'Create Issue'}
</button>
</div>
</div>
</div>
{/if}
<!-- Create Discussion Thread Dialog -->
{#if state.openDialog === 'createThread' && state.user.pubkey}
<div
class="modal-overlay"
role="dialog"
aria-modal="true"
aria-label="Create new discussion thread"
onclick={() => state.openDialog = null}
onkeydown={(e) => e.key === 'Escape' && (state.openDialog = null)}
tabindex="-1"
>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<div
class="modal"
role="document"
onclick={(e) => e.stopPropagation()}
>
<h3>Create New Discussion Thread</h3>
<label>
Title:
<input type="text" bind:value={state.forms.discussion.threadTitle} placeholder="Thread title..." />
</label>
<label>
Content:
<textarea bind:value={state.forms.discussion.threadContent} rows="10" placeholder="Start the discussion..."></textarea>
</label>
<div class="modal-actions">
<button onclick={() => state.openDialog = null} class="cancel-button">Cancel</button>
<button onclick={createDiscussionThread} disabled={!state.forms.discussion.threadTitle.trim() || state.creating.thread} class="save-button">
{state.creating.thread ? 'Creating...' : 'Create Thread'}
</button>
</div>
</div>
</div>
{/if}
<!-- Reply to Thread/Comment Dialog -->
{#if state.openDialog === 'reply' && state.user.pubkey && (state.discussion.replyingToThread || state.discussion.replyingToComment)}
<div
class="modal-overlay"
role="dialog"
aria-modal="true"
aria-label="Reply to thread"
onclick={() => {
state.openDialog = null;
state.discussion.replyingToThread = null;
state.discussion.replyingToComment = null;
state.forms.discussion.replyContent = '';
}}
onkeydown={(e) => e.key === 'Escape' && (state.openDialog = null)}
tabindex="-1"
>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<div
class="modal"
role="document"
onclick={(e) => e.stopPropagation()}
>
<h3>
{#if state.discussion.replyingToComment}
Reply to Comment
{:else if state.discussion.replyingToThread}
Reply to Thread
{:else}
Reply
{/if}
</h3>
<label>
Your Reply:
<textarea bind:value={state.forms.discussion.replyContent} rows="8" placeholder="Write your reply..."></textarea>
</label>
<div class="modal-actions">
<button
onclick={() => {
state.openDialog = null;
state.discussion.replyingToThread = null;
state.discussion.replyingToComment = null;
state.forms.discussion.replyContent = '';
}}
class="cancel-button"
>
Cancel
</button>
<button
onclick={() => createThreadReply()}
disabled={!state.forms.discussion.replyContent.trim() || state.creating.reply}
class="save-button"
>
{state.creating.reply ? 'Posting...' : 'Post Reply'}
</button>
</div>
</div>
</div>
{/if}
<!-- Create PR Dialog -->
{#if state.openDialog === 'createPR' && state.user.pubkey}
<div
class="modal-overlay"
role="dialog"
aria-modal="true"
aria-label="Create new pull request"
onclick={() => state.openDialog = null}
onkeydown={(e) => e.key === 'Escape' && (state.openDialog = null)}
tabindex="-1"
>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<div
class="modal"
role="document"
onclick={(e) => e.stopPropagation()}
>
<h3>Create New Pull Request</h3>
<label>
Subject:
<input type="text" bind:value={state.forms.pr.subject} placeholder="PR title..." />
</label>
<label>
Description:
<textarea bind:value={state.forms.pr.content} rows="8" placeholder="Describe your changes..."></textarea>
</label>
<label>
Commit ID:
<input type="text" bind:value={state.forms.pr.commitId} placeholder="Commit hash..." />
</label>
<label>
Branch Name (optional):
<input type="text" bind:value={state.forms.pr.branchName} placeholder="feature/new-feature" />
</label>
<div class="modal-actions">
<button onclick={() => state.openDialog = null} class="cancel-button">Cancel</button>
<button onclick={createPR} disabled={!state.forms.pr.subject.trim() || !state.forms.pr.content.trim() || !state.forms.pr.commitId.trim() || state.saving} class="save-button">
{state.saving ? 'Creating...' : 'Create PR'}
</button>
</div>
</div>
</div>
{/if}
<!-- Create Patch Dialog -->
{#if state.openDialog === 'createPatch' && state.user.pubkey}
<div
class="modal-overlay"
role="dialog"
aria-modal="true"
aria-label="Create new patch"
onclick={() => state.openDialog = null}
onkeydown={(e) => e.key === 'Escape' && (state.openDialog = null)}
tabindex="-1"
>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<div
class="modal"
role="document"
onclick={(e) => e.stopPropagation()}
>
<h3>Create New Patch</h3>
<p class="help-text">Enter your patch content in git format-patch format. Patches should be under 60KB.</p>
<label>
Subject (optional):
<input type="text" bind:value={state.forms.patch.subject} placeholder="Patch title..." />
</label>
<label>
Patch Content:
<textarea bind:value={state.forms.patch.content} rows="15" placeholder="Paste your git format-patch output here..."></textarea>
</label>
<div class="modal-actions">
<button onclick={() => state.openDialog = null} class="cancel-button">Cancel</button>
<button onclick={createPatch} disabled={!state.forms.patch.content.trim() || state.creating.patch} class="save-button">
{state.creating.patch ? 'Creating...' : 'Create Patch'}
</button>
</div>
</div>
</div>
{/if}
<!-- Patch Highlight Dialog -->
{#if state.openDialog === 'patchHighlight'}
<div
class="modal-overlay"
role="dialog"
aria-modal="true"
aria-label="Create highlight"
tabindex="-1"
onclick={() => state.openDialog = null}
onkeydown={(e) => {
if (e.key === 'Escape') {
state.openDialog = null;
}
}}
>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<div class="modal" role="document" onclick={(e) => e.stopPropagation()}>
<h3>Create Highlight</h3>
<div class="selected-code">
<pre><code>{state.forms.patchHighlight.text}</code></pre>
</div>
<label>
Comment (optional):
<textarea bind:value={state.forms.patchHighlight.comment} rows="4" placeholder="Add a comment about this code..."></textarea>
</label>
<div class="modal-actions">
<button onclick={() => state.openDialog = null} class="cancel-btn">Cancel</button>
<button onclick={createPatchHighlight} disabled={state.creating.patchHighlight} class="save-btn">
{state.creating.patchHighlight ? 'Creating...' : 'Create Highlight'}
</button>
</div>
</div>
</div>
{/if}
<!-- Patch Comment Dialog -->
{#if state.openDialog === 'patchComment'}
<div
class="modal-overlay"
role="dialog"
aria-modal="true"
aria-label={state.forms.patchComment.replyingTo ? 'Reply to comment' : 'Add comment'}
tabindex="-1"
onclick={() => { state.openDialog = null; state.forms.patchComment.replyingTo = null; }}
onkeydown={(e) => {
if (e.key === 'Escape') {
state.openDialog = null;
state.forms.patchComment.replyingTo = null;
}
}}
>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<div class="modal" role="document" onclick={(e) => e.stopPropagation()}>
<h3>{state.forms.patchComment.replyingTo ? 'Reply to Comment' : 'Add Comment'}</h3>
<label>
Comment:
<textarea bind:value={state.forms.patchComment.content} rows="6" placeholder="Write your comment..."></textarea>
</label>
<div class="modal-actions">
<button onclick={() => { state.openDialog = null; state.forms.patchComment.replyingTo = null; }} class="cancel-btn">Cancel</button>
<button onclick={createPatchComment} disabled={state.creating.patchComment || !state.forms.patchComment.content.trim()} class="save-btn">
{state.creating.patchComment ? 'Creating...' : (state.forms.patchComment.replyingTo ? 'Reply' : 'Add Comment')}
</button>
</div>
</div>
</div>
{/if}
<!-- Commit Dialog -->
{#if state.openDialog === 'commit' && state.user.pubkey && state.maintainers.isMaintainer}
<div
class="modal-overlay"
role="dialog"
aria-modal="true"
aria-label="Commit changes"
onclick={() => state.openDialog = null}
onkeydown={(e) => e.key === 'Escape' && (state.openDialog = null)}
tabindex="-1"
>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<div
class="modal"
role="document"
onclick={(e) => e.stopPropagation()}
>
<h3>Commit Changes</h3>
{#if state.git.branches.length > 0}
<label>
Branch:
<select bind:value={state.git.currentBranch} disabled={state.saving}>
{#each state.git.branches as branch}
{@const branchName = typeof branch === 'string' ? branch : branch.name}
<option value={branchName}>{branchName}{#if branchName === state.git.defaultBranch} (default){/if}</option>
{/each}
</select>
</label>
{:else if state.git.currentBranch}
<label>
Branch:
<input type="text" value={state.git.currentBranch} disabled />
</label>
{/if}
<label>
Commit Message:
<textarea
bind:value={state.forms.commit.message}
placeholder="Describe your changes..."
rows="4"
></textarea>
</label>
<div class="modal-actions">
<button onclick={() => state.openDialog = null} class="cancel-button">Cancel</button>
<button
onclick={saveFile}
disabled={!state.forms.commit.message.trim() || state.saving || needsClone || !state.git.currentBranch}
class="save-button"
title={needsClone ? cloneTooltip : (!state.git.currentBranch ? 'Please select a branch' : '')}
>
{state.saving ? 'Saving...' : 'Commit & Save'}
</button>
</div>
</div>
</div>
{/if}
<!-- Verification File Dialog -->
{#if state.openDialog === 'verification' && state.verification.fileContent}
<div
class="modal-overlay"
role="dialog"
aria-modal="true"
aria-label="Repository verification file"
onclick={() => state.openDialog = null}
onkeydown={(e) => e.key === 'Escape' && (state.openDialog = null)}
tabindex="-1"
>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<div
class="modal verification-modal"
role="document"
onclick={(e) => e.stopPropagation()}
>
<div class="modal-header">
<h3>Repository Verification File</h3>
</div>
<div class="modal-body">
<p class="verification-instructions">
The announcement event should be saved to <code>nostr/repo-events.jsonl</code> in your repository.
You can download the announcement event JSON below for reference.
</p>
<div class="verification-file-content">
<div class="file-header">
<span class="filename">announcement-event.json</span>
<div class="file-actions">
<button onclick={copyVerificationToClipboard} class="copy-button">Copy</button>
<button onclick={downloadVerificationFile} class="download-button">Download</button>
</div>
</div>
<pre class="file-content"><code>{state.verification.fileContent}</code></pre>
</div>
</div>
<div class="modal-actions">
<button onclick={() => state.openDialog = null} class="cancel-button">Close</button>
</div>
</div>
</div>
{/if}
<!-- Clone URL Verification Dialog -->
{#if state.openDialog === 'cloneUrlVerification'}
<div
class="modal-overlay"
role="dialog"
aria-modal="true"
aria-label="Verify repository"
onclick={() => state.openDialog = null}
onkeydown={(e) => e.key === 'Escape' && (state.openDialog = null)}
tabindex="-1"
>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<div
class="modal verification-modal"
role="document"
onclick={(e) => e.stopPropagation()}
>
<div class="modal-header">
<h3>Verify Repository</h3>
</div>
<div class="modal-body">
<p class="verification-instructions">
Verify this repository by committing the repo announcement event to it.
</p>
{#if state.verification.selectedCloneUrl}
<p style="margin: 1rem 0;">
<strong>Clone URL:</strong> <code>{state.verification.selectedCloneUrl}</code>
</p>
{/if}
{#if state.clone.isCloned !== true}
<div class="state.error-message" style="margin: 1rem 0; padding: 0.75rem; background: var(--bg-warning, #fff3cd); border-left: 4px solid var(--text-warning, #856404); border-radius: 4px; color: var(--text-warning, #856404);">
<strong>Repository must be cloned first.</strong> Please clone this repository to the server before verifying ownership.
</div>
{:else}
<p style="margin: 1rem 0; color: var(--text-secondary);">
This will commit the repository announcement event to <code>nostr/repo-events.jsonl</code> in the default branch, which verifies that you control this repository.
</p>
{/if}
{#if state.error}
<div class="state.error-message" style="margin: 1rem 0; padding: 0.75rem; background: var(--bg-state.error, #fee); border-left: 4px solid var(--accent-state.error, #f00); border-radius: 4px;">
{state.error}
</div>
{/if}
</div>
<div class="modal-footer">
<button
onclick={verifyCloneUrl}
class="primary-button"
disabled={!!state.verification.selectedCloneUrl || !state.clone.isCloned}
title={!state.clone.isCloned ? 'Repository must be cloned first' : ''}
>
{state.verification.selectedCloneUrl ? 'Verifying...' : 'Verify Repository'}
</button>
<button
onclick={() => {
state.openDialog = null;
state.verification.selectedCloneUrl = null;
state.error = null;
}}
class="cancel-button"
disabled={!!state.verification.selectedCloneUrl}
>
Cancel
</button>
</div>
</div>
</div>
{/if}
</div> </div>
<style> <style>

132
src/routes/repos/[npub]/[repo]/components/dialogs/CloneUrlVerificationDialog.svelte

@ -0,0 +1,132 @@
<script lang="ts">
import Modal from './Modal.svelte';
import type { RepoState } from '../../stores/repo-state.js';
interface Props {
open: boolean;
state: RepoState;
onVerify: () => void;
onClose: () => void;
}
let { open, state, onVerify, onClose }: Props = $props();
function handleClose() {
state.verification.selectedCloneUrl = null;
state.error = null;
onClose();
}
</script>
<Modal {open} title="Verify Repository" ariaLabel="Verify repository" onClose={handleClose}>
<div class="modal-body">
<p class="verification-instructions">
Verify this repository by committing the repo announcement event to it.
</p>
{#if state.verification.selectedCloneUrl}
<p style="margin: 1rem 0;">
<strong>Clone URL:</strong> <code>{state.verification.selectedCloneUrl}</code>
</p>
{/if}
{#if state.clone.isCloned !== true}
<div class="error-message warning">
<strong>Repository must be cloned first.</strong> Please clone this repository to the server before verifying ownership.
</div>
{:else}
<p style="margin: 1rem 0; color: var(--text-secondary);">
This will commit the repository announcement event to <code>nostr/repo-events.jsonl</code> in the default branch, which verifies that you control this repository.
</p>
{/if}
{#if state.error}
<div class="error-message">
{state.error}
</div>
{/if}
</div>
<div class="modal-footer">
<button
onclick={onVerify}
class="primary-button"
disabled={!!state.verification.selectedCloneUrl || !state.clone.isCloned}
title={!state.clone.isCloned ? 'Repository must be cloned first' : ''}
>
{state.verification.selectedCloneUrl ? 'Verifying...' : 'Verify Repository'}
</button>
<button
onclick={handleClose}
class="cancel-button"
disabled={!!state.verification.selectedCloneUrl}
>
Cancel
</button>
</div>
</Modal>
<style>
.modal-body {
margin-bottom: 1rem;
}
.verification-instructions {
margin-bottom: 1rem;
}
.verification-instructions code {
background: var(--bg-secondary, #f5f5f5);
padding: 0.2rem 0.4rem;
border-radius: 3px;
font-family: monospace;
}
.error-message {
margin: 1rem 0;
padding: 0.75rem;
border-radius: 4px;
}
.error-message.warning {
background: var(--bg-warning, #fff3cd);
border-left: 4px solid var(--text-warning, #856404);
color: var(--text-warning, #856404);
}
.error-message:not(.warning) {
background: var(--bg-error, #fee);
border-left: 4px solid var(--accent-error, #f00);
color: var(--text-error, #c00);
}
.modal-footer {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
margin-top: 1rem;
}
.primary-button,
.cancel-button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
.primary-button {
background: var(--primary-color, #2196f3);
color: white;
}
.primary-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.cancel-button {
background: var(--cancel-bg, #e0e0e0);
}
.cancel-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>

99
src/routes/repos/[npub]/[repo]/components/dialogs/CommitDialog.svelte

@ -0,0 +1,99 @@
<script lang="ts">
import Modal from './Modal.svelte';
import type { RepoState } from '../../stores/repo-state.js';
interface Props {
open: boolean;
state: RepoState;
needsClone: boolean;
cloneTooltip: string;
onCommit: () => void;
onClose: () => void;
}
let { open, state, needsClone, cloneTooltip, onCommit, onClose }: Props = $props();
</script>
<Modal {open} title="Commit Changes" ariaLabel="Commit changes" {onClose}>
{#if state.git.branches.length > 0}
<label>
Branch:
<select bind:value={state.git.currentBranch} disabled={state.saving}>
{#each state.git.branches as branch}
{@const branchName = typeof branch === 'string' ? branch : branch.name}
<option value={branchName}>{branchName}{#if branchName === state.git.defaultBranch} (default){/if}</option>
{/each}
</select>
</label>
{:else if state.git.currentBranch}
<label>
Branch:
<input type="text" value={state.git.currentBranch} disabled />
</label>
{/if}
<label>
Commit Message:
<textarea
bind:value={state.forms.commit.message}
placeholder="Describe your changes..."
rows="4"
></textarea>
</label>
<div class="modal-actions">
<button onclick={onClose} class="cancel-button">Cancel</button>
<button
onclick={onCommit}
disabled={!state.forms.commit.message.trim() || state.saving || needsClone || !state.git.currentBranch}
class="save-button"
title={needsClone ? cloneTooltip : (!state.git.currentBranch ? 'Please select a branch' : '')}
>
{state.saving ? 'Saving...' : 'Commit & Save'}
</button>
</div>
</Modal>
<style>
label {
display: block;
margin-bottom: 1rem;
}
label input,
label textarea,
label select {
width: 100%;
padding: 0.5rem;
margin-top: 0.25rem;
border: 1px solid var(--border-color, #e0e0e0);
border-radius: 4px;
}
.modal-actions {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
margin-top: 1rem;
}
.cancel-button,
.save-button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
.cancel-button {
background: var(--cancel-bg, #e0e0e0);
}
.save-button {
background: var(--primary-color, #2196f3);
color: white;
}
.save-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>

96
src/routes/repos/[npub]/[repo]/components/dialogs/CreateBranchDialog.svelte

@ -0,0 +1,96 @@
<script lang="ts">
import Modal from './Modal.svelte';
import type { RepoState } from '../../stores/repo-state.js';
interface Props {
open: boolean;
state: RepoState;
needsClone: boolean;
cloneTooltip: string;
onCreate: () => void;
onClose: () => void;
}
let { open, state, needsClone, cloneTooltip, onCreate, onClose }: Props = $props();
</script>
<Modal {open} title="Create New Branch" ariaLabel="Create new branch" {onClose}>
{#snippet children()}
<label>
Branch Name:
<input type="text" bind:value={state.forms.branch.name} placeholder="feature/new-feature" />
</label>
<label>
From Branch:
<select bind:value={state.forms.branch.from}>
{#if state.git.branches.length === 0}
<option value={null}>No branches - will create initial branch</option>
{:else}
{#each state.git.branches as branch}
{@const branchName = typeof branch === 'string' ? branch : (branch as { name: string }).name}
{@const isDefaultBranch = branchName === state.forms.branch.defaultName}
{#if !isDefaultBranch}
<option value={branchName}>{branchName}</option>
{/if}
{/each}
{/if}
</select>
</label>
<div class="modal-actions">
<button onclick={onClose} class="cancel-button">Cancel</button>
<button
onclick={onCreate}
disabled={!state.forms.branch.name.trim() || state.saving || needsClone}
class="save-button"
title={needsClone ? cloneTooltip : ''}
>
{state.saving ? 'Creating...' : 'Create Branch'}
</button>
</div>
{/snippet}
</Modal>
<style>
label {
display: block;
margin-bottom: 1rem;
}
label input,
label select {
width: 100%;
padding: 0.5rem;
margin-top: 0.25rem;
border: 1px solid var(--border-color, #e0e0e0);
border-radius: 4px;
}
.modal-actions {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
margin-top: 1rem;
}
.cancel-button,
.save-button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
.cancel-button {
background: var(--cancel-bg, #e0e0e0);
}
.save-button {
background: var(--primary-color, #2196f3);
color: white;
}
.save-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>

101
src/routes/repos/[npub]/[repo]/components/dialogs/CreateFileDialog.svelte

@ -0,0 +1,101 @@
<script lang="ts">
import Modal from './Modal.svelte';
import type { RepoState } from '../../stores/repo-state.js';
interface Props {
open: boolean;
state: RepoState;
needsClone: boolean;
cloneTooltip: string;
onCreate: () => void;
onClose: () => void;
}
let { open, state, needsClone, cloneTooltip, onCreate, onClose }: Props = $props();
</script>
<Modal {open} title="Create New File" ariaLabel="Create new file" {onClose}>
{#snippet children()}
{#if state.git.branches.length > 0}
<label>
Branch:
<select bind:value={state.git.currentBranch} disabled={state.saving}>
{#each state.git.branches as branch}
{@const branchName = typeof branch === 'string' ? branch : branch.name}
<option value={branchName}>{branchName}{#if branchName === state.git.defaultBranch} (default){/if}</option>
{/each}
</select>
</label>
{:else if state.git.currentBranch}
<label>
Branch:
<input type="text" value={state.git.currentBranch} disabled />
</label>
{/if}
<label>
File Name:
<input type="text" bind:value={state.forms.file.fileName} placeholder="filename.md" />
</label>
<label>
Content:
<textarea bind:value={state.forms.file.content} rows="10" placeholder="File content..."></textarea>
</label>
<div class="modal-actions">
<button onclick={onClose} class="cancel-button">Cancel</button>
<button
onclick={onCreate}
disabled={!state.forms.file.fileName.trim() || state.saving || needsClone || !state.git.currentBranch}
class="save-button"
title={needsClone ? cloneTooltip : (!state.git.currentBranch ? 'Please select a branch' : '')}
>
{state.saving ? 'Creating...' : 'Create'}
</button>
</div>
{/snippet}
</Modal>
<style>
label {
display: block;
margin-bottom: 1rem;
}
label input,
label textarea,
label select {
width: 100%;
padding: 0.5rem;
margin-top: 0.25rem;
border: 1px solid var(--border-color, #e0e0e0);
border-radius: 4px;
}
.modal-actions {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
margin-top: 1rem;
}
.cancel-button,
.save-button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
.cancel-button {
background: var(--cancel-bg, #e0e0e0);
}
.save-button {
background: var(--primary-color, #2196f3);
color: white;
}
.save-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>

79
src/routes/repos/[npub]/[repo]/components/dialogs/CreateIssueDialog.svelte

@ -0,0 +1,79 @@
<script lang="ts">
import Modal from './Modal.svelte';
import type { RepoState } from '../../stores/repo-state.js';
interface Props {
open: boolean;
state: RepoState;
onCreate: () => void;
onClose: () => void;
}
let { open, state, onCreate, onClose }: Props = $props();
</script>
<Modal {open} title="Create New Issue" ariaLabel="Create new issue" {onClose}>
<label>
Subject:
<input type="text" bind:value={state.forms.issue.subject} placeholder="Issue title..." />
</label>
<label>
Description:
<textarea bind:value={state.forms.issue.content} rows="10" placeholder="Describe the issue..."></textarea>
</label>
<div class="modal-actions">
<button onclick={onClose} class="cancel-button">Cancel</button>
<button
onclick={onCreate}
disabled={!state.forms.issue.subject.trim() || !state.forms.issue.content.trim() || state.saving}
class="save-button"
>
{state.saving ? 'Creating...' : 'Create Issue'}
</button>
</div>
</Modal>
<style>
label {
display: block;
margin-bottom: 1rem;
}
label input,
label textarea {
width: 100%;
padding: 0.5rem;
margin-top: 0.25rem;
border: 1px solid var(--border-color, #e0e0e0);
border-radius: 4px;
}
.modal-actions {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
margin-top: 1rem;
}
.cancel-button,
.save-button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
.cancel-button {
background: var(--cancel-bg, #e0e0e0);
}
.save-button {
background: var(--primary-color, #2196f3);
color: white;
}
.save-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>

87
src/routes/repos/[npub]/[repo]/components/dialogs/CreatePRDialog.svelte

@ -0,0 +1,87 @@
<script lang="ts">
import Modal from './Modal.svelte';
import type { RepoState } from '../../stores/repo-state.js';
interface Props {
open: boolean;
state: RepoState;
onCreate: () => void;
onClose: () => void;
}
let { open, state, onCreate, onClose }: Props = $props();
</script>
<Modal {open} title="Create New Pull Request" ariaLabel="Create new pull request" {onClose}>
<label>
Subject:
<input type="text" bind:value={state.forms.pr.subject} placeholder="PR title..." />
</label>
<label>
Description:
<textarea bind:value={state.forms.pr.content} rows="8" placeholder="Describe your changes..."></textarea>
</label>
<label>
Commit ID:
<input type="text" bind:value={state.forms.pr.commitId} placeholder="Commit hash..." />
</label>
<label>
Branch Name (optional):
<input type="text" bind:value={state.forms.pr.branchName} placeholder="feature/new-feature" />
</label>
<div class="modal-actions">
<button onclick={onClose} class="cancel-button">Cancel</button>
<button
onclick={onCreate}
disabled={!state.forms.pr.subject.trim() || !state.forms.pr.content.trim() || !state.forms.pr.commitId.trim() || state.saving}
class="save-button"
>
{state.saving ? 'Creating...' : 'Create PR'}
</button>
</div>
</Modal>
<style>
label {
display: block;
margin-bottom: 1rem;
}
label input,
label textarea {
width: 100%;
padding: 0.5rem;
margin-top: 0.25rem;
border: 1px solid var(--border-color, #e0e0e0);
border-radius: 4px;
}
.modal-actions {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
margin-top: 1rem;
}
.cancel-button,
.save-button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
.cancel-button {
background: var(--cancel-bg, #e0e0e0);
}
.save-button {
background: var(--primary-color, #2196f3);
color: white;
}
.save-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>

86
src/routes/repos/[npub]/[repo]/components/dialogs/CreatePatchDialog.svelte

@ -0,0 +1,86 @@
<script lang="ts">
import Modal from './Modal.svelte';
import type { RepoState } from '../../stores/repo-state.js';
interface Props {
open: boolean;
state: RepoState;
onCreate: () => void;
onClose: () => void;
}
let { open, state, onCreate, onClose }: Props = $props();
</script>
<Modal {open} title="Create New Patch" ariaLabel="Create new patch" {onClose}>
<p class="help-text">Enter your patch content in git format-patch format. Patches should be under 60KB.</p>
<label>
Subject (optional):
<input type="text" bind:value={state.forms.patch.subject} placeholder="Patch title..." />
</label>
<label>
Patch Content:
<textarea bind:value={state.forms.patch.content} rows="15" placeholder="Paste your git format-patch output here..."></textarea>
</label>
<div class="modal-actions">
<button onclick={onClose} class="cancel-button">Cancel</button>
<button
onclick={onCreate}
disabled={!state.forms.patch.content.trim() || state.creating.patch}
class="save-button"
>
{state.creating.patch ? 'Creating...' : 'Create Patch'}
</button>
</div>
</Modal>
<style>
.help-text {
margin-bottom: 1rem;
color: var(--text-secondary, #666);
font-size: 0.9rem;
}
label {
display: block;
margin-bottom: 1rem;
}
label input,
label textarea {
width: 100%;
padding: 0.5rem;
margin-top: 0.25rem;
border: 1px solid var(--border-color, #e0e0e0);
border-radius: 4px;
}
.modal-actions {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
margin-top: 1rem;
}
.cancel-button,
.save-button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
.cancel-button {
background: var(--cancel-bg, #e0e0e0);
}
.save-button {
background: var(--primary-color, #2196f3);
color: white;
}
.save-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>

96
src/routes/repos/[npub]/[repo]/components/dialogs/CreateReleaseDialog.svelte

@ -0,0 +1,96 @@
<script lang="ts">
import Modal from './Modal.svelte';
import type { RepoState } from '../../stores/repo-state.js';
interface Props {
open: boolean;
state: RepoState;
onCreate: () => void;
onClose: () => void;
}
let { open, state, onCreate, onClose }: Props = $props();
</script>
<Modal {open} title="Create New Release" ariaLabel="Create new release" {onClose}>
<label>
Tag Name:
<input type="text" bind:value={state.forms.release.tagName} placeholder="v1.0.0" />
</label>
<label>
Tag Hash (commit hash):
<input type="text" bind:value={state.forms.release.tagHash} placeholder="abc1234..." />
</label>
<label>
Release Notes:
<textarea bind:value={state.forms.release.notes} rows="10" placeholder="Release notes in markdown..."></textarea>
</label>
<label>
<input type="checkbox" bind:checked={state.forms.release.isDraft} />
Draft Release
</label>
<label>
<input type="checkbox" bind:checked={state.forms.release.isPrerelease} />
Pre-release
</label>
<div class="modal-actions">
<button onclick={onClose} class="cancel-button">Cancel</button>
<button
onclick={onCreate}
disabled={!state.forms.release.tagName.trim() || !state.forms.release.tagHash.trim() || state.creating.release}
class="save-button"
>
{state.creating.release ? 'Creating...' : 'Create Release'}
</button>
</div>
</Modal>
<style>
label {
display: block;
margin-bottom: 1rem;
}
label input,
label textarea {
width: 100%;
padding: 0.5rem;
margin-top: 0.25rem;
border: 1px solid var(--border-color, #e0e0e0);
border-radius: 4px;
}
label input[type="checkbox"] {
width: auto;
margin-right: 0.5rem;
}
.modal-actions {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
margin-top: 1rem;
}
.cancel-button,
.save-button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
.cancel-button {
background: var(--cancel-bg, #e0e0e0);
}
.save-button {
background: var(--primary-color, #2196f3);
color: white;
}
.save-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>

86
src/routes/repos/[npub]/[repo]/components/dialogs/CreateTagDialog.svelte

@ -0,0 +1,86 @@
<script lang="ts">
import Modal from './Modal.svelte';
import type { RepoState } from '../../stores/repo-state.js';
interface Props {
open: boolean;
state: RepoState;
needsClone: boolean;
cloneTooltip: string;
onCreate: () => void;
onClose: () => void;
}
let { open, state, needsClone, cloneTooltip, onCreate, onClose }: Props = $props();
</script>
<Modal {open} title="Create New Tag" ariaLabel="Create new tag" {onClose}>
<label>
Tag Name:
<input type="text" bind:value={state.forms.tag.name} placeholder="v1.0.0" />
</label>
<label>
Reference (commit/branch):
<input type="text" bind:value={state.forms.tag.ref} placeholder="HEAD" />
</label>
<label>
Message (optional, for annotated tag):
<textarea bind:value={state.forms.tag.message} rows="3" placeholder="Tag message..."></textarea>
</label>
<div class="modal-actions">
<button onclick={onClose} class="cancel-button">Cancel</button>
<button
onclick={onCreate}
disabled={!state.forms.tag.name.trim() || state.saving || needsClone}
class="save-button"
title={needsClone ? cloneTooltip : ''}
>
{state.saving ? 'Creating...' : 'Create Tag'}
</button>
</div>
</Modal>
<style>
label {
display: block;
margin-bottom: 1rem;
}
label input,
label textarea {
width: 100%;
padding: 0.5rem;
margin-top: 0.25rem;
border: 1px solid var(--border-color, #e0e0e0);
border-radius: 4px;
}
.modal-actions {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
margin-top: 1rem;
}
.cancel-button,
.save-button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
.cancel-button {
background: var(--cancel-bg, #e0e0e0);
}
.save-button {
background: var(--primary-color, #2196f3);
color: white;
}
.save-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>

79
src/routes/repos/[npub]/[repo]/components/dialogs/CreateThreadDialog.svelte

@ -0,0 +1,79 @@
<script lang="ts">
import Modal from './Modal.svelte';
import type { RepoState } from '../../stores/repo-state.js';
interface Props {
open: boolean;
state: RepoState;
onCreate: () => void;
onClose: () => void;
}
let { open, state, onCreate, onClose }: Props = $props();
</script>
<Modal {open} title="Create New Discussion Thread" ariaLabel="Create new discussion thread" {onClose}>
<label>
Title:
<input type="text" bind:value={state.forms.discussion.threadTitle} placeholder="Thread title..." />
</label>
<label>
Content:
<textarea bind:value={state.forms.discussion.threadContent} rows="10" placeholder="Start the discussion..."></textarea>
</label>
<div class="modal-actions">
<button onclick={onClose} class="cancel-button">Cancel</button>
<button
onclick={onCreate}
disabled={!state.forms.discussion.threadTitle.trim() || state.creating.thread}
class="save-button"
>
{state.creating.thread ? 'Creating...' : 'Create Thread'}
</button>
</div>
</Modal>
<style>
label {
display: block;
margin-bottom: 1rem;
}
label input,
label textarea {
width: 100%;
padding: 0.5rem;
margin-top: 0.25rem;
border: 1px solid var(--border-color, #e0e0e0);
border-radius: 4px;
}
.modal-actions {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
margin-top: 1rem;
}
.cancel-button,
.save-button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
.cancel-button {
background: var(--cancel-bg, #e0e0e0);
}
.save-button {
background: var(--primary-color, #2196f3);
color: white;
}
.save-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>

67
src/routes/repos/[npub]/[repo]/components/dialogs/Modal.svelte

@ -0,0 +1,67 @@
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
open: boolean;
title: string;
ariaLabel?: string;
onClose: () => void;
children?: Snippet;
}
let { open, title, ariaLabel, onClose, children }: Props = $props();
</script>
{#if open}
<div
class="modal-overlay"
role="dialog"
aria-modal="true"
aria-label={ariaLabel || title}
onclick={onClose}
onkeydown={(e) => e.key === 'Escape' && onClose()}
tabindex="-1"
>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<div
class="modal"
role="dialog"
onclick={(e) => e.stopPropagation()}
onkeydown={(e) => e.stopPropagation()}
tabindex="-1"
>
<h3>{title}</h3>
{#if children}{@render children()}{/if}
</div>
</div>
{/if}
<style>
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal {
background: var(--modal-bg, #fff);
border-radius: 8px;
padding: 1.5rem;
max-width: 500px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
}
.modal h3 {
margin: 0 0 1rem 0;
}
</style>

84
src/routes/repos/[npub]/[repo]/components/dialogs/PatchCommentDialog.svelte

@ -0,0 +1,84 @@
<script lang="ts">
import Modal from './Modal.svelte';
import type { RepoState } from '../../stores/repo-state.js';
interface Props {
open: boolean;
state: RepoState;
onCreate: () => void;
onClose: () => void;
}
let { open, state, onCreate, onClose }: Props = $props();
function handleClose() {
state.forms.patchComment.replyingTo = null;
onClose();
}
</script>
<Modal
{open}
title={state.forms.patchComment.replyingTo ? 'Reply to Comment' : 'Add Comment'}
ariaLabel={state.forms.patchComment.replyingTo ? 'Reply to comment' : 'Add comment'}
onClose={handleClose}
>
<label>
Comment:
<textarea bind:value={state.forms.patchComment.content} rows="6" placeholder="Write your comment..."></textarea>
</label>
<div class="modal-actions">
<button onclick={handleClose} class="cancel-button">Cancel</button>
<button
onclick={onCreate}
disabled={state.creating.patchComment || !state.forms.patchComment.content.trim()}
class="save-button"
>
{state.creating.patchComment ? 'Creating...' : (state.forms.patchComment.replyingTo ? 'Reply' : 'Add Comment')}
</button>
</div>
</Modal>
<style>
label {
display: block;
margin-bottom: 1rem;
}
label textarea {
width: 100%;
padding: 0.5rem;
margin-top: 0.25rem;
border: 1px solid var(--border-color, #e0e0e0);
border-radius: 4px;
}
.modal-actions {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
margin-top: 1rem;
}
.cancel-button,
.save-button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
.cancel-button {
background: var(--cancel-bg, #e0e0e0);
}
.save-button {
background: var(--primary-color, #2196f3);
color: white;
}
.save-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>

94
src/routes/repos/[npub]/[repo]/components/dialogs/PatchHighlightDialog.svelte

@ -0,0 +1,94 @@
<script lang="ts">
import Modal from './Modal.svelte';
import type { RepoState } from '../../stores/repo-state.js';
interface Props {
open: boolean;
state: RepoState;
onCreate: () => void;
onClose: () => void;
}
let { open, state, onCreate, onClose }: Props = $props();
</script>
<Modal {open} title="Create Highlight" ariaLabel="Create highlight" {onClose}>
<div class="selected-code">
<pre><code>{state.forms.patchHighlight.text}</code></pre>
</div>
<label>
Comment (optional):
<textarea bind:value={state.forms.patchHighlight.comment} rows="4" placeholder="Add a comment about this code..."></textarea>
</label>
<div class="modal-actions">
<button onclick={onClose} class="cancel-button">Cancel</button>
<button
onclick={onCreate}
disabled={state.creating.patchHighlight}
class="save-button"
>
{state.creating.patchHighlight ? 'Creating...' : 'Create Highlight'}
</button>
</div>
</Modal>
<style>
.selected-code {
margin-bottom: 1rem;
padding: 0.75rem;
background: var(--bg-secondary, #f5f5f5);
border-radius: 4px;
overflow-x: auto;
}
.selected-code pre {
margin: 0;
}
.selected-code code {
font-family: monospace;
font-size: 0.9rem;
}
label {
display: block;
margin-bottom: 1rem;
}
label textarea {
width: 100%;
padding: 0.5rem;
margin-top: 0.25rem;
border: 1px solid var(--border-color, #e0e0e0);
border-radius: 4px;
}
.modal-actions {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
margin-top: 1rem;
}
.cancel-button,
.save-button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
.cancel-button {
background: var(--cancel-bg, #e0e0e0);
}
.save-button {
background: var(--primary-color, #2196f3);
color: white;
}
.save-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>

86
src/routes/repos/[npub]/[repo]/components/dialogs/ReplyDialog.svelte

@ -0,0 +1,86 @@
<script lang="ts">
import Modal from './Modal.svelte';
import type { RepoState } from '../../stores/repo-state.js';
interface Props {
open: boolean;
state: RepoState;
onCreate: () => void;
onClose: () => void;
}
let { open, state, onCreate, onClose }: Props = $props();
function handleClose() {
state.discussion.replyingToThread = null;
state.discussion.replyingToComment = null;
state.forms.discussion.replyContent = '';
onClose();
}
</script>
<Modal
{open}
title={state.discussion.replyingToComment ? 'Reply to Comment' : state.discussion.replyingToThread ? 'Reply to Thread' : 'Reply'}
ariaLabel={state.discussion.replyingToComment ? 'Reply to comment' : 'Reply to thread'}
onClose={handleClose}
>
<label>
Your Reply:
<textarea bind:value={state.forms.discussion.replyContent} rows="8" placeholder="Write your reply..."></textarea>
</label>
<div class="modal-actions">
<button onclick={handleClose} class="cancel-button">Cancel</button>
<button
onclick={onCreate}
disabled={!state.forms.discussion.replyContent.trim() || state.creating.reply}
class="save-button"
>
{state.creating.reply ? 'Posting...' : 'Post Reply'}
</button>
</div>
</Modal>
<style>
label {
display: block;
margin-bottom: 1rem;
}
label textarea {
width: 100%;
padding: 0.5rem;
margin-top: 0.25rem;
border: 1px solid var(--border-color, #e0e0e0);
border-radius: 4px;
}
.modal-actions {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
margin-top: 1rem;
}
.cancel-button,
.save-button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
.cancel-button {
background: var(--cancel-bg, #e0e0e0);
}
.save-button {
background: var(--primary-color, #2196f3);
color: white;
}
.save-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>

117
src/routes/repos/[npub]/[repo]/components/dialogs/VerificationDialog.svelte

@ -0,0 +1,117 @@
<script lang="ts">
import Modal from './Modal.svelte';
import type { RepoState } from '../../stores/repo-state.js';
interface Props {
open: boolean;
state: RepoState;
onCopy: () => void;
onDownload: () => void;
onClose: () => void;
}
let { open, state, onCopy, onDownload, onClose }: Props = $props();
</script>
<Modal {open} title="Repository Verification File" ariaLabel="Repository verification file" {onClose}>
<div class="modal-body">
<p class="verification-instructions">
The announcement event should be saved to <code>nostr/repo-events.jsonl</code> in your repository.
You can download the announcement event JSON below for reference.
</p>
<div class="verification-file-content">
<div class="file-header">
<span class="filename">announcement-event.json</span>
<div class="file-actions">
<button onclick={onCopy} class="copy-button">Copy</button>
<button onclick={onDownload} class="download-button">Download</button>
</div>
</div>
<pre class="file-content"><code>{state.verification.fileContent}</code></pre>
</div>
</div>
<div class="modal-actions">
<button onclick={onClose} class="cancel-button">Close</button>
</div>
</Modal>
<style>
.modal-body {
margin-bottom: 1rem;
}
.verification-instructions {
margin-bottom: 1rem;
color: var(--text-secondary, #666);
}
.verification-instructions code {
background: var(--bg-secondary, #f5f5f5);
padding: 0.2rem 0.4rem;
border-radius: 3px;
font-family: monospace;
}
.verification-file-content {
border: 1px solid var(--border-color, #e0e0e0);
border-radius: 4px;
overflow: hidden;
}
.file-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem;
background: var(--bg-secondary, #f5f5f5);
border-bottom: 1px solid var(--border-color, #e0e0e0);
}
.filename {
font-weight: bold;
font-family: monospace;
}
.file-actions {
display: flex;
gap: 0.5rem;
}
.copy-button,
.download-button {
padding: 0.25rem 0.75rem;
border: 1px solid var(--border-color, #e0e0e0);
border-radius: 4px;
background: white;
cursor: pointer;
}
.file-content {
margin: 0;
padding: 1rem;
overflow-x: auto;
max-height: 400px;
overflow-y: auto;
}
.file-content code {
font-family: monospace;
font-size: 0.85rem;
white-space: pre;
}
.modal-actions {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
margin-top: 1rem;
}
.cancel-button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
background: var(--cancel-bg, #e0e0e0);
}
</style>
Loading…
Cancel
Save