Compare commits

...

34 Commits

Author SHA1 Message Date
Book Pauk
7b601c9c7f Merge branch 'release/0.9.9-1' 2020-11-21 13:36:11 +07:00
Book Pauk
8d2f74daa4 Небольшие доделки в связи с оптимизацией загрузки списка недавних 2020-11-21 13:34:58 +07:00
Book Pauk
01e82dca5f Merge tag '0.9.9' into develop
0.9.9
2020-11-21 04:06:54 +07:00
Book Pauk
094bb407ed Merge branch 'release/0.9.9' 2020-11-21 04:06:49 +07:00
Book Pauk
338baa55ec Версия 0.9.9 2020-11-21 04:06:16 +07:00
Book Pauk
d06d20a33e Оптимизация загрузки и хранения списка недавних книг 2020-11-21 04:03:54 +07:00
Book Pauk
d46ba6b92b Мелкий рефакторинг 2020-11-21 01:13:30 +07:00
Book Pauk
ec2639039d Поправка бага 2020-11-20 21:57:54 +07:00
Book Pauk
3a211ded2e Мелкий рефакторинг, добавлено удобочитаемое имя при сохраненнии fb2 2020-11-20 21:43:37 +07:00
Book Pauk
c2131e3654 Доработки парсинга fb2 - распознаем тег sequence 2020-11-20 21:26:16 +07:00
Book Pauk
594fb59395 Убрал лишнее 2020-11-20 20:45:47 +07:00
Book Pauk
f44378ec84 Рефакторинг 2020-11-20 18:25:24 +07:00
Book Pauk
0f6b366f62 Расширил лог 2020-11-20 16:17:54 +07:00
Book Pauk
8d0a5997ee Небольшая поправка 2020-11-20 16:06:23 +07:00
Book Pauk
347cb3417e Поправки названий и справки 2020-11-19 15:20:40 +07:00
Book Pauk
337fcebd10 Merge tag '0.9.8-5' into develop
0.9.8-5
2020-11-15 14:57:27 +07:00
Book Pauk
e057b130e9 Merge branch 'release/0.9.8-5' 2020-11-15 14:57:23 +07:00
Book Pauk
19a0765a1a Рефакторинг, плюс небольшие доделки 2020-11-15 14:55:02 +07:00
Book Pauk
b81cd3240b Merge tag '0.9.8-4' into develop
0.9.8-4
2020-11-15 00:09:10 +07:00
Book Pauk
1e6105b076 Merge branch 'release/0.9.8-4' 2020-11-15 00:09:03 +07:00
Book Pauk
d8d89b3463 Добавлен показ аннотации в начале книги 2020-11-15 00:07:45 +07:00
Book Pauk
459564cb2d Рефакторинг 2020-11-14 23:43:22 +07:00
Book Pauk
084df35184 Мелкий рефакторинг 2020-11-14 23:32:39 +07:00
Book Pauk
81912babeb Merge tag '0.9.8-3' into develop
0.9.8-3
2020-11-14 23:27:14 +07:00
Book Pauk
3943fc7d95 Merge branch 'release/0.9.8-3' 2020-11-14 23:27:09 +07:00
Book Pauk
3999dc930c Рефакторинг, плюс небольшие изменения внешнего вида 2020-11-14 23:24:20 +07:00
Book Pauk
d4dea16456 Убрал неиспользуемые компоненты 2020-11-14 23:23:51 +07:00
Book Pauk
ed38cb33a5 Merge tag '0.9.8-2' into develop
0.9.8-2
2020-11-14 12:01:44 +07:00
Book Pauk
4cf5a0f4c8 Merge branch 'release/0.9.8-2' 2020-11-14 12:01:39 +07:00
Book Pauk
8f0d526af2 Скрыл хоткей для "Библиотека" в режиме omnireader 2020-11-14 12:00:55 +07:00
Book Pauk
6ca3881841 Merge tag '0.9.8-1' into develop
0.9.8-1
2020-11-14 11:49:06 +07:00
Book Pauk
d8e765a04f Merge branch 'release/0.9.8-1' 2020-11-14 11:49:02 +07:00
Book Pauk
40ff572f94 Добавил проксирование для fantasy-worlds.org 2020-11-14 11:48:03 +07:00
Book Pauk
cc4275dc03 Merge tag '0.9.8' into develop
0.9.8
2020-11-14 00:05:14 +07:00
17 changed files with 423 additions and 255 deletions

View File

@@ -159,6 +159,7 @@ import * as lu from './linkUtils';
const proxySubst = { const proxySubst = {
'http://flibusta.is': 'http://b.liberama.top:23480', 'http://flibusta.is': 'http://b.liberama.top:23480',
'http://fantasy-worlds.org': 'http://b.liberama.top:23580',
}; };
export default @Component({ export default @Component({
@@ -407,7 +408,7 @@ class ExternalLibs extends Vue {
} }
get header() { get header() {
let result = (this.ready ? 'Библиотека' : 'Загрузка...'); let result = (this.ready ? 'Сетевая библиотека' : 'Загрузка...');
if (this.ready && this.selectedLink) { if (this.ready && this.selectedLink) {
result += ` | ${(this.libs.comment ? this.libs.comment + ' ': '') + lu.removeProtocol(this.libs.startLink)}`; result += ` | ${(this.libs.comment ? this.libs.comment + ' ': '') + lu.removeProtocol(this.libs.startLink)}`;
} }
@@ -786,12 +787,17 @@ class ExternalLibs extends Vue {
showHelp() { showHelp() {
this.$root.stdDialog.alert(` this.$root.stdDialog.alert(`
<p>Окно 'Библиотека' позволяет открывать ссылки в читалке без переключения между окнами, <p>Окно 'Сетевая библиотека' позволяет открывать ссылки в читалке без переключения между окнами,
что особенно актуально для мобильных устройств.</p> что особенно актуально для мобильных устройств. Имеется возможность управлять закладками
<p>'Библиотека' разрешает свободный доступ к сайту flibusta.is. Имеется возможность управлять закладками
на понравившиеся ресурсы, книги или страницы авторов. Открытие ссылок и навигация происходят во фрейме, но, на понравившиеся ресурсы, книги или страницы авторов. Открытие ссылок и навигация происходят во фрейме, но,
к сожалению, в нем открываются не все страницы. к сожалению, в нем открываются не все страницы.</p>
<p>Доступ к сайтам <span style="color: blue">http://flibusta.is</span> и <span style="color: blue">http://fantasy-worlds.org</span> работает через прокси.
<br><span style="color: red"><b>ПРЕДУПРЕЖДЕНИЕ!</b></span>
Доступ предназначен только для просмотра и скачивания книг. Авторизоваться на этих сайтах
из фрейма категорически не рекомендуется, т.к. ваше подключение не защищено и данные могут попасть
к третьим лицам.
</p> </p>
<p>Из-за проблем с безопасностью, навигация 'вперед-назад' во фрейме осуществляется с помощью контекстного меню правой кнопкой мыши. <p>Из-за проблем с безопасностью, навигация 'вперед-назад' во фрейме осуществляется с помощью контекстного меню правой кнопкой мыши.

View File

@@ -24,36 +24,34 @@
<div class="tab-panel" v-show="selectedTab == 'contents'"> <div class="tab-panel" v-show="selectedTab == 'contents'">
<div> <div>
<div class="row" v-for="item in contents" :key="item.key"> <div v-for="item in contents" :key="item.key" class="column" style="width: 540px">
<q-expansion-item v-if="item.list.length" <div class="row item q-px-sm no-wrap">
class="item separator-bottom" <div v-if="item.list.length" class="row justify-center items-center expand-button clickable" @click="expandClick(item.key)">
expand-icon-toggle <q-icon name="la la-caret-right" class="icon" :class="{'expanded-icon': item.expanded}" color="green-8" size="20px"/>
switch-toggle-side </div>
expand-icon="la la-arrow-circle-down" <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"/>
<template slot="header"> </div>
<div class="row no-wrap clickable" style="width: 465px" @click="setBookPos(item.offset)"> <div class="col row clickable" @click="setBookPos(item.offset)">
<div :style="item.style"></div> <div :style="item.indentStyle"></div>
<div class="q-mr-sm col overflow-hidden column justify-center" v-html="item.label"></div> <div class="q-mr-sm col overflow-hidden column justify-center" :style="item.labelStyle" v-html="item.label"></div>
<div class="column justify-center">{{ item.perc }}%</div> <div class="column justify-center">{{ item.perc }}%</div>
</div> </div>
</template> </div>
<q-item class="subitem separator-top column justify-center" v-for="subitem in item.list" :key="subitem.key"> <div v-if="item.expanded" :ref="`subitem${item.key}`" class="subitems-transition">
<div class="row no-wrap clickable" style="padding-left: 55px; width: 520px" @click="setBookPos(subitem.offset)"> <div v-for="subitem in item.list" :key="subitem.key" class="row subitem q-px-sm no-wrap">
<div :style="subitem.style"></div> <div class="col row clickable" @click="setBookPos(subitem.offset)">
<div class="q-mr-sm col overflow-hidden column justify-center" v-html="subitem.label"></div> <div class="no-expand-button"></div>
<div :style="subitem.indentStyle"></div>
<div class="q-mr-sm col overflow-hidden column justify-center" :style="item.labelStyle" v-html="subitem.label"></div>
<div class="column justify-center">{{ subitem.perc }}%</div> <div class="column justify-center">{{ subitem.perc }}%</div>
</div> </div>
</q-item>
</q-expansion-item>
<q-item v-else class="item separator-bottom">
<div class="row no-wrap clickable" style="padding-left: 55px; width: 520px" @click="setBookPos(item.offset)">
<div :style="item.style"></div>
<div class="q-mr-sm col overflow-hidden column justify-center" v-html="item.label"></div>
<div class="column justify-center">{{ item.perc }}%</div>
</div> </div>
</q-item> </div>
</div>
<div v-if="!contents.length" class="column justify-center items-center" style="height: 100px">
Оглавление отсутствует
</div> </div>
</div> </div>
</div> </div>
@@ -74,7 +72,7 @@ import Component from 'vue-class-component';
//import _ from 'lodash'; //import _ from 'lodash';
import Window from '../../share/Window.vue'; import Window from '../../share/Window.vue';
//import * as utils from '../../../share/utils'; import * as utils from '../../../share/utils';
export default @Component({ export default @Component({
components: { components: {
@@ -93,25 +91,19 @@ class ContentsPage extends Vue {
async init(currentBook, parsed) { async init(currentBook, parsed) {
this.$refs.window.init(); this.$refs.window.init();
if (this.parsed != parsed) { //закладки
//далее формаирование оглавления
if (this.parsed == parsed)
return;
this.parsed = parsed;
this.contents = []; this.contents = [];
await this.$nextTick(); await this.$nextTick();
this.parsed = parsed;
}
const prepareLabel = (title, bolder = false) => {
let titleParts = title.split('<p>');
const textParts = titleParts.filter(v => v).map(v => `<div>${v.replace(/(<([^>]+)>)/ig, '')}</div>`);
if (bolder && textParts.length > 1)
textParts[0] = `<b>${textParts[0]}</b>`;
return textParts.join('');
}
const insetStyle = inset => `width: ${inset*20}px`;
const pc = parsed.contents; const pc = parsed.contents;
const newpc = []; const newpc = [];
//преобразуем все, кроме первого, разделы body в title-subtitle
//преобразуем не первые разделы body в title-subtitle
let curSubtitles = []; let curSubtitles = [];
let prevBodyIndex = -1; let prevBodyIndex = -1;
for (let i = 0; i < pc.length; i++) { for (let i = 0; i < pc.length; i++) {
@@ -132,25 +124,43 @@ class ContentsPage extends Vue {
} }
} }
const prepareLabel = (title, bolder = false) => {
let titleParts = title.split('<p>');
const textParts = titleParts.filter(v => v).map(v => `<div>${utils.removeHtmlTags(v)}</div>`);
if (bolder && textParts.length > 1)
textParts[0] = `<b>${textParts[0]}</b>`;
return textParts.join('');
}
const getIndentStyle = inset => `width: ${inset*20}px`;
const getLabelStyle = (inset) => {
const fontSizes = ['110%', '100%', '90%', '85%'];
inset = (inset > 3 ? 3 : inset);
return `font-size: ${fontSizes[inset]}`;
};
//формируем newContents //формируем newContents
let i = 0; let i = 0;
const newContents = []; const newContents = [];
newpc.forEach((cont) => { newpc.forEach((cont) => {
const label = prepareLabel(cont.title, true); const label = prepareLabel(cont.title, true);
const style = insetStyle(cont.inset); const indentStyle = getIndentStyle(cont.inset);
const labelStyle = getLabelStyle(cont.inset);
let j = 0; let j = 0;
const list = []; const list = [];
cont.subtitles.forEach((sub) => { cont.subtitles.forEach((sub) => {
const l = prepareLabel(sub.title); const l = prepareLabel(sub.title);
const s = insetStyle(sub.inset + 1); const s = getIndentStyle(sub.inset + 1);
const ls = getLabelStyle(cont.inset + 1);
const p = parsed.para[sub.paraIndex]; const p = parsed.para[sub.paraIndex];
list.push({perc: (p.offset/parsed.textLength*100).toFixed(2), label: l, key: j, offset: p.offset, style: s}); list[j] = {perc: (p.offset/parsed.textLength*100).toFixed(2), label: l, key: j, offset: p.offset, indentStyle: s, labelStyle: ls};
j++; j++;
}); });
const p = parsed.para[cont.paraIndex]; const p = parsed.para[cont.paraIndex];
newContents.push({perc: (p.offset/parsed.textLength*100).toFixed(0), label, key: i, offset: p.offset, style, list}); newContents[i] = {perc: (p.offset/parsed.textLength*100).toFixed(0), label, key: i, offset: p.offset, indentStyle, labelStyle, expanded: false, list};
i++; i++;
}); });
@@ -158,9 +168,27 @@ class ContentsPage extends Vue {
this.contents = newContents; this.contents = newContents;
} }
async expandClick(key) {
const item = this.contents[key];
const expanded = !item.expanded;
if (!expanded) {
const subitems = this.$refs[`subitem${key}`][0];
subitems.style.height = '0';
await utils.sleep(200);
}
this.$set(this.contents, key, Object.assign({}, item, {expanded}));
if (expanded) {
await this.$nextTick();
const subitems = this.$refs[`subitem${key}`][0];
subitems.style.height = subitems.scrollHeight + 'px';
}
}
async setBookPos(newValue) { async setBookPos(newValue) {
this.$emit('book-pos-changed', {bookPos: newValue}); this.$emit('book-pos-changed', {bookPos: newValue});
await this.$nextTick();
this.close(); this.close();
} }
@@ -188,20 +216,32 @@ class ContentsPage extends Vue {
.clickable { .clickable {
cursor: pointer; cursor: pointer;
padding: 10px 0 10px 0;
} }
.item:hover { .item, .subitem {
border-bottom: 1px solid #e0e0e0;
}
.item:hover, .subitem:hover {
background-color: #f0f0f0; background-color: #f0f0f0;
} }
.subitem:hover { .expand-button, .no-expand-button {
background-color: #e0e0e0; width: 40px;
} }
.separator-top { .subitems-transition {
border-top: 1px solid #e0e0e0; height: 0;
transition: height 0.2s linear;
overflow: hidden;
} }
.separator-bottom {
border-top: 1px solid #e0e0e0; .icon {
transition: transform 0.2s;
}
.expanded-icon {
transform: rotate(90deg);
} }
</style> </style>

View File

@@ -250,11 +250,11 @@ class Reader extends Vue {
await this.$nextTick(); await this.$nextTick();
this.paramPosIgnore = false; this.paramPosIgnore = false;
} }
}, 500, {'maxWait':5000}); }, 500, {maxWait: 5000});
this.scrollingSetRecentBook = _.debounce((newValue) => { this.scrollingSetRecentBook = _.debounce((newValue) => {
this.debouncedSetRecentBook(newValue); this.debouncedSetRecentBook(newValue);
}, 15000, {'maxWait':20000}); }, 15000, {maxWait: 20000});
document.addEventListener('fullscreenchange', () => { document.addEventListener('fullscreenchange', () => {
this.fullScreenActive = (document.fullscreenElement !== null); this.fullScreenActive = (document.fullscreenElement !== null);

View File

@@ -59,7 +59,7 @@
<q-td key="links" :props="props" class="td-mp" auto-width> <q-td key="links" :props="props" class="td-mp" auto-width>
<div class="break-word" style="width: 75px; font-size: 90%"> <div class="break-word" style="width: 75px; font-size: 90%">
<a v-show="isUrl(props.row.url)" :href="props.row.url" target="_blank">Оригинал</a><br> <a v-show="isUrl(props.row.url)" :href="props.row.url" target="_blank">Оригинал</a><br>
<a :href="props.row.path" @click.prevent="downloadBook(props.row.path)">Скачать FB2</a> <a :href="props.row.path" @click.prevent="downloadBook(props.row.path, props.row.fullTitle)">Скачать FB2</a>
</div> </div>
</q-td> </q-td>
@@ -87,7 +87,7 @@
import Vue from 'vue'; import Vue from 'vue';
import Component from 'vue-class-component'; import Component from 'vue-class-component';
import path from 'path'; import path from 'path';
import _ from 'lodash'; //import _ from 'lodash';
import * as utils from '../../../share/utils'; import * as utils from '../../../share/utils';
import Window from '../../share/Window.vue'; import Window from '../../share/Window.vue';
@@ -112,6 +112,7 @@ class RecentBooksPage extends Vue {
pagination = {}; pagination = {};
created() { created() {
this.firstInit = true;
this.pagination = {rowsPerPage: 0}; this.pagination = {rowsPerPage: 0};
this.columns = [ this.columns = [
@@ -167,26 +168,11 @@ class RecentBooksPage extends Vue {
this.initing = true; this.initing = true;
if (!bookManager.loaded) { if (this.firstInit) {//для отзывчивости
await this.updateTableData(10);
//для отзывчивости
await utils.sleep(100);
let i = 0;
let j = 5;
while (i < 500 && !bookManager.loaded) {
if (i % j == 0) {
bookManager.sortedRecentCached = null;
await this.updateTableData(20); await this.updateTableData(20);
j *= 2; this.firstInit = false;
}
await utils.sleep(100);
i++;
}
} else {
//для отзывчивости
await utils.sleep(100);
} }
await utils.sleep(50);
await this.updateTableData(); await this.updateTableData();
this.initing = false; this.initing = false;
})(); })();
@@ -222,30 +208,11 @@ class RecentBooksPage extends Vue {
textLen = ` ${Math.round(book.textLength/1000)}k`; textLen = ` ${Math.round(book.textLength/1000)}k`;
} }
const fb2 = (book.fb2 ? book.fb2 : {}); const bt = utils.getBookTitle(book.fb2);
let title = fb2.bookTitle; let title = bt.bookTitle;
if (title) title = (title ? `"${title}"`: '');
title = `"${title}"`; const author = (bt.author ? bt.author : (bt.bookTitle ? bt.bookTitle : book.url));
else
title = '';
let author = '';
if (fb2.author) {
const authorNames = fb2.author.map(a => _.compact([
a.lastName,
a.firstName,
a.middleName
]).join(' '));
author = authorNames.join(', ');
} else {//TODO: убрать в будущем
author = _.compact([
fb2.lastName,
fb2.firstName,
fb2.middleName
]).join(' ');
}
author = (author ? author : (fb2.bookTitle ? fb2.bookTitle : book.url));
result.push({ result.push({
num, num,
@@ -256,9 +223,10 @@ class RecentBooksPage extends Vue {
author, author,
title: `${title}${perc}${textLen}`, title: `${title}${perc}${textLen}`,
}, },
descString: `${author}${title}${perc}${textLen}`, descString: `${author}${title}${perc}${textLen}`,//для сортировки
url: book.url, url: book.url,
path: book.path, path: book.path,
fullTitle: bt.fullTitle,
key: book.key, key: book.key,
}); });
} }
@@ -291,13 +259,18 @@ class RecentBooksPage extends Vue {
return `${(this.search ? 'Найдено' : 'Всего')} ${len} книг${this.wordEnding(len)}`; return `${(this.search ? 'Найдено' : 'Всего')} ${len} книг${this.wordEnding(len)}`;
} }
async downloadBook(fb2path) { async downloadBook(fb2path, fullTitle) {
try { try {
await readerApi.checkCachedBook(fb2path); await readerApi.checkCachedBook(fb2path);
const d = this.$refs.download; const d = this.$refs.download;
d.href = fb2path; d.href = fb2path;
try {
const fn = utils.makeValidFilename(fullTitle);
d.download = fn.substring(0, 100) + '.fb2';
} catch(e) {
d.download = path.basename(fb2path).substr(0, 10) + '.fb2'; d.download = path.basename(fb2path).substr(0, 10) + '.fb2';
}
d.click(); d.click();
} catch (e) { } catch (e) {
@@ -308,14 +281,6 @@ class RecentBooksPage extends Vue {
} }
} }
openOriginal(url) {
window.open(url, '_blank');
}
openFb2(path) {
window.open(path, '_blank');
}
async handleDel(key) { async handleDel(key) {
await bookManager.delRecentBook({key}); await bookManager.delRecentBook({key});
//this.updateTableData();//обновление уже происходит Reader.bookManagerEvent //this.updateTableData();//обновление уже происходит Reader.bookManagerEvent

View File

@@ -507,10 +507,10 @@ class ServerStorage extends Vue {
if (md.key && result[md.key]) if (md.key && result[md.key])
result[md.key] = utils.applyObjDiff(result[md.key], md.mod, {isAddChanged: true}); result[md.key] = utils.applyObjDiff(result[md.key], md.mod, {isAddChanged: true});
if (!bookManager.loaded) { /*if (!bookManager.loaded) {
this.warning('Ожидание загрузки списка книг перед синхронизацией'); this.warning('Ожидание загрузки списка книг перед синхронизацией');
while (!bookManager.loaded) await utils.sleep(100); while (!bookManager.loaded) await utils.sleep(100);
} }*/
if (newRecent.rev != this.cachedRecent.rev) if (newRecent.rev != this.cachedRecent.rev)
await this.setCachedRecent(newRecent); await this.setCachedRecent(newRecent);
@@ -580,8 +580,8 @@ class ServerStorage extends Vue {
let newRecent = {}; let newRecent = {};
if (!itemKey || (needSaveRecentPatch && Object.keys(newRecentPatch.data).length > 10)) { if (!itemKey || (needSaveRecentPatch && Object.keys(newRecentPatch.data).length > 10)) {
//ждем весь bm.recent //ждем весь bm.recent
while (!bookManager.loaded) /*while (!bookManager.loaded)
await utils.sleep(100); await utils.sleep(100);*/
newRecent = {rev: this.cachedRecent.rev + 1, data: _.cloneDeep(bm.recent)}; newRecent = {rev: this.cachedRecent.rev + 1, data: _.cloneDeep(bm.recent)};
newRecentPatch = {rev: this.cachedRecentPatch.rev + 1, data: {}}; newRecentPatch = {rev: this.cachedRecentPatch.rev + 1, data: {}};

View File

@@ -104,8 +104,12 @@ class UserHotKeys extends UserHotKeysProps {
this.updateTableData(); this.updateTableData();
} }
get mode() {
return this.$store.state.config.mode;
}
updateTableData() { updateTableData() {
let result = rstore.hotKeys.map(hk => hk.name); let result = rstore.hotKeys.map(hk => hk.name).filter(name => (this.mode == 'liberama.top' || name != 'libs'));
const search = this.search.toLowerCase(); const search = this.search.toLowerCase();
const codesIncludeSearch = (action) => { const codesIncludeSearch = (action) => {

View File

@@ -40,7 +40,7 @@ import Component from 'vue-class-component';
import {loadCSS} from 'fg-loadcss'; import {loadCSS} from 'fg-loadcss';
import _ from 'lodash'; import _ from 'lodash';
import {sleep} from '../../../share/utils'; import * as utils from '../../../share/utils';
import bookManager from '../share/bookManager'; import bookManager from '../share/bookManager';
import DrawHelper from './DrawHelper'; import DrawHelper from './DrawHelper';
import rstore from '../../../store/modules/reader'; import rstore from '../../../store/modules/reader';
@@ -134,7 +134,7 @@ class TextPage extends Vue {
this.$root.$on('resize', async() => { this.$root.$on('resize', async() => {
this.$nextTick(this.onResize); this.$nextTick(this.onResize);
await sleep(500); await utils.sleep(500);
this.$nextTick(this.onResize); this.$nextTick(this.onResize);
}); });
} }
@@ -285,7 +285,7 @@ class TextPage extends Vue {
let close = null; let close = null;
(async() => { (async() => {
await sleep(500); await utils.sleep(500);
if (this.fontsLoading) if (this.fontsLoading)
close = this.$root.notify.info('Загрузка шрифта &nbsp;<i class="la la-snowflake icon-rotate" style="font-size: 150%"></i>'); close = this.$root.notify.info('Загрузка шрифта &nbsp;<i class="la la-snowflake icon-rotate" style="font-size: 150%"></i>');
})(); })();
@@ -342,7 +342,7 @@ class TextPage extends Vue {
let i = 0; let i = 0;
const t = this.parsed.testText; const t = this.parsed.testText;
while (i++ < 50 && this.parsed === parsed && this.drawHelper.measureText(t, {}) === this.parsed.testWidth) while (i++ < 50 && this.parsed === parsed && this.drawHelper.measureText(t, {}) === this.parsed.testWidth)
await sleep(100); await utils.sleep(100);
if (this.parsed === parsed) { if (this.parsed === parsed) {
this.parsed.testWidth = this.drawHelper.measureText(t, {}); this.parsed.testWidth = this.drawHelper.measureText(t, {});
@@ -366,7 +366,6 @@ class TextPage extends Vue {
this.updateLayout(); this.updateLayout();
this.book = null; this.book = null;
this.meta = null; this.meta = null;
this.fb2 = null;
this.parsed = null; this.parsed = null;
this.linesUp = null; this.linesUp = null;
@@ -383,7 +382,7 @@ class TextPage extends Vue {
try { try {
//подождем ленивый парсинг //подождем ленивый парсинг
this.stopLazyParse = true; this.stopLazyParse = true;
while (this.doingLazyParse) await sleep(10); while (this.doingLazyParse) await utils.sleep(10);
const isParsed = await bookManager.hasBookParsed(this.lastBook); const isParsed = await bookManager.hasBookParsed(this.lastBook);
if (!isParsed) { if (!isParsed) {
@@ -392,21 +391,9 @@ class TextPage extends Vue {
this.book = await bookManager.getBook(this.lastBook); this.book = await bookManager.getBook(this.lastBook);
this.meta = bookManager.metaOnly(this.book); this.meta = bookManager.metaOnly(this.book);
this.fb2 = this.meta.fb2; const bt = utils.getBookTitle(this.meta.fb2);
let authorNames = []; this.title = bt.fullTitle;
if (this.fb2.author) {
authorNames = this.fb2.author.map(a => _.compact([
a.lastName,
a.firstName,
a.middleName
]).join(' '));
}
this.title = _.compact([
authorNames.join(', '),
this.fb2.bookTitle
]).join(' - ');
this.$root.$emit('set-app-title', this.title); this.$root.$emit('set-app-title', this.title);
@@ -493,7 +480,7 @@ class TextPage extends Vue {
let wait = (timeout + 201)/100; let wait = (timeout + 201)/100;
while (wait > 0 && !this[stopPropertyName]) { while (wait > 0 && !this[stopPropertyName]) {
wait--; wait--;
await sleep(100); await utils.sleep(100);
} }
resolve(); resolve();
})().catch(reject); }); })().catch(reject); });
@@ -509,7 +496,7 @@ class TextPage extends Vue {
} }
//ждем анимацию //ждем анимацию
while (this.inAnimation) await sleep(10); while (this.inAnimation) await utils.sleep(10);
this.stopScrolling = false; this.stopScrolling = false;
this.doingScrolling = true; this.doingScrolling = true;
@@ -520,7 +507,7 @@ class TextPage extends Vue {
this.page1 = this.page2; this.page1 = this.page2;
this.toggleLayout = true; this.toggleLayout = true;
await this.$nextTick(); await this.$nextTick();
await sleep(50); await utils.sleep(50);
this.cachedPos = -1; this.cachedPos = -1;
this.draw(); this.draw();
@@ -557,7 +544,7 @@ class TextPage extends Vue {
page.style.transform = 'none'; page.style.transform = 'none';
page.offsetHeight; page.offsetHeight;
while (this.doingScrolling) await sleep(10); while (this.doingScrolling) await utils.sleep(10);
} }
draw() { draw() {
@@ -766,7 +753,7 @@ class TextPage extends Vue {
for (let i = 0; i < this.parsed.para.length; i++) { for (let i = 0; i < this.parsed.para.length; i++) {
j++; j++;
if (j > 1) { if (j > 1) {
await sleep(1); await utils.sleep(1);
j = 0; j = 0;
} }
if (this.stopLazyParse) if (this.stopLazyParse)
@@ -788,7 +775,7 @@ class TextPage extends Vue {
async refreshTime() { async refreshTime() {
if (!this.timeRefreshing) { if (!this.timeRefreshing) {
this.timeRefreshing = true; this.timeRefreshing = true;
await sleep(60*1000); await utils.sleep(60*1000);
if (this.book && this.parsed.textLength) { if (this.book && this.parsed.textLength) {
this.debouncedDrawStatusBar(); this.debouncedDrawStatusBar();
@@ -905,7 +892,7 @@ class TextPage extends Vue {
this.settingsChanging = true; this.settingsChanging = true;
const newSize = (this.settings.fontSize + 1 < 200 ? this.settings.fontSize + 1 : 100); const newSize = (this.settings.fontSize + 1 < 200 ? this.settings.fontSize + 1 : 100);
this.commit('reader/setSettings', {fontSize: newSize}); this.commit('reader/setSettings', {fontSize: newSize});
await sleep(50); await utils.sleep(50);
this.settingsChanging = false; this.settingsChanging = false;
} }
} }
@@ -915,7 +902,7 @@ class TextPage extends Vue {
this.settingsChanging = true; this.settingsChanging = true;
const newSize = (this.settings.fontSize - 1 > 5 ? this.settings.fontSize - 1 : 5); const newSize = (this.settings.fontSize - 1 > 5 ? this.settings.fontSize - 1 : 5);
this.commit('reader/setSettings', {fontSize: newSize}); this.commit('reader/setSettings', {fontSize: newSize});
await sleep(50); await utils.sleep(50);
this.settingsChanging = false; this.settingsChanging = false;
} }
} }
@@ -925,7 +912,7 @@ class TextPage extends Vue {
this.settingsChanging = true; this.settingsChanging = true;
const newDelay = (this.settings.scrollingDelay - 50 > 1 ? this.settings.scrollingDelay - 50 : 1); const newDelay = (this.settings.scrollingDelay - 50 > 1 ? this.settings.scrollingDelay - 50 : 1);
this.commit('reader/setSettings', {scrollingDelay: newDelay}); this.commit('reader/setSettings', {scrollingDelay: newDelay});
await sleep(50); await utils.sleep(50);
this.settingsChanging = false; this.settingsChanging = false;
} }
} }
@@ -935,7 +922,7 @@ class TextPage extends Vue {
this.settingsChanging = true; this.settingsChanging = true;
const newDelay = (this.settings.scrollingDelay + 50 < 10000 ? this.settings.scrollingDelay + 50 : 10000); const newDelay = (this.settings.scrollingDelay + 50 < 10000 ? this.settings.scrollingDelay + 50 : 10000);
this.commit('reader/setSettings', {scrollingDelay: newDelay}); this.commit('reader/setSettings', {scrollingDelay: newDelay});
await sleep(50); await utils.sleep(50);
this.settingsChanging = false; this.settingsChanging = false;
} }
} }
@@ -949,7 +936,7 @@ class TextPage extends Vue {
let delay = 400; let delay = 400;
while (this.repDoing) { while (this.repDoing) {
this.handleClick(pointX, pointY); this.handleClick(pointX, pointY);
await sleep(delay); await utils.sleep(delay);
if (delay > 15) if (delay > 15)
delay *= 0.8; delay *= 0.8;
} }

View File

@@ -1,6 +1,6 @@
import he from 'he'; import he from 'he';
import sax from '../../../../server/core/sax'; import sax from '../../../../server/core/sax';
import {sleep} from '../../../share/utils'; import * as utils from '../../../share/utils';
const maxImageLineCount = 100; const maxImageLineCount = 100;
@@ -90,7 +90,7 @@ export default class BookParser {
i.onerror = reject; i.onerror = reject;
i.src = `data:${binaryType};base64,${data}`; i.src = `data:${binaryType};base64,${data}`;
await sleep(30*1000); await utils.sleep(30*1000);
if (!resolved) if (!resolved)
reject('Не удалось получить размер изображения'); reject('Не удалось получить размер изображения');
})().catch(reject); }); })().catch(reject); });
@@ -112,7 +112,7 @@ export default class BookParser {
i.onerror = reject; i.onerror = reject;
i.src = src; i.src = src;
await sleep(30*1000); await utils.sleep(30*1000);
if (!resolved) if (!resolved)
reject('Не удалось получить размер изображения'); reject('Не удалось получить размер изображения');
})().catch(reject); }); })().catch(reject); });
@@ -216,14 +216,51 @@ export default class BookParser {
} }
} }
if (tag == 'author' && path.indexOf('/fictionbook/description/title-info/author') == 0) { if (path == '/fictionbook/description/title-info/author') {
if (!fb2.author) if (!fb2.author)
fb2.author = []; fb2.author = [];
fb2.author.push({}); fb2.author.push({});
} }
const isPublishSequence = (path == '/fictionbook/description/publish-info/sequence');
if (path == '/fictionbook/description/title-info/sequence' || isPublishSequence) {
if (!fb2.sequence)
fb2.sequence = [];
if (!isPublishSequence || !fb2.sequence.length) {
const attrs = sax.getAttrsSync(tail);
const seq = {};
if (attrs.name && attrs.name.value) {
seq.name = attrs.name.value;
}
if (attrs.number && attrs.number.value) {
seq.number = attrs.number.value;
}
fb2.sequence.push(seq);
}
}
if (path.indexOf('/fictionbook/body') == 0) { if (path.indexOf('/fictionbook/body') == 0) {
if (tag == 'body') { if (tag == 'body') {
if (isFirstBody && fb2.annotation) {
const ann = fb2.annotation.split('<p>').filter(v => v).map(v => utils.removeHtmlTags(v));
ann.forEach(a => {
newParagraph(`<emphasis><space w="1">${a}</space></emphasis>`, a.length);
});
if (ann.length)
newParagraph(' ', 1);
}
if (isFirstBody && fb2.sequence && fb2.sequence.length) {
const bt = utils.getBookTitle(fb2);
if (bt.sequence) {
newParagraph(bt.sequence, bt.sequence.length);
newParagraph(' ', 1);
}
}
if (!isFirstBody) if (!isFirstBody)
newParagraph(' ', 1); newParagraph(' ', 1);
isFirstBody = false; isFirstBody = false;
@@ -419,7 +456,7 @@ export default class BookParser {
}; };
const onProgress = async(prog) => { const onProgress = async(prog) => {
await sleep(1); await utils.sleep(1);
callback(prog); callback(prog);
}; };
@@ -441,7 +478,7 @@ export default class BookParser {
this.textLength = paraOffset; this.textLength = paraOffset;
callback(100); callback(100);
await sleep(10); await utils.sleep(10);
return {fb2}; return {fb2};
} }

View File

@@ -6,17 +6,23 @@ import BookParser from './BookParser';
const maxDataSize = 300*1024*1024;//compressed bytes const maxDataSize = 300*1024*1024;//compressed bytes
//локальный кэш метаданных книг, ограничение maxDataSize
const bmMetaStore = localForage.createInstance({ const bmMetaStore = localForage.createInstance({
name: 'bmMetaStore' name: 'bmMetaStore'
}); });
//локальный кэш самих книг, ограничение maxDataSize
const bmDataStore = localForage.createInstance({ const bmDataStore = localForage.createInstance({
name: 'bmDataStore' name: 'bmDataStore'
}); });
const bmRecentStore = localForage.createInstance({ //список недавно открытых книг
const bmRecentStoreOld = localForage.createInstance({
name: 'bmRecentStore' name: 'bmRecentStore'
}); });
const bmRecentStoreNew = localForage.createInstance({
name: 'bmRecentStoreNew'
});
class BookManager { class BookManager {
async init(settings) { async init(settings) {
@@ -25,9 +31,43 @@ class BookManager {
this.eventListeners = []; this.eventListeners = [];
this.books = {}; this.books = {};
this.recent = {};
this.saveRecent = _.debounce(() => {
bmRecentStoreNew.setItem('recent', this.recent);
}, 300, {maxWait: 800});
this.saveRecentItem = _.debounce(() => {
bmRecentStoreNew.setItem('recent-item', this.recentItem);
this.recentRev = (this.recentRev < 1000 ? this.recentRev + 1 : 1);
bmRecentStoreNew.setItem('rev', this.recentRev);
}, 200, {maxWait: 300});
//загрузка bmRecentStore
this.recentRev = await bmRecentStoreNew.getItem('rev') || 0;
if (this.recentRev) {
this.recent = await bmRecentStoreNew.getItem('recent');
if (!this.recent)
this.recent = {}; this.recent = {};
this.recentLast = await bmRecentStore.getItem('recent-last'); this.recentItem = await bmRecentStoreNew.getItem('recent-item');
if (this.recentItem)
this.recent[this.recentItem.key] = this.recentItem;
this.recentLastKey = await bmRecentStoreNew.getItem('recent-last-key');
if (this.recentLastKey) {
const meta = await bmMetaStore.getItem(`bmMeta-${this.recentLastKey}`);
if (_.isObject(meta)) {
this.books[meta.key] = meta;
}
}
await this.cleanRecentBooks();
if (this.recentRev > 10)
await bmRecentStoreOld.clear();
} else {//TODO: убрать после 06.2021, когда bmRecentStoreOld устареет
this.recentLast = await bmRecentStoreOld.getItem('recent-last');
if (this.recentLast) { if (this.recentLast) {
this.recent[this.recentLast.key] = this.recentLast; this.recent[this.recentLast.key] = this.recentLast;
const meta = await bmMetaStore.getItem(`bmMeta-${this.recentLast.key}`); const meta = await bmMetaStore.getItem(`bmMeta-${this.recentLast.key}`);
@@ -36,14 +76,39 @@ class BookManager {
} }
} }
let key = null;
const len = await bmRecentStoreOld.length();
for (let i = len - 1; i >= 0; i--) {
key = await bmRecentStoreOld.key(i);
if (key) {
let r = await bmRecentStoreOld.getItem(key);
if (_.isObject(r) && r.key) {
this.recent[r.key] = r;
}
} else {
await bmRecentStoreOld.removeItem(key);
}
}
//размножение для дебага
/*if (key) {
for (let i = 0; i < 1000; i++) {
const k = this.keyFromUrl(i.toString());
this.recent[k] = Object.assign({}, _.cloneDeep(this.recent[key]), {key: k, touchTime: Date.now() - 1000000, url: utils.randomHexString(300)});
}
}*/
await bmRecentStoreNew.setItem('recent', this.recent);
this.recentRev = 1;
await bmRecentStoreNew.setItem('rev', this.recentRev);
}
this.recentChanged = true; this.recentChanged = true;
this.loadStored();//no await this.loadStored();//no await
} }
//Долгая асинхронная загрузка из хранилища. //Ленивая асинхронная загрузка bmMetaStore
//Хранение в отдельных записях дает относительно
//нормальное поведение при нескольких вкладках с читалкой в браузере.
async loadStored() { async loadStored() {
//даем время для загрузки последней читаемой книги, чтобы не блокировать приложение //даем время для загрузки последней читаемой книги, чтобы не блокировать приложение
await utils.sleep(2000); await utils.sleep(2000);
@@ -70,32 +135,7 @@ class BookManager {
} }
} }
let key = null;
len = await bmRecentStore.length();
for (let i = len - 1; i >= 0; i--) {
key = await bmRecentStore.key(i);
if (key) {
let r = await bmRecentStore.getItem(key);
if (_.isObject(r) && r.key) {
this.recent[r.key] = r;
}
} else {
await bmRecentStore.removeItem(key);
}
}
//размножение для дебага
/*if (key) {
for (let i = 0; i < 1000; i++) {
const k = this.keyFromUrl(i.toString());
this.recent[k] = Object.assign({}, _.cloneDeep(this.recent[key]), {key: k, touchTime: Date.now() - 1000000, url: utils.randomHexString(300)});
}
}*/
await this.cleanBooks(); await this.cleanBooks();
await this.cleanRecentBooks();
this.recentChanged = true;
this.loaded = true; this.loaded = true;
this.emit('load-stored-finish'); this.emit('load-stored-finish');
} }
@@ -238,7 +278,7 @@ class BookManager {
let book = this.books[meta.key]; let book = this.books[meta.key];
if (!book && !this.loaded) { if (!book && !this.loaded) {
book = await bmDataStore.getItem(`bmMeta-${meta.key}`); book = await bmMetaStore.getItem(`bmMeta-${meta.key}`);
if (book) if (book)
this.books[meta.key] = book; this.books[meta.key] = book;
} }
@@ -254,7 +294,7 @@ class BookManager {
result = this.books[meta.key]; result = this.books[meta.key];
if (!result) { if (!result) {
result = await bmDataStore.getItem(`bmMeta-${meta.key}`); result = await bmMetaStore.getItem(`bmMeta-${meta.key}`);
if (result) if (result)
this.books[meta.key] = result; this.books[meta.key] = result;
} }
@@ -328,6 +368,43 @@ class BookManager {
} }
//-- recent -------------------------------------------------------------- //-- recent --------------------------------------------------------------
async recentSetItem(item = null, skipCheck = false) {
const rev = await bmRecentStoreNew.getItem('rev');
if (rev != this.recentRev && !skipCheck) {
const newRecent = await bmRecentStoreNew.getItem('recent');
Object.assign(this.recent, newRecent);
this.recentItem = await bmRecentStoreNew.getItem('recent-item');
this.recentRev = rev;
}
const prevKey = (this.recentItem ? this.recentItem.key : '');
if (item) {
this.recent[item.key] = item;
this.recentItem = item;
} else {
this.recentItem = null;
}
this.saveRecentItem();
if (!item || prevKey != item.key) {
this.saveRecent();
}
this.recentChanged = true;
if (item) {
this.emit('recent-changed', item.key);
} else {
this.emit('recent-changed');
}
}
async recentSetLastKey(key) {
this.recentLastKey = key;
await bmRecentStoreNew.setItem('recent-last-key', this.recentLastKey);
}
async setRecentBook(value) { async setRecentBook(value) {
const result = this.metaOnly(value); const result = this.metaOnly(value);
result.touchTime = Date.now(); result.touchTime = Date.now();
@@ -341,38 +418,25 @@ class BookManager {
result.bookPosSeen = this.recent[result.key].bookPosSeen; result.bookPosSeen = this.recent[result.key].bookPosSeen;
} }
this.recent[result.key] = result; await this.recentSetLastKey(result.key);
await this.recentSetItem(result);
await bmRecentStore.setItem(result.key, result);
this.recentLast = result;
await bmRecentStore.setItem('recent-last', this.recentLast);
this.recentChanged = true;
this.emit('recent-changed', result.key);
return result; return result;
} }
async getRecentBook(value) { async getRecentBook(value) {
let result = this.recent[value.key]; return this.recent[value.key];
if (!result) {
result = await bmRecentStore.getItem(value.key);
if (result)
this.recent[value.key] = result;
}
return result;
} }
async delRecentBook(value) { async delRecentBook(value) {
this.recent[value.key].deleted = 1; const item = this.recent[value.key];
await bmRecentStore.setItem(value.key, this.recent[value.key]); item.deleted = 1;
if (this.recentLast.key == value.key) { if (this.recentLastKey == value.key) {
this.recentLast = null; await this.recentSetLastKey(null);
await bmRecentStore.setItem('recent-last', this.recentLast);
} }
await this.recentSetItem(item);
this.emit('recent-deleted', value.key); this.emit('recent-deleted', value.key);
this.emit('recent-changed', value.key);
} }
async cleanRecentBooks() { async cleanRecentBooks() {
@@ -380,24 +444,22 @@ class BookManager {
let isDel = false; let isDel = false;
for (let i = 1000; i < sorted.length; i++) { for (let i = 1000; i < sorted.length; i++) {
await bmRecentStore.removeItem(sorted[i].key);
delete this.recent[sorted[i].key]; delete this.recent[sorted[i].key];
await bmRecentStore.removeItem(sorted[i].key);
isDel = true; isDel = true;
} }
this.sortedRecentCached = null; this.sortedRecentCached = null;
if (isDel) if (isDel)
this.emit('recent-changed'); await this.recentSetItem();
return isDel; return isDel;
} }
mostRecentBook() { mostRecentBook() {
if (this.recentLast) { if (this.recentLastKey) {
return this.recentLast; return this.recent[this.recentLastKey];
} }
const oldRecentLast = this.recentLast; const oldKey = this.recentLastKey;
let max = 0; let max = 0;
let result = null; let result = null;
@@ -408,10 +470,11 @@ class BookManager {
result = book; result = book;
} }
} }
this.recentLast = result;
bmRecentStore.setItem('recent-last', this.recentLast);//no await
if (this.recentLast !== oldRecentLast) const newRecentLastKey = (result ? result.key : null);
this.recentSetLastKey(newRecentLastKey);//no await
if (newRecentLastKey !== oldKey)
this.emit('recent-changed'); this.emit('recent-changed');
return result; return result;
@@ -442,24 +505,12 @@ class BookManager {
delete mergedRecent[i]; delete mergedRecent[i];
} }
//"ленивое" обновление хранилища
(async() => {
for (const rec of Object.values(mergedRecent)) {
if (rec.key) {
await bmRecentStore.setItem(rec.key, rec);
await utils.sleep(1);
}
}
})();
this.recent = mergedRecent; this.recent = mergedRecent;
this.recentLast = null; await this.recentSetLastKey(null);
await bmRecentStore.setItem('recent-last', this.recentLast); await this.recentSetItem(null, true);
this.recentChanged = true;
this.emit('set-recent'); this.emit('set-recent');
this.emit('recent-changed');
} }
addEventListener(listener) { addEventListener(listener) {

View File

@@ -1,4 +1,15 @@
export const versionHistory = [ export const versionHistory = [
{
showUntil: '2020-11-20',
header: '0.9.9 (2020-11-21)',
content:
`
<ul>
<li>оптимизации, исправления багов</li>
</ul>
`
},
{ {
showUntil: '2020-11-12', showUntil: '2020-11-12',
header: '0.9.8 (2020-11-13)', header: '0.9.8 (2020-11-13)',

View File

@@ -21,7 +21,7 @@ import {QSlider} from 'quasar/src/components/slider';
import {QTabs, QTab} from 'quasar/src/components/tabs'; import {QTabs, QTab} from 'quasar/src/components/tabs';
//import {QTabPanels, QTabPanel} from 'quasar/src/components/tab-panels'; //import {QTabPanels, QTabPanel} from 'quasar/src/components/tab-panels';
import {QSeparator} from 'quasar/src/components/separator'; import {QSeparator} from 'quasar/src/components/separator';
import {QList, QItem, QItemSection, QItemLabel} from 'quasar/src/components/item'; //import {QList, QItem, QItemSection, QItemLabel} from 'quasar/src/components/item';
import {QTooltip} from 'quasar/src/components/tooltip'; import {QTooltip} from 'quasar/src/components/tooltip';
import {QSpinner} from 'quasar/src/components/spinner'; import {QSpinner} from 'quasar/src/components/spinner';
import {QTable, QTh, QTr, QTd} from 'quasar/src/components/table'; import {QTable, QTh, QTr, QTd} from 'quasar/src/components/table';
@@ -32,8 +32,7 @@ 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 {QExpansionItem} from 'quasar/src/components/expansion-item'; //import {QExpansionItem} from 'quasar/src/components/expansion-item';
const components = { const components = {
//QLayout, //QLayout,
@@ -50,7 +49,7 @@ const components = {
QTabs, QTab, QTabs, QTab,
//QTabPanels, QTabPanel, //QTabPanels, QTabPanel,
QSeparator, QSeparator,
QList, QItem, QItemSection, QItemLabel, //QList, QItem, QItemSection, QItemLabel,
QTooltip, QTooltip,
QSpinner, QSpinner,
QTable, QTh, QTr, QTd, QTable, QTh, QTr, QTd,
@@ -61,7 +60,7 @@ const components = {
QDialog, QDialog,
QChip, QChip,
QTree, QTree,
QExpansionItem, //QExpansionItem,
}; };
//directives //directives

View File

@@ -304,3 +304,55 @@ export function userHotKeysObjectSwap(userHotKeys) {
} }
return result; return result;
} }
export function removeHtmlTags(s) {
return s.replace(/(<([^>]+)>)/ig, '');
}
export function makeValidFilename(filename, repl = '_') {
let f = filename.replace(/[\x00\\/:*"<>|]/g, repl); // eslint-disable-line no-control-regex
f = f.trim();
while (f.length && (f[f.length - 1] == '.' || f[f.length - 1] == '_')) {
f = f.substring(0, f.length - 1);
}
if (f)
return f;
else
throw new Error('Invalid filename');
}
export function getBookTitle(fb2) {
fb2 = (fb2 ? fb2 : {});
const result = {};
if (fb2.author) {
const authorNames = fb2.author.map(a => _.compact([
a.lastName,
a.firstName,
a.middleName
]).join(' '));
result.author = authorNames.join(', ');
}
if (fb2.sequence) {
const seqs = fb2.sequence.map(s => _.compact([
s.name,
(s.number ? `#${s.number}` : null),
]).join(' '));
result.sequence = seqs.join(', ');
if (result.sequence)
result.sequenceTitle = `(${result.sequence})`;
}
result.bookTitle = _.compact([result.sequenceTitle, fb2.bookTitle]).join(' ');
result.fullTitle = _.compact([
result.author,
result.bookTitle
]).join(' - ');
return result;
}

View File

@@ -16,7 +16,7 @@ const readerActions = {
'refresh': 'Принудительно обновить книгу', 'refresh': 'Принудительно обновить книгу',
'offlineMode': 'Автономный режим (без интернета)', 'offlineMode': 'Автономный режим (без интернета)',
'contents': 'Оглавление/закладки', 'contents': 'Оглавление/закладки',
'libs': 'Библиотека', 'libs': 'Сетевая библиотека',
'recentBooks': 'Открыть недавние', 'recentBooks': 'Открыть недавние',
'switchToolbar': 'Показать/скрыть панель управления', 'switchToolbar': 'Показать/скрыть панель управления',
'donate': '', 'donate': '',
@@ -297,15 +297,15 @@ const libsDefaults = {
{r: 'https://flibs.in', s: 'https://flibs.in', list: [ {r: 'https://flibs.in', s: 'https://flibs.in', list: [
{l: 'https://flibs.in', c: 'Flibs'}, {l: 'https://flibs.in', c: 'Flibs'},
]}, ]},
{r: 'http://fantasy-worlds.org', s: 'http://fantasy-worlds.org', list: [
{l: 'http://fantasy-worlds.org', c: 'Миры Фэнтези'},
]},
{r: 'http://samlib.ru', s: 'http://samlib.ru', list: [ {r: 'http://samlib.ru', s: 'http://samlib.ru', list: [
{l: 'http://samlib.ru', c: 'Журнал "Самиздат"'}, {l: 'http://samlib.ru', c: 'Журнал "Самиздат"'},
]}, ]},
{r: 'http://lib.ru', s: 'http://lib.ru', list: [ {r: 'http://lib.ru', s: 'http://lib.ru', list: [
{l: 'http://lib.ru', c: 'Библиотека Максима Мошкова'}, {l: 'http://lib.ru', c: 'Библиотека Максима Мошкова'},
]}, ]},
{r: 'http://fantasy-worlds.org', s: 'http://fantasy-worlds.org', list: [
{l: 'http://fantasy-worlds.org', c: 'Миры Фэнтези'},
]},
{r: 'https://aldebaran.ru', s: 'https://aldebaran.ru', list: [ {r: 'https://aldebaran.ru', s: 'https://aldebaran.ru', list: [
{l: 'https://aldebaran.ru', c: 'АЛЬДЕБАРАН | Электронная библиотека книг'}, {l: 'https://aldebaran.ru', c: 'АЛЬДЕБАРАН | Электронная библиотека книг'},
]}, ]},

View File

@@ -126,3 +126,18 @@ server {
proxy_set_header Referer ""; proxy_set_header Referer "";
} }
} }
server {
listen 23580;
server_name fw_proxy;
valid_referers liberama.top b.liberama.top;
if ($invalid_referer) {
return 403;
}
location / {
proxy_pass http://fantasy-worlds.org;
}
}

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{ {
"name": "Liberama", "name": "Liberama",
"version": "0.9.8", "version": "0.9.9",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "Liberama", "name": "Liberama",
"version": "0.9.8", "version": "0.9.9",
"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",

View File

@@ -171,6 +171,7 @@ class ReaderWorker {
} catch (e) { } catch (e) {
log(LM_ERR, e.stack); log(LM_ERR, e.stack);
log(LM_ERR, `downloadedFilename: ${downloadedFilename}`);
if (e.message == 'abort') if (e.message == 'abort')
e.message = overLoadMes; e.message = overLoadMes;
wState.set({state: 'error', error: e.message}); wState.set({state: 'error', error: e.message});