feat: add bookmark filter buttons by content type

- Add BookmarkFilters component with icon-based filter buttons
- Create bookmarkTypeClassifier utility for content type classification
- Filter bookmarks by article, video, note, or web types
- Apply filters across all bookmark lists (private, public, web, sets)
- Style filter buttons to match existing UI design
This commit is contained in:
Gigi
2025-10-15 21:17:27 +02:00
parent a215d0b026
commit ee1365d3ca
4 changed files with 143 additions and 3 deletions

View File

@@ -0,0 +1,43 @@
import React from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faNewspaper, faStickyNote, faCirclePlay } from '@fortawesome/free-regular-svg-icons'
import { faGlobe, faAsterisk } from '@fortawesome/free-solid-svg-icons'
export type BookmarkFilterType = 'all' | 'article' | 'video' | 'note' | 'web'
interface BookmarkFiltersProps {
selectedFilter: BookmarkFilterType
onFilterChange: (filter: BookmarkFilterType) => void
}
const BookmarkFilters: React.FC<BookmarkFiltersProps> = ({
selectedFilter,
onFilterChange
}) => {
const filters = [
{ type: 'all' as const, icon: faAsterisk, label: 'All' },
{ type: 'article' as const, icon: faNewspaper, label: 'Articles' },
{ type: 'video' as const, icon: faCirclePlay, label: 'Videos' },
{ type: 'note' as const, icon: faStickyNote, label: 'Notes' },
{ type: 'web' as const, icon: faGlobe, label: 'Web' }
]
return (
<div className="bookmark-filters">
{filters.map(filter => (
<button
key={filter.type}
onClick={() => onFilterChange(filter.type)}
className={`filter-btn ${selectedFilter === filter.type ? 'active' : ''}`}
title={filter.label}
aria-label={`Filter by ${filter.label}`}
>
<FontAwesomeIcon icon={filter.icon} />
</button>
))}
</div>
)
}
export default BookmarkFilters

View File

@@ -19,6 +19,8 @@ import AddBookmarkModal from './AddBookmarkModal'
import { createWebBookmark } from '../services/webBookmarkService'
import { RELAYS } from '../config/relays'
import { Hooks } from 'applesauce-react'
import BookmarkFilters, { BookmarkFilterType } from './BookmarkFilters'
import { filterBookmarksByType } from '../utils/bookmarkTypeClassifier'
interface BookmarkListProps {
bookmarks: Bookmark[]
@@ -61,6 +63,7 @@ export const BookmarkList: React.FC<BookmarkListProps> = ({
const bookmarksListRef = useRef<HTMLDivElement>(null)
const friendsColor = settings?.highlightColorFriends || '#f97316'
const [showAddModal, setShowAddModal] = useState(false)
const [selectedFilter, setSelectedFilter] = useState<BookmarkFilterType>('all')
const activeAccount = Hooks.useActiveAccount()
const handleSaveBookmark = async (url: string, title?: string, description?: string, tags?: string[]) => {
@@ -87,9 +90,12 @@ export const BookmarkList: React.FC<BookmarkListProps> = ({
const allIndividualBookmarks = bookmarks.flatMap(b => b.individualBookmarks || [])
.filter(hasContent)
// Apply filter
const filteredBookmarks = filterBookmarksByType(allIndividualBookmarks, selectedFilter)
// Separate bookmarks with setName (kind 30003) from regular bookmarks
const bookmarksWithoutSet = getBookmarksWithoutSet(allIndividualBookmarks)
const bookmarkSets = getBookmarkSets(allIndividualBookmarks)
const bookmarksWithoutSet = getBookmarksWithoutSet(filteredBookmarks)
const bookmarkSets = getBookmarkSets(filteredBookmarks)
// Group non-set bookmarks as before
const groups = groupIndividualBookmarks(bookmarksWithoutSet)
@@ -140,7 +146,18 @@ export const BookmarkList: React.FC<BookmarkListProps> = ({
isMobile={isMobile}
/>
{allIndividualBookmarks.length === 0 ? (
{allIndividualBookmarks.length > 0 && (
<BookmarkFilters
selectedFilter={selectedFilter}
onFilterChange={setSelectedFilter}
/>
)}
{filteredBookmarks.length === 0 && allIndividualBookmarks.length > 0 ? (
<div className="empty-state">
<p>No bookmarks match this filter.</p>
</div>
) : allIndividualBookmarks.length === 0 ? (
loading ? (
<div className={`bookmarks-list ${viewMode}`} aria-busy="true">
<div className={`bookmarks-grid bookmarks-${viewMode}`}>