diff --git a/src/components/dialogKb.tsx b/src/components/dialogKb.tsx
new file mode 100644
index 0000000..3ebb39e
--- /dev/null
+++ b/src/components/dialogKb.tsx
@@ -0,0 +1,45 @@
+import React from "react";
+import Dialog, { type DialogProps } from "@mui/material/Dialog";
+import useVirtualKeyboard from "y/hooks/useVirtualKeyboard";
+import { makeStyles } from "y/utils/makesStyles";
+
+const useStyles = makeStyles<{
+ isOpen: boolean;
+ bottom: string;
+}>()((_, { isOpen, bottom }) => {
+ if (isOpen) {
+ return {
+ container: {
+ position: "relative",
+ },
+ root: {
+ position: "absolute",
+ bottom,
+ },
+ };
+ } else {
+ return {
+ container: {},
+ root: {},
+ };
+ }
+});
+
+export default function DialogKb(props: DialogProps) {
+ const { isSupported, isKeyboardOpen, boundingRect } = useVirtualKeyboard();
+
+ const { classes } = useStyles({
+ isOpen: isSupported && isKeyboardOpen,
+ bottom: boundingRect?.height ? `${boundingRect.height}px` : "50vh",
+ });
+
+ return (
+
+ );
+}
diff --git a/src/components/pages/reader/bookmarkTitle.tsx b/src/components/pages/reader/bookmarkTitle.tsx
new file mode 100644
index 0000000..b421f2d
--- /dev/null
+++ b/src/components/pages/reader/bookmarkTitle.tsx
@@ -0,0 +1,46 @@
+import React, { useEffect, useState } from "react";
+import DialogTitle from "@mui/material/DialogTitle";
+import DialogContent from "@mui/material/DialogContent";
+import DialogActions from "@mui/material/DialogActions";
+import Button from "@mui/material/Button";
+import Box from "@mui/material/Box";
+import TextField from "@mui/material/TextField";
+import DialogKb from "y/components/dialogKb";
+
+type BookmarkTitleProps = {
+ open: boolean;
+ title: string;
+ onCancel: () => void;
+ onConfirm: (title: string) => void;
+};
+
+export default function BookmarkTitle(props: BookmarkTitleProps) {
+ const [title, setTitle] = useState(props.title);
+
+ useEffect(() => {
+ setTitle(props.title);
+ }, [props.title]);
+
+ return (
+
+ 书签标题
+
+
+ setTitle(e.target.value as string)}
+ autoFocus
+ label="请输入标题"
+ fullWidth
+ />
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/hooks/useVirtualKeyboard.ts b/src/hooks/useVirtualKeyboard.ts
index 0f3a96a..f7c58d5 100644
--- a/src/hooks/useVirtualKeyboard.ts
+++ b/src/hooks/useVirtualKeyboard.ts
@@ -17,11 +17,15 @@ export default function useVirtualKeyboard() {
}
};
navigator.virtualKeyboard.addEventListener("geometrychange", fn);
- navigator.virtualKeyboard.overlaysContent = true;
+ if (!navigator.virtualKeyboard.overlaysContent) {
+ navigator.virtualKeyboard.overlaysContent = true;
+ }
return () => {
navigator.virtualKeyboard.removeEventListener("geometrychange", fn);
- navigator.virtualKeyboard.overlaysContent = false;
+ if (navigator.virtualKeyboard.overlaysContent) {
+ navigator.virtualKeyboard.overlaysContent = false;
+ }
};
}
}, [isSupported]);
diff --git a/src/pages/reader/index.tsx b/src/pages/reader/index.tsx
index afef49f..cb8b452 100644
--- a/src/pages/reader/index.tsx
+++ b/src/pages/reader/index.tsx
@@ -13,6 +13,7 @@ import { useTheme } from "@mui/material/styles";
import useMediaQuery from "@mui/material/useMediaQuery";
import { makeStyles } from "y/utils/makesStyles";
import { useReader } from "y/components/pages/reader/epubReader";
+import BookmarkTitle from "y/components/pages/reader/bookmarkTitle";
import {
apiUpdateBookCurrent,
getFileUrl,
@@ -41,6 +42,7 @@ import {
} from "y/components/nestedList";
import { useMutation, useQuery } from "@tanstack/react-query";
import useScreenWakeLock from "y/hooks/useScreenWakeLock";
+import { type CreateMarkParams } from "../api/mark/create";
const useStyles = makeStyles()((theme) => ({
root: { display: "flex", flexDirection: "row-reverse" },
@@ -163,27 +165,6 @@ export default function Reader(props: ReaderProps) {
void router.prefetch("/bookshelf");
}, [router]);
- const addBookmark = async () => {
- if (!epubReaderRef.current) return;
-
- const location = await epubReaderRef.current.currentLocation();
- const cfi = location.start.cfi;
- const range = epubReaderRef.current.getRange(cfi);
- const title = range.startContainer
- ? getElementHeading(range.startContainer as HTMLElement)
- : "";
- await addMark({
- bookId: id,
- type: MarkType.Bookmark,
- selectedString: truncate(range.startContainer.textContent ?? ""),
- epubcfi: cfi,
- title,
- color: "",
- content: "",
- });
- await bookmarkListQuery.refetch();
- };
-
const reportCurrentLocation = useCallback(async () => {
const location = await epubReaderRef.current?.currentLocation();
const cfi = location?.start?.cfi;
@@ -229,6 +210,45 @@ export default function Reader(props: ReaderProps) {
epubReaderRef.current?.removeHighlightById(mark.id);
};
+ // add or update bookmark
+ const [bookmarkTitleOpen, setBookmarkTitleOpen] = useState(false);
+ const [bookmarkTitle, setBookmarkTitle] = useState("");
+ const createBookmarkRef = useRef();
+
+ const addBookmark = async () => {
+ if (!epubReaderRef.current) return;
+
+ const location = await epubReaderRef.current.currentLocation();
+ const cfi = location.start.cfi;
+ const range = epubReaderRef.current.getRange(cfi);
+ const title = range.startContainer
+ ? getElementHeading(range.startContainer as HTMLElement)
+ : "";
+ createBookmarkRef.current = {
+ bookId: id,
+ type: MarkType.Bookmark,
+ selectedString: truncate(range.startContainer.textContent ?? ""),
+ epubcfi: cfi,
+ title,
+ color: "",
+ content: "",
+ };
+ setBookmarkTitleOpen(true);
+ setBookmarkTitle(title);
+ };
+
+ const handleBookmarkTitleConfirm = async (title: string) => {
+ if (!createBookmarkRef.current) return;
+
+ setBookmarkTitleOpen(false);
+ await addMark({
+ ...createBookmarkRef.current,
+ title,
+ });
+ createBookmarkRef.current = undefined;
+ await bookmarkListQuery.refetch();
+ };
+
return (
@@ -285,6 +305,12 @@ export default function Reader(props: ReaderProps) {
className={cx(classes.pageIcon, classes.next)}
/>
+ setBookmarkTitleOpen(false)}
+ onConfirm={handleBookmarkTitleConfirm}
+ />
);
}