Compare commits
172 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
956546585c | ||
|
|
3ca0a92442 | ||
|
|
213f7e48c9 | ||
|
|
8b66fd522d | ||
|
|
fdf5009999 | ||
|
|
bbdba0ef16 | ||
|
|
a63602df7a | ||
|
|
587120f984 | ||
|
|
e72ca0de7e | ||
|
|
c44c27d3d2 | ||
|
|
df4e201ccd | ||
|
|
c8c0e9ec1a | ||
|
|
9a4a84a367 | ||
|
|
1dc3424411 | ||
|
|
c13745e913 | ||
|
|
25c12309f2 | ||
|
|
4b632da5af | ||
|
|
87c364b8ee | ||
|
|
efa48fbc8a | ||
|
|
21df6c1d21 | ||
|
|
39d2ceb94b | ||
|
|
1dad013d60 | ||
|
|
add7a03f88 | ||
|
|
0cefaa6d48 | ||
|
|
f08e73f359 | ||
|
|
cf9ce26438 | ||
|
|
fd74a5a82e | ||
|
|
3109104928 | ||
|
|
b1ec4df2e4 | ||
|
|
1609e149a8 | ||
|
|
298c483d0e | ||
|
|
dc917b75b1 | ||
|
|
ad32bdab44 | ||
|
|
5e8b2e1c87 | ||
|
|
08b4afd287 | ||
|
|
0de31f643b | ||
|
|
5e815eb3c4 | ||
|
|
48c93a2120 | ||
|
|
ad5de42172 | ||
|
|
32bafedaad | ||
|
|
c67fd11be9 | ||
|
|
8b59c72848 | ||
|
|
c35b2f3bfc | ||
|
|
ac63ad4612 | ||
|
|
e1dea1c752 | ||
|
|
25648e2327 | ||
|
|
18ac04bb0f | ||
|
|
5263ee58b2 | ||
|
|
af542b89f7 | ||
|
|
684b675fca | ||
|
|
c29044eca1 | ||
|
|
a36510fcc8 | ||
|
|
bc21ace416 | ||
|
|
57e521e2ff | ||
|
|
ac6ebb9e8d | ||
|
|
54bef54635 | ||
|
|
593e201f79 | ||
|
|
d7b24253fe | ||
|
|
33961abd86 | ||
|
|
37e0e1d42f | ||
|
|
1121f9c918 | ||
|
|
582203f5da | ||
|
|
8c0f193738 | ||
|
|
ebe42956ad | ||
|
|
b8f8df8927 | ||
|
|
2c66ca4fdd | ||
|
|
49f813e880 | ||
|
|
da6fed80d1 | ||
|
|
b901d9b8c9 | ||
|
|
b41d46ac57 | ||
|
|
4f0189f3e0 | ||
|
|
c956e7a802 | ||
|
|
dcbc8409e0 | ||
|
|
fd58568cf0 | ||
|
|
0f81fa53d2 | ||
|
|
44655dc81c | ||
|
|
749667aefd | ||
|
|
dd94418c26 | ||
|
|
55a5375e46 | ||
|
|
df76de7352 | ||
|
|
1fb1a1b2b1 | ||
|
|
f998edb2aa | ||
|
|
7c2cb9a0c7 | ||
|
|
0690a365da | ||
|
|
a20d05aba8 | ||
|
|
4362ae95ba | ||
|
|
d658814399 | ||
|
|
39e14d70ee | ||
|
|
2e58cfdb75 | ||
|
|
fcaa724c00 | ||
|
|
8806b4141e | ||
|
|
7bd159766b | ||
|
|
4df15d603f | ||
|
|
b453c3efe5 | ||
|
|
56590ef8a4 | ||
|
|
7c133136b9 | ||
|
|
41881639aa | ||
|
|
416003f078 | ||
|
|
bbfcd0efa3 | ||
|
|
150e4332c3 | ||
|
|
49649765c7 | ||
|
|
726b7bfa93 | ||
|
|
265f838868 | ||
|
|
6e2e5b5520 | ||
|
|
100ea2f64a | ||
|
|
4e7ed1ee33 | ||
|
|
8ab6aed1aa | ||
|
|
4ff096014c | ||
|
|
03b60b6ca9 | ||
|
|
e30b832e05 | ||
|
|
e646de85a7 | ||
|
|
70a7a0e344 | ||
|
|
b444abeb3e | ||
|
|
c72f56917d | ||
|
|
192283d6b2 | ||
|
|
6be6fa1966 | ||
|
|
510553b055 | ||
|
|
6c4616892e | ||
|
|
1e79a099b8 | ||
|
|
31a22327f1 | ||
|
|
c1712bebc6 | ||
|
|
cd91541245 | ||
|
|
4c1fc83256 | ||
|
|
34c7a33576 | ||
|
|
23ecfeeb4f | ||
|
|
9703f83eb3 | ||
|
|
0f3cc03d00 | ||
|
|
6f7ba1f9fc | ||
|
|
e1b85e4a1b | ||
|
|
b308dd58cc | ||
|
|
9f4c0479ce | ||
|
|
2c57817dde | ||
|
|
ba85c54d7c | ||
|
|
a80e5c3a65 | ||
|
|
22e2c34da8 | ||
|
|
00a8e4c2c5 | ||
|
|
10d0a4079c | ||
|
|
589f7f3c22 | ||
|
|
d1126a7eb0 | ||
|
|
9f4e72a0e1 | ||
|
|
a024295379 | ||
|
|
dc2b2ec488 | ||
|
|
0c5f5975aa | ||
|
|
dc3f682d2d | ||
|
|
2db8876c66 | ||
|
|
8f6201b0f7 | ||
|
|
4b146c70ad | ||
|
|
0118034b4b | ||
|
|
39217053ca | ||
|
|
fba190c826 | ||
|
|
5e9d528e16 | ||
|
|
c5921d88fc | ||
|
|
eb980b0ea1 | ||
|
|
de5b4216f7 | ||
|
|
495ff57b19 | ||
|
|
57948cf6e3 | ||
|
|
1aebbbcabd | ||
|
|
25b4cb072d | ||
|
|
1cdacc3a08 | ||
|
|
34d9466d09 | ||
|
|
c182c4ce66 | ||
|
|
dbb9bd1282 | ||
|
|
8019d2d6cc | ||
|
|
459cdb2e0b | ||
|
|
a230cd9513 | ||
|
|
0c44a25e85 | ||
|
|
34f3d04370 | ||
|
|
1f3e6b7e16 | ||
|
|
47d49a200a | ||
|
|
e1767d6e52 | ||
|
|
0f8e343cd2 | ||
|
|
23ab487baf |
@@ -8,7 +8,7 @@
|
|||||||

|

|
||||||
|
|
||||||
## VPS
|
## VPS
|
||||||
Для разворачивания читалки на чистом VPS с нуля смотрите [docs/omnireader](docs/omnireader/README.md)
|
Для разворачивания читалки на чистом VPS с нуля смотрите [docs/omnireader.ru](docs/omnireader.ru/README.md)
|
||||||
|
|
||||||
## Сборка проекта
|
## Сборка проекта
|
||||||
Необходима версия node.js не ниже 10.
|
Необходима версия node.js не ниже 10.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
|||||||
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
const AppCachePlugin = require('appcache-webpack-plugin');
|
const {GenerateSW} = require('workbox-webpack-plugin');
|
||||||
|
|
||||||
const publicDir = path.resolve(__dirname, '../dist/tmp/public');
|
const publicDir = path.resolve(__dirname, '../dist/tmp/public');
|
||||||
const clientDir = path.resolve(__dirname, '../client');
|
const clientDir = path.resolve(__dirname, '../client');
|
||||||
@@ -55,6 +55,12 @@ module.exports = merge(baseWpConfig, {
|
|||||||
filename: `${publicDir}/index.html`
|
filename: `${publicDir}/index.html`
|
||||||
}),
|
}),
|
||||||
new CopyWebpackPlugin([{from: `${clientDir}/assets/*`, to: `${publicDir}/`, flatten: true}]),
|
new CopyWebpackPlugin([{from: `${clientDir}/assets/*`, to: `${publicDir}/`, flatten: true}]),
|
||||||
new AppCachePlugin({exclude: ['../index.html']})
|
new GenerateSW({
|
||||||
|
cacheId: 'liberama',
|
||||||
|
swDest: `${publicDir}/service-worker.js`,
|
||||||
|
navigateFallback: '/index.html',
|
||||||
|
navigateFallbackDenylist: [new RegExp('^/api'), new RegExp('^/ws'), new RegExp('^/tmp'),],
|
||||||
|
skipWaiting: true,
|
||||||
|
}),
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import * as utils from '../share/utils';
|
||||||
|
|
||||||
const cleanPeriod = 60*1000;//1 минута
|
const cleanPeriod = 60*1000;//1 минута
|
||||||
|
|
||||||
class WebSocketConnection {
|
class WebSocketConnection {
|
||||||
@@ -9,6 +11,8 @@ class WebSocketConnection {
|
|||||||
this.messageQueue = [];
|
this.messageQueue = [];
|
||||||
this.messageLifeTime = messageLifeTime;
|
this.messageLifeTime = messageLifeTime;
|
||||||
this.requestId = 0;
|
this.requestId = 0;
|
||||||
|
|
||||||
|
this.connecting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
addListener(listener) {
|
addListener(listener) {
|
||||||
@@ -53,14 +57,22 @@ class WebSocketConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
open(url) {
|
open(url) {
|
||||||
return new Promise((resolve, reject) => {
|
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) {
|
if (this.ws && this.ws.readyState == WebSocket.OPEN) {
|
||||||
resolve(this.ws);
|
resolve(this.ws);
|
||||||
} else {
|
} else {
|
||||||
let protocol = 'ws:';
|
this.connecting = true;
|
||||||
if (window.location.protocol == 'https:') {
|
const protocol = (window.location.protocol == 'https:' ? 'wss:' : 'ws:');
|
||||||
protocol = 'wss:'
|
|
||||||
}
|
|
||||||
|
|
||||||
url = url || `${protocol}//${window.location.host}/ws`;
|
url = url || `${protocol}//${window.location.host}/ws`;
|
||||||
|
|
||||||
@@ -71,9 +83,8 @@ class WebSocketConnection {
|
|||||||
}
|
}
|
||||||
this.timer = setTimeout(() => { this.periodicClean(); }, cleanPeriod);
|
this.timer = setTimeout(() => { this.periodicClean(); }, cleanPeriod);
|
||||||
|
|
||||||
let resolved = false;
|
|
||||||
this.ws.onopen = (e) => {
|
this.ws.onopen = (e) => {
|
||||||
resolved = true;
|
this.connecting = false;
|
||||||
resolve(e);
|
resolve(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -97,11 +108,13 @@ class WebSocketConnection {
|
|||||||
|
|
||||||
this.ws.onerror = (e) => {
|
this.ws.onerror = (e) => {
|
||||||
this.emit(e.message, true);
|
this.emit(e.message, true);
|
||||||
if (!resolved)
|
if (this.connecting) {
|
||||||
|
this.connecting = false;
|
||||||
reject(e);
|
reject(e);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
})() });
|
||||||
}
|
}
|
||||||
|
|
||||||
//timeout в минутах (cleanPeriod)
|
//timeout в минутах (cleanPeriod)
|
||||||
|
|||||||
5
client/assets/sw-register.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
(function() {
|
||||||
|
if('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.register('/service-worker.js');
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -1,56 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<!--q-layout view="lhr lpr lfr">
|
|
||||||
<q-drawer v-model="showAsideBar" :width="asideWidth">
|
|
||||||
<div class="app-name"><span v-html="appName"></span></div>
|
|
||||||
<q-btn class="el-button-collapse" @click="toggleCollapse"></q-btn>
|
|
||||||
|
|
||||||
<q-list>
|
|
||||||
<q-item clickable v-ripple>
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-icon name="inbox" />
|
|
||||||
</q-item-section>
|
|
||||||
|
|
||||||
<q-item-section>Inbox</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-list-->
|
|
||||||
<!--el-menu class="el-menu-vertical" :default-active="rootRoute" :collapse="isCollapse" router>
|
|
||||||
<el-menu-item index="/cardindex">
|
|
||||||
<i class="el-icon-search"></i>
|
|
||||||
<span :class="itemTitleClass('/cardindex')" slot="title">{{ this.itemRuText['/cardindex'] }}</span>
|
|
||||||
</el-menu-item>
|
|
||||||
<el-menu-item index="/reader">
|
|
||||||
<i class="el-icon-tickets"></i>
|
|
||||||
<span :class="itemTitleClass('/reader')" slot="title">{{ this.itemRuText['/reader'] }}</span>
|
|
||||||
</el-menu-item>
|
|
||||||
<el-menu-item index="/forum" disabled>
|
|
||||||
<i class="el-icon-message"></i>
|
|
||||||
<span :class="itemTitleClass('/forum')" slot="title">{{ this.itemRuText['/forum'] }}</span>
|
|
||||||
</el-menu-item>
|
|
||||||
<el-menu-item index="/income">
|
|
||||||
<i class="el-icon-upload"></i>
|
|
||||||
<span :class="itemTitleClass('/income')" slot="title">{{ this.itemRuText['/income'] }}</span>
|
|
||||||
</el-menu-item>
|
|
||||||
<el-menu-item index="/sources">
|
|
||||||
<i class="el-icon-menu"></i>
|
|
||||||
<span :class="itemTitleClass('/sources')" slot="title">{{ this.itemRuText['/sources'] }}</span>
|
|
||||||
</el-menu-item>
|
|
||||||
<el-menu-item index="/settings">
|
|
||||||
<i class="el-icon-setting"></i>
|
|
||||||
<span :class="itemTitleClass('/settings')" slot="title">{{ this.itemRuText['/settings'] }}</span>
|
|
||||||
</el-menu-item>
|
|
||||||
<el-menu-item index="/help">
|
|
||||||
<i class="el-icon-question"></i>
|
|
||||||
<span :class="itemTitleClass('/help')" slot="title">{{ this.itemRuText['/help'] }}</span>
|
|
||||||
</el-menu-item>
|
|
||||||
</el-menu-->
|
|
||||||
<!--/q-drawer>
|
|
||||||
|
|
||||||
<q-page-container>
|
|
||||||
<keep-alive>
|
|
||||||
<router-view></router-view>
|
|
||||||
</keep-alive>
|
|
||||||
</q-page-container>
|
|
||||||
</q-layout-->
|
|
||||||
<div class="fit row">
|
<div class="fit row">
|
||||||
<Notify ref="notify"/>
|
<Notify ref="notify"/>
|
||||||
<StdDialog ref="stdDialog"/>
|
<StdDialog ref="stdDialog"/>
|
||||||
@@ -90,7 +38,8 @@ class App extends Vue {
|
|||||||
'/sources': 'Источники',
|
'/sources': 'Источники',
|
||||||
'/settings': 'Параметры',
|
'/settings': 'Параметры',
|
||||||
'/help': 'Справка',
|
'/help': 'Справка',
|
||||||
}
|
};
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.commit = this.$store.commit;
|
this.commit = this.$store.commit;
|
||||||
this.dispatch = this.$store.dispatch;
|
this.dispatch = this.$store.dispatch;
|
||||||
@@ -106,10 +55,20 @@ class App extends Vue {
|
|||||||
cachedPath = this.$route.path;
|
cachedPath = this.$route.path;
|
||||||
const m = cachedPath.match(/^(\/[^/]*).*$/i);
|
const m = cachedPath.match(/^(\/[^/]*).*$/i);
|
||||||
cachedRoute = (m ? m[1] : this.$route.path);
|
cachedRoute = (m ? m[1] : this.$route.path);
|
||||||
|
|
||||||
}
|
}
|
||||||
return cachedRoute;
|
return cachedRoute;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.$router.beforeEach((to, from, next) => {
|
||||||
|
//распознавание хоста, если присутствует домен 3-уровня "b.", то разрешена только определенная страница
|
||||||
|
if (window.location.host.indexOf('b.') == 0 && to.path != '/external-libs' && to.path != '/404') {
|
||||||
|
next('/404');
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// set-app-title
|
// set-app-title
|
||||||
this.$root.$on('set-app-title', this.setAppTitle);
|
this.$root.$on('set-app-title', this.setAppTitle);
|
||||||
|
|
||||||
@@ -134,6 +93,9 @@ class App extends Vue {
|
|||||||
document.addEventListener('keyup', (event) => {
|
document.addEventListener('keyup', (event) => {
|
||||||
this.keyHook(event);
|
this.keyHook(event);
|
||||||
});
|
});
|
||||||
|
document.addEventListener('keypress', (event) => {
|
||||||
|
this.keyHook(event);
|
||||||
|
});
|
||||||
document.addEventListener('keydown', (event) => {
|
document.addEventListener('keydown', (event) => {
|
||||||
this.keyHook(event);
|
this.keyHook(event);
|
||||||
});
|
});
|
||||||
@@ -142,6 +104,14 @@ class App extends Vue {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
routerReady() {
|
||||||
|
return new Promise ((resolve) => {
|
||||||
|
this.$router.onReady(() => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$root.notify = this.$refs.notify;
|
this.$root.notify = this.$refs.notify;
|
||||||
this.$root.stdDialog = this.$refs.stdDialog;
|
this.$root.stdDialog = this.$refs.stdDialog;
|
||||||
@@ -157,7 +127,10 @@ class App extends Vue {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.setAppTitle();
|
this.setAppTitle();
|
||||||
|
(async() => {
|
||||||
|
await this.routerReady();
|
||||||
this.redirectIfNeeded();
|
this.redirectIfNeeded();
|
||||||
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleCollapse() {
|
toggleCollapse() {
|
||||||
@@ -202,7 +175,9 @@ class App extends Vue {
|
|||||||
|
|
||||||
setAppTitle(title) {
|
setAppTitle(title) {
|
||||||
if (!title) {
|
if (!title) {
|
||||||
if (this.mode == 'omnireader') {
|
if (this.mode == 'liberama.top') {
|
||||||
|
document.title = `Liberama Reader - всегда с вами`;
|
||||||
|
} 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.$root.rootRoute]}`;
|
||||||
@@ -221,20 +196,22 @@ class App extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get showAsideBar() {
|
get showAsideBar() {
|
||||||
return (this.mode !== null && this.mode != 'reader' && this.mode != 'omnireader');
|
return (this.mode !== null && this.mode != 'reader' && this.mode != 'omnireader' && this.mode != 'liberama.top');
|
||||||
}
|
}
|
||||||
|
|
||||||
set showAsideBar(value) {
|
set showAsideBar(value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
get isReaderActive() {
|
get isReaderActive() {
|
||||||
return this.rootRoute == '/reader';
|
return (this.rootRoute == '/reader' || this.rootRoute == '/external-libs');
|
||||||
}
|
}
|
||||||
|
|
||||||
redirectIfNeeded() {
|
redirectIfNeeded() {
|
||||||
if ((this.mode == 'reader' || this.mode == 'omnireader') && (!this.isReaderActive)) {
|
if ((this.mode == 'reader' || this.mode == 'omnireader' || this.mode == 'liberama.top')) {
|
||||||
//старый url
|
|
||||||
const search = window.location.search.substr(1);
|
const search = window.location.search.substr(1);
|
||||||
|
|
||||||
|
//распознавание параметра url вида "?url=<link>" и редирект при необходимости
|
||||||
|
if (!this.isReaderActive) {
|
||||||
const s = search.split('url=');
|
const s = search.split('url=');
|
||||||
const url = s[1] || '';
|
const url = s[1] || '';
|
||||||
const q = utils.parseQuery(s[0] || '');
|
const q = utils.parseQuery(s[0] || '');
|
||||||
@@ -246,6 +223,7 @@ class App extends Vue {
|
|||||||
this.$router.replace({ path: '/reader', query: q });
|
this.$router.replace({ path: '/reader', query: q });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<template>
|
||||||
|
<Window ref="window" width="600px" height="95%" @close="close">
|
||||||
|
<template slot="header">
|
||||||
|
Настроить закладки
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</Window>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
import Vue from 'vue';
|
||||||
|
import Component from 'vue-class-component';
|
||||||
|
import Window from '../../share/Window.vue';
|
||||||
|
|
||||||
|
const BookmarkSettingsProps = Vue.extend({
|
||||||
|
props: {
|
||||||
|
libs: Object,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default @Component({
|
||||||
|
components: {
|
||||||
|
Window,
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
libs: function() {
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
class BookmarkSettings extends BookmarkSettingsProps {
|
||||||
|
created() {
|
||||||
|
}
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.$refs.window.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.$emit('close');
|
||||||
|
}
|
||||||
|
|
||||||
|
keyHook(event) {
|
||||||
|
if (event.type == 'keydown' && event.key == 'Escape') {
|
||||||
|
this.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
||||||
650
client/components/ExternalLibs/ExternalLibs.vue
Normal file
@@ -0,0 +1,650 @@
|
|||||||
|
<template>
|
||||||
|
<Window ref="window" @close="close" margin="2px">
|
||||||
|
<template slot="header">
|
||||||
|
{{ header }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template slot="buttons">
|
||||||
|
<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-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">На весь экран</q-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div v-show="ready" class="col column" style="min-width: 600px">
|
||||||
|
<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"
|
||||||
|
@popup-show="onSelectPopupShow" @popup-hide="onSelectPopupHide"
|
||||||
|
style="width: 230px"
|
||||||
|
dropdown-icon="la la-angle-down la-sm"
|
||||||
|
rounded outlined dense emit-value map-options display-value-sanitize options-sanitize
|
||||||
|
>
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<q-btn class="q-mr-xs" round dense color="blue" icon="la la-plus" @click.stop="addBookmark" size="12px">
|
||||||
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Добавить закладку</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
<q-btn round dense color="blue" icon="la la-bars" @click.stop="bookmarkSettings" size="12px" disabled>
|
||||||
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Настроить закладки</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</template>
|
||||||
|
<template v-slot:selected>
|
||||||
|
<div style="overflow: hidden; white-space: nowrap;">{{ removeProtocol(rootLink) }}</div>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
|
||||||
|
<q-select class="q-mr-sm" ref="selectedLink" v-model="selectedLink" :options="selectedLinkOptions" @input="selectedLinkInput" style="width: 50px"
|
||||||
|
@popup-show="onSelectPopupShow" @popup-hide="onSelectPopupHide"
|
||||||
|
dropdown-icon="la la-angle-down la-sm"
|
||||||
|
rounded 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-select>
|
||||||
|
|
||||||
|
<q-input class="col q-mr-sm" ref="input" rounded outlined dense bg-color="white" v-model="bookUrl" placeholder="Скопируйте сюда URL книги"
|
||||||
|
@focus="selectAllOnFocus" @keydown="bookUrlKeyDown"
|
||||||
|
>
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<q-btn class="q-mr-xs" round dense color="blue" icon="la la-home" @click="goToLink(libs.startLink)" size="12px">
|
||||||
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Вернуться на стартовую страницу</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
<q-btn round dense color="blue" icon="la la-angle-double-down" @click="openBookUrlInFrame" size="12px">
|
||||||
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Загрузить URL во фрейм</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
|
||||||
|
<q-btn 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-btn>
|
||||||
|
</div>
|
||||||
|
<div class="separator"></div>
|
||||||
|
|
||||||
|
<div class="col fit" style="position: relative;">
|
||||||
|
<iframe v-if="frameVisible" class="fit" ref="frame" :src="frameSrc" frameborder="0"></iframe>
|
||||||
|
<div v-show="transparentLayoutVisible" ref="transparentLayout" class="fit transparent-layout" @click="transparentLayoutClick"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Dialog ref="dialogAddBookmark" v-model="addBookmarkVisible">
|
||||||
|
<template slot="header">
|
||||||
|
<div class="row items-center">
|
||||||
|
<q-icon class="q-mr-sm" name="la la-bookmark" size="28px"></q-icon>
|
||||||
|
Добавить закладку
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<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"
|
||||||
|
placeholder="Ссылка для закладки" maxlength="2000" @focus="selectAllOnFocus">
|
||||||
|
</q-input>
|
||||||
|
|
||||||
|
<q-select class="q-mr-sm" ref="defaultRootLink" v-model="defaultRootLink" :options="defaultRootLinkOptions" @input="defaultRootLinkInput" style="width: 50px"
|
||||||
|
dropdown-icon="la la-angle-down la-sm"
|
||||||
|
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-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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"
|
||||||
|
placeholder="Описание" style="width: 400px" maxlength="100" @focus="selectAllOnFocus">
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template slot="footer">
|
||||||
|
<q-btn class="q-px-md q-ml-sm" dense no-caps v-close-popup>Отмена</q-btn>
|
||||||
|
<q-btn class="q-px-md q-ml-sm" color="primary" dense no-caps @click="okAddBookmark" :disabled="!bookmarkLink">OK</q-btn>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
<BookmarkSettings v-if="bookmarkSettingsActive" ref="bookmarkSettings" :libs="libs" @close="closeBookmarkSettings"></BookmarkSettings>
|
||||||
|
</Window>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
import Vue from 'vue';
|
||||||
|
import Component from 'vue-class-component';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import Window from '../share/Window.vue';
|
||||||
|
import Dialog from '../share/Dialog.vue';
|
||||||
|
import BookmarkSettings from './BookmarkSettings/BookmarkSettings.vue';
|
||||||
|
|
||||||
|
import rstore from '../../store/modules/reader';
|
||||||
|
import * as utils from '../../share/utils';
|
||||||
|
|
||||||
|
const proxySubst = {
|
||||||
|
'http://flibusta.is': 'http://b.liberama.top:23480',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default @Component({
|
||||||
|
components: {
|
||||||
|
Window,
|
||||||
|
Dialog,
|
||||||
|
BookmarkSettings
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
libs: function() {
|
||||||
|
this.loadLibs();
|
||||||
|
},
|
||||||
|
rootLink: function() {
|
||||||
|
this.updateSelectedLink();
|
||||||
|
this.updateStartLink();
|
||||||
|
},
|
||||||
|
selectedLink: function() {
|
||||||
|
this.updateStartLink();
|
||||||
|
},
|
||||||
|
defaultRootLink: function() {
|
||||||
|
this.updateBookmarkLink();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
class ExternalLibs extends Vue {
|
||||||
|
ready = false;
|
||||||
|
frameVisible = false;
|
||||||
|
startLink = '';
|
||||||
|
rootLink = '';
|
||||||
|
selectedLink = '';
|
||||||
|
frameSrc = '';
|
||||||
|
bookUrl = '';
|
||||||
|
libs = {};
|
||||||
|
fullScreenActive = false;
|
||||||
|
addBookmarkVisible = false;
|
||||||
|
transparentLayoutVisible = false;
|
||||||
|
|
||||||
|
bookmarkLink = '';
|
||||||
|
bookmarkDesc = '';
|
||||||
|
defaultRootLink = '';
|
||||||
|
|
||||||
|
bookmarkSettingsActive = false;
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.$root.addKeyHook(this.keyHook);
|
||||||
|
|
||||||
|
document.addEventListener('fullscreenchange', () => {
|
||||||
|
this.fullScreenActive = (document.fullscreenElement !== null);
|
||||||
|
});
|
||||||
|
|
||||||
|
//this.commit = this.$store.commit;
|
||||||
|
//this.commit('reader/setLibs', rstore.libsDefaults);
|
||||||
|
}
|
||||||
|
|
||||||
|
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() => {
|
||||||
|
//подождем this.mode
|
||||||
|
let i = 0;
|
||||||
|
while(!this.mode && i < 100) {
|
||||||
|
await utils.sleep(100);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mode != 'liberama.top') {
|
||||||
|
this.$router.replace('/404');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$refs.window.init();
|
||||||
|
|
||||||
|
this.opener = null;
|
||||||
|
const host = window.location.host;
|
||||||
|
const openerHost = (host.indexOf('b.') == 0 ? host.substring(2) : host);
|
||||||
|
const openerOrigin1 = `http://${openerHost}`;
|
||||||
|
const openerOrigin2 = `https://${openerHost}`;
|
||||||
|
|
||||||
|
window.addEventListener('message', (event) => {
|
||||||
|
if (event.origin !== openerOrigin1 && event.origin !== openerOrigin2)
|
||||||
|
return;
|
||||||
|
if (!_.isObject(event.data) || event.data.from != 'LibsPage')
|
||||||
|
return;
|
||||||
|
if (event.origin == openerOrigin1)
|
||||||
|
this.opener = window.opener;
|
||||||
|
else
|
||||||
|
this.opener = event.source;
|
||||||
|
this.openerOrigin = event.origin;
|
||||||
|
|
||||||
|
//console.log(event);
|
||||||
|
|
||||||
|
this.recvMessage(event.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
//Ожидаем родителя
|
||||||
|
i = 0;
|
||||||
|
while(!this.opener) {
|
||||||
|
await utils.sleep(1000);
|
||||||
|
i++;
|
||||||
|
if (i >= 5) {
|
||||||
|
await this.$root.stdDialog.alert('Нет связи с читалкой. Окно будет закрыто', 'Ошибка');
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Проверка закрытия родительского окна
|
||||||
|
while(this.opener) {
|
||||||
|
await this.checkOpener();
|
||||||
|
await utils.sleep(1000);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
recvMessage(d) {
|
||||||
|
if (d.type == 'mes') {
|
||||||
|
switch(d.data) {
|
||||||
|
case 'hello': this.sendMessage({type: 'mes', data: 'ready'}); break;
|
||||||
|
}
|
||||||
|
} else if (d.type == 'libs') {
|
||||||
|
this.ready = true;
|
||||||
|
this.libs = _.cloneDeep(d.data);
|
||||||
|
if (!this.frameSrc)
|
||||||
|
this.goToLink(this.libs.startLink);
|
||||||
|
} else if (d.type == 'notify') {
|
||||||
|
this.$root.notify.success(d.data, '', {position: 'bottom-right'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessage(d) {
|
||||||
|
(async() => {
|
||||||
|
await this.checkOpener();
|
||||||
|
if (this.opener && this.openerOrigin)
|
||||||
|
this.opener.postMessage(Object.assign({}, {from: 'ExternalLibs'}, d), this.openerOrigin);
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkOpener() {
|
||||||
|
if (this.opener.closed) {
|
||||||
|
await this.$root.stdDialog.alert('Потеряна связь с читалкой. Окно будет закрыто', 'Ошибка');
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commitLibs(libs) {
|
||||||
|
this.sendMessage({type: 'libs', data: libs});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadLibs() {
|
||||||
|
const libs = this.libs;
|
||||||
|
this.startLink = (libs.comment ? libs.comment + ' ': '') + this.removeProtocol(libs.startLink);
|
||||||
|
this.rootLink = this.getOrigin(libs.startLink);
|
||||||
|
this.updateSelectedLink();
|
||||||
|
}
|
||||||
|
|
||||||
|
get mode() {
|
||||||
|
return this.$store.state.config.mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
get header() {
|
||||||
|
let result = (this.ready ? 'Библиотека' : 'Загрузка...');
|
||||||
|
if (this.ready && this.startLink) {
|
||||||
|
result += ` | ${this.startLink}`;
|
||||||
|
}
|
||||||
|
this.$root.$emit('set-app-title', result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSelectedLink() {
|
||||||
|
if (!this.ready)
|
||||||
|
return;
|
||||||
|
const index = this.getRootIndexByUrl(this.libs.groups, this.rootLink);
|
||||||
|
if (index >= 0)
|
||||||
|
this.selectedLink = this.libs.groups[index].s;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStartLink() {
|
||||||
|
if (!this.ready)
|
||||||
|
return;
|
||||||
|
const index = this.getRootIndexByUrl(this.libs.groups, this.rootLink);
|
||||||
|
if (index >= 0) {
|
||||||
|
let libs = _.cloneDeep(this.libs);
|
||||||
|
libs.groups[index].s = this.selectedLink;
|
||||||
|
libs.startLink = this.selectedLink;
|
||||||
|
libs.comment = this.getCommentByLink(libs.groups[index].list, this.selectedLink);
|
||||||
|
this.goToLink(this.selectedLink);
|
||||||
|
this.commitLibs(libs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get rootLinkOptions() {
|
||||||
|
let result = [];
|
||||||
|
if (!this.ready)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
this.libs.groups.forEach(group => {
|
||||||
|
result.push({label: this.removeProtocol(group.r), value: group.r});
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
get defaultRootLinkOptions() {
|
||||||
|
let result = [];
|
||||||
|
|
||||||
|
rstore.libsDefaults.groups.forEach(group => {
|
||||||
|
result.push({label: this.removeProtocol(group.r), value: group.r});
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
get selectedLinkOptions() {
|
||||||
|
let result = [];
|
||||||
|
if (!this.ready)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
const index = this.getRootIndexByUrl(this.libs.groups, this.rootLink);
|
||||||
|
if (index >= 0) {
|
||||||
|
this.libs.groups[index].list.forEach(link => {
|
||||||
|
result.push({label: (link.c ? link.c + ' ': '') + this.removeOrigin(link.l), value: link.l});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
openBookUrlInFrame() {
|
||||||
|
if (this.bookUrl) {
|
||||||
|
this.goToLink(this.addProtocol(this.bookUrl));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
goToLink(link) {
|
||||||
|
if (!this.ready)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.frameSrc = this.makeProxySubst(link);
|
||||||
|
this.frameVisible = false;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.frameVisible = true;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.frame.contentWindow.focus();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addProtocol(url) {
|
||||||
|
if ((url.indexOf('http://') != 0) && (url.indexOf('https://') != 0))
|
||||||
|
return 'http://' + url;
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeProtocol(url) {
|
||||||
|
return url.replace(/(^\w+:|^)\/\//, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrigin(url) {
|
||||||
|
const parsed = new URL(url);
|
||||||
|
return parsed.origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeOrigin(url) {
|
||||||
|
const parsed = new URL(url);
|
||||||
|
const result = url.substring(parsed.origin.length);
|
||||||
|
return (result ? result : '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
getRootIndexByUrl(groups, url) {
|
||||||
|
if (!this.ready)
|
||||||
|
return -1;
|
||||||
|
const origin = this.getOrigin(url);
|
||||||
|
for (let i = 0; i < groups.length; i++) {
|
||||||
|
if (groups[i].r == origin)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
getListItemByLink(list, link) {
|
||||||
|
for (const item of list) {
|
||||||
|
if (item.l == link)
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCommentByLink(list, link) {
|
||||||
|
const item = this.getListItemByLink(list, link);
|
||||||
|
return (item ? item.c : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
makeProxySubst(url, reverse = false) {
|
||||||
|
for (const [key, value] of Object.entries(proxySubst)) {
|
||||||
|
if (reverse && value == url.substring(0, value.length)) {
|
||||||
|
return key + url.substring(value.length);
|
||||||
|
} else if (key == url.substring(0, key.length)) {
|
||||||
|
return value + url.substring(key.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectAllOnFocus(event) {
|
||||||
|
if (event.target.select)
|
||||||
|
event.target.select();
|
||||||
|
}
|
||||||
|
|
||||||
|
rootLinkInput() {
|
||||||
|
this.updateSelectedLink();
|
||||||
|
this.updateStartLink();
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedLinkInput() {
|
||||||
|
this.updateStartLink();
|
||||||
|
}
|
||||||
|
|
||||||
|
submitUrl() {
|
||||||
|
if (this.bookUrl) {
|
||||||
|
this.sendMessage({type: 'submitUrl', data: {
|
||||||
|
url: this.makeProxySubst(this.addProtocol(this.bookUrl), true),
|
||||||
|
force: true
|
||||||
|
}});
|
||||||
|
this.bookUrl = '';
|
||||||
|
if (this.libs.closeAfterSubmit)
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addBookmark() {
|
||||||
|
this.bookmarkLink = (this.bookUrl ? this.makeProxySubst(this.addProtocol(this.bookUrl), true) : '');
|
||||||
|
this.bookmarkDesc = '';
|
||||||
|
this.addBookmarkVisible = true;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.bookmarkLink.focus();
|
||||||
|
this.$refs.defaultRootLink.toggleOption = this.toggleOption;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBookmarkLink() {
|
||||||
|
const index = this.getRootIndexByUrl(rstore.libsDefaults.groups, this.defaultRootLink);
|
||||||
|
if (index >= 0) {
|
||||||
|
this.bookmarkLink = rstore.libsDefaults.groups[index].s;
|
||||||
|
this.bookmarkDesc = this.getCommentByLink(rstore.libsDefaults.groups[index].list, this.bookmarkLink);
|
||||||
|
} else {
|
||||||
|
this.bookmarkLink = '';
|
||||||
|
this.bookmarkDesc = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultRootLinkInput() {
|
||||||
|
this.updateBookmarkLink();
|
||||||
|
}
|
||||||
|
|
||||||
|
bookmarkLinkKeyDown(event) {
|
||||||
|
if (event.key == 'Enter') {
|
||||||
|
this.$refs.bookmarkDesc.focus();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bookmarkDescKeyDown(event) {
|
||||||
|
if (event.key == 'Enter') {
|
||||||
|
this.okAddBookmark();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async okAddBookmark() {
|
||||||
|
if (!this.bookmarkLink)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const link = this.addProtocol(this.bookmarkLink);
|
||||||
|
let index = -1;
|
||||||
|
try {
|
||||||
|
index = this.getRootIndexByUrl(this.libs.groups, link);
|
||||||
|
} catch (e) {
|
||||||
|
await this.$root.stdDialog.alert('Неверный формат ссылки', 'Ошибка');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//есть группа в закладках
|
||||||
|
if (index >= 0) {
|
||||||
|
const item = this.getListItemByLink(this.libs.groups[index].list, link);
|
||||||
|
|
||||||
|
if (!item || item.c != this.bookmarkDesc) {
|
||||||
|
//добавляем
|
||||||
|
let libs = _.cloneDeep(this.libs);
|
||||||
|
|
||||||
|
if (libs.groups[index].list.length >= 100) {
|
||||||
|
await this.$root.stdDialog.alert('Достигнут предел количества закладок для этого сайта', 'Ошибка');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
libs.groups[index].list.push({l: link, c: this.bookmarkDesc});
|
||||||
|
this.commitLibs(libs);
|
||||||
|
}
|
||||||
|
} else {//нет группы в закладках
|
||||||
|
let libs = _.cloneDeep(this.libs);
|
||||||
|
|
||||||
|
if (libs.groups.length >= 100) {
|
||||||
|
await this.$root.stdDialog.alert('Достигнут предел количества различных сайтов в закладках', 'Ошибка');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//добавляем сначала группу
|
||||||
|
libs.groups.push({r: this.getOrigin(link), s: link, list: []});
|
||||||
|
|
||||||
|
index = this.getRootIndexByUrl(libs.groups, link);
|
||||||
|
if (index >= 0)
|
||||||
|
libs.groups[index].list.push({l: link, c: this.bookmarkDesc});
|
||||||
|
|
||||||
|
this.commitLibs(libs);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.addBookmarkVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fullScreenToggle() {
|
||||||
|
this.fullScreenActive = !this.fullScreenActive;
|
||||||
|
if (this.fullScreenActive) {
|
||||||
|
this.$q.fullscreen.request();
|
||||||
|
} else {
|
||||||
|
this.$q.fullscreen.exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transparentLayoutClick() {
|
||||||
|
this.transparentLayoutVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectPopupShow() {
|
||||||
|
this.transparentLayoutVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectPopupHide() {
|
||||||
|
this.transparentLayoutVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.sendMessage({type: 'close'});
|
||||||
|
}
|
||||||
|
|
||||||
|
bookUrlKeyDown(event) {
|
||||||
|
if (event.key == 'Enter') {
|
||||||
|
this.submitUrl();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bookmarkSettings() {
|
||||||
|
this.bookmarkSettingsActive = true;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.bookmarkSettings.init();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
closeBookmarkSettings() {
|
||||||
|
this.bookmarkSettingsActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
keyHook(event) {
|
||||||
|
if (this.$root.rootRoute() == '/external-libs') {
|
||||||
|
if (this.bookmarkSettingsActive && this.$refs.bookmarkSettings.keyHook(event))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (this.$refs.dialogAddBookmark.active)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (event.type == 'keydown' && event.key == 'F4') {
|
||||||
|
this.addBookmark();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type == 'keydown' && event.key == 'Escape' &&
|
||||||
|
(document.activeElement != this.$refs.rootLink.$refs.target || !this.$refs.rootLink.menu) &&
|
||||||
|
(document.activeElement != this.$refs.selectedLink.$refs.target || !this.$refs.selectedLink.menu)
|
||||||
|
) {
|
||||||
|
this.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.separator {
|
||||||
|
height: 1px;
|
||||||
|
background-color: #A0A0A0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-screen-button {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-screen-button:hover {
|
||||||
|
background-color: #69C05F;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transparent-layout {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -91,11 +91,11 @@ class CopyTextPage extends Vue {
|
|||||||
|
|
||||||
close() {
|
close() {
|
||||||
this.stopInit = true;
|
this.stopInit = true;
|
||||||
this.$emit('copy-text-toggle');
|
this.$emit('do-action', {action: 'copyText'});
|
||||||
}
|
}
|
||||||
|
|
||||||
keyHook(event) {
|
keyHook(event) {
|
||||||
if (event.type == 'keydown' && (event.code == 'Escape')) {
|
if (event.type == 'keydown' && event.key == 'Escape') {
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -22,15 +22,15 @@
|
|||||||
на файл из онлайн-библиотеки (например, скопировав адрес ссылки или кнопки "скачать fb2").</p>
|
на файл из онлайн-библиотеки (например, скопировав адрес ссылки или кнопки "скачать 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'">
|
<div v-show="mode == 'omnireader' || mode == 'liberama.top'">
|
||||||
<p>Вы можете добавить в свой браузер закладку, указав в ее свойствах вместо адреса следующий код:
|
<p>Вы можете добавить в свой браузер закладку, указав в ее свойствах вместо адреса следующий код:
|
||||||
<br><strong>javascript:location.href='https://omnireader.ru/?url='+location.href;</strong>
|
<br><strong>{{ bookmarkText }}</strong>
|
||||||
<q-icon class="copy-icon" name="la la-copy" @click="copyText('javascript:location.href=\'https://omnireader.ru/?url=\'+location.href;', 'Код для адреса закладки успешно скопирован в буфер обмена')">
|
<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>или перетащив на панель закладок следующую ссылку:
|
||||||
<br><a style="margin-left: 50px" href="javascript:location.href='https://omnireader.ru/?url='+location.href;">Omni Reader</a>
|
<br><a style="margin-left: 50px" :href="bookmarkText">{{ (mode == 'omnireader' ? 'Omni' : 'Liberama') }} Reader</a>
|
||||||
<br>Тогда, активировав получившуюся закладку на любой странице интернета, вы автоматически загрузите эту страницу в Omni Reader.
|
<br>Тогда, активировав получившуюся закладку на любой странице интернета, вы автоматически загрузите эту страницу в Omni Reader.
|
||||||
<br>В Chrome для Android можно вызывать такую закладку по имени прямо в адресной строке браузера (имя стоит сделать попроще).
|
<br>В Chrome для Android можно вызывать такую закладку по имени прямо в адресной строке браузера (имя стоит сделать попроще).
|
||||||
</p>
|
</p>
|
||||||
@@ -56,6 +56,10 @@ class CommonHelpPage extends Vue {
|
|||||||
return this.$store.state.config.mode;
|
return this.$store.state.config.mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get bookmarkText() {
|
||||||
|
return `javascript:location.href='https://${window.location.host}/?url='+location.href;`
|
||||||
|
}
|
||||||
|
|
||||||
async copyText(text, mes) {
|
async copyText(text, mes) {
|
||||||
const result = await copyTextToClipboard(text);
|
const result = await copyTextToClipboard(text);
|
||||||
const msg = (result ? mes : 'Копирование не удалось');
|
const msg = (result ? mes : 'Копирование не удалось');
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ const pages = {
|
|||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
['CommonHelpPage', 'Общее'],
|
['CommonHelpPage', 'Общее'],
|
||||||
['HotkeysHelpPage', 'Клавиатура'],
|
|
||||||
['MouseHelpPage', 'Мышь/тачскрин'],
|
['MouseHelpPage', 'Мышь/тачскрин'],
|
||||||
|
['HotkeysHelpPage', 'Клавиатура'],
|
||||||
['VersionHistoryPage', 'История версий'],
|
['VersionHistoryPage', 'История версий'],
|
||||||
['DonateHelpPage', 'Помочь проекту'],
|
['DonateHelpPage', 'Помочь проекту'],
|
||||||
];
|
];
|
||||||
@@ -56,7 +56,7 @@ class HelpPage extends Vue {
|
|||||||
selectedTab = 'CommonHelpPage';
|
selectedTab = 'CommonHelpPage';
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
this.$emit('help-toggle');
|
this.$emit('do-action', {action: 'help'});
|
||||||
}
|
}
|
||||||
|
|
||||||
get activePage() {
|
get activePage() {
|
||||||
@@ -81,7 +81,7 @@ class HelpPage extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
keyHook(event) {
|
keyHook(event) {
|
||||||
if (event.type == 'keydown' && (event.code == 'Escape')) {
|
if (event.type == 'keydown' && event.key == 'Escape') {
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,28 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<span class="text-h6 text-bold">Управление с помощью горячих клавиш:</span>
|
<div style="font-size: 120%">
|
||||||
<ul>
|
<div class="text-h6 text-bold">Доступны следующие клавиатурные команды:</div>
|
||||||
<li><b>F1, H</b> - открыть справку</li>
|
<br>
|
||||||
<li><b>Escape</b> - показать/скрыть страницу загрузки</li>
|
</div>
|
||||||
<li><b>Tab, Q</b> - показать/скрыть панель управления</li>
|
<div class="q-mb-md" style="width: 550px">
|
||||||
<li><b>PageUp, Left, Shift+Space, Backspace</b> - страницу назад</li>
|
<div class="text-right text-italic" style="font-size: 80%">* Изменить сочетания клавиш можно в настройках</div>
|
||||||
<li><b>PageDown, Right, Space</b> - страницу вперед</li>
|
<UserHotKeys v-model="userHotKeys" readonly/>
|
||||||
<li><b>Home</b> - в начало книги</li>
|
</div>
|
||||||
<li><b>End</b> - в конец книги</li>
|
|
||||||
<li><b>Up</b> - строчку назад</li>
|
|
||||||
<li><b>Down</b> - строчку вперёд</li>
|
|
||||||
<li><b>A, Shift+A</b> - изменить размер шрифта</li>
|
|
||||||
<li><b>Enter, F, F11, ` (апостроф)</b> - вкл./выкл. полный экран</li>
|
|
||||||
<li><b>Z</b> - вкл./выкл. плавный скроллинг текста</li>
|
|
||||||
<li><b>Shift+Down/Shift+Up</b> - увеличить/уменьшить скорость скроллинга
|
|
||||||
<li><b>P</b> - установить страницу</li>
|
|
||||||
<li><b>Ctrl+F</b> - найти в тексте</li>
|
|
||||||
<li><b>Ctrl+C</b> - скопировать текст со страницы</li>
|
|
||||||
<li><b>R</b> - принудительно обновить книгу в обход кэша</li>
|
|
||||||
<li><b>X</b> - открыть недавние</li>
|
|
||||||
<li><b>O</b> - автономный режим</li>
|
|
||||||
<li><b>S</b> - открыть окно настроек</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -31,11 +16,25 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import Component from 'vue-class-component';
|
import Component from 'vue-class-component';
|
||||||
|
|
||||||
|
import UserHotKeys from '../../SettingsPage/UserHotKeys/UserHotKeys.vue';
|
||||||
|
|
||||||
export default @Component({
|
export default @Component({
|
||||||
|
components: {
|
||||||
|
UserHotKeys,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
class HotkeysHelpPage extends Vue {
|
class HotkeysHelpPage extends Vue {
|
||||||
created() {
|
created() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get userHotKeys() {
|
||||||
|
return this.$store.state.reader.settings.userHotKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
set userHotKeys(value) {
|
||||||
|
//no setter
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
@@ -44,7 +43,5 @@ class HotkeysHelpPage extends Vue {
|
|||||||
.page {
|
.page {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
font-size: 120%;
|
|
||||||
line-height: 130%;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
131
client/components/Reader/LibsPage/LibsPage.vue
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
<template>
|
||||||
|
<div class="hidden"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
import Vue from 'vue';
|
||||||
|
import Component from 'vue-class-component';
|
||||||
|
|
||||||
|
import Window from '../../share/Window.vue';
|
||||||
|
import * as utils from '../../../share/utils';
|
||||||
|
//import rstore from '../../../store/modules/reader';
|
||||||
|
|
||||||
|
export default @Component({
|
||||||
|
components: {
|
||||||
|
Window
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
libs: function() {
|
||||||
|
this.sendLibs();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
class LibsPage extends Vue {
|
||||||
|
created() {
|
||||||
|
this.popupWindow = null;
|
||||||
|
this.commit = this.$store.commit;
|
||||||
|
this.messageListener = null;
|
||||||
|
//this.commit('reader/setLibs', rstore.libsDefaults);
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
if (this.mode != 'liberama.top')
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.childReady = false;
|
||||||
|
const subdomain = (window.location.protocol != 'http:' ? 'b.' : '');
|
||||||
|
this.origin = `http://${subdomain}${window.location.host}`;
|
||||||
|
|
||||||
|
this.messageListener = (event) => {
|
||||||
|
if (event.origin !== this.origin)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//console.log(event.data);
|
||||||
|
|
||||||
|
this.recvMessage(event.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.popupWindow = window.open(`${this.origin}/#/external-libs`);
|
||||||
|
|
||||||
|
if (this.popupWindow) {
|
||||||
|
|
||||||
|
window.addEventListener('message', this.messageListener);
|
||||||
|
|
||||||
|
//Проверка закрытия окна
|
||||||
|
(async() => {
|
||||||
|
while(this.popupWindow) {
|
||||||
|
if (this.popupWindow && this.popupWindow.closed)
|
||||||
|
this.close();
|
||||||
|
await utils.sleep(1000);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
//Установление связи с окном
|
||||||
|
(async() => {
|
||||||
|
let i = 0;
|
||||||
|
while(!this.childReady && this.popupWindow && i < 100) {
|
||||||
|
this.sendMessage({type: 'mes', data: 'hello'});
|
||||||
|
await utils.sleep(100);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
this.sendLibs();
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recvMessage(d) {
|
||||||
|
if (d.type == 'mes') {
|
||||||
|
switch(d.data) {
|
||||||
|
case 'ready':
|
||||||
|
this.childReady = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (d.type == 'libs') {
|
||||||
|
this.commit('reader/setLibs', d.data);
|
||||||
|
} else if (d.type == 'close') {
|
||||||
|
this.close();
|
||||||
|
} else if (d.type == 'submitUrl') {
|
||||||
|
this.$emit('load-book', d.data);
|
||||||
|
this.sendMessage({type: 'notify', data: 'Ссылка передана в читалку'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessage(d) {
|
||||||
|
if (this.popupWindow)
|
||||||
|
this.popupWindow.postMessage(Object.assign({}, {from: 'LibsPage'}, d), this.origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
done() {
|
||||||
|
window.removeEventListener('message', this.messageListener);
|
||||||
|
if (this.popupWindow) {
|
||||||
|
this.popupWindow.close();
|
||||||
|
this.popupWindow = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get mode() {
|
||||||
|
return this.$store.state.config.mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
get libs() {
|
||||||
|
return this.$store.state.reader.libs;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendLibs() {
|
||||||
|
this.sendMessage({type: 'libs', data: this.libs});
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.$emit('libs-close');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.separator {
|
||||||
|
height: 1px;
|
||||||
|
background-color: #A0A0A0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<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 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" cornerColor="#1B695F" gitColor="#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">
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
</q-btn>
|
</q-btn>
|
||||||
|
|
||||||
<div class="q-my-md"></div>
|
<div class="q-my-md"></div>
|
||||||
<div v-if="mode == 'omnireader'">
|
<!--div v-if="mode == 'omnireader'">
|
||||||
<div ref="yaShare2" class="ya-share2"
|
<div ref="yaShare2" class="ya-share2"
|
||||||
data-services="collections,vkontakte,facebook,odnoklassniki,twitter,telegram"
|
data-services="collections,vkontakte,facebook,odnoklassniki,twitter,telegram"
|
||||||
data-description="Чтение fb2-книг онлайн. Загрузка любой страницы интернета одним кликом, синхронизация между устройствами, удобное управление, регистрация не требуется."
|
data-description="Чтение fb2-книг онлайн. Загрузка любой страницы интернета одним кликом, синхронизация между устройствами, удобное управление, регистрация не требуется."
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
data-url="https://omnireader.ru">
|
data-url="https://omnireader.ru">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="q-my-sm"></div>
|
<div class="q-my-sm"></div-->
|
||||||
<span v-if="mode == 'omnireader'" class="bottom-span clickable" @click="openComments">Отзывы о читалке</span>
|
<span v-if="mode == 'omnireader'" class="bottom-span clickable" @click="openComments">Отзывы о читалке</span>
|
||||||
<span v-if="mode == 'omnireader'" class="bottom-span clickable" @click="openOldVersion">Старая версия</span>
|
<span v-if="mode == 'omnireader'" class="bottom-span clickable" @click="openOldVersion">Старая версия</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -82,8 +82,8 @@ class LoaderPage extends Vue {
|
|||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.progress = this.$refs.progress;
|
this.progress = this.$refs.progress;
|
||||||
if (this.mode == 'omnireader')
|
/*if (this.mode == 'omnireader')
|
||||||
Ya.share2(this.$refs.yaShare2);// eslint-disable-line no-undef
|
Ya.share2(this.$refs.yaShare2);// eslint-disable-line no-undef*/
|
||||||
}
|
}
|
||||||
|
|
||||||
activated() {
|
activated() {
|
||||||
@@ -93,6 +93,8 @@ class LoaderPage extends Vue {
|
|||||||
get title() {
|
get title() {
|
||||||
if (this.mode == 'omnireader')
|
if (this.mode == 'omnireader')
|
||||||
return 'Omni Reader - браузерная онлайн-читалка.';
|
return 'Omni Reader - браузерная онлайн-читалка.';
|
||||||
|
if (this.mode == 'liberama.top')
|
||||||
|
return 'Liberama Reader - браузерная онлайн-читалка.';
|
||||||
return 'Универсальная читалка книг и ресурсов интернета.';
|
return 'Универсальная читалка книг и ресурсов интернета.';
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -148,12 +150,12 @@ class LoaderPage extends Vue {
|
|||||||
this.pasteTextActive = !this.pasteTextActive;
|
this.pasteTextActive = !this.pasteTextActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
openHelp() {
|
openHelp(event) {
|
||||||
this.$emit('help-toggle');
|
this.$emit('do-action', {action: 'help', event});
|
||||||
}
|
}
|
||||||
|
|
||||||
openDonate() {
|
openDonate() {
|
||||||
this.$emit('donate-toggle');
|
this.$emit('do-action', {action: 'donate'});
|
||||||
}
|
}
|
||||||
|
|
||||||
openComments() {
|
openComments() {
|
||||||
@@ -171,24 +173,22 @@ class LoaderPage extends Vue {
|
|||||||
|
|
||||||
//недостатки сторонних ui
|
//недостатки сторонних ui
|
||||||
const input = this.$refs.input.$refs.input;
|
const input = this.$refs.input.$refs.input;
|
||||||
if (document.activeElement === input && event.type == 'keydown' && event.code == 'Enter') {
|
if (document.activeElement === input && event.type == 'keydown' && event.key == 'Enter') {
|
||||||
this.submitUrl();
|
this.submitUrl();
|
||||||
}
|
|
||||||
|
|
||||||
if (event.type == 'keydown' && (event.code == 'F1' || (document.activeElement !== input && event.code == 'KeyH'))) {
|
|
||||||
this.$emit('help-toggle');
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.type == 'keydown' && (document.activeElement !== input && event.code == 'KeyQ')) {
|
if (event.type == 'keydown' && document.activeElement !== input) {
|
||||||
this.$emit('tool-bar-toggle');
|
const action = this.$root.readerActionByKeyEvent(event);
|
||||||
event.preventDefault();
|
switch (action) {
|
||||||
event.stopPropagation();
|
case 'help':
|
||||||
|
this.openHelp(event);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ class PasteTextPage extends Vue {
|
|||||||
|
|
||||||
keyHook(event) {
|
keyHook(event) {
|
||||||
if (event.type == 'keydown') {
|
if (event.type == 'keydown') {
|
||||||
switch (event.code) {
|
switch (event.key) {
|
||||||
case 'F2':
|
case 'F2':
|
||||||
this.loadBuffer();
|
this.loadBuffer();
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -2,62 +2,74 @@
|
|||||||
<div class="column no-wrap">
|
<div class="column no-wrap">
|
||||||
<div ref="header" class="header" v-show="toolBarActive">
|
<div ref="header" class="header" v-show="toolBarActive">
|
||||||
<div ref="buttons" class="row justify-between no-wrap">
|
<div ref="buttons" class="row justify-between no-wrap">
|
||||||
|
<div>
|
||||||
<button ref="loader" class="tool-button" :class="buttonActiveClass('loader')" @click="buttonClick('loader')" v-ripple>
|
<button ref="loader" class="tool-button" :class="buttonActiveClass('loader')" @click="buttonClick('loader')" v-ripple>
|
||||||
<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%">Загрузить книгу</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom right" content-style="font-size: 80%">{{ rstore.readerActions['loader'] }}</q-tooltip>
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button ref="undoAction" v-show="showToolButton['undoAction']" class="tool-button" :class="buttonActiveClass('undoAction')" @click="buttonClick('undoAction')" v-ripple>
|
<button ref="undoAction" v-show="showToolButton['undoAction']" class="tool-button" :class="buttonActiveClass('undoAction')" @click="buttonClick('undoAction')" v-ripple>
|
||||||
<q-icon name="la la-angle-left" size="32px"/>
|
<q-icon name="la la-angle-left" size="32px"/>
|
||||||
<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%">{{ 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 ref="redoAction" v-show="showToolButton['redoAction']" class="tool-button" :class="buttonActiveClass('redoAction')" @click="buttonClick('redoAction')" v-ripple>
|
||||||
<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%">Действие вперед</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 ref="fullScreen" v-show="showToolButton['fullScreen']" class="tool-button" :class="buttonActiveClass('fullScreen')" @click="buttonClick('fullScreen')" v-ripple>
|
||||||
<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%">На весь экран</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 ref="scrolling" v-show="showToolButton['scrolling']" class="tool-button" :class="buttonActiveClass('scrolling')" @click="buttonClick('scrolling')" v-ripple>
|
||||||
<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%">Плавный скроллинг</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 ref="setPosition" v-show="showToolButton['setPosition']" class="tool-button" :class="buttonActiveClass('setPosition')" @click="buttonClick('setPosition')" v-ripple>
|
||||||
<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%">На страницу</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 ref="search" v-show="showToolButton['search']" class="tool-button" :class="buttonActiveClass('search')" @click="buttonClick('search')" v-ripple>
|
||||||
<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%">Найти в тексте</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 ref="copyText" v-show="showToolButton['copyText']" class="tool-button" :class="buttonActiveClass('copyText')" @click="buttonClick('copyText')" v-ripple>
|
||||||
<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%">Скопировать текст со страницы</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['copyText'] }}</q-tooltip>
|
||||||
|
</button>
|
||||||
|
<button ref="splitToPara" v-show="showToolButton['splitToPara']" class="tool-button" :class="buttonActiveClass('splitToPara')" @click="buttonClick('splitToPara')" v-ripple>
|
||||||
|
<q-icon name="la la-retweet" size="32px"/>
|
||||||
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['splitToPara'] }}</q-tooltip>
|
||||||
</button>
|
</button>
|
||||||
<button ref="refresh" v-show="showToolButton['refresh']" class="tool-button" :class="buttonActiveClass('refresh')" @click="buttonClick('refresh')" v-ripple>
|
<button ref="refresh" v-show="showToolButton['refresh']" class="tool-button" :class="buttonActiveClass('refresh')" @click="buttonClick('refresh')" v-ripple>
|
||||||
<q-icon name="la la-sync" size="32px"/>
|
<q-icon name="la la-sync" size="32px" :class="{clear: !showRefreshIcon}"/>
|
||||||
<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%">{{ rstore.readerActions['refresh'] }}</q-tooltip>
|
||||||
</button>
|
</button>
|
||||||
<div class="space"></div>
|
<div class="space"></div>
|
||||||
<button ref="offlineMode" v-show="showToolButton['offlineMode']" class="tool-button" :class="buttonActiveClass('offlineMode')" @click="buttonClick('offlineMode')" v-ripple>
|
<button ref="libs" v-show="mode == 'liberama.top' && showToolButton['libs']" class="tool-button" :class="buttonActiveClass('libs')" @click="buttonClick('libs')" v-ripple>
|
||||||
<q-icon name="la la-unlink" size="32px"/>
|
<q-icon name="la la-sitemap" size="32px"/>
|
||||||
<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%">{{ 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 ref="recentBooks" v-show="showToolButton['recentBooks']" class="tool-button" :class="buttonActiveClass('recentBooks')" @click="buttonClick('recentBooks')" v-ripple>
|
||||||
<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%">Открыть недавние</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['recentBooks'] }}</q-tooltip>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button ref="offlineMode" v-show="showToolButton['offlineMode']" class="tool-button" :class="buttonActiveClass('offlineMode')" @click="buttonClick('offlineMode')" v-ripple>
|
||||||
|
<q-icon name="la la-unlink" size="32px"/>
|
||||||
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['offlineMode'] }}</q-tooltip>
|
||||||
|
</button>
|
||||||
<button ref="settings" class="tool-button" :class="buttonActiveClass('settings')" @click="buttonClick('settings')" v-ripple>
|
<button ref="settings" class="tool-button" :class="buttonActiveClass('settings')" @click="buttonClick('settings')" v-ripple>
|
||||||
<q-icon name="la la-cog" size="32px"/>
|
<q-icon name="la la-cog" size="32px"/>
|
||||||
<q-tooltip :delay="1500" anchor="bottom left" content-style="font-size: 80%">Настроить</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom left" content-style="font-size: 80%">{{ rstore.readerActions['settings'] }}</q-tooltip>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="main col row relative-position">
|
<div class="main col row relative-position">
|
||||||
<keep-alive>
|
<keep-alive>
|
||||||
@@ -65,26 +77,22 @@
|
|||||||
@load-book="loadBook"
|
@load-book="loadBook"
|
||||||
@load-file="loadFile"
|
@load-file="loadFile"
|
||||||
@book-pos-changed="bookPosChanged"
|
@book-pos-changed="bookPosChanged"
|
||||||
@tool-bar-toggle="toolBarToggle"
|
@do-action="doAction"
|
||||||
@full-screen-toogle="fullScreenToggle"
|
|
||||||
@stop-scrolling="stopScrolling"
|
|
||||||
@scrolling-toggle="scrollingToggle"
|
|
||||||
@help-toggle="helpToggle"
|
|
||||||
@donate-toggle="donateToggle"
|
|
||||||
></component>
|
></component>
|
||||||
</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"
|
||||||
@search-toggle="searchToggle"
|
@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" @copy-text-toggle="copyTextToggle"></CopyTextPage>
|
<CopyTextPage v-if="copyTextActive" ref="copyTextPage" @do-action="doAction"></CopyTextPage>
|
||||||
|
<LibsPage v-show="libsActive" 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" @settings-toggle="settingsToggle"></SettingsPage>
|
<SettingsPage v-show="settingsActive" ref="settingsPage" @do-action="doAction"></SettingsPage>
|
||||||
<HelpPage v-if="helpActive" ref="helpPage" @help-toggle="helpToggle"></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>
|
<ServerStorage v-show="hidden" ref="serverStorage"></ServerStorage>
|
||||||
|
|
||||||
@@ -164,17 +172,19 @@ import ProgressPage from './ProgressPage/ProgressPage.vue';
|
|||||||
import SetPositionPage from './SetPositionPage/SetPositionPage.vue';
|
import SetPositionPage from './SetPositionPage/SetPositionPage.vue';
|
||||||
import SearchPage from './SearchPage/SearchPage.vue';
|
import SearchPage from './SearchPage/SearchPage.vue';
|
||||||
import CopyTextPage from './CopyTextPage/CopyTextPage.vue';
|
import CopyTextPage from './CopyTextPage/CopyTextPage.vue';
|
||||||
|
import LibsPage from './LibsPage/LibsPage.vue';
|
||||||
import RecentBooksPage from './RecentBooksPage/RecentBooksPage.vue';
|
import RecentBooksPage from './RecentBooksPage/RecentBooksPage.vue';
|
||||||
import SettingsPage from './SettingsPage/SettingsPage.vue';
|
import SettingsPage from './SettingsPage/SettingsPage.vue';
|
||||||
import HelpPage from './HelpPage/HelpPage.vue';
|
import HelpPage from './HelpPage/HelpPage.vue';
|
||||||
import ClickMapPage from './ClickMapPage/ClickMapPage.vue';
|
import ClickMapPage from './ClickMapPage/ClickMapPage.vue';
|
||||||
import ServerStorage from './ServerStorage/ServerStorage.vue';
|
import ServerStorage from './ServerStorage/ServerStorage.vue';
|
||||||
|
import Dialog from '../share/Dialog.vue';
|
||||||
|
|
||||||
import bookManager from './share/bookManager';
|
import bookManager from './share/bookManager';
|
||||||
|
import rstore from '../../store/modules/reader';
|
||||||
import readerApi from '../../api/reader';
|
import readerApi from '../../api/reader';
|
||||||
import * as utils from '../../share/utils';
|
import * as utils from '../../share/utils';
|
||||||
import {versionHistory} from './versionHistory';
|
import {versionHistory} from './versionHistory';
|
||||||
import Dialog from '../share/Dialog.vue';
|
|
||||||
|
|
||||||
export default @Component({
|
export default @Component({
|
||||||
components: {
|
components: {
|
||||||
@@ -185,6 +195,7 @@ export default @Component({
|
|||||||
SetPositionPage,
|
SetPositionPage,
|
||||||
SearchPage,
|
SearchPage,
|
||||||
CopyTextPage,
|
CopyTextPage,
|
||||||
|
LibsPage,
|
||||||
RecentBooksPage,
|
RecentBooksPage,
|
||||||
SettingsPage,
|
SettingsPage,
|
||||||
HelpPage,
|
HelpPage,
|
||||||
@@ -232,7 +243,9 @@ export default @Component({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
class Reader extends Vue {
|
class Reader extends Vue {
|
||||||
|
rstore = {};
|
||||||
loaderActive = false;
|
loaderActive = false;
|
||||||
|
offlineModeActive = false;
|
||||||
progressActive = false;
|
progressActive = false;
|
||||||
fullScreenActive = false;
|
fullScreenActive = false;
|
||||||
|
|
||||||
@@ -240,8 +253,8 @@ class Reader extends Vue {
|
|||||||
setPositionActive = false;
|
setPositionActive = false;
|
||||||
searchActive = false;
|
searchActive = false;
|
||||||
copyTextActive = false;
|
copyTextActive = false;
|
||||||
|
libsActive = false;
|
||||||
recentBooksActive = false;
|
recentBooksActive = false;
|
||||||
offlineModeActive = false;
|
|
||||||
settingsActive = false;
|
settingsActive = false;
|
||||||
helpActive = false;
|
helpActive = false;
|
||||||
clickMapActive = false;
|
clickMapActive = false;
|
||||||
@@ -261,6 +274,7 @@ class Reader extends Vue {
|
|||||||
donationVisible = false;
|
donationVisible = false;
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
|
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.dispatch = this.$store.dispatch;
|
||||||
@@ -317,6 +331,12 @@ class Reader extends Vue {
|
|||||||
this.checkActivateDonateHelpPage();
|
this.checkActivateDonateHelpPage();
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
|
||||||
|
//проверим состояние Settings, и обновим, если надо
|
||||||
|
const newSettings = rstore.addDefaultsToSettings(this.settings);
|
||||||
|
if (newSettings) {
|
||||||
|
this.commit('reader/setSettings', newSettings);
|
||||||
|
}
|
||||||
|
|
||||||
this.updateRoute();
|
this.updateRoute();
|
||||||
|
|
||||||
await this.showWhatsNew();
|
await this.showWhatsNew();
|
||||||
@@ -336,6 +356,11 @@ class Reader extends Vue {
|
|||||||
this.showToolButton = settings.showToolButton;
|
this.showToolButton = settings.showToolButton;
|
||||||
this.enableSitesFilter = settings.enableSitesFilter;
|
this.enableSitesFilter = settings.enableSitesFilter;
|
||||||
|
|
||||||
|
this.readerActionByKeyCode = utils.userHotKeysObjectSwap(settings.userHotKeys);
|
||||||
|
this.$root.readerActionByKeyEvent = (event) => {
|
||||||
|
return this.readerActionByKeyCode[utils.keyEventToCode(event)];
|
||||||
|
}
|
||||||
|
|
||||||
this.updateHeaderMinWidth();
|
this.updateHeaderMinWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,7 +432,7 @@ class Reader extends Vue {
|
|||||||
await utils.sleep(3000);
|
await utils.sleep(3000);
|
||||||
const today = utils.formatDate(new Date(), 'coDate');
|
const today = utils.formatDate(new Date(), 'coDate');
|
||||||
|
|
||||||
if (this.mode == 'omnireader' && today < '2020-03-01' && this.showDonationDialog2020 && this.donationRemindDate != today) {
|
if ((this.mode == 'omnireader' || this.mode == 'liberama.top') && today < '2020-03-01' && this.showDonationDialog2020 && this.donationRemindDate != today) {
|
||||||
this.donationVisible = true;
|
this.donationVisible = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -584,7 +609,7 @@ class Reader extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
closeAllTextPages() {
|
closeAllWindows() {
|
||||||
this.setPositionActive = false;
|
this.setPositionActive = false;
|
||||||
this.copyTextActive = false;
|
this.copyTextActive = false;
|
||||||
this.recentBooksActive = false;
|
this.recentBooksActive = false;
|
||||||
@@ -597,7 +622,7 @@ class Reader extends Vue {
|
|||||||
loaderToggle() {
|
loaderToggle() {
|
||||||
this.loaderActive = !this.loaderActive;
|
this.loaderActive = !this.loaderActive;
|
||||||
if (this.loaderActive) {
|
if (this.loaderActive) {
|
||||||
this.closeAllTextPages();
|
this.closeAllWindows();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -605,7 +630,7 @@ class Reader extends Vue {
|
|||||||
this.setPositionActive = !this.setPositionActive;
|
this.setPositionActive = !this.setPositionActive;
|
||||||
const page = this.$refs.page;
|
const page = this.$refs.page;
|
||||||
if (this.setPositionActive && this.activePage == 'TextPage' && page.parsed) {
|
if (this.setPositionActive && this.activePage == 'TextPage' && page.parsed) {
|
||||||
this.closeAllTextPages();
|
this.closeAllWindows();
|
||||||
this.setPositionActive = true;
|
this.setPositionActive = true;
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
@@ -657,7 +682,7 @@ class Reader extends Vue {
|
|||||||
this.searchActive = !this.searchActive;
|
this.searchActive = !this.searchActive;
|
||||||
const page = this.$refs.page;
|
const page = this.$refs.page;
|
||||||
if (this.searchActive && this.activePage == 'TextPage' && page.parsed) {
|
if (this.searchActive && this.activePage == 'TextPage' && page.parsed) {
|
||||||
this.closeAllTextPages();
|
this.closeAllWindows();
|
||||||
this.searchActive = true;
|
this.searchActive = true;
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
@@ -673,7 +698,7 @@ class Reader extends Vue {
|
|||||||
this.copyTextActive = !this.copyTextActive;
|
this.copyTextActive = !this.copyTextActive;
|
||||||
const page = this.$refs.page;
|
const page = this.$refs.page;
|
||||||
if (this.copyTextActive && this.activePage == 'TextPage' && page.parsed) {
|
if (this.copyTextActive && this.activePage == 'TextPage' && page.parsed) {
|
||||||
this.closeAllTextPages();
|
this.closeAllWindows();
|
||||||
this.copyTextActive = true;
|
this.copyTextActive = true;
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
@@ -684,6 +709,12 @@ class Reader extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refreshBookSplitToPara() {
|
||||||
|
if (this.mostRecentBook()) {
|
||||||
|
this.loadBook({url: this.mostRecentBook().url, skipCheck: true, isText: true, force: true});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
recentBooksClose() {
|
recentBooksClose() {
|
||||||
this.recentBooksActive = false;
|
this.recentBooksActive = false;
|
||||||
}
|
}
|
||||||
@@ -691,7 +722,7 @@ class Reader extends Vue {
|
|||||||
recentBooksToggle() {
|
recentBooksToggle() {
|
||||||
this.recentBooksActive = !this.recentBooksActive;
|
this.recentBooksActive = !this.recentBooksActive;
|
||||||
if (this.recentBooksActive) {
|
if (this.recentBooksActive) {
|
||||||
this.closeAllTextPages();
|
this.closeAllWindows();
|
||||||
this.$refs.recentBooksPage.init();
|
this.$refs.recentBooksPage.init();
|
||||||
this.recentBooksActive = true;
|
this.recentBooksActive = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -699,6 +730,20 @@ class Reader extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
libsClose() {
|
||||||
|
if (this.libsActive)
|
||||||
|
this.libsToogle();
|
||||||
|
}
|
||||||
|
|
||||||
|
libsToogle() {
|
||||||
|
this.libsActive = !this.libsActive;
|
||||||
|
if (this.libsActive) {
|
||||||
|
this.$refs.libsPage.init();
|
||||||
|
} else {
|
||||||
|
this.$refs.libsPage.done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
offlineModeToggle() {
|
offlineModeToggle() {
|
||||||
this.offlineModeActive = !this.offlineModeActive;
|
this.offlineModeActive = !this.offlineModeActive;
|
||||||
this.$refs.serverStorage.offlineModeActive = this.offlineModeActive;
|
this.$refs.serverStorage.offlineModeActive = this.offlineModeActive;
|
||||||
@@ -707,7 +752,7 @@ class Reader extends Vue {
|
|||||||
settingsToggle() {
|
settingsToggle() {
|
||||||
this.settingsActive = !this.settingsActive;
|
this.settingsActive = !this.settingsActive;
|
||||||
if (this.settingsActive) {
|
if (this.settingsActive) {
|
||||||
this.closeAllTextPages();
|
this.closeAllWindows();
|
||||||
this.settingsActive = true;
|
this.settingsActive = true;
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
@@ -721,7 +766,7 @@ class Reader extends Vue {
|
|||||||
helpToggle() {
|
helpToggle() {
|
||||||
this.helpActive = !this.helpActive;
|
this.helpActive = !this.helpActive;
|
||||||
if (this.helpActive) {
|
if (this.helpActive) {
|
||||||
this.closeAllTextPages();
|
this.closeAllWindows();
|
||||||
this.helpActive = true;
|
this.helpActive = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -750,79 +795,52 @@ class Reader extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buttonClick(button) {
|
undoAction() {
|
||||||
const activeClass = this.buttonActiveClass(button);
|
|
||||||
|
|
||||||
this.$refs[button].blur();
|
|
||||||
|
|
||||||
if (activeClass['tool-button-disabled'])
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch (button) {
|
|
||||||
case 'loader':
|
|
||||||
this.loaderToggle();
|
|
||||||
break;
|
|
||||||
case 'undoAction':
|
|
||||||
if (this.actionCur > 0) {
|
if (this.actionCur > 0) {
|
||||||
this.actionCur--;
|
this.actionCur--;
|
||||||
this.bookPosChanged({bookPos: this.actionList[this.actionCur]});
|
this.bookPosChanged({bookPos: this.actionList[this.actionCur]});
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case 'redoAction':
|
|
||||||
|
redoAction() {
|
||||||
if (this.actionCur < this.actionList.length - 1) {
|
if (this.actionCur < this.actionList.length - 1) {
|
||||||
this.actionCur++;
|
this.actionCur++;
|
||||||
this.bookPosChanged({bookPos: this.actionList[this.actionCur]});
|
this.bookPosChanged({bookPos: this.actionList[this.actionCur]});
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case 'fullScreen':
|
|
||||||
this.fullScreenToggle();
|
|
||||||
break;
|
|
||||||
case 'setPosition':
|
|
||||||
this.setPositionToggle();
|
|
||||||
break;
|
|
||||||
case 'scrolling':
|
|
||||||
this.scrollingToggle();
|
|
||||||
break;
|
|
||||||
case 'search':
|
|
||||||
this.searchToggle();
|
|
||||||
break;
|
|
||||||
case 'copyText':
|
|
||||||
this.copyTextToggle();
|
|
||||||
break;
|
|
||||||
case 'refresh':
|
|
||||||
this.refreshBook();
|
|
||||||
break;
|
|
||||||
case 'recentBooks':
|
|
||||||
this.recentBooksToggle();
|
|
||||||
break;
|
|
||||||
case 'offlineMode':
|
|
||||||
this.offlineModeToggle();
|
|
||||||
break;
|
|
||||||
case 'settings':
|
|
||||||
this.settingsToggle();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buttonActiveClass(button) {
|
buttonClick(action) {
|
||||||
|
const activeClass = this.buttonActiveClass(action);
|
||||||
|
|
||||||
|
this.$refs[action].blur();
|
||||||
|
|
||||||
|
if (activeClass['tool-button-disabled'])
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.doAction({action});
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonActiveClass(action) {
|
||||||
const classActive = { 'tool-button-active': true, 'tool-button-active:hover': true };
|
const classActive = { 'tool-button-active': true, 'tool-button-active:hover': true };
|
||||||
const classDisabled = { 'tool-button-disabled': true, 'tool-button-disabled:hover': true };
|
const classDisabled = { 'tool-button-disabled': true, 'tool-button-disabled:hover': true };
|
||||||
let classResult = {};
|
let classResult = {};
|
||||||
|
|
||||||
switch (button) {
|
switch (action) {
|
||||||
case 'loader':
|
case 'loader':
|
||||||
case 'fullScreen':
|
case 'fullScreen':
|
||||||
case 'setPosition':
|
case 'setPosition':
|
||||||
case 'scrolling':
|
case 'scrolling':
|
||||||
case 'search':
|
case 'search':
|
||||||
case 'copyText':
|
case 'copyText':
|
||||||
|
case 'splitToPara':
|
||||||
case 'refresh':
|
case 'refresh':
|
||||||
case 'offlineMode':
|
case 'libs':
|
||||||
case 'recentBooks':
|
case 'recentBooks':
|
||||||
|
case 'offlineMode':
|
||||||
case 'settings':
|
case 'settings':
|
||||||
if (this.progressActive) {
|
if (this.progressActive) {
|
||||||
classResult = classDisabled;
|
classResult = classDisabled;
|
||||||
} else if (this[`${button}Active`]) {
|
} else if (this[`${action}Active`]) {
|
||||||
classResult = classActive;
|
classResult = classActive;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -837,7 +855,7 @@ class Reader extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.activePage == 'LoaderPage' || !this.mostRecentBookReactive) {
|
if (this.activePage == 'LoaderPage' || !this.mostRecentBookReactive) {
|
||||||
switch (button) {
|
switch (action) {
|
||||||
case 'undoAction':
|
case 'undoAction':
|
||||||
case 'redoAction':
|
case 'redoAction':
|
||||||
case 'setPosition':
|
case 'setPosition':
|
||||||
@@ -846,8 +864,9 @@ class Reader extends Vue {
|
|||||||
case 'copyText':
|
case 'copyText':
|
||||||
classResult = classDisabled;
|
classResult = classDisabled;
|
||||||
break;
|
break;
|
||||||
case 'recentBooks':
|
case 'splitToPara':
|
||||||
case 'refresh':
|
case 'refresh':
|
||||||
|
case 'recentBooks':
|
||||||
if (!this.mostRecentBookReactive)
|
if (!this.mostRecentBookReactive)
|
||||||
classResult = classDisabled;
|
classResult = classDisabled;
|
||||||
break;
|
break;
|
||||||
@@ -922,7 +941,7 @@ class Reader extends Vue {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.closeAllTextPages();
|
this.closeAllWindows();
|
||||||
|
|
||||||
let url = encodeURI(decodeURI(opts.url));
|
let url = encodeURI(decodeURI(opts.url));
|
||||||
|
|
||||||
@@ -1000,9 +1019,16 @@ class Reader extends Vue {
|
|||||||
// не удалось, скачиваем книгу полностью с конвертацией
|
// не удалось, скачиваем книгу полностью с конвертацией
|
||||||
let loadCached = true;
|
let loadCached = true;
|
||||||
if (!book) {
|
if (!book) {
|
||||||
book = await readerApi.loadBook({url, enableSitesFilter: this.enableSitesFilter}, (state) => {
|
book = await readerApi.loadBook({
|
||||||
|
url,
|
||||||
|
skipCheck: (opts.skipCheck ? true : false),
|
||||||
|
isText: (opts.isText ? true : false),
|
||||||
|
enableSitesFilter: this.enableSitesFilter
|
||||||
|
},
|
||||||
|
(state) => {
|
||||||
progress.setState(state);
|
progress.setState(state);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
loadCached = false;
|
loadCached = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1030,7 +1056,7 @@ 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, 'Ошибка', {type: 'negative'});
|
this.$root.stdDialog.alert(e.message, 'Ошибка', {color: 'negative'});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1054,7 +1080,7 @@ 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, 'Ошибка', {type: 'negative'});
|
this.$root.stdDialog.alert(e.message, 'Ошибка', {color: 'negative'});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1086,84 +1112,160 @@ class Reader extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
keyHook(event) {
|
doAction(opts) {
|
||||||
if (this.$root.rootRoute() == '/reader') {
|
let result = true;
|
||||||
if (this.$root.stdDialog.active || this.$refs.dialog1.active || this.$refs.dialog2.active)
|
let {action = '', event = false} = opts;
|
||||||
return;
|
|
||||||
|
|
||||||
let handled = false;
|
switch (action) {
|
||||||
if (!handled && this.helpActive)
|
case 'loader':
|
||||||
handled = this.$refs.helpPage.keyHook(event);
|
|
||||||
|
|
||||||
if (!handled && this.settingsActive)
|
|
||||||
handled = this.$refs.settingsPage.keyHook(event);
|
|
||||||
|
|
||||||
if (!handled && this.recentBooksActive)
|
|
||||||
handled = this.$refs.recentBooksPage.keyHook(event);
|
|
||||||
|
|
||||||
if (!handled && this.setPositionActive)
|
|
||||||
handled = this.$refs.setPositionPage.keyHook(event);
|
|
||||||
|
|
||||||
if (!handled && this.searchActive)
|
|
||||||
handled = this.$refs.searchPage.keyHook(event);
|
|
||||||
|
|
||||||
if (!handled && this.copyTextActive)
|
|
||||||
handled = this.$refs.copyTextPage.keyHook(event);
|
|
||||||
|
|
||||||
if (!handled && this.$refs.page && this.$refs.page.keyHook)
|
|
||||||
handled = this.$refs.page.keyHook(event);
|
|
||||||
|
|
||||||
if (!handled && event.type == 'keydown') {
|
|
||||||
if (event.code == 'Escape')
|
|
||||||
this.loaderToggle();
|
this.loaderToggle();
|
||||||
|
|
||||||
if (this.activePage == 'TextPage') {
|
|
||||||
switch (event.code) {
|
|
||||||
case 'KeyH':
|
|
||||||
case 'F1':
|
|
||||||
this.helpToggle();
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
break;
|
break;
|
||||||
case 'KeyZ':
|
case 'help':
|
||||||
|
this.helpToggle();
|
||||||
|
break;
|
||||||
|
case 'undoAction':
|
||||||
|
this.undoAction();
|
||||||
|
break;
|
||||||
|
case 'redoAction':
|
||||||
|
this.redoAction();
|
||||||
|
break;
|
||||||
|
case 'fullScreen':
|
||||||
|
this.fullScreenToggle();
|
||||||
|
break;
|
||||||
|
case 'scrolling':
|
||||||
this.scrollingToggle();
|
this.scrollingToggle();
|
||||||
break;
|
break;
|
||||||
case 'KeyP':
|
case 'stopScrolling':
|
||||||
|
this.stopScrolling();
|
||||||
|
break;
|
||||||
|
case 'setPosition':
|
||||||
this.setPositionToggle();
|
this.setPositionToggle();
|
||||||
break;
|
break;
|
||||||
case 'KeyF':
|
case 'search':
|
||||||
if (event.ctrlKey) {
|
|
||||||
this.searchToggle();
|
this.searchToggle();
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'KeyC':
|
case 'copyText':
|
||||||
if (event.ctrlKey) {
|
|
||||||
this.copyTextToggle();
|
this.copyTextToggle();
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'KeyR':
|
case 'splitToPara':
|
||||||
|
this.refreshBookSplitToPara();
|
||||||
|
break;
|
||||||
|
case 'refresh':
|
||||||
this.refreshBook();
|
this.refreshBook();
|
||||||
break;
|
break;
|
||||||
case 'KeyX':
|
case 'libs':
|
||||||
this.recentBooksToggle();
|
this.libsToogle();
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
break;
|
break;
|
||||||
case 'KeyO':
|
case 'recentBooks':
|
||||||
|
this.recentBooksToggle();
|
||||||
|
break;
|
||||||
|
case 'offlineMode':
|
||||||
this.offlineModeToggle();
|
this.offlineModeToggle();
|
||||||
break;
|
break;
|
||||||
case 'KeyS':
|
case 'settings':
|
||||||
this.settingsToggle();
|
this.settingsToggle();
|
||||||
break;
|
break;
|
||||||
|
case 'switchToolbar':
|
||||||
|
this.toolBarToggle();
|
||||||
|
break;
|
||||||
|
case 'donate':
|
||||||
|
this.donateToggle();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
result = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result && this.activePage == 'TextPage' && this.$refs.page) {
|
||||||
|
result = true;
|
||||||
|
const textPage = this.$refs.page;
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case 'bookBegin':
|
||||||
|
textPage.doHome();
|
||||||
|
break;
|
||||||
|
case 'bookEnd':
|
||||||
|
textPage.doEnd();
|
||||||
|
break;
|
||||||
|
case 'pageBack':
|
||||||
|
textPage.doPageUp();
|
||||||
|
break;
|
||||||
|
case 'pageForward':
|
||||||
|
textPage.doPageDown();
|
||||||
|
break;
|
||||||
|
case 'lineBack':
|
||||||
|
textPage.doUp();
|
||||||
|
break;
|
||||||
|
case 'lineForward':
|
||||||
|
textPage.doDown();
|
||||||
|
break;
|
||||||
|
case 'incFontSize':
|
||||||
|
textPage.doFontSizeInc();
|
||||||
|
break;
|
||||||
|
case 'decFontSize':
|
||||||
|
textPage.doFontSizeDec();
|
||||||
|
break;
|
||||||
|
case 'scrollingSpeedUp':
|
||||||
|
textPage.doScrollingSpeedUp();
|
||||||
|
break;
|
||||||
|
case 'scrollingSpeedDown':
|
||||||
|
textPage.doScrollingSpeedDown();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
result = false;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result && event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
keyHook(event) {
|
||||||
|
let result = false;
|
||||||
|
if (this.$root.rootRoute() == '/reader') {
|
||||||
|
if (this.$root.stdDialog.active || this.$refs.dialog1.active || this.$refs.dialog2.active)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
if (!result && this.helpActive)
|
||||||
|
result = this.$refs.helpPage.keyHook(event);
|
||||||
|
|
||||||
|
if (!result && this.settingsActive)
|
||||||
|
result = this.$refs.settingsPage.keyHook(event);
|
||||||
|
|
||||||
|
if (!result && this.recentBooksActive)
|
||||||
|
result = this.$refs.recentBooksPage.keyHook(event);
|
||||||
|
|
||||||
|
if (!result && this.setPositionActive)
|
||||||
|
result = this.$refs.setPositionPage.keyHook(event);
|
||||||
|
|
||||||
|
if (!result && this.searchActive)
|
||||||
|
result = this.$refs.searchPage.keyHook(event);
|
||||||
|
|
||||||
|
if (!result && this.copyTextActive)
|
||||||
|
result = this.$refs.copyTextPage.keyHook(event);
|
||||||
|
|
||||||
|
if (!result && this.$refs.page && this.$refs.page.keyHook)
|
||||||
|
result = this.$refs.page.keyHook(event);
|
||||||
|
|
||||||
|
if (!result && event.type == 'keydown') {
|
||||||
|
const action = this.$root.readerActionByKeyEvent(event);
|
||||||
|
|
||||||
|
if (action == 'loader') {
|
||||||
|
result = this.doAction({action, event});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result && this.activePage == 'TextPage') {
|
||||||
|
result = this.doAction({action, event});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<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;'></a>
|
<a ref="download" style='display: none;' target="_blank"></a>
|
||||||
|
|
||||||
<q-table
|
<q-table
|
||||||
class="recent-books-table col"
|
class="recent-books-table col"
|
||||||
@@ -298,12 +298,13 @@ class RecentBooksPage extends Vue {
|
|||||||
const d = this.$refs.download;
|
const d = this.$refs.download;
|
||||||
d.href = fb2path;
|
d.href = fb2path;
|
||||||
d.download = path.basename(fb2path).substr(0, 10) + '.fb2';
|
d.download = path.basename(fb2path).substr(0, 10) + '.fb2';
|
||||||
|
|
||||||
d.click();
|
d.click();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
let errMes = e.message;
|
let errMes = e.message;
|
||||||
if (errMes.indexOf('404') >= 0)
|
if (errMes.indexOf('404') >= 0)
|
||||||
errMes = 'Файл не найден на сервере (возможно был удален как устаревший)';
|
errMes = 'Файл не найден на сервере (возможно был удален как устаревший)';
|
||||||
this.$root.stdDialog.alert(errMes, 'Ошибка', {type: 'negative'});
|
this.$root.stdDialog.alert(errMes, 'Ошибка', {color: 'negative'});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,7 +341,7 @@ class RecentBooksPage extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
keyHook(event) {
|
keyHook(event) {
|
||||||
if (!this.$root.stdDialog.active && event.type == 'keydown' && event.code == 'Escape') {
|
if (!this.$root.stdDialog.active && event.type == 'keydown' && event.key == 'Escape') {
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ class SearchPage extends Vue {
|
|||||||
|
|
||||||
close() {
|
close() {
|
||||||
this.stopInit = true;
|
this.stopInit = true;
|
||||||
this.$emit('search-toggle');
|
this.$emit('do-action', {action: 'search'});
|
||||||
}
|
}
|
||||||
|
|
||||||
inputKeyDown(event) {
|
inputKeyDown(event) {
|
||||||
@@ -174,7 +174,7 @@ class SearchPage extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
keyHook(event) {
|
keyHook(event) {
|
||||||
if (event.type == 'keydown' && (event.code == 'Escape')) {
|
if (event.type == 'keydown' && event.key == 'Escape') {
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div></div>
|
<div class="hidden"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -35,6 +35,9 @@ export default @Component({
|
|||||||
currentProfile: function() {
|
currentProfile: function() {
|
||||||
this.currentProfileChanged(true);
|
this.currentProfileChanged(true);
|
||||||
},
|
},
|
||||||
|
libs: function() {
|
||||||
|
this.debouncedSaveLibs();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
class ServerStorage extends Vue {
|
class ServerStorage extends Vue {
|
||||||
@@ -49,27 +52,32 @@ class ServerStorage extends Vue {
|
|||||||
this.saveSettings();
|
this.saveSettings();
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
|
this.debouncedSaveLibs = _.debounce(() => {
|
||||||
|
this.saveLibs();
|
||||||
|
}, 500);
|
||||||
|
|
||||||
this.debouncedNotifySuccess = _.debounce(() => {
|
this.debouncedNotifySuccess = _.debounce(() => {
|
||||||
this.success('Данные синхронизированы с сервером');
|
this.success('Данные синхронизированы с сервером');
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
this.oldProfiles = {};
|
this.oldProfiles = {};
|
||||||
this.oldSettings = {};
|
this.oldSettings = {};
|
||||||
|
this.oldLibs = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
try {
|
try {
|
||||||
this.cachedRecent = await ssCacheStore.getItem('recent');
|
this.cachedRecent = await ssCacheStore.getItem('recent');
|
||||||
if (!this.cachedRecent)
|
if (!this.cachedRecent)
|
||||||
await this.setCachedRecent({rev: 0, data: {}});
|
await this.cleanCachedRecent('cachedRecent');
|
||||||
|
|
||||||
this.cachedRecentPatch = await ssCacheStore.getItem('recent-patch');
|
this.cachedRecentPatch = await ssCacheStore.getItem('recent-patch');
|
||||||
if (!this.cachedRecentPatch)
|
if (!this.cachedRecentPatch)
|
||||||
await this.setCachedRecentPatch({rev: 0, data: {}});
|
await this.cleanCachedRecent('cachedRecentPatch');
|
||||||
|
|
||||||
this.cachedRecentMod = await ssCacheStore.getItem('recent-mod');
|
this.cachedRecentMod = await ssCacheStore.getItem('recent-mod');
|
||||||
if (!this.cachedRecentMod)
|
if (!this.cachedRecentMod)
|
||||||
await this.setCachedRecentMod({rev: 0, data: {}});
|
await this.cleanCachedRecent('cachedRecentMod');
|
||||||
|
|
||||||
if (!this.serverStorageKey) {
|
if (!this.serverStorageKey) {
|
||||||
//генерируем новый ключ
|
//генерируем новый ключ
|
||||||
@@ -97,6 +105,15 @@ class ServerStorage extends Vue {
|
|||||||
this.cachedRecentMod = value;
|
this.cachedRecentMod = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async cleanCachedRecent(whatToClean) {
|
||||||
|
if (whatToClean == 'cachedRecent' || whatToClean == 'all')
|
||||||
|
await this.setCachedRecent({rev: 0, data: {}});
|
||||||
|
if (whatToClean == 'cachedRecentPatch' || whatToClean == 'all')
|
||||||
|
await this.setCachedRecentPatch({rev: 0, data: {}});
|
||||||
|
if (whatToClean == 'cachedRecentMod' || whatToClean == 'all')
|
||||||
|
await this.setCachedRecentMod({rev: 0, data: {}});
|
||||||
|
}
|
||||||
|
|
||||||
async generateNewServerStorageKey() {
|
async generateNewServerStorageKey() {
|
||||||
const key = utils.toBase58(utils.randomArray(32));
|
const key = utils.toBase58(utils.randomArray(32));
|
||||||
this.commit('reader/setServerStorageKey', key);
|
this.commit('reader/setServerStorageKey', key);
|
||||||
@@ -124,11 +141,16 @@ class ServerStorage extends Vue {
|
|||||||
await this.loadProfiles(force);
|
await this.loadProfiles(force);
|
||||||
this.checkCurrentProfile();
|
this.checkCurrentProfile();
|
||||||
await this.currentProfileChanged(force);
|
await this.currentProfileChanged(force);
|
||||||
|
await this.loadLibs(force);
|
||||||
|
|
||||||
|
if (force)
|
||||||
|
await this.cleanCachedRecent('all');
|
||||||
const loadSuccess = await this.loadRecent();
|
const loadSuccess = await this.loadRecent();
|
||||||
if (loadSuccess && force)
|
if (loadSuccess && force) {
|
||||||
await this.saveRecent();
|
await this.saveRecent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async currentProfileChanged(force) {
|
async currentProfileChanged(force) {
|
||||||
if (!this.currentProfile)
|
if (!this.currentProfile)
|
||||||
@@ -169,6 +191,14 @@ class ServerStorage extends Vue {
|
|||||||
return this.settings.showServerStorageMessages;
|
return this.settings.showServerStorageMessages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get libs() {
|
||||||
|
return this.$store.state.reader.libs;
|
||||||
|
}
|
||||||
|
|
||||||
|
get libsRev() {
|
||||||
|
return this.$store.state.reader.libsRev;
|
||||||
|
}
|
||||||
|
|
||||||
checkCurrentProfile() {
|
checkCurrentProfile() {
|
||||||
if (!this.profiles[this.currentProfile]) {
|
if (!this.profiles[this.currentProfile]) {
|
||||||
this.commit('reader/setCurrentProfile', '');
|
this.commit('reader/setCurrentProfile', '');
|
||||||
@@ -345,6 +375,78 @@ class ServerStorage extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async loadLibs(force = false, doNotifySuccess = true) {
|
||||||
|
if (!this.keyInited || !this.serverSyncEnabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const oldRev = this.libsRev;
|
||||||
|
//проверим ревизию на сервере
|
||||||
|
if (!force) {
|
||||||
|
try {
|
||||||
|
const revs = await this.storageCheck({libs: {}});
|
||||||
|
if (revs.state == 'success' && revs.items.libs.rev == oldRev) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
this.error(`Ошибка соединения с сервером: ${e.message}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let libs = null;
|
||||||
|
try {
|
||||||
|
libs = await this.storageGet({libs: {}});
|
||||||
|
} catch(e) {
|
||||||
|
this.error(`Ошибка соединения с сервером: ${e.message}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (libs.state == 'success') {
|
||||||
|
libs = libs.items.libs;
|
||||||
|
|
||||||
|
if (libs.rev == 0)
|
||||||
|
libs.data = {};
|
||||||
|
|
||||||
|
this.oldLibs = _.cloneDeep(libs.data);
|
||||||
|
this.commit('reader/setLibs', libs.data);
|
||||||
|
this.commit('reader/setLibsRev', libs.rev);
|
||||||
|
|
||||||
|
if (doNotifySuccess)
|
||||||
|
this.debouncedNotifySuccess();
|
||||||
|
} else {
|
||||||
|
this.warning(`Неверный ответ сервера: ${libs.state}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveLibs() {
|
||||||
|
if (!this.keyInited || !this.serverSyncEnabled || this.savingLibs)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const diff = utils.getObjDiff(this.oldLibs, this.libs);
|
||||||
|
if (utils.isEmptyObjDiff(diff))
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.savingLibs = true;
|
||||||
|
try {
|
||||||
|
let result = {state: ''};
|
||||||
|
try {
|
||||||
|
result = await this.storageSet({libs: {rev: this.libsRev + 1, data: this.libs}});
|
||||||
|
} catch(e) {
|
||||||
|
this.error(`Ошибка соединения с сервером (${e.message}). Данные не сохранены и могут быть перезаписаны.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.state == 'reject') {
|
||||||
|
await this.loadLibs(true, false);
|
||||||
|
this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
|
||||||
|
} else if (result.state == 'success') {
|
||||||
|
this.oldLibs = _.cloneDeep(this.libs);
|
||||||
|
this.commit('reader/setLibsRev', this.libsRev + 1);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.savingLibs = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async loadRecent(skipRevCheck = false, doNotifySuccess = true) {
|
async loadRecent(skipRevCheck = false, doNotifySuccess = true) {
|
||||||
if (!this.keyInited || !this.serverSyncEnabled || this.loadingRecent)
|
if (!this.keyInited || !this.serverSyncEnabled || this.loadingRecent)
|
||||||
return;
|
return;
|
||||||
@@ -403,7 +505,7 @@ class ServerStorage extends Vue {
|
|||||||
|
|
||||||
const md = newRecentMod.data;
|
const md = newRecentMod.data;
|
||||||
if (md.key && result[md.key])
|
if (md.key && result[md.key])
|
||||||
result[md.key] = utils.applyObjDiff(result[md.key], md.mod, true);
|
result[md.key] = utils.applyObjDiff(result[md.key], md.mod, {isAddChanged: true});
|
||||||
|
|
||||||
if (!bookManager.loaded) {
|
if (!bookManager.loaded) {
|
||||||
this.warning('Ожидание загрузки списка книг перед синхронизацией');
|
this.warning('Ожидание загрузки списка книг перед синхронизацией');
|
||||||
@@ -467,7 +569,7 @@ class ServerStorage extends Vue {
|
|||||||
|
|
||||||
let applyMod = this.cachedRecentMod.data;
|
let 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, true);
|
newRecentPatch.data[applyMod.key] = utils.applyObjDiff(newRecentPatch.data[applyMod.key], applyMod.mod, {isAddChanged: true});
|
||||||
|
|
||||||
newRecentMod = {rev: this.cachedRecentMod.rev + 1, data: {}};
|
newRecentMod = {rev: this.cachedRecentMod.rev + 1, data: {}};
|
||||||
needSaveRecentPatch = true;
|
needSaveRecentPatch = true;
|
||||||
|
|||||||
@@ -58,9 +58,12 @@ class SetPositionPage extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
keyHook(event) {
|
keyHook(event) {
|
||||||
if (event.type == 'keydown' && (event.code == 'Escape' || event.code == 'KeyP')) {
|
if (event.type == 'keydown') {
|
||||||
|
const action = this.$root.readerActionByKeyEvent(event);
|
||||||
|
if (event.key == 'Escape' || action == 'setPosition') {
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
@@include('./include/ButtonsTab.inc');
|
@@include('./include/ButtonsTab.inc');
|
||||||
</div>
|
</div>
|
||||||
<!-- Управление ------------------------------------------------------------------>
|
<!-- Управление ------------------------------------------------------------------>
|
||||||
<div v-if="selectedTab == 'keys'" class="fit tab-panel">
|
<div v-if="selectedTab == 'keys'" class="fit column">
|
||||||
@@include('./include/KeysTab.inc');
|
@@include('./include/KeysTab.inc');
|
||||||
</div>
|
</div>
|
||||||
<!-- Листание -------------------------------------------------------------------->
|
<!-- Листание -------------------------------------------------------------------->
|
||||||
@@ -76,6 +76,8 @@ import _ from 'lodash';
|
|||||||
import * as utils from '../../../share/utils';
|
import * as utils from '../../../share/utils';
|
||||||
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 rstore from '../../../store/modules/reader';
|
import rstore from '../../../store/modules/reader';
|
||||||
import defPalette from './defPalette';
|
import defPalette from './defPalette';
|
||||||
|
|
||||||
@@ -85,6 +87,7 @@ export default @Component({
|
|||||||
components: {
|
components: {
|
||||||
Window,
|
Window,
|
||||||
NumInput,
|
NumInput,
|
||||||
|
UserHotKeys,
|
||||||
},
|
},
|
||||||
data: function() {
|
data: function() {
|
||||||
return Object.assign({}, rstore.settingDefaults);
|
return Object.assign({}, rstore.settingDefaults);
|
||||||
@@ -105,8 +108,10 @@ 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) {
|
||||||
this.fontShifts = Object.assign({}, this.fontShifts, {[font]: newValue});
|
this.fontShifts = Object.assign({}, this.fontShifts, {[font]: newValue});
|
||||||
this.fontVertShift = newValue;
|
this.fontVertShift = newValue;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
fontName: function(newValue) {
|
fontName: function(newValue) {
|
||||||
const font = (this.webFontName ? this.webFontName : newValue);
|
const font = (this.webFontName ? this.webFontName : newValue);
|
||||||
@@ -139,6 +144,7 @@ export default @Component({
|
|||||||
class SettingsPage extends Vue {
|
class SettingsPage extends Vue {
|
||||||
selectedTab = 'profiles';
|
selectedTab = 'profiles';
|
||||||
selectedViewTab = 'color';
|
selectedViewTab = 'color';
|
||||||
|
selectedKeysTab = 'mouse';
|
||||||
form = {};
|
form = {};
|
||||||
fontBold = false;
|
fontBold = false;
|
||||||
fontItalic = false;
|
fontItalic = false;
|
||||||
@@ -152,12 +158,14 @@ class SettingsPage extends Vue {
|
|||||||
|
|
||||||
serverStorageKeyVisible = false;
|
serverStorageKeyVisible = false;
|
||||||
toolButtons = [];
|
toolButtons = [];
|
||||||
|
rstore = {};
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.commit = this.$store.commit;
|
this.commit = this.$store.commit;
|
||||||
this.reader = this.$store.state.reader;
|
this.reader = this.$store.state.reader;
|
||||||
|
|
||||||
this.form = {};
|
this.form = {};
|
||||||
|
this.rstore = rstore;
|
||||||
this.toolButtons = rstore.toolButtons;
|
this.toolButtons = rstore.toolButtons;
|
||||||
this.settingsChanged();
|
this.settingsChanged();
|
||||||
}
|
}
|
||||||
@@ -179,10 +187,18 @@ class SettingsPage extends Vue {
|
|||||||
settingsChanged() {
|
settingsChanged() {
|
||||||
if (_.isEqual(this.form, this.settings))
|
if (_.isEqual(this.form, this.settings))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.form = Object.assign({}, this.settings);
|
this.form = Object.assign({}, this.settings);
|
||||||
|
if (!this.unwatch)
|
||||||
|
this.unwatch = {};
|
||||||
|
|
||||||
for (let prop in rstore.settingDefaults) {
|
for (let prop in rstore.settingDefaults) {
|
||||||
|
if (this.unwatch && this.unwatch[prop])
|
||||||
|
this.unwatch[prop]();
|
||||||
|
|
||||||
this[prop] = this.form[prop];
|
this[prop] = this.form[prop];
|
||||||
this.$watch(prop, (newValue) => {
|
|
||||||
|
this.unwatch[prop] = this.$watch(prop, (newValue) => {
|
||||||
this.form = Object.assign({}, this.form, {[prop]: newValue});
|
this.form = Object.assign({}, this.form, {[prop]: newValue});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -339,7 +355,7 @@ class SettingsPage extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
this.$emit('settings-toggle');
|
this.$emit('do-action', {action: 'settings'});
|
||||||
}
|
}
|
||||||
|
|
||||||
async setDefaults() {
|
async setDefaults() {
|
||||||
@@ -493,7 +509,7 @@ class SettingsPage extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
keyHook(event) {
|
keyHook(event) {
|
||||||
if (!this.$root.stdDialog.active && event.type == 'keydown' && event.code == 'Escape') {
|
if (!this.$root.stdDialog.active && event.type == 'keydown' && event.key == 'Escape') {
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -0,0 +1,248 @@
|
|||||||
|
<template>
|
||||||
|
<div class="table col column no-wrap">
|
||||||
|
<!-- header -->
|
||||||
|
<div class="table-row row">
|
||||||
|
<div class="desc q-pa-sm bg-blue-2">Команда</div>
|
||||||
|
<div class="hotKeys col q-pa-sm bg-blue-2 row no-wrap">
|
||||||
|
<div style="width: 80px">Сочетание клавиш</div>
|
||||||
|
<q-input ref="input" class="q-ml-sm col"
|
||||||
|
outlined dense rounded
|
||||||
|
bg-color="grey-4"
|
||||||
|
placeholder="Найти"
|
||||||
|
v-model="search"
|
||||||
|
@click.stop
|
||||||
|
/>
|
||||||
|
<div v-show="!readonly" class="q-ml-sm column justify-center">
|
||||||
|
<q-btn class="bg-grey-4 text-grey-6" style="height: 35px; width: 35px" rounded flat icon="la la-broom" @click="defaultHotKeyAll">
|
||||||
|
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
||||||
|
Установить все сочетания по умолчанию
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- body -->
|
||||||
|
<div class="table-row row" v-for="(action, index) in tableData" :key="index">
|
||||||
|
<div class="desc q-pa-sm">{{ rstore.readerActions[action] }}</div>
|
||||||
|
<div class="hotKeys col q-pa-sm">
|
||||||
|
<q-chip
|
||||||
|
:color="collisions[code] ? 'red' : 'grey-7'"
|
||||||
|
:removable="!readonly" :clickable="collisions[code] ? true : false"
|
||||||
|
text-color="white" v-for="(code, index) in value[action]" :key="index" @remove="removeCode(action, code)"
|
||||||
|
@click="collisionWarning(code)"
|
||||||
|
>
|
||||||
|
{{ code }}
|
||||||
|
</q-chip>
|
||||||
|
</div>
|
||||||
|
<div v-show="!readonly" class="column q-pa-xs">
|
||||||
|
<q-icon
|
||||||
|
name="la la-plus-circle"
|
||||||
|
class="button bg-green-8 text-white"
|
||||||
|
@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>
|
||||||
|
</q-icon>
|
||||||
|
<q-icon
|
||||||
|
name="la la-broom"
|
||||||
|
class="button text-grey-5"
|
||||||
|
@click="defaultHotKey(action)"
|
||||||
|
v-ripple
|
||||||
|
>
|
||||||
|
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
||||||
|
По умолчанию
|
||||||
|
</q-tooltip>
|
||||||
|
</q-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
import Vue from 'vue';
|
||||||
|
import Component from 'vue-class-component';
|
||||||
|
|
||||||
|
import rstore from '../../../../store/modules/reader';
|
||||||
|
//import * as utils from '../../share/utils';
|
||||||
|
|
||||||
|
const UserHotKeysProps = Vue.extend({
|
||||||
|
props: {
|
||||||
|
value: Object,
|
||||||
|
readonly: Boolean,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default @Component({
|
||||||
|
watch: {
|
||||||
|
search: function() {
|
||||||
|
this.updateTableData();
|
||||||
|
},
|
||||||
|
value: function() {
|
||||||
|
this.checkCollisions();
|
||||||
|
this.updateTableData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
class UserHotKeys extends UserHotKeysProps {
|
||||||
|
search = '';
|
||||||
|
rstore = {};
|
||||||
|
tableData = [];
|
||||||
|
collisions = {};
|
||||||
|
maxCodesLength = 10;
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.rstore = rstore;
|
||||||
|
}
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.checkCollisions();
|
||||||
|
this.updateTableData();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTableData() {
|
||||||
|
let result = rstore.hotKeys.map(hk => hk.name);
|
||||||
|
|
||||||
|
const search = this.search.toLowerCase();
|
||||||
|
const codesIncludeSearch = (action) => {
|
||||||
|
for (const code of this.value[action]) {
|
||||||
|
if (code.toLowerCase().includes(search))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
result = result.filter(item => {
|
||||||
|
return !search ||
|
||||||
|
rstore.readerActions[item].toLowerCase().includes(search) ||
|
||||||
|
codesIncludeSearch(item)
|
||||||
|
});
|
||||||
|
|
||||||
|
this.tableData = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkCollisions() {
|
||||||
|
const cols = {};
|
||||||
|
for (const [action, codes] of Object.entries(this.value)) {
|
||||||
|
codes.forEach(code => {
|
||||||
|
if (!cols[code])
|
||||||
|
cols[code] = [];
|
||||||
|
if (cols[code].indexOf(action) < 0)
|
||||||
|
cols[code].push(action);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {};
|
||||||
|
for (const [code, actions] of Object.entries(cols)) {
|
||||||
|
if (actions.length > 1)
|
||||||
|
result[code] = actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.collisions = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
collisionWarning(code) {
|
||||||
|
if (this.collisions[code]) {
|
||||||
|
const descs = this.collisions[code].map(action => `<b>${rstore.readerActions[action]}</b>`);
|
||||||
|
this.$root.stdDialog.alert(`Сочетание '${code}' одновременно назначено<br>следующим командам:<br>${descs.join('<br>')}<br><br>
|
||||||
|
Возможно неожиданное поведение.`, 'Предупреждение');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeCode(action, code) {
|
||||||
|
let codes = Array.from(this.value[action]);
|
||||||
|
const index = codes.indexOf(code);
|
||||||
|
if (index >= 0) {
|
||||||
|
codes.splice(index, 1);
|
||||||
|
const newValue = Object.assign({}, this.value, {[action]: codes});
|
||||||
|
this.$emit('input', newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addHotKey(action) {
|
||||||
|
if (this.value[action].length >= this.maxCodesLength)
|
||||||
|
return;
|
||||||
|
try {
|
||||||
|
const result = await this.$root.stdDialog.getHotKey(`Добавить сочетание для:<br><b>${rstore.readerActions[action]}</b>`, '');
|
||||||
|
if (result) {
|
||||||
|
let codes = Array.from(this.value[action]);
|
||||||
|
if (codes.indexOf(result) < 0) {
|
||||||
|
codes.push(result);
|
||||||
|
const newValue = Object.assign({}, this.value, {[action]: codes});
|
||||||
|
this.$emit('input', newValue);
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.collisionWarning(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async defaultHotKey(action) {
|
||||||
|
try {
|
||||||
|
if (await this.$root.stdDialog.confirm(`Подтвердите сброс сочетаний клавиш<br>в значения по умолчанию для команды:<br><b>${rstore.readerActions[action]}</b>`, ' ')) {
|
||||||
|
const codes = Array.from(rstore.settingDefaults.userHotKeys[action]);
|
||||||
|
const newValue = Object.assign({}, this.value, {[action]: codes});
|
||||||
|
this.$emit('input', newValue);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async defaultHotKeyAll() {
|
||||||
|
try {
|
||||||
|
if (await this.$root.stdDialog.confirm('Подтвердите сброс сочетаний клавиш<br>для ВСЕХ команд в значения по умолчанию:', ' ')) {
|
||||||
|
const newValue = Object.assign({}, rstore.settingDefaults.userHotKeys);
|
||||||
|
this.$emit('input', newValue);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.table {
|
||||||
|
border-left: 1px solid grey;
|
||||||
|
border-top: 1px solid grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-row {
|
||||||
|
border-right: 1px solid grey;
|
||||||
|
border-bottom: 1px solid grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-row:nth-child(even) {
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-row:hover {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc {
|
||||||
|
width: 130px;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-wrap: break-word;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hotKeys {
|
||||||
|
border-left: 1px solid grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
font-size: 25px;
|
||||||
|
border-radius: 25px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
<div class="part-header">Показывать кнопки панели</div>
|
<div class="part-header">Показывать кнопки панели</div>
|
||||||
|
|
||||||
<div class="item row" v-for="item in toolButtons" :key="item.name">
|
<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)" :value="showToolButton[item.name]" :label="item.text" />
|
<q-checkbox size="xs" @input="changeShowToolButton(item.name)"
|
||||||
|
:value="showToolButton[item.name]" :label="rstore.readerActions[item.name]"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,33 @@
|
|||||||
<div class="part-header">Управление</div>
|
<div class="bg-grey-3 row">
|
||||||
|
<q-tabs
|
||||||
|
v-model="selectedKeysTab"
|
||||||
|
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="mouse" label="Мышь/тачскрин" />
|
||||||
|
<q-tab name="keyboard" label="Клавиатура" />
|
||||||
|
</q-tabs>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="item row">
|
<div class="q-mb-sm"/>
|
||||||
|
|
||||||
|
<div class="col tab-panel">
|
||||||
|
<div v-if="selectedKeysTab == 'mouse'">
|
||||||
|
<div class="item row">
|
||||||
<div class="label-4"></div>
|
<div class="label-4"></div>
|
||||||
<div class="col row">
|
<div class="col row">
|
||||||
<q-checkbox size="xs" v-model="clickControl" label="Включить управление кликом" />
|
<q-checkbox size="xs" v-model="clickControl" label="Включить управление кликом" />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="selectedKeysTab == 'keyboard'">
|
||||||
|
<div class="item row">
|
||||||
|
<UserHotKeys v-model="userHotKeys" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -54,6 +54,34 @@
|
|||||||
<!---------------------------------------------->
|
<!---------------------------------------------->
|
||||||
<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="label-6">Обработка</div>
|
||||||
|
<q-checkbox size="xs" v-model="lazyParseEnabled" label="Предварительная подготовка текста">
|
||||||
|
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
||||||
|
Включение этой опции позволяет делать предварительную<br>
|
||||||
|
подготовку всего текста в ленивом режиме сразу после<br>
|
||||||
|
загрузки книги. Это может повысить отзывчивость читалки,<br>
|
||||||
|
но нагружает процессор каждый раз при открытии книги.
|
||||||
|
</q-tooltip>
|
||||||
|
</q-checkbox>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="item row">
|
<div class="item row">
|
||||||
<div class="label-6">Парам. в URL</div>
|
<div class="label-6">Парам. в URL</div>
|
||||||
<q-checkbox size="xs" v-model="allowUrlParamBookPos">
|
<q-checkbox size="xs" v-model="allowUrlParamBookPos">
|
||||||
@@ -68,18 +96,6 @@
|
|||||||
</q-checkbox>
|
</q-checkbox>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="item row">
|
|
||||||
<div class="label-6">Парсинг</div>
|
|
||||||
<q-checkbox size="xs" v-model="lazyParseEnabled" label="Предварительная обработка текста">
|
|
||||||
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
|
|
||||||
Включение этой опции позволяет делать предварительную<br>
|
|
||||||
обработку текста в ленивом режиме сразу после загрузки<br>
|
|
||||||
книги. Это может повысить отзывчивость читалки, но<br>
|
|
||||||
нагружает процессор каждый раз при открытии книги.
|
|
||||||
</q-tooltip>
|
|
||||||
</q-checkbox>
|
|
||||||
</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="copyFullText" label="Загружать весь текст">
|
<q-checkbox size="xs" v-model="copyFullText" label="Загружать весь текст">
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
<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 v-if="mode == 'omnireader'">
|
<div v-if="mode == 'omnireader' || mode == 'liberama.top'">
|
||||||
<br>Переход по ссылке позволит автоматически ввести ключ доступа:
|
<br>Переход по ссылке позволит автоматически ввести ключ доступа:
|
||||||
<br><div class="text-center" style="margin-top: 5px">
|
<br><div class="text-center" style="margin-top: 5px">
|
||||||
<a :href="setStorageKeyLink" target="_blank">Ссылка для ввода ключа</a>
|
<a :href="setStorageKeyLink" target="_blank">Ссылка для ввода ключа</a>
|
||||||
|
|||||||
@@ -22,3 +22,15 @@
|
|||||||
<NumInput class="col-left" v-model="statusBarColorAlpha" :min="0" :max="1" :digits="2" :step="0.1" :disable="!showStatusBar"/>
|
<NumInput class="col-left" v-model="statusBarColorAlpha" :min="0" :max="1" :digits="2" :step="0.1" :disable="!showStatusBar"/>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
|
|||||||
@@ -108,22 +108,6 @@
|
|||||||
<NumInput class="col" v-model="addEmptyParagraphs" :min="0" :max="2"/>
|
<NumInput class="col" v-model="addEmptyParagraphs" :min="0" :max="2"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="item row">
|
|
||||||
<div class="label-2"></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-2">Изображения</div>
|
<div class="label-2">Изображения</div>
|
||||||
<div class="col row">
|
<div class="col row">
|
||||||
|
|||||||
@@ -21,11 +21,12 @@
|
|||||||
@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;">
|
oncontextmenu="return false;">
|
||||||
<div v-show="showStatusBar" v-html="statusBarClickable" @mousedown.prevent.stop @touchstart.stop
|
<div v-show="showStatusBar && statusBarClickOpen" v-html="statusBarClickable" @mousedown.prevent.stop @touchstart.stop
|
||||||
@click.prevent.stop="onStatusBarClick"></div>
|
@click.prevent.stop="onStatusBarClick"></div>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="!clickControl && showStatusBar" class="layout" v-html="statusBarClickable" @mousedown.prevent.stop @touchstart.stop
|
<div v-show="!clickControl && showStatusBar && statusBarClickOpen" class="layout" v-html="statusBarClickable" @mousedown.prevent.stop @touchstart.stop
|
||||||
@click.prevent.stop="onStatusBarClick"></div>
|
@click.prevent.stop="onStatusBarClick">
|
||||||
|
</div>
|
||||||
<!-- невидимым делать нельзя, вовремя не подгружаютя шрифты -->
|
<!-- невидимым делать нельзя, вовремя не подгружаютя шрифты -->
|
||||||
<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>
|
||||||
@@ -422,7 +423,7 @@ class TextPage extends Vue {
|
|||||||
if (this.lazyParseEnabled)
|
if (this.lazyParseEnabled)
|
||||||
this.lazyParsePara();
|
this.lazyParsePara();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.$root.stdDialog.alert(e.message, 'Ошибка', {type: 'negative'});
|
this.$root.stdDialog.alert(e.message, 'Ошибка', {color: 'negative'});
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
@@ -503,7 +504,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.$emit('stop-scrolling');
|
this.doStopScrolling();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,7 +545,7 @@ class TextPage extends Vue {
|
|||||||
}
|
}
|
||||||
this.resolveTransition1Finish = null;
|
this.resolveTransition1Finish = null;
|
||||||
this.doingScrolling = false;
|
this.doingScrolling = false;
|
||||||
this.$emit('stop-scrolling');
|
this.doStopScrolling();
|
||||||
this.draw();
|
this.draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -838,7 +839,7 @@ class TextPage extends Vue {
|
|||||||
let i = this.pageLineCount;
|
let i = this.pageLineCount;
|
||||||
if (this.keepLastToFirst)
|
if (this.keepLastToFirst)
|
||||||
i--;
|
i--;
|
||||||
if (i >= 0 && this.linesDown.length >= 2*i) {
|
if (i >= 0 && this.linesDown.length >= 2*i + (this.keepLastToFirst ? 1 : 0)) {
|
||||||
this.currentAnimation = this.pageChangeAnimation;
|
this.currentAnimation = this.pageChangeAnimation;
|
||||||
this.pageChangeDirectionDown = true;
|
this.pageChangeDirectionDown = true;
|
||||||
this.bookPos = this.linesDown[i].begin;
|
this.bookPos = this.linesDown[i].begin;
|
||||||
@@ -883,22 +884,26 @@ class TextPage extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
doToolBarToggle() {
|
doToolBarToggle(event) {
|
||||||
this.$emit('tool-bar-toggle');
|
this.$emit('do-action', {action: 'switchToolbar', event});
|
||||||
}
|
}
|
||||||
|
|
||||||
doScrollingToggle() {
|
doScrollingToggle() {
|
||||||
this.$emit('scrolling-toggle');
|
this.$emit('do-action', {action: 'scrolling', event});
|
||||||
}
|
}
|
||||||
|
|
||||||
doFullScreenToggle() {
|
doFullScreenToggle() {
|
||||||
this.$emit('full-screen-toogle');
|
this.$emit('do-action', {action: 'fullScreen', event});
|
||||||
|
}
|
||||||
|
|
||||||
|
doStopScrolling() {
|
||||||
|
this.$emit('do-action', {action: 'stopScrolling', event});
|
||||||
}
|
}
|
||||||
|
|
||||||
async doFontSizeInc() {
|
async doFontSizeInc() {
|
||||||
if (!this.settingsChanging) {
|
if (!this.settingsChanging) {
|
||||||
this.settingsChanging = true;
|
this.settingsChanging = true;
|
||||||
const newSize = (this.settings.fontSize + 1 < 100 ? this.settings.fontSize + 1 : 100);
|
const newSize = (this.settings.fontSize + 1 < 200 ? this.settings.fontSize + 1 : 100);
|
||||||
const newSettings = Object.assign({}, this.settings, {fontSize: newSize});
|
const newSettings = Object.assign({}, this.settings, {fontSize: newSize});
|
||||||
this.commit('reader/setSettings', newSettings);
|
this.commit('reader/setSettings', newSettings);
|
||||||
await sleep(50);
|
await sleep(50);
|
||||||
@@ -939,69 +944,6 @@ class TextPage extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
keyHook(event) {
|
|
||||||
let result = false;
|
|
||||||
if (event.type == 'keydown' && !event.ctrlKey && !event.altKey) {
|
|
||||||
result = true;
|
|
||||||
switch (event.code) {
|
|
||||||
case 'ArrowDown':
|
|
||||||
if (event.shiftKey)
|
|
||||||
this.doScrollingSpeedUp();
|
|
||||||
else
|
|
||||||
this.doDown();
|
|
||||||
break;
|
|
||||||
case 'ArrowUp':
|
|
||||||
if (event.shiftKey)
|
|
||||||
this.doScrollingSpeedDown();
|
|
||||||
else
|
|
||||||
this.doUp();
|
|
||||||
break;
|
|
||||||
case 'PageDown':
|
|
||||||
case 'ArrowRight':
|
|
||||||
this.doPageDown();
|
|
||||||
break;
|
|
||||||
case 'Space':
|
|
||||||
if (event.shiftKey)
|
|
||||||
this.doPageUp();
|
|
||||||
else
|
|
||||||
this.doPageDown();
|
|
||||||
break;
|
|
||||||
case 'PageUp':
|
|
||||||
case 'ArrowLeft':
|
|
||||||
case 'Backspace':
|
|
||||||
this.doPageUp();
|
|
||||||
break;
|
|
||||||
case 'Home':
|
|
||||||
this.doHome();
|
|
||||||
break;
|
|
||||||
case 'End':
|
|
||||||
this.doEnd();
|
|
||||||
break;
|
|
||||||
case 'KeyA':
|
|
||||||
if (event.shiftKey)
|
|
||||||
this.doFontSizeDec();
|
|
||||||
else
|
|
||||||
this.doFontSizeInc();
|
|
||||||
break;
|
|
||||||
case 'Enter':
|
|
||||||
case 'Backquote'://`
|
|
||||||
case 'KeyF':
|
|
||||||
this.doFullScreenToggle();
|
|
||||||
break;
|
|
||||||
case 'Tab':
|
|
||||||
case 'KeyQ':
|
|
||||||
this.doToolBarToggle();
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
result = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
async startClickRepeat(pointX, pointY) {
|
async startClickRepeat(pointX, pointY) {
|
||||||
this.repX = pointX;
|
this.repX = pointX;
|
||||||
this.repY = pointY;
|
this.repY = pointY;
|
||||||
@@ -1079,7 +1021,7 @@ class TextPage extends Vue {
|
|||||||
//движение вправо
|
//движение вправо
|
||||||
this.doScrollingSpeedUp();
|
this.doScrollingSpeedUp();
|
||||||
} else if (Math.abs(dy) < touchDelta && Math.abs(dx) < touchDelta) {
|
} else if (Math.abs(dy) < touchDelta && Math.abs(dx) < touchDelta) {
|
||||||
this.doToolBarToggle();
|
this.doToolBarToggle(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.startTouch = null;
|
this.startTouch = null;
|
||||||
@@ -1106,7 +1048,7 @@ class TextPage extends Vue {
|
|||||||
} else if (event.button == 1) {
|
} else if (event.button == 1) {
|
||||||
this.doScrollingToggle();
|
this.doScrollingToggle();
|
||||||
} else if (event.button == 2) {
|
} else if (event.button == 2) {
|
||||||
this.doToolBarToggle();
|
this.doToolBarToggle(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1131,7 +1073,7 @@ class TextPage extends Vue {
|
|||||||
if (url && url.indexOf('file://') != 0) {
|
if (url && url.indexOf('file://') != 0) {
|
||||||
window.open(url, '_blank');
|
window.open(url, '_blank');
|
||||||
} else {
|
} else {
|
||||||
this.$root.stdDialog.alert('Оригинал недоступен, т.к. файл книги был загружен с локального диска.', ' ', {type: 'info'});
|
this.$root.stdDialog.alert('Оригинал недоступен, т.к. файл книги был загружен с локального диска.', ' ', {color: 'info'});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ export default class BookParser {
|
|||||||
offset: Number, //сумма всех length до этого параграфа
|
offset: Number, //сумма всех length до этого параграфа
|
||||||
length: Number, //длина text без тегов
|
length: Number, //длина text без тегов
|
||||||
text: String, //текст параграфа с вложенными тегами
|
text: String, //текст параграфа с вложенными тегами
|
||||||
cut: Boolean, //параграф - кандидат на сокрытие (cutEmptyParagraphs)
|
|
||||||
addIndex: Number, //индекс добавляемого пустого параграфа (addEmptyParagraphs)
|
addIndex: Number, //индекс добавляемого пустого параграфа (addEmptyParagraphs)
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
@@ -116,7 +115,6 @@ export default class BookParser {
|
|||||||
offset: paraOffset,
|
offset: paraOffset,
|
||||||
length: len,
|
length: len,
|
||||||
text: text,
|
text: text,
|
||||||
cut: (!addIndex && (len == 1 && text[0] == ' ')),
|
|
||||||
addIndex: (addIndex ? addIndex : 0),
|
addIndex: (addIndex ? addIndex : 0),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -132,10 +130,10 @@ export default class BookParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let p = para[paraIndex];
|
let p = para[paraIndex];
|
||||||
//добавление пустых (addEmptyParagraphs) параграфов
|
paraOffset -= p.length;
|
||||||
|
//добавление пустых (addEmptyParagraphs) параграфов перед текущим
|
||||||
if (p.length == 1 && p.text[0] == ' ' && len > 0) {
|
if (p.length == 1 && p.text[0] == ' ' && len > 0) {
|
||||||
paraIndex--;
|
paraIndex--;
|
||||||
paraOffset -= p.length;
|
|
||||||
for (let i = 0; i < 2; i++) {
|
for (let i = 0; i < 2; i++) {
|
||||||
newParagraph(' ', 1, i + 1);
|
newParagraph(' ', 1, i + 1);
|
||||||
}
|
}
|
||||||
@@ -144,15 +142,10 @@ export default class BookParser {
|
|||||||
p.index = paraIndex;
|
p.index = paraIndex;
|
||||||
p.offset = paraOffset;
|
p.offset = paraOffset;
|
||||||
para[paraIndex] = p;
|
para[paraIndex] = p;
|
||||||
paraOffset += p.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
paraOffset -= p.length;
|
//уберем начальный пробел
|
||||||
//параграф оказался непустой
|
|
||||||
if (p.length == 1 && p.text[0] == ' ' && len > 0) {
|
|
||||||
p.length = 0;
|
p.length = 0;
|
||||||
p.text = p.text.substr(1);
|
p.text = p.text.substr(1);
|
||||||
p.cut = (len == 1 && text[0] == ' ');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
p.length += len;
|
p.length += len;
|
||||||
@@ -633,10 +626,7 @@ export default class BookParser {
|
|||||||
imageHeightLines: this.imageHeightLines,
|
imageHeightLines: this.imageHeightLines,
|
||||||
imageFitWidth: this.imageFitWidth,
|
imageFitWidth: this.imageFitWidth,
|
||||||
compactTextPerc: this.compactTextPerc,
|
compactTextPerc: this.compactTextPerc,
|
||||||
visible: !(
|
visible: true, //вычисляется позже
|
||||||
(this.cutEmptyParagraphs && para.cut) ||
|
|
||||||
(para.addIndex > this.addEmptyParagraphs)
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -652,9 +642,12 @@ export default class BookParser {
|
|||||||
text: String,
|
text: String,
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
let parts = this.splitToStyle(para.text);
|
let parts = this.splitToStyle(para.text);
|
||||||
|
|
||||||
|
//инициализация парсера
|
||||||
let line = {begin: para.offset, parts: []};
|
let line = {begin: para.offset, parts: []};
|
||||||
|
let paragraphText = '';//текст параграфа
|
||||||
let partText = '';//накапливаемый кусок со стилем
|
let partText = '';//накапливаемый кусок со стилем
|
||||||
|
|
||||||
let str = '';//измеряемая строка
|
let str = '';//измеряемая строка
|
||||||
@@ -663,14 +656,16 @@ export default class BookParser {
|
|||||||
let style = {};
|
let style = {};
|
||||||
let ofs = 0;//смещение от начала параграфа para.offset
|
let ofs = 0;//смещение от начала параграфа para.offset
|
||||||
let imgW = 0;
|
let imgW = 0;
|
||||||
|
let imageInPara = false;
|
||||||
const compactWidth = this.measureText('W', {})*this.compactTextPerc/100;
|
const compactWidth = this.measureText('W', {})*this.compactTextPerc/100;
|
||||||
// тут начинается самый замес, перенос по слогам и стилизация, а также изображения
|
// тут начинается самый замес, перенос по слогам и стилизация, а также изображения
|
||||||
for (const part of parts) {
|
for (const part of parts) {
|
||||||
style = part.style;
|
style = part.style;
|
||||||
|
paragraphText += part.text;
|
||||||
|
|
||||||
//изображения
|
//изображения
|
||||||
if (part.image.id && !part.image.inline) {
|
if (part.image.id && !part.image.inline) {
|
||||||
parsed.visible = this.showImages;
|
imageInPara = true;
|
||||||
let bin = this.binary[part.image.id];
|
let bin = this.binary[part.image.id];
|
||||||
if (!bin)
|
if (!bin)
|
||||||
bin = {h: 1, w: 1};
|
bin = {h: 1, w: 1};
|
||||||
@@ -837,6 +832,16 @@ export default class BookParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//parsed.visible
|
||||||
|
if (imageInPara) {
|
||||||
|
parsed.visible = this.showImages;
|
||||||
|
} else {
|
||||||
|
parsed.visible = !(
|
||||||
|
(para.addIndex > this.addEmptyParagraphs) ||
|
||||||
|
(para.addIndex == 0 && this.cutEmptyParagraphs && paragraphText.trim() == '')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
parsed.lines = lines;
|
parsed.lines = lines;
|
||||||
para.parsed = parsed;
|
para.parsed = parsed;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,65 @@
|
|||||||
export const versionHistory = [
|
export const versionHistory = [
|
||||||
|
{
|
||||||
|
showUntil: '2020-10-31',
|
||||||
|
header: '0.9.5 (2020-11-01)',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>на панель инструментов добавлена новая кнопка "Обновить с разбиением на параграфы"</li>
|
||||||
|
<li>исправления багов</li>
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
showUntil: '2020-10-28',
|
||||||
|
header: '0.9.4 (2020-10-29)',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>заработал новый сайт <a href="https://liberama.top">https://liberama.top</a>, где будет более свободный обмен книгами</li>
|
||||||
|
<li>для liberama.top добавлено новое окно: "Библиотека"</li>
|
||||||
|
<li>исправления багов</li>
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
showUntil: '2020-05-20',
|
||||||
|
header: '0.9.3 (2020-05-21)',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>исправления багов</li>
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
showUntil: '2020-04-25',
|
||||||
|
header: '0.9.2 (2020-03-15)',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>в настройки добавлена возможность назначать сочетания клавиш на команды в читалке</li>
|
||||||
|
<li>переход на Service Worker вместо AppCache для автономного режима работы</li>
|
||||||
|
<li>исправления багов</li>
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
showUntil: '2020-03-02',
|
||||||
|
header: '0.9.1 (2020-03-03)',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>улучшение работы серверной части</li>
|
||||||
|
<li>незначительные изменения интерфейса</li>
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
showUntil: '2020-02-25',
|
showUntil: '2020-02-25',
|
||||||
header: '0.9.0 (2020-02-26)',
|
header: '0.9.0 (2020-02-26)',
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const DialogProps = Vue.extend({
|
|||||||
props: {
|
props: {
|
||||||
value: Boolean,
|
value: Boolean,
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
export default @Component({
|
export default @Component({
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -19,11 +19,12 @@ class Notify extends Vue {
|
|||||||
iconColor = 'white',
|
iconColor = 'white',
|
||||||
message = '',
|
message = '',
|
||||||
messageColor = 'black',
|
messageColor = 'black',
|
||||||
|
position = 'top-right',
|
||||||
} = opts;
|
} = opts;
|
||||||
|
|
||||||
caption = (caption ? `<div style="font-size: 120%; color: ${captionColor}"><b>${caption}</b></div><br>` : '');
|
caption = (caption ? `<div style="font-size: 120%; color: ${captionColor}"><b>${caption}</b></div><br>` : '');
|
||||||
return this.$q.notify({
|
return this.$q.notify({
|
||||||
position: 'top-right',
|
position,
|
||||||
color,
|
color,
|
||||||
textColor: iconColor,
|
textColor: iconColor,
|
||||||
icon,
|
icon,
|
||||||
@@ -38,20 +39,20 @@ class Notify extends Vue {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
success(message, caption) {
|
success(message, caption, options) {
|
||||||
this.notify({color: 'positive', icon: 'la la-check-circle', message, caption});
|
this.notify(Object.assign({color: 'positive', icon: 'la la-check-circle', message, caption}, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
warning(message, caption) {
|
warning(message, caption, options) {
|
||||||
this.notify({color: 'warning', icon: 'la la-exclamation-circle', message, caption});
|
this.notify(Object.assign({color: 'warning', icon: 'la la-exclamation-circle', message, caption}, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
error(message, caption) {
|
error(message, caption, options) {
|
||||||
this.notify({color: 'negative', icon: 'la la-exclamation-circle', messageColor: 'yellow', captionColor: 'white', message, caption});
|
this.notify(Object.assign({color: 'negative', icon: 'la la-exclamation-circle', messageColor: 'yellow', captionColor: 'white', message, caption}, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
info(message, caption) {
|
info(message, caption, options) {
|
||||||
this.notify({color: 'info', icon: 'la la-bell', message, caption});
|
this.notify(Object.assign({color: 'info', icon: 'la la-bell', message, caption}, options));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
|
||||||
<!--------------------------------------------------->
|
<!--------------------------------------------------->
|
||||||
<div v-show="type == 'alert'" class="dialog column bg-white no-wrap" style="min-height: 150px">
|
<div v-show="type == 'alert'" class="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">
|
||||||
<q-icon v-show="caption" class="q-mr-sm" :class="iconColor" name="las la-exclamation-circle" size="28px"></q-icon>
|
<q-icon v-show="caption" class="q-mr-sm" :class="iconColor" name="las la-exclamation-circle" size="28px"></q-icon>
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col q-mx-md">
|
<div class="q-mx-md">
|
||||||
<div v-html="message"></div>
|
<div v-html="message"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--------------------------------------------------->
|
<!--------------------------------------------------->
|
||||||
<div v-show="type == 'confirm'" class="dialog column bg-white no-wrap" style="min-height: 150px">
|
<div v-show="type == 'confirm'" class="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">
|
||||||
<q-icon v-show="caption" class="q-mr-sm" :class="iconColor" name="las la-exclamation-circle" size="28px"></q-icon>
|
<q-icon v-show="caption" class="q-mr-sm" :class="iconColor" name="las la-exclamation-circle" size="28px"></q-icon>
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col q-mx-md">
|
<div class="q-mx-md">
|
||||||
<div v-html="message"></div>
|
<div v-html="message"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--------------------------------------------------->
|
<!--------------------------------------------------->
|
||||||
<div v-show="type == 'prompt'" class="dialog column bg-white no-wrap" style="min-height: 250px">
|
<div v-show="type == 'prompt'" class="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">
|
||||||
<q-icon v-show="caption" class="q-mr-sm" :class="iconColor" name="las la-exclamation-circle" size="28px"></q-icon>
|
<q-icon v-show="caption" class="q-mr-sm" :class="iconColor" name="las la-exclamation-circle" size="28px"></q-icon>
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col 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" class="q-mt-xs" outlined dense v-model="inputValue"/>
|
||||||
<div class="error"><span v-show="error != ''">{{ error }}</span></div>
|
<div class="error"><span v-show="error != ''">{{ error }}</span></div>
|
||||||
@@ -74,6 +74,34 @@
|
|||||||
<q-btn class="q-px-md q-ml-sm" color="primary" dense no-caps @click="okClick">OK</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>
|
||||||
|
|
||||||
|
<!--------------------------------------------------->
|
||||||
|
<div v-show="type == 'hotKey'" class="bg-white no-wrap">
|
||||||
|
<div class="header row">
|
||||||
|
<div class="caption col row items-center q-ml-md">
|
||||||
|
<q-icon v-show="caption" class="q-mr-sm" :class="iconColor" name="las la-exclamation-circle" size="28px"></q-icon>
|
||||||
|
<div v-html="caption"></div>
|
||||||
|
</div>
|
||||||
|
<div class="close-icon column justify-center items-center">
|
||||||
|
<q-btn flat round dense v-close-popup>
|
||||||
|
<q-icon name="la la-times" size="18px"></q-icon>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="q-mx-md">
|
||||||
|
<div v-html="message"></div>
|
||||||
|
<div class="q-my-md text-center">
|
||||||
|
<div v-show="hotKeyCode == ''" class="text-grey-5">Нет</div>
|
||||||
|
<div>{{ hotKeyCode }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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 class="q-px-md q-ml-sm" color="primary" dense no-caps @click="okClick" :disabled="hotKeyCode == ''">OK</q-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -82,7 +110,7 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import Component from 'vue-class-component';
|
import Component from 'vue-class-component';
|
||||||
|
|
||||||
//import * as utils from '../../share/utils';
|
import * as utils from '../../share/utils';
|
||||||
|
|
||||||
export default @Component({
|
export default @Component({
|
||||||
watch: {
|
watch: {
|
||||||
@@ -99,6 +127,7 @@ class StdDialog extends Vue {
|
|||||||
inputValue = '';
|
inputValue = '';
|
||||||
error = '';
|
error = '';
|
||||||
iconColor = '';
|
iconColor = '';
|
||||||
|
hotKeyCode = '';
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
if (this.$root.addKeyHook) {
|
if (this.$root.addKeyHook) {
|
||||||
@@ -117,8 +146,13 @@ class StdDialog extends Vue {
|
|||||||
this.error = '';
|
this.error = '';
|
||||||
|
|
||||||
this.iconColor = 'text-warning';
|
this.iconColor = 'text-warning';
|
||||||
if (opts && opts.type) {
|
if (opts && opts.color) {
|
||||||
this.iconColor = `text-${opts.type}`;
|
this.iconColor = `text-${opts.color}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hotKeyCode = '';
|
||||||
|
if (opts && opts.hotKeyCode) {
|
||||||
|
this.hotKeyCode = opts.hotKeyCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,6 +192,12 @@ class StdDialog extends Vue {
|
|||||||
this.$refs.dialog.shake();
|
this.$refs.dialog.shake();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.type == 'hotKey' && this.hotKeyCode == '') {
|
||||||
|
this.$refs.dialog.shake();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.ok = true;
|
this.ok = true;
|
||||||
this.$refs.dialog.hide();
|
this.$refs.dialog.hide();
|
||||||
}
|
}
|
||||||
@@ -218,13 +258,51 @@ class StdDialog extends Vue {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getHotKey(message, caption, opts) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.init(message, caption, opts);
|
||||||
|
|
||||||
|
this.hideTrigger = () => {
|
||||||
|
if (this.ok) {
|
||||||
|
resolve(this.hotKeyCode);
|
||||||
|
} else {
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.type = 'hotKey';
|
||||||
|
this.active = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
keyHook(event) {
|
keyHook(event) {
|
||||||
if (this.active && event.code == 'Enter') {
|
if (this.active) {
|
||||||
|
let handled = false;
|
||||||
|
if (this.type == 'hotKey') {
|
||||||
|
if (event.type == 'keydown') {
|
||||||
|
this.hotKeyCode = utils.keyEventToCode(event);
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (event.key == 'Enter') {
|
||||||
this.okClick();
|
this.okClick();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key == 'Escape') {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.dialog.hide();
|
||||||
|
});
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handled) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<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 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 ref="header" class="header row justify-end" @mousedown.prevent.stop="onMouseDown"
|
||||||
@touchstart.stop="onTouchStart" @touchend.stop="onTouchEnd" @touchmove.stop="onTouchMove">
|
@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>
|
||||||
<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>
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ export default @Component({
|
|||||||
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: '',
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
class Window extends Vue {
|
class Window extends Vue {
|
||||||
@@ -39,6 +41,9 @@ class Window extends Vue {
|
|||||||
const top = (this.$refs.main.offsetHeight - this.$refs.windowBox.offsetHeight)/2 + this.topShift;
|
const top = (this.$refs.main.offsetHeight - this.$refs.windowBox.offsetHeight)/2 + this.topShift;
|
||||||
this.$refs.windowBox.style.left = (left > 0 ? left : 0) + 'px';
|
this.$refs.windowBox.style.left = (left > 0 ? left : 0) + 'px';
|
||||||
this.$refs.windowBox.style.top = (top > 0 ? top : 0) + 'px';
|
this.$refs.windowBox.style.top = (top > 0 ? top : 0) + 'px';
|
||||||
|
|
||||||
|
if (this.margin)
|
||||||
|
this.$refs.window.style.margin = this.margin;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,6 +124,7 @@ class Window extends Vue {
|
|||||||
.main {
|
.main {
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
z-index: 50;
|
z-index: 50;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.xyfit {
|
.xyfit {
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html manifest="/app/manifest.appcache">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
<title></title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
<meta name="description" content="Браузерная онлайн-читалка книг. Поддерживаются форматы: fb2, html, txt, rtf, doc, docx, pdf, epub, mobi.">
|
<meta name="description" content="Браузерная онлайн-читалка книг. Поддерживаются форматы: fb2, html, txt, rtf, doc, docx, pdf, epub, mobi.">
|
||||||
<meta name="keywords" content="онлайн,читалка,fb2,книги,читать,браузер,интернет">
|
<meta name="keywords" content="онлайн,читалка,fb2,книги,читать,браузер,интернет">
|
||||||
<title></title>
|
<script src="/sw-register.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script src="https://yastatic.net/share2/share.js" async="async"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import {QSelect} from 'quasar/src/components/select';
|
|||||||
import {QColor} from 'quasar/src/components/color';
|
import {QColor} from 'quasar/src/components/color';
|
||||||
import {QPopupProxy} from 'quasar/src/components/popup-proxy';
|
import {QPopupProxy} from 'quasar/src/components/popup-proxy';
|
||||||
import {QDialog} from 'quasar/src/components/dialog';
|
import {QDialog} from 'quasar/src/components/dialog';
|
||||||
|
import {QChip} from 'quasar/src/components/chip';
|
||||||
|
|
||||||
const components = {
|
const components = {
|
||||||
//QLayout,
|
//QLayout,
|
||||||
@@ -55,6 +56,7 @@ const components = {
|
|||||||
QColor,
|
QColor,
|
||||||
QPopupProxy,
|
QPopupProxy,
|
||||||
QDialog,
|
QDialog,
|
||||||
|
QChip,
|
||||||
};
|
};
|
||||||
|
|
||||||
//directives
|
//directives
|
||||||
@@ -80,7 +82,6 @@ Vue.use(Quasar, { config, components, directives, plugins });
|
|||||||
//import '@quasar/extras/material-icons-outlined/material-icons-outlined.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 '@quasar/extras/material-icons-outlined/material-icons-outlined.css';
|
|
||||||
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 fontawesomeV5 from 'quasar/icon-set/fontawesome-v5.js'
|
||||||
|
|||||||
@@ -2,42 +2,41 @@ import Vue from 'vue';
|
|||||||
import VueRouter from 'vue-router';
|
import VueRouter from 'vue-router';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
//немедленная загрузка
|
const CardIndex = () => import('./components/CardIndex/CardIndex.vue');
|
||||||
import CardIndex from './components/CardIndex/CardIndex.vue';
|
|
||||||
//const CardIndex = () => import('./components/CardIndex/CardIndex.vue');
|
|
||||||
|
|
||||||
const Search = () => import('./components/CardIndex/Search/Search.vue');
|
const Search = () => import('./components/CardIndex/Search/Search.vue');
|
||||||
const Card = () => import('./components/CardIndex/Card/Card.vue');
|
const Card = () => import('./components/CardIndex/Card/Card.vue');
|
||||||
const Book = () => import('./components/CardIndex/Book/Book.vue');
|
const Book = () => import('./components/CardIndex/Book/Book.vue');
|
||||||
const History = () => import('./components/CardIndex/History/History.vue');
|
const History = () => import('./components/CardIndex/History/History.vue');
|
||||||
|
|
||||||
//немедленная загрузка
|
//немедленная загрузка
|
||||||
//const Reader = () => import('./components/Reader/Reader.vue');
|
//import Reader from './components/Reader/Reader.vue';
|
||||||
import Reader from './components/Reader/Reader.vue';
|
const Reader = () => import('./components/Reader/Reader.vue');
|
||||||
|
const ExternalLibs = () => import('./components/ExternalLibs/ExternalLibs.vue');
|
||||||
|
|
||||||
//const Forum = () => import('./components/Forum/Forum.vue');
|
|
||||||
const Income = () => import('./components/Income/Income.vue');
|
const Income = () => import('./components/Income/Income.vue');
|
||||||
const Sources = () => import('./components/Sources/Sources.vue');
|
const Sources = () => import('./components/Sources/Sources.vue');
|
||||||
const Settings = () => import('./components/Settings/Settings.vue');
|
const Settings = () => import('./components/Settings/Settings.vue');
|
||||||
const Help = () => import('./components/Help/Help.vue');
|
const Help = () => import('./components/Help/Help.vue');
|
||||||
//const NotFound404 = () => import('./components/NotFound404/NotFound404.vue');
|
const NotFound404 = () => import('./components/NotFound404/NotFound404.vue');
|
||||||
|
|
||||||
const myRoutes = [
|
const myRoutes = [
|
||||||
['/', null, null, '/cardindex'],
|
['/', null, null, '/cardindex'],
|
||||||
['/cardindex', CardIndex ],
|
['/cardindex', CardIndex],
|
||||||
['/cardindex~search', Search ],
|
['/cardindex~search', Search],
|
||||||
['/cardindex~card', Card ],
|
['/cardindex~card', Card],
|
||||||
['/cardindex~card/:authorId', Card ],
|
['/cardindex~card/:authorId', Card],
|
||||||
['/cardindex~book', Book ],
|
['/cardindex~book', Book],
|
||||||
['/cardindex~book/:bookId', Book ],
|
['/cardindex~book/:bookId', Book],
|
||||||
['/cardindex~history', History ],
|
['/cardindex~history', History],
|
||||||
|
|
||||||
['/reader', Reader ],
|
['/reader', Reader],
|
||||||
['/income', Income ],
|
['/external-libs', ExternalLibs],
|
||||||
['/sources', Sources ],
|
['/income', Income],
|
||||||
['/settings', Settings ],
|
['/sources', Sources],
|
||||||
['/help', Help ],
|
['/settings', Settings],
|
||||||
['*', null, null, '/cardindex' ],
|
['/help', Help],
|
||||||
|
['/404', NotFound404],
|
||||||
|
['*', null, null, '/cardindex'],
|
||||||
];
|
];
|
||||||
|
|
||||||
let routes = {};
|
let routes = {};
|
||||||
|
|||||||
@@ -130,20 +130,53 @@ export function getObjDiff(oldObj, newObj) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isObjDiff(diff) {
|
export function isObjDiff(diff) {
|
||||||
return (_.isObject(diff) && diff.__isDiff);
|
return (_.isObject(diff) && diff.__isDiff && diff.change && diff.add && diff.del);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isEmptyObjDiff(diff) {
|
export function isEmptyObjDiff(diff) {
|
||||||
return (!_.isObject(diff) || !diff.__isDiff ||
|
return (!isObjDiff(diff) ||
|
||||||
(!Object.keys(diff.change).length &&
|
!(Object.keys(diff.change).length ||
|
||||||
!Object.keys(diff.add).length &&
|
Object.keys(diff.add).length ||
|
||||||
!diff.del.length
|
diff.del.length
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyObjDiff(obj, diff, isAddChanged) {
|
export function isEmptyObjDiffDeep(diff, opts = {}) {
|
||||||
const result = _.cloneDeep(obj);
|
if (!isObjDiff(diff))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
const {
|
||||||
|
isApplyChange = true,
|
||||||
|
isApplyAdd = true,
|
||||||
|
isApplyDel = true,
|
||||||
|
} = opts;
|
||||||
|
|
||||||
|
let notEmptyDeep = false;
|
||||||
|
const change = diff.change;
|
||||||
|
for (const key of Object.keys(change)) {
|
||||||
|
if (_.isObject(change[key]))
|
||||||
|
notEmptyDeep |= !isEmptyObjDiffDeep(change[key], opts);
|
||||||
|
else if (isApplyChange)
|
||||||
|
notEmptyDeep = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !(
|
||||||
|
notEmptyDeep ||
|
||||||
|
(isApplyAdd && Object.keys(diff.add).length) ||
|
||||||
|
(isApplyDel && diff.del.length)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyObjDiff(obj, diff, opts = {}) {
|
||||||
|
const {
|
||||||
|
isAddChanged = false,
|
||||||
|
isApplyChange = true,
|
||||||
|
isApplyAdd = true,
|
||||||
|
isApplyDel = true,
|
||||||
|
} = opts;
|
||||||
|
|
||||||
|
let result = _.cloneDeep(obj);
|
||||||
if (!diff.__isDiff)
|
if (!diff.__isDiff)
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
@@ -151,8 +184,9 @@ export function applyObjDiff(obj, diff, isAddChanged) {
|
|||||||
for (const key of Object.keys(change)) {
|
for (const key of Object.keys(change)) {
|
||||||
if (result.hasOwnProperty(key)) {
|
if (result.hasOwnProperty(key)) {
|
||||||
if (_.isObject(change[key])) {
|
if (_.isObject(change[key])) {
|
||||||
result[key] = applyObjDiff(result[key], change[key], isAddChanged);
|
result[key] = applyObjDiff(result[key], change[key], opts);
|
||||||
} else {
|
} else {
|
||||||
|
if (isApplyChange)
|
||||||
result[key] = _.cloneDeep(change[key]);
|
result[key] = _.cloneDeep(change[key]);
|
||||||
}
|
}
|
||||||
} else if (isAddChanged) {
|
} else if (isAddChanged) {
|
||||||
@@ -160,13 +194,19 @@ export function applyObjDiff(obj, diff, isAddChanged) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isApplyAdd) {
|
||||||
for (const key of Object.keys(diff.add)) {
|
for (const key of Object.keys(diff.add)) {
|
||||||
result[key] = _.cloneDeep(diff.add[key]);
|
result[key] = _.cloneDeep(diff.add[key]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isApplyDel && diff.del.length) {
|
||||||
for (const key of diff.del) {
|
for (const key of diff.del) {
|
||||||
delete result[key];
|
delete result[key];
|
||||||
}
|
}
|
||||||
|
if (_.isArray(result))
|
||||||
|
result = result.filter(v => v);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -203,3 +243,37 @@ export function escapeXml(str) {
|
|||||||
.replace(/'/g, ''')
|
.replace(/'/g, ''')
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function keyEventToCode(event) {
|
||||||
|
let result = [];
|
||||||
|
let code = event.code;
|
||||||
|
|
||||||
|
const modCode = code.substring(0, 3);
|
||||||
|
if (event.metaKey && modCode != 'Met')
|
||||||
|
result.push('Meta');
|
||||||
|
if (event.ctrlKey && modCode != 'Con')
|
||||||
|
result.push('Ctrl');
|
||||||
|
if (event.shiftKey && modCode != 'Shi')
|
||||||
|
result.push('Shift');
|
||||||
|
if (event.altKey && modCode != 'Alt')
|
||||||
|
result.push('Alt');
|
||||||
|
|
||||||
|
if (modCode == 'Dig') {
|
||||||
|
code = code.substring(5, 6);
|
||||||
|
} else if (modCode == 'Key') {
|
||||||
|
code = code.substring(3, 4);
|
||||||
|
}
|
||||||
|
result.push(code);
|
||||||
|
|
||||||
|
return result.join('+');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function userHotKeysObjectSwap(userHotKeys) {
|
||||||
|
let result = {};
|
||||||
|
for (const [name, codes] of Object.entries(userHotKeys)) {
|
||||||
|
for (const code of codes) {
|
||||||
|
result[code] = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,15 +1,81 @@
|
|||||||
//занчение toolButtons.name не должно совпадать с settingDefaults-propertyName
|
import * as utils from '../../share/utils';
|
||||||
|
|
||||||
|
const readerActions = {
|
||||||
|
'help': 'Вызвать cправку',
|
||||||
|
'loader': 'На страницу загрузки',
|
||||||
|
'settings': 'Настроить',
|
||||||
|
'undoAction': 'Действие назад',
|
||||||
|
'redoAction': 'Действие вперед',
|
||||||
|
'fullScreen': 'На весь экран',
|
||||||
|
'scrolling': 'Плавный скроллинг',
|
||||||
|
'stopScrolling': '',
|
||||||
|
'setPosition': 'Установить позицию',
|
||||||
|
'search': 'Найти в тексте',
|
||||||
|
'copyText': 'Скопировать текст со страницы',
|
||||||
|
'splitToPara': 'Обновить с разбиением на параграфы',
|
||||||
|
'refresh': 'Принудительно обновить книгу',
|
||||||
|
'offlineMode': 'Автономный режим (без интернета)',
|
||||||
|
'libs': 'Библиотека',
|
||||||
|
'recentBooks': 'Открыть недавние',
|
||||||
|
'switchToolbar': 'Показать/скрыть панель управления',
|
||||||
|
'donate': '',
|
||||||
|
'bookBegin': 'В начало книги',
|
||||||
|
'bookEnd': 'В конец книги',
|
||||||
|
'pageBack': 'Страницу назад',
|
||||||
|
'pageForward': 'Страницу вперед',
|
||||||
|
'lineBack': 'Строчку назад',
|
||||||
|
'lineForward': 'Строчку вперед',
|
||||||
|
'incFontSize': 'Увеличить размер шрифта',
|
||||||
|
'decFontSize': 'Уменьшить размер шрифта',
|
||||||
|
'scrollingSpeedUp': 'Увеличить скорость скроллинга',
|
||||||
|
'scrollingSpeedDown': 'Уменьшить скорость скроллинга',
|
||||||
|
};
|
||||||
|
|
||||||
|
//readerActions[name]
|
||||||
const toolButtons = [
|
const toolButtons = [
|
||||||
{name: 'undoAction', show: true, text: 'Действие назад'},
|
{name: 'undoAction', show: true},
|
||||||
{name: 'redoAction', show: true, text: 'Действие вперед'},
|
{name: 'redoAction', show: true},
|
||||||
{name: 'fullScreen', show: true, text: 'На весь экран'},
|
{name: 'fullScreen', show: true},
|
||||||
{name: 'scrolling', show: false, text: 'Плавный скроллинг'},
|
{name: 'scrolling', show: false},
|
||||||
{name: 'setPosition', show: true, text: 'На страницу'},
|
{name: 'setPosition', show: true},
|
||||||
{name: 'search', show: true, text: 'Найти в тексте'},
|
{name: 'search', show: true},
|
||||||
{name: 'copyText', show: false, text: 'Скопировать текст со страницы'},
|
{name: 'copyText', show: false},
|
||||||
{name: 'refresh', show: true, text: 'Принудительно обновить книгу'},
|
{name: 'splitToPara', show: false},
|
||||||
{name: 'offlineMode', show: false, text: 'Автономный режим (без интернета)'},
|
{name: 'refresh', show: true},
|
||||||
{name: 'recentBooks', show: true, text: 'Открыть недавние'},
|
{name: 'libs', show: true},
|
||||||
|
{name: 'recentBooks', show: true},
|
||||||
|
{name: 'offlineMode', show: false},
|
||||||
|
];
|
||||||
|
|
||||||
|
//readerActions[name]
|
||||||
|
const hotKeys = [
|
||||||
|
{name: 'help', codes: ['F1', 'H']},
|
||||||
|
{name: 'loader', codes: ['Escape']},
|
||||||
|
{name: 'settings', codes: ['S']},
|
||||||
|
{name: 'undoAction', codes: ['Ctrl+BracketLeft']},
|
||||||
|
{name: 'redoAction', codes: ['Ctrl+BracketRight']},
|
||||||
|
{name: 'fullScreen', codes: ['Enter', 'Backquote', 'F']},
|
||||||
|
{name: 'scrolling', codes: ['Z']},
|
||||||
|
{name: 'setPosition', codes: ['P']},
|
||||||
|
{name: 'search', codes: ['Ctrl+F']},
|
||||||
|
{name: 'copyText', codes: ['Ctrl+C']},
|
||||||
|
{name: 'splitToPara', codes: ['Shift+R']},
|
||||||
|
{name: 'refresh', codes: ['R']},
|
||||||
|
{name: 'offlineMode', codes: ['O']},
|
||||||
|
{name: 'libs', codes: ['L']},
|
||||||
|
{name: 'recentBooks', codes: ['X']},
|
||||||
|
|
||||||
|
{name: 'switchToolbar', codes: ['Tab', 'Q']},
|
||||||
|
{name: 'bookBegin', codes: ['Home']},
|
||||||
|
{name: 'bookEnd', codes: ['End']},
|
||||||
|
{name: 'pageBack', codes: ['PageUp', 'ArrowLeft', 'Backspace', 'Shift+Space']},
|
||||||
|
{name: 'pageForward', codes: ['PageDown', 'ArrowRight', 'Space']},
|
||||||
|
{name: 'lineBack', codes: ['ArrowUp']},
|
||||||
|
{name: 'lineForward', codes: ['ArrowDown']},
|
||||||
|
{name: 'incFontSize', codes: ['A']},
|
||||||
|
{name: 'decFontSize', codes: ['Shift+A']},
|
||||||
|
{name: 'scrollingSpeedUp', codes: ['Shift+ArrowDown']},
|
||||||
|
{name: 'scrollingSpeedDown', codes: ['Shift+ArrowUp']},
|
||||||
];
|
];
|
||||||
|
|
||||||
const fonts = [
|
const fonts = [
|
||||||
@@ -136,6 +202,7 @@ const webFonts = [
|
|||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------------------------------------
|
||||||
const settingDefaults = {
|
const settingDefaults = {
|
||||||
textColor: '#000000',
|
textColor: '#000000',
|
||||||
backgroundColor: '#EBE2C9',
|
backgroundColor: '#EBE2C9',
|
||||||
@@ -160,6 +227,7 @@ const settingDefaults = {
|
|||||||
statusBarTop: false,// top, bottom
|
statusBarTop: false,// top, bottom
|
||||||
statusBarHeight: 19,// px
|
statusBarHeight: 19,// px
|
||||||
statusBarColorAlpha: 0.4,
|
statusBarColorAlpha: 0.4,
|
||||||
|
statusBarClickOpen: true,
|
||||||
|
|
||||||
scrollingDelay: 3000,// замедление, ms
|
scrollingDelay: 3000,// замедление, ms
|
||||||
scrollingType: 'ease-in-out', //linear, ease, ease-in, ease-out, ease-in-out
|
scrollingType: 'ease-in-out', //linear, ease, ease-in, ease-out, ease-in-out
|
||||||
@@ -187,6 +255,7 @@ const settingDefaults = {
|
|||||||
|
|
||||||
fontShifts: {},
|
fontShifts: {},
|
||||||
showToolButton: {},
|
showToolButton: {},
|
||||||
|
userHotKeys: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const font of fonts)
|
for (const font of fonts)
|
||||||
@@ -195,6 +264,41 @@ for (const font of webFonts)
|
|||||||
settingDefaults.fontShifts[font.name] = font.fontVertShift;
|
settingDefaults.fontShifts[font.name] = font.fontVertShift;
|
||||||
for (const button of toolButtons)
|
for (const button of toolButtons)
|
||||||
settingDefaults.showToolButton[button.name] = button.show;
|
settingDefaults.showToolButton[button.name] = button.show;
|
||||||
|
for (const hotKey of hotKeys)
|
||||||
|
settingDefaults.userHotKeys[hotKey.name] = hotKey.codes;
|
||||||
|
|
||||||
|
function addDefaultsToSettings(settings) {
|
||||||
|
const diff = utils.getObjDiff(settings, settingDefaults);
|
||||||
|
|
||||||
|
if (!utils.isEmptyObjDiffDeep(diff, {isApplyChange: false})) {
|
||||||
|
return utils.applyObjDiff(settings, diff, {isApplyChange: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const libsDefaults = {
|
||||||
|
startLink: 'http://flibusta.is',
|
||||||
|
comment: 'Флибуста | Книжное братство',
|
||||||
|
closeAfterSubmit: false,
|
||||||
|
groups: [
|
||||||
|
{r: 'http://flibusta.is', s: 'http://flibusta.is', list: [
|
||||||
|
{l: 'http://flibusta.is', c: 'Флибуста | Книжное братство'},
|
||||||
|
]},
|
||||||
|
{r: 'http://samlib.ru', s: 'http://samlib.ru', list: [
|
||||||
|
{l: 'http://samlib.ru', c: 'Журнал "Самиздат"'},
|
||||||
|
]},
|
||||||
|
{r: 'http://lib.ru', s: 'http://lib.ru', list: [
|
||||||
|
{l: 'http://lib.ru', c: 'Библиотека Максима Мошкова'},
|
||||||
|
]},
|
||||||
|
{r: 'http://fantasy-worlds.org', s: 'http://fantasy-worlds.org', list: [
|
||||||
|
{l: 'http://fantasy-worlds.org', c: 'Миры Фэнтези'},
|
||||||
|
]},
|
||||||
|
{r: 'https://aldebaran.ru', s: 'https://aldebaran.ru', list: [
|
||||||
|
{l: 'https://aldebaran.ru', c: 'АЛЬДЕБАРАН | Электронная библиотека книг'},
|
||||||
|
]},
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
// initial state
|
// initial state
|
||||||
const state = {
|
const state = {
|
||||||
@@ -209,6 +313,8 @@ const state = {
|
|||||||
currentProfile: '',
|
currentProfile: '',
|
||||||
settings: Object.assign({}, settingDefaults),
|
settings: Object.assign({}, settingDefaults),
|
||||||
settingsRev: {},
|
settingsRev: {},
|
||||||
|
libs: Object.assign({}, libsDefaults),
|
||||||
|
libsRev: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
// getters
|
// getters
|
||||||
@@ -247,18 +353,34 @@ const mutations = {
|
|||||||
state.currentProfile = value;
|
state.currentProfile = value;
|
||||||
},
|
},
|
||||||
setSettings(state, value) {
|
setSettings(state, value) {
|
||||||
state.settings = Object.assign({}, state.settings, value);
|
const newSettings = Object.assign({}, state.settings, value);
|
||||||
|
const added = addDefaultsToSettings(newSettings);
|
||||||
|
if (added) {
|
||||||
|
state.settings = added;
|
||||||
|
} else {
|
||||||
|
state.settings = newSettings;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setSettingsRev(state, value) {
|
setSettingsRev(state, value) {
|
||||||
state.settingsRev = Object.assign({}, state.settingsRev, value);
|
state.settingsRev = Object.assign({}, state.settingsRev, value);
|
||||||
},
|
},
|
||||||
|
setLibs(state, value) {
|
||||||
|
state.libs = value;
|
||||||
|
},
|
||||||
|
setLibsRev(state, value) {
|
||||||
|
state.libsRev = value;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
readerActions,
|
||||||
toolButtons,
|
toolButtons,
|
||||||
|
hotKeys,
|
||||||
fonts,
|
fonts,
|
||||||
webFonts,
|
webFonts,
|
||||||
settingDefaults,
|
settingDefaults,
|
||||||
|
addDefaultsToSettings,
|
||||||
|
libsDefaults,
|
||||||
|
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state,
|
state,
|
||||||
|
|||||||
85
docs/beta/beta.liberama
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
server {
|
||||||
|
listen 443 ssl; # managed by Certbot
|
||||||
|
ssl_certificate /etc/letsencrypt/live/beta.liberama.top/fullchain.pem; # managed by Certbot
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/beta.liberama.top/privkey.pem; # managed by Certbot
|
||||||
|
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||||||
|
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||||
|
|
||||||
|
server_name beta.liberama.top;
|
||||||
|
|
||||||
|
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:34082;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /ws {
|
||||||
|
proxy_pass http://127.0.0.1:34082;
|
||||||
|
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.liberama.top;
|
||||||
|
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name b.beta.liberama.top;
|
||||||
|
|
||||||
|
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:34082;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /ws {
|
||||||
|
proxy_pass http://127.0.0.1:34082;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
48
docs/beta/beta.omnireader
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
server {
|
||||||
|
listen 443 ssl; # managed by Certbot
|
||||||
|
ssl_certificate /etc/letsencrypt/live/beta.omnireader.ru/fullchain.pem; # managed by Certbot
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/beta.omnireader.ru/privkey.pem; # managed by Certbot
|
||||||
|
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||||||
|
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
4
docs/beta/deploy.sh
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
npm run build:linux
|
||||||
|
sudo -u www-data cp -r ../../dist/linux/* /home/beta.liberama
|
||||||
3
docs/beta/run_server.sh
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
sudo -H -u www-data /home/beta.liberama/liberama
|
||||||
128
docs/liberama.top/liberama
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
server {
|
||||||
|
server_name _;
|
||||||
|
listen 80 default_server;
|
||||||
|
listen 443 ssl default_server;
|
||||||
|
|
||||||
|
#openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/ssl/nginx.key -out /etc/nginx/ssl/nginx.crt
|
||||||
|
ssl_certificate /etc/nginx/ssl/nginx.crt;
|
||||||
|
ssl_certificate_key /etc/nginx/ssl/nginx.key;
|
||||||
|
return 403;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl; # managed by Certbot
|
||||||
|
ssl_certificate /etc/letsencrypt/live/liberama.top/fullchain.pem; # managed by Certbot
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/liberama.top/privkey.pem; # managed by Certbot
|
||||||
|
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||||||
|
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||||
|
|
||||||
|
server_name liberama.top;
|
||||||
|
|
||||||
|
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:55081;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /ws {
|
||||||
|
proxy_pass http://127.0.0.1:55081;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /home/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 liberama.top;
|
||||||
|
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name b.liberama.top;
|
||||||
|
|
||||||
|
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:55081;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /ws {
|
||||||
|
proxy_pass http://127.0.0.1:55081;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /home/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 23480;
|
||||||
|
server_name flibusta_proxy;
|
||||||
|
|
||||||
|
valid_referers liberama.top b.liberama.top;
|
||||||
|
|
||||||
|
if ($invalid_referer) {
|
||||||
|
return 403;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://flibusta.is;
|
||||||
|
proxy_redirect http://static.flibusta.is:443 http://b.liberama.top:23481;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 23481;
|
||||||
|
server_name flibusta_proxy_static;
|
||||||
|
|
||||||
|
valid_referers liberama.top b.liberama.top;
|
||||||
|
|
||||||
|
if ($invalid_referer) {
|
||||||
|
return 403;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://static.flibusta.is:443;
|
||||||
|
proxy_set_header Referer "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,11 +39,11 @@ sudo apt install poppler-utils
|
|||||||
```
|
```
|
||||||
|
|
||||||
### nginx, server config
|
### nginx, server config
|
||||||
Для своего домена необходимо будет подправить docs/omnireader/omnireader.
|
Для своего домена необходимо будет подправить docs/omnireader.ru/omnireader.
|
||||||
Можно также настроить сервер для HTTP, без SSL.
|
Можно также настроить сервер для HTTP, без SSL.
|
||||||
```
|
```
|
||||||
sudo apt install nginx
|
sudo apt install nginx
|
||||||
sudo cp docs/omnireader/omnireader /etc/nginx/sites-available/omnireader
|
sudo cp docs/omnireader.ru/omnireader /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
|
||||||
@@ -59,14 +59,20 @@ sudo service php7.2-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
|
||||||
sudo -u www-data cp -r docs/omnireader/old/* /home/oldreader
|
sudo -u www-data cp -r docs/omnireader.ru/old/* /home/oldreader
|
||||||
|
```
|
||||||
|
|
||||||
|
## Запуск по крону
|
||||||
|
```
|
||||||
|
* * * * * /root/liberama/docs/omnireader.ru/cron_server.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## Деплой и запуск
|
## Деплой и запуск
|
||||||
```
|
```
|
||||||
cd docs/omnireader
|
cd docs/omnireader.ru
|
||||||
|
./stop_server.sh
|
||||||
./deploy.sh
|
./deploy.sh
|
||||||
./run_server.sh
|
./start_server.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
После первого запуска будет создан конфигурационный файл `/home/liberama/data/config.json`.
|
После первого запуска будет создан конфигурационный файл `/home/liberama/data/config.json`.
|
||||||
@@ -81,4 +87,4 @@ cd docs/omnireader
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
и перезапустить `run_server.sh`
|
и перезапустить сервер
|
||||||
8
docs/omnireader.ru/cron_server.sh
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if ! pgrep -x "liberama" > /dev/null ; then
|
||||||
|
sudo -H -u www-data /home/liberama/liberama
|
||||||
|
else
|
||||||
|
echo "Process 'liberama' already running"
|
||||||
|
fi
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 227 B After Width: | Height: | Size: 227 B |
|
Before Width: | Height: | Size: 246 B After Width: | Height: | Size: 246 B |
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 193 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
@@ -30,7 +30,7 @@ server {
|
|||||||
root /home/liberama/public;
|
root /home/liberama/public;
|
||||||
|
|
||||||
location /tmp {
|
location /tmp {
|
||||||
add_header Content-Type text/xml;
|
types { } default_type "application/xml; charset=utf-8";
|
||||||
add_header Content-Encoding gzip;
|
add_header Content-Encoding gzip;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ server {
|
|||||||
root /home/liberama/public;
|
root /home/liberama/public;
|
||||||
|
|
||||||
location /tmp {
|
location /tmp {
|
||||||
add_header Content-Type text/xml;
|
types { } default_type "application/xml; charset=utf-8";
|
||||||
add_header Content-Encoding gzip;
|
add_header Content-Encoding gzip;
|
||||||
}
|
}
|
||||||
|
|
||||||
4
docs/omnireader.ru/start_server.sh
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
sudo -H -u www-data /home/liberama/liberama &
|
||||||
|
sudo service cron start
|
||||||
4
docs/omnireader.ru/stop_server.sh
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
sudo service cron stop
|
||||||
|
sudo killall liberama
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
sudo -H -u www-data bash -c "\
|
|
||||||
while true; do\
|
|
||||||
trap '' 2;\
|
|
||||||
cd /var/www;\
|
|
||||||
/home/liberama/liberama;\
|
|
||||||
trap 2;\
|
|
||||||
echo \"Restart after 5 sec. Press Ctrl+C to exit.\";\
|
|
||||||
sleep 5;\
|
|
||||||
done;"
|
|
||||||
3833
package-lock.json
generated
42
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Liberama",
|
"name": "Liberama",
|
||||||
"version": "0.9.0",
|
"version": "0.9.5",
|
||||||
"author": "Book Pauk <bookpauk@gmail.com>",
|
"author": "Book Pauk <bookpauk@gmail.com>",
|
||||||
"license": "CC0-1.0",
|
"license": "CC0-1.0",
|
||||||
"repository": "bookpauk/liberama",
|
"repository": "bookpauk/liberama",
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-core": "^6.22.1",
|
"babel-core": "^6.22.1",
|
||||||
"babel-eslint": "^10.0.3",
|
"babel-eslint": "^10.1.0",
|
||||||
"babel-loader": "^7.1.1",
|
"babel-loader": "^7.1.1",
|
||||||
"babel-plugin-component": "^1.1.1",
|
"babel-plugin-component": "^1.1.1",
|
||||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||||
@@ -32,7 +32,6 @@
|
|||||||
"clean-webpack-plugin": "^1.0.1",
|
"clean-webpack-plugin": "^1.0.1",
|
||||||
"copy-webpack-plugin": "^5.1.1",
|
"copy-webpack-plugin": "^5.1.1",
|
||||||
"css-loader": "^1.0.0",
|
"css-loader": "^1.0.0",
|
||||||
"element-theme-chalk": "^2.12.0",
|
|
||||||
"eslint": "^5.16.0",
|
"eslint": "^5.16.0",
|
||||||
"eslint-plugin-html": "^5.0.5",
|
"eslint-plugin-html": "^5.0.5",
|
||||||
"eslint-plugin-node": "^8.0.0",
|
"eslint-plugin-node": "^8.0.0",
|
||||||
@@ -41,27 +40,26 @@
|
|||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"mini-css-extract-plugin": "^0.5.0",
|
"mini-css-extract-plugin": "^0.5.0",
|
||||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||||
"pkg": "^4.4.2",
|
"pkg": "^4.4.4",
|
||||||
"terser-webpack-plugin": "^1.4.1",
|
"terser-webpack-plugin": "^1.4.1",
|
||||||
"url-loader": "^1.1.2",
|
"url-loader": "^1.1.2",
|
||||||
"vue-class-component": "^6.3.2",
|
"vue-class-component": "^6.3.2",
|
||||||
"vue-loader": "^15.7.1",
|
"vue-loader": "^15.9.0",
|
||||||
"vue-style-loader": "^4.1.2",
|
"vue-style-loader": "^4.1.2",
|
||||||
"vue-template-compiler": "^2.6.10",
|
"vue-template-compiler": "^2.6.11",
|
||||||
"webpack": "^4.39.3",
|
"webpack": "^4.42.0",
|
||||||
"webpack-cli": "^3.3.7",
|
"webpack-cli": "^3.3.11",
|
||||||
"webpack-dev-middleware": "^3.7.1",
|
"webpack-dev-middleware": "^3.7.2",
|
||||||
"webpack-hot-middleware": "^2.25.0",
|
"webpack-hot-middleware": "^2.25.0",
|
||||||
"webpack-merge": "^4.2.2"
|
"webpack-merge": "^4.2.2",
|
||||||
|
"workbox-webpack-plugin": "^5.1.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@quasar/extras": "^1.5.0",
|
"@quasar/extras": "^1.5.2",
|
||||||
"appcache-webpack-plugin": "^1.4.0",
|
|
||||||
"axios": "^0.18.1",
|
"axios": "^0.18.1",
|
||||||
"base-x": "^3.0.6",
|
"base-x": "^3.0.8",
|
||||||
"chardet": "^0.7.0",
|
"chardet": "^0.7.0",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"element-ui": "^2.12.0",
|
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"fg-loadcss": "^2.1.0",
|
"fg-loadcss": "^2.1.0",
|
||||||
"fs-extra": "^7.0.1",
|
"fs-extra": "^7.0.1",
|
||||||
@@ -72,21 +70,21 @@
|
|||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"minimist": "^1.2.0",
|
"minimist": "^1.2.0",
|
||||||
"multer": "^1.4.2",
|
"multer": "^1.4.2",
|
||||||
"pako": "^1.0.10",
|
"pako": "^1.0.11",
|
||||||
"path-browserify": "^1.0.0",
|
"path-browserify": "^1.0.0",
|
||||||
"quasar": "^1.8.5",
|
"quasar": "^1.14.3",
|
||||||
"safe-buffer": "^5.2.0",
|
"safe-buffer": "^5.2.0",
|
||||||
"sjcl": "^1.0.8",
|
"sjcl": "^1.0.8",
|
||||||
"sql-template-strings": "^2.2.2",
|
"sql-template-strings": "^2.2.2",
|
||||||
"sqlite": "^3.0.3",
|
"sqlite": "^3.0.3",
|
||||||
"tar-fs": "^2.0.0",
|
"tar-fs": "^2.0.0",
|
||||||
"unbzip2-stream": "^1.3.3",
|
"unbzip2-stream": "^1.3.3",
|
||||||
"vue": "github:paulkamer/vue#fix_palemoon_clickhandlers_dist",
|
"vue": "github:bookpauk/vue",
|
||||||
"vue-router": "^3.1.3",
|
"vue-router": "^3.1.6",
|
||||||
"vuex": "^3.1.1",
|
"vuex": "^3.1.2",
|
||||||
"vuex-persistedstate": "^2.5.4",
|
"vuex-persistedstate": "^2.7.1",
|
||||||
"webdav": "^2.10.1",
|
"webdav": "^2.10.2",
|
||||||
"ws": "^7.2.1",
|
"ws": "^7.2.1",
|
||||||
"zip-stream": "^2.1.2"
|
"zip-stream": "^2.1.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ class ReaderController extends BaseController {
|
|||||||
throw new Error(`key 'url' is empty`);
|
throw new Error(`key 'url' is empty`);
|
||||||
const workerId = this.readerWorker.loadBookUrl({
|
const workerId = this.readerWorker.loadBookUrl({
|
||||||
url: request.url,
|
url: request.url,
|
||||||
enableSitesFilter: (request.hasOwnProperty('enableSitesFilter') ? request.enableSitesFilter : true)
|
enableSitesFilter: (request.hasOwnProperty('enableSitesFilter') ? request.enableSitesFilter : true),
|
||||||
|
skipCheck: (request.hasOwnProperty('skipCheck') ? request.skipCheck : false),
|
||||||
|
isText: (request.hasOwnProperty('isText') ? request.isText : false),
|
||||||
});
|
});
|
||||||
const state = this.workerState.getState(workerId);
|
const state = this.workerState.getState(workerId);
|
||||||
return (state ? state : {});
|
return (state ? state : {});
|
||||||
|
|||||||
@@ -119,14 +119,19 @@ class FileDecompressor {
|
|||||||
try {
|
try {
|
||||||
return await zip.unpack(filename, outputDir, {
|
return await zip.unpack(filename, outputDir, {
|
||||||
limitFileSize: this.limitFileSize,
|
limitFileSize: this.limitFileSize,
|
||||||
limitFileCount: 1000
|
limitFileCount: 1000,
|
||||||
});
|
decodeEntryNameCallback: (nameRaw) => {
|
||||||
|
return utils.bufferRemoveZeroes(nameRaw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
fs.emptyDir(outputDir);
|
fs.emptyDir(outputDir);
|
||||||
return await zip.unpack(filename, outputDir, {
|
return await zip.unpack(filename, outputDir, {
|
||||||
limitFileSize: this.limitFileSize,
|
limitFileSize: this.limitFileSize,
|
||||||
limitFileCount: 1000,
|
limitFileCount: 1000,
|
||||||
decodeEntryNameCallback: (nameRaw) => {
|
decodeEntryNameCallback: (nameRaw) => {
|
||||||
|
nameRaw = utils.bufferRemoveZeroes(nameRaw);
|
||||||
const enc = textUtils.getEncodingLite(nameRaw);
|
const enc = textUtils.getEncodingLite(nameRaw);
|
||||||
if (enc.indexOf('ISO-8859') < 0) {
|
if (enc.indexOf('ISO-8859') < 0) {
|
||||||
return iconv.decode(nameRaw, enc);
|
return iconv.decode(nameRaw, enc);
|
||||||
@@ -241,15 +246,16 @@ class FileDecompressor {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async gzipFileIfNotExists(filename, outDir) {
|
async gzipFileIfNotExists(filename, outDir, isMaxCompression) {
|
||||||
const hash = await utils.getFileHash(filename, 'sha256', 'hex');
|
const hash = await utils.getFileHash(filename, 'sha256', 'hex');
|
||||||
|
|
||||||
const outFilename = `${outDir}/${hash}`;
|
const outFilename = `${outDir}/${hash}`;
|
||||||
|
|
||||||
if (!await fs.pathExists(outFilename)) {
|
if (!await fs.pathExists(outFilename)) {
|
||||||
await this.gzipFile(filename, outFilename, 1);
|
await this.gzipFile(filename, outFilename, (isMaxCompression ? 9 : 1));
|
||||||
|
|
||||||
// переупакуем через некоторое время на максималках
|
// переупакуем через некоторое время на максималках, если упаковали плохо
|
||||||
|
if (!isMaxCompression) {
|
||||||
const filenameCopy = `${filename}.copy`;
|
const filenameCopy = `${filename}.copy`;
|
||||||
await fs.copy(filename, filenameCopy);
|
await fs.copy(filename, filenameCopy);
|
||||||
|
|
||||||
@@ -262,6 +268,7 @@ class FileDecompressor {
|
|||||||
|
|
||||||
await fs.remove(filenameCopy);
|
await fs.remove(filenameCopy);
|
||||||
})().catch((e) => { if (appLogger.inited) appLogger.log(LM_ERR, `FileDecompressor.gzipFileIfNotExists: ${e.message}`) });
|
})().catch((e) => { if (appLogger.inited) appLogger.log(LM_ERR, `FileDecompressor.gzipFileIfNotExists: ${e.message}`) });
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
await utils.touchFile(outFilename);
|
await utils.touchFile(outFilename);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const signatures = require('./signatures.json');
|
|||||||
class FileDetector {
|
class FileDetector {
|
||||||
detectFile(filename) {
|
detectFile(filename) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.fromFile(filename, 2000, (err, result) => {
|
this.fromFile(filename, 10000, (err, result) => {
|
||||||
if (err) reject(err);
|
if (err) reject(err);
|
||||||
resolve(result);
|
resolve(result);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -653,40 +653,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
"type": "svg",
|
|
||||||
"ext": "svg",
|
|
||||||
"mime": "image/svg+xml",
|
|
||||||
"rules": [
|
|
||||||
{ "type": "contains", "bytes": "3c737667" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"type": "html",
|
|
||||||
"ext": "html",
|
|
||||||
"mime": "text/html",
|
|
||||||
"rules": [
|
|
||||||
{ "type": "or", "rules":
|
|
||||||
[
|
|
||||||
{ "type": "contains", "bytes": "3c68746d6c" },
|
|
||||||
{ "type": "contains", "bytes": "3c00680074006d006c00" },
|
|
||||||
{ "type": "equal", "end": 5, "bytes": "3c68746d6c" },
|
|
||||||
{ "type": "equal", "end": 10, "bytes": "3c00680074006d006c00" },
|
|
||||||
{ "type": "equal", "end": 9, "bytes": "3c21646f6374797065" },
|
|
||||||
{ "type": "equal", "end": 5, "bytes": "3c626f6479" },
|
|
||||||
{ "type": "equal", "end": 5, "bytes": "3c68656164" },
|
|
||||||
{ "type": "equal", "end": 7, "bytes": "3c696672616d65" },
|
|
||||||
{ "type": "equal", "end": 4, "bytes": "3c696d67" },
|
|
||||||
{ "type": "equal", "end": 7, "bytes": "3c6f626a656374" },
|
|
||||||
{ "type": "equal", "end": 7, "bytes": "3c736372697074" },
|
|
||||||
{ "type": "equal", "end": 6, "bytes": "3c7461626c65" },
|
|
||||||
{ "type": "equal", "end": 6, "bytes": "3c7469746c65" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "docx",
|
"type": "docx",
|
||||||
"ext": "docx",
|
"ext": "docx",
|
||||||
@@ -708,7 +674,9 @@
|
|||||||
{ "type": "or", "rules":
|
{ "type": "or", "rules":
|
||||||
[
|
[
|
||||||
{ "type": "equal", "end": 19, "bytes": "3c3f786d6c2076657273696f6e3d22312e3022" },
|
{ "type": "equal", "end": 19, "bytes": "3c3f786d6c2076657273696f6e3d22312e3022" },
|
||||||
{ "type": "equal", "end": 22, "bytes": "efbbbf3c3f786d6c2076657273696f6e3d22312e3022" }
|
{ "type": "equal", "end": 22, "bytes": "efbbbf3c3f786d6c2076657273696f6e3d22312e3022" },
|
||||||
|
{ "type": "equal", "end": 19, "bytes": "3c3f786d6c2076657273696f6e3d27312e3027" },
|
||||||
|
{ "type": "equal", "end": 22, "bytes": "efbbbf3c3f786d6c2076657273696f6e3d27312e3027" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -721,6 +689,53 @@
|
|||||||
"rules": [
|
"rules": [
|
||||||
{ "type": "equal", "start": 64, "end": 68, "bytes": "4d4f4249" }
|
{ "type": "equal", "start": 64, "end": 68, "bytes": "4d4f4249" }
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"type": "djvu",
|
||||||
|
"ext": "djvu",
|
||||||
|
"mime": "image/vnd.djvu",
|
||||||
|
"rules": [
|
||||||
|
{ "type": "equal", "start": 0, "end": 8, "bytes": "41542654464f524d" },
|
||||||
|
{ "type": "equal", "start": 12, "end": 15, "bytes": "444a56" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"type": "html",
|
||||||
|
"ext": "html",
|
||||||
|
"mime": "text/html",
|
||||||
|
"rules": [
|
||||||
|
{ "type": "or", "rules":
|
||||||
|
[
|
||||||
|
{ "type": "contains", "bytes": "3c68746d6c" },
|
||||||
|
{ "type": "contains", "bytes": "3c00680074006d006c00" },
|
||||||
|
{ "type": "contains", "bytes": "3c48544d4c" },
|
||||||
|
{ "type": "contains", "bytes": "3c00480054004d004c00" },
|
||||||
|
|
||||||
|
{ "type": "equal", "end": 5, "bytes": "3c68746d6c" },
|
||||||
|
{ "type": "equal", "end": 10, "bytes": "3c00680074006d006c00" },
|
||||||
|
{ "type": "equal", "end": 9, "bytes": "3c21646f6374797065" },
|
||||||
|
{ "type": "equal", "end": 9, "bytes": "3c21444f4354595045" },
|
||||||
|
{ "type": "equal", "end": 5, "bytes": "3c626f6479" },
|
||||||
|
{ "type": "equal", "end": 5, "bytes": "3c68656164" },
|
||||||
|
{ "type": "equal", "end": 7, "bytes": "3c696672616d65" },
|
||||||
|
{ "type": "equal", "end": 4, "bytes": "3c696d67" },
|
||||||
|
{ "type": "equal", "end": 7, "bytes": "3c6f626a656374" },
|
||||||
|
{ "type": "equal", "end": 7, "bytes": "3c736372697074" },
|
||||||
|
{ "type": "equal", "end": 6, "bytes": "3c7461626c65" },
|
||||||
|
{ "type": "equal", "end": 6, "bytes": "3c7469746c65" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"type": "svg",
|
||||||
|
"ext": "svg",
|
||||||
|
"mime": "image/svg+xml",
|
||||||
|
"rules": [
|
||||||
|
{ "type": "contains", "bytes": "3c737667" }
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
1
server/core/Reader/BookConverter/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
test
|
||||||
@@ -200,7 +200,7 @@ class ConvertHtml extends ConvertBase {
|
|||||||
|
|
||||||
titleInfo['book-title'] = title;
|
titleInfo['book-title'] = title;
|
||||||
//подозрение на чистый текст, надо разбить на параграфы
|
//подозрение на чистый текст, надо разбить на параграфы
|
||||||
if (isText || pars.length < buf.length/2000) {
|
if (isText || (buf.length > 30*1024 && pars.length < buf.length/2000)) {
|
||||||
let total = 0;
|
let total = 0;
|
||||||
let count = 1;
|
let count = 1;
|
||||||
for (let i = 0; i < spaceCounter.length; i++) {
|
for (let i = 0; i < spaceCounter.length; i++) {
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ class ReaderWorker {
|
|||||||
let decompDir = '';
|
let decompDir = '';
|
||||||
let downloadedFilename = '';
|
let downloadedFilename = '';
|
||||||
let isUploaded = false;
|
let isUploaded = false;
|
||||||
|
let isRestored = false;
|
||||||
let convertFilename = '';
|
let convertFilename = '';
|
||||||
|
|
||||||
const overLoadMes = 'Слишком большая очередь загрузки. Пожалуйста, попробуйте позже.';
|
const overLoadMes = 'Слишком большая очередь загрузки. Пожалуйста, попробуйте позже.';
|
||||||
@@ -88,9 +89,17 @@ class ReaderWorker {
|
|||||||
downloadedFilename = `${this.config.tempDownloadDir}/${tempFilename}`;
|
downloadedFilename = `${this.config.tempDownloadDir}/${tempFilename}`;
|
||||||
await fs.writeFile(downloadedFilename, downdata);
|
await fs.writeFile(downloadedFilename, downdata);
|
||||||
} else {//uploaded file
|
} else {//uploaded file
|
||||||
downloadedFilename = `${this.config.uploadDir}/${url.substr(7)}`;
|
const fileHash = url.substr(7);
|
||||||
if (!await fs.pathExists(downloadedFilename))
|
downloadedFilename = `${this.config.uploadDir}/${fileHash}`;
|
||||||
|
if (!await fs.pathExists(downloadedFilename)) {
|
||||||
|
//если удалено из upload, попробуем восстановить из удаленного хранилища
|
||||||
|
try {
|
||||||
|
downloadedFilename = await this.restoreRemoteFile(fileHash);
|
||||||
|
isRestored = true;
|
||||||
|
} catch(e) {
|
||||||
throw new Error('Файл не найден на сервере (возможно был удален как устаревший). Пожалуйста, загрузите файл с диска на сервер заново.');
|
throw new Error('Файл не найден на сервере (возможно был удален как устаревший). Пожалуйста, загрузите файл с диска на сервер заново.');
|
||||||
|
}
|
||||||
|
}
|
||||||
await utils.touchFile(downloadedFilename);
|
await utils.touchFile(downloadedFilename);
|
||||||
isUploaded = true;
|
isUploaded = true;
|
||||||
}
|
}
|
||||||
@@ -146,6 +155,20 @@ class ReaderWorker {
|
|||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//лениво сохраним downloadedFilename в tmp и в удаленном хранилище в случае isUploaded
|
||||||
|
if (this.remoteWebDavStorage && isUploaded && !isRestored) {
|
||||||
|
(async() => {
|
||||||
|
await utils.sleep(30*1000);
|
||||||
|
try {
|
||||||
|
//сжимаем файл в tmp, если там уже нет с тем же именем-sha256
|
||||||
|
const compDownloadedFilename = await this.decomp.gzipFileIfNotExists(downloadedFilename, this.config.tempPublicDir, true);
|
||||||
|
await this.remoteWebDavStorage.putFile(compDownloadedFilename);
|
||||||
|
} catch (e) {
|
||||||
|
log(LM_ERR, e.stack);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(LM_ERR, e.stack);
|
log(LM_ERR, e.stack);
|
||||||
if (e.message == 'abort')
|
if (e.message == 'abort')
|
||||||
@@ -188,15 +211,7 @@ class ReaderWorker {
|
|||||||
return `file://${hash}`;
|
return `file://${hash}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreCachedFile(filename) {
|
async restoreRemoteFile(filename) {
|
||||||
const workerId = this.workerState.generateWorkerId();
|
|
||||||
const wState = this.workerState.getControl(workerId);
|
|
||||||
wState.set({state: 'start'});
|
|
||||||
|
|
||||||
(async() => {
|
|
||||||
try {
|
|
||||||
wState.set({state: 'download', step: 1, totalSteps: 1, path: filename, progress: 0});
|
|
||||||
|
|
||||||
const basename = path.basename(filename);
|
const basename = path.basename(filename);
|
||||||
const targetName = `${this.config.tempPublicDir}/${basename}`;
|
const targetName = `${this.config.tempPublicDir}/${basename}`;
|
||||||
|
|
||||||
@@ -211,7 +226,22 @@ class ReaderWorker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return targetName;
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreCachedFile(filename) {
|
||||||
|
const workerId = this.workerState.generateWorkerId();
|
||||||
|
const wState = this.workerState.getControl(workerId);
|
||||||
|
wState.set({state: 'start'});
|
||||||
|
|
||||||
|
(async() => {
|
||||||
|
try {
|
||||||
|
wState.set({state: 'download', step: 1, totalSteps: 1, path: filename, progress: 0});
|
||||||
|
|
||||||
|
const targetName = await this.restoreRemoteFile(filename);
|
||||||
const stat = await fs.stat(targetName);
|
const stat = await fs.stat(targetName);
|
||||||
|
|
||||||
|
const basename = path.basename(filename);
|
||||||
wState.finish({path: `/tmp/${basename}`, size: stat.size, progress: 100});
|
wState.finish({path: `/tmp/${basename}`, size: stat.size, progress: 100});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.message.indexOf('404') < 0)
|
if (e.message.indexOf('404') < 0)
|
||||||
|
|||||||
@@ -766,7 +766,7 @@ ZipEntry.prototype.readDataHeader = function(data) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ZipEntry.prototype.read = function(data, offset) {
|
ZipEntry.prototype.read = function(data, offset) {
|
||||||
this.nameRaw = data.slice(offset, offset += this.fnameLen);
|
this.nameRaw = Buffer.from(data.slice(offset, offset += this.fnameLen));
|
||||||
this.name = this.nameRaw.toString();
|
this.name = this.nameRaw.toString();
|
||||||
var lastChar = data[offset - 1];
|
var lastChar = data[offset - 1];
|
||||||
this.isDirectory = (lastChar == 47) || (lastChar == 92);
|
this.isDirectory = (lastChar == 47) || (lastChar == 92);
|
||||||
|
|||||||
@@ -93,6 +93,12 @@ function parseSync(xstr, options) {
|
|||||||
}
|
}
|
||||||
tag = tag.toLowerCase();
|
tag = tag.toLowerCase();
|
||||||
|
|
||||||
|
if (innerCut.has(tag) && (!cutCounter || cutTag === tag)) {
|
||||||
|
if (!cutCounter)
|
||||||
|
cutTag = tag;
|
||||||
|
cutCounter++;
|
||||||
|
}
|
||||||
|
|
||||||
let endTag = (singleTag ? tag : '');
|
let endTag = (singleTag ? tag : '');
|
||||||
if (tag === '' || tag[0] !== '/') {
|
if (tag === '' || tag[0] !== '/') {
|
||||||
_onStartNode(tag, tail, singleTag, cutCounter, cutTag);
|
_onStartNode(tag, tail, singleTag, cutCounter, cutTag);
|
||||||
@@ -103,12 +109,6 @@ function parseSync(xstr, options) {
|
|||||||
if (endTag)
|
if (endTag)
|
||||||
_onEndNode(endTag, tail, singleTag, cutCounter, cutTag);
|
_onEndNode(endTag, tail, singleTag, cutCounter, cutTag);
|
||||||
|
|
||||||
if (innerCut.has(tag) && (!cutCounter || cutTag === tag)) {
|
|
||||||
if (!cutCounter)
|
|
||||||
cutTag = tag;
|
|
||||||
cutCounter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cutTag === endTag) {
|
if (cutTag === endTag) {
|
||||||
cutCounter = (cutCounter > 0 ? cutCounter - 1 : 0);
|
cutCounter = (cutCounter > 0 ? cutCounter - 1 : 0);
|
||||||
if (!cutCounter)
|
if (!cutCounter)
|
||||||
@@ -125,9 +125,9 @@ function parseSync(xstr, options) {
|
|||||||
|
|
||||||
if (i < len) {
|
if (i < len) {
|
||||||
if (inCdata) {
|
if (inCdata) {
|
||||||
_onCdata(xstr.substr(leftData, len - leftData), cutCounter, cutTag);
|
_onCdata(xstr.substr(leftData + 1, len - leftData - 1), cutCounter, cutTag);
|
||||||
} else if (inComment) {
|
} else if (inComment) {
|
||||||
_onComment(xstr.substr(leftData, len - leftData), cutCounter, cutTag);
|
_onComment(xstr.substr(leftData + 1, len - leftData - 1), cutCounter, cutTag);
|
||||||
} else {
|
} else {
|
||||||
_onTextNode(xstr.substr(i, len - i), cutCounter, cutTag);
|
_onTextNode(xstr.substr(i, len - i), cutCounter, cutTag);
|
||||||
}
|
}
|
||||||
@@ -233,6 +233,12 @@ async function parse(xstr, options) {
|
|||||||
}
|
}
|
||||||
tag = tag.toLowerCase();
|
tag = tag.toLowerCase();
|
||||||
|
|
||||||
|
if (innerCut.has(tag) && (!cutCounter || cutTag === tag)) {
|
||||||
|
if (!cutCounter)
|
||||||
|
cutTag = tag;
|
||||||
|
cutCounter++;
|
||||||
|
}
|
||||||
|
|
||||||
let endTag = (singleTag ? tag : '');
|
let endTag = (singleTag ? tag : '');
|
||||||
if (tag === '' || tag[0] !== '/') {
|
if (tag === '' || tag[0] !== '/') {
|
||||||
await _onStartNode(tag, tail, singleTag, cutCounter, cutTag);
|
await _onStartNode(tag, tail, singleTag, cutCounter, cutTag);
|
||||||
@@ -243,12 +249,6 @@ async function parse(xstr, options) {
|
|||||||
if (endTag)
|
if (endTag)
|
||||||
await _onEndNode(endTag, tail, singleTag, cutCounter, cutTag);
|
await _onEndNode(endTag, tail, singleTag, cutCounter, cutTag);
|
||||||
|
|
||||||
if (innerCut.has(tag) && (!cutCounter || cutTag === tag)) {
|
|
||||||
if (!cutCounter)
|
|
||||||
cutTag = tag;
|
|
||||||
cutCounter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cutTag === endTag) {
|
if (cutTag === endTag) {
|
||||||
cutCounter = (cutCounter > 0 ? cutCounter - 1 : 0);
|
cutCounter = (cutCounter > 0 ? cutCounter - 1 : 0);
|
||||||
if (!cutCounter)
|
if (!cutCounter)
|
||||||
@@ -265,9 +265,9 @@ async function parse(xstr, options) {
|
|||||||
|
|
||||||
if (i < len) {
|
if (i < len) {
|
||||||
if (inCdata) {
|
if (inCdata) {
|
||||||
await _onCdata(xstr.substr(leftData, len - leftData), cutCounter, cutTag);
|
await _onCdata(xstr.substr(leftData + 1, len - leftData - 1), cutCounter, cutTag);
|
||||||
} else if (inComment) {
|
} else if (inComment) {
|
||||||
await _onComment(xstr.substr(leftData, len - leftData), cutCounter, cutTag);
|
await _onComment(xstr.substr(leftData + 1, len - leftData - 1), cutCounter, cutTag);
|
||||||
} else {
|
} else {
|
||||||
await _onTextNode(xstr.substr(i, len - i), cutCounter, cutTag);
|
await _onTextNode(xstr.substr(i, len - i), cutCounter, cutTag);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,14 @@ function fromBase36(data) {
|
|||||||
return bs36.decode(data);
|
return bs36.decode(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function bufferRemoveZeroes(buf) {
|
||||||
|
const i = buf.indexOf(0);
|
||||||
|
if (i >= 0) {
|
||||||
|
return buf.slice(0, i);
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
function getFileHash(filename, hashName, enc) {
|
function getFileHash(filename, hashName, enc) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const hash = crypto.createHash(hashName);
|
const hash = crypto.createHash(hashName);
|
||||||
@@ -86,6 +94,7 @@ function spawnProcess(cmd, opts) {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
toBase36,
|
toBase36,
|
||||||
fromBase36,
|
fromBase36,
|
||||||
|
bufferRemoveZeroes,
|
||||||
getFileHash,
|
getFileHash,
|
||||||
sleep,
|
sleep,
|
||||||
randomHexString,
|
randomHexString,
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ async function main() {
|
|||||||
maxAge: '30d',
|
maxAge: '30d',
|
||||||
setHeaders: (res, filePath) => {
|
setHeaders: (res, filePath) => {
|
||||||
if (path.basename(path.dirname(filePath)) == 'tmp') {
|
if (path.basename(path.dirname(filePath)) == 'tmp') {
|
||||||
res.set('Content-Type', 'text/xml');
|
res.set('Content-Type', 'application/xml');
|
||||||
res.set('Content-Encoding', 'gzip');
|
res.set('Content-Encoding', 'gzip');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||