Compare commits

...

62 Commits
0.6.1 ... 0.6.9

Author SHA1 Message Date
Book Pauk
5a4d249cf9 Merge branch 'release/0.6.9' 2019-06-23 18:50:57 +07:00
Book Pauk
4cc7bdee37 Версия 0.6.9 2019-06-23 18:50:28 +07:00
Book Pauk
a6af568411 Ускорил сжатие книги при сохранении в BookStore 2019-06-23 18:49:41 +07:00
Book Pauk
576a6a094a Merge tag '0.6.8' into develop
Версия 0.6.8
2019-06-23 17:20:02 +07:00
Book Pauk
e671e4b6f5 Merge branch 'release/0.6.8' 2019-06-23 17:19:48 +07:00
Book Pauk
a66b2a4c70 Версия 0.6.8 2019-06-23 17:19:30 +07:00
Book Pauk
f1ae409535 На страницу загрузки добавлен блок "Поделиться" 2019-06-23 17:18:04 +07:00
Book Pauk
a4b56b477d Исправление автоформирования заголовка при вставке из буфера обмена 2019-06-23 16:29:36 +07:00
Book Pauk
d9c389812a Добавлен новый вариант анимации перелистывания - листание 2019-06-23 15:51:55 +07:00
Book Pauk
074ef3645f Добавлен вариант перелистывания - rotate 2019-06-23 14:13:59 +07:00
Book Pauk
cc3aa413e8 Исправил сообщение о загрузке шрифтов 2019-06-09 18:23:04 +07:00
Book Pauk
7f90c09227 Улучшение прогрессбара загрузки/сохранения книги 2019-06-09 16:44:11 +07:00
Book Pauk
f6f4d8ccc9 Исправлен баг - не распознавались некоторые книги формата fb2 в кодировке utf8 2019-06-09 15:03:04 +07:00
Book Pauk
31afce8304 Исправление бага - падение сервера при распаковке битых архивов 2019-06-04 17:35:32 +07:00
Book Pauk
2c4ff856cd Merge tag '0.6.7' into develop
Версия 0.6.7
2019-05-30 16:16:19 +07:00
Book Pauk
f59974e310 Merge branch 'release/0.6.7' 2019-05-30 16:16:08 +07:00
Book Pauk
70e2c12a6b Версия 0.6.7 2019-05-30 16:15:46 +07:00
Book Pauk
11f3c6ce6f Мелкие поправки 2019-05-30 16:14:41 +07:00
Book Pauk
e213c4640b Добавлен GET-параметр вида "reader?__pp=50.5&url=..." для указания позиции в книге в процентах 2019-05-30 16:00:47 +07:00
Book Pauk
959c5eaa59 Добавлен GET-параметр вида "reader?__refresh=1&url=..." для принудительного обновления загружаемого текста 2019-05-30 14:54:55 +07:00
Book Pauk
66fa510b26 Добавлена возможность указать название текста 2019-05-28 16:32:54 +07:00
Book Pauk
f26a3b31ac На страницу загрузки добавлена возможность загрузки книги из буфера обмена 2019-05-27 16:25:51 +07:00
Book Pauk
724fbf579e Мелкое форматирование 2019-05-27 15:10:40 +07:00
Book Pauk
f192f8e3cd Мелкий рефакторинг 2019-05-27 15:09:55 +07:00
Book Pauk
f13c3d19fb - добавлена возможность настройки отображаемых кнопок на панели управления
- некоторые кнопки на панели управления были скрыты по-умолчанию
2019-05-26 16:16:20 +07:00
Book Pauk
b51a09efcc В справку добавлена история версий проекта 2019-05-26 14:01:56 +07:00
Book Pauk
6004043782 Мелкие поправки 2019-05-23 13:47:36 +07:00
Book Pauk
f9fd0dc2c3 Поправлен баг 2019-05-23 13:47:12 +07:00
Book Pauk
eb5411cd20 К предыдущему 2019-04-27 19:13:56 +07:00
Book Pauk
da3c7a02f0 Небольшие поправки отображения загрузки шрифта 2019-04-27 18:34:23 +07:00
Book Pauk
e67d05007f Поправлен баг 2019-04-27 17:21:49 +07:00
Book Pauk
b0a9a6a08e Добавлена настройка showWhatsNewDialog 2019-04-27 17:04:34 +07:00
Book Pauk
d848ea35f4 Поправки верстки 2019-04-27 16:58:04 +07:00
Book Pauk
350f20effe Добавлена история версий 2019-04-27 16:40:48 +07:00
Book Pauk
b6dc8f98fe Добавлен диалог whatsNew 2019-04-27 15:40:11 +07:00
Book Pauk
1b762ee48d Merge tag '0.6.6' into develop
0.6.6
2019-03-28 14:48:17 +07:00
Book Pauk
cc3d7f1eac Merge branch 'release/0.6.6' 2019-03-28 14:47:52 +07:00
Book Pauk
4107282fbf Версия 0.6.6 2019-03-28 14:47:28 +07:00
Book Pauk
c29ffc3fcd Поправки багов 2019-03-28 14:45:42 +07:00
Book Pauk
f648bcda13 Доработки, оптимизация сохранения recentLast 2019-03-28 14:05:13 +07:00
Book Pauk
aa0044eed2 package-lock.json 2019-03-28 13:15:29 +07:00
Book Pauk
2312a721ae Поправлен текст помощи для автономной загрузки читалки 2019-03-28 13:14:57 +07:00
Book Pauk
b93fc39b00 Мелкая поправка 2019-03-28 12:44:27 +07:00
Book Pauk
2dc2cd700f Merge tag '0.6.5' into develop
0.6.5
2019-03-25 14:04:51 +07:00
Book Pauk
d69e534f8b Merge branch 'release/0.6.5' 2019-03-25 14:04:43 +07:00
Book Pauk
1de9ddd394 Версия 0.6.5 2019-03-25 14:04:16 +07:00
Book Pauk
77c68d4e11 Небольшие поправки 2019-03-25 14:03:50 +07:00
Book Pauk
2a0d1dcfce Поправка бага 2019-03-25 13:06:48 +07:00
Book Pauk
5a19cca407 Поправка текста 2019-03-25 12:53:50 +07:00
Book Pauk
4e8773ecde Мелкая поправка 2019-03-25 12:51:01 +07:00
Book Pauk
4c7dada809 Merge tag '0.6.4' into develop
0.6.4
2019-03-24 14:33:19 +07:00
Book Pauk
65690b15da Merge branch 'release/0.6.4' 2019-03-24 14:33:09 +07:00
Book Pauk
8ba07812ce Оптимизация 2019-03-24 14:32:08 +07:00
Book Pauk
2dd8f35001 Версия 0.6.4 2019-03-24 14:04:46 +07:00
Book Pauk
2d15aa88d4 Исправления багов 2019-03-24 14:04:21 +07:00
Book Pauk
e4257e50f0 Merge tag '0.6.3' into develop
0.6.3
2019-03-24 12:52:10 +07:00
Book Pauk
33ebc07915 Merge branch 'release/0.6.3' 2019-03-24 12:51:55 +07:00
Book Pauk
bc07299626 Версия 0.6.3 2019-03-24 12:51:27 +07:00
Book Pauk
25e8aeef53 Merge tag '0.6.2' into develop
0.6.2
2019-03-24 12:28:43 +07:00
Book Pauk
a2ed34abf3 Merge branch 'release/0.6.2' 2019-03-24 12:28:28 +07:00
Book Pauk
36a7b7b91a Версия 0.6.2, поправка мелкого бага 2019-03-24 12:27:43 +07:00
Book Pauk
b4e8b7375f Merge tag '0.6.1' into develop
0.6.1
2019-03-24 12:15:16 +07:00
28 changed files with 1075 additions and 211 deletions

View File

@@ -5,11 +5,11 @@ import {Buffer} from 'safe-buffer';
import * as utils from '../share/utils'; import * as utils from '../share/utils';
const api = axios.create({ const api = axios.create({
baseURL: '/api/reader' baseURL: '/api/reader'
}); });
const workerApi = axios.create({ const workerApi = axios.create({
baseURL: '/api/worker' baseURL: '/api/worker'
}); });
class Reader { class Reader {

View File

@@ -113,10 +113,13 @@ class App extends Vue {
this.dispatch('config/loadConfig'); this.dispatch('config/loadConfig');
this.$watch('apiError', function(newError) { this.$watch('apiError', function(newError) {
if (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({ this.$notify.error({
title: 'Ошибка API', title: 'Ошибка API',
dangerouslyUseHTMLString: true, dangerouslyUseHTMLString: true,
message: newError.response.config.url + '<br>' + newError.response.statusText message: mes
}); });
} }
}); });

View File

@@ -4,7 +4,8 @@
<ul> <ul>
<li>загрузка любой страницы интернета</li> <li>загрузка любой страницы интернета</li>
<li>изменение цвета фона, текста, размер и тип шрифта и прочее</li> <li>изменение цвета фона, текста, размер и тип шрифта и прочее</li>
<li>установка и запоминание текущей позиции и настроек в браузере (в будущем планируется сохранение и на сервер)</li> <li>установка и запоминание текущей позиции и настроек в браузере и на сервере</li>
<li>синхронизация данных (настроек и читаемых книг) между различными устройствами</li>
<li>кэширование файлов книг на клиенте и на сервере</li> <li>кэширование файлов книг на клиенте и на сервере</li>
<li>открытие книг с локального диска</li> <li>открытие книг с локального диска</li>
<li>плавный скроллинг текста</li> <li>плавный скроллинг текста</li>
@@ -17,9 +18,15 @@
<li>поддерживаемые браузеры: Google Chrome, Mozilla Firefox последних версий</li> <li>поддерживаемые браузеры: Google Chrome, Mozilla Firefox последних версий</li>
</ul> </ul>
<p>В качестве URL можно задавать html-страничку с книгой, либо прямую ссылку <p>В качестве URL книги можно задавать html-страничку с книгой, либо прямую ссылку
на файл из онлайн-библиотеки (например, скопировав адрес ссылки или кнопки "скачать fb2").</p> на файл из онлайн-библиотеки (например, скопировав адрес ссылки или кнопки "скачать 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> <div v-html="automationHtml"></div>
<p>Связаться с разработчиком: <a href="mailto:bookpauk@gmail.com">bookpauk@gmail.com</a></p> <p>Связаться с разработчиком: <a href="mailto:bookpauk@gmail.com">bookpauk@gmail.com</a></p>
@@ -31,6 +38,8 @@
import Vue from 'vue'; import Vue from 'vue';
import Component from 'vue-class-component'; import Component from 'vue-class-component';
import {copyTextToClipboard} from '../../../../share/utils';
export default @Component({ export default @Component({
}) })
class CommonHelpPage extends Vue { class CommonHelpPage extends Vue {
@@ -40,13 +49,22 @@ class CommonHelpPage extends Vue {
get automationHtml() { get automationHtml() {
if (this.config.mode == 'omnireader') { if (this.config.mode == 'omnireader') {
return `<p>Вы можете добавить в свой браузер закладку, указав в ее свойствах вместо адреса следующий код: return `<p>Вы также можете добавить в свой браузер закладку, указав в ее свойствах вместо адреса следующий код:
<br><strong>javascript:location.href='http://omnireader.ru/?url='+location.href;</strong> <br><strong>javascript:location.href='http://omnireader.ru/?url='+location.href;</strong>
<br>Тогда, нажав на получившуюся кнопку на любой странице интернета, вы автоматически откроете ее в Omni Reader.</p>`; <br>Тогда, нажав на получившуюся кнопку на любой странице интернета, вы автоматически откроете ее в Omni Reader.</p>`;
} else { } else {
return ''; 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> </script>
@@ -63,4 +81,10 @@ class CommonHelpPage extends Vue {
h4 { h4 {
margin: 0; margin: 0;
} }
.clickable {
color: blue;
text-decoration: underline;
cursor: pointer;
}
</style> </style>

View File

@@ -53,11 +53,10 @@ class DonateHelpPage extends Vue {
async copyAddress(address, prefix) { async copyAddress(address, prefix) {
const result = await copyTextToClipboard(address); const result = await copyTextToClipboard(address);
const msg = (result ? `${prefix}-адрес ${address} успешно скопирован в буфер обмена` : 'Копирование не удалось');
if (result) if (result)
this.$notify.success({message: msg}); this.$notify.success({message: `${prefix}-адрес ${address} успешно скопирован в буфер обмена`});
else else
this.$notify.error({message: msg}); this.$notify.error({message: 'Копирование не удалось'});
} }
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------

View File

@@ -16,6 +16,9 @@
<el-tab-pane label="Мышь/тачпад"> <el-tab-pane label="Мышь/тачпад">
<MouseHelpPage></MouseHelpPage> <MouseHelpPage></MouseHelpPage>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="История версий" name="releases">
<VersionHistoryPage></VersionHistoryPage>
</el-tab-pane>
<el-tab-pane label="Помочь проекту" name="donate"> <el-tab-pane label="Помочь проекту" name="donate">
<DonateHelpPage></DonateHelpPage> <DonateHelpPage></DonateHelpPage>
</el-tab-pane> </el-tab-pane>
@@ -36,6 +39,7 @@ import CommonHelpPage from './CommonHelpPage/CommonHelpPage.vue';
import HotkeysHelpPage from './HotkeysHelpPage/HotkeysHelpPage.vue'; import HotkeysHelpPage from './HotkeysHelpPage/HotkeysHelpPage.vue';
import MouseHelpPage from './MouseHelpPage/MouseHelpPage.vue'; import MouseHelpPage from './MouseHelpPage/MouseHelpPage.vue';
import DonateHelpPage from './DonateHelpPage/DonateHelpPage.vue'; import DonateHelpPage from './DonateHelpPage/DonateHelpPage.vue';
import VersionHistoryPage from './VersionHistoryPage/VersionHistoryPage.vue';
export default @Component({ export default @Component({
components: { components: {
@@ -44,6 +48,7 @@ export default @Component({
HotkeysHelpPage, HotkeysHelpPage,
MouseHelpPage, MouseHelpPage,
DonateHelpPage, DonateHelpPage,
VersionHistoryPage,
}, },
}) })
class HelpPage extends Vue { class HelpPage extends Vue {
@@ -57,6 +62,10 @@ class HelpPage extends Vue {
this.selectedTab = 'donate'; this.selectedTab = 'donate';
} }
activateVersionHistoryHelpPage() {
this.selectedTab = 'releases';
}
keyHook(event) { keyHook(event) {
if (event.type == 'keydown' && (event.code == 'Escape')) { if (event.type == 'keydown' && (event.code == 'Escape')) {
this.close(); this.close();

View File

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

View File

@@ -248,7 +248,10 @@ class HistoryPage extends Vue {
} }
isUrl(url) { isUrl(url) {
return (url.indexOf('file://') != 0); if (url)
return (url.indexOf('file://') != 0);
else
return false;
} }
close() { close() {

View File

@@ -18,7 +18,19 @@
Загрузить файл с диска Загрузить файл с диска
</el-button> </el-button>
<div class="space"></div> <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>
<div class="part bottom"> <div class="part bottom">
@@ -26,6 +38,8 @@
<span class="bottom-span clickable" @click="openDonate">Помочь проекту</span> <span class="bottom-span clickable" @click="openDonate">Помочь проекту</span>
<span class="bottom-span">{{ version }}</span> <span class="bottom-span">{{ version }}</span>
</div> </div>
<PasteTextPage v-if="pasteTextActive" ref="pasteTextPage" @paste-text-toggle="pasteTextToggle" @load-buffer="loadBuffer"></PasteTextPage>
</div> </div>
</template> </template>
@@ -33,12 +47,17 @@
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
import Vue from 'vue'; import Vue from 'vue';
import Component from 'vue-class-component'; import Component from 'vue-class-component';
import PasteTextPage from './PasteTextPage/PasteTextPage.vue';
export default @Component({ export default @Component({
components: {
PasteTextPage,
},
}) })
class LoaderPage extends Vue { class LoaderPage extends Vue {
bookUrl = null; bookUrl = null;
loadPercent = 0; loadPercent = 0;
pasteTextActive = false;
created() { created() {
this.commit = this.$store.commit; this.commit = this.$store.commit;
@@ -46,6 +65,8 @@ class LoaderPage extends Vue {
mounted() { mounted() {
this.progress = this.$refs.progress; this.progress = this.$refs.progress;
if (this.mode == 'omnireader')
Ya.share2(this.$refs.yaShare2);// eslint-disable-line no-undef
} }
activated() { activated() {
@@ -53,7 +74,7 @@ class LoaderPage extends Vue {
} }
get title() { get title() {
if (this.$store.state.config.mode == 'omnireader') if (this.mode == 'omnireader')
return 'Omni Reader - браузерная онлайн-читалка.'; return 'Omni Reader - браузерная онлайн-читалка.';
return 'Универсальная читалка книг и ресурсов интернета.'; return 'Универсальная читалка книг и ресурсов интернета.';
@@ -83,12 +104,27 @@ class LoaderPage extends Vue {
} }
loadFile() { loadFile() {
const file = this.$refs.file.files[0]; const file = this.$refs.file.files[0];
this.$refs.file.value = ''; this.$refs.file.value = '';
if (file) if (file)
this.$emit('load-file', {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() { openHelp() {
this.$emit('help-toggle'); this.$emit('help-toggle');
} }
@@ -102,6 +138,10 @@ class LoaderPage extends Vue {
} }
keyHook(event) { keyHook(event) {
if (this.pasteTextActive) {
return this.$refs.pasteTextPage.keyHook(event);
}
//недостатки сторонних ui //недостатки сторонних ui
const input = this.$refs.input.$refs.input; const input = this.$refs.input.$refs.input;
if (document.activeElement === input && event.type == 'keydown' && event.code == 'Enter') { if (document.activeElement === input && event.type == 'keydown' && event.code == 'Enter') {
@@ -130,7 +170,7 @@ class LoaderPage extends Vue {
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 340px; min-height: 400px;
} }
.part { .part {

View File

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

View File

@@ -7,35 +7,35 @@
</el-tooltip> </el-tooltip>
<div> <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-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>
<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-button ref="redoAction" class="tool-button" :class="buttonActiveClass('redoAction')" @click="buttonClick('redoAction')" ><i class="el-icon-arrow-right"></i></el-button>
</el-tooltip> </el-tooltip>
<div class="space"></div> <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-button ref="fullScreen" class="tool-button" :class="buttonActiveClass('fullScreen')" @click="buttonClick('fullScreen')"><i class="el-icon-rank"></i></el-button>
</el-tooltip> </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-button ref="scrolling" class="tool-button" :class="buttonActiveClass('scrolling')" @click="buttonClick('scrolling')"><i class="el-icon-sort"></i></el-button>
</el-tooltip> </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-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>
<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-button ref="search" class="tool-button" :class="buttonActiveClass('search')" @click="buttonClick('search')"><i class="el-icon-search"></i></el-button>
</el-tooltip> </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-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>
<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')"> <el-button ref="refresh" class="tool-button" :class="buttonActiveClass('refresh')" @click="buttonClick('refresh')">
<i class="el-icon-refresh" :class="{clear: !showRefreshIcon}"></i> <i class="el-icon-refresh" :class="{clear: !showRefreshIcon}"></i>
</el-button> </el-button>
</el-tooltip> </el-tooltip>
<div class="space"></div> <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-button ref="history" class="tool-button" :class="buttonActiveClass('history')" @click="buttonClick('history')"><i class="el-icon-document"></i></el-button>
</el-tooltip> </el-tooltip>
</div> </div>
@@ -68,13 +68,27 @@
@start-text-search="startTextSearch" @start-text-search="startTextSearch"
@stop-text-search="stopTextSearch"> @stop-text-search="stopTextSearch">
</SearchPage> </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> <HistoryPage v-show="historyActive" ref="historyPage" @load-book="loadBook" @history-toggle="historyToggle"></HistoryPage>
<SettingsPage v-if="settingsActive" ref="settingsPage" @settings-toggle="settingsToggle"></SettingsPage> <SettingsPage v-if="settingsActive" ref="settingsPage" @settings-toggle="settingsToggle"></SettingsPage>
<HelpPage v-if="helpActive" ref="helpPage" @help-toggle="helpToggle"></HelpPage> <HelpPage v-if="helpActive" ref="helpPage" @help-toggle="helpToggle"></HelpPage>
<ClickMapPage v-show="clickMapActive" ref="clickMapPage"></ClickMapPage> <ClickMapPage v-show="clickMapActive" ref="clickMapPage"></ClickMapPage>
<ServerStorage v-show="hidden" ref="serverStorage"></ServerStorage> <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-main>
</el-container> </el-container>
</template> </template>
@@ -101,6 +115,7 @@ import ServerStorage from './ServerStorage/ServerStorage.vue';
import bookManager from './share/bookManager'; import bookManager from './share/bookManager';
import readerApi from '../../api/reader'; import readerApi from '../../api/reader';
import * as utils from '../../share/utils'; import * as utils from '../../share/utils';
import {versionHistory} from './versionHistory';
export default @Component({ export default @Component({
components: { components: {
@@ -167,11 +182,15 @@ class Reader extends Vue {
allowUrlParamBookPos = false; allowUrlParamBookPos = false;
showRefreshIcon = true; showRefreshIcon = true;
mostRecentBookReactive = null; mostRecentBookReactive = null;
showToolButton = {};
actionList = []; actionList = [];
actionCur = -1; actionCur = -1;
hidden = false; hidden = false;
whatsNewVisible = false;
whatsNewContent = '';
created() { created() {
this.loading = true; this.loading = true;
this.commit = this.$store.commit; this.commit = this.$store.commit;
@@ -223,14 +242,17 @@ class Reader extends Vue {
if (this.$root.rootRoute == '/reader') { if (this.$root.rootRoute == '/reader') {
if (this.routeParamUrl) { 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 { } else {
this.loaderActive = true; this.loaderActive = true;
} }
} }
this.checkSetStorageAccessKey(); this.checkSetStorageAccessKey();
this.checkActivateDonateHelpPage();
this.loading = false; this.loading = false;
await this.showWhatsNew();
})(); })();
} }
@@ -241,6 +263,8 @@ class Reader extends Vue {
this.showClickMapPage = settings.showClickMapPage; this.showClickMapPage = settings.showClickMapPage;
this.clickControl = settings.clickControl; this.clickControl = settings.clickControl;
this.blinkCachedLoad = settings.blinkCachedLoad; this.blinkCachedLoad = settings.blinkCachedLoad;
this.showWhatsNewDialog = settings.showWhatsNewDialog;
this.showToolButton = settings.showToolButton;
} }
checkSetStorageAccessKey() { 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() { get routeParamPos() {
let result = undefined; let result = undefined;
const q = this.$route.query; const q = this.$route.query;
@@ -293,6 +367,11 @@ class Reader extends Vue {
return decodeURIComponent(result); return decodeURIComponent(result);
} }
get routeParamRefresh() {
const q = this.$route.query;
return !!q['__refresh'];
}
bookPosChanged(event) { bookPosChanged(event) {
if (event.bookPosSeen !== undefined) if (event.bookPosSeen !== undefined)
this.bookPosSeen = event.bookPosSeen; this.bookPosSeen = event.bookPosSeen;
@@ -353,6 +432,10 @@ class Reader extends Vue {
return this.$store.state.reader.settings; return this.$store.state.reader.settings;
} }
get whatsNewContentHash() {
return this.$store.state.reader.whatsNewContentHash;
}
addAction(pos) { addAction(pos) {
let a = this.actionList; let a = this.actionList;
if (!a.length || a[a.length - 1] != pos) { 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() { refreshBook() {
if (this.mostRecentBook()) { if (this.mostRecentBook()) {
this.loadBook({url: this.mostRecentBook().url, force: true}); this.loadBook({url: this.mostRecentBook().url, force: true});
@@ -708,7 +800,7 @@ class Reader extends Vue {
this.progressActive = true; this.progressActive = true;
await this.$nextTick() await this.$nextTick();
const progress = this.$refs.page; const progress = this.$refs.page;
@@ -743,6 +835,7 @@ class Reader extends Vue {
progress.hide(); this.progressActive = false; progress.hide(); this.progressActive = false;
this.blinkCachedLoadMessage(); this.blinkCachedLoadMessage();
this.checkBookPosPercent();
await this.activateClickMapPage(); await this.activateClickMapPage();
return; return;
} }
@@ -791,6 +884,7 @@ class Reader extends Vue {
} else } else
this.stopBlink = true; this.stopBlink = true;
this.checkBookPosPercent();
await this.activateClickMapPage(); await this.activateClickMapPage();
} catch (e) { } catch (e) {
progress.hide(); this.progressActive = false; progress.hide(); this.progressActive = false;
@@ -1010,4 +1104,10 @@ i {
.clear { .clear {
color: rgba(0,0,0,0); color: rgba(0,0,0,0);
} }
</style>
.clickable {
color: blue;
text-decoration: underline;
cursor: pointer;
}
</style>

View File

@@ -37,6 +37,7 @@ export default @Component({
class ServerStorage extends Vue { class ServerStorage extends Vue {
created() { created() {
this.inited = false; this.inited = false;
this.keyInited = false;
this.commit = this.$store.commit; this.commit = this.$store.commit;
this.prevServerStorageKey = null; this.prevServerStorageKey = null;
this.$root.$on('generateNewServerStorageKey', () => {this.generateNewServerStorageKey()}); this.$root.$on('generateNewServerStorageKey', () => {this.generateNewServerStorageKey()});
@@ -49,6 +50,7 @@ class ServerStorage extends Vue {
this.oldSettings = {}; this.oldSettings = {};
this.oldRecent = {}; this.oldRecent = {};
this.oldRecentLast = {}; this.oldRecentLast = {};
this.oldRecentLastDiff = {};
} }
async init() { async init() {
@@ -75,7 +77,12 @@ class ServerStorage extends Vue {
async serverSyncEnabledChanged() { async serverSyncEnabledChanged() {
if (this.serverSyncEnabled) { if (this.serverSyncEnabled) {
this.prevServerStorageKey = null; this.prevServerStorageKey = null;
await this.serverStorageKeyChanged(true); if (!this.serverStorageKey) {
//генерируем новый ключ
await this.generateNewServerStorageKey();
} else {
await this.serverStorageKeyChanged(true);
}
} }
} }
@@ -83,6 +90,7 @@ class ServerStorage extends Vue {
if (this.prevServerStorageKey != this.serverStorageKey) { if (this.prevServerStorageKey != this.serverStorageKey) {
this.prevServerStorageKey = this.serverStorageKey; this.prevServerStorageKey = this.serverStorageKey;
this.hashedStorageKey = utils.toBase58(cryptoUtils.sha256(this.serverStorageKey)); this.hashedStorageKey = utils.toBase58(cryptoUtils.sha256(this.serverStorageKey));
this.keyInited = true;
await this.loadProfiles(force); await this.loadProfiles(force);
this.checkCurrentProfile(); this.checkCurrentProfile();
@@ -158,7 +166,7 @@ class ServerStorage extends Vue {
} }
async loadSettings(force) { async loadSettings(force) {
if (!this.serverSyncEnabled || !this.currentProfile) if (!this.keyInited || !this.serverSyncEnabled || !this.currentProfile)
return; return;
const setsId = `settings-${this.currentProfile}`; const setsId = `settings-${this.currentProfile}`;
@@ -201,7 +209,7 @@ class ServerStorage extends Vue {
} }
async saveSettings() { async saveSettings() {
if (!this.serverSyncEnabled || !this.currentProfile || this.savingSettings) if (!this.keyInited || !this.serverSyncEnabled || !this.currentProfile || this.savingSettings)
return; return;
const diff = utils.getObjDiff(this.oldSettings, this.settings); const diff = utils.getObjDiff(this.oldSettings, this.settings);
@@ -247,7 +255,7 @@ class ServerStorage extends Vue {
} }
async loadProfiles(force) { async loadProfiles(force) {
if (!this.serverSyncEnabled) if (!this.keyInited || !this.serverSyncEnabled)
return; return;
const oldRev = this.profilesRev; const oldRev = this.profilesRev;
@@ -289,7 +297,7 @@ class ServerStorage extends Vue {
} }
async saveProfiles() { async saveProfiles() {
if (!this.serverSyncEnabled || this.savingProfiles) if (!this.keyInited || !this.serverSyncEnabled || this.savingProfiles)
return; return;
const diff = utils.getObjDiff(this.oldProfiles, this.profiles); const diff = utils.getObjDiff(this.oldProfiles, this.profiles);
@@ -341,18 +349,20 @@ class ServerStorage extends Vue {
} }
async loadRecent(force) { async loadRecent(force) {
if (!this.serverSyncEnabled) if (!this.keyInited || !this.serverSyncEnabled)
return; return;
const oldRev = bookManager.recentRev; const oldRev = bookManager.recentRev;
const oldLastRev = bookManager.recentLastRev; const oldLastRev = bookManager.recentLastRev;
const oldLastDiffRev = bookManager.recentLastDiffRev;
//проверим ревизию на сервере //проверим ревизию на сервере
let revs = null; let revs = null;
if (!force) { if (!force) {
try { try {
revs = await this.storageCheck({recent: {}, recentLast: {}}); revs = await this.storageCheck({recent: {}, recentLast: {}, recentLastDiff: {}});
if (revs.state == 'success' && revs.items.recent.rev == oldRev && 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; return;
} }
} catch(e) { } catch(e) {
@@ -384,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; let recentLast = null;
try { try {
recentLast = await this.storageGet({recentLast: {}}); recentLast = await this.storageGet({recentLast: {}, recentLastDiff: {}});
} catch(e) { } catch(e) {
this.error(`Ошибка соединения с сервером: ${e.message}`); this.error(`Ошибка соединения с сервером: ${e.message}`);
return; return;
} }
if (recentLast.state == 'success') { if (recentLast.state == 'success') {
const recentLastDiff = recentLast.items.recentLastDiff;
recentLast = recentLast.items.recentLast; recentLast = recentLast.items.recentLast;
if (recentLast.rev == 0) if (recentLast.rev == 0)
recentLast.data = {}; recentLast.data = {};
if (recentLastDiff.rev == 0)
recentLastDiff.data = {};
this.oldRecentLastDiff = _.cloneDeep(recentLastDiff.data);
this.oldRecentLast = _.cloneDeep(recentLast.data); this.oldRecentLast = _.cloneDeep(recentLast.data);
recentLast.data = utils.applyObjDiff(recentLast.data, recentLastDiff.data);
await bookManager.setRecentLast(recentLast.data); await bookManager.setRecentLast(recentLast.data);
await bookManager.setRecentLastRev(recentLast.rev); await bookManager.setRecentLastRev(recentLast.rev);
await bookManager.setRecentLastDiffRev(recentLastDiff.rev);
} else { } else {
this.warning(`Неверный ответ сервера: ${recentLast.state}`); this.warning(`Неверный ответ сервера: ${recentLast.state}`);
} }
@@ -411,7 +429,7 @@ class ServerStorage extends Vue {
} }
async saveRecent() { async saveRecent() {
if (!this.serverSyncEnabled || this.savingRecent) if (!this.keyInited || !this.serverSyncEnabled || this.savingRecent)
return; return;
const bm = bookManager; const bm = bookManager;
@@ -457,7 +475,7 @@ class ServerStorage extends Vue {
} }
async saveRecentLast(force = false) { async saveRecentLast(force = false) {
if (!this.serverSyncEnabled || this.savingRecentLast) if (!this.keyInited || !this.serverSyncEnabled || this.savingRecentLast)
return; return;
const bm = bookManager; const bm = bookManager;
@@ -469,6 +487,11 @@ class ServerStorage extends Vue {
if (utils.isEmptyObjDiff(diff)) if (utils.isEmptyObjDiff(diff))
return; return;
if (this.oldRecentLast.key == recentLast.key && JSON.stringify(recentLast) > JSON.stringify(diff)) {
await this.saveRecentLastDiff(diff, force);
return;
}
this.savingRecentLast = true; this.savingRecentLast = true;
try { try {
let result = {state: ''}; let result = {state: ''};
@@ -508,12 +531,69 @@ class ServerStorage extends Vue {
} else { } else {
this.oldRecentLast = _.cloneDeep(recentLast); this.oldRecentLast = _.cloneDeep(recentLast);
await bm.setRecentLastRev(lastRev + 1); await bm.setRecentLastRev(lastRev + 1);
await this.saveRecentLastDiff({}, true);
} }
} finally { } finally {
this.savingRecentLast = false; 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) { async storageCheck(items) {
return await this.storageApi('check', items); return await this.storageApi('check', items);
} }

View File

@@ -106,6 +106,7 @@
</el-form> </el-form>
</div> </div>
</el-tab-pane> </el-tab-pane>
<!-- Вид -------------------------------------------------------------------------> <!-- Вид ------------------------------------------------------------------------->
<el-tab-pane label="Вид"> <el-tab-pane label="Вид">
@@ -345,6 +346,28 @@
</el-form> </el-form>
</el-tab-pane> </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-tab-pane label="Листание">
<el-form :model="form" size="mini" label-width="120px" @submit.native.prevent> <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="rightShift"></el-option>
<el-option label="Протаивание" value="thaw"></el-option> <el-option label="Протаивание" value="thaw"></el-option>
<el-option label="Мерцание" value="blink"></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-select>
</el-col> </el-col>
</el-form-item> </el-form-item>
@@ -380,14 +405,12 @@
</el-tooltip> </el-tooltip>
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-tab-pane> </el-tab-pane>
<!-- Прочее -------------------------------------------------------------------------> <!-- Прочее ------------------------------------------------------------------------->
<el-tab-pane label="Прочее"> <el-tab-pane label="Прочее">
<el-form :model="form" size="mini" label-width="120px" @submit.native.prevent> <el-form :model="form" size="mini" label-width="120px" @submit.native.prevent>
<el-form-item label="Управление"> <div class="partHeader">Подсказки, уведомления</div>
<el-checkbox v-model="clickControl">Включить управление кликом</el-checkbox>
</el-form-item>
<el-form-item label="Подсказка"> <el-form-item label="Подсказка">
<el-tooltip :open-delay="500" effect="light"> <el-tooltip :open-delay="500" effect="light">
@@ -406,7 +429,7 @@
<el-checkbox v-model="blinkCachedLoad">Предупреждать о загрузке из кэша</el-checkbox> <el-checkbox v-model="blinkCachedLoad">Предупреждать о загрузке из кэша</el-checkbox>
</el-tooltip> </el-tooltip>
</el-form-item> </el-form-item>
<el-form-item label="Уведомления"> <el-form-item label="Уведомление">
<el-tooltip :open-delay="500" effect="light"> <el-tooltip :open-delay="500" effect="light">
<template slot="content"> <template slot="content">
Показывать уведомления и ошибки от<br> Показывать уведомления и ошибки от<br>
@@ -415,8 +438,21 @@
<el-checkbox v-model="showServerStorageMessages">Показывать сообщения синхронизации</el-checkbox> <el-checkbox v-model="showServerStorageMessages">Показывать сообщения синхронизации</el-checkbox>
</el-tooltip> </el-tooltip>
</el-form-item> </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"> <el-tooltip :open-delay="500" effect="light">
<template slot="content"> <template slot="content">
Добавление параметра "__p" в строке браузера<br> Добавление параметра "__p" в строке браузера<br>
@@ -450,9 +486,10 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-tab-pane> </el-tab-pane>
<!-- Сброс -------------------------------------------------------------------------> <!-- Сброс ------------------------------------------------------------------------->
<el-tab-pane label="Сброс"> <el-tab-pane label="Сброс">
<el-button @click="setDefaults">Установить по-умолчанию</el-button> <el-button @click="setDefaults">Установить по умолчанию</el-button>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
@@ -504,6 +541,10 @@ export default @Component({
const font = (newValue ? newValue : this.fontName); const font = (newValue ? newValue : this.fontName);
this.vertShift = this.fontShifts[font] || 0; this.vertShift = this.fontShifts[font] || 0;
}, },
wallpaper: function(newValue) {
if (newValue != '' && this.pageChangeAnimation == 'flip')
this.pageChangeAnimation = '';
},
}, },
}) })
class SettingsPage extends Vue { class SettingsPage extends Vue {
@@ -517,12 +558,14 @@ class SettingsPage extends Vue {
fonts = []; fonts = [];
serverStorageKeyVisible = false; serverStorageKeyVisible = false;
toolButtons = [];
created() { created() {
this.commit = this.$store.commit; this.commit = this.$store.commit;
this.reader = this.$store.state.reader; this.reader = this.$store.state.reader;
this.form = {}; this.form = {};
this.toolButtons = rstore.toolButtons;
this.settingsChanged(); this.settingsChanged();
} }
@@ -536,6 +579,7 @@ class SettingsPage extends Vue {
this.form = Object.assign({}, this.form, {[prop]: newValue}); this.form = Object.assign({}, this.form, {[prop]: newValue});
}); });
} }
this.fontBold = (this.fontWeight == 'bold'); this.fontBold = (this.fontWeight == 'bold');
this.fontItalic = (this.fontStyle == 'italic'); 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() { async addProfile() {
try { try {
if (Object.keys(this.profiles).length >= 100) { if (Object.keys(this.profiles).length >= 100) {
@@ -751,12 +799,12 @@ class SettingsPage extends Vue {
dangerouslyUseHTMLString: true, dangerouslyUseHTMLString: true,
confirmButtonText: 'OK', confirmButtonText: 'OK',
cancelButtonText: 'Отмена', cancelButtonText: 'Отмена',
inputValidator: (str) => { if (str && str.length == 44) return true; else return 'Неверный формат ключа'; }, inputValidator: (str) => { if (str && utils.fromBase58(str).length == 32) return true; else return 'Неверный формат ключа'; },
inputValue: (key && _.isString(key) ? key : null), inputValue: (key && _.isString(key) ? key : null),
type: 'warning' type: 'warning'
}); });
if (result.value && result.value.length == 44) { if (result.value && utils.fromBase58(result.value).length == 32) {
this.commit('reader/setServerStorageKey', result.value); this.commit('reader/setServerStorageKey', result.value);
} }
} catch (e) { } catch (e) {

View File

@@ -317,4 +317,58 @@ export default class DrawHelper {
await animation1Finish(duration); 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 = '';
}
} }

View File

@@ -23,7 +23,6 @@
oncontextmenu="return false;"> oncontextmenu="return false;">
<div v-show="showStatusBar" v-html="statusBarClickable" @mousedown.prevent.stop @touchstart.stop <div v-show="showStatusBar" v-html="statusBarClickable" @mousedown.prevent.stop @touchstart.stop
@click.prevent.stop="onStatusBarClick"></div> @click.prevent.stop="onStatusBarClick"></div>
<div v-show="fontsLoading" ref="fontsLoading"></div>
</div> </div>
<div v-show="!clickControl && showStatusBar" class="layout" v-html="statusBarClickable" @mousedown.prevent.stop @touchstart.stop <div v-show="!clickControl && showStatusBar" class="layout" v-html="statusBarClickable" @mousedown.prevent.stop @touchstart.stop
@click.prevent.stop="onStatusBarClick"></div> @click.prevent.stop="onStatusBarClick"></div>
@@ -77,7 +76,6 @@ class TextPage extends Vue {
page2 = null; page2 = null;
statusBar = null; statusBar = null;
statusBarClickable = null; statusBarClickable = null;
fontsLoading = null;
lastBook = null; lastBook = null;
bookPos = 0; bookPos = 0;
@@ -171,6 +169,13 @@ class TextPage extends Vue {
this.fontShift = this.fontVertShift/100; this.fontShift = this.fontVertShift/100;
this.textShift = this.textVertShift/100 + this.fontShift; 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);
this.statusBarClickable = this.drawHelper.statusBarClickable(this.statusBarTop, this.statusBarHeight);
//drawHelper //drawHelper
this.drawHelper.realWidth = this.realWidth; this.drawHelper.realWidth = this.realWidth;
this.drawHelper.realHeight = this.realHeight; this.drawHelper.realHeight = this.realHeight;
@@ -196,15 +201,6 @@ class TextPage extends Vue {
this.drawHelper.lineHeight = this.lineHeight; this.drawHelper.lineHeight = this.lineHeight;
this.drawHelper.context = this.context; 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';
//parsed //parsed
if (this.parsed) { if (this.parsed) {
this.parsed.p = this.p; this.parsed.p = this.p;
@@ -225,13 +221,6 @@ class TextPage extends Vue {
this.parsed.imageFitWidth = this.imageFitWidth; 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 //scrolling page
const pageSpace = this.scrollHeight - this.pageLineCount*this.lineHeight; const pageSpace = this.scrollHeight - this.pageLineCount*this.lineHeight;
let y = pageSpace/2; let y = pageSpace/2;
@@ -239,6 +228,10 @@ class TextPage extends Vue {
y += this.statusBarHeight*(this.statusBarTop ? 1 : 0); y += this.statusBarHeight*(this.statusBarTop ? 1 : 0);
let page1 = this.$refs.scrollBox1; let page1 = this.$refs.scrollBox1;
let page2 = this.$refs.scrollBox2; let page2 = this.$refs.scrollBox2;
page1.style.perspective = '3072px';
page2.style.perspective = '3072px';
page1.style.width = this.w + this.indentLR + 'px'; page1.style.width = this.w + this.indentLR + 'px';
page2.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'; page1.style.height = this.scrollHeight - (pageSpace > 0 ? pageSpace : 0) + 'px';
@@ -268,6 +261,18 @@ class TextPage extends Vue {
async loadFonts() { async loadFonts() {
this.fontsLoading = true; this.fontsLoading = true;
let inst = null;
(async() => {
await sleep(500);
if (this.fontsLoading)
inst = this.$notify({
title: '',
dangerouslyUseHTMLString: true,
message: 'Загрузка шрифта &nbsp;<i class="el-icon-loading"></i>',
duration: 0
});
})();
if (!this.fontsLoaded) if (!this.fontsLoaded)
this.fontsLoaded = {}; this.fontsLoaded = {};
//загрузка дин.шрифта //загрузка дин.шрифта
@@ -298,6 +303,8 @@ class TextPage extends Vue {
} }
this.fontsLoading = false; this.fontsLoading = false;
if (inst)
inst.close();
} }
getSettings() { getSettings() {
@@ -625,7 +632,7 @@ class TextPage extends Vue {
const animation1Finish = this.generateWaitingFunc('resolveAnimation1Finish', 'stopAnimation'); const animation1Finish = this.generateWaitingFunc('resolveAnimation1Finish', 'stopAnimation');
const animation2Finish = this.generateWaitingFunc('resolveAnimation2Finish', 'stopAnimation'); const animation2Finish = this.generateWaitingFunc('resolveAnimation2Finish', 'stopAnimation');
const transition1Finish = this.generateWaitingFunc('resolveTransition1Finish', '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)); const duration = Math.round(3000*(1 - this.pageChangeAnimationSpeed/100));
let page1 = this.$refs.scrollingPage1; let page1 = this.$refs.scrollingPage1;
@@ -654,6 +661,14 @@ class TextPage extends Vue {
page1.style.height = this.scrollHeight + this.lineHeight + 'px'; page1.style.height = this.scrollHeight + this.lineHeight + 'px';
page2.style.height = this.scrollHeight + this.lineHeight + 'px'; page2.style.height = this.scrollHeight + this.lineHeight + 'px';
break; 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; this.resolveAnimation1Finish = null;
@@ -1120,6 +1135,10 @@ class TextPage extends Vue {
overflow: hidden; overflow: hidden;
} }
.on-top {
z-index: 100;
}
.back { .back {
z-index: 5; z-index: 5;
} }
@@ -1185,4 +1204,5 @@ class TextPage extends Vue {
0% { opacity: 1; } 0% { opacity: 1; }
100% { opacity: 0; } 100% { opacity: 0; }
} }
</style> </style>

View File

@@ -38,6 +38,7 @@ class BookManager {
this.recent[this.recentLast.key] = this.recentLast; this.recent[this.recentLast.key] = this.recentLast;
this.recentRev = await bmRecentStore.getItem('recent-rev') || 0; this.recentRev = await bmRecentStore.getItem('recent-rev') || 0;
this.recentLastRev = await bmRecentStore.getItem('recent-last-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.books = Object.assign({}, this.booksCached);
this.recentChanged2 = true; this.recentChanged2 = true;
@@ -79,27 +80,31 @@ class BookManager {
} }
} }
let key = null; //"ленивая" загрузка
len = await bmRecentStore.length(); (async() => {
for (let i = 0; i < len; i++) { let key = null;
key = await bmRecentStore.key(i); len = await bmRecentStore.length();
if (key) { for (let i = 0; i < len; i++) {
let r = await bmRecentStore.getItem(key); key = await bmRecentStore.key(i);
if (_.isObject(r) && r.key) { if (key) {
this.recent[r.key] = r; let r = await bmRecentStore.getItem(key);
if (_.isObject(r) && r.key) {
this.recent[r.key] = r;
}
} else {
await bmRecentStore.removeItem(key);
} }
} else {
await bmRecentStore.removeItem(key);
} }
} })();
//размножение для дебага //размножение для дебага
/*if (key) { /*if (key) {
for (let i = 0; i < 1000; i++) { for (let i = 0; i < 1000; i++) {
const k = this.keyFromUrl(i.toString()); const k = this.keyFromUrl(i.toString());
this.recent[k] = Object.assign({}, _.cloneDeep(this.recent[key]), {key: k, touchTime: Date.now() - 1000000}); this.recent[k] = Object.assign({}, _.cloneDeep(this.recent[key]), {key: k, touchTime: Date.now() - 1000000, url: utils.randomHexString(300)});
} }
}*/ }*/
await this.cleanBooks(); await this.cleanBooks();
//очистка позже //очистка позже
@@ -138,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) { async addBook(newBook, callback) {
if (!this.books) if (!this.books)
await this.init(); await this.init();
@@ -146,7 +186,12 @@ class BookManager {
meta.addTime = Date.now(); meta.addTime = Date.now();
const cb = (perc) => { 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); callback(p);
}; };
@@ -155,10 +200,11 @@ class BookManager {
let data = newBook.data; let data = newBook.data;
if (result.dataCompressed) { 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; result.dataCompressedLength = data.byteLength;
} }
callback(90); callback(95);
this.books[meta.key] = result; this.books[meta.key] = result;
this.booksCached[meta.key] = this.metaOnly(result); this.booksCached[meta.key] = this.metaOnly(result);
@@ -197,7 +243,12 @@ class BookManager {
await utils.sleep(10); await utils.sleep(10);
if (result.dataCompressed) { 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); callback(20);
@@ -370,9 +421,19 @@ class BookManager {
Object.assign(mergedRecent, value); Object.assign(mergedRecent, value);
const newRecent = {}; const newRecent = {};
//"ленивое" обновление хранилища
(async() => {
for (const rec of Object.values(mergedRecent)) {
if (rec.key) {
await bmRecentStore.setItem(rec.key, rec);
await utils.sleep(1);
}
}
})();
for (const rec of Object.values(mergedRecent)) { for (const rec of Object.values(mergedRecent)) {
if (rec.key) { if (rec.key) {
await bmRecentStore.setItem(rec.key, rec);
newRecent[rec.key] = rec; newRecent[rec.key] = rec;
} }
} }
@@ -399,6 +460,11 @@ class BookManager {
this.recentLast = value; this.recentLast = value;
await bmCacheStore.setItem('recent-last', this.recentLast); await bmCacheStore.setItem('recent-last', this.recentLast);
if (value && value.key) { if (value && value.key) {
//гарантия переключения книги
const mostRecent = this.mostRecentBook();
if (mostRecent)
this.recent[mostRecent.key].touchTime = value.touchTime - 1;
this.recent[value.key] = value; this.recent[value.key] = value;
await bmRecentStore.setItem(value.key, value); await bmRecentStore.setItem(value.key, value);
await bmCacheStore.setItem('recent', this.recent); await bmCacheStore.setItem('recent', this.recent);
@@ -409,10 +475,15 @@ class BookManager {
} }
async setRecentLastRev(value) { async setRecentLastRev(value) {
bmRecentStore.setItem('recent-last-rev', value); await bmRecentStore.setItem('recent-last-rev', value);
this.recentLastRev = value; this.recentLastRev = value;
} }
async setRecentLastDiffRev(value) {
await bmRecentStore.setItem('recent-last-diff-rev', value);
this.recentLastDiffRev = value;
}
addEventListener(listener) { addEventListener(listener) {
if (this.eventListeners.indexOf(listener) < 0) if (this.eventListeners.indexOf(listener) < 0)
this.eventListeners.push(listener); this.eventListeners.push(listener);

View 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>
`
},
]

View File

@@ -86,8 +86,8 @@ import './theme/form-item.css';
import ElColorPicker from 'element-ui/lib/color-picker'; import ElColorPicker from 'element-ui/lib/color-picker';
import './theme/color-picker.css'; import './theme/color-picker.css';
//import ElDialog from 'element-ui/lib/dialog'; import ElDialog from 'element-ui/lib/dialog';
//import './theme/dialog.css'; import './theme/dialog.css';
import Notification from 'element-ui/lib/notification'; import Notification from 'element-ui/lib/notification';
import './theme/notification.css'; import './theme/notification.css';
@@ -106,7 +106,7 @@ const components = {
ElCol, ElContainer, ElAside, ElMain, ElHeader, ElCol, ElContainer, ElAside, ElMain, ElHeader,
ElInput, ElInputNumber, ElSelect, ElOption, ElTable, ElTableColumn, ElInput, ElInputNumber, ElSelect, ElOption, ElTable, ElTableColumn,
ElProgress, ElSlider, ElForm, ElFormItem, ElProgress, ElSlider, ElForm, ElFormItem,
ElColorPicker, ElColorPicker, ElDialog,
}; };
for (let name in components) { for (let name in components) {

View File

@@ -9,5 +9,6 @@
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
<script src="https://yastatic.net/share2/share.js" async="async"></script>
</body> </body>
</html> </html>

View File

@@ -2,13 +2,12 @@ import _ from 'lodash';
import baseX from 'base-x'; import baseX from 'base-x';
import PAKO from 'pako'; import PAKO from 'pako';
import {Buffer} from 'safe-buffer'; import {Buffer} from 'safe-buffer';
import sjclWrapper from './sjclWrapper';
export const pako = PAKO; export const pako = PAKO;
const BASE58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; const BASE58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
const BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
const bs58 = baseX(BASE58); const bs58 = baseX(BASE58);
const bs64 = baseX(BASE64);
export function sleep(ms) { export function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms)); return new Promise(resolve => setTimeout(resolve, ms));
@@ -40,6 +39,10 @@ export function formatDate(d, format) {
case 'normal': case 'normal':
return `${d.getDate().toString().padStart(2, '0')}.${(d.getMonth() + 1).toString().padStart(2, '0')}.${d.getFullYear()} ` + 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')}`; `${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()}`;
} }
} }
@@ -86,12 +89,18 @@ export function fromBase58(data) {
return bs58.decode(data); return bs58.decode(data);
} }
//base-x слишком тормозит, используем sjcl
export function toBase64(data) { export function toBase64(data) {
return bs64.encode(Buffer.from(data)); return sjclWrapper.codec.base64.fromBits(
sjclWrapper.codec.bytes.toBits(Buffer.from(data))
);
} }
//base-x слишком тормозит, используем sjcl
export function fromBase64(data) { export function fromBase64(data) {
return bs64.decode(data); return Buffer.from(sjclWrapper.codec.bytes.fromBits(
sjclWrapper.codec.base64.toBits(data)
));
} }
export function getObjDiff(oldObj, newObj) { export function getObjDiff(oldObj, newObj) {

View File

@@ -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 = [ const fonts = [
{name: 'ReaderDefault', label: 'По-умолчанию', fontVertShift: 0}, {name: 'ReaderDefault', label: 'По-умолчанию', fontVertShift: 0},
{name: 'GEO_1', label: 'BPG Arial', fontVertShift: 10}, {name: 'GEO_1', label: 'BPG Arial', fontVertShift: 10},
@@ -140,7 +153,7 @@ const settingDefaults = {
indentLR: 15,// px, отступ всего текста слева и справа indentLR: 15,// px, отступ всего текста слева и справа
indentTB: 0,// px, отступ всего текста сверху и снизу indentTB: 0,// px, отступ всего текста сверху и снизу
wordWrap: true,//перенос по слогам wordWrap: true,//перенос по слогам
keepLastToFirst: true,// перенос последней строки в первую при листании keepLastToFirst: false,// перенос последней строки в первую при листании
showStatusBar: true, showStatusBar: true,
statusBarTop: false,// top, bottom statusBarTop: false,// top, bottom
@@ -150,7 +163,7 @@ const settingDefaults = {
scrollingDelay: 3000,// замедление, ms scrollingDelay: 3000,// замедление, ms
scrollingType: 'ease-in-out', //linear, ease, ease-in, ease-out, ease-in-out 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% pageChangeAnimationSpeed: 80, //0-100%
allowUrlParamBookPos: false, allowUrlParamBookPos: false,
@@ -166,14 +179,18 @@ const settingDefaults = {
imageHeightLines: 100, imageHeightLines: 100,
imageFitWidth: true, imageFitWidth: true,
showServerStorageMessages: true, showServerStorageMessages: true,
showWhatsNewDialog: true,
fontShifts: {}, fontShifts: {},
showToolButton: {},
}; };
for (const font of fonts) for (const font of fonts)
settingDefaults.fontShifts[font.name] = font.fontVertShift; settingDefaults.fontShifts[font.name] = font.fontVertShift;
for (const font of webFonts) for (const font of webFonts)
settingDefaults.fontShifts[font.name] = font.fontVertShift; settingDefaults.fontShifts[font.name] = font.fontVertShift;
for (const button of toolButtons)
settingDefaults.showToolButton[button.name] = button.show;
// initial state // initial state
const state = { const state = {
@@ -183,6 +200,7 @@ const state = {
profiles: {}, profiles: {},
profilesRev: 0, profilesRev: 0,
allowProfilesSave: false,//подстраховка для разработки allowProfilesSave: false,//подстраховка для разработки
whatsNewContentHash: '',
currentProfile: '', currentProfile: '',
settings: Object.assign({}, settingDefaults), settings: Object.assign({}, settingDefaults),
settingsRev: {}, settingsRev: {},
@@ -214,6 +232,9 @@ const mutations = {
setAllowProfilesSave(state, value) { setAllowProfilesSave(state, value) {
state.allowProfilesSave = value; state.allowProfilesSave = value;
}, },
setWhatsNewContentHash(state, value) {
state.whatsNewContentHash = value;
},
setCurrentProfile(state, value) { setCurrentProfile(state, value) {
state.currentProfile = value; state.currentProfile = value;
}, },
@@ -226,6 +247,7 @@ const mutations = {
}; };
export default { export default {
toolButtons,
fonts, fonts,
webFonts, webFonts,
settingDefaults, settingDefaults,

183
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{ {
"name": "Liberama", "name": "Liberama",
"version": "0.5.6", "version": "0.6.7",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@@ -1623,6 +1623,15 @@
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
"dev": true "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": { "binary-extensions": {
"version": "1.12.0", "version": "1.12.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.12.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.12.0.tgz",
@@ -1847,6 +1856,11 @@
"integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
"dev": true "dev": true
}, },
"buffers": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz",
"integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s="
},
"builtin-status-codes": { "builtin-status-codes": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", "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", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" "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": { "chalk": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
@@ -3074,6 +3096,51 @@
"mimic-response": "^1.0.0" "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": { "deep-extend": {
"version": "0.6.0", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "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": { "extsprintf": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
@@ -4410,8 +4440,7 @@
"ansi-regex": { "ansi-regex": {
"version": "2.1.1", "version": "2.1.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"aproba": { "aproba": {
"version": "1.2.0", "version": "1.2.0",
@@ -4432,14 +4461,12 @@
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@@ -4454,20 +4481,17 @@
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@@ -4584,8 +4608,7 @@
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@@ -4597,7 +4620,6 @@
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@@ -4612,7 +4634,6 @@
"version": "3.0.4", "version": "3.0.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
@@ -4620,14 +4641,12 @@
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.2.4", "version": "2.2.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.1.1", "safe-buffer": "^5.1.1",
"yallist": "^3.0.0" "yallist": "^3.0.0"
@@ -4646,7 +4665,6 @@
"version": "0.5.1", "version": "0.5.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
} }
@@ -4727,8 +4745,7 @@
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@@ -4740,7 +4757,6 @@
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@@ -4826,8 +4842,7 @@
"safe-buffer": { "safe-buffer": {
"version": "5.1.1", "version": "5.1.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"safer-buffer": { "safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
@@ -4863,7 +4878,6 @@
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",
@@ -4883,7 +4897,6 @@
"version": "3.0.1", "version": "3.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"ansi-regex": "^2.0.0" "ansi-regex": "^2.0.0"
} }
@@ -4927,14 +4940,12 @@
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"yallist": { "yallist": {
"version": "3.0.2", "version": "3.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": 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": { "move-concurrently": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@@ -7300,11 +7316,6 @@
"sha.js": "^2.4.8" "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": { "performance-now": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
@@ -9436,8 +9447,7 @@
"q": { "q": {
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc="
"dev": true
}, },
"qs": { "qs": {
"version": "6.5.2", "version": "6.5.2",
@@ -11010,6 +11020,24 @@
"integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=", "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=",
"dev": true "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": { "tough-cookie": {
"version": "2.4.3", "version": "2.4.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", "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": { "trim-right": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",

View File

@@ -1,6 +1,6 @@
{ {
"name": "Liberama", "name": "Liberama",
"version": "0.6.1", "version": "0.6.9",
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.0.0"
}, },
@@ -59,9 +59,9 @@
"base-x": "^3.0.5", "base-x": "^3.0.5",
"chardet": "^0.7.0", "chardet": "^0.7.0",
"compression": "^1.7.3", "compression": "^1.7.3",
"decompress-zip": "^0.2.2",
"element-ui": "^2.4.11", "element-ui": "^2.4.11",
"express": "^4.16.4", "express": "^4.16.4",
"extract-zip": "^1.6.7",
"fg-loadcss": "^2.1.0", "fg-loadcss": "^2.1.0",
"fs-extra": "^7.0.1", "fs-extra": "^7.0.1",
"got": "^9.5.1", "got": "^9.5.1",

View File

@@ -28,7 +28,7 @@ class ConvertHtml extends ConvertBase {
} else { } else {
isText = opts.isText; isText = opts.isText;
} }
const {cutTitle} = opts; let {cutTitle} = opts;
let titleInfo = {}; let titleInfo = {};
let desc = {_n: 'description', 'title-info': 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; inTitle = true;
if (tag == 'cut-title')
cutTitle = true;
}
if (tag == 'fb2-image') { if (tag == 'fb2-image') {
inImage = true; inImage = true;
@@ -153,7 +156,7 @@ class ConvertHtml extends ConvertBase {
} }
} }
if (tag == 'title') if (tag == 'title' || tag == 'cut-title')
inTitle = false; inTitle = false;
if (tag == 'fb2-image') if (tag == 'fb2-image')

View File

@@ -2,9 +2,9 @@ const fs = require('fs-extra');
const zlib = require('zlib'); const zlib = require('zlib');
const crypto = require('crypto'); const crypto = require('crypto');
const path = require('path'); const path = require('path');
const extractZip = require('extract-zip');
const unbzip2Stream = require('unbzip2-stream'); 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 utils = require('./utils');
const FileDetector = require('./FileDetector'); const FileDetector = require('./FileDetector');
@@ -114,16 +114,24 @@ class FileDecompressor {
async unZip(filename, outputDir) { async unZip(filename, outputDir) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const files = []; const files = [];
extractZip(filename, { const unzipper = new DecompressZip(filename);
dir: outputDir,
onEntry: (entry) => { unzipper.on('error', function(err) {
files.push({path: entry.fileName, size: entry.uncompressedSize}); reject(err);
} });
}, (err) => {
if (err) unzipper.on('extract', function() {
reject(err);
resolve(files); 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]); resolve([file]);
}); });
stream.on('error', (err) => {
reject(err);
});
inputStream.on('error', (err) => { inputStream.on('error', (err) => {
reject(err); reject(err);
}); });

View File

@@ -707,7 +707,8 @@
"rules": [ "rules": [
{ "type": "or", "rules": { "type": "or", "rules":
[ [
{ "type": "equal", "end": 19, "bytes": "3c3f786d6c2076657273696f6e3d22312e3022" } { "type": "equal", "end": 19, "bytes": "3c3f786d6c2076657273696f6e3d22312e3022" },
{ "type": "equal", "end": 22, "bytes": "efbbbf3c3f786d6c2076657273696f6e3d22312e3022" }
] ]
} }
] ]

View File

@@ -131,32 +131,34 @@ class ReaderWorker {
return `file://${hash}`; return `file://${hash}`;
} }
async periodicCleanDir(dir, maxSize, timeout) { async periodicCleanDir(dir, maxSize, timeout) {
const list = await fs.readdir(dir); try {
const list = await fs.readdir(dir);
let size = 0; let size = 0;
let files = []; let files = [];
for (const name of list) { for (const name of list) {
const stat = await fs.stat(`${dir}/${name}`); const stat = await fs.stat(`${dir}/${name}`);
if (!stat.isDirectory()) { if (!stat.isDirectory()) {
size += stat.size; size += stat.size;
files.push({name, stat}); 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);
} }
} }

View File

@@ -20,7 +20,7 @@ function webpackDevMiddleware(app) {
function logQueries(app) { function logQueries(app) {
app.use(function(req, res, next) { app.use(function(req, res, next) {
const start = Date.now(); 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)}`) //log(`${JSON.stringify(req.headers, null, 2)}`)
res.once('finish', () => { res.once('finish', () => {
log(`${Date.now() - start}ms`); log(`${Date.now() - start}ms`);

View File

@@ -51,7 +51,7 @@ async function main() {
} }
app.use(compression({ level: 1 })); app.use(compression({ level: 1 }));
app.use(express.json()); app.use(express.json({limit: '10mb'}));
if (devModule) if (devModule)
devModule.logQueries(app); devModule.logQueries(app);