Compare commits

...

4 Commits

Author SHA1 Message Date
Gigi
e821aaf058 chore: bump version to 0.10.12 2025-10-22 13:30:37 +02:00
Gigi
a84d439489 fix: properly deduplicate web bookmarks by d-tag
- Web bookmarks (kind:39701) are replaceable events and should be deduplicated by d-tag
- Update dedupeNip51Events to include kind:39701 in d-tag deduplication logic
- Use coordinate format (kind:pubkey:d-tag) for web bookmark IDs instead of event IDs
- Ensures same URL bookmarked multiple times only appears once
- Keeps newest version when duplicates exist
2025-10-22 13:28:36 +02:00
Gigi
67bf7e017d fix: make profile avatar button same size as other icon buttons on mobile
- Add mobile media query to profile-avatar-button for consistent sizing
- Use --min-touch-target (44px) on mobile to match IconButton components
- Ensures consistent touch target size across all sidebar buttons
2025-10-22 13:26:20 +02:00
Gigi
e47419a0b8 feat: update explore icon to fa-person-hiking and reorder sidebar buttons
- Change explore icon from fa-newspaper to fa-person-hiking in SidebarHeader and Explore components
- Switch positions of settings and explore buttons in sidebar navigation
- Remove all console.log statements from bookmarkController and bookmarkProcessing
- Update CHANGELOG.md with v0.10.11 changes
2025-10-22 13:25:34 +02:00
8 changed files with 73 additions and 34 deletions

View File

@@ -7,6 +7,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.10.11] - 2025-01-27
### Added
- Clock icon for chronological bookmark view
- Clickable highlight count to open highlights sidebar
- Dynamic bookmark filter titles based on selected filter
- Profile picture moved to first position (left-aligned) with consistent sizing
### Changed
- Default bookmark view changed to flat chronological list (newest first)
- Bookmark URL changed from `/me/reading-list` to `/me/bookmarks`
- Router updated to handle `/me/reading-list``/me/bookmarks` redirect
- Me.tsx bookmarks tab now uses dynamic filter titles and chronological sorting
- Me.tsx updated to use faClock icon instead of faBars
- Removed bookmark count from section headings for cleaner display
- Hide close/collapse sidebar buttons on mobile for better UX
### Fixed
- Bookmark sorting now uses proper display time (created_at || listUpdatedAt) with nulls last
- Robust sorting of merged bookmarks with fallback timestamps
- Corrected bookmark timestamp to use bookmark list creation time, not content creation time
- Preserved content created_at while adding listUpdatedAt for proper sorting
- Removed synthetic added_at field, now uses created_at from bookmark list event
- Consistent chronological sorting with useMemo optimization
- Removed unused faTimes import
- Bookmark timestamps now show sane dates using created_at fallback to listUpdatedAt
- Guarded formatters to prevent timestamp display errors
### Refactored
- Removed excessive debug logging for cleaner console output
- Bookmark timestamp handling never defaults to "now", allows nulls and sorts nulls last
- Renders empty when timestamp is missing instead of showing invalid dates
## [0.10.10] - 2025-10-22
### Changed
@@ -2442,7 +2479,8 @@ 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.10.10...HEAD
[Unreleased]: https://github.com/dergigi/boris/compare/v0.10.11...HEAD
[0.10.11]: https://github.com/dergigi/boris/compare/v0.10.10...v0.10.11
[0.10.10]: https://github.com/dergigi/boris/compare/v0.10.9...v0.10.10
[0.10.9]: https://github.com/dergigi/boris/compare/v0.10.8...v0.10.9
[0.10.8]: https://github.com/dergigi/boris/compare/v0.10.7...v0.10.8

View File

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

View File

@@ -1,6 +1,6 @@
import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faNewspaper, faHighlighter, faUser, faUserGroup, faNetworkWired, faArrowsRotate, faSpinner } from '@fortawesome/free-solid-svg-icons'
import { faPersonHiking, faNewspaper, faHighlighter, faUser, faUserGroup, faNetworkWired, faArrowsRotate, faSpinner } from '@fortawesome/free-solid-svg-icons'
import IconButton from './IconButton'
import { BlogPostSkeleton, HighlightSkeleton } from './Skeletons'
import { Hooks } from 'applesauce-react'
@@ -584,7 +584,7 @@ const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, acti
/>
<div className="explore-header">
<h1>
<FontAwesomeIcon icon={faNewspaper} />
<FontAwesomeIcon icon={faPersonHiking} />
Explore
</h1>

View File

@@ -1,7 +1,7 @@
import React from 'react'
import { useNavigate } from 'react-router-dom'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faChevronRight, faRightFromBracket, faUserCircle, faGear, faHome, faNewspaper } from '@fortawesome/free-solid-svg-icons'
import { faChevronRight, faRightFromBracket, faUserCircle, faGear, faHome, faPersonHiking } from '@fortawesome/free-solid-svg-icons'
import { Hooks } from 'applesauce-react'
import { useEventModel } from 'applesauce-react/hooks'
import { Models } from 'applesauce-core'
@@ -58,13 +58,6 @@ const SidebarHeader: React.FC<SidebarHeaderProps> = ({ onToggleCollapse, onLogou
ariaLabel="Home"
variant="ghost"
/>
<IconButton
icon={faNewspaper}
onClick={() => navigate('/explore')}
title="Explore"
ariaLabel="Explore"
variant="ghost"
/>
<IconButton
icon={faGear}
onClick={onOpenSettings}
@@ -72,6 +65,13 @@ const SidebarHeader: React.FC<SidebarHeaderProps> = ({ onToggleCollapse, onLogou
ariaLabel="Settings"
variant="ghost"
/>
<IconButton
icon={faPersonHiking}
onClick={() => navigate('/explore')}
title="Explore"
ariaLabel="Explore"
variant="ghost"
/>
{activeAccount && (
<IconButton
icon={faRightFromBracket}

View File

@@ -357,14 +357,6 @@ class BookmarkController {
const bTs = (b.created_at ?? b.listUpdatedAt ?? -Infinity)
return bTs - aTs
})
// DEBUG: Show top 5 sorted bookmarks
console.log(`🔍 Top 5 bookmarks after sorting:`)
sortedBookmarks.slice(0, 5).forEach((b, i) => {
const listDate = b.listUpdatedAt ? new Date(b.listUpdatedAt * 1000).toISOString() : 'MISSING'
console.log(` ${i + 1}. listUpdatedAt: ${b.listUpdatedAt} (${listDate})`)
console.log(` content: ${(b.content || '').substring(0, 40)}`)
})
const bookmark: Bookmark = {
id: `${activeAccount.pubkey}-bookmarks`,

View File

@@ -15,28 +15,30 @@ export function dedupeNip51Events(events: NostrEvent[]): NostrEvent[] {
}
const unique = Array.from(byId.values())
// Separate web bookmarks (kind:39701) from list-based bookmarks
const webBookmarks = unique.filter(e => e.kind === 39701)
const bookmarkLists = unique
.filter(e => e.kind === 10003 || e.kind === 30003 || e.kind === 30001)
.sort((a, b) => (b.created_at || 0) - (a.created_at || 0))
const latestBookmarkList = bookmarkLists.find(list => !list.tags?.some((t: string[]) => t[0] === 'd'))
// Deduplicate replaceable events (kind:30003, 30001, 39701) by d-tag
const byD = new Map<string, NostrEvent>()
for (const e of unique) {
if (e.kind === 10003 || e.kind === 30003 || e.kind === 30001) {
if (e.kind === 10003 || e.kind === 30003 || e.kind === 30001 || e.kind === 39701) {
const d = (e.tags || []).find((t: string[]) => t[0] === 'd')?.[1] || ''
const prev = byD.get(d)
if (!prev || (e.created_at || 0) > (prev.created_at || 0)) byD.set(d, e)
}
}
const setsAndNamedLists = Array.from(byD.values())
// Separate web bookmarks from bookmark sets/lists
const allReplaceable = Array.from(byD.values())
const webBookmarks = allReplaceable.filter(e => e.kind === 39701)
const setsAndNamedLists = allReplaceable.filter(e => e.kind !== 39701)
const out: NostrEvent[] = []
if (latestBookmarkList) out.push(latestBookmarkList)
out.push(...setsAndNamedLists)
// Add web bookmarks as individual events
// Add deduplicated web bookmarks as individual events
out.push(...webBookmarks)
return out
}

View File

@@ -133,8 +133,12 @@ export async function collectBookmarksFromEvents(
// Handle web bookmarks (kind:39701) as individual bookmarks
if (evt.kind === 39701) {
// Use coordinate format for web bookmarks to enable proper deduplication
// Web bookmarks are replaceable events (kind:39701:pubkey:d-tag)
const webBookmarkId = dTag ? `${evt.kind}:${evt.pubkey}:${dTag}` : evt.id
publicItemsAll.push({
id: evt.id,
id: webBookmarkId,
content: evt.content || '',
created_at: evt.created_at ?? null,
pubkey: evt.pubkey,
@@ -156,13 +160,6 @@ export async function collectBookmarksFromEvents(
const pub = Helpers.getPublicBookmarks(evt)
const processedPub = processApplesauceBookmarks(pub, activeAccount, false, evt.created_at)
// DEBUG: Check timestamps
if (processedPub.length > 0) {
const first = processedPub[0]
console.log(`📋 Bookmark list event kind:${evt.kind}`)
console.log(` evt.created_at: ${evt.created_at} (${evt.created_at ? new Date(evt.created_at * 1000).toISOString() : 'MISSING'})`)
console.log(` first bookmark listUpdatedAt: ${first.listUpdatedAt} (${first.listUpdatedAt ? new Date(first.listUpdatedAt * 1000).toISOString() : 'MISSING'})`)
}
publicItemsAll.push(
...processedPub.map(i => ({

View File

@@ -114,6 +114,16 @@
transition: all 0.2s ease;
}
/* Mobile touch target improvements */
@media (max-width: 768px) {
.profile-avatar-button {
min-width: var(--min-touch-target);
min-height: var(--min-touch-target);
width: var(--min-touch-target);
height: var(--min-touch-target);
}
}
.profile-avatar-button:hover {
background: var(--color-bg-hover);
border-color: var(--color-border);