mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-08-04 10:14:36 +02:00
Update users table info #94, Reorder libraries in config #95, Use dropdown for library menu #96, update mobi reader
This commit is contained in:
parent
9715c53332
commit
cd6e99b4c3
30 changed files with 1361 additions and 557 deletions
506
client/assets/ebooks/basic.js
Normal file
506
client/assets/ebooks/basic.js
Normal file
|
@ -0,0 +1,506 @@
|
|||
/*
|
||||
Calibres stylesheet
|
||||
*/
|
||||
|
||||
export default `
|
||||
@charset "UTF-8";
|
||||
|
||||
/*
|
||||
Calibre styles
|
||||
*/
|
||||
.arabic {
|
||||
display: block;
|
||||
list-style-type: decimal;
|
||||
margin-bottom: 1em;
|
||||
margin-right: 0;
|
||||
margin-top: 1em;
|
||||
text-align: justify
|
||||
}
|
||||
.attribution {
|
||||
display: block;
|
||||
font-size: 1em;
|
||||
line-height: 1.2;
|
||||
text-align: left;
|
||||
margin: 0.3em 0
|
||||
}
|
||||
.big {
|
||||
font-size: 1.375em;
|
||||
line-height: 1.2
|
||||
}
|
||||
.big1 {
|
||||
font-size: 1em
|
||||
}
|
||||
.block {
|
||||
display: block;
|
||||
text-align: justify;
|
||||
margin: 1em 1em 2em
|
||||
}
|
||||
.block1 {
|
||||
display: block;
|
||||
text-align: justify;
|
||||
margin: 1em 4em
|
||||
}
|
||||
.block2 {
|
||||
display: block;
|
||||
text-align: justify;
|
||||
margin: 1em 1em 1em 2em
|
||||
}
|
||||
.bullet {
|
||||
display: block;
|
||||
list-style-type: disc;
|
||||
margin-bottom: 1em;
|
||||
margin-right: 0;
|
||||
margin-top: 1em;
|
||||
text-align: disc
|
||||
}
|
||||
.calibre {
|
||||
background-color: #000007;
|
||||
display: block;
|
||||
font-family: Charis, "Times New Roman", Verdana, Arial;
|
||||
font-size: 1.125em;
|
||||
line-height: 1.2;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
text-align: center;
|
||||
margin: 0 5pt
|
||||
}
|
||||
.calibre1 {
|
||||
display: block
|
||||
}
|
||||
.calibre2 {
|
||||
height: auto;
|
||||
width: auto
|
||||
}
|
||||
.calibre3:not(strong) {
|
||||
display: block;
|
||||
font-family: Charis, "Times New Roman", Verdana, Arial;
|
||||
font-size: 1.125em;
|
||||
line-height: 1.2;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
margin: 0 5pt
|
||||
}
|
||||
.calibre4 {
|
||||
font-weight: bold
|
||||
}
|
||||
.calibre5 {
|
||||
font-style: italic
|
||||
}
|
||||
.calibre6 {
|
||||
background-color: #FFF;
|
||||
display: block;
|
||||
font-family: Charis, "Times New Roman", Verdana, Arial;
|
||||
font-size: 1.125em;
|
||||
line-height: 1.2;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
text-align: center;
|
||||
margin: 0 5pt
|
||||
}
|
||||
.calibre7 {
|
||||
display: list-item
|
||||
}
|
||||
.calibre8 {
|
||||
font-size: 1em;
|
||||
line-height: 1.2;
|
||||
vertical-align: super
|
||||
}
|
||||
.calibre9 {
|
||||
border-collapse: separate;
|
||||
border-spacing: 2px;
|
||||
display: table;
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
text-indent: 0
|
||||
}
|
||||
.calibre10 {
|
||||
display: table-row;
|
||||
vertical-align: middle
|
||||
}
|
||||
.calibre11 {
|
||||
display: table-cell;
|
||||
text-align: right;
|
||||
vertical-align: inherit;
|
||||
padding: 1px
|
||||
}
|
||||
.calibre12 {
|
||||
display: table-cell;
|
||||
text-align: left;
|
||||
vertical-align: inherit;
|
||||
padding: 1px
|
||||
}
|
||||
.calibre13 {
|
||||
height: 1em;
|
||||
width: auto
|
||||
}
|
||||
.calibre14 {
|
||||
font-size: 0.88889em;
|
||||
line-height: 1.2;
|
||||
vertical-align: super
|
||||
}
|
||||
.calibre15 {
|
||||
font-size: 1em;
|
||||
line-height: 1.2;
|
||||
vertical-align: sub
|
||||
}
|
||||
.calibre16 {
|
||||
display: block;
|
||||
list-style-type: decimal;
|
||||
margin-bottom: 1em;
|
||||
margin-right: 0;
|
||||
margin-top: 1em
|
||||
}
|
||||
.calibre17 {
|
||||
display: block;
|
||||
font-size: 1.125em;
|
||||
font-weight: bold;
|
||||
line-height: 1.2;
|
||||
margin: 0.83em 0
|
||||
}
|
||||
.center {
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin: 1em 0
|
||||
}
|
||||
.center1 {
|
||||
display: block;
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
line-height: 1.2;
|
||||
text-align: center;
|
||||
margin: -2em 0 3em
|
||||
}
|
||||
.center2 {
|
||||
display: block;
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
line-height: 1.2;
|
||||
text-align: center;
|
||||
margin: 2em 0 1em
|
||||
}
|
||||
.center3 {
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin: -1em 0 1em
|
||||
}
|
||||
.center4 {
|
||||
display: block;
|
||||
text-align: center;
|
||||
text-indent: 3%;
|
||||
margin: 1em 0
|
||||
}
|
||||
.chapter {
|
||||
display: block;
|
||||
font-size: 1.125em;
|
||||
font-weight: bold;
|
||||
line-height: 2em;
|
||||
text-align: center;
|
||||
margin: 2em 0 1em
|
||||
}
|
||||
.chapter1 {
|
||||
display: block;
|
||||
font-size: 0.88889em;
|
||||
line-height: 1.2;
|
||||
margin-left: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
margin-top: 2em
|
||||
}
|
||||
.chapter2 {
|
||||
display: block;
|
||||
font-size: 1.125em;
|
||||
font-weight: bold;
|
||||
line-height: 2em;
|
||||
text-align: center;
|
||||
margin: 2em 0 3em
|
||||
}
|
||||
.copyright {
|
||||
display: block;
|
||||
font-size: 0.88889em;
|
||||
line-height: 1.2;
|
||||
margin-top: 4em;
|
||||
text-align: center
|
||||
}
|
||||
.dedication {
|
||||
display: block;
|
||||
font-size: 0.88889em;
|
||||
line-height: 1.2;
|
||||
margin-top: 4em
|
||||
}
|
||||
.dropcaps {
|
||||
float: left;
|
||||
font-size: 3.4375rem;
|
||||
line-height: 50px;
|
||||
margin-right: 0.09em;
|
||||
margin-top: -0.05em;
|
||||
padding-top: 1px
|
||||
}
|
||||
.dropcaps1 {
|
||||
float: left;
|
||||
font-size: 3.4375rem;
|
||||
line-height: 50px;
|
||||
margin-right: 0.09em;
|
||||
margin-top: 0.15em;
|
||||
padding-top: 1px
|
||||
}
|
||||
.extract {
|
||||
display: block;
|
||||
text-align: justify;
|
||||
margin: 2em 0 0.3em
|
||||
}
|
||||
.extract1 {
|
||||
display: block;
|
||||
text-align: justify;
|
||||
text-indent: 3%;
|
||||
margin: 2em 0 0.3em
|
||||
}
|
||||
.extract2 {
|
||||
display: block;
|
||||
text-align: justify;
|
||||
margin: 1em 0 0.3em
|
||||
}
|
||||
.footnote {
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 0;
|
||||
border-left-style: solid;
|
||||
border-left-width: 0;
|
||||
border-right-style: solid;
|
||||
border-right-width: 0;
|
||||
border-top-style: solid;
|
||||
border-top-width: 1px;
|
||||
display: block;
|
||||
font-size: 1em;
|
||||
line-height: 1.2;
|
||||
margin-top: 2 em
|
||||
}
|
||||
.footnote1 {
|
||||
display: block;
|
||||
text-align: justify;
|
||||
margin: 0.3em 0 0.3em 2
|
||||
}
|
||||
.footnote2 {
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 0;
|
||||
border-left-style: solid;
|
||||
border-left-width: 0;
|
||||
border-right-style: solid;
|
||||
border-right-width: 0;
|
||||
border-top-style: solid;
|
||||
border-top-width: 1px;
|
||||
display: block;
|
||||
font-size: 0.88889em;
|
||||
line-height: 1.2;
|
||||
margin-top: 2 em
|
||||
}
|
||||
.hanging {
|
||||
display: block;
|
||||
font-size: 0.88889em;
|
||||
line-height: 1.2;
|
||||
text-align: left;
|
||||
text-indent: -1em;
|
||||
margin: 0.5em 0 0.3em 1em
|
||||
}
|
||||
.hanging1 {
|
||||
display: block;
|
||||
font-size: 0.88889em;
|
||||
line-height: 1.2;
|
||||
text-align: left;
|
||||
text-indent: -1em;
|
||||
margin: 0.5em 0 0.3em 1.5em
|
||||
}
|
||||
.hanging2 {
|
||||
display: block;
|
||||
font-size: 1em;
|
||||
line-height: 1.2;
|
||||
text-indent: -1em;
|
||||
margin: 0.5em 0 0.3em 1em
|
||||
}
|
||||
.hanging3 {
|
||||
display: block;
|
||||
font-size: 1em;
|
||||
line-height: 1.2;
|
||||
text-align: left;
|
||||
text-indent: 1em;
|
||||
margin: 0.1em 0 0.3em 1em
|
||||
}
|
||||
.hanging4 {
|
||||
display: block;
|
||||
font-size: 1em;
|
||||
line-height: 1.2;
|
||||
text-align: left;
|
||||
text-indent: 0.1em;
|
||||
margin: 0.1em 0 0.3em 1em
|
||||
}
|
||||
a.hlink {
|
||||
text-decoration: none
|
||||
}
|
||||
.indent {
|
||||
display: block;
|
||||
text-align: justify;
|
||||
text-indent: 1em;
|
||||
margin: 0.3em 0
|
||||
}
|
||||
.line {
|
||||
border-top: currentColor solid 1px;
|
||||
border-bottom: currentColor solid 1px
|
||||
}
|
||||
.loweralpha {
|
||||
display: block;
|
||||
list-style-type: lower-alpha;
|
||||
margin-bottom: 1em;
|
||||
margin-right: 0;
|
||||
margin-top: 1em;
|
||||
text-align: justify
|
||||
}
|
||||
.none {
|
||||
display: block;
|
||||
list-style-type: none;
|
||||
margin-bottom: 1em;
|
||||
margin-right: 0;
|
||||
margin-top: 1em;
|
||||
text-align: justify
|
||||
}
|
||||
.none1 {
|
||||
display: block;
|
||||
list-style-type: none;
|
||||
margin-bottom: 0;
|
||||
margin-right: 0;
|
||||
margin-top: 0;
|
||||
text-align: justify
|
||||
}
|
||||
.nonindent {
|
||||
display: block;
|
||||
text-align: justify;
|
||||
margin: 0.3em 0
|
||||
}
|
||||
.nonindent1 {
|
||||
display: block;
|
||||
font-size: 1.125em;
|
||||
line-height: 1.2;
|
||||
text-indent: -1em;
|
||||
margin: 0.5em 0 0.3em 0.1em
|
||||
}
|
||||
.nonindent2 {
|
||||
display: block;
|
||||
font-size: 1.125em;
|
||||
line-height: 1.2;
|
||||
text-indent: -1em;
|
||||
margin: 0.5em 0 0.3em -0.5em
|
||||
}
|
||||
.nonindent3 {
|
||||
display: block;
|
||||
text-align: justify;
|
||||
text-indent: 3%;
|
||||
margin: 0.3em 0
|
||||
}
|
||||
.part {
|
||||
display: block;
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
line-height: 2em;
|
||||
text-align: center;
|
||||
margin: 4em 0 1em
|
||||
}
|
||||
.preface {
|
||||
display: block;
|
||||
font-size: 0.88889em;
|
||||
line-height: 1.2;
|
||||
margin-left: 2em;
|
||||
margin-right: 2em;
|
||||
text-align: justify
|
||||
}
|
||||
.pubhlink {
|
||||
color: green;
|
||||
text-decoration: none
|
||||
}
|
||||
.right {
|
||||
display: block;
|
||||
text-align: right;
|
||||
margin: 0.3em 0
|
||||
}
|
||||
.section {
|
||||
display: block;
|
||||
font-size: 1.125em;
|
||||
font-weight: bold;
|
||||
line-height: 1.2;
|
||||
text-align: center;
|
||||
margin: 2em 0 0.5em
|
||||
}
|
||||
.section1 {
|
||||
display: block;
|
||||
font-size: 1.125em;
|
||||
font-weight: bold;
|
||||
line-height: 1.2;
|
||||
text-align: left;
|
||||
margin: 2em 0 0.3em
|
||||
}
|
||||
.section2 {
|
||||
display: block;
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
line-height: 1.2;
|
||||
text-align: left;
|
||||
margin: 2em 0 0.3em 1em
|
||||
}
|
||||
.small {
|
||||
font-size: 0.66667em
|
||||
}
|
||||
.small1 {
|
||||
font-size: 0.75em
|
||||
}
|
||||
.subchapter {
|
||||
display: block;
|
||||
font-size: 1.125em;
|
||||
font-weight: bold;
|
||||
line-height: 1.2;
|
||||
margin: 1em 0
|
||||
}
|
||||
.textbox {
|
||||
background-color: #E4E4E4;
|
||||
display: block;
|
||||
line-height: 1.5em;
|
||||
margin-bottom: 2em;
|
||||
margin-top: 2em;
|
||||
text-align: justify;
|
||||
border-top: currentColor double 2px;
|
||||
border-bottom: currentColor double 2px
|
||||
}
|
||||
.textbox1 {
|
||||
display: block;
|
||||
text-align: justify;
|
||||
margin: 0.3em 0.5em 0.3em 0.8em
|
||||
}
|
||||
.textbox2 {
|
||||
display: block;
|
||||
text-align: justify;
|
||||
text-indent: 1em;
|
||||
margin: 0.3em 0.5em
|
||||
}
|
||||
.textbox3 {
|
||||
display: block;
|
||||
text-align: justify;
|
||||
text-indent: 3%;
|
||||
margin: 0.3em 0.5em 0.3em 0.8em
|
||||
}
|
||||
.titlepage {
|
||||
display: block;
|
||||
margin-left: -0.4em;
|
||||
margin-top: 1.2em
|
||||
}
|
||||
.toc {
|
||||
display: block;
|
||||
font-size: 1em;
|
||||
line-height: 1.2;
|
||||
text-align: center
|
||||
}
|
||||
.toc1 {
|
||||
display: block;
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
line-height: 1.2;
|
||||
text-align: center;
|
||||
margin: 0.67em 0 3em
|
||||
}
|
||||
.underline {
|
||||
text-decoration: underline
|
||||
}
|
||||
`
|
248
client/assets/ebooks/htmlParser.js
Normal file
248
client/assets/ebooks/htmlParser.js
Normal file
|
@ -0,0 +1,248 @@
|
|||
/*
|
||||
This is borrowed from koodo-reader https://github.com/troyeguo/koodo-reader/tree/master/src
|
||||
*/
|
||||
|
||||
export const isTitle = (
|
||||
line,
|
||||
isContainDI = false,
|
||||
isContainChapter = false,
|
||||
isContainCHAPTER = false
|
||||
) => {
|
||||
return (
|
||||
line.length < 30 &&
|
||||
line.indexOf("[") === -1 &&
|
||||
line.indexOf("(") === -1 &&
|
||||
(line.startsWith("CHAPTER") ||
|
||||
line.startsWith("Chapter") ||
|
||||
line.startsWith("序章") ||
|
||||
line.startsWith("前言") ||
|
||||
line.startsWith("声明") ||
|
||||
line.startsWith("聲明") ||
|
||||
line.startsWith("写在前面的话") ||
|
||||
line.startsWith("后记") ||
|
||||
line.startsWith("楔子") ||
|
||||
line.startsWith("后序") ||
|
||||
line.startsWith("寫在前面的話") ||
|
||||
line.startsWith("後記") ||
|
||||
line.startsWith("後序") ||
|
||||
/(?=[MDCLXVI])M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/.test(
|
||||
line
|
||||
) ||
|
||||
(line.startsWith("第") && startWithDI(line)) ||
|
||||
(line.startsWith("卷") && startWithJUAN(line)) ||
|
||||
startWithRomanNum(line) ||
|
||||
(!isContainDI &&
|
||||
!isContainChapter &&
|
||||
!isContainCHAPTER &&
|
||||
line.indexOf("第") > -1 &&
|
||||
(line[line.indexOf("第") - 1] === " " ||
|
||||
line[line.indexOf("第") - 1] === " " ||
|
||||
line[line.indexOf("第") - 1] === "、" ||
|
||||
line[line.indexOf("第") - 1] === ":" ||
|
||||
line[line.indexOf("第") - 1] === ":") &&
|
||||
startWithDI(line.substr(line.indexOf("第")))) ||
|
||||
(!isContainDI &&
|
||||
!isContainChapter &&
|
||||
!isContainCHAPTER &&
|
||||
line.indexOf(" ") &&
|
||||
startWithNumAndSpace(line)) ||
|
||||
(!isContainDI &&
|
||||
!isContainChapter &&
|
||||
!isContainCHAPTER &&
|
||||
line.indexOf(" ") &&
|
||||
startWithNumAndSpace(line)) ||
|
||||
(!isContainDI &&
|
||||
!isContainChapter &&
|
||||
!isContainCHAPTER &&
|
||||
line.indexOf("、") &&
|
||||
startWithNumAndPause(line)) ||
|
||||
(!isContainDI &&
|
||||
!isContainChapter &&
|
||||
!isContainCHAPTER &&
|
||||
line.indexOf(":") &&
|
||||
startWithNumAndColon(line)) ||
|
||||
(!isContainDI &&
|
||||
!isContainChapter &&
|
||||
!isContainCHAPTER &&
|
||||
line.indexOf(":") &&
|
||||
startWithNumAndColon(line)))
|
||||
);
|
||||
};
|
||||
const startWithDI = (line) => {
|
||||
let keywords = [
|
||||
"章",
|
||||
"节",
|
||||
"回",
|
||||
"節",
|
||||
"卷",
|
||||
"部",
|
||||
"輯",
|
||||
"辑",
|
||||
"話",
|
||||
"集",
|
||||
"话",
|
||||
"篇",
|
||||
];
|
||||
let flag = false;
|
||||
for (let i = 0; i < keywords.length; i++) {
|
||||
if (
|
||||
(line.indexOf(keywords[i]) > -1 &&
|
||||
(line[line.indexOf(keywords[i]) + 1] === " " ||
|
||||
line[line.indexOf(keywords[i]) + 1] === " " ||
|
||||
line[line.indexOf(keywords[i]) + 1] === "、" ||
|
||||
line[line.indexOf(keywords[i]) + 1] === ":" ||
|
||||
line[line.indexOf(keywords[i]) + 1] === ":")) ||
|
||||
!line[line.indexOf(keywords[i]) + 1]
|
||||
) {
|
||||
if (
|
||||
/^[\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u842c]+$/.test(
|
||||
line.substring(1, line.indexOf(keywords[i])).trim()
|
||||
) ||
|
||||
/^\d+$/.test(line.substring(1, line.indexOf(keywords[i])).trim())
|
||||
) {
|
||||
flag = true;
|
||||
}
|
||||
if (flag) break;
|
||||
}
|
||||
}
|
||||
return flag;
|
||||
};
|
||||
const startWithJUAN = (line) => {
|
||||
if (
|
||||
/^[\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u842c]+$/.test(
|
||||
line.substring(1, line.indexOf(" "))
|
||||
) ||
|
||||
/^\d+$/.test(line.substring(1, line.indexOf(" ")))
|
||||
)
|
||||
return true;
|
||||
if (
|
||||
/^[\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u842c]+$/.test(
|
||||
line.substring(1, line.indexOf(" "))
|
||||
) ||
|
||||
/^\d+$/.test(line.substring(1, line.indexOf(" ")))
|
||||
)
|
||||
return true;
|
||||
if (
|
||||
/^[\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u842c]+$/.test(
|
||||
line.substring(1)
|
||||
) ||
|
||||
/^\d+$/.test(line.substring(1))
|
||||
)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
const startWithRomanNum = (line) => {
|
||||
if (
|
||||
/(?=[MDCLXVI])M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/.test(
|
||||
line.substring(0, line.indexOf(" "))
|
||||
)
|
||||
)
|
||||
return true;
|
||||
if (
|
||||
/(?=[MDCLXVI])M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/.test(
|
||||
line.substring(0, line.indexOf("."))
|
||||
)
|
||||
)
|
||||
return true;
|
||||
if (
|
||||
/(?=[MDCLXVI])M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/.test(
|
||||
line.trim()
|
||||
)
|
||||
)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
const startWithNumAndSpace = (line) => {
|
||||
if (
|
||||
/^[\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u842c]+$/.test(
|
||||
line.substring(0, line.indexOf(" "))
|
||||
)
|
||||
)
|
||||
return true;
|
||||
if (
|
||||
/^[\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u842c]+$/.test(
|
||||
line.substring(0, line.indexOf(" "))
|
||||
)
|
||||
)
|
||||
return true;
|
||||
|
||||
if (/^\d+$/.test(line.substring(0, line.indexOf(" ")))) return true;
|
||||
if (/^\d+$/.test(line.substring(0, line.indexOf(" ")))) return true;
|
||||
return false;
|
||||
};
|
||||
const startWithNumAndColon = (line) => {
|
||||
if (
|
||||
/^[\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u842c]+$/.test(
|
||||
line.substring(0, line.indexOf(":"))
|
||||
)
|
||||
)
|
||||
return true;
|
||||
if (
|
||||
/^[\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u842c]+$/.test(
|
||||
line.substring(0, line.indexOf(":"))
|
||||
)
|
||||
)
|
||||
return true;
|
||||
|
||||
if (/^\d+$/.test(line.substring(0, line.indexOf(":")))) return true;
|
||||
if (/^\d+$/.test(line.substring(0, line.indexOf(":")))) return true;
|
||||
return false;
|
||||
};
|
||||
const startWithNumAndPause = (line) => {
|
||||
if (
|
||||
/^[\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u842c]+$/.test(
|
||||
line.substring(0, line.indexOf("、"))
|
||||
)
|
||||
)
|
||||
return true;
|
||||
|
||||
if (/^\d+$/.test(line.substring(0, line.indexOf("、")))) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
class HtmlParser {
|
||||
bookDoc;
|
||||
contentList;
|
||||
contentTitleList;
|
||||
constructor(bookDoc) {
|
||||
this.bookDoc = bookDoc;
|
||||
this.contentList = [];
|
||||
this.contentTitleList = [];
|
||||
this.getContent(bookDoc);
|
||||
}
|
||||
getContent(bookDoc) {
|
||||
this.contentList = Array.from(
|
||||
bookDoc.querySelectorAll("h1,h2,h3,h4,h5,b,font")
|
||||
).filter((item, index) => {
|
||||
return isTitle(item.innerText.trim());
|
||||
});
|
||||
|
||||
for (let i = 0; i < this.contentList.length; i++) {
|
||||
let random = Math.floor(Math.random() * 900000) + 100000;
|
||||
this.contentTitleList.push({
|
||||
label: this.contentList[i].innerText,
|
||||
id: "title" + random,
|
||||
href: "#title" + 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.filter((item, index) => {
|
||||
if (index > 0) {
|
||||
return item.label !== this.contentTitleList[index - 1].label;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default HtmlParser;
|
450
client/assets/ebooks/mobi.js
Normal file
450
client/assets/ebooks/mobi.js
Normal file
|
@ -0,0 +1,450 @@
|
|||
/*
|
||||
This is borrowed from koodo-reader https://github.com/troyeguo/koodo-reader/tree/master/src
|
||||
*/
|
||||
|
||||
function ab2str(buf) {
|
||||
if (buf instanceof ArrayBuffer) {
|
||||
buf = new Uint8Array(buf);
|
||||
}
|
||||
return new TextDecoder("utf-8").decode(buf);
|
||||
}
|
||||
|
||||
var domParser = new DOMParser();
|
||||
|
||||
class Buffer {
|
||||
capacity;
|
||||
fragment_list;
|
||||
imageArray;
|
||||
cur_fragment;
|
||||
constructor(capacity) {
|
||||
this.capacity = capacity;
|
||||
this.fragment_list = [];
|
||||
this.imageArray = [];
|
||||
this.cur_fragment = new Fragment(capacity);
|
||||
this.fragment_list.push(this.cur_fragment);
|
||||
}
|
||||
write(byte) {
|
||||
var result = this.cur_fragment.write(byte);
|
||||
if (!result) {
|
||||
this.cur_fragment = new Fragment(this.capacity);
|
||||
this.fragment_list.push(this.cur_fragment);
|
||||
this.cur_fragment.write(byte);
|
||||
}
|
||||
}
|
||||
get(idx) {
|
||||
var fi = 0;
|
||||
while (fi < this.fragment_list.length) {
|
||||
var frag = this.fragment_list[fi];
|
||||
if (idx < frag.size) {
|
||||
return frag.get(idx);
|
||||
}
|
||||
idx -= frag.size;
|
||||
fi += 1;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
size() {
|
||||
var s = 0;
|
||||
for (var i = 0; i < this.fragment_list.length; i++) {
|
||||
s += this.fragment_list[i].size;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
shrink() {
|
||||
var total_buffer = new Uint8Array(this.size());
|
||||
var offset = 0;
|
||||
for (var i = 0; i < this.fragment_list.length; i++) {
|
||||
var frag = this.fragment_list[i];
|
||||
if (frag.full()) {
|
||||
total_buffer.set(frag.buffer, offset);
|
||||
} else {
|
||||
total_buffer.set(frag.buffer.slice(0, frag.size), offset);
|
||||
}
|
||||
offset += frag.size;
|
||||
}
|
||||
return total_buffer;
|
||||
}
|
||||
}
|
||||
|
||||
var copagesne_uint8array = function (buffers) {
|
||||
var total_size = 0;
|
||||
for (let i = 0; i < buffers.length; i++) {
|
||||
var buffer = buffers[i];
|
||||
total_size += buffer.length;
|
||||
}
|
||||
var total_buffer = new Uint8Array(total_size);
|
||||
var offset = 0;
|
||||
for (let i = 0; i < buffers.length; i++) {
|
||||
buffer = buffers[i];
|
||||
total_buffer.set(buffer, offset);
|
||||
offset += buffer.length;
|
||||
}
|
||||
return total_buffer;
|
||||
};
|
||||
|
||||
class Fragment {
|
||||
buffer;
|
||||
capacity;
|
||||
size;
|
||||
constructor(capacity) {
|
||||
this.buffer = new Uint8Array(capacity);
|
||||
this.capacity = capacity;
|
||||
this.size = 0;
|
||||
}
|
||||
|
||||
write(byte) {
|
||||
if (this.size >= this.capacity) {
|
||||
return false;
|
||||
}
|
||||
this.buffer[this.size] = byte;
|
||||
this.size += 1;
|
||||
return true;
|
||||
}
|
||||
full() {
|
||||
return this.size === this.capacity;
|
||||
}
|
||||
get(idx) {
|
||||
return this.buffer[idx];
|
||||
}
|
||||
}
|
||||
|
||||
var uncompression_lz77 = function (data) {
|
||||
var length = data.length;
|
||||
var offset = 0; // Current offset into data
|
||||
var buffer = new Buffer(data.length);
|
||||
|
||||
while (offset < length) {
|
||||
var char = data[offset];
|
||||
offset += 1;
|
||||
|
||||
if (char === 0) {
|
||||
buffer.write(char);
|
||||
} else if (char <= 8) {
|
||||
for (var i = offset; i < offset + char; i++) {
|
||||
buffer.write(data[i]);
|
||||
}
|
||||
offset += char;
|
||||
} else if (char <= 0x7f) {
|
||||
buffer.write(char);
|
||||
} else if (char <= 0xbf) {
|
||||
var next = data[offset];
|
||||
offset += 1;
|
||||
var distance = (((char << 8) | next) >> 3) & 0x7ff;
|
||||
var lz_length = (next & 0x7) + 3;
|
||||
|
||||
var buffer_size = buffer.size();
|
||||
for (let i = 0; i < lz_length; i++) {
|
||||
buffer.write(buffer.get(buffer_size - distance));
|
||||
buffer_size += 1;
|
||||
}
|
||||
} else {
|
||||
buffer.write(32);
|
||||
buffer.write(char ^ 0x80);
|
||||
}
|
||||
}
|
||||
return buffer;
|
||||
};
|
||||
|
||||
class MobiFile {
|
||||
view;
|
||||
buffer;
|
||||
offset;
|
||||
header;
|
||||
palm_header;
|
||||
mobi_header;
|
||||
reclist;
|
||||
constructor(data) {
|
||||
this.view = new DataView(data);
|
||||
this.buffer = this.view.buffer;
|
||||
this.offset = 0;
|
||||
this.header = null;
|
||||
}
|
||||
|
||||
parse() { }
|
||||
|
||||
getUint8() {
|
||||
var v = this.view.getUint8(this.offset);
|
||||
this.offset += 1;
|
||||
return v;
|
||||
}
|
||||
|
||||
getUint16() {
|
||||
var v = this.view.getUint16(this.offset);
|
||||
this.offset += 2;
|
||||
return v;
|
||||
}
|
||||
|
||||
getUint32() {
|
||||
var v = this.view.getUint32(this.offset);
|
||||
this.offset += 4;
|
||||
return v;
|
||||
}
|
||||
|
||||
getStr(size) {
|
||||
var v = ab2str(this.buffer.slice(this.offset, this.offset + size));
|
||||
this.offset += size;
|
||||
return v;
|
||||
}
|
||||
|
||||
skip(size) {
|
||||
this.offset += size;
|
||||
}
|
||||
|
||||
setoffset(_of) {
|
||||
this.offset = _of;
|
||||
}
|
||||
|
||||
get_record_extrasize(data, flags) {
|
||||
var pos = data.length - 1;
|
||||
var extra = 0;
|
||||
for (var i = 15; i > 0; i--) {
|
||||
if (flags & (1 << i)) {
|
||||
var res = this.buffer_get_varlen(data, pos);
|
||||
var size = res[0];
|
||||
var l = res[1];
|
||||
pos = res[2];
|
||||
pos -= size - l;
|
||||
extra += size;
|
||||
}
|
||||
}
|
||||
if (flags & 1) {
|
||||
var a = data[pos];
|
||||
extra += (a & 0x3) + 1;
|
||||
}
|
||||
return extra;
|
||||
}
|
||||
|
||||
// data should be uint8array
|
||||
buffer_get_varlen(data, pos) {
|
||||
var l = 0;
|
||||
var size = 0;
|
||||
var byte_count = 0;
|
||||
var mask = 0x7f;
|
||||
var stop_flag = 0x80;
|
||||
var shift = 0;
|
||||
for (var i = 0; ; i++) {
|
||||
var byte = data[pos];
|
||||
size |= (byte & mask) << shift;
|
||||
shift += 7;
|
||||
l += 1;
|
||||
byte_count += 1;
|
||||
pos -= 1;
|
||||
|
||||
var to_stop = byte & stop_flag;
|
||||
if (byte_count >= 4 || to_stop > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return [size, l, pos];
|
||||
}
|
||||
// 读出文本内容
|
||||
read_text() {
|
||||
var text_end = this.palm_header.record_count;
|
||||
var buffers = [];
|
||||
for (var i = 1; i <= text_end; i++) {
|
||||
buffers.push(this.read_text_record(i));
|
||||
}
|
||||
var all = copagesne_uint8array(buffers);
|
||||
return ab2str(all);
|
||||
}
|
||||
|
||||
read_text_record(i) {
|
||||
var flags = this.mobi_header.extra_flags;
|
||||
var begin = this.reclist[i].offset;
|
||||
var end = this.reclist[i + 1].offset;
|
||||
|
||||
var data = new Uint8Array(this.buffer.slice(begin, end));
|
||||
var ex = this.get_record_extrasize(data, flags);
|
||||
|
||||
data = new Uint8Array(this.buffer.slice(begin, end - ex));
|
||||
if (this.palm_header.compression === 2) {
|
||||
var buffer = uncompression_lz77(data);
|
||||
return buffer.shrink();
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
// 从buffer中读出image
|
||||
read_image(idx) {
|
||||
var first_image_idx = this.mobi_header.first_image_idx;
|
||||
var begin = this.reclist[first_image_idx + idx].offset;
|
||||
var end = this.reclist[first_image_idx + idx + 1].offset;
|
||||
var data = new Uint8Array(this.buffer.slice(begin, end));
|
||||
return new Blob([data.buffer]);
|
||||
}
|
||||
|
||||
load() {
|
||||
this.header = this.load_pdbheader();
|
||||
this.reclist = this.load_reclist();
|
||||
this.load_record0();
|
||||
}
|
||||
|
||||
load_pdbheader() {
|
||||
var header = {};
|
||||
header.name = this.getStr(32);
|
||||
header.attr = this.getUint16();
|
||||
header.version = this.getUint16();
|
||||
header.ctime = this.getUint32();
|
||||
header.mtime = this.getUint32();
|
||||
header.btime = this.getUint32();
|
||||
header.mod_num = this.getUint32();
|
||||
header.appinfo_offset = this.getUint32();
|
||||
header.sortinfo_offset = this.getUint32();
|
||||
header.type = this.getStr(4);
|
||||
header.creator = this.getStr(4);
|
||||
header.uid = this.getUint32();
|
||||
header.next_rec = this.getUint32();
|
||||
header.record_num = this.getUint16();
|
||||
return header;
|
||||
}
|
||||
|
||||
load_reclist() {
|
||||
var reclist = [];
|
||||
for (var i = 0; i < this.header.record_num; i++) {
|
||||
var record = {};
|
||||
record.offset = this.getUint32();
|
||||
// TODO(zz) change
|
||||
record.attr = this.getUint32();
|
||||
reclist.push(record);
|
||||
}
|
||||
return reclist;
|
||||
}
|
||||
load_record0() {
|
||||
this.palm_header = this.load_record0_header();
|
||||
this.mobi_header = this.load_mobi_header();
|
||||
}
|
||||
|
||||
load_record0_header() {
|
||||
var p_header = {};
|
||||
var first_record = this.reclist[0];
|
||||
this.setoffset(first_record.offset);
|
||||
|
||||
p_header.compression = this.getUint16();
|
||||
this.skip(2);
|
||||
p_header.text_length = this.getUint32();
|
||||
p_header.record_count = this.getUint16();
|
||||
p_header.record_size = this.getUint16();
|
||||
p_header.encryption_type = this.getUint16();
|
||||
this.skip(2);
|
||||
|
||||
return p_header;
|
||||
}
|
||||
|
||||
load_mobi_header() {
|
||||
var mobi_header = {};
|
||||
|
||||
var start_offset = this.offset;
|
||||
|
||||
mobi_header.identifier = this.getUint32();
|
||||
mobi_header.header_length = this.getUint32();
|
||||
mobi_header.mobi_type = this.getUint32();
|
||||
mobi_header.text_encoding = this.getUint32();
|
||||
mobi_header.uid = this.getUint32();
|
||||
mobi_header.generator_version = this.getUint32();
|
||||
|
||||
this.skip(40);
|
||||
|
||||
mobi_header.first_nonbook_index = this.getUint32();
|
||||
mobi_header.full_name_offset = this.getUint32();
|
||||
mobi_header.full_name_length = this.getUint32();
|
||||
|
||||
mobi_header.language = this.getUint32();
|
||||
mobi_header.input_language = this.getUint32();
|
||||
mobi_header.output_language = this.getUint32();
|
||||
mobi_header.min_version = this.getUint32();
|
||||
mobi_header.first_image_idx = this.getUint32();
|
||||
|
||||
mobi_header.huff_rec_index = this.getUint32();
|
||||
mobi_header.huff_rec_count = this.getUint32();
|
||||
mobi_header.datp_rec_index = this.getUint32();
|
||||
mobi_header.datp_rec_count = this.getUint32();
|
||||
|
||||
mobi_header.exth_flags = this.getUint32();
|
||||
|
||||
this.skip(36);
|
||||
|
||||
mobi_header.drm_offset = this.getUint32();
|
||||
mobi_header.drm_count = this.getUint32();
|
||||
mobi_header.drm_size = this.getUint32();
|
||||
mobi_header.drm_flags = this.getUint32();
|
||||
|
||||
this.skip(8);
|
||||
|
||||
// TODO (zz) fdst_index
|
||||
this.skip(4);
|
||||
|
||||
this.skip(46);
|
||||
|
||||
mobi_header.extra_flags = this.getUint16();
|
||||
|
||||
this.setoffset(start_offset + mobi_header.header_length);
|
||||
|
||||
return mobi_header;
|
||||
}
|
||||
load_exth_header() {
|
||||
// TODO
|
||||
return {};
|
||||
}
|
||||
extractContent(s) {
|
||||
var span = document.createElement("span");
|
||||
span.innerHTML = s;
|
||||
return span.textContent || span.innerText;
|
||||
}
|
||||
render(isElectron = false) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.load();
|
||||
var content = this.read_text();
|
||||
var bookDoc = domParser.parseFromString(content, "text/html")
|
||||
.documentElement;
|
||||
let lines = Array.from(
|
||||
bookDoc.querySelectorAll("p,b,font,h3,h2,h1")
|
||||
);
|
||||
let parseContent = [];
|
||||
for (let i = 0, len = lines.length; i < len - 1; i++) {
|
||||
lines[i].innerText &&
|
||||
lines[i].innerText !== parseContent[parseContent.length - 1] &&
|
||||
parseContent.push(lines[i].innerText);
|
||||
let imgDoms = lines[i].getElementsByTagName("img");
|
||||
if (imgDoms.length > 0) {
|
||||
for (let i = 0; i < imgDoms.length; i++) {
|
||||
parseContent.push("#image");
|
||||
}
|
||||
}
|
||||
}
|
||||
const handleImage = async () => {
|
||||
var imgDoms = bookDoc.getElementsByTagName("img");
|
||||
parseContent.push("~image");
|
||||
for (let i = 0; i < imgDoms.length; i++) {
|
||||
const src = await this.render_image(imgDoms, i);
|
||||
parseContent.push(
|
||||
src + " " + imgDoms[i].width + " " + imgDoms[i].height
|
||||
);
|
||||
}
|
||||
if (imgDoms.length > 200 || !isElectron) {
|
||||
resolve(bookDoc);
|
||||
} else {
|
||||
resolve(parseContent.join("\n \n"));
|
||||
}
|
||||
};
|
||||
handleImage();
|
||||
});
|
||||
}
|
||||
render_image = (imgDoms, i) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
var imgDom = imgDoms[i];
|
||||
var idx = +imgDom.getAttribute("recindex");
|
||||
var blob = this.read_image(idx - 1);
|
||||
var imgReader = new FileReader();
|
||||
imgReader.onload = (e) => {
|
||||
imgDom.src = e.target?.result;
|
||||
resolve(e.target?.result);
|
||||
};
|
||||
imgReader.onerror = function (err) {
|
||||
reject(err);
|
||||
};
|
||||
imgReader.readAsDataURL(blob);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default MobiFile;
|
Loading…
Add table
Add a link
Reference in a new issue