From 99c6a4c23b4bf39bf2d1d935eafdc666ca41a7c2 Mon Sep 17 00:00:00 2001 From: Gigi Date: Fri, 3 Oct 2025 09:44:39 +0200 Subject: [PATCH] feat: add view mode switching for bookmarks with compact list view - Add ViewMode type with options: compact, cards, large - Add view mode toggle buttons in SidebarHeader - Implement compact list view rendering in BookmarkItem - Add CSS styles for compact view with condensed layout - Cards view remains the default and current style --- dist/index.html | 4 +- src/components/BookmarkItem.tsx | 44 +++++++++++++- src/components/BookmarkList.tsx | 24 ++++++-- src/components/Bookmarks.tsx | 5 ++ src/components/SidebarHeader.tsx | 78 +++++++++++++++++-------- src/index.css | 98 ++++++++++++++++++++++++++++++++ 6 files changed, 221 insertions(+), 32 deletions(-) diff --git a/dist/index.html b/dist/index.html index 9e130496..87aeb5fc 100644 --- a/dist/index.html +++ b/dist/index.html @@ -5,8 +5,8 @@ Markr - Nostr Bookmarks - - + +
diff --git a/src/components/BookmarkItem.tsx b/src/components/BookmarkItem.tsx index b715a40f..3aab933c 100644 --- a/src/components/BookmarkItem.tsx +++ b/src/components/BookmarkItem.tsx @@ -12,14 +12,16 @@ import { getKindIcon } from './kindIcon' import ContentWithResolvedProfiles from './ContentWithResolvedProfiles' import { extractUrlsFromContent } from '../services/bookmarkHelpers' import { classifyUrl } from '../utils/helpers' +import { ViewMode } from './Bookmarks' interface BookmarkItemProps { bookmark: IndividualBookmark index: number onSelectUrl?: (url: string) => void + viewMode?: ViewMode } -export const BookmarkItem: React.FC = ({ bookmark, index, onSelectUrl }) => { +export const BookmarkItem: React.FC = ({ bookmark, index, onSelectUrl, viewMode = 'cards' }) => { const [expanded, setExpanded] = useState(false) const [urlsExpanded, setUrlsExpanded] = useState(false) // removed copy-to-clipboard buttons @@ -75,6 +77,46 @@ export const BookmarkItem: React.FC = ({ bookmark, index, onS // Get classification for the first URL (for the main button) const firstUrlClassification = hasUrls ? classifyUrl(extractedUrls[0]) : null + // Compact view rendering + if (viewMode === 'compact') { + return ( +
+
+ + {bookmark.isPrivate ? ( + <> + + + + ) : ( + + )} + +
+ {bookmark.content && ( +
+ 100 ? '…' : '')} /> +
+ )} +
+ {formatDate(bookmark.created_at)} + {hasUrls && ( + + )} +
+
+
+
+ ) + } + + // Card/Large view rendering (existing) return (
diff --git a/src/components/BookmarkList.tsx b/src/components/BookmarkList.tsx index e3a6bfc1..c5d72579 100644 --- a/src/components/BookmarkList.tsx +++ b/src/components/BookmarkList.tsx @@ -5,6 +5,7 @@ import { Bookmark } from '../types/bookmarks' import { BookmarkItem } from './BookmarkItem' import { formatDate, renderParsedContent } from '../utils/bookmarkUtils' import SidebarHeader from './SidebarHeader' +import { ViewMode } from './Bookmarks' interface BookmarkListProps { bookmarks: Bookmark[] @@ -12,6 +13,8 @@ interface BookmarkListProps { isCollapsed: boolean onToggleCollapse: () => void onLogout: () => void + viewMode: ViewMode + onViewModeChange: (mode: ViewMode) => void } export const BookmarkList: React.FC = ({ @@ -19,7 +22,9 @@ export const BookmarkList: React.FC = ({ onSelectUrl, isCollapsed, onToggleCollapse, - onLogout + onLogout, + viewMode, + onViewModeChange }) => { if (isCollapsed) { return ( @@ -38,7 +43,12 @@ export const BookmarkList: React.FC = ({ return (
- + {bookmarks.length === 0 ? (
@@ -75,9 +85,15 @@ export const BookmarkList: React.FC = ({ )} {bookmark.individualBookmarks && bookmark.individualBookmarks.length > 0 && (
-
+
{bookmark.individualBookmarks.map((individualBookmark, index) => - + )}
diff --git a/src/components/Bookmarks.tsx b/src/components/Bookmarks.tsx index 4ad36012..c2b185ae 100644 --- a/src/components/Bookmarks.tsx +++ b/src/components/Bookmarks.tsx @@ -7,6 +7,8 @@ import { fetchBookmarks } from '../services/bookmarkService' import ContentPanel from './ContentPanel' import { fetchReadableContent, ReadableContent } from '../services/readerService' +export type ViewMode = 'compact' | 'cards' | 'large' + interface BookmarksProps { relayPool: RelayPool | null onLogout: () => void @@ -19,6 +21,7 @@ const Bookmarks: React.FC = ({ relayPool, onLogout }) => { const [readerLoading, setReaderLoading] = useState(false) const [readerContent, setReaderContent] = useState(undefined) const [isCollapsed, setIsCollapsed] = useState(false) + const [viewMode, setViewMode] = useState('cards') const activeAccount = Hooks.useActiveAccount() const accountManager = Hooks.useAccountManager() @@ -85,6 +88,8 @@ const Bookmarks: React.FC = ({ relayPool, onLogout }) => { isCollapsed={isCollapsed} onToggleCollapse={() => setIsCollapsed(!isCollapsed)} onLogout={onLogout} + viewMode={viewMode} + onViewModeChange={setViewMode} />
diff --git a/src/components/SidebarHeader.tsx b/src/components/SidebarHeader.tsx index daf22d73..1e6642bd 100644 --- a/src/components/SidebarHeader.tsx +++ b/src/components/SidebarHeader.tsx @@ -1,17 +1,20 @@ import React from 'react' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { faChevronRight, faRightFromBracket, faUser } from '@fortawesome/free-solid-svg-icons' +import { faChevronRight, faRightFromBracket, faUser, faList, faThLarge, faImage } from '@fortawesome/free-solid-svg-icons' import { Hooks } from 'applesauce-react' import { useEventModel } from 'applesauce-react/hooks' import { Models } from 'applesauce-core' import IconButton from './IconButton' +import { ViewMode } from './Bookmarks' interface SidebarHeaderProps { onToggleCollapse: () => void onLogout: () => void + viewMode: ViewMode + onViewModeChange: (mode: ViewMode) => void } -const SidebarHeader: React.FC = ({ onToggleCollapse, onLogout }) => { +const SidebarHeader: React.FC = ({ onToggleCollapse, onLogout, viewMode, onViewModeChange }) => { const activeAccount = Hooks.useActiveAccount() const profile = useEventModel(Models.ProfileModel, activeAccount ? [activeAccount.pubkey] : null) @@ -30,30 +33,55 @@ const SidebarHeader: React.FC = ({ onToggleCollapse, onLogou const profileImage = getProfileImage() return ( -
- -
- {profileImage ? ( - {getUserDisplayName()} - ) : ( - - )} + <> +
+ +
+ {profileImage ? ( + {getUserDisplayName()} + ) : ( + + )} +
+
- -
+
+ onViewModeChange('compact')} + title="Compact list view" + ariaLabel="Compact list view" + variant={viewMode === 'compact' ? 'primary' : 'ghost'} + /> + onViewModeChange('cards')} + title="Cards view" + ariaLabel="Cards view" + variant={viewMode === 'cards' ? 'primary' : 'ghost'} + /> + onViewModeChange('large')} + title="Large preview view" + ariaLabel="Large preview view" + variant={viewMode === 'large' ? 'primary' : 'ghost'} + /> +
+ ) } diff --git a/src/index.css b/src/index.css index 528f0074..4fab495d 100644 --- a/src/index.css +++ b/src/index.css @@ -107,7 +107,19 @@ body { background: #1a1a1a; border: 1px solid #333; border-radius: 8px; + margin-bottom: 0.5rem; +} + +.view-mode-controls { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 1rem; + background: #1a1a1a; + border: 1px solid #333; + border-radius: 8px; margin-bottom: 1rem; + justify-content: center; } .profile-avatar { @@ -588,6 +600,10 @@ body { gap: 1rem; } +.bookmarks-grid.bookmarks-compact { + gap: 0.5rem; +} + .individual-bookmark { background: #2a2a2a; padding: 1.25rem; @@ -605,6 +621,88 @@ body { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); } +/* Compact view styles */ +.individual-bookmark.compact { + padding: 0.5rem 0.75rem; + background: transparent; + border-bottom: 1px solid #333; + border-radius: 0; + box-shadow: none; +} + +.individual-bookmark.compact:hover { + background: #2a2a2a; + transform: none; + box-shadow: none; +} + +.compact-header { + display: flex; + align-items: flex-start; + gap: 0.75rem; +} + +.bookmark-type-compact { + display: flex; + align-items: center; + gap: 0.25rem; + color: #646cff; + font-size: 0.9rem; + flex-shrink: 0; + padding-top: 0.25rem; +} + +.compact-content { + flex: 1; + min-width: 0; +} + +.compact-text { + color: #ccc; + font-size: 0.9rem; + line-height: 1.4; + margin-bottom: 0.25rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.compact-meta { + display: flex; + align-items: center; + gap: 0.5rem; + justify-content: space-between; +} + +.bookmark-date-compact { + font-size: 0.75rem; + color: #666; +} + +.compact-read-btn { + background: #28a745; + color: white; + border: none; + padding: 0.25rem 0.5rem; + border-radius: 4px; + cursor: pointer; + font-size: 0.8rem; + display: flex; + align-items: center; + justify-content: center; + min-width: 28px; + height: 24px; + transition: background-color 0.2s ease; +} + +.compact-read-btn:hover { + background: #218838; +} + +.compact-read-btn:active { + transform: translateY(1px); +} + .bookmark-header { display: flex; justify-content: space-between;