Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fd577d7c5 | ||
|
|
2c8efebe98 | ||
|
|
93c9fb53ac | ||
|
|
5a4d249cf9 | ||
|
|
4cc7bdee37 | ||
|
|
a6af568411 | ||
|
|
576a6a094a | ||
|
|
e671e4b6f5 | ||
|
|
a66b2a4c70 | ||
|
|
f1ae409535 | ||
|
|
a4b56b477d | ||
|
|
d9c389812a | ||
|
|
074ef3645f | ||
|
|
cc3aa413e8 | ||
|
|
7f90c09227 | ||
|
|
f6f4d8ccc9 | ||
|
|
31afce8304 | ||
|
|
2c4ff856cd | ||
|
|
f59974e310 | ||
|
|
70e2c12a6b | ||
|
|
11f3c6ce6f | ||
|
|
e213c4640b | ||
|
|
959c5eaa59 | ||
|
|
66fa510b26 | ||
|
|
f26a3b31ac | ||
|
|
724fbf579e | ||
|
|
f192f8e3cd | ||
|
|
f13c3d19fb | ||
|
|
b51a09efcc | ||
|
|
6004043782 | ||
|
|
f9fd0dc2c3 | ||
|
|
eb5411cd20 | ||
|
|
da3c7a02f0 | ||
|
|
e67d05007f | ||
|
|
b0a9a6a08e | ||
|
|
d848ea35f4 | ||
|
|
350f20effe | ||
|
|
b6dc8f98fe | ||
|
|
1b762ee48d | ||
|
|
cc3d7f1eac | ||
|
|
4107282fbf | ||
|
|
c29ffc3fcd | ||
|
|
f648bcda13 | ||
|
|
aa0044eed2 | ||
|
|
2312a721ae | ||
|
|
b93fc39b00 | ||
|
|
2dc2cd700f |
@@ -5,11 +5,11 @@ import {Buffer} from 'safe-buffer';
|
||||
import * as utils from '../share/utils';
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: '/api/reader'
|
||||
baseURL: '/api/reader'
|
||||
});
|
||||
|
||||
const workerApi = axios.create({
|
||||
baseURL: '/api/worker'
|
||||
baseURL: '/api/worker'
|
||||
});
|
||||
|
||||
class Reader {
|
||||
|
||||
@@ -113,10 +113,13 @@ class App extends Vue {
|
||||
this.dispatch('config/loadConfig');
|
||||
this.$watch('apiError', function(newError) {
|
||||
if (newError) {
|
||||
let mes = newError.message;
|
||||
if (newError.response && newError.response.config)
|
||||
mes = newError.response.config.url + '<br>' + newError.response.statusText;
|
||||
this.$notify.error({
|
||||
title: 'Ошибка API',
|
||||
dangerouslyUseHTMLString: true,
|
||||
message: newError.response.config.url + '<br>' + newError.response.statusText
|
||||
message: mes
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -18,9 +18,15 @@
|
||||
<li>поддерживаемые браузеры: Google Chrome, Mozilla Firefox последних версий</li>
|
||||
</ul>
|
||||
|
||||
<p>В качестве URL можно задавать html-страничку с книгой, либо прямую ссылку
|
||||
<p>В качестве URL книги можно задавать html-страничку с книгой, либо прямую ссылку
|
||||
на файл из онлайн-библиотеки (например, скопировав адрес ссылки или кнопки "скачать fb2").</p>
|
||||
<p>Поддерживаемые форматы: <b>fb2, fb2.zip, html, txt</b> и другие</p>
|
||||
<p>Поддерживаемые форматы: <b>fb2, fb2.zip, html, txt</b> и другие.</p>
|
||||
|
||||
<p>Для автономной загрузки читалки (без интернета):<br>
|
||||
В Google Chrome можно установить флаг <span class="clickable" @click="copyText('chrome://flags/#show-saved-copy')">chrome://flags/#show-saved-copy</span>
|
||||
в значение "Primary". В этом случае на стандартной странице "нет соединения" появится кнопка для автономной загрузки сайта из кэша.<br>
|
||||
В Mozilla Firefox в автономном режиме сайт загружается из кэша автоматически. Если этого не происходит, можно установить опцию
|
||||
"Веб-разработка" -> "Работать автономно".</p>
|
||||
|
||||
<div v-html="automationHtml"></div>
|
||||
<p>Связаться с разработчиком: <a href="mailto:bookpauk@gmail.com">bookpauk@gmail.com</a></p>
|
||||
@@ -32,6 +38,8 @@
|
||||
import Vue from 'vue';
|
||||
import Component from 'vue-class-component';
|
||||
|
||||
import {copyTextToClipboard} from '../../../../share/utils';
|
||||
|
||||
export default @Component({
|
||||
})
|
||||
class CommonHelpPage extends Vue {
|
||||
@@ -41,13 +49,22 @@ class CommonHelpPage extends Vue {
|
||||
|
||||
get automationHtml() {
|
||||
if (this.config.mode == 'omnireader') {
|
||||
return `<p>Вы можете добавить в свой браузер закладку, указав в ее свойствах вместо адреса следующий код:
|
||||
return `<p>Вы также можете добавить в свой браузер закладку, указав в ее свойствах вместо адреса следующий код:
|
||||
<br><strong>javascript:location.href='http://omnireader.ru/?url='+location.href;</strong>
|
||||
<br>Тогда, нажав на получившуюся кнопку на любой странице интернета, вы автоматически откроете ее в Omni Reader.</p>`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
async copyText(text) {
|
||||
const result = await copyTextToClipboard(text);
|
||||
const msg = (result ? `Ссылка на флаг успешно скопирована в буфер обмена. Можно открыть ее в новой вкладке.` : 'Копирование не удалось');
|
||||
if (result)
|
||||
this.$notify.success({message: msg});
|
||||
else
|
||||
this.$notify.error({message: msg});
|
||||
}
|
||||
}
|
||||
//-----------------------------------------------------------------------------
|
||||
</script>
|
||||
@@ -64,4 +81,10 @@ class CommonHelpPage extends Vue {
|
||||
h4 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
color: blue;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -53,11 +53,10 @@ class DonateHelpPage extends Vue {
|
||||
|
||||
async copyAddress(address, prefix) {
|
||||
const result = await copyTextToClipboard(address);
|
||||
const msg = (result ? `${prefix}-адрес ${address} успешно скопирован в буфер обмена` : 'Копирование не удалось');
|
||||
if (result)
|
||||
this.$notify.success({message: msg});
|
||||
this.$notify.success({message: `${prefix}-адрес ${address} успешно скопирован в буфер обмена`});
|
||||
else
|
||||
this.$notify.error({message: msg});
|
||||
this.$notify.error({message: 'Копирование не удалось'});
|
||||
}
|
||||
}
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
<el-tab-pane label="Мышь/тачпад">
|
||||
<MouseHelpPage></MouseHelpPage>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="История версий" name="releases">
|
||||
<VersionHistoryPage></VersionHistoryPage>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="Помочь проекту" name="donate">
|
||||
<DonateHelpPage></DonateHelpPage>
|
||||
</el-tab-pane>
|
||||
@@ -36,6 +39,7 @@ import CommonHelpPage from './CommonHelpPage/CommonHelpPage.vue';
|
||||
import HotkeysHelpPage from './HotkeysHelpPage/HotkeysHelpPage.vue';
|
||||
import MouseHelpPage from './MouseHelpPage/MouseHelpPage.vue';
|
||||
import DonateHelpPage from './DonateHelpPage/DonateHelpPage.vue';
|
||||
import VersionHistoryPage from './VersionHistoryPage/VersionHistoryPage.vue';
|
||||
|
||||
export default @Component({
|
||||
components: {
|
||||
@@ -44,6 +48,7 @@ export default @Component({
|
||||
HotkeysHelpPage,
|
||||
MouseHelpPage,
|
||||
DonateHelpPage,
|
||||
VersionHistoryPage,
|
||||
},
|
||||
})
|
||||
class HelpPage extends Vue {
|
||||
@@ -57,6 +62,10 @@ class HelpPage extends Vue {
|
||||
this.selectedTab = 'donate';
|
||||
}
|
||||
|
||||
activateVersionHistoryHelpPage() {
|
||||
this.selectedTab = 'releases';
|
||||
}
|
||||
|
||||
keyHook(event) {
|
||||
if (event.type == 'keydown' && (event.code == 'Escape')) {
|
||||
this.close();
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div id="versionHistoryPage" class="page">
|
||||
<span class="clickable" v-for="(item, index) in versionHeader" :key="index" @click="showRelease(item)">
|
||||
<p>
|
||||
{{ item }}
|
||||
</p>
|
||||
</span>
|
||||
|
||||
<br>
|
||||
<h4>История версий:</h4>
|
||||
<br>
|
||||
|
||||
<div v-for="item in versionContent" :id="item.key" :key="item.key">
|
||||
<span v-html="item.content"></span>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
//-----------------------------------------------------------------------------
|
||||
import Vue from 'vue';
|
||||
import Component from 'vue-class-component';
|
||||
import {versionHistory} from '../../versionHistory';
|
||||
|
||||
export default @Component({
|
||||
})
|
||||
class VersionHistoryPage extends Vue {
|
||||
versionHeader = [];
|
||||
versionContent = [];
|
||||
|
||||
created() {
|
||||
}
|
||||
|
||||
mounted() {
|
||||
let vh = [];
|
||||
for (const version of versionHistory) {
|
||||
vh.push(version.header);
|
||||
}
|
||||
this.versionHeader = vh;
|
||||
|
||||
let vc = [];
|
||||
for (const version of versionHistory) {
|
||||
vc.push({key: version.header, content: 'Версия ' + version.header + version.content});
|
||||
}
|
||||
this.versionContent = vc;
|
||||
}
|
||||
|
||||
showRelease(id) {
|
||||
let el = document.getElementById(id);
|
||||
if (el) {
|
||||
document.getElementById('versionHistoryPage').scrollTop = el.offsetTop;
|
||||
}
|
||||
}
|
||||
}
|
||||
//-----------------------------------------------------------------------------
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
flex: 1;
|
||||
padding: 15px;
|
||||
overflow-y: auto;
|
||||
font-size: 120%;
|
||||
line-height: 130%;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 15px;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
color: blue;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@@ -18,7 +18,19 @@
|
||||
Загрузить файл с диска
|
||||
</el-button>
|
||||
<div class="space"></div>
|
||||
<span v-if="mode == 'omnireader'" class="bottom-span clickable" @click="openComments">Комментарии</span>
|
||||
<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="http://omnireader.ru">
|
||||
</div>
|
||||
<div class="space"></div>
|
||||
<span v-if="mode == 'omnireader'" class="bottom-span clickable" @click="openComments">Отзывы о читалке</span>
|
||||
</div>
|
||||
|
||||
<div class="part bottom">
|
||||
@@ -26,6 +38,8 @@
|
||||
<span class="bottom-span clickable" @click="openDonate">Помочь проекту</span>
|
||||
<span class="bottom-span">{{ version }}</span>
|
||||
</div>
|
||||
|
||||
<PasteTextPage v-if="pasteTextActive" ref="pasteTextPage" @paste-text-toggle="pasteTextToggle" @load-buffer="loadBuffer"></PasteTextPage>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -33,12 +47,17 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
import Vue from 'vue';
|
||||
import Component from 'vue-class-component';
|
||||
import PasteTextPage from './PasteTextPage/PasteTextPage.vue';
|
||||
|
||||
export default @Component({
|
||||
components: {
|
||||
PasteTextPage,
|
||||
},
|
||||
})
|
||||
class LoaderPage extends Vue {
|
||||
bookUrl = null;
|
||||
loadPercent = 0;
|
||||
pasteTextActive = false;
|
||||
|
||||
created() {
|
||||
this.commit = this.$store.commit;
|
||||
@@ -46,6 +65,8 @@ class LoaderPage extends Vue {
|
||||
|
||||
mounted() {
|
||||
this.progress = this.$refs.progress;
|
||||
if (this.mode == 'omnireader')
|
||||
Ya.share2(this.$refs.yaShare2);// eslint-disable-line no-undef
|
||||
}
|
||||
|
||||
activated() {
|
||||
@@ -53,7 +74,7 @@ class LoaderPage extends Vue {
|
||||
}
|
||||
|
||||
get title() {
|
||||
if (this.$store.state.config.mode == 'omnireader')
|
||||
if (this.mode == 'omnireader')
|
||||
return 'Omni Reader - браузерная онлайн-читалка.';
|
||||
return 'Универсальная читалка книг и ресурсов интернета.';
|
||||
|
||||
@@ -83,12 +104,27 @@ class LoaderPage extends Vue {
|
||||
}
|
||||
|
||||
loadFile() {
|
||||
const file = this.$refs.file.files[0];
|
||||
const file = this.$refs.file.files[0];
|
||||
this.$refs.file.value = '';
|
||||
if (file)
|
||||
this.$emit('load-file', {file});
|
||||
}
|
||||
|
||||
loadBufferClick() {
|
||||
this.pasteTextToggle();
|
||||
}
|
||||
|
||||
loadBuffer(opts) {
|
||||
if (opts.buffer.length) {
|
||||
const file = new File([opts.buffer], 'dummyName-PasteFromClipboard');
|
||||
this.$emit('load-file', {file});
|
||||
}
|
||||
}
|
||||
|
||||
pasteTextToggle() {
|
||||
this.pasteTextActive = !this.pasteTextActive;
|
||||
}
|
||||
|
||||
openHelp() {
|
||||
this.$emit('help-toggle');
|
||||
}
|
||||
@@ -102,6 +138,10 @@ class LoaderPage extends Vue {
|
||||
}
|
||||
|
||||
keyHook(event) {
|
||||
if (this.pasteTextActive) {
|
||||
return this.$refs.pasteTextPage.keyHook(event);
|
||||
}
|
||||
|
||||
//недостатки сторонних ui
|
||||
const input = this.$refs.input.$refs.input;
|
||||
if (document.activeElement === input && event.type == 'keydown' && event.code == 'Enter') {
|
||||
@@ -130,7 +170,7 @@ class LoaderPage extends Vue {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 340px;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.part {
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<div ref="main" class="main" @click="close">
|
||||
<div class="mainWindow" @click.stop>
|
||||
<Window @close="close">
|
||||
<template slot="header">
|
||||
Вставьте текст и нажмите
|
||||
<el-button size="mini" style="font-size: 120%; color: blue" @click="loadBuffer">Загрузить</el-button>
|
||||
|
||||
или F2
|
||||
</template>
|
||||
|
||||
<div>
|
||||
<el-input placeholder="Введите название текста" class="input" v-model="bookTitle"></el-input>
|
||||
</div>
|
||||
<hr/>
|
||||
<textarea ref="textArea" class="text" @paste="calcTitle"></textarea>
|
||||
</Window>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
//-----------------------------------------------------------------------------
|
||||
import Vue from 'vue';
|
||||
import Component from 'vue-class-component';
|
||||
|
||||
import Window from '../../../share/Window.vue';
|
||||
import _ from 'lodash';
|
||||
import * as utils from '../../../../share/utils';
|
||||
|
||||
export default @Component({
|
||||
components: {
|
||||
Window,
|
||||
},
|
||||
})
|
||||
class PasteTextPage extends Vue {
|
||||
bookTitle = '';
|
||||
|
||||
created() {
|
||||
}
|
||||
|
||||
mounted() {
|
||||
this.$refs.textArea.focus();
|
||||
}
|
||||
|
||||
getNonEmptyLine3words(text, count) {
|
||||
let result = '';
|
||||
const lines = text.split("\n");
|
||||
let i = 0;
|
||||
while (i < lines.length) {
|
||||
if (lines[i].trim() != '') {
|
||||
count--;
|
||||
if (count <= 0) {
|
||||
result = lines[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
result = result.trim().split(' ');
|
||||
return result.slice(0, 3).join(' ');
|
||||
}
|
||||
|
||||
calcTitle(event) {
|
||||
if (this.bookTitle == '') {
|
||||
let text = event.clipboardData.getData('text');
|
||||
this.bookTitle = `Из буфера обмена ${utils.formatDate(new Date(), 'noDate')}: ` + _.compact([
|
||||
this.getNonEmptyLine3words(text, 1),
|
||||
this.getNonEmptyLine3words(text, 2)
|
||||
]).join(' - ');
|
||||
}
|
||||
}
|
||||
|
||||
loadBuffer() {
|
||||
this.$emit('load-buffer', {buffer: `<cut-title>${this.bookTitle}</cut-title>${this.$refs.textArea.value}`});
|
||||
this.close();
|
||||
}
|
||||
|
||||
close() {
|
||||
this.$emit('paste-text-toggle');
|
||||
}
|
||||
|
||||
keyHook(event) {
|
||||
if (event.type == 'keydown') {
|
||||
switch (event.code) {
|
||||
case 'F2':
|
||||
this.loadBuffer();
|
||||
break;
|
||||
case 'Escape':
|
||||
this.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
//-----------------------------------------------------------------------------
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.main {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 40;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mainWindow {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.text {
|
||||
flex: 1;
|
||||
overflow-wrap: anywhere;
|
||||
overflow-y: auto;
|
||||
padding: 0 10px 0 10px;
|
||||
position: relative;
|
||||
font-size: 120%;
|
||||
}
|
||||
|
||||
.text:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -7,35 +7,35 @@
|
||||
</el-tooltip>
|
||||
|
||||
<div>
|
||||
<el-tooltip content="Действие назад" :open-delay="1000" effect="light">
|
||||
<el-tooltip v-show="showToolButton['undoAction']" content="Действие назад" :open-delay="1000" effect="light">
|
||||
<el-button ref="undoAction" class="tool-button" :class="buttonActiveClass('undoAction')" @click="buttonClick('undoAction')" ><i class="el-icon-arrow-left"></i></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="Действие вперед" :open-delay="1000" effect="light">
|
||||
<el-tooltip v-show="showToolButton['redoAction']" content="Действие вперед" :open-delay="1000" effect="light">
|
||||
<el-button ref="redoAction" class="tool-button" :class="buttonActiveClass('redoAction')" @click="buttonClick('redoAction')" ><i class="el-icon-arrow-right"></i></el-button>
|
||||
</el-tooltip>
|
||||
<div class="space"></div>
|
||||
<el-tooltip content="На весь экран" :open-delay="1000" effect="light">
|
||||
<el-tooltip v-show="showToolButton['fullScreen']" content="На весь экран" :open-delay="1000" effect="light">
|
||||
<el-button ref="fullScreen" class="tool-button" :class="buttonActiveClass('fullScreen')" @click="buttonClick('fullScreen')"><i class="el-icon-rank"></i></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="Плавный скроллинг" :open-delay="1000" effect="light">
|
||||
<el-tooltip v-show="showToolButton['scrolling']" content="Плавный скроллинг" :open-delay="1000" effect="light">
|
||||
<el-button ref="scrolling" class="tool-button" :class="buttonActiveClass('scrolling')" @click="buttonClick('scrolling')"><i class="el-icon-sort"></i></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="Перелистнуть" :open-delay="1000" effect="light">
|
||||
<el-tooltip v-show="showToolButton['setPosition']" content="На страницу" :open-delay="1000" effect="light">
|
||||
<el-button ref="setPosition" class="tool-button" :class="buttonActiveClass('setPosition')" @click="buttonClick('setPosition')"><i class="el-icon-d-arrow-right"></i></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="Найти в тексте" :open-delay="1000" effect="light">
|
||||
<el-tooltip v-show="showToolButton['search']" content="Найти в тексте" :open-delay="1000" effect="light">
|
||||
<el-button ref="search" class="tool-button" :class="buttonActiveClass('search')" @click="buttonClick('search')"><i class="el-icon-search"></i></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="Скопировать текст со страницы" :open-delay="1000" effect="light">
|
||||
<el-tooltip v-show="showToolButton['copyText']" content="Скопировать текст со страницы" :open-delay="1000" effect="light">
|
||||
<el-button ref="copyText" class="tool-button" :class="buttonActiveClass('copyText')" @click="buttonClick('copyText')"><i class="el-icon-edit-outline"></i></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="Принудительно обновить книгу в обход кэша" :open-delay="1000" effect="light">
|
||||
<el-tooltip v-show="showToolButton['refresh']" content="Принудительно обновить книгу в обход кэша" :open-delay="1000" effect="light">
|
||||
<el-button ref="refresh" class="tool-button" :class="buttonActiveClass('refresh')" @click="buttonClick('refresh')">
|
||||
<i class="el-icon-refresh" :class="{clear: !showRefreshIcon}"></i>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<div class="space"></div>
|
||||
<el-tooltip content="Открыть недавние" :open-delay="1000" effect="light">
|
||||
<el-tooltip v-show="showToolButton['history']" content="Открыть недавние" :open-delay="1000" effect="light">
|
||||
<el-button ref="history" class="tool-button" :class="buttonActiveClass('history')" @click="buttonClick('history')"><i class="el-icon-document"></i></el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
@@ -68,13 +68,27 @@
|
||||
@start-text-search="startTextSearch"
|
||||
@stop-text-search="stopTextSearch">
|
||||
</SearchPage>
|
||||
<CopyTextPage v-if="copyTextActive" ref="copyTextPage" @copy-text-toggle="copyTextToggle"></CopyTextPage>
|
||||
<CopyTextPage v-if="copyTextActive" ref="copyTextPage" @copy-text-toggle="copyTextToggle"></CopyTextPage>
|
||||
<HistoryPage v-show="historyActive" ref="historyPage" @load-book="loadBook" @history-toggle="historyToggle"></HistoryPage>
|
||||
<SettingsPage v-if="settingsActive" ref="settingsPage" @settings-toggle="settingsToggle"></SettingsPage>
|
||||
<HelpPage v-if="helpActive" ref="helpPage" @help-toggle="helpToggle"></HelpPage>
|
||||
<ClickMapPage v-show="clickMapActive" ref="clickMapPage"></ClickMapPage>
|
||||
<ServerStorage v-show="hidden" ref="serverStorage"></ServerStorage>
|
||||
|
||||
<el-dialog
|
||||
title="Что нового:"
|
||||
:visible.sync="whatsNewVisible"
|
||||
width="80%">
|
||||
<div style="line-height: 20px" v-html="whatsNewContent"></div>
|
||||
|
||||
<span class="clickable" @click="openVersionHistory">Посмотреть историю версий</span>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="whatsNewDisable">Больше не показывать</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
</el-main>
|
||||
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
@@ -101,6 +115,7 @@ import ServerStorage from './ServerStorage/ServerStorage.vue';
|
||||
import bookManager from './share/bookManager';
|
||||
import readerApi from '../../api/reader';
|
||||
import * as utils from '../../share/utils';
|
||||
import {versionHistory} from './versionHistory';
|
||||
|
||||
export default @Component({
|
||||
components: {
|
||||
@@ -167,11 +182,15 @@ class Reader extends Vue {
|
||||
allowUrlParamBookPos = false;
|
||||
showRefreshIcon = true;
|
||||
mostRecentBookReactive = null;
|
||||
showToolButton = {};
|
||||
|
||||
actionList = [];
|
||||
actionCur = -1;
|
||||
hidden = false;
|
||||
|
||||
whatsNewVisible = false;
|
||||
whatsNewContent = '';
|
||||
|
||||
created() {
|
||||
this.loading = true;
|
||||
this.commit = this.$store.commit;
|
||||
@@ -223,14 +242,17 @@ class Reader extends Vue {
|
||||
|
||||
if (this.$root.rootRoute == '/reader') {
|
||||
if (this.routeParamUrl) {
|
||||
await this.loadBook({url: this.routeParamUrl, bookPos: this.routeParamPos});
|
||||
await this.loadBook({url: this.routeParamUrl, bookPos: this.routeParamPos, force: this.routeParamRefresh});
|
||||
} else {
|
||||
this.loaderActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.checkSetStorageAccessKey();
|
||||
this.checkActivateDonateHelpPage();
|
||||
this.loading = false;
|
||||
|
||||
await this.showWhatsNew();
|
||||
})();
|
||||
}
|
||||
|
||||
@@ -241,6 +263,8 @@ class Reader extends Vue {
|
||||
this.showClickMapPage = settings.showClickMapPage;
|
||||
this.clickControl = settings.clickControl;
|
||||
this.blinkCachedLoad = settings.blinkCachedLoad;
|
||||
this.showWhatsNewDialog = settings.showWhatsNewDialog;
|
||||
this.showToolButton = settings.showToolButton;
|
||||
}
|
||||
|
||||
checkSetStorageAccessKey() {
|
||||
@@ -257,6 +281,56 @@ class Reader extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
checkActivateDonateHelpPage() {
|
||||
const q = this.$route.query;
|
||||
|
||||
if (q['donate']) {
|
||||
this.$router.replace(`/reader`);
|
||||
this.helpToggle();
|
||||
this.$nextTick(() => {
|
||||
this.$refs.helpPage.activateDonateHelpPage();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
checkBookPosPercent() {
|
||||
const q = this.$route.query;
|
||||
if (q['__pp']) {
|
||||
let pp = q['__pp'];
|
||||
if (pp) {
|
||||
pp = parseFloat(pp) || 0;
|
||||
const recent = this.mostRecentBook();
|
||||
(async() => {
|
||||
await utils.sleep(100);
|
||||
this.bookPos = Math.floor(recent.textLength*pp/100);
|
||||
})();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async showWhatsNew() {
|
||||
await utils.sleep(2000);
|
||||
|
||||
const whatsNew = versionHistory[0];
|
||||
if (this.showWhatsNewDialog &&
|
||||
whatsNew.showUntil >= utils.formatDate(new Date(), 'coDate') &&
|
||||
whatsNew.header != this.whatsNewContentHash) {
|
||||
this.whatsNewContent = 'Версия ' + whatsNew.header + whatsNew.content;
|
||||
this.whatsNewVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
openVersionHistory() {
|
||||
this.whatsNewVisible = false;
|
||||
this.versionHistoryToggle();
|
||||
}
|
||||
|
||||
whatsNewDisable() {
|
||||
this.whatsNewVisible = false;
|
||||
const whatsNew = versionHistory[0];
|
||||
this.commit('reader/setWhatsNewContentHash', whatsNew.header);
|
||||
}
|
||||
|
||||
get routeParamPos() {
|
||||
let result = undefined;
|
||||
const q = this.$route.query;
|
||||
@@ -293,6 +367,11 @@ class Reader extends Vue {
|
||||
return decodeURIComponent(result);
|
||||
}
|
||||
|
||||
get routeParamRefresh() {
|
||||
const q = this.$route.query;
|
||||
return !!q['__refresh'];
|
||||
}
|
||||
|
||||
bookPosChanged(event) {
|
||||
if (event.bookPosSeen !== undefined)
|
||||
this.bookPosSeen = event.bookPosSeen;
|
||||
@@ -353,6 +432,10 @@ class Reader extends Vue {
|
||||
return this.$store.state.reader.settings;
|
||||
}
|
||||
|
||||
get whatsNewContentHash() {
|
||||
return this.$store.state.reader.whatsNewContentHash;
|
||||
}
|
||||
|
||||
addAction(pos) {
|
||||
let a = this.actionList;
|
||||
if (!a.length || a[a.length - 1] != pos) {
|
||||
@@ -523,6 +606,15 @@ class Reader extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
versionHistoryToggle() {
|
||||
this.helpToggle();
|
||||
if (this.helpActive) {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.helpPage.activateVersionHistoryHelpPage();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
refreshBook() {
|
||||
if (this.mostRecentBook()) {
|
||||
this.loadBook({url: this.mostRecentBook().url, force: true});
|
||||
@@ -708,7 +800,7 @@ class Reader extends Vue {
|
||||
|
||||
this.progressActive = true;
|
||||
|
||||
await this.$nextTick()
|
||||
await this.$nextTick();
|
||||
|
||||
const progress = this.$refs.page;
|
||||
|
||||
@@ -743,6 +835,7 @@ class Reader extends Vue {
|
||||
progress.hide(); this.progressActive = false;
|
||||
this.blinkCachedLoadMessage();
|
||||
|
||||
this.checkBookPosPercent();
|
||||
await this.activateClickMapPage();
|
||||
return;
|
||||
}
|
||||
@@ -791,6 +884,7 @@ class Reader extends Vue {
|
||||
} else
|
||||
this.stopBlink = true;
|
||||
|
||||
this.checkBookPosPercent();
|
||||
await this.activateClickMapPage();
|
||||
} catch (e) {
|
||||
progress.hide(); this.progressActive = false;
|
||||
@@ -1010,4 +1104,10 @@ i {
|
||||
.clear {
|
||||
color: rgba(0,0,0,0);
|
||||
}
|
||||
</style>
|
||||
|
||||
.clickable {
|
||||
color: blue;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -50,6 +50,7 @@ class ServerStorage extends Vue {
|
||||
this.oldSettings = {};
|
||||
this.oldRecent = {};
|
||||
this.oldRecentLast = {};
|
||||
this.oldRecentLastDiff = {};
|
||||
}
|
||||
|
||||
async init() {
|
||||
@@ -353,13 +354,15 @@ class ServerStorage extends Vue {
|
||||
|
||||
const oldRev = bookManager.recentRev;
|
||||
const oldLastRev = bookManager.recentLastRev;
|
||||
const oldLastDiffRev = bookManager.recentLastDiffRev;
|
||||
//проверим ревизию на сервере
|
||||
let revs = null;
|
||||
if (!force) {
|
||||
try {
|
||||
revs = await this.storageCheck({recent: {}, recentLast: {}});
|
||||
revs = await this.storageCheck({recent: {}, recentLast: {}, recentLastDiff: {}});
|
||||
if (revs.state == 'success' && revs.items.recent.rev == oldRev &&
|
||||
revs.items.recentLast.rev == oldLastRev) {
|
||||
revs.items.recentLast.rev == oldLastRev &&
|
||||
revs.items.recentLastDiff.rev == oldLastDiffRev) {
|
||||
return;
|
||||
}
|
||||
} catch(e) {
|
||||
@@ -391,24 +394,32 @@ class ServerStorage extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
if (force || revs.items.recentLast.rev != oldLastRev) {
|
||||
if (force || revs.items.recentLast.rev != oldLastRev || revs.items.recentLastDiff.rev != oldLastDiffRev) {
|
||||
let recentLast = null;
|
||||
try {
|
||||
recentLast = await this.storageGet({recentLast: {}});
|
||||
recentLast = await this.storageGet({recentLast: {}, recentLastDiff: {}});
|
||||
} catch(e) {
|
||||
this.error(`Ошибка соединения с сервером: ${e.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (recentLast.state == 'success') {
|
||||
const recentLastDiff = recentLast.items.recentLastDiff;
|
||||
recentLast = recentLast.items.recentLast;
|
||||
|
||||
if (recentLast.rev == 0)
|
||||
recentLast.data = {};
|
||||
if (recentLastDiff.rev == 0)
|
||||
recentLastDiff.data = {};
|
||||
|
||||
this.oldRecentLastDiff = _.cloneDeep(recentLastDiff.data);
|
||||
this.oldRecentLast = _.cloneDeep(recentLast.data);
|
||||
|
||||
recentLast.data = utils.applyObjDiff(recentLast.data, recentLastDiff.data);
|
||||
|
||||
await bookManager.setRecentLast(recentLast.data);
|
||||
await bookManager.setRecentLastRev(recentLast.rev);
|
||||
await bookManager.setRecentLastDiffRev(recentLastDiff.rev);
|
||||
} else {
|
||||
this.warning(`Неверный ответ сервера: ${recentLast.state}`);
|
||||
}
|
||||
@@ -476,6 +487,11 @@ class ServerStorage extends Vue {
|
||||
if (utils.isEmptyObjDiff(diff))
|
||||
return;
|
||||
|
||||
if (this.oldRecentLast.key == recentLast.key && JSON.stringify(recentLast) > JSON.stringify(diff)) {
|
||||
await this.saveRecentLastDiff(diff, force);
|
||||
return;
|
||||
}
|
||||
|
||||
this.savingRecentLast = true;
|
||||
try {
|
||||
let result = {state: ''};
|
||||
@@ -515,12 +531,69 @@ class ServerStorage extends Vue {
|
||||
} else {
|
||||
this.oldRecentLast = _.cloneDeep(recentLast);
|
||||
await bm.setRecentLastRev(lastRev + 1);
|
||||
await this.saveRecentLastDiff({}, true);
|
||||
}
|
||||
} finally {
|
||||
this.savingRecentLast = false;
|
||||
}
|
||||
}
|
||||
|
||||
async saveRecentLastDiff(diff, force = false) {
|
||||
if (!this.keyInited || !this.serverSyncEnabled || this.savingRecentLastDiff)
|
||||
return;
|
||||
|
||||
const bm = bookManager;
|
||||
let lastRev = bm.recentLastDiffRev;
|
||||
|
||||
const d = utils.getObjDiff(this.oldRecentLastDiff, diff);
|
||||
if (utils.isEmptyObjDiff(d))
|
||||
return;
|
||||
|
||||
this.savingRecentLastDiff = true;
|
||||
try {
|
||||
let result = {state: ''};
|
||||
let tries = 0;
|
||||
while (result.state != 'success' && tries < maxSetTries) {
|
||||
if (force) {
|
||||
try {
|
||||
const revs = await this.storageCheck({recentLastDiff: {}});
|
||||
if (revs.items.recentLastDiff.rev)
|
||||
lastRev = revs.items.recentLastDiff.rev;
|
||||
} catch(e) {
|
||||
this.error(`Ошибка соединения с сервером: ${e.message}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
result = await this.storageSet({recentLastDiff: {rev: lastRev + 1, data: diff}}, force);
|
||||
} catch(e) {
|
||||
this.savingRecentLastDiff = false;
|
||||
this.error(`Ошибка соединения с сервером: (${e.message}). Изменения не сохранены.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.state == 'reject') {
|
||||
await this.loadRecent(false);
|
||||
this.savingRecentLastDiff = false;
|
||||
return;
|
||||
}
|
||||
|
||||
tries++;
|
||||
}
|
||||
|
||||
if (tries >= maxSetTries) {
|
||||
console.error(result);
|
||||
this.error('Не удалось отправить данные на сервер. Данные не сохранены и могут быть перезаписаны.');
|
||||
} else {
|
||||
this.oldRecentLastDiff = _.cloneDeep(diff);
|
||||
await bm.setRecentLastDiffRev(lastRev + 1);
|
||||
}
|
||||
} finally {
|
||||
this.savingRecentLastDiff = false;
|
||||
}
|
||||
}
|
||||
|
||||
async storageCheck(items) {
|
||||
return await this.storageApi('check', items);
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@
|
||||
</el-form>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- Вид ------------------------------------------------------------------------->
|
||||
<el-tab-pane label="Вид">
|
||||
|
||||
@@ -345,6 +346,28 @@
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- Кнопки ------------------------------------------------------------------------->
|
||||
<el-tab-pane label="Кнопки">
|
||||
<el-form :model="form" size="mini" label-width="120px" @submit.native.prevent>
|
||||
<div class="partHeader">Показывать кнопки панели</div>
|
||||
|
||||
<el-form-item label="" v-for="item in toolButtons" :key="item.name">
|
||||
<el-checkbox @change="changeShowToolButton(item.name)" :value="showToolButton[item.name]">{{item.text}}</el-checkbox>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- Управление ------------------------------------------------------------------------->
|
||||
<el-tab-pane label="Управление">
|
||||
<el-form :model="form" size="mini" label-width="120px" @submit.native.prevent>
|
||||
<div class="partHeader">Управление</div>
|
||||
|
||||
<el-form-item label="">
|
||||
<el-checkbox v-model="clickControl">Включить управление кликом</el-checkbox>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- Листание ------------------------------------------------------------------------->
|
||||
<el-tab-pane label="Листание">
|
||||
<el-form :model="form" size="mini" label-width="120px" @submit.native.prevent>
|
||||
@@ -358,6 +381,8 @@
|
||||
<el-option label="Вправо-влево" value="rightShift"></el-option>
|
||||
<el-option label="Протаивание" value="thaw"></el-option>
|
||||
<el-option label="Мерцание" value="blink"></el-option>
|
||||
<el-option label="Вращение" value="rotate"></el-option>
|
||||
<el-option v-show="wallpaper == ''" label="Листание" value="flip"></el-option>
|
||||
</el-select>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
@@ -380,14 +405,12 @@
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- Прочее ------------------------------------------------------------------------->
|
||||
<el-tab-pane label="Прочее">
|
||||
<el-form :model="form" size="mini" label-width="120px" @submit.native.prevent>
|
||||
<el-form-item label="Управление">
|
||||
<el-checkbox v-model="clickControl">Включить управление кликом</el-checkbox>
|
||||
</el-form-item>
|
||||
<div class="partHeader">Подсказки, уведомления</div>
|
||||
|
||||
<el-form-item label="Подсказка">
|
||||
<el-tooltip :open-delay="500" effect="light">
|
||||
@@ -406,7 +429,7 @@
|
||||
<el-checkbox v-model="blinkCachedLoad">Предупреждать о загрузке из кэша</el-checkbox>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
<el-form-item label="Уведомления">
|
||||
<el-form-item label="Уведомление">
|
||||
<el-tooltip :open-delay="500" effect="light">
|
||||
<template slot="content">
|
||||
Показывать уведомления и ошибки от<br>
|
||||
@@ -415,8 +438,21 @@
|
||||
<el-checkbox v-model="showServerStorageMessages">Показывать сообщения синхронизации</el-checkbox>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
<el-form-item label="Уведомление">
|
||||
<el-tooltip :open-delay="500" effect="light">
|
||||
<template slot="content">
|
||||
Показывать уведомления "Что нового"<br>
|
||||
при каждом выходе новой версии читалки
|
||||
</template>
|
||||
<el-checkbox v-model="showWhatsNewDialog">Показывать уведомление "Что нового"</el-checkbox>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-form-item label="URL">
|
||||
<el-form :model="form" size="mini" label-width="120px" @submit.native.prevent>
|
||||
<div class="partHeader">Прочее</div>
|
||||
|
||||
<el-form-item label="Парам. в URL">
|
||||
<el-tooltip :open-delay="500" effect="light">
|
||||
<template slot="content">
|
||||
Добавление параметра "__p" в строке браузера<br>
|
||||
@@ -450,6 +486,7 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- Сброс ------------------------------------------------------------------------->
|
||||
<el-tab-pane label="Сброс">
|
||||
<el-button @click="setDefaults">Установить по умолчанию</el-button>
|
||||
@@ -504,6 +541,10 @@ export default @Component({
|
||||
const font = (newValue ? newValue : this.fontName);
|
||||
this.vertShift = this.fontShifts[font] || 0;
|
||||
},
|
||||
wallpaper: function(newValue) {
|
||||
if (newValue != '' && this.pageChangeAnimation == 'flip')
|
||||
this.pageChangeAnimation = '';
|
||||
},
|
||||
},
|
||||
})
|
||||
class SettingsPage extends Vue {
|
||||
@@ -517,12 +558,14 @@ class SettingsPage extends Vue {
|
||||
fonts = [];
|
||||
|
||||
serverStorageKeyVisible = false;
|
||||
toolButtons = [];
|
||||
|
||||
created() {
|
||||
this.commit = this.$store.commit;
|
||||
this.reader = this.$store.state.reader;
|
||||
|
||||
this.form = {};
|
||||
this.toolButtons = rstore.toolButtons;
|
||||
this.settingsChanged();
|
||||
}
|
||||
|
||||
@@ -536,6 +579,7 @@ class SettingsPage extends Vue {
|
||||
this.form = Object.assign({}, this.form, {[prop]: newValue});
|
||||
});
|
||||
}
|
||||
|
||||
this.fontBold = (this.fontWeight == 'bold');
|
||||
this.fontItalic = (this.fontStyle == 'italic');
|
||||
|
||||
@@ -642,6 +686,10 @@ class SettingsPage extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
changeShowToolButton(buttonName) {
|
||||
this.showToolButton = Object.assign({}, this.showToolButton, {[buttonName]: !this.showToolButton[buttonName]});
|
||||
}
|
||||
|
||||
async addProfile() {
|
||||
try {
|
||||
if (Object.keys(this.profiles).length >= 100) {
|
||||
|
||||
@@ -317,4 +317,58 @@ export default class DrawHelper {
|
||||
await animation1Finish(duration);
|
||||
}
|
||||
}
|
||||
|
||||
async doPageAnimationRotate(page1, page2, duration, isDown, animation1Finish, animation2Finish) {
|
||||
if (isDown) {
|
||||
page1.style.transform = `rotateY(90deg)`;
|
||||
await sleep(30);
|
||||
|
||||
page2.style.transition = `${duration/2}ms ease-in`;
|
||||
page2.style.transform = `rotateY(-90deg)`;
|
||||
|
||||
await animation2Finish(duration/2);
|
||||
|
||||
page1.style.transition = `${duration/2}ms ease-out`;
|
||||
page1.style.transform = `rotateY(0deg)`;
|
||||
await animation1Finish(duration/2);
|
||||
} else {
|
||||
page1.style.transform = `rotateY(-90deg)`;
|
||||
await sleep(30);
|
||||
|
||||
page2.style.transition = `${duration/2}ms ease-in`;
|
||||
page2.style.transform = `rotateY(90deg)`;
|
||||
|
||||
await animation2Finish(duration/2);
|
||||
|
||||
page1.style.transition = `${duration/2}ms ease-out`;
|
||||
page1.style.transform = `rotateY(0deg)`;
|
||||
await animation1Finish(duration/2);
|
||||
}
|
||||
}
|
||||
|
||||
async doPageAnimationFlip(page1, page2, duration, isDown, animation1Finish, animation2Finish, backgroundColor) {
|
||||
page2.style.background = backgroundColor;
|
||||
|
||||
if (isDown) {
|
||||
page2.style.transformOrigin = '10%';
|
||||
await sleep(30);
|
||||
|
||||
page2.style.transformOrigin = '0%';
|
||||
page2.style.transition = `${duration}ms ease-in-out`;
|
||||
page2.style.transform = `rotateY(-120deg)`;
|
||||
await animation2Finish(duration);
|
||||
} else {
|
||||
page2.style.transformOrigin = '90%';
|
||||
await sleep(30);
|
||||
|
||||
page2.style.transformOrigin = '100%';
|
||||
page2.style.transition = `${duration}ms ease-in-out`;
|
||||
page2.style.transform = `rotateY(120deg)`;
|
||||
await animation2Finish(duration);
|
||||
}
|
||||
|
||||
page2.style.transformOrigin = 'center';
|
||||
page2.style.background = '';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,7 +23,6 @@
|
||||
oncontextmenu="return false;">
|
||||
<div v-show="showStatusBar" v-html="statusBarClickable" @mousedown.prevent.stop @touchstart.stop
|
||||
@click.prevent.stop="onStatusBarClick"></div>
|
||||
<div v-show="fontsLoading" ref="fontsLoading"></div>
|
||||
</div>
|
||||
<div v-show="!clickControl && showStatusBar" class="layout" v-html="statusBarClickable" @mousedown.prevent.stop @touchstart.stop
|
||||
@click.prevent.stop="onStatusBarClick"></div>
|
||||
@@ -77,7 +76,6 @@ class TextPage extends Vue {
|
||||
page2 = null;
|
||||
statusBar = null;
|
||||
statusBarClickable = null;
|
||||
fontsLoading = null;
|
||||
|
||||
lastBook = null;
|
||||
bookPos = 0;
|
||||
@@ -171,6 +169,12 @@ class TextPage extends Vue {
|
||||
this.fontShift = this.fontVertShift/100;
|
||||
this.textShift = this.textVertShift/100 + this.fontShift;
|
||||
|
||||
//statusBar
|
||||
this.$refs.statusBar.style.left = '0px';
|
||||
this.$refs.statusBar.style.top = (this.statusBarTop ? 1 : this.realHeight - this.statusBarHeight) + 'px';
|
||||
|
||||
this.statusBarColor = this.hex2rgba(this.textColor || '#000000', this.statusBarColorAlpha);
|
||||
|
||||
//drawHelper
|
||||
this.drawHelper.realWidth = this.realWidth;
|
||||
this.drawHelper.realHeight = this.realHeight;
|
||||
@@ -196,14 +200,8 @@ class TextPage extends Vue {
|
||||
this.drawHelper.lineHeight = this.lineHeight;
|
||||
this.drawHelper.context = this.context;
|
||||
|
||||
//сообщение "Загрузка шрифтов..."
|
||||
const flText = 'Загрузка шрифта...';
|
||||
this.$refs.fontsLoading.innerHTML = flText;
|
||||
const fontsLoadingStyle = this.$refs.fontsLoading.style;
|
||||
fontsLoadingStyle.position = 'absolute';
|
||||
fontsLoadingStyle.fontSize = this.fontSize + 'px';
|
||||
fontsLoadingStyle.top = (this.realHeight/2 - 2*this.fontSize) + 'px';
|
||||
fontsLoadingStyle.left = (this.realWidth - this.drawHelper.measureText(flText, {}))/2 + 'px';
|
||||
//statusBar
|
||||
this.statusBarClickable = this.drawHelper.statusBarClickable(this.statusBarTop, this.statusBarHeight);
|
||||
|
||||
//parsed
|
||||
if (this.parsed) {
|
||||
@@ -225,13 +223,6 @@ class TextPage extends Vue {
|
||||
this.parsed.imageFitWidth = this.imageFitWidth;
|
||||
}
|
||||
|
||||
//statusBar
|
||||
this.$refs.statusBar.style.left = '0px';
|
||||
this.$refs.statusBar.style.top = (this.statusBarTop ? 1 : this.realHeight - this.statusBarHeight) + 'px';
|
||||
|
||||
this.statusBarColor = this.hex2rgba(this.textColor || '#000000', this.statusBarColorAlpha);
|
||||
this.statusBarClickable = this.drawHelper.statusBarClickable(this.statusBarTop, this.statusBarHeight);
|
||||
|
||||
//scrolling page
|
||||
const pageSpace = this.scrollHeight - this.pageLineCount*this.lineHeight;
|
||||
let y = pageSpace/2;
|
||||
@@ -239,6 +230,10 @@ class TextPage extends Vue {
|
||||
y += this.statusBarHeight*(this.statusBarTop ? 1 : 0);
|
||||
let page1 = this.$refs.scrollBox1;
|
||||
let page2 = this.$refs.scrollBox2;
|
||||
|
||||
page1.style.perspective = '3072px';
|
||||
page2.style.perspective = '3072px';
|
||||
|
||||
page1.style.width = this.w + this.indentLR + 'px';
|
||||
page2.style.width = this.w + this.indentLR + 'px';
|
||||
page1.style.height = this.scrollHeight - (pageSpace > 0 ? pageSpace : 0) + 'px';
|
||||
@@ -268,6 +263,18 @@ class TextPage extends Vue {
|
||||
async loadFonts() {
|
||||
this.fontsLoading = true;
|
||||
|
||||
let inst = null;
|
||||
(async() => {
|
||||
await sleep(500);
|
||||
if (this.fontsLoading)
|
||||
inst = this.$notify({
|
||||
title: '',
|
||||
dangerouslyUseHTMLString: true,
|
||||
message: 'Загрузка шрифта <i class="el-icon-loading"></i>',
|
||||
duration: 0
|
||||
});
|
||||
})();
|
||||
|
||||
if (!this.fontsLoaded)
|
||||
this.fontsLoaded = {};
|
||||
//загрузка дин.шрифта
|
||||
@@ -298,6 +305,8 @@ class TextPage extends Vue {
|
||||
}
|
||||
|
||||
this.fontsLoading = false;
|
||||
if (inst)
|
||||
inst.close();
|
||||
}
|
||||
|
||||
getSettings() {
|
||||
@@ -625,7 +634,7 @@ class TextPage extends Vue {
|
||||
const animation1Finish = this.generateWaitingFunc('resolveAnimation1Finish', 'stopAnimation');
|
||||
const animation2Finish = this.generateWaitingFunc('resolveAnimation2Finish', 'stopAnimation');
|
||||
const transition1Finish = this.generateWaitingFunc('resolveTransition1Finish', 'stopAnimation');
|
||||
//const transition2Finish = this.generateWaitingFunc('resolveTransition2Finish', 'stopAnimation');
|
||||
const transition2Finish = this.generateWaitingFunc('resolveTransition2Finish', 'stopAnimation');
|
||||
|
||||
const duration = Math.round(3000*(1 - this.pageChangeAnimationSpeed/100));
|
||||
let page1 = this.$refs.scrollingPage1;
|
||||
@@ -654,6 +663,14 @@ class TextPage extends Vue {
|
||||
page1.style.height = this.scrollHeight + this.lineHeight + 'px';
|
||||
page2.style.height = this.scrollHeight + this.lineHeight + 'px';
|
||||
break;
|
||||
case 'rotate':
|
||||
await this.drawHelper.doPageAnimationRotate(page1, page2,
|
||||
duration, this.pageChangeDirectionDown, transition1Finish, transition2Finish);
|
||||
break;
|
||||
case 'flip':
|
||||
await this.drawHelper.doPageAnimationFlip(page1, page2,
|
||||
duration, this.pageChangeDirectionDown, transition1Finish, transition2Finish, this.backgroundColor);
|
||||
break;
|
||||
}
|
||||
|
||||
this.resolveAnimation1Finish = null;
|
||||
@@ -1120,6 +1137,10 @@ class TextPage extends Vue {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.on-top {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.back {
|
||||
z-index: 5;
|
||||
}
|
||||
@@ -1185,4 +1206,5 @@ class TextPage extends Vue {
|
||||
0% { opacity: 1; }
|
||||
100% { opacity: 0; }
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -38,6 +38,7 @@ class BookManager {
|
||||
this.recent[this.recentLast.key] = this.recentLast;
|
||||
this.recentRev = await bmRecentStore.getItem('recent-rev') || 0;
|
||||
this.recentLastRev = await bmRecentStore.getItem('recent-last-rev') || 0;
|
||||
this.recentLastDiffRev = await bmRecentStore.getItem('recent-last-diff-rev') || 0;
|
||||
this.books = Object.assign({}, this.booksCached);
|
||||
|
||||
this.recentChanged2 = true;
|
||||
@@ -142,6 +143,41 @@ class BookManager {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async deflateWithProgress(data, callback) {
|
||||
const chunkSize = 128*1024;
|
||||
const deflator = new utils.pako.Deflate({level: 5});
|
||||
|
||||
let chunkTotal = 1 + Math.floor(data.length/chunkSize);
|
||||
let chunkNum = 0;
|
||||
let perc = 0;
|
||||
let prevPerc = 0;
|
||||
|
||||
for (var i = 0; i < data.length; i += chunkSize) {
|
||||
if ((i + chunkSize) >= data.length) {
|
||||
deflator.push(data.substring(i, i + chunkSize), true);
|
||||
} else {
|
||||
deflator.push(data.substring(i, i + chunkSize), false);
|
||||
}
|
||||
chunkNum++;
|
||||
|
||||
perc = Math.round(chunkNum/chunkTotal*100);
|
||||
if (perc != prevPerc) {
|
||||
callback(perc);
|
||||
await utils.sleep(1);
|
||||
prevPerc = perc;
|
||||
}
|
||||
}
|
||||
|
||||
if (deflator.err) {
|
||||
throw new Error(deflator.msg);
|
||||
}
|
||||
|
||||
callback(100);
|
||||
|
||||
return deflator.result;
|
||||
}
|
||||
|
||||
async addBook(newBook, callback) {
|
||||
if (!this.books)
|
||||
await this.init();
|
||||
@@ -150,7 +186,12 @@ class BookManager {
|
||||
meta.addTime = Date.now();
|
||||
|
||||
const cb = (perc) => {
|
||||
const p = Math.round(80*perc/100);
|
||||
const p = Math.round(30*perc/100);
|
||||
callback(p);
|
||||
};
|
||||
|
||||
const cb2 = (perc) => {
|
||||
const p = Math.round(30 + 65*perc/100);
|
||||
callback(p);
|
||||
};
|
||||
|
||||
@@ -159,10 +200,11 @@ class BookManager {
|
||||
|
||||
let data = newBook.data;
|
||||
if (result.dataCompressed) {
|
||||
data = utils.pako.deflate(data, {level: 9});
|
||||
//data = utils.pako.deflate(data, {level: 9});
|
||||
data = await this.deflateWithProgress(data, cb2);
|
||||
result.dataCompressedLength = data.byteLength;
|
||||
}
|
||||
callback(90);
|
||||
callback(95);
|
||||
|
||||
this.books[meta.key] = result;
|
||||
this.booksCached[meta.key] = this.metaOnly(result);
|
||||
@@ -201,7 +243,12 @@ class BookManager {
|
||||
await utils.sleep(10);
|
||||
|
||||
if (result.dataCompressed) {
|
||||
data = utils.pako.inflate(data, {to: 'string'});
|
||||
try {
|
||||
data = utils.pako.inflate(data, {to: 'string'});
|
||||
} catch (e) {
|
||||
this.delBook(meta);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
callback(20);
|
||||
|
||||
@@ -428,10 +475,15 @@ class BookManager {
|
||||
}
|
||||
|
||||
async setRecentLastRev(value) {
|
||||
bmRecentStore.setItem('recent-last-rev', value);
|
||||
await bmRecentStore.setItem('recent-last-rev', value);
|
||||
this.recentLastRev = value;
|
||||
}
|
||||
|
||||
async setRecentLastDiffRev(value) {
|
||||
await bmRecentStore.setItem('recent-last-diff-rev', value);
|
||||
this.recentLastDiffRev = value;
|
||||
}
|
||||
|
||||
addEventListener(listener) {
|
||||
if (this.eventListeners.indexOf(listener) < 0)
|
||||
this.eventListeners.push(listener);
|
||||
|
||||
113
client/components/Reader/versionHistory.js
Normal file
113
client/components/Reader/versionHistory.js
Normal file
@@ -0,0 +1,113 @@
|
||||
export const versionHistory = [
|
||||
{
|
||||
showUntil: '2019-06-22',
|
||||
header: '0.6.9 (2019-06-23)',
|
||||
content:
|
||||
`
|
||||
<ul>
|
||||
<li>исправлен баг - падение сервера при распаковке битых архивов книг</li>
|
||||
<li>исправлен баг - не распознавались некоторые книги формата fb2 в кодировке utf8</li>
|
||||
<li>добавлены новые варианты анимации перелистывания</li>
|
||||
<li>на страницу загрузки добавлен блок "Поделиться"</li>
|
||||
<li>улучшены прогрессбары</li>
|
||||
<li>исправления недочетов, небольшие оптимизации</li>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
|
||||
{
|
||||
showUntil: '2019-06-05',
|
||||
header: '0.6.7 (2019-05-30)',
|
||||
content:
|
||||
`
|
||||
<ul>
|
||||
<li>добавлен диалог "Что нового"</li>
|
||||
<li>в справку добавлена история версий проекта</li>
|
||||
<li>добавлена возможность настройки отображаемых кнопок на панели управления</li>
|
||||
<li>некоторые кнопки на панели управления были скрыты по умолчанию</li>
|
||||
<li>на страницу загрузки добавлена возможность загрузки книги из буфера обмена</li>
|
||||
<li>добавлен GET-параметр вида "/reader?__refresh=1&url=..." для принудительного обновления загружаемого текста</li>
|
||||
<li>добавлен GET-параметр вида "/reader?__pp=50.5&url=..." для указания позиции в книге в процентах</li>
|
||||
<li>исправления багов и недочетов</li>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
|
||||
{
|
||||
showUntil: '2019-03-29',
|
||||
header: '0.6.6 (2019-03-29)',
|
||||
content:
|
||||
`
|
||||
<ul>
|
||||
<li>в справку добавлено описание настройки браузеров для автономной работы читалки (без доступа к интернету)</li>
|
||||
<li>оптимизации процесса синхронизации, внутренние переделки</li>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
|
||||
{
|
||||
showUntil: '2019-03-24',
|
||||
header: '0.6.4 (2019-03-24)',
|
||||
content:
|
||||
`
|
||||
<ul>
|
||||
<li>исправления багов, оптимизации</li>
|
||||
<li>добавлена возможность синхронизации данных между устройствами</li>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
|
||||
{
|
||||
showUntil: '2019-03-04',
|
||||
header: '0.5.4 (2019-03-04)',
|
||||
content:
|
||||
`
|
||||
<ul>
|
||||
<li>добавлена поддержка форматов pdf, epub, mobi</li>
|
||||
<li>(0.5.2) добавлена поддержка форматов rtf, doc, docx</li>
|
||||
<li>(0.4.2) фильтр для СИ больше не вырезает изображения</li>
|
||||
<li>(0.4.0) добавлено отображение картинок в fb2</li>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
|
||||
{
|
||||
showUntil: '2019-02-17',
|
||||
header: '0.3.0 (2019-02-17)',
|
||||
content:
|
||||
`
|
||||
<ul>
|
||||
<li>поправки багов</li>
|
||||
<li>улучшено распознавание текста</li>
|
||||
<li>изменена верстка страницы - убрано позиционирование каждого слова</li>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
|
||||
{
|
||||
showUntil: '2019-02-14',
|
||||
header: '0.1.7 (2019-02-14)',
|
||||
content:
|
||||
`
|
||||
<ul>
|
||||
<li>увеличены верхние границы отступов и др.размеров</li>
|
||||
<li>добавлена настройка для удаления/вставки пустых параграфов</li>
|
||||
<li>добавлена настройка включения/отключения управления кликом</li>
|
||||
<li>добавлена возможность сброса настроек</li>
|
||||
<li>убран автоматический редирект на последнюю загруженную книгу, если не задан url в маршруте</li>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
|
||||
{
|
||||
showUntil: '2019-02-12',
|
||||
header: '0.1.0 (2019-02-12)',
|
||||
content:
|
||||
`
|
||||
<ul>
|
||||
<li>первый деплой проекта, длительность разработки - 2 месяца</li>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
|
||||
]
|
||||
@@ -86,8 +86,8 @@ import './theme/form-item.css';
|
||||
import ElColorPicker from 'element-ui/lib/color-picker';
|
||||
import './theme/color-picker.css';
|
||||
|
||||
//import ElDialog from 'element-ui/lib/dialog';
|
||||
//import './theme/dialog.css';
|
||||
import ElDialog from 'element-ui/lib/dialog';
|
||||
import './theme/dialog.css';
|
||||
|
||||
import Notification from 'element-ui/lib/notification';
|
||||
import './theme/notification.css';
|
||||
@@ -106,7 +106,7 @@ const components = {
|
||||
ElCol, ElContainer, ElAside, ElMain, ElHeader,
|
||||
ElInput, ElInputNumber, ElSelect, ElOption, ElTable, ElTableColumn,
|
||||
ElProgress, ElSlider, ElForm, ElFormItem,
|
||||
ElColorPicker,
|
||||
ElColorPicker, ElDialog,
|
||||
};
|
||||
|
||||
for (let name in components) {
|
||||
|
||||
@@ -9,5 +9,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="https://yastatic.net/share2/share.js" async="async"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -39,6 +39,10 @@ export function formatDate(d, format) {
|
||||
case 'normal':
|
||||
return `${d.getDate().toString().padStart(2, '0')}.${(d.getMonth() + 1).toString().padStart(2, '0')}.${d.getFullYear()} ` +
|
||||
`${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}`;
|
||||
case 'coDate':
|
||||
return `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, '0')}-${d.getDate().toString().padStart(2, '0')}`;
|
||||
case 'noDate':
|
||||
return `${d.getDate().toString().padStart(2, '0')}.${(d.getMonth() + 1).toString().padStart(2, '0')}.${d.getFullYear()}`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
//занчение toolButtons.name не должно совпадать с settingDefaults-propertyName
|
||||
const toolButtons = [
|
||||
{name: 'undoAction', show: true, text: 'Действие назад'},
|
||||
{name: 'redoAction', show: true, text: 'Действие вперед'},
|
||||
{name: 'fullScreen', show: true, text: 'На весь экран'},
|
||||
{name: 'scrolling', show: false, text: 'Плавный скроллинг'},
|
||||
{name: 'setPosition', show: true, text: 'На страницу'},
|
||||
{name: 'search', show: true, text: 'Найти в тексте'},
|
||||
{name: 'copyText', show: false, text: 'Скопировать текст со страницы'},
|
||||
{name: 'refresh', show: true, text: 'Принудительно обновить книгу'},
|
||||
{name: 'history', show: true, text: 'Открыть недавние'},
|
||||
];
|
||||
|
||||
const fonts = [
|
||||
{name: 'ReaderDefault', label: 'По-умолчанию', fontVertShift: 0},
|
||||
{name: 'GEO_1', label: 'BPG Arial', fontVertShift: 10},
|
||||
@@ -140,7 +153,7 @@ const settingDefaults = {
|
||||
indentLR: 15,// px, отступ всего текста слева и справа
|
||||
indentTB: 0,// px, отступ всего текста сверху и снизу
|
||||
wordWrap: true,//перенос по слогам
|
||||
keepLastToFirst: true,// перенос последней строки в первую при листании
|
||||
keepLastToFirst: false,// перенос последней строки в первую при листании
|
||||
|
||||
showStatusBar: true,
|
||||
statusBarTop: false,// top, bottom
|
||||
@@ -150,7 +163,7 @@ const settingDefaults = {
|
||||
scrollingDelay: 3000,// замедление, ms
|
||||
scrollingType: 'ease-in-out', //linear, ease, ease-in, ease-out, ease-in-out
|
||||
|
||||
pageChangeAnimation: 'blink',// '' - нет, downShift, rightShift, thaw - протаивание, blink - мерцание
|
||||
pageChangeAnimation: 'flip',// '' - нет, downShift, rightShift, thaw - протаивание, blink - мерцание, rotate - вращение, flip - листание
|
||||
pageChangeAnimationSpeed: 80, //0-100%
|
||||
|
||||
allowUrlParamBookPos: false,
|
||||
@@ -166,14 +179,18 @@ const settingDefaults = {
|
||||
imageHeightLines: 100,
|
||||
imageFitWidth: true,
|
||||
showServerStorageMessages: true,
|
||||
showWhatsNewDialog: true,
|
||||
|
||||
fontShifts: {},
|
||||
showToolButton: {},
|
||||
};
|
||||
|
||||
for (const font of fonts)
|
||||
settingDefaults.fontShifts[font.name] = font.fontVertShift;
|
||||
for (const font of webFonts)
|
||||
settingDefaults.fontShifts[font.name] = font.fontVertShift;
|
||||
for (const button of toolButtons)
|
||||
settingDefaults.showToolButton[button.name] = button.show;
|
||||
|
||||
// initial state
|
||||
const state = {
|
||||
@@ -183,6 +200,7 @@ const state = {
|
||||
profiles: {},
|
||||
profilesRev: 0,
|
||||
allowProfilesSave: false,//подстраховка для разработки
|
||||
whatsNewContentHash: '',
|
||||
currentProfile: '',
|
||||
settings: Object.assign({}, settingDefaults),
|
||||
settingsRev: {},
|
||||
@@ -214,6 +232,9 @@ const mutations = {
|
||||
setAllowProfilesSave(state, value) {
|
||||
state.allowProfilesSave = value;
|
||||
},
|
||||
setWhatsNewContentHash(state, value) {
|
||||
state.whatsNewContentHash = value;
|
||||
},
|
||||
setCurrentProfile(state, value) {
|
||||
state.currentProfile = value;
|
||||
},
|
||||
@@ -226,6 +247,7 @@ const mutations = {
|
||||
};
|
||||
|
||||
export default {
|
||||
toolButtons,
|
||||
fonts,
|
||||
webFonts,
|
||||
settingDefaults,
|
||||
|
||||
183
package-lock.json
generated
183
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Liberama",
|
||||
"version": "0.5.6",
|
||||
"version": "0.6.7",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -1623,6 +1623,15 @@
|
||||
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
|
||||
"dev": true
|
||||
},
|
||||
"binary": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz",
|
||||
"integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=",
|
||||
"requires": {
|
||||
"buffers": "~0.1.1",
|
||||
"chainsaw": "~0.1.0"
|
||||
}
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "1.12.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.12.0.tgz",
|
||||
@@ -1847,6 +1856,11 @@
|
||||
"integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
|
||||
"dev": true
|
||||
},
|
||||
"buffers": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz",
|
||||
"integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s="
|
||||
},
|
||||
"builtin-status-codes": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
|
||||
@@ -2057,6 +2071,14 @@
|
||||
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
|
||||
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
|
||||
},
|
||||
"chainsaw": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz",
|
||||
"integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=",
|
||||
"requires": {
|
||||
"traverse": ">=0.3.0 <0.4"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||
@@ -3074,6 +3096,51 @@
|
||||
"mimic-response": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"decompress-zip": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/decompress-zip/-/decompress-zip-0.2.2.tgz",
|
||||
"integrity": "sha512-v+Na3Ck86Px7s2ix+f77pMQC3GlkxHHN+YyvnkEW7+xX5F39pcDpIV/VFvGYk8MznTFcMoPjL3XNWEJLXWoSPw==",
|
||||
"requires": {
|
||||
"binary": "^0.3.0",
|
||||
"graceful-fs": "^4.1.3",
|
||||
"mkpath": "^0.1.0",
|
||||
"nopt": "^3.0.1",
|
||||
"q": "^1.1.2",
|
||||
"readable-stream": "^1.1.8",
|
||||
"touch": "0.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
|
||||
},
|
||||
"nopt": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
|
||||
"integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
|
||||
"requires": {
|
||||
"abbrev": "1"
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
|
||||
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.1",
|
||||
"isarray": "0.0.1",
|
||||
"string_decoder": "~0.10.x"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
|
||||
}
|
||||
}
|
||||
},
|
||||
"deep-extend": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||
@@ -4062,43 +4129,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-zip": {
|
||||
"version": "1.6.7",
|
||||
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz",
|
||||
"integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=",
|
||||
"requires": {
|
||||
"concat-stream": "1.6.2",
|
||||
"debug": "2.6.9",
|
||||
"mkdirp": "0.5.1",
|
||||
"yauzl": "2.4.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"fd-slicer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz",
|
||||
"integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=",
|
||||
"requires": {
|
||||
"pend": "~1.2.0"
|
||||
}
|
||||
},
|
||||
"yauzl": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz",
|
||||
"integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=",
|
||||
"requires": {
|
||||
"fd-slicer": "~1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"extsprintf": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
|
||||
@@ -4410,8 +4440,7 @@
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
@@ -4432,14 +4461,12 @@
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -4454,20 +4481,17 @@
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
@@ -4584,8 +4608,7 @@
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
@@ -4597,7 +4620,6 @@
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
@@ -4612,7 +4634,6 @@
|
||||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
@@ -4620,14 +4641,12 @@
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.2.4",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.1",
|
||||
"yallist": "^3.0.0"
|
||||
@@ -4646,7 +4665,6 @@
|
||||
"version": "0.5.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
@@ -4727,8 +4745,7 @@
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
@@ -4740,7 +4757,6 @@
|
||||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
@@ -4826,8 +4842,7 @@
|
||||
"safe-buffer": {
|
||||
"version": "5.1.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
@@ -4863,7 +4878,6 @@
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
@@ -4883,7 +4897,6 @@
|
||||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
@@ -4927,14 +4940,12 @@
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -6512,6 +6523,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"mkpath": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mkpath/-/mkpath-0.1.0.tgz",
|
||||
"integrity": "sha1-dVSm+Nhxg0zJe1RisSLEwSTW3pE="
|
||||
},
|
||||
"move-concurrently": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
|
||||
@@ -7300,11 +7316,6 @@
|
||||
"sha.js": "^2.4.8"
|
||||
}
|
||||
},
|
||||
"pend": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
||||
"integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA="
|
||||
},
|
||||
"performance-now": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
@@ -9436,8 +9447,7 @@
|
||||
"q": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
|
||||
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=",
|
||||
"dev": true
|
||||
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc="
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.5.2",
|
||||
@@ -11010,6 +11020,24 @@
|
||||
"integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=",
|
||||
"dev": true
|
||||
},
|
||||
"touch": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/touch/-/touch-0.0.3.tgz",
|
||||
"integrity": "sha1-Ua7z1ElXHU8oel2Hyci0kYGg2x0=",
|
||||
"requires": {
|
||||
"nopt": "~1.0.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"nopt": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
|
||||
"integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=",
|
||||
"requires": {
|
||||
"abbrev": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
|
||||
@@ -11026,6 +11054,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"traverse": {
|
||||
"version": "0.3.9",
|
||||
"resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz",
|
||||
"integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk="
|
||||
},
|
||||
"trim-right": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Liberama",
|
||||
"version": "0.6.5",
|
||||
"version": "0.6.9",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
@@ -59,9 +59,9 @@
|
||||
"base-x": "^3.0.5",
|
||||
"chardet": "^0.7.0",
|
||||
"compression": "^1.7.3",
|
||||
"decompress-zip": "^0.2.2",
|
||||
"element-ui": "^2.4.11",
|
||||
"express": "^4.16.4",
|
||||
"extract-zip": "^1.6.7",
|
||||
"fg-loadcss": "^2.1.0",
|
||||
"fs-extra": "^7.0.1",
|
||||
"got": "^9.5.1",
|
||||
|
||||
@@ -28,7 +28,7 @@ class ConvertHtml extends ConvertBase {
|
||||
} else {
|
||||
isText = opts.isText;
|
||||
}
|
||||
const {cutTitle} = opts;
|
||||
let {cutTitle} = opts;
|
||||
|
||||
let titleInfo = {};
|
||||
let desc = {_n: 'description', 'title-info': titleInfo};
|
||||
@@ -123,8 +123,11 @@ class ConvertHtml extends ConvertBase {
|
||||
}
|
||||
}
|
||||
|
||||
if (tag == 'title')
|
||||
if (tag == 'title' || tag == 'cut-title') {
|
||||
inTitle = true;
|
||||
if (tag == 'cut-title')
|
||||
cutTitle = true;
|
||||
}
|
||||
|
||||
if (tag == 'fb2-image') {
|
||||
inImage = true;
|
||||
@@ -153,7 +156,7 @@ class ConvertHtml extends ConvertBase {
|
||||
}
|
||||
}
|
||||
|
||||
if (tag == 'title')
|
||||
if (tag == 'title' || tag == 'cut-title')
|
||||
inTitle = false;
|
||||
|
||||
if (tag == 'fb2-image')
|
||||
|
||||
@@ -2,9 +2,9 @@ const fs = require('fs-extra');
|
||||
const zlib = require('zlib');
|
||||
const crypto = require('crypto');
|
||||
const path = require('path');
|
||||
const extractZip = require('extract-zip');
|
||||
const unbzip2Stream = require('unbzip2-stream');
|
||||
const tar = require('tar-fs')
|
||||
const tar = require('tar-fs');
|
||||
const DecompressZip = require('decompress-zip');
|
||||
|
||||
const utils = require('./utils');
|
||||
const FileDetector = require('./FileDetector');
|
||||
@@ -114,16 +114,24 @@ class FileDecompressor {
|
||||
async unZip(filename, outputDir) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const files = [];
|
||||
extractZip(filename, {
|
||||
dir: outputDir,
|
||||
onEntry: (entry) => {
|
||||
files.push({path: entry.fileName, size: entry.uncompressedSize});
|
||||
}
|
||||
}, (err) => {
|
||||
if (err)
|
||||
reject(err);
|
||||
const unzipper = new DecompressZip(filename);
|
||||
|
||||
unzipper.on('error', function(err) {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
unzipper.on('extract', function() {
|
||||
resolve(files);
|
||||
});
|
||||
|
||||
unzipper.extract({
|
||||
path: outputDir,
|
||||
filter: function(file) {
|
||||
if (file.type == 'File')
|
||||
files.push({path: file.path, size: file.uncompressedSize});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -184,6 +192,10 @@ class FileDecompressor {
|
||||
resolve([file]);
|
||||
});
|
||||
|
||||
stream.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
inputStream.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
@@ -707,7 +707,8 @@
|
||||
"rules": [
|
||||
{ "type": "or", "rules":
|
||||
[
|
||||
{ "type": "equal", "end": 19, "bytes": "3c3f786d6c2076657273696f6e3d22312e3022" }
|
||||
{ "type": "equal", "end": 19, "bytes": "3c3f786d6c2076657273696f6e3d22312e3022" },
|
||||
{ "type": "equal", "end": 22, "bytes": "efbbbf3c3f786d6c2076657273696f6e3d22312e3022" }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -131,32 +131,34 @@ class ReaderWorker {
|
||||
return `file://${hash}`;
|
||||
}
|
||||
|
||||
async periodicCleanDir(dir, maxSize, timeout) {
|
||||
const list = await fs.readdir(dir);
|
||||
async periodicCleanDir(dir, maxSize, timeout) {
|
||||
try {
|
||||
const list = await fs.readdir(dir);
|
||||
|
||||
let size = 0;
|
||||
let files = [];
|
||||
for (const name of list) {
|
||||
const stat = await fs.stat(`${dir}/${name}`);
|
||||
if (!stat.isDirectory()) {
|
||||
size += stat.size;
|
||||
files.push({name, stat});
|
||||
let size = 0;
|
||||
let files = [];
|
||||
for (const name of list) {
|
||||
const stat = await fs.stat(`${dir}/${name}`);
|
||||
if (!stat.isDirectory()) {
|
||||
size += stat.size;
|
||||
files.push({name, stat});
|
||||
}
|
||||
}
|
||||
|
||||
files.sort((a, b) => a.stat.mtimeMs - b.stat.mtimeMs);
|
||||
|
||||
let i = 0;
|
||||
while (i < files.length && size > maxSize) {
|
||||
const file = files[i];
|
||||
await fs.remove(`${dir}/${file.name}`);
|
||||
size -= file.stat.size;
|
||||
i++;
|
||||
}
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
this.periodicCleanDir(dir, maxSize, timeout);
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
files.sort((a, b) => a.stat.mtimeMs - b.stat.mtimeMs);
|
||||
|
||||
let i = 0;
|
||||
while (i < files.length && size > maxSize) {
|
||||
const file = files[i];
|
||||
await fs.remove(`${dir}/${file.name}`);
|
||||
size -= file.stat.size;
|
||||
i++;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.periodicCleanDir(dir, maxSize, timeout);
|
||||
}, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ function webpackDevMiddleware(app) {
|
||||
function logQueries(app) {
|
||||
app.use(function(req, res, next) {
|
||||
const start = Date.now();
|
||||
log(`${req.method} ${req.originalUrl} ${JSON.stringify(req.body)}`);
|
||||
log(`${req.method} ${req.originalUrl} ${JSON.stringify(req.body).substr(0, 2000)}`);
|
||||
//log(`${JSON.stringify(req.headers, null, 2)}`)
|
||||
res.once('finish', () => {
|
||||
log(`${Date.now() - start}ms`);
|
||||
|
||||
Reference in New Issue
Block a user