Browse Source

bug-fixes

master
Silberengel 4 weeks ago
parent
commit
c3a42d7357
  1. 155
      src/lib/components/layout/PageHeader.svelte
  2. 22
      src/routes/about/+page.svelte
  3. 178
      src/routes/bookmarks/+page.svelte
  4. 56
      src/routes/cache/+page.svelte
  5. 9
      src/routes/discussions/+page.svelte
  6. 4
      src/routes/event/[id]/+page.svelte
  7. 22
      src/routes/feed/+page.svelte
  8. 2
      src/routes/feed/relay/[relay]/+page.svelte
  9. 8
      src/routes/find/+page.svelte
  10. 3
      src/routes/highlights/+page.svelte
  11. 7
      src/routes/lists/+page.svelte
  12. 8
      src/routes/profile/[pubkey]/+page.svelte
  13. 3
      src/routes/relay/+page.svelte
  14. 17
      src/routes/repos/+page.svelte
  15. 5
      src/routes/repos/[naddr]/+page.svelte
  16. 3
      src/routes/rss/+page.svelte
  17. 56
      src/routes/settings/+page.svelte
  18. 3
      src/routes/topics/+page.svelte
  19. 34
      src/routes/topics/[name]/+page.svelte
  20. 5
      src/routes/write/+page.svelte
  21. 6
      static/icons/refresh.svg

155
src/lib/components/layout/PageHeader.svelte

@ -0,0 +1,155 @@ @@ -0,0 +1,155 @@
<script lang="ts">
import Icon from '../ui/Icon.svelte';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
interface Props {
title: string;
onRefresh?: () => void | Promise<void>;
refreshLoading?: boolean;
}
let { title, onRefresh, refreshLoading = false }: Props = $props();
function handleBack() {
if (typeof window !== 'undefined' && window.history.length > 1) {
window.history.back();
} else {
goto('/discussions');
}
}
async function handleRefresh() {
if (onRefresh) {
await onRefresh();
} else {
// Default: reload the page
window.location.reload();
}
}
</script>
<div class="page-header">
<div class="page-header-left">
<button
class="page-header-button back-button"
onclick={handleBack}
aria-label="Go back"
title="Go back"
>
<Icon name="arrow-left" size={20} />
</button>
<h1 class="page-header-title">{title}</h1>
</div>
<button
class="page-header-button refresh-button"
class:loading={refreshLoading}
onclick={handleRefresh}
disabled={refreshLoading}
aria-label="Refresh"
title="Refresh"
>
<Icon name="refresh" size={20} />
</button>
</div>
<style>
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
padding: 0 1rem;
margin-bottom: 1.5rem;
}
.page-header-left {
display: flex;
align-items: center;
gap: 0.75rem;
flex: 1;
min-width: 0;
}
.page-header-title {
font-size: 1.5em;
font-weight: bold;
color: var(--fog-text, #1f2937);
margin: 0;
font-family: monospace;
flex: 1;
min-width: 0;
word-break: break-word;
}
:global(.dark) .page-header-title {
color: var(--fog-dark-text, #f9fafb);
}
.page-header-button {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.5rem;
border: 1px solid var(--fog-border, #e5e7eb);
border-radius: 0.375rem;
background: var(--fog-post, #ffffff);
color: var(--fog-text, #1f2937);
cursor: pointer;
transition: all 0.2s;
flex-shrink: 0;
width: 2.5rem;
height: 2.5rem;
}
:global(.dark) .page-header-button {
border-color: var(--fog-dark-border, #374151);
background: var(--fog-dark-post, #1f2937);
color: var(--fog-dark-text, #f9fafb);
}
.page-header-button:hover:not(:disabled) {
background: var(--fog-highlight, #f3f4f6);
border-color: var(--fog-accent, #64748b);
}
:global(.dark) .page-header-button:hover:not(:disabled) {
background: var(--fog-dark-highlight, #475569);
border-color: var(--fog-dark-accent, #94a3b8);
}
.page-header-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.page-header-button.loading {
animation: spin 1s linear infinite;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (max-width: 640px) {
.page-header {
padding: 0 0.5rem;
margin-bottom: 1rem;
}
.page-header-title {
font-size: 1.25em;
}
.page-header-button {
width: 2.25rem;
height: 2.25rem;
padding: 0.375rem;
}
}
</style>

22
src/routes/about/+page.svelte

@ -1,8 +1,7 @@ @@ -1,8 +1,7 @@
<script lang="ts">
import Header from '../../lib/components/layout/Header.svelte';
import PageHeader from '../../lib/components/layout/PageHeader.svelte';
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import Icon from '../../lib/components/ui/Icon.svelte';
import { getAppVersion } from '../../lib/services/version-manager.js';
import { getAllVersions, loadChangelog } from '../../lib/services/changelog.js';
@ -19,30 +18,13 @@ @@ -19,30 +18,13 @@
loadingChangelog = false;
});
function handleBack() {
if (typeof window !== 'undefined' && window.history.length > 1) {
window.history.back();
} else {
goto('/');
}
}
</script>
<Header />
<main class="container mx-auto px-4 py-8">
<div class="about-page">
<div class="about-header">
<h1 class="font-bold text-fog-text dark:text-fog-dark-text font-mono mb-6" style="font-size: 1.5em;">/About</h1>
<button
onclick={handleBack}
class="back-button flex items-center gap-2"
aria-label="Go back to previous page"
>
<Icon name="arrow-left" size={16} />
<span>Back</span>
</button>
</div>
<PageHeader title="/About" />
<div class="about-content space-y-8">
<!-- Product Information -->

178
src/routes/bookmarks/+page.svelte

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
<script lang="ts">
import Header from '../../lib/components/layout/Header.svelte';
import PageHeader from '../../lib/components/layout/PageHeader.svelte';
import FeedPost from '../../lib/modules/feed/FeedPost.svelte';
import { getBookmarkedEvents } from '../../lib/services/user-actions.js';
import { getEvent } from '../../lib/services/cache/event-cache.js';
@ -13,7 +14,8 @@ @@ -13,7 +14,8 @@
import Pagination from '../../lib/components/ui/Pagination.svelte';
import { getPaginatedItems, getCurrentPage, ITEMS_PER_PAGE } from '../../lib/utils/pagination.js';
let loading = $state(true);
let loading = $state(false); // Start as false - will be set to true only if no cache
let cacheLoaded = $state(false); // Track if cache has been checked
let events = $state<NostrEvent[]>([]);
let error = $state<string | null>(null);
@ -25,8 +27,75 @@ @@ -25,8 +27,75 @@
: events
);
// Load cached bookmarks immediately when component initializes (before mount)
$effect(() => {
if (!cacheLoaded && typeof window !== 'undefined') {
cacheLoaded = true;
loadCachedBookmarks();
}
});
async function loadCachedBookmarks() {
try {
// Check if user is logged in
const session = sessionManager.getSession();
if (!session) {
error = 'You must be logged in to view bookmarks.';
loading = false;
goto('/login');
return;
}
// Get bookmarked event IDs
const bookmarkedIds = await getBookmarkedEvents();
if (bookmarkedIds.size === 0) {
events = [];
loading = false; // Show empty state immediately
return;
}
// Fetch events from cache in parallel (much faster than sequential)
const eventIds = Array.from(bookmarkedIds);
const cachePromises = eventIds.map(id => getEvent(id));
const cacheResults = await Promise.all(cachePromises);
const cachedEvents: NostrEvent[] = [];
const missingIds: string[] = [];
for (let i = 0; i < cacheResults.length; i++) {
const cached = cacheResults[i];
if (cached) {
cachedEvents.push(cached as NostrEvent);
} else {
missingIds.push(eventIds[i]);
}
}
// Show cached events immediately
if (cachedEvents.length > 0) {
events = cachedEvents.sort((a, b) => b.created_at - a.created_at);
loading = false; // Show cached data immediately
} else {
// No cached events, show loading
loading = true;
}
} catch (error) {
// Cache error (non-critical) - show loading
loading = true;
}
}
async function loadBookmarks() {
loading = true;
// Check cache first before making any network requests
// If we already have cached events, we'll enhance with fresh data in the background
const hasCachedData = events.length > 0;
// Only show loading spinner if we don't have cached events
if (!hasCachedData) {
loading = true;
}
error = null;
try {
@ -34,7 +103,9 @@ @@ -34,7 +103,9 @@
const session = sessionManager.getSession();
if (!session) {
error = 'You must be logged in to view bookmarks.';
loading = false;
if (!hasCachedData) {
loading = false;
}
goto('/login');
return;
}
@ -46,48 +117,103 @@ @@ -46,48 +117,103 @@
if (bookmarkedIds.size === 0) {
events = [];
loading = false;
if (!hasCachedData) {
loading = false;
}
return;
}
// Fetch events from cache first
// Fetch events from cache in parallel (much faster than sequential)
const eventIds = Array.from(bookmarkedIds);
const cachePromises = eventIds.map(id => getEvent(id));
const cacheResults = await Promise.all(cachePromises);
const cachedEvents: NostrEvent[] = [];
const missingIds: string[] = [];
for (const id of eventIds) {
const cached = await getEvent(id);
for (let i = 0; i < cacheResults.length; i++) {
const cached = cacheResults[i];
if (cached) {
cachedEvents.push(cached as NostrEvent);
} else {
missingIds.push(id);
missingIds.push(eventIds[i]);
}
}
// Update with cached events immediately (in case cache was updated)
if (cachedEvents.length > 0) {
const sortedCached = cachedEvents.sort((a, b) => b.created_at - a.created_at);
events = sortedCached;
if (!hasCachedData) {
loading = false; // Show cached data immediately
}
}
// Fetch missing events from relays
let relayEvents: NostrEvent[] = [];
if (missingIds.length > 0) {
const relays = relayManager.getAllAvailableRelays();
relayEvents = await nostrClient.fetchEvents(
[{ ids: missingIds, limit: missingIds.length }],
relays,
{ useCache: true, cacheResults: true }
);
// Refresh all bookmarked events from relays to get any updates
// This ensures we have the latest versions even if they're in cache
// Use cache-first to prioritize cache, but fetch from relays for updates
const allRelayEvents = await nostrClient.fetchEvents(
[{ ids: eventIds, limit: eventIds.length }],
relayManager.getAllAvailableRelays(),
{
useCache: 'cache-first', // Check cache first, then fetch from relays
cacheResults: true,
onUpdate: (newEvents) => {
// Merge with existing events as they stream in (progressive enhancement)
const eventsMap = new Map<string, NostrEvent>();
// Add existing events first
for (const event of events) {
eventsMap.set(event.id, event);
}
// Add/update with new events from network
for (const event of newEvents) {
eventsMap.set(event.id, event);
}
// Update UI with merged events
events = Array.from(eventsMap.values()).sort((a, b) => b.created_at - a.created_at);
}
}
);
// Final merge of any remaining events (in case onUpdate didn't catch them all)
const eventsMap = new Map<string, NostrEvent>();
// Add existing cached events first
for (const event of events) {
eventsMap.set(event.id, event);
}
// Combine and sort by created_at (newest first)
const allEvents = [...cachedEvents, ...relayEvents];
events = allEvents.sort((a, b) => b.created_at - a.created_at);
// Add/update with new events from network
for (const event of allRelayEvents) {
eventsMap.set(event.id, event);
}
// Update with final merged events
events = Array.from(eventsMap.values()).sort((a, b) => b.created_at - a.created_at);
} catch (err) {
console.error('Error loading bookmarks:', err);
error = 'Failed to load bookmarks.';
// Only clear events if we don't have cached data
if (!hasCachedData) {
events = [];
}
} finally {
loading = false;
}
}
onMount(() => {
loadBookmarks();
onMount(async () => {
await nostrClient.initialize();
// Only load from network if we don't have cached events
if (events.length === 0) {
await loadBookmarks();
} else {
// Load fresh data in background while showing cached data
loadBookmarks();
}
});
</script>
@ -95,9 +221,7 @@ @@ -95,9 +221,7 @@
<main class="container mx-auto px-4 py-8">
<div class="bookmarks-content">
<div class="bookmarks-header mb-6">
<h1 class="font-bold text-fog-text dark:text-fog-dark-text font-mono" style="font-size: 1.5em;">/Bookmarks</h1>
</div>
<PageHeader title="/Bookmarks" onRefresh={loadBookmarks} refreshLoading={loading} />
{#if loading}
<div class="loading-state">
@ -133,10 +257,6 @@ @@ -133,10 +257,6 @@
margin: 0 auto;
}
.bookmarks-header {
padding: 0 1rem;
}
.loading-state,
.error-state,
.empty-state {

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

@ -1,16 +1,9 @@ @@ -1,16 +1,9 @@
<script lang="ts">
import Header from '../../lib/components/layout/Header.svelte';
import PageHeader from '../../lib/components/layout/PageHeader.svelte';
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import Icon from '../../lib/components/ui/Icon.svelte';
function handleBack() {
if (typeof window !== 'undefined' && window.history.length > 1) {
window.history.back();
} else {
goto('/');
}
}
import { getCacheStats, getAllCachedEvents, clearAllCache, clearCacheByKind, clearCacheByKinds, clearCacheByDate, deleteEventById, type CacheStats } from '../../lib/services/cache/cache-manager.js';
import { cacheEvent } from '../../lib/services/cache/event-cache.js';
import type { CachedEvent } from '../../lib/services/cache/event-cache.js';
@ -562,17 +555,7 @@ @@ -562,17 +555,7 @@
<main class="container mx-auto px-4 py-8">
<div class="cache-page">
<div class="cache-header">
<h1 class="font-bold text-fog-text dark:text-fog-dark-text font-mono mb-6" style="font-size: 1.5em;">/Cache</h1>
<button
onclick={handleBack}
class="back-button flex items-center gap-2"
aria-label="Go back to previous page"
>
<Icon name="arrow-left" size={16} />
<span>Back</span>
</button>
</div>
<PageHeader title="/Cache" />
{#if loading && !stats}
<div class="loading-state">
@ -881,41 +864,6 @@ @@ -881,41 +864,6 @@
padding: 0 1rem;
}
.cache-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1.5rem;
}
.back-button {
padding: 0.5rem 1rem;
border: 1px solid var(--fog-border, #cbd5e1);
border-radius: 4px;
background: var(--fog-post, #ffffff);
color: var(--fog-text, #1e293b);
cursor: pointer;
transition: all 0.2s;
font-size: 0.875em;
white-space: nowrap;
}
.back-button:hover {
background: var(--fog-highlight, #f1f5f9);
border-color: var(--fog-accent, #94a3b8);
}
:global(.dark) .back-button {
background: var(--fog-dark-post, #334155);
border-color: var(--fog-dark-border, #475569);
color: var(--fog-dark-text, #f1f5f9);
}
:global(.dark) .back-button:hover {
background: var(--fog-dark-highlight, #475569);
border-color: var(--fog-dark-accent, #64748b);
}
.stats-section,
.filters-section,

9
src/routes/discussions/+page.svelte

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
<script lang="ts">
import Header from '../../lib/components/layout/Header.svelte';
import PageHeader from '../../lib/components/layout/PageHeader.svelte';
import DiscussionList from '../../lib/modules/discussions/DiscussionList.svelte';
import UnifiedSearch from '../../lib/components/layout/UnifiedSearch.svelte';
import FeedPost from '../../lib/modules/feed/FeedPost.svelte';
@ -51,13 +52,9 @@ @@ -51,13 +52,9 @@
<main class="container mx-auto px-4 py-8">
<div class="discussions-content">
<PageHeader title="/Discussions" />
<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">
<div class="discussions-header-top">
<div>
<h1 class="font-bold text-fog-text dark:text-fog-dark-text font-mono" style="font-size: 1.5em;">/Discussions</h1>
<p class="text-fog-text dark:text-fog-dark-text">Decentralized discussion board on Nostr.</p>
</div>
</div>
<div class="discussions-controls">
<div class="search-section">

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

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
<script lang="ts">
import Header from '../../../lib/components/layout/Header.svelte';
import PageHeader from '../../../lib/components/layout/PageHeader.svelte';
import DiscussionView from '../../../lib/modules/discussions/DiscussionView.svelte';
import EventView from '../../../lib/modules/events/EventView.svelte';
import { nostrClient } from '../../../lib/services/nostr/nostr-client.js';
@ -146,10 +147,13 @@ @@ -146,10 +147,13 @@
<main class="container mx-auto px-4 py-8">
{#if loading}
<PageHeader title="Loading event..." />
<p class="text-fog-text dark:text-fog-dark-text">Loading event...</p>
{:else if error}
<PageHeader title="Error" />
<p class="text-fog-text dark:text-fog-dark-text">{error}</p>
{:else if decodedEventId}
<PageHeader title="Event" />
{#if eventKind === KIND.DISCUSSION_THREAD}
<!-- Route kind 11 (discussion threads) to DiscussionView -->
<DiscussionView threadId={decodedEventId} />

22
src/routes/feed/+page.svelte

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
<script lang="ts">
import Header from '../../lib/components/layout/Header.svelte';
import PageHeader from '../../lib/components/layout/PageHeader.svelte';
import FeedPage from '../../lib/modules/feed/FeedPage.svelte';
import { nostrClient } from '../../lib/services/nostr/nostr-client.js';
import { onMount } from 'svelte';
@ -13,8 +14,17 @@ @@ -13,8 +14,17 @@
hasMoreEvents: boolean;
waitingRoomEvents: NostrEvent[];
loadWaitingRoomEvents: () => void;
refresh?: () => Promise<void>;
} | null = $state(null);
async function handleRefresh() {
if (feedPageComponent?.refresh) {
await feedPageComponent.refresh();
} else {
window.location.reload();
}
}
let showOnlyOPs = $state<boolean>(false);
let preferenceLoaded = $state(false);
@ -49,10 +59,8 @@ @@ -49,10 +59,8 @@
<main class="container mx-auto px-4 py-8">
<div class="feed-content">
<PageHeader title="/Feed" onRefresh={handleRefresh} refreshLoading={feedPageComponent?.loadingMore || false} />
<div class="feed-header mb-6">
<div class="feed-header-top">
<h1 class="font-bold text-fog-text dark:text-fog-dark-text font-mono" style="font-size: 1.5em;">/Feed</h1>
</div>
<div class="feed-controls">
<div class="feed-filter">
<label class="feed-filter-label">
@ -106,13 +114,7 @@ @@ -106,13 +114,7 @@
gap: 1rem;
padding: 0 1rem;
margin-bottom: 1rem;
}
.feed-header-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
margin-top: -1rem;
}
.feed-controls {

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

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
<script lang="ts">
import Header from '../../../../lib/components/layout/Header.svelte';
import PageHeader from '../../../../lib/components/layout/PageHeader.svelte';
import FeedPage from '../../../../lib/modules/feed/FeedPage.svelte';
import UnifiedSearch from '../../../../lib/components/layout/UnifiedSearch.svelte';
import RelayInfo from '../../../../lib/components/relay/RelayInfo.svelte';
@ -83,6 +84,7 @@ @@ -83,6 +84,7 @@
<main class="container mx-auto px-4 py-8">
<div class="relay-feed-content">
<PageHeader title={decodedRelay ? `Relay: ${decodedRelay}` : 'Relay Feed'} />
<div class="search-section mb-6">
<UnifiedSearch mode="search" />
</div>

8
src/routes/find/+page.svelte

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
<script lang="ts">
import Header from '../../lib/components/layout/Header.svelte';
import PageHeader from '../../lib/components/layout/PageHeader.svelte';
import NormalSearch from '../../lib/components/find/NormalSearch.svelte';
import AdvancedSearch from '../../lib/components/find/AdvancedSearch.svelte';
import SearchAddressableEvents from '../../lib/components/find/SearchAddressableEvents.svelte';
@ -134,8 +135,8 @@ @@ -134,8 +135,8 @@
<main class="container mx-auto px-4 py-8">
<div class="find-page">
<PageHeader title="/Find" />
<div class="page-header">
<h1 class="page-title font-bold mb-4 text-fog-text dark:text-fog-dark-text font-mono">/Find</h1>
{#if hasActiveSearch}
<button
class="clear-button"
@ -267,6 +268,7 @@ @@ -267,6 +268,7 @@
align-items: center;
margin-bottom: 1rem;
gap: 1rem;
margin-top: -1rem;
}
@media (min-width: 640px) {
@ -275,10 +277,6 @@ @@ -275,10 +277,6 @@
}
}
.page-title {
font-size: 1.5em;
}
.clear-button {
padding: 0.5rem 1rem;
background: var(--fog-highlight, #f3f4f6);

3
src/routes/highlights/+page.svelte

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
<script lang="ts">
import Header from '../../lib/components/layout/Header.svelte';
import PageHeader from '../../lib/components/layout/PageHeader.svelte';
import HighlightCard from '../../lib/modules/feed/HighlightCard.svelte';
import UnifiedSearch from '../../lib/components/layout/UnifiedSearch.svelte';
import ProfileBadge from '../../lib/components/layout/ProfileBadge.svelte';
@ -222,7 +223,7 @@ @@ -222,7 +223,7 @@
<main class="container mx-auto px-4 py-8">
<div class="highlights-page">
<h1 class="font-bold text-fog-text dark:text-fog-dark-text font-mono mb-6" style="font-size: 1.5em;">/Highlights</h1>
<PageHeader title="/Highlights" />
{#if loading}
<div class="loading-state">

7
src/routes/lists/+page.svelte

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
<script lang="ts">
import Header from '../../lib/components/layout/Header.svelte';
import PageHeader from '../../lib/components/layout/PageHeader.svelte';
import FeedPost from '../../lib/modules/feed/FeedPost.svelte';
import { sessionManager } from '../../lib/services/auth/session-manager.js';
import { nostrClient } from '../../lib/services/nostr/nostr-client.js';
@ -384,16 +385,15 @@ @@ -384,16 +385,15 @@
</div>
{:else if !hasLists}
<div class="text-center py-8">
<h1 class="font-bold text-fog-text dark:text-fog-dark-text font-mono mb-4" style="font-size: 1.5em;">/Lists</h1>
<PageHeader title="/Lists" />
<p>You don't have any lists yet.</p>
<p class="text-sm text-fog-text-light dark:text-fog-dark-text-light mt-2">
Create a kind 3 (contacts) or kind 30000 (follow_set) event to get started.
</p>
</div>
{:else}
<PageHeader title="/Lists" />
<div class="lists-header mb-6">
<h1 class="font-bold text-fog-text dark:text-fog-dark-text font-mono mb-4" style="font-size: 1.5em;">/Lists</h1>
<div class="list-selector mb-4">
<label for="list-select" class="block text-sm font-medium text-fog-text dark:text-fog-dark-text mb-2">
Select a list:
@ -454,6 +454,7 @@ @@ -454,6 +454,7 @@
.lists-header {
border-bottom: 1px solid var(--fog-border, #e5e7eb);
padding-bottom: 1rem;
margin-top: -1rem;
}
:global(.dark) .lists-header {

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

@ -1,8 +1,15 @@ @@ -1,8 +1,15 @@
<script lang="ts">
import Header from '../../../lib/components/layout/Header.svelte';
import PageHeader from '../../../lib/components/layout/PageHeader.svelte';
import ProfilePage from '../../../lib/modules/profiles/ProfilePage.svelte';
import { nostrClient } from '../../../lib/services/nostr/nostr-client.js';
import { onMount } from 'svelte';
import { page } from '$app/stores';
import { nip19 } from 'nostr-tools';
async function handleRefresh() {
window.location.reload();
}
onMount(async () => {
await nostrClient.initialize();
@ -12,5 +19,6 @@ @@ -12,5 +19,6 @@
<Header />
<main class="container mx-auto px-2 sm:px-4 py-4 sm:py-8">
<PageHeader title="Profile" onRefresh={handleRefresh} />
<ProfilePage />
</main>

3
src/routes/relay/+page.svelte

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
<script lang="ts">
import Header from '../../lib/components/layout/Header.svelte';
import PageHeader from '../../lib/components/layout/PageHeader.svelte';
import { nostrClient } from '../../lib/services/nostr/nostr-client.js';
import { relayManager } from '../../lib/services/nostr/relay-manager.js';
import { config } from '../../lib/services/nostr/config.js';
@ -281,7 +282,7 @@ @@ -281,7 +282,7 @@
<main class="container mx-auto px-4 py-8">
<div class="relay-page">
<h1 class="font-bold text-fog-text dark:text-fog-dark-text font-mono mb-6" style="font-size: 1.5em;">/Relay</h1>
<PageHeader title="/Relay" onRefresh={loadRelays} refreshLoading={loading} />
<!-- Custom Relay Input -->
<section class="relay-category custom-relay-section">

17
src/routes/repos/+page.svelte

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
<script lang="ts">
import Header from '../../lib/components/layout/Header.svelte';
import PageHeader from '../../lib/components/layout/PageHeader.svelte';
import UnifiedSearch from '../../lib/components/layout/UnifiedSearch.svelte';
import FeedPost from '../../lib/modules/feed/FeedPost.svelte';
import ProfileBadge from '../../lib/components/layout/ProfileBadge.svelte';
@ -505,14 +506,12 @@ @@ -505,14 +506,12 @@
<main class="container mx-auto px-4 py-8">
<div class="repos-content">
<PageHeader title="/Repos" onRefresh={loadRepos} refreshLoading={loading} />
<p class="repos-description text-fog-text-light dark:text-fog-dark-text-light mb-4">
Discover and explore repositories announced on Nostr
</p>
<div class="repos-header mb-6">
<div class="repos-header-top">
<div>
<h1 class="font-bold text-fog-text dark:text-fog-dark-text font-mono mb-6" style="font-size: 1.5em;">/Repos</h1>
<p class="text-fog-text-light dark:text-fog-dark-text-light mt-2 mb-4">
Discover and explore repositories announced on Nostr
</p>
</div>
{#if isLoggedIn}
<label class="my-repos-checkbox">
<input
@ -647,6 +646,12 @@ @@ -647,6 +646,12 @@
margin: 0 auto;
}
.repos-description {
padding: 0 1rem;
margin-top: -1rem;
margin-bottom: 1rem;
}
.repos-header {
padding: 0 1rem;
border-bottom: 1px solid var(--fog-border, #e5e7eb);

5
src/routes/repos/[naddr]/+page.svelte

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
<script lang="ts">
import Header from '../../../lib/components/layout/Header.svelte';
import PageHeader from '../../../lib/components/layout/PageHeader.svelte';
import { nostrClient } from '../../../lib/services/nostr/nostr-client.js';
import { relayManager } from '../../../lib/services/nostr/relay-manager.js';
import { onMount } from 'svelte';
@ -1095,6 +1096,7 @@ @@ -1095,6 +1096,7 @@
<img src={getBannerUrl()!} alt="{getRepoName()} banner" class="repo-banner" />
</div>
{/if}
<PageHeader title={getRepoName()} onRefresh={loadRepo} refreshLoading={loading || loadingRepo} />
<div class="repo-header mb-6">
<div class="repo-header-top">
{#if getImageUrl()}
@ -1104,9 +1106,6 @@ @@ -1104,9 +1106,6 @@
{/if}
<div class="repo-title-section">
<div class="repo-title-row">
<h1 class="font-bold text-fog-text dark:text-fog-dark-text font-mono mb-6" style="font-size: 1.5em;">
{getRepoName()}
</h1>
{#if repoEvent}
<EventMenu event={repoEvent} showContentActions={true} />
{/if}

3
src/routes/rss/+page.svelte

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
<script lang="ts">
import Header from '../../lib/components/layout/Header.svelte';
import PageHeader from '../../lib/components/layout/PageHeader.svelte';
import { sessionManager } from '../../lib/services/auth/session-manager.js';
import { nostrClient } from '../../lib/services/nostr/nostr-client.js';
import { relayManager } from '../../lib/services/nostr/relay-manager.js';
@ -363,7 +364,7 @@ @@ -363,7 +364,7 @@
<main class="container mx-auto px-4 py-8">
<div class="rss-page">
<h1 class="font-bold text-fog-text dark:text-fog-dark-text font-mono mb-6" style="font-size: 1.5em;">/RSS</h1>
<PageHeader title="/RSS" onRefresh={async () => { await checkRssEvent(); if (subscribedFeeds.length > 0) await loadRssFeeds(); }} refreshLoading={loading || loadingFeeds} />
{#if loading}
<p class="text-fog-text dark:text-fog-dark-text">Loading...</p>

56
src/routes/settings/+page.svelte

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
<script lang="ts">
import Header from '../../lib/components/layout/Header.svelte';
import PageHeader from '../../lib/components/layout/PageHeader.svelte';
import { onMount } from 'svelte';
import { hasExpiringEventsEnabled } from '../../lib/services/event-expiration.js';
import { shouldIncludeClientTag, setIncludeClientTag } from '../../lib/services/client-tag-preference.js';
@ -205,13 +206,6 @@ @@ -205,13 +206,6 @@
setIncludeClientTag(includeClientTag);
}
function handleBack() {
if (typeof window !== 'undefined' && window.history.length > 1) {
window.history.back();
} else {
goto('/');
}
}
async function loadApiKeyStates() {
for (const { type } of apiKeyTypes) {
@ -346,17 +340,7 @@ @@ -346,17 +340,7 @@
<main class="container mx-auto px-4 py-8">
<div class="settings-page">
<div class="settings-header">
<h1 class="font-bold text-fog-text dark:text-fog-dark-text font-mono mb-6" style="font-size: 1.5em;">/Settings</h1>
<button
onclick={handleBack}
class="back-button flex items-center gap-2"
aria-label="Go back to previous page"
>
<Icon name="arrow-left" size={16} />
<span>Back</span>
</button>
</div>
<PageHeader title="/Settings" />
<div class="space-y-6">
<!-- About -->
@ -742,42 +726,6 @@ @@ -742,42 +726,6 @@
padding: 0;
}
.settings-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1.5rem;
}
.back-button {
padding: 0.5rem 1rem;
border: 1px solid var(--fog-border, #cbd5e1);
border-radius: 4px;
background: var(--fog-post, #ffffff);
color: var(--fog-text, #1e293b);
cursor: pointer;
transition: all 0.2s;
font-size: 0.875em;
white-space: nowrap;
}
.back-button:hover {
background: var(--fog-highlight, #f1f5f9);
border-color: var(--fog-accent, #94a3b8);
}
:global(.dark) .back-button {
background: var(--fog-dark-post, #334155);
border-color: var(--fog-dark-border, #475569);
color: var(--fog-dark-text, #f1f5f9);
}
:global(.dark) .back-button:hover {
background: var(--fog-dark-highlight, #475569);
border-color: var(--fog-dark-accent, #64748b);
}
.preference-section {
margin-bottom: 0;
}

3
src/routes/topics/+page.svelte

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
<script lang="ts">
import Header from '../../lib/components/layout/Header.svelte';
import PageHeader from '../../lib/components/layout/PageHeader.svelte';
import UnifiedSearch from '../../lib/components/layout/UnifiedSearch.svelte';
import FeedPost from '../../lib/modules/feed/FeedPost.svelte';
import ProfileBadge from '../../lib/components/layout/ProfileBadge.svelte';
@ -240,7 +241,7 @@ @@ -240,7 +241,7 @@
<main class="container mx-auto px-4 py-8">
<div class="topics-page">
<h1 class="font-bold text-fog-text dark:text-fog-dark-text font-mono mb-6" style="font-size: 1.5em;">/Topics</h1>
<PageHeader title="/Topics" />
{#if loading}
<p class="text-fog-text dark:text-fog-dark-text">Loading topics...</p>

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

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
<script lang="ts">
import Header from '../../../lib/components/layout/Header.svelte';
import PageHeader from '../../../lib/components/layout/PageHeader.svelte';
import FeedPost from '../../../lib/modules/feed/FeedPost.svelte';
import { nostrClient } from '../../../lib/services/nostr/nostr-client.js';
import { relayManager } from '../../../lib/services/nostr/relay-manager.js';
@ -157,19 +158,15 @@ @@ -157,19 +158,15 @@
<main class="topic-main container mx-auto px-2 sm:px-4 py-8">
<div class="topic-content">
<div class="topic-header mb-6">
<h1 class="font-bold text-fog-text dark:text-fog-dark-text" style="font-size: 1.5em;">
Topic: #{topicName}
</h1>
<p class="text-fog-text-light dark:text-fog-dark-text-light mt-2">
{events.length} {events.length === 1 ? 'event' : 'events'} found
{#if totalPages > 1}
<span class="text-fog-text-light dark:text-fog-dark-text-light">
(Page {currentPage} of {totalPages})
</span>
{/if}
</p>
</div>
<PageHeader title={`Topic: #${topicName}`} />
<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}
<span class="text-fog-text-light dark:text-fog-dark-text-light">
(Page {currentPage} of {totalPages})
</span>
{/if}
</p>
{#if loading}
<div class="loading-state">
@ -249,17 +246,10 @@ @@ -249,17 +246,10 @@
}
}
.topic-header {
.topic-description {
padding: 0 1rem;
border-bottom: 1px solid var(--fog-border, #e5e7eb);
padding-bottom: 1rem;
margin-top: -1rem;
margin-bottom: 1rem;
overflow: hidden;
width: 100%;
max-width: 100%;
box-sizing: border-box;
word-break: break-word;
overflow-wrap: anywhere;
}
@media (max-width: 640px) {

5
src/routes/write/+page.svelte

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
<script lang="ts">
import Header from '../../lib/components/layout/Header.svelte';
import PageHeader from '../../lib/components/layout/PageHeader.svelte';
import CreateEventForm from '../../lib/components/write/CreateEventForm.svelte';
import { nostrClient } from '../../lib/services/nostr/nostr-client.js';
import { sessionManager } from '../../lib/services/auth/session-manager.js';
@ -112,9 +113,7 @@ @@ -112,9 +113,7 @@
<main class="container mx-auto px-4 py-8">
<div class="write-page">
<h1 class="font-bold mb-6 text-fog-text dark:text-fog-dark-text font-mono" style="font-size: 1.5em;">
{isCloneMode ? '/Write: Edit or Clone an event' : '/Write'}
</h1>
<PageHeader title={isCloneMode ? '/Write: Edit or Clone an event' : '/Write'} />
{#if !isLoggedIn}
<div class="login-prompt">

6
static/icons/refresh.svg

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/>
<path d="M21 3v5h-5"/>
<path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/>
<path d="M3 21v-5h5"/>
</svg>

After

Width:  |  Height:  |  Size: 370 B

Loading…
Cancel
Save