Browse Source

bug-fixes

master
Silberengel 4 weeks ago
parent
commit
52775e29c7
  1. 25
      src/lib/components/content/MarkdownRenderer.svelte
  2. 12
      src/lib/modules/discussions/DiscussionList.svelte
  3. 10
      src/lib/modules/feed/FeedPage.svelte
  4. 16
      src/lib/modules/profiles/ProfilePage.svelte
  5. 2
      src/routes/cache/+page.svelte
  6. 12
      src/routes/discussions/+page.svelte
  7. 6
      src/routes/event/[id]/+page.svelte
  8. 11
      src/routes/feed/relay/[relay]/+page.svelte
  9. 2
      src/routes/find/+page.svelte
  10. 2
      src/routes/highlights/+page.svelte
  11. 8
      src/routes/profile/[pubkey]/+page.svelte
  12. 103
      src/routes/rss/+page.svelte
  13. 2
      src/routes/topics/+page.svelte
  14. 2
      src/routes/topics/[name]/+page.svelte

25
src/lib/components/content/MarkdownRenderer.svelte

@ -30,6 +30,14 @@ @@ -30,6 +30,14 @@
}
let { content, event, excludeMediaUrls = [] }: Props = $props();
// Ensure excludeMediaUrls is always an array (safety check)
let normalizedExcludeMediaUrls = $derived.by(() => {
if (!excludeMediaUrls || !Array.isArray(excludeMediaUrls)) {
return [];
}
return excludeMediaUrls;
});
let containerRef = $state<HTMLElement | null>(null);
let emojiUrls = $state<Map<string, string>>(new Map());
let highlights = $state<Highlight[]>([]);
@ -445,9 +453,10 @@ @@ -445,9 +453,10 @@
// Process and convert media URLs: handles markdown, AsciiDoc, and plain URLs
// Removes excluded URLs (displayed by MediaAttachments) and converts others to HTML tags
function processMediaUrls(text: string): string {
const excludeUrls = normalizedExcludeMediaUrls;
// Normalize exclude URLs for comparison
const normalizedExcludeUrls = excludeMediaUrls.length > 0
? new Set(excludeMediaUrls.map(url => normalizeUrl(url)))
const normalizedExcludeUrls = excludeUrls.length > 0
? new Set(excludeUrls.map(url => normalizeUrl(url)))
: new Set<string>();
// Debug: Log excluded URLs and check if they're being found in text
@ -458,11 +467,11 @@ @@ -458,11 +467,11 @@
for (const excludedUrl of normalizedExcludeUrls) {
// Try to find the URL in the text (check both normalized and original)
const urlInText = text.includes(excludedUrl) ||
excludeMediaUrls.some(orig => text.includes(orig));
excludeUrls.some(orig => text.includes(orig));
if (urlInText) {
console.debug('MarkdownRenderer: Found excluded URL in text:', excludedUrl);
// Find where it appears
const index = text.indexOf(excludeMediaUrls.find(orig => text.includes(orig)) || '');
const index = text.indexOf(excludeUrls.find(orig => text.includes(orig)) || '');
if (index >= 0) {
console.debug('MarkdownRenderer: URL found at index:', index, 'context:', text.substring(Math.max(0, index - 20), Math.min(text.length, index + 100)));
}
@ -764,9 +773,10 @@ @@ -764,9 +773,10 @@
// Helper function to apply exclusion filtering to HTML (used for both fresh and cached content)
function applyExclusionFiltering(htmlContent: string): string {
if (excludeMediaUrls.length === 0) return htmlContent;
const excludeUrls = normalizedExcludeMediaUrls;
if (!excludeUrls || excludeUrls.length === 0) return htmlContent;
const normalizedExcludeUrls = new Set(excludeMediaUrls.map(url => normalizeUrl(url)));
const normalizedExcludeUrls = new Set(excludeUrls.map(url => normalizeUrl(url)));
let filtered = htmlContent;
// Remove ALL img tags with excluded URLs (aggressive cleanup)
@ -898,7 +908,8 @@ @@ -898,7 +908,8 @@
});
// Normalize exclude URLs for comparison
const normalizedExcludeUrls = new Set(excludeMediaUrls.map(url => normalizeUrl(url)));
const excludeUrls = normalizedExcludeMediaUrls;
const normalizedExcludeUrls = new Set(excludeUrls.map(url => normalizeUrl(url)));
// AGGRESSIVE CLEANUP: Remove ALL img tags with excluded URLs first (before any other processing)
// This is the most important step - it catches images regardless of how they were created

12
src/lib/modules/discussions/DiscussionList.svelte

@ -19,6 +19,18 @@ @@ -19,6 +19,18 @@
// Expose state for parent component
export { sortBy, showOlder };
// Refresh function for parent component
async function refresh() {
if (!isMounted) return;
threadsMap.clear();
reactionsMap.clear();
commentsMap.clear();
loading = true;
await loadCachedThreads();
await loadAllData();
}
export { refresh };
// Resolved pubkey from filter (handled by parent component's PubkeyFilter)
// For now, we'll do basic normalization here since we don't have access to the filter component
// The parent component should resolve NIP-05 before passing it here

10
src/lib/modules/feed/FeedPage.svelte

@ -26,6 +26,16 @@ @@ -26,6 +26,16 @@
// Note: The warning about loadOlderEvents is a false positive - functions don't need to be reactive
export { loadOlderEvents, loadingMore, hasMoreEvents, waitingRoomEvents, loadWaitingRoomEvents };
// Refresh function for parent component
async function refresh() {
if (!isMounted) return;
allEvents = [];
oldestTimestamp = null;
waitingRoomEvents = [];
await loadFeed();
}
export { refresh };
// Core state
let allEvents = $state<NostrEvent[]>([]);
let loading = $state(true);

16
src/lib/modules/profiles/ProfilePage.svelte

@ -770,6 +770,22 @@ @@ -770,6 +770,22 @@
}
}
}
// Refresh function for parent component
async function refresh() {
if (!isMounted) return;
// Reset state and reload
profile = null;
profileEvent = null;
userStatus = null;
userStatusEvent = null;
notifications = [];
interactionsWithMe = [];
wallComments = [];
pins = [];
await loadProfile();
}
export { refresh };
</script>
<div class="profile-page">

2
src/routes/cache/+page.svelte vendored

@ -555,7 +555,7 @@ @@ -555,7 +555,7 @@
<main class="container mx-auto px-4 py-8">
<div class="cache-page">
<PageHeader title="/Cache" />
<PageHeader title="/Cache" onRefresh={async () => { await loadStats(); await loadArchiveStats(); await loadEvents(); }} refreshLoading={loading || archiving || recovering || recoveringEvent} />
{#if loading && !stats}
<div class="loading-state">

12
src/routes/discussions/+page.svelte

@ -17,7 +17,15 @@ @@ -17,7 +17,15 @@
let filterResult = $state<{ type: 'event' | 'pubkey' | 'text' | null; value: string | null }>({ type: null, value: null });
let searchResults = $state<{ events: NostrEvent[]; profiles: string[] }>({ events: [], profiles: [] });
let unifiedSearchComponent: { triggerSearch: () => void } | null = $state(null);
let discussionListComponent: { sortBy: 'newest' | 'active' | 'upvoted'; showOlder: boolean } | null = $state(null);
let discussionListComponent: { sortBy: 'newest' | 'active' | 'upvoted'; showOlder: boolean; refresh?: () => Promise<void> } | null = $state(null);
async function handleRefresh() {
// Clear search results and refresh discussion list
searchResults = { events: [], profiles: [] };
if (discussionListComponent?.refresh) {
await discussionListComponent.refresh();
}
}
// Pagination for search results
let currentPage = $derived(getCurrentPage($page.url.searchParams));
@ -52,7 +60,7 @@ @@ -52,7 +60,7 @@
<main class="container mx-auto px-4 py-8">
<div class="discussions-content">
<PageHeader title="/Discussions" />
<PageHeader title="/Discussions" onRefresh={handleRefresh} />
<p class="discussions-description text-fog-text dark:text-fog-dark-text mb-4">Decentralized discussion board on Nostr.</p>
<div class="discussions-header mb-6">

6
src/routes/event/[id]/+page.svelte

@ -147,13 +147,13 @@ @@ -147,13 +147,13 @@
<main class="container mx-auto px-4 py-8">
{#if loading}
<PageHeader title="Loading event..." />
<PageHeader title="Loading event..." onRefresh={loadEvent} refreshLoading={loading} />
<p class="text-fog-text dark:text-fog-dark-text">Loading event...</p>
{:else if error}
<PageHeader title="Error" />
<PageHeader title="Error" onRefresh={loadEvent} refreshLoading={loading} />
<p class="text-fog-text dark:text-fog-dark-text">{error}</p>
{:else if decodedEventId}
<PageHeader title="Event" />
<PageHeader title="Event" onRefresh={loadEvent} refreshLoading={loading} />
{#if eventKind === KIND.DISCUSSION_THREAD}
<!-- Route kind 11 (discussion threads) to DiscussionView -->
<DiscussionView threadId={decodedEventId} />

11
src/routes/feed/relay/[relay]/+page.svelte

@ -10,6 +10,13 @@ @@ -10,6 +10,13 @@
let decodedRelay = $state<string | null>(null);
let error = $state<string | null>(null);
let feedPageComponent: { refresh?: () => Promise<void> } | null = $state(null);
async function handleRefresh() {
if (feedPageComponent?.refresh) {
await feedPageComponent.refresh();
}
}
function decodeRelayUrl(encoded: string): string | null {
try {
@ -84,7 +91,7 @@ @@ -84,7 +91,7 @@
<main class="container mx-auto px-4 py-8">
<div class="relay-feed-content">
<PageHeader title={decodedRelay ? `Relay: ${decodedRelay}` : 'Relay Feed'} />
<PageHeader title={decodedRelay ? `Relay: ${decodedRelay}` : 'Relay Feed'} onRefresh={handleRefresh} />
<div class="search-section mb-6">
<UnifiedSearch mode="search" />
</div>
@ -95,7 +102,7 @@ @@ -95,7 +102,7 @@
</div>
{:else if decodedRelay}
<RelayInfo relayUrl={decodedRelay} />
<FeedPage singleRelay={decodedRelay} />
<FeedPage singleRelay={decodedRelay} bind:this={feedPageComponent} />
{:else}
<div class="loading-state">
<p class="text-fog-text dark:text-fog-dark-text">Loading relay feed...</p>

2
src/routes/find/+page.svelte

@ -135,7 +135,7 @@ @@ -135,7 +135,7 @@
<main class="container mx-auto px-4 py-8">
<div class="find-page">
<PageHeader title="/Find" />
<PageHeader title="/Find" onRefresh={clearAllSearches} />
<div class="page-header">
{#if hasActiveSearch}
<button

2
src/routes/highlights/+page.svelte

@ -223,7 +223,7 @@ @@ -223,7 +223,7 @@
<main class="container mx-auto px-4 py-8">
<div class="highlights-page">
<PageHeader title="/Highlights" />
<PageHeader title="/Highlights" onRefresh={loadHighlights} refreshLoading={loading} />
{#if loading}
<div class="loading-state">

8
src/routes/profile/[pubkey]/+page.svelte

@ -7,8 +7,12 @@ @@ -7,8 +7,12 @@
import { page } from '$app/stores';
import { nip19 } from 'nostr-tools';
let profilePageComponent: { refresh?: () => Promise<void> } | null = $state(null);
async function handleRefresh() {
window.location.reload();
if (profilePageComponent?.refresh) {
await profilePageComponent.refresh();
}
}
onMount(async () => {
@ -20,5 +24,5 @@ @@ -20,5 +24,5 @@
<main class="container mx-auto px-2 sm:px-4 py-4 sm:py-8">
<PageHeader title="Profile" onRefresh={handleRefresh} />
<ProfilePage />
<ProfilePage bind:this={profilePageComponent} />
</main>

103
src/routes/rss/+page.svelte

@ -238,17 +238,84 @@ @@ -238,17 +238,84 @@
}
}
// List of CORS proxies to try in order
const CORS_PROXIES = [
// Primary: allorigins.win (most reliable)
(url: string) => `https://api.allorigins.win/raw?url=${encodeURIComponent(url)}`,
// Fallback 1: corsproxy.io
(url: string) => `https://corsproxy.io/?${encodeURIComponent(url)}`,
// Fallback 2: cors-anywhere (public instance, may have rate limits)
(url: string) => `https://cors-anywhere.herokuapp.com/${url}`,
// Fallback 3: corsproxy.com
(url: string) => `https://api.corsproxy.io/?${encodeURIComponent(url)}`,
];
async function fetchRssFeed(feedUrl: string): Promise<RSSItem[]> {
// Always use a CORS proxy to avoid CORS errors
// Direct fetch will fail for most RSS feeds due to CORS restrictions
const proxyUrl = `https://api.allorigins.win/raw?url=${encodeURIComponent(feedUrl)}`;
const response = await fetch(proxyUrl);
// Try each CORS proxy in order until one succeeds
let lastError: Error | null = null;
for (let i = 0; i < CORS_PROXIES.length; i++) {
const proxyUrl = CORS_PROXIES[i](feedUrl);
try {
// Create abort controller for timeout
const abortController = new AbortController();
const timeoutId = setTimeout(() => abortController.abort(), 15000); // 15 second timeout
const response = await fetch(proxyUrl, {
signal: abortController.signal
});
clearTimeout(timeoutId); // Clear timeout if fetch succeeds
if (!response.ok) {
// If it's a 500 or other server error, try next proxy
if (response.status >= 500 || response.status === 403 || response.status === 429) {
lastError = new Error(`Proxy ${i + 1} returned ${response.status}: ${response.statusText}`);
continue; // Try next proxy
}
// For client errors (4xx), don't retry
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const xmlText = await response.text();
// Success! Read the response
let xmlText: string;
try {
xmlText = await response.text();
} catch (error) {
// Handle Content-Length mismatch errors
if (error instanceof TypeError) {
const errorMsg = error.message.toLowerCase();
if (errorMsg.includes('content-length') || errorMsg.includes('network')) {
lastError = new Error('Response size mismatch. Trying next proxy...');
continue; // Try next proxy
}
}
throw error;
}
// Parse the XML
return parseRssXml(xmlText, feedUrl);
} catch (error) {
// If it's an abort (timeout) or network error, try next proxy
if (error instanceof Error) {
if (error.name === 'AbortError' || error.name === 'TypeError') {
lastError = error;
continue; // Try next proxy
}
// For other errors, rethrow
throw error;
}
lastError = error instanceof Error ? error : new Error('Unknown error');
}
}
// All proxies failed
throw new Error(`All CORS proxies failed. Last error: ${lastError?.message || 'Unknown error'}`);
}
function parseRssXml(xmlText: string, feedUrl: string): RSSItem[] {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlText, 'text/xml');
@ -358,13 +425,37 @@ @@ -358,13 +425,37 @@
// Use the URL as the threadId for RSS items
return url;
}
async function handleRefresh() {
// Set loading state immediately
loading = true;
loadingFeeds = true;
// Reset feeds loaded state so loadRssFeeds can actually reload
feedsLoaded = false;
lastLoadedFeeds = [];
try {
// Refresh the RSS event
await checkRssEvent();
// Refresh feeds if we have any
if (subscribedFeeds.length > 0) {
await loadRssFeeds();
}
} finally {
// Ensure loading states are cleared
loading = false;
loadingFeeds = false;
}
}
</script>
<Header />
<main class="container mx-auto px-4 py-8">
<div class="rss-page">
<PageHeader title="/RSS" onRefresh={async () => { await checkRssEvent(); if (subscribedFeeds.length > 0) await loadRssFeeds(); }} refreshLoading={loading || loadingFeeds} />
<PageHeader title="/RSS" onRefresh={handleRefresh} refreshLoading={loading || loadingFeeds} />
{#if loading}
<p class="text-fog-text dark:text-fog-dark-text">Loading...</p>

2
src/routes/topics/+page.svelte

@ -241,7 +241,7 @@ @@ -241,7 +241,7 @@
<main class="container mx-auto px-4 py-8">
<div class="topics-page">
<PageHeader title="/Topics" />
<PageHeader title="/Topics" onRefresh={loadTopics} refreshLoading={loading} />
{#if loading}
<p class="text-fog-text dark:text-fog-dark-text">Loading topics...</p>

2
src/routes/topics/[name]/+page.svelte

@ -158,7 +158,7 @@ @@ -158,7 +158,7 @@
<main class="topic-main container mx-auto px-2 sm:px-4 py-8">
<div class="topic-content">
<PageHeader title={`Topic: #${topicName}`} />
<PageHeader title={`Topic: #${topicName}`} onRefresh={async () => { await loadCachedTopicEvents(); await loadTopicEvents(); }} refreshLoading={loadingEvents} />
<p class="topic-description text-fog-text-light dark:text-fog-dark-text-light mb-4">
{events.length} {events.length === 1 ? 'event' : 'events'} found
{#if totalPages > 1}

Loading…
Cancel
Save