Prechádzať zdrojové kódy

Merge branch 'release/0.4.4'

Book Pauk 6 rokov pred
rodič
commit
c36e9b36d8

+ 13 - 1
client/components/Reader/HistoryPage/HistoryPage.vue

@@ -15,8 +15,14 @@
                     border
                     :default-sort = "{prop: 'touchDateTime', order: 'descending'}"
                     :header-cell-style = "headerCellStyle"
+                    :row-key = "rowKey"
                     >
 
+                    <el-table-column
+                        type="index"
+                        width="35px"
+                        >
+                    </el-table-column>
                     <el-table-column
                         prop="touchDateTime"
                         min-width="90px"
@@ -127,6 +133,10 @@ class HistoryPage extends Vue {
         });
     }
 
+    rowKey(row) {
+        return row.key;
+    }
+
     updateTableData() {
         let result = [];
 
@@ -176,13 +186,15 @@ class HistoryPage extends Vue {
         }
 
         const search = this.search;
-        this.tableData = result.filter(item => {
+        result = result.filter(item => {
             return !search ||
                 item.touchTime.includes(search) ||
                 item.touchDate.includes(search) ||
                 item.desc.title.toLowerCase().includes(search.toLowerCase()) ||
                 item.desc.author.toLowerCase().includes(search.toLowerCase())
         });
+
+        this.tableData = result;
     }
 
     headerCellStyle(cell) {

+ 4 - 3
client/components/Reader/SettingsPage/SettingsPage.vue

@@ -185,7 +185,7 @@
                                 <el-checkbox v-model="wordWrap">Перенос по слогам</el-checkbox>
                             </el-form-item>
                             <el-form-item label="Обработка">
-                                <el-checkbox v-model="cutEmptyParagraphs">Убирать пустые параграфы</el-checkbox>
+                                <el-checkbox v-model="cutEmptyParagraphs">Убирать пустые строки</el-checkbox>
                             </el-form-item>
                             <el-form-item label="">
                                 <el-col :span="12">
@@ -439,11 +439,12 @@ class SettingsPage extends Vue {
           '#000000',
           '#202020',
           '#ebe2c9',
+          '#cfdc99',
+          '#478355',
+          '#a6caf0',
           '#909080',
           '#808080',
           '#c8c8c8',
-          '#478355',
-          '#a6caf0',
         ];
     }
 

+ 3 - 3
client/components/Reader/TextPage/DrawHelper.js

@@ -28,7 +28,7 @@ export default class DrawHelper {
 
         let out = `<div style="width: ${this.w}px; height: ${this.h}px;` + 
             ` position: absolute; top: ${this.fontSize*this.textShift}px; color: ${this.textColor}; font: ${font}; ${justify}` +
-            ` line-height: ${this.lineHeight}px;">`;
+            ` line-height: ${this.lineHeight}px; white-space: nowrap;">`;
 
         let imageDrawn = new Set();
         let len = lines.length;
@@ -91,13 +91,13 @@ export default class DrawHelper {
                 } else
                     text = part.text;
 
-                if (text.trim() == '')
+                if (text && text.trim() == '')
                     text = `<span style="white-space: pre">${text}</span>`;
 
                 lineText += `${tOpen}${text}${tClose}`;
 
                 center = center || part.style.center;
-                space = (part.style.space > 0 ? part.style.space : space);
+                space = (part.style.space > space ? part.style.space : space);
 
                 //избражения
                 //image: {local: Boolean, inline: Boolean, id: String, imageLine: Number, lineCount: Number, paraIndex: Number},

+ 26 - 14
client/components/Reader/share/BookParser.js

@@ -45,6 +45,9 @@ export default class BookParser {
         let italic = false;
         let space = 0;
         let inPara = false;
+        let isFirstBody = true;
+        let isFirstSection = true;
+        let isFirstTitlePara = false;
 
         this.binary = {};
         let binaryId = '';
@@ -185,7 +188,7 @@ export default class BookParser {
                 if (attrs.href && attrs.href.value) {
                     const href = attrs.href.value;
                     if (href[0] == '#') {//local
-                        if (inPara && !this.showInlineImagesInCenter)
+                        if (inPara && !this.showInlineImagesInCenter && !center)
                             growParagraph(`<image-inline href="${href}"></image-inline>`, 0);
                         else
                             newParagraph(`<image href="${href}">${' '.repeat(maxImageLineCount)}</image>`, maxImageLineCount);
@@ -199,14 +202,23 @@ export default class BookParser {
             }
 
             if (path.indexOf('/fictionbook/body') == 0) {
+                if (tag == 'body') {
+                    if (!isFirstBody)
+                        newParagraph(' ', 1);
+                    isFirstBody = false;
+                }
+
                 if (tag == 'title') {
                     newParagraph(' ', 1);
+                    isFirstTitlePara = true;
                     bold = true;
                     center = true;
                 }
 
                 if (tag == 'section') {
-                    newParagraph(' ', 1);
+                    if (!isFirstSection)
+                        newParagraph(' ', 1);
+                    isFirstSection = false;
                 }
 
                 if (tag == 'emphasis' || tag == 'strong') {
@@ -214,13 +226,17 @@ export default class BookParser {
                 }
 
                 if ((tag == 'p' || tag == 'empty-line' || tag == 'v')) {
-                    newParagraph(' ', 1);
-                    if (tag == 'p')
+                    if (!(tag == 'p' && isFirstTitlePara))
+                        newParagraph(' ', 1);
+                    if (tag == 'p') {
                         inPara = true;
+                        isFirstTitlePara = false;
+                    }
                 }
 
                 if (tag == 'subtitle') {
                     newParagraph(' ', 1);
+                    isFirstTitlePara = true;
                     bold = true;
                 }
 
@@ -248,6 +264,7 @@ export default class BookParser {
             
                 if (path.indexOf('/fictionbook/body') == 0) {
                     if (tag == 'title') {
+                        isFirstTitlePara = false;
                         bold = false;
                         center = false;
                     }
@@ -261,6 +278,7 @@ export default class BookParser {
                     }
 
                     if (tag == 'subtitle') {
+                        isFirstTitlePara = false;
                         bold = false;
                     }
 
@@ -293,10 +311,10 @@ export default class BookParser {
             text = text.replace(/>/g, '&gt;');
             text = text.replace(/</g, '&lt;');
 
-            if (text != ' ' && text.trim() == '')
-                text = text.trim();
+            if (text && text.trim() == '')
+                text = (text.indexOf(' ') >= 0 ? ' ' : '');
 
-            if (text == '')
+            if (!text)
                 return;
 
             text = text.replace(/[\t\n\r]/g, ' ');
@@ -348,13 +366,7 @@ export default class BookParser {
             }
 
             if (path.indexOf('/fictionbook/body/section') == 0) {
-                switch (tag) {
-                    case 'p':
-                        growParagraph(`${tOpen}${text}${tClose}`, text.length);
-                        break;
-                    default:
-                        growParagraph(`${tOpen}${text}${tClose}`, text.length);
-                }
+                growParagraph(`${tOpen}${text}${tClose}`, text.length);
             }
 
             if (binaryId) {

+ 1 - 1
package.json

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

+ 71 - 31
server/core/BookConverter/index.js

@@ -10,6 +10,7 @@ const FileDetector = require('../FileDetector');
 
 const repSpaces = (text) => text.replace(/&nbsp;|[\t\n\r]/g, ' ');
 const repSpaces2 = (text) => text.replace(/[\n\r]/g, '');
+const repSpaces3 = (text) => text.replace(/&nbsp;/g, ' ');
 
 class BookConverter {
     constructor() {
@@ -66,7 +67,10 @@ class BookConverter {
             }
         }
 
-        return iconv.decode(data, selected);
+        if (selected.toLowerCase() != 'utf-8')
+            return iconv.decode(data, selected);
+        else
+            return data;
     }
 
     checkEncoding(data) {
@@ -108,19 +112,21 @@ class BookConverter {
         };
 
         const growParagraph = (text) => {
+            if (!pars.length)
+                newParagraph();
+
             const l = pars.length;
-            if (l) {
-                if (pars[l - 1]._t == '')
-                    text = text.trimLeft();
-                pars[l - 1]._t += text;
-            }
+            if (pars[l - 1]._t == '')
+                text = text.trimLeft();
+            pars[l - 1]._t += text;
 
             //посчитаем отступы у текста, чтобы выделить потом параграфы
             const lines = text.split('\n');
-            for (const line of lines) {
-                const sp = line.split(' ');
+            for (let line of lines) {
+                line = repSpaces2(line).replace(/\t/g, '    ');
+
                 let l = 0;
-                while (l < sp.length && sp[l].trim() == '') {
+                while (l < line.length && line[l] == ' ') {
                     l++;
                 }
                 if (!spaceCounter[l])
@@ -129,7 +135,6 @@ class BookConverter {
             }
         };
 
-        newParagraph();
         const newPara = new Set(['tr', 'br', 'br/', 'dd', 'p', 'title', '/title', 'h1', 'h2', 'h3', '/h1', '/h2', '/h3']);
 
         const onTextNode = (text, cutCounter, cutTag) => {// eslint-disable-line no-unused-vars
@@ -183,22 +188,28 @@ class BookConverter {
             };
 
             const growPar = (text) => {
+                if (!newPars.length)
+                    newPar();
+
                 const l = newPars.length;
-                if (l) {
-                    newPars[l - 1]._t += text;
-                }
+                newPars[l - 1]._t += text;
             }
 
+            i = 0;
             for (const par of pars) {
-                newPar();
+                if (i > 0)
+                    newPar();
+                i++;
 
                 const lines = par._t.split('\n');
-                for (const line of lines) {
-                    const sp = line.split(' ');
+                for (let line of lines) {
+                    line = repSpaces2(line).replace(/\t/g, '    ');
+
                     let l = 0;
-                    while (l < sp.length && sp[l].trim() == '') {
+                    while (l < line.length && line[l] == ' ') {
                         l++;
                     }
+
                     if (l >= parIndent)
                         newPar();
                     growPar(line.trim() + ' ');
@@ -227,6 +238,7 @@ class BookConverter {
         let inSubtitle = false;
         let inJustify = true;
         let inImage = false;
+        let isFirstPara = false;
         let path = '';
         let tag = '';// eslint-disable-line no-unused-vars
 
@@ -258,13 +270,17 @@ class BookConverter {
         };
 
         const growParagraph = (text) => {
+            if (!node._p) {
+                if (text.trim() != '')
+                    openTag('p');
+                else
+                    return;
+            }
             if (node._n == 'p' && node._a.length == 0)
                 text = text.trimLeft();
             node._a.push({_t: text});
         };
 
-        openTag('p');
-
         const onStartNode = (elemName, tail, singleTag, cutCounter, cutTag) => {// eslint-disable-line no-unused-vars
             if (elemName == '')
                 return;
@@ -272,18 +288,25 @@ class BookConverter {
                 path += '/' + elemName;
                 tag = elemName;
             } else {
-                if (inPara && elemName != 'i' && elemName != 'b' && elemName != 'em' && elemName != 'strong' && elemName != 'img')
-                    closeTag('p');
-
                 switch (elemName) {
                     case 'li':
                     case 'p':
                     case 'dd':
+                    case 'br':
+                        if (!(inSubtitle && isFirstPara)) {
+                            if (inPara)
+                                closeTag('p');
+                            openTag('p');
+                        }
+                        isFirstPara = false;
+                        break;
                     case 'h1':
                     case 'h2':
                     case 'h3':
-                    case 'br':
+                        if (inPara)
+                            closeTag('p');
                         openTag('p');
+                        bold = true;
                         break;
                     case 'i':
                     case 'em':
@@ -294,9 +317,12 @@ class BookConverter {
                         bold = true;
                         break;
                     case 'div':
+                        if (inPara)
+                            closeTag('p');
                         if (tail.indexOf('align="center"') >= 0) {
                             openTag('subtitle');
                             inSubtitle = true;
+                            isFirstPara = true;
                         }
 
                         if (tail.indexOf('align="justify"') >= 0) {
@@ -306,6 +332,8 @@ class BookConverter {
 
                         break;
                     case 'img': {
+                        if (inPara)
+                            closeTag('p');
                         const attrs = sax.getAttrsSync(tail);
                         if (attrs.src && attrs.src.value) {
                             let href = attrs.src.value;
@@ -341,10 +369,13 @@ class BookConverter {
                     case 'li':
                     case 'p':
                     case 'dd':
+                        closeTag('p');
+                        break;
                     case 'h1':
                     case 'h2':
                     case 'h3':
                         closeTag('p');
+                        bold = false;
                         break;
                     case 'i':
                     case 'em':
@@ -358,6 +389,7 @@ class BookConverter {
                         if (inSubtitle) {
                             closeTag('subtitle');
                             inSubtitle = false;
+                            isFirstPara = false;
                         }
 
                         if (inJustify) {
@@ -384,10 +416,10 @@ class BookConverter {
         };
 
         const onTextNode = (text) => {// eslint-disable-line no-unused-vars
-            if (text != ' ' && text.trim() == '')
-                text = text.trim();
+            if (text && text.trim() == '')
+                text = (text.indexOf(' ') >= 0 ? ' ' : '');
 
-            if (text == '')
+            if (!text)
                 return;
 
             switch (path) {
@@ -416,7 +448,7 @@ class BookConverter {
                 growParagraph(`${tOpen}${text}${tClose}`);
         };
 
-        sax.parseSync(repSpaces(repSpaces2(this.decode(data).toString())), {
+        sax.parseSync(repSpaces3(this.decode(data).toString()), {
             onStartNode, onEndNode, onTextNode, onComment,
             innerCut: new Set(['head', 'script', 'style'])
         });
@@ -474,21 +506,29 @@ class BookConverter {
                 }
             }
 
+            let tOpen = '';
+            let tBody = '';
+            let tClose = '';
             if (name)
-                out += `<${name}${attrs}>`;
+                tOpen += `<${name}${attrs}>`;
             if (node.hasOwnProperty('_t'))
-                out += repSpaces(node._t);
+                tBody += repSpaces(node._t);
 
             for (let nodeName in node) {
                 if (nodeName && nodeName[0] == '_' && nodeName != '_a')
                     continue;
 
                 const n = node[nodeName];
-                out += this.formatFb2Node(n, nodeName);
+                tBody += this.formatFb2Node(n, nodeName);
             }
             
             if (name)
-                out += `</${name}>`;
+                tClose += `</${name}>`;
+
+            if (attrs == '' && name == 'p' && tBody.trim() == '')
+                out += '<empty-line/>'
+            else
+                out += `${tOpen}${tBody}${tClose}`;
         }
         return out;
     }

+ 39 - 23
server/core/BookConverter/textUtils.js

@@ -1,4 +1,4 @@
-function getEncoding(buf) {
+function getEncoding(buf, returnAll) {
     const lowerCase = 3;
     const upperCase = 1;
 
@@ -8,6 +8,7 @@ function getEncoding(buf) {
         'd': 'cp866',
         'i': 'ISO-8859-5',
         'm': 'maccyrillic',
+        'u': 'utf-8',
     };
 
     let charsets = {
@@ -15,38 +16,47 @@ function getEncoding(buf) {
         'w': 0,
         'd': 0,
         'i': 0,
-        'm': 0
+        'm': 0,
+        'u': 0,
     };
 
     const len = buf.length;
     const blockSize = (len > 5*3000 ? 3000 : len);
     let counter = 0;
     let i = 0;
+    let totalChecked = 0;
     while (i < len) {
         const char = buf[i];
+        const nextChar = (i < len - 1 ? buf[i + 1] : 0);
+        totalChecked++;
         i++;
         //non-russian characters
         if (char < 128 || char > 256)
             continue;
-        //CP866
-        if ((char > 159 && char < 176) || (char > 223 && char < 242)) charsets['d'] += lowerCase;
-        if ((char > 127 && char < 160)) charsets['d'] += upperCase;
-
-        //KOI8-R
-        if ((char > 191 && char < 223)) charsets['k'] += lowerCase;
-        if ((char > 222 && char < 256)) charsets['k'] += upperCase;
-
-        //WIN-1251
-        if (char > 223 && char < 256) charsets['w'] += lowerCase;
-        if (char > 191 && char < 224) charsets['w'] += upperCase;
-
-        //MAC
-        if (char > 221 && char < 255) charsets['m'] += lowerCase;
-        if (char > 127 && char < 160) charsets['m'] += upperCase;
-
-        //ISO-8859-5
-        if (char > 207 && char < 240) charsets['i'] += lowerCase;
-        if (char > 175 && char < 208) charsets['i'] += upperCase;
+        //UTF-8
+        if ((char == 208 || char == 209) && nextChar >= 128 && nextChar <= 190)
+            charsets['u'] += lowerCase;
+        else {
+            //CP866
+            if ((char > 159 && char < 176) || (char > 223 && char < 242)) charsets['d'] += lowerCase;
+            if ((char > 127 && char < 160)) charsets['d'] += upperCase;
+
+            //KOI8-R
+            if ((char > 191 && char < 223)) charsets['k'] += lowerCase;
+            if ((char > 222 && char < 256)) charsets['k'] += upperCase;
+
+            //WIN-1251
+            if (char > 223 && char < 256) charsets['w'] += lowerCase;
+            if (char > 191 && char < 224) charsets['w'] += upperCase;
+
+            //MAC
+            if (char > 221 && char < 255) charsets['m'] += lowerCase;
+            if (char > 127 && char < 160) charsets['m'] += upperCase;
+
+            //ISO-8859-5
+            if (char > 207 && char < 240) charsets['i'] += lowerCase;
+            if (char > 175 && char < 208) charsets['i'] += upperCase;
+        }
 
         counter++;
 
@@ -57,18 +67,24 @@ function getEncoding(buf) {
     }
 
     let sorted = Object.keys(charsets).map(function(key) {
-        return { codePage: codePage[key], c: charsets[key] };
+        return { codePage: codePage[key], c: charsets[key], totalChecked };
     });
 
     sorted.sort((a, b) => b.c - a.c);
 
-    if (sorted[0].c > 0)
+    if (returnAll)
+        return sorted;
+    else if (sorted[0].c > 0)
         return sorted[0].codePage;
     else
         return 'ISO-8859-5';
 }
 
 function checkIfText(buf) {
+    const enc = getEncoding(buf, true);
+    if (enc[0].c > enc[0].totalChecked*0.9)
+        return true;
+
     let spaceCount = 0;
     let crCount = 0;
     let lfCount = 0;

+ 1 - 0
server/index.js

@@ -31,6 +31,7 @@ async function init() {
 }
 
 async function main() {
+    log(`${config.name} v${config.version}`);
     log('Initializing');
     await init();