Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f7cafb240 | ||
|
|
08fd0f15ff | ||
|
|
dbb1bfe587 | ||
|
|
fe4b7a5a85 | ||
|
|
d8df5d76e5 | ||
|
|
b65dcc5ade | ||
|
|
a5c387a19e | ||
|
|
07c38d9a9f | ||
|
|
20ac8a444b | ||
|
|
7b601c9c7f | ||
|
|
8d2f74daa4 | ||
|
|
01e82dca5f | ||
|
|
094bb407ed | ||
|
|
338baa55ec | ||
|
|
d06d20a33e | ||
|
|
d46ba6b92b | ||
|
|
ec2639039d | ||
|
|
3a211ded2e | ||
|
|
c2131e3654 | ||
|
|
594fb59395 | ||
|
|
f44378ec84 | ||
|
|
0f6b366f62 | ||
|
|
8d0a5997ee | ||
|
|
347cb3417e | ||
|
|
337fcebd10 | ||
|
|
e057b130e9 | ||
|
|
19a0765a1a | ||
|
|
b81cd3240b | ||
|
|
1e6105b076 | ||
|
|
d8d89b3463 | ||
|
|
459564cb2d | ||
|
|
084df35184 | ||
|
|
81912babeb | ||
|
|
3943fc7d95 | ||
|
|
3999dc930c | ||
|
|
d4dea16456 | ||
|
|
ed38cb33a5 | ||
|
|
4cf5a0f4c8 | ||
|
|
8f0d526af2 | ||
|
|
6ca3881841 | ||
|
|
d8e765a04f | ||
|
|
40ff572f94 | ||
|
|
cc4275dc03 | ||
|
|
b387f4a0db | ||
|
|
1a096031c4 | ||
|
|
83a60b4091 | ||
|
|
b292407ec2 | ||
|
|
952c337b76 | ||
|
|
e947b887fe | ||
|
|
bd1e5485d7 | ||
|
|
e095c3318b | ||
|
|
d75a08b519 |
@@ -159,6 +159,7 @@ import * as lu from './linkUtils';
|
||||
|
||||
const proxySubst = {
|
||||
'http://flibusta.is': 'http://b.liberama.top:23480',
|
||||
'http://fantasy-worlds.org': 'http://b.liberama.top:23580',
|
||||
};
|
||||
|
||||
export default @Component({
|
||||
@@ -407,7 +408,7 @@ class ExternalLibs extends Vue {
|
||||
}
|
||||
|
||||
get header() {
|
||||
let result = (this.ready ? 'Библиотека' : 'Загрузка...');
|
||||
let result = (this.ready ? 'Сетевая библиотека' : 'Загрузка...');
|
||||
if (this.ready && this.selectedLink) {
|
||||
result += ` | ${(this.libs.comment ? this.libs.comment + ' ': '') + lu.removeProtocol(this.libs.startLink)}`;
|
||||
}
|
||||
@@ -786,12 +787,17 @@ class ExternalLibs extends Vue {
|
||||
|
||||
showHelp() {
|
||||
this.$root.stdDialog.alert(`
|
||||
<p>Окно 'Библиотека' позволяет открывать ссылки в читалке без переключения между окнами,
|
||||
что особенно актуально для мобильных устройств.</p>
|
||||
|
||||
<p>'Библиотека' разрешает свободный доступ к сайту flibusta.is. Имеется возможность управлять закладками
|
||||
<p>Окно 'Сетевая библиотека' позволяет открывать ссылки в читалке без переключения между окнами,
|
||||
что особенно актуально для мобильных устройств. Имеется возможность управлять закладками
|
||||
на понравившиеся ресурсы, книги или страницы авторов. Открытие ссылок и навигация происходят во фрейме, но,
|
||||
к сожалению, в нем открываются не все страницы.
|
||||
к сожалению, в нем открываются не все страницы.</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>Из-за проблем с безопасностью, навигация 'вперед-назад' во фрейме осуществляется с помощью контекстного меню правой кнопкой мыши.
|
||||
|
||||
247
client/components/Reader/ContentsPage/ContentsPage.vue
Normal file
247
client/components/Reader/ContentsPage/ContentsPage.vue
Normal file
@@ -0,0 +1,247 @@
|
||||
<template>
|
||||
<Window width="600px" ref="window" @close="close">
|
||||
<template slot="header">
|
||||
Оглавление/закладки
|
||||
</template>
|
||||
|
||||
<div class="bg-grey-3 row">
|
||||
<q-tabs
|
||||
v-model="selectedTab"
|
||||
active-color="black"
|
||||
active-bg-color="white"
|
||||
indicator-color="white"
|
||||
dense
|
||||
no-caps
|
||||
inline-label
|
||||
class="no-mp bg-grey-4 text-grey-7"
|
||||
>
|
||||
<q-tab name="contents" icon="la la-list" label="Оглавление" />
|
||||
<q-tab name="bookmarks" icon="la la-bookmark" label="Закладки" />
|
||||
</q-tabs>
|
||||
</div>
|
||||
|
||||
<div class="q-mb-sm"/>
|
||||
|
||||
<div class="tab-panel" v-show="selectedTab == 'contents'">
|
||||
<div>
|
||||
<div v-for="item in contents" :key="item.key" class="column" style="width: 540px">
|
||||
<div class="row item q-px-sm no-wrap">
|
||||
<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"/>
|
||||
</div>
|
||||
<div v-else class="no-expand-button clickable" @click="setBookPos(item.offset)">
|
||||
<q-icon name="la la-stop" class="icon" style="visibility: hidden" size="20px"/>
|
||||
</div>
|
||||
<div class="col row clickable" @click="setBookPos(item.offset)">
|
||||
<div :style="item.indentStyle"></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>
|
||||
</div>
|
||||
|
||||
<div v-if="item.expanded" :ref="`subitem${item.key}`" class="subitems-transition">
|
||||
<div v-for="subitem in item.list" :key="subitem.key" class="row subitem q-px-sm no-wrap">
|
||||
<div class="col row clickable" @click="setBookPos(subitem.offset)">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!contents.length" class="column justify-center items-center" style="height: 100px">
|
||||
Оглавление отсутствует
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-panel" v-show="selectedTab == 'bookmarks'">
|
||||
<div class="column justify-center items-center" style="height: 100px">
|
||||
Раздел находится в разработке
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</Window>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
//-----------------------------------------------------------------------------
|
||||
import Vue from 'vue';
|
||||
import Component from 'vue-class-component';
|
||||
//import _ from 'lodash';
|
||||
|
||||
import Window from '../../share/Window.vue';
|
||||
import * as utils from '../../../share/utils';
|
||||
|
||||
export default @Component({
|
||||
components: {
|
||||
Window,
|
||||
},
|
||||
watch: {
|
||||
},
|
||||
})
|
||||
class ContentsPage extends Vue {
|
||||
selectedTab = 'contents';
|
||||
contents = [];
|
||||
|
||||
created() {
|
||||
}
|
||||
|
||||
async init(currentBook, parsed) {
|
||||
this.$refs.window.init();
|
||||
|
||||
//закладки
|
||||
|
||||
//далее формаирование оглавления
|
||||
if (this.parsed == parsed)
|
||||
return;
|
||||
|
||||
this.parsed = parsed;
|
||||
this.contents = [];
|
||||
await this.$nextTick();
|
||||
|
||||
const pc = parsed.contents;
|
||||
const newpc = [];
|
||||
//преобразуем все, кроме первого, разделы body в title-subtitle
|
||||
let curSubtitles = [];
|
||||
let prevBodyIndex = -1;
|
||||
for (let i = 0; i < pc.length; i++) {
|
||||
const cont = pc[i];
|
||||
if (prevBodyIndex != cont.bodyIndex)
|
||||
curSubtitles = [];
|
||||
|
||||
prevBodyIndex = cont.bodyIndex;
|
||||
|
||||
if (cont.bodyIndex > 1) {
|
||||
if (cont.inset < 1) {
|
||||
newpc.push(Object.assign({}, cont, {subtitles: curSubtitles}));
|
||||
} else {
|
||||
curSubtitles.push(Object.assign({}, cont, {inset: cont.inset - 1}));
|
||||
}
|
||||
} else {
|
||||
newpc.push(cont);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
let i = 0;
|
||||
const newContents = [];
|
||||
newpc.forEach((cont) => {
|
||||
const label = prepareLabel(cont.title, true);
|
||||
const indentStyle = getIndentStyle(cont.inset);
|
||||
const labelStyle = getLabelStyle(cont.inset);
|
||||
|
||||
let j = 0;
|
||||
const list = [];
|
||||
cont.subtitles.forEach((sub) => {
|
||||
const l = prepareLabel(sub.title);
|
||||
const s = getIndentStyle(sub.inset + 1);
|
||||
const ls = getLabelStyle(cont.inset + 1);
|
||||
const p = parsed.para[sub.paraIndex];
|
||||
list[j] = {perc: (p.offset/parsed.textLength*100).toFixed(2), label: l, key: j, offset: p.offset, indentStyle: s, labelStyle: ls};
|
||||
j++;
|
||||
});
|
||||
|
||||
const p = parsed.para[cont.paraIndex];
|
||||
newContents[i] = {perc: (p.offset/parsed.textLength*100).toFixed(0), label, key: i, offset: p.offset, indentStyle, labelStyle, expanded: false, list};
|
||||
|
||||
i++;
|
||||
});
|
||||
|
||||
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) {
|
||||
this.$emit('book-pos-changed', {bookPos: newValue});
|
||||
this.close();
|
||||
}
|
||||
|
||||
close() {
|
||||
this.$emit('do-action', {action: 'contents'});
|
||||
}
|
||||
|
||||
keyHook(event) {
|
||||
if (!this.$root.stdDialog.active && event.type == 'keydown' && event.key == 'Escape') {
|
||||
this.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
//-----------------------------------------------------------------------------
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tab-panel {
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
font-size: 90%;
|
||||
padding: 0 10px 0px 10px;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
padding: 10px 0 10px 0;
|
||||
}
|
||||
|
||||
.item, .subitem {
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.item:hover, .subitem:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.expand-button, .no-expand-button {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.subitems-transition {
|
||||
height: 0;
|
||||
transition: height 0.2s linear;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.icon {
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.expanded-icon {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
</style>
|
||||
@@ -48,6 +48,10 @@
|
||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['refresh'] }}</q-tooltip>
|
||||
</button>
|
||||
<div class="space"></div>
|
||||
<button ref="contents" v-show="showToolButton['contents']" class="tool-button" :class="buttonActiveClass('contents')" @click="buttonClick('contents')" v-ripple>
|
||||
<q-icon name="la la-list" size="32px"/>
|
||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['contents'] }}</q-tooltip>
|
||||
</button>
|
||||
<button ref="libs" v-show="mode == 'liberama.top' && showToolButton['libs']" class="tool-button" :class="buttonActiveClass('libs')" @click="buttonClick('libs')" v-ripple>
|
||||
<q-icon name="la la-sitemap" size="32px"/>
|
||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['libs'] }}</q-tooltip>
|
||||
@@ -89,12 +93,13 @@
|
||||
@stop-text-search="stopTextSearch">
|
||||
</SearchPage>
|
||||
<CopyTextPage v-if="copyTextActive" ref="copyTextPage" @do-action="doAction"></CopyTextPage>
|
||||
<LibsPage v-show="libsActive" ref="libsPage" @load-book="loadBook" @libs-close="libsClose" @do-action="doAction"></LibsPage>
|
||||
<LibsPage v-show="hidden" ref="libsPage" @load-book="loadBook" @libs-close="libsClose" @do-action="doAction"></LibsPage>
|
||||
<RecentBooksPage v-show="recentBooksActive" ref="recentBooksPage" @load-book="loadBook" @recent-books-close="recentBooksClose"></RecentBooksPage>
|
||||
<SettingsPage v-show="settingsActive" ref="settingsPage" @do-action="doAction"></SettingsPage>
|
||||
<HelpPage v-if="helpActive" ref="helpPage" @do-action="doAction"></HelpPage>
|
||||
<ClickMapPage v-show="clickMapActive" ref="clickMapPage"></ClickMapPage>
|
||||
<ServerStorage v-show="hidden" ref="serverStorage"></ServerStorage>
|
||||
<ContentsPage v-show="contentsActive" ref="contentsPage" @do-action="doAction" @book-pos-changed="bookPosChanged"></ContentsPage>
|
||||
|
||||
<ReaderDialogs ref="dialogs" @donate-toggle="donateToggle" @version-history-toggle="versionHistoryToggle"></ReaderDialogs>
|
||||
</div>
|
||||
@@ -121,6 +126,8 @@ import SettingsPage from './SettingsPage/SettingsPage.vue';
|
||||
import HelpPage from './HelpPage/HelpPage.vue';
|
||||
import ClickMapPage from './ClickMapPage/ClickMapPage.vue';
|
||||
import ServerStorage from './ServerStorage/ServerStorage.vue';
|
||||
import ContentsPage from './ContentsPage/ContentsPage.vue';
|
||||
|
||||
import ReaderDialogs from './ReaderDialogs/ReaderDialogs.vue';
|
||||
|
||||
import bookManager from './share/bookManager';
|
||||
@@ -143,6 +150,8 @@ export default @Component({
|
||||
HelpPage,
|
||||
ClickMapPage,
|
||||
ServerStorage,
|
||||
ContentsPage,
|
||||
|
||||
ReaderDialogs,
|
||||
},
|
||||
watch: {
|
||||
@@ -200,6 +209,7 @@ class Reader extends Vue {
|
||||
settingsActive = false;
|
||||
helpActive = false;
|
||||
clickMapActive = false;
|
||||
contentsActive = false;
|
||||
|
||||
bookPos = null;
|
||||
allowUrlParamBookPos = false;
|
||||
@@ -240,11 +250,11 @@ class Reader extends Vue {
|
||||
await this.$nextTick();
|
||||
this.paramPosIgnore = false;
|
||||
}
|
||||
}, 500, {'maxWait':5000});
|
||||
}, 500, {maxWait: 5000});
|
||||
|
||||
this.scrollingSetRecentBook = _.debounce((newValue) => {
|
||||
this.debouncedSetRecentBook(newValue);
|
||||
}, 15000, {'maxWait':20000});
|
||||
}, 15000, {maxWait: 20000});
|
||||
|
||||
document.addEventListener('fullscreenchange', () => {
|
||||
this.fullScreenActive = (document.fullscreenElement !== null);
|
||||
@@ -490,6 +500,7 @@ class Reader extends Vue {
|
||||
this.stopScrolling();
|
||||
this.stopSearch();
|
||||
this.helpActive = false;
|
||||
this.contentsActive = false;
|
||||
}
|
||||
|
||||
loaderToggle() {
|
||||
@@ -603,6 +614,21 @@ class Reader extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
contentsPageToggle() {
|
||||
this.contentsActive = !this.contentsActive;
|
||||
const page = this.$refs.page;
|
||||
if (this.contentsActive && this.activePage == 'TextPage' && page.parsed) {
|
||||
this.closeAllWindows();
|
||||
this.contentsActive = true;
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$refs.contentsPage.init(this.mostRecentBook(), page.parsed);
|
||||
});
|
||||
} else {
|
||||
this.contentsActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
libsClose() {
|
||||
if (this.libsActive)
|
||||
this.libsToogle();
|
||||
@@ -707,6 +733,7 @@ class Reader extends Vue {
|
||||
case 'copyText':
|
||||
case 'splitToPara':
|
||||
case 'refresh':
|
||||
case 'contents':
|
||||
case 'libs':
|
||||
case 'recentBooks':
|
||||
case 'offlineMode':
|
||||
@@ -735,6 +762,7 @@ class Reader extends Vue {
|
||||
case 'scrolling':
|
||||
case 'search':
|
||||
case 'copyText':
|
||||
case 'contents':
|
||||
classResult = classDisabled;
|
||||
break;
|
||||
case 'splitToPara':
|
||||
@@ -1026,6 +1054,9 @@ class Reader extends Vue {
|
||||
case 'refresh':
|
||||
this.refreshBook();
|
||||
break;
|
||||
case 'contents':
|
||||
this.contentsPageToggle();
|
||||
break;
|
||||
case 'libs':
|
||||
this.libsToogle();
|
||||
break;
|
||||
@@ -1125,6 +1156,9 @@ class Reader extends Vue {
|
||||
if (!result && this.copyTextActive)
|
||||
result = this.$refs.copyTextPage.keyHook(event);
|
||||
|
||||
if (!result && this.contentsActive)
|
||||
result = this.$refs.contentsPage.keyHook(event);
|
||||
|
||||
if (!result && this.$refs.page && this.$refs.page.keyHook)
|
||||
result = this.$refs.page.keyHook(event);
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
<q-td key="links" :props="props" class="td-mp" auto-width>
|
||||
<div class="break-word" style="width: 75px; font-size: 90%">
|
||||
<a v-show="isUrl(props.row.url)" :href="props.row.url" target="_blank">Оригинал</a><br>
|
||||
<a :href="props.row.path" @click.prevent="downloadBook(props.row.path)">Скачать FB2</a>
|
||||
<a :href="props.row.path" @click.prevent="downloadBook(props.row.path, props.row.fullTitle)">Скачать FB2</a>
|
||||
</div>
|
||||
</q-td>
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
import Vue from 'vue';
|
||||
import Component from 'vue-class-component';
|
||||
import path from 'path';
|
||||
import _ from 'lodash';
|
||||
//import _ from 'lodash';
|
||||
|
||||
import * as utils from '../../../share/utils';
|
||||
import Window from '../../share/Window.vue';
|
||||
@@ -112,6 +112,7 @@ class RecentBooksPage extends Vue {
|
||||
pagination = {};
|
||||
|
||||
created() {
|
||||
this.firstInit = true;
|
||||
this.pagination = {rowsPerPage: 0};
|
||||
|
||||
this.columns = [
|
||||
@@ -167,26 +168,11 @@ class RecentBooksPage extends Vue {
|
||||
this.initing = true;
|
||||
|
||||
|
||||
if (!bookManager.loaded) {
|
||||
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);
|
||||
j *= 2;
|
||||
}
|
||||
|
||||
await utils.sleep(100);
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
//для отзывчивости
|
||||
await utils.sleep(100);
|
||||
if (this.firstInit) {//для отзывчивости
|
||||
await this.updateTableData(20);
|
||||
this.firstInit = false;
|
||||
}
|
||||
await utils.sleep(50);
|
||||
await this.updateTableData();
|
||||
this.initing = false;
|
||||
})();
|
||||
@@ -222,30 +208,11 @@ class RecentBooksPage extends Vue {
|
||||
textLen = ` ${Math.round(book.textLength/1000)}k`;
|
||||
}
|
||||
|
||||
const fb2 = (book.fb2 ? book.fb2 : {});
|
||||
const bt = utils.getBookTitle(book.fb2);
|
||||
|
||||
let title = fb2.bookTitle;
|
||||
if (title)
|
||||
title = `"${title}"`;
|
||||
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));
|
||||
let title = bt.bookTitle;
|
||||
title = (title ? `"${title}"`: '');
|
||||
const author = (bt.author ? bt.author : (bt.bookTitle ? bt.bookTitle : book.url));
|
||||
|
||||
result.push({
|
||||
num,
|
||||
@@ -256,9 +223,10 @@ class RecentBooksPage extends Vue {
|
||||
author,
|
||||
title: `${title}${perc}${textLen}`,
|
||||
},
|
||||
descString: `${author}${title}${perc}${textLen}`,
|
||||
descString: `${author}${title}${perc}${textLen}`,//для сортировки
|
||||
url: book.url,
|
||||
path: book.path,
|
||||
fullTitle: bt.fullTitle,
|
||||
key: book.key,
|
||||
});
|
||||
}
|
||||
@@ -291,13 +259,18 @@ class RecentBooksPage extends Vue {
|
||||
return `${(this.search ? 'Найдено' : 'Всего')} ${len} книг${this.wordEnding(len)}`;
|
||||
}
|
||||
|
||||
async downloadBook(fb2path) {
|
||||
async downloadBook(fb2path, fullTitle) {
|
||||
try {
|
||||
await readerApi.checkCachedBook(fb2path);
|
||||
|
||||
const d = this.$refs.download;
|
||||
d.href = fb2path;
|
||||
d.download = path.basename(fb2path).substr(0, 10) + '.fb2';
|
||||
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.click();
|
||||
} 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) {
|
||||
await bookManager.delRecentBook({key});
|
||||
//this.updateTableData();//обновление уже происходит Reader.bookManagerEvent
|
||||
|
||||
@@ -507,10 +507,10 @@ class ServerStorage extends Vue {
|
||||
if (md.key && result[md.key])
|
||||
result[md.key] = utils.applyObjDiff(result[md.key], md.mod, {isAddChanged: true});
|
||||
|
||||
if (!bookManager.loaded) {
|
||||
/*if (!bookManager.loaded) {
|
||||
this.warning('Ожидание загрузки списка книг перед синхронизацией');
|
||||
while (!bookManager.loaded) await utils.sleep(100);
|
||||
}
|
||||
}*/
|
||||
|
||||
if (newRecent.rev != this.cachedRecent.rev)
|
||||
await this.setCachedRecent(newRecent);
|
||||
@@ -580,8 +580,8 @@ class ServerStorage extends Vue {
|
||||
let newRecent = {};
|
||||
if (!itemKey || (needSaveRecentPatch && Object.keys(newRecentPatch.data).length > 10)) {
|
||||
//ждем весь bm.recent
|
||||
while (!bookManager.loaded)
|
||||
await utils.sleep(100);
|
||||
/*while (!bookManager.loaded)
|
||||
await utils.sleep(100);*/
|
||||
|
||||
newRecent = {rev: this.cachedRecent.rev + 1, data: _.cloneDeep(bm.recent)};
|
||||
newRecentPatch = {rev: this.cachedRecentPatch.rev + 1, data: {}};
|
||||
|
||||
@@ -104,8 +104,12 @@ class UserHotKeys extends UserHotKeysProps {
|
||||
this.updateTableData();
|
||||
}
|
||||
|
||||
get mode() {
|
||||
return this.$store.state.config.mode;
|
||||
}
|
||||
|
||||
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 codesIncludeSearch = (action) => {
|
||||
|
||||
@@ -40,7 +40,7 @@ import Component from 'vue-class-component';
|
||||
import {loadCSS} from 'fg-loadcss';
|
||||
import _ from 'lodash';
|
||||
|
||||
import {sleep} from '../../../share/utils';
|
||||
import * as utils from '../../../share/utils';
|
||||
import bookManager from '../share/bookManager';
|
||||
import DrawHelper from './DrawHelper';
|
||||
import rstore from '../../../store/modules/reader';
|
||||
@@ -134,7 +134,7 @@ class TextPage extends Vue {
|
||||
|
||||
this.$root.$on('resize', async() => {
|
||||
this.$nextTick(this.onResize);
|
||||
await sleep(500);
|
||||
await utils.sleep(500);
|
||||
this.$nextTick(this.onResize);
|
||||
});
|
||||
}
|
||||
@@ -285,7 +285,7 @@ class TextPage extends Vue {
|
||||
|
||||
let close = null;
|
||||
(async() => {
|
||||
await sleep(500);
|
||||
await utils.sleep(500);
|
||||
if (this.fontsLoading)
|
||||
close = this.$root.notify.info('Загрузка шрифта <i class="la la-snowflake icon-rotate" style="font-size: 150%"></i>');
|
||||
})();
|
||||
@@ -342,7 +342,7 @@ class TextPage extends Vue {
|
||||
let i = 0;
|
||||
const t = this.parsed.testText;
|
||||
while (i++ < 50 && this.parsed === parsed && this.drawHelper.measureText(t, {}) === this.parsed.testWidth)
|
||||
await sleep(100);
|
||||
await utils.sleep(100);
|
||||
|
||||
if (this.parsed === parsed) {
|
||||
this.parsed.testWidth = this.drawHelper.measureText(t, {});
|
||||
@@ -366,7 +366,6 @@ class TextPage extends Vue {
|
||||
this.updateLayout();
|
||||
this.book = null;
|
||||
this.meta = null;
|
||||
this.fb2 = null;
|
||||
this.parsed = null;
|
||||
|
||||
this.linesUp = null;
|
||||
@@ -383,7 +382,7 @@ class TextPage extends Vue {
|
||||
try {
|
||||
//подождем ленивый парсинг
|
||||
this.stopLazyParse = true;
|
||||
while (this.doingLazyParse) await sleep(10);
|
||||
while (this.doingLazyParse) await utils.sleep(10);
|
||||
|
||||
const isParsed = await bookManager.hasBookParsed(this.lastBook);
|
||||
if (!isParsed) {
|
||||
@@ -392,21 +391,9 @@ class TextPage extends Vue {
|
||||
|
||||
this.book = await bookManager.getBook(this.lastBook);
|
||||
this.meta = bookManager.metaOnly(this.book);
|
||||
this.fb2 = this.meta.fb2;
|
||||
const bt = utils.getBookTitle(this.meta.fb2);
|
||||
|
||||
let authorNames = [];
|
||||
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.title = bt.fullTitle;
|
||||
|
||||
this.$root.$emit('set-app-title', this.title);
|
||||
|
||||
@@ -493,7 +480,7 @@ class TextPage extends Vue {
|
||||
let wait = (timeout + 201)/100;
|
||||
while (wait > 0 && !this[stopPropertyName]) {
|
||||
wait--;
|
||||
await sleep(100);
|
||||
await utils.sleep(100);
|
||||
}
|
||||
resolve();
|
||||
})().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.doingScrolling = true;
|
||||
@@ -520,7 +507,7 @@ class TextPage extends Vue {
|
||||
this.page1 = this.page2;
|
||||
this.toggleLayout = true;
|
||||
await this.$nextTick();
|
||||
await sleep(50);
|
||||
await utils.sleep(50);
|
||||
|
||||
this.cachedPos = -1;
|
||||
this.draw();
|
||||
@@ -557,7 +544,7 @@ class TextPage extends Vue {
|
||||
page.style.transform = 'none';
|
||||
page.offsetHeight;
|
||||
|
||||
while (this.doingScrolling) await sleep(10);
|
||||
while (this.doingScrolling) await utils.sleep(10);
|
||||
}
|
||||
|
||||
draw() {
|
||||
@@ -766,7 +753,7 @@ class TextPage extends Vue {
|
||||
for (let i = 0; i < this.parsed.para.length; i++) {
|
||||
j++;
|
||||
if (j > 1) {
|
||||
await sleep(1);
|
||||
await utils.sleep(1);
|
||||
j = 0;
|
||||
}
|
||||
if (this.stopLazyParse)
|
||||
@@ -788,7 +775,7 @@ class TextPage extends Vue {
|
||||
async refreshTime() {
|
||||
if (!this.timeRefreshing) {
|
||||
this.timeRefreshing = true;
|
||||
await sleep(60*1000);
|
||||
await utils.sleep(60*1000);
|
||||
|
||||
if (this.book && this.parsed.textLength) {
|
||||
this.debouncedDrawStatusBar();
|
||||
@@ -905,7 +892,7 @@ class TextPage extends Vue {
|
||||
this.settingsChanging = true;
|
||||
const newSize = (this.settings.fontSize + 1 < 200 ? this.settings.fontSize + 1 : 100);
|
||||
this.commit('reader/setSettings', {fontSize: newSize});
|
||||
await sleep(50);
|
||||
await utils.sleep(50);
|
||||
this.settingsChanging = false;
|
||||
}
|
||||
}
|
||||
@@ -915,7 +902,7 @@ class TextPage extends Vue {
|
||||
this.settingsChanging = true;
|
||||
const newSize = (this.settings.fontSize - 1 > 5 ? this.settings.fontSize - 1 : 5);
|
||||
this.commit('reader/setSettings', {fontSize: newSize});
|
||||
await sleep(50);
|
||||
await utils.sleep(50);
|
||||
this.settingsChanging = false;
|
||||
}
|
||||
}
|
||||
@@ -925,7 +912,7 @@ class TextPage extends Vue {
|
||||
this.settingsChanging = true;
|
||||
const newDelay = (this.settings.scrollingDelay - 50 > 1 ? this.settings.scrollingDelay - 50 : 1);
|
||||
this.commit('reader/setSettings', {scrollingDelay: newDelay});
|
||||
await sleep(50);
|
||||
await utils.sleep(50);
|
||||
this.settingsChanging = false;
|
||||
}
|
||||
}
|
||||
@@ -935,7 +922,7 @@ class TextPage extends Vue {
|
||||
this.settingsChanging = true;
|
||||
const newDelay = (this.settings.scrollingDelay + 50 < 10000 ? this.settings.scrollingDelay + 50 : 10000);
|
||||
this.commit('reader/setSettings', {scrollingDelay: newDelay});
|
||||
await sleep(50);
|
||||
await utils.sleep(50);
|
||||
this.settingsChanging = false;
|
||||
}
|
||||
}
|
||||
@@ -949,7 +936,7 @@ class TextPage extends Vue {
|
||||
let delay = 400;
|
||||
while (this.repDoing) {
|
||||
this.handleClick(pointX, pointY);
|
||||
await sleep(delay);
|
||||
await utils.sleep(delay);
|
||||
if (delay > 15)
|
||||
delay *= 0.8;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import he from 'he';
|
||||
import sax from '../../../../server/core/sax';
|
||||
import {sleep} from '../../../share/utils';
|
||||
import * as utils from '../../../share/utils';
|
||||
|
||||
const maxImageLineCount = 100;
|
||||
|
||||
@@ -46,11 +46,21 @@ export default class BookParser {
|
||||
let isFirstSection = true;
|
||||
let isFirstTitlePara = false;
|
||||
|
||||
//изображения
|
||||
this.binary = {};
|
||||
let binaryId = '';
|
||||
let binaryType = '';
|
||||
let dimPromises = [];
|
||||
|
||||
//оглавление
|
||||
this.contents = [];
|
||||
let curTitle = {paraIndex: -1, title: '', subtitles: []};
|
||||
let curSubtitle = {paraIndex: -1, title: ''};
|
||||
let inTitle = false;
|
||||
let inSubtitle = false;
|
||||
let sectionLevel = 0;
|
||||
let bodyIndex = 0;
|
||||
|
||||
let paraIndex = -1;
|
||||
let paraOffset = 0;
|
||||
let para = []; /*array of
|
||||
@@ -80,7 +90,7 @@ export default class BookParser {
|
||||
i.onerror = reject;
|
||||
|
||||
i.src = `data:${binaryType};base64,${data}`;
|
||||
await sleep(30*1000);
|
||||
await utils.sleep(30*1000);
|
||||
if (!resolved)
|
||||
reject('Не удалось получить размер изображения');
|
||||
})().catch(reject); });
|
||||
@@ -102,7 +112,7 @@ export default class BookParser {
|
||||
i.onerror = reject;
|
||||
|
||||
i.src = src;
|
||||
await sleep(30*1000);
|
||||
await utils.sleep(30*1000);
|
||||
if (!resolved)
|
||||
reject('Не удалось получить размер изображения');
|
||||
})().catch(reject); });
|
||||
@@ -118,6 +128,12 @@ export default class BookParser {
|
||||
addIndex: (addIndex ? addIndex : 0),
|
||||
};
|
||||
|
||||
if (inSubtitle) {
|
||||
curSubtitle.title += '<p>';
|
||||
} else if (inTitle) {
|
||||
curTitle.title += '<p>';
|
||||
}
|
||||
|
||||
para[paraIndex] = p;
|
||||
paraOffset += p.length;
|
||||
};
|
||||
@@ -129,6 +145,7 @@ export default class BookParser {
|
||||
return;
|
||||
}
|
||||
|
||||
const prevParaIndex = paraIndex;
|
||||
let p = para[paraIndex];
|
||||
paraOffset -= p.length;
|
||||
//добавление пустых (addEmptyParagraphs) параграфов перед текущим
|
||||
@@ -143,6 +160,11 @@ export default class BookParser {
|
||||
p.offset = paraOffset;
|
||||
para[paraIndex] = p;
|
||||
|
||||
if (curTitle.paraIndex == prevParaIndex)
|
||||
curTitle.paraIndex = paraIndex;
|
||||
if (curSubtitle.paraIndex == prevParaIndex)
|
||||
curSubtitle.paraIndex = paraIndex;
|
||||
|
||||
//уберем начальный пробел
|
||||
p.length = 0;
|
||||
p.text = p.text.substr(1);
|
||||
@@ -151,6 +173,13 @@ export default class BookParser {
|
||||
p.length += len;
|
||||
p.text += text;
|
||||
|
||||
|
||||
if (inSubtitle) {
|
||||
curSubtitle.title += text;
|
||||
} else if (inTitle) {
|
||||
curTitle.title += text;
|
||||
}
|
||||
|
||||
para[paraIndex] = p;
|
||||
paraOffset += p.length;
|
||||
};
|
||||
@@ -160,7 +189,7 @@ export default class BookParser {
|
||||
return;
|
||||
|
||||
tag = elemName;
|
||||
path += '/' + elemName;
|
||||
path += '/' + tag;
|
||||
|
||||
if (tag == 'binary') {
|
||||
let attrs = sax.getAttrsSync(tail);
|
||||
@@ -187,17 +216,55 @@ export default class BookParser {
|
||||
}
|
||||
}
|
||||
|
||||
if (elemName == 'author' && path.indexOf('/fictionbook/description/title-info/author') == 0) {
|
||||
if (path == '/fictionbook/description/title-info/author') {
|
||||
if (!fb2.author)
|
||||
fb2.author = [];
|
||||
|
||||
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 (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)
|
||||
newParagraph(' ', 1);
|
||||
isFirstBody = false;
|
||||
bodyIndex++;
|
||||
}
|
||||
|
||||
if (tag == 'title') {
|
||||
@@ -205,12 +272,17 @@ export default class BookParser {
|
||||
isFirstTitlePara = true;
|
||||
bold = true;
|
||||
center = true;
|
||||
|
||||
inTitle = true;
|
||||
curTitle = {paraIndex, title: '', inset: sectionLevel, bodyIndex, subtitles: []};
|
||||
this.contents.push(curTitle);
|
||||
}
|
||||
|
||||
if (tag == 'section') {
|
||||
if (!isFirstSection)
|
||||
newParagraph(' ', 1);
|
||||
isFirstSection = false;
|
||||
sectionLevel++;
|
||||
}
|
||||
|
||||
if (tag == 'emphasis' || tag == 'strong') {
|
||||
@@ -231,9 +303,13 @@ export default class BookParser {
|
||||
isFirstTitlePara = true;
|
||||
bold = true;
|
||||
center = true;
|
||||
|
||||
inSubtitle = true;
|
||||
curSubtitle = {paraIndex, inset: sectionLevel, title: ''};
|
||||
curTitle.subtitles.push(curSubtitle);
|
||||
}
|
||||
|
||||
if (tag == 'epigraph') {
|
||||
if (tag == 'epigraph' || tag == 'annotation') {
|
||||
italic = true;
|
||||
space += 1;
|
||||
}
|
||||
@@ -260,6 +336,11 @@ export default class BookParser {
|
||||
isFirstTitlePara = false;
|
||||
bold = false;
|
||||
center = false;
|
||||
inTitle = false;
|
||||
}
|
||||
|
||||
if (tag == 'section') {
|
||||
sectionLevel--;
|
||||
}
|
||||
|
||||
if (tag == 'emphasis' || tag == 'strong') {
|
||||
@@ -274,11 +355,14 @@ export default class BookParser {
|
||||
isFirstTitlePara = false;
|
||||
bold = false;
|
||||
center = false;
|
||||
inSubtitle = false;
|
||||
}
|
||||
|
||||
if (tag == 'epigraph') {
|
||||
if (tag == 'epigraph' || tag == 'annotation') {
|
||||
italic = false;
|
||||
space -= 1;
|
||||
if (tag == 'annotation')
|
||||
newParagraph(' ', 1);
|
||||
}
|
||||
|
||||
if (tag == 'stanza') {
|
||||
@@ -372,7 +456,7 @@ export default class BookParser {
|
||||
};
|
||||
|
||||
const onProgress = async(prog) => {
|
||||
await sleep(1);
|
||||
await utils.sleep(1);
|
||||
callback(prog);
|
||||
};
|
||||
|
||||
@@ -394,7 +478,7 @@ export default class BookParser {
|
||||
this.textLength = paraOffset;
|
||||
|
||||
callback(100);
|
||||
await sleep(10);
|
||||
await utils.sleep(10);
|
||||
|
||||
return {fb2};
|
||||
}
|
||||
|
||||
@@ -6,17 +6,23 @@ import BookParser from './BookParser';
|
||||
|
||||
const maxDataSize = 300*1024*1024;//compressed bytes
|
||||
|
||||
//локальный кэш метаданных книг, ограничение maxDataSize
|
||||
const bmMetaStore = localForage.createInstance({
|
||||
name: 'bmMetaStore'
|
||||
});
|
||||
|
||||
//локальный кэш самих книг, ограничение maxDataSize
|
||||
const bmDataStore = localForage.createInstance({
|
||||
name: 'bmDataStore'
|
||||
});
|
||||
|
||||
const bmRecentStore = localForage.createInstance({
|
||||
//список недавно открытых книг
|
||||
const bmRecentStoreOld = localForage.createInstance({
|
||||
name: 'bmRecentStore'
|
||||
});
|
||||
const bmRecentStoreNew = localForage.createInstance({
|
||||
name: 'bmRecentStoreNew'
|
||||
});
|
||||
|
||||
class BookManager {
|
||||
async init(settings) {
|
||||
@@ -25,15 +31,76 @@ class BookManager {
|
||||
|
||||
this.eventListeners = [];
|
||||
this.books = {};
|
||||
this.recent = {};
|
||||
|
||||
this.recentLast = await bmRecentStore.getItem('recent-last');
|
||||
if (this.recentLast) {
|
||||
this.recent[this.recentLast.key] = this.recentLast;
|
||||
const meta = await bmMetaStore.getItem(`bmMeta-${this.recentLast.key}`);
|
||||
if (_.isObject(meta)) {
|
||||
this.books[meta.key] = meta;
|
||||
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.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) {
|
||||
this.recent[this.recentLast.key] = this.recentLast;
|
||||
const meta = await bmMetaStore.getItem(`bmMeta-${this.recentLast.key}`);
|
||||
if (_.isObject(meta)) {
|
||||
this.books[meta.key] = meta;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -41,9 +108,7 @@ class BookManager {
|
||||
this.loadStored();//no await
|
||||
}
|
||||
|
||||
//Долгая асинхронная загрузка из хранилища.
|
||||
//Хранение в отдельных записях дает относительно
|
||||
//нормальное поведение при нескольких вкладках с читалкой в браузере.
|
||||
//Ленивая асинхронная загрузка bmMetaStore
|
||||
async loadStored() {
|
||||
//даем время для загрузки последней читаемой книги, чтобы не блокировать приложение
|
||||
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.cleanRecentBooks();
|
||||
|
||||
this.recentChanged = true;
|
||||
this.loaded = true;
|
||||
this.emit('load-stored-finish');
|
||||
}
|
||||
@@ -238,7 +278,7 @@ class BookManager {
|
||||
let book = this.books[meta.key];
|
||||
|
||||
if (!book && !this.loaded) {
|
||||
book = await bmDataStore.getItem(`bmMeta-${meta.key}`);
|
||||
book = await bmMetaStore.getItem(`bmMeta-${meta.key}`);
|
||||
if (book)
|
||||
this.books[meta.key] = book;
|
||||
}
|
||||
@@ -254,7 +294,7 @@ class BookManager {
|
||||
result = this.books[meta.key];
|
||||
|
||||
if (!result) {
|
||||
result = await bmDataStore.getItem(`bmMeta-${meta.key}`);
|
||||
result = await bmMetaStore.getItem(`bmMeta-${meta.key}`);
|
||||
if (result)
|
||||
this.books[meta.key] = result;
|
||||
}
|
||||
@@ -328,6 +368,43 @@ class BookManager {
|
||||
}
|
||||
|
||||
//-- 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) {
|
||||
const result = this.metaOnly(value);
|
||||
result.touchTime = Date.now();
|
||||
@@ -341,38 +418,25 @@ class BookManager {
|
||||
result.bookPosSeen = this.recent[result.key].bookPosSeen;
|
||||
}
|
||||
|
||||
this.recent[result.key] = 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);
|
||||
await this.recentSetLastKey(result.key);
|
||||
await this.recentSetItem(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
async getRecentBook(value) {
|
||||
let result = this.recent[value.key];
|
||||
if (!result) {
|
||||
result = await bmRecentStore.getItem(value.key);
|
||||
if (result)
|
||||
this.recent[value.key] = result;
|
||||
}
|
||||
return result;
|
||||
return this.recent[value.key];
|
||||
}
|
||||
|
||||
async delRecentBook(value) {
|
||||
this.recent[value.key].deleted = 1;
|
||||
await bmRecentStore.setItem(value.key, this.recent[value.key]);
|
||||
const item = this.recent[value.key];
|
||||
item.deleted = 1;
|
||||
|
||||
if (this.recentLast.key == value.key) {
|
||||
this.recentLast = null;
|
||||
await bmRecentStore.setItem('recent-last', this.recentLast);
|
||||
if (this.recentLastKey == value.key) {
|
||||
await this.recentSetLastKey(null);
|
||||
}
|
||||
|
||||
await this.recentSetItem(item);
|
||||
this.emit('recent-deleted', value.key);
|
||||
this.emit('recent-changed', value.key);
|
||||
}
|
||||
|
||||
async cleanRecentBooks() {
|
||||
@@ -380,24 +444,22 @@ class BookManager {
|
||||
|
||||
let isDel = false;
|
||||
for (let i = 1000; i < sorted.length; i++) {
|
||||
await bmRecentStore.removeItem(sorted[i].key);
|
||||
delete this.recent[sorted[i].key];
|
||||
await bmRecentStore.removeItem(sorted[i].key);
|
||||
isDel = true;
|
||||
}
|
||||
|
||||
this.sortedRecentCached = null;
|
||||
|
||||
if (isDel)
|
||||
this.emit('recent-changed');
|
||||
await this.recentSetItem();
|
||||
return isDel;
|
||||
}
|
||||
|
||||
mostRecentBook() {
|
||||
if (this.recentLast) {
|
||||
return this.recentLast;
|
||||
if (this.recentLastKey) {
|
||||
return this.recent[this.recentLastKey];
|
||||
}
|
||||
const oldRecentLast = this.recentLast;
|
||||
const oldKey = this.recentLastKey;
|
||||
|
||||
let max = 0;
|
||||
let result = null;
|
||||
@@ -408,10 +470,11 @@ class BookManager {
|
||||
result = book;
|
||||
}
|
||||
}
|
||||
this.recentLast = result;
|
||||
bmRecentStore.setItem('recent-last', this.recentLast);//no await
|
||||
|
||||
const newRecentLastKey = (result ? result.key : null);
|
||||
this.recentSetLastKey(newRecentLastKey);//no await
|
||||
|
||||
if (this.recentLast !== oldRecentLast)
|
||||
if (newRecentLastKey !== oldKey)
|
||||
this.emit('recent-changed');
|
||||
|
||||
return result;
|
||||
@@ -442,24 +505,12 @@ class BookManager {
|
||||
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.recentLast = null;
|
||||
await bmRecentStore.setItem('recent-last', this.recentLast);
|
||||
await this.recentSetLastKey(null);
|
||||
await this.recentSetItem(null, true);
|
||||
|
||||
this.recentChanged = true;
|
||||
this.emit('set-recent');
|
||||
this.emit('recent-changed');
|
||||
}
|
||||
|
||||
addEventListener(listener) {
|
||||
|
||||
@@ -1,4 +1,26 @@
|
||||
export const versionHistory = [
|
||||
{
|
||||
showUntil: '2020-11-20',
|
||||
header: '0.9.9 (2020-11-21)',
|
||||
content:
|
||||
`
|
||||
<ul>
|
||||
<li>оптимизации, исправления багов</li>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
|
||||
{
|
||||
showUntil: '2020-11-12',
|
||||
header: '0.9.8 (2020-11-13)',
|
||||
content:
|
||||
`
|
||||
<ul>
|
||||
<li>добавлено окно "Оглавление/закладки"</li>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
|
||||
{
|
||||
showUntil: '2020-11-11',
|
||||
header: '0.9.7 (2020-11-12)',
|
||||
|
||||
@@ -21,7 +21,7 @@ import {QSlider} from 'quasar/src/components/slider';
|
||||
import {QTabs, QTab} from 'quasar/src/components/tabs';
|
||||
//import {QTabPanels, QTabPanel} from 'quasar/src/components/tab-panels';
|
||||
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 {QSpinner} from 'quasar/src/components/spinner';
|
||||
import {QTable, QTh, QTr, QTd} from 'quasar/src/components/table';
|
||||
@@ -32,6 +32,7 @@ import {QPopupProxy} from 'quasar/src/components/popup-proxy';
|
||||
import {QDialog} from 'quasar/src/components/dialog';
|
||||
import {QChip} from 'quasar/src/components/chip';
|
||||
import {QTree} from 'quasar/src/components/tree';
|
||||
//import {QExpansionItem} from 'quasar/src/components/expansion-item';
|
||||
|
||||
const components = {
|
||||
//QLayout,
|
||||
@@ -48,7 +49,7 @@ const components = {
|
||||
QTabs, QTab,
|
||||
//QTabPanels, QTabPanel,
|
||||
QSeparator,
|
||||
QList, QItem, QItemSection, QItemLabel,
|
||||
//QList, QItem, QItemSection, QItemLabel,
|
||||
QTooltip,
|
||||
QSpinner,
|
||||
QTable, QTh, QTr, QTd,
|
||||
@@ -58,7 +59,8 @@ const components = {
|
||||
QPopupProxy,
|
||||
QDialog,
|
||||
QChip,
|
||||
QTree
|
||||
QTree,
|
||||
//QExpansionItem,
|
||||
};
|
||||
|
||||
//directives
|
||||
|
||||
@@ -304,3 +304,55 @@ export function userHotKeysObjectSwap(userHotKeys) {
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -15,7 +15,8 @@ const readerActions = {
|
||||
'splitToPara': 'Обновить с разбиением на параграфы',
|
||||
'refresh': 'Принудительно обновить книгу',
|
||||
'offlineMode': 'Автономный режим (без интернета)',
|
||||
'libs': 'Библиотека',
|
||||
'contents': 'Оглавление/закладки',
|
||||
'libs': 'Сетевая библиотека',
|
||||
'recentBooks': 'Открыть недавние',
|
||||
'switchToolbar': 'Показать/скрыть панель управления',
|
||||
'donate': '',
|
||||
@@ -42,6 +43,7 @@ const toolButtons = [
|
||||
{name: 'copyText', show: false},
|
||||
{name: 'splitToPara', show: false},
|
||||
{name: 'refresh', show: true},
|
||||
{name: 'contents', show: true},
|
||||
{name: 'libs', show: true},
|
||||
{name: 'recentBooks', show: true},
|
||||
{name: 'offlineMode', show: false},
|
||||
@@ -61,9 +63,10 @@ const hotKeys = [
|
||||
{name: 'copyText', codes: ['Ctrl+C']},
|
||||
{name: 'splitToPara', codes: ['Shift+R']},
|
||||
{name: 'refresh', codes: ['R']},
|
||||
{name: 'offlineMode', codes: ['O']},
|
||||
{name: 'contents', codes: ['C']},
|
||||
{name: 'libs', codes: ['L']},
|
||||
{name: 'recentBooks', codes: ['X']},
|
||||
{name: 'offlineMode', codes: ['O']},
|
||||
|
||||
{name: 'switchToolbar', codes: ['Tab', 'Q']},
|
||||
{name: 'bookBegin', codes: ['Home']},
|
||||
@@ -294,15 +297,15 @@ const libsDefaults = {
|
||||
{r: 'https://flibs.in', s: 'https://flibs.in', list: [
|
||||
{l: 'https://flibs.in', c: 'Flibs'},
|
||||
]},
|
||||
{r: 'http://fantasy-worlds.org', s: 'http://fantasy-worlds.org', list: [
|
||||
{l: 'http://fantasy-worlds.org', c: 'Миры Фэнтези'},
|
||||
]},
|
||||
{r: 'http://samlib.ru', s: 'http://samlib.ru', list: [
|
||||
{l: 'http://samlib.ru', c: 'Журнал "Самиздат"'},
|
||||
]},
|
||||
{r: 'http://lib.ru', s: 'http://lib.ru', list: [
|
||||
{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: [
|
||||
{l: 'https://aldebaran.ru', c: 'АЛЬДЕБАРАН | Электронная библиотека книг'},
|
||||
]},
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
sudo -H -u www-data /home/beta.liberama/liberama
|
||||
sudo -H -u www-data bash -c "cd /var/www; /home/beta.liberama/liberama"
|
||||
|
||||
@@ -126,3 +126,18 @@ server {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,9 +27,9 @@ sudo chown www-data.www-data /home/liberama
|
||||
|
||||
### external converter `calibre`, download from https://download.calibre-ebook.com/
|
||||
```
|
||||
wget "https://download.calibre-ebook.com/3.39.1/calibre-3.39.1-x86_64.txz"
|
||||
wget "https://download.calibre-ebook.com/5.5.0/calibre-5.5.0-x86_64.txz"
|
||||
sudo -u www-data mkdir -p /home/liberama/data/calibre
|
||||
sudo -u www-data tar xvf calibre-3.39.1-x86_64.txz -C /home/liberama/data/calibre
|
||||
sudo -u www-data tar xvf calibre-5.5.0-x86_64.txz -C /home/liberama/data/calibre
|
||||
```
|
||||
|
||||
### external converters
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
if ! pgrep -x "liberama" > /dev/null ; then
|
||||
sudo -H -u www-data /home/liberama/liberama
|
||||
sudo -H -u www-data bash -c "cd /var/www; /home/liberama/liberama"
|
||||
else
|
||||
echo "Process 'liberama' already running"
|
||||
fi
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
sudo -H -u www-data /home/liberama/liberama &
|
||||
sudo -H -u www-data bash -c "cd /var/www; /home/liberama/liberama" &
|
||||
sudo service cron start
|
||||
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Liberama",
|
||||
"version": "0.9.7",
|
||||
"version": "0.9.9",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Liberama",
|
||||
"version": "0.9.7",
|
||||
"version": "0.9.9",
|
||||
"author": "Book Pauk <bookpauk@gmail.com>",
|
||||
"license": "CC0-1.0",
|
||||
"repository": "bookpauk/liberama",
|
||||
|
||||
@@ -54,7 +54,7 @@ class FileDetector {
|
||||
|
||||
fromBuffer(buffer, callback) {
|
||||
let result = null;
|
||||
|
||||
//console.log(buffer);
|
||||
const invalidSignaturesList = this.validateSigantures();
|
||||
if (invalidSignaturesList.length) {
|
||||
return callback(invalidSignaturesList);
|
||||
|
||||
@@ -676,7 +676,9 @@
|
||||
{ "type": "equal", "end": 19, "bytes": "3c3f786d6c2076657273696f6e3d22312e3022" },
|
||||
{ "type": "equal", "end": 22, "bytes": "efbbbf3c3f786d6c2076657273696f6e3d22312e3022" },
|
||||
{ "type": "equal", "end": 19, "bytes": "3c3f786d6c2076657273696f6e3d27312e3027" },
|
||||
{ "type": "equal", "end": 22, "bytes": "efbbbf3c3f786d6c2076657273696f6e3d27312e3027" }
|
||||
{ "type": "equal", "end": 22, "bytes": "efbbbf3c3f786d6c2076657273696f6e3d27312e3027" },
|
||||
{ "type": "equal", "end": 40, "bytes": "fffe3c003f0078006d006c002000760065007200730069006f006e003d00220031002e0030002200" },
|
||||
{ "type": "equal", "end": 40, "bytes": "fffe3c003f0078006d006c002000760065007200730069006f006e003d00270031002e0030002700" }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -53,9 +53,7 @@ class ConvertBase {
|
||||
abort
|
||||
});
|
||||
if (result.code != 0) {
|
||||
let error = result.code;
|
||||
if (this.config.branch == 'development')
|
||||
error = `exec: ${path}, stdout: ${result.stdout}, stderr: ${result.stderr}`;
|
||||
const error = `${result.code}|FORLOG|, exec: ${path}, args: ${args.join(' ')}, stdout: ${result.stdout}, stderr: ${result.stderr}`;
|
||||
throw new Error(`Внешний конвертер завершился с ошибкой: ${error}`);
|
||||
}
|
||||
} catch(e) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const ConvertBase = require('./ConvertBase');
|
||||
const iconv = require('iconv-lite');
|
||||
const textUtils = require('./textUtils');
|
||||
|
||||
class ConvertFb2 extends ConvertBase {
|
||||
check(data, opts) {
|
||||
@@ -9,26 +10,46 @@ class ConvertFb2 extends ConvertBase {
|
||||
}
|
||||
|
||||
async run(data, opts) {
|
||||
if (!this.check(data, opts))
|
||||
let newData = data;
|
||||
|
||||
//Корректируем кодировку, 16-битные кодировки должны стать utf-8
|
||||
const encoding = textUtils.getEncoding(newData);
|
||||
if (encoding.indexOf('UTF-16') == 0) {
|
||||
newData = Buffer.from(iconv.decode(newData, encoding));
|
||||
}
|
||||
|
||||
if (!this.check(newData, opts))
|
||||
return false;
|
||||
|
||||
return this.checkEncoding(data);
|
||||
return this.checkEncoding(newData);
|
||||
}
|
||||
|
||||
checkEncoding(data) {
|
||||
let result = data;
|
||||
|
||||
const left = data.indexOf('<?xml version="1.0"');
|
||||
let q = '"';
|
||||
let left = data.indexOf('<?xml version="1.0"');
|
||||
if (left < 0) {
|
||||
left = data.indexOf('<?xml version=\'1.0\'');
|
||||
q = '\'';
|
||||
}
|
||||
|
||||
if (left >= 0) {
|
||||
const right = data.indexOf('?>', left);
|
||||
if (right >= 0) {
|
||||
const head = data.slice(left, right + 2).toString();
|
||||
const m = head.match(/encoding="(.*?)"/);
|
||||
const m = head.match(/encoding=['"](.*?)['"]/);
|
||||
if (m) {
|
||||
let encoding = m[1].toLowerCase();
|
||||
if (encoding != 'utf-8') {
|
||||
result = iconv.decode(data, encoding);
|
||||
result = Buffer.from(result.toString().replace(m[0], 'encoding="utf-8"'));
|
||||
//encoding может не соответсвовать реальной кодировке файла, поэтому:
|
||||
let calcEncoding = textUtils.getEncoding(data);
|
||||
if (calcEncoding.indexOf('ISO-8859') >= 0) {
|
||||
calcEncoding = encoding;
|
||||
}
|
||||
|
||||
result = iconv.decode(data, calcEncoding);
|
||||
result = Buffer.from(result.toString().replace(m[0], `encoding=${q}utf-8${q}`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +218,7 @@ class ConvertHtml extends ConvertBase {
|
||||
|
||||
let i = 0;
|
||||
//если разброс не слишком большой, выделяем параграфы
|
||||
if (d < 10 && spaceCounter.length) {
|
||||
if (d < 20 && spaceCounter.length) {
|
||||
total /= 20;
|
||||
i = spaceCounter.length - 1;
|
||||
while (i > 0 && (!spaceCounter[i] || spaceCounter[i] < total)) i--;
|
||||
|
||||
@@ -24,7 +24,7 @@ class ConvertPdf extends ConvertHtml {
|
||||
|
||||
//конвертируем в xml
|
||||
let perc = 0;
|
||||
await this.execConverter(this.pdfToHtmlPath, ['-c', '-s', '-xml', inputFiles.sourceFile, outFile], () => {
|
||||
await this.execConverter(this.pdfToHtmlPath, ['-nodrm', '-c', '-s', '-xml', inputFiles.sourceFile, outFile], () => {
|
||||
perc = (perc < 80 ? perc + 10 : 40);
|
||||
callback(perc);
|
||||
}, abort);
|
||||
|
||||
@@ -171,9 +171,15 @@ class ReaderWorker {
|
||||
|
||||
} catch (e) {
|
||||
log(LM_ERR, e.stack);
|
||||
if (e.message == 'abort')
|
||||
e.message = overLoadMes;
|
||||
wState.set({state: 'error', error: e.message});
|
||||
let mes = e.message.split('|FORLOG|');
|
||||
if (mes[1])
|
||||
log(LM_ERR, mes[0] + mes[1]);
|
||||
log(LM_ERR, `downloadedFilename: ${downloadedFilename}`);
|
||||
|
||||
mes = mes[0];
|
||||
if (mes == 'abort')
|
||||
mes = overLoadMes;
|
||||
wState.set({state: 'error', error: mes});
|
||||
} finally {
|
||||
//clean
|
||||
if (q)
|
||||
|
||||
Reference in New Issue
Block a user