|
@@ -1,5 +1,10 @@
|
|
|
const path = require('path');
|
|
|
+const _ = require('lodash');
|
|
|
+const he = require('he');
|
|
|
+const dayjs = require('dayjs');
|
|
|
+
|
|
|
const BasePage = require('./BasePage');
|
|
|
+const Fb2Parser = require('../fb2/Fb2Parser');
|
|
|
|
|
|
class BookPage extends BasePage {
|
|
|
constructor(config) {
|
|
@@ -7,24 +12,181 @@ class BookPage extends BasePage {
|
|
|
|
|
|
this.id = 'book';
|
|
|
this.title = 'Книга';
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ formatSize(size) {
|
|
|
+ size = size/1024;
|
|
|
+ let unit = 'KB';
|
|
|
+ if (size > 1024) {
|
|
|
+ size = size/1024;
|
|
|
+ unit = 'MB';
|
|
|
+ }
|
|
|
+ return `${size.toFixed(1)} ${unit}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ inpxInfo(bookRec) {
|
|
|
+ const mapping = [
|
|
|
+ {name: 'fileInfo', label: 'Информация о файле', value: [
|
|
|
+ {name: 'folder', label: 'Папка'},
|
|
|
+ {name: 'file', label: 'Файл'},
|
|
|
+ {name: 'size', label: 'Размер'},
|
|
|
+ {name: 'date', label: 'Добавлен'},
|
|
|
+ {name: 'del', label: 'Удален'},
|
|
|
+ {name: 'libid', label: 'LibId'},
|
|
|
+ {name: 'insno', label: 'InsideNo'},
|
|
|
+ ]},
|
|
|
+
|
|
|
+ {name: 'titleInfo', label: 'Общая информация', value: [
|
|
|
+ {name: 'author', label: 'Автор(ы)'},
|
|
|
+ {name: 'title', label: 'Название'},
|
|
|
+ {name: 'series', label: 'Серия'},
|
|
|
+ {name: 'genre', label: 'Жанр'},
|
|
|
+ {name: 'librate', label: 'Оценка'},
|
|
|
+ {name: 'lang', label: 'Язык книги'},
|
|
|
+ {name: 'keywords', label: 'Ключевые слова'},
|
|
|
+ ]},
|
|
|
+ ];
|
|
|
+
|
|
|
+ const valueToString = (value, nodePath, b) => {//eslint-disable-line no-unused-vars
|
|
|
+ if (nodePath == 'fileInfo/file')
|
|
|
+ return `${value}.${b.ext}`;
|
|
|
+
|
|
|
+ if (nodePath == 'fileInfo/size')
|
|
|
+ return `${this.formatSize(value)} (${value.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1 ')} Bytes)`;
|
|
|
+
|
|
|
+ if (nodePath == 'fileInfo/date')
|
|
|
+ return dayjs(value, 'YYYY-MM-DD').format('DD.MM.YYYY');
|
|
|
+
|
|
|
+ if (nodePath == 'fileInfo/del')
|
|
|
+ return (value ? 'Да' : null);
|
|
|
+
|
|
|
+ if (nodePath == 'fileInfo/insno')
|
|
|
+ return (value ? value : null);
|
|
|
+
|
|
|
+ if (nodePath == 'titleInfo/author')
|
|
|
+ return value.split(',').join(', ');
|
|
|
+
|
|
|
+ if (nodePath == 'titleInfo/librate' && !value)
|
|
|
+ return null;
|
|
|
+
|
|
|
+ if (typeof(value) === 'string') {
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+
|
|
|
+ return (value.toString ? value.toString() : '');
|
|
|
+ };
|
|
|
+
|
|
|
+ let result = [];
|
|
|
+ const book = _.cloneDeep(bookRec);
|
|
|
+ book.series = [book.series, book.serno].filter(v => v).join(' #');
|
|
|
+
|
|
|
+ for (const item of mapping) {
|
|
|
+ const itemOut = {name: item.name, label: item.label, value: []};
|
|
|
+
|
|
|
+ for (const subItem of item.value) {
|
|
|
+ const subItemOut = {
|
|
|
+ name: subItem.name,
|
|
|
+ label: subItem.label,
|
|
|
+ value: valueToString(book[subItem.name], `${item.name}/${subItem.name}`, book)
|
|
|
+ };
|
|
|
+ if (subItemOut.value)
|
|
|
+ itemOut.value.push(subItemOut);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (itemOut.value.length)
|
|
|
+ result.push(itemOut);
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ htmlInfo(title, infoList) {
|
|
|
+ let info = '';
|
|
|
+ for (const part of infoList) {
|
|
|
+ if (part.value.length)
|
|
|
+ info += `<h3>${part.label}</h3>`;
|
|
|
+ for (const rec of part.value)
|
|
|
+ info += `<p>${rec.label}: ${rec.value}</p>`;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (info)
|
|
|
+ info = `<h2>${title}</h2>${info}`;
|
|
|
+
|
|
|
+ return info;
|
|
|
}
|
|
|
|
|
|
async body(req) {
|
|
|
const result = {};
|
|
|
|
|
|
+ result.link = [
|
|
|
+ this.navLink({rel: 'start'}),
|
|
|
+ this.acqLink({rel: 'self', href: req.originalUrl, hrefAsIs: true}),
|
|
|
+ ];
|
|
|
+
|
|
|
const bookUid = req.query.uid;
|
|
|
const entry = [];
|
|
|
- if (bookUid) {
|
|
|
+ if (bookUid) {
|
|
|
const {bookInfo} = await this.webWorker.getBookInfo(bookUid);
|
|
|
+
|
|
|
if (bookInfo) {
|
|
|
+ const {genreMap} = await this.getGenres();
|
|
|
+ const fileFormat = `${bookInfo.book.ext}+zip`;
|
|
|
+
|
|
|
+ //entry
|
|
|
const e = this.makeEntry({
|
|
|
id: bookUid,
|
|
|
title: bookInfo.book.title || 'Без названия',
|
|
|
- link: [
|
|
|
- this.downLink({href: bookInfo.link, type: `application/${bookInfo.book.ext}+zip`}),
|
|
|
- ],
|
|
|
});
|
|
|
|
|
|
+ e['dc:language'] = bookInfo.book.lang;
|
|
|
+ e['dc:format'] = fileFormat;
|
|
|
+
|
|
|
+ //genre
|
|
|
+ const genre = bookInfo.book.genre.split(',');
|
|
|
+ for (const g of genre) {
|
|
|
+ const genreName = genreMap.get(g);
|
|
|
+ if (genreName) {
|
|
|
+ if (!e.category)
|
|
|
+ e.category = [];
|
|
|
+ e.category.push({
|
|
|
+ '*ATTRS': {term: genreName, label: genreName},
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ let content = '';
|
|
|
+ let ann = '';
|
|
|
+ let info = '';
|
|
|
+ //fb2 info
|
|
|
+ if (bookInfo.fb2) {
|
|
|
+ const parser = new Fb2Parser(bookInfo.fb2);
|
|
|
+ const infoObj = parser.bookInfo();
|
|
|
+
|
|
|
+ if (infoObj.titleInfo) {
|
|
|
+ if (infoObj.titleInfo.author.length) {
|
|
|
+ e.author = infoObj.titleInfo.author.map(a => ({name: a}));
|
|
|
+ }
|
|
|
+
|
|
|
+ ann = infoObj.titleInfo.annotationHtml || '';
|
|
|
+ const infoList = parser.bookInfoList(infoObj);
|
|
|
+ info += this.htmlInfo('Fb2 инфо', infoList);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //content
|
|
|
+ info += this.htmlInfo('Inpx инфо', this.inpxInfo(bookInfo.book));
|
|
|
+
|
|
|
+ content = `${ann}${info}`;
|
|
|
+ if (content) {
|
|
|
+ e.content = {
|
|
|
+ '*ATTRS': {type: 'text/html'},
|
|
|
+ '*TEXT': he.escape(content),
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ //links
|
|
|
+ e.link = [ this.downLink({href: bookInfo.link, type: `application/${fileFormat}`}) ];
|
|
|
if (bookInfo.cover) {
|
|
|
let coverType = 'image/jpeg';
|
|
|
if (path.extname(bookInfo.cover) == '.png')
|
|
@@ -39,6 +201,7 @@ class BookPage extends BasePage {
|
|
|
}
|
|
|
|
|
|
result.entry = entry;
|
|
|
+
|
|
|
return this.makeBody(result, req);
|
|
|
}
|
|
|
}
|