From d372c7db37d43e447fd075d15ff8f61d38f1042f Mon Sep 17 00:00:00 2001 From: Silberengel Date: Tue, 17 Feb 2026 14:53:48 +0100 Subject: [PATCH] make the repo page more complete --- src/app.css | 29 ++ src/routes/signup/+page.svelte | 472 ++++++++++++++++++++++++++++++++- 2 files changed, 493 insertions(+), 8 deletions(-) diff --git a/src/app.css b/src/app.css index f4c3970..6fd92df 100644 --- a/src/app.css +++ b/src/app.css @@ -329,6 +329,35 @@ label small, .label small { font-size: 0.875rem; } +.checkbox-label { + display: flex; + flex-direction: row; + align-items: flex-start; + gap: 0.5rem; + cursor: pointer; + user-select: none; +} + +.checkbox-label input[type="checkbox"] { + width: 1.1rem; + height: 1.1rem; + accent-color: var(--accent); + cursor: pointer; + flex-shrink: 0; + margin-top: 0.125rem; +} + +.checkbox-label > span { + font-weight: 500; + color: var(--text-primary); + flex: 1; +} + +.checkbox-label small { + display: block; + margin-top: 0.25rem; +} + input, textarea, select { padding: 0.75rem; border: 1px solid var(--input-border); diff --git a/src/routes/signup/+page.svelte b/src/routes/signup/+page.svelte index 4c92704..c5fcc0b 100644 --- a/src/routes/signup/+page.svelte +++ b/src/routes/signup/+page.svelte @@ -18,6 +18,19 @@ let repoName = $state(''); let description = $state(''); let cloneUrls = $state(['']); + let webUrls = $state(['']); + let maintainers = $state(['']); + let tags = $state(['']); + let documentation = $state(['']); + let alt = $state(''); + let imageUrl = $state(''); + let bannerUrl = $state(''); + let earliestCommit = $state(''); + let isPrivate = $state(false); + let isFork = $state(false); + let forkRepoAddress = $state(''); // a tag: 30617:owner:repo + let forkOwnerPubkey = $state(''); // p tag: original owner pubkey + let addClientTag = $state(true); // Add ["client", "gitrepublic-web"] tag let existingRepoRef = $state(''); // hex, nevent, or naddr let loadingExisting = $state(false); @@ -43,6 +56,62 @@ cloneUrls = newUrls; } + function addWebUrl() { + webUrls = [...webUrls, '']; + } + + function removeWebUrl(index: number) { + webUrls = webUrls.filter((_, i) => i !== index); + } + + function updateWebUrl(index: number, value: string) { + const newUrls = [...webUrls]; + newUrls[index] = value; + webUrls = newUrls; + } + + function addMaintainer() { + maintainers = [...maintainers, '']; + } + + function removeMaintainer(index: number) { + maintainers = maintainers.filter((_, i) => i !== index); + } + + function updateMaintainer(index: number, value: string) { + const newMaintainers = [...maintainers]; + newMaintainers[index] = value; + maintainers = newMaintainers; + } + + function addTag() { + tags = [...tags, '']; + } + + function removeTag(index: number) { + tags = tags.filter((_, i) => i !== index); + } + + function updateTag(index: number, value: string) { + const newTags = [...tags]; + newTags[index] = value; + tags = newTags; + } + + function addDocumentation() { + documentation = [...documentation, '']; + } + + function removeDocumentation(index: number) { + documentation = documentation.filter((_, i) => i !== index); + } + + function updateDocumentation(index: number, value: string) { + const newDocs = [...documentation]; + newDocs[index] = value; + documentation = newDocs; + } + async function loadExistingRepo() { if (!existingRepoRef.trim()) return; @@ -96,24 +165,99 @@ const dTag = event.tags.find(t => t[0] === 'd')?.[1] || ''; const nameTag = event.tags.find(t => t[0] === 'name')?.[1] || ''; const descTag = event.tags.find(t => t[0] === 'description')?.[1] || ''; + const imageTag = event.tags.find(t => t[0] === 'image')?.[1] || ''; + const bannerTag = event.tags.find(t => t[0] === 'banner')?.[1] || ''; + const privateTag = event.tags.find(t => (t[0] === 'private' && t[1] === 'true') || (t[0] === 't' && t[1] === 'private')); repoName = nameTag || dTag; description = descTag; + imageUrl = imageTag; + bannerUrl = bannerTag; + isPrivate = !!privateTag; - // Extract clone URLs + // Extract clone URLs - handle both formats: separate tags and multiple values in one tag const urls: string[] = []; for (const tag of event.tags) { if (tag[0] === 'clone') { for (let i = 1; i < tag.length; i++) { const url = tag[i]; - if (url && typeof url === 'string') { - urls.push(url); + if (url && typeof url === 'string' && url.trim()) { + urls.push(url.trim()); } } } } cloneUrls = urls.length > 0 ? urls : ['']; + // Extract web URLs - handle both formats + const webUrlsList: string[] = []; + for (const tag of event.tags) { + if (tag[0] === 'web') { + for (let i = 1; i < tag.length; i++) { + const url = tag[i]; + if (url && typeof url === 'string' && url.trim()) { + webUrlsList.push(url.trim()); + } + } + } + } + webUrls = webUrlsList.length > 0 ? webUrlsList : ['']; + + // Extract maintainers - handle both formats + const maintainersList: string[] = []; + for (const tag of event.tags) { + if (tag[0] === 'maintainers') { + for (let i = 1; i < tag.length; i++) { + const maintainer = tag[i]; + if (maintainer && typeof maintainer === 'string' && maintainer.trim()) { + maintainersList.push(maintainer.trim()); + } + } + } + } + maintainers = maintainersList.length > 0 ? maintainersList : ['']; + + // Extract tags/labels + const tagsList: string[] = []; + for (const tag of event.tags) { + if (tag[0] === 't' && tag[1] && tag[1] !== 'private' && tag[1] !== 'fork') { + tagsList.push(tag[1]); + } + } + tags = tagsList.length > 0 ? tagsList : ['']; + + // Extract documentation - handle both formats + const docsList: string[] = []; + for (const tag of event.tags) { + if (tag[0] === 'documentation') { + for (let i = 1; i < tag.length; i++) { + const doc = tag[i]; + if (doc && typeof doc === 'string' && doc.trim()) { + docsList.push(doc.trim()); + } + } + } + } + documentation = docsList.length > 0 ? docsList : ['']; + + // Extract alt tag + const altTag = event.tags.find(t => t[0] === 'alt'); + alt = altTag?.[1] || ''; + + // Extract fork information + const aTag = event.tags.find(t => t[0] === 'a' && t[1]?.startsWith('30617:')); + forkRepoAddress = aTag?.[1] || ''; + const pTag = event.tags.find(t => t[0] === 'p' && t[1] && t[1] !== event.pubkey); + forkOwnerPubkey = pTag?.[1] || ''; + isFork = !!(forkRepoAddress || forkOwnerPubkey || event.tags.some(t => t[0] === 't' && t[1] === 'fork')); + + // Extract earliest unique commit + const rTag = event.tags.find(t => t[0] === 'r' && t[2] === 'euc'); + earliestCommit = rTag?.[1] || ''; + + // Check if client tag exists + addClientTag = !event.tags.some(t => t[0] === 'client' && t[1] === 'gitrepublic-web'); + } catch (e) { error = `Failed to load repository: ${String(e)}`; } finally { @@ -159,22 +303,66 @@ ...cloneUrls.filter(url => url.trim() && !url.includes(gitDomain)) ]; - // Build tags - const tags: string[][] = [ + // Build web URLs + const allWebUrls = webUrls.filter(url => url.trim()); + + // Build maintainers list + const allMaintainers = maintainers.filter(m => m.trim()); + + // Build documentation list + const allDocumentation = documentation.filter(d => d.trim()); + + // Build tags/labels (excluding 'private' and 'fork' which are handled separately) + const allTags = tags.filter(t => t.trim() && t !== 'private' && t !== 'fork'); + + // Build event tags - use separate tag for each value (correct format) + const eventTags: string[][] = [ ['d', dTag], ['name', repoName], ...(description ? [['description', description]] : []), - ...allCloneUrls.map(url => ['clone', url]), - ['relays', ...DEFAULT_NOSTR_RELAYS] + ...allCloneUrls.map(url => ['clone', url]), // Separate tag per URL + ...allWebUrls.map(url => ['web', url]), // Separate tag per URL + ...allMaintainers.map(m => ['maintainers', m]), // Separate tag per maintainer + ...allDocumentation.map(d => ['documentation', d]), // Separate tag per documentation + ...allTags.map(t => ['t', t]), + ...(imageUrl.trim() ? [['image', imageUrl.trim()]] : []), + ...(bannerUrl.trim() ? [['banner', bannerUrl.trim()]] : []), + ...(alt.trim() ? [['alt', alt.trim()]] : []), + ...(earliestCommit.trim() ? [['r', earliestCommit.trim(), 'euc']] : []), + ...DEFAULT_NOSTR_RELAYS.map(relay => ['relays', relay]) // Separate tag per relay (correct format) ]; + // Add fork tags if this is a fork + if (isFork) { + if (forkRepoAddress.trim()) { + eventTags.push(['a', forkRepoAddress.trim()]); + } + if (forkOwnerPubkey.trim()) { + eventTags.push(['p', forkOwnerPubkey.trim()]); + } + // Add 'fork' tag if not already in tags + if (!allTags.includes('fork')) { + eventTags.push(['t', 'fork']); + } + } + + // Add private tag if enabled + if (isPrivate) { + eventTags.push(['private', 'true']); + } + + // Add client tag if enabled + if (addClientTag) { + eventTags.push(['client', 'gitrepublic-web']); + } + // Build event const eventTemplate: Omit = { kind: KIND.REPO_ANNOUNCEMENT, pubkey, created_at: Math.floor(Date.now() / 1000), content: '', // Empty per NIP-34 - tags + tags: eventTags }; // Sign with NIP-07 @@ -330,6 +518,274 @@ +
+
+ Web URLs (optional) + Webpage URLs for browsing the repository (e.g., GitHub/GitLab web interface) +
+ {#each webUrls as url, index} +
+ updateWebUrl(index, e.currentTarget.value)} + placeholder="https://github.com/user/repo" + disabled={loading} + /> + {#if webUrls.length > 1} + + {/if} +
+ {/each} + +
+ +
+
+ Maintainers (optional) + Other maintainer pubkeys (npub or hex format). Example: npub1abc... or hex pubkey +
+ {#each maintainers as maintainer, index} +
+ updateMaintainer(index, e.currentTarget.value)} + placeholder="npub1abc... or hex pubkey" + disabled={loading} + /> + {#if maintainers.length > 1} + + {/if} +
+ {/each} + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
+ Tags/Labels (optional) + Hashtags or labels for the repository. Examples: "javascript", "web-app", "personal-fork" (indicates author isn't a maintainer) +
+ {#each tags as tag, index} +
+ updateTag(index, e.currentTarget.value)} + placeholder="javascript" + disabled={loading} + /> + {#if tags.length > 1} + + {/if} +
+ {/each} + +
+ +
+ + +
+ +
+
+ Documentation (optional) + Documentation event addresses (naddr format). Example: 30818:pubkey:nkbip-01 +
+ {#each documentation as doc, index} +
+ updateDocumentation(index, e.currentTarget.value)} + placeholder="30818:pubkey:doc-name" + disabled={loading} + /> + {#if documentation.length > 1} + + {/if} +
+ {/each} + +
+ +
+ +
+ + {#if isFork} +
+ + +
+ +
+ + +
+ {/if} + +
+ +
+ +
+ +
+