Compare commits

...

9 Commits

Author SHA1 Message Date
Gigi
61fd5bbadc chore: bump version to 0.2.8 2025-10-08 12:42:24 +01:00
Gigi
d642c87527 fix: pass article summary through to ReadableContent
- Add summary field when converting ArticleContent to ReadableContent
- Fix contentLoader.ts to include article.summary
- Fix useArticleLoader.ts to include article.summary
- Article summaries now properly display in reader header
- Resolves missing summary display for kind:30023 articles
2025-10-08 12:41:03 +01:00
Gigi
fea425b5d0 feat: display article summary in header
- Add summary field to ReadableContent interface
- Pass summary through ContentPanel to ReaderHeader
- Display summary below title in both overlay and standard layouts
- Style summary with reading font for consistency
- Summary appears in white with shadow in image overlays
- Summary appears in gray (#aaa) in standard headers
- Enhances article preview and reading experience
2025-10-08 12:35:05 +01:00
Gigi
1609c6e580 feat: overlay title and metadata on hero images
- Position title and metadata absolutely over hero images
- Add gradient background for text readability (dark at bottom)
- Use backdrop-filter blur for metadata badges
- White text with shadow for better contrast
- Maintain original layout when no image present
- Creates more immersive reading experience
2025-10-08 12:30:00 +01:00
Gigi
270ea94c70 feat: apply reading font to article titles
- Add font-family: var(--reading-font) to .reader-title class
- Ensures consistent typography between titles and body text
- Titles now respect user's reading font preference from settings
2025-10-08 12:09:36 +01:00
Gigi
83e2f23357 chore: update homepage URL to read.withboris.com 2025-10-08 12:08:11 +01:00
Gigi
9df0261071 fix: correct Jina AI Reader proxy URL format
- Remove hardcoded http:// prefix in proxy URL
- Preserve original protocol (http/https) when constructing proxy URL
- Fix: https://r.jina.ai/https://example.com instead of /http://example.com
- Resolves metadata fetching issues for HTTPS URLs
2025-10-08 12:00:03 +01:00
Gigi
1dfe66651a refactor: reorder toolbar buttons
- New order: Profile, Home, Settings, Refresh, Plus, Logout
- Navigation first (Home, Settings)
- Actions in middle (Refresh, Plus)
- Logout at end
2025-10-08 11:58:28 +01:00
Gigi
dcb7933ede refactor: reorder toolbar buttons
- New order: Profile, Home, Refresh, Add, Settings, Logout
- Groups navigation (Profile, Home) at start
- Action buttons (Refresh, Add) in middle
- Settings and Logout at end
2025-10-08 11:48:19 +01:00
9 changed files with 107 additions and 25 deletions

View File

@@ -1,8 +1,8 @@
{ {
"name": "boris", "name": "boris",
"version": "0.2.7", "version": "0.2.8",
"description": "A minimal nostr client for bookmark management", "description": "A minimal nostr client for bookmark management",
"homepage": "https://xn--bris-v0b.com/", "homepage": "https://read.withboris.com/",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -19,6 +19,7 @@ interface ContentPanelProps {
markdown?: string markdown?: string
selectedUrl?: string selectedUrl?: string
image?: string image?: string
summary?: string
highlights?: Highlight[] highlights?: Highlight[]
showHighlights?: boolean showHighlights?: boolean
highlightStyle?: 'marker' | 'underline' highlightStyle?: 'marker' | 'underline'
@@ -40,6 +41,7 @@ const ContentPanel: React.FC<ContentPanelProps> = ({
markdown, markdown,
selectedUrl, selectedUrl,
image, image,
summary,
highlights = [], highlights = [],
showHighlights = true, showHighlights = true,
highlightStyle = 'marker', highlightStyle = 'marker',
@@ -117,6 +119,7 @@ const ContentPanel: React.FC<ContentPanelProps> = ({
<ReaderHeader <ReaderHeader
title={title} title={title}
image={image} image={image}
summary={summary}
readingTimeText={readingStats ? readingStats.text : null} readingTimeText={readingStats ? readingStats.text : null}
hasHighlights={hasHighlights} hasHighlights={hasHighlights}
highlightCount={relevantHighlights.length} highlightCount={relevantHighlights.length}

View File

@@ -5,6 +5,7 @@ import { faHighlighter, faClock } from '@fortawesome/free-solid-svg-icons'
interface ReaderHeaderProps { interface ReaderHeaderProps {
title?: string title?: string
image?: string image?: string
summary?: string
readingTimeText?: string | null readingTimeText?: string | null
hasHighlights: boolean hasHighlights: boolean
highlightCount: number highlightCount: number
@@ -13,20 +14,45 @@ interface ReaderHeaderProps {
const ReaderHeader: React.FC<ReaderHeaderProps> = ({ const ReaderHeader: React.FC<ReaderHeaderProps> = ({
title, title,
image, image,
summary,
readingTimeText, readingTimeText,
hasHighlights, hasHighlights,
highlightCount highlightCount
}) => { }) => {
if (image) {
return (
<div className="reader-hero-image">
<img src={image} alt={title || 'Article image'} />
{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>
)}
</div>
)
}
return ( return (
<> <>
{image && (
<div className="reader-hero-image">
<img src={image} alt={title || 'Article image'} />
</div>
)}
{title && ( {title && (
<div className="reader-header"> <div className="reader-header">
<h2 className="reader-title">{title}</h2> <h2 className="reader-title">{title}</h2>
{summary && <p className="reader-summary">{summary}</p>}
<div className="reader-meta"> <div className="reader-meta">
{readingTimeText && ( {readingTimeText && (
<div className="reading-time"> <div className="reading-time">

View File

@@ -108,15 +108,6 @@ const SidebarHeader: React.FC<SidebarHeaderProps> = ({ onToggleCollapse, onLogou
ariaLabel="Settings" ariaLabel="Settings"
variant="ghost" variant="ghost"
/> />
{activeAccount && (
<IconButton
icon={faPlus}
onClick={() => setShowAddModal(true)}
title="Add bookmark"
ariaLabel="Add bookmark"
variant="ghost"
/>
)}
{onRefresh && ( {onRefresh && (
<IconButton <IconButton
icon={faRotate} icon={faRotate}
@@ -128,6 +119,15 @@ const SidebarHeader: React.FC<SidebarHeaderProps> = ({ onToggleCollapse, onLogou
spin={isRefreshing} spin={isRefreshing}
/> />
)} )}
{activeAccount && (
<IconButton
icon={faPlus}
onClick={() => setShowAddModal(true)}
title="Add bookmark"
ariaLabel="Add bookmark"
variant="ghost"
/>
)}
{activeAccount ? ( {activeAccount ? (
<IconButton <IconButton
icon={faRightFromBracket} icon={faRightFromBracket}

View File

@@ -105,6 +105,7 @@ const ThreePaneLayout: React.FC<ThreePaneLayoutProps> = (props) => {
html={props.readerContent?.html} html={props.readerContent?.html}
markdown={props.readerContent?.markdown} markdown={props.readerContent?.markdown}
image={props.readerContent?.image} image={props.readerContent?.image}
summary={props.readerContent?.summary}
selectedUrl={props.selectedUrl} selectedUrl={props.selectedUrl}
highlights={props.classifiedHighlights} highlights={props.classifiedHighlights}
showHighlights={props.showHighlights} showHighlights={props.showHighlights}

View File

@@ -49,6 +49,7 @@ export function useArticleLoader({
title: article.title, title: article.title,
markdown: article.markdown, markdown: article.markdown,
image: article.image, image: article.image,
summary: article.summary,
url: `nostr:${naddr}` url: `nostr:${naddr}`
}) })

View File

@@ -501,17 +501,20 @@ body {
} }
.reader-header { .reader-header {
display: flex; margin-bottom: 2rem;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
gap: 1rem;
flex-wrap: wrap;
} }
.reader-title { .reader-title {
margin: 0; margin: 0 0 0.75rem 0;
flex: 1; font-family: var(--reading-font);
}
.reader-summary {
color: #aaa;
font-size: 1.1rem;
line-height: 1.5;
margin: 0 0 1rem 0;
font-family: var(--reading-font);
} }
.reader-meta { .reader-meta {
@@ -1070,6 +1073,8 @@ body {
margin: 0 0 2rem 0; margin: 0 0 2rem 0;
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
position: relative;
min-height: 300px;
} }
.reader-hero-image img { .reader-hero-image img {
@@ -1080,6 +1085,50 @@ body {
display: block; display: block;
} }
.reader-header-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 2rem 2rem 1.5rem;
background: linear-gradient(to top, rgba(0, 0, 0, 0.85) 0%, rgba(0, 0, 0, 0.6) 60%, rgba(0, 0, 0, 0) 100%);
}
.reader-header-overlay .reader-title {
color: #fff;
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
margin-bottom: 0.75rem;
}
.reader-header-overlay .reader-summary {
color: rgba(255, 255, 255, 0.9);
font-size: 1.1rem;
line-height: 1.5;
margin: 0 0 1rem 0;
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
font-family: var(--reading-font);
}
.reader-header-overlay .reader-meta {
display: flex;
align-items: center;
gap: 0.75rem;
flex-wrap: wrap;
}
.reader-header-overlay .reading-time,
.reader-header-overlay .highlight-indicator {
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(8px);
border: 1px solid rgba(255, 255, 255, 0.25);
color: #fff;
}
.reader-header-overlay .highlight-indicator {
background: rgba(100, 108, 255, 0.25);
border: 1px solid rgba(100, 108, 255, 0.4);
}
/* Private Bookmark Styles */ /* Private Bookmark Styles */
.private-bookmark { .private-bookmark {
background: #2a2a2a; background: #2a2a2a;

View File

@@ -7,6 +7,7 @@ export interface ReadableContent {
html?: string html?: string
markdown?: string markdown?: string
image?: string image?: string
summary?: string
} }
interface CachedContent { interface CachedContent {
@@ -57,7 +58,7 @@ function saveToCache(url: string, content: ReadableContent): void {
function toProxyUrl(url: string): string { function toProxyUrl(url: string): string {
// Ensure the target URL has a protocol and build the proxy URL // Ensure the target URL has a protocol and build the proxy URL
const normalized = /^https?:\/\//i.test(url) ? url : `https://${url}` const normalized = /^https?:\/\//i.test(url) ? url : `https://${url}`
return `https://r.jina.ai/http://${normalized.replace(/^https?:\/\//, '')}` return `https://r.jina.ai/${normalized}`
} }
export async function fetchReadableContent( export async function fetchReadableContent(

View File

@@ -32,6 +32,7 @@ export async function loadContent(
title: article.title, title: article.title,
markdown: article.markdown, markdown: article.markdown,
image: article.image, image: article.image,
summary: article.summary,
url: `nostr:${naddr}` url: `nostr:${naddr}`
} }
} else { } else {