diff --git a/packages/app/src/components/notes/NotesPage.tsx b/packages/app/src/components/notes/NotesPage.tsx index 4fbd54b49..c15bd77ae 100644 --- a/packages/app/src/components/notes/NotesPage.tsx +++ b/packages/app/src/components/notes/NotesPage.tsx @@ -33,6 +33,7 @@ import { */ import { useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; +import { pageNoteLabel } from "@/lib/reader/page-note"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import { toast } from "sonner"; @@ -654,12 +655,21 @@ function NoteDetailCard({
{/* Quoted highlight text */} -

- "{highlight.text}" -

+ {highlight.text ? ( +

+ "{highlight.text}" +

+ ) : ( +

+ {pageNoteLabel(highlight.cfi, t)} +

+ )} {/* Note content */} {isEditing ? ( @@ -751,12 +761,21 @@ function HighlightDetailCard({ highlight, onDelete, onNavigate, t }: HighlightDe />
-

- "{highlight.text}" -

+ {highlight.text ? ( +

+ "{highlight.text}" +

+ ) : ( +

+ {pageNoteLabel(highlight.cfi, t)} +

+ )}
diff --git a/packages/app/src/components/reader/NotebookPanel.tsx b/packages/app/src/components/reader/NotebookPanel.tsx index a57cc99ce..43d8c25e5 100644 --- a/packages/app/src/components/reader/NotebookPanel.tsx +++ b/packages/app/src/components/reader/NotebookPanel.tsx @@ -13,7 +13,9 @@ import { ChevronDown, ChevronRight, Edit3, + FileText, Highlighter, + Plus, NotebookPen, Save, Trash2, @@ -25,11 +27,13 @@ import { import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import ReactMarkdown from "react-markdown"; +import { pageNoteLabel } from "@/lib/reader/page-note"; import remarkGfm from "remark-gfm"; interface NotebookPanelProps { bookId: string; onClose: () => void; + onAddPageNote?: () => void; onGoToCfi?: (cfi: string) => void; onAddAnnotation?: (cfi: string, color: string, note?: string) => void; onDeleteAnnotation?: (cfi: string) => void; @@ -38,6 +42,7 @@ interface NotebookPanelProps { export function NotebookPanel({ bookId, onClose, + onAddPageNote, onGoToCfi, onAddAnnotation, onDeleteAnnotation, @@ -202,6 +207,15 @@ export function NotebookPanel({
{t("notebook.title")}
+ - @@ -381,7 +420,13 @@ function HighlightNoteItem({ style={{ backgroundColor: HIGHLIGHT_COLOR_HEX[highlight.color] }} />
-

"{highlight.text}"

+ {highlight.text ? ( +

"{highlight.text}"

+ ) : ( +

+ {pageNoteLabel(highlight.cfi, t)} +

+ )} {highlight.note && (
{highlight.note} @@ -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/i18n/locales/en/reader.json b/packages/core/src/i18n/locales/en/reader.json index d5b86f34f..fcb296cf2 100644 --- a/packages/core/src/i18n/locales/en/reader.json +++ b/packages/core/src/i18n/locales/en/reader.json @@ -113,6 +113,11 @@ "emptyHint": "Select text in the book to highlight or add notes", "editNote": "Edit note", "addNoteBtn": "Add note", + "addPageNote": "Add page note", + "pageNoteBadge": "Page note", + "pageNotePage": "Page {{page}}", + "pageNoteNoPage": "Current position", + "pageNoteWithPage": "Note on page {{page}}", "deleteHighlightBtn": "Delete highlight" }, "editor": { diff --git a/packages/core/src/i18n/locales/es/reader.json b/packages/core/src/i18n/locales/es/reader.json index b1f3fa60e..27cde9d84 100644 --- a/packages/core/src/i18n/locales/es/reader.json +++ b/packages/core/src/i18n/locales/es/reader.json @@ -109,6 +109,11 @@ "emptyHint": "Selecciona texto en el libro para subrayar o agregar notas", "editNote": "Editar nota", "addNoteBtn": "Agregar nota", + "addPageNote": "Añadir nota de página", + "pageNoteBadge": "Nota de página", + "pageNotePage": "Página {{page}}", + "pageNoteNoPage": "Posición actual", + "pageNoteWithPage": "Nota en la página {{page}}", "deleteHighlightBtn": "Eliminar subrayado" }, "editor": { diff --git a/packages/core/src/i18n/locales/fr/reader.json b/packages/core/src/i18n/locales/fr/reader.json index b754cc55a..68b21a8d4 100644 --- a/packages/core/src/i18n/locales/fr/reader.json +++ b/packages/core/src/i18n/locales/fr/reader.json @@ -109,6 +109,11 @@ "emptyHint": "Sélectionnez du texte dans le livre pour surligner ou ajouter des notes", "editNote": "Modifier la note", "addNoteBtn": "Ajouter une note", + "addPageNote": "Ajouter une note de page", + "pageNoteBadge": "Note de page", + "pageNotePage": "Page {{page}}", + "pageNoteNoPage": "Position actuelle", + "pageNoteWithPage": "Note page {{page}}", "deleteHighlightBtn": "Supprimer le surlignage" }, "editor": { diff --git a/packages/core/src/i18n/locales/ja/reader.json b/packages/core/src/i18n/locales/ja/reader.json index 2edd42242..48fc7d36a 100644 --- a/packages/core/src/i18n/locales/ja/reader.json +++ b/packages/core/src/i18n/locales/ja/reader.json @@ -109,6 +109,11 @@ "emptyHint": "本のテキストを選択してハイライトやノートを追加", "editNote": "ノートを編集", "addNoteBtn": "ノートを追加", + "addPageNote": "ページメモを追加", + "pageNoteBadge": "ページメモ", + "pageNotePage": "{{page}} ページ", + "pageNoteNoPage": "現在位置", + "pageNoteWithPage": "{{page}} ページのメモ", "deleteHighlightBtn": "ハイライトを削除" }, "editor": { diff --git a/packages/core/src/i18n/locales/ko/reader.json b/packages/core/src/i18n/locales/ko/reader.json index 7fefaa391..9b0c5511f 100644 --- a/packages/core/src/i18n/locales/ko/reader.json +++ b/packages/core/src/i18n/locales/ko/reader.json @@ -109,6 +109,11 @@ "emptyHint": "책에서 텍스트를 선택하여 하이라이트하거나 노트를 추가하세요", "editNote": "노트 편집", "addNoteBtn": "노트 추가", + "addPageNote": "페이지 메모 추가", + "pageNoteBadge": "페이지 메모", + "pageNotePage": "{{page}}쪽", + "pageNoteNoPage": "현재 위치", + "pageNoteWithPage": "{{page}}쪽 메모", "deleteHighlightBtn": "하이라이트 삭제" }, "editor": { diff --git a/packages/core/src/i18n/locales/zh-TW/reader.json b/packages/core/src/i18n/locales/zh-TW/reader.json index 5e4117f8f..68a7ea35e 100644 --- a/packages/core/src/i18n/locales/zh-TW/reader.json +++ b/packages/core/src/i18n/locales/zh-TW/reader.json @@ -109,6 +109,11 @@ "emptyHint": "選取書中文字進行醒目標示或新增筆記", "editNote": "編輯筆記", "addNoteBtn": "新增筆記", + "addPageNote": "新增頁級筆記", + "pageNoteBadge": "頁級筆記", + "pageNotePage": "第 {{page}} 頁", + "pageNoteNoPage": "當前位置", + "pageNoteWithPage": "第 {{page}} 頁筆記", "deleteHighlightBtn": "刪除醒目標示" }, "editor": { diff --git a/packages/core/src/i18n/locales/zh/reader.json b/packages/core/src/i18n/locales/zh/reader.json index 706280ce1..79dcbb5c0 100644 --- a/packages/core/src/i18n/locales/zh/reader.json +++ b/packages/core/src/i18n/locales/zh/reader.json @@ -113,6 +113,11 @@ "emptyHint": "选择书中文本进行高亮或添加笔记", "editNote": "编辑笔记", "addNoteBtn": "添加笔记", + "addPageNote": "添加页级笔记", + "pageNoteBadge": "页级笔记", + "pageNotePage": "第 {{page}} 页", + "pageNoteNoPage": "当前位置", + "pageNoteWithPage": "第 {{page}} 页笔记", "deleteHighlightBtn": "删除高亮" }, "editor": { 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 */