mirror of
https://github.com/koodo-reader/koodo-reader.git
synced 2026-06-22 23:02:30 -04:00
fix bug
Former-commit-id: 924785aa776d93c4e0235e57ba1de960ee6d2633
This commit is contained in:
344
src/containers/epubReader/component.tsx
Normal file
344
src/containers/epubReader/component.tsx
Normal file
@@ -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<ReaderProps, ReaderState> {
|
||||
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 (
|
||||
<div
|
||||
className="viewer"
|
||||
style={{
|
||||
filter: `brightness(${
|
||||
OtherUtil.getReaderConfig("brightness") || 1
|
||||
}) invert(${
|
||||
OtherUtil.getReaderConfig("isInvert") === "yes" ? 1 : 0
|
||||
})`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="previous-chapter-single-container"
|
||||
onClick={() => {
|
||||
this.prevPage();
|
||||
}}
|
||||
>
|
||||
<span className="icon-dropdown previous-chapter-single"></span>
|
||||
</div>
|
||||
<div
|
||||
className="next-chapter-single-container"
|
||||
onClick={() => {
|
||||
this.nextPage();
|
||||
}}
|
||||
>
|
||||
<span className="icon-dropdown next-chapter-single"></span>
|
||||
</div>
|
||||
<div
|
||||
className="reader-setting-icon-container"
|
||||
onClick={() => {
|
||||
this.handleEnterReader("left");
|
||||
this.handleEnterReader("right");
|
||||
this.handleEnterReader("bottom");
|
||||
this.handleEnterReader("top");
|
||||
}}
|
||||
>
|
||||
<span className="icon-grid reader-setting-icon"></span>
|
||||
</div>
|
||||
{this.state.isMessage ? <MessageBox /> : null}
|
||||
<div
|
||||
className="left-panel"
|
||||
onMouseEnter={() => {
|
||||
if (this.state.isTouch || this.state.isOpenNavPanel) {
|
||||
return;
|
||||
}
|
||||
this.handleEnterReader("left");
|
||||
}}
|
||||
onClick={() => {
|
||||
this.handleEnterReader("left");
|
||||
}}
|
||||
></div>
|
||||
<div
|
||||
className="right-panel"
|
||||
onMouseEnter={() => {
|
||||
if (this.state.isTouch || this.state.isOpenSettingPanel) {
|
||||
return;
|
||||
}
|
||||
this.handleEnterReader("right");
|
||||
}}
|
||||
onClick={() => {
|
||||
this.handleEnterReader("right");
|
||||
}}
|
||||
></div>
|
||||
<div
|
||||
className="top-panel"
|
||||
onMouseEnter={() => {
|
||||
if (this.state.isTouch || this.state.isOpenOperationPanel) {
|
||||
return;
|
||||
}
|
||||
this.handleEnterReader("top");
|
||||
}}
|
||||
onClick={() => {
|
||||
this.handleEnterReader("top");
|
||||
}}
|
||||
></div>
|
||||
<div
|
||||
className="bottom-panel"
|
||||
onMouseEnter={() => {
|
||||
if (this.state.isTouch || this.state.isOpenProgressPanel) {
|
||||
return;
|
||||
}
|
||||
this.handleEnterReader("bottom");
|
||||
}}
|
||||
onClick={() => {
|
||||
this.handleEnterReader("bottom");
|
||||
}}
|
||||
></div>
|
||||
|
||||
{this.state.rendition && this.props.currentEpub.rendition && (
|
||||
<ViewArea {...renditionProps} />
|
||||
)}
|
||||
<div
|
||||
className="setting-panel-container"
|
||||
onMouseLeave={(event) => {
|
||||
this.handleLeaveReader("right");
|
||||
}}
|
||||
style={
|
||||
this.state.isOpenSettingPanel
|
||||
? {}
|
||||
: {
|
||||
transform: "translateX(309px)",
|
||||
}
|
||||
}
|
||||
>
|
||||
<SettingPanel />
|
||||
</div>
|
||||
<div
|
||||
className="navigation-panel-container"
|
||||
onMouseLeave={(event) => {
|
||||
this.handleLeaveReader("left");
|
||||
}}
|
||||
style={
|
||||
this.state.isOpenNavPanel
|
||||
? {}
|
||||
: {
|
||||
transform: "translateX(-309px)",
|
||||
}
|
||||
}
|
||||
>
|
||||
<NavigationPanel {...{ time: this.state.time }} />
|
||||
</div>
|
||||
<div
|
||||
className="progress-panel-container"
|
||||
onMouseLeave={(event) => {
|
||||
this.handleLeaveReader("bottom");
|
||||
}}
|
||||
style={
|
||||
this.state.isOpenProgressPanel
|
||||
? {}
|
||||
: {
|
||||
transform: "translateY(110px)",
|
||||
}
|
||||
}
|
||||
>
|
||||
<ProgressPanel {...{ time: this.state.time }} />
|
||||
</div>
|
||||
<div
|
||||
className="operation-panel-container"
|
||||
onMouseLeave={(event) => {
|
||||
this.handleLeaveReader("top");
|
||||
}}
|
||||
style={
|
||||
this.state.isOpenOperationPanel
|
||||
? {}
|
||||
: {
|
||||
transform: "translateY(-110px)",
|
||||
}
|
||||
}
|
||||
>
|
||||
<OperationPanel {...{ time: this.state.time }} />
|
||||
</div>
|
||||
<div
|
||||
className="view-area-page"
|
||||
id="page-area"
|
||||
style={
|
||||
document.body.clientWidth < 570
|
||||
? { left: 0, right: 0 }
|
||||
: this.state.readerMode === "continuous"
|
||||
? {
|
||||
left: `calc(50vw - ${270 * parseFloat(this.state.scale)}px)`,
|
||||
right: `calc(50vw - ${270 * parseFloat(this.state.scale)}px)`,
|
||||
top: "75px",
|
||||
bottom: "75px",
|
||||
}
|
||||
: this.state.readerMode === "single"
|
||||
? {
|
||||
left: `calc(50vw - ${270 * parseFloat(this.state.scale)}px)`,
|
||||
right: `calc(50vw - ${270 * parseFloat(this.state.scale)}px)`,
|
||||
}
|
||||
: this.state.readerMode === "double"
|
||||
? {
|
||||
left: this.state.margin - 40 + "px",
|
||||
right: this.state.margin - 40 + "px",
|
||||
}
|
||||
: {}
|
||||
}
|
||||
></div>
|
||||
|
||||
<Background {...{ time: this.state.time }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Reader;
|
||||
@@ -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;
|
||||
31
src/containers/epubReader/index.tsx
Normal file
31
src/containers/epubReader/index.tsx
Normal file
@@ -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);
|
||||
26
src/containers/epubReader/interface.tsx
Normal file
26
src/containers/epubReader/interface.tsx
Normal file
@@ -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;
|
||||
}
|
||||
@@ -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<ReaderProps, ReaderState> {
|
||||
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<ViewAreaProps, ViewAreaStates> {
|
||||
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 (
|
||||
<div
|
||||
className="viewer"
|
||||
style={{
|
||||
filter: `brightness(${
|
||||
OtherUtil.getReaderConfig("brightness") || 1
|
||||
}) invert(${
|
||||
OtherUtil.getReaderConfig("isInvert") === "yes" ? 1 : 0
|
||||
})`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="previous-chapter-single-container"
|
||||
onClick={() => {
|
||||
this.prevPage();
|
||||
<div className="view-area">
|
||||
<ImageViewer
|
||||
{...{
|
||||
isShow: this.props.isShow,
|
||||
rendition: this.props.rendition,
|
||||
handleEnterReader: this.props.handleEnterReader,
|
||||
handleLeaveReader: this.props.handleLeaveReader,
|
||||
}}
|
||||
>
|
||||
<span className="icon-dropdown previous-chapter-single"></span>
|
||||
</div>
|
||||
<div
|
||||
className="next-chapter-single-container"
|
||||
onClick={() => {
|
||||
this.nextPage();
|
||||
}}
|
||||
>
|
||||
<span className="icon-dropdown next-chapter-single"></span>
|
||||
</div>
|
||||
<div
|
||||
className="reader-setting-icon-container"
|
||||
onClick={() => {
|
||||
this.handleEnterReader("left");
|
||||
this.handleEnterReader("right");
|
||||
this.handleEnterReader("bottom");
|
||||
this.handleEnterReader("top");
|
||||
}}
|
||||
>
|
||||
<span className="icon-grid reader-setting-icon"></span>
|
||||
</div>
|
||||
{this.state.isMessage ? <MessageBox /> : null}
|
||||
<div
|
||||
className="left-panel"
|
||||
onMouseEnter={() => {
|
||||
if (this.state.isTouch || this.state.isOpenNavPanel) {
|
||||
return;
|
||||
}
|
||||
this.handleEnterReader("left");
|
||||
}}
|
||||
onClick={() => {
|
||||
this.handleEnterReader("left");
|
||||
}}
|
||||
></div>
|
||||
<div
|
||||
className="right-panel"
|
||||
onMouseEnter={() => {
|
||||
if (this.state.isTouch || this.state.isOpenSettingPanel) {
|
||||
return;
|
||||
}
|
||||
this.handleEnterReader("right");
|
||||
}}
|
||||
onClick={() => {
|
||||
this.handleEnterReader("right");
|
||||
}}
|
||||
></div>
|
||||
<div
|
||||
className="top-panel"
|
||||
onMouseEnter={() => {
|
||||
if (this.state.isTouch || this.state.isOpenOperationPanel) {
|
||||
return;
|
||||
}
|
||||
this.handleEnterReader("top");
|
||||
}}
|
||||
onClick={() => {
|
||||
this.handleEnterReader("top");
|
||||
}}
|
||||
></div>
|
||||
<div
|
||||
className="bottom-panel"
|
||||
onMouseEnter={() => {
|
||||
if (this.state.isTouch || this.state.isOpenProgressPanel) {
|
||||
return;
|
||||
}
|
||||
this.handleEnterReader("bottom");
|
||||
}}
|
||||
onClick={() => {
|
||||
this.handleEnterReader("bottom");
|
||||
}}
|
||||
></div>
|
||||
|
||||
{this.state.rendition && this.props.currentEpub.rendition && (
|
||||
<ViewArea {...renditionProps} />
|
||||
)}
|
||||
<div
|
||||
className="setting-panel-container"
|
||||
onMouseLeave={(event) => {
|
||||
this.handleLeaveReader("right");
|
||||
}}
|
||||
style={
|
||||
this.state.isOpenSettingPanel
|
||||
? {}
|
||||
: {
|
||||
transform: "translateX(309px)",
|
||||
}
|
||||
}
|
||||
>
|
||||
<SettingPanel />
|
||||
</div>
|
||||
<div
|
||||
className="navigation-panel-container"
|
||||
onMouseLeave={(event) => {
|
||||
this.handleLeaveReader("left");
|
||||
}}
|
||||
style={
|
||||
this.state.isOpenNavPanel
|
||||
? {}
|
||||
: {
|
||||
transform: "translateX(-309px)",
|
||||
}
|
||||
}
|
||||
>
|
||||
<NavigationPanel {...{ time: this.state.time }} />
|
||||
</div>
|
||||
<div
|
||||
className="progress-panel-container"
|
||||
onMouseLeave={(event) => {
|
||||
this.handleLeaveReader("bottom");
|
||||
}}
|
||||
style={
|
||||
this.state.isOpenProgressPanel
|
||||
? {}
|
||||
: {
|
||||
transform: "translateY(110px)",
|
||||
}
|
||||
}
|
||||
>
|
||||
<ProgressPanel {...{ time: this.state.time }} />
|
||||
</div>
|
||||
<div
|
||||
className="operation-panel-container"
|
||||
onMouseLeave={(event) => {
|
||||
this.handleLeaveReader("top");
|
||||
}}
|
||||
style={
|
||||
this.state.isOpenOperationPanel
|
||||
? {}
|
||||
: {
|
||||
transform: "translateY(-110px)",
|
||||
}
|
||||
}
|
||||
>
|
||||
<OperationPanel {...{ time: this.state.time }} />
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="view-area-page"
|
||||
id="page-area"
|
||||
style={
|
||||
document.body.clientWidth < 570
|
||||
? { left: 0, right: 0 }
|
||||
: this.state.readerMode === "continuous"
|
||||
? {
|
||||
left: `calc(50vw - ${270 * parseFloat(this.state.scale)}px)`,
|
||||
right: `calc(50vw - ${270 * parseFloat(this.state.scale)}px)`,
|
||||
top: "75px",
|
||||
bottom: "75px",
|
||||
}
|
||||
: this.state.readerMode === "single"
|
||||
? {
|
||||
left: `calc(50vw - ${270 * parseFloat(this.state.scale)}px)`,
|
||||
right: `calc(50vw - ${270 * parseFloat(this.state.scale)}px)`,
|
||||
}
|
||||
: this.state.readerMode === "double"
|
||||
? {
|
||||
left: this.state.margin - 40 + "px",
|
||||
right: this.state.margin - 40 + "px",
|
||||
}
|
||||
: {}
|
||||
}
|
||||
></div>
|
||||
|
||||
<Background {...{ time: this.state.time }} />
|
||||
/>
|
||||
<PopupMenu {...popupMenuProps} />
|
||||
{this.state.loading ? (
|
||||
<div className="spinner">
|
||||
<Lottie options={siriOptions} height={100} width={300} />
|
||||
</div>
|
||||
) : null}
|
||||
<>
|
||||
{this.props.isShowBookmark ? <div className="bookmark"></div> : null}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Reader;
|
||||
export default ViewArea;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
283
src/containers/htmlReader/component.tsx
Normal file
283
src/containers/htmlReader/component.tsx
Normal file
@@ -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<ReaderProps, ReaderState> {
|
||||
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 (
|
||||
<div
|
||||
className="viewer"
|
||||
style={{
|
||||
filter: `brightness(${
|
||||
OtherUtil.getReaderConfig("brightness") || 1
|
||||
}) invert(${
|
||||
OtherUtil.getReaderConfig("isInvert") === "yes" ? 1 : 0
|
||||
})`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="previous-chapter-single-container"
|
||||
onClick={() => {
|
||||
this.prevPage();
|
||||
}}
|
||||
>
|
||||
<span className="icon-dropdown previous-chapter-single"></span>
|
||||
</div>
|
||||
<div
|
||||
className="next-chapter-single-container"
|
||||
onClick={() => {
|
||||
this.nextPage();
|
||||
}}
|
||||
>
|
||||
<span className="icon-dropdown next-chapter-single"></span>
|
||||
</div>
|
||||
<div
|
||||
className="reader-setting-icon-container"
|
||||
onClick={() => {
|
||||
this.handleEnterReader("left");
|
||||
this.handleEnterReader("right");
|
||||
this.handleEnterReader("bottom");
|
||||
this.handleEnterReader("top");
|
||||
}}
|
||||
>
|
||||
<span className="icon-grid reader-setting-icon"></span>
|
||||
</div>
|
||||
{this.state.isMessage ? <MessageBox /> : null}
|
||||
<div
|
||||
className="left-panel"
|
||||
onMouseEnter={() => {
|
||||
if (this.state.isTouch || this.state.isOpenNavPanel) {
|
||||
return;
|
||||
}
|
||||
this.handleEnterReader("left");
|
||||
}}
|
||||
onClick={() => {
|
||||
this.handleEnterReader("left");
|
||||
}}
|
||||
></div>
|
||||
<div
|
||||
className="right-panel"
|
||||
onMouseEnter={() => {
|
||||
if (this.state.isTouch || this.state.isOpenSettingPanel) {
|
||||
return;
|
||||
}
|
||||
this.handleEnterReader("right");
|
||||
}}
|
||||
onClick={() => {
|
||||
this.handleEnterReader("right");
|
||||
}}
|
||||
></div>
|
||||
<div
|
||||
className="top-panel"
|
||||
onMouseEnter={() => {
|
||||
if (this.state.isTouch || this.state.isOpenOperationPanel) {
|
||||
return;
|
||||
}
|
||||
this.handleEnterReader("top");
|
||||
}}
|
||||
onClick={() => {
|
||||
this.handleEnterReader("top");
|
||||
}}
|
||||
></div>
|
||||
<div
|
||||
className="bottom-panel"
|
||||
onMouseEnter={() => {
|
||||
if (this.state.isTouch || this.state.isOpenProgressPanel) {
|
||||
return;
|
||||
}
|
||||
this.handleEnterReader("bottom");
|
||||
}}
|
||||
onClick={() => {
|
||||
this.handleEnterReader("bottom");
|
||||
}}
|
||||
></div>
|
||||
<Viewer {...renditionProps} />
|
||||
<div
|
||||
className="setting-panel-container"
|
||||
onMouseLeave={(event) => {
|
||||
this.handleLeaveReader("right");
|
||||
}}
|
||||
style={
|
||||
this.state.isOpenSettingPanel
|
||||
? {}
|
||||
: {
|
||||
transform: "translateX(309px)",
|
||||
}
|
||||
}
|
||||
>
|
||||
<SettingPanel />
|
||||
</div>
|
||||
<div
|
||||
className="navigation-panel-container"
|
||||
onMouseLeave={(event) => {
|
||||
this.handleLeaveReader("left");
|
||||
}}
|
||||
style={
|
||||
this.state.isOpenNavPanel
|
||||
? {}
|
||||
: {
|
||||
transform: "translateX(-309px)",
|
||||
}
|
||||
}
|
||||
>
|
||||
<NavigationPanel {...{ time: this.state.time }} />
|
||||
</div>
|
||||
<div
|
||||
className="progress-panel-container"
|
||||
onMouseLeave={(event) => {
|
||||
this.handleLeaveReader("bottom");
|
||||
}}
|
||||
style={
|
||||
this.state.isOpenProgressPanel
|
||||
? {}
|
||||
: {
|
||||
transform: "translateY(110px)",
|
||||
}
|
||||
}
|
||||
>
|
||||
<ProgressPanel {...{ time: this.state.time }} />
|
||||
</div>
|
||||
<div
|
||||
className="operation-panel-container"
|
||||
onMouseLeave={(event) => {
|
||||
this.handleLeaveReader("top");
|
||||
}}
|
||||
style={
|
||||
this.state.isOpenOperationPanel
|
||||
? {}
|
||||
: {
|
||||
transform: "translateY(-110px)",
|
||||
}
|
||||
}
|
||||
>
|
||||
<OperationPanel {...{ time: this.state.time }} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Reader;
|
||||
30
src/containers/htmlReader/index.tsx
Normal file
30
src/containers/htmlReader/index.tsx
Normal file
@@ -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);
|
||||
26
src/containers/htmlReader/interface.tsx
Normal file
26
src/containers/htmlReader/interface.tsx
Normal file
@@ -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;
|
||||
}
|
||||
@@ -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<ViewerProps, ViewerState> {
|
||||
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<ViewerProps, ViewerState> {
|
||||
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<ViewerProps, ViewerState> {
|
||||
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<ViewerProps, ViewerState> {
|
||||
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<ViewerProps, ViewerState> {
|
||||
);
|
||||
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 <div className="ebook-viewer">Loading</div>;
|
||||
return (
|
||||
<iframe
|
||||
className="ebook-viewer"
|
||||
title="html-viewer"
|
||||
width="100%"
|
||||
height="100%"
|
||||
>
|
||||
Loading
|
||||
</iframe>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default withRouter(Viewer as any);
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -17,18 +17,48 @@ class ContentList extends React.Component<ContentListProps, ContentListState> {
|
||||
|
||||
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) => {
|
||||
|
||||
@@ -6,6 +6,7 @@ const mapStateToProps = (state: stateType) => {
|
||||
return {
|
||||
currentEpub: state.book.currentEpub,
|
||||
chapters: state.reader.chapters,
|
||||
htmlBook: state.reader.htmlBook,
|
||||
};
|
||||
};
|
||||
const actionCreator = {};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import HtmlBookModel from "../../../model/HtmlBook";
|
||||
export interface ContentListProps {
|
||||
currentEpub: any;
|
||||
chapters: any;
|
||||
htmlBook: HtmlBookModel;
|
||||
}
|
||||
export interface ContentListState {
|
||||
chapters: any;
|
||||
|
||||
@@ -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<ViewAreaProps, ViewAreaStates> {
|
||||
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 (
|
||||
<div className="view-area">
|
||||
<ImageViewer
|
||||
{...{
|
||||
isShow: this.props.isShow,
|
||||
rendition: this.props.rendition,
|
||||
handleEnterReader: this.props.handleEnterReader,
|
||||
handleLeaveReader: this.props.handleLeaveReader,
|
||||
}}
|
||||
/>
|
||||
<PopupMenu {...popupMenuProps} />
|
||||
{this.state.loading ? (
|
||||
<div className="spinner">
|
||||
<Lottie options={siriOptions} height={100} width={300} />
|
||||
</div>
|
||||
) : null}
|
||||
<>
|
||||
{this.props.isShowBookmark ? <div className="bookmark"></div> : null}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ViewArea;
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
17
src/model/HtmlBook.ts
Normal file
17
src/model/HtmlBook.ts
Normal file
@@ -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;
|
||||
@@ -43,14 +43,14 @@ class Viewer extends React.Component<ViewerProps, ViewerState> {
|
||||
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<ViewerProps, ViewerState> {
|
||||
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<ViewerProps, ViewerState> {
|
||||
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<ViewerProps, ViewerState> {
|
||||
};
|
||||
render() {
|
||||
return (
|
||||
<div className="ebook-viewer">
|
||||
<iframe
|
||||
className="ebook-viewer"
|
||||
title="html-viewer"
|
||||
width="100%"
|
||||
height="100%"
|
||||
>
|
||||
<p>Loading</p>
|
||||
</div>
|
||||
</iframe>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
48
src/pages/htmlPage/component.tsx
Normal file
48
src/pages/htmlPage/component.tsx
Normal file
@@ -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<ViewerProps, ViewerState> {
|
||||
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 <Viewer />;
|
||||
}
|
||||
}
|
||||
export default withRouter(HtmlReader as any);
|
||||
@@ -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<ViewerProps, ViewerState> {
|
||||
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 (
|
||||
<iframe
|
||||
className="html-viewer"
|
||||
title="html-viewer"
|
||||
width="100%"
|
||||
height="100%"
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default withRouter(Viewer as any);
|
||||
@@ -1,3 +0,0 @@
|
||||
/* #root {
|
||||
height: ;
|
||||
} */
|
||||
@@ -1,11 +1,10 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { Route, Switch, HashRouter } from "react-router-dom";
|
||||
import Manager from "../pages/manager";
|
||||
import EpubReader from "../pages/epubReader";
|
||||
import Viewer from "../pages/viewer";
|
||||
import EpubReader from "../pages/epubPage";
|
||||
import HtmlReader from "../containers/htmlReader";
|
||||
import DjvuReader from "../pages/djvuReader";
|
||||
import ComicReader from "../pages/comicReader";
|
||||
import HtmlViewer from "../pages/htmlViewer";
|
||||
import _Redirect from "../pages/redirect";
|
||||
import i18n from "../i18n";
|
||||
import OtherUtil from "../utils/otherUtil";
|
||||
@@ -34,22 +33,23 @@ const Router = () => {
|
||||
<HashRouter>
|
||||
<Switch>
|
||||
<Route component={Manager} path="/manager" />
|
||||
<Route component={EpubReader} path="/epub" />{" "}
|
||||
<Route component={EpubReader} path="/epub" />
|
||||
<Route component={DjvuReader} path="/djvu" />
|
||||
<Route component={Viewer} path="/mobi" />
|
||||
<Route component={Viewer} path="/azw3" />
|
||||
<Route component={Viewer} path="/txt" />
|
||||
<Route component={Viewer} path="/docx" />
|
||||
<Route component={Viewer} path="/md" />
|
||||
<Route component={HtmlViewer} path="/html" />
|
||||
<Route component={HtmlViewer} path="/htm" />
|
||||
<Route component={HtmlViewer} path="/xml" />
|
||||
<Route component={HtmlViewer} path="/xhtml" />
|
||||
<Route component={Viewer} path="/rtf" />
|
||||
<Route component={Viewer} path="/fb2" />
|
||||
<Route component={HtmlReader} path="/mobi" />
|
||||
<Route component={HtmlReader} path="/azw3" />
|
||||
<Route component={HtmlReader} path="/txt" />
|
||||
<Route component={HtmlReader} path="/docx" />
|
||||
<Route component={HtmlReader} path="/md" />
|
||||
<Route component={HtmlReader} path="/rtf" />
|
||||
<Route component={HtmlReader} path="/fb2" />
|
||||
<Route component={ComicReader} path="/cbr" />
|
||||
<Route component={ComicReader} path="/cbz" />
|
||||
<Route component={ComicReader} path="/cbt" />
|
||||
<Route component={ComicReader} path="/cbt" />{" "}
|
||||
<Route component={HtmlReader} path="/html" />
|
||||
<Route component={HtmlReader} path="/htm" />
|
||||
<Route component={HtmlReader} path="/xml" />
|
||||
<Route component={HtmlReader} path="/xhtml" />
|
||||
<Route component={HtmlReader} path="/href" />
|
||||
<Route component={_Redirect} path="/" />
|
||||
</Switch>
|
||||
</HashRouter>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import localforage from "localforage";
|
||||
import NoteModel from "../../model/Note";
|
||||
import BookmarkModel from "../../model/Bookmark";
|
||||
import HtmlBookModel from "../../model/HtmlBook";
|
||||
import AddTrash from "../../utils/readUtils/addTrash";
|
||||
export function handleNotes(notes: NoteModel[]) {
|
||||
return { type: "HANDLE_NOTES", payload: notes };
|
||||
@@ -17,7 +18,9 @@ export function handleBookmarks(bookmarks: BookmarkModel[]) {
|
||||
export function handleDigests(digests: NoteModel[]) {
|
||||
return { type: "HANDLE_DIGESTS", payload: digests };
|
||||
}
|
||||
|
||||
export function handleHtmlBook(htmlBook: HtmlBookModel) {
|
||||
return { type: "HANDLE_HTML_BOOK", payload: htmlBook };
|
||||
}
|
||||
export function handleCurrentChapter(currentChapter: string) {
|
||||
return { type: "HANDLE_CURRENT_CHAPTER", payload: currentChapter };
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { backupPage } from "./reducers/backupPage";
|
||||
import BookModel from "../model/Book";
|
||||
import NoteModel from "../model/Note";
|
||||
import BookmarkModel from "../model/Bookmark";
|
||||
import HtmlBookModel from "../model/HtmlBook";
|
||||
const rootReducer = combineReducers({
|
||||
book,
|
||||
manager,
|
||||
@@ -76,6 +77,7 @@ export type stateType = {
|
||||
flattenChapters: any;
|
||||
noteKey: string;
|
||||
originalText: string;
|
||||
htmlBook: HtmlBookModel;
|
||||
};
|
||||
sidebar: {
|
||||
mode: string;
|
||||
|
||||
@@ -9,6 +9,7 @@ const initState = {
|
||||
color: OtherUtil.getReaderConfig("isDisplayDark") === "yes" ? 3 : 0,
|
||||
noteKey: "",
|
||||
originalText: "",
|
||||
htmlBook: null,
|
||||
readerMode: OtherUtil.getReaderConfig("readerMode") || "double",
|
||||
};
|
||||
export function reader(
|
||||
@@ -31,6 +32,11 @@ export function reader(
|
||||
...state,
|
||||
originalText: action.payload,
|
||||
};
|
||||
case "HANDLE_HTML_BOOK":
|
||||
return {
|
||||
...state,
|
||||
htmlBook: action.payload,
|
||||
};
|
||||
case "HANDLE_COLOR":
|
||||
return {
|
||||
...state,
|
||||
|
||||
36
src/utils/htmlParser.tsx
Normal file
36
src/utils/htmlParser.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
class HtmlParser {
|
||||
bookDoc: any;
|
||||
contentList: HTMLElement[];
|
||||
contentTitleList: any[];
|
||||
constructor(bookDoc: any) {
|
||||
this.bookDoc = bookDoc;
|
||||
this.contentList = [];
|
||||
this.contentTitleList = [];
|
||||
this.getContent(bookDoc);
|
||||
}
|
||||
getContent(bookDoc: HTMLElement) {
|
||||
this.contentList = Array.from(
|
||||
bookDoc.querySelectorAll("h1,h2,h3,h4,h5,font")
|
||||
);
|
||||
for (let i = 0; i < this.contentList.length; i++) {
|
||||
let random = Math.floor(Math.random() * 900) + 100;
|
||||
this.contentTitleList.push({
|
||||
label: this.contentList[i].innerText,
|
||||
id: this.contentList[i].innerText.replaceAll(" ", "_") + random,
|
||||
href: "#" + this.contentList[i].innerText.replaceAll(" ", "_") + random,
|
||||
subitems: [],
|
||||
});
|
||||
}
|
||||
for (let i = 0; i < this.contentList.length; i++) {
|
||||
this.contentList[i].id = this.contentTitleList[i].id;
|
||||
}
|
||||
}
|
||||
getAnchoredDoc() {
|
||||
return this.bookDoc;
|
||||
}
|
||||
getContentList() {
|
||||
return this.contentTitleList;
|
||||
}
|
||||
}
|
||||
|
||||
export default HtmlParser;
|
||||
@@ -7,7 +7,7 @@ class styleUtil {
|
||||
if (!iframe) return;
|
||||
let doc = iframe.contentDocument;
|
||||
if (!doc) return;
|
||||
let css = this.getDefaultCss();
|
||||
let css = this.getDefaultCss() + this.getCustomCss();
|
||||
let style = doc.getElementById("default-style");
|
||||
let background = document.querySelector(".background");
|
||||
if (!background) return;
|
||||
@@ -27,6 +27,32 @@ class styleUtil {
|
||||
}
|
||||
style.textContent = css;
|
||||
}
|
||||
static addHtmlCss() {
|
||||
let iframe = document.getElementsByTagName("iframe")[0];
|
||||
if (!iframe) return;
|
||||
let doc = iframe.contentDocument;
|
||||
if (!doc) return;
|
||||
let css = this.getDefaultCss();
|
||||
let style = doc.getElementById("default-style");
|
||||
let background = document.querySelector(".viewer");
|
||||
if (!background) return;
|
||||
background!.setAttribute(
|
||||
"style",
|
||||
`background-color:${OtherUtil.getReaderConfig("backgroundColor")}`
|
||||
);
|
||||
window.frames[0].document.body.setAttribute(
|
||||
"style",
|
||||
this.getCustomCss() as string
|
||||
);
|
||||
if (!style) {
|
||||
style = doc.createElement("style");
|
||||
style.id = "default-style";
|
||||
style.textContent = css;
|
||||
doc.head.appendChild(style);
|
||||
return;
|
||||
}
|
||||
style.textContent = css;
|
||||
}
|
||||
// 获取为文档默认应用的css样式
|
||||
static getDefaultCss() {
|
||||
let colors = ["#FBF1D1", "#EFEEB0", "#CAEFC9", "#76BEE9"];
|
||||
@@ -34,6 +60,106 @@ class styleUtil {
|
||||
|
||||
return `::selection{background:#f3a6a68c}::-moz-selection{background:#f3a6a68c}[class*=color-]:hover{cursor:pointer;background-image:linear-gradient(0,rgba(0,0,0,.075),rgba(0,0,0,.075))}.color-0{background-color:${colors[0]}}.color-1{background-color:${colors[1]}}.color-2{background-color:${colors[2]}}.color-3{background-color:${colors[3]}}.line-0{border-bottom:2px solid ${lines[0]}}.line-1{border-bottom:2px solid ${lines[1]}}.line-2{border-bottom:2px solid ${lines[2]}}.line-3{border-bottom:2px solid ${lines[3]}}}`;
|
||||
}
|
||||
static getCustomCss(isJSON: boolean = true) {
|
||||
if (isJSON) {
|
||||
return `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"
|
||||
: ""
|
||||
};margin-bottom: ${
|
||||
OtherUtil.getReaderConfig("paraSpacing") || 0
|
||||
}px !important;`;
|
||||
} else {
|
||||
return {
|
||||
"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"
|
||||
: ""
|
||||
}`,
|
||||
"margin-bottom": `${
|
||||
OtherUtil.getReaderConfig("paraSpacing") || 0
|
||||
}px !important`,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
static addStyle = (url: string) => {
|
||||
const style = document.createElement("link");
|
||||
style.href = url;
|
||||
|
||||
Reference in New Issue
Block a user