feat(pdf): paragraph-precise bookmarks
Some checks failed
CI / Windows build (push) Has been cancelled
Some checks failed
CI / Windows build (push) Has been cancelled
Add a bookmark tool to the PDF editor. A bookmark anchors to a precise location: when text is selected it captures the selection's normalized rect + the start char index in the page text (the true paragraph anchor); with no selection it falls back to the tapped page + point. - Bookmark model gains optional normalized anchor rect + charIndex + label (all absent from JSON when null, so old sidecars still load). - Bookmarks persist in the sidecar (scheduleBookmarkUpsert) and load on open; a bookmarks panel lists them and tapping one jumps to its page. Delete is persisted. Scoped to the PDF editor (note bookmarks later); scroll-to-anchor is page-level for now. analyze clean, 391 tests green.
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import '../../models/bookmark.dart';
|
||||
import '../../models/scratch_link.dart';
|
||||
import '../../storage/badnote_sidecar.dart';
|
||||
import '../../storage/sidecar_store.dart';
|
||||
@@ -139,6 +140,9 @@ class SidecarRepository {
|
||||
/// Scratch-link anchors loaded from the sidecar.
|
||||
List<SidecarScratchLink> get loadedScratchLinks => _sidecar.scratchLinks;
|
||||
|
||||
/// Bookmarks loaded from the sidecar.
|
||||
List<Bookmark> get loadedBookmarks => _sidecar.bookmarks;
|
||||
|
||||
/// The current in-memory sidecar (for tests / inspection).
|
||||
BadnoteSidecar get sidecar => _sidecar;
|
||||
|
||||
@@ -232,6 +236,30 @@ class SidecarRepository {
|
||||
_replace(scratchLinks: next);
|
||||
}
|
||||
|
||||
/// Add (or update, by id) a bookmark and schedule a save.
|
||||
void scheduleBookmarkUpsert(Bookmark bookmark) {
|
||||
final next = List<Bookmark>.of(_sidecar.bookmarks);
|
||||
final idx = next.indexWhere((b) => b.id == bookmark.id);
|
||||
if (idx == -1) {
|
||||
next.add(bookmark);
|
||||
} else {
|
||||
next[idx] = bookmark;
|
||||
}
|
||||
_replace(bookmarks: next);
|
||||
}
|
||||
|
||||
/// Remove the bookmark by [bookmarkId] and schedule a save.
|
||||
void scheduleBookmarkDelete(String bookmarkId) {
|
||||
final next =
|
||||
_sidecar.bookmarks.where((b) => b.id != bookmarkId).toList();
|
||||
_replace(bookmarks: next);
|
||||
}
|
||||
|
||||
/// Replace the whole bookmark list and schedule a save.
|
||||
void scheduleBookmarksSave(List<Bookmark> bookmarks) {
|
||||
_replace(bookmarks: List<Bookmark>.of(bookmarks));
|
||||
}
|
||||
|
||||
/// The embedded scratchpad for [linkId], or null if the anchor is unknown.
|
||||
SidecarScratchpad? scratchpadFor(String linkId) {
|
||||
for (final s in _sidecar.scratchLinks) {
|
||||
@@ -274,6 +302,7 @@ class SidecarRepository {
|
||||
String? title,
|
||||
Map<int, List<EditorStroke>>? strokes,
|
||||
Map<int, List<SidecarHighlight>>? highlights,
|
||||
List<Bookmark>? bookmarks,
|
||||
List<SidecarScratchLink>? scratchLinks,
|
||||
String? ocrText,
|
||||
bool clearOcrText = false,
|
||||
@@ -291,7 +320,7 @@ class SidecarRepository {
|
||||
updatedAt: DateTime.now().toUtc(),
|
||||
strokes: strokes ?? _sidecar.strokes,
|
||||
highlights: highlights ?? _sidecar.highlights,
|
||||
bookmarks: _sidecar.bookmarks,
|
||||
bookmarks: bookmarks ?? _sidecar.bookmarks,
|
||||
scratchLinks: scratchLinks ?? _sidecar.scratchLinks,
|
||||
ocrText: clearOcrText ? null : (ocrText ?? _sidecar.ocrText),
|
||||
background: background ?? _sidecar.background,
|
||||
|
||||
Reference in New Issue
Block a user