Compare commits
232 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a26100a8d0 | ||
|
|
8c52f4718c | ||
|
|
85b5c3c4ec | ||
|
|
4fd559e4c7 | ||
|
|
a337d0ddc7 | ||
|
|
9e4cb7071e | ||
|
|
c3f1707343 | ||
|
|
1ed058a553 | ||
|
|
0500a8178d | ||
|
|
7d0059f573 | ||
|
|
4e3b882362 | ||
|
|
13cf47873e | ||
|
|
7ee23ec38f | ||
|
|
eebf17c42c | ||
|
|
f84536788b | ||
|
|
4bbfdc2cb2 | ||
|
|
211fec35e3 | ||
|
|
b8214a46ae | ||
|
|
549ef91c81 | ||
|
|
cede65313b | ||
|
|
d897a7400f | ||
|
|
47f059213f | ||
|
|
8af51bbf08 | ||
|
|
53d9f5ddc6 | ||
|
|
06fffdccc8 | ||
|
|
aa13dc68fc | ||
|
|
813876dd90 | ||
|
|
596c7d65c5 | ||
|
|
ce8dcb75bf | ||
|
|
1bd51b5565 | ||
|
|
1f9ec305b4 | ||
|
|
be0f6e57d7 | ||
|
|
b268e9ee74 | ||
|
|
e97774435b | ||
|
|
93586bc5bb | ||
|
|
fe23089714 | ||
|
|
e743986f38 | ||
|
|
a6c9b700ed | ||
|
|
afa3fcb524 | ||
|
|
b9aeb648d6 | ||
|
|
5f5df1e5b7 | ||
|
|
ad885679e4 | ||
|
|
e002bebfbe | ||
|
|
a8a41e2b3d | ||
|
|
31940caa84 | ||
|
|
880334054e | ||
|
|
5f03ad5597 | ||
|
|
1efa3f055d | ||
|
|
8ccf11278b | ||
|
|
8a9e7ab4c3 | ||
|
|
c0fa7c0c51 | ||
|
|
022dfd4709 | ||
|
|
71e08aacc3 | ||
|
|
337eca87f2 | ||
|
|
074aceff8f | ||
|
|
cdc6cf229a | ||
|
|
1f33513dc9 | ||
|
|
b095b91ff2 | ||
|
|
454a62dbb9 | ||
|
|
5f7cc12157 | ||
|
|
97ef1ee201 | ||
|
|
a318568b72 | ||
|
|
5bb9949440 | ||
|
|
c33e91d5d0 | ||
|
|
ca65ef3cb7 | ||
|
|
9ebdbc81d0 | ||
|
|
b64985349e | ||
|
|
625fd9d1a4 | ||
|
|
eac5fdcec0 | ||
|
|
970b4d5d97 | ||
|
|
f741bc818d | ||
|
|
5f04c24187 | ||
|
|
a382bef336 | ||
|
|
4ddf28f344 | ||
|
|
0dc650305a | ||
|
|
697093d1c9 | ||
|
|
622f7a4479 | ||
|
|
c4b607804b | ||
|
|
864f008679 | ||
|
|
25f309bcb0 | ||
|
|
1354361ad9 | ||
|
|
8136c7b072 | ||
|
|
c9243e7249 | ||
|
|
1a487da3d9 | ||
|
|
b52395751c | ||
|
|
cfa6cc9a83 | ||
|
|
f203384b00 | ||
|
|
9ac3be455c | ||
|
|
20b74a9dcd | ||
|
|
3b848a5a86 | ||
|
|
a9b5e865a5 | ||
|
|
ab46a1b99d | ||
|
|
4a08465f5b | ||
|
|
a7960d6cd6 | ||
|
|
3caea77dde | ||
|
|
fdaa3b7f93 | ||
|
|
4f433b4456 | ||
|
|
309a9ad4fb | ||
|
|
b0e7431e72 | ||
|
|
158118d183 | ||
|
|
382e37fc5a | ||
|
|
3390676847 | ||
|
|
544a995312 | ||
|
|
f209d49bb5 | ||
|
|
42ed691fdc | ||
|
|
98d2e9d266 | ||
|
|
6111158896 | ||
|
|
3267fc653c | ||
|
|
7250608767 | ||
|
|
e82063e435 | ||
|
|
6d4c44bc25 | ||
|
|
2bc94d8792 | ||
|
|
4ca3edd789 | ||
|
|
d6859efde2 | ||
|
|
3f8cbfa259 | ||
|
|
5d18c9371d | ||
|
|
631990e3bb | ||
|
|
4ae7338f94 | ||
|
|
0d1e51cb21 | ||
|
|
475fb833ea | ||
|
|
580b030ee4 | ||
|
|
6a7cbc70d6 | ||
|
|
d76f60639c | ||
|
|
e2bea407ee | ||
|
|
558fed31aa | ||
|
|
f6513d40c8 | ||
|
|
259f9baa59 | ||
|
|
db5650e276 | ||
|
|
51ebbbc569 | ||
|
|
5184661652 | ||
|
|
7853a14ce6 | ||
|
|
a01e78ace9 | ||
|
|
f7eb576d0d | ||
|
|
34f1ad8fae | ||
|
|
c60f0991df | ||
|
|
d505fd0795 | ||
|
|
93cf506535 | ||
|
|
bfb37e55d4 | ||
|
|
92afc5cb33 | ||
|
|
75cb611701 | ||
|
|
2ec1dd58a5 | ||
|
|
7d59af54de | ||
|
|
2b5f47b3de | ||
|
|
16eebfb9a4 | ||
|
|
9025218671 | ||
|
|
6bccb546bb | ||
|
|
29d49046a0 | ||
|
|
717af9ffaf | ||
|
|
00060c9f43 | ||
|
|
759ff46c92 | ||
|
|
41957cdceb | ||
|
|
d418e3a1c9 | ||
|
|
f650124428 | ||
|
|
795d109c76 | ||
|
|
6868b3effc | ||
|
|
26747b7013 | ||
|
|
5198f8aa60 | ||
|
|
552da48a32 | ||
|
|
db8a688620 | ||
|
|
3088028d05 | ||
|
|
fd62ef865d | ||
|
|
ed74ed00ed | ||
|
|
741317aaaf | ||
|
|
9b6ecd4e6b | ||
|
|
7863b3358e | ||
|
|
e1be68ec3d | ||
|
|
a054186d4b | ||
|
|
2d5c549c83 | ||
|
|
9f6072dfe1 | ||
|
|
69c44fe1ab | ||
|
|
4fa7b2443e | ||
|
|
25a69592bb | ||
|
|
44e0b26990 | ||
|
|
c4496f8dc8 | ||
|
|
9e296231d9 | ||
|
|
49b3f05d65 | ||
|
|
f124b9c050 | ||
|
|
63a86f7c06 | ||
|
|
fd0f523c64 | ||
|
|
487e605520 | ||
|
|
9e169e1f4b | ||
|
|
9612e7ebcd | ||
|
|
f66162efe7 | ||
|
|
656642697b | ||
|
|
feb70f85f8 | ||
|
|
ab1981559b | ||
|
|
c8852d9a8e | ||
|
|
9ac8dc7fd1 | ||
|
|
c9419d99e6 | ||
|
|
a1f4a83e72 | ||
|
|
a8abd5d427 | ||
|
|
629d1b0630 | ||
|
|
97c368f63a | ||
|
|
3266a444d0 | ||
|
|
1c246f71f8 | ||
|
|
96945dfc4a | ||
|
|
30eb3001ef | ||
|
|
bdd8636390 | ||
|
|
f762d2a271 | ||
|
|
cf2efc2b92 | ||
|
|
7670da4cba | ||
|
|
d87f9f2a21 | ||
|
|
6e690f3fea | ||
|
|
6321002617 | ||
|
|
15ec362428 | ||
|
|
454004e705 | ||
|
|
e14b414fc1 | ||
|
|
c4b47a5915 | ||
|
|
957c252cd7 | ||
|
|
d6a6c21762 | ||
|
|
834580cfdf | ||
|
|
de13cfb555 | ||
|
|
4f87508834 | ||
|
|
682a044f32 | ||
|
|
bdb5d90b1d | ||
|
|
01880f4456 | ||
|
|
39f78ce7e8 | ||
|
|
755c6b92da | ||
|
|
2eab9c2837 | ||
|
|
63861789de | ||
|
|
086c353eff | ||
|
|
4fe5b44655 | ||
|
|
036547e260 | ||
|
|
696f434c90 | ||
|
|
0c654d9346 | ||
|
|
a2c393b06b | ||
|
|
eae2c2b102 | ||
|
|
d9e49e3484 | ||
|
|
a28d4c2f1c | ||
|
|
9af055ec54 | ||
|
|
0d41171e9d | ||
|
|
08af826ae9 |
@@ -24,8 +24,8 @@ async function main() {
|
|||||||
await fs.ensureDir(tempDownloadDir);
|
await fs.ensureDir(tempDownloadDir);
|
||||||
|
|
||||||
//sqlite3
|
//sqlite3
|
||||||
const sqliteRemoteUrl = 'https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v4.0.4/node-v64-linux-x64.tar.gz';
|
const sqliteRemoteUrl = 'https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v4.1.1/node-v72-linux-x64.tar.gz';
|
||||||
const sqliteDecompressedFilename = `${tempDownloadDir}/node-v64-linux-x64/node_sqlite3.node`;
|
const sqliteDecompressedFilename = `${tempDownloadDir}/node-v72-linux-x64/node_sqlite3.node`;
|
||||||
|
|
||||||
if (!await fs.pathExists(sqliteDecompressedFilename)) {
|
if (!await fs.pathExists(sqliteDecompressedFilename)) {
|
||||||
// Скачиваем node_sqlite3.node для винды, т.к. pkg не включает его в сборку
|
// Скачиваем node_sqlite3.node для винды, т.к. pkg не включает его в сборку
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
|||||||
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
|
const AppCachePlugin = require('appcache-webpack-plugin');
|
||||||
|
|
||||||
const publicDir = path.resolve(__dirname, '../dist/tmp/public');
|
const publicDir = path.resolve(__dirname, '../dist/tmp/public');
|
||||||
const clientDir = path.resolve(__dirname, '../client');
|
const clientDir = path.resolve(__dirname, '../client');
|
||||||
@@ -53,6 +54,7 @@ module.exports = merge(baseWpConfig, {
|
|||||||
template: `${clientDir}/index.html.template`,
|
template: `${clientDir}/index.html.template`,
|
||||||
filename: `${publicDir}/index.html`
|
filename: `${publicDir}/index.html`
|
||||||
}),
|
}),
|
||||||
new CopyWebpackPlugin([{from: `${clientDir}/assets/*`, to: `${publicDir}/`, flatten: true}])
|
new CopyWebpackPlugin([{from: `${clientDir}/assets/*`, to: `${publicDir}/`, flatten: true}]),
|
||||||
|
new AppCachePlugin({exclude: ['../index.html']})
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ async function main() {
|
|||||||
await fs.ensureDir(tempDownloadDir);
|
await fs.ensureDir(tempDownloadDir);
|
||||||
|
|
||||||
//sqlite3
|
//sqlite3
|
||||||
const sqliteRemoteUrl = 'https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v4.0.4/node-v64-win32-x64.tar.gz';
|
const sqliteRemoteUrl = 'https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v4.1.1/node-v72-win32-x64.tar.gz';
|
||||||
const sqliteDecompressedFilename = `${tempDownloadDir}/node-v64-win32-x64/node_sqlite3.node`;
|
const sqliteDecompressedFilename = `${tempDownloadDir}/node-v72-win32-x64/node_sqlite3.node`;
|
||||||
|
|
||||||
if (!await fs.pathExists(sqliteDecompressedFilename)) {
|
if (!await fs.pathExists(sqliteDecompressedFilename)) {
|
||||||
// Скачиваем node_sqlite3.node для винды, т.к. pkg не включает его в сборку
|
// Скачиваем node_sqlite3.node для винды, т.к. pkg не включает его в сборку
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import {Buffer} from 'safe-buffer';
|
|
||||||
|
|
||||||
import * as utils from '../share/utils';
|
import * as utils from '../share/utils';
|
||||||
|
|
||||||
@@ -13,11 +11,11 @@ const workerApi = axios.create({
|
|||||||
});
|
});
|
||||||
|
|
||||||
class Reader {
|
class Reader {
|
||||||
async loadBook(url, callback) {
|
async loadBook(opts, callback) {
|
||||||
const refreshPause = 300;
|
const refreshPause = 300;
|
||||||
if (!callback) callback = () => {};
|
if (!callback) callback = () => {};
|
||||||
|
|
||||||
let response = await api.post('/load-book', {type: 'url', url});
|
let response = await api.post('/load-book', opts);
|
||||||
|
|
||||||
const workerId = response.data.workerId;
|
const workerId = response.data.workerId;
|
||||||
if (!workerId)
|
if (!workerId)
|
||||||
@@ -58,7 +56,11 @@ class Reader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadCachedBook(url, callback){
|
async checkUrl(url) {
|
||||||
|
return await axios.head(url, {headers: {'Cache-Control': 'no-cache'}});
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadCachedBook(url, callback) {
|
||||||
const response = await axios.head(url);
|
const response = await axios.head(url);
|
||||||
|
|
||||||
let estSize = 1000000;
|
let estSize = 1000000;
|
||||||
@@ -66,12 +68,13 @@ class Reader {
|
|||||||
estSize = response.headers['content-length'];
|
estSize = response.headers['content-length'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
callback({state: 'loading', progress: 0});
|
||||||
const options = {
|
const options = {
|
||||||
onDownloadProgress: progress => {
|
onDownloadProgress: progress => {
|
||||||
while (progress.loaded > estSize) estSize *= 1.5;
|
while (progress.loaded > estSize) estSize *= 1.5;
|
||||||
|
|
||||||
if (callback)
|
if (callback)
|
||||||
callback({state: 'loading', progress: Math.round((progress.loaded*100)/estSize)});
|
callback({progress: Math.round((progress.loaded*100)/estSize)});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//загрузка
|
//загрузка
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
User-agent: *
|
User-agent: *
|
||||||
Disallow: /?*url=
|
Disallow: /?*url=
|
||||||
Disallow: /#/
|
|
||||||
@@ -47,14 +47,12 @@
|
|||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import Component from 'vue-class-component';
|
import Component from 'vue-class-component';
|
||||||
|
import * as utils from '../share/utils';
|
||||||
|
|
||||||
export default @Component({
|
export default @Component({
|
||||||
watch: {
|
watch: {
|
||||||
rootRoute: function() {
|
|
||||||
this.setAppTitle();
|
|
||||||
this.redirectIfNeeded();
|
|
||||||
},
|
|
||||||
mode: function() {
|
mode: function() {
|
||||||
|
this.setAppTitle();
|
||||||
this.redirectIfNeeded();
|
this.redirectIfNeeded();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -123,6 +121,9 @@ class App extends Vue {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.setAppTitle();
|
||||||
|
this.redirectIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleCollapse() {
|
toggleCollapse() {
|
||||||
@@ -201,15 +202,18 @@ class App extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
redirectIfNeeded() {
|
redirectIfNeeded() {
|
||||||
if ((this.mode == 'reader' || this.mode == 'omnireader') && (this.rootRoute != '/reader')) {
|
if ((this.mode == 'reader' || this.mode == 'omnireader') && (!this.isReaderActive)) {
|
||||||
//старый url
|
//старый url
|
||||||
const search = window.location.search.substr(1);
|
const search = window.location.search.substr(1);
|
||||||
const url = search.split('url=')[1] || '';
|
const s = search.split('url=');
|
||||||
|
const url = s[1] || '';
|
||||||
|
const q = utils.parseQuery(s[0] || '');
|
||||||
if (url) {
|
if (url) {
|
||||||
window.location = `/#/reader?url=${url}`;
|
q.url = decodeURIComponent(url);
|
||||||
} else {
|
|
||||||
this.$router.replace('/reader');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.history.replaceState({}, '', '/');
|
||||||
|
this.$router.replace({ path: '/reader', query: q });
|
||||||
}
|
}
|
||||||
|
|
||||||
//yandex-метрика для omnireader
|
//yandex-метрика для omnireader
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="main" class="main" @click="close">
|
<Window @close="close">
|
||||||
<div class="mainWindow" @click.stop>
|
<template slot="header">
|
||||||
<Window @close="close">
|
Скопировать текст
|
||||||
<template slot="header">
|
</template>
|
||||||
Скопировать текст
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div ref="text" class="text" tabindex="-1">
|
<div ref="text" class="text" tabindex="-1">
|
||||||
<div v-html="text"></div>
|
<div v-html="text"></div>
|
||||||
</div>
|
|
||||||
</Window>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Window>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -109,23 +105,6 @@ class CopyTextPage extends Vue {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<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 {
|
.text {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<h4>Возможности читалки:</h4>
|
<h4>Возможности читалки:</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li>загрузка любой страницы интернета</li>
|
<li>загрузка любой страницы интернета</li>
|
||||||
|
<li>работа в автономном режиме (без связи)</li>
|
||||||
<li>изменение цвета фона, текста, размер и тип шрифта и прочее</li>
|
<li>изменение цвета фона, текста, размер и тип шрифта и прочее</li>
|
||||||
<li>установка и запоминание текущей позиции и настроек в браузере и на сервере</li>
|
<li>установка и запоминание текущей позиции и настроек в браузере и на сервере</li>
|
||||||
<li>синхронизация данных (настроек и читаемых книг) между различными устройствами</li>
|
<li>синхронизация данных (настроек и читаемых книг) между различными устройствами</li>
|
||||||
@@ -13,7 +14,6 @@
|
|||||||
<li>поиск по тексту и копирование фрагмента</li>
|
<li>поиск по тексту и копирование фрагмента</li>
|
||||||
<li>запоминание недавних книг, скачивание книги из читалки в формате fb2</li>
|
<li>запоминание недавних книг, скачивание книги из читалки в формате fb2</li>
|
||||||
<li>управление кликом и с клавиатуры</li>
|
<li>управление кликом и с клавиатуры</li>
|
||||||
<li>подключение к интернету не обязательно для чтения книги после ее загрузки</li>
|
|
||||||
<li>регистрация не требуется</li>
|
<li>регистрация не требуется</li>
|
||||||
<li>поддерживаемые браузеры: Google Chrome, Mozilla Firefox последних версий</li>
|
<li>поддерживаемые браузеры: Google Chrome, Mozilla Firefox последних версий</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -22,13 +22,19 @@
|
|||||||
на файл из онлайн-библиотеки (например, скопировав адрес ссылки или кнопки "скачать fb2").</p>
|
на файл из онлайн-библиотеки (например, скопировав адрес ссылки или кнопки "скачать fb2").</p>
|
||||||
<p>Поддерживаемые форматы: <b>fb2, fb2.zip, html, txt</b> и другие.</p>
|
<p>Поддерживаемые форматы: <b>fb2, fb2.zip, html, txt</b> и другие.</p>
|
||||||
|
|
||||||
<p>Для автономной загрузки читалки (без интернета):<br>
|
<div v-show="mode == 'omnireader'">
|
||||||
В Google Chrome можно установить флаг <span class="clickable" @click="copyText('chrome://flags/#show-saved-copy')">chrome://flags/#show-saved-copy</span>
|
<p>Вы можете добавить в свой браузер закладку, указав в ее свойствах вместо адреса следующий код:
|
||||||
в значение "Primary". В этом случае на стандартной странице "нет соединения" появится кнопка для автономной загрузки сайта из кэша.<br>
|
<br><strong>javascript:location.href='https://omnireader.ru/?url='+location.href;</strong>
|
||||||
В Mozilla Firefox в автономном режиме сайт загружается из кэша автоматически. Если этого не происходит, можно установить опцию
|
|
||||||
"Веб-разработка" -> "Работать автономно".</p>
|
<span class="clickable" @click="copyText('javascript:location.href=\'https://omnireader.ru/?url=\'+location.href;', 'Код для адреса закладки успешно скопирован в буфер обмена')">
|
||||||
|
(скопировать)
|
||||||
<div v-html="automationHtml"></div>
|
</span>
|
||||||
|
<br>или перетащив на панель закладок следующую ссылку:
|
||||||
|
<br><a style="margin-left: 50px" href="javascript:location.href='https://omnireader.ru/?url='+location.href;">Omni Reader</a>
|
||||||
|
<br>Тогда, активировав получившуюся закладку на любой странице интернета, вы автоматически загрузите эту страницу в Omni Reader.
|
||||||
|
<br>В Chrome для Android можно вызывать такую закладку по имени прямо в адресной строке браузера (имя стоит сделать попроще).
|
||||||
|
</p>
|
||||||
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -44,22 +50,15 @@ export default @Component({
|
|||||||
})
|
})
|
||||||
class CommonHelpPage extends Vue {
|
class CommonHelpPage extends Vue {
|
||||||
created() {
|
created() {
|
||||||
this.config = this.$store.state.config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get automationHtml() {
|
get mode() {
|
||||||
if (this.config.mode == 'omnireader') {
|
return this.$store.state.config.mode;
|
||||||
return `<p>Вы также можете добавить в свой браузер закладку, указав в ее свойствах вместо адреса следующий код:
|
|
||||||
<br><strong>javascript:location.href='http://omnireader.ru/?url='+location.href;</strong>
|
|
||||||
<br>Тогда, нажав на получившуюся кнопку на любой странице интернета, вы автоматически откроете ее в Omni Reader.</p>`;
|
|
||||||
} else {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async copyText(text) {
|
async copyText(text, mes) {
|
||||||
const result = await copyTextToClipboard(text);
|
const result = await copyTextToClipboard(text);
|
||||||
const msg = (result ? `Ссылка на флаг успешно скопирована в буфер обмена. Можно открыть ее в новой вкладке.` : 'Копирование не удалось');
|
const msg = (result ? mes : 'Копирование не удалось');
|
||||||
if (result)
|
if (result)
|
||||||
this.$notify.success({message: msg});
|
this.$notify.success({message: msg});
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -1,32 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="main" class="main" @click="close">
|
<Window @close="close">
|
||||||
<div class="mainWindow" @click.stop>
|
<template slot="header">
|
||||||
<Window @close="close">
|
Справка
|
||||||
<template slot="header">
|
</template>
|
||||||
Справка
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<el-tabs type="border-card" v-model="selectedTab">
|
<el-tabs type="border-card" v-model="selectedTab">
|
||||||
<el-tab-pane class="tab" label="Общее">
|
<el-tab-pane class="tab" label="Общее">
|
||||||
<CommonHelpPage></CommonHelpPage>
|
<CommonHelpPage></CommonHelpPage>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="Клавиатура">
|
<el-tab-pane label="Клавиатура">
|
||||||
<HotkeysHelpPage></HotkeysHelpPage>
|
<HotkeysHelpPage></HotkeysHelpPage>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="Мышь/тачпад">
|
<el-tab-pane label="Мышь/тачскрин">
|
||||||
<MouseHelpPage></MouseHelpPage>
|
<MouseHelpPage></MouseHelpPage>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="История версий" name="releases">
|
<el-tab-pane label="История версий" name="releases">
|
||||||
<VersionHistoryPage></VersionHistoryPage>
|
<VersionHistoryPage></VersionHistoryPage>
|
||||||
</el-tab-pane>
|
</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>
|
||||||
|
</el-tabs>
|
||||||
</el-tabs>
|
</Window>
|
||||||
</Window>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -77,23 +72,6 @@ class HelpPage extends Vue {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-tabs {
|
.el-tabs {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
<li><b>Ctrl+C</b> - скопировать текст со страницы</li>
|
<li><b>Ctrl+C</b> - скопировать текст со страницы</li>
|
||||||
<li><b>R</b> - принудительно обновить книгу в обход кэша</li>
|
<li><b>R</b> - принудительно обновить книгу в обход кэша</li>
|
||||||
<li><b>X</b> - открыть недавние</li>
|
<li><b>X</b> - открыть недавние</li>
|
||||||
|
<li><b>O</b> - автономный режим</li>
|
||||||
<li><b>S</b> - открыть окно настроек</li>
|
<li><b>S</b> - открыть окно настроек</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,15 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<h4>Управление с помощью мыши/тачпада:</h4>
|
<h4>Управление с помощью мыши/тачскрина:</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li><b>ЛКМ/ТАЧ</b> по экрану в одну из областей - активация действия:</li>
|
<li><b>ЛКМ/ТАЧ</b> по экрану в одну из областей - активация действия:</li>
|
||||||
<div class="click-map-page">
|
<div class="click-map-page">
|
||||||
<ClickMapPage ref="clickMapPage"></ClickMapPage>
|
<ClickMapPage ref="clickMapPage"></ClickMapPage>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<li><b>ПКМ</b> - показать/скрыть панель управления</li>
|
<li><b>ПКМ</b> - показать/скрыть панель управления</li>
|
||||||
<li><b>СКМ</b> - вкл./выкл. плавный скроллинг текста</li>
|
<li><b>СКМ</b> - вкл./выкл. плавный скроллинг текста</li>
|
||||||
|
<br>
|
||||||
|
<li>Жесты для тачскрина:</li>
|
||||||
|
<ul>
|
||||||
|
<li style="list-style-type: square">от центра вверх: на весь экран</li>
|
||||||
|
<li style="list-style-type: square">от центра вниз: плавный скроллинг</li>
|
||||||
|
<li style="list-style-type: square">от центра вправо: увеличить скорость скроллинга</li>
|
||||||
|
<li style="list-style-type: square">от центра влево: уменьшить скорость скроллинга</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
* Для управления с помощью мыши/тачпада необходимо установить галочку "Включить управление кликом" в настройках
|
* Для управления с помощью мыши/тачскрина необходимо установить галочку "Включить управление кликом" в настройках
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,290 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div ref="main" class="main" @click="close">
|
|
||||||
<div class="mainWindow" @click.stop>
|
|
||||||
<Window @close="close">
|
|
||||||
<template slot="header">
|
|
||||||
Последние 100 открытых книг
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<el-table
|
|
||||||
:data="tableData"
|
|
||||||
style="width: 100%"
|
|
||||||
size="mini"
|
|
||||||
height="1px"
|
|
||||||
stripe
|
|
||||||
border
|
|
||||||
:default-sort = "{prop: 'touchDateTime', order: 'descending'}"
|
|
||||||
:header-cell-style = "headerCellStyle"
|
|
||||||
:row-key = "rowKey"
|
|
||||||
>
|
|
||||||
|
|
||||||
<el-table-column
|
|
||||||
type="index"
|
|
||||||
width="35px"
|
|
||||||
>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
prop="touchDateTime"
|
|
||||||
min-width="90px"
|
|
||||||
sortable
|
|
||||||
>
|
|
||||||
<template slot="header" slot-scope="scope"><!-- eslint-disable-line vue/no-unused-vars -->
|
|
||||||
<span style="font-size: 90%">Время<br>просм.</span>
|
|
||||||
</template>
|
|
||||||
<template slot-scope="scope"><!-- eslint-disable-line vue/no-unused-vars -->
|
|
||||||
<div class="desc" @click="loadBook(scope.row.url)">
|
|
||||||
{{ scope.row.touchDate }}<br>
|
|
||||||
{{ scope.row.touchTime }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
<el-table-column
|
|
||||||
>
|
|
||||||
<template slot="header" slot-scope="scope"><!-- eslint-disable-line vue/no-unused-vars -->
|
|
||||||
<!--el-input ref="input"
|
|
||||||
:value="search" @input="search = $event"
|
|
||||||
size="mini"
|
|
||||||
style="margin: 0; padding: 0; vertical-align: bottom; margin-top: 10px"
|
|
||||||
placeholder="Найти"/-->
|
|
||||||
<div class="el-input el-input--mini">
|
|
||||||
<input class="el-input__inner"
|
|
||||||
ref="input"
|
|
||||||
placeholder="Найти"
|
|
||||||
style="margin: 0; padding: 0; vertical-align: bottom; margin-top: 20px; padding: 0 10px 0 10px"
|
|
||||||
:value="search" @input="search = $event.target.value"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<el-table-column
|
|
||||||
min-width="300px"
|
|
||||||
>
|
|
||||||
<template slot-scope="scope">
|
|
||||||
<div class="desc" @click="loadBook(scope.row.url)">
|
|
||||||
<span style="color: green">{{ scope.row.desc.author }}</span><br>
|
|
||||||
<span>{{ scope.row.desc.title }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
<el-table-column
|
|
||||||
min-width="100px"
|
|
||||||
>
|
|
||||||
<template slot-scope="scope">
|
|
||||||
<a v-show="isUrl(scope.row.url)" :href="scope.row.url" target="_blank">Оригинал</a><br>
|
|
||||||
<a :href="scope.row.path" :download="getFileNameFromPath(scope.row.path)">Скачать FB2</a>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
<el-table-column
|
|
||||||
width="60px"
|
|
||||||
>
|
|
||||||
<template slot-scope="scope">
|
|
||||||
<el-button
|
|
||||||
size="mini"
|
|
||||||
style="width: 30px; padding: 7px 0 7px 0; margin-left: 4px"
|
|
||||||
@click="handleDel(scope.row.key)"><i class="el-icon-close"></i>
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
</el-table>
|
|
||||||
</Window>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
import Vue from 'vue';
|
|
||||||
import Component from 'vue-class-component';
|
|
||||||
import path from 'path';
|
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
import {formatDate} from '../../../share/utils';
|
|
||||||
import Window from '../../share/Window.vue';
|
|
||||||
import bookManager from '../share/bookManager';
|
|
||||||
|
|
||||||
export default @Component({
|
|
||||||
components: {
|
|
||||||
Window,
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
search: function() {
|
|
||||||
this.updateTableData();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
class HistoryPage extends Vue {
|
|
||||||
search = null;
|
|
||||||
tableData = null;
|
|
||||||
|
|
||||||
created() {
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this.updateTableData();
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.$refs.input.focus();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
rowKey(row) {
|
|
||||||
return row.key;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateTableData() {
|
|
||||||
let result = [];
|
|
||||||
|
|
||||||
const sorted = bookManager.getSortedRecent();
|
|
||||||
for (let i = 0; i < sorted.length; i++) {
|
|
||||||
const book = sorted[i];
|
|
||||||
if (book.deleted)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
let d = new Date();
|
|
||||||
d.setTime(book.touchTime);
|
|
||||||
const t = formatDate(d).split(' ');
|
|
||||||
|
|
||||||
let perc = '';
|
|
||||||
let textLen = '';
|
|
||||||
const p = (book.bookPosSeen ? book.bookPosSeen : (book.bookPos ? book.bookPos : 0));
|
|
||||||
if (book.textLength) {
|
|
||||||
perc = ` [${((p/book.textLength)*100).toFixed(2)}%]`;
|
|
||||||
textLen = ` ${Math.round(book.textLength/1000)}k`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fb2 = (book.fb2 ? book.fb2 : {});
|
|
||||||
|
|
||||||
let title = fb2.bookTitle;
|
|
||||||
if (title)
|
|
||||||
title = `"${title}"`;
|
|
||||||
else
|
|
||||||
title = '';
|
|
||||||
|
|
||||||
let author = '';
|
|
||||||
if (fb2.author) {
|
|
||||||
const authorNames = fb2.author.map(a => _.compact([
|
|
||||||
a.lastName,
|
|
||||||
a.firstName,
|
|
||||||
a.middleName
|
|
||||||
]).join(' '));
|
|
||||||
author = authorNames.join(', ');
|
|
||||||
} else {
|
|
||||||
author = _.compact([
|
|
||||||
fb2.lastName,
|
|
||||||
fb2.firstName,
|
|
||||||
fb2.middleName
|
|
||||||
]).join(' ');
|
|
||||||
}
|
|
||||||
author = (author ? author : (fb2.bookTitle ? fb2.bookTitle : book.url));
|
|
||||||
|
|
||||||
result.push({
|
|
||||||
touchDateTime: book.touchTime,
|
|
||||||
touchDate: t[0],
|
|
||||||
touchTime: t[1],
|
|
||||||
desc: {
|
|
||||||
title: `${title}${perc}${textLen}`,
|
|
||||||
author,
|
|
||||||
},
|
|
||||||
url: book.url,
|
|
||||||
path: book.path,
|
|
||||||
key: book.key,
|
|
||||||
});
|
|
||||||
if (result.length >= 100)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const search = this.search;
|
|
||||||
result = result.filter(item => {
|
|
||||||
return !search ||
|
|
||||||
item.touchTime.includes(search) ||
|
|
||||||
item.touchDate.includes(search) ||
|
|
||||||
item.desc.title.toLowerCase().includes(search.toLowerCase()) ||
|
|
||||||
item.desc.author.toLowerCase().includes(search.toLowerCase())
|
|
||||||
});
|
|
||||||
|
|
||||||
this.tableData = result;
|
|
||||||
}
|
|
||||||
|
|
||||||
headerCellStyle(cell) {
|
|
||||||
let result = {margin: 0, padding: 0};
|
|
||||||
if (cell.columnIndex > 0) {
|
|
||||||
result['border-bottom'] = 0;
|
|
||||||
}
|
|
||||||
if (cell.rowIndex > 0) {
|
|
||||||
result.height = '0px';
|
|
||||||
result['border-right'] = 0;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
getFileNameFromPath(fb2Path) {
|
|
||||||
return path.basename(fb2Path).substr(0, 10) + '.fb2';
|
|
||||||
}
|
|
||||||
|
|
||||||
openOriginal(url) {
|
|
||||||
window.open(url, '_blank');
|
|
||||||
}
|
|
||||||
|
|
||||||
openFb2(path) {
|
|
||||||
window.open(path, '_blank');
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleDel(key) {
|
|
||||||
await bookManager.delRecentBook({key});
|
|
||||||
this.updateTableData();
|
|
||||||
|
|
||||||
if (!bookManager.mostRecentBook())
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
loadBook(url) {
|
|
||||||
this.$emit('load-book', {url});
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
isUrl(url) {
|
|
||||||
if (url)
|
|
||||||
return (url.indexOf('file://') != 0);
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
close() {
|
|
||||||
this.$emit('history-toggle');
|
|
||||||
}
|
|
||||||
|
|
||||||
keyHook(event) {
|
|
||||||
if (event.type == 'keydown' && event.code == 'Escape') {
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.main {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
z-index: 50;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mainWindow {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.desc {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="main" class="main">
|
<div ref="main" class="main">
|
||||||
<div class="part">
|
<div class="part top">
|
||||||
<span class="greeting bold-font">{{ title }}</span>
|
<span class="greeting bold-font">{{ title }}</span>
|
||||||
<div class="space"></div>
|
<div class="space"></div>
|
||||||
<span class="greeting">Добро пожаловать!</span>
|
<span class="greeting">Добро пожаловать!</span>
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
</el-input>
|
</el-input>
|
||||||
<div class="space"></div>
|
<div class="space"></div>
|
||||||
<input type="file" id="file" ref="file" @change="loadFile" style='display: none;'/>
|
<input type="file" id="file" ref="file" @change="loadFile" style='display: none;'/>
|
||||||
|
|
||||||
<el-button size="mini" @click="loadFileClick">
|
<el-button size="mini" @click="loadFileClick">
|
||||||
Загрузить файл с диска
|
Загрузить файл с диска
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -21,22 +22,28 @@
|
|||||||
<el-button size="mini" @click="loadBufferClick">
|
<el-button size="mini" @click="loadBufferClick">
|
||||||
Из буфера обмена
|
Из буфера обмена
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|
||||||
<div class="space"></div>
|
<div class="space"></div>
|
||||||
<div class="space"></div>
|
<div class="space"></div>
|
||||||
<div v-if="mode == 'omnireader'" ref="yaShare2" class="ya-share2"
|
<div v-if="mode == 'omnireader'">
|
||||||
data-services="collections,vkontakte,facebook,odnoklassniki,twitter,telegram"
|
<div ref="yaShare2" class="ya-share2"
|
||||||
data-description="Чтение fb2-книг онлайн. Загрузка любой страницы интернета одним кликом, синхронизация между устройствами, удобное управление, регистрация не требуется."
|
data-services="collections,vkontakte,facebook,odnoklassniki,twitter,telegram"
|
||||||
data-title="Omni Reader - браузерная онлайн-читалка"
|
data-description="Чтение fb2-книг онлайн. Загрузка любой страницы интернета одним кликом, синхронизация между устройствами, удобное управление, регистрация не требуется."
|
||||||
data-url="http://omnireader.ru">
|
data-title="Omni Reader - браузерная онлайн-читалка"
|
||||||
|
data-url="https://omnireader.ru">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="space"></div>
|
<div class="space"></div>
|
||||||
<span v-if="mode == 'omnireader'" class="bottom-span clickable" @click="openComments">Отзывы о читалке</span>
|
<span v-if="mode == 'omnireader'" class="bottom-span clickable" @click="openComments">Отзывы о читалке</span>
|
||||||
|
<span v-if="mode == 'omnireader'" class="bottom-span clickable" @click="openOldVersion">Старая версия</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="part bottom">
|
<div class="part bottom">
|
||||||
<span class="bottom-span clickable" @click="openHelp">Справка</span>
|
<span class="bottom-span clickable" @click="openHelp">Справка</span>
|
||||||
<span class="bottom-span clickable" @click="openDonate">Помочь проекту</span>
|
<span class="bottom-span clickable" @click="openDonate">Помочь проекту</span>
|
||||||
<span class="bottom-span">{{ version }}</span>
|
|
||||||
|
<span v-if="version == clientVersion" class="bottom-span">v{{ version }}</span>
|
||||||
|
<span v-else class="bottom-span">Версия сервера {{ version }}, версия клиента {{ clientVersion }}, необходимо обновить страницу</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PasteTextPage v-if="pasteTextActive" ref="pasteTextPage" @paste-text-toggle="pasteTextToggle" @load-buffer="loadBuffer"></PasteTextPage>
|
<PasteTextPage v-if="pasteTextActive" ref="pasteTextPage" @paste-text-toggle="pasteTextToggle" @load-buffer="loadBuffer"></PasteTextPage>
|
||||||
@@ -48,6 +55,7 @@
|
|||||||
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';
|
import PasteTextPage from './PasteTextPage/PasteTextPage.vue';
|
||||||
|
import {versionHistory} from '../versionHistory';
|
||||||
|
|
||||||
export default @Component({
|
export default @Component({
|
||||||
components: {
|
components: {
|
||||||
@@ -85,13 +93,19 @@ class LoaderPage extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get version() {
|
get version() {
|
||||||
return `v${this.$store.state.config.version}`;
|
return this.$store.state.config.version;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isExternalConverter() {
|
get isExternalConverter() {
|
||||||
return this.$store.state.config.useExternalBookConverter;
|
return this.$store.state.config.useExternalBookConverter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get clientVersion() {
|
||||||
|
let v = versionHistory[0].header;
|
||||||
|
v = v.split(' ')[0];
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
submitUrl() {
|
submitUrl() {
|
||||||
if (this.bookUrl) {
|
if (this.bookUrl) {
|
||||||
this.$emit('load-book', {url: this.bookUrl});
|
this.$emit('load-book', {url: this.bookUrl});
|
||||||
@@ -137,6 +151,10 @@ class LoaderPage extends Vue {
|
|||||||
window.open('http://samlib.ru/comment/b/bookpauk/bookpauk_reader', '_blank');
|
window.open('http://samlib.ru/comment/b/bookpauk/bookpauk_reader', '_blank');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openOldVersion() {
|
||||||
|
window.open('http://old.omnireader.ru', '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
keyHook(event) {
|
keyHook(event) {
|
||||||
if (this.pasteTextActive) {
|
if (this.pasteTextActive) {
|
||||||
return this.$refs.pasteTextPage.keyHook(event);
|
return this.$refs.pasteTextPage.keyHook(event);
|
||||||
@@ -170,7 +188,7 @@ class LoaderPage extends Vue {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 400px;
|
min-height: 480px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.part {
|
.part {
|
||||||
@@ -196,9 +214,14 @@ class LoaderPage extends Vue {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.top {
|
||||||
|
min-height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
.center {
|
.center {
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
padding: 0 10px 0 10px;
|
padding: 0 10px 0 10px;
|
||||||
|
min-height: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom {
|
.bottom {
|
||||||
|
|||||||
@@ -1,22 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="main" class="main" @click="close">
|
<Window @close="close">
|
||||||
<div class="mainWindow" @click.stop>
|
<template slot="header">
|
||||||
<Window @close="close">
|
<span style="position: relative; top: -3px">
|
||||||
<template slot="header">
|
Вставьте текст и нажмите
|
||||||
Вставьте текст и нажмите
|
<span class="clickable" style="font-size: 150%; position: relative; top: 1px" @click="loadBuffer">загрузить</span>
|
||||||
<el-button size="mini" style="font-size: 120%; color: blue" @click="loadBuffer">Загрузить</el-button>
|
или F2
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
или F2
|
<div>
|
||||||
</template>
|
<el-input placeholder="Введите название текста" class="input" v-model="bookTitle"></el-input>
|
||||||
|
|
||||||
<div>
|
|
||||||
<el-input placeholder="Введите название текста" class="input" v-model="bookTitle"></el-input>
|
|
||||||
</div>
|
|
||||||
<hr/>
|
|
||||||
<textarea ref="textArea" class="text" @paste="calcTitle"></textarea>
|
|
||||||
</Window>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<hr/>
|
||||||
|
<textarea ref="textArea" class="text" @paste="calcTitle"></textarea>
|
||||||
|
</Window>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -99,23 +96,6 @@ class PasteTextPage extends Vue {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<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 {
|
.text {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
@@ -123,6 +103,7 @@ class PasteTextPage extends Vue {
|
|||||||
padding: 0 10px 0 10px;
|
padding: 0 10px 0 10px;
|
||||||
position: relative;
|
position: relative;
|
||||||
font-size: 120%;
|
font-size: 120%;
|
||||||
|
min-width: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text:focus {
|
.text:focus {
|
||||||
@@ -133,4 +114,10 @@ hr {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.clickable {
|
||||||
|
color: blue;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -94,6 +94,6 @@ class ProgressPage extends Vue {
|
|||||||
</style>
|
</style>
|
||||||
<style>
|
<style>
|
||||||
.el-progress__text {
|
.el-progress__text {
|
||||||
color: lightgreen;
|
color: lightgreen !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-container>
|
<el-container>
|
||||||
|
<div v-show="isShowMigrationWarning" style="height: 30px; overflow: hidden; white-space:nowrap;">
|
||||||
|
<span style="margin-left: 10px; font-size: 20px; position: relative; top: 2px" class="clickable" @click="showMigrationHttp">HTTP-версия читалки скоро станет недоступна. Подробнее...</span>
|
||||||
|
</div>
|
||||||
<el-header v-show="toolBarActive" height='50px'>
|
<el-header v-show="toolBarActive" height='50px'>
|
||||||
<div class="header">
|
<div ref="header" class="header">
|
||||||
<el-tooltip content="Загрузить книгу" :open-delay="1000" effect="light">
|
<el-tooltip content="Загрузить книгу" :open-delay="1000" effect="light">
|
||||||
<el-button ref="loader" class="tool-button" :class="buttonActiveClass('loader')" @click="buttonClick('loader')"><i class="el-icon-back"></i></el-button>
|
<el-button ref="loader" class="tool-button" :class="buttonActiveClass('loader')" @click="buttonClick('loader')"><i class="el-icon-back"></i></el-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
@@ -35,8 +38,11 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<div class="space"></div>
|
<div class="space"></div>
|
||||||
<el-tooltip v-show="showToolButton['history']" content="Открыть недавние" :open-delay="1000" effect="light">
|
<el-tooltip v-show="showToolButton['offlineMode']" 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="offlineMode" class="tool-button" :class="buttonActiveClass('offlineMode')" @click="buttonClick('offlineMode')"><i class="el-icon-connection"></i></el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip v-show="showToolButton['recentBooks']" content="Открыть недавние" :open-delay="1000" effect="light">
|
||||||
|
<el-button ref="recentBooks" class="tool-button" :class="buttonActiveClass('recentBooks')" @click="buttonClick('recentBooks')"><i class="el-icon-document"></i></el-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -69,7 +75,7 @@
|
|||||||
@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>
|
<RecentBooksPage v-show="recentBooksActive" ref="recentBooksPage" @load-book="loadBook" @recent-books-toggle="recentBooksToggle"></RecentBooksPage>
|
||||||
<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>
|
||||||
@@ -87,6 +93,87 @@
|
|||||||
</span>
|
</span>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
title="Внимание!"
|
||||||
|
:visible.sync="migrationVisible1"
|
||||||
|
width="90%">
|
||||||
|
<div>
|
||||||
|
Появилась httpS-версия сайта по адресу <a href="https://omnireader.ru" target="_blank">https://omnireader.ru</a><br>
|
||||||
|
Работа по httpS-протоколу, помимо безопасности соединения, позволяет воспользоваться всеми возможностями
|
||||||
|
современных браузеров, а именно, применительно к нашему ресурсу:
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>возможность автономной работы с читалкой (без доступа к интернету), кеширование сайта через appcache</li>
|
||||||
|
<li>безопасная передача на сервер данных о настройках и читаемых книгах при включенной синхронизации; все данные шифруются на стороне
|
||||||
|
браузера ключом доступа и никто (в т.ч. администратор) не имеет возможности их прочитать
|
||||||
|
<li>использование встроенных в JS функций шифрования и других</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
Для того, чтобы перейти на новую версию с сохранением настроек и читаемых книг необходимо синхронизировать обе читалки:
|
||||||
|
<ul>
|
||||||
|
<li>зайти в "Настройки"->"Профили" и поставить галочку "Включить синхронизацию с сервером"</li>
|
||||||
|
<li>там же добавить профиль устройства с любым именем для синхронизации настроек<br>
|
||||||
|
<span style="margin-left: 20px"><i style="font-size: 90%" class="el-icon-info"></i>
|
||||||
|
после этого все данные будут автоматически сохранены на сервер
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li>далее нажать на кнопку "Показать ключ доступа" и кликнуть по ссылке "Ссылка для ввода ключа"<br>
|
||||||
|
<span style="margin-left: 20px"><i style="font-size: 90%" class="el-icon-info"></i>
|
||||||
|
произойдет переход на https-версию читалки и откроется окно для ввода ключа
|
||||||
|
</span><br>
|
||||||
|
<span style="margin-left: 20px"><i style="font-size: 90%" class="el-icon-info"></i>
|
||||||
|
подтвердив ввод ключа нажатием "OK", включив синхронизацию с сервером и выбрав профиль устройства, вы восстановите все ваши настройки в новой версии
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
Старая http-версия сайта будет доступна до конца 2019 года.<br>
|
||||||
|
Приносим извинения за доставленные неудобства.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span slot="footer" class="dialog-footer">
|
||||||
|
<el-button @click="migrationDialogDisable">Больше не показывать</el-button>
|
||||||
|
<el-button @click="migrationDialogRemind">Напомнить позже</el-button>
|
||||||
|
</span>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
title="Внимание!"
|
||||||
|
:visible.sync="migrationVisible2"
|
||||||
|
width="90%">
|
||||||
|
<div>
|
||||||
|
Информация для пользователей старой версии читалки по адресу <a href="http://omnireader.ru" target="_blank">http://omnireader.ru</a><br>
|
||||||
|
Для того, чтобы перейти на новую httpS-версию с сохранением настроек и читаемых книг необходимо синхронизировать обе читалки:
|
||||||
|
<ul>
|
||||||
|
<li>перейти на старую версию ресурса <a href="http://omnireader.ru" target="_blank">http://omnireader.ru</a></li>
|
||||||
|
<li>зайти в "Настройки"->"Профили" и поставить галочку "Включить синхронизацию с сервером"</li>
|
||||||
|
<li>там же добавить профиль устройства с любым именем для синхронизации настроек<br>
|
||||||
|
<span style="margin-left: 20px"><i style="font-size: 90%" class="el-icon-info"></i>
|
||||||
|
после этого все данные будут автоматически сохранены на сервер
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li>далее нажать на кнопку "Показать ключ доступа" и кликнуть по ссылке "Ссылка для ввода ключа"<br>
|
||||||
|
<span style="margin-left: 20px"><i style="font-size: 90%" class="el-icon-info"></i>
|
||||||
|
произойдет переход на https-версию читалки и откроется окно для ввода ключа
|
||||||
|
</span><br>
|
||||||
|
<span style="margin-left: 20px"><i style="font-size: 90%" class="el-icon-info"></i>
|
||||||
|
подтвердив ввод ключа нажатием "OK", включив синхронизацию с сервером и выбрав профиль устройства, вы восстановите все ваши настройки в новой версии
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
Старая http-версия сайта будет доступна до конца 2019 года.<br>
|
||||||
|
Приносим извинения за доставленные неудобства.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span slot="footer" class="dialog-footer">
|
||||||
|
<el-button @click="migrationDialogDisable">Больше не показывать</el-button>
|
||||||
|
<el-button @click="migrationDialogRemind">Напомнить позже</el-button>
|
||||||
|
</span>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
</el-main>
|
</el-main>
|
||||||
|
|
||||||
</el-container>
|
</el-container>
|
||||||
@@ -106,7 +193,7 @@ import ProgressPage from './ProgressPage/ProgressPage.vue';
|
|||||||
import SetPositionPage from './SetPositionPage/SetPositionPage.vue';
|
import SetPositionPage from './SetPositionPage/SetPositionPage.vue';
|
||||||
import SearchPage from './SearchPage/SearchPage.vue';
|
import SearchPage from './SearchPage/SearchPage.vue';
|
||||||
import CopyTextPage from './CopyTextPage/CopyTextPage.vue';
|
import CopyTextPage from './CopyTextPage/CopyTextPage.vue';
|
||||||
import HistoryPage from './HistoryPage/HistoryPage.vue';
|
import RecentBooksPage from './RecentBooksPage/RecentBooksPage.vue';
|
||||||
import SettingsPage from './SettingsPage/SettingsPage.vue';
|
import SettingsPage from './SettingsPage/SettingsPage.vue';
|
||||||
import HelpPage from './HelpPage/HelpPage.vue';
|
import HelpPage from './HelpPage/HelpPage.vue';
|
||||||
import ClickMapPage from './ClickMapPage/ClickMapPage.vue';
|
import ClickMapPage from './ClickMapPage/ClickMapPage.vue';
|
||||||
@@ -126,7 +213,7 @@ export default @Component({
|
|||||||
SetPositionPage,
|
SetPositionPage,
|
||||||
SearchPage,
|
SearchPage,
|
||||||
CopyTextPage,
|
CopyTextPage,
|
||||||
HistoryPage,
|
RecentBooksPage,
|
||||||
SettingsPage,
|
SettingsPage,
|
||||||
HelpPage,
|
HelpPage,
|
||||||
ClickMapPage,
|
ClickMapPage,
|
||||||
@@ -136,14 +223,19 @@ export default @Component({
|
|||||||
bookPos: function(newValue) {
|
bookPos: function(newValue) {
|
||||||
if (newValue !== undefined && this.activePage == 'TextPage') {
|
if (newValue !== undefined && this.activePage == 'TextPage') {
|
||||||
const textPage = this.$refs.page;
|
const textPage = this.$refs.page;
|
||||||
|
|
||||||
if (textPage.bookPos != newValue) {
|
if (textPage.bookPos != newValue) {
|
||||||
textPage.bookPos = newValue;
|
textPage.bookPos = newValue;
|
||||||
}
|
}
|
||||||
this.debouncedSetRecentBook(newValue);
|
|
||||||
|
if (!this.scrollingActive)
|
||||||
|
this.debouncedSetRecentBook(newValue);
|
||||||
|
else
|
||||||
|
this.scrollingSetRecentBook(newValue);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
routeParamPos: function(newValue) {
|
routeParamPos: function(newValue) {
|
||||||
if (newValue !== undefined && newValue != this.bookPos) {
|
if (!this.paramPosIgnore && newValue !== undefined && newValue != this.bookPos) {
|
||||||
this.bookPos = newValue;
|
this.bookPos = newValue;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -157,10 +249,12 @@ export default @Component({
|
|||||||
this.updateRoute();
|
this.updateRoute();
|
||||||
},
|
},
|
||||||
loaderActive: function(newValue) {
|
loaderActive: function(newValue) {
|
||||||
const recent = this.mostRecentBook();
|
(async() => {
|
||||||
if (!newValue && !this.loading && recent && !bookManager.hasBookParsed(recent)) {
|
const recent = this.mostRecentBook();
|
||||||
this.loadBook(recent);
|
if (!newValue && !this.loading && recent && !await bookManager.hasBookParsed(recent)) {
|
||||||
}
|
this.loadBook(recent);
|
||||||
|
}
|
||||||
|
})();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -173,7 +267,8 @@ class Reader extends Vue {
|
|||||||
setPositionActive = false;
|
setPositionActive = false;
|
||||||
searchActive = false;
|
searchActive = false;
|
||||||
copyTextActive = false;
|
copyTextActive = false;
|
||||||
historyActive = false;
|
recentBooksActive = false;
|
||||||
|
offlineModeActive = false;
|
||||||
settingsActive = false;
|
settingsActive = false;
|
||||||
helpActive = false;
|
helpActive = false;
|
||||||
clickMapActive = false;
|
clickMapActive = false;
|
||||||
@@ -190,6 +285,8 @@ class Reader extends Vue {
|
|||||||
|
|
||||||
whatsNewVisible = false;
|
whatsNewVisible = false;
|
||||||
whatsNewContent = '';
|
whatsNewContent = '';
|
||||||
|
migrationVisible1 = false;
|
||||||
|
migrationVisible2 = false;
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
@@ -202,10 +299,6 @@ class Reader extends Vue {
|
|||||||
|
|
||||||
this.lastActivePage = false;
|
this.lastActivePage = false;
|
||||||
|
|
||||||
this.debouncedUpdateRoute = _.debounce(() => {
|
|
||||||
this.updateRoute();
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
this.debouncedSetRecentBook = _.debounce(async(newValue) => {
|
this.debouncedSetRecentBook = _.debounce(async(newValue) => {
|
||||||
const recent = this.mostRecentBook();
|
const recent = this.mostRecentBook();
|
||||||
if (recent && (recent.bookPos != newValue || recent.bookPosSeen !== this.bookPosSeen)) {
|
if (recent && (recent.bookPos != newValue || recent.bookPosSeen !== this.bookPosSeen)) {
|
||||||
@@ -213,20 +306,17 @@ class Reader extends Vue {
|
|||||||
|
|
||||||
if (this.actionCur < 0 || (this.actionCur >= 0 && this.actionList[this.actionCur] != newValue))
|
if (this.actionCur < 0 || (this.actionCur >= 0 && this.actionList[this.actionCur] != newValue))
|
||||||
this.addAction(newValue);
|
this.addAction(newValue);
|
||||||
|
|
||||||
|
this.paramPosIgnore = true;
|
||||||
|
this.updateRoute();
|
||||||
|
await this.$nextTick();
|
||||||
|
this.paramPosIgnore = false;
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 500, {'maxWait':5000});
|
||||||
|
|
||||||
this.debouncedSaveRecent = _.debounce(async() => {
|
this.scrollingSetRecentBook = _.debounce((newValue) => {
|
||||||
const serverStorage = this.$refs.serverStorage;
|
this.debouncedSetRecentBook(newValue);
|
||||||
while (!serverStorage.inited) await utils.sleep(1000);
|
}, 15000, {'maxWait':20000});
|
||||||
await serverStorage.saveRecent();
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
this.debouncedSaveRecentLast = _.debounce(async() => {
|
|
||||||
const serverStorage = this.$refs.serverStorage;
|
|
||||||
while (!serverStorage.inited) await utils.sleep(1000);
|
|
||||||
await serverStorage.saveRecentLast();
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
document.addEventListener('fullscreenchange', () => {
|
document.addEventListener('fullscreenchange', () => {
|
||||||
this.fullScreenActive = (document.fullscreenElement !== null);
|
this.fullScreenActive = (document.fullscreenElement !== null);
|
||||||
@@ -236,10 +326,12 @@ class Reader extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.updateHeaderMinWidth();
|
||||||
|
|
||||||
(async() => {
|
(async() => {
|
||||||
await bookManager.init(this.settings);
|
await bookManager.init(this.settings);
|
||||||
bookManager.addEventListener(this.bookManagerEvent);
|
bookManager.addEventListener(this.bookManagerEvent);
|
||||||
|
|
||||||
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, force: this.routeParamRefresh});
|
await this.loadBook({url: this.routeParamUrl, bookPos: this.routeParamPos, force: this.routeParamRefresh});
|
||||||
@@ -248,11 +340,15 @@ class Reader extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.$refs.serverStorage.init();
|
||||||
this.checkSetStorageAccessKey();
|
this.checkSetStorageAccessKey();
|
||||||
this.checkActivateDonateHelpPage();
|
this.checkActivateDonateHelpPage();
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
|
||||||
await this.showWhatsNew();
|
await this.showWhatsNew();
|
||||||
|
await this.showMigration();
|
||||||
|
|
||||||
|
this.updateRoute();
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,7 +360,17 @@ class Reader extends Vue {
|
|||||||
this.clickControl = settings.clickControl;
|
this.clickControl = settings.clickControl;
|
||||||
this.blinkCachedLoad = settings.blinkCachedLoad;
|
this.blinkCachedLoad = settings.blinkCachedLoad;
|
||||||
this.showWhatsNewDialog = settings.showWhatsNewDialog;
|
this.showWhatsNewDialog = settings.showWhatsNewDialog;
|
||||||
|
this.showMigrationDialog = settings.showMigrationDialog;
|
||||||
this.showToolButton = settings.showToolButton;
|
this.showToolButton = settings.showToolButton;
|
||||||
|
this.enableSitesFilter = settings.enableSitesFilter;
|
||||||
|
|
||||||
|
this.updateHeaderMinWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHeaderMinWidth() {
|
||||||
|
const showButtonCount = Object.values(this.showToolButton).reduce((a, b) => a + (b ? 1 : 0), 0);
|
||||||
|
if (this.$refs.header)
|
||||||
|
this.$refs.header.style.minWidth = 65*showButtonCount + 'px';
|
||||||
}
|
}
|
||||||
|
|
||||||
checkSetStorageAccessKey() {
|
checkSetStorageAccessKey() {
|
||||||
@@ -320,6 +426,41 @@ class Reader extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async showMigration() {
|
||||||
|
await utils.sleep(3000);
|
||||||
|
if (!this.settingsActive &&
|
||||||
|
this.mode == 'omnireader' && this.showMigrationDialog && this.migrationRemindDate != utils.formatDate(new Date(), 'coDate')) {
|
||||||
|
if (window.location.protocol == 'http:') {
|
||||||
|
this.migrationVisible1 = true;
|
||||||
|
} else if (window.location.protocol == 'https:') {
|
||||||
|
this.migrationVisible2 = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get isShowMigrationWarning() {
|
||||||
|
return window.location.protocol == 'http:';
|
||||||
|
}
|
||||||
|
|
||||||
|
showMigrationHttp() {
|
||||||
|
this.migrationVisible1 = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
migrationDialogDisable() {
|
||||||
|
this.migrationVisible1 = false;
|
||||||
|
this.migrationVisible2 = false;
|
||||||
|
if (this.showMigrationDialog) {
|
||||||
|
const newSettings = Object.assign({}, this.settings, { showMigrationDialog: false });
|
||||||
|
this.commit('reader/setSettings', newSettings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
migrationDialogRemind() {
|
||||||
|
this.migrationVisible1 = false;
|
||||||
|
this.migrationVisible2 = false;
|
||||||
|
this.commit('reader/setMigrationRemindDate', utils.formatDate(new Date(), 'coDate'));
|
||||||
|
}
|
||||||
|
|
||||||
openVersionHistory() {
|
openVersionHistory() {
|
||||||
this.whatsNewVisible = false;
|
this.whatsNewVisible = false;
|
||||||
this.versionHistoryToggle();
|
this.versionHistoryToggle();
|
||||||
@@ -350,12 +491,16 @@ class Reader extends Vue {
|
|||||||
const pos = (recent && recent.bookPos && this.allowUrlParamBookPos ? `__p=${recent.bookPos}&` : '');
|
const pos = (recent && recent.bookPos && this.allowUrlParamBookPos ? `__p=${recent.bookPos}&` : '');
|
||||||
const url = (recent ? `url=${recent.url}` : '');
|
const url = (recent ? `url=${recent.url}` : '');
|
||||||
if (isNewRoute)
|
if (isNewRoute)
|
||||||
this.$router.push(`/reader?${pos}${url}`);
|
this.$router.push(`/reader?${pos}${url}`).catch(() => {});
|
||||||
else
|
else
|
||||||
this.$router.replace(`/reader?${pos}${url}`);
|
this.$router.replace(`/reader?${pos}${url}`).catch(() => {});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get mode() {
|
||||||
|
return this.$store.state.config.mode;
|
||||||
|
}
|
||||||
|
|
||||||
get routeParamUrl() {
|
get routeParamUrl() {
|
||||||
let result = '';
|
let result = '';
|
||||||
const path = this.$route.fullPath;
|
const path = this.$route.fullPath;
|
||||||
@@ -376,44 +521,42 @@ class Reader extends Vue {
|
|||||||
if (event.bookPosSeen !== undefined)
|
if (event.bookPosSeen !== undefined)
|
||||||
this.bookPosSeen = event.bookPosSeen;
|
this.bookPosSeen = event.bookPosSeen;
|
||||||
this.bookPos = event.bookPos;
|
this.bookPos = event.bookPos;
|
||||||
this.debouncedUpdateRoute();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async bookManagerEvent(eventName) {
|
async bookManagerEvent(eventName, value) {
|
||||||
const serverStorage = this.$refs.serverStorage;
|
if (eventName == 'set-recent' || eventName == 'recent-deleted') {
|
||||||
if (eventName == 'load-meta-finish') {
|
const oldBook = (this.textPage ? this.textPage.lastBook : null);
|
||||||
serverStorage.init();
|
const oldPos = (this.textPage ? this.textPage.bookPos : null);
|
||||||
const result = await bookManager.cleanRecentBooks();
|
|
||||||
if (result)
|
|
||||||
this.debouncedSaveRecent();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventName == 'recent-changed' || eventName == 'save-recent') {
|
|
||||||
if (this.historyActive) {
|
|
||||||
this.$refs.historyPage.updateTableData();
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldBook = this.mostRecentBookReactive;
|
|
||||||
const newBook = bookManager.mostRecentBook();
|
const newBook = bookManager.mostRecentBook();
|
||||||
|
|
||||||
|
if (!(oldBook && newBook && oldBook.key == newBook.key)) {
|
||||||
|
this.mostRecentBook();
|
||||||
|
}
|
||||||
|
|
||||||
if (oldBook && newBook) {
|
if (oldBook && newBook) {
|
||||||
if (oldBook.key != newBook.key) {
|
if (oldBook.key != newBook.key || oldBook.path != newBook.path) {
|
||||||
this.loadingBook = true;
|
this.loadingBook = true;
|
||||||
try {
|
try {
|
||||||
await this.loadBook(newBook);
|
await this.loadBook(newBook);
|
||||||
} finally {
|
} finally {
|
||||||
this.loadingBook = false;
|
this.loadingBook = false;
|
||||||
}
|
}
|
||||||
} else if (oldBook.bookPos != newBook.bookPos) {
|
} else if (oldPos != newBook.bookPos) {
|
||||||
while (this.loadingBook) await utils.sleep(100);
|
while (this.loadingBook) await utils.sleep(100);
|
||||||
this.bookPosChanged({bookPos: newBook.bookPos});
|
this.bookPosChanged({bookPos: newBook.bookPos});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (eventName == 'recent-changed') {
|
if (eventName == 'recent-changed') {
|
||||||
this.debouncedSaveRecentLast();
|
if (this.recentBooksActive) {
|
||||||
} else {
|
await this.$refs.recentBooksPage.updateTableData();
|
||||||
this.debouncedSaveRecent();
|
}
|
||||||
|
|
||||||
|
//сохранение в serverStorage
|
||||||
|
if (value) {
|
||||||
|
await utils.sleep(500);
|
||||||
|
await this.$refs.serverStorage.saveRecent(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -436,6 +579,10 @@ class Reader extends Vue {
|
|||||||
return this.$store.state.reader.whatsNewContentHash;
|
return this.$store.state.reader.whatsNewContentHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get migrationRemindDate() {
|
||||||
|
return this.$store.state.reader.migrationRemindDate;
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
||||||
@@ -476,7 +623,7 @@ class Reader extends Vue {
|
|||||||
closeAllTextPages() {
|
closeAllTextPages() {
|
||||||
this.setPositionActive = false;
|
this.setPositionActive = false;
|
||||||
this.copyTextActive = false;
|
this.copyTextActive = false;
|
||||||
this.historyActive = false;
|
this.recentBooksActive = false;
|
||||||
this.settingsActive = false;
|
this.settingsActive = false;
|
||||||
this.stopScrolling();
|
this.stopScrolling();
|
||||||
this.stopSearch();
|
this.stopSearch();
|
||||||
@@ -520,6 +667,10 @@ class Reader extends Vue {
|
|||||||
page.stopTextScrolling();
|
page.stopTextScrolling();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.scrollingActive) {
|
||||||
|
this.scrollingSetRecentBook.flush();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stopSearch() {
|
stopSearch() {
|
||||||
@@ -568,22 +719,31 @@ class Reader extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
historyToggle() {
|
recentBooksToggle() {
|
||||||
this.historyActive = !this.historyActive;
|
this.recentBooksActive = !this.recentBooksActive;
|
||||||
if (this.historyActive) {
|
if (this.recentBooksActive) {
|
||||||
this.closeAllTextPages();
|
this.closeAllTextPages();
|
||||||
this.$refs.historyPage.init();
|
this.$refs.recentBooksPage.init();
|
||||||
this.historyActive = true;
|
this.recentBooksActive = true;
|
||||||
} else {
|
} else {
|
||||||
this.historyActive = false;
|
this.recentBooksActive = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
offlineModeToggle() {
|
||||||
|
this.offlineModeActive = !this.offlineModeActive;
|
||||||
|
this.$refs.serverStorage.offlineModeActive = this.offlineModeActive;
|
||||||
|
}
|
||||||
|
|
||||||
settingsToggle() {
|
settingsToggle() {
|
||||||
this.settingsActive = !this.settingsActive;
|
this.settingsActive = !this.settingsActive;
|
||||||
if (this.settingsActive) {
|
if (this.settingsActive) {
|
||||||
this.closeAllTextPages();
|
this.closeAllTextPages();
|
||||||
this.settingsActive = true;
|
this.settingsActive = true;
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.settingsPage.init();
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.settingsActive = false;
|
this.settingsActive = false;
|
||||||
}
|
}
|
||||||
@@ -660,12 +820,15 @@ class Reader extends Vue {
|
|||||||
case 'copyText':
|
case 'copyText':
|
||||||
this.copyTextToggle();
|
this.copyTextToggle();
|
||||||
break;
|
break;
|
||||||
case 'history':
|
|
||||||
this.historyToggle();
|
|
||||||
break;
|
|
||||||
case 'refresh':
|
case 'refresh':
|
||||||
this.refreshBook();
|
this.refreshBook();
|
||||||
break;
|
break;
|
||||||
|
case 'recentBooks':
|
||||||
|
this.recentBooksToggle();
|
||||||
|
break;
|
||||||
|
case 'offlineMode':
|
||||||
|
this.offlineModeToggle();
|
||||||
|
break;
|
||||||
case 'settings':
|
case 'settings':
|
||||||
this.settingsToggle();
|
this.settingsToggle();
|
||||||
break;
|
break;
|
||||||
@@ -684,7 +847,8 @@ class Reader extends Vue {
|
|||||||
case 'scrolling':
|
case 'scrolling':
|
||||||
case 'search':
|
case 'search':
|
||||||
case 'copyText':
|
case 'copyText':
|
||||||
case 'history':
|
case 'recentBooks':
|
||||||
|
case 'offlineMode':
|
||||||
case 'settings':
|
case 'settings':
|
||||||
if (this[`${button}Active`])
|
if (this[`${button}Active`])
|
||||||
classResult = classActive;
|
classResult = classActive;
|
||||||
@@ -702,7 +866,7 @@ class Reader extends Vue {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.activePage == 'LoaderPage' || !this.mostRecentBook()) {
|
if (this.activePage == 'LoaderPage' || !this.mostRecentBookReactive) {
|
||||||
switch (button) {
|
switch (button) {
|
||||||
case 'undoAction':
|
case 'undoAction':
|
||||||
case 'redoAction':
|
case 'redoAction':
|
||||||
@@ -712,9 +876,9 @@ class Reader extends Vue {
|
|||||||
case 'copyText':
|
case 'copyText':
|
||||||
classResult = classDisabled;
|
classResult = classDisabled;
|
||||||
break;
|
break;
|
||||||
case 'history':
|
case 'recentBooks':
|
||||||
case 'refresh':
|
case 'refresh':
|
||||||
if (!this.mostRecentBook())
|
if (!this.mostRecentBookReactive)
|
||||||
classResult = classDisabled;
|
classResult = classDisabled;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -759,7 +923,8 @@ class Reader extends Vue {
|
|||||||
//акивируем страницу с текстом
|
//акивируем страницу с текстом
|
||||||
this.$nextTick(async() => {
|
this.$nextTick(async() => {
|
||||||
const last = this.mostRecentBookReactive;
|
const last = this.mostRecentBookReactive;
|
||||||
const isParsed = bookManager.hasBookParsed(last);
|
const isParsed = await bookManager.hasBookParsed(last);
|
||||||
|
|
||||||
if (!isParsed) {
|
if (!isParsed) {
|
||||||
this.$root.$emit('set-app-title');
|
this.$root.$emit('set-app-title');
|
||||||
return;
|
return;
|
||||||
@@ -768,6 +933,7 @@ class Reader extends Vue {
|
|||||||
this.updateRoute();
|
this.updateRoute();
|
||||||
const textPage = this.$refs.page;
|
const textPage = this.$refs.page;
|
||||||
if (textPage.showBook) {
|
if (textPage.showBook) {
|
||||||
|
this.textPage = textPage;
|
||||||
textPage.lastBook = last;
|
textPage.lastBook = last;
|
||||||
textPage.bookPos = (last.bookPos !== undefined ? last.bookPos : 0);
|
textPage.bookPos = (last.bookPos !== undefined ? last.bookPos : 0);
|
||||||
|
|
||||||
@@ -792,8 +958,10 @@ class Reader extends Vue {
|
|||||||
url = 'http://' + url;
|
url = 'http://' + url;
|
||||||
|
|
||||||
// уже просматривается сейчас
|
// уже просматривается сейчас
|
||||||
const lastBook = (this.$refs.page ? this.$refs.page.lastBook : null);
|
const lastBook = (this.textPage ? this.textPage.lastBook : null);
|
||||||
if (!opts.force && lastBook && lastBook.url == url && bookManager.hasBookParsed(lastBook)) {
|
if (!opts.force && lastBook && lastBook.url == url &&
|
||||||
|
(!opts.path || opts.path == lastBook.path) &&
|
||||||
|
await bookManager.hasBookParsed(lastBook)) {
|
||||||
this.loaderActive = false;
|
this.loaderActive = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -822,7 +990,7 @@ class Reader extends Vue {
|
|||||||
|
|
||||||
if (!opts.force) {
|
if (!opts.force) {
|
||||||
// пытаемся загрузить и распарсить книгу в менеджере из локального кэша
|
// пытаемся загрузить и распарсить книгу в менеджере из локального кэша
|
||||||
const bookParsed = await bookManager.getBook({url}, (prog) => {
|
const bookParsed = await bookManager.getBook({url, path: opts.path}, (prog) => {
|
||||||
progress.setState({progress: prog});
|
progress.setState({progress: prog});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -843,6 +1011,7 @@ class Reader extends Vue {
|
|||||||
// иначе идем на сервер
|
// иначе идем на сервер
|
||||||
// пытаемся загрузить готовый файл с сервера
|
// пытаемся загрузить готовый файл с сервера
|
||||||
if (wasOpened.path) {
|
if (wasOpened.path) {
|
||||||
|
progress.setState({totalSteps: 5});
|
||||||
try {
|
try {
|
||||||
const resp = await readerApi.loadCachedBook(wasOpened.path, (state) => {
|
const resp = await readerApi.loadCachedBook(wasOpened.path, (state) => {
|
||||||
progress.setState(state);
|
progress.setState(state);
|
||||||
@@ -855,11 +1024,10 @@ class Reader extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
progress.setState({totalSteps: 5});
|
progress.setState({totalSteps: 5});
|
||||||
|
|
||||||
// не удалось, скачиваем книгу полностью с конвертацией
|
// не удалось, скачиваем книгу полностью с конвертацией
|
||||||
let loadCached = true;
|
let loadCached = true;
|
||||||
if (!book) {
|
if (!book) {
|
||||||
book = await readerApi.loadBook(url, (state) => {
|
book = await readerApi.loadBook({url, enableSitesFilter: this.enableSitesFilter}, (state) => {
|
||||||
progress.setState(state);
|
progress.setState(state);
|
||||||
});
|
});
|
||||||
loadCached = false;
|
loadCached = false;
|
||||||
@@ -929,7 +1097,7 @@ class Reader extends Vue {
|
|||||||
let page = this.$refs.page;
|
let page = this.$refs.page;
|
||||||
while (this.blinkCount) {
|
while (this.blinkCount) {
|
||||||
this.showRefreshIcon = !this.showRefreshIcon;
|
this.showRefreshIcon = !this.showRefreshIcon;
|
||||||
if (page.blinkCachedLoadMessage)
|
if (page && page.blinkCachedLoadMessage)
|
||||||
page.blinkCachedLoadMessage(this.showRefreshIcon);
|
page.blinkCachedLoadMessage(this.showRefreshIcon);
|
||||||
await utils.sleep(500);
|
await utils.sleep(500);
|
||||||
if (this.stopBlink)
|
if (this.stopBlink)
|
||||||
@@ -939,7 +1107,7 @@ class Reader extends Vue {
|
|||||||
}
|
}
|
||||||
this.showRefreshIcon = true;
|
this.showRefreshIcon = true;
|
||||||
this.inBlink = false;
|
this.inBlink = false;
|
||||||
if (page.blinkCachedLoadMessage)
|
if (page && page.blinkCachedLoadMessage)
|
||||||
page.blinkCachedLoadMessage('finish');
|
page.blinkCachedLoadMessage('finish');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -954,8 +1122,8 @@ class Reader extends Vue {
|
|||||||
if (!handled && this.settingsActive)
|
if (!handled && this.settingsActive)
|
||||||
handled = this.$refs.settingsPage.keyHook(event);
|
handled = this.$refs.settingsPage.keyHook(event);
|
||||||
|
|
||||||
if (!handled && this.historyActive)
|
if (!handled && this.recentBooksActive)
|
||||||
handled = this.$refs.historyPage.keyHook(event);
|
handled = this.$refs.recentBooksPage.keyHook(event);
|
||||||
|
|
||||||
if (!handled && this.setPositionActive)
|
if (!handled && this.setPositionActive)
|
||||||
handled = this.$refs.setPositionPage.keyHook(event);
|
handled = this.$refs.setPositionPage.keyHook(event);
|
||||||
@@ -1005,10 +1173,13 @@ class Reader extends Vue {
|
|||||||
this.refreshBook();
|
this.refreshBook();
|
||||||
break;
|
break;
|
||||||
case 'KeyX':
|
case 'KeyX':
|
||||||
this.historyToggle();
|
this.recentBooksToggle();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
break;
|
break;
|
||||||
|
case 'KeyO':
|
||||||
|
this.offlineModeToggle();
|
||||||
|
break;
|
||||||
case 'KeyS':
|
case 'KeyS':
|
||||||
this.settingsToggle();
|
this.settingsToggle();
|
||||||
break;
|
break;
|
||||||
@@ -1036,11 +1207,10 @@ class Reader extends Vue {
|
|||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
min-width: 550px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-main {
|
.el-main {
|
||||||
@@ -1064,6 +1234,10 @@ class Reader extends Vue {
|
|||||||
box-shadow: 3px 3px 5px black;
|
box-shadow: 3px 3px 5px black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tool-button + .tool-button {
|
||||||
|
margin: 0 2px 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.tool-button:hover {
|
.tool-button:hover {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|||||||
335
client/components/Reader/RecentBooksPage/RecentBooksPage.vue
Normal file
335
client/components/Reader/RecentBooksPage/RecentBooksPage.vue
Normal file
@@ -0,0 +1,335 @@
|
|||||||
|
<template>
|
||||||
|
<Window width="600px" ref="window" @close="close">
|
||||||
|
<template slot="header">
|
||||||
|
<span v-show="!loading">Последние {{tableData ? tableData.length : 0}} открытых книг</span>
|
||||||
|
<span v-show="loading"><i class="el-icon-loading" style="font-size: 25px"></i> <span style="position: relative; top: -4px">Список загружается</span></span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<a ref="download" style='display: none;'></a>
|
||||||
|
<el-table
|
||||||
|
:data="tableData"
|
||||||
|
style="width: 570px"
|
||||||
|
size="mini"
|
||||||
|
height="1px"
|
||||||
|
stripe
|
||||||
|
border
|
||||||
|
:default-sort = "{prop: 'touchDateTime', order: 'descending'}"
|
||||||
|
:header-cell-style = "headerCellStyle"
|
||||||
|
:row-key = "rowKey"
|
||||||
|
>
|
||||||
|
|
||||||
|
<el-table-column
|
||||||
|
type="index"
|
||||||
|
width="35px"
|
||||||
|
>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="touchDateTime"
|
||||||
|
min-width="85px"
|
||||||
|
sortable
|
||||||
|
>
|
||||||
|
<template slot="header" slot-scope="scope"><!-- eslint-disable-line vue/no-unused-vars -->
|
||||||
|
<span style="font-size: 90%">Время<br>просм.</span>
|
||||||
|
</template>
|
||||||
|
<template slot-scope="scope"><!-- eslint-disable-line vue/no-unused-vars -->
|
||||||
|
<div class="desc" @click="loadBook(scope.row.url)">
|
||||||
|
{{ scope.row.touchDate }}<br>
|
||||||
|
{{ scope.row.touchTime }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column
|
||||||
|
>
|
||||||
|
<template slot="header" slot-scope="scope"><!-- eslint-disable-line vue/no-unused-vars -->
|
||||||
|
<!--el-input ref="input"
|
||||||
|
:value="search" @input="search = $event"
|
||||||
|
size="mini"
|
||||||
|
style="margin: 0; padding: 0; vertical-align: bottom; margin-top: 10px"
|
||||||
|
placeholder="Найти"/-->
|
||||||
|
<div class="el-input el-input--mini">
|
||||||
|
<input class="el-input__inner"
|
||||||
|
ref="input"
|
||||||
|
placeholder="Найти"
|
||||||
|
style="margin: 0; vertical-align: bottom; margin-top: 20px; padding: 0 10px 0 10px"
|
||||||
|
:value="search" @input="search = $event.target.value"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-table-column
|
||||||
|
min-width="280px"
|
||||||
|
>
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<div class="desc" @click="loadBook(scope.row.url)">
|
||||||
|
<span style="color: green">{{ scope.row.desc.author }}</span><br>
|
||||||
|
<span>{{ scope.row.desc.title }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column
|
||||||
|
min-width="90px"
|
||||||
|
>
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<a v-show="isUrl(scope.row.url)" :href="scope.row.url" target="_blank">Оригинал</a><br>
|
||||||
|
<a :href="scope.row.path" @click.prevent="downloadBook(scope.row.path)">Скачать FB2</a>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column
|
||||||
|
width="60px"
|
||||||
|
>
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-button
|
||||||
|
size="mini"
|
||||||
|
style="width: 30px; padding: 7px 0 7px 0; margin-left: 4px"
|
||||||
|
@click="handleDel(scope.row.key)"><i class="el-icon-close"></i>
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
</el-table>
|
||||||
|
</Window>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
import Vue from 'vue';
|
||||||
|
import Component from 'vue-class-component';
|
||||||
|
import path from 'path';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import * as utils from '../../../share/utils';
|
||||||
|
import Window from '../../share/Window.vue';
|
||||||
|
import bookManager from '../share/bookManager';
|
||||||
|
import readerApi from '../../../api/reader';
|
||||||
|
|
||||||
|
export default @Component({
|
||||||
|
components: {
|
||||||
|
Window,
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
search: function() {
|
||||||
|
this.updateTableData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
class RecentBooksPage extends Vue {
|
||||||
|
loading = false;
|
||||||
|
search = null;
|
||||||
|
tableData = [];
|
||||||
|
|
||||||
|
created() {
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.$refs.window.init();
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
//this.$refs.input.focus();
|
||||||
|
});
|
||||||
|
(async() => {//отбражение подгрузки списка, иначе тормозит
|
||||||
|
if (this.initing)
|
||||||
|
return;
|
||||||
|
this.initing = true;
|
||||||
|
|
||||||
|
await this.updateTableData(3);
|
||||||
|
await utils.sleep(200);
|
||||||
|
|
||||||
|
if (bookManager.loaded) {
|
||||||
|
const t = Date.now();
|
||||||
|
await this.updateTableData(10);
|
||||||
|
if (bookManager.getSortedRecent().length > 10)
|
||||||
|
await utils.sleep(10*(Date.now() - t));
|
||||||
|
} else {
|
||||||
|
let i = 0;
|
||||||
|
let j = 5;
|
||||||
|
while (i < 500 && !bookManager.loaded) {
|
||||||
|
if (i % j == 0) {
|
||||||
|
bookManager.sortedRecentCached = null;
|
||||||
|
await this.updateTableData(100);
|
||||||
|
j *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
await utils.sleep(100);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await this.updateTableData();
|
||||||
|
this.initing = false;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
rowKey(row) {
|
||||||
|
return row.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateTableData(limit) {
|
||||||
|
while (this.updating) await utils.sleep(100);
|
||||||
|
this.updating = true;
|
||||||
|
let result = [];
|
||||||
|
|
||||||
|
this.loading = !!limit;
|
||||||
|
const sorted = bookManager.getSortedRecent();
|
||||||
|
|
||||||
|
for (let i = 0; i < sorted.length; i++) {
|
||||||
|
const book = sorted[i];
|
||||||
|
if (book.deleted)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (limit && result.length >= limit)
|
||||||
|
break;
|
||||||
|
|
||||||
|
let d = new Date();
|
||||||
|
d.setTime(book.touchTime);
|
||||||
|
const t = utils.formatDate(d).split(' ');
|
||||||
|
|
||||||
|
let perc = '';
|
||||||
|
let textLen = '';
|
||||||
|
const p = (book.bookPosSeen ? book.bookPosSeen : (book.bookPos ? book.bookPos : 0));
|
||||||
|
if (book.textLength) {
|
||||||
|
perc = ` [${((p/book.textLength)*100).toFixed(2)}%]`;
|
||||||
|
textLen = ` ${Math.round(book.textLength/1000)}k`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fb2 = (book.fb2 ? book.fb2 : {});
|
||||||
|
|
||||||
|
let title = fb2.bookTitle;
|
||||||
|
if (title)
|
||||||
|
title = `"${title}"`;
|
||||||
|
else
|
||||||
|
title = '';
|
||||||
|
|
||||||
|
let author = '';
|
||||||
|
if (fb2.author) {
|
||||||
|
const authorNames = fb2.author.map(a => _.compact([
|
||||||
|
a.lastName,
|
||||||
|
a.firstName,
|
||||||
|
a.middleName
|
||||||
|
]).join(' '));
|
||||||
|
author = authorNames.join(', ');
|
||||||
|
} else {//TODO: убрать в будущем
|
||||||
|
author = _.compact([
|
||||||
|
fb2.lastName,
|
||||||
|
fb2.firstName,
|
||||||
|
fb2.middleName
|
||||||
|
]).join(' ');
|
||||||
|
}
|
||||||
|
author = (author ? author : (fb2.bookTitle ? fb2.bookTitle : book.url));
|
||||||
|
|
||||||
|
result.push({
|
||||||
|
touchDateTime: book.touchTime,
|
||||||
|
touchDate: t[0],
|
||||||
|
touchTime: t[1],
|
||||||
|
desc: {
|
||||||
|
title: `${title}${perc}${textLen}`,
|
||||||
|
author,
|
||||||
|
},
|
||||||
|
url: book.url,
|
||||||
|
path: book.path,
|
||||||
|
key: book.key,
|
||||||
|
});
|
||||||
|
if (result.length >= 100)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const search = this.search;
|
||||||
|
result = result.filter(item => {
|
||||||
|
return !search ||
|
||||||
|
item.touchTime.includes(search) ||
|
||||||
|
item.touchDate.includes(search) ||
|
||||||
|
item.desc.title.toLowerCase().includes(search.toLowerCase()) ||
|
||||||
|
item.desc.author.toLowerCase().includes(search.toLowerCase())
|
||||||
|
});
|
||||||
|
|
||||||
|
/*for (let i = 0; i < result.length; i++) {
|
||||||
|
if (!_.isEqual(this.tableData[i], result[i])) {
|
||||||
|
this.$set(this.tableData, i, result[i]);
|
||||||
|
await utils.sleep(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.tableData.length > result.length)
|
||||||
|
this.tableData.splice(result.length);*/
|
||||||
|
|
||||||
|
this.tableData = result;
|
||||||
|
this.updating = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
headerCellStyle(cell) {
|
||||||
|
let result = {margin: 0, padding: 0};
|
||||||
|
if (cell.columnIndex > 0) {
|
||||||
|
result['border-bottom'] = 0;
|
||||||
|
}
|
||||||
|
if (cell.rowIndex > 0) {
|
||||||
|
result.height = '0px';
|
||||||
|
result['border-right'] = 0;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async downloadBook(fb2path) {
|
||||||
|
try {
|
||||||
|
await readerApi.checkUrl(fb2path);
|
||||||
|
|
||||||
|
const d = this.$refs.download;
|
||||||
|
d.href = fb2path;
|
||||||
|
d.download = path.basename(fb2path).substr(0, 10) + '.fb2';
|
||||||
|
d.click();
|
||||||
|
} catch (e) {
|
||||||
|
let errMes = e.message;
|
||||||
|
if (errMes.indexOf('404') >= 0)
|
||||||
|
errMes = 'Файл не найден на сервере (возможно был удален как устаревший)';
|
||||||
|
this.$alert(errMes, 'Ошибка', {type: 'error'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openOriginal(url) {
|
||||||
|
window.open(url, '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
|
openFb2(path) {
|
||||||
|
window.open(path, '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleDel(key) {
|
||||||
|
await bookManager.delRecentBook({key});
|
||||||
|
this.updateTableData();
|
||||||
|
|
||||||
|
if (!bookManager.mostRecentBook())
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadBook(url) {
|
||||||
|
this.$emit('load-book', {url});
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
isUrl(url) {
|
||||||
|
if (url)
|
||||||
|
return (url.indexOf('file://') != 0);
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.$emit('recent-books-toggle');
|
||||||
|
}
|
||||||
|
|
||||||
|
keyHook(event) {
|
||||||
|
if (event.type == 'keydown' && event.code == 'Escape') {
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.desc {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,28 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="main" class="main" @click="close">
|
<Window ref="window" height="125px" max-width="600px" :top-shift="-50" @close="close">
|
||||||
<div class="mainWindow" @click.stop>
|
<template slot="header">
|
||||||
<Window @close="close">
|
{{ header }}
|
||||||
<template slot="header">
|
</template>
|
||||||
{{ header }}
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<span v-show="initStep">{{ initPercentage }}%</span>
|
<span v-show="initStep">{{ initPercentage }}%</span>
|
||||||
|
|
||||||
<div v-show="!initStep" class="input">
|
<div v-show="!initStep" class="input">
|
||||||
<input ref="input" class="el-input__inner"
|
<input ref="input" class="el-input__inner"
|
||||||
placeholder="что ищем"
|
placeholder="что ищем"
|
||||||
:value="needle" @input="needle = $event.target.value"/>
|
:value="needle" @input="needle = $event.target.value"/>
|
||||||
<div style="position: absolute; right: 10px; margin-top: 10px; font-size: 16px;">{{ foundText }}</div>
|
<div style="position: absolute; right: 10px; margin-top: 10px; font-size: 16px;">{{ foundText }}</div>
|
||||||
</div>
|
</div>
|
||||||
<el-button-group v-show="!initStep" class="button-group">
|
<el-button-group v-show="!initStep" class="button-group">
|
||||||
<el-button @click="showNext"><i class="el-icon-arrow-down"></i></el-button>
|
<el-button @click="showNext"><i class="el-icon-arrow-down"></i></el-button>
|
||||||
<el-button @click="showPrev"><i class="el-icon-arrow-up"></i></el-button>
|
<el-button @click="showPrev"><i class="el-icon-arrow-up"></i></el-button>
|
||||||
</el-button-group>
|
</el-button-group>
|
||||||
</div>
|
|
||||||
</Window>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Window>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -61,6 +57,8 @@ class SearchPage extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async init(parsed) {
|
async init(parsed) {
|
||||||
|
this.$refs.window.init();
|
||||||
|
|
||||||
if (this.parsed != parsed) {
|
if (this.parsed != parsed) {
|
||||||
this.initStep = true;
|
this.initStep = true;
|
||||||
this.stopInit = false;
|
this.stopInit = false;
|
||||||
@@ -178,32 +176,13 @@ class SearchPage extends Vue {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<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%;
|
|
||||||
max-width: 500px;
|
|
||||||
height: 125px;
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
top: -50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
min-width: 430px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
@@ -222,6 +201,7 @@ class SearchPage extends Vue {
|
|||||||
|
|
||||||
.el-button {
|
.el-button {
|
||||||
padding: 9px 17px 9px 17px;
|
padding: 9px 17px 9px 17px;
|
||||||
|
width: 55px;
|
||||||
}
|
}
|
||||||
|
|
||||||
i {
|
i {
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ import readerApi from '../../../api/reader';
|
|||||||
import * as utils from '../../../share/utils';
|
import * as utils from '../../../share/utils';
|
||||||
import * as cryptoUtils from '../../../share/cryptoUtils';
|
import * as cryptoUtils from '../../../share/cryptoUtils';
|
||||||
|
|
||||||
const maxSetTries = 5;
|
import localForage from 'localforage';
|
||||||
|
const ssCacheStore = localForage.createInstance({
|
||||||
|
name: 'ssCacheStore'
|
||||||
|
});
|
||||||
|
|
||||||
export default @Component({
|
export default @Component({
|
||||||
watch: {
|
watch: {
|
||||||
@@ -46,28 +49,54 @@ class ServerStorage extends Vue {
|
|||||||
this.saveSettings();
|
this.saveSettings();
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
|
this.debouncedNotifySuccess = _.debounce(() => {
|
||||||
|
this.success('Данные синхронизированы с сервером');
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
this.oldProfiles = {};
|
this.oldProfiles = {};
|
||||||
this.oldSettings = {};
|
this.oldSettings = {};
|
||||||
this.oldRecent = {};
|
|
||||||
this.oldRecentLast = {};
|
|
||||||
this.oldRecentLastDiff = {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
try {
|
try {
|
||||||
|
this.cachedRecent = await ssCacheStore.getItem('recent');
|
||||||
|
if (!this.cachedRecent)
|
||||||
|
await this.setCachedRecent({rev: 0, data: {}});
|
||||||
|
|
||||||
|
this.cachedRecentPatch = await ssCacheStore.getItem('recent-patch');
|
||||||
|
if (!this.cachedRecentPatch)
|
||||||
|
await this.setCachedRecentPatch({rev: 0, data: {}});
|
||||||
|
|
||||||
|
this.cachedRecentMod = await ssCacheStore.getItem('recent-mod');
|
||||||
|
if (!this.cachedRecentMod)
|
||||||
|
await this.setCachedRecentMod({rev: 0, data: {}});
|
||||||
|
|
||||||
if (!this.serverStorageKey) {
|
if (!this.serverStorageKey) {
|
||||||
//генерируем новый ключ
|
//генерируем новый ключ
|
||||||
await this.generateNewServerStorageKey();
|
await this.generateNewServerStorageKey();
|
||||||
} else {
|
} else {
|
||||||
await this.serverStorageKeyChanged();
|
await this.serverStorageKeyChanged();
|
||||||
}
|
}
|
||||||
this.oldRecent = _.cloneDeep(bookManager.recent);
|
|
||||||
this.oldRecentLast = _.cloneDeep(bookManager.recentLast) || {};
|
|
||||||
} finally {
|
} finally {
|
||||||
this.inited = true;
|
this.inited = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setCachedRecent(value) {
|
||||||
|
await ssCacheStore.setItem('recent', value);
|
||||||
|
this.cachedRecent = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setCachedRecentPatch(value) {
|
||||||
|
await ssCacheStore.setItem('recent-patch', value);
|
||||||
|
this.cachedRecentPatch = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setCachedRecentMod(value) {
|
||||||
|
await ssCacheStore.setItem('recent-mod', value);
|
||||||
|
this.cachedRecentMod = value;
|
||||||
|
}
|
||||||
|
|
||||||
async generateNewServerStorageKey() {
|
async generateNewServerStorageKey() {
|
||||||
const key = utils.toBase58(utils.randomArray(32));
|
const key = utils.toBase58(utils.randomArray(32));
|
||||||
this.commit('reader/setServerStorageKey', key);
|
this.commit('reader/setServerStorageKey', key);
|
||||||
@@ -95,8 +124,8 @@ class ServerStorage extends Vue {
|
|||||||
await this.loadProfiles(force);
|
await this.loadProfiles(force);
|
||||||
this.checkCurrentProfile();
|
this.checkCurrentProfile();
|
||||||
await this.currentProfileChanged(force);
|
await this.currentProfileChanged(force);
|
||||||
await this.loadRecent(force);
|
const loadSuccess = await this.loadRecent();
|
||||||
if (force)
|
if (loadSuccess && force)
|
||||||
await this.saveRecent();
|
await this.saveRecent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,26 +175,22 @@ class ServerStorage extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notifySuccess() {
|
|
||||||
this.success('Данные синхронизированы с сервером');
|
|
||||||
}
|
|
||||||
|
|
||||||
success(message) {
|
success(message) {
|
||||||
if (this.showServerStorageMessages)
|
if (this.showServerStorageMessages)
|
||||||
this.$notify.success({message});
|
this.$notify.success({message});
|
||||||
}
|
}
|
||||||
|
|
||||||
warning(message) {
|
warning(message) {
|
||||||
if (this.showServerStorageMessages)
|
if (this.showServerStorageMessages && !this.offlineModeActive)
|
||||||
this.$notify.warning({message});
|
this.$notify.warning({message});
|
||||||
}
|
}
|
||||||
|
|
||||||
error(message) {
|
error(message) {
|
||||||
if (this.showServerStorageMessages)
|
if (this.showServerStorageMessages && !this.offlineModeActive)
|
||||||
this.$notify.error({message});
|
this.$notify.error({message});
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadSettings(force) {
|
async loadSettings(force = false, doNotifySuccess = true) {
|
||||||
if (!this.keyInited || !this.serverSyncEnabled || !this.currentProfile)
|
if (!this.keyInited || !this.serverSyncEnabled || !this.currentProfile)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -202,7 +227,8 @@ class ServerStorage extends Vue {
|
|||||||
this.commit('reader/setSettings', sets.data);
|
this.commit('reader/setSettings', sets.data);
|
||||||
this.commit('reader/setSettingsRev', {[setsId]: sets.rev});
|
this.commit('reader/setSettingsRev', {[setsId]: sets.rev});
|
||||||
|
|
||||||
this.notifySuccess();
|
if (doNotifySuccess)
|
||||||
|
this.debouncedNotifySuccess();
|
||||||
} else {
|
} else {
|
||||||
this.warning(`Неверный ответ сервера: ${sets.state}`);
|
this.warning(`Неверный ответ сервера: ${sets.state}`);
|
||||||
}
|
}
|
||||||
@@ -220,32 +246,18 @@ class ServerStorage extends Vue {
|
|||||||
try {
|
try {
|
||||||
const setsId = `settings-${this.currentProfile}`;
|
const setsId = `settings-${this.currentProfile}`;
|
||||||
let result = {state: ''};
|
let result = {state: ''};
|
||||||
let tries = 0;
|
|
||||||
while (result.state != 'success' && tries < maxSetTries) {
|
|
||||||
const oldRev = this.settingsRev[setsId] || 0;
|
|
||||||
try {
|
|
||||||
result = await this.storageSet({[setsId]: {rev: oldRev + 1, data: this.settings}});
|
|
||||||
} catch(e) {
|
|
||||||
this.savingSettings = false;
|
|
||||||
this.error(`Ошибка соединения с сервером (${e.message}). Данные не сохранены и могут быть перезаписаны.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.state == 'reject') {
|
const oldRev = this.settingsRev[setsId] || 0;
|
||||||
await this.loadSettings(true);
|
try {
|
||||||
const newSettings = utils.applyObjDiff(this.settings, diff);
|
result = await this.storageSet({[setsId]: {rev: oldRev + 1, data: this.settings}});
|
||||||
this.commit('reader/setSettings', newSettings);
|
} catch(e) {
|
||||||
}
|
this.error(`Ошибка соединения с сервером (${e.message}). Данные не сохранены и могут быть перезаписаны.`);
|
||||||
|
|
||||||
tries++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tries >= maxSetTries) {
|
if (result.state == 'reject') {
|
||||||
//отменять изменения не будем, просто предупредим
|
await this.loadSettings(true, false);
|
||||||
//this.commit('reader/setSettings', this.oldSettings);
|
this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
|
||||||
console.error(result);
|
} else if (result.state == 'success') {
|
||||||
this.error('Не удалось отправить настройки на сервер. Данные не сохранены и могут быть перезаписаны.');
|
|
||||||
} else {
|
|
||||||
this.oldSettings = _.cloneDeep(this.settings);
|
this.oldSettings = _.cloneDeep(this.settings);
|
||||||
this.commit('reader/setSettingsRev', {[setsId]: this.settingsRev[setsId] + 1});
|
this.commit('reader/setSettingsRev', {[setsId]: this.settingsRev[setsId] + 1});
|
||||||
}
|
}
|
||||||
@@ -254,7 +266,7 @@ class ServerStorage extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadProfiles(force) {
|
async loadProfiles(force = false, doNotifySuccess = true) {
|
||||||
if (!this.keyInited || !this.serverSyncEnabled)
|
if (!this.keyInited || !this.serverSyncEnabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -289,8 +301,10 @@ class ServerStorage extends Vue {
|
|||||||
this.oldProfiles = _.cloneDeep(prof.data);
|
this.oldProfiles = _.cloneDeep(prof.data);
|
||||||
this.commit('reader/setProfiles', prof.data);
|
this.commit('reader/setProfiles', prof.data);
|
||||||
this.commit('reader/setProfilesRev', prof.rev);
|
this.commit('reader/setProfilesRev', prof.rev);
|
||||||
|
this.checkCurrentProfile();
|
||||||
|
|
||||||
this.notifySuccess();
|
if (doNotifySuccess)
|
||||||
|
this.debouncedNotifySuccess();
|
||||||
} else {
|
} else {
|
||||||
this.warning(`Неверный ответ сервера: ${prof.state}`);
|
this.warning(`Неверный ответ сервера: ${prof.state}`);
|
||||||
}
|
}
|
||||||
@@ -304,7 +318,7 @@ class ServerStorage extends Vue {
|
|||||||
if (utils.isEmptyObjDiff(diff))
|
if (utils.isEmptyObjDiff(diff))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
//обнуляются профили во время разработки, подстраховка
|
//обнуляются профили во время разработки при hotReload, подстраховка
|
||||||
if (!this.$store.state.reader.allowProfilesSave) {
|
if (!this.$store.state.reader.allowProfilesSave) {
|
||||||
console.error('Сохранение профилей не санкционировано');
|
console.error('Сохранение профилей не санкционировано');
|
||||||
return;
|
return;
|
||||||
@@ -313,33 +327,16 @@ class ServerStorage extends Vue {
|
|||||||
this.savingProfiles = true;
|
this.savingProfiles = true;
|
||||||
try {
|
try {
|
||||||
let result = {state: ''};
|
let result = {state: ''};
|
||||||
let tries = 0;
|
try {
|
||||||
while (result.state != 'success' && tries < maxSetTries) {
|
result = await this.storageSet({profiles: {rev: this.profilesRev + 1, data: this.profiles}});
|
||||||
try {
|
} catch(e) {
|
||||||
result = await this.storageSet({profiles: {rev: this.profilesRev + 1, data: this.profiles}});
|
this.error(`Ошибка соединения с сервером (${e.message}). Данные не сохранены и могут быть перезаписаны.`);
|
||||||
} catch(e) {
|
|
||||||
this.savingProfiles = false;
|
|
||||||
this.commit('reader/setProfiles', this.oldProfiles);
|
|
||||||
this.checkCurrentProfile();
|
|
||||||
this.error(`Ошибка соединения с сервером: (${e.message}). Изменения отменены.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.state == 'reject') {
|
|
||||||
await this.loadProfiles(true);
|
|
||||||
const newProfiles = utils.applyObjDiff(this.profiles, diff);
|
|
||||||
this.commit('reader/setProfiles', newProfiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
tries++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tries >= maxSetTries) {
|
if (result.state == 'reject') {
|
||||||
this.commit('reader/setProfiles', this.oldProfiles);
|
await this.loadProfiles(true, false);
|
||||||
this.checkCurrentProfile();
|
this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
|
||||||
console.error(result);
|
} else if (result.state == 'success') {
|
||||||
this.error('Не удалось отправить данные на сервер. Изменения отменены.');
|
|
||||||
} else {
|
|
||||||
this.oldProfiles = _.cloneDeep(this.profiles);
|
this.oldProfiles = _.cloneDeep(this.profiles);
|
||||||
this.commit('reader/setProfilesRev', this.profilesRev + 1);
|
this.commit('reader/setProfilesRev', this.profilesRev + 1);
|
||||||
}
|
}
|
||||||
@@ -348,252 +345,193 @@ class ServerStorage extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadRecent(force) {
|
async loadRecent(skipRevCheck = false, doNotifySuccess = true) {
|
||||||
if (!this.keyInited || !this.serverSyncEnabled)
|
if (!this.keyInited || !this.serverSyncEnabled || this.loadingRecent)
|
||||||
return;
|
return;
|
||||||
|
this.loadingRecent = true;
|
||||||
const oldRev = bookManager.recentRev;
|
try {
|
||||||
const oldLastRev = bookManager.recentLastRev;
|
//проверим ревизию на сервере
|
||||||
const oldLastDiffRev = bookManager.recentLastDiffRev;
|
let query = {recent: {}, recentPatch: {}, recentMod: {}};
|
||||||
//проверим ревизию на сервере
|
let revs = null;
|
||||||
let revs = null;
|
if (!skipRevCheck) {
|
||||||
if (!force) {
|
try {
|
||||||
try {
|
revs = await this.storageCheck(query);
|
||||||
revs = await this.storageCheck({recent: {}, recentLast: {}, recentLastDiff: {}});
|
if (revs.state == 'success') {
|
||||||
if (revs.state == 'success' && revs.items.recent.rev == oldRev &&
|
if (revs.items.recent.rev != this.cachedRecent.rev) {
|
||||||
revs.items.recentLast.rev == oldLastRev &&
|
//no changes
|
||||||
revs.items.recentLastDiff.rev == oldLastDiffRev) {
|
} else if (revs.items.recentPatch.rev != this.cachedRecentPatch.rev) {
|
||||||
|
query = {recentPatch: {}, recentMod: {}};
|
||||||
|
} else if (revs.items.recentMod.rev != this.cachedRecentMod.rev) {
|
||||||
|
query = {recentMod: {}};
|
||||||
|
} else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
this.error(`Ошибка соединения с сервером: ${e.message}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch(e) {
|
|
||||||
this.error(`Ошибка соединения с сервером: ${e.message}`);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (force || revs.items.recent.rev != oldRev) {
|
|
||||||
let recent = null;
|
let recent = null;
|
||||||
try {
|
try {
|
||||||
recent = await this.storageGet({recent: {}});
|
recent = await this.storageGet(query);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
this.error(`Ошибка соединения с сервером: ${e.message}`);
|
this.error(`Ошибка соединения с сервером: ${e.message}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recent.state == 'success') {
|
if (recent.state == 'success') {
|
||||||
recent = recent.items.recent;
|
let newRecent = recent.items.recent;
|
||||||
|
let newRecentPatch = recent.items.recentPatch;
|
||||||
|
let newRecentMod = recent.items.recentMod;
|
||||||
|
|
||||||
if (recent.rev == 0)
|
if (!newRecent) {
|
||||||
recent.data = {};
|
newRecent = _.cloneDeep(this.cachedRecent);
|
||||||
|
}
|
||||||
|
if (!newRecentPatch) {
|
||||||
|
newRecentPatch = _.cloneDeep(this.cachedRecentPatch);
|
||||||
|
}
|
||||||
|
if (!newRecentMod) {
|
||||||
|
newRecentMod = _.cloneDeep(this.cachedRecentMod);
|
||||||
|
}
|
||||||
|
|
||||||
this.oldRecent = _.cloneDeep(recent.data);
|
if (newRecent.rev == 0) newRecent.data = {};
|
||||||
await bookManager.setRecent(recent.data);
|
if (newRecentPatch.rev == 0) newRecentPatch.data = {};
|
||||||
await bookManager.setRecentRev(recent.rev);
|
if (newRecentMod.rev == 0) newRecentMod.data = {};
|
||||||
|
|
||||||
|
let result = Object.assign({}, newRecent.data, newRecentPatch.data);
|
||||||
|
|
||||||
|
const md = newRecentMod.data;
|
||||||
|
if (md.key && result[md.key])
|
||||||
|
result[md.key] = utils.applyObjDiff(result[md.key], md.mod, true);
|
||||||
|
|
||||||
|
if (!bookManager.loaded) {
|
||||||
|
this.warning('Ожидание загрузки списка книг перед синхронизацией');
|
||||||
|
while (!bookManager.loaded) await utils.sleep(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newRecent.rev != this.cachedRecent.rev)
|
||||||
|
await this.setCachedRecent(newRecent);
|
||||||
|
if (newRecentPatch.rev != this.cachedRecentPatch.rev)
|
||||||
|
await this.setCachedRecentPatch(newRecentPatch);
|
||||||
|
if (newRecentMod.rev != this.cachedRecentMod.rev)
|
||||||
|
await this.setCachedRecentMod(newRecentMod);
|
||||||
|
|
||||||
|
await bookManager.setRecent(result);
|
||||||
} else {
|
} else {
|
||||||
this.warning(`Неверный ответ сервера: ${recent.state}`);
|
this.warning(`Неверный ответ сервера: ${recent.state}`);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (force || revs.items.recentLast.rev != oldLastRev || revs.items.recentLastDiff.rev != oldLastDiffRev) {
|
|
||||||
let recentLast = null;
|
|
||||||
try {
|
|
||||||
recentLast = await this.storageGet({recentLast: {}, recentLastDiff: {}});
|
|
||||||
} catch(e) {
|
|
||||||
this.error(`Ошибка соединения с сервером: ${e.message}`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recentLast.state == 'success') {
|
if (doNotifySuccess)
|
||||||
const recentLastDiff = recentLast.items.recentLastDiff;
|
this.debouncedNotifySuccess();
|
||||||
recentLast = recentLast.items.recentLast;
|
} finally {
|
||||||
|
this.loadingRecent = false;
|
||||||
if (recentLast.rev == 0)
|
|
||||||
recentLast.data = {};
|
|
||||||
if (recentLastDiff.rev == 0)
|
|
||||||
recentLastDiff.data = {};
|
|
||||||
|
|
||||||
this.oldRecentLastDiff = _.cloneDeep(recentLastDiff.data);
|
|
||||||
this.oldRecentLast = _.cloneDeep(recentLast.data);
|
|
||||||
|
|
||||||
recentLast.data = utils.applyObjDiff(recentLast.data, recentLastDiff.data);
|
|
||||||
|
|
||||||
await bookManager.setRecentLast(recentLast.data);
|
|
||||||
await bookManager.setRecentLastRev(recentLast.rev);
|
|
||||||
await bookManager.setRecentLastDiffRev(recentLastDiff.rev);
|
|
||||||
} else {
|
|
||||||
this.warning(`Неверный ответ сервера: ${recentLast.state}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
this.notifySuccess();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveRecent() {
|
async saveRecent(itemKey, recurse) {
|
||||||
|
while (!this.inited || this.savingRecent)
|
||||||
|
await utils.sleep(100);
|
||||||
|
|
||||||
if (!this.keyInited || !this.serverSyncEnabled || this.savingRecent)
|
if (!this.keyInited || !this.serverSyncEnabled || this.savingRecent)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const bm = bookManager;
|
|
||||||
|
|
||||||
const diff = utils.getObjDiff(this.oldRecent, bm.recent);
|
|
||||||
if (utils.isEmptyObjDiff(diff))
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.savingRecent = true;
|
this.savingRecent = true;
|
||||||
try {
|
try {
|
||||||
let result = {state: ''};
|
const bm = bookManager;
|
||||||
let tries = 0;
|
|
||||||
while (result.state != 'success' && tries < maxSetTries) {
|
|
||||||
try {
|
|
||||||
result = await this.storageSet({recent: {rev: bm.recentRev + 1, data: bm.recent}});
|
|
||||||
} catch(e) {
|
|
||||||
this.savingRecent = false;
|
|
||||||
this.error(`Ошибка соединения с сервером: (${e.message}). Изменения не сохранены.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.state == 'reject') {
|
let needSaveRecent = false;
|
||||||
await this.loadRecent(true);
|
let needSaveRecentPatch = false;
|
||||||
//похоже это лишнее
|
let needSaveRecentMod = false;
|
||||||
/*const newRecent = utils.applyObjDiff(bm.recent, diff);
|
|
||||||
await bm.setRecent(newRecent);*/
|
|
||||||
}
|
|
||||||
|
|
||||||
tries++;
|
//newRecentMod
|
||||||
|
let newRecentMod = {};
|
||||||
|
if (itemKey && this.cachedRecentPatch.data[itemKey] && this.prevItemKey == itemKey) {
|
||||||
|
newRecentMod = _.cloneDeep(this.cachedRecentMod);
|
||||||
|
newRecentMod.rev++;
|
||||||
|
|
||||||
|
newRecentMod.data.key = itemKey;
|
||||||
|
newRecentMod.data.mod = utils.getObjDiff(this.cachedRecentPatch.data[itemKey], bm.recent[itemKey]);
|
||||||
|
needSaveRecentMod = true;
|
||||||
|
}
|
||||||
|
this.prevItemKey = itemKey;
|
||||||
|
|
||||||
|
//newRecentPatch
|
||||||
|
let newRecentPatch = {};
|
||||||
|
if (itemKey && !needSaveRecentMod) {
|
||||||
|
newRecentPatch = _.cloneDeep(this.cachedRecentPatch);
|
||||||
|
newRecentPatch.rev++;
|
||||||
|
newRecentPatch.data[itemKey] = _.cloneDeep(bm.recent[itemKey]);
|
||||||
|
|
||||||
|
let applyMod = this.cachedRecentMod.data;
|
||||||
|
if (applyMod && applyMod.key && newRecentPatch.data[applyMod.key])
|
||||||
|
newRecentPatch.data[applyMod.key] = utils.applyObjDiff(newRecentPatch.data[applyMod.key], applyMod.mod, true);
|
||||||
|
|
||||||
|
newRecentMod = {rev: this.cachedRecentMod.rev + 1, data: {}};
|
||||||
|
needSaveRecentPatch = true;
|
||||||
|
needSaveRecentMod = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tries >= maxSetTries) {
|
//newRecent
|
||||||
console.error(result);
|
let newRecent = {};
|
||||||
this.error('Не удалось отправить данные на сервер. Данные не сохранены и могут быть перезаписаны.');
|
if (!itemKey || (needSaveRecentPatch && Object.keys(newRecentPatch.data).length > 10)) {
|
||||||
|
//ждем весь bm.recent
|
||||||
|
while (!bookManager.loaded)
|
||||||
|
await utils.sleep(100);
|
||||||
|
|
||||||
|
newRecent = {rev: this.cachedRecent.rev + 1, data: _.cloneDeep(bm.recent)};
|
||||||
|
newRecentPatch = {rev: this.cachedRecentPatch.rev + 1, data: {}};
|
||||||
|
newRecentMod = {rev: this.cachedRecentMod.rev + 1, data: {}};
|
||||||
|
needSaveRecent = true;
|
||||||
|
needSaveRecentPatch = true;
|
||||||
|
needSaveRecentMod = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//query
|
||||||
|
let query = {};
|
||||||
|
if (needSaveRecent) {
|
||||||
|
query = {recent: newRecent, recentPatch: newRecentPatch, recentMod: newRecentMod};
|
||||||
|
} else if (needSaveRecentPatch) {
|
||||||
|
query = {recentPatch: newRecentPatch, recentMod: newRecentMod};
|
||||||
} else {
|
} else {
|
||||||
this.oldRecent = _.cloneDeep(bm.recent);
|
query = {recentMod: newRecentMod};
|
||||||
await bm.setRecentRev(bm.recentRev + 1);
|
}
|
||||||
await this.saveRecentLast(true);
|
|
||||||
|
//сохранение
|
||||||
|
let result = {state: ''};
|
||||||
|
|
||||||
|
try {
|
||||||
|
result = await this.storageSet(query);
|
||||||
|
} catch(e) {
|
||||||
|
this.error(`Ошибка соединения с сервером (${e.message}). Данные не сохранены и могут быть перезаписаны.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.state == 'reject') {
|
||||||
|
|
||||||
|
const res = await this.loadRecent(false, false);
|
||||||
|
|
||||||
|
if (res)
|
||||||
|
this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
|
||||||
|
if (!recurse && itemKey) {
|
||||||
|
this.savingRecent = false;
|
||||||
|
this.saveRecent(itemKey, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (result.state == 'success') {
|
||||||
|
if (needSaveRecent && newRecent.rev)
|
||||||
|
await this.setCachedRecent(newRecent);
|
||||||
|
if (needSaveRecentPatch && newRecentPatch.rev)
|
||||||
|
await this.setCachedRecentPatch(newRecentPatch);
|
||||||
|
if (needSaveRecentMod && newRecentMod.rev)
|
||||||
|
await this.setCachedRecentMod(newRecentMod);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this.savingRecent = false;
|
this.savingRecent = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveRecentLast(force = false) {
|
|
||||||
if (!this.keyInited || !this.serverSyncEnabled || this.savingRecentLast)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const bm = bookManager;
|
|
||||||
let recentLast = bm.recentLast;
|
|
||||||
recentLast = (recentLast ? recentLast : {});
|
|
||||||
let lastRev = bm.recentLastRev;
|
|
||||||
|
|
||||||
const diff = utils.getObjDiff(this.oldRecentLast, recentLast);
|
|
||||||
if (utils.isEmptyObjDiff(diff))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (this.oldRecentLast.key == recentLast.key && JSON.stringify(recentLast) > JSON.stringify(diff)) {
|
|
||||||
await this.saveRecentLastDiff(diff, force);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.savingRecentLast = true;
|
|
||||||
try {
|
|
||||||
let result = {state: ''};
|
|
||||||
let tries = 0;
|
|
||||||
while (result.state != 'success' && tries < maxSetTries) {
|
|
||||||
if (force) {
|
|
||||||
try {
|
|
||||||
const revs = await this.storageCheck({recentLast: {}});
|
|
||||||
if (revs.items.recentLast.rev)
|
|
||||||
lastRev = revs.items.recentLast.rev;
|
|
||||||
} catch(e) {
|
|
||||||
this.error(`Ошибка соединения с сервером: ${e.message}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
result = await this.storageSet({recentLast: {rev: lastRev + 1, data: recentLast}}, force);
|
|
||||||
} catch(e) {
|
|
||||||
this.savingRecentLast = false;
|
|
||||||
this.error(`Ошибка соединения с сервером: (${e.message}). Изменения не сохранены.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.state == 'reject') {
|
|
||||||
await this.loadRecent(false);
|
|
||||||
this.savingRecentLast = false;//!!!
|
|
||||||
return;//!!!
|
|
||||||
}
|
|
||||||
|
|
||||||
tries++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tries >= maxSetTries) {
|
|
||||||
console.error(result);
|
|
||||||
this.error('Не удалось отправить данные на сервер. Данные не сохранены и могут быть перезаписаны.');
|
|
||||||
} else {
|
|
||||||
this.oldRecentLast = _.cloneDeep(recentLast);
|
|
||||||
await bm.setRecentLastRev(lastRev + 1);
|
|
||||||
await this.saveRecentLastDiff({}, true);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
this.savingRecentLast = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async saveRecentLastDiff(diff, force = false) {
|
|
||||||
if (!this.keyInited || !this.serverSyncEnabled || this.savingRecentLastDiff)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const bm = bookManager;
|
|
||||||
let lastRev = bm.recentLastDiffRev;
|
|
||||||
|
|
||||||
const d = utils.getObjDiff(this.oldRecentLastDiff, diff);
|
|
||||||
if (utils.isEmptyObjDiff(d))
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.savingRecentLastDiff = true;
|
|
||||||
try {
|
|
||||||
let result = {state: ''};
|
|
||||||
let tries = 0;
|
|
||||||
while (result.state != 'success' && tries < maxSetTries) {
|
|
||||||
if (force) {
|
|
||||||
try {
|
|
||||||
const revs = await this.storageCheck({recentLastDiff: {}});
|
|
||||||
if (revs.items.recentLastDiff.rev)
|
|
||||||
lastRev = revs.items.recentLastDiff.rev;
|
|
||||||
} catch(e) {
|
|
||||||
this.error(`Ошибка соединения с сервером: ${e.message}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
result = await this.storageSet({recentLastDiff: {rev: lastRev + 1, data: diff}}, force);
|
|
||||||
} catch(e) {
|
|
||||||
this.savingRecentLastDiff = false;
|
|
||||||
this.error(`Ошибка соединения с сервером: (${e.message}). Изменения не сохранены.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.state == 'reject') {
|
|
||||||
await this.loadRecent(false);
|
|
||||||
this.savingRecentLastDiff = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
tries++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tries >= maxSetTries) {
|
|
||||||
console.error(result);
|
|
||||||
this.error('Не удалось отправить данные на сервер. Данные не сохранены и могут быть перезаписаны.');
|
|
||||||
} else {
|
|
||||||
this.oldRecentLastDiff = _.cloneDeep(diff);
|
|
||||||
await bm.setRecentLastDiffRev(lastRev + 1);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
this.savingRecentLastDiff = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async storageCheck(items) {
|
async storageCheck(items) {
|
||||||
return await this.storageApi('check', items);
|
return await this.storageApi('check', items);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="main" class="main" @click="close">
|
<Window ref="window" height="140px" max-width="600px" :top-shift="-50" @close="close">
|
||||||
<div class="mainWindow" @click.stop>
|
<template slot="header">
|
||||||
<Window @close="close">
|
Установить позицию
|
||||||
<template slot="header">
|
</template>
|
||||||
Установить позицию
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div class="slider">
|
<div class="slider">
|
||||||
<el-slider v-model="sliderValue" :max="sliderMax" :format-tooltip="formatTooltip"></el-slider>
|
<el-slider v-model="sliderValue" :max="sliderMax" :format-tooltip="formatTooltip"></el-slider>
|
||||||
</div>
|
|
||||||
</Window>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Window>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -43,6 +39,8 @@ class SetPositionPage extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init(sliderValue, sliderMax) {
|
init(sliderValue, sliderMax) {
|
||||||
|
this.$refs.window.init();
|
||||||
|
|
||||||
this.sliderMax = sliderMax;
|
this.sliderMax = sliderMax;
|
||||||
this.sliderValue = sliderValue;
|
this.sliderValue = sliderValue;
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
@@ -70,26 +68,6 @@ class SetPositionPage extends Vue {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<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%;
|
|
||||||
max-width: 600px;
|
|
||||||
height: 140px;
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
top: -50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider {
|
.slider {
|
||||||
margin: 20px;
|
margin: 20px;
|
||||||
background-color: #efefef;
|
background-color: #efefef;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -350,20 +350,18 @@ export default class DrawHelper {
|
|||||||
page2.style.background = backgroundColor;
|
page2.style.background = backgroundColor;
|
||||||
|
|
||||||
if (isDown) {
|
if (isDown) {
|
||||||
page2.style.transformOrigin = '10%';
|
page2.style.transformOrigin = '5%';
|
||||||
await sleep(30);
|
await sleep(30);
|
||||||
|
|
||||||
page2.style.transformOrigin = '0%';
|
|
||||||
page2.style.transition = `${duration}ms ease-in-out`;
|
page2.style.transition = `${duration}ms ease-in-out`;
|
||||||
page2.style.transform = `rotateY(-120deg)`;
|
page2.style.transform = `rotateY(-120deg) translateX(${this.w/4}px)`;
|
||||||
await animation2Finish(duration);
|
await animation2Finish(duration);
|
||||||
} else {
|
} else {
|
||||||
page2.style.transformOrigin = '90%';
|
page2.style.transformOrigin = '95%';
|
||||||
await sleep(30);
|
await sleep(30);
|
||||||
|
|
||||||
page2.style.transformOrigin = '100%';
|
|
||||||
page2.style.transition = `${duration}ms ease-in-out`;
|
page2.style.transition = `${duration}ms ease-in-out`;
|
||||||
page2.style.transform = `rotateY(120deg)`;
|
page2.style.transform = `rotateY(120deg) translateX(-${this.w/4}px)`;
|
||||||
await animation2Finish(duration);
|
await animation2Finish(duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-show="clickControl" ref="layoutEvents" class="layout events" @mousedown.prevent.stop="onMouseDown" @mouseup.prevent.stop="onMouseUp"
|
<div v-show="clickControl" ref="layoutEvents" class="layout events" @mousedown.prevent.stop="onMouseDown" @mouseup.prevent.stop="onMouseUp"
|
||||||
@wheel.prevent.stop="onMouseWheel"
|
@wheel.prevent.stop="onMouseWheel"
|
||||||
@touchstart.stop="onTouchStart" @touchend.stop="onTouchEnd" @touchcancel.prevent.stop="onTouchCancel"
|
@touchstart.stop="onTouchStart" @touchend.stop="onTouchEnd" @touchmove.stop="onTouchMove" @touchcancel.prevent.stop="onTouchCancel"
|
||||||
oncontextmenu="return false;">
|
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>
|
||||||
@@ -131,7 +131,6 @@ class TextPage extends Vue {
|
|||||||
}, 10);
|
}, 10);
|
||||||
|
|
||||||
this.$root.$on('resize', () => {this.$nextTick(this.onResize)});
|
this.$root.$on('resize', () => {this.$nextTick(this.onResize)});
|
||||||
this.mobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
@@ -221,6 +220,7 @@ class TextPage extends Vue {
|
|||||||
this.parsed.showInlineImagesInCenter = this.showInlineImagesInCenter;
|
this.parsed.showInlineImagesInCenter = this.showInlineImagesInCenter;
|
||||||
this.parsed.imageHeightLines = this.imageHeightLines;
|
this.parsed.imageHeightLines = this.imageHeightLines;
|
||||||
this.parsed.imageFitWidth = this.imageFitWidth;
|
this.parsed.imageFitWidth = this.imageFitWidth;
|
||||||
|
this.parsed.compactTextPerc = this.compactTextPerc;
|
||||||
}
|
}
|
||||||
|
|
||||||
//scrolling page
|
//scrolling page
|
||||||
@@ -477,7 +477,7 @@ class TextPage extends Vue {
|
|||||||
|
|
||||||
generateWaitingFunc(waitingHandlerName, stopPropertyName) {
|
generateWaitingFunc(waitingHandlerName, stopPropertyName) {
|
||||||
const func = (timeout) => {
|
const func = (timeout) => {
|
||||||
return new Promise(async(resolve) => {
|
return new Promise((resolve, reject) => { (async() => {
|
||||||
this[waitingHandlerName] = resolve;
|
this[waitingHandlerName] = resolve;
|
||||||
let wait = (timeout + 201)/100;
|
let wait = (timeout + 201)/100;
|
||||||
while (wait > 0 && !this[stopPropertyName]) {
|
while (wait > 0 && !this[stopPropertyName]) {
|
||||||
@@ -485,7 +485,7 @@ class TextPage extends Vue {
|
|||||||
await sleep(100);
|
await sleep(100);
|
||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
});
|
})().catch(reject); });
|
||||||
};
|
};
|
||||||
return func;
|
return func;
|
||||||
}
|
}
|
||||||
@@ -877,6 +877,14 @@ class TextPage extends Vue {
|
|||||||
this.$emit('tool-bar-toggle');
|
this.$emit('tool-bar-toggle');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
doScrollingToggle() {
|
||||||
|
this.$emit('scrolling-toggle');
|
||||||
|
}
|
||||||
|
|
||||||
|
doFullScreenToggle() {
|
||||||
|
this.$emit('full-screen-toogle');
|
||||||
|
}
|
||||||
|
|
||||||
async doFontSizeInc() {
|
async doFontSizeInc() {
|
||||||
if (!this.settingsChanging) {
|
if (!this.settingsChanging) {
|
||||||
this.settingsChanging = true;
|
this.settingsChanging = true;
|
||||||
@@ -968,7 +976,7 @@ class TextPage extends Vue {
|
|||||||
case 'Enter':
|
case 'Enter':
|
||||||
case 'Backquote'://`
|
case 'Backquote'://`
|
||||||
case 'KeyF':
|
case 'KeyF':
|
||||||
this.$emit('full-screen-toogle');
|
this.doFullScreenToggle();
|
||||||
break;
|
break;
|
||||||
case 'Tab':
|
case 'Tab':
|
||||||
case 'KeyQ':
|
case 'KeyQ':
|
||||||
@@ -1006,57 +1014,100 @@ class TextPage extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onTouchStart(event) {
|
onTouchStart(event) {
|
||||||
if (!this.mobile)
|
if (!this.$isMobileDevice)
|
||||||
return;
|
return;
|
||||||
this.endClickRepeat();
|
this.endClickRepeat();
|
||||||
|
|
||||||
if (event.touches.length == 1) {
|
if (event.touches.length == 1) {
|
||||||
const touch = event.touches[0];
|
const touch = event.touches[0];
|
||||||
const rect = event.target.getBoundingClientRect();
|
const rect = event.target.getBoundingClientRect();
|
||||||
const x = touch.pageX - rect.left;
|
const x = touch.pageX - rect.left;
|
||||||
const y = touch.pageY - rect.top;
|
const y = touch.pageY - rect.top;
|
||||||
if (this.handleClick(x, y)) {
|
const hc = this.handleClick(x, y, new Set(['Menu']));
|
||||||
this.repDoing = true;
|
if (hc) {
|
||||||
this.debouncedStartClickRepeat(x, y);
|
if (hc != 'Menu') {
|
||||||
|
this.repDoing = true;
|
||||||
|
this.debouncedStartClickRepeat(x, y);
|
||||||
|
} else {
|
||||||
|
this.startTouch = {x, y};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onTouchEnd() {
|
onTouchMove(event) {
|
||||||
if (!this.mobile)
|
if (this.startTouch) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onTouchEnd(event) {
|
||||||
|
if (!this.$isMobileDevice)
|
||||||
return;
|
return;
|
||||||
this.endClickRepeat();
|
this.endClickRepeat();
|
||||||
|
|
||||||
|
if (event.changedTouches.length == 1) {
|
||||||
|
const touch = event.changedTouches[0];
|
||||||
|
const rect = event.target.getBoundingClientRect();
|
||||||
|
const x = touch.pageX - rect.left;
|
||||||
|
const y = touch.pageY - rect.top;
|
||||||
|
if (this.startTouch) {
|
||||||
|
const dy = this.startTouch.y - y;
|
||||||
|
const dx = this.startTouch.x - x;
|
||||||
|
const moveDelta = 30;
|
||||||
|
const touchDelta = 15;
|
||||||
|
if (dy > 0 && Math.abs(dy) >= moveDelta && Math.abs(dy) > Math.abs(dx)) {
|
||||||
|
//движение вверх
|
||||||
|
this.doFullScreenToggle();
|
||||||
|
} else if (dy < 0 && Math.abs(dy) >= moveDelta && Math.abs(dy) > Math.abs(dx)) {
|
||||||
|
//движение вниз
|
||||||
|
this.doScrollingToggle();
|
||||||
|
} else if (dx > 0 && Math.abs(dx) >= moveDelta && Math.abs(dy) < Math.abs(dx)) {
|
||||||
|
//движение влево
|
||||||
|
this.doScrollingSpeedDown();
|
||||||
|
} else if (dx < 0 && Math.abs(dx) >= moveDelta && Math.abs(dy) < Math.abs(dx)) {
|
||||||
|
//движение вправо
|
||||||
|
this.doScrollingSpeedUp();
|
||||||
|
} else if (Math.abs(dy) < touchDelta && Math.abs(dx) < touchDelta) {
|
||||||
|
this.doToolBarToggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.startTouch = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onTouchCancel() {
|
onTouchCancel() {
|
||||||
if (!this.mobile)
|
if (!this.$isMobileDevice)
|
||||||
return;
|
return;
|
||||||
this.endClickRepeat();
|
this.endClickRepeat();
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseDown(event) {
|
onMouseDown(event) {
|
||||||
if (this.mobile)
|
if (this.$isMobileDevice)
|
||||||
return;
|
return;
|
||||||
this.endClickRepeat();
|
this.endClickRepeat();
|
||||||
if (event.button == 0) {
|
if (event.button == 0) {
|
||||||
if (this.handleClick(event.offsetX, event.offsetY)) {
|
const hc = this.handleClick(event.offsetX, event.offsetY);
|
||||||
|
if (hc && hc != 'Menu') {
|
||||||
this.repDoing = true;
|
this.repDoing = true;
|
||||||
this.debouncedStartClickRepeat(event.offsetX, event.offsetY);
|
this.debouncedStartClickRepeat(event.offsetX, event.offsetY);
|
||||||
}
|
}
|
||||||
} else if (event.button == 1) {
|
} else if (event.button == 1) {
|
||||||
this.$emit('scrolling-toggle');
|
this.doScrollingToggle();
|
||||||
} else if (event.button == 2) {
|
} else if (event.button == 2) {
|
||||||
this.doToolBarToggle();
|
this.doToolBarToggle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseUp() {
|
onMouseUp() {
|
||||||
if (this.mobile)
|
if (this.$isMobileDevice)
|
||||||
return;
|
return;
|
||||||
this.endClickRepeat();
|
this.endClickRepeat();
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseWheel(event) {
|
onMouseWheel(event) {
|
||||||
if (this.mobile)
|
if (this.$isMobileDevice)
|
||||||
return;
|
return;
|
||||||
if (event.deltaY > 0) {
|
if (event.deltaY > 0) {
|
||||||
this.doDown();
|
this.doDown();
|
||||||
@@ -1074,7 +1125,7 @@ class TextPage extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick(pointX, pointY) {
|
getClickAction(pointX, pointY) {
|
||||||
const w = pointX/this.realWidth*100;
|
const w = pointX/this.realWidth*100;
|
||||||
const h = pointY/this.realHeight*100;
|
const h = pointY/this.realHeight*100;
|
||||||
|
|
||||||
@@ -1090,27 +1141,35 @@ class TextPage extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (action) {
|
return action;
|
||||||
case 'Down' ://Down
|
}
|
||||||
this.doDown();
|
|
||||||
break;
|
handleClick(pointX, pointY, exclude) {
|
||||||
case 'Up' ://Up
|
const action = this.getClickAction(pointX, pointY);
|
||||||
this.doUp();
|
|
||||||
break;
|
if (!exclude || !exclude.has(action)) {
|
||||||
case 'PgDown' ://PgDown
|
switch (action) {
|
||||||
this.doPageDown();
|
case 'Down' ://Down
|
||||||
break;
|
this.doDown();
|
||||||
case 'PgUp' ://PgUp
|
break;
|
||||||
this.doPageUp();
|
case 'Up' ://Up
|
||||||
break;
|
this.doUp();
|
||||||
case 'Menu' :
|
break;
|
||||||
this.doToolBarToggle();
|
case 'PgDown' ://PgDown
|
||||||
break;
|
this.doPageDown();
|
||||||
default :
|
break;
|
||||||
// Nothing
|
case 'PgUp' ://PgUp
|
||||||
|
this.doPageUp();
|
||||||
|
break;
|
||||||
|
case 'Menu' :
|
||||||
|
this.doToolBarToggle();
|
||||||
|
break;
|
||||||
|
default :
|
||||||
|
// Nothing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (action && action != 'Menu');
|
return action;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import he from 'he';
|
import he from 'he';
|
||||||
import sax from '../../../../server/core/BookConverter/sax';
|
import sax from '../../../../server/core/sax';
|
||||||
import {sleep} from '../../../share/utils';
|
import {sleep} from '../../../share/utils';
|
||||||
|
|
||||||
const maxImageLineCount = 100;
|
const maxImageLineCount = 100;
|
||||||
@@ -32,9 +32,6 @@ export default class BookParser {
|
|||||||
|
|
||||||
//defaults
|
//defaults
|
||||||
let fb2 = {
|
let fb2 = {
|
||||||
firstName: '',
|
|
||||||
middleName: '',
|
|
||||||
lastName: '',
|
|
||||||
bookTitle: '',
|
bookTitle: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -67,7 +64,7 @@ export default class BookParser {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
const getImageDimensions = (binaryId, binaryType, data) => {
|
const getImageDimensions = (binaryId, binaryType, data) => {
|
||||||
return new Promise (async(resolve, reject) => {
|
return new Promise ((resolve, reject) => { (async() => {
|
||||||
const i = new Image();
|
const i = new Image();
|
||||||
let resolved = false;
|
let resolved = false;
|
||||||
i.onload = () => {
|
i.onload = () => {
|
||||||
@@ -81,19 +78,17 @@ export default class BookParser {
|
|||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
i.onerror = (e) => {
|
i.onerror = reject;
|
||||||
reject(e);
|
|
||||||
};
|
|
||||||
|
|
||||||
i.src = `data:${binaryType};base64,${data}`;
|
i.src = `data:${binaryType};base64,${data}`;
|
||||||
await sleep(30*1000);
|
await sleep(30*1000);
|
||||||
if (!resolved)
|
if (!resolved)
|
||||||
reject('Не удалось получить размер изображения');
|
reject('Не удалось получить размер изображения');
|
||||||
});
|
})().catch(reject); });
|
||||||
};
|
};
|
||||||
|
|
||||||
const getExternalImageDimensions = (src) => {
|
const getExternalImageDimensions = (src) => {
|
||||||
return new Promise (async(resolve, reject) => {
|
return new Promise ((resolve, reject) => { (async() => {
|
||||||
const i = new Image();
|
const i = new Image();
|
||||||
let resolved = false;
|
let resolved = false;
|
||||||
i.onload = () => {
|
i.onload = () => {
|
||||||
@@ -105,15 +100,13 @@ export default class BookParser {
|
|||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
i.onerror = (e) => {
|
i.onerror = reject;
|
||||||
reject(e);
|
|
||||||
};
|
|
||||||
|
|
||||||
i.src = src;
|
i.src = src;
|
||||||
await sleep(30*1000);
|
await sleep(30*1000);
|
||||||
if (!resolved)
|
if (!resolved)
|
||||||
reject('Не удалось получить размер изображения');
|
reject('Не удалось получить размер изображения');
|
||||||
});
|
})().catch(reject); });
|
||||||
};
|
};
|
||||||
|
|
||||||
const newParagraph = (text, len, addIndex) => {
|
const newParagraph = (text, len, addIndex) => {
|
||||||
@@ -179,7 +172,7 @@ export default class BookParser {
|
|||||||
if (tag == 'binary') {
|
if (tag == 'binary') {
|
||||||
let attrs = sax.getAttrsSync(tail);
|
let attrs = sax.getAttrsSync(tail);
|
||||||
binaryType = (attrs['content-type'] && attrs['content-type'].value ? attrs['content-type'].value : '');
|
binaryType = (attrs['content-type'] && attrs['content-type'].value ? attrs['content-type'].value : '');
|
||||||
if (binaryType == 'image/jpeg' || binaryType == 'image/png')
|
if (binaryType == 'image/jpeg' || binaryType == 'image/png' || binaryType == 'application/octet-stream')
|
||||||
binaryId = (attrs.id.value ? attrs.id.value : '');
|
binaryId = (attrs.id.value ? attrs.id.value : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,6 +237,7 @@ export default class BookParser {
|
|||||||
newParagraph(' ', 1);
|
newParagraph(' ', 1);
|
||||||
isFirstTitlePara = true;
|
isFirstTitlePara = true;
|
||||||
bold = true;
|
bold = true;
|
||||||
|
center = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag == 'epigraph') {
|
if (tag == 'epigraph') {
|
||||||
@@ -286,6 +280,7 @@ export default class BookParser {
|
|||||||
if (tag == 'subtitle') {
|
if (tag == 'subtitle') {
|
||||||
isFirstTitlePara = false;
|
isFirstTitlePara = false;
|
||||||
bold = false;
|
bold = false;
|
||||||
|
center = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag == 'epigraph') {
|
if (tag == 'epigraph') {
|
||||||
@@ -371,11 +366,10 @@ export default class BookParser {
|
|||||||
tClose += (bold ? '</strong>' : '');
|
tClose += (bold ? '</strong>' : '');
|
||||||
tClose += (center ? '</center>' : '');
|
tClose += (center ? '</center>' : '');
|
||||||
|
|
||||||
if (path.indexOf('/fictionbook/body/title') == 0) {
|
if (path.indexOf('/fictionbook/body/title') == 0 ||
|
||||||
growParagraph(`${tOpen}${text}${tClose}`, text.length);
|
path.indexOf('/fictionbook/body/section') == 0 ||
|
||||||
}
|
path.indexOf('/fictionbook/body/epigraph') == 0
|
||||||
|
) {
|
||||||
if (path.indexOf('/fictionbook/body/section') == 0) {
|
|
||||||
growParagraph(`${tOpen}${text}${tClose}`, text.length);
|
growParagraph(`${tOpen}${text}${tClose}`, text.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -620,7 +614,8 @@ export default class BookParser {
|
|||||||
para.parsed.addEmptyParagraphs === this.addEmptyParagraphs &&
|
para.parsed.addEmptyParagraphs === this.addEmptyParagraphs &&
|
||||||
para.parsed.showImages === this.showImages &&
|
para.parsed.showImages === this.showImages &&
|
||||||
para.parsed.imageHeightLines === this.imageHeightLines &&
|
para.parsed.imageHeightLines === this.imageHeightLines &&
|
||||||
para.parsed.imageFitWidth === this.imageFitWidth
|
para.parsed.imageFitWidth === this.imageFitWidth &&
|
||||||
|
para.parsed.compactTextPerc === this.compactTextPerc
|
||||||
)
|
)
|
||||||
return para.parsed;
|
return para.parsed;
|
||||||
|
|
||||||
@@ -635,6 +630,7 @@ export default class BookParser {
|
|||||||
showImages: this.showImages,
|
showImages: this.showImages,
|
||||||
imageHeightLines: this.imageHeightLines,
|
imageHeightLines: this.imageHeightLines,
|
||||||
imageFitWidth: this.imageFitWidth,
|
imageFitWidth: this.imageFitWidth,
|
||||||
|
compactTextPerc: this.compactTextPerc,
|
||||||
visible: !(
|
visible: !(
|
||||||
(this.cutEmptyParagraphs && para.cut) ||
|
(this.cutEmptyParagraphs && para.cut) ||
|
||||||
(para.addIndex > this.addEmptyParagraphs)
|
(para.addIndex > this.addEmptyParagraphs)
|
||||||
@@ -665,6 +661,7 @@ export default class BookParser {
|
|||||||
let style = {};
|
let style = {};
|
||||||
let ofs = 0;//смещение от начала параграфа para.offset
|
let ofs = 0;//смещение от начала параграфа para.offset
|
||||||
let imgW = 0;
|
let imgW = 0;
|
||||||
|
const compactWidth = this.measureText('W', {})*this.compactTextPerc/100;
|
||||||
// тут начинается самый замес, перенос по слогам и стилизация, а также изображения
|
// тут начинается самый замес, перенос по слогам и стилизация, а также изображения
|
||||||
for (const part of parts) {
|
for (const part of parts) {
|
||||||
style = part.style;
|
style = part.style;
|
||||||
@@ -749,7 +746,7 @@ export default class BookParser {
|
|||||||
p = (style.space ? p + parsed.p*style.space : p);
|
p = (style.space ? p + parsed.p*style.space : p);
|
||||||
let w = this.measureText(str, style) + p;
|
let w = this.measureText(str, style) + p;
|
||||||
let wordTail = word;
|
let wordTail = word;
|
||||||
if (w > parsed.w && prevStr != '') {
|
if (w > parsed.w + compactWidth && prevStr != '') {
|
||||||
if (parsed.wordWrap) {//по слогам
|
if (parsed.wordWrap) {//по слогам
|
||||||
let slogi = this.splitToSlogi(word);
|
let slogi = this.splitToSlogi(word);
|
||||||
|
|
||||||
@@ -762,7 +759,7 @@ export default class BookParser {
|
|||||||
for (let k = 0; k < slogiLen - 1; k++) {
|
for (let k = 0; k < slogiLen - 1; k++) {
|
||||||
let slog = slogi[0];
|
let slog = slogi[0];
|
||||||
let ww = this.measureText(s + slog + (slog[slog.length - 1] == '-' ? '' : '-'), style) + p;
|
let ww = this.measureText(s + slog + (slog[slog.length - 1] == '-' ? '' : '-'), style) + p;
|
||||||
if (ww <= parsed.w) {
|
if (ww <= parsed.w + compactWidth) {
|
||||||
s += slog;
|
s += slog;
|
||||||
ss += slog;
|
ss += slog;
|
||||||
} else
|
} else
|
||||||
|
|||||||
@@ -18,49 +18,38 @@ const bmRecentStore = localForage.createInstance({
|
|||||||
name: 'bmRecentStore'
|
name: 'bmRecentStore'
|
||||||
});
|
});
|
||||||
|
|
||||||
const bmCacheStore = localForage.createInstance({
|
|
||||||
name: 'bmCacheStore'
|
|
||||||
});
|
|
||||||
|
|
||||||
class BookManager {
|
class BookManager {
|
||||||
async init(settings) {
|
async init(settings) {
|
||||||
|
this.loaded = false;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
|
|
||||||
this.eventListeners = [];
|
this.eventListeners = [];
|
||||||
|
this.books = {};
|
||||||
|
this.recent = {};
|
||||||
|
|
||||||
//bmCacheStore нужен только для ускорения загрузки читалки
|
this.recentLast = await bmRecentStore.getItem('recent-last');
|
||||||
this.booksCached = await bmCacheStore.getItem('books');
|
if (this.recentLast) {
|
||||||
if (!this.booksCached)
|
|
||||||
this.booksCached = {};
|
|
||||||
this.recent = await bmCacheStore.getItem('recent');
|
|
||||||
this.recentLast = await bmCacheStore.getItem('recent-last');
|
|
||||||
if (this.recentLast)
|
|
||||||
this.recent[this.recentLast.key] = this.recentLast;
|
this.recent[this.recentLast.key] = this.recentLast;
|
||||||
this.recentRev = await bmRecentStore.getItem('recent-rev') || 0;
|
const meta = await bmMetaStore.getItem(`bmMeta-${this.recentLast.key}`);
|
||||||
this.recentLastRev = await bmRecentStore.getItem('recent-last-rev') || 0;
|
if (_.isObject(meta)) {
|
||||||
this.recentLastDiffRev = await bmRecentStore.getItem('recent-last-diff-rev') || 0;
|
this.books[meta.key] = meta;
|
||||||
this.books = Object.assign({}, this.booksCached);
|
}
|
||||||
|
|
||||||
this.recentChanged2 = true;
|
|
||||||
|
|
||||||
if (!this.books || !this.recent) {
|
|
||||||
this.books = {};
|
|
||||||
this.recent = {};
|
|
||||||
await this.loadMeta(true);
|
|
||||||
} else {
|
|
||||||
this.loadMeta(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.recentChanged = true;
|
||||||
|
|
||||||
|
this.loadStored();//no await
|
||||||
}
|
}
|
||||||
|
|
||||||
//долгая загрузка из хранилища,
|
//Долгая асинхронная загрузка из хранилища.
|
||||||
//хранение в отдельных записях дает относительно
|
//Хранение в отдельных записях дает относительно
|
||||||
//нормальное поведение при нескольких вкладках с читалкой в браузере
|
//нормальное поведение при нескольких вкладках с читалкой в браузере.
|
||||||
async loadMeta(immediate) {
|
async loadStored() {
|
||||||
if (!immediate)
|
//даем время для загрузки последней читаемой книги, чтобы не блокировать приложение
|
||||||
await utils.sleep(2000);
|
await utils.sleep(2000);
|
||||||
|
|
||||||
let len = await bmMetaStore.length();
|
let len = await bmMetaStore.length();
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = len - 1; i >= 0; i--) {
|
||||||
const key = await bmMetaStore.key(i);
|
const key = await bmMetaStore.key(i);
|
||||||
const keySplit = key.split('-');
|
const keySplit = key.split('-');
|
||||||
|
|
||||||
@@ -68,6 +57,7 @@ class BookManager {
|
|||||||
let meta = await bmMetaStore.getItem(key);
|
let meta = await bmMetaStore.getItem(key);
|
||||||
|
|
||||||
if (_.isObject(meta)) {
|
if (_.isObject(meta)) {
|
||||||
|
//уже может быть распарсена книга
|
||||||
const oldBook = this.books[meta.key];
|
const oldBook = this.books[meta.key];
|
||||||
this.books[meta.key] = meta;
|
this.books[meta.key] = meta;
|
||||||
|
|
||||||
@@ -80,22 +70,19 @@ class BookManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//"ленивая" загрузка
|
let key = null;
|
||||||
(async() => {
|
len = await bmRecentStore.length();
|
||||||
let key = null;
|
for (let i = len - 1; i >= 0; i--) {
|
||||||
len = await bmRecentStore.length();
|
key = await bmRecentStore.key(i);
|
||||||
for (let i = 0; i < len; i++) {
|
if (key) {
|
||||||
key = await bmRecentStore.key(i);
|
let r = await bmRecentStore.getItem(key);
|
||||||
if (key) {
|
if (_.isObject(r) && r.key) {
|
||||||
let r = await bmRecentStore.getItem(key);
|
this.recent[r.key] = r;
|
||||||
if (_.isObject(r) && r.key) {
|
|
||||||
this.recent[r.key] = r;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await bmRecentStore.removeItem(key);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
await bmRecentStore.removeItem(key);
|
||||||
}
|
}
|
||||||
})();
|
}
|
||||||
|
|
||||||
//размножение для дебага
|
//размножение для дебага
|
||||||
/*if (key) {
|
/*if (key) {
|
||||||
@@ -106,17 +93,11 @@ class BookManager {
|
|||||||
}*/
|
}*/
|
||||||
|
|
||||||
await this.cleanBooks();
|
await this.cleanBooks();
|
||||||
|
await this.cleanRecentBooks();
|
||||||
|
|
||||||
//очистка позже
|
this.recentChanged = true;
|
||||||
//await this.cleanRecentBooks();
|
this.loaded = true;
|
||||||
|
this.emit('load-stored-finish');
|
||||||
this.booksCached = {};
|
|
||||||
for (const key in this.books) {
|
|
||||||
this.booksCached[key] = this.metaOnly(this.books[key]);
|
|
||||||
}
|
|
||||||
await bmCacheStore.setItem('books', this.booksCached);
|
|
||||||
await bmCacheStore.setItem('recent', this.recent);
|
|
||||||
this.emit('load-meta-finish');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async cleanBooks() {
|
async cleanBooks() {
|
||||||
@@ -136,14 +117,13 @@ class BookManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (size > maxDataSize && toDel) {
|
if (size > maxDataSize && toDel) {
|
||||||
await this._delBook(toDel);
|
await this.delBook(toDel);
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async deflateWithProgress(data, callback) {
|
async deflateWithProgress(data, callback) {
|
||||||
const chunkSize = 128*1024;
|
const chunkSize = 128*1024;
|
||||||
const deflator = new utils.pako.Deflate({level: 5});
|
const deflator = new utils.pako.Deflate({level: 5});
|
||||||
@@ -178,9 +158,41 @@ class BookManager {
|
|||||||
return deflator.result;
|
return deflator.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async addBook(newBook, callback) {
|
async inflateWithProgress(data, callback) {
|
||||||
if (!this.books)
|
const chunkSize = 64*1024;
|
||||||
await this.init();
|
const inflator = new utils.pako.Inflate({to: 'string'});
|
||||||
|
|
||||||
|
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) {
|
||||||
|
inflator.push(data.subarray(i, i + chunkSize), true);
|
||||||
|
} else {
|
||||||
|
inflator.push(data.subarray(i, i + chunkSize), false);
|
||||||
|
}
|
||||||
|
chunkNum++;
|
||||||
|
|
||||||
|
perc = Math.round(chunkNum/chunkTotal*100);
|
||||||
|
if (perc != prevPerc) {
|
||||||
|
callback(perc);
|
||||||
|
await utils.sleep(1);
|
||||||
|
prevPerc = perc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inflator.err) {
|
||||||
|
throw new Error(inflator.msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(100);
|
||||||
|
|
||||||
|
return inflator.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async addBook(newBook, callback) {
|
||||||
let meta = {url: newBook.url, path: newBook.path};
|
let meta = {url: newBook.url, path: newBook.path};
|
||||||
meta.key = this.keyFromUrl(meta.url);
|
meta.key = this.keyFromUrl(meta.url);
|
||||||
meta.addTime = Date.now();
|
meta.addTime = Date.now();
|
||||||
@@ -200,51 +212,72 @@ 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: 5});
|
||||||
data = await this.deflateWithProgress(data, cb2);
|
data = await this.deflateWithProgress(data, cb2);
|
||||||
result.dataCompressedLength = data.byteLength;
|
result.dataCompressedLength = data.byteLength;
|
||||||
}
|
}
|
||||||
callback(95);
|
callback(95);
|
||||||
|
|
||||||
this.books[meta.key] = result;
|
this.books[meta.key] = result;
|
||||||
this.booksCached[meta.key] = this.metaOnly(result);
|
|
||||||
|
|
||||||
await bmMetaStore.setItem(`bmMeta-${meta.key}`, this.metaOnly(result));
|
await bmMetaStore.setItem(`bmMeta-${meta.key}`, this.metaOnly(result));
|
||||||
await bmDataStore.setItem(`bmData-${meta.key}`, data);
|
await bmDataStore.setItem(`bmData-${meta.key}`, data);
|
||||||
await bmCacheStore.setItem('books', this.booksCached);
|
|
||||||
|
|
||||||
callback(100);
|
callback(100);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasBookParsed(meta) {
|
async hasBookParsed(meta) {
|
||||||
if (!this.books)
|
if (!this.books)
|
||||||
return false;
|
return false;
|
||||||
if (!meta.url)
|
if (!meta.url)
|
||||||
return false;
|
return false;
|
||||||
if (!meta.key)
|
if (!meta.key)
|
||||||
meta.key = this.keyFromUrl(meta.url);
|
meta.key = this.keyFromUrl(meta.url);
|
||||||
|
|
||||||
let book = this.books[meta.key];
|
let book = this.books[meta.key];
|
||||||
|
|
||||||
|
if (!book && !this.loaded) {
|
||||||
|
book = await bmDataStore.getItem(`bmMeta-${meta.key}`);
|
||||||
|
if (book)
|
||||||
|
this.books[meta.key] = book;
|
||||||
|
}
|
||||||
|
|
||||||
return !!(book && book.parsed);
|
return !!(book && book.parsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBook(meta, callback) {
|
async getBook(meta, callback) {
|
||||||
if (!this.books)
|
|
||||||
await this.init();
|
|
||||||
let result = undefined;
|
let result = undefined;
|
||||||
if (!meta.key)
|
if (!meta.key)
|
||||||
meta.key = this.keyFromUrl(meta.url);
|
meta.key = this.keyFromUrl(meta.url);
|
||||||
|
|
||||||
result = this.books[meta.key];
|
result = this.books[meta.key];
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
result = await bmDataStore.getItem(`bmMeta-${meta.key}`);
|
||||||
|
if (result)
|
||||||
|
this.books[meta.key] = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Если файл на сервере изменился, считаем, что в кеше его нету
|
||||||
|
if (meta.path && result && meta.path != result.path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (result && !result.parsed) {
|
if (result && !result.parsed) {
|
||||||
let data = await bmDataStore.getItem(`bmData-${meta.key}`);
|
let data = await bmDataStore.getItem(`bmData-${meta.key}`);
|
||||||
callback(10);
|
callback(5);
|
||||||
await utils.sleep(10);
|
await utils.sleep(10);
|
||||||
|
|
||||||
|
let cb = (perc) => {
|
||||||
|
const p = 5 + Math.round(15*perc/100);
|
||||||
|
callback(p);
|
||||||
|
};
|
||||||
|
|
||||||
if (result.dataCompressed) {
|
if (result.dataCompressed) {
|
||||||
try {
|
try {
|
||||||
data = utils.pako.inflate(data, {to: 'string'});
|
//data = utils.pako.inflate(data, {to: 'string'});
|
||||||
|
data = await this.inflateWithProgress(data, cb);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.delBook(meta);
|
this.delBook(meta);
|
||||||
throw e;
|
throw e;
|
||||||
@@ -252,7 +285,7 @@ class BookManager {
|
|||||||
}
|
}
|
||||||
callback(20);
|
callback(20);
|
||||||
|
|
||||||
const cb = (perc) => {
|
cb = (perc) => {
|
||||||
const p = 20 + Math.round(80*perc/100);
|
const p = 20 + Math.round(80*perc/100);
|
||||||
callback(p);
|
callback(p);
|
||||||
};
|
};
|
||||||
@@ -264,27 +297,14 @@ class BookManager {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _delBook(meta) {
|
async delBook(meta) {
|
||||||
await bmMetaStore.removeItem(`bmMeta-${meta.key}`);
|
await bmMetaStore.removeItem(`bmMeta-${meta.key}`);
|
||||||
await bmDataStore.removeItem(`bmData-${meta.key}`);
|
await bmDataStore.removeItem(`bmData-${meta.key}`);
|
||||||
|
|
||||||
delete this.books[meta.key];
|
delete this.books[meta.key];
|
||||||
delete this.booksCached[meta.key];
|
|
||||||
}
|
|
||||||
|
|
||||||
async delBook(meta) {
|
|
||||||
if (!this.books)
|
|
||||||
await this.init();
|
|
||||||
|
|
||||||
await this._delBook(meta);
|
|
||||||
|
|
||||||
await bmCacheStore.setItem('books', this.booksCached);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseBook(meta, data, callback) {
|
async parseBook(meta, data, callback) {
|
||||||
if (!this.books)
|
|
||||||
await this.init();
|
|
||||||
|
|
||||||
const parsed = new BookParser(this.settings);
|
const parsed = new BookParser(this.settings);
|
||||||
|
|
||||||
const parsedMeta = await parsed.parse(data, callback);
|
const parsedMeta = await parsed.parse(data, callback);
|
||||||
@@ -299,7 +319,6 @@ class BookManager {
|
|||||||
|
|
||||||
metaOnly(book) {
|
metaOnly(book) {
|
||||||
let result = Object.assign({}, book);
|
let result = Object.assign({}, book);
|
||||||
delete result.data;//можно будет убрать эту строку со временем
|
|
||||||
delete result.parsed;
|
delete result.parsed;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -308,9 +327,8 @@ class BookManager {
|
|||||||
return utils.stringToHex(url);
|
return utils.stringToHex(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-- recent --------------------------------------------------------------
|
||||||
async setRecentBook(value) {
|
async setRecentBook(value) {
|
||||||
if (!this.recent)
|
|
||||||
await this.init();
|
|
||||||
const result = this.metaOnly(value);
|
const result = this.metaOnly(value);
|
||||||
result.touchTime = Date.now();
|
result.touchTime = Date.now();
|
||||||
result.deleted = 0;
|
result.deleted = 0;
|
||||||
@@ -327,67 +345,59 @@ class BookManager {
|
|||||||
|
|
||||||
await bmRecentStore.setItem(result.key, result);
|
await bmRecentStore.setItem(result.key, result);
|
||||||
|
|
||||||
//кэшируем, аккуратно
|
|
||||||
let saveRecent = false;
|
|
||||||
if (!(this.recentLast && this.recentLast.key == result.key)) {
|
|
||||||
await bmCacheStore.setItem('recent', this.recent);
|
|
||||||
saveRecent = true;
|
|
||||||
}
|
|
||||||
this.recentLast = result;
|
this.recentLast = result;
|
||||||
await bmCacheStore.setItem('recent-last', this.recentLast);
|
await bmRecentStore.setItem('recent-last', this.recentLast);
|
||||||
|
|
||||||
this.mostRecentCached = result;
|
this.recentChanged = true;
|
||||||
this.recentChanged2 = true;
|
this.emit('recent-changed', result.key);
|
||||||
|
|
||||||
if (saveRecent)
|
|
||||||
this.emit('save-recent');
|
|
||||||
this.emit('recent-changed');
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRecentBook(value) {
|
async getRecentBook(value) {
|
||||||
if (!this.recent)
|
let result = this.recent[value.key];
|
||||||
await this.init();
|
if (!result) {
|
||||||
return this.recent[value.key];
|
result = await bmRecentStore.getItem(value.key);
|
||||||
|
if (result)
|
||||||
|
this.recent[value.key] = result;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async delRecentBook(value) {
|
async delRecentBook(value) {
|
||||||
if (!this.recent)
|
|
||||||
await this.init();
|
|
||||||
|
|
||||||
this.recent[value.key].deleted = 1;
|
this.recent[value.key].deleted = 1;
|
||||||
await bmRecentStore.setItem(value.key, this.recent[value.key]);
|
await bmRecentStore.setItem(value.key, this.recent[value.key]);
|
||||||
await bmCacheStore.setItem('recent', this.recent);
|
|
||||||
|
|
||||||
this.mostRecentCached = null;
|
if (this.recentLast.key == value.key) {
|
||||||
this.recentChanged2 = true;
|
this.recentLast = null;
|
||||||
|
await bmRecentStore.setItem('recent-last', this.recentLast);
|
||||||
this.emit('save-recent');
|
}
|
||||||
|
this.emit('recent-deleted', value.key);
|
||||||
|
this.emit('recent-changed', value.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
async cleanRecentBooks() {
|
async cleanRecentBooks() {
|
||||||
if (!this.recent)
|
|
||||||
await this.init();
|
|
||||||
|
|
||||||
const sorted = this.getSortedRecent();
|
const sorted = this.getSortedRecent();
|
||||||
|
|
||||||
let isDel = false;
|
let isDel = false;
|
||||||
for (let i = 1000; i < sorted.length; i++) {
|
for (let i = 1000; i < sorted.length; i++) {
|
||||||
await bmRecentStore.removeItem(sorted[i].key);
|
await bmRecentStore.removeItem(sorted[i].key);
|
||||||
delete this.recent[sorted[i].key];
|
delete this.recent[sorted[i].key];
|
||||||
|
await bmRecentStore.removeItem(sorted[i].key);
|
||||||
isDel = true;
|
isDel = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sortedRecentCached = null;
|
this.sortedRecentCached = null;
|
||||||
await bmCacheStore.setItem('recent', this.recent);
|
|
||||||
|
|
||||||
|
if (isDel)
|
||||||
|
this.emit('recent-changed');
|
||||||
return isDel;
|
return isDel;
|
||||||
}
|
}
|
||||||
|
|
||||||
mostRecentBook() {
|
mostRecentBook() {
|
||||||
if (this.mostRecentCached) {
|
if (this.recentLast) {
|
||||||
return this.mostRecentCached;
|
return this.recentLast;
|
||||||
}
|
}
|
||||||
|
const oldRecentLast = this.recentLast;
|
||||||
|
|
||||||
let max = 0;
|
let max = 0;
|
||||||
let result = null;
|
let result = null;
|
||||||
@@ -398,12 +408,17 @@ class BookManager {
|
|||||||
result = book;
|
result = book;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.mostRecentCached = result;
|
this.recentLast = result;
|
||||||
|
bmRecentStore.setItem('recent-last', this.recentLast);//no await
|
||||||
|
|
||||||
|
if (this.recentLast !== oldRecentLast)
|
||||||
|
this.emit('recent-changed');
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSortedRecent() {
|
getSortedRecent() {
|
||||||
if (!this.recentChanged2 && this.sortedRecentCached) {
|
if (!this.recentChanged && this.sortedRecentCached) {
|
||||||
return this.sortedRecentCached;
|
return this.sortedRecentCached;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,7 +427,7 @@ class BookManager {
|
|||||||
result.sort((a, b) => b.touchTime - a.touchTime);
|
result.sort((a, b) => b.touchTime - a.touchTime);
|
||||||
|
|
||||||
this.sortedRecentCached = result;
|
this.sortedRecentCached = result;
|
||||||
this.recentChanged2 = false;
|
this.recentChanged = false;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,7 +435,12 @@ class BookManager {
|
|||||||
const mergedRecent = _.cloneDeep(this.recent);
|
const mergedRecent = _.cloneDeep(this.recent);
|
||||||
|
|
||||||
Object.assign(mergedRecent, value);
|
Object.assign(mergedRecent, value);
|
||||||
const newRecent = {};
|
|
||||||
|
//подстраховка от hotReload
|
||||||
|
for (let i of Object.keys(mergedRecent)) {
|
||||||
|
if (!mergedRecent[i].key || mergedRecent[i].key !== i)
|
||||||
|
delete mergedRecent[i];
|
||||||
|
}
|
||||||
|
|
||||||
//"ленивое" обновление хранилища
|
//"ленивое" обновление хранилища
|
||||||
(async() => {
|
(async() => {
|
||||||
@@ -432,58 +452,16 @@ class BookManager {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
for (const rec of Object.values(mergedRecent)) {
|
this.recent = mergedRecent;
|
||||||
if (rec.key) {
|
|
||||||
newRecent[rec.key] = rec;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.recent = newRecent;
|
|
||||||
await bmCacheStore.setItem('recent', this.recent);
|
|
||||||
|
|
||||||
this.recentLast = null;
|
this.recentLast = null;
|
||||||
await bmCacheStore.setItem('recent-last', this.recentLast);
|
await bmRecentStore.setItem('recent-last', this.recentLast);
|
||||||
|
|
||||||
this.mostRecentCached = null;
|
this.recentChanged = true;
|
||||||
|
this.emit('set-recent');
|
||||||
this.emit('recent-changed');
|
this.emit('recent-changed');
|
||||||
}
|
}
|
||||||
|
|
||||||
async setRecentRev(value) {
|
|
||||||
await bmRecentStore.setItem('recent-rev', value);
|
|
||||||
this.recentRev = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
async setRecentLast(value) {
|
|
||||||
if (!value.key)
|
|
||||||
value = null;
|
|
||||||
|
|
||||||
this.recentLast = value;
|
|
||||||
await bmCacheStore.setItem('recent-last', this.recentLast);
|
|
||||||
if (value && value.key) {
|
|
||||||
//гарантия переключения книги
|
|
||||||
const mostRecent = this.mostRecentBook();
|
|
||||||
if (mostRecent)
|
|
||||||
this.recent[mostRecent.key].touchTime = value.touchTime - 1;
|
|
||||||
|
|
||||||
this.recent[value.key] = value;
|
|
||||||
await bmRecentStore.setItem(value.key, value);
|
|
||||||
await bmCacheStore.setItem('recent', this.recent);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.mostRecentCached = null;
|
|
||||||
this.emit('recent-changed');
|
|
||||||
}
|
|
||||||
|
|
||||||
async setRecentLastRev(value) {
|
|
||||||
await bmRecentStore.setItem('recent-last-rev', value);
|
|
||||||
this.recentLastRev = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
async setRecentLastDiffRev(value) {
|
|
||||||
await bmRecentStore.setItem('recent-last-diff-rev', value);
|
|
||||||
this.recentLastDiffRev = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
addEventListener(listener) {
|
addEventListener(listener) {
|
||||||
if (this.eventListeners.indexOf(listener) < 0)
|
if (this.eventListeners.indexOf(listener) < 0)
|
||||||
this.eventListeners.push(listener);
|
this.eventListeners.push(listener);
|
||||||
@@ -496,8 +474,12 @@ class BookManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
emit(eventName, value) {
|
emit(eventName, value) {
|
||||||
for (const listener of this.eventListeners)
|
if (this.eventListeners) {
|
||||||
listener(eventName, value);
|
for (const listener of this.eventListeners) {
|
||||||
|
//console.log(eventName);
|
||||||
|
listener(eventName, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,121 @@
|
|||||||
export const versionHistory = [
|
export const versionHistory = [
|
||||||
|
{
|
||||||
|
showUntil: '2019-11-26',
|
||||||
|
header: '0.7.9 (2019-11-27)',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>добавлен неубираемый баннер для http-версии о переходе на httpS</li>
|
||||||
|
<li>исправления багов</li>
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
showUntil: '2019-11-24',
|
||||||
|
header: '0.7.8 (2019-11-25)',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>улучшение html-фильтров для сайтов</li>
|
||||||
|
<li>исправления багов</li>
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
showUntil: '2019-11-10',
|
||||||
|
header: '0.7.7 (2019-11-06)',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>добавлены следующие жесты для тачскрина (только при включенной опции "управление кликом"):</li>
|
||||||
|
<ul>
|
||||||
|
<li style="list-style-type: square">от центра вверх: на весь экран</li>
|
||||||
|
<li style="list-style-type: square">от центра вниз: плавный скроллинг</li>
|
||||||
|
<li style="list-style-type: square">от центра вправо: увеличить скорость скроллинга</li>
|
||||||
|
<li style="list-style-type: square">от центра влево: уменьшить скорость скроллинга</li>
|
||||||
|
</ul>
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
showUntil: '2019-10-29',
|
||||||
|
header: '0.7.6 (2019-10-30)',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>исправления багов</li>
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
showUntil: '2019-10-21',
|
||||||
|
header: '0.7.5 (2019-10-22)',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>исправления багов</li>
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
showUntil: '2019-10-17',
|
||||||
|
header: '0.7.3 (2019-10-18)',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>внутренние переделки механизма синхронизации с сервером</li>
|
||||||
|
<li>добавлен html-фильтр для сайтов www.fanfiction.net, archiveofourown.org</li>
|
||||||
|
<li>добавлен параметр "Включить html-фильтр для сайтов" в раздел "Вид"->"Текст" в настройках</li>
|
||||||
|
<li>исправления багов</li>
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
showUntil: '2019-09-19',
|
||||||
|
header: '0.7.1 (2019-09-20)',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>исправления багов</li>
|
||||||
|
<li>на панель управления добавлена кнопка "Автономный режим"</li>
|
||||||
|
<li>актуализирована справка</li>
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
showUntil: '2019-10-01',
|
||||||
|
header: '0.7.0 (2019-09-07)',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>налажена работа https-версии сайта, рекомендуется плавный переход</li>
|
||||||
|
<li>добавлена возможность загрузки и работы https-версии читалки в оффлайн-режиме (при отсутствии интернета)</li>
|
||||||
|
<li>упрощение механизма серверной синхронизации с целью повышения надежности и избавления от багов</li>
|
||||||
|
<li>окна теперь можно перемещать за заголовок</li>
|
||||||
|
<li>немного улучшен внешний вид и управление на смартфонах</li>
|
||||||
|
<li>добавлен параметр "Компактность" в раздел "Вид"->"Текст" в настройках</li>
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
showUntil: '2019-07-20',
|
||||||
|
header: '0.6.10 (2019-07-21)',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>исправления багов</li>
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
showUntil: '2019-06-22',
|
showUntil: '2019-06-22',
|
||||||
header: '0.6.9 (2019-06-23)',
|
header: '0.6.9 (2019-06-23)',
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="window">
|
<div ref="main" class="main" @click="close" @mouseup="onMouseUp" @mousemove="onMouseMove">
|
||||||
<div class="header">
|
<div ref="windowBox" class="windowBox" @click.stop>
|
||||||
<span class="header-text"><slot name="header"></slot></span>
|
<div class="window">
|
||||||
<span class="close-button" @click="close"><i class="el-icon-close"></i></span>
|
<div ref="header" class="header" @mousedown.prevent.stop="onMouseDown"
|
||||||
|
@touchstart.stop="onTouchStart" @touchend.stop="onTouchEnd" @touchmove.stop="onTouchMove">
|
||||||
|
<span class="header-text"><slot name="header"></slot></span>
|
||||||
|
<span class="close-button" @mousedown.stop @click="close"><i class="el-icon-close"></i></span>
|
||||||
|
</div>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<slot></slot>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -14,17 +19,116 @@ import Vue from 'vue';
|
|||||||
import Component from 'vue-class-component';
|
import Component from 'vue-class-component';
|
||||||
|
|
||||||
export default @Component({
|
export default @Component({
|
||||||
|
props: {
|
||||||
|
height: { type: String, default: '100%' },
|
||||||
|
width: { type: String, default: '100%' },
|
||||||
|
maxWidth: { type: String, default: '' },
|
||||||
|
topShift: { type: Number, default: 0 },
|
||||||
|
}
|
||||||
})
|
})
|
||||||
class Window extends Vue {
|
class Window extends Vue {
|
||||||
close() {
|
init() {
|
||||||
this.$emit('close');
|
this.$nextTick(() => {
|
||||||
|
this.$refs.windowBox.style.height = this.height;
|
||||||
|
this.$refs.windowBox.style.width = this.width;
|
||||||
|
if (this.maxWidth)
|
||||||
|
this.$refs.windowBox.style.maxWidth = this.maxWidth;
|
||||||
|
|
||||||
|
const left = (this.$refs.main.offsetWidth - this.$refs.windowBox.offsetWidth)/2;
|
||||||
|
const top = (this.$refs.main.offsetHeight - this.$refs.windowBox.offsetHeight)/2 + this.topShift;
|
||||||
|
this.$refs.windowBox.style.left = (left > 0 ? left : 0) + 'px';
|
||||||
|
this.$refs.windowBox.style.top = (top > 0 ? top : 0) + 'px';
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMouseDown(event) {
|
||||||
|
if (this.$isMobileDevice)
|
||||||
|
return;
|
||||||
|
if (event.button == 0) {
|
||||||
|
this.$refs.header.style.cursor = 'move';
|
||||||
|
this.startX = event.screenX;
|
||||||
|
this.startY = event.screenY;
|
||||||
|
this.moving = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseUp(event) {
|
||||||
|
if (event.button == 0) {
|
||||||
|
this.$refs.header.style.cursor = 'default';
|
||||||
|
this.moving = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseMove(event) {
|
||||||
|
if (this.moving) {
|
||||||
|
const deltaX = event.screenX - this.startX;
|
||||||
|
const deltaY = event.screenY - this.startY;
|
||||||
|
this.startX = event.screenX;
|
||||||
|
this.startY = event.screenY;
|
||||||
|
|
||||||
|
this.$refs.windowBox.style.left = (this.$refs.windowBox.offsetLeft + deltaX) + 'px';
|
||||||
|
this.$refs.windowBox.style.top = (this.$refs.windowBox.offsetTop + deltaY) + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onTouchStart(event) {
|
||||||
|
if (!this.$isMobileDevice)
|
||||||
|
return;
|
||||||
|
if (event.touches.length == 1) {
|
||||||
|
const touch = event.touches[0];
|
||||||
|
this.$refs.header.style.cursor = 'move';
|
||||||
|
this.startX = touch.screenX;
|
||||||
|
this.startY = touch.screenY;
|
||||||
|
this.moving = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onTouchMove(event) {
|
||||||
|
if (!this.$isMobileDevice)
|
||||||
|
return;
|
||||||
|
if (event.touches.length == 1 && this.moving) {
|
||||||
|
const touch = event.touches[0];
|
||||||
|
const deltaX = touch.screenX - this.startX;
|
||||||
|
const deltaY = touch.screenY - this.startY;
|
||||||
|
this.startX = touch.screenX;
|
||||||
|
this.startY = touch.screenY;
|
||||||
|
|
||||||
|
this.$refs.windowBox.style.left = (this.$refs.windowBox.offsetLeft + deltaX) + 'px';
|
||||||
|
this.$refs.windowBox.style.top = (this.$refs.windowBox.offsetTop + deltaY) + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onTouchEnd() {
|
||||||
|
if (!this.$isMobileDevice)
|
||||||
|
return;
|
||||||
|
this.$refs.header.style.cursor = 'default';
|
||||||
|
this.moving = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
close() {
|
||||||
|
if (!this.moving)
|
||||||
|
this.$emit('close');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.main {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.windowBox {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.window {
|
.window {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -39,9 +143,9 @@ class Window extends Vue {
|
|||||||
.header {
|
.header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
background-color: #e5e7ea;
|
background-color: #59B04F;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 40px;
|
height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-text {
|
.header-text {
|
||||||
@@ -54,8 +158,12 @@ class Window extends Vue {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 40px;
|
width: 30px;
|
||||||
height: 40px;
|
height: 30px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.close-button:hover {
|
||||||
|
background-color: #69C05F;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -9,97 +9,37 @@ Vue.use(ElementUI, { locale });
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
//------------------------------------------------------
|
//------------------------------------------------------
|
||||||
//import './theme/index.css';
|
import './theme/index.css';
|
||||||
|
|
||||||
import './theme/icon.css';
|
|
||||||
import './theme/tooltip.css';
|
|
||||||
|
|
||||||
import ElMenu from 'element-ui/lib/menu';
|
import ElMenu from 'element-ui/lib/menu';
|
||||||
import './theme/menu.css';
|
|
||||||
|
|
||||||
import ElMenuItem from 'element-ui/lib/menu-item';
|
import ElMenuItem from 'element-ui/lib/menu-item';
|
||||||
import './theme/menu-item.css';
|
|
||||||
|
|
||||||
import ElButton from 'element-ui/lib/button';
|
import ElButton from 'element-ui/lib/button';
|
||||||
import './theme/button.css';
|
|
||||||
|
|
||||||
import ElButtonGroup from 'element-ui/lib/button-group';
|
import ElButtonGroup from 'element-ui/lib/button-group';
|
||||||
import './theme/button-group.css';
|
|
||||||
|
|
||||||
import ElCheckbox from 'element-ui/lib/checkbox';
|
import ElCheckbox from 'element-ui/lib/checkbox';
|
||||||
import './theme/checkbox.css';
|
|
||||||
|
|
||||||
import ElTabs from 'element-ui/lib/tabs';
|
import ElTabs from 'element-ui/lib/tabs';
|
||||||
import './theme/tabs.css';
|
|
||||||
|
|
||||||
import ElTabPane from 'element-ui/lib/tab-pane';
|
import ElTabPane from 'element-ui/lib/tab-pane';
|
||||||
import './theme/tab-pane.css';
|
|
||||||
|
|
||||||
import ElTooltip from 'element-ui/lib/tooltip';
|
import ElTooltip from 'element-ui/lib/tooltip';
|
||||||
import './theme/tooltip.css';
|
|
||||||
|
|
||||||
import ElCol from 'element-ui/lib/col';
|
import ElCol from 'element-ui/lib/col';
|
||||||
import './theme/col.css';
|
|
||||||
|
|
||||||
import ElContainer from 'element-ui/lib/container';
|
import ElContainer from 'element-ui/lib/container';
|
||||||
import './theme/container.css';
|
|
||||||
|
|
||||||
import ElAside from 'element-ui/lib/aside';
|
import ElAside from 'element-ui/lib/aside';
|
||||||
import './theme/aside.css';
|
|
||||||
|
|
||||||
import ElHeader from 'element-ui/lib/header';
|
import ElHeader from 'element-ui/lib/header';
|
||||||
import './theme/header.css';
|
|
||||||
|
|
||||||
import ElMain from 'element-ui/lib/main';
|
import ElMain from 'element-ui/lib/main';
|
||||||
import './theme/main.css';
|
|
||||||
|
|
||||||
import ElInput from 'element-ui/lib/input';
|
import ElInput from 'element-ui/lib/input';
|
||||||
import './theme/input.css';
|
|
||||||
|
|
||||||
import ElInputNumber from 'element-ui/lib/input-number';
|
import ElInputNumber from 'element-ui/lib/input-number';
|
||||||
import './theme/input-number.css';
|
|
||||||
|
|
||||||
import ElSelect from 'element-ui/lib/select';
|
import ElSelect from 'element-ui/lib/select';
|
||||||
import './theme/select.css';
|
|
||||||
|
|
||||||
import ElOption from 'element-ui/lib/option';
|
import ElOption from 'element-ui/lib/option';
|
||||||
import './theme/option.css';
|
|
||||||
|
|
||||||
import ElTable from 'element-ui/lib/table';
|
import ElTable from 'element-ui/lib/table';
|
||||||
import './theme/table.css';
|
|
||||||
|
|
||||||
import ElTableColumn from 'element-ui/lib/table-column';
|
import ElTableColumn from 'element-ui/lib/table-column';
|
||||||
import './theme/table-column.css';
|
|
||||||
|
|
||||||
import ElProgress from 'element-ui/lib/progress';
|
import ElProgress from 'element-ui/lib/progress';
|
||||||
import './theme/progress.css';
|
|
||||||
|
|
||||||
import ElSlider from 'element-ui/lib/slider';
|
import ElSlider from 'element-ui/lib/slider';
|
||||||
import './theme/slider.css';
|
|
||||||
|
|
||||||
import ElForm from 'element-ui/lib/form';
|
import ElForm from 'element-ui/lib/form';
|
||||||
import './theme/form.css';
|
|
||||||
|
|
||||||
import ElFormItem from 'element-ui/lib/form-item';
|
import ElFormItem from 'element-ui/lib/form-item';
|
||||||
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 ElDialog from 'element-ui/lib/dialog';
|
import ElDialog from 'element-ui/lib/dialog';
|
||||||
import './theme/dialog.css';
|
|
||||||
|
|
||||||
import Notification from 'element-ui/lib/notification';
|
import Notification from 'element-ui/lib/notification';
|
||||||
import './theme/notification.css';
|
|
||||||
|
|
||||||
import Loading from 'element-ui/lib/loading';
|
import Loading from 'element-ui/lib/loading';
|
||||||
import './theme/loading.css';
|
|
||||||
|
|
||||||
import MessageBox from 'element-ui/lib/message-box';
|
import MessageBox from 'element-ui/lib/message-box';
|
||||||
import './theme/message-box.css';
|
|
||||||
|
|
||||||
//import Message from 'element-ui/lib/message';
|
|
||||||
//import './theme/message.css';
|
|
||||||
|
|
||||||
const components = {
|
const components = {
|
||||||
ElMenu, ElMenuItem, ElButton, ElButtonGroup, ElCheckbox, ElTabs, ElTabPane, ElTooltip,
|
ElMenu, ElMenuItem, ElButton, ElButtonGroup, ElCheckbox, ElTabs, ElTabPane, ElTooltip,
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html manifest="/app/manifest.appcache">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
<meta name="description" content="браузерная онлайн-читалка книг из интернета и библиотека">
|
<meta name="description" content="Браузерная онлайн-читалка книг. Поддерживаются форматы: fb2, html, txt, rtf, doc, docx, pdf, epub, mobi.">
|
||||||
<meta name="keywords" content="библиотека,онлайн,читалка,книги,читать,браузер,интернет">
|
<meta name="keywords" content="онлайн,читалка,fb2,книги,читать,браузер,интернет">
|
||||||
<title></title>
|
<title></title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import App from './components/App.vue';
|
|
||||||
|
|
||||||
import router from './router';
|
import router from './router';
|
||||||
import store from './store';
|
import store from './store';
|
||||||
import './element';
|
import './element';
|
||||||
|
|
||||||
|
import App from './components/App.vue';
|
||||||
//Vue.config.productionTip = false;
|
//Vue.config.productionTip = false;
|
||||||
|
Vue.prototype.$isMobileDevice = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent);
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
router,
|
router,
|
||||||
|
|||||||
@@ -2,21 +2,25 @@ import Vue from 'vue';
|
|||||||
import VueRouter from 'vue-router';
|
import VueRouter from 'vue-router';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import App from './components/App.vue';
|
//немедленная загрузка
|
||||||
|
import CardIndex from './components/CardIndex/CardIndex.vue';
|
||||||
|
//const CardIndex = () => import('./components/CardIndex/CardIndex.vue');
|
||||||
|
|
||||||
const CardIndex = () => import('./components/CardIndex/CardIndex.vue');
|
|
||||||
const Search = () => import('./components/CardIndex/Search/Search.vue');
|
const Search = () => import('./components/CardIndex/Search/Search.vue');
|
||||||
const Card = () => import('./components/CardIndex/Card/Card.vue');
|
const Card = () => import('./components/CardIndex/Card/Card.vue');
|
||||||
const Book = () => import('./components/CardIndex/Book/Book.vue');
|
const Book = () => import('./components/CardIndex/Book/Book.vue');
|
||||||
const History = () => import('./components/CardIndex/History/History.vue');
|
const History = () => import('./components/CardIndex/History/History.vue');
|
||||||
|
|
||||||
const Reader = () => import('./components/Reader/Reader.vue');
|
//немедленная загрузка
|
||||||
|
//const Reader = () => import('./components/Reader/Reader.vue');
|
||||||
|
import Reader from './components/Reader/Reader.vue';
|
||||||
|
|
||||||
//const Forum = () => import('./components/Forum/Forum.vue');
|
//const Forum = () => import('./components/Forum/Forum.vue');
|
||||||
const Income = () => import('./components/Income/Income.vue');
|
const Income = () => import('./components/Income/Income.vue');
|
||||||
const Sources = () => import('./components/Sources/Sources.vue');
|
const Sources = () => import('./components/Sources/Sources.vue');
|
||||||
const Settings = () => import('./components/Settings/Settings.vue');
|
const Settings = () => import('./components/Settings/Settings.vue');
|
||||||
const Help = () => import('./components/Help/Help.vue');
|
const Help = () => import('./components/Help/Help.vue');
|
||||||
const NotFound404 = () => import('./components/NotFound404/NotFound404.vue');
|
//const NotFound404 = () => import('./components/NotFound404/NotFound404.vue');
|
||||||
|
|
||||||
const myRoutes = [
|
const myRoutes = [
|
||||||
['/', null, null, '/cardindex'],
|
['/', null, null, '/cardindex'],
|
||||||
|
|||||||
@@ -129,6 +129,10 @@ export function getObjDiff(oldObj, newObj) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isObjDiff(diff) {
|
||||||
|
return (_.isObject(diff) && diff.__isDiff);
|
||||||
|
}
|
||||||
|
|
||||||
export function isEmptyObjDiff(diff) {
|
export function isEmptyObjDiff(diff) {
|
||||||
return (!_.isObject(diff) || !diff.__isDiff ||
|
return (!_.isObject(diff) || !diff.__isDiff ||
|
||||||
(!Object.keys(diff.change).length &&
|
(!Object.keys(diff.change).length &&
|
||||||
@@ -166,3 +170,27 @@ export function applyObjDiff(obj, diff, isAddChanged) {
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseQuery(str) {
|
||||||
|
if (typeof str != 'string' || str.length == 0)
|
||||||
|
return {};
|
||||||
|
let s = str.split('&');
|
||||||
|
let s_length = s.length;
|
||||||
|
let bit, query = {}, first, second;
|
||||||
|
|
||||||
|
for (let i = 0; i < s_length; i++) {
|
||||||
|
bit = s[i].split('=');
|
||||||
|
first = decodeURIComponent(bit[0]);
|
||||||
|
if (first.length == 0)
|
||||||
|
continue;
|
||||||
|
second = decodeURIComponent(bit[1]);
|
||||||
|
if (typeof query[first] == 'undefined')
|
||||||
|
query[first] = second;
|
||||||
|
else
|
||||||
|
if (query[first] instanceof Array)
|
||||||
|
query[first].push(second);
|
||||||
|
else
|
||||||
|
query[first] = [query[first], second];
|
||||||
|
}
|
||||||
|
return query;
|
||||||
|
}
|
||||||
@@ -8,7 +8,8 @@ const toolButtons = [
|
|||||||
{name: 'search', show: true, text: 'Найти в тексте'},
|
{name: 'search', show: true, text: 'Найти в тексте'},
|
||||||
{name: 'copyText', show: false, text: 'Скопировать текст со страницы'},
|
{name: 'copyText', show: false, text: 'Скопировать текст со страницы'},
|
||||||
{name: 'refresh', show: true, text: 'Принудительно обновить книгу'},
|
{name: 'refresh', show: true, text: 'Принудительно обновить книгу'},
|
||||||
{name: 'history', show: true, text: 'Открыть недавние'},
|
{name: 'offlineMode', show: false, text: 'Автономный режим (без интернета)'},
|
||||||
|
{name: 'recentBooks', show: true, text: 'Открыть недавние'},
|
||||||
];
|
];
|
||||||
|
|
||||||
const fonts = [
|
const fonts = [
|
||||||
@@ -145,7 +146,7 @@ const settingDefaults = {
|
|||||||
fontName: 'ReaderDefault',
|
fontName: 'ReaderDefault',
|
||||||
webFontName: '',
|
webFontName: '',
|
||||||
fontVertShift: 0,
|
fontVertShift: 0,
|
||||||
textVertShift: -20,
|
textVertShift: 0,
|
||||||
|
|
||||||
lineInterval: 3,// px, межстрочный интервал
|
lineInterval: 3,// px, межстрочный интервал
|
||||||
textAlignJustify: true,// выравнивание по ширине
|
textAlignJustify: true,// выравнивание по ширине
|
||||||
@@ -176,10 +177,13 @@ const settingDefaults = {
|
|||||||
blinkCachedLoad: true,
|
blinkCachedLoad: true,
|
||||||
showImages: true,
|
showImages: true,
|
||||||
showInlineImagesInCenter: true,
|
showInlineImagesInCenter: true,
|
||||||
|
compactTextPerc: 0,
|
||||||
imageHeightLines: 100,
|
imageHeightLines: 100,
|
||||||
imageFitWidth: true,
|
imageFitWidth: true,
|
||||||
showServerStorageMessages: true,
|
showServerStorageMessages: true,
|
||||||
showWhatsNewDialog: true,
|
showWhatsNewDialog: true,
|
||||||
|
showMigrationDialog: true,
|
||||||
|
enableSitesFilter: true,
|
||||||
|
|
||||||
fontShifts: {},
|
fontShifts: {},
|
||||||
showToolButton: {},
|
showToolButton: {},
|
||||||
@@ -201,6 +205,7 @@ const state = {
|
|||||||
profilesRev: 0,
|
profilesRev: 0,
|
||||||
allowProfilesSave: false,//подстраховка для разработки
|
allowProfilesSave: false,//подстраховка для разработки
|
||||||
whatsNewContentHash: '',
|
whatsNewContentHash: '',
|
||||||
|
migrationRemindDate: '',
|
||||||
currentProfile: '',
|
currentProfile: '',
|
||||||
settings: Object.assign({}, settingDefaults),
|
settings: Object.assign({}, settingDefaults),
|
||||||
settingsRev: {},
|
settingsRev: {},
|
||||||
@@ -235,6 +240,9 @@ const mutations = {
|
|||||||
setWhatsNewContentHash(state, value) {
|
setWhatsNewContentHash(state, value) {
|
||||||
state.whatsNewContentHash = value;
|
state.whatsNewContentHash = value;
|
||||||
},
|
},
|
||||||
|
setMigrationRemindDate(state, value) {
|
||||||
|
state.migrationRemindDate = value;
|
||||||
|
},
|
||||||
setCurrentProfile(state, value) {
|
setCurrentProfile(state, value) {
|
||||||
state.currentProfile = value;
|
state.currentProfile = value;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
.el-alert{width:100%;padding:8px 16px;margin:0;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:4px;position:relative;background-color:#fff;overflow:hidden;opacity:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-transition:opacity .2s;transition:opacity .2s}.el-alert.is-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.el-alert--success{background-color:#f0f9eb;color:#67c23a}.el-alert--success .el-alert__description{color:#67c23a}.el-alert--info{background-color:#f4f4f5;color:#909399}.el-alert--info .el-alert__description{color:#909399}.el-alert--warning{background-color:#fdf6ec;color:#e6a23c}.el-alert--warning .el-alert__description{color:#e6a23c}.el-alert--error{background-color:#fef0f0;color:#f56c6c}.el-alert--error .el-alert__description{color:#f56c6c}.el-alert__content{display:table-cell;padding:0 8px}.el-alert__icon{font-size:16px;width:16px}.el-alert__icon.is-big{font-size:28px;width:28px}.el-alert__title{font-size:13px;line-height:18px}.el-alert__title.is-bold{font-weight:700}.el-alert .el-alert__description{font-size:12px;margin:5px 0 0}.el-alert__closebtn{font-size:12px;color:#c0c4cc;opacity:1;position:absolute;top:12px;right:15px;cursor:pointer}.el-alert__closebtn.is-customed{font-style:normal;font-size:13px;top:9px}.el-alert-fade-enter,.el-alert-fade-leave-active{opacity:0}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.el-aside{overflow:auto;-webkit-box-sizing:border-box;box-sizing:border-box;-ms-flex-negative:0;flex-shrink:0}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
.el-badge{position:relative;vertical-align:middle;display:inline-block}.el-badge__content{background-color:#f56c6c;border-radius:10px;color:#fff;display:inline-block;font-size:12px;height:18px;line-height:18px;padding:0 6px;text-align:center;white-space:nowrap;border:1px solid #fff}.el-badge__content.is-fixed{position:absolute;top:0;right:10px;-webkit-transform:translateY(-50%) translateX(100%);transform:translateY(-50%) translateX(100%)}.el-badge__content.is-fixed.is-dot{right:5px}.el-badge__content.is-dot{height:8px;width:8px;padding:0;right:0;border-radius:50%}.el-badge__content--primary{background-color:#00468F}.el-badge__content--success{background-color:#67c23a}.el-badge__content--warning{background-color:#e6a23c}.el-badge__content--info{background-color:#909399}.el-badge__content--danger{background-color:#f56c6c}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
.el-breadcrumb{font-size:14px;line-height:1}.el-breadcrumb::after,.el-breadcrumb::before{display:table;content:""}.el-breadcrumb::after{clear:both}.el-breadcrumb__separator{margin:0 9px;font-weight:700;color:#c0c4cc}.el-breadcrumb__separator[class*=icon]{margin:0 6px;font-weight:400}.el-breadcrumb__item{float:left}.el-breadcrumb__inner{color:#606266}.el-breadcrumb__inner a,.el-breadcrumb__inner.is-link{font-weight:700;text-decoration:none;-webkit-transition:color .2s cubic-bezier(.645,.045,.355,1);transition:color .2s cubic-bezier(.645,.045,.355,1);color:#303133}.el-breadcrumb__inner a:hover,.el-breadcrumb__inner.is-link:hover{color:#00468F;cursor:pointer}.el-breadcrumb__item:last-child .el-breadcrumb__inner,.el-breadcrumb__item:last-child .el-breadcrumb__inner a,.el-breadcrumb__item:last-child .el-breadcrumb__inner a:hover,.el-breadcrumb__item:last-child .el-breadcrumb__inner:hover{font-weight:400;color:#606266;cursor:text}.el-breadcrumb__item:last-child .el-breadcrumb__separator{display:none}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
.el-card{border-radius:4px;border:1px solid #ebeef5;background-color:#fff;overflow:hidden;color:#303133;-webkit-transition:.3s;transition:.3s}.el-card.is-always-shadow,.el-card.is-hover-shadow:focus,.el-card.is-hover-shadow:hover{-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-card__header{padding:18px 20px;border-bottom:1px solid #ebeef5;-webkit-box-sizing:border-box;box-sizing:border-box}.el-card__body{padding:20px}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.el-carousel__item,.el-carousel__mask{position:absolute;height:100%;top:0;left:0}.el-carousel__item{width:100%;display:inline-block;overflow:hidden;z-index:0}.el-carousel__item.is-active{z-index:2}.el-carousel__item.is-animating{-webkit-transition:-webkit-transform .4s ease-in-out;transition:-webkit-transform .4s ease-in-out;transition:transform .4s ease-in-out;transition:transform .4s ease-in-out,-webkit-transform .4s ease-in-out}.el-carousel__item--card{width:50%;-webkit-transition:-webkit-transform .4s ease-in-out;transition:-webkit-transform .4s ease-in-out;transition:transform .4s ease-in-out;transition:transform .4s ease-in-out,-webkit-transform .4s ease-in-out}.el-carousel__item--card.is-in-stage{cursor:pointer;z-index:1}.el-carousel__item--card.is-in-stage.is-hover .el-carousel__mask,.el-carousel__item--card.is-in-stage:hover .el-carousel__mask{opacity:.12}.el-carousel__item--card.is-active{z-index:2}.el-carousel__mask{width:100%;background-color:#fff;opacity:.24;-webkit-transition:.2s;transition:.2s}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.el-carousel{overflow-x:hidden;position:relative}.el-carousel__container{position:relative;height:300px}.el-carousel__arrow{border:none;outline:0;padding:0;margin:0;height:36px;width:36px;cursor:pointer;-webkit-transition:.3s;transition:.3s;border-radius:50%;background-color:rgba(31,45,61,.11);color:#fff;position:absolute;top:50%;z-index:10;-webkit-transform:translateY(-50%);transform:translateY(-50%);text-align:center;font-size:12px}.el-carousel__arrow--left{left:16px}.el-carousel__arrow--right{right:16px}.el-carousel__arrow:hover{background-color:rgba(31,45,61,.23)}.el-carousel__arrow i{cursor:pointer}.el-carousel__indicators{position:absolute;list-style:none;bottom:0;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%);margin:0;padding:0;z-index:2}.el-carousel__indicators--outside{bottom:26px;text-align:center;position:static;-webkit-transform:none;transform:none}.el-carousel__indicators--outside .el-carousel__indicator:hover button{opacity:.64}.el-carousel__indicators--outside button{background-color:#c0c4cc;opacity:.24}.el-carousel__indicators--labels{left:0;right:0;-webkit-transform:none;transform:none;text-align:center}.el-carousel__indicators--labels .el-carousel__button{height:auto;width:auto;padding:2px 18px;font-size:12px}.el-carousel__indicators--labels .el-carousel__indicator{padding:6px 4px}.el-carousel__indicator{display:inline-block;background-color:transparent;padding:12px 4px;cursor:pointer}.el-carousel__indicator:hover button{opacity:.72}.el-carousel__indicator.is-active button{opacity:1}.el-carousel__button{display:block;opacity:.48;width:30px;height:2px;background-color:#fff;border:none;outline:0;padding:0;margin:0;cursor:pointer;-webkit-transition:.3s;transition:.3s}.carousel-arrow-left-enter,.carousel-arrow-left-leave-active{-webkit-transform:translateY(-50%) translateX(-10px);transform:translateY(-50%) translateX(-10px);opacity:0}.carousel-arrow-right-enter,.carousel-arrow-right-leave-active{-webkit-transform:translateY(-50%) translateX(10px);transform:translateY(-50%) translateX(10px);opacity:0}
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
.el-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-flex:1;-ms-flex:1;flex:1;-ms-flex-preferred-size:auto;flex-basis:auto;-webkit-box-sizing:border-box;box-sizing:border-box;min-width:0}.el-container.is-vertical{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
.v-modal-enter{-webkit-animation:v-modal-in .2s ease;animation:v-modal-in .2s ease}.v-modal-leave{-webkit-animation:v-modal-out .2s ease forwards;animation:v-modal-out .2s ease forwards}@-webkit-keyframes v-modal-in{0%{opacity:0}}@keyframes v-modal-in{0%{opacity:0}}@-webkit-keyframes v-modal-out{100%{opacity:0}}@keyframes v-modal-out{100%{opacity:0}}.v-modal{position:fixed;left:0;top:0;width:100%;height:100%;opacity:.5;background:#000}.el-popup-parent--hidden{overflow:hidden}.el-dialog{position:relative;margin:0 auto 50px;background:#fff;border-radius:2px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,.3);box-shadow:0 1px 3px rgba(0,0,0,.3);-webkit-box-sizing:border-box;box-sizing:border-box;width:50%}.el-dialog.is-fullscreen{width:100%;margin-top:0;margin-bottom:0;height:100%;overflow:auto}.el-dialog__wrapper{position:fixed;top:0;right:0;bottom:0;left:0;overflow:auto;margin:0}.el-dialog__header{padding:20px 20px 10px}.el-dialog__headerbtn{position:absolute;top:20px;right:20px;padding:0;background:0 0;border:none;outline:0;cursor:pointer;font-size:16px}.el-dialog__headerbtn .el-dialog__close{color:#909399}.el-dialog__headerbtn:focus .el-dialog__close,.el-dialog__headerbtn:hover .el-dialog__close{color:#00468F}.el-dialog__title{line-height:24px;font-size:18px;color:#303133}.el-dialog__body{padding:30px 20px;color:#606266;font-size:14px}.el-dialog__footer{padding:10px 20px 20px;text-align:right;-webkit-box-sizing:border-box;box-sizing:border-box}.el-dialog--center{text-align:center}.el-dialog--center .el-dialog__body{text-align:initial;padding:25px 25px 30px}.el-dialog--center .el-dialog__footer{text-align:inherit}.dialog-fade-enter-active{-webkit-animation:dialog-fade-in .3s;animation:dialog-fade-in .3s}.dialog-fade-leave-active{-webkit-animation:dialog-fade-out .3s;animation:dialog-fade-out .3s}@-webkit-keyframes dialog-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@keyframes dialog-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@-webkit-keyframes dialog-fade-out{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}100%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}@keyframes dialog-fade-out{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}100%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
@media only screen and (max-width:767px){.hidden-xs-only{display:none!important}}@media only screen and (min-width:768px){.hidden-sm-and-up{display:none!important}}@media only screen and (min-width:768px) and (max-width:991px){.hidden-sm-only{display:none!important}}@media only screen and (max-width:991px){.hidden-sm-and-down{display:none!important}}@media only screen and (min-width:992px){.hidden-md-and-up{display:none!important}}@media only screen and (min-width:992px) and (max-width:1199px){.hidden-md-only{display:none!important}}@media only screen and (max-width:1199px){.hidden-md-and-down{display:none!important}}@media only screen and (min-width:1200px){.hidden-lg-and-up{display:none!important}}@media only screen and (min-width:1200px) and (max-width:1919px){.hidden-lg-only{display:none!important}}@media only screen and (max-width:1919px){.hidden-lg-and-down{display:none!important}}@media only screen and (min-width:1920px){.hidden-xl-only{display:none!important}}
|
|
||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
.el-footer{padding:0 20px;-webkit-box-sizing:border-box;box-sizing:border-box;-ms-flex-negative:0;flex-shrink:0}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.el-form--inline .el-form-item,.el-form--inline .el-form-item__content{display:inline-block;vertical-align:top}.el-form-item::after,.el-form-item__content::after{clear:both}.el-form--label-left .el-form-item__label{text-align:left}.el-form--label-top .el-form-item__label{float:none;display:inline-block;text-align:left;padding:0 0 10px}.el-form--inline .el-form-item{margin-right:10px}.el-form--inline .el-form-item__label{float:none;display:inline-block}.el-form-item__content .el-input-group,.el-form-item__label{vertical-align:middle}.el-form--inline.el-form--label-top .el-form-item__content{display:block}.el-form-item{margin-bottom:22px}.el-form-item::after,.el-form-item::before{display:table;content:""}.el-form-item .el-form-item{margin-bottom:0}.el-form-item--mini.el-form-item,.el-form-item--small.el-form-item{margin-bottom:18px}.el-form-item .el-input__validateIcon{display:none}.el-form-item--medium .el-form-item__content,.el-form-item--medium .el-form-item__label{line-height:36px}.el-form-item--small .el-form-item__content,.el-form-item--small .el-form-item__label{line-height:32px}.el-form-item--small .el-form-item__error{padding-top:2px}.el-form-item--mini .el-form-item__content,.el-form-item--mini .el-form-item__label{line-height:28px}.el-form-item--mini .el-form-item__error{padding-top:1px}.el-form-item__label{text-align:right;float:left;font-size:14px;color:#606266;line-height:40px;padding:0 12px 0 0;-webkit-box-sizing:border-box;box-sizing:border-box}.el-form-item__content{line-height:40px;position:relative;font-size:14px}.el-form-item__content::after,.el-form-item__content::before{display:table;content:""}.el-form-item__error{color:#f56c6c;font-size:12px;line-height:1;padding-top:4px;position:absolute;top:100%;left:0}.el-form-item__error--inline{position:relative;top:auto;left:auto;display:inline-block;margin-left:10px}.el-form-item.is-required:not(.is-no-asterisk)>.el-form-item__label:before{content:'*';color:#f56c6c;margin-right:4px}.el-form-item.is-error .el-input__inner,.el-form-item.is-error .el-input__inner:focus,.el-form-item.is-error .el-textarea__inner,.el-form-item.is-error .el-textarea__inner:focus{border-color:#f56c6c}.el-form-item.is-error .el-input-group__append .el-input__inner,.el-form-item.is-error .el-input-group__prepend .el-input__inner{border-color:transparent}.el-form-item.is-error .el-input__validateIcon{color:#f56c6c}.el-form-item.is-success .el-input__inner,.el-form-item.is-success .el-input__inner:focus,.el-form-item.is-success .el-textarea__inner,.el-form-item.is-success .el-textarea__inner:focus{border-color:#67c23a}.el-form-item.is-success .el-input-group__append .el-input__inner,.el-form-item.is-success .el-input-group__prepend .el-input__inner{border-color:transparent}.el-form-item.is-success .el-input__validateIcon{color:#67c23a}.el-form-item--feedback .el-input__validateIcon{display:inline-block}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.el-header{padding:0 20px;-webkit-box-sizing:border-box;box-sizing:border-box;-ms-flex-negative:0;flex-shrink:0}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
@font-face{font-family:element-icons;src:url(fonts/element-icons.woff) format("woff"),url(fonts/element-icons.ttf) format("truetype");font-weight:400;font-style:normal}[class*=" el-icon-"],[class^=el-icon-]{font-family:element-icons!important;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;vertical-align:baseline;display:inline-block;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.el-icon-info:before{content:"\e61a"}.el-icon-error:before{content:"\e62c"}.el-icon-success:before{content:"\e62d"}.el-icon-warning:before{content:"\e62e"}.el-icon-question:before{content:"\e634"}.el-icon-back:before{content:"\e606"}.el-icon-arrow-left:before{content:"\e600"}.el-icon-arrow-down:before{content:"\e603"}.el-icon-arrow-right:before{content:"\e604"}.el-icon-arrow-up:before{content:"\e605"}.el-icon-caret-left:before{content:"\e60a"}.el-icon-caret-bottom:before{content:"\e60b"}.el-icon-caret-top:before{content:"\e60c"}.el-icon-caret-right:before{content:"\e60e"}.el-icon-d-arrow-left:before{content:"\e610"}.el-icon-d-arrow-right:before{content:"\e613"}.el-icon-minus:before{content:"\e621"}.el-icon-plus:before{content:"\e62b"}.el-icon-remove:before{content:"\e635"}.el-icon-circle-plus:before{content:"\e601"}.el-icon-remove-outline:before{content:"\e63c"}.el-icon-circle-plus-outline:before{content:"\e602"}.el-icon-close:before{content:"\e60f"}.el-icon-check:before{content:"\e611"}.el-icon-circle-close:before{content:"\e607"}.el-icon-circle-check:before{content:"\e639"}.el-icon-circle-close-outline:before{content:"\e609"}.el-icon-circle-check-outline:before{content:"\e63e"}.el-icon-zoom-out:before{content:"\e645"}.el-icon-zoom-in:before{content:"\e641"}.el-icon-d-caret:before{content:"\e615"}.el-icon-sort:before{content:"\e640"}.el-icon-sort-down:before{content:"\e630"}.el-icon-sort-up:before{content:"\e631"}.el-icon-tickets:before{content:"\e63f"}.el-icon-document:before{content:"\e614"}.el-icon-goods:before{content:"\e618"}.el-icon-sold-out:before{content:"\e63b"}.el-icon-news:before{content:"\e625"}.el-icon-message:before{content:"\e61b"}.el-icon-date:before{content:"\e608"}.el-icon-printer:before{content:"\e62f"}.el-icon-time:before{content:"\e642"}.el-icon-bell:before{content:"\e622"}.el-icon-mobile-phone:before{content:"\e624"}.el-icon-service:before{content:"\e63a"}.el-icon-view:before{content:"\e643"}.el-icon-menu:before{content:"\e620"}.el-icon-more:before{content:"\e646"}.el-icon-more-outline:before{content:"\e626"}.el-icon-star-on:before{content:"\e637"}.el-icon-star-off:before{content:"\e63d"}.el-icon-location:before{content:"\e61d"}.el-icon-location-outline:before{content:"\e61f"}.el-icon-phone:before{content:"\e627"}.el-icon-phone-outline:before{content:"\e628"}.el-icon-picture:before{content:"\e629"}.el-icon-picture-outline:before{content:"\e62a"}.el-icon-delete:before{content:"\e612"}.el-icon-search:before{content:"\e619"}.el-icon-edit:before{content:"\e61c"}.el-icon-edit-outline:before{content:"\e616"}.el-icon-rank:before{content:"\e632"}.el-icon-refresh:before{content:"\e633"}.el-icon-share:before{content:"\e636"}.el-icon-setting:before{content:"\e638"}.el-icon-upload:before{content:"\e60d"}.el-icon-upload2:before{content:"\e644"}.el-icon-download:before{content:"\e617"}.el-icon-loading:before{content:"\e61e"}.el-icon-loading{-webkit-animation:rotating 2s linear infinite;animation:rotating 2s linear infinite}.el-icon--right{margin-left:5px}.el-icon--left{margin-right:5px}@-webkit-keyframes rotating{0%{-webkit-transform:rotateZ(0);transform:rotateZ(0)}100%{-webkit-transform:rotateZ(360deg);transform:rotateZ(360deg)}}@keyframes rotating{0%{-webkit-transform:rotateZ(0);transform:rotateZ(0)}100%{-webkit-transform:rotateZ(360deg);transform:rotateZ(360deg)}}
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
.el-loading-parent--relative{position:relative!important}.el-loading-parent--hidden{overflow:hidden!important}.el-loading-mask{position:absolute;z-index:2000;background-color:rgba(255,255,255,.9);margin:0;top:0;right:0;bottom:0;left:0;-webkit-transition:opacity .3s;transition:opacity .3s}.el-loading-mask.is-fullscreen{position:fixed}.el-loading-mask.is-fullscreen .el-loading-spinner{margin-top:-25px}.el-loading-mask.is-fullscreen .el-loading-spinner .circular{height:50px;width:50px}.el-loading-spinner{top:50%;margin-top:-21px;width:100%;text-align:center;position:absolute}.el-loading-spinner .el-loading-text{color:#00468F;margin:3px 0;font-size:14px}.el-loading-spinner .circular{height:42px;width:42px;-webkit-animation:loading-rotate 2s linear infinite;animation:loading-rotate 2s linear infinite}.el-loading-spinner .path{-webkit-animation:loading-dash 1.5s ease-in-out infinite;animation:loading-dash 1.5s ease-in-out infinite;stroke-dasharray:90,150;stroke-dashoffset:0;stroke-width:2;stroke:#00468F;stroke-linecap:round}.el-loading-spinner i{color:#00468F}.el-loading-fade-enter,.el-loading-fade-leave-active{opacity:0}@-webkit-keyframes loading-rotate{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes loading-rotate{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes loading-dash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-40px}100%{stroke-dasharray:90,150;stroke-dashoffset:-120px}}@keyframes loading-dash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-40px}100%{stroke-dasharray:90,150;stroke-dashoffset:-120px}}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.el-main{display:block;-webkit-box-flex:1;-ms-flex:1;flex:1;-ms-flex-preferred-size:auto;flex-basis:auto;overflow:auto;-webkit-box-sizing:border-box;box-sizing:border-box;padding:20px}
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
.el-message__closeBtn:focus,.el-message__content:focus{outline-width:0}.el-message{min-width:380px;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:4px;border-width:1px;border-style:solid;border-color:#ebeef5;position:fixed;left:50%;top:20px;-webkit-transform:translateX(-50%);transform:translateX(-50%);background-color:#edf2fc;-webkit-transition:opacity .3s,-webkit-transform .4s;transition:opacity .3s,-webkit-transform .4s;transition:opacity .3s,transform .4s;transition:opacity .3s,transform .4s,-webkit-transform .4s;overflow:hidden;padding:15px 15px 15px 20px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-message.is-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.el-message.is-closable .el-message__content{padding-right:16px}.el-message p{margin:0}.el-message--info .el-message__content{color:#909399}.el-message--success{background-color:#f0f9eb;border-color:#e1f3d8}.el-message--success .el-message__content{color:#67c23a}.el-message--warning{background-color:#fdf6ec;border-color:#faecd8}.el-message--warning .el-message__content{color:#e6a23c}.el-message--error{background-color:#fef0f0;border-color:#fde2e2}.el-message--error .el-message__content{color:#f56c6c}.el-message__icon{margin-right:10px}.el-message__content{padding:0;font-size:14px;line-height:1}.el-message__closeBtn{position:absolute;top:50%;right:15px;-webkit-transform:translateY(-50%);transform:translateY(-50%);cursor:pointer;color:#c0c4cc;font-size:16px}.el-message__closeBtn:hover{color:#909399}.el-message .el-icon-success{color:#67c23a}.el-message .el-icon-error{color:#f56c6c}.el-message .el-icon-info{color:#909399}.el-message .el-icon-warning{color:#e6a23c}.el-message-fade-enter,.el-message-fade-leave-active{opacity:0;-webkit-transform:translate(-50%,-100%);transform:translate(-50%,-100%)}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.el-notification{display:-webkit-box;display:-ms-flexbox;display:flex;width:330px;padding:14px 26px 14px 13px;border-radius:8px;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid #ebeef5;position:fixed;background-color:#fff;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);-webkit-transition:opacity .3s,left .3s,right .3s,top .4s,bottom .3s,-webkit-transform .3s;transition:opacity .3s,left .3s,right .3s,top .4s,bottom .3s,-webkit-transform .3s;transition:opacity .3s,transform .3s,left .3s,right .3s,top .4s,bottom .3s;transition:opacity .3s,transform .3s,left .3s,right .3s,top .4s,bottom .3s,-webkit-transform .3s;overflow:hidden}.el-notification.right{right:16px}.el-notification.left{left:16px}.el-notification__group{margin-left:13px}.el-notification__title{font-weight:700;font-size:16px;color:#303133;margin:0}.el-notification__content{font-size:14px;line-height:21px;margin:6px 0 0;color:#606266;text-align:justify}.el-notification__content p{margin:0}.el-notification__icon{height:24px;width:24px;font-size:24px}.el-notification__closeBtn{position:absolute;top:18px;right:15px;cursor:pointer;color:#909399;font-size:16px}.el-notification__closeBtn:hover{color:#606266}.el-notification .el-icon-success{color:#67c23a}.el-notification .el-icon-error{color:#f56c6c}.el-notification .el-icon-info{color:#909399}.el-notification .el-icon-warning{color:#e6a23c}.el-notification-fade-enter.right{right:0;-webkit-transform:translateX(100%);transform:translateX(100%)}.el-notification-fade-enter.left{left:0;-webkit-transform:translateX(-100%);transform:translateX(-100%)}.el-notification-fade-leave-active{opacity:0}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.el-select-group{margin:0;padding:0}.el-select-group__wrap{position:relative;list-style:none;margin:0;padding:0}.el-select-group__wrap:not(:last-of-type){padding-bottom:24px}.el-select-group__wrap:not(:last-of-type)::after{content:'';position:absolute;display:block;left:20px;right:20px;bottom:12px;height:1px;background:#e4e7ed}.el-select-group__title{padding-left:20px;font-size:12px;color:#909399;line-height:30px}.el-select-group .el-select-dropdown__item{padding-left:20px}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.el-select-dropdown__item{font-size:14px;padding:0 20px;position:relative;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#606266;height:34px;line-height:34px;-webkit-box-sizing:border-box;box-sizing:border-box;cursor:pointer}.el-select-dropdown__item.is-disabled{color:#c0c4cc;cursor:not-allowed}.el-select-dropdown__item.is-disabled:hover{background-color:#fff}.el-select-dropdown__item.hover,.el-select-dropdown__item:hover{background-color:#f5f7fa}.el-select-dropdown__item.selected{color:#00468F;font-weight:700}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
.el-popper .popper__arrow,.el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.el-popper .popper__arrow::after{content:" ";border-width:6px}.el-popper[x-placement^=top]{margin-bottom:12px}.el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#ebeef5;border-bottom-width:0}.el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.el-popper[x-placement^=bottom]{margin-top:12px}.el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#ebeef5}.el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.el-popper[x-placement^=right]{margin-left:12px}.el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#ebeef5;border-left-width:0}.el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.el-popper[x-placement^=left]{margin-right:12px}.el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#ebeef5}.el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}.el-popover{position:absolute;background:#fff;min-width:150px;border-radius:4px;border:1px solid #ebeef5;padding:12px;z-index:2000;color:#606266;line-height:1.4;text-align:justify;font-size:14px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-popover--plain{padding:18px 20px}.el-popover__title{color:#303133;font-size:16px;line-height:1;margin-bottom:12px}.el-popover:focus,.el-popover:focus:active,.el-popover__reference:focus:hover,.el-popover__reference:focus:not(.focusing){outline-width:0}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.el-popper .popper__arrow,.el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.el-popper .popper__arrow::after{content:" ";border-width:6px}.el-popper[x-placement^=top]{margin-bottom:12px}.el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#ebeef5;border-bottom-width:0}.el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.el-popper[x-placement^=bottom]{margin-top:12px}.el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#ebeef5}.el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.el-popper[x-placement^=right]{margin-left:12px}.el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#ebeef5;border-left-width:0}.el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.el-popper[x-placement^=left]{margin-right:12px}.el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#ebeef5}.el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.el-progress{position:relative;line-height:1}.el-progress__text{font-size:14px;color:#606266;display:inline-block;vertical-align:middle;margin-left:10px;line-height:1}.el-progress__text i{vertical-align:middle;display:block}.el-progress--circle{display:inline-block}.el-progress--circle .el-progress__text{position:absolute;top:50%;left:0;width:100%;text-align:center;margin:0;-webkit-transform:translate(0,-50%);transform:translate(0,-50%)}.el-progress--circle .el-progress__text i{vertical-align:middle;display:inline-block}.el-progress--without-text .el-progress__text{display:none}.el-progress--without-text .el-progress-bar{padding-right:0;margin-right:0;display:block}.el-progress-bar,.el-progress-bar__inner::after,.el-progress-bar__innerText{display:inline-block;vertical-align:middle}.el-progress--text-inside .el-progress-bar{padding-right:0;margin-right:0}.el-progress.is-success .el-progress-bar__inner{background-color:#67c23a}.el-progress.is-success .el-progress__text{color:#67c23a}.el-progress.is-exception .el-progress-bar__inner{background-color:#f56c6c}.el-progress.is-exception .el-progress__text{color:#f56c6c}.el-progress-bar{padding-right:50px;width:100%;margin-right:-55px;-webkit-box-sizing:border-box;box-sizing:border-box}.el-progress-bar__outer{height:6px;border-radius:100px;background-color:#ebeef5;overflow:hidden;position:relative;vertical-align:middle}.el-progress-bar__inner{position:absolute;left:0;top:0;height:100%;background-color:#00468F;text-align:right;border-radius:100px;line-height:1;white-space:nowrap;-webkit-transition:width .6s ease;transition:width .6s ease}.el-progress-bar__inner::after{content:"";height:100%}.el-progress-bar__innerText{color:#fff;font-size:12px;margin:0 5px}@-webkit-keyframes progress{0%{background-position:0 0}100%{background-position:32px 0}}@keyframes progress{0%{background-position:0 0}100%{background-position:32px 0}}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
@charset "UTF-8";.el-radio-button,.el-radio-button__inner{display:inline-block;position:relative;outline:0}.el-radio-button__inner{line-height:1;white-space:nowrap;vertical-align:middle;background:#fff;border:1px solid #dcdfe6;font-weight:500;border-left:0;color:#606266;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;cursor:pointer;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);padding:12px 20px;font-size:14px;border-radius:0}.el-radio-button__inner.is-round{padding:12px 20px}.el-radio-button__inner:hover{color:#00468F}.el-radio-button__inner [class*=el-icon-]{line-height:.9}.el-radio-button__inner [class*=el-icon-]+span{margin-left:5px}.el-radio-button:first-child .el-radio-button__inner{border-left:1px solid #dcdfe6;border-radius:4px 0 0 4px;-webkit-box-shadow:none!important;box-shadow:none!important}.el-radio-button__orig-radio{opacity:0;outline:0;position:absolute;z-index:-1}.el-radio-button__orig-radio:checked+.el-radio-button__inner{color:#fff;background-color:#00468F;border-color:#00468F;-webkit-box-shadow:-1px 0 0 0 #00468F;box-shadow:-1px 0 0 0 #00468F}.el-radio-button__orig-radio:disabled+.el-radio-button__inner{color:#c0c4cc;cursor:not-allowed;background-image:none;background-color:#fff;border-color:#ebeef5;-webkit-box-shadow:none;box-shadow:none}.el-radio-button__orig-radio:disabled:checked+.el-radio-button__inner{background-color:#f2f6fc}.el-radio-button:last-child .el-radio-button__inner{border-radius:0 4px 4px 0}.el-radio-button:first-child:last-child .el-radio-button__inner{border-radius:4px}.el-radio-button--medium .el-radio-button__inner{padding:10px 20px;font-size:14px;border-radius:0}.el-radio-button--medium .el-radio-button__inner.is-round{padding:10px 20px}.el-radio-button--small .el-radio-button__inner{padding:9px 15px;font-size:12px;border-radius:0}.el-radio-button--small .el-radio-button__inner.is-round{padding:9px 15px}.el-radio-button--mini .el-radio-button__inner{padding:7px 15px;font-size:12px;border-radius:0}.el-radio-button--mini .el-radio-button__inner.is-round{padding:7px 15px}.el-radio-button:focus:not(.is-focus):not(:active):not(.is-disabled){-webkit-box-shadow:0 0 2px 2px #00468F;box-shadow:0 0 2px 2px #00468F}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.el-radio-group{display:inline-block;line-height:1;vertical-align:middle;font-size:0}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
@charset "UTF-8";.el-radio,.el-radio--medium.is-bordered .el-radio__label{font-size:14px}.el-radio,.el-radio__input{white-space:nowrap;line-height:1;outline:0}.el-radio,.el-radio__inner,.el-radio__input{position:relative;display:inline-block}.el-radio{color:#606266;font-weight:500;cursor:pointer;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.el-radio.is-bordered{padding:12px 20px 0 10px;border-radius:4px;border:1px solid #dcdfe6;-webkit-box-sizing:border-box;box-sizing:border-box;height:40px}.el-radio.is-bordered.is-checked{border-color:#00468F}.el-radio.is-bordered.is-disabled{cursor:not-allowed;border-color:#ebeef5}.el-radio__input.is-disabled .el-radio__inner,.el-radio__input.is-disabled.is-checked .el-radio__inner{background-color:#f5f7fa;border-color:#e4e7ed}.el-radio.is-bordered+.el-radio.is-bordered{margin-left:10px}.el-radio--medium.is-bordered{padding:10px 20px 0 10px;border-radius:4px;height:36px}.el-radio--mini.is-bordered .el-radio__label,.el-radio--small.is-bordered .el-radio__label{font-size:12px}.el-radio--medium.is-bordered .el-radio__inner{height:14px;width:14px}.el-radio--small.is-bordered{padding:8px 15px 0 10px;border-radius:3px;height:32px}.el-radio--small.is-bordered .el-radio__inner{height:12px;width:12px}.el-radio--mini.is-bordered{padding:6px 15px 0 10px;border-radius:3px;height:28px}.el-radio--mini.is-bordered .el-radio__inner{height:12px;width:12px}.el-radio+.el-radio{margin-left:30px}.el-radio__input{cursor:pointer;vertical-align:middle}.el-radio__input.is-disabled .el-radio__inner{cursor:not-allowed}.el-radio__input.is-disabled .el-radio__inner::after{cursor:not-allowed;background-color:#f5f7fa}.el-radio__input.is-disabled .el-radio__inner+.el-radio__label{cursor:not-allowed}.el-radio__input.is-disabled.is-checked .el-radio__inner::after{background-color:#c0c4cc}.el-radio__input.is-disabled+span.el-radio__label{color:#c0c4cc;cursor:not-allowed}.el-radio__input.is-checked .el-radio__inner{border-color:#00468F;background:#00468F}.el-radio__input.is-checked .el-radio__inner::after{-webkit-transform:translate(-50%,-50%) scale(1);transform:translate(-50%,-50%) scale(1)}.el-radio__input.is-checked+.el-radio__label{color:#00468F}.el-radio__input.is-focus .el-radio__inner{border-color:#00468F}.el-radio__inner{border:1px solid #dcdfe6;border-radius:100%;width:14px;height:14px;background-color:#fff;cursor:pointer;-webkit-box-sizing:border-box;box-sizing:border-box}.el-radio__inner:hover{border-color:#00468F}.el-radio__inner::after{width:4px;height:4px;border-radius:100%;background-color:#fff;content:"";position:absolute;left:50%;top:50%;-webkit-transform:translate(-50%,-50%) scale(0);transform:translate(-50%,-50%) scale(0);-webkit-transition:-webkit-transform .15s ease-in;transition:-webkit-transform .15s ease-in;transition:transform .15s ease-in;transition:transform .15s ease-in,-webkit-transform .15s ease-in}.el-radio__original{opacity:0;outline:0;position:absolute;z-index:-1;top:0;left:0;right:0;bottom:0;margin:0}.el-radio:focus:not(.is-focus):not(:active):not(.is-disabled) .el-radio__inner{-webkit-box-shadow:0 0 2px 2px #00468F;box-shadow:0 0 2px 2px #00468F}.el-radio__label{font-size:14px;padding-left:10px}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.el-rate__icon,.el-rate__item{position:relative;display:inline-block}.el-rate{height:20px;line-height:1}.el-rate:active,.el-rate:focus{outline-width:0}.el-rate__item{font-size:0;vertical-align:middle}.el-rate__icon{font-size:18px;margin-right:6px;color:#c0c4cc;-webkit-transition:.3s;transition:.3s}.el-rate__decimal,.el-rate__icon .path2{position:absolute;top:0;left:0}.el-rate__icon.hover{-webkit-transform:scale(1.15);transform:scale(1.15)}.el-rate__decimal{display:inline-block;overflow:hidden}.el-rate__text{font-size:14px;vertical-align:middle}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
@charset "UTF-8";body{font-family:"Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif;font-weight:400;font-size:14px;color:#000;-webkit-font-smoothing:antialiased}a{color:#00468F;text-decoration:none}a:focus,a:hover{color:rgb(51, 107, 165)}a:active{color:rgb(0, 63, 129)}h1,h2,h3,h4,h5,h6{color:#606266;font-weight:inherit}h1:first-child,h2:first-child,h3:first-child,h4:first-child,h5:first-child,h6:first-child,p:first-child{margin-top:0}h1:last-child,h2:last-child,h3:last-child,h4:last-child,h5:last-child,h6:last-child,p:last-child{margin-bottom:0}h1{font-size:20px}h2{font-size:18px}h3{font-size:16px}h4,h5,h6,p{font-size:inherit}p{line-height:1.8}sub,sup{font-size:13px}small{font-size:12px}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.el-row{position:relative;-webkit-box-sizing:border-box;box-sizing:border-box}.el-row::after,.el-row::before{display:table;content:""}.el-row::after{clear:both}.el-row--flex{display:-webkit-box;display:-ms-flexbox;display:flex}.el-row--flex:after,.el-row--flex:before{display:none}.el-row--flex.is-justify-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.el-row--flex.is-justify-end{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.el-row--flex.is-justify-space-between{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.el-row--flex.is-justify-space-around{-ms-flex-pack:distribute;justify-content:space-around}.el-row--flex.is-align-middle{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-row--flex.is-align-bottom{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.el-scrollbar{overflow:hidden;position:relative}.el-scrollbar:active>.el-scrollbar__bar,.el-scrollbar:focus>.el-scrollbar__bar,.el-scrollbar:hover>.el-scrollbar__bar{opacity:1;-webkit-transition:opacity 340ms ease-out;transition:opacity 340ms ease-out}.el-scrollbar__wrap{overflow:scroll;height:100%}.el-scrollbar__wrap--hidden-default::-webkit-scrollbar{width:0;height:0}.el-scrollbar__thumb{position:relative;display:block;width:0;height:0;cursor:pointer;border-radius:inherit;background-color:rgba(144,147,153,.3);-webkit-transition:.3s background-color;transition:.3s background-color}.el-scrollbar__thumb:hover{background-color:rgba(144,147,153,.5)}.el-scrollbar__bar{position:absolute;right:2px;bottom:2px;z-index:1;border-radius:4px;opacity:0;-webkit-transition:opacity 120ms ease-out;transition:opacity 120ms ease-out}.el-scrollbar__bar.is-vertical{width:6px;top:2px}.el-scrollbar__bar.is-vertical>div{width:100%}.el-scrollbar__bar.is-horizontal{height:6px;left:2px}.el-scrollbar__bar.is-horizontal>div{height:100%}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.el-popper .popper__arrow,.el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.el-popper .popper__arrow::after{content:" ";border-width:6px}.el-popper[x-placement^=top]{margin-bottom:12px}.el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#ebeef5;border-bottom-width:0}.el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.el-popper[x-placement^=bottom]{margin-top:12px}.el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#ebeef5}.el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.el-popper[x-placement^=right]{margin-left:12px}.el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#ebeef5;border-left-width:0}.el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.el-popper[x-placement^=left]{margin-right:12px}.el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#ebeef5}.el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}.el-select-dropdown{position:absolute;z-index:1001;border:1px solid #e4e7ed;border-radius:4px;background-color:#fff;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);-webkit-box-sizing:border-box;box-sizing:border-box;margin:5px 0}.el-select-dropdown.is-multiple .el-select-dropdown__item.selected{color:#00468F;background-color:#fff}.el-select-dropdown.is-multiple .el-select-dropdown__item.selected.hover{background-color:#f5f7fa}.el-select-dropdown.is-multiple .el-select-dropdown__item.selected::after{position:absolute;right:20px;font-family:element-icons;content:"\E611";font-size:12px;font-weight:700;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.el-select-dropdown .el-scrollbar.is-empty .el-select-dropdown__list{padding:0}.el-select-dropdown__empty{padding:10px 0;margin:0;text-align:center;color:#999;font-size:14px}.el-select-dropdown__wrap{max-height:274px}.el-select-dropdown__list{list-style:none;padding:6px 0;margin:0;-webkit-box-sizing:border-box;box-sizing:border-box}
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
.el-time-spinner{width:100%;white-space:nowrap}.el-spinner{display:inline-block;vertical-align:middle}.el-spinner-inner{-webkit-animation:rotate 2s linear infinite;animation:rotate 2s linear infinite;width:50px;height:50px}.el-spinner-inner .path{stroke:#ececec;stroke-linecap:round;-webkit-animation:dash 1.5s ease-in-out infinite;animation:dash 1.5s ease-in-out infinite}@-webkit-keyframes rotate{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes rotate{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes dash{0%{stroke-dasharray:1,150;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-35}100%{stroke-dasharray:90,150;stroke-dashoffset:-124}}@keyframes dash{0%{stroke-dasharray:1,150;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-35}100%{stroke-dasharray:90,150;stroke-dashoffset:-124}}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
.el-steps{display:-webkit-box;display:-ms-flexbox;display:flex}.el-steps--simple{padding:13px 8%;border-radius:4px;background:#f5f7fa}.el-steps--horizontal{white-space:nowrap}.el-steps--vertical{height:100%;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-flow:column;flex-flow:column}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.el-switch{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;position:relative;font-size:14px;line-height:20px;height:20px;vertical-align:middle}.el-switch.is-disabled .el-switch__core,.el-switch.is-disabled .el-switch__label{cursor:not-allowed}.el-switch__core,.el-switch__label{display:inline-block;cursor:pointer;vertical-align:middle}.el-switch__label{-webkit-transition:.2s;transition:.2s;height:20px;font-size:14px;font-weight:500;color:#303133}.el-switch__label.is-active{color:#00468F}.el-switch__label--left{margin-right:10px}.el-switch__label--right{margin-left:10px}.el-switch__label *{line-height:1;font-size:14px;display:inline-block}.el-switch__input{position:absolute;width:0;height:0;opacity:0;margin:0}.el-switch__core{margin:0;position:relative;width:40px;height:20px;border:1px solid #dcdfe6;outline:0;border-radius:10px;-webkit-box-sizing:border-box;box-sizing:border-box;background:#dcdfe6;-webkit-transition:border-color .3s,background-color .3s;transition:border-color .3s,background-color .3s}.el-switch__core:after{content:"";position:absolute;top:1px;left:1px;border-radius:100%;-webkit-transition:all .3s;transition:all .3s;width:16px;height:16px;background-color:#fff}.el-switch.is-checked .el-switch__core{border-color:#00468F;background-color:#00468F}.el-switch.is-checked .el-switch__core::after{left:100%;margin-left:-17px}.el-switch.is-disabled{opacity:.6}.el-switch--wide .el-switch__label.el-switch__label--left span{left:10px}.el-switch--wide .el-switch__label.el-switch__label--right span{right:10px}.el-switch .label-fade-enter,.el-switch .label-fade-leave-active{opacity:0}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user