Compare commits
297 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4836a737c6 | ||
|
|
5712b2ee17 | ||
|
|
32dd17694e | ||
|
|
3ebc932a6a | ||
|
|
8f351d9bef | ||
|
|
5ae3ea94e4 | ||
|
|
f203d453a4 | ||
|
|
0d5cba121b | ||
|
|
0cd6a48a46 | ||
|
|
4e07ce2b5c | ||
|
|
85a525e301 | ||
|
|
03e4a6d723 | ||
|
|
ab28af1abe | ||
|
|
7fceed5301 | ||
|
|
0077816afa | ||
|
|
cb01423147 | ||
|
|
61b0712d36 | ||
|
|
12d7843377 | ||
|
|
9293c0a0d4 | ||
|
|
bb9522197a | ||
|
|
450a2e0664 | ||
|
|
41e35f3ec8 | ||
|
|
a9bc98abe3 | ||
|
|
47bca03532 | ||
|
|
942021371c | ||
|
|
ea2f178730 | ||
|
|
4b5c8d9efe | ||
|
|
28ebf13c3a | ||
|
|
5d52e63dd9 | ||
|
|
1a0e024050 | ||
|
|
e627a0d970 | ||
|
|
48668d94ad | ||
|
|
e08c431dd9 | ||
|
|
5ee58ad6f0 | ||
|
|
ac0a4f0586 | ||
|
|
b6f4c153e5 | ||
|
|
4fdaf5f555 | ||
|
|
b4ee9d6c00 | ||
|
|
7c73c74730 | ||
|
|
c20aa089fa | ||
|
|
b0e15c22ea | ||
|
|
d58a2c065a | ||
|
|
53135e7ee8 | ||
|
|
5c48ca9e6c | ||
|
|
c4a280f3d8 | ||
|
|
ba2943c722 | ||
|
|
26f6ffc83a | ||
|
|
bcf075a72c | ||
|
|
02d458d192 | ||
|
|
a349d8af68 | ||
|
|
0dbaf32aac | ||
|
|
e8c41ef3a8 | ||
|
|
e43a44e986 | ||
|
|
f14b8ed277 | ||
|
|
bbfe8a64cb | ||
|
|
bcf3c2dab0 | ||
|
|
d5404fd260 | ||
|
|
54bc662e43 | ||
|
|
42546ca97e | ||
|
|
5c13cf0eb9 | ||
|
|
2a9d44ae9a | ||
|
|
38414ae7b6 | ||
|
|
3ecb3e80ac | ||
|
|
4968828488 | ||
|
|
4db3cd24df | ||
|
|
45c6d3da77 | ||
|
|
4aab1da3c6 | ||
|
|
bf5dfa1c15 | ||
|
|
7549bdd2b4 | ||
|
|
1bb2525ab2 | ||
|
|
22a556f612 | ||
|
|
056611e87c | ||
|
|
6debe24880 | ||
|
|
56559bddab | ||
|
|
9ec74eccb4 | ||
|
|
3d2f45c20d | ||
|
|
fb2eedd5ba | ||
|
|
e278b4a00e | ||
|
|
0beaa611f6 | ||
|
|
14ca2daa39 | ||
|
|
714eb3ae83 | ||
|
|
6286d663c9 | ||
|
|
b5db2079d2 | ||
|
|
b3b30b9bd9 | ||
|
|
0b6a726503 | ||
|
|
609334c5a6 | ||
|
|
4852c7aec3 | ||
|
|
b1e3d33694 | ||
|
|
2bfc557071 | ||
|
|
e1216109bc | ||
|
|
990b8f390c | ||
|
|
e6f6cd4ff3 | ||
|
|
7deb745651 | ||
|
|
70f3ca8067 | ||
|
|
bb8497a997 | ||
|
|
2127e2ec0a | ||
|
|
9be4011d54 | ||
|
|
c534edfeb5 | ||
|
|
adc8cd7243 | ||
|
|
522d2d3b9c | ||
|
|
046933a05e | ||
|
|
9143288de2 | ||
|
|
6053ca6c0e | ||
|
|
084197530e | ||
|
|
9f366ca811 | ||
|
|
7c07e6f004 | ||
|
|
3d4d7e0342 | ||
|
|
1a8f241aad | ||
|
|
33e938b76a | ||
|
|
e2db546066 | ||
|
|
def9ee52e2 | ||
|
|
1afe10be03 | ||
|
|
fa44641fa2 | ||
|
|
9a1ef85c93 | ||
|
|
b848cf5aa7 | ||
|
|
8057e18ebc | ||
|
|
76e09ef34e | ||
|
|
00cb2dc274 | ||
|
|
ed46e91432 | ||
|
|
88d75fb0d8 | ||
|
|
a1d7a73459 | ||
|
|
687f89729b | ||
|
|
6bf678e01f | ||
|
|
a18aec2f96 | ||
|
|
1c0cf303a0 | ||
|
|
5c7ae73982 | ||
|
|
4e9c69a1cf | ||
|
|
78375be8bf | ||
|
|
b684725094 | ||
|
|
ff52602c3a | ||
|
|
ce704c5e26 | ||
|
|
4503e4ed17 | ||
|
|
01c384c43a | ||
|
|
dda2de58a8 | ||
|
|
0365acbf7a | ||
|
|
bbf1ab7180 | ||
|
|
83bf1f1d3a | ||
|
|
fdf04fed0e | ||
|
|
acce32bfa7 | ||
|
|
614c45ac7d | ||
|
|
c4c0199a1b | ||
|
|
a53ebb9355 | ||
|
|
06e12930c7 | ||
|
|
0f7655773a | ||
|
|
26660461d4 | ||
|
|
b41ee91db5 | ||
|
|
746dd8d37a | ||
|
|
fb4a57027d | ||
|
|
c97660bed0 | ||
|
|
fd8c8812a3 | ||
|
|
0101392858 | ||
|
|
cc3f82d693 | ||
|
|
d21997c918 | ||
|
|
74fec12f5c | ||
|
|
59525f8fa7 | ||
|
|
3c6d3befb2 | ||
|
|
dfa72c80bc | ||
|
|
c6e534b9db | ||
|
|
032ab6a85d | ||
|
|
830c066ebf | ||
|
|
c432388515 | ||
|
|
476deba93a | ||
|
|
ffb4f2386d | ||
|
|
21716163cb | ||
|
|
ca924148a5 | ||
|
|
37aa9b84ae | ||
|
|
c7bd7d4d7d | ||
|
|
d81a50e696 | ||
|
|
dda9943dbe | ||
|
|
2b4b9f24a1 | ||
|
|
2af77f22d6 | ||
|
|
f142e5812d | ||
|
|
ed901fc181 | ||
|
|
87a068899a | ||
|
|
115f683128 | ||
|
|
111568fc2e | ||
|
|
825136b5ff | ||
|
|
eae34b1121 | ||
|
|
b9d7a6a3bb | ||
|
|
1e5375f8f9 | ||
|
|
f597c603bf | ||
|
|
b93dd0a59e | ||
|
|
a5740e4349 | ||
|
|
dacbd05911 | ||
|
|
65c66e0feb | ||
|
|
52f9131f99 | ||
|
|
cfc946ad12 | ||
|
|
a207a0554c | ||
|
|
675e898163 | ||
|
|
d2167d8605 | ||
|
|
de849d3447 | ||
|
|
6c20b0b83e | ||
|
|
a09b70a991 | ||
|
|
2427a3e08b | ||
|
|
1104f9b850 | ||
|
|
dc48700e9e | ||
|
|
f0b0c39328 | ||
|
|
aad74cf682 | ||
|
|
d449478204 | ||
|
|
d4f6536caa | ||
|
|
1eac00f71c | ||
|
|
ca1170a9f0 | ||
|
|
79dda03bac | ||
|
|
6c8e0b8573 | ||
|
|
17c14722fd | ||
|
|
48612ee118 | ||
|
|
205c676999 | ||
|
|
54e0dd0478 | ||
|
|
2de8d7515e | ||
|
|
a251d16432 | ||
|
|
599caba912 | ||
|
|
3477c43465 | ||
|
|
200dac7946 | ||
|
|
e60829946d | ||
|
|
ef12a84285 | ||
|
|
6a18ae3f27 | ||
|
|
a250e95950 | ||
|
|
b174ae452b | ||
|
|
0b63bce357 | ||
|
|
de0d10e792 | ||
|
|
b358b340b4 | ||
|
|
455aba7f4f | ||
|
|
fde0437157 | ||
|
|
480c95bd63 | ||
|
|
972f957685 | ||
|
|
40ff04e5dc | ||
|
|
b3c028bd7a | ||
|
|
51ec6a54fa | ||
|
|
7a29b16ee8 | ||
|
|
7af6fd8248 | ||
|
|
e1c93169b5 | ||
|
|
f4716d5a1e | ||
|
|
f5c06ce420 | ||
|
|
9492f85d80 | ||
|
|
b1303a3ba2 | ||
|
|
5c9cfe5e6f | ||
|
|
b89b5322b8 | ||
|
|
945feba6b2 | ||
|
|
c8af4b907b | ||
|
|
298e8928cf | ||
|
|
8cb67d2976 | ||
|
|
32b8382641 | ||
|
|
007e97463b | ||
|
|
e4f190698d | ||
|
|
b3be07b17e | ||
|
|
72f8977071 | ||
|
|
3dbf00344e | ||
|
|
ffdf0b12cd | ||
|
|
a51150c729 | ||
|
|
37e14b397c | ||
|
|
e48af7ee7d | ||
|
|
3eb3dd371a | ||
|
|
8ef6551560 | ||
|
|
b1f5f3dd28 | ||
|
|
6074c4b7bd | ||
|
|
9906dd43c7 | ||
|
|
17699f66f8 | ||
|
|
80a29e654d | ||
|
|
4184fda247 | ||
|
|
7460ff7055 | ||
|
|
3137b86cee | ||
|
|
b2ca84bb7e | ||
|
|
7d692dd730 | ||
|
|
8850a89aa7 | ||
|
|
57b01dd204 | ||
|
|
8aa1da36b6 | ||
|
|
2dbe29d632 | ||
|
|
7fa891b4fc | ||
|
|
6cb7412cf3 | ||
|
|
157322834b | ||
|
|
1a13a0fee1 | ||
|
|
37256255bf | ||
|
|
75e01c899e | ||
|
|
ef0d6eab89 | ||
|
|
5d54b1b0f4 | ||
|
|
522f953b4f | ||
|
|
15f02c7115 | ||
|
|
174c877eee | ||
|
|
fd9ec736d7 | ||
|
|
2c94025ba3 | ||
|
|
bfadf35c40 | ||
|
|
f3b69caa12 | ||
|
|
18a83a5b0b | ||
|
|
bd9669b782 | ||
|
|
e05713aa7f | ||
|
|
bc3e1f0a6f | ||
|
|
063d01b5ca | ||
|
|
81c38d7749 | ||
|
|
a29842b084 | ||
|
|
bb5adcdaf6 | ||
|
|
537e17a219 | ||
|
|
03ce50153e | ||
|
|
15d01ad7fc | ||
|
|
e2b29e2c2f | ||
|
|
ce7ae84e0f | ||
|
|
01eb545f15 | ||
|
|
706738c7f1 |
6
.babelrc
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"presets": [['@babel/preset-env', { "targets": { "esmodules": true } }]],
|
||||||
|
"plugins": [
|
||||||
|
["@babel/plugin-proposal-decorators", { "legacy": true }]
|
||||||
|
]
|
||||||
|
}
|
||||||
19
.eslintrc
@@ -1,17 +1,18 @@
|
|||||||
{
|
{
|
||||||
|
"parser": "vue-eslint-parser",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"parser": "babel-eslint"
|
"parser": "@babel/eslint-parser",
|
||||||
|
"sourceType": "module"
|
||||||
},
|
},
|
||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:vue/essential"
|
"plugin:vue/recommended"
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"vue",
|
"@babel"
|
||||||
"html",
|
|
||||||
"node"
|
|
||||||
],
|
],
|
||||||
"env": {
|
"env": {
|
||||||
|
"es6": true,
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"node": true
|
"node": true
|
||||||
},
|
},
|
||||||
@@ -24,6 +25,14 @@
|
|||||||
"LM_TOTAL": false
|
"LM_TOTAL": false
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"vue/html-indent": ["warn", 4, {
|
||||||
|
"alignAttributesVertically": false
|
||||||
|
}],
|
||||||
|
"vue/max-attributes-per-line": "off",
|
||||||
|
"vue/html-self-closing": "off",
|
||||||
|
"vue/no-v-html": "off",
|
||||||
|
"vue/no-v-model-argument": "off",
|
||||||
|
|
||||||
"strict": 0,
|
"strict": 0,
|
||||||
"indent": [0, 4, {
|
"indent": [0, 4, {
|
||||||
"SwitchCase": 1
|
"SwitchCase": 1
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Браузерная онлайн-читалка книг и децентрализованная библиотека.
|
Браузерная онлайн-читалка книг и децентрализованная библиотека.
|
||||||
|
|
||||||
Читалка [OmniReader](https://omnireader.ru) является частью данного проекта, размещенной на VPS:
|
Читалка <img src="https://omnireader.ru/favicon.ico" width="14px"/>[OmniReader](https://omnireader.ru) является частью данного проекта, размещенной на VPS:
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
@@ -11,7 +11,7 @@
|
|||||||
Для разворачивания читалки на чистом VPS с нуля смотрите [docs/omnireader.ru](docs/omnireader.ru/README.md)
|
Для разворачивания читалки на чистом VPS с нуля смотрите [docs/omnireader.ru](docs/omnireader.ru/README.md)
|
||||||
|
|
||||||
## Сборка проекта
|
## Сборка проекта
|
||||||
Необходима версия node.js не ниже 10.
|
Необходима версия node.js не ниже 14.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ git clone https://github.com/bookpauk/liberama
|
$ git clone https://github.com/bookpauk/liberama
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const util = require('util');
|
|||||||
const stream = require('stream');
|
const stream = require('stream');
|
||||||
const pipeline = util.promisify(stream.pipeline);
|
const pipeline = util.promisify(stream.pipeline);
|
||||||
|
|
||||||
const got = require('got');
|
const axios = require('axios');
|
||||||
const FileDecompressor = require('../server/core/FileDecompressor');
|
const FileDecompressor = require('../server/core/FileDecompressor');
|
||||||
|
|
||||||
const distDir = path.resolve(__dirname, '../dist');
|
const distDir = path.resolve(__dirname, '../dist');
|
||||||
@@ -24,12 +24,13 @@ async function main() {
|
|||||||
await fs.ensureDir(tempDownloadDir);
|
await fs.ensureDir(tempDownloadDir);
|
||||||
|
|
||||||
//sqlite3
|
//sqlite3
|
||||||
const sqliteRemoteUrl = 'https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v4.1.1/node-v72-linux-x64.tar.gz';
|
const sqliteRemoteUrl = 'https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v5.0.2/napi-v3-linux-x64.tar.gz';
|
||||||
const sqliteDecompressedFilename = `${tempDownloadDir}/node-v72-linux-x64/node_sqlite3.node`;
|
const sqliteDecompressedFilename = `${tempDownloadDir}/napi-v3-linux-x64/node_sqlite3.node`;
|
||||||
|
|
||||||
if (!await fs.pathExists(sqliteDecompressedFilename)) {
|
if (!await fs.pathExists(sqliteDecompressedFilename)) {
|
||||||
// Скачиваем node_sqlite3.node для винды, т.к. pkg не включает его в сборку
|
// Скачиваем node_sqlite3.node для винды, т.к. pkg не включает его в сборку
|
||||||
await pipeline(got.stream(sqliteRemoteUrl), fs.createWriteStream(`${tempDownloadDir}/sqlite.tar.gz`));
|
const res = await axios.get(sqliteRemoteUrl, {responseType: 'stream'})
|
||||||
|
await pipeline(res.data, fs.createWriteStream(`${tempDownloadDir}/sqlite.tar.gz`));
|
||||||
console.log(`done downloading ${sqliteRemoteUrl}`);
|
console.log(`done downloading ${sqliteRemoteUrl}`);
|
||||||
|
|
||||||
//распаковываем
|
//распаковываем
|
||||||
@@ -46,7 +47,8 @@ async function main() {
|
|||||||
// Скачиваем ipfs
|
// Скачиваем ipfs
|
||||||
const ipfsRemoteUrl = 'https://dist.ipfs.io/go-ipfs/v0.4.18/go-ipfs_v0.4.18_linux-amd64.tar.gz';
|
const ipfsRemoteUrl = 'https://dist.ipfs.io/go-ipfs/v0.4.18/go-ipfs_v0.4.18_linux-amd64.tar.gz';
|
||||||
|
|
||||||
await pipeline(got.stream(ipfsRemoteUrl), fs.createWriteStream(`${tempDownloadDir}/ipfs.tar.gz`));
|
const res = await axios.get(ipfsRemoteUrl, {responseType: 'stream'})
|
||||||
|
await pipeline(res.data, fs.createWriteStream(`${tempDownloadDir}/ipfs.tar.gz`));
|
||||||
console.log(`done downloading ${ipfsRemoteUrl}`);
|
console.log(`done downloading ${ipfsRemoteUrl}`);
|
||||||
|
|
||||||
//распаковываем
|
//распаковываем
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
//const webpack = require('webpack');
|
const DefinePlugin = require('webpack').DefinePlugin;
|
||||||
const VueLoaderPlugin = require('vue-loader/lib/plugin');
|
const { VueLoaderPlugin } = require('vue-loader');
|
||||||
|
|
||||||
const clientDir = path.resolve(__dirname, '../client');
|
const clientDir = path.resolve(__dirname, '../client');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
ws: false,
|
||||||
|
//vue: '@vue/compat'
|
||||||
|
}
|
||||||
|
},
|
||||||
entry: [`${clientDir}/main.js`],
|
entry: [`${clientDir}/main.js`],
|
||||||
output: {
|
output: {
|
||||||
publicPath: '/app/',
|
publicPath: '/app/',
|
||||||
@@ -14,58 +20,61 @@ module.exports = {
|
|||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.vue$/,
|
test: /\.vue$/,
|
||||||
loader: "vue-loader"
|
loader: 'vue-loader',
|
||||||
|
/*options: {
|
||||||
|
compilerOptions: {
|
||||||
|
compatConfig: {
|
||||||
|
MODE: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.includer$/,
|
|
||||||
resourceQuery: /^\?vue/,
|
resourceQuery: /^\?vue/,
|
||||||
use: path.resolve('build/includer.js')
|
use: path.resolve(__dirname, 'includer.js')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
loader: 'babel-loader',
|
loader: 'babel-loader',
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
query: {
|
options: {
|
||||||
|
presets: [['@babel/preset-env', { targets: { esmodules: true } }]],
|
||||||
plugins: [
|
plugins: [
|
||||||
'syntax-dynamic-import',
|
['@babel/plugin-proposal-decorators', { legacy: true }]
|
||||||
'transform-decorators-legacy',
|
|
||||||
'transform-class-properties',
|
|
||||||
// ["component", { "libraryName": "element-ui", "styleLibraryName": `~${clientDir}/theme` } ]
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.gif$/,
|
test: /\.(gif|png)$/,
|
||||||
loader: "url-loader",
|
type: 'asset/inline',
|
||||||
options: {
|
|
||||||
name: "images/[name]-[hash:6].[ext]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.png$/,
|
|
||||||
loader: "url-loader",
|
|
||||||
options: {
|
|
||||||
name: "images/[name]-[hash:6].[ext]"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.jpg$/,
|
test: /\.jpg$/,
|
||||||
loader: "file-loader",
|
type: 'asset/resource',
|
||||||
options: {
|
generator: {
|
||||||
name: "images/[name]-[hash:6].[ext]"
|
filename: 'images/[name]-[hash:6][ext]'
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
test: /\.(ttf|eot|woff|woff2)$/,
|
test: /\.(ttf|eot|woff|woff2)$/,
|
||||||
loader: "file-loader",
|
type: 'asset/resource',
|
||||||
options: {
|
generator: {
|
||||||
name: "fonts/[name]-[hash:6].[ext]"
|
filename: 'fonts/[name]-[hash:6][ext]'
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
|
new DefinePlugin({
|
||||||
|
__VUE_OPTIONS_API__: true,
|
||||||
|
__VUE_PROD_DEVTOOLS__: false,
|
||||||
|
__QUASAR_SSR__: false,
|
||||||
|
__QUASAR_SSR_SERVER__: false,
|
||||||
|
__QUASAR_SSR_CLIENT__: false,
|
||||||
|
__QUASAR_VERSION__: false,
|
||||||
|
}),
|
||||||
new VueLoaderPlugin(),
|
new VueLoaderPlugin(),
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
|
|
||||||
const merge = require('webpack-merge');
|
const { merge } = require('webpack-merge');
|
||||||
const baseWpConfig = require('./webpack.base.config');
|
const baseWpConfig = require('./webpack.base.config');
|
||||||
|
|
||||||
baseWpConfig.entry.unshift('webpack-hot-middleware/client');
|
baseWpConfig.entry.unshift('webpack-hot-middleware/client');
|
||||||
@@ -13,7 +13,7 @@ const clientDir = path.resolve(__dirname, '../client');
|
|||||||
|
|
||||||
module.exports = merge(baseWpConfig, {
|
module.exports = merge(baseWpConfig, {
|
||||||
mode: 'development',
|
mode: 'development',
|
||||||
devtool: "#inline-source-map",
|
devtool: 'inline-source-map',
|
||||||
output: {
|
output: {
|
||||||
path: `${publicDir}/app`,
|
path: `${publicDir}/app`,
|
||||||
filename: 'bundle.js'
|
filename: 'bundle.js'
|
||||||
@@ -38,6 +38,6 @@ 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({patterns: [{from: `${clientDir}/assets/*`, to: `${publicDir}/`}]})
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
//const webpack = require('webpack');
|
//const webpack = require('webpack');
|
||||||
|
|
||||||
const merge = require('webpack-merge');
|
const { merge } = require('webpack-merge');
|
||||||
const baseWpConfig = require('./webpack.base.config');
|
const baseWpConfig = require('./webpack.base.config');
|
||||||
const TerserPlugin = require('terser-webpack-plugin');
|
const TerserPlugin = require('terser-webpack-plugin');
|
||||||
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin');
|
||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
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 {GenerateSW} = require('workbox-webpack-plugin');
|
const {GenerateSW} = require('workbox-webpack-plugin');
|
||||||
@@ -34,19 +34,18 @@ module.exports = merge(baseWpConfig, {
|
|||||||
optimization: {
|
optimization: {
|
||||||
minimizer: [
|
minimizer: [
|
||||||
new TerserPlugin({
|
new TerserPlugin({
|
||||||
cache: true,
|
|
||||||
parallel: true,
|
parallel: true,
|
||||||
terserOptions: {
|
terserOptions: {
|
||||||
output: {
|
format: {
|
||||||
comments: false,
|
comments: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
new OptimizeCSSAssetsPlugin()
|
new CssMinimizerWebpackPlugin()
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new CleanWebpackPlugin([publicDir], {root: path.resolve(__dirname, '..')}),
|
//new CleanWebpackPlugin({ cleanOnceBeforeBuildPatterns: [`${publicDir}/**`] }),
|
||||||
new MiniCssExtractPlugin({
|
new MiniCssExtractPlugin({
|
||||||
filename: "[name].[contenthash].css"
|
filename: "[name].[contenthash].css"
|
||||||
}),
|
}),
|
||||||
@@ -54,7 +53,9 @@ 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({patterns:
|
||||||
|
[{from: `${clientDir}/assets/*`, to: `${publicDir}/`, context: `${clientDir}/assets` }]
|
||||||
|
}),
|
||||||
new GenerateSW({
|
new GenerateSW({
|
||||||
cacheId: 'liberama',
|
cacheId: 'liberama',
|
||||||
swDest: `${publicDir}/service-worker.js`,
|
swDest: `${publicDir}/service-worker.js`,
|
||||||
|
|||||||
12
build/win.js
@@ -4,7 +4,7 @@ const util = require('util');
|
|||||||
const stream = require('stream');
|
const stream = require('stream');
|
||||||
const pipeline = util.promisify(stream.pipeline);
|
const pipeline = util.promisify(stream.pipeline);
|
||||||
|
|
||||||
const got = require('got');
|
const axios = require('axios');
|
||||||
const FileDecompressor = require('../server/core/FileDecompressor');
|
const FileDecompressor = require('../server/core/FileDecompressor');
|
||||||
|
|
||||||
const distDir = path.resolve(__dirname, '../dist');
|
const distDir = path.resolve(__dirname, '../dist');
|
||||||
@@ -24,12 +24,13 @@ async function main() {
|
|||||||
await fs.ensureDir(tempDownloadDir);
|
await fs.ensureDir(tempDownloadDir);
|
||||||
|
|
||||||
//sqlite3
|
//sqlite3
|
||||||
const sqliteRemoteUrl = 'https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v4.1.1/node-v72-win32-x64.tar.gz';
|
const sqliteRemoteUrl = 'https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v5.0.2/napi-v3-win32-x64.tar.gz';
|
||||||
const sqliteDecompressedFilename = `${tempDownloadDir}/node-v72-win32-x64/node_sqlite3.node`;
|
const sqliteDecompressedFilename = `${tempDownloadDir}/napi-v3-win32-x64/node_sqlite3.node`;
|
||||||
|
|
||||||
if (!await fs.pathExists(sqliteDecompressedFilename)) {
|
if (!await fs.pathExists(sqliteDecompressedFilename)) {
|
||||||
// Скачиваем node_sqlite3.node для винды, т.к. pkg не включает его в сборку
|
// Скачиваем node_sqlite3.node для винды, т.к. pkg не включает его в сборку
|
||||||
await pipeline(got.stream(sqliteRemoteUrl), fs.createWriteStream(`${tempDownloadDir}/sqlite.tar.gz`));
|
const res = await axios.get(sqliteRemoteUrl, {responseType: 'stream'})
|
||||||
|
await pipeline(res.data, fs.createWriteStream(`${tempDownloadDir}/sqlite.tar.gz`));
|
||||||
console.log(`done downloading ${sqliteRemoteUrl}`);
|
console.log(`done downloading ${sqliteRemoteUrl}`);
|
||||||
|
|
||||||
//распаковываем
|
//распаковываем
|
||||||
@@ -46,7 +47,8 @@ async function main() {
|
|||||||
// Скачиваем ipfs
|
// Скачиваем ipfs
|
||||||
const ipfsRemoteUrl = 'https://dist.ipfs.io/go-ipfs/v0.4.18/go-ipfs_v0.4.18_windows-amd64.zip';
|
const ipfsRemoteUrl = 'https://dist.ipfs.io/go-ipfs/v0.4.18/go-ipfs_v0.4.18_windows-amd64.zip';
|
||||||
|
|
||||||
await pipeline(got.stream(ipfsRemoteUrl), fs.createWriteStream(`${tempDownloadDir}/ipfs.zip`));
|
const res = await axios.get(ipfsRemoteUrl, {responseType: 'stream'})
|
||||||
|
await pipeline(res.data, fs.createWriteStream(`${tempDownloadDir}/ipfs.zip`));
|
||||||
console.log(`done downloading ${ipfsRemoteUrl}`);
|
console.log(`done downloading ${ipfsRemoteUrl}`);
|
||||||
|
|
||||||
//распаковываем
|
//распаковываем
|
||||||
|
|||||||
@@ -9,12 +9,11 @@ class Misc {
|
|||||||
async loadConfig() {
|
async loadConfig() {
|
||||||
|
|
||||||
const query = {params: [
|
const query = {params: [
|
||||||
'name', 'version', 'mode', 'maxUploadFileSize', 'useExternalBookConverter', 'branch',
|
'name', 'version', 'mode', 'maxUploadFileSize', 'useExternalBookConverter', 'acceptFileExt', 'branch',
|
||||||
]};
|
]};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await wsc.open();
|
const config = await wsc.message(await wsc.send(Object.assign({action: 'get-config'}, query)));
|
||||||
const config = await wsc.message(wsc.send(Object.assign({action: 'get-config'}, query)));
|
|
||||||
if (config.error)
|
if (config.error)
|
||||||
throw new Error(config.error);
|
throw new Error(config.error);
|
||||||
return config;
|
return config;
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ class Reader {
|
|||||||
|
|
||||||
let response = {};
|
let response = {};
|
||||||
try {
|
try {
|
||||||
await wsc.open();
|
const requestId = await wsc.send({action: 'worker-get-state-finish', workerId});
|
||||||
const requestId = wsc.send({action: 'worker-get-state-finish', workerId});
|
|
||||||
|
|
||||||
let prevResponse = false;
|
let prevResponse = false;
|
||||||
while (1) {// eslint-disable-line no-constant-condition
|
while (1) {// eslint-disable-line no-constant-condition
|
||||||
@@ -66,7 +65,7 @@ class Reader {
|
|||||||
await utils.sleep(refreshPause);
|
await utils.sleep(refreshPause);
|
||||||
|
|
||||||
i++;
|
i++;
|
||||||
if (i > 120*1000/refreshPause) {//2 мин ждем телодвижений воркера
|
if (i > 180*1000/refreshPause) {//3 мин ждем телодвижений воркера
|
||||||
throw new Error('Слишком долгое время ожидания');
|
throw new Error('Слишком долгое время ожидания');
|
||||||
}
|
}
|
||||||
//проверка воркера
|
//проверка воркера
|
||||||
@@ -124,8 +123,7 @@ class Reader {
|
|||||||
let response = null
|
let response = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await wsc.open();
|
response = await wsc.message(await wsc.send({action: 'reader-restore-cached-file', path: url}));
|
||||||
response = await wsc.message(wsc.send({action: 'reader-restore-cached-file', path: url}));
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
//если с WebSocket проблема, работаем по http
|
//если с WebSocket проблема, работаем по http
|
||||||
@@ -210,8 +208,7 @@ class Reader {
|
|||||||
async storage(request) {
|
async storage(request) {
|
||||||
let response = null;
|
let response = null;
|
||||||
try {
|
try {
|
||||||
await wsc.open();
|
response = await wsc.message(await wsc.send({action: 'reader-storage', body: request}));
|
||||||
response = await wsc.message(wsc.send({action: 'reader-storage', body: request}));
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
//если с WebSocket проблема, работаем по http
|
//если с WebSocket проблема, работаем по http
|
||||||
@@ -222,7 +219,7 @@ class Reader {
|
|||||||
const state = response.state;
|
const state = response.state;
|
||||||
if (!state)
|
if (!state)
|
||||||
throw new Error('Неверный ответ api');
|
throw new Error('Неверный ответ api');
|
||||||
if (response.state == 'error') {
|
if (state == 'error') {
|
||||||
throw new Error(response.error);
|
throw new Error(response.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,185 +1,3 @@
|
|||||||
import * as utils from '../share/utils';
|
import WebSocketConnection from '../../server/core/WebSocketConnection';
|
||||||
|
|
||||||
const cleanPeriod = 60*1000;//1 минута
|
|
||||||
|
|
||||||
class WebSocketConnection {
|
|
||||||
//messageLifeTime в минутах (cleanPeriod)
|
|
||||||
constructor(messageLifeTime = 5) {
|
|
||||||
this.ws = null;
|
|
||||||
this.timer = null;
|
|
||||||
this.listeners = [];
|
|
||||||
this.messageQueue = [];
|
|
||||||
this.messageLifeTime = messageLifeTime;
|
|
||||||
this.requestId = 0;
|
|
||||||
|
|
||||||
this.connecting = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
addListener(listener) {
|
|
||||||
if (this.listeners.indexOf(listener) < 0)
|
|
||||||
this.listeners.push(Object.assign({regTime: Date.now()}, listener));
|
|
||||||
}
|
|
||||||
|
|
||||||
//рассылаем сообщение и удаляем те обработчики, которые его получили
|
|
||||||
emit(mes, isError) {
|
|
||||||
const len = this.listeners.length;
|
|
||||||
if (len > 0) {
|
|
||||||
let newListeners = [];
|
|
||||||
for (const listener of this.listeners) {
|
|
||||||
let emitted = false;
|
|
||||||
if (isError) {
|
|
||||||
if (listener.onError)
|
|
||||||
listener.onError(mes);
|
|
||||||
emitted = true;
|
|
||||||
} else {
|
|
||||||
if (listener.onMessage) {
|
|
||||||
if (listener.requestId) {
|
|
||||||
if (listener.requestId === mes.requestId) {
|
|
||||||
listener.onMessage(mes);
|
|
||||||
emitted = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
listener.onMessage(mes);
|
|
||||||
emitted = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
emitted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!emitted)
|
|
||||||
newListeners.push(listener);
|
|
||||||
}
|
|
||||||
this.listeners = newListeners;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.listeners.length != len;
|
|
||||||
}
|
|
||||||
|
|
||||||
open(url) {
|
|
||||||
return new Promise((resolve, reject) => { (async() => {
|
|
||||||
//Ожидаем окончания процесса подключения, если open уже был вызван
|
|
||||||
let i = 0;
|
|
||||||
while (this.connecting && i < 200) {//10 сек
|
|
||||||
await utils.sleep(50);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
if (i >= 200)
|
|
||||||
this.connecting = false;
|
|
||||||
|
|
||||||
//проверим подключение, и если нет, то подключимся заново
|
|
||||||
if (this.ws && this.ws.readyState == WebSocket.OPEN) {
|
|
||||||
resolve(this.ws);
|
|
||||||
} else {
|
|
||||||
this.connecting = true;
|
|
||||||
const protocol = (window.location.protocol == 'https:' ? 'wss:' : 'ws:');
|
|
||||||
|
|
||||||
url = url || `${protocol}//${window.location.host}/ws`;
|
|
||||||
|
|
||||||
this.ws = new WebSocket(url);
|
|
||||||
|
|
||||||
if (this.timer) {
|
|
||||||
clearTimeout(this.timer);
|
|
||||||
}
|
|
||||||
this.timer = setTimeout(() => { this.periodicClean(); }, cleanPeriod);
|
|
||||||
|
|
||||||
this.ws.onopen = (e) => {
|
|
||||||
this.connecting = false;
|
|
||||||
resolve(e);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.ws.onmessage = (e) => {
|
|
||||||
try {
|
|
||||||
const mes = JSON.parse(e.data);
|
|
||||||
this.messageQueue.push({regTime: Date.now(), mes});
|
|
||||||
|
|
||||||
let newMessageQueue = [];
|
|
||||||
for (const message of this.messageQueue) {
|
|
||||||
if (!this.emit(message.mes)) {
|
|
||||||
newMessageQueue.push(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.messageQueue = newMessageQueue;
|
|
||||||
} catch (e) {
|
|
||||||
this.emit(e.message, true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.ws.onerror = (e) => {
|
|
||||||
this.emit(e.message, true);
|
|
||||||
if (this.connecting) {
|
|
||||||
this.connecting = false;
|
|
||||||
reject(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
})() });
|
|
||||||
}
|
|
||||||
|
|
||||||
//timeout в минутах (cleanPeriod)
|
|
||||||
message(requestId, timeout = 2) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.addListener({
|
|
||||||
requestId,
|
|
||||||
timeout,
|
|
||||||
onMessage: (mes) => {
|
|
||||||
resolve(mes);
|
|
||||||
},
|
|
||||||
onError: (e) => {
|
|
||||||
reject(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
send(req) {
|
|
||||||
if (this.ws && this.ws.readyState == WebSocket.OPEN) {
|
|
||||||
const requestId = ++this.requestId;
|
|
||||||
this.ws.send(JSON.stringify(Object.assign({requestId}, req)));
|
|
||||||
return requestId;
|
|
||||||
} else {
|
|
||||||
throw new Error('WebSocket connection is not ready');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
close() {
|
|
||||||
if (this.ws && this.ws.readyState == WebSocket.OPEN) {
|
|
||||||
this.ws.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
periodicClean() {
|
|
||||||
try {
|
|
||||||
this.timer = null;
|
|
||||||
|
|
||||||
const now = Date.now();
|
|
||||||
//чистка listeners
|
|
||||||
let newListeners = [];
|
|
||||||
for (const listener of this.listeners) {
|
|
||||||
if (now - listener.regTime < listener.timeout*cleanPeriod - 50) {
|
|
||||||
newListeners.push(listener);
|
|
||||||
} else {
|
|
||||||
if (listener.onError)
|
|
||||||
listener.onError('Время ожидания ответа истекло');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.listeners = newListeners;
|
|
||||||
|
|
||||||
//чистка messageQueue
|
|
||||||
let newMessageQueue = [];
|
|
||||||
for (const message of this.messageQueue) {
|
|
||||||
if (now - message.regTime < this.messageLifeTime*cleanPeriod - 50) {
|
|
||||||
newMessageQueue.push(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.messageQueue = newMessageQueue;
|
|
||||||
} finally {
|
|
||||||
if (this.ws.readyState == WebSocket.OPEN) {
|
|
||||||
this.timer = setTimeout(() => { this.periodicClean(); }, cleanPeriod);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default new WebSocketConnection();
|
export default new WebSocketConnection();
|
||||||
|
Before Width: | Height: | Size: 227 B After Width: | Height: | Size: 1.3 KiB |
@@ -1,22 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="fit row">
|
<div class="fit row">
|
||||||
<Notify ref="notify"/>
|
<Notify ref="notify" />
|
||||||
<StdDialog ref="stdDialog"/>
|
<StdDialog ref="stdDialog" />
|
||||||
<keep-alive>
|
|
||||||
<router-view class="col"></router-view>
|
<router-view v-slot="{ Component }">
|
||||||
</keep-alive>
|
<keep-alive v-if="showPage">
|
||||||
|
<component :is="Component" class="col" />
|
||||||
|
</keep-alive>
|
||||||
|
</router-view>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from './vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
import Notify from './share/Notify.vue';
|
import Notify from './share/Notify.vue';
|
||||||
import StdDialog from './share/StdDialog.vue';
|
import StdDialog from './share/StdDialog.vue';
|
||||||
|
import sanitizeHtml from 'sanitize-html';
|
||||||
|
|
||||||
|
import miscApi from '../api/misc';
|
||||||
import * as utils from '../share/utils';
|
import * as utils from '../share/utils';
|
||||||
|
|
||||||
export default @Component({
|
const componentOptions = {
|
||||||
components: {
|
components: {
|
||||||
Notify,
|
Notify,
|
||||||
StdDialog,
|
StdDialog,
|
||||||
@@ -28,8 +34,11 @@ export default @Component({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
})
|
};
|
||||||
class App extends Vue {
|
class App {
|
||||||
|
_options = componentOptions;
|
||||||
|
showPage = false;
|
||||||
|
|
||||||
itemRuText = {
|
itemRuText = {
|
||||||
'/cardindex': 'Картотека',
|
'/cardindex': 'Картотека',
|
||||||
'/reader': 'Читалка',
|
'/reader': 'Читалка',
|
||||||
@@ -42,7 +51,6 @@ class App extends Vue {
|
|||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.commit = this.$store.commit;
|
this.commit = this.$store.commit;
|
||||||
this.dispatch = this.$store.dispatch;
|
|
||||||
this.state = this.$store.state;
|
this.state = this.$store.state;
|
||||||
this.uistate = this.$store.state.uistate;
|
this.uistate = this.$store.state.uistate;
|
||||||
this.config = this.$store.state.config;
|
this.config = this.$store.state.config;
|
||||||
@@ -50,7 +58,7 @@ class App extends Vue {
|
|||||||
//root route
|
//root route
|
||||||
let cachedRoute = '';
|
let cachedRoute = '';
|
||||||
let cachedPath = '';
|
let cachedPath = '';
|
||||||
this.$root.rootRoute = () => {
|
this.$root.getRootRoute = () => {
|
||||||
if (this.$route.path != cachedPath) {
|
if (this.$route.path != cachedPath) {
|
||||||
cachedPath = this.$route.path;
|
cachedPath = this.$route.path;
|
||||||
const m = cachedPath.match(/^(\/[^/]*).*$/i);
|
const m = cachedPath.match(/^(\/[^/]*).*$/i);
|
||||||
@@ -69,46 +77,50 @@ class App extends Vue {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// set-app-title
|
this.$root.isMobileDevice = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent);
|
||||||
this.$root.$on('set-app-title', this.setAppTitle);
|
|
||||||
|
|
||||||
//global keyHooks
|
// setAppTitle
|
||||||
this.keyHooks = [];
|
this.$root.setAppTitle = this.setAppTitle;
|
||||||
this.keyHook = (event) => {
|
|
||||||
for (const hook of this.keyHooks)
|
//sanitize
|
||||||
|
this.$root.sanitize = sanitizeHtml;
|
||||||
|
|
||||||
|
//global event hooks
|
||||||
|
this.eventHooks = {};
|
||||||
|
this.$root.eventHook = (hookName, event) => {
|
||||||
|
if (!this.eventHooks[hookName])
|
||||||
|
return;
|
||||||
|
for (const hook of this.eventHooks[hookName])
|
||||||
hook(event);
|
hook(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$root.addKeyHook = (hook) => {
|
this.$root.addEventHook = (hookName, hook) => {
|
||||||
if (this.keyHooks.indexOf(hook) < 0)
|
if (!this.eventHooks[hookName])
|
||||||
this.keyHooks.push(hook);
|
this.eventHooks[hookName] = [];
|
||||||
|
if (this.eventHooks[hookName].indexOf(hook) < 0)
|
||||||
|
this.eventHooks[hookName].push(hook);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$root.removeKeyHook = (hook) => {
|
this.$root.removeEventHook = (hookName, hook) => {
|
||||||
const i = this.keyHooks.indexOf(hook);
|
if (!this.eventHooks[hookName])
|
||||||
|
return;
|
||||||
|
const i = this.eventHooks[hookName].indexOf(hook);
|
||||||
if (i >= 0)
|
if (i >= 0)
|
||||||
this.keyHooks.splice(i, 1);
|
this.eventHooks[hookName].splice(i, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('keyup', (event) => {
|
document.addEventListener('keyup', (event) => {
|
||||||
this.keyHook(event);
|
this.$root.eventHook('key', event);
|
||||||
});
|
});
|
||||||
document.addEventListener('keypress', (event) => {
|
document.addEventListener('keypress', (event) => {
|
||||||
this.keyHook(event);
|
this.$root.eventHook('key', event);
|
||||||
});
|
});
|
||||||
document.addEventListener('keydown', (event) => {
|
document.addEventListener('keydown', (event) => {
|
||||||
this.keyHook(event);
|
this.$root.eventHook('key', event);
|
||||||
});
|
});
|
||||||
window.addEventListener('resize', () => {
|
|
||||||
this.$root.$emit('resize');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
routerReady() {
|
window.addEventListener('resize', (event) => {
|
||||||
return new Promise ((resolve) => {
|
this.$root.eventHook('resize', event);
|
||||||
this.$router.onReady(() => {
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,30 +128,36 @@ class App extends Vue {
|
|||||||
this.$root.notify = this.$refs.notify;
|
this.$root.notify = this.$refs.notify;
|
||||||
this.$root.stdDialog = this.$refs.stdDialog;
|
this.$root.stdDialog = this.$refs.stdDialog;
|
||||||
|
|
||||||
this.dispatch('config/loadConfig');
|
|
||||||
this.$watch('apiError', function(newError) {
|
|
||||||
if (newError) {
|
|
||||||
let mes = newError.message;
|
|
||||||
if (newError.response && newError.response.config)
|
|
||||||
mes = newError.response.config.url + '<br>' + newError.response.statusText;
|
|
||||||
this.$root.notify.error(mes, 'Ошибка API');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setAppTitle();
|
this.setAppTitle();
|
||||||
(async() => {
|
(async() => {
|
||||||
|
//загрузим конфиг сревера
|
||||||
|
try {
|
||||||
|
const config = await miscApi.loadConfig();
|
||||||
|
this.commit('config/setConfig', config);
|
||||||
|
this.showPage = true;
|
||||||
|
} catch(e) {
|
||||||
|
//проверим, не получен ли конфиг ранее
|
||||||
|
if (!this.mode) {
|
||||||
|
this.$root.notify.error(e.message, 'Ошибка API');
|
||||||
|
} else {
|
||||||
|
//вероятно, работаем в оффлайне
|
||||||
|
this.showPage = true;
|
||||||
|
}
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
//запросим persistent storage
|
//запросим persistent storage
|
||||||
if (navigator.storage && navigator.storage.persist) {
|
if (navigator.storage && navigator.storage.persist) {
|
||||||
navigator.storage.persist();
|
navigator.storage.persist();
|
||||||
}
|
}
|
||||||
await this.routerReady();
|
await this.$router.isReady();
|
||||||
this.redirectIfNeeded();
|
this.redirectIfNeeded();
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleCollapse() {
|
toggleCollapse() {
|
||||||
this.commit('uistate/setAsideBarCollapse', !this.uistate.asideBarCollapse);
|
this.commit('uistate/setAsideBarCollapse', !this.uistate.asideBarCollapse);
|
||||||
this.$root.$emit('resize');
|
this.$root.eventHook('resize');
|
||||||
}
|
}
|
||||||
|
|
||||||
get isCollapse() {
|
get isCollapse() {
|
||||||
@@ -174,7 +192,7 @@ class App extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get rootRoute() {
|
get rootRoute() {
|
||||||
return this.$root.rootRoute();
|
return this.$root.getRootRoute();
|
||||||
}
|
}
|
||||||
|
|
||||||
setAppTitle(title) {
|
setAppTitle(title) {
|
||||||
@@ -184,7 +202,7 @@ class App extends Vue {
|
|||||||
} else if (this.mode == 'omnireader') {
|
} else if (this.mode == 'omnireader') {
|
||||||
document.title = `Omni Reader - всегда с вами`;
|
document.title = `Omni Reader - всегда с вами`;
|
||||||
} else if (this.config && this.mode !== null) {
|
} else if (this.config && this.mode !== null) {
|
||||||
document.title = `${this.config.name} - ${this.itemRuText[this.$root.rootRoute]}`;
|
document.title = `${this.config.name} - ${this.itemRuText[this.rootRoute]}`;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
document.title = title;
|
document.title = title;
|
||||||
@@ -229,6 +247,8 @@ class App extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(App);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -260,6 +280,14 @@ body, html, #app {
|
|||||||
animation: rotating 2s linear infinite;
|
animation: rotating 2s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes rotating {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
} to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.notify-button-icon {
|
.notify-button-icon {
|
||||||
font-size: 16px !important;
|
font-size: 16px !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,15 +6,14 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
|
|
||||||
export default @Component({
|
class Book {
|
||||||
})
|
|
||||||
class Book extends Vue {
|
|
||||||
created() {
|
created() {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(Book);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -6,15 +6,14 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
|
|
||||||
export default @Component({
|
class Card {
|
||||||
})
|
|
||||||
class Card extends Vue {
|
|
||||||
created() {
|
created() {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(Card);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<keep-alive>
|
<router-view v-slot="{ Component }">
|
||||||
<router-view></router-view>
|
<keep-alive>
|
||||||
</keep-alive>
|
<component :is="Component" />
|
||||||
|
</keep-alive>
|
||||||
|
</router-view>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
const selfRoute = '/cardindex';
|
const selfRoute = '/cardindex';
|
||||||
@@ -21,20 +22,32 @@ const tab2Route = [
|
|||||||
];
|
];
|
||||||
let lastActiveTab = null;
|
let lastActiveTab = null;
|
||||||
|
|
||||||
export default @Component({
|
const componentOptions = {
|
||||||
watch: {
|
watch: {
|
||||||
selectedTab: function(newValue, oldValue) {
|
selectedTab: function(newValue) {
|
||||||
lastActiveTab = newValue;
|
lastActiveTab = newValue;
|
||||||
this.setRouteByTab(newValue);
|
this.setRouteByTab(newValue);
|
||||||
},
|
},
|
||||||
curRoute: function(newValue, oldValue) {
|
curRoute: function(newValue) {
|
||||||
this.setTabByRoute(newValue);
|
this.setTabByRoute(newValue);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
class CardIndex extends Vue {
|
class CardIndex {
|
||||||
|
_options = componentOptions;
|
||||||
selectedTab = null;
|
selectedTab = null;
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.$watch(
|
||||||
|
() => this.$route.path,
|
||||||
|
(newValue) => {
|
||||||
|
if (newValue == '/cardindex' && this.isReader) {
|
||||||
|
this.$router.replace({ path: '/reader' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.setTabByRoute(this.curRoute);
|
this.setTabByRoute(this.curRoute);
|
||||||
}
|
}
|
||||||
@@ -57,12 +70,22 @@ class CardIndex extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get mode() {
|
||||||
|
return this.$store.state.config.mode;
|
||||||
|
}
|
||||||
|
|
||||||
get curRoute() {
|
get curRoute() {
|
||||||
const m = this.$route.path.match(/^(\/[^\/]*\/[^\/]*).*$/i);
|
const m = this.$route.path.match(/^(\/[^/]*\/[^/]*).*$/i);
|
||||||
return (m ? m[1] : this.$route.path);
|
return (m ? m[1] : this.$route.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isReader() {
|
||||||
|
return (this.mode !== null && (this.mode == 'reader' || this.mode == 'omnireader' || this.mode == 'liberama.top'));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(CardIndex);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -6,15 +6,14 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
|
|
||||||
export default @Component({
|
class History {
|
||||||
})
|
|
||||||
class History extends Vue {
|
|
||||||
created() {
|
created() {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(History);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -6,15 +6,14 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
|
|
||||||
export default @Component({
|
class Search {
|
||||||
})
|
|
||||||
class Search extends Vue {
|
|
||||||
created() {
|
created() {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(Search);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,63 +1,79 @@
|
|||||||
<template>
|
<template>
|
||||||
<Window ref="window" width="600px" height="95%" @close="close">
|
<Window ref="window" width="600px" height="95%" @close="close">
|
||||||
<template slot="header">
|
<template #header>
|
||||||
Настроить закладки
|
Настроить закладки
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="col column fit">
|
<div class="col column fit">
|
||||||
<div class="row items-center top-panel bg-grey-3">
|
<div class="row items-center top-panel bg-grey-3">
|
||||||
<q-btn class="q-mr-md" round dense color="blue" icon="la la-check" @click.stop="openSelected" size="16px" :disabled="!selected">
|
<q-btn :disabled="!selected" class="q-mr-md" round dense color="blue" icon="la la-check" size="16px" @click.stop="openSelected">
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Открыть выбранную закладку</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
Открыть выбранную закладку
|
||||||
|
</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-input class="col" ref="search" rounded outlined dense bg-color="white" placeholder="Найти" v-model="search">
|
<q-input ref="search" v-model="search" class="col" rounded outlined dense bg-color="white" placeholder="Найти">
|
||||||
<template v-slot:append>
|
<template #append>
|
||||||
<q-icon v-if="search !== ''" name="la la-times" class="cursor-pointer" @click="resetSearch"/>
|
<q-icon v-if="search !== ''" name="la la-times" class="cursor-pointer" @click="resetSearch" />
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col row">
|
<div class="col row">
|
||||||
<div class="left-panel column items-center no-wrap bg-grey-3">
|
<div class="left-panel column items-center no-wrap bg-grey-3">
|
||||||
<q-btn class="q-my-sm" round dense color="blue" icon="la la-plus" @click.stop="addBookmark" size="14px">
|
<q-btn class="q-my-sm" round dense color="blue" icon="la la-plus" size="14px" @click.stop="addBookmark">
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Добавить закладку</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
Добавить закладку
|
||||||
|
</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-btn class="q-mb-sm" round dense color="blue" icon="la la-minus" @click.stop="delBookmark" size="14px" :disabled="!ticked.length">
|
<q-btn :disabled="!ticked.length" class="q-mb-sm" round dense color="blue" icon="la la-minus" size="14px" @click.stop="delBookmark">
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Удалить отмеченные закладки</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
Удалить отмеченные закладки
|
||||||
|
</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-btn class="q-mb-sm" round dense color="blue" icon="la la-edit" @click.stop="editBookmark" size="14px" :disabled="!selected || selected.indexOf('r-') == 0">
|
<q-btn :disabled="!selected || selected.indexOf('r-') == 0" class="q-mb-sm" round dense color="blue" icon="la la-edit" size="14px" @click.stop="editBookmark">
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Редактировать закладку</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
Редактировать закладку
|
||||||
|
</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-btn class="q-mb-sm" round dense color="blue" icon="la la-arrow-up" @click.stop="moveBookmark(false)" size="14px" :disabled="!ticked.length">
|
<q-btn :disabled="!ticked.length" class="q-mb-sm" round dense color="blue" icon="la la-arrow-up" size="14px" @click.stop="moveBookmark(false)">
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Переместить отмеченные вверх</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
Переместить отмеченные вверх
|
||||||
|
</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-btn class="q-mb-sm" round dense color="blue" icon="la la-arrow-down" @click.stop="moveBookmark(true)" size="14px" :disabled="!ticked.length">
|
<q-btn :disabled="!ticked.length" class="q-mb-sm" round dense color="blue" icon="la la-arrow-down" size="14px" @click.stop="moveBookmark(true)">
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Переместить отмеченные вниз</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
Переместить отмеченные вниз
|
||||||
|
</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-btn class="q-mb-sm" round dense color="blue" icon="la la-broom" @click.stop="setDefaultBookmarks" size="14px">
|
<q-btn class="q-mb-sm" round dense color="blue" icon="la la-broom" size="14px" @click.stop="setDefaultBookmarks">
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Установить по умолчанию</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
Установить по умолчанию
|
||||||
|
</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<div class="space"/>
|
<div class="space" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col fit tree">
|
<div class="col fit tree">
|
||||||
<div v-show="nodes.length" class="checkbox-tick-all">
|
<div v-show="nodes.length" class="checkbox-tick-all">
|
||||||
<q-checkbox v-model="tickAll" @input="makeTickAll" size="36px" label="Выбрать все" />
|
<q-checkbox v-model="tickAll" size="36px" label="Выбрать все" @update:model-value="makeTickAll" />
|
||||||
</div>
|
</div>
|
||||||
<q-tree
|
<q-tree
|
||||||
|
v-model:selected="selected"
|
||||||
|
v-model:ticked="ticked"
|
||||||
|
v-model:expanded="expanded"
|
||||||
class="q-my-xs"
|
class="q-my-xs"
|
||||||
:nodes="nodes"
|
:nodes="nodes"
|
||||||
node-key="key"
|
node-key="key"
|
||||||
tick-strategy="leaf"
|
tick-strategy="leaf"
|
||||||
:selected.sync="selected"
|
|
||||||
:ticked.sync="ticked"
|
|
||||||
:expanded.sync="expanded"
|
|
||||||
selected-color="black"
|
selected-color="black"
|
||||||
:filter="search"
|
:filter="search"
|
||||||
no-nodes-label="Закладок пока нет"
|
no-nodes-label="Закладок пока нет"
|
||||||
no-results-label="Ничего не найдено"
|
no-results-label="Ничего не найдено"
|
||||||
>
|
>
|
||||||
<template v-slot:default-header="p">
|
<template #default-header="p">
|
||||||
<div class="q-px-xs" :class="{selected: selected == p.key}">{{ p.node.label }}</div>
|
<div class="q-px-xs" :class="{selected: selected == p.key}">
|
||||||
|
{{ p.node.label }}
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</q-tree>
|
</q-tree>
|
||||||
</div>
|
</div>
|
||||||
@@ -68,32 +84,31 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import Window from '../../share/Window.vue';
|
import Window from '../../share/Window.vue';
|
||||||
import * as lu from '../linkUtils';
|
import * as lu from '../linkUtils';
|
||||||
import rstore from '../../../store/modules/reader';
|
import rstore from '../../../store/modules/reader';
|
||||||
|
|
||||||
const BookmarkSettingsProps = Vue.extend({
|
const componentOptions = {
|
||||||
props: {
|
|
||||||
libs: Object,
|
|
||||||
addBookmarkVisible: Boolean,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default @Component({
|
|
||||||
components: {
|
components: {
|
||||||
Window,
|
Window,
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
ticked: function() {
|
ticked() {
|
||||||
this.checkAllTicked();
|
this.checkAllTicked();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
};
|
||||||
class BookmarkSettings extends BookmarkSettingsProps {
|
class BookmarkSettings {
|
||||||
|
_options = componentOptions;
|
||||||
|
_props = {
|
||||||
|
libs: Object,
|
||||||
|
addBookmarkVisible: Boolean,
|
||||||
|
};
|
||||||
|
|
||||||
search = '';
|
search = '';
|
||||||
selected = '';
|
selected = '';
|
||||||
ticked = [];
|
ticked = [];
|
||||||
@@ -308,6 +323,8 @@ class BookmarkSettings extends BookmarkSettingsProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(BookmarkSettings);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,82 +1,114 @@
|
|||||||
<template>
|
<template>
|
||||||
<Window ref="window" @close="close" margin="2px">
|
<Window ref="window" margin="2px" @close="close">
|
||||||
<template slot="header">
|
<template #header>
|
||||||
{{ header }}
|
{{ header }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template slot="buttons">
|
<template #buttons>
|
||||||
<span class="full-screen-button row justify-center items-center" @mousedown.stop @click="fullScreenToggle">
|
<span class="full-screen-button row justify-center items-center" @mousedown.stop @click="fullScreenToggle">
|
||||||
<q-icon :name="(fullScreenActive ? 'la la-compress-arrows-alt': 'la la-expand-arrows-alt')" size="16px"/>
|
<q-icon :name="(fullScreenActive ? 'la la-compress-arrows-alt': 'la la-expand-arrows-alt')" size="16px" />
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">На весь экран</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">На весь экран</q-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<span class="full-screen-button row justify-center items-center" @mousedown.stop @click="changeScale(0.1)">
|
<span class="full-screen-button row justify-center items-center" @mousedown.stop @click="changeScale(0.1)">
|
||||||
<q-icon name="la la-plus" size="16px"/>
|
<q-icon name="la la-plus" size="16px" />
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Увеличить масштаб</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Увеличить масштаб</q-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<span class="full-screen-button row justify-center items-center" @mousedown.stop @click="changeScale(-0.1)">
|
<span class="full-screen-button row justify-center items-center" @mousedown.stop @click="changeScale(-0.1)">
|
||||||
<q-icon name="la la-minus" size="16px"/>
|
<q-icon name="la la-minus" size="16px" />
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Уменьшить масштаб</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Уменьшить масштаб</q-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<span class="full-screen-button row justify-center items-center" @mousedown.stop @click="showHelp">
|
<span class="full-screen-button row justify-center items-center" @mousedown.stop @click="showHelp">
|
||||||
<q-icon name="la la-question-circle" size="16px"/>
|
<q-icon name="la la-question-circle" size="16px" />
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Справка</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Справка</q-tooltip>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div v-show="ready" class="col column" style="min-width: 600px">
|
<div v-show="ready" class="col column" style="min-width: 600px">
|
||||||
<div class="row items-center q-px-sm" style="height: 50px">
|
<div class="row items-center q-px-sm" style="height: 50px">
|
||||||
<q-select class="q-mr-sm" ref="rootLink" v-model="rootLink" :options="rootLinkOptions" @input="rootLinkInput"
|
<q-select
|
||||||
@popup-show="onSelectPopupShow" @popup-hide="onSelectPopupHide"
|
ref="rootLink"
|
||||||
|
v-model="rootLink"
|
||||||
|
class="q-mr-sm"
|
||||||
|
:options="rootLinkOptions"
|
||||||
style="width: 230px"
|
style="width: 230px"
|
||||||
dropdown-icon="la la-angle-down la-sm"
|
dropdown-icon="la la-angle-down la-sm"
|
||||||
rounded outlined dense emit-value map-options display-value-sanitize options-sanitize
|
rounded outlined dense emit-value map-options display-value-sanitize options-sanitize
|
||||||
|
@popup-show="onSelectPopupShow" @popup-hide="onSelectPopupHide"
|
||||||
>
|
>
|
||||||
<template v-slot:prepend>
|
<template #prepend>
|
||||||
<q-btn class="q-mr-xs" round dense color="blue" icon="la la-plus" @click.stop="addBookmark" size="12px">
|
<q-btn class="q-mr-xs" round dense color="blue" icon="la la-plus" size="12px" @click.stop="addBookmark">
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Добавить закладку</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
Добавить закладку
|
||||||
|
</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-btn round dense color="blue" icon="la la-bars" @click.stop="bookmarkSettings" size="12px">
|
<q-btn round dense color="blue" icon="la la-bars" size="12px" @click.stop="bookmarkSettings">
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Настроить закладки</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
Настроить закладки
|
||||||
|
</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:selected>
|
<template #selected>
|
||||||
<div style="overflow: hidden; white-space: nowrap;">{{ rootLinkWithoutProtocol }}</div>
|
<div style="overflow: hidden; white-space: nowrap;">
|
||||||
|
{{ rootLinkWithoutProtocol }}
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</q-select>
|
</q-select>
|
||||||
|
|
||||||
<q-select class="q-mr-sm" ref="selectedLink" v-model="selectedLink" :options="selectedLinkOptions" @input="selectedLinkInput" style="width: 50px"
|
<q-select
|
||||||
@popup-show="onSelectPopupShow" @popup-hide="onSelectPopupHide"
|
ref="selectedLink"
|
||||||
|
v-model="selectedLink"
|
||||||
|
class="q-mr-sm"
|
||||||
|
:options="selectedLinkOptions"
|
||||||
|
style="width: 50px"
|
||||||
dropdown-icon="la la-angle-down la-sm"
|
dropdown-icon="la la-angle-down la-sm"
|
||||||
rounded outlined dense emit-value map-options hide-selected display-value-sanitize options-sanitize
|
rounded outlined dense emit-value map-options hide-selected display-value-sanitize options-sanitize
|
||||||
|
@popup-show="onSelectPopupShow" @popup-hide="onSelectPopupHide"
|
||||||
>
|
>
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Закладки</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
Закладки
|
||||||
|
</q-tooltip>
|
||||||
</q-select>
|
</q-select>
|
||||||
|
|
||||||
<q-input class="col q-mr-sm" ref="input" rounded outlined dense bg-color="white" v-model="bookUrl" placeholder="Скопируйте сюда URL книги"
|
<q-input
|
||||||
|
ref="input"
|
||||||
|
v-model="bookUrl"
|
||||||
|
class="col q-mr-sm"
|
||||||
|
rounded outlined dense
|
||||||
|
bg-color="white"
|
||||||
|
placeholder="Скопируйте сюда ссылку на книгу и нажмите 'Открыть'"
|
||||||
@focus="selectAllOnFocus" @keydown="bookUrlKeyDown"
|
@focus="selectAllOnFocus" @keydown="bookUrlKeyDown"
|
||||||
>
|
>
|
||||||
<template v-slot:prepend>
|
<template #prepend>
|
||||||
<q-btn class="q-mr-xs" round dense color="blue" icon="la la-home" @click="goToLink(selectedLink)" size="12px">
|
<q-btn class="q-mr-xs" round dense color="blue" icon="la la-home" size="12px" @click="goToLink(selectedLink)">
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Вернуться на стартовую страницу</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
Вернуться на стартовую страницу
|
||||||
|
</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-btn round dense color="blue" icon="la la-angle-double-down" @click="openBookUrlInFrame" size="12px" :disabled="!bookUrl">
|
<q-btn :disabled="!bookUrl" round dense color="blue" icon="la la-angle-double-down" size="12px" @click="openBookUrlInFrame">
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Загрузить URL во фрейм</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
Загрузить URL во фрейм
|
||||||
|
</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:append>
|
<template #append>
|
||||||
<q-btn round dense color="blue" icon="la la-cog" @click.stop="optionsVisible = true" size="12px">
|
<q-btn round dense color="blue" icon="la la-cog" size="12px" @click.stop="optionsVisible = true">
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Опции</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
Опции
|
||||||
|
</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
|
|
||||||
<q-btn rounded color="green-7" no-caps size="14px" @click="submitUrl" :disabled="!bookUrl">Открыть
|
<q-btn :disabled="!bookUrl" rounded color="green-7" no-caps size="14px" @click="submitUrl">
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Открыть в читалке</q-tooltip>
|
Открыть
|
||||||
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
Открыть в читалке
|
||||||
|
</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
<div class="separator"></div>
|
<div class="separator"></div>
|
||||||
|
|
||||||
<div class="col fit" ref="frameBox" style="position: relative;">
|
<div ref="frameBox" class="col fit" style="position: relative;">
|
||||||
<div ref="frameWrap" class="overflow-hidden">
|
<div ref="frameWrap" class="overflow-hidden">
|
||||||
<iframe v-if="frameVisible" ref="frame" :src="frameSrc" frameborder="0"></iframe>
|
<iframe v-if="frameVisible" ref="frame" :src="frameSrc" frameborder="0"></iframe>
|
||||||
</div>
|
</div>
|
||||||
@@ -84,41 +116,68 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Dialog ref="dialogAddBookmark" v-model="addBookmarkVisible">
|
<Dialog ref="dialogAddBookmark" v-model="addBookmarkVisible">
|
||||||
<template slot="header">
|
<template #header>
|
||||||
<div class="row items-center">
|
<div class="row items-center">
|
||||||
<q-icon class="q-mr-sm" name="la la-bookmark" size="28px"></q-icon>
|
<q-icon class="q-mr-sm" name="la la-bookmark" size="28px"></q-icon>
|
||||||
<div v-if="addBookmarkMode == 'edit'">Редактировать закладку</div>
|
<div v-if="addBookmarkMode == 'edit'">
|
||||||
<div v-else>Добавить закладку</div>
|
Редактировать закладку
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
Добавить закладку
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="q-mx-md row">
|
<div class="q-mx-md row">
|
||||||
<q-input ref="bookmarkLink" class="col q-mr-sm" outlined dense bg-color="white" v-model="bookmarkLink" @keydown="bookmarkLinkKeyDown"
|
<q-input
|
||||||
placeholder="Ссылка для закладки" maxlength="2000" @focus="selectAllOnFocus">
|
ref="bookmarkLink"
|
||||||
|
v-model="bookmarkLink"
|
||||||
|
class="col q-mr-sm"
|
||||||
|
outlined dense
|
||||||
|
bg-color="white"
|
||||||
|
placeholder="Ссылка для закладки" maxlength="2000" @focus="selectAllOnFocus" @keydown="bookmarkLinkKeyDown"
|
||||||
|
>
|
||||||
</q-input>
|
</q-input>
|
||||||
|
|
||||||
<q-select class="q-mr-sm" ref="defaultRootLink" v-model="defaultRootLink" :options="defaultRootLinkOptions" @input="defaultRootLinkInput" style="width: 50px"
|
<q-select
|
||||||
|
ref="defaultRootLink"
|
||||||
|
v-model="defaultRootLink"
|
||||||
|
class="q-mr-sm"
|
||||||
|
:options="defaultRootLinkOptions"
|
||||||
|
style="width: 50px"
|
||||||
dropdown-icon="la la-angle-down la-sm"
|
dropdown-icon="la la-angle-down la-sm"
|
||||||
outlined dense emit-value map-options hide-selected display-value-sanitize options-sanitize
|
outlined dense emit-value map-options hide-selected display-value-sanitize options-sanitize
|
||||||
>
|
>
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Предустановленные ссылки</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
Предустановленные ссылки
|
||||||
|
</q-tooltip>
|
||||||
</q-select>
|
</q-select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="q-mx-md q-mt-md">
|
<div class="q-mx-md q-mt-md">
|
||||||
<q-input class="col q-mr-sm" ref="bookmarkDesc" outlined dense bg-color="white" v-model="bookmarkDesc" @keydown="bookmarkDescKeyDown"
|
<q-input
|
||||||
placeholder="Описание" style="width: 400px" maxlength="100" @focus="selectAllOnFocus">
|
ref="bookmarkDesc"
|
||||||
|
v-model="bookmarkDesc"
|
||||||
|
class="col q-mr-sm"
|
||||||
|
outlined dense
|
||||||
|
bg-color="white"
|
||||||
|
placeholder="Описание" style="width: 400px" maxlength="100" @focus="selectAllOnFocus" @keydown="bookmarkDescKeyDown"
|
||||||
|
>
|
||||||
</q-input>
|
</q-input>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template slot="footer">
|
<template #footer>
|
||||||
<q-btn class="q-px-md q-ml-sm" dense no-caps v-close-popup>Отмена</q-btn>
|
<q-btn v-close-popup class="q-px-md q-ml-sm" dense no-caps>
|
||||||
<q-btn class="q-px-md q-ml-sm" color="primary" dense no-caps @click="okAddBookmark" :disabled="!bookmarkLink">OK</q-btn>
|
Отмена
|
||||||
|
</q-btn>
|
||||||
|
<q-btn :disabled="!bookmarkLink" class="q-px-md q-ml-sm" color="primary" dense no-caps @click="okAddBookmark">
|
||||||
|
OK
|
||||||
|
</q-btn>
|
||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<Dialog ref="options" v-model="optionsVisible">
|
<Dialog ref="options" v-model="optionsVisible">
|
||||||
<template slot="header">
|
<template #header>
|
||||||
<div class="row items-center">
|
<div class="row items-center">
|
||||||
<q-icon class="q-mr-sm" name="la la-cog" size="28px"></q-icon>
|
<q-icon class="q-mr-sm" name="la la-cog" size="28px"></q-icon>
|
||||||
Опции
|
Опции
|
||||||
@@ -131,22 +190,29 @@
|
|||||||
<q-checkbox v-model="openInFrameOnAdd" size="36px" label="Активировать новую закладку после добавления" />
|
<q-checkbox v-model="openInFrameOnAdd" size="36px" label="Активировать новую закладку после добавления" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template slot="footer">
|
<template #footer>
|
||||||
<q-btn class="q-px-md q-ml-sm" color="primary" dense no-caps @click="optionsVisible = false">OK</q-btn>
|
<q-btn class="q-px-md q-ml-sm" color="primary" dense no-caps @click="optionsVisible = false">
|
||||||
|
OK
|
||||||
|
</q-btn>
|
||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<BookmarkSettings v-if="bookmarkSettingsActive" ref="bookmarkSettings" :libs="libs" :addBookmarkVisible="addBookmarkVisible"
|
<BookmarkSettings
|
||||||
@do-action="doAction" @close="closeBookmarkSettings">
|
v-if="bookmarkSettingsActive"
|
||||||
|
ref="bookmarkSettings"
|
||||||
|
:libs="libs"
|
||||||
|
:add-bookmark-visible="addBookmarkVisible"
|
||||||
|
@do-action="doAction" @close="closeBookmarkSettings"
|
||||||
|
>
|
||||||
</BookmarkSettings>
|
</BookmarkSettings>
|
||||||
</Window>
|
</Window>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import Window from '../share/Window.vue';
|
import Window from '../share/Window.vue';
|
||||||
@@ -162,20 +228,20 @@ const proxySubst = {
|
|||||||
'http://fantasy-worlds.org': 'http://b.liberama.top:23580',
|
'http://fantasy-worlds.org': 'http://b.liberama.top:23580',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default @Component({
|
const componentOptions = {
|
||||||
components: {
|
components: {
|
||||||
Window,
|
Window,
|
||||||
Dialog,
|
Dialog,
|
||||||
BookmarkSettings
|
BookmarkSettings
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
libs: function() {
|
libs() {
|
||||||
this.loadLibs();
|
this.loadLibs();
|
||||||
},
|
},
|
||||||
defaultRootLink: function() {
|
defaultRootLink() {
|
||||||
this.updateBookmarkLink();
|
this.updateBookmarkLink();
|
||||||
},
|
},
|
||||||
bookUrl: function(newValue) {
|
bookUrl(newValue) {
|
||||||
const value = lu.addProtocol(newValue);
|
const value = lu.addProtocol(newValue);
|
||||||
const subst = this.makeProxySubst(value, true);
|
const subst = this.makeProxySubst(value, true);
|
||||||
if (value != subst) {
|
if (value != subst) {
|
||||||
@@ -184,7 +250,7 @@ export default @Component({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
bookmarkLink: function(newValue) {
|
bookmarkLink(newValue) {
|
||||||
const value = lu.addProtocol(newValue);
|
const value = lu.addProtocol(newValue);
|
||||||
const subst = this.makeProxySubst(value, true);
|
const subst = this.makeProxySubst(value, true);
|
||||||
if (value != subst) {
|
if (value != subst) {
|
||||||
@@ -193,18 +259,26 @@ export default @Component({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
closeAfterSubmit: function(newValue) {
|
closeAfterSubmit(newValue) {
|
||||||
this.commitProp('closeAfterSubmit', newValue);
|
this.commitProp('closeAfterSubmit', newValue);
|
||||||
},
|
},
|
||||||
openInFrameOnEnter: function(newValue) {
|
openInFrameOnEnter(newValue) {
|
||||||
this.commitProp('openInFrameOnEnter', newValue);
|
this.commitProp('openInFrameOnEnter', newValue);
|
||||||
},
|
},
|
||||||
openInFrameOnAdd: function(newValue) {
|
openInFrameOnAdd(newValue) {
|
||||||
this.commitProp('openInFrameOnAdd', newValue);
|
this.commitProp('openInFrameOnAdd', newValue);
|
||||||
},
|
},
|
||||||
|
rootLink() {
|
||||||
|
this.rootLinkInput();
|
||||||
|
},
|
||||||
|
selectedLink() {
|
||||||
|
this.selectedLinkInput();
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
};
|
||||||
class ExternalLibs extends Vue {
|
class ExternalLibs {
|
||||||
|
_options = componentOptions;
|
||||||
|
|
||||||
ready = false;
|
ready = false;
|
||||||
frameVisible = false;
|
frameVisible = false;
|
||||||
rootLink = '';
|
rootLink = '';
|
||||||
@@ -233,9 +307,9 @@ class ExternalLibs extends Vue {
|
|||||||
created() {
|
created() {
|
||||||
this.oldStartLink = '';
|
this.oldStartLink = '';
|
||||||
this.justOpened = true;
|
this.justOpened = true;
|
||||||
this.$root.addKeyHook(this.keyHook);
|
this.$root.addEventHook('key', this.keyHook);
|
||||||
|
|
||||||
this.$root.$on('resize', async() => {
|
this.$root.addEventHook('resize', async() => {
|
||||||
await utils.sleep(200);
|
await utils.sleep(200);
|
||||||
this.frameResize();
|
this.frameResize();
|
||||||
});
|
});
|
||||||
@@ -252,29 +326,6 @@ class ExternalLibs extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
//Поправка метода toggleOption компонента select фреймворка quasar, необходимо другое поведение
|
|
||||||
//$emit('input'.. вызывается всегда
|
|
||||||
this.toggleOption = function(opt, keepOpen) {
|
|
||||||
if (this.editable !== true || opt === void 0 || this.isOptionDisabled(opt) === true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const optValue = this.getOptionValue(opt);
|
|
||||||
|
|
||||||
if (this.multiple !== true) {
|
|
||||||
if (keepOpen !== true) {
|
|
||||||
this.updateInputValue(this.fillInput === true ? this.getOptionLabel(opt) : '', true, true);
|
|
||||||
this.hidePopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$refs.target !== void 0 && this.$refs.target.focus();
|
|
||||||
this.$emit('input', this.emitValue === true ? optValue : opt);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.$refs.rootLink.toggleOption = this.toggleOption;
|
|
||||||
this.$refs.selectedLink.toggleOption = this.toggleOption;
|
|
||||||
|
|
||||||
(async() => {
|
(async() => {
|
||||||
//подождем this.mode
|
//подождем this.mode
|
||||||
let i = 0;
|
let i = 0;
|
||||||
@@ -412,7 +463,7 @@ class ExternalLibs extends Vue {
|
|||||||
if (this.ready && this.selectedLink) {
|
if (this.ready && this.selectedLink) {
|
||||||
result += ` | ${(this.libs.comment ? this.libs.comment + ' ': '') + lu.removeProtocol(this.libs.startLink)}`;
|
result += ` | ${(this.libs.comment ? this.libs.comment + ' ': '') + lu.removeProtocol(this.libs.startLink)}`;
|
||||||
}
|
}
|
||||||
this.$root.$emit('set-app-title', result);
|
this.$root.setAppTitle(result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -621,9 +672,9 @@ class ExternalLibs extends Vue {
|
|||||||
|
|
||||||
this.addBookmarkMode = mode;
|
this.addBookmarkMode = mode;
|
||||||
this.addBookmarkVisible = true;
|
this.addBookmarkVisible = true;
|
||||||
this.$nextTick(() => {
|
this.$nextTick(async() => {
|
||||||
|
await this.$refs.dialogAddBookmark.waitShown();
|
||||||
this.$refs.bookmarkLink.focus();
|
this.$refs.bookmarkLink.focus();
|
||||||
this.$refs.defaultRootLink.toggleOption = this.toggleOption;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -638,10 +689,6 @@ class ExternalLibs extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultRootLinkInput() {
|
|
||||||
this.updateBookmarkLink();
|
|
||||||
}
|
|
||||||
|
|
||||||
bookmarkLinkKeyDown(event) {
|
bookmarkLinkKeyDown(event) {
|
||||||
if (event.key == 'Enter') {
|
if (event.key == 'Enter') {
|
||||||
this.$refs.bookmarkDesc.focus();
|
this.$refs.bookmarkDesc.focus();
|
||||||
@@ -704,7 +751,7 @@ class ExternalLibs extends Vue {
|
|||||||
this.commitLibs(libs);
|
this.commitLibs(libs);
|
||||||
} else if (item.c != this.bookmarkDesc) {
|
} else if (item.c != this.bookmarkDesc) {
|
||||||
if (await this.$root.stdDialog.confirm(`Такая закладка уже существует с другим описанием.<br>` +
|
if (await this.$root.stdDialog.confirm(`Такая закладка уже существует с другим описанием.<br>` +
|
||||||
`Заменить '${this.$sanitize(item.c)}' на '${this.$sanitize(this.bookmarkDesc)}'?`, ' ')) {
|
`Заменить '${this.$root.sanitize(item.c)}' на '${this.$root.sanitize(this.bookmarkDesc)}'?`, ' ')) {
|
||||||
item.c = this.bookmarkDesc;
|
item.c = this.bookmarkDesc;
|
||||||
this.commitLibs(libs);
|
this.commitLibs(libs);
|
||||||
} else
|
} else
|
||||||
@@ -810,7 +857,7 @@ class ExternalLibs extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
keyHook(event) {
|
keyHook(event) {
|
||||||
if (this.$root.rootRoute() == '/external-libs') {
|
if (this.$root.getRootRoute() == '/external-libs') {
|
||||||
if (this.$root.stdDialog.active)
|
if (this.$root.stdDialog.active)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -836,6 +883,8 @@ class ExternalLibs extends Vue {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(ExternalLibs);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -6,15 +6,14 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
|
|
||||||
export default @Component({
|
class Help {
|
||||||
})
|
|
||||||
class Help extends Vue {
|
|
||||||
created() {
|
created() {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(Help);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -6,15 +6,14 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
|
|
||||||
export default @Component({
|
class Income {
|
||||||
})
|
|
||||||
class Income extends Vue {
|
|
||||||
created() {
|
created() {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(Income);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -6,15 +6,14 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
|
|
||||||
export default @Component({
|
class NotFound404 {
|
||||||
})
|
|
||||||
class NotFound404 extends Vue {
|
|
||||||
created() {
|
created() {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(NotFound404);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -6,15 +6,12 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
|
|
||||||
import {sleep} from '../../../share/utils';
|
import {sleep} from '../../../share/utils';
|
||||||
import {clickMap, clickMapText} from '../share/clickMap';
|
import {clickMap, clickMapText} from '../share/clickMap';
|
||||||
|
|
||||||
export default @Component({
|
class ClickMapPage {
|
||||||
})
|
|
||||||
class ClickMapPage extends Vue {
|
|
||||||
fontSize = '200%';
|
fontSize = '200%';
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
@@ -53,6 +50,8 @@ class ClickMapPage extends Vue {
|
|||||||
await sleep(5000);
|
await sleep(5000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(ClickMapPage);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,89 +1,144 @@
|
|||||||
<template>
|
<template>
|
||||||
<Window width="600px" ref="window" @close="close">
|
<Window ref="window" width="600px" @close="close">
|
||||||
<template slot="header">
|
<template #header>
|
||||||
Оглавление/закладки
|
Оглавление/закладки
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="bg-grey-3 row">
|
<div class="bg-grey-3 row">
|
||||||
<q-tabs
|
<q-tabs
|
||||||
v-model="selectedTab"
|
v-model="selectedTab"
|
||||||
active-color="black"
|
active-color="black"
|
||||||
active-bg-color="white"
|
active-bg-color="white"
|
||||||
indicator-color="white"
|
indicator-color="white"
|
||||||
dense
|
dense
|
||||||
no-caps
|
no-caps
|
||||||
inline-label
|
inline-label
|
||||||
class="no-mp bg-grey-4 text-grey-7"
|
class="no-mp bg-grey-4 text-grey-7"
|
||||||
>
|
>
|
||||||
<q-tab name="contents" icon="la la-list" label="Оглавление" />
|
<q-tab name="contents" icon="la la-list" label="Оглавление" />
|
||||||
<q-tab name="bookmarks" icon="la la-bookmark" label="Закладки" />
|
<q-tab name="images" icon="la la-image" label="Изображения" />
|
||||||
</q-tabs>
|
<q-tab name="bookmarks" icon="la la-bookmark" label="Закладки" />
|
||||||
</div>
|
</q-tabs>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="q-mb-sm"/>
|
<div class="q-mb-sm" />
|
||||||
|
|
||||||
<div class="tab-panel" v-show="selectedTab == 'contents'">
|
<div v-show="selectedTab == 'contents'" class="tab-panel">
|
||||||
<div>
|
<div>
|
||||||
<div v-for="item in contents" :key="item.key" class="column" style="width: 540px">
|
<div v-for="item in contents" :key="item.key" class="column" style="width: 540px">
|
||||||
<div class="row item q-px-sm no-wrap">
|
<div class="row q-px-sm no-wrap" :class="{'item': !item.isBookPos, 'item-book-pos': item.isBookPos}">
|
||||||
<div v-if="item.list.length" class="row justify-center items-center expand-button clickable" @click="expandClick(item.key)">
|
<div v-if="item.list.length" class="row justify-center items-center expand-button clickable" @click="expandClick(item.key)">
|
||||||
<q-icon name="la la-caret-right" class="icon" :class="{'expanded-icon': item.expanded}" color="green-8" size="20px"/>
|
<q-icon name="la la-caret-right" class="icon" :class="{'expanded-icon': item.expanded}" color="green-8" size="20px" />
|
||||||
|
</div>
|
||||||
|
<div v-else class="no-expand-button clickable" @click="setBookPos(item.offset)">
|
||||||
|
<q-icon name="la la-stop" class="icon" style="visibility: hidden" size="20px" />
|
||||||
|
</div>
|
||||||
|
<div class="col row clickable" @click="setBookPos(item.offset)">
|
||||||
|
<div :style="item.indentStyle"></div>
|
||||||
|
<div class="q-mr-sm col overflow-hidden column justify-center" :style="item.labelStyle" v-html="item.label"></div>
|
||||||
|
<div class="column justify-center">
|
||||||
|
{{ item.perc }}%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="no-expand-button clickable" @click="setBookPos(item.offset)">
|
|
||||||
<q-icon name="la la-stop" class="icon" style="visibility: hidden" size="20px"/>
|
<div v-if="item.expanded" :ref="`subitem${item.key}`" class="subitems-transition">
|
||||||
</div>
|
<div v-for="subitem in item.list" :key="subitem.key" class="row q-px-sm no-wrap" :class="{'subitem': !subitem.isBookPos, 'subitem-book-pos': subitem.isBookPos}">
|
||||||
<div class="col row clickable" @click="setBookPos(item.offset)">
|
<div class="col row clickable" @click="setBookPos(subitem.offset)">
|
||||||
<div :style="item.indentStyle"></div>
|
<div class="no-expand-button"></div>
|
||||||
<div class="q-mr-sm col overflow-hidden column justify-center" :style="item.labelStyle" v-html="item.label"></div>
|
<div :style="subitem.indentStyle"></div>
|
||||||
<div class="column justify-center">{{ item.perc }}%</div>
|
<div class="q-mr-sm col overflow-hidden column justify-center" :style="item.labelStyle" v-html="subitem.label"></div>
|
||||||
</div>
|
<div class="column justify-center">
|
||||||
</div>
|
{{ subitem.perc }}%
|
||||||
|
</div>
|
||||||
<div v-if="item.expanded" :ref="`subitem${item.key}`" class="subitems-transition">
|
</div>
|
||||||
<div v-for="subitem in item.list" :key="subitem.key" class="row subitem q-px-sm no-wrap">
|
|
||||||
<div class="col row clickable" @click="setBookPos(subitem.offset)">
|
|
||||||
<div class="no-expand-button"></div>
|
|
||||||
<div :style="subitem.indentStyle"></div>
|
|
||||||
<div class="q-mr-sm col overflow-hidden column justify-center" :style="item.labelStyle" v-html="subitem.label"></div>
|
|
||||||
<div class="column justify-center">{{ subitem.perc }}%</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div v-if="!contents.length" class="column justify-center items-center" style="height: 100px">
|
||||||
<div v-if="!contents.length" class="column justify-center items-center" style="height: 100px">
|
Оглавление отсутствует
|
||||||
Оглавление отсутствует
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tab-panel" v-show="selectedTab == 'bookmarks'">
|
<div v-show="selectedTab == 'images'" class="tab-panel">
|
||||||
<div class="column justify-center items-center" style="height: 100px">
|
<div>
|
||||||
Раздел находится в разработке
|
<div v-for="item in images" :key="item.key" class="column" style="width: 540px">
|
||||||
|
<div class="row q-px-sm no-wrap" :class="{'item': !item.isBookPos, 'item-book-pos': item.isBookPos}">
|
||||||
|
<div class="col row clickable" @click="setBookPos(item.offset)">
|
||||||
|
<div class="image-thumb-box row justify-center items-center">
|
||||||
|
<div v-show="!imageLoaded[item.id]" class="image-thumb column justify-center">
|
||||||
|
<i class="loading-img-icon la la-images"></i>
|
||||||
|
</div>
|
||||||
|
<img v-show="imageLoaded[item.id]" class="image-thumb" :src="imageSrc[item.id]" />
|
||||||
|
</div>
|
||||||
|
<div class="no-expand-button column justify-center items-center">
|
||||||
|
<div class="image-num">
|
||||||
|
{{ item.num }}
|
||||||
|
</div>
|
||||||
|
<div v-show="item.type == 'image/jpeg'" class="image-type it-jpg-color row justify-center">
|
||||||
|
JPG
|
||||||
|
</div>
|
||||||
|
<div v-show="item.type == 'image/png'" class="image-type it-png-color row justify-center">
|
||||||
|
PNG
|
||||||
|
</div>
|
||||||
|
<div v-show="!item.local" class="image-type it-net-color row justify-center">
|
||||||
|
INET
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div :style="item.indentStyle"></div>
|
||||||
|
<div class="q-mr-sm col overflow-hidden column justify-center" :style="item.labelStyle" v-html="item.label"></div>
|
||||||
|
<div class="column justify-center">
|
||||||
|
{{ item.perc }}%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="!images.length" class="column justify-center items-center" style="height: 100px">
|
||||||
|
Изображения отсутствуют
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div v-show="selectedTab == 'bookmarks'" class="tab-panel">
|
||||||
|
<div class="column justify-center items-center" style="height: 100px">
|
||||||
|
Раздел находится в разработке
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</Window>
|
</Window>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
//import _ from 'lodash';
|
//import _ from 'lodash';
|
||||||
|
|
||||||
import Window from '../../share/Window.vue';
|
import Window from '../../share/Window.vue';
|
||||||
import * as utils from '../../../share/utils';
|
import * as utils from '../../../share/utils';
|
||||||
|
|
||||||
export default @Component({
|
const componentOptions = {
|
||||||
components: {
|
components: {
|
||||||
Window,
|
Window,
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
bookPos() {
|
||||||
|
this.updateBookPosSelection();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
class ContentsPage extends Vue {
|
class ContentsPage {
|
||||||
|
_options = componentOptions;
|
||||||
|
_props = {
|
||||||
|
bookPos: Number,
|
||||||
|
isVisible: Boolean,
|
||||||
|
};
|
||||||
|
|
||||||
selectedTab = 'contents';
|
selectedTab = 'contents';
|
||||||
contents = [];
|
contents = [];
|
||||||
|
images = [];
|
||||||
|
imageSrc = [];
|
||||||
|
imageLoaded = [];
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
}
|
}
|
||||||
@@ -93,34 +148,48 @@ class ContentsPage extends Vue {
|
|||||||
|
|
||||||
//закладки
|
//закладки
|
||||||
|
|
||||||
//далее формаирование оглавления
|
//проверим, надо ли обновлять списки
|
||||||
if (this.parsed == parsed)
|
if (this.parsed == parsed) {
|
||||||
|
this.updateBookPosSelection();
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//далее формирование оглавления
|
||||||
this.parsed = parsed;
|
this.parsed = parsed;
|
||||||
this.contents = [];
|
this.contents = [];
|
||||||
await this.$nextTick();
|
await this.$nextTick();
|
||||||
|
|
||||||
const pc = parsed.contents;
|
const pc = parsed.contents;
|
||||||
const newpc = [];
|
const ims = parsed.images;
|
||||||
//преобразуем все, кроме первого, разделы body в title-subtitle
|
const newpc = [];
|
||||||
let curSubtitles = [];
|
if (pc.length) {//если есть оглавление
|
||||||
let prevBodyIndex = -1;
|
//преобразуем все, кроме первого, разделы body в title-subtitle
|
||||||
for (let i = 0; i < pc.length; i++) {
|
let curSubtitles = [];
|
||||||
const cont = pc[i];
|
let prevBodyIndex = -1;
|
||||||
if (prevBodyIndex != cont.bodyIndex)
|
for (let i = 0; i < pc.length; i++) {
|
||||||
curSubtitles = [];
|
const cont = pc[i];
|
||||||
|
if (prevBodyIndex != cont.bodyIndex)
|
||||||
|
curSubtitles = [];
|
||||||
|
|
||||||
prevBodyIndex = cont.bodyIndex;
|
prevBodyIndex = cont.bodyIndex;
|
||||||
|
|
||||||
if (cont.bodyIndex > 1) {
|
if (cont.bodyIndex > 1) {
|
||||||
if (cont.inset < 1) {
|
if (cont.inset < 1) {
|
||||||
newpc.push(Object.assign({}, cont, {subtitles: curSubtitles}));
|
newpc.push(Object.assign({}, cont, {subtitles: curSubtitles}));
|
||||||
|
} else {
|
||||||
|
curSubtitles.push(Object.assign({}, cont, {inset: cont.inset - 1}));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
curSubtitles.push(Object.assign({}, cont, {inset: cont.inset - 1}));
|
newpc.push(cont);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {//попробуем вытащить из images
|
||||||
|
for (let i = 0; i < ims.length; i++) {
|
||||||
|
const image = ims[i];
|
||||||
|
|
||||||
|
if (image.alt) {
|
||||||
|
newpc.push({paraIndex: image.paraIndex, title: image.alt, inset: 1, bodyIndex: 0, subtitles: []});
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
newpc.push(cont);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,6 +235,90 @@ class ContentsPage extends Vue {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.contents = newContents;
|
this.contents = newContents;
|
||||||
|
|
||||||
|
//формируем newImages
|
||||||
|
const newImages = [];
|
||||||
|
for (i = 0; i < ims.length; i++) {
|
||||||
|
const image = ims[i];
|
||||||
|
const bin = parsed.binary[image.id];
|
||||||
|
const type = (bin ? bin.type : '');
|
||||||
|
|
||||||
|
const label = (image.alt ? image.alt : '<span style="font-size: 90%; color: #dddddd"><i>Без названия</i></span>');
|
||||||
|
const indentStyle = getIndentStyle(1);
|
||||||
|
const labelStyle = getLabelStyle(1);
|
||||||
|
|
||||||
|
const p = parsed.para[image.paraIndex];
|
||||||
|
newImages.push({perc: (p.offset/parsed.textLength*100).toFixed(0), label, key: i, offset: p.offset,
|
||||||
|
indentStyle, labelStyle, type, num: image.num, id: image.id, local: image.local});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.images = newImages;
|
||||||
|
|
||||||
|
if (this.selectedTab == 'contents' && !this.contents.length && this.images.length)
|
||||||
|
this.selectedTab = 'images';
|
||||||
|
|
||||||
|
//выделим на bookPos
|
||||||
|
this.updateBookPosSelection();
|
||||||
|
|
||||||
|
//асинхронная загрузка изображений
|
||||||
|
this.imageSrc = [];
|
||||||
|
this.imageLoaded = [];
|
||||||
|
await utils.sleep(50);
|
||||||
|
(async() => {
|
||||||
|
for (i = 0; i < ims.length; i++) {
|
||||||
|
const {id, local} = ims[i];
|
||||||
|
const bin = this.parsed.binary[id];
|
||||||
|
if (local)
|
||||||
|
this.imageSrc[id] = (bin ? `data:${bin.type};base64,${bin.data}` : '');
|
||||||
|
else
|
||||||
|
this.imageSrc[id] = id;
|
||||||
|
this.imageLoaded[id] = true;
|
||||||
|
await utils.sleep(5);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateBookPosSelection() {
|
||||||
|
if (!this.isVisible)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await utils.sleep(50);
|
||||||
|
const bp = this.bookPos;
|
||||||
|
|
||||||
|
for (let i = 0; i < this.contents.length; i++) {
|
||||||
|
const item = this.contents[i];
|
||||||
|
const nextOffset = (i < this.contents.length - 1 ? this.contents[i + 1].offset : this.parsed.textLength);
|
||||||
|
|
||||||
|
for (let j = 0; j < item.list.length; j++) {
|
||||||
|
const subitem = item.list[j];
|
||||||
|
const nextSubOffset = (j < item.list.length - 1 ? item.list[j + 1].offset : nextOffset);
|
||||||
|
|
||||||
|
if (bp >= subitem.offset && bp < nextSubOffset) {
|
||||||
|
subitem.isBookPos = true;
|
||||||
|
this.contents[i] = Object.assign(item, {list: item.list});
|
||||||
|
} else if (subitem.isBookPos) {
|
||||||
|
subitem.isBookPos = false;
|
||||||
|
this.contents[i] = Object.assign(item, {list: item.list});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bp >= item.offset && bp < nextOffset) {
|
||||||
|
this.contents[i] = Object.assign(item, {isBookPos: true});
|
||||||
|
} else if (item.isBookPos) {
|
||||||
|
this.contents[i] = Object.assign(item, {isBookPos: false});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < this.images.length; i++) {
|
||||||
|
const img = this.images[i];
|
||||||
|
const nextOffset = (i < this.images.length - 1 ? this.images[i + 1].offset : this.parsed.textLength);
|
||||||
|
|
||||||
|
if (bp >= img.offset && bp < nextOffset) {
|
||||||
|
this.images[i] = Object.assign(img, {isBookPos: true});
|
||||||
|
} else if (img.isBookPos) {
|
||||||
|
this.images[i] = Object.assign(img, {isBookPos: false});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async expandClick(key) {
|
async expandClick(key) {
|
||||||
@@ -173,16 +326,16 @@ class ContentsPage extends Vue {
|
|||||||
const expanded = !item.expanded;
|
const expanded = !item.expanded;
|
||||||
|
|
||||||
if (!expanded) {
|
if (!expanded) {
|
||||||
const subitems = this.$refs[`subitem${key}`][0];
|
const subitems = this.$refs[`subitem${key}`];
|
||||||
subitems.style.height = '0';
|
subitems.style.height = '0';
|
||||||
await utils.sleep(200);
|
await utils.sleep(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$set(this.contents, key, Object.assign({}, item, {expanded}));
|
this.contents[key] = Object.assign({}, item, {expanded});
|
||||||
|
|
||||||
if (expanded) {
|
if (expanded) {
|
||||||
await this.$nextTick();
|
await this.$nextTick();
|
||||||
const subitems = this.$refs[`subitem${key}`][0];
|
const subitems = this.$refs[`subitem${key}`];
|
||||||
subitems.style.height = subitems.scrollHeight + 'px';
|
subitems.style.height = subitems.scrollHeight + 'px';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,6 +356,8 @@ class ContentsPage extends Vue {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(ContentsPage);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -219,7 +374,7 @@ class ContentsPage extends Vue {
|
|||||||
padding: 10px 0 10px 0;
|
padding: 10px 0 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item, .subitem {
|
.item, .subitem, .item-book-pos, .subitem-book-pos {
|
||||||
border-bottom: 1px solid #e0e0e0;
|
border-bottom: 1px solid #e0e0e0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,6 +382,22 @@ class ContentsPage extends Vue {
|
|||||||
background-color: #f0f0f0;
|
background-color: #f0f0f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.item-book-pos {
|
||||||
|
background-color: #b0f0b0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subitem-book-pos {
|
||||||
|
background-color: #d0f5d0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-book-pos:hover {
|
||||||
|
background-color: #b0e0b0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subitem-book-pos:hover {
|
||||||
|
background-color: #d0f0d0;
|
||||||
|
}
|
||||||
|
|
||||||
.expand-button, .no-expand-button {
|
.expand-button, .no-expand-button {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
}
|
}
|
||||||
@@ -244,4 +415,38 @@ class ContentsPage extends Vue {
|
|||||||
.expanded-icon {
|
.expanded-icon {
|
||||||
transform: rotate(90deg);
|
transform: rotate(90deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.image-num {
|
||||||
|
font-size: 120%;
|
||||||
|
padding-bottom: 3px;
|
||||||
|
}
|
||||||
|
.image-type {
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 80%;
|
||||||
|
padding: 2px 0 2px 0;
|
||||||
|
width: 34px;
|
||||||
|
}
|
||||||
|
.it-jpg-color {
|
||||||
|
background: linear-gradient(to right, #fabc3d, #ffec6d);
|
||||||
|
}
|
||||||
|
.it-png-color {
|
||||||
|
background: linear-gradient(to right, #4bc4e5, #6bf4ff);
|
||||||
|
}
|
||||||
|
.it-net-color {
|
||||||
|
background: linear-gradient(to right, #00c400, #00f400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-thumb-box {
|
||||||
|
width: 120px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-thumb {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-img-icon {
|
||||||
|
font-size: 250%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<Window @close="close">
|
<Window @close="close">
|
||||||
<template slot="header">
|
<template #header>
|
||||||
Скопировать текст
|
Скопировать текст
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -12,18 +12,19 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
|
|
||||||
import Window from '../../share/Window.vue';
|
import Window from '../../share/Window.vue';
|
||||||
import {sleep} from '../../../share/utils';
|
import {sleep} from '../../../share/utils';
|
||||||
|
|
||||||
export default @Component({
|
const componentOptions = {
|
||||||
components: {
|
components: {
|
||||||
Window,
|
Window,
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
class CopyTextPage extends Vue {
|
class CopyTextPage {
|
||||||
|
_options = componentOptions;
|
||||||
|
|
||||||
text = null;
|
text = null;
|
||||||
initStep = null;
|
initStep = null;
|
||||||
initPercentage = 0;
|
initPercentage = 0;
|
||||||
@@ -101,6 +102,8 @@ class CopyTextPage extends Vue {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(CopyTextPage);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -18,15 +18,20 @@
|
|||||||
<li>поддерживаемые браузеры: Google Chrome, Mozilla Firefox последних версий</li>
|
<li>поддерживаемые браузеры: Google Chrome, Mozilla Firefox последних версий</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>В качестве URL книги можно задавать html-страничку с книгой, либо прямую ссылку
|
<p>
|
||||||
на файл из онлайн-библиотеки (например, скопировав адрес ссылки или кнопки "скачать fb2").</p>
|
В качестве URL книги можно задавать html-страничку с книгой, либо прямую ссылку
|
||||||
|
на файл из онлайн-библиотеки (например, скопировав адрес ссылки или кнопки "скачать fb2").
|
||||||
|
</p>
|
||||||
<p>Поддерживаемые форматы: <b>fb2, fb2.zip, html, txt</b> и другие.</p>
|
<p>Поддерживаемые форматы: <b>fb2, fb2.zip, html, txt</b> и другие.</p>
|
||||||
|
|
||||||
<div v-show="mode == 'omnireader' || mode == 'liberama.top'">
|
<div v-show="mode == 'omnireader' || mode == 'liberama.top'">
|
||||||
<p>Вы можете добавить в свой браузер закладку, указав в ее свойствах вместо адреса следующий код:
|
<p>
|
||||||
|
Вы можете добавить в свой браузер закладку, указав в ее свойствах вместо адреса следующий код:
|
||||||
<br><strong>{{ bookmarkText }}</strong>
|
<br><strong>{{ bookmarkText }}</strong>
|
||||||
<q-icon class="copy-icon" name="la la-copy" @click="copyText(bookmarkText, 'Код для адреса закладки успешно скопирован в буфер обмена')">
|
<q-icon class="copy-icon" name="la la-copy" @click="copyText(bookmarkText, 'Код для адреса закладки успешно скопирован в буфер обмена')">
|
||||||
<q-tooltip :delay="1000" anchor="top middle" self="center middle" content-style="font-size: 80%">Скопировать</q-tooltip>
|
<q-tooltip :delay="1000" anchor="top middle" self="center middle" content-style="font-size: 80%">
|
||||||
|
Скопировать
|
||||||
|
</q-tooltip>
|
||||||
</q-icon>
|
</q-icon>
|
||||||
|
|
||||||
<br>или перетащив на панель закладок следующую ссылку:
|
<br>или перетащив на панель закладок следующую ссылку:
|
||||||
@@ -41,14 +46,11 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../../../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
|
|
||||||
import {copyTextToClipboard} from '../../../../share/utils';
|
import {copyTextToClipboard} from '../../../../share/utils';
|
||||||
|
|
||||||
export default @Component({
|
class CommonHelpPage {
|
||||||
})
|
|
||||||
class CommonHelpPage extends Vue {
|
|
||||||
created() {
|
created() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +71,8 @@ class CommonHelpPage extends Vue {
|
|||||||
this.$root.notify.error(msg);
|
this.$root.notify.error(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(CommonHelpPage);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,49 +1,68 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<p class="p">Вы можете пожертвовать на развитие проекта любую сумму:</p>
|
<p class="p">
|
||||||
|
Вы можете пожертвовать на развитие проекта любую сумму:
|
||||||
|
</p>
|
||||||
<div class="address">
|
<div class="address">
|
||||||
<img class="logo" src="./assets/yandex.png">
|
<img class="logo" src="./assets/yoomoney.png">
|
||||||
<q-btn class="q-ml-sm q-px-sm" dense no-caps @click="donateYandexMoney">Пожертвовать</q-btn><br>
|
<q-btn class="q-ml-sm q-px-sm" dense no-caps @click="donateYooMoney">
|
||||||
<div class="para">{{ yandexAddress }}
|
Пожертвовать
|
||||||
<q-icon class="copy-icon" name="la la-copy" @click="copyAddress(yandexAddress, 'Яндекс кошелек')">
|
</q-btn><br>
|
||||||
<q-tooltip :delay="1000" anchor="top middle" self="center middle" content-style="font-size: 80%">Скопировать</q-tooltip>
|
<div class="para">
|
||||||
|
{{ yooAddress }}
|
||||||
|
<q-icon class="copy-icon" name="la la-copy" @click="copyAddress(yooAddress, 'Кошелёк ЮMoney')">
|
||||||
|
<q-tooltip :delay="1000" anchor="top middle" self="center middle" content-style="font-size: 80%">
|
||||||
|
Скопировать
|
||||||
|
</q-tooltip>
|
||||||
</q-icon>
|
</q-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="address">
|
<!--div class="address">
|
||||||
<img class="logo" src="./assets/paypal.png">
|
<img class="logo" src="./assets/paypal.png">
|
||||||
<div class="para">{{ paypalAddress }}
|
<div class="para">
|
||||||
|
{{ paypalAddress }}
|
||||||
<q-icon class="copy-icon" name="la la-copy" @click="copyAddress(paypalAddress, 'Paypal-адрес')">
|
<q-icon class="copy-icon" name="la la-copy" @click="copyAddress(paypalAddress, 'Paypal-адрес')">
|
||||||
<q-tooltip :delay="1000" anchor="top middle" self="center middle" content-style="font-size: 80%">Скопировать</q-tooltip>
|
<q-tooltip :delay="1000" anchor="top middle" self="center middle" content-style="font-size: 80%">
|
||||||
|
Скопировать
|
||||||
|
</q-tooltip>
|
||||||
</q-icon>
|
</q-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div-->
|
||||||
|
|
||||||
<div class="address">
|
<div class="address">
|
||||||
<img class="logo" src="./assets/bitcoin.png">
|
<img class="logo" src="./assets/bitcoin.png">
|
||||||
<div class="para">{{ bitcoinAddress }}
|
<div class="para">
|
||||||
|
{{ bitcoinAddress }}
|
||||||
<q-icon class="copy-icon" name="la la-copy" @click="copyAddress(bitcoinAddress, 'Bitcoin-адрес')">
|
<q-icon class="copy-icon" name="la la-copy" @click="copyAddress(bitcoinAddress, 'Bitcoin-адрес')">
|
||||||
<q-tooltip :delay="1000" anchor="top middle" self="center middle" content-style="font-size: 80%">Скопировать</q-tooltip>
|
<q-tooltip :delay="1000" anchor="top middle" self="center middle" content-style="font-size: 80%">
|
||||||
|
Скопировать
|
||||||
|
</q-tooltip>
|
||||||
</q-icon>
|
</q-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="address">
|
<div class="address">
|
||||||
<img class="logo" src="./assets/litecoin.png">
|
<img class="logo" src="./assets/litecoin.png">
|
||||||
<div class="para">{{ litecoinAddress }}
|
<div class="para">
|
||||||
|
{{ litecoinAddress }}
|
||||||
<q-icon class="copy-icon" name="la la-copy" @click="copyAddress(litecoinAddress, 'Litecoin-адрес')">
|
<q-icon class="copy-icon" name="la la-copy" @click="copyAddress(litecoinAddress, 'Litecoin-адрес')">
|
||||||
<q-tooltip :delay="1000" anchor="top middle" self="center middle" content-style="font-size: 80%">Скопировать</q-tooltip>
|
<q-tooltip :delay="1000" anchor="top middle" self="center middle" content-style="font-size: 80%">
|
||||||
|
Скопировать
|
||||||
|
</q-tooltip>
|
||||||
</q-icon>
|
</q-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="address">
|
<div class="address">
|
||||||
<img class="logo" src="./assets/monero.png">
|
<img class="logo" src="./assets/monero.png">
|
||||||
<div class="para">{{ moneroAddress }}
|
<div class="para">
|
||||||
|
{{ moneroAddress }}
|
||||||
<q-icon class="copy-icon" name="la la-copy" @click="copyAddress(moneroAddress, 'Monero-адрес')">
|
<q-icon class="copy-icon" name="la la-copy" @click="copyAddress(moneroAddress, 'Monero-адрес')">
|
||||||
<q-tooltip :delay="1000" anchor="top middle" self="center middle" content-style="font-size: 80%">Скопировать</q-tooltip>
|
<q-tooltip :delay="1000" anchor="top middle" self="center middle" content-style="font-size: 80%">
|
||||||
|
Скопировать
|
||||||
|
</q-tooltip>
|
||||||
</q-icon>
|
</q-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -53,14 +72,12 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../../../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
import {copyTextToClipboard} from '../../../../share/utils';
|
import {copyTextToClipboard} from '../../../../share/utils';
|
||||||
|
|
||||||
export default @Component({
|
class DonateHelpPage {
|
||||||
})
|
yooAddress = '410018702323056';
|
||||||
class DonateHelpPage extends Vue {
|
|
||||||
yandexAddress = '410018702323056';
|
|
||||||
paypalAddress = 'bookpauk@gmail.com';
|
paypalAddress = 'bookpauk@gmail.com';
|
||||||
bitcoinAddress = '3EbgZ7MK1UVaN38Gty5DCBtS4PknM4Ut85';
|
bitcoinAddress = '3EbgZ7MK1UVaN38Gty5DCBtS4PknM4Ut85';
|
||||||
litecoinAddress = 'MP39Riec4oSNB3XMjiquKoLWxbufRYNXxZ';
|
litecoinAddress = 'MP39Riec4oSNB3XMjiquKoLWxbufRYNXxZ';
|
||||||
@@ -69,8 +86,8 @@ class DonateHelpPage extends Vue {
|
|||||||
created() {
|
created() {
|
||||||
}
|
}
|
||||||
|
|
||||||
donateYandexMoney() {
|
donateYooMoney() {
|
||||||
window.open(`https://money.yandex.ru/to/${this.yandexAddress}`, '_blank');
|
window.open(`https://yoomoney.ru/to/${this.yooAddress}`, '_blank');
|
||||||
}
|
}
|
||||||
|
|
||||||
async copyAddress(address, prefix) {
|
async copyAddress(address, prefix) {
|
||||||
@@ -81,6 +98,8 @@ class DonateHelpPage extends Vue {
|
|||||||
this.$root.notify.error('Копирование не удалось');
|
this.$root.notify.error('Копирование не удалось');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(DonateHelpPage);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<Window @close="close">
|
<Window @close="close">
|
||||||
<template slot="header">
|
<template #header>
|
||||||
Справка
|
Справка
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -14,8 +14,7 @@
|
|||||||
<div class="separator"></div>
|
<div class="separator"></div>
|
||||||
|
|
||||||
<keep-alive>
|
<keep-alive>
|
||||||
<component ref="page" class="col" :is="activePage"
|
<component :is="activePage" ref="page" class="col"></component>
|
||||||
></component>
|
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
</div>
|
</div>
|
||||||
</Window>
|
</Window>
|
||||||
@@ -23,22 +22,21 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
|
|
||||||
import Window from '../../share/Window.vue';
|
import Window from '../../share/Window.vue';
|
||||||
import CommonHelpPage from './CommonHelpPage/CommonHelpPage.vue';
|
import CommonHelpPage from './CommonHelpPage/CommonHelpPage.vue';
|
||||||
import HotkeysHelpPage from './HotkeysHelpPage/HotkeysHelpPage.vue';
|
import HotkeysHelpPage from './HotkeysHelpPage/HotkeysHelpPage.vue';
|
||||||
import MouseHelpPage from './MouseHelpPage/MouseHelpPage.vue';
|
import MouseHelpPage from './MouseHelpPage/MouseHelpPage.vue';
|
||||||
import VersionHistoryPage from './VersionHistoryPage/VersionHistoryPage.vue';
|
import VersionHistoryPage from './VersionHistoryPage/VersionHistoryPage.vue';
|
||||||
import DonateHelpPage from './DonateHelpPage/DonateHelpPage.vue';
|
//import DonateHelpPage from './DonateHelpPage/DonateHelpPage.vue';
|
||||||
|
|
||||||
const pages = {
|
const pages = {
|
||||||
'CommonHelpPage': CommonHelpPage,
|
'CommonHelpPage': CommonHelpPage,
|
||||||
'HotkeysHelpPage': HotkeysHelpPage,
|
'HotkeysHelpPage': HotkeysHelpPage,
|
||||||
'MouseHelpPage': MouseHelpPage,
|
'MouseHelpPage': MouseHelpPage,
|
||||||
'VersionHistoryPage': VersionHistoryPage,
|
'VersionHistoryPage': VersionHistoryPage,
|
||||||
'DonateHelpPage': DonateHelpPage,
|
//'DonateHelpPage': DonateHelpPage,
|
||||||
};
|
};
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
@@ -46,13 +44,15 @@ const tabs = [
|
|||||||
['MouseHelpPage', 'Мышь/тачскрин'],
|
['MouseHelpPage', 'Мышь/тачскрин'],
|
||||||
['HotkeysHelpPage', 'Клавиатура'],
|
['HotkeysHelpPage', 'Клавиатура'],
|
||||||
['VersionHistoryPage', 'История версий'],
|
['VersionHistoryPage', 'История версий'],
|
||||||
['DonateHelpPage', 'Помочь проекту'],
|
//['DonateHelpPage', 'Помочь проекту'],
|
||||||
];
|
];
|
||||||
|
|
||||||
export default @Component({
|
const componentOptions = {
|
||||||
components: Object.assign({ Window }, pages),
|
components: Object.assign({ Window }, pages),
|
||||||
})
|
};
|
||||||
class HelpPage extends Vue {
|
class HelpPage {
|
||||||
|
_options = componentOptions;
|
||||||
|
|
||||||
selectedTab = 'CommonHelpPage';
|
selectedTab = 'CommonHelpPage';
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
@@ -73,7 +73,7 @@ class HelpPage extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
activateDonateHelpPage() {
|
activateDonateHelpPage() {
|
||||||
this.selectedTab = 'DonateHelpPage';
|
//this.selectedTab = 'DonateHelpPage';
|
||||||
}
|
}
|
||||||
|
|
||||||
activateVersionHistoryHelpPage() {
|
activateVersionHistoryHelpPage() {
|
||||||
@@ -87,6 +87,8 @@ class HelpPage extends Vue {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(HelpPage);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,34 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<div style="font-size: 120%">
|
<div style="font-size: 120%">
|
||||||
<div class="text-h6 text-bold">Доступны следующие клавиатурные команды:</div>
|
<div class="text-h6 text-bold">
|
||||||
|
Доступны следующие клавиатурные команды:
|
||||||
|
</div>
|
||||||
<br>
|
<br>
|
||||||
</div>
|
</div>
|
||||||
<div class="q-mb-md" style="width: 550px">
|
<div class="q-mb-md" style="width: 550px">
|
||||||
<div class="text-right text-italic" style="font-size: 80%">* Изменить сочетания клавиш можно в настройках</div>
|
<div class="text-right text-italic" style="font-size: 80%">
|
||||||
<UserHotKeys v-model="userHotKeys" readonly/>
|
* Изменить сочетания клавиш можно в настройках
|
||||||
|
</div>
|
||||||
|
<UserHotKeys v-model="userHotKeys" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../../../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
|
|
||||||
import UserHotKeys from '../../SettingsPage/UserHotKeys/UserHotKeys.vue';
|
import UserHotKeys from '../../SettingsPage/UserHotKeys/UserHotKeys.vue';
|
||||||
|
|
||||||
export default @Component({
|
const componentOptions = {
|
||||||
components: {
|
components: {
|
||||||
UserHotKeys,
|
UserHotKeys,
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
class HotkeysHelpPage extends Vue {
|
class HotkeysHelpPage {
|
||||||
|
_options = componentOptions;
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,6 +41,8 @@ class HotkeysHelpPage extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(HotkeysHelpPage);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -3,21 +3,28 @@
|
|||||||
<span class="text-h6 text-bold">Управление с помощью мыши/тачскрина:</span>
|
<span class="text-h6 text-bold">Управление с помощью мыши/тачскрина:</span>
|
||||||
<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>
|
<br>
|
||||||
<li>Жесты для тачскрина:</li>
|
<li>Жесты для тачскрина:</li>
|
||||||
<ul>
|
<ul>
|
||||||
<li style="list-style-type: square">от центра вверх: на весь экран</li>
|
<li style="list-style-type: square">
|
||||||
<li style="list-style-type: square">от центра вниз: плавный скроллинг</li>
|
от центра вверх: на весь экран
|
||||||
<li style="list-style-type: square">от центра вправо: увеличить скорость скроллинга</li>
|
</li>
|
||||||
<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>
|
</ul>
|
||||||
* Для управления с помощью мыши/тачскрина необходимо установить галочку "Включить управление кликом" в настройках
|
* Для управления с помощью мыши/тачскрина необходимо установить галочку "Включить управление кликом" в настройках
|
||||||
</div>
|
</div>
|
||||||
@@ -25,17 +32,18 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../../../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
|
|
||||||
import ClickMapPage from '../../ClickMapPage/ClickMapPage.vue';
|
import ClickMapPage from '../../ClickMapPage/ClickMapPage.vue';
|
||||||
|
|
||||||
export default @Component({
|
const componentOptions = {
|
||||||
components: {
|
components: {
|
||||||
ClickMapPage,
|
ClickMapPage,
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
class MouseHelpPage extends Vue {
|
class MouseHelpPage {
|
||||||
|
_options = componentOptions;
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,6 +52,8 @@ class MouseHelpPage extends Vue {
|
|||||||
this.$refs.clickMapPage.$el.style.backgroundColor = '#478355';
|
this.$refs.clickMapPage.$el.style.backgroundColor = '#478355';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(MouseHelpPage);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
<span class="text-h6 text-bold">История версий:</span>
|
<span class="text-h6 text-bold">История версий:</span>
|
||||||
<br><br>
|
<br><br>
|
||||||
|
|
||||||
<span class="clickable" v-for="(item, index) in versionHeader" :key="index" @click="showRelease(item)">
|
<span v-for="(item, index) in versionHeader" :key="index" class="clickable" @click="showRelease(item)">
|
||||||
<p>
|
<p>
|
||||||
{{ item }}
|
{{ item }}
|
||||||
</p>
|
</p>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -20,13 +20,11 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../../../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
import {versionHistory} from '../../versionHistory';
|
import {versionHistory} from '../../versionHistory';
|
||||||
|
|
||||||
export default @Component({
|
class VersionHistoryPage {
|
||||||
})
|
|
||||||
class VersionHistoryPage extends Vue {
|
|
||||||
versionHeader = [];
|
versionHeader = [];
|
||||||
versionContent = [];
|
versionContent = [];
|
||||||
|
|
||||||
@@ -35,14 +33,15 @@ class VersionHistoryPage extends Vue {
|
|||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
let vh = [];
|
let vh = [];
|
||||||
for (const version of versionHistory) {
|
for (const v of versionHistory) {
|
||||||
vh.push(version.header);
|
vh.push(`${v.version} (${v.releaseDate})`);
|
||||||
}
|
}
|
||||||
this.versionHeader = vh;
|
this.versionHeader = vh;
|
||||||
|
|
||||||
let vc = [];
|
let vc = [];
|
||||||
for (const version of versionHistory) {
|
for (const v of versionHistory) {
|
||||||
vc.push({key: version.header, content: 'Версия ' + version.header + version.content});
|
let header = `${v.version} (${v.releaseDate})`;
|
||||||
|
vc.push({key: header, content: 'Версия ' + header + v.content});
|
||||||
}
|
}
|
||||||
this.versionContent = vc;
|
this.versionContent = vc;
|
||||||
}
|
}
|
||||||
@@ -54,6 +53,8 @@ class VersionHistoryPage extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(VersionHistoryPage);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,14 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
|
|
||||||
import Window from '../../share/Window.vue';
|
import Window from '../../share/Window.vue';
|
||||||
import * as utils from '../../../share/utils';
|
import * as utils from '../../../share/utils';
|
||||||
//import rstore from '../../../store/modules/reader';
|
//import rstore from '../../../store/modules/reader';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
export default @Component({
|
const componentOptions = {
|
||||||
components: {
|
components: {
|
||||||
Window
|
Window
|
||||||
},
|
},
|
||||||
@@ -20,8 +20,10 @@ export default @Component({
|
|||||||
this.sendLibs();
|
this.sendLibs();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
};
|
||||||
class LibsPage extends Vue {
|
class LibsPage {
|
||||||
|
_options = componentOptions;
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.popupWindow = null;
|
this.popupWindow = null;
|
||||||
this.commit = this.$store.commit;
|
this.commit = this.$store.commit;
|
||||||
@@ -113,13 +115,15 @@ class LibsPage extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sendLibs() {
|
sendLibs() {
|
||||||
this.sendMessage({type: 'libs', data: this.libs});
|
this.sendMessage({type: 'libs', data: _.cloneDeep(this.libs)});
|
||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
this.$emit('libs-close');
|
this.$emit('libs-close');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(LibsPage);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="main" class="column no-wrap" style="min-height: 500px">
|
<div ref="main" class="column no-wrap" style="min-height: 500px">
|
||||||
<div v-if="mode != 'liberama.top'" class="relative-position">
|
<div v-if="mode != 'liberama.top'" class="relative-position">
|
||||||
<GithubCorner url="https://github.com/bookpauk/liberama" cornerColor="#1B695F" gitColor="#EBE2C9"></GithubCorner>
|
<GithubCorner url="https://github.com/bookpauk/liberama" corner-color="#1B695F" git-color="#EBE2C9"></GithubCorner>
|
||||||
</div>
|
</div>
|
||||||
<div class="col column justify-center items-center no-wrap overflow-hidden" style="min-height: 230px">
|
<div class="col column justify-center items-center no-wrap overflow-hidden" style="min-height: 230px">
|
||||||
<span class="greeting"><b>{{ title }}</b></span>
|
<span class="greeting"><b>{{ title }}</b></span>
|
||||||
@@ -12,21 +12,31 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-auto column justify-start items-center no-wrap overflow-hidden">
|
<div class="col-auto column justify-start items-center no-wrap overflow-hidden">
|
||||||
<q-input ref="input" class="full-width q-px-sm" style="max-width: 700px" outlined dense bg-color="white" v-model="bookUrl" placeholder="URL книги">
|
<q-input
|
||||||
<template v-slot:append>
|
ref="input" v-model="bookUrl" class="full-width q-px-sm" style="max-width: 700px"
|
||||||
<q-btn rounded flat style="width: 40px" icon="la la-check" @click="submitUrl"/>
|
outlined dense bg-color="white" placeholder="Ссылка на книгу или веб-страницу" @keydown="onInputKeydown"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<q-btn rounded flat style="width: 40px" icon="la la-check" @click="submitUrl" />
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
|
|
||||||
<input type="file" id="file" ref="file" @change="loadFile" style='display: none;'/>
|
<input
|
||||||
|
id="file" ref="file" type="file"
|
||||||
|
style="display: none;"
|
||||||
|
:accept="acceptFileExt"
|
||||||
|
@change="loadFile"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="q-my-sm"></div>
|
<div class="q-my-sm"></div>
|
||||||
<q-btn no-caps dense class="q-px-sm" color="primary" size="13px" @click="loadFileClick">
|
<q-btn no-caps dense class="q-px-sm" color="primary" size="13px" @click="loadFileClick">
|
||||||
|
<q-icon class="q-mr-xs" name="la la-caret-square-up" size="24px" />
|
||||||
Загрузить файл с диска
|
Загрузить файл с диска
|
||||||
</q-btn>
|
</q-btn>
|
||||||
|
|
||||||
<div class="q-my-sm"></div>
|
<div class="q-my-sm"></div>
|
||||||
<q-btn no-caps dense class="q-px-sm" color="primary" size="13px" @click="loadBufferClick">
|
<q-btn no-caps dense class="q-px-sm" color="primary" size="13px" @click="loadBufferClick">
|
||||||
|
<q-icon class="q-mr-xs" name="la la-comment" size="24px" />
|
||||||
Из буфера обмена
|
Из буфера обмена
|
||||||
</q-btn>
|
</q-btn>
|
||||||
|
|
||||||
@@ -45,36 +55,55 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col column justify-end items-center no-wrap overflow-hidden">
|
<div class="col column justify-end items-center no-wrap overflow-hidden">
|
||||||
|
<span v-if="mode == 'omnireader'" class="bottom-span clickable" @click="findBook">Найти книгу</span>
|
||||||
<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 v-if="version == clientVersion" class="bottom-span">v{{ version }}</span>
|
<span v-if="version == clientVersion" class="bottom-span">v{{ version }}</span>
|
||||||
<span v-else class="bottom-span">Версия сервера {{ version }}, версия клиента {{ clientVersion }}, необходимо обновить страницу</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>
|
||||||
|
|
||||||
|
<Dialog ref="dialog1" v-model="findBookVisible">
|
||||||
|
<template #header>
|
||||||
|
Подсказка ;-)
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div style="word-break: normal">
|
||||||
|
Если вы хотите найти определенную книгу, добро пожаловать в
|
||||||
|
раздел "Сетевая библиотека" (кнопка <q-icon name="la la-sitemap" size="32px" />) на сайте читалки
|
||||||
|
<a href="https://liberama.top" target="_blank">liberama.top</a>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
import GithubCorner from './GithubCorner/GithubCorner.vue';
|
import GithubCorner from './GithubCorner/GithubCorner.vue';
|
||||||
|
|
||||||
|
import Dialog from '../../share/Dialog.vue';
|
||||||
import PasteTextPage from './PasteTextPage/PasteTextPage.vue';
|
import PasteTextPage from './PasteTextPage/PasteTextPage.vue';
|
||||||
import {versionHistory} from '../versionHistory';
|
import {versionHistory} from '../versionHistory';
|
||||||
|
import * as utils from '../../../share/utils';
|
||||||
|
|
||||||
export default @Component({
|
const componentOptions = {
|
||||||
components: {
|
components: {
|
||||||
GithubCorner,
|
GithubCorner,
|
||||||
|
Dialog,
|
||||||
PasteTextPage,
|
PasteTextPage,
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
class LoaderPage extends Vue {
|
class LoaderPage {
|
||||||
|
_options = componentOptions;
|
||||||
|
|
||||||
bookUrl = null;
|
bookUrl = null;
|
||||||
loadPercent = 0;
|
loadPercent = 0;
|
||||||
pasteTextActive = false;
|
pasteTextActive = false;
|
||||||
|
findBookVisible = false;
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.commit = this.$store.commit;
|
this.commit = this.$store.commit;
|
||||||
@@ -107,14 +136,16 @@ class LoaderPage extends Vue {
|
|||||||
return this.$store.state.config.version;
|
return this.$store.state.config.version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get acceptFileExt() {
|
||||||
|
return this.$store.state.config.acceptFileExt;
|
||||||
|
}
|
||||||
|
|
||||||
get isExternalConverter() {
|
get isExternalConverter() {
|
||||||
return this.$store.state.config.useExternalBookConverter;
|
return this.$store.state.config.useExternalBookConverter;
|
||||||
}
|
}
|
||||||
|
|
||||||
get clientVersion() {
|
get clientVersion() {
|
||||||
let v = versionHistory[0].header;
|
return versionHistory[0].version;
|
||||||
v = v.split(' ')[0];
|
|
||||||
return v;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
submitUrl() {
|
submitUrl() {
|
||||||
@@ -136,7 +167,7 @@ class LoaderPage extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadBufferClick() {
|
loadBufferClick() {
|
||||||
this.pasteTextToggle();
|
this.showPasteText();
|
||||||
}
|
}
|
||||||
|
|
||||||
loadBuffer(opts) {
|
loadBuffer(opts) {
|
||||||
@@ -146,6 +177,10 @@ class LoaderPage extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showPasteText() {
|
||||||
|
this.pasteTextActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
pasteTextToggle() {
|
pasteTextToggle() {
|
||||||
this.pasteTextActive = !this.pasteTextActive;
|
this.pasteTextActive = !this.pasteTextActive;
|
||||||
}
|
}
|
||||||
@@ -158,6 +193,10 @@ class LoaderPage extends Vue {
|
|||||||
this.$emit('do-action', {action: 'donate'});
|
this.$emit('do-action', {action: 'donate'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findBook() {
|
||||||
|
this.findBookVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
openComments() {
|
openComments() {
|
||||||
window.open('http://samlib.ru/comment/b/bookpauk/bookpauk_reader', '_blank');
|
window.open('http://samlib.ru/comment/b/bookpauk/bookpauk_reader', '_blank');
|
||||||
}
|
}
|
||||||
@@ -166,30 +205,30 @@ class LoaderPage extends Vue {
|
|||||||
window.open('http://old.omnireader.ru', '_blank');
|
window.open('http://old.omnireader.ru', '_blank');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async onInputKeydown(event) {
|
||||||
|
if (event.key == 'Enter') {
|
||||||
|
await utils.sleep(100);
|
||||||
|
this.submitUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
keyHook(event) {
|
keyHook(event) {
|
||||||
|
if (this.$refs.dialog1.active)
|
||||||
|
return true;
|
||||||
|
|
||||||
if (this.pasteTextActive) {
|
if (this.pasteTextActive) {
|
||||||
return this.$refs.pasteTextPage.keyHook(event);
|
return this.$refs.pasteTextPage.keyHook(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
//недостатки сторонних ui
|
const input = this.$refs.input.getNativeElement();
|
||||||
const input = this.$refs.input.$refs.input;
|
if (event.type == 'keydown' && (document.activeElement === input || event.code == 'Enter') && event.code != 'Escape')
|
||||||
if (document.activeElement === input && event.type == 'keydown' && event.key == 'Enter') {
|
|
||||||
this.submitUrl();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
if (event.type == 'keydown' && document.activeElement !== input) {
|
|
||||||
const action = this.$root.readerActionByKeyEvent(event);
|
|
||||||
switch (action) {
|
|
||||||
case 'help':
|
|
||||||
this.openHelp(event);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(LoaderPage);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<Window @close="close">
|
<Window @close="close">
|
||||||
<template slot="header">
|
<template #header>
|
||||||
<span style="position: relative; top: -3px">
|
<span style="position: relative; top: -3px">
|
||||||
Вставьте текст и нажмите
|
Вставьте текст и нажмите
|
||||||
<span class="clickable text-primary" style="font-size: 150%; position: relative; top: 1px" @click="loadBuffer">загрузить</span>
|
<span class="clickable text-primary" style="font-size: 150%; position: relative; top: 1px" @click="loadBuffer">загрузить</span>
|
||||||
@@ -8,27 +8,28 @@
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<q-input class="q-px-sm" dense borderless v-model="bookTitle" placeholder="Введите название текста"/>
|
<q-input v-model="bookTitle" class="q-px-sm" dense borderless placeholder="Введите название текста" />
|
||||||
<hr/>
|
<hr />
|
||||||
<textarea ref="textArea" class="text" @paste="calcTitle"></textarea>
|
<textarea ref="textArea" class="text" @paste="calcTitle"></textarea>
|
||||||
</Window>
|
</Window>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../../../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
|
|
||||||
import Window from '../../../share/Window.vue';
|
import Window from '../../../share/Window.vue';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import * as utils from '../../../../share/utils';
|
import * as utils from '../../../../share/utils';
|
||||||
|
|
||||||
export default @Component({
|
const componentOptions = {
|
||||||
components: {
|
components: {
|
||||||
Window,
|
Window,
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
class PasteTextPage extends Vue {
|
class PasteTextPage {
|
||||||
|
_options = componentOptions;
|
||||||
|
|
||||||
bookTitle = '';
|
bookTitle = '';
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
@@ -59,16 +60,20 @@ class PasteTextPage extends Vue {
|
|||||||
|
|
||||||
calcTitle(event) {
|
calcTitle(event) {
|
||||||
if (this.bookTitle == '') {
|
if (this.bookTitle == '') {
|
||||||
let text = event.clipboardData.getData('text');
|
this.bookTitle = `Из буфера обмена ${utils.formatDate(new Date(), 'noDate')}`;
|
||||||
this.bookTitle = `Из буфера обмена ${utils.formatDate(new Date(), 'noDate')}: ` + _.compact([
|
if (event) {
|
||||||
this.getNonEmptyLine3words(text, 1),
|
let text = event.clipboardData.getData('text');
|
||||||
this.getNonEmptyLine3words(text, 2)
|
this.bookTitle += ': ' + _.compact([
|
||||||
]).join(' - ');
|
this.getNonEmptyLine3words(text, 1),
|
||||||
|
this.getNonEmptyLine3words(text, 2)
|
||||||
|
]).join(' - ');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadBuffer() {
|
loadBuffer() {
|
||||||
this.$emit('load-buffer', {buffer: `<buffer><cut-title>${utils.escapeXml(this.bookTitle)}</cut-title>${this.$refs.textArea.value}</buffer>`});
|
this.calcTitle();
|
||||||
|
this.$emit('load-buffer', {buffer: `<buffer><fb2-title>${utils.escapeXml(this.bookTitle)}</fb2-title>${utils.escapeXml(this.$refs.textArea.value)}</buffer>`});
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +95,8 @@ class PasteTextPage extends Vue {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(PasteTextPage);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<span class="text-yellow">{{ text }}</span>
|
<span class="text-yellow">{{ text }}</span>
|
||||||
<q-icon :style="iconStyle" color="yellow" name="la la-slash" size="20px"/>
|
<q-icon :style="iconStyle" color="yellow" name="la la-slash" size="20px" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -25,8 +25,8 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
import * as utils from '../../../share/utils';
|
import * as utils from '../../../share/utils';
|
||||||
|
|
||||||
const ruMessage = {
|
const ruMessage = {
|
||||||
@@ -42,9 +42,7 @@ const ruMessage = {
|
|||||||
'upload': 'отправка',
|
'upload': 'отправка',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default @Component({
|
class ProgressPage {
|
||||||
})
|
|
||||||
class ProgressPage extends Vue {
|
|
||||||
text = '';
|
text = '';
|
||||||
totalSteps = 1;
|
totalSteps = 1;
|
||||||
step = 1;
|
step = 1;
|
||||||
@@ -96,5 +94,7 @@ class ProgressPage extends Vue {
|
|||||||
return Math.round(((this.step - 1)/this.totalSteps + this.progress/(100*this.totalSteps))*100);
|
return Math.round(((this.step - 1)/this.totalSteps + this.progress/(100*this.totalSteps))*100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(ProgressPage);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,75 +1,131 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="column no-wrap">
|
<div class="column no-wrap">
|
||||||
<div ref="header" class="header" v-show="toolBarActive">
|
<div v-show="toolBarActive" ref="header" class="header">
|
||||||
<div ref="buttons" class="row justify-between no-wrap">
|
<div ref="buttons" class="row justify-between no-wrap">
|
||||||
<div>
|
<div class="row no-wrap">
|
||||||
<button ref="loader" class="tool-button" :class="buttonActiveClass('loader')" @click="buttonClick('loader')" v-ripple>
|
<button ref="loader" v-ripple class="tool-button" :class="buttonActiveClass('loader')" @click="buttonClick('loader')">
|
||||||
<q-icon name="la la-arrow-left" size="32px"/>
|
<q-icon name="la la-arrow-left" size="32px" />
|
||||||
<q-tooltip :delay="1500" anchor="bottom right" content-style="font-size: 80%">{{ rstore.readerActions['loader'] }}</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom right" content-style="font-size: 80%">
|
||||||
|
{{ rstore.readerActions['loader'] }}
|
||||||
|
</q-tooltip>
|
||||||
|
</button>
|
||||||
|
<button v-show="showToolButton['loadFile']" ref="loadFile" v-ripple class="tool-button" :class="buttonActiveClass('loadFile')" @click="buttonClick('loadFile')">
|
||||||
|
<q-icon name="la la-caret-square-up" size="32px" />
|
||||||
|
<q-tooltip :delay="1500" anchor="bottom right" content-style="font-size: 80%">
|
||||||
|
{{ rstore.readerActions['loadFile'] }}
|
||||||
|
</q-tooltip>
|
||||||
|
</button>
|
||||||
|
<button v-show="showToolButton['loadBuffer']" ref="loadBuffer" v-ripple class="tool-button" :class="buttonActiveClass('loadBuffer')" @click="buttonClick('loadBuffer')">
|
||||||
|
<q-icon name="la la-comment" size="32px" />
|
||||||
|
<q-tooltip :delay="1500" anchor="bottom right" content-style="font-size: 80%">
|
||||||
|
{{ rstore.readerActions['loadBuffer'] }}
|
||||||
|
</q-tooltip>
|
||||||
|
</button>
|
||||||
|
<button v-show="showToolButton['help']" ref="help" v-ripple class="tool-button" :class="buttonActiveClass('help')" @click="buttonClick('help')">
|
||||||
|
<q-icon name="la la-question" size="32px" />
|
||||||
|
<q-tooltip :delay="1500" anchor="bottom right" content-style="font-size: 80%">
|
||||||
|
{{ rstore.readerActions['help'] }}
|
||||||
|
</q-tooltip>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="row no-wrap">
|
||||||
<button ref="undoAction" v-show="showToolButton['undoAction']" class="tool-button" :class="buttonActiveClass('undoAction')" @click="buttonClick('undoAction')" v-ripple>
|
<div class="space"></div>
|
||||||
<q-icon name="la la-angle-left" size="32px"/>
|
<button v-show="showToolButton['undoAction']" ref="undoAction" v-ripple class="tool-button" :class="buttonActiveClass('undoAction')" @click="buttonClick('undoAction')">
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['undoAction'] }}</q-tooltip>
|
<q-icon name="la la-angle-left" size="32px" />
|
||||||
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
{{ rstore.readerActions['undoAction'] }}
|
||||||
|
</q-tooltip>
|
||||||
</button>
|
</button>
|
||||||
<button ref="redoAction" v-show="showToolButton['redoAction']" class="tool-button" :class="buttonActiveClass('redoAction')" @click="buttonClick('redoAction')" v-ripple>
|
<button v-show="showToolButton['redoAction']" ref="redoAction" v-ripple class="tool-button" :class="buttonActiveClass('redoAction')" @click="buttonClick('redoAction')">
|
||||||
<q-icon name="la la-angle-right" size="32px"/>
|
<q-icon name="la la-angle-right" size="32px" />
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['redoAction'] }}</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
{{ rstore.readerActions['redoAction'] }}
|
||||||
|
</q-tooltip>
|
||||||
</button>
|
</button>
|
||||||
<div class="space"></div>
|
<div class="space"></div>
|
||||||
<button ref="fullScreen" v-show="showToolButton['fullScreen']" class="tool-button" :class="buttonActiveClass('fullScreen')" @click="buttonClick('fullScreen')" v-ripple>
|
<button v-show="showToolButton['fullScreen']" ref="fullScreen" v-ripple class="tool-button" :class="buttonActiveClass('fullScreen')" @click="buttonClick('fullScreen')">
|
||||||
<q-icon :name="(fullScreenActive ? 'la la-compress-arrows-alt': 'la la-expand-arrows-alt')" size="32px"/>
|
<q-icon :name="(fullScreenActive ? 'la la-compress-arrows-alt': 'la la-expand-arrows-alt')" size="32px" />
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['fullScreen'] }}</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
{{ rstore.readerActions['fullScreen'] }}
|
||||||
|
</q-tooltip>
|
||||||
</button>
|
</button>
|
||||||
<button ref="scrolling" v-show="showToolButton['scrolling']" class="tool-button" :class="buttonActiveClass('scrolling')" @click="buttonClick('scrolling')" v-ripple>
|
<button v-show="showToolButton['scrolling']" ref="scrolling" v-ripple class="tool-button" :class="buttonActiveClass('scrolling')" @click="buttonClick('scrolling')">
|
||||||
<q-icon name="la la-film" size="32px"/>
|
<q-icon name="la la-film" size="32px" />
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['scrolling'] }}</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
{{ rstore.readerActions['scrolling'] }}
|
||||||
|
</q-tooltip>
|
||||||
</button>
|
</button>
|
||||||
<button ref="setPosition" v-show="showToolButton['setPosition']" class="tool-button" :class="buttonActiveClass('setPosition')" @click="buttonClick('setPosition')" v-ripple>
|
<button v-show="showToolButton['setPosition']" ref="setPosition" v-ripple class="tool-button" :class="buttonActiveClass('setPosition')" @click="buttonClick('setPosition')">
|
||||||
<q-icon name="la la-angle-double-right" size="32px"/>
|
<q-icon name="la la-angle-double-right" size="32px" />
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['setPosition'] }}</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
{{ rstore.readerActions['setPosition'] }}
|
||||||
|
</q-tooltip>
|
||||||
</button>
|
</button>
|
||||||
<button ref="search" v-show="showToolButton['search']" class="tool-button" :class="buttonActiveClass('search')" @click="buttonClick('search')" v-ripple>
|
<button v-show="showToolButton['search']" ref="search" v-ripple class="tool-button" :class="buttonActiveClass('search')" @click="buttonClick('search')">
|
||||||
<q-icon name="la la-search" size="32px"/>
|
<q-icon name="la la-search" size="32px" />
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['search'] }}</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
{{ rstore.readerActions['search'] }}
|
||||||
|
</q-tooltip>
|
||||||
</button>
|
</button>
|
||||||
<button ref="copyText" v-show="showToolButton['copyText']" class="tool-button" :class="buttonActiveClass('copyText')" @click="buttonClick('copyText')" v-ripple>
|
<button v-show="showToolButton['copyText']" ref="copyText" v-ripple class="tool-button" :class="buttonActiveClass('copyText')" @click="buttonClick('copyText')">
|
||||||
<q-icon name="la la-copy" size="32px"/>
|
<q-icon name="la la-copy" size="32px" />
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['copyText'] }}</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
{{ rstore.readerActions['copyText'] }}
|
||||||
|
</q-tooltip>
|
||||||
</button>
|
</button>
|
||||||
<button ref="splitToPara" v-show="showToolButton['splitToPara']" class="tool-button" :class="buttonActiveClass('splitToPara')" @click="buttonClick('splitToPara')" v-ripple>
|
<button v-show="showToolButton['convOptions']" ref="convOptions" v-ripple class="tool-button" :class="buttonActiveClass('convOptions')" @click="buttonClick('convOptions')">
|
||||||
<q-icon name="la la-retweet" size="32px"/>
|
<q-icon name="la la-magic" size="32px" />
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['splitToPara'] }}</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
{{ rstore.readerActions['convOptions'] }}
|
||||||
|
</q-tooltip>
|
||||||
</button>
|
</button>
|
||||||
<button ref="refresh" v-show="showToolButton['refresh']" class="tool-button" :class="buttonActiveClass('refresh')" @click="buttonClick('refresh')" v-ripple>
|
<button v-show="showToolButton['refresh']" ref="refresh" v-ripple class="tool-button" :class="buttonActiveClass('refresh')" @click="buttonClick('refresh')">
|
||||||
<q-icon name="la la-sync" size="32px" :class="{clear: !showRefreshIcon}"/>
|
<q-icon name="la la-sync" size="32px" :class="{clear: !showRefreshIcon}" />
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['refresh'] }}</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
{{ rstore.readerActions['refresh'] }}
|
||||||
|
</q-tooltip>
|
||||||
</button>
|
</button>
|
||||||
<div class="space"></div>
|
<div class="space"></div>
|
||||||
<button ref="contents" v-show="showToolButton['contents']" class="tool-button" :class="buttonActiveClass('contents')" @click="buttonClick('contents')" v-ripple>
|
<button v-show="showToolButton['contents']" ref="contents" v-ripple class="tool-button" :class="buttonActiveClass('contents')" @click="buttonClick('contents')">
|
||||||
<q-icon name="la la-list" size="32px"/>
|
<q-icon name="la la-list" size="32px" />
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['contents'] }}</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
{{ rstore.readerActions['contents'] }}
|
||||||
|
</q-tooltip>
|
||||||
</button>
|
</button>
|
||||||
<button ref="libs" v-show="mode == 'liberama.top' && showToolButton['libs']" class="tool-button" :class="buttonActiveClass('libs')" @click="buttonClick('libs')" v-ripple>
|
<button v-show="mode == 'liberama.top' && showToolButton['libs']" ref="libs" v-ripple class="tool-button" :class="buttonActiveClass('libs')" @click="buttonClick('libs')">
|
||||||
<q-icon name="la la-sitemap" size="32px"/>
|
<q-icon name="la la-sitemap" size="32px" />
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['libs'] }}</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
{{ rstore.readerActions['libs'] }}
|
||||||
|
</q-tooltip>
|
||||||
</button>
|
</button>
|
||||||
<button ref="recentBooks" v-show="showToolButton['recentBooks']" class="tool-button" :class="buttonActiveClass('recentBooks')" @click="buttonClick('recentBooks')" v-ripple>
|
<button v-show="showToolButton['recentBooks']" ref="recentBooks" v-ripple class="tool-button" :class="buttonActiveClass('recentBooks')" @click="buttonClick('recentBooks')">
|
||||||
<q-icon name="la la-book-open" size="32px"/>
|
<q-icon name="la la-book-open" size="32px" />
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['recentBooks'] }}</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
{{ rstore.readerActions['recentBooks'] }}
|
||||||
|
</q-tooltip>
|
||||||
</button>
|
</button>
|
||||||
|
<div class="space"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="row no-wrap">
|
||||||
<button ref="offlineMode" v-show="showToolButton['offlineMode']" class="tool-button" :class="buttonActiveClass('offlineMode')" @click="buttonClick('offlineMode')" v-ripple>
|
<button v-show="showToolButton['clickControl']" ref="clickControl" v-ripple class="tool-button" :class="buttonActiveClass('clickControl')" @click="buttonClick('clickControl')">
|
||||||
<q-icon name="la la-unlink" size="32px"/>
|
<q-icon name="la la-mouse" size="32px" />
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['offlineMode'] }}</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
{{ rstore.readerActions['clickControl'] }}
|
||||||
|
</q-tooltip>
|
||||||
</button>
|
</button>
|
||||||
<button ref="settings" class="tool-button" :class="buttonActiveClass('settings')" @click="buttonClick('settings')" v-ripple>
|
<button v-show="showToolButton['offlineMode']" ref="offlineMode" v-ripple class="tool-button" :class="buttonActiveClass('offlineMode')" @click="buttonClick('offlineMode')">
|
||||||
<q-icon name="la la-cog" size="32px"/>
|
<q-icon name="la la-unlink" size="32px" />
|
||||||
<q-tooltip :delay="1500" anchor="bottom left" content-style="font-size: 80%">{{ rstore.readerActions['settings'] }}</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
{{ rstore.readerActions['offlineMode'] }}
|
||||||
|
</q-tooltip>
|
||||||
|
</button>
|
||||||
|
<button ref="settings" v-ripple class="tool-button" :class="buttonActiveClass('settings')" @click="buttonClick('settings')">
|
||||||
|
<q-icon name="la la-cog" size="32px" />
|
||||||
|
<q-tooltip :delay="1500" anchor="bottom left" content-style="font-size: 80%">
|
||||||
|
{{ rstore.readerActions['settings'] }}
|
||||||
|
</q-tooltip>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -77,7 +133,10 @@
|
|||||||
|
|
||||||
<div class="main col row relative-position">
|
<div class="main col row relative-position">
|
||||||
<keep-alive>
|
<keep-alive>
|
||||||
<component ref="page" class="col" :is="activePage"
|
<component
|
||||||
|
:is="activePage"
|
||||||
|
ref="page"
|
||||||
|
class="col"
|
||||||
@load-book="loadBook"
|
@load-book="loadBook"
|
||||||
@load-file="loadFile"
|
@load-file="loadFile"
|
||||||
@book-pos-changed="bookPosChanged"
|
@book-pos-changed="bookPosChanged"
|
||||||
@@ -86,30 +145,32 @@
|
|||||||
</keep-alive>
|
</keep-alive>
|
||||||
|
|
||||||
<SetPositionPage v-if="setPositionActive" ref="setPositionPage" @set-position-toggle="setPositionToggle" @book-pos-changed="bookPosChanged"></SetPositionPage>
|
<SetPositionPage v-if="setPositionActive" ref="setPositionPage" @set-position-toggle="setPositionToggle" @book-pos-changed="bookPosChanged"></SetPositionPage>
|
||||||
<SearchPage v-show="searchActive" ref="searchPage"
|
<SearchPage
|
||||||
|
v-show="searchActive"
|
||||||
|
ref="searchPage"
|
||||||
@do-action="doAction"
|
@do-action="doAction"
|
||||||
@book-pos-changed="bookPosChanged"
|
@book-pos-changed="bookPosChanged"
|
||||||
@start-text-search="startTextSearch"
|
@start-text-search="startTextSearch"
|
||||||
@stop-text-search="stopTextSearch">
|
@stop-text-search="stopTextSearch"
|
||||||
</SearchPage>
|
></SearchPage>
|
||||||
<CopyTextPage v-if="copyTextActive" ref="copyTextPage" @do-action="doAction"></CopyTextPage>
|
<CopyTextPage v-if="copyTextActive" ref="copyTextPage" @do-action="doAction"></CopyTextPage>
|
||||||
<LibsPage v-show="hidden" ref="libsPage" @load-book="loadBook" @libs-close="libsClose" @do-action="doAction"></LibsPage>
|
<LibsPage v-show="hidden" ref="libsPage" @load-book="loadBook" @libs-close="libsClose" @do-action="doAction"></LibsPage>
|
||||||
<RecentBooksPage v-show="recentBooksActive" ref="recentBooksPage" @load-book="loadBook" @recent-books-close="recentBooksClose"></RecentBooksPage>
|
<RecentBooksPage v-show="recentBooksActive" ref="recentBooksPage" @load-book="loadBook" @recent-books-close="recentBooksClose"></RecentBooksPage>
|
||||||
<SettingsPage v-show="settingsActive" ref="settingsPage" @do-action="doAction"></SettingsPage>
|
<SettingsPage v-show="settingsActive" ref="settingsPage" @do-action="doAction"></SettingsPage>
|
||||||
<HelpPage v-if="helpActive" ref="helpPage" @do-action="doAction"></HelpPage>
|
<HelpPage v-if="helpActive" ref="helpPage" @do-action="doAction"></HelpPage>
|
||||||
<ClickMapPage v-show="clickMapActive" ref="clickMapPage"></ClickMapPage>
|
<ClickMapPage v-show="clickMapActive" ref="clickMapPage"></ClickMapPage>
|
||||||
<ServerStorage v-show="hidden" ref="serverStorage"></ServerStorage>
|
<ContentsPage v-show="contentsActive" ref="contentsPage" :book-pos="bookPos" :is-visible="contentsActive" @do-action="doAction" @book-pos-changed="bookPosChanged"></ContentsPage>
|
||||||
<ContentsPage v-show="contentsActive" ref="contentsPage" @do-action="doAction" @book-pos-changed="bookPosChanged"></ContentsPage>
|
|
||||||
|
|
||||||
<ReaderDialogs ref="dialogs" @donate-toggle="donateToggle" @version-history-toggle="versionHistoryToggle"></ReaderDialogs>
|
<ServerStorage v-show="hidden" ref="serverStorage"></ServerStorage>
|
||||||
|
<ReaderDialogs ref="dialogs" @donate-toggle="donateToggle" @version-history-toggle="versionHistoryToggle" @load-buffer-toggle="loadBufferToggle"></ReaderDialogs>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import {Buffer} from 'safe-buffer';
|
import {Buffer} from 'safe-buffer';
|
||||||
|
|
||||||
@@ -125,17 +186,23 @@ 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';
|
||||||
import ServerStorage from './ServerStorage/ServerStorage.vue';
|
|
||||||
import ContentsPage from './ContentsPage/ContentsPage.vue';
|
import ContentsPage from './ContentsPage/ContentsPage.vue';
|
||||||
|
|
||||||
|
import ServerStorage from './ServerStorage/ServerStorage.vue';
|
||||||
import ReaderDialogs from './ReaderDialogs/ReaderDialogs.vue';
|
import ReaderDialogs from './ReaderDialogs/ReaderDialogs.vue';
|
||||||
|
|
||||||
import bookManager from './share/bookManager';
|
import bookManager from './share/bookManager';
|
||||||
|
import wallpaperStorage from './share/wallpaperStorage';
|
||||||
|
import dynamicCss from '../../share/dynamicCss';
|
||||||
|
|
||||||
import rstore from '../../store/modules/reader';
|
import rstore from '../../store/modules/reader';
|
||||||
import readerApi from '../../api/reader';
|
import readerApi from '../../api/reader';
|
||||||
|
import miscApi from '../../api/misc';
|
||||||
|
|
||||||
|
import {versionHistory} from './versionHistory';
|
||||||
import * as utils from '../../share/utils';
|
import * as utils from '../../share/utils';
|
||||||
|
|
||||||
export default @Component({
|
const componentOptions = {
|
||||||
components: {
|
components: {
|
||||||
LoaderPage,
|
LoaderPage,
|
||||||
TextPage,
|
TextPage,
|
||||||
@@ -149,9 +216,9 @@ export default @Component({
|
|||||||
SettingsPage,
|
SettingsPage,
|
||||||
HelpPage,
|
HelpPage,
|
||||||
ClickMapPage,
|
ClickMapPage,
|
||||||
ServerStorage,
|
|
||||||
ContentsPage,
|
ContentsPage,
|
||||||
|
|
||||||
|
ServerStorage,
|
||||||
ReaderDialogs,
|
ReaderDialogs,
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -191,25 +258,38 @@ export default @Component({
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
},
|
},
|
||||||
|
dualPageMode(newValue) {
|
||||||
|
if (newValue)
|
||||||
|
this.stopScrolling();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
class Reader extends Vue {
|
|
||||||
rstore = {};
|
|
||||||
loaderActive = false;
|
|
||||||
offlineModeActive = false;
|
|
||||||
progressActive = false;
|
|
||||||
fullScreenActive = false;
|
|
||||||
|
|
||||||
scrollingActive = false;
|
class Reader {
|
||||||
|
_options = componentOptions;
|
||||||
|
|
||||||
|
rstore = {};
|
||||||
|
|
||||||
|
loaderActive = false;
|
||||||
|
loadFileActive = false;
|
||||||
|
loadBufferActive = false;
|
||||||
|
fullScreenActive = false;
|
||||||
setPositionActive = false;
|
setPositionActive = false;
|
||||||
searchActive = false;
|
searchActive = false;
|
||||||
copyTextActive = false;
|
copyTextActive = false;
|
||||||
|
convOptionsActive = false;
|
||||||
|
refreshActive = false;
|
||||||
|
contentsActive = false;
|
||||||
libsActive = false;
|
libsActive = false;
|
||||||
recentBooksActive = false;
|
recentBooksActive = false;
|
||||||
|
clickControlActive = false;
|
||||||
|
offlineModeActive = false;
|
||||||
settingsActive = false;
|
settingsActive = false;
|
||||||
helpActive = false;
|
|
||||||
clickMapActive = false;
|
clickMapActive = false;
|
||||||
contentsActive = false;
|
helpActive = false;
|
||||||
|
scrollingActive = false;
|
||||||
|
progressActive = false;
|
||||||
|
|
||||||
bookPos = null;
|
bookPos = null;
|
||||||
allowUrlParamBookPos = false;
|
allowUrlParamBookPos = false;
|
||||||
@@ -224,19 +304,28 @@ class Reader extends Vue {
|
|||||||
whatsNewVisible = false;
|
whatsNewVisible = false;
|
||||||
whatsNewContent = '';
|
whatsNewContent = '';
|
||||||
donationVisible = false;
|
donationVisible = false;
|
||||||
|
dualPageMode = false;
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.rstore = rstore;
|
this.rstore = rstore;
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.commit = this.$store.commit;
|
this.commit = this.$store.commit;
|
||||||
this.dispatch = this.$store.dispatch;
|
|
||||||
this.reader = this.$store.state.reader;
|
this.reader = this.$store.state.reader;
|
||||||
this.config = this.$store.state.config;
|
this.config = this.$store.state.config;
|
||||||
|
|
||||||
this.$root.addKeyHook(this.keyHook);
|
this.$root.addEventHook('key', this.keyHook);
|
||||||
|
|
||||||
this.lastActivePage = false;
|
this.lastActivePage = false;
|
||||||
|
|
||||||
|
this.$watch(
|
||||||
|
() => this.$route.path,
|
||||||
|
(newValue) => {
|
||||||
|
if (newValue == '/reader') {
|
||||||
|
this.updateRoute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
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)) {
|
||||||
@@ -250,7 +339,7 @@ class Reader extends Vue {
|
|||||||
await this.$nextTick();
|
await this.$nextTick();
|
||||||
this.paramPosIgnore = false;
|
this.paramPosIgnore = false;
|
||||||
}
|
}
|
||||||
}, 500, {maxWait: 5000});
|
}, 250, {maxWait: 5000});
|
||||||
|
|
||||||
this.scrollingSetRecentBook = _.debounce((newValue) => {
|
this.scrollingSetRecentBook = _.debounce((newValue) => {
|
||||||
this.debouncedSetRecentBook(newValue);
|
this.debouncedSetRecentBook(newValue);
|
||||||
@@ -264,13 +353,12 @@ class Reader extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.updateHeaderMinWidth();
|
|
||||||
|
|
||||||
(async() => {
|
(async() => {
|
||||||
|
await wallpaperStorage.init();
|
||||||
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.getRootRoute() == '/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});
|
||||||
} else {
|
} else {
|
||||||
@@ -293,6 +381,16 @@ class Reader extends Vue {
|
|||||||
|
|
||||||
await this.$refs.dialogs.init();
|
await this.$refs.dialogs.init();
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
(async() => {
|
||||||
|
this.isFirstNeedUpdateNotify = true;
|
||||||
|
//вечный цикл, запрашиваем периодически конфиг для проверки выхода новой версии читалки
|
||||||
|
while (true) {// eslint-disable-line no-constant-condition
|
||||||
|
await this.checkNewVersionAvailable();
|
||||||
|
await utils.sleep(3600*1000); //каждый час
|
||||||
|
}
|
||||||
|
//дальше кода нет
|
||||||
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
loadSettings() {
|
loadSettings() {
|
||||||
@@ -301,27 +399,87 @@ class Reader extends Vue {
|
|||||||
this.copyFullText = settings.copyFullText;
|
this.copyFullText = settings.copyFullText;
|
||||||
this.showClickMapPage = settings.showClickMapPage;
|
this.showClickMapPage = settings.showClickMapPage;
|
||||||
this.clickControl = settings.clickControl;
|
this.clickControl = settings.clickControl;
|
||||||
|
this.clickControlActive = this.clickControl;
|
||||||
this.blinkCachedLoad = settings.blinkCachedLoad;
|
this.blinkCachedLoad = settings.blinkCachedLoad;
|
||||||
this.showToolButton = settings.showToolButton;
|
this.showToolButton = settings.showToolButton;
|
||||||
this.enableSitesFilter = settings.enableSitesFilter;
|
this.enableSitesFilter = settings.enableSitesFilter;
|
||||||
|
this.showNeedUpdateNotify = settings.showNeedUpdateNotify;
|
||||||
|
this.splitToPara = settings.splitToPara;
|
||||||
|
this.djvuQuality = settings.djvuQuality;
|
||||||
|
this.pdfAsText = settings.pdfAsText;
|
||||||
|
this.pdfQuality = settings.pdfQuality;
|
||||||
|
this.dualPageMode = settings.dualPageMode;
|
||||||
|
this.userWallpapers = settings.userWallpapers;
|
||||||
|
|
||||||
this.readerActionByKeyCode = utils.userHotKeysObjectSwap(settings.userHotKeys);
|
this.readerActionByKeyCode = utils.userHotKeysObjectSwap(settings.userHotKeys);
|
||||||
this.$root.readerActionByKeyEvent = (event) => {
|
this.$root.readerActionByKeyEvent = (event) => {
|
||||||
return this.readerActionByKeyCode[utils.keyEventToCode(event)];
|
return this.readerActionByKeyCode[utils.keyEventToCode(event)];
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateHeaderMinWidth();
|
this.loadWallpapers();//no await
|
||||||
}
|
}
|
||||||
|
|
||||||
updateHeaderMinWidth() {
|
showHelpOnErrorIfNeeded(errorMessage) {
|
||||||
const showButtonCount = Object.values(this.showToolButton).reduce((a, b) => a + (b ? 1 : 0), 0);
|
//небольшая эвристика
|
||||||
if (this.$refs.buttons)
|
let i = errorMessage.indexOf('http://');
|
||||||
this.$refs.buttons.style.minWidth = 65*showButtonCount + 'px';
|
if (i < 0)
|
||||||
(async() => {
|
i = errorMessage.indexOf('https://');
|
||||||
await utils.sleep(1000);
|
|
||||||
if (this.$refs.header)
|
errorMessage = errorMessage.substring(i + 7);
|
||||||
this.$refs.header.style.overflowX = 'auto';
|
const perCount = errorMessage.split('%').length - 1;
|
||||||
})();
|
|
||||||
|
if (perCount > errorMessage.length/3.2) {
|
||||||
|
this.$refs.dialogs.showUrlHelp();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//wallpaper css
|
||||||
|
async loadWallpapers() {
|
||||||
|
const wallpaperDataLength = await wallpaperStorage.getLength();
|
||||||
|
if (wallpaperDataLength !== this.wallpaperDataLength) {//оптимизация
|
||||||
|
this.wallpaperDataLength = wallpaperDataLength;
|
||||||
|
|
||||||
|
let newCss = '';
|
||||||
|
for (const wp of this.userWallpapers) {
|
||||||
|
const data = await wallpaperStorage.getData(wp.cssClass);
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
//здесь будем восстанавливать данные с сервера
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
newCss += `.${wp.cssClass} {background: url(${data}) center; background-size: 100% 100%;}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dynamicCss.replace('wallpapers', newCss);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkNewVersionAvailable() {
|
||||||
|
if (!this.checkingNewVersion && this.showNeedUpdateNotify) {
|
||||||
|
this.checkingNewVersion = true;
|
||||||
|
try {
|
||||||
|
await utils.sleep(15*1000); //подождем 15 секунд, чтобы прогрузился ServiceWorker при выходе новой версии
|
||||||
|
const config = await miscApi.loadConfig();
|
||||||
|
this.commit('config/setConfig', config);
|
||||||
|
|
||||||
|
let againMes = '';
|
||||||
|
if (this.isFirstNeedUpdateNotify) {
|
||||||
|
againMes = ' еще один раз';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.version != this.clientVersion)
|
||||||
|
this.$root.notify.info(`Вышла новая версия (v${this.version}) читалки.<br>Пожалуйста, обновите страницу${againMes}.`, 'Обновление');
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e);
|
||||||
|
} finally {
|
||||||
|
this.checkingNewVersion = false;
|
||||||
|
}
|
||||||
|
this.isFirstNeedUpdateNotify = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkSetStorageAccessKey() {
|
checkSetStorageAccessKey() {
|
||||||
@@ -394,6 +552,14 @@ class Reader extends Vue {
|
|||||||
return this.$store.state.config.mode;
|
return this.$store.state.config.mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get version() {
|
||||||
|
return this.$store.state.config.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
get clientVersion() {
|
||||||
|
return versionHistory[0].version;
|
||||||
|
}
|
||||||
|
|
||||||
get routeParamUrl() {
|
get routeParamUrl() {
|
||||||
let result = '';
|
let result = '';
|
||||||
const path = this.$route.fullPath;
|
const path = this.$route.fullPath;
|
||||||
@@ -449,7 +615,20 @@ class Reader extends Vue {
|
|||||||
//сохранение в serverStorage
|
//сохранение в serverStorage
|
||||||
if (value) {
|
if (value) {
|
||||||
await utils.sleep(500);
|
await utils.sleep(500);
|
||||||
await this.$refs.serverStorage.saveRecent(value);
|
|
||||||
|
let timer = setTimeout(() => {
|
||||||
|
if (!this.offlineModeActive)
|
||||||
|
this.$root.notify.error('Таймаут соединения');
|
||||||
|
}, 10000);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.$refs.serverStorage.saveRecent(value);
|
||||||
|
} catch (e) {
|
||||||
|
if (!this.offlineModeActive)
|
||||||
|
this.$root.notify.error(e.message);
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -480,7 +659,7 @@ class Reader extends Vue {
|
|||||||
|
|
||||||
toolBarToggle() {
|
toolBarToggle() {
|
||||||
this.commit('reader/setToolBarActive', !this.toolBarActive);
|
this.commit('reader/setToolBarActive', !this.toolBarActive);
|
||||||
this.$root.$emit('resize');
|
this.$root.eventHook('resize');
|
||||||
}
|
}
|
||||||
|
|
||||||
fullScreenToggle() {
|
fullScreenToggle() {
|
||||||
@@ -510,6 +689,28 @@ class Reader extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadFileToggle() {
|
||||||
|
if (!this.loaderActive)
|
||||||
|
this.loaderToggle();
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const page = this.$refs.page;
|
||||||
|
if (this.activePage == 'LoaderPage' && page.loadFileClick) {
|
||||||
|
page.loadFileClick();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadBufferToggle() {
|
||||||
|
if (!this.loaderActive)
|
||||||
|
this.loaderToggle();
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const page = this.$refs.page;
|
||||||
|
if (this.activePage == 'LoaderPage' && page.showPasteText) {
|
||||||
|
page.showPasteText();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setPositionToggle() {
|
setPositionToggle() {
|
||||||
this.setPositionActive = !this.setPositionActive;
|
this.setPositionActive = !this.setPositionActive;
|
||||||
const page = this.$refs.page;
|
const page = this.$refs.page;
|
||||||
@@ -637,6 +838,12 @@ class Reader extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clickControlToggle() {
|
||||||
|
const newSettings = _.cloneDeep(this.settings);
|
||||||
|
newSettings.clickControl = !this.clickControl;
|
||||||
|
this.commit('reader/setSettings', newSettings);
|
||||||
|
}
|
||||||
|
|
||||||
offlineModeToggle() {
|
offlineModeToggle() {
|
||||||
this.offlineModeActive = !this.offlineModeActive;
|
this.offlineModeActive = !this.offlineModeActive;
|
||||||
this.$refs.serverStorage.offlineModeActive = this.offlineModeActive;
|
this.$refs.serverStorage.offlineModeActive = this.offlineModeActive;
|
||||||
@@ -656,6 +863,12 @@ class Reader extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
convOptionsToggle() {
|
||||||
|
this.settingsToggle();
|
||||||
|
if (this.settingsActive)
|
||||||
|
this.$refs.settingsPage.selectedTab = 'convert';
|
||||||
|
}
|
||||||
|
|
||||||
helpToggle() {
|
helpToggle() {
|
||||||
this.helpActive = !this.helpActive;
|
this.helpActive = !this.helpActive;
|
||||||
if (this.helpActive) {
|
if (this.helpActive) {
|
||||||
@@ -682,15 +895,9 @@ class Reader extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshBook(mode) {
|
refreshBook() {
|
||||||
const mrb = this.mostRecentBook();
|
const mrb = this.mostRecentBook();
|
||||||
if (mrb) {
|
this.loadBook({url: mrb.url, uploadFileName: mrb.uploadFileName, force: true});
|
||||||
if (mode && mode == 'split') {
|
|
||||||
this.loadBook({url: mrb.url, uploadFileName: mrb.uploadFileName, skipCheck: true, isText: true, force: true});
|
|
||||||
} else {
|
|
||||||
this.loadBook({url: mrb.url, uploadFileName: mrb.uploadFileName, force: true});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
undoAction() {
|
undoAction() {
|
||||||
@@ -725,16 +932,19 @@ class Reader extends Vue {
|
|||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'loader':
|
case 'loader':
|
||||||
|
case 'loadFile':
|
||||||
|
case 'loadBuffer':
|
||||||
|
case 'help':
|
||||||
case 'fullScreen':
|
case 'fullScreen':
|
||||||
case 'setPosition':
|
case 'setPosition':
|
||||||
case 'scrolling':
|
|
||||||
case 'search':
|
case 'search':
|
||||||
case 'copyText':
|
case 'copyText':
|
||||||
case 'splitToPara':
|
case 'convOptions':
|
||||||
case 'refresh':
|
case 'refresh':
|
||||||
case 'contents':
|
case 'contents':
|
||||||
case 'libs':
|
case 'libs':
|
||||||
case 'recentBooks':
|
case 'recentBooks':
|
||||||
|
case 'clickControl':
|
||||||
case 'offlineMode':
|
case 'offlineMode':
|
||||||
case 'settings':
|
case 'settings':
|
||||||
if (this.progressActive) {
|
if (this.progressActive) {
|
||||||
@@ -743,6 +953,13 @@ class Reader extends Vue {
|
|||||||
classResult = classActive;
|
classResult = classActive;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'scrolling':
|
||||||
|
if (this.progressActive || this.dualPageMode) {
|
||||||
|
classResult = classDisabled;
|
||||||
|
} else if (this[`${action}Active`]) {
|
||||||
|
classResult = classActive;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'undoAction':
|
case 'undoAction':
|
||||||
if (this.actionCur <= 0)
|
if (this.actionCur <= 0)
|
||||||
classResult = classDisabled;
|
classResult = classDisabled;
|
||||||
@@ -764,7 +981,6 @@ class Reader extends Vue {
|
|||||||
case 'contents':
|
case 'contents':
|
||||||
classResult = classDisabled;
|
classResult = classDisabled;
|
||||||
break;
|
break;
|
||||||
case 'splitToPara':
|
|
||||||
case 'refresh':
|
case 'refresh':
|
||||||
case 'recentBooks':
|
case 'recentBooks':
|
||||||
if (!this.mostRecentBookReactive)
|
if (!this.mostRecentBookReactive)
|
||||||
@@ -785,7 +1001,7 @@ class Reader extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get activePage() {
|
get activePage() {
|
||||||
let result = '';
|
let result = undefined;
|
||||||
|
|
||||||
if (this.progressActive)
|
if (this.progressActive)
|
||||||
result = 'ProgressPage';
|
result = 'ProgressPage';
|
||||||
@@ -800,7 +1016,7 @@ class Reader extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (result != 'TextPage') {
|
if (result != 'TextPage') {
|
||||||
this.$root.$emit('set-app-title');
|
this.$root.setAppTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
// на LoaderPage всегда показываем toolBar
|
// на LoaderPage всегда показываем toolBar
|
||||||
@@ -815,7 +1031,7 @@ class Reader extends Vue {
|
|||||||
const isParsed = await bookManager.hasBookParsed(last);
|
const isParsed = await bookManager.hasBookParsed(last);
|
||||||
|
|
||||||
if (!isParsed) {
|
if (!isParsed) {
|
||||||
this.$root.$emit('set-app-title');
|
this.$root.setAppTitle();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -926,10 +1142,13 @@ class Reader extends Vue {
|
|||||||
if (!book) {
|
if (!book) {
|
||||||
book = await readerApi.loadBook({
|
book = await readerApi.loadBook({
|
||||||
url,
|
url,
|
||||||
skipCheck: (opts.skipCheck ? true : false),
|
uploadFileName,
|
||||||
isText: (opts.isText ? true : false),
|
|
||||||
enableSitesFilter: this.enableSitesFilter,
|
enableSitesFilter: this.enableSitesFilter,
|
||||||
uploadFileName
|
skipHtmlCheck: (this.splitToPara ? true : false),
|
||||||
|
isText: (this.splitToPara ? true : false),
|
||||||
|
djvuQuality: this.djvuQuality,
|
||||||
|
pdfAsText: this.pdfAsText,
|
||||||
|
pdfQuality: this.pdfQuality,
|
||||||
},
|
},
|
||||||
(state) => {
|
(state) => {
|
||||||
progress.setState(state);
|
progress.setState(state);
|
||||||
@@ -962,7 +1181,11 @@ class Reader extends Vue {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
progress.hide(); this.progressActive = false;
|
progress.hide(); this.progressActive = false;
|
||||||
this.loaderActive = true;
|
this.loaderActive = true;
|
||||||
this.$root.stdDialog.alert(e.message, 'Ошибка', {color: 'negative'});
|
if (!this.showHelpOnErrorIfNeeded(e.message)) {
|
||||||
|
this.$root.stdDialog.alert(e.message, 'Ошибка', {color: 'negative'});
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.checkNewVersionAvailable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1026,6 +1249,12 @@ class Reader extends Vue {
|
|||||||
case 'loader':
|
case 'loader':
|
||||||
this.loaderToggle();
|
this.loaderToggle();
|
||||||
break;
|
break;
|
||||||
|
case 'loadFile':
|
||||||
|
this.loadFileToggle();
|
||||||
|
break;
|
||||||
|
case 'loadBuffer':
|
||||||
|
this.loadBufferToggle();
|
||||||
|
break;
|
||||||
case 'help':
|
case 'help':
|
||||||
this.helpToggle();
|
this.helpToggle();
|
||||||
break;
|
break;
|
||||||
@@ -1053,8 +1282,8 @@ class Reader extends Vue {
|
|||||||
case 'copyText':
|
case 'copyText':
|
||||||
this.copyTextToggle();
|
this.copyTextToggle();
|
||||||
break;
|
break;
|
||||||
case 'splitToPara':
|
case 'convOptions':
|
||||||
this.refreshBook('split');
|
this.convOptionsToggle();
|
||||||
break;
|
break;
|
||||||
case 'refresh':
|
case 'refresh':
|
||||||
this.refreshBook();
|
this.refreshBook();
|
||||||
@@ -1068,6 +1297,9 @@ class Reader extends Vue {
|
|||||||
case 'recentBooks':
|
case 'recentBooks':
|
||||||
this.recentBooksToggle();
|
this.recentBooksToggle();
|
||||||
break;
|
break;
|
||||||
|
case 'clickControl':
|
||||||
|
this.clickControlToggle();
|
||||||
|
break;
|
||||||
case 'offlineMode':
|
case 'offlineMode':
|
||||||
this.offlineModeToggle();
|
this.offlineModeToggle();
|
||||||
break;
|
break;
|
||||||
@@ -1136,7 +1368,7 @@ class Reader extends Vue {
|
|||||||
|
|
||||||
keyHook(event) {
|
keyHook(event) {
|
||||||
let result = false;
|
let result = false;
|
||||||
if (this.$root.rootRoute() == '/reader') {
|
if (this.$root.getRootRoute() == '/reader') {
|
||||||
if (this.$root.stdDialog.active)
|
if (this.$root.stdDialog.active)
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
@@ -1170,29 +1402,53 @@ class Reader extends Vue {
|
|||||||
if (!result && event.type == 'keydown') {
|
if (!result && event.type == 'keydown') {
|
||||||
const action = this.$root.readerActionByKeyEvent(event);
|
const action = this.$root.readerActionByKeyEvent(event);
|
||||||
|
|
||||||
if (action == 'loader') {
|
/*if (action == 'loader') {
|
||||||
result = this.doAction({action, event});
|
result = this.doAction({action, event});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result && this.activePage == 'TextPage') {
|
if (!result && this.activePage == 'TextPage') {
|
||||||
result = this.doAction({action, event});
|
result = this.doAction({action, event});
|
||||||
}
|
}*/
|
||||||
|
result = this.doAction({action, event});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(Reader);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.header {
|
.header {
|
||||||
|
height: 50px;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
background-color: #1B695F;
|
background-color: #1B695F;
|
||||||
color: #000;
|
color: #000;
|
||||||
overflow: hidden;
|
overflow-x: auto;
|
||||||
height: 50px;
|
overflow-y: hidden;
|
||||||
|
scrollbar-color: #c49a60 #e4e4e4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header::-webkit-scrollbar {
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header::-webkit-scrollbar-track {
|
||||||
|
background-color: #e4e4e4;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #c49a60;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 2px solid #e4e4e4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: #b48a50;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
|
|||||||
@@ -1,20 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<Dialog ref="dialog1" v-model="whatsNewVisible">
|
<Dialog ref="dialog1" v-model="whatsNewVisible">
|
||||||
<template slot="header">
|
<template #header>
|
||||||
Что нового:
|
Что нового:
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div style="line-height: 20px" v-html="whatsNewContent"></div>
|
<div style="line-height: 20px; min-width: 300px">
|
||||||
|
<div v-html="whatsNewContent"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<span class="clickable" @click="openVersionHistory">Посмотреть историю версий</span>
|
<span class="clickable" style="font-size: 13px" @click="openVersionHistory">Посмотреть историю версий</span>
|
||||||
<span slot="footer">
|
|
||||||
<q-btn class="q-px-md" dense no-caps @click="whatsNewDisable">Больше не показывать</q-btn>
|
<template #footer>
|
||||||
</span>
|
<q-btn class="q-px-md" dense no-caps @click="whatsNewDisable">
|
||||||
|
Больше не показывать
|
||||||
|
</q-btn>
|
||||||
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<Dialog ref="dialog2" v-model="donationVisible">
|
<Dialog ref="dialog2" v-model="donationVisible">
|
||||||
<template slot="header">
|
<template #header>
|
||||||
Здравствуйте, уважаемые читатели!
|
Здравствуйте, уважаемые читатели!
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -35,7 +40,9 @@
|
|||||||
Автор также обращается с просьбой о помощи в распространении
|
Автор также обращается с просьбой о помощи в распространении
|
||||||
<a href="https://omnireader.ru" target="_blank">ссылки</a>
|
<a href="https://omnireader.ru" target="_blank">ссылки</a>
|
||||||
<q-icon class="copy-icon" name="la la-copy" @click="copyLink('https://omnireader.ru')">
|
<q-icon class="copy-icon" name="la la-copy" @click="copyLink('https://omnireader.ru')">
|
||||||
<q-tooltip :delay="1000" anchor="top middle" self="center middle" content-style="font-size: 80%">Скопировать</q-tooltip>
|
<q-tooltip :delay="1000" anchor="top middle" self="center middle" content-style="font-size: 80%">
|
||||||
|
Скопировать
|
||||||
|
</q-tooltip>
|
||||||
</q-icon>
|
</q-icon>
|
||||||
на читалку через тематические форумы, соцсети, мессенджеры и пр.
|
на читалку через тематические форумы, соцсети, мессенджеры и пр.
|
||||||
Чем нас больше, тем легче оставаться на плаву и тем больше мотивации у разработчика, чтобы продолжать работать над проектом.
|
Чем нас больше, тем легче оставаться на плаву и тем больше мотивации у разработчика, чтобы продолжать работать над проектом.
|
||||||
@@ -47,60 +54,53 @@
|
|||||||
|
|
||||||
<br><br>
|
<br><br>
|
||||||
<div class="row justify-center">
|
<div class="row justify-center">
|
||||||
<q-btn class="q-px-sm" color="primary" dense no-caps rounded @click="openDonate">Помочь проекту</q-btn>
|
<!--q-btn class="q-px-sm" color="primary" dense no-caps rounded @click="openDonate">
|
||||||
|
Помочь проекту
|
||||||
|
</q-btn-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span slot="footer">
|
<template #footer>
|
||||||
<span class="clickable row justify-end" style="font-size: 60%; color: grey" @click="donationDialogDisable">Больше не показывать</span>
|
<span class="clickable row justify-end" style="font-size: 60%; color: grey" @click="donationDialogDisable">Больше не показывать</span>
|
||||||
<br>
|
<br>
|
||||||
<q-btn class="q-px-sm" dense no-caps @click="donationDialogRemind">Напомнить позже</q-btn>
|
<q-btn class="q-px-sm" dense no-caps @click="donationDialogRemind">
|
||||||
</span>
|
Напомнить позже
|
||||||
|
</q-btn>
|
||||||
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<Dialog ref="dialog3" v-model="liberamaTopVisible">
|
<Dialog ref="dialog3" v-model="urlHelpVisible">
|
||||||
<template slot="header">
|
<template #header>
|
||||||
Здравствуйте, уважаемые читатели!
|
Обнаружена невалидная ссылка в поле "URL книги".
|
||||||
|
<br>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div style="word-break: normal">
|
<div style="word-break: normal">
|
||||||
Создан новый ресурс:<br><br>
|
Если вы хотите найти определенную книгу и открыть в читалке, добро пожаловать в
|
||||||
|
раздел "Сетевая библиотека" (кнопка <q-icon name="la la-sitemap" size="32px" />) на сайте
|
||||||
|
<a href="https://liberama.top" target="_blank">liberama.top</a>
|
||||||
|
|
||||||
<a href="https://liberama.top" target="_blank">https://liberama.top</a>
|
|
||||||
<br><br>
|
<br><br>
|
||||||
Это клон читалки Omni Reader, но с некоторыми дополнениями, ориентированными в сторону более свободного обмена книгами:
|
Если же вы пытаетесь вставить текст в читалку из буфера обмена, пожалуйста воспользуйтесь кнопкой
|
||||||
|
<q-btn no-caps dense class="q-px-sm" color="primary" size="13px" @click="loadBufferClick">
|
||||||
<ul>
|
<q-icon class="q-mr-xs" name="la la-comment" size="24px" />
|
||||||
<li>добавлено новое окно "Библиотека" для свободного доступа к Флибусте и другим ресурсам по желанию читателя</li>
|
Из буфера обмена
|
||||||
<li>планируется добавить возможность создания подборок книг и обмена ими между пользователями</li>
|
</q-btn>
|
||||||
</ul>
|
на странице загрузки.
|
||||||
|
|
||||||
Легко мигрировать на новый сайт можно с помощью синхронизации с сервером.
|
|
||||||
О багах и предложениях просьба сообщать на почту <a href="mailto:bookpauk@gmail.com">bookpauk@gmail.com</a><br><br>
|
|
||||||
Спасибо, что вы с нами!
|
|
||||||
<br><br>
|
|
||||||
<div class="row justify-center">
|
|
||||||
<q-btn class="q-px-sm" color="primary" dense no-caps rounded @click="openDonate">Помочь проекту</q-btn>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span slot="footer">
|
|
||||||
<q-btn class="q-px-sm" dense no-caps @click="liberamaTopDialogDisable">Больше не показывать</q-btn>
|
|
||||||
</span>
|
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
|
|
||||||
import Dialog from '../../share/Dialog.vue';
|
import Dialog from '../../share/Dialog.vue';
|
||||||
import * as utils from '../../../share/utils';
|
import * as utils from '../../../share/utils';
|
||||||
import {versionHistory} from '../versionHistory';
|
import {versionHistory} from '../versionHistory';
|
||||||
|
|
||||||
export default @Component({
|
const componentOptions = {
|
||||||
components: {
|
components: {
|
||||||
Dialog
|
Dialog
|
||||||
},
|
},
|
||||||
@@ -109,12 +109,14 @@ export default @Component({
|
|||||||
this.loadSettings();
|
this.loadSettings();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
class ReaderDialogs extends Vue {
|
class ReaderDialogs {
|
||||||
|
_options = componentOptions;
|
||||||
|
|
||||||
whatsNewVisible = false;
|
whatsNewVisible = false;
|
||||||
whatsNewContent = '';
|
whatsNewContent = '';
|
||||||
donationVisible = false;
|
donationVisible = false;
|
||||||
liberamaTopVisible = false;
|
urlHelpVisible = false;
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.commit = this.$store.commit;
|
this.commit = this.$store.commit;
|
||||||
@@ -127,23 +129,21 @@ class ReaderDialogs extends Vue {
|
|||||||
async init() {
|
async init() {
|
||||||
await this.showWhatsNew();
|
await this.showWhatsNew();
|
||||||
await this.showDonation();
|
await this.showDonation();
|
||||||
await this.showLiberamaTop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadSettings() {
|
loadSettings() {
|
||||||
const settings = this.settings;
|
const settings = this.settings;
|
||||||
this.showWhatsNewDialog = settings.showWhatsNewDialog;
|
this.showWhatsNewDialog = settings.showWhatsNewDialog;
|
||||||
this.showDonationDialog2020 = settings.showDonationDialog2020;
|
this.showDonationDialog2020 = settings.showDonationDialog2020;
|
||||||
this.showLiberamaTopDialog2020 = settings.showLiberamaTopDialog2020;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async showWhatsNew() {
|
async showWhatsNew() {
|
||||||
const whatsNew = versionHistory[0];
|
const whatsNew = versionHistory[0];
|
||||||
if (this.showWhatsNewDialog &&
|
if (this.showWhatsNewDialog &&
|
||||||
whatsNew.showUntil >= utils.formatDate(new Date(), 'coDate') &&
|
whatsNew.showUntil >= utils.formatDate(new Date(), 'coDate') &&
|
||||||
whatsNew.header != this.whatsNewContentHash) {
|
this.whatsNewHeader != this.whatsNewContentHash) {
|
||||||
await utils.sleep(2000);
|
await utils.sleep(2000);
|
||||||
this.whatsNewContent = 'Версия ' + whatsNew.header + whatsNew.content;
|
this.whatsNewContent = 'Версия ' + this.whatsNewHeader + whatsNew.content;
|
||||||
this.whatsNewVisible = true;
|
this.whatsNewVisible = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,6 +157,15 @@ class ReaderDialogs extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async showUrlHelp() {
|
||||||
|
this.urlHelpVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadBufferClick() {
|
||||||
|
this.$emit('load-buffer-toggle');
|
||||||
|
this.urlHelpVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
donationDialogDisable() {
|
donationDialogDisable() {
|
||||||
this.donationVisible = false;
|
this.donationVisible = false;
|
||||||
if (this.showDonationDialog2020) {
|
if (this.showDonationDialog2020) {
|
||||||
@@ -171,7 +180,6 @@ class ReaderDialogs extends Vue {
|
|||||||
|
|
||||||
openDonate() {
|
openDonate() {
|
||||||
this.donationVisible = false;
|
this.donationVisible = false;
|
||||||
this.liberamaTopVisible = false;
|
|
||||||
this.$emit('donate-toggle');
|
this.$emit('donate-toggle');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,8 +198,11 @@ class ReaderDialogs extends Vue {
|
|||||||
|
|
||||||
whatsNewDisable() {
|
whatsNewDisable() {
|
||||||
this.whatsNewVisible = false;
|
this.whatsNewVisible = false;
|
||||||
const whatsNew = versionHistory[0];
|
this.commit('reader/setWhatsNewContentHash', this.whatsNewHeader);
|
||||||
this.commit('reader/setWhatsNewContentHash', whatsNew.header);
|
}
|
||||||
|
|
||||||
|
get whatsNewHeader() {
|
||||||
|
return `${versionHistory[0].version} (${versionHistory[0].releaseDate})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
get mode() {
|
get mode() {
|
||||||
@@ -210,28 +221,14 @@ class ReaderDialogs extends Vue {
|
|||||||
return this.$store.state.reader.donationRemindDate;
|
return this.$store.state.reader.donationRemindDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
async showLiberamaTop() {
|
|
||||||
const today = utils.formatDate(new Date(), 'coDate');
|
|
||||||
|
|
||||||
if (this.mode == 'omnireader' && today < '2020-12-01' && this.showLiberamaTopDialog2020) {
|
|
||||||
await utils.sleep(3000);
|
|
||||||
this.liberamaTopVisible = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
liberamaTopDialogDisable() {
|
|
||||||
this.liberamaTopVisible = false;
|
|
||||||
if (this.showLiberamaTopDialog2020) {
|
|
||||||
this.commit('reader/setSettings', { showLiberamaTopDialog2020: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
keyHook() {
|
keyHook() {
|
||||||
if (this.$refs.dialog1.active || this.$refs.dialog2.active || this.$refs.dialog3.active)
|
if (this.$refs.dialog1.active || this.$refs.dialog2.active || this.$refs.dialog3.active)
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(ReaderDialogs);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,40 +1,49 @@
|
|||||||
<template>
|
<template>
|
||||||
<Window width="600px" ref="window" @close="close">
|
<Window ref="window" width="600px" @close="close">
|
||||||
<template slot="header">
|
<template #header>
|
||||||
<span v-show="!loading">{{ header }}</span>
|
<span v-show="!loading">{{ header }}</span>
|
||||||
<span v-if="loading"><q-spinner class="q-mr-sm" color="lime-12" size="20px" :thickness="7"/>Список загружается</span>
|
<span v-if="loading"><q-spinner class="q-mr-sm" color="lime-12" size="20px" :thickness="7" />
|
||||||
|
Список загружается
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<a ref="download" style='display: none;' target="_blank"></a>
|
<a ref="download" style="display: none;" target="_blank"></a>
|
||||||
|
|
||||||
<q-table
|
<q-table
|
||||||
class="recent-books-table col"
|
class="recent-books-table col"
|
||||||
:data="tableData"
|
:rows="tableData"
|
||||||
:columns="columns"
|
|
||||||
row-key="key"
|
row-key="key"
|
||||||
:pagination.sync="pagination"
|
:columns="columns"
|
||||||
|
:pagination="pagination"
|
||||||
separator="cell"
|
separator="cell"
|
||||||
hide-bottom
|
hide-bottom
|
||||||
virtual-scroll
|
virtual-scroll
|
||||||
dense
|
dense
|
||||||
>
|
>
|
||||||
<template v-slot:header="props">
|
<template #header="props">
|
||||||
<q-tr :props="props">
|
<q-tr :props="props">
|
||||||
<q-th class="td-mp" style="width: 25px" key="num" :props="props"><span v-html="props.cols[0].label"></span></q-th>
|
<q-th key="num" class="td-mp" style="width: 25px" :props="props">
|
||||||
<q-th class="td-mp break-word" style="width: 77px" key="date" :props="props"><span v-html="props.cols[1].label"></span></q-th>
|
<span v-html="props.cols[0].label"></span>
|
||||||
<q-th class="td-mp" style="width: 332px" key="desc" :props="props" colspan="4">
|
</q-th>
|
||||||
<q-input ref="input" outlined dense rounded style="position: absolute; top: 6px; left: 90px; width: 380px" bg-color="white"
|
<q-th key="date" class="td-mp break-word" style="width: 77px" :props="props">
|
||||||
placeholder="Найти"
|
<span v-html="props.cols[1].label"></span>
|
||||||
v-model="search"
|
</q-th>
|
||||||
|
<q-th key="desc" class="td-mp" style="width: 332px" :props="props" colspan="4">
|
||||||
|
<q-input ref="input" v-model="search"
|
||||||
|
outlined dense rounded style="position: absolute; top: 6px; left: 90px; width: 380px" bg-color="white"
|
||||||
|
placeholder="Найти"
|
||||||
@click.stop
|
@click.stop
|
||||||
/>
|
>
|
||||||
|
<template #append>
|
||||||
|
<q-icon v-if="search !== ''" name="la la-times" class="cursor-pointer" @click.stop="resetSearch" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
<span v-html="props.cols[2].label"></span>
|
<span v-html="props.cols[2].label"></span>
|
||||||
</q-th>
|
</q-th>
|
||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:body="props">
|
<template #body="props">
|
||||||
<q-tr :props="props">
|
<q-tr :props="props">
|
||||||
<q-td key="num" :props="props" class="td-mp" auto-width>
|
<q-td key="num" :props="props" class="td-mp" auto-width>
|
||||||
<div class="break-word" style="width: 25px">
|
<div class="break-word" style="width: 25px">
|
||||||
@@ -42,17 +51,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</q-td>
|
</q-td>
|
||||||
|
|
||||||
<q-td key="date" :props="props" class="td-mp clickable" @click="loadBook(props.row.url)" auto-width>
|
<q-td key="date" auto-width :props="props" class="td-mp clickable" @click="loadBook(props.row.url)">
|
||||||
<div class="break-word" style="width: 68px">
|
<div class="break-word" style="width: 68px">
|
||||||
{{ props.row.touchDate }}<br>
|
{{ props.row.touchDate }}<br>
|
||||||
{{ props.row.touchTime }}
|
{{ props.row.touchTime }}
|
||||||
</div>
|
</div>
|
||||||
</q-td>
|
</q-td>
|
||||||
|
|
||||||
<q-td key="desc" :props="props" class="td-mp clickable" @click="loadBook(props.row.url)" auto-width>
|
<q-td key="desc" auto-width :props="props" class="td-mp clickable" @click="loadBook(props.row.url)">
|
||||||
<div class="break-word" style="width: 332px; font-size: 90%">
|
<div class="break-word" style="width: 332px; font-size: 90%">
|
||||||
<div style="color: green">{{ props.row.desc.author }}</div>
|
<div style="color: green">
|
||||||
|
{{ props.row.desc.author }}
|
||||||
|
</div>
|
||||||
<div>{{ props.row.desc.title }}</div>
|
<div>{{ props.row.desc.title }}</div>
|
||||||
|
<div class="read-bar" :style="`width: ${332*props.row.readPart}px`"></div>
|
||||||
</div>
|
</div>
|
||||||
</q-td>
|
</q-td>
|
||||||
|
|
||||||
@@ -68,8 +80,9 @@
|
|||||||
<q-btn
|
<q-btn
|
||||||
dense
|
dense
|
||||||
style="width: 30px; height: 30px; padding: 7px 0 7px 0; margin-left: 4px"
|
style="width: 30px; height: 30px; padding: 7px 0 7px 0; margin-left: 4px"
|
||||||
@click="handleDel(props.row.key)">
|
@click="handleDel(props.row.key)"
|
||||||
<q-icon class="la la-times" size="14px" style="top: -6px"/>
|
>
|
||||||
|
<q-icon class="la la-times" size="14px" />
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
</q-td>
|
</q-td>
|
||||||
@@ -78,15 +91,14 @@
|
|||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
</q-table>
|
</q-table>
|
||||||
|
|
||||||
</Window>
|
</Window>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
import path from 'path';
|
import path from 'path-browserify';
|
||||||
//import _ from 'lodash';
|
//import _ from 'lodash';
|
||||||
|
|
||||||
import * as utils from '../../../share/utils';
|
import * as utils from '../../../share/utils';
|
||||||
@@ -94,7 +106,7 @@ import Window from '../../share/Window.vue';
|
|||||||
import bookManager from '../share/bookManager';
|
import bookManager from '../share/bookManager';
|
||||||
import readerApi from '../../../api/reader';
|
import readerApi from '../../../api/reader';
|
||||||
|
|
||||||
export default @Component({
|
const componentOptions = {
|
||||||
components: {
|
components: {
|
||||||
Window,
|
Window,
|
||||||
},
|
},
|
||||||
@@ -103,10 +115,12 @@ export default @Component({
|
|||||||
this.updateTableData();
|
this.updateTableData();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
class RecentBooksPage extends Vue {
|
class RecentBooksPage {
|
||||||
|
_options = componentOptions;
|
||||||
|
|
||||||
loading = false;
|
loading = false;
|
||||||
search = null;
|
search = '';
|
||||||
tableData = [];
|
tableData = [];
|
||||||
columns = [];
|
columns = [];
|
||||||
pagination = {};
|
pagination = {};
|
||||||
@@ -167,13 +181,13 @@ class RecentBooksPage extends Vue {
|
|||||||
return;
|
return;
|
||||||
this.initing = true;
|
this.initing = true;
|
||||||
|
|
||||||
|
|
||||||
if (this.firstInit) {//для отзывчивости
|
if (this.firstInit) {//для отзывчивости
|
||||||
await this.updateTableData(20);
|
await this.updateTableData(20);
|
||||||
this.firstInit = false;
|
this.firstInit = false;
|
||||||
}
|
}
|
||||||
await utils.sleep(50);
|
await utils.sleep(50);
|
||||||
await this.updateTableData();
|
await this.updateTableData();
|
||||||
|
|
||||||
this.initing = false;
|
this.initing = false;
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
@@ -200,11 +214,13 @@ class RecentBooksPage extends Vue {
|
|||||||
d.setTime(book.touchTime);
|
d.setTime(book.touchTime);
|
||||||
const t = utils.formatDate(d).split(' ');
|
const t = utils.formatDate(d).split(' ');
|
||||||
|
|
||||||
|
let readPart = 0;
|
||||||
let perc = '';
|
let perc = '';
|
||||||
let textLen = '';
|
let textLen = '';
|
||||||
const p = (book.bookPosSeen ? book.bookPosSeen : (book.bookPos ? book.bookPos : 0));
|
const p = (book.bookPosSeen ? book.bookPosSeen : (book.bookPos ? book.bookPos : 0));
|
||||||
if (book.textLength) {
|
if (book.textLength) {
|
||||||
perc = ` [${((p/book.textLength)*100).toFixed(2)}%]`;
|
readPart = p/book.textLength;
|
||||||
|
perc = ` [${(readPart*100).toFixed(2)}%]`;
|
||||||
textLen = ` ${Math.round(book.textLength/1000)}k`;
|
textLen = ` ${Math.round(book.textLength/1000)}k`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,6 +239,7 @@ class RecentBooksPage extends Vue {
|
|||||||
author,
|
author,
|
||||||
title: `${title}${perc}${textLen}`,
|
title: `${title}${perc}${textLen}`,
|
||||||
},
|
},
|
||||||
|
readPart,
|
||||||
descString: `${author}${title}${perc}${textLen}`,//для сортировки
|
descString: `${author}${title}${perc}${textLen}`,//для сортировки
|
||||||
url: book.url,
|
url: book.url,
|
||||||
path: book.path,
|
path: book.path,
|
||||||
@@ -244,6 +261,11 @@ class RecentBooksPage extends Vue {
|
|||||||
this.updating = false;
|
this.updating = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetSearch() {
|
||||||
|
this.search = '';
|
||||||
|
this.$refs.input.focus();
|
||||||
|
}
|
||||||
|
|
||||||
wordEnding(num) {
|
wordEnding(num) {
|
||||||
const endings = ['', 'а', 'и', 'и', 'и', '', '', '', '', ''];
|
const endings = ['', 'а', 'и', 'и', 'и', '', '', '', '', ''];
|
||||||
const deci = num % 100;
|
const deci = num % 100;
|
||||||
@@ -312,6 +334,8 @@ class RecentBooksPage extends Vue {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(RecentBooksPage);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -346,6 +370,10 @@ class RecentBooksPage extends Vue {
|
|||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.read-bar {
|
||||||
|
height: 3px;
|
||||||
|
background-color: #aaaaaa;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<Window ref="window" height="125px" max-width="600px" :top-shift="-50" @close="close">
|
<Window ref="window" height="125px" max-width="600px" :top-shift="-50" @close="close">
|
||||||
<template slot="header">
|
<template #header>
|
||||||
{{ header }}
|
{{ header }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -11,15 +11,22 @@
|
|||||||
<!--input ref="input"
|
<!--input ref="input"
|
||||||
placeholder="что ищем"
|
placeholder="что ищем"
|
||||||
:value="needle" @input="needle = $event.target.value"/-->
|
:value="needle" @input="needle = $event.target.value"/-->
|
||||||
<q-input ref="input" class="col" outlined dense
|
<q-input ref="input" v-model="needle"
|
||||||
|
class="col" outlined dense
|
||||||
placeholder="что ищем"
|
placeholder="что ищем"
|
||||||
v-model="needle" @keydown="inputKeyDown"
|
@keydown="inputKeyDown"
|
||||||
/>
|
/>
|
||||||
<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>
|
||||||
<q-btn-group v-show="!initStep" class="button-group row no-wrap">
|
<q-btn-group v-show="!initStep" class="button-group row no-wrap">
|
||||||
<q-btn class="button" dense stretch @click="showNext"><q-icon style="top: -6px" name="la la-angle-down" dense size="22px"/></q-btn>
|
<q-btn class="button" dense stretch @click="showNext">
|
||||||
<q-btn class="button" dense stretch @click="showPrev"><q-icon style="top: -4px" class="icon" name="la la-angle-up" dense size="22px"/></q-btn>
|
<q-icon style="top: -6px" name="la la-angle-down" dense size="22px" />
|
||||||
|
</q-btn>
|
||||||
|
<q-btn class="button" dense stretch @click="showPrev">
|
||||||
|
<q-icon style="top: -4px" class="icon" name="la la-angle-up" dense size="22px" />
|
||||||
|
</q-btn>
|
||||||
</q-btn-group>
|
</q-btn-group>
|
||||||
</div>
|
</div>
|
||||||
</Window>
|
</Window>
|
||||||
@@ -27,13 +34,12 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
|
|
||||||
import Window from '../../share/Window.vue';
|
import Window from '../../share/Window.vue';
|
||||||
import {sleep} from '../../../share/utils';
|
import {sleep} from '../../../share/utils';
|
||||||
|
|
||||||
export default @Component({
|
const componentOptions = {
|
||||||
components: {
|
components: {
|
||||||
Window,
|
Window,
|
||||||
},
|
},
|
||||||
@@ -49,8 +55,10 @@ export default @Component({
|
|||||||
el.style.paddingRight = newValue.length*12 + 'px';
|
el.style.paddingRight = newValue.length*12 + 'px';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
class SearchPage extends Vue {
|
class SearchPage {
|
||||||
|
_options = componentOptions;
|
||||||
|
|
||||||
header = null;
|
header = null;
|
||||||
initStep = null;
|
initStep = null;
|
||||||
initPercentage = 0;
|
initPercentage = 0;
|
||||||
@@ -180,6 +188,8 @@ class SearchPage extends Vue {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(SearchPage);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import bookManager from '../share/bookManager';
|
import bookManager from '../share/bookManager';
|
||||||
@@ -18,7 +18,7 @@ const ssCacheStore = localForage.createInstance({
|
|||||||
name: 'ssCacheStore'
|
name: 'ssCacheStore'
|
||||||
});
|
});
|
||||||
|
|
||||||
export default @Component({
|
const componentOptions = {
|
||||||
watch: {
|
watch: {
|
||||||
serverSyncEnabled: function() {
|
serverSyncEnabled: function() {
|
||||||
this.serverSyncEnabledChanged();
|
this.serverSyncEnabledChanged();
|
||||||
@@ -39,14 +39,16 @@ export default @Component({
|
|||||||
this.debouncedSaveLibs();
|
this.debouncedSaveLibs();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
class ServerStorage extends Vue {
|
class ServerStorage {
|
||||||
|
_options = componentOptions;
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.inited = false;
|
this.inited = false;
|
||||||
this.keyInited = false;
|
this.keyInited = false;
|
||||||
this.commit = this.$store.commit;
|
this.commit = this.$store.commit;
|
||||||
this.prevServerStorageKey = null;
|
this.prevServerStorageKey = null;
|
||||||
this.$root.$on('generateNewServerStorageKey', () => {this.generateNewServerStorageKey()});
|
this.$root.generateNewServerStorageKey = () => {this.generateNewServerStorageKey()};
|
||||||
|
|
||||||
this.debouncedSaveSettings = _.debounce(() => {
|
this.debouncedSaveSettings = _.debounce(() => {
|
||||||
this.saveSettings();
|
this.saveSettings();
|
||||||
@@ -574,7 +576,7 @@ class ServerStorage extends Vue {
|
|||||||
newRecentPatch.rev++;
|
newRecentPatch.rev++;
|
||||||
newRecentPatch.data[itemKey] = _.cloneDeep(bm.recent[itemKey]);
|
newRecentPatch.data[itemKey] = _.cloneDeep(bm.recent[itemKey]);
|
||||||
|
|
||||||
let applyMod = this.cachedRecentMod.data;
|
const applyMod = this.cachedRecentMod.data;
|
||||||
if (applyMod && applyMod.key && newRecentPatch.data[applyMod.key])
|
if (applyMod && applyMod.key && newRecentPatch.data[applyMod.key])
|
||||||
newRecentPatch.data[applyMod.key] = utils.applyObjDiff(newRecentPatch.data[applyMod.key], applyMod.mod, {isAddChanged: true});
|
newRecentPatch.data[applyMod.key] = utils.applyObjDiff(newRecentPatch.data[applyMod.key], applyMod.mod, {isAddChanged: true});
|
||||||
|
|
||||||
@@ -625,7 +627,7 @@ class ServerStorage extends Vue {
|
|||||||
this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
|
this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
|
||||||
if (!recurse && itemKey) {
|
if (!recurse && itemKey) {
|
||||||
this.savingRecent = false;
|
this.savingRecent = false;
|
||||||
this.saveRecent(itemKey, true);
|
await this.saveRecent(itemKey, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (result.state == 'success') {
|
} else if (result.state == 'success') {
|
||||||
@@ -726,13 +728,15 @@ class ServerStorage extends Vue {
|
|||||||
const ids = id.split('.');
|
const ids = id.split('.');
|
||||||
if (!(ids.length == 2) || !(ids[0] == this.hashedStorageKey))
|
if (!(ids.length == 2) || !(ids[0] == this.hashedStorageKey))
|
||||||
throw new Error(`decodeStorageItems: bad id - ${id}`);
|
throw new Error(`decodeStorageItems: bad id - ${id}`);
|
||||||
items[utils.fromBase58(ids[1])] = decoded;
|
items[utils.fromBase58(ids[1]).toString()] = decoded;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result.items = items;
|
result.items = items;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(ServerStorage);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<Window ref="window" height="140px" max-width="600px" :top-shift="-50" @close="close">
|
<Window ref="window" height="140px" max-width="600px" :top-shift="-50" @close="close">
|
||||||
<template slot="header">
|
<template #header>
|
||||||
Установить позицию
|
Установить позицию
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div id="set-position-slider" class="slider q-px-md">
|
<div id="set-position-slider" class="slider q-px-md">
|
||||||
<q-slider
|
<q-slider
|
||||||
thumb-path="M 2, 10 a 8.5,8.5 0 1,0 17,0 a 8.5,8.5 0 1,0 -17,0"
|
|
||||||
v-model="sliderValue"
|
v-model="sliderValue"
|
||||||
|
thumb-path="M 2, 10 a 8.5,8.5 0 1,0 17,0 a 8.5,8.5 0 1,0 -17,0"
|
||||||
|
|
||||||
:max="sliderMax"
|
:max="sliderMax"
|
||||||
label
|
label
|
||||||
:label-value="(sliderMax ? (sliderValue/this.sliderMax*100).toFixed(2) + '%' : 0)"
|
:label-value="(sliderMax ? (sliderValue/sliderMax*100).toFixed(2) + '%' : 0)"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -19,12 +20,11 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
|
|
||||||
import Window from '../../share/Window.vue';
|
import Window from '../../share/Window.vue';
|
||||||
|
|
||||||
export default @Component({
|
const componentOptions = {
|
||||||
components: {
|
components: {
|
||||||
Window,
|
Window,
|
||||||
},
|
},
|
||||||
@@ -34,8 +34,10 @@ export default @Component({
|
|||||||
this.$emit('book-pos-changed', {bookPos: newValue});
|
this.$emit('book-pos-changed', {bookPos: newValue});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
class SetPositionPage extends Vue {
|
class SetPositionPage {
|
||||||
|
_options = componentOptions;
|
||||||
|
|
||||||
sliderValue = null;
|
sliderValue = null;
|
||||||
sliderMax = null;
|
sliderMax = null;
|
||||||
|
|
||||||
@@ -67,6 +69,8 @@ class SetPositionPage extends Vue {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(SetPositionPage);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,7 @@
|
|||||||
<div class="item row" v-for="item in toolButtons" :key="item.name" v-show="item.name != 'libs' || mode == 'liberama.top'">
|
<div class="item row" v-for="item in toolButtons" :key="item.name" v-show="item.name != 'libs' || mode == 'liberama.top'">
|
||||||
<div class="label-3"></div>
|
<div class="label-3"></div>
|
||||||
<div class="col row">
|
<div class="col row">
|
||||||
<q-checkbox size="xs" @input="changeShowToolButton(item.name)"
|
<q-checkbox size="xs" v-model="showToolButton[item.name]" :label="rstore.readerActions[item.name]"
|
||||||
:value="showToolButton[item.name]" :label="rstore.readerActions[item.name]"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
87
client/components/Reader/SettingsPage/ConvertTab.inc
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<!---------------------------------------------->
|
||||||
|
<div class="q-mt-sm column items-center">
|
||||||
|
<span>Настройки конвертирования применяются ко всем</span>
|
||||||
|
<span>вновь загружаемым или обновляемым файлам</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!---------------------------------------------->
|
||||||
|
<div class="part-header">HTML, XML, TXT</div>
|
||||||
|
|
||||||
|
<div class="item row">
|
||||||
|
<div class="label-7">Текст</div>
|
||||||
|
<div class="col row">
|
||||||
|
<q-checkbox v-model="splitToPara" size="xs" label="Попытаться разбить текст на параграфы">
|
||||||
|
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
||||||
|
Опция принудительно включает эвристику разбиения текста на<br>
|
||||||
|
параграфы в случае, если формат файла определен как html,<br>
|
||||||
|
xml или txt. Возможна нечитабельная разметка текста.
|
||||||
|
</q-tooltip>
|
||||||
|
</q-checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item row">
|
||||||
|
<div class="label-7">Сайты</div>
|
||||||
|
<div class="col row">
|
||||||
|
<q-checkbox v-model="enableSitesFilter" size="xs" label="Включить html-фильтр для сайтов">
|
||||||
|
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
||||||
|
Html-фильтр вырезает лишние элементы со<br>
|
||||||
|
страницы для определенных сайтов, таких как:<br>
|
||||||
|
samlib.ru<br>
|
||||||
|
www.fanfiction.net<br>
|
||||||
|
archiveofourown.org<br>
|
||||||
|
и других
|
||||||
|
</q-tooltip>
|
||||||
|
</q-checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!---------------------------------------------->
|
||||||
|
<div v-if="isExternalConverter">
|
||||||
|
<div class="part-header">PDF</div>
|
||||||
|
|
||||||
|
<div class="item row">
|
||||||
|
<div class="label-7">Формат</div>
|
||||||
|
<div class="col row">
|
||||||
|
<q-checkbox v-model="pdfAsText" size="xs" label="Извлекать текст из PDF">
|
||||||
|
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
||||||
|
Пытается извлечь текст из pdf-файла и переразбить на параграфы.<br>
|
||||||
|
Размер получаемого fb2-файла при этом относительно небольшой.<br>
|
||||||
|
При отключении этой опции, pdf будет представлен как набор<br>
|
||||||
|
изображений (аналогично ковертированию djvu).
|
||||||
|
</q-tooltip>
|
||||||
|
</q-checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item row">
|
||||||
|
<div class="label-7">Качество</div>
|
||||||
|
<div class="col row">
|
||||||
|
<NumInput class="col-5" v-model="pdfQuality" :min="10" :max="100" :disable="pdfAsText" >
|
||||||
|
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
||||||
|
Качество конвертирования Pdf в Fb2. Чем значение выше, тем больше<br>
|
||||||
|
размер итогового файла. Если сервер отказывается конвертировать<br>
|
||||||
|
слишком большой файл, то попробуйте понизить качество.
|
||||||
|
</q-tooltip>
|
||||||
|
</NumInput>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!---------------------------------------------->
|
||||||
|
<div v-if="isExternalConverter">
|
||||||
|
<div class="part-header">DJVU</div>
|
||||||
|
|
||||||
|
<div class="item row">
|
||||||
|
<div class="label-7">Качество</div>
|
||||||
|
<div class="col row">
|
||||||
|
<NumInput class="col-5" v-model="djvuQuality" :min="10" :max="100">
|
||||||
|
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
||||||
|
Качество конвертирования Djvu в Fb2. Чем значение выше, тем больше<br>
|
||||||
|
размер итогового файла. Если сервер отказывается конвертировать<br>
|
||||||
|
слишком большой файл, то попробуйте понизить качество.
|
||||||
|
</q-tooltip>
|
||||||
|
</NumInput>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -36,12 +36,23 @@
|
|||||||
Показывать уведомление "Что нового"
|
Показывать уведомление "Что нового"
|
||||||
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
||||||
Показывать уведомления "Что нового"<br>
|
Показывать уведомления "Что нового"<br>
|
||||||
при каждом выходе новой версии читалки
|
при появлении новой версии читалки
|
||||||
</q-tooltip>
|
</q-tooltip>
|
||||||
</q-checkbox>
|
</q-checkbox>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="item row">
|
<div class="item row">
|
||||||
|
<div class="label-6">Уведомление</div>
|
||||||
|
<q-checkbox size="xs" v-model="showNeedUpdateNotify">
|
||||||
|
Показывать уведомление о новой версии
|
||||||
|
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
||||||
|
Напоминать о необходимости обновления страницы<br>
|
||||||
|
при появлении новой версии читалки
|
||||||
|
</q-tooltip>
|
||||||
|
</q-checkbox>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--div class="item row">
|
||||||
<div class="label-6">Уведомление</div>
|
<div class="label-6">Уведомление</div>
|
||||||
<q-checkbox size="xs" v-model="showDonationDialog2020">
|
<q-checkbox size="xs" v-model="showDonationDialog2020">
|
||||||
Показывать "Оплатим хостинг вместе"
|
Показывать "Оплатим хостинг вместе"
|
||||||
@@ -49,27 +60,11 @@
|
|||||||
Показывать уведомление "Оплатим хостинг вместе"
|
Показывать уведомление "Оплатим хостинг вместе"
|
||||||
</q-tooltip>
|
</q-tooltip>
|
||||||
</q-checkbox>
|
</q-checkbox>
|
||||||
</div>
|
</div-->
|
||||||
|
|
||||||
<!---------------------------------------------->
|
<!---------------------------------------------->
|
||||||
<div class="part-header">Другое</div>
|
<div class="part-header">Другое</div>
|
||||||
|
|
||||||
<div class="item row">
|
|
||||||
<div class="label-6">Обработка</div>
|
|
||||||
<div class="col row">
|
|
||||||
<q-checkbox v-model="enableSitesFilter" @input="needTextReload" size="xs" label="Включить html-фильтр для сайтов">
|
|
||||||
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
|
||||||
Html-фильтр вырезает лишние элементы со<br>
|
|
||||||
страницы для определенных сайтов, таких как:<br>
|
|
||||||
samlib.ru<br>
|
|
||||||
www.fanfiction.net<br>
|
|
||||||
archiveofourown.org<br>
|
|
||||||
и других
|
|
||||||
</q-tooltip>
|
|
||||||
</q-checkbox>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item row">
|
<div class="item row">
|
||||||
<div class="label-6">Обработка</div>
|
<div class="label-6">Обработка</div>
|
||||||
<q-checkbox size="xs" v-model="lazyParseEnabled" label="Предварительная подготовка текста">
|
<q-checkbox size="xs" v-model="lazyParseEnabled" label="Предварительная подготовка текста">
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template lang="includer">
|
<template>
|
||||||
<Window ref="window" height="95%" width="600px" @close="close">
|
<Window ref="window" height="95%" width="600px" @close="close">
|
||||||
<template slot="header">
|
<template #header>
|
||||||
Настройки
|
Настройки
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -8,8 +8,9 @@
|
|||||||
<div class="full-height">
|
<div class="full-height">
|
||||||
<q-tabs
|
<q-tabs
|
||||||
ref="tabs"
|
ref="tabs"
|
||||||
class="bg-grey-3 text-black"
|
|
||||||
v-model="selectedTab"
|
v-model="selectedTab"
|
||||||
|
class="bg-grey-3 text-black"
|
||||||
|
|
||||||
left-icon="la la-caret-up"
|
left-icon="la la-caret-up"
|
||||||
right-icon="la la-caret-down"
|
right-icon="la la-caret-down"
|
||||||
active-color="white"
|
active-color="white"
|
||||||
@@ -20,70 +21,115 @@
|
|||||||
stretch
|
stretch
|
||||||
inline-label
|
inline-label
|
||||||
>
|
>
|
||||||
<div v-show="tabsScrollable" class="q-pt-lg"/>
|
<div v-show="tabsScrollable" class="q-pt-lg" />
|
||||||
<q-tab class="tab" name="profiles" icon="la la-users" label="Профили" />
|
<q-tab class="tab" name="profiles" icon="la la-users" label="Профили" />
|
||||||
<q-tab class="tab" name="view" icon="la la-eye" label="Вид" />
|
<q-tab class="tab" name="view" icon="la la-eye" label="Вид" />
|
||||||
<q-tab class="tab" name="buttons" icon="la la-grip-horizontal" label="Кнопки" />
|
<q-tab class="tab" name="buttons" icon="la la-grip-horizontal" label="Кнопки" />
|
||||||
<q-tab class="tab" name="keys" icon="la la-gamepad" label="Управление" />
|
<q-tab class="tab" name="keys" icon="la la-gamepad" label="Управление" />
|
||||||
<q-tab class="tab" name="pagemove" icon="la la-school" label="Листание" />
|
<q-tab class="tab" name="pagemove" icon="la la-school" label="Листание" />
|
||||||
|
<q-tab class="tab" name="convert" icon="la la-magic" label="Конвертир." />
|
||||||
<q-tab class="tab" name="others" icon="la la-list-ul" label="Прочее" />
|
<q-tab class="tab" name="others" icon="la la-list-ul" label="Прочее" />
|
||||||
<q-tab class="tab" name="reset" icon="la la-broom" label="Сброс" />
|
<q-tab class="tab" name="reset" icon="la la-broom" label="Сброс" />
|
||||||
<div v-show="tabsScrollable" class="q-pt-lg"/>
|
<div v-show="tabsScrollable" class="q-pt-lg" />
|
||||||
</q-tabs>
|
</q-tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col fit">
|
<div class="col fit">
|
||||||
<!-- Профили --------------------------------------------------------------------->
|
<!-- Профили --------------------------------------------------------------------->
|
||||||
<div v-if="selectedTab == 'profiles'" class="fit tab-panel">
|
<div v-if="selectedTab == 'profiles'" class="fit tab-panel">
|
||||||
@@include('./include/ProfilesTab.inc');
|
@@include('./ProfilesTab.inc');
|
||||||
</div>
|
</div>
|
||||||
<!-- Вид ------------------------------------------------------------------------->
|
<!-- Вид ------------------------------------------------------------------------->
|
||||||
<div v-if="selectedTab == 'view'" class="fit column">
|
<div v-if="selectedTab == 'view'" class="fit column">
|
||||||
@@include('./include/ViewTab.inc');
|
<q-tabs
|
||||||
|
v-model="selectedViewTab"
|
||||||
|
active-color="black"
|
||||||
|
active-bg-color="white"
|
||||||
|
indicator-color="white"
|
||||||
|
dense
|
||||||
|
no-caps
|
||||||
|
class="no-mp bg-grey-4 text-grey-7"
|
||||||
|
>
|
||||||
|
<q-tab name="mode" label="Режим" />
|
||||||
|
<q-tab name="color" label="Цвет" />
|
||||||
|
<q-tab name="font" label="Шрифт" />
|
||||||
|
<q-tab name="text" label="Текст" />
|
||||||
|
<q-tab name="status" label="Строка статуса" />
|
||||||
|
</q-tabs>
|
||||||
|
|
||||||
|
<div class="q-mb-sm" />
|
||||||
|
|
||||||
|
<div class="col tab-panel">
|
||||||
|
<div v-if="selectedViewTab == 'mode'">
|
||||||
|
@@include('./ViewTab/Mode.inc');
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="selectedViewTab == 'color'">
|
||||||
|
@@include('./ViewTab/Color.inc');
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="selectedViewTab == 'font'">
|
||||||
|
@@include('./ViewTab/Font.inc');
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="selectedViewTab == 'text'">
|
||||||
|
@@include('./ViewTab/Text.inc');
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="selectedViewTab == 'status'">
|
||||||
|
@@include('./ViewTab/Status.inc');
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Кнопки ---------------------------------------------------------------------->
|
<!-- Кнопки ---------------------------------------------------------------------->
|
||||||
<div v-if="selectedTab == 'buttons'" class="fit tab-panel">
|
<div v-if="selectedTab == 'buttons'" class="fit tab-panel">
|
||||||
@@include('./include/ButtonsTab.inc');
|
@@include('./ButtonsTab.inc');
|
||||||
</div>
|
</div>
|
||||||
<!-- Управление ------------------------------------------------------------------>
|
<!-- Управление ------------------------------------------------------------------>
|
||||||
<div v-if="selectedTab == 'keys'" class="fit column">
|
<div v-if="selectedTab == 'keys'" class="fit column">
|
||||||
@@include('./include/KeysTab.inc');
|
@@include('./KeysTab.inc');
|
||||||
</div>
|
</div>
|
||||||
<!-- Листание -------------------------------------------------------------------->
|
<!-- Листание -------------------------------------------------------------------->
|
||||||
<div v-if="selectedTab == 'pagemove'" class="fit tab-panel">
|
<div v-if="selectedTab == 'pagemove'" class="fit tab-panel">
|
||||||
@@include('./include/PageMoveTab.inc');
|
@@include('./PageMoveTab.inc');
|
||||||
|
</div>
|
||||||
|
<!-- Конвертирование ------------------------------------------------------------->
|
||||||
|
<div v-if="selectedTab == 'convert'" class="fit tab-panel">
|
||||||
|
@@include('./ConvertTab.inc');
|
||||||
</div>
|
</div>
|
||||||
<!-- Прочее ---------------------------------------------------------------------->
|
<!-- Прочее ---------------------------------------------------------------------->
|
||||||
<div v-if="selectedTab == 'others'" class="fit tab-panel">
|
<div v-if="selectedTab == 'others'" class="fit tab-panel">
|
||||||
@@include('./include/OthersTab.inc');
|
@@include('./OthersTab.inc');
|
||||||
</div>
|
</div>
|
||||||
<!-- Сброс ----------------------------------------------------------------------->
|
<!-- Сброс ----------------------------------------------------------------------->
|
||||||
<div v-if="selectedTab == 'reset'" class="fit tab-panel">
|
<div v-if="selectedTab == 'reset'" class="fit tab-panel">
|
||||||
@@include('./include/ResetTab.inc');
|
@@include('./ResetTab.inc');
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</Window>
|
</Window>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import Component from 'vue-class-component';
|
import vueComponent from '../../vueComponent.js';
|
||||||
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import * as utils from '../../../share/utils';
|
import * as utils from '../../../share/utils';
|
||||||
|
import * as cryptoUtils from '../../../share/cryptoUtils';
|
||||||
import Window from '../../share/Window.vue';
|
import Window from '../../share/Window.vue';
|
||||||
import NumInput from '../../share/NumInput.vue';
|
import NumInput from '../../share/NumInput.vue';
|
||||||
import UserHotKeys from './UserHotKeys/UserHotKeys.vue';
|
import UserHotKeys from './UserHotKeys/UserHotKeys.vue';
|
||||||
|
import wallpaperStorage from '../share/wallpaperStorage';
|
||||||
|
|
||||||
import rstore from '../../../store/modules/reader';
|
import rstore from '../../../store/modules/reader';
|
||||||
import defPalette from './defPalette';
|
import defPalette from './defPalette';
|
||||||
|
|
||||||
const hex = /^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$/;
|
const hex = /^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$/;
|
||||||
|
|
||||||
export default @Component({
|
const componentOptions = {
|
||||||
components: {
|
components: {
|
||||||
Window,
|
Window,
|
||||||
NumInput,
|
NumInput,
|
||||||
@@ -97,8 +143,9 @@ export default @Component({
|
|||||||
this.settingsChanged();
|
this.settingsChanged();
|
||||||
},
|
},
|
||||||
form: function(newValue) {
|
form: function(newValue) {
|
||||||
if (this.inited)
|
if (this.inited) {
|
||||||
this.commit('reader/setSettings', newValue);
|
this.commit('reader/setSettings', _.cloneDeep(newValue));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
fontBold: function(newValue) {
|
fontBold: function(newValue) {
|
||||||
this.fontWeight = (newValue ? 'bold' : '');
|
this.fontWeight = (newValue ? 'bold' : '');
|
||||||
@@ -108,7 +155,7 @@ export default @Component({
|
|||||||
},
|
},
|
||||||
vertShift: function(newValue) {
|
vertShift: function(newValue) {
|
||||||
const font = (this.webFontName ? this.webFontName : this.fontName);
|
const font = (this.webFontName ? this.webFontName : this.fontName);
|
||||||
if (this.fontShifts[font] != newValue) {
|
if (this.fontShifts[font] != newValue || this.fontVertShift != newValue) {
|
||||||
this.fontShifts = Object.assign({}, this.fontShifts, {[font]: newValue});
|
this.fontShifts = Object.assign({}, this.fontShifts, {[font]: newValue});
|
||||||
this.fontVertShift = newValue;
|
this.fontVertShift = newValue;
|
||||||
}
|
}
|
||||||
@@ -125,6 +172,10 @@ export default @Component({
|
|||||||
if (newValue != '' && this.pageChangeAnimation == 'flip')
|
if (newValue != '' && this.pageChangeAnimation == 'flip')
|
||||||
this.pageChangeAnimation = '';
|
this.pageChangeAnimation = '';
|
||||||
},
|
},
|
||||||
|
dualPageMode(newValue) {
|
||||||
|
if (newValue && this.pageChangeAnimation == 'flip' || this.pageChangeAnimation == 'rightShift')
|
||||||
|
this.pageChangeAnimation = '';
|
||||||
|
},
|
||||||
textColor: function(newValue) {
|
textColor: function(newValue) {
|
||||||
this.textColorFiltered = newValue;
|
this.textColorFiltered = newValue;
|
||||||
},
|
},
|
||||||
@@ -139,19 +190,35 @@ export default @Component({
|
|||||||
if (hex.test(newValue))
|
if (hex.test(newValue))
|
||||||
this.backgroundColor = newValue;
|
this.backgroundColor = newValue;
|
||||||
},
|
},
|
||||||
|
dualDivColor(newValue) {
|
||||||
|
this.dualDivColorFiltered = newValue;
|
||||||
|
},
|
||||||
|
dualDivColorFiltered(newValue) {
|
||||||
|
if (hex.test(newValue))
|
||||||
|
this.dualDivColor = newValue;
|
||||||
|
},
|
||||||
|
statusBarColor(newValue) {
|
||||||
|
this.statusBarColorFiltered = newValue;
|
||||||
|
},
|
||||||
|
statusBarColorFiltered(newValue) {
|
||||||
|
if (hex.test(newValue))
|
||||||
|
this.statusBarColor = newValue;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
class SettingsPage extends Vue {
|
class SettingsPage {
|
||||||
|
_options = componentOptions;
|
||||||
|
|
||||||
selectedTab = 'profiles';
|
selectedTab = 'profiles';
|
||||||
selectedViewTab = 'color';
|
selectedViewTab = 'mode';
|
||||||
selectedKeysTab = 'mouse';
|
selectedKeysTab = 'mouse';
|
||||||
form = {};
|
|
||||||
fontBold = false;
|
fontBold = false;
|
||||||
fontItalic = false;
|
fontItalic = false;
|
||||||
vertShift = 0;
|
vertShift = 0;
|
||||||
tabsScrollable = false;
|
tabsScrollable = false;
|
||||||
textColorFiltered = '';
|
textColorFiltered = '';
|
||||||
bgColorFiltered = '';
|
bgColorFiltered = '';
|
||||||
|
dualDivColorFiltered = '';
|
||||||
|
|
||||||
webFonts = [];
|
webFonts = [];
|
||||||
fonts = [];
|
fonts = [];
|
||||||
@@ -160,6 +227,19 @@ class SettingsPage extends Vue {
|
|||||||
toolButtons = [];
|
toolButtons = [];
|
||||||
rstore = {};
|
rstore = {};
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
const settingsProps = { form: ref({}) };
|
||||||
|
|
||||||
|
for (let prop in rstore.settingDefaults) {
|
||||||
|
settingsProps[prop] = ref(_.cloneDeep(rstore.settingDefaults[prop]));
|
||||||
|
watch(settingsProps[prop], (newValue) => {
|
||||||
|
settingsProps.form.value = Object.assign({}, settingsProps.form.value, {[prop]: newValue});
|
||||||
|
}, {deep: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
return settingsProps;
|
||||||
|
}
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.commit = this.$store.commit;
|
this.commit = this.$store.commit;
|
||||||
this.reader = this.$store.state.reader;
|
this.reader = this.$store.state.reader;
|
||||||
@@ -174,7 +254,7 @@ class SettingsPage extends Vue {
|
|||||||
this.$watch(
|
this.$watch(
|
||||||
'$refs.tabs.scrollable',
|
'$refs.tabs.scrollable',
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
this.tabsScrollable = newValue && !this.$isMobileDevice;
|
this.tabsScrollable = newValue && !this.$root.isMobileDevice;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -189,18 +269,8 @@ class SettingsPage extends Vue {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
this.form = Object.assign({}, this.settings);
|
this.form = Object.assign({}, this.settings);
|
||||||
if (!this.unwatch)
|
for (const prop in rstore.settingDefaults) {
|
||||||
this.unwatch = {};
|
this[prop] = _.cloneDeep(this.form[prop]);
|
||||||
|
|
||||||
for (let prop in rstore.settingDefaults) {
|
|
||||||
if (this.unwatch && this.unwatch[prop])
|
|
||||||
this.unwatch[prop]();
|
|
||||||
|
|
||||||
this[prop] = this.form[prop];
|
|
||||||
|
|
||||||
this.unwatch[prop] = this.$watch(prop, (newValue) => {
|
|
||||||
this.form = Object.assign({}, this.form, {[prop]: newValue});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fontBold = (this.fontWeight == 'bold');
|
this.fontBold = (this.fontWeight == 'bold');
|
||||||
@@ -212,12 +282,18 @@ class SettingsPage extends Vue {
|
|||||||
this.vertShift = this.fontShifts[font] || 0;
|
this.vertShift = this.fontShifts[font] || 0;
|
||||||
this.textColorFiltered = this.textColor;
|
this.textColorFiltered = this.textColor;
|
||||||
this.bgColorFiltered = this.backgroundColor;
|
this.bgColorFiltered = this.backgroundColor;
|
||||||
|
this.dualDivColorFiltered = this.dualDivColor;
|
||||||
|
this.statusBarColorFiltered = this.statusBarColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
get mode() {
|
get mode() {
|
||||||
return this.$store.state.config.mode;
|
return this.$store.state.config.mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isExternalConverter() {
|
||||||
|
return this.$store.state.config.useExternalBookConverter;
|
||||||
|
}
|
||||||
|
|
||||||
get settings() {
|
get settings() {
|
||||||
return this.$store.state.reader.settings;
|
return this.$store.state.reader.settings;
|
||||||
}
|
}
|
||||||
@@ -247,9 +323,19 @@ class SettingsPage extends Vue {
|
|||||||
|
|
||||||
get wallpaperOptions() {
|
get wallpaperOptions() {
|
||||||
let result = [{label: 'Нет', value: ''}];
|
let result = [{label: 'Нет', value: ''}];
|
||||||
for (let i = 1; i < 10; i++) {
|
|
||||||
|
const userWallpapers = _.cloneDeep(this.userWallpapers);
|
||||||
|
userWallpapers.sort((a, b) => a.label.localeCompare(b.label));
|
||||||
|
|
||||||
|
for (const wp of userWallpapers) {
|
||||||
|
if (wallpaperStorage.keyExists(wp.cssClass))
|
||||||
|
result.push({label: wp.label, value: wp.cssClass});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 1; i <= 17; i++) {
|
||||||
result.push({label: i, value: `paper${i}`});
|
result.push({label: i, value: `paper${i}`});
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,13 +359,15 @@ class SettingsPage extends Vue {
|
|||||||
let result = [
|
let result = [
|
||||||
{label: 'Нет', value: ''},
|
{label: 'Нет', value: ''},
|
||||||
{label: 'Вверх-вниз', value: 'downShift'},
|
{label: 'Вверх-вниз', value: 'downShift'},
|
||||||
{label: 'Вправо-влево', value: 'rightShift'},
|
(!this.dualPageMode ? {label: 'Вправо-влево', value: 'rightShift'} : null),
|
||||||
{label: 'Протаивание', value: 'thaw'},
|
{label: 'Протаивание', value: 'thaw'},
|
||||||
{label: 'Мерцание', value: 'blink'},
|
{label: 'Мерцание', value: 'blink'},
|
||||||
{label: 'Вращение', value: 'rotate'},
|
{label: 'Вращение', value: 'rotate'},
|
||||||
];
|
(this.wallpaper == '' && !this.dualPageMode ? {label: 'Листание', value: 'flip'} : null),
|
||||||
if (this.wallpaper == '')
|
];
|
||||||
result.push({label: 'Листание', value: 'flip'});
|
|
||||||
|
result = result.filter(v => v);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,6 +430,12 @@ class SettingsPage extends Vue {
|
|||||||
case 'bg':
|
case 'bg':
|
||||||
result += `background-color: ${this.backgroundColor};`
|
result += `background-color: ${this.backgroundColor};`
|
||||||
break;
|
break;
|
||||||
|
case 'div':
|
||||||
|
result += `background-color: ${this.dualDivColor};`
|
||||||
|
break;
|
||||||
|
case 'statusbar':
|
||||||
|
result += `background-color: ${this.statusBarColor};`
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -371,10 +465,6 @@ class SettingsPage extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
changeShowToolButton(buttonName) {
|
|
||||||
this.showToolButton = Object.assign({}, this.showToolButton, {[buttonName]: !this.showToolButton[buttonName]});
|
|
||||||
}
|
|
||||||
|
|
||||||
async addProfile() {
|
async addProfile() {
|
||||||
try {
|
try {
|
||||||
if (Object.keys(this.profiles).length >= 100) {
|
if (Object.keys(this.profiles).length >= 100) {
|
||||||
@@ -407,7 +497,7 @@ class SettingsPage extends Vue {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await this.$root.stdDialog.prompt(`<b>Предупреждение!</b> Удаление профиля '${this.$sanitize(this.currentProfile)}' необратимо.` +
|
const result = await this.$root.stdDialog.prompt(`<b>Предупреждение!</b> Удаление профиля '${this.$root.sanitize(this.currentProfile)}' необратимо.` +
|
||||||
`<br>Все настройки профиля будут потеряны, однако список читаемых книг сохранится.` +
|
`<br>Все настройки профиля будут потеряны, однако список читаемых книг сохранится.` +
|
||||||
`<br><br>Введите 'да' для подтверждения удаления:`, ' ', {
|
`<br><br>Введите 'да' для подтверждения удаления:`, ' ', {
|
||||||
inputValidator: (str) => { if (str && str.toLowerCase() === 'да') return true; else return 'Удаление не подтверждено'; },
|
inputValidator: (str) => { if (str && str.toLowerCase() === 'да') return true; else return 'Удаление не подтверждено'; },
|
||||||
@@ -500,7 +590,8 @@ class SettingsPage extends Vue {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (result && result.value && result.value.toLowerCase() == 'да') {
|
if (result && result.value && result.value.toLowerCase() == 'да') {
|
||||||
this.$root.$emit('generateNewServerStorageKey');
|
if (this.$root.generateNewServerStorageKey)
|
||||||
|
this.$root.generateNewServerStorageKey();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
//
|
//
|
||||||
@@ -508,6 +599,71 @@ class SettingsPage extends Vue {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadWallpaperFileClick() {
|
||||||
|
this.$refs.file.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadWallpaperFile() {
|
||||||
|
const file = this.$refs.file.files[0];
|
||||||
|
if (file.size > 10*1024*1024) {
|
||||||
|
this.$root.stdDialog.alert('Файл обоев не должен превышать в размере 10Mb', 'Ошибка');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.type != 'image/png' && file.type != 'image/jpeg') {
|
||||||
|
this.$root.stdDialog.alert('Файл обоев должен иметь тип PNG или JPEG', 'Ошибка');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.userWallpapers.length >= 100) {
|
||||||
|
this.$root.stdDialog.alert('Превышено максимальное количество пользовательских обоев.', 'Ошибка');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$refs.file.value = '';
|
||||||
|
if (file) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = (e) => {
|
||||||
|
(async() => {
|
||||||
|
const data = e.target.result;
|
||||||
|
const key = utils.toHex(cryptoUtils.sha256(data));
|
||||||
|
const label = `#${key.substring(0, 4)}`;
|
||||||
|
const cssClass = `user-paper${key}`;
|
||||||
|
|
||||||
|
const newUserWallpapers = _.cloneDeep(this.userWallpapers);
|
||||||
|
const index = _.findIndex(newUserWallpapers, (item) => (item.cssClass == cssClass));
|
||||||
|
|
||||||
|
if (index < 0)
|
||||||
|
newUserWallpapers.push({label, cssClass});
|
||||||
|
if (!wallpaperStorage.keyExists(cssClass))
|
||||||
|
await wallpaperStorage.setData(cssClass, data);
|
||||||
|
|
||||||
|
this.userWallpapers = newUserWallpapers;
|
||||||
|
this.wallpaper = cssClass;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async delWallpaper() {
|
||||||
|
if (this.wallpaper.indexOf('user-paper') == 0) {
|
||||||
|
const newUserWallpapers = [];
|
||||||
|
for (const wp of this.userWallpapers) {
|
||||||
|
if (wp.cssClass != this.wallpaper) {
|
||||||
|
newUserWallpapers.push(wp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await wallpaperStorage.removeData(this.wallpaper);
|
||||||
|
|
||||||
|
this.userWallpapers = newUserWallpapers;
|
||||||
|
this.wallpaper = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
keyHook(event) {
|
keyHook(event) {
|
||||||
if (!this.$root.stdDialog.active && event.type == 'keydown' && event.key == 'Escape') {
|
if (!this.$root.stdDialog.active && event.type == 'keydown' && event.key == 'Escape') {
|
||||||
this.close();
|
this.close();
|
||||||
@@ -515,6 +671,8 @@ class SettingsPage extends Vue {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(SettingsPage);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -544,7 +702,7 @@ class SettingsPage extends Vue {
|
|||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label-1 {
|
.label-1, .label-7 {
|
||||||
width: 75px;
|
width: 75px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -556,7 +714,7 @@ class SettingsPage extends Vue {
|
|||||||
width: 100px;
|
width: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label-1, .label-2, .label-3, .label-4, .label-5, .label-6 {
|
.label-1, .label-2, .label-3, .label-4, .label-5, .label-6, .label-7 {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -2,14 +2,20 @@
|
|||||||
<div class="table col column no-wrap">
|
<div class="table col column no-wrap">
|
||||||
<!-- header -->
|
<!-- header -->
|
||||||
<div class="table-row row">
|
<div class="table-row row">
|
||||||
<div class="desc q-pa-sm bg-blue-2">Команда</div>
|
<div class="desc q-pa-sm bg-blue-2">
|
||||||
|
Команда
|
||||||
|
</div>
|
||||||
<div class="hotKeys col q-pa-sm bg-blue-2 row no-wrap">
|
<div class="hotKeys col q-pa-sm bg-blue-2 row no-wrap">
|
||||||
<div style="width: 80px">Сочетание клавиш</div>
|
<div style="width: 80px">
|
||||||
<q-input ref="input" class="q-ml-sm col"
|
Сочетание клавиш
|
||||||
|
</div>
|
||||||
|
<q-input
|
||||||
|
ref="input"
|
||||||
|
v-model="search"
|
||||||
|
class="q-ml-sm col"
|
||||||
outlined dense rounded
|
outlined dense rounded
|
||||||
bg-color="grey-4"
|
bg-color="grey-4"
|
||||||
placeholder="Найти"
|
placeholder="Найти"
|
||||||
v-model="search"
|
|
||||||
@click.stop
|
@click.stop
|
||||||
/>
|
/>
|
||||||
<div v-show="!readonly" class="q-ml-sm column justify-center">
|
<div v-show="!readonly" class="q-ml-sm column justify-center">
|
||||||
@@ -23,35 +29,38 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- body -->
|
<!-- body -->
|
||||||
<div class="table-row row" v-for="(action, index) in tableData" :key="index">
|
<div v-for="(action, index) in tableData" :key="index" class="table-row row">
|
||||||
<div class="desc q-pa-sm">{{ rstore.readerActions[action] }}</div>
|
<div class="desc q-pa-sm">
|
||||||
|
{{ rstore.readerActions[action] }}
|
||||||
|
</div>
|
||||||
<div class="hotKeys col q-pa-sm">
|
<div class="hotKeys col q-pa-sm">
|
||||||
<q-chip
|
<q-chip
|
||||||
|
v-for="(code, index2) in modelValue[action]" :key="index2"
|
||||||
:color="collisions[code] ? 'red' : 'grey-7'"
|
:color="collisions[code] ? 'red' : 'grey-7'"
|
||||||
:removable="!readonly" :clickable="collisions[code] ? true : false"
|
:removable="!readonly" :clickable="collisions[code] ? true : false"
|
||||||
text-color="white" v-for="(code, index) in value[action]" :key="index" @remove="removeCode(action, code)"
|
text-color="white" @remove="removeCode(action, code)"
|
||||||
@click="collisionWarning(code)"
|
@click="collisionWarning(code)"
|
||||||
>
|
>
|
||||||
{{ code }}
|
{{ code }}
|
||||||
</q-chip>
|
</q-chip>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="!readonly" class="column q-pa-xs">
|
<div v-show="!readonly" class="column q-pa-xs">
|
||||||
<q-icon
|
<q-icon
|
||||||
|
v-ripple
|
||||||
|
:disabled="(modelValue[action].length >= maxCodesLength) || null"
|
||||||
name="la la-plus-circle"
|
name="la la-plus-circle"
|
||||||
class="button bg-green-8 text-white"
|
class="button bg-green-8 text-white"
|
||||||
@click="addHotKey(action)"
|
@click="addHotKey(action)"
|
||||||
v-ripple
|
|
||||||
:disabled="value[action].length >= maxCodesLength"
|
|
||||||
>
|
>
|
||||||
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
||||||
Добавить сочетание клавиш
|
Добавить сочетание клавиш
|
||||||
</q-tooltip>
|
</q-tooltip>
|
||||||
</q-icon>
|
</q-icon>
|
||||||
<q-icon
|
<q-icon
|
||||||
|
v-ripple
|
||||||
name="la la-broom"
|
name="la la-broom"
|
||||||
class="button text-grey-5"
|
class="button text-grey-5"
|
||||||
@click="defaultHotKey(action)"
|
@click="defaultHotKey(action)"
|
||||||
v-ripple
|
|
||||||
>
|
>
|
||||||
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
||||||
По умолчанию
|
По умолчанию
|
||||||
@@ -64,31 +73,29 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../../../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
|
|
||||||
import rstore from '../../../../store/modules/reader';
|
import rstore from '../../../../store/modules/reader';
|
||||||
//import * as utils from '../../share/utils';
|
//import * as utils from '../../share/utils';
|
||||||
|
|
||||||
const UserHotKeysProps = Vue.extend({
|
const componentOptions = {
|
||||||
props: {
|
|
||||||
value: Object,
|
|
||||||
readonly: Boolean,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default @Component({
|
|
||||||
watch: {
|
watch: {
|
||||||
search: function() {
|
search: function() {
|
||||||
this.updateTableData();
|
this.updateTableData();
|
||||||
},
|
},
|
||||||
value: function() {
|
modelValue: function() {
|
||||||
this.checkCollisions();
|
this.checkCollisions();
|
||||||
this.updateTableData();
|
this.updateTableData();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
class UserHotKeys extends UserHotKeysProps {
|
class UserHotKeys {
|
||||||
|
_options = componentOptions;
|
||||||
|
_props = {
|
||||||
|
modelValue: Object,
|
||||||
|
readonly: Boolean,
|
||||||
|
};
|
||||||
|
|
||||||
search = '';
|
search = '';
|
||||||
rstore = {};
|
rstore = {};
|
||||||
tableData = [];
|
tableData = [];
|
||||||
@@ -113,7 +120,7 @@ class UserHotKeys extends UserHotKeysProps {
|
|||||||
|
|
||||||
const search = this.search.toLowerCase();
|
const search = this.search.toLowerCase();
|
||||||
const codesIncludeSearch = (action) => {
|
const codesIncludeSearch = (action) => {
|
||||||
for (const code of this.value[action]) {
|
for (const code of this.modelValue[action]) {
|
||||||
if (code.toLowerCase().includes(search))
|
if (code.toLowerCase().includes(search))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -131,7 +138,7 @@ class UserHotKeys extends UserHotKeysProps {
|
|||||||
|
|
||||||
checkCollisions() {
|
checkCollisions() {
|
||||||
const cols = {};
|
const cols = {};
|
||||||
for (const [action, codes] of Object.entries(this.value)) {
|
for (const [action, codes] of Object.entries(this.modelValue)) {
|
||||||
codes.forEach(code => {
|
codes.forEach(code => {
|
||||||
if (!cols[code])
|
if (!cols[code])
|
||||||
cols[code] = [];
|
cols[code] = [];
|
||||||
@@ -158,26 +165,26 @@ class UserHotKeys extends UserHotKeysProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeCode(action, code) {
|
removeCode(action, code) {
|
||||||
let codes = Array.from(this.value[action]);
|
let codes = Array.from(this.modelValue[action]);
|
||||||
const index = codes.indexOf(code);
|
const index = codes.indexOf(code);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
codes.splice(index, 1);
|
codes.splice(index, 1);
|
||||||
const newValue = Object.assign({}, this.value, {[action]: codes});
|
const newValue = Object.assign({}, this.modelValue, {[action]: codes});
|
||||||
this.$emit('input', newValue);
|
this.$emit('update:modelValue', newValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async addHotKey(action) {
|
async addHotKey(action) {
|
||||||
if (this.value[action].length >= this.maxCodesLength)
|
if (this.modelValue[action].length >= this.maxCodesLength)
|
||||||
return;
|
return;
|
||||||
try {
|
try {
|
||||||
const result = await this.$root.stdDialog.getHotKey(`Добавить сочетание для:<br><b>${rstore.readerActions[action]}</b>`, '');
|
const result = await this.$root.stdDialog.getHotKey(`Добавить сочетание для:<br><b>${rstore.readerActions[action]}</b>`, '');
|
||||||
if (result) {
|
if (result) {
|
||||||
let codes = Array.from(this.value[action]);
|
let codes = Array.from(this.modelValue[action]);
|
||||||
if (codes.indexOf(result) < 0) {
|
if (codes.indexOf(result) < 0) {
|
||||||
codes.push(result);
|
codes.push(result);
|
||||||
const newValue = Object.assign({}, this.value, {[action]: codes});
|
const newValue = Object.assign({}, this.modelValue, {[action]: codes});
|
||||||
this.$emit('input', newValue);
|
this.$emit('update:modelValue', newValue);
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.collisionWarning(result);
|
this.collisionWarning(result);
|
||||||
});
|
});
|
||||||
@@ -192,8 +199,8 @@ class UserHotKeys extends UserHotKeysProps {
|
|||||||
try {
|
try {
|
||||||
if (await this.$root.stdDialog.confirm(`Подтвердите сброс сочетаний клавиш<br>в значения по умолчанию для команды:<br><b>${rstore.readerActions[action]}</b>`, ' ')) {
|
if (await this.$root.stdDialog.confirm(`Подтвердите сброс сочетаний клавиш<br>в значения по умолчанию для команды:<br><b>${rstore.readerActions[action]}</b>`, ' ')) {
|
||||||
const codes = Array.from(rstore.settingDefaults.userHotKeys[action]);
|
const codes = Array.from(rstore.settingDefaults.userHotKeys[action]);
|
||||||
const newValue = Object.assign({}, this.value, {[action]: codes});
|
const newValue = Object.assign({}, this.modelValue, {[action]: codes});
|
||||||
this.$emit('input', newValue);
|
this.$emit('update:modelValue', newValue);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
//
|
//
|
||||||
@@ -204,13 +211,15 @@ class UserHotKeys extends UserHotKeysProps {
|
|||||||
try {
|
try {
|
||||||
if (await this.$root.stdDialog.confirm('Подтвердите сброс сочетаний клавиш<br>для ВСЕХ команд в значения по умолчанию:', ' ')) {
|
if (await this.$root.stdDialog.confirm('Подтвердите сброс сочетаний клавиш<br>для ВСЕХ команд в значения по умолчанию:', ' ')) {
|
||||||
const newValue = Object.assign({}, rstore.settingDefaults.userHotKeys);
|
const newValue = Object.assign({}, rstore.settingDefaults.userHotKeys);
|
||||||
this.$emit('input', newValue);
|
this.$emit('update:modelValue', newValue);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(UserHotKeys);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
116
client/components/Reader/SettingsPage/ViewTab/Color.inc
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
<!---------------------------------------------->
|
||||||
|
<div class="hidden part-header">
|
||||||
|
Цвет
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item row">
|
||||||
|
<div class="label-2">
|
||||||
|
Текст
|
||||||
|
</div>
|
||||||
|
<div class="col row">
|
||||||
|
<q-input
|
||||||
|
v-model="textColorFiltered"
|
||||||
|
class="col-left no-mp"
|
||||||
|
outlined dense
|
||||||
|
|
||||||
|
:rules="['hexColor']"
|
||||||
|
style="max-width: 150px"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<q-icon name="la la-angle-down la-xs" class="cursor-pointer text-white" :style="colorPanStyle('text')">
|
||||||
|
<q-popup-proxy anchor="bottom middle" self="top middle">
|
||||||
|
<div>
|
||||||
|
<q-color
|
||||||
|
v-model="textColor"
|
||||||
|
no-header default-view="palette" :palette="predefineTextColors"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-popup-proxy>
|
||||||
|
</q-icon>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="q-mt-md" />
|
||||||
|
<div class="item row">
|
||||||
|
<div class="label-2">
|
||||||
|
Фон
|
||||||
|
</div>
|
||||||
|
<div class="col row">
|
||||||
|
<q-input
|
||||||
|
v-model="bgColorFiltered"
|
||||||
|
class="col-left no-mp"
|
||||||
|
outlined dense
|
||||||
|
|
||||||
|
:rules="['hexColor']"
|
||||||
|
style="max-width: 150px"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<q-icon name="la la-angle-down la-xs" class="cursor-pointer text-white" :style="colorPanStyle('bg')">
|
||||||
|
<q-popup-proxy anchor="bottom middle" self="top middle">
|
||||||
|
<div>
|
||||||
|
<q-color v-model="backgroundColor" no-header default-view="palette" :palette="predefineBackgroundColors" />
|
||||||
|
</div>
|
||||||
|
</q-popup-proxy>
|
||||||
|
</q-icon>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="q-mt-md" />
|
||||||
|
<div class="item row">
|
||||||
|
<div class="label-2">
|
||||||
|
Обои
|
||||||
|
</div>
|
||||||
|
<div class="col row items-center">
|
||||||
|
<q-select
|
||||||
|
v-model="wallpaper"
|
||||||
|
class="col-left no-mp"
|
||||||
|
:options="wallpaperOptions"
|
||||||
|
dropdown-icon="la la-angle-down la-sm"
|
||||||
|
outlined dense emit-value map-options
|
||||||
|
>
|
||||||
|
<template #selected-item="scope">
|
||||||
|
<div>
|
||||||
|
{{ scope.opt.label }}
|
||||||
|
</div>
|
||||||
|
<div v-show="scope.opt.value" class="q-ml-sm" :class="scope.opt.value" style="width: 40px; height: 28px;"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #option="scope">
|
||||||
|
<q-item
|
||||||
|
v-bind="scope.itemProps"
|
||||||
|
>
|
||||||
|
<q-item-section style="min-width: 50px;">
|
||||||
|
<q-item-label v-html="scope.opt.label" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section v-show="scope.opt.value" :class="scope.opt.value" style="min-width: 70px; min-height: 50px;" />
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
|
||||||
|
<div class="q-px-xs" />
|
||||||
|
<q-btn class="q-ml-sm" round dense color="blue" icon="la la-plus" @click.stop="loadWallpaperFileClick">
|
||||||
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
Добавить файл обоев
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
<q-btn v-show="wallpaper.indexOf('user-paper') === 0" class="q-ml-sm" round dense color="blue" icon="la la-minus" @click.stop="delWallpaper">
|
||||||
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||||
|
Удалить выбранные обои
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="q-mt-sm" />
|
||||||
|
<div class="item row">
|
||||||
|
<div class="label-2"></div>
|
||||||
|
<div class="col row items-center">
|
||||||
|
<q-checkbox v-model="wallpaperIgnoreStatusBar" size="xs" label="Не включать строку статуса в обои" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input ref="file" type="file" style="display: none;" @change="loadWallpaperFile" />
|
||||||
124
client/components/Reader/SettingsPage/ViewTab/Mode.inc
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
<!---------------------------------------------->
|
||||||
|
<div class="hidden part-header">Режим</div>
|
||||||
|
|
||||||
|
<div class="item row">
|
||||||
|
<div class="label-2"></div>
|
||||||
|
<div class="col row">
|
||||||
|
<q-checkbox v-model="dualPageMode" size="xs" label="Двухстраничный режим" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="part-header">Страницы</div>
|
||||||
|
<div class="item row">
|
||||||
|
<div class="label-2">Отступ границ</div>
|
||||||
|
<div class="col row">
|
||||||
|
<NumInput class="col-left" v-model="indentLR" :min="0" :max="2000">
|
||||||
|
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
||||||
|
Слева/справа от края экрана
|
||||||
|
</q-tooltip>
|
||||||
|
</NumInput>
|
||||||
|
<div class="q-px-sm"/>
|
||||||
|
<NumInput class="col" v-model="indentTB" :min="0" :max="2000">
|
||||||
|
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
||||||
|
Сверху/снизу от края экрана
|
||||||
|
</q-tooltip>
|
||||||
|
</NumInput>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-show="dualPageMode" class="item row">
|
||||||
|
<div class="label-2">Отступ внутри</div>
|
||||||
|
<div class="col row">
|
||||||
|
<NumInput class="col-left" v-model="dualIndentLR" :min="0" :max="2000">
|
||||||
|
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
||||||
|
Слева/справа внутри страницы
|
||||||
|
</q-tooltip>
|
||||||
|
</NumInput>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-show="dualPageMode">
|
||||||
|
<div class="part-header">Разделитель</div>
|
||||||
|
|
||||||
|
<div class="item row no-wrap">
|
||||||
|
<div class="label-2">Цвет</div>
|
||||||
|
<div class="col-left row">
|
||||||
|
<q-input class="col-left no-mp"
|
||||||
|
outlined dense
|
||||||
|
v-model="dualDivColorFiltered"
|
||||||
|
:rules="['hexColor']"
|
||||||
|
style="max-width: 150px"
|
||||||
|
:disable="dualDivColorAsText"
|
||||||
|
>
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<q-icon name="la la-angle-down la-xs" class="cursor-pointer text-white" :style="colorPanStyle('div')">
|
||||||
|
<q-popup-proxy anchor="bottom middle" self="top middle">
|
||||||
|
<div>
|
||||||
|
<q-color v-model="dualDivColor"
|
||||||
|
no-header default-view="palette" :palette="predefineTextColors"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-popup-proxy>
|
||||||
|
</q-icon>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="q-px-xs"/>
|
||||||
|
<q-checkbox v-model="dualDivColorAsText" size="xs" label="Как у текста" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item row">
|
||||||
|
<div class="label-2">Прозрачность</div>
|
||||||
|
<div class="col row">
|
||||||
|
<NumInput class="col-left" v-model="dualDivColorAlpha" :min="0" :max="1" :digits="2" :step="0.1"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item row">
|
||||||
|
<div class="label-2">Ширина (px)</div>
|
||||||
|
<div class="col row">
|
||||||
|
<NumInput class="col-left" v-model="dualDivWidth" :min="0" :max="100">
|
||||||
|
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
||||||
|
Ширина разделителя
|
||||||
|
</q-tooltip>
|
||||||
|
</NumInput>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item row">
|
||||||
|
<div class="label-2">Высота (%)</div>
|
||||||
|
<div class="col row">
|
||||||
|
<NumInput class="col-left" v-model="dualDivHeight" :min="0" :max="100">
|
||||||
|
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
||||||
|
Высота разделителя
|
||||||
|
</q-tooltip>
|
||||||
|
</NumInput>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item row">
|
||||||
|
<div class="label-2">Пунктир</div>
|
||||||
|
<div class="col row">
|
||||||
|
<NumInput class="col-left" v-model="dualDivStrokeFill" :min="0" :max="2000">
|
||||||
|
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
||||||
|
Заполнение пунктира
|
||||||
|
</q-tooltip>
|
||||||
|
</NumInput>
|
||||||
|
<div class="q-px-sm"/>
|
||||||
|
<NumInput class="col" v-model="dualDivStrokeGap" :min="0" :max="2000">
|
||||||
|
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
||||||
|
Промежуток пунктира
|
||||||
|
</q-tooltip>
|
||||||
|
</NumInput>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item row">
|
||||||
|
<div class="label-2">Ширина тени</div>
|
||||||
|
<div class="col row">
|
||||||
|
<NumInput class="col-left" v-model="dualDivShadowWidth" :min="0" :max="100"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
64
client/components/Reader/SettingsPage/ViewTab/Status.inc
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<!---------------------------------------------->
|
||||||
|
<div class="hidden part-header">Строка статуса</div>
|
||||||
|
|
||||||
|
<div class="item row">
|
||||||
|
<div class="label-2">Статус</div>
|
||||||
|
<div class="col row">
|
||||||
|
<q-checkbox v-model="showStatusBar" size="xs" label="Показывать" />
|
||||||
|
<q-checkbox v-show="showStatusBar" class="q-ml-sm" v-model="statusBarTop" size="xs" label="Вверху/внизу" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-show="showStatusBar" class="item row no-wrap">
|
||||||
|
<div class="label-2">Цвет</div>
|
||||||
|
<div class="col-left row">
|
||||||
|
<q-input class="col-left no-mp"
|
||||||
|
outlined dense
|
||||||
|
v-model="statusBarColorFiltered"
|
||||||
|
:rules="['hexColor']"
|
||||||
|
style="max-width: 150px"
|
||||||
|
:disable="statusBarColorAsText"
|
||||||
|
>
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<q-icon name="la la-angle-down la-xs" class="cursor-pointer text-white" :style="colorPanStyle('statusbar')">
|
||||||
|
<q-popup-proxy anchor="bottom middle" self="top middle">
|
||||||
|
<div>
|
||||||
|
<q-color v-model="statusBarColor"
|
||||||
|
no-header default-view="palette" :palette="predefineTextColors"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-popup-proxy>
|
||||||
|
</q-icon>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="q-px-xs"/>
|
||||||
|
<q-checkbox v-model="statusBarColorAsText" size="xs" label="Как у текста"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-show="showStatusBar" class="item row">
|
||||||
|
<div class="label-2">Прозрачность</div>
|
||||||
|
<div class="col row">
|
||||||
|
<NumInput class="col-left" v-model="statusBarColorAlpha" :min="0" :max="1" :digits="2" :step="0.1"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-show="showStatusBar" class="item row">
|
||||||
|
<div class="label-2">Высота</div>
|
||||||
|
<div class="col row">
|
||||||
|
<NumInput class="col-left" v-model="statusBarHeight" :min="5" :max="100"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-show="showStatusBar" class="item row">
|
||||||
|
<div class="label-2"></div>
|
||||||
|
<div class="col row">
|
||||||
|
<q-checkbox v-model="statusBarClickOpen" size="xs" label="Открывать оригинал по клику">
|
||||||
|
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
||||||
|
По клику на автора-название в строке статуса<br>
|
||||||
|
открывать оригинал произведения в новой вкладке
|
||||||
|
</q-tooltip>
|
||||||
|
</q-checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -15,23 +15,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="item row">
|
|
||||||
<div class="label-2">Отступ</div>
|
|
||||||
<div class="col row">
|
|
||||||
<NumInput class="col-left" v-model="indentLR" :min="0" :max="2000">
|
|
||||||
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
|
||||||
Слева/справа
|
|
||||||
</q-tooltip>
|
|
||||||
</NumInput>
|
|
||||||
<div class="q-px-sm"/>
|
|
||||||
<NumInput class="col" v-model="indentTB" :min="0" :max="2000">
|
|
||||||
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
|
||||||
Сверху/снизу
|
|
||||||
</q-tooltip>
|
|
||||||
</NumInput>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item row">
|
<div class="item row">
|
||||||
<div class="label-2">Сдвиг</div>
|
<div class="label-2">Сдвиг</div>
|
||||||
<div class="col row">
|
<div class="col row">
|
||||||
@@ -123,7 +106,7 @@
|
|||||||
<div class="item row">
|
<div class="item row">
|
||||||
<div class="label-2"></div>
|
<div class="label-2"></div>
|
||||||
<div class="col row">
|
<div class="col row">
|
||||||
<q-checkbox v-model="imageFitWidth" :disable="!showImages" size="xs" label="Ширина не более размера экрана" />
|
<q-checkbox v-model="imageFitWidth" size="xs" label="Ширина не более размера страницы" :disable="!showImages || dualPageMode"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
<q-tabs
|
|
||||||
v-model="selectedViewTab"
|
|
||||||
active-color="black"
|
|
||||||
active-bg-color="white"
|
|
||||||
indicator-color="white"
|
|
||||||
dense
|
|
||||||
no-caps
|
|
||||||
class="no-mp bg-grey-4 text-grey-7"
|
|
||||||
>
|
|
||||||
<q-tab name="color" label="Цвет" />
|
|
||||||
<q-tab name="font" label="Шрифт" />
|
|
||||||
<q-tab name="text" label="Текст" />
|
|
||||||
<q-tab name="status" label="Строка статуса" />
|
|
||||||
</q-tabs>
|
|
||||||
|
|
||||||
<div class="q-mb-sm"/>
|
|
||||||
|
|
||||||
<div class="col tab-panel">
|
|
||||||
<div v-if="selectedViewTab == 'color'">
|
|
||||||
@@include('./ViewTab/Color.inc');
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="selectedViewTab == 'font'">
|
|
||||||
@@include('./ViewTab/Font.inc');
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="selectedViewTab == 'text'">
|
|
||||||
@@include('./ViewTab/Text.inc');
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="selectedViewTab == 'status'">
|
|
||||||
@@include('./ViewTab/Status.inc');
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
<!---------------------------------------------->
|
|
||||||
<div class="hidden part-header">Цвет</div>
|
|
||||||
|
|
||||||
<div class="item row">
|
|
||||||
<div class="label-2">Текст</div>
|
|
||||||
<div class="col row">
|
|
||||||
<q-input class="col-left no-mp"
|
|
||||||
outlined dense
|
|
||||||
v-model="textColorFiltered"
|
|
||||||
:rules="['hexColor']"
|
|
||||||
style="max-width: 150px"
|
|
||||||
>
|
|
||||||
<template v-slot:prepend>
|
|
||||||
<q-icon name="la la-angle-down la-xs" class="cursor-pointer text-white" :style="colorPanStyle('text')">
|
|
||||||
<q-popup-proxy anchor="bottom middle" self="top middle">
|
|
||||||
<div>
|
|
||||||
<q-color v-model="textColor"
|
|
||||||
no-header default-view="palette" :palette="predefineTextColors"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</q-popup-proxy>
|
|
||||||
</q-icon>
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
|
|
||||||
<span class="col" style="position: relative; top: 35px; left: 15px;">Обои:</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="q-mt-md"/>
|
|
||||||
<div class="item row">
|
|
||||||
<div class="label-2">Фон</div>
|
|
||||||
<div class="col row">
|
|
||||||
<q-input class="col-left no-mp"
|
|
||||||
outlined dense
|
|
||||||
v-model="bgColorFiltered"
|
|
||||||
:rules="['hexColor']"
|
|
||||||
style="max-width: 150px"
|
|
||||||
:disable="wallpaper != ''"
|
|
||||||
>
|
|
||||||
<template v-slot:prepend>
|
|
||||||
<q-icon name="la la-angle-down la-xs" class="cursor-pointer text-white" :style="colorPanStyle('bg')">
|
|
||||||
<q-popup-proxy anchor="bottom middle" self="top middle">
|
|
||||||
<div>
|
|
||||||
<q-color v-model="backgroundColor" no-header default-view="palette" :palette="predefineBackgroundColors"/>
|
|
||||||
</div>
|
|
||||||
</q-popup-proxy>
|
|
||||||
</q-icon>
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
|
|
||||||
<div class="q-px-sm"/>
|
|
||||||
<q-select class="col" v-model="wallpaper" :options="wallpaperOptions"
|
|
||||||
dropdown-icon="la la-angle-down la-sm"
|
|
||||||
outlined dense emit-value map-options
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
<!---------------------------------------------->
|
|
||||||
<div class="hidden part-header">Строка статуса</div>
|
|
||||||
|
|
||||||
<div class="item row">
|
|
||||||
<div class="label-2">Статус</div>
|
|
||||||
<div class="col row">
|
|
||||||
<q-checkbox v-model="showStatusBar" size="xs" label="Показывать" />
|
|
||||||
<q-checkbox class="q-ml-sm" v-model="statusBarTop" size="xs" :disable="!showStatusBar" label="Вверху/внизу" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item row">
|
|
||||||
<div class="label-2">Высота</div>
|
|
||||||
<div class="col row">
|
|
||||||
<NumInput class="col-left" v-model="statusBarHeight" :min="5" :max="100" :disable="!showStatusBar"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item row">
|
|
||||||
<div class="label-2">Прозрачность</div>
|
|
||||||
<div class="col row">
|
|
||||||
<NumInput class="col-left" v-model="statusBarColorAlpha" :min="0" :max="1" :digits="2" :step="0.1" :disable="!showStatusBar"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item row">
|
|
||||||
<div class="label-2"></div>
|
|
||||||
<div class="col row">
|
|
||||||
<q-checkbox v-model="statusBarClickOpen" size="xs" label="Открывать оригинал по клику">
|
|
||||||
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
|
||||||
По клику на автора-название в строке статуса<br>
|
|
||||||
открывать оригинал произведения в новой вкладке
|
|
||||||
</q-tooltip>
|
|
||||||
</q-checkbox>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -2,11 +2,11 @@ import {sleep} from '../../../share/utils';
|
|||||||
|
|
||||||
export default class DrawHelper {
|
export default class DrawHelper {
|
||||||
fontBySize(size) {
|
fontBySize(size) {
|
||||||
return `${size}px ${this.fontName}`;
|
return `${size}px '${this.fontName}'`;
|
||||||
}
|
}
|
||||||
|
|
||||||
fontByStyle(style) {
|
fontByStyle(style) {
|
||||||
return `${style.italic ? 'italic' : this.fontStyle} ${style.bold ? 'bold' : this.fontWeight} ${this.fontSize}px ${this.fontName}`;
|
return `${style.italic ? 'italic' : this.fontStyle} ${style.bold ? 'bold' : this.fontWeight} ${this.fontSize}px '${this.fontName}'`;
|
||||||
}
|
}
|
||||||
|
|
||||||
measureText(text, style) {// eslint-disable-line no-unused-vars
|
measureText(text, style) {// eslint-disable-line no-unused-vars
|
||||||
@@ -19,6 +19,109 @@ export default class DrawHelper {
|
|||||||
return this.context.measureText(text).width;
|
return this.context.measureText(text).width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drawLine(line, lineIndex, baseLineIndex, sel, imageDrawn) {
|
||||||
|
/* line:
|
||||||
|
{
|
||||||
|
begin: Number,
|
||||||
|
end: Number,
|
||||||
|
first: Boolean,
|
||||||
|
last: Boolean,
|
||||||
|
parts: array of {
|
||||||
|
style: {bold: Boolean, italic: Boolean, center: Boolean},
|
||||||
|
image: {local: Boolean, inline: Boolean, id: String, imageLine: Number, lineCount: Number, paraIndex: Number},
|
||||||
|
text: String,
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
let out = '<div>';
|
||||||
|
|
||||||
|
let lineText = '';
|
||||||
|
let center = false;
|
||||||
|
let space = 0;
|
||||||
|
let j = 0;
|
||||||
|
//формируем строку
|
||||||
|
for (const part of line.parts) {
|
||||||
|
let tOpen = '';
|
||||||
|
tOpen += (part.style.bold ? '<b>' : '');
|
||||||
|
tOpen += (part.style.italic ? '<i>' : '');
|
||||||
|
tOpen += (part.style.sup ? '<span style="vertical-align: baseline; position: relative; line-height: 0; top: -0.3em">' : '');
|
||||||
|
tOpen += (part.style.sub ? '<span style="vertical-align: baseline; position: relative; line-height: 0; top: 0.3em">' : '');
|
||||||
|
let tClose = '';
|
||||||
|
tClose += (part.style.sub ? '</span>' : '');
|
||||||
|
tClose += (part.style.sup ? '</span>' : '');
|
||||||
|
tClose += (part.style.italic ? '</i>' : '');
|
||||||
|
tClose += (part.style.bold ? '</b>' : '');
|
||||||
|
|
||||||
|
let text = '';
|
||||||
|
if (lineIndex == 0 && this.searching) {
|
||||||
|
for (let k = 0; k < part.text.length; k++) {
|
||||||
|
text += (sel.has(j) ? `<ins>${part.text[k]}</ins>` : part.text[k]);
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
text = part.text;
|
||||||
|
|
||||||
|
if (text && text.trim() == '')
|
||||||
|
text = `<span style="white-space: pre">${text}</span>`;
|
||||||
|
|
||||||
|
lineText += `${tOpen}${text}${tClose}`;
|
||||||
|
|
||||||
|
center = center || part.style.center;
|
||||||
|
space = (part.style.space > space ? part.style.space : space);
|
||||||
|
|
||||||
|
//избражения
|
||||||
|
//image: {local: Boolean, inline: Boolean, id: String, imageLine: Number, lineCount: Number, paraIndex: Number, w: Number, h: Number},
|
||||||
|
const img = part.image;
|
||||||
|
if (img && img.id && !img.inline && !imageDrawn.has(img.paraIndex)) {
|
||||||
|
const bin = this.parsed.binary[img.id];
|
||||||
|
if (bin) {
|
||||||
|
let resize = '';
|
||||||
|
if (bin.h > img.h) {
|
||||||
|
resize = `height: ${img.h}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const left = (this.w - img.w)/2;
|
||||||
|
const top = ((img.lineCount*this.lineHeight - img.h)/2) + (lineIndex - baseLineIndex - img.imageLine)*this.lineHeight;
|
||||||
|
if (img.local) {
|
||||||
|
lineText += `<img src="data:${bin.type};base64,${bin.data}" style="position: absolute; left: ${left}px; top: ${top}px; ${resize}"/>`;
|
||||||
|
} else {
|
||||||
|
lineText += `<img src="${img.id}" style="position: absolute; left: ${left}px; top: ${top}px; ${resize}"/>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
imageDrawn.add(img.paraIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (img && img.id && img.inline) {
|
||||||
|
if (img.local) {
|
||||||
|
const bin = this.parsed.binary[img.id];
|
||||||
|
if (bin) {
|
||||||
|
let resize = '';
|
||||||
|
if (bin.h > this.fontSize) {
|
||||||
|
resize = `height: ${this.fontSize - 3}px`;
|
||||||
|
}
|
||||||
|
lineText += `<img src="data:${bin.type};base64,${bin.data}" style="${resize}"/>`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const centerStyle = (center ? `text-align: center; text-align-last: center; width: ${this.w}px` : '')
|
||||||
|
if ((line.first || space) && !center) {
|
||||||
|
let p = (line.first ? this.p : 0);
|
||||||
|
p = (space ? p + this.p*space : p);
|
||||||
|
lineText = `<span style="display: inline-block; margin-left: ${p}px"></span>${lineText}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.last || center)
|
||||||
|
lineText = `<span style="display: inline-block; ${centerStyle}">${lineText}</span>`;
|
||||||
|
|
||||||
|
out += lineText + '</div>';
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
drawPage(lines, isScrolling) {
|
drawPage(lines, isScrolling) {
|
||||||
if (!this.lastBook || this.pageLineCount < 1 || !this.book || !lines || !this.parsed.textLength)
|
if (!this.lastBook || this.pageLineCount < 1 || !this.book || !lines || !this.parsed.textLength)
|
||||||
return '';
|
return '';
|
||||||
@@ -26,140 +129,78 @@ export default class DrawHelper {
|
|||||||
const font = this.fontByStyle({});
|
const font = this.fontByStyle({});
|
||||||
const justify = (this.textAlignJustify ? 'text-align: justify; text-align-last: justify;' : '');
|
const justify = (this.textAlignJustify ? 'text-align: justify; text-align-last: justify;' : '');
|
||||||
|
|
||||||
let out = `<div style="width: ${this.w}px; height: ${this.h + (isScrolling ? this.lineHeight : 0)}px;` +
|
const boxH = this.h + (isScrolling ? this.lineHeight : 0);
|
||||||
|
let out = `<div class="row no-wrap" style="width: ${this.boxW}px; height: ${boxH}px;` +
|
||||||
` position: absolute; top: ${this.fontSize*this.textShift}px; color: ${this.textColor}; font: ${font}; ${justify}` +
|
` position: absolute; top: ${this.fontSize*this.textShift}px; color: ${this.textColor}; font: ${font}; ${justify}` +
|
||||||
` line-height: ${this.lineHeight}px; white-space: nowrap;">`;
|
` line-height: ${this.lineHeight}px; white-space: nowrap;">`;
|
||||||
|
|
||||||
let imageDrawn = new Set();
|
let imageDrawn1 = new Set();
|
||||||
|
let imageDrawn2 = new Set();
|
||||||
let len = lines.length;
|
let len = lines.length;
|
||||||
const lineCount = this.pageLineCount + (isScrolling ? 1 : 0);
|
const lineCount = this.pageLineCount + (isScrolling ? 1 : 0);
|
||||||
len = (len > lineCount ? lineCount : len);
|
len = (len > lineCount ? lineCount : len);
|
||||||
|
|
||||||
for (let i = 0; i < len; i++) {
|
//поиск
|
||||||
const line = lines[i];
|
let sel = new Set();
|
||||||
/* line:
|
if (len > 0 && this.searching) {
|
||||||
{
|
const line = lines[0];
|
||||||
begin: Number,
|
let pureText = '';
|
||||||
end: Number,
|
for (const part of line.parts) {
|
||||||
first: Boolean,
|
pureText += part.text;
|
||||||
last: Boolean,
|
|
||||||
parts: array of {
|
|
||||||
style: {bold: Boolean, italic: Boolean, center: Boolean},
|
|
||||||
image: {local: Boolean, inline: Boolean, id: String, imageLine: Number, lineCount: Number, paraIndex: Number},
|
|
||||||
text: String,
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
let sel = new Set();
|
|
||||||
//поиск
|
|
||||||
if (i == 0 && this.searching) {
|
|
||||||
let pureText = '';
|
|
||||||
for (const part of line.parts) {
|
|
||||||
pureText += part.text;
|
|
||||||
}
|
|
||||||
|
|
||||||
pureText = pureText.toLowerCase();
|
|
||||||
let j = 0;
|
|
||||||
while (1) {// eslint-disable-line no-constant-condition
|
|
||||||
j = pureText.indexOf(this.needle, j);
|
|
||||||
if (j >= 0) {
|
|
||||||
for (let k = 0; k < this.needle.length; k++) {
|
|
||||||
sel.add(j + k);
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
break;
|
|
||||||
j++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let lineText = '';
|
pureText = pureText.toLowerCase();
|
||||||
let center = false;
|
|
||||||
let space = 0;
|
|
||||||
let j = 0;
|
let j = 0;
|
||||||
//формируем строку
|
while (1) {// eslint-disable-line no-constant-condition
|
||||||
for (const part of line.parts) {
|
j = pureText.indexOf(this.needle, j);
|
||||||
let tOpen = (part.style.bold ? '<b>' : '');
|
if (j >= 0) {
|
||||||
tOpen += (part.style.italic ? '<i>' : '');
|
for (let k = 0; k < this.needle.length; k++) {
|
||||||
let tClose = (part.style.italic ? '</i>' : '');
|
sel.add(j + k);
|
||||||
tClose += (part.style.bold ? '</b>' : '');
|
|
||||||
|
|
||||||
let text = '';
|
|
||||||
if (i == 0 && this.searching) {
|
|
||||||
for (let k = 0; k < part.text.length; k++) {
|
|
||||||
text += (sel.has(j) ? `<ins>${part.text[k]}</ins>` : part.text[k]);
|
|
||||||
j++;
|
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
text = part.text;
|
break;
|
||||||
|
j++;
|
||||||
if (text && text.trim() == '')
|
|
||||||
text = `<span style="white-space: pre">${text}</span>`;
|
|
||||||
|
|
||||||
lineText += `${tOpen}${text}${tClose}`;
|
|
||||||
|
|
||||||
center = center || part.style.center;
|
|
||||||
space = (part.style.space > space ? part.style.space : space);
|
|
||||||
|
|
||||||
//избражения
|
|
||||||
//image: {local: Boolean, inline: Boolean, id: String, imageLine: Number, lineCount: Number, paraIndex: Number, w: Number, h: Number},
|
|
||||||
const img = part.image;
|
|
||||||
if (img && img.id && !img.inline && !imageDrawn.has(img.paraIndex)) {
|
|
||||||
const bin = this.parsed.binary[img.id];
|
|
||||||
if (bin) {
|
|
||||||
let resize = '';
|
|
||||||
if (bin.h > img.h) {
|
|
||||||
resize = `height: ${img.h}px`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const left = (this.w - img.w)/2;
|
|
||||||
const top = ((img.lineCount*this.lineHeight - img.h)/2) + (i - img.imageLine)*this.lineHeight;
|
|
||||||
if (img.local) {
|
|
||||||
lineText += `<img src="data:${bin.type};base64,${bin.data}" style="position: absolute; left: ${left}px; top: ${top}px; ${resize}"/>`;
|
|
||||||
} else {
|
|
||||||
lineText += `<img src="${img.id}" style="position: absolute; left: ${left}px; top: ${top}px; ${resize}"/>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
imageDrawn.add(img.paraIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (img && img.id && img.inline) {
|
|
||||||
if (img.local) {
|
|
||||||
const bin = this.parsed.binary[img.id];
|
|
||||||
if (bin) {
|
|
||||||
let resize = '';
|
|
||||||
if (bin.h > this.fontSize) {
|
|
||||||
resize = `height: ${this.fontSize - 3}px`;
|
|
||||||
}
|
|
||||||
lineText += `<img src="data:${bin.type};base64,${bin.data}" style="${resize}"/>`;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const centerStyle = (center ? `text-align: center; text-align-last: center; width: ${this.w}px` : '')
|
//отрисовка строк
|
||||||
if ((line.first || space) && !center) {
|
if (!this.dualPageMode) {
|
||||||
let p = (line.first ? this.p : 0);
|
out += `<div class="fit">`;
|
||||||
p = (space ? p + this.p*space : p);
|
for (let i = 0; i < len; i++) {
|
||||||
lineText = `<span style="display: inline-block; margin-left: ${p}px"></span>${lineText}`;
|
out += this.drawLine(lines[i], i, 0, sel, imageDrawn1);
|
||||||
}
|
}
|
||||||
|
out += `</div>`;
|
||||||
|
} else {
|
||||||
|
//левая страница
|
||||||
|
out += `<div style="width: ${this.w}px; margin-left: ${this.dualIndentLR}px; position: relative;">`;
|
||||||
|
const l2 = (this.pageRowsCount > len ? len : this.pageRowsCount);
|
||||||
|
for (let i = 0; i < l2; i++) {
|
||||||
|
out += this.drawLine(lines[i], i, 0, sel, imageDrawn1);
|
||||||
|
}
|
||||||
|
out += '</div>';
|
||||||
|
|
||||||
if (line.last || center)
|
//разделитель
|
||||||
lineText = `<span style="display: inline-block; ${centerStyle}">${lineText}</span>`;
|
out += `<div style="width: ${this.dualIndentLR*2}px;"></div>`;
|
||||||
|
|
||||||
out += (i > 0 ? '<br>' : '') + lineText;
|
//правая страница
|
||||||
|
out += `<div style="width: ${this.w}px; margin-right: ${this.dualIndentLR}px; position: relative;">`;
|
||||||
|
for (let i = l2; i < len; i++) {
|
||||||
|
out += this.drawLine(lines[i], i, l2, sel, imageDrawn2);
|
||||||
|
}
|
||||||
|
out += '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
out += '</div>';
|
out += '</div>';
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
drawPercentBar(x, y, w, h, font, fontSize, bookPos, textLength) {
|
drawPercentBar(x, y, w, h, font, fontSize, bookPos, textLength, imageNum, imageLength) {
|
||||||
const pad = 3;
|
const pad = 3;
|
||||||
const fh = h - 2*pad;
|
const fh = h - 2*pad;
|
||||||
const fh2 = fh/2;
|
const fh2 = fh/2;
|
||||||
|
|
||||||
const t1 = `${Math.floor((bookPos + 1)/1000)}/${Math.floor(textLength/1000)}`;
|
const tImg = (imageNum > 0 ? ` (${imageNum}/${imageLength})` : '');
|
||||||
|
const t1 = `${Math.floor((bookPos + 1)/1000)}/${Math.floor(textLength/1000)}${tImg}`;
|
||||||
const w1 = this.measureTextFont(t1, font) + fh2;
|
const w1 = this.measureTextFont(t1, font) + fh2;
|
||||||
const read = (bookPos + 1)/textLength;
|
const read = (bookPos + 1)/textLength;
|
||||||
const t2 = `${(read*100).toFixed(2)}%`;
|
const t2 = `${(read*100).toFixed(2)}%`;
|
||||||
@@ -172,8 +213,8 @@ export default class DrawHelper {
|
|||||||
|
|
||||||
if (w1 + w2 + w3 <= w && w3 > (10 + fh2)) {
|
if (w1 + w2 + w3 <= w && w3 > (10 + fh2)) {
|
||||||
const barWidth = w - w1 - w2 - fh2;
|
const barWidth = w - w1 - w2 - fh2;
|
||||||
out += this.strokeRect(x + w1, y + pad, barWidth, fh - 2, this.statusBarColor);
|
out += this.strokeRect(x + w1, y + pad, barWidth, fh - 2, this.statusBarRgbaColor);
|
||||||
out += this.fillRect(x + w1 + 2, y + pad + 2, (barWidth - 4)*read, fh - 6, this.statusBarColor);
|
out += this.fillRect(x + w1 + 2, y + pad + 2, (barWidth - 4)*read, fh - 6, this.statusBarRgbaColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (w1 <= w)
|
if (w1 <= w)
|
||||||
@@ -182,16 +223,16 @@ export default class DrawHelper {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
drawStatusBar(statusBarTop, statusBarHeight, bookPos, textLength, title) {
|
drawStatusBar(statusBarTop, statusBarHeight, bookPos, textLength, title, imageNum, imageLength) {
|
||||||
|
|
||||||
let out = `<div class="layout" style="` +
|
let out = `<div class="layout" style="` +
|
||||||
`width: ${this.realWidth}px; height: ${statusBarHeight}px; ` +
|
`width: ${this.realWidth}px; height: ${statusBarHeight}px; ` +
|
||||||
`color: ${this.statusBarColor}">`;
|
`color: ${this.statusBarRgbaColor}">`;
|
||||||
|
|
||||||
const fontSize = statusBarHeight*0.75;
|
const fontSize = statusBarHeight*0.75;
|
||||||
const font = 'bold ' + this.fontBySize(fontSize);
|
const font = 'bold ' + this.fontBySize(fontSize);
|
||||||
|
|
||||||
out += this.fillRect(0, (statusBarTop ? statusBarHeight : 0), this.realWidth, 1, this.statusBarColor);
|
out += this.fillRect(0, (statusBarTop ? statusBarHeight : 0), this.realWidth, 1, this.statusBarRgbaColor);
|
||||||
|
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
const time = `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
|
const time = `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
|
||||||
@@ -200,7 +241,7 @@ export default class DrawHelper {
|
|||||||
|
|
||||||
out += this.fillTextShift(this.fittingString(title, this.realWidth/2 - fontSize - 3, font), fontSize, 2, font, fontSize);
|
out += this.fillTextShift(this.fittingString(title, this.realWidth/2 - fontSize - 3, font), fontSize, 2, font, fontSize);
|
||||||
|
|
||||||
out += this.drawPercentBar(this.realWidth/2, 2, this.realWidth/2 - timeW - 2*fontSize, statusBarHeight, font, fontSize, bookPos, textLength);
|
out += this.drawPercentBar(this.realWidth/2 + fontSize, 2, this.realWidth/2 - timeW - 3*fontSize, statusBarHeight, font, fontSize, bookPos, textLength, imageNum, imageLength);
|
||||||
|
|
||||||
out += '</div>';
|
out += '</div>';
|
||||||
return out;
|
return out;
|
||||||
@@ -267,7 +308,7 @@ export default class DrawHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async doPageAnimationRightShift(page1, page2, duration, isDown, animation1Finish) {
|
async doPageAnimationRightShift(page1, page2, duration, isDown, animation1Finish) {
|
||||||
const s = this.w + this.fontSize;
|
const s = this.boxW + this.fontSize;
|
||||||
|
|
||||||
if (isDown) {
|
if (isDown) {
|
||||||
page1.style.transform = `translateX(${s}px)`;
|
page1.style.transform = `translateX(${s}px)`;
|
||||||
|
|||||||
93
client/components/Reader/TextPage/TextPage.css
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
@keyframes page1-animation-thaw {
|
||||||
|
0% { opacity: 0; }
|
||||||
|
100% { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes page2-animation-thaw {
|
||||||
|
0% { opacity: 1; }
|
||||||
|
100% { opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper1 {
|
||||||
|
background: url("images/paper1.jpg") center;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper2 {
|
||||||
|
background: url("images/paper2.jpg") center;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper3 {
|
||||||
|
background: url("images/paper3.jpg") center;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper4 {
|
||||||
|
background: url("images/paper4.jpg") center;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper5 {
|
||||||
|
background: url("images/paper5.jpg") center;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper6 {
|
||||||
|
background: url("images/paper6.jpg") center;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper7 {
|
||||||
|
background: url("images/paper7.jpg") center;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper8 {
|
||||||
|
background: url("images/paper8.jpg") center;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper9 {
|
||||||
|
background: url("images/paper9.jpg");
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper10 {
|
||||||
|
background: url("images/paper10.png") center;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper11 {
|
||||||
|
background: url("images/paper11.png") center;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper12 {
|
||||||
|
background: url("images/paper12.png") center;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper13 {
|
||||||
|
background: url("images/paper13.png") center;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper14 {
|
||||||
|
background: url("images/paper14.png") center;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper15 {
|
||||||
|
background: url("images/paper15.png") center;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper16 {
|
||||||
|
background: url("images/paper16.png") center;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper17 {
|
||||||
|
background: url("images/paper17.png") center;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
@@ -1,33 +1,43 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="main" class="main">
|
<div ref="main" class="main">
|
||||||
<div class="layout back" @wheel.prevent.stop="onMouseWheel">
|
<div class="layout back" @wheel.prevent.stop="onMouseWheel">
|
||||||
<div v-html="background"></div>
|
<div class="absolute" v-html="background"></div>
|
||||||
<!-- img -->
|
<div class="absolute" v-html="pageDivider"></div>
|
||||||
</div>
|
</div>
|
||||||
<div ref="scrollBox1" class="layout over-hidden" @wheel.prevent.stop="onMouseWheel">
|
<div ref="scrollBox1" class="layout over-hidden" @wheel.prevent.stop="onMouseWheel">
|
||||||
<div ref="scrollingPage1" class="layout over-hidden" @transitionend="onPage1TransitionEnd" @animationend="onPage1AnimationEnd">
|
<div ref="scrollingPage1" class="layout over-hidden" @transitionend="onPage1TransitionEnd" @animationend="onPage1AnimationEnd">
|
||||||
<div v-html="page1"></div>
|
<div @copy.prevent="copyText" v-html="page1"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ref="scrollBox2" class="layout over-hidden" @wheel.prevent.stop="onMouseWheel">
|
<div ref="scrollBox2" class="layout over-hidden" @wheel.prevent.stop="onMouseWheel">
|
||||||
<div ref="scrollingPage2" class="layout over-hidden" @transitionend="onPage2TransitionEnd" @animationend="onPage2AnimationEnd">
|
<div ref="scrollingPage2" class="layout over-hidden" @transitionend="onPage2TransitionEnd" @animationend="onPage2AnimationEnd">
|
||||||
<div v-html="page2"></div>
|
<div @copy.prevent="copyText" v-html="page2"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="showStatusBar" ref="statusBar" class="layout">
|
<div v-show="showStatusBar" ref="statusBar" class="layout">
|
||||||
<div v-html="statusBar"></div>
|
<div v-html="statusBar"></div>
|
||||||
</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"
|
||||||
|
oncontextmenu="return false;"
|
||||||
|
@mousedown.prevent.stop="onMouseDown" @mouseup.prevent.stop="onMouseUp"
|
||||||
@wheel.prevent.stop="onMouseWheel"
|
@wheel.prevent.stop="onMouseWheel"
|
||||||
@touchstart.stop="onTouchStart" @touchend.stop="onTouchEnd" @touchmove.stop="onTouchMove" @touchcancel.prevent.stop="onTouchCancel"
|
@touchstart.stop="onTouchStart" @touchend.stop="onTouchEnd" @touchmove.stop="onTouchMove" @touchcancel.prevent.stop="onTouchCancel"
|
||||||
oncontextmenu="return false;">
|
>
|
||||||
<div v-show="showStatusBar && statusBarClickOpen" v-html="statusBarClickable" @mousedown.prevent.stop @touchstart.stop
|
<div
|
||||||
@click.prevent.stop="onStatusBarClick"></div>
|
v-show="showStatusBar && statusBarClickOpen" @mousedown.prevent.stop @touchstart.stop
|
||||||
|
@click.prevent.stop="onStatusBarClick"
|
||||||
|
v-html="statusBarClickable"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="!clickControl && showStatusBar && statusBarClickOpen" class="layout" v-html="statusBarClickable" @mousedown.prevent.stop @touchstart.stop
|
<div
|
||||||
@click.prevent.stop="onStatusBarClick">
|
v-show="!clickControl && showStatusBar && statusBarClickOpen" class="layout"
|
||||||
|
@mousedown.prevent.stop @touchstart.stop
|
||||||
|
@click.prevent.stop="onStatusBarClick"
|
||||||
|
v-html="statusBarClickable"
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<!-- невидимым делать нельзя, вовремя не подгружаютя шрифты -->
|
<!-- невидимым делать нельзя (display: none), вовремя не подгружаютя шрифты -->
|
||||||
<canvas ref="offscreenCanvas" class="layout" style="visibility: hidden"></canvas>
|
<canvas ref="offscreenCanvas" class="layout" style="visibility: hidden"></canvas>
|
||||||
<div ref="measureWidth" style="position: absolute; visibility: hidden"></div>
|
<div ref="measureWidth" style="position: absolute; visibility: hidden"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -35,12 +45,16 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
import {loadCSS} from 'fg-loadcss';
|
import {loadCSS} from 'fg-loadcss';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import he from 'he';
|
||||||
|
|
||||||
|
import './TextPage.css';
|
||||||
|
|
||||||
import * as utils from '../../../share/utils';
|
import * as utils from '../../../share/utils';
|
||||||
|
|
||||||
import bookManager from '../share/bookManager';
|
import bookManager from '../share/bookManager';
|
||||||
import DrawHelper from './DrawHelper';
|
import DrawHelper from './DrawHelper';
|
||||||
import rstore from '../../../store/modules/reader';
|
import rstore from '../../../store/modules/reader';
|
||||||
@@ -48,7 +62,7 @@ import {clickMap} from '../share/clickMap';
|
|||||||
|
|
||||||
const minLayoutWidth = 100;
|
const minLayoutWidth = 100;
|
||||||
|
|
||||||
export default @Component({
|
const componentOptions = {
|
||||||
watch: {
|
watch: {
|
||||||
bookPos: function() {
|
bookPos: function() {
|
||||||
this.$emit('book-pos-changed', {bookPos: this.bookPos, bookPosSeen: this.bookPosSeen});
|
this.$emit('book-pos-changed', {bookPos: this.bookPos, bookPosSeen: this.bookPosSeen});
|
||||||
@@ -67,13 +81,16 @@ export default @Component({
|
|||||||
this.updateLayout();
|
this.updateLayout();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
class TextPage extends Vue {
|
class TextPage {
|
||||||
|
_options = componentOptions;
|
||||||
|
|
||||||
toggleLayout = false;
|
toggleLayout = false;
|
||||||
showStatusBar = false;
|
showStatusBar = false;
|
||||||
clickControl = true;
|
clickControl = true;
|
||||||
|
|
||||||
background = null;
|
background = null;
|
||||||
|
pageDivider = null;
|
||||||
page1 = null;
|
page1 = null;
|
||||||
page2 = null;
|
page2 = null;
|
||||||
statusBar = null;
|
statusBar = null;
|
||||||
@@ -110,7 +127,11 @@ class TextPage extends Vue {
|
|||||||
|
|
||||||
this.debouncedDrawStatusBar = _.throttle(() => {
|
this.debouncedDrawStatusBar = _.throttle(() => {
|
||||||
this.drawStatusBar();
|
this.drawStatusBar();
|
||||||
}, 60);
|
}, 60);
|
||||||
|
|
||||||
|
this.debouncedDrawPageDividerAndOrnament = _.throttle(() => {
|
||||||
|
this.drawPageDividerAndOrnament();
|
||||||
|
}, 65);
|
||||||
|
|
||||||
this.debouncedLoadSettings = _.debounce(() => {
|
this.debouncedLoadSettings = _.debounce(() => {
|
||||||
this.loadSettings();
|
this.loadSettings();
|
||||||
@@ -132,7 +153,7 @@ class TextPage extends Vue {
|
|||||||
await this.doPageAnimation();
|
await this.doPageAnimation();
|
||||||
}, 10);
|
}, 10);
|
||||||
|
|
||||||
this.$root.$on('resize', async() => {
|
this.$root.addEventHook('resize', async() => {
|
||||||
this.$nextTick(this.onResize);
|
this.$nextTick(this.onResize);
|
||||||
await utils.sleep(500);
|
await utils.sleep(500);
|
||||||
this.$nextTick(this.onResize);
|
this.$nextTick(this.onResize);
|
||||||
@@ -152,7 +173,7 @@ class TextPage extends Vue {
|
|||||||
const wideLetter = 'Щ';
|
const wideLetter = 'Щ';
|
||||||
|
|
||||||
//preloaded fonts
|
//preloaded fonts
|
||||||
this.fontList = [`12px ${this.fontName}`];
|
this.fontList = [`12px '${this.fontName}'`];
|
||||||
|
|
||||||
//widths
|
//widths
|
||||||
this.realWidth = this.$refs.main.clientWidth;
|
this.realWidth = this.$refs.main.clientWidth;
|
||||||
@@ -161,14 +182,16 @@ class TextPage extends Vue {
|
|||||||
this.$refs.layoutEvents.style.width = this.realWidth + 'px';
|
this.$refs.layoutEvents.style.width = this.realWidth + 'px';
|
||||||
this.$refs.layoutEvents.style.height = this.realHeight + 'px';
|
this.$refs.layoutEvents.style.height = this.realHeight + 'px';
|
||||||
|
|
||||||
this.w = this.realWidth - 2*this.indentLR;
|
const dual = (this.dualPageMode ? 2 : 1);
|
||||||
|
this.boxW = this.realWidth - 2*this.indentLR;
|
||||||
|
this.w = this.boxW/dual - (this.dualPageMode ? 2*this.dualIndentLR : 0);
|
||||||
|
|
||||||
this.scrollHeight = this.realHeight - (this.showStatusBar ? this.statusBarHeight : 0);
|
this.scrollHeight = this.realHeight - (this.showStatusBar ? this.statusBarHeight : 0);
|
||||||
this.h = this.scrollHeight - 2*this.indentTB;
|
this.h = this.scrollHeight - 2*this.indentTB;
|
||||||
this.lineHeight = this.fontSize + this.lineInterval;
|
|
||||||
this.pageLineCount = 1 + Math.floor((this.h - this.lineHeight + this.lineInterval/2)/this.lineHeight);
|
|
||||||
|
|
||||||
this.$refs.scrollingPage1.style.width = this.w + 'px';
|
this.lineHeight = this.fontSize + this.lineInterval;
|
||||||
this.$refs.scrollingPage2.style.width = this.w + 'px';
|
this.pageRowsCount = 1 + Math.floor((this.h - this.lineHeight + this.lineInterval/2)/this.lineHeight);
|
||||||
|
this.pageLineCount = (this.dualPageMode ? this.pageRowsCount*2 : this.pageRowsCount)
|
||||||
|
|
||||||
//stuff
|
//stuff
|
||||||
this.currentAnimation = '';
|
this.currentAnimation = '';
|
||||||
@@ -180,7 +203,10 @@ class TextPage extends Vue {
|
|||||||
this.$refs.statusBar.style.left = '0px';
|
this.$refs.statusBar.style.left = '0px';
|
||||||
this.$refs.statusBar.style.top = (this.statusBarTop ? 1 : this.realHeight - this.statusBarHeight) + 'px';
|
this.$refs.statusBar.style.top = (this.statusBarTop ? 1 : this.realHeight - this.statusBarHeight) + 'px';
|
||||||
|
|
||||||
this.statusBarColor = this.hex2rgba(this.textColor || '#000000', this.statusBarColorAlpha);
|
const sbColor = (this.statusBarColorAsText ? this.textColor : this.statusBarColor);
|
||||||
|
this.statusBarRgbaColor = this.hex2rgba(sbColor || '#000000', this.statusBarColorAlpha);
|
||||||
|
const ddColor = (this.dualDivColorAsText ? this.textColor : this.dualDivColor);
|
||||||
|
this.dualDivRgbaColor = this.hex2rgba(ddColor || '#000000', this.dualDivColorAlpha);
|
||||||
|
|
||||||
//drawHelper
|
//drawHelper
|
||||||
this.drawHelper.realWidth = this.realWidth;
|
this.drawHelper.realWidth = this.realWidth;
|
||||||
@@ -188,10 +214,20 @@ class TextPage extends Vue {
|
|||||||
this.drawHelper.lastBook = this.lastBook;
|
this.drawHelper.lastBook = this.lastBook;
|
||||||
this.drawHelper.book = this.book;
|
this.drawHelper.book = this.book;
|
||||||
this.drawHelper.parsed = this.parsed;
|
this.drawHelper.parsed = this.parsed;
|
||||||
|
this.drawHelper.pageRowsCount = this.pageRowsCount;
|
||||||
this.drawHelper.pageLineCount = this.pageLineCount;
|
this.drawHelper.pageLineCount = this.pageLineCount;
|
||||||
|
|
||||||
|
this.drawHelper.dualPageMode = this.dualPageMode;
|
||||||
|
this.drawHelper.dualIndentLR = this.dualIndentLR;
|
||||||
|
/*this.drawHelper.dualDivWidth = this.dualDivWidth;
|
||||||
|
this.drawHelper.dualDivHeight = this.dualDivHeight;
|
||||||
|
this.drawHelper.dualDivRgbaColor = this.dualDivRgbaColor;
|
||||||
|
this.drawHelper.dualDivStrokeFill = this.dualDivStrokeFill;
|
||||||
|
this.drawHelper.dualDivStrokeGap = this.dualDivStrokeGap;
|
||||||
|
this.drawHelper.dualDivShadowWidth = this.dualDivShadowWidth;*/
|
||||||
|
|
||||||
this.drawHelper.backgroundColor = this.backgroundColor;
|
this.drawHelper.backgroundColor = this.backgroundColor;
|
||||||
this.drawHelper.statusBarColor = this.statusBarColor;
|
this.drawHelper.statusBarRgbaColor = this.statusBarRgbaColor;
|
||||||
this.drawHelper.fontStyle = this.fontStyle;
|
this.drawHelper.fontStyle = this.fontStyle;
|
||||||
this.drawHelper.fontWeight = this.fontWeight;
|
this.drawHelper.fontWeight = this.fontWeight;
|
||||||
this.drawHelper.fontSize = this.fontSize;
|
this.drawHelper.fontSize = this.fontSize;
|
||||||
@@ -200,6 +236,7 @@ class TextPage extends Vue {
|
|||||||
this.drawHelper.textColor = this.textColor;
|
this.drawHelper.textColor = this.textColor;
|
||||||
this.drawHelper.textShift = this.textShift;
|
this.drawHelper.textShift = this.textShift;
|
||||||
this.drawHelper.p = this.p;
|
this.drawHelper.p = this.p;
|
||||||
|
this.drawHelper.boxW = this.boxW;
|
||||||
this.drawHelper.w = this.w;
|
this.drawHelper.w = this.w;
|
||||||
this.drawHelper.h = this.h;
|
this.drawHelper.h = this.h;
|
||||||
this.drawHelper.indentLR = this.indentLR;
|
this.drawHelper.indentLR = this.indentLR;
|
||||||
@@ -228,32 +265,33 @@ class TextPage extends Vue {
|
|||||||
|
|
||||||
//parsed
|
//parsed
|
||||||
if (this.parsed) {
|
if (this.parsed) {
|
||||||
this.parsed.p = this.p;
|
let wideLine = wideLetter;
|
||||||
this.parsed.w = this.w;// px, ширина текста
|
if (!this.drawHelper.measureText(wideLine, {}))
|
||||||
this.parsed.font = this.font;
|
|
||||||
this.parsed.fontSize = this.fontSize;
|
|
||||||
this.parsed.wordWrap = this.wordWrap;
|
|
||||||
this.parsed.cutEmptyParagraphs = this.cutEmptyParagraphs;
|
|
||||||
this.parsed.addEmptyParagraphs = this.addEmptyParagraphs;
|
|
||||||
let t = wideLetter;
|
|
||||||
if (!this.drawHelper.measureText(t, {}))
|
|
||||||
throw new Error('Ошибка measureText');
|
throw new Error('Ошибка measureText');
|
||||||
while (this.drawHelper.measureText(t, {}) < this.w) t += wideLetter;
|
while (this.drawHelper.measureText(wideLine, {}) < this.w) wideLine += wideLetter;
|
||||||
this.parsed.maxWordLength = t.length - 1;
|
|
||||||
this.parsed.measureText = this.drawHelper.measureText.bind(this.drawHelper);
|
|
||||||
this.parsed.lineHeight = this.lineHeight;
|
|
||||||
this.parsed.showImages = this.showImages;
|
|
||||||
this.parsed.showInlineImagesInCenter = this.showInlineImagesInCenter;
|
|
||||||
this.parsed.imageHeightLines = this.imageHeightLines;
|
|
||||||
this.parsed.imageFitWidth = this.imageFitWidth;
|
|
||||||
this.parsed.compactTextPerc = this.compactTextPerc;
|
|
||||||
|
|
||||||
this.parsed.testText = 'Это тестовый текст. Его ширина выдается системой неверно некоторое время.';
|
this.parsed.setSettings({
|
||||||
this.parsed.testWidth = this.drawHelper.measureText(this.parsed.testText, {});
|
p: this.p,
|
||||||
|
w: this.w,
|
||||||
|
font: this.font,
|
||||||
|
fontSize: this.fontSize,
|
||||||
|
wordWrap: this.wordWrap,
|
||||||
|
cutEmptyParagraphs: this.cutEmptyParagraphs,
|
||||||
|
addEmptyParagraphs: this.addEmptyParagraphs,
|
||||||
|
maxWordLength: wideLine.length - 1,
|
||||||
|
lineHeight: this.lineHeight,
|
||||||
|
showImages: this.showImages,
|
||||||
|
showInlineImagesInCenter: this.showInlineImagesInCenter,
|
||||||
|
imageHeightLines: this.imageHeightLines,
|
||||||
|
imageFitWidth: this.imageFitWidth,
|
||||||
|
compactTextPerc: this.compactTextPerc,
|
||||||
|
testWidth: 0,
|
||||||
|
measureText: this.drawHelper.measureText.bind(this.drawHelper),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//scrolling page
|
//scrolling page
|
||||||
const pageSpace = this.scrollHeight - this.pageLineCount*this.lineHeight;
|
const pageSpace = this.scrollHeight - this.pageRowsCount*this.lineHeight;
|
||||||
let top = pageSpace/2;
|
let top = pageSpace/2;
|
||||||
if (this.showStatusBar)
|
if (this.showStatusBar)
|
||||||
top += this.statusBarHeight*(this.statusBarTop ? 1 : 0);
|
top += this.statusBarHeight*(this.statusBarTop ? 1 : 0);
|
||||||
@@ -262,14 +300,14 @@ class TextPage extends Vue {
|
|||||||
|
|
||||||
page1.perspective = page2.perspective = '3072px';
|
page1.perspective = page2.perspective = '3072px';
|
||||||
|
|
||||||
page1.width = page2.width = this.w + this.indentLR + 'px';
|
page1.width = page2.width = this.boxW + this.indentLR + 'px';
|
||||||
page1.height = page2.height = this.scrollHeight - (pageSpace > 0 ? pageSpace : 0) + 'px';
|
page1.height = page2.height = this.scrollHeight - (pageSpace > 0 ? pageSpace : 0) + 'px';
|
||||||
page1.top = page2.top = top + 'px';
|
page1.top = page2.top = top + 'px';
|
||||||
page1.left = page2.left = this.indentLR + 'px';
|
page1.left = page2.left = this.indentLR + 'px';
|
||||||
|
|
||||||
page1 = this.$refs.scrollingPage1.style;
|
page1 = this.$refs.scrollingPage1.style;
|
||||||
page2 = this.$refs.scrollingPage2.style;
|
page2 = this.$refs.scrollingPage2.style;
|
||||||
page1.width = page2.width = this.w + this.indentLR + 'px';
|
page1.width = page2.width = this.boxW + this.indentLR + 'px';
|
||||||
page1.height = page2.height = this.scrollHeight + this.lineHeight + 'px';
|
page1.height = page2.height = this.scrollHeight + this.lineHeight + 'px';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,20 +371,36 @@ class TextPage extends Vue {
|
|||||||
if (!omitLoadFonts)
|
if (!omitLoadFonts)
|
||||||
await this.loadFonts();
|
await this.loadFonts();
|
||||||
|
|
||||||
this.draw();
|
if (omitLoadFonts) {
|
||||||
|
this.draw();
|
||||||
// ширина шрифта некоторое время выдается неверно, поэтому
|
} else {
|
||||||
if (!omitLoadFonts) {
|
// ширина шрифта некоторое время выдается неверно,
|
||||||
const parsed = this.parsed;
|
// не удалось событийно отловить этот момент, поэтому костыль
|
||||||
|
while (this.checkingFont) {
|
||||||
let i = 0;
|
this.stopCheckingFont = true;
|
||||||
const t = this.parsed.testText;
|
|
||||||
while (i++ < 50 && this.parsed === parsed && this.drawHelper.measureText(t, {}) === this.parsed.testWidth)
|
|
||||||
await utils.sleep(100);
|
await utils.sleep(100);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.parsed === parsed) {
|
this.checkingFont = true;
|
||||||
this.parsed.testWidth = this.drawHelper.measureText(t, {});
|
this.stopCheckingFont = false;
|
||||||
this.draw();
|
try {
|
||||||
|
const parsed = this.parsed;
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
const t = 'Это тестовый текст. Его ширина выдается системой неправильно некоторое время.';
|
||||||
|
let twprev = 0;
|
||||||
|
//5 секунд проверяем изменения шрифта
|
||||||
|
while (!this.stopCheckingFont && i++ < 50 && this.parsed === parsed) {
|
||||||
|
const tw = this.drawHelper.measureText(t, {});
|
||||||
|
if (tw !== twprev) {
|
||||||
|
this.parsed.setSettings({testWidth: tw});
|
||||||
|
this.draw();
|
||||||
|
twprev = tw;
|
||||||
|
}
|
||||||
|
await utils.sleep(100);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.checkingFont = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -395,7 +449,7 @@ class TextPage extends Vue {
|
|||||||
|
|
||||||
this.title = bt.fullTitle;
|
this.title = bt.fullTitle;
|
||||||
|
|
||||||
this.$root.$emit('set-app-title', this.title);
|
this.$root.setAppTitle(this.title);
|
||||||
|
|
||||||
this.parsed = this.book.parsed;
|
this.parsed = this.book.parsed;
|
||||||
|
|
||||||
@@ -430,8 +484,18 @@ class TextPage extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setBackground() {
|
setBackground() {
|
||||||
this.background = `<div class="layout ${this.wallpaper}" style="width: ${this.realWidth}px; height: ${this.realHeight}px;` +
|
if (this.wallpaperIgnoreStatusBar) {
|
||||||
` background-color: ${this.backgroundColor}"></div>`;
|
this.background = `<div class="layout" style="width: ${this.realWidth}px; height: ${this.realHeight}px;` +
|
||||||
|
` background-color: ${this.backgroundColor}">` +
|
||||||
|
`<div class="layout ${this.wallpaper}" style="width: ${this.realWidth}px; height: ${this.scrollHeight}px; ` +
|
||||||
|
`top: ${(this.showStatusBar && this.statusBarTop ? this.statusBarHeight + 1 : 0)}px; position: relative;">` +
|
||||||
|
`</div>` +
|
||||||
|
`</div>`;
|
||||||
|
} else {
|
||||||
|
this.background = `<div class="layout ${this.wallpaper}" style="width: ${this.realWidth}px; height: ${this.realHeight}px;` +
|
||||||
|
` background-color: ${this.backgroundColor}"></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async onResize() {
|
async onResize() {
|
||||||
@@ -449,7 +513,7 @@ class TextPage extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get font() {
|
get font() {
|
||||||
return `${this.fontStyle} ${this.fontWeight} ${this.fontSize}px ${this.fontName}`;
|
return `${this.fontStyle} ${this.fontWeight} ${this.fontSize}px '${this.fontName}'`;
|
||||||
}
|
}
|
||||||
|
|
||||||
onPage1TransitionEnd() {
|
onPage1TransitionEnd() {
|
||||||
@@ -490,7 +554,7 @@ class TextPage extends Vue {
|
|||||||
|
|
||||||
async startTextScrolling() {
|
async startTextScrolling() {
|
||||||
if (this.doingScrolling || !this.book || !this.parsed.textLength || !this.linesDown || this.pageLineCount < 1 ||
|
if (this.doingScrolling || !this.book || !this.parsed.textLength || !this.linesDown || this.pageLineCount < 1 ||
|
||||||
this.linesDown.length <= this.pageLineCount) {
|
this.linesDown.length <= this.pageLineCount || this.dualPageMode) {
|
||||||
this.doStopScrolling();
|
this.doStopScrolling();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -608,6 +672,7 @@ class TextPage extends Vue {
|
|||||||
if (!this.pageChangeAnimation)
|
if (!this.pageChangeAnimation)
|
||||||
this.debouncedPrepareNextPage();
|
this.debouncedPrepareNextPage();
|
||||||
this.debouncedDrawStatusBar();
|
this.debouncedDrawStatusBar();
|
||||||
|
this.debouncedDrawPageDividerAndOrnament();
|
||||||
|
|
||||||
if (this.book && this.linesDown && this.linesDown.length < this.pageLineCount) {
|
if (this.book && this.linesDown && this.linesDown.length < this.pageLineCount) {
|
||||||
this.doEnd(true);
|
this.doEnd(true);
|
||||||
@@ -722,8 +787,24 @@ class TextPage extends Vue {
|
|||||||
message = this.statusBarMessage;
|
message = this.statusBarMessage;
|
||||||
if (!message)
|
if (!message)
|
||||||
message = this.title;
|
message = this.title;
|
||||||
|
|
||||||
|
//check image num
|
||||||
|
let imageNum = 0;
|
||||||
|
const len = (lines.length > 2 ? 2 : lines.length);
|
||||||
|
loop:
|
||||||
|
for (let j = 0; j < len; j++) {
|
||||||
|
const line = lines[j];
|
||||||
|
for (const part of line.parts) {
|
||||||
|
if (part.image) {
|
||||||
|
imageNum = part.image.num;
|
||||||
|
break loop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//drawing
|
||||||
this.statusBar = this.drawHelper.drawStatusBar(this.statusBarTop, this.statusBarHeight,
|
this.statusBar = this.drawHelper.drawStatusBar(this.statusBarTop, this.statusBarHeight,
|
||||||
lines[i].end, this.parsed.textLength, message);
|
lines[i].end, this.parsed.textLength, message, imageNum, this.parsed.images.length);
|
||||||
|
|
||||||
this.bookPosSeen = lines[i].end;
|
this.bookPosSeen = lines[i].end;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -731,6 +812,25 @@ class TextPage extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drawPageDividerAndOrnament() {
|
||||||
|
if (this.dualPageMode) {
|
||||||
|
this.pageDivider = `<div class="layout" style="width: ${this.realWidth}px; height: ${this.scrollHeight}px; ` +
|
||||||
|
`top: ${(this.showStatusBar && this.statusBarTop ? this.statusBarHeight + 1 : 0)}px; position: relative;">` +
|
||||||
|
`<div class="fit row justify-center items-center no-wrap">` +
|
||||||
|
`<div style="height: ${Math.round(this.scrollHeight*this.dualDivHeight/100)}px; width: ${this.dualDivWidth}px; ` +
|
||||||
|
`box-shadow: 0 0 ${this.dualDivShadowWidth}px ${this.dualDivRgbaColor}; ` +
|
||||||
|
`background-image: url("data:image/svg+xml;utf8,<svg width='100%' height='100%' xmlns='http://www.w3.org/2000/svg'>` +
|
||||||
|
`<line x1='${this.dualDivWidth/2}' y1='0' x2='${this.dualDivWidth/2}' y2='100%' stroke='${this.dualDivRgbaColor}' ` +
|
||||||
|
`stroke-width='${this.dualDivWidth}' stroke-dasharray='${this.dualDivStrokeFill} ${this.dualDivStrokeGap}'/>` +
|
||||||
|
`</svg>");">` +
|
||||||
|
`</div>` +
|
||||||
|
`</div>` +
|
||||||
|
`</div>`;
|
||||||
|
} else {
|
||||||
|
this.pageDivider = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
blinkCachedLoadMessage(state) {
|
blinkCachedLoadMessage(state) {
|
||||||
if (state === 'finish') {
|
if (state === 'finish') {
|
||||||
this.statusBarMessage = '';
|
this.statusBarMessage = '';
|
||||||
@@ -949,7 +1049,7 @@ class TextPage extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onTouchStart(event) {
|
onTouchStart(event) {
|
||||||
if (!this.$isMobileDevice)
|
if (!this.$root.isMobileDevice)
|
||||||
return;
|
return;
|
||||||
this.endClickRepeat();
|
this.endClickRepeat();
|
||||||
|
|
||||||
@@ -977,7 +1077,7 @@ class TextPage extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onTouchEnd(event) {
|
onTouchEnd(event) {
|
||||||
if (!this.$isMobileDevice)
|
if (!this.$root.isMobileDevice)
|
||||||
return;
|
return;
|
||||||
this.endClickRepeat();
|
this.endClickRepeat();
|
||||||
|
|
||||||
@@ -1013,13 +1113,13 @@ class TextPage extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onTouchCancel() {
|
onTouchCancel() {
|
||||||
if (!this.$isMobileDevice)
|
if (!this.$root.isMobileDevice)
|
||||||
return;
|
return;
|
||||||
this.endClickRepeat();
|
this.endClickRepeat();
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseDown(event) {
|
onMouseDown(event) {
|
||||||
if (this.$isMobileDevice)
|
if (this.$root.isMobileDevice)
|
||||||
return;
|
return;
|
||||||
this.endClickRepeat();
|
this.endClickRepeat();
|
||||||
if (event.button == 0) {
|
if (event.button == 0) {
|
||||||
@@ -1036,13 +1136,13 @@ class TextPage extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMouseUp() {
|
onMouseUp() {
|
||||||
if (this.$isMobileDevice)
|
if (this.$root.isMobileDevice)
|
||||||
return;
|
return;
|
||||||
this.endClickRepeat();
|
this.endClickRepeat();
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseWheel(event) {
|
onMouseWheel(event) {
|
||||||
if (this.$isMobileDevice)
|
if (this.$root.isMobileDevice)
|
||||||
return;
|
return;
|
||||||
if (event.deltaY > 0) {
|
if (event.deltaY > 0) {
|
||||||
this.doDown();
|
this.doDown();
|
||||||
@@ -1105,9 +1205,57 @@ class TextPage extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
copyText(event) {
|
||||||
|
//все это для того, чтобы правильно расставить переносы \n при копировании текста
|
||||||
|
//прямо с текущей страницы
|
||||||
|
|
||||||
|
//подготовка, вытаскиваем весь текст страницы
|
||||||
|
const lines = this.getLines(this.bookPos);
|
||||||
|
const decodedLines = [];
|
||||||
|
for (const line of lines.linesDown) {
|
||||||
|
let lineText = '';
|
||||||
|
for (const part of line.parts) {
|
||||||
|
lineText += part.text;
|
||||||
|
}
|
||||||
|
decodedLines.push({text: he.decode(lineText), first: line.first});
|
||||||
|
}
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
const findDecoded = (line) => {
|
||||||
|
for (let j = i; j < decodedLines.length; j++) {
|
||||||
|
const decoded = decodedLines[j];
|
||||||
|
if (decoded.text.indexOf(line) >= 0) {
|
||||||
|
i = j;
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selection = document.getSelection();
|
||||||
|
const splitted = selection.toString().split(/[\n\r]/);
|
||||||
|
|
||||||
|
let filtered = '';
|
||||||
|
//формируем filtered, учитывая переносы из decodedLines
|
||||||
|
for (const line of splitted) {
|
||||||
|
const found = findDecoded(line);
|
||||||
|
if (found && found.first) {
|
||||||
|
filtered += (filtered ? '\n' : '') + line;
|
||||||
|
} else {
|
||||||
|
filtered += (filtered ? '\r ' : '') + line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//маленькие хитрости, убираем переносы по слогам
|
||||||
|
filtered = filtered.replace(/-\r /g, '').replace(/\r /g, ' ');
|
||||||
|
|
||||||
|
event.clipboardData.setData('text/plain', filtered);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(TextPage);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -1145,60 +1293,3 @@ class TextPage extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style>
|
|
||||||
.paper1 {
|
|
||||||
background: url("images/paper1.jpg") center;
|
|
||||||
background-size: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.paper2 {
|
|
||||||
background: url("images/paper2.jpg") center;
|
|
||||||
background-size: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.paper3 {
|
|
||||||
background: url("images/paper3.jpg") center;
|
|
||||||
background-size: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.paper4 {
|
|
||||||
background: url("images/paper4.jpg") center;
|
|
||||||
background-size: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.paper5 {
|
|
||||||
background: url("images/paper5.jpg") center;
|
|
||||||
background-size: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.paper6 {
|
|
||||||
background: url("images/paper6.jpg") center;
|
|
||||||
background-size: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.paper7 {
|
|
||||||
background: url("images/paper7.jpg") center;
|
|
||||||
background-size: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.paper8 {
|
|
||||||
background: url("images/paper8.jpg") center;
|
|
||||||
background-size: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.paper9 {
|
|
||||||
background: url("images/paper9.jpg");
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes page1-animation-thaw {
|
|
||||||
0% { opacity: 0; }
|
|
||||||
100% { opacity: 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes page2-animation-thaw {
|
|
||||||
0% { opacity: 1; }
|
|
||||||
100% { opacity: 0; }
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|||||||
BIN
client/components/Reader/TextPage/images/paper10.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
client/components/Reader/TextPage/images/paper11.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
client/components/Reader/TextPage/images/paper12.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
client/components/Reader/TextPage/images/paper13.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
client/components/Reader/TextPage/images/paper14.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
client/components/Reader/TextPage/images/paper15.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
client/components/Reader/TextPage/images/paper16.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
client/components/Reader/TextPage/images/paper17.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
@@ -4,23 +4,55 @@ import * as utils from '../../../share/utils';
|
|||||||
|
|
||||||
const maxImageLineCount = 100;
|
const maxImageLineCount = 100;
|
||||||
|
|
||||||
|
// defaults
|
||||||
|
const defaultSettings = {
|
||||||
|
p: 30, //px, отступ параграфа
|
||||||
|
w: 500, //px, ширина страницы
|
||||||
|
|
||||||
|
font: '', //css описание шрифта
|
||||||
|
fontSize: 20, //px, размер шрифта
|
||||||
|
wordWrap: false, //перенос по слогам
|
||||||
|
cutEmptyParagraphs: false, //убирать пустые параграфы
|
||||||
|
addEmptyParagraphs: 0, //добавлять n пустых параграфов перед непустым
|
||||||
|
maxWordLength: 500, //px, максимальная длина слова без пробелов
|
||||||
|
lineHeight: 26, //px, высота строки
|
||||||
|
showImages: true, //показыввать изображения
|
||||||
|
showInlineImagesInCenter: true, //выносить изображения в центр, работает на этапе первичного парсинга (parse)
|
||||||
|
imageHeightLines: 100, //кол-во строк, максимальная высота изображения
|
||||||
|
imageFitWidth: true, //ширина изображения не более ширины страницы
|
||||||
|
dualPageMode: false, //двухстраничный режим
|
||||||
|
compactTextPerc: 0, //проценты, степень компактности текста
|
||||||
|
testWidth: 0, //ширина тестовой строки, пересчитывается извне при изменении шрифта браузером
|
||||||
|
isTesting: false, //тестовый режим
|
||||||
|
|
||||||
|
//заглушка, измеритель ширины текста
|
||||||
|
measureText: (text, style) => {// eslint-disable-line no-unused-vars
|
||||||
|
return text.length*20;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
//for splitToSlogi()
|
||||||
|
const glas = new Set(['а', 'А', 'о', 'О', 'и', 'И', 'е', 'Е', 'ё', 'Ё', 'э', 'Э', 'ы', 'Ы', 'у', 'У', 'ю', 'Ю', 'я', 'Я']);
|
||||||
|
const soglas = new Set([
|
||||||
|
'б', 'в', 'г', 'д', 'ж', 'з', 'й', 'к', 'л', 'м', 'н', 'п', 'р', 'с', 'т', 'ф', 'х', 'ц', 'ч', 'ш', 'щ',
|
||||||
|
'Б', 'В', 'Г', 'Д', 'Ж', 'З', 'Й', 'К', 'Л', 'М', 'Н', 'П', 'Р', 'С', 'Т', 'Ф', 'Х', 'Ч', 'Ц', 'Ш', 'Щ'
|
||||||
|
]);
|
||||||
|
const znak = new Set(['ь', 'Ь', 'ъ', 'Ъ', 'й', 'Й']);
|
||||||
|
const alpha = new Set([...glas, ...soglas, ...znak]);
|
||||||
|
|
||||||
export default class BookParser {
|
export default class BookParser {
|
||||||
constructor(settings) {
|
constructor(settings = {}) {
|
||||||
if (settings) {
|
this.sets = {};
|
||||||
this.showInlineImagesInCenter = settings.showInlineImagesInCenter;
|
|
||||||
}
|
|
||||||
|
|
||||||
// defaults
|
this.setSettings(defaultSettings);
|
||||||
this.p = 30;// px, отступ параграфа
|
this.setSettings(settings);
|
||||||
this.w = 300;// px, ширина страницы
|
|
||||||
this.wordWrap = false;// перенос по слогам
|
|
||||||
|
|
||||||
//заглушка
|
|
||||||
this.measureText = (text, style) => {// eslint-disable-line no-unused-vars
|
|
||||||
return text.length*20;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setSettings(settings = {}) {
|
||||||
|
this.sets = Object.assign({}, this.sets, settings);
|
||||||
|
this.measureText = this.sets.measureText;
|
||||||
|
}
|
||||||
|
|
||||||
async parse(data, callback) {
|
async parse(data, callback) {
|
||||||
if (!callback)
|
if (!callback)
|
||||||
callback = () => {};
|
callback = () => {};
|
||||||
@@ -54,12 +86,14 @@ export default class BookParser {
|
|||||||
|
|
||||||
//оглавление
|
//оглавление
|
||||||
this.contents = [];
|
this.contents = [];
|
||||||
|
this.images = [];
|
||||||
let curTitle = {paraIndex: -1, title: '', subtitles: []};
|
let curTitle = {paraIndex: -1, title: '', subtitles: []};
|
||||||
let curSubtitle = {paraIndex: -1, title: ''};
|
let curSubtitle = {paraIndex: -1, title: ''};
|
||||||
let inTitle = false;
|
let inTitle = false;
|
||||||
let inSubtitle = false;
|
let inSubtitle = false;
|
||||||
let sectionLevel = 0;
|
let sectionLevel = 0;
|
||||||
let bodyIndex = 0;
|
let bodyIndex = 0;
|
||||||
|
let imageNum = 0;
|
||||||
|
|
||||||
let paraIndex = -1;
|
let paraIndex = -1;
|
||||||
let paraOffset = 0;
|
let paraOffset = 0;
|
||||||
@@ -74,6 +108,7 @@ export default class BookParser {
|
|||||||
*/
|
*/
|
||||||
const getImageDimensions = (binaryId, binaryType, data) => {
|
const getImageDimensions = (binaryId, binaryType, data) => {
|
||||||
return new Promise ((resolve, reject) => { (async() => {
|
return new Promise ((resolve, reject) => { (async() => {
|
||||||
|
data = data.replace(/[\n\r\s]/g, '');
|
||||||
const i = new Image();
|
const i = new Image();
|
||||||
let resolved = false;
|
let resolved = false;
|
||||||
i.onload = () => {
|
i.onload = () => {
|
||||||
@@ -118,14 +153,67 @@ export default class BookParser {
|
|||||||
})().catch(reject); });
|
})().catch(reject); });
|
||||||
};
|
};
|
||||||
|
|
||||||
const newParagraph = (text, len, addIndex) => {
|
const correctCurrentPara = () => {
|
||||||
|
//коррекция текущего параграфа
|
||||||
|
if (paraIndex >= 0) {
|
||||||
|
const prevParaIndex = paraIndex;
|
||||||
|
let p = para[paraIndex];
|
||||||
|
paraOffset -= p.length;
|
||||||
|
|
||||||
|
//уберем пробелы с концов параграфа, минимум 1 пробел должен быть у пустого параграфа
|
||||||
|
let newParaText = p.text.trim();
|
||||||
|
newParaText = (newParaText.length ? newParaText : ' ');
|
||||||
|
const ldiff = p.text.length - newParaText.length;
|
||||||
|
if (ldiff != 0) {
|
||||||
|
p.text = newParaText;
|
||||||
|
p.length -= ldiff;
|
||||||
|
}
|
||||||
|
|
||||||
|
//удаление параграфов, которые содержат только разметку, такого не должно быть
|
||||||
|
if (!p.length) {
|
||||||
|
delete para[paraIndex];
|
||||||
|
paraIndex--;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//добавление пустых (не)видимых (addEmptyParagraphs) параграфов перед текущим непустым
|
||||||
|
if (p.text.trim() != '') {
|
||||||
|
for (let i = 0; i < 2; i++) {
|
||||||
|
para[paraIndex] = {
|
||||||
|
index: paraIndex,
|
||||||
|
offset: paraOffset,
|
||||||
|
length: 1,
|
||||||
|
text: ' ',
|
||||||
|
addIndex: i + 1,
|
||||||
|
};
|
||||||
|
paraIndex++;
|
||||||
|
paraOffset++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curTitle.paraIndex == prevParaIndex)
|
||||||
|
curTitle.paraIndex = paraIndex;
|
||||||
|
if (curSubtitle.paraIndex == prevParaIndex)
|
||||||
|
curSubtitle.paraIndex = paraIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.index = paraIndex;
|
||||||
|
p.offset = paraOffset;
|
||||||
|
para[paraIndex] = p;
|
||||||
|
paraOffset += p.length;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const newParagraph = (text = '', len = 0) => {
|
||||||
|
correctCurrentPara();
|
||||||
|
|
||||||
|
//новый параграф
|
||||||
paraIndex++;
|
paraIndex++;
|
||||||
let p = {
|
let p = {
|
||||||
index: paraIndex,
|
index: paraIndex,
|
||||||
offset: paraOffset,
|
offset: paraOffset,
|
||||||
length: len,
|
length: len,//длина текста внутри параграфа без учета длины разметки
|
||||||
text: text,
|
text: text,
|
||||||
addIndex: (addIndex ? addIndex : 0),
|
addIndex: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (inSubtitle) {
|
if (inSubtitle) {
|
||||||
@@ -135,53 +223,26 @@ export default class BookParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
para[paraIndex] = p;
|
para[paraIndex] = p;
|
||||||
paraOffset += p.length;
|
paraOffset += len;
|
||||||
};
|
};
|
||||||
|
|
||||||
const growParagraph = (text, len) => {
|
const growParagraph = (text, len) => {
|
||||||
if (paraIndex < 0) {
|
if (paraIndex < 0) {
|
||||||
newParagraph(' ', 1);
|
newParagraph();
|
||||||
growParagraph(text, len);
|
growParagraph(text, len);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const prevParaIndex = paraIndex;
|
|
||||||
let p = para[paraIndex];
|
|
||||||
paraOffset -= p.length;
|
|
||||||
//добавление пустых (addEmptyParagraphs) параграфов перед текущим
|
|
||||||
if (p.length == 1 && p.text[0] == ' ' && len > 0) {
|
|
||||||
paraIndex--;
|
|
||||||
for (let i = 0; i < 2; i++) {
|
|
||||||
newParagraph(' ', 1, i + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
paraIndex++;
|
|
||||||
p.index = paraIndex;
|
|
||||||
p.offset = paraOffset;
|
|
||||||
para[paraIndex] = p;
|
|
||||||
|
|
||||||
if (curTitle.paraIndex == prevParaIndex)
|
|
||||||
curTitle.paraIndex = paraIndex;
|
|
||||||
if (curSubtitle.paraIndex == prevParaIndex)
|
|
||||||
curSubtitle.paraIndex = paraIndex;
|
|
||||||
|
|
||||||
//уберем начальный пробел
|
|
||||||
p.length = 0;
|
|
||||||
p.text = p.text.substr(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
p.length += len;
|
|
||||||
p.text += text;
|
|
||||||
|
|
||||||
|
|
||||||
if (inSubtitle) {
|
if (inSubtitle) {
|
||||||
curSubtitle.title += text;
|
curSubtitle.title += text;
|
||||||
} else if (inTitle) {
|
} else if (inTitle) {
|
||||||
curTitle.title += text;
|
curTitle.title += text;
|
||||||
}
|
}
|
||||||
|
|
||||||
para[paraIndex] = p;
|
const p = para[paraIndex];
|
||||||
paraOffset += p.length;
|
p.length += len;
|
||||||
|
p.text += text;
|
||||||
|
paraOffset += len;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onStartNode = (elemName, tail) => {// eslint-disable-line no-unused-vars
|
const onStartNode = (elemName, tail) => {// eslint-disable-line no-unused-vars
|
||||||
@@ -194,7 +255,8 @@ 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' || binaryType == 'application/octet-stream')
|
binaryType = (binaryType == 'image/jpg' || binaryType == 'application/octet-stream' ? 'image/jpeg' : binaryType);
|
||||||
|
if (binaryType == 'image/jpeg' || binaryType == 'image/png')
|
||||||
binaryId = (attrs.id.value ? attrs.id.value : '');
|
binaryId = (attrs.id.value ? attrs.id.value : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,16 +264,31 @@ export default class BookParser {
|
|||||||
let attrs = sax.getAttrsSync(tail);
|
let attrs = sax.getAttrsSync(tail);
|
||||||
if (attrs.href && attrs.href.value) {
|
if (attrs.href && attrs.href.value) {
|
||||||
const href = attrs.href.value;
|
const href = attrs.href.value;
|
||||||
|
const alt = (attrs.alt && attrs.alt.value ? attrs.alt.value : '');
|
||||||
|
const {id, local} = this.imageHrefToId(href);
|
||||||
if (href[0] == '#') {//local
|
if (href[0] == '#') {//local
|
||||||
if (inPara && !this.showInlineImagesInCenter && !center)
|
imageNum++;
|
||||||
growParagraph(`<image-inline href="${href}"></image-inline>`, 0);
|
|
||||||
|
if (inPara && !this.sets.showInlineImagesInCenter && !center)
|
||||||
|
growParagraph(`<image-inline href="${href}" num="${imageNum}"></image-inline>`, 0);
|
||||||
else
|
else
|
||||||
newParagraph(`<image href="${href}">${' '.repeat(maxImageLineCount)}</image>`, maxImageLineCount);
|
newParagraph(`<image href="${href}" num="${imageNum}">${' '.repeat(maxImageLineCount)}</image>`, maxImageLineCount);
|
||||||
if (inPara && this.showInlineImagesInCenter)
|
|
||||||
newParagraph(' ', 1);
|
this.images.push({paraIndex, num: imageNum, id, local, alt});
|
||||||
|
|
||||||
|
if (inPara && this.sets.showInlineImagesInCenter)
|
||||||
|
newParagraph();
|
||||||
} else {//external
|
} else {//external
|
||||||
dimPromises.push(getExternalImageDimensions(href));
|
imageNum++;
|
||||||
newParagraph(`<image href="${href}">${' '.repeat(maxImageLineCount)}</image>`, maxImageLineCount);
|
|
||||||
|
if (!this.sets.isTesting) {
|
||||||
|
dimPromises.push(getExternalImageDimensions(href));
|
||||||
|
} else {
|
||||||
|
dimPromises.push(this.sets.getExternalImageDimensions(this, href));
|
||||||
|
}
|
||||||
|
newParagraph(`<image href="${href}" num="${imageNum}">${' '.repeat(maxImageLineCount)}</image>`, maxImageLineCount);
|
||||||
|
|
||||||
|
this.images.push({paraIndex, num: imageNum, id, local, alt});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,25 +327,25 @@ export default class BookParser {
|
|||||||
newParagraph(`<emphasis><space w="1">${a}</space></emphasis>`, a.length);
|
newParagraph(`<emphasis><space w="1">${a}</space></emphasis>`, a.length);
|
||||||
});
|
});
|
||||||
if (ann.length)
|
if (ann.length)
|
||||||
newParagraph(' ', 1);
|
newParagraph();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFirstBody && fb2.sequence && fb2.sequence.length) {
|
if (isFirstBody && fb2.sequence && fb2.sequence.length) {
|
||||||
const bt = utils.getBookTitle(fb2);
|
const bt = utils.getBookTitle(fb2);
|
||||||
if (bt.sequence) {
|
if (bt.sequence) {
|
||||||
newParagraph(bt.sequence, bt.sequence.length);
|
newParagraph(bt.sequence, bt.sequence.length);
|
||||||
newParagraph(' ', 1);
|
newParagraph();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isFirstBody)
|
if (!isFirstBody)
|
||||||
newParagraph(' ', 1);
|
newParagraph();
|
||||||
isFirstBody = false;
|
isFirstBody = false;
|
||||||
bodyIndex++;
|
bodyIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag == 'title') {
|
if (tag == 'title') {
|
||||||
newParagraph(' ', 1);
|
newParagraph();
|
||||||
isFirstTitlePara = true;
|
isFirstTitlePara = true;
|
||||||
bold = true;
|
bold = true;
|
||||||
center = true;
|
center = true;
|
||||||
@@ -280,18 +357,18 @@ export default class BookParser {
|
|||||||
|
|
||||||
if (tag == 'section') {
|
if (tag == 'section') {
|
||||||
if (!isFirstSection)
|
if (!isFirstSection)
|
||||||
newParagraph(' ', 1);
|
newParagraph();
|
||||||
isFirstSection = false;
|
isFirstSection = false;
|
||||||
sectionLevel++;
|
sectionLevel++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag == 'emphasis' || tag == 'strong') {
|
if (tag == 'emphasis' || tag == 'strong' || tag == 'sup' || tag == 'sub') {
|
||||||
growParagraph(`<${tag}>`, 0);
|
growParagraph(`<${tag}>`, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((tag == 'p' || tag == 'empty-line' || tag == 'v')) {
|
if ((tag == 'p' || tag == 'empty-line' || tag == 'v')) {
|
||||||
if (!(tag == 'p' && isFirstTitlePara))
|
if (!(tag == 'p' && isFirstTitlePara))
|
||||||
newParagraph(' ', 1);
|
newParagraph();
|
||||||
if (tag == 'p') {
|
if (tag == 'p') {
|
||||||
inPara = true;
|
inPara = true;
|
||||||
isFirstTitlePara = false;
|
isFirstTitlePara = false;
|
||||||
@@ -299,11 +376,16 @@ export default class BookParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (tag == 'subtitle') {
|
if (tag == 'subtitle') {
|
||||||
newParagraph(' ', 1);
|
newParagraph();
|
||||||
isFirstTitlePara = true;
|
isFirstTitlePara = true;
|
||||||
bold = true;
|
bold = true;
|
||||||
center = true;
|
center = true;
|
||||||
|
|
||||||
|
if (curTitle.paraIndex < 0) {
|
||||||
|
curTitle = {paraIndex, title: 'Оглавление', inset: sectionLevel, bodyIndex, subtitles: []};
|
||||||
|
this.contents.push(curTitle);
|
||||||
|
}
|
||||||
|
|
||||||
inSubtitle = true;
|
inSubtitle = true;
|
||||||
curSubtitle = {paraIndex, inset: sectionLevel, title: ''};
|
curSubtitle = {paraIndex, inset: sectionLevel, title: ''};
|
||||||
curTitle.subtitles.push(curSubtitle);
|
curTitle.subtitles.push(curSubtitle);
|
||||||
@@ -315,11 +397,12 @@ export default class BookParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (tag == 'poem') {
|
if (tag == 'poem') {
|
||||||
newParagraph(' ', 1);
|
newParagraph();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag == 'text-author') {
|
if (tag == 'text-author') {
|
||||||
newParagraph(' ', 1);
|
newParagraph();
|
||||||
|
bold = true;
|
||||||
space += 1;
|
space += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -343,7 +426,7 @@ export default class BookParser {
|
|||||||
sectionLevel--;
|
sectionLevel--;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag == 'emphasis' || tag == 'strong') {
|
if (tag == 'emphasis' || tag == 'strong' || tag == 'sup' || tag == 'sub') {
|
||||||
growParagraph(`</${tag}>`, 0);
|
growParagraph(`</${tag}>`, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,15 +444,15 @@ export default class BookParser {
|
|||||||
if (tag == 'epigraph' || tag == 'annotation') {
|
if (tag == 'epigraph' || tag == 'annotation') {
|
||||||
italic = false;
|
italic = false;
|
||||||
space -= 1;
|
space -= 1;
|
||||||
if (tag == 'annotation')
|
newParagraph();
|
||||||
newParagraph(' ', 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag == 'stanza') {
|
if (tag == 'stanza') {
|
||||||
newParagraph(' ', 1);
|
newParagraph();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag == 'text-author') {
|
if (tag == 'text-author') {
|
||||||
|
bold = false;
|
||||||
space -= 1;
|
space -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -386,17 +469,14 @@ export default class BookParser {
|
|||||||
|
|
||||||
const onTextNode = (text) => {// eslint-disable-line no-unused-vars
|
const onTextNode = (text) => {// eslint-disable-line no-unused-vars
|
||||||
text = he.decode(text);
|
text = he.decode(text);
|
||||||
text = text.replace(/>/g, '>');
|
text = text.replace(/>/g, '>').replace(/</g, '<').replace(/[\t\n\r\xa0]/g, ' ');
|
||||||
text = text.replace(/</g, '<');
|
|
||||||
|
|
||||||
if (text && text.trim() == '')
|
if (text && text.trim() == '')
|
||||||
text = (text.indexOf(' ') >= 0 ? ' ' : '');
|
text = ' ';
|
||||||
|
|
||||||
if (!text)
|
if (!text)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
text = text.replace(/[\t\n\r\xa0]/g, ' ');
|
|
||||||
|
|
||||||
const authorLength = (fb2.author && fb2.author.length ? fb2.author.length : 0);
|
const authorLength = (fb2.author && fb2.author.length ? fb2.author.length : 0);
|
||||||
switch (path) {
|
switch (path) {
|
||||||
case '/fictionbook/description/title-info/author/first-name':
|
case '/fictionbook/description/title-info/author/first-name':
|
||||||
@@ -434,24 +514,31 @@ export default class BookParser {
|
|||||||
fb2.annotation += text;
|
fb2.annotation += text;
|
||||||
}
|
}
|
||||||
|
|
||||||
let tOpen = (center ? '<center>' : '');
|
if (binaryId) {
|
||||||
tOpen += (bold ? '<strong>' : '');
|
if (!this.sets.isTesting) {
|
||||||
tOpen += (italic ? '<emphasis>' : '');
|
dimPromises.push(getImageDimensions(binaryId, binaryType, text));
|
||||||
tOpen += (space ? `<space w="${space}">` : '');
|
} else {
|
||||||
let tClose = (space ? '</space>' : '');
|
dimPromises.push(this.sets.getImageDimensions(this, binaryId, binaryType, text));
|
||||||
tClose += (italic ? '</emphasis>' : '');
|
}
|
||||||
tClose += (bold ? '</strong>' : '');
|
}
|
||||||
tClose += (center ? '</center>' : '');
|
|
||||||
|
|
||||||
if (path.indexOf('/fictionbook/body/title') == 0 ||
|
if (path.indexOf('/fictionbook/body/title') == 0 ||
|
||||||
path.indexOf('/fictionbook/body/section') == 0 ||
|
path.indexOf('/fictionbook/body/section') == 0 ||
|
||||||
path.indexOf('/fictionbook/body/epigraph') == 0
|
path.indexOf('/fictionbook/body/epigraph') == 0
|
||||||
) {
|
) {
|
||||||
growParagraph(`${tOpen}${text}${tClose}`, text.length);
|
let tOpen = (center ? '<center>' : '');
|
||||||
}
|
tOpen += (bold ? '<strong>' : '');
|
||||||
|
tOpen += (italic ? '<emphasis>' : '');
|
||||||
|
tOpen += (space ? `<space w="${space}">` : '');
|
||||||
|
let tClose = (space ? '</space>' : '');
|
||||||
|
tClose += (italic ? '</emphasis>' : '');
|
||||||
|
tClose += (bold ? '</strong>' : '');
|
||||||
|
tClose += (center ? '</center>' : '');
|
||||||
|
|
||||||
if (binaryId) {
|
if (text != ' ')
|
||||||
dimPromises.push(getImageDimensions(binaryId, binaryType, text));
|
growParagraph(`${tOpen}${text}${tClose}`, text.length);
|
||||||
|
else
|
||||||
|
growParagraph(' ', 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -463,6 +550,7 @@ export default class BookParser {
|
|||||||
await sax.parse(data, {
|
await sax.parse(data, {
|
||||||
onStartNode, onEndNode, onTextNode, onProgress
|
onStartNode, onEndNode, onTextNode, onProgress
|
||||||
});
|
});
|
||||||
|
correctCurrentPara();
|
||||||
|
|
||||||
if (dimPromises.length) {
|
if (dimPromises.length) {
|
||||||
try {
|
try {
|
||||||
@@ -483,6 +571,15 @@ export default class BookParser {
|
|||||||
return {fb2};
|
return {fb2};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
imageHrefToId(id) {
|
||||||
|
let local = false;
|
||||||
|
if (id[0] == '#') {
|
||||||
|
id = id.substr(1);
|
||||||
|
local = true;
|
||||||
|
}
|
||||||
|
return {id, local};
|
||||||
|
}
|
||||||
|
|
||||||
findParaIndex(bookPos) {
|
findParaIndex(bookPos) {
|
||||||
let result = undefined;
|
let result = undefined;
|
||||||
//дихотомия
|
//дихотомия
|
||||||
@@ -507,16 +604,26 @@ export default class BookParser {
|
|||||||
|
|
||||||
splitToStyle(s) {
|
splitToStyle(s) {
|
||||||
let result = [];/*array of {
|
let result = [];/*array of {
|
||||||
style: {bold: Boolean, italic: Boolean, center: Boolean, space: Number},
|
style: {bold: Boolean, italic: Boolean, sup: Boolean, sub: Boolean, center: Boolean, space: Number},
|
||||||
image: {local: Boolean, inline: Boolean, id: String},
|
image: {local: Boolean, inline: Boolean, id: String},
|
||||||
text: String,
|
text: String,
|
||||||
}*/
|
}*/
|
||||||
let style = {};
|
let style = {};
|
||||||
let image = {};
|
let image = {};
|
||||||
|
|
||||||
|
//оптимизация по памяти
|
||||||
|
const copyStyle = (s) => {
|
||||||
|
const r = {};
|
||||||
|
for (const prop in s) {
|
||||||
|
if (s[prop])
|
||||||
|
r[prop] = s[prop];
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
};
|
||||||
|
|
||||||
const onTextNode = async(text) => {// eslint-disable-line no-unused-vars
|
const onTextNode = async(text) => {// eslint-disable-line no-unused-vars
|
||||||
result.push({
|
result.push({
|
||||||
style: Object.assign({}, style),
|
style: copyStyle(style),
|
||||||
image,
|
image,
|
||||||
text
|
text
|
||||||
});
|
});
|
||||||
@@ -530,6 +637,12 @@ export default class BookParser {
|
|||||||
case 'emphasis':
|
case 'emphasis':
|
||||||
style.italic = true;
|
style.italic = true;
|
||||||
break;
|
break;
|
||||||
|
case 'sup':
|
||||||
|
style.sup = true;
|
||||||
|
break;
|
||||||
|
case 'sub':
|
||||||
|
style.sub = true;
|
||||||
|
break;
|
||||||
case 'center':
|
case 'center':
|
||||||
style.center = true;
|
style.center = true;
|
||||||
break;
|
break;
|
||||||
@@ -542,28 +655,21 @@ export default class BookParser {
|
|||||||
case 'image': {
|
case 'image': {
|
||||||
let attrs = sax.getAttrsSync(tail);
|
let attrs = sax.getAttrsSync(tail);
|
||||||
if (attrs.href && attrs.href.value) {
|
if (attrs.href && attrs.href.value) {
|
||||||
let id = attrs.href.value;
|
image = this.imageHrefToId(attrs.href.value);
|
||||||
let local = false;
|
image.inline = false;
|
||||||
if (id[0] == '#') {
|
image.num = (attrs.num && attrs.num.value ? attrs.num.value : 0);
|
||||||
id = id.substr(1);
|
|
||||||
local = true;
|
|
||||||
}
|
|
||||||
image = {local, inline: false, id};
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'image-inline': {
|
case 'image-inline': {
|
||||||
let attrs = sax.getAttrsSync(tail);
|
let attrs = sax.getAttrsSync(tail);
|
||||||
if (attrs.href && attrs.href.value) {
|
if (attrs.href && attrs.href.value) {
|
||||||
let id = attrs.href.value;
|
const img = this.imageHrefToId(attrs.href.value);
|
||||||
let local = false;
|
img.inline = true;
|
||||||
if (id[0] == '#') {
|
img.num = (attrs.num && attrs.num.value ? attrs.num.value : 0);
|
||||||
id = id.substr(1);
|
|
||||||
local = true;
|
|
||||||
}
|
|
||||||
result.push({
|
result.push({
|
||||||
style: Object.assign({}, style),
|
style: copyStyle(style),
|
||||||
image: {local, inline: true, id},
|
image: img,
|
||||||
text: ''
|
text: ''
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -580,6 +686,12 @@ export default class BookParser {
|
|||||||
case 'emphasis':
|
case 'emphasis':
|
||||||
style.italic = false;
|
style.italic = false;
|
||||||
break;
|
break;
|
||||||
|
case 'sup':
|
||||||
|
style.sup = false;
|
||||||
|
break;
|
||||||
|
case 'sub':
|
||||||
|
style.sub = false;
|
||||||
|
break;
|
||||||
case 'center':
|
case 'center':
|
||||||
style.center = false;
|
style.center = false;
|
||||||
break;
|
break;
|
||||||
@@ -599,7 +711,7 @@ export default class BookParser {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//длинные слова (или белиберду без пробелов) тоже разобьем
|
//длинные слова (или белиберду без пробелов) тоже разобьем
|
||||||
const maxWordLength = this.maxWordLength;
|
const maxWordLength = this.sets.maxWordLength;
|
||||||
const parts = result;
|
const parts = result;
|
||||||
result = [];
|
result = [];
|
||||||
for (const part of parts) {
|
for (const part of parts) {
|
||||||
@@ -612,7 +724,7 @@ export default class BookParser {
|
|||||||
spaceIndex = i;
|
spaceIndex = i;
|
||||||
|
|
||||||
if (i - spaceIndex >= maxWordLength && i < p.text.length - 1 &&
|
if (i - spaceIndex >= maxWordLength && i < p.text.length - 1 &&
|
||||||
this.measureText(p.text.substr(spaceIndex + 1, i - spaceIndex), p.style) >= this.w - this.p) {
|
this.measureText(p.text.substr(spaceIndex + 1, i - spaceIndex), p.style) >= this.sets.w - this.sets.p) {
|
||||||
result.push({style: p.style, image: p.image, text: p.text.substr(0, i + 1)});
|
result.push({style: p.style, image: p.image, text: p.text.substr(0, i + 1)});
|
||||||
p = {style: p.style, image: p.image, text: p.text.substr(i + 1)};
|
p = {style: p.style, image: p.image, text: p.text.substr(i + 1)};
|
||||||
spaceIndex = -1;
|
spaceIndex = -1;
|
||||||
@@ -630,86 +742,87 @@ export default class BookParser {
|
|||||||
splitToSlogi(word) {
|
splitToSlogi(word) {
|
||||||
let result = [];
|
let result = [];
|
||||||
|
|
||||||
const glas = new Set(['а', 'А', 'о', 'О', 'и', 'И', 'е', 'Е', 'ё', 'Ё', 'э', 'Э', 'ы', 'Ы', 'у', 'У', 'ю', 'Ю', 'я', 'Я']);
|
|
||||||
const soglas = new Set([
|
|
||||||
'б', 'в', 'г', 'д', 'ж', 'з', 'й', 'к', 'л', 'м', 'н', 'п', 'р', 'с', 'т', 'ф', 'х', 'ц', 'ч', 'ш', 'щ',
|
|
||||||
'Б', 'В', 'Г', 'Д', 'Ж', 'З', 'Й', 'К', 'Л', 'М', 'Н', 'П', 'Р', 'С', 'Т', 'Ф', 'Х', 'Ч', 'Ц', 'Ш', 'Щ'
|
|
||||||
]);
|
|
||||||
const znak = new Set(['ь', 'Ь', 'ъ', 'Ъ', 'й', 'Й']);
|
|
||||||
const alpha = new Set([...glas, ...soglas, ...znak]);
|
|
||||||
|
|
||||||
let slog = '';
|
|
||||||
let slogLen = 0;
|
|
||||||
const len = word.length;
|
const len = word.length;
|
||||||
word += ' ';
|
if (len > 3) {
|
||||||
for (let i = 0; i < len; i++) {
|
let slog = '';
|
||||||
slog += word[i];
|
let slogLen = 0;
|
||||||
if (alpha.has(word[i]))
|
word += ' ';
|
||||||
slogLen++;
|
for (let i = 0; i < len; i++) {
|
||||||
|
slog += word[i];
|
||||||
|
if (alpha.has(word[i]))
|
||||||
|
slogLen++;
|
||||||
|
|
||||||
if (slogLen > 1 && i < len - 2 && (
|
if (slogLen > 1 && i < len - 2 && (
|
||||||
//гласная, а следом не 2 согласные буквы
|
//гласная, а следом не 2 согласные буквы
|
||||||
(glas.has(word[i]) && !(soglas.has(word[i + 1]) &&
|
(glas.has(word[i]) && !( soglas.has(word[i + 1]) && soglas.has(word[i + 2]) ) &&
|
||||||
soglas.has(word[i + 2])) && alpha.has(word[i + 1]) && alpha.has(word[i + 2])
|
alpha.has(word[i + 1]) && alpha.has(word[i + 2])
|
||||||
) ||
|
) ||
|
||||||
//предыдущая не согласная буква, текущая согласная, а следом согласная и согласная|гласная буквы
|
//предыдущая не согласная буква, текущая согласная, а следом согласная и согласная|гласная буквы
|
||||||
(alpha.has(word[i - 1]) && !soglas.has(word[i - 1]) &&
|
(alpha.has(word[i - 1]) && !soglas.has(word[i - 1]) && soglas.has(word[i]) && soglas.has(word[i + 1]) &&
|
||||||
soglas.has(word[i]) && soglas.has(word[i + 1]) &&
|
( glas.has(word[i + 2]) || soglas.has(word[i + 2]) ) &&
|
||||||
(glas.has(word[i + 2]) || soglas.has(word[i + 2])) &&
|
alpha.has(word[i + 1]) && alpha.has(word[i + 2])
|
||||||
alpha.has(word[i + 1]) && alpha.has(word[i + 2])
|
) ||
|
||||||
) ||
|
//мягкий или твердый знак или Й
|
||||||
//мягкий или твердый знак или Й
|
(znak.has(word[i]) && alpha.has(word[i + 1]) && alpha.has(word[i + 2])) ||
|
||||||
(znak.has(word[i]) && alpha.has(word[i + 1]) && alpha.has(word[i + 2])) ||
|
(word[i] == '-')
|
||||||
(word[i] == '-')
|
) &&
|
||||||
) &&
|
//нельзя оставлять окончания на ь, ъ, й
|
||||||
//нельзя оставлять окончания на ь, ъ, й
|
!(znak.has(word[i + 2]) && !alpha.has(word[i + 3]))
|
||||||
!(znak.has(word[i + 2]) && !alpha.has(word[i + 3]))
|
|
||||||
|
|
||||||
) {
|
) {
|
||||||
result.push(slog);
|
result.push(slog);
|
||||||
slog = '';
|
slog = '';
|
||||||
slogLen = 0;
|
slogLen = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (slog)
|
||||||
|
result.push(slog);
|
||||||
|
} else {
|
||||||
|
result.push(word);
|
||||||
}
|
}
|
||||||
if (slog)
|
|
||||||
result.push(slog);
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
parsePara(paraIndex) {
|
parsePara(paraIndex) {
|
||||||
const para = this.para[paraIndex];
|
const para = this.para[paraIndex];
|
||||||
|
const s = this.sets;
|
||||||
|
|
||||||
|
//перераспарсиваем только при изменении одного из параметров
|
||||||
if (!this.force &&
|
if (!this.force &&
|
||||||
para.parsed &&
|
para.parsed &&
|
||||||
para.parsed.testWidth === this.testWidth &&
|
para.parsed.p === s.p &&
|
||||||
para.parsed.w === this.w &&
|
para.parsed.w === s.w &&
|
||||||
para.parsed.p === this.p &&
|
para.parsed.font === s.font &&
|
||||||
para.parsed.wordWrap === this.wordWrap &&
|
para.parsed.fontSize === s.fontSize &&
|
||||||
para.parsed.maxWordLength === this.maxWordLength &&
|
para.parsed.wordWrap === s.wordWrap &&
|
||||||
para.parsed.font === this.font &&
|
para.parsed.cutEmptyParagraphs === s.cutEmptyParagraphs &&
|
||||||
para.parsed.cutEmptyParagraphs === this.cutEmptyParagraphs &&
|
para.parsed.addEmptyParagraphs === s.addEmptyParagraphs &&
|
||||||
para.parsed.addEmptyParagraphs === this.addEmptyParagraphs &&
|
para.parsed.maxWordLength === s.maxWordLength &&
|
||||||
para.parsed.showImages === this.showImages &&
|
para.parsed.lineHeight === s.lineHeight &&
|
||||||
para.parsed.imageHeightLines === this.imageHeightLines &&
|
para.parsed.showImages === s.showImages &&
|
||||||
para.parsed.imageFitWidth === this.imageFitWidth &&
|
para.parsed.imageHeightLines === s.imageHeightLines &&
|
||||||
para.parsed.compactTextPerc === this.compactTextPerc
|
para.parsed.imageFitWidth === (s.imageFitWidth || s.dualPageMode) &&
|
||||||
|
para.parsed.compactTextPerc === s.compactTextPerc &&
|
||||||
|
para.parsed.testWidth === s.testWidth
|
||||||
)
|
)
|
||||||
return para.parsed;
|
return para.parsed;
|
||||||
|
|
||||||
const parsed = {
|
const parsed = {
|
||||||
testWidth: this.testWidth,
|
p: s.p,
|
||||||
w: this.w,
|
w: s.w,
|
||||||
p: this.p,
|
font: s.font,
|
||||||
wordWrap: this.wordWrap,
|
fontSize: s.fontSize,
|
||||||
maxWordLength: this.maxWordLength,
|
wordWrap: s.wordWrap,
|
||||||
font: this.font,
|
cutEmptyParagraphs: s.cutEmptyParagraphs,
|
||||||
cutEmptyParagraphs: this.cutEmptyParagraphs,
|
addEmptyParagraphs: s.addEmptyParagraphs,
|
||||||
addEmptyParagraphs: this.addEmptyParagraphs,
|
maxWordLength: s.maxWordLength,
|
||||||
showImages: this.showImages,
|
lineHeight: s.lineHeight,
|
||||||
imageHeightLines: this.imageHeightLines,
|
showImages: s.showImages,
|
||||||
imageFitWidth: this.imageFitWidth,
|
imageHeightLines: s.imageHeightLines,
|
||||||
compactTextPerc: this.compactTextPerc,
|
imageFitWidth: (s.imageFitWidth || s.dualPageMode),
|
||||||
|
compactTextPerc: s.compactTextPerc,
|
||||||
|
testWidth: s.testWidth,
|
||||||
visible: true, //вычисляется позже
|
visible: true, //вычисляется позже
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -741,7 +854,7 @@ export default class BookParser {
|
|||||||
let ofs = 0;//смещение от начала параграфа para.offset
|
let ofs = 0;//смещение от начала параграфа para.offset
|
||||||
let imgW = 0;
|
let imgW = 0;
|
||||||
let imageInPara = false;
|
let imageInPara = false;
|
||||||
const compactWidth = this.measureText('W', {})*this.compactTextPerc/100;
|
const compactWidth = this.measureText('W', {})*parsed.compactTextPerc/100;
|
||||||
// тут начинается самый замес, перенос по слогам и стилизация, а также изображения
|
// тут начинается самый замес, перенос по слогам и стилизация, а также изображения
|
||||||
for (const part of parts) {
|
for (const part of parts) {
|
||||||
style = part.style;
|
style = part.style;
|
||||||
@@ -754,14 +867,14 @@ export default class BookParser {
|
|||||||
if (!bin)
|
if (!bin)
|
||||||
bin = {h: 1, w: 1};
|
bin = {h: 1, w: 1};
|
||||||
|
|
||||||
let lineCount = this.imageHeightLines;
|
let lineCount = parsed.imageHeightLines;
|
||||||
let c = Math.ceil(bin.h/this.lineHeight);
|
let c = Math.ceil(bin.h/parsed.lineHeight);
|
||||||
|
|
||||||
const maxH = lineCount*this.lineHeight;
|
const maxH = lineCount*parsed.lineHeight;
|
||||||
let maxH2 = maxH;
|
let maxH2 = maxH;
|
||||||
if (this.imageFitWidth && bin.w > this.w) {
|
if (parsed.imageFitWidth && bin.w > parsed.w) {
|
||||||
maxH2 = bin.h*this.w/bin.w;
|
maxH2 = bin.h*parsed.w/bin.w;
|
||||||
c = Math.ceil(maxH2/this.lineHeight);
|
c = Math.ceil(maxH2/parsed.lineHeight);
|
||||||
}
|
}
|
||||||
lineCount = (c < lineCount ? c : lineCount);
|
lineCount = (c < lineCount ? c : lineCount);
|
||||||
|
|
||||||
@@ -784,6 +897,7 @@ export default class BookParser {
|
|||||||
paraIndex,
|
paraIndex,
|
||||||
w: imageWidth,
|
w: imageWidth,
|
||||||
h: imageHeight,
|
h: imageHeight,
|
||||||
|
num: part.image.num
|
||||||
}});
|
}});
|
||||||
lines.push(line);
|
lines.push(line);
|
||||||
line = {begin: line.end + 1, parts: []};
|
line = {begin: line.end + 1, parts: []};
|
||||||
@@ -794,19 +908,19 @@ export default class BookParser {
|
|||||||
line.last = true;
|
line.last = true;
|
||||||
line.parts.push({style, text: ' ',
|
line.parts.push({style, text: ' ',
|
||||||
image: {local: part.image.local, inline: false, id: part.image.id,
|
image: {local: part.image.local, inline: false, id: part.image.id,
|
||||||
imageLine: i, lineCount, paraIndex, w: imageWidth, h: imageHeight}
|
imageLine: i, lineCount, paraIndex, w: imageWidth, h: imageHeight, num: part.image.num}
|
||||||
});
|
});
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (part.image.id && part.image.inline && this.showImages) {
|
if (part.image.id && part.image.inline && parsed.showImages) {
|
||||||
const bin = this.binary[part.image.id];
|
const bin = this.binary[part.image.id];
|
||||||
if (bin) {
|
if (bin) {
|
||||||
let imgH = (bin.h > this.fontSize ? this.fontSize : bin.h);
|
let imgH = (bin.h > parsed.fontSize ? parsed.fontSize : bin.h);
|
||||||
imgW += bin.w*imgH/bin.h;
|
imgW += bin.w*imgH/bin.h;
|
||||||
line.parts.push({style, text: '',
|
line.parts.push({style, text: '',
|
||||||
image: {local: part.image.local, inline: true, id: part.image.id}});
|
image: {local: part.image.local, inline: true, id: part.image.id, num: part.image.num}});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -918,11 +1032,11 @@ export default class BookParser {
|
|||||||
|
|
||||||
//parsed.visible
|
//parsed.visible
|
||||||
if (imageInPara) {
|
if (imageInPara) {
|
||||||
parsed.visible = this.showImages;
|
parsed.visible = parsed.showImages;
|
||||||
} else {
|
} else {
|
||||||
parsed.visible = !(
|
parsed.visible = !(
|
||||||
(para.addIndex > this.addEmptyParagraphs) ||
|
(para.addIndex > parsed.addEmptyParagraphs) ||
|
||||||
(para.addIndex == 0 && this.cutEmptyParagraphs && paragraphText.trim() == '')
|
(para.addIndex == 0 && parsed.cutEmptyParagraphs && paragraphText.trim() == '')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,48 +63,6 @@ class BookManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.cleanRecentBooks();
|
await this.cleanRecentBooks();
|
||||||
|
|
||||||
//TODO: убрать после 06.2021, когда bmRecentStoreOld устареет
|
|
||||||
{
|
|
||||||
await this.convertFileToDiskPrefix();
|
|
||||||
if (this.recentRev > 10)
|
|
||||||
await bmRecentStoreOld.clear();
|
|
||||||
}
|
|
||||||
} else {//TODO: убрать после 06.2021, когда bmRecentStoreOld устареет
|
|
||||||
this.recentLast = await bmRecentStoreOld.getItem('recent-last');
|
|
||||||
if (this.recentLast) {
|
|
||||||
this.recent[this.recentLast.key] = this.recentLast;
|
|
||||||
const meta = await bmMetaStore.getItem(`bmMeta-${this.recentLast.key}`);
|
|
||||||
if (_.isObject(meta)) {
|
|
||||||
this.books[meta.key] = meta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let key = null;
|
|
||||||
const len = await bmRecentStoreOld.length();
|
|
||||||
for (let i = len - 1; i >= 0; i--) {
|
|
||||||
key = await bmRecentStoreOld.key(i);
|
|
||||||
if (key) {
|
|
||||||
let r = await bmRecentStoreOld.getItem(key);
|
|
||||||
if (_.isObject(r) && r.key) {
|
|
||||||
this.recent[r.key] = r;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await bmRecentStoreOld.removeItem(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//размножение для дебага
|
|
||||||
/*if (key) {
|
|
||||||
for (let i = 0; i < 1000; i++) {
|
|
||||||
const k = this.keyFromUrl(i.toString());
|
|
||||||
this.recent[k] = Object.assign({}, _.cloneDeep(this.recent[key]), {key: k, touchTime: Date.now() - 1000000, url: utils.randomHexString(300)});
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
await bmRecentStoreNew.setItem('recent', this.recent);
|
|
||||||
this.recentRev = 1;
|
|
||||||
await bmRecentStoreNew.setItem('rev', this.recentRev);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.recentChanged = true;
|
this.recentChanged = true;
|
||||||
@@ -169,7 +127,7 @@ class BookManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async deflateWithProgress(data, callback) {
|
async deflateWithProgress(data, callback) {
|
||||||
const chunkSize = 128*1024;
|
const chunkSize = 512*1024;
|
||||||
const deflator = new utils.pako.Deflate({level: 5});
|
const deflator = new utils.pako.Deflate({level: 5});
|
||||||
|
|
||||||
let chunkTotal = 1 + Math.floor(data.length/chunkSize);
|
let chunkTotal = 1 + Math.floor(data.length/chunkSize);
|
||||||
@@ -203,7 +161,7 @@ class BookManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async inflateWithProgress(data, callback) {
|
async inflateWithProgress(data, callback) {
|
||||||
const chunkSize = 64*1024;
|
const chunkSize = 512*1024;
|
||||||
const inflator = new utils.pako.Inflate({to: 'string'});
|
const inflator = new utils.pako.Inflate({to: 'string'});
|
||||||
|
|
||||||
let chunkTotal = 1 + Math.floor(data.length/chunkSize);
|
let chunkTotal = 1 + Math.floor(data.length/chunkSize);
|
||||||
@@ -374,7 +332,7 @@ class BookManager {
|
|||||||
//-- recent --------------------------------------------------------------
|
//-- recent --------------------------------------------------------------
|
||||||
async recentSetItem(item = null, skipCheck = false) {
|
async recentSetItem(item = null, skipCheck = false) {
|
||||||
const rev = await bmRecentStoreNew.getItem('rev');
|
const rev = await bmRecentStoreNew.getItem('rev');
|
||||||
if (rev != this.recentRev && !skipCheck) {
|
if (rev != this.recentRev && !skipCheck) {//если изменение произошло в другой вкладке барузера
|
||||||
const newRecent = await bmRecentStoreNew.getItem('recent');
|
const newRecent = await bmRecentStoreNew.getItem('recent');
|
||||||
Object.assign(this.recent, newRecent);
|
Object.assign(this.recent, newRecent);
|
||||||
this.recentItem = await bmRecentStoreNew.getItem('recent-item');
|
this.recentItem = await bmRecentStoreNew.getItem('recent-item');
|
||||||
@@ -410,16 +368,12 @@ class BookManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async setRecentBook(value) {
|
async setRecentBook(value) {
|
||||||
const result = this.metaOnly(value);
|
let result = this.metaOnly(value);
|
||||||
result.touchTime = Date.now();
|
result.touchTime = Date.now();
|
||||||
result.deleted = 0;
|
result.deleted = 0;
|
||||||
|
|
||||||
if (this.recent[result.key] && this.recent[result.key].deleted) {
|
if (this.recent[result.key]) {
|
||||||
//восстановим из небытия пользовательские данные
|
result = Object.assign({}, this.recent[result.key], result);
|
||||||
if (!result.bookPos)
|
|
||||||
result.bookPos = this.recent[result.key].bookPos;
|
|
||||||
if (!result.bookPosSeen)
|
|
||||||
result.bookPosSeen = this.recent[result.key].bookPosSeen;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.recentSetLastKey(result.key);
|
await this.recentSetLastKey(result.key);
|
||||||
@@ -459,33 +413,6 @@ class BookManager {
|
|||||||
return isDel;
|
return isDel;
|
||||||
}
|
}
|
||||||
|
|
||||||
async convertFileToDiskPrefix() {
|
|
||||||
let isConverted = false;
|
|
||||||
|
|
||||||
const newRecent = {};
|
|
||||||
for (let key of Object.keys(this.recent)) {
|
|
||||||
let newKey = key;
|
|
||||||
let newUrl = this.recent[key].url;
|
|
||||||
|
|
||||||
if (newKey.indexOf('66696c65') == 0) {
|
|
||||||
newKey = newKey.replace(/^66696c65/, '6469736b');
|
|
||||||
if (newUrl)
|
|
||||||
newUrl = newUrl.replace(/^file/, 'disk');
|
|
||||||
isConverted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
newRecent[newKey] = this.recent[key];
|
|
||||||
newRecent[newKey].key = newKey;
|
|
||||||
if (newUrl)
|
|
||||||
newRecent[newKey].url = newUrl;
|
|
||||||
}
|
|
||||||
if (isConverted) {
|
|
||||||
this.recent = newRecent;
|
|
||||||
await this.recentSetItem(null, true);
|
|
||||||
}
|
|
||||||
return isConverted;
|
|
||||||
}
|
|
||||||
|
|
||||||
mostRecentBook() {
|
mostRecentBook() {
|
||||||
if (this.recentLastKey) {
|
if (this.recentLastKey) {
|
||||||
return this.recent[this.recentLastKey];
|
return this.recent[this.recentLastKey];
|
||||||
|
|||||||
40
client/components/Reader/share/wallpaperStorage.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import localForage from 'localforage';
|
||||||
|
//import _ from 'lodash';
|
||||||
|
|
||||||
|
const wpStore = localForage.createInstance({
|
||||||
|
name: 'wallpaperStorage'
|
||||||
|
});
|
||||||
|
|
||||||
|
class WallpaperStorage {
|
||||||
|
constructor() {
|
||||||
|
this.cachedKeys = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
this.cachedKeys = await wpStore.keys();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLength() {
|
||||||
|
return await wpStore.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
async setData(key, data) {
|
||||||
|
await wpStore.setItem(key, data);
|
||||||
|
this.cachedKeys = await wpStore.keys();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getData(key) {
|
||||||
|
return await wpStore.getItem(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeData(key) {
|
||||||
|
await wpStore.removeItem(key);
|
||||||
|
this.cachedKeys = await wpStore.keys();
|
||||||
|
}
|
||||||
|
|
||||||
|
keyExists(key) {//не асинхронная
|
||||||
|
return this.cachedKeys.includes(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new WallpaperStorage();
|
||||||
@@ -1,7 +1,146 @@
|
|||||||
export const versionHistory = [
|
export const versionHistory = [
|
||||||
{
|
{
|
||||||
|
version: '0.11.6',
|
||||||
|
releaseDate: '2022-07-02',
|
||||||
|
showUntil: '2022-07-01',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>улучшено копирование текста прямо со страницы, для переводчиков</li>
|
||||||
|
<li>актуализация используемых пакетов</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
version: '0.11.5',
|
||||||
|
releaseDate: '2022-04-15',
|
||||||
|
showUntil: '2022-04-14',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>небольшие дополнения интерфейса</li>
|
||||||
|
<li>исправления багов</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
version: '0.11.1',
|
||||||
|
releaseDate: '2021-12-03',
|
||||||
|
showUntil: '2021-12-02',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>переход на JembaDb вместо SQLite</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
version: '0.11.0',
|
||||||
|
releaseDate: '2021-11-18',
|
||||||
|
showUntil: '2021-11-17',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>переход на Vue 3</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
version: '0.10.3',
|
||||||
|
releaseDate: '2021-10-24',
|
||||||
|
showUntil: '2021-10-23',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>исправления багов</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
version: '0.10.2',
|
||||||
|
releaseDate: '2021-10-19',
|
||||||
|
showUntil: '2021-10-18',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>актуализация версий пакетов и стека используемых технологий</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
version: '0.10.1',
|
||||||
|
releaseDate: '2021-10-10',
|
||||||
|
showUntil: '2021-10-09',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>исправления багов</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
version: '0.10.0',
|
||||||
|
releaseDate: '2021-02-09',
|
||||||
|
showUntil: '2021-02-16',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>добавлен двухстраничный режим</li>
|
||||||
|
<li>в настройки добавлены все кириллические веб-шрифты от google</li>
|
||||||
|
<li>в настройки добавлена возможность загрузки пользовательских обоев (пока без синхронизации)</li>
|
||||||
|
<li>немного улучшен парсинг fb2</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
version: '0.9.12',
|
||||||
|
releaseDate: '2020-12-18',
|
||||||
|
showUntil: '2020-12-17',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>добавлена вкладка "Изображения" в окно оглавления</li>
|
||||||
|
<li>настройки конвертирования вынесены в отдельную вкладку</li>
|
||||||
|
<li>добавлена кнопка для быстрого доступа к настройкам конвертирования</li>
|
||||||
|
<li>улучшения работы конвертеров</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
version: '0.9.11',
|
||||||
|
releaseDate: '2020-12-09',
|
||||||
|
showUntil: '2020-12-08',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>оптимизации, улучшения работы конвертеров</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
version: '0.9.10',
|
||||||
|
releaseDate: '2020-12-03',
|
||||||
showUntil: '2020-12-10',
|
showUntil: '2020-12-10',
|
||||||
header: '0.9.10 (2020-12-03)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
@@ -9,69 +148,81 @@ export const versionHistory = [
|
|||||||
<li>добавлена поддержка Rar-архивов</li>
|
<li>добавлена поддержка Rar-архивов</li>
|
||||||
<li>исправления багов</li>
|
<li>исправления багов</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.9.9',
|
||||||
|
releaseDate: '2020-11-21',
|
||||||
showUntil: '2020-11-20',
|
showUntil: '2020-11-20',
|
||||||
header: '0.9.9 (2020-11-21)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
<li>оптимизации, исправления багов</li>
|
<li>оптимизации, исправления багов</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.9.8',
|
||||||
|
releaseDate: '2020-11-13',
|
||||||
showUntil: '2020-11-12',
|
showUntil: '2020-11-12',
|
||||||
header: '0.9.8 (2020-11-13)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
<li>добавлено окно "Оглавление/закладки"</li>
|
<li>добавлено окно "Оглавление/закладки"</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.9.7',
|
||||||
|
releaseDate: '2020-11-12',
|
||||||
showUntil: '2020-11-11',
|
showUntil: '2020-11-11',
|
||||||
header: '0.9.7 (2020-11-12)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
<li>исправления багов</li>
|
<li>исправления багов</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.9.6',
|
||||||
|
releaseDate: '2020-11-06',
|
||||||
showUntil: '2020-11-05',
|
showUntil: '2020-11-05',
|
||||||
header: '0.9.6 (2020-11-06)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
<li>завершена работа над новым окном "Библиотека"</li>
|
<li>завершена работа над новым окном "Библиотека"</li>
|
||||||
<li>исправления багов</li>
|
<li>исправления багов</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.9.5',
|
||||||
|
releaseDate: '2020-11-01',
|
||||||
showUntil: '2020-10-31',
|
showUntil: '2020-10-31',
|
||||||
header: '0.9.5 (2020-11-01)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
<li>на панель инструментов добавлена новая кнопка "Обновить с разбиением на параграфы"</li>
|
<li>на панель инструментов добавлена новая кнопка "Обновить с разбиением на параграфы"</li>
|
||||||
<li>исправления багов</li>
|
<li>исправления багов</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.9.4',
|
||||||
|
releaseDate: '2020-10-29',
|
||||||
showUntil: '2020-10-28',
|
showUntil: '2020-10-28',
|
||||||
header: '0.9.4 (2020-10-29)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
@@ -79,23 +230,27 @@ export const versionHistory = [
|
|||||||
<li>для liberama.top добавлено новое окно: "Библиотека"</li>
|
<li>для liberama.top добавлено новое окно: "Библиотека"</li>
|
||||||
<li>исправления багов</li>
|
<li>исправления багов</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.9.3',
|
||||||
|
releaseDate: '2020-05-21',
|
||||||
showUntil: '2020-05-20',
|
showUntil: '2020-05-20',
|
||||||
header: '0.9.3 (2020-05-21)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
<li>исправления багов</li>
|
<li>исправления багов</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.9.2',
|
||||||
|
releaseDate: '2020-03-15',
|
||||||
showUntil: '2020-04-25',
|
showUntil: '2020-04-25',
|
||||||
header: '0.9.2 (2020-03-15)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
@@ -103,119 +258,139 @@ export const versionHistory = [
|
|||||||
<li>переход на Service Worker вместо AppCache для автономного режима работы</li>
|
<li>переход на Service Worker вместо AppCache для автономного режима работы</li>
|
||||||
<li>исправления багов</li>
|
<li>исправления багов</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.9.1',
|
||||||
|
releaseDate: '2020-03-03',
|
||||||
showUntil: '2020-03-02',
|
showUntil: '2020-03-02',
|
||||||
header: '0.9.1 (2020-03-03)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
<li>улучшение работы серверной части</li>
|
<li>улучшение работы серверной части</li>
|
||||||
<li>незначительные изменения интерфейса</li>
|
<li>незначительные изменения интерфейса</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.9.0',
|
||||||
|
releaseDate: '2020-02-26',
|
||||||
showUntil: '2020-02-25',
|
showUntil: '2020-02-25',
|
||||||
header: '0.9.0 (2020-02-26)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
<li>переход на UI-фреймфорк Quasar</li>
|
<li>переход на UI-фреймфорк Quasar</li>
|
||||||
<li>незначительные изменения интерфейса</li>
|
<li>незначительные изменения интерфейса</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.8.4',
|
||||||
|
releaseDate: '2020-02-06',
|
||||||
showUntil: '2020-02-05',
|
showUntil: '2020-02-05',
|
||||||
header: '0.8.4 (2020-02-06)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
<li>добавлен paypal-адрес для пожертвований</li>
|
<li>добавлен paypal-адрес для пожертвований</li>
|
||||||
<li>исправления багов</li>
|
<li>исправления багов</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.8.3',
|
||||||
|
releaseDate: '2020-01-28',
|
||||||
showUntil: '2020-01-27',
|
showUntil: '2020-01-27',
|
||||||
header: '0.8.3 (2020-01-28)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
<li>добавлено всплывающее окно с акцией "Оплатим хостинг вместе"</li>
|
<li>добавлено всплывающее окно с акцией "Оплатим хостинг вместе"</li>
|
||||||
<li>внутренние оптимизации</li>
|
<li>внутренние оптимизации</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.8.2',
|
||||||
|
releaseDate: '2020-01-20',
|
||||||
showUntil: '2020-01-19',
|
showUntil: '2020-01-19',
|
||||||
header: '0.8.2 (2020-01-20)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
<li>внутренние оптимизации</li>
|
<li>внутренние оптимизации</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.8.1',
|
||||||
|
releaseDate: '2020-01-07',
|
||||||
showUntil: '2020-01-06',
|
showUntil: '2020-01-06',
|
||||||
header: '0.8.1 (2020-01-07)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
<li>добавлена частичная поддержка формата FB3</li>
|
<li>добавлена частичная поддержка формата FB3</li>
|
||||||
<li>исправлен баг "Request path contains unescaped characters"</li>
|
<li>исправлен баг "Request path contains unescaped characters"</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.8.0',
|
||||||
|
releaseDate: '2020-01-02',
|
||||||
showUntil: '2020-01-05',
|
showUntil: '2020-01-05',
|
||||||
header: '0.8.0 (2020-01-02)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
<li>окончательный переход на https</li>
|
<li>окончательный переход на https</li>
|
||||||
<li>код проекта теперь Open Source: <a href="https://github.com/bookpauk/liberama" target="_blank">https://github.com/bookpauk/liberama</a></li>
|
<li>код проекта теперь Open Source: <a href="https://github.com/bookpauk/liberama" target="_blank">https://github.com/bookpauk/liberama</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.7.9',
|
||||||
|
releaseDate: '2019-11-27',
|
||||||
showUntil: '2019-11-26',
|
showUntil: '2019-11-26',
|
||||||
header: '0.7.9 (2019-11-27)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
<li>добавлен неубираемый баннер для http-версии о переходе на httpS</li>
|
<li>добавлен неубираемый баннер для http-версии о переходе на httpS</li>
|
||||||
<li>исправления багов</li>
|
<li>исправления багов</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.7.8',
|
||||||
|
releaseDate: '2019-11-25',
|
||||||
showUntil: '2019-11-24',
|
showUntil: '2019-11-24',
|
||||||
header: '0.7.8 (2019-11-25)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
<li>улучшение html-фильтров для сайтов</li>
|
<li>улучшение html-фильтров для сайтов</li>
|
||||||
<li>исправления багов</li>
|
<li>исправления багов</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.7.7',
|
||||||
|
releaseDate: '2019-11-06',
|
||||||
showUntil: '2019-11-10',
|
showUntil: '2019-11-10',
|
||||||
header: '0.7.7 (2019-11-06)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
@@ -227,34 +402,40 @@ export const versionHistory = [
|
|||||||
<li style="list-style-type: square">от центра влево: уменьшить скорость скроллинга</li>
|
<li style="list-style-type: square">от центра влево: уменьшить скорость скроллинга</li>
|
||||||
</ul>
|
</ul>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.7.6',
|
||||||
|
releaseDate: '2019-10-30',
|
||||||
showUntil: '2019-10-29',
|
showUntil: '2019-10-29',
|
||||||
header: '0.7.6 (2019-10-30)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
<li>исправления багов</li>
|
<li>исправления багов</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.7.5',
|
||||||
|
releaseDate: '2019-10-22',
|
||||||
showUntil: '2019-10-21',
|
showUntil: '2019-10-21',
|
||||||
header: '0.7.5 (2019-10-22)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
<li>исправления багов</li>
|
<li>исправления багов</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.7.3',
|
||||||
|
releaseDate: '2019-10-18',
|
||||||
showUntil: '2019-10-17',
|
showUntil: '2019-10-17',
|
||||||
header: '0.7.3 (2019-10-18)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
@@ -263,12 +444,14 @@ export const versionHistory = [
|
|||||||
<li>добавлен параметр "Включить html-фильтр для сайтов" в раздел "Вид"->"Текст" в настройках</li>
|
<li>добавлен параметр "Включить html-фильтр для сайтов" в раздел "Вид"->"Текст" в настройках</li>
|
||||||
<li>исправления багов</li>
|
<li>исправления багов</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.7.1',
|
||||||
|
releaseDate: '2019-09-20',
|
||||||
showUntil: '2019-09-19',
|
showUntil: '2019-09-19',
|
||||||
header: '0.7.1 (2019-09-20)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
@@ -276,12 +459,14 @@ export const versionHistory = [
|
|||||||
<li>на панель управления добавлена кнопка "Автономный режим"</li>
|
<li>на панель управления добавлена кнопка "Автономный режим"</li>
|
||||||
<li>актуализирована справка</li>
|
<li>актуализирована справка</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.7.0',
|
||||||
|
releaseDate: '2019-09-07',
|
||||||
showUntil: '2019-10-01',
|
showUntil: '2019-10-01',
|
||||||
header: '0.7.0 (2019-09-07)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
@@ -292,23 +477,27 @@ export const versionHistory = [
|
|||||||
<li>немного улучшен внешний вид и управление на смартфонах</li>
|
<li>немного улучшен внешний вид и управление на смартфонах</li>
|
||||||
<li>добавлен параметр "Компактность" в раздел "Вид"->"Текст" в настройках</li>
|
<li>добавлен параметр "Компактность" в раздел "Вид"->"Текст" в настройках</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.6.10',
|
||||||
|
releaseDate: '2019-07-21',
|
||||||
showUntil: '2019-07-20',
|
showUntil: '2019-07-20',
|
||||||
header: '0.6.10 (2019-07-21)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
<li>исправления багов</li>
|
<li>исправления багов</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.6.9',
|
||||||
|
releaseDate: '2019-06-23',
|
||||||
showUntil: '2019-06-22',
|
showUntil: '2019-06-22',
|
||||||
header: '0.6.9 (2019-06-23)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
@@ -319,12 +508,14 @@ export const versionHistory = [
|
|||||||
<li>улучшены прогрессбары</li>
|
<li>улучшены прогрессбары</li>
|
||||||
<li>исправления недочетов, небольшие оптимизации</li>
|
<li>исправления недочетов, небольшие оптимизации</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.6.7',
|
||||||
|
releaseDate: '2019-05-30',
|
||||||
showUntil: '2019-06-05',
|
showUntil: '2019-06-05',
|
||||||
header: '0.6.7 (2019-05-30)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
@@ -337,36 +528,42 @@ export const versionHistory = [
|
|||||||
<li>добавлен GET-параметр вида "/reader?__pp=50.5&url=..." для указания позиции в книге в процентах</li>
|
<li>добавлен GET-параметр вида "/reader?__pp=50.5&url=..." для указания позиции в книге в процентах</li>
|
||||||
<li>исправления багов и недочетов</li>
|
<li>исправления багов и недочетов</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.6.6',
|
||||||
|
releaseDate: '2019-03-29',
|
||||||
showUntil: '2019-03-29',
|
showUntil: '2019-03-29',
|
||||||
header: '0.6.6 (2019-03-29)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
<li>в справку добавлено описание настройки браузеров для автономной работы читалки (без доступа к интернету)</li>
|
<li>в справку добавлено описание настройки браузеров для автономной работы читалки (без доступа к интернету)</li>
|
||||||
<li>оптимизации процесса синхронизации, внутренние переделки</li>
|
<li>оптимизации процесса синхронизации, внутренние переделки</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.6.4',
|
||||||
|
releaseDate: '2019-03-24',
|
||||||
showUntil: '2019-03-24',
|
showUntil: '2019-03-24',
|
||||||
header: '0.6.4 (2019-03-24)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
<li>исправления багов, оптимизации</li>
|
<li>исправления багов, оптимизации</li>
|
||||||
<li>добавлена возможность синхронизации данных между устройствами</li>
|
<li>добавлена возможность синхронизации данных между устройствами</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.5.4',
|
||||||
|
releaseDate: '2019-03-04',
|
||||||
showUntil: '2019-03-04',
|
showUntil: '2019-03-04',
|
||||||
header: '0.5.4 (2019-03-04)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
@@ -375,12 +572,14 @@ export const versionHistory = [
|
|||||||
<li>(0.4.2) фильтр для СИ больше не вырезает изображения</li>
|
<li>(0.4.2) фильтр для СИ больше не вырезает изображения</li>
|
||||||
<li>(0.4.0) добавлено отображение картинок в fb2</li>
|
<li>(0.4.0) добавлено отображение картинок в fb2</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.3.0',
|
||||||
|
releaseDate: '2019-02-17',
|
||||||
showUntil: '2019-02-17',
|
showUntil: '2019-02-17',
|
||||||
header: '0.3.0 (2019-02-17)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
@@ -388,12 +587,14 @@ export const versionHistory = [
|
|||||||
<li>улучшено распознавание текста</li>
|
<li>улучшено распознавание текста</li>
|
||||||
<li>изменена верстка страницы - убрано позиционирование каждого слова</li>
|
<li>изменена верстка страницы - убрано позиционирование каждого слова</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.1.7',
|
||||||
|
releaseDate: '2019-02-14',
|
||||||
showUntil: '2019-02-14',
|
showUntil: '2019-02-14',
|
||||||
header: '0.1.7 (2019-02-14)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
@@ -403,17 +604,20 @@ export const versionHistory = [
|
|||||||
<li>добавлена возможность сброса настроек</li>
|
<li>добавлена возможность сброса настроек</li>
|
||||||
<li>убран автоматический редирект на последнюю загруженную книгу, если не задан url в маршруте</li>
|
<li>убран автоматический редирект на последнюю загруженную книгу, если не задан url в маршруте</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
version: '0.1.0',
|
||||||
|
releaseDate: '2019-02-12',
|
||||||
showUntil: '2019-02-12',
|
showUntil: '2019-02-12',
|
||||||
header: '0.1.0 (2019-02-12)',
|
|
||||||
content:
|
content:
|
||||||
`
|
`
|
||||||
<ul>
|
<ul>
|
||||||
<li>первый деплой проекта, длительность разработки - 2 месяца</li>
|
<li>первый деплой проекта, длительность разработки - 2 месяца</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -6,15 +6,14 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
|
|
||||||
export default @Component({
|
class Settings {
|
||||||
})
|
|
||||||
class Settings extends Vue {
|
|
||||||
created() {
|
created() {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(Settings);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -6,15 +6,14 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
|
|
||||||
export default @Component({
|
class Sources {
|
||||||
})
|
|
||||||
class Sources extends Vue {
|
|
||||||
created() {
|
created() {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(Sources);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<q-dialog v-model="active" no-route-dismiss>
|
<q-dialog v-model="active" no-route-dismiss @show="onShow" @hide="onHide">
|
||||||
<div class="column bg-white no-wrap">
|
<div class="column bg-white no-wrap">
|
||||||
<div class="header row">
|
<div class="header row">
|
||||||
<div class="caption col row items-center q-ml-md">
|
<div class="caption col row items-center q-ml-md">
|
||||||
<slot name="header"></slot>
|
<slot name="header"></slot>
|
||||||
</div>
|
</div>
|
||||||
<div class="close-icon column justify-center items-center">
|
<div class="close-icon column justify-center items-center">
|
||||||
<q-btn flat round dense v-close-popup>
|
<q-btn v-close-popup flat round dense>
|
||||||
<q-icon name="la la-times" size="18px"></q-icon>
|
<q-icon name="la la-times" size="18px"></q-icon>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
@@ -25,26 +25,42 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
import * as utils from '../../share/utils';
|
||||||
|
|
||||||
const DialogProps = Vue.extend({
|
class Dialog {
|
||||||
props: {
|
_props = {
|
||||||
value: Boolean,
|
modelValue: Boolean,
|
||||||
}
|
};
|
||||||
});
|
|
||||||
|
shown = false;
|
||||||
|
|
||||||
export default @Component({
|
|
||||||
})
|
|
||||||
class Dialog extends DialogProps {
|
|
||||||
get active() {
|
get active() {
|
||||||
return this.value;
|
return this.modelValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
set active(value) {
|
set active(value) {
|
||||||
this.$emit('input', value);
|
this.$emit('update:modelValue', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
onShow() {
|
||||||
|
this.shown = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onHide() {
|
||||||
|
this.shown = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async waitShown() {
|
||||||
|
let i = 100;
|
||||||
|
while (!this.shown && i > 0) {
|
||||||
|
await utils.sleep(10);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(Dialog);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,9 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
|
|
||||||
export default @Component({
|
class Notify {
|
||||||
})
|
|
||||||
class Notify extends Vue {
|
|
||||||
notify(opts) {
|
notify(opts) {
|
||||||
let {
|
let {
|
||||||
caption = null,
|
caption = null,
|
||||||
@@ -55,5 +52,7 @@ class Notify extends Vue {
|
|||||||
this.notify(Object.assign({color: 'info', icon: 'la la-bell', message, caption}, options));
|
this.notify(Object.assign({color: 'info', icon: 'la la-bell', message, caption}, options));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(Notify);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<q-input outlined dense
|
<q-input
|
||||||
v-model="filteredValue"
|
v-model="filteredValue"
|
||||||
|
outlined dense
|
||||||
input-style="text-align: center"
|
input-style="text-align: center"
|
||||||
class="no-mp"
|
class="no-mp"
|
||||||
:class="(error ? 'error' : '')"
|
:class="(error ? 'error' : '')"
|
||||||
:disable="disable"
|
:disable="disable"
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<template v-slot:prepend>
|
<template #prepend>
|
||||||
<q-icon :class="(validate(value - step) ? '' : 'disable')"
|
<q-icon
|
||||||
|
v-ripple="validate(modelValue - step)"
|
||||||
|
:class="(validate(modelValue - step) ? '' : 'disable')"
|
||||||
name="la la-minus-circle"
|
name="la la-minus-circle"
|
||||||
class="button"
|
class="button"
|
||||||
v-ripple="validate(value - step)"
|
|
||||||
@click="minus"
|
@click="minus"
|
||||||
@mousedown.prevent.stop="onMouseDown($event, 'minus')"
|
@mousedown.prevent.stop="onMouseDown($event, 'minus')"
|
||||||
@mouseup.prevent.stop="onMouseUp"
|
@mouseup.prevent.stop="onMouseUp"
|
||||||
@@ -21,11 +23,12 @@
|
|||||||
@touchcancel.prevent.stop="onTouchEnd"
|
@touchcancel.prevent.stop="onTouchEnd"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:append>
|
<template #append>
|
||||||
<q-icon :class="(validate(value + step) ? '' : 'disable')"
|
<q-icon
|
||||||
|
v-ripple="validate(modelValue + step)"
|
||||||
|
:class="(validate(modelValue + step) ? '' : 'disable')"
|
||||||
name="la la-plus-circle"
|
name="la la-plus-circle"
|
||||||
class="button"
|
class="button"
|
||||||
v-ripple="validate(value + step)"
|
|
||||||
@click="plus"
|
@click="plus"
|
||||||
@mousedown.prevent.stop="onMouseDown($event, 'plus')"
|
@mousedown.prevent.stop="onMouseDown($event, 'plus')"
|
||||||
@mouseup.prevent.stop="onMouseUp"
|
@mouseup.prevent.stop="onMouseUp"
|
||||||
@@ -40,43 +43,41 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
|
|
||||||
import * as utils from '../../share/utils';
|
import * as utils from '../../share/utils';
|
||||||
|
|
||||||
const NumInputProps = Vue.extend({
|
const componentOptions = {
|
||||||
props: {
|
watch: {
|
||||||
value: Number,
|
filteredValue: function(newValue) {
|
||||||
|
if (this.validate(newValue)) {
|
||||||
|
this.error = false;
|
||||||
|
this.$emit('update:modelValue', this.string2number(newValue));
|
||||||
|
} else {
|
||||||
|
this.error = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modelValue: function(newValue) {
|
||||||
|
this.filteredValue = newValue;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
class NumInput {
|
||||||
|
_options = componentOptions;
|
||||||
|
_props = {
|
||||||
|
modelValue: Number,
|
||||||
min: { type: Number, default: -Number.MAX_VALUE },
|
min: { type: Number, default: -Number.MAX_VALUE },
|
||||||
max: { type: Number, default: Number.MAX_VALUE },
|
max: { type: Number, default: Number.MAX_VALUE },
|
||||||
step: { type: Number, default: 1 },
|
step: { type: Number, default: 1 },
|
||||||
digits: { type: Number, default: 0 },
|
digits: { type: Number, default: 0 },
|
||||||
disable: Boolean
|
disable: Boolean
|
||||||
}
|
};
|
||||||
});
|
|
||||||
|
|
||||||
export default @Component({
|
|
||||||
watch: {
|
|
||||||
filteredValue: function(newValue) {
|
|
||||||
if (this.validate(newValue)) {
|
|
||||||
this.error = false;
|
|
||||||
this.$emit('input', this.string2number(newValue));
|
|
||||||
} else {
|
|
||||||
this.error = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
value: function(newValue) {
|
|
||||||
this.filteredValue = newValue;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
class NumInput extends NumInputProps {
|
|
||||||
filteredValue = 0;
|
filteredValue = 0;
|
||||||
error = false;
|
error = false;
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.filteredValue = this.value;
|
this.filteredValue = this.modelValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
string2number(value) {
|
string2number(value) {
|
||||||
@@ -95,13 +96,13 @@ class NumInput extends NumInputProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
plus() {
|
plus() {
|
||||||
const newValue = this.value + this.step;
|
const newValue = this.modelValue + this.step;
|
||||||
if (this.validate(newValue))
|
if (this.validate(newValue))
|
||||||
this.filteredValue = newValue;
|
this.filteredValue = newValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
minus() {
|
minus() {
|
||||||
const newValue = this.value - this.step;
|
const newValue = this.modelValue - this.step;
|
||||||
if (this.validate(newValue))
|
if (this.validate(newValue))
|
||||||
this.filteredValue = newValue;
|
this.filteredValue = newValue;
|
||||||
}
|
}
|
||||||
@@ -136,7 +137,7 @@ class NumInput extends NumInputProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onTouchStart(event, way) {
|
onTouchStart(event, way) {
|
||||||
if (!this.$isMobileDevice)
|
if (!this.$root.isMobileDevice)
|
||||||
return;
|
return;
|
||||||
if (event.touches.length == 1) {
|
if (event.touches.length == 1) {
|
||||||
this.inTouch = true;
|
this.inTouch = true;
|
||||||
@@ -145,12 +146,14 @@ class NumInput extends NumInputProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onTouchEnd() {
|
onTouchEnd() {
|
||||||
if (!this.$isMobileDevice)
|
if (!this.$root.isMobileDevice)
|
||||||
return;
|
return;
|
||||||
this.inTouch = false;
|
this.inTouch = false;
|
||||||
this.onMouseUp();
|
this.onMouseUp();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(NumInput);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<q-dialog ref="dialog" v-model="active" @show="onShow" @hide="onHide" no-route-dismiss>
|
<q-dialog ref="dialog" v-model="active" no-route-dismiss @show="onShow" @hide="onHide">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
|
||||||
<!--------------------------------------------------->
|
<!--------------------------------------------------->
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<div v-html="caption"></div>
|
<div v-html="caption"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="close-icon column justify-center items-center">
|
<div class="close-icon column justify-center items-center">
|
||||||
<q-btn flat round dense v-close-popup>
|
<q-btn v-close-popup flat round dense>
|
||||||
<q-icon name="la la-times" size="18px"></q-icon>
|
<q-icon name="la la-times" size="18px"></q-icon>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
@@ -21,7 +21,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="buttons row justify-end q-pa-md">
|
<div class="buttons row justify-end q-pa-md">
|
||||||
<q-btn class="q-px-md" dense no-caps @click="okClick">OK</q-btn>
|
<q-btn class="q-px-md" dense no-caps @click="okClick">
|
||||||
|
OK
|
||||||
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -33,7 +35,7 @@
|
|||||||
<div v-html="caption"></div>
|
<div v-html="caption"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="close-icon column justify-center items-center">
|
<div class="close-icon column justify-center items-center">
|
||||||
<q-btn flat round dense v-close-popup>
|
<q-btn v-close-popup flat round dense>
|
||||||
<q-icon name="la la-times" size="18px"></q-icon>
|
<q-icon name="la la-times" size="18px"></q-icon>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
@@ -44,8 +46,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="buttons row justify-end q-pa-md">
|
<div class="buttons row justify-end q-pa-md">
|
||||||
<q-btn class="q-px-md q-ml-sm" dense no-caps v-close-popup>Отмена</q-btn>
|
<q-btn v-close-popup class="q-px-md q-ml-sm" dense no-caps>
|
||||||
<q-btn class="q-px-md q-ml-sm" color="primary" dense no-caps @click="okClick">OK</q-btn>
|
Отмена
|
||||||
|
</q-btn>
|
||||||
|
<q-btn class="q-px-md q-ml-sm" color="primary" dense no-caps @click="okClick">
|
||||||
|
OK
|
||||||
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -57,7 +63,7 @@
|
|||||||
<div v-html="caption"></div>
|
<div v-html="caption"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="close-icon column justify-center items-center">
|
<div class="close-icon column justify-center items-center">
|
||||||
<q-btn flat round dense v-close-popup>
|
<q-btn v-close-popup flat round dense>
|
||||||
<q-icon name="la la-times" size="18px"></q-icon>
|
<q-icon name="la la-times" size="18px"></q-icon>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
@@ -65,13 +71,19 @@
|
|||||||
|
|
||||||
<div class="q-mx-md">
|
<div class="q-mx-md">
|
||||||
<div v-html="message"></div>
|
<div v-html="message"></div>
|
||||||
<q-input ref="input" class="q-mt-xs" outlined dense v-model="inputValue"/>
|
<q-input ref="input" v-model="inputValue" class="q-mt-xs" outlined dense />
|
||||||
<div class="error"><span v-show="error != ''">{{ error }}</span></div>
|
<div class="error">
|
||||||
|
<span v-show="error != ''">{{ error }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="buttons row justify-end q-pa-md">
|
<div class="buttons row justify-end q-pa-md">
|
||||||
<q-btn class="q-px-md q-ml-sm" dense no-caps v-close-popup>Отмена</q-btn>
|
<q-btn v-close-popup class="q-px-md q-ml-sm" dense no-caps>
|
||||||
<q-btn class="q-px-md q-ml-sm" color="primary" dense no-caps @click="okClick">OK</q-btn>
|
Отмена
|
||||||
|
</q-btn>
|
||||||
|
<q-btn class="q-px-md q-ml-sm" color="primary" dense no-caps @click="okClick">
|
||||||
|
OK
|
||||||
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -83,7 +95,7 @@
|
|||||||
<div v-html="caption"></div>
|
<div v-html="caption"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="close-icon column justify-center items-center">
|
<div class="close-icon column justify-center items-center">
|
||||||
<q-btn flat round dense v-close-popup>
|
<q-btn v-close-popup flat round dense>
|
||||||
<q-icon name="la la-times" size="18px"></q-icon>
|
<q-icon name="la la-times" size="18px"></q-icon>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
@@ -92,14 +104,20 @@
|
|||||||
<div class="q-mx-md">
|
<div class="q-mx-md">
|
||||||
<div v-html="message"></div>
|
<div v-html="message"></div>
|
||||||
<div class="q-my-md text-center">
|
<div class="q-my-md text-center">
|
||||||
<div v-show="hotKeyCode == ''" class="text-grey-5">Нет</div>
|
<div v-show="hotKeyCode == ''" class="text-grey-5">
|
||||||
|
Нет
|
||||||
|
</div>
|
||||||
<div>{{ hotKeyCode }}</div>
|
<div>{{ hotKeyCode }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="buttons row justify-end q-pa-md">
|
<div class="buttons row justify-end q-pa-md">
|
||||||
<q-btn class="q-px-md q-ml-sm" dense no-caps v-close-popup>Отмена</q-btn>
|
<q-btn v-close-popup class="q-px-md q-ml-sm" dense no-caps>
|
||||||
<q-btn class="q-px-md q-ml-sm" color="primary" dense no-caps @click="okClick" :disabled="hotKeyCode == ''">OK</q-btn>
|
Отмена
|
||||||
|
</q-btn>
|
||||||
|
<q-btn class="q-px-md q-ml-sm" color="primary" dense no-caps :disabled="hotKeyCode == ''" @click="okClick">
|
||||||
|
OK
|
||||||
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
@@ -107,19 +125,18 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
|
|
||||||
import * as utils from '../../share/utils';
|
import * as utils from '../../share/utils';
|
||||||
|
|
||||||
export default @Component({
|
const componentOptions = {
|
||||||
watch: {
|
watch: {
|
||||||
inputValue: function(newValue) {
|
inputValue: function(newValue) {
|
||||||
this.validate(newValue);
|
this.validate(newValue);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
};
|
||||||
class StdDialog extends Vue {
|
class StdDialog {
|
||||||
|
_options = componentOptions;
|
||||||
caption = '';
|
caption = '';
|
||||||
message = '';
|
message = '';
|
||||||
active = false;
|
active = false;
|
||||||
@@ -131,8 +148,8 @@ class StdDialog extends Vue {
|
|||||||
hotKeyCode = '';
|
hotKeyCode = '';
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
if (this.$root.addKeyHook) {
|
if (this.$root.addEventHook) {
|
||||||
this.$root.addKeyHook(this.keyHook);
|
this.$root.addEventHook('key', this.keyHook);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,6 +330,8 @@ class StdDialog extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(StdDialog);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,17 @@
|
|||||||
<div ref="main" class="main xyfit absolute" @click="close" @mouseup="onMouseUp" @mousemove="onMouseMove">
|
<div ref="main" class="main xyfit absolute" @click="close" @mouseup="onMouseUp" @mousemove="onMouseMove">
|
||||||
<div ref="windowBox" class="xyfit absolute flex no-wrap" @click.stop>
|
<div ref="windowBox" class="xyfit absolute flex no-wrap" @click.stop>
|
||||||
<div ref="window" class="window flexfit column no-wrap">
|
<div ref="window" class="window flexfit column no-wrap">
|
||||||
<div ref="header" class="header row justify-end" @mousedown.prevent.stop="onMouseDown"
|
<div
|
||||||
@touchstart.stop="onTouchStart" @touchend.stop="onTouchEnd" @touchmove.stop="onTouchMove">
|
ref="header"
|
||||||
|
class="header row justify-end"
|
||||||
|
@mousedown.prevent.stop="onMouseDown"
|
||||||
|
@touchstart.stop="onTouchStart"
|
||||||
|
@touchend.stop="onTouchEnd"
|
||||||
|
@touchmove.stop="onTouchMove"
|
||||||
|
>
|
||||||
<span class="header-text col"><slot name="header"></slot></span>
|
<span class="header-text col"><slot name="header"></slot></span>
|
||||||
<slot name="buttons"></slot>
|
<slot name="buttons"></slot>
|
||||||
<span class="close-button row justify-center items-center" @mousedown.stop @click="close"><q-icon name="la la-times" size="16px"/></span>
|
<span class="close-button row justify-center items-center" @mousedown.stop @click="close"><q-icon name="la la-times" size="16px" /></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
@@ -17,19 +23,17 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import vueComponent from '../vueComponent.js';
|
||||||
import Component from 'vue-class-component';
|
|
||||||
|
|
||||||
export default @Component({
|
class Window {
|
||||||
props: {
|
_props = {
|
||||||
height: { type: String, default: '100%' },
|
height: { type: String, default: '100%' },
|
||||||
width: { type: String, default: '100%' },
|
width: { type: String, default: '100%' },
|
||||||
maxWidth: { type: String, default: '' },
|
maxWidth: { type: String, default: '' },
|
||||||
topShift: { type: Number, default: 0 },
|
topShift: { type: Number, default: 0 },
|
||||||
margin: '',
|
margin: '',
|
||||||
}
|
};
|
||||||
})
|
|
||||||
class Window extends Vue {
|
|
||||||
init() {
|
init() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$refs.main.style.top = 0;
|
this.$refs.main.style.top = 0;
|
||||||
@@ -51,7 +55,7 @@ class Window extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMouseDown(event) {
|
onMouseDown(event) {
|
||||||
if (this.$isMobileDevice)
|
if (this.$root.isMobileDevice)
|
||||||
return;
|
return;
|
||||||
if (event.button == 0) {
|
if (event.button == 0) {
|
||||||
this.$refs.header.style.cursor = 'move';
|
this.$refs.header.style.cursor = 'move';
|
||||||
@@ -81,7 +85,7 @@ class Window extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onTouchStart(event) {
|
onTouchStart(event) {
|
||||||
if (!this.$isMobileDevice)
|
if (!this.$root.isMobileDevice)
|
||||||
return;
|
return;
|
||||||
if (event.touches.length == 1) {
|
if (event.touches.length == 1) {
|
||||||
const touch = event.touches[0];
|
const touch = event.touches[0];
|
||||||
@@ -93,7 +97,7 @@ class Window extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onTouchMove(event) {
|
onTouchMove(event) {
|
||||||
if (!this.$isMobileDevice)
|
if (!this.$root.isMobileDevice)
|
||||||
return;
|
return;
|
||||||
if (event.touches.length == 1 && this.moving) {
|
if (event.touches.length == 1 && this.moving) {
|
||||||
const touch = event.touches[0];
|
const touch = event.touches[0];
|
||||||
@@ -108,7 +112,7 @@ class Window extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onTouchEnd() {
|
onTouchEnd() {
|
||||||
if (!this.$isMobileDevice)
|
if (!this.$root.isMobileDevice)
|
||||||
return;
|
return;
|
||||||
this.$refs.header.style.cursor = 'default';
|
this.$refs.header.style.cursor = 'default';
|
||||||
this.moving = false;
|
this.moving = false;
|
||||||
@@ -120,6 +124,8 @@ class Window extends Vue {
|
|||||||
this.$emit('close');
|
this.$emit('close');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default vueComponent(Window);
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
52
client/components/vueComponent.js
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
export default function(componentClass) {
|
||||||
|
const comp = {};
|
||||||
|
const obj = new componentClass();
|
||||||
|
|
||||||
|
//data, options, props
|
||||||
|
const data = {};
|
||||||
|
for (const prop of Object.getOwnPropertyNames(obj)) {
|
||||||
|
if (['_options', '_props'].includes(prop)) {//meta props
|
||||||
|
if (prop === '_options') {
|
||||||
|
const options = obj[prop];
|
||||||
|
for (const optName of ['components', 'watch', 'emits']) {
|
||||||
|
if (options[optName]) {
|
||||||
|
comp[optName] = options[optName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (prop === '_props') {
|
||||||
|
comp['props'] = obj[prop];
|
||||||
|
}
|
||||||
|
} else {//usual prop
|
||||||
|
data[prop] = obj[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
comp.data = () => _.cloneDeep(data);
|
||||||
|
|
||||||
|
//methods
|
||||||
|
const classProto = Object.getPrototypeOf(obj);
|
||||||
|
const classMethods = Object.getOwnPropertyNames(classProto);
|
||||||
|
const methods = {};
|
||||||
|
const computed = {};
|
||||||
|
for (const method of classMethods) {
|
||||||
|
const desc = Object.getOwnPropertyDescriptor(classProto, method);
|
||||||
|
if (desc.get) {//has getter, computed
|
||||||
|
computed[method] = {get: desc.get};
|
||||||
|
if (desc.set)
|
||||||
|
computed[method].set = desc.set;
|
||||||
|
} else if ( ['beforeCreate', 'created', 'beforeMount', 'mounted', 'beforeUpdate', 'updated', 'activated',//life cycle hooks
|
||||||
|
'deactivated', 'beforeUnmount', 'unmounted', 'errorCaptured', 'renderTracked', 'renderTriggered',//life cycle hooks
|
||||||
|
'setup'].includes(method) ) {
|
||||||
|
comp[method] = obj[method];
|
||||||
|
} else if (method !== 'constructor') {//usual
|
||||||
|
methods[method] = obj[method];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
comp.methods = methods;
|
||||||
|
comp.computed = computed;
|
||||||
|
|
||||||
|
//console.log(comp);
|
||||||
|
return defineComponent(comp);
|
||||||
|
}
|
||||||
@@ -1,18 +1,16 @@
|
|||||||
import Vue from 'vue';
|
import { createApp } from 'vue';
|
||||||
|
|
||||||
import router from './router';
|
import router from './router';
|
||||||
import store from './store';
|
import store from './store';
|
||||||
import './quasar';
|
import q from './quasar';
|
||||||
|
|
||||||
import vueSanitize from 'vue-sanitize';
|
|
||||||
Vue.use(vueSanitize);
|
|
||||||
|
|
||||||
import App from './components/App.vue';
|
import App from './components/App.vue';
|
||||||
//Vue.config.productionTip = false;
|
|
||||||
Vue.prototype.$isMobileDevice = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent);
|
|
||||||
|
|
||||||
new Vue({
|
const app = createApp(App);
|
||||||
router,
|
|
||||||
store,
|
app.use(router);
|
||||||
render: h => h(App),
|
app.use(store);
|
||||||
}).$mount('#app');
|
app.use(q.quasar, q.options);
|
||||||
|
q.init();
|
||||||
|
|
||||||
|
app.mount('#app');
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import Vue from 'vue';
|
|
||||||
|
|
||||||
import 'quasar/dist/quasar.css';
|
import 'quasar/dist/quasar.css';
|
||||||
import Quasar from 'quasar/src/vue-plugin.js'
|
//import Quasar from 'quasar/dist/quasar.umd.prod.js';
|
||||||
|
|
||||||
|
import Quasar from 'quasar/src/vue-plugin.js';
|
||||||
//config
|
//config
|
||||||
const config = {};
|
const config = {};
|
||||||
|
|
||||||
@@ -21,7 +20,8 @@ import {QSlider} from 'quasar/src/components/slider';
|
|||||||
import {QTabs, QTab} from 'quasar/src/components/tabs';
|
import {QTabs, QTab} from 'quasar/src/components/tabs';
|
||||||
//import {QTabPanels, QTabPanel} from 'quasar/src/components/tab-panels';
|
//import {QTabPanels, QTabPanel} from 'quasar/src/components/tab-panels';
|
||||||
import {QSeparator} from 'quasar/src/components/separator';
|
import {QSeparator} from 'quasar/src/components/separator';
|
||||||
//import {QList, QItem, QItemSection, QItemLabel} from 'quasar/src/components/item';
|
//import {QList} from 'quasar/src/components/item';
|
||||||
|
import {QItem, QItemSection, QItemLabel} from 'quasar/src/components/item';
|
||||||
import {QTooltip} from 'quasar/src/components/tooltip';
|
import {QTooltip} from 'quasar/src/components/tooltip';
|
||||||
import {QSpinner} from 'quasar/src/components/spinner';
|
import {QSpinner} from 'quasar/src/components/spinner';
|
||||||
import {QTable, QTh, QTr, QTd} from 'quasar/src/components/table';
|
import {QTable, QTh, QTr, QTd} from 'quasar/src/components/table';
|
||||||
@@ -49,7 +49,8 @@ const components = {
|
|||||||
QTabs, QTab,
|
QTabs, QTab,
|
||||||
//QTabPanels, QTabPanel,
|
//QTabPanels, QTabPanel,
|
||||||
QSeparator,
|
QSeparator,
|
||||||
//QList, QItem, QItemSection, QItemLabel,
|
//QList,
|
||||||
|
QItem, QItemSection, QItemLabel,
|
||||||
QTooltip,
|
QTooltip,
|
||||||
QSpinner,
|
QSpinner,
|
||||||
QTable, QTh, QTr, QTd,
|
QTable, QTh, QTr, QTd,
|
||||||
@@ -78,16 +79,17 @@ const plugins = {
|
|||||||
Notify,
|
Notify,
|
||||||
};
|
};
|
||||||
|
|
||||||
//use
|
|
||||||
Vue.use(Quasar, { config, components, directives, plugins });
|
|
||||||
|
|
||||||
//icons
|
//icons
|
||||||
//import '@quasar/extras/material-icons/material-icons.css';
|
|
||||||
//import '@quasar/extras/material-icons-outlined/material-icons-outlined.css';
|
|
||||||
//import '@quasar/extras/fontawesome-v5/fontawesome-v5.css';
|
//import '@quasar/extras/fontawesome-v5/fontawesome-v5.css';
|
||||||
|
//import fontawesomeV5 from 'quasar/icon-set/fontawesome-v5.js'
|
||||||
|
|
||||||
import '@quasar/extras/line-awesome/line-awesome.css';
|
import '@quasar/extras/line-awesome/line-awesome.css';
|
||||||
|
|
||||||
//import fontawesomeV5 from 'quasar/icon-set/fontawesome-v5.js'
|
|
||||||
import lineAwesome from 'quasar/icon-set/line-awesome.js'
|
import lineAwesome from 'quasar/icon-set/line-awesome.js'
|
||||||
Quasar.iconSet.set(lineAwesome);
|
|
||||||
|
export default {
|
||||||
|
quasar: Quasar,
|
||||||
|
options: { config, components, directives, plugins },
|
||||||
|
init: () => {
|
||||||
|
Quasar.iconSet.set(lineAwesome);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import Vue from 'vue';
|
import { createRouter, createWebHashHistory } from 'vue-router';
|
||||||
import VueRouter from 'vue-router';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
const CardIndex = () => import('./components/CardIndex/CardIndex.vue');
|
const CardIndex = () => import('./components/CardIndex/CardIndex.vue');
|
||||||
@@ -36,7 +35,7 @@ const myRoutes = [
|
|||||||
['/settings', Settings],
|
['/settings', Settings],
|
||||||
['/help', Help],
|
['/help', Help],
|
||||||
['/404', NotFound404],
|
['/404', NotFound404],
|
||||||
['*', null, null, '/cardindex'],
|
['/:pathMatch(.*)*', null, null, '/cardindex'],
|
||||||
];
|
];
|
||||||
|
|
||||||
let routes = {};
|
let routes = {};
|
||||||
@@ -63,8 +62,7 @@ for (let route of myRoutes) {
|
|||||||
}
|
}
|
||||||
routes = routes.children;
|
routes = routes.children;
|
||||||
|
|
||||||
Vue.use(VueRouter);
|
export default createRouter({
|
||||||
|
history: createWebHashHistory(),
|
||||||
export default new VueRouter({
|
|
||||||
routes
|
routes
|
||||||
});
|
});
|
||||||
|
|||||||
22
client/share/dynamicCss.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
class DynamicCss {
|
||||||
|
constructor() {
|
||||||
|
this.cssNodes = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
replace(name, cssText) {
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.type = 'text/css';
|
||||||
|
style.innerHTML = cssText;
|
||||||
|
|
||||||
|
const parent = document.getElementsByTagName('head')[0];
|
||||||
|
|
||||||
|
if (this.cssNodes[name]) {
|
||||||
|
parent.removeChild(this.cssNodes[name]);
|
||||||
|
delete this.cssNodes[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cssNodes[name] = parent.appendChild(style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new DynamicCss();
|
||||||
@@ -13,6 +13,10 @@ export function sleep(ms) {
|
|||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toHex(buf) {
|
||||||
|
return Buffer.from(buf).toString('hex');
|
||||||
|
}
|
||||||
|
|
||||||
export function stringToHex(str) {
|
export function stringToHex(str) {
|
||||||
return Buffer.from(str).toString('hex');
|
return Buffer.from(str).toString('hex');
|
||||||
}
|
}
|
||||||
@@ -86,7 +90,7 @@ export function toBase58(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function fromBase58(data) {
|
export function fromBase58(data) {
|
||||||
return bs58.decode(data);
|
return Buffer.from(bs58.decode(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
//base-x слишком тормозит, используем sjcl
|
//base-x слишком тормозит, используем sjcl
|
||||||
@@ -103,6 +107,10 @@ export function fromBase64(data) {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function hasProp(obj, prop) {
|
||||||
|
return Object.prototype.hasOwnProperty.call(obj, prop);
|
||||||
|
}
|
||||||
|
|
||||||
export function getObjDiff(oldObj, newObj, opts = {}) {
|
export function getObjDiff(oldObj, newObj, opts = {}) {
|
||||||
const {
|
const {
|
||||||
exclude = [],
|
exclude = [],
|
||||||
@@ -122,7 +130,7 @@ export function getObjDiff(oldObj, newObj, opts = {}) {
|
|||||||
for (const key of Object.keys(oldObj)) {
|
for (const key of Object.keys(oldObj)) {
|
||||||
const kp = `${keyPath}${key}`;
|
const kp = `${keyPath}${key}`;
|
||||||
|
|
||||||
if (newObj.hasOwnProperty(key)) {
|
if (Object.prototype.hasOwnProperty.call(newObj, key)) {
|
||||||
if (ex.has(kp))
|
if (ex.has(kp))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -145,7 +153,7 @@ export function getObjDiff(oldObj, newObj, opts = {}) {
|
|||||||
if (exAdd.has(kp))
|
if (exAdd.has(kp))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!oldObj.hasOwnProperty(key)) {
|
if (!Object.prototype.hasOwnProperty.call(oldObj, key)) {
|
||||||
result.add[key] = _.cloneDeep(newObj[key]);
|
result.add[key] = _.cloneDeep(newObj[key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -209,7 +217,7 @@ export function applyObjDiff(obj, diff, opts = {}) {
|
|||||||
|
|
||||||
const change = diff.change;
|
const change = diff.change;
|
||||||
for (const key of Object.keys(change)) {
|
for (const key of Object.keys(change)) {
|
||||||
if (result.hasOwnProperty(key)) {
|
if (Object.prototype.hasOwnProperty.call(result, key)) {
|
||||||
if (_.isObject(change[key])) {
|
if (_.isObject(change[key])) {
|
||||||
result[key] = applyObjDiff(result[key], change[key], opts);
|
result[key] = applyObjDiff(result[key], change[key], opts);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import Vue from 'vue';
|
import { createStore } from 'vuex';
|
||||||
import Vuex from 'vuex';
|
|
||||||
import createPersistedState from 'vuex-persistedstate';
|
import createPersistedState from 'vuex-persistedstate';
|
||||||
|
|
||||||
import root from './root.js';
|
import root from './root.js';
|
||||||
@@ -7,11 +6,9 @@ import uistate from './modules/uistate';
|
|||||||
import config from './modules/config';
|
import config from './modules/config';
|
||||||
import reader from './modules/reader';
|
import reader from './modules/reader';
|
||||||
|
|
||||||
Vue.use(Vuex);
|
|
||||||
|
|
||||||
const debug = process.env.NODE_ENV !== 'production';
|
const debug = process.env.NODE_ENV !== 'production';
|
||||||
|
|
||||||
export default new Vuex.Store(Object.assign({}, root, {
|
export default createStore(Object.assign({}, root, {
|
||||||
modules: {
|
modules: {
|
||||||
uistate,
|
uistate,
|
||||||
config,
|
config,
|
||||||
|
|||||||
@@ -10,18 +10,7 @@ const state = {
|
|||||||
const getters = {};
|
const getters = {};
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
const actions = {
|
const actions = {};
|
||||||
async loadConfig({ commit, state }) {
|
|
||||||
commit('setApiError', null, { root: true });
|
|
||||||
commit('setConfig', {});
|
|
||||||
try {
|
|
||||||
const config = await miscApi.loadConfig();
|
|
||||||
commit('setConfig', config);
|
|
||||||
} catch (e) {
|
|
||||||
commit('setApiError', e, { root: true });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// mutations
|
// mutations
|
||||||
const mutations = {
|
const mutations = {
|
||||||
|
|||||||
1
client/store/modules/fonts/fonts.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
["Alegreya","Alegreya SC","Alegreya Sans","Alegreya Sans SC","Alice","Amatic SC","Andika","Anonymous Pro","Arimo","Arsenal","Bad Script","Balsamiq Sans","Bellota","Bellota Text","Bitter","Caveat","Comfortaa","Commissioner","Cormorant","Cormorant Garamond","Cormorant Infant","Cormorant SC","Cormorant Unicase","Cousine","Cuprum","Didact Gothic","EB Garamond","El Messiri","Exo 2","Fira Code","Fira Mono","Fira Sans","Fira Sans Condensed","Fira Sans Extra Condensed","Forum","Gabriela","Hachi Maru Pop","IBM Plex Mono","IBM Plex Sans","IBM Plex Serif","Inter","Istok Web","JetBrains Mono","Jost","Jura","Kelly Slab","Kosugi","Kosugi Maru","Kurale","Ledger","Literata","Lobster","Lora","M PLUS 1p","M PLUS Rounded 1c","Manrope","Marck Script","Marmelad","Merriweather","Montserrat","Montserrat Alternates","Neucha","Noto Sans","Noto Serif","Nunito","Old Standard TT","Open Sans","Open Sans Condensed","Oranienbaum","Oswald","PT Mono","PT Sans","PT Sans Caption","PT Sans Narrow","PT Serif","PT Serif Caption","Pacifico","Pangolin","Pattaya","Philosopher","Piazzolla","Play","Playfair Display","Playfair Display SC","Podkova","Poiret One","Prata","Press Start 2P","Prosto One","Raleway","Roboto","Roboto Condensed","Roboto Mono","Roboto Slab","Rubik","Rubik Mono One","Ruda","Ruslan Display","Russo One","Sawarabi Gothic","Scada","Seymour One","Source Code Pro","Source Sans Pro","Source Serif Pro","Spectral","Spectral SC","Stalinist One","Tenor Sans","Tinos","Ubuntu","Ubuntu Condensed","Ubuntu Mono","Underdog","Viaoda Libre","Vollkorn","Vollkorn SC","Yanone Kaffeesatz","Yeseva One"]
|
||||||
13
client/store/modules/fonts/fonts2list.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
const fs = require('fs-extra');
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const webfonts = await fs.readFile('webfonts.json');
|
||||||
|
let fonts = JSON.parse(webfonts);
|
||||||
|
|
||||||
|
fonts = fonts.items.filter(item => item.subsets.includes('cyrillic'));
|
||||||
|
fonts = fonts.map(item => item.family);
|
||||||
|
fonts.sort();
|
||||||
|
|
||||||
|
await fs.writeFile('fonts.json', JSON.stringify(fonts));
|
||||||
|
}
|
||||||
|
main();
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
import * as utils from '../../share/utils';
|
import * as utils from '../../share/utils';
|
||||||
|
import googleFonts from './fonts/fonts.json';
|
||||||
|
|
||||||
const readerActions = {
|
const readerActions = {
|
||||||
'help': 'Вызвать cправку',
|
|
||||||
'loader': 'На страницу загрузки',
|
'loader': 'На страницу загрузки',
|
||||||
|
'loadFile': 'Загрузить файл с диска',
|
||||||
|
'loadBuffer': 'Загрузить из буфера обмена',
|
||||||
|
'help': 'Вызвать cправку',
|
||||||
'settings': 'Настроить',
|
'settings': 'Настроить',
|
||||||
'undoAction': 'Действие назад',
|
'undoAction': 'Действие назад',
|
||||||
'redoAction': 'Действие вперед',
|
'redoAction': 'Действие вперед',
|
||||||
@@ -12,8 +15,9 @@ const readerActions = {
|
|||||||
'setPosition': 'Установить позицию',
|
'setPosition': 'Установить позицию',
|
||||||
'search': 'Найти в тексте',
|
'search': 'Найти в тексте',
|
||||||
'copyText': 'Скопировать текст со страницы',
|
'copyText': 'Скопировать текст со страницы',
|
||||||
'splitToPara': 'Обновить с разбиением на параграфы',
|
'convOptions': 'Настроить конвертирование',
|
||||||
'refresh': 'Принудительно обновить книгу',
|
'refresh': 'Принудительно обновить книгу',
|
||||||
|
'clickControl': 'Управление кликом',
|
||||||
'offlineMode': 'Автономный режим (без интернета)',
|
'offlineMode': 'Автономный режим (без интернета)',
|
||||||
'contents': 'Оглавление/закладки',
|
'contents': 'Оглавление/закладки',
|
||||||
'libs': 'Сетевая библиотека',
|
'libs': 'Сетевая библиотека',
|
||||||
@@ -34,6 +38,9 @@ const readerActions = {
|
|||||||
|
|
||||||
//readerActions[name]
|
//readerActions[name]
|
||||||
const toolButtons = [
|
const toolButtons = [
|
||||||
|
{name: 'loadFile', show: true},
|
||||||
|
{name: 'loadBuffer', show: true},
|
||||||
|
{name: 'help', show: true},
|
||||||
{name: 'undoAction', show: true},
|
{name: 'undoAction', show: true},
|
||||||
{name: 'redoAction', show: true},
|
{name: 'redoAction', show: true},
|
||||||
{name: 'fullScreen', show: true},
|
{name: 'fullScreen', show: true},
|
||||||
@@ -41,18 +48,21 @@ const toolButtons = [
|
|||||||
{name: 'setPosition', show: true},
|
{name: 'setPosition', show: true},
|
||||||
{name: 'search', show: true},
|
{name: 'search', show: true},
|
||||||
{name: 'copyText', show: false},
|
{name: 'copyText', show: false},
|
||||||
{name: 'splitToPara', show: false},
|
{name: 'convOptions', show: true},
|
||||||
{name: 'refresh', show: true},
|
{name: 'refresh', show: true},
|
||||||
{name: 'contents', show: true},
|
{name: 'contents', show: true},
|
||||||
{name: 'libs', show: true},
|
{name: 'libs', show: true},
|
||||||
{name: 'recentBooks', show: true},
|
{name: 'recentBooks', show: true},
|
||||||
|
{name: 'clickControl', show: false},
|
||||||
{name: 'offlineMode', show: false},
|
{name: 'offlineMode', show: false},
|
||||||
];
|
];
|
||||||
|
|
||||||
//readerActions[name]
|
//readerActions[name]
|
||||||
const hotKeys = [
|
const hotKeys = [
|
||||||
{name: 'help', codes: ['F1', 'H']},
|
|
||||||
{name: 'loader', codes: ['Escape']},
|
{name: 'loader', codes: ['Escape']},
|
||||||
|
{name: 'loadFile', codes: ['F3']},
|
||||||
|
{name: 'loadBuffer', codes: ['F4']},
|
||||||
|
{name: 'help', codes: ['F1', 'H']},
|
||||||
{name: 'settings', codes: ['S']},
|
{name: 'settings', codes: ['S']},
|
||||||
{name: 'undoAction', codes: ['Ctrl+BracketLeft']},
|
{name: 'undoAction', codes: ['Ctrl+BracketLeft']},
|
||||||
{name: 'redoAction', codes: ['Ctrl+BracketRight']},
|
{name: 'redoAction', codes: ['Ctrl+BracketRight']},
|
||||||
@@ -60,12 +70,13 @@ const hotKeys = [
|
|||||||
{name: 'scrolling', codes: ['Z']},
|
{name: 'scrolling', codes: ['Z']},
|
||||||
{name: 'setPosition', codes: ['P']},
|
{name: 'setPosition', codes: ['P']},
|
||||||
{name: 'search', codes: ['Ctrl+F']},
|
{name: 'search', codes: ['Ctrl+F']},
|
||||||
{name: 'copyText', codes: ['Ctrl+C']},
|
{name: 'copyText', codes: ['Ctrl+Space']},
|
||||||
{name: 'splitToPara', codes: ['Shift+R']},
|
{name: 'convOptions', codes: ['Ctrl+M']},
|
||||||
{name: 'refresh', codes: ['R']},
|
{name: 'refresh', codes: ['R']},
|
||||||
{name: 'contents', codes: ['C']},
|
{name: 'contents', codes: ['C']},
|
||||||
{name: 'libs', codes: ['L']},
|
{name: 'libs', codes: ['L']},
|
||||||
{name: 'recentBooks', codes: ['X']},
|
{name: 'recentBooks', codes: ['X']},
|
||||||
|
{name: 'clickControl', codes: ['Ctrl+B']},
|
||||||
{name: 'offlineMode', codes: ['O']},
|
{name: 'offlineMode', codes: ['O']},
|
||||||
|
|
||||||
{name: 'switchToolbar', codes: ['Tab', 'Q']},
|
{name: 'switchToolbar', codes: ['Tab', 'Q']},
|
||||||
@@ -91,125 +102,22 @@ const fonts = [
|
|||||||
{name: 'Rubik', fontVertShift: 0},
|
{name: 'Rubik', fontVertShift: 0},
|
||||||
];
|
];
|
||||||
|
|
||||||
const webFonts = [
|
//webFonts: [{css: 'https://fonts.googleapis.com/css?family=Alegreya', name: 'Alegreya', fontVertShift: 0}, ...],
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Alegreya', name: 'Alegreya', fontVertShift: -5},
|
const webFonts = [];
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Alegreya+Sans', name: 'Alegreya Sans', fontVertShift: 5},
|
for (const family of googleFonts) {
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Alegreya+SC', name: 'Alegreya SC', fontVertShift: -5},
|
webFonts.push({
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Alice', name: 'Alice', fontVertShift: 5},
|
css: `https://fonts.googleapis.com/css?family=${family.replace(/\s/g, '+')}`,
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Amatic+SC', name: 'Amatic SC', fontVertShift: 0},
|
name: family,
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Andika', name: 'Andika', fontVertShift: -35},
|
fontVertShift: 0,
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Anonymous+Pro', name: 'Anonymous Pro', fontVertShift: 5},
|
});
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Arsenal', name: 'Arsenal', fontVertShift: 0},
|
}
|
||||||
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Bad+Script', name: 'Bad Script', fontVertShift: -30},
|
|
||||||
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Caveat', name: 'Caveat', fontVertShift: -5},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Comfortaa', name: 'Comfortaa', fontVertShift: 10},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Cormorant', name: 'Cormorant', fontVertShift: 0},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Cormorant+Garamond', name: 'Cormorant Garamond', fontVertShift: 0},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Cormorant+Infant', name: 'Cormorant Infant', fontVertShift: 5},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Cormorant+Unicase', name: 'Cormorant Unicase', fontVertShift: 0},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Cousine', name: 'Cousine', fontVertShift: 0},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Cuprum', name: 'Cuprum', fontVertShift: 5},
|
|
||||||
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Didact+Gothic', name: 'Didact Gothic', fontVertShift: -10},
|
|
||||||
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=EB+Garamond', name: 'EB Garamond', fontVertShift: -5},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=El+Messiri', name: 'El Messiri', fontVertShift: -5},
|
|
||||||
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Fira+Mono', name: 'Fira Mono', fontVertShift: 5},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Fira+Sans', name: 'Fira Sans', fontVertShift: 5},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Fira+Sans+Condensed', name: 'Fira Sans Condensed', fontVertShift: 5},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Fira+Sans+Extra+Condensed', name: 'Fira Sans Extra Condensed', fontVertShift: 5},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Forum', name: 'Forum', fontVertShift: 5},
|
|
||||||
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Gabriela', name: 'Gabriela', fontVertShift: 5},
|
|
||||||
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=IBM+Plex+Mono', name: 'IBM Plex Mono', fontVertShift: -5},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=IBM+Plex+Sans', name: 'IBM Plex Sans', fontVertShift: -5},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=IBM+Plex+Serif', name: 'IBM Plex Serif', fontVertShift: -5},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Istok+Web', name: 'Istok Web', fontVertShift: -5},
|
|
||||||
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Jura', name: 'Jura', fontVertShift: 0},
|
|
||||||
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Kelly+Slab', name: 'Kelly Slab', fontVertShift: 0},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Kosugi', name: 'Kosugi', fontVertShift: 5},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Kosugi+Maru', name: 'Kosugi Maru', fontVertShift: 10},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Kurale', name: 'Kurale', fontVertShift: -15},
|
|
||||||
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Ledger', name: 'Ledger', fontVertShift: -5},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Lobster', name: 'Lobster', fontVertShift: 0},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Lora', name: 'Lora', fontVertShift: 0},
|
|
||||||
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Marck+Script', name: 'Marck Script', fontVertShift: 0},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Marmelad', name: 'Marmelad', fontVertShift: 0},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Merriweather', name: 'Merriweather', fontVertShift: 0},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Montserrat', name: 'Montserrat', fontVertShift: 0},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Montserrat+Alternates', name: 'Montserrat Alternates', fontVertShift: 0},
|
|
||||||
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Neucha', name: 'Neucha', fontVertShift: 0},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Noto+Sans', name: 'Noto Sans', fontVertShift: -10},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Noto+Sans+SC', name: 'Noto Sans SC', fontVertShift: -15},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Noto+Serif', name: 'Noto Serif', fontVertShift: -10},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Noto+Serif+TC', name: 'Noto Serif TC', fontVertShift: -15},
|
|
||||||
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Old+Standard+TT', name: 'Old Standard TT', fontVertShift: 15},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Open+Sans+Condensed:300', name: 'Open Sans Condensed', fontVertShift: -5},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Oranienbaum', name: 'Oranienbaum', fontVertShift: 5},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Oswald', name: 'Oswald', fontVertShift: -20},
|
|
||||||
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Pacifico', name: 'Pacifico', fontVertShift: -35},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Pangolin', name: 'Pangolin', fontVertShift: 5},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Pattaya', name: 'Pattaya', fontVertShift: 0},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Philosopher', name: 'Philosopher', fontVertShift: 5},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Play', name: 'Play', fontVertShift: 5},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Playfair+Display', name: 'Playfair Display', fontVertShift: -5},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Playfair+Display+SC', name: 'Playfair Display SC', fontVertShift: -5},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Podkova', name: 'Podkova', fontVertShift: 10},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Poiret+One', name: 'Poiret One', fontVertShift: 0},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Prata', name: 'Prata', fontVertShift: 5},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Prosto+One', name: 'Prosto One', fontVertShift: 0},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=PT+Mono', name: 'PT Mono', fontVertShift: 5},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=PT+Sans', name: 'PT Sans', fontVertShift: -10},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=PT+Sans+Caption', name: 'PT Sans Caption', fontVertShift: -10},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=PT+Sans+Narrow', name: 'PT Sans Narrow', fontVertShift: -10},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=PT+Serif', name: 'PT Serif', fontVertShift: -10},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=PT+Serif+Caption', name: 'PT Serif Caption', fontVertShift: -10},
|
|
||||||
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Roboto+Condensed', name: 'Roboto Condensed', fontVertShift: 0},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Roboto+Mono', name: 'Roboto Mono', fontVertShift: -5},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Roboto+Slab', name: 'Roboto Slab', fontVertShift: 0},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Ruslan+Display', name: 'Ruslan Display', fontVertShift: 20},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Russo+One', name: 'Russo One', fontVertShift: 5},
|
|
||||||
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Sawarabi+Gothic', name: 'Sawarabi Gothic', fontVertShift: -15},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Scada', name: 'Scada', fontVertShift: 0},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Seymour+One', name: 'Seymour One', fontVertShift: 0},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Source+Sans+Pro', name: 'Source Sans Pro', fontVertShift: 0},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Spectral', name: 'Spectral', fontVertShift: -5},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Stalinist+One', name: 'Stalinist One', fontVertShift: 0},
|
|
||||||
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Tinos', name: 'Tinos', fontVertShift: 5},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Tenor+Sans', name: 'Tenor Sans', fontVertShift: 5},
|
|
||||||
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Underdog', name: 'Underdog', fontVertShift: 10},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Ubuntu+Mono', name: 'Ubuntu Mono', fontVertShift: 0},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Ubuntu+Condensed', name: 'Ubuntu Condensed', fontVertShift: -5},
|
|
||||||
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Vollkorn', name: 'Vollkorn', fontVertShift: -5},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Vollkorn+SC', name: 'Vollkorn SC', fontVertShift: 0},
|
|
||||||
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Yanone+Kaffeesatz', name: 'Yanone Kaffeesatz', fontVertShift: 20},
|
|
||||||
{css: 'https://fonts.googleapis.com/css?family=Yeseva+One', name: 'Yeseva One', fontVertShift: 10},
|
|
||||||
|
|
||||||
|
|
||||||
];
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------------
|
||||||
const settingDefaults = {
|
const settingDefaults = {
|
||||||
textColor: '#000000',
|
textColor: '#000000',
|
||||||
backgroundColor: '#EBE2C9',
|
backgroundColor: '#ebe2c9',
|
||||||
wallpaper: '',
|
wallpaper: '',
|
||||||
|
wallpaperIgnoreStatusBar: false,
|
||||||
fontStyle: '',// 'italic'
|
fontStyle: '',// 'italic'
|
||||||
fontWeight: '',// 'bold'
|
fontWeight: '',// 'bold'
|
||||||
fontSize: 20,// px
|
fontSize: 20,// px
|
||||||
@@ -226,9 +134,22 @@ const settingDefaults = {
|
|||||||
wordWrap: true,//перенос по слогам
|
wordWrap: true,//перенос по слогам
|
||||||
keepLastToFirst: false,// перенос последней строки в первую при листании
|
keepLastToFirst: false,// перенос последней строки в первую при листании
|
||||||
|
|
||||||
|
dualPageMode: false,
|
||||||
|
dualIndentLR: 10,// px, отступ слева и справа внутри страницы в двухстраничном режиме
|
||||||
|
dualDivWidth: 2,// px, ширина разделителя
|
||||||
|
dualDivHeight: 100,// процент, высота разделителя
|
||||||
|
dualDivColorAsText: true,//цвет как у текста
|
||||||
|
dualDivColor: '#000000',
|
||||||
|
dualDivColorAlpha: 0.7,// прозрачность разделителя
|
||||||
|
dualDivStrokeFill: 1,// px, заполнение пунктира
|
||||||
|
dualDivStrokeGap: 1,// px, промежуток пунктира
|
||||||
|
dualDivShadowWidth: 0,// px, ширина тени
|
||||||
|
|
||||||
showStatusBar: true,
|
showStatusBar: true,
|
||||||
statusBarTop: false,// top, bottom
|
statusBarTop: false,// top, bottom
|
||||||
statusBarHeight: 19,// px
|
statusBarHeight: 19,// px
|
||||||
|
statusBarColorAsText: true,//цвет как у текста
|
||||||
|
statusBarColor: '#000000',
|
||||||
statusBarColorAlpha: 0.4,
|
statusBarColorAlpha: 0.4,
|
||||||
statusBarClickOpen: true,
|
statusBarClickOpen: true,
|
||||||
|
|
||||||
@@ -251,15 +172,21 @@ const settingDefaults = {
|
|||||||
compactTextPerc: 0,
|
compactTextPerc: 0,
|
||||||
imageHeightLines: 100,
|
imageHeightLines: 100,
|
||||||
imageFitWidth: true,
|
imageFitWidth: true,
|
||||||
|
enableSitesFilter: true,
|
||||||
|
splitToPara: false,
|
||||||
|
djvuQuality: 20,
|
||||||
|
pdfAsText: true,
|
||||||
|
pdfQuality: 20,
|
||||||
|
|
||||||
showServerStorageMessages: true,
|
showServerStorageMessages: true,
|
||||||
showWhatsNewDialog: true,
|
showWhatsNewDialog: true,
|
||||||
showDonationDialog2020: true,
|
showDonationDialog2020: true,
|
||||||
showLiberamaTopDialog2020: true,
|
showNeedUpdateNotify: true,
|
||||||
enableSitesFilter: true,
|
|
||||||
|
|
||||||
fontShifts: {},
|
fontShifts: {},
|
||||||
showToolButton: {},
|
showToolButton: {},
|
||||||
userHotKeys: {},
|
userHotKeys: {},
|
||||||
|
userWallpapers: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const font of fonts)
|
for (const font of fonts)
|
||||||
@@ -271,12 +198,13 @@ for (const button of toolButtons)
|
|||||||
for (const hotKey of hotKeys)
|
for (const hotKey of hotKeys)
|
||||||
settingDefaults.userHotKeys[hotKey.name] = hotKey.codes;
|
settingDefaults.userHotKeys[hotKey.name] = hotKey.codes;
|
||||||
|
|
||||||
const excludeDiffHotKeys = [];
|
const diffExclude = [];
|
||||||
for (const hotKey of hotKeys)
|
for (const hotKey of hotKeys)
|
||||||
excludeDiffHotKeys.push(`userHotKeys/${hotKey.name}`);
|
diffExclude.push(`userHotKeys/${hotKey.name}`);
|
||||||
|
diffExclude.push('userWallpapers');
|
||||||
|
|
||||||
function addDefaultsToSettings(settings) {
|
function addDefaultsToSettings(settings) {
|
||||||
const diff = utils.getObjDiff(settings, settingDefaults, {exclude: excludeDiffHotKeys});
|
const diff = utils.getObjDiff(settings, settingDefaults, {exclude: diffExclude});
|
||||||
if (!utils.isEmptyObjDiffDeep(diff, {isApplyChange: false})) {
|
if (!utils.isEmptyObjDiffDeep(diff, {isApplyChange: false})) {
|
||||||
return utils.applyObjDiff(settings, diff, {isApplyChange: false});
|
return utils.applyObjDiff(settings, diff, {isApplyChange: false});
|
||||||
}
|
}
|
||||||
|
|||||||
43
docs/beta/beta.omnireader_http
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name beta.omnireader.ru;
|
||||||
|
|
||||||
|
client_max_body_size 50m;
|
||||||
|
proxy_read_timeout 1h;
|
||||||
|
|
||||||
|
gzip on;
|
||||||
|
gzip_min_length 1024;
|
||||||
|
gzip_proxied expired no-cache no-store private auth;
|
||||||
|
gzip_types *;
|
||||||
|
|
||||||
|
location /api {
|
||||||
|
proxy_pass http://127.0.0.1:34081;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /ws {
|
||||||
|
proxy_pass http://127.0.0.1:34081;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /home/beta.liberama/public;
|
||||||
|
|
||||||
|
location /tmp {
|
||||||
|
types { } default_type "application/xml; charset=utf-8";
|
||||||
|
add_header Content-Encoding gzip;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~* \.(?:manifest|appcache|html)$ {
|
||||||
|
expires -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name beta.omnireader.ru;
|
||||||
|
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
@@ -35,6 +35,7 @@ server {
|
|||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection "upgrade";
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_read_timeout 600s;
|
||||||
}
|
}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
@@ -139,5 +140,6 @@ server {
|
|||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://fantasy-worlds.org;
|
proxy_pass http://fantasy-worlds.org;
|
||||||
|
proxy_hide_header x-frame-options;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
## Разворачивание сервера OmniReader в Ubuntu:
|
## Разворачивание сервера OmniReader в Ubuntu 20.04:
|
||||||
|
|
||||||
### git, clone
|
### git, clone
|
||||||
```
|
```
|
||||||
|
cd ~
|
||||||
sudo apt install ssh git
|
sudo apt install ssh git
|
||||||
git clone https://github.com/bookpauk/liberama
|
git clone https://github.com/bookpauk/liberama
|
||||||
```
|
```
|
||||||
@@ -9,7 +10,7 @@ git clone https://github.com/bookpauk/liberama
|
|||||||
### node.js
|
### node.js
|
||||||
```
|
```
|
||||||
sudo apt install -y curl
|
sudo apt install -y curl
|
||||||
curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
|
curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash -
|
||||||
sudo apt install -y nodejs
|
sudo apt install -y nodejs
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -25,29 +26,25 @@ sudo mkdir /home/liberama
|
|||||||
sudo chown www-data.www-data /home/liberama
|
sudo chown www-data.www-data /home/liberama
|
||||||
```
|
```
|
||||||
|
|
||||||
### external converter `calibre`, download from https://download.calibre-ebook.com/
|
### external converter `calibre`
|
||||||
|
#### download from https://download.calibre-ebook.com/
|
||||||
```
|
```
|
||||||
wget "https://download.calibre-ebook.com/5.5.0/calibre-5.5.0-x86_64.txz"
|
wget "https://download.calibre-ebook.com/5.29.0/calibre-5.29.0-x86_64.txz"
|
||||||
sudo -u www-data mkdir -p /home/liberama/data/calibre
|
sudo -u www-data mkdir -p /home/liberama/data/calibre
|
||||||
sudo -u www-data tar xvf calibre-5.5.0-x86_64.txz -C /home/liberama/data/calibre
|
sudo -u www-data tar xvf calibre-5.29.0-x86_64.txz -C /home/liberama/data/calibre
|
||||||
```
|
```
|
||||||
|
|
||||||
### external converters
|
### external converters
|
||||||
```
|
```
|
||||||
sudo apt install rar
|
sudo apt install rar libreoffice poppler-utils djvulibre-bin libtiff-tools graphicsmagick-imagemagick-compat
|
||||||
sudo apt install libreoffice
|
|
||||||
sudo apt install poppler-utils
|
|
||||||
sudo apt install djvulibre-bin
|
|
||||||
sudo apt install libtiff-tools
|
|
||||||
sudo apt install graphicsmagick-imagemagick-compat
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### nginx, server config
|
### nginx, server config
|
||||||
Для своего домена необходимо будет подправить docs/omnireader.ru/omnireader.
|
#### Для своего домена необходимо будет подправить docs/omnireader.ru/omnireader и docs/omnireader.ru/omnireader_http
|
||||||
Можно также настроить сервер для HTTP, без SSL.
|
Сначала настроим для HTTP:
|
||||||
```
|
```
|
||||||
sudo apt install nginx
|
sudo apt install nginx
|
||||||
sudo cp docs/omnireader.ru/omnireader /etc/nginx/sites-available/omnireader
|
sudo cp docs/omnireader.ru/omnireader_http /etc/nginx/sites-available/omnireader
|
||||||
sudo ln -s /etc/nginx/sites-available/omnireader /etc/nginx/sites-enabled/omnireader
|
sudo ln -s /etc/nginx/sites-available/omnireader /etc/nginx/sites-enabled/omnireader
|
||||||
sudo rm /etc/nginx/sites-enabled/default
|
sudo rm /etc/nginx/sites-enabled/default
|
||||||
sudo service nginx reload
|
sudo service nginx reload
|
||||||
@@ -55,11 +52,19 @@ sudo chown -R www-data.www-data /var/www
|
|||||||
```
|
```
|
||||||
|
|
||||||
### certbot
|
### certbot
|
||||||
Следовать инструкции установки certbot https://certbot.eff.org/lets-encrypt/ubuntubionic-nginx
|
#### Следовать инструкции установки certbot https://certbot.eff.org/instructions?ws=nginx&os=ubuntu-20
|
||||||
### old.omnireader
|
После установки сертификата, можно использовать конфиг для nginx c ssl:
|
||||||
```
|
```
|
||||||
sudo apt install php7.2 php7.2-curl php7.2-mbstring php7.2-fpm
|
sudo cp docs/omnireader.ru/omnireader /etc/nginx/sites-available/omnireader
|
||||||
sudo service php7.2-fpm restart
|
sudo service nginx reload
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### old.omnireader
|
||||||
|
#### Старая версия omnireader на базе PHP, можно не устанавливать
|
||||||
|
```
|
||||||
|
sudo apt install php7.4 php7.4-curl php7.4-mbstring php7.4-fpm
|
||||||
|
sudo service php7.4-fpm restart
|
||||||
|
|
||||||
sudo mkdir /home/oldreader
|
sudo mkdir /home/oldreader
|
||||||
sudo chown www-data.www-data /home/oldreader
|
sudo chown www-data.www-data /home/oldreader
|
||||||
@@ -68,7 +73,7 @@ sudo -u www-data cp -r docs/omnireader.ru/old/* /home/oldreader
|
|||||||
|
|
||||||
## Запуск по крону
|
## Запуск по крону
|
||||||
```
|
```
|
||||||
* * * * * /root/liberama/docs/omnireader.ru/cron_server.sh
|
* * * * * ~/liberama/docs/omnireader.ru/cron_server.sh >>~/liberama_cron.log 2>&1
|
||||||
```
|
```
|
||||||
|
|
||||||
## Деплой и запуск
|
## Деплой и запуск
|
||||||
@@ -78,8 +83,8 @@ cd docs/omnireader.ru
|
|||||||
./deploy.sh
|
./deploy.sh
|
||||||
./start_server.sh
|
./start_server.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
После первого запуска будет создан конфигурационный файл `/home/liberama/data/config.json`.
|
После первого запуска будет создан конфигурационный файл `/home/liberama/data/config.json`.
|
||||||
|
|
||||||
Необходимо переключить приложение в режим `omnireader`, отредактировав опцию `servers`:
|
Необходимо переключить приложение в режим `omnireader`, отредактировав опцию `servers`:
|
||||||
```
|
```
|
||||||
"servers": [
|
"servers": [
|
||||||
@@ -91,4 +96,8 @@ cd docs/omnireader.ru
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
и перезапустить сервер
|
Для использования установленных внешних конвертеров можно также поправить:
|
||||||
|
```
|
||||||
|
"useExternalBookConverter": true,
|
||||||
|
```
|
||||||
|
и перезапустить сервер.
|
||||||
@@ -24,6 +24,7 @@ server {
|
|||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection "upgrade";
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_read_timeout 600s;
|
||||||
}
|
}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
@@ -66,6 +67,6 @@ server {
|
|||||||
location ~ \.php$ {
|
location ~ \.php$ {
|
||||||
try_files $uri =404;
|
try_files $uri =404;
|
||||||
include /etc/nginx/fastcgi.conf;
|
include /etc/nginx/fastcgi.conf;
|
||||||
fastcgi_pass unix:/run/php/php7.2-fpm.sock;
|
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,6 @@ server {
|
|||||||
location ~ \.php$ {
|
location ~ \.php$ {
|
||||||
try_files $uri =404;
|
try_files $uri =404;
|
||||||
include /etc/nginx/fastcgi.conf;
|
include /etc/nginx/fastcgi.conf;
|
||||||
fastcgi_pass unix:/run/php/php7.2-fpm.sock;
|
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||