Эх сурвалжийг харах

Merge branch 'release/0.9.5'

Book Pauk 4 жил өмнө
parent
commit
e72ca0de7e

+ 116 - 18
client/components/ExternalLibs/ExternalLibs.vue

@@ -1,5 +1,5 @@
 <template>
-    <Window ref="window" @close="close">
+    <Window ref="window" @close="close" margin="2px">
         <template slot="header">
             {{ header }}
         </template>
@@ -13,7 +13,8 @@
 
         <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"
+                <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
@@ -30,13 +31,18 @@
                         <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"
+
+                <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="onInputFocus">
+
+                <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>
@@ -46,13 +52,17 @@
                         </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>
+            <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">
@@ -63,11 +73,11 @@
                 </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 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" v-model="defaultRootLink" :options="defaultRootLinkOptions" style="width: 50px"
+                    <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
                     >
@@ -76,8 +86,8 @@
                 </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 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>
 
@@ -137,6 +147,7 @@ class ExternalLibs extends Vue {
     libs = {};
     fullScreenActive = false;
     addBookmarkVisible = false;
+    transparentLayoutVisible = false;
 
     bookmarkLink = '';
     bookmarkDesc = '';
@@ -145,11 +156,38 @@ class ExternalLibs extends Vue {
     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;
@@ -320,8 +358,9 @@ class ExternalLibs extends Vue {
     }
 
     openBookUrlInFrame() {
-        if (this.bookUrl)
+        if (this.bookUrl) {
             this.goToLink(this.addProtocol(this.bookUrl));
+        }
     }
 
     goToLink(link) {
@@ -332,6 +371,9 @@ class ExternalLibs extends Vue {
         this.frameVisible = false;
         this.$nextTick(() => {
             this.frameVisible = true;
+            this.$nextTick(() => {
+                this.$refs.frame.contentWindow.focus();
+            });
         });
     }
 
@@ -392,11 +434,20 @@ class ExternalLibs extends Vue {
         return url;
     }
 
-    onInputFocus(event) {
+    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: {
@@ -415,6 +466,7 @@ class ExternalLibs extends Vue {
         this.addBookmarkVisible = true;
         this.$nextTick(() => {
             this.$refs.bookmarkLink.focus();
+            this.$refs.defaultRootLink.toggleOption = this.toggleOption;
         });
     }
 
@@ -429,7 +481,28 @@ class ExternalLibs extends Vue {
         }
     }
 
+    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 {
@@ -488,23 +561,43 @@ class ExternalLibs extends Vue {
         }
     }
 
+    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();
+        }
+    }
+
     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 == 'F4') {
+                this.addBookmark()
+                return;
             }
 
-            if (event.type == 'keydown' && event.key == 'Escape') {
+            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;
@@ -532,4 +625,9 @@ class ExternalLibs extends Vue {
     background-color: #69C05F;
 }
 
+.transparent-layout {
+    top: 0;
+    left: 0;
+    position: absolute;
+}
 </style>

+ 1 - 1
client/components/Reader/LoaderPage/LoaderPage.vue

@@ -1,6 +1,6 @@
 <template>
     <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>
         </div>
         <div class="col column justify-center items-center no-wrap overflow-hidden" style="min-height: 230px">

+ 26 - 4
client/components/Reader/Reader.vue

@@ -39,6 +39,10 @@
                         <q-icon name="la la-copy" size="32px"/>
                         <q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['copyText'] }}</q-tooltip>
                     </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 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" :class="{clear: !showRefreshIcon}"/>
                         <q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['refresh'] }}</q-tooltip>
@@ -699,6 +703,12 @@ class Reader extends Vue {
         }
     }
 
+    refreshBookSplitToPara() {
+        if (this.mostRecentBook()) {
+            this.loadBook({url: this.mostRecentBook().url, skipCheck: true, isText: true, force: true});
+        }
+    }
+
     recentBooksClose() {
         this.recentBooksActive = false;
     }
@@ -816,6 +826,7 @@ class Reader extends Vue {
             case 'scrolling':
             case 'search':
             case 'copyText':
+            case 'splitToPara':
             case 'refresh':
             case 'libs':
             case 'recentBooks':
@@ -847,8 +858,9 @@ class Reader extends Vue {
                 case 'copyText':
                     classResult = classDisabled;
                     break;
-                case 'recentBooks':
+                case 'splitToPara':
                 case 'refresh':
+                case 'recentBooks':
                     if (!this.mostRecentBookReactive)
                         classResult = classDisabled;
                     break;
@@ -1001,9 +1013,16 @@ class Reader extends Vue {
             // не удалось, скачиваем книгу полностью с конвертацией
             let loadCached = true;
             if (!book) {
-                book = await readerApi.loadBook({url, enableSitesFilter: this.enableSitesFilter}, (state) => {
-                    progress.setState(state);
-                });
+                book = await readerApi.loadBook({
+                        url,
+                        skipCheck: (opts.skipCheck ? true : false),
+                        isText: (opts.isText ? true : false),
+                        enableSitesFilter: this.enableSitesFilter
+                    },
+                    (state) => {
+                        progress.setState(state);
+                    }
+                );
                 loadCached = false;
             }
 
@@ -1122,6 +1141,9 @@ class Reader extends Vue {
             case 'copyText':
                 this.copyTextToggle();
                 break;
+            case 'splitToPara':
+                this.refreshBookSplitToPara();
+                break;
             case 'refresh':
                 this.refreshBook();
                 break;

+ 16 - 4
client/components/Reader/ServerStorage/ServerStorage.vue

@@ -69,15 +69,15 @@ class ServerStorage extends Vue {
         try {
             this.cachedRecent = await ssCacheStore.getItem('recent');
             if (!this.cachedRecent)
-                await this.setCachedRecent({rev: 0, data: {}});
+                await this.cleanCachedRecent('cachedRecent');
 
             this.cachedRecentPatch = await ssCacheStore.getItem('recent-patch');
             if (!this.cachedRecentPatch)
-                await this.setCachedRecentPatch({rev: 0, data: {}});
+                await this.cleanCachedRecent('cachedRecentPatch');
 
             this.cachedRecentMod = await ssCacheStore.getItem('recent-mod');
             if (!this.cachedRecentMod)
-                await this.setCachedRecentMod({rev: 0, data: {}});
+                await this.cleanCachedRecent('cachedRecentMod');
 
             if (!this.serverStorageKey) {
                 //генерируем новый ключ
@@ -105,6 +105,15 @@ class ServerStorage extends Vue {
         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() {
         const key = utils.toBase58(utils.randomArray(32));
         this.commit('reader/setServerStorageKey', key);
@@ -134,9 +143,12 @@ class ServerStorage extends Vue {
             await this.currentProfileChanged(force);
             await this.loadLibs(force);
 
+            if (force)
+                await this.cleanCachedRecent('all');
             const loadSuccess = await this.loadRecent();
-            if (loadSuccess && force)
+            if (loadSuccess && force) {
                 await this.saveRecent();
+            }
         }
     }
 

+ 12 - 0
client/components/Reader/versionHistory.js

@@ -1,4 +1,16 @@
 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)',

+ 5 - 1
client/components/share/Window.vue

@@ -1,7 +1,7 @@
 <template>
     <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 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"
                     @touchstart.stop="onTouchStart" @touchend.stop="onTouchEnd" @touchmove.stop="onTouchMove">
                     <span class="header-text col"><slot name="header"></slot></span>
@@ -26,6 +26,7 @@ export default @Component({
         width: { type: String, default: '100%' },
         maxWidth: { type: String, default: '' },
         topShift: { type: Number, default: 0 },
+        margin: '',
     }
 })
 class Window extends Vue {
@@ -40,6 +41,9 @@ class Window extends Vue {
             const top = (this.$refs.main.offsetHeight - this.$refs.windowBox.offsetHeight)/2 + this.topShift;
             this.$refs.windowBox.style.left = (left > 0 ? left : 0) + 'px';
             this.$refs.windowBox.style.top = (top > 0 ? top : 0) + 'px';
+
+            if (this.margin)
+                this.$refs.window.style.margin = this.margin;
         });
     }
 

+ 3 - 0
client/store/modules/reader.js

@@ -10,6 +10,7 @@ const readerActions = {
     'setPosition': 'Установить позицию',
     'search': 'Найти в тексте',
     'copyText': 'Скопировать текст со страницы',
+    'splitToPara': 'Обновить с разбиением на параграфы',
     'refresh': 'Принудительно обновить книгу',
     'offlineMode': 'Автономный режим (без интернета)',
     'libs': 'Библиотека',
@@ -37,6 +38,7 @@ const toolButtons = [
     {name: 'setPosition', show: true},
     {name: 'search',      show: true},
     {name: 'copyText',    show: false},
+    {name: 'splitToPara', show: false},
     {name: 'refresh',     show: true},
     {name: 'libs',        show: true},
     {name: 'recentBooks', show: true},
@@ -55,6 +57,7 @@ const hotKeys = [
     {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']},

+ 85 - 0
docs/beta/beta.liberama

@@ -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;
+    }
+  }
+}

+ 0 - 0
docs/beta.omnireader.ru/beta.omnireader → docs/beta/beta.omnireader


+ 0 - 0
docs/beta.omnireader.ru/deploy.sh → docs/beta/deploy.sh


+ 0 - 0
docs/beta.omnireader.ru/run_server.sh → docs/beta/run_server.sh


+ 1 - 1
docs/omnireader.ru/README.md

@@ -64,7 +64,7 @@ sudo -u www-data cp -r docs/omnireader.ru/old/* /home/oldreader
 
 ## Запуск по крону
 ```
-* * * * * /root/liberama/docs/omnireader/cron_server.sh
+* * * * * /root/liberama/docs/omnireader.ru/cron_server.sh
 ```
 
 ## Деплой и запуск

+ 1 - 1
package-lock.json

@@ -1,6 +1,6 @@
 {
   "name": "Liberama",
-  "version": "0.9.4",
+  "version": "0.9.5",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "Liberama",
-  "version": "0.9.4",
+  "version": "0.9.5",
   "author": "Book Pauk <bookpauk@gmail.com>",
   "license": "CC0-1.0",
   "repository": "bookpauk/liberama",

+ 3 - 1
server/controllers/ReaderController.js

@@ -19,7 +19,9 @@ class ReaderController extends BaseController {
                 throw new Error(`key 'url' is empty`);
             const workerId = this.readerWorker.loadBookUrl({
                 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);
             return (state ? state : {});

+ 1 - 1
server/core/Reader/BookConverter/ConvertHtml.js

@@ -200,7 +200,7 @@ class ConvertHtml extends ConvertBase {
 
         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 count = 1;
             for (let i = 0; i < spaceCounter.length; i++) {