diff --git a/client/components/Reader/HistoryPage/HistoryPage.vue b/client/components/Reader/HistoryPage/HistoryPage.vue index 619220f2..7cd98ebb 100644 --- a/client/components/Reader/HistoryPage/HistoryPage.vue +++ b/client/components/Reader/HistoryPage/HistoryPage.vue @@ -15,8 +15,14 @@ border :default-sort = "{prop: 'touchDateTime', order: 'descending'}" :header-cell-style = "headerCellStyle" + :row-key = "rowKey" > + + { + 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) { diff --git a/client/components/Reader/SettingsPage/SettingsPage.vue b/client/components/Reader/SettingsPage/SettingsPage.vue index e8e4090a..58a8ea2f 100644 --- a/client/components/Reader/SettingsPage/SettingsPage.vue +++ b/client/components/Reader/SettingsPage/SettingsPage.vue @@ -185,7 +185,7 @@ Перенос по слогам - Убирать пустые параграфы + Убирать пустые строки @@ -439,11 +439,12 @@ class SettingsPage extends Vue { '#000000', '#202020', '#ebe2c9', + '#cfdc99', + '#478355', + '#a6caf0', '#909080', '#808080', '#c8c8c8', - '#478355', - '#a6caf0', ]; } diff --git a/client/components/Reader/TextPage/DrawHelper.js b/client/components/Reader/TextPage/DrawHelper.js index 458927b1..de702ae4 100644 --- a/client/components/Reader/TextPage/DrawHelper.js +++ b/client/components/Reader/TextPage/DrawHelper.js @@ -28,7 +28,7 @@ export default class DrawHelper { let out = `
`; + ` 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 = `${text}`; 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}, diff --git a/client/components/Reader/share/BookParser.js b/client/components/Reader/share/BookParser.js index b4c24a9a..b5a990ee 100644 --- a/client/components/Reader/share/BookParser.js +++ b/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(``, 0); else newParagraph(`${' '.repeat(maxImageLineCount)}`, 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, '>'); text = text.replace(/= 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) { diff --git a/package.json b/package.json index 894c7552..912df4b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Liberama", - "version": "0.4.3", + "version": "0.4.4", "engines": { "node": ">=10.0.0" }, diff --git a/server/core/BookConverter/index.js b/server/core/BookConverter/index.js index 93865f30..657ed90b 100644 --- a/server/core/BookConverter/index.js +++ b/server/core/BookConverter/index.js @@ -10,6 +10,7 @@ const FileDetector = require('../FileDetector'); const repSpaces = (text) => text.replace(/ |[\t\n\r]/g, ' '); const repSpaces2 = (text) => text.replace(/[\n\r]/g, ''); +const repSpaces3 = (text) => text.replace(/ /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 += ``; + tClose += ``; + + if (attrs == '' && name == 'p' && tBody.trim() == '') + out += '' + else + out += `${tOpen}${tBody}${tClose}`; } return out; } diff --git a/server/core/BookConverter/textUtils.js b/server/core/BookConverter/textUtils.js index 475ef572..6ee5169d 100644 --- a/server/core/BookConverter/textUtils.js +++ b/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; + //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; + //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; + //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; + //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; + //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; diff --git a/server/index.js b/server/index.js index 1d8e79e9..47b6620a 100644 --- a/server/index.js +++ b/server/index.js @@ -31,6 +31,7 @@ async function init() { } async function main() { + log(`${config.name} v${config.version}`); log('Initializing'); await init();