Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3f1707343 | ||
|
|
1ed058a553 | ||
|
|
0500a8178d | ||
|
|
7d0059f573 | ||
|
|
4e3b882362 | ||
|
|
13cf47873e | ||
|
|
7ee23ec38f | ||
|
|
eebf17c42c | ||
|
|
f84536788b | ||
|
|
4bbfdc2cb2 | ||
|
|
211fec35e3 | ||
|
|
b8214a46ae | ||
|
|
549ef91c81 | ||
|
|
cede65313b | ||
|
|
d897a7400f | ||
|
|
47f059213f | ||
|
|
8af51bbf08 | ||
|
|
53d9f5ddc6 | ||
|
|
06fffdccc8 | ||
|
|
aa13dc68fc | ||
|
|
813876dd90 | ||
|
|
596c7d65c5 | ||
|
|
ce8dcb75bf | ||
|
|
1bd51b5565 | ||
|
|
1f9ec305b4 | ||
|
|
be0f6e57d7 | ||
|
|
b268e9ee74 | ||
|
|
e97774435b | ||
|
|
93586bc5bb | ||
|
|
fe23089714 | ||
|
|
e743986f38 | ||
|
|
a6c9b700ed | ||
|
|
afa3fcb524 | ||
|
|
b9aeb648d6 | ||
|
|
5f5df1e5b7 | ||
|
|
ad885679e4 | ||
|
|
e002bebfbe | ||
|
|
a8a41e2b3d | ||
|
|
31940caa84 | ||
|
|
880334054e | ||
|
|
5f03ad5597 | ||
|
|
1efa3f055d | ||
|
|
8ccf11278b | ||
|
|
8a9e7ab4c3 | ||
|
|
c0fa7c0c51 |
@@ -56,7 +56,11 @@ class Reader {
|
||||
}
|
||||
}
|
||||
|
||||
async loadCachedBook(url, callback){
|
||||
async checkUrl(url) {
|
||||
return await axios.head(url, {headers: {'Cache-Control': 'no-cache'}});
|
||||
}
|
||||
|
||||
async loadCachedBook(url, callback) {
|
||||
const response = await axios.head(url);
|
||||
|
||||
let estSize = 1000000;
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow: /?*url=
|
||||
Disallow: /#/
|
||||
@@ -11,7 +11,7 @@
|
||||
<el-tab-pane label="Клавиатура">
|
||||
<HotkeysHelpPage></HotkeysHelpPage>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="Мышь/тачпад">
|
||||
<el-tab-pane label="Мышь/тачскрин">
|
||||
<MouseHelpPage></MouseHelpPage>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="История версий" name="releases">
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<h4>Управление с помощью мыши/тачпада:</h4>
|
||||
<h4>Управление с помощью мыши/тачскрина:</h4>
|
||||
<ul>
|
||||
<li><b>ЛКМ/ТАЧ</b> по экрану в одну из областей - активация действия:</li>
|
||||
<div class="click-map-page">
|
||||
<ClickMapPage ref="clickMapPage"></ClickMapPage>
|
||||
</div>
|
||||
|
||||
<li><b>ПКМ</b> - показать/скрыть панель управления</li>
|
||||
<li><b>СКМ</b> - вкл./выкл. плавный скроллинг текста</li>
|
||||
<br>
|
||||
<li>Жесты для тачскрина:</li>
|
||||
<ul>
|
||||
<li style="list-style-type: square">от центра вверх: на весь экран</li>
|
||||
<li style="list-style-type: square">от центра вниз: плавный скроллинг</li>
|
||||
<li style="list-style-type: square">от центра вправо: увеличить скорость скроллинга</li>
|
||||
<li style="list-style-type: square">от центра влево: уменьшить скорость скроллинга</li>
|
||||
</ul>
|
||||
|
||||
</ul>
|
||||
* Для управления с помощью мыши/тачпада необходимо установить галочку "Включить управление кликом" в настройках
|
||||
* Для управления с помощью мыши/тачскрина необходимо установить галочку "Включить управление кликом" в настройках
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div ref="main" class="main">
|
||||
<div class="part">
|
||||
<div class="part top">
|
||||
<span class="greeting bold-font">{{ title }}</span>
|
||||
<div class="space"></div>
|
||||
<span class="greeting">Добро пожаловать!</span>
|
||||
@@ -14,6 +14,7 @@
|
||||
</el-input>
|
||||
<div class="space"></div>
|
||||
<input type="file" id="file" ref="file" @change="loadFile" style='display: none;'/>
|
||||
|
||||
<el-button size="mini" @click="loadFileClick">
|
||||
Загрузить файл с диска
|
||||
</el-button>
|
||||
@@ -21,13 +22,16 @@
|
||||
<el-button size="mini" @click="loadBufferClick">
|
||||
Из буфера обмена
|
||||
</el-button>
|
||||
|
||||
<div class="space"></div>
|
||||
<div class="space"></div>
|
||||
<div v-if="mode == 'omnireader'" ref="yaShare2" class="ya-share2"
|
||||
data-services="collections,vkontakte,facebook,odnoklassniki,twitter,telegram"
|
||||
data-description="Чтение fb2-книг онлайн. Загрузка любой страницы интернета одним кликом, синхронизация между устройствами, удобное управление, регистрация не требуется."
|
||||
data-title="Omni Reader - браузерная онлайн-читалка"
|
||||
data-url="https://omnireader.ru">
|
||||
<div v-if="mode == 'omnireader'">
|
||||
<div ref="yaShare2" class="ya-share2"
|
||||
data-services="collections,vkontakte,facebook,odnoklassniki,twitter,telegram"
|
||||
data-description="Чтение fb2-книг онлайн. Загрузка любой страницы интернета одним кликом, синхронизация между устройствами, удобное управление, регистрация не требуется."
|
||||
data-title="Omni Reader - браузерная онлайн-читалка"
|
||||
data-url="https://omnireader.ru">
|
||||
</div>
|
||||
</div>
|
||||
<div class="space"></div>
|
||||
<span v-if="mode == 'omnireader'" class="bottom-span clickable" @click="openComments">Отзывы о читалке</span>
|
||||
@@ -184,7 +188,7 @@ class LoaderPage extends Vue {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 400px;
|
||||
min-height: 480px;
|
||||
}
|
||||
|
||||
.part {
|
||||
@@ -210,9 +214,14 @@ class LoaderPage extends Vue {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.top {
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.center {
|
||||
justify-content: flex-start;
|
||||
padding: 0 10px 0 10px;
|
||||
min-height: 250px;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<el-container>
|
||||
<div v-show="isShowMigrationWarning" style="height: 30px; overflow: hidden; white-space:nowrap;">
|
||||
<span style="margin-left: 10px; font-size: 20px; position: relative; top: 2px" class="clickable" @click="showMigrationHttp">HTTP-версия читалки скоро станет недоступна. Подробнее...</span>
|
||||
</div>
|
||||
<el-header v-show="toolBarActive" height='50px'>
|
||||
<div ref="header" class="header">
|
||||
<el-tooltip content="Загрузить книгу" :open-delay="1000" effect="light">
|
||||
@@ -320,15 +323,6 @@ class Reader extends Vue {
|
||||
});
|
||||
|
||||
this.loadSettings();
|
||||
|
||||
//TODO: убрать в будущем
|
||||
if (this.showToolButton['history']) {
|
||||
const newShowToolButton = Object.assign({}, this.showToolButton);
|
||||
newShowToolButton['recentBooks'] = true;
|
||||
delete newShowToolButton['history'];
|
||||
const newSettings = Object.assign({}, this.settings, { showToolButton: newShowToolButton });
|
||||
this.commit('reader/setSettings', newSettings);
|
||||
}
|
||||
}
|
||||
|
||||
mounted() {
|
||||
@@ -444,6 +438,14 @@ class Reader extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
get isShowMigrationWarning() {
|
||||
return window.location.protocol == 'http:';
|
||||
}
|
||||
|
||||
showMigrationHttp() {
|
||||
this.migrationVisible1 = true;
|
||||
}
|
||||
|
||||
migrationDialogDisable() {
|
||||
this.migrationVisible1 = false;
|
||||
this.migrationVisible2 = false;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<span v-show="loading"><i class="el-icon-loading" style="font-size: 25px"></i> <span style="position: relative; top: -4px">Список загружается</span></span>
|
||||
</template>
|
||||
|
||||
<a ref="download" style='display: none;'></a>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
style="width: 570px"
|
||||
@@ -72,7 +73,7 @@
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<a v-show="isUrl(scope.row.url)" :href="scope.row.url" target="_blank">Оригинал</a><br>
|
||||
<a :href="scope.row.path" :download="getFileNameFromPath(scope.row.path)">Скачать FB2</a>
|
||||
<a :href="scope.row.path" @click.prevent="downloadBook(scope.row.path)">Скачать FB2</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
@@ -104,6 +105,7 @@ import _ from 'lodash';
|
||||
import * as utils from '../../../share/utils';
|
||||
import Window from '../../share/Window.vue';
|
||||
import bookManager from '../share/bookManager';
|
||||
import readerApi from '../../../api/reader';
|
||||
|
||||
export default @Component({
|
||||
components: {
|
||||
@@ -209,7 +211,7 @@ class RecentBooksPage extends Vue {
|
||||
a.middleName
|
||||
]).join(' '));
|
||||
author = authorNames.join(', ');
|
||||
} else {
|
||||
} else {//TODO: убрать в будущем
|
||||
author = _.compact([
|
||||
fb2.lastName,
|
||||
fb2.firstName,
|
||||
@@ -268,8 +270,20 @@ class RecentBooksPage extends Vue {
|
||||
return result;
|
||||
}
|
||||
|
||||
getFileNameFromPath(fb2Path) {
|
||||
return path.basename(fb2Path).substr(0, 10) + '.fb2';
|
||||
async downloadBook(fb2path) {
|
||||
try {
|
||||
await readerApi.checkUrl(fb2path);
|
||||
|
||||
const d = this.$refs.download;
|
||||
d.href = fb2path;
|
||||
d.download = path.basename(fb2path).substr(0, 10) + '.fb2';
|
||||
d.click();
|
||||
} catch (e) {
|
||||
let errMes = e.message;
|
||||
if (errMes.indexOf('404') >= 0)
|
||||
errMes = 'Файл не найден на сервере (возможно был удален как устаревший)';
|
||||
this.$alert(errMes, 'Ошибка', {type: 'error'});
|
||||
}
|
||||
}
|
||||
|
||||
openOriginal(url) {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</div>
|
||||
<div v-show="clickControl" ref="layoutEvents" class="layout events" @mousedown.prevent.stop="onMouseDown" @mouseup.prevent.stop="onMouseUp"
|
||||
@wheel.prevent.stop="onMouseWheel"
|
||||
@touchstart.stop="onTouchStart" @touchend.stop="onTouchEnd" @touchcancel.prevent.stop="onTouchCancel"
|
||||
@touchstart.stop="onTouchStart" @touchend.stop="onTouchEnd" @touchmove.stop="onTouchMove" @touchcancel.prevent.stop="onTouchCancel"
|
||||
oncontextmenu="return false;">
|
||||
<div v-show="showStatusBar" v-html="statusBarClickable" @mousedown.prevent.stop @touchstart.stop
|
||||
@click.prevent.stop="onStatusBarClick"></div>
|
||||
@@ -877,6 +877,14 @@ class TextPage extends Vue {
|
||||
this.$emit('tool-bar-toggle');
|
||||
}
|
||||
|
||||
doScrollingToggle() {
|
||||
this.$emit('scrolling-toggle');
|
||||
}
|
||||
|
||||
doFullScreenToggle() {
|
||||
this.$emit('full-screen-toogle');
|
||||
}
|
||||
|
||||
async doFontSizeInc() {
|
||||
if (!this.settingsChanging) {
|
||||
this.settingsChanging = true;
|
||||
@@ -968,7 +976,7 @@ class TextPage extends Vue {
|
||||
case 'Enter':
|
||||
case 'Backquote'://`
|
||||
case 'KeyF':
|
||||
this.$emit('full-screen-toogle');
|
||||
this.doFullScreenToggle();
|
||||
break;
|
||||
case 'Tab':
|
||||
case 'KeyQ':
|
||||
@@ -1009,22 +1017,64 @@ class TextPage extends Vue {
|
||||
if (!this.$isMobileDevice)
|
||||
return;
|
||||
this.endClickRepeat();
|
||||
|
||||
if (event.touches.length == 1) {
|
||||
const touch = event.touches[0];
|
||||
const rect = event.target.getBoundingClientRect();
|
||||
const x = touch.pageX - rect.left;
|
||||
const y = touch.pageY - rect.top;
|
||||
if (this.handleClick(x, y)) {
|
||||
this.repDoing = true;
|
||||
this.debouncedStartClickRepeat(x, y);
|
||||
const hc = this.handleClick(x, y, new Set(['Menu']));
|
||||
if (hc) {
|
||||
if (hc != 'Menu') {
|
||||
this.repDoing = true;
|
||||
this.debouncedStartClickRepeat(x, y);
|
||||
} else {
|
||||
this.startTouch = {x, y};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onTouchEnd() {
|
||||
onTouchMove(event) {
|
||||
if (this.startTouch) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
onTouchEnd(event) {
|
||||
if (!this.$isMobileDevice)
|
||||
return;
|
||||
this.endClickRepeat();
|
||||
|
||||
if (event.changedTouches.length == 1) {
|
||||
const touch = event.changedTouches[0];
|
||||
const rect = event.target.getBoundingClientRect();
|
||||
const x = touch.pageX - rect.left;
|
||||
const y = touch.pageY - rect.top;
|
||||
if (this.startTouch) {
|
||||
const dy = this.startTouch.y - y;
|
||||
const dx = this.startTouch.x - x;
|
||||
const moveDelta = 30;
|
||||
const touchDelta = 15;
|
||||
if (dy > 0 && Math.abs(dy) >= moveDelta && Math.abs(dy) > Math.abs(dx)) {
|
||||
//движение вверх
|
||||
this.doFullScreenToggle();
|
||||
} else if (dy < 0 && Math.abs(dy) >= moveDelta && Math.abs(dy) > Math.abs(dx)) {
|
||||
//движение вниз
|
||||
this.doScrollingToggle();
|
||||
} else if (dx > 0 && Math.abs(dx) >= moveDelta && Math.abs(dy) < Math.abs(dx)) {
|
||||
//движение влево
|
||||
this.doScrollingSpeedDown();
|
||||
} else if (dx < 0 && Math.abs(dx) >= moveDelta && Math.abs(dy) < Math.abs(dx)) {
|
||||
//движение вправо
|
||||
this.doScrollingSpeedUp();
|
||||
} else if (Math.abs(dy) < touchDelta && Math.abs(dx) < touchDelta) {
|
||||
this.doToolBarToggle();
|
||||
}
|
||||
|
||||
this.startTouch = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onTouchCancel() {
|
||||
@@ -1038,12 +1088,13 @@ class TextPage extends Vue {
|
||||
return;
|
||||
this.endClickRepeat();
|
||||
if (event.button == 0) {
|
||||
if (this.handleClick(event.offsetX, event.offsetY)) {
|
||||
const hc = this.handleClick(event.offsetX, event.offsetY);
|
||||
if (hc && hc != 'Menu') {
|
||||
this.repDoing = true;
|
||||
this.debouncedStartClickRepeat(event.offsetX, event.offsetY);
|
||||
}
|
||||
} else if (event.button == 1) {
|
||||
this.$emit('scrolling-toggle');
|
||||
this.doScrollingToggle();
|
||||
} else if (event.button == 2) {
|
||||
this.doToolBarToggle();
|
||||
}
|
||||
@@ -1074,7 +1125,7 @@ class TextPage extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
handleClick(pointX, pointY) {
|
||||
getClickAction(pointX, pointY) {
|
||||
const w = pointX/this.realWidth*100;
|
||||
const h = pointY/this.realHeight*100;
|
||||
|
||||
@@ -1090,27 +1141,35 @@ class TextPage extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case 'Down' ://Down
|
||||
this.doDown();
|
||||
break;
|
||||
case 'Up' ://Up
|
||||
this.doUp();
|
||||
break;
|
||||
case 'PgDown' ://PgDown
|
||||
this.doPageDown();
|
||||
break;
|
||||
case 'PgUp' ://PgUp
|
||||
this.doPageUp();
|
||||
break;
|
||||
case 'Menu' :
|
||||
this.doToolBarToggle();
|
||||
break;
|
||||
default :
|
||||
// Nothing
|
||||
return action;
|
||||
}
|
||||
|
||||
handleClick(pointX, pointY, exclude) {
|
||||
const action = this.getClickAction(pointX, pointY);
|
||||
|
||||
if (!exclude || !exclude.has(action)) {
|
||||
switch (action) {
|
||||
case 'Down' ://Down
|
||||
this.doDown();
|
||||
break;
|
||||
case 'Up' ://Up
|
||||
this.doUp();
|
||||
break;
|
||||
case 'PgDown' ://PgDown
|
||||
this.doPageDown();
|
||||
break;
|
||||
case 'PgUp' ://PgUp
|
||||
this.doPageUp();
|
||||
break;
|
||||
case 'Menu' :
|
||||
this.doToolBarToggle();
|
||||
break;
|
||||
default :
|
||||
// Nothing
|
||||
}
|
||||
}
|
||||
|
||||
return (action && action != 'Menu');
|
||||
return action;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,9 +32,6 @@ export default class BookParser {
|
||||
|
||||
//defaults
|
||||
let fb2 = {
|
||||
firstName: '',
|
||||
middleName: '',
|
||||
lastName: '',
|
||||
bookTitle: '',
|
||||
};
|
||||
|
||||
@@ -240,6 +237,7 @@ export default class BookParser {
|
||||
newParagraph(' ', 1);
|
||||
isFirstTitlePara = true;
|
||||
bold = true;
|
||||
center = true;
|
||||
}
|
||||
|
||||
if (tag == 'epigraph') {
|
||||
@@ -282,6 +280,7 @@ export default class BookParser {
|
||||
if (tag == 'subtitle') {
|
||||
isFirstTitlePara = false;
|
||||
bold = false;
|
||||
center = false;
|
||||
}
|
||||
|
||||
if (tag == 'epigraph') {
|
||||
@@ -367,11 +366,10 @@ export default class BookParser {
|
||||
tClose += (bold ? '</strong>' : '');
|
||||
tClose += (center ? '</center>' : '');
|
||||
|
||||
if (path.indexOf('/fictionbook/body/title') == 0) {
|
||||
growParagraph(`${tOpen}${text}${tClose}`, text.length);
|
||||
}
|
||||
|
||||
if (path.indexOf('/fictionbook/body/section') == 0) {
|
||||
if (path.indexOf('/fictionbook/body/title') == 0 ||
|
||||
path.indexOf('/fictionbook/body/section') == 0 ||
|
||||
path.indexOf('/fictionbook/body/epigraph') == 0
|
||||
) {
|
||||
growParagraph(`${tOpen}${text}${tClose}`, text.length);
|
||||
}
|
||||
|
||||
|
||||
@@ -319,7 +319,6 @@ class BookManager {
|
||||
|
||||
metaOnly(book) {
|
||||
let result = Object.assign({}, book);
|
||||
delete result.data;//можно будет убрать эту строку со временем
|
||||
delete result.parsed;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,45 @@
|
||||
export const versionHistory = [
|
||||
{
|
||||
showUntil: '2019-11-26',
|
||||
header: '0.7.9 (2019-11-27)',
|
||||
content:
|
||||
`
|
||||
<ul>
|
||||
<li>добавлен неубираемый баннер для http-версии о переходе на httpS</li>
|
||||
<li>исправления багов</li>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
|
||||
{
|
||||
showUntil: '2019-11-24',
|
||||
header: '0.7.8 (2019-11-25)',
|
||||
content:
|
||||
`
|
||||
<ul>
|
||||
<li>улучшение html-фильтров для сайтов</li>
|
||||
<li>исправления багов</li>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
|
||||
{
|
||||
showUntil: '2019-11-10',
|
||||
header: '0.7.7 (2019-11-06)',
|
||||
content:
|
||||
`
|
||||
<ul>
|
||||
<li>добавлены следующие жесты для тачскрина (только при включенной опции "управление кликом"):</li>
|
||||
<ul>
|
||||
<li style="list-style-type: square">от центра вверх: на весь экран</li>
|
||||
<li style="list-style-type: square">от центра вниз: плавный скроллинг</li>
|
||||
<li style="list-style-type: square">от центра вправо: увеличить скорость скроллинга</li>
|
||||
<li style="list-style-type: square">от центра влево: уменьшить скорость скроллинга</li>
|
||||
</ul>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
|
||||
{
|
||||
showUntil: '2019-10-29',
|
||||
header: '0.7.6 (2019-10-30)',
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<meta name="description" content="браузерная онлайн-читалка книг из интернета и библиотека">
|
||||
<meta name="keywords" content="библиотека,онлайн,читалка,книги,читать,браузер,интернет">
|
||||
<meta name="description" content="Браузерная онлайн-читалка книг. Поддерживаются форматы: fb2, html, txt, rtf, doc, docx, pdf, epub, mobi.">
|
||||
<meta name="keywords" content="онлайн,читалка,fb2,книги,читать,браузер,интернет">
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Liberama",
|
||||
"version": "0.7.6",
|
||||
"version": "0.7.9",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
|
||||
@@ -41,9 +41,9 @@ class ConfigManager {
|
||||
process.env.NODE_ENV = this.branch;
|
||||
|
||||
this.branchConfigFile = __dirname + `/${this.branch}.js`;
|
||||
await fs.access(this.branchConfigFile);
|
||||
this._config = require(this.branchConfigFile);
|
||||
|
||||
await fs.ensureDir(this._config.dataDir);
|
||||
this._userConfigFile = `${this._config.dataDir}/config.json`;
|
||||
|
||||
this.inited = true;
|
||||
@@ -83,6 +83,7 @@ class ConfigManager {
|
||||
async save() {
|
||||
if (!this.inited)
|
||||
throw new Error('not inited');
|
||||
|
||||
const dataToSave = _.pick(this._config, propsToSave);
|
||||
await fs.writeFile(this.userConfigFile, JSON.stringify(dataToSave, null, 4));
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ class AppLogger {
|
||||
loggerParams = [
|
||||
{log: 'ConsoleLog'},
|
||||
{log: 'FileLog', fileName: `${config.logDir}/${config.name}.log`},
|
||||
{log: 'FileLog', fileName: `${config.logDir}/${config.name}.err.log`, exclude: [LM_OK, LM_INFO]},
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ class ConvertBase {
|
||||
}
|
||||
|
||||
escapeEntities(text) {
|
||||
return he.escape(he.decode(text));
|
||||
return he.escape(he.decode(text.replace(/ /g, ' ')));
|
||||
}
|
||||
|
||||
formatFb2(fb2) {
|
||||
|
||||
@@ -23,7 +23,7 @@ class ConvertFb2 extends ConvertBase {
|
||||
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') {
|
||||
|
||||
@@ -39,16 +39,19 @@ class ConvertHtml extends ConvertBase {
|
||||
|
||||
let title = '';
|
||||
let inTitle = false;
|
||||
let inSubTitle = false;
|
||||
let inImage = false;
|
||||
let image = {};
|
||||
let bold = false;
|
||||
let italic = false;
|
||||
let begining = true;
|
||||
|
||||
let spaceCounter = [];
|
||||
|
||||
const repCrLfTab = (text) => text.replace(/[\n\r]/g, '').replace(/\t/g, ' ');
|
||||
|
||||
const newParagraph = () => {
|
||||
begining = false;
|
||||
pars.push({_n: 'p', _t: ''});
|
||||
};
|
||||
|
||||
@@ -58,12 +61,15 @@ class ConvertHtml extends ConvertBase {
|
||||
|
||||
const l = pars.length;
|
||||
pars[l - 1]._t += text;
|
||||
if (inSubTitle)
|
||||
pars[l - 1]._n = '';
|
||||
|
||||
//посчитаем отступы у текста, чтобы выделить потом параграфы
|
||||
const lines = text.split('\n');
|
||||
for (let line of lines) {
|
||||
if (line.trim() == '')
|
||||
continue;
|
||||
|
||||
line = repCrLfTab(line);
|
||||
|
||||
let l = 0;
|
||||
@@ -76,16 +82,21 @@ class ConvertHtml extends ConvertBase {
|
||||
}
|
||||
};
|
||||
|
||||
const newPara = new Set(['tr', '/table', 'hr', 'br', 'br/', 'li', 'dt', 'dd', 'p', 'title', '/title', 'h1', 'h2', 'h3', '/h1', '/h2', '/h3']);
|
||||
const newPara = new Set(['tr', '/table', 'hr', 'br', 'br/', 'li', 'dt', 'dd', 'p', 'title', '/title', 'ul', '/ul', 'h1', 'h2', 'h3', 'h4', 'h5', '/h1', '/h2', '/h3', '/h4', '/h5']);
|
||||
const newPara2 = new Set(['h1', 'h2', 'h3', 'h4', 'h5']);
|
||||
|
||||
const onTextNode = (text, cutCounter, cutTag) => {// eslint-disable-line no-unused-vars
|
||||
text = this.escapeEntities(text);
|
||||
|
||||
if (!cutCounter && !(cutTitle && inTitle)) {
|
||||
let tOpen = (bold ? '<strong>' : '');
|
||||
let tOpen = '';
|
||||
tOpen += (inSubTitle ? '<subtitle>' : '');
|
||||
tOpen += (bold ? '<strong>' : '');
|
||||
tOpen += (italic ? '<emphasis>' : '');
|
||||
let tClose = (italic ? '</emphasis>' : '');
|
||||
let tClose = ''
|
||||
tClose += (italic ? '</emphasis>' : '');
|
||||
tClose += (bold ? '</strong>' : '');
|
||||
tClose += (inSubTitle ? '</subtitle>' : '');
|
||||
|
||||
growParagraph(`${tOpen}${text}${tClose}`);
|
||||
}
|
||||
@@ -105,6 +116,8 @@ class ConvertHtml extends ConvertBase {
|
||||
|
||||
const onStartNode = (tag, tail, singleTag, cutCounter, cutTag) => {// eslint-disable-line no-unused-vars
|
||||
if (!cutCounter) {
|
||||
if (newPara2.has(tag) && !begining)
|
||||
newParagraph();
|
||||
if (newPara.has(tag))
|
||||
newParagraph();
|
||||
|
||||
@@ -129,6 +142,10 @@ class ConvertHtml extends ConvertBase {
|
||||
cutTitle = true;
|
||||
}
|
||||
|
||||
if (tag == 'subtitle') {
|
||||
inSubTitle = true;
|
||||
}
|
||||
|
||||
if (tag == 'fb2-image') {
|
||||
inImage = true;
|
||||
const attrs = sax.getAttrsSync(tail);
|
||||
@@ -140,6 +157,8 @@ class ConvertHtml extends ConvertBase {
|
||||
if (!cutCounter) {
|
||||
if (newPara.has('/' + tag))
|
||||
newParagraph();
|
||||
if (newPara2.has('/' + tag))
|
||||
newParagraph();
|
||||
|
||||
switch (tag) {
|
||||
case 'i':
|
||||
@@ -159,6 +178,9 @@ class ConvertHtml extends ConvertBase {
|
||||
if (tag == 'title' || tag == 'cut-title')
|
||||
inTitle = false;
|
||||
|
||||
if (tag == 'subtitle')
|
||||
inSubTitle = false;
|
||||
|
||||
if (tag == 'fb2-image')
|
||||
inImage = false;
|
||||
};
|
||||
@@ -171,7 +193,6 @@ class ConvertHtml extends ConvertBase {
|
||||
});
|
||||
|
||||
titleInfo['book-title'] = title;
|
||||
|
||||
//подозрение на чистый текст, надо разбить на параграфы
|
||||
if (isText || pars.length < buf.length/2000) {
|
||||
let total = 0;
|
||||
@@ -197,7 +218,8 @@ class ConvertHtml extends ConvertBase {
|
||||
while (i > 0 && (!spaceCounter[i] || spaceCounter[i] < total)) i--;
|
||||
}
|
||||
|
||||
const parIndent = (i > 0 ? i : 0);
|
||||
let parIndent = (i > 0 ? i : 0);
|
||||
if (parIndent > 2) parIndent--;
|
||||
|
||||
let newPars = [];
|
||||
const newPar = () => {
|
||||
@@ -233,7 +255,7 @@ class ConvertHtml extends ConvertBase {
|
||||
l++;
|
||||
}
|
||||
|
||||
if (l >= parIndent) {
|
||||
if (l >= parIndent || line == '') {
|
||||
if (j > 0)
|
||||
newPar();
|
||||
j++;
|
||||
@@ -250,6 +272,7 @@ class ConvertHtml extends ConvertBase {
|
||||
//убираем лишнее, делаем валидный fb2, т.к. в рез-те разбиения на параграфы бьются теги
|
||||
bold = false;
|
||||
italic = false;
|
||||
inSubTitle = false;
|
||||
pars = body.section._a[0];
|
||||
for (let i = 0; i < pars.length; i++) {
|
||||
if (pars[i]._n != 'p')
|
||||
@@ -257,17 +280,26 @@ class ConvertHtml extends ConvertBase {
|
||||
|
||||
pars[i]._t = this.repSpaces(pars[i]._t).trim();
|
||||
|
||||
if (pars[i]._t.indexOf('<') >= 0) {
|
||||
if (pars[i]._t.indexOf('<') >= 0 || bold || italic) {
|
||||
const t = pars[i]._t;
|
||||
let first = true;
|
||||
|
||||
let a = [];
|
||||
|
||||
const onTextNode = (text) => {
|
||||
let tOpen = (bold ? '<strong>' : '');
|
||||
let tOpen = '';
|
||||
tOpen += (inSubTitle ? '<subtitle>' : '');
|
||||
tOpen += (bold ? '<strong>' : '');
|
||||
tOpen += (italic ? '<emphasis>' : '');
|
||||
let tClose = (italic ? '</emphasis>' : '');
|
||||
let tClose = ''
|
||||
tClose += (italic ? '</emphasis>' : '');
|
||||
tClose += (bold ? '</strong>' : '');
|
||||
tClose += (inSubTitle ? '</subtitle>' : '');
|
||||
|
||||
if (first)
|
||||
text = text.replace(/^\s+/, ''); //trimLeft
|
||||
a.push(`${tOpen}${text}${tClose}`);
|
||||
first = false;
|
||||
}
|
||||
|
||||
const onStartNode = (tag) => {
|
||||
@@ -275,6 +307,8 @@ class ConvertHtml extends ConvertBase {
|
||||
bold = true;
|
||||
if (tag == 'emphasis')
|
||||
italic = true;
|
||||
if (tag == 'subtitle')
|
||||
inSubTitle = true;
|
||||
}
|
||||
|
||||
const onEndNode = (tag) => {
|
||||
@@ -282,6 +316,8 @@ class ConvertHtml extends ConvertBase {
|
||||
bold = false;
|
||||
if (tag == 'emphasis')
|
||||
italic = false;
|
||||
if (tag == 'subtitle')
|
||||
inSubTitle = false;
|
||||
}
|
||||
|
||||
sax.parseSync(t, { onStartNode, onEndNode, onTextNode });
|
||||
|
||||
@@ -102,6 +102,8 @@ class ConvertSamlib extends ConvertBase {
|
||||
case 'h1':
|
||||
case 'h2':
|
||||
case 'h3':
|
||||
case 'h4':
|
||||
case 'h5':
|
||||
if (inPara)
|
||||
closeTag('p');
|
||||
openTag('p');
|
||||
@@ -173,6 +175,8 @@ class ConvertSamlib extends ConvertBase {
|
||||
case 'h1':
|
||||
case 'h2':
|
||||
case 'h3':
|
||||
case 'h4':
|
||||
case 'h5':
|
||||
closeTag('p');
|
||||
bold = false;
|
||||
break;
|
||||
|
||||
@@ -12,7 +12,10 @@ const sitesFilter = {
|
||||
converter: 'cutter',
|
||||
begin: `<!-- BEGIN section where work skin applies -->`,
|
||||
end: `<!-- END work skin -->`,
|
||||
}
|
||||
},
|
||||
'flibusta.is': {
|
||||
converter: 'flibusta'
|
||||
},
|
||||
};
|
||||
|
||||
class ConvertSites extends ConvertHtml {
|
||||
@@ -54,11 +57,11 @@ class ConvertSites extends ConvertHtml {
|
||||
if (m)
|
||||
title = m[1];
|
||||
|
||||
return `<title>${title.trim()}</title>`;
|
||||
return title.trim();
|
||||
}
|
||||
|
||||
cutter(text, opts) {
|
||||
const title = this.getTitle(text);
|
||||
const title = `<title>${this.getTitle(text)}</title>`;
|
||||
const l = text.indexOf(opts.begin) + opts.begin.length;
|
||||
const r = text.indexOf(opts.end);
|
||||
if (l < 0 || r < 0 || r <= l)
|
||||
@@ -66,6 +69,42 @@ class ConvertSites extends ConvertHtml {
|
||||
|
||||
return text.substring(l, r) + title;
|
||||
}
|
||||
|
||||
flibusta(text) {
|
||||
let author = '';
|
||||
let m = text.match(/- <a href=".+">([\s\S]*?)<\/a><br\/?>/);
|
||||
if (m)
|
||||
author = m[1];
|
||||
|
||||
let book = this.getTitle(text);
|
||||
book = book.replace(' (fb2) | Флибуста', '');
|
||||
|
||||
const title = `<title>${author}${(author ? ' - ' : '')}${book}</title>`;
|
||||
|
||||
let begin = '<h3 class="book">';
|
||||
if (text.indexOf(begin) <= 0)
|
||||
begin = '<h3 class=book>';
|
||||
|
||||
const end = '<div id="footer">';
|
||||
|
||||
const l = text.indexOf(begin);
|
||||
const r = text.indexOf(end);
|
||||
if (l < 0 || r < 0 || r <= l)
|
||||
return false;
|
||||
|
||||
return text.substring(l, r)
|
||||
.replace(/blockquote class="?book"?/g, 'p')
|
||||
.replace(/<br\/?>\s*<\/h3>/g, '</h3>')
|
||||
.replace(/<h3 class="?book"?>/g, '<br><br><subtitle>')
|
||||
.replace(/<h5 class="?book"?>/g, '<br><br><subtitle>')
|
||||
.replace(/<h3>/g, '<br><br><subtitle>')
|
||||
.replace(/<h5>/g, '<br><br><subtitle>')
|
||||
.replace(/<\/h3>/g, '</subtitle><br>')
|
||||
.replace(/<\/h5>/g, '</subtitle><br>')
|
||||
.replace(/<div class="?stanza"?>/g, '<br>')
|
||||
.replace(/<div>/g, '<br>')
|
||||
+ title;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ConvertSites;
|
||||
|
||||
@@ -74,8 +74,7 @@ class ReaderWorker {
|
||||
try {
|
||||
decompFiles = await this.decomp.decompressNested(downloadedFilename, decompDir);
|
||||
} catch (e) {
|
||||
if (this.config.branch == 'development')
|
||||
console.error(e);
|
||||
log(LM_ERR, e.stack);
|
||||
throw new Error('Ошибка распаковки');
|
||||
}
|
||||
wState.set({progress: 100});
|
||||
@@ -97,8 +96,7 @@ class ReaderWorker {
|
||||
wState.finish({path: `/tmp/${finishFilename}`});
|
||||
|
||||
} catch (e) {
|
||||
if (this.config.branch == 'development')
|
||||
console.error(e);
|
||||
log(LM_ERR, e.stack);
|
||||
wState.set({state: 'error', error: (errMes ? errMes : e.message)});
|
||||
} finally {
|
||||
//clean
|
||||
@@ -137,7 +135,6 @@ class ReaderWorker {
|
||||
|
||||
async periodicCleanDir(dir, maxSize, timeout) {
|
||||
try {
|
||||
log(`Start clean dir: ${dir}, maxSize=${maxSize}`);
|
||||
const list = await fs.readdir(dir);
|
||||
|
||||
let size = 0;
|
||||
@@ -149,21 +146,20 @@ class ReaderWorker {
|
||||
files.push({name, stat});
|
||||
}
|
||||
}
|
||||
log(`found ${files.length} files in dir ${dir}`);
|
||||
log(`clean dir ${dir}, maxSize=${maxSize}, found ${files.length} files`);
|
||||
|
||||
files.sort((a, b) => a.stat.mtimeMs - b.stat.mtimeMs);
|
||||
|
||||
let i = 0;
|
||||
while (i < files.length && size > maxSize) {
|
||||
const file = files[i];
|
||||
log(`rm ${dir}/${file.name}`);
|
||||
await fs.remove(`${dir}/${file.name}`);
|
||||
size -= file.stat.size;
|
||||
i++;
|
||||
}
|
||||
log(`removed ${i} files`);
|
||||
} catch(e) {
|
||||
log(LM_ERR, e.message);
|
||||
log(LM_ERR, e.stack);
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
this.periodicCleanDir(dir, maxSize, timeout);
|
||||
|
||||
@@ -18,7 +18,7 @@ async function init() {
|
||||
const log = appLogger.log;
|
||||
|
||||
//dirs
|
||||
log(`${config.name} v${config.version}`);
|
||||
log(`${config.name} v${config.version}, Node.js ${process.version}`);
|
||||
log('Initializing');
|
||||
|
||||
await fs.ensureDir(config.dataDir);
|
||||
@@ -40,7 +40,7 @@ async function init() {
|
||||
await connManager.init(config);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
async function main() {
|
||||
const log = new (require('./core/AppLogger'))().log;//singleton
|
||||
const config = new (require('./config'))().config;//singleton
|
||||
|
||||
|
||||
Reference in New Issue
Block a user