Bläddra i källkod

Merge branch 'release/0.9.11-2'

Book Pauk 4 år sedan
förälder
incheckning
b3be07b17e

+ 93 - 1
client/components/Reader/ContentsPage/ContentsPage.vue

@@ -16,6 +16,7 @@
             class="no-mp bg-grey-4 text-grey-7"
         >
             <q-tab name="contents" icon="la la-list" label="Оглавление" />
+            <q-tab name="images" icon="la la-image" label="Изображения" />
             <q-tab name="bookmarks"  icon="la la-bookmark" label="Закладки" />
         </q-tabs>
     </div>
@@ -56,6 +57,31 @@
         </div>
     </div>
 
+    <div class="tab-panel" v-show="selectedTab == 'images'">
+        <div>
+            <div v-for="item in images" :key="item.key" class="column" style="width: 540px">
+                <div class="row item q-px-sm no-wrap">
+                    <div class="col row clickable" @click="setBookPos(item.offset)">
+                        <div class="image-thumb-box row justify-center items-center">
+                            <div v-show="!imageLoaded" class="image-thumb column justify-center"><i class="loading-img-icon la la-images"></i></div>
+                            <img v-show="imageLoaded" class="image-thumb" :src="imageSrc[item.imageId]"/>
+                        </div>
+                        <div class="no-expand-button column justify-center items-center">
+                            <div v-show="item.type == 'image/jpeg'" class="image-type it-jpg-color row justify-center">JPG</div>
+                            <div v-show="item.type == 'image/png'" class="image-type it-png-color row justify-center">PNG</div>
+                        </div>
+                        <div :style="item.indentStyle"></div>
+                        <div class="q-mr-sm col overflow-hidden column justify-center" :style="item.labelStyle" v-html="item.label"></div>
+                        <div class="column justify-center">{{ item.perc }}%</div>
+                    </div>
+                </div>
+            </div>
+            <div v-if="!images.length" class="column justify-center items-center" style="height: 100px">
+                Изображения отсутствуют
+            </div>
+        </div>
+    </div>
+
     <div class="tab-panel" v-show="selectedTab == 'bookmarks'">
         <div class="column justify-center items-center" style="height: 100px">
             Раздел находится в разработке
@@ -84,6 +110,9 @@ export default @Component({
 class ContentsPage extends Vue {
     selectedTab = 'contents';
     contents = [];
+    images = [];
+    imageSrc = [];
+    imageLoaded = false;
 
     created() {
     }
@@ -93,7 +122,7 @@ class ContentsPage extends Vue {
 
         //закладки
 
-        //далее формаирование оглавления
+        //далее формирование оглавления
         if (this.parsed == parsed)
             return;
 
@@ -166,6 +195,42 @@ class ContentsPage extends Vue {
         });
 
         this.contents = newContents;
+
+        //формируем newImages
+        const newImages = [];
+        const ims = parsed.images;
+        for (i = 0; i < ims.length; i++) {
+            const image = ims[i];
+            const bin = parsed.binary[image.id];
+            const type = (bin ? bin.type : '');
+            
+            const label = `Изображение ${image.num}`;
+            const indentStyle = getIndentStyle(1);
+            const labelStyle = getLabelStyle(0);
+
+            const p = parsed.para[image.paraIndex];
+            newImages.push({perc: (p.offset/parsed.textLength*100).toFixed(0), label, key: i, offset: p.offset,
+                indentStyle, labelStyle, type, imageId: image.id});
+        }
+
+        this.images = newImages;
+
+        if (this.selectedTab == 'contents' && !this.contents.length && this.images.length)
+            this.selectedTab = 'images';
+
+        //асинхронная загрузка изображений
+        this.imageSrc = [];
+        this.imageLoaded = false;
+        await utils.sleep(50);
+        (async() => {
+            for (i = 0; i < ims.length; i++) {
+                const id = ims[i].id;
+                const bin = this.parsed.binary[id];
+                this.$set(this.imageSrc, id, (bin ? `data:${bin.type};base64,${bin.data}` : ''));
+                await utils.sleep(5);
+            }
+            this.imageLoaded = true;
+        })();
     }
 
     async expandClick(key) {
@@ -244,4 +309,31 @@ class ContentsPage extends Vue {
 .expanded-icon {
     transform: rotate(90deg);
 }
+
+.image-type {
+    border: 1px solid black;
+    border-radius: 6px;
+    font-size: 80%;
+    padding: 2px 0 2px 0;
+    width: 34px;
+}
+.it-jpg-color {
+    background: linear-gradient(to right, #fabc3d, #ffec6d);
+}
+.it-png-color {
+    background: linear-gradient(to right, #4bc4e5, #6bf4ff);
+}
+
+.image-thumb-box {
+    width: 120px;
+    overflow: hidden;
+}
+
+.image-thumb {
+    height: 50px;
+}
+
+.loading-img-icon {
+    font-size: 250%;
+}
 </style>

+ 5 - 4
client/components/Reader/TextPage/DrawHelper.js

@@ -160,12 +160,13 @@ export default class DrawHelper {
         return out;
     }
 
-    drawPercentBar(x, y, w, h, font, fontSize, bookPos, textLength) {
+    drawPercentBar(x, y, w, h, font, fontSize, bookPos, textLength, imageNum, imageLength) {
         const pad = 3;
         const fh = h - 2*pad;
         const fh2 = fh/2;
 
-        const t1 = `${Math.floor((bookPos + 1)/1000)}/${Math.floor(textLength/1000)}`;
+        const tImg = (imageNum > 0 ? ` (${imageNum}/${imageLength})` : '');
+        const t1 = `${Math.floor((bookPos + 1)/1000)}/${Math.floor(textLength/1000)}${tImg}`;
         const w1 = this.measureTextFont(t1, font) + fh2;
         const read = (bookPos + 1)/textLength;
         const t2 = `${(read*100).toFixed(2)}%`;
@@ -188,7 +189,7 @@ export default class DrawHelper {
         return out;
     }
 
-    drawStatusBar(statusBarTop, statusBarHeight, bookPos, textLength, title) {
+    drawStatusBar(statusBarTop, statusBarHeight, bookPos, textLength, title, imageNum, imageLength) {
 
         let out = `<div class="layout" style="` + 
             `width: ${this.realWidth}px; height: ${statusBarHeight}px; ` + 
@@ -206,7 +207,7 @@ export default class DrawHelper {
 
         out += this.fillTextShift(this.fittingString(title, this.realWidth/2 - fontSize - 3, font), fontSize, 2, font, fontSize);
 
-        out += this.drawPercentBar(this.realWidth/2, 2, this.realWidth/2 - timeW - 2*fontSize, statusBarHeight, font, fontSize, bookPos, textLength);
+        out += this.drawPercentBar(this.realWidth/2, 2, this.realWidth/2 - timeW - 2*fontSize, statusBarHeight, font, fontSize, bookPos, textLength, imageNum, imageLength);
         
         out += '</div>';
         return out;

+ 17 - 1
client/components/Reader/TextPage/TextPage.vue

@@ -722,8 +722,24 @@ class TextPage extends Vue {
                     message = this.statusBarMessage;
                 if (!message)
                     message = this.title;
+
+                //check image num
+                let imageNum = 0;
+                const len = (lines.length > 2 ? 2 : lines.length);
+                loop:
+                for (let j = 0; j < len; j++) {
+                    const line = lines[j];
+                    for (const part of line.parts) {
+                        if (part.image) {
+                            imageNum = part.image.num;
+                            break loop;
+                        }
+                    }
+                }
+                //drawing
                 this.statusBar = this.drawHelper.drawStatusBar(this.statusBarTop, this.statusBarHeight, 
-                    lines[i].end, this.parsed.textLength, message);
+                    lines[i].end, this.parsed.textLength, message, imageNum, this.parsed.images.length);
+
                 this.bookPosSeen = lines[i].end;
             }
         } else {

+ 34 - 19
client/components/Reader/share/BookParser.js

@@ -54,12 +54,14 @@ export default class BookParser {
 
         //оглавление
         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;
@@ -202,16 +204,26 @@ export default class BookParser {
                 let attrs = sax.getAttrsSync(tail);
                 if (attrs.href && attrs.href.value) {
                     const href = attrs.href.value;
+                    const {id} = this.imageHrefToId(href);
                     if (href[0] == '#') {//local
+                        imageNum++;
+
                         if (inPara && !this.showInlineImagesInCenter && !center)
-                            growParagraph(`<image-inline href="${href}"></image-inline>`, 0);
+                            growParagraph(`<image-inline href="${href}" num="${imageNum}"></image-inline>`, 0);
                         else
-                            newParagraph(`<image href="${href}">${' '.repeat(maxImageLineCount)}</image>`, maxImageLineCount);
+                            newParagraph(`<image href="${href}" num="${imageNum}">${' '.repeat(maxImageLineCount)}</image>`, maxImageLineCount);
+
+                        this.images.push({paraIndex, num: imageNum, id});
+
                         if (inPara && this.showInlineImagesInCenter)
                             newParagraph(' ', 1);
                     } else {//external
+                        imageNum++;
+
                         dimPromises.push(getExternalImageDimensions(href));
-                        newParagraph(`<image href="${href}">${' '.repeat(maxImageLineCount)}</image>`, maxImageLineCount);
+                        newParagraph(`<image href="${href}" num="${imageNum}">${' '.repeat(maxImageLineCount)}</image>`, maxImageLineCount);
+
+                        this.images.push({paraIndex, num: imageNum, id});
                     }
                 }
             }
@@ -488,6 +500,15 @@ export default class BookParser {
         return {fb2};
     }
 
+    imageHrefToId(id) {
+        let local = false;
+        if (id[0] == '#') {
+            id = id.substr(1);
+            local = true;
+        }
+        return {id, local};
+    }
+
     findParaIndex(bookPos) {
         let result = undefined;
         //дихотомия
@@ -553,28 +574,21 @@ export default class BookParser {
                 case 'image': {
                     let attrs = sax.getAttrsSync(tail);
                     if (attrs.href && attrs.href.value) {
-                        let id = attrs.href.value;
-                        let local = false;
-                        if (id[0] == '#') {
-                            id = id.substr(1);
-                            local = true;
-                        }
-                        image = {local, inline: false, id};
+                        image = this.imageHrefToId(attrs.href.value);
+                        image.inline = false;
+                        image.num = (attrs.num && attrs.num.value ? attrs.num.value : 0);
                     }
                     break;
                 }
                 case 'image-inline': {
                     let attrs = sax.getAttrsSync(tail);
                     if (attrs.href && attrs.href.value) {
-                        let id = attrs.href.value;
-                        let local = false;
-                        if (id[0] == '#') {
-                            id = id.substr(1);
-                            local = true;
-                        }
+                        const img = this.imageHrefToId(attrs.href.value);
+                        img.inline = true;
+                        img.num = (attrs.num && attrs.num.value ? attrs.num.value : 0);
                         result.push({
                             style: Object.assign({}, style),
-                            image: {local, inline: true, id},
+                            image: img,
                             text: ''
                         });
                     }
@@ -801,6 +815,7 @@ export default class BookParser {
                         paraIndex,
                         w: imageWidth,
                         h: imageHeight,
+                        num: part.image.num
                     }});
                     lines.push(line);
                     line = {begin: line.end + 1, parts: []};
@@ -811,7 +826,7 @@ export default class BookParser {
                 line.last = true;
                 line.parts.push({style, text: ' ',
                     image: {local: part.image.local, inline: false, id: part.image.id,
-                        imageLine: i, lineCount, paraIndex, w: imageWidth, h: imageHeight}
+                        imageLine: i, lineCount, paraIndex, w: imageWidth, h: imageHeight, num: part.image.num}
                 });
                 
                 continue;
@@ -823,7 +838,7 @@ export default class BookParser {
                     let imgH = (bin.h > this.fontSize ? this.fontSize : bin.h);
                     imgW += bin.w*imgH/bin.h;
                     line.parts.push({style, text: '',
-                        image: {local: part.image.local, inline: true, id: part.image.id}});
+                        image: {local: part.image.local, inline: true, id: part.image.id, num: part.image.num}});
                 }
             }
 

+ 5 - 0
server/core/Reader/BookConverter/ConvertBase.js

@@ -103,6 +103,11 @@ class ConvertBase {
         return he.escape(he.decode(text.replace(/&nbsp;/g, ' ')));
     }
 
+    isDataXml(data) {
+        const str = data.toString().trim();
+        return (str.indexOf('<?xml version="1.0"') == 0 || str.indexOf('<?xml version=\'1.0\'') == 0 );
+    }
+
     formatFb2(fb2) {
         const out = xmlParser.formatXml({
             FictionBook: {

+ 4 - 1
server/core/Reader/BookConverter/ConvertFb2.js

@@ -6,7 +6,10 @@ class ConvertFb2 extends ConvertBase {
     check(data, opts) {
         const {dataType} = opts;
 
-        return (dataType && dataType.ext == 'xml' && data.toString().indexOf('<FictionBook') >= 0);
+        return (
+            ( (dataType && dataType.ext == 'xml') || this.isDataXml(data) ) && 
+            data.toString().indexOf('<FictionBook') >= 0
+        );
     }
 
     async run(data, opts) {

+ 1 - 1
server/core/Reader/BookConverter/ConvertHtml.js

@@ -7,7 +7,7 @@ class ConvertHtml extends ConvertBase {
         const {dataType} = opts;
 
         //html?
-        if (dataType && (dataType.ext == 'html' || dataType.ext == 'xml')) 
+        if ( ( (dataType && (dataType.ext == 'html' || dataType.ext == 'xml')) ) || this.isDataXml(data) )
             return {isText: false};
 
         //может это чистый текст?