Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4cb61bebe | ||
|
|
7d5310af42 | ||
|
|
f68c610c0d | ||
|
|
ccfb6a6d73 | ||
|
|
da55996e22 | ||
|
|
ecd8400a34 | ||
|
|
03914883bc | ||
|
|
9981e1f3bd | ||
|
|
4d1df66025 | ||
|
|
a0f64e188b | ||
|
|
08407a1094 | ||
|
|
445ea3bb2e | ||
|
|
0e0aab98b1 | ||
|
|
721d5eb0c1 | ||
|
|
6d99dbc3a7 | ||
|
|
2be31f649b | ||
|
|
828ac27c03 | ||
|
|
b3d614002f | ||
|
|
2b2000ca10 | ||
|
|
8d7428d099 | ||
|
|
57f8322f31 | ||
|
|
bee7bc4294 | ||
|
|
28702065bc | ||
|
|
c248057081 | ||
|
|
6186f5e138 | ||
|
|
2201d8176d | ||
|
|
2ba6819876 | ||
|
|
a393b2a370 | ||
|
|
59fe713df2 | ||
|
|
4b8efaca9a | ||
|
|
a26100a8d0 | ||
|
|
8c52f4718c | ||
|
|
85b5c3c4ec | ||
|
|
4fd559e4c7 | ||
|
|
a337d0ddc7 | ||
|
|
9e4cb7071e | ||
|
|
c3f1707343 | ||
|
|
1ed058a553 | ||
|
|
0500a8178d | ||
|
|
7d0059f573 | ||
|
|
4e3b882362 | ||
|
|
13cf47873e | ||
|
|
7ee23ec38f | ||
|
|
eebf17c42c | ||
|
|
f84536788b | ||
|
|
4bbfdc2cb2 | ||
|
|
211fec35e3 | ||
|
|
b8214a46ae | ||
|
|
549ef91c81 | ||
|
|
cede65313b | ||
|
|
d897a7400f | ||
|
|
47f059213f | ||
|
|
8af51bbf08 | ||
|
|
53d9f5ddc6 | ||
|
|
06fffdccc8 |
106
LICENSE.md
Normal file
106
LICENSE.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# CC0 1.0 Universal
|
||||
|
||||
## Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator and
|
||||
subsequent owner(s) (each and all, an “owner”) of an original work of
|
||||
authorship and/or a database (each, a “Work”).
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for the
|
||||
purpose of contributing to a commons of creative, cultural and scientific works
|
||||
(“Commons”) that the public can reliably and without fear of later claims of
|
||||
infringement build upon, modify, incorporate in other works, reuse and
|
||||
redistribute as freely as possible in any form whatsoever and for any purposes,
|
||||
including without limitation commercial purposes. These owners may contribute
|
||||
to the Commons to promote the ideal of a free culture and the further
|
||||
production of creative, cultural and scientific works, or to gain reputation or
|
||||
greater distribution for their Work in part through the use and efforts of
|
||||
others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any expectation of
|
||||
additional consideration or compensation, the person associating CC0 with a
|
||||
Work (the “Affirmer”), to the extent that he or she is an owner of Copyright
|
||||
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and
|
||||
publicly distribute the Work under its terms, with knowledge of his or her
|
||||
Copyright and Related Rights in the Work and the meaning and intended legal
|
||||
effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights (“Copyright and
|
||||
Related Rights”). Copyright and Related Rights include, but are not limited
|
||||
to, the following:
|
||||
1. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
2. moral rights retained by the original author(s) and/or performer(s);
|
||||
3. publicity and privacy rights pertaining to a person’s image or likeness
|
||||
depicted in a Work;
|
||||
4. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(i), below;
|
||||
5. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
6. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation thereof,
|
||||
including any amended or successor version of such directive); and
|
||||
7. other similar, equivalent or corresponding rights throughout the world
|
||||
based on applicable law or treaty, and any national implementations
|
||||
thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention of,
|
||||
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
|
||||
unconditionally waives, abandons, and surrenders all of Affirmer’s Copyright
|
||||
and Related Rights and associated claims and causes of action, whether now
|
||||
known or unknown (including existing as well as future claims and causes of
|
||||
action), in the Work (i) in all territories worldwide, (ii) for the maximum
|
||||
duration provided by applicable law or treaty (including future time
|
||||
extensions), (iii) in any current or future medium and for any number of
|
||||
copies, and (iv) for any purpose whatsoever, including without limitation
|
||||
commercial, advertising or promotional purposes (the “Waiver”). Affirmer makes
|
||||
the Waiver for the benefit of each member of the public at large and to the
|
||||
detriment of Affirmer’s heirs and successors, fully intending that such Waiver
|
||||
shall not be subject to revocation, rescission, cancellation, termination, or
|
||||
any other legal or equitable action to disrupt the quiet enjoyment of the Work
|
||||
by the public as contemplated by Affirmer’s express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason be
|
||||
judged legally invalid or ineffective under applicable law, then the Waiver
|
||||
shall be preserved to the maximum extent permitted taking into account
|
||||
Affirmer’s express Statement of Purpose. In addition, to the extent the Waiver
|
||||
is so judged Affirmer hereby grants to each affected person a royalty-free, non
|
||||
transferable, non sublicensable, non exclusive, irrevocable and unconditional
|
||||
license to exercise Affirmer’s Copyright and Related Rights in the Work (i) in
|
||||
all territories worldwide, (ii) for the maximum duration provided by applicable
|
||||
law or treaty (including future time extensions), (iii) in any current or
|
||||
future medium and for any number of copies, and (iv) for any purpose
|
||||
whatsoever, including without limitation commercial, advertising or promotional
|
||||
purposes (the “License”). The License shall be deemed effective as of the date
|
||||
CC0 was applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder of the
|
||||
License, and in such case Affirmer hereby affirms that he or she will not (i)
|
||||
exercise any of his or her remaining Copyright and Related Rights in the Work
|
||||
or (ii) assert any associated claims and causes of action with respect to the
|
||||
Work, in either case contrary to Affirmer’s express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
1. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
2. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied, statutory
|
||||
or otherwise, including without limitation warranties of title,
|
||||
merchantability, fitness for a particular purpose, non infringement, or
|
||||
the absence of latent or other defects, accuracy, or the present or
|
||||
absence of errors, whether or not discoverable, all to the greatest
|
||||
extent permissible under applicable law.
|
||||
3. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person’s Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the Work.
|
||||
4. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
||||
|
||||
For more information, please see
|
||||
http://creativecommons.org/publicdomain/zero/1.0/.
|
||||
42
README.md
42
README.md
@@ -1,3 +1,43 @@
|
||||
# Liberama
|
||||
|
||||
Свободный обмен книгами в формате fb2
|
||||
Браузерная онлайн-читалка книг и децентрализованная библиотека.
|
||||
|
||||
Читалка [OmniReader](https://omnireader.ru) является частью данного проекта, размещенной на VPS:
|
||||
|
||||

|
||||

|
||||
|
||||
## VPS
|
||||
Для разворачивания читалки на чистом VPS с нуля смотрите [docs/omnireader](docs/omnireader/README.md)
|
||||
|
||||
## Сборка проекта
|
||||
Необходима версия node.js не ниже 10.
|
||||
|
||||
```
|
||||
$ git clone https://github.com/bookpauk/liberama
|
||||
$ cd liberama
|
||||
$ npm i
|
||||
```
|
||||
|
||||
### Windows
|
||||
```
|
||||
$ npm run build:win
|
||||
```
|
||||
|
||||
### Linux
|
||||
```
|
||||
$ npm run build:linux
|
||||
```
|
||||
|
||||
Результат сборки будет доступен в каталоге `dist/linux|win` в виде исполнимого (standalone) файла
|
||||
|
||||
### Разработка
|
||||
```
|
||||
$ npm run dev
|
||||
```
|
||||
|
||||
## Помочь проекту
|
||||
|
||||
* bitcoin: 3EbgZ7MK1UVaN38Gty5DCBtS4PknM4Ut85
|
||||
* litecoin: MP39Riec4oSNB3XMjiquKoLWxbufRYNXxZ
|
||||
* monero: 8BQPnvHcPSHM5gMQsmuypDgx9NNsYqwXKfDDuswEyF2Q2ewQSfd2pkK6ydH2wmMyq2JViZvy9DQ35hLMx7g72mFWNJTPtnz
|
||||
|
||||
@@ -24,8 +24,8 @@ async function main() {
|
||||
await fs.ensureDir(tempDownloadDir);
|
||||
|
||||
//sqlite3
|
||||
const sqliteRemoteUrl = 'https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v4.0.4/node-v64-linux-x64.tar.gz';
|
||||
const sqliteDecompressedFilename = `${tempDownloadDir}/node-v64-linux-x64/node_sqlite3.node`;
|
||||
const sqliteRemoteUrl = 'https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v4.1.1/node-v72-linux-x64.tar.gz';
|
||||
const sqliteDecompressedFilename = `${tempDownloadDir}/node-v72-linux-x64/node_sqlite3.node`;
|
||||
|
||||
if (!await fs.pathExists(sqliteDecompressedFilename)) {
|
||||
// Скачиваем node_sqlite3.node для винды, т.к. pkg не включает его в сборку
|
||||
|
||||
@@ -24,8 +24,8 @@ async function main() {
|
||||
await fs.ensureDir(tempDownloadDir);
|
||||
|
||||
//sqlite3
|
||||
const sqliteRemoteUrl = 'https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v4.0.4/node-v64-win32-x64.tar.gz';
|
||||
const sqliteDecompressedFilename = `${tempDownloadDir}/node-v64-win32-x64/node_sqlite3.node`;
|
||||
const sqliteRemoteUrl = 'https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v4.1.1/node-v72-win32-x64.tar.gz';
|
||||
const sqliteDecompressedFilename = `${tempDownloadDir}/node-v72-win32-x64/node_sqlite3.node`;
|
||||
|
||||
if (!await fs.pathExists(sqliteDecompressedFilename)) {
|
||||
// Скачиваем node_sqlite3.node для винды, т.к. pkg не включает его в сборку
|
||||
|
||||
@@ -56,7 +56,11 @@ class Reader {
|
||||
}
|
||||
}
|
||||
|
||||
async loadCachedBook(url, callback){
|
||||
async checkUrl(url) {
|
||||
return await axios.head(url, {headers: {'Cache-Control': 'no-cache'}});
|
||||
}
|
||||
|
||||
async loadCachedBook(url, callback) {
|
||||
const response = await axios.head(url);
|
||||
|
||||
let estSize = 1000000;
|
||||
|
||||
@@ -215,22 +215,6 @@ class App extends Vue {
|
||||
window.history.replaceState({}, '', '/');
|
||||
this.$router.replace({ path: '/reader', query: q });
|
||||
}
|
||||
|
||||
//yandex-метрика для omnireader
|
||||
if (this.config.branch == 'production' && this.mode == 'omnireader' && !this.yaMetricsDone) {
|
||||
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
|
||||
m[i].l=1*new Date();k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
|
||||
(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");// eslint-disable-line no-unexpected-multiline
|
||||
|
||||
ym(52347334, "init", {// eslint-disable-line no-undef
|
||||
id:52347334,
|
||||
clickmap:true,
|
||||
trackLinks:true,
|
||||
accurateTrackBounce:true
|
||||
});
|
||||
|
||||
this.yaMetricsDone = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
@@ -90,89 +90,7 @@
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
title="Внимание!"
|
||||
:visible.sync="migrationVisible1"
|
||||
width="90%">
|
||||
<div>
|
||||
Появилась httpS-версия сайта по адресу <a href="https://omnireader.ru" target="_blank">https://omnireader.ru</a><br>
|
||||
Работа по httpS-протоколу, помимо безопасности соединения, позволяет воспользоваться всеми возможностями
|
||||
современных браузеров, а именно, применительно к нашему ресурсу:
|
||||
|
||||
<ul>
|
||||
<li>возможность автономной работы с читалкой (без доступа к интернету), кеширование сайта через appcache</li>
|
||||
<li>безопасная передача на сервер данных о настройках и читаемых книгах при включенной синхронизации; все данные шифруются на стороне
|
||||
браузера ключом доступа и никто (в т.ч. администратор) не имеет возможности их прочитать
|
||||
<li>использование встроенных в JS функций шифрования и других</li>
|
||||
</ul>
|
||||
|
||||
Для того, чтобы перейти на новую версию с сохранением настроек и читаемых книг необходимо синхронизировать обе читалки:
|
||||
<ul>
|
||||
<li>зайти в "Настройки"->"Профили" и поставить галочку "Включить синхронизацию с сервером"</li>
|
||||
<li>там же добавить профиль устройства с любым именем для синхронизации настроек<br>
|
||||
<span style="margin-left: 20px"><i style="font-size: 90%" class="el-icon-info"></i>
|
||||
после этого все данные будут автоматически сохранены на сервер
|
||||
</span>
|
||||
</li>
|
||||
<li>далее нажать на кнопку "Показать ключ доступа" и кликнуть по ссылке "Ссылка для ввода ключа"<br>
|
||||
<span style="margin-left: 20px"><i style="font-size: 90%" class="el-icon-info"></i>
|
||||
произойдет переход на https-версию читалки и откроется окно для ввода ключа
|
||||
</span><br>
|
||||
<span style="margin-left: 20px"><i style="font-size: 90%" class="el-icon-info"></i>
|
||||
подтвердив ввод ключа нажатием "OK", включив синхронизацию с сервером и выбрав профиль устройства, вы восстановите все ваши настройки в новой версии
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
Старая http-версия сайта будет доступна до конца 2019 года.<br>
|
||||
Приносим извинения за доставленные неудобства.
|
||||
</div>
|
||||
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="migrationDialogDisable">Больше не показывать</el-button>
|
||||
<el-button @click="migrationDialogRemind">Напомнить позже</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
title="Внимание!"
|
||||
:visible.sync="migrationVisible2"
|
||||
width="90%">
|
||||
<div>
|
||||
Информация для пользователей старой версии читалки по адресу <a href="http://omnireader.ru" target="_blank">http://omnireader.ru</a><br>
|
||||
Для того, чтобы перейти на новую httpS-версию с сохранением настроек и читаемых книг необходимо синхронизировать обе читалки:
|
||||
<ul>
|
||||
<li>перейти на старую версию ресурса <a href="http://omnireader.ru" target="_blank">http://omnireader.ru</a></li>
|
||||
<li>зайти в "Настройки"->"Профили" и поставить галочку "Включить синхронизацию с сервером"</li>
|
||||
<li>там же добавить профиль устройства с любым именем для синхронизации настроек<br>
|
||||
<span style="margin-left: 20px"><i style="font-size: 90%" class="el-icon-info"></i>
|
||||
после этого все данные будут автоматически сохранены на сервер
|
||||
</span>
|
||||
</li>
|
||||
<li>далее нажать на кнопку "Показать ключ доступа" и кликнуть по ссылке "Ссылка для ввода ключа"<br>
|
||||
<span style="margin-left: 20px"><i style="font-size: 90%" class="el-icon-info"></i>
|
||||
произойдет переход на https-версию читалки и откроется окно для ввода ключа
|
||||
</span><br>
|
||||
<span style="margin-left: 20px"><i style="font-size: 90%" class="el-icon-info"></i>
|
||||
подтвердив ввод ключа нажатием "OK", включив синхронизацию с сервером и выбрав профиль устройства, вы восстановите все ваши настройки в новой версии
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
Старая http-версия сайта будет доступна до конца 2019 года.<br>
|
||||
Приносим извинения за доставленные неудобства.
|
||||
</div>
|
||||
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="migrationDialogDisable">Больше не показывать</el-button>
|
||||
<el-button @click="migrationDialogRemind">Напомнить позже</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
</el-main>
|
||||
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
@@ -282,8 +200,6 @@ class Reader extends Vue {
|
||||
|
||||
whatsNewVisible = false;
|
||||
whatsNewContent = '';
|
||||
migrationVisible1 = false;
|
||||
migrationVisible2 = false;
|
||||
|
||||
created() {
|
||||
this.loading = true;
|
||||
@@ -320,15 +236,6 @@ class Reader extends Vue {
|
||||
});
|
||||
|
||||
this.loadSettings();
|
||||
|
||||
//TODO: убрать в будущем
|
||||
if (this.showToolButton['history']) {
|
||||
const newShowToolButton = Object.assign({}, this.showToolButton);
|
||||
newShowToolButton['recentBooks'] = true;
|
||||
delete newShowToolButton['history'];
|
||||
const newSettings = Object.assign({}, this.settings, { showToolButton: newShowToolButton });
|
||||
this.commit('reader/setSettings', newSettings);
|
||||
}
|
||||
}
|
||||
|
||||
mounted() {
|
||||
@@ -352,7 +259,6 @@ class Reader extends Vue {
|
||||
this.loading = false;
|
||||
|
||||
await this.showWhatsNew();
|
||||
await this.showMigration();
|
||||
|
||||
this.updateRoute();
|
||||
})();
|
||||
@@ -366,7 +272,6 @@ class Reader extends Vue {
|
||||
this.clickControl = settings.clickControl;
|
||||
this.blinkCachedLoad = settings.blinkCachedLoad;
|
||||
this.showWhatsNewDialog = settings.showWhatsNewDialog;
|
||||
this.showMigrationDialog = settings.showMigrationDialog;
|
||||
this.showToolButton = settings.showToolButton;
|
||||
this.enableSitesFilter = settings.enableSitesFilter;
|
||||
|
||||
@@ -432,33 +337,6 @@ class Reader extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
async showMigration() {
|
||||
await utils.sleep(3000);
|
||||
if (!this.settingsActive &&
|
||||
this.mode == 'omnireader' && this.showMigrationDialog && this.migrationRemindDate != utils.formatDate(new Date(), 'coDate')) {
|
||||
if (window.location.protocol == 'http:') {
|
||||
this.migrationVisible1 = true;
|
||||
} else if (window.location.protocol == 'https:') {
|
||||
this.migrationVisible2 = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
migrationDialogDisable() {
|
||||
this.migrationVisible1 = false;
|
||||
this.migrationVisible2 = false;
|
||||
if (this.showMigrationDialog) {
|
||||
const newSettings = Object.assign({}, this.settings, { showMigrationDialog: false });
|
||||
this.commit('reader/setSettings', newSettings);
|
||||
}
|
||||
}
|
||||
|
||||
migrationDialogRemind() {
|
||||
this.migrationVisible1 = false;
|
||||
this.migrationVisible2 = false;
|
||||
this.commit('reader/setMigrationRemindDate', utils.formatDate(new Date(), 'coDate'));
|
||||
}
|
||||
|
||||
openVersionHistory() {
|
||||
this.whatsNewVisible = false;
|
||||
this.versionHistoryToggle();
|
||||
@@ -577,10 +455,6 @@ class Reader extends Vue {
|
||||
return this.$store.state.reader.whatsNewContentHash;
|
||||
}
|
||||
|
||||
get migrationRemindDate() {
|
||||
return this.$store.state.reader.migrationRemindDate;
|
||||
}
|
||||
|
||||
addAction(pos) {
|
||||
let a = this.actionList;
|
||||
if (!a.length || a[a.length - 1] != pos) {
|
||||
@@ -950,7 +824,8 @@ class Reader extends Vue {
|
||||
return;
|
||||
}
|
||||
|
||||
let url = opts.url;
|
||||
let url = encodeURI(decodeURI(opts.url));
|
||||
|
||||
if ((url.indexOf('http://') != 0) && (url.indexOf('https://') != 0) &&
|
||||
(url.indexOf('file://') != 0))
|
||||
url = 'http://' + url;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<span v-show="loading"><i class="el-icon-loading" style="font-size: 25px"></i> <span style="position: relative; top: -4px">Список загружается</span></span>
|
||||
</template>
|
||||
|
||||
<a ref="download" style='display: none;'></a>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
style="width: 570px"
|
||||
@@ -72,7 +73,7 @@
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<a v-show="isUrl(scope.row.url)" :href="scope.row.url" target="_blank">Оригинал</a><br>
|
||||
<a :href="scope.row.path" :download="getFileNameFromPath(scope.row.path)">Скачать FB2</a>
|
||||
<a :href="scope.row.path" @click.prevent="downloadBook(scope.row.path)">Скачать FB2</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
@@ -104,6 +105,7 @@ import _ from 'lodash';
|
||||
import * as utils from '../../../share/utils';
|
||||
import Window from '../../share/Window.vue';
|
||||
import bookManager from '../share/bookManager';
|
||||
import readerApi from '../../../api/reader';
|
||||
|
||||
export default @Component({
|
||||
components: {
|
||||
@@ -209,7 +211,7 @@ class RecentBooksPage extends Vue {
|
||||
a.middleName
|
||||
]).join(' '));
|
||||
author = authorNames.join(', ');
|
||||
} else {
|
||||
} else {//TODO: убрать в будущем
|
||||
author = _.compact([
|
||||
fb2.lastName,
|
||||
fb2.firstName,
|
||||
@@ -268,8 +270,20 @@ class RecentBooksPage extends Vue {
|
||||
return result;
|
||||
}
|
||||
|
||||
getFileNameFromPath(fb2Path) {
|
||||
return path.basename(fb2Path).substr(0, 10) + '.fb2';
|
||||
async downloadBook(fb2path) {
|
||||
try {
|
||||
await readerApi.checkUrl(fb2path);
|
||||
|
||||
const d = this.$refs.download;
|
||||
d.href = fb2path;
|
||||
d.download = path.basename(fb2path).substr(0, 10) + '.fb2';
|
||||
d.click();
|
||||
} catch (e) {
|
||||
let errMes = e.message;
|
||||
if (errMes.indexOf('404') >= 0)
|
||||
errMes = 'Файл не найден на сервере (возможно был удален как устаревший)';
|
||||
this.$alert(errMes, 'Ошибка', {type: 'error'});
|
||||
}
|
||||
}
|
||||
|
||||
openOriginal(url) {
|
||||
|
||||
@@ -471,18 +471,10 @@
|
||||
<el-checkbox v-model="showWhatsNewDialog">Показывать уведомление "Что нового"</el-checkbox>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
<el-form-item label="Уведомление">
|
||||
<el-tooltip :open-delay="500" effect="light">
|
||||
<template slot="content">
|
||||
Показывать диалог о переходе на httpS-версию
|
||||
</template>
|
||||
<el-checkbox v-model="showMigrationDialog">Уведомлять о переходе на httpS-версию</el-checkbox>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-form :model="form" size="mini" label-width="120px" @submit.native.prevent>
|
||||
<div class="partHeader">Прочее</div>
|
||||
<div class="partHeader">Другое</div>
|
||||
|
||||
<el-form-item label="Парам. в URL">
|
||||
<el-tooltip :open-delay="500" effect="light">
|
||||
|
||||
@@ -225,30 +225,23 @@ class TextPage extends Vue {
|
||||
|
||||
//scrolling page
|
||||
const pageSpace = this.scrollHeight - this.pageLineCount*this.lineHeight;
|
||||
let y = pageSpace/2;
|
||||
let top = pageSpace/2;
|
||||
if (this.showStatusBar)
|
||||
y += this.statusBarHeight*(this.statusBarTop ? 1 : 0);
|
||||
let page1 = this.$refs.scrollBox1;
|
||||
let page2 = this.$refs.scrollBox2;
|
||||
top += this.statusBarHeight*(this.statusBarTop ? 1 : 0);
|
||||
let page1 = this.$refs.scrollBox1.style;
|
||||
let page2 = this.$refs.scrollBox2.style;
|
||||
|
||||
page1.style.perspective = '3072px';
|
||||
page2.style.perspective = '3072px';
|
||||
page1.perspective = page2.perspective = '3072px';
|
||||
|
||||
page1.style.width = this.w + this.indentLR + 'px';
|
||||
page2.style.width = this.w + this.indentLR + 'px';
|
||||
page1.style.height = this.scrollHeight - (pageSpace > 0 ? pageSpace : 0) + 'px';
|
||||
page2.style.height = this.scrollHeight - (pageSpace > 0 ? pageSpace : 0) + 'px';
|
||||
page1.style.top = y + 'px';
|
||||
page2.style.top = y + 'px';
|
||||
page1.style.left = this.indentLR + 'px';
|
||||
page2.style.left = this.indentLR + 'px';
|
||||
page1.width = page2.width = this.w + this.indentLR + 'px';
|
||||
page1.height = page2.height = this.scrollHeight - (pageSpace > 0 ? pageSpace : 0) + 'px';
|
||||
page1.top = page2.top = top + 'px';
|
||||
page1.left = page2.left = this.indentLR + 'px';
|
||||
|
||||
page1 = this.$refs.scrollingPage1;
|
||||
page2 = this.$refs.scrollingPage2;
|
||||
page1.style.width = this.w + this.indentLR + 'px';
|
||||
page2.style.width = this.w + this.indentLR + 'px';
|
||||
page1.style.height = this.scrollHeight + this.lineHeight + 'px';
|
||||
page2.style.height = this.scrollHeight + this.lineHeight + 'px';
|
||||
page1 = this.$refs.scrollingPage1.style;
|
||||
page2 = this.$refs.scrollingPage2.style;
|
||||
page1.width = page2.width = this.w + this.indentLR + 'px';
|
||||
page1.height = page2.height = this.scrollHeight + this.lineHeight + 'px';
|
||||
}
|
||||
|
||||
async checkLoadedFonts() {
|
||||
@@ -334,13 +327,15 @@ class TextPage extends Vue {
|
||||
|
||||
this.draw();
|
||||
|
||||
// шрифты хрен знает когда подгружаются в div, поэтому
|
||||
const parsed = this.parsed;
|
||||
await sleep(5000);
|
||||
if (this.parsed === parsed) {
|
||||
parsed.force = true;
|
||||
this.draw();
|
||||
parsed.force = false;
|
||||
// ширина шрифта некоторое время выдается неверно, поэтому
|
||||
if (!omitLoadFonts) {
|
||||
const parsed = this.parsed;
|
||||
await sleep(100);
|
||||
if (this.parsed === parsed) {
|
||||
parsed.force = true;
|
||||
this.draw();
|
||||
parsed.force = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,9 +32,6 @@ export default class BookParser {
|
||||
|
||||
//defaults
|
||||
let fb2 = {
|
||||
firstName: '',
|
||||
middleName: '',
|
||||
lastName: '',
|
||||
bookTitle: '',
|
||||
};
|
||||
|
||||
@@ -240,6 +237,7 @@ export default class BookParser {
|
||||
newParagraph(' ', 1);
|
||||
isFirstTitlePara = true;
|
||||
bold = true;
|
||||
center = true;
|
||||
}
|
||||
|
||||
if (tag == 'epigraph') {
|
||||
@@ -282,6 +280,7 @@ export default class BookParser {
|
||||
if (tag == 'subtitle') {
|
||||
isFirstTitlePara = false;
|
||||
bold = false;
|
||||
center = false;
|
||||
}
|
||||
|
||||
if (tag == 'epigraph') {
|
||||
@@ -367,11 +366,10 @@ export default class BookParser {
|
||||
tClose += (bold ? '</strong>' : '');
|
||||
tClose += (center ? '</center>' : '');
|
||||
|
||||
if (path.indexOf('/fictionbook/body/title') == 0) {
|
||||
growParagraph(`${tOpen}${text}${tClose}`, text.length);
|
||||
}
|
||||
|
||||
if (path.indexOf('/fictionbook/body/section') == 0) {
|
||||
if (path.indexOf('/fictionbook/body/title') == 0 ||
|
||||
path.indexOf('/fictionbook/body/section') == 0 ||
|
||||
path.indexOf('/fictionbook/body/epigraph') == 0
|
||||
) {
|
||||
growParagraph(`${tOpen}${text}${tClose}`, text.length);
|
||||
}
|
||||
|
||||
|
||||
@@ -319,7 +319,6 @@ class BookManager {
|
||||
|
||||
metaOnly(book) {
|
||||
let result = Object.assign({}, book);
|
||||
delete result.data;//можно будет убрать эту строку со временем
|
||||
delete result.parsed;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,52 @@
|
||||
export const versionHistory = [
|
||||
{
|
||||
showUntil: '2020-01-06',
|
||||
header: '0.8.1 (2020-01-07)',
|
||||
content:
|
||||
`
|
||||
<ul>
|
||||
<li>добавлена частичная поддержка формата FB3</li>
|
||||
<li>исправлен баг "Request path contains unescaped characters"</li>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
|
||||
{
|
||||
showUntil: '2020-01-05',
|
||||
header: '0.8.0 (2020-01-02)',
|
||||
content:
|
||||
`
|
||||
<ul>
|
||||
<li>окончательный переход на https</li>
|
||||
<li>код проекта теперь Open Source: <a href="https://github.com/bookpauk/liberama" target="_blank">https://github.com/bookpauk/liberama</a></li>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
|
||||
{
|
||||
showUntil: '2019-11-26',
|
||||
header: '0.7.9 (2019-11-27)',
|
||||
content:
|
||||
`
|
||||
<ul>
|
||||
<li>добавлен неубираемый баннер для http-версии о переходе на httpS</li>
|
||||
<li>исправления багов</li>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
|
||||
{
|
||||
showUntil: '2019-11-24',
|
||||
header: '0.7.8 (2019-11-25)',
|
||||
content:
|
||||
`
|
||||
<ul>
|
||||
<li>улучшение html-фильтров для сайтов</li>
|
||||
<li>исправления багов</li>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
|
||||
{
|
||||
showUntil: '2019-11-10',
|
||||
header: '0.7.7 (2019-11-06)',
|
||||
|
||||
@@ -182,7 +182,6 @@ const settingDefaults = {
|
||||
imageFitWidth: true,
|
||||
showServerStorageMessages: true,
|
||||
showWhatsNewDialog: true,
|
||||
showMigrationDialog: true,
|
||||
enableSitesFilter: true,
|
||||
|
||||
fontShifts: {},
|
||||
@@ -205,7 +204,6 @@ const state = {
|
||||
profilesRev: 0,
|
||||
allowProfilesSave: false,//подстраховка для разработки
|
||||
whatsNewContentHash: '',
|
||||
migrationRemindDate: '',
|
||||
currentProfile: '',
|
||||
settings: Object.assign({}, settingDefaults),
|
||||
settingsRev: {},
|
||||
@@ -240,9 +238,6 @@ const mutations = {
|
||||
setWhatsNewContentHash(state, value) {
|
||||
state.whatsNewContentHash = value;
|
||||
},
|
||||
setMigrationRemindDate(state, value) {
|
||||
state.migrationRemindDate = value;
|
||||
},
|
||||
setCurrentProfile(state, value) {
|
||||
state.currentProfile = value;
|
||||
},
|
||||
|
||||
BIN
docs/assets/face.jpg
Normal file
BIN
docs/assets/face.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
BIN
docs/assets/reader.jpg
Normal file
BIN
docs/assets/reader.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
84
docs/omnireader/README.md
Normal file
84
docs/omnireader/README.md
Normal file
@@ -0,0 +1,84 @@
|
||||
## Разворачивание сервера OmniReader в Ubuntu:
|
||||
|
||||
### git, clone
|
||||
```
|
||||
sudo apt install ssh git
|
||||
git clone https://github.com/bookpauk/liberama
|
||||
```
|
||||
|
||||
### node.js
|
||||
```
|
||||
sudo apt install -y curl
|
||||
curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
|
||||
sudo apt install -y nodejs
|
||||
```
|
||||
|
||||
### install packages
|
||||
```
|
||||
cd liberama
|
||||
npm i
|
||||
```
|
||||
|
||||
### create public dir
|
||||
```
|
||||
sudo mkdir /home/liberama
|
||||
sudo chown www-data.www-data /home/liberama
|
||||
```
|
||||
|
||||
### external converter `calibre`, download from https://download.calibre-ebook.com/
|
||||
```
|
||||
wget "https://download.calibre-ebook.com/3.39.1/calibre-3.39.1-x86_64.txz"
|
||||
sudo -u www-data mkdir -p /home/liberama/data/calibre
|
||||
sudo -u www-data tar xvf calibre-3.39.1-x86_64.txz -C /home/liberama/data/calibre
|
||||
```
|
||||
|
||||
### external converters
|
||||
```
|
||||
sudo apt install libreoffice
|
||||
sudo apt install poppler-utils
|
||||
```
|
||||
|
||||
### nginx, server config
|
||||
Для своего домена необходимо будет подправить docs/omnireader/omnireader.
|
||||
Можно также настроить сервер для HTTP, без SSL.
|
||||
```
|
||||
sudo apt install nginx
|
||||
sudo cp docs/omnireader/omnireader /etc/nginx/sites-available/omnireader
|
||||
sudo ln -s /etc/nginx/sites-available/omnireader /etc/nginx/sites-enabled/omnireader
|
||||
sudo rm /etc/nginx/sites-enabled/default
|
||||
sudo service nginx reload
|
||||
sudo chown -R www-data.www-data /var/www
|
||||
```
|
||||
|
||||
### certbot
|
||||
Следовать инструкции установки certbot https://certbot.eff.org/lets-encrypt/ubuntubionic-nginx
|
||||
### old.omnireader
|
||||
```
|
||||
sudo apt install php7.2 php7.2-curl php7.2-mbstring php7.2-fpm
|
||||
sudo service php7.2-fpm restart
|
||||
|
||||
sudo mkdir /home/oldreader
|
||||
sudo chown www-data.www-data /home/oldreader
|
||||
sudo -u www-data cp -r docs/omnireader/old/* /home/oldreader
|
||||
```
|
||||
|
||||
## Деплой и запуск
|
||||
```
|
||||
cd docs/omnireader
|
||||
./deploy.sh
|
||||
./run_server.sh
|
||||
```
|
||||
|
||||
После первого запуска будет создан конфигурационный файл `/home/liberama/data/config.json`.
|
||||
Необходимо переключить приложение в режим `omnireader`, отредактировав опцию `servers`:
|
||||
```
|
||||
"servers": [
|
||||
{
|
||||
"serverName": "1",
|
||||
"mode": "omnireader",
|
||||
"ip": "0.0.0.0",
|
||||
"port": "44081"
|
||||
}
|
||||
]
|
||||
```
|
||||
и перезапустить `run_server.sh`
|
||||
@@ -1,2 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
npm run build:linux
|
||||
sudo -u www-data cp -r ../../dist/linux/* /home/liberama
|
||||
|
||||
@@ -36,26 +36,7 @@ server {
|
||||
listen 80;
|
||||
server_name omnireader.ru;
|
||||
|
||||
client_max_body_size 50m;
|
||||
|
||||
gzip on;
|
||||
gzip_min_length 1024;
|
||||
gzip_proxied expired no-cache no-store private auth;
|
||||
gzip_types *;
|
||||
|
||||
location /api {
|
||||
proxy_pass http://127.0.0.1:44081;
|
||||
}
|
||||
|
||||
location /tmp {
|
||||
root /home/liberama/public;
|
||||
add_header Content-Type text/xml;
|
||||
add_header Content-Encoding gzip;
|
||||
}
|
||||
|
||||
location / {
|
||||
root /home/liberama/public;
|
||||
}
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
sudo bash
|
||||
|
||||
mkdir /home/liberama
|
||||
chown www-data.www-data /home/liberama
|
||||
|
||||
### oldreader
|
||||
# ubuntu 18
|
||||
apt install php7.2 php7.2-curl php7.2-mbstring php7.2-fpm
|
||||
service php7.2-fpm restart
|
||||
|
||||
mkdir /home/oldreader
|
||||
chown www-data /home/oldreader
|
||||
chgrp www-data /home/oldreader
|
||||
sudo -u www-data cp -r ./old/* /home/oldreader
|
||||
###
|
||||
|
||||
### external converter
|
||||
# calibre releases https://download.calibre-ebook.com/
|
||||
# download, unpack to data/calibre
|
||||
# 3.39.1
|
||||
wget "https://download.calibre-ebook.com/3.39.1/calibre-3.39.1-x86_64.txz"
|
||||
sudo -u www-data mkdir -p /home/liberama/data/calibre
|
||||
sudo -u www-data tar xvf calibre-3.39.1-x86_64.txz -C /home/liberama/data/calibre
|
||||
|
||||
apt install libreoffice
|
||||
apt install poppler-utils
|
||||
###
|
||||
|
||||
apt install nginx
|
||||
|
||||
cp omnireader /etc/nginx/sites-available/omnireader
|
||||
ln -s /etc/nginx/sites-available/omnireader /etc/nginx/sites-enabled/omnireader
|
||||
rm /etc/nginx/sites-enabled/default
|
||||
service nginx reload
|
||||
|
||||
chown -R www-data.www-data /var/www
|
||||
|
||||
exit
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
sudo -H -u www-data sh -c "cd /var/www; /home/liberama/liberama"
|
||||
@@ -1,12 +0,0 @@
|
||||
# Разворачивание среды:
|
||||
|
||||
# GIT REPO
|
||||
sudo apt install ssh git
|
||||
git clone
|
||||
|
||||
#nodejs
|
||||
sudo apt install -y curl
|
||||
curl -sL https://deb.nodesource.com/setup_10.x | sudo bash -
|
||||
sudo apt install -y nodejs
|
||||
npm i
|
||||
|
||||
847
package-lock.json
generated
847
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"name": "Liberama",
|
||||
"version": "0.7.7",
|
||||
"version": "0.8.1",
|
||||
"author": "Book Pauk <bookpauk@gmail.com>",
|
||||
"license": "CC0-1.0",
|
||||
"repository": "bookpauk/liberama",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
@@ -27,21 +30,18 @@
|
||||
"babel-plugin-transform-decorators-legacy": "^1.3.5",
|
||||
"babel-preset-env": "^1.3.2",
|
||||
"clean-webpack-plugin": "^1.0.1",
|
||||
"copy-webpack-plugin": "^4.6.0",
|
||||
"copy-webpack-plugin": "^5.1.1",
|
||||
"css-loader": "^1.0.0",
|
||||
"disable-output-webpack-plugin": "^1.0.1",
|
||||
"element-theme-chalk": "^2.12.0",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-plugin-html": "^5.0.5",
|
||||
"eslint-plugin-node": "^8.0.0",
|
||||
"eslint-plugin-vue": "^5.2.3",
|
||||
"event-hooks-webpack-plugin": "^2.1.4",
|
||||
"file-loader": "^3.0.1",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"mini-css-extract-plugin": "^0.5.0",
|
||||
"null-loader": "^0.1.1",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||
"pkg": "4.3.7",
|
||||
"pkg": "^4.4.2",
|
||||
"terser-webpack-plugin": "^1.4.1",
|
||||
"url-loader": "^1.1.2",
|
||||
"vue-class-component": "^6.3.2",
|
||||
@@ -77,7 +77,7 @@
|
||||
"safe-buffer": "^5.2.0",
|
||||
"sjcl": "^1.0.8",
|
||||
"sql-template-strings": "^2.2.2",
|
||||
"sqlite": "3.0.0",
|
||||
"sqlite": "^3.0.3",
|
||||
"tar-fs": "^2.0.0",
|
||||
"unbzip2-stream": "^1.3.3",
|
||||
"vue": "github:paulkamer/vue#fix_palemoon_clickhandlers_dist",
|
||||
|
||||
@@ -41,9 +41,9 @@ class ConfigManager {
|
||||
process.env.NODE_ENV = this.branch;
|
||||
|
||||
this.branchConfigFile = __dirname + `/${this.branch}.js`;
|
||||
await fs.access(this.branchConfigFile);
|
||||
this._config = require(this.branchConfigFile);
|
||||
|
||||
await fs.ensureDir(this._config.dataDir);
|
||||
this._userConfigFile = `${this._config.dataDir}/config.json`;
|
||||
|
||||
this.inited = true;
|
||||
@@ -83,6 +83,7 @@ class ConfigManager {
|
||||
async save() {
|
||||
if (!this.inited)
|
||||
throw new Error('not inited');
|
||||
|
||||
const dataToSave = _.pick(this._config, propsToSave);
|
||||
await fs.writeFile(this.userConfigFile, JSON.stringify(dataToSave, null, 4));
|
||||
}
|
||||
|
||||
@@ -4,14 +4,15 @@ const path = require('path');
|
||||
const ConvertBase = require('./ConvertBase');
|
||||
|
||||
class ConvertDocX extends ConvertBase {
|
||||
check(data, opts) {
|
||||
async check(data, opts) {
|
||||
const {inputFiles} = opts;
|
||||
if (this.config.useExternalBookConverter &&
|
||||
inputFiles.sourceFileType && inputFiles.sourceFileType.ext == 'zip') {
|
||||
//ищем файл '[Content_Types].xml'
|
||||
for (const file of inputFiles.files) {
|
||||
if (file.path == '[Content_Types].xml') {
|
||||
return true;
|
||||
const contentTypes = await fs.readFile(`${inputFiles.filesDir}/${file.path}`, 'utf8');
|
||||
return contentTypes.indexOf('/word/document.xml') >= 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +31,7 @@ class ConvertDocX extends ConvertBase {
|
||||
}
|
||||
|
||||
async run(data, opts) {
|
||||
if (!this.check(data, opts))
|
||||
if (!(await this.check(data, opts)))
|
||||
return false;
|
||||
await this.checkExternalConverterPresent();
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ class ConvertFb2 extends ConvertBase {
|
||||
const right = data.indexOf('?>', left);
|
||||
if (right >= 0) {
|
||||
const head = data.slice(left, right + 2).toString();
|
||||
const m = head.match(/encoding="(.*)"/);
|
||||
const m = head.match(/encoding="(.*?)"/);
|
||||
if (m) {
|
||||
let encoding = m[1].toLowerCase();
|
||||
if (encoding != 'utf-8') {
|
||||
|
||||
52
server/core/Reader/BookConverter/ConvertFb3.js
Normal file
52
server/core/Reader/BookConverter/ConvertFb3.js
Normal file
@@ -0,0 +1,52 @@
|
||||
const fs = require('fs-extra');
|
||||
|
||||
const ConvertHtml = require('./ConvertHtml');
|
||||
|
||||
class ConvertDocX extends ConvertHtml {
|
||||
async check(data, opts) {
|
||||
const {inputFiles} = opts;
|
||||
if (this.config.useExternalBookConverter &&
|
||||
inputFiles.sourceFileType && inputFiles.sourceFileType.ext == 'zip') {
|
||||
//ищем файл '[Content_Types].xml'
|
||||
for (const file of inputFiles.files) {
|
||||
if (file.path == '[Content_Types].xml') {
|
||||
const contentTypes = await fs.readFile(`${inputFiles.filesDir}/${file.path}`, 'utf8');
|
||||
return contentTypes.indexOf('/fb3/body.xml') >= 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
getTitle(text) {
|
||||
let title = '';
|
||||
const m = text.match(/<title>([\s\S]*?)<\/title>/);
|
||||
if (m)
|
||||
title = m[1];
|
||||
|
||||
return title.trim();
|
||||
}
|
||||
|
||||
async run(data, opts) {
|
||||
if (!(await this.check(data, opts)))
|
||||
return false;
|
||||
await this.checkExternalConverterPresent();
|
||||
|
||||
const {inputFiles} = opts;
|
||||
|
||||
let text = await fs.readFile(`${inputFiles.filesDir}/fb3/body.xml`, 'utf8');
|
||||
|
||||
const title = this.getTitle(text)
|
||||
.replace(/<\/?p>/g, '')
|
||||
;
|
||||
text = `<title>${title}</title>` + text
|
||||
.replace(/<title>/g, '<br><b>')
|
||||
.replace(/<\/title>/g, '</b><br>')
|
||||
.replace(/<subtitle>/g, '<br><br><subtitle>')
|
||||
;
|
||||
return await super.run(Buffer.from(text), {skipCheck: true, cutTitle: true});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ConvertDocX;
|
||||
@@ -39,16 +39,19 @@ class ConvertHtml extends ConvertBase {
|
||||
|
||||
let title = '';
|
||||
let inTitle = false;
|
||||
let inSubTitle = false;
|
||||
let inImage = false;
|
||||
let image = {};
|
||||
let bold = false;
|
||||
let italic = false;
|
||||
let begining = true;
|
||||
|
||||
let spaceCounter = [];
|
||||
|
||||
const repCrLfTab = (text) => text.replace(/[\n\r]/g, '').replace(/\t/g, ' ');
|
||||
|
||||
const newParagraph = () => {
|
||||
begining = false;
|
||||
pars.push({_n: 'p', _t: ''});
|
||||
};
|
||||
|
||||
@@ -58,6 +61,8 @@ class ConvertHtml extends ConvertBase {
|
||||
|
||||
const l = pars.length;
|
||||
pars[l - 1]._t += text;
|
||||
if (inSubTitle)
|
||||
pars[l - 1]._n = '';
|
||||
|
||||
//посчитаем отступы у текста, чтобы выделить потом параграфы
|
||||
const lines = text.split('\n');
|
||||
@@ -77,16 +82,21 @@ class ConvertHtml extends ConvertBase {
|
||||
}
|
||||
};
|
||||
|
||||
const newPara = new Set(['tr', '/table', 'hr', 'br', 'br/', 'li', 'dt', 'dd', 'p', 'title', '/title', 'h1', 'h2', 'h3', '/h1', '/h2', '/h3']);
|
||||
const newPara = new Set(['tr', '/table', 'hr', 'br', 'br/', 'li', 'dt', 'dd', 'p', 'title', '/title', 'ul', '/ul', 'h1', 'h2', 'h3', 'h4', 'h5', '/h1', '/h2', '/h3', '/h4', '/h5']);
|
||||
const newPara2 = new Set(['h1', 'h2', 'h3', 'h4', 'h5']);
|
||||
|
||||
const onTextNode = (text, cutCounter, cutTag) => {// eslint-disable-line no-unused-vars
|
||||
text = this.escapeEntities(text);
|
||||
|
||||
if (!cutCounter && !(cutTitle && inTitle)) {
|
||||
let tOpen = (bold ? '<strong>' : '');
|
||||
let tOpen = '';
|
||||
tOpen += (inSubTitle ? '<subtitle>' : '');
|
||||
tOpen += (bold ? '<strong>' : '');
|
||||
tOpen += (italic ? '<emphasis>' : '');
|
||||
let tClose = (italic ? '</emphasis>' : '');
|
||||
let tClose = ''
|
||||
tClose += (italic ? '</emphasis>' : '');
|
||||
tClose += (bold ? '</strong>' : '');
|
||||
tClose += (inSubTitle ? '</subtitle>' : '');
|
||||
|
||||
growParagraph(`${tOpen}${text}${tClose}`);
|
||||
}
|
||||
@@ -106,6 +116,8 @@ class ConvertHtml extends ConvertBase {
|
||||
|
||||
const onStartNode = (tag, tail, singleTag, cutCounter, cutTag) => {// eslint-disable-line no-unused-vars
|
||||
if (!cutCounter) {
|
||||
if (newPara2.has(tag) && !begining)
|
||||
newParagraph();
|
||||
if (newPara.has(tag))
|
||||
newParagraph();
|
||||
|
||||
@@ -130,6 +142,10 @@ class ConvertHtml extends ConvertBase {
|
||||
cutTitle = true;
|
||||
}
|
||||
|
||||
if (tag == 'subtitle') {
|
||||
inSubTitle = true;
|
||||
}
|
||||
|
||||
if (tag == 'fb2-image') {
|
||||
inImage = true;
|
||||
const attrs = sax.getAttrsSync(tail);
|
||||
@@ -141,6 +157,8 @@ class ConvertHtml extends ConvertBase {
|
||||
if (!cutCounter) {
|
||||
if (newPara.has('/' + tag))
|
||||
newParagraph();
|
||||
if (newPara2.has('/' + tag))
|
||||
newParagraph();
|
||||
|
||||
switch (tag) {
|
||||
case 'i':
|
||||
@@ -160,6 +178,9 @@ class ConvertHtml extends ConvertBase {
|
||||
if (tag == 'title' || tag == 'cut-title')
|
||||
inTitle = false;
|
||||
|
||||
if (tag == 'subtitle')
|
||||
inSubTitle = false;
|
||||
|
||||
if (tag == 'fb2-image')
|
||||
inImage = false;
|
||||
};
|
||||
@@ -197,7 +218,8 @@ class ConvertHtml extends ConvertBase {
|
||||
while (i > 0 && (!spaceCounter[i] || spaceCounter[i] < total)) i--;
|
||||
}
|
||||
|
||||
const parIndent = (i > 0 ? i : 0);
|
||||
let parIndent = (i > 0 ? i : 0);
|
||||
if (parIndent > 2) parIndent--;
|
||||
|
||||
let newPars = [];
|
||||
const newPar = () => {
|
||||
@@ -233,7 +255,7 @@ class ConvertHtml extends ConvertBase {
|
||||
l++;
|
||||
}
|
||||
|
||||
if (l >= parIndent) {
|
||||
if (l >= parIndent || line == '') {
|
||||
if (j > 0)
|
||||
newPar();
|
||||
j++;
|
||||
@@ -250,6 +272,7 @@ class ConvertHtml extends ConvertBase {
|
||||
//убираем лишнее, делаем валидный fb2, т.к. в рез-те разбиения на параграфы бьются теги
|
||||
bold = false;
|
||||
italic = false;
|
||||
inSubTitle = false;
|
||||
pars = body.section._a[0];
|
||||
for (let i = 0; i < pars.length; i++) {
|
||||
if (pars[i]._n != 'p')
|
||||
@@ -259,16 +282,24 @@ class ConvertHtml extends ConvertBase {
|
||||
|
||||
if (pars[i]._t.indexOf('<') >= 0 || bold || italic) {
|
||||
const t = pars[i]._t;
|
||||
let first = true;
|
||||
|
||||
let a = [];
|
||||
|
||||
const onTextNode = (text) => {
|
||||
let tOpen = (bold ? '<strong>' : '');
|
||||
let tOpen = '';
|
||||
tOpen += (inSubTitle ? '<subtitle>' : '');
|
||||
tOpen += (bold ? '<strong>' : '');
|
||||
tOpen += (italic ? '<emphasis>' : '');
|
||||
let tClose = (italic ? '</emphasis>' : '');
|
||||
let tClose = ''
|
||||
tClose += (italic ? '</emphasis>' : '');
|
||||
tClose += (bold ? '</strong>' : '');
|
||||
tClose += (inSubTitle ? '</subtitle>' : '');
|
||||
|
||||
if (first)
|
||||
text = text.replace(/^\s+/, ''); //trimLeft
|
||||
a.push(`${tOpen}${text}${tClose}`);
|
||||
first = false;
|
||||
}
|
||||
|
||||
const onStartNode = (tag) => {
|
||||
@@ -276,6 +307,8 @@ class ConvertHtml extends ConvertBase {
|
||||
bold = true;
|
||||
if (tag == 'emphasis')
|
||||
italic = true;
|
||||
if (tag == 'subtitle')
|
||||
inSubTitle = true;
|
||||
}
|
||||
|
||||
const onEndNode = (tag) => {
|
||||
@@ -283,6 +316,8 @@ class ConvertHtml extends ConvertBase {
|
||||
bold = false;
|
||||
if (tag == 'emphasis')
|
||||
italic = false;
|
||||
if (tag == 'subtitle')
|
||||
inSubTitle = false;
|
||||
}
|
||||
|
||||
sax.parseSync(t, { onStartNode, onEndNode, onTextNode });
|
||||
|
||||
@@ -102,6 +102,8 @@ class ConvertSamlib extends ConvertBase {
|
||||
case 'h1':
|
||||
case 'h2':
|
||||
case 'h3':
|
||||
case 'h4':
|
||||
case 'h5':
|
||||
if (inPara)
|
||||
closeTag('p');
|
||||
openTag('p');
|
||||
@@ -173,6 +175,8 @@ class ConvertSamlib extends ConvertBase {
|
||||
case 'h1':
|
||||
case 'h2':
|
||||
case 'h3':
|
||||
case 'h4':
|
||||
case 'h5':
|
||||
closeTag('p');
|
||||
bold = false;
|
||||
break;
|
||||
|
||||
@@ -12,7 +12,10 @@ const sitesFilter = {
|
||||
converter: 'cutter',
|
||||
begin: `<!-- BEGIN section where work skin applies -->`,
|
||||
end: `<!-- END work skin -->`,
|
||||
}
|
||||
},
|
||||
'flibusta.is': {
|
||||
converter: 'flibusta'
|
||||
},
|
||||
};
|
||||
|
||||
class ConvertSites extends ConvertHtml {
|
||||
@@ -54,11 +57,11 @@ class ConvertSites extends ConvertHtml {
|
||||
if (m)
|
||||
title = m[1];
|
||||
|
||||
return `<title>${title.trim()}</title>`;
|
||||
return title.trim();
|
||||
}
|
||||
|
||||
cutter(text, opts) {
|
||||
const title = this.getTitle(text);
|
||||
const title = `<title>${this.getTitle(text)}</title>`;
|
||||
const l = text.indexOf(opts.begin) + opts.begin.length;
|
||||
const r = text.indexOf(opts.end);
|
||||
if (l < 0 || r < 0 || r <= l)
|
||||
@@ -66,6 +69,42 @@ class ConvertSites extends ConvertHtml {
|
||||
|
||||
return text.substring(l, r) + title;
|
||||
}
|
||||
|
||||
flibusta(text) {
|
||||
let author = '';
|
||||
let m = text.match(/- <a href=".+">([\s\S]*?)<\/a><br\/?>/);
|
||||
if (m)
|
||||
author = m[1];
|
||||
|
||||
let book = this.getTitle(text);
|
||||
book = book.replace(' (fb2) | Флибуста', '');
|
||||
|
||||
const title = `<title>${author}${(author ? ' - ' : '')}${book}</title>`;
|
||||
|
||||
let begin = '<h3 class="book">';
|
||||
if (text.indexOf(begin) <= 0)
|
||||
begin = '<h3 class=book>';
|
||||
|
||||
const end = '<div id="footer">';
|
||||
|
||||
const l = text.indexOf(begin);
|
||||
const r = text.indexOf(end);
|
||||
if (l < 0 || r < 0 || r <= l)
|
||||
return false;
|
||||
|
||||
return text.substring(l, r)
|
||||
.replace(/blockquote class="?book"?/g, 'p')
|
||||
.replace(/<br\/?>\s*<\/h3>/g, '</h3>')
|
||||
.replace(/<h3 class="?book"?>/g, '<br><br><subtitle>')
|
||||
.replace(/<h5 class="?book"?>/g, '<br><br><subtitle>')
|
||||
.replace(/<h3>/g, '<br><br><subtitle>')
|
||||
.replace(/<h5>/g, '<br><br><subtitle>')
|
||||
.replace(/<\/h3>/g, '</subtitle><br>')
|
||||
.replace(/<\/h5>/g, '</subtitle><br>')
|
||||
.replace(/<div class="?stanza"?>/g, '<br>')
|
||||
.replace(/<div>/g, '<br>')
|
||||
+ title;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ConvertSites;
|
||||
|
||||
@@ -7,6 +7,7 @@ const convertClassFactory = [
|
||||
require('./ConvertPdf'),
|
||||
require('./ConvertRtf'),
|
||||
require('./ConvertDocX'),
|
||||
require('./ConvertFb3'),
|
||||
require('./ConvertDoc'),
|
||||
require('./ConvertMobi'),
|
||||
require('./ConvertFb2'),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
require('tls').DEFAULT_MIN_VERSION = 'TLSv1';
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const argv = require('minimist')(process.argv.slice(2));
|
||||
@@ -18,7 +19,7 @@ async function init() {
|
||||
const log = appLogger.log;
|
||||
|
||||
//dirs
|
||||
log(`${config.name} v${config.version}`);
|
||||
log(`${config.name} v${config.version}, Node.js ${process.version}`);
|
||||
log('Initializing');
|
||||
|
||||
await fs.ensureDir(config.dataDir);
|
||||
@@ -40,7 +41,7 @@ async function init() {
|
||||
await connManager.init(config);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
async function main() {
|
||||
const log = new (require('./core/AppLogger'))().log;//singleton
|
||||
const config = new (require('./config'))().config;//singleton
|
||||
|
||||
|
||||
Reference in New Issue
Block a user