From 0bc89889e016045c3637e04e65265e2b8e96f195 Mon Sep 17 00:00:00 2001 From: Gigi Date: Sun, 5 Oct 2025 12:57:09 +0100 Subject: [PATCH] feat: show highlights in article content and add mode toggle Fixes: - Fixed highlight filtering for Nostr articles in urlHelpers.ts Now returns all highlights for nostr: URLs since they're pre-filtered - This fixes highlights not appearing in article content Features: - Added highlight mode toggle: 'my highlights' vs 'other highlights' - Icons: faUser (mine) and faUserGroup (others) - Mode toggle only shows when user is logged in - Filters highlights by user pubkey based on selected mode - Default mode is 'others' to show community highlights - Added CSS styling for mode toggle buttons Result: Highlights now show both in the panel AND underlined in the article text. Users can switch between viewing their own highlights vs highlights from others. --- src/components/Bookmarks.tsx | 5 ++ src/components/HighlightsPanel.tsx | 88 ++++++++++++++++++++++-------- src/index.css | 29 ++++++++++ src/utils/urlHelpers.ts | 7 +++ 4 files changed, 105 insertions(+), 24 deletions(-) diff --git a/src/components/Bookmarks.tsx b/src/components/Bookmarks.tsx index 54dc9252..a09df8ce 100644 --- a/src/components/Bookmarks.tsx +++ b/src/components/Bookmarks.tsx @@ -16,6 +16,7 @@ import Toast from './Toast' import { useSettings } from '../hooks/useSettings' import { useArticleLoader } from '../hooks/useArticleLoader' import { loadContent, BookmarkReference } from '../utils/contentLoader' +import { HighlightMode } from './HighlightsPanel' export type ViewMode = 'compact' | 'cards' | 'large' interface BookmarksProps { @@ -39,6 +40,7 @@ const Bookmarks: React.FC = ({ relayPool, onLogout }) => { const [showSettings, setShowSettings] = useState(false) const [currentArticleCoordinate, setCurrentArticleCoordinate] = useState(undefined) const [currentArticleEventId, setCurrentArticleEventId] = useState(undefined) + const [highlightMode, setHighlightMode] = useState('others') const activeAccount = Hooks.useActiveAccount() const accountManager = Hooks.useAccountManager() const eventStore = useEventStore() @@ -191,6 +193,9 @@ const Bookmarks: React.FC = ({ relayPool, onLogout }) => { selectedHighlightId={selectedHighlightId} onRefresh={handleFetchHighlights} onHighlightClick={setSelectedHighlightId} + currentUserPubkey={activeAccount?.pubkey} + highlightMode={highlightMode} + onHighlightModeChange={setHighlightMode} /> diff --git a/src/components/HighlightsPanel.tsx b/src/components/HighlightsPanel.tsx index 4f0c7e3e..0902ed9f 100644 --- a/src/components/HighlightsPanel.tsx +++ b/src/components/HighlightsPanel.tsx @@ -1,9 +1,11 @@ import React, { useMemo, useState } from 'react' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { faChevronRight, faHighlighter, faEye, faEyeSlash, faRotate } from '@fortawesome/free-solid-svg-icons' +import { faChevronRight, faHighlighter, faEye, faEyeSlash, faRotate, faUser, faUserGroup } from '@fortawesome/free-solid-svg-icons' import { Highlight } from '../types/highlights' import { HighlightItem } from './HighlightItem' +export type HighlightMode = 'mine' | 'others' + interface HighlightsPanelProps { highlights: Highlight[] loading: boolean @@ -15,6 +17,9 @@ interface HighlightsPanelProps { selectedHighlightId?: string onRefresh?: () => void onHighlightClick?: (highlightId: string) => void + currentUserPubkey?: string + highlightMode?: HighlightMode + onHighlightModeChange?: (mode: HighlightMode) => void } export const HighlightsPanel: React.FC = ({ @@ -27,7 +32,10 @@ export const HighlightsPanel: React.FC = ({ onToggleUnderlines, selectedHighlightId, onRefresh, - onHighlightClick + onHighlightClick, + currentUserPubkey, + highlightMode = 'others', + onHighlightModeChange }) => { const [showUnderlines, setShowUnderlines] = useState(true) @@ -37,36 +45,48 @@ export const HighlightsPanel: React.FC = ({ onToggleUnderlines?.(newValue) } - // Filter highlights to show only those relevant to the current URL or article + // Filter highlights based on mode and URL const filteredHighlights = useMemo(() => { if (!selectedUrl) return highlights - // For Nostr articles (URL starts with "nostr:"), we don't need to filter + let urlFiltered = highlights + + // For Nostr articles (URL starts with "nostr:"), we don't need to filter by URL // because we already fetched highlights specifically for this article - if (selectedUrl.startsWith('nostr:')) { - return highlights - } - - // For web URLs, filter by URL matching - const normalizeUrl = (url: string) => { - try { - const urlObj = new URL(url.startsWith('http') ? url : `https://${url}`) - return `${urlObj.hostname.replace(/^www\./, '')}${urlObj.pathname}`.replace(/\/$/, '').toLowerCase() - } catch { - return url.replace(/^https?:\/\//, '').replace(/^www\./, '').replace(/\/$/, '').toLowerCase() + if (!selectedUrl.startsWith('nostr:')) { + // For web URLs, filter by URL matching + const normalizeUrl = (url: string) => { + try { + const urlObj = new URL(url.startsWith('http') ? url : `https://${url}`) + return `${urlObj.hostname.replace(/^www\./, '')}${urlObj.pathname}`.replace(/\/$/, '').toLowerCase() + } catch { + return url.replace(/^https?:\/\//, '').replace(/^www\./, '').replace(/\/$/, '').toLowerCase() + } } + + const normalizedSelected = normalizeUrl(selectedUrl) + + urlFiltered = highlights.filter(h => { + if (!h.urlReference) return false + const normalizedRef = normalizeUrl(h.urlReference) + return normalizedSelected === normalizedRef || + normalizedSelected.includes(normalizedRef) || + normalizedRef.includes(normalizedSelected) + }) } - const normalizedSelected = normalizeUrl(selectedUrl) + // Filter by mode (mine vs others) + if (!currentUserPubkey) { + // If no user is logged in, show all highlights (others mode only makes sense) + return urlFiltered + } - return highlights.filter(h => { - if (!h.urlReference) return false - const normalizedRef = normalizeUrl(h.urlReference) - return normalizedSelected === normalizedRef || - normalizedSelected.includes(normalizedRef) || - normalizedRef.includes(normalizedSelected) - }) - }, [highlights, selectedUrl]) + if (highlightMode === 'mine') { + return urlFiltered.filter(h => h.pubkey === currentUserPubkey) + } else { + return urlFiltered.filter(h => h.pubkey !== currentUserPubkey) + } + }, [highlights, selectedUrl, highlightMode, currentUserPubkey]) if (isCollapsed) { const hasHighlights = filteredHighlights.length > 0 @@ -95,6 +115,26 @@ export const HighlightsPanel: React.FC = ({ {!loading && ({filteredHighlights.length})}
+ {currentUserPubkey && onHighlightModeChange && ( +
+ + +
+ )} {onRefresh && (