Forráskód Böngészése

Merge branch 'release/0.6.7'

Book Pauk 6 éve
szülő
commit
f59974e310

+ 2 - 2
client/api/reader.js

@@ -5,11 +5,11 @@ import {Buffer} from 'safe-buffer';
 import * as utils from '../share/utils';
 import * as utils from '../share/utils';
 
 
 const api = axios.create({
 const api = axios.create({
-  baseURL: '/api/reader'
+    baseURL: '/api/reader'
 });
 });
 
 
 const workerApi = axios.create({
 const workerApi = axios.create({
-  baseURL: '/api/worker'
+    baseURL: '/api/worker'
 });
 });
 
 
 class Reader {
 class Reader {

+ 2 - 3
client/components/Reader/HelpPage/DonateHelpPage/DonateHelpPage.vue

@@ -53,11 +53,10 @@ class DonateHelpPage extends Vue {
 
 
     async copyAddress(address, prefix) {
     async copyAddress(address, prefix) {
         const result = await copyTextToClipboard(address);
         const result = await copyTextToClipboard(address);
-        const msg = (result ? `${prefix}-адрес ${address} успешно скопирован в буфер обмена` : 'Копирование не удалось');
         if (result)
         if (result)
-            this.$notify.success({message: msg});
+            this.$notify.success({message: `${prefix}-адрес ${address} успешно скопирован в буфер обмена`});
         else
         else
-            this.$notify.error({message: msg});
+            this.$notify.error({message: 'Копирование не удалось'});
     }
     }
 }
 }
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------

+ 9 - 0
client/components/Reader/HelpPage/HelpPage.vue

@@ -16,6 +16,9 @@
                     <el-tab-pane label="Мышь/тачпад">
                     <el-tab-pane label="Мышь/тачпад">
                         <MouseHelpPage></MouseHelpPage>
                         <MouseHelpPage></MouseHelpPage>
                     </el-tab-pane>
                     </el-tab-pane>
+                    <el-tab-pane label="История версий" name="releases">
+                        <VersionHistoryPage></VersionHistoryPage>
+                    </el-tab-pane>
                     <el-tab-pane label="Помочь проекту" name="donate">
                     <el-tab-pane label="Помочь проекту" name="donate">
                         <DonateHelpPage></DonateHelpPage>
                         <DonateHelpPage></DonateHelpPage>
                     </el-tab-pane>
                     </el-tab-pane>
@@ -36,6 +39,7 @@ import CommonHelpPage from './CommonHelpPage/CommonHelpPage.vue';
 import HotkeysHelpPage from './HotkeysHelpPage/HotkeysHelpPage.vue';
 import HotkeysHelpPage from './HotkeysHelpPage/HotkeysHelpPage.vue';
 import MouseHelpPage from './MouseHelpPage/MouseHelpPage.vue';
 import MouseHelpPage from './MouseHelpPage/MouseHelpPage.vue';
 import DonateHelpPage from './DonateHelpPage/DonateHelpPage.vue';
 import DonateHelpPage from './DonateHelpPage/DonateHelpPage.vue';
+import VersionHistoryPage from './VersionHistoryPage/VersionHistoryPage.vue';
 
 
 export default @Component({
 export default @Component({
     components: {
     components: {
@@ -44,6 +48,7 @@ export default @Component({
         HotkeysHelpPage,
         HotkeysHelpPage,
         MouseHelpPage,
         MouseHelpPage,
         DonateHelpPage,
         DonateHelpPage,
+        VersionHistoryPage,
     },
     },
 })
 })
 class HelpPage extends Vue {
 class HelpPage extends Vue {
@@ -57,6 +62,10 @@ class HelpPage extends Vue {
         this.selectedTab = 'donate';
         this.selectedTab = 'donate';
     }
     }
 
 
+    activateVersionHistoryHelpPage() {
+        this.selectedTab = 'releases';
+    }
+
     keyHook(event) {
     keyHook(event) {
         if (event.type == 'keydown' && (event.code == 'Escape')) {
         if (event.type == 'keydown' && (event.code == 'Escape')) {
             this.close();
             this.close();

+ 81 - 0
client/components/Reader/HelpPage/VersionHistoryPage/VersionHistoryPage.vue

@@ -0,0 +1,81 @@
+<template>
+    <div id="versionHistoryPage" class="page">
+        <span class="clickable" v-for="(item, index) in versionHeader" :key="index" @click="showRelease(item)">
+            <p>
+            {{ item }}
+            </p>
+        </span>
+
+        <br>
+        <h4>История версий:</h4>
+        <br>
+
+        <div v-for="item in versionContent" :id="item.key" :key="item.key">
+            <span v-html="item.content"></span>
+            <br>
+        </div>
+    </div>
+</template>
+
+<script>
+//-----------------------------------------------------------------------------
+import Vue from 'vue';
+import Component from 'vue-class-component';
+import {versionHistory} from '../../versionHistory';
+
+export default @Component({
+})
+class VersionHistoryPage extends Vue {
+    versionHeader = [];
+    versionContent = [];
+
+    created() {
+    }
+
+    mounted() {
+        let vh = [];
+        for (const version of versionHistory) {
+            vh.push(version.header);
+        }
+        this.versionHeader = vh;
+
+        let vc = [];
+        for (const version of versionHistory) {
+            vc.push({key: version.header, content: 'Версия ' + version.header + version.content});
+        }
+        this.versionContent = vc;
+    }
+
+    showRelease(id) {
+        let el = document.getElementById(id);
+        if (el) {
+            document.getElementById('versionHistoryPage').scrollTop = el.offsetTop;
+        }
+    }
+}
+//-----------------------------------------------------------------------------
+</script>
+
+<style scoped>
+.page {
+    flex: 1;
+    padding: 15px;
+    overflow-y: auto;
+    font-size: 120%;
+    line-height: 130%;
+}
+
+h4 {
+    margin: 0;
+}
+
+p {
+    line-height: 15px;
+}
+
+.clickable {
+    color: blue;
+    text-decoration: underline;
+    cursor: pointer;
+}
+</style>

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

@@ -18,6 +18,10 @@
                 Загрузить файл с диска
                 Загрузить файл с диска
             </el-button>
             </el-button>
             <div class="space"></div>
             <div class="space"></div>
+            <el-button size="mini" @click="loadBufferClick">
+                Из буфера обмена
+            </el-button>
+            <div class="space"></div>
             <span v-if="mode == 'omnireader'" class="bottom-span clickable" @click="openComments">Комментарии</span>
             <span v-if="mode == 'omnireader'" class="bottom-span clickable" @click="openComments">Комментарии</span>
         </div>
         </div>
 
 
@@ -26,6 +30,8 @@
             <span class="bottom-span clickable" @click="openDonate">Помочь проекту</span>
             <span class="bottom-span clickable" @click="openDonate">Помочь проекту</span>
             <span class="bottom-span">{{ version }}</span>
             <span class="bottom-span">{{ version }}</span>
         </div>
         </div>
+
+        <PasteTextPage v-if="pasteTextActive" ref="pasteTextPage" @paste-text-toggle="pasteTextToggle" @load-buffer="loadBuffer"></PasteTextPage>
     </div>
     </div>
 </template>
 </template>
 
 
@@ -33,12 +39,17 @@
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 import Vue from 'vue';
 import Vue from 'vue';
 import Component from 'vue-class-component';
 import Component from 'vue-class-component';
+import PasteTextPage from './PasteTextPage/PasteTextPage.vue';
 
 
 export default @Component({
 export default @Component({
+    components: {
+        PasteTextPage,
+    },
 })
 })
 class LoaderPage extends Vue {
 class LoaderPage extends Vue {
     bookUrl = null;
     bookUrl = null;
     loadPercent = 0;
     loadPercent = 0;
+    pasteTextActive = false;
 
 
     created() {
     created() {
         this.commit = this.$store.commit;
         this.commit = this.$store.commit;
@@ -83,12 +94,27 @@ class LoaderPage extends Vue {
     }
     }
 
 
     loadFile() {
     loadFile() {
-        const file = this.$refs.file.files[0];
+        const file = this.$refs.file.files[0];        
         this.$refs.file.value = '';
         this.$refs.file.value = '';
         if (file)
         if (file)
             this.$emit('load-file', {file});
             this.$emit('load-file', {file});
     }
     }
 
 
+    loadBufferClick() {
+        this.pasteTextToggle();
+    }
+
+    loadBuffer(opts) {
+        if (opts.buffer.length) {
+            const file = new File([opts.buffer], 'dummyName-PasteFromClipboard');
+            this.$emit('load-file', {file});
+        }
+    }
+
+    pasteTextToggle() {
+        this.pasteTextActive = !this.pasteTextActive;
+    }
+
     openHelp() {
     openHelp() {
         this.$emit('help-toggle');
         this.$emit('help-toggle');
     }
     }
@@ -102,6 +128,10 @@ class LoaderPage extends Vue {
     }
     }
 
 
     keyHook(event) {
     keyHook(event) {
+        if (this.pasteTextActive) {
+            return this.$refs.pasteTextPage.keyHook(event);
+        }
+
         //недостатки сторонних ui
         //недостатки сторонних ui
         const input = this.$refs.input.$refs.input;
         const input = this.$refs.input.$refs.input;
         if (document.activeElement === input && event.type == 'keydown' && event.code == 'Enter') {
         if (document.activeElement === input && event.type == 'keydown' && event.code == 'Enter') {

+ 133 - 0
client/components/Reader/LoaderPage/PasteTextPage/PasteTextPage.vue

@@ -0,0 +1,133 @@
+<template>
+    <div ref="main" class="main" @click="close">
+        <div class="mainWindow" @click.stop>
+            <Window @close="close">
+                <template slot="header">
+                    Вставьте текст и нажмите
+                    <el-button size="mini" style="font-size: 120%; color: blue" @click="loadBuffer">Загрузить</el-button>
+
+                    или F2
+                </template>
+
+                <div>
+                    <el-input placeholder="Введите название текста" class="input" v-model="bookTitle"></el-input>
+                </div>
+                <hr/>
+                <textarea ref="textArea" class="text" @paste="calcTitle"></textarea>
+            </Window>
+        </div>
+    </div>
+</template>
+
+<script>
+//-----------------------------------------------------------------------------
+import Vue from 'vue';
+import Component from 'vue-class-component';
+
+import Window from '../../../share/Window.vue';
+import _ from 'lodash';
+
+export default @Component({
+    components: {
+        Window,
+    },
+})
+class PasteTextPage extends Vue {
+    bookTitle = '';
+
+    created() {
+    }
+
+    mounted() {
+        this.$refs.textArea.focus();
+    }
+
+    getNonEmptyLine(text, count) {
+        let result = '';
+        const lines = text.split("\n");
+        let i = 0;
+        while (i < lines.length) {
+            if (lines[i].trim() != '') {
+                count--;
+                if (count <= 0) {
+                    result = lines[i];
+                    break;
+                }
+            }
+            i++;
+        }
+        return result;
+    }
+
+    calcTitle(event) {
+        if (this.bookTitle == '') {
+            let text = event.clipboardData.getData('text');
+            this.bookTitle = _.compact([
+                this.getNonEmptyLine(text, 1),
+                this.getNonEmptyLine(text, 2)
+            ]).join(' - ');
+        }
+    }
+
+    loadBuffer() {
+        this.$emit('load-buffer', {buffer: `<title>${this.bookTitle}</title>${this.$refs.textArea.value}`});
+        this.close();
+    }
+
+    close() {
+        this.$emit('paste-text-toggle');
+    }
+
+    keyHook(event) {
+        if (event.type == 'keydown') {
+            switch (event.code) {
+                case 'F2':
+                    this.loadBuffer();
+                    break;
+                case 'Escape':
+                    this.close();
+                    break;
+            }
+        }
+        return true;
+    }
+}
+//-----------------------------------------------------------------------------
+</script>
+
+<style scoped>
+.main {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    z-index: 40;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+}
+
+.mainWindow {
+    width: 100%;
+    height: 100%;
+    display: flex;
+}
+
+.text {
+    flex: 1;
+    overflow-wrap: anywhere;
+    overflow-y: auto;
+    padding: 0 10px 0 10px;
+    position: relative;
+    font-size: 120%;
+}
+
+.text:focus {
+    outline: none;
+}
+
+hr {
+    margin: 0;
+    padding: 0;
+}
+</style>

+ 113 - 13
client/components/Reader/Reader.vue

@@ -7,35 +7,35 @@
                 </el-tooltip>
                 </el-tooltip>
 
 
                 <div>
                 <div>
-                    <el-tooltip content="Действие назад" :open-delay="1000" effect="light">
+                    <el-tooltip v-show="showToolButton['undoAction']" content="Действие назад" :open-delay="1000" effect="light">
                         <el-button ref="undoAction" class="tool-button" :class="buttonActiveClass('undoAction')" @click="buttonClick('undoAction')" ><i class="el-icon-arrow-left"></i></el-button>
                         <el-button ref="undoAction" class="tool-button" :class="buttonActiveClass('undoAction')" @click="buttonClick('undoAction')" ><i class="el-icon-arrow-left"></i></el-button>
                     </el-tooltip>
                     </el-tooltip>
-                    <el-tooltip content="Действие вперед" :open-delay="1000" effect="light">
+                    <el-tooltip v-show="showToolButton['redoAction']" content="Действие вперед" :open-delay="1000" effect="light">
                         <el-button ref="redoAction" class="tool-button" :class="buttonActiveClass('redoAction')" @click="buttonClick('redoAction')" ><i class="el-icon-arrow-right"></i></el-button>
                         <el-button ref="redoAction" class="tool-button" :class="buttonActiveClass('redoAction')" @click="buttonClick('redoAction')" ><i class="el-icon-arrow-right"></i></el-button>
                     </el-tooltip>
                     </el-tooltip>
                     <div class="space"></div>
                     <div class="space"></div>
-                    <el-tooltip content="На весь экран" :open-delay="1000" effect="light">
+                    <el-tooltip v-show="showToolButton['fullScreen']" content="На весь экран" :open-delay="1000" effect="light">
                         <el-button ref="fullScreen" class="tool-button" :class="buttonActiveClass('fullScreen')" @click="buttonClick('fullScreen')"><i class="el-icon-rank"></i></el-button>
                         <el-button ref="fullScreen" class="tool-button" :class="buttonActiveClass('fullScreen')" @click="buttonClick('fullScreen')"><i class="el-icon-rank"></i></el-button>
                     </el-tooltip>
                     </el-tooltip>
-                    <el-tooltip content="Плавный скроллинг" :open-delay="1000" effect="light">
+                    <el-tooltip v-show="showToolButton['scrolling']" content="Плавный скроллинг" :open-delay="1000" effect="light">
                         <el-button ref="scrolling" class="tool-button" :class="buttonActiveClass('scrolling')" @click="buttonClick('scrolling')"><i class="el-icon-sort"></i></el-button>
                         <el-button ref="scrolling" class="tool-button" :class="buttonActiveClass('scrolling')" @click="buttonClick('scrolling')"><i class="el-icon-sort"></i></el-button>
                     </el-tooltip>
                     </el-tooltip>
-                    <el-tooltip content="Перелистнуть" :open-delay="1000" effect="light">
+                    <el-tooltip v-show="showToolButton['setPosition']" content="На страницу" :open-delay="1000" effect="light">
                         <el-button ref="setPosition" class="tool-button" :class="buttonActiveClass('setPosition')" @click="buttonClick('setPosition')"><i class="el-icon-d-arrow-right"></i></el-button>
                         <el-button ref="setPosition" class="tool-button" :class="buttonActiveClass('setPosition')" @click="buttonClick('setPosition')"><i class="el-icon-d-arrow-right"></i></el-button>
                     </el-tooltip>
                     </el-tooltip>
-                    <el-tooltip content="Найти в тексте" :open-delay="1000" effect="light">
+                    <el-tooltip v-show="showToolButton['search']" content="Найти в тексте" :open-delay="1000" effect="light">
                         <el-button ref="search" class="tool-button" :class="buttonActiveClass('search')" @click="buttonClick('search')"><i class="el-icon-search"></i></el-button>
                         <el-button ref="search" class="tool-button" :class="buttonActiveClass('search')" @click="buttonClick('search')"><i class="el-icon-search"></i></el-button>
                     </el-tooltip>
                     </el-tooltip>
-                    <el-tooltip content="Скопировать текст со страницы" :open-delay="1000" effect="light">
+                    <el-tooltip v-show="showToolButton['copyText']" content="Скопировать текст со страницы" :open-delay="1000" effect="light">
                         <el-button ref="copyText" class="tool-button" :class="buttonActiveClass('copyText')" @click="buttonClick('copyText')"><i class="el-icon-edit-outline"></i></el-button>
                         <el-button ref="copyText" class="tool-button" :class="buttonActiveClass('copyText')" @click="buttonClick('copyText')"><i class="el-icon-edit-outline"></i></el-button>
                     </el-tooltip>
                     </el-tooltip>
-                    <el-tooltip content="Принудительно обновить книгу в обход кэша" :open-delay="1000" effect="light">
+                    <el-tooltip v-show="showToolButton['refresh']" content="Принудительно обновить книгу в обход кэша" :open-delay="1000" effect="light">
                         <el-button ref="refresh" class="tool-button" :class="buttonActiveClass('refresh')" @click="buttonClick('refresh')">
                         <el-button ref="refresh" class="tool-button" :class="buttonActiveClass('refresh')" @click="buttonClick('refresh')">
                             <i class="el-icon-refresh" :class="{clear: !showRefreshIcon}"></i>
                             <i class="el-icon-refresh" :class="{clear: !showRefreshIcon}"></i>
                         </el-button>
                         </el-button>
                     </el-tooltip>
                     </el-tooltip>
                     <div class="space"></div>
                     <div class="space"></div>
-                    <el-tooltip content="Открыть недавние" :open-delay="1000" effect="light">
+                    <el-tooltip v-show="showToolButton['history']" content="Открыть недавние" :open-delay="1000" effect="light">
                         <el-button ref="history" class="tool-button" :class="buttonActiveClass('history')" @click="buttonClick('history')"><i class="el-icon-document"></i></el-button>
                         <el-button ref="history" class="tool-button" :class="buttonActiveClass('history')" @click="buttonClick('history')"><i class="el-icon-document"></i></el-button>
                     </el-tooltip>
                     </el-tooltip>
                 </div>
                 </div>
@@ -68,13 +68,27 @@
                 @start-text-search="startTextSearch"
                 @start-text-search="startTextSearch"
                 @stop-text-search="stopTextSearch">
                 @stop-text-search="stopTextSearch">
             </SearchPage>
             </SearchPage>
-            <CopyTextPage v-if="copyTextActive" ref="copyTextPage" @copy-text-toggle="copyTextToggle"></CopyTextPage>            
+            <CopyTextPage v-if="copyTextActive" ref="copyTextPage" @copy-text-toggle="copyTextToggle"></CopyTextPage>
             <HistoryPage v-show="historyActive" ref="historyPage" @load-book="loadBook" @history-toggle="historyToggle"></HistoryPage>
             <HistoryPage v-show="historyActive" ref="historyPage" @load-book="loadBook" @history-toggle="historyToggle"></HistoryPage>
             <SettingsPage v-if="settingsActive" ref="settingsPage" @settings-toggle="settingsToggle"></SettingsPage>
             <SettingsPage v-if="settingsActive" ref="settingsPage" @settings-toggle="settingsToggle"></SettingsPage>
             <HelpPage v-if="helpActive" ref="helpPage" @help-toggle="helpToggle"></HelpPage>
             <HelpPage v-if="helpActive" ref="helpPage" @help-toggle="helpToggle"></HelpPage>
             <ClickMapPage v-show="clickMapActive" ref="clickMapPage"></ClickMapPage>
             <ClickMapPage v-show="clickMapActive" ref="clickMapPage"></ClickMapPage>
             <ServerStorage v-show="hidden" ref="serverStorage"></ServerStorage>
             <ServerStorage v-show="hidden" ref="serverStorage"></ServerStorage>
+
+            <el-dialog
+                title="Что нового:"
+                :visible.sync="whatsNewVisible"
+                width="80%">
+                <div style="line-height: 20px" v-html="whatsNewContent"></div>
+
+                <span class="clickable" @click="openVersionHistory">Посмотреть историю версий</span>
+                <span slot="footer" class="dialog-footer">
+                    <el-button @click="whatsNewDisable">Больше не показывать</el-button>
+                </span>
+            </el-dialog>
+
         </el-main>
         </el-main>
+
     </el-container>
     </el-container>
 </template>
 </template>
 
 
@@ -101,6 +115,7 @@ import ServerStorage from './ServerStorage/ServerStorage.vue';
 import bookManager from './share/bookManager';
 import bookManager from './share/bookManager';
 import readerApi from '../../api/reader';
 import readerApi from '../../api/reader';
 import * as utils from '../../share/utils';
 import * as utils from '../../share/utils';
+import {versionHistory} from './versionHistory';
 
 
 export default @Component({
 export default @Component({
     components: {
     components: {
@@ -167,11 +182,15 @@ class Reader extends Vue {
     allowUrlParamBookPos = false;
     allowUrlParamBookPos = false;
     showRefreshIcon = true;
     showRefreshIcon = true;
     mostRecentBookReactive = null;
     mostRecentBookReactive = null;
+    showToolButton = {};
 
 
     actionList = [];
     actionList = [];
     actionCur = -1;
     actionCur = -1;
     hidden = false;
     hidden = false;
 
 
+    whatsNewVisible = false;
+    whatsNewContent = '';
+
     created() {
     created() {
         this.loading = true;
         this.loading = true;
         this.commit = this.$store.commit;
         this.commit = this.$store.commit;
@@ -223,14 +242,17 @@ class Reader extends Vue {
 
 
             if (this.$root.rootRoute == '/reader') {
             if (this.$root.rootRoute == '/reader') {
                 if (this.routeParamUrl) {
                 if (this.routeParamUrl) {
-                    await this.loadBook({url: this.routeParamUrl, bookPos: this.routeParamPos});
+                    await this.loadBook({url: this.routeParamUrl, bookPos: this.routeParamPos, force: this.routeParamRefresh});
                 } else {
                 } else {
                     this.loaderActive = true;
                     this.loaderActive = true;
                 }
                 }
             }
             }
 
 
             this.checkSetStorageAccessKey();
             this.checkSetStorageAccessKey();
+            this.checkActivateDonateHelpPage();
             this.loading = false;
             this.loading = false;
+
+            await this.showWhatsNew();
         })();
         })();
     }
     }
 
 
@@ -241,6 +263,8 @@ class Reader extends Vue {
         this.showClickMapPage = settings.showClickMapPage;
         this.showClickMapPage = settings.showClickMapPage;
         this.clickControl = settings.clickControl;
         this.clickControl = settings.clickControl;
         this.blinkCachedLoad = settings.blinkCachedLoad;
         this.blinkCachedLoad = settings.blinkCachedLoad;
+        this.showWhatsNewDialog = settings.showWhatsNewDialog;
+        this.showToolButton = settings.showToolButton;
     }
     }
 
 
     checkSetStorageAccessKey() {
     checkSetStorageAccessKey() {
@@ -257,6 +281,56 @@ class Reader extends Vue {
         }
         }
     }
     }
 
 
+    checkActivateDonateHelpPage() {
+        const q = this.$route.query;
+
+        if (q['donate']) {
+            this.$router.replace(`/reader`);
+            this.helpToggle();
+            this.$nextTick(() => {
+                this.$refs.helpPage.activateDonateHelpPage();
+            });
+        }
+    }
+
+    checkBookPosPercent() {
+        const q = this.$route.query;
+        if (q['__pp']) {
+            let pp = q['__pp'];
+            if (pp) {
+                pp = parseFloat(pp) || 0;
+                const recent = this.mostRecentBook();
+                (async() => {
+                    await utils.sleep(100);
+                    this.bookPos = Math.floor(recent.textLength*pp/100);
+                })();
+            }
+        }
+    }
+
+    async showWhatsNew() {
+        await utils.sleep(2000);
+
+        const whatsNew = versionHistory[0];
+        if (this.showWhatsNewDialog &&
+            whatsNew.showUntil >= utils.formatDate(new Date(), 'coDate') &&
+            whatsNew.header != this.whatsNewContentHash) {
+            this.whatsNewContent = 'Версия ' + whatsNew.header + whatsNew.content;
+            this.whatsNewVisible = true;
+        }
+    }
+
+    openVersionHistory() {
+        this.whatsNewVisible = false;
+        this.versionHistoryToggle();
+    }
+
+    whatsNewDisable() {
+        this.whatsNewVisible = false;
+        const whatsNew = versionHistory[0];
+        this.commit('reader/setWhatsNewContentHash', whatsNew.header);
+    }
+
     get routeParamPos() {
     get routeParamPos() {
         let result = undefined;
         let result = undefined;
         const q = this.$route.query;
         const q = this.$route.query;
@@ -293,6 +367,11 @@ class Reader extends Vue {
         return decodeURIComponent(result);
         return decodeURIComponent(result);
     }
     }
 
 
+    get routeParamRefresh() {
+        const q = this.$route.query;
+        return !!q['__refresh'];
+    }
+
     bookPosChanged(event) {
     bookPosChanged(event) {
         if (event.bookPosSeen !== undefined)
         if (event.bookPosSeen !== undefined)
             this.bookPosSeen = event.bookPosSeen;
             this.bookPosSeen = event.bookPosSeen;
@@ -353,6 +432,10 @@ class Reader extends Vue {
         return this.$store.state.reader.settings;
         return this.$store.state.reader.settings;
     }
     }
 
 
+    get whatsNewContentHash() {
+        return this.$store.state.reader.whatsNewContentHash;
+    }
+
     addAction(pos) {
     addAction(pos) {
         let a = this.actionList;
         let a = this.actionList;
         if (!a.length || a[a.length - 1] != pos) {
         if (!a.length || a[a.length - 1] != pos) {
@@ -523,6 +606,15 @@ class Reader extends Vue {
         }
         }
     }
     }
 
 
+    versionHistoryToggle() {
+        this.helpToggle();
+        if (this.helpActive) {
+            this.$nextTick(() => {
+                this.$refs.helpPage.activateVersionHistoryHelpPage();
+            });
+        }
+    }
+
     refreshBook() {
     refreshBook() {
         if (this.mostRecentBook()) {
         if (this.mostRecentBook()) {
             this.loadBook({url: this.mostRecentBook().url, force: true});
             this.loadBook({url: this.mostRecentBook().url, force: true});
@@ -708,7 +800,7 @@ class Reader extends Vue {
 
 
         this.progressActive = true;
         this.progressActive = true;
 
 
-        await this.$nextTick()
+        await this.$nextTick();
 
 
         const progress = this.$refs.page;
         const progress = this.$refs.page;
 
 
@@ -743,6 +835,7 @@ class Reader extends Vue {
                     progress.hide(); this.progressActive = false;
                     progress.hide(); this.progressActive = false;
                     this.blinkCachedLoadMessage();
                     this.blinkCachedLoadMessage();
 
 
+                    this.checkBookPosPercent();
                     await this.activateClickMapPage();
                     await this.activateClickMapPage();
                     return;
                     return;
                 }
                 }
@@ -791,6 +884,7 @@ class Reader extends Vue {
             } else
             } else
                 this.stopBlink = true;
                 this.stopBlink = true;
 
 
+            this.checkBookPosPercent();
             await this.activateClickMapPage();
             await this.activateClickMapPage();
         } catch (e) {
         } catch (e) {
             progress.hide(); this.progressActive = false;
             progress.hide(); this.progressActive = false;
@@ -1010,4 +1104,10 @@ i {
 .clear {
 .clear {
     color: rgba(0,0,0,0);
     color: rgba(0,0,0,0);
 }
 }
-</style>
+
+.clickable {
+    color: blue;
+    text-decoration: underline;
+    cursor: pointer;
+}
+</style>

+ 48 - 6
client/components/Reader/SettingsPage/SettingsPage.vue

@@ -106,6 +106,7 @@
                         </el-form>
                         </el-form>
                         </div>
                         </div>
                     </el-tab-pane>
                     </el-tab-pane>
+
                     <!-- Вид ------------------------------------------------------------------------->                    
                     <!-- Вид ------------------------------------------------------------------------->                    
                     <el-tab-pane label="Вид">
                     <el-tab-pane label="Вид">
 
 
@@ -345,6 +346,28 @@
                         </el-form>
                         </el-form>
                     </el-tab-pane>
                     </el-tab-pane>
 
 
+                    <!-- Кнопки ------------------------------------------------------------------------->
+                    <el-tab-pane label="Кнопки">
+                        <el-form :model="form" size="mini" label-width="120px" @submit.native.prevent>
+                            <div class="partHeader">Показывать кнопки панели</div>
+
+                            <el-form-item label="" v-for="item in toolButtons" :key="item.name">
+                                <el-checkbox @change="changeShowToolButton(item.name)" :value="showToolButton[item.name]">{{item.text}}</el-checkbox>
+                            </el-form-item>
+                        </el-form>
+                    </el-tab-pane>
+
+                    <!-- Управление ------------------------------------------------------------------------->
+                    <el-tab-pane label="Управление">
+                        <el-form :model="form" size="mini" label-width="120px" @submit.native.prevent>
+                            <div class="partHeader">Управление</div>
+
+                            <el-form-item label="">
+                                <el-checkbox v-model="clickControl">Включить управление кликом</el-checkbox>
+                            </el-form-item>
+                        </el-form>
+                    </el-tab-pane>
+
                     <!-- Листание ------------------------------------------------------------------------->
                     <!-- Листание ------------------------------------------------------------------------->
                     <el-tab-pane label="Листание">
                     <el-tab-pane label="Листание">
                         <el-form :model="form" size="mini" label-width="120px" @submit.native.prevent>
                         <el-form :model="form" size="mini" label-width="120px" @submit.native.prevent>
@@ -380,14 +403,12 @@
                                 </el-tooltip>
                                 </el-tooltip>
                             </el-form-item>
                             </el-form-item>
                         </el-form>
                         </el-form>
-                        
                     </el-tab-pane>
                     </el-tab-pane>
+
                     <!-- Прочее ------------------------------------------------------------------------->
                     <!-- Прочее ------------------------------------------------------------------------->
                     <el-tab-pane label="Прочее">
                     <el-tab-pane label="Прочее">
                         <el-form :model="form" size="mini" label-width="120px" @submit.native.prevent>
                         <el-form :model="form" size="mini" label-width="120px" @submit.native.prevent>
-                            <el-form-item label="Управление">
-                                <el-checkbox v-model="clickControl">Включить управление кликом</el-checkbox>
-                            </el-form-item>
+                            <div class="partHeader">Подсказки, уведомления</div>
 
 
                             <el-form-item label="Подсказка">
                             <el-form-item label="Подсказка">
                                 <el-tooltip :open-delay="500" effect="light">
                                 <el-tooltip :open-delay="500" effect="light">
@@ -406,7 +427,7 @@
                                     <el-checkbox v-model="blinkCachedLoad">Предупреждать о загрузке из кэша</el-checkbox>
                                     <el-checkbox v-model="blinkCachedLoad">Предупреждать о загрузке из кэша</el-checkbox>
                                 </el-tooltip>
                                 </el-tooltip>
                             </el-form-item>
                             </el-form-item>
-                            <el-form-item label="Уведомления">
+                            <el-form-item label="Уведомление">
                                 <el-tooltip :open-delay="500" effect="light">
                                 <el-tooltip :open-delay="500" effect="light">
                                     <template slot="content">
                                     <template slot="content">
                                         Показывать уведомления и ошибки от<br>
                                         Показывать уведомления и ошибки от<br>
@@ -415,8 +436,21 @@
                                     <el-checkbox v-model="showServerStorageMessages">Показывать сообщения синхронизации</el-checkbox>
                                     <el-checkbox v-model="showServerStorageMessages">Показывать сообщения синхронизации</el-checkbox>
                                 </el-tooltip>
                                 </el-tooltip>
                             </el-form-item>
                             </el-form-item>
+                            <el-form-item label="Уведомление">
+                                <el-tooltip :open-delay="500" effect="light">
+                                    <template slot="content">
+                                        Показывать уведомления "Что нового"<br>
+                                        при каждом выходе новой версии читалки
+                                    </template>
+                                    <el-checkbox v-model="showWhatsNewDialog">Показывать уведомление "Что нового"</el-checkbox>
+                                </el-tooltip>
+                            </el-form-item>
+                        </el-form>
+
+                        <el-form :model="form" size="mini" label-width="120px" @submit.native.prevent>
+                            <div class="partHeader">Прочее</div>
 
 
-                            <el-form-item label="URL">
+                            <el-form-item label="Парам. в URL">
                                 <el-tooltip :open-delay="500" effect="light">
                                 <el-tooltip :open-delay="500" effect="light">
                                     <template slot="content">
                                     <template slot="content">
                                         Добавление параметра "__p" в строке браузера<br>
                                         Добавление параметра "__p" в строке браузера<br>
@@ -450,6 +484,7 @@
                             </el-form-item>
                             </el-form-item>
                         </el-form>
                         </el-form>
                     </el-tab-pane>
                     </el-tab-pane>
+
                     <!-- Сброс ------------------------------------------------------------------------->
                     <!-- Сброс ------------------------------------------------------------------------->
                     <el-tab-pane label="Сброс">
                     <el-tab-pane label="Сброс">
                         <el-button @click="setDefaults">Установить по умолчанию</el-button>
                         <el-button @click="setDefaults">Установить по умолчанию</el-button>
@@ -517,12 +552,14 @@ class SettingsPage extends Vue {
     fonts = [];
     fonts = [];
 
 
     serverStorageKeyVisible = false;
     serverStorageKeyVisible = false;
+    toolButtons = [];
 
 
     created() {
     created() {
         this.commit = this.$store.commit;
         this.commit = this.$store.commit;
         this.reader = this.$store.state.reader;
         this.reader = this.$store.state.reader;
 
 
         this.form = {};
         this.form = {};
+        this.toolButtons = rstore.toolButtons;
         this.settingsChanged();
         this.settingsChanged();
     }
     }
 
 
@@ -536,6 +573,7 @@ class SettingsPage extends Vue {
                 this.form = Object.assign({}, this.form, {[prop]: newValue});
                 this.form = Object.assign({}, this.form, {[prop]: newValue});
             });
             });
         }
         }
+
         this.fontBold = (this.fontWeight == 'bold');
         this.fontBold = (this.fontWeight == 'bold');
         this.fontItalic = (this.fontStyle == 'italic');
         this.fontItalic = (this.fontStyle == 'italic');
 
 
@@ -642,6 +680,10 @@ class SettingsPage extends Vue {
         }
         }
     }
     }
 
 
+    changeShowToolButton(buttonName) {
+        this.showToolButton = Object.assign({}, this.showToolButton, {[buttonName]: !this.showToolButton[buttonName]});
+    }
+
     async addProfile() {
     async addProfile() {
         try {
         try {
             if (Object.keys(this.profiles).length >= 100) {
             if (Object.keys(this.profiles).length >= 100) {

+ 20 - 14
client/components/Reader/TextPage/TextPage.vue

@@ -171,6 +171,13 @@ class TextPage extends Vue {
         this.fontShift = this.fontVertShift/100;
         this.fontShift = this.fontVertShift/100;
         this.textShift = this.textVertShift/100 + this.fontShift;
         this.textShift = this.textVertShift/100 + this.fontShift;
 
 
+        //statusBar
+        this.$refs.statusBar.style.left = '0px';
+        this.$refs.statusBar.style.top = (this.statusBarTop ? 1 : this.realHeight - this.statusBarHeight) + 'px';
+
+        this.statusBarColor = this.hex2rgba(this.textColor || '#000000', this.statusBarColorAlpha);
+        this.statusBarClickable = this.drawHelper.statusBarClickable(this.statusBarTop, this.statusBarHeight);
+
         //drawHelper
         //drawHelper
         this.drawHelper.realWidth = this.realWidth;
         this.drawHelper.realWidth = this.realWidth;
         this.drawHelper.realHeight = this.realHeight;
         this.drawHelper.realHeight = this.realHeight;
@@ -197,13 +204,19 @@ class TextPage extends Vue {
         this.drawHelper.context = this.context;
         this.drawHelper.context = this.context;
 
 
         //сообщение "Загрузка шрифтов..."
         //сообщение "Загрузка шрифтов..."
-        const flText = 'Загрузка шрифта...';
-        this.$refs.fontsLoading.innerHTML = flText;
-        const fontsLoadingStyle = this.$refs.fontsLoading.style;
-        fontsLoadingStyle.position = 'absolute';
-        fontsLoadingStyle.fontSize = this.fontSize + 'px';
-        fontsLoadingStyle.top = (this.realHeight/2 - 2*this.fontSize) + 'px';
-        fontsLoadingStyle.left = (this.realWidth - this.drawHelper.measureText(flText, {}))/2 + 'px';
+        this.$refs.fontsLoading.innerHTML = '';
+        (async() => {
+            await sleep(500);
+            const flText = 'Загрузка шрифта';
+            this.$refs.fontsLoading.innerHTML = flText + ' &nbsp;<i class="el-icon-loading"></i>';
+            const fontsLoadingStyle = this.$refs.fontsLoading.style;
+            fontsLoadingStyle.position = 'absolute';
+            fontsLoadingStyle.fontSize = this.fontSize + 'px';
+            fontsLoadingStyle.top = (this.realHeight/2 - 2*this.fontSize) + 'px';
+            fontsLoadingStyle.left = (this.realWidth - this.drawHelper.measureText(flText, {}))/2 + 'px';
+            await sleep(10500);
+            this.$refs.fontsLoading.innerHTML = '';
+        })();
 
 
         //parsed
         //parsed
         if (this.parsed) {
         if (this.parsed) {
@@ -225,13 +238,6 @@ class TextPage extends Vue {
             this.parsed.imageFitWidth = this.imageFitWidth;
             this.parsed.imageFitWidth = this.imageFitWidth;
         }
         }
 
 
-        //statusBar
-        this.$refs.statusBar.style.left = '0px';
-        this.$refs.statusBar.style.top = (this.statusBarTop ? 1 : this.realHeight - this.statusBarHeight) + 'px';
-
-        this.statusBarColor = this.hex2rgba(this.textColor || '#000000', this.statusBarColorAlpha);
-        this.statusBarClickable = this.drawHelper.statusBarClickable(this.statusBarTop, this.statusBarHeight);
-
         //scrolling page
         //scrolling page
         const pageSpace = this.scrollHeight - this.pageLineCount*this.lineHeight;
         const pageSpace = this.scrollHeight - this.pageLineCount*this.lineHeight;
         let y = pageSpace/2;
         let y = pageSpace/2;

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

@@ -0,0 +1,97 @@
+export const versionHistory = [
+{
+    showUntil: '2019-06-05',
+    header: '0.6.7 (2019-05-30)',
+    content:
+`
+<ul>
+    <li>добавлен диалог "Что нового"</li>
+    <li>в справку добавлена история версий проекта</li>
+    <li>добавлена возможность настройки отображаемых кнопок на панели управления</li>
+    <li>некоторые кнопки на панели управления были скрыты по умолчанию</li>
+    <li>на страницу загрузки добавлена возможность загрузки книги из буфера обмена</li>
+    <li>добавлен GET-параметр вида "/reader?__refresh=1&url=..." для принудительного обновления загружаемого текста</li>
+    <li>добавлен GET-параметр вида "/reader?__pp=50.5&url=..." для указания позиции в книге в процентах</li>
+    <li>исправления багов и недочетов</li>
+</ul>
+`
+},
+
+{
+    showUntil: '2019-03-29',
+    header: '0.6.6 (2019-03-29)',
+    content:
+`
+<ul>
+    <li>в справку добавлено описание настройки браузеров для автономной работы читалки (без доступа к интернету)</li>
+    <li>оптимизации процесса синхронизации, внутренние переделки</li>
+</ul>
+`
+},
+
+{
+    showUntil: '2019-03-24',
+    header: '0.6.4 (2019-03-24)',
+    content:
+`
+<ul>
+    <li>исправления багов, оптимизации</li>
+    <li>добавлена возможность синхронизации данных между устройствами</li>
+</ul>
+`
+},
+
+{
+    showUntil: '2019-03-04',
+    header: '0.5.4 (2019-03-04)',
+    content:
+`
+<ul>
+    <li>добавлена поддержка форматов pdf, epub, mobi</li>
+    <li>(0.5.2) добавлена поддержка форматов rtf, doc, docx</li>
+    <li>(0.4.2) фильтр для СИ больше не вырезает изображения</li>
+    <li>(0.4.0) добавлено отображение картинок в fb2</li>
+</ul>
+`
+},
+
+{
+    showUntil: '2019-02-17',
+    header: '0.3.0 (2019-02-17)',
+    content:
+`
+<ul>
+    <li>поправки багов</li>
+    <li>улучшено распознавание текста</li>
+    <li>изменена верстка страницы - убрано позиционирование каждого слова</li>
+</ul>
+`
+},
+
+{
+    showUntil: '2019-02-14',
+    header: '0.1.7 (2019-02-14)',
+    content:
+`
+<ul>
+    <li>увеличены верхние границы отступов и др.размеров</li>
+    <li>добавлена настройка для удаления/вставки пустых параграфов</li>
+    <li>добавлена настройка включения/отключения управления кликом</li>
+    <li>добавлена возможность сброса настроек</li>
+    <li>убран автоматический редирект на последнюю загруженную книгу, если не задан url в маршруте</li>
+</ul>
+`
+},
+
+{
+    showUntil: '2019-02-12',
+    header: '0.1.0 (2019-02-12)',
+    content:
+`
+<ul>
+    <li>первый деплой проекта, длительность разработки - 2 месяца</li>
+</ul>
+`
+},
+
+]

+ 3 - 3
client/element.js

@@ -86,8 +86,8 @@ import './theme/form-item.css';
 import ElColorPicker from 'element-ui/lib/color-picker';
 import ElColorPicker from 'element-ui/lib/color-picker';
 import './theme/color-picker.css';
 import './theme/color-picker.css';
 
 
-//import ElDialog from 'element-ui/lib/dialog';
-//import './theme/dialog.css';
+import ElDialog from 'element-ui/lib/dialog';
+import './theme/dialog.css';
 
 
 import Notification from 'element-ui/lib/notification';
 import Notification from 'element-ui/lib/notification';
 import './theme/notification.css';
 import './theme/notification.css';
@@ -106,7 +106,7 @@ const components = {
     ElCol, ElContainer, ElAside, ElMain, ElHeader,
     ElCol, ElContainer, ElAside, ElMain, ElHeader,
     ElInput, ElInputNumber, ElSelect, ElOption, ElTable, ElTableColumn,
     ElInput, ElInputNumber, ElSelect, ElOption, ElTable, ElTableColumn,
     ElProgress, ElSlider, ElForm, ElFormItem,
     ElProgress, ElSlider, ElForm, ElFormItem,
-    ElColorPicker,
+    ElColorPicker, ElDialog,
 };
 };
 
 
 for (let name in components) {
 for (let name in components) {

+ 2 - 0
client/share/utils.js

@@ -39,6 +39,8 @@ export function formatDate(d, format) {
         case 'normal':
         case 'normal':
             return `${d.getDate().toString().padStart(2, '0')}.${(d.getMonth() + 1).toString().padStart(2, '0')}.${d.getFullYear()} ` + 
             return `${d.getDate().toString().padStart(2, '0')}.${(d.getMonth() + 1).toString().padStart(2, '0')}.${d.getFullYear()} ` + 
                 `${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}`;
                 `${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}`;
+        case 'coDate':
+            return `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, '0')}-${d.getDate().toString().padStart(2, '0')}`;
     }
     }
     
     
 }
 }

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

@@ -1,3 +1,16 @@
+//занчение toolButtons.name не должно совпадать с settingDefaults-propertyName
+const toolButtons = [
+    {name: 'undoAction',  show: true, text: 'Действие назад'},
+    {name: 'redoAction',  show: true, text: 'Действие вперед'},
+    {name: 'fullScreen',  show: true, text: 'На весь экран'},
+    {name: 'scrolling',   show: false, text: 'Плавный скроллинг'},
+    {name: 'setPosition', show: true, text: 'На страницу'},
+    {name: 'search',      show: true, text: 'Найти в тексте'},
+    {name: 'copyText',    show: false, text: 'Скопировать текст со страницы'},
+    {name: 'refresh',     show: true, text: 'Принудительно обновить книгу'},
+    {name: 'history',     show: true, text: 'Открыть недавние'},
+];
+
 const fonts = [
 const fonts = [
     {name: 'ReaderDefault', label: 'По-умолчанию', fontVertShift: 0},
     {name: 'ReaderDefault', label: 'По-умолчанию', fontVertShift: 0},
     {name: 'GEO_1', label: 'BPG Arial', fontVertShift: 10},
     {name: 'GEO_1', label: 'BPG Arial', fontVertShift: 10},
@@ -166,14 +179,18 @@ const settingDefaults = {
     imageHeightLines: 100,
     imageHeightLines: 100,
     imageFitWidth: true,
     imageFitWidth: true,
     showServerStorageMessages: true,
     showServerStorageMessages: true,
+    showWhatsNewDialog: true,
 
 
     fontShifts: {},
     fontShifts: {},
+    showToolButton: {},
 };
 };
 
 
 for (const font of fonts)
 for (const font of fonts)
     settingDefaults.fontShifts[font.name] = font.fontVertShift;
     settingDefaults.fontShifts[font.name] = font.fontVertShift;
 for (const font of webFonts)
 for (const font of webFonts)
     settingDefaults.fontShifts[font.name] = font.fontVertShift;
     settingDefaults.fontShifts[font.name] = font.fontVertShift;
+for (const button of toolButtons)
+    settingDefaults.showToolButton[button.name] = button.show;
 
 
 // initial state
 // initial state
 const state = {
 const state = {
@@ -183,6 +200,7 @@ const state = {
     profiles: {},
     profiles: {},
     profilesRev: 0,
     profilesRev: 0,
     allowProfilesSave: false,//подстраховка для разработки
     allowProfilesSave: false,//подстраховка для разработки
+    whatsNewContentHash: '',
     currentProfile: '',
     currentProfile: '',
     settings: Object.assign({}, settingDefaults),
     settings: Object.assign({}, settingDefaults),
     settingsRev: {},
     settingsRev: {},
@@ -214,6 +232,9 @@ const mutations = {
     setAllowProfilesSave(state, value) {
     setAllowProfilesSave(state, value) {
         state.allowProfilesSave = value;
         state.allowProfilesSave = value;
     },
     },
+    setWhatsNewContentHash(state, value) {
+        state.whatsNewContentHash = value;
+    },
     setCurrentProfile(state, value) {
     setCurrentProfile(state, value) {
         state.currentProfile = value;
         state.currentProfile = value;
     },
     },
@@ -226,6 +247,7 @@ const mutations = {
 };
 };
 
 
 export default {
 export default {
+    toolButtons,
     fonts,
     fonts,
     webFonts,
     webFonts,
     settingDefaults,
     settingDefaults,

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "Liberama",
   "name": "Liberama",
-  "version": "0.6.6",
+  "version": "0.6.7",
   "engines": {
   "engines": {
     "node": ">=10.0.0"
     "node": ">=10.0.0"
   },
   },

+ 24 - 22
server/core/ReaderWorker.js

@@ -131,32 +131,34 @@ class ReaderWorker {
         return `file://${hash}`;
         return `file://${hash}`;
     }
     }
 
 
-    async periodicCleanDir(dir, maxSize, timeout) {        
-        const list = await fs.readdir(dir);
-
-        let size = 0;
-        let files = [];
-        for (const name of list) {
-            const stat = await fs.stat(`${dir}/${name}`);
-            if (!stat.isDirectory()) {
-                size += stat.size;
-                files.push({name, stat});
+    async periodicCleanDir(dir, maxSize, timeout) {
+        try {
+            const list = await fs.readdir(dir);
+
+            let size = 0;
+            let files = [];
+            for (const name of list) {
+                const stat = await fs.stat(`${dir}/${name}`);
+                if (!stat.isDirectory()) {
+                    size += stat.size;
+                    files.push({name, stat});
+                }
             }
             }
-        }
 
 
-        files.sort((a, b) => a.stat.mtimeMs - b.stat.mtimeMs);
+            files.sort((a, b) => a.stat.mtimeMs - b.stat.mtimeMs);
 
 
-        let i = 0;
-        while (i < files.length && size > maxSize) {
-            const file = files[i];
-            await fs.remove(`${dir}/${file.name}`);
-            size -= file.stat.size;
-            i++;
+            let i = 0;
+            while (i < files.length && size > maxSize) {
+                const file = files[i];
+                await fs.remove(`${dir}/${file.name}`);
+                size -= file.stat.size;
+                i++;
+            }
+        } finally {
+            setTimeout(() => {
+                this.periodicCleanDir(dir, maxSize, timeout);
+            }, timeout);
         }
         }
-
-        setTimeout(() => {
-            this.periodicCleanDir(dir, maxSize, timeout);
-        }, timeout);
     }
     }
 }
 }