瀏覽代碼

Merge branch 'release/0.11.6'

Book Pauk 3 年之前
父節點
當前提交
4836a737c6

+ 5 - 3
build/linux.js

@@ -4,7 +4,7 @@ const util = require('util');
 const stream = require('stream');
 const pipeline = util.promisify(stream.pipeline);
 
-const got = require('got');
+const axios = require('axios');
 const FileDecompressor = require('../server/core/FileDecompressor');
 
 const distDir = path.resolve(__dirname, '../dist');
@@ -29,7 +29,8 @@ async function main() {
 
     if (!await fs.pathExists(sqliteDecompressedFilename)) {
         // Скачиваем node_sqlite3.node для винды, т.к. pkg не включает его в сборку
-        await pipeline(got.stream(sqliteRemoteUrl), fs.createWriteStream(`${tempDownloadDir}/sqlite.tar.gz`));
+        const res = await axios.get(sqliteRemoteUrl, {responseType: 'stream'})
+        await pipeline(res.data, fs.createWriteStream(`${tempDownloadDir}/sqlite.tar.gz`));
         console.log(`done downloading ${sqliteRemoteUrl}`);
 
         //распаковываем
@@ -46,7 +47,8 @@ async function main() {
         // Скачиваем ipfs
         const ipfsRemoteUrl = 'https://dist.ipfs.io/go-ipfs/v0.4.18/go-ipfs_v0.4.18_linux-amd64.tar.gz';
 
-        await pipeline(got.stream(ipfsRemoteUrl), fs.createWriteStream(`${tempDownloadDir}/ipfs.tar.gz`));
+        const res = await axios.get(ipfsRemoteUrl, {responseType: 'stream'})
+        await pipeline(res.data, fs.createWriteStream(`${tempDownloadDir}/ipfs.tar.gz`));
         console.log(`done downloading ${ipfsRemoteUrl}`);
 
         //распаковываем

+ 5 - 3
build/win.js

@@ -4,7 +4,7 @@ const util = require('util');
 const stream = require('stream');
 const pipeline = util.promisify(stream.pipeline);
 
-const got = require('got');
+const axios = require('axios');
 const FileDecompressor = require('../server/core/FileDecompressor');
 
 const distDir = path.resolve(__dirname, '../dist');
@@ -29,7 +29,8 @@ async function main() {
 
     if (!await fs.pathExists(sqliteDecompressedFilename)) {
         // Скачиваем node_sqlite3.node для винды, т.к. pkg не включает его в сборку
-        await pipeline(got.stream(sqliteRemoteUrl), fs.createWriteStream(`${tempDownloadDir}/sqlite.tar.gz`));
+        const res = await axios.get(sqliteRemoteUrl, {responseType: 'stream'})
+        await pipeline(res.data, fs.createWriteStream(`${tempDownloadDir}/sqlite.tar.gz`));
         console.log(`done downloading ${sqliteRemoteUrl}`);
 
         //распаковываем
@@ -46,7 +47,8 @@ async function main() {
         // Скачиваем ipfs
         const ipfsRemoteUrl = 'https://dist.ipfs.io/go-ipfs/v0.4.18/go-ipfs_v0.4.18_windows-amd64.zip';
 
-        await pipeline(got.stream(ipfsRemoteUrl), fs.createWriteStream(`${tempDownloadDir}/ipfs.zip`));
+        const res = await axios.get(ipfsRemoteUrl, {responseType: 'stream'})
+        await pipeline(res.data, fs.createWriteStream(`${tempDownloadDir}/ipfs.zip`));
         console.log(`done downloading ${ipfsRemoteUrl}`);
 
         //распаковываем

+ 1 - 1
client/api/misc.js

@@ -9,7 +9,7 @@ class Misc {
     async loadConfig() {
 
         const query = {params: [
-            'name', 'version', 'mode', 'maxUploadFileSize', 'useExternalBookConverter', 'branch',
+            'name', 'version', 'mode', 'maxUploadFileSize', 'useExternalBookConverter', 'acceptFileExt', 'branch',
         ]};
 
         try {

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

@@ -21,7 +21,12 @@
                 </template>
             </q-input>
 
-            <input id="file" ref="file" type="file" style="display: none;" @change="loadFile" />
+            <input
+                id="file" ref="file" type="file" 
+                style="display: none;"
+                :accept="acceptFileExt"
+                @change="loadFile" 
+            />
 
             <div class="q-my-sm"></div>
             <q-btn no-caps dense class="q-px-sm" color="primary" size="13px" @click="loadFileClick">
@@ -131,6 +136,10 @@ class LoaderPage {
         return this.$store.state.config.version;
     }
 
+    get acceptFileExt() {
+        return this.$store.state.config.acceptFileExt;
+    }
+
     get isExternalConverter() {
         return this.$store.state.config.useExternalBookConverter;
     }

+ 2 - 2
client/components/Reader/ServerStorage/ServerStorage.vue

@@ -728,10 +728,10 @@ class ServerStorage {
                 const ids = id.split('.');
                 if (!(ids.length == 2) || !(ids[0] == this.hashedStorageKey))
                     throw new Error(`decodeStorageItems: bad id - ${id}`);
-                items[utils.fromBase58(ids[1])] = decoded;
+                items[utils.fromBase58(ids[1]).toString()] = decoded;
             }
         }
-
+        
         result.items = items;
         return result;
     }

+ 56 - 6
client/components/Reader/TextPage/TextPage.vue

@@ -6,29 +6,32 @@
         </div>
         <div ref="scrollBox1" class="layout over-hidden" @wheel.prevent.stop="onMouseWheel">
             <div ref="scrollingPage1" class="layout over-hidden" @transitionend="onPage1TransitionEnd" @animationend="onPage1AnimationEnd">
-                <div v-html="page1"></div>
+                <div @copy.prevent="copyText" v-html="page1"></div>
             </div>
         </div>
         <div ref="scrollBox2" class="layout over-hidden" @wheel.prevent.stop="onMouseWheel">
             <div ref="scrollingPage2" class="layout over-hidden" @transitionend="onPage2TransitionEnd" @animationend="onPage2AnimationEnd">
-                <div v-html="page2"></div>
+                <div @copy.prevent="copyText" v-html="page2"></div>
             </div>
         </div>
         <div v-show="showStatusBar" ref="statusBar" class="layout">
             <div v-html="statusBar"></div>
         </div>
-        <div v-show="clickControl" ref="layoutEvents" class="layout events" 
+        <div
+            v-show="clickControl" ref="layoutEvents" class="layout events" 
             oncontextmenu="return false;"
             @mousedown.prevent.stop="onMouseDown" @mouseup.prevent.stop="onMouseUp"
             @wheel.prevent.stop="onMouseWheel"
             @touchstart.stop="onTouchStart" @touchend.stop="onTouchEnd" @touchmove.stop="onTouchMove" @touchcancel.prevent.stop="onTouchCancel"            
         >
-            <div v-show="showStatusBar && statusBarClickOpen" @mousedown.prevent.stop @touchstart.stop
+            <div
+                v-show="showStatusBar && statusBarClickOpen" @mousedown.prevent.stop @touchstart.stop
                 @click.prevent.stop="onStatusBarClick"
                 v-html="statusBarClickable"
             ></div>
         </div>
-        <div v-show="!clickControl && showStatusBar && statusBarClickOpen" class="layout" 
+        <div
+            v-show="!clickControl && showStatusBar && statusBarClickOpen" class="layout" 
             @mousedown.prevent.stop @touchstart.stop
             @click.prevent.stop="onStatusBarClick"
             v-html="statusBarClickable"
@@ -46,6 +49,7 @@ import vueComponent from '../../vueComponent.js';
 
 import {loadCSS} from 'fg-loadcss';
 import _ from 'lodash';
+import he from 'he';
 
 import './TextPage.css';
 
@@ -1201,8 +1205,54 @@ class TextPage {
         }
 
         return action;
-   }
+    }
 
+    copyText(event) {
+        //все это для того, чтобы правильно расставить переносы \n при копировании текста
+        //прямо с текущей страницы
+
+        //подготовка, вытаскиваем весь текст страницы
+        const lines = this.getLines(this.bookPos);
+        const decodedLines = [];
+        for (const line of lines.linesDown) {
+            let lineText = '';
+            for (const part of line.parts) {
+                lineText += part.text;
+            }
+            decodedLines.push({text: he.decode(lineText), first: line.first});
+        }
+
+        let i = 0;
+        const findDecoded = (line) => {
+            for (let j = i; j < decodedLines.length; j++) {
+                const decoded = decodedLines[j];
+                if (decoded.text.indexOf(line) >= 0) {
+                    i = j;
+                    return decoded;
+                }
+            }
+            return;
+        }
+
+        const selection = document.getSelection();
+        const splitted = selection.toString().split(/[\n\r]/);
+        
+        let filtered = '';
+        //формируем filtered, учитывая переносы из decodedLines
+        for (const line of splitted) {
+            const found = findDecoded(line);
+            if (found && found.first) {
+                filtered += (filtered ? '\n' : '') + line;
+            } else {
+                filtered += (filtered ? '\r ' : '') + line;
+            }
+        }
+
+        //маленькие хитрости, убираем переносы по слогам
+        filtered = filtered.replace(/-\r /g, '').replace(/\r /g, ' ');
+
+        event.clipboardData.setData('text/plain', filtered);
+    }
 }
 
 export default vueComponent(TextPage);

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

@@ -1,4 +1,18 @@
 export const versionHistory = [
+{
+    version: '0.11.6',
+    releaseDate: '2022-07-02',
+    showUntil: '2022-07-01',
+    content:
+`
+<ul>
+    <li>улучшено копирование текста прямо со страницы, для переводчиков</li>
+    <li>актуализация используемых пакетов</li>
+</ul>
+
+`
+},
+
 {
     version: '0.11.5',
     releaseDate: '2022-04-15',

+ 8 - 4
client/share/utils.js

@@ -90,7 +90,7 @@ export function toBase58(data) {
 }
 
 export function fromBase58(data) {
-    return bs58.decode(data);
+    return Buffer.from(bs58.decode(data));
 }
 
 //base-x слишком тормозит, используем sjcl
@@ -107,6 +107,10 @@ export function fromBase64(data) {
     ));
 }
 
+export function hasProp(obj, prop) {
+    return Object.prototype.hasOwnProperty.call(obj, prop);
+}
+
 export function getObjDiff(oldObj, newObj, opts = {}) {
     const {
         exclude = [],
@@ -126,7 +130,7 @@ export function getObjDiff(oldObj, newObj, opts = {}) {
         for (const key of Object.keys(oldObj)) {
             const kp = `${keyPath}${key}`;
 
-            if (newObj.hasOwnProperty(key)) {
+            if (Object.prototype.hasOwnProperty.call(newObj, key)) {
                 if (ex.has(kp))
                     continue;
 
@@ -149,7 +153,7 @@ export function getObjDiff(oldObj, newObj, opts = {}) {
             if (exAdd.has(kp))
                 continue;
 
-            if (!oldObj.hasOwnProperty(key)) {
+            if (!Object.prototype.hasOwnProperty.call(oldObj, key)) {
                 result.add[key] = _.cloneDeep(newObj[key]);
             }
         }
@@ -213,7 +217,7 @@ export function applyObjDiff(obj, diff, opts = {}) {
 
     const change = diff.change;
     for (const key of Object.keys(change)) {
-        if (result.hasOwnProperty(key)) {
+        if (Object.prototype.hasOwnProperty.call(result, key)) {
             if (_.isObject(change[key])) {
                 result[key] = applyObjDiff(result[key], change[key], opts);
             } else {

+ 1 - 1
client/store/modules/reader.js

@@ -70,7 +70,7 @@ const hotKeys = [
     {name: 'scrolling', codes: ['Z']},
     {name: 'setPosition', codes: ['P']},
     {name: 'search', codes: ['Ctrl+F']},
-    {name: 'copyText', codes: ['Ctrl+C']},    
+    {name: 'copyText', codes: ['Ctrl+Space']},    
     {name: 'convOptions', codes: ['Ctrl+M']},
     {name: 'refresh', codes: ['R']},
     {name: 'contents', codes: ['C']},

+ 1 - 0
docs/liberama.top/liberama

@@ -140,5 +140,6 @@ server {
 
   location / {
     proxy_pass http://fantasy-worlds.org;
+    proxy_hide_header x-frame-options;
   }
 }

文件差異過大導致無法顯示
+ 301 - 270
package-lock.json


+ 11 - 12
package.json

@@ -1,6 +1,6 @@
 {
   "name": "Liberama",
-  "version": "0.11.5",
+  "version": "0.11.6",
   "author": "Book Pauk <bookpauk@gmail.com>",
   "license": "CC0-1.0",
   "repository": "bookpauk/liberama",
@@ -28,17 +28,17 @@
     "@babel/preset-env": "^7.16.0",
     "@vue/compiler-sfc": "^3.2.22",
     "babel-loader": "^8.2.3",
-    "copy-webpack-plugin": "^9.1.0",
+    "copy-webpack-plugin": "^11.0.0",
     "css-loader": "^6.5.1",
-    "css-minimizer-webpack-plugin": "^3.1.3",
-    "eslint": "^8.2.0",
-    "eslint-plugin-vue": "^8.0.3",
+    "css-minimizer-webpack-plugin": "^4.0.0",
+    "eslint": "^8.19.0",
+    "eslint-plugin-vue": "^9.1.1",
     "html-webpack-plugin": "^5.5.0",
     "mini-css-extract-plugin": "^2.4.4",
     "pkg": "^5.5.1",
     "terser-webpack-plugin": "^5.2.5",
-    "vue-eslint-parser": "^8.0.1",
-    "vue-loader": "^16.8.3",
+    "vue-eslint-parser": "^9.0.3",
+    "vue-loader": "^17.0.0",
     "vue-style-loader": "^4.1.3",
     "webpack": "^5.64.1",
     "webpack-cli": "^4.9.1",
@@ -50,17 +50,16 @@
   "dependencies": {
     "@quasar/extras": "^1.12.0",
     "@vue/compat": "^3.2.21",
-    "axios": "^0.24.0",
-    "base-x": "^3.0.9",
+    "axios": "^0.27.2",
+    "base-x": "^4.0.0",
     "chardet": "^1.4.0",
     "compression": "^1.7.4",
     "express": "^4.17.1",
     "fg-loadcss": "^3.1.0",
-    "fs-extra": "^9.0.1",
-    "got": "^11.8.2",
+    "fs-extra": "^10.1.0",
     "he": "^1.2.0",
     "iconv-lite": "^0.6.3",
-    "jembadb": "^2.3.0",
+    "jembadb": "^3.0.6",
     "localforage": "^1.10.0",
     "lodash": "^4.17.21",
     "minimist": "^1.2.5",

+ 3 - 2
server/config/base.js

@@ -22,7 +22,8 @@ module.exports = {
     maxUploadPublicDirSize: 200*1024*1024,//100Мб
 
     useExternalBookConverter: false,
-    webConfigParams: ['name', 'version', 'mode', 'maxUploadFileSize', 'useExternalBookConverter', 'branch'],
+    acceptFileExt: '.fb2, .fb3, .html, .txt, .zip, .bz2, .gz, .rar, .epub, .mobi, .rtf, .doc, .docx, .pdf, .djvu, .jpg, .jpeg, .png',
+    webConfigParams: ['name', 'version', 'mode', 'maxUploadFileSize', 'useExternalBookConverter', 'acceptFileExt', 'branch'],
 
     db: [
         {
@@ -48,7 +49,7 @@ module.exports = {
     servers: [
         {
             serverName: '1',
-            mode: 'normal', //'none', 'normal', 'site', 'reader', 'omnireader'
+            mode: 'normal', //'none', 'normal', 'site', 'reader', 'omnireader', 'liberama.top'
             ip: '0.0.0.0',
             port: '33080',
         },

+ 1 - 54
server/controllers/WorkerController.js

@@ -25,60 +25,7 @@ class WorkerController extends BaseController {
         res.status(400).send({error});
         return false;
     }
-
-    //TODO: удалить бесполезную getStateFinish
-    async getStateFinish(req, res) {
-        const request = req.body;
-        let error = '';
-        try {
-            if (!request.workerId)
-                throw new Error(`key 'workerId' is wrong`);
-
-            res.writeHead(200, {
-                'Content-Type': 'text/json; charset=utf-8',
-            });
-
-            const splitter = '-- aod2t5hDXU32bUFyqlFE next status --';            
-            const refreshPause = 200;
-            let i = 0;
-            let prevProgress = -1;
-            let prevState = '';
-            let state;
-            while (1) {// eslint-disable-line no-constant-condition
-                state = this.workerState.getState(request.workerId);
-                if (!state) break;
-
-                res.write(splitter + JSON.stringify(state));
-                res.flush();
-
-                if (state.state != 'finish' && state.state != 'error')
-                    await utils.sleep(refreshPause);
-                else
-                    break;
-
-                i++;
-                if (i > 2*60*1000/refreshPause) {//2 мин ждем телодвижений воркера
-                    res.write(splitter + JSON.stringify({state: 'error', error: 'Слишком долгое время ожидания'}));
-                    break;
-                }
-                i = (prevProgress != state.progress || prevState != state.state ? 1 : i);
-                prevProgress = state.progress;
-                prevState = state.state;
-            }
-            
-            if (!state) {
-                res.write(splitter + JSON.stringify({}));
-            }
-
-            res.end();
-            return false;
-        } catch (e) {
-            error = e.message;
-        }
-        //bad request
-        res.status(400).send({error});
-        return false;
-    }
+   
 }
 
 module.exports = WorkerController;

+ 4 - 4
server/core/AsyncExit.js

@@ -21,10 +21,10 @@ class AsyncExit {
     }
 
     _init(signals, codeOnSignal) {
-        const runSingalCallbacks = async(signal) => {
+        const runSingalCallbacks = async(signal, err, origin) => {
             for (const signalCallback of this.onSignalCallbacks.keys()) {
                 try {
-                    await signalCallback(signal);
+                    await signalCallback(signal, err, origin);
                 } catch(e) {
                     console.error(e);
                 }
@@ -32,8 +32,8 @@ class AsyncExit {
         };
 
         for (const signal of signals) {
-            process.once(signal, async() => {
-                await runSingalCallbacks(signal);
+            process.once(signal, async(err, origin) => {
+                await runSingalCallbacks(signal, err, origin);
                 this.exit(codeOnSignal);
             });
         }

+ 59 - 31
server/core/FileDownloader.js

@@ -1,4 +1,4 @@
-const got = require('got');
+const axios = require('axios');
 
 class FileDownloader {
     constructor(limitDownloadSize = 0) {
@@ -7,54 +7,82 @@ class FileDownloader {
 
     async load(url, callback, abort) {
         let errMes = '';
+
         const options = {
             headers: {
                 'user-agent': 'Mozilla/5.0 (X11; HasCodingOs 1.0; Linux x64) AppleWebKit/637.36 (KHTML, like Gecko) Chrome/70.0.3112.101 Safari/637.36 HasBrowser/5.0'
             },
-            responseType: 'buffer',
+            responseType: 'stream',
         };
 
-        const response = await got(url, Object.assign({}, options, {method: 'HEAD'}));
+        try {
+            const res = await axios.get(url, options);
 
-        let estSize = 0;
-        if (response.headers['content-length']) {
-            estSize = response.headers['content-length'];
-        }
+            let estSize = 0;
+            if (res.headers['content-length']) {
+                estSize = res.headers['content-length'];
+            }
 
-        let prevProg = 0;
-        const request = got(url, options);
+            if (estSize > this.limitDownloadSize) {
+                throw new Error('Файл слишком большой');
+            }
+
+            let prevProg = 0;
+            let transferred = 0;
 
-        request.on('downloadProgress', progress => {
-            if (this.limitDownloadSize) {
-                if (progress.transferred > this.limitDownloadSize) {
-                    errMes = 'Файл слишком большой';
-                    request.cancel();
+            const download = this.streamToBuffer(res.data, (chunk) => {
+                transferred += chunk.length;
+                if (this.limitDownloadSize) {
+                    if (transferred > this.limitDownloadSize) {
+                        errMes = 'Файл слишком большой';
+                        res.request.abort();
+                    }
                 }
-            }
 
-            let prog = 0;
-            if (estSize)
-                prog = Math.round(progress.transferred/estSize*100);
-            else if (progress.transferred)
-                prog = Math.round(progress.transferred/(progress.transferred + 200000)*100);
+                let prog = 0;
+                if (estSize)
+                    prog = Math.round(transferred/estSize*100);
+                else
+                    prog = Math.round(transferred/(transferred + 200000)*100);
 
-            if (prog != prevProg && callback)
-                callback(prog);
-            prevProg = prog;
+                if (prog != prevProg && callback)
+                    callback(prog);
+                prevProg = prog;
 
-            if (abort && abort()) {
-                errMes = 'abort';
-                request.cancel();
-            }
-        });
+                if (abort && abort()) {
+                    errMes = 'abort';
+                    res.request.abort();
+                }
+            });
 
-        try {
-            return (await request).body;
+            return await download;
         } catch (error) {
             errMes = (errMes ? errMes : error.message);
             throw new Error(errMes);
         }
     }
+
+    streamToBuffer(stream, progress) {
+        return new Promise((resolve, reject) => {
+            
+            if (!progress)
+                progress = () => {};
+
+            const _buf = [];
+
+            stream.on('data', (chunk) => {
+                _buf.push(chunk);
+                progress(chunk);
+            });
+            stream.on('end', () => resolve(Buffer.concat(_buf)));
+            stream.on('error', (err) => {
+                reject(err);
+            });
+            stream.on('aborted', () => {
+                reject(new Error('aborted'));
+            });
+        });
+    }
 }
 
-module.exports = FileDownloader;
+module.exports = FileDownloader;

+ 2 - 2
server/core/Logger.js

@@ -188,8 +188,8 @@ class Logger {
         }
 
         this.closed = false;
-        ayncExit.onSignal((signal) => {
-            this.log(LM_FATAL, `Signal ${signal} received, exiting...`);
+        ayncExit.onSignal((signal, err) => {
+            this.log(LM_FATAL, `Signal "${signal}" received, error: "${(err.stack ? err.stack : err)}", exiting...`);
         });
         ayncExit.addAfter(this.close.bind(this));
     }

+ 1 - 1
server/core/utils.js

@@ -13,7 +13,7 @@ function toBase36(data) {
 }
 
 function fromBase36(data) {
-    return bs36.decode(data);
+    return Buffer.from(bs36.decode(data));
 }
 
 function bufferRemoveZeroes(buf) {

+ 0 - 1
server/routes.js

@@ -31,7 +31,6 @@ function initRoutes(app, wss, config) {
         ['POST', '/api/reader/upload-file', [upload.single('file'), reader.uploadFile.bind(reader)], [aAll], {}],
         ['POST', '/api/reader/restore-cached-file', reader.restoreCachedFile.bind(reader), [aAll], {}],        
         ['POST', '/api/worker/get-state', worker.getState.bind(worker), [aAll], {}],
-        ['POST', '/api/worker/get-state-finish', worker.getStateFinish.bind(worker), [aAll], {}],
     ];
 
     //to app

部分文件因文件數量過多而無法顯示