mirror of
https://github.com/hoornet/vega.git
synced 2026-05-06 12:19:11 -07:00
Replace themes (Sepia, Nord Frost), batch bookmark fetch, debounce bookmark publish
- Replace Solarized (low contrast) with Sepia (warm coffee tones) - Replace Tokyo Night with Nord Frost (cool blue-grey, brighter) - Bookmark fetch uses single batch filter instead of one-by-one (much faster) - Debounce bookmark publish to prevent race conditions with replaceable events
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
||||
import { NDKEvent, NDKFilter } from "@nostr-dev-kit/ndk";
|
||||
import { useBookmarkStore } from "../../stores/bookmark";
|
||||
import { useUserStore } from "../../stores/user";
|
||||
import { fetchNoteById, fetchByAddr, getNDK } from "../../lib/nostr";
|
||||
import { fetchNoteById, fetchByAddr, getNDK, fetchWithTimeout } from "../../lib/nostr";
|
||||
import { dbLoadBookmarkedNotes, dbSaveBookmarkedNotes } from "../../lib/db";
|
||||
import { NoteCard } from "../feed/NoteCard";
|
||||
import { ArticleCard } from "../article/ArticleCard";
|
||||
@@ -83,13 +83,25 @@ export function BookmarkView() {
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Background: fetch from relays and merge
|
||||
// 2) Background: batch fetch from relays (single filter is much faster than one-by-one)
|
||||
try {
|
||||
const results = await Promise.all(
|
||||
bookmarkedIds.map((id) => fetchNoteById(id))
|
||||
);
|
||||
const batchSize = 50;
|
||||
const allFetched: NDKEvent[] = [];
|
||||
for (let i = 0; i < bookmarkedIds.length; i += batchSize) {
|
||||
const batch = bookmarkedIds.slice(i, i + batchSize);
|
||||
const filter: NDKFilter = { ids: batch };
|
||||
const events = await fetchWithTimeout(getNDK(), filter, 10000);
|
||||
allFetched.push(...Array.from(events));
|
||||
}
|
||||
// Fallback: try individual fetch for any IDs not found in batch
|
||||
const foundIds = new Set(allFetched.map((e) => e.id));
|
||||
const missing = bookmarkedIds.filter((id) => !foundIds.has(id));
|
||||
if (missing.length > 0 && missing.length <= 10) {
|
||||
const fallback = await Promise.all(missing.map((id) => fetchNoteById(id)));
|
||||
allFetched.push(...fallback.filter((e): e is NDKEvent => e !== null));
|
||||
}
|
||||
if (!cancelled) {
|
||||
const fetched = results.filter((e): e is NDKEvent => e !== null);
|
||||
const fetched = allFetched;
|
||||
// Separate articles (kind 30023) bookmarked via e-tag from notes
|
||||
const fetchedNotes = fetched.filter((e) => e.kind !== 30023);
|
||||
const articlesFromETag = fetched.filter((e) => e.kind === 30023);
|
||||
|
||||
@@ -83,23 +83,23 @@ export const themes: Theme[] = [
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "tokyo-night",
|
||||
name: "Tokyo Night",
|
||||
id: "sepia",
|
||||
name: "Sepia",
|
||||
colors: {
|
||||
bg: "#1a1b26",
|
||||
"bg-raised": "#24283b",
|
||||
"bg-hover": "#292e42",
|
||||
border: "#3b4261",
|
||||
"border-subtle": "#292e42",
|
||||
text: "#a9b1d6",
|
||||
"text-muted": "#565f89",
|
||||
"text-dim": "#3b4261",
|
||||
accent: "#7aa2f7",
|
||||
"accent-hover": "#89b4fa",
|
||||
zap: "#e0af68",
|
||||
danger: "#f7768e",
|
||||
warning: "#e0af68",
|
||||
success: "#9ece6a",
|
||||
bg: "#2b2018",
|
||||
"bg-raised": "#382a1f",
|
||||
"bg-hover": "#453527",
|
||||
border: "#5a4636",
|
||||
"border-subtle": "#382a1f",
|
||||
text: "#e8d5c4",
|
||||
"text-muted": "#b89c84",
|
||||
"text-dim": "#7a6452",
|
||||
accent: "#e09850",
|
||||
"accent-hover": "#c47f3a",
|
||||
zap: "#f0c040",
|
||||
danger: "#d45040",
|
||||
warning: "#e0a040",
|
||||
success: "#7ab860",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -123,23 +123,23 @@ export const themes: Theme[] = [
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "ethereal",
|
||||
name: "Ethereal",
|
||||
id: "nord",
|
||||
name: "Nord Frost",
|
||||
colors: {
|
||||
bg: "#1a1a2e",
|
||||
"bg-raised": "#16213e",
|
||||
"bg-hover": "#1f2f50",
|
||||
border: "#2a3a5c",
|
||||
"border-subtle": "#1f2f50",
|
||||
text: "#dfe6e9",
|
||||
"text-muted": "#a0aec0",
|
||||
"text-dim": "#5a6a8a",
|
||||
accent: "#a29bfe",
|
||||
"accent-hover": "#6c5ce7",
|
||||
zap: "#ffeaa7",
|
||||
danger: "#ff7675",
|
||||
warning: "#ffeaa7",
|
||||
success: "#55efc4",
|
||||
bg: "#2e3440",
|
||||
"bg-raised": "#3b4252",
|
||||
"bg-hover": "#434c5e",
|
||||
border: "#4c566a",
|
||||
"border-subtle": "#3b4252",
|
||||
text: "#eceff4",
|
||||
"text-muted": "#d8dee9",
|
||||
"text-dim": "#7b88a1",
|
||||
accent: "#88c0d0",
|
||||
"accent-hover": "#81a1c1",
|
||||
zap: "#ebcb8b",
|
||||
danger: "#bf616a",
|
||||
warning: "#ebcb8b",
|
||||
success: "#a3be8c",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
import { create } from "zustand";
|
||||
import { fetchBookmarkList, fetchBookmarkListFull, publishBookmarkListFull } from "../lib/nostr";
|
||||
|
||||
// Debounce bookmark publishing to avoid race conditions with replaceable events
|
||||
let publishTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
function debouncedPublish(get: () => BookmarkState) {
|
||||
if (publishTimer) clearTimeout(publishTimer);
|
||||
publishTimer = setTimeout(() => {
|
||||
const { bookmarkedIds, bookmarkedArticleAddrs } = get();
|
||||
publishBookmarkListFull(bookmarkedIds, bookmarkedArticleAddrs).catch(() => {});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
const STORAGE_KEY = "wrystr_bookmarks";
|
||||
const ARTICLE_STORAGE_KEY = "wrystr_bookmarks_articles";
|
||||
const READ_STORAGE_KEY = "wrystr_articles_read";
|
||||
@@ -87,20 +97,19 @@ export const useBookmarkStore = create<BookmarkState>((set, get) => ({
|
||||
},
|
||||
|
||||
addBookmark: async (eventId: string) => {
|
||||
const { bookmarkedIds, bookmarkedArticleAddrs } = get();
|
||||
const { bookmarkedIds } = get();
|
||||
if (bookmarkedIds.includes(eventId)) return;
|
||||
const updated = [...bookmarkedIds, eventId];
|
||||
set({ bookmarkedIds: updated });
|
||||
saveLocal(updated);
|
||||
publishBookmarkListFull(updated, bookmarkedArticleAddrs).catch(() => {});
|
||||
debouncedPublish(get);
|
||||
},
|
||||
|
||||
removeBookmark: async (eventId: string) => {
|
||||
const { bookmarkedArticleAddrs } = get();
|
||||
const updated = get().bookmarkedIds.filter((id) => id !== eventId);
|
||||
set({ bookmarkedIds: updated });
|
||||
saveLocal(updated);
|
||||
publishBookmarkListFull(updated, bookmarkedArticleAddrs).catch(() => {});
|
||||
debouncedPublish(get);
|
||||
},
|
||||
|
||||
isBookmarked: (eventId: string) => {
|
||||
@@ -108,20 +117,19 @@ export const useBookmarkStore = create<BookmarkState>((set, get) => ({
|
||||
},
|
||||
|
||||
addArticleBookmark: async (addr: string) => {
|
||||
const { bookmarkedIds, bookmarkedArticleAddrs } = get();
|
||||
const { bookmarkedArticleAddrs } = get();
|
||||
if (bookmarkedArticleAddrs.includes(addr)) return;
|
||||
const updated = [...bookmarkedArticleAddrs, addr];
|
||||
set({ bookmarkedArticleAddrs: updated });
|
||||
saveArticleAddrs(updated);
|
||||
publishBookmarkListFull(bookmarkedIds, updated).catch(() => {});
|
||||
debouncedPublish(get);
|
||||
},
|
||||
|
||||
removeArticleBookmark: async (addr: string) => {
|
||||
const { bookmarkedIds } = get();
|
||||
const updated = get().bookmarkedArticleAddrs.filter((a) => a !== addr);
|
||||
set({ bookmarkedArticleAddrs: updated });
|
||||
saveArticleAddrs(updated);
|
||||
publishBookmarkListFull(bookmarkedIds, updated).catch(() => {});
|
||||
debouncedPublish(get);
|
||||
},
|
||||
|
||||
isArticleBookmarked: (addr: string) => {
|
||||
|
||||
Reference in New Issue
Block a user