Browse Source

gif picker

get rid of grayscale on images
master
Silberengel 1 month ago
parent
commit
d374ba024b
  1. 15
      src/app.css
  2. 2
      src/lib/components/content/EmojiPicker.svelte
  3. 70
      src/lib/components/content/GifPicker.svelte
  4. 7
      src/lib/components/layout/ProfileBadge.svelte
  5. 5
      src/lib/modules/comments/CommentForm.svelte
  6. 22
      src/lib/modules/reactions/FeedReactionButtons.svelte
  7. 24
      src/lib/services/nostr/gif-service.ts
  8. 9
      src/lib/services/nostr/nip30-emoji.ts
  9. 42
      src/lib/services/nostr/nostr-client.ts

15
src/app.css

@ -92,7 +92,7 @@ body { @@ -92,7 +92,7 @@ body {
background-color: #1e293b;
}
/* Anon aesthetic: Pure gray to slightly bluish tints for profile pics */
/* Profile pictures - display in full color */
/* Profile pictures - all instances */
img.profile-picture,
.profile-badge img,
@ -101,18 +101,7 @@ img[alt*="profile" i], @@ -101,18 +101,7 @@ img[alt*="profile" i],
img[alt*="avatar" i],
img[src*="avatar" i],
img[src*="profile" i] {
filter: grayscale(100%) sepia(10%) hue-rotate(200deg) saturate(30%) !important;
transition: filter 0.3s ease;
}
.dark img.profile-picture,
.dark .profile-badge img,
.dark .profile-picture,
.dark img[alt*="profile" i],
.dark img[alt*="avatar" i],
.dark img[src*="avatar" i],
.dark img[src*="profile" i] {
filter: grayscale(100%) sepia(12%) hue-rotate(200deg) saturate(35%) !important;
/* No grayscale filter - profile pictures should be in full color */
}
/* Emoji images - no grayscale filter, display in full color */

2
src/lib/components/content/EmojiPicker.svelte

@ -45,7 +45,7 @@ @@ -45,7 +45,7 @@
try {
await loadAllEmojiPacks();
const allEmojis = getAllCustomEmojis();
console.log(`[EmojiPicker] Loaded ${allEmojis.length} custom emojis`);
console.debug(`[EmojiPicker] Loaded ${allEmojis.length} custom emojis`);
customEmojis = allEmojis;
} catch (error) {
console.error('Error loading custom emojis:', error);

70
src/lib/components/content/GifPicker.svelte

@ -15,21 +15,32 @@ @@ -15,21 +15,32 @@
let searchQuery = $state('');
let searchInput: HTMLInputElement | null = $state(null);
let selectedGif: GifMetadata | null = $state(null);
let error: string | null = $state(null);
// Debounce search
let searchTimeout: ReturnType<typeof setTimeout> | null = null;
async function loadGifs(query?: string) {
loading = true;
error = null;
try {
console.log('[GifPicker] Loading GIFs, query:', query || 'none');
let results: GifMetadata[];
if (query && query.trim()) {
gifs = await searchGifs(query.trim(), 50);
results = await searchGifs(query.trim(), 50);
} else {
gifs = await fetchGifs(undefined, 50);
results = await fetchGifs(undefined, 50);
}
console.log('[GifPicker] Loaded', results.length, 'GIFs');
gifs = results;
if (results.length === 0 && !query) {
error = 'No GIFs found. Try searching for a specific term, or there may be no GIF events on the relays.';
}
} catch (error) {
console.error('[GifPicker] Error loading GIFs:', error);
const errorMessage = error instanceof Error ? error.message : String(error);
gifs = [];
error = `Failed to load GIFs: ${errorMessage}`;
} finally {
loading = false;
}
@ -118,12 +129,22 @@ @@ -118,12 +129,22 @@
<div class="gif-picker-content">
{#if loading}
<div class="gif-loading">Loading GIFs...</div>
{:else if error}
<div class="gif-error">
<p>{error}</p>
<button onclick={() => loadGifs(searchQuery)} class="retry-button">
Retry
</button>
</div>
{:else if gifs.length === 0}
<div class="gif-empty">
{#if searchQuery}
No GIFs found for "{searchQuery}"
<p>No GIFs found for "{searchQuery}"</p>
<p class="gif-hint">Try a different search term. The relays were queried but returned no matching kind 94 (NIP94) GIF events.</p>
{:else}
No GIFs available
<p>No GIFs available</p>
<p class="gif-hint">The relays were queried successfully, but no kind 94 (NIP94) GIF events were found. This means there are currently no GIFs published as NIP94 file attachments on the connected relays.</p>
<p class="gif-hint">You can try searching for a specific term, or the relays may not have any GIF events available at this time.</p>
{/if}
</div>
{:else}
@ -139,6 +160,11 @@ @@ -139,6 +160,11 @@
alt="GIF"
loading="lazy"
class="gif-thumbnail"
onerror={(e) => {
console.warn('[GifPicker] Failed to load image:', gif.url);
const target = e.target as HTMLImageElement;
target.style.display = 'none';
}}
/>
</button>
{/each}
@ -297,7 +323,8 @@ @@ -297,7 +323,8 @@
}
.gif-loading,
.gif-empty {
.gif-empty,
.gif-error {
text-align: center;
padding: 2rem;
color: var(--fog-text-light, #9ca3af);
@ -305,10 +332,41 @@ @@ -305,10 +332,41 @@
}
:global(.dark) .gif-loading,
:global(.dark) .gif-empty {
:global(.dark) .gif-empty,
:global(.dark) .gif-error {
color: var(--fog-dark-text-light, #6b7280);
}
.gif-hint {
margin-top: 0.5rem;
font-size: 0.75rem;
opacity: 0.8;
}
.gif-error {
color: var(--fog-error, #dc2626);
}
:global(.dark) .gif-error {
color: var(--fog-dark-error, #ef4444);
}
.retry-button {
margin-top: 1rem;
padding: 0.5rem 1rem;
background: var(--fog-accent, #64748b);
color: white;
border: none;
border-radius: 0.375rem;
cursor: pointer;
font-size: 0.875rem;
transition: opacity 0.2s;
}
.retry-button:hover {
opacity: 0.9;
}
.gif-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));

7
src/lib/components/layout/ProfileBadge.svelte

@ -104,7 +104,7 @@ @@ -104,7 +104,7 @@
{:else}
<div
class="profile-placeholder w-6 h-6 rounded flex-shrink-0 flex items-center justify-center text-xs font-semibold"
style="background: {avatarColor}; color: white; filter: grayscale(100%) sepia(10%) hue-rotate(200deg) saturate(30%);"
style="background: {avatarColor}; color: white;"
title={pubkey}
>
{avatarInitials}
@ -122,13 +122,12 @@ @@ -122,13 +122,12 @@
text-decoration: none;
color: inherit;
max-width: 100%;
filter: grayscale(100%) opacity(0.7);
transition: filter 0.2s;
transition: opacity 0.2s;
}
.profile-badge:hover {
text-decoration: underline;
filter: grayscale(100%) opacity(0.9);
opacity: 0.9;
}
.profile-picture {

5
src/lib/modules/comments/CommentForm.svelte

@ -191,7 +191,10 @@ @@ -191,7 +191,10 @@
<div class="textarea-buttons">
<button
type="button"
onclick={() => { showGifPicker = !showGifPicker; showEmojiPicker = false; }}
onclick={() => {
showGifPicker = !showGifPicker;
showEmojiPicker = false;
}}
class="toolbar-button"
title="Insert GIF"
aria-label="Insert GIF"

22
src/lib/modules/reactions/FeedReactionButtons.svelte

@ -71,7 +71,7 @@ @@ -71,7 +71,7 @@
// Handle real-time updates - process reactions when new ones arrive
async function handleReactionUpdate(updated: NostrEvent[]) {
console.log(`[FeedReactionButtons] Received reaction update for event ${event.id.substring(0, 16)}...:`, {
console.debug(`[FeedReactionButtons] Received reaction update for event ${event.id.substring(0, 16)}...:`, {
count: updated.length,
events: updated.map(r => ({
id: r.id.substring(0, 16) + '...',
@ -103,8 +103,8 @@ @@ -103,8 +103,8 @@
// Use getProfileReadRelays() to include defaultRelays + profileRelays + user inbox + localRelays
// This ensures we get all reactions from the complete relay set, matching ThreadList behavior
const reactionRelays = relayManager.getProfileReadRelays();
console.log(`[FeedReactionButtons] Loading reactions for event ${event.id.substring(0, 16)}... (kind ${event.kind})`);
console.log(`[FeedReactionButtons] Using relays:`, reactionRelays);
console.debug(`[FeedReactionButtons] Loading reactions for event ${event.id.substring(0, 16)}... (kind ${event.kind})`);
console.debug(`[FeedReactionButtons] Using relays:`, reactionRelays);
// Clear and rebuild reactions map for this event
allReactionsMap.clear();
@ -120,7 +120,7 @@ @@ -120,7 +120,7 @@
{ useCache: true, cacheResults: true, onUpdate: handleReactionUpdate }
);
console.log(`[FeedReactionButtons] Reactions fetched:`, {
console.debug(`[FeedReactionButtons] Reactions fetched:`, {
eventId: event.id.substring(0, 16) + '...',
kind: event.kind,
withLowerE: reactionsWithLowerE.length,
@ -150,7 +150,7 @@ @@ -150,7 +150,7 @@
}
const reactionEvents = Array.from(allReactionsMap.values());
console.log(`[FeedReactionButtons] All reactions (deduplicated):`, {
console.debug(`[FeedReactionButtons] All reactions (deduplicated):`, {
total: reactionEvents.length,
events: reactionEvents.map(r => ({
id: r.id.substring(0, 16) + '...',
@ -164,7 +164,7 @@ @@ -164,7 +164,7 @@
// Filter out deleted reactions (kind 5)
const filteredReactions = await filterDeletedReactions(reactionEvents);
console.log(`[FeedReactionButtons] After filtering deleted reactions:`, {
console.debug(`[FeedReactionButtons] After filtering deleted reactions:`, {
before: reactionEvents.length,
after: filteredReactions.length,
filtered: reactionEvents.length - filteredReactions.length,
@ -196,7 +196,7 @@ @@ -196,7 +196,7 @@
{ useCache: true }
);
console.log(`[FeedReactionButtons] Deletion events fetched:`, {
console.debug(`[FeedReactionButtons] Deletion events fetched:`, {
count: deletionEvents.length,
events: deletionEvents.map(d => ({
id: d.id.substring(0, 16) + '...',
@ -221,7 +221,7 @@ @@ -221,7 +221,7 @@
}
}
console.log(`[FeedReactionButtons] Deleted reaction IDs by pubkey:`,
console.debug(`[FeedReactionButtons] Deleted reaction IDs by pubkey:`,
Array.from(deletedReactionIdsByPubkey.entries()).map(([pubkey, ids]) => ({
pubkey: pubkey.substring(0, 16) + '...',
deletedIds: Array.from(ids).map(id => id.substring(0, 16) + '...')
@ -233,7 +233,7 @@ @@ -233,7 +233,7 @@
const deletedIds = deletedReactionIdsByPubkey.get(reaction.pubkey);
const isDeleted = deletedIds && deletedIds.has(reaction.id);
if (isDeleted) {
console.log(`[FeedReactionButtons] Filtering out deleted reaction:`, {
console.debug(`[FeedReactionButtons] Filtering out deleted reaction:`, {
id: reaction.id.substring(0, 16) + '...',
pubkey: reaction.pubkey.substring(0, 16) + '...',
content: reaction.content,
@ -247,7 +247,7 @@ @@ -247,7 +247,7 @@
}
async function processReactions(reactionEvents: NostrEvent[]) {
console.log(`[FeedReactionButtons] Processing ${reactionEvents.length} reactions for event ${event.id.substring(0, 16)}... (kind ${event.kind})`);
console.debug(`[FeedReactionButtons] Processing ${reactionEvents.length} reactions for event ${event.id.substring(0, 16)}... (kind ${event.kind})`);
const reactionMap = new Map<string, { content: string; pubkeys: Set<string>; eventIds: Map<string, string> }>();
const currentUser = sessionManager.getCurrentPubkey();
let skippedInvalid = 0;
@ -287,7 +287,7 @@ @@ -287,7 +287,7 @@
}
}
console.log(`[FeedReactionButtons] Processed reactions summary:`, {
console.debug(`[FeedReactionButtons] Processed reactions summary:`, {
totalReactions: reactionEvents.length,
skippedInvalid,
reactionCounts: Array.from(reactionMap.entries()).map(([content, data]) => ({

24
src/lib/services/nostr/gif-service.ts

@ -106,6 +106,7 @@ export async function fetchGifs(searchQuery?: string, limit: number = 50): Promi @@ -106,6 +106,7 @@ export async function fetchGifs(searchQuery?: string, limit: number = 50): Promi
try {
// Use profile read relays to get GIFs
const relays = relayManager.getProfileReadRelays();
console.debug(`[gif-service] Fetching GIFs from ${relays.length} relays:`, relays);
// Fetch kind 94 events (NIP94 file attachments)
const filters = [{
@ -113,18 +114,23 @@ export async function fetchGifs(searchQuery?: string, limit: number = 50): Promi @@ -113,18 +114,23 @@ export async function fetchGifs(searchQuery?: string, limit: number = 50): Promi
limit: limit * 2 // Fetch more to filter for GIFs
}];
console.debug(`[gif-service] Fetching kind 94 events with filters:`, filters);
const events = await nostrClient.fetchEvents(filters, relays, {
useCache: true,
cacheResults: true
});
console.debug(`[gif-service] Received ${events.length} kind 94 events`);
// Parse and filter for GIFs
const gifs: GifMetadata[] = [];
console.log(`[gif-service] Processing ${events.length} kind 94 events`);
let parsedCount = 0;
let skippedCount = 0;
for (const event of events) {
const gif = parseNip94Event(event);
if (gif) {
parsedCount++;
// If search query provided, filter by content or tags
if (searchQuery) {
const query = searchQuery.toLowerCase();
@ -133,21 +139,33 @@ export async function fetchGifs(searchQuery?: string, limit: number = 50): Promi @@ -133,21 +139,33 @@ export async function fetchGifs(searchQuery?: string, limit: number = 50): Promi
if (content.includes(query) || tags.includes(query)) {
gifs.push(gif);
} else {
skippedCount++;
}
} else {
gifs.push(gif);
}
} else {
skippedCount++;
}
}
console.log(`[gif-service] Found ${gifs.length} GIFs from ${events.length} events`);
console.debug(`[gif-service] Parsed ${parsedCount} GIFs, skipped ${skippedCount} non-GIF events`);
// Only log final result if GIFs were found, otherwise it's just noise
if (gifs.length > 0) {
console.log(`[gif-service] Found ${gifs.length} GIFs${searchQuery ? ` matching "${searchQuery}"` : ''}`);
} else {
console.debug(`[gif-service] Final result: 0 GIFs${searchQuery ? ` matching "${searchQuery}"` : ''}`);
}
// Sort by creation date (newest first) and limit
gifs.sort((a, b) => b.createdAt - a.createdAt);
return gifs.slice(0, limit);
} catch (error) {
console.error('[gif-service] Error fetching GIFs:', error);
return [];
const errorMessage = error instanceof Error ? error.message : String(error);
console.error('[gif-service] Error details:', errorMessage);
throw error; // Re-throw so the UI can show the error
}
}

9
src/lib/services/nostr/nip30-emoji.ts

@ -155,7 +155,7 @@ export async function loadAllEmojiPacks(): Promise<void> { @@ -155,7 +155,7 @@ export async function loadAllEmojiPacks(): Promise<void> {
try {
// Use profile relays to get emoji packs from more sources
const relays = relayManager.getProfileReadRelays();
console.log('[nip30-emoji] Loading all emoji packs/sets...');
console.debug('[nip30-emoji] Loading all emoji packs/sets...');
// Fetch all emoji sets (10030) and emoji packs (30030)
// Use a high limit to get all available packs - increase limit to get more
@ -165,7 +165,7 @@ export async function loadAllEmojiPacks(): Promise<void> { @@ -165,7 +165,7 @@ export async function loadAllEmojiPacks(): Promise<void> {
{ useCache: true, cacheResults: true, timeout: 15000 }
);
console.log(`[nip30-emoji] Found ${events.length} emoji pack/set events`);
console.debug(`[nip30-emoji] Found ${events.length} emoji pack/set events`);
// Process and cache all emoji sets/packs
// Track shortcode -> (url, created_at) to prefer most recent
@ -206,7 +206,12 @@ export async function loadAllEmojiPacks(): Promise<void> { @@ -206,7 +206,12 @@ export async function loadAllEmojiPacks(): Promise<void> {
shortcodeCache.set(shortcode, url);
}
// Only log if we actually found emojis, otherwise it's just noise
if (shortcodeCache.size > 0) {
console.log(`[nip30-emoji] Cached ${emojiSetsByPubkey.size} emoji sets with ${shortcodeCache.size} unique shortcodes`);
} else {
console.debug(`[nip30-emoji] Cached ${emojiSetsByPubkey.size} emoji sets with ${shortcodeCache.size} unique shortcodes`);
}
allEmojiPacksLoaded = true;
} catch (error) {
console.error('Error loading all emoji packs:', error);

42
src/lib/services/nostr/nostr-client.ts

@ -88,7 +88,8 @@ class NostrClient { @@ -88,7 +88,8 @@ class NostrClient {
this.relays.set(url, relay);
// Clear failure tracking on successful connection
this.failedRelays.delete(url);
console.log(`[nostr-client] Successfully connected to relay: ${url}`);
// Log successful connection at debug level to reduce console noise
console.debug(`[nostr-client] Successfully connected to relay: ${url}`);
} catch (error) {
// Track the failure
const existingFailure = this.failedRelays.get(url) || { lastFailure: 0, retryAfter: this.INITIAL_RETRY_DELAY, failureCount: 0 };
@ -156,8 +157,19 @@ class NostrClient { @@ -156,8 +157,19 @@ class NostrClient {
try {
let candidateEvents: NostrEvent[] = [];
// Prioritize ID queries when IDs are specified (more efficient than querying by kind)
if (filter.ids && filter.ids.length > 0 && filter.ids.length <= 10) {
// For small number of IDs, query directly by ID
const idEvents: NostrEvent[] = [];
for (const id of filter.ids) {
const event = await getEvent(id);
if (event) {
idEvents.push(event);
}
}
candidateEvents = idEvents;
} else if (filter.kinds && filter.kinds.length > 0) {
// Query by kind(s) if specified
if (filter.kinds && filter.kinds.length > 0) {
// If single kind, use index for efficiency
if (filter.kinds.length === 1) {
candidateEvents = await getEventsByKind(filter.kinds[0], (filter.limit || 100) * 3);
@ -210,9 +222,10 @@ class NostrClient { @@ -210,9 +222,10 @@ class NostrClient {
}
}
console.log(`[nostr-client] Cache query found ${candidateEvents.length} candidate events for filter:`, filter);
// Filter candidates by all filter criteria
const queriedByIds = filter.ids && filter.ids.length > 0 && filter.ids.length <= 10;
let beforeFilter = candidateEvents.length;
for (const event of candidateEvents) {
if (seen.has(event.id)) continue;
@ -220,8 +233,8 @@ class NostrClient { @@ -220,8 +233,8 @@ class NostrClient {
if (filter.since && event.created_at < filter.since) continue;
if (filter.until && event.created_at > filter.until) continue;
// Apply ids filter
if (filter.ids && filter.ids.length > 0 && !filter.ids.includes(event.id)) continue;
// Apply ids filter (skip if we already queried by IDs)
if (!queriedByIds && filter.ids && filter.ids.length > 0 && !filter.ids.includes(event.id)) continue;
// Apply authors filter (if not already used for query)
if (filter.authors && filter.authors.length > 0 && !filter.authors.includes(event.pubkey)) continue;
@ -279,7 +292,10 @@ class NostrClient { @@ -279,7 +292,10 @@ class NostrClient {
const limited = sorted.slice(0, limit);
const filtered = filterEvents(limited);
// Only log cache queries that return results to reduce console noise
if (filtered.length > 0) {
console.log(`[nostr-client] Cache query: ${limited.length} events before filter, ${filtered.length} after filter`);
}
return filtered;
} catch (error) {
@ -567,7 +583,7 @@ class NostrClient { @@ -567,7 +583,7 @@ class NostrClient {
try {
const cachedEvents = await this.getCachedEvents(filters);
if (cachedEvents.length > 0) {
console.log(`[nostr-client] Returning ${cachedEvents.length} cached events for filter:`, filters);
console.debug(`[nostr-client] Returning ${cachedEvents.length} cached events for filter:`, filters);
// Return cached immediately, fetch fresh in background with delay
// Don't pass onUpdate to background fetch to avoid interfering with cached results
if (cacheResults) {
@ -594,7 +610,8 @@ class NostrClient { @@ -594,7 +610,8 @@ class NostrClient {
}
return cachedEvents;
} else {
console.log(`[nostr-client] No cached events found for filter:`, filters);
// No cached events - this is expected and normal, so use debug level
console.debug(`[nostr-client] No cached events found for filter:`, filters);
}
} catch (error) {
console.error('[nostr-client] Error querying cache:', error);
@ -653,7 +670,7 @@ class NostrClient { @@ -653,7 +670,7 @@ class NostrClient {
return [];
}
console.log(`[nostr-client] Fetching from ${connectedRelays.length} connected relay(s) out of ${relays.length} requested`);
console.debug(`[nostr-client] Fetching from ${connectedRelays.length} connected relay(s) out of ${relays.length} requested`);
// Process relays sequentially with throttling to avoid overload
const events: Map<string, NostrEvent> = new Map();
@ -680,9 +697,12 @@ class NostrClient { @@ -680,9 +697,12 @@ class NostrClient {
console.log(`[nostr-client] Fetch returned ${filtered.length} events, calling onUpdate`);
options.onUpdate(filtered);
} else if (options.onUpdate && filtered.length === 0) {
console.log(`[nostr-client] Fetch returned 0 events, skipping onUpdate to preserve cached results`);
console.debug(`[nostr-client] Fetch returned 0 events, skipping onUpdate to preserve cached results`);
} else if (!options.onUpdate) {
console.log(`[nostr-client] Fetch returned ${filtered.length} events (background refresh, no onUpdate)`);
// Only log background refreshes that return events, not empty results
if (filtered.length > 0) {
console.debug(`[nostr-client] Fetch returned ${filtered.length} events (background refresh, no onUpdate)`);
}
}
return filtered;

Loading…
Cancel
Save