class MessageTip {
constructor(tip = null, css = null) {
this.tip = tip || ""
this.node = $('<div/>', {
text: this.tip,
css: css || {
zIndex: 999,
fontSize: '1rem',
color: 'rgb(255, 255, 255)',
backgroundColor: 'rgba(0, 0, 0, 0.6)',
padding: '10px 15px',
margin: '0px 0px 0px -60px',
borderRadius: '4px',
position: 'fixed',
top: '50%',
left: '50%',
width: '130px',
textAlign: 'center'
}
})
$("body").append(this.node)
}
text(text) {
this.tip = text
$(this.node).text(text)
}
show() {
$(this.node).show()
}
hide() {
$(this.node).hide()
}
open(time = null) {
if (this.hold != undefined) {
clearInterval(this.hold)
}
this.index = -1
this.list = [".", "..", "..."]
this.text(this.tip + this.list[2])
this.hold = setInterval(() => {
if (++this.index >= this.list.length) {
this.index = 0
}
this.text(this.tip + this.list[this.index])
}, time || 750)
}
off() {
clearInterval(this.hold)
}
}
function CheckConnection() {
return new Promise(
(resolve) => {
var xhr = new XMLHttpRequest()
var url = window.host + "/" + "getReadConfig"
xhr.timeout = 1500
xhr.open('GET', url, true)
xhr.onload = function () {
resolve(true)
}
xhr.ontimeout = function (e) {
console.log("ontimeout", e);
resolve(false)
}
xhr.onerror = function (e) {
console.log("onerror", e);
resolve(false)
}
xhr.send(null)
}
)
}
class Offline {
constructor(cb) {
this.update().then(cb)
}
get(key) {
return new Promise(
completed => {
ldb.get(key, function (value) {
completed(value)
})
}
)
}
set(key, value) {
ldb.set(key, value)
}
update() {
return new Promise(
async completed => {
var obj = (await this.get("offline")) || "[]"
this.data = JSON.parse(obj)
this.keys = []
for (let key in this.data) {
this.keys.push(this.data[key]['url'])
}
console.log("缓存数量", this.keys.length)
this.size(obj).then(
size => console.log(size)
)
completed(this)
}
)
}
find(key, value) {
let enumerate = this.data
for (let index in enumerate) {
if (enumerate[index][key] == value) {
return enumerate[index]['data']
}
}
return null
}
save(object) {
if (this.keys.indexOf(object['url']) != -1) {//重复写入
return null
}
this.keys.push(object['url'])
this.data.push(object)
this.data = this.data.slice(this.data.length > 1500 ? this.data.length - 1500 : 0)
this.set("offline", JSON.stringify(this.data))
}
async size(obj = null) {
function calculateStringSize(str) {
var bytes = str.length * 2;
var kb = bytes / 1024;
var mb = kb / 1024;
return { bytes: bytes, kb: kb, mb: mb };
}
return calculateStringSize(obj || (await offline.get("offline")))
}
}
function request(url, options = null, of = null) {
var exp = function (value) {
this.value = value;
}
exp.prototype.json = function () {
return JSON.parse(this.value)
}
exp.prototype.text = function () {
return this.value
}
if (of == null) {
of = !window.online || false
}
return new Promise(
async (completed) => {
async function send() {
let value = await (await fetch(url, options)).text()
offline.save({
"url": url,
"data": value
})
return value
}
if (of) { // 离线
let value = offline.find("url", url)
if (value != null) {
completed(new exp(value))
console.log("离线加载", url)
} else {
console.log("找不到相关缓存", url)
completed(false)
}
} else {
completed(new exp(await send()))
}
}
)
}
class Books {
constructor() {
this.wrapper = $(".wrapper")[0]
this.book_num = null
this.book_info = null
}
async update() {
this.book_shelf = (await request(`${host}/getBookshelf`)).json()['data']
this.delete()
for (let i in this.book_shelf) {
let info = this.book_shelf[i]
let dv = "data-v-79a0da0d"
let img = encodeURIComponent(info['coverUrl'])
let name = info['name']
let author = info['author'] || "未知"
let size = info['totalChapterNum'] || null
let date = Math.floor((Date.now() - parseInt(info['lastCheckTime'])) / (1000 * 60 * 60))
let node = $(`<div ${dv}="" class="book">
<div ${dv}="" class="cover-img"><img ${dv}="" class="cover"
src="${host}/cover?path=${img}"
alt=""></div>
<div ${dv}="" class="info">
<div ${dv}="" class="name">${name}</div>
<div ${dv}="" class="sub">
<div ${dv}="" class="author">${author}</div><!---->
<div ${dv}="" class="update-info">
<div ${dv}="" class="dot">•</div>
<div ${dv}="" class="size">共${size}章</div>
<div ${dv}="" class="dot">•</div>
<div ${dv}="" class="date">${date}小时前</div>
</div>
</div><!---->
<div ${dv}="" class="dur-chapter"> 已读:${info['durChapterTitle']}</div>
<div ${dv}="" class="last-chapter">最新:${info['latestChapterTitle']}</div>
</div>
</div>`)
$(this.wrapper).append(node)
$(node).on("click", function (method, _this, context) {
return function () {
method.call(_this, context)
}
}(this.openbook, this, i))
}
this.book_num = -1
}
async directory() {
var url = host + `/getChapterList?url=${encodeURIComponent(this.book_info['bookUrl'])}`
try {
this.chapter_list = (await request(url)).json()['data']
} catch (error) {
console.log("读目录失败,请检查缓存/网络",error)
books.visual()
return false
}
if (window.online) { // 最后阅读的索引
this.chapter_index = this.book_info['durChapterIndex']
} else {
let value = JSON.parse(localStorage.getItem(this.book_info['name']))
if (value == null) {
this.chapter_index = this.book_info['durChapterIndex']
} else {
this.chapter_index = value['index']
}
console.log("使用本地进度", this.chapter_index)
}
$('.chapter-box-bd').html("") // 清空章节目录
for (let i in this.chapter_list) {
let title = `${this.chapter_list[i]['title']}`
let node = $(`<div class="chapter">${title}</div>`)
this.chapter_list[i]['node'] = node
$('.chapter-box-bd').append(node)
}
this.openchapter(this.chapter_index)
}
openchapter(index = null) {
return new Promise(
async complete => {
var src = host + `/getBookContent?url=${encodeURIComponent(this.book_info['bookUrl'])}&index=${index || this.chapter_index}`
var content = await request(src)
if (content == false) { // 获取失败
console.log("请求失败了呢!", src)
return complete(false)
}
content = content.json()['data']
this.text(content)
var title = this.chapter_list[index || this.chapter_index]['title']
this.title(title)
this.sprogress(index || this.chapter_index)
$('#frame1')[0].style = ''
$(".select").toggleClass("select", false)
$(this.chapter_list[index || this.chapter_index]['node']).toggleClass("select", true)
complete(true)
}
)
}
sprogress(index) {
if (window['online'] == false) {
console.log("离线模式不支持上传进度") // 待:缓存进度到本地联网后上传
localStorage.setItem(this.book_info['name'], JSON.stringify({
"index": index,
"time": Date.now()
}))
return false
}
var data = { "name": this.book_info['name'], "author": this.book_info['author'], "durChapterIndex": index, "durChapterPos": 0, "durChapterTime": Date.now(), "durChapterTitle": this.chapter_list[index]['title'] }
var options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
}
fetch(`${host}/saveBookProgress`, options).then(
resp => resp.json()
).then(
data => console.log(data)
)
}
openbook(i) {
this.book_info = this.book_shelf[i]
this.book_num = i
this.visual()
this.bname()
this.author()
this.directory()
this.max_index = this.book_info['totalChapterNum'] // 最大章节
}
delete() {
this.wrapper.innerHTML = ""
}
visual() {
if (this.wrapper.style.display === 'none') {
this.wrapper.style.display = 'grid';
} else {
this.wrapper.style.display = 'none';
}
}
prev() {
let backup = this.chapter_index
if (--this.chapter_index < 0) {
this.chapter_index = 0
console.log("已经是第一章了哦");
} else {
this.openchapter(this.chapter_index).then(
(stat) => {
if (stat == false) {
this.chapter_index = backup
}else{
window.scrollTo(0, 0)
}
}
)
}
}
next() {
let backup = this.chapter_index
if (++this.chapter_index > this.max_index) {
this.chapter_index = this.max_index
console.log("已经是最后一章了哦");
} else {
this.openchapter(this.chapter_index).then(
(stat) => {
if (stat == false) {
this.chapter_index = backup
}else{
window.scrollTo(0, 0)
}
}
)
}
}
text(text) {
var columns = text.split("\n")
var bhtml = []
columns.forEach(column => {
let match = /src="([^"]*)"/.exec(column)
if (match) {
let src = `${host}/image?path=${match[1]}&url=${encodeURIComponent(this.book_info['bookUrl'])}&width=960`
column = `<img src="${src}" />`
}
bhtml.push(`<p>${column}<\p>`)
})
var content = bhtml.join("\n")
$("#ChapterBody").html(content)
$(".article-desc span:nth-child(2)").text("字数:" + content.length)
}
bname() {
$(".item.bold").text(this.book_info['name'])
}
author() {
$(".article-desc span:nth-child(1)").text("作者:" + this.book_info['author'] || "未知")
}
title(title) {
$(".article-title").text(title)
}
async offline() {
var chapters = this.chapter_list
for (let i = 0; i < chapters.length; i++) {
let url = `${host}/getBookContent?url=${encodeURIComponent(this.book_info['bookUrl'])}&index=${i}`
await request(url)
console.log(`缓存进度${i + 1}/${chapters.length}`)
}
}
}
function InitBook() {
window['host'] = 'http://192.168.31.112:1087'
window.tip = new MessageTip("正在检查连接")
tip.show()
tip.open()
CheckConnection().then(
(status) => {
if (status) {
tip.off()
tip.hide()
} else {
tip.off()
setTimeout(() => {
tip.text("离线模式")
setTimeout(() => {
tip.hide()
}, 3000)
}, 1000)
}
window['online'] = status
console.log('连接状态:' + status)
window.offline = new Offline(
function () {
console.log("Offline初始化完成");
window.books = new Books()
books.update()
books.visual()
console.log("初始化结束");
}
)
}
)
}
$('#chapter').mouseenter(function () {
clearTimeout(window.timeoutId);
});
$('#chapter').mouseleave(function () {
window.timeoutId = setTimeout(function () {
$(".chapter-box").toggleClass("hide", true)
}, 1500);
});
$('#settingBox').mouseenter(function () {
clearTimeout(window.timeoutId2);
});
$('#settingBox').mouseleave(function () {
window.timeoutId2 = setTimeout(function () {
$("#settingBox").toggleClass("hide", true)
}, 1500);
});
$(".ctrl-btn.menu-btn").on("click", function () {
$(".chapter-box").toggleClass("hide")
books.chapter_list[books.chapter_index]['node'][0].scrollIntoView({
behavior: 'smooth',
block: 'center'
})
})//目录
$(".ctrl-btn.setting-btn").on("click", function () {
$("#settingBox").toggleClass("hide")
$(".chapter-box").toggleClass("hide", true)
})//设置
$(".ctrl-btn.back-list-btn").on("click", function () {
books.visual()
$("#settingBox").toggleClass("hide", true)
$(".chapter-box").toggleClass("hide", true)
})//书架
$(".ctrl-btn-prev-btn").on("click", function () {
books.prev()
})//上一页
$(".ctrl-btn-next-btn").on("click", function () {
books.next()
})//下一页
$(InitBook)