Compare commits
77 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a60ad354c | ||
|
|
c565a20344 | ||
|
|
735ee88f0b | ||
|
|
9405ce2cc0 | ||
|
|
115277d88a | ||
|
|
6925c11dbd | ||
|
|
984d835892 | ||
|
|
23353a4960 | ||
|
|
955bcda032 | ||
|
|
81ad5d7a2c | ||
|
|
dada7980ec | ||
|
|
511a308646 | ||
|
|
65c8f2cc81 | ||
|
|
238c18bc48 | ||
|
|
873a08fee1 | ||
|
|
7e89228803 | ||
|
|
fc630923a4 | ||
|
|
928f911d03 | ||
|
|
7ffcd3fe1b | ||
|
|
0efbaf643a | ||
|
|
f1bf8e54ae | ||
|
|
b4aa6ab6c8 | ||
|
|
72431f0202 | ||
|
|
04a326c0e4 | ||
|
|
931966f4f3 | ||
|
|
8808cc4779 | ||
|
|
988c959eba | ||
|
|
c0b658d9e6 | ||
|
|
3190246f34 | ||
|
|
d957b4a5f9 | ||
|
|
bef9e5705c | ||
|
|
eb2affa518 | ||
|
|
07b9a3c033 | ||
|
|
3ca14ae06a | ||
|
|
7caa0c2112 | ||
|
|
9c69f5bc01 | ||
|
|
125a2e0f17 | ||
|
|
1b4360b897 | ||
|
|
4775d6e47b | ||
|
|
33fc553c55 | ||
|
|
25cad81c50 | ||
|
|
02a2099c1f | ||
|
|
1cda186b1a | ||
|
|
f10291b6c6 | ||
|
|
26ab5d6765 | ||
|
|
5edeed0747 | ||
|
|
c878ce432f | ||
|
|
81798897c8 | ||
|
|
63840fadbc | ||
|
|
36aa057035 | ||
|
|
30afd2421c | ||
|
|
53a1d90bd8 | ||
|
|
2ecf6beef2 | ||
|
|
85910a20e9 | ||
|
|
66cf7790b3 | ||
|
|
4a9eb7e4bb | ||
|
|
07446696c1 | ||
|
|
a29f9d9a4b | ||
|
|
d49c9baec3 | ||
|
|
8c9d4a12ee | ||
|
|
fce69e4657 | ||
|
|
b387509f88 | ||
|
|
8dc8bdc0d6 | ||
|
|
00caae8363 | ||
|
|
2ead8570a7 | ||
|
|
408315466b | ||
|
|
c651836554 | ||
|
|
03a1e70fce | ||
|
|
ab5a11a24f | ||
|
|
8cd6ed472c | ||
|
|
055181b744 | ||
|
|
e331a3920b | ||
|
|
c62bccb470 | ||
|
|
ea351ea293 | ||
|
|
d806a07c60 | ||
|
|
c0ea096f1f | ||
|
|
011d4a1672 |
@@ -11,7 +11,7 @@
|
||||
Открыть выбранную закладку
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
<q-input ref="search" v-model="search" class="col" rounded outlined dense bg-color="white" placeholder="Найти">
|
||||
<q-input ref="search" v-model="search" class="col" outlined dense bg-color="white" placeholder="Найти">
|
||||
<template #append>
|
||||
<q-icon v-if="search !== ''" name="la la-times" class="cursor-pointer" @click="resetSearch" />
|
||||
</template>
|
||||
|
||||
@@ -5,19 +5,19 @@
|
||||
</template>
|
||||
|
||||
<template #buttons>
|
||||
<span class="full-screen-button row justify-center items-center" @mousedown.stop @click="fullScreenToggle">
|
||||
<span class="header-button row justify-center items-center" @mousedown.stop @click="fullScreenToggle">
|
||||
<q-icon :name="(fullScreenActive ? 'la la-compress-arrows-alt': 'la la-expand-arrows-alt')" size="16px" />
|
||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">На весь экран</q-tooltip>
|
||||
</span>
|
||||
<span class="full-screen-button row justify-center items-center" @mousedown.stop @click="changeScale(0.1)">
|
||||
<span class="header-button row justify-center items-center" @mousedown.stop @click="changeScale(0.1)">
|
||||
<q-icon name="la la-plus" size="16px" />
|
||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Увеличить масштаб</q-tooltip>
|
||||
</span>
|
||||
<span class="full-screen-button row justify-center items-center" @mousedown.stop @click="changeScale(-0.1)">
|
||||
<span class="header-button row justify-center items-center" @mousedown.stop @click="changeScale(-0.1)">
|
||||
<q-icon name="la la-minus" size="16px" />
|
||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Уменьшить масштаб</q-tooltip>
|
||||
</span>
|
||||
<span class="full-screen-button row justify-center items-center" @mousedown.stop @click="showHelp">
|
||||
<span class="header-button row justify-center items-center" @mousedown.stop @click="showHelp">
|
||||
<q-icon name="la la-question-circle" size="16px" />
|
||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Справка</q-tooltip>
|
||||
</span>
|
||||
@@ -32,7 +32,7 @@
|
||||
:options="rootLinkOptions"
|
||||
style="width: 230px"
|
||||
dropdown-icon="la la-angle-down la-sm"
|
||||
rounded outlined dense emit-value map-options display-value-sanitize options-sanitize
|
||||
outlined dense emit-value map-options display-value-sanitize options-sanitize
|
||||
@popup-show="onSelectPopupShow" @popup-hide="onSelectPopupHide"
|
||||
>
|
||||
<template #prepend>
|
||||
@@ -61,7 +61,7 @@
|
||||
:options="selectedLinkOptions"
|
||||
style="width: 50px"
|
||||
dropdown-icon="la la-angle-down la-sm"
|
||||
rounded outlined dense emit-value map-options hide-selected display-value-sanitize options-sanitize
|
||||
outlined dense emit-value map-options hide-selected display-value-sanitize options-sanitize
|
||||
@popup-show="onSelectPopupShow" @popup-hide="onSelectPopupHide"
|
||||
>
|
||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||
@@ -73,7 +73,7 @@
|
||||
ref="input"
|
||||
v-model="bookUrl"
|
||||
class="col q-mr-sm"
|
||||
rounded outlined dense
|
||||
outlined dense
|
||||
bg-color="white"
|
||||
placeholder="Скопируйте сюда ссылку на книгу и нажмите 'Открыть'"
|
||||
@focus="selectAllOnFocus" @keydown="bookUrlKeyDown"
|
||||
@@ -99,7 +99,7 @@
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<q-btn :disabled="!bookUrl" rounded color="green-7" no-caps size="14px" @click="submitUrl">
|
||||
<q-btn :disabled="!bookUrl" color="green-7" no-caps size="14px" @click="submitUrl">
|
||||
Открыть
|
||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||
Открыть в читалке
|
||||
@@ -894,14 +894,15 @@ export default vueComponent(ExternalLibs);
|
||||
background-color: #A0A0A0;
|
||||
}
|
||||
|
||||
.full-screen-button {
|
||||
.header-button {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.full-screen-button:hover {
|
||||
background-color: #69C05F;
|
||||
.header-button:hover {
|
||||
color: white;
|
||||
background-color: #39902F;
|
||||
}
|
||||
|
||||
.transparent-layout {
|
||||
|
||||
@@ -23,15 +23,15 @@
|
||||
|
||||
<div class="q-mb-sm" />
|
||||
|
||||
<div v-show="selectedTab == 'contents'" class="tab-panel">
|
||||
<div v-show="selectedTab == 'contents'" ref="tabPanelContents" class="tab-panel">
|
||||
<div>
|
||||
<div v-for="item in contents" :key="item.key" class="column" style="width: 540px">
|
||||
<div class="row q-px-sm no-wrap" :class="{'item': !item.isBookPos, 'item-book-pos': item.isBookPos}">
|
||||
<div :ref="`mainitem${item.key}`" class="row q-px-sm no-wrap" :class="{'item': !item.isBookPos, 'item-book-pos': item.isBookPos}">
|
||||
<div v-if="item.list.length" class="row justify-center items-center expand-button clickable" @click="expandClick(item.key)">
|
||||
<q-icon name="la la-caret-right" class="icon" :class="{'expanded-icon': item.expanded}" color="green-8" size="20px" />
|
||||
<q-icon name="la la-caret-right" class="icon" :class="{'expanded-icon': item.expanded}" color="green-8" size="24px" />
|
||||
</div>
|
||||
<div v-else class="no-expand-button clickable" @click="setBookPos(item.offset)">
|
||||
<q-icon name="la la-stop" class="icon" style="visibility: hidden" size="20px" />
|
||||
<q-icon name="la la-stop" class="icon" style="visibility: hidden" size="24px" />
|
||||
</div>
|
||||
<div class="col row clickable" @click="setBookPos(item.offset)">
|
||||
<div :style="item.indentStyle"></div>
|
||||
@@ -42,8 +42,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="item.expanded" :ref="`subitem${item.key}`" class="subitems-transition">
|
||||
<div v-for="subitem in item.list" :key="subitem.key" class="row q-px-sm no-wrap" :class="{'subitem': !subitem.isBookPos, 'subitem-book-pos': subitem.isBookPos}">
|
||||
<div v-if="item.expanded" :ref="`subdiv${item.key}`" class="subitems-transition">
|
||||
<div
|
||||
v-for="subitem in item.list"
|
||||
:ref="`subitem${subitem.key}`"
|
||||
:key="subitem.key" class="row q-px-sm no-wrap" :class="{'subitem': !subitem.isBookPos, 'subitem-book-pos': subitem.isBookPos}"
|
||||
>
|
||||
<div class="col row clickable" @click="setBookPos(subitem.offset)">
|
||||
<div class="no-expand-button"></div>
|
||||
<div :style="subitem.indentStyle"></div>
|
||||
@@ -61,10 +65,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-show="selectedTab == 'images'" class="tab-panel">
|
||||
<div v-show="selectedTab == 'images'" ref="tabPanelImages" class="tab-panel">
|
||||
<div>
|
||||
<div v-for="item in images" :key="item.key" class="column" style="width: 540px">
|
||||
<div class="row q-px-sm no-wrap" :class="{'item': !item.isBookPos, 'item-book-pos': item.isBookPos}">
|
||||
<div :ref="`image${item.key}`" class="row q-px-sm no-wrap" :class="{'item': !item.isBookPos, 'item-book-pos': item.isBookPos}">
|
||||
<div class="col row clickable" @click="setBookPos(item.offset)">
|
||||
<div class="image-thumb-box row justify-center items-center">
|
||||
<div v-show="!imageLoaded[item.id]" class="image-thumb column justify-center">
|
||||
@@ -124,7 +128,10 @@ const componentOptions = {
|
||||
watch: {
|
||||
bookPos() {
|
||||
this.updateBookPosSelection();
|
||||
}
|
||||
},
|
||||
selectedTab() {
|
||||
this.updateBookPosScrollTop();
|
||||
},
|
||||
},
|
||||
};
|
||||
class ContentsPage {
|
||||
@@ -282,31 +289,30 @@ class ContentsPage {
|
||||
if (!this.isVisible)
|
||||
return;
|
||||
|
||||
await utils.sleep(50);
|
||||
await this.$nextTick();
|
||||
const bp = this.bookPos;
|
||||
|
||||
for (let i = 0; i < this.contents.length; i++) {
|
||||
const item = this.contents[i];
|
||||
const nextOffset = (i < this.contents.length - 1 ? this.contents[i + 1].offset : this.parsed.textLength);
|
||||
|
||||
if (bp >= item.offset && bp < nextOffset) {
|
||||
item.isBookPos = true;
|
||||
} else if (item.isBookPos) {
|
||||
item.isBookPos = false;
|
||||
}
|
||||
|
||||
for (let j = 0; j < item.list.length; j++) {
|
||||
const subitem = item.list[j];
|
||||
const nextSubOffset = (j < item.list.length - 1 ? item.list[j + 1].offset : nextOffset);
|
||||
|
||||
if (bp >= subitem.offset && bp < nextSubOffset) {
|
||||
subitem.isBookPos = true;
|
||||
this.contents[i] = Object.assign(item, {list: item.list});
|
||||
this.updateBookPosScrollTop('contents', item, subitem, j);
|
||||
} else if (subitem.isBookPos) {
|
||||
subitem.isBookPos = false;
|
||||
this.contents[i] = Object.assign(item, {list: item.list});
|
||||
}
|
||||
}
|
||||
|
||||
if (bp >= item.offset && bp < nextOffset) {
|
||||
this.contents[i] = Object.assign(item, {isBookPos: true});
|
||||
} else if (item.isBookPos) {
|
||||
this.contents[i] = Object.assign(item, {isBookPos: false});
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.images.length; i++) {
|
||||
@@ -314,11 +320,96 @@ class ContentsPage {
|
||||
const nextOffset = (i < this.images.length - 1 ? this.images[i + 1].offset : this.parsed.textLength);
|
||||
|
||||
if (bp >= img.offset && bp < nextOffset) {
|
||||
this.images[i] = Object.assign(img, {isBookPos: true});
|
||||
this.images[i].isBookPos = true;
|
||||
} else if (img.isBookPos) {
|
||||
this.images[i] = Object.assign(img, {isBookPos: false});
|
||||
this.images[i].isBookPos = false;
|
||||
}
|
||||
}
|
||||
|
||||
this.updateBookPosScrollTop();
|
||||
}
|
||||
|
||||
/*getOffsetTop(key) {
|
||||
let el = this.getFirstElem(this.$refs[`mainitem${key}`]);
|
||||
return (el ? el.offsetTop : 0);
|
||||
}*/
|
||||
|
||||
async updateBookPosScrollTop() {
|
||||
try {
|
||||
await this.$nextTick();
|
||||
|
||||
if (this.selectedTab == 'contents') {
|
||||
let item;
|
||||
let subitem;
|
||||
let i;
|
||||
|
||||
//ищем выделенные item
|
||||
for(const _item of this.contents) {
|
||||
if (_item.isBookPos) {
|
||||
item = _item;
|
||||
for (let ii = 0; ii < item.list.length; ii++) {
|
||||
const _subitem = item.list[ii];
|
||||
if (_subitem.isBookPos) {
|
||||
subitem = _subitem;
|
||||
i = ii;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
//вычисляем и смещаем tabPanel.scrollTop
|
||||
let el = this.getFirstElem(this.$refs[`mainitem${item.key}`]);
|
||||
let elShift = 0;
|
||||
if (subitem && item.expanded) {
|
||||
const subEl = this.getFirstElem(this.$refs[`subitem${subitem.key}`]);
|
||||
elShift = el.offsetHeight - subEl.offsetHeight*(i + 1);
|
||||
} else {
|
||||
elShift = el.offsetHeight;
|
||||
}
|
||||
|
||||
const tabPanel = this.$refs.tabPanelContents;
|
||||
const halfH = tabPanel.clientHeight/2;
|
||||
const newScrollTop = el.offsetTop - halfH - elShift;
|
||||
if (newScrollTop < 20 + tabPanel.scrollTop - halfH || newScrollTop > -20 + tabPanel.scrollTop + halfH)
|
||||
tabPanel.scrollTop = newScrollTop;
|
||||
}
|
||||
|
||||
if (this.selectedTab == 'images') {
|
||||
let item;
|
||||
|
||||
//ищем выделенные item
|
||||
for(const _item of this.images) {
|
||||
if (_item.isBookPos) {
|
||||
item = _item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
//вычисляем и смещаем tabPanel.scrollTop
|
||||
let el = this.getFirstElem(this.$refs[`image${item.key}`]);
|
||||
|
||||
const tabPanel = this.$refs.tabPanelImages;
|
||||
const halfH = tabPanel.clientHeight/2;
|
||||
const newScrollTop = el.offsetTop - halfH - el.offsetHeight/2;
|
||||
|
||||
if (newScrollTop < 20 + tabPanel.scrollTop - halfH || newScrollTop > -20 + tabPanel.scrollTop + halfH)
|
||||
tabPanel.scrollTop = newScrollTop;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
getFirstElem(items) {
|
||||
return (Array.isArray(items) ? items[0] : items);
|
||||
}
|
||||
|
||||
async expandClick(key) {
|
||||
@@ -326,17 +417,17 @@ class ContentsPage {
|
||||
const expanded = !item.expanded;
|
||||
|
||||
if (!expanded) {
|
||||
const subitems = this.$refs[`subitem${key}`];
|
||||
subitems.style.height = '0';
|
||||
let subdiv = this.getFirstElem(this.$refs[`subdiv${key}`]);
|
||||
subdiv.style.height = '0';
|
||||
await utils.sleep(200);
|
||||
}
|
||||
|
||||
this.contents[key] = Object.assign({}, item, {expanded});
|
||||
this.contents[key].expanded = expanded;
|
||||
|
||||
if (expanded) {
|
||||
await this.$nextTick();
|
||||
const subitems = this.$refs[`subitem${key}`];
|
||||
subitems.style.height = subitems.scrollHeight + 'px';
|
||||
let subdiv = this.getFirstElem(this.$refs[`subdiv${key}`]);
|
||||
subdiv.style.height = subdiv.scrollHeight + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,13 +5,20 @@
|
||||
</template>
|
||||
|
||||
<div class="col column" style="min-width: 600px">
|
||||
<q-btn-toggle
|
||||
v-model="selectedTab"
|
||||
toggle-color="primary"
|
||||
no-caps unelevated
|
||||
:options="buttons"
|
||||
/>
|
||||
<div class="separator"></div>
|
||||
<div class="bg-grey-3 row">
|
||||
<q-tabs
|
||||
v-model="selectedTab"
|
||||
active-color="black"
|
||||
active-bg-color="white"
|
||||
indicator-color="white"
|
||||
dense
|
||||
no-caps
|
||||
inline-label
|
||||
class="bg-grey-4 text-grey-7"
|
||||
>
|
||||
<q-tab v-for="btn in buttons" :key="btn.value" :name="btn.value" :label="btn.label" />
|
||||
</q-tabs>
|
||||
</div>
|
||||
|
||||
<keep-alive>
|
||||
<component :is="activePage" ref="page" class="col"></component>
|
||||
@@ -93,8 +100,4 @@ export default vueComponent(HelpPage);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.separator {
|
||||
height: 1px;
|
||||
background-color: #E0E0E0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div v-show="visible" class="column justify-center items-center z-max" style="background-color: rgba(0, 0, 0, 0.8)">
|
||||
<div v-show="visible" class="column justify-center items-center" style="background-color: rgba(0, 0, 0, 0.8); z-index: 100;">
|
||||
<div class="column justify-start items-center" style="height: 250px">
|
||||
<q-circular-progress
|
||||
show-value
|
||||
|
||||
@@ -141,6 +141,7 @@
|
||||
@load-file="loadFile"
|
||||
@book-pos-changed="bookPosChanged"
|
||||
@do-action="doAction"
|
||||
@hide-tool-bar="hideToolBar"
|
||||
></component>
|
||||
</keep-alive>
|
||||
|
||||
@@ -201,6 +202,7 @@ import miscApi from '../../api/misc';
|
||||
|
||||
import {versionHistory} from './versionHistory';
|
||||
import * as utils from '../../share/utils';
|
||||
import LockQueue from '../../share/LockQueue';
|
||||
|
||||
const componentOptions = {
|
||||
components: {
|
||||
@@ -313,6 +315,8 @@ class Reader {
|
||||
this.reader = this.$store.state.reader;
|
||||
this.config = this.$store.state.config;
|
||||
|
||||
this.lock = new LockQueue(100);
|
||||
|
||||
this.$root.addEventHook('key', this.keyHook);
|
||||
|
||||
this.lastActivePage = false;
|
||||
@@ -345,6 +349,13 @@ class Reader {
|
||||
this.debouncedSetRecentBook(newValue);
|
||||
}, 15000, {maxWait: 20000});
|
||||
|
||||
this.debouncedHideToolBar = _.debounce((event) => {
|
||||
if (this.toolBarHideOnScroll && this.toolBarActive !== !!event.show) {
|
||||
this.commit('reader/setToolBarActive', !!event.show);
|
||||
this.$root.eventHook('resize');
|
||||
}
|
||||
}, 200);
|
||||
|
||||
document.addEventListener('fullscreenchange', () => {
|
||||
this.fullScreenActive = (document.fullscreenElement !== null);
|
||||
});
|
||||
@@ -402,6 +413,7 @@ class Reader {
|
||||
this.clickControlActive = this.clickControl;
|
||||
this.blinkCachedLoad = settings.blinkCachedLoad;
|
||||
this.showToolButton = settings.showToolButton;
|
||||
this.toolBarHideOnScroll = settings.toolBarHideOnScroll;
|
||||
this.enableSitesFilter = settings.enableSitesFilter;
|
||||
this.showNeedUpdateNotify = settings.showNeedUpdateNotify;
|
||||
this.splitToPara = settings.splitToPara;
|
||||
@@ -662,6 +674,10 @@ class Reader {
|
||||
this.$root.eventHook('resize');
|
||||
}
|
||||
|
||||
hideToolBar(event) {
|
||||
this.debouncedHideToolBar(event);
|
||||
}
|
||||
|
||||
fullScreenToggle() {
|
||||
this.fullScreenActive = !this.fullScreenActive;
|
||||
if (this.fullScreenActive) {
|
||||
@@ -897,7 +913,7 @@ class Reader {
|
||||
|
||||
refreshBook() {
|
||||
const mrb = this.mostRecentBook();
|
||||
this.loadBook({url: mrb.url, uploadFileName: mrb.uploadFileName, force: true});
|
||||
this.loadBook(Object.assign({}, mrb, {force: true}));
|
||||
}
|
||||
|
||||
undoAction() {
|
||||
@@ -1051,7 +1067,7 @@ class Reader {
|
||||
return result;
|
||||
}
|
||||
|
||||
async loadBook(opts) {
|
||||
async _loadBook(opts) {
|
||||
if (!opts || !opts.url) {
|
||||
this.mostRecentBook();
|
||||
return;
|
||||
@@ -1061,10 +1077,6 @@ class Reader {
|
||||
|
||||
let url = encodeURI(decodeURI(opts.url));
|
||||
|
||||
//TODO: убрать конвертирование 'file://' после 06.2021
|
||||
if (url.length == 71 && url.indexOf('file://') == 0)
|
||||
url = url.replace(/^file/, 'disk');
|
||||
|
||||
if ((url.indexOf('http://') != 0) && (url.indexOf('https://') != 0) &&
|
||||
(url.indexOf('disk://') != 0))
|
||||
url = 'http://' + url;
|
||||
@@ -1091,33 +1103,36 @@ class Reader {
|
||||
progress.show();
|
||||
progress.setState({state: 'parse'});
|
||||
|
||||
// есть ли среди недавних
|
||||
const key = bookManager.keyFromUrl(url);
|
||||
let wasOpened = await bookManager.getRecentBook({key});
|
||||
wasOpened = (wasOpened ? wasOpened : {});
|
||||
const bookPos = (opts.bookPos !== undefined ? opts.bookPos : wasOpened.bookPos);
|
||||
const bookPosSeen = (opts.bookPos !== undefined ? opts.bookPos : wasOpened.bookPosSeen);
|
||||
const uploadFileName = (opts.uploadFileName ? opts.uploadFileName : '');
|
||||
// есть ли среди загруженных
|
||||
let wasOpened = bookManager.findRecentByUrlAndPath(url, opts.path);
|
||||
wasOpened = (wasOpened ? _.cloneDeep(wasOpened) : {});
|
||||
|
||||
wasOpened = Object.assign(wasOpened, {
|
||||
path: (opts.path !== undefined ? opts.path : wasOpened.path),
|
||||
bookPos: (opts.bookPos !== undefined ? opts.bookPos : wasOpened.bookPos),
|
||||
bookPosSeen: (opts.bookPos !== undefined ? opts.bookPos : wasOpened.bookPosSeen),
|
||||
uploadFileName: (opts.uploadFileName ? opts.uploadFileName : wasOpened.uploadFileName),
|
||||
});
|
||||
|
||||
let book = null;
|
||||
|
||||
if (!opts.force) {
|
||||
// пытаемся загрузить и распарсить книгу в менеджере из локального кэша
|
||||
const bookParsed = await bookManager.getBook({url, path: opts.path}, (prog) => {
|
||||
const bookParsed = await bookManager.getBook(wasOpened, (prog) => {
|
||||
progress.setState({progress: prog});
|
||||
});
|
||||
|
||||
// если есть в локальном кэше
|
||||
if (bookParsed) {
|
||||
await bookManager.setRecentBook(Object.assign({bookPos, bookPosSeen}, bookParsed));
|
||||
await bookManager.setRecentBook(Object.assign(wasOpened, bookParsed));
|
||||
this.mostRecentBook();
|
||||
this.addAction(bookPos);
|
||||
this.addAction(wasOpened.bookPos);
|
||||
this.loaderActive = false;
|
||||
progress.hide(); this.progressActive = false;
|
||||
this.blinkCachedLoadMessage();
|
||||
|
||||
this.checkBookPosPercent();
|
||||
await this.activateClickMapPage();
|
||||
this.activateClickMapPage();//no await
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1131,7 +1146,7 @@ class Reader {
|
||||
});
|
||||
book = Object.assign({}, wasOpened, {data: resp.data});
|
||||
} catch (e) {
|
||||
//молчим
|
||||
this.$root.notify.error('Конвертированный файл не найден на сервере.<br>Пробуем загрузить оригинал.', 'Ошибка загрузки');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1142,7 +1157,7 @@ class Reader {
|
||||
if (!book) {
|
||||
book = await readerApi.loadBook({
|
||||
url,
|
||||
uploadFileName,
|
||||
uploadFileName: wasOpened.uploadFileName,
|
||||
enableSitesFilter: this.enableSitesFilter,
|
||||
skipHtmlCheck: (this.splitToPara ? true : false),
|
||||
isText: (this.splitToPara ? true : false),
|
||||
@@ -1159,14 +1174,44 @@ class Reader {
|
||||
|
||||
// добавляем в bookManager
|
||||
progress.setState({state: 'parse', step: 5});
|
||||
|
||||
const addedBook = await bookManager.addBook(book, (prog) => {
|
||||
progress.setState({progress: prog});
|
||||
});
|
||||
|
||||
// sameBookKey
|
||||
if (url.indexOf('disk://') == 0) {
|
||||
//ищем такой файл в загруженных
|
||||
let found = bookManager.findRecentBySameBookKey(wasOpened.uploadFileName);
|
||||
found = (found ? _.cloneDeep(found) : found);
|
||||
|
||||
if (found) {
|
||||
if (wasOpened.sameBookKey != found.sameBookKey) {
|
||||
//спрашиваем, надо ли объединить файлы
|
||||
const askResult = bookManager.keysEqual(found.path, addedBook.path) ||
|
||||
await this.$root.stdDialog.askYesNo(`
|
||||
Файл с именем "${wasOpened.uploadFileName}" уже есть в загруженных.
|
||||
<br>Объединить позицию?`, 'Найдена похожая книга');
|
||||
if (askResult) {
|
||||
wasOpened.bookPos = found.bookPos;
|
||||
wasOpened.bookPosSeen = found.bookPosSeen;
|
||||
wasOpened.sameBookKey = found.sameBookKey;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
wasOpened.sameBookKey = wasOpened.uploadFileName;
|
||||
}
|
||||
} else {
|
||||
wasOpened.sameBookKey = addedBook.url;
|
||||
}
|
||||
|
||||
if (!bookManager.keysEqual(wasOpened.path, addedBook.path))
|
||||
delete wasOpened.loadTime;
|
||||
|
||||
// добавляем в историю
|
||||
await bookManager.setRecentBook(Object.assign({bookPos, bookPosSeen, uploadFileName}, addedBook));
|
||||
await bookManager.setRecentBook(Object.assign(wasOpened, addedBook));
|
||||
this.mostRecentBook();
|
||||
this.addAction(bookPos);
|
||||
this.addAction(wasOpened.bookPos);
|
||||
this.updateRoute(true);
|
||||
|
||||
this.loaderActive = false;
|
||||
@@ -1177,11 +1222,11 @@ class Reader {
|
||||
this.stopBlink = true;
|
||||
|
||||
this.checkBookPosPercent();
|
||||
await this.activateClickMapPage();
|
||||
this.activateClickMapPage();//no await
|
||||
} catch (e) {
|
||||
progress.hide(); this.progressActive = false;
|
||||
this.loaderActive = true;
|
||||
if (!this.showHelpOnErrorIfNeeded(e.message)) {
|
||||
if (!this.showHelpOnErrorIfNeeded(url)) {
|
||||
this.$root.stdDialog.alert(e.message, 'Ошибка', {color: 'negative'});
|
||||
}
|
||||
} finally {
|
||||
@@ -1189,7 +1234,16 @@ class Reader {
|
||||
}
|
||||
}
|
||||
|
||||
async loadFile(opts) {
|
||||
async loadBook(opts) {
|
||||
await this.lock.get();
|
||||
try {
|
||||
await this._loadBook(opts);
|
||||
} finally {
|
||||
this.lock.ret();
|
||||
}
|
||||
}
|
||||
|
||||
async _loadFile(opts) {
|
||||
this.progressActive = true;
|
||||
|
||||
await this.$nextTick();
|
||||
@@ -1205,7 +1259,7 @@ class Reader {
|
||||
|
||||
progress.hide(); this.progressActive = false;
|
||||
|
||||
await this.loadBook({url, uploadFileName: opts.file.name, force: true});
|
||||
await this._loadBook({url, uploadFileName: opts.file.name, force: true});
|
||||
} catch (e) {
|
||||
progress.hide(); this.progressActive = false;
|
||||
this.loaderActive = true;
|
||||
@@ -1213,6 +1267,15 @@ class Reader {
|
||||
}
|
||||
}
|
||||
|
||||
async loadFile(opts) {
|
||||
await this.lock.get();
|
||||
try {
|
||||
await this._loadFile(opts);
|
||||
} finally {
|
||||
this.lock.ret();
|
||||
}
|
||||
}
|
||||
|
||||
blinkCachedLoadMessage() {
|
||||
if (!this.blinkCachedLoad)
|
||||
return;
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
|
||||
<br><br>
|
||||
<div class="row justify-center">
|
||||
<!--q-btn class="q-px-sm" color="primary" dense no-caps rounded @click="openDonate">
|
||||
<!--q-btn class="q-px-sm" color="primary" dense no-caps @click="openDonate">
|
||||
Помочь проекту
|
||||
</q-btn-->
|
||||
</div>
|
||||
|
||||
@@ -9,88 +9,157 @@
|
||||
|
||||
<a ref="download" style="display: none;" target="_blank"></a>
|
||||
|
||||
<q-table
|
||||
class="recent-books-table col"
|
||||
:rows="tableData"
|
||||
row-key="key"
|
||||
:columns="columns"
|
||||
:pagination="pagination"
|
||||
separator="cell"
|
||||
hide-bottom
|
||||
virtual-scroll
|
||||
dense
|
||||
>
|
||||
<template #header="props">
|
||||
<q-tr :props="props">
|
||||
<q-th key="num" class="td-mp" style="width: 25px" :props="props">
|
||||
<span v-html="props.cols[0].label"></span>
|
||||
</q-th>
|
||||
<q-th key="date" class="td-mp break-word" style="width: 77px" :props="props">
|
||||
<span v-html="props.cols[1].label"></span>
|
||||
</q-th>
|
||||
<q-th key="desc" class="td-mp" style="width: 332px" :props="props" colspan="4">
|
||||
<q-input ref="input" v-model="search"
|
||||
outlined dense rounded style="position: absolute; top: 6px; left: 90px; width: 380px" bg-color="white"
|
||||
placeholder="Найти"
|
||||
@click.stop
|
||||
>
|
||||
<template #append>
|
||||
<q-icon v-if="search !== ''" name="la la-times" class="cursor-pointer" @click.stop="resetSearch" />
|
||||
</template>
|
||||
</q-input>
|
||||
<span v-html="props.cols[2].label"></span>
|
||||
</q-th>
|
||||
</q-tr>
|
||||
</template>
|
||||
<div id="vs-container" ref="vsContainer" class="recent-books-scroll col">
|
||||
<div ref="header" class="scroll-header row bg-blue-2">
|
||||
<q-btn class="tool-button" round @click="showSameBookClick">
|
||||
<q-icon name="la la-caret-right" class="icon" :class="{'expanded-icon': showSameBook}" color="green-8" size="24px" />
|
||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||
Показать/скрыть версии книг
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
|
||||
<template #body="props">
|
||||
<q-tr :props="props">
|
||||
<q-td key="num" :props="props" class="td-mp" auto-width>
|
||||
<div class="break-word" style="width: 25px">
|
||||
{{ props.row.num }}
|
||||
<q-btn class="tool-button" round @click="scrollToBegin">
|
||||
<q-icon name="la la-arrow-up" color="green-8" size="24px" />
|
||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||
В начало списка
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
|
||||
<q-btn class="tool-button" round @click="scrollToEnd">
|
||||
<q-icon name="la la-arrow-down" color="green-8" size="24px" />
|
||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||
В конец списка
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
|
||||
<q-btn class="tool-button" round @click="scrollToActiveBook">
|
||||
<q-icon name="la la-location-arrow" color="green-8" size="24px" />
|
||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||
На текущую книгу
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
|
||||
<q-input
|
||||
ref="input"
|
||||
v-model="search"
|
||||
class="q-ml-sm q-mt-xs"
|
||||
outlined dense
|
||||
style="width: 185px"
|
||||
bg-color="white"
|
||||
placeholder="Найти"
|
||||
@click.stop
|
||||
>
|
||||
<template #append>
|
||||
<q-icon v-if="search !== ''" name="la la-times" class="cursor-pointer" @click.stop="resetSearch" />
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<q-select
|
||||
ref="sortMethod"
|
||||
v-model="sortMethod"
|
||||
class="q-ml-sm q-mt-xs"
|
||||
:options="sortMethodOptions"
|
||||
style="width: 180px"
|
||||
bg-color="white"
|
||||
dropdown-icon="la la-angle-down la-sm"
|
||||
outlined dense emit-value map-options display-value-sanitize options-sanitize
|
||||
options-html display-value-html
|
||||
|
||||
@update:model-value="sortMethodSelected"
|
||||
>
|
||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||
Метод сортировки
|
||||
</q-tooltip>
|
||||
|
||||
<template #selected-item="scope">
|
||||
<div style="height: 28px; padding-top: 2px; overflow: hidden" v-html="scope.opt.label" />
|
||||
</template>
|
||||
</q-select>
|
||||
</div>
|
||||
|
||||
<q-virtual-scroll
|
||||
ref="virtualScroll"
|
||||
v-slot="{ item, index }"
|
||||
:items="tableData"
|
||||
scroll-target="#vs-container"
|
||||
virtual-scroll-item-size="80"
|
||||
@virtual-scroll="onScroll"
|
||||
>
|
||||
<div class="table-row row" :class="{even: index % 2 > 0, 'active-book': item.active, 'active-parent-book': item.activeParent}">
|
||||
<div v-show="item.inGroup" class="row-part column justify-center items-center" style="width: 40px">
|
||||
<q-icon name="la la-code-branch" size="24px" style="color: green" />
|
||||
</div>
|
||||
|
||||
<div class="row-part column justify-center items-stretch" style="width: 80px">
|
||||
<div class="col row justify-center items-center clickable" @click="loadBook(item)">
|
||||
<q-icon name="la la-book" size="40px" style="color: #dddddd" />
|
||||
</div>
|
||||
</q-td>
|
||||
|
||||
<q-td key="date" auto-width :props="props" class="td-mp clickable" @click="loadBook(props.row.url)">
|
||||
<div class="break-word" style="width: 68px">
|
||||
{{ props.row.touchDate }}<br>
|
||||
{{ props.row.touchTime }}
|
||||
<div v-show="!showSameBook && item.group && item.group.length > 0" class="row justify-center" style="font-size: 70%">
|
||||
{{ (item.group ? item.group.length + 1 : 0) }} верси{{ wordEnding((item.group ? item.group.length + 1 : 0), 1) }}
|
||||
</div>
|
||||
</q-td>
|
||||
</div>
|
||||
|
||||
<q-td key="desc" auto-width :props="props" class="td-mp clickable" @click="loadBook(props.row.url)">
|
||||
<div class="break-word" style="width: 332px; font-size: 90%">
|
||||
<div style="color: green">
|
||||
{{ props.row.desc.author }}
|
||||
<div class="row-part column items-stretch clickable break-word" :style="{ 'width': (350 - 40*(+item.inGroup)) + 'px' }" style="font-size: 75%" @click="loadBook(item)">
|
||||
<div class="col" style="border: 1px solid #cccccc; border-bottom: 0; padding: 4px" :style="{ 'width': (340 - 40*(+item.inGroup)) + 'px' }">
|
||||
<div class="text-green-10" style="font-size: 105%">
|
||||
{{ item.desc.author }}
|
||||
</div>
|
||||
<div>{{ props.row.desc.title }}</div>
|
||||
<div class="read-bar" :style="`width: ${332*props.row.readPart}px`"></div>
|
||||
<div>{{ item.desc.title }}</div>
|
||||
</div>
|
||||
</q-td>
|
||||
|
||||
<q-td key="links" :props="props" class="td-mp" auto-width>
|
||||
<div class="break-word" style="width: 75px; font-size: 90%">
|
||||
<a v-show="isUrl(props.row.url)" :href="props.row.url" target="_blank">Оригинал</a><br>
|
||||
<a :href="props.row.path" @click.prevent="downloadBook(props.row.path, props.row.fullTitle)">Скачать FB2</a>
|
||||
</div>
|
||||
</q-td>
|
||||
<div class="row" style="font-size: 10px">
|
||||
<div class="row justify-center items-center row-info-top" style="width: 60px">
|
||||
{{ item.desc.textLen }}
|
||||
</div>
|
||||
|
||||
<q-td key="close" :props="props" class="td-mp" auto-width>
|
||||
<div style="width: 38px">
|
||||
<q-btn
|
||||
dense
|
||||
style="width: 30px; height: 30px; padding: 7px 0 7px 0; margin-left: 4px"
|
||||
@click="handleDel(props.row.key)"
|
||||
>
|
||||
<q-icon class="la la-times" size="14px" />
|
||||
</q-btn>
|
||||
<div class="row items-center row-info-top" :style="`width: ${(220 - 40*(+item.inGroup))}px; padding: 1px`">
|
||||
<div class="read-bar" :style="`width: ${100*item.readPart}%`"></div>
|
||||
</div>
|
||||
|
||||
<div class="row justify-center items-center row-info-top" style="width: 59px">
|
||||
{{ item.desc.perc }}
|
||||
</div>
|
||||
<div class="row-info-top" style="width: 1px">
|
||||
</div>
|
||||
</div>
|
||||
</q-td>
|
||||
<q-td key="last" :props="props" class="no-mp">
|
||||
</q-td>
|
||||
</q-tr>
|
||||
</template>
|
||||
</q-table>
|
||||
|
||||
<div class="row" style="font-size: 10px" :style="{ 'width': (340 - 40*(+item.inGroup)) + 'px' }">
|
||||
<div class="row justify-center items-center row-info-bottom" style="width: 30px">
|
||||
{{ item.num }}
|
||||
</div>
|
||||
<div class="col row">
|
||||
<div class="row justify-center items-center row-info-bottom time-info" style="width: 50%">
|
||||
Загружен: {{ item.loadTime }}
|
||||
</div>
|
||||
<div class="row justify-center items-center row-info-bottom time-info" style="width: 50%">
|
||||
Читался: {{ item.touchTime }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-info-bottom" style="width: 1px">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row-part column justify-center" style="width: 80px; font-size: 75%">
|
||||
<div>
|
||||
<a v-show="isUrl(item.url)" :href="item.url" target="_blank">Оригинал</a><br><br>
|
||||
<a :href="item.path" @click.prevent="downloadBook(item.path, item.fullTitle)">Скачать FB2</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row-part column justify-center">
|
||||
<q-btn
|
||||
dense
|
||||
style="width: 30px; height: 30px; padding: 7px 0 7px 0; margin-left: 4px"
|
||||
@click="handleDel(item.key)"
|
||||
>
|
||||
<q-icon class="la la-times" size="14px" />
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</q-virtual-scroll>
|
||||
</div>
|
||||
</Window>
|
||||
</template>
|
||||
|
||||
@@ -99,9 +168,10 @@
|
||||
import vueComponent from '../../vueComponent.js';
|
||||
|
||||
import path from 'path-browserify';
|
||||
//import _ from 'lodash';
|
||||
import _ from 'lodash';
|
||||
|
||||
import * as utils from '../../../share/utils';
|
||||
import LockQueue from '../../../share/LockQueue';
|
||||
import Window from '../../share/Window.vue';
|
||||
import bookManager from '../share/bookManager';
|
||||
import readerApi from '../../../api/reader';
|
||||
@@ -111,9 +181,15 @@ const componentOptions = {
|
||||
Window,
|
||||
},
|
||||
watch: {
|
||||
search: function() {
|
||||
search() {
|
||||
this.updateTableData();
|
||||
}
|
||||
},
|
||||
sortMethod() {
|
||||
this.updateTableData();
|
||||
},
|
||||
settings() {
|
||||
this.loadSettings();
|
||||
},
|
||||
},
|
||||
};
|
||||
class RecentBooksPage {
|
||||
@@ -122,52 +198,18 @@ class RecentBooksPage {
|
||||
loading = false;
|
||||
search = '';
|
||||
tableData = [];
|
||||
columns = [];
|
||||
pagination = {};
|
||||
sortMethod = '';
|
||||
showSameBook = false;
|
||||
|
||||
created() {
|
||||
this.firstInit = true;
|
||||
this.pagination = {rowsPerPage: 0};
|
||||
this.commit = this.$store.commit;
|
||||
|
||||
this.columns = [
|
||||
{
|
||||
name: 'num',
|
||||
label: '#',
|
||||
align: 'center',
|
||||
sortable: true,
|
||||
field: 'num',
|
||||
},
|
||||
{
|
||||
name: 'date',
|
||||
label: 'Время<br>просм.',
|
||||
align: 'left',
|
||||
field: 'touchDateTime',
|
||||
sortable: true,
|
||||
sort: (a, b, rowA, rowB) => rowA.touchDateTime - rowB.touchDateTime,
|
||||
},
|
||||
{
|
||||
name: 'desc',
|
||||
label: 'Название',
|
||||
align: 'left',
|
||||
field: 'descString',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: 'links',
|
||||
label: '',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'close',
|
||||
label: '',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'last',
|
||||
label: '',
|
||||
align: 'left',
|
||||
},
|
||||
];
|
||||
this.lastScrollTop1 = 0;
|
||||
this.lastScrollTop2 = 0;
|
||||
|
||||
this.lock = new LockQueue(100);
|
||||
|
||||
this.loadSettings();
|
||||
}
|
||||
|
||||
init() {
|
||||
@@ -176,89 +218,185 @@ class RecentBooksPage {
|
||||
this.$nextTick(() => {
|
||||
//this.$refs.input.focus();//плохо на планшетах
|
||||
});
|
||||
(async() => {//подгрузка списка
|
||||
if (this.initing)
|
||||
return;
|
||||
this.initing = true;
|
||||
|
||||
if (this.firstInit) {//для отзывчивости
|
||||
await this.updateTableData(20);
|
||||
this.firstInit = false;
|
||||
}
|
||||
await utils.sleep(50);
|
||||
this.inited = true;
|
||||
|
||||
(async() => {
|
||||
this.showBar();
|
||||
await this.updateTableData();
|
||||
|
||||
this.initing = false;
|
||||
await this.scrollToActiveBook();
|
||||
})();
|
||||
}
|
||||
|
||||
async updateTableData(limit) {
|
||||
while (this.updating) await utils.sleep(100);
|
||||
this.updating = true;
|
||||
let result = [];
|
||||
loadSettings() {
|
||||
const settings = this.settings;
|
||||
this.showSameBook = settings.recentShowSameBook;
|
||||
this.sortMethod = settings.recentSortMethod || 'loadTimeDesc';
|
||||
}
|
||||
|
||||
this.loading = !!limit;
|
||||
const sorted = bookManager.getSortedRecent();
|
||||
get settings() {
|
||||
return this.$store.state.reader.settings;
|
||||
}
|
||||
|
||||
let num = 0;
|
||||
for (let i = 0; i < sorted.length; i++) {
|
||||
const book = sorted[i];
|
||||
if (book.deleted)
|
||||
continue;
|
||||
async updateTableData() {
|
||||
if (!this.inited)
|
||||
return;
|
||||
|
||||
num++;
|
||||
if (limit && result.length >= limit)
|
||||
break;
|
||||
await this.lock.get();
|
||||
try {
|
||||
let result = [];
|
||||
|
||||
let d = new Date();
|
||||
d.setTime(book.touchTime);
|
||||
const t = utils.formatDate(d).split(' ');
|
||||
const sorted = bookManager.getSortedRecent();
|
||||
const activeBook = bookManager.mostRecentBook();
|
||||
|
||||
let readPart = 0;
|
||||
let perc = '';
|
||||
let textLen = '';
|
||||
const p = (book.bookPosSeen ? book.bookPosSeen : (book.bookPos ? book.bookPos : 0));
|
||||
if (book.textLength) {
|
||||
readPart = p/book.textLength;
|
||||
perc = ` [${(readPart*100).toFixed(2)}%]`;
|
||||
textLen = ` ${Math.round(book.textLength/1000)}k`;
|
||||
//подготовка полей
|
||||
for (const book of sorted) {
|
||||
if (book.deleted)
|
||||
continue;
|
||||
|
||||
let d = new Date();
|
||||
d.setTime(book.touchTime);
|
||||
const touchTime = utils.formatDate(d);
|
||||
const loadTimeRaw = (book.loadTime ? book.loadTime : 0);//book.addTime);
|
||||
d.setTime(loadTimeRaw);
|
||||
const loadTime = utils.formatDate(d);
|
||||
|
||||
let readPart = 0;
|
||||
let perc = '';
|
||||
let textLen = '';
|
||||
const p = (book.bookPosSeen ? book.bookPosSeen : (book.bookPos ? book.bookPos : 0));
|
||||
if (book.textLength) {
|
||||
readPart = p/book.textLength;
|
||||
perc = `${(readPart*100).toFixed(2)}%`;
|
||||
textLen = `${Math.floor(readPart*book.textLength/1000)}/${Math.floor(book.textLength/1000)}`;
|
||||
}
|
||||
|
||||
const bt = utils.getBookTitle(book.fb2);
|
||||
|
||||
let title = bt.bookTitle;
|
||||
title = (title ? `"${title}"`: '');
|
||||
const author = (bt.author ? bt.author : (bt.bookTitle ? bt.bookTitle : (book.uploadFileName ? book.uploadFileName : book.url)));
|
||||
|
||||
result.push({
|
||||
touchTime,
|
||||
loadTime,
|
||||
desc: {
|
||||
author,
|
||||
title,
|
||||
perc,
|
||||
textLen,
|
||||
},
|
||||
readPart,
|
||||
url: book.url,
|
||||
path: book.path,
|
||||
fullTitle: bt.fullTitle,
|
||||
key: book.key,
|
||||
sameBookKey: book.sameBookKey,
|
||||
active: (activeBook.key == book.key),
|
||||
activeParent: false,
|
||||
inGroup: false,
|
||||
|
||||
//для сортировки
|
||||
loadTimeRaw,
|
||||
touchTimeRaw: book.touchTime,
|
||||
});
|
||||
}
|
||||
|
||||
const bt = utils.getBookTitle(book.fb2);
|
||||
//нумерация
|
||||
result.sort((a, b) => b.loadTimeRaw - a.loadTimeRaw);
|
||||
let num = 0;
|
||||
for (let i = result.length - 1; i >= 0; i--) {
|
||||
num++;
|
||||
result[i].num = num;
|
||||
}
|
||||
|
||||
let title = bt.bookTitle;
|
||||
title = (title ? `"${title}"`: '');
|
||||
const author = (bt.author ? bt.author : (bt.bookTitle ? bt.bookTitle : book.url));
|
||||
//фильтрация
|
||||
const search = this.search;
|
||||
if (search) {
|
||||
result = result.filter(item => {
|
||||
return !search ||
|
||||
item.touchTime.includes(search) ||
|
||||
item.loadTime.includes(search) ||
|
||||
item.desc.title.toLowerCase().includes(search.toLowerCase()) ||
|
||||
item.desc.author.toLowerCase().includes(search.toLowerCase())
|
||||
});
|
||||
}
|
||||
|
||||
result.push({
|
||||
num,
|
||||
touchDateTime: book.touchTime,
|
||||
touchDate: t[0],
|
||||
touchTime: t[1],
|
||||
desc: {
|
||||
author,
|
||||
title: `${title}${perc}${textLen}`,
|
||||
},
|
||||
readPart,
|
||||
descString: `${author}${title}${perc}${textLen}`,//для сортировки
|
||||
url: book.url,
|
||||
path: book.path,
|
||||
fullTitle: bt.fullTitle,
|
||||
key: book.key,
|
||||
});
|
||||
//сортировка
|
||||
switch (this.sortMethod) {
|
||||
case 'loadTimeDesc':
|
||||
result.sort((a, b) => b.loadTimeRaw - a.loadTimeRaw);
|
||||
break;
|
||||
case 'loadTimeAsc':
|
||||
result.sort((a, b) => a.loadTimeRaw - b.loadTimeRaw);
|
||||
break;
|
||||
case 'touchTimeDesc':
|
||||
result.sort((a, b) => b.touchTimeRaw - a.touchTimeRaw);
|
||||
break;
|
||||
case 'touchTimeAsc':
|
||||
result.sort((a, b) => a.touchTimeRaw - b.touchTimeRaw);
|
||||
break;
|
||||
case 'authorDesc':
|
||||
result.sort((a, b) => b.desc.author.localeCompare(a.desc.author));
|
||||
break;
|
||||
case 'authorAsc':
|
||||
result.sort((a, b) => a.desc.author.localeCompare(b.desc.author));
|
||||
break;
|
||||
case 'titleDesc':
|
||||
result.sort((a, b) => b.desc.title.localeCompare(a.desc.title));
|
||||
break;
|
||||
case 'titleAsc':
|
||||
result.sort((a, b) => a.desc.title.localeCompare(b.desc.title));
|
||||
break;
|
||||
}
|
||||
|
||||
//группировка
|
||||
const groups = {};
|
||||
const parents = {};
|
||||
let newResult = [];
|
||||
for (const book of result) {
|
||||
if (book.sameBookKey !== undefined) {
|
||||
if (!groups[book.sameBookKey]) {
|
||||
groups[book.sameBookKey] = [];
|
||||
parents[book.sameBookKey] = book;
|
||||
|
||||
book.group = groups[book.sameBookKey];
|
||||
newResult.push(book);
|
||||
} else {
|
||||
book.inGroup = true;
|
||||
if (book.active)
|
||||
parents[book.sameBookKey].activeParent = true;
|
||||
|
||||
groups[book.sameBookKey].push(book);
|
||||
}
|
||||
} else {
|
||||
newResult.push(book);
|
||||
}
|
||||
}
|
||||
result = newResult;
|
||||
|
||||
//showSameBook
|
||||
if (this.showSameBook) {
|
||||
newResult = [];
|
||||
for (const book of result) {
|
||||
newResult.push(book);
|
||||
if (book.group) {
|
||||
for (const sameBook of book.group) {
|
||||
newResult.push(sameBook);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = newResult;
|
||||
}
|
||||
|
||||
//другие стадии
|
||||
//.....
|
||||
|
||||
this.tableData = result;
|
||||
} finally {
|
||||
this.lock.ret();
|
||||
}
|
||||
|
||||
const search = this.search;
|
||||
result = result.filter(item => {
|
||||
return !search ||
|
||||
item.touchTime.includes(search) ||
|
||||
item.touchDate.includes(search) ||
|
||||
item.desc.title.toLowerCase().includes(search.toLowerCase()) ||
|
||||
item.desc.author.toLowerCase().includes(search.toLowerCase())
|
||||
});
|
||||
|
||||
this.tableData = result;
|
||||
this.updating = false;
|
||||
}
|
||||
|
||||
resetSearch() {
|
||||
@@ -266,19 +404,22 @@ class RecentBooksPage {
|
||||
this.$refs.input.focus();
|
||||
}
|
||||
|
||||
wordEnding(num) {
|
||||
const endings = ['', 'а', 'и', 'и', 'и', '', '', '', '', ''];
|
||||
wordEnding(num, type = 0) {
|
||||
const endings = [
|
||||
['ов', '', 'а', 'а', 'а', 'ов', 'ов', 'ов', 'ов', 'ов'],
|
||||
['й', 'я', 'и', 'и', 'и', 'й', 'й', 'й', 'й', 'й']
|
||||
];
|
||||
const deci = num % 100;
|
||||
if (deci > 10 && deci < 20) {
|
||||
return '';
|
||||
return endings[type][0];
|
||||
} else {
|
||||
return endings[num % 10];
|
||||
return endings[type][num % 10];
|
||||
}
|
||||
}
|
||||
|
||||
get header() {
|
||||
const len = (this.tableData ? this.tableData.length : 0);
|
||||
return `${(this.search ? 'Найдено' : 'Всего')} ${len} книг${this.wordEnding(len)}`;
|
||||
return `${(this.search ? 'Найдено' : 'Всего')} ${len} файл${this.wordEnding(len)}`;
|
||||
}
|
||||
|
||||
async downloadBook(fb2path, fullTitle) {
|
||||
@@ -311,8 +452,8 @@ class RecentBooksPage {
|
||||
this.close();
|
||||
}
|
||||
|
||||
loadBook(url) {
|
||||
this.$emit('load-book', {url});
|
||||
loadBook(row) {
|
||||
this.$emit('load-book', {url: row.url, path: row.path});
|
||||
this.close();
|
||||
}
|
||||
|
||||
@@ -323,6 +464,111 @@ class RecentBooksPage {
|
||||
return false;
|
||||
}
|
||||
|
||||
showBar() {
|
||||
this.lastScrollTop1 = this.$refs.vsContainer.scrollTop;
|
||||
this.$refs.header.style.position = 'sticky';
|
||||
this.$refs.header.style.top = 0;
|
||||
}
|
||||
|
||||
onScroll() {
|
||||
const curScrollTop = this.$refs.vsContainer.scrollTop;
|
||||
|
||||
if (this.lockScroll) {
|
||||
this.lastScrollTop1 = curScrollTop;
|
||||
return;
|
||||
}
|
||||
|
||||
if (curScrollTop - this.lastScrollTop1 > 100) {
|
||||
this.$refs.header.style.top = `-${this.$refs.header.offsetHeight}px`;
|
||||
this.$refs.header.style.transition = 'top 0.2s ease 0s';
|
||||
|
||||
this.lastScrollTop1 = curScrollTop;
|
||||
} else if (curScrollTop - this.lastScrollTop2 < 0) {
|
||||
this.$refs.header.style.position = 'sticky';
|
||||
this.$refs.header.style.top = 0;
|
||||
|
||||
this.lastScrollTop1 = curScrollTop;
|
||||
}
|
||||
|
||||
this.lastScrollTop2 = curScrollTop;
|
||||
}
|
||||
|
||||
showSameBookClick() {
|
||||
this.showSameBook = !this.showSameBook;
|
||||
|
||||
const newSettings = _.cloneDeep(this.settings);
|
||||
newSettings.recentShowSameBook = this.showSameBook;
|
||||
this.commit('reader/setSettings', newSettings);
|
||||
|
||||
this.updateTableData();
|
||||
}
|
||||
|
||||
sortMethodSelected() {
|
||||
const newSettings = _.cloneDeep(this.settings);
|
||||
newSettings.recentSortMethod = this.sortMethod;
|
||||
this.commit('reader/setSettings', newSettings);
|
||||
}
|
||||
|
||||
async scrollToActiveBook() {
|
||||
this.lockScroll = true;
|
||||
try {
|
||||
let activeIndex = -1;
|
||||
let activeParentIndex = -1;
|
||||
for (let i = 0; i < this.tableData.length; i++) {
|
||||
const book = this.tableData[i];
|
||||
if (book.active)
|
||||
activeIndex = i;
|
||||
if (book.activeParent)
|
||||
activeParentIndex = i;
|
||||
|
||||
if (activeIndex >= 0 && activeParentIndex >= 0)
|
||||
break;
|
||||
}
|
||||
|
||||
const index = (activeIndex >= 0 ? activeIndex : activeParentIndex);
|
||||
if (index >= 0) {
|
||||
this.$refs.virtualScroll.scrollTo(index, 'center');
|
||||
}
|
||||
} finally {
|
||||
await utils.sleep(100);
|
||||
this.lockScroll = false;
|
||||
}
|
||||
}
|
||||
|
||||
async scrollToBegin() {
|
||||
this.lockScroll = true;
|
||||
try {
|
||||
this.$refs.virtualScroll.scrollTo(0, 'center');
|
||||
} finally {
|
||||
await utils.sleep(100);
|
||||
this.lockScroll = false;
|
||||
}
|
||||
}
|
||||
|
||||
async scrollToEnd() {
|
||||
this.lockScroll = true;
|
||||
try {
|
||||
this.$refs.virtualScroll.scrollTo(this.tableData.length, 'center');
|
||||
} finally {
|
||||
await utils.sleep(100);
|
||||
this.lockScroll = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
get sortMethodOptions() {
|
||||
return [
|
||||
{label: '<span style="font-size: 150%">↑</span> Время загрузки', value: 'loadTimeDesc'},
|
||||
{label: '<span style="font-size: 150%">↓</span> Время загрузки', value: 'loadTimeAsc'},
|
||||
{label: '<span style="font-size: 150%">↑</span> Время чтения', value: 'touchTimeDesc'},
|
||||
{label: '<span style="font-size: 150%">↓</span> Время чтения', value: 'touchTimeAsc'},
|
||||
{label: '<span style="font-size: 150%">↑</span> Автор', value: 'authorDesc'},
|
||||
{label: '<span style="font-size: 150%">↓</span> Автор', value: 'authorAsc'},
|
||||
{label: '<span style="font-size: 150%">↑</span> Название', value: 'titleDesc'},
|
||||
{label: '<span style="font-size: 150%">↓</span> Название', value: 'titleAsc'},
|
||||
];
|
||||
}
|
||||
|
||||
close() {
|
||||
this.$emit('recent-books-close');
|
||||
}
|
||||
@@ -340,29 +586,33 @@ export default vueComponent(RecentBooksPage);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.recent-books-table {
|
||||
width: 600px;
|
||||
.recent-books-scroll {
|
||||
width: 573px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.scroll-header {
|
||||
height: 50px;
|
||||
position: sticky;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
border-bottom: 2px solid #aaaaaa;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.row-part {
|
||||
padding: 4px 4px 4px 4px;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.td-mp {
|
||||
margin: 0 !important;
|
||||
padding: 4px 4px 4px 4px !important;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.no-mp {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
border: 0;
|
||||
border-left: 1px solid #ddd !important;
|
||||
}
|
||||
|
||||
.break-word {
|
||||
line-height: 180%;
|
||||
overflow-wrap: break-word;
|
||||
@@ -370,25 +620,55 @@ export default vueComponent(RecentBooksPage);
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.even {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
.active-book {
|
||||
background-color: #b0f0b0 !important;
|
||||
}
|
||||
|
||||
.active-parent-book {
|
||||
background-color: #ffbbbb !important;
|
||||
}
|
||||
|
||||
.icon {
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.expanded-icon {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.tool-button {
|
||||
min-width: 30px;
|
||||
width: 30px;
|
||||
min-height: 30px;
|
||||
height: 30px;
|
||||
margin: 10px 6px 0px 3px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.row-info-bottom {
|
||||
line-height: 110%;
|
||||
border-left: 1px solid #cccccc;
|
||||
border-bottom: 1px solid #cccccc;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.row-info-top {
|
||||
line-height: 110%;
|
||||
border: 1px solid #cccccc;
|
||||
border-right: 0;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.time-info, .row-info-top {
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
.read-bar {
|
||||
height: 3px;
|
||||
background-color: #aaaaaa;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.recent-books-table .q-table__middle {
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.recent-books-table thead tr:first-child th {
|
||||
position: sticky;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
background-color: #c1f4cd;
|
||||
}
|
||||
.recent-books-table tr:nth-child(even) {
|
||||
background-color: #f8f8f8;
|
||||
height: 4px;
|
||||
background-color: #bbbbbb;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,12 +8,10 @@
|
||||
<span v-show="initStep">{{ initPercentage }}%</span>
|
||||
|
||||
<div v-show="!initStep" class="input">
|
||||
<!--input ref="input"
|
||||
placeholder="что ищем"
|
||||
:value="needle" @input="needle = $event.target.value"/-->
|
||||
<q-input ref="input" v-model="needle"
|
||||
<q-input
|
||||
ref="input" v-model="needle"
|
||||
class="col" outlined dense
|
||||
placeholder="что ищем"
|
||||
placeholder="Найти"
|
||||
@keydown="inputKeyDown"
|
||||
/>
|
||||
<div style="position: absolute; right: 10px; margin-top: 10px; font-size: 16px;">
|
||||
@@ -108,7 +106,7 @@ class SearchPage {
|
||||
this.parsed = parsed;
|
||||
}
|
||||
|
||||
this.header = 'Найти';
|
||||
this.header = 'Поиск в тексте';
|
||||
await this.$nextTick();
|
||||
this.$refs.input.focus();
|
||||
this.$refs.input.select();
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<div class="part-header">Показывать кнопки панели</div>
|
||||
|
||||
<div class="item row" v-for="item in toolButtons" :key="item.name" v-show="item.name != 'libs' || mode == 'liberama.top'">
|
||||
<div class="label-3"></div>
|
||||
<div class="col row">
|
||||
<q-checkbox size="xs" v-model="showToolButton[item.name]" :label="rstore.readerActions[item.name]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Window ref="window" height="95%" width="600px" @close="close">
|
||||
<Window ref="window" width="600px" @close="close">
|
||||
<template #header>
|
||||
Настройки
|
||||
</template>
|
||||
@@ -24,7 +24,7 @@
|
||||
<div v-show="tabsScrollable" class="q-pt-lg" />
|
||||
<q-tab class="tab" name="profiles" icon="la la-users" label="Профили" />
|
||||
<q-tab class="tab" name="view" icon="la la-eye" label="Вид" />
|
||||
<q-tab class="tab" name="buttons" icon="la la-grip-horizontal" label="Кнопки" />
|
||||
<q-tab class="tab" name="toolbar" icon="la la-grip-horizontal" label="Панель" />
|
||||
<q-tab class="tab" name="keys" icon="la la-gamepad" label="Управление" />
|
||||
<q-tab class="tab" name="pagemove" icon="la la-school" label="Листание" />
|
||||
<q-tab class="tab" name="convert" icon="la la-magic" label="Конвертир." />
|
||||
@@ -82,8 +82,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- Кнопки ---------------------------------------------------------------------->
|
||||
<div v-if="selectedTab == 'buttons'" class="fit tab-panel">
|
||||
@@include('./ButtonsTab.inc');
|
||||
<div v-if="selectedTab == 'toolbar'" class="fit tab-panel">
|
||||
@@include('./ToolBarTab.inc');
|
||||
</div>
|
||||
<!-- Управление ------------------------------------------------------------------>
|
||||
<div v-if="selectedTab == 'keys'" class="fit column">
|
||||
@@ -702,11 +702,11 @@ export default vueComponent(SettingsPage);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.label-1, .label-7 {
|
||||
.label-1, .label-3, .label-7 {
|
||||
width: 75px;
|
||||
}
|
||||
|
||||
.label-2, .label-3, .label-4, .label-5 {
|
||||
.label-2, .label-4, .label-5 {
|
||||
width: 110px;
|
||||
}
|
||||
|
||||
|
||||
18
client/components/Reader/SettingsPage/ToolBarTab.inc
Normal file
18
client/components/Reader/SettingsPage/ToolBarTab.inc
Normal file
@@ -0,0 +1,18 @@
|
||||
<div class="part-header">Отображение</div>
|
||||
|
||||
<div class="item row no-wrap">
|
||||
<div class="label-3"></div>
|
||||
<q-checkbox size="xs" v-model="toolBarHideOnScroll" label="Скрывать/показывать панель при прокрутке" >
|
||||
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
||||
Скрывать/показывть панель при прокрутке текста вперед/назад
|
||||
</q-tooltip>
|
||||
</q-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="part-header">Показывать кнопки</div>
|
||||
|
||||
<div class="item row no-wrap" v-for="item in toolButtons" :key="item.name" v-show="item.name != 'libs' || mode == 'liberama.top'">
|
||||
<div class="label-3"></div>
|
||||
<q-checkbox size="xs" v-model="showToolButton[item.name]" :label="rstore.readerActions[item.name]"
|
||||
/>
|
||||
</div>
|
||||
@@ -13,7 +13,7 @@
|
||||
ref="input"
|
||||
v-model="search"
|
||||
class="q-ml-sm col"
|
||||
outlined dense rounded
|
||||
outlined dense
|
||||
bg-color="grey-4"
|
||||
placeholder="Найти"
|
||||
@click.stop
|
||||
|
||||
@@ -66,7 +66,14 @@ const componentOptions = {
|
||||
watch: {
|
||||
bookPos: function() {
|
||||
this.$emit('book-pos-changed', {bookPos: this.bookPos, bookPosSeen: this.bookPosSeen});
|
||||
|
||||
this.draw();
|
||||
|
||||
if (this.userBookPosChange) {
|
||||
this.$emit('hide-tool-bar', {show: (this.bookPos == 0 || this.bookPos < this.prevBookPos)});
|
||||
this.prevBookPos = this.bookPos;
|
||||
this.userBookPosChange = false;
|
||||
}
|
||||
},
|
||||
bookPosSeen: function() {
|
||||
this.$emit('book-pos-changed', {bookPos: this.bookPos, bookPosSeen: this.bookPosSeen});
|
||||
@@ -99,6 +106,8 @@ class TextPage {
|
||||
lastBook = null;
|
||||
bookPos = 0;
|
||||
bookPosSeen = null;
|
||||
prevBookPos = 0;
|
||||
userBookPosChange = false;
|
||||
|
||||
fontStyle = null;
|
||||
fontSize = null;
|
||||
@@ -155,7 +164,7 @@ class TextPage {
|
||||
|
||||
this.$root.addEventHook('resize', async() => {
|
||||
this.$nextTick(this.onResize);
|
||||
await utils.sleep(500);
|
||||
await utils.sleep(200);
|
||||
this.$nextTick(this.onResize);
|
||||
});
|
||||
}
|
||||
@@ -499,12 +508,25 @@ class TextPage {
|
||||
}
|
||||
|
||||
async onResize() {
|
||||
if (this.resizing)
|
||||
return;
|
||||
|
||||
this.resizing = true;
|
||||
try {
|
||||
const scrolled = this.doingScrolling;
|
||||
if (scrolled)
|
||||
await this.stopTextScrolling();
|
||||
|
||||
this.calcDrawProps();
|
||||
this.setBackground();
|
||||
this.draw();
|
||||
|
||||
if (scrolled)
|
||||
this.startTextScrolling();
|
||||
} catch (e) {
|
||||
//
|
||||
} finally {
|
||||
this.resizing = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -652,7 +674,7 @@ class TextPage {
|
||||
}
|
||||
|
||||
if (this.book && this.bookPos > 0 && this.bookPos >= this.parsed.textLength) {
|
||||
this.doEnd(true);
|
||||
this.doEnd(true, false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -675,7 +697,7 @@ class TextPage {
|
||||
this.debouncedDrawPageDividerAndOrnament();
|
||||
|
||||
if (this.book && this.linesDown && this.linesDown.length < this.pageLineCount) {
|
||||
this.doEnd(true);
|
||||
this.doEnd(true, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -911,12 +933,14 @@ class TextPage {
|
||||
|
||||
doDown() {
|
||||
if (this.linesDown && this.linesDown.length > this.pageLineCount && this.pageLineCount > 0) {
|
||||
this.userBookPosChange = true;
|
||||
this.bookPos = this.linesDown[1].begin;
|
||||
}
|
||||
}
|
||||
|
||||
doUp() {
|
||||
if (this.linesUp && this.linesUp.length > 1 && this.pageLineCount > 0) {
|
||||
this.userBookPosChange = true;
|
||||
this.bookPos = this.linesUp[1].begin;
|
||||
}
|
||||
}
|
||||
@@ -929,6 +953,7 @@ class TextPage {
|
||||
if (i >= 0 && this.linesDown.length >= 2*i + (this.keepLastToFirst ? 1 : 0)) {
|
||||
this.currentAnimation = this.pageChangeAnimation;
|
||||
this.pageChangeDirectionDown = true;
|
||||
this.userBookPosChange = true;
|
||||
this.bookPos = this.linesDown[i].begin;
|
||||
} else
|
||||
this.doEnd();
|
||||
@@ -944,6 +969,7 @@ class TextPage {
|
||||
if (i >= 0 && this.linesUp.length > i) {
|
||||
this.currentAnimation = this.pageChangeAnimation;
|
||||
this.pageChangeDirectionDown = false;
|
||||
this.userBookPosChange = true;
|
||||
this.bookPos = this.linesUp[i].begin;
|
||||
}
|
||||
}
|
||||
@@ -952,10 +978,11 @@ class TextPage {
|
||||
doHome() {
|
||||
this.currentAnimation = this.pageChangeAnimation;
|
||||
this.pageChangeDirectionDown = false;
|
||||
this.userBookPosChange = true;
|
||||
this.bookPos = 0;
|
||||
}
|
||||
|
||||
doEnd(noAni) {
|
||||
doEnd(noAni, isUser = true) {
|
||||
if (this.parsed.para.length && this.pageLineCount > 0) {
|
||||
let i = this.parsed.para.length - 1;
|
||||
let lastPos = this.parsed.para[i].offset + this.parsed.para[i].length - 1;
|
||||
@@ -966,6 +993,7 @@ class TextPage {
|
||||
if (!noAni)
|
||||
this.currentAnimation = this.pageChangeAnimation;
|
||||
this.pageChangeDirectionDown = true;
|
||||
this.userBookPosChange = isUser;
|
||||
this.bookPos = lines[i].begin;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import sax from '../../../../server/core/sax';
|
||||
import * as utils from '../../../share/utils';
|
||||
|
||||
const maxImageLineCount = 100;
|
||||
const maxParaTextLength = 10000;
|
||||
|
||||
// defaults
|
||||
const defaultSettings = {
|
||||
@@ -226,13 +227,25 @@ export default class BookParser {
|
||||
paraOffset += len;
|
||||
};
|
||||
|
||||
const growParagraph = (text, len) => {
|
||||
const growParagraph = (text, len, textRaw) => {
|
||||
if (paraIndex < 0) {
|
||||
newParagraph();
|
||||
growParagraph(text, len);
|
||||
return;
|
||||
}
|
||||
|
||||
//ограничение на размер куска текста в параграфе
|
||||
if (textRaw && textRaw.length > maxParaTextLength) {
|
||||
while (textRaw.length > 0) {
|
||||
const textPart = textRaw.substring(0, maxParaTextLength);
|
||||
textRaw = textRaw.substring(maxParaTextLength);
|
||||
|
||||
newParagraph();
|
||||
growParagraph(textPart, textPart.length);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (inSubtitle) {
|
||||
curSubtitle.title += text;
|
||||
} else if (inTitle) {
|
||||
@@ -536,7 +549,7 @@ export default class BookParser {
|
||||
tClose += (center ? '</center>' : '');
|
||||
|
||||
if (text != ' ')
|
||||
growParagraph(`${tOpen}${text}${tClose}`, text.length);
|
||||
growParagraph(`${tOpen}${text}${tClose}`, text.length, text);
|
||||
else
|
||||
growParagraph(' ', 1);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import localForage from 'localforage';
|
||||
import path from 'path-browserify';
|
||||
import _ from 'lodash';
|
||||
|
||||
import * as utils from '../../../share/utils';
|
||||
import BookParser from './BookParser';
|
||||
|
||||
const maxDataSize = 500*1024*1024;//compressed bytes
|
||||
const maxRecentLength = 5000;
|
||||
|
||||
//локальный кэш метаданных книг, ограничение maxDataSize
|
||||
const bmMetaStore = localForage.createInstance({
|
||||
@@ -17,9 +19,6 @@ const bmDataStore = localForage.createInstance({
|
||||
});
|
||||
|
||||
//список недавно открытых книг
|
||||
const bmRecentStoreOld = localForage.createInstance({
|
||||
name: 'bmRecentStore'
|
||||
});
|
||||
const bmRecentStoreNew = localForage.createInstance({
|
||||
name: 'bmRecentStoreNew'
|
||||
});
|
||||
@@ -39,7 +38,7 @@ class BookManager {
|
||||
|
||||
this.saveRecentItem = _.debounce(() => {
|
||||
bmRecentStoreNew.setItem('recent-item', this.recentItem);
|
||||
this.recentRev = (this.recentRev < 1000 ? this.recentRev + 1 : 1);
|
||||
this.recentRev = (this.recentRev < maxRecentLength ? this.recentRev + 1 : 1);
|
||||
bmRecentStoreNew.setItem('rev', this.recentRev);
|
||||
}, 200, {maxWait: 300});
|
||||
|
||||
@@ -54,6 +53,9 @@ class BookManager {
|
||||
if (this.recentItem)
|
||||
this.recent[this.recentItem.key] = this.recentItem;
|
||||
|
||||
//конвертируем в новые ключи
|
||||
await this.convertRecent();
|
||||
|
||||
this.recentLastKey = await bmRecentStoreNew.getItem('recent-last-key');
|
||||
if (this.recentLastKey) {
|
||||
const meta = await bmMetaStore.getItem(`bmMeta-${this.recentLastKey}`);
|
||||
@@ -70,6 +72,40 @@ class BookManager {
|
||||
this.loadStored();//no await
|
||||
}
|
||||
|
||||
//TODO: убрать в 2025г
|
||||
async convertRecent() {
|
||||
const converted = await bmRecentStoreNew.getItem('recent-converted');
|
||||
|
||||
if (converted)
|
||||
return;
|
||||
|
||||
const newRecent = {};
|
||||
for (const book of Object.values(this.recent)) {
|
||||
|
||||
if (!book.path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const newKey = this.keyFromPath(book.path);
|
||||
|
||||
newRecent[newKey] = _.cloneDeep(book);
|
||||
newRecent[newKey].key = newKey;
|
||||
if (!newRecent[newKey].loadTime)
|
||||
newRecent[newKey].loadTime = newRecent[newKey].addTime;
|
||||
}
|
||||
|
||||
this.recent = newRecent;
|
||||
|
||||
//console.log(converted);
|
||||
(async() => {
|
||||
await utils.sleep(3000);
|
||||
this.saveRecent();
|
||||
this.emit('recent-changed');
|
||||
this.emit('set-recent');
|
||||
await bmRecentStoreNew.setItem('recent-converted', true);
|
||||
})();
|
||||
}
|
||||
|
||||
//Ленивая асинхронная загрузка bmMetaStore
|
||||
async loadStored() {
|
||||
//даем время для загрузки последней читаемой книги, чтобы не блокировать приложение
|
||||
@@ -196,8 +232,8 @@ class BookManager {
|
||||
|
||||
async addBook(newBook, callback) {
|
||||
let meta = {url: newBook.url, path: newBook.path};
|
||||
meta.key = this.keyFromUrl(meta.url);
|
||||
meta.addTime = Date.now();
|
||||
meta.key = this.keyFromPath(meta.path);
|
||||
meta.addTime = Date.now();//время добавления в кеш
|
||||
|
||||
const cb = (perc) => {
|
||||
const p = Math.round(30*perc/100);
|
||||
@@ -232,10 +268,10 @@ class BookManager {
|
||||
async hasBookParsed(meta) {
|
||||
if (!this.books)
|
||||
return false;
|
||||
if (!meta.url)
|
||||
if (!meta.path)
|
||||
return false;
|
||||
if (!meta.key)
|
||||
meta.key = this.keyFromUrl(meta.url);
|
||||
meta.key = this.keyFromPath(meta.path);
|
||||
|
||||
let book = this.books[meta.key];
|
||||
|
||||
@@ -250,8 +286,12 @@ class BookManager {
|
||||
|
||||
async getBook(meta, callback) {
|
||||
let result = undefined;
|
||||
|
||||
if (!meta.path)
|
||||
return;
|
||||
|
||||
if (!meta.key)
|
||||
meta.key = this.keyFromUrl(meta.url);
|
||||
meta.key = this.keyFromPath(meta.path);
|
||||
|
||||
result = this.books[meta.key];
|
||||
|
||||
@@ -261,11 +301,6 @@ class BookManager {
|
||||
this.books[meta.key] = result;
|
||||
}
|
||||
|
||||
//Если файл на сервере изменился, считаем, что в кеше его нету
|
||||
if (meta.path && result && meta.path != result.path) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result && !result.parsed) {
|
||||
let data = await bmDataStore.getItem(`bmData-${meta.key}`);
|
||||
callback(5);
|
||||
@@ -325,10 +360,20 @@ class BookManager {
|
||||
return result;
|
||||
}
|
||||
|
||||
keyFromUrl(url) {
|
||||
/*keyFromUrl(url) {
|
||||
return utils.stringToHex(url);
|
||||
}*/
|
||||
|
||||
keyFromPath(bookPath) {
|
||||
return path.basename(bookPath);
|
||||
}
|
||||
|
||||
keysEqual(bookPath1, bookPath2) {
|
||||
if (bookPath1 === undefined || bookPath2 === undefined)
|
||||
return false;
|
||||
|
||||
return (this.keyFromPath(bookPath1) === this.keyFromPath(bookPath2));
|
||||
}
|
||||
//-- recent --------------------------------------------------------------
|
||||
async recentSetItem(item = null, skipCheck = false) {
|
||||
const rev = await bmRecentStoreNew.getItem('rev');
|
||||
@@ -369,7 +414,10 @@ class BookManager {
|
||||
|
||||
async setRecentBook(value) {
|
||||
let result = this.metaOnly(value);
|
||||
result.touchTime = Date.now();
|
||||
result.touchTime = Date.now();//время последнего чтения
|
||||
if (!result.loadTime)
|
||||
result.loadTime = Date.now();//время загрузки файла
|
||||
|
||||
result.deleted = 0;
|
||||
|
||||
if (this.recent[result.key]) {
|
||||
@@ -401,7 +449,7 @@ class BookManager {
|
||||
const sorted = this.getSortedRecent();
|
||||
|
||||
let isDel = false;
|
||||
for (let i = 1000; i < sorted.length; i++) {
|
||||
for (let i = maxRecentLength; i < sorted.length; i++) {
|
||||
delete this.recent[sorted[i].key];
|
||||
isDel = true;
|
||||
}
|
||||
@@ -421,7 +469,7 @@ class BookManager {
|
||||
|
||||
let max = 0;
|
||||
let result = null;
|
||||
for (let key in this.recent) {
|
||||
for (const key in this.recent) {
|
||||
const book = this.recent[key];
|
||||
if (!book.deleted && book.touchTime > max) {
|
||||
max = book.touchTime;
|
||||
@@ -452,6 +500,43 @@ class BookManager {
|
||||
return result;
|
||||
}
|
||||
|
||||
findRecentByUrlAndPath(url, bookPath) {
|
||||
if (bookPath) {
|
||||
const key = this.keyFromPath(bookPath);
|
||||
const book = this.recent[key];
|
||||
if (book && !book.deleted)
|
||||
return book;
|
||||
}
|
||||
|
||||
let max = 0;
|
||||
let result = null;
|
||||
|
||||
for (const key in this.recent) {
|
||||
const book = this.recent[key];
|
||||
if (!book.deleted && book.url == url && book.loadTime > max) {
|
||||
max = book.loadTime;
|
||||
result = book;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
findRecentBySameBookKey(sameKey) {
|
||||
let max = 0;
|
||||
let result = null;
|
||||
|
||||
for (const key in this.recent) {
|
||||
const book = this.recent[key];
|
||||
if (!book.deleted && book.sameBookKey == sameKey && book.loadTime > max) {
|
||||
max = book.loadTime;
|
||||
result = book;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async setRecent(value) {
|
||||
const mergedRecent = _.cloneDeep(this.recent);
|
||||
|
||||
|
||||
@@ -1,4 +1,26 @@
|
||||
export const versionHistory = [
|
||||
{
|
||||
version: '0.11.7',
|
||||
releaseDate: '2022-07-12',
|
||||
showUntil: '2022-07-19',
|
||||
content:
|
||||
`
|
||||
<ul>
|
||||
<li>добавлено автосокрытие панели управления при листании, отключается в настройках</li>
|
||||
<li>изменения в окне загруженных книг:</li>
|
||||
<ul>
|
||||
<li>добавлена группировка по версиям файла одной и той же книги</li>
|
||||
<li>группировка происходит по имени загружаемого файла, либо по URL книги</li>
|
||||
<li>добавлены различные методы сортировки списка загруженных книг</li>
|
||||
<li>нумерация всегда осуществляется по времени загрузки</li>
|
||||
</ul>
|
||||
<li>незначительные общие изменения интерфейса, приведение к единому стилю</li>
|
||||
<li>исправления багов</li>
|
||||
</ul>
|
||||
|
||||
`
|
||||
},
|
||||
|
||||
{
|
||||
version: '0.11.6',
|
||||
releaseDate: '2022-07-02',
|
||||
|
||||
@@ -55,6 +55,34 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--------------------------------------------------->
|
||||
<div v-show="type == 'askYesNo'" class="bg-white no-wrap">
|
||||
<div class="header row">
|
||||
<div class="caption col row items-center q-ml-md">
|
||||
<q-icon v-show="caption" class="q-mr-sm" :class="iconColor" :name="iconName" size="28px"></q-icon>
|
||||
<div v-html="caption"></div>
|
||||
</div>
|
||||
<div class="close-icon column justify-center items-center">
|
||||
<q-btn v-close-popup flat round dense>
|
||||
<q-icon name="la la-times" size="18px"></q-icon>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="q-mx-md">
|
||||
<div v-html="message"></div>
|
||||
</div>
|
||||
|
||||
<div class="buttons row justify-end q-pa-md">
|
||||
<q-btn v-close-popup class="q-px-md q-ml-sm" dense no-caps>
|
||||
Нет
|
||||
</q-btn>
|
||||
<q-btn class="q-px-md q-ml-sm" color="primary" dense no-caps @click="okClick">
|
||||
Да
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--------------------------------------------------->
|
||||
<div v-show="type == 'prompt'" class="bg-white no-wrap">
|
||||
<div class="header row">
|
||||
@@ -262,6 +290,23 @@ class StdDialog {
|
||||
});
|
||||
}
|
||||
|
||||
askYesNo(message, caption, opts) {
|
||||
return new Promise((resolve) => {
|
||||
this.init(message, caption, opts);
|
||||
|
||||
this.hideTrigger = () => {
|
||||
if (this.ok) {
|
||||
resolve(true);
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
};
|
||||
|
||||
this.type = 'askYesNo';
|
||||
this.active = true;
|
||||
});
|
||||
}
|
||||
|
||||
prompt(message, caption, opts) {
|
||||
return new Promise((resolve) => {
|
||||
this.enableValidator = false;
|
||||
|
||||
@@ -153,7 +153,7 @@ export default vueComponent(Window);
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(to bottom right, green, #59B04F);
|
||||
background: linear-gradient(to bottom right, #007000, #59B04F);
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
}
|
||||
@@ -161,8 +161,8 @@ export default vueComponent(Window);
|
||||
.header-text {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
color: yellow;
|
||||
text-shadow: 2px 1px 5px black, 2px 2px 5px black;
|
||||
color: #FFFFA0;
|
||||
text-shadow: 2px 2px 5px #005000, 2px 1px 5px #005000;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -174,7 +174,8 @@ export default vueComponent(Window);
|
||||
}
|
||||
|
||||
.close-button:hover {
|
||||
background-color: #69C05F;
|
||||
color: white;
|
||||
background-color: #FF3030;
|
||||
}
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -32,6 +32,8 @@ import {QPopupProxy} from 'quasar/src/components/popup-proxy';
|
||||
import {QDialog} from 'quasar/src/components/dialog';
|
||||
import {QChip} from 'quasar/src/components/chip';
|
||||
import {QTree} from 'quasar/src/components/tree';
|
||||
import {QVirtualScroll} from 'quasar/src/components/virtual-scroll';
|
||||
|
||||
//import {QExpansionItem} from 'quasar/src/components/expansion-item';
|
||||
|
||||
const components = {
|
||||
@@ -62,6 +64,7 @@ const components = {
|
||||
QChip,
|
||||
QTree,
|
||||
//QExpansionItem,
|
||||
QVirtualScroll,
|
||||
};
|
||||
|
||||
//directives
|
||||
|
||||
53
client/share/LockQueue.js
Normal file
53
client/share/LockQueue.js
Normal file
@@ -0,0 +1,53 @@
|
||||
class LockQueue {
|
||||
constructor(queueSize) {
|
||||
this.queueSize = queueSize;
|
||||
this.freed = true;
|
||||
this.waitingQueue = [];
|
||||
}
|
||||
|
||||
//async
|
||||
get(take = true) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.freed) {
|
||||
if (take)
|
||||
this.freed = false;
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.waitingQueue.length < this.queueSize) {
|
||||
this.waitingQueue.push({resolve, reject});
|
||||
} else {
|
||||
reject(new Error('Lock queue is too long'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ret() {
|
||||
if (this.waitingQueue.length) {
|
||||
this.waitingQueue.shift().resolve();
|
||||
} else {
|
||||
this.freed = true;
|
||||
}
|
||||
}
|
||||
|
||||
//async
|
||||
wait() {
|
||||
return this.get(false);
|
||||
}
|
||||
|
||||
retAll() {
|
||||
while (this.waitingQueue.length) {
|
||||
this.waitingQueue.shift().resolve();
|
||||
}
|
||||
}
|
||||
|
||||
errAll(error = 'rejected') {
|
||||
while (this.waitingQueue.length) {
|
||||
this.waitingQueue.shift().reject(new Error(error));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default LockQueue;
|
||||
@@ -21,7 +21,7 @@ const readerActions = {
|
||||
'offlineMode': 'Автономный режим (без интернета)',
|
||||
'contents': 'Оглавление/закладки',
|
||||
'libs': 'Сетевая библиотека',
|
||||
'recentBooks': 'Открыть недавние',
|
||||
'recentBooks': 'Показать загруженные',
|
||||
'switchToolbar': 'Показать/скрыть панель управления',
|
||||
'donate': '',
|
||||
'bookBegin': 'В начало книги',
|
||||
@@ -185,8 +185,12 @@ const settingDefaults = {
|
||||
|
||||
fontShifts: {},
|
||||
showToolButton: {},
|
||||
toolBarHideOnScroll: true,
|
||||
userHotKeys: {},
|
||||
userWallpapers: [],
|
||||
|
||||
recentShowSameBook: false,
|
||||
recentSortMethod: '',
|
||||
};
|
||||
|
||||
for (const font of fonts)
|
||||
@@ -222,9 +226,6 @@ const libsDefaults = {
|
||||
{r: 'http://flibusta.is', s: 'http://flibusta.is', list: [
|
||||
{l: 'http://flibusta.is', c: 'Флибуста | Книжное братство'},
|
||||
]},
|
||||
{r: 'https://flibs.in', s: 'https://flibs.in', list: [
|
||||
{l: 'https://flibs.in', c: 'Flibs'},
|
||||
]},
|
||||
{r: 'http://fantasy-worlds.org', s: 'http://fantasy-worlds.org', list: [
|
||||
{l: 'http://fantasy-worlds.org', c: 'Миры Фэнтези'},
|
||||
]},
|
||||
|
||||
@@ -18,7 +18,7 @@ server {
|
||||
|
||||
server_name liberama.top;
|
||||
|
||||
client_max_body_size 50m;
|
||||
client_max_body_size 100m;
|
||||
proxy_read_timeout 1h;
|
||||
|
||||
gzip on;
|
||||
@@ -63,7 +63,7 @@ server {
|
||||
listen 80;
|
||||
server_name b.liberama.top;
|
||||
|
||||
client_max_body_size 50m;
|
||||
client_max_body_size 100m;
|
||||
proxy_read_timeout 1h;
|
||||
|
||||
gzip on;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
if ! pgrep -x "liberama" > /dev/null ; then
|
||||
sudo -H -u www-data bash -c "cd /var/www; /home/liberama/liberama"
|
||||
sudo -H -u www-data bash -c "cd /var/www; /home/liberama/liberama >/dev/null"
|
||||
else
|
||||
echo "Process 'liberama' already running"
|
||||
fi
|
||||
|
||||
@@ -7,7 +7,7 @@ server {
|
||||
|
||||
server_name omnireader.ru;
|
||||
|
||||
client_max_body_size 50m;
|
||||
client_max_body_size 100m;
|
||||
proxy_read_timeout 1h;
|
||||
|
||||
gzip on;
|
||||
@@ -52,7 +52,7 @@ server {
|
||||
listen 80;
|
||||
server_name old.omnireader.ru;
|
||||
|
||||
client_max_body_size 50m;
|
||||
client_max_body_size 100m;
|
||||
|
||||
gzip on;
|
||||
gzip_min_length 1024;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
sudo -H -u www-data bash -c "cd /var/www; /home/liberama/liberama" & disown
|
||||
sudo -H -u www-data bash -c "cd /var/www; /home/liberama/liberama >/dev/null & disown"
|
||||
sudo service cron start
|
||||
|
||||
186
package-lock.json
generated
186
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "Liberama",
|
||||
"version": "0.11.5",
|
||||
"version": "0.11.7",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "Liberama",
|
||||
"version": "0.11.5",
|
||||
"version": "0.11.7",
|
||||
"hasInstallScript": true,
|
||||
"license": "CC0-1.0",
|
||||
"dependencies": {
|
||||
@@ -21,15 +21,15 @@
|
||||
"fs-extra": "^10.1.0",
|
||||
"he": "^1.2.0",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"jembadb": "^3.0.6",
|
||||
"jembadb": "^3.0.8",
|
||||
"localforage": "^1.10.0",
|
||||
"lodash": "^4.17.21",
|
||||
"minimist": "^1.2.5",
|
||||
"multer": "^1.4.3",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"pako": "^2.0.4",
|
||||
"path-browserify": "^1.0.1",
|
||||
"pidusage": "^3.0.0",
|
||||
"quasar": "^2.3.2",
|
||||
"quasar": "^2.7.5",
|
||||
"safe-buffer": "^5.2.1",
|
||||
"sanitize-html": "^2.5.3",
|
||||
"sjcl": "^1.0.8",
|
||||
@@ -38,8 +38,8 @@
|
||||
"sqlite3": "^5.0.2",
|
||||
"tar-fs": "^2.1.1",
|
||||
"unbzip2-stream": "^1.4.3",
|
||||
"vue": "^3.2.22",
|
||||
"vue-router": "^4.0.12",
|
||||
"vue": "^3.2.37",
|
||||
"vue-router": "^4.1.1",
|
||||
"vuex": "^4.0.2",
|
||||
"vuex-persistedstate": "^4.1.0",
|
||||
"webdav": "^4.7.0",
|
||||
@@ -61,7 +61,7 @@
|
||||
"css-loader": "^6.5.1",
|
||||
"css-minimizer-webpack-plugin": "^4.0.0",
|
||||
"eslint": "^8.19.0",
|
||||
"eslint-plugin-vue": "^9.1.1",
|
||||
"eslint-plugin-vue": "^9.2.0",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"mini-css-extract-plugin": "^2.4.4",
|
||||
"pkg": "^5.5.1",
|
||||
@@ -3239,15 +3239,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/busboy": {
|
||||
"version": "0.2.14",
|
||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz",
|
||||
"integrity": "sha512-InWFDomvlkEj+xWLBfU3AvnbVYqeTWmQopiW0tWWEy5yehYm2YkGEc59sUmw/4ty5Zj/b0WHGs1LgecuBSBGrg==",
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
|
||||
"dependencies": {
|
||||
"dicer": "0.2.5",
|
||||
"readable-stream": "1.1.x"
|
||||
"streamsearch": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
"node": ">=10.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/byte-length": {
|
||||
@@ -4348,18 +4347,6 @@
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/dicer": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz",
|
||||
"integrity": "sha512-FDvbtnq7dzlPz0wyYlOExifDEZcu8h+rErEXgfxqmLfRfC/kJidEFh4+effJRO3P0xmfqyPbSMG0LveNRfTKVg==",
|
||||
"dependencies": {
|
||||
"readable-stream": "1.1.x",
|
||||
"streamsearch": "0.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dir-glob": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
||||
@@ -4785,9 +4772,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-vue": {
|
||||
"version": "9.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.1.1.tgz",
|
||||
"integrity": "sha512-W9n5PB1X2jzC7CK6riG0oAcxjmKrjTF6+keL1rni8n57DZeilx/Fulz+IRJK3lYseLNAygN0I62L7DvioW40Tw==",
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.2.0.tgz",
|
||||
"integrity": "sha512-W2hc+NUXoce8sZtWgZ45miQTy6jNyuSdub5aZ1IBune4JDeAyzucYX0TzkrQ1jMO52sNUDYlCIHDoaNePe0p5g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"eslint-utils": "^3.0.0",
|
||||
@@ -6429,11 +6416,6 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/isarray": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
@@ -6538,9 +6520,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jembadb": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/jembadb/-/jembadb-3.0.6.tgz",
|
||||
"integrity": "sha512-JRs6bTPDMq/UbkI2OweSSGpJX20JiUSQyBJjAOAn1ITPkFGr7q8F3ClEio1gPcmUJryy6EWMX+UnxDS6smJdJA==",
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/jembadb/-/jembadb-3.0.8.tgz",
|
||||
"integrity": "sha512-ukLawbH4IXsKeDTJWNmAEV7D9flQ0m3OMcDSbSjpFWimoyzSn7TyBhWSB8hqmM+9Dyqyh2/nZAcCBu2exXgirQ==",
|
||||
"engines": {
|
||||
"node": ">=14.4.0"
|
||||
}
|
||||
@@ -7242,22 +7224,20 @@
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/multer": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4.tgz",
|
||||
"integrity": "sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw==",
|
||||
"deprecated": "Multer 1.x is affected by CVE-2022-24434. This is fixed in v1.4.4-lts.1 which drops support for versions of Node.js before 6. Please upgrade to at least Node.js 6 and version 1.4.4-lts.1 of Multer. If you need support for older versions of Node.js, we are open to accepting patches that would fix the CVE on the main 1.x release line, whilst maintaining compatibility with Node.js 0.10.",
|
||||
"version": "1.4.5-lts.1",
|
||||
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz",
|
||||
"integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==",
|
||||
"dependencies": {
|
||||
"append-field": "^1.0.0",
|
||||
"busboy": "^0.2.11",
|
||||
"busboy": "^1.0.0",
|
||||
"concat-stream": "^1.5.2",
|
||||
"mkdirp": "^0.5.4",
|
||||
"object-assign": "^4.1.1",
|
||||
"on-finished": "^2.3.0",
|
||||
"type-is": "^1.6.4",
|
||||
"xtend": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10.0"
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/multistream": {
|
||||
@@ -8802,9 +8782,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/quasar": {
|
||||
"version": "2.7.4",
|
||||
"resolved": "https://registry.npmjs.org/quasar/-/quasar-2.7.4.tgz",
|
||||
"integrity": "sha512-8OIa6azm7N6QUPjcZ5AhDCEBha5NnNqt+D1BMIteqaSqkVKFYBf+FMhUCC8R/Tc6Myz85vK7KGPn9tvaC6gXYQ==",
|
||||
"version": "2.7.5",
|
||||
"resolved": "https://registry.npmjs.org/quasar/-/quasar-2.7.5.tgz",
|
||||
"integrity": "sha512-DWI0S+bXASfMSPrB8c/LVsXpA4dF7cBUbaJlcrM+1ioTNBHtiudma2Nhk2SDd5bzk9AYVHh5A8JCZuKqQAXt7g==",
|
||||
"engines": {
|
||||
"node": ">= 10.18.1",
|
||||
"npm": ">= 6.13.4",
|
||||
@@ -8924,17 +8904,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
|
||||
"integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==",
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.1",
|
||||
"isarray": "0.0.1",
|
||||
"string_decoder": "~0.10.x"
|
||||
}
|
||||
},
|
||||
"node_modules/rechoir": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz",
|
||||
@@ -9735,18 +9704,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/streamsearch": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
|
||||
"integrity": "sha512-jos8u++JKm0ARcSUTAZXOVC0mSox7Bhn6sBgty73P1f3JGf7yG2clTbBNHUdde/kdvP2FESam+vM6l8jBrNxHA==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
|
||||
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||
"integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
||||
@@ -10611,11 +10575,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vue-router": {
|
||||
"version": "4.0.16",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.16.tgz",
|
||||
"integrity": "sha512-JcO7cb8QJLBWE+DfxGUL3xUDOae/8nhM1KVdnudadTAORbuxIC/xAydC5Zr/VLHUDQi1ppuTF5/rjBGzgzrJNA==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.1.tgz",
|
||||
"integrity": "sha512-Wp1mEf2xCwT0ez7o9JvgpfBp9JGnVb+dPERzXDbugTatzJAJ60VWOhJKifQty85k+jOreoFHER4r5fu062PhPw==",
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": "^6.0.0"
|
||||
"@vue/devtools-api": "^6.1.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/posva"
|
||||
@@ -13960,12 +13924,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"busboy": {
|
||||
"version": "0.2.14",
|
||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz",
|
||||
"integrity": "sha512-InWFDomvlkEj+xWLBfU3AvnbVYqeTWmQopiW0tWWEy5yehYm2YkGEc59sUmw/4ty5Zj/b0WHGs1LgecuBSBGrg==",
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
|
||||
"requires": {
|
||||
"dicer": "0.2.5",
|
||||
"readable-stream": "1.1.x"
|
||||
"streamsearch": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"byte-length": {
|
||||
@@ -14792,15 +14755,6 @@
|
||||
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
|
||||
"dev": true
|
||||
},
|
||||
"dicer": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz",
|
||||
"integrity": "sha512-FDvbtnq7dzlPz0wyYlOExifDEZcu8h+rErEXgfxqmLfRfC/kJidEFh4+effJRO3P0xmfqyPbSMG0LveNRfTKVg==",
|
||||
"requires": {
|
||||
"readable-stream": "1.1.x",
|
||||
"streamsearch": "0.1.2"
|
||||
}
|
||||
},
|
||||
"dir-glob": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
||||
@@ -15220,9 +15174,9 @@
|
||||
}
|
||||
},
|
||||
"eslint-plugin-vue": {
|
||||
"version": "9.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.1.1.tgz",
|
||||
"integrity": "sha512-W9n5PB1X2jzC7CK6riG0oAcxjmKrjTF6+keL1rni8n57DZeilx/Fulz+IRJK3lYseLNAygN0I62L7DvioW40Tw==",
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.2.0.tgz",
|
||||
"integrity": "sha512-W2hc+NUXoce8sZtWgZ45miQTy6jNyuSdub5aZ1IBune4JDeAyzucYX0TzkrQ1jMO52sNUDYlCIHDoaNePe0p5g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"eslint-utils": "^3.0.0",
|
||||
@@ -16340,11 +16294,6 @@
|
||||
"call-bind": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"isarray": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
|
||||
},
|
||||
"isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
@@ -16421,9 +16370,9 @@
|
||||
}
|
||||
},
|
||||
"jembadb": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/jembadb/-/jembadb-3.0.6.tgz",
|
||||
"integrity": "sha512-JRs6bTPDMq/UbkI2OweSSGpJX20JiUSQyBJjAOAn1ITPkFGr7q8F3ClEio1gPcmUJryy6EWMX+UnxDS6smJdJA=="
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/jembadb/-/jembadb-3.0.8.tgz",
|
||||
"integrity": "sha512-ukLawbH4IXsKeDTJWNmAEV7D9flQ0m3OMcDSbSjpFWimoyzSn7TyBhWSB8hqmM+9Dyqyh2/nZAcCBu2exXgirQ=="
|
||||
},
|
||||
"jest-worker": {
|
||||
"version": "27.5.1",
|
||||
@@ -16980,16 +16929,15 @@
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"multer": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4.tgz",
|
||||
"integrity": "sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw==",
|
||||
"version": "1.4.5-lts.1",
|
||||
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz",
|
||||
"integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==",
|
||||
"requires": {
|
||||
"append-field": "^1.0.0",
|
||||
"busboy": "^0.2.11",
|
||||
"busboy": "^1.0.0",
|
||||
"concat-stream": "^1.5.2",
|
||||
"mkdirp": "^0.5.4",
|
||||
"object-assign": "^4.1.1",
|
||||
"on-finished": "^2.3.0",
|
||||
"type-is": "^1.6.4",
|
||||
"xtend": "^4.0.0"
|
||||
}
|
||||
@@ -18077,9 +18025,9 @@
|
||||
}
|
||||
},
|
||||
"quasar": {
|
||||
"version": "2.7.4",
|
||||
"resolved": "https://registry.npmjs.org/quasar/-/quasar-2.7.4.tgz",
|
||||
"integrity": "sha512-8OIa6azm7N6QUPjcZ5AhDCEBha5NnNqt+D1BMIteqaSqkVKFYBf+FMhUCC8R/Tc6Myz85vK7KGPn9tvaC6gXYQ=="
|
||||
"version": "2.7.5",
|
||||
"resolved": "https://registry.npmjs.org/quasar/-/quasar-2.7.5.tgz",
|
||||
"integrity": "sha512-DWI0S+bXASfMSPrB8c/LVsXpA4dF7cBUbaJlcrM+1ioTNBHtiudma2Nhk2SDd5bzk9AYVHh5A8JCZuKqQAXt7g=="
|
||||
},
|
||||
"querystring": {
|
||||
"version": "0.2.1",
|
||||
@@ -18158,17 +18106,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
|
||||
"integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.1",
|
||||
"isarray": "0.0.1",
|
||||
"string_decoder": "~0.10.x"
|
||||
}
|
||||
},
|
||||
"rechoir": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz",
|
||||
@@ -18773,14 +18710,9 @@
|
||||
}
|
||||
},
|
||||
"streamsearch": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
|
||||
"integrity": "sha512-jos8u++JKm0ARcSUTAZXOVC0mSox7Bhn6sBgty73P1f3JGf7yG2clTbBNHUdde/kdvP2FESam+vM6l8jBrNxHA=="
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||
"integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
|
||||
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="
|
||||
},
|
||||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
@@ -19424,11 +19356,11 @@
|
||||
}
|
||||
},
|
||||
"vue-router": {
|
||||
"version": "4.0.16",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.16.tgz",
|
||||
"integrity": "sha512-JcO7cb8QJLBWE+DfxGUL3xUDOae/8nhM1KVdnudadTAORbuxIC/xAydC5Zr/VLHUDQi1ppuTF5/rjBGzgzrJNA==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.1.tgz",
|
||||
"integrity": "sha512-Wp1mEf2xCwT0ez7o9JvgpfBp9JGnVb+dPERzXDbugTatzJAJ60VWOhJKifQty85k+jOreoFHER4r5fu062PhPw==",
|
||||
"requires": {
|
||||
"@vue/devtools-api": "^6.0.0"
|
||||
"@vue/devtools-api": "^6.1.4"
|
||||
}
|
||||
},
|
||||
"vue-style-loader": {
|
||||
|
||||
14
package.json
14
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Liberama",
|
||||
"version": "0.11.6",
|
||||
"version": "0.11.7",
|
||||
"author": "Book Pauk <bookpauk@gmail.com>",
|
||||
"license": "CC0-1.0",
|
||||
"repository": "bookpauk/liberama",
|
||||
@@ -32,7 +32,7 @@
|
||||
"css-loader": "^6.5.1",
|
||||
"css-minimizer-webpack-plugin": "^4.0.0",
|
||||
"eslint": "^8.19.0",
|
||||
"eslint-plugin-vue": "^9.1.1",
|
||||
"eslint-plugin-vue": "^9.2.0",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"mini-css-extract-plugin": "^2.4.4",
|
||||
"pkg": "^5.5.1",
|
||||
@@ -59,15 +59,15 @@
|
||||
"fs-extra": "^10.1.0",
|
||||
"he": "^1.2.0",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"jembadb": "^3.0.6",
|
||||
"jembadb": "^3.0.8",
|
||||
"localforage": "^1.10.0",
|
||||
"lodash": "^4.17.21",
|
||||
"minimist": "^1.2.5",
|
||||
"multer": "^1.4.3",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"pako": "^2.0.4",
|
||||
"path-browserify": "^1.0.1",
|
||||
"pidusage": "^3.0.0",
|
||||
"quasar": "^2.3.2",
|
||||
"quasar": "^2.7.5",
|
||||
"safe-buffer": "^5.2.1",
|
||||
"sanitize-html": "^2.5.3",
|
||||
"sjcl": "^1.0.8",
|
||||
@@ -76,8 +76,8 @@
|
||||
"sqlite3": "^5.0.2",
|
||||
"tar-fs": "^2.1.1",
|
||||
"unbzip2-stream": "^1.4.3",
|
||||
"vue": "^3.2.22",
|
||||
"vue-router": "^4.0.12",
|
||||
"vue": "^3.2.37",
|
||||
"vue-router": "^4.1.1",
|
||||
"vuex": "^4.0.2",
|
||||
"vuex-persistedstate": "^4.1.0",
|
||||
"webdav": "^4.7.0",
|
||||
|
||||
@@ -49,7 +49,7 @@ class BaseLog {
|
||||
this.outputBuffer = [];
|
||||
|
||||
await this.flushImpl(this.data)
|
||||
.catch(e => { console.log(e); ayncExit.exit(1); } );
|
||||
.catch(e => { console.error(`Logger error: ${e}`); ayncExit.exit(1); } );
|
||||
this.flushing = false;
|
||||
}
|
||||
|
||||
@@ -218,6 +218,8 @@ class Logger {
|
||||
} else {
|
||||
console.log(mes);
|
||||
}
|
||||
|
||||
return mes;
|
||||
}
|
||||
|
||||
async close() {
|
||||
|
||||
@@ -3,7 +3,7 @@ const chardet = require('chardet');
|
||||
function getEncoding(buf) {
|
||||
let selected = getEncodingLite(buf);
|
||||
|
||||
if (selected == 'ISO-8859-5') {
|
||||
if (selected == 'ISO-8859-5' && buf.length > 10) {
|
||||
const charsetAll = chardet.analyse(buf.slice(0, 20000));
|
||||
for (const charset of charsetAll) {
|
||||
if (charset.name.indexOf('ISO-8859') < 0) {
|
||||
|
||||
@@ -2,6 +2,7 @@ const _ = require('lodash');
|
||||
|
||||
const utils = require('../utils');
|
||||
const JembaConnManager = require('../../db/JembaConnManager');//singleton
|
||||
const log = new (require('../AppLogger'))().log;//singleton
|
||||
|
||||
let instance = null;
|
||||
|
||||
@@ -20,25 +21,30 @@ class JembaReaderStorage {
|
||||
}
|
||||
|
||||
async doAction(act) {
|
||||
if (!_.isObject(act.items))
|
||||
throw new Error('items is not an object');
|
||||
try {
|
||||
if (!_.isObject(act.items))
|
||||
throw new Error('items is not an object');
|
||||
|
||||
let result = {};
|
||||
switch (act.action) {
|
||||
case 'check':
|
||||
result = await this.checkItems(act.items);
|
||||
break;
|
||||
case 'get':
|
||||
result = await this.getItems(act.items);
|
||||
break;
|
||||
case 'set':
|
||||
result = await this.setItems(act.items, act.force);
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unknown action');
|
||||
let result = {};
|
||||
switch (act.action) {
|
||||
case 'check':
|
||||
result = await this.checkItems(act.items);
|
||||
break;
|
||||
case 'get':
|
||||
result = await this.getItems(act.items);
|
||||
break;
|
||||
case 'set':
|
||||
result = await this.setItems(act.items, act.force);
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unknown action');
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (e) {
|
||||
log(LM_ERR, `JembaReaderStorage: ${e.message}`);
|
||||
throw e;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async checkItems(items) {
|
||||
|
||||
Reference in New Issue
Block a user