diff --git a/src/lib/components/PRDetail.svelte b/src/lib/components/PRDetail.svelte index bc813f0..475a61e 100644 --- a/src/lib/components/PRDetail.svelte +++ b/src/lib/components/PRDetail.svelte @@ -27,8 +27,27 @@ let { pr, npub, repo, repoOwnerPubkey }: Props = $props(); - let highlights = $state>([]); - let comments = $state>([]); + let highlights = $state; + [key: string]: unknown; + }>>([]); + let comments = $state>([]); let loading = $state(false); let error = $state(null); let userPubkey = $state(null); @@ -105,7 +124,7 @@ if (response.ok) { const data = await response.json(); // Combine all file diffs - prDiff = data.map((d: any) => + prDiff = data.map((d: { file: string; diff: string }) => `--- ${d.file}\n+++ ${d.file}\n${d.diff}` ).join('\n\n'); } diff --git a/src/lib/services/git/file-manager.ts b/src/lib/services/git/file-manager.ts index fe3de4b..0ee4d69 100644 --- a/src/lib/services/git/file-manager.ts +++ b/src/lib/services/git/file-manager.ts @@ -671,7 +671,11 @@ export class FileManager { const git: SimpleGit = simpleGit(repoPath); try { - const logOptions: any = { + const logOptions: { + maxCount: number; + from: string; + file?: string; + } = { maxCount: limit, from: branch }; @@ -687,7 +691,7 @@ export class FileManager { message: commit.message, author: `${commit.author_name} <${commit.author_email}>`, date: commit.date, - files: commit.diff?.files?.map((f: any) => f.file) || [] + files: commit.diff?.files?.map((f: { file: string }) => f.file) || [] })); } catch (error) { logger.error({ error, repoPath, branch, limit }, 'Error getting commit history'); diff --git a/src/lib/types/nostr.ts b/src/lib/types/nostr.ts index 9a68858..2548c6c 100644 --- a/src/lib/types/nostr.ts +++ b/src/lib/types/nostr.ts @@ -23,6 +23,7 @@ export interface NostrFilter { since?: number; until?: number; limit?: number; + search?: string; // NIP-50: Search capability } export const KIND = { diff --git a/src/lib/utils/npub-utils.ts b/src/lib/utils/npub-utils.ts new file mode 100644 index 0000000..c2157e0 --- /dev/null +++ b/src/lib/utils/npub-utils.ts @@ -0,0 +1,53 @@ +/** + * Utility functions for working with npub (Nostr public key) encoding/decoding + */ + +import { nip19 } from 'nostr-tools'; + +/** + * Decode an npub to a hex pubkey + * @param npub - The npub string to decode + * @returns The hex pubkey, or null if invalid + */ +export function decodeNpubToHex(npub: string): string | null { + try { + const decoded = nip19.decode(npub); + if (decoded.type === 'npub') { + return decoded.data as string; + } + return null; + } catch { + return null; + } +} + +/** + * Decode an npub and return both the type and data + * @param npub - The npub string to decode + * @returns Object with type and hex pubkey, or null if invalid + */ +export function decodeNpub(npub: string): { type: 'npub'; data: string } | null { + try { + const decoded = nip19.decode(npub); + if (decoded.type === 'npub') { + return { type: 'npub', data: decoded.data as string }; + } + return null; + } catch { + return null; + } +} + +/** + * Validate and decode npub, throwing an error if invalid + * @param npub - The npub string to decode + * @returns The hex pubkey + * @throws Error if npub is invalid + */ +export function requireNpubHex(npub: string): string { + const decoded = decodeNpub(npub); + if (!decoded) { + throw new Error('Invalid npub format'); + } + return decoded.data; +} diff --git a/src/routes/api/git/[...path]/+server.ts b/src/routes/api/git/[...path]/+server.ts index f13b92f..bc00c22 100644 --- a/src/routes/api/git/[...path]/+server.ts +++ b/src/routes/api/git/[...path]/+server.ts @@ -7,6 +7,7 @@ import { error } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; import { RepoManager } from '$lib/services/git/repo-manager.js'; import { nip19 } from 'nostr-tools'; +import { requireNpubHex } from '$lib/utils/npub-utils.js'; import { spawn, execSync } from 'child_process'; import { existsSync } from 'fs'; import { join, resolve } from 'path'; @@ -69,11 +70,7 @@ function findGitHttpBackend(): string | null { */ async function getRepoAnnouncement(npub: string, repoName: string): Promise { try { - const decoded = nip19.decode(npub); - if (decoded.type !== 'npub') { - return null; - } - const pubkey = decoded.data as string; + const pubkey = requireNpubHex(npub); const events = await nostrClient.fetchEvents([ { @@ -127,10 +124,7 @@ export const GET: RequestHandler = async ({ params, url, request }) => { // Validate npub format try { - const decoded = nip19.decode(npub); - if (decoded.type !== 'npub') { - return error(400, 'Invalid npub format'); - } + pubkey = requireNpubHex(npub); } catch { return error(400, 'Invalid npub format'); } @@ -150,11 +144,7 @@ export const GET: RequestHandler = async ({ params, url, request }) => { // Check repository privacy for clone/fetch operations let originalOwnerPubkey: string; try { - const decoded = nip19.decode(npub); - if (decoded.type !== 'npub') { - return error(400, 'Invalid npub format'); - } - originalOwnerPubkey = decoded.data as string; + originalOwnerPubkey = requireNpubHex(npub); } catch { return error(400, 'Invalid npub format'); } @@ -351,11 +341,7 @@ export const POST: RequestHandler = async ({ params, url, request }) => { // Validate npub format and decode to get pubkey let originalOwnerPubkey: string; try { - const decoded = nip19.decode(npub); - if (decoded.type !== 'npub') { - return error(400, 'Invalid npub format'); - } - originalOwnerPubkey = decoded.data as string; + originalOwnerPubkey = requireNpubHex(npub); } catch { return error(400, 'Invalid npub format'); } diff --git a/src/routes/api/repos/[npub]/[repo]/branch-protection/+server.ts b/src/routes/api/repos/[npub]/[repo]/branch-protection/+server.ts index db87141..00cd275 100644 --- a/src/routes/api/repos/[npub]/[repo]/branch-protection/+server.ts +++ b/src/routes/api/repos/[npub]/[repo]/branch-protection/+server.ts @@ -13,6 +13,7 @@ import { NostrClient } from '$lib/services/nostr/nostr-client.js'; import { nip19 } from 'nostr-tools'; import type { BranchProtectionRule } from '$lib/services/nostr/branch-protection-service.js'; import logger from '$lib/services/logger.js'; +import { requireNpubHex, decodeNpubToHex } from '$lib/utils/npub-utils.js'; const branchProtectionService = new BranchProtectionService(DEFAULT_NOSTR_RELAYS); const ownershipTransferService = new OwnershipTransferService(DEFAULT_NOSTR_RELAYS); @@ -32,12 +33,7 @@ export const GET: RequestHandler = async ({ params }: { params: { npub?: string; // Decode npub to get pubkey let ownerPubkey: string; try { - const decoded = nip19.decode(npub); - if (decoded.type === 'npub' && typeof decoded.data === 'string') { - ownerPubkey = decoded.data; - } else { - return error(400, 'Invalid npub format'); - } + ownerPubkey = requireNpubHex(npub); } catch { return error(400, 'Invalid npub format'); } @@ -82,27 +78,12 @@ export const POST: RequestHandler = async ({ params, request }: { params: { npub // Decode npub to get pubkey let ownerPubkey: string; try { - const decoded = nip19.decode(npub); - if (decoded.type === 'npub' && typeof decoded.data === 'string') { - ownerPubkey = decoded.data; - } else { - return error(400, 'Invalid npub format'); - } + ownerPubkey = requireNpubHex(npub); } catch { return error(400, 'Invalid npub format'); } - let userPubkeyHex: string = userPubkey; - try { - const userDecoded = nip19.decode(userPubkey) as { type: string; data: unknown }; - // Type guard: check if it's an npub - if (userDecoded.type === 'npub' && typeof userDecoded.data === 'string') { - userPubkeyHex = userDecoded.data; - } - // If not npub, assume it's already hex - } catch { - // Assume it's already hex - } + const userPubkeyHex = decodeNpubToHex(userPubkey) || userPubkey; // Check if user is owner const currentOwner = await ownershipTransferService.getCurrentOwner(ownerPubkey, repo); @@ -111,7 +92,7 @@ export const POST: RequestHandler = async ({ params, request }: { params: { npub } // Validate rules - const validatedRules: BranchProtectionRule[] = rules.map((rule: any) => ({ + const validatedRules: BranchProtectionRule[] = rules.map((rule: { branch: string; requirePullRequest?: boolean; requireReviewers?: string[]; allowForcePush?: boolean; requireStatusChecks?: string[] }) => ({ branch: rule.branch, requirePullRequest: rule.requirePullRequest || false, requireReviewers: rule.requireReviewers || [], diff --git a/src/routes/api/repos/[npub]/[repo]/branches/+server.ts b/src/routes/api/repos/[npub]/[repo]/branches/+server.ts index 760414a..17dc169 100644 --- a/src/routes/api/repos/[npub]/[repo]/branches/+server.ts +++ b/src/routes/api/repos/[npub]/[repo]/branches/+server.ts @@ -9,6 +9,7 @@ import { FileManager } from '$lib/services/git/file-manager.js'; import { MaintainerService } from '$lib/services/nostr/maintainer-service.js'; import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js'; import { nip19 } from 'nostr-tools'; +import { requireNpubHex, decodeNpubToHex } from '$lib/utils/npub-utils.js'; import logger from '$lib/services/logger.js'; const repoRoot = process.env.GIT_REPO_ROOT || '/repos'; @@ -64,27 +65,13 @@ export const POST: RequestHandler = async ({ params, request }: { params: { npub // Check if user is a maintainer let repoOwnerPubkey: string; try { - const decoded = nip19.decode(npub); - if (decoded.type === 'npub') { - repoOwnerPubkey = decoded.data as string; - } else { - return error(400, 'Invalid npub format'); - } + repoOwnerPubkey = requireNpubHex(npub); } catch { return error(400, 'Invalid npub format'); } // Convert userPubkey to hex if needed - let userPubkeyHex = userPubkey; - try { - const userDecoded = nip19.decode(userPubkey); - // @ts-ignore - nip19 types are incomplete, but we know npub returns string - if (userDecoded.type === 'npub') { - userPubkeyHex = userDecoded.data as unknown as string; - } - } catch { - // Assume it's already a hex pubkey - } + const userPubkeyHex = decodeNpubToHex(userPubkey) || userPubkey; const isMaintainer = await maintainerService.isMaintainer(userPubkeyHex, repoOwnerPubkey, repo); if (!isMaintainer) { diff --git a/src/routes/api/repos/[npub]/[repo]/download/+server.ts b/src/routes/api/repos/[npub]/[repo]/download/+server.ts index 31ea23d..df0d717 100644 --- a/src/routes/api/repos/[npub]/[repo]/download/+server.ts +++ b/src/routes/api/repos/[npub]/[repo]/download/+server.ts @@ -8,6 +8,7 @@ import { FileManager } from '$lib/services/git/file-manager.js'; import { MaintainerService } from '$lib/services/nostr/maintainer-service.js'; import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js'; import { nip19 } from 'nostr-tools'; +import { requireNpubHex } from '$lib/utils/npub-utils.js'; import { exec } from 'child_process'; import { promisify } from 'util'; import { existsSync, readFileSync } from 'fs'; @@ -37,12 +38,7 @@ export const GET: RequestHandler = async ({ params, url, request }) => { // Check repository privacy let repoOwnerPubkey: string; try { - const decoded = nip19.decode(npub); - if (decoded.type === 'npub') { - repoOwnerPubkey = decoded.data as string; - } else { - return error(400, 'Invalid npub format'); - } + repoOwnerPubkey = requireNpubHex(npub); } catch { return error(400, 'Invalid npub format'); } diff --git a/src/routes/api/repos/[npub]/[repo]/file/+server.ts b/src/routes/api/repos/[npub]/[repo]/file/+server.ts index 0e5e8d2..3bbf3dd 100644 --- a/src/routes/api/repos/[npub]/[repo]/file/+server.ts +++ b/src/routes/api/repos/[npub]/[repo]/file/+server.ts @@ -12,6 +12,8 @@ import { nip19 } from 'nostr-tools'; import { verifyNIP98Auth } from '$lib/services/nostr/nip98-auth.js'; import { auditLogger } from '$lib/services/security/audit-logger.js'; import logger from '$lib/services/logger.js'; +import type { NostrEvent } from '$lib/types/nostr.js'; +import { requireNpubHex, decodeNpubToHex } from '$lib/utils/npub-utils.js'; const repoRoot = process.env.GIT_REPO_ROOT || '/repos'; const fileManager = new FileManager(repoRoot); @@ -35,12 +37,7 @@ export const GET: RequestHandler = async ({ params, url, request }: { params: { // Check repository privacy let repoOwnerPubkey: string; try { - const decoded = nip19.decode(npub); - if (decoded.type === 'npub') { - repoOwnerPubkey = decoded.data as string; - } else { - return error(400, 'Invalid npub format'); - } + repoOwnerPubkey = requireNpubHex(npub); } catch { return error(400, 'Invalid npub format'); } @@ -131,27 +128,13 @@ export const POST: RequestHandler = async ({ params, url, request }: { params: { // Check if user is a maintainer let repoOwnerPubkey: string; try { - const decoded = nip19.decode(npub); - if (decoded.type === 'npub') { - repoOwnerPubkey = decoded.data as string; - } else { - return error(400, 'Invalid npub format'); - } + repoOwnerPubkey = requireNpubHex(npub); } catch { return error(400, 'Invalid npub format'); } // Convert userPubkey to hex if needed - let userPubkeyHex = userPubkey; - try { - const userDecoded = nip19.decode(userPubkey); - // @ts-ignore - nip19 types are incomplete, but we know npub returns string - if (userDecoded.type === 'npub') { - userPubkeyHex = userDecoded.data as unknown as string; - } - } catch { - // Assume it's already a hex pubkey - } + const userPubkeyHex = decodeNpubToHex(userPubkey) || userPubkey; const isMaintainer = await maintainerService.isMaintainer(userPubkeyHex, repoOwnerPubkey, repo); if (!isMaintainer) { @@ -164,7 +147,7 @@ export const POST: RequestHandler = async ({ params, url, request }: { params: { // nsecKey is only for server-side use via environment variables. const signingOptions: { useNIP07?: boolean; - nip98Event?: any; + nip98Event?: NostrEvent; nsecKey?: string; } = {}; diff --git a/src/routes/api/repos/[npub]/[repo]/fork/+server.ts b/src/routes/api/repos/[npub]/[repo]/fork/+server.ts index 9376ebe..321b2d9 100644 --- a/src/routes/api/repos/[npub]/[repo]/fork/+server.ts +++ b/src/routes/api/repos/[npub]/[repo]/fork/+server.ts @@ -11,6 +11,7 @@ import { NostrClient } from '$lib/services/nostr/nostr-client.js'; import { KIND, type NostrEvent } from '$lib/types/nostr.js'; import { nip19 } from 'nostr-tools'; import { signEventWithNIP07 } from '$lib/services/nostr/nip07-signer.js'; +import { requireNpubHex, decodeNpubToHex } from '$lib/utils/npub-utils.js'; import { OwnershipTransferService } from '$lib/services/nostr/ownership-transfer-service.js'; import { exec } from 'child_process'; import { promisify } from 'util'; @@ -92,28 +93,13 @@ export const POST: RequestHandler = async ({ params, request }) => { // Decode original repo owner npub let originalOwnerPubkey: string; try { - const decoded = nip19.decode(npub); - if (decoded.type === 'npub') { - originalOwnerPubkey = decoded.data as string; - } else { - return error(400, 'Invalid npub format'); - } + originalOwnerPubkey = requireNpubHex(npub); } catch { return error(400, 'Invalid npub format'); } // Decode user pubkey if needed (must be done before using it) - let userPubkeyHex = userPubkey; - try { - const userDecoded = nip19.decode(userPubkey) as { type: string; data: unknown }; - // Type guard: check if it's an npub - if (userDecoded.type === 'npub' && typeof userDecoded.data === 'string') { - userPubkeyHex = userDecoded.data; - } - // If not npub, assume it's already hex - } catch { - // Assume it's already hex - } + const userPubkeyHex = decodeNpubToHex(userPubkey) || userPubkey; // Convert to npub for resource check and path construction const userNpub = nip19.npubEncode(userPubkeyHex); @@ -394,12 +380,7 @@ export const GET: RequestHandler = async ({ params }) => { // Decode repo owner npub let ownerPubkey: string; try { - const decoded = nip19.decode(npub); - if (decoded.type === 'npub') { - ownerPubkey = decoded.data as string; - } else { - return error(400, 'Invalid npub format'); - } + ownerPubkey = requireNpubHex(npub); } catch { return error(400, 'Invalid npub format'); } diff --git a/src/routes/api/repos/[npub]/[repo]/highlights/+server.ts b/src/routes/api/repos/[npub]/[repo]/highlights/+server.ts index 6c2bf24..755847d 100644 --- a/src/routes/api/repos/[npub]/[repo]/highlights/+server.ts +++ b/src/routes/api/repos/[npub]/[repo]/highlights/+server.ts @@ -7,6 +7,7 @@ import type { RequestHandler } from './$types'; import { HighlightsService } from '$lib/services/nostr/highlights-service.js'; import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js'; import { nip19 } from 'nostr-tools'; +import { requireNpubHex, decodeNpubToHex } from '$lib/utils/npub-utils.js'; import { verifyEvent } from 'nostr-tools'; import type { NostrEvent } from '$lib/types/nostr.js'; import { combineRelays } from '$lib/config.js'; @@ -38,26 +39,13 @@ export const GET: RequestHandler = async ({ params, url }) => { // Decode npub to get pubkey let repoOwnerPubkey: string; try { - const decoded = nip19.decode(npub); - if (decoded.type === 'npub') { - repoOwnerPubkey = decoded.data as string; - } else { - return error(400, 'Invalid npub format'); - } + repoOwnerPubkey = requireNpubHex(npub); } catch { return error(400, 'Invalid npub format'); } // Decode prAuthor if it's an npub - let prAuthorPubkey = prAuthor; - try { - const decoded = nip19.decode(prAuthor); - if (decoded.type === 'npub') { - prAuthorPubkey = decoded.data as string; - } - } catch { - // Assume it's already hex - } + const prAuthorPubkey = decodeNpubToHex(prAuthor) || prAuthor; // Get highlights for the PR const highlights = await highlightsService.getHighlightsForPR( diff --git a/src/routes/api/repos/[npub]/[repo]/issues/+server.ts b/src/routes/api/repos/[npub]/[repo]/issues/+server.ts index 591d86b..5f6ee45 100644 --- a/src/routes/api/repos/[npub]/[repo]/issues/+server.ts +++ b/src/routes/api/repos/[npub]/[repo]/issues/+server.ts @@ -19,11 +19,12 @@ export const GET: RequestHandler = async ({ params, url, request }) => { try { // Convert npub to pubkey - const decoded = nip19.decode(npub); - if (decoded.type !== 'npub') { + let repoOwnerPubkey: string; + try { + repoOwnerPubkey = requireNpubHex(npub); + } catch { return error(400, 'Invalid npub format'); } - const repoOwnerPubkey = decoded.data as string; // Check repository privacy const { checkRepoAccess } = await import('$lib/utils/repo-privacy.js'); diff --git a/src/routes/api/repos/[npub]/[repo]/maintainers/+server.ts b/src/routes/api/repos/[npub]/[repo]/maintainers/+server.ts index f07dbca..7281328 100644 --- a/src/routes/api/repos/[npub]/[repo]/maintainers/+server.ts +++ b/src/routes/api/repos/[npub]/[repo]/maintainers/+server.ts @@ -8,6 +8,7 @@ import type { RequestHandler } from './$types'; import { MaintainerService } from '$lib/services/nostr/maintainer-service.js'; import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js'; import { nip19 } from 'nostr-tools'; +import { requireNpubHex, decodeNpubToHex } from '$lib/utils/npub-utils.js'; import logger from '$lib/services/logger.js'; const maintainerService = new MaintainerService(DEFAULT_NOSTR_RELAYS); @@ -24,12 +25,7 @@ export const GET: RequestHandler = async ({ params, url }: { params: { npub?: st // Convert npub to pubkey let repoOwnerPubkey: string; try { - const decoded = nip19.decode(npub); - if (decoded.type === 'npub') { - repoOwnerPubkey = decoded.data as string; - } else { - return error(400, 'Invalid npub format'); - } + repoOwnerPubkey = requireNpubHex(npub); } catch { return error(400, 'Invalid npub format'); } @@ -38,17 +34,7 @@ export const GET: RequestHandler = async ({ params, url }: { params: { npub?: st // If userPubkey provided, check if they're a maintainer if (userPubkey) { - let userPubkeyHex = userPubkey; - try { - // Try to decode if it's an npub - const userDecoded = nip19.decode(userPubkey); - // @ts-ignore - nip19 types are incomplete, but we know npub returns string - if (userDecoded.type === 'npub') { - userPubkeyHex = userDecoded.data as unknown as string; - } - } catch { - // Assume it's already a hex pubkey - } + const userPubkeyHex = decodeNpubToHex(userPubkey) || userPubkey; const isMaintainer = maintainers.includes(userPubkeyHex); return json({ diff --git a/src/routes/api/repos/[npub]/[repo]/prs/+server.ts b/src/routes/api/repos/[npub]/[repo]/prs/+server.ts index 17653b7..ea19d83 100644 --- a/src/routes/api/repos/[npub]/[repo]/prs/+server.ts +++ b/src/routes/api/repos/[npub]/[repo]/prs/+server.ts @@ -8,6 +8,7 @@ import type { RequestHandler } from './$types'; import { PRsService } from '$lib/services/nostr/prs-service.js'; import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js'; import { nip19 } from 'nostr-tools'; +import { requireNpubHex } from '$lib/utils/npub-utils.js'; import logger from '$lib/services/logger.js'; export const GET: RequestHandler = async ({ params, url, request }: { params: { npub?: string; repo?: string }; url: URL; request: Request }) => { @@ -22,12 +23,7 @@ export const GET: RequestHandler = async ({ params, url, request }: { params: { // Convert npub to pubkey let repoOwnerPubkey: string; try { - const decoded = nip19.decode(npub); - if (decoded.type === 'npub') { - repoOwnerPubkey = decoded.data as string; - } else { - return error(400, 'Invalid npub format'); - } + repoOwnerPubkey = requireNpubHex(npub); } catch { return error(400, 'Invalid npub format'); } diff --git a/src/routes/api/repos/[npub]/[repo]/raw/+server.ts b/src/routes/api/repos/[npub]/[repo]/raw/+server.ts index 1642713..19a2af4 100644 --- a/src/routes/api/repos/[npub]/[repo]/raw/+server.ts +++ b/src/routes/api/repos/[npub]/[repo]/raw/+server.ts @@ -8,6 +8,7 @@ import { FileManager } from '$lib/services/git/file-manager.js'; import { MaintainerService } from '$lib/services/nostr/maintainer-service.js'; import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js'; import { nip19 } from 'nostr-tools'; +import { requireNpubHex } from '$lib/utils/npub-utils.js'; import logger from '$lib/services/logger.js'; const repoRoot = process.env.GIT_REPO_ROOT || '/repos'; @@ -32,12 +33,7 @@ export const GET: RequestHandler = async ({ params, url, request }) => { // Check repository privacy let repoOwnerPubkey: string; try { - const decoded = nip19.decode(npub); - if (decoded.type === 'npub') { - repoOwnerPubkey = decoded.data as string; - } else { - return error(400, 'Invalid npub format'); - } + repoOwnerPubkey = requireNpubHex(npub); } catch { return error(400, 'Invalid npub format'); } diff --git a/src/routes/api/repos/[npub]/[repo]/readme/+server.ts b/src/routes/api/repos/[npub]/[repo]/readme/+server.ts index 83c0903..02798fa 100644 --- a/src/routes/api/repos/[npub]/[repo]/readme/+server.ts +++ b/src/routes/api/repos/[npub]/[repo]/readme/+server.ts @@ -8,6 +8,7 @@ import { FileManager } from '$lib/services/git/file-manager.js'; import { MaintainerService } from '$lib/services/nostr/maintainer-service.js'; import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js'; import { nip19 } from 'nostr-tools'; +import { requireNpubHex } from '$lib/utils/npub-utils.js'; import logger from '$lib/services/logger.js'; const repoRoot = process.env.GIT_REPO_ROOT || '/repos'; @@ -42,12 +43,7 @@ export const GET: RequestHandler = async ({ params, url, request }) => { // Check repository privacy let repoOwnerPubkey: string; try { - const decoded = nip19.decode(npub); - if (decoded.type === 'npub') { - repoOwnerPubkey = decoded.data as string; - } else { - return error(400, 'Invalid npub format'); - } + repoOwnerPubkey = requireNpubHex(npub); } catch { return error(400, 'Invalid npub format'); } diff --git a/src/routes/api/repos/[npub]/[repo]/settings/+server.ts b/src/routes/api/repos/[npub]/[repo]/settings/+server.ts index 15c6f07..d4791fd 100644 --- a/src/routes/api/repos/[npub]/[repo]/settings/+server.ts +++ b/src/routes/api/repos/[npub]/[repo]/settings/+server.ts @@ -9,6 +9,7 @@ import { DEFAULT_NOSTR_RELAYS, combineRelays, getGitUrl } from '$lib/config.js'; import { getUserRelays } from '$lib/services/nostr/user-relays.js'; import { KIND } from '$lib/types/nostr.js'; import { nip19 } from 'nostr-tools'; +import { requireNpubHex, decodeNpubToHex } from '$lib/utils/npub-utils.js'; import { signEventWithNIP07 } from '$lib/services/nostr/nip07-signer.js'; import { MaintainerService } from '$lib/services/nostr/maintainer-service.js'; import logger from '$lib/services/logger.js'; @@ -33,12 +34,7 @@ export const GET: RequestHandler = async ({ params, url, request }) => { // Decode npub to get pubkey let repoOwnerPubkey: string; try { - const decoded = nip19.decode(npub) as { type: string; data: unknown }; - if (decoded.type === 'npub' && typeof decoded.data === 'string') { - repoOwnerPubkey = decoded.data; - } else { - return error(400, 'Invalid npub format'); - } + repoOwnerPubkey = requireNpubHex(npub); } catch { return error(400, 'Invalid npub format'); } @@ -48,15 +44,7 @@ export const GET: RequestHandler = async ({ params, url, request }) => { return error(401, 'Authentication required'); } - let userPubkeyHex = userPubkey; - try { - const userDecoded = nip19.decode(userPubkey) as { type: string; data: unknown }; - if (userDecoded.type === 'npub' && typeof userDecoded.data === 'string') { - userPubkeyHex = userDecoded.data; - } - } catch { - // Assume it's already hex - } + const userPubkeyHex = decodeNpubToHex(userPubkey) || userPubkey; const currentOwner = await ownershipTransferService.getCurrentOwner(repoOwnerPubkey, repo); if (userPubkeyHex !== currentOwner) { @@ -127,25 +115,12 @@ export const POST: RequestHandler = async ({ params, request }) => { // Decode npub to get pubkey let repoOwnerPubkey: string; try { - const decoded = nip19.decode(npub) as { type: string; data: unknown }; - if (decoded.type === 'npub' && typeof decoded.data === 'string') { - repoOwnerPubkey = decoded.data; - } else { - return error(400, 'Invalid npub format'); - } + repoOwnerPubkey = requireNpubHex(npub); } catch { return error(400, 'Invalid npub format'); } - let userPubkeyHex = userPubkey; - try { - const userDecoded = nip19.decode(userPubkey) as { type: string; data: unknown }; - if (userDecoded.type === 'npub' && typeof userDecoded.data === 'string') { - userPubkeyHex = userDecoded.data; - } - } catch { - // Assume it's already hex - } + const userPubkeyHex = decodeNpubToHex(userPubkey) || userPubkey; // Check if user is owner const currentOwner = await ownershipTransferService.getCurrentOwner(repoOwnerPubkey, repo); diff --git a/src/routes/api/repos/[npub]/[repo]/tags/+server.ts b/src/routes/api/repos/[npub]/[repo]/tags/+server.ts index ca1c531..1da4f54 100644 --- a/src/routes/api/repos/[npub]/[repo]/tags/+server.ts +++ b/src/routes/api/repos/[npub]/[repo]/tags/+server.ts @@ -9,6 +9,7 @@ import { FileManager } from '$lib/services/git/file-manager.js'; import { MaintainerService } from '$lib/services/nostr/maintainer-service.js'; import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js'; import { nip19 } from 'nostr-tools'; +import { requireNpubHex, decodeNpubToHex } from '$lib/utils/npub-utils.js'; import logger from '$lib/services/logger.js'; const repoRoot = process.env.GIT_REPO_ROOT || '/repos'; @@ -73,27 +74,13 @@ export const POST: RequestHandler = async ({ params, request }: { params: { npub // Check if user is a maintainer let repoOwnerPubkey: string; try { - const decoded = nip19.decode(npub); - if (decoded.type === 'npub') { - repoOwnerPubkey = decoded.data as string; - } else { - return error(400, 'Invalid npub format'); - } + repoOwnerPubkey = requireNpubHex(npub); } catch { return error(400, 'Invalid npub format'); } // Convert userPubkey to hex if needed - let userPubkeyHex = userPubkey; - try { - const userDecoded = nip19.decode(userPubkey); - // @ts-ignore - nip19 types are incomplete, but we know npub returns string - if (userDecoded.type === 'npub') { - userPubkeyHex = userDecoded.data as unknown as string; - } - } catch { - // Assume it's already a hex pubkey - } + const userPubkeyHex = decodeNpubToHex(userPubkey) || userPubkey; const isMaintainer = await maintainerService.isMaintainer(userPubkeyHex, repoOwnerPubkey, repo); if (!isMaintainer) { diff --git a/src/routes/api/repos/[npub]/[repo]/transfer/+server.ts b/src/routes/api/repos/[npub]/[repo]/transfer/+server.ts index 3e70157..71911de 100644 --- a/src/routes/api/repos/[npub]/[repo]/transfer/+server.ts +++ b/src/routes/api/repos/[npub]/[repo]/transfer/+server.ts @@ -9,6 +9,7 @@ import { NostrClient } from '$lib/services/nostr/nostr-client.js'; import { DEFAULT_NOSTR_RELAYS, combineRelays } from '$lib/config.js'; import { KIND } from '$lib/types/nostr.js'; import { nip19 } from 'nostr-tools'; +import { requireNpubHex, decodeNpubToHex } from '$lib/utils/npub-utils.js'; import { verifyEvent } from 'nostr-tools'; import type { NostrEvent } from '$lib/types/nostr.js'; import { getUserRelays } from '$lib/services/nostr/user-relays.js'; @@ -31,12 +32,7 @@ export const GET: RequestHandler = async ({ params }) => { // Decode npub to get pubkey let originalOwnerPubkey: string; try { - const decoded = nip19.decode(npub); - if (decoded.type === 'npub') { - originalOwnerPubkey = decoded.data as string; - } else { - return error(400, 'Invalid npub format'); - } + originalOwnerPubkey = requireNpubHex(npub); } catch { return error(400, 'Invalid npub format'); } @@ -109,12 +105,7 @@ export const POST: RequestHandler = async ({ params, request }) => { // Decode npub to get original owner pubkey let originalOwnerPubkey: string; try { - const decoded = nip19.decode(npub); - if (decoded.type === 'npub') { - originalOwnerPubkey = decoded.data as string; - } else { - return error(400, 'Invalid npub format'); - } + originalOwnerPubkey = requireNpubHex(npub); } catch { return error(400, 'Invalid npub format'); } diff --git a/src/routes/api/repos/[npub]/[repo]/tree/+server.ts b/src/routes/api/repos/[npub]/[repo]/tree/+server.ts index 1ef8847..8a1e6f4 100644 --- a/src/routes/api/repos/[npub]/[repo]/tree/+server.ts +++ b/src/routes/api/repos/[npub]/[repo]/tree/+server.ts @@ -8,6 +8,7 @@ import { FileManager } from '$lib/services/git/file-manager.js'; import { MaintainerService } from '$lib/services/nostr/maintainer-service.js'; import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js'; import { nip19 } from 'nostr-tools'; +import { requireNpubHex } from '$lib/utils/npub-utils.js'; import logger from '$lib/services/logger.js'; const repoRoot = process.env.GIT_REPO_ROOT || '/repos'; @@ -32,12 +33,7 @@ export const GET: RequestHandler = async ({ params, url, request }) => { // Check repository privacy let repoOwnerPubkey: string; try { - const decoded = nip19.decode(npub); - if (decoded.type === 'npub') { - repoOwnerPubkey = decoded.data as string; - } else { - return error(400, 'Invalid npub format'); - } + repoOwnerPubkey = requireNpubHex(npub); } catch { return error(400, 'Invalid npub format'); } diff --git a/src/routes/api/repos/[npub]/[repo]/verify/+server.ts b/src/routes/api/repos/[npub]/[repo]/verify/+server.ts index e78a052..6d9bdcf 100644 --- a/src/routes/api/repos/[npub]/[repo]/verify/+server.ts +++ b/src/routes/api/repos/[npub]/[repo]/verify/+server.ts @@ -32,12 +32,7 @@ export const GET: RequestHandler = async ({ params }: { params: { npub?: string; // Decode npub to get pubkey let ownerPubkey: string; try { - const decoded = nip19.decode(npub) as { type: string; data: unknown }; - if (decoded.type === 'npub' && typeof decoded.data === 'string') { - ownerPubkey = decoded.data; - } else { - return error(400, 'Invalid npub format'); - } + ownerPubkey = requireNpubHex(npub); } catch { return error(400, 'Invalid npub format'); } @@ -87,10 +82,7 @@ export const GET: RequestHandler = async ({ params }: { params: { npub?: string; // Decode npub if needed if (toPubkey) { try { - const decoded = nip19.decode(toPubkey) as { type: string; data: unknown }; - if (decoded.type === 'npub' && typeof decoded.data === 'string') { - toPubkey = decoded.data; - } + toPubkey = decodeNpubToHex(toPubkey) || toPubkey; } catch { // Assume it's already hex } diff --git a/src/routes/api/search/+server.ts b/src/routes/api/search/+server.ts index ee7618a..0899aec 100644 --- a/src/routes/api/search/+server.ts +++ b/src/routes/api/search/+server.ts @@ -5,7 +5,7 @@ import { json, error } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; import { NostrClient } from '$lib/services/nostr/nostr-client.js'; -import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js'; +import { DEFAULT_NOSTR_RELAYS, DEFAULT_NOSTR_SEARCH_RELAYS } from '$lib/config.js'; import { KIND } from '$lib/types/nostr.js'; import { FileManager } from '$lib/services/git/file-manager.js'; import { nip19 } from 'nostr-tools'; @@ -30,8 +30,8 @@ export const GET: RequestHandler = async ({ url }) => { } try { - // Create a new client instance for each search to ensure fresh connections - const nostrClient = new NostrClient(DEFAULT_NOSTR_RELAYS); + // Use search relays which are more likely to support NIP-50 + const nostrClient = new NostrClient(DEFAULT_NOSTR_SEARCH_RELAYS); const results: { repos: Array<{ id: string; name: string; description: string; owner: string; npub: string }>; @@ -41,48 +41,80 @@ export const GET: RequestHandler = async ({ url }) => { code: [] }; - // Search repositories + // Search repositories using NIP-50 if (type === 'repos' || type === 'all') { - const events = await nostrClient.fetchEvents([ - { - kinds: [KIND.REPO_ANNOUNCEMENT], - limit: 100 - } - ]); - - const searchLower = query.toLowerCase(); + let events: Array<{ id: string; pubkey: string; tags: string[][]; content: string; created_at: number }> = []; + + try { + // Try NIP-50 search first (relays that support it will return results sorted by relevance) + events = await nostrClient.fetchEvents([ + { + kinds: [KIND.REPO_ANNOUNCEMENT], + search: query, // NIP-50: Search field + limit: limit * 2 // Get more results to account for different relay implementations + } + ]); + + logger.info({ query, eventCount: events.length }, 'NIP-50 search results'); + } catch (nip50Error) { + // Fallback to manual filtering if NIP-50 fails or isn't supported + logger.warn({ error: nip50Error, query }, 'NIP-50 search failed, falling back to manual filtering'); + + const allEvents = await nostrClient.fetchEvents([ + { + kinds: [KIND.REPO_ANNOUNCEMENT], + limit: 500 // Get more events for manual filtering + } + ]); + + const searchLower = query.toLowerCase(); + events = allEvents.filter(event => { + const name = event.tags.find(t => t[0] === 'name')?.[1] || ''; + const description = event.tags.find(t => t[0] === 'description')?.[1] || ''; + const repoId = event.tags.find(t => t[0] === 'd')?.[1] || ''; + const content = event.content || ''; + + return name.toLowerCase().includes(searchLower) || + description.toLowerCase().includes(searchLower) || + repoId.toLowerCase().includes(searchLower) || + content.toLowerCase().includes(searchLower); + }); + } + // Process events into results + const searchLower = query.toLowerCase(); for (const event of events) { const name = event.tags.find(t => t[0] === 'name')?.[1] || ''; const description = event.tags.find(t => t[0] === 'description')?.[1] || ''; const repoId = event.tags.find(t => t[0] === 'd')?.[1] || ''; - const nameMatch = name.toLowerCase().includes(searchLower); - const descMatch = description.toLowerCase().includes(searchLower); - const repoMatch = repoId.toLowerCase().includes(searchLower); - - if (nameMatch || descMatch || repoMatch) { - try { - const npub = nip19.npubEncode(event.pubkey); - results.repos.push({ - id: event.id, - name: name || repoId, - description: description || '', - owner: event.pubkey, - npub - }); - } catch { - // Skip if npub encoding fails - } + try { + const npub = nip19.npubEncode(event.pubkey); + results.repos.push({ + id: event.id, + name: name || repoId, + description: description || '', + owner: event.pubkey, + npub + }); + } catch { + // Skip if npub encoding fails } } - // Sort by relevance (name matches first) + // Sort by relevance (name matches first, then description) + // Note: NIP-50 compliant relays should already return results sorted by relevance results.repos.sort((a, b) => { const aNameMatch = a.name.toLowerCase().includes(searchLower); const bNameMatch = b.name.toLowerCase().includes(searchLower); if (aNameMatch && !bNameMatch) return -1; if (!aNameMatch && bNameMatch) return 1; + + const aDescMatch = a.description.toLowerCase().includes(searchLower); + const bDescMatch = b.description.toLowerCase().includes(searchLower); + if (aDescMatch && !bDescMatch) return -1; + if (!aDescMatch && bDescMatch) return 1; + return 0; }); diff --git a/src/routes/docs/+page.svelte b/src/routes/docs/+page.svelte index e5a6ec5..354014a 100644 --- a/src/routes/docs/+page.svelte +++ b/src/routes/docs/+page.svelte @@ -21,7 +21,10 @@ return '
' +
                        hljs.highlight(str, { language: lang }).value +
                        '
'; - } catch (__) {} + } catch (err) { + // Fallback to escaped HTML if highlighting fails + // This is expected for unsupported languages + } } return '
' + md.utils.escapeHtml(str) + '
'; } diff --git a/src/routes/docs/nip34/+page.svelte b/src/routes/docs/nip34/+page.svelte index cdd9c66..aa0e7cf 100644 --- a/src/routes/docs/nip34/+page.svelte +++ b/src/routes/docs/nip34/+page.svelte @@ -21,7 +21,10 @@ return '
' +
                        hljs.highlight(str, { language: lang }).value +
                        '
'; - } catch (__) {} + } catch (err) { + // Fallback to escaped HTML if highlighting fails + // This is expected for unsupported languages + } } return '
' + md.utils.escapeHtml(str) + '
'; } diff --git a/src/routes/docs/nip34/spec/+page.svelte b/src/routes/docs/nip34/spec/+page.svelte index 3b1a7cd..9c51287 100644 --- a/src/routes/docs/nip34/spec/+page.svelte +++ b/src/routes/docs/nip34/spec/+page.svelte @@ -21,7 +21,10 @@ return '
' +
                        hljs.highlight(str, { language: lang }).value +
                        '
'; - } catch (__) {} + } catch (err) { + // Fallback to escaped HTML if highlighting fails + // This is expected for unsupported languages + } } return '
' + md.utils.escapeHtml(str) + '
'; } diff --git a/src/routes/repos/[npub]/[repo]/+page.svelte b/src/routes/repos/[npub]/[repo]/+page.svelte index 352c549..fda2560 100644 --- a/src/routes/repos/[npub]/[repo]/+page.svelte +++ b/src/routes/repos/[npub]/[repo]/+page.svelte @@ -135,7 +135,10 @@ return '
' +
                            hljs.highlight(str, { language: lang }).value +
                            '
'; - } catch (__) {} + } catch (err) { + // Fallback to escaped HTML if highlighting fails + // This is expected for unsupported languages + } } return '
' + md.utils.escapeHtml(str) + '
'; } @@ -881,7 +884,7 @@ const response = await fetch(`/api/repos/${npub}/${repo}/issues`); if (response.ok) { const data = await response.json(); - issues = data.map((issue: any) => ({ + issues = data.map((issue: { id: string; tags: string[][]; content: string; status?: string; pubkey: string; created_at: number }) => ({ id: issue.id, subject: issue.tags.find((t: string[]) => t[0] === 'subject')?.[1] || 'Untitled', content: issue.content, @@ -955,7 +958,7 @@ const response = await fetch(`/api/repos/${npub}/${repo}/prs`); if (response.ok) { const data = await response.json(); - prs = data.map((pr: any) => ({ + prs = data.map((pr: { id: string; tags: string[][]; content: string; status?: string; pubkey: string; created_at: number; commitId?: string }) => ({ id: pr.id, subject: pr.tags.find((t: string[]) => t[0] === 'subject')?.[1] || 'Untitled', content: pr.content, diff --git a/src/routes/search/+page.svelte b/src/routes/search/+page.svelte index ed3968b..250ed61 100644 --- a/src/routes/search/+page.svelte +++ b/src/routes/search/+page.svelte @@ -52,7 +52,7 @@
@@ -65,6 +65,9 @@ {loading ? 'Searching...' : 'Search'}
+
+ Using NIP-50 search across multiple relays for better results +
{#if error} @@ -146,3 +149,14 @@ + diff --git a/src/routes/signup/+page.svelte b/src/routes/signup/+page.svelte index 31c967a..a0f92d5 100644 --- a/src/routes/signup/+page.svelte +++ b/src/routes/signup/+page.svelte @@ -45,7 +45,8 @@ // Lookup state let lookupLoading = $state<{ [key: string]: boolean }>({}); let lookupError = $state<{ [key: string]: string | null }>({}); - let lookupResults = $state<{ [key: string]: any }>({}); + type ProfileData = { pubkey: string; npub: string; name?: string; about?: string; picture?: string }; + let lookupResults = $state<{ [key: string]: Array | null }>({}); import { DEFAULT_NOSTR_RELAYS, DEFAULT_NOSTR_SEARCH_RELAYS, combineRelays } from '../../lib/config.js'; @@ -434,7 +435,13 @@ } ]); - let profileData: any = { + let profileData: { + pubkey: string; + npub: string; + name?: string; + about?: string; + picture?: string; + } = { pubkey, npub: query.startsWith('npub') ? query : nip19.npubEncode(pubkey) }; @@ -1212,24 +1219,26 @@ Clear - {#each lookupResults['repo-existingRepoRef'] as result} - {@const nameTag = result.tags.find((t: string[]) => t[0] === 'name')?.[1]} - {@const dTag = result.tags.find((t: string[]) => t[0] === 'd')?.[1]} - {@const descTag = result.tags.find((t: string[]) => t[0] === 'description')?.[1]} - {@const imageTag = result.tags.find((t: string[]) => t[0] === 'image')?.[1]} - {@const ownerNpub = nip19.npubEncode(result.pubkey)} - {@const tags = result.tags.filter((t: string[]) => t[0] === 't' && t[1] && t[1] !== 'private' && t[1] !== 'fork').map((t: string[]) => t[1])} -
selectRepoResult(result, 'existingRepoRef')} - onkeydown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - selectRepoResult(result, 'existingRepoRef'); - } - }} + {#each (lookupResults['repo-existingRepoRef'] || []) as result} + {#if 'tags' in result} + {@const event = result as NostrEvent} + {@const nameTag = event.tags.find((t: string[]) => t[0] === 'name')?.[1]} + {@const dTag = event.tags.find((t: string[]) => t[0] === 'd')?.[1]} + {@const descTag = event.tags.find((t: string[]) => t[0] === 'description')?.[1]} + {@const imageTag = event.tags.find((t: string[]) => t[0] === 'image')?.[1]} + {@const ownerNpub = nip19.npubEncode(event.pubkey)} + {@const tags = event.tags.filter((t: string[]) => t[0] === 't' && t[1] && t[1] !== 'private' && t[1] !== 'fork').map((t: string[]) => t[1])} +
selectRepoResult(event, 'existingRepoRef')} + onkeydown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + selectRepoResult(event, 'existingRepoRef'); + } + }} >
{#if imageTag} @@ -1245,7 +1254,7 @@ {/if}
Owner: {ownerNpub.slice(0, 16)}... - Event: {result.id.slice(0, 16)}... + Event: {event.id.slice(0, 16)}...
{#if tags.length > 0}
@@ -1257,6 +1266,7 @@
+ {/if} {/each}
{/if} @@ -1435,7 +1445,7 @@ {#if lookupResults[`npub-maintainers-${index}`]}
- Found {lookupResults[`npub-maintainers-${index}`].length} profile(s): + Found {(lookupResults[`npub-maintainers-${index}`] || []).length} profile(s):
- {#each lookupResults[`npub-maintainers-${index}`] as result} -
selectNpubResult(result, 'maintainers', index)} - onkeydown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - selectNpubResult(result, 'maintainers', index); - } - }} - > -
- {#if result.picture} - - {:else} -
- {(result.name || result.npub).slice(0, 2).toUpperCase()} -
- {/if} -
- {result.name || 'Unknown'} - {#if result.about} -

{result.about}

+ {#each (lookupResults[`npub-maintainers-${index}`] || []) as result} + {#if 'npub' in result} + {@const profile = result as ProfileData} +
selectNpubResult(profile, 'maintainers', index)} + onkeydown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + selectNpubResult(profile, 'maintainers', index); + } + }} + > +
+ {#if profile.picture} + + {:else} +
+ {(profile.name || profile.npub).slice(0, 2).toUpperCase()} +
{/if} - {result.npub} +
+ {profile.name || 'Unknown'} + {#if profile.about} +

{profile.about}

+ {/if} + {profile.npub} +
-
+ {/if} {/each}
{/if} @@ -1750,24 +1763,26 @@ Clear
- {#each lookupResults['repo-forkOriginalRepo'] as result} - {@const nameTag = result.tags.find((t: string[]) => t[0] === 'name')?.[1]} - {@const dTag = result.tags.find((t: string[]) => t[0] === 'd')?.[1]} - {@const descTag = result.tags.find((t: string[]) => t[0] === 'description')?.[1]} - {@const imageTag = result.tags.find((t: string[]) => t[0] === 'image')?.[1]} - {@const ownerNpub = nip19.npubEncode(result.pubkey)} - {@const tags = result.tags.filter((t: string[]) => t[0] === 't' && t[1] && t[1] !== 'private' && t[1] !== 'fork').map((t: string[]) => t[1])} -
selectRepoResult(result, 'forkOriginalRepo')} - onkeydown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - selectRepoResult(result, 'forkOriginalRepo'); - } - }} + {#each (lookupResults['repo-forkOriginalRepo'] || []) as result} + {#if 'tags' in result} + {@const event = result as NostrEvent} + {@const nameTag = event.tags.find((t: string[]) => t[0] === 'name')?.[1]} + {@const dTag = event.tags.find((t: string[]) => t[0] === 'd')?.[1]} + {@const descTag = event.tags.find((t: string[]) => t[0] === 'description')?.[1]} + {@const imageTag = event.tags.find((t: string[]) => t[0] === 'image')?.[1]} + {@const ownerNpub = nip19.npubEncode(event.pubkey)} + {@const tags = event.tags.filter((t: string[]) => t[0] === 't' && t[1] && t[1] !== 'private' && t[1] !== 'fork').map((t: string[]) => t[1])} +
selectRepoResult(event, 'forkOriginalRepo')} + onkeydown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + selectRepoResult(event, 'forkOriginalRepo'); + } + }} >
{#if imageTag} @@ -1783,7 +1798,7 @@ {/if}
Owner: {ownerNpub.slice(0, 16)}... - Event: {result.id.slice(0, 16)}... + Event: {event.id.slice(0, 16)}...
{#if tags.length > 0}
@@ -1795,6 +1810,7 @@
+ {/if} {/each}
{/if}