Compare commits

...

9 Commits
0.9.7 ... 0.9.8

Author SHA1 Message Date
Book Pauk
b387f4a0db Merge branch 'release/0.9.8' 2020-11-14 00:05:07 +07:00
Book Pauk
1a096031c4 Версия 0.9.8 2020-11-14 00:04:30 +07:00
Book Pauk
83a60b4091 Небольшие поправки парсера 2020-11-13 23:59:55 +07:00
Book Pauk
b292407ec2 Работа над ContentsPage 2020-11-13 23:33:36 +07:00
Book Pauk
952c337b76 Работа над ContentsPage 2020-11-13 22:45:34 +07:00
Book Pauk
e947b887fe Работа над ContentsPage 2020-11-13 19:10:15 +07:00
Book Pauk
bd1e5485d7 Работа над ContentsPage 2020-11-13 18:47:11 +07:00
Book Pauk
e095c3318b Начало работы над ContentsPage 2020-11-13 14:23:55 +07:00
Book Pauk
d75a08b519 Merge tag '0.9.7' into develop
0.9.7
2020-11-12 19:44:15 +07:00
8 changed files with 314 additions and 9 deletions

View File

@@ -0,0 +1,207 @@
<template>
<Window width="600px" ref="window" @close="close">
<template slot="header">
Оглавление/закладки
</template>
<div class="bg-grey-3 row">
<q-tabs
v-model="selectedTab"
active-color="black"
active-bg-color="white"
indicator-color="white"
dense
no-caps
inline-label
class="no-mp bg-grey-4 text-grey-7"
>
<q-tab name="contents" icon="la la-list" label="Оглавление" />
<q-tab name="bookmarks" icon="la la-bookmark" label="Закладки" />
</q-tabs>
</div>
<div class="q-mb-sm"/>
<div class="tab-panel" v-show="selectedTab == 'contents'">
<div>
<div class="row" v-for="item in contents" :key="item.key">
<q-expansion-item v-if="item.list.length"
class="item separator-bottom"
expand-icon-toggle
switch-toggle-side
expand-icon="la la-arrow-circle-down"
>
<template slot="header">
<div class="row no-wrap clickable" style="width: 465px" @click="setBookPos(item.offset)">
<div :style="item.style"></div>
<div class="q-mr-sm col overflow-hidden column justify-center" v-html="item.label"></div>
<div class="column justify-center">{{ item.perc }}%</div>
</div>
</template>
<q-item class="subitem separator-top column justify-center" v-for="subitem in item.list" :key="subitem.key">
<div class="row no-wrap clickable" style="padding-left: 55px; width: 520px" @click="setBookPos(subitem.offset)">
<div :style="subitem.style"></div>
<div class="q-mr-sm col overflow-hidden column justify-center" v-html="subitem.label"></div>
<div class="column justify-center">{{ subitem.perc }}%</div>
</div>
</q-item>
</q-expansion-item>
<q-item v-else class="item separator-bottom">
<div class="row no-wrap clickable" style="padding-left: 55px; width: 520px" @click="setBookPos(item.offset)">
<div :style="item.style"></div>
<div class="q-mr-sm col overflow-hidden column justify-center" v-html="item.label"></div>
<div class="column justify-center">{{ item.perc }}%</div>
</div>
</q-item>
</div>
</div>
</div>
<div class="tab-panel" v-show="selectedTab == 'bookmarks'">
<div class="column justify-center items-center" style="height: 100px">
Раздел находится в разработке
</div>
</div>
</Window>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
//import _ from 'lodash';
import Window from '../../share/Window.vue';
//import * as utils from '../../../share/utils';
export default @Component({
components: {
Window,
},
watch: {
},
})
class ContentsPage extends Vue {
selectedTab = 'contents';
contents = [];
created() {
}
async init(currentBook, parsed) {
this.$refs.window.init();
if (this.parsed != parsed) {
this.contents = [];
await this.$nextTick();
this.parsed = parsed;
}
const prepareLabel = (title, bolder = false) => {
let titleParts = title.split('<p>');
const textParts = titleParts.filter(v => v).map(v => `<div>${v.replace(/(<([^>]+)>)/ig, '')}</div>`);
if (bolder && textParts.length > 1)
textParts[0] = `<b>${textParts[0]}</b>`;
return textParts.join('');
}
const insetStyle = inset => `width: ${inset*20}px`;
const pc = parsed.contents;
const newpc = [];
//преобразуем не первые разделы body в title-subtitle
let curSubtitles = [];
let prevBodyIndex = -1;
for (let i = 0; i < pc.length; i++) {
const cont = pc[i];
if (prevBodyIndex != cont.bodyIndex)
curSubtitles = [];
prevBodyIndex = cont.bodyIndex;
if (cont.bodyIndex > 1) {
if (cont.inset < 1) {
newpc.push(Object.assign({}, cont, {subtitles: curSubtitles}));
} else {
curSubtitles.push(Object.assign({}, cont, {inset: cont.inset - 1}));
}
} else {
newpc.push(cont);
}
}
//формируем newContents
let i = 0;
const newContents = [];
newpc.forEach((cont) => {
const label = prepareLabel(cont.title, true);
const style = insetStyle(cont.inset);
let j = 0;
const list = [];
cont.subtitles.forEach((sub) => {
const l = prepareLabel(sub.title);
const s = insetStyle(sub.inset + 1);
const p = parsed.para[sub.paraIndex];
list.push({perc: (p.offset/parsed.textLength*100).toFixed(2), label: l, key: j, offset: p.offset, style: s});
j++;
});
const p = parsed.para[cont.paraIndex];
newContents.push({perc: (p.offset/parsed.textLength*100).toFixed(0), label, key: i, offset: p.offset, style, list});
i++;
});
this.contents = newContents;
}
async setBookPos(newValue) {
this.$emit('book-pos-changed', {bookPos: newValue});
await this.$nextTick();
this.close();
}
close() {
this.$emit('do-action', {action: 'contents'});
}
keyHook(event) {
if (!this.$root.stdDialog.active && event.type == 'keydown' && event.key == 'Escape') {
this.close();
}
return true;
}
}
//-----------------------------------------------------------------------------
</script>
<style scoped>
.tab-panel {
overflow-x: hidden;
overflow-y: auto;
font-size: 90%;
padding: 0 10px 0px 10px;
}
.clickable {
cursor: pointer;
}
.item:hover {
background-color: #f0f0f0;
}
.subitem:hover {
background-color: #e0e0e0;
}
.separator-top {
border-top: 1px solid #e0e0e0;
}
.separator-bottom {
border-top: 1px solid #e0e0e0;
}
</style>

View File

@@ -48,6 +48,10 @@
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['refresh'] }}</q-tooltip>
</button>
<div class="space"></div>
<button ref="contents" v-show="showToolButton['contents']" class="tool-button" :class="buttonActiveClass('contents')" @click="buttonClick('contents')" v-ripple>
<q-icon name="la la-list" size="32px"/>
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['contents'] }}</q-tooltip>
</button>
<button ref="libs" v-show="mode == 'liberama.top' && showToolButton['libs']" class="tool-button" :class="buttonActiveClass('libs')" @click="buttonClick('libs')" v-ripple>
<q-icon name="la la-sitemap" size="32px"/>
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['libs'] }}</q-tooltip>
@@ -89,12 +93,13 @@
@stop-text-search="stopTextSearch">
</SearchPage>
<CopyTextPage v-if="copyTextActive" ref="copyTextPage" @do-action="doAction"></CopyTextPage>
<LibsPage v-show="libsActive" ref="libsPage" @load-book="loadBook" @libs-close="libsClose" @do-action="doAction"></LibsPage>
<LibsPage v-show="hidden" ref="libsPage" @load-book="loadBook" @libs-close="libsClose" @do-action="doAction"></LibsPage>
<RecentBooksPage v-show="recentBooksActive" ref="recentBooksPage" @load-book="loadBook" @recent-books-close="recentBooksClose"></RecentBooksPage>
<SettingsPage v-show="settingsActive" ref="settingsPage" @do-action="doAction"></SettingsPage>
<HelpPage v-if="helpActive" ref="helpPage" @do-action="doAction"></HelpPage>
<ClickMapPage v-show="clickMapActive" ref="clickMapPage"></ClickMapPage>
<ServerStorage v-show="hidden" ref="serverStorage"></ServerStorage>
<ContentsPage v-show="contentsActive" ref="contentsPage" @do-action="doAction" @book-pos-changed="bookPosChanged"></ContentsPage>
<ReaderDialogs ref="dialogs" @donate-toggle="donateToggle" @version-history-toggle="versionHistoryToggle"></ReaderDialogs>
</div>
@@ -121,6 +126,8 @@ import SettingsPage from './SettingsPage/SettingsPage.vue';
import HelpPage from './HelpPage/HelpPage.vue';
import ClickMapPage from './ClickMapPage/ClickMapPage.vue';
import ServerStorage from './ServerStorage/ServerStorage.vue';
import ContentsPage from './ContentsPage/ContentsPage.vue';
import ReaderDialogs from './ReaderDialogs/ReaderDialogs.vue';
import bookManager from './share/bookManager';
@@ -143,6 +150,8 @@ export default @Component({
HelpPage,
ClickMapPage,
ServerStorage,
ContentsPage,
ReaderDialogs,
},
watch: {
@@ -200,6 +209,7 @@ class Reader extends Vue {
settingsActive = false;
helpActive = false;
clickMapActive = false;
contentsActive = false;
bookPos = null;
allowUrlParamBookPos = false;
@@ -490,6 +500,7 @@ class Reader extends Vue {
this.stopScrolling();
this.stopSearch();
this.helpActive = false;
this.contentsActive = false;
}
loaderToggle() {
@@ -603,6 +614,21 @@ class Reader extends Vue {
}
}
contentsPageToggle() {
this.contentsActive = !this.contentsActive;
const page = this.$refs.page;
if (this.contentsActive && this.activePage == 'TextPage' && page.parsed) {
this.closeAllWindows();
this.contentsActive = true;
this.$nextTick(() => {
this.$refs.contentsPage.init(this.mostRecentBook(), page.parsed);
});
} else {
this.contentsActive = false;
}
}
libsClose() {
if (this.libsActive)
this.libsToogle();
@@ -707,6 +733,7 @@ class Reader extends Vue {
case 'copyText':
case 'splitToPara':
case 'refresh':
case 'contents':
case 'libs':
case 'recentBooks':
case 'offlineMode':
@@ -735,6 +762,7 @@ class Reader extends Vue {
case 'scrolling':
case 'search':
case 'copyText':
case 'contents':
classResult = classDisabled;
break;
case 'splitToPara':
@@ -1026,6 +1054,9 @@ class Reader extends Vue {
case 'refresh':
this.refreshBook();
break;
case 'contents':
this.contentsPageToggle();
break;
case 'libs':
this.libsToogle();
break;
@@ -1125,6 +1156,9 @@ class Reader extends Vue {
if (!result && this.copyTextActive)
result = this.$refs.copyTextPage.keyHook(event);
if (!result && this.contentsActive)
result = this.$refs.contentsPage.keyHook(event);
if (!result && this.$refs.page && this.$refs.page.keyHook)
result = this.$refs.page.keyHook(event);

View File

@@ -46,11 +46,21 @@ export default class BookParser {
let isFirstSection = true;
let isFirstTitlePara = false;
//изображения
this.binary = {};
let binaryId = '';
let binaryType = '';
let dimPromises = [];
//оглавление
this.contents = [];
let curTitle = {paraIndex: -1, title: '', subtitles: []};
let curSubtitle = {paraIndex: -1, title: ''};
let inTitle = false;
let inSubtitle = false;
let sectionLevel = 0;
let bodyIndex = 0;
let paraIndex = -1;
let paraOffset = 0;
let para = []; /*array of
@@ -118,6 +128,12 @@ export default class BookParser {
addIndex: (addIndex ? addIndex : 0),
};
if (inSubtitle) {
curSubtitle.title += '<p>';
} else if (inTitle) {
curTitle.title += '<p>';
}
para[paraIndex] = p;
paraOffset += p.length;
};
@@ -129,6 +145,7 @@ export default class BookParser {
return;
}
const prevParaIndex = paraIndex;
let p = para[paraIndex];
paraOffset -= p.length;
//добавление пустых (addEmptyParagraphs) параграфов перед текущим
@@ -143,6 +160,11 @@ export default class BookParser {
p.offset = paraOffset;
para[paraIndex] = p;
if (curTitle.paraIndex == prevParaIndex)
curTitle.paraIndex = paraIndex;
if (curSubtitle.paraIndex == prevParaIndex)
curSubtitle.paraIndex = paraIndex;
//уберем начальный пробел
p.length = 0;
p.text = p.text.substr(1);
@@ -151,6 +173,13 @@ export default class BookParser {
p.length += len;
p.text += text;
if (inSubtitle) {
curSubtitle.title += text;
} else if (inTitle) {
curTitle.title += text;
}
para[paraIndex] = p;
paraOffset += p.length;
};
@@ -160,7 +189,7 @@ export default class BookParser {
return;
tag = elemName;
path += '/' + elemName;
path += '/' + tag;
if (tag == 'binary') {
let attrs = sax.getAttrsSync(tail);
@@ -187,7 +216,7 @@ export default class BookParser {
}
}
if (elemName == 'author' && path.indexOf('/fictionbook/description/title-info/author') == 0) {
if (tag == 'author' && path.indexOf('/fictionbook/description/title-info/author') == 0) {
if (!fb2.author)
fb2.author = [];
fb2.author.push({});
@@ -198,6 +227,7 @@ export default class BookParser {
if (!isFirstBody)
newParagraph(' ', 1);
isFirstBody = false;
bodyIndex++;
}
if (tag == 'title') {
@@ -205,12 +235,17 @@ export default class BookParser {
isFirstTitlePara = true;
bold = true;
center = true;
inTitle = true;
curTitle = {paraIndex, title: '', inset: sectionLevel, bodyIndex, subtitles: []};
this.contents.push(curTitle);
}
if (tag == 'section') {
if (!isFirstSection)
newParagraph(' ', 1);
isFirstSection = false;
sectionLevel++;
}
if (tag == 'emphasis' || tag == 'strong') {
@@ -231,9 +266,13 @@ export default class BookParser {
isFirstTitlePara = true;
bold = true;
center = true;
inSubtitle = true;
curSubtitle = {paraIndex, inset: sectionLevel, title: ''};
curTitle.subtitles.push(curSubtitle);
}
if (tag == 'epigraph') {
if (tag == 'epigraph' || tag == 'annotation') {
italic = true;
space += 1;
}
@@ -260,6 +299,11 @@ export default class BookParser {
isFirstTitlePara = false;
bold = false;
center = false;
inTitle = false;
}
if (tag == 'section') {
sectionLevel--;
}
if (tag == 'emphasis' || tag == 'strong') {
@@ -274,11 +318,14 @@ export default class BookParser {
isFirstTitlePara = false;
bold = false;
center = false;
inSubtitle = false;
}
if (tag == 'epigraph') {
if (tag == 'epigraph' || tag == 'annotation') {
italic = false;
space -= 1;
if (tag == 'annotation')
newParagraph(' ', 1);
}
if (tag == 'stanza') {

View File

@@ -1,4 +1,15 @@
export const versionHistory = [
{
showUntil: '2020-11-12',
header: '0.9.8 (2020-11-13)',
content:
`
<ul>
<li>добавлено окно "Оглавление/закладки"</li>
</ul>
`
},
{
showUntil: '2020-11-11',
header: '0.9.7 (2020-11-12)',

View File

@@ -32,6 +32,8 @@ import {QPopupProxy} from 'quasar/src/components/popup-proxy';
import {QDialog} from 'quasar/src/components/dialog';
import {QChip} from 'quasar/src/components/chip';
import {QTree} from 'quasar/src/components/tree';
import {QExpansionItem} from 'quasar/src/components/expansion-item';
const components = {
//QLayout,
@@ -58,7 +60,8 @@ const components = {
QPopupProxy,
QDialog,
QChip,
QTree
QTree,
QExpansionItem,
};
//directives

View File

@@ -15,6 +15,7 @@ const readerActions = {
'splitToPara': 'Обновить с разбиением на параграфы',
'refresh': 'Принудительно обновить книгу',
'offlineMode': 'Автономный режим (без интернета)',
'contents': 'Оглавление/закладки',
'libs': 'Библиотека',
'recentBooks': 'Открыть недавние',
'switchToolbar': 'Показать/скрыть панель управления',
@@ -42,6 +43,7 @@ const toolButtons = [
{name: 'copyText', show: false},
{name: 'splitToPara', show: false},
{name: 'refresh', show: true},
{name: 'contents', show: true},
{name: 'libs', show: true},
{name: 'recentBooks', show: true},
{name: 'offlineMode', show: false},
@@ -61,9 +63,10 @@ const hotKeys = [
{name: 'copyText', codes: ['Ctrl+C']},
{name: 'splitToPara', codes: ['Shift+R']},
{name: 'refresh', codes: ['R']},
{name: 'offlineMode', codes: ['O']},
{name: 'contents', codes: ['C']},
{name: 'libs', codes: ['L']},
{name: 'recentBooks', codes: ['X']},
{name: 'offlineMode', codes: ['O']},
{name: 'switchToolbar', codes: ['Tab', 'Q']},
{name: 'bookBegin', codes: ['Home']},

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "Liberama",
"version": "0.9.7",
"version": "0.9.8",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "Liberama",
"version": "0.9.7",
"version": "0.9.8",
"author": "Book Pauk <bookpauk@gmail.com>",
"license": "CC0-1.0",
"repository": "bookpauk/liberama",