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} + />
); }