Browse Source

refactor 11

Nostr-Signature: bb9d5c56a291e48221df96868fb925e309cb560aa350c2cf5f9c4ddd5e5c4a6b 573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc 75662c916bf4d8bb3d70cdae4e4882382692c6f1ca67598a69abe3dc96069ef6f2bda5a1b8f91b724aa43b3cb3c6b8ad6cbce286b5d165377a34a881e7275d2a
main
Silberengel 2 weeks ago
parent
commit
d580ab99f3
  1. 1
      nostr/commit-signatures.jsonl
  2. 10
      src/lib/components/NostrHtmlRenderer.svelte
  3. 8
      src/lib/utils/nostr-links.ts
  4. 1550
      src/routes/repos/[npub]/[repo]/+page.svelte
  5. 80
      src/routes/repos/[npub]/[repo]/services/discussion-operations.ts
  6. 35
      src/routes/repos/[npub]/[repo]/utils/discussion-utils.ts
  7. 7
      src/routes/repos/[npub]/[repo]/utils/file-processing.ts
  8. 76
      src/routes/repos/[npub]/[repo]/utils/repo-callbacks.ts
  9. 33
      src/routes/repos/[npub]/[repo]/utils/safe-wrappers.ts

1
nostr/commit-signatures.jsonl

@ -104,3 +104,4 @@
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772130529,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","refactor 8"]],"content":"Signed commit: refactor 8","id":"716cfe7b5d8b788e6e24092a6ad7e92de0b3d383c43a343f3c5bec4d2bbdd4b9","sig":"e80ed3d9d471bd6907e212edfd7cf3f6039fa80e4434c35f0591729515eaa98c7a8ac54f2ac6f7a2fefb7846de0e2f0a120543a0dbe862c47c7710a653189b0c"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772130529,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","refactor 8"]],"content":"Signed commit: refactor 8","id":"716cfe7b5d8b788e6e24092a6ad7e92de0b3d383c43a343f3c5bec4d2bbdd4b9","sig":"e80ed3d9d471bd6907e212edfd7cf3f6039fa80e4434c35f0591729515eaa98c7a8ac54f2ac6f7a2fefb7846de0e2f0a120543a0dbe862c47c7710a653189b0c"}
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772131858,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","refactor 9"]],"content":"Signed commit: refactor 9","id":"0d92496bc69fe5a2005be0eba26653a729d358f9d9f227e1af01c330eb0c4387","sig":"9203dcc9cfb44804957d37d3b47079ac324bffb42e445a3a79130622e5e20fd10513d987f48edf195514ebf6cb136ba6c5992b39de21b8b47a086194e22cbaeb"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772131858,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","refactor 9"]],"content":"Signed commit: refactor 9","id":"0d92496bc69fe5a2005be0eba26653a729d358f9d9f227e1af01c330eb0c4387","sig":"9203dcc9cfb44804957d37d3b47079ac324bffb42e445a3a79130622e5e20fd10513d987f48edf195514ebf6cb136ba6c5992b39de21b8b47a086194e22cbaeb"}
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772136696,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","refactor 10"]],"content":"Signed commit: refactor 10","id":"7fb8d54e26ab59486f3b56d97e225ed02f893140025c03ccb95a991e523e6182","sig":"f4bb5a037c48d06854d9346ebf96aa9f65f11d3f96e23d08b7d38d0ebea9bab242ffa917239aa432d83a55f369586d66603f439f40eac8156aeaaf80737b81a1"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772136696,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","refactor 10"]],"content":"Signed commit: refactor 10","id":"7fb8d54e26ab59486f3b56d97e225ed02f893140025c03ccb95a991e523e6182","sig":"f4bb5a037c48d06854d9346ebf96aa9f65f11d3f96e23d08b7d38d0ebea9bab242ffa917239aa432d83a55f369586d66603f439f40eac8156aeaaf80737b81a1"}
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772141183,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","bug-fixes"]],"content":"Signed commit: bug-fixes","id":"b92b203686c0629409fef055e7f3189cf9f26be5cca0253ab00cf7e8498e1115","sig":"06a13aac9d2f794e52b0416044db6ebf9dd248d254d2166d7e7f3fefd2b7d37d1a85072c3e92316898c31068e25cf37bc5afd2fcd8ae2050d0a30b1bc1973678"}

10
src/lib/components/NostrHtmlRenderer.svelte

@ -79,9 +79,9 @@
// Create and assign loading promise immediately // Create and assign loading promise immediately
loadingPromise = (async () => { loadingPromise = (async () => {
try { try {
await loadNostrLinks(currentHtml, nostrClient!, newEventCache, newProfileCache); await loadNostrLinks(currentHtml, nostrClient!, newEventCache, newProfileCache);
console.log('[NostrHtmlRenderer] After loadNostrLinks - events:', newEventCache.size, 'profiles:', newProfileCache.size); console.log('[NostrHtmlRenderer] After loadNostrLinks - events:', newEventCache.size, 'profiles:', newProfileCache.size);
// Only update if this is still the current HTML (prevent race conditions) // Only update if this is still the current HTML (prevent race conditions)
if (currentHtml === html) { if (currentHtml === html) {
@ -91,8 +91,8 @@
// Set lastHtml only after successful load to prevent re-triggering // Set lastHtml only after successful load to prevent re-triggering
lastHtml = currentHtml; lastHtml = currentHtml;
} }
} catch (err) { } catch (err) {
console.error('[NostrHtmlRenderer] Error loading nostr links:', err); console.error('[NostrHtmlRenderer] Error loading nostr links:', err);
} finally { } finally {
// Only clear loading state if HTML hasn't changed (prevent race conditions) // Only clear loading state if HTML hasn't changed (prevent race conditions)
if (currentHtml === html) { if (currentHtml === html) {
@ -119,7 +119,7 @@
// Only load if HTML actually changed (not just if we don't have data) // Only load if HTML actually changed (not just if we don't have data)
// This prevents re-loading when component re-renders // This prevents re-loading when component re-renders
if (currentHtml !== lastHtml) { if (currentHtml !== lastHtml) {
loadEventsAndProfiles(); loadEventsAndProfiles();
} }
} else if (!currentHtml) { } else if (!currentHtml) {
loading = false; loading = false;

8
src/lib/utils/nostr-links.ts

@ -201,7 +201,7 @@ export async function loadNostrLinks(
const defaultEvents = await Promise.race([ const defaultEvents = await Promise.race([
nostrClient.fetchEvents([{ ids: missingIds, limit: missingIds.length }]), nostrClient.fetchEvents([{ ids: missingIds, limit: missingIds.length }]),
new Promise<NostrEvent[]>((resolve) => setTimeout(() => resolve([]), 15000)) new Promise<NostrEvent[]>((resolve) => setTimeout(() => resolve([]), 15000))
]); ]);
events.push(...defaultEvents); events.push(...defaultEvents);
defaultEvents.forEach(e => foundIds.add(e.id)); defaultEvents.forEach(e => foundIds.add(e.id));
console.log('[loadNostrLinks] Fetched', defaultEvents.length, 'additional events from default client'); console.log('[loadNostrLinks] Fetched', defaultEvents.length, 'additional events from default client');
@ -250,9 +250,9 @@ export async function loadNostrLinks(
// Fallback to default relays if no events found // Fallback to default relays if no events found
if (events.length === 0) { if (events.length === 0) {
events = await Promise.race([ events = await Promise.race([
nostrClient.fetchEvents([{ kinds: [kind], authors: [pubkey], '#d': [dTag], limit: 1 }]), nostrClient.fetchEvents([{ kinds: [kind], authors: [pubkey], '#d': [dTag], limit: 1 }]),
new Promise<NostrEvent[]>((resolve) => setTimeout(() => resolve([]), 10000)) new Promise<NostrEvent[]>((resolve) => setTimeout(() => resolve([]), 10000))
]); ]);
} }
if (events.length > 0) { if (events.length > 0) {

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

File diff suppressed because it is too large Load Diff

80
src/routes/repos/[npub]/[repo]/services/discussion-operations.ts

@ -17,7 +17,7 @@ interface DiscussionOperationsCallbacks {
loadDiscussions: () => Promise<void>; loadDiscussions: () => Promise<void>;
loadNostrLinks: (content: string) => Promise<void>; loadNostrLinks: (content: string) => Promise<void>;
loadDiscussionEvents: (discussions: Array<{ loadDiscussionEvents: (discussions: Array<{
type: 'thread' | 'comments' | string; type: string;
id: string; id: string;
title: string; title: string;
content: string; content: string;
@ -25,7 +25,13 @@ interface DiscussionOperationsCallbacks {
createdAt: number; createdAt: number;
kind?: number; kind?: number;
pubkey?: string; pubkey?: string;
comments?: Array<any>; comments?: Array<{
id: string;
replies?: Array<{
id: string;
replies?: Array<{ id: string }>;
}>;
}>;
}>) => Promise<void>; }>) => Promise<void>;
} }
@ -427,6 +433,76 @@ export async function createThreadReply(
} }
} }
/**
* Load full events for discussions and comments to get tags for blurbs
*/
export async function loadDiscussionEvents(
discussionsList: Array<{
type: string;
id: string;
title: string;
content: string;
author: string;
createdAt: number;
kind?: number;
pubkey?: string;
comments?: Array<{
id: string;
replies?: Array<{
id: string;
replies?: Array<{ id: string }>;
}>;
}>;
}>,
state: RepoState,
nostrClient: NostrClient
): Promise<void> {
const eventIds = new Set<string>();
// Collect all event IDs
for (const discussion of discussionsList) {
if (discussion.id) {
eventIds.add(discussion.id);
}
if (discussion.comments) {
for (const comment of discussion.comments) {
if (comment.id) {
eventIds.add(comment.id);
}
if (comment.replies) {
for (const reply of comment.replies) {
if (reply.id) {
eventIds.add(reply.id);
}
if (reply.replies) {
for (const nestedReply of reply.replies) {
if (nestedReply.id) {
eventIds.add(nestedReply.id);
}
}
}
}
}
}
}
}
if (eventIds.size === 0) return;
try {
const events = await Promise.race([
nostrClient.fetchEvents([{ ids: Array.from(eventIds), limit: eventIds.size }]),
new Promise<NostrEvent[]>((resolve) => setTimeout(() => resolve([]), 10000))
]);
for (const event of events) {
state.discussion.events.set(event.id, event);
}
} catch {
// Ignore fetch errors
}
}
/** /**
* Load documentation from the repository * Load documentation from the repository
*/ */

35
src/routes/repos/[npub]/[repo]/utils/discussion-utils.ts

@ -33,18 +33,41 @@ export function getDiscussionEvent(eventId: string, events: Map<string, NostrEve
} }
/** /**
* Get referenced event from discussion * Get referenced event from discussion (checks e-tag, a-tag, and q-tag)
*/ */
export function getReferencedEventFromDiscussion( export function getReferencedEventFromDiscussion(
event: NostrEvent, event: NostrEvent,
events: Map<string, NostrEvent> events: Map<string, NostrEvent>
): NostrEvent | undefined { ): NostrEvent | undefined {
// Check for 'e' tags (event references) // Check e-tag
const eTags = event.tags.filter(t => t[0] === 'e' && t[1]); const eTag = event.tags.find(t => t[0] === 'e' && t[1])?.[1];
if (eTags.length > 0) { if (eTag) {
const referencedId = eTags[0][1] as string; const referenced = events.get(eTag);
return events.get(referencedId); if (referenced) return referenced;
} }
// Check a-tag
const aTag = event.tags.find(t => t[0] === 'a' && t[1])?.[1];
if (aTag) {
const parts = aTag.split(':');
if (parts.length === 3) {
const kind = parseInt(parts[0]);
const pubkey = parts[1];
const dTag = parts[2];
return Array.from(events.values()).find(e =>
e.kind === kind &&
e.pubkey === pubkey &&
e.tags.find(t => t[0] === 'd' && t[1] === dTag)
);
}
}
// Check q-tag
const qTag = event.tags.find(t => t[0] === 'q' && t[1])?.[1];
if (qTag) {
return events.get(qTag);
}
return undefined; return undefined;
} }

7
src/routes/repos/[npub]/[repo]/utils/file-processing.ts

@ -131,7 +131,11 @@ export function rewriteImagePaths(
repo?: string, repo?: string,
branch?: string | null branch?: string | null
): string { ): string {
if (!filePath || !html) return html; if (!html || !filePath) return html || '';
if (typeof html !== 'string') {
console.error('[rewriteImagePaths] Invalid html parameter:', typeof html, html);
return '';
}
// Get directory path (remove filename) // Get directory path (remove filename)
const fileDir = filePath.includes('/') const fileDir = filePath.includes('/')
@ -395,3 +399,4 @@ export async function renderFileAsHtml(
setHtml(''); setHtml('');
} }
} }

76
src/routes/repos/[npub]/[repo]/utils/repo-callbacks.ts

@ -0,0 +1,76 @@
/**
* Shared callbacks object for repo operations
* Consolidates common callback patterns to reduce duplication
*/
import type { RepoState } from '../stores/repo-state.js';
import { rewriteImagePaths as rewriteImagePathsUtil } from './file-processing.js';
import { findReadmeFile as findReadmeFileUtil } from './file-helpers.js';
export interface FileCallbacks {
getUserEmail: () => Promise<string>;
getUserName: () => Promise<string>;
loadFiles: (path: string) => Promise<void>;
loadFile: (path: string) => Promise<void>;
renderFileAsHtml: (content: string, ext: string) => Promise<void>;
applySyntaxHighlighting: (content: string, ext: string) => Promise<void>;
findReadmeFile: (fileList: Array<{ name: string; path: string; type: 'file' | 'directory' }>) => { name: string; path: string; type: 'file' | 'directory' } | null;
rewriteImagePaths: (html: string, filePath: string | null) => string;
}
export interface BranchCallbacks {
loadBranches: () => Promise<void>;
loadFiles: (path: string) => Promise<void>;
loadFile: (path: string) => Promise<void>;
loadReadme: () => Promise<void>;
loadCommitHistory: () => Promise<void>;
loadDocumentation: () => Promise<void>;
}
/**
* Create file callbacks from state and functions
*/
export function createFileCallbacks(
state: RepoState,
getUserEmail: () => Promise<string>,
getUserName: () => Promise<string>,
loadFiles: (path: string) => Promise<void>,
loadFile: (path: string) => Promise<void>,
renderFileAsHtml: (content: string, ext: string) => Promise<void>,
applySyntaxHighlighting: (content: string, ext: string) => Promise<void>
): FileCallbacks {
return {
getUserEmail,
getUserName,
loadFiles,
loadFile,
renderFileAsHtml,
applySyntaxHighlighting,
findReadmeFile: findReadmeFileUtil,
rewriteImagePaths: (html: string, filePath: string | null) => {
const branch = state.git.currentBranch || state.git.defaultBranch || null;
return rewriteImagePathsUtil(html, filePath, state.npub, state.repo, branch);
}
};
}
/**
* Create branch callbacks from functions
*/
export function createBranchCallbacks(
loadBranches: () => Promise<void>,
loadFiles: (path: string) => Promise<void>,
loadFile: (path: string) => Promise<void>,
loadReadme: () => Promise<void>,
loadCommitHistory: () => Promise<void>,
loadDocumentation: () => Promise<void>
): BranchCallbacks {
return {
loadBranches,
loadFiles,
loadFile,
loadReadme,
loadCommitHistory,
loadDocumentation
};
}

33
src/routes/repos/[npub]/[repo]/utils/safe-wrappers.ts

@ -0,0 +1,33 @@
/**
* Safe wrapper functions for SSR compatibility
* These functions check for window availability before executing
*/
/**
* Safely execute an async function, returning a resolved promise if window is undefined
*/
export function safeAsync<T>(
fn: () => Promise<T>
): Promise<T | void> {
if (typeof window === 'undefined') return Promise.resolve();
try {
return fn();
} catch (err) {
console.warn('Error in safe async function:', err);
return Promise.resolve();
}
}
/**
* Safely execute a sync function, returning void if window is undefined
*/
export function safeSync<T>(
fn: () => T
): T | void {
if (typeof window === 'undefined') return;
try {
return fn();
} catch (err) {
console.warn('Error in safe sync function:', err);
}
}
Loading…
Cancel
Save