From 3f8399e2038a1e7bd749aff27b9b47b7a7fcb696 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Tue, 17 Feb 2026 16:33:16 +0100 Subject: [PATCH] bug-fixes --- docs/tutorial.md | 2 +- src/app.css | 14 +- src/routes/docs/nip34/+page.svelte | 3 +- src/routes/docs/nip34/spec/+page.server.ts | 20 ++ src/routes/docs/nip34/spec/+page.svelte | 169 +++++++++++++++ src/routes/signup/+page.svelte | 241 +++++++++++++++++++-- 6 files changed, 432 insertions(+), 17 deletions(-) create mode 100644 src/routes/docs/nip34/spec/+page.server.ts create mode 100644 src/routes/docs/nip34/spec/+page.svelte diff --git a/docs/tutorial.md b/docs/tutorial.md index d8b58a0..cffba96 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -592,7 +592,7 @@ GitRepublic implements NIP-34 for repository announcements. Key event types: - **Kind 1621**: Issue - **Kind 1641**: Ownership transfer -See the [NIP-34 specification](https://github.com/nostr-protocol/nips/blob/master/34.md) for full details. +See the [NIP-34 specification](/docs/nip34/spec) for full details. ### NIP-98 HTTP Authentication diff --git a/src/app.css b/src/app.css index 0a00a43..ef03d7d 100644 --- a/src/app.css +++ b/src/app.css @@ -1170,8 +1170,10 @@ pre code { background: var(--bg-secondary); } +label.filter-checkbox, .filter-checkbox { - display: flex; + display: flex !important; + flex-direction: row !important; align-items: center; gap: 0.5rem; cursor: pointer; @@ -1180,11 +1182,21 @@ pre code { font-size: 0.9rem; } +label.filter-checkbox input[type="checkbox"], .filter-checkbox input[type="checkbox"] { cursor: pointer; width: 1.1rem; height: 1.1rem; accent-color: var(--accent); + flex-shrink: 0; + margin: 0; + display: block; +} + +label.filter-checkbox > span, +.filter-checkbox > span { + display: inline; + line-height: 1.5; } .repos-list { diff --git a/src/routes/docs/nip34/+page.svelte b/src/routes/docs/nip34/+page.svelte index 30cbf65..cdd9c66 100644 --- a/src/routes/docs/nip34/+page.svelte +++ b/src/routes/docs/nip34/+page.svelte @@ -42,7 +42,8 @@
-

GitRepublic Documentation

+

GitRepublic Tutorial

+

Complete guide to using GitRepublic

diff --git a/src/routes/docs/nip34/spec/+page.server.ts b/src/routes/docs/nip34/spec/+page.server.ts new file mode 100644 index 0000000..80fc543 --- /dev/null +++ b/src/routes/docs/nip34/spec/+page.server.ts @@ -0,0 +1,20 @@ +/** + * Server-side loader for NIP-34 specification reference + */ + +import { readFile } from 'fs/promises'; +import { join } from 'path'; +import type { PageServerLoad } from './$types'; +import logger from '$lib/services/logger.js'; + +export const load: PageServerLoad = async () => { + try { + // Read NIP-34 specification from docs/34.md + const filePath = join(process.cwd(), 'docs', '34.md'); + const content = await readFile(filePath, 'utf-8'); + return { content }; + } catch (error) { + logger.error({ error }, 'Error loading NIP-34 specification'); + return { content: null, error: 'Failed to load NIP-34 specification' }; + } +}; diff --git a/src/routes/docs/nip34/spec/+page.svelte b/src/routes/docs/nip34/spec/+page.svelte new file mode 100644 index 0000000..3b1a7cd --- /dev/null +++ b/src/routes/docs/nip34/spec/+page.svelte @@ -0,0 +1,169 @@ + + +
+
+

NIP-34 Specification

+

Nostr Improvement Proposal for Git Collaboration

+
+ +
+ {#if loading} +
Loading specification...
+ {:else if error} +
{error}
+ {:else} +
+ {@html content} +
+ {/if} +
+
+ + diff --git a/src/routes/signup/+page.svelte b/src/routes/signup/+page.svelte index 9797c00..bc01fc5 100644 --- a/src/routes/signup/+page.svelte +++ b/src/routes/signup/+page.svelte @@ -20,6 +20,8 @@ let cloneUrls = $state(['']); let webUrls = $state(['']); let maintainers = $state(['']); + let relays = $state(['']); + let blossoms = $state(['']); let tags = $state(['']); let documentation = $state(['']); let alt = $state(''); @@ -96,6 +98,34 @@ maintainers = newMaintainers; } + function addRelay() { + relays = [...relays, '']; + } + + function removeRelay(index: number) { + relays = relays.filter((_, i) => i !== index); + } + + function updateRelay(index: number, value: string) { + const newRelays = [...relays]; + newRelays[index] = value; + relays = newRelays; + } + + function addBlossom() { + blossoms = [...blossoms, '']; + } + + function removeBlossom(index: number) { + blossoms = blossoms.filter((_, i) => i !== index); + } + + function updateBlossom(index: number, value: string) { + const newBlossoms = [...blossoms]; + newBlossoms[index] = value; + blossoms = newBlossoms; + } + function addTag() { tags = [...tags, '']; } @@ -603,6 +633,34 @@ } maintainers = maintainersList.length > 0 ? maintainersList : ['']; + // Extract relays + const relaysList: string[] = []; + for (const tag of event.tags) { + if (tag[0] === 'relays') { + for (let i = 1; i < tag.length; i++) { + const relay = tag[i]; + if (relay && typeof relay === 'string' && relay.trim()) { + relaysList.push(relay.trim()); + } + } + } + } + relays = relaysList.length > 0 ? relaysList : ['']; + + // Extract blossoms + const blossomsList: string[] = []; + for (const tag of event.tags) { + if (tag[0] === 'blossoms') { + for (let i = 1; i < tag.length; i++) { + const blossom = tag[i]; + if (blossom && typeof blossom === 'string' && blossom.trim()) { + blossomsList.push(blossom.trim()); + } + } + } + } + blossoms = blossomsList.length > 0 ? blossomsList : ['']; + // Extract tags/labels const tagsList: string[] = []; for (const tag of event.tags) { @@ -612,15 +670,90 @@ } tags = tagsList.length > 0 ? tagsList : ['']; - // Extract documentation - handle both formats + // Extract documentation - handle relay hints correctly + // Only treat values as multiple entries if they are in the same format + // If a value looks like a relay URL (wss:// or ws://), it's a relay hint for the previous value const docsList: string[] = []; + const isRelayUrl = (value: string): boolean => { + return typeof value === 'string' && (value.startsWith('wss://') || value.startsWith('ws://')); + }; + + const getDocFormat = (value: string): string | null => { + // Check if it's naddr format (starts with naddr1) + if (value.startsWith('naddr1')) return 'naddr'; + // Check if it's kind:pubkey:identifier format + if (/^\d+:[0-9a-f]{64}:[a-zA-Z0-9_-]+$/.test(value)) return 'kind:pubkey:identifier'; + return null; + }; + 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()); + let i = 1; + + while (i < tag.length) { + const value = tag[i]; + if (!value || typeof value !== 'string' || !value.trim()) { + i++; + continue; + } + + const trimmed = value.trim(); + + // Skip relay URLs (they're hints, not entries) + if (isRelayUrl(trimmed)) { + i++; + continue; + } + + // Check if this is a documentation reference + const format = getDocFormat(trimmed); + if (!format) { + i++; + continue; // Skip invalid formats + } + + // Check if next value is a relay URL (hint for this entry) + const nextValue = i + 1 < tag.length ? tag[i + 1] : null; + if (nextValue && typeof nextValue === 'string' && isRelayUrl(nextValue.trim())) { + // Current value has a relay hint - store just the doc reference, skip the relay + docsList.push(trimmed); + i += 2; // Skip both the doc and the relay hint + continue; } + + // Check if we have multiple entries in the same format + // Collect all consecutive entries of the same format + const sameFormatEntries: string[] = [trimmed]; + let j = i + 1; + while (j < tag.length) { + const nextVal = tag[j]; + if (!nextVal || typeof nextVal !== 'string' || !nextVal.trim()) { + j++; + continue; + } + + const nextTrimmed = nextVal.trim(); + + // Stop if we hit a relay URL (it's a hint for the previous entry) + if (isRelayUrl(nextTrimmed)) { + break; + } + + // Check if it's the same format + const nextFormat = getDocFormat(nextTrimmed); + if (nextFormat === format) { + sameFormatEntries.push(nextTrimmed); + j++; + } else { + // Different format - stop collecting + break; + } + } + + // If we have multiple entries in the same format, add them all + // Otherwise, just add the single entry + docsList.push(...sameFormatEntries); + i = j; // Move to the next unprocessed value } } } @@ -761,27 +894,37 @@ // Build maintainers list const allMaintainers = maintainers.filter(m => m.trim()); + // Build relays list - combine user relays with default relays + const allRelays = [ + ...relays.filter(r => r.trim()), + ...DEFAULT_NOSTR_RELAYS.filter(r => !relays.includes(r)) + ]; + + // Build blossoms list + const allBlossoms = blossoms.filter(b => b.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) + // Build event tags - use single tag with multiple values (NIP-34 format) const eventTags: string[][] = [ ['d', dTag], ['name', repoName], ...(description ? [['description', description]] : []), - ...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 + ...(allCloneUrls.length > 0 ? [['clone', ...allCloneUrls]] : []), // Single tag with all clone URLs + ...(allWebUrls.length > 0 ? [['web', ...allWebUrls]] : []), // Single tag with all web URLs + ...(allMaintainers.length > 0 ? [['maintainers', ...allMaintainers]] : []), // Single tag with all maintainers + ...(allRelays.length > 0 ? [['relays', ...allRelays]] : []), // Single tag with all relays + ...(allBlossoms.length > 0 ? [['blossoms', ...allBlossoms]] : []), // Single tag with all blossoms + ...allDocumentation.map(d => ['documentation', d]), // Documentation can have relay hints, so keep separate ...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) + ...(earliestCommit.trim() ? [['r', earliestCommit.trim(), 'euc']] : []) ]; // Add fork tags if this is a fork @@ -1296,6 +1439,76 @@
+
+
+ Relays (optional) + Nostr relays that this repository will monitor for patches and issues. Default relays will be added automatically. +
+ {#each relays as relay, index} +
+ updateRelay(index, e.currentTarget.value)} + placeholder="wss://relay.example.com" + disabled={loading} + /> + {#if relays.length > 1} + + {/if} +
+ {/each} + +
+ +
+
+ Blossoms (optional) + Blossom URLs for this repository. These are preserved but not actively used by GitRepublic. +
+ {#each blossoms as blossom, index} +
+ updateBlossom(index, e.currentTarget.value)} + placeholder="https://example.com" + disabled={loading} + /> + {#if blossoms.length > 1} + + {/if} +
+ {/each} + +
+