Compare commits
94 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -8,7 +8,7 @@
|
||||

|
||||
|
||||
## VPS
|
||||
Для разворачивания читалки на чистом VPS с нуля смотрите [docs/omnireader](docs/omnireader/README.md)
|
||||
Для разворачивания читалки на чистом VPS с нуля смотрите [docs/omnireader.ru](docs/omnireader.ru/README.md)
|
||||
|
||||
## Сборка проекта
|
||||
Необходима версия node.js не ниже 10.
|
||||
|
||||
@@ -9,7 +9,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
|
||||
const {GenerateSW} = require('workbox-webpack-plugin');
|
||||
|
||||
const publicDir = path.resolve(__dirname, '../dist/tmp/public');
|
||||
const clientDir = path.resolve(__dirname, '../client');
|
||||
@@ -55,12 +55,12 @@ module.exports = merge(baseWpConfig, {
|
||||
filename: `${publicDir}/index.html`
|
||||
}),
|
||||
new CopyWebpackPlugin([{from: `${clientDir}/assets/*`, to: `${publicDir}/`, flatten: true}]),
|
||||
new SWPrecacheWebpackPlugin({
|
||||
new GenerateSW({
|
||||
cacheId: 'liberama',
|
||||
filepath: `${publicDir}/service-worker.js`,
|
||||
minify: true,
|
||||
swDest: `${publicDir}/service-worker.js`,
|
||||
navigateFallback: '/index.html',
|
||||
stripPrefix: publicDir,
|
||||
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 минута
|
||||
|
||||
class WebSocketConnection {
|
||||
@@ -9,6 +11,8 @@ class WebSocketConnection {
|
||||
this.messageQueue = [];
|
||||
this.messageLifeTime = messageLifeTime;
|
||||
this.requestId = 0;
|
||||
|
||||
this.connecting = false;
|
||||
}
|
||||
|
||||
addListener(listener) {
|
||||
@@ -53,14 +57,22 @@ class WebSocketConnection {
|
||||
}
|
||||
|
||||
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) {
|
||||
resolve(this.ws);
|
||||
} else {
|
||||
let protocol = 'ws:';
|
||||
if (window.location.protocol == 'https:') {
|
||||
protocol = 'wss:'
|
||||
}
|
||||
this.connecting = true;
|
||||
const protocol = (window.location.protocol == 'https:' ? 'wss:' : 'ws:');
|
||||
|
||||
url = url || `${protocol}//${window.location.host}/ws`;
|
||||
|
||||
@@ -71,9 +83,8 @@ class WebSocketConnection {
|
||||
}
|
||||
this.timer = setTimeout(() => { this.periodicClean(); }, cleanPeriod);
|
||||
|
||||
let resolved = false;
|
||||
this.ws.onopen = (e) => {
|
||||
resolved = true;
|
||||
this.connecting = false;
|
||||
resolve(e);
|
||||
};
|
||||
|
||||
@@ -97,11 +108,13 @@ class WebSocketConnection {
|
||||
|
||||
this.ws.onerror = (e) => {
|
||||
this.emit(e.message, true);
|
||||
if (!resolved)
|
||||
if (this.connecting) {
|
||||
this.connecting = false;
|
||||
reject(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
})() });
|
||||
}
|
||||
|
||||
//timeout в минутах (cleanPeriod)
|
||||
|
||||
@@ -1,56 +1,4 @@
|
||||
<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">
|
||||
<Notify ref="notify"/>
|
||||
<StdDialog ref="stdDialog"/>
|
||||
@@ -90,7 +38,8 @@ class App extends Vue {
|
||||
'/sources': 'Источники',
|
||||
'/settings': 'Параметры',
|
||||
'/help': 'Справка',
|
||||
}
|
||||
};
|
||||
|
||||
created() {
|
||||
this.commit = this.$store.commit;
|
||||
this.dispatch = this.$store.dispatch;
|
||||
@@ -106,10 +55,20 @@ class App extends Vue {
|
||||
cachedPath = this.$route.path;
|
||||
const m = cachedPath.match(/^(\/[^/]*).*$/i);
|
||||
cachedRoute = (m ? m[1] : this.$route.path);
|
||||
|
||||
}
|
||||
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
|
||||
this.$root.$on('set-app-title', this.setAppTitle);
|
||||
|
||||
@@ -133,15 +92,26 @@ class App extends Vue {
|
||||
|
||||
document.addEventListener('keyup', (event) => {
|
||||
this.keyHook(event);
|
||||
});
|
||||
});
|
||||
document.addEventListener('keypress', (event) => {
|
||||
this.keyHook(event);
|
||||
});
|
||||
document.addEventListener('keydown', (event) => {
|
||||
this.keyHook(event);
|
||||
});
|
||||
});
|
||||
window.addEventListener('resize', () => {
|
||||
this.$root.$emit('resize');
|
||||
});
|
||||
}
|
||||
|
||||
routerReady() {
|
||||
return new Promise ((resolve) => {
|
||||
this.$router.onReady(() => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
mounted() {
|
||||
this.$root.notify = this.$refs.notify;
|
||||
this.$root.stdDialog = this.$refs.stdDialog;
|
||||
@@ -157,7 +127,10 @@ class App extends Vue {
|
||||
});
|
||||
|
||||
this.setAppTitle();
|
||||
this.redirectIfNeeded();
|
||||
(async() => {
|
||||
await this.routerReady();
|
||||
this.redirectIfNeeded();
|
||||
})();
|
||||
}
|
||||
|
||||
toggleCollapse() {
|
||||
@@ -202,7 +175,9 @@ class App extends Vue {
|
||||
|
||||
setAppTitle(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 - всегда с вами`;
|
||||
} else if (this.config && this.mode !== null) {
|
||||
document.title = `${this.config.name} - ${this.itemRuText[this.$root.rootRoute]}`;
|
||||
@@ -221,29 +196,32 @@ class App extends Vue {
|
||||
}
|
||||
|
||||
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) {
|
||||
}
|
||||
|
||||
get isReaderActive() {
|
||||
return this.rootRoute == '/reader';
|
||||
return (this.rootRoute == '/reader' || this.rootRoute == '/external-libs');
|
||||
}
|
||||
|
||||
redirectIfNeeded() {
|
||||
if ((this.mode == 'reader' || this.mode == 'omnireader') && (!this.isReaderActive)) {
|
||||
//старый url
|
||||
if ((this.mode == 'reader' || this.mode == 'omnireader' || this.mode == 'liberama.top')) {
|
||||
const search = window.location.search.substr(1);
|
||||
const s = search.split('url=');
|
||||
const url = s[1] || '';
|
||||
const q = utils.parseQuery(s[0] || '');
|
||||
if (url) {
|
||||
q.url = decodeURIComponent(url);
|
||||
}
|
||||
|
||||
window.history.replaceState({}, '', '/');
|
||||
this.$router.replace({ path: '/reader', query: q });
|
||||
//распознавание параметра url вида "?url=<link>" и редирект при необходимости
|
||||
if (!this.isReaderActive) {
|
||||
const s = search.split('url=');
|
||||
const url = s[1] || '';
|
||||
const q = utils.parseQuery(s[0] || '');
|
||||
if (url) {
|
||||
q.url = decodeURIComponent(url);
|
||||
}
|
||||
|
||||
window.history.replaceState({}, '', '/');
|
||||
this.$router.replace({ path: '/reader', query: q });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
535
client/components/ExternalLibs/ExternalLibs.vue
Normal file
@@ -0,0 +1,535 @@
|
||||
<template>
|
||||
<Window ref="window" @close="close">
|
||||
<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" v-model="rootLink" :options="rootLinkOptions"
|
||||
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" v-model="selectedLink" :options="selectedLinkOptions" style="width: 50px"
|
||||
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="onInputFocus">
|
||||
<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>
|
||||
|
||||
<iframe v-if="frameVisible" class="col fit" ref="frame" :src="frameSrc" frameborder="0"></iframe>
|
||||
|
||||
<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"
|
||||
placeholder="Ссылка для закладки" maxlength="2000" @focus="onInputFocus">
|
||||
</q-input>
|
||||
|
||||
<q-select class="q-mr-sm" v-model="defaultRootLink" :options="defaultRootLinkOptions" 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" outlined dense bg-color="white" v-model="bookmarkDesc"
|
||||
placeholder="Описание" style="width: 400px" maxlength="100" @focus="onInputFocus">
|
||||
</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>
|
||||
</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 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
|
||||
},
|
||||
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;
|
||||
|
||||
bookmarkLink = '';
|
||||
bookmarkDesc = '';
|
||||
defaultRootLink = '';
|
||||
|
||||
created() {
|
||||
this.$root.addKeyHook(this.keyHook);
|
||||
|
||||
//this.commit = this.$store.commit;
|
||||
//this.commit('reader/setLibs', rstore.libsDefaults);
|
||||
}
|
||||
|
||||
mounted() {
|
||||
(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;
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
onInputFocus(event) {
|
||||
if (event.target.select)
|
||||
event.target.select();
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
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 = '';
|
||||
}
|
||||
}
|
||||
|
||||
async okAddBookmark() {
|
||||
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;
|
||||
}
|
||||
|
||||
bookmarkSettings() {
|
||||
}
|
||||
|
||||
fullScreenToggle() {
|
||||
this.fullScreenActive = !this.fullScreenActive;
|
||||
if (this.fullScreenActive) {
|
||||
this.$q.fullscreen.request();
|
||||
} else {
|
||||
this.$q.fullscreen.exit();
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
this.sendMessage({type: 'close'});
|
||||
}
|
||||
|
||||
keyHook() {
|
||||
if (this.$root.rootRoute() == '/external-libs') {
|
||||
if (this.$refs.dialogAddBookmark.active)
|
||||
return false;
|
||||
|
||||
//недостатки сторонних ui
|
||||
const input = this.$refs.input.$refs.input;
|
||||
if (document.activeElement === input && event.type == 'keydown' && event.key == 'Enter') {
|
||||
this.submitUrl();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event.type == 'keydown' && event.key == 'Escape') {
|
||||
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;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -95,7 +95,7 @@ class CopyTextPage extends Vue {
|
||||
}
|
||||
|
||||
keyHook(event) {
|
||||
if (event.type == 'keydown' && (event.code == 'Escape')) {
|
||||
if (event.type == 'keydown' && event.key == 'Escape') {
|
||||
this.close();
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -22,15 +22,15 @@
|
||||
на файл из онлайн-библиотеки (например, скопировав адрес ссылки или кнопки "скачать fb2").</p>
|
||||
<p>Поддерживаемые форматы: <b>fb2, fb2.zip, html, txt</b> и другие.</p>
|
||||
|
||||
<div v-show="mode == 'omnireader'">
|
||||
<div v-show="mode == 'omnireader' || mode == 'liberama.top'">
|
||||
<p>Вы можете добавить в свой браузер закладку, указав в ее свойствах вместо адреса следующий код:
|
||||
<br><strong>javascript:location.href='https://omnireader.ru/?url='+location.href;</strong>
|
||||
<q-icon class="copy-icon" name="la la-copy" @click="copyText('javascript:location.href=\'https://omnireader.ru/?url=\'+location.href;', 'Код для адреса закладки успешно скопирован в буфер обмена')">
|
||||
<br><strong>{{ bookmarkText }}</strong>
|
||||
<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-icon>
|
||||
|
||||
<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>В Chrome для Android можно вызывать такую закладку по имени прямо в адресной строке браузера (имя стоит сделать попроще).
|
||||
</p>
|
||||
@@ -56,6 +56,10 @@ class CommonHelpPage extends Vue {
|
||||
return this.$store.state.config.mode;
|
||||
}
|
||||
|
||||
get bookmarkText() {
|
||||
return `javascript:location.href='https://${window.location.host}/?url='+location.href;`
|
||||
}
|
||||
|
||||
async copyText(text, mes) {
|
||||
const result = await copyTextToClipboard(text);
|
||||
const msg = (result ? mes : 'Копирование не удалось');
|
||||
|
||||
@@ -81,7 +81,7 @@ class HelpPage extends Vue {
|
||||
}
|
||||
|
||||
keyHook(event) {
|
||||
if (event.type == 'keydown' && (event.code == 'Escape')) {
|
||||
if (event.type == 'keydown' && event.key == 'Escape') {
|
||||
this.close();
|
||||
}
|
||||
return true;
|
||||
|
||||
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>
|
||||
@@ -31,7 +31,7 @@
|
||||
</q-btn>
|
||||
|
||||
<div class="q-my-md"></div>
|
||||
<div v-if="mode == 'omnireader'">
|
||||
<!--div v-if="mode == 'omnireader'">
|
||||
<div ref="yaShare2" class="ya-share2"
|
||||
data-services="collections,vkontakte,facebook,odnoklassniki,twitter,telegram"
|
||||
data-description="Чтение fb2-книг онлайн. Загрузка любой страницы интернета одним кликом, синхронизация между устройствами, удобное управление, регистрация не требуется."
|
||||
@@ -39,7 +39,7 @@
|
||||
data-url="https://omnireader.ru">
|
||||
</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="openOldVersion">Старая версия</span>
|
||||
</div>
|
||||
@@ -82,8 +82,8 @@ class LoaderPage extends Vue {
|
||||
|
||||
mounted() {
|
||||
this.progress = this.$refs.progress;
|
||||
if (this.mode == 'omnireader')
|
||||
Ya.share2(this.$refs.yaShare2);// eslint-disable-line no-undef
|
||||
/*if (this.mode == 'omnireader')
|
||||
Ya.share2(this.$refs.yaShare2);// eslint-disable-line no-undef*/
|
||||
}
|
||||
|
||||
activated() {
|
||||
@@ -93,6 +93,8 @@ class LoaderPage extends Vue {
|
||||
get title() {
|
||||
if (this.mode == 'omnireader')
|
||||
return 'Omni Reader - браузерная онлайн-читалка.';
|
||||
if (this.mode == 'liberama.top')
|
||||
return 'Liberama Reader - браузерная онлайн-читалка.';
|
||||
return 'Универсальная читалка книг и ресурсов интернета.';
|
||||
|
||||
}
|
||||
@@ -171,7 +173,7 @@ class LoaderPage extends Vue {
|
||||
|
||||
//недостатки сторонних ui
|
||||
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();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ class PasteTextPage extends Vue {
|
||||
|
||||
keyHook(event) {
|
||||
if (event.type == 'keydown') {
|
||||
switch (event.code) {
|
||||
switch (event.key) {
|
||||
case 'F2':
|
||||
this.loadBuffer();
|
||||
break;
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
<div class="column no-wrap">
|
||||
<div ref="header" class="header" v-show="toolBarActive">
|
||||
<div ref="buttons" class="row justify-between no-wrap">
|
||||
<button ref="loader" class="tool-button" :class="buttonActiveClass('loader')" @click="buttonClick('loader')" v-ripple>
|
||||
<q-icon name="la la-arrow-left" size="32px"/>
|
||||
<q-tooltip :delay="1500" anchor="bottom right" content-style="font-size: 80%">{{ rstore.readerActions['loader'] }}</q-tooltip>
|
||||
</button>
|
||||
<div>
|
||||
<button ref="loader" class="tool-button" :class="buttonActiveClass('loader')" @click="buttonClick('loader')" v-ripple>
|
||||
<q-icon name="la la-arrow-left" size="32px"/>
|
||||
<q-tooltip :delay="1500" anchor="bottom right" content-style="font-size: 80%">{{ rstore.readerActions['loader'] }}</q-tooltip>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button ref="undoAction" v-show="showToolButton['undoAction']" class="tool-button" :class="buttonActiveClass('undoAction')" @click="buttonClick('undoAction')" v-ripple>
|
||||
@@ -42,9 +44,9 @@
|
||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['refresh'] }}</q-tooltip>
|
||||
</button>
|
||||
<div class="space"></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 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-sitemap" size="32px"/>
|
||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['libs'] }}</q-tooltip>
|
||||
</button>
|
||||
<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"/>
|
||||
@@ -52,10 +54,16 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button ref="settings" class="tool-button" :class="buttonActiveClass('settings')" @click="buttonClick('settings')" v-ripple>
|
||||
<q-icon name="la la-cog" size="32px"/>
|
||||
<q-tooltip :delay="1500" anchor="bottom left" content-style="font-size: 80%">{{ rstore.readerActions['settings'] }}</q-tooltip>
|
||||
</button>
|
||||
<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>
|
||||
<q-icon name="la la-cog" size="32px"/>
|
||||
<q-tooltip :delay="1500" anchor="bottom left" content-style="font-size: 80%">{{ rstore.readerActions['settings'] }}</q-tooltip>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -77,6 +85,7 @@
|
||||
@stop-text-search="stopTextSearch">
|
||||
</SearchPage>
|
||||
<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>
|
||||
<SettingsPage v-show="settingsActive" ref="settingsPage" @do-action="doAction"></SettingsPage>
|
||||
<HelpPage v-if="helpActive" ref="helpPage" @do-action="doAction"></HelpPage>
|
||||
@@ -159,6 +168,7 @@ import ProgressPage from './ProgressPage/ProgressPage.vue';
|
||||
import SetPositionPage from './SetPositionPage/SetPositionPage.vue';
|
||||
import SearchPage from './SearchPage/SearchPage.vue';
|
||||
import CopyTextPage from './CopyTextPage/CopyTextPage.vue';
|
||||
import LibsPage from './LibsPage/LibsPage.vue';
|
||||
import RecentBooksPage from './RecentBooksPage/RecentBooksPage.vue';
|
||||
import SettingsPage from './SettingsPage/SettingsPage.vue';
|
||||
import HelpPage from './HelpPage/HelpPage.vue';
|
||||
@@ -181,6 +191,7 @@ export default @Component({
|
||||
SetPositionPage,
|
||||
SearchPage,
|
||||
CopyTextPage,
|
||||
LibsPage,
|
||||
RecentBooksPage,
|
||||
SettingsPage,
|
||||
HelpPage,
|
||||
@@ -230,6 +241,7 @@ export default @Component({
|
||||
class Reader extends Vue {
|
||||
rstore = {};
|
||||
loaderActive = false;
|
||||
offlineModeActive = false;
|
||||
progressActive = false;
|
||||
fullScreenActive = false;
|
||||
|
||||
@@ -237,8 +249,8 @@ class Reader extends Vue {
|
||||
setPositionActive = false;
|
||||
searchActive = false;
|
||||
copyTextActive = false;
|
||||
libsActive = false;
|
||||
recentBooksActive = false;
|
||||
offlineModeActive = false;
|
||||
settingsActive = false;
|
||||
helpActive = false;
|
||||
clickMapActive = false;
|
||||
@@ -410,7 +422,7 @@ class Reader extends Vue {
|
||||
await utils.sleep(3000);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -587,7 +599,7 @@ class Reader extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
closeAllTextPages() {
|
||||
closeAllWindows() {
|
||||
this.setPositionActive = false;
|
||||
this.copyTextActive = false;
|
||||
this.recentBooksActive = false;
|
||||
@@ -600,7 +612,7 @@ class Reader extends Vue {
|
||||
loaderToggle() {
|
||||
this.loaderActive = !this.loaderActive;
|
||||
if (this.loaderActive) {
|
||||
this.closeAllTextPages();
|
||||
this.closeAllWindows();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -608,7 +620,7 @@ class Reader extends Vue {
|
||||
this.setPositionActive = !this.setPositionActive;
|
||||
const page = this.$refs.page;
|
||||
if (this.setPositionActive && this.activePage == 'TextPage' && page.parsed) {
|
||||
this.closeAllTextPages();
|
||||
this.closeAllWindows();
|
||||
this.setPositionActive = true;
|
||||
|
||||
this.$nextTick(() => {
|
||||
@@ -660,7 +672,7 @@ class Reader extends Vue {
|
||||
this.searchActive = !this.searchActive;
|
||||
const page = this.$refs.page;
|
||||
if (this.searchActive && this.activePage == 'TextPage' && page.parsed) {
|
||||
this.closeAllTextPages();
|
||||
this.closeAllWindows();
|
||||
this.searchActive = true;
|
||||
|
||||
this.$nextTick(() => {
|
||||
@@ -676,7 +688,7 @@ class Reader extends Vue {
|
||||
this.copyTextActive = !this.copyTextActive;
|
||||
const page = this.$refs.page;
|
||||
if (this.copyTextActive && this.activePage == 'TextPage' && page.parsed) {
|
||||
this.closeAllTextPages();
|
||||
this.closeAllWindows();
|
||||
this.copyTextActive = true;
|
||||
|
||||
this.$nextTick(() => {
|
||||
@@ -694,7 +706,7 @@ class Reader extends Vue {
|
||||
recentBooksToggle() {
|
||||
this.recentBooksActive = !this.recentBooksActive;
|
||||
if (this.recentBooksActive) {
|
||||
this.closeAllTextPages();
|
||||
this.closeAllWindows();
|
||||
this.$refs.recentBooksPage.init();
|
||||
this.recentBooksActive = true;
|
||||
} else {
|
||||
@@ -702,6 +714,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() {
|
||||
this.offlineModeActive = !this.offlineModeActive;
|
||||
this.$refs.serverStorage.offlineModeActive = this.offlineModeActive;
|
||||
@@ -710,7 +736,7 @@ class Reader extends Vue {
|
||||
settingsToggle() {
|
||||
this.settingsActive = !this.settingsActive;
|
||||
if (this.settingsActive) {
|
||||
this.closeAllTextPages();
|
||||
this.closeAllWindows();
|
||||
this.settingsActive = true;
|
||||
|
||||
this.$nextTick(() => {
|
||||
@@ -724,7 +750,7 @@ class Reader extends Vue {
|
||||
helpToggle() {
|
||||
this.helpActive = !this.helpActive;
|
||||
if (this.helpActive) {
|
||||
this.closeAllTextPages();
|
||||
this.closeAllWindows();
|
||||
this.helpActive = true;
|
||||
}
|
||||
}
|
||||
@@ -791,8 +817,9 @@ class Reader extends Vue {
|
||||
case 'search':
|
||||
case 'copyText':
|
||||
case 'refresh':
|
||||
case 'offlineMode':
|
||||
case 'libs':
|
||||
case 'recentBooks':
|
||||
case 'offlineMode':
|
||||
case 'settings':
|
||||
if (this.progressActive) {
|
||||
classResult = classDisabled;
|
||||
@@ -896,7 +923,7 @@ class Reader extends Vue {
|
||||
return;
|
||||
}
|
||||
|
||||
this.closeAllTextPages();
|
||||
this.closeAllWindows();
|
||||
|
||||
let url = encodeURI(decodeURI(opts.url));
|
||||
|
||||
@@ -1071,9 +1098,6 @@ class Reader extends Vue {
|
||||
case 'help':
|
||||
this.helpToggle();
|
||||
break;
|
||||
case 'settings':
|
||||
this.settingsToggle();
|
||||
break;
|
||||
case 'undoAction':
|
||||
this.undoAction();
|
||||
break;
|
||||
@@ -1101,12 +1125,18 @@ class Reader extends Vue {
|
||||
case 'refresh':
|
||||
this.refreshBook();
|
||||
break;
|
||||
case 'offlineMode':
|
||||
this.offlineModeToggle();
|
||||
case 'libs':
|
||||
this.libsToogle();
|
||||
break;
|
||||
case 'recentBooks':
|
||||
this.recentBooksToggle();
|
||||
break;
|
||||
case 'offlineMode':
|
||||
this.offlineModeToggle();
|
||||
break;
|
||||
case 'settings':
|
||||
this.settingsToggle();
|
||||
break;
|
||||
case 'switchToolbar':
|
||||
this.toolBarToggle();
|
||||
break;
|
||||
@@ -1173,29 +1203,28 @@ class Reader extends Vue {
|
||||
if (this.$root.stdDialog.active || this.$refs.dialog1.active || this.$refs.dialog2.active)
|
||||
return result;
|
||||
|
||||
let handled = false;
|
||||
if (!handled && this.helpActive)
|
||||
handled = this.$refs.helpPage.keyHook(event);
|
||||
if (!result && this.helpActive)
|
||||
result = this.$refs.helpPage.keyHook(event);
|
||||
|
||||
if (!handled && this.settingsActive)
|
||||
handled = this.$refs.settingsPage.keyHook(event);
|
||||
if (!result && this.settingsActive)
|
||||
result = this.$refs.settingsPage.keyHook(event);
|
||||
|
||||
if (!handled && this.recentBooksActive)
|
||||
handled = this.$refs.recentBooksPage.keyHook(event);
|
||||
if (!result && this.recentBooksActive)
|
||||
result = this.$refs.recentBooksPage.keyHook(event);
|
||||
|
||||
if (!handled && this.setPositionActive)
|
||||
handled = this.$refs.setPositionPage.keyHook(event);
|
||||
if (!result && this.setPositionActive)
|
||||
result = this.$refs.setPositionPage.keyHook(event);
|
||||
|
||||
if (!handled && this.searchActive)
|
||||
handled = this.$refs.searchPage.keyHook(event);
|
||||
if (!result && this.searchActive)
|
||||
result = this.$refs.searchPage.keyHook(event);
|
||||
|
||||
if (!handled && this.copyTextActive)
|
||||
handled = this.$refs.copyTextPage.keyHook(event);
|
||||
if (!result && this.copyTextActive)
|
||||
result = this.$refs.copyTextPage.keyHook(event);
|
||||
|
||||
if (!handled && this.$refs.page && this.$refs.page.keyHook)
|
||||
handled = this.$refs.page.keyHook(event);
|
||||
if (!result && this.$refs.page && this.$refs.page.keyHook)
|
||||
result = this.$refs.page.keyHook(event);
|
||||
|
||||
if (!handled && event.type == 'keydown') {
|
||||
if (!result && event.type == 'keydown') {
|
||||
const action = this.$root.readerActionByKeyEvent(event);
|
||||
|
||||
if (action == 'loader') {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<span v-if="loading"><q-spinner class="q-mr-sm" color="lime-12" size="20px" :thickness="7"/>Список загружается</span>
|
||||
</template>
|
||||
|
||||
<a ref="download" style='display: none;'></a>
|
||||
<a ref="download" style='display: none;' target="_blank"></a>
|
||||
|
||||
<q-table
|
||||
class="recent-books-table col"
|
||||
@@ -298,6 +298,7 @@ class RecentBooksPage extends Vue {
|
||||
const d = this.$refs.download;
|
||||
d.href = fb2path;
|
||||
d.download = path.basename(fb2path).substr(0, 10) + '.fb2';
|
||||
|
||||
d.click();
|
||||
} catch (e) {
|
||||
let errMes = e.message;
|
||||
@@ -340,7 +341,7 @@ class RecentBooksPage extends Vue {
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -174,7 +174,7 @@ class SearchPage extends Vue {
|
||||
}
|
||||
|
||||
keyHook(event) {
|
||||
if (event.type == 'keydown' && (event.code == 'Escape')) {
|
||||
if (event.type == 'keydown' && event.key == 'Escape') {
|
||||
this.close();
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div></div>
|
||||
<div class="hidden"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -35,6 +35,9 @@ export default @Component({
|
||||
currentProfile: function() {
|
||||
this.currentProfileChanged(true);
|
||||
},
|
||||
libs: function() {
|
||||
this.debouncedSaveLibs();
|
||||
},
|
||||
},
|
||||
})
|
||||
class ServerStorage extends Vue {
|
||||
@@ -49,12 +52,17 @@ class ServerStorage extends Vue {
|
||||
this.saveSettings();
|
||||
}, 500);
|
||||
|
||||
this.debouncedSaveLibs = _.debounce(() => {
|
||||
this.saveLibs();
|
||||
}, 500);
|
||||
|
||||
this.debouncedNotifySuccess = _.debounce(() => {
|
||||
this.success('Данные синхронизированы с сервером');
|
||||
}, 1000);
|
||||
|
||||
this.oldProfiles = {};
|
||||
this.oldSettings = {};
|
||||
this.oldLibs = {};
|
||||
}
|
||||
|
||||
async init() {
|
||||
@@ -124,6 +132,8 @@ class ServerStorage extends Vue {
|
||||
await this.loadProfiles(force);
|
||||
this.checkCurrentProfile();
|
||||
await this.currentProfileChanged(force);
|
||||
await this.loadLibs(force);
|
||||
|
||||
const loadSuccess = await this.loadRecent();
|
||||
if (loadSuccess && force)
|
||||
await this.saveRecent();
|
||||
@@ -169,6 +179,14 @@ class ServerStorage extends Vue {
|
||||
return this.settings.showServerStorageMessages;
|
||||
}
|
||||
|
||||
get libs() {
|
||||
return this.$store.state.reader.libs;
|
||||
}
|
||||
|
||||
get libsRev() {
|
||||
return this.$store.state.reader.libsRev;
|
||||
}
|
||||
|
||||
checkCurrentProfile() {
|
||||
if (!this.profiles[this.currentProfile]) {
|
||||
this.commit('reader/setCurrentProfile', '');
|
||||
@@ -338,13 +356,85 @@ class ServerStorage extends Vue {
|
||||
this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
|
||||
} else if (result.state == 'success') {
|
||||
this.oldProfiles = _.cloneDeep(this.profiles);
|
||||
this.commit('reader/setProfilesRev', this.profilesRev + 1);
|
||||
this.commit('reader/setProfilesRev', this.profilesRev + 1);
|
||||
}
|
||||
} finally {
|
||||
this.savingProfiles = false;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!this.keyInited || !this.serverSyncEnabled || this.loadingRecent)
|
||||
return;
|
||||
|
||||
@@ -60,7 +60,7 @@ class SetPositionPage extends Vue {
|
||||
keyHook(event) {
|
||||
if (event.type == 'keydown') {
|
||||
const action = this.$root.readerActionByKeyEvent(event);
|
||||
if (event.code == 'Escape' || action == 'setPosition') {
|
||||
if (event.key == 'Escape' || action == 'setPosition') {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -499,7 +499,7 @@ class SettingsPage extends Vue {
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<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="col row">
|
||||
<q-checkbox size="xs" @input="changeShowToolButton(item.name)" :value="showToolButton[item.name]" :label="rstore.readerActions[item.name]" />
|
||||
<q-checkbox size="xs" @input="changeShowToolButton(item.name)"
|
||||
:value="showToolButton[item.name]" :label="rstore.readerActions[item.name]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
<q-tooltip :delay="1000" anchor="top middle" self="center middle" content-style="font-size: 80%">Скопировать</q-tooltip>
|
||||
</q-icon>
|
||||
</div>
|
||||
<div v-if="mode == 'omnireader'">
|
||||
<div v-if="mode == 'omnireader' || mode == 'liberama.top'">
|
||||
<br>Переход по ссылке позволит автоматически ввести ключ доступа:
|
||||
<br><div class="text-center" style="margin-top: 5px">
|
||||
<a :href="setStorageKeyLink" target="_blank">Ссылка для ввода ключа</a>
|
||||
|
||||
@@ -839,7 +839,7 @@ class TextPage extends Vue {
|
||||
let i = this.pageLineCount;
|
||||
if (this.keepLastToFirst)
|
||||
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.pageChangeDirectionDown = true;
|
||||
this.bookPos = this.linesDown[i].begin;
|
||||
|
||||
@@ -59,7 +59,6 @@ export default class BookParser {
|
||||
offset: Number, //сумма всех length до этого параграфа
|
||||
length: Number, //длина text без тегов
|
||||
text: String, //текст параграфа с вложенными тегами
|
||||
cut: Boolean, //параграф - кандидат на сокрытие (cutEmptyParagraphs)
|
||||
addIndex: Number, //индекс добавляемого пустого параграфа (addEmptyParagraphs)
|
||||
}
|
||||
*/
|
||||
@@ -116,7 +115,6 @@ export default class BookParser {
|
||||
offset: paraOffset,
|
||||
length: len,
|
||||
text: text,
|
||||
cut: (!addIndex && (len == 1 && text[0] == ' ')),
|
||||
addIndex: (addIndex ? addIndex : 0),
|
||||
};
|
||||
|
||||
@@ -132,10 +130,10 @@ export default class BookParser {
|
||||
}
|
||||
|
||||
let p = para[paraIndex];
|
||||
//добавление пустых (addEmptyParagraphs) параграфов
|
||||
paraOffset -= p.length;
|
||||
//добавление пустых (addEmptyParagraphs) параграфов перед текущим
|
||||
if (p.length == 1 && p.text[0] == ' ' && len > 0) {
|
||||
paraIndex--;
|
||||
paraOffset -= p.length;
|
||||
for (let i = 0; i < 2; i++) {
|
||||
newParagraph(' ', 1, i + 1);
|
||||
}
|
||||
@@ -144,15 +142,10 @@ export default class BookParser {
|
||||
p.index = paraIndex;
|
||||
p.offset = paraOffset;
|
||||
para[paraIndex] = p;
|
||||
paraOffset += p.length;
|
||||
}
|
||||
|
||||
paraOffset -= p.length;
|
||||
//параграф оказался непустой
|
||||
if (p.length == 1 && p.text[0] == ' ' && len > 0) {
|
||||
//уберем начальный пробел
|
||||
p.length = 0;
|
||||
p.text = p.text.substr(1);
|
||||
p.cut = (len == 1 && text[0] == ' ');
|
||||
}
|
||||
|
||||
p.length += len;
|
||||
@@ -633,10 +626,7 @@ export default class BookParser {
|
||||
imageHeightLines: this.imageHeightLines,
|
||||
imageFitWidth: this.imageFitWidth,
|
||||
compactTextPerc: this.compactTextPerc,
|
||||
visible: !(
|
||||
(this.cutEmptyParagraphs && para.cut) ||
|
||||
(para.addIndex > this.addEmptyParagraphs)
|
||||
)
|
||||
visible: true, //вычисляется позже
|
||||
};
|
||||
|
||||
|
||||
@@ -652,9 +642,12 @@ export default class BookParser {
|
||||
text: String,
|
||||
}
|
||||
}*/
|
||||
|
||||
let parts = this.splitToStyle(para.text);
|
||||
|
||||
//инициализация парсера
|
||||
let line = {begin: para.offset, parts: []};
|
||||
let paragraphText = '';//текст параграфа
|
||||
let partText = '';//накапливаемый кусок со стилем
|
||||
|
||||
let str = '';//измеряемая строка
|
||||
@@ -663,14 +656,16 @@ export default class BookParser {
|
||||
let style = {};
|
||||
let ofs = 0;//смещение от начала параграфа para.offset
|
||||
let imgW = 0;
|
||||
let imageInPara = false;
|
||||
const compactWidth = this.measureText('W', {})*this.compactTextPerc/100;
|
||||
// тут начинается самый замес, перенос по слогам и стилизация, а также изображения
|
||||
for (const part of parts) {
|
||||
style = part.style;
|
||||
paragraphText += part.text;
|
||||
|
||||
//изображения
|
||||
if (part.image.id && !part.image.inline) {
|
||||
parsed.visible = this.showImages;
|
||||
imageInPara = true;
|
||||
let bin = this.binary[part.image.id];
|
||||
if (!bin)
|
||||
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;
|
||||
para.parsed = parsed;
|
||||
|
||||
|
||||
@@ -1,4 +1,28 @@
|
||||
export const versionHistory = [
|
||||
{
|
||||
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)',
|
||||
|
||||
@@ -19,11 +19,12 @@ class Notify extends Vue {
|
||||
iconColor = 'white',
|
||||
message = '',
|
||||
messageColor = 'black',
|
||||
position = 'top-right',
|
||||
} = opts;
|
||||
|
||||
caption = (caption ? `<div style="font-size: 120%; color: ${captionColor}"><b>${caption}</b></div><br>` : '');
|
||||
return this.$q.notify({
|
||||
position: 'top-right',
|
||||
position,
|
||||
color,
|
||||
textColor: iconColor,
|
||||
icon,
|
||||
@@ -38,20 +39,20 @@ class Notify extends Vue {
|
||||
});
|
||||
}
|
||||
|
||||
success(message, caption) {
|
||||
this.notify({color: 'positive', icon: 'la la-check-circle', message, caption});
|
||||
success(message, caption, options) {
|
||||
this.notify(Object.assign({color: 'positive', icon: 'la la-check-circle', message, caption}, options));
|
||||
}
|
||||
|
||||
warning(message, caption) {
|
||||
this.notify({color: 'warning', icon: 'la la-exclamation-circle', message, caption});
|
||||
warning(message, caption, options) {
|
||||
this.notify(Object.assign({color: 'warning', icon: 'la la-exclamation-circle', message, caption}, options));
|
||||
}
|
||||
|
||||
error(message, caption) {
|
||||
this.notify({color: 'negative', icon: 'la la-exclamation-circle', messageColor: 'yellow', captionColor: 'white', message, caption});
|
||||
error(message, caption, options) {
|
||||
this.notify(Object.assign({color: 'negative', icon: 'la la-exclamation-circle', messageColor: 'yellow', captionColor: 'white', message, caption}, options));
|
||||
}
|
||||
|
||||
info(message, caption) {
|
||||
this.notify({color: 'info', icon: 'la la-bell', message, caption});
|
||||
info(message, caption, options) {
|
||||
this.notify(Object.assign({color: 'info', icon: 'la la-bell', message, caption}, options));
|
||||
}
|
||||
}
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
@@ -277,21 +277,30 @@ class StdDialog extends Vue {
|
||||
|
||||
keyHook(event) {
|
||||
if (this.active) {
|
||||
let handled = false;
|
||||
if (this.type == 'hotKey') {
|
||||
if (event.type == 'keydown') {
|
||||
this.hotKeyCode = utils.keyEventToCode(event);
|
||||
handled = true;
|
||||
}
|
||||
} else {
|
||||
if (event.code == 'Enter')
|
||||
if (event.key == 'Enter') {
|
||||
this.okClick();
|
||||
if (event.code == 'Escape') {
|
||||
handled = true;
|
||||
}
|
||||
|
||||
if (event.key == 'Escape') {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.dialog.hide();
|
||||
});
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
if (handled) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<div ref="header" class="header row justify-end" @mousedown.prevent.stop="onMouseDown"
|
||||
@touchstart.stop="onTouchStart" @touchend.stop="onTouchEnd" @touchmove.stop="onTouchMove">
|
||||
<span class="header-text col"><slot name="header"></slot></span>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -10,6 +10,5 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="https://yastatic.net/share2/share.js" async="async"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -2,42 +2,41 @@ import Vue from 'vue';
|
||||
import VueRouter from 'vue-router';
|
||||
import _ from 'lodash';
|
||||
|
||||
//немедленная загрузка
|
||||
import CardIndex from './components/CardIndex/CardIndex.vue';
|
||||
//const CardIndex = () => import('./components/CardIndex/CardIndex.vue');
|
||||
|
||||
const CardIndex = () => import('./components/CardIndex/CardIndex.vue');
|
||||
const Search = () => import('./components/CardIndex/Search/Search.vue');
|
||||
const Card = () => import('./components/CardIndex/Card/Card.vue');
|
||||
const Book = () => import('./components/CardIndex/Book/Book.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 Sources = () => import('./components/Sources/Sources.vue');
|
||||
const Settings = () => import('./components/Settings/Settings.vue');
|
||||
const Help = () => import('./components/Help/Help.vue');
|
||||
//const NotFound404 = () => import('./components/NotFound404/NotFound404.vue');
|
||||
const NotFound404 = () => import('./components/NotFound404/NotFound404.vue');
|
||||
|
||||
const myRoutes = [
|
||||
['/', null, null, '/cardindex'],
|
||||
['/cardindex', CardIndex ],
|
||||
['/cardindex~search', Search ],
|
||||
['/cardindex~card', Card ],
|
||||
['/cardindex~card/:authorId', Card ],
|
||||
['/cardindex~book', Book ],
|
||||
['/cardindex~book/:bookId', Book ],
|
||||
['/cardindex~history', History ],
|
||||
['/cardindex', CardIndex],
|
||||
['/cardindex~search', Search],
|
||||
['/cardindex~card', Card],
|
||||
['/cardindex~card/:authorId', Card],
|
||||
['/cardindex~book', Book],
|
||||
['/cardindex~book/:bookId', Book],
|
||||
['/cardindex~history', History],
|
||||
|
||||
['/reader', Reader ],
|
||||
['/income', Income ],
|
||||
['/sources', Sources ],
|
||||
['/settings', Settings ],
|
||||
['/help', Help ],
|
||||
['*', null, null, '/cardindex' ],
|
||||
['/reader', Reader],
|
||||
['/external-libs', ExternalLibs],
|
||||
['/income', Income],
|
||||
['/sources', Sources],
|
||||
['/settings', Settings],
|
||||
['/help', Help],
|
||||
['/404', NotFound404],
|
||||
['*', null, null, '/cardindex'],
|
||||
];
|
||||
|
||||
let routes = {};
|
||||
|
||||
@@ -12,6 +12,7 @@ const readerActions = {
|
||||
'copyText': 'Скопировать текст со страницы',
|
||||
'refresh': 'Принудительно обновить книгу',
|
||||
'offlineMode': 'Автономный режим (без интернета)',
|
||||
'libs': 'Библиотека',
|
||||
'recentBooks': 'Открыть недавние',
|
||||
'switchToolbar': 'Показать/скрыть панель управления',
|
||||
'donate': '',
|
||||
@@ -37,8 +38,9 @@ const toolButtons = [
|
||||
{name: 'search', show: true},
|
||||
{name: 'copyText', show: false},
|
||||
{name: 'refresh', show: true},
|
||||
{name: 'offlineMode', show: false},
|
||||
{name: 'libs', show: true},
|
||||
{name: 'recentBooks', show: true},
|
||||
{name: 'offlineMode', show: false},
|
||||
];
|
||||
|
||||
//readerActions[name]
|
||||
@@ -55,6 +57,7 @@ const hotKeys = [
|
||||
{name: 'copyText', codes: ['Ctrl+C']},
|
||||
{name: 'refresh', codes: ['R']},
|
||||
{name: 'offlineMode', codes: ['O']},
|
||||
{name: 'libs', codes: ['L']},
|
||||
{name: 'recentBooks', codes: ['X']},
|
||||
|
||||
{name: 'switchToolbar', codes: ['Tab', 'Q']},
|
||||
@@ -250,6 +253,29 @@ const settingDefaults = {
|
||||
userHotKeys: {},
|
||||
};
|
||||
|
||||
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: 'АЛЬДЕБАРАН | Электронная библиотека книг'},
|
||||
]},
|
||||
]
|
||||
};
|
||||
|
||||
for (const font of fonts)
|
||||
settingDefaults.fontShifts[font.name] = font.fontVertShift;
|
||||
for (const font of webFonts)
|
||||
@@ -272,6 +298,8 @@ const state = {
|
||||
currentProfile: '',
|
||||
settings: Object.assign({}, settingDefaults),
|
||||
settingsRev: {},
|
||||
libs: Object.assign({}, libsDefaults),
|
||||
libsRev: 0,
|
||||
};
|
||||
|
||||
// getters
|
||||
@@ -315,6 +343,12 @@ const mutations = {
|
||||
setSettingsRev(state, value) {
|
||||
state.settingsRev = Object.assign({}, state.settingsRev, value);
|
||||
},
|
||||
setLibs(state, value) {
|
||||
state.libs = value;
|
||||
},
|
||||
setLibsRev(state, value) {
|
||||
state.libsRev = value;
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
@@ -324,6 +358,7 @@ export default {
|
||||
fonts,
|
||||
webFonts,
|
||||
settingDefaults,
|
||||
libsDefaults,
|
||||
|
||||
namespaced: true,
|
||||
state,
|
||||
|
||||
@@ -30,7 +30,7 @@ server {
|
||||
root /home/beta.liberama/public;
|
||||
|
||||
location /tmp {
|
||||
add_header Content-Type text/xml;
|
||||
types { } default_type "application/xml; charset=utf-8";
|
||||
add_header Content-Encoding gzip;
|
||||
}
|
||||
|
||||
3
docs/beta.omnireader.ru/run_server.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
sudo -H -u www-data /home/beta.liberama/liberama
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
sudo -H -u www-data bash -c "\
|
||||
while true; do\
|
||||
trap '' 2;\
|
||||
cd /var/www;\
|
||||
/home/beta.liberama/liberama;\
|
||||
trap 2;\
|
||||
echo \"Restart after 5 sec. Press Ctrl+C to exit.\";\
|
||||
sleep 5;\
|
||||
done;"
|
||||
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
|
||||
Для своего домена необходимо будет подправить docs/omnireader/omnireader.
|
||||
Для своего домена необходимо будет подправить docs/omnireader.ru/omnireader.
|
||||
Можно также настроить сервер для HTTP, без SSL.
|
||||
```
|
||||
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 rm /etc/nginx/sites-enabled/default
|
||||
sudo service nginx reload
|
||||
@@ -59,14 +59,20 @@ sudo service php7.2-fpm restart
|
||||
|
||||
sudo mkdir /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/cron_server.sh
|
||||
```
|
||||
|
||||
## Деплой и запуск
|
||||
```
|
||||
cd docs/omnireader
|
||||
cd docs/omnireader.ru
|
||||
./stop_server.sh
|
||||
./deploy.sh
|
||||
./run_server.sh
|
||||
./start_server.sh
|
||||
```
|
||||
|
||||
После первого запуска будет создан конфигурационный файл `/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;
|
||||
|
||||
location /tmp {
|
||||
add_header Content-Type text/xml;
|
||||
types { } default_type "application/xml; charset=utf-8";
|
||||
add_header Content-Encoding gzip;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ server {
|
||||
root /home/liberama/public;
|
||||
|
||||
location /tmp {
|
||||
add_header Content-Type text/xml;
|
||||
types { } default_type "application/xml; charset=utf-8";
|
||||
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;"
|
||||
3155
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Liberama",
|
||||
"version": "0.9.2",
|
||||
"version": "0.9.4",
|
||||
"author": "Book Pauk <bookpauk@gmail.com>",
|
||||
"license": "CC0-1.0",
|
||||
"repository": "bookpauk/liberama",
|
||||
@@ -41,7 +41,6 @@
|
||||
"mini-css-extract-plugin": "^0.5.0",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||
"pkg": "^4.4.4",
|
||||
"sw-precache-webpack-plugin": "^1.0.0",
|
||||
"terser-webpack-plugin": "^1.4.1",
|
||||
"url-loader": "^1.1.2",
|
||||
"vue-class-component": "^6.3.2",
|
||||
@@ -52,7 +51,8 @@
|
||||
"webpack-cli": "^3.3.11",
|
||||
"webpack-dev-middleware": "^3.7.2",
|
||||
"webpack-hot-middleware": "^2.25.0",
|
||||
"webpack-merge": "^4.2.2"
|
||||
"webpack-merge": "^4.2.2",
|
||||
"workbox-webpack-plugin": "^5.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@quasar/extras": "^1.5.2",
|
||||
@@ -72,7 +72,7 @@
|
||||
"multer": "^1.4.2",
|
||||
"pako": "^1.0.11",
|
||||
"path-browserify": "^1.0.0",
|
||||
"quasar": "^1.9.7",
|
||||
"quasar": "^1.14.3",
|
||||
"safe-buffer": "^5.2.0",
|
||||
"sjcl": "^1.0.8",
|
||||
"sql-template-strings": "^2.2.2",
|
||||
|
||||
@@ -119,14 +119,19 @@ class FileDecompressor {
|
||||
try {
|
||||
return await zip.unpack(filename, outputDir, {
|
||||
limitFileSize: this.limitFileSize,
|
||||
limitFileCount: 1000
|
||||
});
|
||||
limitFileCount: 1000,
|
||||
decodeEntryNameCallback: (nameRaw) => {
|
||||
return utils.bufferRemoveZeroes(nameRaw);
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
fs.emptyDir(outputDir);
|
||||
return await zip.unpack(filename, outputDir, {
|
||||
limitFileSize: this.limitFileSize,
|
||||
limitFileCount: 1000,
|
||||
decodeEntryNameCallback: (nameRaw) => {
|
||||
nameRaw = utils.bufferRemoveZeroes(nameRaw);
|
||||
const enc = textUtils.getEncodingLite(nameRaw);
|
||||
if (enc.indexOf('ISO-8859') < 0) {
|
||||
return iconv.decode(nameRaw, enc);
|
||||
|
||||
@@ -653,15 +653,6 @@
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"type": "svg",
|
||||
"ext": "svg",
|
||||
"mime": "image/svg+xml",
|
||||
"rules": [
|
||||
{ "type": "contains", "bytes": "3c737667" }
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"type": "docx",
|
||||
"ext": "docx",
|
||||
@@ -683,7 +674,9 @@
|
||||
{ "type": "or", "rules":
|
||||
[
|
||||
{ "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" }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -698,6 +691,16 @@
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"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",
|
||||
@@ -725,6 +728,14 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"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
|
||||
@@ -766,7 +766,7 @@ ZipEntry.prototype.readDataHeader = function(data) {
|
||||
};
|
||||
|
||||
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();
|
||||
var lastChar = data[offset - 1];
|
||||
this.isDirectory = (lastChar == 47) || (lastChar == 92);
|
||||
|
||||
@@ -93,6 +93,12 @@ function parseSync(xstr, options) {
|
||||
}
|
||||
tag = tag.toLowerCase();
|
||||
|
||||
if (innerCut.has(tag) && (!cutCounter || cutTag === tag)) {
|
||||
if (!cutCounter)
|
||||
cutTag = tag;
|
||||
cutCounter++;
|
||||
}
|
||||
|
||||
let endTag = (singleTag ? tag : '');
|
||||
if (tag === '' || tag[0] !== '/') {
|
||||
_onStartNode(tag, tail, singleTag, cutCounter, cutTag);
|
||||
@@ -103,12 +109,6 @@ function parseSync(xstr, options) {
|
||||
if (endTag)
|
||||
_onEndNode(endTag, tail, singleTag, cutCounter, cutTag);
|
||||
|
||||
if (innerCut.has(tag) && (!cutCounter || cutTag === tag)) {
|
||||
if (!cutCounter)
|
||||
cutTag = tag;
|
||||
cutCounter++;
|
||||
}
|
||||
|
||||
if (cutTag === endTag) {
|
||||
cutCounter = (cutCounter > 0 ? cutCounter - 1 : 0);
|
||||
if (!cutCounter)
|
||||
@@ -125,9 +125,9 @@ function parseSync(xstr, options) {
|
||||
|
||||
if (i < len) {
|
||||
if (inCdata) {
|
||||
_onCdata(xstr.substr(leftData, len - leftData), cutCounter, cutTag);
|
||||
_onCdata(xstr.substr(leftData + 1, len - leftData - 1), cutCounter, cutTag);
|
||||
} else if (inComment) {
|
||||
_onComment(xstr.substr(leftData, len - leftData), cutCounter, cutTag);
|
||||
_onComment(xstr.substr(leftData + 1, len - leftData - 1), cutCounter, cutTag);
|
||||
} else {
|
||||
_onTextNode(xstr.substr(i, len - i), cutCounter, cutTag);
|
||||
}
|
||||
@@ -233,6 +233,12 @@ async function parse(xstr, options) {
|
||||
}
|
||||
tag = tag.toLowerCase();
|
||||
|
||||
if (innerCut.has(tag) && (!cutCounter || cutTag === tag)) {
|
||||
if (!cutCounter)
|
||||
cutTag = tag;
|
||||
cutCounter++;
|
||||
}
|
||||
|
||||
let endTag = (singleTag ? tag : '');
|
||||
if (tag === '' || tag[0] !== '/') {
|
||||
await _onStartNode(tag, tail, singleTag, cutCounter, cutTag);
|
||||
@@ -243,12 +249,6 @@ async function parse(xstr, options) {
|
||||
if (endTag)
|
||||
await _onEndNode(endTag, tail, singleTag, cutCounter, cutTag);
|
||||
|
||||
if (innerCut.has(tag) && (!cutCounter || cutTag === tag)) {
|
||||
if (!cutCounter)
|
||||
cutTag = tag;
|
||||
cutCounter++;
|
||||
}
|
||||
|
||||
if (cutTag === endTag) {
|
||||
cutCounter = (cutCounter > 0 ? cutCounter - 1 : 0);
|
||||
if (!cutCounter)
|
||||
@@ -265,9 +265,9 @@ async function parse(xstr, options) {
|
||||
|
||||
if (i < len) {
|
||||
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) {
|
||||
await _onComment(xstr.substr(leftData, len - leftData), cutCounter, cutTag);
|
||||
await _onComment(xstr.substr(leftData + 1, len - leftData - 1), cutCounter, cutTag);
|
||||
} else {
|
||||
await _onTextNode(xstr.substr(i, len - i), cutCounter, cutTag);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,14 @@ function fromBase36(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) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const hash = crypto.createHash(hashName);
|
||||
@@ -86,6 +94,7 @@ function spawnProcess(cmd, opts) {
|
||||
module.exports = {
|
||||
toBase36,
|
||||
fromBase36,
|
||||
bufferRemoveZeroes,
|
||||
getFileHash,
|
||||
sleep,
|
||||
randomHexString,
|
||||
|
||||
@@ -72,7 +72,7 @@ async function main() {
|
||||
maxAge: '30d',
|
||||
setHeaders: (res, filePath) => {
|
||||
if (path.basename(path.dirname(filePath)) == 'tmp') {
|
||||
res.set('Content-Type', 'text/xml');
|
||||
res.set('Content-Type', 'application/xml');
|
||||
res.set('Content-Encoding', 'gzip');
|
||||
}
|
||||
}
|
||||
|
||||