feat(pdf): move ebook reader to embedpdf, add bookmarks, remove old pdf.js stuff#393
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds PDF page-number bookmark support backend (DTOs, entity, repo, service, DB migration) and replaces ngx-extended-pdf-viewer with an EmbedPDF-based frontend reader plus sidebar, bookmark/annotation services, converters, UI/templates, styles, and selection link handling. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant PdfReader as PdfReaderComponent
participant EmbedPdf as EmbedPdfBookService
participant PdfBookmarkSvc as PdfBookmarkService
participant BookMarkApi as Frontend Bookmark API Client
participant Backend
User->>PdfReader: Open book
PdfReader->>EmbedPdf: init(target, url, theme)
EmbedPdf-->>PdfReader: documentOpened / pageChange / layoutReady
PdfReader->>PdfBookmarkSvc: initialize(bookId)
PdfReader->>PdfBookmarkSvc: loadBookmarks()
PdfBookmarkSvc->>BookMarkApi: GET /api/bookmarks?bookId
BookMarkApi->>Backend: HTTP GET
Backend-->>BookMarkApi: bookmarks (may include pageNumber)
BookMarkApi-->>PdfBookmarkSvc: bookmarks[]
PdfBookmarkSvc-->>PdfReader: cached bookmarks
User->>PdfReader: Toggle bookmark on page N
PdfReader->>PdfBookmarkSvc: toggleBookmark(N)
PdfBookmarkSvc->>BookMarkApi: POST /api/bookmarks {pageNumber:N,...}
BookMarkApi->>Backend: POST
Backend->>Backend: validateNoDuplicatePdfBookmark(pageNumber, bookId, userId)
alt duplicate exists
Backend-->>BookMarkApi: 409 Conflict
BookMarkApi-->>PdfBookmarkSvc: error 409
PdfBookmarkSvc-->>PdfReader: show "bookmark exists" toast
else created
Backend-->>BookMarkApi: 201 Created + bookmark
BookMarkApi-->>PdfBookmarkSvc: bookmark
PdfBookmarkSvc-->>PdfReader: update cache, show success toast
end
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 15
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
booklore-api/src/main/java/org/booklore/service/book/BookMarkService.java (1)
81-89:⚠️ Potential issue | 🟠 MajorPage-number updates bypass the duplicate-bookmark guard.
createBookmark()rejects duplicate PDF bookmarks, butupdateBookmark()still only revalidates CFIs beforeapplyUpdates()mutatespageNumber. Changing a bookmark to an already-bookmarked page will therefore create the exact duplicate this PR is supposed to prevent. Please add the same exclude-id uniqueness check for page numbers that you already use for CFI updates.Also applies to: 160-166
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@booklore-api/src/main/java/org/booklore/service/book/BookMarkService.java` around lines 81 - 89, updateBookmark currently checks CFI uniqueness but skips pageNumber, so changing pageNumber can create duplicates; before calling applyUpdates in BookMarkService.updateBookmark, if request.getPageNumber() != null call the same uniqueness check used for CFIs (i.e. validateNoDuplicateBookmark) passing request.getPageNumber(), the existing bookmark.getBookId(), bookmark.getUserId(), and bookmarkId as the exclude-id so the current bookmark is ignored; mirror the CFI-path logic used in createBookmark/updateBookmark to prevent duplicate page-number bookmarks.frontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts (1)
249-255:⚠️ Potential issue | 🟠 MajorPersist the spread mode you actually apply.
spreadModenever gets initialized from the savedthis.spread, andcycleSpreadMode()never writes back tothis.spread. The viewer changes for the current session, but reloads will still use the stale saved preference.💡 Suggested fix
if (globalOrIndividual === 'Global') { this.zoom = myself.userSettings.pdfReaderSetting.pageZoom || 'page-fit'; this.spread = myself.userSettings.pdfReaderSetting.pageSpread || 'off'; } else { this.zoom = pdfPrefs.pdfSettings?.zoom || myself.userSettings.pdfReaderSetting.pageZoom || 'page-fit'; this.spread = pdfPrefs.pdfSettings?.spread || myself.userSettings.pdfReaderSetting.pageSpread || 'off'; this.isDarkTheme = pdfPrefs.pdfSettings?.isDarkTheme ?? true; } + this.spreadMode = this.spread === 'off' ? 'none' : this.spread; @@ cycleSpreadMode(): void { const modes: ('none' | 'odd' | 'even')[] = ['none', 'odd', 'even']; const idx = modes.indexOf(this.spreadMode); this.spreadMode = modes[(idx + 1) % modes.length]; + this.spread = this.spreadMode === 'none' ? 'off' : this.spreadMode; this.embedPdfBook.setSpreadMode(this.spreadMode); + this.updateViewerSetting(); }Also applies to: 519-524
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts` around lines 249 - 255, Initialize the viewer's spreadMode from the persisted this.spread when setting up the viewer (so spreadMode reflects the currently applied preference) and update/persist this.spread whenever cycleSpreadMode() changes spreadMode; specifically, in the initialization code that reads pdfPrefs.pdfSettings and myself.userSettings.pdfReaderSetting set spreadMode = this.spread, and in cycleSpreadMode() after computing the new spreadMode assign this.spread = spreadMode and write it back into pdfPrefs.pdfSettings.spread (and call the existing preferences-saving routine used elsewhere to persist the change). This same fix should be applied in the other occurrence noted (lines ~519-524).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@booklore-api/src/main/java/org/booklore/model/dto/CreateBookMarkRequest.java`:
- Around line 24-25: The CreateBookMarkRequest DTO allows pageNumber values <=
0; add a validation constraint so any non-null pageNumber must be >=1 to match
UpdateBookMarkRequest and controller `@Valid` handling. On the pageNumber field in
class CreateBookMarkRequest add the appropriate validation annotation (e.g.,
`@Min`(1)) and import the corresponding javax.validation constraint so POST
/bookmarks rejects 0 or negative pages while still allowing null for non-PDF
bookmarks.
In
`@booklore-api/src/main/resources/db/migration/V136__Add_page_number_to_book_marks.sql`:
- Line 1: The migration currently only adds page_number to book_marks which
leaves a TOCTOU race; modify the migration to also create a composite UNIQUE
index enforcing per-book page uniqueness (e.g. a unique index on (book_id,
page_number) for the book_marks table) and use a partial index that applies only
when page_number IS NOT NULL so existing/null bookmarks aren’t blocked; give the
index a clear name (e.g. unique_book_marks_book_id_page_number) and add that
CREATE UNIQUE INDEX step to the same migration after adding the page_number
column.
In `@frontend/src/app/features/book/model/book.model.ts`:
- Around line 304-307: Update the PdfViewerSetting type to use 'none' instead of
'off' and normalize legacy 'off' values when reading saved prefs; in the
documentOpened$ handler (after the existing setZoomLevel call) apply the saved
spread by calling this.embedPdfBook.setSpreadMode(this.spread) and set
this.spreadMode = this.spread as 'none'|'odd'|'even'; in cycleSpreadMode()
ensure you update both this.spreadMode and this.spread, call
this.embedPdfBook.setSpreadMode(this.spreadMode), and then call
this.updateViewerSetting() so the new spread persists.
In
`@frontend/src/app/features/readers/pdf-reader/components/pdf-sidebar.component.html`:
- Around line 7-8: The close button uses t('title') (the sidebar title) for its
aria-label, so update the button with class "sidebar-close-btn" to use a
dedicated action label (e.g., t('actions.close') or a new i18n key like
readerPdf.sidebar.close) instead of t('title'); keep the (click)="closed.emit()"
behavior and ensure the new key is added to the translation files so screen
readers announce the action as "close" rather than the panel title.
In
`@frontend/src/app/features/readers/pdf-reader/components/pdf-sidebar.component.scss`:
- Around line 195-200: The delete button (.delete-btn) is only revealed on
parent :hover, which hides it from keyboard and touch users; update the
stylesheet so the button is also shown when focused and when the parent gains
focus (use :focus and :focus-within on the same selector that currently uses
:hover), and also ensure .delete-btn itself becomes visible when it receives
focus (add a rule for .delete-btn:focus { opacity: 1 } or equivalent); apply
these same changes to the other block affecting .delete-btn (the similar rules
around the second occurrence) so keyboard and touch users can discover and
activate the control.
In `@frontend/src/app/features/readers/pdf-reader/pdf-reader.component.html`:
- Around line 211-215: Add a persistent accessible name to the search input so
assistive tech still has a label after the placeholder disappears: update the
input element that uses [(ngModel)]="searchQuery" and (input)="onSearchInput()"
to include an aria-label bound to the same translation used for the placeholder
(e.g. [attr.aria-label]="'readerPdf.toolbar.searchPlaceholder' | transloco") or
add a visually hidden <label> that uses that translation; ensure you leave
existing handlers onSearchInput/onSearchNext/closeSearch unchanged.
In `@frontend/src/app/features/readers/pdf-reader/pdf-reader.component.scss`:
- Around line 680-682: The CSS currently forces ".search-bar" to "display: none"
at mobile sizes while the toolbar search button in pdf-reader.component.html
remains tappable; either restore the search input on small screens or
hide/disable the toolbar action. Fix by updating the mobile CSS rule that
contains ".zoom-menu-wrapper, .search-bar" so it only hides ".zoom-menu-wrapper"
(remove ".search-bar" from that selector) or alternately add a mobile-only rule
to hide/disable the toolbar search button element in the pdf-reader component
(the toolbar search button class/id) so the button is not shown/tappable when
".search-bar" is hidden.
In `@frontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts`:
- Around line 319-330: The annotationEvent$ handler currently only marks
annotations dirty and triggers save/cache; also refresh the sidebar list so
new/edited annotations appear immediately: inside the subscriber (the block
guarded by this.annotationsLoaded && !this.isImportingAnnotations) after
this.annotationCacheSubject.next(), call the function that rebuilds or refreshes
annotationListItems (e.g. this.buildAnnotationListItems() or
this.refreshAnnotationList()—or emit an existing annotationListRefreshSubject)
so that this.annotationListItems is updated on each annotationEvent; ensure the
call runs inside the same ngZone run block and does not run during import.
- Around line 573-579: Handlers like onSidebarNavigateToPage should not set
this.page before calling onPageChange because onPageChange contains a guard that
will immediately short-circuit; remove the direct assignment (e.g., delete
this.page = pageNumber) and instead call this.onPageChange(pageNumber) (keep
other side effects such as this.sidebarOpen = false and
embedPdfBook.scrollToPage(pageNumber) as needed). Apply the same change to
goToFirstPage, goToPreviousPage, goToLastPage, onSliderChange, and onGoToPage so
that onPageChange is responsible for updating this.page and performing
persistence/progress logic.
- Around line 167-202: The handlers registered inside ngZone.runOutsideAngular
(mouseMoveHandler, clickHandler, keydownHandler) mutate bound UI state but do so
outside Angular's zone; wrap any UI-affecting calls—specifically calls to
showChrome() and startChromeAutoHide() in mouseMoveHandler, the assignment
this.isZoomMenuOpen = false in clickHandler, and any calls that update view
state (e.g., closeReader() is already run inside ngZone for the key handler but
double-check) —inside this.ngZone.run(() => { ... }) so change detection runs
and the view updates reliably.
In
`@frontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.ts`:
- Around line 311-365: The applyWorkerShims function uses an untyped window cast
and leaves patched globals permanent while also unconditionally firing a
synthetic "ready" message; fix by replacing the any cast with a minimal typed
interface (extend Window with __grimmoryShimsApplied and optional original
constructors/flags), store the original Blob and Worker constructors (e.g.,
OrigBlob/OrigWorker) and any state flags (readySent, wasmError) on that typed
window shim, update PatchedBlob/PatchedWorker to set a wasmError flag when a
wasmError message is observed, and only dispatch the fallback ready after
timeout if neither wasmError nor an actual ready was received; finally restore
the originals and clear flags inside destroy() (reference applyWorkerShims,
PatchedBlob, PatchedWorker, w.__grimmoryShimsApplied, readySent, wasmError,
destroy), and also replace the other casts like this.scroll as any with proper
types.
In
`@frontend/src/app/features/readers/pdf-reader/services/pdf-bookmark.service.ts`:
- Around line 22-33: loadBookmarks() currently clears this.bookmarks only on
successful fetch and lets errors bubble up, leaving UI without a safe
empty-state; modify loadBookmarks to handle failures locally by adding a
catchError on the Observable returned by
bookMarkService.getBookmarksForBook(this.bookId) that clears this.bookmarks and
returns of([]) so subscribers always receive an empty array on error; reference
the loadBookmarks method, this.bookmarks Map, and
bookMarkService.getBookmarksForBook to locate where to add the catchError and
the of([]) fallback.
In `@frontend/src/app/features/readers/pdf-reader/utils/annotation-converter.ts`:
- Around line 44-59: serializeAnnotations and restoreArrayBuffers currently use
an `any` cast to access serialized fields; remove those casts and introduce a
local helper interface (e.g., EncodedAnnotation or SerializableAnnotation) that
models the shape added during serialization (ctx?: { data: string;
_dataEncoding?: 'base64' } along with original AnnotationTransferItem fields),
then use that type when mapping/inspecting items in serializeAnnotations and
when restoring in restoreArrayBuffers so you can safely read ctx.data and
ctx._dataEncoding without `any`; update logic to convert ArrayBuffer->base64 and
base64->ArrayBuffer using the helper type to satisfy the eslint rule against
`any` while preserving the existing serialization behavior.
- Around line 68-76: The legacy annotation type mapping in the switch inside
convertLegacyAnnotation (the switch handling numeric legacy types) incorrectly
treats highlights as case 16 and thus drops type 9 annotations; change the
switch so that highlights are handled for the pdf.js value 9
(AnnotationEditorType.HIGHLIGHT = 9) instead of 16, ensure type 9 returns a
proper Highlight import (not null) so highlights aren’t skipped, and update the
nearby comment that lists pdf.js/ngx-extended-pdf-viewer numeric values to
correctly state HIGHLIGHT = 9 (and remove or correct the incorrect “16” claim).
---
Outside diff comments:
In `@booklore-api/src/main/java/org/booklore/service/book/BookMarkService.java`:
- Around line 81-89: updateBookmark currently checks CFI uniqueness but skips
pageNumber, so changing pageNumber can create duplicates; before calling
applyUpdates in BookMarkService.updateBookmark, if request.getPageNumber() !=
null call the same uniqueness check used for CFIs (i.e.
validateNoDuplicateBookmark) passing request.getPageNumber(), the existing
bookmark.getBookId(), bookmark.getUserId(), and bookmarkId as the exclude-id so
the current bookmark is ignored; mirror the CFI-path logic used in
createBookmark/updateBookmark to prevent duplicate page-number bookmarks.
In `@frontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts`:
- Around line 249-255: Initialize the viewer's spreadMode from the persisted
this.spread when setting up the viewer (so spreadMode reflects the currently
applied preference) and update/persist this.spread whenever cycleSpreadMode()
changes spreadMode; specifically, in the initialization code that reads
pdfPrefs.pdfSettings and myself.userSettings.pdfReaderSetting set spreadMode =
this.spread, and in cycleSpreadMode() after computing the new spreadMode assign
this.spread = spreadMode and write it back into pdfPrefs.pdfSettings.spread (and
call the existing preferences-saving routine used elsewhere to persist the
change). This same fix should be applied in the other occurrence noted (lines
~519-524).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b064df60-9a59-4e6a-8d9c-a658c2d9eeca
📒 Files selected for processing (19)
booklore-api/src/main/java/org/booklore/model/dto/BookMark.javabooklore-api/src/main/java/org/booklore/model/dto/CreateBookMarkRequest.javabooklore-api/src/main/java/org/booklore/model/dto/UpdateBookMarkRequest.javabooklore-api/src/main/java/org/booklore/model/entity/BookMarkEntity.javabooklore-api/src/main/java/org/booklore/repository/BookMarkRepository.javabooklore-api/src/main/java/org/booklore/service/book/BookMarkService.javabooklore-api/src/main/resources/db/migration/V136__Add_page_number_to_book_marks.sqlfrontend/src/app/features/book/model/book.model.tsfrontend/src/app/features/readers/pdf-reader/components/pdf-sidebar.component.htmlfrontend/src/app/features/readers/pdf-reader/components/pdf-sidebar.component.scssfrontend/src/app/features/readers/pdf-reader/components/pdf-sidebar.component.tsfrontend/src/app/features/readers/pdf-reader/pdf-reader.component.htmlfrontend/src/app/features/readers/pdf-reader/pdf-reader.component.scssfrontend/src/app/features/readers/pdf-reader/pdf-reader.component.tsfrontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.tsfrontend/src/app/features/readers/pdf-reader/services/pdf-bookmark.service.tsfrontend/src/app/features/readers/pdf-reader/utils/annotation-converter.tsfrontend/src/app/shared/service/book-mark.service.tsfrontend/src/i18n/en/reader-pdf.json
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Test Suite / Frontend Tests
- GitHub Check: Test Suite / Backend Tests
- GitHub Check: Analyze (java-kotlin)
🧰 Additional context used
📓 Path-based instructions (7)
booklore-api/src/main/resources/db/migration/V*.sql
📄 CodeRabbit inference engine (AGENTS.md)
Add Flyway migrations as new files named
V<number>__<Description>.sql
Files:
booklore-api/src/main/resources/db/migration/V136__Add_page_number_to_book_marks.sql
booklore-api/src/**/*.java
📄 CodeRabbit inference engine (AGENTS.md)
booklore-api/src/**/*.java: Use 4-space indentation in Java files and match surrounding Java style
Prefer constructor injection via Lombok patterns already used in the codebase. Do not introduce@Autowiredfield injection
Use MapStruct for entity/DTO mapping
Files:
booklore-api/src/main/java/org/booklore/model/dto/BookMark.javabooklore-api/src/main/java/org/booklore/model/entity/BookMarkEntity.javabooklore-api/src/main/java/org/booklore/model/dto/UpdateBookMarkRequest.javabooklore-api/src/main/java/org/booklore/repository/BookMarkRepository.javabooklore-api/src/main/java/org/booklore/service/book/BookMarkService.javabooklore-api/src/main/java/org/booklore/model/dto/CreateBookMarkRequest.java
booklore-api/src/**/*Entity.java
📄 CodeRabbit inference engine (AGENTS.md)
Keep JPA entities on the
*Entitysuffix
Files:
booklore-api/src/main/java/org/booklore/model/entity/BookMarkEntity.java
frontend/src/**/*.{ts,tsx,html,scss}
📄 CodeRabbit inference engine (AGENTS.md)
Use 2-space indentation in TypeScript, HTML, and SCSS files
Files:
frontend/src/app/features/book/model/book.model.tsfrontend/src/app/shared/service/book-mark.service.tsfrontend/src/app/features/readers/pdf-reader/components/pdf-sidebar.component.htmlfrontend/src/app/features/readers/pdf-reader/components/pdf-sidebar.component.scssfrontend/src/app/features/readers/pdf-reader/pdf-reader.component.scssfrontend/src/app/features/readers/pdf-reader/utils/annotation-converter.tsfrontend/src/app/features/readers/pdf-reader/components/pdf-sidebar.component.tsfrontend/src/app/features/readers/pdf-reader/pdf-reader.component.htmlfrontend/src/app/features/readers/pdf-reader/services/pdf-bookmark.service.tsfrontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.tsfrontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts
frontend/src/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
frontend/src/app/**/*.{ts,tsx}: Preferinject()over constructor injection
Followfrontend/eslint.config.js: component selectors useapp-*, directive selectors useapp*, andanyis disallowed
Files:
frontend/src/app/features/book/model/book.model.tsfrontend/src/app/shared/service/book-mark.service.tsfrontend/src/app/features/readers/pdf-reader/utils/annotation-converter.tsfrontend/src/app/features/readers/pdf-reader/components/pdf-sidebar.component.tsfrontend/src/app/features/readers/pdf-reader/services/pdf-bookmark.service.tsfrontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.tsfrontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts
frontend/src/i18n/**
📄 CodeRabbit inference engine (AGENTS.md)
Put user-facing strings in Transloco files under
frontend/src/i18n/
Files:
frontend/src/i18n/en/reader-pdf.json
frontend/src/app/**/*.component.ts
📄 CodeRabbit inference engine (AGENTS.md)
Keep Angular code on standalone components. Do not add NgModules
Files:
frontend/src/app/features/readers/pdf-reader/components/pdf-sidebar.component.tsfrontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts
🧠 Learnings (7)
📓 Common learnings
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:37.417Z
Learning: In grimmory-tools/grimmory, before commenting on any file processing code (epub or pdf), always verify the current state of the relevant upstream grimmory libraries: `grimmory-tools/epub4j` (for epub) and `grimmory-tools/PDFium4j` (for pdf). These custom libraries may have different APIs, capabilities, and limitations compared to the third-party libraries they replace. Issues about streaming, buffering, or API surface may need to be filed/addressed in those upstream repos rather than in grimmory itself.
📚 Learning: 2026-03-26T01:46:48.863Z
Learnt from: CR
Repo: grimmory-tools/grimmory PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T01:46:48.863Z
Learning: Applies to booklore-api/src/main/resources/db/migration/V*.sql : Add Flyway migrations as new files named `V<number>__<Description>.sql`
Applied to files:
booklore-api/src/main/resources/db/migration/V136__Add_page_number_to_book_marks.sql
📚 Learning: 2026-03-26T03:22:24.500Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 209
File: booklore-api/src/main/java/org/booklore/util/FileUtils.java:32-38
Timestamp: 2026-03-26T03:22:24.500Z
Learning: In `grimmory-tools/grimmory`, `BookFileEntity.fileSubPath` (in `booklore-api/src/main/java/org/booklore/model/entity/BookFileEntity.java`) is declared with `Column(name = "file_sub_path", length = 512, nullable = false)`, meaning it is never null. Do not flag null-guard concerns for this field.
Applied to files:
booklore-api/src/main/java/org/booklore/model/entity/BookMarkEntity.java
📚 Learning: 2026-03-26T01:46:48.863Z
Learnt from: CR
Repo: grimmory-tools/grimmory PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T01:46:48.863Z
Learning: Applies to booklore-api/src/**/*Entity.java : Keep JPA entities on the `*Entity` suffix
Applied to files:
booklore-api/src/main/java/org/booklore/model/entity/BookMarkEntity.java
📚 Learning: 2026-04-04T14:03:24.311Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 372
File: frontend/src/app/features/book/service/app-books-api.service.ts:150-178
Timestamp: 2026-04-04T14:03:24.311Z
Learning: In `frontend/src/app/features/book/service/app-books-api.service.ts`, the `summaryToBook` function intentionally uses `as unknown as Book` to cast a partial object to the `Book` type. The returned object omits some required `Book` fields (e.g. `metadata.bookId`, `pdfProgress.page`) and includes an extra `bookFiles: []` property not in the `Book` interface. This is by design — consumers of `AppBooksApiService.books()` (e.g. `BookCardComponent`) only access the subset of properties that are populated from `AppBookSummary`, so no runtime issues occur. Do not flag this as a type safety error.
Applied to files:
frontend/src/app/features/book/model/book.model.tsfrontend/src/app/shared/service/book-mark.service.tsfrontend/src/app/features/readers/pdf-reader/services/pdf-bookmark.service.tsfrontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.tsfrontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts
📚 Learning: 2026-03-26T01:46:48.863Z
Learnt from: CR
Repo: grimmory-tools/grimmory PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T01:46:48.863Z
Learning: Applies to frontend/src/app/**/*.component.ts : Keep Angular code on standalone components. Do not add NgModules
Applied to files:
frontend/src/app/features/readers/pdf-reader/components/pdf-sidebar.component.ts
📚 Learning: 2026-03-31T19:52:06.262Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 306
File: frontend/src/app/features/readers/cbx-reader/cbx-reader.component.ts:787-810
Timestamp: 2026-03-31T19:52:06.262Z
Learning: In `frontend/src/app/features/readers/cbx-reader/cbx-reader.component.ts`, the `doublePairs` field (type `DoublePairs = Record<number, number>`) maps right-page indices to their paired left-page indices. By design, `currentPage` always points to the left (lower-indexed) page of any spread pair — backward navigation enforces this via `Math.min(targetPage, pairedWith)` and `goToPage`/`alignCurrentPageToParity` also preserve this invariant. Therefore, looking up `doublePairs[currentPage]` in forward navigation will only ever encounter left-page keys, and there is no scenario where `currentPage` would be a right-page key.
Applied to files:
frontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts
🪛 ast-grep (0.42.0)
frontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.ts
[warning] 357-359: Message event listeners should validate the origin to prevent XSS attacks. Always check the event origin before processing the message.
Context: worker.addEventListener('message', (evt: MessageEvent) => {
if (evt.data?.type === 'ready') readySent = true;
})
Note: [CWE-346] Origin Validation Error [REFERENCES]
- https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
- https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
(event-origin-validation)
🪛 HTMLHint (1.9.2)
frontend/src/app/features/readers/pdf-reader/components/pdf-sidebar.component.html
[error] 1-1: Doctype must be declared before any non-comment content.
(doctype-first)
[error] 26-26: Special characters must be escaped : [ > ].
(spec-char-escape)
[error] 36-36: Special characters must be escaped : [ > ].
(spec-char-escape)
[error] 46-46: Special characters must be escaped : [ > ].
(spec-char-escape)
[error] 80-80: Special characters must be escaped : [ > ].
(spec-char-escape)
[error] 104-104: Special characters must be escaped : [ > ].
(spec-char-escape)
🔀 Multi-repo context grimmory-tools/grimmory-docs
Linked repositories findings
grimmory-tools/grimmory-docs
-
docs/integration/komga-api.md: documents Komga API path using {pageNumber} for "Get book page image" — indicates external integrations or docs referencing page numbers in API paths may need review for new bookmark page-number semantics. [::grimmory-tools/grimmory-docs::docs/integration/komga-api.md:254]
-
docs/readers/pdf-reader.md:
- Mentions "Page input | Jump to a specific page number" and that the reader tracks current page number and reading percentage; confirms docs and UX expectations around page numbers for PDFs which align with adding pageNumber to bookmarks. [::grimmory-tools/grimmory-docs::docs/readers/pdf-reader.md:21,103]
-
docs/readers/cbx-reader.md: references "The page number (auto-filled)" — suggests other reader docs reference page-number behavior and may need consistency checks. [::grimmory-tools/grimmory-docs::docs/readers/cbx-reader.md:92]
-
docs/metadata/metadata-center.md: notes that progress is recorded as percentages for ebooks and page numbers for PDFs — relevant to how bookmarks with pageNumber interact with progress tracking. [::grimmory-tools/grimmory-docs::docs/metadata/metadata-center.md:228]
[tag]
🔇 Additional comments (1)
frontend/src/app/features/readers/pdf-reader/components/pdf-sidebar.component.ts (1)
18-55: Nice standalone sidebar setup.The signal-based visibility/closing state and
DestroyRefcleanup look tidy and component-scoped.
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@frontend/src/app/features/readers/ebook-reader/ebook-reader.component.ts`:
- Around line 484-489: The subscription to viewManager.goTo(linkUrl) in the
go-to-link branch can leak because it lacks the takeUntil(this.destroy$)
teardown used elsewhere; update the call that currently does
this.viewManager.goTo(linkUrl).subscribe() so the observable is piped through
takeUntil(this.destroy$) before subscribing (referencing viewManager.goTo and
this.destroy$ in ebook-reader.component.ts), then keep the existing
this.selectionService.handleAction(action) call as-is.
In
`@frontend/src/app/features/readers/ebook-reader/shared/selection-popup.component.html`:
- Around line 72-77: The tooltip/action are mismatched: the button shows
"removeLink" when linkUrl exists but onDelete() always emits a delete action, so
either change the tooltip to always use t('deleteAnnotation') or make the click
emit a link-removal action; to fix, update the click handler logic in the
component where onDelete() is defined so it checks linkUrl and emits {type:
'removeLink', annotationId: overlappingAnnotationId, linkUrl} when linkUrl is
truthy (and keep emitting {type: 'delete', annotationId:
overlappingAnnotationId} otherwise), and ensure the parent handler that
processes these events (the consumer of onDelete()/the emitted action) is
updated to handle the new 'removeLink' action type; references:
overlappingAnnotationId, linkUrl, onDelete(), emitted action object.
In `@frontend/src/app/features/readers/pdf-reader/pdf-reader.component.html`:
- Around line 29-31: The icon-only buttons in pdf-reader.component.html lack
accessible labels; update each <button> that currently only contains
<app-reader-icon> (the ones wired to dismissDocViewerInfo(), the bookmark toggle
handler, the overflow/open menu handler, and the search close handler) to
include an aria-label that mirrors the action text (or reference an i18n key) —
e.g. aria-label="{{ 'pdf.dismissNotice' | translate }}" for
dismissDocViewerInfo(), aria-label="{{ 'pdf.toggleBookmark' | translate }}" for
the bookmark button, aria-label="{{ 'pdf.moreOptions' | translate }}" for the
overflow/open menu, and aria-label="{{ 'pdf.closeSearch' | translate }}" for the
search close — ensuring the label strings match visible action text or new i18n
keys.
In `@frontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts`:
- Around line 383-389: The code stores temporary object URLs in pdfBlobUrl when
loading PDFs via fetchAsObjectUrl but never revokes them, causing memory leaks;
before assigning a new pdfBlobUrl in the pdf loading branch (the block that
calls this.fetchAsObjectUrl) call URL.revokeObjectURL on the existing
this.pdfBlobUrl if present, set this.pdfBlobUrl to the new URL, and also revoke
any non-null this.pdfBlobUrl inside destroyBookViewer() and the component
teardown (ngOnDestroy) to ensure all temporary blob URLs are released; reference
the pdfBlobUrl property, fetchAsObjectUrl method, and
destroyBookViewer()/ngOnDestroy lifecycle teardown to locate where to add the
revoke calls.
- Around line 752-770: The mode switch currently flips this.viewerMode before
saving and never stores the setTimeout handles, causing persistAnnotations/save
paths (see persistAnnotations and saveEmbedPdfDocument) to short-circuit and
stale timers to run; fix setViewerMode so it first clears any existing
this.initTimeout, awaits persistAnnotations (when switching to 'document') or
triggers saveEmbedPdfDocument appropriately, only then updates this.viewerMode,
and store the return value of setTimeout in this.initTimeout for both branches
so future clearTimeout calls actually cancel pending inits; also ensure
destroyBookViewer/destroyDocViewerIframe and initDocViewerIframe/initBookViewer
are invoked after the save completes and the timeout handle is set.
In
`@frontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.ts`:
- Around line 366-367: The teardown currently calls restoreWorkerShims() and
restoreReleasePointerCapture() but fails to restore a patched global
devicePixelRatio; update the code where devicePixelRatio is overridden (the
patch logic around devicePixelRatio) to save the original descriptor/value
(e.g., originalDevicePixelRatioDescriptor or originalDevicePixelRatioValue) when
patching, add a restoreDevicePixelRatio() that re-applies the saved
descriptor/value, and call that restore from destroy() alongside
restoreWorkerShims() and restoreReleasePointerCapture() so the global
window.devicePixelRatio is returned to its original state after the reader
closes.
- Around line 50-53: The destroy() method is completing the shared Subjects
(pageChange$, annotationEvent$, documentOpened$, layoutReady$) which makes the
service unusable after a mode/book switch; either stop calling complete() on
those four streams in destroy() so they remain usable for the service lifetime,
or move their creation out of the readonly class fields and recreate
pageChange$, annotationEvent$, documentOpened$, and layoutReady$ at the start of
init() so each new document gets fresh Subjects; update destroy() or init()
accordingly and keep references to the symbols pageChange$, annotationEvent$,
documentOpened$, layoutReady$, destroy(), and init() when applying the change.
In
`@frontend/src/app/features/readers/pdf-reader/services/pdf-bookmark.service.ts`:
- Around line 67-84: On a 409 duplicate error from
this.bookMarkService.createBookmark, the handler should refresh the local cache
for the affected page (or reload the book's bookmarks) before returning false so
the UI reflects the server state; update the catchError block to, when
error?.status === 409, call the appropriate bookmark-fetching method (e.g., a
method that loads all bookmarks for the current book or a method that fetches
the bookmark for pageNumber), update this.bookmarks.set(pageNumber,
fetchedBookmark) (or repopulate this.bookmarks from the returned list), then
show the existing warning via this.messageService.add and return of(false) so
toggleBookmark() will see the hydrated cache.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 96da7383-1f02-447a-a66c-c7ed1debd25f
📒 Files selected for processing (24)
booklore-api/src/main/java/org/booklore/model/dto/CreateBookMarkRequest.javabooklore-api/src/main/java/org/booklore/repository/BookMarkRepository.javabooklore-api/src/main/java/org/booklore/service/book/BookMarkService.javabooklore-api/src/main/resources/db/migration/V136__Add_page_number_to_book_marks.sqlfrontend/src/app/features/book/model/book.model.tsfrontend/src/app/features/readers/ebook-reader/core/event.service.spec.tsfrontend/src/app/features/readers/ebook-reader/core/event.service.tsfrontend/src/app/features/readers/ebook-reader/ebook-reader.component.htmlfrontend/src/app/features/readers/ebook-reader/ebook-reader.component.tsfrontend/src/app/features/readers/ebook-reader/features/selection/selection.service.tsfrontend/src/app/features/readers/ebook-reader/shared/icon.component.tsfrontend/src/app/features/readers/ebook-reader/shared/selection-popup.component.htmlfrontend/src/app/features/readers/ebook-reader/shared/selection-popup.component.tsfrontend/src/app/features/readers/pdf-reader/components/pdf-sidebar.component.htmlfrontend/src/app/features/readers/pdf-reader/components/pdf-sidebar.component.scssfrontend/src/app/features/readers/pdf-reader/pdf-reader.component.htmlfrontend/src/app/features/readers/pdf-reader/pdf-reader.component.scssfrontend/src/app/features/readers/pdf-reader/pdf-reader.component.tsfrontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.tsfrontend/src/app/features/readers/pdf-reader/services/pdf-bookmark.service.tsfrontend/src/app/features/readers/pdf-reader/utils/annotation-converter.tsfrontend/src/assets/embedpdf-frame.htmlfrontend/src/i18n/en/reader-ebook.jsonfrontend/src/i18n/en/reader-pdf.json
✅ Files skipped from review due to trivial changes (4)
- booklore-api/src/main/resources/db/migration/V136__Add_page_number_to_book_marks.sql
- frontend/src/i18n/en/reader-ebook.json
- frontend/src/assets/embedpdf-frame.html
- frontend/src/app/features/readers/pdf-reader/components/pdf-sidebar.component.scss
🚧 Files skipped from review as they are similar to previous changes (6)
- booklore-api/src/main/java/org/booklore/model/dto/CreateBookMarkRequest.java
- booklore-api/src/main/java/org/booklore/repository/BookMarkRepository.java
- booklore-api/src/main/java/org/booklore/service/book/BookMarkService.java
- frontend/src/i18n/en/reader-pdf.json
- frontend/src/app/features/readers/pdf-reader/pdf-reader.component.scss
- frontend/src/app/features/book/model/book.model.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Test Suite / Backend Tests
- GitHub Check: Test Suite / Frontend Tests
- GitHub Check: Analyze (java-kotlin)
🧰 Additional context used
📓 Path-based instructions (4)
frontend/src/**/*.{ts,tsx,html,scss}
📄 CodeRabbit inference engine (AGENTS.md)
Use 2-space indentation in TypeScript, HTML, and SCSS files
Files:
frontend/src/app/features/readers/ebook-reader/ebook-reader.component.htmlfrontend/src/app/features/readers/ebook-reader/shared/selection-popup.component.htmlfrontend/src/app/features/readers/ebook-reader/ebook-reader.component.tsfrontend/src/app/features/readers/ebook-reader/core/event.service.spec.tsfrontend/src/app/features/readers/ebook-reader/features/selection/selection.service.tsfrontend/src/app/features/readers/ebook-reader/core/event.service.tsfrontend/src/app/features/readers/ebook-reader/shared/icon.component.tsfrontend/src/app/features/readers/ebook-reader/shared/selection-popup.component.tsfrontend/src/app/features/readers/pdf-reader/services/pdf-bookmark.service.tsfrontend/src/app/features/readers/pdf-reader/utils/annotation-converter.tsfrontend/src/app/features/readers/pdf-reader/pdf-reader.component.htmlfrontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.tsfrontend/src/app/features/readers/pdf-reader/pdf-reader.component.tsfrontend/src/app/features/readers/pdf-reader/components/pdf-sidebar.component.html
frontend/src/app/**/*.component.ts
📄 CodeRabbit inference engine (AGENTS.md)
Keep Angular code on standalone components. Do not add NgModules
Files:
frontend/src/app/features/readers/ebook-reader/ebook-reader.component.tsfrontend/src/app/features/readers/ebook-reader/shared/icon.component.tsfrontend/src/app/features/readers/ebook-reader/shared/selection-popup.component.tsfrontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts
frontend/src/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
frontend/src/app/**/*.{ts,tsx}: Preferinject()over constructor injection
Followfrontend/eslint.config.js: component selectors useapp-*, directive selectors useapp*, andanyis disallowed
Files:
frontend/src/app/features/readers/ebook-reader/ebook-reader.component.tsfrontend/src/app/features/readers/ebook-reader/core/event.service.spec.tsfrontend/src/app/features/readers/ebook-reader/features/selection/selection.service.tsfrontend/src/app/features/readers/ebook-reader/core/event.service.tsfrontend/src/app/features/readers/ebook-reader/shared/icon.component.tsfrontend/src/app/features/readers/ebook-reader/shared/selection-popup.component.tsfrontend/src/app/features/readers/pdf-reader/services/pdf-bookmark.service.tsfrontend/src/app/features/readers/pdf-reader/utils/annotation-converter.tsfrontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.tsfrontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts
frontend/src/**/*.{test,spec}.ts
📄 CodeRabbit inference engine (AGENTS.md)
Use Vitest for tests in the frontend
Files:
frontend/src/app/features/readers/ebook-reader/core/event.service.spec.ts
🧠 Learnings (6)
📓 Common learnings
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:37.417Z
Learning: In grimmory-tools/grimmory, before commenting on any file processing code (epub or pdf), always verify the current state of the relevant upstream grimmory libraries: `grimmory-tools/epub4j` (for epub) and `grimmory-tools/PDFium4j` (for pdf). These custom libraries may have different APIs, capabilities, and limitations compared to the third-party libraries they replace. Issues about streaming, buffering, or API surface may need to be filed/addressed in those upstream repos rather than in grimmory itself.
📚 Learning: 2026-04-04T14:03:24.311Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 372
File: frontend/src/app/features/book/service/app-books-api.service.ts:150-178
Timestamp: 2026-04-04T14:03:24.311Z
Learning: In `frontend/src/app/features/book/service/app-books-api.service.ts`, the `summaryToBook` function intentionally uses `as unknown as Book` to cast a partial object to the `Book` type. The returned object omits some required `Book` fields (e.g. `metadata.bookId`, `pdfProgress.page`) and includes an extra `bookFiles: []` property not in the `Book` interface. This is by design — consumers of `AppBooksApiService.books()` (e.g. `BookCardComponent`) only access the subset of properties that are populated from `AppBookSummary`, so no runtime issues occur. Do not flag this as a type safety error.
Applied to files:
frontend/src/app/features/readers/ebook-reader/ebook-reader.component.tsfrontend/src/app/features/readers/ebook-reader/features/selection/selection.service.tsfrontend/src/app/features/readers/pdf-reader/services/pdf-bookmark.service.tsfrontend/src/app/features/readers/pdf-reader/utils/annotation-converter.tsfrontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.tsfrontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts
📚 Learning: 2026-04-05T21:16:01.715Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 385
File: frontend/src/app/app.component.ts:55-56
Timestamp: 2026-04-05T21:16:01.715Z
Learning: When reviewing code in the Grimmory frontend (Angular), prefer modern Angular patterns. Specifically: (1) Prefer `DestroyRef` with `takeUntilDestroyed(destroyRef)` for teardown in Angular v16+ instead of manually tracking `Subscription` arrays and calling `unsubscribe()` in `ngOnDestroy()`. (2) Prefer `inject()` for dependency injection over constructor injection where appropriate. (3) Prefer Angular signals (e.g., `signal`, `computed`) over `BehaviorSubject`/`Observable` for state where signals/computed values fit the use case. Flag older patterns when they can be replaced with these modern equivalents without changing behavior.
Applied to files:
frontend/src/app/features/readers/ebook-reader/ebook-reader.component.tsfrontend/src/app/features/readers/ebook-reader/core/event.service.spec.tsfrontend/src/app/features/readers/ebook-reader/features/selection/selection.service.tsfrontend/src/app/features/readers/ebook-reader/core/event.service.tsfrontend/src/app/features/readers/ebook-reader/shared/icon.component.tsfrontend/src/app/features/readers/ebook-reader/shared/selection-popup.component.tsfrontend/src/app/features/readers/pdf-reader/services/pdf-bookmark.service.tsfrontend/src/app/features/readers/pdf-reader/utils/annotation-converter.tsfrontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.tsfrontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts
📚 Learning: 2026-04-02T09:25:37.417Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:37.417Z
Learning: In grimmory-tools/grimmory, before commenting on any file processing code (epub or pdf), always verify the current state of the relevant upstream grimmory libraries: `grimmory-tools/epub4j` (for epub) and `grimmory-tools/PDFium4j` (for pdf). These custom libraries may have different APIs, capabilities, and limitations compared to the third-party libraries they replace. Issues about streaming, buffering, or API surface may need to be filed/addressed in those upstream repos rather than in grimmory itself.
Applied to files:
frontend/src/app/features/readers/pdf-reader/utils/annotation-converter.tsfrontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts
📚 Learning: 2026-03-31T19:52:06.262Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 306
File: frontend/src/app/features/readers/cbx-reader/cbx-reader.component.ts:787-810
Timestamp: 2026-03-31T19:52:06.262Z
Learning: In `frontend/src/app/features/readers/cbx-reader/cbx-reader.component.ts`, the `doublePairs` field (type `DoublePairs = Record<number, number>`) maps right-page indices to their paired left-page indices. By design, `currentPage` always points to the left (lower-indexed) page of any spread pair — backward navigation enforces this via `Math.min(targetPage, pairedWith)` and `goToPage`/`alignCurrentPageToParity` also preserve this invariant. Therefore, looking up `doublePairs[currentPage]` in forward navigation will only ever encounter left-page keys, and there is no scenario where `currentPage` would be a right-page key.
Applied to files:
frontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts
📚 Learning: 2026-03-24T18:58:08.199Z
Learnt from: pedronave
Repo: grimmory-tools/grimmory PR: 173
File: booklore-api/src/main/java/org/booklore/service/hardcover/HardcoverSyncService.java:291-294
Timestamp: 2026-03-24T18:58:08.199Z
Learning: In grimmory-tools/grimmory (HardcoverSyncService.java), when a Hardcover edition has no page count, falling back to the book-level page count (book.pages) is the preferred behavior. Skipping the sync entirely in this case is considered worse than using a slightly approximate total, because the page count is only a proxy for converting progress % to page numbers for the API. The Hardcover API allows setting page-based progress even when the edition lacks a page count, and progress displays in at least some UI views (e.g., the book detail dropdown).
Applied to files:
frontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts
🪛 ast-grep (0.42.1)
frontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.ts
[warning] 483-486: Message event listeners should validate the origin to prevent XSS attacks. Always check the event origin before processing the message.
Context: worker.addEventListener('message', (evt: MessageEvent) => {
if (evt.data?.type === 'ready') readySent = true;
if (evt.data?.type === 'wasmError') wasmError = true;
})
Note: [CWE-346] Origin Validation Error [REFERENCES]
- https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
- https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
(event-origin-validation)
🪛 HTMLHint (1.9.2)
frontend/src/app/features/readers/pdf-reader/components/pdf-sidebar.component.html
[error] 1-1: Doctype must be declared before any non-comment content.
(doctype-first)
[error] 29-29: Special characters must be escaped : [ > ].
(spec-char-escape)
[error] 39-39: Special characters must be escaped : [ > ].
(spec-char-escape)
[error] 49-49: Special characters must be escaped : [ > ].
(spec-char-escape)
[error] 83-83: Special characters must be escaped : [ > ].
(spec-char-escape)
[error] 107-107: Special characters must be escaped : [ > ].
(spec-char-escape)
🔀 Multi-repo context grimmory-tools/grimmory-docs
Linked repositories findings
-
Komga API documents per-page endpoints that use a pageNumber path parameter: GET /komga/api/v1/books/{bookId}/pages/{pageNumber}. This is in docs/integration/komga-api.md and confirms external consumers or docs expect page-number semantics for pages. [::grimmory-tools/grimmory-docs::docs/integration/komga-api.md:/* contains "Get book page image - GET /komga/api/v1/books/{bookId}/pages/{pageNumber}" */]
-
PDF reader docs explicitly describe tracking and UI around page numbers (page input, current page tracking, reading progress stored as page number). Changes adding pageNumber to bookmarks align with these docs and UX expectations. [::grimmory-tools/grimmory-docs::docs/readers/pdf-reader.md:/* PDF reader: "Page input | Jump to a specific page number", "tracks current page number" */]
-
Comic/CBX reader docs mention bookmarks by page and sidebar bookmark UI; similar UX patterns exist across readers that now include PDF page-number bookmarks. [::grimmory-tools/grimmory-docs::docs/readers/cbx-reader.md:/* mentions bookmarks, sidebar and "page number (auto-filled)" */]
-
Metadata docs note that progress is recorded as percentages for ebooks and page numbers for PDFs — relevant to how pageNumber in bookmarks maps to persisted progress semantics. [::grimmory-tools/grimmory-docs::docs/metadata/metadata-center.md:/* "progress is recorded as percentages for ebooks and page numbers for PDFs" */]
Conclusion: documentation references and external API docs use page-number semantics; the backend/frontend PR introducing pageNumber on bookmarks is consistent with existing docs but may impact any integrations or consumers that rely on page-based Komga endpoints or documented reader behavior. [::grimmory-tools/grimmory-docs::]
🔇 Additional comments (11)
frontend/src/app/features/readers/ebook-reader/ebook-reader.component.html (1)
70-78: LGTM!The
linkUrlbinding is correctly added to the selection popup, passing the value from the selection state signal. This integrates cleanly with the new link navigation feature.frontend/src/app/features/readers/ebook-reader/shared/selection-popup.component.html (1)
13-18: LGTM!The "Go to link" button is correctly shown only when
linkUrlis truthy, with proper icon and localized tooltip.frontend/src/app/features/readers/ebook-reader/shared/icon.component.ts (1)
38-39: LGTM!The
'link'icon name and corresponding SVG path data are correctly added to both the type union and theICONSrecord. The SVG paths render a standard chain-link icon.Also applies to: 203-207
frontend/src/app/features/readers/ebook-reader/core/event.service.spec.ts (2)
12-27: LGTM!The
RangeLikeandTestSelectioninterface extensions correctly mirror the DOM APIs now used bygetLinkUrl()in the event service. Mock implementations are consistent across all test scenarios.
141-154: LGTM!Default selection mock correctly initializes the new range/selection properties with sensible defaults (
doc.bodyfor containers,() => falseforintersectsNode).frontend/src/app/features/readers/ebook-reader/features/selection/selection.service.ts (2)
127-131: LGTM!The
'go-to-link'action handling correctly clears the preview and selection state. The flow continues to resetcurrentSelectionand emit state (lines 133-134), which is appropriate for cleanup after navigation is initiated by the parent component.
11-18: LGTM!The
linkUrlproperty is consistently added toSelectionState,SelectionDetail, anddefaultState, maintaining type safety throughout the selection flow.Also applies to: 20-26, 48-55
frontend/src/app/features/readers/ebook-reader/core/event.service.ts (2)
491-530: LGTM!The
getLinkUrl()implementation is robust:
- Traverses from selection nodes upward to find ancestor
<a>elements- Uses
element.closest('a')as an additional check- Falls back to querying all links in the common ancestor and checking
intersectsNode()- Defensive type/null checks throughout (
typeof element.closest === 'function', etc.) handle cross-document/iframe edge cases
480-486: LGTM!The
linkUrlis correctly extracted and included in thetext-selectedevent payload, enabling downstream components to offer link navigation.frontend/src/app/features/readers/ebook-reader/shared/selection-popup.component.ts (2)
8-14: LGTM!The
'go-to-link'action type is correctly added to theTextSelectionActionunion, maintaining type safety for action handling throughout the selection flow.
40-40: LGTM!The
linkUrlinput andonGoToLink()method are correctly implemented with a guard to prevent emitting when no URL exists.Also applies to: 126-130
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
frontend/src/app/features/readers/pdf-reader/pdf-reader.component.html (1)
104-118: Consider extracting repeated SVG icons to reduce duplication.The bookmark icon SVGs are duplicated between desktop (lines 104-118) and mobile (lines 210-223) sections. The same applies to theme toggle icons. You could extract these to
<ng-template>blocks with*ngTemplateOutletor create small icon components.This is optional since the current approach works and keeps responsive layouts self-contained.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/app/features/readers/pdf-reader/pdf-reader.component.html` around lines 104 - 118, Extract the duplicated bookmark SVGs used in the bookmark button (the template that references isCurrentPageBookmarked and (click)="toggleBookmark()") into a single reusable piece—either an <ng-template> (e.g., `#bookmarkFilled` and `#bookmarkOutline`) and render with *ngTemplateOutlet where needed, or create a small BookmarkIconComponent that accepts an input like [filled]="isCurrentPageBookmarked" and replace the inline SVGs in both desktop and mobile buttons with the reusable template/component; do the same for theme toggle icons to eliminate duplication while preserving the existing bindings and button classes (e.g., class="icon-btn bookmark-btn") and click handler toggleBookmark().frontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.ts (1)
172-179: Consider storing and clearing the fallback timeout.If
destroy()is called within 500ms ofinit(), this timeout will still fire and invokezone.run()on a completed subject. While safe (no-op), storing the handle and clearing it indestroy()avoids the unnecessary callback.♻️ Optional improvement
+ private documentOpenedTimeout: ReturnType<typeof setTimeout> | null = null; + // Fallback: emit after a delay once scroll is ready this.zone.runOutsideAngular(() => { - setTimeout(() => { + this.documentOpenedTimeout = setTimeout(() => { this.zone.run(() => { this.documentOpened$.next({pageCount: this.scroll?.getTotalPages() ?? 0}); }); }, 500); });Then in
destroy():destroy(): void { this.pageChangeUnsub?.(); this.annotationEventUnsub?.(); this.layoutReadyUnsub?.(); this.documentOpenedUnsub?.(); + + if (this.documentOpenedTimeout !== null) { + clearTimeout(this.documentOpenedTimeout); + this.documentOpenedTimeout = null; + } this.resizeObserver?.disconnect();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.ts` around lines 172 - 179, The fallback setTimeout started inside init (zone.runOutsideAngular -> setTimeout -> zone.run -> this.documentOpened$.next(...)) should be tracked and cleared in destroy to avoid firing after teardown; add a class property (e.g., fallbackTimeoutId) to store the value returned by setTimeout when scheduling the fallback and call clearTimeout(fallbackTimeoutId) and null it inside destroy() before completing/disposing documentOpened$ so the timeout callback never runs after destroy().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@frontend/src/app/features/readers/pdf-reader/pdf-reader.component.html`:
- Around line 129-142: The zoom label button lacks an accessible name and the
dropdown doesn't close on outside clicks; update the button in
pdf-reader.component (the element that calls toggleZoomMenu() and displays {{
zoom }}) to include an accessible name via aria-label or title (e.g., "Zoom
options, current: {{ zoom }}") and include aria-expanded bound to
isZoomMenuOpen; then implement a backdrop element rendered when isZoomMenuOpen
is true (mirror the overflow menu pattern) that captures clicks and calls
toggleZoomMenu() or a new closeZoomMenu() method to close the zoom dropdown;
ensure the dropdown options (setZoomPreset and zoomPresets loop) remain
keyboard-focusable and that the backdrop is inserted/removed together with
isZoomMenuOpen.
- Around line 244-246: The backdrop div's (keydown.escape) won't fire because it
isn't focusable; update the PDFReaderComponent to handle Escape properly by
moving the escape listener to a focusable element or the component/document:
when isToolbarOverflowOpen becomes true, set focus into the toolbar element
(query the element identified by "toolbar-overflow-menu" via `@ViewChild` in
PDFReaderComponent) and attach a keydown handler there to call
closeToolbarOverflow(), or alternatively add a host/document-level keydown
listener in the component that listens for Escape and calls
closeToolbarOverflow(); ensure the focused element is removed or the listener
unsubscribed when the menu closes.
In
`@frontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.ts`:
- Around line 654-670: The ResizeObserver created in setupResizeObserver isn't
stored and may not be disconnected if destroy() runs before layoutReady$ emits;
change setupResizeObserver (and the class) to save the created observer (e.g.,
this.resizeObserver) and any subscription (from layoutReady$) as class fields,
stop using an anonymous observer variable, and in destroy() call
this.resizeObserver.disconnect() and unsubscribe the saved subscription (or set
it to null) to ensure deterministic cleanup; keep the existing
zoom/getSpreadMode/requestZoom logic intact.
---
Nitpick comments:
In `@frontend/src/app/features/readers/pdf-reader/pdf-reader.component.html`:
- Around line 104-118: Extract the duplicated bookmark SVGs used in the bookmark
button (the template that references isCurrentPageBookmarked and
(click)="toggleBookmark()") into a single reusable piece—either an <ng-template>
(e.g., `#bookmarkFilled` and `#bookmarkOutline`) and render with *ngTemplateOutlet
where needed, or create a small BookmarkIconComponent that accepts an input like
[filled]="isCurrentPageBookmarked" and replace the inline SVGs in both desktop
and mobile buttons with the reusable template/component; do the same for theme
toggle icons to eliminate duplication while preserving the existing bindings and
button classes (e.g., class="icon-btn bookmark-btn") and click handler
toggleBookmark().
In
`@frontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.ts`:
- Around line 172-179: The fallback setTimeout started inside init
(zone.runOutsideAngular -> setTimeout -> zone.run ->
this.documentOpened$.next(...)) should be tracked and cleared in destroy to
avoid firing after teardown; add a class property (e.g., fallbackTimeoutId) to
store the value returned by setTimeout when scheduling the fallback and call
clearTimeout(fallbackTimeoutId) and null it inside destroy() before
completing/disposing documentOpened$ so the timeout callback never runs after
destroy().
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 4e986d1c-ab82-41dc-bc87-c36c4724f9f5
📒 Files selected for processing (7)
frontend/src/app/features/readers/ebook-reader/ebook-reader.component.tsfrontend/src/app/features/readers/ebook-reader/shared/selection-popup.component.htmlfrontend/src/app/features/readers/pdf-reader/pdf-reader.component.htmlfrontend/src/app/features/readers/pdf-reader/pdf-reader.component.tsfrontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.tsfrontend/src/app/features/readers/pdf-reader/services/pdf-bookmark.service.tsfrontend/src/i18n/en/reader-pdf.json
✅ Files skipped from review due to trivial changes (1)
- frontend/src/i18n/en/reader-pdf.json
🚧 Files skipped from review as they are similar to previous changes (4)
- frontend/src/app/features/readers/ebook-reader/shared/selection-popup.component.html
- frontend/src/app/features/readers/ebook-reader/ebook-reader.component.ts
- frontend/src/app/features/readers/pdf-reader/services/pdf-bookmark.service.ts
- frontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Test Suite / Backend Tests
- GitHub Check: Test Suite / Frontend Tests
- GitHub Check: Analyze (java-kotlin)
🧰 Additional context used
📓 Path-based instructions (2)
frontend/src/**/*.{ts,tsx,html,scss}
📄 CodeRabbit inference engine (AGENTS.md)
Use 2-space indentation in TypeScript, HTML, and SCSS files
Files:
frontend/src/app/features/readers/pdf-reader/pdf-reader.component.htmlfrontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.ts
frontend/src/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
frontend/src/app/**/*.{ts,tsx}: Preferinject()over constructor injection
Followfrontend/eslint.config.js: component selectors useapp-*, directive selectors useapp*, andanyis disallowed
Files:
frontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:37.417Z
Learning: In grimmory-tools/grimmory, before commenting on any file processing code (epub or pdf), always verify the current state of the relevant upstream grimmory libraries: `grimmory-tools/epub4j` (for epub) and `grimmory-tools/PDFium4j` (for pdf). These custom libraries may have different APIs, capabilities, and limitations compared to the third-party libraries they replace. Issues about streaming, buffering, or API surface may need to be filed/addressed in those upstream repos rather than in grimmory itself.
📚 Learning: 2026-04-04T14:03:24.311Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 372
File: frontend/src/app/features/book/service/app-books-api.service.ts:150-178
Timestamp: 2026-04-04T14:03:24.311Z
Learning: In `frontend/src/app/features/book/service/app-books-api.service.ts`, the `summaryToBook` function intentionally uses `as unknown as Book` to cast a partial object to the `Book` type. The returned object omits some required `Book` fields (e.g. `metadata.bookId`, `pdfProgress.page`) and includes an extra `bookFiles: []` property not in the `Book` interface. This is by design — consumers of `AppBooksApiService.books()` (e.g. `BookCardComponent`) only access the subset of properties that are populated from `AppBookSummary`, so no runtime issues occur. Do not flag this as a type safety error.
Applied to files:
frontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.ts
📚 Learning: 2026-04-05T21:16:01.715Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 385
File: frontend/src/app/app.component.ts:55-56
Timestamp: 2026-04-05T21:16:01.715Z
Learning: When reviewing code in the Grimmory frontend (Angular), prefer modern Angular patterns. Specifically: (1) Prefer `DestroyRef` with `takeUntilDestroyed(destroyRef)` for teardown in Angular v16+ instead of manually tracking `Subscription` arrays and calling `unsubscribe()` in `ngOnDestroy()`. (2) Prefer `inject()` for dependency injection over constructor injection where appropriate. (3) Prefer Angular signals (e.g., `signal`, `computed`) over `BehaviorSubject`/`Observable` for state where signals/computed values fit the use case. Flag older patterns when they can be replaced with these modern equivalents without changing behavior.
Applied to files:
frontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.ts
🪛 ast-grep (0.42.1)
frontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.ts
[warning] 493-496: Message event listeners should validate the origin to prevent XSS attacks. Always check the event origin before processing the message.
Context: worker.addEventListener('message', (evt: MessageEvent) => {
if (evt.data?.type === 'ready') readySent = true;
if (evt.data?.type === 'wasmError') wasmError = true;
})
Note: [CWE-346] Origin Validation Error [REFERENCES]
- https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
- https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
(event-origin-validation)
🔀 Multi-repo context grimmory-tools/grimmory-docs
Linked repositories findings
-
Documentation confirms per-page Komga endpoints and page-number semantics that align with the PR's new bookmark.pageNumber field:
- docs/integration/komga-api.md — "Get book page image - GET /komga/api/v1/books/{bookId}/pages/{pageNumber}" [::grimmory-tools/grimmory-docs::docs/integration/komga-api.md:253-255]
-
PDF reader docs explicitly reference page-number tracking and PDF UX that expect page numbers (so adding pageNumber to bookmarks matches existing product semantics):
- docs/readers/pdf-reader.md — "Page input | Jump to a specific page number" and "reader tracks both the current page number and the reading percentage." [::grimmory-tools/grimmory-docs::docs/readers/pdf-reader.md:21,103]
-
Comic (CBX) reader docs show existing UX patterns for page-number bookmarks and a sidebar bookmarks UI — indicates consistency expectations across readers for page-number bookmarks:
- docs/readers/cbx-reader.md — "Bookmark toggles a bookmark on the current page", "The page number (auto-filled)", and sidebar/bookmarks descriptions [::grimmory-tools/grimmory-docs::docs/readers/cbx-reader.md:17-18,92,37]
-
Metadata docs note that PDFs use page-number progress (vs percentages for ebooks), meaning backend schema and APIs that treat page numbers should be consistent:
- docs/metadata/metadata-center.md — "Progress is recorded as percentages for ebooks and page numbers for PDFs." [::grimmory-tools/grimmory-docs::docs/metadata/metadata-center.md:228]
Conclusion: docs reference page-number semantics and per-page endpoints; the PR's addition of pageNumber to bookmarks is consistent with existing documentation and UX. Potential external integrations that consume per-page Komga endpoints or expect page-number based bookmarks (Komga clients, CBX readers, documentation consumers) should continue to work, but reviewers should validate API contracts (request/response shapes) and DB uniqueness constraints against any external consumers expecting previous bookmark formats.
🔇 Additional comments (6)
frontend/src/app/features/readers/pdf-reader/pdf-reader.component.html (4)
26-33: LGTM! Accessibility improvements applied.The dismiss button now has a proper
aria-labelattribute for screen reader support, addressing the previous review feedback.
336-341: Search input accessibility is now properly configured.The search input has both an
aria-labeland placeholder, addressing the previous review feedback about providing a persistent accessible name.
358-369: Clean sidebar component integration.The sidebar component is well-wired with proper input bindings for data and output event handlers for user interactions. This follows Angular's recommended pattern for parent-child communication.
421-428: Good internationalization for viewer mode labels.The hardcoded "Book Viewer" / "Doc Viewer" strings have been replaced with Transloco keys, maintaining consistency with the rest of the localized UI.
frontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.ts (2)
493-496: Static analysis false positive: origin validation not needed for Worker messages.The static analysis tool flagged this Worker
messagelistener for missing origin validation. However, origin checks apply towindow.postMessageevents between different browsing contexts, not to Worker message events. Workers created via blob URLs (as here) communicate over a dedicated channel, so origin validation is unnecessary and not applicable.
64-68: Nice fix: Subjects recreated on each init.Recreating the event streams here resolves the issue where
destroy()would leave the service unusable. The service can now be reused across book/document toggles. Well done!
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (2)
frontend/src/app/features/readers/pdf-reader/pdf-reader.component.html (1)
130-140:⚠️ Potential issue | 🟡 MinorName the zoom menu trigger for assistive tech.
This button is currently announced as just the raw zoom value (
100%,fit-page, etc.), not as the control that opens the zoom menu. Add anaria-labeland bindaria-expandedtoisZoomMenuOpen.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/app/features/readers/pdf-reader/pdf-reader.component.html` around lines 130 - 140, The zoom toggle button currently exposes only the raw zoom value (zoom) to assistive tech; update the button element that calls toggleZoomMenu() to include a descriptive aria-label (e.g., "Zoom options" or similar), bind aria-expanded to isZoomMenuOpen, and optionally add aria-haspopup="menu" so screen readers know it opens a menu; keep existing behavior for displaying {{ zoom }} and the existing click handler (toggleZoomMenu()) and leave the dropdown rendering (zoomPresets, setZoomPreset) unchanged.frontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.ts (1)
172-180:⚠️ Potential issue | 🟠 MajorClear the fallback
documentOpenedtimer on teardown.The plugin unsubscribe is cleaned up now, but this fallback
setTimeout()is still left live. Becauseinit()recreatesdocumentOpened$, a fast book→document→book switch can let the stale callback emit into the next init cycle and rerun document-open work for the wrong viewer.🛠️ Possible fix
+ private documentOpenedFallbackTimer?: ReturnType<typeof setTimeout>; + ... - setTimeout(() => { + this.documentOpenedFallbackTimer = setTimeout(() => { this.zone.run(() => { this.documentOpened$.next({pageCount: this.scroll?.getTotalPages() ?? 0}); }); }, 500); ... + if (this.documentOpenedFallbackTimer) { + clearTimeout(this.documentOpenedFallbackTimer); + this.documentOpenedFallbackTimer = undefined; + }Also applies to: 346-350
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.ts` around lines 172 - 180, The fallback setTimeout started in init() (inside this.zone.runOutsideAngular -> setTimeout -> this.documentOpened$.next(...)) can fire after teardown and pollute a new init cycle; store its timer id (from setTimeout) in a class field when scheduling and clear it during teardown/unsubscribe (e.g., in the same method that cleans up the plugin/unsubscribe or ngOnDestroy) by calling clearTimeout on that id; apply the same change to the duplicate fallback at the other location (the second setTimeout around lines 346-350) so no stale callbacks emit into a recreated documentOpened$.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@frontend/src/app/features/readers/pdf-reader/pdf-reader.component.html`:
- Around line 44-45: The template uses a wrong translation key
'readerPdf.sidebar.title' in the PDF reader button; update the binding in
pdf-reader.component.html to use the existing key 'readerPdf.toolbar.sidebar' so
the button's title resolves to the localized string (locate the button with
class "icon-btn" and the (click)="toggleSidebar()" handler and replace the
translation key accordingly).
In `@frontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts`:
- Around line 255-263: Debounced search still fires a queued query after the
search UI is closed; update the stream that listens to searchQuery$ to cancel
stale searches when the search is closed by adding a guard or cancellation
trigger (e.g., filter by an isSearchOpen observable/property or use takeUntil
with a searchClosed Subject emitted from closeSearch()). Specifically, modify
the pipeline around searchQuery$ (the debounceTime(...).subscribe(...) block
that calls embedPdfBook.searchAllPages) so it only proceeds when the search is
open (use filter(() => this.isSearchOpen) or takeUntil(this.searchClose$) tied
to closeSearch()), ensuring no queued debounced searches run after closeSearch()
is called.
- Around line 1132-1168: Footer handlers (goToFirstPage, goToPreviousPage,
goToNextPage, goToLastPage, onSliderChange, onGoToPage) currently always call
onPageChange even when viewerMode !== 'book', causing persisted page state to
drift; update these methods to only call onPageChange when the viewer actually
moves: either (A) short-term: guard the onPageChange calls with if
(this.viewerMode === 'book') so document mode does not update persisted state,
or (B) full fix: when viewerMode === 'document' send a postMessage navigation
command to the iframe and wait for the iframe/page-change sync event before
calling onPageChange (use existing page/totalPages/goToPageInput fields to
validate). Choose one approach and implement it consistently across
goToFirstPage, goToPreviousPage, goToNextPage, goToLastPage, onSliderChange and
onGoToPage.
---
Duplicate comments:
In `@frontend/src/app/features/readers/pdf-reader/pdf-reader.component.html`:
- Around line 130-140: The zoom toggle button currently exposes only the raw
zoom value (zoom) to assistive tech; update the button element that calls
toggleZoomMenu() to include a descriptive aria-label (e.g., "Zoom options" or
similar), bind aria-expanded to isZoomMenuOpen, and optionally add
aria-haspopup="menu" so screen readers know it opens a menu; keep existing
behavior for displaying {{ zoom }} and the existing click handler
(toggleZoomMenu()) and leave the dropdown rendering (zoomPresets, setZoomPreset)
unchanged.
In
`@frontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.ts`:
- Around line 172-180: The fallback setTimeout started in init() (inside
this.zone.runOutsideAngular -> setTimeout -> this.documentOpened$.next(...)) can
fire after teardown and pollute a new init cycle; store its timer id (from
setTimeout) in a class field when scheduling and clear it during
teardown/unsubscribe (e.g., in the same method that cleans up the
plugin/unsubscribe or ngOnDestroy) by calling clearTimeout on that id; apply the
same change to the duplicate fallback at the other location (the second
setTimeout around lines 346-350) so no stale callbacks emit into a recreated
documentOpened$.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 39b22117-df59-4439-bf7b-e525460d6af6
📒 Files selected for processing (3)
frontend/src/app/features/readers/pdf-reader/pdf-reader.component.htmlfrontend/src/app/features/readers/pdf-reader/pdf-reader.component.tsfrontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Test Suite / Frontend Tests
- GitHub Check: Test Suite / Backend Tests
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: Analyze (java-kotlin)
🧰 Additional context used
📓 Path-based instructions (3)
frontend/src/**/*.{ts,tsx,html,scss}
📄 CodeRabbit inference engine (AGENTS.md)
Use 2-space indentation in TypeScript, HTML, and SCSS files
Files:
frontend/src/app/features/readers/pdf-reader/pdf-reader.component.htmlfrontend/src/app/features/readers/pdf-reader/pdf-reader.component.tsfrontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.ts
frontend/src/app/**/*.component.ts
📄 CodeRabbit inference engine (AGENTS.md)
Keep Angular code on standalone components. Do not add NgModules
Files:
frontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts
frontend/src/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
frontend/src/app/**/*.{ts,tsx}: Preferinject()over constructor injection
Followfrontend/eslint.config.js: component selectors useapp-*, directive selectors useapp*, andanyis disallowed
Files:
frontend/src/app/features/readers/pdf-reader/pdf-reader.component.tsfrontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.ts
🧠 Learnings (6)
📓 Common learnings
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:37.417Z
Learning: In grimmory-tools/grimmory, before commenting on any file processing code (epub or pdf), always verify the current state of the relevant upstream grimmory libraries: `grimmory-tools/epub4j` (for epub) and `grimmory-tools/PDFium4j` (for pdf). These custom libraries may have different APIs, capabilities, and limitations compared to the third-party libraries they replace. Issues about streaming, buffering, or API surface may need to be filed/addressed in those upstream repos rather than in grimmory itself.
📚 Learning: 2026-04-04T14:03:24.311Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 372
File: frontend/src/app/features/book/service/app-books-api.service.ts:150-178
Timestamp: 2026-04-04T14:03:24.311Z
Learning: In `frontend/src/app/features/book/service/app-books-api.service.ts`, the `summaryToBook` function intentionally uses `as unknown as Book` to cast a partial object to the `Book` type. The returned object omits some required `Book` fields (e.g. `metadata.bookId`, `pdfProgress.page`) and includes an extra `bookFiles: []` property not in the `Book` interface. This is by design — consumers of `AppBooksApiService.books()` (e.g. `BookCardComponent`) only access the subset of properties that are populated from `AppBookSummary`, so no runtime issues occur. Do not flag this as a type safety error.
Applied to files:
frontend/src/app/features/readers/pdf-reader/pdf-reader.component.tsfrontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.ts
📚 Learning: 2026-04-05T21:16:01.715Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 385
File: frontend/src/app/app.component.ts:55-56
Timestamp: 2026-04-05T21:16:01.715Z
Learning: When reviewing code in the Grimmory frontend (Angular), prefer modern Angular patterns. Specifically: (1) Prefer `DestroyRef` with `takeUntilDestroyed(destroyRef)` for teardown in Angular v16+ instead of manually tracking `Subscription` arrays and calling `unsubscribe()` in `ngOnDestroy()`. (2) Prefer `inject()` for dependency injection over constructor injection where appropriate. (3) Prefer Angular signals (e.g., `signal`, `computed`) over `BehaviorSubject`/`Observable` for state where signals/computed values fit the use case. Flag older patterns when they can be replaced with these modern equivalents without changing behavior.
Applied to files:
frontend/src/app/features/readers/pdf-reader/pdf-reader.component.tsfrontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.ts
📚 Learning: 2026-03-31T19:52:06.262Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 306
File: frontend/src/app/features/readers/cbx-reader/cbx-reader.component.ts:787-810
Timestamp: 2026-03-31T19:52:06.262Z
Learning: In `frontend/src/app/features/readers/cbx-reader/cbx-reader.component.ts`, the `doublePairs` field (type `DoublePairs = Record<number, number>`) maps right-page indices to their paired left-page indices. By design, `currentPage` always points to the left (lower-indexed) page of any spread pair — backward navigation enforces this via `Math.min(targetPage, pairedWith)` and `goToPage`/`alignCurrentPageToParity` also preserve this invariant. Therefore, looking up `doublePairs[currentPage]` in forward navigation will only ever encounter left-page keys, and there is no scenario where `currentPage` would be a right-page key.
Applied to files:
frontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts
📚 Learning: 2026-04-02T09:25:37.417Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:37.417Z
Learning: In grimmory-tools/grimmory, before commenting on any file processing code (epub or pdf), always verify the current state of the relevant upstream grimmory libraries: `grimmory-tools/epub4j` (for epub) and `grimmory-tools/PDFium4j` (for pdf). These custom libraries may have different APIs, capabilities, and limitations compared to the third-party libraries they replace. Issues about streaming, buffering, or API surface may need to be filed/addressed in those upstream repos rather than in grimmory itself.
Applied to files:
frontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts
📚 Learning: 2026-03-24T18:58:08.199Z
Learnt from: pedronave
Repo: grimmory-tools/grimmory PR: 173
File: booklore-api/src/main/java/org/booklore/service/hardcover/HardcoverSyncService.java:291-294
Timestamp: 2026-03-24T18:58:08.199Z
Learning: In grimmory-tools/grimmory (HardcoverSyncService.java), when a Hardcover edition has no page count, falling back to the book-level page count (book.pages) is the preferred behavior. Skipping the sync entirely in this case is considered worse than using a slightly approximate total, because the page count is only a proxy for converting progress % to page numbers for the API. The Hardcover API allows setting page-based progress even when the edition lacks a page count, and progress displays in at least some UI views (e.g., the book detail dropdown).
Applied to files:
frontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts
🪛 ast-grep (0.42.1)
frontend/src/app/features/readers/pdf-reader/services/embedpdf-book.service.ts
[warning] 496-499: Message event listeners should validate the origin to prevent XSS attacks. Always check the event origin before processing the message.
Context: worker.addEventListener('message', (evt: MessageEvent) => {
if (evt.data?.type === 'ready') readySent = true;
if (evt.data?.type === 'wasmError') wasmError = true;
})
Note: [CWE-346] Origin Validation Error [REFERENCES]
- https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
- https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
(event-origin-validation)
🔀 Multi-repo context grimmory-tools/grimmory-docs
Linked repositories findings
grimmory-tools/grimmory-docs
-
Komga per-page endpoints and page-number semantics documented:
- GET /komga/api/v1/books/{bookId}/pages/{pageNumber} referenced in docs/integration/komga-api.md. [::grimmory-tools/grimmory-docs::docs/integration/komga-api.md:252-256]
-
PDF reader docs expect page-number tracking (reader tracks current page number and reading percentage); adding pageNumber to bookmarks aligns with documented UX. [::grimmory-tools/grimmory-docs::docs/readers/pdf-reader.md: Reading Progress section]
-
CBX (comic) reader docs describe page-number bookmarks and sidebar bookmarks UX — indicates parity expectation across readers for page-number bookmarks (relevant for consistency of new PDF bookmark behavior and sidebar UI). [::grimmory-tools/grimmory-docs::docs/readers/cbx-reader.md:16-18,37,102]
-
Metadata docs explicitly state PDFs use page-number progress (vs percentages for ebooks), supporting the backend schema change to include page_number. [::grimmory-tools/grimmory-docs::docs/metadata/metadata-center.md:228]
Assessment: Documentation in the available repo consistently uses page-number semantics and Komga per-page APIs. The PR's addition of pageNumber to backend DTOs/entities, DB, repository checks, and frontend models/components aligns with existing documentation and reader UX expectations. No conflicting doc references to a CFI-only bookmark model were found.
🔇 Additional comments (1)
frontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts (1)
1105-1109: This code already executes in an injection context and does not require an explicit injector.The
toggleToolbarOverflow()method is called from a template click binding(click)="toggleToolbarOverflow()". Template event bindings in Angular components execute within the component's dependency injection context. Since the method is invoked within an injection context,afterNextRender()has access to the current context without requiring an explicit injector parameter.According to Angular documentation, passing an
InjectorviaAfterRenderOptionsis necessary when callingafterNextRender()outside an injection context (e.g., from event listeners or callbacks outside the component flow). This code does not fall into that category.> Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@frontend/src/app/features/readers/ebook-reader/ebook-reader.component.ts`:
- Around line 487-489: The subscription to
viewManager.goTo(linkUrl).pipe(takeUntil(this.destroy$)).subscribe() lacks error
handling and can emit unhandled errors; update the call to handle errors by
providing an error callback to subscribe (or pipe in catchError) that gracefully
handles/logs navigation failures (e.g., call a logger, show user feedback, or
ignore benign errors) and ensures the stream completes cleanly; target the
viewManager.goTo(...) invocation and the subscribe() call so the fix adds an
error handler and any needed cleanup logic tied to destroy$.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e6d1e1fa-65fa-41b5-a4ef-c489926abd03
📒 Files selected for processing (2)
frontend/src/app/features/readers/ebook-reader/ebook-reader.component.tsfrontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Test Suite / Backend Tests
- GitHub Check: Test Suite / Frontend Tests
- GitHub Check: Analyze (java-kotlin)
- GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (3)
frontend/src/**/*.{ts,tsx,html,scss}
📄 CodeRabbit inference engine (AGENTS.md)
Use 2-space indentation in TypeScript, HTML, and SCSS files
Files:
frontend/src/app/features/readers/ebook-reader/ebook-reader.component.ts
frontend/src/app/**/*.component.ts
📄 CodeRabbit inference engine (AGENTS.md)
Keep Angular code on standalone components. Do not add NgModules
Files:
frontend/src/app/features/readers/ebook-reader/ebook-reader.component.ts
frontend/src/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
frontend/src/app/**/*.{ts,tsx}: Preferinject()over constructor injection
Followfrontend/eslint.config.js: component selectors useapp-*, directive selectors useapp*, andanyis disallowed
Files:
frontend/src/app/features/readers/ebook-reader/ebook-reader.component.ts
🧠 Learnings (2)
📚 Learning: 2026-04-07T09:28:09.587Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 393
File: frontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts:255-263
Timestamp: 2026-04-07T09:28:09.587Z
Learning: In this Angular frontend (under frontend/src/app/), flag manual resource management/cleanup patterns when there is an Angular v16+ automatic alternative. Examples to prefer: (1) Instead of manually pairing document/window event listeners with stored cleanup functions (e.g., add/removeEventListener with mouseMoveCleanup/documentClickCleanup/keydownCleanup/touchCleanup fields), register teardown via DestroyRef.onDestroy(cleanupFn) (or equivalent Angular v16+ teardown mechanism). (2) Instead of storing Subscriptions in fields and explicitly unsubscribing in ngOnDestroy (e.g., annotationSaveSubscription/annotationCacheSubscription), use takeUntilDestroyed(destroyRef) (piped into the observable) or other Angular v16+ primitives. (3) If teardown is lifecycle-coupled and can be automated via DestroyRef/takeUntilDestroyed/signals (or other Angular v16+ mechanisms), prefer the automated approach over manual ngOnDestroy cleanup. Raise a review finding for the manual pattern and recommend the aut...
Applied to files:
frontend/src/app/features/readers/ebook-reader/ebook-reader.component.ts
📚 Learning: 2026-04-05T21:16:01.715Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 385
File: frontend/src/app/app.component.ts:55-56
Timestamp: 2026-04-05T21:16:01.715Z
Learning: When reviewing code in the Grimmory frontend (Angular), prefer modern Angular patterns. Specifically: (1) Prefer `DestroyRef` with `takeUntilDestroyed(destroyRef)` for teardown in Angular v16+ instead of manually tracking `Subscription` arrays and calling `unsubscribe()` in `ngOnDestroy()`. (2) Prefer `inject()` for dependency injection over constructor injection where appropriate. (3) Prefer Angular signals (e.g., `signal`, `computed`) over `BehaviorSubject`/`Observable` for state where signals/computed values fit the use case. Flag older patterns when they can be replaced with these modern equivalents without changing behavior.
Applied to files:
frontend/src/app/features/readers/ebook-reader/ebook-reader.component.ts
🔀 Multi-repo context grimmory-tools/grimmory-docs
Findings (repository: grimmory-tools/grimmory-docs)
-
Komga per-page endpoint documented: GET /komga/api/v1/books/{bookId}/pages/{pageNumber} — confirms external integrations expect page-number semantics for PDFs. [::grimmory-tools/grimmory-docs::docs/integration/komga-api.md:254-256]
-
PDF reader docs explicitly state the reader tracks current page number and reading percentage; progress is saved on every page change — aligns with adding pageNumber to bookmarks. [::grimmory-tools/grimmory-docs::docs/readers/pdf-reader.md:103-111]
-
Reader preferences and view docs describe PDF page spread/zoom/page-number features (spread modes include 'None'|'Even'|'Odd' and page input/jump) — matches frontend changes that rename spread values to 'none' and change zoom to string presets. [::grimmory-tools/grimmory-docs::docs/reader-preferences.md:74-83] [::grimmory-tools/grimmory-docs::docs/readers/pdf-reader.md:21]
-
CBX (comic) reader docs reference page-number bookmarks and a sidebar bookmark UX, indicating expected parity of sidebar/bookmark behavior across readers (relevant to the new PdfSidebarComponent). [::grimmory-tools/grimmory-docs::docs/readers/cbx-reader.md:17-18,37,102]
-
Metadata docs note PDFs use page-number progress (vs CFI for ebooks), supporting the database schema addition of page_number and unique index on (user_id, book_id, page_number). [::grimmory-tools/grimmory-docs::docs/metadata/metadata-center.md:228]
Assessment: Documentation in this repo consistently uses page-number semantics for PDFs and describes sidebar/bookmark UX that matches the PR's backend and frontend changes (pageNumber field, uniqueness by page, PdfSidebarComponent, spread/zoom type changes). No conflicting docs referencing a CFI-only bookmark model were found.
…df.js stuff (grimmory-tools#393) * feat(pdf): move ebook reader to embedpdf, add bookmarks, remove old pdf.js stuff * feat(pdf): add page number support for bookmarks and enhance mobile navigation * feat(pdf): implement document ID tracking for search functionality * feat(pdf): add page number column to book marks and update unique index * feat(pdf): enhance annotation handling and improve search result scrolling * feat(pdf): improve layout handling and enhance resize observer functionality * feat(pdf): improve document viewer initialization and improve close button styling * fix(pdf): remove 'form' from disabled categories * feat(pdf): add pan mode functionality and enhance sidebar layout * feat(pdf): improve book viewer initialization with enhanced element detection * feat(pdf): add link handling to text selection and improve popup actions * feat(pdf): enhance link detection and improve selection handling in event service * feat(pdf): improve accessibility and improve resource management in PDF viewer * feat(pdf): add resize observer for improved layout handling * feat(pdf): error handling and improve navigation in PDF reader
…df.js stuff (grimmory-tools#393) * feat(pdf): move ebook reader to embedpdf, add bookmarks, remove old pdf.js stuff * feat(pdf): add page number support for bookmarks and enhance mobile navigation * feat(pdf): implement document ID tracking for search functionality * feat(pdf): add page number column to book marks and update unique index * feat(pdf): enhance annotation handling and improve search result scrolling * feat(pdf): improve layout handling and enhance resize observer functionality * feat(pdf): improve document viewer initialization and improve close button styling * fix(pdf): remove 'form' from disabled categories * feat(pdf): add pan mode functionality and enhance sidebar layout * feat(pdf): improve book viewer initialization with enhanced element detection * feat(pdf): add link handling to text selection and improve popup actions * feat(pdf): enhance link detection and improve selection handling in event service * feat(pdf): improve accessibility and improve resource management in PDF viewer * feat(pdf): add resize observer for improved layout handling * feat(pdf): error handling and improve navigation in PDF reader
…df.js stuff (#393) * feat(pdf): move ebook reader to embedpdf, add bookmarks, remove old pdf.js stuff * feat(pdf): add page number support for bookmarks and enhance mobile navigation * feat(pdf): implement document ID tracking for search functionality * feat(pdf): add page number column to book marks and update unique index * feat(pdf): enhance annotation handling and improve search result scrolling * feat(pdf): improve layout handling and enhance resize observer functionality * feat(pdf): improve document viewer initialization and improve close button styling * fix(pdf): remove 'form' from disabled categories * feat(pdf): add pan mode functionality and enhance sidebar layout * feat(pdf): improve book viewer initialization with enhanced element detection * feat(pdf): add link handling to text selection and improve popup actions * feat(pdf): enhance link detection and improve selection handling in event service * feat(pdf): improve accessibility and improve resource management in PDF viewer * feat(pdf): add resize observer for improved layout handling * feat(pdf): error handling and improve navigation in PDF reader
…df.js stuff (#393) * feat(pdf): move ebook reader to embedpdf, add bookmarks, remove old pdf.js stuff * feat(pdf): add page number support for bookmarks and enhance mobile navigation * feat(pdf): implement document ID tracking for search functionality * feat(pdf): add page number column to book marks and update unique index * feat(pdf): enhance annotation handling and improve search result scrolling * feat(pdf): improve layout handling and enhance resize observer functionality * feat(pdf): improve document viewer initialization and improve close button styling * fix(pdf): remove 'form' from disabled categories * feat(pdf): add pan mode functionality and enhance sidebar layout * feat(pdf): improve book viewer initialization with enhanced element detection * feat(pdf): add link handling to text selection and improve popup actions * feat(pdf): enhance link detection and improve selection handling in event service * feat(pdf): improve accessibility and improve resource management in PDF viewer * feat(pdf): add resize observer for improved layout handling * feat(pdf): error handling and improve navigation in PDF reader
…df.js stuff (grimmory-tools#393) * feat(pdf): move ebook reader to embedpdf, add bookmarks, remove old pdf.js stuff * feat(pdf): add page number support for bookmarks and enhance mobile navigation * feat(pdf): implement document ID tracking for search functionality * feat(pdf): add page number column to book marks and update unique index * feat(pdf): enhance annotation handling and improve search result scrolling * feat(pdf): improve layout handling and enhance resize observer functionality * feat(pdf): improve document viewer initialization and improve close button styling * fix(pdf): remove 'form' from disabled categories * feat(pdf): add pan mode functionality and enhance sidebar layout * feat(pdf): improve book viewer initialization with enhanced element detection * feat(pdf): add link handling to text selection and improve popup actions * feat(pdf): enhance link detection and improve selection handling in event service * feat(pdf): improve accessibility and improve resource management in PDF viewer * feat(pdf): add resize observer for improved layout handling * feat(pdf): error handling and improve navigation in PDF reader
Description
Linked Issue: Fixes #
Changes
This pull request introduces first-class support for PDF bookmarks throughout the backend and frontend, and adds a new, feature-rich sidebar to the PDF reader UI. The main changes include schema and API updates to support PDF bookmarks, backend logic to prevent duplicate bookmarks, and a new Angular component for the PDF sidebar with tabs for contents, bookmarks, and annotations.
Backend: PDF Bookmark Support
pageNumberfield for PDF bookmarks to theBookMark,BookMarkEntity,CreateBookMarkRequest, andUpdateBookMarkRequestmodels, and updated the database schema to include the new column.Frontend: PDF Reader Sidebar
PdfSidebarComponentwith a tabbed UI for navigating the PDF outline, bookmarks, and annotations, including handlers for navigation and deletion actions.Other Frontend Improvements
zoomproperty ofPdfViewerSettingtostringfor improved compatibility.Summary by CodeRabbit
New Features
Bug Fixes