Compare commits

...

5 Commits

Author SHA1 Message Date
Gigi
a89c87819a chore: bump version to 0.5.3 2025-10-13 08:55:12 +02:00
Gigi
b09ae3bae3 fix: increase profile icon size when logged out
- Change .profile-avatar svg font-size from 1rem to 1.25rem
- Matches size of other icon buttons in sidebar header
2025-10-13 08:54:18 +02:00
Gigi
6ea8c0d40e feat: make relay status indicator smaller and hide on scroll
- Reduce overall size of indicator on desktop (smaller padding, font sizes)
- On mobile, match size with sidebar toggle buttons (var(--min-touch-target))
- Auto-collapse on mobile (collapsed by default, tap to expand)
- Hide when scrolling down on mobile, show when scrolling up
- Match behavior of other mobile UI controls for consistency
2025-10-13 08:53:17 +02:00
Gigi
079501337c fix: filter out invalid bookmarks without IDs
- Skip bookmarks that don't have a valid ID instead of generating temporary IDs
- Use bookmark's original created_at timestamp for added_at instead of Date.now()
- Fixes issue where first 2 bookmarks always showed 'Now' timestamp with no content
2025-10-13 08:50:29 +02:00
Gigi
5bf0382227 docs(changelog): add v0.5.1 and v0.5.2 entries 2025-10-13 00:14:16 +02:00
6 changed files with 100 additions and 36 deletions

View File

@@ -7,6 +7,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.5.2] - 2025-10-12
### Added
- Three-dot menu to highlight cards for more compact UI
- Combines "Open on Nostr" and "Delete" actions into dropdown menu
- Uses horizontal ellipsis icon (⋯)
- Click-outside functionality to close menu
### Changed
- Switch Nostr gateway from njump.me/search.dergigi.com to ants.sh
- Centralized gateway URLs in config file
- All profile and event links now use ants.sh
- Automatic detection of identifier type (profile vs event) for proper routing
- Remove loading text from Explore and Me pages (spinner only)
- "Open on Nostr" now links to the highlight event itself instead of the article
### Fixed
- Gateway URL routing for ants.sh requirements (/p/ for profiles, /e/ for events)
- Linting errors in HighlightItem component
## [0.5.1] - 2025-10-12
### Added
- Highlight color customization to UI elements
- Apply user's "my highlights" color to highlight creation buttons
- Apply highlight group colors to highlight count indicators
- Apply "my highlights" color to collapsed highlights panel button
### Fixed
- Highlight count indicator styling to match reading-time element
- Brightness and border styling for highlight count indicator
- User highlight color now applies to both marker and arrow icons
- Highlight group color properly applied to count indicator background
### Removed
- MOBILE_IMPLEMENTATION.md documentation file
## [0.5.0] - 2025-10-12
### Added
@@ -692,7 +729,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Optimize relay usage following applesauce-relay best practices
- Use applesauce-react event models for better profile handling
[Unreleased]: https://github.com/dergigi/boris/compare/v0.5.0...HEAD
[Unreleased]: https://github.com/dergigi/boris/compare/v0.5.2...HEAD
[0.5.2]: https://github.com/dergigi/boris/compare/v0.5.1...v0.5.2
[0.5.1]: https://github.com/dergigi/boris/compare/v0.5.0...v0.5.1
[0.5.0]: https://github.com/dergigi/boris/compare/v0.4.3...v0.5.0
[0.4.0]: https://github.com/dergigi/boris/compare/v0.3.8...v0.4.0
[0.3.8]: https://github.com/dergigi/boris/compare/v0.3.7...v0.3.8

View File

@@ -1,6 +1,6 @@
{
"name": "boris",
"version": "0.5.2",
"version": "0.5.3",
"description": "A minimal nostr client for bookmark management",
"homepage": "https://read.withboris.com/",
"type": "module",

View File

@@ -8,9 +8,13 @@ import { useIsMobile } from '../hooks/useMediaQuery'
interface RelayStatusIndicatorProps {
relayPool: RelayPool | null
showOnMobile?: boolean // Control visibility based on scroll
}
export const RelayStatusIndicator: React.FC<RelayStatusIndicatorProps> = ({ relayPool }) => {
export const RelayStatusIndicator: React.FC<RelayStatusIndicatorProps> = ({
relayPool,
showOnMobile = true
}) => {
// Poll frequently for responsive offline indicator (5s instead of default 20s)
const relayStatuses = useRelayStatus({ relayPool, pollingInterval: 5000 })
const [isConnecting, setIsConnecting] = useState(true)
@@ -70,7 +74,7 @@ export const RelayStatusIndicator: React.FC<RelayStatusIndicatorProps> = ({ rela
return (
<div
className={`relay-status-indicator ${isConnecting ? 'connecting' : ''} ${isMobile ? 'mobile' : ''} ${isExpanded ? 'expanded' : ''}`}
className={`relay-status-indicator ${isConnecting ? 'connecting' : ''} ${isMobile ? 'mobile' : ''} ${isExpanded ? 'expanded' : ''} ${isMobile && !showOnMobile ? 'hidden' : 'visible'}`}
title={
!isMobile ? (
isConnecting

View File

@@ -366,7 +366,10 @@ const ThreePaneLayout: React.FC<ThreePaneLayoutProps> = (props) => {
highlightColor={props.settings.highlightColorMine || '#ffff00'}
/>
)}
<RelayStatusIndicator relayPool={props.relayPool} />
<RelayStatusIndicator
relayPool={props.relayPool}
showOnMobile={showMobileButtons}
/>
{props.toastMessage && (
<Toast
message={props.toastMessage}

View File

@@ -221,7 +221,7 @@ body.mobile-sidebar-open {
}
.profile-avatar svg {
font-size: 1rem;
font-size: 1.25rem;
}
.sidebar-header-bar .toggle-sidebar-btn {
@@ -3182,13 +3182,13 @@ body.mobile-sidebar-open {
/* Relay Status Indicator */
.relay-status-indicator {
position: fixed;
bottom: 1.5rem;
left: 1.5rem;
bottom: 1rem;
left: 1rem;
z-index: 999;
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
background: rgba(245, 158, 11, 0.95);
border: 1px solid rgba(245, 158, 11, 0.4);
border-radius: 8px;
@@ -3196,12 +3196,13 @@ body.mobile-sidebar-open {
backdrop-filter: blur(10px);
transition: all 0.3s ease;
cursor: default;
font-size: 0.875rem;
}
/* Mobile compact mode - just show icon */
/* Mobile compact mode - just show icon, match sidebar button size */
@media (max-width: 768px) {
.relay-status-indicator.mobile {
padding: 0.5rem;
padding: 0;
width: var(--min-touch-target);
height: var(--min-touch-target);
display: flex;
@@ -3209,12 +3210,13 @@ body.mobile-sidebar-open {
justify-content: center;
bottom: 1rem;
left: 1rem;
gap: 0;
}
.relay-status-indicator.mobile.expanded {
width: auto;
padding: 0.75rem 1rem;
gap: 0.75rem;
padding: 0.5rem 0.75rem;
gap: 0.5rem;
}
.relay-status-indicator.mobile .relay-status-icon {
@@ -3224,6 +3226,18 @@ body.mobile-sidebar-open {
.relay-status-indicator.mobile:active {
transform: scale(0.95);
}
/* Hide/show on scroll */
.relay-status-indicator.mobile.hidden {
opacity: 0;
visibility: hidden;
pointer-events: none;
}
.relay-status-indicator.mobile.visible {
opacity: 1;
visibility: visible;
}
}
.relay-status-indicator.connecting {
@@ -3250,7 +3264,7 @@ body.mobile-sidebar-open {
}
.relay-status-icon {
font-size: 1.25rem;
font-size: 1rem;
color: #1a1a1a;
display: flex;
align-items: center;
@@ -3260,18 +3274,18 @@ body.mobile-sidebar-open {
.relay-status-text {
display: flex;
flex-direction: column;
gap: 0.15rem;
gap: 0.1rem;
}
.relay-status-title {
font-size: 0.875rem;
font-size: 0.8125rem;
font-weight: 600;
color: #1a1a1a;
line-height: 1.2;
}
.relay-status-subtitle {
font-size: 0.75rem;
font-size: 0.6875rem;
color: rgba(26, 26, 26, 0.8);
line-height: 1.2;
}

View File

@@ -60,8 +60,27 @@ export const processApplesauceBookmarks = (
if (applesauceBookmarks.articles) allItems.push(...applesauceBookmarks.articles)
if (applesauceBookmarks.hashtags) allItems.push(...applesauceBookmarks.hashtags)
if (applesauceBookmarks.urls) allItems.push(...applesauceBookmarks.urls)
return allItems.map((bookmark: BookmarkData) => ({
id: bookmark.id || `${isPrivate ? 'private' : 'public'}-${Date.now()}`,
return allItems
.filter((bookmark: BookmarkData) => bookmark.id) // Skip bookmarks without valid IDs
.map((bookmark: BookmarkData) => ({
id: bookmark.id!,
content: bookmark.content || '',
created_at: bookmark.created_at || Math.floor(Date.now() / 1000),
pubkey: activeAccount.pubkey,
kind: bookmark.kind || 30001,
tags: bookmark.tags || [],
parsedContent: bookmark.content ? (getParsedContent(bookmark.content) as ParsedContent) : undefined,
type: 'event' as const,
isPrivate,
added_at: bookmark.created_at || Math.floor(Date.now() / 1000)
}))
}
const bookmarkArray = Array.isArray(bookmarks) ? bookmarks : [bookmarks]
return bookmarkArray
.filter((bookmark: BookmarkData) => bookmark.id) // Skip bookmarks without valid IDs
.map((bookmark: BookmarkData) => ({
id: bookmark.id!,
content: bookmark.content || '',
created_at: bookmark.created_at || Math.floor(Date.now() / 1000),
pubkey: activeAccount.pubkey,
@@ -70,23 +89,8 @@ export const processApplesauceBookmarks = (
parsedContent: bookmark.content ? (getParsedContent(bookmark.content) as ParsedContent) : undefined,
type: 'event' as const,
isPrivate,
added_at: Math.floor(Date.now() / 1000)
added_at: bookmark.created_at || Math.floor(Date.now() / 1000)
}))
}
const bookmarkArray = Array.isArray(bookmarks) ? bookmarks : [bookmarks]
return bookmarkArray.map((bookmark: BookmarkData) => ({
id: bookmark.id || `${isPrivate ? 'private' : 'public'}-${Date.now()}`,
content: bookmark.content || '',
created_at: bookmark.created_at || Math.floor(Date.now() / 1000),
pubkey: activeAccount.pubkey,
kind: bookmark.kind || 30001,
tags: bookmark.tags || [],
parsedContent: bookmark.content ? (getParsedContent(bookmark.content) as ParsedContent) : undefined,
type: 'event' as const,
isPrivate,
added_at: Math.floor(Date.now() / 1000)
}))
}
// Types and guards around signer/decryption APIs