Commit Graph

1688 Commits

Author SHA1 Message Date
Chad Curtis bf9cef5bb5 Allow adding app default servers/relays to personal lists 2026-03-14 16:19:23 -05:00
Alex Gleason b094e01337 Extract shared parseBlossomServerList into appBlossom.ts
Deduplicates the kind 10063 tag parsing logic that was repeated in both
NostrSync.tsx and useInitialSync.ts. Both now call the shared function
from lib/appBlossom.ts.
2026-03-14 15:55:29 -05:00
Chad Curtis f200359059 Implement BUD-03 kind 10063 Blossom server list management
Blossom servers now work exactly like relays:
- App default servers (APP_BLOSSOM_SERVERS) can be toggled on/off
- User's kind 10063 server list is fetched and synced from Nostr
- NostrSync fetches kind 10063 on login and applies it to config
- useInitialSync fetches kind 10063 in parallel on first login
- BlossomSettings UI mirrors RelayListManager with two sections:
  App Blossom Servers (with enable toggle) + Your Blossom Servers
- User changes publish a kind 10063 replaceable event to Nostr
- getEffectiveBlossomServers() merges app + user servers like getEffectiveRelays()
- Migration from old blossomServers[] to blossomServerMetadata handled in AppProvider
2026-03-14 12:34:59 -05:00
Chad Curtis c36174d9de Paginate FollowsList in search accounts tab to fix perf with 1000+ follows 2026-03-14 11:50:11 -05:00
Chad Curtis 396007d455 Remove unused eslint-disable directive in SearchPage 2026-03-14 11:45:06 -05:00
Chad Curtis a827744ddc Remove focus ring from reaction buttons and add double-click to heart react 2026-03-14 11:43:53 -05:00
Chad Curtis 4684491b9f fix: apply search query filter to all streamed event kinds
Non-kind-1 events (articles, photos, videos, etc.) bypassed the client-side
search check via an early return, so streamed events of any extra kind passed
through regardless of the search query. Remove the early return and apply the
query filter to all kinds, also checking title/summary/subject/alt tags for
non-text events. Reply filtering still only applies to kind 1/1111.
2026-03-14 11:29:39 -05:00
Chad Curtis b4f88ec448 fix: align filter grid labels and rename Platform to Protocol
The Protocol label used flex layout for the HelpTip icon while the other
three labels did not, causing vertical misalignment of the selects below
them. Give all four labels the same flex items-center gap-1 classes.
Also rename Platform to Protocol.
2026-03-14 11:22:00 -05:00
Chad Curtis 97fe74be07 fix: unwrap repost events before rendering in search feed
Kind 6/16 repost events were passed raw to NoteCard without extracting the
embedded original event, so reposts showed nothing. Mirrors the same
parseRepostContent + repostedBy pattern used in useFeed/Feed.tsx.
2026-03-14 11:19:15 -05:00
Chad Curtis 79ea07098d fix: use all picker kinds when kind filter is set to 'all'
parseKindFilter returned undefined for 'all', causing useStreamPosts to fall
back to the user's enabled feed-settings kinds instead of every kind in the
kind picker list. Now kindsOverride always contains explicit kind numbers:
all picker kinds when filter is 'all', or the specific selection otherwise.
2026-03-14 11:15:59 -05:00
Chad Curtis cf1edc3230 fix: always include kind 1 in default search kinds
getEnabledFeedKinds only returns user-toggled extra kinds, never kind 1.
The fallback paths for videos/images/all were missing kind 1 entirely,
so searches returned no results unless the user had explicitly enabled
extra feed kinds.
2026-03-14 11:13:11 -05:00
Chad Curtis b789c0e821 fix: resolve search input focus loss, lag, and add-to-feed button regressions
- Prevent focus steal on first keystroke: the add-to-feed bookmark button was
  conditionally mounted when searchQuery became non-empty, causing a DOM mutation
  mid-keystroke that stole focus from the input

- Eliminate searchParams feedback loop: the state→URL sync effect had searchParams
  in its dependency array, causing a cascade of extra re-renders on every keystroke
  after the debounce settled

- Eliminate per-keystroke re-renders: raw input state now lives in a dedicated
  SearchInput child component with its own internal debounce; SearchPage only
  holds the debounced value and re-renders when typing stops, not on every character
2026-03-14 11:11:33 -05:00
Alex Gleason 78cf05f623 Use lowercase 'k' for thousands in short number format 2026-03-14 02:39:52 -05:00
Alex Gleason 0ffe2de89d Use short number format for large stats (e.g. 1.7K, 314K, 2M) 2026-03-14 02:39:14 -05:00
Alex Gleason 09091441c6 Linkify URLs and hashtags in user bios on profile pages
Add BioContent component that tokenizes bio text to render URLs as
clickable links and hashtags as internal navigation links, while
preserving NIP-30 custom emoji support. Applied to ProfilePage,
ProfileHoverCard, and ProfileCard read-only bio displays.
2026-03-14 01:30:57 -05:00
Alex Gleason 71f9a6c354 Expand vine slides to fill screen when bottom nav hides on scroll
The vine-slide-height CSS class statically subtracted 3.5rem for the
bottom nav. Added a vine-slide-height-expanded class without that
subtraction, toggled by scroll direction so the video fills the full
viewport when the bottom nav slides away.
2026-03-14 01:18:08 -05:00
Alex Gleason f74d333d63 Fix mobile bottom nav not hiding on swipe in Vines page
The Vines page uses an internal snap-scroll container instead of window
scroll, so useScrollDirection (which listened to window.scrollY) never
detected any scroll movement. Add a scrollContainer option to
LayoutOptions so pages with custom scroll containers can communicate
the element to MobileBottomNav, which passes it to useScrollDirection.
2026-03-14 01:08:45 -05:00
Dirk Rost 33c95f6ace Merge branch 'fix/youtube-thumbnails-again' into 'main'
Fix YouTube thumbnail gray placeholder with off-screen probing

See merge request soapbox-pub/ditto!69
2026-03-14 05:59:03 +00:00
Lemon d3457f5868 Fix YouTube thumbnail gray placeholder with off-screen probing 2026-03-14 05:59:03 +00:00
Alex Gleason 8782d8e1bc Change mobile bottom nav items to Search, Notifications, and Profile
Replace Feed/Home button with Profile, which shows the user's avatar at
a small size. Search and Notifications remain. Logged-out users see a
generic User icon for Profile instead of the avatar.
2026-03-14 00:52:52 -05:00
Alex Gleason 59311e6b2c Restore mobile bottom nav with scroll-to-hide behavior
Bring back the mobile bottom navigation bar with Home, Notifications, and
Search buttons. The nav slides out of view when scrolling down and reappears
on upward scroll, keeping the viewport uncluttered while reading. Search
retains the popover account search sheet for fast profile lookup without
navigating away. Updated CSS height calculations for full-screen pages
(AI chat, vines, livestream) and FAB positioning to account for the nav.
2026-03-13 22:47:17 -05:00
Alex Gleason 3727975178 Move 'Set a status' into profile menu on mobile drawer 2026-03-13 22:30:17 -05:00
Alex Gleason e95a63fcf6 Fix emoji picker touch scrolling on mobile in avatar shape dialog 2026-03-13 22:16:59 -05:00
Alex Gleason 2373848d2e Use drop-shadow border for emoji-shaped avatars on profile pages
Replace border/ring with a crisp drop-shadow outline (via emojiAvatarBorderStyle)
so the border follows the emoji mask shape without clipping. Shrink emoji avatars
by the border width so they match the visual footprint of bordered circular ones.
2026-03-13 21:58:45 -05:00
Alex Gleason f23faa7ef3 Fix mute list not being honored: parse public tags and support NIP-04
The mute list (kind 10000) was only reading encrypted content, ignoring
public tags in the event's tags array. Per NIP-51, mute items can be
stored as either public tags or private encrypted content. Most Nostr
clients store mutes as public tags, so these were being silently ignored.

Changes:
- Parse both public tags and encrypted content from kind 10000 events
- Add NIP-04 backward compatibility (detect by '?iv=' in ciphertext)
- Deduplicate items that appear in both public and private sections
- Apply same fixes to useInitialSync for login-time bootstrapping

Fixes: https://gitlab.com/soapbox-pub/ditto/-/work_items/96
2026-03-13 21:42:56 -05:00
Alex Gleason d6aec14936 Fix edit profile avatar/banner hover: keep pencil badge visible and replace camera icons with pencil 2026-03-13 21:23:10 -05:00
Alex Gleason 10bb16abfb Clean up avatar shape code: remove magic strings, type casts, and inconsistencies
- avatar.tsx: consolidate to single isEmojiShape boolean, fix AvatarFallback using same logic as Avatar
- avatarShape.ts: accept NostrMetadata directly in getAvatarShape (no cast needed)
- Remove 'as Record<string, unknown>' casts from all ~50 call sites
- Replace 'circle' magic string with empty string in form defaults and parseShape
- Use isValidAvatarShape instead of string comparison in save logic
- ProfileCard: extract IIFE overlay style into useMemo, use isEmojiShape throughout
2026-03-13 21:14:03 -05:00
Alex Gleason b661e9be9b Remove predefined shape dead code: delete AvatarShapePicker, UserAvatar, and geometric shape utilities 2026-03-13 20:57:09 -05:00
Alex Gleason 3d5417cf97 Add 'Remove avatar shape' button to shape picker dialog 2026-03-13 20:40:15 -05:00
Alex Gleason a55baebd22 Use Dialog instead of Popover for avatar shape emoji picker 2026-03-13 20:36:51 -05:00
Alex Gleason 259dbcc20f Replace avatar click with dropdown menu: Change avatar, Set avatar shape, Remove avatar
Move avatar shape selection from standalone sections in EditProfileForm and
ProfileSettings into a dropdown menu on the ProfileCard avatar itself.
Clicking the avatar now shows a context menu instead of immediately opening
the file browser.
2026-03-13 20:35:15 -05:00
Alex Gleason 55e8075542 Support emoji as avatar shape: use any emoji as a mask over the avatar
Extend the kind-0 'shape' field to accept any emoji string in addition to
the predefined geometric shapes (circle, triangle, hexagon, star, etc.).
When an emoji is chosen, its native OS glyph is rendered onto a canvas,
the alpha channel extracted as a PNG mask, and applied via CSS mask-image.

Implementation:
- Widen AvatarShape type to accept emoji strings alongside predefined names
- isEmoji() detects any short non-ASCII string (avoids fragile Unicode regex)
- getEmojiMaskUrl() renders emoji at 512px, crops to tight bounding box
  (with alpha threshold to ignore shadows/glows), squares the crop, scales
  to 256px output, converts to white+alpha mask PNG. Cached per emoji.
- Avatar component applies mask-image for emoji, clip-path for geometric
- ProfileCard uses ring-4 instead of border-4 for emoji shapes to avoid
  mask/content misalignment (border is inside the element; ring is outside)
- AvatarShapePicker adds emoji-mart picker in a popover
- Form schemas use z.string() to accommodate both predefined and emoji values
- NIP.md documents emoji shape support
2026-03-13 20:27:14 -05:00
Alex Gleason 9e03579d0f Fix heart shape: use polygon approximation instead of path() which requires pixel coordinates
clip-path: path() uses pixel coordinates, making a 0-1 normalized path
invisible. Replaced with a parametric heart curve sampled into a 50-point
polygon using percentage coordinates, consistent with all other shapes.
2026-03-13 17:29:03 -05:00
Alex Gleason 61ea5ff1fe Add heart shape and document kind 0 shape extension in NIP.md
- Add 'heart' as the 8th avatar shape using SVG path() clip-path with cubic beziers
- Document the kind 0 'shape' metadata extension in NIP.md with all 8 defined values,
  client behavior rules, and forward-compatibility guidance
2026-03-13 17:25:50 -05:00
Alex Gleason 0781b1a87c Merge origin/main and re-apply avatar shape changes to conflicted files 2026-03-13 17:23:30 -05:00
Dirk Rost b9c590598d Merge main into feature/development-feed and fix lint issues
Resolve merge conflicts from badges, books, help, and emojis rename.
Fix biome lint: add type="button" to all buttons, use template literals,
format new components with project conventions.
2026-03-13 16:34:36 -05:00
Alex Gleason 3b79b384e5 Add avatar shapes: users can set a shape (circle, triangle, hexagon, star, etc.) for their avatar via kind 0 metadata
- New 'shape' property on kind 0 profile metadata with 7 predefined shapes:
  circle, triangle, inverted-triangle, hexagon, star, inverted-star, hexagram
- Avatar component updated with clip-path support for non-circle shapes
- Visual shape picker added to both EditProfileForm and ProfileSettings
- Shape applied across all 49 avatar render sites in the app
- New UserAvatar wrapper component for future simplified avatar rendering
- Unknown shape values gracefully fall back to circle (forward compatible)
2026-03-13 16:14:40 -05:00
Dirk Rost 980d4678c4 Merge branch 'fix/color-overflow' into 'main'
New Theme Modal Mobile Fixes

See merge request soapbox-pub/ditto!64
2026-03-13 20:33:24 +00:00
Dirk Rost 6ba1fb6bd2 Merge branch 'fix/yt-embed-res' into 'main'
Use sddefault.jpg for YouTube thumbnails for reliability

See merge request soapbox-pub/ditto!63
2026-03-13 20:33:23 +00:00
Lemon 7e19a7f773 New Theme Modal Mobile Fixes 2026-03-13 20:33:23 +00:00
Lemon 18a0b7dce0 Use sddefault.jpg for YouTube thumbnails for reliability 2026-03-13 20:33:23 +00:00
Alex Gleason a67337b798 Merge branch 'collage' into 'main'
Replace square media grids with justified collage layout using imeta dim tags

See merge request soapbox-pub/ditto!66
2026-03-13 20:26:37 +00:00
Dirk Rost 5ca5b975ea Merge branch 'fix/search-input-remount' into 'main'
Fix search page remounting on every keystroke

See merge request soapbox-pub/ditto!65
2026-03-13 20:03:57 +00:00
Alex Gleason d82bdc4613 Rename MediaGrid to MediaCollage
The component is a justified collage layout, not a grid. Renames the file,
component, skeleton, and props interface, and updates all imports.
2026-03-13 14:58:17 -05:00
Alex Gleason 487d2d778d Fix LightboxImage onLoad stability with useCallback instead of lint suppression
Pass markLoaded directly to LightboxSlot/LightboxImage as (url: string) => void,
and derive a stable handleLoaded callback via useCallback inside LightboxImage.
This removes the eslint-disable comment and properly declares all useEffect deps.
2026-03-13 14:54:23 -05:00
Alex Gleason a50782952e Fix infinite re-render loop in LightboxImage useEffect
The onLoad callback passed to LightboxImage was recreated on every render
(inline arrow function). When combined with a cached image triggering the
useEffect, this caused: effect fires → onLoad() → setState → re-render →
new onLoad ref → effect fires again. Remove onLoad from the dependency
array since the check only needs to run when src changes.
2026-03-13 14:38:11 -05:00
Alex Gleason 31770b9858 Drop orphan single-item trailing row in sidebar media collage 2026-03-13 14:32:46 -05:00
Alex Gleason 2286aa7994 Remove redundant spinner below media collage, keep only skeleton loading
The MediaGrid now renders its own skeleton rows when hasNextPage is true,
so the external Loader2 spinner is no longer needed. Keep the intersection
observer sentinel div for infinite scroll triggering.
2026-03-13 14:27:30 -05:00
Alex Gleason 641c85ed56 Hide orphan trailing row when more pages are loading, show skeleton instead
When hasNextPage is true and the last row is incomplete (not fully packed),
hide it to prevent oversized straggler thumbnails. Show 2 skeleton rows as
a loading placeholder. Once the next page loads, those items get properly
packed with new neighbors.
2026-03-13 14:21:43 -05:00
Alex Gleason 932483049e Fix collage row height: set aspectRatio on the row, not individual items
Items in a row now all stretch to the full row height. The row's aspect
ratio is the sum of its items' aspect ratios (since at equal height, total
width = sum of ARs * height). Individual items use flexGrow proportional
to their AR and h-full to fill the row.
2026-03-13 14:14:48 -05:00