Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8e765a04f | ||
|
|
40ff572f94 | ||
|
|
cc4275dc03 | ||
|
|
b387f4a0db | ||
|
|
1a096031c4 | ||
|
|
83a60b4091 | ||
|
|
b292407ec2 | ||
|
|
952c337b76 | ||
|
|
e947b887fe | ||
|
|
bd1e5485d7 | ||
|
|
e095c3318b | ||
|
|
d75a08b519 | ||
|
|
d55a616fe0 | ||
|
|
2146cb3576 | ||
|
|
ae260e74f6 | ||
|
|
355410c03c | ||
|
|
718ad51fac | ||
|
|
4242a8679f | ||
|
|
4ff9ff699b | ||
|
|
7a76673274 |
@@ -159,6 +159,7 @@ import * as lu from './linkUtils';
|
|||||||
|
|
||||||
const proxySubst = {
|
const proxySubst = {
|
||||||
'http://flibusta.is': 'http://b.liberama.top:23480',
|
'http://flibusta.is': 'http://b.liberama.top:23480',
|
||||||
|
'http://fantasy-worlds.org': 'http://b.liberama.top:23580',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default @Component({
|
export default @Component({
|
||||||
|
|||||||
207
client/components/Reader/ContentsPage/ContentsPage.vue
Normal file
207
client/components/Reader/ContentsPage/ContentsPage.vue
Normal 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>
|
||||||
@@ -48,6 +48,10 @@
|
|||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['refresh'] }}</q-tooltip>
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['refresh'] }}</q-tooltip>
|
||||||
</button>
|
</button>
|
||||||
<div class="space"></div>
|
<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>
|
<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-icon name="la la-sitemap" size="32px"/>
|
||||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['libs'] }}</q-tooltip>
|
<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">
|
@stop-text-search="stopTextSearch">
|
||||||
</SearchPage>
|
</SearchPage>
|
||||||
<CopyTextPage v-if="copyTextActive" ref="copyTextPage" @do-action="doAction"></CopyTextPage>
|
<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>
|
<RecentBooksPage v-show="recentBooksActive" ref="recentBooksPage" @load-book="loadBook" @recent-books-close="recentBooksClose"></RecentBooksPage>
|
||||||
<SettingsPage v-show="settingsActive" ref="settingsPage" @do-action="doAction"></SettingsPage>
|
<SettingsPage v-show="settingsActive" ref="settingsPage" @do-action="doAction"></SettingsPage>
|
||||||
<HelpPage v-if="helpActive" ref="helpPage" @do-action="doAction"></HelpPage>
|
<HelpPage v-if="helpActive" ref="helpPage" @do-action="doAction"></HelpPage>
|
||||||
<ClickMapPage v-show="clickMapActive" ref="clickMapPage"></ClickMapPage>
|
<ClickMapPage v-show="clickMapActive" ref="clickMapPage"></ClickMapPage>
|
||||||
<ServerStorage v-show="hidden" ref="serverStorage"></ServerStorage>
|
<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>
|
<ReaderDialogs ref="dialogs" @donate-toggle="donateToggle" @version-history-toggle="versionHistoryToggle"></ReaderDialogs>
|
||||||
</div>
|
</div>
|
||||||
@@ -121,6 +126,8 @@ import SettingsPage from './SettingsPage/SettingsPage.vue';
|
|||||||
import HelpPage from './HelpPage/HelpPage.vue';
|
import HelpPage from './HelpPage/HelpPage.vue';
|
||||||
import ClickMapPage from './ClickMapPage/ClickMapPage.vue';
|
import ClickMapPage from './ClickMapPage/ClickMapPage.vue';
|
||||||
import ServerStorage from './ServerStorage/ServerStorage.vue';
|
import ServerStorage from './ServerStorage/ServerStorage.vue';
|
||||||
|
import ContentsPage from './ContentsPage/ContentsPage.vue';
|
||||||
|
|
||||||
import ReaderDialogs from './ReaderDialogs/ReaderDialogs.vue';
|
import ReaderDialogs from './ReaderDialogs/ReaderDialogs.vue';
|
||||||
|
|
||||||
import bookManager from './share/bookManager';
|
import bookManager from './share/bookManager';
|
||||||
@@ -143,6 +150,8 @@ export default @Component({
|
|||||||
HelpPage,
|
HelpPage,
|
||||||
ClickMapPage,
|
ClickMapPage,
|
||||||
ServerStorage,
|
ServerStorage,
|
||||||
|
ContentsPage,
|
||||||
|
|
||||||
ReaderDialogs,
|
ReaderDialogs,
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -200,6 +209,7 @@ class Reader extends Vue {
|
|||||||
settingsActive = false;
|
settingsActive = false;
|
||||||
helpActive = false;
|
helpActive = false;
|
||||||
clickMapActive = false;
|
clickMapActive = false;
|
||||||
|
contentsActive = false;
|
||||||
|
|
||||||
bookPos = null;
|
bookPos = null;
|
||||||
allowUrlParamBookPos = false;
|
allowUrlParamBookPos = false;
|
||||||
@@ -490,6 +500,7 @@ class Reader extends Vue {
|
|||||||
this.stopScrolling();
|
this.stopScrolling();
|
||||||
this.stopSearch();
|
this.stopSearch();
|
||||||
this.helpActive = false;
|
this.helpActive = false;
|
||||||
|
this.contentsActive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
loaderToggle() {
|
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() {
|
libsClose() {
|
||||||
if (this.libsActive)
|
if (this.libsActive)
|
||||||
this.libsToogle();
|
this.libsToogle();
|
||||||
@@ -707,6 +733,7 @@ class Reader extends Vue {
|
|||||||
case 'copyText':
|
case 'copyText':
|
||||||
case 'splitToPara':
|
case 'splitToPara':
|
||||||
case 'refresh':
|
case 'refresh':
|
||||||
|
case 'contents':
|
||||||
case 'libs':
|
case 'libs':
|
||||||
case 'recentBooks':
|
case 'recentBooks':
|
||||||
case 'offlineMode':
|
case 'offlineMode':
|
||||||
@@ -735,6 +762,7 @@ class Reader extends Vue {
|
|||||||
case 'scrolling':
|
case 'scrolling':
|
||||||
case 'search':
|
case 'search':
|
||||||
case 'copyText':
|
case 'copyText':
|
||||||
|
case 'contents':
|
||||||
classResult = classDisabled;
|
classResult = classDisabled;
|
||||||
break;
|
break;
|
||||||
case 'splitToPara':
|
case 'splitToPara':
|
||||||
@@ -1026,6 +1054,9 @@ class Reader extends Vue {
|
|||||||
case 'refresh':
|
case 'refresh':
|
||||||
this.refreshBook();
|
this.refreshBook();
|
||||||
break;
|
break;
|
||||||
|
case 'contents':
|
||||||
|
this.contentsPageToggle();
|
||||||
|
break;
|
||||||
case 'libs':
|
case 'libs':
|
||||||
this.libsToogle();
|
this.libsToogle();
|
||||||
break;
|
break;
|
||||||
@@ -1125,6 +1156,9 @@ class Reader extends Vue {
|
|||||||
if (!result && this.copyTextActive)
|
if (!result && this.copyTextActive)
|
||||||
result = this.$refs.copyTextPage.keyHook(event);
|
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)
|
if (!result && this.$refs.page && this.$refs.page.keyHook)
|
||||||
result = this.$refs.page.keyHook(event);
|
result = this.$refs.page.keyHook(event);
|
||||||
|
|
||||||
|
|||||||
@@ -46,11 +46,21 @@ export default class BookParser {
|
|||||||
let isFirstSection = true;
|
let isFirstSection = true;
|
||||||
let isFirstTitlePara = false;
|
let isFirstTitlePara = false;
|
||||||
|
|
||||||
|
//изображения
|
||||||
this.binary = {};
|
this.binary = {};
|
||||||
let binaryId = '';
|
let binaryId = '';
|
||||||
let binaryType = '';
|
let binaryType = '';
|
||||||
let dimPromises = [];
|
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 paraIndex = -1;
|
||||||
let paraOffset = 0;
|
let paraOffset = 0;
|
||||||
let para = []; /*array of
|
let para = []; /*array of
|
||||||
@@ -118,6 +128,12 @@ export default class BookParser {
|
|||||||
addIndex: (addIndex ? addIndex : 0),
|
addIndex: (addIndex ? addIndex : 0),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (inSubtitle) {
|
||||||
|
curSubtitle.title += '<p>';
|
||||||
|
} else if (inTitle) {
|
||||||
|
curTitle.title += '<p>';
|
||||||
|
}
|
||||||
|
|
||||||
para[paraIndex] = p;
|
para[paraIndex] = p;
|
||||||
paraOffset += p.length;
|
paraOffset += p.length;
|
||||||
};
|
};
|
||||||
@@ -129,6 +145,7 @@ export default class BookParser {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const prevParaIndex = paraIndex;
|
||||||
let p = para[paraIndex];
|
let p = para[paraIndex];
|
||||||
paraOffset -= p.length;
|
paraOffset -= p.length;
|
||||||
//добавление пустых (addEmptyParagraphs) параграфов перед текущим
|
//добавление пустых (addEmptyParagraphs) параграфов перед текущим
|
||||||
@@ -143,6 +160,11 @@ export default class BookParser {
|
|||||||
p.offset = paraOffset;
|
p.offset = paraOffset;
|
||||||
para[paraIndex] = p;
|
para[paraIndex] = p;
|
||||||
|
|
||||||
|
if (curTitle.paraIndex == prevParaIndex)
|
||||||
|
curTitle.paraIndex = paraIndex;
|
||||||
|
if (curSubtitle.paraIndex == prevParaIndex)
|
||||||
|
curSubtitle.paraIndex = paraIndex;
|
||||||
|
|
||||||
//уберем начальный пробел
|
//уберем начальный пробел
|
||||||
p.length = 0;
|
p.length = 0;
|
||||||
p.text = p.text.substr(1);
|
p.text = p.text.substr(1);
|
||||||
@@ -151,6 +173,13 @@ export default class BookParser {
|
|||||||
p.length += len;
|
p.length += len;
|
||||||
p.text += text;
|
p.text += text;
|
||||||
|
|
||||||
|
|
||||||
|
if (inSubtitle) {
|
||||||
|
curSubtitle.title += text;
|
||||||
|
} else if (inTitle) {
|
||||||
|
curTitle.title += text;
|
||||||
|
}
|
||||||
|
|
||||||
para[paraIndex] = p;
|
para[paraIndex] = p;
|
||||||
paraOffset += p.length;
|
paraOffset += p.length;
|
||||||
};
|
};
|
||||||
@@ -160,7 +189,7 @@ export default class BookParser {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
tag = elemName;
|
tag = elemName;
|
||||||
path += '/' + elemName;
|
path += '/' + tag;
|
||||||
|
|
||||||
if (tag == 'binary') {
|
if (tag == 'binary') {
|
||||||
let attrs = sax.getAttrsSync(tail);
|
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)
|
if (!fb2.author)
|
||||||
fb2.author = [];
|
fb2.author = [];
|
||||||
fb2.author.push({});
|
fb2.author.push({});
|
||||||
@@ -198,6 +227,7 @@ export default class BookParser {
|
|||||||
if (!isFirstBody)
|
if (!isFirstBody)
|
||||||
newParagraph(' ', 1);
|
newParagraph(' ', 1);
|
||||||
isFirstBody = false;
|
isFirstBody = false;
|
||||||
|
bodyIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag == 'title') {
|
if (tag == 'title') {
|
||||||
@@ -205,12 +235,17 @@ export default class BookParser {
|
|||||||
isFirstTitlePara = true;
|
isFirstTitlePara = true;
|
||||||
bold = true;
|
bold = true;
|
||||||
center = true;
|
center = true;
|
||||||
|
|
||||||
|
inTitle = true;
|
||||||
|
curTitle = {paraIndex, title: '', inset: sectionLevel, bodyIndex, subtitles: []};
|
||||||
|
this.contents.push(curTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag == 'section') {
|
if (tag == 'section') {
|
||||||
if (!isFirstSection)
|
if (!isFirstSection)
|
||||||
newParagraph(' ', 1);
|
newParagraph(' ', 1);
|
||||||
isFirstSection = false;
|
isFirstSection = false;
|
||||||
|
sectionLevel++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag == 'emphasis' || tag == 'strong') {
|
if (tag == 'emphasis' || tag == 'strong') {
|
||||||
@@ -231,9 +266,13 @@ export default class BookParser {
|
|||||||
isFirstTitlePara = true;
|
isFirstTitlePara = true;
|
||||||
bold = true;
|
bold = true;
|
||||||
center = true;
|
center = true;
|
||||||
|
|
||||||
|
inSubtitle = true;
|
||||||
|
curSubtitle = {paraIndex, inset: sectionLevel, title: ''};
|
||||||
|
curTitle.subtitles.push(curSubtitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag == 'epigraph') {
|
if (tag == 'epigraph' || tag == 'annotation') {
|
||||||
italic = true;
|
italic = true;
|
||||||
space += 1;
|
space += 1;
|
||||||
}
|
}
|
||||||
@@ -260,6 +299,11 @@ export default class BookParser {
|
|||||||
isFirstTitlePara = false;
|
isFirstTitlePara = false;
|
||||||
bold = false;
|
bold = false;
|
||||||
center = false;
|
center = false;
|
||||||
|
inTitle = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tag == 'section') {
|
||||||
|
sectionLevel--;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag == 'emphasis' || tag == 'strong') {
|
if (tag == 'emphasis' || tag == 'strong') {
|
||||||
@@ -274,11 +318,14 @@ export default class BookParser {
|
|||||||
isFirstTitlePara = false;
|
isFirstTitlePara = false;
|
||||||
bold = false;
|
bold = false;
|
||||||
center = false;
|
center = false;
|
||||||
|
inSubtitle = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag == 'epigraph') {
|
if (tag == 'epigraph' || tag == 'annotation') {
|
||||||
italic = false;
|
italic = false;
|
||||||
space -= 1;
|
space -= 1;
|
||||||
|
if (tag == 'annotation')
|
||||||
|
newParagraph(' ', 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag == 'stanza') {
|
if (tag == 'stanza') {
|
||||||
|
|||||||
@@ -1,4 +1,26 @@
|
|||||||
export const versionHistory = [
|
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)',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>исправления багов</li>
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
showUntil: '2020-11-05',
|
showUntil: '2020-11-05',
|
||||||
header: '0.9.6 (2020-11-06)',
|
header: '0.9.6 (2020-11-06)',
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ import {QPopupProxy} from 'quasar/src/components/popup-proxy';
|
|||||||
import {QDialog} from 'quasar/src/components/dialog';
|
import {QDialog} from 'quasar/src/components/dialog';
|
||||||
import {QChip} from 'quasar/src/components/chip';
|
import {QChip} from 'quasar/src/components/chip';
|
||||||
import {QTree} from 'quasar/src/components/tree';
|
import {QTree} from 'quasar/src/components/tree';
|
||||||
|
import {QExpansionItem} from 'quasar/src/components/expansion-item';
|
||||||
|
|
||||||
|
|
||||||
const components = {
|
const components = {
|
||||||
//QLayout,
|
//QLayout,
|
||||||
@@ -58,7 +60,8 @@ const components = {
|
|||||||
QPopupProxy,
|
QPopupProxy,
|
||||||
QDialog,
|
QDialog,
|
||||||
QChip,
|
QChip,
|
||||||
QTree
|
QTree,
|
||||||
|
QExpansionItem,
|
||||||
};
|
};
|
||||||
|
|
||||||
//directives
|
//directives
|
||||||
|
|||||||
@@ -103,30 +103,57 @@ export function fromBase64(data) {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getObjDiff(oldObj, newObj) {
|
export function getObjDiff(oldObj, newObj, opts = {}) {
|
||||||
const result = {__isDiff: true, change: {}, add: {}, del: []};
|
const {
|
||||||
|
exclude = [],
|
||||||
|
excludeAdd = [],
|
||||||
|
excludeDel = [],
|
||||||
|
} = opts;
|
||||||
|
|
||||||
for (const key of Object.keys(oldObj)) {
|
const ex = new Set(exclude);
|
||||||
if (newObj.hasOwnProperty(key)) {
|
const exAdd = new Set(excludeAdd);
|
||||||
if (!_.isEqual(oldObj[key], newObj[key])) {
|
const exDel = new Set(excludeDel);
|
||||||
if (_.isObject(oldObj[key]) && _.isObject(newObj[key])) {
|
|
||||||
result.change[key] = getObjDiff(oldObj[key], newObj[key]);
|
const makeObjDiff = (oldObj, newObj, keyPath) => {
|
||||||
} else {
|
const result = {__isDiff: true, change: {}, add: {}, del: []};
|
||||||
result.change[key] = _.cloneDeep(newObj[key]);
|
|
||||||
|
keyPath = `${keyPath}${keyPath ? '/' : ''}`;
|
||||||
|
|
||||||
|
for (const key of Object.keys(oldObj)) {
|
||||||
|
const kp = `${keyPath}${key}`;
|
||||||
|
|
||||||
|
if (newObj.hasOwnProperty(key)) {
|
||||||
|
if (ex.has(kp))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!_.isEqual(oldObj[key], newObj[key])) {
|
||||||
|
if (_.isObject(oldObj[key]) && _.isObject(newObj[key])) {
|
||||||
|
result.change[key] = makeObjDiff(oldObj[key], newObj[key], kp);
|
||||||
|
} else {
|
||||||
|
result.change[key] = _.cloneDeep(newObj[key]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (exDel.has(kp))
|
||||||
|
continue;
|
||||||
|
result.del.push(key);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
result.del.push(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const key of Object.keys(newObj)) {
|
||||||
|
const kp = `${keyPath}${key}`;
|
||||||
|
if (exAdd.has(kp))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!oldObj.hasOwnProperty(key)) {
|
||||||
|
result.add[key] = _.cloneDeep(newObj[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key of Object.keys(newObj)) {
|
return makeObjDiff(oldObj, newObj, '');
|
||||||
if (!oldObj.hasOwnProperty(key)) {
|
|
||||||
result.add[key] = _.cloneDeep(newObj[key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isObjDiff(diff) {
|
export function isObjDiff(diff) {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const readerActions = {
|
|||||||
'splitToPara': 'Обновить с разбиением на параграфы',
|
'splitToPara': 'Обновить с разбиением на параграфы',
|
||||||
'refresh': 'Принудительно обновить книгу',
|
'refresh': 'Принудительно обновить книгу',
|
||||||
'offlineMode': 'Автономный режим (без интернета)',
|
'offlineMode': 'Автономный режим (без интернета)',
|
||||||
|
'contents': 'Оглавление/закладки',
|
||||||
'libs': 'Библиотека',
|
'libs': 'Библиотека',
|
||||||
'recentBooks': 'Открыть недавние',
|
'recentBooks': 'Открыть недавние',
|
||||||
'switchToolbar': 'Показать/скрыть панель управления',
|
'switchToolbar': 'Показать/скрыть панель управления',
|
||||||
@@ -42,6 +43,7 @@ const toolButtons = [
|
|||||||
{name: 'copyText', show: false},
|
{name: 'copyText', show: false},
|
||||||
{name: 'splitToPara', show: false},
|
{name: 'splitToPara', show: false},
|
||||||
{name: 'refresh', show: true},
|
{name: 'refresh', show: true},
|
||||||
|
{name: 'contents', show: true},
|
||||||
{name: 'libs', show: true},
|
{name: 'libs', show: true},
|
||||||
{name: 'recentBooks', show: true},
|
{name: 'recentBooks', show: true},
|
||||||
{name: 'offlineMode', show: false},
|
{name: 'offlineMode', show: false},
|
||||||
@@ -61,9 +63,10 @@ const hotKeys = [
|
|||||||
{name: 'copyText', codes: ['Ctrl+C']},
|
{name: 'copyText', codes: ['Ctrl+C']},
|
||||||
{name: 'splitToPara', codes: ['Shift+R']},
|
{name: 'splitToPara', codes: ['Shift+R']},
|
||||||
{name: 'refresh', codes: ['R']},
|
{name: 'refresh', codes: ['R']},
|
||||||
{name: 'offlineMode', codes: ['O']},
|
{name: 'contents', codes: ['C']},
|
||||||
{name: 'libs', codes: ['L']},
|
{name: 'libs', codes: ['L']},
|
||||||
{name: 'recentBooks', codes: ['X']},
|
{name: 'recentBooks', codes: ['X']},
|
||||||
|
{name: 'offlineMode', codes: ['O']},
|
||||||
|
|
||||||
{name: 'switchToolbar', codes: ['Tab', 'Q']},
|
{name: 'switchToolbar', codes: ['Tab', 'Q']},
|
||||||
{name: 'bookBegin', codes: ['Home']},
|
{name: 'bookBegin', codes: ['Home']},
|
||||||
@@ -268,9 +271,12 @@ for (const button of toolButtons)
|
|||||||
for (const hotKey of hotKeys)
|
for (const hotKey of hotKeys)
|
||||||
settingDefaults.userHotKeys[hotKey.name] = hotKey.codes;
|
settingDefaults.userHotKeys[hotKey.name] = hotKey.codes;
|
||||||
|
|
||||||
|
const excludeDiffHotKeys = [];
|
||||||
|
for (const hotKey of hotKeys)
|
||||||
|
excludeDiffHotKeys.push(`userHotKeys/${hotKey.name}`);
|
||||||
|
|
||||||
function addDefaultsToSettings(settings) {
|
function addDefaultsToSettings(settings) {
|
||||||
const diff = utils.getObjDiff(settings, settingDefaults);
|
const diff = utils.getObjDiff(settings, settingDefaults, {exclude: excludeDiffHotKeys});
|
||||||
|
|
||||||
if (!utils.isEmptyObjDiffDeep(diff, {isApplyChange: false})) {
|
if (!utils.isEmptyObjDiffDeep(diff, {isApplyChange: false})) {
|
||||||
return utils.applyObjDiff(settings, diff, {isApplyChange: false});
|
return utils.applyObjDiff(settings, diff, {isApplyChange: false});
|
||||||
}
|
}
|
||||||
@@ -288,15 +294,18 @@ const libsDefaults = {
|
|||||||
{r: 'http://flibusta.is', s: 'http://flibusta.is', list: [
|
{r: 'http://flibusta.is', s: 'http://flibusta.is', list: [
|
||||||
{l: 'http://flibusta.is', c: 'Флибуста | Книжное братство'},
|
{l: 'http://flibusta.is', c: 'Флибуста | Книжное братство'},
|
||||||
]},
|
]},
|
||||||
|
{r: 'https://flibs.in', s: 'https://flibs.in', list: [
|
||||||
|
{l: 'https://flibs.in', c: 'Flibs'},
|
||||||
|
]},
|
||||||
|
{r: 'http://fantasy-worlds.org', s: 'http://fantasy-worlds.org', list: [
|
||||||
|
{l: 'http://fantasy-worlds.org', c: 'Миры Фэнтези'},
|
||||||
|
]},
|
||||||
{r: 'http://samlib.ru', s: 'http://samlib.ru', list: [
|
{r: 'http://samlib.ru', s: 'http://samlib.ru', list: [
|
||||||
{l: 'http://samlib.ru', c: 'Журнал "Самиздат"'},
|
{l: 'http://samlib.ru', c: 'Журнал "Самиздат"'},
|
||||||
]},
|
]},
|
||||||
{r: 'http://lib.ru', s: 'http://lib.ru', list: [
|
{r: 'http://lib.ru', s: 'http://lib.ru', list: [
|
||||||
{l: 'http://lib.ru', c: 'Библиотека Максима Мошкова'},
|
{l: 'http://lib.ru', c: 'Библиотека Максима Мошкова'},
|
||||||
]},
|
]},
|
||||||
{r: 'http://fantasy-worlds.org', s: 'http://fantasy-worlds.org', list: [
|
|
||||||
{l: 'http://fantasy-worlds.org', c: 'Миры Фэнтези'},
|
|
||||||
]},
|
|
||||||
{r: 'https://aldebaran.ru', s: 'https://aldebaran.ru', list: [
|
{r: 'https://aldebaran.ru', s: 'https://aldebaran.ru', list: [
|
||||||
{l: 'https://aldebaran.ru', c: 'АЛЬДЕБАРАН | Электронная библиотека книг'},
|
{l: 'https://aldebaran.ru', c: 'АЛЬДЕБАРАН | Электронная библиотека книг'},
|
||||||
]},
|
]},
|
||||||
|
|||||||
@@ -126,3 +126,18 @@ server {
|
|||||||
proxy_set_header Referer "";
|
proxy_set_header Referer "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 23580;
|
||||||
|
server_name fw_proxy;
|
||||||
|
|
||||||
|
valid_referers liberama.top b.liberama.top;
|
||||||
|
|
||||||
|
if ($invalid_referer) {
|
||||||
|
return 403;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://fantasy-worlds.org;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Liberama",
|
"name": "Liberama",
|
||||||
"version": "0.9.6",
|
"version": "0.9.8",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Liberama",
|
"name": "Liberama",
|
||||||
"version": "0.9.6",
|
"version": "0.9.8",
|
||||||
"author": "Book Pauk <bookpauk@gmail.com>",
|
"author": "Book Pauk <bookpauk@gmail.com>",
|
||||||
"license": "CC0-1.0",
|
"license": "CC0-1.0",
|
||||||
"repository": "bookpauk/liberama",
|
"repository": "bookpauk/liberama",
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ module.exports = {
|
|||||||
loggingEnabled: true,
|
loggingEnabled: true,
|
||||||
|
|
||||||
maxUploadFileSize: 50*1024*1024,//50Мб
|
maxUploadFileSize: 50*1024*1024,//50Мб
|
||||||
maxTempPublicDirSize: 512*1024*1024,//512Мб
|
maxTempPublicDirSize: 512*1024*1024,//512Мб + 20% квота если проблема с remoteWebDavStorage
|
||||||
maxUploadPublicDirSize: 200*1024*1024,//100Мб
|
maxUploadPublicDirSize: 200*1024*1024,//100Мб
|
||||||
|
|
||||||
useExternalBookConverter: false,
|
useExternalBookConverter: false,
|
||||||
|
|||||||
@@ -266,30 +266,38 @@ class ReaderWorker {
|
|||||||
files.push({name, stat});
|
files.push({name, stat});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log(`clean dir ${dir}, maxSize=${maxSize}, found ${files.length} files`);
|
log(`clean dir ${dir}, maxSize=${maxSize}, found ${files.length} files, total size=${size}`);
|
||||||
|
|
||||||
files.sort((a, b) => a.stat.mtimeMs - b.stat.mtimeMs);
|
files.sort((a, b) => a.stat.mtimeMs - b.stat.mtimeMs);
|
||||||
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
let j = 0;
|
||||||
while (i < files.length && size > maxSize) {
|
while (i < files.length && size > maxSize) {
|
||||||
const file = files[i];
|
const file = files[i];
|
||||||
const oldFile = `${dir}/${file.name}`;
|
const oldFile = `${dir}/${file.name}`;
|
||||||
|
|
||||||
|
let remoteSuccess = true;
|
||||||
//отправляем только this.config.tempPublicDir
|
//отправляем только this.config.tempPublicDir
|
||||||
//TODO: убрать в будущем, т.к. уже делается ленивое сохранение compFilename в удаленном хранилище
|
|
||||||
if (this.remoteWebDavStorage && dir === this.config.tempPublicDir) {
|
if (this.remoteWebDavStorage && dir === this.config.tempPublicDir) {
|
||||||
|
remoteSuccess = false;
|
||||||
try {
|
try {
|
||||||
//log(`remoteWebDavStorage.putFile ${path.basename(oldFile)}`);
|
//log(`remoteWebDavStorage.putFile ${path.basename(oldFile)}`);
|
||||||
await this.remoteWebDavStorage.putFile(oldFile);
|
await this.remoteWebDavStorage.putFile(oldFile);
|
||||||
|
remoteSuccess = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(LM_ERR, e.stack);
|
log(LM_ERR, e.stack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await fs.remove(oldFile);
|
//реально удаляем только если сохранили в хранилище
|
||||||
|
if (remoteSuccess || size > maxSize*1.2) {
|
||||||
|
await fs.remove(oldFile);
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
|
||||||
size -= file.stat.size;
|
size -= file.stat.size;
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
log(`removed ${i} files`);
|
log(`removed ${j} files`);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
log(LM_ERR, e.stack);
|
log(LM_ERR, e.stack);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Reference in New Issue
Block a user