Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/app-expo/src/components/reader/SelectionPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { RichTextEditor } from "@/components/ui/RichTextEditor";
import type { SelectionEvent } from "@/hooks/use-reader-bridge";
import { radius, spacing, useColors, withOpacity } from "@/styles/theme";
import type { ThemeColors } from "@/styles/theme";
import { pageNoteLabel } from "@readany/core/reader";
import { HIGHLIGHT_COLORS, HIGHLIGHT_COLOR_HEX } from "@readany/core/types";
import type { HighlightColor } from "@readany/core/types";
import * as Clipboard from "expo-clipboard";
Expand Down Expand Up @@ -296,7 +297,8 @@ export function SelectionPopover({
</TouchableOpacity>
</View>
<Text style={s.noteModalPreview} numberOfLines={2}>
{selection.text}
{/* 页级笔记(固定版式无选中文本):用「第N页笔记」这类位置标签替代空引用 */}
{selection.text || pageNoteLabel(selection.cfi, t)}
</Text>
<View style={s.editorContainer}>
<RichTextEditor
Expand Down
8 changes: 6 additions & 2 deletions packages/app-expo/src/screens/NotesView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { useFocusEffect, useNavigation } from "@react-navigation/native";
import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
import type { HighlightWithBook } from "@readany/core/db/database";
import { AnnotationExporter, type ExportFormat } from "@readany/core/export";
import { sortAnnotationsByPosition } from "@readany/core/reader";
import { pageNoteLabel, sortAnnotationsByPosition } from "@readany/core/reader";
import type { Highlight } from "@readany/core/types";
import { eventBus } from "@readany/core/utils/event-bus";
/**
Expand Down Expand Up @@ -196,7 +196,11 @@ export function NotesView({
const chapters: { chapter: string; items: HighlightWithBook[] }[] = [];
const chapterMap = new Map<string, HighlightWithBook[]>();
for (const h of currentList) {
const chapter = h.chapterTitle || t("notes.unknownChapter", "未知章节");
// 页级笔记(引用文本为空、固定版式)没有章节标题时用页码标签分组,别全落进「未知章节」
const chapter =
h.chapterTitle ||
(!h.text ? pageNoteLabel(h.cfi, t) : "") ||
t("notes.unknownChapter", "未知章节");
const arr = chapterMap.get(chapter) || [];
arr.push(h);
chapterMap.set(chapter, arr);
Expand Down
87 changes: 80 additions & 7 deletions packages/app-expo/src/screens/ReaderScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
HeadphonesIcon,
LanguagesIcon,
NotebookPenIcon,
PlusIcon,
SearchIcon,
XIcon,
} from "@/components/ui/Icon";
Expand All @@ -39,7 +40,7 @@ import { readingContextService } from "@readany/core/ai/reading-context-service"
import { runWithDbRetry } from "@readany/core/db/write-retry";
import { useChapterTranslation } from "@readany/core/hooks";
import { useReadingSession } from "@readany/core/hooks/use-reading-session";
import { createSelectionNoteMutation } from "@readany/core/reader";
import { createSelectionNoteMutation, pageNoteLabel } from "@readany/core/reader";
import { getPlatformService } from "@readany/core/services";
import { getCSSFontFace, useFontStore } from "@readany/core/stores";
import type { HighlightColor, ReadSettings, TOCItem } from "@readany/core/types";
Expand Down Expand Up @@ -74,6 +75,7 @@ import { SafeAreaView } from "react-native-safe-area-context";
import { WebView } from "react-native-webview";

// ── Extracted modules ──
import { PageNoteModal } from "./reader/PageNoteModal";
import { ReaderNoteViewModal } from "./reader/ReaderNoteViewModal";

const REFLOWABLE_CHARACTERS_PER_LOCATION = 1500;
Expand Down Expand Up @@ -224,6 +226,9 @@ export function ReaderScreen({ route, navigation }: Props) {
const [showSettings, setShowSettings] = useState(false);
const [showSearch, setShowSearch] = useState(false);
const [showNotebook, setShowNotebook] = useState(false);
// 页级笔记(固定版式无选中文本时的新建入口,对应桌面 NotebookPanel 的「+」)
const [showPageNote, setShowPageNote] = useState(false);
const [pageNoteContent, setPageNoteContent] = useState("");
const [showTranslation, setShowTranslation] = useState(false);
const [translationText, setTranslationText] = useState("");
const [showTTS, setShowTTS] = useState(false);
Expand Down Expand Up @@ -1048,6 +1053,39 @@ export function ReaderScreen({ route, navigation }: Props) {
setSelection(null);
}, []);

// 页级笔记:锚定「当前位置」,给固定版式(PDF/CBZ)这种没法选中文本的格式用。
// 与桌面端 NotebookPanel 的「+」完全同语义:落库时 text 为空,页码由 CFI 反推。
const handleSavePageNote = useCallback(() => {
if (!currentCfi || !bookId) return;
const mutation = createSelectionNoteMutation({
bookId,
cfi: currentCfi,
text: "",
note: pageNoteContent,
chapterTitle: currentChapter || undefined,
defaultColor: readSettings.defaultHighlightColor ?? "yellow",
});
if (mutation.kind === "create") {
addHighlight(mutation.highlight);
bridge.addAnnotation({
value: currentCfi,
type: "highlight",
color: mutation.highlight.color,
note: mutation.highlight.note,
});
}
setPageNoteContent("");
setShowPageNote(false);
}, [
currentCfi,
bookId,
pageNoteContent,
currentChapter,
readSettings.defaultHighlightColor,
addHighlight,
bridge,
]);

useEffect(() => {
setGoToCfiFn(() => bridge.goToCFI);
return () => setGoToCfiFn(null);
Expand Down Expand Up @@ -2043,9 +2081,23 @@ export function ReaderScreen({ route, navigation }: Props) {
>
<View style={s.sheetHeader}>
<Text style={s.sheetTitle}>{t("reader.notebook", "笔记本")}</Text>
<TouchableOpacity onPress={() => setShowNotebook(false)}>
<XIcon size={18} color={colors.mutedForeground} />
</TouchableOpacity>
<View style={s.sheetHeaderActions}>
{/* 「+」= 在当前页新建页级笔记(固定版式 PDF/CBZ 无选中文本时的入口;
桌面端同名按钮位于 NotebookPanel 头部同一位置) */}
<TouchableOpacity
disabled={!currentCfi}
style={!currentCfi ? s.sheetHeaderActionDisabled : undefined}
onPress={() => {
setPageNoteContent("");
setShowPageNote(true);
}}
>
<PlusIcon size={18} color={colors.foreground} />
</TouchableOpacity>
<TouchableOpacity onPress={() => setShowNotebook(false)}>
<XIcon size={18} color={colors.mutedForeground} />
</TouchableOpacity>
</View>
</View>
{highlights.length > 0 ? (
<ScrollView showsVerticalScrollIndicator={false} style={s.sheetScroll}>
Expand All @@ -2071,9 +2123,16 @@ export function ReaderScreen({ route, navigation }: Props) {
]}
/>
<View style={s.highlightContent}>
<Text style={s.highlightText} numberOfLines={3}>
{h.text}
</Text>
{h.text ? (
<Text style={s.highlightText} numberOfLines={3}>
{h.text}
</Text>
) : (
/* 页级笔记(text 为空):用「第N页笔记」这类位置标签代替空白引用 */
<Text style={s.highlightLabel} numberOfLines={3}>
{pageNoteLabel(h.cfi, t)}
</Text>
)}
{h.note && <Text style={s.highlightNote}>{h.note}</Text>}
</View>
</View>
Expand All @@ -2090,6 +2149,20 @@ export function ReaderScreen({ route, navigation }: Props) {
</View>
</Modal>

{/* ─── Page-level Note Modal(固定版式「页级笔记」新建入口) ─── */}
<PageNoteModal
visible={showPageNote}
cfi={currentCfi}
chapterTitle={currentChapter || undefined}
content={pageNoteContent}
onContentChange={setPageNoteContent}
onCancel={() => {
setShowPageNote(false);
setPageNoteContent("");
}}
onSave={handleSavePageNote}
/>

{/* ─── Note View Modal ─── */}
<ReaderNoteViewModal
highlight={noteViewHighlight}
Expand Down
10 changes: 9 additions & 1 deletion packages/app-expo/src/screens/notes/HighlightCard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Trash2Icon } from "@/components/ui/Icon";
import { useColors } from "@/styles/theme";
import type { HighlightWithBook } from "@readany/core/db/database";
import { pageNoteLabel } from "@readany/core/reader";
import { HIGHLIGHT_COLOR_HEX } from "@readany/core/types";
import { useTranslation } from "react-i18next";
import { Text, TouchableOpacity, View } from "react-native";
import { makeStyles } from "./notes-styles";

Expand All @@ -15,6 +17,7 @@ export function HighlightCard({
onNavigate: () => void;
}) {
const colors = useColors();
const { t } = useTranslation();
const s = makeStyles(colors);
return (
<View style={s.highlightCard}>
Expand All @@ -24,7 +27,12 @@ export function HighlightCard({
/>
<View style={s.highlightBody}>
<TouchableOpacity onPress={onNavigate}>
<Text style={s.highlightText} numberOfLines={2}>"{highlight.text}"</Text>
{highlight.text ? (
<Text style={s.highlightText} numberOfLines={2}>"{highlight.text}"</Text>
) : (
/* 页级笔记(固定版式无选中文本):用「第N页笔记」这类位置标签替代引用 */
<Text style={s.highlightLabel} numberOfLines={2}>{pageNoteLabel(highlight.cfi, t)}</Text>
)}
</TouchableOpacity>
{highlight.chapterTitle && <Text style={s.highlightChapter}>{highlight.chapterTitle}</Text>}
</View>
Expand Down
17 changes: 14 additions & 3 deletions packages/app-expo/src/screens/notes/NoteCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { CheckIcon, EditIcon, Trash2Icon, XIcon } from "@/components/ui/Icon";
import { RichTextEditor } from "@/components/ui/RichTextEditor";
import { useColors } from "@/styles/theme";
import type { HighlightWithBook } from "@readany/core/db/database";
import { pageNoteLabel } from "@readany/core/reader";
import { HIGHLIGHT_COLOR_HEX } from "@readany/core/types";
import type { TFunction } from "i18next";
import { Text, TouchableOpacity, View } from "react-native";
Expand Down Expand Up @@ -43,9 +44,19 @@ export function NoteCard({
{ backgroundColor: HIGHLIGHT_COLOR_HEX[highlight.color] || colors.amber },
]}
/>
<Text style={s.noteQuote} numberOfLines={2}>
"{highlight.text}"
</Text>
<View style={s.noteQuoteWrap}>
{highlight.text ? (
<Text style={s.noteQuote} numberOfLines={2}>
"{highlight.text}"
</Text>
) : (
/* 页级笔记(固定版式无选中文本):用「第N页笔记」这类位置标签替代引用 */
<Text style={[s.noteQuote, s.noteQuoteEmpty]} numberOfLines={2}>
{pageNoteLabel(highlight.cfi, t)}
</Text>
)}
{highlight.chapterTitle && <Text style={s.noteChapter}>{highlight.chapterTitle}</Text>}
</View>
</TouchableOpacity>

{isEditing ? (
Expand Down
6 changes: 6 additions & 0 deletions packages/app-expo/src/screens/notes/notes-styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,10 @@ export const makeStyles = (colors: ThemeColors) =>
noteCardTop: { flexDirection: "row", alignItems: "flex-start", gap: 8 },
colorDot: { width: 10, height: 10, borderRadius: 5, marginTop: 4 },
noteQuote: { flex: 1, fontSize: fontSize.sm, color: colors.foreground, lineHeight: 20 },
noteQuoteWrap: { flex: 1 },
/** 页级笔记没有引用文本,这里显示「第N页笔记」这类位置标签 */
noteQuoteEmpty: { color: colors.mutedForeground, fontStyle: "italic" },
noteChapter: { fontSize: fontSize.xs, color: colors.mutedForeground, marginTop: 4 },
noteBody: {
marginTop: 8,
backgroundColor: colors.muted,
Expand Down Expand Up @@ -274,6 +278,8 @@ export const makeStyles = (colors: ThemeColors) =>
},
highlightBody: { flex: 1, minWidth: 0 },
highlightText: { fontSize: fontSize.sm, color: colors.foreground, lineHeight: 20 },
/** 页级笔记没有引用文本,这里显示「第N页笔记」这类位置标签 */
highlightLabel: { fontSize: fontSize.sm, color: colors.mutedForeground, fontStyle: "italic", lineHeight: 20 },
highlightChapter: { fontSize: fontSize.xs, color: colors.mutedForeground, marginTop: 4 },
highlightDeleteBtn: { padding: 6, borderRadius: radius.sm },
exportOverlay: { flex: 1 },
Expand Down
Loading