Compare commits
251 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
445ea3bb2e | ||
|
|
0e0aab98b1 | ||
|
|
721d5eb0c1 | ||
|
|
6d99dbc3a7 | ||
|
|
2be31f649b | ||
|
|
828ac27c03 | ||
|
|
b3d614002f | ||
|
|
2b2000ca10 | ||
|
|
8d7428d099 | ||
|
|
57f8322f31 | ||
|
|
bee7bc4294 | ||
|
|
28702065bc | ||
|
|
c248057081 | ||
|
|
6186f5e138 | ||
|
|
2201d8176d | ||
|
|
2ba6819876 | ||
|
|
a393b2a370 | ||
|
|
59fe713df2 | ||
|
|
4b8efaca9a | ||
|
|
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 |
106
LICENSE.md
Normal file
106
LICENSE.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
# CC0 1.0 Universal
|
||||||
|
|
||||||
|
## Statement of Purpose
|
||||||
|
|
||||||
|
The laws of most jurisdictions throughout the world automatically confer
|
||||||
|
exclusive Copyright and Related Rights (defined below) upon the creator and
|
||||||
|
subsequent owner(s) (each and all, an “owner”) of an original work of
|
||||||
|
authorship and/or a database (each, a “Work”).
|
||||||
|
|
||||||
|
Certain owners wish to permanently relinquish those rights to a Work for the
|
||||||
|
purpose of contributing to a commons of creative, cultural and scientific works
|
||||||
|
(“Commons”) that the public can reliably and without fear of later claims of
|
||||||
|
infringement build upon, modify, incorporate in other works, reuse and
|
||||||
|
redistribute as freely as possible in any form whatsoever and for any purposes,
|
||||||
|
including without limitation commercial purposes. These owners may contribute
|
||||||
|
to the Commons to promote the ideal of a free culture and the further
|
||||||
|
production of creative, cultural and scientific works, or to gain reputation or
|
||||||
|
greater distribution for their Work in part through the use and efforts of
|
||||||
|
others.
|
||||||
|
|
||||||
|
For these and/or other purposes and motivations, and without any expectation of
|
||||||
|
additional consideration or compensation, the person associating CC0 with a
|
||||||
|
Work (the “Affirmer”), to the extent that he or she is an owner of Copyright
|
||||||
|
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and
|
||||||
|
publicly distribute the Work under its terms, with knowledge of his or her
|
||||||
|
Copyright and Related Rights in the Work and the meaning and intended legal
|
||||||
|
effect of CC0 on those rights.
|
||||||
|
|
||||||
|
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||||
|
protected by copyright and related or neighboring rights (“Copyright and
|
||||||
|
Related Rights”). Copyright and Related Rights include, but are not limited
|
||||||
|
to, the following:
|
||||||
|
1. the right to reproduce, adapt, distribute, perform, display,
|
||||||
|
communicate, and translate a Work;
|
||||||
|
2. moral rights retained by the original author(s) and/or performer(s);
|
||||||
|
3. publicity and privacy rights pertaining to a person’s image or likeness
|
||||||
|
depicted in a Work;
|
||||||
|
4. rights protecting against unfair competition in regards to a Work,
|
||||||
|
subject to the limitations in paragraph 4(i), below;
|
||||||
|
5. rights protecting the extraction, dissemination, use and reuse of data
|
||||||
|
in a Work;
|
||||||
|
6. database rights (such as those arising under Directive 96/9/EC of the
|
||||||
|
European Parliament and of the Council of 11 March 1996 on the legal
|
||||||
|
protection of databases, and under any national implementation thereof,
|
||||||
|
including any amended or successor version of such directive); and
|
||||||
|
7. other similar, equivalent or corresponding rights throughout the world
|
||||||
|
based on applicable law or treaty, and any national implementations
|
||||||
|
thereof.
|
||||||
|
|
||||||
|
2. Waiver. To the greatest extent permitted by, but not in contravention of,
|
||||||
|
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
|
||||||
|
unconditionally waives, abandons, and surrenders all of Affirmer’s Copyright
|
||||||
|
and Related Rights and associated claims and causes of action, whether now
|
||||||
|
known or unknown (including existing as well as future claims and causes of
|
||||||
|
action), in the Work (i) in all territories worldwide, (ii) for the maximum
|
||||||
|
duration provided by applicable law or treaty (including future time
|
||||||
|
extensions), (iii) in any current or future medium and for any number of
|
||||||
|
copies, and (iv) for any purpose whatsoever, including without limitation
|
||||||
|
commercial, advertising or promotional purposes (the “Waiver”). Affirmer makes
|
||||||
|
the Waiver for the benefit of each member of the public at large and to the
|
||||||
|
detriment of Affirmer’s heirs and successors, fully intending that such Waiver
|
||||||
|
shall not be subject to revocation, rescission, cancellation, termination, or
|
||||||
|
any other legal or equitable action to disrupt the quiet enjoyment of the Work
|
||||||
|
by the public as contemplated by Affirmer’s express Statement of Purpose.
|
||||||
|
|
||||||
|
3. Public License Fallback. Should any part of the Waiver for any reason be
|
||||||
|
judged legally invalid or ineffective under applicable law, then the Waiver
|
||||||
|
shall be preserved to the maximum extent permitted taking into account
|
||||||
|
Affirmer’s express Statement of Purpose. In addition, to the extent the Waiver
|
||||||
|
is so judged Affirmer hereby grants to each affected person a royalty-free, non
|
||||||
|
transferable, non sublicensable, non exclusive, irrevocable and unconditional
|
||||||
|
license to exercise Affirmer’s Copyright and Related Rights in the Work (i) in
|
||||||
|
all territories worldwide, (ii) for the maximum duration provided by applicable
|
||||||
|
law or treaty (including future time extensions), (iii) in any current or
|
||||||
|
future medium and for any number of copies, and (iv) for any purpose
|
||||||
|
whatsoever, including without limitation commercial, advertising or promotional
|
||||||
|
purposes (the “License”). The License shall be deemed effective as of the date
|
||||||
|
CC0 was applied by Affirmer to the Work. Should any part of the License for any
|
||||||
|
reason be judged legally invalid or ineffective under applicable law, such
|
||||||
|
partial invalidity or ineffectiveness shall not invalidate the remainder of the
|
||||||
|
License, and in such case Affirmer hereby affirms that he or she will not (i)
|
||||||
|
exercise any of his or her remaining Copyright and Related Rights in the Work
|
||||||
|
or (ii) assert any associated claims and causes of action with respect to the
|
||||||
|
Work, in either case contrary to Affirmer’s express Statement of Purpose.
|
||||||
|
|
||||||
|
4. Limitations and Disclaimers.
|
||||||
|
1. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||||
|
surrendered, licensed or otherwise affected by this document.
|
||||||
|
2. Affirmer offers the Work as-is and makes no representations or
|
||||||
|
warranties of any kind concerning the Work, express, implied, statutory
|
||||||
|
or otherwise, including without limitation warranties of title,
|
||||||
|
merchantability, fitness for a particular purpose, non infringement, or
|
||||||
|
the absence of latent or other defects, accuracy, or the present or
|
||||||
|
absence of errors, whether or not discoverable, all to the greatest
|
||||||
|
extent permissible under applicable law.
|
||||||
|
3. Affirmer disclaims responsibility for clearing rights of other persons
|
||||||
|
that may apply to the Work or any use thereof, including without
|
||||||
|
limitation any person’s Copyright and Related Rights in the Work.
|
||||||
|
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||||
|
consents, permissions or other rights required for any use of the Work.
|
||||||
|
4. Affirmer understands and acknowledges that Creative Commons is not a
|
||||||
|
party to this document and has no duty or obligation with respect to
|
||||||
|
this CC0 or use of the Work.
|
||||||
|
|
||||||
|
For more information, please see
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/.
|
||||||
41
README.md
41
README.md
@@ -1,3 +1,42 @@
|
|||||||
# Liberama
|
# Liberama
|
||||||
|
|
||||||
Свободный обмен книгами в формате fb2
|
Браузерная онлайн-читалка книг и децентрализованная библиотека.
|
||||||
|
|
||||||
|
Читалка [OmniReader](https://omnireader.ru) является частью данного проекта, размещенной на VPS:
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
## VPS
|
||||||
|
Для разворачивания читалки на чистом VPS с нуля смотрите [docs/omnireader](docs/omnireader/README.md)
|
||||||
|
|
||||||
|
## Сборка проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
$ git clone https://github.com/bookpauk/liberama
|
||||||
|
$ cd liberama
|
||||||
|
$ npm i
|
||||||
|
```
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
```
|
||||||
|
$ npm run build:win
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
```
|
||||||
|
$ npm run build:linux
|
||||||
|
```
|
||||||
|
|
||||||
|
Результат сборки будет доступен в каталоге `dist/linux|win` в виде исполнимого (standalone) файла
|
||||||
|
|
||||||
|
### Разработка
|
||||||
|
```
|
||||||
|
$ npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Помочь проекту
|
||||||
|
|
||||||
|
* bitcoin: 3EbgZ7MK1UVaN38Gty5DCBtS4PknM4Ut85
|
||||||
|
* litecoin: MP39Riec4oSNB3XMjiquKoLWxbufRYNXxZ
|
||||||
|
* monero: 8BQPnvHcPSHM5gMQsmuypDgx9NNsYqwXKfDDuswEyF2Q2ewQSfd2pkK6ydH2wmMyq2JViZvy9DQ35hLMx7g72mFWNJTPtnz
|
||||||
|
|||||||
@@ -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,31 +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');
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
//yandex-метрика для omnireader
|
window.history.replaceState({}, '', '/');
|
||||||
if (this.config.branch == 'production' && this.mode == 'omnireader' && !this.yaMetricsDone) {
|
this.$router.replace({ path: '/reader', query: q });
|
||||||
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
|
|
||||||
m[i].l=1*new Date();k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
|
|
||||||
(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");// eslint-disable-line no-unexpected-multiline
|
|
||||||
|
|
||||||
ym(52347334, "init", {// eslint-disable-line no-undef
|
|
||||||
id:52347334,
|
|
||||||
clickmap:true,
|
|
||||||
trackLinks:true,
|
|
||||||
accurateTrackBounce:true
|
|
||||||
});
|
|
||||||
|
|
||||||
this.yaMetricsDone = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-container>
|
<el-container>
|
||||||
<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 +35,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 +72,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>
|
||||||
@@ -88,7 +91,6 @@
|
|||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
</el-main>
|
</el-main>
|
||||||
|
|
||||||
</el-container>
|
</el-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -106,7 +108,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 +128,7 @@ export default @Component({
|
|||||||
SetPositionPage,
|
SetPositionPage,
|
||||||
SearchPage,
|
SearchPage,
|
||||||
CopyTextPage,
|
CopyTextPage,
|
||||||
HistoryPage,
|
RecentBooksPage,
|
||||||
SettingsPage,
|
SettingsPage,
|
||||||
HelpPage,
|
HelpPage,
|
||||||
ClickMapPage,
|
ClickMapPage,
|
||||||
@@ -136,14 +138,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 +164,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 +182,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;
|
||||||
@@ -202,10 +212,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 +219,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 +239,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 +253,14 @@ 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();
|
||||||
|
|
||||||
|
this.updateRoute();
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,6 +273,15 @@ class Reader extends Vue {
|
|||||||
this.blinkCachedLoad = settings.blinkCachedLoad;
|
this.blinkCachedLoad = settings.blinkCachedLoad;
|
||||||
this.showWhatsNewDialog = settings.showWhatsNewDialog;
|
this.showWhatsNewDialog = settings.showWhatsNewDialog;
|
||||||
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() {
|
||||||
@@ -350,12 +367,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 +397,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -476,7 +495,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 +539,10 @@ class Reader extends Vue {
|
|||||||
page.stopTextScrolling();
|
page.stopTextScrolling();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.scrollingActive) {
|
||||||
|
this.scrollingSetRecentBook.flush();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stopSearch() {
|
stopSearch() {
|
||||||
@@ -568,22 +591,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 +692,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 +719,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 +738,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 +748,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 +795,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 +805,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 +830,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 +862,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 +883,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 +896,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 +969,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 +979,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 +994,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 +1045,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 +1079,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 +1106,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
|
||||||
@@ -334,13 +334,15 @@ class TextPage extends Vue {
|
|||||||
|
|
||||||
this.draw();
|
this.draw();
|
||||||
|
|
||||||
// шрифты хрен знает когда подгружаются в div, поэтому
|
// ширина шрифта некоторое время выдается неверно, поэтому
|
||||||
const parsed = this.parsed;
|
if (!omitLoadFonts) {
|
||||||
await sleep(5000);
|
const parsed = this.parsed;
|
||||||
if (this.parsed === parsed) {
|
await sleep(100);
|
||||||
parsed.force = true;
|
if (this.parsed === parsed) {
|
||||||
this.draw();
|
parsed.force = true;
|
||||||
parsed.force = false;
|
this.draw();
|
||||||
|
parsed.force = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -477,7 +479,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 +487,7 @@ class TextPage extends Vue {
|
|||||||
await sleep(100);
|
await sleep(100);
|
||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
});
|
})().catch(reject); });
|
||||||
};
|
};
|
||||||
return func;
|
return func;
|
||||||
}
|
}
|
||||||
@@ -877,6 +879,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 +978,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 +1016,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 +1127,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 +1143,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,133 @@
|
|||||||
export const versionHistory = [
|
export const versionHistory = [
|
||||||
|
{
|
||||||
|
showUntil: '2020-01-15',
|
||||||
|
header: '0.8.0 (2020-01-02)',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>окончательный переход на https</li>
|
||||||
|
<li>код проекта теперь Open Source: <a href="https://github.com/bookpauk/liberama" target="_blank">https://github.com/bookpauk/liberama</a></li>
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
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,12 @@ 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,
|
||||||
|
enableSitesFilter: true,
|
||||||
|
|
||||||
fontShifts: {},
|
fontShifts: {},
|
||||||
showToolButton: {},
|
showToolButton: {},
|
||||||
|
|||||||
@@ -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}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user