From 0430105061f95cd704e706556e463a7225f1e4a1 Mon Sep 17 00:00:00 2001 From: Book Pauk Date: Sun, 28 Jul 2024 17:23:16 +0700 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=BE=D1=82=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BF=D1=80=D0=B8=D0=BC=D0=B5=D1=87=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B9=20=D0=BD=D0=B0=20=D0=BC=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D0=B5,=20=D0=BF=D0=BE=20=D0=BA=D0=BB=D0=B8=D0=BA=D1=83=20?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BF=D1=80=D0=B8=D0=BC=D0=B5=D1=87=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B8=20(#50)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/Reader/TextPage/DrawHelper.js | 9 ++ .../components/Reader/TextPage/TextPage.vue | 98 +++++++++++++++++-- client/components/Reader/share/BookParser.js | 96 ++++++++++++++++-- 3 files changed, 187 insertions(+), 16 deletions(-) diff --git a/client/components/Reader/TextPage/DrawHelper.js b/client/components/Reader/TextPage/DrawHelper.js index 2bcf2583..f3bf9e77 100644 --- a/client/components/Reader/TextPage/DrawHelper.js +++ b/client/components/Reader/TextPage/DrawHelper.js @@ -39,6 +39,7 @@ export default class DrawHelper { let center = false; let space = 0; let j = 0; + const pad = this.fontSize/2; //формируем строку for (const part of line.parts) { let tOpen = ''; @@ -46,7 +47,12 @@ export default class DrawHelper { tOpen += (part.style.italic ? '' : ''); tOpen += (part.style.sup ? '' : ''); tOpen += (part.style.sub ? '' : ''); + tOpen += (part.style.note ? `` + + `__TEXT` : ''); let tClose = ''; + tClose += (part.style.note ? '' : ''); tClose += (part.style.sub ? '' : ''); tClose += (part.style.sup ? '' : ''); tClose += (part.style.italic ? '' : ''); @@ -64,6 +70,9 @@ export default class DrawHelper { if (text && text.trim() == '') text = `${text}`; + if (part.style.note) + tOpen = tOpen.replace('__TEXT', text); + lineText += `${tOpen}${text}${tClose}`; center = center || part.style.center; diff --git a/client/components/Reader/TextPage/TextPage.vue b/client/components/Reader/TextPage/TextPage.vue index 5568a501..fd393296 100644 --- a/client/components/Reader/TextPage/TextPage.vue +++ b/client/components/Reader/TextPage/TextPage.vue @@ -4,12 +4,12 @@
-
+
-
+
@@ -24,14 +24,9 @@ @wheel.prevent.stop="onMouseWheel" @touchstart.stop="onTouchStart" @touchend.stop="onTouchEnd" @touchmove.stop="onTouchMove" @touchcancel.prevent.stop="onTouchCancel" > -
+ + + + + +
+
+
+ + +
@@ -51,6 +69,7 @@ import {loadCSS} from 'fg-loadcss'; import _ from 'lodash'; import he from 'he'; +import Dialog from '../../share/Dialog.vue'; import './TextPage.css'; import * as utils from '../../../share/utils'; @@ -62,7 +81,19 @@ import {clickMap} from '../share/clickMap'; const minLayoutWidth = 100; +//обработчик кликов по примечаниям, см. DrawHelper +//коряво, но иначе придется сильно усложнять рендеринг страниц (через Vue) +window.onNoteClickLiberama = (noteId, orig) => { + const textPage = window.textPageLiberama; + if (textPage) { + textPage.showNote(noteId, orig); + } +} + const componentOptions = { + components: { + Dialog + }, watch: { bookPos: function() { this.$emit('book-pos-changed', {bookPos: this.bookPos, bookPosSeen: this.bookPosSeen}); @@ -90,6 +121,7 @@ class TextPage { _options = componentOptions; showStatusBar = false; + statusBarClickOpen = false; clickControl = true; background = null; @@ -114,6 +146,10 @@ class TextPage { meta = null; + noteDialogVisible = false; + noteId = ''; + noteHtml = ''; + created() { this.drawHelper = new DrawHelper(); @@ -153,6 +189,8 @@ class TextPage { await utils.sleep(200); this.$nextTick(this.onResize); }); + + window.textPageLiberama = this; } mounted() { @@ -297,6 +335,8 @@ class TextPage { top += this.statusBarHeight*(this.statusBarTop ? 1 : 0); let page1 = this.$refs.scrollBox1.style; let page2 = this.$refs.scrollBox2.style; + + page1.pointerEvents = page2.pointerEvents = (this.clickControl ? 'none' : 'auto'); page1.perspective = page2.perspective = '3072px'; @@ -1209,6 +1249,46 @@ class TextPage { event.clipboardData.setData('text/plain', filtered); } + + showNote(noteId, orig) { + const note = this.parsed.notes[noteId]; + if (note) { + if (orig) {//show dialog + this.noteId = noteId; + const pad = (note.para.length > 1 ? 20 : 0); + this.noteHtml = note.para.map(p => `

${p}

`).join(''); + this.noteDialogVisible = true; + } else {//go to orig + this.goToOrigNote(noteId); + } + } + } + + goToNotes() { + const note = this.parsed.notes[this.noteId]; + if (note && note.noteParaIndex >= 0) { + + const para = this.parsed.parsePara(note.noteParaIndex); + + this.userBookPosChange = true; + this.bookPos = para.lines[0].begin; + + this.noteDialogVisible = false; + } + } + + goToOrigNote(noteId) { + const note = this.parsed.notes[noteId]; + if (note && note.noteParaIndex >= 0) { + + const para = this.parsed.parsePara(note.linkParaIndex); + + this.userBookPosChange = true; + this.bookPos = para.lines[0].begin; + + this.noteDialogVisible = false; + } + } } export default vueComponent(TextPage); @@ -1244,7 +1324,7 @@ export default vueComponent(TextPage); } .events { - z-index: 20; + z-index: 9; background-color: rgba(0,0,0,0); } diff --git a/client/components/Reader/share/BookParser.js b/client/components/Reader/share/BookParser.js index cfceea3e..54f6236d 100644 --- a/client/components/Reader/share/BookParser.js +++ b/client/components/Reader/share/BookParser.js @@ -86,17 +86,23 @@ export default class BookParser { let binaryType = ''; let dimPromises = []; this.coverPageId = ''; + this.images = []; + let imageNum = 0; + + //примечания + this.notes = {}; + let inNote = false; + let noteId = ''; + let inNotesBody = false; //оглавление this.contents = []; - this.images = []; let curTitle = {paraIndex: -1, title: '', subtitles: []}; let curSubtitle = {paraIndex: -1, title: ''}; let inTitle = false; let inSubtitle = false; let sectionLevel = 0; let bodyIndex = 0; - let imageNum = 0; let paraIndex = -1; let paraOffset = 0; @@ -289,7 +295,7 @@ export default class BookParser { if (attrs.href && attrs.href.value) { const href = attrs.href.value; const alt = (attrs.alt && attrs.alt.value ? attrs.alt.value : ''); - const {id, local} = this.imageHrefToId(href); + const {id, local} = this.hrefToId(href); if (local) {//local imageNum++; @@ -322,6 +328,23 @@ export default class BookParser { } } + if (tag == 'a') { + let attrs = sax.getAttrsSync(tail); + if (attrs.href && attrs.href.value && attrs.type && attrs.type.value === 'note') {//note + const href = attrs.href.value; + const {id, local} = this.hrefToId(href); + + if (local) { + inNote = true; + growParagraph(``, 0); + + if (!this.notes[id]) { + this.notes[id] = {id, linkParaIndex: paraIndex}; + } + } + } + } + if (path == '/fictionbook/description/title-info/author') { if (!fb2.author) fb2.author = []; @@ -350,6 +373,11 @@ export default class BookParser { if (path.indexOf('/fictionbook/body') == 0) { if (tag == 'body') { + let attrs = sax.getAttrsSync(tail); + if (attrs.name && attrs.name.value === 'notes') {//notes + inNotesBody = true; + } + if (isFirstBody && fb2.annotation) { const ann = fb2.annotation.split('

').filter(v => v).map(v => utils.removeHtmlTags(v)); ann.forEach(a => { @@ -389,6 +417,23 @@ export default class BookParser { newParagraph(); isFirstSection = false; sectionLevel++; + + if (inNotesBody) { + let attrs = sax.getAttrsSync(tail); + if (attrs.id && attrs.id.value) {//notes + const id = attrs.id.value; + let note = this.notes[id]; + if (!note) { + note = {id}; + this.notes[id] = note; + } + + note.noteParaIndex = paraIndex; + note.para = []; + noteId = id; + } + + } } if (tag == 'emphasis' || tag == 'strong' || tag == 'sup' || tag == 'sub') { @@ -401,6 +446,14 @@ export default class BookParser { if (tag == 'p') { inPara = true; isFirstTitlePara = false; + + if (inNotesBody && noteId) { + if (!inTitle) { + this.notes[noteId].para.push(''); + } else { + growParagraph(``, 0); + } + } } } @@ -440,11 +493,20 @@ export default class BookParser { const onEndNode = (elemName) => {// eslint-disable-line no-unused-vars tag = elemName; + if (tag == 'a' && inNote) { + growParagraph('', 0); + inNote = false; + } + if (tag == 'binary') { binaryId = ''; } if (path.indexOf('/fictionbook/body') == 0) { + if (tag == 'body') { + inNotesBody = false; + } + if (tag == 'title') { isFirstTitlePara = false; bold = false; @@ -462,6 +524,10 @@ export default class BookParser { if (tag == 'p') { inPara = false; + + if (inTitle && inNotesBody && noteId) { + growParagraph('', 0); + } } if (tag == 'subtitle') { @@ -570,6 +636,12 @@ export default class BookParser { growParagraph(`${tOpen}${text}${tClose}`, text.length, text); else growParagraph(' ', 1); + + if (!inTitle && inPara && inNotesBody && noteId) { + const p = this.notes[noteId].para; + if (p.length) + p[p.length - 1] = p[p.length - 1] + text; + } } }; @@ -602,7 +674,7 @@ export default class BookParser { return {fb2}; } - imageHrefToId(id) { + hrefToId(id) { let local = false; if (id[0] == '#') { id = id.substr(1); @@ -635,7 +707,7 @@ export default class BookParser { splitToStyle(s) { let result = [];/*array of { - style: {bold: Boolean, italic: Boolean, sup: Boolean, sub: Boolean, center: Boolean, space: Number}, + style: {bold: Boolean, italic: Boolean, sup: Boolean, sub: Boolean, center: Boolean, space: Number, note: Object}, image: {local: Boolean, inline: Boolean, id: String}, text: String, }*/ @@ -686,7 +758,7 @@ export default class BookParser { case 'image': { let attrs = sax.getAttrsSync(tail); if (attrs.href && attrs.href.value) { - image = this.imageHrefToId(attrs.href.value); + image = this.hrefToId(attrs.href.value); image.inline = false; image.num = (attrs.num && attrs.num.value ? attrs.num.value : 0); } @@ -695,7 +767,7 @@ export default class BookParser { case 'image-inline': { let attrs = sax.getAttrsSync(tail); if (attrs.href && attrs.href.value) { - const img = this.imageHrefToId(attrs.href.value); + const img = this.hrefToId(attrs.href.value); img.inline = true; img.num = (attrs.num && attrs.num.value ? attrs.num.value : 0); result.push({ @@ -706,6 +778,13 @@ export default class BookParser { } break; } + case 'note': { + let attrs = sax.getAttrsSync(tail); + if (attrs.href && attrs.href.value) { + style.note = {id: attrs.href.value, orig: attrs.orig?.value}; + } + break; + } } }; @@ -734,6 +813,9 @@ export default class BookParser { break; case 'image-inline': break; + case 'note': + style.note = false; + break; } };