diff --git a/src/components/ArchiveFilters.tsx b/src/components/ArchiveFilters.tsx new file mode 100644 index 00000000..0b1404f2 --- /dev/null +++ b/src/components/ArchiveFilters.tsx @@ -0,0 +1,47 @@ +import React from 'react' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faBookOpen, faBookmark, faCheckCircle, faAsterisk } from '@fortawesome/free-solid-svg-icons' +import { faBooks } from '../icons/customIcons' + +export type ArchiveFilterType = 'all' | 'to-read' | 'reading' | 'completed' | 'marked' + +interface ArchiveFiltersProps { + selectedFilter: ArchiveFilterType + onFilterChange: (filter: ArchiveFilterType) => void +} + +const ArchiveFilters: React.FC = ({ selectedFilter, onFilterChange }) => { + const filters = [ + { type: 'all' as const, icon: faAsterisk, label: 'All' }, + { type: 'to-read' as const, icon: faBookmark, label: 'To Read' }, + { type: 'reading' as const, icon: faBookOpen, label: 'Reading' }, + { type: 'completed' as const, icon: faCheckCircle, label: 'Completed' }, + { type: 'marked' as const, icon: faBooks, label: 'Marked as Read' } + ] + + return ( +
+ {filters.map(filter => { + const isActive = selectedFilter === filter.type + // Only "completed" gets green color, everything else uses default blue + const activeStyle = isActive && filter.type === 'completed' ? { color: '#10b981' } : undefined + + return ( + + ) + })} +
+ ) +} + +export default ArchiveFilters + diff --git a/src/components/BookmarkItem.tsx b/src/components/BookmarkItem.tsx index d1d19de9..2d75f737 100644 --- a/src/components/BookmarkItem.tsx +++ b/src/components/BookmarkItem.tsx @@ -19,10 +19,9 @@ interface BookmarkItemProps { index: number onSelectUrl?: (url: string, bookmark?: { id: string; kind: number; tags: string[][]; pubkey: string }) => void viewMode?: ViewMode - readingProgress?: number // 0-1 reading progress (optional) } -export const BookmarkItem: React.FC = ({ bookmark, index, onSelectUrl, viewMode = 'cards', readingProgress }) => { +export const BookmarkItem: React.FC = ({ bookmark, index, onSelectUrl, viewMode = 'cards' }) => { const [ogImage, setOgImage] = useState(null) const short = (v: string) => `${v.slice(0, 8)}...${v.slice(-8)}` @@ -151,7 +150,7 @@ export const BookmarkItem: React.FC = ({ bookmark, index, onS if (viewMode === 'large') { const previewImage = articleImage || instantPreview || ogImage - return + return } return diff --git a/src/components/BookmarkList.tsx b/src/components/BookmarkList.tsx index f4c9bfea..74e6adc5 100644 --- a/src/components/BookmarkList.tsx +++ b/src/components/BookmarkList.tsx @@ -21,7 +21,6 @@ import { RELAYS } from '../config/relays' import { Hooks } from 'applesauce-react' import BookmarkFilters, { BookmarkFilterType } from './BookmarkFilters' import { filterBookmarksByType } from '../utils/bookmarkTypeClassifier' -import ReadingProgressFilters, { ReadingProgressFilterType } from './ReadingProgressFilters' interface BookmarkListProps { bookmarks: Bookmark[] @@ -40,8 +39,6 @@ interface BookmarkListProps { relayPool: RelayPool | null isMobile?: boolean settings?: UserSettings - readingPositions?: Map - markedAsReadIds?: Set } export const BookmarkList: React.FC = ({ @@ -60,16 +57,13 @@ export const BookmarkList: React.FC = ({ loading = false, relayPool, isMobile = false, - settings, - readingPositions, - markedAsReadIds + settings }) => { const navigate = useNavigate() const bookmarksListRef = useRef(null) const friendsColor = settings?.highlightColorFriends || '#f97316' const [showAddModal, setShowAddModal] = useState(false) const [selectedFilter, setSelectedFilter] = useState('all') - const [readingProgressFilter, setReadingProgressFilter] = useState('all') const activeAccount = Hooks.useActiveAccount() const handleSaveBookmark = async (url: string, title?: string, description?: string, tags?: string[]) => { @@ -96,42 +90,8 @@ export const BookmarkList: React.FC = ({ const allIndividualBookmarks = bookmarks.flatMap(b => b.individualBookmarks || []) .filter(hasContent) - // Apply type filter - const typeFilteredBookmarks = filterBookmarksByType(allIndividualBookmarks, selectedFilter) - - // Apply reading progress filter (only affects kind:30023 articles) - const filteredBookmarks = typeFilteredBookmarks.filter(bookmark => { - // Only apply reading progress filter to kind:30023 articles - if (bookmark.kind !== 30023) return true - - // If reading progress filter is 'all', show all articles - if (readingProgressFilter === 'all') return true - - const isMarkedAsRead = markedAsReadIds?.has(bookmark.id) - const position = readingPositions?.get(bookmark.id) - - // Marked-as-read articles are always treated as 100% complete - if (isMarkedAsRead) { - return readingProgressFilter === 'completed' - } - - switch (readingProgressFilter) { - case 'unopened': - // No reading progress - never opened - return !position || position === 0 - case 'started': - // 0-10% reading progress - opened but not read far - return position !== undefined && position > 0 && position <= 0.10 - case 'reading': - // Has some progress but not completed (11% - 94%) - return position !== undefined && position > 0.10 && position <= 0.94 - case 'completed': - // 95% or more read - return position !== undefined && position >= 0.95 - default: - return true - } - }) + // Apply filter + const filteredBookmarks = filterBookmarksByType(allIndividualBookmarks, selectedFilter) // Separate bookmarks with setName (kind 30003) from regular bookmarks const bookmarksWithoutSet = getBookmarksWithoutSet(filteredBookmarks) @@ -244,7 +204,6 @@ export const BookmarkList: React.FC = ({ index={index} onSelectUrl={onSelectUrl} viewMode={viewMode} - readingProgress={markedAsReadIds?.has(individualBookmark.id) ? 1.0 : readingPositions?.get(individualBookmark.id)} /> ))} @@ -252,17 +211,6 @@ export const BookmarkList: React.FC = ({ ))} )} - - {/* Reading progress filters - only show if there are kind:30023 articles */} - {typeFilteredBookmarks.some(b => b.kind === 30023) && ( -
- -
- )} -
= ({ const { isReadingComplete, progressPercentage, saveNow } = useReadingPosition({ enabled: isTextContent, syncEnabled: settings?.syncReadingPosition, - onSave: handleSavePosition + onSave: handleSavePosition, + onReadingComplete: () => { + // Optional: Auto-mark as read when reading is complete + if (activeAccount && !isMarkedAsRead) { + // Could trigger auto-mark as read here if desired + } + } }) - // Determine if we're on a nostr-native article (/a/) or external URL (/r/) - const isNostrArticle = selectedUrl && selectedUrl.startsWith('nostr:') - - // Define handleMarkAsRead with useCallback to use in auto-mark effect - const handleMarkAsRead = useCallback(() => { - if (!activeAccount || !relayPool || isMarkedAsRead) { - return - } - - // Instantly update UI with checkmark animation - setIsMarkedAsRead(true) - setShowCheckAnimation(true) - - // Reset animation after it completes (2.5s for full fancy animation) - setTimeout(() => { - setShowCheckAnimation(false) - }, 2500) - - // Fire-and-forget: publish in background without blocking UI - ;(async () => { - try { - if (isNostrArticle && currentArticle) { - await createEventReaction( - currentArticle.id, - currentArticle.pubkey, - currentArticle.kind, - activeAccount, - relayPool - ) - console.log('✅ Marked nostr article as read') - } else if (selectedUrl) { - await createWebsiteReaction( - selectedUrl, - activeAccount, - relayPool - ) - console.log('✅ Marked website as read') - } - } catch (error) { - console.error('Failed to mark as read:', error) - // Revert UI state on error - setIsMarkedAsRead(false) - } - })() - }, [activeAccount, relayPool, isMarkedAsRead, isNostrArticle, currentArticle, selectedUrl]) - - // Auto-mark as read when reaching 100% for 2 seconds - useEffect(() => { - if (!settings?.autoMarkAsReadAt100 || isMarkedAsRead || !activeAccount || !relayPool) { - return - } - - // Only trigger when progress is exactly 100% - if (progressPercentage === 100) { - console.log('📍 [ContentPanel] Progress at 100%, starting 2-second timer for auto-mark') - - const timer = setTimeout(() => { - console.log('✅ [ContentPanel] Auto-marking as read after 2 seconds at 100%') - handleMarkAsRead() - }, 2000) - - return () => { - console.log('âšī¸ [ContentPanel] Canceling auto-mark timer (progress changed or unmounting)') - clearTimeout(timer) - } - } - }, [progressPercentage, settings?.autoMarkAsReadAt100, isMarkedAsRead, activeAccount, relayPool, handleMarkAsRead]) - // Load saved reading position when article loads useEffect(() => { if (!isTextContent || !activeAccount || !relayPool || !eventStore || !articleIdentifier) { @@ -288,25 +226,19 @@ const ContentPanel: React.FC = ({ if (savedPosition && savedPosition.position > 0.05 && savedPosition.position < 1) { console.log('đŸŽ¯ [ContentPanel] Restoring position:', Math.round(savedPosition.position * 100) + '%') - - // Only auto-scroll if the setting is enabled (default: true) - if (settings?.autoScrollToPosition !== false) { - // Wait for content to be fully rendered before scrolling - setTimeout(() => { - const documentHeight = document.documentElement.scrollHeight - const windowHeight = window.innerHeight - const scrollTop = savedPosition.position * (documentHeight - windowHeight) - - window.scrollTo({ - top: scrollTop, - behavior: 'smooth' - }) - - console.log('✅ [ContentPanel] Restored to position:', Math.round(savedPosition.position * 100) + '%', 'scrollTop:', scrollTop) - }, 500) // Give content time to render - } else { - console.log('â­ī¸ [ContentPanel] Auto-scroll disabled in settings') - } + // Wait for content to be fully rendered before scrolling + setTimeout(() => { + const documentHeight = document.documentElement.scrollHeight + const windowHeight = window.innerHeight + const scrollTop = savedPosition.position * (documentHeight - windowHeight) + + window.scrollTo({ + top: scrollTop, + behavior: 'smooth' + }) + + console.log('✅ [ContentPanel] Restored to position:', Math.round(savedPosition.position * 100) + '%', 'scrollTop:', scrollTop) + }, 500) // Give content time to render } else if (savedPosition) { if (savedPosition.position === 1) { console.log('✅ [ContentPanel] Article completed (100%), starting from top') @@ -320,7 +252,7 @@ const ContentPanel: React.FC = ({ } loadPosition() - }, [isTextContent, activeAccount, relayPool, eventStore, articleIdentifier, settings?.syncReadingPosition, settings?.autoScrollToPosition, selectedUrl]) + }, [isTextContent, activeAccount, relayPool, eventStore, articleIdentifier, settings?.syncReadingPosition, selectedUrl]) // Save position before unmounting or changing article useEffect(() => { @@ -392,6 +324,8 @@ const ContentPanel: React.FC = ({ const hasHighlights = relevantHighlights.length > 0 + // Determine if we're on a nostr-native article (/a/) or external URL (/r/) + const isNostrArticle = selectedUrl && selectedUrl.startsWith('nostr:') const isExternalVideo = !isNostrArticle && !!selectedUrl && ['youtube', 'video'].includes(classifyUrl(selectedUrl).type) // Track external video duration (in seconds) for display in header @@ -660,6 +594,48 @@ const ContentPanel: React.FC = ({ checkReadStatus() }, [selectedUrl, currentArticle, activeAccount, relayPool, isNostrArticle]) + + const handleMarkAsRead = () => { + if (!activeAccount || !relayPool || isMarkedAsRead) { + return + } + + // Instantly update UI with checkmark animation + setIsMarkedAsRead(true) + setShowCheckAnimation(true) + + // Reset animation after it completes + setTimeout(() => { + setShowCheckAnimation(false) + }, 600) + + // Fire-and-forget: publish in background without blocking UI + ;(async () => { + try { + if (isNostrArticle && currentArticle) { + await createEventReaction( + currentArticle.id, + currentArticle.pubkey, + currentArticle.kind, + activeAccount, + relayPool + ) + console.log('✅ Marked nostr article as read') + } else if (selectedUrl) { + await createWebsiteReaction( + selectedUrl, + activeAccount, + relayPool + ) + console.log('✅ Marked website as read') + } + } catch (error) { + console.error('Failed to mark as read:', error) + // Revert UI state on error + setIsMarkedAsRead(false) + } + })() + } if (!selectedUrl) { return ( diff --git a/src/components/Explore.tsx b/src/components/Explore.tsx index 8a5cd483..fae4c2ad 100644 --- a/src/components/Explore.tsx +++ b/src/components/Explore.tsx @@ -22,8 +22,6 @@ import { usePullToRefresh } from 'use-pull-to-refresh' import RefreshIndicator from './RefreshIndicator' import { classifyHighlights } from '../utils/highlightClassification' import { HighlightVisibility } from './HighlightsPanel' -import { loadReadingPosition, generateArticleIdentifier } from '../services/readingPositionService' -import { fetchReadArticles } from '../services/libraryService' interface ExploreProps { relayPool: RelayPool @@ -43,8 +41,6 @@ const Explore: React.FC = ({ relayPool, eventStore, settings, acti const [followedPubkeys, setFollowedPubkeys] = useState>(new Set()) const [loading, setLoading] = useState(true) const [refreshTrigger, setRefreshTrigger] = useState(0) - const [readingPositions, setReadingPositions] = useState>(new Map()) - const [markedAsReadIds, setMarkedAsReadIds] = useState>(new Set()) // Visibility filters (defaults from settings, or friends only) const [visibility, setVisibility] = useState({ @@ -217,88 +213,6 @@ const Explore: React.FC = ({ relayPool, eventStore, settings, acti loadData() }, [relayPool, activeAccount, refreshTrigger, eventStore, settings]) - // Fetch marked-as-read articles - useEffect(() => { - const loadMarkedAsRead = async () => { - if (!activeAccount || !eventStore) { - return - } - - try { - const readArticles = await fetchReadArticles(relayPool, activeAccount.pubkey) - - // Create a set of article IDs that are marked as read - const markedArticleIds = new Set() - - // For each read article, add both event ID and coordinate format - for (const readArticle of readArticles) { - // Add the event ID directly - markedArticleIds.add(readArticle.id) - - // For nostr-native articles (kind:7 reactions), also add the coordinate format - if (readArticle.eventId && readArticle.eventAuthor && readArticle.eventKind) { - // Try to get the event from the eventStore to find the 'd' tag - const event = eventStore.getEvent(readArticle.eventId) - if (event) { - const dTag = event.tags?.find((t: string[]) => t[0] === 'd')?.[1] || '' - const coordinate = `${event.kind}:${event.pubkey}:${dTag}` - markedArticleIds.add(coordinate) - } - } - } - - setMarkedAsReadIds(markedArticleIds) - } catch (error) { - console.warn('âš ī¸ [Explore] Failed to load marked-as-read articles:', error) - } - } - - loadMarkedAsRead() - }, [relayPool, activeAccount, eventStore]) - - // Load reading positions for blog posts - useEffect(() => { - const loadPositions = async () => { - if (!activeAccount || !eventStore || blogPosts.length === 0 || !settings?.syncReadingPosition) { - return - } - - const positions = new Map() - - await Promise.all( - blogPosts.map(async (post) => { - try { - const dTag = post.event.tags.find(t => t[0] === 'd')?.[1] || '' - const naddr = nip19.naddrEncode({ - kind: 30023, - pubkey: post.author, - identifier: dTag - }) - const articleUrl = `nostr:${naddr}` - const identifier = generateArticleIdentifier(articleUrl) - - const savedPosition = await loadReadingPosition( - relayPool, - eventStore, - activeAccount.pubkey, - identifier - ) - - if (savedPosition && savedPosition.position > 0) { - positions.set(post.event.id, savedPosition.position) - } - } catch (error) { - console.warn('âš ī¸ [Explore] Failed to load reading position for post:', error) - } - }) - ) - - setReadingPositions(positions) - } - - loadPositions() - }, [blogPosts, activeAccount, relayPool, eventStore, settings?.syncReadingPosition]) - // Pull-to-refresh const { isRefreshing, pullPosition } = usePullToRefresh({ onRefresh: () => { @@ -388,7 +302,6 @@ const Explore: React.FC = ({ relayPool, eventStore, settings, acti post={post} href={getPostUrl(post)} level={post.level} - readingProgress={markedAsReadIds.has(post.event.id) ? 1.0 : readingPositions.get(post.event.id)} /> ))}
diff --git a/src/components/Settings/LayoutBehaviorSettings.tsx b/src/components/Settings/LayoutBehaviorSettings.tsx index 847ed9d2..efc17384 100644 --- a/src/components/Settings/LayoutBehaviorSettings.tsx +++ b/src/components/Settings/LayoutBehaviorSettings.tsx @@ -117,32 +117,6 @@ const LayoutBehaviorSettings: React.FC = ({ setting Sync reading position across devices
- -
- -
- -
- -
) } diff --git a/src/components/ThreePaneLayout.tsx b/src/components/ThreePaneLayout.tsx index 113b4396..b3912f90 100644 --- a/src/components/ThreePaneLayout.tsx +++ b/src/components/ThreePaneLayout.tsx @@ -47,8 +47,6 @@ interface ThreePaneLayoutProps { onRefresh: () => void relayPool: RelayPool | null eventStore: IEventStore | null - readingPositions?: Map - markedAsReadIds?: Set // Content pane readerLoading: boolean @@ -326,8 +324,6 @@ const ThreePaneLayout: React.FC = (props) => { loading={props.bookmarksLoading} relayPool={props.relayPool} isMobile={isMobile} - readingPositions={props.readingPositions} - markedAsReadIds={props.markedAsReadIds} settings={props.settings} /> diff --git a/src/hooks/useBookmarksData.ts b/src/hooks/useBookmarksData.ts index f9f07f0d..e7b5a1c8 100644 --- a/src/hooks/useBookmarksData.ts +++ b/src/hooks/useBookmarksData.ts @@ -1,16 +1,12 @@ import { useState, useEffect, useCallback } from 'react' import { RelayPool } from 'applesauce-relay' import { IAccount, AccountManager } from 'applesauce-accounts' -import { IEventStore } from 'applesauce-core' import { Bookmark } from '../types/bookmarks' import { Highlight } from '../types/highlights' import { fetchBookmarks } from '../services/bookmarkService' import { fetchHighlights, fetchHighlightsForArticle } from '../services/highlightService' import { fetchContacts } from '../services/contactService' import { UserSettings } from '../services/settingsService' -import { loadReadingPosition, generateArticleIdentifier } from '../services/readingPositionService' -import { fetchReadArticles } from '../services/libraryService' -import { nip19 } from 'nostr-tools' interface UseBookmarksDataParams { relayPool: RelayPool | null @@ -21,7 +17,6 @@ interface UseBookmarksDataParams { currentArticleCoordinate?: string currentArticleEventId?: string settings?: UserSettings - eventStore?: IEventStore } export const useBookmarksData = ({ @@ -32,8 +27,7 @@ export const useBookmarksData = ({ externalUrl, currentArticleCoordinate, currentArticleEventId, - settings, - eventStore + settings }: UseBookmarksDataParams) => { const [bookmarks, setBookmarks] = useState([]) const [bookmarksLoading, setBookmarksLoading] = useState(true) @@ -42,8 +36,6 @@ export const useBookmarksData = ({ const [followedPubkeys, setFollowedPubkeys] = useState>(new Set()) const [isRefreshing, setIsRefreshing] = useState(false) const [lastFetchTime, setLastFetchTime] = useState(null) - const [readingPositions, setReadingPositions] = useState>(new Map()) - const [markedAsReadIds, setMarkedAsReadIds] = useState>(new Set()) const handleFetchContacts = useCallback(async () => { if (!relayPool || !activeAccount) return @@ -133,93 +125,6 @@ export const useBookmarksData = ({ handleFetchContacts() }, [relayPool, activeAccount, naddr, externalUrl, handleFetchHighlights, handleFetchContacts]) - // Fetch marked-as-read articles - useEffect(() => { - const loadMarkedAsRead = async () => { - if (!activeAccount || !relayPool || !eventStore || bookmarks.length === 0) { - return - } - - try { - const readArticles = await fetchReadArticles(relayPool, activeAccount.pubkey) - - // Create a set of bookmark IDs that are marked as read - const markedBookmarkIds = new Set() - - // For each read article, we need to match it to bookmark IDs - for (const readArticle of readArticles) { - // Add the event ID directly (for web bookmarks and legacy compatibility) - markedBookmarkIds.add(readArticle.id) - - // For nostr-native articles (kind:7 reactions), also add the coordinate format - if (readArticle.eventId && readArticle.eventAuthor && readArticle.eventKind) { - // Try to get the event from the eventStore to find the 'd' tag - const event = eventStore.getEvent(readArticle.eventId) - if (event) { - const dTag = event.tags?.find((t: string[]) => t[0] === 'd')?.[1] || '' - const coordinate = `${event.kind}:${event.pubkey}:${dTag}` - markedBookmarkIds.add(coordinate) - } - } - } - - setMarkedAsReadIds(markedBookmarkIds) - } catch (error) { - console.warn('âš ī¸ [Bookmarks] Failed to load marked-as-read articles:', error) - } - } - - loadMarkedAsRead() - }, [relayPool, activeAccount, eventStore, bookmarks]) - - // Load reading positions for bookmarked articles (kind:30023) - useEffect(() => { - const loadPositions = async () => { - if (!activeAccount || !relayPool || !eventStore || bookmarks.length === 0 || !settings?.syncReadingPosition) { - return - } - - const positions = new Map() - - // Extract all kind:30023 articles from bookmarks - const articles = bookmarks.flatMap(bookmark => - (bookmark.individualBookmarks || []).filter(item => item.kind === 30023) - ) - - await Promise.all( - articles.map(async (article) => { - try { - const dTag = article.tags.find(t => t[0] === 'd')?.[1] || '' - const naddr = nip19.naddrEncode({ - kind: 30023, - pubkey: article.pubkey, - identifier: dTag - }) - const articleUrl = `nostr:${naddr}` - const identifier = generateArticleIdentifier(articleUrl) - - const savedPosition = await loadReadingPosition( - relayPool, - eventStore, - activeAccount.pubkey, - identifier - ) - - if (savedPosition && savedPosition.position > 0) { - positions.set(article.id, savedPosition.position) - } - } catch (error) { - console.warn('âš ī¸ [Bookmarks] Failed to load reading position for article:', error) - } - }) - ) - - setReadingPositions(positions) - } - - loadPositions() - }, [bookmarks, activeAccount, relayPool, eventStore, settings?.syncReadingPosition]) - return { bookmarks, bookmarksLoading, @@ -232,9 +137,7 @@ export const useBookmarksData = ({ lastFetchTime, handleFetchBookmarks, handleFetchHighlights, - handleRefreshAll, - readingPositions, - markedAsReadIds + handleRefreshAll } } diff --git a/src/services/meCache.ts b/src/services/meCache.ts index b7b3bcd4..53b59a6a 100644 --- a/src/services/meCache.ts +++ b/src/services/meCache.ts @@ -1,12 +1,11 @@ import { Highlight } from '../types/highlights' import { Bookmark } from '../types/bookmarks' -import { ReadItem } from './readsService' +import { BlogPostPreview } from './exploreService' export interface MeCache { highlights: Highlight[] bookmarks: Bookmark[] - reads: ReadItem[] - links: ReadItem[] + readArticles: BlogPostPreview[] timestamp: number } @@ -22,14 +21,12 @@ export function setCachedMeData( pubkey: string, highlights: Highlight[], bookmarks: Bookmark[], - reads: ReadItem[], - links: ReadItem[] = [] + readArticles: BlogPostPreview[] ): void { meCache.set(pubkey, { highlights, bookmarks, - reads, - links, + readArticles, timestamp: Date.now() }) } @@ -48,10 +45,10 @@ export function updateCachedBookmarks(pubkey: string, bookmarks: Bookmark[]): vo } } -export function updateCachedReads(pubkey: string, reads: ReadItem[]): void { +export function updateCachedReadArticles(pubkey: string, readArticles: BlogPostPreview[]): void { const existing = meCache.get(pubkey) if (existing) { - meCache.set(pubkey, { ...existing, reads, timestamp: Date.now() }) + meCache.set(pubkey, { ...existing, readArticles, timestamp: Date.now() }) } } diff --git a/src/services/settingsService.ts b/src/services/settingsService.ts index ed5edcff..a36c7879 100644 --- a/src/services/settingsService.ts +++ b/src/services/settingsService.ts @@ -56,8 +56,6 @@ export interface UserSettings { paragraphAlignment?: 'left' | 'justify' // default: justify // Reading position sync syncReadingPosition?: boolean // default: false (opt-in) - autoScrollToPosition?: boolean // default: true (auto-scroll to last reading position) - autoMarkAsReadAt100?: boolean // default: false (auto-mark as read when reaching 100% for 2 seconds) } export async function loadSettings( diff --git a/src/styles/components/reader.css b/src/styles/components/reader.css index 04e5f6cd..7cd148a6 100644 --- a/src/styles/components/reader.css +++ b/src/styles/components/reader.css @@ -216,72 +216,7 @@ .mark-as-read-btn:hover:not(:disabled) { background: var(--color-border); border-color: var(--color-text-muted); transform: translateY(-1px); } .mark-as-read-btn:active:not(:disabled) { transform: translateY(0); } .mark-as-read-btn:disabled { opacity: 0.6; cursor: not-allowed; } -.mark-as-read-btn svg { font-size: 1.1rem; transition: transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1); } - -/* Fancy Mark as Read animation */ -@keyframes markAsReadSuccess { - 0% { - background: var(--color-bg-elevated); - border-color: var(--color-border-subtle); - transform: scale(1); - box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); - } - 10% { - transform: scale(1.05); - box-shadow: 0 0 0 8px rgba(16, 185, 129, 0.3); - } - 25% { - background: linear-gradient(135deg, #10b981 0%, #059669 100%); - border-color: #10b981; - color: white; - transform: scale(1.02); - box-shadow: 0 4px 20px rgba(16, 185, 129, 0.4); - } - 65% { - background: linear-gradient(135deg, #10b981 0%, #059669 100%); - border-color: #10b981; - color: white; - transform: scale(1.02); - box-shadow: 0 4px 20px rgba(16, 185, 129, 0.4); - } - 100% { - background: #6b7280; - border-color: #6b7280; - color: white; - transform: scale(1); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); - } -} - -@keyframes iconSpin { - 0% { - transform: rotate(0deg) scale(1); - } - 15% { - transform: rotate(0deg) scale(1.2); - } - 50% { - transform: rotate(360deg) scale(1.2); - } - 100% { - transform: rotate(360deg) scale(1); - } -} - -.mark-as-read-btn.animating { - animation: markAsReadSuccess 2.5s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; - pointer-events: none; -} - -.mark-as-read-btn.animating svg { - animation: iconSpin 0.8s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; -} - -.mark-as-read-btn.marked { - background: #6b7280; - border-color: #6b7280; - color: white; -} +.mark-as-read-btn svg { font-size: 1.1rem; } @media (max-width: 768px) { .reader { max-width: 100%; diff --git a/src/styles/layout/sidebar.css b/src/styles/layout/sidebar.css index d199a9b5..192c4eb5 100644 --- a/src/styles/layout/sidebar.css +++ b/src/styles/layout/sidebar.css @@ -211,12 +211,3 @@ background: transparent; } -/* Reading progress filters in bookmarks sidebar - add top border, remove bottom border to avoid double border with view-mode-controls */ -.reading-progress-filters-wrapper { - border-top: 1px solid var(--color-border); -} - -.reading-progress-filters-wrapper .bookmark-filters { - border-bottom: none; -} -