- Add localization keys for "open_url" and "open_original_page" in EN/DE
- Create URLUtil.openUrlLabel() helper function for consistent formatting
- Replace incorrect string concatenation with proper localized labels
- Fix: "example.comopen" now displays as "Open example.com" (EN) or "example.com öffnen" (DE)
- Update BookmarkDetailLegacyView, BookmarkDetailView2, and BookmarkCardView
The JavaScript was executing scrollIntoView() but the WebView itself cannot
scroll (isScrollEnabled = false). Fixed by calculating the annotation's Y
position in the WebView and scrolling the outer ScrollView to the correct
position instead.
Changes:
- WebView.swift: Added onScrollToPosition callback and scrollToPosition
message handler. JavaScript now calculates and sends annotation position
to Swift instead of using scrollIntoView().
- NativeWebView.swift: Same changes for iOS 26+ with polling mechanism for
window.__pendingScrollPosition.
- BookmarkDetailLegacyView.swift: Implemented onScrollToPosition callback
that calculates final scroll position (header height + annotation position)
and scrolls the outer ScrollView.
- BookmarkDetailView2.swift: Same implementation as BookmarkDetailLegacyView.
Add conditional visibility for the annotations button in the toolbar based on whether the loaded article contains any rd-annotation tags.
Changes:
- Add hasAnnotations property to BookmarkDetailViewModel
- Check for <rd-annotation tags when processing article content
- Conditionally show/hide annotations button in BookmarkDetailView2
- Move AnnotationColor enum to Constants.swift for centralized color management
- Add hexColor property to provide hex values for JavaScript overlays
- Add cssColorWithOpacity method for flexible opacity control
- Update NativeWebView and WebView to use centralized color values
- Replace modal color picker with inline overlay for better UX
- Implement annotation creation directly from text selection
- Add API endpoint for creating annotations with selectors
Implement text selection detection in NativeWebView:
- Add onTextSelected callback parameter to NativeWebView
- Use JavaScript polling to detect text selections
- Calculate text offsets for precise annotation positioning
- Integrate color picker in BookmarkDetailView2 for iOS 26+
- Match feature parity with legacy WebView implementation
Text selection now works on both WebView implementations.
Implement interactive text annotation feature:
- Add text selection detection via JavaScript in WebView
- Create AnnotationColorPicker with 4 color options (yellow, green, blue, red)
- Integrate color picker sheet in bookmark detail views
- Calculate text offsets for precise annotation positioning
- Add onTextSelected callback for WebView component
- Prepare UI for future API integration
Users can now select text in articles and choose a highlight color.
API integration for persisting annotations will follow.
Add comprehensive annotations feature to bookmark detail views:
- Implement annotations list view with date formatting and state machine
- Add CSS-based highlighting for rd-annotation tags in WebView components
- Support Readeck color scheme (yellow, green, blue, red) for annotations
- Enable tap-to-scroll functionality to navigate to selected annotations
- Integrate annotations button in bookmark detail toolbar
- Add API endpoint and repository layer for fetching annotations
BookmarkDetailView2 enhancements:
- Implement floating action buttons with iOS 26 GlassEffect
- Buttons appear at 90% reading progress with slide-up animation
- Use GlassEffectContainer with liquid glass interaction effect
- Position buttons in bottom-right corner with spring animation
- Auto-hide when scrolling back above 90%
Header image improvements:
- Use aspect fit with blurred background for better image display
- Prevents random cropping of header images
- Maintains full image visibility while filling header space
Debug-only features:
- Add #if DEBUG wrapper for view toggle buttons
- Toggle between legacy and native WebView only in debug builds
Technical details:
- GlassEffectContainer with 52pt buttons and 31pt icons
- Spring animation (response: 0.6, damping: 0.8)
- Combined move and opacity transitions
- Full screen ScrollView with bottom safe area extension
- Blurred background layer for non-filling images
Replace onScrollGeometryChange/onScrollPhaseChange with ContentHeightPreferenceKey
approach for improved scroll performance and accurate read progress tracking.
Changes:
- Add ScrollOffsetPreferenceKey and ContentHeightPreferenceKey for scroll tracking
- Track content end position dynamically as WebView loads
- Calculate progress from scroll offset relative to total scrollable content
- Implement 3% threshold for progress updates to reduce API calls
- Add progress locking at 100% to prevent pixel-variation regressions
- Guarantee 100% update when reaching end of content
- Apply to both BookmarkDetailLegacyView and BookmarkDetailView2
Technical approach:
- Place invisible marker at end of content to measure position in scrollView
- Update initialContentEndPosition as content grows during WebView loading
- Progress = (initialPosition - currentPosition) / (initialPosition - containerHeight)
- Lock progress once 100% reached to avoid 100% -> 99% fluctuations
Added ContentHeightPreferenceKey to track the total ScrollView content height.
The bug: Progress was calculated using only webViewHeight - containerHeight,
which ignores the header, title, and other content above the webview.
The fix: Use total content height (header + title + webview + archive section)
instead of just webViewHeight for accurate progress calculation.
Changes:
- Added ContentHeightPreferenceKey preference key
- Added contentHeight state variable
- Added background GeometryReader to VStack to measure total content height
- Changed progress calculation: contentHeight - containerHeight (not webViewHeight)
Applied to both BookmarkDetailLegacyView and BookmarkDetailView2.
Changed headerView from var to func with width parameter, matching LegacyView.
Added .frame(width: width, height: headerHeight) to constrain header image width.
This was the root cause of content overflow - without explicit width on the
header image, the entire ZStack and its children (including title and webview)
could grow beyond viewport width. Now matches LegacyView implementation exactly.
Added width: geometry.size.width to the spacer Color.clear.frame()
to constrain the VStack width, matching the LegacyView implementation.
This prevents NativeWebView content from overflowing the screen width.
The explicit width on the spacer propagates to the parent VStack,
which then constrains all child views including NativeWebView.
Removed .frame(maxWidth: .infinity) from NativeWebView which was causing
the content to be wider than the viewport. The NativeWebView now respects
the parent container's width constraints set by .padding(.horizontal, 4).
- Implemented ScrollOffsetPreferenceKey for BookmarkDetailView2
- Replaced onScrollGeometryChange + onScrollPhaseChange with onPreferenceChange
- Removed currentScrollOffset and scrollViewHeight state variables
- Changed jumpButton from var to func with containerHeight parameter
- Fixed excessive spacing between header and content by using ZStack layout
Layout fix: Header image is now in ZStack background with content in foreground,
eliminating double spacing that occurred with separate VStacks.
Performance: Same PreferenceKey benefits as LegacyView - more efficient scroll tracking.
- Created BookmarkDetailView2 with native SwiftUI WebView (iOS 26+)
- Refactored BookmarkDetailView as version router
- Renamed original implementation to BookmarkDetailLegacyView
- Moved Archive/Favorite buttons to bottom toolbar using ToolbarItemGroup
- Added toggle button to switch between native and legacy views
- Implemented onScrollPhaseChange for optimized reading progress tracking
- Added NativeWebView component with improved JavaScript height detection
- All changes preserve existing functionality while adding modern alternatives