Compare commits

..

74 Commits

Author SHA1 Message Date
Book Pauk
9405ce2cc0 Merge branch 'release/0.11.7-3' 2022-07-13 16:34:16 +07:00
Book Pauk
115277d88a Поправки разметки 2022-07-13 16:34:00 +07:00
Book Pauk
6925c11dbd Merge tag '0.11.7-2' into develop
0.11.7-2
2022-07-13 16:25:11 +07:00
Book Pauk
984d835892 Merge branch 'release/0.11.7-2' 2022-07-13 16:25:05 +07:00
Book Pauk
23353a4960 Улучшен парсинг fb2 2022-07-13 16:23:52 +07:00
Book Pauk
955bcda032 Поправки разметки 2022-07-13 15:01:35 +07:00
Book Pauk
81ad5d7a2c Поправки разметки 2022-07-13 14:47:24 +07:00
Book Pauk
dada7980ec Merge tag '0.11.7-1' into develop
0.11.7-1
2022-07-12 19:23:38 +07:00
Book Pauk
511a308646 Merge branch 'release/0.11.7-1' 2022-07-12 19:23:33 +07:00
Book Pauk
65c8f2cc81 Небольшие поправки на панели, изменена нумерация на обратную 2022-07-12 19:21:26 +07:00
Book Pauk
238c18bc48 Merge tag '0.11.7' into develop
0.11.7
2022-07-12 19:08:35 +07:00
Book Pauk
873a08fee1 Merge branch 'release/0.11.7' 2022-07-12 19:08:27 +07:00
Book Pauk
7e89228803 Версия 0.11.7 2022-07-12 19:07:39 +07:00
Book Pauk
fc630923a4 Настройка методов сортировки 2022-07-12 18:50:35 +07:00
Book Pauk
928f911d03 Добавлены подсказки к кнопкам 2022-07-12 17:53:14 +07:00
Book Pauk
7ffcd3fe1b Поправки поведения при скроллинге 2022-07-12 17:33:03 +07:00
Book Pauk
0efbaf643a Поправил сообщение об ошибке 2022-07-12 17:32:19 +07:00
Book Pauk
f1bf8e54ae Добавлен метод scrollToActiveBook 2022-07-12 17:10:50 +07:00
Book Pauk
b4aa6ab6c8 Поправки поиска 2022-07-12 16:58:34 +07:00
Book Pauk
72431f0202 Работа над группировкой 2022-07-12 16:51:32 +07:00
Book Pauk
04a326c0e4 Работа над группировкой 2022-07-12 15:51:43 +07:00
Book Pauk
931966f4f3 Поправки разметки 2022-07-12 15:05:17 +07:00
Book Pauk
8808cc4779 Работа над группировкой по файлам 2022-07-12 14:46:34 +07:00
Book Pauk
988c959eba Работа над группировкой файлов 2022-07-12 04:05:51 +07:00
Book Pauk
c0b658d9e6 К предыдущему 2022-07-12 01:41:18 +07:00
Book Pauk
3190246f34 Улучшена реакция на onResize 2022-07-12 01:35:19 +07:00
Book Pauk
d957b4a5f9 Добавлена возможность автосокрытия панели при прокрутке 2022-07-12 01:03:44 +07:00
Book Pauk
bef9e5705c Поправки текстовых строк 2022-07-11 23:53:54 +07:00
Book Pauk
eb2affa518 Приведение input к единому стилю 2022-07-11 23:50:51 +07:00
Book Pauk
07b9a3c033 Мелкие правки 2022-07-11 22:28:48 +07:00
Book Pauk
3ca14ae06a Работа над группировкой 2022-07-11 22:26:34 +07:00
Book Pauk
7caa0c2112 Начало добавления группировки в RecentBooksPage 2022-07-11 20:11:38 +07:00
Book Pauk
9c69f5bc01 Поправил размер иконки 2022-07-11 20:10:51 +07:00
Book Pauk
125a2e0f17 Исправление багов 2022-07-11 17:12:17 +07:00
Book Pauk
1b4360b897 Дополнение в convertRecent 2022-07-11 16:26:03 +07:00
Book Pauk
4775d6e47b Поправлен баг 2022-07-10 20:07:33 +07:00
Book Pauk
33fc553c55 Добавлен запрос на объединение позиций при
обнаружении похожего файла в загруженных
2022-07-10 19:54:00 +07:00
Book Pauk
25cad81c50 Улучшение отображения загруженных 2022-07-10 19:53:30 +07:00
Book Pauk
02a2099c1f Поправлен z-index 2022-07-10 19:52:58 +07:00
Book Pauk
1cda186b1a Добавлен диалог askYesNo 2022-07-10 19:52:29 +07:00
Book Pauk
f10291b6c6 Поправка названия действия 2022-07-10 19:51:31 +07:00
Book Pauk
26ab5d6765 Рефакторинг 2022-07-10 18:27:05 +07:00
Book Pauk
5edeed0747 Изменение механизма хранения книг 2022-07-10 17:31:21 +07:00
Book Pauk
c878ce432f Небольшое исправление опознававния кодировки 2022-07-10 17:20:47 +07:00
Book Pauk
81798897c8 Изменения в механизме хранения книг:
теперь ориентируемся на "ключ-filepath", а не "ключ-url"
2022-07-10 16:38:54 +07:00
Book Pauk
63840fadbc К предыдущему 2022-07-10 14:59:39 +07:00
Book Pauk
36aa057035 Поправка цвета 2022-07-09 21:00:09 +07:00
Book Pauk
30afd2421c Рефакторинг 2022-07-09 20:50:31 +07:00
Book Pauk
53a1d90bd8 Улучшение поведения при очереди загрузки книг 2022-07-09 02:01:14 +07:00
Book Pauk
2ecf6beef2 Небольшой багфикс 2022-07-09 01:56:42 +07:00
Book Pauk
85910a20e9 Улучшение ContentsPage 2022-07-08 20:50:55 +07:00
Book Pauk
66cf7790b3 Улучшения ContentsPage 2022-07-08 19:09:57 +07:00
Book Pauk
4a9eb7e4bb Удалил устаревшее 2022-07-08 14:30:44 +07:00
Book Pauk
07446696c1 Поправлен цвет заголовка 2022-07-08 13:52:45 +07:00
Book Pauk
a29f9d9a4b Унификация размеров окон 2022-07-08 13:43:59 +07:00
Book Pauk
d49c9baec3 Унификация интерфейса 2022-07-08 13:34:53 +07:00
Book Pauk
8c9d4a12ee Настройка цветов 2022-07-08 13:24:13 +07:00
Book Pauk
fce69e4657 Настройка цветов 2022-07-08 13:21:42 +07:00
Book Pauk
b387509f88 Добавил блокировку при загрузке книг, теперь загружаются последовательно 2022-07-08 12:26:47 +07:00
Book Pauk
8dc8bdc0d6 Merge tag '0.11.6-2' into develop
0.11.6-2
2022-07-07 19:43:47 +07:00
Book Pauk
00caae8363 Merge branch 'release/0.11.6-2' 2022-07-07 19:43:40 +07:00
Book Pauk
2ead8570a7 Небольшая поправка 2022-07-07 19:39:02 +07:00
Book Pauk
408315466b Частичный откат предыдущих изменений 2022-07-07 19:38:17 +07:00
Book Pauk
c651836554 Поправки скриптов запуска 2022-07-07 19:33:32 +07:00
Book Pauk
03a1e70fce Поправки, чтобы не падал в случае детача скрина 2022-07-07 19:05:54 +07:00
Book Pauk
ab5a11a24f Убрал сайт flibs.in из сетевых библиотек 2022-07-07 17:42:05 +07:00
Book Pauk
8cd6ed472c Изменил client_max_body_size 100m 2022-07-07 17:37:25 +07:00
Book Pauk
055181b744 Исправлен баг выпадающих списков в оглавлении 2022-07-07 17:34:03 +07:00
Book Pauk
e331a3920b Актуализация пакетов 2022-07-07 17:29:47 +07:00
Book Pauk
c62bccb470 Улучшил журналирование ошибок БД 2022-07-07 16:24:59 +07:00
Book Pauk
ea351ea293 Merge tag '0.11.6-1' into develop
0.11.6-1
2022-07-04 12:23:55 +07:00
Book Pauk
d806a07c60 Merge branch 'release/0.11.6-1' 2022-07-04 12:23:48 +07:00
Book Pauk
c0ea096f1f Обновил jembadb 2022-07-04 12:22:27 +07:00
Book Pauk
011d4a1672 Merge tag '0.11.6' into develop
0.11.6
2022-07-02 17:41:42 +07:00
31 changed files with 1152 additions and 516 deletions

View File

@@ -11,7 +11,7 @@
Открыть выбранную закладку Открыть выбранную закладку
</q-tooltip> </q-tooltip>
</q-btn> </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> <template #append>
<q-icon v-if="search !== ''" name="la la-times" class="cursor-pointer" @click="resetSearch" /> <q-icon v-if="search !== ''" name="la la-times" class="cursor-pointer" @click="resetSearch" />
</template> </template>

View File

@@ -5,19 +5,19 @@
</template> </template>
<template #buttons> <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-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> <q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">На весь экран</q-tooltip>
</span> </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-icon name="la la-plus" size="16px" />
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Увеличить масштаб</q-tooltip> <q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Увеличить масштаб</q-tooltip>
</span> </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-icon name="la la-minus" size="16px" />
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Уменьшить масштаб</q-tooltip> <q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Уменьшить масштаб</q-tooltip>
</span> </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-icon name="la la-question-circle" size="16px" />
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Справка</q-tooltip> <q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Справка</q-tooltip>
</span> </span>
@@ -32,7 +32,7 @@
:options="rootLinkOptions" :options="rootLinkOptions"
style="width: 230px" style="width: 230px"
dropdown-icon="la la-angle-down la-sm" 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" @popup-show="onSelectPopupShow" @popup-hide="onSelectPopupHide"
> >
<template #prepend> <template #prepend>
@@ -61,7 +61,7 @@
:options="selectedLinkOptions" :options="selectedLinkOptions"
style="width: 50px" style="width: 50px"
dropdown-icon="la la-angle-down la-sm" 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" @popup-show="onSelectPopupShow" @popup-hide="onSelectPopupHide"
> >
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%"> <q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
@@ -73,7 +73,7 @@
ref="input" ref="input"
v-model="bookUrl" v-model="bookUrl"
class="col q-mr-sm" class="col q-mr-sm"
rounded outlined dense outlined dense
bg-color="white" bg-color="white"
placeholder="Скопируйте сюда ссылку на книгу и нажмите 'Открыть'" placeholder="Скопируйте сюда ссылку на книгу и нажмите 'Открыть'"
@focus="selectAllOnFocus" @keydown="bookUrlKeyDown" @focus="selectAllOnFocus" @keydown="bookUrlKeyDown"
@@ -99,7 +99,7 @@
</template> </template>
</q-input> </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%"> <q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
Открыть в читалке Открыть в читалке
@@ -894,14 +894,15 @@ export default vueComponent(ExternalLibs);
background-color: #A0A0A0; background-color: #A0A0A0;
} }
.full-screen-button { .header-button {
width: 30px; width: 30px;
height: 30px; height: 30px;
cursor: pointer; cursor: pointer;
} }
.full-screen-button:hover { .header-button:hover {
background-color: #69C05F; color: white;
background-color: #39902F;
} }
.transparent-layout { .transparent-layout {

View File

@@ -23,15 +23,15 @@
<div class="q-mb-sm" /> <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>
<div v-for="item in contents" :key="item.key" class="column" style="width: 540px"> <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)"> <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>
<div v-else class="no-expand-button clickable" @click="setBookPos(item.offset)"> <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>
<div class="col row clickable" @click="setBookPos(item.offset)"> <div class="col row clickable" @click="setBookPos(item.offset)">
<div :style="item.indentStyle"></div> <div :style="item.indentStyle"></div>
@@ -42,8 +42,12 @@
</div> </div>
</div> </div>
<div v-if="item.expanded" :ref="`subitem${item.key}`" class="subitems-transition"> <div v-if="item.expanded" :ref="`subdiv${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-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="col row clickable" @click="setBookPos(subitem.offset)">
<div class="no-expand-button"></div> <div class="no-expand-button"></div>
<div :style="subitem.indentStyle"></div> <div :style="subitem.indentStyle"></div>
@@ -61,10 +65,10 @@
</div> </div>
</div> </div>
<div v-show="selectedTab == 'images'" class="tab-panel"> <div v-show="selectedTab == 'images'" ref="tabPanelImages" class="tab-panel">
<div> <div>
<div v-for="item in images" :key="item.key" class="column" style="width: 540px"> <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="col row clickable" @click="setBookPos(item.offset)">
<div class="image-thumb-box row justify-center items-center"> <div class="image-thumb-box row justify-center items-center">
<div v-show="!imageLoaded[item.id]" class="image-thumb column justify-center"> <div v-show="!imageLoaded[item.id]" class="image-thumb column justify-center">
@@ -124,7 +128,10 @@ const componentOptions = {
watch: { watch: {
bookPos() { bookPos() {
this.updateBookPosSelection(); this.updateBookPosSelection();
} },
selectedTab() {
this.updateBookPosScrollTop();
},
}, },
}; };
class ContentsPage { class ContentsPage {
@@ -282,31 +289,30 @@ class ContentsPage {
if (!this.isVisible) if (!this.isVisible)
return; return;
await utils.sleep(50); await this.$nextTick();
const bp = this.bookPos; const bp = this.bookPos;
for (let i = 0; i < this.contents.length; i++) { for (let i = 0; i < this.contents.length; i++) {
const item = this.contents[i]; const item = this.contents[i];
const nextOffset = (i < this.contents.length - 1 ? this.contents[i + 1].offset : this.parsed.textLength); 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++) { for (let j = 0; j < item.list.length; j++) {
const subitem = item.list[j]; const subitem = item.list[j];
const nextSubOffset = (j < item.list.length - 1 ? item.list[j + 1].offset : nextOffset); const nextSubOffset = (j < item.list.length - 1 ? item.list[j + 1].offset : nextOffset);
if (bp >= subitem.offset && bp < nextSubOffset) { if (bp >= subitem.offset && bp < nextSubOffset) {
subitem.isBookPos = true; subitem.isBookPos = true;
this.contents[i] = Object.assign(item, {list: item.list}); this.updateBookPosScrollTop('contents', item, subitem, j);
} else if (subitem.isBookPos) { } else if (subitem.isBookPos) {
subitem.isBookPos = false; 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++) { 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); const nextOffset = (i < this.images.length - 1 ? this.images[i + 1].offset : this.parsed.textLength);
if (bp >= img.offset && bp < nextOffset) { if (bp >= img.offset && bp < nextOffset) {
this.images[i] = Object.assign(img, {isBookPos: true}); this.images[i].isBookPos = true;
} else if (img.isBookPos) { } 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) { async expandClick(key) {
@@ -326,17 +417,17 @@ class ContentsPage {
const expanded = !item.expanded; const expanded = !item.expanded;
if (!expanded) { if (!expanded) {
const subitems = this.$refs[`subitem${key}`]; let subdiv = this.getFirstElem(this.$refs[`subdiv${key}`]);
subitems.style.height = '0'; subdiv.style.height = '0';
await utils.sleep(200); await utils.sleep(200);
} }
this.contents[key] = Object.assign({}, item, {expanded}); this.contents[key].expanded = expanded;
if (expanded) { if (expanded) {
await this.$nextTick(); await this.$nextTick();
const subitems = this.$refs[`subitem${key}`]; let subdiv = this.getFirstElem(this.$refs[`subdiv${key}`]);
subitems.style.height = subitems.scrollHeight + 'px'; subdiv.style.height = subdiv.scrollHeight + 'px';
} }
} }

View File

@@ -5,13 +5,20 @@
</template> </template>
<div class="col column" style="min-width: 600px"> <div class="col column" style="min-width: 600px">
<q-btn-toggle <div class="bg-grey-3 row">
v-model="selectedTab" <q-tabs
toggle-color="primary" v-model="selectedTab"
no-caps unelevated active-color="black"
:options="buttons" active-bg-color="white"
/> indicator-color="white"
<div class="separator"></div> 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> <keep-alive>
<component :is="activePage" ref="page" class="col"></component> <component :is="activePage" ref="page" class="col"></component>
@@ -93,8 +100,4 @@ export default vueComponent(HelpPage);
</script> </script>
<style scoped> <style scoped>
.separator {
height: 1px;
background-color: #E0E0E0;
}
</style> </style>

View File

@@ -1,5 +1,5 @@
<template> <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"> <div class="column justify-start items-center" style="height: 250px">
<q-circular-progress <q-circular-progress
show-value show-value

View File

@@ -141,6 +141,7 @@
@load-file="loadFile" @load-file="loadFile"
@book-pos-changed="bookPosChanged" @book-pos-changed="bookPosChanged"
@do-action="doAction" @do-action="doAction"
@hide-tool-bar="hideToolBar"
></component> ></component>
</keep-alive> </keep-alive>
@@ -201,6 +202,7 @@ import miscApi from '../../api/misc';
import {versionHistory} from './versionHistory'; import {versionHistory} from './versionHistory';
import * as utils from '../../share/utils'; import * as utils from '../../share/utils';
import LockQueue from '../../share/LockQueue';
const componentOptions = { const componentOptions = {
components: { components: {
@@ -313,6 +315,8 @@ class Reader {
this.reader = this.$store.state.reader; this.reader = this.$store.state.reader;
this.config = this.$store.state.config; this.config = this.$store.state.config;
this.lock = new LockQueue(100);
this.$root.addEventHook('key', this.keyHook); this.$root.addEventHook('key', this.keyHook);
this.lastActivePage = false; this.lastActivePage = false;
@@ -345,6 +349,13 @@ class Reader {
this.debouncedSetRecentBook(newValue); this.debouncedSetRecentBook(newValue);
}, 15000, {maxWait: 20000}); }, 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', () => { document.addEventListener('fullscreenchange', () => {
this.fullScreenActive = (document.fullscreenElement !== null); this.fullScreenActive = (document.fullscreenElement !== null);
}); });
@@ -402,6 +413,7 @@ class Reader {
this.clickControlActive = this.clickControl; this.clickControlActive = this.clickControl;
this.blinkCachedLoad = settings.blinkCachedLoad; this.blinkCachedLoad = settings.blinkCachedLoad;
this.showToolButton = settings.showToolButton; this.showToolButton = settings.showToolButton;
this.toolBarHideOnScroll = settings.toolBarHideOnScroll;
this.enableSitesFilter = settings.enableSitesFilter; this.enableSitesFilter = settings.enableSitesFilter;
this.showNeedUpdateNotify = settings.showNeedUpdateNotify; this.showNeedUpdateNotify = settings.showNeedUpdateNotify;
this.splitToPara = settings.splitToPara; this.splitToPara = settings.splitToPara;
@@ -662,6 +674,10 @@ class Reader {
this.$root.eventHook('resize'); this.$root.eventHook('resize');
} }
hideToolBar(event) {
this.debouncedHideToolBar(event);
}
fullScreenToggle() { fullScreenToggle() {
this.fullScreenActive = !this.fullScreenActive; this.fullScreenActive = !this.fullScreenActive;
if (this.fullScreenActive) { if (this.fullScreenActive) {
@@ -897,7 +913,7 @@ class Reader {
refreshBook() { refreshBook() {
const mrb = this.mostRecentBook(); const mrb = this.mostRecentBook();
this.loadBook({url: mrb.url, uploadFileName: mrb.uploadFileName, force: true}); this.loadBook(Object.assign({}, mrb, {force: true}));
} }
undoAction() { undoAction() {
@@ -1051,7 +1067,7 @@ class Reader {
return result; return result;
} }
async loadBook(opts) { async _loadBook(opts) {
if (!opts || !opts.url) { if (!opts || !opts.url) {
this.mostRecentBook(); this.mostRecentBook();
return; return;
@@ -1061,10 +1077,6 @@ class Reader {
let url = encodeURI(decodeURI(opts.url)); 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) && if ((url.indexOf('http://') != 0) && (url.indexOf('https://') != 0) &&
(url.indexOf('disk://') != 0)) (url.indexOf('disk://') != 0))
url = 'http://' + url; url = 'http://' + url;
@@ -1091,33 +1103,36 @@ class Reader {
progress.show(); progress.show();
progress.setState({state: 'parse'}); progress.setState({state: 'parse'});
// есть ли среди недавних // есть ли среди загруженных
const key = bookManager.keyFromUrl(url); let wasOpened = bookManager.findRecentByUrlAndPath(url, opts.path);
let wasOpened = await bookManager.getRecentBook({key}); wasOpened = (wasOpened ? _.cloneDeep(wasOpened) : {});
wasOpened = (wasOpened ? wasOpened : {});
const bookPos = (opts.bookPos !== undefined ? opts.bookPos : wasOpened.bookPos); wasOpened = Object.assign(wasOpened, {
const bookPosSeen = (opts.bookPos !== undefined ? opts.bookPos : wasOpened.bookPosSeen); path: (opts.path !== undefined ? opts.path : wasOpened.path),
const uploadFileName = (opts.uploadFileName ? opts.uploadFileName : ''); 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; let book = null;
if (!opts.force) { if (!opts.force) {
// пытаемся загрузить и распарсить книгу в менеджере из локального кэша // пытаемся загрузить и распарсить книгу в менеджере из локального кэша
const bookParsed = await bookManager.getBook({url, path: opts.path}, (prog) => { const bookParsed = await bookManager.getBook(wasOpened, (prog) => {
progress.setState({progress: prog}); progress.setState({progress: prog});
}); });
// если есть в локальном кэше // если есть в локальном кэше
if (bookParsed) { if (bookParsed) {
await bookManager.setRecentBook(Object.assign({bookPos, bookPosSeen}, bookParsed)); await bookManager.setRecentBook(Object.assign(wasOpened, bookParsed));
this.mostRecentBook(); this.mostRecentBook();
this.addAction(bookPos); this.addAction(wasOpened.bookPos);
this.loaderActive = false; this.loaderActive = false;
progress.hide(); this.progressActive = false; progress.hide(); this.progressActive = false;
this.blinkCachedLoadMessage(); this.blinkCachedLoadMessage();
this.checkBookPosPercent(); this.checkBookPosPercent();
await this.activateClickMapPage(); this.activateClickMapPage();//no await
return; return;
} }
@@ -1131,7 +1146,7 @@ class Reader {
}); });
book = Object.assign({}, wasOpened, {data: resp.data}); book = Object.assign({}, wasOpened, {data: resp.data});
} catch (e) { } catch (e) {
//молчим this.$root.notify.error('Конвертированный файл не найден на сервере.<br>Пробуем загрузить оригинал.', 'Ошибка загрузки');
} }
} }
} }
@@ -1142,7 +1157,7 @@ class Reader {
if (!book) { if (!book) {
book = await readerApi.loadBook({ book = await readerApi.loadBook({
url, url,
uploadFileName, uploadFileName: wasOpened.uploadFileName,
enableSitesFilter: this.enableSitesFilter, enableSitesFilter: this.enableSitesFilter,
skipHtmlCheck: (this.splitToPara ? true : false), skipHtmlCheck: (this.splitToPara ? true : false),
isText: (this.splitToPara ? true : false), isText: (this.splitToPara ? true : false),
@@ -1159,14 +1174,44 @@ class Reader {
// добавляем в bookManager // добавляем в bookManager
progress.setState({state: 'parse', step: 5}); progress.setState({state: 'parse', step: 5});
const addedBook = await bookManager.addBook(book, (prog) => { const addedBook = await bookManager.addBook(book, (prog) => {
progress.setState({progress: 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.mostRecentBook();
this.addAction(bookPos); this.addAction(wasOpened.bookPos);
this.updateRoute(true); this.updateRoute(true);
this.loaderActive = false; this.loaderActive = false;
@@ -1177,11 +1222,11 @@ class Reader {
this.stopBlink = true; this.stopBlink = true;
this.checkBookPosPercent(); this.checkBookPosPercent();
await this.activateClickMapPage(); this.activateClickMapPage();//no await
} catch (e) { } catch (e) {
progress.hide(); this.progressActive = false; progress.hide(); this.progressActive = false;
this.loaderActive = true; this.loaderActive = true;
if (!this.showHelpOnErrorIfNeeded(e.message)) { if (!this.showHelpOnErrorIfNeeded(url)) {
this.$root.stdDialog.alert(e.message, 'Ошибка', {color: 'negative'}); this.$root.stdDialog.alert(e.message, 'Ошибка', {color: 'negative'});
} }
} finally { } 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; this.progressActive = true;
await this.$nextTick(); await this.$nextTick();
@@ -1205,7 +1259,7 @@ class Reader {
progress.hide(); this.progressActive = false; 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) { } catch (e) {
progress.hide(); this.progressActive = false; progress.hide(); this.progressActive = false;
this.loaderActive = true; 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() { blinkCachedLoadMessage() {
if (!this.blinkCachedLoad) if (!this.blinkCachedLoad)
return; return;

View File

@@ -54,7 +54,7 @@
<br><br> <br><br>
<div class="row justify-center"> <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--> </q-btn-->
</div> </div>

View File

@@ -9,88 +9,157 @@
<a ref="download" style="display: none;" target="_blank"></a> <a ref="download" style="display: none;" target="_blank"></a>
<q-table <div id="vs-container" ref="vsContainer" class="recent-books-scroll col">
class="recent-books-table col" <div ref="header" class="scroll-header row bg-blue-2">
:rows="tableData" <q-btn class="tool-button" round @click="showSameBookClick">
row-key="key" <q-icon name="la la-caret-right" class="icon" :class="{'expanded-icon': showSameBook}" color="green-8" size="24px" />
:columns="columns" <q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
:pagination="pagination" Показать/скрыть версии книг
separator="cell" </q-tooltip>
hide-bottom </q-btn>
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>
<template #body="props"> <q-btn class="tool-button" round @click="scrollToBegin">
<q-tr :props="props"> <q-icon name="la la-arrow-up" color="green-8" size="24px" />
<q-td key="num" :props="props" class="td-mp" auto-width> <q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
<div class="break-word" style="width: 25px"> В начало списка
{{ props.row.num }} </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> </div>
</q-td>
<q-td key="date" auto-width :props="props" class="td-mp clickable" @click="loadBook(props.row.url)"> <div v-show="!showSameBook && item.group && item.group.length > 0" class="row justify-center" style="font-size: 70%">
<div class="break-word" style="width: 68px"> {{ (item.group ? item.group.length + 1 : 0) }} верси{{ wordEnding((item.group ? item.group.length + 1 : 0), 1) }}
{{ props.row.touchDate }}<br>
{{ props.row.touchTime }}
</div> </div>
</q-td> </div>
<q-td key="desc" auto-width :props="props" class="td-mp clickable" @click="loadBook(props.row.url)"> <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="break-word" style="width: 332px; font-size: 90%"> <div class="col" style="border: 1px solid #cccccc; border-bottom: 0; padding: 4px" :style="{ 'width': (340 - 40*(+item.inGroup)) + 'px' }">
<div style="color: green"> <div class="text-green-10" style="font-size: 105%">
{{ props.row.desc.author }} {{ item.desc.author }}
</div> </div>
<div>{{ props.row.desc.title }}</div> <div>{{ item.desc.title }}</div>
<div class="read-bar" :style="`width: ${332*props.row.readPart}px`"></div>
</div> </div>
</q-td>
<q-td key="links" :props="props" class="td-mp" auto-width> <div class="row" style="font-size: 10px">
<div class="break-word" style="width: 75px; font-size: 90%"> <div class="row justify-center items-center row-info-top" style="width: 60px">
<a v-show="isUrl(props.row.url)" :href="props.row.url" target="_blank">Оригинал</a><br> {{ item.desc.textLen }}
<a :href="props.row.path" @click.prevent="downloadBook(props.row.path, props.row.fullTitle)">Скачать FB2</a> </div>
</div>
</q-td>
<q-td key="close" :props="props" class="td-mp" auto-width> <div class="row items-center row-info-top" :style="`width: ${(220 - 40*(+item.inGroup))}px; padding: 1px`">
<div style="width: 38px"> <div class="read-bar" :style="`width: ${100*item.readPart}%`"></div>
<q-btn </div>
dense
style="width: 30px; height: 30px; padding: 7px 0 7px 0; margin-left: 4px" <div class="row justify-center items-center row-info-top" style="width: 59px">
@click="handleDel(props.row.key)" {{ item.desc.perc }}
> </div>
<q-icon class="la la-times" size="14px" /> <div class="row-info-top" style="width: 1px">
</q-btn> </div>
</div> </div>
</q-td>
<q-td key="last" :props="props" class="no-mp"> <div class="row" style="font-size: 10px" :style="{ 'width': (340 - 40*(+item.inGroup)) + 'px' }">
</q-td> <div class="row justify-center items-center row-info-bottom" style="width: 30px">
</q-tr> {{ item.num }}
</template> </div>
</q-table> <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> </Window>
</template> </template>
@@ -99,9 +168,10 @@
import vueComponent from '../../vueComponent.js'; import vueComponent from '../../vueComponent.js';
import path from 'path-browserify'; import path from 'path-browserify';
//import _ from 'lodash'; import _ from 'lodash';
import * as utils from '../../../share/utils'; import * as utils from '../../../share/utils';
import LockQueue from '../../../share/LockQueue';
import Window from '../../share/Window.vue'; import Window from '../../share/Window.vue';
import bookManager from '../share/bookManager'; import bookManager from '../share/bookManager';
import readerApi from '../../../api/reader'; import readerApi from '../../../api/reader';
@@ -111,9 +181,15 @@ const componentOptions = {
Window, Window,
}, },
watch: { watch: {
search: function() { search() {
this.updateTableData(); this.updateTableData();
} },
sortMethod() {
this.updateTableData();
},
settings() {
this.loadSettings();
},
}, },
}; };
class RecentBooksPage { class RecentBooksPage {
@@ -122,52 +198,18 @@ class RecentBooksPage {
loading = false; loading = false;
search = ''; search = '';
tableData = []; tableData = [];
columns = []; sortMethod = '';
pagination = {}; showSameBook = false;
created() { created() {
this.firstInit = true; this.commit = this.$store.commit;
this.pagination = {rowsPerPage: 0};
this.columns = [ this.lastScrollTop1 = 0;
{ this.lastScrollTop2 = 0;
name: 'num',
label: '#', this.lock = new LockQueue(100);
align: 'center',
sortable: true, this.loadSettings();
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',
},
];
} }
init() { init() {
@@ -176,89 +218,185 @@ class RecentBooksPage {
this.$nextTick(() => { this.$nextTick(() => {
//this.$refs.input.focus();//плохо на планшетах //this.$refs.input.focus();//плохо на планшетах
}); });
(async() => {//подгрузка списка
if (this.initing)
return;
this.initing = true;
if (this.firstInit) {//для отзывчивости this.inited = true;
await this.updateTableData(20);
this.firstInit = false; (async() => {
} this.showBar();
await utils.sleep(50);
await this.updateTableData(); await this.updateTableData();
await this.scrollToActiveBook();
this.initing = false;
})(); })();
} }
async updateTableData(limit) { loadSettings() {
while (this.updating) await utils.sleep(100); const settings = this.settings;
this.updating = true; this.showSameBook = settings.recentShowSameBook;
let result = []; this.sortMethod = settings.recentSortMethod || 'loadTimeDesc';
}
this.loading = !!limit; get settings() {
const sorted = bookManager.getSortedRecent(); return this.$store.state.reader.settings;
}
let num = 0; async updateTableData() {
for (let i = 0; i < sorted.length; i++) { if (!this.inited)
const book = sorted[i]; return;
if (book.deleted)
continue;
num++; await this.lock.get();
if (limit && result.length >= limit) try {
break; let result = [];
let d = new Date(); const sorted = bookManager.getSortedRecent();
d.setTime(book.touchTime); const activeBook = bookManager.mostRecentBook();
const t = utils.formatDate(d).split(' ');
let readPart = 0; //подготовка полей
let perc = ''; for (const book of sorted) {
let textLen = ''; if (book.deleted)
const p = (book.bookPosSeen ? book.bookPosSeen : (book.bookPos ? book.bookPos : 0)); continue;
if (book.textLength) {
readPart = p/book.textLength; let d = new Date();
perc = ` [${(readPart*100).toFixed(2)}%]`; d.setTime(book.touchTime);
textLen = ` ${Math.round(book.textLength/1000)}k`; 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 search = this.search;
const author = (bt.author ? bt.author : (bt.bookTitle ? bt.bookTitle : book.url)); 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, switch (this.sortMethod) {
touchDateTime: book.touchTime, case 'loadTimeDesc':
touchDate: t[0], result.sort((a, b) => b.loadTimeRaw - a.loadTimeRaw);
touchTime: t[1], break;
desc: { case 'loadTimeAsc':
author, result.sort((a, b) => a.loadTimeRaw - b.loadTimeRaw);
title: `${title}${perc}${textLen}`, break;
}, case 'touchTimeDesc':
readPart, result.sort((a, b) => b.touchTimeRaw - a.touchTimeRaw);
descString: `${author}${title}${perc}${textLen}`,//для сортировки break;
url: book.url, case 'touchTimeAsc':
path: book.path, result.sort((a, b) => a.touchTimeRaw - b.touchTimeRaw);
fullTitle: bt.fullTitle, break;
key: book.key, 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() { resetSearch() {
@@ -266,19 +404,22 @@ class RecentBooksPage {
this.$refs.input.focus(); this.$refs.input.focus();
} }
wordEnding(num) { wordEnding(num, type = 0) {
const endings = ['', 'а', 'и', 'и', 'и', '', '', '', '', '']; const endings = [
['ов', '', 'а', 'а', 'а', 'ов', 'ов', 'ов', 'ов', 'ов'],
['й', 'я', 'и', 'и', 'и', 'й', 'й', 'й', 'й', 'й']
];
const deci = num % 100; const deci = num % 100;
if (deci > 10 && deci < 20) { if (deci > 10 && deci < 20) {
return ''; return endings[type][0];
} else { } else {
return endings[num % 10]; return endings[type][num % 10];
} }
} }
get header() { get header() {
const len = (this.tableData ? this.tableData.length : 0); 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) { async downloadBook(fb2path, fullTitle) {
@@ -311,8 +452,8 @@ class RecentBooksPage {
this.close(); this.close();
} }
loadBook(url) { loadBook(row) {
this.$emit('load-book', {url}); this.$emit('load-book', {url: row.url, path: row.path});
this.close(); this.close();
} }
@@ -323,6 +464,111 @@ class RecentBooksPage {
return false; 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%">&uarr;</span> Время загрузки', value: 'loadTimeDesc'},
{label: '<span style="font-size: 150%">&darr;</span> Время загрузки', value: 'loadTimeAsc'},
{label: '<span style="font-size: 150%">&uarr;</span> Время чтения', value: 'touchTimeDesc'},
{label: '<span style="font-size: 150%">&darr;</span> Время чтения', value: 'touchTimeAsc'},
{label: '<span style="font-size: 150%">&uarr;</span> Автор', value: 'authorDesc'},
{label: '<span style="font-size: 150%">&darr;</span> Автор', value: 'authorAsc'},
{label: '<span style="font-size: 150%">&uarr;</span> Название', value: 'titleDesc'},
{label: '<span style="font-size: 150%">&darr;</span> Название', value: 'titleAsc'},
];
}
close() { close() {
this.$emit('recent-books-close'); this.$emit('recent-books-close');
} }
@@ -340,29 +586,33 @@ export default vueComponent(RecentBooksPage);
</script> </script>
<style scoped> <style scoped>
.recent-books-table { .recent-books-scroll {
width: 600px; width: 573px;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; 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 { .clickable {
cursor: pointer; 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 { .break-word {
line-height: 180%; line-height: 180%;
overflow-wrap: break-word; overflow-wrap: break-word;
@@ -370,25 +620,55 @@ export default vueComponent(RecentBooksPage);
white-space: normal; 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 { .read-bar {
height: 3px; height: 6px;
background-color: #aaaaaa; background-color: #bbbbbb;
}
</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;
} }
</style> </style>

View File

@@ -8,12 +8,10 @@
<span v-show="initStep">{{ initPercentage }}%</span> <span v-show="initStep">{{ initPercentage }}%</span>
<div v-show="!initStep" class="input"> <div v-show="!initStep" class="input">
<!--input ref="input" <q-input
placeholder="что ищем" ref="input" v-model="needle"
:value="needle" @input="needle = $event.target.value"/-->
<q-input ref="input" v-model="needle"
class="col" outlined dense class="col" outlined dense
placeholder="что ищем" placeholder="Найти"
@keydown="inputKeyDown" @keydown="inputKeyDown"
/> />
<div style="position: absolute; right: 10px; margin-top: 10px; font-size: 16px;"> <div style="position: absolute; right: 10px; margin-top: 10px; font-size: 16px;">
@@ -108,7 +106,7 @@ class SearchPage {
this.parsed = parsed; this.parsed = parsed;
} }
this.header = 'Найти'; this.header = 'Поиск в тексте';
await this.$nextTick(); await this.$nextTick();
this.$refs.input.focus(); this.$refs.input.focus();
this.$refs.input.select(); this.$refs.input.select();

View File

@@ -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>

View File

@@ -1,5 +1,5 @@
<template> <template>
<Window ref="window" height="95%" width="600px" @close="close"> <Window ref="window" width="600px" @close="close">
<template #header> <template #header>
Настройки Настройки
</template> </template>
@@ -24,7 +24,7 @@
<div v-show="tabsScrollable" class="q-pt-lg" /> <div v-show="tabsScrollable" class="q-pt-lg" />
<q-tab class="tab" name="profiles" icon="la la-users" label="Профили" /> <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="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="keys" icon="la la-gamepad" label="Управление" />
<q-tab class="tab" name="pagemove" icon="la la-school" label="Листание" /> <q-tab class="tab" name="pagemove" icon="la la-school" label="Листание" />
<q-tab class="tab" name="convert" icon="la la-magic" label="Конвертир." /> <q-tab class="tab" name="convert" icon="la la-magic" label="Конвертир." />
@@ -82,8 +82,8 @@
</div> </div>
</div> </div>
<!-- Кнопки ----------------------------------------------------------------------> <!-- Кнопки ---------------------------------------------------------------------->
<div v-if="selectedTab == 'buttons'" class="fit tab-panel"> <div v-if="selectedTab == 'toolbar'" class="fit tab-panel">
@@include('./ButtonsTab.inc'); @@include('./ToolBarTab.inc');
</div> </div>
<!-- Управление ------------------------------------------------------------------> <!-- Управление ------------------------------------------------------------------>
<div v-if="selectedTab == 'keys'" class="fit column"> <div v-if="selectedTab == 'keys'" class="fit column">
@@ -702,11 +702,11 @@ export default vueComponent(SettingsPage);
margin-bottom: 5px; margin-bottom: 5px;
} }
.label-1, .label-7 { .label-1, .label-3, .label-7 {
width: 75px; width: 75px;
} }
.label-2, .label-3, .label-4, .label-5 { .label-2, .label-4, .label-5 {
width: 110px; width: 110px;
} }

View 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>

View File

@@ -13,7 +13,7 @@
ref="input" ref="input"
v-model="search" v-model="search"
class="q-ml-sm col" class="q-ml-sm col"
outlined dense rounded outlined dense
bg-color="grey-4" bg-color="grey-4"
placeholder="Найти" placeholder="Найти"
@click.stop @click.stop

View File

@@ -66,7 +66,14 @@ const componentOptions = {
watch: { watch: {
bookPos: function() { bookPos: function() {
this.$emit('book-pos-changed', {bookPos: this.bookPos, bookPosSeen: this.bookPosSeen}); this.$emit('book-pos-changed', {bookPos: this.bookPos, bookPosSeen: this.bookPosSeen});
this.draw(); 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() { bookPosSeen: function() {
this.$emit('book-pos-changed', {bookPos: this.bookPos, bookPosSeen: this.bookPosSeen}); this.$emit('book-pos-changed', {bookPos: this.bookPos, bookPosSeen: this.bookPosSeen});
@@ -99,6 +106,8 @@ class TextPage {
lastBook = null; lastBook = null;
bookPos = 0; bookPos = 0;
bookPosSeen = null; bookPosSeen = null;
prevBookPos = 0;
userBookPosChange = false;
fontStyle = null; fontStyle = null;
fontSize = null; fontSize = null;
@@ -155,7 +164,7 @@ class TextPage {
this.$root.addEventHook('resize', async() => { this.$root.addEventHook('resize', async() => {
this.$nextTick(this.onResize); this.$nextTick(this.onResize);
await utils.sleep(500); await utils.sleep(200);
this.$nextTick(this.onResize); this.$nextTick(this.onResize);
}); });
} }
@@ -499,12 +508,25 @@ class TextPage {
} }
async onResize() { async onResize() {
if (this.resizing)
return;
this.resizing = true;
try { try {
const scrolled = this.doingScrolling;
if (scrolled)
await this.stopTextScrolling();
this.calcDrawProps(); this.calcDrawProps();
this.setBackground(); this.setBackground();
this.draw(); this.draw();
if (scrolled)
this.startTextScrolling();
} catch (e) { } catch (e) {
// //
} finally {
this.resizing = false;
} }
} }
@@ -652,7 +674,7 @@ class TextPage {
} }
if (this.book && this.bookPos > 0 && this.bookPos >= this.parsed.textLength) { if (this.book && this.bookPos > 0 && this.bookPos >= this.parsed.textLength) {
this.doEnd(true); this.doEnd(true, false);
return; return;
} }
@@ -675,7 +697,7 @@ class TextPage {
this.debouncedDrawPageDividerAndOrnament(); this.debouncedDrawPageDividerAndOrnament();
if (this.book && this.linesDown && this.linesDown.length < this.pageLineCount) { if (this.book && this.linesDown && this.linesDown.length < this.pageLineCount) {
this.doEnd(true); this.doEnd(true, false);
return; return;
} }
} }
@@ -911,12 +933,14 @@ class TextPage {
doDown() { doDown() {
if (this.linesDown && this.linesDown.length > this.pageLineCount && this.pageLineCount > 0) { if (this.linesDown && this.linesDown.length > this.pageLineCount && this.pageLineCount > 0) {
this.userBookPosChange = true;
this.bookPos = this.linesDown[1].begin; this.bookPos = this.linesDown[1].begin;
} }
} }
doUp() { doUp() {
if (this.linesUp && this.linesUp.length > 1 && this.pageLineCount > 0) { if (this.linesUp && this.linesUp.length > 1 && this.pageLineCount > 0) {
this.userBookPosChange = true;
this.bookPos = this.linesUp[1].begin; this.bookPos = this.linesUp[1].begin;
} }
} }
@@ -929,6 +953,7 @@ class TextPage {
if (i >= 0 && this.linesDown.length >= 2*i + (this.keepLastToFirst ? 1 : 0)) { if (i >= 0 && this.linesDown.length >= 2*i + (this.keepLastToFirst ? 1 : 0)) {
this.currentAnimation = this.pageChangeAnimation; this.currentAnimation = this.pageChangeAnimation;
this.pageChangeDirectionDown = true; this.pageChangeDirectionDown = true;
this.userBookPosChange = true;
this.bookPos = this.linesDown[i].begin; this.bookPos = this.linesDown[i].begin;
} else } else
this.doEnd(); this.doEnd();
@@ -944,6 +969,7 @@ class TextPage {
if (i >= 0 && this.linesUp.length > i) { if (i >= 0 && this.linesUp.length > i) {
this.currentAnimation = this.pageChangeAnimation; this.currentAnimation = this.pageChangeAnimation;
this.pageChangeDirectionDown = false; this.pageChangeDirectionDown = false;
this.userBookPosChange = true;
this.bookPos = this.linesUp[i].begin; this.bookPos = this.linesUp[i].begin;
} }
} }
@@ -952,10 +978,11 @@ class TextPage {
doHome() { doHome() {
this.currentAnimation = this.pageChangeAnimation; this.currentAnimation = this.pageChangeAnimation;
this.pageChangeDirectionDown = false; this.pageChangeDirectionDown = false;
this.userBookPosChange = true;
this.bookPos = 0; this.bookPos = 0;
} }
doEnd(noAni) { doEnd(noAni, isUser = true) {
if (this.parsed.para.length && this.pageLineCount > 0) { if (this.parsed.para.length && this.pageLineCount > 0) {
let i = this.parsed.para.length - 1; let i = this.parsed.para.length - 1;
let lastPos = this.parsed.para[i].offset + this.parsed.para[i].length - 1; let lastPos = this.parsed.para[i].offset + this.parsed.para[i].length - 1;
@@ -966,6 +993,7 @@ class TextPage {
if (!noAni) if (!noAni)
this.currentAnimation = this.pageChangeAnimation; this.currentAnimation = this.pageChangeAnimation;
this.pageChangeDirectionDown = true; this.pageChangeDirectionDown = true;
this.userBookPosChange = isUser;
this.bookPos = lines[i].begin; this.bookPos = lines[i].begin;
} }
} }

View File

@@ -3,6 +3,7 @@ import sax from '../../../../server/core/sax';
import * as utils from '../../../share/utils'; import * as utils from '../../../share/utils';
const maxImageLineCount = 100; const maxImageLineCount = 100;
const maxParaTextLength = 10000;
// defaults // defaults
const defaultSettings = { const defaultSettings = {
@@ -226,13 +227,25 @@ export default class BookParser {
paraOffset += len; paraOffset += len;
}; };
const growParagraph = (text, len) => { const growParagraph = (text, len, textRaw) => {
if (paraIndex < 0) { if (paraIndex < 0) {
newParagraph(); newParagraph();
growParagraph(text, len); growParagraph(text, len);
return; 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) { if (inSubtitle) {
curSubtitle.title += text; curSubtitle.title += text;
} else if (inTitle) { } else if (inTitle) {
@@ -536,7 +549,7 @@ export default class BookParser {
tClose += (center ? '</center>' : ''); tClose += (center ? '</center>' : '');
if (text != ' ') if (text != ' ')
growParagraph(`${tOpen}${text}${tClose}`, text.length); growParagraph(`${tOpen}${text}${tClose}`, text.length, text);
else else
growParagraph(' ', 1); growParagraph(' ', 1);
} }

View File

@@ -1,10 +1,12 @@
import localForage from 'localforage'; import localForage from 'localforage';
import path from 'path-browserify';
import _ from 'lodash'; import _ from 'lodash';
import * as utils from '../../../share/utils'; import * as utils from '../../../share/utils';
import BookParser from './BookParser'; import BookParser from './BookParser';
const maxDataSize = 500*1024*1024;//compressed bytes const maxDataSize = 500*1024*1024;//compressed bytes
const maxRecentLength = 5000;
//локальный кэш метаданных книг, ограничение maxDataSize //локальный кэш метаданных книг, ограничение maxDataSize
const bmMetaStore = localForage.createInstance({ const bmMetaStore = localForage.createInstance({
@@ -17,9 +19,6 @@ const bmDataStore = localForage.createInstance({
}); });
//список недавно открытых книг //список недавно открытых книг
const bmRecentStoreOld = localForage.createInstance({
name: 'bmRecentStore'
});
const bmRecentStoreNew = localForage.createInstance({ const bmRecentStoreNew = localForage.createInstance({
name: 'bmRecentStoreNew' name: 'bmRecentStoreNew'
}); });
@@ -39,7 +38,7 @@ class BookManager {
this.saveRecentItem = _.debounce(() => { this.saveRecentItem = _.debounce(() => {
bmRecentStoreNew.setItem('recent-item', this.recentItem); 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); bmRecentStoreNew.setItem('rev', this.recentRev);
}, 200, {maxWait: 300}); }, 200, {maxWait: 300});
@@ -54,6 +53,9 @@ class BookManager {
if (this.recentItem) if (this.recentItem)
this.recent[this.recentItem.key] = this.recentItem; this.recent[this.recentItem.key] = this.recentItem;
//конвертируем в новые ключи
await this.convertRecent();
this.recentLastKey = await bmRecentStoreNew.getItem('recent-last-key'); this.recentLastKey = await bmRecentStoreNew.getItem('recent-last-key');
if (this.recentLastKey) { if (this.recentLastKey) {
const meta = await bmMetaStore.getItem(`bmMeta-${this.recentLastKey}`); const meta = await bmMetaStore.getItem(`bmMeta-${this.recentLastKey}`);
@@ -70,6 +72,40 @@ class BookManager {
this.loadStored();//no await 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 //Ленивая асинхронная загрузка bmMetaStore
async loadStored() { async loadStored() {
//даем время для загрузки последней читаемой книги, чтобы не блокировать приложение //даем время для загрузки последней читаемой книги, чтобы не блокировать приложение
@@ -196,8 +232,8 @@ class BookManager {
async addBook(newBook, callback) { async addBook(newBook, callback) {
let meta = {url: newBook.url, path: newBook.path}; let meta = {url: newBook.url, path: newBook.path};
meta.key = this.keyFromUrl(meta.url); meta.key = this.keyFromPath(meta.path);
meta.addTime = Date.now(); meta.addTime = Date.now();//время добавления в кеш
const cb = (perc) => { const cb = (perc) => {
const p = Math.round(30*perc/100); const p = Math.round(30*perc/100);
@@ -232,10 +268,10 @@ class BookManager {
async hasBookParsed(meta) { async hasBookParsed(meta) {
if (!this.books) if (!this.books)
return false; return false;
if (!meta.url) if (!meta.path)
return false; return false;
if (!meta.key) if (!meta.key)
meta.key = this.keyFromUrl(meta.url); meta.key = this.keyFromPath(meta.path);
let book = this.books[meta.key]; let book = this.books[meta.key];
@@ -250,8 +286,12 @@ class BookManager {
async getBook(meta, callback) { async getBook(meta, callback) {
let result = undefined; let result = undefined;
if (!meta.path)
return;
if (!meta.key) if (!meta.key)
meta.key = this.keyFromUrl(meta.url); meta.key = this.keyFromPath(meta.path);
result = this.books[meta.key]; result = this.books[meta.key];
@@ -261,11 +301,6 @@ class BookManager {
this.books[meta.key] = result; this.books[meta.key] = result;
} }
//Если файл на сервере изменился, считаем, что в кеше его нету
if (meta.path && result && meta.path != result.path) {
return;
}
if (result && !result.parsed) { if (result && !result.parsed) {
let data = await bmDataStore.getItem(`bmData-${meta.key}`); let data = await bmDataStore.getItem(`bmData-${meta.key}`);
callback(5); callback(5);
@@ -325,10 +360,20 @@ class BookManager {
return result; return result;
} }
keyFromUrl(url) { /*keyFromUrl(url) {
return utils.stringToHex(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 -------------------------------------------------------------- //-- recent --------------------------------------------------------------
async recentSetItem(item = null, skipCheck = false) { async recentSetItem(item = null, skipCheck = false) {
const rev = await bmRecentStoreNew.getItem('rev'); const rev = await bmRecentStoreNew.getItem('rev');
@@ -369,7 +414,10 @@ class BookManager {
async setRecentBook(value) { async setRecentBook(value) {
let result = this.metaOnly(value); let result = this.metaOnly(value);
result.touchTime = Date.now(); result.touchTime = Date.now();//время последнего чтения
if (!result.loadTime)
result.loadTime = Date.now();//время загрузки файла
result.deleted = 0; result.deleted = 0;
if (this.recent[result.key]) { if (this.recent[result.key]) {
@@ -401,7 +449,7 @@ class BookManager {
const sorted = this.getSortedRecent(); const sorted = this.getSortedRecent();
let isDel = false; 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]; delete this.recent[sorted[i].key];
isDel = true; isDel = true;
} }
@@ -421,7 +469,7 @@ class BookManager {
let max = 0; let max = 0;
let result = null; let result = null;
for (let key in this.recent) { for (const key in this.recent) {
const book = this.recent[key]; const book = this.recent[key];
if (!book.deleted && book.touchTime > max) { if (!book.deleted && book.touchTime > max) {
max = book.touchTime; max = book.touchTime;
@@ -452,6 +500,43 @@ class BookManager {
return result; 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) { async setRecent(value) {
const mergedRecent = _.cloneDeep(this.recent); const mergedRecent = _.cloneDeep(this.recent);

View File

@@ -1,4 +1,26 @@
export const versionHistory = [ 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', version: '0.11.6',
releaseDate: '2022-07-02', releaseDate: '2022-07-02',

View File

@@ -55,6 +55,34 @@
</div> </div>
</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 v-show="type == 'prompt'" class="bg-white no-wrap">
<div class="header row"> <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) { prompt(message, caption, opts) {
return new Promise((resolve) => { return new Promise((resolve) => {
this.enableValidator = false; this.enableValidator = false;

View File

@@ -153,7 +153,7 @@ export default vueComponent(Window);
} }
.header { .header {
background: linear-gradient(to bottom right, green, #59B04F); background: linear-gradient(to bottom right, #007000, #59B04F);
align-items: center; align-items: center;
height: 30px; height: 30px;
} }
@@ -161,8 +161,8 @@ export default vueComponent(Window);
.header-text { .header-text {
margin-left: 10px; margin-left: 10px;
margin-right: 10px; margin-right: 10px;
color: yellow; color: #FFFFA0;
text-shadow: 2px 1px 5px black, 2px 2px 5px black; text-shadow: 2px 2px 5px #005000, 2px 1px 5px #005000;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
} }
@@ -174,7 +174,8 @@ export default vueComponent(Window);
} }
.close-button:hover { .close-button:hover {
background-color: #69C05F; color: white;
background-color: #FF3030;
} }
</style> </style>

View File

@@ -32,6 +32,8 @@ import {QPopupProxy} from 'quasar/src/components/popup-proxy';
import {QDialog} from 'quasar/src/components/dialog'; import {QDialog} from 'quasar/src/components/dialog';
import {QChip} from 'quasar/src/components/chip'; import {QChip} from 'quasar/src/components/chip';
import {QTree} from 'quasar/src/components/tree'; import {QTree} from 'quasar/src/components/tree';
import {QVirtualScroll} from 'quasar/src/components/virtual-scroll';
//import {QExpansionItem} from 'quasar/src/components/expansion-item'; //import {QExpansionItem} from 'quasar/src/components/expansion-item';
const components = { const components = {
@@ -62,6 +64,7 @@ const components = {
QChip, QChip,
QTree, QTree,
//QExpansionItem, //QExpansionItem,
QVirtualScroll,
}; };
//directives //directives

53
client/share/LockQueue.js Normal file
View 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;

View File

@@ -21,7 +21,7 @@ const readerActions = {
'offlineMode': 'Автономный режим (без интернета)', 'offlineMode': 'Автономный режим (без интернета)',
'contents': 'Оглавление/закладки', 'contents': 'Оглавление/закладки',
'libs': 'Сетевая библиотека', 'libs': 'Сетевая библиотека',
'recentBooks': 'Открыть недавние', 'recentBooks': 'Показать загруженные',
'switchToolbar': 'Показать/скрыть панель управления', 'switchToolbar': 'Показать/скрыть панель управления',
'donate': '', 'donate': '',
'bookBegin': 'В начало книги', 'bookBegin': 'В начало книги',
@@ -185,8 +185,12 @@ const settingDefaults = {
fontShifts: {}, fontShifts: {},
showToolButton: {}, showToolButton: {},
toolBarHideOnScroll: true,
userHotKeys: {}, userHotKeys: {},
userWallpapers: [], userWallpapers: [],
recentShowSameBook: false,
recentSortMethod: '',
}; };
for (const font of fonts) for (const font of fonts)
@@ -222,9 +226,6 @@ const libsDefaults = {
{r: 'http://flibusta.is', s: 'http://flibusta.is', list: [ {r: 'http://flibusta.is', s: 'http://flibusta.is', list: [
{l: 'http://flibusta.is', c: 'Флибуста | Книжное братство'}, {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: [ {r: 'http://fantasy-worlds.org', s: 'http://fantasy-worlds.org', list: [
{l: 'http://fantasy-worlds.org', c: 'Миры Фэнтези'}, {l: 'http://fantasy-worlds.org', c: 'Миры Фэнтези'},
]}, ]},

View File

@@ -18,7 +18,7 @@ server {
server_name liberama.top; server_name liberama.top;
client_max_body_size 50m; client_max_body_size 100m;
proxy_read_timeout 1h; proxy_read_timeout 1h;
gzip on; gzip on;
@@ -63,7 +63,7 @@ server {
listen 80; listen 80;
server_name b.liberama.top; server_name b.liberama.top;
client_max_body_size 50m; client_max_body_size 100m;
proxy_read_timeout 1h; proxy_read_timeout 1h;
gzip on; gzip on;

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
if ! pgrep -x "liberama" > /dev/null ; then 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 else
echo "Process 'liberama' already running" echo "Process 'liberama' already running"
fi fi

View File

@@ -7,7 +7,7 @@ server {
server_name omnireader.ru; server_name omnireader.ru;
client_max_body_size 50m; client_max_body_size 100m;
proxy_read_timeout 1h; proxy_read_timeout 1h;
gzip on; gzip on;
@@ -52,7 +52,7 @@ server {
listen 80; listen 80;
server_name old.omnireader.ru; server_name old.omnireader.ru;
client_max_body_size 50m; client_max_body_size 100m;
gzip on; gzip on;
gzip_min_length 1024; gzip_min_length 1024;

View File

@@ -1,4 +1,4 @@
#!/bin/bash #!/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 sudo service cron start

186
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "Liberama", "name": "Liberama",
"version": "0.11.5", "version": "0.11.7",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "Liberama", "name": "Liberama",
"version": "0.11.5", "version": "0.11.7",
"hasInstallScript": true, "hasInstallScript": true,
"license": "CC0-1.0", "license": "CC0-1.0",
"dependencies": { "dependencies": {
@@ -21,15 +21,15 @@
"fs-extra": "^10.1.0", "fs-extra": "^10.1.0",
"he": "^1.2.0", "he": "^1.2.0",
"iconv-lite": "^0.6.3", "iconv-lite": "^0.6.3",
"jembadb": "^3.0.6", "jembadb": "^3.0.8",
"localforage": "^1.10.0", "localforage": "^1.10.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"minimist": "^1.2.5", "minimist": "^1.2.5",
"multer": "^1.4.3", "multer": "^1.4.5-lts.1",
"pako": "^2.0.4", "pako": "^2.0.4",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"pidusage": "^3.0.0", "pidusage": "^3.0.0",
"quasar": "^2.3.2", "quasar": "^2.7.5",
"safe-buffer": "^5.2.1", "safe-buffer": "^5.2.1",
"sanitize-html": "^2.5.3", "sanitize-html": "^2.5.3",
"sjcl": "^1.0.8", "sjcl": "^1.0.8",
@@ -38,8 +38,8 @@
"sqlite3": "^5.0.2", "sqlite3": "^5.0.2",
"tar-fs": "^2.1.1", "tar-fs": "^2.1.1",
"unbzip2-stream": "^1.4.3", "unbzip2-stream": "^1.4.3",
"vue": "^3.2.22", "vue": "^3.2.37",
"vue-router": "^4.0.12", "vue-router": "^4.1.1",
"vuex": "^4.0.2", "vuex": "^4.0.2",
"vuex-persistedstate": "^4.1.0", "vuex-persistedstate": "^4.1.0",
"webdav": "^4.7.0", "webdav": "^4.7.0",
@@ -61,7 +61,7 @@
"css-loader": "^6.5.1", "css-loader": "^6.5.1",
"css-minimizer-webpack-plugin": "^4.0.0", "css-minimizer-webpack-plugin": "^4.0.0",
"eslint": "^8.19.0", "eslint": "^8.19.0",
"eslint-plugin-vue": "^9.1.1", "eslint-plugin-vue": "^9.2.0",
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
"mini-css-extract-plugin": "^2.4.4", "mini-css-extract-plugin": "^2.4.4",
"pkg": "^5.5.1", "pkg": "^5.5.1",
@@ -3239,15 +3239,14 @@
} }
}, },
"node_modules/busboy": { "node_modules/busboy": {
"version": "0.2.14", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
"integrity": "sha512-InWFDomvlkEj+xWLBfU3AvnbVYqeTWmQopiW0tWWEy5yehYm2YkGEc59sUmw/4ty5Zj/b0WHGs1LgecuBSBGrg==", "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
"dependencies": { "dependencies": {
"dicer": "0.2.5", "streamsearch": "^1.1.0"
"readable-stream": "1.1.x"
}, },
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=10.16.0"
} }
}, },
"node_modules/byte-length": { "node_modules/byte-length": {
@@ -4348,18 +4347,6 @@
"node": ">=0.10" "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": { "node_modules/dir-glob": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -4785,9 +4772,9 @@
} }
}, },
"node_modules/eslint-plugin-vue": { "node_modules/eslint-plugin-vue": {
"version": "9.1.1", "version": "9.2.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.1.1.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.2.0.tgz",
"integrity": "sha512-W9n5PB1X2jzC7CK6riG0oAcxjmKrjTF6+keL1rni8n57DZeilx/Fulz+IRJK3lYseLNAygN0I62L7DvioW40Tw==", "integrity": "sha512-W2hc+NUXoce8sZtWgZ45miQTy6jNyuSdub5aZ1IBune4JDeAyzucYX0TzkrQ1jMO52sNUDYlCIHDoaNePe0p5g==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"eslint-utils": "^3.0.0", "eslint-utils": "^3.0.0",
@@ -6429,11 +6416,6 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/isexe": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -6538,9 +6520,9 @@
} }
}, },
"node_modules/jembadb": { "node_modules/jembadb": {
"version": "3.0.6", "version": "3.0.8",
"resolved": "https://registry.npmjs.org/jembadb/-/jembadb-3.0.6.tgz", "resolved": "https://registry.npmjs.org/jembadb/-/jembadb-3.0.8.tgz",
"integrity": "sha512-JRs6bTPDMq/UbkI2OweSSGpJX20JiUSQyBJjAOAn1ITPkFGr7q8F3ClEio1gPcmUJryy6EWMX+UnxDS6smJdJA==", "integrity": "sha512-ukLawbH4IXsKeDTJWNmAEV7D9flQ0m3OMcDSbSjpFWimoyzSn7TyBhWSB8hqmM+9Dyqyh2/nZAcCBu2exXgirQ==",
"engines": { "engines": {
"node": ">=14.4.0" "node": ">=14.4.0"
} }
@@ -7242,22 +7224,20 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}, },
"node_modules/multer": { "node_modules/multer": {
"version": "1.4.4", "version": "1.4.5-lts.1",
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4.tgz", "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz",
"integrity": "sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw==", "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==",
"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.",
"dependencies": { "dependencies": {
"append-field": "^1.0.0", "append-field": "^1.0.0",
"busboy": "^0.2.11", "busboy": "^1.0.0",
"concat-stream": "^1.5.2", "concat-stream": "^1.5.2",
"mkdirp": "^0.5.4", "mkdirp": "^0.5.4",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
"on-finished": "^2.3.0",
"type-is": "^1.6.4", "type-is": "^1.6.4",
"xtend": "^4.0.0" "xtend": "^4.0.0"
}, },
"engines": { "engines": {
"node": ">= 0.10.0" "node": ">= 6.0.0"
} }
}, },
"node_modules/multistream": { "node_modules/multistream": {
@@ -8802,9 +8782,9 @@
} }
}, },
"node_modules/quasar": { "node_modules/quasar": {
"version": "2.7.4", "version": "2.7.5",
"resolved": "https://registry.npmjs.org/quasar/-/quasar-2.7.4.tgz", "resolved": "https://registry.npmjs.org/quasar/-/quasar-2.7.5.tgz",
"integrity": "sha512-8OIa6azm7N6QUPjcZ5AhDCEBha5NnNqt+D1BMIteqaSqkVKFYBf+FMhUCC8R/Tc6Myz85vK7KGPn9tvaC6gXYQ==", "integrity": "sha512-DWI0S+bXASfMSPrB8c/LVsXpA4dF7cBUbaJlcrM+1ioTNBHtiudma2Nhk2SDd5bzk9AYVHh5A8JCZuKqQAXt7g==",
"engines": { "engines": {
"node": ">= 10.18.1", "node": ">= 10.18.1",
"npm": ">= 6.13.4", "npm": ">= 6.13.4",
@@ -8924,17 +8904,6 @@
"node": ">=0.10.0" "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": { "node_modules/rechoir": {
"version": "0.7.1", "version": "0.7.1",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz",
@@ -9735,18 +9704,13 @@
} }
}, },
"node_modules/streamsearch": { "node_modules/streamsearch": {
"version": "0.1.2", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
"integrity": "sha512-jos8u++JKm0ARcSUTAZXOVC0mSox7Bhn6sBgty73P1f3JGf7yG2clTbBNHUdde/kdvP2FESam+vM6l8jBrNxHA==", "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
"engines": { "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": { "node_modules/string-width": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
@@ -10611,11 +10575,11 @@
} }
}, },
"node_modules/vue-router": { "node_modules/vue-router": {
"version": "4.0.16", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.16.tgz", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.1.tgz",
"integrity": "sha512-JcO7cb8QJLBWE+DfxGUL3xUDOae/8nhM1KVdnudadTAORbuxIC/xAydC5Zr/VLHUDQi1ppuTF5/rjBGzgzrJNA==", "integrity": "sha512-Wp1mEf2xCwT0ez7o9JvgpfBp9JGnVb+dPERzXDbugTatzJAJ60VWOhJKifQty85k+jOreoFHER4r5fu062PhPw==",
"dependencies": { "dependencies": {
"@vue/devtools-api": "^6.0.0" "@vue/devtools-api": "^6.1.4"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/posva" "url": "https://github.com/sponsors/posva"
@@ -13960,12 +13924,11 @@
"dev": true "dev": true
}, },
"busboy": { "busboy": {
"version": "0.2.14", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
"integrity": "sha512-InWFDomvlkEj+xWLBfU3AvnbVYqeTWmQopiW0tWWEy5yehYm2YkGEc59sUmw/4ty5Zj/b0WHGs1LgecuBSBGrg==", "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
"requires": { "requires": {
"dicer": "0.2.5", "streamsearch": "^1.1.0"
"readable-stream": "1.1.x"
} }
}, },
"byte-length": { "byte-length": {
@@ -14792,15 +14755,6 @@
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
"dev": true "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": { "dir-glob": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -15220,9 +15174,9 @@
} }
}, },
"eslint-plugin-vue": { "eslint-plugin-vue": {
"version": "9.1.1", "version": "9.2.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.1.1.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.2.0.tgz",
"integrity": "sha512-W9n5PB1X2jzC7CK6riG0oAcxjmKrjTF6+keL1rni8n57DZeilx/Fulz+IRJK3lYseLNAygN0I62L7DvioW40Tw==", "integrity": "sha512-W2hc+NUXoce8sZtWgZ45miQTy6jNyuSdub5aZ1IBune4JDeAyzucYX0TzkrQ1jMO52sNUDYlCIHDoaNePe0p5g==",
"dev": true, "dev": true,
"requires": { "requires": {
"eslint-utils": "^3.0.0", "eslint-utils": "^3.0.0",
@@ -16340,11 +16294,6 @@
"call-bind": "^1.0.2" "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": { "isexe": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -16421,9 +16370,9 @@
} }
}, },
"jembadb": { "jembadb": {
"version": "3.0.6", "version": "3.0.8",
"resolved": "https://registry.npmjs.org/jembadb/-/jembadb-3.0.6.tgz", "resolved": "https://registry.npmjs.org/jembadb/-/jembadb-3.0.8.tgz",
"integrity": "sha512-JRs6bTPDMq/UbkI2OweSSGpJX20JiUSQyBJjAOAn1ITPkFGr7q8F3ClEio1gPcmUJryy6EWMX+UnxDS6smJdJA==" "integrity": "sha512-ukLawbH4IXsKeDTJWNmAEV7D9flQ0m3OMcDSbSjpFWimoyzSn7TyBhWSB8hqmM+9Dyqyh2/nZAcCBu2exXgirQ=="
}, },
"jest-worker": { "jest-worker": {
"version": "27.5.1", "version": "27.5.1",
@@ -16980,16 +16929,15 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}, },
"multer": { "multer": {
"version": "1.4.4", "version": "1.4.5-lts.1",
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4.tgz", "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz",
"integrity": "sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw==", "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==",
"requires": { "requires": {
"append-field": "^1.0.0", "append-field": "^1.0.0",
"busboy": "^0.2.11", "busboy": "^1.0.0",
"concat-stream": "^1.5.2", "concat-stream": "^1.5.2",
"mkdirp": "^0.5.4", "mkdirp": "^0.5.4",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
"on-finished": "^2.3.0",
"type-is": "^1.6.4", "type-is": "^1.6.4",
"xtend": "^4.0.0" "xtend": "^4.0.0"
} }
@@ -18077,9 +18025,9 @@
} }
}, },
"quasar": { "quasar": {
"version": "2.7.4", "version": "2.7.5",
"resolved": "https://registry.npmjs.org/quasar/-/quasar-2.7.4.tgz", "resolved": "https://registry.npmjs.org/quasar/-/quasar-2.7.5.tgz",
"integrity": "sha512-8OIa6azm7N6QUPjcZ5AhDCEBha5NnNqt+D1BMIteqaSqkVKFYBf+FMhUCC8R/Tc6Myz85vK7KGPn9tvaC6gXYQ==" "integrity": "sha512-DWI0S+bXASfMSPrB8c/LVsXpA4dF7cBUbaJlcrM+1ioTNBHtiudma2Nhk2SDd5bzk9AYVHh5A8JCZuKqQAXt7g=="
}, },
"querystring": { "querystring": {
"version": "0.2.1", "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": { "rechoir": {
"version": "0.7.1", "version": "0.7.1",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz",
@@ -18773,14 +18710,9 @@
} }
}, },
"streamsearch": { "streamsearch": {
"version": "0.1.2", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
"integrity": "sha512-jos8u++JKm0ARcSUTAZXOVC0mSox7Bhn6sBgty73P1f3JGf7yG2clTbBNHUdde/kdvP2FESam+vM6l8jBrNxHA==" "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="
},
"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=="
}, },
"string-width": { "string-width": {
"version": "1.0.2", "version": "1.0.2",
@@ -19424,11 +19356,11 @@
} }
}, },
"vue-router": { "vue-router": {
"version": "4.0.16", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.16.tgz", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.1.tgz",
"integrity": "sha512-JcO7cb8QJLBWE+DfxGUL3xUDOae/8nhM1KVdnudadTAORbuxIC/xAydC5Zr/VLHUDQi1ppuTF5/rjBGzgzrJNA==", "integrity": "sha512-Wp1mEf2xCwT0ez7o9JvgpfBp9JGnVb+dPERzXDbugTatzJAJ60VWOhJKifQty85k+jOreoFHER4r5fu062PhPw==",
"requires": { "requires": {
"@vue/devtools-api": "^6.0.0" "@vue/devtools-api": "^6.1.4"
} }
}, },
"vue-style-loader": { "vue-style-loader": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "Liberama", "name": "Liberama",
"version": "0.11.6", "version": "0.11.7",
"author": "Book Pauk <bookpauk@gmail.com>", "author": "Book Pauk <bookpauk@gmail.com>",
"license": "CC0-1.0", "license": "CC0-1.0",
"repository": "bookpauk/liberama", "repository": "bookpauk/liberama",
@@ -32,7 +32,7 @@
"css-loader": "^6.5.1", "css-loader": "^6.5.1",
"css-minimizer-webpack-plugin": "^4.0.0", "css-minimizer-webpack-plugin": "^4.0.0",
"eslint": "^8.19.0", "eslint": "^8.19.0",
"eslint-plugin-vue": "^9.1.1", "eslint-plugin-vue": "^9.2.0",
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
"mini-css-extract-plugin": "^2.4.4", "mini-css-extract-plugin": "^2.4.4",
"pkg": "^5.5.1", "pkg": "^5.5.1",
@@ -59,15 +59,15 @@
"fs-extra": "^10.1.0", "fs-extra": "^10.1.0",
"he": "^1.2.0", "he": "^1.2.0",
"iconv-lite": "^0.6.3", "iconv-lite": "^0.6.3",
"jembadb": "^3.0.6", "jembadb": "^3.0.8",
"localforage": "^1.10.0", "localforage": "^1.10.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"minimist": "^1.2.5", "minimist": "^1.2.5",
"multer": "^1.4.3", "multer": "^1.4.5-lts.1",
"pako": "^2.0.4", "pako": "^2.0.4",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"pidusage": "^3.0.0", "pidusage": "^3.0.0",
"quasar": "^2.3.2", "quasar": "^2.7.5",
"safe-buffer": "^5.2.1", "safe-buffer": "^5.2.1",
"sanitize-html": "^2.5.3", "sanitize-html": "^2.5.3",
"sjcl": "^1.0.8", "sjcl": "^1.0.8",
@@ -76,8 +76,8 @@
"sqlite3": "^5.0.2", "sqlite3": "^5.0.2",
"tar-fs": "^2.1.1", "tar-fs": "^2.1.1",
"unbzip2-stream": "^1.4.3", "unbzip2-stream": "^1.4.3",
"vue": "^3.2.22", "vue": "^3.2.37",
"vue-router": "^4.0.12", "vue-router": "^4.1.1",
"vuex": "^4.0.2", "vuex": "^4.0.2",
"vuex-persistedstate": "^4.1.0", "vuex-persistedstate": "^4.1.0",
"webdav": "^4.7.0", "webdav": "^4.7.0",

View File

@@ -49,7 +49,7 @@ class BaseLog {
this.outputBuffer = []; this.outputBuffer = [];
await this.flushImpl(this.data) 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; this.flushing = false;
} }
@@ -218,6 +218,8 @@ class Logger {
} else { } else {
console.log(mes); console.log(mes);
} }
return mes;
} }
async close() { async close() {

View File

@@ -3,7 +3,7 @@ const chardet = require('chardet');
function getEncoding(buf) { function getEncoding(buf) {
let selected = getEncodingLite(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)); const charsetAll = chardet.analyse(buf.slice(0, 20000));
for (const charset of charsetAll) { for (const charset of charsetAll) {
if (charset.name.indexOf('ISO-8859') < 0) { if (charset.name.indexOf('ISO-8859') < 0) {

View File

@@ -2,6 +2,7 @@ const _ = require('lodash');
const utils = require('../utils'); const utils = require('../utils');
const JembaConnManager = require('../../db/JembaConnManager');//singleton const JembaConnManager = require('../../db/JembaConnManager');//singleton
const log = new (require('../AppLogger'))().log;//singleton
let instance = null; let instance = null;
@@ -20,25 +21,30 @@ class JembaReaderStorage {
} }
async doAction(act) { async doAction(act) {
if (!_.isObject(act.items)) try {
throw new Error('items is not an object'); if (!_.isObject(act.items))
throw new Error('items is not an object');
let result = {}; let result = {};
switch (act.action) { switch (act.action) {
case 'check': case 'check':
result = await this.checkItems(act.items); result = await this.checkItems(act.items);
break; break;
case 'get': case 'get':
result = await this.getItems(act.items); result = await this.getItems(act.items);
break; break;
case 'set': case 'set':
result = await this.setItems(act.items, act.force); result = await this.setItems(act.items, act.force);
break; break;
default: default:
throw new Error('Unknown action'); throw new Error('Unknown action');
}
return result;
} catch (e) {
log(LM_ERR, `JembaReaderStorage: ${e.message}`);
throw e;
} }
return result;
} }
async checkItems(items) { async checkItems(items) {