mirror of
https://github.com/dergigi/boris.git
synced 2026-02-17 21:15:02 +01:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a67be8096 | ||
|
|
9a929a6be4 | ||
|
|
e0ca010026 | ||
|
|
8bd5d7aadf | ||
|
|
9115c38cde | ||
|
|
0c7c1d54d9 | ||
|
|
d529d83eb8 | ||
|
|
a3127c7836 | ||
|
|
4d5fe1f425 | ||
|
|
c7a4de9786 |
29
CHANGELOG.md
29
CHANGELOG.md
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.4.1] - 2025-10-10
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Long article summaries overlapping with hero image content on mobile devices
|
||||||
|
- Article summary now moves below hero image on mobile when longer than 150 characters
|
||||||
|
- Article summary line clamp reduced from 3 to 2 lines on mobile for better space utilization
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Hero image rendering on mobile now uses zoom-to-fit approach with viewport-based sizing
|
||||||
|
- Hero image height on mobile set to 50vh (constrained between 280px-400px)
|
||||||
|
- Improved image cropping with center positioning for better visual presentation
|
||||||
|
- Optimized reader header overlay padding and title sizing on mobile
|
||||||
|
|
||||||
|
## [0.4.0] - 2025-10-10
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Mobile-responsive design with overlay sidebar drawer
|
- Mobile-responsive design with overlay sidebar drawer
|
||||||
- Media query hooks for responsive behavior (`useIsMobile`, `useIsTablet`, `useIsCoarsePointer`)
|
- Media query hooks for responsive behavior (`useIsMobile`, `useIsTablet`, `useIsCoarsePointer`)
|
||||||
@@ -19,12 +34,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Mobile-optimized modals (full-screen sheet style)
|
- Mobile-optimized modals (full-screen sheet style)
|
||||||
- Mobile-optimized toast notifications (bottom position)
|
- Mobile-optimized toast notifications (bottom position)
|
||||||
- Dynamic viewport height support (100dvh)
|
- Dynamic viewport height support (100dvh)
|
||||||
|
- Mobile highlights panel as overlay with toggle button
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Sidebar now displays as overlay drawer on mobile (≤768px)
|
- Sidebar now displays as overlay drawer on mobile (≤768px)
|
||||||
- Highlights panel hidden on mobile for better content focus
|
- Highlights panel hidden on mobile for better content focus
|
||||||
- Sidebar auto-closes when selecting content on mobile
|
- Sidebar auto-closes when selecting content on mobile
|
||||||
- Hover effects disabled on touch devices
|
- Hover effects disabled on touch devices
|
||||||
|
- Replace hamburger icon with bookmark icon on mobile
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Ensure bookmarks container fills mobile sidepane properly
|
||||||
|
- Restore desktop grid layout for highlights panel
|
||||||
|
- Improve empty state and loading visibility in mobile sidepanes
|
||||||
|
- Add flex properties to mobile bookmark containers for proper filling
|
||||||
|
- Force bookmarks pane expanded on mobile and ensure highlights pane sits above content on desktop
|
||||||
|
- Reduce mobile backdrop opacity and ensure sidepanes appear above it
|
||||||
|
- Replace any type with proper bookmark interface for linter compliance
|
||||||
|
|
||||||
## [0.3.8] - 2025-10-10
|
## [0.3.8] - 2025-10-10
|
||||||
|
|
||||||
@@ -564,6 +590,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Optimize relay usage following applesauce-relay best practices
|
- Optimize relay usage following applesauce-relay best practices
|
||||||
- Use applesauce-react event models for better profile handling
|
- Use applesauce-react event models for better profile handling
|
||||||
|
|
||||||
|
[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
|
||||||
|
[0.3.7]: https://github.com/dergigi/boris/compare/v0.3.6...v0.3.7
|
||||||
[0.3.6]: https://github.com/dergigi/boris/compare/v0.3.5...v0.3.6
|
[0.3.6]: https://github.com/dergigi/boris/compare/v0.3.5...v0.3.6
|
||||||
[0.3.5]: https://github.com/dergigi/boris/compare/v0.3.4...v0.3.5
|
[0.3.5]: https://github.com/dergigi/boris/compare/v0.3.4...v0.3.5
|
||||||
[0.3.4]: https://github.com/dergigi/boris/compare/v0.3.3...v0.3.4
|
[0.3.4]: https://github.com/dergigi/boris/compare/v0.3.3...v0.3.4
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "boris",
|
"name": "boris",
|
||||||
"version": "0.3.8",
|
"version": "0.4.1",
|
||||||
"description": "A minimal nostr client for bookmark management",
|
"description": "A minimal nostr client for bookmark management",
|
||||||
"homepage": "https://read.withboris.com/",
|
"homepage": "https://read.withboris.com/",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ export const BookmarkList: React.FC<BookmarkListProps> = ({
|
|||||||
<div className="empty-state">
|
<div className="empty-state">
|
||||||
<p>No bookmarks found.</p>
|
<p>No bookmarks found.</p>
|
||||||
<p>Add bookmarks using your nostr client to see them here.</p>
|
<p>Add bookmarks using your nostr client to see them here.</p>
|
||||||
|
<p>If you aren't on nostr yet, start here: <a href="https://nstart.me/" target="_blank" rel="noopener noreferrer">nstart.me</a></p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="bookmarks-list">
|
<div className="bookmarks-list">
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ const ContentPanel: React.FC<ContentPanelProps> = ({
|
|||||||
followedPubkeys
|
followedPubkeys
|
||||||
})
|
})
|
||||||
|
|
||||||
const { contentRef, handleMouseUp } = useHighlightInteractions({
|
const { contentRef, handleSelectionEnd } = useHighlightInteractions({
|
||||||
onHighlightClick,
|
onHighlightClick,
|
||||||
selectedHighlightId,
|
selectedHighlightId,
|
||||||
onTextSelection,
|
onTextSelection,
|
||||||
@@ -138,7 +138,8 @@ const ContentPanel: React.FC<ContentPanelProps> = ({
|
|||||||
ref={contentRef}
|
ref={contentRef}
|
||||||
className="reader-markdown"
|
className="reader-markdown"
|
||||||
dangerouslySetInnerHTML={{ __html: finalHtml }}
|
dangerouslySetInnerHTML={{ __html: finalHtml }}
|
||||||
onMouseUp={handleMouseUp}
|
onMouseUp={handleSelectionEnd}
|
||||||
|
onTouchEnd={handleSelectionEnd}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="reader-markdown">
|
<div className="reader-markdown">
|
||||||
@@ -152,7 +153,8 @@ const ContentPanel: React.FC<ContentPanelProps> = ({
|
|||||||
ref={contentRef}
|
ref={contentRef}
|
||||||
className="reader-html"
|
className="reader-html"
|
||||||
dangerouslySetInnerHTML={{ __html: finalHtml || html || '' }}
|
dangerouslySetInnerHTML={{ __html: finalHtml || html || '' }}
|
||||||
onMouseUp={handleMouseUp}
|
onMouseUp={handleSelectionEnd}
|
||||||
|
onTouchEnd={handleSelectionEnd}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -28,36 +28,45 @@ const ReaderHeader: React.FC<ReaderHeaderProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const cachedImage = useImageCache(image, settings)
|
const cachedImage = useImageCache(image, settings)
|
||||||
const formattedDate = published ? format(new Date(published * 1000), 'MMM d, yyyy') : null
|
const formattedDate = published ? format(new Date(published * 1000), 'MMM d, yyyy') : null
|
||||||
|
const isLongSummary = summary && summary.length > 150
|
||||||
|
|
||||||
if (cachedImage) {
|
if (cachedImage) {
|
||||||
return (
|
return (
|
||||||
<div className="reader-hero-image">
|
<>
|
||||||
<img src={cachedImage} alt={title || 'Article image'} />
|
<div className="reader-hero-image">
|
||||||
{formattedDate && (
|
<img src={cachedImage} alt={title || 'Article image'} />
|
||||||
<div className="publish-date-topright">
|
{formattedDate && (
|
||||||
{formattedDate}
|
<div className="publish-date-topright">
|
||||||
</div>
|
{formattedDate}
|
||||||
)}
|
|
||||||
{title && (
|
|
||||||
<div className="reader-header-overlay">
|
|
||||||
<h2 className="reader-title">{title}</h2>
|
|
||||||
{summary && <p className="reader-summary">{summary}</p>}
|
|
||||||
<div className="reader-meta">
|
|
||||||
{readingTimeText && (
|
|
||||||
<div className="reading-time">
|
|
||||||
<FontAwesomeIcon icon={faClock} />
|
|
||||||
<span>{readingTimeText}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{hasHighlights && (
|
|
||||||
<div className="highlight-indicator">
|
|
||||||
<FontAwesomeIcon icon={faHighlighter} />
|
|
||||||
<span>{highlightCount} highlight{highlightCount !== 1 ? 's' : ''}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
{title && (
|
||||||
|
<div className="reader-header-overlay">
|
||||||
|
<h2 className="reader-title">{title}</h2>
|
||||||
|
{summary && <p className={`reader-summary ${isLongSummary ? 'hide-on-mobile' : ''}`}>{summary}</p>}
|
||||||
|
<div className="reader-meta">
|
||||||
|
{readingTimeText && (
|
||||||
|
<div className="reading-time">
|
||||||
|
<FontAwesomeIcon icon={faClock} />
|
||||||
|
<span>{readingTimeText}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{hasHighlights && (
|
||||||
|
<div className="highlight-indicator">
|
||||||
|
<FontAwesomeIcon icon={faHighlighter} />
|
||||||
|
<span>{highlightCount} highlight{highlightCount !== 1 ? 's' : ''}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{isLongSummary && (
|
||||||
|
<div className="reader-summary-below-image">
|
||||||
|
<p className="reader-summary">{summary}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ const SidebarHeader: React.FC<SidebarHeaderProps> = ({ onToggleCollapse, onLogou
|
|||||||
accountManager.setActive(account)
|
accountManager.setActive(account)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Login failed:', error)
|
console.error('Login failed:', error)
|
||||||
alert('Login failed. Please install a nostr browser extension and try again.')
|
alert('Login failed. Please install a nostr browser extension and try again.\n\nIf you aren\'t on nostr yet, start here: https://nstart.me/')
|
||||||
} finally {
|
} finally {
|
||||||
setIsConnecting(false)
|
setIsConnecting(false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ export const useHighlightInteractions = ({
|
|||||||
}
|
}
|
||||||
}, [selectedHighlightId])
|
}, [selectedHighlightId])
|
||||||
|
|
||||||
// Handle text selection
|
// Handle text selection (works for both mouse and touch)
|
||||||
const handleMouseUp = useCallback(() => {
|
const handleSelectionEnd = useCallback(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const selection = window.getSelection()
|
const selection = window.getSelection()
|
||||||
if (!selection || selection.rangeCount === 0) {
|
if (!selection || selection.rangeCount === 0) {
|
||||||
@@ -76,6 +76,6 @@ export const useHighlightInteractions = ({
|
|||||||
}, 10)
|
}, 10)
|
||||||
}, [onTextSelection, onClearSelection])
|
}, [onTextSelection, onClearSelection])
|
||||||
|
|
||||||
return { contentRef, handleMouseUp }
|
return { contentRef, handleSelectionEnd }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1437,6 +1437,51 @@ body.mobile-sidebar-open {
|
|||||||
border: 1px solid rgba(100, 108, 255, 0.4);
|
border: 1px solid rgba(100, 108, 255, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.reader-summary-below-image {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.reader-header-overlay .reader-summary.hide-on-mobile {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reader-summary-below-image {
|
||||||
|
display: block;
|
||||||
|
padding: 0 0 1.5rem 0;
|
||||||
|
margin-top: -1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reader-summary-below-image .reader-summary {
|
||||||
|
color: #aaa;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reader-hero-image {
|
||||||
|
min-height: 280px;
|
||||||
|
max-height: 400px;
|
||||||
|
height: 50vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reader-hero-image img {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reader-header-overlay {
|
||||||
|
padding: 1.5rem 1rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reader-header-overlay .reader-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Private Bookmark Styles */
|
/* Private Bookmark Styles */
|
||||||
.private-bookmark {
|
.private-bookmark {
|
||||||
background: #2a2a2a;
|
background: #2a2a2a;
|
||||||
@@ -3062,4 +3107,13 @@ body.mobile-sidebar-open {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
gap: 1.5rem;
|
gap: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.blog-post-card-summary {
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-card-content {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user