@@ -440,7 +485,13 @@ function HighlightItem({ highlight, onClick, onAddNote, onDelete }: HighlightIte
style={{ backgroundColor: HIGHLIGHT_COLOR_HEX[highlight.color] }}
/>
-
"{highlight.text}"
+ {highlight.text ? (
+
"{highlight.text}"
+ ) : (
+
+ {pageNoteLabel(highlight.cfi, t)}
+
+ )}
{highlight.chapterTitle && (
{highlight.chapterTitle}
)}
diff --git a/packages/app/src/components/reader/ReaderView.tsx b/packages/app/src/components/reader/ReaderView.tsx
index ade0140b9..6dd27b2be 100644
--- a/packages/app/src/components/reader/ReaderView.tsx
+++ b/packages/app/src/components/reader/ReaderView.tsx
@@ -1673,6 +1673,19 @@ export function ReaderView({ bookId, tabId }: ReaderViewProps) {
setSelection(null);
}, [selection, bookId, highlights, readerTab?.chapterTitle]);
+ // Handle page-level note button — anchor a note to the current position so
+ // formats without usable text selection (e.g. PDF) can still take notes.
+ const handleAddPageNote = useCallback(() => {
+ const cfi = readerTab?.currentCfi;
+ if (!cfi) return;
+ useNotebookStore.getState().startNewNote({
+ text: "",
+ cfi,
+ chapterTitle: readerTab?.chapterTitle,
+ page: currentPage || undefined,
+ });
+ }, [readerTab?.currentCfi, readerTab?.chapterTitle, currentPage]);
+
const handleCopy = useCallback(() => {
if (selection?.text) navigator.clipboard.writeText(selection.text);
setSelection(null);
@@ -2870,6 +2883,7 @@ export function ReaderView({ bookId, tabId }: ReaderViewProps) {
{/* Notebook sidebar — LEFT side */}
{
foliateRef.current?.addAnnotation({
@@ -3214,6 +3228,7 @@ export function ReaderView({ bookId, tabId }: ReaderViewProps) {
// Separate component to use notebook store hook
function NotebookSidebarWrapper({
bookId,
+ onAddPageNote,
onGoToCfi,
onAddAnnotation,
onDeleteAnnotation,
@@ -3223,6 +3238,7 @@ function NotebookSidebarWrapper({
onResizeEnd,
}: {
bookId: string;
+ onAddPageNote?: () => void;
onGoToCfi: (cfi: string) => void;
onAddAnnotation: (cfi: string, color: string, note?: string) => void;
onDeleteAnnotation: (cfi: string) => void;
@@ -3250,6 +3266,7 @@ function NotebookSidebarWrapper({
0 ? page : null;
+}
+
+type LabelT = (key: string, options?: Record) => string;
+
+/** List label for a page-level note: "第N页笔记" when the page is known. */
+export function pageNoteLabel(cfi: string, t: LabelT): string {
+ const page = parseFakeCfiPage(cfi);
+ return page
+ ? t("notebook.pageNoteWithPage", { page })
+ : t("notebook.pageNoteBadge");
+}
diff --git a/packages/core/src/stores/notebook-store.ts b/packages/core/src/stores/notebook-store.ts
index 9c7c6cd91..0d15deb9c 100644
--- a/packages/core/src/stores/notebook-store.ts
+++ b/packages/core/src/stores/notebook-store.ts
@@ -5,8 +5,10 @@ import { create } from "zustand";
import type { Highlight } from "../types";
export interface PendingNote {
- /** Selected text to annotate */
+ /** Selected text to annotate (empty string = page-level note) */
text: string;
+ /** Page number for page-level notes (fixed-layout books) */
+ page?: number;
/** CFI location */
cfi: string;
/** Chapter title for context */