Compare commits

...

399 Commits

Author SHA1 Message Date
Book Pauk
e72ca0de7e Merge branch 'release/0.9.5' 2020-11-01 19:55:45 +07:00
Book Pauk
c44c27d3d2 Версия 0.9.5 2020-11-01 19:36:13 +07:00
Book Pauk
df4e201ccd Небольшие поправки 2020-11-01 19:33:02 +07:00
Book Pauk
c8c0e9ec1a Добавлена кнопка 'Обновить с разбиением на параграфы' 2020-11-01 16:42:20 +07:00
Book Pauk
9a4a84a367 Сертификаты для beta.liberama 2020-11-01 15:00:23 +07:00
Book Pauk
1dc3424411 Убрал лишнее 2020-11-01 14:53:51 +07:00
Book Pauk
c13745e913 Добавлен конфиг nginx для beta.liberama 2020-11-01 14:50:18 +07:00
Book Pauk
25c12309f2 Поправки поведения диалога на "Enter" 2020-11-01 14:41:40 +07:00
Book Pauk
4b632da5af Поправки поведения Select 2020-11-01 14:17:35 +07:00
Book Pauk
87c364b8ee Исправление поведения компонента select 2020-11-01 13:38:05 +07:00
Book Pauk
efa48fbc8a Поправки margin окна 2020-11-01 11:42:54 +07:00
Book Pauk
21df6c1d21 Поправка goToLink 2020-11-01 11:34:51 +07:00
Book Pauk
39d2ceb94b Мелкая поправка для fullscreen 2020-11-01 11:26:58 +07:00
Book Pauk
1dad013d60 Исправлен баг синхронизации при первом включении опции 2020-11-01 11:23:15 +07:00
Book Pauk
add7a03f88 Убираем ссылку на гитхаб для liberama.top 2020-10-31 21:20:30 +07:00
Book Pauk
0cefaa6d48 Мелкая поправка 2020-10-30 19:16:06 +07:00
Book Pauk
f08e73f359 Merge tag '0.9.4' into develop
Версия 0.9.4
2020-10-30 19:13:17 +07:00
Book Pauk
cf9ce26438 Merge branch 'release/0.9.4' 2020-10-30 19:13:00 +07:00
Book Pauk
fd74a5a82e 0.9.4 2020-10-30 19:12:49 +07:00
Book Pauk
3109104928 Замена event.code на event.key 2020-10-30 19:07:33 +07:00
Book Pauk
b1ec4df2e4 Вернул обратно хук на keydown 2020-10-30 18:15:12 +07:00
Book Pauk
1609e149a8 Поправлен конфиг 2020-10-30 17:01:58 +07:00
Book Pauk
298c483d0e Исправления скриптов запуска сервера для крона 2020-10-30 16:53:54 +07:00
Book Pauk
dc917b75b1 Убрал дебаг 2020-10-30 16:11:02 +07:00
Book Pauk
ad32bdab44 Исправление проблемы нескольких одновременных попыток подключения по WebSocket 2020-10-30 16:02:09 +07:00
Book Pauk
5e8b2e1c87 Отладка 2020-10-30 12:22:54 +07:00
Book Pauk
08b4afd287 Libs добавлены в синхронизацию 2020-10-30 12:01:22 +07:00
Book Pauk
0de31f643b Мелкая поправка 2020-10-30 11:35:41 +07:00
Book Pauk
5e815eb3c4 Мелкий рефакторинг 2020-10-30 11:30:21 +07:00
Book Pauk
48c93a2120 Небольшие поправки 2020-10-30 11:23:18 +07:00
Book Pauk
ad5de42172 Небольшие поправки 2020-10-30 11:13:09 +07:00
Book Pauk
32bafedaad Небольшие доработки 2020-10-30 00:10:42 +07:00
Book Pauk
c67fd11be9 Небольшие доделки 2020-10-30 00:00:05 +07:00
Book Pauk
8b59c72848 Небольшие доработки 2020-10-29 23:39:13 +07:00
Book Pauk
c35b2f3bfc Работа над LibsPage 2020-10-29 23:29:10 +07:00
Book Pauk
ac63ad4612 Версия 0.9.4 2020-10-29 23:28:42 +07:00
Book Pauk
e1dea1c752 Версия 0.9.4 2020-10-29 23:28:17 +07:00
Book Pauk
25648e2327 Работа над LibsPage 2020-10-29 23:05:26 +07:00
Book Pauk
18ac04bb0f Работа над ExternalLibs 2020-10-29 22:54:02 +07:00
Book Pauk
5263ee58b2 Работа над ExternalLibs 2020-10-29 21:09:23 +07:00
Book Pauk
af542b89f7 Работа над LibsPage 2020-10-29 16:31:09 +07:00
Book Pauk
684b675fca Поправка конфига 2020-10-29 15:40:32 +07:00
Book Pauk
c29044eca1 Работа над LibsPage 2020-10-29 15:35:09 +07:00
Book Pauk
a36510fcc8 Мелкая поправка 2020-10-29 15:27:28 +07:00
Book Pauk
bc21ace416 Работа над LibsPage 2020-10-29 15:14:27 +07:00
Book Pauk
57e521e2ff Работа над LibsPage 2020-10-29 14:45:10 +07:00
Book Pauk
ac6ebb9e8d Настройки перенаправления 2020-10-28 23:41:15 +07:00
Book Pauk
54bef54635 Поправки конфига nginx 2020-10-28 22:47:30 +07:00
Book Pauk
593e201f79 Работа над LibsPage (ExternalLibs) 2020-10-28 22:45:05 +07:00
Book Pauk
d7b24253fe Работа над LibsPage 2020-10-28 22:04:47 +07:00
Book Pauk
33961abd86 Поправлен баг редиректа 2020-10-28 21:06:07 +07:00
Book Pauk
37e0e1d42f Добавки для правильной работы с nginx 2020-10-28 20:42:47 +07:00
Book Pauk
1121f9c918 Работа над LibsPage 2020-10-28 20:30:57 +07:00
Book Pauk
582203f5da Работа над LibsPage (ExternalLibs) 2020-10-28 20:14:42 +07:00
Book Pauk
8c0f193738 Исправлен баг редиректа 2020-10-28 18:28:28 +07:00
Book Pauk
ebe42956ad Все компоненты загружаются динамически 2020-10-28 18:27:24 +07:00
Book Pauk
b8f8df8927 Поправил настройки nginx 2020-10-28 11:19:31 +07:00
Book Pauk
2c66ca4fdd Поправки конфига nginx 2020-10-27 23:46:44 +07:00
Book Pauk
49f813e880 Работа над LibsPage 2020-10-27 23:10:45 +07:00
Book Pauk
da6fed80d1 Работа над LibsPage 2020-10-27 23:02:28 +07:00
Book Pauk
b901d9b8c9 Убрал эксперимент 2020-10-27 17:45:08 +07:00
Book Pauk
b41d46ac57 Эксперимент 2020-10-27 17:40:56 +07:00
Book Pauk
4f0189f3e0 Эксперимент 2020-10-27 16:24:59 +07:00
Book Pauk
c956e7a802 Убрал дебаг 2020-10-27 14:34:12 +07:00
Book Pauk
dcbc8409e0 Работа над LibsPage 2020-10-27 14:31:07 +07:00
Book Pauk
fd58568cf0 Работа над LibsPage 2020-10-27 14:16:30 +07:00
Book Pauk
0f81fa53d2 Работа над LibsPage 2020-10-27 13:30:05 +07:00
Book Pauk
44655dc81c Quasar update 2020-10-27 13:28:54 +07:00
Book Pauk
749667aefd Работа над LibsPage 2020-10-27 11:17:49 +07:00
Book Pauk
dd94418c26 Процесс добавления нового окна: "Библиотеки" 2020-10-26 00:23:30 +07:00
Book Pauk
55a5375e46 Скрываем ссылки "Отзывы о читалке" и "Старая версия" в режиме "liberama.top" 2020-10-25 23:36:02 +07:00
Book Pauk
df76de7352 Поправки для нового сайта liberama.top 2020-10-25 18:35:42 +07:00
Book Pauk
1fb1a1b2b1 Временная настройка liberama.top на beta-среду 2020-10-25 18:08:04 +07:00
Book Pauk
f998edb2aa Добавлены сертификаты для ssl 2020-10-25 17:46:14 +07:00
Book Pauk
7c2cb9a0c7 Поправка дока в связи с переименованиями 2020-10-25 17:39:26 +07:00
Book Pauk
0690a365da Добавлена настройка прокси для flibusta.is 2020-10-25 17:35:53 +07:00
Book Pauk
a20d05aba8 Переименования в доках, добавлены настройки nginx для сайта liberama.top 2020-10-25 17:30:34 +07:00
Book Pauk
4362ae95ba Поправки по результату тестирования 2020-10-24 17:25:36 +07:00
Book Pauk
d658814399 Поправки по результатам тестирования 2020-10-24 13:11:17 +07:00
Book Pauk
39e14d70ee Исправление бага распаковки Zip-архивов с плохими именами файлов 2020-10-13 16:24:35 +07:00
Book Pauk
2e58cfdb75 Исправлен мелкий баг PageDown 2020-09-18 18:05:32 +07:00
Book Pauk
fcaa724c00 Убрал ya-share 2020-09-18 17:45:27 +07:00
Book Pauk
8806b4141e Поправки истории версий 2020-09-18 17:43:05 +07:00
Book Pauk
7bd159766b Исправление бага исчезновения картинок при включенной настройке "Убирать пустые строки" 2020-09-18 17:40:37 +07:00
Book Pauk
4df15d603f Merge tag '0.9.3-2' into develop
0.9.3-2
2020-06-30 16:54:31 +07:00
Book Pauk
b453c3efe5 Merge branch 'release/0.9.3-2' 2020-06-30 16:54:25 +07:00
Book Pauk
56590ef8a4 Исправление бага при определении пустых параграфов 2020-06-30 16:51:43 +07:00
Book Pauk
7c133136b9 Merge tag '0.9.3-1' into develop
0.9.3-1
2020-06-04 19:13:23 +07:00
Book Pauk
41881639aa Merge branch 'release/0.9.3-1' 2020-06-04 19:13:15 +07:00
Book Pauk
416003f078 Поправки сигнатур файлов 2020-06-04 19:12:05 +07:00
Book Pauk
bbfcd0efa3 Merge tag '0.9.3' into develop
0.9.3
2020-05-21 05:04:37 +07:00
Book Pauk
150e4332c3 Merge branch 'release/0.9.3' 2020-05-21 05:04:17 +07:00
Book Pauk
49649765c7 Версия 0.9.3 2020-05-21 05:03:08 +07:00
Book Pauk
726b7bfa93 Вернул обратно 2020-05-21 05:02:10 +07:00
Book Pauk
265f838868 Эксперимент 2020-05-21 04:58:32 +07:00
Book Pauk
6e2e5b5520 Вернул обратно 2020-05-21 04:54:39 +07:00
Book Pauk
100ea2f64a Эксперимент 2020-05-21 04:50:16 +07:00
Book Pauk
4e7ed1ee33 Поправки Content-Type 2020-05-21 04:36:03 +07:00
Book Pauk
8ab6aed1aa Поправлен баг 2020-05-21 04:04:57 +07:00
Book Pauk
4ff096014c Эксперимент 2020-05-21 02:56:44 +07:00
Book Pauk
03b60b6ca9 Эксперимент 2020-05-21 02:24:53 +07:00
Book Pauk
e30b832e05 Эксперимент 2020-05-21 02:06:17 +07:00
Book Pauk
e646de85a7 Поправка настроек SW 2020-05-21 01:51:47 +07:00
Book Pauk
70a7a0e344 Замена sw-precache-webpack-plugin на workbox-webpack-plugin 2020-05-21 01:31:39 +07:00
Book Pauk
b444abeb3e Исправлен баг перехвата клавиш в диалогах 2020-05-21 00:14:47 +07:00
Book Pauk
c72f56917d Quasar upgrade 2020-05-21 00:06:20 +07:00
Book Pauk
192283d6b2 Добавил gitignore 2020-05-20 23:13:08 +07:00
Book Pauk
6be6fa1966 Merge tag '0.9.2-2' into develop
0.9.2-2
2020-05-01 14:33:54 +07:00
Book Pauk
510553b055 Merge branch 'release/0.9.2-2' 2020-05-01 14:33:45 +07:00
Book Pauk
6c4616892e Поправлен баг распознавания html 2020-05-01 14:32:31 +07:00
Book Pauk
1e79a099b8 Merge tag '0.9.2-1' into develop
0.9.2-1
2020-04-15 16:37:17 +07:00
Book Pauk
31a22327f1 Merge branch 'release/0.9.2-1' 2020-04-15 16:37:10 +07:00
Book Pauk
c1712bebc6 0.9.2-1 2020-04-15 16:36:29 +07:00
Book Pauk
cd91541245 Исправлен баг "Не удалось определить формат файла" при загрузке html-страниц 2020-04-15 16:35:05 +07:00
Book Pauk
4c1fc83256 Merge tag '0.9.2' into develop
0.9.2
2020-04-15 15:37:59 +07:00
Book Pauk
34c7a33576 Merge branch 'release/0.9.2' 2020-04-15 15:37:21 +07:00
Book Pauk
23ecfeeb4f Версия 0.9.2 2020-04-15 15:36:42 +07:00
Book Pauk
9703f83eb3 Мелкая поправка 2020-04-15 15:32:32 +07:00
Book Pauk
0f3cc03d00 Мелкие поправки 2020-03-19 19:44:06 +07:00
Book Pauk
6f7ba1f9fc Окончание работы над хоткеями 2020-03-19 19:38:40 +07:00
Book Pauk
e1b85e4a1b Рефакторинг 2020-03-19 19:09:39 +07:00
Book Pauk
b308dd58cc Рефакторинг 2020-03-19 17:12:12 +07:00
Book Pauk
9f4c0479ce Рефакторинг 2020-03-19 17:05:29 +07:00
Book Pauk
2c57817dde Рефакторинг 2020-03-19 16:38:31 +07:00
Book Pauk
ba85c54d7c Рефакторинг 2020-03-19 15:57:56 +07:00
Book Pauk
a80e5c3a65 Поправки текста 2020-03-19 15:46:32 +07:00
Book Pauk
22e2c34da8 Работа над хоткеями 2020-03-18 20:20:06 +07:00
Book Pauk
00a8e4c2c5 Работа над хоткеями 2020-03-18 20:04:44 +07:00
Book Pauk
10d0a4079c Работа над хоткеями 2020-03-18 19:00:02 +07:00
Book Pauk
589f7f3c22 Работа над хоткеями 2020-03-18 18:20:57 +07:00
Book Pauk
d1126a7eb0 Работа над хоткеями, промежуточный коммит 2020-03-18 15:37:19 +07:00
Book Pauk
9f4e72a0e1 Работа над хоткеями, промежуточный коммит 2020-03-18 15:06:29 +07:00
Book Pauk
a024295379 Мелкий рефакторинг 2020-03-15 21:48:53 +07:00
Book Pauk
dc2b2ec488 Мелкая доработка 2020-03-15 21:46:35 +07:00
Book Pauk
0c5f5975aa Работа над хоткеями 2020-03-15 21:44:26 +07:00
Book Pauk
dc3f682d2d Работа над хоткеями 2020-03-15 21:29:58 +07:00
Book Pauk
2db8876c66 Рефакторинг, работа над хоткеями 2020-03-15 20:58:06 +07:00
Book Pauk
8f6201b0f7 Работа над хоткеями 2020-03-15 16:12:55 +07:00
Book Pauk
4b146c70ad Мелкий рефакторинг 2020-03-08 18:53:40 +07:00
Book Pauk
0118034b4b Запись в историю 2020-03-06 18:10:22 +07:00
Book Pauk
39217053ca Исправление багов 2020-03-06 18:00:10 +07:00
Book Pauk
fba190c826 Переход на ServiceWorker вместо appcache 2020-03-06 15:20:56 +07:00
Book Pauk
5e9d528e16 Поправка конфига nginx 2020-03-05 14:35:10 +07:00
Book Pauk
c5921d88fc Добавлена настройка certbot 2020-03-05 14:31:48 +07:00
Book Pauk
eb980b0ea1 Удалил ненужное 2020-03-05 14:23:16 +07:00
Book Pauk
de5b4216f7 Добавлены конфиги для среды beta 2020-03-05 13:46:52 +07:00
Book Pauk
495ff57b19 Добавлен пакет sw-precache-webpack-plugin 2020-03-05 13:46:21 +07:00
Book Pauk
57948cf6e3 Мелкий рефакторинг 2020-03-04 15:45:13 +07:00
Book Pauk
1aebbbcabd Мелкий рефакторинг 2020-03-04 15:32:05 +07:00
Book Pauk
25b4cb072d Обновление пакетов 2020-03-04 15:15:53 +07:00
Book Pauk
1cdacc3a08 Merge tag '0.9.1' into develop
0.9.1
2020-03-03 21:48:07 +07:00
Book Pauk
34d9466d09 Merge branch 'release/0.9.1' 2020-03-03 21:48:01 +07:00
Book Pauk
c182c4ce66 Версия 0.9.1 2020-03-03 21:47:13 +07:00
Book Pauk
dbb9bd1282 Добавлено сохранение валидных uploaded-файлов в удаленном хранилище 2020-03-03 18:06:46 +07:00
Book Pauk
8019d2d6cc Мелкая поправка 2020-03-03 16:44:21 +07:00
Book Pauk
459cdb2e0b Мелкие поправки 2020-03-03 16:41:38 +07:00
Book Pauk
a230cd9513 Добавлена настройка "Открывать оригинал по клику" 2020-03-03 12:51:40 +07:00
Book Pauk
0c44a25e85 Перенос чекбокса в другой раздел 2020-03-03 12:30:31 +07:00
Book Pauk
34f3d04370 Апдейт пакетов 2020-03-02 19:08:17 +07:00
Book Pauk
1f3e6b7e16 Удалены более ненужные пакеты 2020-03-02 17:24:39 +07:00
Book Pauk
47d49a200a Merge tag '0.9.0-1' into develop
0.9.0-1
2020-02-26 18:10:56 +07:00
Book Pauk
e1767d6e52 Merge branch 'release/0.9.0-1' 2020-02-26 18:10:48 +07:00
Book Pauk
0f8e343cd2 Поправлен баг 2020-02-26 18:10:02 +07:00
Book Pauk
23ab487baf Merge tag '0.9.0' into develop
0.9.0
2020-02-26 17:48:45 +07:00
Book Pauk
22e5d38ef5 Merge branch 'release/0.9.0' 2020-02-26 17:48:37 +07:00
Book Pauk
5819ccb528 Версия 0.9.0 2020-02-26 17:47:26 +07:00
Book Pauk
42a2fd77cf Удаление element-ui 2020-02-26 17:44:03 +07:00
Book Pauk
ab93a8b0b3 Merge branch 'feature/quasar' into develop 2020-02-26 17:31:50 +07:00
Book Pauk
84437eafa6 Поправки для смартфонов 2020-02-26 17:31:08 +07:00
Book Pauk
0107d848e0 Мелкая поправка 2020-02-26 16:58:32 +07:00
Book Pauk
5eeac96a0d Поправки цветов 2020-02-26 16:52:38 +07:00
Book Pauk
9351c115be Переход на quasar 2020-02-26 16:44:01 +07:00
Book Pauk
f95a11096c Переход на quasar 2020-02-26 16:40:26 +07:00
Book Pauk
4203d179e6 Переход на quasar 2020-02-26 14:34:30 +07:00
Book Pauk
78dfc9cb1c Переход на quasar 2020-02-26 14:32:47 +07:00
Book Pauk
0bef307d77 Переход на quasar 2020-02-26 14:16:17 +07:00
Book Pauk
b0da806f7a Переход на quasar 2020-02-26 13:45:03 +07:00
Book Pauk
badecd1d81 Переход на quasar 2020-02-26 13:42:22 +07:00
Book Pauk
6418e8ee30 Переход на quasar 2020-02-26 13:40:06 +07:00
Book Pauk
09115c9658 Переход на quasar 2020-02-26 13:26:15 +07:00
Book Pauk
74e3866bd7 Поправки цветов таба 2020-02-26 13:08:25 +07:00
Book Pauk
408de78c13 Переход на quasar 2020-02-25 22:02:18 +07:00
Book Pauk
c0451c18b3 Переход на quasar 2020-02-25 21:59:22 +07:00
Book Pauk
f303d26c1e Переход на quasar 2020-02-25 21:52:55 +07:00
Book Pauk
1b58a34859 Переход на quasar 2020-02-25 21:51:11 +07:00
Book Pauk
82ea416e67 Переход на quasar 2020-02-25 21:46:30 +07:00
Book Pauk
efd4fbad70 Переход на quasar 2020-02-25 21:07:12 +07:00
Book Pauk
01bd15121b Мелкий рефакторинг 2020-02-25 20:46:58 +07:00
Book Pauk
a9c2495349 Переход на quasar 2020-02-25 20:42:34 +07:00
Book Pauk
e7c50b50ed Переход на quasar 2020-02-25 17:37:41 +07:00
Book Pauk
6e25b289d2 Переход на quasar 2020-02-24 13:09:10 +07:00
Book Pauk
157267eaf7 Мелкая поправка 2020-02-24 12:56:03 +07:00
Book Pauk
a317f9137a Переход на quasar 2020-02-24 12:32:58 +07:00
Book Pauk
5dad3d22ea Переход на quasar 2020-02-24 12:29:47 +07:00
Book Pauk
be85df456b Переход на quasar 2020-02-24 12:21:52 +07:00
Book Pauk
2e172a08c7 Доработка NumInput 2020-02-20 21:06:21 +07:00
Book Pauk
bb1069ca60 Переход на quasar 2020-02-20 18:45:31 +07:00
Book Pauk
d8141a1628 Переход на quasar 2020-02-20 18:30:40 +07:00
Book Pauk
de9f7c4baf Переход на quasar 2020-02-20 18:24:00 +07:00
Book Pauk
fa9b3116f1 Переход на quasar 2020-02-20 18:20:59 +07:00
Book Pauk
dcf9d52961 Переход на quasar 2020-02-20 18:00:03 +07:00
Book Pauk
1da93e2cc7 Доработка NumInput 2020-02-20 16:46:58 +07:00
Book Pauk
1d1bab988e Переход на quasar 2020-02-20 16:44:28 +07:00
Book Pauk
dcc6ad3af3 Доработка NumInput 2020-02-19 15:11:54 +07:00
Book Pauk
d57f266789 Доработка NumInput 2020-02-19 15:11:15 +07:00
Book Pauk
c3395e1eff Переход на quasar 2020-02-19 11:43:29 +07:00
Book Pauk
ca59ec2dbe Переход на quasar 2020-02-18 21:06:44 +07:00
Book Pauk
79788125f3 Убрал дебаг 2020-02-18 20:52:42 +07:00
Book Pauk
2154f20fa4 Мелкая поправка 2020-02-18 20:52:12 +07:00
Book Pauk
afe40b6a89 Доработка NumInput 2020-02-18 20:52:00 +07:00
Book Pauk
ba4b3bd6b8 Доработки NumInput 2020-02-18 13:49:55 +07:00
Book Pauk
e423b5d745 Переход на quasar 2020-02-18 13:29:15 +07:00
Book Pauk
6de8eca7ea Переход на quasar 2020-02-18 12:46:18 +07:00
Book Pauk
9d68cfcaf0 Улучшена загрузка внешних шрифтов 2020-02-17 11:53:03 +07:00
Book Pauk
225de11e6a Переход на quasar 2020-02-17 11:26:05 +07:00
Book Pauk
916581bbd0 Поправлен баг 2020-02-16 11:30:48 +07:00
Book Pauk
1cbb35840f Переход на quasar 2020-02-15 20:58:17 +07:00
Book Pauk
7a1d769e39 Мелкие поправки 2020-02-15 20:58:07 +07:00
Book Pauk
8254bf934c Переход на quasar 2020-02-14 21:24:05 +07:00
Book Pauk
5e2f20542f Переход на quasar 2020-02-14 21:09:49 +07:00
Book Pauk
551a707ee4 Переход на quasar 2020-02-14 19:32:00 +07:00
Book Pauk
024b15b4f9 Мелкие поправки 2020-02-14 12:53:15 +07:00
Book Pauk
1935df4143 Merge branch 'develop' into feature/quasar 2020-02-14 11:51:50 +07:00
Book Pauk
3f99f90076 Merge tag '0.8.4-1' into develop
0.8.4-1
2020-02-14 11:46:37 +07:00
Book Pauk
53cb445dde Merge branch 'release/0.8.4-1' 2020-02-14 11:46:28 +07:00
Book Pauk
6e46947220 Исправлен баг 2020-02-14 11:44:39 +07:00
Book Pauk
9b65e1671b Переход на quasar 2020-02-14 11:35:35 +07:00
Book Pauk
d5c741db35 Переход на quasar 2020-02-12 16:05:31 +07:00
Book Pauk
11e0780b6e Переход на quasar 2020-02-12 16:03:42 +07:00
Book Pauk
f153541570 Переход на quasar 2020-02-11 15:17:47 +07:00
Book Pauk
f066af88e7 Поправка формата заголовка 2020-02-11 15:05:43 +07:00
Book Pauk
97e1eef799 Поправлено дефолтное значение для pageChangeAnimation 2020-02-11 15:00:10 +07:00
Book Pauk
1bcd902817 Закрыта дыра безопасности 2020-02-11 13:02:43 +07:00
Book Pauk
2484568b21 Доработка общения по вебсокету 2020-02-11 11:16:32 +07:00
Book Pauk
085cc47ea5 Переход на quasar 2020-02-10 17:13:12 +07:00
Book Pauk
aac36a88f3 Переход на quasar 2020-02-10 16:39:14 +07:00
Book Pauk
1f2ebc82b7 Поправлен мелкий баг 2020-02-10 13:56:11 +07:00
Book Pauk
9781949064 Переход на quasar 2020-02-09 22:06:57 +07:00
Book Pauk
b06ef3781a Переход на quasar 2020-02-09 21:22:18 +07:00
Book Pauk
b32213cb7b Небольшие доработки 2020-02-09 17:11:39 +07:00
Book Pauk
ac4c7d2421 Переход на quasar 2020-02-09 17:11:11 +07:00
Book Pauk
824a49b80f Переход на quasar 2020-02-09 16:49:08 +07:00
Book Pauk
13efd50d80 Добавлен универсальный includer для включения файлов в исходник 2020-02-09 16:43:36 +07:00
Book Pauk
6fb091d20f Мелкие доработки 2020-02-09 11:13:35 +07:00
Book Pauk
518ab85cae Небольшие доработки 2020-02-08 20:50:47 +07:00
Book Pauk
f5124ad8b5 Мелкие поправки 2020-02-08 20:24:15 +07:00
Book Pauk
6f80900aa8 Добавлена анимация слеша на страницу 2020-02-08 20:12:55 +07:00
Book Pauk
06b80e9281 Поправлены размеры иконок на кнопках 2020-02-08 11:59:59 +07:00
Book Pauk
51b39d9365 Мелкие поправки 2020-02-07 19:59:47 +07:00
Book Pauk
f7d2d8fc95 Мелкие поправки 2020-02-07 19:57:53 +07:00
Book Pauk
f34fb94c1a Убрал лишнее 2020-02-07 19:52:42 +07:00
Book Pauk
3107224e50 Переход на quasar 2020-02-07 19:51:23 +07:00
Book Pauk
e1c481c534 Поправлена сортировка 2020-02-07 19:26:00 +07:00
Book Pauk
945a2dd3eb Переход на quasar 2020-02-07 18:56:51 +07:00
Book Pauk
e318945eb1 Переход на quasar 2020-02-07 16:18:32 +07:00
Book Pauk
926709568d Merge branch 'develop' into feature/quasar 2020-02-06 22:04:31 +07:00
Book Pauk
da040e799c Merge tag '0.8.4' into develop
0.8.4
2020-02-06 21:55:56 +07:00
Book Pauk
694976cb6e Merge branch 'release/0.8.4' 2020-02-06 21:55:48 +07:00
Book Pauk
3f7bd1846a Версия 0.8.4 2020-02-06 21:50:58 +07:00
Book Pauk
714898b4c3 Добавлен paypal-адрес для пожертвований 2020-02-06 21:49:20 +07:00
Book Pauk
4efc9b6990 Merge tag '0.8.3-4' into develop
0.8.3-4
2020-02-06 21:07:35 +07:00
Book Pauk
73c3beaff1 Merge branch 'release/0.8.3-4' 2020-02-06 21:07:28 +07:00
Book Pauk
a6bdccd4ef Доработки конвертирования из буфера обмена 2020-02-06 21:04:40 +07:00
Book Pauk
8007991e7d Merge tag '0.8.3-3' into develop
0.8.3-3
2020-02-06 20:27:29 +07:00
Book Pauk
0e5d1ed1c3 Merge branch 'release/0.8.3-3' 2020-02-06 20:27:20 +07:00
Book Pauk
91dc2f4f71 Поправки логирования 2020-02-06 20:25:49 +07:00
Book Pauk
950bab3023 Добавлено декодирование имен файлов при распаковке Zip-архива в случае,
если кодировка имени не дает создать файл на диске
2020-02-06 20:20:29 +07:00
Book Pauk
29082a10e6 Рефакторинг 2020-02-06 20:13:33 +07:00
Book Pauk
65c1227d88 Удален node-stream-zip, т.к. в него внесены ручные правки 2020-02-06 20:12:01 +07:00
Book Pauk
5d121a68cf Поправки скриптов деплоя и запуска, добавлен авторестарт при падении сервера 2020-02-06 16:40:13 +07:00
Book Pauk
ad07d2b8b1 Переход на quasar 2020-02-05 21:16:05 +07:00
Book Pauk
c5aef78085 Поправки иконки 2020-02-05 19:46:48 +07:00
Book Pauk
522ebc8aa2 К предыдущему 2020-02-05 15:36:24 +07:00
Book Pauk
199b3761b5 Мелкая поправка 2020-02-05 15:34:57 +07:00
Book Pauk
daf7b45e45 Поправка иконки 2020-02-05 15:31:24 +07:00
Book Pauk
fc71b953c7 update quasar 2020-02-05 15:28:09 +07:00
Book Pauk
74ccd4a001 Переход на иконки line-awesome 2020-02-05 15:27:26 +07:00
Book Pauk
3c09f6ca55 Переход на quasar 2020-02-05 14:21:50 +07:00
Book Pauk
c7dbe8599d Поправки вида слайдера 2020-02-05 14:20:46 +07:00
Book Pauk
ca036b6676 Поправки копирования в буфер обмена, поправки текста 2020-02-04 19:51:19 +07:00
Book Pauk
5ae87c8e03 Рефакторинг 2020-02-04 19:45:06 +07:00
Book Pauk
9774fc4f65 Небольшие доработки 2020-02-04 19:17:43 +07:00
Book Pauk
d0891fb652 Поправлен баг вычисления rootRoute 2020-02-04 16:29:10 +07:00
Book Pauk
e388e2a1c7 Переименование 2020-02-04 16:16:31 +07:00
Book Pauk
d9ab354338 Небольшая поправка 2020-02-04 14:36:45 +07:00
Book Pauk
9ea0a0e214 К предыдущему 2020-02-04 13:39:46 +07:00
Book Pauk
131ddf0355 Поправил цвет 2020-02-04 13:37:52 +07:00
Book Pauk
8abe71a0fe Поправлен баг загрузки шрифтов 2020-02-04 13:30:48 +07:00
Book Pauk
43e27a7e68 Переход на quasar 2020-02-04 13:08:49 +07:00
Book Pauk
b784d277e4 Небольшие поправки 2020-02-04 12:29:54 +07:00
Book Pauk
cb443157da Переход на quasar 2020-02-04 12:20:12 +07:00
Book Pauk
c886015d92 Поправлен tooltip delay 2020-02-03 16:57:54 +07:00
Book Pauk
3161247da9 Рефакторинг 2020-02-03 16:57:39 +07:00
Book Pauk
743a250131 Поправлена иконка 2020-02-03 16:16:13 +07:00
Book Pauk
4fb4b21a9e Поправил цвет кнопки 2020-02-03 16:07:25 +07:00
Book Pauk
e1a7d3ebc5 Мелкие поправки 2020-02-03 16:05:36 +07:00
Book Pauk
72b8b156ac Переход на quasar 2020-02-03 15:49:27 +07:00
Book Pauk
134dafb608 Поправлен баг 2020-02-03 15:39:09 +07:00
Book Pauk
d5102b6422 Мелкая поправка 2020-02-03 15:38:20 +07:00
Book Pauk
a2cfb9d423 Переход на quasar 2020-02-03 15:33:54 +07:00
Book Pauk
bef70f94ab Переход на quasar 2020-02-03 15:12:28 +07:00
Book Pauk
4233fffe74 Переход на quasar 2020-02-03 14:23:07 +07:00
Book Pauk
81c214748d Закомментировал пока неиспользуемые компоненты 2020-02-03 02:22:16 +07:00
Book Pauk
c6a61dc8c8 Переход на quasar 2020-02-03 02:20:46 +07:00
Book Pauk
483092d40d Небольшие поправки 2020-02-03 01:57:04 +07:00
Book Pauk
88cb02f6bc Мелкие поправки 2020-02-03 01:39:52 +07:00
Book Pauk
9628188730 Переход на quasar 2020-02-03 01:25:57 +07:00
Book Pauk
2e66134bf8 Переход на quasar 2020-02-03 01:10:58 +07:00
Book Pauk
424fe4d1e9 Переход на quasar 2020-02-03 01:01:10 +07:00
Book Pauk
2b6f9568de Мелкий рефакторинг 2020-02-02 23:25:28 +07:00
Book Pauk
4b270bce8b Переход на quasar 2020-02-02 23:15:59 +07:00
Book Pauk
6b077e67db Мелкие поправки 2020-02-02 20:17:31 +07:00
Book Pauk
4c79ea0679 Мелкая поправка 2020-02-02 18:20:25 +07:00
Book Pauk
8c4c4c25aa Переход на quasar 2020-02-02 18:17:30 +07:00
Book Pauk
a37dbe2c06 Переход на quasar 2020-02-02 16:49:12 +07:00
Book Pauk
5e10cb2d16 Переход на quasar 2020-02-02 15:55:04 +07:00
Book Pauk
58316c5c1d Переход на quasar 2020-02-02 15:37:36 +07:00
Book Pauk
55f092f161 Переход на quasar 2020-02-02 15:14:22 +07:00
Book Pauk
ab5049127a Переход на quasar 2020-02-02 15:03:50 +07:00
Book Pauk
5f99067e56 Переход на quasar 2020-02-02 14:24:43 +07:00
Book Pauk
3a89e61bd8 Небольшие поправки 2020-02-02 12:58:13 +07:00
Book Pauk
06edfa2fee Поправки цветов заголовка 2020-02-02 12:48:07 +07:00
Book Pauk
77bfd72458 Переход на quasar 2020-01-31 21:14:22 +07:00
Book Pauk
5ddf19be4d Удалил мусор 2020-01-31 21:14:09 +07:00
Book Pauk
6657b47746 Небольшие поправки 2020-01-31 20:41:17 +07:00
Book Pauk
5690efb07a Переход на quasar 2020-01-31 20:21:25 +07:00
Book Pauk
05600cba08 Переход на quasar 2020-01-31 19:51:53 +07:00
Book Pauk
e3b4120b2c Переход на quasar 2020-01-31 18:35:30 +07:00
Book Pauk
1059245fd9 Мелкая поправка 2020-01-31 18:17:39 +07:00
Book Pauk
87c8d310b3 Переход на quasar, добавлены иконки 2020-01-31 18:14:38 +07:00
Book Pauk
fdc4999556 Merge branch 'develop' into feature/quasar 2020-01-31 17:03:46 +07:00
Book Pauk
d28a8db4ff Добавлен альтернативный метод вычисления ширины строки в пикселях 2020-01-31 16:59:34 +07:00
Book Pauk
ab9e7d10dd Добавлен отлов ошибок при инициализации, добавлена генерация ошибки measureText 2020-01-31 16:08:37 +07:00
Book Pauk
3ff72b26b9 0.8.3 2020-01-31 14:52:49 +07:00
Book Pauk
107ae70651 Переход на quasar 2020-01-31 14:49:59 +07:00
Book Pauk
04de19033e Переход на quasar 2020-01-31 14:49:44 +07:00
Book Pauk
089ac70cd3 Используем css-классы quasar 2020-01-30 18:53:01 +07:00
Book Pauk
ae40a9ead9 К предыдущему 2020-01-30 18:03:10 +07:00
Book Pauk
152806b7f6 Рефакторинг 2020-01-30 18:01:02 +07:00
Book Pauk
06beb8e704 Рефакторинг 2020-01-30 17:55:24 +07:00
Book Pauk
64f2b94685 0.8.3 2020-01-30 16:44:34 +07:00
Book Pauk
5a42eb98ab Merge branch 'develop' into feature/quasar 2020-01-30 16:36:17 +07:00
Book Pauk
404b87d78d Небольшие поправки 2020-01-30 16:34:05 +07:00
Book Pauk
dcb8fbdbf4 Merge tag '0.8.3-2' into develop
0.8.3-2
2020-01-29 01:03:20 +07:00
Book Pauk
0fe513d7f5 Merge branch 'release/0.8.3-2' 2020-01-29 01:02:56 +07:00
Book Pauk
0be05325e4 Исправлен баг 2020-01-29 01:02:05 +07:00
Book Pauk
75b39308cd Merge tag '0.8.3-1' into develop
0.8.3-1
2020-01-28 21:32:25 +07:00
Book Pauk
35ded81713 Merge branch 'release/0.8.3-1' 2020-01-28 21:32:02 +07:00
Book Pauk
07c85280cd Исправлены таймауты для конвертера calibre, добавлен флаг запуска -vv, соответственно поправлено вычисление прогресса 2020-01-28 21:27:54 +07:00
Book Pauk
43f1d86be0 Merge tag '0.8.3' into develop
0.8.3
2020-01-28 20:21:40 +07:00
Book Pauk
82f5ed4c44 Merge branch 'release/0.8.3' 2020-01-28 20:21:31 +07:00
Book Pauk
0b53ad4b4d Версия 0.8.3 2020-01-28 20:20:10 +07:00
Book Pauk
56ad41d10c Поправки текста объявления 2020-01-28 20:18:02 +07:00
Book Pauk
249a4564e0 Добавлено уведомление "Оплатим хостинг вместе" 2020-01-28 19:46:34 +07:00
Book Pauk
efb2413720 Небольшое изменение содержимого страницы 2020-01-28 19:44:52 +07:00
Book Pauk
1226acefd6 Небольшие исправления, queue теперь в одном экземпляре на класс 2020-01-28 14:51:09 +07:00
Book Pauk
76f7d7bc90 Мелкая поправка 2020-01-27 19:52:56 +07:00
Book Pauk
a5cb2641fd Мелкая поправка 2020-01-27 19:42:30 +07:00
Book Pauk
57fc64af79 Добавлен abort конвертеров при истечении времени ожидания подвижек очереди 2020-01-27 19:34:10 +07:00
Book Pauk
f8b7b8b698 Исправления LimitedQueue, исправления багов, добавлена проверка флага abort 2020-01-27 18:57:42 +07:00
Book Pauk
3da6befe10 Добавлен класс LimitedQueue для организации очередей 2020-01-26 18:38:09 +07:00
Book Pauk
a50d61c3ce Добавлена очередь скачивания и конвертирования 2020-01-26 18:37:14 +07:00
Book Pauk
b7568975e7 Добавлена обработка state = 'queue' 2020-01-26 18:31:31 +07:00
Book Pauk
4b9475310f Убрал ненужный this.taken 2020-01-26 16:23:20 +07:00
Book Pauk
639f726c83 Добавлен лимит на размер файла при распаковке 2020-01-26 15:17:45 +07:00
Book Pauk
7997c486cf Мелкий рефакторинг 2020-01-26 15:07:14 +07:00
Book Pauk
2569d00bd0 Мелкие поправки 2020-01-26 13:47:25 +07:00
Book Pauk
2cd80d8fa1 Merge tag '0.8.2-5' into develop
0.8.2-5
2020-01-23 17:12:46 +07:00
Book Pauk
eedca4db9b Merge branch 'release/0.8.2-5' 2020-01-23 17:12:37 +07:00
Book Pauk
1d352a76ce Поправка опечаток 2020-01-23 17:00:17 +07:00
Book Pauk
17670aabf9 WebSocket: добавлен метод reader-storage, поправки багов 2020-01-23 16:59:08 +07:00
Book Pauk
3456b3d90e WebSocket: добавлен метод worker-get-state-finish, небольшой рефакторинг 2020-01-23 16:25:06 +07:00
Book Pauk
f3da5a9026 Поправил комментарий 2020-01-23 15:56:26 +07:00
Book Pauk
00cc63b7cd WebSocket: добавлен метод get-config 2020-01-23 15:54:46 +07:00
Book Pauk
8df80ce738 Мелкая поправка 2020-01-23 15:16:49 +07:00
Book Pauk
12e7a783b0 Небольшие изменения блокирования кнопок панели 2020-01-22 22:06:12 +07:00
Book Pauk
be86a15351 Добавил настройку proxy_read_timeout 2020-01-22 21:37:28 +07:00
Book Pauk
2c5022e7b4 Merge tag '0.8.2-4' into develop
0.8.2-4
2020-01-22 21:17:58 +07:00
Book Pauk
f4a996fcb9 Merge branch 'release/0.8.2-4' 2020-01-22 21:17:52 +07:00
Book Pauk
fdbf508bbf Используем протокол WSS при необходимости 2020-01-22 21:17:10 +07:00
Book Pauk
500fafa5b2 Merge tag '0.8.2-3' into develop
0.8.2-3
2020-01-22 21:05:36 +07:00
Book Pauk
bfa315c68b Merge branch 'release/0.8.2-3' 2020-01-22 21:05:27 +07:00
Book Pauk
4972f085a3 Мелкая поправка 2020-01-22 20:59:52 +07:00
Book Pauk
9c13261929 Добавлена настройка для вебсокетов, добавлен конфиг nginx omnireader_http 2020-01-22 20:58:57 +07:00
Book Pauk
e36dc4a913 Небольшие поправки 2020-01-22 20:28:46 +07:00
Book Pauk
4cccb56ee3 Поправил комментарий 2020-01-22 20:15:33 +07:00
Book Pauk
3199af570d Добавлен WebSocketServer и контроллер для него 2020-01-22 20:06:51 +07:00
Book Pauk
7dad47b3c8 Добавлено использование WebSocketConnection 2020-01-22 20:02:42 +07:00
Book Pauk
fbd50bad1d Исправления багов 2020-01-22 20:02:05 +07:00
Book Pauk
10469bae7b Мелкая поправка 2020-01-22 20:01:21 +07:00
Book Pauk
b6a000a001 Добавлен пакет ws 2020-01-22 20:00:52 +07:00
Book Pauk
59539e7e90 Добавлен класс WebSocketConnection 2020-01-22 19:32:11 +07:00
Book Pauk
a2c41bc5ec Merge tag '0.8.2-2' into develop
0.8.2-2
2020-01-21 16:56:20 +07:00
Book Pauk
7d4baa7046 Поправил цвет github corner 2020-01-11 01:01:18 +07:00
Book Pauk
a24eaaed50 К предыдущему 2020-01-10 20:39:04 +07:00
Book Pauk
26813c582f Небольшие поправки 2020-01-10 20:37:18 +07:00
Book Pauk
6067ac73e2 Линкуем только необходимые компоненты 2020-01-09 22:00:41 +07:00
Book Pauk
b1d94b67f4 Мелкая поправка 2020-01-09 22:00:22 +07:00
Book Pauk
452f4e69fd Начало перехода от Element-UI на Quasar 2020-01-09 21:07:59 +07:00
138 changed files with 10055 additions and 2571 deletions

View File

@@ -8,7 +8,7 @@
![](docs/assets/reader.jpg)
## VPS
Для разворачивания читалки на чистом VPS с нуля смотрите [docs/omnireader](docs/omnireader/README.md)
Для разворачивания читалки на чистом VPS с нуля смотрите [docs/omnireader.ru](docs/omnireader.ru/README.md)
## Сборка проекта
Необходима версия node.js не ниже 10.

31
build/includer.js Normal file
View File

@@ -0,0 +1,31 @@
const path = require('path');
const fs = require('fs');
//пример в коде:
// @@include('./test/testFile.inc');
function includeRecursive(self, parentFile, source, depth) {
depth = (depth ? depth : 0);
if (depth > 50)
throw new Error('includer: stack too big');
const lines = source.split('\n');
let result = [];
for (const line of lines) {
const trimmed = line.trim();
const m = trimmed.match(/^@@[\s]*?include[\s]*?\(['"](.*)['"]\)/);
if (m) {
const includedFile = path.resolve(path.dirname(parentFile), m[1]);
self.addDependency(includedFile);
const fileContent = fs.readFileSync(includedFile, 'utf8');
result = result.concat(includeRecursive(self, includedFile, fileContent, depth + 1));
} else {
result.push(line);
}
}
return result;
}
exports.default = function includer(source) {
return includeRecursive(this, this.resourcePath, source).join('\n');
}

View File

@@ -16,6 +16,11 @@ module.exports = {
test: /\.vue$/,
loader: "vue-loader"
},
{
test: /\.includer$/,
resourceQuery: /^\?vue/,
use: path.resolve('build/includer.js')
},
{
test: /\.js$/,
loader: 'babel-loader',

View File

@@ -9,7 +9,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const AppCachePlugin = require('appcache-webpack-plugin');
const {GenerateSW} = require('workbox-webpack-plugin');
const publicDir = path.resolve(__dirname, '../dist/tmp/public');
const clientDir = path.resolve(__dirname, '../client');
@@ -55,6 +55,12 @@ module.exports = merge(baseWpConfig, {
filename: `${publicDir}/index.html`
}),
new CopyWebpackPlugin([{from: `${clientDir}/assets/*`, to: `${publicDir}/`, flatten: true}]),
new AppCachePlugin({exclude: ['../index.html']})
new GenerateSW({
cacheId: 'liberama',
swDest: `${publicDir}/service-worker.js`,
navigateFallback: '/index.html',
navigateFallbackDenylist: [new RegExp('^/api'), new RegExp('^/ws'), new RegExp('^/tmp'),],
skipWaiting: true,
}),
]
});

View File

@@ -1,4 +1,5 @@
import axios from 'axios';
import wsc from './webSocketConnection';
const api = axios.create({
baseURL: '/api'
@@ -6,9 +7,23 @@ const api = axios.create({
class Misc {
async loadConfig() {
const response = await api.post('/config', {params: [
const query = {params: [
'name', 'version', 'mode', 'maxUploadFileSize', 'useExternalBookConverter', 'branch',
]});
]};
try {
await wsc.open();
const config = await wsc.message(wsc.send(Object.assign({action: 'get-config'}, query)));
if (config.error)
throw new Error(config.error);
return config;
} catch (e) {
console.error(e);
}
//если с WebSocket проблема, работаем по http
const response = await api.post('/config', query);
return response.data;
}
}

View File

@@ -1,4 +1,6 @@
import axios from 'axios';
import * as utils from '../share/utils';
import wsc from './webSocketConnection';
const api = axios.create({
baseURL: '/api/reader'
@@ -9,44 +11,66 @@ const workerApi = axios.create({
});
class Reader {
constructor() {
}
async getStateFinish(workerId, callback) {
async getWorkerStateFinish(workerId, callback) {
if (!callback) callback = () => {};
//присылается текст, состоящий из json-объектов state каждые 300ms, с разделителем splitter между ними
const splitter = '-- aod2t5hDXU32bUFyqlFE next status --';
let lastIndex = 0;
let response = await workerApi.post('/get-state-finish', {workerId}, {
onDownloadProgress: progress => {
//небольая оптимизация, вместо простого responseText.split
const xhr = progress.target;
let currIndex = xhr.responseText.length;
if (lastIndex == currIndex)
return;
const last = xhr.responseText.substring(lastIndex, currIndex);
lastIndex = currIndex;
let response = {};
try {
await wsc.open();
const requestId = wsc.send({action: 'worker-get-state-finish', workerId});
//быстрее будет last.split
const res = last.split(splitter).pop();
if (res) {
try {
callback(JSON.parse(res));
} catch (e) {
//
}
let prevResponse = false;
while (1) {// eslint-disable-line no-constant-condition
response = await wsc.message(requestId);
if (!response.state && prevResponse !== false) {//экономия траффика
callback(prevResponse);
} else {//были изменения worker state
if (!response.state)
throw new Error('Неверный ответ api');
callback(response);
prevResponse = response;
}
if (response.state == 'finish' || response.state == 'error') {
break;
}
}
});
return response;
} catch (e) {
console.error(e);
}
//берем последний state
response = response.data.split(splitter).pop();
//если с WebSocket проблема, работаем по http
const refreshPause = 500;
let i = 0;
response = {};
while (1) {// eslint-disable-line no-constant-condition
const prevProgress = response.progress || 0;
const prevState = response.state || 0;
response = await workerApi.post('/get-state', {workerId});
response = response.data;
callback(response);
if (response) {
try {
response = JSON.parse(response);
} catch (e) {
response = false;
if (!response.state)
throw new Error('Неверный ответ api');
if (response.state == 'finish' || response.state == 'error') {
break;
}
if (i > 0)
await utils.sleep(refreshPause);
i++;
if (i > 120*1000/refreshPause) {//2 мин ждем телодвижений воркера
throw new Error('Слишком долгое время ожидания');
}
//проверка воркера
i = (prevProgress != response.progress || prevState != response.state ? 1 : i);
}
return response;
@@ -64,12 +88,12 @@ class Reader {
callback({totalSteps: 4});
callback(response.data);
response = await this.getStateFinish(workerId, callback);
response = await this.getWorkerStateFinish(workerId, callback);
if (response) {
if (response.state == 'finish') {//воркер закончил работу, можно скачивать кешированный на сервере файл
callback({step: 4});
const book = await this.loadCachedBook(response.path, callback, false, (response.size ? response.size : -1));
const book = await this.loadCachedBook(response.path, callback, response.size);
return Object.assign({}, response, {data: book.data});
}
@@ -87,75 +111,61 @@ class Reader {
}
}
async checkUrl(url) {
let fileExists = false;
async checkCachedBook(url) {
let estSize = -1;
try {
await axios.head(url, {headers: {'Cache-Control': 'no-cache'}});
fileExists = true;
const response = await axios.head(url, {headers: {'Cache-Control': 'no-cache'}});
if (response.headers['content-length']) {
estSize = response.headers['content-length'];
}
} catch (e) {
//
}
//восстановим при необходимости файл на сервере из удаленного облака
if (!fileExists) {
let response = await api.post('/restore-cached-file', {path: url});
const workerId = response.data.workerId;
if (!workerId)
throw new Error('Неверный ответ api');
response = await this.getStateFinish(workerId);
if (response.state == 'error') {
throw new Error(response.error);
}
}
return true;
}
async loadCachedBook(url, callback, restore = true, estSize = -1) {
if (!callback) callback = () => {};
let response = null;
callback({state: 'loading', progress: 0});
//получение размера файла
let fileExists = false;
if (estSize < 0) {
//восстановим при необходимости файл на сервере из удаленного облака
let response = null
try {
response = await axios.head(url, {headers: {'Cache-Control': 'no-cache'}});
if (response.headers['content-length']) {
estSize = response.headers['content-length'];
}
fileExists = true;
await wsc.open();
response = await wsc.message(wsc.send({action: 'reader-restore-cached-file', path: url}));
} catch (e) {
//
console.error(e);
//если с WebSocket проблема, работаем по http
response = await api.post('/restore-cached-file', {path: url});
response = response.data;
}
}
//восстановим при необходимости файл на сервере из удаленного облака
if (restore && !fileExists) {
response = await api.post('/restore-cached-file', {path: url});
const workerId = response.data.workerId;
if (!workerId)
throw new Error('Неверный ответ api');
response = await this.getStateFinish(workerId);
if (response.state == 'error') {
throw new Error(response.error);
}
const workerId = response.workerId;
if (!workerId)
throw new Error('Неверный ответ api');
response = await this.getWorkerStateFinish(workerId);
if (response.state == 'error') {
throw new Error(response.error);
}
if (response.size && estSize < 0) {
estSize = response.size;
}
}
return estSize;
}
async loadCachedBook(url, callback, estSize = -1) {
if (!callback) callback = () => {};
callback({state: 'loading', progress: 0});
//получение размера файла
if (estSize && estSize < 0) {
estSize = await this.checkCachedBook(url);
}
//получение файла
estSize = (estSize > 0 ? estSize : 1000000);
const options = {
onDownloadProgress: progress => {
onDownloadProgress: (progress) => {
while (progress.loaded > estSize) estSize *= 1.5;
if (callback)
@@ -199,13 +209,25 @@ class Reader {
}
async storage(request) {
let response = await api.post('/storage', request);
let response = null;
try {
await wsc.open();
response = await wsc.message(wsc.send({action: 'reader-storage', body: request}));
} catch (e) {
console.error(e);
//если с WebSocket проблема, работаем по http
response = await api.post('/storage', request);
response = response.data;
}
const state = response.data.state;
const state = response.state;
if (!state)
throw new Error('Неверный ответ api');
if (response.state == 'error') {
throw new Error(response.error);
}
return response.data;
return response;
}
}

View File

@@ -0,0 +1,185 @@
import * as utils from '../share/utils';
const cleanPeriod = 60*1000;//1 минута
class WebSocketConnection {
//messageLifeTime в минутах (cleanPeriod)
constructor(messageLifeTime = 5) {
this.ws = null;
this.timer = null;
this.listeners = [];
this.messageQueue = [];
this.messageLifeTime = messageLifeTime;
this.requestId = 0;
this.connecting = false;
}
addListener(listener) {
if (this.listeners.indexOf(listener) < 0)
this.listeners.push(Object.assign({regTime: Date.now()}, listener));
}
//рассылаем сообщение и удаляем те обработчики, которые его получили
emit(mes, isError) {
const len = this.listeners.length;
if (len > 0) {
let newListeners = [];
for (const listener of this.listeners) {
let emitted = false;
if (isError) {
if (listener.onError)
listener.onError(mes);
emitted = true;
} else {
if (listener.onMessage) {
if (listener.requestId) {
if (listener.requestId === mes.requestId) {
listener.onMessage(mes);
emitted = true;
}
} else {
listener.onMessage(mes);
emitted = true;
}
} else {
emitted = true;
}
}
if (!emitted)
newListeners.push(listener);
}
this.listeners = newListeners;
}
return this.listeners.length != len;
}
open(url) {
return new Promise((resolve, reject) => { (async() => {
//Ожидаем окончания процесса подключения, если open уже был вызван
let i = 0;
while (this.connecting && i < 200) {//10 сек
await utils.sleep(50);
i++;
}
if (i >= 200)
this.connecting = false;
//проверим подключение, и если нет, то подключимся заново
if (this.ws && this.ws.readyState == WebSocket.OPEN) {
resolve(this.ws);
} else {
this.connecting = true;
const protocol = (window.location.protocol == 'https:' ? 'wss:' : 'ws:');
url = url || `${protocol}//${window.location.host}/ws`;
this.ws = new WebSocket(url);
if (this.timer) {
clearTimeout(this.timer);
}
this.timer = setTimeout(() => { this.periodicClean(); }, cleanPeriod);
this.ws.onopen = (e) => {
this.connecting = false;
resolve(e);
};
this.ws.onmessage = (e) => {
try {
const mes = JSON.parse(e.data);
this.messageQueue.push({regTime: Date.now(), mes});
let newMessageQueue = [];
for (const message of this.messageQueue) {
if (!this.emit(message.mes)) {
newMessageQueue.push(message);
}
}
this.messageQueue = newMessageQueue;
} catch (e) {
this.emit(e.message, true);
}
};
this.ws.onerror = (e) => {
this.emit(e.message, true);
if (this.connecting) {
this.connecting = false;
reject(e);
}
};
}
})() });
}
//timeout в минутах (cleanPeriod)
message(requestId, timeout = 2) {
return new Promise((resolve, reject) => {
this.addListener({
requestId,
timeout,
onMessage: (mes) => {
resolve(mes);
},
onError: (e) => {
reject(e);
}
});
});
}
send(req) {
if (this.ws && this.ws.readyState == WebSocket.OPEN) {
const requestId = ++this.requestId;
this.ws.send(JSON.stringify(Object.assign({requestId}, req)));
return requestId;
} else {
throw new Error('WebSocket connection is not ready');
}
}
close() {
if (this.ws && this.ws.readyState == WebSocket.OPEN) {
this.ws.close();
}
}
periodicClean() {
try {
this.timer = null;
const now = Date.now();
//чистка listeners
let newListeners = [];
for (const listener of this.listeners) {
if (now - listener.regTime < listener.timeout*cleanPeriod - 50) {
newListeners.push(listener);
} else {
if (listener.onError)
listener.onError('Время ожидания ответа истекло');
}
}
this.listeners = newListeners;
//чистка messageQueue
let newMessageQueue = [];
for (const message of this.messageQueue) {
if (now - message.regTime < this.messageLifeTime*cleanPeriod - 50) {
newMessageQueue.push(message);
}
}
this.messageQueue = newMessageQueue;
} finally {
if (this.ws.readyState == WebSocket.OPEN) {
this.timer = setTimeout(() => { this.periodicClean(); }, cleanPeriod);
}
}
}
}
export default new WebSocketConnection();

View File

@@ -0,0 +1,5 @@
(function() {
if('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js');
}
})();

View File

@@ -1,55 +1,26 @@
<template>
<el-container>
<el-aside v-if="showAsideBar" :width="asideWidth">
<div class="app-name"><span v-html="appName"></span></div>
<el-button class="el-button-collapse" @click="toggleCollapse" :icon="buttonCollapseIcon"></el-button>
<el-menu class="el-menu-vertical" :default-active="rootRoute" :collapse="isCollapse" router>
<el-menu-item index="/cardindex">
<i class="el-icon-search"></i>
<span :class="itemTitleClass('/cardindex')" slot="title">{{ this.itemRuText['/cardindex'] }}</span>
</el-menu-item>
<el-menu-item index="/reader">
<i class="el-icon-tickets"></i>
<span :class="itemTitleClass('/reader')" slot="title">{{ this.itemRuText['/reader'] }}</span>
</el-menu-item>
<el-menu-item index="/forum" disabled>
<i class="el-icon-message"></i>
<span :class="itemTitleClass('/forum')" slot="title">{{ this.itemRuText['/forum'] }}</span>
</el-menu-item>
<el-menu-item index="/income">
<i class="el-icon-upload"></i>
<span :class="itemTitleClass('/income')" slot="title">{{ this.itemRuText['/income'] }}</span>
</el-menu-item>
<el-menu-item index="/sources">
<i class="el-icon-menu"></i>
<span :class="itemTitleClass('/sources')" slot="title">{{ this.itemRuText['/sources'] }}</span>
</el-menu-item>
<el-menu-item index="/settings">
<i class="el-icon-setting"></i>
<span :class="itemTitleClass('/settings')" slot="title">{{ this.itemRuText['/settings'] }}</span>
</el-menu-item>
<el-menu-item index="/help">
<i class="el-icon-question"></i>
<span :class="itemTitleClass('/help')" slot="title">{{ this.itemRuText['/help'] }}</span>
</el-menu-item>
</el-menu>
</el-aside>
<el-main v-if="showMain" :style="{padding: (isReaderActive ? 0 : '5px')}">
<keep-alive>
<router-view></router-view>
</keep-alive>
</el-main>
</el-container>
<div class="fit row">
<Notify ref="notify"/>
<StdDialog ref="stdDialog"/>
<keep-alive>
<router-view class="col"></router-view>
</keep-alive>
</div>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import Notify from './share/Notify.vue';
import StdDialog from './share/StdDialog.vue';
import * as utils from '../share/utils';
export default @Component({
components: {
Notify,
StdDialog,
},
watch: {
mode: function() {
this.setAppTitle();
@@ -67,7 +38,8 @@ class App extends Vue {
'/sources': 'Источники',
'/settings': 'Параметры',
'/help': 'Справка',
}
};
created() {
this.commit = this.$store.commit;
this.dispatch = this.$store.dispatch;
@@ -75,6 +47,28 @@ class App extends Vue {
this.uistate = this.$store.state.uistate;
this.config = this.$store.state.config;
//root route
let cachedRoute = '';
let cachedPath = '';
this.$root.rootRoute = () => {
if (this.$route.path != cachedPath) {
cachedPath = this.$route.path;
const m = cachedPath.match(/^(\/[^/]*).*$/i);
cachedRoute = (m ? m[1] : this.$route.path);
}
return cachedRoute;
}
this.$router.beforeEach((to, from, next) => {
//распознавание хоста, если присутствует домен 3-уровня "b.", то разрешена только определенная страница
if (window.location.host.indexOf('b.') == 0 && to.path != '/external-libs' && to.path != '/404') {
next('/404');
} else {
next();
}
});
// set-app-title
this.$root.$on('set-app-title', this.setAppTitle);
@@ -98,32 +92,45 @@ class App extends Vue {
document.addEventListener('keyup', (event) => {
this.keyHook(event);
});
});
document.addEventListener('keypress', (event) => {
this.keyHook(event);
});
document.addEventListener('keydown', (event) => {
this.keyHook(event);
});
});
window.addEventListener('resize', () => {
this.$root.$emit('resize');
});
}
routerReady() {
return new Promise ((resolve) => {
this.$router.onReady(() => {
resolve();
});
});
}
mounted() {
this.$root.notify = this.$refs.notify;
this.$root.stdDialog = this.$refs.stdDialog;
this.dispatch('config/loadConfig');
this.$watch('apiError', function(newError) {
if (newError) {
let mes = newError.message;
if (newError.response && newError.response.config)
mes = newError.response.config.url + '<br>' + newError.response.statusText;
this.$notify.error({
title: 'Ошибка API',
dangerouslyUseHTMLString: true,
message: mes
});
this.$root.notify.error(mes, 'Ошибка API');
}
});
this.setAppTitle();
this.redirectIfNeeded();
(async() => {
await this.routerReady();
this.redirectIfNeeded();
})();
}
toggleCollapse() {
@@ -137,9 +144,9 @@ class App extends Vue {
get asideWidth() {
if (this.uistate.asideBarCollapse) {
return '64px';
return 64;
} else {
return '170px';
return 170;
}
}
@@ -163,15 +170,14 @@ class App extends Vue {
}
get rootRoute() {
const m = this.$route.path.match(/^(\/[^/]*).*$/i);
this.$root.rootRoute = (m ? m[1] : this.$route.path);
return this.$root.rootRoute;
return this.$root.rootRoute();
}
setAppTitle(title) {
if (!title) {
if (this.mode == 'omnireader') {
if (this.mode == 'liberama.top') {
document.title = `Liberama Reader - всегда с вами`;
} else if (this.mode == 'omnireader') {
document.title = `Omni Reader - всегда с вами`;
} else if (this.config && this.mode !== null) {
document.title = `${this.config.name} - ${this.itemRuText[this.$root.rootRoute]}`;
@@ -190,30 +196,32 @@ class App extends Vue {
}
get showAsideBar() {
return (this.mode !== null && this.mode != 'reader' && this.mode != 'omnireader');
return (this.mode !== null && this.mode != 'reader' && this.mode != 'omnireader' && this.mode != 'liberama.top');
}
set showAsideBar(value) {
}
get isReaderActive() {
return this.rootRoute == '/reader';
}
get showMain() {
return (this.showAsideBar || this.isReaderActive);
return (this.rootRoute == '/reader' || this.rootRoute == '/external-libs');
}
redirectIfNeeded() {
if ((this.mode == 'reader' || this.mode == 'omnireader') && (!this.isReaderActive)) {
//старый url
if ((this.mode == 'reader' || this.mode == 'omnireader' || this.mode == 'liberama.top')) {
const search = window.location.search.substr(1);
const s = search.split('url=');
const url = s[1] || '';
const q = utils.parseQuery(s[0] || '');
if (url) {
q.url = decodeURIComponent(url);
}
window.history.replaceState({}, '', '/');
this.$router.replace({ path: '/reader', query: q });
//распознавание параметра url вида "?url=<link>" и редирект при необходимости
if (!this.isReaderActive) {
const s = search.split('url=');
const url = s[1] || '';
const q = utils.parseQuery(s[0] || '');
if (url) {
q.url = decodeURIComponent(url);
}
window.history.replaceState({}, '', '/');
this.$router.replace({ path: '/reader', query: q });
}
}
}
}
@@ -228,68 +236,28 @@ class App extends Vue {
line-height: 140%;
font-weight: bold;
}
.bold-font {
font-weight: bold;
}
.el-container {
height: 100%;
}
.el-aside {
line-height: 1;
background-color: #ccc;
color: #000;
}
.el-main {
padding: 0;
background-color: #E6EDF4;
color: #000;
}
.el-menu-vertical:not(.el-menu--collapse) {
background-color: inherit;
color: inherit;
text-align: left;
width: 100%;
border: 0;
}
.el-menu--collapse {
background-color: inherit;
color: inherit;
border: 0;
}
.el-button-collapse, .el-button-collapse:focus, .el-button-collapse:active, .el-button-collapse:hover {
background-color: inherit;
color: inherit;
margin-top: 5px;
width: 100%;
height: 64px;
border: 0;
}
.el-menu-item {
font-size: 85%;
}
</style>
<style>
body, html, #app {
body, html, #app {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
font: normal 12pt ReaderDefault;
}
.el-tabs__content {
flex: 1;
padding: 0 !important;
display: flex;
flex-direction: column;
overflow: hidden;
.dborder {
border: 2px solid yellow !important;
}
.icon-rotate {
vertical-align: middle;
animation: rotating 2s linear infinite;
}
.notify-button-icon {
font-size: 16px !important;
}
@font-face {

View File

@@ -1,7 +1,7 @@
<template>
<el-container>
<div>
Раздел Book в разработке
</el-container>
</div>
</template>
<script>

View File

@@ -1,7 +1,7 @@
<template>
<el-container>
<div>
Раздел Card в разработке
</el-container>
</div>
</template>
<script>

View File

@@ -1,15 +1,9 @@
<template>
<el-container direction="vertical">
<el-tabs type="border-card" style="height: 100%;" v-model="selectedTab">
<el-tab-pane label="Поиск"></el-tab-pane>
<el-tab-pane label="Автор"></el-tab-pane>
<el-tab-pane label="Книга"></el-tab-pane>
<el-tab-pane label="История"></el-tab-pane>
<keep-alive>
<router-view></router-view>
</keep-alive>
</el-tabs>
</el-container>
<div>
<keep-alive>
<router-view></router-view>
</keep-alive>
</div>
</template>
<script>
@@ -18,7 +12,7 @@ import Vue from 'vue';
import Component from 'vue-class-component';
import _ from 'lodash';
const rootRoute = '/cardindex';
const selfRoute = '/cardindex';
const tab2Route = [
'/cardindex/search',
'/cardindex/card',
@@ -51,7 +45,7 @@ class CardIndex extends Vue {
if (t !== this.selectedTab)
this.selectedTab = t.toString();
} else {
if (route == rootRoute && lastActiveTab !== null)
if (route == selfRoute && lastActiveTab !== null)
this.setRouteByTab(lastActiveTab);
}
}

View File

@@ -1,7 +1,7 @@
<template>
<el-container>
<div>
Раздел History в разработке
</el-container>
</div>
</template>
<script>

View File

@@ -1,7 +1,7 @@
<template>
<el-container>
<div>
Раздел Search в разработке
</el-container>
</div>
</template>
<script>

View File

@@ -0,0 +1,633 @@
<template>
<Window ref="window" @close="close" margin="2px">
<template slot="header">
{{ header }}
</template>
<template slot="buttons">
<span class="full-screen-button row justify-center items-center" @mousedown.stop @click="fullScreenToggle">
<q-icon :name="(fullScreenActive ? 'la la-compress-arrows-alt': 'la la-expand-arrows-alt')" size="16px"/>
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">На весь экран</q-tooltip>
</span>
</template>
<div v-show="ready" class="col column" style="min-width: 600px">
<div class="row items-center q-px-sm" style="height: 50px">
<q-select class="q-mr-sm" ref="rootLink" v-model="rootLink" :options="rootLinkOptions" @input="rootLinkInput"
@popup-show="onSelectPopupShow" @popup-hide="onSelectPopupHide"
style="width: 230px"
dropdown-icon="la la-angle-down la-sm"
rounded outlined dense emit-value map-options display-value-sanitize options-sanitize
>
<template v-slot:prepend>
<q-btn class="q-mr-xs" round dense color="blue" icon="la la-plus" @click.stop="addBookmark" size="12px">
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Добавить закладку</q-tooltip>
</q-btn>
<q-btn round dense color="blue" icon="la la-bars" @click.stop="bookmarkSettings" size="12px" disabled>
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Настроить закладки (пока недоступно)</q-tooltip>
</q-btn>
</template>
<template v-slot:selected>
<div style="overflow: hidden; white-space: nowrap;">{{ removeProtocol(rootLink) }}</div>
</template>
</q-select>
<q-select class="q-mr-sm" ref="selectedLink" v-model="selectedLink" :options="selectedLinkOptions" @input="selectedLinkInput" style="width: 50px"
@popup-show="onSelectPopupShow" @popup-hide="onSelectPopupHide"
dropdown-icon="la la-angle-down la-sm"
rounded outlined dense emit-value map-options hide-selected display-value-sanitize options-sanitize
>
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Закладки</q-tooltip>
</q-select>
<q-input class="col q-mr-sm" ref="input" rounded outlined dense bg-color="white" v-model="bookUrl" placeholder="Скопируйте сюда URL книги"
@focus="selectAllOnFocus" @keydown="bookUrlKeyDown"
>
<template v-slot:prepend>
<q-btn class="q-mr-xs" round dense color="blue" icon="la la-home" @click="goToLink(libs.startLink)" size="12px">
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Вернуться на стартовую страницу</q-tooltip>
</q-btn>
<q-btn round dense color="blue" icon="la la-angle-double-down" @click="openBookUrlInFrame" size="12px">
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Загрузить URL во фрейм</q-tooltip>
</q-btn>
</template>
</q-input>
<q-btn rounded color="green-7" no-caps size="14px" @click="submitUrl">Открыть
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Открыть в читалке</q-tooltip>
</q-btn>
</div>
<div class="separator"></div>
<div class="col fit" style="position: relative;">
<iframe v-if="frameVisible" class="fit" ref="frame" :src="frameSrc" frameborder="0"></iframe>
<div v-show="transparentLayoutVisible" ref="transparentLayout" class="fit transparent-layout" @click="transparentLayoutClick"></div>
</div>
<Dialog ref="dialogAddBookmark" v-model="addBookmarkVisible">
<template slot="header">
<div class="row items-center">
<q-icon class="q-mr-sm" name="la la-bookmark" size="28px"></q-icon>
Добавить закладку
</div>
</template>
<div class="q-mx-md row">
<q-input ref="bookmarkLink" class="col q-mr-sm" outlined dense bg-color="white" v-model="bookmarkLink" @keydown="bookmarkLinkKeyDown"
placeholder="Ссылка для закладки" maxlength="2000" @focus="selectAllOnFocus">
</q-input>
<q-select class="q-mr-sm" ref="defaultRootLink" v-model="defaultRootLink" :options="defaultRootLinkOptions" @input="defaultRootLinkInput" style="width: 50px"
dropdown-icon="la la-angle-down la-sm"
outlined dense emit-value map-options hide-selected display-value-sanitize options-sanitize
>
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Предустановленные ссылки</q-tooltip>
</q-select>
</div>
<div class="q-mx-md q-mt-md">
<q-input class="col q-mr-sm" ref="bookmarkDesc" outlined dense bg-color="white" v-model="bookmarkDesc" @keydown="bookmarkDescKeyDown"
placeholder="Описание" style="width: 400px" maxlength="100" @focus="selectAllOnFocus">
</q-input>
</div>
<template slot="footer">
<q-btn class="q-px-md q-ml-sm" dense no-caps v-close-popup>Отмена</q-btn>
<q-btn class="q-px-md q-ml-sm" color="primary" dense no-caps @click="okAddBookmark" :disabled="!bookmarkLink">OK</q-btn>
</template>
</Dialog>
</div>
</Window>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import _ from 'lodash';
import Window from '../share/Window.vue';
import Dialog from '../share/Dialog.vue';
import rstore from '../../store/modules/reader';
import * as utils from '../../share/utils';
const proxySubst = {
'http://flibusta.is': 'http://b.liberama.top:23480',
};
export default @Component({
components: {
Window,
Dialog
},
watch: {
libs: function() {
this.loadLibs();
},
rootLink: function() {
this.updateSelectedLink();
this.updateStartLink();
},
selectedLink: function() {
this.updateStartLink();
},
defaultRootLink: function() {
this.updateBookmarkLink();
}
}
})
class ExternalLibs extends Vue {
ready = false;
frameVisible = false;
startLink = '';
rootLink = '';
selectedLink = '';
frameSrc = '';
bookUrl = '';
libs = {};
fullScreenActive = false;
addBookmarkVisible = false;
transparentLayoutVisible = false;
bookmarkLink = '';
bookmarkDesc = '';
defaultRootLink = '';
created() {
this.$root.addKeyHook(this.keyHook);
document.addEventListener('fullscreenchange', () => {
this.fullScreenActive = (document.fullscreenElement !== null);
});
//this.commit = this.$store.commit;
//this.commit('reader/setLibs', rstore.libsDefaults);
}
mounted() {
//Поправка метода toggleOption компонента select фреймворка quasar, необходимо другое поведение
//$emit('input'.. вызывается всегда
this.toggleOption = function(opt, keepOpen) {
if (this.editable !== true || opt === void 0 || this.isOptionDisabled(opt) === true) {
return;
}
const optValue = this.getOptionValue(opt);
if (this.multiple !== true) {
if (keepOpen !== true) {
this.updateInputValue(this.fillInput === true ? this.getOptionLabel(opt) : '', true, true);
this.hidePopup();
}
this.$refs.target !== void 0 && this.$refs.target.focus();
this.$emit('input', this.emitValue === true ? optValue : opt);
}
};
this.$refs.rootLink.toggleOption = this.toggleOption;
this.$refs.selectedLink.toggleOption = this.toggleOption;
(async() => {
//подождем this.mode
let i = 0;
while(!this.mode && i < 100) {
await utils.sleep(100);
i++;
}
if (this.mode != 'liberama.top') {
this.$router.replace('/404');
return;
}
this.$refs.window.init();
this.opener = null;
const host = window.location.host;
const openerHost = (host.indexOf('b.') == 0 ? host.substring(2) : host);
const openerOrigin1 = `http://${openerHost}`;
const openerOrigin2 = `https://${openerHost}`;
window.addEventListener('message', (event) => {
if (event.origin !== openerOrigin1 && event.origin !== openerOrigin2)
return;
if (!_.isObject(event.data) || event.data.from != 'LibsPage')
return;
if (event.origin == openerOrigin1)
this.opener = window.opener;
else
this.opener = event.source;
this.openerOrigin = event.origin;
//console.log(event);
this.recvMessage(event.data);
});
//Ожидаем родителя
i = 0;
while(!this.opener) {
await utils.sleep(1000);
i++;
if (i >= 5) {
await this.$root.stdDialog.alert('Нет связи с читалкой. Окно будет закрыто', 'Ошибка');
window.close();
}
}
//Проверка закрытия родительского окна
while(this.opener) {
await this.checkOpener();
await utils.sleep(1000);
}
})();
}
recvMessage(d) {
if (d.type == 'mes') {
switch(d.data) {
case 'hello': this.sendMessage({type: 'mes', data: 'ready'}); break;
}
} else if (d.type == 'libs') {
this.ready = true;
this.libs = _.cloneDeep(d.data);
if (!this.frameSrc)
this.goToLink(this.libs.startLink);
} else if (d.type == 'notify') {
this.$root.notify.success(d.data, '', {position: 'bottom-right'});
}
}
sendMessage(d) {
(async() => {
await this.checkOpener();
if (this.opener && this.openerOrigin)
this.opener.postMessage(Object.assign({}, {from: 'ExternalLibs'}, d), this.openerOrigin);
})();
}
async checkOpener() {
if (this.opener.closed) {
await this.$root.stdDialog.alert('Потеряна связь с читалкой. Окно будет закрыто', 'Ошибка');
window.close();
}
}
commitLibs(libs) {
this.sendMessage({type: 'libs', data: libs});
}
loadLibs() {
const libs = this.libs;
this.startLink = (libs.comment ? libs.comment + ' ': '') + this.removeProtocol(libs.startLink);
this.rootLink = this.getOrigin(libs.startLink);
this.updateSelectedLink();
}
get mode() {
return this.$store.state.config.mode;
}
get header() {
let result = (this.ready ? 'Библиотека' : 'Загрузка...');
if (this.ready && this.startLink) {
result += ` | ${this.startLink}`;
}
this.$root.$emit('set-app-title', result);
return result;
}
updateSelectedLink() {
if (!this.ready)
return;
const index = this.getRootIndexByUrl(this.libs.groups, this.rootLink);
if (index >= 0)
this.selectedLink = this.libs.groups[index].s;
}
updateStartLink() {
if (!this.ready)
return;
const index = this.getRootIndexByUrl(this.libs.groups, this.rootLink);
if (index >= 0) {
let libs = _.cloneDeep(this.libs);
libs.groups[index].s = this.selectedLink;
libs.startLink = this.selectedLink;
libs.comment = this.getCommentByLink(libs.groups[index].list, this.selectedLink);
this.goToLink(this.selectedLink);
this.commitLibs(libs);
}
}
get rootLinkOptions() {
let result = [];
if (!this.ready)
return result;
this.libs.groups.forEach(group => {
result.push({label: this.removeProtocol(group.r), value: group.r});
});
return result;
}
get defaultRootLinkOptions() {
let result = [];
rstore.libsDefaults.groups.forEach(group => {
result.push({label: this.removeProtocol(group.r), value: group.r});
});
return result;
}
get selectedLinkOptions() {
let result = [];
if (!this.ready)
return result;
const index = this.getRootIndexByUrl(this.libs.groups, this.rootLink);
if (index >= 0) {
this.libs.groups[index].list.forEach(link => {
result.push({label: (link.c ? link.c + ' ': '') + this.removeOrigin(link.l), value: link.l});
});
}
return result;
}
openBookUrlInFrame() {
if (this.bookUrl) {
this.goToLink(this.addProtocol(this.bookUrl));
}
}
goToLink(link) {
if (!this.ready)
return;
this.frameSrc = this.makeProxySubst(link);
this.frameVisible = false;
this.$nextTick(() => {
this.frameVisible = true;
this.$nextTick(() => {
this.$refs.frame.contentWindow.focus();
});
});
}
addProtocol(url) {
if ((url.indexOf('http://') != 0) && (url.indexOf('https://') != 0))
return 'http://' + url;
return url;
}
removeProtocol(url) {
return url.replace(/(^\w+:|^)\/\//, '');
}
getOrigin(url) {
const parsed = new URL(url);
return parsed.origin;
}
removeOrigin(url) {
const parsed = new URL(url);
const result = url.substring(parsed.origin.length);
return (result ? result : '/');
}
getRootIndexByUrl(groups, url) {
if (!this.ready)
return -1;
const origin = this.getOrigin(url);
for (let i = 0; i < groups.length; i++) {
if (groups[i].r == origin)
return i;
}
return -1;
}
getListItemByLink(list, link) {
for (const item of list) {
if (item.l == link)
return item;
}
return null;
}
getCommentByLink(list, link) {
const item = this.getListItemByLink(list, link);
return (item ? item.c : '');
}
makeProxySubst(url, reverse = false) {
for (const [key, value] of Object.entries(proxySubst)) {
if (reverse && value == url.substring(0, value.length)) {
return key + url.substring(value.length);
} else if (key == url.substring(0, key.length)) {
return value + url.substring(key.length);
}
}
return url;
}
selectAllOnFocus(event) {
if (event.target.select)
event.target.select();
}
rootLinkInput() {
this.updateSelectedLink();
this.updateStartLink();
}
selectedLinkInput() {
this.updateStartLink();
}
submitUrl() {
if (this.bookUrl) {
this.sendMessage({type: 'submitUrl', data: {
url: this.makeProxySubst(this.addProtocol(this.bookUrl), true),
force: true
}});
this.bookUrl = '';
if (this.libs.closeAfterSubmit)
this.close();
}
}
addBookmark() {
this.bookmarkLink = (this.bookUrl ? this.makeProxySubst(this.addProtocol(this.bookUrl), true) : '');
this.bookmarkDesc = '';
this.addBookmarkVisible = true;
this.$nextTick(() => {
this.$refs.bookmarkLink.focus();
this.$refs.defaultRootLink.toggleOption = this.toggleOption;
});
}
updateBookmarkLink() {
const index = this.getRootIndexByUrl(rstore.libsDefaults.groups, this.defaultRootLink);
if (index >= 0) {
this.bookmarkLink = rstore.libsDefaults.groups[index].s;
this.bookmarkDesc = this.getCommentByLink(rstore.libsDefaults.groups[index].list, this.bookmarkLink);
} else {
this.bookmarkLink = '';
this.bookmarkDesc = '';
}
}
defaultRootLinkInput() {
this.updateBookmarkLink();
}
bookmarkLinkKeyDown(event) {
if (event.key == 'Enter') {
this.$refs.bookmarkDesc.focus();
event.preventDefault();
}
}
bookmarkDescKeyDown(event) {
if (event.key == 'Enter') {
this.okAddBookmark();
event.preventDefault();
}
}
async okAddBookmark() {
if (!this.bookmarkLink)
return;
const link = this.addProtocol(this.bookmarkLink);
let index = -1;
try {
index = this.getRootIndexByUrl(this.libs.groups, link);
} catch (e) {
await this.$root.stdDialog.alert('Неверный формат ссылки', 'Ошибка');
return;
}
//есть группа в закладках
if (index >= 0) {
const item = this.getListItemByLink(this.libs.groups[index].list, link);
if (!item || item.c != this.bookmarkDesc) {
//добавляем
let libs = _.cloneDeep(this.libs);
if (libs.groups[index].list.length >= 100) {
await this.$root.stdDialog.alert('Достигнут предел количества закладок для этого сайта', 'Ошибка');
return;
}
libs.groups[index].list.push({l: link, c: this.bookmarkDesc});
this.commitLibs(libs);
}
} else {//нет группы в закладках
let libs = _.cloneDeep(this.libs);
if (libs.groups.length >= 100) {
await this.$root.stdDialog.alert('Достигнут предел количества различных сайтов в закладках', 'Ошибка');
return;
}
//добавляем сначала группу
libs.groups.push({r: this.getOrigin(link), s: link, list: []});
index = this.getRootIndexByUrl(libs.groups, link);
if (index >= 0)
libs.groups[index].list.push({l: link, c: this.bookmarkDesc});
this.commitLibs(libs);
}
this.addBookmarkVisible = false;
}
bookmarkSettings() {
}
fullScreenToggle() {
this.fullScreenActive = !this.fullScreenActive;
if (this.fullScreenActive) {
this.$q.fullscreen.request();
} else {
this.$q.fullscreen.exit();
}
}
transparentLayoutClick() {
this.transparentLayoutVisible = false;
}
onSelectPopupShow() {
this.transparentLayoutVisible = true;
}
onSelectPopupHide() {
this.transparentLayoutVisible = false;
}
close() {
this.sendMessage({type: 'close'});
}
bookUrlKeyDown(event) {
if (event.key == 'Enter') {
this.submitUrl();
event.preventDefault();
}
}
keyHook() {
if (this.$root.rootRoute() == '/external-libs') {
if (this.$refs.dialogAddBookmark.active)
return false;
if (event.type == 'keydown' && event.key == 'F4') {
this.addBookmark()
return;
}
if (event.type == 'keydown' && event.key == 'Escape' &&
(document.activeElement != this.$refs.rootLink.$refs.target || !this.$refs.rootLink.menu) &&
(document.activeElement != this.$refs.selectedLink.$refs.target || !this.$refs.selectedLink.menu)
) {
this.close();
}
return true;
}
return false;
}
}
//-----------------------------------------------------------------------------
</script>
<style scoped>
.separator {
height: 1px;
background-color: #A0A0A0;
}
.full-screen-button {
width: 30px;
height: 30px;
cursor: pointer;
}
.full-screen-button:hover {
background-color: #69C05F;
}
.transparent-layout {
top: 0;
left: 0;
position: absolute;
}
</style>

View File

@@ -1,7 +1,7 @@
<template>
<el-container>
<div>
Раздел Help в разработке
</el-container>
</div>
</template>
<script>

View File

@@ -1,7 +1,7 @@
<template>
<el-container>
<div>
Раздел Income в разработке
</el-container>
</div>
</template>
<script>

View File

@@ -1,7 +1,7 @@
<template>
<el-container>
<div>
Страница не найдена
</el-container>
</div>
</template>
<script>

View File

@@ -91,11 +91,11 @@ class CopyTextPage extends Vue {
close() {
this.stopInit = true;
this.$emit('copy-text-toggle');
this.$emit('do-action', {action: 'copyText'});
}
keyHook(event) {
if (event.type == 'keydown' && (event.code == 'Escape')) {
if (event.type == 'keydown' && event.key == 'Escape') {
this.close();
}
return true;

View File

@@ -1,12 +1,12 @@
<template>
<div class="page">
<h4>Возможности читалки:</h4>
<span class="text-h6 text-bold">Возможности читалки:</span>
<ul>
<li>загрузка любой страницы интернета</li>
<li>синхронизация данных (настроек и читаемых книг) между различными устройствами</li>
<li>работа в автономном режиме (без связи)</li>
<li>изменение цвета фона, текста, размер и тип шрифта и прочее</li>
<li>установка и запоминание текущей позиции и настроек в браузере и на сервере</li>
<li>синхронизация данных (настроек и читаемых книг) между различными устройствами</li>
<li>кэширование файлов книг на клиенте и на сервере</li>
<li>открытие книг с локального диска</li>
<li>плавный скроллинг текста</li>
@@ -22,15 +22,15 @@
на файл из онлайн-библиотеки (например, скопировав адрес ссылки или кнопки "скачать fb2").</p>
<p>Поддерживаемые форматы: <b>fb2, fb2.zip, html, txt</b> и другие.</p>
<div v-show="mode == 'omnireader'">
<div v-show="mode == 'omnireader' || mode == 'liberama.top'">
<p>Вы можете добавить в свой браузер закладку, указав в ее свойствах вместо адреса следующий код:
<br><strong>javascript:location.href='https://omnireader.ru/?url='+location.href;</strong>
&nbsp;
<span class="clickable" @click="copyText('javascript:location.href=\'https://omnireader.ru/?url=\'+location.href;', 'Код для адреса закладки успешно скопирован в буфер обмена')">
(скопировать)
</span>
<br><strong>{{ bookmarkText }}</strong>
<q-icon class="copy-icon" name="la la-copy" @click="copyText(bookmarkText, 'Код для адреса закладки успешно скопирован в буфер обмена')">
<q-tooltip :delay="1000" anchor="top middle" self="center middle" content-style="font-size: 80%">Скопировать</q-tooltip>
</q-icon>
<br>или перетащив на панель закладок следующую ссылку:
<br><a style="margin-left: 50px" href="javascript:location.href='https://omnireader.ru/?url='+location.href;">Omni Reader</a>
<br><a style="margin-left: 50px" :href="bookmarkText">{{ (mode == 'omnireader' ? 'Omni' : 'Liberama') }} Reader</a>
<br>Тогда, активировав получившуюся закладку на любой странице интернета, вы автоматически загрузите эту страницу в Omni Reader.
<br>В Chrome для Android можно вызывать такую закладку по имени прямо в адресной строке браузера (имя стоит сделать попроще).
</p>
@@ -56,13 +56,17 @@ class CommonHelpPage extends Vue {
return this.$store.state.config.mode;
}
get bookmarkText() {
return `javascript:location.href='https://${window.location.host}/?url='+location.href;`
}
async copyText(text, mes) {
const result = await copyTextToClipboard(text);
const msg = (result ? mes : 'Копирование не удалось');
if (result)
this.$notify.success({message: msg});
this.$root.notify.success(msg);
else
this.$notify.error({message: msg});
this.$root.notify.error(msg);
}
}
//-----------------------------------------------------------------------------
@@ -70,20 +74,16 @@ class CommonHelpPage extends Vue {
<style scoped>
.page {
flex: 1;
padding: 15px;
overflow-y: auto;
font-size: 120%;
line-height: 130%;
}
h4 {
margin: 0;
}
.clickable {
color: blue;
text-decoration: underline;
.copy-icon {
margin-left: 10px;
cursor: pointer;
font-size: 120%;
color: blue;
}
</style>

View File

@@ -1,30 +1,51 @@
<template>
<div class="page">
<div class="box">
<p class="p">Проект существует исключительно на личном энтузиазме.</p>
<p class="p">Чтобы энтузиазма было побольше, вы можете пожертвовать на развитие проекта любую сумму:</p>
<p class="p">Вы можете пожертвовать на развитие проекта любую сумму:</p>
<div class="address">
<img class="logo" src="./assets/yandex.png">
<el-button class="button" @click="donateYandexMoney">Пожертвовать</el-button><br>
<div class="para">{{ yandexAddress }}</div>
<q-btn class="q-ml-sm q-px-sm" dense no-caps @click="donateYandexMoney">Пожертвовать</q-btn><br>
<div class="para">{{ yandexAddress }}
<q-icon class="copy-icon" name="la la-copy" @click="copyAddress(yandexAddress, 'Яндекс кошелек')">
<q-tooltip :delay="1000" anchor="top middle" self="center middle" content-style="font-size: 80%">Скопировать</q-tooltip>
</q-icon>
</div>
</div>
<div class="address">
<img class="logo" src="./assets/paypal.png">
<div class="para">{{ paypalAddress }}
<q-icon class="copy-icon" name="la la-copy" @click="copyAddress(paypalAddress, 'Paypal-адрес')">
<q-tooltip :delay="1000" anchor="top middle" self="center middle" content-style="font-size: 80%">Скопировать</q-tooltip>
</q-icon>
</div>
</div>
<div class="address">
<img class="logo" src="./assets/bitcoin.png">
<el-button class="button" @click="copyAddress(bitcoinAddress, 'Bitcoin')">Скопировать</el-button><br>
<div class="para">{{ bitcoinAddress }}</div>
<div class="para">{{ bitcoinAddress }}
<q-icon class="copy-icon" name="la la-copy" @click="copyAddress(bitcoinAddress, 'Bitcoin-адрес')">
<q-tooltip :delay="1000" anchor="top middle" self="center middle" content-style="font-size: 80%">Скопировать</q-tooltip>
</q-icon>
</div>
</div>
<div class="address">
<img class="logo" src="./assets/litecoin.png">
<el-button class="button" @click="copyAddress(litecoinAddress, 'Litecoin')">Скопировать</el-button><br>
<div class="para">{{ litecoinAddress }}</div>
<div class="para">{{ litecoinAddress }}
<q-icon class="copy-icon" name="la la-copy" @click="copyAddress(litecoinAddress, 'Litecoin-адрес')">
<q-tooltip :delay="1000" anchor="top middle" self="center middle" content-style="font-size: 80%">Скопировать</q-tooltip>
</q-icon>
</div>
</div>
<div class="address">
<img class="logo" src="./assets/monero.png">
<el-button class="button" @click="copyAddress(moneroAddress, 'Monero')">Скопировать</el-button><br>
<div class="para">{{ moneroAddress }}</div>
<div class="para">{{ moneroAddress }}
<q-icon class="copy-icon" name="la la-copy" @click="copyAddress(moneroAddress, 'Monero-адрес')">
<q-tooltip :delay="1000" anchor="top middle" self="center middle" content-style="font-size: 80%">Скопировать</q-tooltip>
</q-icon>
</div>
</div>
</div>
</div>
@@ -40,6 +61,7 @@ export default @Component({
})
class DonateHelpPage extends Vue {
yandexAddress = '410018702323056';
paypalAddress = 'bookpauk@gmail.com';
bitcoinAddress = '3EbgZ7MK1UVaN38Gty5DCBtS4PknM4Ut85';
litecoinAddress = 'MP39Riec4oSNB3XMjiquKoLWxbufRYNXxZ';
moneroAddress = '8BQPnvHcPSHM5gMQsmuypDgx9NNsYqwXKfDDuswEyF2Q2ewQSfd2pkK6ydH2wmMyq2JViZvy9DQ35hLMx7g72mFWNJTPtnz';
@@ -54,9 +76,9 @@ class DonateHelpPage extends Vue {
async copyAddress(address, prefix) {
const result = await copyTextToClipboard(address);
if (result)
this.$notify.success({message: `${prefix}-адрес ${address} успешно скопирован в буфер обмена`});
this.$root.notify.success(`${prefix} ${address} успешно скопирован в буфер обмена`);
else
this.$notify.error({message: 'Копирование не удалось'});
this.$root.notify.error('Копирование не удалось');
}
}
//-----------------------------------------------------------------------------
@@ -64,12 +86,10 @@ class DonateHelpPage extends Vue {
<style scoped>
.page {
flex: 1;
padding: 15px;
overflow-y: auto;
font-size: 120%;
line-height: 130%;
display: flex;
}
.p {
@@ -79,15 +99,10 @@ class DonateHelpPage extends Vue {
}
.box {
flex: 1;
max-width: 550px;
overflow-wrap: break-word;
}
h5 {
margin: 0;
}
.address {
padding-top: 10px;
margin-top: 20px;
@@ -97,13 +112,16 @@ h5 {
margin: 10px 10px 10px 40px;
}
.button {
margin-left: 10px;
}
.logo {
width: 130px;
position: relative;
top: 10px;
}
.copy-icon {
margin-left: 10px;
cursor: pointer;
font-size: 120%;
color: blue;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -4,23 +4,20 @@
Справка
</template>
<el-tabs type="border-card" v-model="selectedTab">
<el-tab-pane class="tab" label="Общее">
<CommonHelpPage></CommonHelpPage>
</el-tab-pane>
<el-tab-pane label="Клавиатура">
<HotkeysHelpPage></HotkeysHelpPage>
</el-tab-pane>
<el-tab-pane label="Мышь/тачскрин">
<MouseHelpPage></MouseHelpPage>
</el-tab-pane>
<el-tab-pane label="История версий" name="releases">
<VersionHistoryPage></VersionHistoryPage>
</el-tab-pane>
<el-tab-pane label="Помочь проекту" name="donate">
<DonateHelpPage></DonateHelpPage>
</el-tab-pane>
</el-tabs>
<div class="col column" style="min-width: 600px">
<q-btn-toggle
v-model="selectedTab"
toggle-color="primary"
no-caps unelevated
:options="buttons"
/>
<div class="separator"></div>
<keep-alive>
<component ref="page" class="col" :is="activePage"
></component>
</keep-alive>
</div>
</Window>
</template>
@@ -33,36 +30,58 @@ import Window from '../../share/Window.vue';
import CommonHelpPage from './CommonHelpPage/CommonHelpPage.vue';
import HotkeysHelpPage from './HotkeysHelpPage/HotkeysHelpPage.vue';
import MouseHelpPage from './MouseHelpPage/MouseHelpPage.vue';
import DonateHelpPage from './DonateHelpPage/DonateHelpPage.vue';
import VersionHistoryPage from './VersionHistoryPage/VersionHistoryPage.vue';
import DonateHelpPage from './DonateHelpPage/DonateHelpPage.vue';
const pages = {
'CommonHelpPage': CommonHelpPage,
'HotkeysHelpPage': HotkeysHelpPage,
'MouseHelpPage': MouseHelpPage,
'VersionHistoryPage': VersionHistoryPage,
'DonateHelpPage': DonateHelpPage,
};
const tabs = [
['CommonHelpPage', 'Общее'],
['MouseHelpPage', 'Мышь/тачскрин'],
['HotkeysHelpPage', 'Клавиатура'],
['VersionHistoryPage', 'История версий'],
['DonateHelpPage', 'Помочь проекту'],
];
export default @Component({
components: {
Window,
CommonHelpPage,
HotkeysHelpPage,
MouseHelpPage,
DonateHelpPage,
VersionHistoryPage,
},
components: Object.assign({ Window }, pages),
})
class HelpPage extends Vue {
selectedTab = null;
selectedTab = 'CommonHelpPage';
close() {
this.$emit('help-toggle');
this.$emit('do-action', {action: 'help'});
}
get activePage() {
if (pages[this.selectedTab])
return pages[this.selectedTab];
return null;
}
get buttons() {
let result = [];
for (const tab of tabs)
result.push({label: tab[1], value: tab[0]});
return result;
}
activateDonateHelpPage() {
this.selectedTab = 'donate';
this.selectedTab = 'DonateHelpPage';
}
activateVersionHistoryHelpPage() {
this.selectedTab = 'releases';
this.selectedTab = 'VersionHistoryPage';
}
keyHook(event) {
if (event.type == 'keydown' && (event.code == 'Escape')) {
if (event.type == 'keydown' && event.key == 'Escape') {
this.close();
}
return true;
@@ -72,16 +91,8 @@ class HelpPage extends Vue {
</script>
<style scoped>
.el-tabs {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.el-tab-pane {
flex: 1;
display: flex;
overflow: hidden;
.separator {
height: 1px;
background-color: #E0E0E0;
}
</style>

View File

@@ -1,28 +1,13 @@
<template>
<div class="page">
<h4>Управление с помощью горячих клавиш:</h4>
<ul>
<li><b>F1, H</b> - открыть справку</li>
<li><b>Escape</b> - показать/скрыть страницу загрузки</li>
<li><b>Tab, Q</b> - показать/скрыть панель управления</li>
<li><b>PageUp, Left, Shift+Space, Backspace</b> - страницу назад</li>
<li><b>PageDown, Right, Space</b> - страницу вперед</li>
<li><b>Home</b> - в начало книги</li>
<li><b>End</b> - в конец книги</li>
<li><b>Up</b> - строчку назад</li>
<li><b>Down</b> - строчку вперёд</li>
<li><b>A, Shift+A</b> - изменить размер шрифта</li>
<li><b>Enter, F, F11, ` (апостроф)</b> - вкл./выкл. полный экран</li>
<li><b>Z</b> - вкл./выкл. плавный скроллинг текста</li>
<li><b>Shift+Down/Shift+Up</b> - увеличить/уменьшить скорость скроллинга
<li><b>P</b> - установить страницу</li>
<li><b>Ctrl+F</b> - найти в тексте</li>
<li><b>Ctrl+C</b> - скопировать текст со страницы</li>
<li><b>R</b> - принудительно обновить книгу в обход кэша</li>
<li><b>X</b> - открыть недавние</li>
<li><b>O</b> - автономный режим</li>
<li><b>S</b> - открыть окно настроек</li>
</ul>
<div style="font-size: 120%">
<div class="text-h6 text-bold">Доступны следующие клавиатурные команды:</div>
<br>
</div>
<div class="q-mb-md" style="width: 550px">
<div class="text-right text-italic" style="font-size: 80%">* Изменить сочетания клавиш можно в настройках</div>
<UserHotKeys v-model="userHotKeys" readonly/>
</div>
</div>
</template>
@@ -31,25 +16,32 @@
import Vue from 'vue';
import Component from 'vue-class-component';
import UserHotKeys from '../../SettingsPage/UserHotKeys/UserHotKeys.vue';
export default @Component({
components: {
UserHotKeys,
},
})
class HotkeysHelpPage extends Vue {
created() {
}
get userHotKeys() {
return this.$store.state.reader.settings.userHotKeys;
}
set userHotKeys(value) {
//no setter
}
}
//-----------------------------------------------------------------------------
</script>
<style scoped>
.page {
flex: 1;
padding: 15px;
overflow-y: auto;
font-size: 120%;
line-height: 130%;
}
h4 {
margin: 0;
}
</style>

View File

@@ -1,6 +1,6 @@
<template>
<div class="page">
<h4>Управление с помощью мыши/тачскрина:</h4>
<span class="text-h6 text-bold">Управление с помощью мыши/тачскрина:</span>
<ul>
<li><b>ЛКМ/ТАЧ</b> по экрану в одну из областей - активация действия:</li>
<div class="click-map-page">
@@ -49,17 +49,12 @@ class MouseHelpPage extends Vue {
<style scoped>
.page {
flex: 1;
padding: 15px;
overflow-y: auto;
font-size: 120%;
line-height: 130%;
}
h4 {
margin: 0;
}
.click-map-page {
position: relative;
width: 400px;

View File

@@ -1,13 +1,14 @@
<template>
<div id="versionHistoryPage" class="page">
<span class="text-h6 text-bold">История версий:</span>
<br><br>
<span class="clickable" v-for="(item, index) in versionHeader" :key="index" @click="showRelease(item)">
<p>
{{ item }}
</p>
</span>
<br>
<h4>История версий:</h4>
<br>
<div v-for="item in versionContent" :id="item.key" :key="item.key">
@@ -58,15 +59,11 @@ class VersionHistoryPage extends Vue {
<style scoped>
.page {
flex: 1;
padding: 15px;
overflow-y: auto;
font-size: 120%;
line-height: 130%;
}
h4 {
margin: 0;
position: relative;
}
p {

View File

@@ -0,0 +1,131 @@
<template>
<div class="hidden"></div>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import Window from '../../share/Window.vue';
import * as utils from '../../../share/utils';
//import rstore from '../../../store/modules/reader';
export default @Component({
components: {
Window
},
watch: {
libs: function() {
this.sendLibs();
},
}
})
class LibsPage extends Vue {
created() {
this.popupWindow = null;
this.commit = this.$store.commit;
this.messageListener = null;
//this.commit('reader/setLibs', rstore.libsDefaults);
}
init() {
if (this.mode != 'liberama.top')
return;
this.childReady = false;
const subdomain = (window.location.protocol != 'http:' ? 'b.' : '');
this.origin = `http://${subdomain}${window.location.host}`;
this.messageListener = (event) => {
if (event.origin !== this.origin)
return;
//console.log(event.data);
this.recvMessage(event.data);
};
this.popupWindow = window.open(`${this.origin}/#/external-libs`);
if (this.popupWindow) {
window.addEventListener('message', this.messageListener);
//Проверка закрытия окна
(async() => {
while(this.popupWindow) {
if (this.popupWindow && this.popupWindow.closed)
this.close();
await utils.sleep(1000);
}
})();
//Установление связи с окном
(async() => {
let i = 0;
while(!this.childReady && this.popupWindow && i < 100) {
this.sendMessage({type: 'mes', data: 'hello'});
await utils.sleep(100);
i++;
}
this.sendLibs();
})();
}
}
recvMessage(d) {
if (d.type == 'mes') {
switch(d.data) {
case 'ready':
this.childReady = true;
break;
}
} else if (d.type == 'libs') {
this.commit('reader/setLibs', d.data);
} else if (d.type == 'close') {
this.close();
} else if (d.type == 'submitUrl') {
this.$emit('load-book', d.data);
this.sendMessage({type: 'notify', data: 'Ссылка передана в читалку'});
}
}
sendMessage(d) {
if (this.popupWindow)
this.popupWindow.postMessage(Object.assign({}, {from: 'LibsPage'}, d), this.origin);
}
done() {
window.removeEventListener('message', this.messageListener);
if (this.popupWindow) {
this.popupWindow.close();
this.popupWindow = null;
}
}
get mode() {
return this.$store.state.config.mode;
}
get libs() {
return this.$store.state.reader.libs;
}
sendLibs() {
this.sendMessage({type: 'libs', data: this.libs});
}
close() {
this.$emit('libs-close');
}
}
//-----------------------------------------------------------------------------
</script>
<style scoped>
.separator {
height: 1px;
background-color: #A0A0A0;
}
</style>

View File

@@ -47,7 +47,7 @@
default: false
}
},
data () {
data() {
return {
svgStyle: {
fill: this.cornerColor,
@@ -60,8 +60,13 @@
},
flipped: false,
svgPath1: 'M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z',
svgPath2: 'M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2',
svgPath3: 'M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z'
svgPath2: 'M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 ' +
'123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2',
svgPath3: 'M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 ' +
'C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 ' +
'176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 ' +
'216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 ' +
'C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z'
}
},
methods: {
@@ -99,7 +104,7 @@
}
</script>
<style>
<style scoped>
#github-corner .octo-arm {
transform-origin: 130px 106px
}
@@ -122,7 +127,8 @@
top: 0;
border: 0;
}
#github-corner-svg, #github-corner-svg .octo-arm, #github-corner-svg .octo-body {
transition: fill 1s ease;
}
#github-corner-svg, #github-corner-svg .octo-arm, #github-corner-svg .octo-body {
transition: fill 1s ease;
}
</style>

View File

@@ -1,32 +1,37 @@
<template>
<div ref="main" class="main">
<GithubCorner url="https://github.com/bookpauk/liberama" cornerColor="#1B695F"></GithubCorner>
<div class="part top">
<span class="greeting bold-font">{{ title }}</span>
<div class="space"></div>
<div ref="main" class="column no-wrap" style="min-height: 500px">
<div v-if="mode != 'liberama.top'" class="relative-position">
<GithubCorner url="https://github.com/bookpauk/liberama" cornerColor="#1B695F" gitColor="#EBE2C9"></GithubCorner>
</div>
<div class="col column justify-center items-center no-wrap overflow-hidden" style="min-height: 230px">
<span class="greeting"><b>{{ title }}</b></span>
<div class="q-my-sm"></div>
<span class="greeting">Добро пожаловать!</span>
<span class="greeting">Поддерживаются форматы: <b>fb2, html, txt</b> и сжатие: <b>zip, bz2, gz</b></span>
<span v-if="isExternalConverter" class="greeting">...а также форматы: <b>rtf, doc, docx, pdf, epub, mobi</b></span>
</div>
<div class="part center">
<el-input ref="input" placeholder="URL книги" v-model="bookUrl">
<el-button slot="append" icon="el-icon-check" @click="submitUrl"></el-button>
</el-input>
<div class="space"></div>
<div class="col-auto column justify-start items-center no-wrap overflow-hidden">
<q-input ref="input" class="full-width q-px-sm" style="max-width: 700px" outlined dense bg-color="white" v-model="bookUrl" placeholder="URL книги">
<template v-slot:append>
<q-btn rounded flat style="width: 40px" icon="la la-check" @click="submitUrl"/>
</template>
</q-input>
<input type="file" id="file" ref="file" @change="loadFile" style='display: none;'/>
<el-button size="mini" @click="loadFileClick">
<div class="q-my-sm"></div>
<q-btn no-caps dense class="q-px-sm" color="primary" size="13px" @click="loadFileClick">
Загрузить файл с диска
</el-button>
<div class="space"></div>
<el-button size="mini" @click="loadBufferClick">
</q-btn>
<div class="q-my-sm"></div>
<q-btn no-caps dense class="q-px-sm" color="primary" size="13px" @click="loadBufferClick">
Из буфера обмена
</el-button>
</q-btn>
<div class="space"></div>
<div class="space"></div>
<div v-if="mode == 'omnireader'">
<div class="q-my-md"></div>
<!--div v-if="mode == 'omnireader'">
<div ref="yaShare2" class="ya-share2"
data-services="collections,vkontakte,facebook,odnoklassniki,twitter,telegram"
data-description="Чтение fb2-книг онлайн. Загрузка любой страницы интернета одним кликом, синхронизация между устройствами, удобное управление, регистрация не требуется."
@@ -34,12 +39,12 @@
data-url="https://omnireader.ru">
</div>
</div>
<div class="space"></div>
<div class="q-my-sm"></div-->
<span v-if="mode == 'omnireader'" class="bottom-span clickable" @click="openComments">Отзывы о читалке</span>
<span v-if="mode == 'omnireader'" class="bottom-span clickable" @click="openOldVersion">Старая версия</span>
</div>
<div class="part bottom">
<div class="col column justify-end items-center no-wrap overflow-hidden">
<span class="bottom-span clickable" @click="openHelp">Справка</span>
<span class="bottom-span clickable" @click="openDonate">Помочь проекту</span>
@@ -77,8 +82,8 @@ class LoaderPage extends Vue {
mounted() {
this.progress = this.$refs.progress;
if (this.mode == 'omnireader')
Ya.share2(this.$refs.yaShare2);// eslint-disable-line no-undef
/*if (this.mode == 'omnireader')
Ya.share2(this.$refs.yaShare2);// eslint-disable-line no-undef*/
}
activated() {
@@ -88,6 +93,8 @@ class LoaderPage extends Vue {
get title() {
if (this.mode == 'omnireader')
return 'Omni Reader - браузерная онлайн-читалка.';
if (this.mode == 'liberama.top')
return 'Liberama Reader - браузерная онлайн-читалка.';
return 'Универсальная читалка книг и ресурсов интернета.';
}
@@ -143,12 +150,12 @@ class LoaderPage extends Vue {
this.pasteTextActive = !this.pasteTextActive;
}
openHelp() {
this.$emit('help-toggle');
openHelp(event) {
this.$emit('do-action', {action: 'help', event});
}
openDonate() {
this.$emit('donate-toggle');
this.$emit('do-action', {action: 'donate'});
}
openComments() {
@@ -166,82 +173,39 @@ class LoaderPage extends Vue {
//недостатки сторонних ui
const input = this.$refs.input.$refs.input;
if (document.activeElement === input && event.type == 'keydown' && event.code == 'Enter') {
if (document.activeElement === input && event.type == 'keydown' && event.key == 'Enter') {
this.submitUrl();
}
if (event.type == 'keydown' && (event.code == 'F1' || (document.activeElement !== input && event.code == 'KeyH'))) {
this.$emit('help-toggle');
event.preventDefault();
event.stopPropagation();
return true;
}
if (event.type == 'keydown' && (document.activeElement !== input && event.code == 'KeyQ')) {
this.$emit('tool-bar-toggle');
event.preventDefault();
event.stopPropagation();
return true;
if (event.type == 'keydown' && document.activeElement !== input) {
const action = this.$root.readerActionByKeyEvent(event);
switch (action) {
case 'help':
this.openHelp(event);
return true;
}
}
return false;
}
}
//-----------------------------------------------------------------------------
</script>
<style scoped>
.main {
flex: 1;
display: flex;
flex-direction: column;
min-height: 480px;
}
.part {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.greeting {
font-size: 120%;
line-height: 160%;
}
.bold-font {
font-weight: bold;
}
.clickable {
color: blue;
text-decoration: underline;
cursor: pointer;
}
.top {
min-height: 120px;
}
.center {
justify-content: flex-start;
padding: 0 10px 0 10px;
min-height: 250px;
}
.bottom {
justify-content: flex-end;
}
.bottom-span {
font-size: 70%;
margin-bottom: 10px;
}
.el-input {
max-width: 700px;
}
.space {
height: 20px;
}
</style>

View File

@@ -3,14 +3,12 @@
<template slot="header">
<span style="position: relative; top: -3px">
Вставьте текст и нажмите
<span class="clickable" style="font-size: 150%; position: relative; top: 1px" @click="loadBuffer">загрузить</span>
<span class="clickable text-primary" style="font-size: 150%; position: relative; top: 1px" @click="loadBuffer">загрузить</span>
или F2
</span>
</template>
<div>
<el-input placeholder="Введите название текста" class="input" v-model="bookTitle"></el-input>
</div>
<q-input class="q-px-sm" dense borderless v-model="bookTitle" placeholder="Введите название текста"/>
<hr/>
<textarea ref="textArea" class="text" @paste="calcTitle"></textarea>
</Window>
@@ -70,7 +68,7 @@ class PasteTextPage extends Vue {
}
loadBuffer() {
this.$emit('load-buffer', {buffer: `<cut-title>${this.bookTitle}</cut-title>${this.$refs.textArea.value}`});
this.$emit('load-buffer', {buffer: `<buffer><cut-title>${utils.escapeXml(this.bookTitle)}</cut-title>${this.$refs.textArea.value}</buffer>`});
this.close();
}
@@ -80,7 +78,7 @@ class PasteTextPage extends Vue {
keyHook(event) {
if (event.type == 'keydown') {
switch (event.code) {
switch (event.key) {
case 'F2':
this.loadBuffer();
break;

View File

@@ -1,8 +1,24 @@
<template>
<div v-show="visible" class="main">
<div class="center">
<el-progress type="circle" :width="100" :stroke-width="6" color="#0F9900" :percentage="percentage"></el-progress>
<p class="text">{{ text }}</p>
<div v-show="visible" class="column justify-center items-center z-max" style="background-color: rgba(0, 0, 0, 0.8)">
<div class="column justify-start items-center" style="height: 250px">
<q-circular-progress
show-value
instant-feedback
font-size="13px"
:value="percentage"
size="100px"
:thickness="0.11"
color="green-7"
track-color="grey-4"
class="q-ma-md"
>
<span class="text-yellow">{{ percentage }}%</span>
</q-circular-progress>
<div>
<span class="text-yellow">{{ text }}</span>
<q-icon :style="iconStyle" color="yellow" name="la la-slash" size="20px"/>
</div>
</div>
</div>
</template>
@@ -11,11 +27,13 @@
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import * as utils from '../../../share/utils';
const ruMessage = {
'start': ' ',
'finish': ' ',
'error': ' ',
'queue': 'очередь',
'download': 'скачивание',
'decompress': 'распаковка',
'convert': 'конвертирование',
@@ -32,68 +50,51 @@ class ProgressPage extends Vue {
step = 1;
progress = 0;
visible = false;
iconStyle = '';
show() {
this.$el.style.width = this.$parent.$el.offsetWidth + 'px';
this.$el.style.height = this.$parent.$el.offsetHeight + 'px';
this.text = '';
this.totalSteps = 1;
this.step = 1;
this.progress = 0;
this.iconAngle = 0;
this.ani = false;
this.visible = true;
}
hide() {
this.visible = false;
this.text = '';
this.iconAngle = 0;
}
setState(state) {
if (state.state)
this.text = (ruMessage[state.state] ? ruMessage[state.state] : state.state);
if (state.state) {
if (state.state == 'queue') {
this.text = (state.place ? 'Номер в очереди: ' + state.place : '');
} else {
this.text = (ruMessage[state.state] ? ruMessage[state.state] : state.state);
}
}
this.step = (state.step ? state.step : this.step);
this.totalSteps = (state.totalSteps > this.totalSteps ? state.totalSteps : this.totalSteps);
this.progress = state.progress || 0;
if (!this.ani) {
(async() => {
this.ani = true;
this.iconAngle += 30;
this.iconStyle = `transform: rotate(${this.iconAngle}deg); transition: 150ms linear`;
await utils.sleep(150);
this.ani = false;
})();
}
}
get percentage() {
let circle = document.querySelector('path[class="el-progress-circle__path"]');
if (circle)
circle.style.transition = '';
return Math.round(((this.step - 1)/this.totalSteps + this.progress/(100*this.totalSteps))*100);
}
}
//-----------------------------------------------------------------------------
</script>
<style scoped>
.main {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 100;
background-color: rgba(0, 0, 0, 0.8);
position: absolute;
}
.center {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
color: white;
height: 300px;
}
.text {
color: yellow;
}
</style>
<style>
.el-progress__text {
color: lightgreen !important;
}
</style>

View File

@@ -1,97 +1,161 @@
<template>
<el-container>
<el-header v-show="toolBarActive" height='50px'>
<div ref="header" class="header">
<el-tooltip content="Загрузить книгу" :open-delay="1000" effect="light">
<el-button ref="loader" class="tool-button" :class="buttonActiveClass('loader')" @click="buttonClick('loader')"><i class="el-icon-back"></i></el-button>
</el-tooltip>
<div class="column no-wrap">
<div ref="header" class="header" v-show="toolBarActive">
<div ref="buttons" class="row justify-between no-wrap">
<div>
<el-tooltip v-show="showToolButton['undoAction']" content="Действие назад" :open-delay="1000" effect="light">
<el-button ref="undoAction" class="tool-button" :class="buttonActiveClass('undoAction')" @click="buttonClick('undoAction')" ><i class="el-icon-arrow-left"></i></el-button>
</el-tooltip>
<el-tooltip v-show="showToolButton['redoAction']" content="Действие вперед" :open-delay="1000" effect="light">
<el-button ref="redoAction" class="tool-button" :class="buttonActiveClass('redoAction')" @click="buttonClick('redoAction')" ><i class="el-icon-arrow-right"></i></el-button>
</el-tooltip>
<div class="space"></div>
<el-tooltip v-show="showToolButton['fullScreen']" content="На весь экран" :open-delay="1000" effect="light">
<el-button ref="fullScreen" class="tool-button" :class="buttonActiveClass('fullScreen')" @click="buttonClick('fullScreen')"><i class="el-icon-rank"></i></el-button>
</el-tooltip>
<el-tooltip v-show="showToolButton['scrolling']" content="Плавный скроллинг" :open-delay="1000" effect="light">
<el-button ref="scrolling" class="tool-button" :class="buttonActiveClass('scrolling')" @click="buttonClick('scrolling')"><i class="el-icon-sort"></i></el-button>
</el-tooltip>
<el-tooltip v-show="showToolButton['setPosition']" content="На страницу" :open-delay="1000" effect="light">
<el-button ref="setPosition" class="tool-button" :class="buttonActiveClass('setPosition')" @click="buttonClick('setPosition')"><i class="el-icon-d-arrow-right"></i></el-button>
</el-tooltip>
<el-tooltip v-show="showToolButton['search']" content="Найти в тексте" :open-delay="1000" effect="light">
<el-button ref="search" class="tool-button" :class="buttonActiveClass('search')" @click="buttonClick('search')"><i class="el-icon-search"></i></el-button>
</el-tooltip>
<el-tooltip v-show="showToolButton['copyText']" content="Скопировать текст со страницы" :open-delay="1000" effect="light">
<el-button ref="copyText" class="tool-button" :class="buttonActiveClass('copyText')" @click="buttonClick('copyText')"><i class="el-icon-edit-outline"></i></el-button>
</el-tooltip>
<el-tooltip v-show="showToolButton['refresh']" content="Принудительно обновить книгу в обход кэша" :open-delay="1000" effect="light">
<el-button ref="refresh" class="tool-button" :class="buttonActiveClass('refresh')" @click="buttonClick('refresh')">
<i class="el-icon-refresh" :class="{clear: !showRefreshIcon}"></i>
</el-button>
</el-tooltip>
<div class="space"></div>
<el-tooltip v-show="showToolButton['offlineMode']" content="Автономный режим (без интернета)" :open-delay="1000" effect="light">
<el-button ref="offlineMode" class="tool-button" :class="buttonActiveClass('offlineMode')" @click="buttonClick('offlineMode')"><i class="el-icon-connection"></i></el-button>
</el-tooltip>
<el-tooltip v-show="showToolButton['recentBooks']" content="Открыть недавние" :open-delay="1000" effect="light">
<el-button ref="recentBooks" class="tool-button" :class="buttonActiveClass('recentBooks')" @click="buttonClick('recentBooks')"><i class="el-icon-document"></i></el-button>
</el-tooltip>
<button ref="loader" class="tool-button" :class="buttonActiveClass('loader')" @click="buttonClick('loader')" v-ripple>
<q-icon name="la la-arrow-left" size="32px"/>
<q-tooltip :delay="1500" anchor="bottom right" content-style="font-size: 80%">{{ rstore.readerActions['loader'] }}</q-tooltip>
</button>
</div>
<el-tooltip content="Настроить" :open-delay="1000" effect="light">
<el-button ref="settings" class="tool-button" :class="buttonActiveClass('settings')" @click="buttonClick('settings')"><i class="el-icon-setting"></i></el-button>
</el-tooltip>
</div>
</el-header>
<div>
<button ref="undoAction" v-show="showToolButton['undoAction']" class="tool-button" :class="buttonActiveClass('undoAction')" @click="buttonClick('undoAction')" v-ripple>
<q-icon name="la la-angle-left" size="32px"/>
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['undoAction'] }}</q-tooltip>
</button>
<button ref="redoAction" v-show="showToolButton['redoAction']" class="tool-button" :class="buttonActiveClass('redoAction')" @click="buttonClick('redoAction')" v-ripple>
<q-icon name="la la-angle-right" size="32px"/>
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['redoAction'] }}</q-tooltip>
</button>
<div class="space"></div>
<button ref="fullScreen" v-show="showToolButton['fullScreen']" class="tool-button" :class="buttonActiveClass('fullScreen')" @click="buttonClick('fullScreen')" v-ripple>
<q-icon :name="(fullScreenActive ? 'la la-compress-arrows-alt': 'la la-expand-arrows-alt')" size="32px"/>
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['fullScreen'] }}</q-tooltip>
</button>
<button ref="scrolling" v-show="showToolButton['scrolling']" class="tool-button" :class="buttonActiveClass('scrolling')" @click="buttonClick('scrolling')" v-ripple>
<q-icon name="la la-film" size="32px"/>
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['scrolling'] }}</q-tooltip>
</button>
<button ref="setPosition" v-show="showToolButton['setPosition']" class="tool-button" :class="buttonActiveClass('setPosition')" @click="buttonClick('setPosition')" v-ripple>
<q-icon name="la la-angle-double-right" size="32px"/>
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['setPosition'] }}</q-tooltip>
</button>
<button ref="search" v-show="showToolButton['search']" class="tool-button" :class="buttonActiveClass('search')" @click="buttonClick('search')" v-ripple>
<q-icon name="la la-search" size="32px"/>
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['search'] }}</q-tooltip>
</button>
<button ref="copyText" v-show="showToolButton['copyText']" class="tool-button" :class="buttonActiveClass('copyText')" @click="buttonClick('copyText')" v-ripple>
<q-icon name="la la-copy" size="32px"/>
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['copyText'] }}</q-tooltip>
</button>
<button ref="splitToPara" v-show="showToolButton['splitToPara']" class="tool-button" :class="buttonActiveClass('splitToPara')" @click="buttonClick('splitToPara')" v-ripple>
<q-icon name="la la-retweet" size="32px"/>
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['splitToPara'] }}</q-tooltip>
</button>
<button ref="refresh" v-show="showToolButton['refresh']" class="tool-button" :class="buttonActiveClass('refresh')" @click="buttonClick('refresh')" v-ripple>
<q-icon name="la la-sync" size="32px" :class="{clear: !showRefreshIcon}"/>
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['refresh'] }}</q-tooltip>
</button>
<div class="space"></div>
<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>
</button>
<button ref="recentBooks" v-show="showToolButton['recentBooks']" class="tool-button" :class="buttonActiveClass('recentBooks')" @click="buttonClick('recentBooks')" v-ripple>
<q-icon name="la la-book-open" size="32px"/>
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['recentBooks'] }}</q-tooltip>
</button>
</div>
<el-main>
<div>
<button ref="offlineMode" v-show="showToolButton['offlineMode']" class="tool-button" :class="buttonActiveClass('offlineMode')" @click="buttonClick('offlineMode')" v-ripple>
<q-icon name="la la-unlink" size="32px"/>
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">{{ rstore.readerActions['offlineMode'] }}</q-tooltip>
</button>
<button ref="settings" class="tool-button" :class="buttonActiveClass('settings')" @click="buttonClick('settings')" v-ripple>
<q-icon name="la la-cog" size="32px"/>
<q-tooltip :delay="1500" anchor="bottom left" content-style="font-size: 80%">{{ rstore.readerActions['settings'] }}</q-tooltip>
</button>
</div>
</div>
</div>
<div class="main col row relative-position">
<keep-alive>
<component ref="page" :is="activePage"
<component ref="page" class="col" :is="activePage"
@load-book="loadBook"
@load-file="loadFile"
@book-pos-changed="bookPosChanged"
@tool-bar-toggle="toolBarToggle"
@full-screen-toogle="fullScreenToggle"
@stop-scrolling="stopScrolling"
@scrolling-toggle="scrollingToggle"
@help-toggle="helpToggle"
@donate-toggle="donateToggle"
@do-action="doAction"
></component>
</keep-alive>
<SetPositionPage v-if="setPositionActive" ref="setPositionPage" @set-position-toggle="setPositionToggle" @book-pos-changed="bookPosChanged"></SetPositionPage>
<SearchPage v-show="searchActive" ref="searchPage"
@search-toggle="searchToggle"
@do-action="doAction"
@book-pos-changed="bookPosChanged"
@start-text-search="startTextSearch"
@stop-text-search="stopTextSearch">
</SearchPage>
<CopyTextPage v-if="copyTextActive" ref="copyTextPage" @copy-text-toggle="copyTextToggle"></CopyTextPage>
<RecentBooksPage v-show="recentBooksActive" ref="recentBooksPage" @load-book="loadBook" @recent-books-toggle="recentBooksToggle"></RecentBooksPage>
<SettingsPage v-if="settingsActive" ref="settingsPage" @settings-toggle="settingsToggle"></SettingsPage>
<HelpPage v-if="helpActive" ref="helpPage" @help-toggle="helpToggle"></HelpPage>
<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>
<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>
<el-dialog
title="Что нового:"
:visible.sync="whatsNewVisible"
width="80%">
<Dialog ref="dialog1" v-model="whatsNewVisible">
<template slot="header">
Что нового:
</template>
<div style="line-height: 20px" v-html="whatsNewContent"></div>
<span class="clickable" @click="openVersionHistory">Посмотреть историю версий</span>
<span slot="footer" class="dialog-footer">
<el-button @click="whatsNewDisable">Больше не показывать</el-button>
<span slot="footer">
<q-btn class="q-px-md" dense no-caps @click="whatsNewDisable">Больше не показывать</q-btn>
</span>
</el-dialog>
</Dialog>
</el-main>
</el-container>
<Dialog ref="dialog2" v-model="donationVisible">
<template slot="header">
Здравствуйте, уважаемые читатели!
</template>
<div style="word-break: normal">
Стартовала ежегодная акция "Оплатим хостинг вместе".<br><br>
Для оплаты годового хостинга читалки, необходимо собрать около 2000 рублей.
В настоящий момент у автора эта сумма есть в наличии. Однако будет справедливо, если каждый
сможет проголосовать рублем за то, чтобы читалка так и оставалась:
<ul>
<li>непрерывно улучшаемой</li>
<li>без рекламы</li>
<li>без регистрации</li>
<li>Open Source</li>
</ul>
Автор также обращается с просьбой о помощи в распространении
<a href="https://omnireader.ru" target="_blank">ссылки</a>
<q-icon class="copy-icon" name="la la-copy" @click="copyLink('https://omnireader.ru')">
<q-tooltip :delay="1000" anchor="top middle" self="center middle" content-style="font-size: 80%">Скопировать</q-tooltip>
</q-icon>
на читалку через тематические форумы, соцсети, мессенджеры и пр.
Чем нас больше, тем легче оставаться на плаву и тем больше мотивации у разработчика, чтобы продолжать работать над проектом.
<br><br>
Если соберется бóльшая сумма, то разработка децентрализованной библиотеки для свободного обмена книгами будет по возможности ускорена.
<br><br>
P.S. При необходимости можно воспользоваться подходящим обменником на <a href="https://www.bestchange.ru" target="_blank">bestchange.ru</a>
<br><br>
<div class="row justify-center">
<q-btn class="q-px-sm" color="primary" dense no-caps rounded @click="openDonate">Помочь проекту</q-btn>
</div>
</div>
<span slot="footer">
<span class="clickable row justify-end" style="font-size: 60%; color: grey" @click="donationDialogDisable">Больше не показывать</span>
<br>
<q-btn class="q-px-sm" dense no-caps @click="donationDialogRemind">Напомнить позже</q-btn>
</span>
</Dialog>
</div>
</div>
</template>
<script>
@@ -108,13 +172,16 @@ import ProgressPage from './ProgressPage/ProgressPage.vue';
import SetPositionPage from './SetPositionPage/SetPositionPage.vue';
import SearchPage from './SearchPage/SearchPage.vue';
import CopyTextPage from './CopyTextPage/CopyTextPage.vue';
import LibsPage from './LibsPage/LibsPage.vue';
import RecentBooksPage from './RecentBooksPage/RecentBooksPage.vue';
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 Dialog from '../share/Dialog.vue';
import bookManager from './share/bookManager';
import rstore from '../../store/modules/reader';
import readerApi from '../../api/reader';
import * as utils from '../../share/utils';
import {versionHistory} from './versionHistory';
@@ -128,11 +195,13 @@ export default @Component({
SetPositionPage,
SearchPage,
CopyTextPage,
LibsPage,
RecentBooksPage,
SettingsPage,
HelpPage,
ClickMapPage,
ServerStorage,
Dialog,
},
watch: {
bookPos: function(newValue) {
@@ -174,7 +243,9 @@ export default @Component({
},
})
class Reader extends Vue {
rstore = {};
loaderActive = false;
offlineModeActive = false;
progressActive = false;
fullScreenActive = false;
@@ -182,8 +253,8 @@ class Reader extends Vue {
setPositionActive = false;
searchActive = false;
copyTextActive = false;
libsActive = false;
recentBooksActive = false;
offlineModeActive = false;
settingsActive = false;
helpActive = false;
clickMapActive = false;
@@ -200,8 +271,10 @@ class Reader extends Vue {
whatsNewVisible = false;
whatsNewContent = '';
donationVisible = false;
created() {
this.rstore = rstore;
this.loading = true;
this.commit = this.$store.commit;
this.dispatch = this.$store.dispatch;
@@ -244,8 +317,8 @@ class Reader extends Vue {
(async() => {
await bookManager.init(this.settings);
bookManager.addEventListener(this.bookManagerEvent);
if (this.$root.rootRoute == '/reader') {
if (this.$root.rootRoute() == '/reader') {
if (this.routeParamUrl) {
await this.loadBook({url: this.routeParamUrl, bookPos: this.routeParamPos, force: this.routeParamRefresh});
} else {
@@ -258,9 +331,10 @@ class Reader extends Vue {
this.checkActivateDonateHelpPage();
this.loading = false;
await this.showWhatsNew();
this.updateRoute();
await this.showWhatsNew();
await this.showDonation();
})();
}
@@ -272,16 +346,27 @@ class Reader extends Vue {
this.clickControl = settings.clickControl;
this.blinkCachedLoad = settings.blinkCachedLoad;
this.showWhatsNewDialog = settings.showWhatsNewDialog;
this.showDonationDialog2020 = settings.showDonationDialog2020;
this.showToolButton = settings.showToolButton;
this.enableSitesFilter = settings.enableSitesFilter;
this.readerActionByKeyCode = utils.userHotKeysObjectSwap(settings.userHotKeys);
this.$root.readerActionByKeyEvent = (event) => {
return this.readerActionByKeyCode[utils.keyEventToCode(event)];
}
this.updateHeaderMinWidth();
}
updateHeaderMinWidth() {
const showButtonCount = Object.values(this.showToolButton).reduce((a, b) => a + (b ? 1 : 0), 0);
if (this.$refs.header)
this.$refs.header.style.minWidth = 65*showButtonCount + 'px';
if (this.$refs.buttons)
this.$refs.buttons.style.minWidth = 65*showButtonCount + 'px';
(async() => {
await utils.sleep(1000);
if (this.$refs.header)
this.$refs.header.style.overflowX = 'auto';
})();
}
checkSetStorageAccessKey() {
@@ -337,6 +422,41 @@ class Reader extends Vue {
}
}
async showDonation() {
await utils.sleep(3000);
const today = utils.formatDate(new Date(), 'coDate');
if ((this.mode == 'omnireader' || this.mode == 'liberama.top') && today < '2020-03-01' && this.showDonationDialog2020 && this.donationRemindDate != today) {
this.donationVisible = true;
}
}
donationDialogDisable() {
this.donationVisible = false;
if (this.showDonationDialog2020) {
const newSettings = Object.assign({}, this.settings, { showDonationDialog2020: false });
this.commit('reader/setSettings', newSettings);
}
}
donationDialogRemind() {
this.donationVisible = false;
this.commit('reader/setDonationRemindDate', utils.formatDate(new Date(), 'coDate'));
}
openDonate() {
this.donationVisible = false;
this.donateToggle();
}
async copyLink(link) {
const result = await utils.copyTextToClipboard(link);
if (result)
this.$root.notify.success(`Ссылка ${link} успешно скопирована в буфер обмена`);
else
this.$root.notify.error('Копирование не удалось');
}
openVersionHistory() {
this.whatsNewVisible = false;
this.versionHistoryToggle();
@@ -455,6 +575,10 @@ class Reader extends Vue {
return this.$store.state.reader.whatsNewContentHash;
}
get donationRemindDate() {
return this.$store.state.reader.donationRemindDate;
}
addAction(pos) {
let a = this.actionList;
if (!a.length || a[a.length - 1] != pos) {
@@ -473,26 +597,13 @@ class Reader extends Vue {
fullScreenToggle() {
this.fullScreenActive = !this.fullScreenActive;
if (this.fullScreenActive) {
const element = document.documentElement;
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.webkitrequestFullscreen) {
element.webkitRequestFullscreen();
} else if (element.mozRequestFullscreen) {
element.mozRequestFullScreen();
}
this.$q.fullscreen.request();
} else {
if (document.cancelFullScreen) {
document.cancelFullScreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitCancelFullScreen) {
document.webkitCancelFullScreen();
}
this.$q.fullscreen.exit();
}
}
closeAllTextPages() {
closeAllWindows() {
this.setPositionActive = false;
this.copyTextActive = false;
this.recentBooksActive = false;
@@ -505,14 +616,15 @@ class Reader extends Vue {
loaderToggle() {
this.loaderActive = !this.loaderActive;
if (this.loaderActive) {
this.closeAllTextPages();
this.closeAllWindows();
}
}
setPositionToggle() {
this.setPositionActive = !this.setPositionActive;
if (this.setPositionActive && this.activePage == 'TextPage' && this.mostRecentBook()) {
this.closeAllTextPages();
const page = this.$refs.page;
if (this.setPositionActive && this.activePage == 'TextPage' && page.parsed) {
this.closeAllWindows();
this.setPositionActive = true;
this.$nextTick(() => {
@@ -564,7 +676,7 @@ class Reader extends Vue {
this.searchActive = !this.searchActive;
const page = this.$refs.page;
if (this.searchActive && this.activePage == 'TextPage' && page.parsed) {
this.closeAllTextPages();
this.closeAllWindows();
this.searchActive = true;
this.$nextTick(() => {
@@ -580,7 +692,7 @@ class Reader extends Vue {
this.copyTextActive = !this.copyTextActive;
const page = this.$refs.page;
if (this.copyTextActive && this.activePage == 'TextPage' && page.parsed) {
this.closeAllTextPages();
this.closeAllWindows();
this.copyTextActive = true;
this.$nextTick(() => {
@@ -591,10 +703,20 @@ class Reader extends Vue {
}
}
refreshBookSplitToPara() {
if (this.mostRecentBook()) {
this.loadBook({url: this.mostRecentBook().url, skipCheck: true, isText: true, force: true});
}
}
recentBooksClose() {
this.recentBooksActive = false;
}
recentBooksToggle() {
this.recentBooksActive = !this.recentBooksActive;
if (this.recentBooksActive) {
this.closeAllTextPages();
this.closeAllWindows();
this.$refs.recentBooksPage.init();
this.recentBooksActive = true;
} else {
@@ -602,6 +724,20 @@ class Reader extends Vue {
}
}
libsClose() {
if (this.libsActive)
this.libsToogle();
}
libsToogle() {
this.libsActive = !this.libsActive;
if (this.libsActive) {
this.$refs.libsPage.init();
} else {
this.$refs.libsPage.done();
}
}
offlineModeToggle() {
this.offlineModeActive = !this.offlineModeActive;
this.$refs.serverStorage.offlineModeActive = this.offlineModeActive;
@@ -610,7 +746,7 @@ class Reader extends Vue {
settingsToggle() {
this.settingsActive = !this.settingsActive;
if (this.settingsActive) {
this.closeAllTextPages();
this.closeAllWindows();
this.settingsActive = true;
this.$nextTick(() => {
@@ -624,7 +760,7 @@ class Reader extends Vue {
helpToggle() {
this.helpActive = !this.helpActive;
if (this.helpActive) {
this.closeAllTextPages();
this.closeAllWindows();
this.helpActive = true;
}
}
@@ -653,81 +789,55 @@ class Reader extends Vue {
}
}
buttonClick(button) {
const activeClass = this.buttonActiveClass(button);
undoAction() {
if (this.actionCur > 0) {
this.actionCur--;
this.bookPosChanged({bookPos: this.actionList[this.actionCur]});
}
}
this.$refs[button].$el.blur();
redoAction() {
if (this.actionCur < this.actionList.length - 1) {
this.actionCur++;
this.bookPosChanged({bookPos: this.actionList[this.actionCur]});
}
}
buttonClick(action) {
const activeClass = this.buttonActiveClass(action);
this.$refs[action].blur();
if (activeClass['tool-button-disabled'])
return;
switch (button) {
case 'loader':
this.loaderToggle();
break;
case 'undoAction':
if (this.actionCur > 0) {
this.actionCur--;
this.bookPosChanged({bookPos: this.actionList[this.actionCur]});
}
break;
case 'redoAction':
if (this.actionCur < this.actionList.length - 1) {
this.actionCur++;
this.bookPosChanged({bookPos: this.actionList[this.actionCur]});
}
break;
case 'fullScreen':
this.fullScreenToggle();
break;
case 'setPosition':
this.setPositionToggle();
break;
case 'scrolling':
this.scrollingToggle();
break;
case 'search':
this.searchToggle();
break;
case 'copyText':
this.copyTextToggle();
break;
case 'refresh':
this.refreshBook();
break;
case 'recentBooks':
this.recentBooksToggle();
break;
case 'offlineMode':
this.offlineModeToggle();
break;
case 'settings':
this.settingsToggle();
break;
}
this.doAction({action});
}
buttonActiveClass(button) {
buttonActiveClass(action) {
const classActive = { 'tool-button-active': true, 'tool-button-active:hover': true };
const classDisabled = { 'tool-button-disabled': true, 'tool-button-disabled:hover': true };
let classResult = {};
switch (button) {
switch (action) {
case 'loader':
case 'fullScreen':
case 'setPosition':
case 'scrolling':
case 'search':
case 'copyText':
case 'splitToPara':
case 'refresh':
case 'libs':
case 'recentBooks':
case 'offlineMode':
case 'settings':
if (this[`${button}Active`])
if (this.progressActive) {
classResult = classDisabled;
} else if (this[`${action}Active`]) {
classResult = classActive;
}
break;
}
switch (button) {
case 'undoAction':
if (this.actionCur <= 0)
classResult = classDisabled;
@@ -739,7 +849,7 @@ class Reader extends Vue {
}
if (this.activePage == 'LoaderPage' || !this.mostRecentBookReactive) {
switch (button) {
switch (action) {
case 'undoAction':
case 'redoAction':
case 'setPosition':
@@ -748,8 +858,9 @@ class Reader extends Vue {
case 'copyText':
classResult = classDisabled;
break;
case 'recentBooks':
case 'splitToPara':
case 'refresh':
case 'recentBooks':
if (!this.mostRecentBookReactive)
classResult = classDisabled;
break;
@@ -824,6 +935,8 @@ class Reader extends Vue {
return;
}
this.closeAllWindows();
let url = encodeURI(decodeURI(opts.url));
if ((url.indexOf('http://') != 0) && (url.indexOf('https://') != 0) &&
@@ -900,9 +1013,16 @@ class Reader extends Vue {
// не удалось, скачиваем книгу полностью с конвертацией
let loadCached = true;
if (!book) {
book = await readerApi.loadBook({url, enableSitesFilter: this.enableSitesFilter}, (state) => {
progress.setState(state);
});
book = await readerApi.loadBook({
url,
skipCheck: (opts.skipCheck ? true : false),
isText: (opts.isText ? true : false),
enableSitesFilter: this.enableSitesFilter
},
(state) => {
progress.setState(state);
}
);
loadCached = false;
}
@@ -930,7 +1050,7 @@ class Reader extends Vue {
} catch (e) {
progress.hide(); this.progressActive = false;
this.loaderActive = true;
this.$alert(e.message, 'Ошибка', {type: 'error'});
this.$root.stdDialog.alert(e.message, 'Ошибка', {color: 'negative'});
}
}
@@ -954,7 +1074,7 @@ class Reader extends Vue {
} catch (e) {
progress.hide(); this.progressActive = false;
this.loaderActive = true;
this.$alert(e.message, 'Ошибка', {type: 'error'});
this.$root.stdDialog.alert(e.message, 'Ошибка', {color: 'negative'});
}
}
@@ -986,117 +1106,181 @@ class Reader extends Vue {
}
}
doAction(opts) {
let result = true;
let {action = '', event = false} = opts;
switch (action) {
case 'loader':
this.loaderToggle();
break;
case 'help':
this.helpToggle();
break;
case 'undoAction':
this.undoAction();
break;
case 'redoAction':
this.redoAction();
break;
case 'fullScreen':
this.fullScreenToggle();
break;
case 'scrolling':
this.scrollingToggle();
break;
case 'stopScrolling':
this.stopScrolling();
break;
case 'setPosition':
this.setPositionToggle();
break;
case 'search':
this.searchToggle();
break;
case 'copyText':
this.copyTextToggle();
break;
case 'splitToPara':
this.refreshBookSplitToPara();
break;
case 'refresh':
this.refreshBook();
break;
case 'libs':
this.libsToogle();
break;
case 'recentBooks':
this.recentBooksToggle();
break;
case 'offlineMode':
this.offlineModeToggle();
break;
case 'settings':
this.settingsToggle();
break;
case 'switchToolbar':
this.toolBarToggle();
break;
case 'donate':
this.donateToggle();
break;
default:
result = false;
break;
}
if (!result && this.activePage == 'TextPage' && this.$refs.page) {
result = true;
const textPage = this.$refs.page;
switch (action) {
case 'bookBegin':
textPage.doHome();
break;
case 'bookEnd':
textPage.doEnd();
break;
case 'pageBack':
textPage.doPageUp();
break;
case 'pageForward':
textPage.doPageDown();
break;
case 'lineBack':
textPage.doUp();
break;
case 'lineForward':
textPage.doDown();
break;
case 'incFontSize':
textPage.doFontSizeInc();
break;
case 'decFontSize':
textPage.doFontSizeDec();
break;
case 'scrollingSpeedUp':
textPage.doScrollingSpeedUp();
break;
case 'scrollingSpeedDown':
textPage.doScrollingSpeedDown();
break;
default:
result = false;
break;
}
}
if (result && event) {
event.preventDefault();
event.stopPropagation();
}
return result;
}
keyHook(event) {
if (this.$root.rootRoute == '/reader') {
let handled = false;
if (!handled && this.helpActive)
handled = this.$refs.helpPage.keyHook(event);
let result = false;
if (this.$root.rootRoute() == '/reader') {
if (this.$root.stdDialog.active || this.$refs.dialog1.active || this.$refs.dialog2.active)
return result;
if (!handled && this.settingsActive)
handled = this.$refs.settingsPage.keyHook(event);
if (!result && this.helpActive)
result = this.$refs.helpPage.keyHook(event);
if (!handled && this.recentBooksActive)
handled = this.$refs.recentBooksPage.keyHook(event);
if (!result && this.settingsActive)
result = this.$refs.settingsPage.keyHook(event);
if (!handled && this.setPositionActive)
handled = this.$refs.setPositionPage.keyHook(event);
if (!result && this.recentBooksActive)
result = this.$refs.recentBooksPage.keyHook(event);
if (!handled && this.searchActive)
handled = this.$refs.searchPage.keyHook(event);
if (!result && this.setPositionActive)
result = this.$refs.setPositionPage.keyHook(event);
if (!handled && this.copyTextActive)
handled = this.$refs.copyTextPage.keyHook(event);
if (!result && this.searchActive)
result = this.$refs.searchPage.keyHook(event);
if (!handled && this.$refs.page && this.$refs.page.keyHook)
handled = this.$refs.page.keyHook(event);
if (!result && this.copyTextActive)
result = this.$refs.copyTextPage.keyHook(event);
if (!handled && event.type == 'keydown') {
if (event.code == 'Escape')
this.loaderToggle();
if (!result && this.$refs.page && this.$refs.page.keyHook)
result = this.$refs.page.keyHook(event);
if (this.activePage == 'TextPage') {
switch (event.code) {
case 'KeyH':
case 'F1':
this.helpToggle();
event.preventDefault();
event.stopPropagation();
break;
case 'KeyZ':
this.scrollingToggle();
break;
case 'KeyP':
this.setPositionToggle();
break;
case 'KeyF':
if (event.ctrlKey) {
this.searchToggle();
event.preventDefault();
event.stopPropagation();
}
break;
case 'KeyC':
if (event.ctrlKey) {
this.copyTextToggle();
event.preventDefault();
event.stopPropagation();
}
break;
case 'KeyR':
this.refreshBook();
break;
case 'KeyX':
this.recentBooksToggle();
event.preventDefault();
event.stopPropagation();
break;
case 'KeyO':
this.offlineModeToggle();
break;
case 'KeyS':
this.settingsToggle();
break;
}
if (!result && event.type == 'keydown') {
const action = this.$root.readerActionByKeyEvent(event);
if (action == 'loader') {
result = this.doAction({action, event});
}
if (!result && this.activePage == 'TextPage') {
result = this.doAction({action, event});
}
}
}
return result;
}
}
//-----------------------------------------------------------------------------
</script>
<style scoped>
.el-container {
padding: 0;
margin: 0;
height: 100%;
}
.el-header {
.header {
padding-left: 5px;
padding-right: 5px;
background-color: #1B695F;
color: #000;
overflow-x: auto;
overflow-y: hidden;
overflow: hidden;
height: 50px;
}
.header {
display: flex;
justify-content: space-between;
}
.el-main {
position: relative;
display: flex;
padding: 0;
margin: 0;
.main {
background-color: #EBE2C9;
color: #000;
}
.tool-button {
margin: 0 2px 0 2px;
margin: 0px 2px 0 2px;
padding: 0;
color: #3E843E;
background-color: #E6EDF4;
@@ -1104,15 +1288,14 @@ class Reader extends Vue {
height: 38px;
width: 38px;
border: 0;
border-radius: 6px;
box-shadow: 3px 3px 5px black;
}
.tool-button + .tool-button {
margin: 0 2px 0 2px;
outline: 0;
}
.tool-button:hover {
background-color: white;
cursor: pointer;
}
.tool-button-active {
@@ -1127,20 +1310,19 @@ class Reader extends Vue {
.tool-button-active:hover {
color: white;
background-color: #81C581;
cursor: pointer;
}
.tool-button-disabled {
color: lightgray;
background-color: gray;
cursor: default;
}
.tool-button-disabled:hover {
color: lightgray;
background-color: gray;
}
i {
font-size: 200%;
cursor: default;
}
.space {
@@ -1157,4 +1339,10 @@ i {
text-decoration: underline;
cursor: pointer;
}
.copy-icon {
cursor: pointer;
font-size: 120%;
color: blue;
}
</style>

View File

@@ -1,97 +1,84 @@
<template>
<Window width="600px" ref="window" @close="close">
<template slot="header">
<span v-show="!loading">Последние {{tableData ? tableData.length : 0}} открытых книг</span>
<span v-show="loading"><i class="el-icon-loading" style="font-size: 25px"></i> <span style="position: relative; top: -4px">Список загружается</span></span>
<span v-show="!loading">{{ header }}</span>
<span v-if="loading"><q-spinner class="q-mr-sm" color="lime-12" size="20px" :thickness="7"/>Список загружается</span>
</template>
<a ref="download" style='display: none;'></a>
<el-table
<a ref="download" style='display: none;' target="_blank"></a>
<q-table
class="recent-books-table col"
:data="tableData"
style="width: 570px"
size="mini"
height="1px"
stripe
border
:default-sort = "{prop: 'touchDateTime', order: 'descending'}"
:header-cell-style = "headerCellStyle"
:row-key = "rowKey"
>
:columns="columns"
row-key="key"
:pagination.sync="pagination"
separator="cell"
hide-bottom
virtual-scroll
dense
>
<template v-slot:header="props">
<q-tr :props="props">
<q-th class="td-mp" style="width: 25px" key="num" :props="props"><span v-html="props.cols[0].label"></span></q-th>
<q-th class="td-mp break-word" style="width: 77px" key="date" :props="props"><span v-html="props.cols[1].label"></span></q-th>
<q-th class="td-mp" style="width: 332px" key="desc" :props="props" colspan="4">
<q-input ref="input" outlined dense rounded style="position: absolute; top: 6px; left: 90px; width: 380px" bg-color="white"
placeholder="Найти"
v-model="search"
@click.stop
/>
<el-table-column
type="index"
width="35px"
>
</el-table-column>
<el-table-column
prop="touchDateTime"
min-width="85px"
sortable
>
<template slot="header" slot-scope="scope"><!-- eslint-disable-line vue/no-unused-vars -->
<span style="font-size: 90%">Время<br>просм.</span>
</template>
<template slot-scope="scope"><!-- eslint-disable-line vue/no-unused-vars -->
<div class="desc" @click="loadBook(scope.row.url)">
{{ scope.row.touchDate }}<br>
{{ scope.row.touchTime }}
</div>
</template>
</el-table-column>
<span v-html="props.cols[2].label"></span>
</q-th>
</q-tr>
</template>
<el-table-column
>
<template slot="header" slot-scope="scope"><!-- eslint-disable-line vue/no-unused-vars -->
<!--el-input ref="input"
:value="search" @input="search = $event"
size="mini"
style="margin: 0; padding: 0; vertical-align: bottom; margin-top: 10px"
placeholder="Найти"/-->
<div class="el-input el-input--mini">
<input class="el-input__inner"
ref="input"
placeholder="Найти"
style="margin: 0; vertical-align: bottom; margin-top: 20px; padding: 0 10px 0 10px"
:value="search" @input="search = $event.target.value"
/>
<template v-slot:body="props">
<q-tr :props="props">
<q-td key="num" :props="props" class="td-mp" auto-width>
<div class="break-word" style="width: 25px">
{{ props.row.num }}
</div>
</template>
</q-td>
<el-table-column
min-width="280px"
>
<template slot-scope="scope">
<div class="desc" @click="loadBook(scope.row.url)">
<span style="color: green">{{ scope.row.desc.author }}</span><br>
<span>{{ scope.row.desc.title }}</span>
<q-td key="date" :props="props" class="td-mp clickable" @click="loadBook(props.row.url)" auto-width>
<div class="break-word" style="width: 68px">
{{ props.row.touchDate }}<br>
{{ props.row.touchTime }}
</div>
</template>
</el-table-column>
</q-td>
<el-table-column
min-width="90px"
>
<template slot-scope="scope">
<a v-show="isUrl(scope.row.url)" :href="scope.row.url" target="_blank">Оригинал</a><br>
<a :href="scope.row.path" @click.prevent="downloadBook(scope.row.path)">Скачать FB2</a>
</template>
</el-table-column>
<q-td key="desc" :props="props" class="td-mp clickable" @click="loadBook(props.row.url)" auto-width>
<div class="break-word" style="width: 332px; font-size: 90%">
<div style="color: green">{{ props.row.desc.author }}</div>
<div>{{ props.row.desc.title }}</div>
</div>
</q-td>
<el-table-column
width="60px"
>
<template slot-scope="scope">
<el-button
size="mini"
style="width: 30px; padding: 7px 0 7px 0; margin-left: 4px"
@click="handleDel(scope.row.key)"><i class="el-icon-close"></i>
</el-button>
</template>
</el-table-column>
<q-td key="links" :props="props" class="td-mp" auto-width>
<div class="break-word" style="width: 75px; font-size: 90%">
<a v-show="isUrl(props.row.url)" :href="props.row.url" target="_blank">Оригинал</a><br>
<a :href="props.row.path" @click.prevent="downloadBook(props.row.path)">Скачать FB2</a>
</div>
</q-td>
</el-table-column>
<q-td key="close" :props="props" class="td-mp" auto-width>
<div style="width: 38px">
<q-btn
dense
style="width: 30px; height: 30px; padding: 7px 0 7px 0; margin-left: 4px"
@click="handleDel(props.row.key)">
<q-icon class="la la-times" size="14px" style="top: -6px"/>
</q-btn>
</div>
</q-td>
<q-td key="last" :props="props" class="no-mp">
</q-td>
</q-tr>
</template>
</q-table>
</el-table>
</Window>
</template>
@@ -121,52 +108,90 @@ class RecentBooksPage extends Vue {
loading = false;
search = null;
tableData = [];
columns = [];
pagination = {};
created() {
this.pagination = {rowsPerPage: 0};
this.columns = [
{
name: 'num',
label: '#',
align: 'center',
sortable: true,
field: 'num',
},
{
name: 'date',
label: 'Время<br>просм.',
align: 'left',
field: 'touchDateTime',
sortable: true,
sort: (a, b, rowA, rowB) => rowA.touchDateTime - rowB.touchDateTime,
},
{
name: 'desc',
label: 'Название',
align: 'left',
field: 'descString',
sortable: true,
},
{
name: 'links',
label: '',
align: 'left',
},
{
name: 'close',
label: '',
align: 'left',
},
{
name: 'last',
label: '',
align: 'left',
},
];
}
init() {
this.$refs.window.init();
this.$nextTick(() => {
//this.$refs.input.focus();
//this.$refs.input.focus();//плохо на планшетах
});
(async() => {//отбражение подгрузки списка, иначе тормозит
(async() => {//подгрузка списка
if (this.initing)
return;
this.initing = true;
await this.updateTableData(3);
await utils.sleep(200);
if (bookManager.loaded) {
const t = Date.now();
if (!bookManager.loaded) {
await this.updateTableData(10);
if (bookManager.getSortedRecent().length > 10)
await utils.sleep(10*(Date.now() - t));
} else {
//для отзывчивости
await utils.sleep(100);
let i = 0;
let j = 5;
while (i < 500 && !bookManager.loaded) {
if (i % j == 0) {
bookManager.sortedRecentCached = null;
await this.updateTableData(100);
await this.updateTableData(20);
j *= 2;
}
await utils.sleep(100);
i++;
}
} else {
//для отзывчивости
await utils.sleep(100);
}
await this.updateTableData();
this.initing = false;
})();
}
rowKey(row) {
return row.key;
}
async updateTableData(limit) {
while (this.updating) await utils.sleep(100);
this.updating = true;
@@ -175,11 +200,13 @@ class RecentBooksPage extends Vue {
this.loading = !!limit;
const sorted = bookManager.getSortedRecent();
let num = 0;
for (let i = 0; i < sorted.length; i++) {
const book = sorted[i];
if (book.deleted)
continue;
num++;
if (limit && result.length >= limit)
break;
@@ -221,19 +248,19 @@ class RecentBooksPage extends Vue {
author = (author ? author : (fb2.bookTitle ? fb2.bookTitle : book.url));
result.push({
num,
touchDateTime: book.touchTime,
touchDate: t[0],
touchTime: t[1],
desc: {
title: `${title}${perc}${textLen}`,
author,
title: `${title}${perc}${textLen}`,
},
descString: `${author}${title}${perc}${textLen}`,
url: book.url,
path: book.path,
key: book.key,
});
if (result.length >= 100)
break;
}
const search = this.search;
@@ -245,44 +272,39 @@ class RecentBooksPage extends Vue {
item.desc.author.toLowerCase().includes(search.toLowerCase())
});
/*for (let i = 0; i < result.length; i++) {
if (!_.isEqual(this.tableData[i], result[i])) {
this.$set(this.tableData, i, result[i]);
await utils.sleep(10);
}
}
if (this.tableData.length > result.length)
this.tableData.splice(result.length);*/
this.tableData = result;
this.updating = false;
}
headerCellStyle(cell) {
let result = {margin: 0, padding: 0};
if (cell.columnIndex > 0) {
result['border-bottom'] = 0;
wordEnding(num) {
const endings = ['', 'а', 'и', 'и', 'и', '', '', '', '', ''];
const deci = num % 100;
if (deci > 10 && deci < 20) {
return '';
} else {
return endings[num % 10];
}
if (cell.rowIndex > 0) {
result.height = '0px';
result['border-right'] = 0;
}
return result;
}
get header() {
const len = (this.tableData ? this.tableData.length : 0);
return `${(this.search ? 'Найдено' : 'Всего')} ${len} книг${this.wordEnding(len)}`;
}
async downloadBook(fb2path) {
try {
await readerApi.checkUrl(fb2path);
await readerApi.checkCachedBook(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'});
this.$root.stdDialog.alert(errMes, 'Ошибка', {color: 'negative'});
}
}
@@ -296,7 +318,7 @@ class RecentBooksPage extends Vue {
async handleDel(key) {
await bookManager.delRecentBook({key});
this.updateTableData();
//this.updateTableData();//обновление уже происходит Reader.bookManagerEvent
if (!bookManager.mostRecentBook())
this.close();
@@ -315,11 +337,11 @@ class RecentBooksPage extends Vue {
}
close() {
this.$emit('recent-books-toggle');
this.$emit('recent-books-close');
}
keyHook(event) {
if (event.type == 'keydown' && event.code == 'Escape') {
if (!this.$root.stdDialog.active && event.type == 'keydown' && event.key == 'Escape') {
this.close();
}
return true;
@@ -329,7 +351,51 @@ class RecentBooksPage extends Vue {
</script>
<style scoped>
.desc {
.recent-books-table {
width: 600px;
overflow-y: auto;
overflow-x: hidden;
}
.clickable {
cursor: pointer;
}
</style>
.td-mp {
margin: 0 !important;
padding: 4px 4px 4px 4px !important;
border-bottom: 1px solid #ddd;
}
.no-mp {
margin: 0 !important;
padding: 0 !important;
border: 0;
border-left: 1px solid #ddd !important;
}
.break-word {
line-height: 180%;
overflow-wrap: break-word;
word-wrap: break-word;
white-space: normal;
}
</style>
<style>
.recent-books-table .q-table__middle {
height: 100%;
overflow-x: hidden;
}
.recent-books-table thead tr:first-child th {
position: sticky;
z-index: 1;
top: 0;
background-color: #c1f4cd;
}
.recent-books-table tr:nth-child(even) {
background-color: #f8f8f8;
}
</style>

View File

@@ -8,15 +8,19 @@
<span v-show="initStep">{{ initPercentage }}%</span>
<div v-show="!initStep" class="input">
<input ref="input" class="el-input__inner"
<!--input ref="input"
placeholder="что ищем"
:value="needle" @input="needle = $event.target.value"/>
:value="needle" @input="needle = $event.target.value"/-->
<q-input ref="input" class="col" outlined dense
placeholder="что ищем"
v-model="needle" @keydown="inputKeyDown"
/>
<div style="position: absolute; right: 10px; margin-top: 10px; font-size: 16px;">{{ foundText }}</div>
</div>
<el-button-group v-show="!initStep" class="button-group">
<el-button @click="showNext"><i class="el-icon-arrow-down"></i></el-button>
<el-button @click="showPrev"><i class="el-icon-arrow-up"></i></el-button>
</el-button-group>
<q-btn-group v-show="!initStep" class="button-group row no-wrap">
<q-btn class="button" dense stretch @click="showNext"><q-icon style="top: -6px" name="la la-angle-down" dense size="22px"/></q-btn>
<q-btn class="button" dense stretch @click="showPrev"><q-icon style="top: -4px" class="icon" name="la la-angle-up" dense size="22px"/></q-btn>
</q-btn-group>
</div>
</Window>
</template>
@@ -39,7 +43,10 @@ export default @Component({
},
foundText: function(newValue) {
this.$refs.input.style.paddingRight = (10 + newValue.length*12) + 'px';
//недостатки сторонних ui
const el = this.$refs.input.$el.querySelector('label div div div input');
if (el)
el.style.paddingRight = newValue.length*12 + 'px';
},
},
})
@@ -157,16 +164,17 @@ class SearchPage extends Vue {
close() {
this.stopInit = true;
this.$emit('search-toggle');
this.$emit('do-action', {action: 'search'});
}
inputKeyDown(event) {
if (event.key == 'Enter') {
this.showNext();
}
}
keyHook(event) {
//недостатки сторонних ui
if (document.activeElement === this.$refs.input && event.type == 'keydown' && event.key == 'Enter') {
this.showNext();
}
if (event.type == 'keydown' && (event.code == 'Escape')) {
if (event.type == 'keydown' && event.key == 'Escape') {
this.close();
}
return true;
@@ -194,17 +202,14 @@ class SearchPage extends Vue {
}
.button-group {
width: 150px;
width: 100px;
margin: 0;
padding: 0;
height: 37px;
}
.el-button {
.button {
padding: 9px 17px 9px 17px;
width: 55px;
}
i {
font-size: 20px;
width: 50px;
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<div></div>
<div class="hidden"></div>
</template>
<script>
@@ -35,6 +35,9 @@ export default @Component({
currentProfile: function() {
this.currentProfileChanged(true);
},
libs: function() {
this.debouncedSaveLibs();
},
},
})
class ServerStorage extends Vue {
@@ -49,27 +52,32 @@ class ServerStorage extends Vue {
this.saveSettings();
}, 500);
this.debouncedSaveLibs = _.debounce(() => {
this.saveLibs();
}, 500);
this.debouncedNotifySuccess = _.debounce(() => {
this.success('Данные синхронизированы с сервером');
}, 1000);
this.oldProfiles = {};
this.oldSettings = {};
this.oldLibs = {};
}
async init() {
try {
this.cachedRecent = await ssCacheStore.getItem('recent');
if (!this.cachedRecent)
await this.setCachedRecent({rev: 0, data: {}});
await this.cleanCachedRecent('cachedRecent');
this.cachedRecentPatch = await ssCacheStore.getItem('recent-patch');
if (!this.cachedRecentPatch)
await this.setCachedRecentPatch({rev: 0, data: {}});
await this.cleanCachedRecent('cachedRecentPatch');
this.cachedRecentMod = await ssCacheStore.getItem('recent-mod');
if (!this.cachedRecentMod)
await this.setCachedRecentMod({rev: 0, data: {}});
await this.cleanCachedRecent('cachedRecentMod');
if (!this.serverStorageKey) {
//генерируем новый ключ
@@ -97,6 +105,15 @@ class ServerStorage extends Vue {
this.cachedRecentMod = value;
}
async cleanCachedRecent(whatToClean) {
if (whatToClean == 'cachedRecent' || whatToClean == 'all')
await this.setCachedRecent({rev: 0, data: {}});
if (whatToClean == 'cachedRecentPatch' || whatToClean == 'all')
await this.setCachedRecentPatch({rev: 0, data: {}});
if (whatToClean == 'cachedRecentMod' || whatToClean == 'all')
await this.setCachedRecentMod({rev: 0, data: {}});
}
async generateNewServerStorageKey() {
const key = utils.toBase58(utils.randomArray(32));
this.commit('reader/setServerStorageKey', key);
@@ -124,9 +141,14 @@ class ServerStorage extends Vue {
await this.loadProfiles(force);
this.checkCurrentProfile();
await this.currentProfileChanged(force);
await this.loadLibs(force);
if (force)
await this.cleanCachedRecent('all');
const loadSuccess = await this.loadRecent();
if (loadSuccess && force)
if (loadSuccess && force) {
await this.saveRecent();
}
}
}
@@ -169,6 +191,14 @@ class ServerStorage extends Vue {
return this.settings.showServerStorageMessages;
}
get libs() {
return this.$store.state.reader.libs;
}
get libsRev() {
return this.$store.state.reader.libsRev;
}
checkCurrentProfile() {
if (!this.profiles[this.currentProfile]) {
this.commit('reader/setCurrentProfile', '');
@@ -177,17 +207,17 @@ class ServerStorage extends Vue {
success(message) {
if (this.showServerStorageMessages)
this.$notify.success({message});
this.$root.notify.success(message);
}
warning(message) {
if (this.showServerStorageMessages && !this.offlineModeActive)
this.$notify.warning({message});
this.$root.notify.warning(message);
}
error(message) {
if (this.showServerStorageMessages && !this.offlineModeActive)
this.$notify.error({message});
this.$root.notify.error(message);
}
async loadSettings(force = false, doNotifySuccess = true) {
@@ -338,13 +368,85 @@ class ServerStorage extends Vue {
this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
} else if (result.state == 'success') {
this.oldProfiles = _.cloneDeep(this.profiles);
this.commit('reader/setProfilesRev', this.profilesRev + 1);
this.commit('reader/setProfilesRev', this.profilesRev + 1);
}
} finally {
this.savingProfiles = false;
}
}
async loadLibs(force = false, doNotifySuccess = true) {
if (!this.keyInited || !this.serverSyncEnabled)
return;
const oldRev = this.libsRev;
//проверим ревизию на сервере
if (!force) {
try {
const revs = await this.storageCheck({libs: {}});
if (revs.state == 'success' && revs.items.libs.rev == oldRev) {
return;
}
} catch(e) {
this.error(`Ошибка соединения с сервером: ${e.message}`);
return;
}
}
let libs = null;
try {
libs = await this.storageGet({libs: {}});
} catch(e) {
this.error(`Ошибка соединения с сервером: ${e.message}`);
return;
}
if (libs.state == 'success') {
libs = libs.items.libs;
if (libs.rev == 0)
libs.data = {};
this.oldLibs = _.cloneDeep(libs.data);
this.commit('reader/setLibs', libs.data);
this.commit('reader/setLibsRev', libs.rev);
if (doNotifySuccess)
this.debouncedNotifySuccess();
} else {
this.warning(`Неверный ответ сервера: ${libs.state}`);
}
}
async saveLibs() {
if (!this.keyInited || !this.serverSyncEnabled || this.savingLibs)
return;
const diff = utils.getObjDiff(this.oldLibs, this.libs);
if (utils.isEmptyObjDiff(diff))
return;
this.savingLibs = true;
try {
let result = {state: ''};
try {
result = await this.storageSet({libs: {rev: this.libsRev + 1, data: this.libs}});
} catch(e) {
this.error(`Ошибка соединения с сервером (${e.message}). Данные не сохранены и могут быть перезаписаны.`);
}
if (result.state == 'reject') {
await this.loadLibs(true, false);
this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
} else if (result.state == 'success') {
this.oldLibs = _.cloneDeep(this.libs);
this.commit('reader/setLibsRev', this.libsRev + 1);
}
} finally {
this.savingLibs = false;
}
}
async loadRecent(skipRevCheck = false, doNotifySuccess = true) {
if (!this.keyInited || !this.serverSyncEnabled || this.loadingRecent)
return;

View File

@@ -4,8 +4,15 @@
Установить позицию
</template>
<div class="slider">
<el-slider v-model="sliderValue" :max="sliderMax" :format-tooltip="formatTooltip"></el-slider>
<div id="set-position-slider" class="slider q-px-md">
<q-slider
thumb-path="M 2, 10 a 8.5,8.5 0 1,0 17,0 a 8.5,8.5 0 1,0 -17,0"
v-model="sliderValue"
:max="sliderMax"
label
:label-value="(sliderMax ? (sliderValue/this.sliderMax*100).toFixed(2) + '%' : 0)"
color="primary"
/>
</div>
</Window>
</template>
@@ -46,20 +53,16 @@ class SetPositionPage extends Vue {
this.initialized = true;
}
formatTooltip(val) {
if (this.sliderMax)
return (val/this.sliderMax*100).toFixed(2) + '%';
else
return 0;
}
close() {
this.$emit('set-position-toggle');
}
keyHook(event) {
if (event.type == 'keydown' && (event.code == 'Escape' || event.code == 'KeyP')) {
this.close();
if (event.type == 'keydown') {
const action = this.$root.readerActionByKeyEvent(event);
if (event.key == 'Escape' || action == 'setPosition') {
this.close();
}
}
return true;
}
@@ -73,9 +76,13 @@ class SetPositionPage extends Vue {
background-color: #efefef;
border-radius: 15px;
}
</style>
.el-slider {
margin-right: 20px;
margin-left: 20px;
<style>
#set-position-slider .q-slider__thumb path {
fill: white !important;
stroke: blue !important;
stroke-width: 2 !important;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,248 @@
<template>
<div class="table col column no-wrap">
<!-- header -->
<div class="table-row row">
<div class="desc q-pa-sm bg-blue-2">Команда</div>
<div class="hotKeys col q-pa-sm bg-blue-2 row no-wrap">
<div style="width: 80px">Сочетание клавиш</div>
<q-input ref="input" class="q-ml-sm col"
outlined dense rounded
bg-color="grey-4"
placeholder="Найти"
v-model="search"
@click.stop
/>
<div v-show="!readonly" class="q-ml-sm column justify-center">
<q-btn class="bg-grey-4 text-grey-6" style="height: 35px; width: 35px" rounded flat icon="la la-broom" @click="defaultHotKeyAll">
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Установить все сочетания по умолчанию
</q-tooltip>
</q-btn>
</div>
</div>
</div>
<!-- body -->
<div class="table-row row" v-for="(action, index) in tableData" :key="index">
<div class="desc q-pa-sm">{{ rstore.readerActions[action] }}</div>
<div class="hotKeys col q-pa-sm">
<q-chip
:color="collisions[code] ? 'red' : 'grey-7'"
:removable="!readonly" :clickable="collisions[code] ? true : false"
text-color="white" v-for="(code, index) in value[action]" :key="index" @remove="removeCode(action, code)"
@click="collisionWarning(code)"
>
{{ code }}
</q-chip>
</div>
<div v-show="!readonly" class="column q-pa-xs">
<q-icon
name="la la-plus-circle"
class="button bg-green-8 text-white"
@click="addHotKey(action)"
v-ripple
:disabled="value[action].length >= maxCodesLength"
>
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Добавить сочетание клавиш
</q-tooltip>
</q-icon>
<q-icon
name="la la-broom"
class="button text-grey-5"
@click="defaultHotKey(action)"
v-ripple
>
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
По умолчанию
</q-tooltip>
</q-icon>
</div>
</div>
</div>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import rstore from '../../../../store/modules/reader';
//import * as utils from '../../share/utils';
const UserHotKeysProps = Vue.extend({
props: {
value: Object,
readonly: Boolean,
}
});
export default @Component({
watch: {
search: function() {
this.updateTableData();
},
value: function() {
this.checkCollisions();
this.updateTableData();
}
},
})
class UserHotKeys extends UserHotKeysProps {
search = '';
rstore = {};
tableData = [];
collisions = {};
maxCodesLength = 10;
created() {
this.rstore = rstore;
}
mounted() {
this.checkCollisions();
this.updateTableData();
}
updateTableData() {
let result = rstore.hotKeys.map(hk => hk.name);
const search = this.search.toLowerCase();
const codesIncludeSearch = (action) => {
for (const code of this.value[action]) {
if (code.toLowerCase().includes(search))
return true;
}
return false;
};
result = result.filter(item => {
return !search ||
rstore.readerActions[item].toLowerCase().includes(search) ||
codesIncludeSearch(item)
});
this.tableData = result;
}
checkCollisions() {
const cols = {};
for (const [action, codes] of Object.entries(this.value)) {
codes.forEach(code => {
if (!cols[code])
cols[code] = [];
if (cols[code].indexOf(action) < 0)
cols[code].push(action);
});
}
const result = {};
for (const [code, actions] of Object.entries(cols)) {
if (actions.length > 1)
result[code] = actions;
}
this.collisions = result;
}
collisionWarning(code) {
if (this.collisions[code]) {
const descs = this.collisions[code].map(action => `<b>${rstore.readerActions[action]}</b>`);
this.$root.stdDialog.alert(`Сочетание '${code}' одновременно назначено<br>следующим командам:<br>${descs.join('<br>')}<br><br>
Возможно неожиданное поведение.`, 'Предупреждение');
}
}
removeCode(action, code) {
let codes = Array.from(this.value[action]);
const index = codes.indexOf(code);
if (index >= 0) {
codes.splice(index, 1);
const newValue = Object.assign({}, this.value, {[action]: codes});
this.$emit('input', newValue);
}
}
async addHotKey(action) {
if (this.value[action].length >= this.maxCodesLength)
return;
try {
const result = await this.$root.stdDialog.getHotKey(`Добавить сочетание для:<br><b>${rstore.readerActions[action]}</b>`, '');
if (result) {
let codes = Array.from(this.value[action]);
if (codes.indexOf(result) < 0) {
codes.push(result);
const newValue = Object.assign({}, this.value, {[action]: codes});
this.$emit('input', newValue);
this.$nextTick(() => {
this.collisionWarning(result);
});
}
}
} catch (e) {
//
}
}
async defaultHotKey(action) {
try {
if (await this.$root.stdDialog.confirm(`Подтвердите сброс сочетаний клавиш<br>в значения по умолчанию для команды:<br><b>${rstore.readerActions[action]}</b>`, ' ')) {
const codes = Array.from(rstore.settingDefaults.userHotKeys[action]);
const newValue = Object.assign({}, this.value, {[action]: codes});
this.$emit('input', newValue);
}
} catch (e) {
//
}
}
async defaultHotKeyAll() {
try {
if (await this.$root.stdDialog.confirm('Подтвердите сброс сочетаний клавиш<br>для ВСЕХ команд в значения по умолчанию:', ' ')) {
const newValue = Object.assign({}, rstore.settingDefaults.userHotKeys);
this.$emit('input', newValue);
}
} catch (e) {
//
}
}
}
//-----------------------------------------------------------------------------
</script>
<style scoped>
.table {
border-left: 1px solid grey;
border-top: 1px solid grey;
}
.table-row {
border-right: 1px solid grey;
border-bottom: 1px solid grey;
}
.table-row:nth-child(even) {
background-color: #f7f7f7;
}
.table-row:hover {
background-color: #f0f0f0;
}
.desc {
width: 130px;
overflow-wrap: break-word;
word-wrap: break-word;
white-space: normal;
}
.hotKeys {
border-left: 1px solid grey;
}
.button {
font-size: 25px;
border-radius: 25px;
cursor: pointer;
}
</style>

View File

@@ -0,0 +1,17 @@
const defPalette = [
'rgb(255,204,204)', 'rgb(255,230,204)', 'rgb(255,255,204)', 'rgb(204,255,204)', 'rgb(204,255,230)',
'rgb(204,255,255)', 'rgb(204,230,255)', 'rgb(204,204,255)', 'rgb(230,204,255)', 'rgb(255,204,255)',
'rgb(255,153,153)', 'rgb(255,204,153)', 'rgb(255,255,153)', 'rgb(153,255,153)', 'rgb(153,255,204)',
'rgb(153,255,255)', 'rgb(153,204,255)', 'rgb(153,153,255)', 'rgb(204,153,255)', 'rgb(255,153,255)',
'rgb(255,102,102)', 'rgb(255,179,102)', 'rgb(255,255,102)', 'rgb(102,255,102)', 'rgb(102,255,179)',
'rgb(102,255,255)', 'rgb(102,179,255)', 'rgb(102,102,255)', 'rgb(179,102,255)', 'rgb(255,102,255)',
'rgb(255,51,51)', 'rgb(255,153,51)', 'rgb(255,255,51)', 'rgb(51,255,51)', 'rgb(51,255,153)', 'rgb(51,255,255)', 'rgb(51,153,255)', 'rgb(51,51,255)', 'rgb(153,51,255)', 'rgb(255,51,255)',
'rgb(255,0,0)', 'rgb(255,128,0)', 'rgb(255,255,0)', 'rgb(0,255,0)', 'rgb(0,255,128)', 'rgb(0,255,255)', 'rgb(0,128,255)', 'rgb(0,0,255)', 'rgb(128,0,255)', 'rgb(255,0,255)',
'rgb(245,0,0)', 'rgb(245,123,0)', 'rgb(245,245,0)', 'rgb(0,245,0)', 'rgb(0,245,123)', 'rgb(0,245,245)', 'rgb(0,123,245)', 'rgb(0,0,245)', 'rgb(123,0,245)', 'rgb(245,0,245)',
'rgb(214,0,0)', 'rgb(214,108,0)', 'rgb(214,214,0)', 'rgb(0,214,0)', 'rgb(0,214,108)', 'rgb(0,214,214)', 'rgb(0,108,214)', 'rgb(0,0,214)', 'rgb(108,0,214)', 'rgb(214,0,214)',
'rgb(163,0,0)', 'rgb(163,82,0)', 'rgb(163,163,0)', 'rgb(0,163,0)', 'rgb(0,163,82)', 'rgb(0,163,163)', 'rgb(0,82,163)', 'rgb(0,0,163)', 'rgb(82,0,163)', 'rgb(163,0,163)',
'rgb(92,0,0)', 'rgb(92,46,0)', 'rgb(92,92,0)', 'rgb(0,92,0)', 'rgb(0,92,46)', 'rgb(0,92,92)', 'rgb(0,46,92)', 'rgb(0,0,92)', 'rgb(46,0,92)', 'rgb(92,0,92)',
'rgb(255,255,255)', 'rgb(205,205,205)', 'rgb(178,178,178)', 'rgb(153,153,153)', 'rgb(127,127,127)', 'rgb(102,102,102)', 'rgb(76,76,76)', 'rgb(51,51,51)', 'rgb(25,25,25)', 'rgb(0,0,0)'
];
export default defPalette;

View File

@@ -0,0 +1,10 @@
<div class="part-header">Показывать кнопки панели</div>
<div class="item row" v-for="item in toolButtons" :key="item.name" v-show="item.name != 'libs' || mode == 'liberama.top'">
<div class="label-3"></div>
<div class="col row">
<q-checkbox size="xs" @input="changeShowToolButton(item.name)"
:value="showToolButton[item.name]" :label="rstore.readerActions[item.name]"
/>
</div>
</div>

View File

@@ -0,0 +1,33 @@
<div class="bg-grey-3 row">
<q-tabs
v-model="selectedKeysTab"
active-color="black"
active-bg-color="white"
indicator-color="white"
dense
no-caps
class="no-mp bg-grey-4 text-grey-7"
>
<q-tab name="mouse" label="Мышь/тачскрин" />
<q-tab name="keyboard" label="Клавиатура" />
</q-tabs>
</div>
<div class="q-mb-sm"/>
<div class="col tab-panel">
<div v-if="selectedKeysTab == 'mouse'">
<div class="item row">
<div class="label-4"></div>
<div class="col row">
<q-checkbox size="xs" v-model="clickControl" label="Включить управление кликом" />
</div>
</div>
</div>
<div v-if="selectedKeysTab == 'keyboard'">
<div class="item row">
<UserHotKeys v-model="userHotKeys" />
</div>
</div>
</div>

View File

@@ -0,0 +1,107 @@
<!---------------------------------------------->
<div class="part-header">Подсказки, уведомления</div>
<div class="item row no-wrap">
<div class="label-6">Подсказка</div>
<q-checkbox size="xs" v-model="showClickMapPage" label="Показывать области управления кликом" :disable="!clickControl" >
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Показывать или нет подсказку при каждой загрузке книги
</q-tooltip>
</q-checkbox>
</div>
<div class="item row">
<div class="label-6">Подсказка</div>
<q-checkbox size="xs" v-model="blinkCachedLoad" label="Предупреждать о загрузке из кэша">
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Мерцать сообщением в строке статуса и на кнопке<br>
обновления при загрузке книги из кэша
</q-tooltip>
</q-checkbox>
</div>
<div class="item row no-wrap">
<div class="label-6">Уведомление</div>
<q-checkbox size="xs" v-model="showServerStorageMessages" label="Показывать сообщения синхронизации">
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Показывать уведомления и ошибки от<br>
синхронизатора данных с сервером
</q-tooltip>
</q-checkbox>
</div>
<div class="item row">
<div class="label-6">Уведомление</div>
<q-checkbox size="xs" v-model="showWhatsNewDialog">
Показывать уведомление "Что нового"
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Показывать уведомления "Что нового"<br>
при каждом выходе новой версии читалки
</q-tooltip>
</q-checkbox>
</div>
<div class="item row">
<div class="label-6">Уведомление</div>
<q-checkbox size="xs" v-model="showDonationDialog2020">
Показывать "Оплатим хостинг вместе"
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Показывать уведомление "Оплатим хостинг вместе"
</q-tooltip>
</q-checkbox>
</div>
<!---------------------------------------------->
<div class="part-header">Другое</div>
<div class="item row">
<div class="label-6">Обработка</div>
<div class="col row">
<q-checkbox v-model="enableSitesFilter" @input="needTextReload" size="xs" label="Включить html-фильтр для сайтов">
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Html-фильтр вырезает лишние элементы со<br>
страницы для определенных сайтов, таких как:<br>
samlib.ru<br>
www.fanfiction.net<br>
archiveofourown.org<br>
и других
</q-tooltip>
</q-checkbox>
</div>
</div>
<div class="item row">
<div class="label-6">Обработка</div>
<q-checkbox size="xs" v-model="lazyParseEnabled" label="Предварительная подготовка текста">
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Включение этой опции позволяет делать предварительную<br>
подготовку всего текста в ленивом режиме сразу после<br>
загрузки книги. Это может повысить отзывчивость читалки,<br>
но нагружает процессор каждый раз при открытии книги.
</q-tooltip>
</q-checkbox>
</div>
<div class="item row">
<div class="label-6">Парам. в URL</div>
<q-checkbox size="xs" v-model="allowUrlParamBookPos">
Добавлять параметр "__p"
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Добавление параметра "__p" в строке браузера<br>
позволяет передавать ссылку на книгу в читалке<br>
без потери текущей позиции. Однако в этом случае<br>
при листании забивается история браузера, т.к. на<br>
каждое изменение позиции происходит смена URL.
</q-tooltip>
</q-checkbox>
</div>
<div class="item row">
<div class="label-6">Копирование</div>
<q-checkbox size="xs" v-model="copyFullText" label="Загружать весь текст">
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Загружать весь текст в окно<br>
копирования текста со страницы
</q-tooltip>
</q-checkbox>
</div>

View File

@@ -0,0 +1,28 @@
<!---------------------------------------------->
<div class="part-header">Анимация</div>
<div class="item row">
<div class="label-5">Тип</div>
<q-select class="col-left" v-model="pageChangeAnimation" :options="pageChangeAnimationOptions"
dropdown-icon="la la-angle-down la-sm"
outlined dense emit-value map-options
/>
</div>
<div class="item row">
<div class="label-5">Скорость</div>
<NumInput class="col-left" v-model="pageChangeAnimationSpeed" :min="0" :max="100" :disable="pageChangeAnimation == ''"/>
</div>
<!---------------------------------------------->
<div class="part-header">Другое</div>
<div class="item row">
<div class="label-5">Страница</div>
<q-checkbox v-model="keepLastToFirst" size="xs" label="Переносить последнюю строку">
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Переносить последнюю строку страницы<br>
в начало следующей при листании
</q-tooltip>
</q-checkbox>
</div>

View File

@@ -0,0 +1,101 @@
<div class="part-header">Управление синхронизацией данных</div>
<div class="item row">
<div class="label-1"></div>
<q-checkbox class="col" v-model="serverSyncEnabled" size="xs" label="Включить синхронизацию с сервером" />
</div>
<div v-show="serverSyncEnabled">
<!---------------------------------------------->
<div class="part-header">Профили устройств</div>
<div class="item row">
<div class="label-1"></div>
<div class="text col">
Выберите или добавьте профиль устройства, чтобы начать синхронизацию настроек с сервером.
<br>При выборе "Нет" синхронизация настроек (но не книг) отключается.
</div>
</div>
<div class="item row">
<div class="label-1">Устройство</div>
<div class="col">
<q-select v-model="currentProfile" :options="currentProfileOptions"
style="width: 275px"
dropdown-icon="la la-angle-down la-sm"
outlined dense emit-value map-options
/>
</div>
</div>
<div class="item row">
<div class="label-1"></div>
<q-btn class="button" dense no-caps @click="addProfile">Добавить</q-btn>
<q-btn class="button" dense no-caps @click="delProfile">Удалить</q-btn>
<q-btn class="button" dense no-caps @click="delAllProfiles">Удалить все</q-btn>
</div>
<!---------------------------------------------->
<div class="part-header">Ключ доступа</div>
<div class="item row">
<div class="label-1"></div>
<div class="text col">
Ключ доступа позволяет восстановить профили с настройками и список читаемых книг.
Для этого необходимо передать ключ на новое устройство через почту, мессенджер или другим способом.
</div>
</div>
<div class="item row">
<div class="label-1"></div>
<q-btn class="button" style="width: 250px" dense no-caps @click="showServerStorageKey">
<span v-show="serverStorageKeyVisible">Скрыть</span>
<span v-show="!serverStorageKeyVisible">Показать</span>
&nbsp;ключ доступа
</q-btn>
</div>
<div class="item row">
<div class="label-1"></div>
<div v-if="!serverStorageKeyVisible" class="col">
<hr/>
<b>{{ partialStorageKey }}</b> (часть вашего ключа)
<hr/>
</div>
<div v-else class="col" style="line-height: 100%">
<hr/>
<div style="width: 300px; padding-top: 5px; overflow-wrap: break-word;">
<b>{{ serverStorageKey }}</b>
<q-icon class="copy-icon" name="la la-copy" @click="copyToClip(serverStorageKey, 'Ключ')">
<q-tooltip :delay="1000" anchor="top middle" self="center middle" content-style="font-size: 80%">Скопировать</q-tooltip>
</q-icon>
</div>
<div v-if="mode == 'omnireader' || mode == 'liberama.top'">
<br>Переход по ссылке позволит автоматически ввести ключ доступа:
<br><div class="text-center" style="margin-top: 5px">
<a :href="setStorageKeyLink" target="_blank">Ссылка для ввода ключа</a>
<q-icon class="copy-icon" name="la la-copy" @click="copyToClip(setStorageKeyLink, 'Ссылка')">
<q-tooltip :delay="1000" anchor="top middle" self="center middle" content-style="font-size: 80%">Скопировать</q-tooltip>
</q-icon>
</div>
</div>
<hr/>
</div>
</div>
<div class="item row">
<div class="label-1"></div>
<q-btn class="button" style="width: 250px" dense no-caps @click="enterServerStorageKey">Ввести ключ доступа</q-btn>
</div>
<div class="item row">
<div class="label-1"></div>
<q-btn class="button" style="width: 250px" dense no-caps @click="generateServerStorageKey">Сгенерировать новый ключ</q-btn>
</div>
<div class="item row">
<div class="label-1"></div>
<div class="text col">
Рекомендуется сохранить ключ в надежном месте, чтобы всегда иметь возможность восстановить настройки,
например, после переустановки ОС или чистки/смены браузера.<br>
<b>ПРЕДУПРЕЖДЕНИЕ!</b> При утере ключа, НИКТО не сможет восстановить ваши данные, т.к. они сжимаются
и шифруются ключом доступа перед отправкой на сервер.
</div>
</div>
</div>

View File

@@ -0,0 +1,3 @@
<div class="item row">
<q-btn class="col q-ma-sm" dense no-caps @click="setDefaults">Установить по умолчанию</q-btn>
</div>

View File

@@ -0,0 +1,34 @@
<q-tabs
v-model="selectedViewTab"
active-color="black"
active-bg-color="white"
indicator-color="white"
dense
no-caps
class="no-mp bg-grey-4 text-grey-7"
>
<q-tab name="color" label="Цвет" />
<q-tab name="font" label="Шрифт" />
<q-tab name="text" label="Текст" />
<q-tab name="status" label="Строка статуса" />
</q-tabs>
<div class="q-mb-sm"/>
<div class="col tab-panel">
<div v-if="selectedViewTab == 'color'">
@@include('./ViewTab/Color.inc');
</div>
<div v-if="selectedViewTab == 'font'">
@@include('./ViewTab/Font.inc');
</div>
<div v-if="selectedViewTab == 'text'">
@@include('./ViewTab/Text.inc');
</div>
<div v-if="selectedViewTab == 'status'">
@@include('./ViewTab/Status.inc');
</div>
</div>

View File

@@ -0,0 +1,58 @@
<!---------------------------------------------->
<div class="hidden part-header">Цвет</div>
<div class="item row">
<div class="label-2">Текст</div>
<div class="col row">
<q-input class="col-left no-mp"
outlined dense
v-model="textColorFiltered"
:rules="['hexColor']"
style="max-width: 150px"
>
<template v-slot:prepend>
<q-icon name="la la-angle-down la-xs" class="cursor-pointer text-white" :style="colorPanStyle('text')">
<q-popup-proxy anchor="bottom middle" self="top middle">
<div>
<q-color v-model="textColor"
no-header default-view="palette" :palette="predefineTextColors"
/>
</div>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
<span class="col" style="position: relative; top: 35px; left: 15px;">Обои:</span>
</div>
</div>
<div class="q-mt-md"/>
<div class="item row">
<div class="label-2">Фон</div>
<div class="col row">
<q-input class="col-left no-mp"
outlined dense
v-model="bgColorFiltered"
:rules="['hexColor']"
style="max-width: 150px"
:disable="wallpaper != ''"
>
<template v-slot:prepend>
<q-icon name="la la-angle-down la-xs" class="cursor-pointer text-white" :style="colorPanStyle('bg')">
<q-popup-proxy anchor="bottom middle" self="top middle">
<div>
<q-color v-model="backgroundColor" no-header default-view="palette" :palette="predefineBackgroundColors"/>
</div>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
<div class="q-px-sm"/>
<q-select class="col" v-model="wallpaper" :options="wallpaperOptions"
dropdown-icon="la la-angle-down la-sm"
outlined dense emit-value map-options
/>
</div>
</div>

View File

@@ -0,0 +1,56 @@
<!---------------------------------------------->
<div class="hidden part-header">Шрифт</div>
<div class="item row">
<div class="label-2">Локальный/веб</div>
<div class="col row">
<q-select class="col-left" v-model="fontName" :options="fontsOptions" :disable="webFontName != ''"
dropdown-icon="la la-angle-down la-sm"
outlined dense emit-value map-options
/>
<div class="q-px-sm"/>
<q-select class="col" v-model="webFontName" :options="webFontsOptions"
dropdown-icon="la la-angle-down la-sm"
outlined dense emit-value map-options
>
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Веб шрифты дают большое разнообразие,<br>
однако есть шанс, что шрифт будет загружаться<br>
очень медленно или вовсе не загрузится
</q-tooltip>
</q-select>
</div>
</div>
<div class="item row">
<div class="label-2">Размер</div>
<div class="col row">
<NumInput class="col-left" v-model="fontSize" :min="5" :max="200"/>
<div class="col q-pt-xs text-right">
<a href="https://fonts.google.com/?subset=cyrillic" target="_blank">Примеры</a>
</div>
</div>
</div>
<div class="item row">
<div class="label-2">Сдвиг</div>
<div class="col row">
<NumInput class="col-left" v-model="vertShift" :min="-100" :max="100">
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Сдвиг шрифта по вертикали в процентах от размера.<br>
Отрицательное значение сдвигает вверх, положительное -<br>
вниз. Значение зависит от метрики шрифта.
</q-tooltip>
</NumInput>
</div>
</div>
<div class="item row">
<div class="label-2">Стиль</div>
<div class="col row">
<q-checkbox v-model="fontBold" size="xs" label="Жирный" />
<q-checkbox class="q-ml-sm" v-model="fontItalic" size="xs" label="Курсив" />
</div>
</div>

View File

@@ -0,0 +1,36 @@
<!---------------------------------------------->
<div class="hidden part-header">Строка статуса</div>
<div class="item row">
<div class="label-2">Статус</div>
<div class="col row">
<q-checkbox v-model="showStatusBar" size="xs" label="Показывать" />
<q-checkbox class="q-ml-sm" v-model="statusBarTop" size="xs" :disable="!showStatusBar" label="Вверху/внизу" />
</div>
</div>
<div class="item row">
<div class="label-2">Высота</div>
<div class="col row">
<NumInput class="col-left" v-model="statusBarHeight" :min="5" :max="100" :disable="!showStatusBar"/>
</div>
</div>
<div class="item row">
<div class="label-2">Прозрачность</div>
<div class="col row">
<NumInput class="col-left" v-model="statusBarColorAlpha" :min="0" :max="1" :digits="2" :step="0.1" :disable="!showStatusBar"/>
</div>
</div>
<div class="item row">
<div class="label-2"></div>
<div class="col row">
<q-checkbox v-model="statusBarClickOpen" size="xs" label="Открывать оригинал по клику">
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
По клику на автора-название в строке статуса<br>
открывать оригинал произведения в новой вкладке
</q-tooltip>
</q-checkbox>
</div>
</div>

View File

@@ -0,0 +1,144 @@
<!---------------------------------------------->
<div class="hidden part-header">Текст</div>
<div class="item row">
<div class="label-2">Интервал</div>
<div class="col row">
<NumInput class="col-left" v-model="lineInterval" :min="0" :max="200"/>
</div>
</div>
<div class="item row">
<div class="label-2">Параграф</div>
<div class="col row">
<NumInput class="col-left" v-model="p" :min="0" :max="2000"/>
</div>
</div>
<div class="item row">
<div class="label-2">Отступ</div>
<div class="col row">
<NumInput class="col-left" v-model="indentLR" :min="0" :max="2000">
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Слева/справа
</q-tooltip>
</NumInput>
<div class="q-px-sm"/>
<NumInput class="col" v-model="indentTB" :min="0" :max="2000">
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Сверху/снизу
</q-tooltip>
</NumInput>
</div>
</div>
<div class="item row">
<div class="label-2">Сдвиг</div>
<div class="col row">
<NumInput class="col-left" v-model="textVertShift" :min="-100" :max="100">
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Сдвиг текста по вертикали в процентах от размера шрифта.<br>
Отрицательное значение сдвигает вверх, положительное -<br>
вниз.
</q-tooltip>
</NumInput>
</div>
</div>
<div class="item row">
<div class="label-2">Скроллинг</div>
<div class="col row">
<NumInput class="col-left" v-model="scrollingDelay" :min="1" :max="10000">
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Замедление скроллинга в миллисекундах.<br>
Определяет время, за которое текст<br>
прокручивается на одну строку.
</q-tooltip>
</NumInput>
<div class="q-px-sm"/>
<q-select class="col" v-model="scrollingType" :options="['linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out']"
dropdown-icon="la la-angle-down la-sm"
outlined dense emit-value map-options
>
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Вид скроллинга: линейный,<br>
ускорение-замедление и пр.
</q-tooltip>
</q-select>
</div>
</div>
<div class="item row">
<div class="label-2">Выравнивание</div>
<div class="col row">
<q-checkbox v-model="textAlignJustify" size="xs" label="По ширине" />
<q-checkbox class="q-ml-sm" v-model="wordWrap" size="xs" label="Перенос по слогам" />
</div>
</div>
<div class="item row">
<div class="label-2"></div>
<div class="col-left column justify-center text-right">
Компактность
</div>
<div class="q-px-sm"/>
<NumInput class="col" v-model="compactTextPerc" :min="0" :max="100">
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Степень компактности текста в процентах.<br>
Чем больше компактность, тем хуже выравнивание<br>
по правому краю.
</q-tooltip>
</NumInput>
</div>
<div class="item row">
<div class="label-2">Обработка</div>
<div class="col row">
<q-checkbox v-model="cutEmptyParagraphs" size="xs" label="Убирать пустые строки" />
</div>
</div>
<div class="item row">
<div class="label-2"></div>
<div class="col-left column justify-center text-right">
Добавлять пустые
</div>
<div class="q-px-sm"/>
<NumInput class="col" v-model="addEmptyParagraphs" :min="0" :max="2"/>
</div>
<div class="item row">
<div class="label-2">Изображения</div>
<div class="col row">
<q-checkbox v-model="showImages" size="xs" label="Показывать" />
<q-checkbox class="q-ml-sm" v-model="showInlineImagesInCenter" @input="needReload" :disable="!showImages" size="xs" label="Инлайн в центр">
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Выносить все изображения в центр экрана
</q-tooltip>
</q-checkbox>
</div>
</div>
<div class="item row">
<div class="label-2"></div>
<div class="col row">
<q-checkbox v-model="imageFitWidth" :disable="!showImages" size="xs" label="Ширина не более размера экрана" />
</div>
</div>
<div class="item row">
<div class="label-2"></div>
<div class="col-left column justify-center text-right">
Высота не более
</div>
<div class="q-px-sm"/>
<NumInput class="col" v-model="imageHeightLines" :min="1" :max="100" :disable="!showImages">
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Определяет высоту изображения количеством строк.<br>
В случае превышения высоты, изображение будет<br>
уменьшено с сохранением пропорций так, чтобы<br>
помещаться в указанное количество строк.
</q-tooltip>
</NumInput>
</div>

View File

@@ -21,13 +21,15 @@
@wheel.prevent.stop="onMouseWheel"
@touchstart.stop="onTouchStart" @touchend.stop="onTouchEnd" @touchmove.stop="onTouchMove" @touchcancel.prevent.stop="onTouchCancel"
oncontextmenu="return false;">
<div v-show="showStatusBar" v-html="statusBarClickable" @mousedown.prevent.stop @touchstart.stop
<div v-show="showStatusBar && statusBarClickOpen" v-html="statusBarClickable" @mousedown.prevent.stop @touchstart.stop
@click.prevent.stop="onStatusBarClick"></div>
</div>
<div v-show="!clickControl && showStatusBar" class="layout" v-html="statusBarClickable" @mousedown.prevent.stop @touchstart.stop
@click.prevent.stop="onStatusBarClick"></div>
<div v-show="!clickControl && showStatusBar && statusBarClickOpen" class="layout" v-html="statusBarClickable" @mousedown.prevent.stop @touchstart.stop
@click.prevent.stop="onStatusBarClick">
</div>
<!-- невидимым делать нельзя, вовремя не подгружаютя шрифты -->
<canvas ref="offscreenCanvas" class="layout" style="width: 0px; height: 0px"></canvas>
<canvas ref="offscreenCanvas" class="layout" style="visibility: hidden"></canvas>
<div ref="measureWidth" style="position: absolute; visibility: hidden"></div>
</div>
</template>
@@ -37,8 +39,8 @@ import Vue from 'vue';
import Component from 'vue-class-component';
import {loadCSS} from 'fg-loadcss';
import _ from 'lodash';
import {sleep} from '../../../share/utils';
import {sleep} from '../../../share/utils';
import bookManager from '../share/bookManager';
import DrawHelper from './DrawHelper';
import rstore from '../../../store/modules/reader';
@@ -130,7 +132,11 @@ class TextPage extends Vue {
await this.doPageAnimation();
}, 10);
this.$root.$on('resize', () => {this.$nextTick(this.onResize)});
this.$root.$on('resize', async() => {
this.$nextTick(this.onResize);
await sleep(500);
this.$nextTick(this.onResize);
});
}
mounted() {
@@ -143,6 +149,8 @@ class TextPage extends Vue {
}
calcDrawProps() {
const wideLetter = 'Щ';
//preloaded fonts
this.fontList = [`12px ${this.fontName}`];
@@ -199,6 +207,22 @@ class TextPage extends Vue {
this.drawHelper.lineHeight = this.lineHeight;
this.drawHelper.context = this.context;
//альтернатива context.measureText
if (!this.context.measureText(wideLetter).width) {
const ctx = this.$refs.measureWidth;
this.drawHelper.measureText = function(text, style) {
ctx.innerText = text;
ctx.style.font = this.fontByStyle(style);
return ctx.clientWidth;
};
this.drawHelper.measureTextFont = function(text, font) {
ctx.innerText = text;
ctx.style.font = font;
return ctx.clientWidth;
}
}
//statusBar
this.statusBarClickable = this.drawHelper.statusBarClickable(this.statusBarTop, this.statusBarHeight);
@@ -211,8 +235,10 @@ class TextPage extends Vue {
this.parsed.wordWrap = this.wordWrap;
this.parsed.cutEmptyParagraphs = this.cutEmptyParagraphs;
this.parsed.addEmptyParagraphs = this.addEmptyParagraphs;
let t = '';
while (this.drawHelper.measureText(t, {}) < this.w) t += 'Щ';
let t = wideLetter;
if (!this.drawHelper.measureText(t, {}))
throw new Error('Ошибка measureText');
while (this.drawHelper.measureText(t, {}) < this.w) t += wideLetter;
this.parsed.maxWordLength = t.length - 1;
this.parsed.measureText = this.drawHelper.measureText.bind(this.drawHelper);
this.parsed.lineHeight = this.lineHeight;
@@ -221,6 +247,9 @@ class TextPage extends Vue {
this.parsed.imageHeightLines = this.imageHeightLines;
this.parsed.imageFitWidth = this.imageFitWidth;
this.parsed.compactTextPerc = this.compactTextPerc;
this.parsed.testText = 'Это тестовый текст. Его ширина выдается системой неверно некоторое время.';
this.parsed.testWidth = this.drawHelper.measureText(this.parsed.testText, {});
}
//scrolling page
@@ -247,25 +276,18 @@ class TextPage extends Vue {
async checkLoadedFonts() {
let loaded = await Promise.all(this.fontList.map(font => document.fonts.check(font)));
if (loaded.some(r => !r)) {
loaded = await Promise.all(this.fontList.map(font => document.fonts.load(font)));
if (loaded.some(r => !r.length))
throw new Error('some font not loaded');
await Promise.all(this.fontList.map(font => document.fonts.load(font)));
}
}
async loadFonts() {
this.fontsLoading = true;
let inst = null;
let close = null;
(async() => {
await sleep(500);
if (this.fontsLoading)
inst = this.$notify({
title: '',
dangerouslyUseHTMLString: true,
message: 'Загрузка шрифта &nbsp;<i class="el-icon-loading"></i>',
duration: 0
});
close = this.$root.notify.info('Загрузка шрифта &nbsp;<i class="la la-snowflake icon-rotate" style="font-size: 150%"></i>');
})();
if (!this.fontsLoaded)
@@ -277,29 +299,15 @@ class TextPage extends Vue {
this.fontsLoaded[this.fontCssUrl] = 1;
}
const waitingTime = 10*1000;
const delay = 100;
let i = 0;
//ждем шрифты
while (i < waitingTime/delay) {
i++;
try {
await this.checkLoadedFonts();
i = waitingTime;
} catch (e) {
await sleep(delay);
}
}
if (i !== waitingTime) {
this.$notify.error({
title: 'Ошибка загрузки',
message: 'Некоторые шрифты не удалось загрузить'
});
try {
await this.checkLoadedFonts();
} catch (e) {
this.$root.notify.error('Некоторые шрифты не удалось загрузить', 'Ошибка загрузки');
}
this.fontsLoading = false;
if (inst)
inst.close();
if (close)
close();
}
getSettings() {
@@ -330,11 +338,15 @@ class TextPage extends Vue {
// ширина шрифта некоторое время выдается неверно, поэтому
if (!omitLoadFonts) {
const parsed = this.parsed;
await sleep(100);
let i = 0;
const t = this.parsed.testText;
while (i++ < 50 && this.parsed === parsed && this.drawHelper.measureText(t, {}) === this.parsed.testWidth)
await sleep(100);
if (this.parsed === parsed) {
parsed.force = true;
this.parsed.testWidth = this.drawHelper.measureText(t, {});
this.draw();
parsed.force = false;
}
}
}
@@ -368,47 +380,51 @@ class TextPage extends Vue {
if (this.lastBook) {
(async() => {
//подождем ленивый парсинг
this.stopLazyParse = true;
while (this.doingLazyParse) await sleep(10);
try {
//подождем ленивый парсинг
this.stopLazyParse = true;
while (this.doingLazyParse) await sleep(10);
const isParsed = await bookManager.hasBookParsed(this.lastBook);
if (!isParsed) {
return;
const isParsed = await bookManager.hasBookParsed(this.lastBook);
if (!isParsed) {
return;
}
this.book = await bookManager.getBook(this.lastBook);
this.meta = bookManager.metaOnly(this.book);
this.fb2 = this.meta.fb2;
let authorNames = [];
if (this.fb2.author) {
authorNames = this.fb2.author.map(a => _.compact([
a.lastName,
a.firstName,
a.middleName
]).join(' '));
}
this.title = _.compact([
authorNames.join(', '),
this.fb2.bookTitle
]).join(' - ');
this.$root.$emit('set-app-title', this.title);
this.parsed = this.book.parsed;
this.page1 = null;
this.page2 = null;
this.statusBar = null;
await this.stopTextScrolling();
await this.calcPropsAndLoadFonts();
this.refreshTime();
if (this.lazyParseEnabled)
this.lazyParsePara();
} catch (e) {
this.$root.stdDialog.alert(e.message, 'Ошибка', {color: 'negative'});
}
this.book = await bookManager.getBook(this.lastBook);
this.meta = bookManager.metaOnly(this.book);
this.fb2 = this.meta.fb2;
let authorNames = [];
if (this.fb2.author) {
authorNames = this.fb2.author.map(a => _.compact([
a.lastName,
a.firstName,
a.middleName
]).join(' '));
}
this.title = _.compact([
authorNames.join(', '),
this.fb2.bookTitle
]).join(' - ');
this.$root.$emit('set-app-title', this.title);
this.parsed = this.book.parsed;
this.page1 = null;
this.page2 = null;
this.statusBar = null;
await this.stopTextScrolling();
this.calcPropsAndLoadFonts();
this.refreshTime();
if (this.lazyParseEnabled)
this.lazyParsePara();
})();
}
}
@@ -432,13 +448,13 @@ class TextPage extends Vue {
}
async onResize() {
/*this.page1 = null;
this.page2 = null;
this.statusBar = null;*/
this.calcDrawProps();
this.setBackground();
this.draw();
try {
this.calcDrawProps();
this.setBackground();
this.draw();
} catch (e) {
//
}
}
get settings() {
@@ -488,7 +504,7 @@ class TextPage extends Vue {
async startTextScrolling() {
if (this.doingScrolling || !this.book || !this.parsed.textLength || !this.linesDown || this.pageLineCount < 1 ||
this.linesDown.length <= this.pageLineCount) {
this.$emit('stop-scrolling');
this.doStopScrolling();
return;
}
@@ -529,7 +545,7 @@ class TextPage extends Vue {
}
this.resolveTransition1Finish = null;
this.doingScrolling = false;
this.$emit('stop-scrolling');
this.doStopScrolling();
this.draw();
}
@@ -823,7 +839,7 @@ class TextPage extends Vue {
let i = this.pageLineCount;
if (this.keepLastToFirst)
i--;
if (i >= 0 && this.linesDown.length >= 2*i) {
if (i >= 0 && this.linesDown.length >= 2*i + (this.keepLastToFirst ? 1 : 0)) {
this.currentAnimation = this.pageChangeAnimation;
this.pageChangeDirectionDown = true;
this.bookPos = this.linesDown[i].begin;
@@ -868,22 +884,26 @@ class TextPage extends Vue {
}
}
doToolBarToggle() {
this.$emit('tool-bar-toggle');
doToolBarToggle(event) {
this.$emit('do-action', {action: 'switchToolbar', event});
}
doScrollingToggle() {
this.$emit('scrolling-toggle');
this.$emit('do-action', {action: 'scrolling', event});
}
doFullScreenToggle() {
this.$emit('full-screen-toogle');
this.$emit('do-action', {action: 'fullScreen', event});
}
doStopScrolling() {
this.$emit('do-action', {action: 'stopScrolling', event});
}
async doFontSizeInc() {
if (!this.settingsChanging) {
this.settingsChanging = true;
const newSize = (this.settings.fontSize + 1 < 100 ? this.settings.fontSize + 1 : 100);
const newSize = (this.settings.fontSize + 1 < 200 ? this.settings.fontSize + 1 : 100);
const newSettings = Object.assign({}, this.settings, {fontSize: newSize});
this.commit('reader/setSettings', newSettings);
await sleep(50);
@@ -924,69 +944,6 @@ class TextPage extends Vue {
}
}
keyHook(event) {
let result = false;
if (event.type == 'keydown' && !event.ctrlKey && !event.altKey) {
result = true;
switch (event.code) {
case 'ArrowDown':
if (event.shiftKey)
this.doScrollingSpeedUp();
else
this.doDown();
break;
case 'ArrowUp':
if (event.shiftKey)
this.doScrollingSpeedDown();
else
this.doUp();
break;
case 'PageDown':
case 'ArrowRight':
this.doPageDown();
break;
case 'Space':
if (event.shiftKey)
this.doPageUp();
else
this.doPageDown();
break;
case 'PageUp':
case 'ArrowLeft':
case 'Backspace':
this.doPageUp();
break;
case 'Home':
this.doHome();
break;
case 'End':
this.doEnd();
break;
case 'KeyA':
if (event.shiftKey)
this.doFontSizeDec();
else
this.doFontSizeInc();
break;
case 'Enter':
case 'Backquote'://`
case 'KeyF':
this.doFullScreenToggle();
break;
case 'Tab':
case 'KeyQ':
this.doToolBarToggle();
event.preventDefault();
event.stopPropagation();
break;
default:
result = false;
break;
}
}
return result;
}
async startClickRepeat(pointX, pointY) {
this.repX = pointX;
this.repY = pointY;
@@ -1064,7 +1021,7 @@ class TextPage extends Vue {
//движение вправо
this.doScrollingSpeedUp();
} else if (Math.abs(dy) < touchDelta && Math.abs(dx) < touchDelta) {
this.doToolBarToggle();
this.doToolBarToggle(event);
}
this.startTouch = null;
@@ -1091,7 +1048,7 @@ class TextPage extends Vue {
} else if (event.button == 1) {
this.doScrollingToggle();
} else if (event.button == 2) {
this.doToolBarToggle();
this.doToolBarToggle(event);
}
}
@@ -1116,7 +1073,7 @@ class TextPage extends Vue {
if (url && url.indexOf('file://') != 0) {
window.open(url, '_blank');
} else {
this.$alert('Оригинал недоступен, т.к. файл книги был загружен с локального диска', '', {type: 'warning'});
this.$root.stdDialog.alert('Оригинал недоступен, т.к. файл книги был загружен с локального диска.', ' ', {color: 'info'});
}
}

View File

@@ -59,7 +59,6 @@ export default class BookParser {
offset: Number, //сумма всех length до этого параграфа
length: Number, //длина text без тегов
text: String, //текст параграфа с вложенными тегами
cut: Boolean, //параграф - кандидат на сокрытие (cutEmptyParagraphs)
addIndex: Number, //индекс добавляемого пустого параграфа (addEmptyParagraphs)
}
*/
@@ -116,7 +115,6 @@ export default class BookParser {
offset: paraOffset,
length: len,
text: text,
cut: (!addIndex && (len == 1 && text[0] == ' ')),
addIndex: (addIndex ? addIndex : 0),
};
@@ -132,10 +130,10 @@ export default class BookParser {
}
let p = para[paraIndex];
//добавление пустых (addEmptyParagraphs) параграфов
paraOffset -= p.length;
//добавление пустых (addEmptyParagraphs) параграфов перед текущим
if (p.length == 1 && p.text[0] == ' ' && len > 0) {
paraIndex--;
paraOffset -= p.length;
for (let i = 0; i < 2; i++) {
newParagraph(' ', 1, i + 1);
}
@@ -144,15 +142,10 @@ export default class BookParser {
p.index = paraIndex;
p.offset = paraOffset;
para[paraIndex] = p;
paraOffset += p.length;
}
paraOffset -= p.length;
//параграф оказался непустой
if (p.length == 1 && p.text[0] == ' ' && len > 0) {
//уберем начальный пробел
p.length = 0;
p.text = p.text.substr(1);
p.cut = (len == 1 && text[0] == ' ');
}
p.length += len;
@@ -605,6 +598,7 @@ export default class BookParser {
if (!this.force &&
para.parsed &&
para.parsed.testWidth === this.testWidth &&
para.parsed.w === this.w &&
para.parsed.p === this.p &&
para.parsed.wordWrap === this.wordWrap &&
@@ -620,6 +614,7 @@ export default class BookParser {
return para.parsed;
const parsed = {
testWidth: this.testWidth,
w: this.w,
p: this.p,
wordWrap: this.wordWrap,
@@ -631,10 +626,7 @@ export default class BookParser {
imageHeightLines: this.imageHeightLines,
imageFitWidth: this.imageFitWidth,
compactTextPerc: this.compactTextPerc,
visible: !(
(this.cutEmptyParagraphs && para.cut) ||
(para.addIndex > this.addEmptyParagraphs)
)
visible: true, //вычисляется позже
};
@@ -650,9 +642,12 @@ export default class BookParser {
text: String,
}
}*/
let parts = this.splitToStyle(para.text);
//инициализация парсера
let line = {begin: para.offset, parts: []};
let paragraphText = '';//текст параграфа
let partText = '';//накапливаемый кусок со стилем
let str = '';//измеряемая строка
@@ -661,14 +656,16 @@ export default class BookParser {
let style = {};
let ofs = 0;//смещение от начала параграфа para.offset
let imgW = 0;
let imageInPara = false;
const compactWidth = this.measureText('W', {})*this.compactTextPerc/100;
// тут начинается самый замес, перенос по слогам и стилизация, а также изображения
for (const part of parts) {
style = part.style;
paragraphText += part.text;
//изображения
if (part.image.id && !part.image.inline) {
parsed.visible = this.showImages;
imageInPara = true;
let bin = this.binary[part.image.id];
if (!bin)
bin = {h: 1, w: 1};
@@ -835,6 +832,16 @@ export default class BookParser {
}
}
//parsed.visible
if (imageInPara) {
parsed.visible = this.showImages;
} else {
parsed.visible = !(
(para.addIndex > this.addEmptyParagraphs) ||
(para.addIndex == 0 && this.cutEmptyParagraphs && paragraphText.trim() == '')
);
}
parsed.lines = lines;
para.parsed = parsed;

View File

@@ -464,7 +464,7 @@ class BookManager {
addEventListener(listener) {
if (this.eventListeners.indexOf(listener) < 0)
this.eventListeners.push(listener);
this.eventListeners.push(listener);
}
removeEventListener(listener) {

View File

@@ -1,4 +1,101 @@
export const versionHistory = [
{
showUntil: '2020-10-31',
header: '0.9.5 (2020-11-01)',
content:
`
<ul>
<li>на панель инструментов добавлена новая кнопка "Обновить с разбиением на параграфы"</li>
<li>исправления багов</li>
</ul>
`
},
{
showUntil: '2020-10-28',
header: '0.9.4 (2020-10-29)',
content:
`
<ul>
<li>заработал новый сайт <a href="https://liberama.top">https://liberama.top</a>, где будет более свободный обмен книгами</li>
<li>для liberama.top добавлено новое окно: "Библиотека"</li>
<li>исправления багов</li>
</ul>
`
},
{
showUntil: '2020-05-20',
header: '0.9.3 (2020-05-21)',
content:
`
<ul>
<li>исправления багов</li>
</ul>
`
},
{
showUntil: '2020-04-25',
header: '0.9.2 (2020-03-15)',
content:
`
<ul>
<li>в настройки добавлена возможность назначать сочетания клавиш на команды в читалке</li>
<li>переход на Service Worker вместо AppCache для автономного режима работы</li>
<li>исправления багов</li>
</ul>
`
},
{
showUntil: '2020-03-02',
header: '0.9.1 (2020-03-03)',
content:
`
<ul>
<li>улучшение работы серверной части</li>
<li>незначительные изменения интерфейса</li>
</ul>
`
},
{
showUntil: '2020-02-25',
header: '0.9.0 (2020-02-26)',
content:
`
<ul>
<li>переход на UI-фреймфорк Quasar</li>
<li>незначительные изменения интерфейса</li>
</ul>
`
},
{
showUntil: '2020-02-05',
header: '0.8.4 (2020-02-06)',
content:
`
<ul>
<li>добавлен paypal-адрес для пожертвований</li>
<li>исправления багов</li>
</ul>
`
},
{
showUntil: '2020-01-27',
header: '0.8.3 (2020-01-28)',
content:
`
<ul>
<li>добавлено всплывающее окно с акцией "Оплатим хостинг вместе"</li>
<li>внутренние оптимизации</li>
</ul>
`
},
{
showUntil: '2020-01-19',
header: '0.8.2 (2020-01-20)',

View File

@@ -1,7 +1,7 @@
<template>
<el-container>
<div>
Раздел Settings в разработке
</el-container>
</div>
</template>
<script>

View File

@@ -1,7 +1,7 @@
<template>
<el-container>
<div>
Раздел Sources в разработке
</el-container>
</div>
</template>
<script>

View File

@@ -0,0 +1,64 @@
<template>
<q-dialog v-model="active">
<div class="column bg-white no-wrap">
<div class="header row">
<div class="caption col row items-center q-ml-md">
<slot name="header"></slot>
</div>
<div class="close-icon column justify-center items-center">
<q-btn flat round dense v-close-popup>
<q-icon name="la la-times" size="18px"></q-icon>
</q-btn>
</div>
</div>
<div class="col q-mx-md">
<slot></slot>
</div>
<div class="row justify-end q-pa-md">
<slot name="footer"></slot>
</div>
</div>
</q-dialog>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
const DialogProps = Vue.extend({
props: {
value: Boolean,
}
})
export default @Component({
})
class Dialog extends DialogProps {
get active() {
return this.value;
}
set active(value) {
this.$emit('input', value);
}
}
//-----------------------------------------------------------------------------
</script>
<style scoped>
.header {
height: 50px;
}
.caption {
font-size: 110%;
overflow: hidden;
}
.close-icon {
width: 50px;
}
</style>

View File

@@ -0,0 +1,59 @@
<template>
<div class="hidden"></div>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
export default @Component({
})
class Notify extends Vue {
notify(opts) {
let {
caption = null,
captionColor = 'black',
color = 'positive',
icon = '',
iconColor = 'white',
message = '',
messageColor = 'black',
position = 'top-right',
} = opts;
caption = (caption ? `<div style="font-size: 120%; color: ${captionColor}"><b>${caption}</b></div><br>` : '');
return this.$q.notify({
position,
color,
textColor: iconColor,
icon,
actions: [{icon: 'la la-times notify-button-icon', color: 'black'}],
html: true,
message:
`<div style="max-width: 350px;">
${caption}
<div style="color: ${messageColor}; overflow-wrap: break-word; word-wrap: break-word;">${message}</div>
</div>`
});
}
success(message, caption, options) {
this.notify(Object.assign({color: 'positive', icon: 'la la-check-circle', message, caption}, options));
}
warning(message, caption, options) {
this.notify(Object.assign({color: 'warning', icon: 'la la-exclamation-circle', message, caption}, options));
}
error(message, caption, options) {
this.notify(Object.assign({color: 'negative', icon: 'la la-exclamation-circle', messageColor: 'yellow', captionColor: 'white', message, caption}, options));
}
info(message, caption, options) {
this.notify(Object.assign({color: 'info', icon: 'la la-bell', message, caption}, options));
}
}
//-----------------------------------------------------------------------------
</script>

View File

@@ -0,0 +1,185 @@
<template>
<q-input outlined dense
v-model="filteredValue"
input-style="text-align: center"
class="no-mp"
:class="(error ? 'error' : '')"
:disable="disable"
>
<slot></slot>
<template v-slot:prepend>
<q-icon :class="(validate(value - step) ? '' : 'disable')"
name="la la-minus-circle"
class="button"
v-ripple="validate(value - step)"
@click="minus"
@mousedown.prevent.stop="onMouseDown($event, 'minus')"
@mouseup.prevent.stop="onMouseUp"
@mouseout.prevent.stop="onMouseUp"
@touchstart.stop="onTouchStart($event, 'minus')"
@touchend.stop="onTouchEnd"
@touchcancel.prevent.stop="onTouchEnd"
/>
</template>
<template v-slot:append>
<q-icon :class="(validate(value + step) ? '' : 'disable')"
name="la la-plus-circle"
class="button"
v-ripple="validate(value + step)"
@click="plus"
@mousedown.prevent.stop="onMouseDown($event, 'plus')"
@mouseup.prevent.stop="onMouseUp"
@mouseout.prevent.stop="onMouseUp"
@touchstart.stop="onTouchStart($event, 'plus')"
@touchend.stop="onTouchEnd"
@touchcancel.prevent.stop="onTouchEnd"
/>
</template>
</q-input>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import * as utils from '../../share/utils';
const NumInputProps = Vue.extend({
props: {
value: Number,
min: { type: Number, default: -Number.MAX_VALUE },
max: { type: Number, default: Number.MAX_VALUE },
step: { type: Number, default: 1 },
digits: { type: Number, default: 0 },
disable: Boolean
}
});
export default @Component({
watch: {
filteredValue: function(newValue) {
if (this.validate(newValue)) {
this.error = false;
this.$emit('input', this.string2number(newValue));
} else {
this.error = true;
}
},
value: function(newValue) {
this.filteredValue = newValue;
},
}
})
class NumInput extends NumInputProps {
filteredValue = 0;
error = false;
created() {
this.filteredValue = this.value;
}
string2number(value) {
return Number.parseFloat(Number.parseFloat(value).toFixed(this.digits));
}
validate(value) {
let n = this.string2number(value);
if (isNaN(n))
return false;
if (n < this.min)
return false;
if (n > this.max)
return false;
return true;
}
plus() {
const newValue = this.value + this.step;
if (this.validate(newValue))
this.filteredValue = newValue;
}
minus() {
const newValue = this.value - this.step;
if (this.validate(newValue))
this.filteredValue = newValue;
}
onMouseDown(event, way) {
this.startClickRepeat = true;
this.clickRepeat = false;
if (event.button == 0) {
(async() => {
await utils.sleep(300);
if (this.startClickRepeat) {
this.clickRepeat = true;
while (this.clickRepeat) {
if (way == 'plus') {
this.plus();
} else {
this.minus();
}
await utils.sleep(50);
}
}
})();
}
}
onMouseUp() {
if (this.inTouch)
return;
this.startClickRepeat = false;
this.clickRepeat = false;
}
onTouchStart(event, way) {
if (!this.$isMobileDevice)
return;
if (event.touches.length == 1) {
this.inTouch = true;
this.onMouseDown({button: 0}, way);
}
}
onTouchEnd() {
if (!this.$isMobileDevice)
return;
this.inTouch = false;
this.onMouseUp();
}
}
//-----------------------------------------------------------------------------
</script>
<style scoped>
.no-mp {
margin: 0;
padding: 0;
}
.button {
font-size: 130%;
border-radius: 20px;
color: #bbb;
cursor: pointer;
}
.button:hover {
color: #616161;
background-color: #efebe9;
}
.error {
background-color: #ffabab;
border-radius: 3px;
}
.disable, .disable:hover {
cursor: not-allowed;
color: #bbb;
background-color: white;
}
</style>

View File

@@ -0,0 +1,333 @@
<template>
<q-dialog ref="dialog" v-model="active" @show="onShow" @hide="onHide">
<slot></slot>
<!--------------------------------------------------->
<div v-show="type == 'alert'" class="bg-white no-wrap">
<div class="header row">
<div class="caption col row items-center q-ml-md">
<q-icon v-show="caption" class="q-mr-sm" :class="iconColor" name="las la-exclamation-circle" size="28px"></q-icon>
<div v-html="caption"></div>
</div>
<div class="close-icon column justify-center items-center">
<q-btn flat round dense v-close-popup>
<q-icon name="la la-times" size="18px"></q-icon>
</q-btn>
</div>
</div>
<div class="q-mx-md">
<div v-html="message"></div>
</div>
<div class="buttons row justify-end q-pa-md">
<q-btn class="q-px-md" dense no-caps @click="okClick">OK</q-btn>
</div>
</div>
<!--------------------------------------------------->
<div v-show="type == 'confirm'" class="bg-white no-wrap">
<div class="header row">
<div class="caption col row items-center q-ml-md">
<q-icon v-show="caption" class="q-mr-sm" :class="iconColor" name="las la-exclamation-circle" size="28px"></q-icon>
<div v-html="caption"></div>
</div>
<div class="close-icon column justify-center items-center">
<q-btn flat round dense v-close-popup>
<q-icon name="la la-times" size="18px"></q-icon>
</q-btn>
</div>
</div>
<div class="q-mx-md">
<div v-html="message"></div>
</div>
<div class="buttons row justify-end q-pa-md">
<q-btn class="q-px-md q-ml-sm" dense no-caps v-close-popup>Отмена</q-btn>
<q-btn class="q-px-md q-ml-sm" color="primary" dense no-caps @click="okClick">OK</q-btn>
</div>
</div>
<!--------------------------------------------------->
<div v-show="type == 'prompt'" class="bg-white no-wrap">
<div class="header row">
<div class="caption col row items-center q-ml-md">
<q-icon v-show="caption" class="q-mr-sm" :class="iconColor" name="las la-exclamation-circle" size="28px"></q-icon>
<div v-html="caption"></div>
</div>
<div class="close-icon column justify-center items-center">
<q-btn flat round dense v-close-popup>
<q-icon name="la la-times" size="18px"></q-icon>
</q-btn>
</div>
</div>
<div class="q-mx-md">
<div v-html="message"></div>
<q-input ref="input" class="q-mt-xs" outlined dense v-model="inputValue"/>
<div class="error"><span v-show="error != ''">{{ error }}</span></div>
</div>
<div class="buttons row justify-end q-pa-md">
<q-btn class="q-px-md q-ml-sm" dense no-caps v-close-popup>Отмена</q-btn>
<q-btn class="q-px-md q-ml-sm" color="primary" dense no-caps @click="okClick">OK</q-btn>
</div>
</div>
<!--------------------------------------------------->
<div v-show="type == 'hotKey'" class="bg-white no-wrap">
<div class="header row">
<div class="caption col row items-center q-ml-md">
<q-icon v-show="caption" class="q-mr-sm" :class="iconColor" name="las la-exclamation-circle" size="28px"></q-icon>
<div v-html="caption"></div>
</div>
<div class="close-icon column justify-center items-center">
<q-btn flat round dense v-close-popup>
<q-icon name="la la-times" size="18px"></q-icon>
</q-btn>
</div>
</div>
<div class="q-mx-md">
<div v-html="message"></div>
<div class="q-my-md text-center">
<div v-show="hotKeyCode == ''" class="text-grey-5">Нет</div>
<div>{{ hotKeyCode }}</div>
</div>
</div>
<div class="buttons row justify-end q-pa-md">
<q-btn class="q-px-md q-ml-sm" dense no-caps v-close-popup>Отмена</q-btn>
<q-btn class="q-px-md q-ml-sm" color="primary" dense no-caps @click="okClick" :disabled="hotKeyCode == ''">OK</q-btn>
</div>
</div>
</q-dialog>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import * as utils from '../../share/utils';
export default @Component({
watch: {
inputValue: function(newValue) {
this.validate(newValue);
},
}
})
class StdDialog extends Vue {
caption = '';
message = '';
active = false;
type = '';
inputValue = '';
error = '';
iconColor = '';
hotKeyCode = '';
created() {
if (this.$root.addKeyHook) {
this.$root.addKeyHook(this.keyHook);
}
}
init(message, caption, opts) {
this.caption = caption;
this.message = message;
this.ok = false;
this.type = '';
this.inputValidator = null;
this.inputValue = '';
this.error = '';
this.iconColor = 'text-warning';
if (opts && opts.color) {
this.iconColor = `text-${opts.color}`;
}
this.hotKeyCode = '';
if (opts && opts.hotKeyCode) {
this.hotKeyCode = opts.hotKeyCode;
}
}
onHide() {
if (this.hideTrigger) {
this.hideTrigger();
this.hideTrigger = null;
}
}
onShow() {
if (this.type == 'prompt') {
this.enableValidator = true;
if (this.inputValue)
this.validate(this.inputValue);
this.$refs.input.focus();
}
}
validate(value) {
if (!this.enableValidator)
return false;
if (this.inputValidator) {
const result = this.inputValidator(value);
if (result !== true) {
this.error = result;
return false;
}
}
this.error = '';
return true;
}
okClick() {
if (this.type == 'prompt' && !this.validate(this.inputValue)) {
this.$refs.dialog.shake();
return;
}
if (this.type == 'hotKey' && this.hotKeyCode == '') {
this.$refs.dialog.shake();
return;
}
this.ok = true;
this.$refs.dialog.hide();
}
alert(message, caption, opts) {
return new Promise((resolve) => {
this.init(message, caption, opts);
this.hideTrigger = () => {
if (this.ok) {
resolve(true);
} else {
resolve(false);
}
};
this.type = 'alert';
this.active = true;
});
}
confirm(message, caption, opts) {
return new Promise((resolve) => {
this.init(message, caption, opts);
this.hideTrigger = () => {
if (this.ok) {
resolve(true);
} else {
resolve(false);
}
};
this.type = 'confirm';
this.active = true;
});
}
prompt(message, caption, opts) {
return new Promise((resolve) => {
this.enableValidator = false;
this.init(message, caption, opts);
this.hideTrigger = () => {
if (this.ok) {
resolve({value: this.inputValue});
} else {
resolve(false);
}
};
this.type = 'prompt';
if (opts) {
this.inputValidator = opts.inputValidator || null;
this.inputValue = opts.inputValue || '';
}
this.active = true;
});
}
getHotKey(message, caption, opts) {
return new Promise((resolve) => {
this.init(message, caption, opts);
this.hideTrigger = () => {
if (this.ok) {
resolve(this.hotKeyCode);
} else {
resolve(false);
}
};
this.type = 'hotKey';
this.active = true;
});
}
keyHook(event) {
if (this.active) {
let handled = false;
if (this.type == 'hotKey') {
if (event.type == 'keydown') {
this.hotKeyCode = utils.keyEventToCode(event);
handled = true;
}
} else {
if (event.key == 'Enter') {
this.okClick();
handled = true;
}
if (event.key == 'Escape') {
this.$nextTick(() => {
this.$refs.dialog.hide();
});
handled = true;
}
}
if (handled) {
event.stopPropagation();
event.preventDefault();
}
}
}
}
//-----------------------------------------------------------------------------
</script>
<style scoped>
.header {
height: 50px;
}
.caption {
font-size: 110%;
overflow: hidden;
}
.close-icon {
width: 50px;
}
.buttons {
height: 60px;
}
.error {
height: 20px;
font-size: 80%;
color: red;
}
</style>

View File

@@ -1,12 +1,14 @@
<template>
<div ref="main" class="main" @click="close" @mouseup="onMouseUp" @mousemove="onMouseMove">
<div ref="windowBox" class="windowBox" @click.stop>
<div class="window">
<div ref="header" class="header" @mousedown.prevent.stop="onMouseDown"
<div ref="main" class="main xyfit absolute" @click="close" @mouseup="onMouseUp" @mousemove="onMouseMove">
<div ref="windowBox" class="xyfit absolute flex no-wrap" @click.stop>
<div ref="window" class="window flexfit column no-wrap">
<div ref="header" class="header row justify-end" @mousedown.prevent.stop="onMouseDown"
@touchstart.stop="onTouchStart" @touchend.stop="onTouchEnd" @touchmove.stop="onTouchMove">
<span class="header-text"><slot name="header"></slot></span>
<span class="close-button" @mousedown.stop @click="close"><i class="el-icon-close"></i></span>
<span class="header-text col"><slot name="header"></slot></span>
<slot name="buttons"></slot>
<span class="close-button row justify-center items-center" @mousedown.stop @click="close"><q-icon name="la la-times" size="16px"/></span>
</div>
<slot></slot>
</div>
</div>
@@ -24,6 +26,7 @@ export default @Component({
width: { type: String, default: '100%' },
maxWidth: { type: String, default: '' },
topShift: { type: Number, default: 0 },
margin: '',
}
})
class Window extends Vue {
@@ -38,6 +41,9 @@ class Window extends Vue {
const top = (this.$refs.main.offsetHeight - this.$refs.windowBox.offsetHeight)/2 + this.topShift;
this.$refs.windowBox.style.left = (left > 0 ? left : 0) + 'px';
this.$refs.windowBox.style.top = (top > 0 ? top : 0) + 'px';
if (this.margin)
this.$refs.window.style.margin = this.margin;
});
}
@@ -116,23 +122,20 @@ class Window extends Vue {
<style scoped>
.main {
position: absolute;
width: 100%;
height: 100%;
background-color: transparent !important;
z-index: 50;
}
.windowBox {
position: absolute;
display: flex;
.xyfit {
height: 100%;
width: 100%;
}
.window {
.flexfit {
flex: 1;
display: flex;
flex-direction: column;
}
.window {
margin: 10px;
background-color: #ffffff;
border: 3px double black;
@@ -141,23 +144,21 @@ class Window extends Vue {
}
.header {
display: flex;
justify-content: flex-end;
background-color: #59B04F;
background: linear-gradient(to bottom right, green, #59B04F);
align-items: center;
height: 30px;
}
.header-text {
flex: 1;
margin-left: 10px;
margin-right: 10px;
color: yellow;
text-shadow: 2px 1px 5px black, 2px 2px 5px black;
overflow: hidden;
white-space: nowrap;
}
.close-button {
display: flex;
justify-content: center;
align-items: center;
width: 30px;
height: 30px;
cursor: pointer;
@@ -166,4 +167,5 @@ class Window extends Vue {
.close-button:hover {
background-color: #69C05F;
}
</style>

View File

@@ -1,68 +0,0 @@
import Vue from 'vue';
/*
import ElementUI from 'element-ui';
import './theme/index.css';
import locale from 'element-ui/lib/locale/lang/ru-RU';
Vue.use(ElementUI, { locale });
*/
//------------------------------------------------------
import './theme/index.css';
import ElMenu from 'element-ui/lib/menu';
import ElMenuItem from 'element-ui/lib/menu-item';
import ElButton from 'element-ui/lib/button';
import ElButtonGroup from 'element-ui/lib/button-group';
import ElCheckbox from 'element-ui/lib/checkbox';
import ElTabs from 'element-ui/lib/tabs';
import ElTabPane from 'element-ui/lib/tab-pane';
import ElTooltip from 'element-ui/lib/tooltip';
import ElCol from 'element-ui/lib/col';
import ElContainer from 'element-ui/lib/container';
import ElAside from 'element-ui/lib/aside';
import ElHeader from 'element-ui/lib/header';
import ElMain from 'element-ui/lib/main';
import ElInput from 'element-ui/lib/input';
import ElInputNumber from 'element-ui/lib/input-number';
import ElSelect from 'element-ui/lib/select';
import ElOption from 'element-ui/lib/option';
import ElTable from 'element-ui/lib/table';
import ElTableColumn from 'element-ui/lib/table-column';
import ElProgress from 'element-ui/lib/progress';
import ElSlider from 'element-ui/lib/slider';
import ElForm from 'element-ui/lib/form';
import ElFormItem from 'element-ui/lib/form-item';
import ElColorPicker from 'element-ui/lib/color-picker';
import ElDialog from 'element-ui/lib/dialog';
import Notification from 'element-ui/lib/notification';
import Loading from 'element-ui/lib/loading';
import MessageBox from 'element-ui/lib/message-box';
const components = {
ElMenu, ElMenuItem, ElButton, ElButtonGroup, ElCheckbox, ElTabs, ElTabPane, ElTooltip,
ElCol, ElContainer, ElAside, ElMain, ElHeader,
ElInput, ElInputNumber, ElSelect, ElOption, ElTable, ElTableColumn,
ElProgress, ElSlider, ElForm, ElFormItem,
ElColorPicker, ElDialog,
};
for (let name in components) {
Vue.component(name, components[name]);
}
//Vue.use(Loading.directive);
Vue.prototype.$loading = Loading.service;
Vue.prototype.$msgbox = MessageBox;
Vue.prototype.$alert = MessageBox.alert;
Vue.prototype.$confirm = MessageBox.confirm;
Vue.prototype.$prompt = MessageBox.prompt;
Vue.prototype.$notify = Notification;
//Vue.prototype.$message = Message;
import lang from 'element-ui/lib/locale/lang/ru-RU';
import locale from 'element-ui/lib/locale';
locale.use(lang);

View File

@@ -1,14 +1,14 @@
<!DOCTYPE html>
<html manifest="/app/manifest.appcache">
<html>
<head>
<title></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="description" content="Браузерная онлайн-читалка книг. Поддерживаются форматы: fb2, html, txt, rtf, doc, docx, pdf, epub, mobi.">
<meta name="keywords" content="онлайн,читалка,fb2,книги,читать,браузер,интернет">
<title></title>
<script src="/sw-register.js"></script>
</head>
<body>
<div id="app"></div>
<script src="https://yastatic.net/share2/share.js" async="async"></script>
</body>
</html>

View File

@@ -2,7 +2,7 @@ import Vue from 'vue';
import router from './router';
import store from './store';
import './element';
import './quasar';
import App from './components/App.vue';
//Vue.config.productionTip = false;

89
client/quasar.js Normal file
View File

@@ -0,0 +1,89 @@
import Vue from 'vue';
import 'quasar/dist/quasar.css';
import Quasar from 'quasar/src/vue-plugin.js'
//config
const config = {};
//components
//import {QLayout} from 'quasar/src/components/layout';
//import {QPageContainer, QPage} from 'quasar/src/components/page';
//import {QDrawer} from 'quasar/src/components/drawer';
import {QCircularProgress} from 'quasar/src/components/circular-progress';
import {QInput} from 'quasar/src/components/input';
import {QBtn} from 'quasar/src/components/btn';
import {QBtnGroup} from 'quasar/src/components/btn-group';
import {QBtnToggle} from 'quasar/src/components/btn-toggle';
import {QIcon} from 'quasar/src/components/icon';
import {QSlider} from 'quasar/src/components/slider';
import {QTabs, QTab} from 'quasar/src/components/tabs';
//import {QTabPanels, QTabPanel} from 'quasar/src/components/tab-panels';
import {QSeparator} from 'quasar/src/components/separator';
import {QList, QItem, QItemSection, QItemLabel} from 'quasar/src/components/item';
import {QTooltip} from 'quasar/src/components/tooltip';
import {QSpinner} from 'quasar/src/components/spinner';
import {QTable, QTh, QTr, QTd} from 'quasar/src/components/table';
import {QCheckbox} from 'quasar/src/components/checkbox';
import {QSelect} from 'quasar/src/components/select';
import {QColor} from 'quasar/src/components/color';
import {QPopupProxy} from 'quasar/src/components/popup-proxy';
import {QDialog} from 'quasar/src/components/dialog';
import {QChip} from 'quasar/src/components/chip';
const components = {
//QLayout,
//QPageContainer, QPage,
//QDrawer,
QCircularProgress,
QInput,
QBtn,
QBtnGroup,
QBtnToggle,
QIcon,
QSlider,
QTabs, QTab,
//QTabPanels, QTabPanel,
QSeparator,
QList, QItem, QItemSection, QItemLabel,
QTooltip,
QSpinner,
QTable, QTh, QTr, QTd,
QCheckbox,
QSelect,
QColor,
QPopupProxy,
QDialog,
QChip,
};
//directives
import Ripple from 'quasar/src/directives/Ripple';
import ClosePopup from 'quasar/src/directives/ClosePopup';
const directives = {Ripple, ClosePopup};
//plugins
import AppFullscreen from 'quasar/src/plugins/AppFullscreen';
import Notify from 'quasar/src/plugins/Notify';
const plugins = {
AppFullscreen,
Notify,
};
//use
Vue.use(Quasar, { config, components, directives, plugins });
//icons
//import '@quasar/extras/material-icons/material-icons.css';
//import '@quasar/extras/material-icons-outlined/material-icons-outlined.css';
//import '@quasar/extras/fontawesome-v5/fontawesome-v5.css';
import '@quasar/extras/line-awesome/line-awesome.css';
//import fontawesomeV5 from 'quasar/icon-set/fontawesome-v5.js'
import lineAwesome from 'quasar/icon-set/line-awesome.js'
Quasar.iconSet.set(lineAwesome);

View File

@@ -2,42 +2,41 @@ import Vue from 'vue';
import VueRouter from 'vue-router';
import _ from 'lodash';
//немедленная загрузка
import CardIndex from './components/CardIndex/CardIndex.vue';
//const CardIndex = () => import('./components/CardIndex/CardIndex.vue');
const CardIndex = () => import('./components/CardIndex/CardIndex.vue');
const Search = () => import('./components/CardIndex/Search/Search.vue');
const Card = () => import('./components/CardIndex/Card/Card.vue');
const Book = () => import('./components/CardIndex/Book/Book.vue');
const History = () => import('./components/CardIndex/History/History.vue');
//немедленная загрузка
//const Reader = () => import('./components/Reader/Reader.vue');
import Reader from './components/Reader/Reader.vue';
//import Reader from './components/Reader/Reader.vue';
const Reader = () => import('./components/Reader/Reader.vue');
const ExternalLibs = () => import('./components/ExternalLibs/ExternalLibs.vue');
//const Forum = () => import('./components/Forum/Forum.vue');
const Income = () => import('./components/Income/Income.vue');
const Sources = () => import('./components/Sources/Sources.vue');
const Settings = () => import('./components/Settings/Settings.vue');
const Help = () => import('./components/Help/Help.vue');
//const NotFound404 = () => import('./components/NotFound404/NotFound404.vue');
const NotFound404 = () => import('./components/NotFound404/NotFound404.vue');
const myRoutes = [
['/', null, null, '/cardindex'],
['/cardindex', CardIndex ],
['/cardindex~search', Search ],
['/cardindex~card', Card ],
['/cardindex~card/:authorId', Card ],
['/cardindex~book', Book ],
['/cardindex~book/:bookId', Book ],
['/cardindex~history', History ],
['/cardindex', CardIndex],
['/cardindex~search', Search],
['/cardindex~card', Card],
['/cardindex~card/:authorId', Card],
['/cardindex~book', Book],
['/cardindex~book/:bookId', Book],
['/cardindex~history', History],
['/reader', Reader ],
['/income', Income ],
['/sources', Sources ],
['/settings', Settings ],
['/help', Help ],
['*', null, null, '/cardindex' ],
['/reader', Reader],
['/external-libs', ExternalLibs],
['/income', Income],
['/sources', Sources],
['/settings', Settings],
['/help', Help],
['/404', NotFound404],
['*', null, null, '/cardindex'],
];
let routes = {};

View File

@@ -193,4 +193,47 @@ export function parseQuery(str) {
query[first] = [query[first], second];
}
return query;
}
}
export function escapeXml(str) {
return str.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;')
;
}
export function keyEventToCode(event) {
let result = [];
let code = event.code;
const modCode = code.substring(0, 3);
if (event.metaKey && modCode != 'Met')
result.push('Meta');
if (event.ctrlKey && modCode != 'Con')
result.push('Ctrl');
if (event.shiftKey && modCode != 'Shi')
result.push('Shift');
if (event.altKey && modCode != 'Alt')
result.push('Alt');
if (modCode == 'Dig') {
code = code.substring(5, 6);
} else if (modCode == 'Key') {
code = code.substring(3, 4);
}
result.push(code);
return result.join('+');
}
export function userHotKeysObjectSwap(userHotKeys) {
let result = {};
for (const [name, codes] of Object.entries(userHotKeys)) {
for (const code of codes) {
result[code] = name;
}
}
return result;
}

View File

@@ -12,11 +12,11 @@ Vue.use(Vuex);
const debug = process.env.NODE_ENV !== 'production';
export default new Vuex.Store(Object.assign({}, root, {
modules: {
uistate,
config,
reader,
},
strict: debug,
plugins: [createPersistedState()]
modules: {
uistate,
config,
reader,
},
strict: debug,
plugins: [createPersistedState()]
}));

View File

@@ -1,15 +1,79 @@
//занчение toolButtons.name не должно совпадать с settingDefaults-propertyName
const readerActions = {
'help': 'Вызвать cправку',
'loader': 'На страницу загрузки',
'settings': 'Настроить',
'undoAction': 'Действие назад',
'redoAction': 'Действие вперед',
'fullScreen': 'На весь экран',
'scrolling': 'Плавный скроллинг',
'stopScrolling': '',
'setPosition': 'Установить позицию',
'search': 'Найти в тексте',
'copyText': 'Скопировать текст со страницы',
'splitToPara': 'Обновить с разбиением на параграфы',
'refresh': 'Принудительно обновить книгу',
'offlineMode': 'Автономный режим (без интернета)',
'libs': 'Библиотека',
'recentBooks': 'Открыть недавние',
'switchToolbar': 'Показать/скрыть панель управления',
'donate': '',
'bookBegin': 'В начало книги',
'bookEnd': 'В конец книги',
'pageBack': 'Страницу назад',
'pageForward': 'Страницу вперед',
'lineBack': 'Строчку назад',
'lineForward': 'Строчку вперед',
'incFontSize': 'Увеличить размер шрифта',
'decFontSize': 'Уменьшить размер шрифта',
'scrollingSpeedUp': 'Увеличить скорость скроллинга',
'scrollingSpeedDown': 'Уменьшить скорость скроллинга',
};
//readerActions[name]
const toolButtons = [
{name: 'undoAction', show: true, text: 'Действие назад'},
{name: 'redoAction', show: true, text: 'Действие вперед'},
{name: 'fullScreen', show: true, text: 'На весь экран'},
{name: 'scrolling', show: false, text: 'Плавный скроллинг'},
{name: 'setPosition', show: true, text: 'На страницу'},
{name: 'search', show: true, text: 'Найти в тексте'},
{name: 'copyText', show: false, text: 'Скопировать текст со страницы'},
{name: 'refresh', show: true, text: 'Принудительно обновить книгу'},
{name: 'offlineMode', show: false, text: 'Автономный режим (без интернета)'},
{name: 'recentBooks', show: true, text: 'Открыть недавние'},
{name: 'undoAction', show: true},
{name: 'redoAction', show: true},
{name: 'fullScreen', show: true},
{name: 'scrolling', show: false},
{name: 'setPosition', show: true},
{name: 'search', show: true},
{name: 'copyText', show: false},
{name: 'splitToPara', show: false},
{name: 'refresh', show: true},
{name: 'libs', show: true},
{name: 'recentBooks', show: true},
{name: 'offlineMode', show: false},
];
//readerActions[name]
const hotKeys = [
{name: 'help', codes: ['F1', 'H']},
{name: 'loader', codes: ['Escape']},
{name: 'settings', codes: ['S']},
{name: 'undoAction', codes: ['Ctrl+BracketLeft']},
{name: 'redoAction', codes: ['Ctrl+BracketRight']},
{name: 'fullScreen', codes: ['Enter', 'Backquote', 'F']},
{name: 'scrolling', codes: ['Z']},
{name: 'setPosition', codes: ['P']},
{name: 'search', codes: ['Ctrl+F']},
{name: 'copyText', codes: ['Ctrl+C']},
{name: 'splitToPara', codes: ['Shift+R']},
{name: 'refresh', codes: ['R']},
{name: 'offlineMode', codes: ['O']},
{name: 'libs', codes: ['L']},
{name: 'recentBooks', codes: ['X']},
{name: 'switchToolbar', codes: ['Tab', 'Q']},
{name: 'bookBegin', codes: ['Home']},
{name: 'bookEnd', codes: ['End']},
{name: 'pageBack', codes: ['PageUp', 'ArrowLeft', 'Backspace', 'Shift+Space']},
{name: 'pageForward', codes: ['PageDown', 'ArrowRight', 'Space']},
{name: 'lineBack', codes: ['ArrowUp']},
{name: 'lineForward', codes: ['ArrowDown']},
{name: 'incFontSize', codes: ['A']},
{name: 'decFontSize', codes: ['Shift+A']},
{name: 'scrollingSpeedUp', codes: ['Shift+ArrowDown']},
{name: 'scrollingSpeedDown', codes: ['Shift+ArrowUp']},
];
const fonts = [
@@ -136,6 +200,7 @@ const webFonts = [
];
//----------------------------------------------------------------------------------------------------------
const settingDefaults = {
textColor: '#000000',
backgroundColor: '#EBE2C9',
@@ -160,11 +225,12 @@ const settingDefaults = {
statusBarTop: false,// top, bottom
statusBarHeight: 19,// px
statusBarColorAlpha: 0.4,
statusBarClickOpen: true,
scrollingDelay: 3000,// замедление, ms
scrollingType: 'ease-in-out', //linear, ease, ease-in, ease-out, ease-in-out
pageChangeAnimation: 'flip',// '' - нет, downShift, rightShift, thaw - протаивание, blink - мерцание, rotate - вращение, flip - листание
pageChangeAnimation: 'blink',// '' - нет, downShift, rightShift, thaw - протаивание, blink - мерцание, rotate - вращение, flip - листание
pageChangeAnimationSpeed: 80, //0-100%
allowUrlParamBookPos: false,
@@ -182,10 +248,35 @@ const settingDefaults = {
imageFitWidth: true,
showServerStorageMessages: true,
showWhatsNewDialog: true,
showDonationDialog2020: true,
enableSitesFilter: true,
fontShifts: {},
showToolButton: {},
userHotKeys: {},
};
const libsDefaults = {
startLink: 'http://flibusta.is',
comment: 'Флибуста | Книжное братство',
closeAfterSubmit: false,
groups: [
{r: 'http://flibusta.is', s: 'http://flibusta.is', list: [
{l: 'http://flibusta.is', c: 'Флибуста | Книжное братство'},
]},
{r: 'http://samlib.ru', s: 'http://samlib.ru', list: [
{l: 'http://samlib.ru', c: 'Журнал "Самиздат"'},
]},
{r: 'http://lib.ru', s: 'http://lib.ru', list: [
{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: [
{l: 'https://aldebaran.ru', c: 'АЛЬДЕБАРАН | Электронная библиотека книг'},
]},
]
};
for (const font of fonts)
@@ -194,6 +285,8 @@ for (const font of webFonts)
settingDefaults.fontShifts[font.name] = font.fontVertShift;
for (const button of toolButtons)
settingDefaults.showToolButton[button.name] = button.show;
for (const hotKey of hotKeys)
settingDefaults.userHotKeys[hotKey.name] = hotKey.codes;
// initial state
const state = {
@@ -204,9 +297,12 @@ const state = {
profilesRev: 0,
allowProfilesSave: false,//подстраховка для разработки
whatsNewContentHash: '',
donationRemindDate: '',
currentProfile: '',
settings: Object.assign({}, settingDefaults),
settingsRev: {},
libs: Object.assign({}, libsDefaults),
libsRev: 0,
};
// getters
@@ -238,6 +334,9 @@ const mutations = {
setWhatsNewContentHash(state, value) {
state.whatsNewContentHash = value;
},
setDonationRemindDate(state, value) {
state.donationRemindDate = value;
},
setCurrentProfile(state, value) {
state.currentProfile = value;
},
@@ -247,13 +346,22 @@ const mutations = {
setSettingsRev(state, value) {
state.settingsRev = Object.assign({}, state.settingsRev, value);
},
setLibs(state, value) {
state.libs = value;
},
setLibsRev(state, value) {
state.libsRev = value;
},
};
export default {
readerActions,
toolButtons,
hotKeys,
fonts,
webFonts,
settingDefaults,
libsDefaults,
namespaced: true,
state,

Binary file not shown.

File diff suppressed because one or more lines are too long

85
docs/beta/beta.liberama Normal file
View File

@@ -0,0 +1,85 @@
server {
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/beta.liberama.top/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/beta.liberama.top/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
server_name beta.liberama.top;
client_max_body_size 50m;
proxy_read_timeout 1h;
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:34082;
}
location /ws {
proxy_pass http://127.0.0.1:34082;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location / {
root /home/beta.liberama/public;
location /tmp {
types { } default_type "application/xml; charset=utf-8";
add_header Content-Encoding gzip;
}
location ~* \.(?:manifest|appcache|html)$ {
expires -1;
}
}
}
server {
listen 80;
server_name beta.liberama.top;
return 301 https://$host$request_uri;
}
server {
listen 80;
server_name b.beta.liberama.top;
client_max_body_size 50m;
proxy_read_timeout 1h;
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:34082;
}
location /ws {
proxy_pass http://127.0.0.1:34082;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location / {
root /home/beta.liberama/public;
location /tmp {
types { } default_type "application/xml; charset=utf-8";
add_header Content-Encoding gzip;
}
location ~* \.(?:manifest|appcache|html)$ {
expires -1;
}
}
}

48
docs/beta/beta.omnireader Normal file
View File

@@ -0,0 +1,48 @@
server {
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/beta.omnireader.ru/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/beta.omnireader.ru/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
server_name beta.omnireader.ru;
client_max_body_size 50m;
proxy_read_timeout 1h;
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:34081;
}
location /ws {
proxy_pass http://127.0.0.1:34081;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location / {
root /home/beta.liberama/public;
location /tmp {
types { } default_type "application/xml; charset=utf-8";
add_header Content-Encoding gzip;
}
location ~* \.(?:manifest|appcache|html)$ {
expires -1;
}
}
}
server {
listen 80;
server_name beta.omnireader.ru;
return 301 https://$host$request_uri;
}

4
docs/beta/deploy.sh Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
npm run build:linux
sudo -u www-data cp -r ../../dist/linux/* /home/beta.liberama

3
docs/beta/run_server.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
sudo -H -u www-data /home/beta.liberama/liberama

128
docs/liberama.top/liberama Normal file
View File

@@ -0,0 +1,128 @@
server {
server_name _;
listen 80 default_server;
listen 443 ssl default_server;
#openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/ssl/nginx.key -out /etc/nginx/ssl/nginx.crt
ssl_certificate /etc/nginx/ssl/nginx.crt;
ssl_certificate_key /etc/nginx/ssl/nginx.key;
return 403;
}
server {
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/liberama.top/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/liberama.top/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
server_name liberama.top;
client_max_body_size 50m;
proxy_read_timeout 1h;
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:55081;
}
location /ws {
proxy_pass http://127.0.0.1:55081;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location / {
root /home/liberama/public;
location /tmp {
types { } default_type "application/xml; charset=utf-8";
add_header Content-Encoding gzip;
}
location ~* \.(?:manifest|appcache|html)$ {
expires -1;
}
}
}
server {
listen 80;
server_name liberama.top;
return 301 https://$host$request_uri;
}
server {
listen 80;
server_name b.liberama.top;
client_max_body_size 50m;
proxy_read_timeout 1h;
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:55081;
}
location /ws {
proxy_pass http://127.0.0.1:55081;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location / {
root /home/liberama/public;
location /tmp {
types { } default_type "application/xml; charset=utf-8";
add_header Content-Encoding gzip;
}
location ~* \.(?:manifest|appcache|html)$ {
expires -1;
}
}
}
server {
listen 23480;
server_name flibusta_proxy;
valid_referers liberama.top b.liberama.top;
if ($invalid_referer) {
return 403;
}
location / {
proxy_pass http://flibusta.is;
proxy_redirect http://static.flibusta.is:443 http://b.liberama.top:23481;
}
}
server {
listen 23481;
server_name flibusta_proxy_static;
valid_referers liberama.top b.liberama.top;
if ($invalid_referer) {
return 403;
}
location / {
proxy_pass http://static.flibusta.is:443;
proxy_set_header Referer "";
}
}

View File

@@ -39,11 +39,11 @@ sudo apt install poppler-utils
```
### nginx, server config
Для своего домена необходимо будет подправить docs/omnireader/omnireader.
Для своего домена необходимо будет подправить docs/omnireader.ru/omnireader.
Можно также настроить сервер для HTTP, без SSL.
```
sudo apt install nginx
sudo cp docs/omnireader/omnireader /etc/nginx/sites-available/omnireader
sudo cp docs/omnireader.ru/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
@@ -59,14 +59,20 @@ 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
sudo -u www-data cp -r docs/omnireader.ru/old/* /home/oldreader
```
## Запуск по крону
```
* * * * * /root/liberama/docs/omnireader.ru/cron_server.sh
```
## Деплой и запуск
```
cd docs/omnireader
cd docs/omnireader.ru
./stop_server.sh
./deploy.sh
./run_server.sh
./start_server.sh
```
После первого запуска будет создан конфигурационный файл `/home/liberama/data/config.json`.
@@ -81,4 +87,4 @@ cd docs/omnireader
}
]
```
и перезапустить `run_server.sh`
и перезапустить сервер

View File

@@ -0,0 +1,8 @@
#!/bin/bash
if ! pgrep -x "liberama" > /dev/null ; then
sudo -H -u www-data /home/liberama/liberama
else
echo "Process 'liberama' already running"
fi

View File

@@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/bash
npm run build:linux
sudo -u www-data cp -r ../../dist/linux/* /home/liberama

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 227 B

After

Width:  |  Height:  |  Size: 227 B

View File

Before

Width:  |  Height:  |  Size: 246 B

After

Width:  |  Height:  |  Size: 246 B

View File

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 193 KiB

View File

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -8,6 +8,7 @@ server {
server_name omnireader.ru;
client_max_body_size 50m;
proxy_read_timeout 1h;
gzip on;
gzip_min_length 1024;
@@ -18,11 +19,18 @@ server {
proxy_pass http://127.0.0.1:44081;
}
location /ws {
proxy_pass http://127.0.0.1:44081;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location / {
root /home/liberama/public;
location /tmp {
add_header Content-Type text/xml;
types { } default_type "application/xml; charset=utf-8";
add_header Content-Encoding gzip;
}

View File

@@ -0,0 +1,59 @@
server {
listen 80;
server_name omnireader.ru;
client_max_body_size 50m;
proxy_read_timeout 1h;
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 /ws {
proxy_pass http://127.0.0.1:44081;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location / {
root /home/liberama/public;
location /tmp {
types { } default_type "application/xml; charset=utf-8";
add_header Content-Encoding gzip;
}
location ~* \.(?:manifest|appcache|html)$ {
expires -1;
}
}
}
server {
listen 80;
server_name old.omnireader.ru;
client_max_body_size 50m;
gzip on;
gzip_min_length 1024;
gzip_proxied expired no-cache no-store private auth;
gzip_types *;
root /home/oldreader;
index index.html;
# Обработка php файлов с помощью fpm
location ~ \.php$ {
try_files $uri =404;
include /etc/nginx/fastcgi.conf;
fastcgi_pass unix:/run/php/php7.2-fpm.sock;
}
}

Some files were not shown because too many files have changed in this diff Show More