diff --git a/src/containers/epubReader/component.tsx b/src/containers/epubReader/component.tsx new file mode 100644 index 00000000..dcb85ba9 --- /dev/null +++ b/src/containers/epubReader/component.tsx @@ -0,0 +1,344 @@ +import React from "react"; +import ViewArea from "../epubViewer"; +import Background from "../background"; +import SettingPanel from "../panels/settingPanel"; +import NavigationPanel from "../panels/navigationPanel"; +import OperationPanel from "../panels/operationPanel"; +import MessageBox from "../messageBox"; +import ProgressPanel from "../panels/progressPanel"; +import { ReaderProps, ReaderState } from "./interface"; +import { MouseEvent } from "../../utils/mouseEvent"; +import OtherUtil from "../../utils/otherUtil"; +import ReadingTime from "../../utils/readUtils/readingTime"; + +class Reader extends React.Component { + messageTimer!: NodeJS.Timeout; + tickTimer!: NodeJS.Timeout; + rendition: any; + + constructor(props: ReaderProps) { + super(props); + this.state = { + isOpenSettingPanel: + OtherUtil.getReaderConfig("isSettingLocked") === "yes" ? true : false, + isOpenOperationPanel: false, + isOpenProgressPanel: false, + isOpenNavPanel: + OtherUtil.getReaderConfig("isNavLocked") === "yes" ? true : false, + isMessage: false, + rendition: null, + scale: OtherUtil.getReaderConfig("scale") || 1, + margin: parseInt(OtherUtil.getReaderConfig("margin")) || 30, + time: ReadingTime.getTime(this.props.currentBook.key), + isTouch: OtherUtil.getReaderConfig("isTouch") === "yes", + readerMode: OtherUtil.getReaderConfig("readerMode") || "double", + }; + } + componentWillMount() { + this.props.handleFetchBookmarks(); + this.props.handleFetchPercentage(this.props.currentBook); + this.props.handleFetchNotes(); + this.props.handleFetchBooks(); + this.props.handleFetchChapters(this.props.currentEpub); + } + UNSAFE_componentWillReceiveProps(nextProps: ReaderProps) { + this.setState({ + isMessage: nextProps.isMessage, + }); + + //控制消息提示两秒之后消失 + if (nextProps.isMessage) { + this.messageTimer = setTimeout(() => { + this.props.handleMessageBox(false); + this.setState({ isMessage: false }); + }, 2000); + } + } + componentDidMount() { + this.handleRenderBook(); + window.addEventListener("resize", () => { + this.handleRenderBook(); + }); + } + handleRenderBook = () => { + let page = document.querySelector("#page-area"); + let epub = this.props.currentEpub; + (window as any).rangy.init(); // 初始化 + this.rendition = epub.renderTo(page, { + manager: + this.state.readerMode === "continuous" ? "continuous" : "default", + flow: this.state.readerMode === "continuous" ? "scrolled" : "auto", + width: "100%", + height: "100%", + snap: true, + spread: + OtherUtil.getReaderConfig("readerMode") === "single" ? "none" : "", + }); + this.setState({ rendition: this.rendition }); + this.state.readerMode !== "continuous" && MouseEvent(this.rendition); // 绑定事件 + this.tickTimer = setInterval(() => { + let time = this.state.time; + time += 1; + this.setState({ time }); + }, 1000); + }; + + //进入阅读器 + handleEnterReader = (position: string) => { + //控制上下左右的菜单的显示 + switch (position) { + case "right": + this.setState({ + isOpenSettingPanel: this.state.isOpenSettingPanel ? false : true, + }); + break; + case "left": + this.setState({ + isOpenNavPanel: this.state.isOpenNavPanel ? false : true, + }); + break; + case "top": + this.setState({ + isOpenOperationPanel: this.state.isOpenOperationPanel ? false : true, + }); + break; + case "bottom": + this.setState({ + isOpenProgressPanel: this.state.isOpenProgressPanel ? false : true, + }); + break; + default: + break; + } + }; + //退出阅读器 + handleLeaveReader = (position: string) => { + //控制上下左右的菜单的显示 + switch (position) { + case "right": + if (OtherUtil.getReaderConfig("isSettingLocked") === "yes") { + break; + } else { + this.setState({ isOpenSettingPanel: false }); + break; + } + + case "left": + if (OtherUtil.getReaderConfig("isNavLocked") === "yes") { + break; + } else { + this.setState({ isOpenNavPanel: false }); + break; + } + case "top": + this.setState({ isOpenOperationPanel: false }); + break; + case "bottom": + this.setState({ isOpenProgressPanel: false }); + break; + default: + break; + } + }; + nextPage = () => { + this.state.rendition.next(); + }; + prevPage = () => { + this.state.rendition.prev(); + }; + render() { + const renditionProps = { + rendition: this.state.rendition, + handleLeaveReader: this.handleLeaveReader, + handleEnterReader: this.handleEnterReader, + isShow: + this.state.isOpenNavPanel || + this.state.isOpenOperationPanel || + this.state.isOpenProgressPanel || + this.state.isOpenSettingPanel, + }; + return ( +
+
{ + this.prevPage(); + }} + > + +
+
{ + this.nextPage(); + }} + > + +
+
{ + this.handleEnterReader("left"); + this.handleEnterReader("right"); + this.handleEnterReader("bottom"); + this.handleEnterReader("top"); + }} + > + +
+ {this.state.isMessage ? : null} +
{ + if (this.state.isTouch || this.state.isOpenNavPanel) { + return; + } + this.handleEnterReader("left"); + }} + onClick={() => { + this.handleEnterReader("left"); + }} + >
+
{ + if (this.state.isTouch || this.state.isOpenSettingPanel) { + return; + } + this.handleEnterReader("right"); + }} + onClick={() => { + this.handleEnterReader("right"); + }} + >
+
{ + if (this.state.isTouch || this.state.isOpenOperationPanel) { + return; + } + this.handleEnterReader("top"); + }} + onClick={() => { + this.handleEnterReader("top"); + }} + >
+
{ + if (this.state.isTouch || this.state.isOpenProgressPanel) { + return; + } + this.handleEnterReader("bottom"); + }} + onClick={() => { + this.handleEnterReader("bottom"); + }} + >
+ + {this.state.rendition && this.props.currentEpub.rendition && ( + + )} +
{ + this.handleLeaveReader("right"); + }} + style={ + this.state.isOpenSettingPanel + ? {} + : { + transform: "translateX(309px)", + } + } + > + +
+
{ + this.handleLeaveReader("left"); + }} + style={ + this.state.isOpenNavPanel + ? {} + : { + transform: "translateX(-309px)", + } + } + > + +
+
{ + this.handleLeaveReader("bottom"); + }} + style={ + this.state.isOpenProgressPanel + ? {} + : { + transform: "translateY(110px)", + } + } + > + +
+
{ + this.handleLeaveReader("top"); + }} + style={ + this.state.isOpenOperationPanel + ? {} + : { + transform: "translateY(-110px)", + } + } + > + +
+
+ + +
+ ); + } +} + +export default Reader; diff --git a/src/containers/epubViewer/epubViewer.css b/src/containers/epubReader/epubViewer.css similarity index 93% rename from src/containers/epubViewer/epubViewer.css rename to src/containers/epubReader/epubViewer.css index fb6dbf56..0abb06fa 100644 --- a/src/containers/epubViewer/epubViewer.css +++ b/src/containers/epubReader/epubViewer.css @@ -67,7 +67,7 @@ height: 200px; position: absolute; left: 0px; - top: calc(50% - 100px); + top: calc(50vh - 100px); background-color: pink; z-index: 10; opacity: 0; @@ -77,7 +77,7 @@ height: 200px; position: absolute; right: 0px; - top: calc(50% - 100px); + top: calc(50vh - 100px); background-color: pink; z-index: 10; opacity: 0; @@ -86,7 +86,7 @@ width: 200px; height: 50px; position: absolute; - right: calc(50% - 100px); + right: calc(50vw - 100px); top: 0px; background-color: pink; z-index: 10; @@ -96,8 +96,8 @@ width: 200px; height: 50px; position: absolute; - right: calc(50% - 100px); - bottom: 0px; + right: calc(50vw - 100px); + top: calc(100vh - 50px); background-color: pink; z-index: 10; opacity: 0; @@ -107,7 +107,7 @@ height: 60px; position: absolute; top: 0px; - left: calc(50% - 206px); + left: calc(50vw - 206px); z-index: 15; transition: transform 0.5s ease; @@ -116,8 +116,8 @@ width: 412px; height: 60px; position: absolute; - bottom: 0px; - left: calc(50% - 206px); + top: calc(100vh - 60px); + left: calc(50vw - 206px); z-index: 15; transition: transform 0.5s ease; } @@ -125,7 +125,7 @@ width: 299px; height: 100vh; position: absolute; - bottom: 0px; + top: 0px; right: 0px; transition: transform 0.5s ease; z-index: 15; @@ -134,7 +134,7 @@ width: 299px; height: 100vh; position: absolute; - bottom: 0px; + top: 0px; left: 0px; transition: transform 0.5s ease; z-index: 15; diff --git a/src/containers/epubReader/index.tsx b/src/containers/epubReader/index.tsx new file mode 100644 index 00000000..368e1d31 --- /dev/null +++ b/src/containers/epubReader/index.tsx @@ -0,0 +1,31 @@ +import { + handleFetchNotes, + handleFetchBookmarks, + handleFetchChapters, +} from "../../store/actions/reader"; +import { handleFetchPercentage } from "../../store/actions/progressPanel"; +import { + handleMessageBox, + handleFetchBooks, +} from "../../store/actions/manager"; +import "./epubViewer.css"; +import { connect } from "react-redux"; +import { stateType } from "../../store"; +import Reader from "./component"; + +const mapStateToProps = (state: stateType) => { + return { + currentEpub: state.book.currentEpub, + currentBook: state.book.currentBook, + isMessage: state.manager.isMessage, + }; +}; +const actionCreator = { + handleFetchNotes, + handleFetchBookmarks, + handleFetchChapters, + handleMessageBox, + handleFetchPercentage, + handleFetchBooks, +}; +export default connect(mapStateToProps, actionCreator)(Reader); diff --git a/src/containers/epubReader/interface.tsx b/src/containers/epubReader/interface.tsx new file mode 100644 index 00000000..84f6dca5 --- /dev/null +++ b/src/containers/epubReader/interface.tsx @@ -0,0 +1,26 @@ +import BookModel from "../../model/Book"; +export interface ReaderProps { + currentEpub: any; + currentBook: BookModel; + isMessage: boolean; + handleFetchNotes: () => void; + handleFetchBooks: () => void; + handleFetchBookmarks: () => void; + handleMessageBox: (isShow: boolean) => void; + handleFetchPercentage: (currentBook: BookModel) => void; + handleFetchChapters: (currentEpub: any) => void; +} + +export interface ReaderState { + isOpenSettingPanel: boolean; + isOpenOperationPanel: boolean; + isOpenProgressPanel: boolean; + isOpenNavPanel: boolean; + isMessage: boolean; + isTouch: boolean; + readerMode: string; + rendition: any; + time: number; + scale: string; + margin: number; +} diff --git a/src/containers/epubViewer/component.tsx b/src/containers/epubViewer/component.tsx index 23e68097..a4ae6a9a 100644 --- a/src/containers/epubViewer/component.tsx +++ b/src/containers/epubViewer/component.tsx @@ -1,345 +1,126 @@ +//阅读器图书内容区域 import React from "react"; -import ViewArea from "../viewArea"; -import Background from "../background"; -import SettingPanel from "../panels/settingPanel"; -import NavigationPanel from "../panels/navigationPanel"; -import OperationPanel from "../panels/operationPanel"; -import MessageBox from "../messageBox"; -import ProgressPanel from "../panels/progressPanel"; -import { ReaderProps, ReaderState } from "./interface"; -import { MouseEvent } from "../../utils/mouseEvent"; +import "./viewArea.css"; +import PopupMenu from "../../components/popups/popupMenu"; +import { ViewAreaProps, ViewAreaStates } from "./interface"; +import RecordLocation from "../../utils/readUtils/recordLocation"; import OtherUtil from "../../utils/otherUtil"; -import ReadingTime from "../../utils/readUtils/readingTime"; +import BookmarkModel from "../../model/Bookmark"; +import StyleUtil from "../../utils/readUtils/styleUtil"; +import ImageViewer from "../../components/imageViewer"; +import Lottie from "react-lottie"; +import animationSiri from "../../assets/lotties/siri.json"; -class Reader extends React.Component { - messageTimer!: NodeJS.Timeout; - tickTimer!: NodeJS.Timeout; - rendition: any; +const siriOptions = { + loop: true, + autoplay: true, + animationData: animationSiri, + rendererSettings: { + preserveAspectRatio: "xMidYMid slice", + }, +}; +declare var window: any; - constructor(props: ReaderProps) { +class ViewArea extends React.Component { + isFirst: boolean; + constructor(props: ViewAreaProps) { super(props); this.state = { - isOpenSettingPanel: - OtherUtil.getReaderConfig("isSettingLocked") === "yes" ? true : false, - isOpenOperationPanel: false, - isOpenProgressPanel: false, - isOpenNavPanel: - OtherUtil.getReaderConfig("isNavLocked") === "yes" ? true : false, - isMessage: false, - rendition: null, - scale: OtherUtil.getReaderConfig("scale") || 1, - margin: parseInt(OtherUtil.getReaderConfig("margin")) || 30, - time: ReadingTime.getTime(this.props.currentBook.key), - isTouch: OtherUtil.getReaderConfig("isTouch") === "yes", - readerMode: OtherUtil.getReaderConfig("readerMode") || "double", + cfiRange: null, + contents: null, + rect: null, + loading: true, }; + this.isFirst = true; } - componentWillMount() { - this.props.handleFetchBookmarks(); - this.props.handleFetchPercentage(this.props.currentBook); - this.props.handleFetchNotes(); - this.props.handleFetchBooks(); - this.props.handleFetchChapters(this.props.currentEpub); - } - UNSAFE_componentWillReceiveProps(nextProps: ReaderProps) { - this.setState({ - isMessage: nextProps.isMessage, - }); - //控制消息提示两秒之后消失 - if (nextProps.isMessage) { - this.messageTimer = setTimeout(() => { - this.props.handleMessageBox(false); - this.setState({ isMessage: false }); - }, 2000); - } - } componentDidMount() { - this.handleRenderBook(); - window.addEventListener("resize", () => { - this.handleRenderBook(); - }); - } - handleRenderBook = () => { - let page = document.querySelector("#page-area"); let epub = this.props.currentEpub; - (window as any).rangy.init(); // 初始化 - this.rendition = epub.renderTo(page, { - manager: - this.state.readerMode === "continuous" ? "continuous" : "default", - flow: this.state.readerMode === "continuous" ? "scrolled" : "auto", - width: "100%", - height: "100%", - snap: true, - spread: - OtherUtil.getReaderConfig("readerMode") === "single" ? "none" : "", + window.rangy.init(); // 初始化 + this.props.rendition.on("locationChanged", () => { + this.props.handleReadingEpub(epub); + this.props.handleOpenMenu(false); + const currentLocation = this.props.rendition.currentLocation(); + if (!currentLocation.start) { + return; + } + const cfi = currentLocation.start.cfi; + this.props.handleShowBookmark( + this.props.bookmarks && + this.props.bookmarks.filter( + (item: BookmarkModel) => item.cfi === cfi + )[0] + ? true + : false + ); + + if (!this.isFirst && this.props.locations) { + let percentage = this.props.locations.percentageFromCfi(cfi); + RecordLocation.recordCfi(this.props.currentBook.key, cfi, percentage); + this.props.handlePercentage(percentage); + } else if (!this.isFirst) { + //如果过暂时没有解析出locations,就直接记录cfi + RecordLocation.recordCfi( + this.props.currentBook.key, + cfi, + RecordLocation.getCfi(this.props.currentBook.key).percentage + ); + } + this.isFirst = false; }); - this.setState({ rendition: this.rendition }); - this.state.readerMode !== "continuous" && MouseEvent(this.rendition); // 绑定事件 - this.tickTimer = setInterval(() => { - let time = this.state.time; - time += 1; - this.setState({ time }); - }, 1000); - }; + this.props.rendition.on("rendered", () => { + this.setState({ loading: false }); + let iframe = document.getElementsByTagName("iframe")[0]; + if (!iframe) return; + let doc = iframe.contentDocument; + if (!doc) { + return; + } + StyleUtil.addDefaultCss(); + this.props.rendition.themes.default(StyleUtil.getCustomCss(false)); + }); + this.props.rendition.on("selected", (cfiRange: any, contents: any) => { + var range = contents.range(cfiRange); + var rect = range.getBoundingClientRect(); + this.setState({ cfiRange, contents, rect }); + }); + this.props.rendition.themes.default(StyleUtil.getCustomCss(false)); + this.props.rendition.display( + RecordLocation.getCfi(this.props.currentBook.key) === null + ? null + : RecordLocation.getCfi(this.props.currentBook.key).cfi + ); + } - //进入阅读器 - handleEnterReader = (position: string) => { - //控制上下左右的菜单的显示 - switch (position) { - case "right": - this.setState({ - isOpenSettingPanel: this.state.isOpenSettingPanel ? false : true, - }); - break; - case "left": - this.setState({ - isOpenNavPanel: this.state.isOpenNavPanel ? false : true, - }); - break; - case "top": - this.setState({ - isOpenOperationPanel: this.state.isOpenOperationPanel ? false : true, - }); - break; - case "bottom": - this.setState({ - isOpenProgressPanel: this.state.isOpenProgressPanel ? false : true, - }); - break; - default: - break; - } - }; - //退出阅读器 - handleLeaveReader = (position: string) => { - //控制上下左右的菜单的显示 - switch (position) { - case "right": - if (OtherUtil.getReaderConfig("isSettingLocked") === "yes") { - break; - } else { - this.setState({ isOpenSettingPanel: false }); - break; - } - - case "left": - if (OtherUtil.getReaderConfig("isNavLocked") === "yes") { - break; - } else { - this.setState({ isOpenNavPanel: false }); - break; - } - case "top": - this.setState({ isOpenOperationPanel: false }); - break; - case "bottom": - this.setState({ isOpenProgressPanel: false }); - break; - default: - break; - } - }; - nextPage = () => { - this.state.rendition.next(); - }; - prevPage = () => { - this.state.rendition.prev(); - }; render() { - const renditionProps = { - rendition: this.state.rendition, - handleLeaveReader: this.handleLeaveReader, - handleEnterReader: this.handleEnterReader, - isShow: - this.state.isOpenNavPanel || - this.state.isOpenOperationPanel || - this.state.isOpenProgressPanel || - this.state.isOpenSettingPanel, + const popupMenuProps = { + rendition: this.props.rendition, + cfiRange: this.state.cfiRange, + contents: this.state.contents, + rect: this.state.rect, }; return ( -
-
{ - this.prevPage(); +
+ - -
-
{ - this.nextPage(); - }} - > - -
-
{ - this.handleEnterReader("left"); - this.handleEnterReader("right"); - this.handleEnterReader("bottom"); - this.handleEnterReader("top"); - }} - > - -
- {this.state.isMessage ? : null} -
{ - if (this.state.isTouch || this.state.isOpenNavPanel) { - return; - } - this.handleEnterReader("left"); - }} - onClick={() => { - this.handleEnterReader("left"); - }} - >
-
{ - if (this.state.isTouch || this.state.isOpenSettingPanel) { - return; - } - this.handleEnterReader("right"); - }} - onClick={() => { - this.handleEnterReader("right"); - }} - >
-
{ - if (this.state.isTouch || this.state.isOpenOperationPanel) { - return; - } - this.handleEnterReader("top"); - }} - onClick={() => { - this.handleEnterReader("top"); - }} - >
-
{ - if (this.state.isTouch || this.state.isOpenProgressPanel) { - return; - } - this.handleEnterReader("bottom"); - }} - onClick={() => { - this.handleEnterReader("bottom"); - }} - >
- - {this.state.rendition && this.props.currentEpub.rendition && ( - - )} -
{ - this.handleLeaveReader("right"); - }} - style={ - this.state.isOpenSettingPanel - ? {} - : { - transform: "translateX(309px)", - } - } - > - -
-
{ - this.handleLeaveReader("left"); - }} - style={ - this.state.isOpenNavPanel - ? {} - : { - transform: "translateX(-309px)", - } - } - > - -
-
{ - this.handleLeaveReader("bottom"); - }} - style={ - this.state.isOpenProgressPanel - ? {} - : { - transform: "translateY(110px)", - } - } - > - -
-
{ - this.handleLeaveReader("top"); - }} - style={ - this.state.isOpenOperationPanel - ? {} - : { - transform: "translateY(-110px)", - } - } - > - -
- -
- - + /> + + {this.state.loading ? ( +
+ +
+ ) : null} + <> + {this.props.isShowBookmark ?
: null} +
); } } -export default Reader; +export default ViewArea; diff --git a/src/containers/epubViewer/index.tsx b/src/containers/epubViewer/index.tsx index 368e1d31..ae073df4 100644 --- a/src/containers/epubViewer/index.tsx +++ b/src/containers/epubViewer/index.tsx @@ -1,31 +1,28 @@ -import { - handleFetchNotes, - handleFetchBookmarks, - handleFetchChapters, -} from "../../store/actions/reader"; -import { handleFetchPercentage } from "../../store/actions/progressPanel"; -import { - handleMessageBox, - handleFetchBooks, -} from "../../store/actions/manager"; -import "./epubViewer.css"; import { connect } from "react-redux"; import { stateType } from "../../store"; -import Reader from "./component"; +import ViewArea from "./component"; +import { handlePercentage } from "../../store/actions/progressPanel"; +import { + handleOpenMenu, + handleShowBookmark, +} from "../../store/actions/viewArea"; +import { handleReadingEpub } from "../../store/actions/book"; const mapStateToProps = (state: stateType) => { return { + chapters: state.reader.chapters, currentEpub: state.book.currentEpub, currentBook: state.book.currentBook, - isMessage: state.manager.isMessage, + locations: state.progressPanel.locations, + bookmarks: state.reader.bookmarks, + isShowBookmark: state.viewArea.isShowBookmark, }; }; const actionCreator = { - handleFetchNotes, - handleFetchBookmarks, - handleFetchChapters, - handleMessageBox, - handleFetchPercentage, - handleFetchBooks, + handlePercentage, + handleOpenMenu, + handleShowBookmark, + handleReadingEpub, }; -export default connect(mapStateToProps, actionCreator)(Reader); + +export default connect(mapStateToProps, actionCreator)(ViewArea); diff --git a/src/containers/epubViewer/interface.tsx b/src/containers/epubViewer/interface.tsx index 84f6dca5..02506e91 100644 --- a/src/containers/epubViewer/interface.tsx +++ b/src/containers/epubViewer/interface.tsx @@ -1,26 +1,26 @@ import BookModel from "../../model/Book"; -export interface ReaderProps { - currentEpub: any; - currentBook: BookModel; - isMessage: boolean; - handleFetchNotes: () => void; - handleFetchBooks: () => void; - handleFetchBookmarks: () => void; - handleMessageBox: (isShow: boolean) => void; - handleFetchPercentage: (currentBook: BookModel) => void; - handleFetchChapters: (currentEpub: any) => void; -} +import BookmarkModel from "../../model/Bookmark"; -export interface ReaderState { - isOpenSettingPanel: boolean; - isOpenOperationPanel: boolean; - isOpenProgressPanel: boolean; - isOpenNavPanel: boolean; - isMessage: boolean; - isTouch: boolean; - readerMode: string; +export interface ViewAreaProps { rendition: any; - time: number; - scale: string; - margin: number; + currentBook: BookModel; + currentEpub: any; + bookmarks: BookmarkModel[]; + locations: any; + isShowBookmark: boolean; + chapters: any[]; + isShow: boolean; + handleLeaveReader: (position: string) => void; + handleEnterReader: (position: string) => void; + handlePercentage: (percentage: number) => void; + handleOpenMenu: (isOpenMenu: boolean) => void; + handleShowBookmark: (isShowBookmark: boolean) => void; + handleReadingEpub: (epub: object) => void; +} +export interface ViewAreaStates { + loading: boolean; + cfiRange: any; + contents: any; + // rendition: any; + rect: any; } diff --git a/src/containers/viewArea/viewArea.css b/src/containers/epubViewer/viewArea.css similarity index 100% rename from src/containers/viewArea/viewArea.css rename to src/containers/epubViewer/viewArea.css diff --git a/src/containers/htmlReader/component.tsx b/src/containers/htmlReader/component.tsx new file mode 100644 index 00000000..0a4f99dd --- /dev/null +++ b/src/containers/htmlReader/component.tsx @@ -0,0 +1,283 @@ +import React from "react"; +import SettingPanel from "../panels/settingPanel"; +import NavigationPanel from "../panels/navigationPanel"; +import OperationPanel from "../panels/operationPanel"; +import MessageBox from "../messageBox"; +import ProgressPanel from "../panels/progressPanel"; +import { ReaderProps, ReaderState } from "./interface"; +import OtherUtil from "../../utils/otherUtil"; +import ReadingTime from "../../utils/readUtils/readingTime"; +import Viewer from "../htmlViewer"; + +class Reader extends React.Component { + messageTimer!: NodeJS.Timeout; + tickTimer!: NodeJS.Timeout; + rendition: any; + + constructor(props: ReaderProps) { + super(props); + this.state = { + isOpenSettingPanel: + OtherUtil.getReaderConfig("isSettingLocked") === "yes" ? true : false, + isOpenOperationPanel: false, + isOpenProgressPanel: false, + isOpenNavPanel: + OtherUtil.getReaderConfig("isNavLocked") === "yes" ? true : false, + isMessage: false, + rendition: null, + scale: OtherUtil.getReaderConfig("scale") || 1, + margin: parseInt(OtherUtil.getReaderConfig("margin")) || 30, + time: ReadingTime.getTime(this.props.currentBook.key), + isTouch: OtherUtil.getReaderConfig("isTouch") === "yes", + readerMode: OtherUtil.getReaderConfig("readerMode") || "double", + }; + } + componentWillMount() { + // this.props.handleFetchBookmarks(); + // this.props.handleFetchPercentage(this.props.currentBook); + // this.props.handleFetchNotes(); + // this.props.handleFetchBooks(); + // this.props.handleFetchChapters(this.props.currentEpub); + } + UNSAFE_componentWillReceiveProps(nextProps: ReaderProps) { + this.setState({ + isMessage: nextProps.isMessage, + }); + + //控制消息提示两秒之后消失 + if (nextProps.isMessage) { + this.messageTimer = setTimeout(() => { + this.props.handleMessageBox(false); + this.setState({ isMessage: false }); + }, 2000); + } + } + + //进入阅读器 + handleEnterReader = (position: string) => { + //控制上下左右的菜单的显示 + switch (position) { + case "right": + this.setState({ + isOpenSettingPanel: this.state.isOpenSettingPanel ? false : true, + }); + break; + case "left": + this.setState({ + isOpenNavPanel: this.state.isOpenNavPanel ? false : true, + }); + break; + case "top": + this.setState({ + isOpenOperationPanel: this.state.isOpenOperationPanel ? false : true, + }); + break; + case "bottom": + this.setState({ + isOpenProgressPanel: this.state.isOpenProgressPanel ? false : true, + }); + break; + default: + break; + } + }; + //退出阅读器 + handleLeaveReader = (position: string) => { + //控制上下左右的菜单的显示 + switch (position) { + case "right": + if (OtherUtil.getReaderConfig("isSettingLocked") === "yes") { + break; + } else { + this.setState({ isOpenSettingPanel: false }); + break; + } + + case "left": + if (OtherUtil.getReaderConfig("isNavLocked") === "yes") { + break; + } else { + this.setState({ isOpenNavPanel: false }); + break; + } + case "top": + this.setState({ isOpenOperationPanel: false }); + break; + case "bottom": + this.setState({ isOpenProgressPanel: false }); + break; + default: + break; + } + }; + nextPage = () => { + this.state.rendition.next(); + }; + prevPage = () => { + this.state.rendition.prev(); + }; + render() { + const renditionProps = { + rendition: this.state.rendition, + handleLeaveReader: this.handleLeaveReader, + handleEnterReader: this.handleEnterReader, + isShow: + this.state.isOpenNavPanel || + this.state.isOpenOperationPanel || + this.state.isOpenProgressPanel || + this.state.isOpenSettingPanel, + }; + return ( +
+
{ + this.prevPage(); + }} + > + +
+
{ + this.nextPage(); + }} + > + +
+
{ + this.handleEnterReader("left"); + this.handleEnterReader("right"); + this.handleEnterReader("bottom"); + this.handleEnterReader("top"); + }} + > + +
+ {this.state.isMessage ? : null} +
{ + if (this.state.isTouch || this.state.isOpenNavPanel) { + return; + } + this.handleEnterReader("left"); + }} + onClick={() => { + this.handleEnterReader("left"); + }} + >
+
{ + if (this.state.isTouch || this.state.isOpenSettingPanel) { + return; + } + this.handleEnterReader("right"); + }} + onClick={() => { + this.handleEnterReader("right"); + }} + >
+
{ + if (this.state.isTouch || this.state.isOpenOperationPanel) { + return; + } + this.handleEnterReader("top"); + }} + onClick={() => { + this.handleEnterReader("top"); + }} + >
+
{ + if (this.state.isTouch || this.state.isOpenProgressPanel) { + return; + } + this.handleEnterReader("bottom"); + }} + onClick={() => { + this.handleEnterReader("bottom"); + }} + >
+ +
{ + this.handleLeaveReader("right"); + }} + style={ + this.state.isOpenSettingPanel + ? {} + : { + transform: "translateX(309px)", + } + } + > + +
+
{ + this.handleLeaveReader("left"); + }} + style={ + this.state.isOpenNavPanel + ? {} + : { + transform: "translateX(-309px)", + } + } + > + +
+
{ + this.handleLeaveReader("bottom"); + }} + style={ + this.state.isOpenProgressPanel + ? {} + : { + transform: "translateY(110px)", + } + } + > + +
+
{ + this.handleLeaveReader("top"); + }} + style={ + this.state.isOpenOperationPanel + ? {} + : { + transform: "translateY(-110px)", + } + } + > + +
+
+ ); + } +} + +export default Reader; diff --git a/src/containers/htmlReader/index.tsx b/src/containers/htmlReader/index.tsx new file mode 100644 index 00000000..dd3ba94b --- /dev/null +++ b/src/containers/htmlReader/index.tsx @@ -0,0 +1,30 @@ +import { + handleFetchNotes, + handleFetchBookmarks, + handleFetchChapters, +} from "../../store/actions/reader"; +import { handleFetchPercentage } from "../../store/actions/progressPanel"; +import { + handleMessageBox, + handleFetchBooks, +} from "../../store/actions/manager"; +import { connect } from "react-redux"; +import { stateType } from "../../store"; +import Reader from "./component"; + +const mapStateToProps = (state: stateType) => { + return { + currentEpub: state.book.currentEpub, + currentBook: state.book.currentBook, + isMessage: state.manager.isMessage, + }; +}; +const actionCreator = { + handleFetchNotes, + handleFetchBookmarks, + handleFetchChapters, + handleMessageBox, + handleFetchPercentage, + handleFetchBooks, +}; +export default connect(mapStateToProps, actionCreator)(Reader); diff --git a/src/containers/htmlReader/interface.tsx b/src/containers/htmlReader/interface.tsx new file mode 100644 index 00000000..84f6dca5 --- /dev/null +++ b/src/containers/htmlReader/interface.tsx @@ -0,0 +1,26 @@ +import BookModel from "../../model/Book"; +export interface ReaderProps { + currentEpub: any; + currentBook: BookModel; + isMessage: boolean; + handleFetchNotes: () => void; + handleFetchBooks: () => void; + handleFetchBookmarks: () => void; + handleMessageBox: (isShow: boolean) => void; + handleFetchPercentage: (currentBook: BookModel) => void; + handleFetchChapters: (currentEpub: any) => void; +} + +export interface ReaderState { + isOpenSettingPanel: boolean; + isOpenOperationPanel: boolean; + isOpenProgressPanel: boolean; + isOpenNavPanel: boolean; + isMessage: boolean; + isTouch: boolean; + readerMode: string; + rendition: any; + time: number; + scale: string; + margin: number; +} diff --git a/src/pages/viewer/component.tsx b/src/containers/htmlViewer/component.tsx similarity index 60% rename from src/pages/viewer/component.tsx rename to src/containers/htmlViewer/component.tsx index c4a40e83..08ebed15 100644 --- a/src/pages/viewer/component.tsx +++ b/src/containers/htmlViewer/component.tsx @@ -8,13 +8,15 @@ import _ from "underscore"; import BookUtil from "../../utils/bookUtil"; import MobiParser from "../../utils/mobiParser"; import marked from "marked"; -import "./viewer.css"; -import OtherUtil from "../../utils/otherUtil"; import iconv from "iconv-lite"; import chardet from "chardet"; import rtfToHTML from "@iarna/rtf-to-html"; import { xmlBookTagFilter, xmlBookToObj } from "../../utils/xmlUtil"; +import HtmlParser from "../../utils/htmlParser"; +import OtherUtil from "../../utils/otherUtil"; import RecordLocation from "../../utils/readUtils/recordLocation"; +import { mimetype } from "../../constants/mimetype"; +import styleUtil from "../../utils/readUtils/styleUtil"; declare var window: any; @@ -45,29 +47,40 @@ class Viewer extends React.Component { this.handleRtf(result as ArrayBuffer); } else if (book.format === "DOCX") { this.handleDocx(result as ArrayBuffer); - } else if (book.format === "FB2") { - this.handleFb2(result as ArrayBuffer); + } else if ( + book.format === "HTML" || + book.format === "XHTML" || + book.format === "HTM" || + book.format === "XML" + ) { + this.handleHtml(result as ArrayBuffer, book.format); } this.props.handleReadingState(true); RecentBooks.setRecent(key); }); }); + // document.documentElement.style.height = "auto"; + // document.documentElement.style.overflow = "auto"; - document.documentElement.style.height = "auto"; - document.documentElement.style.overflow = "auto"; - window.addEventListener("wheel", (event) => { + window.frames[0].document.addEventListener("wheel", (event) => { RecordLocation.recordScrollHeight( key, document.body.clientWidth, document.body.clientHeight, - document.scrollingElement!.scrollTop + window.frames[0].document.scrollingElement!.scrollTop ); }); + window.frames[0].document.addEventListener("click", (event) => { + this.props.handleLeaveReader("left"); + this.props.handleLeaveReader("right"); + this.props.handleLeaveReader("top"); + this.props.handleLeaveReader("bottom"); + }); + window.onbeforeunload = () => { this.handleExit(); }; } - // 点击退出按钮的处理程序 handleExit() { this.props.handleReadingState(false); @@ -76,8 +89,9 @@ class Viewer extends React.Component { OtherUtil.setReaderConfig("windowX", window.screenX + ""); OtherUtil.setReaderConfig("windowY", window.screenY + ""); } - handleJump = () => { - document.scrollingElement!.scrollTo( + handleRest = () => { + styleUtil.addHtmlCss(); + window.frames[0].document.scrollingElement!.scrollTo( 0, RecordLocation.getScrollHeight(this.state.key).scroll ); @@ -85,29 +99,26 @@ class Viewer extends React.Component { handleMobi = async (result: ArrayBuffer) => { let mobiFile = new MobiParser(result); let content: any = await mobiFile.render(); - let viewer: HTMLElement | null = document.querySelector(".ebook-viewer"); - if (!viewer?.innerHTML) return; - viewer.innerHTML = content.outerHTML; - this.handleJump(); + window.frames[0].document.body.innerHTML = content.outerHTML; + this.handleRest(); }; handleTxt = (result: ArrayBuffer) => { - let viewer: HTMLElement | null = document.querySelector(".ebook-viewer"); let text = iconv.decode( Buffer.from(result), chardet.detect(Buffer.from(result)) as string ); - if (!viewer?.innerText) return; - viewer.innerText = text; - this.handleJump(); + + window.frames[0].document.body.innerText = text; + this.handleRest(); }; handleMD = (result: ArrayBuffer) => { var blob = new Blob([result], { type: "text/plain" }); var reader = new FileReader(); reader.onload = (evt) => { - let viewer: HTMLElement | null = document.querySelector(".ebook-viewer"); - if (!viewer?.innerHTML) return; - viewer.innerHTML = marked(evt.target?.result as any); - this.handleJump(); + window.frames[0].document.body.innerHTML = marked( + evt.target?.result as any + ); + this.handleRest(); }; reader.readAsText(blob, "UTF-8"); }; @@ -117,18 +128,26 @@ class Viewer extends React.Component { chardet.detect(Buffer.from(result)) as string ); rtfToHTML.fromString(text, (err: any, html: any) => { - let viewer: HTMLElement | null = document.querySelector(".ebook-viewer"); - if (!viewer?.innerHTML) return; - viewer.innerHTML = html; - this.handleJump(); + window.frames[0].document.body.innerHTML = html; + this.handleRest(); }); }; handleDocx = (result: ArrayBuffer) => { window.mammoth.convertToHtml({ arrayBuffer: result }).then((res: any) => { - let viewer: HTMLElement | null = document.querySelector(".ebook-viewer"); + let viewer: any = document.querySelector(".ebook-viewer"); if (!viewer?.innerHTML) return; - viewer.innerHTML = res.value; - this.handleJump(); + + let htmlParser = new HtmlParser( + new DOMParser().parseFromString(res.value, "text/html") + ); + this.props.handleHtmlBook({ + doc: htmlParser.getAnchoredDoc(), + chapters: htmlParser.getContentList(), + subitems: [], + }); + window.frames[0].document.body.innerHTML = htmlParser.getAnchoredDoc().documentElement.outerHTML; + + this.handleRest(); }); }; handleFb2 = (result: ArrayBuffer) => { @@ -138,13 +157,33 @@ class Viewer extends React.Component { ); let bookObj = xmlBookToObj(Buffer.from(result)); bookObj += xmlBookTagFilter(fb2Str); - let viewer: HTMLElement | null = document.querySelector(".ebook-viewer"); - if (!viewer?.innerHTML) return; - viewer.innerHTML = bookObj; - this.handleJump(); + + window.frames[0].document.body.innerHTML = bookObj; + this.handleRest(); + }; + handleHtml = (result: ArrayBuffer, format: string) => { + var blob = new Blob([result], { + type: mimetype[format.toLocaleLowerCase()], + }); + var reader = new FileReader(); + reader.onload = (evt) => { + const html = evt.target?.result as any; + window.frames[0].document.body.innerHTML = html; + this.handleRest(); + }; + reader.readAsText(blob, "UTF-8"); }; render() { - return
Loading
; + return ( + + ); } } export default withRouter(Viewer as any); diff --git a/src/pages/viewer/index.tsx b/src/containers/htmlViewer/index.tsx similarity index 90% rename from src/pages/viewer/index.tsx rename to src/containers/htmlViewer/index.tsx index 5f4aebbc..b5abc94b 100644 --- a/src/pages/viewer/index.tsx +++ b/src/containers/htmlViewer/index.tsx @@ -7,6 +7,7 @@ import { handleReadingEpub, } from "../../store/actions/book"; import { handleMessageBox, handleMessage } from "../../store/actions/manager"; +import { handleHtmlBook } from "../../store/actions/reader"; import Viewer from "./component"; import { stateType } from "../../store"; @@ -24,5 +25,6 @@ const actionCreator = { handleActionDialog, handleMessageBox, handleMessage, + handleHtmlBook, }; export default connect(mapStateToProps, actionCreator)(Viewer as any); diff --git a/src/pages/viewer/interface.tsx b/src/containers/htmlViewer/interface.tsx similarity index 71% rename from src/pages/viewer/interface.tsx rename to src/containers/htmlViewer/interface.tsx index bd74fd10..78240a00 100644 --- a/src/pages/viewer/interface.tsx +++ b/src/containers/htmlViewer/interface.tsx @@ -1,4 +1,5 @@ import BookModel from "../../model/Book"; +import HtmlBookModel from "../../model/HtmlBook"; export interface ViewerProps { book: BookModel; @@ -8,6 +9,8 @@ export interface ViewerProps { handleReadingBook: (book: BookModel) => void; handleMessage: (message: string) => void; handleMessageBox: (isShow: boolean) => void; + handleHtmlBook: (htmlBook: HtmlBookModel) => void; + handleLeaveReader: (position: string) => void; } export interface ViewerState { key: string; diff --git a/src/containers/lists/contentList/component.tsx b/src/containers/lists/contentList/component.tsx index a0438dc9..0a532fc1 100644 --- a/src/containers/lists/contentList/component.tsx +++ b/src/containers/lists/contentList/component.tsx @@ -17,18 +17,48 @@ class ContentList extends React.Component { componentWillMount() { //获取目录 - this.props.currentEpub.loaded.navigation - .then((chapters: any) => { - this.setState({ chapters: chapters.toc }); - }) - .catch(() => { - console.log("Error occurs"); - }); + if (this.props.currentEpub.loaded) { + this.props.currentEpub.loaded.navigation + .then((chapters: any) => { + this.setState({ chapters: chapters.toc }); + }) + .catch(() => { + console.log("Error occurs"); + }); + } } handleJump(event: any) { event.preventDefault(); let href = event.target.getAttribute("href"); - this.props.currentEpub.rendition.display(href); + if (this.props.currentEpub.rendition) { + this.props.currentEpub.rendition.display(href); + } else { + // let a = document.createElement("a"); + // a.href = href; + // a.innerHTML = "testastas"; + // window.frames[0].document.body.appendChild(a); + let id = href.substr(1); + console.log( + id, + window.frames[0].document, + window.frames[0].document.getElementById(id) + ); + var top = window.frames[0].document.getElementById(id)?.offsetTop; //Getting Y of target element + if (!top) return; + window.frames[0].scrollTo(0, top); + // window.frames[0].location.href = href; + // const clickEvent = new MouseEvent("click", { + // view: window, + // bubbles: true, + // cancelable: true, + // }); + // a.dispatchEvent(clickEvent); + } + } + UNSAFE_componentWillReceiveProps(nextProps: ContentListProps) { + if (nextProps.htmlBook !== this.props.htmlBook) { + this.setState({ chapters: nextProps.htmlBook.chapters }); + } } render() { const renderContentList = (items: any, level: number) => { diff --git a/src/containers/lists/contentList/index.tsx b/src/containers/lists/contentList/index.tsx index 5c912507..0c33d3b1 100644 --- a/src/containers/lists/contentList/index.tsx +++ b/src/containers/lists/contentList/index.tsx @@ -6,6 +6,7 @@ const mapStateToProps = (state: stateType) => { return { currentEpub: state.book.currentEpub, chapters: state.reader.chapters, + htmlBook: state.reader.htmlBook, }; }; const actionCreator = {}; diff --git a/src/containers/lists/contentList/interface.tsx b/src/containers/lists/contentList/interface.tsx index cb39b31d..841034e4 100644 --- a/src/containers/lists/contentList/interface.tsx +++ b/src/containers/lists/contentList/interface.tsx @@ -1,6 +1,8 @@ +import HtmlBookModel from "../../../model/HtmlBook"; export interface ContentListProps { currentEpub: any; chapters: any; + htmlBook: HtmlBookModel; } export interface ContentListState { chapters: any; diff --git a/src/containers/viewArea/component.tsx b/src/containers/viewArea/component.tsx deleted file mode 100644 index 39c0ad63..00000000 --- a/src/containers/viewArea/component.tsx +++ /dev/null @@ -1,234 +0,0 @@ -//阅读器图书内容区域 -import React from "react"; -import "./viewArea.css"; -import PopupMenu from "../../components/popups/popupMenu"; -import { ViewAreaProps, ViewAreaStates } from "./interface"; -import RecordLocation from "../../utils/readUtils/recordLocation"; -import OtherUtil from "../../utils/otherUtil"; -import BookmarkModel from "../../model/Bookmark"; -import StyleUtil from "../../utils/readUtils/styleUtil"; -import ImageViewer from "../../components/imageViewer"; -import Lottie from "react-lottie"; -import animationSiri from "../../assets/lotties/siri.json"; - -const siriOptions = { - loop: true, - autoplay: true, - animationData: animationSiri, - rendererSettings: { - preserveAspectRatio: "xMidYMid slice", - }, -}; -declare var window: any; - -class ViewArea extends React.Component { - isFirst: boolean; - constructor(props: ViewAreaProps) { - super(props); - this.state = { - cfiRange: null, - contents: null, - rect: null, - loading: true, - }; - this.isFirst = true; - } - - componentDidMount() { - let epub = this.props.currentEpub; - window.rangy.init(); // 初始化 - this.props.rendition.on("locationChanged", () => { - this.props.handleReadingEpub(epub); - this.props.handleOpenMenu(false); - const currentLocation = this.props.rendition.currentLocation(); - if (!currentLocation.start) { - return; - } - const cfi = currentLocation.start.cfi; - this.props.handleShowBookmark( - this.props.bookmarks && - this.props.bookmarks.filter( - (item: BookmarkModel) => item.cfi === cfi - )[0] - ? true - : false - ); - - if (!this.isFirst && this.props.locations) { - let percentage = this.props.locations.percentageFromCfi(cfi); - RecordLocation.recordCfi(this.props.currentBook.key, cfi, percentage); - this.props.handlePercentage(percentage); - } else if (!this.isFirst) { - //如果过暂时没有解析出locations,就直接记录cfi - RecordLocation.recordCfi( - this.props.currentBook.key, - cfi, - RecordLocation.getCfi(this.props.currentBook.key).percentage - ); - } - this.isFirst = false; - }); - this.props.rendition.on("rendered", () => { - this.setState({ loading: false }); - let iframe = document.getElementsByTagName("iframe")[0]; - if (!iframe) return; - let doc = iframe.contentDocument; - if (!doc) { - return; - } - StyleUtil.addDefaultCss(); - this.props.rendition.themes.default({ - "a, article, cite, code, div, li, p, pre, span, table": { - "font-size": `${ - OtherUtil.getReaderConfig("fontSize") || 17 - }px !important`, - "line-height": `${ - OtherUtil.getReaderConfig("lineHeight") || "1.25" - } !important`, - "font-family": `${ - OtherUtil.getReaderConfig("fontFamily") || "Helvetica" - } !important`, - color: `${ - OtherUtil.getReaderConfig("textColor") - ? OtherUtil.getReaderConfig("textColor") - : OtherUtil.getReaderConfig("backgroundColor") === - "rgba(44,47,49,1)" || - OtherUtil.getReaderConfig("isDisplayDark") === "yes" - ? "white" - : "" - } !important`, - "letter-spacing": `${ - OtherUtil.getReaderConfig("letterSpacing") - ? `${OtherUtil.getReaderConfig("letterSpacing")}px` - : "" - } !important`, - "text-align": `${ - OtherUtil.getReaderConfig("textAlign") - ? `${OtherUtil.getReaderConfig("textAlign")}` - : "" - } !important`, - "font-weight": `${ - OtherUtil.getReaderConfig("isBold") === "yes" - ? "bold !important" - : "" - }`, - "font-style": `${ - OtherUtil.getReaderConfig("isItalic") === "yes" - ? "italic !important" - : "" - }`, - "text-shadow": `${ - OtherUtil.getReaderConfig("isShadow") === "yes" - ? "2px 2px 2px #cccccc !important" - : "" - }`, - "text-decoration": `${ - OtherUtil.getReaderConfig("isUnderline") === "yes" - ? "underline !important" - : "" - }`, - "p, div, table": { - "margin-bottom": `${ - OtherUtil.getReaderConfig("paraSpacing") || 0 - }px !important`, - }, - }, - }); - }); - this.props.rendition.on("selected", (cfiRange: any, contents: any) => { - var range = contents.range(cfiRange); - var rect = range.getBoundingClientRect(); - this.setState({ cfiRange, contents, rect }); - }); - this.props.rendition.themes.default({ - "a, article, cite, code, div, li, p, pre, span, table": { - "font-size": `${ - OtherUtil.getReaderConfig("fontSize") || 17 - }px !important`, - "line-height": `${ - OtherUtil.getReaderConfig("lineHeight") || "1.25" - } !important`, - "letter-spacing": `${ - OtherUtil.getReaderConfig("letterSpacing") || "0" - }px !important`, - "font-family": `${ - OtherUtil.getReaderConfig("fontFamily") || "Built-in font" - } !important`, - color: `${ - OtherUtil.getReaderConfig("textColor") - ? OtherUtil.getReaderConfig("textColor") - : OtherUtil.getReaderConfig("backgroundColor") === - "rgba(44,47,49,1)" || - OtherUtil.getReaderConfig("isDisplayDark") === "yes" - ? "white" - : "" - } !important`, - "text-align": `${ - OtherUtil.getReaderConfig("textAlign") - ? `${OtherUtil.getReaderConfig("textAlign")}` - : "" - } !important`, - "font-weight": `${ - OtherUtil.getReaderConfig("isBold") === "yes" ? "bold !important" : "" - }`, - "font-style": `${ - OtherUtil.getReaderConfig("isItalic") === "yes" - ? "italic !important" - : "" - }`, - "text-shadow": `${ - OtherUtil.getReaderConfig("isShadow") === "yes" - ? "2px 2px 2px #cccccc !important" - : "" - }`, - "text-decoration": `${ - OtherUtil.getReaderConfig("isUnderline") === "yes" - ? "underline !important" - : "" - }`, - }, - "p, div, table": { - "margin-bottom": `${ - OtherUtil.getReaderConfig("paraSpacing") || 0 - }px !important`, - }, - }); - this.props.rendition.display( - RecordLocation.getCfi(this.props.currentBook.key) === null - ? null - : RecordLocation.getCfi(this.props.currentBook.key).cfi - ); - } - - render() { - const popupMenuProps = { - rendition: this.props.rendition, - cfiRange: this.state.cfiRange, - contents: this.state.contents, - rect: this.state.rect, - }; - return ( -
- - - {this.state.loading ? ( -
- -
- ) : null} - <> - {this.props.isShowBookmark ?
: null} - -
- ); - } -} - -export default ViewArea; diff --git a/src/containers/viewArea/index.tsx b/src/containers/viewArea/index.tsx deleted file mode 100644 index ae073df4..00000000 --- a/src/containers/viewArea/index.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { connect } from "react-redux"; -import { stateType } from "../../store"; -import ViewArea from "./component"; -import { handlePercentage } from "../../store/actions/progressPanel"; -import { - handleOpenMenu, - handleShowBookmark, -} from "../../store/actions/viewArea"; -import { handleReadingEpub } from "../../store/actions/book"; - -const mapStateToProps = (state: stateType) => { - return { - chapters: state.reader.chapters, - currentEpub: state.book.currentEpub, - currentBook: state.book.currentBook, - locations: state.progressPanel.locations, - bookmarks: state.reader.bookmarks, - isShowBookmark: state.viewArea.isShowBookmark, - }; -}; -const actionCreator = { - handlePercentage, - handleOpenMenu, - handleShowBookmark, - handleReadingEpub, -}; - -export default connect(mapStateToProps, actionCreator)(ViewArea); diff --git a/src/containers/viewArea/interface.tsx b/src/containers/viewArea/interface.tsx deleted file mode 100644 index 02506e91..00000000 --- a/src/containers/viewArea/interface.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import BookModel from "../../model/Book"; -import BookmarkModel from "../../model/Bookmark"; - -export interface ViewAreaProps { - rendition: any; - currentBook: BookModel; - currentEpub: any; - bookmarks: BookmarkModel[]; - locations: any; - isShowBookmark: boolean; - chapters: any[]; - isShow: boolean; - handleLeaveReader: (position: string) => void; - handleEnterReader: (position: string) => void; - handlePercentage: (percentage: number) => void; - handleOpenMenu: (isOpenMenu: boolean) => void; - handleShowBookmark: (isShowBookmark: boolean) => void; - handleReadingEpub: (epub: object) => void; -} -export interface ViewAreaStates { - loading: boolean; - cfiRange: any; - contents: any; - // rendition: any; - rect: any; -} diff --git a/src/model/HtmlBook.ts b/src/model/HtmlBook.ts new file mode 100644 index 00000000..b427b2dd --- /dev/null +++ b/src/model/HtmlBook.ts @@ -0,0 +1,17 @@ +class HtmlBook { + + doc:HTMLElement; + chapters:{label:string,id:string,href:string}[]; + subitems:any; + constructor( + doc:HTMLElement, + chapters:{label:string,id:string,href:string}[], + subitems:any, + ) { + this.doc=doc, + this.chapters=chapters, + this.subitems=subitems + } +} + +export default HtmlBook; diff --git a/src/pages/comicReader/component.tsx b/src/pages/comicReader/component.tsx index 5f8e9adb..6fe1ff00 100644 --- a/src/pages/comicReader/component.tsx +++ b/src/pages/comicReader/component.tsx @@ -43,14 +43,14 @@ class Viewer extends React.Component { RecentBooks.setRecent(key); }); }); - document.documentElement.style.height = "auto"; - document.documentElement.style.overflow = "auto"; - window.addEventListener("wheel", (event) => { + // document.documentElement.style.height = "auto"; + // document.documentElement.style.overflow = "auto"; + window.frames[0].document.addEventListener("wheel", (event) => { RecordLocation.recordScrollHeight( key, document.body.clientWidth, document.body.clientHeight, - document.scrollingElement!.scrollTop + window.frames[0].document.scrollingElement!.scrollTop ); }); window.onbeforeunload = () => { @@ -108,12 +108,16 @@ class Viewer extends React.Component { imageDom.src = "data:" + mimetype[extension.toLowerCase()] + ";base64," + url; imageDom.setAttribute("style", "width: 100%"); - let viewer: HTMLElement | null = document.querySelector(".ebook-viewer"); - if (!viewer?.innerHTML) return; - viewer.appendChild(imageDom); - let loading = document.querySelector("p"); + window.frames[0].document.body.appendChild(imageDom); + let loading = window.frames[0].document.querySelector("p"); if (!loading) return; - viewer.removeChild(loading); + window.frames[0].document.body.removeChild(loading); + }; + handleJump = () => { + window.frames[0].document.scrollingElement!.scrollTo( + 0, + RecordLocation.getScrollHeight(this.state.key).scroll + ); }; handleCbz = (result: ArrayBuffer) => { let zip = new JSZip(); @@ -123,11 +127,7 @@ class Viewer extends React.Component { const extension = filename.split(".").reverse()[0]; this.addImage(content, extension); } - - document.scrollingElement!.scrollTo( - 0, - RecordLocation.getScrollHeight(this.state.key).scroll - ); + this.handleJump(); }); }; handleCbr = (result: ArrayBuffer) => { @@ -169,9 +169,14 @@ class Viewer extends React.Component { }; render() { return ( -
+ ); } } diff --git a/src/pages/epubReader/component.tsx b/src/pages/epubPage/component.tsx similarity index 96% rename from src/pages/epubReader/component.tsx rename to src/pages/epubPage/component.tsx index 9419f640..68f30dbb 100644 --- a/src/pages/epubReader/component.tsx +++ b/src/pages/epubPage/component.tsx @@ -2,7 +2,7 @@ import React from "react"; import RecentBooks from "../../utils/readUtils/recordRecent"; import { EpubReaderProps, EpubReaderState } from "./interface"; import localforage from "localforage"; -import Reader from "../../containers/epubViewer"; +import Reader from "../../containers/epubReader"; import { withRouter } from "react-router-dom"; import _ from "underscore"; import BookUtil from "../../utils/bookUtil"; diff --git a/src/pages/epubReader/index.tsx b/src/pages/epubPage/index.tsx similarity index 100% rename from src/pages/epubReader/index.tsx rename to src/pages/epubPage/index.tsx diff --git a/src/pages/epubReader/interface.tsx b/src/pages/epubPage/interface.tsx similarity index 100% rename from src/pages/epubReader/interface.tsx rename to src/pages/epubPage/interface.tsx diff --git a/src/pages/htmlPage/component.tsx b/src/pages/htmlPage/component.tsx new file mode 100644 index 00000000..5230160d --- /dev/null +++ b/src/pages/htmlPage/component.tsx @@ -0,0 +1,48 @@ +//卡片模式下的图书显示 +import React from "react"; +import { ViewerProps, ViewerState } from "./interface"; +import { withRouter } from "react-router-dom"; +import OtherUtil from "../../utils/otherUtil"; +import RecordLocation from "../../utils/readUtils/recordLocation"; +import Viewer from "../../containers/htmlViewer"; + +class HtmlReader extends React.Component { + constructor(props: ViewerProps) { + super(props); + this.state = {}; + } + + componentDidMount() { + let url = document.location.href.split("/"); + let key = url[url.length - 1].split("?")[0]; + + document.documentElement.style.height = "auto"; + document.documentElement.style.overflow = "auto"; + window.addEventListener("wheel", (event) => { + RecordLocation.recordScrollHeight( + key, + document.body.clientWidth, + document.body.clientHeight, + document.scrollingElement!.scrollTop + ); + }); + + window.onbeforeunload = () => { + this.handleExit(); + }; + } + // 点击退出按钮的处理程序 + handleExit() { + this.props.handleReadingState(false); + + OtherUtil.setReaderConfig("windowWidth", document.body.clientWidth + ""); + OtherUtil.setReaderConfig("windowHeight", document.body.clientHeight + ""); + OtherUtil.setReaderConfig("windowX", window.screenX + ""); + OtherUtil.setReaderConfig("windowY", window.screenY + ""); + } + + render() { + return ; + } +} +export default withRouter(HtmlReader as any); diff --git a/src/pages/htmlViewer/index.tsx b/src/pages/htmlPage/index.tsx similarity index 100% rename from src/pages/htmlViewer/index.tsx rename to src/pages/htmlPage/index.tsx diff --git a/src/pages/htmlViewer/interface.tsx b/src/pages/htmlPage/interface.tsx similarity index 100% rename from src/pages/htmlViewer/interface.tsx rename to src/pages/htmlPage/interface.tsx diff --git a/src/pages/htmlViewer/viewer.css b/src/pages/htmlPage/viewer.css similarity index 100% rename from src/pages/htmlViewer/viewer.css rename to src/pages/htmlPage/viewer.css diff --git a/src/pages/htmlViewer/component.tsx b/src/pages/htmlViewer/component.tsx deleted file mode 100644 index 78967e4d..00000000 --- a/src/pages/htmlViewer/component.tsx +++ /dev/null @@ -1,79 +0,0 @@ -//卡片模式下的图书显示 -import React from "react"; -import RecentBooks from "../../utils/readUtils/recordRecent"; -import { ViewerProps, ViewerState } from "./interface"; -import localforage from "localforage"; -import { withRouter } from "react-router-dom"; -import _ from "underscore"; -import BookUtil from "../../utils/bookUtil"; -import "./viewer.css"; -import OtherUtil from "../../utils/otherUtil"; -import { mimetype } from "../../constants/mimetype"; -declare var window: any; - -class Viewer extends React.Component { - epub: any; - constructor(props: ViewerProps) { - super(props); - this.state = {}; - } - - componentDidMount() { - let url = document.location.href.split("/"); - let key = url[url.length - 1].split("?")[0]; - - localforage.getItem("books").then((result: any) => { - let book = result[_.findIndex(result, { key })]; - BookUtil.fetchBook(key, true).then((result) => { - this.props.handleReadingBook(book); - - this.handleHtml(result as ArrayBuffer, book.format); - - this.props.handleReadingState(true); - RecentBooks.setRecent(key); - }); - }); - - // document - // .querySelectorAll('style,link[rel="stylesheet"]') - // .forEach((item) => item.remove()); - - window.onbeforeunload = () => { - this.handleExit(); - }; - } - // 点击退出按钮的处理程序 - handleExit() { - this.props.handleReadingState(false); - - OtherUtil.setReaderConfig("windowWidth", document.body.clientWidth + ""); - OtherUtil.setReaderConfig("windowHeight", document.body.clientHeight + ""); - OtherUtil.setReaderConfig("windowX", window.screenX + ""); - OtherUtil.setReaderConfig("windowY", window.screenY + ""); - } - handleHtml = (result: ArrayBuffer, format: string) => { - var blob = new Blob([result], { - type: mimetype[format.toLocaleLowerCase()], - }); - var reader = new FileReader(); - reader.onload = function (evt) { - let iframe: any = document.querySelector(".html-viewer"); - if (!iframe) return; - const html = evt.target?.result as any; - iframe.sandbox = ""; - iframe.srcdoc = html; - }; - reader.readAsText(blob, "UTF-8"); - }; - render() { - return ( -