Compare commits

...

1372 Commits

Author SHA1 Message Date
Book Pauk
d58a2c065a Merge branch 'release/0.11.3' 2022-03-29 17:40:57 +07:00
Book Pauk
53135e7ee8 Поправка даты 2022-03-29 17:40:29 +07:00
Book Pauk
5c48ca9e6c Рефакторинг versionHistory, небольшие поправки 2022-03-29 17:37:24 +07:00
Book Pauk
c4a280f3d8 Скрыл устаревший чекбокс 2022-03-29 16:52:03 +07:00
Book Pauk
ba2943c722 Поправлен баг 2022-03-29 16:49:04 +07:00
Book Pauk
26f6ffc83a Убрал PayPal из списка 2022-03-29 16:25:26 +07:00
Book Pauk
bcf075a72c Доработки WebSocketConnection 2022-03-29 16:23:34 +07:00
Book Pauk
02d458d192 Миграция "jembadb" => "^2.3.0" 2022-03-29 15:49:48 +07:00
Book Pauk
a349d8af68 Обновил пакет JembaDb 2022-02-08 20:55:31 +07:00
Book Pauk
0dbaf32aac Merge tag '0.11.2' into develop
0.11.2
2022-01-11 23:25:23 +07:00
Book Pauk
e8c41ef3a8 Merge branch 'release/0.11.2' 2022-01-11 23:24:58 +07:00
Book Pauk
e43a44e986 0.11.2 2022-01-11 23:24:37 +07:00
Book Pauk
f14b8ed277 Добавлена реакция на сигнал SIGUSR2 2022-01-11 23:23:54 +07:00
Book Pauk
bbfe8a64cb Мелкая поправка 2022-01-11 23:11:04 +07:00
Book Pauk
bcf3c2dab0 Улучшение обработки ошибок 2022-01-11 22:23:35 +07:00
Book Pauk
d5404fd260 Убрал устаревший код 2022-01-11 21:30:43 +07:00
Book Pauk
54bc662e43 Поправил конфиг для nginx 2021-12-24 17:59:26 +07:00
Book Pauk
42546ca97e Обновление jembadb до версии 1.3.0 2021-12-21 20:21:32 +07:00
Book Pauk
5c13cf0eb9 Добавил -C GZip для pkg 2021-12-20 17:27:04 +07:00
Book Pauk
2a9d44ae9a Поправка конфига для eslint 2021-12-20 17:26:19 +07:00
Book Pauk
38414ae7b6 Переход на пакет jembadb 2021-12-17 20:05:57 +07:00
Book Pauk
3ecb3e80ac Удалил комментарии 2021-12-12 01:56:24 +07:00
Book Pauk
4968828488 Merge tag '0.11.1-2' into develop
0.11.1-2
2021-12-03 15:25:17 +07:00
Book Pauk
4db3cd24df Merge branch 'release/0.11.1-2' 2021-12-03 15:25:11 +07:00
Book Pauk
45c6d3da77 Поправил таймаут, улучшение скорости синхронизации 2021-12-03 15:16:39 +07:00
Book Pauk
4aab1da3c6 Merge tag '0.11.1-1' into develop
0.11.1-1
2021-12-03 15:03:46 +07:00
Book Pauk
bf5dfa1c15 Merge branch 'release/0.11.1-1' 2021-12-03 15:03:37 +07:00
Book Pauk
7549bdd2b4 Обновил pkg 2021-12-03 15:02:56 +07:00
Book Pauk
1bb2525ab2 Merge tag '0.11.1' into develop
0.11.1
2021-12-03 14:35:04 +07:00
Book Pauk
22a556f612 Merge branch 'release/0.11.1' 2021-12-03 14:34:56 +07:00
Book Pauk
056611e87c Версия 0.11.1 2021-12-03 14:34:36 +07:00
Book Pauk
6debe24880 Удален более ненужный файл 2021-12-03 14:30:57 +07:00
Book Pauk
56559bddab Мелкий рефакторинг 2021-12-03 14:28:17 +07:00
Book Pauk
9ec74eccb4 Добавлен папаметр forceAutoRepair 2021-12-03 14:21:50 +07:00
Book Pauk
3d2f45c20d Мелие поправки 2021-12-03 14:21:36 +07:00
Book Pauk
fb2eedd5ba Добавлен конвертер SQLITE -> JambaDb 2021-12-03 14:07:32 +07:00
Book Pauk
e278b4a00e Мелкие поправки 2021-12-02 18:39:28 +07:00
Book Pauk
0beaa611f6 Переход на JembaDb 2021-12-02 18:36:49 +07:00
Book Pauk
14ca2daa39 Небольшой рефакторинг 2021-12-01 22:09:48 +07:00
Book Pauk
714eb3ae83 Поправки по результату тестирования 2021-12-01 21:26:26 +07:00
Book Pauk
6286d663c9 Поправлен баг 2021-12-01 19:27:16 +07:00
Book Pauk
b5db2079d2 Jemba-миграции 2021-12-01 17:50:48 +07:00
Book Pauk
b3b30b9bd9 Поправил триггер для autorepair 2021-11-24 15:15:22 +07:00
Book Pauk
0b6a726503 Новый движок БД 2021-11-24 14:15:09 +07:00
Book Pauk
609334c5a6 Пометил модули устаревшими 2021-11-24 14:14:24 +07:00
Book Pauk
4852c7aec3 Добавлен модуль AsyncExit для выполненния cleanup-процедур перед выходом из приложения 2021-11-24 14:13:13 +07:00
Book Pauk
b1e3d33694 Merge tag '0.11.0-1' into develop
0.11.0-1
2021-11-22 21:12:42 +07:00
Book Pauk
2bfc557071 Merge branch 'release/0.11.0-1' 2021-11-22 21:12:35 +07:00
Book Pauk
e1216109bc Поправлен баг с maxBodyLength клиента WebDav 2021-11-22 21:12:02 +07:00
Book Pauk
990b8f390c Merge tag '0.11.0' into develop
0.11.0
2021-11-18 18:43:53 +07:00
Book Pauk
e6f6cd4ff3 Merge branch 'release/0.11.0' 2021-11-18 18:43:45 +07:00
Book Pauk
7deb745651 Поправил ссылку на инструкцию certbot 2021-11-18 18:28:25 +07:00
Book Pauk
70f3ca8067 Добавил beta-конфиг nginx для http 2021-11-18 18:25:04 +07:00
Book Pauk
bb8497a997 Поправил ошибку в доке 2021-11-18 18:22:08 +07:00
Book Pauk
2127e2ec0a Версия 0.11.0 2021-11-18 18:17:22 +07:00
Book Pauk
9be4011d54 Небольшая поправка формирования заголовка 2021-11-18 17:56:54 +07:00
Book Pauk
c534edfeb5 Поправки 2021-11-16 15:41:51 +07:00
Book Pauk
adc8cd7243 Переход на Vue 3 2021-11-16 15:05:00 +07:00
Book Pauk
522d2d3b9c Актуализация пакетов 2021-11-16 14:41:53 +07:00
Book Pauk
046933a05e Поправка багов 2021-11-16 14:32:54 +07:00
Book Pauk
9143288de2 Поправка копирования assets 2021-11-16 14:03:21 +07:00
Book Pauk
6053ca6c0e Настройка правильных редиректов роутера 2021-11-07 15:38:17 +07:00
Book Pauk
084197530e Форматирование кода 2021-11-07 15:38:05 +07:00
Book Pauk
9f366ca811 Поправлен баг resize 2021-11-07 14:49:33 +07:00
Book Pauk
7c07e6f004 Поправка бага 2021-11-01 19:08:17 +07:00
Book Pauk
3d4d7e0342 Переход на Vue 3 2021-11-01 18:23:58 +07:00
Book Pauk
1a8f241aad Переход на Vue 3, небольшая реструктуризация файлов 2021-11-01 17:56:45 +07:00
Book Pauk
33e938b76a Поправлен баг 2021-10-31 21:51:03 +07:00
Book Pauk
e2db546066 Переход на Vue 3 2021-10-31 21:28:31 +07:00
Book Pauk
def9ee52e2 Поправка разметки 2021-10-31 13:19:11 +07:00
Book Pauk
1afe10be03 Переход на Vue 3 2021-10-31 13:14:12 +07:00
Book Pauk
fa44641fa2 Актуализация пакетов 2021-10-31 13:00:41 +07:00
Book Pauk
9a1ef85c93 Переход на Vue 3 2021-10-29 19:11:10 +07:00
Book Pauk
b848cf5aa7 Переход на Vue 3 2021-10-29 18:24:23 +07:00
Book Pauk
8057e18ebc Переход на Vue 3 2021-10-29 16:33:38 +07:00
Book Pauk
76e09ef34e Переход на Vue 3, в процессе 2021-10-29 15:27:04 +07:00
Book Pauk
00cb2dc274 Переход на Vue 3, в процессе 2021-10-29 12:56:28 +07:00
Book Pauk
ed46e91432 Переход на Vue 3, в процессе 2021-10-29 12:21:53 +07:00
Book Pauk
88d75fb0d8 Переход на Vue 3, в процессе 2021-10-28 16:55:44 +07:00
Book Pauk
a1d7a73459 Переход на Vue 3, в процессе 2021-10-28 15:17:19 +07:00
Book Pauk
687f89729b Переход на Vue 3, в процессе 2021-10-28 14:53:22 +07:00
Book Pauk
6bf678e01f Функция для преобразования Vue-класса во Vue-компонент 2021-10-28 13:52:25 +07:00
Book Pauk
a18aec2f96 Переход на Vue 3 - начало, пока ничего не работает 2021-10-27 23:09:20 +07:00
Book Pauk
1c0cf303a0 Поправка настроек eslint 2021-10-27 16:11:20 +07:00
Book Pauk
5c7ae73982 Поправки по требованиям eslint 2021-10-27 15:07:25 +07:00
Book Pauk
4e9c69a1cf Настройка eslint 2021-10-27 15:05:37 +07:00
Book Pauk
78375be8bf Поправки по требованиям eslint 2021-10-27 15:05:18 +07:00
Book Pauk
b684725094 Настройка eslint 2021-10-27 04:11:07 +07:00
Book Pauk
ff52602c3a Мелкие поправки 2021-10-27 04:10:19 +07:00
Book Pauk
ce704c5e26 Актуализация пакетов 2021-10-27 01:28:16 +07:00
Book Pauk
4503e4ed17 Актуализация пакетов 2021-10-27 01:03:47 +07:00
Book Pauk
01c384c43a Актуализация пакетов 2021-10-27 00:52:57 +07:00
Book Pauk
dda2de58a8 Актуализация пакетов, в процессе 2021-10-27 00:06:42 +07:00
Book Pauk
0365acbf7a Актуализация пакетов 2021-10-26 01:05:09 +07:00
Book Pauk
bbf1ab7180 Актуализация пакетов 2021-10-26 00:35:45 +07:00
Book Pauk
83bf1f1d3a Актуализация пакетов 2021-10-26 00:18:18 +07:00
Book Pauk
fdf04fed0e Актуализация пакетов 2021-10-25 23:55:26 +07:00
Book Pauk
acce32bfa7 Актуализация пакетов 2021-10-25 16:15:15 +07:00
Book Pauk
614c45ac7d Актуализировал readme 2021-10-25 15:31:07 +07:00
Book Pauk
c4c0199a1b Merge tag '0.10.3' into develop
0.10.3
2021-10-25 01:54:07 +07:00
Book Pauk
a53ebb9355 Merge branch 'release/0.10.3' 2021-10-25 01:54:00 +07:00
Book Pauk
06e12930c7 Актуализирован конвертер для samlib.ru 2021-10-25 01:01:26 +07:00
Book Pauk
0f7655773a Версия 0.10.3 2021-10-20 18:29:52 +07:00
Book Pauk
26660461d4 Исправлен баг парсера с пустыми параграфами (содержащими только разметку) 2021-10-20 18:05:38 +07:00
Book Pauk
b41ee91db5 Актуализировал инструкцию 2021-10-20 15:49:07 +07:00
Book Pauk
746dd8d37a Актуализация инструкции 2021-10-20 15:42:09 +07:00
Book Pauk
fb4a57027d Merge tag '0.10.2' into develop
0.10.2
2021-10-19 23:33:48 +07:00
Book Pauk
c97660bed0 Merge branch 'release/0.10.2' 2021-10-19 23:33:38 +07:00
Book Pauk
fd8c8812a3 Версия 0.10.2 2021-10-19 23:26:45 +07:00
Book Pauk
0101392858 Актуализированы версии пакетов 2021-10-19 19:55:46 +07:00
Book Pauk
cc3f82d693 Обновление пакетов pkg и sqlite 2021-10-19 02:48:08 +07:00
Book Pauk
d21997c918 Подготовка конфигов и инструкций к разворачиванию на Ubuntu 20.04 2021-10-18 20:25:45 +07:00
Book Pauk
74fec12f5c Merge tag '0.10.1-1' into develop
0.10.1-1
2021-10-10 19:08:28 +07:00
Book Pauk
59525f8fa7 Merge branch 'release/0.10.1-1' 2021-10-10 19:08:20 +07:00
Book Pauk
3c6d3befb2 Поправил историю версий 2021-10-10 19:07:48 +07:00
Book Pauk
dfa72c80bc Merge tag '0.10.1' into develop
0.10.1
2021-10-10 18:35:24 +07:00
Book Pauk
c6e534b9db Merge branch 'hotfix/0.10.1' 2021-10-10 18:35:11 +07:00
Book Pauk
032ab6a85d Хотфикс для исправления проблемы с пустой БД storage при инициализации 2021-10-10 18:34:18 +07:00
Book Pauk
830c066ebf Обновил иконку 2021-10-10 18:25:32 +07:00
Book Pauk
c432388515 Поправил иконку 2021-02-11 22:51:41 +07:00
Book Pauk
476deba93a Заменил иконку 2021-02-11 22:07:58 +07:00
Book Pauk
ffb4f2386d Merge tag '0.10.0-2' into develop
0.10.0-2
2021-02-10 20:19:51 +07:00
Book Pauk
21716163cb Merge branch 'release/0.10.0-2' 2021-02-10 20:19:47 +07:00
Book Pauk
ca924148a5 Поправки багов 2021-02-10 20:18:41 +07:00
Book Pauk
37aa9b84ae Merge tag '0.10.0-1' into develop
0.10.0-1
2021-02-10 15:41:24 +07:00
Book Pauk
c7bd7d4d7d Merge branch 'release/0.10.0-1' 2021-02-10 15:41:19 +07:00
Book Pauk
d81a50e696 Поправки багов 2021-02-10 15:40:44 +07:00
Book Pauk
dda9943dbe Merge tag '0.10.0' into develop
0.10.0
2021-02-10 03:23:57 +07:00
Book Pauk
2b4b9f24a1 Merge branch 'release/0.10.0' 2021-02-10 03:23:50 +07:00
Book Pauk
2af77f22d6 Мелкая поправка 2021-02-10 03:22:20 +07:00
Book Pauk
f142e5812d Добавлена опция "Не включать строку статуса в обои" 2021-02-10 03:18:47 +07:00
Book Pauk
ed901fc181 Добавлена возможность загружать пользовательские обои, пока без синхронизации 2021-02-10 02:55:47 +07:00
Book Pauk
87a068899a Поправки wallpaper 2021-02-09 22:29:20 +07:00
Book Pauk
115f683128 Улучшение отображения селектора обоев 2021-02-09 21:55:19 +07:00
Book Pauk
111568fc2e Поправлен баг 2021-02-09 21:16:17 +07:00
Book Pauk
825136b5ff 0.10.0 2021-02-09 21:05:26 +07:00
Book Pauk
eae34b1121 История 2021-02-09 21:04:56 +07:00
Book Pauk
b9d7a6a3bb Убрал дебаг 2021-02-09 21:00:51 +07:00
Book Pauk
1e5375f8f9 Рефакторинг 2021-02-09 21:00:18 +07:00
Book Pauk
f597c603bf Добавил цвета для статусбара и разделителя 2021-02-09 18:43:43 +07:00
Book Pauk
b93dd0a59e Поправка 2021-02-09 18:08:13 +07:00
Book Pauk
a5740e4349 Доработки 2021-02-09 18:07:02 +07:00
Book Pauk
dacbd05911 Работа над двухстраничным режимом 2021-02-09 17:47:10 +07:00
Book Pauk
65c66e0feb Работа над двухстраничным режимом 2021-02-09 15:46:57 +07:00
Book Pauk
52f9131f99 Доработки двухстраничного режима 2021-02-04 20:34:25 +07:00
Book Pauk
cfc946ad12 Работа над двухстраничным режимом 2021-02-04 20:08:06 +07:00
Book Pauk
a207a0554c Работа на двухстраничным режимом 2021-02-04 15:55:12 +07:00
Book Pauk
675e898163 Работа над двухстраничным режимом 2021-02-04 15:18:32 +07:00
Book Pauk
d2167d8605 Работа над двухстраничным режимом 2021-02-02 18:09:21 +07:00
Book Pauk
de849d3447 Рефакторинг 2021-02-02 18:08:55 +07:00
Book Pauk
6c20b0b83e Улучшения SqliteConnectionPool 2021-02-01 18:05:32 +07:00
Book Pauk
a09b70a991 Рефакторинг WebSocketConnection, небольшие улучшения 2021-02-01 17:57:24 +07:00
Book Pauk
2427a3e08b Поправка версии node 2020-12-30 03:41:59 +07:00
Book Pauk
1104f9b850 Небольшая поправка 2020-12-24 21:39:39 +07:00
Book Pauk
dc48700e9e Небольшая поправка 2020-12-24 21:35:31 +07:00
Book Pauk
f0b0c39328 Поправки по результату тестирования, незначительные улучшения 2020-12-24 20:51:02 +07:00
Book Pauk
aad74cf682 Поправки по результату тестирования, оптимизации 2020-12-24 18:32:57 +07:00
Book Pauk
d449478204 Небольшое форматирование 2020-12-24 18:21:18 +07:00
Book Pauk
d4f6536caa Поправки по результату тестирования 2020-12-24 16:33:44 +07:00
Book Pauk
1eac00f71c Поправка багов 2020-12-24 00:44:38 +07:00
Book Pauk
ca1170a9f0 Поправки по результату тестирования 2020-12-24 00:25:54 +07:00
Book Pauk
79dda03bac Рефакторинг, плюс небольшое улучшение механизма загрузки шрифта 2020-12-23 22:38:52 +07:00
Book Pauk
6c8e0b8573 Поправил баг 2020-12-23 22:23:37 +07:00
Book Pauk
17c14722fd Рефакторинг 2020-12-23 21:17:39 +07:00
Book Pauk
48612ee118 Поправлен баг 2020-12-22 02:24:46 +07:00
Book Pauk
205c676999 Переименование YandexMoney -> ЮMoney 2020-12-21 19:50:27 +07:00
Book Pauk
54e0dd0478 В список недавних добавлена полоска прочитанного 2020-12-21 18:08:35 +07:00
Book Pauk
2de8d7515e Добалвлен крестик в строку поиска 2020-12-21 17:48:49 +07:00
Book Pauk
a251d16432 Merge tag '0.9.12-1' into develop
0.9.12-1
2020-12-19 21:23:43 +07:00
Book Pauk
599caba912 Merge branch 'release/0.9.12-1' 2020-12-19 21:23:38 +07:00
Book Pauk
3477c43465 Поправка по результату чтения логов 2020-12-19 21:19:58 +07:00
Book Pauk
200dac7946 Небольшая поправка 2020-12-19 21:17:31 +07:00
Book Pauk
e60829946d Merge tag '0.9.12' into develop
0.9.12
2020-12-19 03:23:32 +07:00
Book Pauk
ef12a84285 Merge branch 'release/0.9.12' 2020-12-19 03:23:27 +07:00
Book Pauk
6a18ae3f27 Версия 0.9.12 2020-12-19 03:22:47 +07:00
Book Pauk
a250e95950 Поправил баг 2020-12-19 03:07:59 +07:00
Book Pauk
b174ae452b Оптимизации проверок типа файла 2020-12-19 03:05:58 +07:00
Book Pauk
0b63bce357 Исправления багов 2020-12-19 02:47:06 +07:00
Book Pauk
de0d10e792 Мелкая поправка 2020-12-19 02:46:46 +07:00
Book Pauk
b358b340b4 Улучшение формирования оглавления 2020-12-19 00:20:11 +07:00
Book Pauk
455aba7f4f Мелкая поправка текста 2020-12-19 00:17:36 +07:00
Book Pauk
fde0437157 Добавлено извлечение схемы документа в ConvertPdfImages, мелкий рефакторинг 2020-12-18 23:56:55 +07:00
Book Pauk
480c95bd63 Добавлена возможность конвертирования pdf как набор изображений.
Добавлены соответствующие настройки в читалку.
2020-12-18 23:30:13 +07:00
Book Pauk
972f957685 Работа над вкладкой "Конвертирование" 2020-12-18 22:44:20 +07:00
Book Pauk
40ff04e5dc Работа над вкладкой "Конвертирование" 2020-12-18 21:48:08 +07:00
Book Pauk
b3c028bd7a Убрал устаревшее 2020-12-18 21:23:02 +07:00
Book Pauk
51ec6a54fa Переименования, небольшое улучшение html-title 2020-12-17 23:39:45 +07:00
Book Pauk
7a29b16ee8 Коментарии к 0.9.12 2020-12-17 23:37:00 +07:00
Book Pauk
7af6fd8248 Новая вкладка 2020-12-17 23:36:30 +07:00
Book Pauk
e1c93169b5 Добавлена вкладка "Конвертирование" 2020-12-17 23:35:56 +07:00
Book Pauk
f4716d5a1e Поправлен баг 2020-12-17 23:12:36 +07:00
Book Pauk
f5c06ce420 Добавлен парсинг оглавления из djvu, добавлено отображение атрибута alt изображений в ContentsPage 2020-12-17 20:57:29 +07:00
Book Pauk
9492f85d80 Merge tag '0.9.11-4' into develop
0.9.11-4
2020-12-16 21:17:05 +07:00
Book Pauk
b1303a3ba2 Merge branch 'release/0.9.11-4' 2020-12-16 21:16:58 +07:00
Book Pauk
5c9cfe5e6f Оптимизация 2020-12-16 21:15:45 +07:00
Book Pauk
b89b5322b8 Merge tag '0.9.11-3' into develop
0.9.11-3
2020-12-16 21:07:39 +07:00
Book Pauk
945feba6b2 Merge branch 'release/0.9.11-3' 2020-12-16 21:07:32 +07:00
Book Pauk
c8af4b907b Добавлено отображение текущей позиции в оглавлении 2020-12-16 21:06:27 +07:00
Book Pauk
298e8928cf Поправлен мелкий баг 2020-12-16 17:10:44 +07:00
Book Pauk
8cb67d2976 Поправлен баг 2020-12-16 16:41:51 +07:00
Book Pauk
32b8382641 Поправлен баг 2020-12-16 16:09:35 +07:00
Book Pauk
007e97463b Небольшая поправка 2020-12-16 15:56:10 +07:00
Book Pauk
e4f190698d Merge tag '0.9.11-2' into develop
0.9.11-2
2020-12-16 01:43:05 +07:00
Book Pauk
b3be07b17e Merge branch 'release/0.9.11-2' 2020-12-16 01:43:00 +07:00
Book Pauk
72f8977071 Добавлено отображение номера изображения в статусбар 2020-12-16 01:41:37 +07:00
Book Pauk
3dbf00344e Мелкая поправка 2020-12-16 01:05:06 +07:00
Book Pauk
ffdf0b12cd В список изображений добавлено отображение самой картинки 2020-12-16 01:00:54 +07:00
Book Pauk
a51150c729 Рефакторинг 2020-12-15 23:01:58 +07:00
Book Pauk
37e14b397c Рефакторинг 2020-12-15 21:56:14 +07:00
Book Pauk
e48af7ee7d Дополнительно отображаем тип файла в списке изображений 2020-12-15 20:17:21 +07:00
Book Pauk
3eb3dd371a В ContentsPage добавлена вкладка "Изображения" 2020-12-15 15:40:12 +07:00
Book Pauk
8ef6551560 Улучшено распознавание xml-формата 2020-12-15 15:04:30 +07:00
Book Pauk
b1f5f3dd28 Merge tag '0.9.11-1' into develop
0.9.11-1
2020-12-14 02:23:42 +07:00
Book Pauk
6074c4b7bd Merge branch 'release/0.9.11-1' 2020-12-14 02:23:36 +07:00
Book Pauk
9906dd43c7 Работа над конвертером pdf 2020-12-14 02:22:38 +07:00
Book Pauk
17699f66f8 Небольшое улучшение парсинга оглавления 2020-12-14 02:07:20 +07:00
Book Pauk
80a29e654d Поправки механизма оповещения о выходе новой версии 2020-12-14 00:21:48 +07:00
Book Pauk
4184fda247 Мелкая поправка 2020-12-13 22:53:47 +07:00
Book Pauk
7460ff7055 Добавлена проверка выхода новой версии читалки и уведомление об этом,
заодно попутный рефакторинг
2020-12-13 22:50:24 +07:00
Book Pauk
3137b86cee Работа над конвертером Pdf 2020-12-13 21:54:03 +07:00
Book Pauk
b2ca84bb7e Поправил readme 2020-12-13 19:07:15 +07:00
Book Pauk
7d692dd730 Рефакторинг 2020-12-13 18:55:56 +07:00
Book Pauk
8850a89aa7 Поправлен баг 2020-12-13 18:51:13 +07:00
Book Pauk
57b01dd204 Рефакторинг, добавлена поддержка jpeg, png 2020-12-13 17:03:47 +07:00
Book Pauk
8aa1da36b6 Небольшие поправки 2020-12-13 16:21:21 +07:00
Book Pauk
2dbe29d632 Merge tag '0.9.11' into develop
0.9.11
2020-12-09 22:31:37 +07:00
Book Pauk
7fa891b4fc Merge branch 'release/0.9.11' 2020-12-09 22:31:33 +07:00
Book Pauk
6cb7412cf3 Версия 0.9.11 2020-12-09 22:30:58 +07:00
Book Pauk
157322834b Небольшая поправка 2020-12-09 22:30:19 +07:00
Book Pauk
1a13a0fee1 Работа над конвертером pdf 2020-12-09 22:19:14 +07:00
Book Pauk
37256255bf Добавлена поддержка тегов 'sup' и 'sub' 2020-12-09 20:35:52 +07:00
Book Pauk
75e01c899e Работа над конвертером pdf 2020-12-09 20:08:17 +07:00
Book Pauk
ef0d6eab89 Работа над конвертером Pdf 2020-12-09 19:05:09 +07:00
Book Pauk
5d54b1b0f4 Работа над конвертером pdf 2020-12-09 03:52:24 +07:00
Book Pauk
522f953b4f Работа над конвертером pdf 2020-12-09 03:06:15 +07:00
Book Pauk
15f02c7115 Работа над конвертером pdf 2020-12-09 01:29:58 +07:00
Book Pauk
174c877eee Рефакторинг, плюс небольшие доработки 2020-12-09 01:29:09 +07:00
Book Pauk
fd9ec736d7 Рефакторинг 2020-12-08 19:36:53 +07:00
Book Pauk
2c94025ba3 Поправлен баг 2020-12-08 19:31:00 +07:00
Book Pauk
bfadf35c40 Закончена работа над xmlParser, оттестировано 2020-12-08 18:48:55 +07:00
Book Pauk
f3b69caa12 Работа над модулем xmlParser 2020-12-08 16:17:36 +07:00
Book Pauk
18a83a5b0b Поправки настроек сжатия 2020-12-08 14:26:49 +07:00
Book Pauk
bd9669b782 Поправка цели dev 2020-12-08 14:26:25 +07:00
Book Pauk
e05713aa7f Работа над конвертером pdf 2020-12-08 14:15:17 +07:00
Book Pauk
bc3e1f0a6f Мелкий рефакторинг 2020-12-07 22:13:14 +07:00
Book Pauk
063d01b5ca Перевод pdf-конвертера на использование pdfalto 2020-12-07 22:05:01 +07:00
Book Pauk
81c38d7749 Мелкий рефакторинг 2020-12-07 20:13:32 +07:00
Book Pauk
a29842b084 Поправка readme 2020-12-07 20:12:37 +07:00
Book Pauk
bb5adcdaf6 Рефакторинг 2020-12-07 01:30:10 +07:00
Book Pauk
537e17a219 Merge tag '0.9.10-5' into develop
0.9.10-5
2020-12-05 13:42:45 +07:00
Book Pauk
03ce50153e Merge branch 'release/0.9.10-5' 2020-12-05 13:42:39 +07:00
Book Pauk
15d01ad7fc Коррекция таймаутов очереди ожидания 2020-12-05 13:41:42 +07:00
Book Pauk
e2b29e2c2f Merge tag '0.9.10-4' into develop
0.9.10-4
2020-12-05 13:25:10 +07:00
Book Pauk
ce7ae84e0f Merge branch 'release/0.9.10-4' 2020-12-05 13:25:06 +07:00
Book Pauk
01eb545f15 Улучшение работы с очередью, поправка багов 2020-12-05 13:24:04 +07:00
Book Pauk
706738c7f1 Merge tag '0.9.10-3' into develop
0.9.10-3
2020-12-05 01:40:37 +07:00
Book Pauk
6afa78cde9 Merge branch 'release/0.9.10-3' 2020-12-05 01:40:30 +07:00
Book Pauk
71f5710bba Увеличен лимит количества файлов для распаковки 2020-12-05 01:12:29 +07:00
Book Pauk
0d87043f91 Поправлен неверный вызов reject 2020-12-05 01:11:31 +07:00
Book Pauk
e25375fb7a Поправка багов 2020-12-05 00:31:53 +07:00
Book Pauk
41822999c8 Небольшие поправки 2020-12-05 00:06:54 +07:00
Book Pauk
07444bc7c2 Добавлена подсказка в сообщение об ошибке 2020-12-04 23:25:34 +07:00
Book Pauk
ec48e5b0b7 Мелкая поправка 2020-12-04 20:14:53 +07:00
Book Pauk
e8e2e9297f Merge tag '0.9.10-2' into develop
0.9.10-2
2020-12-04 20:00:40 +07:00
Book Pauk
4f871dd5ca Merge branch 'release/0.9.10-2' 2020-12-04 20:00:35 +07:00
Book Pauk
f5f07a591a Небольшие доработки конвертирования 2020-12-04 20:00:05 +07:00
Book Pauk
4c11e6918f Merge tag '0.9.10-1' into develop
0.9.10-1
2020-12-04 18:38:02 +07:00
Book Pauk
403b9c0508 Merge branch 'release/0.9.10-1' 2020-12-04 18:37:58 +07:00
Book Pauk
ee8ba75371 Переделывание конвертера Djvu, теперь работает быстрее и без промежуточного конвертирования в pdf 2020-12-04 18:24:08 +07:00
Book Pauk
a2773fb180 Поправил readme 2020-12-04 18:23:57 +07:00
Book Pauk
ca36d588fc Merge tag '0.9.10' into develop
0.9.10
2020-12-03 22:05:47 +07:00
Book Pauk
1e65707b7f Merge branch 'release/0.9.10' 2020-12-03 22:05:42 +07:00
Book Pauk
eddf34ce55 Мелкая поправка 2020-12-03 22:04:47 +07:00
Book Pauk
0fb43aa33c Версия 0.9.10 2020-12-03 21:41:23 +07:00
Book Pauk
b273b02da4 Убрал "k" в статусбаре 2020-12-03 21:37:58 +07:00
Book Pauk
0b997f9673 Поправка приветственного сообщения 2020-12-03 21:37:27 +07:00
Book Pauk
bdb2ae57a8 Добавлена частичная поддержка формата Djvu 2020-12-03 21:30:50 +07:00
Book Pauk
b5e563679a Мелкая поправка 2020-12-03 20:00:22 +07:00
Book Pauk
992c104262 Удален ненужный файл 2020-12-03 19:57:27 +07:00
Book Pauk
555154031e Добавлен запрос persistent storage, увеличен размер кэша книг 2020-12-03 19:53:08 +07:00
Book Pauk
acb083e429 Поправлен readme 2020-12-03 19:30:27 +07:00
Book Pauk
4a527d192d Улучшено управление внешними конвертерами 2020-12-03 19:04:34 +07:00
Book Pauk
39c3bf17dd Добавлен пакет pidusage 2020-12-03 18:43:57 +07:00
Book Pauk
afc8c84f41 Мелкие поправки таймаутов 2020-12-03 12:39:16 +07:00
Book Pauk
a085e04c4d Поправки параметров запуска Rar 2020-12-03 10:24:47 +07:00
Book Pauk
2f82b0db34 Добавлена поддержка rar-архивов с помощью внешнего архиватора 2020-12-02 23:56:17 +07:00
Book Pauk
0124c2b17d Поправка цели dev 2020-12-02 23:55:23 +07:00
Book Pauk
d2cfbbc9f3 Поправлен триггер сокрытия диалога 2020-12-02 22:24:11 +07:00
Book Pauk
c59f48822c Префикс "file://" заменен на "disk://", т.к. порождается конфликт с браузерным протоколом file:// 2020-12-02 22:01:36 +07:00
Book Pauk
b2d6584c4a Merge tag '0.9.9-2' into develop
0.9.9-2
2020-11-24 04:53:10 +07:00
Book Pauk
8f7cafb240 Merge branch 'release/0.9.9-2' 2020-11-24 04:52:55 +07:00
Book Pauk
08fd0f15ff Улучшено распознавание параграфов чистого текста 2020-11-24 04:49:18 +07:00
Book Pauk
dbb1bfe587 Поправки распознавания кодировки fb2-файла 2020-11-24 02:09:17 +07:00
Book Pauk
fe4b7a5a85 Улучшено распознавание xml-формата 2020-11-23 23:49:20 +07:00
Book Pauk
d8df5d76e5 Поправка ридми 2020-11-23 21:57:52 +07:00
Book Pauk
b65dcc5ade Добавлен параметр -nodrm 2020-11-23 21:49:19 +07:00
Book Pauk
a5c387a19e Поправка строки запуска сервера 2020-11-23 21:35:45 +07:00
Book Pauk
07c38d9a9f Поправка для журналирования ошибок конвертера 2020-11-23 21:19:04 +07:00
Book Pauk
20ac8a444b Merge tag '0.9.9-1' into develop
0.9.9-1
2020-11-21 13:36:15 +07:00
Book Pauk
7b601c9c7f Merge branch 'release/0.9.9-1' 2020-11-21 13:36:11 +07:00
Book Pauk
8d2f74daa4 Небольшие доделки в связи с оптимизацией загрузки списка недавних 2020-11-21 13:34:58 +07:00
Book Pauk
01e82dca5f Merge tag '0.9.9' into develop
0.9.9
2020-11-21 04:06:54 +07:00
Book Pauk
094bb407ed Merge branch 'release/0.9.9' 2020-11-21 04:06:49 +07:00
Book Pauk
338baa55ec Версия 0.9.9 2020-11-21 04:06:16 +07:00
Book Pauk
d06d20a33e Оптимизация загрузки и хранения списка недавних книг 2020-11-21 04:03:54 +07:00
Book Pauk
d46ba6b92b Мелкий рефакторинг 2020-11-21 01:13:30 +07:00
Book Pauk
ec2639039d Поправка бага 2020-11-20 21:57:54 +07:00
Book Pauk
3a211ded2e Мелкий рефакторинг, добавлено удобочитаемое имя при сохраненнии fb2 2020-11-20 21:43:37 +07:00
Book Pauk
c2131e3654 Доработки парсинга fb2 - распознаем тег sequence 2020-11-20 21:26:16 +07:00
Book Pauk
594fb59395 Убрал лишнее 2020-11-20 20:45:47 +07:00
Book Pauk
f44378ec84 Рефакторинг 2020-11-20 18:25:24 +07:00
Book Pauk
0f6b366f62 Расширил лог 2020-11-20 16:17:54 +07:00
Book Pauk
8d0a5997ee Небольшая поправка 2020-11-20 16:06:23 +07:00
Book Pauk
347cb3417e Поправки названий и справки 2020-11-19 15:20:40 +07:00
Book Pauk
337fcebd10 Merge tag '0.9.8-5' into develop
0.9.8-5
2020-11-15 14:57:27 +07:00
Book Pauk
e057b130e9 Merge branch 'release/0.9.8-5' 2020-11-15 14:57:23 +07:00
Book Pauk
19a0765a1a Рефакторинг, плюс небольшие доделки 2020-11-15 14:55:02 +07:00
Book Pauk
b81cd3240b Merge tag '0.9.8-4' into develop
0.9.8-4
2020-11-15 00:09:10 +07:00
Book Pauk
1e6105b076 Merge branch 'release/0.9.8-4' 2020-11-15 00:09:03 +07:00
Book Pauk
d8d89b3463 Добавлен показ аннотации в начале книги 2020-11-15 00:07:45 +07:00
Book Pauk
459564cb2d Рефакторинг 2020-11-14 23:43:22 +07:00
Book Pauk
084df35184 Мелкий рефакторинг 2020-11-14 23:32:39 +07:00
Book Pauk
81912babeb Merge tag '0.9.8-3' into develop
0.9.8-3
2020-11-14 23:27:14 +07:00
Book Pauk
3943fc7d95 Merge branch 'release/0.9.8-3' 2020-11-14 23:27:09 +07:00
Book Pauk
3999dc930c Рефакторинг, плюс небольшие изменения внешнего вида 2020-11-14 23:24:20 +07:00
Book Pauk
d4dea16456 Убрал неиспользуемые компоненты 2020-11-14 23:23:51 +07:00
Book Pauk
ed38cb33a5 Merge tag '0.9.8-2' into develop
0.9.8-2
2020-11-14 12:01:44 +07:00
Book Pauk
4cf5a0f4c8 Merge branch 'release/0.9.8-2' 2020-11-14 12:01:39 +07:00
Book Pauk
8f0d526af2 Скрыл хоткей для "Библиотека" в режиме omnireader 2020-11-14 12:00:55 +07:00
Book Pauk
6ca3881841 Merge tag '0.9.8-1' into develop
0.9.8-1
2020-11-14 11:49:06 +07:00
Book Pauk
d8e765a04f Merge branch 'release/0.9.8-1' 2020-11-14 11:49:02 +07:00
Book Pauk
40ff572f94 Добавил проксирование для fantasy-worlds.org 2020-11-14 11:48:03 +07:00
Book Pauk
cc4275dc03 Merge tag '0.9.8' into develop
0.9.8
2020-11-14 00:05:14 +07:00
Book Pauk
b387f4a0db Merge branch 'release/0.9.8' 2020-11-14 00:05:07 +07:00
Book Pauk
1a096031c4 Версия 0.9.8 2020-11-14 00:04:30 +07:00
Book Pauk
83a60b4091 Небольшие поправки парсера 2020-11-13 23:59:55 +07:00
Book Pauk
b292407ec2 Работа над ContentsPage 2020-11-13 23:33:36 +07:00
Book Pauk
952c337b76 Работа над ContentsPage 2020-11-13 22:45:34 +07:00
Book Pauk
e947b887fe Работа над ContentsPage 2020-11-13 19:10:15 +07:00
Book Pauk
bd1e5485d7 Работа над ContentsPage 2020-11-13 18:47:11 +07:00
Book Pauk
e095c3318b Начало работы над ContentsPage 2020-11-13 14:23:55 +07:00
Book Pauk
d75a08b519 Merge tag '0.9.7' into develop
0.9.7
2020-11-12 19:44:15 +07:00
Book Pauk
d55a616fe0 Merge branch 'release/0.9.7' 2020-11-12 19:44:08 +07:00
Book Pauk
2146cb3576 Версия 0.9.7 2020-11-12 19:43:33 +07:00
Book Pauk
ae260e74f6 Поправка механизма чистки TempPublicDir 2020-11-12 19:39:57 +07:00
Book Pauk
355410c03c Исправление бага - не изменялись хоткеи в настройках 2020-11-12 19:13:24 +07:00
Book Pauk
718ad51fac Merge tag '0.9.6-1' into develop
0.9.6-1
2020-11-07 20:57:02 +07:00
Book Pauk
4242a8679f Merge branch 'release/0.9.6-1' 2020-11-07 20:56:57 +07:00
Book Pauk
4ff9ff699b Добавил ссылку на flibs.in в предустановленные закладки 2020-11-07 20:56:07 +07:00
Book Pauk
7a76673274 Merge tag '0.9.6' into develop
0.9.6
2020-11-06 18:00:31 +07:00
Book Pauk
bd03ca5136 Merge branch 'release/0.9.6' 2020-11-06 18:00:22 +07:00
Book Pauk
b3e1e4b909 Версия 0.9.6 2020-11-06 17:59:54 +07:00
Book Pauk
4bb22183df Добавлен диалог с сообщением о новой читалке 2020-11-06 17:57:16 +07:00
Book Pauk
e72f8f4245 Мелкий рефакторинг 2020-11-06 17:36:05 +07:00
Book Pauk
5b52f48bce Поправка для автоматического отображения справки 2020-11-06 16:50:17 +07:00
Book Pauk
f07a157a2a Поправка бага при resize 2020-11-06 01:08:22 +07:00
Book Pauk
ca40854106 Рефакторинг, версия 0.9.6 2020-11-05 22:21:52 +07:00
Book Pauk
d6a8209b31 Работа над ExternalLibs 2020-11-05 21:52:34 +07:00
Book Pauk
731e1f1f15 Мелкая поправка 2020-11-05 20:18:23 +07:00
Book Pauk
b4a2a8fb98 Merge tag '0.9.5-2' into develop
0.9.5-2
2020-11-05 16:58:53 +07:00
Book Pauk
5a3e4ee5ca Merge branch 'release/0.9.5-2' 2020-11-05 16:58:37 +07:00
Book Pauk
ab2cf0aeec Работа над ExternalLibs 2020-11-05 16:56:27 +07:00
Book Pauk
9de6a02b30 Поправка безопасности 2020-11-05 16:42:00 +07:00
Book Pauk
9fb7892bfe Работа над ExternalLibs 2020-11-05 16:40:33 +07:00
Book Pauk
546f4556f6 Мелкий рефакторинг 2020-11-05 15:43:15 +07:00
Book Pauk
471de104bc Работа над ExternalLibs 2020-11-05 15:36:58 +07:00
Book Pauk
d30be1536d Работа над BookmarkSettings 2020-11-05 15:06:36 +07:00
Book Pauk
6c0678ed61 Рефакторинг 2020-11-05 14:08:15 +07:00
Book Pauk
4883b8a190 Работа над ExternalLibs 2020-11-05 03:21:54 +07:00
Book Pauk
14742ed4ad Работа над BookmarkSettings 2020-11-04 22:56:28 +07:00
Book Pauk
1d8bd56862 Поправки багов 2020-11-04 22:23:39 +07:00
Book Pauk
94b8f9fe1c Работа над BookmarkSettings 2020-11-04 19:06:11 +07:00
Book Pauk
27412211a5 Работа над BookmarkSettings 2020-11-04 18:53:14 +07:00
Book Pauk
f8c4960079 Работа над BookmarkSettings 2020-11-04 14:43:06 +07:00
Book Pauk
b2e0bcf995 Улучшение безопасности отображения списка 2020-11-04 14:42:35 +07:00
Book Pauk
fcf6639d38 Небольшие поправки обработки клавиш 2020-11-04 14:41:38 +07:00
Book Pauk
d540cb91a9 Добавлен пакет vue-sanitize 2020-11-04 14:41:11 +07:00
Book Pauk
f69cc6f1b1 Работа над BookmarkSettings 2020-11-03 23:04:06 +07:00
Book Pauk
607f2ff407 Работа над BookmarkSettings 2020-11-03 22:24:32 +07:00
Book Pauk
ba6bf8c091 Работа над BookmarkSettings 2020-11-03 17:03:57 +07:00
Book Pauk
7e4c938dfd Поправлен небольшой баг отображения окна 2020-11-03 14:42:02 +07:00
Book Pauk
7f36d55320 Мелкая поправка 2020-11-03 14:41:32 +07:00
Book Pauk
d9634a134c Обновление caniuse -lite 2020-11-03 00:35:44 +07:00
Book Pauk
4f8868d4b1 Merge tag '0.9.5-1' into develop
0.9.5-1
2020-11-03 00:29:22 +07:00
Book Pauk
956546585c Merge branch 'release/0.9.5-1' 2020-11-03 00:29:12 +07:00
Book Pauk
3ca0a92442 Временно отключил кнопку "Настроить закладки" 2020-11-03 00:28:15 +07:00
Book Pauk
213f7e48c9 Работа над BookmarkSettings 2020-11-03 00:26:25 +07:00
Book Pauk
8b66fd522d Добавлено восстановление дефолтных настроек в объекте settings при их отсутствии 2020-11-03 00:00:31 +07:00
Book Pauk
fdf5009999 Мелкая поправка 2020-11-02 23:51:41 +07:00
Book Pauk
bbdba0ef16 Поправки по результату тестирования 2020-11-02 23:45:59 +07:00
Book Pauk
a63602df7a Поправки багов 2020-11-02 14:33:15 +07:00
Book Pauk
587120f984 Merge tag '0.9.5' into develop
0.9.5
2020-11-01 19:55:53 +07:00
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
c4a06858fb Merge branch 'release/0.8.2-2' 2020-01-21 16:56:12 +07:00
Book Pauk
15b0f05a05 Добавил комментарий 2020-01-21 16:55:41 +07:00
Book Pauk
67feee9aa1 Поправлен баг 2020-01-21 16:53:34 +07:00
Book Pauk
185fb57b8c Удален нерабочий код 2020-01-21 16:25:30 +07:00
Book Pauk
e9039f8208 Merge tag '0.8.2-1' into develop
0.8.2-1
2020-01-21 16:14:21 +07:00
Book Pauk
440d1b3ba0 Merge branch 'release/0.8.2-1' 2020-01-21 16:14:15 +07:00
Book Pauk
9c7a6c64b0 Небольшие поправки 2020-01-21 16:13:38 +07:00
Book Pauk
7cc63fe849 Добавлена автоматическая отправка загруженной книги удаленное хранилище 2020-01-21 15:53:23 +07:00
Book Pauk
5647e8219d Мелкий рефакторинг 2020-01-21 14:58:42 +07:00
Book Pauk
81629fab7a Замена webdav-fs на webdav 2020-01-21 13:54:21 +07:00
Book Pauk
992d2033f3 Merge tag '0.8.2' into develop
0.8.2
2020-01-20 21:49:08 +07:00
Book Pauk
d52d4a1278 Merge branch 'release/0.8.2' 2020-01-20 21:49:00 +07:00
Book Pauk
57a44c5952 Версия 0.8.2 2020-01-20 21:48:31 +07:00
Book Pauk
a04161ac7c Добавил принудительную загрузку книги в обход кэша, если указан URL 2020-01-20 21:44:09 +07:00
Book Pauk
47e46f13c3 Добавлен работа с RemoteWebDavStorage, в т.ч. через api 2020-01-20 21:39:55 +07:00
Book Pauk
5535bd91c8 В конфиг добавлена опция remoteWebDavStorage 2020-01-20 21:37:31 +07:00
Book Pauk
8747a00de6 Поправлен баг 2020-01-20 21:36:44 +07:00
Book Pauk
c926b86926 Добавлен пакет webdav-fs 2020-01-20 21:22:27 +07:00
Book Pauk
010ac9aa7c Доработка api, восстановление кэшированного файла из хранилища 2020-01-20 21:21:13 +07:00
Book Pauk
4ab0c337f1 Рефакторинг 2020-01-15 16:20:46 +07:00
Book Pauk
f814c42fdd Поправлен баг в getStateFinish 2020-01-15 16:06:28 +07:00
Book Pauk
02aee3e625 Добавлена переупаковка файла книги по максимуму через 5 сек после загрузки и конвертирования 2020-01-15 15:49:45 +07:00
Book Pauk
52a32cfdd1 Добавлена обработка ошибок JSON.parse 2020-01-12 20:06:50 +07:00
Book Pauk
6faa7b2efe Уменьшение запросов get-state к api, добавлен метод get-state-finish 2020-01-12 18:51:12 +07:00
Book Pauk
f8481413c9 Мелкий рефакторинг 2020-01-12 17:03:34 +07:00
Book Pauk
7d4baa7046 Поправил цвет github corner 2020-01-11 01:01:18 +07:00
Book Pauk
0951d01383 Merge tag '0.8.1-1' into develop
0.8.1-1
2020-01-10 21:47:58 +07:00
Book Pauk
da34472a6f Merge branch 'release/0.8.1-1' 2020-01-10 21:47:50 +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
Book Pauk
e89b6e3ea0 Добавлен компонент GithubCorner на LoaderPage 2020-01-09 20:15:32 +07:00
Book Pauk
977bab4745 0.8.1 2020-01-09 20:14:49 +07:00
Book Pauk
26c73109fe Небольшая поправка 2020-01-08 13:46:24 +07:00
Book Pauk
65f911ad51 Поправил CRLF => LF 2020-01-07 23:12:06 +07:00
Book Pauk
f8ed5ebd6a Merge tag '0.8.1' into develop
0.8.1
2020-01-07 22:57:38 +07:00
Book Pauk
e4cb61bebe Merge branch 'release/0.8.1' 2020-01-07 22:57:30 +07:00
Book Pauk
7d5310af42 Версия 0.8.1 2020-01-07 22:57:06 +07:00
Book Pauk
f68c610c0d Добавлена поддержка формата FB3 "для галочки" 2020-01-07 22:52:28 +07:00
Book Pauk
ccfb6a6d73 Исправление бага "Request path contains unescaped characters" 2020-01-07 20:31:52 +07:00
Book Pauk
da55996e22 Удалил неиспользуемые пакеты 2020-01-05 15:28:35 +07:00
Book Pauk
ecd8400a34 Удалил неиспользуемые пакеты 2020-01-05 15:15:55 +07:00
Book Pauk
03914883bc Поправил readme 2020-01-05 13:37:09 +07:00
Book Pauk
9981e1f3bd Мелкие поправки 2020-01-03 17:06:03 +07:00
Book Pauk
4d1df66025 Небольшой рефакторинг 2020-01-03 16:58:37 +07:00
Book Pauk
a0f64e188b Поправил readme 2020-01-03 15:59:00 +07:00
Book Pauk
08407a1094 Merge tag '0.8.0-1' into develop
0.8.0-1
2020-01-02 23:45:55 +07:00
Book Pauk
445ea3bb2e Merge branch 'release/0.8.0-1' 2020-01-02 23:45:46 +07:00
Book Pauk
0e0aab98b1 Поправил историю версий 2020-01-02 23:45:16 +07:00
Book Pauk
721d5eb0c1 Поправил readme 2020-01-02 23:45:03 +07:00
Book Pauk
6d99dbc3a7 Мелкая поправка 2020-01-02 22:49:31 +07:00
Book Pauk
2be31f649b Merge tag '0.8.0' into develop
0.8.0
2020-01-02 22:26:15 +07:00
Book Pauk
828ac27c03 Merge branch 'release/0.8.0' 2020-01-02 22:26:06 +07:00
Book Pauk
b3d614002f Поправил readme 2020-01-02 22:25:41 +07:00
Book Pauk
2b2000ca10 Поправил readme 2020-01-02 22:12:43 +07:00
Book Pauk
8d7428d099 Избавление от предупреждений npm 2020-01-02 20:03:20 +07:00
Book Pauk
57f8322f31 Поправил описание 2020-01-02 19:47:07 +07:00
Book Pauk
bee7bc4294 0.8.0 2020-01-02 19:45:13 +07:00
Book Pauk
28702065bc Добавил repository 2020-01-02 19:43:49 +07:00
Book Pauk
c248057081 Добавлен файл лицензии 2020-01-02 19:33:15 +07:00
Book Pauk
6186f5e138 Версия 0.8.0 2020-01-02 19:32:56 +07:00
Book Pauk
2201d8176d Удалена yandex-метрика 2020-01-01 14:55:29 +07:00
Book Pauk
2ba6819876 Окончательный переход на https 2020-01-01 14:48:32 +07:00
Book Pauk
a393b2a370 Окончательный переход на https 2020-01-01 14:34:54 +07:00
Book Pauk
59fe713df2 Немного улучшил загрузку веб-шрифтов 2019-12-25 01:29:32 +07:00
Book Pauk
4b8efaca9a Merge tag '0.7.9-3' into develop
0.7.9-3
2019-12-24 23:47:32 +07:00
Book Pauk
a26100a8d0 Merge branch 'release/0.7.9-3' 2019-12-24 23:47:21 +07:00
Book Pauk
8c52f4718c Добавил require('tls').DEFAULT_MIN_VERSION = 'TLSv1' 2019-12-24 23:45:04 +07:00
Book Pauk
85b5c3c4ec Merge tag '0.7.9-2' into develop
0.7.9-2
2019-12-23 22:25:47 +07:00
Book Pauk
4fd559e4c7 Merge branch 'release/0.7.9-2' 2019-12-23 22:25:40 +07:00
Book Pauk
a337d0ddc7 Попытка обновить pkg, версия node 10.4.1 глючит с setTimeout 2019-12-23 22:24:36 +07:00
Book Pauk
9e4cb7071e Merge tag '0.7.9-1' into develop
0.7.9-1
2019-12-23 21:02:11 +07:00
Book Pauk
c3f1707343 Merge branch 'release/0.7.9-1' 2019-12-23 21:02:02 +07:00
Book Pauk
1ed058a553 Мелкие поправки 2019-12-23 20:57:44 +07:00
Book Pauk
0500a8178d Merge tag '0.7.9' into develop
0.7.9
2019-11-27 18:16:52 +07:00
Book Pauk
7d0059f573 Merge branch 'release/0.7.9' 2019-11-27 18:16:43 +07:00
Book Pauk
4e3b882362 Версия 0.7.9 2019-11-27 18:15:28 +07:00
Book Pauk
13cf47873e Добавлен неубираемый баннер о переходе на httpS 2019-11-27 18:11:34 +07:00
Book Pauk
7ee23ec38f Удален устаревший код 2019-11-27 17:33:30 +07:00
Book Pauk
eebf17c42c Добавлена проверка наличия файла на сервере перед скачиванием fb2 2019-11-27 17:12:07 +07:00
Book Pauk
f84536788b Merge tag '0.7.8b' into develop
0.7.8b
2019-11-25 16:00:10 +07:00
Book Pauk
4bbfdc2cb2 Merge branch 'release/0.7.8b' 2019-11-25 16:00:00 +07:00
Book Pauk
211fec35e3 Исправлен баг 2019-11-25 15:56:29 +07:00
Book Pauk
b8214a46ae Merge tag '0.7.8' into develop
0.7.8
2019-11-25 15:39:35 +07:00
Book Pauk
549ef91c81 Merge branch 'release/0.7.8' 2019-11-25 15:39:23 +07:00
Book Pauk
cede65313b Версия 0.7.8 2019-11-25 15:38:52 +07:00
Book Pauk
d897a7400f Улучшение парсера fb2 2019-11-25 15:36:34 +07:00
Book Pauk
47f059213f Добавлен конвертер для flibusta 2019-11-25 15:21:33 +07:00
Book Pauk
8af51bbf08 Улучшение фильтра html 2019-11-25 15:15:06 +07:00
Book Pauk
53d9f5ddc6 Улучшение конвертирования html->fb2 2019-11-24 15:36:11 +07:00
Book Pauk
06fffdccc8 Merge tag '0.7.7d' into develop
0.7.7d
2019-11-18 20:04:03 +07:00
Book Pauk
aa13dc68fc Merge branch 'release/0.7.7d' 2019-11-18 20:03:46 +07:00
Book Pauk
813876dd90 Поправлены мета-теги 2019-11-18 20:03:09 +07:00
Book Pauk
596c7d65c5 Merge tag '0.7.7c' into develop
0.7.7c
2019-11-16 18:05:56 +07:00
Book Pauk
ce8dcb75bf Merge branch 'release/0.7.7c' 2019-11-16 18:05:40 +07:00
Book Pauk
1bd51b5565 Поправил robots.txt 2019-11-16 18:04:48 +07:00
Book Pauk
1f9ec305b4 Merge tag '0.7.7b' into develop
0.7.7b
2019-11-13 19:42:30 +07:00
Book Pauk
be0f6e57d7 Merge branch 'release/0.7.7b' 2019-11-13 19:42:21 +07:00
Book Pauk
b268e9ee74 Улучшение парсинга html 2019-11-13 19:41:20 +07:00
Book Pauk
e97774435b Merge tag '0.7.7a' into develop
0.7.7a
2019-11-08 17:21:17 +07:00
Book Pauk
93586bc5bb Merge branch 'release/0.7.7a' 2019-11-08 17:21:03 +07:00
Book Pauk
fe23089714 Небольшие поправки верстки 2019-11-08 17:20:31 +07:00
Book Pauk
e743986f38 Merge tag '0.7.7' into develop
0.7.7
2019-11-06 20:18:02 +07:00
Book Pauk
a6c9b700ed Merge branch 'release/0.7.7' 2019-11-06 20:17:42 +07:00
Book Pauk
afa3fcb524 Merge branch 'feature/lss' into develop 2019-11-06 20:17:25 +07:00
Book Pauk
b9aeb648d6 Версия 0.7.7 2019-11-06 20:16:56 +07:00
Book Pauk
5f5df1e5b7 Добавлены жесты для тачскрина 2019-11-06 20:02:21 +07:00
Book Pauk
ad885679e4 Merge branch 'develop' into feature/lss 2019-11-03 18:06:19 +07:00
Book Pauk
e002bebfbe Merge tag '0.7.6c' into develop
0.7.6c
2019-11-03 18:05:52 +07:00
Book Pauk
a8a41e2b3d Merge branch 'release/0.7.6c' 2019-11-03 18:05:42 +07:00
Book Pauk
31940caa84 Поправки логирования 2019-11-03 18:05:02 +07:00
Book Pauk
880334054e Merge tag '0.7.6b' into develop
0.7.6b
2019-11-03 17:56:44 +07:00
Book Pauk
5f03ad5597 Merge branch 'release/0.7.6b' 2019-11-03 17:56:33 +07:00
Book Pauk
1efa3f055d Merge branch 'feature/lss' into develop 2019-11-03 17:56:10 +07:00
Book Pauk
8ccf11278b Поправки логирования 2019-11-03 17:55:43 +07:00
Book Pauk
8a9e7ab4c3 Merge branch 'develop' into feature/lss 2019-11-03 17:06:04 +07:00
Book Pauk
c0fa7c0c51 Merge tag '0.7.6a' into develop
0.7.6a
2019-11-03 17:04:50 +07:00
Book Pauk
022dfd4709 Merge branch 'release/0.7.6a' 2019-11-03 17:04:39 +07:00
Book Pauk
71e08aacc3 Поправки багов 2019-11-03 17:03:58 +07:00
Book Pauk
337eca87f2 Merge tag '0.7.6' into develop
0.7.6
2019-10-30 17:37:19 +07:00
Book Pauk
074aceff8f Merge branch 'release/0.7.6' 2019-10-30 17:37:10 +07:00
Book Pauk
cdc6cf229a Версия 0.7.6 2019-10-30 17:36:46 +07:00
Book Pauk
1f33513dc9 Merge branch 'feature/lss' into develop 2019-10-30 17:35:18 +07:00
Book Pauk
b095b91ff2 Поправки багов 2019-10-30 17:33:13 +07:00
Book Pauk
454a62dbb9 Работа над MegaStorage 2019-10-29 21:03:56 +07:00
Book Pauk
5f7cc12157 Поправка путей 2019-10-29 19:07:34 +07:00
Book Pauk
97ef1ee201 Рефакторинг 2019-10-29 19:05:21 +07:00
Book Pauk
a318568b72 Рефакторинг конфига 2019-10-29 18:58:40 +07:00
Book Pauk
5bb9949440 Изменение путей к модулям 2019-10-29 14:45:55 +07:00
Book Pauk
c33e91d5d0 Рефакторинг, преобразование классов в синглтоны 2019-10-29 14:37:05 +07:00
Book Pauk
ca65ef3cb7 ReaderWorker теперь синглтон 2019-10-28 23:33:10 +07:00
Book Pauk
9ebdbc81d0 Рефакторинг 2019-10-28 23:30:02 +07:00
Book Pauk
b64985349e Рефакторинг 2019-10-28 22:49:02 +07:00
Book Pauk
625fd9d1a4 Поправки 2019-10-28 21:17:52 +07:00
Book Pauk
eac5fdcec0 Рефакторинг, поправки 2019-10-28 21:14:50 +07:00
Book Pauk
970b4d5d97 Мелкий рефакторинг 2019-10-28 20:52:04 +07:00
Book Pauk
f741bc818d Добавлен метод readFiles 2019-10-28 20:31:06 +07:00
Book Pauk
5f04c24187 Убрал adm-zip 2019-10-28 16:04:09 +07:00
Book Pauk
a382bef336 Замена AdmZip на ZipStreamer 2019-10-28 16:02:44 +07:00
Book Pauk
4ddf28f344 Добавлен метод unpack 2019-10-28 15:55:26 +07:00
Book Pauk
0dc650305a Поправки потенциальных багов, мелкий рефакторинг 2019-10-27 19:36:50 +07:00
Book Pauk
697093d1c9 Поправка бага 2019-10-27 19:27:26 +07:00
Book Pauk
622f7a4479 Исправление бага 2019-10-27 19:24:18 +07:00
Book Pauk
c4b607804b Заготовка LibSharedStorage 2019-10-27 19:19:20 +07:00
Book Pauk
864f008679 Работа над MegaStorage 2019-10-27 19:18:37 +07:00
Book Pauk
25f309bcb0 Добавлены модули node-stream-zip, zip-stream 2019-10-27 17:57:21 +07:00
Book Pauk
1354361ad9 Убрал дебаг 2019-10-27 17:55:54 +07:00
Book Pauk
8136c7b072 Начало работы над LibSharedStorage 2019-10-25 21:18:09 +07:00
Book Pauk
c9243e7249 Поправка конфигов 2019-10-25 21:17:50 +07:00
Book Pauk
1a487da3d9 Небольшой рефакторинг 2019-10-25 21:16:22 +07:00
Book Pauk
b52395751c Поправил размер кнопок 2019-10-25 13:37:20 +07:00
Book Pauk
cfa6cc9a83 Merge tag '0.7.5a' into develop
0.7.5a
2019-10-23 23:58:03 +07:00
Book Pauk
f203384b00 Merge branch 'release/0.7.5a' 2019-10-23 23:57:51 +07:00
Book Pauk
9ac3be455c Поправки багов 2019-10-23 23:53:37 +07:00
Book Pauk
20b74a9dcd Небольшие поправки 2019-10-23 20:42:14 +07:00
Book Pauk
3b848a5a86 Исправления конфига nginx 2019-10-23 20:07:38 +07:00
Book Pauk
a9b5e865a5 Merge tag '0.7.5b' into develop
0.7.5b
2019-10-22 18:24:20 +07:00
Book Pauk
ab46a1b99d Merge branch 'release/0.7.5b' 2019-10-22 18:24:06 +07:00
Book Pauk
4a08465f5b Поправка бага 2019-10-22 18:23:17 +07:00
Book Pauk
a7960d6cd6 Merge tag '0.7.5' into develop
0.7.5
2019-10-22 17:59:15 +07:00
Book Pauk
3caea77dde Merge branch 'release/0.7.5' 2019-10-22 17:59:05 +07:00
Book Pauk
fdaa3b7f93 Версия 0.7.5 2019-10-22 17:58:20 +07:00
Book Pauk
4f433b4456 Поправил описание 2019-10-22 17:56:17 +07:00
Book Pauk
309a9ad4fb Улучшение синхронизации 2019-10-22 15:41:14 +07:00
Book Pauk
b0e7431e72 Поправки синхронизации 2019-10-21 22:03:00 +07:00
Book Pauk
158118d183 Мелкая поправка 2019-10-21 21:36:43 +07:00
Book Pauk
382e37fc5a Merge tag '0.7.4b' into develop
0.7.4b
2019-10-21 20:13:21 +07:00
Book Pauk
3390676847 Merge branch 'release/0.7.4b' 2019-10-21 20:13:10 +07:00
Book Pauk
544a995312 Поправки багов синхронизации 2019-10-21 20:07:42 +07:00
Book Pauk
f209d49bb5 Поправил настройки кеширования 2019-10-21 13:07:16 +07:00
Book Pauk
42ed691fdc Merge tag '0.7.4' into develop
0.7.4
2019-10-21 12:10:45 +07:00
Book Pauk
98d2e9d266 Merge branch 'release/0.7.4' 2019-10-21 12:10:35 +07:00
Book Pauk
6111158896 Версия 0.7.4 2019-10-21 12:10:13 +07:00
Book Pauk
3267fc653c Поправил тему 2019-10-21 12:08:15 +07:00
Book Pauk
7250608767 Временно переключил Vue на ветку github:paulkamer/vue#fix_palemoon_clickhandlers_dist
до выхода нового релиза Vue с исправлениями
2019-10-21 12:03:11 +07:00
Book Pauk
e82063e435 Изменение темы, css подключается целиком 2019-10-21 11:31:41 +07:00
Book Pauk
6d4c44bc25 Обновлена тема element-ui 2019-10-21 11:27:04 +07:00
Book Pauk
2bc94d8792 Улучшил сообщение о версиях продукта 2019-10-21 09:23:49 +07:00
Book Pauk
4ca3edd789 Поправил текст 2019-10-21 09:23:29 +07:00
Book Pauk
d6859efde2 Добавлено использование reader-api от новой читалки для конвертации книг 2019-10-21 08:38:56 +07:00
Book Pauk
3f8cbfa259 Поправка бага 2019-10-20 18:22:21 +07:00
Book Pauk
5d18c9371d Мелкая поправка 2019-10-20 18:09:24 +07:00
Book Pauk
631990e3bb Merge tag '0.7.3b' into develop
0.7.3b
2019-10-19 19:28:51 +07:00
Book Pauk
4ae7338f94 Merge branch 'release/0.7.3b' 2019-10-19 19:28:36 +07:00
Book Pauk
0d1e51cb21 Поправка бага 2019-10-19 19:27:18 +07:00
Book Pauk
475fb833ea Merge tag '0.7.3' into develop
0.7.3
2019-10-18 19:30:51 +07:00
Book Pauk
580b030ee4 Merge branch 'release/0.7.3' 2019-10-18 19:30:42 +07:00
Book Pauk
6a7cbc70d6 0.7.3 2019-10-18 19:30:21 +07:00
Book Pauk
d76f60639c Добавлен параметр "Включить html-фильтр для сайтов" в раздел "Вид"->"Текст" в настройках 2019-10-18 19:13:17 +07:00
Book Pauk
e2bea407ee 0.7.3 2019-10-18 17:12:30 +07:00
Book Pauk
558fed31aa Добавлен фильтр для некоторых сайтов 2019-10-18 17:07:28 +07:00
Book Pauk
f6513d40c8 Небольшая поправка 2019-10-18 15:32:05 +07:00
Book Pauk
259f9baa59 Merge branch 'feature/ss_fix2' into develop 2019-10-18 15:25:57 +07:00
Book Pauk
db5650e276 Мелкая поправка 2019-10-18 15:20:32 +07:00
Book Pauk
51ebbbc569 Закончена очередная переработка loadRecent & saveRecent 2019-10-18 15:19:53 +07:00
Book Pauk
5184661652 Мелкая поправка 2019-10-18 14:00:52 +07:00
Book Pauk
7853a14ce6 Начало очередной переделки ServerStorage 2019-10-18 13:53:44 +07:00
Book Pauk
a01e78ace9 Merge tag '0.7.2' into develop
0.7.2
2019-10-18 09:17:58 +07:00
Book Pauk
f7eb576d0d Merge branch 'release/0.7.2' 2019-10-18 09:17:45 +07:00
Book Pauk
34f1ad8fae Версия 0.7.2 2019-10-18 09:16:52 +07:00
Book Pauk
c60f0991df Правка багов 2019-10-18 09:06:04 +07:00
Book Pauk
d505fd0795 Мелкая поправка 2019-10-17 21:38:13 +07:00
Book Pauk
93cf506535 Поправка бага 2019-10-17 20:38:52 +07:00
Book Pauk
bfb37e55d4 Поправка отображения прогрессбара загрузки книги 2019-10-17 19:18:56 +07:00
Book Pauk
92afc5cb33 Поправки багов 2019-10-17 19:09:05 +07:00
Book Pauk
75cb611701 Поправки багов 2019-10-17 17:20:43 +07:00
Book Pauk
2ec1dd58a5 Добавлена ссылка "Старая версия" 2019-10-17 13:49:24 +07:00
Book Pauk
7d59af54de Улучшил определение кодировки 2019-10-16 21:48:41 +07:00
Book Pauk
2b5f47b3de Замена decompress-zip на adm-zip 2019-10-16 21:08:34 +07:00
Book Pauk
16eebfb9a4 Поправил текст ссылки 2019-10-16 16:41:26 +07:00
Book Pauk
9025218671 Мелкая поправка 2019-10-16 11:19:40 +07:00
Book Pauk
6bccb546bb Merge tag '0.7.1e' into develop
0.7.1e
2019-09-21 11:29:57 +07:00
Book Pauk
29d49046a0 Merge branch 'release/0.7.1e' 2019-09-21 11:29:46 +07:00
Book Pauk
717af9ffaf Изменил местоположение кнопки "Автономный режим" 2019-09-21 11:28:31 +07:00
Book Pauk
00060c9f43 Merge tag '0.7.1d' into develop
0.7.1d
2019-09-21 00:25:17 +07:00
Book Pauk
759ff46c92 Merge branch 'release/0.7.1d' 2019-09-21 00:25:08 +07:00
Book Pauk
41957cdceb Актуализирована справка, доделки 2019-09-21 00:24:27 +07:00
Book Pauk
d418e3a1c9 Merge tag '0.7.1c' into develop
0.7.1c
2019-09-20 23:54:50 +07:00
Book Pauk
f650124428 Merge branch 'release/0.7.1c' 2019-09-20 23:54:38 +07:00
Book Pauk
795d109c76 Поправил описание 2019-09-20 23:54:02 +07:00
Book Pauk
6868b3effc Добавлена кнопка offlineMode 2019-09-20 23:52:45 +07:00
Book Pauk
26747b7013 Небольшие поправки 2019-09-20 22:44:36 +07:00
Book Pauk
5198f8aa60 Merge tag '0.7.1b' into develop
0.7.1b
2019-09-20 22:18:09 +07:00
Book Pauk
552da48a32 Merge branch 'release/0.7.1b' 2019-09-20 22:17:58 +07:00
Book Pauk
db8a688620 Манипуляции с appcache 2019-09-20 22:17:28 +07:00
Book Pauk
3088028d05 Поправки багов 2019-09-20 21:45:29 +07:00
Book Pauk
fd62ef865d Merge tag '0.7.1a' into develop
0.7.1a
2019-09-20 20:37:33 +07:00
Book Pauk
ed74ed00ed Merge branch 'release/0.7.1a' 2019-09-20 20:37:23 +07:00
Book Pauk
741317aaaf К предыдущему 2019-09-20 20:36:31 +07:00
Book Pauk
9b6ecd4e6b Убрал вычисление диффа 2019-09-20 20:35:12 +07:00
Book Pauk
7863b3358e Убрал appcache 2019-09-20 20:34:42 +07:00
Book Pauk
e1be68ec3d Поправка бага 2019-09-20 20:20:11 +07:00
Book Pauk
a054186d4b Merge tag '0.7.1' into develop
Версия 0.7.1
2019-09-20 19:58:36 +07:00
Book Pauk
2d5c549c83 Merge branch 'release/0.7.1' 2019-09-20 19:58:22 +07:00
Book Pauk
9f6072dfe1 Версия 0.7.1 2019-09-20 19:54:59 +07:00
Book Pauk
69c44fe1ab Откатил новые версии pkg и sqlite, новый pkg глючит 2019-09-20 19:53:55 +07:00
Book Pauk
4fa7b2443e Добавил дебаг-лог в periodicCleanDir 2019-09-20 19:35:22 +07:00
Book Pauk
25a69592bb Правка багов 2019-09-20 19:14:14 +07:00
Book Pauk
44e0b26990 Поправка бага 2019-09-20 18:58:36 +07:00
Book Pauk
c4496f8dc8 Улучшение механизма синхронизации 2019-09-20 18:53:21 +07:00
Book Pauk
9e296231d9 Поправки багов 2019-09-20 16:54:03 +07:00
Book Pauk
49b3f05d65 Поправки багов 2019-09-20 16:38:33 +07:00
Book Pauk
f124b9c050 Переделки синхронизации, замена diff на delta 2019-09-20 16:29:19 +07:00
Book Pauk
63a86f7c06 Версия 0.7.1 2019-09-20 14:04:17 +07:00
Book Pauk
fd0f523c64 Улучшение синхронизации 2019-09-19 20:39:01 +07:00
Book Pauk
487e605520 Поправлен баг 2019-09-19 18:19:14 +07:00
Book Pauk
9e169e1f4b Улучшение синхронизации 2019-09-19 17:51:04 +07:00
Book Pauk
9612e7ebcd Merge tag '0.7.0' into develop
0.7.0
2019-09-07 22:15:32 +07:00
Book Pauk
f66162efe7 Merge branch 'release/0.7.0' 2019-09-07 22:15:14 +07:00
Book Pauk
656642697b Версия 0.7.0 2019-09-07 22:13:13 +07:00
Book Pauk
feb70f85f8 Merge branch 'feature/ss_fix' into develop 2019-09-07 22:00:35 +07:00
Book Pauk
ab1981559b Поправка бага 2019-09-07 21:59:00 +07:00
Book Pauk
c8852d9a8e Небольшая доработка 2019-09-07 20:40:48 +07:00
Book Pauk
9ac8dc7fd1 Доработки отображения диалогов на смартфонах 2019-09-07 17:39:00 +07:00
Book Pauk
c9419d99e6 К предыдущему 2019-09-07 16:44:00 +07:00
Book Pauk
a1f4a83e72 Работа над saveRecent 2019-09-07 16:39:29 +07:00
Book Pauk
a8abd5d427 Эталонный работающий вариант ServerStorage без оптимизации, с дебагом 2019-09-07 12:25:14 +07:00
Book Pauk
629d1b0630 Поправка рассылки сообщений 2019-09-07 12:17:08 +07:00
Book Pauk
97c368f63a Поправки уведомления 2019-09-07 11:02:52 +07:00
Book Pauk
3266a444d0 Доработка 2019-09-06 22:36:48 +07:00
Book Pauk
1c246f71f8 Доделки ServerStorage 2019-09-06 22:07:15 +07:00
Book Pauk
96945dfc4a Начало переделки ServerStorage 2019-09-06 18:47:07 +07:00
Book Pauk
30eb3001ef Переход на https 2019-09-06 15:10:40 +07:00
Book Pauk
bdd8636390 Переход на https-версию, небольшой рефакторинг, улучшения 2019-09-06 15:06:58 +07:00
Book Pauk
f762d2a271 Сделан npm update, поправлены ошибки 2019-09-03 22:45:00 +07:00
Book Pauk
cf2efc2b92 Добавлены уведомления о выходе новой httpS версии сайта 2019-09-03 19:59:43 +07:00
Book Pauk
7670da4cba Мелкие поправки 2019-08-30 02:35:56 +07:00
Book Pauk
d87f9f2a21 Добавил конфиг для https с помощью certbot 2019-08-30 02:14:25 +07:00
Book Pauk
6e690f3fea Добавил cache.manifest 2019-08-29 16:30:24 +07:00
Book Pauk
6321002617 Коррекция размеров окна 2019-08-29 15:57:47 +07:00
Book Pauk
15ec362428 Поправлен баг - не распознавались картинки, если в fb2 указан binaryType == 'application/octet-stream' 2019-08-29 15:25:50 +07:00
Book Pauk
454004e705 Поправил дефолт 2019-08-28 18:45:13 +07:00
Book Pauk
e14b414fc1 Поправил баг 2019-08-28 18:41:29 +07:00
Book Pauk
c4b47a5915 Обновил element-ui 2019-08-28 18:34:51 +07:00
Book Pauk
957c252cd7 Отключил пока ServerStorage 2019-08-28 18:33:26 +07:00
Book Pauk
d6a6c21762 К предыдущему 2019-08-28 18:15:12 +07:00
Book Pauk
834580cfdf Поправил описание 2019-08-28 17:36:31 +07:00
Book Pauk
de13cfb555 К предыдущему 2019-08-28 17:30:29 +07:00
Book Pauk
4f87508834 К предыдущему 2019-08-28 17:14:58 +07:00
Book Pauk
682a044f32 Добавлена возможность двигать окна, небольшое облагораживание отображения 2019-08-28 16:48:16 +07:00
Book Pauk
bdb5d90b1d Переименование HistoryPage -> RecentBooksPage 2019-08-28 11:03:09 +07:00
Book Pauk
01880f4456 Добавил описание 0.7.0 2019-08-28 10:48:04 +07:00
Book Pauk
39f78ce7e8 Добавлен параметр compactTextPerc 2019-08-23 19:48:55 +07:00
Book Pauk
755c6b92da Мелкие поправки 2019-08-23 18:55:14 +07:00
Book Pauk
2eab9c2837 Улучшение анимации листания 2019-08-23 15:38:12 +07:00
Book Pauk
63861789de Мелкая поправка 2019-08-23 13:38:23 +07:00
Book Pauk
086c353eff Поправки багов 2019-08-23 13:32:18 +07:00
Book Pauk
4fe5b44655 Мелкие поправки 2019-08-23 12:48:32 +07:00
Book Pauk
036547e260 Мелкая поправка 2019-08-22 23:53:16 +07:00
Book Pauk
696f434c90 Улучшение отображения загрузки списка недавних 2019-08-22 23:37:55 +07:00
Book Pauk
0c654d9346 К предыдущему 2019-08-22 20:01:48 +07:00
Book Pauk
a2c393b06b Рефакторинг, упрощение, начало переделки ServerStorage 2019-08-22 15:37:15 +07:00
Book Pauk
eae2c2b102 Merge tag '0.6.10' into develop
0.6.10
2019-07-21 14:44:57 +07:00
Book Pauk
d9e49e3484 Merge branch 'release/0.6.10' 2019-07-21 14:44:43 +07:00
Book Pauk
a28d4c2f1c Версия 0.6.10 2019-07-21 14:42:54 +07:00
Book Pauk
9af055ec54 Поправки порядка загрузки компонентов и сопутствующих багов 2019-07-21 14:39:06 +07:00
Book Pauk
0d41171e9d Улучшение отзывчивости прогрессбаров 2019-07-21 12:15:33 +07:00
Book Pauk
08af826ae9 Merge tag '0.6.9s' into develop
0.6.9s
2019-06-26 20:04:45 +07:00
Book Pauk
4fd577d7c5 Merge branch 'release/0.6.9s' 2019-06-26 20:04:25 +07:00
Book Pauk
2c8efebe98 Поправлен баг клика в статус баре 2019-06-26 20:03:19 +07:00
Book Pauk
93c9fb53ac Merge tag '0.6.9' into develop
Версия 0.6.9
2019-06-23 18:51:11 +07:00
Book Pauk
5a4d249cf9 Merge branch 'release/0.6.9' 2019-06-23 18:50:57 +07:00
Book Pauk
4cc7bdee37 Версия 0.6.9 2019-06-23 18:50:28 +07:00
Book Pauk
a6af568411 Ускорил сжатие книги при сохранении в BookStore 2019-06-23 18:49:41 +07:00
Book Pauk
576a6a094a Merge tag '0.6.8' into develop
Версия 0.6.8
2019-06-23 17:20:02 +07:00
Book Pauk
e671e4b6f5 Merge branch 'release/0.6.8' 2019-06-23 17:19:48 +07:00
Book Pauk
a66b2a4c70 Версия 0.6.8 2019-06-23 17:19:30 +07:00
Book Pauk
f1ae409535 На страницу загрузки добавлен блок "Поделиться" 2019-06-23 17:18:04 +07:00
Book Pauk
a4b56b477d Исправление автоформирования заголовка при вставке из буфера обмена 2019-06-23 16:29:36 +07:00
Book Pauk
d9c389812a Добавлен новый вариант анимации перелистывания - листание 2019-06-23 15:51:55 +07:00
Book Pauk
074ef3645f Добавлен вариант перелистывания - rotate 2019-06-23 14:13:59 +07:00
Book Pauk
cc3aa413e8 Исправил сообщение о загрузке шрифтов 2019-06-09 18:23:04 +07:00
Book Pauk
7f90c09227 Улучшение прогрессбара загрузки/сохранения книги 2019-06-09 16:44:11 +07:00
Book Pauk
f6f4d8ccc9 Исправлен баг - не распознавались некоторые книги формата fb2 в кодировке utf8 2019-06-09 15:03:04 +07:00
Book Pauk
31afce8304 Исправление бага - падение сервера при распаковке битых архивов 2019-06-04 17:35:32 +07:00
Book Pauk
2c4ff856cd Merge tag '0.6.7' into develop
Версия 0.6.7
2019-05-30 16:16:19 +07:00
Book Pauk
f59974e310 Merge branch 'release/0.6.7' 2019-05-30 16:16:08 +07:00
Book Pauk
70e2c12a6b Версия 0.6.7 2019-05-30 16:15:46 +07:00
Book Pauk
11f3c6ce6f Мелкие поправки 2019-05-30 16:14:41 +07:00
Book Pauk
e213c4640b Добавлен GET-параметр вида "reader?__pp=50.5&url=..." для указания позиции в книге в процентах 2019-05-30 16:00:47 +07:00
Book Pauk
959c5eaa59 Добавлен GET-параметр вида "reader?__refresh=1&url=..." для принудительного обновления загружаемого текста 2019-05-30 14:54:55 +07:00
Book Pauk
66fa510b26 Добавлена возможность указать название текста 2019-05-28 16:32:54 +07:00
Book Pauk
f26a3b31ac На страницу загрузки добавлена возможность загрузки книги из буфера обмена 2019-05-27 16:25:51 +07:00
Book Pauk
724fbf579e Мелкое форматирование 2019-05-27 15:10:40 +07:00
Book Pauk
f192f8e3cd Мелкий рефакторинг 2019-05-27 15:09:55 +07:00
Book Pauk
f13c3d19fb - добавлена возможность настройки отображаемых кнопок на панели управления
- некоторые кнопки на панели управления были скрыты по-умолчанию
2019-05-26 16:16:20 +07:00
Book Pauk
b51a09efcc В справку добавлена история версий проекта 2019-05-26 14:01:56 +07:00
Book Pauk
6004043782 Мелкие поправки 2019-05-23 13:47:36 +07:00
Book Pauk
f9fd0dc2c3 Поправлен баг 2019-05-23 13:47:12 +07:00
Book Pauk
eb5411cd20 К предыдущему 2019-04-27 19:13:56 +07:00
Book Pauk
da3c7a02f0 Небольшие поправки отображения загрузки шрифта 2019-04-27 18:34:23 +07:00
Book Pauk
e67d05007f Поправлен баг 2019-04-27 17:21:49 +07:00
Book Pauk
b0a9a6a08e Добавлена настройка showWhatsNewDialog 2019-04-27 17:04:34 +07:00
Book Pauk
d848ea35f4 Поправки верстки 2019-04-27 16:58:04 +07:00
Book Pauk
350f20effe Добавлена история версий 2019-04-27 16:40:48 +07:00
Book Pauk
b6dc8f98fe Добавлен диалог whatsNew 2019-04-27 15:40:11 +07:00
Book Pauk
1b762ee48d Merge tag '0.6.6' into develop
0.6.6
2019-03-28 14:48:17 +07:00
Book Pauk
cc3d7f1eac Merge branch 'release/0.6.6' 2019-03-28 14:47:52 +07:00
Book Pauk
4107282fbf Версия 0.6.6 2019-03-28 14:47:28 +07:00
Book Pauk
c29ffc3fcd Поправки багов 2019-03-28 14:45:42 +07:00
Book Pauk
f648bcda13 Доработки, оптимизация сохранения recentLast 2019-03-28 14:05:13 +07:00
Book Pauk
aa0044eed2 package-lock.json 2019-03-28 13:15:29 +07:00
Book Pauk
2312a721ae Поправлен текст помощи для автономной загрузки читалки 2019-03-28 13:14:57 +07:00
Book Pauk
b93fc39b00 Мелкая поправка 2019-03-28 12:44:27 +07:00
Book Pauk
2dc2cd700f Merge tag '0.6.5' into develop
0.6.5
2019-03-25 14:04:51 +07:00
Book Pauk
d69e534f8b Merge branch 'release/0.6.5' 2019-03-25 14:04:43 +07:00
Book Pauk
1de9ddd394 Версия 0.6.5 2019-03-25 14:04:16 +07:00
Book Pauk
77c68d4e11 Небольшие поправки 2019-03-25 14:03:50 +07:00
Book Pauk
2a0d1dcfce Поправка бага 2019-03-25 13:06:48 +07:00
Book Pauk
5a19cca407 Поправка текста 2019-03-25 12:53:50 +07:00
Book Pauk
4e8773ecde Мелкая поправка 2019-03-25 12:51:01 +07:00
Book Pauk
4c7dada809 Merge tag '0.6.4' into develop
0.6.4
2019-03-24 14:33:19 +07:00
Book Pauk
65690b15da Merge branch 'release/0.6.4' 2019-03-24 14:33:09 +07:00
Book Pauk
8ba07812ce Оптимизация 2019-03-24 14:32:08 +07:00
Book Pauk
2dd8f35001 Версия 0.6.4 2019-03-24 14:04:46 +07:00
Book Pauk
2d15aa88d4 Исправления багов 2019-03-24 14:04:21 +07:00
Book Pauk
e4257e50f0 Merge tag '0.6.3' into develop
0.6.3
2019-03-24 12:52:10 +07:00
Book Pauk
33ebc07915 Merge branch 'release/0.6.3' 2019-03-24 12:51:55 +07:00
Book Pauk
bc07299626 Версия 0.6.3 2019-03-24 12:51:27 +07:00
Book Pauk
25e8aeef53 Merge tag '0.6.2' into develop
0.6.2
2019-03-24 12:28:43 +07:00
Book Pauk
a2ed34abf3 Merge branch 'release/0.6.2' 2019-03-24 12:28:28 +07:00
Book Pauk
36a7b7b91a Версия 0.6.2, поправка мелкого бага 2019-03-24 12:27:43 +07:00
Book Pauk
b4e8b7375f Merge tag '0.6.1' into develop
0.6.1
2019-03-24 12:15:16 +07:00
Book Pauk
153b635bdb Merge branch 'release/0.6.1' 2019-03-24 12:15:05 +07:00
Book Pauk
80af72465e Версия 0.6.1, поправлен баг 2019-03-24 12:14:24 +07:00
Book Pauk
a91a8f9993 Merge tag '0.6.0' into develop
0.6.0
2019-03-24 12:07:41 +07:00
Book Pauk
a0ccc7fe07 Merge branch 'release/0.6.0' 2019-03-24 12:07:32 +07:00
Book Pauk
c162c9ae0e Версия 0.6.0 2019-03-24 12:07:10 +07:00
Book Pauk
25542cdff3 Исправления багов 2019-03-24 11:44:15 +07:00
Book Pauk
16d0ae60c1 Добавлен бэкап БД при запуске 2019-03-24 11:35:43 +07:00
Book Pauk
b1937eb8c0 Мелкий рефакторинг 2019-03-24 11:24:54 +07:00
Book Pauk
3f6b468021 Поправки багов 2019-03-22 20:14:33 +07:00
Book Pauk
92d929b704 Доделки сохранения recentLast 2019-03-22 17:03:06 +07:00
Book Pauk
737ae75c28 Синхронизация recent, пока не оптимизировано 2019-03-22 14:14:47 +07:00
Book Pauk
79ced4eca4 Работа над ServerStorage - saveRecent 2019-03-22 13:20:43 +07:00
Book Pauk
329ac44c11 Работа над ServerStorage, попутный рефакторинг 2019-03-22 13:00:59 +07:00
Book Pauk
f65a91dfed Работа над ServerStorage 2019-03-22 11:58:14 +07:00
Book Pauk
2a79207427 Поправил баг 2019-03-20 15:46:31 +07:00
Book Pauk
70be3d10d0 Дебаг 2019-03-20 15:38:06 +07:00
Book Pauk
3500a40599 Рефакторинг 2019-03-20 15:22:34 +07:00
Book Pauk
090ffa9921 Добавление событий, рефакторинг 2019-03-20 14:57:40 +07:00
Book Pauk
b12198fdcf Работа над ServerStorage, попутные оптимизации 2019-03-20 14:34:01 +07:00
Book Pauk
826ee18666 Работа над ServerStorage 2019-03-20 13:59:37 +07:00
Book Pauk
f9d8b37b1a Небольшие доработки 2019-03-20 12:30:13 +07:00
Book Pauk
e626cb6b40 Переделал механизм удаления recent 2019-03-18 19:38:47 +07:00
Book Pauk
20697ad9e4 Удалил более не нужный restoreOldSettings 2019-03-18 18:52:50 +07:00
Book Pauk
0800385b96 Мелкий рефакторинг 2019-03-18 18:48:16 +07:00
Book Pauk
d6e326e8be Небольшая оптимизация 2019-03-18 18:40:03 +07:00
Book Pauk
8b969a6d36 Мелкая поправка 2019-03-18 17:05:26 +07:00
Book Pauk
d520e13c88 Добавил настройку отображения уведомлений от синхронизатора 2019-03-18 17:03:28 +07:00
Book Pauk
ae4081001c Поправки сообщений 2019-03-18 16:52:30 +07:00
Book Pauk
5a48b597b9 Небольшие доработки 2019-03-18 16:47:35 +07:00
Book Pauk
c8a953db7c На LoaderPage всегда показываем toolBar 2019-03-17 23:46:25 +07:00
Book Pauk
d20ec144ff Небольшая доработка 2019-03-17 23:39:00 +07:00
Book Pauk
0147a82b0a Добавил ввод ключа доступа по ссылке 2019-03-17 23:33:34 +07:00
Book Pauk
8732a78d01 Небольшие поправки 2019-03-17 22:47:57 +07:00
Book Pauk
015254ae40 Мелкая поправка 2019-03-17 22:39:40 +07:00
Book Pauk
712bf405bb Работа с ServerStorage 2019-03-17 22:04:56 +07:00
Book Pauk
3a46a157f9 Работа над ServerStorage 2019-03-17 20:09:16 +07:00
Book Pauk
2a4ff926ae Работа над ServerStorage 2019-03-17 19:41:47 +07:00
Book Pauk
58941116c8 Работа над ключом доступа 2019-03-17 18:10:26 +07:00
Book Pauk
a13146d722 Работа над профилями 2019-03-17 16:14:03 +07:00
Book Pauk
02e6f392b4 Работа над профилями 2019-03-17 16:05:38 +07:00
Book Pauk
d4515bd643 Работа над профилями 2019-03-17 15:02:12 +07:00
Book Pauk
a73555b7ca Улучшение парсиннга 2019-03-16 16:45:38 +07:00
Book Pauk
983d9ee1b9 Улучшение парсинга html 2019-03-16 16:40:31 +07:00
Book Pauk
e800dfe796 Мелкий рефакторинг 2019-03-16 02:24:07 +07:00
Book Pauk
b0c59be340 Поправил комментарий 2019-03-16 01:56:16 +07:00
Book Pauk
dca12b6467 Сконфигурировал TerserPlugin 2019-03-16 01:52:51 +07:00
Book Pauk
5a0d98cbd0 Допиливание sjcl 2019-03-16 01:51:48 +07:00
Book Pauk
9cbaf22270 Добавлен модуль sjcl для шифрования AES, т.к. WebCrypto API не работает с http, а только с https 2019-03-16 01:11:20 +07:00
Book Pauk
a64687f64f Промежуточный коммит 2019-03-15 19:31:09 +07:00
Book Pauk
d229aab8c9 Работа над профилями и ключом доступа 2019-03-15 19:11:27 +07:00
Book Pauk
2ff94c1458 Работа над ServerStorage - профили 2019-03-15 17:56:19 +07:00
Book Pauk
3b9f3ea81d Мелкая поправка 2019-03-15 15:29:43 +07:00
Book Pauk
23f12ad3cf Добавил время записи в item 2019-03-15 13:52:37 +07:00
Book Pauk
01e7c1f183 Добавлено кодирование id в base58 при сохранении в server-storage 2019-03-15 13:15:08 +07:00
Book Pauk
37d60bc9b9 Улучшил парсинг имени автора из fb2 2019-03-15 11:27:34 +07:00
Book Pauk
cd5d3903fe Работа над ServerStorage 2019-03-13 20:22:04 +07:00
Book Pauk
6904cfd224 Добавил сжатие данных книги в кеше 2019-03-13 17:41:32 +07:00
Book Pauk
c430e2c8f4 Увличил каоличество шагов для отсылки прогресса 2019-03-13 17:41:08 +07:00
Book Pauk
0cf8a94b24 Небольшая оптимизация по памяти 2019-03-13 16:47:22 +07:00
Book Pauk
ff3674aca7 Мелкие поправки 2019-03-13 00:57:09 +07:00
Book Pauk
b50498fa46 Поправил баг 2019-03-13 00:51:16 +07:00
Book Pauk
571f71c7f0 Работа над ServerStorage 2019-03-13 00:14:43 +07:00
Book Pauk
091c50ec84 Модуль с утилитами шифрования 2019-03-12 23:35:25 +07:00
Book Pauk
e473dc8843 Работа над ServerStorage 2019-03-12 23:34:41 +07:00
Book Pauk
886af11d3a Добавлены пакеты base-x, pako, safe-buffer 2019-03-12 18:56:38 +07:00
Book Pauk
c72fd7ee9c Компонент ServerStorage, добавлена работа с api reader/storage 2019-03-11 19:22:59 +07:00
Book Pauk
7dc76b4222 Мелкая поправка 2019-03-11 16:02:05 +07:00
Book Pauk
5011e23050 Добавил периодическую очистку кэша 2019-03-11 15:59:05 +07:00
Book Pauk
89d9a90901 Небольшая оптимизация 2019-03-08 20:50:30 +07:00
Book Pauk
05128b12a8 Добавлен метод api /reader/storage и класс ReaderStorage 2019-03-08 20:38:07 +07:00
Book Pauk
c287ca9ea8 Пробные миграции 2019-03-08 18:26:03 +07:00
Book Pauk
5122cda6db Добавляем миграции в БД sqlite 2019-03-08 18:05:58 +07:00
Book Pauk
a39626f867 Добавлен connManager для управления пулами соединений к базам Sqlite, попутный рефакторинг 2019-03-08 16:50:44 +07:00
Book Pauk
c7abae10b7 Мелкий рефакторинг 2019-03-08 13:37:27 +07:00
Book Pauk
9a8f35fd8a Merge tag '0.5.6' into develop
0.5.6
2019-03-07 20:20:45 +07:00
Book Pauk
0341cc1630 Merge branch 'release/0.5.6' 2019-03-07 20:20:26 +07:00
Book Pauk
d307d233f0 Версия 0.5.6 2019-03-07 20:16:20 +07:00
Book Pauk
5931b9625b Изменил механизмы overflow при отрисовке страницы - теперь не "съедает" часть шрифта 2019-03-07 20:12:38 +07:00
Book Pauk
fb837f5b97 Merge tag '0.5.5' into develop
0.5.5
2019-03-05 18:38:45 +07:00
Book Pauk
8cfe95b3cf Merge branch 'release/0.5.5' 2019-03-05 18:38:32 +07:00
Book Pauk
5fd73ac1e1 Версия 0.5.5 2019-03-05 18:37:41 +07:00
Book Pauk
b51a574038 Мелкие поправки текста 2019-03-05 18:37:08 +07:00
Book Pauk
51b39f0775 Улучшение парсинга pdf 2019-03-04 23:28:27 +07:00
Book Pauk
17c4f96c94 Merge tag '0.5.4' into develop
0.5.4
2019-03-04 22:57:47 +07:00
Book Pauk
89bf907613 Merge branch 'release/0.5.4' 2019-03-04 22:57:38 +07:00
Book Pauk
641d0e45fd Версия 0.5.4 2019-03-04 22:57:10 +07:00
Book Pauk
b3e579d8b7 Улучшение парсинга pdf и html 2019-03-04 22:56:15 +07:00
Book Pauk
fcb61c89d5 Улучшение парсинга html 2019-03-04 22:42:54 +07:00
Book Pauk
3483d78c2c Улучшение парсинга pdf и текстов 2019-03-04 22:28:11 +07:00
Book Pauk
36b14d0b3a Мелкая поправка 2019-03-04 21:26:07 +07:00
Book Pauk
2f8b68ec62 Улучшение парсинга Pdf 2019-03-04 21:22:12 +07:00
Book Pauk
cb65cac333 Конвертер pdf - загружаем изображения 2019-03-04 20:00:51 +07:00
Book Pauk
d12ffc3d0d Поправил комментарий 2019-03-04 16:39:30 +07:00
Book Pauk
921744167e Поправка мелкого бага 2019-03-03 12:32:22 +07:00
Book Pauk
ebd96c4759 Merge tag '0.5.3' into develop
0.5.3
2019-03-01 21:30:38 +07:00
Book Pauk
dd9876fc43 Merge branch 'release/0.5.3' 2019-03-01 21:30:28 +07:00
Book Pauk
e0de614f30 Версия 0.5.3 2019-03-01 21:30:03 +07:00
Book Pauk
30260883fb Поправки текста приветствия 2019-03-01 21:29:11 +07:00
Book Pauk
91c331e5f3 Добавлен конвертер для Mobi 2019-03-01 21:23:33 +07:00
Book Pauk
db803bcd23 Отказ от пакета decompress 2019-03-01 20:47:55 +07:00
Book Pauk
cd482ea890 Небольшая поправка 2019-03-01 20:47:39 +07:00
Book Pauk
a2497c939a Добавлен пакет tar-fs 2019-03-01 20:12:19 +07:00
Book Pauk
2e5249d30b Доработки, переименования 2019-03-01 20:11:37 +07:00
Book Pauk
b1d60c19d5 Добавлен метод unTar 2019-03-01 19:48:50 +07:00
Book Pauk
d28a82b33a УБрал дебаг 2019-03-01 19:39:55 +07:00
Book Pauk
787821f64b Поправки багов 2019-03-01 19:28:25 +07:00
Book Pauk
612b15fecc Добавлен метод unBz2 2019-03-01 18:23:46 +07:00
Book Pauk
d88d5a1352 Начата переделка FileDecompressor - отказ от использования багнутого пакета decompress 2019-03-01 18:00:09 +07:00
Book Pauk
8584ddd00e Убрал сигнатуру Epub 2019-03-01 17:58:23 +07:00
Book Pauk
4f572b5a10 Добавлен конвертер для Epub 2019-03-01 17:55:42 +07:00
Book Pauk
90a0882c59 Рефакторинг 2019-03-01 15:10:01 +07:00
Book Pauk
759344bb34 Улучшение конвертера html 2019-03-01 00:24:19 +07:00
Book Pauk
c9b65a3c43 Улучшение конвертирования Pdf 2019-02-28 23:02:34 +07:00
Book Pauk
b06e600946 Поправка багов 2019-02-28 22:04:42 +07:00
Book Pauk
2777751e54 Улучшение конвертирования Pdf 2019-02-28 20:29:09 +07:00
Book Pauk
b4493b2e8d Работа над конвертером pdf 2019-02-28 20:02:14 +07:00
Book Pauk
55d5f6524d Работа над конвертером Pdf 2019-02-28 18:58:41 +07:00
Book Pauk
c7d376adf2 Некоторые доработки 2019-02-28 18:31:08 +07:00
Book Pauk
56bf69a770 Урезал очередь конвертирования до 10 процессов 2019-02-28 15:52:13 +07:00
Book Pauk
22f9287d8b Небольшие доработки 2019-02-28 15:38:03 +07:00
Book Pauk
ca47d9272c Поправка бага 2019-02-28 15:20:20 +07:00
Book Pauk
0d61f5523a Мелкая поправка 2019-02-28 15:00:06 +07:00
Book Pauk
863ea9089a Небольшие оптимизации работы с кешем и чистки 2019-02-28 14:58:48 +07:00
Book Pauk
ad2af95ebd Поправки текста 2019-02-28 01:01:55 +07:00
Book Pauk
d65092c203 Merge tag '0.5.2' into develop
0.5.2
2019-02-28 00:50:52 +07:00
Book Pauk
7982698880 Merge branch 'release/0.5.2' 2019-02-28 00:50:41 +07:00
Book Pauk
afbdff8a88 К предыдущему 2019-02-28 00:50:03 +07:00
Book Pauk
cec07208ac Версия 0.5.2 - поправки для продакшена 2019-02-28 00:48:49 +07:00
Book Pauk
c1b82d0fd2 Merge tag '0.5.1' into develop
0.5.1
2019-02-28 00:06:59 +07:00
Book Pauk
c69e9d4b69 Merge branch 'hotfix/0.5.1' 2019-02-28 00:06:47 +07:00
Book Pauk
cc66c7f5ce Версия 0.5.1, поправки для работы на продакшене 2019-02-28 00:06:16 +07:00
Book Pauk
7d3a689577 Merge tag '0.5.0' into develop
0.5.0
2019-02-27 23:32:32 +07:00
Book Pauk
7b5915cdf7 Merge branch 'release/0.5.0' 2019-02-27 23:32:20 +07:00
Book Pauk
5cc99366ef Версия 0.5.0 2019-02-27 23:31:51 +07:00
Book Pauk
73289543e7 Поправки приветствия 2019-02-27 23:30:22 +07:00
Book Pauk
716b8b5b9a Добавил распаковку вложенных архивов 2019-02-27 23:19:18 +07:00
Book Pauk
c4f6c9383c Меняем текст приветствия в зависимости от useExternalBookConverter 2019-02-27 22:09:00 +07:00
Book Pauk
7d77d478c1 Добавил конвертер файлов rtf 2019-02-27 21:59:45 +07:00
Book Pauk
572c0d0717 Увеличил время ожидания до 2х минут 2019-02-27 21:50:47 +07:00
Book Pauk
b786b7b2d6 Поправки spawnProcess 2019-02-27 21:31:47 +07:00
Book Pauk
3253858c7f Добавил конвертер для msdoc 2019-02-27 21:14:47 +07:00
Book Pauk
a5fe61078d Сделал конвертер для DocX 2019-02-27 20:39:15 +07:00
Book Pauk
528adae3d0 Подготовка к работе с внешними конвертерами 2019-02-27 20:00:55 +07:00
Book Pauk
d3ff0edbff Рефакторинг, плюс небольшие изменения - подготовка к использованию внешних конвертеров 2019-02-27 19:30:04 +07:00
Book Pauk
61cfee222f Переименования 2019-02-27 19:10:33 +07:00
Book Pauk
a96eb50784 Подготовка к запуску внешних конвертеров, для обработки файлов pdf, doc, epub, mobi и пр. 2019-02-27 17:48:37 +07:00
Book Pauk
8219e19c1b Улучшение FileDetector 2019-02-27 17:48:06 +07:00
Book Pauk
3d56a6915f Улучшил отображение ошибки 2019-02-27 17:47:06 +07:00
Book Pauk
4d6502f5e2 Удалил пакет detect-file-type 2019-02-27 17:46:39 +07:00
Book Pauk
f0f245884f Добавил инструкции по установке внешних конвертеров 2019-02-27 15:59:19 +07:00
Book Pauk
815f9178bf Merge tag '0.4.7' into develop
0.4.7
2019-02-27 06:22:09 +07:00
Book Pauk
2df88f4280 Merge branch 'release/0.4.7' 2019-02-27 06:21:57 +07:00
Book Pauk
156a1b4aa8 Версия 0.4.7 2019-02-27 06:21:41 +07:00
Book Pauk
962eda7860 Поправки багов 2019-02-27 06:12:59 +07:00
Book Pauk
954ce9e85c Поправки 2019-02-27 06:00:30 +07:00
Book Pauk
ff27ddd442 Оптимизация - улучшение скорости загрузки читалки 2019-02-27 05:52:59 +07:00
Book Pauk
f9cc2ad70a Мелкая поправка 2019-02-27 04:13:56 +07:00
Book Pauk
4bcd45a795 Рефакторинг - вынес методы конвертирования в отдельные классы 2019-02-27 03:59:08 +07:00
Book Pauk
9e7ccd6e20 Мелкий рефакторинг 2019-02-26 18:23:17 +07:00
Book Pauk
631c5930e9 Мелкая поправка 2019-02-25 20:49:12 +07:00
Book Pauk
0ddb182642 Улучшение парсинга fb2 2019-02-25 17:14:44 +07:00
Book Pauk
de2f0e74c8 Небольшая поправка 2019-02-25 15:29:58 +07:00
Book Pauk
ad4ee6ccc9 Merge tag '0.4.6' into develop
0.4.6
2019-02-24 22:02:53 +07:00
Book Pauk
1670df02db Merge branch 'release/0.4.6' 2019-02-24 22:02:43 +07:00
Book Pauk
0268e647cd Версия 0.4.6 2019-02-24 22:02:27 +07:00
Book Pauk
4b9315c13c Поправка бага 2019-02-24 22:00:41 +07:00
Book Pauk
14a9948dd2 Merge tag '0.4.5' into develop
0.4.5
2019-02-24 21:37:21 +07:00
Book Pauk
060ec98b0c Merge branch 'release/0.4.5' 2019-02-24 21:37:11 +07:00
Book Pauk
9c1efe381e Версия 0.4.5 2019-02-24 21:36:49 +07:00
Book Pauk
6a1b052d16 Поправил баг 2019-02-24 21:36:06 +07:00
Book Pauk
d6151d541e Поправил баг 2019-02-24 21:27:18 +07:00
Book Pauk
3e90277e1e Добавил опцию imageFitWidth 2019-02-24 19:32:16 +07:00
Book Pauk
641cbdfe85 Merge tag '0.4.4' into develop
0.4.4
2019-02-24 00:41:59 +07:00
Book Pauk
c36e9b36d8 Merge branch 'release/0.4.4' 2019-02-24 00:41:48 +07:00
Book Pauk
ecc3acce93 Версия 0.4.4 2019-02-24 00:41:35 +07:00
Book Pauk
33a2ca55f0 Поправки 2019-02-24 00:32:19 +07:00
Book Pauk
9d0bbec4b3 Поправка отображения картинок внутри title 2019-02-24 00:00:55 +07:00
Book Pauk
4de0b3cffd Поправки парсера fb2 2019-02-23 23:51:06 +07:00
Book Pauk
06221a474b Мелкая поправка 2019-02-23 23:38:19 +07:00
Book Pauk
e99a42b7af К предыдущему 2019-02-23 23:20:03 +07:00
Book Pauk
37822e8409 Мелкая поправка 2019-02-23 23:18:03 +07:00
Book Pauk
2e477e6c99 Улучшение парсинга СИ 2019-02-23 23:03:01 +07:00
Book Pauk
360ee98d8d Улучшение парсинга fb2 2019-02-23 22:17:16 +07:00
Book Pauk
69afd7720a Поправки форматирования fb2 2019-02-23 20:45:21 +07:00
Book Pauk
a75590c493 Улучшение формирования fb2 2019-02-23 20:15:40 +07:00
Book Pauk
2acb65f6b3 Мелкие поправки 2019-02-23 20:04:41 +07:00
Book Pauk
1e1a58b58c Улучшен convertHtml 2019-02-23 19:47:16 +07:00
Book Pauk
aeadb5aeb8 Улучшено определение кодировки и текстового файла 2019-02-23 19:09:57 +07:00
Book Pauk
3e2f01d56d Мелкие поправки 2019-02-23 18:06:29 +07:00
Book Pauk
cad97e639a Добавил цвет фона 2019-02-23 14:37:40 +07:00
Book Pauk
e2632f1802 Добавил вывод версии в консоль 2019-02-23 14:24:29 +07:00
Book Pauk
9aa0bb2bde Merge tag '0.4.3' into develop
0.4.3
2019-02-23 14:17:13 +07:00
Book Pauk
f015d5f7ed Merge branch 'release/0.4.3' 2019-02-23 14:17:00 +07:00
Book Pauk
74f8f7f9a4 0.4.3 2019-02-23 14:16:43 +07:00
Book Pauk
2598538de9 Улучшил отзывчивость historyPage при открытии 2019-02-23 14:13:41 +07:00
Book Pauk
a78a00df2b Поправка label 2019-02-23 14:03:28 +07:00
Book Pauk
92f6beb64e Merge tag '0.4.2' into develop
0.4.2
2019-02-21 21:39:19 +07:00
Book Pauk
3920b71613 Merge branch 'release/0.4.2' 2019-02-21 21:39:09 +07:00
Book Pauk
d661150665 0.4.2 2019-02-21 21:38:46 +07:00
Book Pauk
ab29c80dab Поправки багов 2019-02-21 21:36:17 +07:00
Book Pauk
e5384e27e5 Поправки багов 2019-02-21 21:02:27 +07:00
Book Pauk
06cdc6eb63 Улучшение парсинга samlib 2019-02-21 20:26:56 +07:00
Book Pauk
da284c793e Добавил загрузку внешних изображений 2019-02-21 20:22:25 +07:00
Book Pauk
c2cef91eb3 Merge tag '0.4.1' into develop
0.4.1
2019-02-20 21:40:10 +07:00
Book Pauk
19da1aff45 Merge branch 'hotfix/0.4.1' 2019-02-20 21:39:59 +07:00
Book Pauk
5f2206e766 Добавил опцию "Инлайн в центр" 2019-02-20 21:39:17 +07:00
Book Pauk
e272308823 Merge tag '0.4.0' into develop
0.4.0
2019-02-20 21:08:53 +07:00
Book Pauk
8491c40890 Merge branch 'release/0.4.0' 2019-02-20 21:08:42 +07:00
Book Pauk
dfa7013cbd Версия 0.4.0 2019-02-20 21:08:23 +07:00
Book Pauk
1a7ceb333d Добавлена обработка inline-изображений 2019-02-20 20:57:34 +07:00
Book Pauk
d3a30b87f4 Улучшение парсинга fb2 2019-02-20 19:43:39 +07:00
Book Pauk
dd61c04d63 Поправки багов 2019-02-20 19:25:52 +07:00
Book Pauk
5496e874c4 Улучшение парсинга fb2 2019-02-20 18:19:23 +07:00
Book Pauk
56d13288ff Поправка бага, комментариев 2019-02-20 18:04:05 +07:00
Book Pauk
618111ab05 Поправка бага 2019-02-20 17:32:26 +07:00
Book Pauk
55d02495a3 Добавил настройки для изображений, добаил автоматический ресайз 2019-02-20 17:17:51 +07:00
Book Pauk
83fc586e03 Работа над изображениями 2019-02-20 16:49:03 +07:00
Book Pauk
ae3dc9b22c Промежуточный коммит, работа над изображениями, небольшой рефакторинг попутно 2019-02-20 15:26:54 +07:00
Book Pauk
12e0f9459b Merge tag '0.3.5' into develop
0.3.5
2019-02-19 11:31:05 +07:00
Book Pauk
fd1dd54b99 Merge branch 'release/0.3.5' 2019-02-19 11:30:55 +07:00
Book Pauk
32cbb2a82c 0.3.5 2019-02-19 11:30:24 +07:00
Book Pauk
048b7c08ca Merge tag '0.3.4' into develop
0.3.4
2019-02-19 11:25:36 +07:00
289 changed files with 36902 additions and 13495 deletions

6
.babelrc Normal file
View File

@@ -0,0 +1,6 @@
{
"presets": [['@babel/preset-env', { "targets": { "esmodules": true } }]],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }]
]
}

View File

@@ -1,17 +1,18 @@
{
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "babel-eslint"
"parser": "@babel/eslint-parser",
"sourceType": "module"
},
"extends": [
"eslint:recommended",
"plugin:vue/essential"
"plugin:vue/recommended"
],
"plugins": [
"vue",
"html",
"node"
"@babel"
],
"env": {
"es6": true,
"browser": true,
"node": true
},
@@ -24,6 +25,14 @@
"LM_TOTAL": false
},
"rules": {
"vue/html-indent": ["warn", 4, {
"alignAttributesVertically": false
}],
"vue/max-attributes-per-line": "off",
"vue/html-self-closing": "off",
"vue/no-v-html": "off",
"vue/no-v-model-argument": "off",
"strict": 0,
"indent": [0, 4, {
"SwitchCase": 1

106
LICENSE.md Normal file
View File

@@ -0,0 +1,106 @@
# CC0 1.0 Universal
## Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator and
subsequent owner(s) (each and all, an “owner”) of an original work of
authorship and/or a database (each, a “Work”).
Certain owners wish to permanently relinquish those rights to a Work for the
purpose of contributing to a commons of creative, cultural and scientific works
(“Commons”) that the public can reliably and without fear of later claims of
infringement build upon, modify, incorporate in other works, reuse and
redistribute as freely as possible in any form whatsoever and for any purposes,
including without limitation commercial purposes. These owners may contribute
to the Commons to promote the ideal of a free culture and the further
production of creative, cultural and scientific works, or to gain reputation or
greater distribution for their Work in part through the use and efforts of
others.
For these and/or other purposes and motivations, and without any expectation of
additional consideration or compensation, the person associating CC0 with a
Work (the “Affirmer”), to the extent that he or she is an owner of Copyright
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and
publicly distribute the Work under its terms, with knowledge of his or her
Copyright and Related Rights in the Work and the meaning and intended legal
effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights (“Copyright and
Related Rights”). Copyright and Related Rights include, but are not limited
to, the following:
1. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
2. moral rights retained by the original author(s) and/or performer(s);
3. publicity and privacy rights pertaining to a persons image or likeness
depicted in a Work;
4. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(i), below;
5. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
6. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation thereof,
including any amended or successor version of such directive); and
7. other similar, equivalent or corresponding rights throughout the world
based on applicable law or treaty, and any national implementations
thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention of,
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
unconditionally waives, abandons, and surrenders all of Affirmers Copyright
and Related Rights and associated claims and causes of action, whether now
known or unknown (including existing as well as future claims and causes of
action), in the Work (i) in all territories worldwide, (ii) for the maximum
duration provided by applicable law or treaty (including future time
extensions), (iii) in any current or future medium and for any number of
copies, and (iv) for any purpose whatsoever, including without limitation
commercial, advertising or promotional purposes (the “Waiver”). Affirmer makes
the Waiver for the benefit of each member of the public at large and to the
detriment of Affirmers heirs and successors, fully intending that such Waiver
shall not be subject to revocation, rescission, cancellation, termination, or
any other legal or equitable action to disrupt the quiet enjoyment of the Work
by the public as contemplated by Affirmers express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason be
judged legally invalid or ineffective under applicable law, then the Waiver
shall be preserved to the maximum extent permitted taking into account
Affirmers express Statement of Purpose. In addition, to the extent the Waiver
is so judged Affirmer hereby grants to each affected person a royalty-free, non
transferable, non sublicensable, non exclusive, irrevocable and unconditional
license to exercise Affirmers Copyright and Related Rights in the Work (i) in
all territories worldwide, (ii) for the maximum duration provided by applicable
law or treaty (including future time extensions), (iii) in any current or
future medium and for any number of copies, and (iv) for any purpose
whatsoever, including without limitation commercial, advertising or promotional
purposes (the “License”). The License shall be deemed effective as of the date
CC0 was applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder of the
License, and in such case Affirmer hereby affirms that he or she will not (i)
exercise any of his or her remaining Copyright and Related Rights in the Work
or (ii) assert any associated claims and causes of action with respect to the
Work, in either case contrary to Affirmers express Statement of Purpose.
4. Limitations and Disclaimers.
1. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
2. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied, statutory
or otherwise, including without limitation warranties of title,
merchantability, fitness for a particular purpose, non infringement, or
the absence of latent or other defects, accuracy, or the present or
absence of errors, whether or not discoverable, all to the greatest
extent permissible under applicable law.
3. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any persons Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the Work.
4. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.
For more information, please see
http://creativecommons.org/publicdomain/zero/1.0/.

View File

@@ -1,3 +1,43 @@
# Liberama
Свободный обмен книгами в формате fb2
Браузерная онлайн-читалка книг и децентрализованная библиотека.
Читалка <img src="https://omnireader.ru/favicon.ico" width="14px"/>[OmniReader](https://omnireader.ru) является частью данного проекта, размещенной на VPS:
![](docs/assets/face.jpg)
![](docs/assets/reader.jpg)
## VPS
Для разворачивания читалки на чистом VPS с нуля смотрите [docs/omnireader.ru](docs/omnireader.ru/README.md)
## Сборка проекта
Необходима версия node.js не ниже 14.
```
$ git clone https://github.com/bookpauk/liberama
$ cd liberama
$ npm i
```
### Windows
```
$ npm run build:win
```
### Linux
```
$ npm run build:linux
```
Результат сборки будет доступен в каталоге `dist/linux|win` в виде исполнимого (standalone) файла
### Разработка
```
$ npm run dev
```
## Помочь проекту
* bitcoin: 3EbgZ7MK1UVaN38Gty5DCBtS4PknM4Ut85
* litecoin: MP39Riec4oSNB3XMjiquKoLWxbufRYNXxZ
* monero: 8BQPnvHcPSHM5gMQsmuypDgx9NNsYqwXKfDDuswEyF2Q2ewQSfd2pkK6ydH2wmMyq2JViZvy9DQ35hLMx7g72mFWNJTPtnz

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

@@ -5,8 +5,7 @@ const stream = require('stream');
const pipeline = util.promisify(stream.pipeline);
const got = require('got');
const decompress = require('decompress');
const decompressTargz = require('decompress-targz');
const FileDecompressor = require('../server/core/FileDecompressor');
const distDir = path.resolve(__dirname, '../dist');
const publicDir = `${distDir}/tmp/public`;
@@ -15,6 +14,8 @@ const outDir = `${distDir}/linux`;
const tempDownloadDir = `${distDir}/tmp/download`;
async function main() {
const decomp = new FileDecompressor();
await fs.emptyDir(outDir);
// перемещаем public на место
if (await fs.pathExists(publicDir))
@@ -23,8 +24,8 @@ async function main() {
await fs.ensureDir(tempDownloadDir);
//sqlite3
const sqliteRemoteUrl = 'https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v4.0.4/node-v64-linux-x64.tar.gz';
const sqliteDecompressedFilename = `${tempDownloadDir}/node-v64-linux-x64/node_sqlite3.node`;
const sqliteRemoteUrl = 'https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v5.0.2/napi-v3-linux-x64.tar.gz';
const sqliteDecompressedFilename = `${tempDownloadDir}/napi-v3-linux-x64/node_sqlite3.node`;
if (!await fs.pathExists(sqliteDecompressedFilename)) {
// Скачиваем node_sqlite3.node для винды, т.к. pkg не включает его в сборку
@@ -32,11 +33,7 @@ async function main() {
console.log(`done downloading ${sqliteRemoteUrl}`);
//распаковываем
await decompress(`${tempDownloadDir}/sqlite.tar.gz`, `${tempDownloadDir}`, {
plugins: [
decompressTargz()
]
});
console.log(await decomp.unpackTarZZ(`${tempDownloadDir}/sqlite.tar.gz`, tempDownloadDir));
console.log('files decompressed');
}
// копируем в дистрибутив
@@ -53,11 +50,7 @@ async function main() {
console.log(`done downloading ${ipfsRemoteUrl}`);
//распаковываем
await decompress(`${tempDownloadDir}/ipfs.tar.gz`, `${tempDownloadDir}`, {
plugins: [
decompressTargz()
]
});
console.log(await decomp.unpackTarZZ(`${tempDownloadDir}/ipfs.tar.gz`, tempDownloadDir));
console.log('files decompressed');
}

View File

@@ -1,10 +1,16 @@
const path = require('path');
//const webpack = require('webpack');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const DefinePlugin = require('webpack').DefinePlugin;
const { VueLoaderPlugin } = require('vue-loader');
const clientDir = path.resolve(__dirname, '../client');
module.exports = {
resolve: {
alias: {
ws: false,
//vue: '@vue/compat'
}
},
entry: [`${clientDir}/main.js`],
output: {
publicPath: '/app/',
@@ -14,53 +20,61 @@ module.exports = {
rules: [
{
test: /\.vue$/,
loader: "vue-loader"
loader: 'vue-loader',
/*options: {
compilerOptions: {
compatConfig: {
MODE: 2
}
}
}*/
},
{
resourceQuery: /^\?vue/,
use: path.resolve(__dirname, 'includer.js')
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
options: {
presets: [['@babel/preset-env', { targets: { esmodules: true } }]],
plugins: [
'syntax-dynamic-import',
'transform-decorators-legacy',
'transform-class-properties',
// ["component", { "libraryName": "element-ui", "styleLibraryName": `~${clientDir}/theme` } ]
['@babel/plugin-proposal-decorators', { legacy: true }]
]
}
},
{
test: /\.gif$/,
loader: "url-loader",
options: {
name: "images/[name]-[hash:6].[ext]"
}
},
{
test: /\.png$/,
loader: "url-loader",
options: {
name: "images/[name]-[hash:6].[ext]"
}
test: /\.(gif|png)$/,
type: 'asset/inline',
},
{
test: /\.jpg$/,
loader: "file-loader",
options: {
name: "images/[name]-[hash:6].[ext]"
}
type: 'asset/resource',
generator: {
filename: 'images/[name]-[hash:6][ext]'
},
},
{
test: /\.(ttf|eot|woff|woff2)$/,
loader: "file-loader",
options: {
name: "fonts/[name]-[hash:6].[ext]"
}
type: 'asset/resource',
generator: {
filename: 'fonts/[name]-[hash:6][ext]'
},
},
]
},
plugins: [
new DefinePlugin({
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false,
__QUASAR_SSR__: false,
__QUASAR_SSR_SERVER__: false,
__QUASAR_SSR_CLIENT__: false,
__QUASAR_VERSION__: false,
}),
new VueLoaderPlugin(),
]
};

View File

@@ -1,7 +1,7 @@
const path = require('path');
const webpack = require('webpack');
const merge = require('webpack-merge');
const { merge } = require('webpack-merge');
const baseWpConfig = require('./webpack.base.config');
baseWpConfig.entry.unshift('webpack-hot-middleware/client');
@@ -13,7 +13,7 @@ const clientDir = path.resolve(__dirname, '../client');
module.exports = merge(baseWpConfig, {
mode: 'development',
devtool: "#inline-source-map",
devtool: 'inline-source-map',
output: {
path: `${publicDir}/app`,
filename: 'bundle.js'
@@ -38,6 +38,6 @@ module.exports = merge(baseWpConfig, {
template: `${clientDir}/index.html.template`,
filename: `${publicDir}/index.html`
}),
new CopyWebpackPlugin([{from: `${clientDir}/assets/*`, to: `${publicDir}/`, flatten: true}])
new CopyWebpackPlugin({patterns: [{from: `${clientDir}/assets/*`, to: `${publicDir}/`}]})
]
});

View File

@@ -1,14 +1,15 @@
const path = require('path');
//const webpack = require('webpack');
const merge = require('webpack-merge');
const { merge } = require('webpack-merge');
const baseWpConfig = require('./webpack.base.config');
const TerserPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
//const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const {GenerateSW} = require('workbox-webpack-plugin');
const publicDir = path.resolve(__dirname, '../dist/tmp/public');
const clientDir = path.resolve(__dirname, '../client');
@@ -32,12 +33,19 @@ module.exports = merge(baseWpConfig, {
},
optimization: {
minimizer: [
new TerserPlugin(),
new OptimizeCSSAssetsPlugin()
new TerserPlugin({
parallel: true,
terserOptions: {
format: {
comments: false,
},
},
}),
new CssMinimizerWebpackPlugin()
]
},
plugins: [
new CleanWebpackPlugin([publicDir], {root: path.resolve(__dirname, '..')}),
//new CleanWebpackPlugin({ cleanOnceBeforeBuildPatterns: [`${publicDir}/**`] }),
new MiniCssExtractPlugin({
filename: "[name].[contenthash].css"
}),
@@ -45,6 +53,15 @@ module.exports = merge(baseWpConfig, {
template: `${clientDir}/index.html.template`,
filename: `${publicDir}/index.html`
}),
new CopyWebpackPlugin([{from: `${clientDir}/assets/*`, to: `${publicDir}/`, flatten: true}])
new CopyWebpackPlugin({patterns:
[{from: `${clientDir}/assets/*`, to: `${publicDir}/`, context: `${clientDir}/assets` }]
}),
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

@@ -5,8 +5,7 @@ const stream = require('stream');
const pipeline = util.promisify(stream.pipeline);
const got = require('got');
const decompress = require('decompress');
const decompressTargz = require('decompress-targz');
const FileDecompressor = require('../server/core/FileDecompressor');
const distDir = path.resolve(__dirname, '../dist');
const publicDir = `${distDir}/tmp/public`;
@@ -15,6 +14,8 @@ const outDir = `${distDir}/win`;
const tempDownloadDir = `${distDir}/tmp/download`;
async function main() {
const decomp = new FileDecompressor();
await fs.emptyDir(outDir);
// перемещаем public на место
if (await fs.pathExists(publicDir))
@@ -23,8 +24,8 @@ async function main() {
await fs.ensureDir(tempDownloadDir);
//sqlite3
const sqliteRemoteUrl = 'https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v4.0.4/node-v64-win32-x64.tar.gz';
const sqliteDecompressedFilename = `${tempDownloadDir}/node-v64-win32-x64/node_sqlite3.node`;
const sqliteRemoteUrl = 'https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v5.0.2/napi-v3-win32-x64.tar.gz';
const sqliteDecompressedFilename = `${tempDownloadDir}/napi-v3-win32-x64/node_sqlite3.node`;
if (!await fs.pathExists(sqliteDecompressedFilename)) {
// Скачиваем node_sqlite3.node для винды, т.к. pkg не включает его в сборку
@@ -32,11 +33,7 @@ async function main() {
console.log(`done downloading ${sqliteRemoteUrl}`);
//распаковываем
await decompress(`${tempDownloadDir}/sqlite.tar.gz`, `${tempDownloadDir}`, {
plugins: [
decompressTargz()
]
});
console.log(await decomp.unpackTarZZ(`${tempDownloadDir}/sqlite.tar.gz`, tempDownloadDir));
console.log('files decompressed');
}
// копируем в дистрибутив
@@ -53,7 +50,7 @@ async function main() {
console.log(`done downloading ${ipfsRemoteUrl}`);
//распаковываем
await decompress(`${tempDownloadDir}/ipfs.zip`, `${tempDownloadDir}`);
console.log(await decomp.unpack(`${tempDownloadDir}/ipfs.zip`, tempDownloadDir));
console.log('files decompressed');
}
// копируем в дистрибутив

View File

@@ -1,4 +1,5 @@
import axios from 'axios';
import wsc from './webSocketConnection';
const api = axios.create({
baseURL: '/api'
@@ -6,7 +7,22 @@ const api = axios.create({
class Misc {
async loadConfig() {
const response = await api.post('/config', {params: ['name', 'version', 'mode', 'maxUploadFileSize', 'branch']});
const query = {params: [
'name', 'version', 'mode', 'maxUploadFileSize', 'useExternalBookConverter', 'branch',
]};
try {
const config = await wsc.message(await 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,76 +1,176 @@
import axios from 'axios';
import {sleep} from '../share/utils';
import * as utils from '../share/utils';
import wsc from './webSocketConnection';
const api = axios.create({
baseURL: '/api/reader'
baseURL: '/api/reader'
});
const workerApi = axios.create({
baseURL: '/api/worker'
baseURL: '/api/worker'
});
class Reader {
async loadBook(url, callback) {
const refreshPause = 200;
constructor() {
}
async getWorkerStateFinish(workerId, callback) {
if (!callback) callback = () => {};
let response = await api.post('/load-book', {type: 'url', url});
let response = {};
try {
const requestId = await wsc.send({action: 'worker-get-state-finish', workerId});
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);
}
//если с 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.state)
throw new Error('Неверный ответ api');
if (response.state == 'finish' || response.state == 'error') {
break;
}
if (i > 0)
await utils.sleep(refreshPause);
i++;
if (i > 180*1000/refreshPause) {//3 мин ждем телодвижений воркера
throw new Error('Слишком долгое время ожидания');
}
//проверка воркера
i = (prevProgress != response.progress || prevState != response.state ? 1 : i);
}
return response;
}
async loadBook(opts, callback) {
if (!callback) callback = () => {};
let response = await api.post('/load-book', opts);
const workerId = response.data.workerId;
if (!workerId)
throw new Error('Неверный ответ api');
callback({totalSteps: 4});
callback(response.data);
let i = 0;
while (1) {// eslint-disable-line no-constant-condition
callback(response.data);
response = await this.getWorkerStateFinish(workerId, callback);
if (response.data.state == 'finish') {//воркер закончил работу, можно скачивать кешированный на сервере файл
if (response) {
if (response.state == 'finish') {//воркер закончил работу, можно скачивать кешированный на сервере файл
callback({step: 4});
const book = await this.loadCachedBook(response.data.path, callback);
return Object.assign({}, response.data, {data: book.data});
const book = await this.loadCachedBook(response.path, callback, response.size);
return Object.assign({}, response, {data: book.data});
}
if (response.data.state == 'error') {
let errMes = response.data.error;
if (response.state == 'error') {
let errMes = response.error;
if (errMes.indexOf('getaddrinfo') >= 0 ||
errMes.indexOf('ECONNRESET') >= 0 ||
errMes.indexOf('EINVAL') >= 0 ||
errMes.indexOf('404') >= 0)
errMes = `Ресурс не найден по адресу: ${response.data.url}`;
errMes = `Ресурс не найден по адресу: ${response.url}`;
throw new Error(errMes);
}
if (i > 0)
await sleep(refreshPause);
i++;
if (i > 30*1000/refreshPause) {//30 сек ждем телодвижений воркера
throw new Error('Слишком долгое время ожидания');
}
//проверка воркера
const prevProgress = response.data.progress;
response = await workerApi.post('/get-state', {workerId});
i = (prevProgress != response.data.progress ? 1 : i);
} else {
throw new Error('Пустой ответ сервера');
}
}
async loadCachedBook(url, callback){
const response = await axios.head(url);
async checkCachedBook(url) {
let estSize = -1;
try {
const response = await axios.head(url, {headers: {'Cache-Control': 'no-cache'}});
let estSize = 1000000;
if (response.headers['content-length']) {
estSize = response.headers['content-length'];
if (response.headers['content-length']) {
estSize = response.headers['content-length'];
}
} catch (e) {
//восстановим при необходимости файл на сервере из удаленного облака
let response = null
try {
response = await wsc.message(await 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 (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)
callback({state: 'loading', progress: Math.round((progress.loaded*100)/estSize)});
callback({progress: Math.round((progress.loaded*100)/estSize)});
}
}
//загрузка
return await axios.get(url, options);
}
@@ -79,9 +179,8 @@ class Reader {
maxUploadFileSize = 10*1024*1024;
if (file.size > maxUploadFileSize)
throw new Error(`Размер файла превышает ${maxUploadFileSize} байт`);
let formData = new FormData();
formData.append('file', file);
formData.append('file', file, file.name);
const options = {
headers: {
@@ -105,6 +204,27 @@ class Reader {
return url;
}
async storage(request) {
let response = null;
try {
response = await wsc.message(await 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.state;
if (!state)
throw new Error('Неверный ответ api');
if (state == 'error') {
throw new Error(response.error);
}
return response;
}
}
export default new Reader();

View File

@@ -0,0 +1,3 @@
import WebSocketConnection from '../../server/core/WebSocketConnection';
export default new WebSocketConnection();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 B

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,3 +1,2 @@
User-agent: *
Disallow: /?*url=
Disallow: /#/

View File

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

View File

@@ -1,66 +1,44 @@
<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>
<div class="fit row">
<Notify ref="notify" />
<StdDialog ref="stdDialog" />
<el-main v-if="showMain" :style="{padding: (isReaderActive ? 0 : '5px')}">
<keep-alive>
<router-view></router-view>
<router-view v-slot="{ Component }">
<keep-alive v-if="showPage">
<component :is="Component" class="col" />
</keep-alive>
</el-main>
</el-container>
</router-view>
</div>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import vueComponent from './vueComponent.js';
export default @Component({
import Notify from './share/Notify.vue';
import StdDialog from './share/StdDialog.vue';
import sanitizeHtml from 'sanitize-html';
import miscApi from '../api/misc';
import * as utils from '../share/utils';
const componentOptions = {
components: {
Notify,
StdDialog,
},
watch: {
rootRoute: function() {
this.setAppTitle();
this.redirectIfNeeded();
},
mode: function() {
this.setAppTitle();
this.redirectIfNeeded();
}
},
})
class App extends Vue {
};
class App {
_options = componentOptions;
showPage = false;
itemRuText = {
'/cardindex': 'Картотека',
'/reader': 'Читалка',
@@ -69,62 +47,117 @@ class App extends Vue {
'/sources': 'Источники',
'/settings': 'Параметры',
'/help': 'Справка',
}
};
created() {
this.commit = this.$store.commit;
this.dispatch = this.$store.dispatch;
this.state = this.$store.state;
this.uistate = this.$store.state.uistate;
this.config = this.$store.state.config;
// set-app-title
this.$root.$on('set-app-title', this.setAppTitle);
//root route
let cachedRoute = '';
let cachedPath = '';
this.$root.getRootRoute = () => {
if (this.$route.path != cachedPath) {
cachedPath = this.$route.path;
const m = cachedPath.match(/^(\/[^/]*).*$/i);
cachedRoute = (m ? m[1] : this.$route.path);
//global keyHooks
this.keyHooks = [];
this.keyHook = (event) => {
for (const hook of this.keyHooks)
}
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();
}
});
this.$root.isMobileDevice = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent);
// setAppTitle
this.$root.setAppTitle = this.setAppTitle;
//sanitize
this.$root.sanitize = sanitizeHtml;
//global event hooks
this.eventHooks = {};
this.$root.eventHook = (hookName, event) => {
if (!this.eventHooks[hookName])
return;
for (const hook of this.eventHooks[hookName])
hook(event);
}
this.$root.addKeyHook = (hook) => {
if (this.keyHooks.indexOf(hook) < 0)
this.keyHooks.push(hook);
this.$root.addEventHook = (hookName, hook) => {
if (!this.eventHooks[hookName])
this.eventHooks[hookName] = [];
if (this.eventHooks[hookName].indexOf(hook) < 0)
this.eventHooks[hookName].push(hook);
}
this.$root.removeKeyHook = (hook) => {
const i = this.keyHooks.indexOf(hook);
this.$root.removeEventHook = (hookName, hook) => {
if (!this.eventHooks[hookName])
return;
const i = this.eventHooks[hookName].indexOf(hook);
if (i >= 0)
this.keyHooks.splice(i, 1);
this.eventHooks[hookName].splice(i, 1);
}
document.addEventListener('keyup', (event) => {
this.keyHook(event);
});
this.$root.eventHook('key', event);
});
document.addEventListener('keypress', (event) => {
this.$root.eventHook('key', event);
});
document.addEventListener('keydown', (event) => {
this.keyHook(event);
});
window.addEventListener('resize', () => {
this.$root.$emit('resize');
this.$root.eventHook('key', event);
});
window.addEventListener('resize', (event) => {
this.$root.eventHook('resize', event);
});
}
mounted() {
this.dispatch('config/loadConfig');
this.$watch('apiError', function(newError) {
if (newError) {
this.$notify.error({
title: 'Ошибка API',
dangerouslyUseHTMLString: true,
message: newError.response.config.url + '<br>' + newError.response.statusText
});
this.$root.notify = this.$refs.notify;
this.$root.stdDialog = this.$refs.stdDialog;
this.setAppTitle();
(async() => {
//загрузим конфиг сревера
try {
const config = await miscApi.loadConfig();
this.commit('config/setConfig', config);
this.showPage = true;
} catch(e) {
//проверим, не получен ли конфиг ранее
if (!this.mode) {
this.$root.notify.error(e.message, 'Ошибка API');
} else {
//вероятно, работаем в оффлайне
this.showPage = true;
}
console.error(e);
}
});
//запросим persistent storage
if (navigator.storage && navigator.storage.persist) {
navigator.storage.persist();
}
await this.$router.isReady();
this.redirectIfNeeded();
})();
}
toggleCollapse() {
this.commit('uistate/setAsideBarCollapse', !this.uistate.asideBarCollapse);
this.$root.$emit('resize');
this.$root.eventHook('resize');
}
get isCollapse() {
@@ -133,9 +166,9 @@ class App extends Vue {
get asideWidth() {
if (this.uistate.asideBarCollapse) {
return '64px';
return 64;
} else {
return '170px';
return 170;
}
}
@@ -159,18 +192,17 @@ 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.getRootRoute();
}
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]}`;
document.title = `${this.config.name} - ${this.itemRuText[this.rootRoute]}`;
}
} else {
document.title = title;
@@ -186,46 +218,37 @@ 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.rootRoute != '/reader')) {
//старый url
if ((this.mode == 'reader' || this.mode == 'omnireader' || this.mode == 'liberama.top')) {
const search = window.location.search.substr(1);
const url = search.split('url=')[1] || '';
if (url) {
window.location = `/#/reader?url=${url}`;
} else {
this.$router.replace('/reader');
//распознавание параметра 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 });
}
}
//yandex-метрика для omnireader
if (this.config.branch == 'production' && this.mode == 'omnireader' && !this.yaMetricsDone) {
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
m[i].l=1*new Date();k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");// eslint-disable-line no-unexpected-multiline
ym(52347334, "init", {// eslint-disable-line no-undef
id:52347334,
clickmap:true,
trackLinks:true,
accurateTrackBounce:true
});
this.yaMetricsDone = true;
}
}
}
export default vueComponent(App);
//-----------------------------------------------------------------------------
</script>
@@ -237,68 +260,36 @@ 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 magenta !important;
}
.icon-rotate {
vertical-align: middle;
animation: rotating 2s linear infinite;
}
@keyframes rotating {
from {
transform: rotate(0deg);
} to {
transform: rotate(360deg);
}
}
.notify-button-icon {
font-size: 16px !important;
}
@font-face {

View File

@@ -1,20 +1,19 @@
<template>
<el-container>
<div>
Раздел Book в разработке
</el-container>
</div>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import vueComponent from '../../vueComponent.js';
export default @Component({
})
class Book extends Vue {
class Book {
created() {
}
}
export default vueComponent(Book);
//-----------------------------------------------------------------------------
</script>

View File

@@ -1,20 +1,19 @@
<template>
<el-container>
<div>
Раздел Card в разработке
</el-container>
</div>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import vueComponent from '../../vueComponent.js';
export default @Component({
})
class Card extends Vue {
class Card {
created() {
}
}
export default vueComponent(Card);
//-----------------------------------------------------------------------------
</script>

View File

@@ -1,24 +1,19 @@
<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>
<div>
<router-view v-slot="{ Component }">
<keep-alive>
<router-view></router-view>
<component :is="Component" />
</keep-alive>
</el-tabs>
</el-container>
</router-view>
</div>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import vueComponent from '../vueComponent.js';
import _ from 'lodash';
const rootRoute = '/cardindex';
const selfRoute = '/cardindex';
const tab2Route = [
'/cardindex/search',
'/cardindex/card',
@@ -27,20 +22,32 @@ const tab2Route = [
];
let lastActiveTab = null;
export default @Component({
const componentOptions = {
watch: {
selectedTab: function(newValue, oldValue) {
selectedTab: function(newValue) {
lastActiveTab = newValue;
this.setRouteByTab(newValue);
},
curRoute: function(newValue, oldValue) {
curRoute: function(newValue) {
this.setTabByRoute(newValue);
},
},
})
class CardIndex extends Vue {
};
class CardIndex {
_options = componentOptions;
selectedTab = null;
created() {
this.$watch(
() => this.$route.path,
(newValue) => {
if (newValue == '/cardindex' && this.isReader) {
this.$router.replace({ path: '/reader' });
}
}
)
}
mounted() {
this.setTabByRoute(this.curRoute);
}
@@ -51,7 +58,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);
}
}
@@ -63,12 +70,22 @@ class CardIndex extends Vue {
}
}
get mode() {
return this.$store.state.config.mode;
}
get curRoute() {
const m = this.$route.path.match(/^(\/[^\/]*\/[^\/]*).*$/i);
const m = this.$route.path.match(/^(\/[^/]*\/[^/]*).*$/i);
return (m ? m[1] : this.$route.path);
}
get isReader() {
return (this.mode !== null && (this.mode == 'reader' || this.mode == 'omnireader' || this.mode == 'liberama.top'));
}
}
export default vueComponent(CardIndex);
//-----------------------------------------------------------------------------
</script>

View File

@@ -1,20 +1,19 @@
<template>
<el-container>
<div>
Раздел History в разработке
</el-container>
</div>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import vueComponent from '../../vueComponent.js';
export default @Component({
})
class History extends Vue {
class History {
created() {
}
}
export default vueComponent(History);
//-----------------------------------------------------------------------------
</script>

View File

@@ -1,20 +1,19 @@
<template>
<el-container>
<div>
Раздел Search в разработке
</el-container>
</div>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import vueComponent from '../../vueComponent.js';
export default @Component({
})
class Search extends Vue {
class Search {
created() {
}
}
export default vueComponent(Search);
//-----------------------------------------------------------------------------
</script>

View File

@@ -0,0 +1,366 @@
<template>
<Window ref="window" width="600px" height="95%" @close="close">
<template #header>
Настроить закладки
</template>
<div class="col column fit">
<div class="row items-center top-panel bg-grey-3">
<q-btn :disabled="!selected" class="q-mr-md" round dense color="blue" icon="la la-check" size="16px" @click.stop="openSelected">
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
Открыть выбранную закладку
</q-tooltip>
</q-btn>
<q-input ref="search" v-model="search" class="col" rounded outlined dense bg-color="white" placeholder="Найти">
<template #append>
<q-icon v-if="search !== ''" name="la la-times" class="cursor-pointer" @click="resetSearch" />
</template>
</q-input>
</div>
<div class="col row">
<div class="left-panel column items-center no-wrap bg-grey-3">
<q-btn class="q-my-sm" round dense color="blue" icon="la la-plus" size="14px" @click.stop="addBookmark">
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
Добавить закладку
</q-tooltip>
</q-btn>
<q-btn :disabled="!ticked.length" class="q-mb-sm" round dense color="blue" icon="la la-minus" size="14px" @click.stop="delBookmark">
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
Удалить отмеченные закладки
</q-tooltip>
</q-btn>
<q-btn :disabled="!selected || selected.indexOf('r-') == 0" class="q-mb-sm" round dense color="blue" icon="la la-edit" size="14px" @click.stop="editBookmark">
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
Редактировать закладку
</q-tooltip>
</q-btn>
<q-btn :disabled="!ticked.length" class="q-mb-sm" round dense color="blue" icon="la la-arrow-up" size="14px" @click.stop="moveBookmark(false)">
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
Переместить отмеченные вверх
</q-tooltip>
</q-btn>
<q-btn :disabled="!ticked.length" class="q-mb-sm" round dense color="blue" icon="la la-arrow-down" size="14px" @click.stop="moveBookmark(true)">
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
Переместить отмеченные вниз
</q-tooltip>
</q-btn>
<q-btn class="q-mb-sm" round dense color="blue" icon="la la-broom" size="14px" @click.stop="setDefaultBookmarks">
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
Установить по умолчанию
</q-tooltip>
</q-btn>
<div class="space" />
</div>
<div class="col fit tree">
<div v-show="nodes.length" class="checkbox-tick-all">
<q-checkbox v-model="tickAll" size="36px" label="Выбрать все" @update:model-value="makeTickAll" />
</div>
<q-tree
v-model:selected="selected"
v-model:ticked="ticked"
v-model:expanded="expanded"
class="q-my-xs"
:nodes="nodes"
node-key="key"
tick-strategy="leaf"
selected-color="black"
:filter="search"
no-nodes-label="Закладок пока нет"
no-results-label="Ничего не найдено"
>
<template #default-header="p">
<div class="q-px-xs" :class="{selected: selected == p.key}">
{{ p.node.label }}
</div>
</template>
</q-tree>
</div>
</div>
</div>
</Window>
</template>
<script>
//-----------------------------------------------------------------------------
import vueComponent from '../../vueComponent.js';
import _ from 'lodash';
import Window from '../../share/Window.vue';
import * as lu from '../linkUtils';
import rstore from '../../../store/modules/reader';
const componentOptions = {
components: {
Window,
},
watch: {
ticked() {
this.checkAllTicked();
},
}
};
class BookmarkSettings {
_options = componentOptions;
_props = {
libs: Object,
addBookmarkVisible: Boolean,
};
search = '';
selected = '';
ticked = [];
expanded = [];
tickAll = false;
created() {
this.afterInit = true;
}
mounted() {
}
init() {
this.$refs.window.init();
}
get nodes() {
const result = [];
const expanded = [];
this.links = {};
this.libs.groups.forEach(group => {
const rkey = `r-${group.r}`;
const g = {label: group.r, key: rkey, children: []};
this.links[rkey] = {l: group.r, c: ''};
group.list.forEach(link => {
const key = link.l;
g.children.push({
label: (link.c ? link.c + ' ': '') + lu.removeOrigin(link.l),
key
});
this.links[key] = link;
if (link.l == this.libs.startLink && expanded.indexOf(rkey) < 0) {
expanded.push(rkey);
}
});
result.push(g);
});
if (this.afterInit) {
this.$nextTick(() => {
this.expanded = expanded;
});
this.afterInit = false;
}
return result;
}
makeTickAll() {
if (this.tickAll) {
const newTicked = [];
for (const key of Object.keys(this.links)) {
if (key.indexOf('r-') != 0)
newTicked.push(key);
}
this.ticked = newTicked;
} else {
this.ticked = [];
}
}
checkAllTicked() {
const ticked = new Set(this.ticked);
let newTickAll = !!(this.nodes.length);
for (const key of Object.keys(this.links)) {
if (key.indexOf('r-') != 0 && !ticked.has(key))
newTickAll = false;
}
this.tickAll = newTickAll;
}
resetSearch() {
this.search = '';
this.$refs.search.focus();
}
openSelected() {
if (!this.selected)
return;
if (this.selected.indexOf('r-') === 0) {//rootLink
this.$emit('do-action', {action: 'setRootLink', data: this.links[this.selected].l});
} else {//selectedLink
this.$emit('do-action', {action: 'setSelectedLink', data: this.links[this.selected].l});
}
this.close();
}
editBookmark() {
this.$emit('do-action', {action: 'editBookmark', data: {link: this.links[this.selected].l, desc: this.links[this.selected].c}});
}
addBookmark() {
this.$emit('do-action', {action: 'addBookmark'});
}
async delBookmark() {
const newLibs = _.cloneDeep(this.libs);
if (await this.$root.stdDialog.confirm(`Подтвердите удаление ${this.ticked.length} закладок:`, ' ')) {
const ticked = new Set(this.ticked);
for (let i = newLibs.groups.length - 1; i >= 0; i--) {
const g = newLibs.groups[i];
for (let j = g.list.length - 1; j >= 0; j--) {
if (ticked.has(g.list[j].l)) {
delete g.list[j];
}
}
g.list = g.list.filter(v => v);
if (!g.list.length)
delete newLibs.groups[i];
else {
const item = lu.getListItemByLink(g.list, g.s);
if (!item)
g.s = g.list[0].l;
}
}
newLibs.groups = newLibs.groups.filter(v => v);
this.ticked = [];
this.selected = '';
this.$emit('do-action', {action: 'setLibs', data: newLibs});
}
}
moveBookmark(down = false) {
const newLibs = _.cloneDeep(this.libs);
const ticked = new Set(this.ticked);
let moved = false;
let prevFull = false;
if (!down) {
for (let i = 0; i < newLibs.groups.length; i++) {
const g = newLibs.groups[i];
let count = 0;
for (let j = 0; j < g.list.length; j++) {
if (ticked.has(g.list[j].l)) {
if (j > 0 && !ticked.has(g.list[j - 1].l)) {
[g.list[j], g.list[j - 1]] = [g.list[j - 1], g.list[j]];
moved = true;
}
count++;
}
}
if (count == g.list.length && !prevFull && i > 0) {
const gs = newLibs.groups;
[gs[i], gs[i - 1]] = [gs[i - 1], gs[i]];
moved = true;
} else
prevFull = (count == g.list.length);
}
} else {
for (let i = newLibs.groups.length - 1; i >= 0; i--) {
const g = newLibs.groups[i];
let count = 0;
for (let j = g.list.length - 1; j >= 0; j--) {
if (ticked.has(g.list[j].l)) {
if (j < g.list.length - 1 && !ticked.has(g.list[j + 1].l)) {
[g.list[j], g.list[j + 1]] = [g.list[j + 1], g.list[j]];
moved = true;
}
count++;
}
}
if (count == g.list.length && !prevFull && i < newLibs.groups.length - 1) {
const gs = newLibs.groups;
[gs[i], gs[i + 1]] = [gs[i + 1], gs[i]];
moved = true;
} else
prevFull = (count == g.list.length);
}
}
if (moved)
this.$emit('do-action', {action: 'setLibs', data: newLibs});
}
async setDefaultBookmarks() {
const result = await this.$root.stdDialog.prompt(`Введите 'да' для сброса всех закладок в предустановленные значения:`, ' ', {
inputValidator: (str) => { if (str && str.toLowerCase() === 'да') return true; else return 'Удаление не подтверждено'; },
});
if (result && result.value && result.value.toLowerCase() == 'да') {
this.$emit('do-action', {action: 'setLibs', data: _.cloneDeep(
Object.assign({helpShowed: true}, rstore.libsDefaults)
)});
}
}
close() {
this.afterInit = false;
this.$emit('close');
}
keyHook(event) {
if (this.addBookmarkVisible)
return false;
if (event.type == 'keydown' && event.key == 'Escape') {
this.close();
return true;
}
return false;
}
}
export default vueComponent(BookmarkSettings);
//-----------------------------------------------------------------------------
</script>
<style scoped>
.top-panel {
height: 50px;
border-bottom: 1px solid gray;
padding: 0 10px 0 12px;
}
.left-panel {
width: 60px;
height: 100%;
border-right: 1px solid gray;
overflow-x: hidden;
overflow-y: auto;
}
.tree {
padding: 0px 10px 10px 10px;
overflow-x: auto;
overflow-y: auto;
}
.selected {
text-shadow: 0 0 20px yellow, 0 0 15px yellow, 0 0 10px yellow, 0 0 10px yellow, 0 0 5px yellow;
}
.checkbox-tick-all {
border-bottom: 1px solid #bbbbbb;
margin-bottom: 7px;
padding: 5px 5px 2px 16px;
}
.space {
min-height: 1px;
width: 1px;
}
</style>

View File

@@ -0,0 +1,912 @@
<template>
<Window ref="window" margin="2px" @close="close">
<template #header>
{{ header }}
</template>
<template #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>
<span class="full-screen-button row justify-center items-center" @mousedown.stop @click="changeScale(0.1)">
<q-icon name="la la-plus" size="16px" />
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Увеличить масштаб</q-tooltip>
</span>
<span class="full-screen-button row justify-center items-center" @mousedown.stop @click="changeScale(-0.1)">
<q-icon name="la la-minus" size="16px" />
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">Уменьшить масштаб</q-tooltip>
</span>
<span class="full-screen-button row justify-center items-center" @mousedown.stop @click="showHelp">
<q-icon name="la la-question-circle" 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
ref="rootLink"
v-model="rootLink"
class="q-mr-sm"
:options="rootLinkOptions"
style="width: 230px"
dropdown-icon="la la-angle-down la-sm"
rounded outlined dense emit-value map-options display-value-sanitize options-sanitize
@popup-show="onSelectPopupShow" @popup-hide="onSelectPopupHide"
>
<template #prepend>
<q-btn class="q-mr-xs" round dense color="blue" icon="la la-plus" size="12px" @click.stop="addBookmark">
<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" size="12px" @click.stop="bookmarkSettings">
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
Настроить закладки
</q-tooltip>
</q-btn>
</template>
<template #selected>
<div style="overflow: hidden; white-space: nowrap;">
{{ rootLinkWithoutProtocol }}
</div>
</template>
</q-select>
<q-select
ref="selectedLink"
v-model="selectedLink"
class="q-mr-sm"
:options="selectedLinkOptions"
style="width: 50px"
dropdown-icon="la la-angle-down la-sm"
rounded outlined dense emit-value map-options hide-selected display-value-sanitize options-sanitize
@popup-show="onSelectPopupShow" @popup-hide="onSelectPopupHide"
>
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
Закладки
</q-tooltip>
</q-select>
<q-input
ref="input"
v-model="bookUrl"
class="col q-mr-sm"
rounded outlined dense
bg-color="white"
placeholder="Скопируйте сюда URL книги"
@focus="selectAllOnFocus" @keydown="bookUrlKeyDown"
>
<template #prepend>
<q-btn class="q-mr-xs" round dense color="blue" icon="la la-home" size="12px" @click="goToLink(selectedLink)">
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
Вернуться на стартовую страницу
</q-tooltip>
</q-btn>
<q-btn :disabled="!bookUrl" round dense color="blue" icon="la la-angle-double-down" size="12px" @click="openBookUrlInFrame">
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
Загрузить URL во фрейм
</q-tooltip>
</q-btn>
</template>
<template #append>
<q-btn round dense color="blue" icon="la la-cog" size="12px" @click.stop="optionsVisible = true">
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
Опции
</q-tooltip>
</q-btn>
</template>
</q-input>
<q-btn :disabled="!bookUrl" 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 ref="frameBox" class="col fit" style="position: relative;">
<div ref="frameWrap" class="overflow-hidden">
<iframe v-if="frameVisible" ref="frame" :src="frameSrc" frameborder="0"></iframe>
</div>
<div v-show="transparentLayoutVisible" ref="transparentLayout" class="fit transparent-layout" @click="transparentLayoutClick"></div>
</div>
<Dialog ref="dialogAddBookmark" v-model="addBookmarkVisible">
<template #header>
<div class="row items-center">
<q-icon class="q-mr-sm" name="la la-bookmark" size="28px"></q-icon>
<div v-if="addBookmarkMode == 'edit'">
Редактировать закладку
</div>
<div v-else>
Добавить закладку
</div>
</div>
</template>
<div class="q-mx-md row">
<q-input
ref="bookmarkLink"
v-model="bookmarkLink"
class="col q-mr-sm"
outlined dense
bg-color="white"
placeholder="Ссылка для закладки" maxlength="2000" @focus="selectAllOnFocus" @keydown="bookmarkLinkKeyDown"
>
</q-input>
<q-select
ref="defaultRootLink"
v-model="defaultRootLink"
class="q-mr-sm"
:options="defaultRootLinkOptions"
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
ref="bookmarkDesc"
v-model="bookmarkDesc"
class="col q-mr-sm"
outlined dense
bg-color="white"
placeholder="Описание" style="width: 400px" maxlength="100" @focus="selectAllOnFocus" @keydown="bookmarkDescKeyDown"
>
</q-input>
</div>
<template #footer>
<q-btn v-close-popup class="q-px-md q-ml-sm" dense no-caps>
Отмена
</q-btn>
<q-btn :disabled="!bookmarkLink" class="q-px-md q-ml-sm" color="primary" dense no-caps @click="okAddBookmark">
OK
</q-btn>
</template>
</Dialog>
<Dialog ref="options" v-model="optionsVisible">
<template #header>
<div class="row items-center">
<q-icon class="q-mr-sm" name="la la-cog" size="28px"></q-icon>
Опции
</div>
</template>
<div class="q-mx-md column">
<q-checkbox v-model="closeAfterSubmit" size="36px" label="Закрыть окно при отправке ссылки в читалку" />
<q-checkbox v-model="openInFrameOnEnter" size="36px" label="Открывать ссылку во фрейме при нажатии 'Enter'" />
<q-checkbox v-model="openInFrameOnAdd" size="36px" label="Активировать новую закладку после добавления" />
</div>
<template #footer>
<q-btn class="q-px-md q-ml-sm" color="primary" dense no-caps @click="optionsVisible = false">
OK
</q-btn>
</template>
</Dialog>
</div>
<BookmarkSettings
v-if="bookmarkSettingsActive"
ref="bookmarkSettings"
:libs="libs"
:add-bookmark-visible="addBookmarkVisible"
@do-action="doAction" @close="closeBookmarkSettings"
>
</BookmarkSettings>
</Window>
</template>
<script>
//-----------------------------------------------------------------------------
import vueComponent from '../vueComponent.js';
import _ from 'lodash';
import Window from '../share/Window.vue';
import Dialog from '../share/Dialog.vue';
import BookmarkSettings from './BookmarkSettings/BookmarkSettings.vue';
import rstore from '../../store/modules/reader';
import * as utils from '../../share/utils';
import * as lu from './linkUtils';
const proxySubst = {
'http://flibusta.is': 'http://b.liberama.top:23480',
'http://fantasy-worlds.org': 'http://b.liberama.top:23580',
};
const componentOptions = {
components: {
Window,
Dialog,
BookmarkSettings
},
watch: {
libs() {
this.loadLibs();
},
defaultRootLink() {
this.updateBookmarkLink();
},
bookUrl(newValue) {
const value = lu.addProtocol(newValue);
const subst = this.makeProxySubst(value, true);
if (value != subst) {
this.$nextTick(() => {
this.bookUrl = subst;
});
}
},
bookmarkLink(newValue) {
const value = lu.addProtocol(newValue);
const subst = this.makeProxySubst(value, true);
if (value != subst) {
this.$nextTick(() => {
this.bookmarkLink = subst;
});
}
},
closeAfterSubmit(newValue) {
this.commitProp('closeAfterSubmit', newValue);
},
openInFrameOnEnter(newValue) {
this.commitProp('openInFrameOnEnter', newValue);
},
openInFrameOnAdd(newValue) {
this.commitProp('openInFrameOnAdd', newValue);
},
rootLink() {
this.rootLinkInput();
},
selectedLink() {
this.selectedLinkInput();
},
}
};
class ExternalLibs {
_options = componentOptions;
ready = false;
frameVisible = false;
rootLink = '';
selectedLink = '';
frameSrc = '';
bookUrl = '';
libs = {};
fullScreenActive = false;
transparentLayoutVisible = false;
addBookmarkVisible = false;
optionsVisible = false;
addBookmarkMode = '';
bookmarkLink = '';
bookmarkDesc = '';
defaultRootLink = '';
bookmarkSettingsActive = false;
closeAfterSubmit = false;
openInFrameOnEnter = false;
openInFrameOnAdd = false;
frameScale = 1;
created() {
this.oldStartLink = '';
this.justOpened = true;
this.$root.addEventHook('key', this.keyHook);
this.$root.addEventHook('resize', async() => {
await utils.sleep(200);
this.frameResize();
});
document.addEventListener('fullscreenchange', () => {
this.fullScreenActive = (document.fullscreenElement !== null);
});
this.debouncedGoToLink = _.debounce((link) => {
this.goToLink(link);
}, 100, {'maxWait':200});
//this.commit = this.$store.commit;
//this.commit('reader/setLibs', rstore.libsDefaults);
}
mounted() {
(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);
} 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});
}
commitProp(prop, value) {
let libs = _.cloneDeep(this.libs);
libs[prop] = value;
this.commitLibs(libs);
}
loadLibs() {
const libs = this.libs;
if (!libs.helpShowed) {
this.showHelp();
(async() => {
await utils.sleep(1000);
this.commitProp('helpShowed', true);
})();
}
this.selectedLink = libs.startLink;
this.closeAfterSubmit = libs.closeAfterSubmit || false;
this.openInFrameOnEnter = libs.openInFrameOnEnter || false;
this.openInFrameOnAdd = libs.openInFrameOnAdd || false;
this.frameScale = 1;
const index = lu.getSafeRootIndexByUrl(this.libs.groups, this.selectedLink);
if (index >= 0)
this.frameScale = this.libs.groups[index].frameScale || 1;
this.updateStartLink();
}
doAction(event) {
switch (event.action) {
case 'setLibs': this.commitLibs(event.data); break;
case 'setRootLink': this.rootLink = event.data; this.rootLinkInput(); break;
case 'setSelectedLink': this.selectedLink = event.data; this.selectedLinkInput(); break;
case 'editBookmark': this.addBookmark('edit', event.data.link, event.data.desc); break;
case 'addBookmark': this.addBookmark('add'); break;
}
}
get mode() {
return this.$store.state.config.mode;
}
get header() {
let result = (this.ready ? 'Сетевая библиотека' : 'Загрузка...');
if (this.ready && this.selectedLink) {
result += ` | ${(this.libs.comment ? this.libs.comment + ' ': '') + lu.removeProtocol(this.libs.startLink)}`;
}
this.$root.setAppTitle(result);
return result;
}
get rootLinkWithoutProtocol() {
return lu.removeProtocol(this.rootLink);
}
updateSelectedLinkByRoot() {
if (!this.ready)
return;
const index = lu.getSafeRootIndexByUrl(this.libs.groups, this.rootLink);
if (index >= 0)
this.selectedLink = this.libs.groups[index].s;
else
this.selectedLink = '';
}
updateStartLink(force) {
if (!this.ready)
return;
let index = -1;
try {
this.rootLink = lu.getOrigin(this.selectedLink);
index = lu.getRootIndexByUrl(this.libs.groups, this.rootLink);
} catch(e) {
//
}
if (index >= 0) {
let libs = _.cloneDeep(this.libs);
const com = this.getCommentByLink(libs.groups[index].list, this.selectedLink);
if (libs.groups[index].s != this.selectedLink ||
libs.startLink != this.selectedLink ||
libs.comment != com) {
libs.groups[index].s = this.selectedLink;
libs.startLink = this.selectedLink;
libs.comment = com;
this.commitLibs(libs);
}
if (force || this.oldStartLink != libs.startLink) {
this.oldStartLink = libs.startLink;
this.debouncedGoToLink(this.selectedLink);
}
} else {
this.rootLink = '';
this.selectedLink = '';
this.debouncedGoToLink(this.selectedLink);
}
}
get rootLinkOptions() {
let result = [];
if (!this.ready)
return result;
this.libs.groups.forEach(group => {
result.push({label: lu.removeProtocol(group.r), value: group.r});
});
return result;
}
get defaultRootLinkOptions() {
let result = [];
rstore.libsDefaults.groups.forEach(group => {
result.push({label: lu.removeProtocol(group.r), value: group.r});
});
return result;
}
get selectedLinkOptions() {
let result = [];
if (!this.ready)
return result;
const index = lu.getSafeRootIndexByUrl(this.libs.groups, this.rootLink);
if (index >= 0) {
this.libs.groups[index].list.forEach(link => {
result.push({label: (link.c ? link.c + ' ': '') + lu.removeOrigin(link.l), value: link.l});
});
}
return result;
}
openBookUrlInFrame() {
if (this.bookUrl) {
this.goToLink(lu.addProtocol(this.bookUrl));
}
}
goToLink(link) {
if (!this.ready || !link)
return;
if (!link) {
this.frameVisible = false;
return;
}
this.frameSrc = this.makeProxySubst(link);
this.frameVisible = false;
this.$nextTick(() => {
this.frameVisible = true;
this.$nextTick(() => {
if (this.$refs.frame) {
this.$refs.frame.contentWindow.focus();
this.frameResize();
}
});
});
}
frameResize() {
this.$refs.frameWrap.style = 'width: 1px; height: 1px;';
this.$nextTick(() => {
if (this.$refs.frame) {
const w = this.$refs.frameBox.offsetWidth;
const h = this.$refs.frameBox.offsetHeight;
const normalSize = `width: ${w}px; height: ${h}px;`;
this.$refs.frameWrap.style = normalSize;
if (this.frameScale != 1) {
const s = this.frameScale;
this.$refs.frame.style = `width: ${w/s}px; height: ${h/s}px; transform: scale(${s}); transform-origin: 0 0;`;
} else {
this.$refs.frame.style = normalSize;
}
}
});
}
changeScale(delta) {
if ((this.frameScale > 0.1 && delta <= 0) || (this.frameScale < 5 && delta >= 0)) {
this.frameScale = _.round(this.frameScale + delta, 1);
const index = lu.getSafeRootIndexByUrl(this.libs.groups, this.selectedLink);
if (index >= 0) {
let libs = _.cloneDeep(this.libs);
libs.groups[index].frameScale = this.frameScale;
this.commitLibs(libs);
}
this.frameResize();
this.$root.notify.success(`Масштаб изменен: ${(this.frameScale*100).toFixed(0)}%`, '', {position: 'bottom-right'});
}
}
getCommentByLink(list, link) {
const item = lu.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 (!reverse && 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.updateSelectedLinkByRoot();
this.updateStartLink(true);
}
selectedLinkInput() {
this.updateStartLink(true);
}
submitUrl() {
if (this.bookUrl) {
this.sendMessage({type: 'submitUrl', data: {
url: this.bookUrl,
force: true
}});
this.bookUrl = '';
if (this.closeAfterSubmit)
this.close();
}
}
addBookmark(mode = 'add', link = '', desc = '') {
if (mode == 'edit') {
this.editBookmarkLink = this.bookmarkLink = link;
this.editBookmarkDesc = this.bookmarkDesc = desc;
} else {
this.bookmarkLink = this.bookUrl;
this.bookmarkDesc = '';
}
this.addBookmarkMode = mode;
this.addBookmarkVisible = true;
this.$nextTick(async() => {
await this.$refs.dialogAddBookmark.waitShown();
this.$refs.bookmarkLink.focus();
});
}
updateBookmarkLink() {
const index = lu.getSafeRootIndexByUrl(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 = '';
}
}
bookmarkLinkKeyDown(event) {
if (event.key == 'Enter') {
this.$refs.bookmarkDesc.focus();
event.preventDefault();
}
}
bookmarkDescKeyDown(event) {
if (event.key == 'Enter') {
event.stopPropagation();
event.preventDefault();
this.okAddBookmark();
}
}
async okAddBookmark() {
if (!this.bookmarkLink)
return;
const link = (this.addBookmarkMode == 'edit' ? lu.addProtocol(this.editBookmarkLink) : lu.addProtocol(this.bookmarkLink));
let index = -1;
try {
index = lu.getRootIndexByUrl(this.libs.groups, link);
} catch (e) {
await this.$root.stdDialog.alert('Неверный формат ссылки', 'Ошибка');
return;
}
let libs = _.cloneDeep(this.libs);
//добавление
//есть группа в закладках
if (index >= 0) {
const item = lu.getListItemByLink(libs.groups[index].list, link);
//редактирование
if (item && this.addBookmarkMode == 'edit') {
if (item) {
//редактируем
item.l = link;
item.c = this.bookmarkDesc;
this.commitLibs(libs);
} else {
await this.$root.stdDialog.alert('Не удалось отредактировать закладку', 'Ошибка');
}
} else if (!item) {
//добавляем
if (libs.groups[index].list.length >= 100) {
await this.$root.stdDialog.alert('Достигнут предел количества закладок для этого сайта', 'Ошибка');
return;
}
libs.groups[index].list.push({l: link, c: this.bookmarkDesc});
if (this.openInFrameOnAdd) {
libs.startLink = link;
libs.comment = this.bookmarkDesc;
}
this.commitLibs(libs);
} else if (item.c != this.bookmarkDesc) {
if (await this.$root.stdDialog.confirm(`Такая закладка уже существует с другим описанием.<br>` +
`Заменить '${this.$root.sanitize(item.c)}' на '${this.$root.sanitize(this.bookmarkDesc)}'?`, ' ')) {
item.c = this.bookmarkDesc;
this.commitLibs(libs);
} else
return;
} else {
await this.$root.stdDialog.alert('Такая закладка уже существует', ' ');
return;
}
} else {//нет группы в закладках
if (libs.groups.length >= 100) {
await this.$root.stdDialog.alert('Достигнут предел количества различных сайтов в закладках', 'Ошибка');
return;
}
//добавляем сначала группу
libs.groups.push({r: lu.getOrigin(link), s: link, list: []});
index = lu.getSafeRootIndexByUrl(libs.groups, link);
if (index >= 0)
libs.groups[index].list.push({l: link, c: this.bookmarkDesc});
if (this.openInFrameOnAdd) {
libs.startLink = link;
libs.comment = this.bookmarkDesc;
}
this.commitLibs(libs);
}
this.addBookmarkVisible = false;
}
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') {
if (!this.openInFrameOnEnter) {
this.submitUrl();
} else {
if (this.bookUrl)
this.goToLink(this.bookUrl);
}
event.preventDefault();
}
}
bookmarkSettings() {
this.bookmarkSettingsActive = true;
this.$nextTick(() => {
this.$refs.bookmarkSettings.init();
});
}
closeBookmarkSettings() {
this.bookmarkSettingsActive = false;
}
showHelp() {
this.$root.stdDialog.alert(`
<p>Окно 'Сетевая библиотека' позволяет открывать ссылки в читалке без переключения между окнами,
что особенно актуально для мобильных устройств. Имеется возможность управлять закладками
на понравившиеся ресурсы, книги или страницы авторов. Открытие ссылок и навигация происходят во фрейме, но,
к сожалению, в нем открываются не все страницы.</p>
<p>Доступ к сайтам <span style="color: blue">http://flibusta.is</span> и <span style="color: blue">http://fantasy-worlds.org</span> работает через прокси.
<br><span style="color: red"><b>ПРЕДУПРЕЖДЕНИЕ!</b></span>
Доступ предназначен только для просмотра и скачивания книг. Авторизоваться на этих сайтах
из фрейма категорически не рекомендуется, т.к. ваше подключение не защищено и данные могут попасть
к третьим лицам.
</p>
<p>Из-за проблем с безопасностью, навигация 'вперед-назад' во фрейме осуществляется с помощью контекстного меню правой кнопкой мыши.
На мобильных устройствах для этого служит системная клавиша 'Назад (стрелка влево)' и опция 'Вперед (стрелка вправо)' в меню браузера.
</p>
<p>Приятного пользования ;-)
</p>
`, 'Справка', {iconName: 'la la-info-circle'});
}
keyHook(event) {
if (this.$root.getRootRoute() == '/external-libs') {
if (this.$root.stdDialog.active)
return false;
if (this.bookmarkSettingsActive && this.$refs.bookmarkSettings.keyHook(event))
return true;
if (this.addBookmarkVisible || this.optionsVisible)
return false;
if (event.type == 'keydown' && event.key == 'F4') {
this.addBookmark();
return true;
}
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;
}
}
export default vueComponent(ExternalLibs);
//-----------------------------------------------------------------------------
</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

@@ -0,0 +1,48 @@
export function addProtocol(url) {
if ((url.indexOf('http://') != 0) && (url.indexOf('https://') != 0))
return 'http://' + url;
return url;
}
export function removeProtocol(url) {
return url.replace(/(^\w+:|^)\/\//, '');
}
export function getOrigin(url) {
const parsed = new URL(url);
return parsed.origin;
}
export function removeOrigin(url) {
const parsed = new URL(url);
const result = url.substring(parsed.origin.length);
return (result ? result : '/');
}
export function getRootIndexByUrl(groups, url) {
const origin = getOrigin(url);
for (let i = 0; i < groups.length; i++) {
if (groups[i].r == origin)
return i;
}
return -1;
}
export function getSafeRootIndexByUrl(groups, url) {
let index = -1;
try {
index = getRootIndexByUrl(groups, url);
} catch(e) {
//
}
return index;
}
export function getListItemByLink(list, link) {
for (const item of list) {
if (item.l == link)
return item;
}
return null;
}

View File

@@ -1,20 +1,19 @@
<template>
<el-container>
<div>
Раздел Help в разработке
</el-container>
</div>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import vueComponent from '../vueComponent.js';
export default @Component({
})
class Help extends Vue {
class Help {
created() {
}
}
export default vueComponent(Help);
//-----------------------------------------------------------------------------
</script>

View File

@@ -1,20 +1,19 @@
<template>
<el-container>
<div>
Раздел Income в разработке
</el-container>
</div>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import vueComponent from '../vueComponent.js';
export default @Component({
})
class Income extends Vue {
class Income {
created() {
}
}
export default vueComponent(Income);
//-----------------------------------------------------------------------------
</script>

View File

@@ -1,20 +1,19 @@
<template>
<el-container>
<div>
Страница не найдена
</el-container>
</div>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import vueComponent from '../vueComponent.js';
export default @Component({
})
class NotFound404 extends Vue {
class NotFound404 {
created() {
}
}
export default vueComponent(NotFound404);
//-----------------------------------------------------------------------------
</script>

View File

@@ -6,15 +6,12 @@
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import vueComponent from '../../vueComponent.js';
import {sleep} from '../../../share/utils';
import {clickMap, clickMapText} from '../share/clickMap';
export default @Component({
})
class ClickMapPage extends Vue {
class ClickMapPage {
fontSize = '200%';
created() {
@@ -53,6 +50,8 @@ class ClickMapPage extends Vue {
await sleep(5000);
}
}
export default vueComponent(ClickMapPage);
//-----------------------------------------------------------------------------
</script>

View File

@@ -0,0 +1,452 @@
<template>
<Window ref="window" width="600px" @close="close">
<template #header>
Оглавление/закладки
</template>
<div class="bg-grey-3 row">
<q-tabs
v-model="selectedTab"
active-color="black"
active-bg-color="white"
indicator-color="white"
dense
no-caps
inline-label
class="no-mp bg-grey-4 text-grey-7"
>
<q-tab name="contents" icon="la la-list" label="Оглавление" />
<q-tab name="images" icon="la la-image" label="Изображения" />
<q-tab name="bookmarks" icon="la la-bookmark" label="Закладки" />
</q-tabs>
</div>
<div class="q-mb-sm" />
<div v-show="selectedTab == 'contents'" class="tab-panel">
<div>
<div v-for="item in contents" :key="item.key" class="column" style="width: 540px">
<div class="row q-px-sm no-wrap" :class="{'item': !item.isBookPos, 'item-book-pos': item.isBookPos}">
<div v-if="item.list.length" class="row justify-center items-center expand-button clickable" @click="expandClick(item.key)">
<q-icon name="la la-caret-right" class="icon" :class="{'expanded-icon': item.expanded}" color="green-8" size="20px" />
</div>
<div v-else class="no-expand-button clickable" @click="setBookPos(item.offset)">
<q-icon name="la la-stop" class="icon" style="visibility: hidden" size="20px" />
</div>
<div class="col row clickable" @click="setBookPos(item.offset)">
<div :style="item.indentStyle"></div>
<div class="q-mr-sm col overflow-hidden column justify-center" :style="item.labelStyle" v-html="item.label"></div>
<div class="column justify-center">
{{ item.perc }}%
</div>
</div>
</div>
<div v-if="item.expanded" :ref="`subitem${item.key}`" class="subitems-transition">
<div v-for="subitem in item.list" :key="subitem.key" class="row q-px-sm no-wrap" :class="{'subitem': !subitem.isBookPos, 'subitem-book-pos': subitem.isBookPos}">
<div class="col row clickable" @click="setBookPos(subitem.offset)">
<div class="no-expand-button"></div>
<div :style="subitem.indentStyle"></div>
<div class="q-mr-sm col overflow-hidden column justify-center" :style="item.labelStyle" v-html="subitem.label"></div>
<div class="column justify-center">
{{ subitem.perc }}%
</div>
</div>
</div>
</div>
</div>
<div v-if="!contents.length" class="column justify-center items-center" style="height: 100px">
Оглавление отсутствует
</div>
</div>
</div>
<div v-show="selectedTab == 'images'" class="tab-panel">
<div>
<div v-for="item in images" :key="item.key" class="column" style="width: 540px">
<div class="row q-px-sm no-wrap" :class="{'item': !item.isBookPos, 'item-book-pos': item.isBookPos}">
<div class="col row clickable" @click="setBookPos(item.offset)">
<div class="image-thumb-box row justify-center items-center">
<div v-show="!imageLoaded[item.id]" class="image-thumb column justify-center">
<i class="loading-img-icon la la-images"></i>
</div>
<img v-show="imageLoaded[item.id]" class="image-thumb" :src="imageSrc[item.id]" />
</div>
<div class="no-expand-button column justify-center items-center">
<div class="image-num">
{{ item.num }}
</div>
<div v-show="item.type == 'image/jpeg'" class="image-type it-jpg-color row justify-center">
JPG
</div>
<div v-show="item.type == 'image/png'" class="image-type it-png-color row justify-center">
PNG
</div>
<div v-show="!item.local" class="image-type it-net-color row justify-center">
INET
</div>
</div>
<div :style="item.indentStyle"></div>
<div class="q-mr-sm col overflow-hidden column justify-center" :style="item.labelStyle" v-html="item.label"></div>
<div class="column justify-center">
{{ item.perc }}%
</div>
</div>
</div>
</div>
<div v-if="!images.length" class="column justify-center items-center" style="height: 100px">
Изображения отсутствуют
</div>
</div>
</div>
<div v-show="selectedTab == 'bookmarks'" class="tab-panel">
<div class="column justify-center items-center" style="height: 100px">
Раздел находится в разработке
</div>
</div>
</Window>
</template>
<script>
//-----------------------------------------------------------------------------
import vueComponent from '../../vueComponent.js';
//import _ from 'lodash';
import Window from '../../share/Window.vue';
import * as utils from '../../../share/utils';
const componentOptions = {
components: {
Window,
},
watch: {
bookPos() {
this.updateBookPosSelection();
}
},
};
class ContentsPage {
_options = componentOptions;
_props = {
bookPos: Number,
isVisible: Boolean,
};
selectedTab = 'contents';
contents = [];
images = [];
imageSrc = [];
imageLoaded = [];
created() {
}
async init(currentBook, parsed) {
this.$refs.window.init();
//закладки
//проверим, надо ли обновлять списки
if (this.parsed == parsed) {
this.updateBookPosSelection();
return;
}
//далее формирование оглавления
this.parsed = parsed;
this.contents = [];
await this.$nextTick();
const pc = parsed.contents;
const ims = parsed.images;
const newpc = [];
if (pc.length) {//если есть оглавление
//преобразуем все, кроме первого, разделы body в title-subtitle
let curSubtitles = [];
let prevBodyIndex = -1;
for (let i = 0; i < pc.length; i++) {
const cont = pc[i];
if (prevBodyIndex != cont.bodyIndex)
curSubtitles = [];
prevBodyIndex = cont.bodyIndex;
if (cont.bodyIndex > 1) {
if (cont.inset < 1) {
newpc.push(Object.assign({}, cont, {subtitles: curSubtitles}));
} else {
curSubtitles.push(Object.assign({}, cont, {inset: cont.inset - 1}));
}
} else {
newpc.push(cont);
}
}
} else {//попробуем вытащить из images
for (let i = 0; i < ims.length; i++) {
const image = ims[i];
if (image.alt) {
newpc.push({paraIndex: image.paraIndex, title: image.alt, inset: 1, bodyIndex: 0, subtitles: []});
}
}
}
const prepareLabel = (title, bolder = false) => {
let titleParts = title.split('<p>');
const textParts = titleParts.filter(v => v).map(v => `<div>${utils.removeHtmlTags(v)}</div>`);
if (bolder && textParts.length > 1)
textParts[0] = `<b>${textParts[0]}</b>`;
return textParts.join('');
}
const getIndentStyle = inset => `width: ${inset*20}px`;
const getLabelStyle = (inset) => {
const fontSizes = ['110%', '100%', '90%', '85%'];
inset = (inset > 3 ? 3 : inset);
return `font-size: ${fontSizes[inset]}`;
};
//формируем newContents
let i = 0;
const newContents = [];
newpc.forEach((cont) => {
const label = prepareLabel(cont.title, true);
const indentStyle = getIndentStyle(cont.inset);
const labelStyle = getLabelStyle(cont.inset);
let j = 0;
const list = [];
cont.subtitles.forEach((sub) => {
const l = prepareLabel(sub.title);
const s = getIndentStyle(sub.inset + 1);
const ls = getLabelStyle(cont.inset + 1);
const p = parsed.para[sub.paraIndex];
list[j] = {perc: (p.offset/parsed.textLength*100).toFixed(2), label: l, key: j, offset: p.offset, indentStyle: s, labelStyle: ls};
j++;
});
const p = parsed.para[cont.paraIndex];
newContents[i] = {perc: (p.offset/parsed.textLength*100).toFixed(0), label, key: i, offset: p.offset, indentStyle, labelStyle, expanded: false, list};
i++;
});
this.contents = newContents;
//формируем newImages
const newImages = [];
for (i = 0; i < ims.length; i++) {
const image = ims[i];
const bin = parsed.binary[image.id];
const type = (bin ? bin.type : '');
const label = (image.alt ? image.alt : '<span style="font-size: 90%; color: #dddddd"><i>Без названия</i></span>');
const indentStyle = getIndentStyle(1);
const labelStyle = getLabelStyle(1);
const p = parsed.para[image.paraIndex];
newImages.push({perc: (p.offset/parsed.textLength*100).toFixed(0), label, key: i, offset: p.offset,
indentStyle, labelStyle, type, num: image.num, id: image.id, local: image.local});
}
this.images = newImages;
if (this.selectedTab == 'contents' && !this.contents.length && this.images.length)
this.selectedTab = 'images';
//выделим на bookPos
this.updateBookPosSelection();
//асинхронная загрузка изображений
this.imageSrc = [];
this.imageLoaded = [];
await utils.sleep(50);
(async() => {
for (i = 0; i < ims.length; i++) {
const {id, local} = ims[i];
const bin = this.parsed.binary[id];
if (local)
this.imageSrc[id] = (bin ? `data:${bin.type};base64,${bin.data}` : '');
else
this.imageSrc[id] = id;
this.imageLoaded[id] = true;
await utils.sleep(5);
}
})();
}
async updateBookPosSelection() {
if (!this.isVisible)
return;
await utils.sleep(50);
const bp = this.bookPos;
for (let i = 0; i < this.contents.length; i++) {
const item = this.contents[i];
const nextOffset = (i < this.contents.length - 1 ? this.contents[i + 1].offset : this.parsed.textLength);
for (let j = 0; j < item.list.length; j++) {
const subitem = item.list[j];
const nextSubOffset = (j < item.list.length - 1 ? item.list[j + 1].offset : nextOffset);
if (bp >= subitem.offset && bp < nextSubOffset) {
subitem.isBookPos = true;
this.contents[i] = Object.assign(item, {list: item.list});
} else if (subitem.isBookPos) {
subitem.isBookPos = false;
this.contents[i] = Object.assign(item, {list: item.list});
}
}
if (bp >= item.offset && bp < nextOffset) {
this.contents[i] = Object.assign(item, {isBookPos: true});
} else if (item.isBookPos) {
this.contents[i] = Object.assign(item, {isBookPos: false});
}
}
for (let i = 0; i < this.images.length; i++) {
const img = this.images[i];
const nextOffset = (i < this.images.length - 1 ? this.images[i + 1].offset : this.parsed.textLength);
if (bp >= img.offset && bp < nextOffset) {
this.images[i] = Object.assign(img, {isBookPos: true});
} else if (img.isBookPos) {
this.images[i] = Object.assign(img, {isBookPos: false});
}
}
}
async expandClick(key) {
const item = this.contents[key];
const expanded = !item.expanded;
if (!expanded) {
const subitems = this.$refs[`subitem${key}`];
subitems.style.height = '0';
await utils.sleep(200);
}
this.contents[key] = Object.assign({}, item, {expanded});
if (expanded) {
await this.$nextTick();
const subitems = this.$refs[`subitem${key}`];
subitems.style.height = subitems.scrollHeight + 'px';
}
}
async setBookPos(newValue) {
this.$emit('book-pos-changed', {bookPos: newValue});
this.close();
}
close() {
this.$emit('do-action', {action: 'contents'});
}
keyHook(event) {
if (!this.$root.stdDialog.active && event.type == 'keydown' && event.key == 'Escape') {
this.close();
}
return true;
}
}
export default vueComponent(ContentsPage);
//-----------------------------------------------------------------------------
</script>
<style scoped>
.tab-panel {
overflow-x: hidden;
overflow-y: auto;
font-size: 90%;
padding: 0 10px 0px 10px;
}
.clickable {
cursor: pointer;
padding: 10px 0 10px 0;
}
.item, .subitem, .item-book-pos, .subitem-book-pos {
border-bottom: 1px solid #e0e0e0;
}
.item:hover, .subitem:hover {
background-color: #f0f0f0;
}
.item-book-pos {
background-color: #b0f0b0;
}
.subitem-book-pos {
background-color: #d0f5d0;
}
.item-book-pos:hover {
background-color: #b0e0b0;
}
.subitem-book-pos:hover {
background-color: #d0f0d0;
}
.expand-button, .no-expand-button {
width: 40px;
}
.subitems-transition {
height: 0;
transition: height 0.2s linear;
overflow: hidden;
}
.icon {
transition: transform 0.2s;
}
.expanded-icon {
transform: rotate(90deg);
}
.image-num {
font-size: 120%;
padding-bottom: 3px;
}
.image-type {
border: 1px solid black;
border-radius: 6px;
font-size: 80%;
padding: 2px 0 2px 0;
width: 34px;
}
.it-jpg-color {
background: linear-gradient(to right, #fabc3d, #ffec6d);
}
.it-png-color {
background: linear-gradient(to right, #4bc4e5, #6bf4ff);
}
.it-net-color {
background: linear-gradient(to right, #00c400, #00f400);
}
.image-thumb-box {
width: 120px;
overflow: hidden;
}
.image-thumb {
height: 50px;
}
.loading-img-icon {
font-size: 250%;
}
</style>

View File

@@ -1,33 +1,30 @@
<template>
<div ref="main" class="main" @click="close">
<div class="mainWindow" @click.stop>
<Window @close="close">
<template slot="header">
Скопировать текст
</template>
<Window @close="close">
<template #header>
Скопировать текст
</template>
<div ref="text" class="text" tabindex="-1">
<div v-html="text"></div>
</div>
</Window>
<div ref="text" class="text" tabindex="-1">
<div v-html="text"></div>
</div>
</div>
</Window>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import vueComponent from '../../vueComponent.js';
import Window from '../../share/Window.vue';
import {sleep} from '../../../share/utils';
export default @Component({
const componentOptions = {
components: {
Window,
},
})
class CopyTextPage extends Vue {
};
class CopyTextPage {
_options = componentOptions;
text = null;
initStep = null;
initPercentage = 0;
@@ -95,37 +92,22 @@ 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;
}
}
export default vueComponent(CopyTextPage);
//-----------------------------------------------------------------------------
</script>
<style scoped>
.main {
position: absolute;
width: 100%;
height: 100%;
z-index: 40;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.mainWindow {
width: 100%;
height: 100%;
display: flex;
}
.text {
flex: 1;
overflow-wrap: anywhere;

View File

@@ -1,10 +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>
@@ -12,55 +14,80 @@
<li>поиск по тексту и копирование фрагмента</li>
<li>запоминание недавних книг, скачивание книги из читалки в формате fb2</li>
<li>управление кликом и с клавиатуры</li>
<li>подключение к интернету не обязательно для чтения книги после ее загрузки</li>
<li>регистрация не требуется</li>
<li>поддерживаемые браузеры: Google Chrome, Mozilla Firefox последних версий</li>
</ul>
<p>В качестве URL можно задавать html-страничку с книгой, либо прямую ссылку
на файл из онлайн-библиотеки (например, скопировав адрес ссылки или кнопки "скачать fb2").</p>
<p>Поддерживаемые форматы: <strong>html, txt, fb2, fb2.zip</strong></p>
<p>
В качестве URL книги можно задавать html-страничку с книгой, либо прямую ссылку
на файл из онлайн-библиотеки (например, скопировав адрес ссылки или кнопки "скачать fb2").
</p>
<p>Поддерживаемые форматы: <b>fb2, fb2.zip, html, txt</b> и другие.</p>
<div v-html="automationHtml"></div>
<div v-show="mode == 'omnireader' || mode == 'liberama.top'">
<p>
Вы можете добавить в свой браузер закладку, указав в ее свойствах вместо адреса следующий код:
<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="bookmarkText">{{ (mode == 'omnireader' ? 'Omni' : 'Liberama') }} Reader</a>
<br>Тогда, активировав получившуюся закладку на любой странице интернета, вы автоматически загрузите эту страницу в Omni Reader.
<br>В Chrome для Android можно вызывать такую закладку по имени прямо в адресной строке браузера (имя стоит сделать попроще).
</p>
</div>
<p>Связаться с разработчиком: <a href="mailto:bookpauk@gmail.com">bookpauk@gmail.com</a></p>
</div>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import vueComponent from '../../../vueComponent.js';
export default @Component({
})
class CommonHelpPage extends Vue {
import {copyTextToClipboard} from '../../../../share/utils';
class CommonHelpPage {
created() {
this.config = this.$store.state.config;
}
get automationHtml() {
if (this.config.mode == 'omnireader') {
return `<p>Вы можете добавить в свой браузер закладку, указав в ее свойствах вместо адреса следующий код:
<br><strong>javascript:location.href='http://omnireader.ru/?url='+location.href;</strong>
<br>Тогда, нажав на получившуюся кнопку на любой странице интернета, вы автоматически откроете ее в Omni Reader.</p>`;
} else {
return '';
}
get mode() {
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.$root.notify.success(msg);
else
this.$root.notify.error(msg);
}
}
export default vueComponent(CommonHelpPage);
//-----------------------------------------------------------------------------
</script>
<style scoped>
.page {
flex: 1;
padding: 15px;
overflow-y: auto;
font-size: 120%;
line-height: 130%;
}
h4 {
margin: 0;
.copy-icon {
margin-left: 10px;
cursor: pointer;
font-size: 120%;
color: blue;
}
</style>

View File

@@ -1,30 +1,70 @@
<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>
<img class="logo" src="./assets/yoomoney.png">
<q-btn class="q-ml-sm q-px-sm" dense no-caps @click="donateYooMoney">
Пожертвовать
</q-btn><br>
<div class="para">
{{ yooAddress }}
<q-icon class="copy-icon" name="la la-copy" @click="copyAddress(yooAddress, 'Кошелёк ЮMoney')">
<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>
@@ -32,14 +72,13 @@
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import vueComponent from '../../../vueComponent.js';
import {copyTextToClipboard} from '../../../../share/utils';
export default @Component({
})
class DonateHelpPage extends Vue {
yandexAddress = '410018702323056';
class DonateHelpPage {
yooAddress = '410018702323056';
paypalAddress = 'bookpauk@gmail.com';
bitcoinAddress = '3EbgZ7MK1UVaN38Gty5DCBtS4PknM4Ut85';
litecoinAddress = 'MP39Riec4oSNB3XMjiquKoLWxbufRYNXxZ';
moneroAddress = '8BQPnvHcPSHM5gMQsmuypDgx9NNsYqwXKfDDuswEyF2Q2ewQSfd2pkK6ydH2wmMyq2JViZvy9DQ35hLMx7g72mFWNJTPtnz';
@@ -47,30 +86,29 @@ class DonateHelpPage extends Vue {
created() {
}
donateYandexMoney() {
window.open(`https://money.yandex.ru/to/${this.yandexAddress}`, '_blank');
donateYooMoney() {
window.open(`https://yoomoney.ru/to/${this.yooAddress}`, '_blank');
}
async copyAddress(address, prefix) {
const result = await copyTextToClipboard(address);
const msg = (result ? `${prefix}-адрес ${address} успешно скопирован в буфер обмена` : 'Копирование не удалось');
if (result)
this.$notify.success({message: msg});
this.$root.notify.success(`${prefix} ${address} успешно скопирован в буфер обмена`);
else
this.$notify.error({message: msg});
this.$root.notify.error('Копирование не удалось');
}
}
export default vueComponent(DonateHelpPage);
//-----------------------------------------------------------------------------
</script>
<style scoped>
.page {
flex: 1;
padding: 15px;
overflow-y: auto;
font-size: 120%;
line-height: 130%;
display: flex;
}
.p {
@@ -80,15 +118,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;
@@ -98,13 +131,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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@@ -1,100 +1,100 @@
<template>
<div ref="main" class="main" @click="close">
<div class="mainWindow" @click.stop>
<Window @close="close">
<template slot="header">
Справка
</template>
<Window @close="close">
<template #header>
Справка
</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="donate">
<DonateHelpPage></DonateHelpPage>
</el-tab-pane>
<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>
</el-tabs>
</Window>
<keep-alive>
<component :is="activePage" ref="page" class="col"></component>
</keep-alive>
</div>
</div>
</Window>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import vueComponent from '../../vueComponent.js';
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 VersionHistoryPage from './VersionHistoryPage/VersionHistoryPage.vue';
import DonateHelpPage from './DonateHelpPage/DonateHelpPage.vue';
export default @Component({
components: {
Window,
CommonHelpPage,
HotkeysHelpPage,
MouseHelpPage,
DonateHelpPage,
},
})
class HelpPage extends Vue {
selectedTab = null;
const pages = {
'CommonHelpPage': CommonHelpPage,
'HotkeysHelpPage': HotkeysHelpPage,
'MouseHelpPage': MouseHelpPage,
'VersionHistoryPage': VersionHistoryPage,
'DonateHelpPage': DonateHelpPage,
};
const tabs = [
['CommonHelpPage', 'Общее'],
['MouseHelpPage', 'Мышь/тачскрин'],
['HotkeysHelpPage', 'Клавиатура'],
['VersionHistoryPage', 'История версий'],
['DonateHelpPage', 'Помочь проекту'],
];
const componentOptions = {
components: Object.assign({ Window }, pages),
};
class HelpPage {
_options = componentOptions;
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 = 'VersionHistoryPage';
}
keyHook(event) {
if (event.type == 'keydown' && (event.code == 'Escape')) {
if (event.type == 'keydown' && event.key == 'Escape') {
this.close();
}
return true;
}
}
export default vueComponent(HelpPage);
//-----------------------------------------------------------------------------
</script>
<style scoped>
.main {
position: absolute;
width: 100%;
height: 100%;
z-index: 40;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.mainWindow {
width: 100%;
height: 100%;
display: flex;
}
.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,54 +1,54 @@
<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>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>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import vueComponent from '../../../vueComponent.js';
import UserHotKeys from '../../SettingsPage/UserHotKeys/UserHotKeys.vue';
const componentOptions = {
components: {
UserHotKeys,
},
};
class HotkeysHelpPage {
_options = componentOptions;
export default @Component({
})
class HotkeysHelpPage extends Vue {
created() {
}
get userHotKeys() {
return this.$store.state.reader.settings.userHotKeys;
}
set userHotKeys(value) {
//no setter
}
}
export default vueComponent(HotkeysHelpPage);
//-----------------------------------------------------------------------------
</script>
<style scoped>
.page {
flex: 1;
padding: 15px;
overflow-y: auto;
font-size: 120%;
line-height: 130%;
}
h4 {
margin: 0;
}
</style>

View File

@@ -1,31 +1,49 @@
<template>
<div class="page">
<h4>Управление с помощью мыши/тачпада:</h4>
<span class="text-h6 text-bold">Управление с помощью мыши/тачскрина:</span>
<ul>
<li><b>ЛКМ/ТАЧ</b> по экрану в одну из областей - активация действия:</li>
<div class="click-map-page">
<ClickMapPage ref="clickMapPage"></ClickMapPage>
</div>
<div class="click-map-page">
<ClickMapPage ref="clickMapPage"></ClickMapPage>
</div>
<li><b>ПКМ</b> - показать/скрыть панель управления</li>
<li><b>СКМ</b> - вкл./выкл. плавный скроллинг текста</li>
<br>
<li>Жесты для тачскрина:</li>
<ul>
<li style="list-style-type: square">
от центра вверх: на весь экран
</li>
<li style="list-style-type: square">
от центра вниз: плавный скроллинг
</li>
<li style="list-style-type: square">
от центра вправо: увеличить скорость скроллинга
</li>
<li style="list-style-type: square">
от центра влево: уменьшить скорость скроллинга
</li>
</ul>
</ul>
* Для управления с помощью мыши/тачпада необходимо установить галочку "Включить управление кликом" в настройках
* Для управления с помощью мыши/тачскрина необходимо установить галочку "Включить управление кликом" в настройках
</div>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import vueComponent from '../../../vueComponent.js';
import ClickMapPage from '../../ClickMapPage/ClickMapPage.vue';
export default @Component({
const componentOptions = {
components: {
ClickMapPage,
},
})
class MouseHelpPage extends Vue {
};
class MouseHelpPage {
_options = componentOptions;
created() {
}
@@ -34,22 +52,19 @@ class MouseHelpPage extends Vue {
this.$refs.clickMapPage.$el.style.backgroundColor = '#478355';
}
}
export default vueComponent(MouseHelpPage);
//-----------------------------------------------------------------------------
</script>
<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

@@ -0,0 +1,79 @@
<template>
<div id="versionHistoryPage" class="page">
<span class="text-h6 text-bold">История версий:</span>
<br><br>
<span v-for="(item, index) in versionHeader" :key="index" class="clickable" @click="showRelease(item)">
<p>
{{ item }}
</p>
</span>
<br>
<div v-for="item in versionContent" :id="item.key" :key="item.key">
<span v-html="item.content"></span>
<br>
</div>
</div>
</template>
<script>
//-----------------------------------------------------------------------------
import vueComponent from '../../../vueComponent.js';
import {versionHistory} from '../../versionHistory';
class VersionHistoryPage {
versionHeader = [];
versionContent = [];
created() {
}
mounted() {
let vh = [];
for (const v of versionHistory) {
vh.push(`${v.version} (${v.releaseDate})`);
}
this.versionHeader = vh;
let vc = [];
for (const v of versionHistory) {
let header = `${v.version} (${v.releaseDate})`;
vc.push({key: header, content: 'Версия ' + header + v.content});
}
this.versionContent = vc;
}
showRelease(id) {
let el = document.getElementById(id);
if (el) {
document.getElementById('versionHistoryPage').scrollTop = el.offsetTop;
}
}
}
export default vueComponent(VersionHistoryPage);
//-----------------------------------------------------------------------------
</script>
<style scoped>
.page {
padding: 15px;
overflow-y: auto;
font-size: 120%;
line-height: 130%;
position: relative;
}
p {
line-height: 15px;
}
.clickable {
color: blue;
text-decoration: underline;
cursor: pointer;
}
</style>

View File

@@ -1,263 +0,0 @@
<template>
<div ref="main" class="main" @click="close">
<div class="mainWindow" @click.stop>
<Window @close="close">
<template slot="header">
Последние 100 открытых книг
</template>
<el-table
:data="tableData"
style="width: 100%"
size="mini"
height="1px"
stripe
border
:default-sort = "{prop: 'touchDateTime', order: 'descending'}"
:header-cell-style = "headerCellStyle"
>
<el-table-column
prop="touchDateTime"
min-width="90px"
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>
<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"
placeholder="Найти"
style="margin: 0; padding: 0; vertical-align: bottom; margin-top: 20px; padding: 0 10px 0 10px"
:value="search" @input="search = $event.target.value"
/>
</div>
</template>
<el-table-column
min-width="300px"
>
<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>
</div>
</template>
</el-table-column>
<el-table-column
min-width="100px"
>
<template slot-scope="scope">
<a v-show="isUrl(scope.row.url)" :href="scope.row.url" target="_blank">Оригинал</a><br>
<a :href="scope.row.path" :download="getFileNameFromPath(scope.row.path)">Скачать FB2</a>
</template>
</el-table-column>
<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>
</el-table-column>
</el-table>
</Window>
</div>
</div>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import path from 'path';
import _ from 'lodash';
import {formatDate} from '../../../share/utils';
import Window from '../../share/Window.vue';
import bookManager from '../share/bookManager';
export default @Component({
components: {
Window,
},
watch: {
search: function() {
this.updateTableData();
}
},
})
class HistoryPage extends Vue {
search = null;
tableData = null;
created() {
}
mounted() {
this.updateTableData();
this.mostRecentBook = bookManager.mostRecentBook();
}
updateTableData() {
let result = [];
const sorted = bookManager.getSortedRecent();
const len = (sorted.length < 100 ? sorted.length : 100);
for (let i = 0; i < len; i++) {
const book = sorted[i];
let d = new Date();
d.setTime(book.touchTime);
const t = formatDate(d).split(' ');
let perc = '';
let textLen = '';
const p = (book.bookPosSeen ? book.bookPosSeen : (book.bookPos ? book.bookPos : 0));
if (book.textLength) {
perc = ` [${((p/book.textLength)*100).toFixed(2)}%]`;
textLen = ` ${Math.round(book.textLength/1000)}k`;
}
const fb2 = (book.fb2 ? book.fb2 : {});
let title = fb2.bookTitle;
if (title)
title = `"${title}"`;
else
title = '';
let author = _.compact([
fb2.lastName,
fb2.firstName,
fb2.middleName
]).join(' ');
author = (author ? author : (fb2.bookTitle ? fb2.bookTitle : book.url));
result.push({
touchDateTime: book.touchTime,
touchDate: t[0],
touchTime: t[1],
desc: {
title: `${title}${perc}${textLen}`,
author,
},
url: book.url,
path: book.path,
key: book.key,
});
}
const search = this.search;
this.tableData = result.filter(item => {
return !search ||
item.touchTime.includes(search) ||
item.touchDate.includes(search) ||
item.desc.title.toLowerCase().includes(search.toLowerCase()) ||
item.desc.author.toLowerCase().includes(search.toLowerCase())
});
}
headerCellStyle(cell) {
let result = {margin: 0, padding: 0};
if (cell.columnIndex > 0) {
result['border-bottom'] = 0;
}
if (cell.rowIndex > 0) {
result.height = '0px';
result['border-right'] = 0;
}
return result;
}
getFileNameFromPath(fb2Path) {
return path.basename(fb2Path).substr(0, 10) + '.fb2';
}
openOriginal(url) {
window.open(url, '_blank');
}
openFb2(path) {
window.open(path, '_blank');
}
async handleDel(key) {
await bookManager.delRecentBook({key});
this.updateTableData();
const newRecent = bookManager.mostRecentBook();
if (this.mostRecentBook != newRecent)
this.$emit('load-book', newRecent);
this.mostRecentBook = newRecent;
if (!this.mostRecentBook)
this.close();
}
loadBook(url) {
this.$emit('load-book', {url});
this.close();
}
isUrl(url) {
return (url.indexOf('file://') != 0);
}
close() {
this.$emit('history-toggle');
}
keyHook(event) {
if (event.type == 'keydown' && event.code == 'Escape') {
this.close();
}
return true;
}
}
//-----------------------------------------------------------------------------
</script>
<style scoped>
.main {
position: absolute;
width: 100%;
height: 100%;
z-index: 50;
display: flex;
flex-direction: column;
align-items: center;
}
.mainWindow {
height: 100%;
display: flex;
}
.desc {
cursor: pointer;
}
</style>

View File

@@ -0,0 +1,135 @@
<template>
<div class="hidden"></div>
</template>
<script>
//-----------------------------------------------------------------------------
import vueComponent from '../../vueComponent.js';
import Window from '../../share/Window.vue';
import * as utils from '../../../share/utils';
//import rstore from '../../../store/modules/reader';
import _ from 'lodash';
const componentOptions = {
components: {
Window
},
watch: {
libs: function() {
this.sendLibs();
},
}
};
class LibsPage {
_options = componentOptions;
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: _.cloneDeep(this.libs)});
}
close() {
this.$emit('libs-close');
}
}
export default vueComponent(LibsPage);
//-----------------------------------------------------------------------------
</script>
<style scoped>
.separator {
height: 1px;
background-color: #A0A0A0;
}
</style>

View File

@@ -0,0 +1,134 @@
<template>
<div id="vue-github-corner">
<a :href="url" id="github-corner" target="_blank" aria-label="View source on Github" >
<svg id="github-corner-svg"
aria-hidden="true"
viewBox="0 0 250 250"
:width="size" :height="size"
:style="svgStyle" >
<path :d="svgPath1" @mouseenter="flipColor" @mouseleave="flipColor"></path>
<path :d="svgPath2" :style="gitStyle" class="octo-arm"></path>
<path :d="svgPath3" :style="gitStyle" class="octo-body"></path>
</svg>
</a>
</div>
</template>
<script>
export default {
name: 'GithubCorner',
props: {
url: {
type: String,
default: '/'
},
size: {
type: Number,
default: 80
},
colorScheme: {
type: String,
default: 'auto'
},
cornerColor: {
type: String,
default: '#625D5D'
},
gitColor: {
type: String,
default: 'PeachPuff'
},
leftCorner: {
type: Boolean,
default: false
},
flipOnHover: {
type: Boolean,
default: false
}
},
data() {
return {
svgStyle: {
fill: this.cornerColor,
right: (this.leftCorner ? 'auto' : '0'),
left: (this.leftCorner ? '0' : 'auto'),
transform: (this.leftCorner ? 'scale(-1, 1)' : 'none')
},
gitStyle: {
fill: this.gitColor
},
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'
}
},
methods: {
flipColor: function() {
if (this.flipOnHover) {
let holdSvgFill = this.svgStyle.fill
this.svgStyle.fill = this.gitStyle.fill
this.gitStyle.fill = holdSvgFill
}
}
},
beforeMount: function() {
if (this.colorScheme != 'auto') {
let sch = this.colorScheme
this.gitStyle.fill = '#fff'
if (sch.toLowerCase() == 'black') {
this.svgStyle.fill = '#151513'
}
if (sch.toLowerCase() == 'green') {
this.svgStyle.fill = '#64CEAA'
}
if (sch.toLowerCase() == 'red') {
this.svgStyle.fill = '#FD6C6C'
}
if (sch.toLowerCase() == 'blue') {
this.svgStyle.fill = '#70B7FD'
}
if (sch.toLowerCase() == 'white') {
this.svgStyle.fill = '#fff'
this.gitStyle.fill = '#151513'
}
}
}
}
</script>
<style scoped>
#github-corner .octo-arm {
transform-origin: 130px 106px
}
#github-corner:hover .octo-arm {
animation: octocat-wave 560ms ease-in-out;
}
@keyframes octocat-wave {
0% { transform: rotate(0deg); }
20% { transform: rotate(-25deg); }
40% { transform: rotate(10deg); }
60% { transform: rotate(-25deg); }
80% { transform: rotate(10deg); }
100% { transform: rotate(0deg); }
}
#github-corner-svg {
color: #fff;
position: absolute;
top: 0;
border: 0;
}
#github-corner-svg, #github-corner-svg .octo-arm, #github-corner-svg .octo-body {
transition: fill 1s ease;
}
</style>

View File

@@ -1,48 +1,91 @@
<template>
<div ref="main" class="main">
<div class="part">
<span class="greeting bold-font">{{ title }}</span>
<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" corner-color="#1B695F" git-color="#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">Поддерживаются форматы: fb2, fb2.zip, html, txt</span>
<span class="greeting">Поддерживаются форматы: <b>fb2, html, txt</b> и сжатие: <b>zip, bz2, gz<span v-if="isExternalConverter">, rar</span></b></span>
<span v-if="isExternalConverter" class="greeting">...а также частично форматы: <b>epub, mobi, rtf, doc, docx, pdf, djvu</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>
<input type="file" id="file" ref="file" @change="loadFile" style='display: none;'/>
<el-button size="mini" @click="loadFileClick">
<div class="col-auto column justify-start items-center no-wrap overflow-hidden">
<q-input ref="input" v-model="bookUrl" class="full-width q-px-sm" style="max-width: 700px" outlined dense bg-color="white" placeholder="URL книги" @keydown="onInputKeydown">
<template #append>
<q-btn rounded flat style="width: 40px" icon="la la-check" @click="submitUrl" />
</template>
</q-input>
<input id="file" ref="file" type="file" style="display: none;" @change="loadFile" />
<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>
<span v-if="config.mode == 'omnireader'" class="bottom-span clickable" @click="openComments">Комментарии</span>
</q-btn>
<div class="q-my-sm"></div>
<q-btn no-caps dense class="q-px-sm" color="primary" size="13px" @click="loadBufferClick">
Из буфера обмена
</q-btn>
<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-книг онлайн. Загрузка любой страницы интернета одним кликом, синхронизация между устройствами, удобное управление, регистрация не требуется."
data-title="Omni Reader - браузерная онлайн-читалка"
data-url="https://omnireader.ru">
</div>
</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>
<span class="bottom-span">{{ version }}</span>
<span v-if="version == clientVersion" class="bottom-span">v{{ version }}</span>
<span v-else class="bottom-span">Версия сервера {{ version }}, версия клиента {{ clientVersion }}, необходимо обновить страницу</span>
</div>
<PasteTextPage v-if="pasteTextActive" ref="pasteTextPage" @paste-text-toggle="pasteTextToggle" @load-buffer="loadBuffer"></PasteTextPage>
</div>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import vueComponent from '../../vueComponent.js';
import GithubCorner from './GithubCorner/GithubCorner.vue';
import PasteTextPage from './PasteTextPage/PasteTextPage.vue';
import {versionHistory} from '../versionHistory';
const componentOptions = {
components: {
GithubCorner,
PasteTextPage,
},
};
class LoaderPage {
_options = componentOptions;
export default @Component({
})
class LoaderPage extends Vue {
bookUrl = null;
loadPercent = 0;
pasteTextActive = false;
created() {
this.commit = this.$store.commit;
this.config = this.$store.state.config;
}
mounted() {
this.progress = this.$refs.progress;
/*if (this.mode == 'omnireader')
Ya.share2(this.$refs.yaShare2);// eslint-disable-line no-undef*/
}
activated() {
@@ -50,19 +93,33 @@ class LoaderPage extends Vue {
}
get title() {
if (this.config.mode == 'omnireader')
if (this.mode == 'omnireader')
return 'Omni Reader - браузерная онлайн-читалка.';
if (this.mode == 'liberama.top')
return 'Liberama Reader - браузерная онлайн-читалка.';
return 'Универсальная читалка книг и ресурсов интернета.';
}
get mode() {
return this.$store.state.config.mode;
}
get version() {
return `v${this.config.version}`;
return this.$store.state.config.version;
}
get isExternalConverter() {
return this.$store.state.config.useExternalBookConverter;
}
get clientVersion() {
return versionHistory[0].version;
}
submitUrl() {
if (this.bookUrl) {
this.$emit('load-book', {url: this.bookUrl});
this.$emit('load-book', {url: this.bookUrl, force: true});
this.bookUrl = '';
}
}
@@ -72,70 +129,75 @@ class LoaderPage extends Vue {
}
loadFile() {
const file = this.$refs.file.files[0];
const file = this.$refs.file.files[0];
this.$refs.file.value = '';
if (file)
this.$emit('load-file', {file});
}
openHelp() {
this.$emit('help-toggle');
loadBufferClick() {
this.pasteTextToggle();
}
loadBuffer(opts) {
if (opts.buffer.length) {
const file = new File([opts.buffer], 'dummyName-PasteFromClipboard');
this.$emit('load-file', {file});
}
}
pasteTextToggle() {
this.pasteTextActive = !this.pasteTextActive;
}
openHelp(event) {
this.$emit('do-action', {action: 'help', event});
}
openDonate() {
this.$emit('donate-toggle');
this.$emit('do-action', {action: 'donate'});
}
openComments() {
window.open('http://samlib.ru/comment/b/bookpauk/bookpauk_reader', '_blank');
}
keyHook(event) {
//недостатки сторонних ui
const input = this.$refs.input.$refs.input;
if (document.activeElement === input && event.type == 'keydown' && event.code == 'Enter') {
openOldVersion() {
window.open('http://old.omnireader.ru', '_blank');
}
onInputKeydown(event) {
if (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;
keyHook(event) {
if (this.pasteTextActive) {
return this.$refs.pasteTextPage.keyHook(event);
}
if (event.type == 'keydown' && (document.activeElement !== input && event.code == 'KeyQ')) {
this.$emit('tool-bar-toggle');
event.preventDefault();
event.stopPropagation();
return true;
const input = this.$refs.input.getNativeElement();
if (event.type == 'keydown' && document.activeElement !== input) {
const action = this.$root.readerActionByKeyEvent(event);
switch (action) {
case 'help':
this.openHelp(event);
return true;
}
}
return false;
}
}
export default vueComponent(LoaderPage);
//-----------------------------------------------------------------------------
</script>
<style scoped>
.main {
flex: 1;
display: flex;
flex-direction: column;
}
.part {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.greeting {
font-size: 130%;
line-height: 170%;
}
.bold-font {
font-weight: bold;
font-size: 120%;
line-height: 160%;
}
.clickable {
@@ -144,25 +206,8 @@ class LoaderPage extends Vue {
cursor: pointer;
}
.center {
justify-content: flex-start;
padding: 0 10px 0 10px;
}
.bottom {
justify-content: flex-end;
}
.bottom-span {
font-size: 70%;
margin-bottom: 10px;
}
.el-input {
max-width: 700px;
}
.space {
height: 20px;
}
</style>

View File

@@ -0,0 +1,128 @@
<template>
<Window @close="close">
<template #header>
<span style="position: relative; top: -3px">
Вставьте текст и нажмите
<span class="clickable text-primary" style="font-size: 150%; position: relative; top: 1px" @click="loadBuffer">загрузить</span>
или F2
</span>
</template>
<q-input v-model="bookTitle" class="q-px-sm" dense borderless placeholder="Введите название текста" />
<hr />
<textarea ref="textArea" class="text" @paste="calcTitle"></textarea>
</Window>
</template>
<script>
//-----------------------------------------------------------------------------
import vueComponent from '../../../vueComponent.js';
import Window from '../../../share/Window.vue';
import _ from 'lodash';
import * as utils from '../../../../share/utils';
const componentOptions = {
components: {
Window,
},
};
class PasteTextPage {
_options = componentOptions;
bookTitle = '';
created() {
}
mounted() {
this.$refs.textArea.focus();
}
getNonEmptyLine3words(text, count) {
let result = '';
const lines = text.split("\n");
let i = 0;
while (i < lines.length) {
if (lines[i].trim() != '') {
count--;
if (count <= 0) {
result = lines[i];
break;
}
}
i++;
}
result = result.trim().split(' ');
return result.slice(0, 3).join(' ');
}
calcTitle(event) {
if (this.bookTitle == '') {
this.bookTitle = `Из буфера обмена ${utils.formatDate(new Date(), 'noDate')}`;
if (event) {
let text = event.clipboardData.getData('text');
this.bookTitle += ': ' + _.compact([
this.getNonEmptyLine3words(text, 1),
this.getNonEmptyLine3words(text, 2)
]).join(' - ');
}
}
}
loadBuffer() {
this.calcTitle();
this.$emit('load-buffer', {buffer: `<buffer><fb2-title>${utils.escapeXml(this.bookTitle)}</fb2-title>${utils.escapeXml(this.$refs.textArea.value)}</buffer>`});
this.close();
}
close() {
this.$emit('paste-text-toggle');
}
keyHook(event) {
if (event.type == 'keydown') {
switch (event.key) {
case 'F2':
this.loadBuffer();
break;
case 'Escape':
this.close();
break;
}
}
return true;
}
}
export default vueComponent(PasteTextPage);
//-----------------------------------------------------------------------------
</script>
<style scoped>
.text {
flex: 1;
overflow-wrap: anywhere;
overflow-y: auto;
padding: 0 10px 0 10px;
position: relative;
font-size: 120%;
min-width: 400px;
}
.text:focus {
outline: none;
}
hr {
margin: 0;
padding: 0;
}
.clickable {
color: blue;
cursor: pointer;
}
</style>

View File

@@ -1,21 +1,39 @@
<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>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import vueComponent from '../../vueComponent.js';
import * as utils from '../../../share/utils';
const ruMessage = {
'start': ' ',
'finish': ' ',
'error': ' ',
'queue': 'очередь',
'download': 'скачивание',
'decompress': 'распаковка',
'convert': 'конвертирование',
@@ -24,76 +42,59 @@ const ruMessage = {
'upload': 'отправка',
};
export default @Component({
})
class ProgressPage extends Vue {
class ProgressPage {
text = '';
totalSteps = 1;
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);
}
}
export default vueComponent(ProgressPage);
//-----------------------------------------------------------------------------
</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;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,216 @@
<template>
<div>
<Dialog ref="dialog1" v-model="whatsNewVisible">
<template #header>
Что нового:
</template>
<div style="line-height: 20px; min-width: 300px">
<div v-html="whatsNewContent"></div>
</div>
<span class="clickable" style="font-size: 13px" @click="openVersionHistory">Посмотреть историю версий</span>
<template #footer>
<q-btn class="q-px-md" dense no-caps @click="whatsNewDisable">
Больше не показывать
</q-btn>
</template>
</Dialog>
<Dialog ref="dialog2" v-model="donationVisible">
<template #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>
<template #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>
</template>
</Dialog>
</div>
</template>
<script>
//-----------------------------------------------------------------------------
import vueComponent from '../../vueComponent.js';
import Dialog from '../../share/Dialog.vue';
import * as utils from '../../../share/utils';
import {versionHistory} from '../versionHistory';
const componentOptions = {
components: {
Dialog
},
watch: {
settings: function() {
this.loadSettings();
},
},
};
class ReaderDialogs {
_options = componentOptions;
whatsNewVisible = false;
whatsNewContent = '';
donationVisible = false;
created() {
this.commit = this.$store.commit;
this.loadSettings();
}
mounted() {
}
async init() {
await this.showWhatsNew();
await this.showDonation();
}
loadSettings() {
const settings = this.settings;
this.showWhatsNewDialog = settings.showWhatsNewDialog;
this.showDonationDialog2020 = settings.showDonationDialog2020;
}
async showWhatsNew() {
const whatsNew = versionHistory[0];
if (this.showWhatsNewDialog &&
whatsNew.showUntil >= utils.formatDate(new Date(), 'coDate') &&
this.whatsNewHeader != this.whatsNewContentHash) {
await utils.sleep(2000);
this.whatsNewContent = 'Версия ' + this.whatsNewHeader + whatsNew.content;
this.whatsNewVisible = true;
}
}
async showDonation() {
const today = utils.formatDate(new Date(), 'coDate');
if ((this.mode == 'omnireader' || this.mode == 'liberama.top') && today < '2020-03-01' && this.showDonationDialog2020 && this.donationRemindDate != today) {
await utils.sleep(3000);
this.donationVisible = true;
}
}
donationDialogDisable() {
this.donationVisible = false;
if (this.showDonationDialog2020) {
this.commit('reader/setSettings', { showDonationDialog2020: false });
}
}
donationDialogRemind() {
this.donationVisible = false;
this.commit('reader/setDonationRemindDate', utils.formatDate(new Date(), 'coDate'));
}
openDonate() {
this.donationVisible = false;
this.$emit('donate-toggle');
}
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.$emit('version-history-toggle');
}
whatsNewDisable() {
this.whatsNewVisible = false;
this.commit('reader/setWhatsNewContentHash', this.whatsNewHeader);
}
get whatsNewHeader() {
return `${versionHistory[0].version} (${versionHistory[0].releaseDate})`;
}
get mode() {
return this.$store.state.config.mode;
}
get settings() {
return this.$store.state.reader.settings;
}
get whatsNewContentHash() {
return this.$store.state.reader.whatsNewContentHash;
}
get donationRemindDate() {
return this.$store.state.reader.donationRemindDate;
}
keyHook() {
if (this.$refs.dialog1.active || this.$refs.dialog2.active)
return true;
return false;
}
}
export default vueComponent(ReaderDialogs);
//-----------------------------------------------------------------------------
</script>
<style scoped>
.clickable {
color: blue;
text-decoration: underline;
cursor: pointer;
}
.copy-icon {
cursor: pointer;
font-size: 120%;
color: blue;
}
</style>

View File

@@ -0,0 +1,394 @@
<template>
<Window ref="window" width="600px" @close="close">
<template #header>
<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;" target="_blank"></a>
<q-table
class="recent-books-table col"
:rows="tableData"
row-key="key"
:columns="columns"
:pagination="pagination"
separator="cell"
hide-bottom
virtual-scroll
dense
>
<template #header="props">
<q-tr :props="props">
<q-th key="num" class="td-mp" style="width: 25px" :props="props">
<span v-html="props.cols[0].label"></span>
</q-th>
<q-th key="date" class="td-mp break-word" style="width: 77px" :props="props">
<span v-html="props.cols[1].label"></span>
</q-th>
<q-th key="desc" class="td-mp" style="width: 332px" :props="props" colspan="4">
<q-input ref="input" v-model="search"
outlined dense rounded style="position: absolute; top: 6px; left: 90px; width: 380px" bg-color="white"
placeholder="Найти"
@click.stop
>
<template #append>
<q-icon v-if="search !== ''" name="la la-times" class="cursor-pointer" @click.stop="resetSearch" />
</template>
</q-input>
<span v-html="props.cols[2].label"></span>
</q-th>
</q-tr>
</template>
<template #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>
</q-td>
<q-td key="date" auto-width :props="props" class="td-mp clickable" @click="loadBook(props.row.url)">
<div class="break-word" style="width: 68px">
{{ props.row.touchDate }}<br>
{{ props.row.touchTime }}
</div>
</q-td>
<q-td key="desc" auto-width :props="props" class="td-mp clickable" @click="loadBook(props.row.url)">
<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 class="read-bar" :style="`width: ${332*props.row.readPart}px`"></div>
</div>
</q-td>
<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, props.row.fullTitle)">Скачать FB2</a>
</div>
</q-td>
<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" />
</q-btn>
</div>
</q-td>
<q-td key="last" :props="props" class="no-mp">
</q-td>
</q-tr>
</template>
</q-table>
</Window>
</template>
<script>
//-----------------------------------------------------------------------------
import vueComponent from '../../vueComponent.js';
import path from 'path-browserify';
//import _ from 'lodash';
import * as utils from '../../../share/utils';
import Window from '../../share/Window.vue';
import bookManager from '../share/bookManager';
import readerApi from '../../../api/reader';
const componentOptions = {
components: {
Window,
},
watch: {
search: function() {
this.updateTableData();
}
},
};
class RecentBooksPage {
_options = componentOptions;
loading = false;
search = '';
tableData = [];
columns = [];
pagination = {};
created() {
this.firstInit = true;
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();//плохо на планшетах
});
(async() => {//подгрузка списка
if (this.initing)
return;
this.initing = true;
if (this.firstInit) {//для отзывчивости
await this.updateTableData(20);
this.firstInit = false;
}
await utils.sleep(50);
await this.updateTableData();
this.initing = false;
})();
}
async updateTableData(limit) {
while (this.updating) await utils.sleep(100);
this.updating = true;
let result = [];
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;
let d = new Date();
d.setTime(book.touchTime);
const t = utils.formatDate(d).split(' ');
let readPart = 0;
let perc = '';
let textLen = '';
const p = (book.bookPosSeen ? book.bookPosSeen : (book.bookPos ? book.bookPos : 0));
if (book.textLength) {
readPart = p/book.textLength;
perc = ` [${(readPart*100).toFixed(2)}%]`;
textLen = ` ${Math.round(book.textLength/1000)}k`;
}
const bt = utils.getBookTitle(book.fb2);
let title = bt.bookTitle;
title = (title ? `"${title}"`: '');
const author = (bt.author ? bt.author : (bt.bookTitle ? bt.bookTitle : book.url));
result.push({
num,
touchDateTime: book.touchTime,
touchDate: t[0],
touchTime: t[1],
desc: {
author,
title: `${title}${perc}${textLen}`,
},
readPart,
descString: `${author}${title}${perc}${textLen}`,//для сортировки
url: book.url,
path: book.path,
fullTitle: bt.fullTitle,
key: book.key,
});
}
const search = this.search;
result = result.filter(item => {
return !search ||
item.touchTime.includes(search) ||
item.touchDate.includes(search) ||
item.desc.title.toLowerCase().includes(search.toLowerCase()) ||
item.desc.author.toLowerCase().includes(search.toLowerCase())
});
this.tableData = result;
this.updating = false;
}
resetSearch() {
this.search = '';
this.$refs.input.focus();
}
wordEnding(num) {
const endings = ['', 'а', 'и', 'и', 'и', '', '', '', '', ''];
const deci = num % 100;
if (deci > 10 && deci < 20) {
return '';
} else {
return endings[num % 10];
}
}
get header() {
const len = (this.tableData ? this.tableData.length : 0);
return `${(this.search ? 'Найдено' : 'Всего')} ${len} книг${this.wordEnding(len)}`;
}
async downloadBook(fb2path, fullTitle) {
try {
await readerApi.checkCachedBook(fb2path);
const d = this.$refs.download;
d.href = fb2path;
try {
const fn = utils.makeValidFilename(fullTitle);
d.download = fn.substring(0, 100) + '.fb2';
} catch(e) {
d.download = path.basename(fb2path).substr(0, 10) + '.fb2';
}
d.click();
} catch (e) {
let errMes = e.message;
if (errMes.indexOf('404') >= 0)
errMes = 'Файл не найден на сервере (возможно был удален как устаревший)';
this.$root.stdDialog.alert(errMes, 'Ошибка', {color: 'negative'});
}
}
async handleDel(key) {
await bookManager.delRecentBook({key});
//this.updateTableData();//обновление уже происходит Reader.bookManagerEvent
if (!bookManager.mostRecentBook())
this.close();
}
loadBook(url) {
this.$emit('load-book', {url});
this.close();
}
isUrl(url) {
if (url)
return (url.indexOf('disk://') != 0);
else
return false;
}
close() {
this.$emit('recent-books-close');
}
keyHook(event) {
if (!this.$root.stdDialog.active && event.type == 'keydown' && event.key == 'Escape') {
this.close();
}
return true;
}
}
export default vueComponent(RecentBooksPage);
//-----------------------------------------------------------------------------
</script>
<style scoped>
.recent-books-table {
width: 600px;
overflow-y: auto;
overflow-x: hidden;
}
.clickable {
cursor: pointer;
}
.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;
}
.read-bar {
height: 3px;
background-color: #aaaaaa;
}
</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

@@ -1,39 +1,45 @@
<template>
<div ref="main" class="main" @click="close">
<div class="mainWindow" @click.stop>
<Window @close="close">
<template slot="header">
{{ header }}
</template>
<Window ref="window" height="125px" max-width="600px" :top-shift="-50" @close="close">
<template #header>
{{ header }}
</template>
<div class="content">
<span v-show="initStep">{{ initPercentage }}%</span>
<div class="content">
<span v-show="initStep">{{ initPercentage }}%</span>
<div v-show="!initStep" class="input">
<input ref="input" class="el-input__inner"
placeholder="что ищем"
:value="needle" @input="needle = $event.target.value"/>
<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>
<div v-show="!initStep" class="input">
<!--input ref="input"
placeholder="что ищем"
:value="needle" @input="needle = $event.target.value"/-->
<q-input ref="input" v-model="needle"
class="col" outlined dense
placeholder="что ищем"
@keydown="inputKeyDown"
/>
<div style="position: absolute; right: 10px; margin-top: 10px; font-size: 16px;">
{{ foundText }}
</div>
</Window>
</div>
<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>
</div>
</Window>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import vueComponent from '../../vueComponent.js';
import Window from '../../share/Window.vue';
import {sleep} from '../../../share/utils';
export default @Component({
const componentOptions = {
components: {
Window,
},
@@ -43,11 +49,16 @@ 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';
},
},
})
class SearchPage extends Vue {
};
class SearchPage {
_options = componentOptions;
header = null;
initStep = null;
initPercentage = 0;
@@ -61,6 +72,8 @@ class SearchPage extends Vue {
}
async init(parsed) {
this.$refs.window.init();
if (this.parsed != parsed) {
this.initStep = true;
this.stopInit = false;
@@ -159,51 +172,35 @@ 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;
}
}
export default vueComponent(SearchPage);
//-----------------------------------------------------------------------------
</script>
<style scoped>
.main {
position: absolute;
width: 100%;
height: 100%;
z-index: 40;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.mainWindow {
width: 100%;
max-width: 500px;
height: 125px;
display: flex;
position: relative;
top: -50px;
}
.content {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
padding: 10px;
min-width: 430px;
}
.input {
@@ -215,16 +212,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;
}
i {
font-size: 20px;
width: 50px;
}
</style>

View File

@@ -0,0 +1,742 @@
<template>
<div class="hidden"></div>
</template>
<script>
//-----------------------------------------------------------------------------
import vueComponent from '../../vueComponent.js';
import _ from 'lodash';
import bookManager from '../share/bookManager';
import readerApi from '../../../api/reader';
import * as utils from '../../../share/utils';
import * as cryptoUtils from '../../../share/cryptoUtils';
import localForage from 'localforage';
const ssCacheStore = localForage.createInstance({
name: 'ssCacheStore'
});
const componentOptions = {
watch: {
serverSyncEnabled: function() {
this.serverSyncEnabledChanged();
},
serverStorageKey: function() {
this.serverStorageKeyChanged(true);
},
settings: function() {
this.debouncedSaveSettings();
},
profiles: function() {
this.saveProfiles();
},
currentProfile: function() {
this.currentProfileChanged(true);
},
libs: function() {
this.debouncedSaveLibs();
},
},
};
class ServerStorage {
_options = componentOptions;
created() {
this.inited = false;
this.keyInited = false;
this.commit = this.$store.commit;
this.prevServerStorageKey = null;
this.$root.generateNewServerStorageKey = () => {this.generateNewServerStorageKey()};
this.debouncedSaveSettings = _.debounce(() => {
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.cleanCachedRecent('cachedRecent');
this.cachedRecentPatch = await ssCacheStore.getItem('recent-patch');
if (!this.cachedRecentPatch)
await this.cleanCachedRecent('cachedRecentPatch');
this.cachedRecentMod = await ssCacheStore.getItem('recent-mod');
if (!this.cachedRecentMod)
await this.cleanCachedRecent('cachedRecentMod');
if (!this.serverStorageKey) {
//генерируем новый ключ
await this.generateNewServerStorageKey();
} else {
await this.serverStorageKeyChanged();
}
} finally {
this.inited = true;
}
}
async setCachedRecent(value) {
await ssCacheStore.setItem('recent', value);
this.cachedRecent = value;
}
async setCachedRecentPatch(value) {
await ssCacheStore.setItem('recent-patch', value);
this.cachedRecentPatch = value;
}
async setCachedRecentMod(value) {
await ssCacheStore.setItem('recent-mod', value);
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);
await this.serverStorageKeyChanged(true);
}
async serverSyncEnabledChanged() {
if (this.serverSyncEnabled) {
this.prevServerStorageKey = null;
if (!this.serverStorageKey) {
//генерируем новый ключ
await this.generateNewServerStorageKey();
} else {
await this.serverStorageKeyChanged(true);
}
}
}
async serverStorageKeyChanged(force) {
if (this.prevServerStorageKey != this.serverStorageKey) {
this.prevServerStorageKey = this.serverStorageKey;
this.hashedStorageKey = utils.toBase58(cryptoUtils.sha256(this.serverStorageKey));
this.keyInited = true;
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) {
await this.saveRecent();
}
}
}
async currentProfileChanged(force) {
if (!this.currentProfile)
return;
await this.loadSettings(force);
}
get serverSyncEnabled() {
return this.$store.state.reader.serverSyncEnabled;
}
get settings() {
return this.$store.state.reader.settings;
}
get settingsRev() {
return this.$store.state.reader.settingsRev;
}
get serverStorageKey() {
return this.$store.state.reader.serverStorageKey;
}
get profiles() {
return this.$store.state.reader.profiles;
}
get profilesRev() {
return this.$store.state.reader.profilesRev;
}
get currentProfile() {
return this.$store.state.reader.currentProfile;
}
get showServerStorageMessages() {
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', '');
}
}
success(message) {
if (this.showServerStorageMessages)
this.$root.notify.success(message);
}
warning(message) {
if (this.showServerStorageMessages && !this.offlineModeActive)
this.$root.notify.warning(message);
}
error(message) {
if (this.showServerStorageMessages && !this.offlineModeActive) {
this.errorMessageCounter = (this.errorMessageCounter ? this.errorMessageCounter + 1 : 1);
const hint = (this.errorMessageCounter < 2 ? '' :
'<div><br>Надоело это сообщение? Добавьте в настройках кнопку "Автономный режим" ' +
'<i class="la la-unlink" style="font-size: 20px; color: white"></i> на панель инструментов и активируйте ее.</div>'
);
this.$root.notify.error(message + hint);
}
}
async loadSettings(force = false, doNotifySuccess = true) {
if (!this.keyInited || !this.serverSyncEnabled || !this.currentProfile)
return;
const setsId = `settings-${this.currentProfile}`;
const oldRev = this.settingsRev[setsId] || 0;
//проверим ревизию на сервере
if (!force) {
try {
const revs = await this.storageCheck({[setsId]: {}});
if (revs.state == 'success' && revs.items[setsId].rev == oldRev) {
return;
}
} catch(e) {
this.error(`Ошибка соединения с сервером: ${e.message}`);
return;
}
}
let sets = null;
try {
sets = await this.storageGet({[setsId]: {}});
} catch(e) {
this.error(`Ошибка соединения с сервером: ${e.message}`);
return;
}
if (sets.state == 'success') {
sets = sets.items[setsId];
if (sets.rev == 0)
sets.data = {};
this.oldSettings = _.cloneDeep(sets.data);
this.commit('reader/setSettings', sets.data);
this.commit('reader/setSettingsRev', {[setsId]: sets.rev});
if (doNotifySuccess)
this.debouncedNotifySuccess();
} else {
this.warning(`Неверный ответ сервера: ${sets.state}`);
}
}
async saveSettings() {
if (!this.keyInited || !this.serverSyncEnabled || !this.currentProfile || this.savingSettings)
return;
const diff = utils.getObjDiff(this.oldSettings, this.settings);
if (utils.isEmptyObjDiff(diff))
return;
this.savingSettings = true;
try {
const setsId = `settings-${this.currentProfile}`;
let result = {state: ''};
const oldRev = this.settingsRev[setsId] || 0;
try {
result = await this.storageSet({[setsId]: {rev: oldRev + 1, data: this.settings}});
} catch(e) {
this.error(`Ошибка соединения с сервером (${e.message}). Данные не сохранены и могут быть перезаписаны.`);
}
if (result.state == 'reject') {
await this.loadSettings(true, false);
this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
} else if (result.state == 'success') {
this.oldSettings = _.cloneDeep(this.settings);
this.commit('reader/setSettingsRev', {[setsId]: this.settingsRev[setsId] + 1});
}
} finally {
this.savingSettings = false;
}
}
async loadProfiles(force = false, doNotifySuccess = true) {
if (!this.keyInited || !this.serverSyncEnabled)
return;
const oldRev = this.profilesRev;
//проверим ревизию на сервере
if (!force) {
try {
const revs = await this.storageCheck({profiles: {}});
if (revs.state == 'success' && revs.items.profiles.rev == oldRev) {
return;
}
} catch(e) {
this.error(`Ошибка соединения с сервером: ${e.message}`);
return;
}
}
let prof = null;
try {
prof = await this.storageGet({profiles: {}});
} catch(e) {
this.error(`Ошибка соединения с сервером: ${e.message}`);
return;
}
if (prof.state == 'success') {
prof = prof.items.profiles;
if (prof.rev == 0)
prof.data = {};
this.oldProfiles = _.cloneDeep(prof.data);
this.commit('reader/setProfiles', prof.data);
this.commit('reader/setProfilesRev', prof.rev);
this.checkCurrentProfile();
if (doNotifySuccess)
this.debouncedNotifySuccess();
} else {
this.warning(`Неверный ответ сервера: ${prof.state}`);
}
}
async saveProfiles() {
if (!this.keyInited || !this.serverSyncEnabled || this.savingProfiles)
return;
const diff = utils.getObjDiff(this.oldProfiles, this.profiles);
if (utils.isEmptyObjDiff(diff))
return;
//обнуляются профили во время разработки при hotReload, подстраховка
if (!this.$store.state.reader.allowProfilesSave) {
console.error('Сохранение профилей не санкционировано');
return;
}
this.savingProfiles = true;
try {
let result = {state: ''};
try {
result = await this.storageSet({profiles: {rev: this.profilesRev + 1, data: this.profiles}});
} catch(e) {
this.error(`Ошибка соединения с сервером (${e.message}). Данные не сохранены и могут быть перезаписаны.`);
}
if (result.state == 'reject') {
await this.loadProfiles(true, false);
this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
} else if (result.state == 'success') {
this.oldProfiles = _.cloneDeep(this.profiles);
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;
this.loadingRecent = true;
try {
//проверим ревизию на сервере
let query = {recent: {}, recentPatch: {}, recentMod: {}};
let revs = null;
if (!skipRevCheck) {
try {
revs = await this.storageCheck(query);
if (revs.state == 'success') {
if (revs.items.recent.rev != this.cachedRecent.rev) {
//no changes
} else if (revs.items.recentPatch.rev != this.cachedRecentPatch.rev) {
query = {recentPatch: {}, recentMod: {}};
} else if (revs.items.recentMod.rev != this.cachedRecentMod.rev) {
query = {recentMod: {}};
} else
return true;
}
} catch(e) {
this.error(`Ошибка соединения с сервером: ${e.message}`);
return;
}
}
let recent = null;
try {
recent = await this.storageGet(query);
} catch(e) {
this.error(`Ошибка соединения с сервером: ${e.message}`);
return;
}
if (recent.state == 'success') {
let newRecent = recent.items.recent;
let newRecentPatch = recent.items.recentPatch;
let newRecentMod = recent.items.recentMod;
if (!newRecent) {
newRecent = _.cloneDeep(this.cachedRecent);
}
if (!newRecentPatch) {
newRecentPatch = _.cloneDeep(this.cachedRecentPatch);
}
if (!newRecentMod) {
newRecentMod = _.cloneDeep(this.cachedRecentMod);
}
if (newRecent.rev == 0) newRecent.data = {};
if (newRecentPatch.rev == 0) newRecentPatch.data = {};
if (newRecentMod.rev == 0) newRecentMod.data = {};
let result = Object.assign({}, newRecent.data, newRecentPatch.data);
const md = newRecentMod.data;
if (md.key && result[md.key])
result[md.key] = utils.applyObjDiff(result[md.key], md.mod, {isAddChanged: true});
/*if (!bookManager.loaded) {
this.warning('Ожидание загрузки списка книг перед синхронизацией');
while (!bookManager.loaded) await utils.sleep(100);
}*/
if (newRecent.rev != this.cachedRecent.rev)
await this.setCachedRecent(newRecent);
if (newRecentPatch.rev != this.cachedRecentPatch.rev)
await this.setCachedRecentPatch(newRecentPatch);
if (newRecentMod.rev != this.cachedRecentMod.rev)
await this.setCachedRecentMod(newRecentMod);
await bookManager.setRecent(result);
} else {
this.warning(`Неверный ответ сервера: ${recent.state}`);
return;
}
if (doNotifySuccess)
this.debouncedNotifySuccess();
} finally {
this.loadingRecent = false;
}
return true;
}
async saveRecent(itemKey, recurse) {
while (!this.inited || this.savingRecent)
await utils.sleep(100);
if (!this.keyInited || !this.serverSyncEnabled || this.savingRecent)
return;
this.savingRecent = true;
try {
const bm = bookManager;
let needSaveRecent = false;
let needSaveRecentPatch = false;
let needSaveRecentMod = false;
//newRecentMod
let newRecentMod = {};
if (itemKey && this.cachedRecentPatch.data[itemKey] && this.prevItemKey == itemKey) {
newRecentMod = _.cloneDeep(this.cachedRecentMod);
newRecentMod.rev++;
newRecentMod.data.key = itemKey;
newRecentMod.data.mod = utils.getObjDiff(this.cachedRecentPatch.data[itemKey], bm.recent[itemKey]);
needSaveRecentMod = true;
}
this.prevItemKey = itemKey;
//newRecentPatch
let newRecentPatch = {};
if (itemKey && !needSaveRecentMod) {
newRecentPatch = _.cloneDeep(this.cachedRecentPatch);
newRecentPatch.rev++;
newRecentPatch.data[itemKey] = _.cloneDeep(bm.recent[itemKey]);
const applyMod = this.cachedRecentMod.data;
if (applyMod && applyMod.key && newRecentPatch.data[applyMod.key])
newRecentPatch.data[applyMod.key] = utils.applyObjDiff(newRecentPatch.data[applyMod.key], applyMod.mod, {isAddChanged: true});
newRecentMod = {rev: this.cachedRecentMod.rev + 1, data: {}};
needSaveRecentPatch = true;
needSaveRecentMod = true;
}
//newRecent
let newRecent = {};
if (!itemKey || (needSaveRecentPatch && Object.keys(newRecentPatch.data).length > 10)) {
//ждем весь bm.recent
/*while (!bookManager.loaded)
await utils.sleep(100);*/
newRecent = {rev: this.cachedRecent.rev + 1, data: _.cloneDeep(bm.recent)};
newRecentPatch = {rev: this.cachedRecentPatch.rev + 1, data: {}};
newRecentMod = {rev: this.cachedRecentMod.rev + 1, data: {}};
needSaveRecent = true;
needSaveRecentPatch = true;
needSaveRecentMod = true;
}
//query
let query = {};
if (needSaveRecent) {
query = {recent: newRecent, recentPatch: newRecentPatch, recentMod: newRecentMod};
} else if (needSaveRecentPatch) {
query = {recentPatch: newRecentPatch, recentMod: newRecentMod};
} else {
query = {recentMod: newRecentMod};
}
//сохранение
let result = {state: ''};
try {
result = await this.storageSet(query);
} catch(e) {
this.error(`Ошибка соединения с сервером (${e.message}). Данные не сохранены и могут быть перезаписаны.`);
}
if (result.state == 'reject') {
const res = await this.loadRecent(false, false);
if (res)
this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
if (!recurse && itemKey) {
this.savingRecent = false;
await this.saveRecent(itemKey, true);
return;
}
} else if (result.state == 'success') {
if (needSaveRecent && newRecent.rev)
await this.setCachedRecent(newRecent);
if (needSaveRecentPatch && newRecentPatch.rev)
await this.setCachedRecentPatch(newRecentPatch);
if (needSaveRecentMod && newRecentMod.rev)
await this.setCachedRecentMod(newRecentMod);
}
} finally {
this.savingRecent = false;
}
}
async storageCheck(items) {
return await this.storageApi('check', items);
}
async storageGet(items) {
return await this.storageApi('get', items);
}
async storageSet(items, force) {
return await this.storageApi('set', items, force);
}
async storageApi(action, items, force) {
const request = {action, items};
if (force)
request.force = true;
const encodedRequest = await this.encodeStorageItems(request);
return await this.decodeStorageItems(await readerApi.storage(encodedRequest));
}
async encodeStorageItems(request) {
if (!this.hashedStorageKey)
throw new Error('hashedStorageKey is empty');
if (!_.isObject(request.items))
throw new Error('items is not an object');
let result = Object.assign({}, request);
let items = {};
for (const id of Object.keys(request.items)) {
const item = request.items[id];
if (request.action == 'set' && !_.isObject(item.data))
throw new Error('encodeStorageItems: data is not an object');
let encoded = Object.assign({}, item);
if (item.data) {
const comp = utils.pako.deflate(JSON.stringify(item.data), {level: 1});
let encrypted = null;
try {
encrypted = cryptoUtils.aesEncrypt(comp, this.serverStorageKey);
} catch (e) {
throw new Error('encrypt failed');
}
encoded.data = '1' + utils.toBase64(encrypted);
}
items[`${this.hashedStorageKey}.${utils.toBase58(id)}`] = encoded;
}
result.items = items;
return result;
}
async decodeStorageItems(response) {
if (!this.hashedStorageKey)
throw new Error('hashedStorageKey is empty');
let result = Object.assign({}, response);
let items = {};
if (response.items) {
if (!_.isObject(response.items))
throw new Error('items is not an object');
for (const id of Object.keys(response.items)) {
const item = response.items[id];
let decoded = Object.assign({}, item);
if (item.data) {
if (!_.isString(item.data) || !item.data.length)
throw new Error('decodeStorageItems: data is not a string');
if (item.data[0] !== '1')
throw new Error('decodeStorageItems: unknown data format');
const a = utils.fromBase64(item.data.substr(1));
let decrypted = null;
try {
decrypted = cryptoUtils.aesDecrypt(a, this.serverStorageKey);
} catch (e) {
throw new Error('decrypt failed');
}
decoded.data = JSON.parse(utils.pako.inflate(decrypted, {to: 'string'}));
}
const ids = id.split('.');
if (!(ids.length == 2) || !(ids[0] == this.hashedStorageKey))
throw new Error(`decodeStorageItems: bad id - ${id}`);
items[utils.fromBase58(ids[1])] = decoded;
}
}
result.items = items;
return result;
}
}
export default vueComponent(ServerStorage);
//-----------------------------------------------------------------------------
</script>

View File

@@ -1,51 +1,58 @@
<template>
<div ref="main" class="main" @click="close">
<div class="mainWindow" @click.stop>
<Window @close="close">
<template slot="header">
Установить позицию
</template>
<Window ref="window" height="140px" max-width="600px" :top-shift="-50" @close="close">
<template #header>
Установить позицию
</template>
<div class="slider">
<el-slider v-model="sliderValue" :max="sliderMax" :format-tooltip="formatTooltip"></el-slider>
</div>
</Window>
<div id="set-position-slider" class="slider q-px-md">
<q-slider
v-model="sliderValue"
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"
:max="sliderMax"
label
:label-value="(sliderMax ? (sliderValue/sliderMax*100).toFixed(2) + '%' : 0)"
color="primary"
/>
</div>
</div>
</Window>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import _ from 'lodash';
import vueComponent from '../../vueComponent.js';
import Window from '../../share/Window.vue';
export default @Component({
const componentOptions = {
components: {
Window,
},
watch: {
sliderValue: function(newValue) {
this.$emit('book-pos-changed', {bookPos: newValue});
if (this.initialized)
this.$emit('book-pos-changed', {bookPos: newValue});
},
},
})
class SetPositionPage extends Vue {
};
class SetPositionPage {
_options = componentOptions;
sliderValue = null;
sliderMax = null;
created() {
this.commit = this.$store.commit;
this.reader = this.$store.state.reader;
this.initialized = false;
}
formatTooltip(val) {
if (this.sliderMax)
return (val/this.sliderMax*100).toFixed(2) + '%';
else
return 0;
init(sliderValue, sliderMax) {
this.$refs.window.init();
this.sliderMax = sliderMax;
this.sliderValue = sliderValue;
this.initialized = true;
}
close() {
@@ -53,44 +60,33 @@ class SetPositionPage extends Vue {
}
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;
}
}
export default vueComponent(SetPositionPage);
//-----------------------------------------------------------------------------
</script>
<style scoped>
.main {
position: absolute;
width: 100%;
height: 100%;
z-index: 40;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.mainWindow {
width: 100%;
max-width: 600px;
height: 140px;
display: flex;
position: relative;
top: -50px;
}
.slider {
margin: 20px;
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>

View File

@@ -0,0 +1,9 @@
<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" v-model="showToolButton[item.name]" :label="rstore.readerActions[item.name]"
/>
</div>
</div>

View File

@@ -0,0 +1,87 @@
<!---------------------------------------------->
<div class="q-mt-sm column items-center">
<span>Настройки конвертирования применяются ко всем</span>
<span>вновь загружаемым или обновляемым файлам</span>
</div>
<!---------------------------------------------->
<div class="part-header">HTML, XML, TXT</div>
<div class="item row">
<div class="label-7">Текст</div>
<div class="col row">
<q-checkbox v-model="splitToPara" size="xs" label="Попытаться разбить текст на параграфы">
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Опция принудительно включает эвристику разбиения текста на<br>
параграфы в случае, если формат файла определен как html,<br>
xml или txt. Возможна нечитабельная разметка текста.
</q-tooltip>
</q-checkbox>
</div>
</div>
<div class="item row">
<div class="label-7">Сайты</div>
<div class="col row">
<q-checkbox v-model="enableSitesFilter" 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 v-if="isExternalConverter">
<div class="part-header">PDF</div>
<div class="item row">
<div class="label-7">Формат</div>
<div class="col row">
<q-checkbox v-model="pdfAsText" size="xs" label="Извлекать текст из PDF">
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Пытается извлечь текст из pdf-файла и переразбить на параграфы.<br>
Размер получаемого fb2-файла при этом относительно небольшой.<br>
При отключении этой опции, pdf будет представлен как набор<br>
изображений (аналогично ковертированию djvu).
</q-tooltip>
</q-checkbox>
</div>
</div>
<div class="item row">
<div class="label-7">Качество</div>
<div class="col row">
<NumInput class="col-5" v-model="pdfQuality" :min="10" :max="100" :disable="pdfAsText" >
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Качество конвертирования Pdf в Fb2. Чем значение выше, тем больше<br>
размер итогового файла. Если сервер отказывается конвертировать<br>
слишком большой файл, то попробуйте понизить качество.
</q-tooltip>
</NumInput>
</div>
</div>
</div>
<!---------------------------------------------->
<div v-if="isExternalConverter">
<div class="part-header">DJVU</div>
<div class="item row">
<div class="label-7">Качество</div>
<div class="col row">
<NumInput class="col-5" v-model="djvuQuality" :min="10" :max="100">
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Качество конвертирования Djvu в Fb2. Чем значение выше, тем больше<br>
размер итогового файла. Если сервер отказывается конвертировать<br>
слишком большой файл, то попробуйте понизить качество.
</q-tooltip>
</NumInput>
</div>
</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,102 @@
<!---------------------------------------------->
<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="showNeedUpdateNotify">
Показывать уведомление о новой версии
<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>
<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 display-value-sanitize options-sanitize
/>
</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>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,261 @@
<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"
v-model="search"
class="q-ml-sm col"
outlined dense rounded
bg-color="grey-4"
placeholder="Найти"
@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 v-for="(action, index) in tableData" :key="index" class="table-row row">
<div class="desc q-pa-sm">
{{ rstore.readerActions[action] }}
</div>
<div class="hotKeys col q-pa-sm">
<q-chip
v-for="(code, index2) in modelValue[action]" :key="index2"
:color="collisions[code] ? 'red' : 'grey-7'"
:removable="!readonly" :clickable="collisions[code] ? true : false"
text-color="white" @remove="removeCode(action, code)"
@click="collisionWarning(code)"
>
{{ code }}
</q-chip>
</div>
<div v-show="!readonly" class="column q-pa-xs">
<q-icon
v-ripple
:disabled="(modelValue[action].length >= maxCodesLength) || null"
name="la la-plus-circle"
class="button bg-green-8 text-white"
@click="addHotKey(action)"
>
<q-tooltip :delay="1000" anchor="top middle" self="bottom middle" content-style="font-size: 80%">
Добавить сочетание клавиш
</q-tooltip>
</q-icon>
<q-icon
v-ripple
name="la la-broom"
class="button text-grey-5"
@click="defaultHotKey(action)"
>
<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 vueComponent from '../../../vueComponent.js';
import rstore from '../../../../store/modules/reader';
//import * as utils from '../../share/utils';
const componentOptions = {
watch: {
search: function() {
this.updateTableData();
},
modelValue: function() {
this.checkCollisions();
this.updateTableData();
}
},
};
class UserHotKeys {
_options = componentOptions;
_props = {
modelValue: Object,
readonly: Boolean,
};
search = '';
rstore = {};
tableData = [];
collisions = {};
maxCodesLength = 10;
created() {
this.rstore = rstore;
}
mounted() {
this.checkCollisions();
this.updateTableData();
}
get mode() {
return this.$store.state.config.mode;
}
updateTableData() {
let result = rstore.hotKeys.map(hk => hk.name).filter(name => (this.mode == 'liberama.top' || name != 'libs'));
const search = this.search.toLowerCase();
const codesIncludeSearch = (action) => {
for (const code of this.modelValue[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.modelValue)) {
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.modelValue[action]);
const index = codes.indexOf(code);
if (index >= 0) {
codes.splice(index, 1);
const newValue = Object.assign({}, this.modelValue, {[action]: codes});
this.$emit('update:modelValue', newValue);
}
}
async addHotKey(action) {
if (this.modelValue[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.modelValue[action]);
if (codes.indexOf(result) < 0) {
codes.push(result);
const newValue = Object.assign({}, this.modelValue, {[action]: codes});
this.$emit('update:modelValue', 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.modelValue, {[action]: codes});
this.$emit('update:modelValue', newValue);
}
} catch (e) {
//
}
}
async defaultHotKeyAll() {
try {
if (await this.$root.stdDialog.confirm('Подтвердите сброс сочетаний клавиш<br>для ВСЕХ команд в значения по умолчанию:', ' ')) {
const newValue = Object.assign({}, rstore.settingDefaults.userHotKeys);
this.$emit('update:modelValue', newValue);
}
} catch (e) {
//
}
}
}
export default vueComponent(UserHotKeys);
//-----------------------------------------------------------------------------
</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,116 @@
<!---------------------------------------------->
<div class="hidden part-header">
Цвет
</div>
<div class="item row">
<div class="label-2">
Текст
</div>
<div class="col row">
<q-input
v-model="textColorFiltered"
class="col-left no-mp"
outlined dense
:rules="['hexColor']"
style="max-width: 150px"
>
<template #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>
</div>
</div>
<div class="q-mt-md" />
<div class="item row">
<div class="label-2">
Фон
</div>
<div class="col row">
<q-input
v-model="bgColorFiltered"
class="col-left no-mp"
outlined dense
:rules="['hexColor']"
style="max-width: 150px"
>
<template #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>
</div>
<div class="q-mt-md" />
<div class="item row">
<div class="label-2">
Обои
</div>
<div class="col row items-center">
<q-select
v-model="wallpaper"
class="col-left no-mp"
:options="wallpaperOptions"
dropdown-icon="la la-angle-down la-sm"
outlined dense emit-value map-options
>
<template #selected-item="scope">
<div>
{{ scope.opt.label }}
</div>
<div v-show="scope.opt.value" class="q-ml-sm" :class="scope.opt.value" style="width: 40px; height: 28px;"></div>
</template>
<template #option="scope">
<q-item
v-bind="scope.itemProps"
>
<q-item-section style="min-width: 50px;">
<q-item-label v-html="scope.opt.label" />
</q-item-section>
<q-item-section v-show="scope.opt.value" :class="scope.opt.value" style="min-width: 70px; min-height: 50px;" />
</q-item>
</template>
</q-select>
<div class="q-px-xs" />
<q-btn class="q-ml-sm" round dense color="blue" icon="la la-plus" @click.stop="loadWallpaperFileClick">
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
Добавить файл обоев
</q-tooltip>
</q-btn>
<q-btn v-show="wallpaper.indexOf('user-paper') === 0" class="q-ml-sm" round dense color="blue" icon="la la-minus" @click.stop="delWallpaper">
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
Удалить выбранные обои
</q-tooltip>
</q-btn>
</div>
</div>
<div class="q-mt-sm" />
<div class="item row">
<div class="label-2"></div>
<div class="col row items-center">
<q-checkbox v-model="wallpaperIgnoreStatusBar" size="xs" label="Не включать строку статуса в обои" />
</div>
</div>
<input ref="file" type="file" style="display: none;" @change="loadWallpaperFile" />

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,124 @@
<!---------------------------------------------->
<div class="hidden part-header">Режим</div>
<div class="item row">
<div class="label-2"></div>
<div class="col row">
<q-checkbox v-model="dualPageMode" size="xs" label="Двухстраничный режим" />
</div>
</div>
<div class="part-header">Страницы</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 v-show="dualPageMode" class="item row">
<div class="label-2">Отступ внутри</div>
<div class="col row">
<NumInput class="col-left" v-model="dualIndentLR" :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 v-show="dualPageMode">
<div class="part-header">Разделитель</div>
<div class="item row no-wrap">
<div class="label-2">Цвет</div>
<div class="col-left row">
<q-input class="col-left no-mp"
outlined dense
v-model="dualDivColorFiltered"
:rules="['hexColor']"
style="max-width: 150px"
:disable="dualDivColorAsText"
>
<template v-slot:prepend>
<q-icon name="la la-angle-down la-xs" class="cursor-pointer text-white" :style="colorPanStyle('div')">
<q-popup-proxy anchor="bottom middle" self="top middle">
<div>
<q-color v-model="dualDivColor"
no-header default-view="palette" :palette="predefineTextColors"
/>
</div>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
</div>
<div class="q-px-xs"/>
<q-checkbox v-model="dualDivColorAsText" size="xs" label="Как у текста" />
</div>
<div class="item row">
<div class="label-2">Прозрачность</div>
<div class="col row">
<NumInput class="col-left" v-model="dualDivColorAlpha" :min="0" :max="1" :digits="2" :step="0.1"/>
</div>
</div>
<div class="item row">
<div class="label-2">Ширина (px)</div>
<div class="col row">
<NumInput class="col-left" v-model="dualDivWidth" :min="0" :max="100">
<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="dualDivHeight" :min="0" :max="100">
<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="dualDivStrokeFill" :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="dualDivStrokeGap" :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="dualDivShadowWidth" :min="0" :max="100"/>
</div>
</div>
</div>

View File

@@ -0,0 +1,64 @@
<!---------------------------------------------->
<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 v-show="showStatusBar" class="q-ml-sm" v-model="statusBarTop" size="xs" label="Вверху/внизу" />
</div>
</div>
<div v-show="showStatusBar" class="item row no-wrap">
<div class="label-2">Цвет</div>
<div class="col-left row">
<q-input class="col-left no-mp"
outlined dense
v-model="statusBarColorFiltered"
:rules="['hexColor']"
style="max-width: 150px"
:disable="statusBarColorAsText"
>
<template v-slot:prepend>
<q-icon name="la la-angle-down la-xs" class="cursor-pointer text-white" :style="colorPanStyle('statusbar')">
<q-popup-proxy anchor="bottom middle" self="top middle">
<div>
<q-color v-model="statusBarColor"
no-header default-view="palette" :palette="predefineTextColors"
/>
</div>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
</div>
<div class="q-px-xs"/>
<q-checkbox v-model="statusBarColorAsText" size="xs" label="Как у текста"/>
</div>
<div v-show="showStatusBar" 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"/>
</div>
</div>
<div v-show="showStatusBar" class="item row">
<div class="label-2">Высота</div>
<div class="col row">
<NumInput class="col-left" v-model="statusBarHeight" :min="5" :max="100"/>
</div>
</div>
<div v-show="showStatusBar" 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,127 @@
<!---------------------------------------------->
<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="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" size="xs" label="Ширина не более размера страницы" :disable="!showImages || dualPageMode"/>
</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

@@ -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

@@ -2,11 +2,11 @@ import {sleep} from '../../../share/utils';
export default class DrawHelper {
fontBySize(size) {
return `${size}px ${this.fontName}`;
return `${size}px '${this.fontName}'`;
}
fontByStyle(style) {
return `${style.italic ? 'italic' : this.fontStyle} ${style.bold ? 'bold' : this.fontWeight} ${this.fontSize}px ${this.fontName}`;
return `${style.italic ? 'italic' : this.fontStyle} ${style.bold ? 'bold' : this.fontWeight} ${this.fontSize}px '${this.fontName}'`;
}
measureText(text, style) {// eslint-disable-line no-unused-vars
@@ -19,6 +19,109 @@ export default class DrawHelper {
return this.context.measureText(text).width;
}
drawLine(line, lineIndex, baseLineIndex, sel, imageDrawn) {
/* line:
{
begin: Number,
end: Number,
first: Boolean,
last: Boolean,
parts: array of {
style: {bold: Boolean, italic: Boolean, center: Boolean},
image: {local: Boolean, inline: Boolean, id: String, imageLine: Number, lineCount: Number, paraIndex: Number},
text: String,
}
}*/
let out = '<div>';
let lineText = '';
let center = false;
let space = 0;
let j = 0;
//формируем строку
for (const part of line.parts) {
let tOpen = '';
tOpen += (part.style.bold ? '<b>' : '');
tOpen += (part.style.italic ? '<i>' : '');
tOpen += (part.style.sup ? '<span style="vertical-align: baseline; position: relative; line-height: 0; top: -0.3em">' : '');
tOpen += (part.style.sub ? '<span style="vertical-align: baseline; position: relative; line-height: 0; top: 0.3em">' : '');
let tClose = '';
tClose += (part.style.sub ? '</span>' : '');
tClose += (part.style.sup ? '</span>' : '');
tClose += (part.style.italic ? '</i>' : '');
tClose += (part.style.bold ? '</b>' : '');
let text = '';
if (lineIndex == 0 && this.searching) {
for (let k = 0; k < part.text.length; k++) {
text += (sel.has(j) ? `<ins>${part.text[k]}</ins>` : part.text[k]);
j++;
}
} else
text = part.text;
if (text && text.trim() == '')
text = `<span style="white-space: pre">${text}</span>`;
lineText += `${tOpen}${text}${tClose}`;
center = center || part.style.center;
space = (part.style.space > space ? part.style.space : space);
//избражения
//image: {local: Boolean, inline: Boolean, id: String, imageLine: Number, lineCount: Number, paraIndex: Number, w: Number, h: Number},
const img = part.image;
if (img && img.id && !img.inline && !imageDrawn.has(img.paraIndex)) {
const bin = this.parsed.binary[img.id];
if (bin) {
let resize = '';
if (bin.h > img.h) {
resize = `height: ${img.h}px`;
}
const left = (this.w - img.w)/2;
const top = ((img.lineCount*this.lineHeight - img.h)/2) + (lineIndex - baseLineIndex - img.imageLine)*this.lineHeight;
if (img.local) {
lineText += `<img src="data:${bin.type};base64,${bin.data}" style="position: absolute; left: ${left}px; top: ${top}px; ${resize}"/>`;
} else {
lineText += `<img src="${img.id}" style="position: absolute; left: ${left}px; top: ${top}px; ${resize}"/>`;
}
}
imageDrawn.add(img.paraIndex);
}
if (img && img.id && img.inline) {
if (img.local) {
const bin = this.parsed.binary[img.id];
if (bin) {
let resize = '';
if (bin.h > this.fontSize) {
resize = `height: ${this.fontSize - 3}px`;
}
lineText += `<img src="data:${bin.type};base64,${bin.data}" style="${resize}"/>`;
}
} else {
//
}
}
}
const centerStyle = (center ? `text-align: center; text-align-last: center; width: ${this.w}px` : '')
if ((line.first || space) && !center) {
let p = (line.first ? this.p : 0);
p = (space ? p + this.p*space : p);
lineText = `<span style="display: inline-block; margin-left: ${p}px"></span>${lineText}`;
}
if (line.last || center)
lineText = `<span style="display: inline-block; ${centerStyle}">${lineText}</span>`;
out += lineText + '</div>';
return out;
}
drawPage(lines, isScrolling) {
if (!this.lastBook || this.pageLineCount < 1 || !this.book || !lines || !this.parsed.textLength)
return '';
@@ -26,90 +129,78 @@ export default class DrawHelper {
const font = this.fontByStyle({});
const justify = (this.textAlignJustify ? 'text-align: justify; text-align-last: justify;' : '');
let out = `<div class="layout" style="width: ${this.w}px; height: ${this.h}px;` +
const boxH = this.h + (isScrolling ? this.lineHeight : 0);
let out = `<div class="row no-wrap" style="width: ${this.boxW}px; height: ${boxH}px;` +
` position: absolute; top: ${this.fontSize*this.textShift}px; color: ${this.textColor}; font: ${font}; ${justify}` +
` line-height: ${this.lineHeight}px;">`;
` line-height: ${this.lineHeight}px; white-space: nowrap;">`;
let imageDrawn1 = new Set();
let imageDrawn2 = new Set();
let len = lines.length;
const lineCount = this.pageLineCount + (isScrolling ? 1 : 0);
len = (len > lineCount ? lineCount : len);
for (let i = 0; i < len; i++) {
const line = lines[i];
/* line:
{
begin: Number,
end: Number,
first: Boolean,
last: Boolean,
parts: array of {
style: {bold: Boolean, italic: Boolean, center: Boolean}
text: String,
}
}*/
let sel = new Set();
if (i == 0 && this.searching) {
let pureText = '';
for (const part of line.parts) {
pureText += part.text;
}
pureText = pureText.toLowerCase();
let j = 0;
while (1) {// eslint-disable-line no-constant-condition
j = pureText.indexOf(this.needle, j);
if (j >= 0) {
for (let k = 0; k < this.needle.length; k++) {
sel.add(j + k);
}
} else
break;
j++;
}
//поиск
let sel = new Set();
if (len > 0 && this.searching) {
const line = lines[0];
let pureText = '';
for (const part of line.parts) {
pureText += part.text;
}
let lineText = '';
let center = false;
pureText = pureText.toLowerCase();
let j = 0;
for (const part of line.parts) {
let tOpen = (part.style.bold ? '<b>' : '');
tOpen += (part.style.italic ? '<i>' : '');
let tClose = (part.style.italic ? '</i>' : '');
tClose += (part.style.bold ? '</b>' : '');
let text = '';
if (i == 0 && this.searching) {
for (let k = 0; k < part.text.length; k++) {
text += (sel.has(j) ? `<ins>${part.text[k]}</ins>` : part.text[k]);
j++;
while (1) {// eslint-disable-line no-constant-condition
j = pureText.indexOf(this.needle, j);
if (j >= 0) {
for (let k = 0; k < this.needle.length; k++) {
sel.add(j + k);
}
} else
text = part.text;
lineText += `${tOpen}${text}${tClose}`;
center = center || part.style.center;
break;
j++;
}
}
const centerStyle = (center ? `text-align: center; text-align-last: center; width: ${this.w}px` : '')
if (line.first)
lineText = `<span style="display: inline-block; margin-left: ${this.p}px"></span>${lineText}`;
if (line.last || center)
lineText = `<span style="display: inline-block; ${centerStyle}">${lineText}</span>`;
//отрисовка строк
if (!this.dualPageMode) {
out += `<div class="fit">`;
for (let i = 0; i < len; i++) {
out += this.drawLine(lines[i], i, 0, sel, imageDrawn1);
}
out += `</div>`;
} else {
//левая страница
out += `<div style="width: ${this.w}px; margin-left: ${this.dualIndentLR}px; position: relative;">`;
const l2 = (this.pageRowsCount > len ? len : this.pageRowsCount);
for (let i = 0; i < l2; i++) {
out += this.drawLine(lines[i], i, 0, sel, imageDrawn1);
}
out += '</div>';
out += (i > 0 ? '<br>' : '') + lineText;
//разделитель
out += `<div style="width: ${this.dualIndentLR*2}px;"></div>`;
//правая страница
out += `<div style="width: ${this.w}px; margin-right: ${this.dualIndentLR}px; position: relative;">`;
for (let i = l2; i < len; i++) {
out += this.drawLine(lines[i], i, l2, sel, imageDrawn2);
}
out += '</div>';
}
out += '</div>';
return out;
}
drawPercentBar(x, y, w, h, font, fontSize, bookPos, textLength) {
drawPercentBar(x, y, w, h, font, fontSize, bookPos, textLength, imageNum, imageLength) {
const pad = 3;
const fh = h - 2*pad;
const fh2 = fh/2;
const t1 = `${Math.floor((bookPos + 1)/1000)}k/${Math.floor(textLength/1000)}k`;
const tImg = (imageNum > 0 ? ` (${imageNum}/${imageLength})` : '');
const t1 = `${Math.floor((bookPos + 1)/1000)}/${Math.floor(textLength/1000)}${tImg}`;
const w1 = this.measureTextFont(t1, font) + fh2;
const read = (bookPos + 1)/textLength;
const t2 = `${(read*100).toFixed(2)}%`;
@@ -122,8 +213,8 @@ export default class DrawHelper {
if (w1 + w2 + w3 <= w && w3 > (10 + fh2)) {
const barWidth = w - w1 - w2 - fh2;
out += this.strokeRect(x + w1, y + pad, barWidth, fh - 2, this.statusBarColor);
out += this.fillRect(x + w1 + 2, y + pad + 2, (barWidth - 4)*read, fh - 6, this.statusBarColor);
out += this.strokeRect(x + w1, y + pad, barWidth, fh - 2, this.statusBarRgbaColor);
out += this.fillRect(x + w1 + 2, y + pad + 2, (barWidth - 4)*read, fh - 6, this.statusBarRgbaColor);
}
if (w1 <= w)
@@ -132,16 +223,16 @@ export default class DrawHelper {
return out;
}
drawStatusBar(statusBarTop, statusBarHeight, bookPos, textLength, title) {
drawStatusBar(statusBarTop, statusBarHeight, bookPos, textLength, title, imageNum, imageLength) {
let out = `<div class="layout" style="` +
`width: ${this.realWidth}px; height: ${statusBarHeight}px; ` +
`color: ${this.statusBarColor}">`;
`color: ${this.statusBarRgbaColor}">`;
const fontSize = statusBarHeight*0.75;
const font = 'bold ' + this.fontBySize(fontSize);
out += this.fillRect(0, (statusBarTop ? statusBarHeight : 0), this.realWidth, 1, this.statusBarColor);
out += this.fillRect(0, (statusBarTop ? statusBarHeight : 0), this.realWidth, 1, this.statusBarRgbaColor);
const date = new Date();
const time = `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
@@ -150,7 +241,7 @@ export default class DrawHelper {
out += this.fillTextShift(this.fittingString(title, this.realWidth/2 - fontSize - 3, font), fontSize, 2, font, fontSize);
out += this.drawPercentBar(this.realWidth/2, 2, this.realWidth/2 - timeW - 2*fontSize, statusBarHeight, font, fontSize, bookPos, textLength);
out += this.drawPercentBar(this.realWidth/2 + fontSize, 2, this.realWidth/2 - timeW - 3*fontSize, statusBarHeight, font, fontSize, bookPos, textLength, imageNum, imageLength);
out += '</div>';
return out;
@@ -217,7 +308,7 @@ export default class DrawHelper {
}
async doPageAnimationRightShift(page1, page2, duration, isDown, animation1Finish) {
const s = this.w + this.fontSize;
const s = this.boxW + this.fontSize;
if (isDown) {
page1.style.transform = `translateX(${s}px)`;
@@ -267,4 +358,56 @@ export default class DrawHelper {
await animation1Finish(duration);
}
}
async doPageAnimationRotate(page1, page2, duration, isDown, animation1Finish, animation2Finish) {
if (isDown) {
page1.style.transform = `rotateY(90deg)`;
await sleep(30);
page2.style.transition = `${duration/2}ms ease-in`;
page2.style.transform = `rotateY(-90deg)`;
await animation2Finish(duration/2);
page1.style.transition = `${duration/2}ms ease-out`;
page1.style.transform = `rotateY(0deg)`;
await animation1Finish(duration/2);
} else {
page1.style.transform = `rotateY(-90deg)`;
await sleep(30);
page2.style.transition = `${duration/2}ms ease-in`;
page2.style.transform = `rotateY(90deg)`;
await animation2Finish(duration/2);
page1.style.transition = `${duration/2}ms ease-out`;
page1.style.transform = `rotateY(0deg)`;
await animation1Finish(duration/2);
}
}
async doPageAnimationFlip(page1, page2, duration, isDown, animation1Finish, animation2Finish, backgroundColor) {
page2.style.background = backgroundColor;
if (isDown) {
page2.style.transformOrigin = '5%';
await sleep(30);
page2.style.transition = `${duration}ms ease-in-out`;
page2.style.transform = `rotateY(-120deg) translateX(${this.w/4}px)`;
await animation2Finish(duration);
} else {
page2.style.transformOrigin = '95%';
await sleep(30);
page2.style.transition = `${duration}ms ease-in-out`;
page2.style.transform = `rotateY(120deg) translateX(-${this.w/4}px)`;
await animation2Finish(duration);
}
page2.style.transformOrigin = 'center';
page2.style.background = '';
}
}

View File

@@ -0,0 +1,93 @@
@keyframes page1-animation-thaw {
0% { opacity: 0; }
100% { opacity: 1; }
}
@keyframes page2-animation-thaw {
0% { opacity: 1; }
100% { opacity: 0; }
}
.paper1 {
background: url("images/paper1.jpg") center;
background-size: 100% 100%;
}
.paper2 {
background: url("images/paper2.jpg") center;
background-size: 100% 100%;
}
.paper3 {
background: url("images/paper3.jpg") center;
background-size: 100% 100%;
}
.paper4 {
background: url("images/paper4.jpg") center;
background-size: 100% 100%;
}
.paper5 {
background: url("images/paper5.jpg") center;
background-size: 100% 100%;
}
.paper6 {
background: url("images/paper6.jpg") center;
background-size: 100% 100%;
}
.paper7 {
background: url("images/paper7.jpg") center;
background-size: 100% 100%;
}
.paper8 {
background: url("images/paper8.jpg") center;
background-size: 100% 100%;
}
.paper9 {
background: url("images/paper9.jpg");
}
.paper10 {
background: url("images/paper10.png") center;
background-size: 100% 100%;
}
.paper11 {
background: url("images/paper11.png") center;
background-size: 100% 100%;
}
.paper12 {
background: url("images/paper12.png") center;
background-size: 100% 100%;
}
.paper13 {
background: url("images/paper13.png") center;
background-size: 100% 100%;
}
.paper14 {
background: url("images/paper14.png") center;
background-size: 100% 100%;
}
.paper15 {
background: url("images/paper15.png") center;
background-size: 100% 100%;
}
.paper16 {
background: url("images/paper16.png") center;
background-size: 100% 100%;
}
.paper17 {
background: url("images/paper17.png") center;
background-size: 100% 100%;
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -1,49 +1,105 @@
import localForage from 'localforage';
import _ from 'lodash';
import * as utils from '../../../share/utils';
import BookParser from './BookParser';
const maxDataSize = 500*1024*1024;//chars, not bytes
const maxDataSize = 500*1024*1024;//compressed bytes
//локальный кэш метаданных книг, ограничение maxDataSize
const bmMetaStore = localForage.createInstance({
name: 'bmMetaStore'
});
//локальный кэш самих книг, ограничение maxDataSize
const bmDataStore = localForage.createInstance({
name: 'bmDataStore'
});
const bmRecentStore = localForage.createInstance({
//список недавно открытых книг
const bmRecentStoreOld = localForage.createInstance({
name: 'bmRecentStore'
});
const bmRecentStoreNew = localForage.createInstance({
name: 'bmRecentStoreNew'
});
class BookManager {
async init() {
async init(settings) {
this.loaded = false;
this.settings = settings;
this.eventListeners = [];
this.books = {};
this.recent = {};
this.recentChanged1 = true;
this.recentChanged2 = true;
this.saveRecent = _.debounce(() => {
bmRecentStoreNew.setItem('recent', this.recent);
}, 300, {maxWait: 800});
this.saveRecentItem = _.debounce(() => {
bmRecentStoreNew.setItem('recent-item', this.recentItem);
this.recentRev = (this.recentRev < 1000 ? this.recentRev + 1 : 1);
bmRecentStoreNew.setItem('rev', this.recentRev);
}, 200, {maxWait: 300});
//загрузка bmRecentStore
this.recentRev = await bmRecentStoreNew.getItem('rev') || 0;
if (this.recentRev) {
this.recent = await bmRecentStoreNew.getItem('recent');
if (!this.recent)
this.recent = {};
this.recentItem = await bmRecentStoreNew.getItem('recent-item');
if (this.recentItem)
this.recent[this.recentItem.key] = this.recentItem;
this.recentLastKey = await bmRecentStoreNew.getItem('recent-last-key');
if (this.recentLastKey) {
const meta = await bmMetaStore.getItem(`bmMeta-${this.recentLastKey}`);
if (_.isObject(meta)) {
this.books[meta.key] = meta;
}
}
await this.cleanRecentBooks();
}
this.recentChanged = true;
this.loadStored();//no await
}
//Ленивая асинхронная загрузка bmMetaStore
async loadStored() {
//даем время для загрузки последней читаемой книги, чтобы не блокировать приложение
await utils.sleep(2000);
let len = await bmMetaStore.length();
for (let i = 0; i < len; i++) {
for (let i = len - 1; i >= 0; i--) {
const key = await bmMetaStore.key(i);
const keySplit = key.split('-');
if (keySplit.length == 2 && keySplit[0] == 'bmMeta') {
let meta = await bmMetaStore.getItem(key);
this.books[meta.key] = meta;
if (_.isObject(meta)) {
//уже может быть распарсена книга
const oldBook = this.books[meta.key];
this.books[meta.key] = meta;
if (oldBook && oldBook.parsed) {
this.books[meta.key].parsed = oldBook.parsed;
}
} else {
await bmMetaStore.removeItem(key);
}
}
}
len = await bmRecentStore.length();
for (let i = 0; i < len; i++) {
const key = await bmRecentStore.key(i);
let r = await bmRecentStore.getItem(key);
this.recent[r.key] = r;
}
await this.cleanBooks();
this.loaded = true;
this.emit('load-stored-finish');
}
async cleanBooks() {
@@ -53,7 +109,8 @@ class BookManager {
let toDel = null;
for (let key in this.books) {
let book = this.books[key];
size += (book.length ? book.length : 0);
const bookLength = (book.length ? book.length : 0);
size += (book.dataCompressedLength ? book.dataCompressedLength : bookLength);
if (book.addTime < min) {
toDel = book;
@@ -69,49 +126,173 @@ class BookManager {
}
}
async addBook(newBook, callback) {
if (!this.books)
await this.init();
async deflateWithProgress(data, callback) {
const chunkSize = 512*1024;
const deflator = new utils.pako.Deflate({level: 5});
let chunkTotal = 1 + Math.floor(data.length/chunkSize);
let chunkNum = 0;
let perc = 0;
let prevPerc = 0;
for (var i = 0; i < data.length; i += chunkSize) {
if ((i + chunkSize) >= data.length) {
deflator.push(data.substring(i, i + chunkSize), true);
} else {
deflator.push(data.substring(i, i + chunkSize), false);
}
chunkNum++;
perc = Math.round(chunkNum/chunkTotal*100);
if (perc != prevPerc) {
callback(perc);
await utils.sleep(1);
prevPerc = perc;
}
}
if (deflator.err) {
throw new Error(deflator.msg);
}
callback(100);
return deflator.result;
}
async inflateWithProgress(data, callback) {
const chunkSize = 512*1024;
const inflator = new utils.pako.Inflate({to: 'string'});
let chunkTotal = 1 + Math.floor(data.length/chunkSize);
let chunkNum = 0;
let perc = 0;
let prevPerc = 0;
for (var i = 0; i < data.length; i += chunkSize) {
if ((i + chunkSize) >= data.length) {
inflator.push(data.subarray(i, i + chunkSize), true);
} else {
inflator.push(data.subarray(i, i + chunkSize), false);
}
chunkNum++;
perc = Math.round(chunkNum/chunkTotal*100);
if (perc != prevPerc) {
callback(perc);
await utils.sleep(1);
prevPerc = perc;
}
}
if (inflator.err) {
throw new Error(inflator.msg);
}
callback(100);
return inflator.result;
}
async addBook(newBook, callback) {
let meta = {url: newBook.url, path: newBook.path};
meta.key = this.keyFromUrl(meta.url);
meta.addTime = Date.now();
const result = await this.parseBook(meta, newBook.data, callback);
const cb = (perc) => {
const p = Math.round(30*perc/100);
callback(p);
};
const cb2 = (perc) => {
const p = Math.round(30 + 65*perc/100);
callback(p);
};
const result = await this.parseBook(meta, newBook.data, cb);
result.dataCompressed = true;
let data = newBook.data;
if (result.dataCompressed) {
//data = utils.pako.deflate(data, {level: 5});
data = await this.deflateWithProgress(data, cb2);
result.dataCompressedLength = data.byteLength;
}
callback(95);
this.books[meta.key] = result;
await bmMetaStore.setItem(`bmMeta-${meta.key}`, this.metaOnly(result));
await bmDataStore.setItem(`bmData-${meta.key}`, result.data);
await bmDataStore.setItem(`bmData-${meta.key}`, data);
callback(100);
return result;
}
hasBookParsed(meta) {
async hasBookParsed(meta) {
if (!this.books)
return false;
if (!meta.url)
return false;
if (!meta.key)
meta.key = this.keyFromUrl(meta.url);
let book = this.books[meta.key];
if (!book && !this.loaded) {
book = await bmMetaStore.getItem(`bmMeta-${meta.key}`);
if (book)
this.books[meta.key] = book;
}
return !!(book && book.parsed);
}
async getBook(meta, callback) {
if (!this.books)
await this.init();
let result = undefined;
if (!meta.key)
meta.key = this.keyFromUrl(meta.url);
result = this.books[meta.key];
if (result && !result.data) {
result.data = await bmDataStore.getItem(`bmData-${meta.key}`);
this.books[meta.key] = result;
if (!result) {
result = await bmMetaStore.getItem(`bmMeta-${meta.key}`);
if (result)
this.books[meta.key] = result;
}
//Если файл на сервере изменился, считаем, что в кеше его нету
if (meta.path && result && meta.path != result.path) {
return;
}
if (result && !result.parsed) {
result = await this.parseBook(result, result.data, callback);
let data = await bmDataStore.getItem(`bmData-${meta.key}`);
callback(5);
await utils.sleep(10);
let cb = (perc) => {
const p = 5 + Math.round(15*perc/100);
callback(p);
};
if (result.dataCompressed) {
try {
//data = utils.pako.inflate(data, {to: 'string'});
data = await this.inflateWithProgress(data, cb);
} catch (e) {
this.delBook(meta);
throw e;
}
}
callback(20);
cb = (perc) => {
const p = 20 + Math.round(80*perc/100);
callback(p);
};
result = await this.parseBook(result, data, cb);
this.books[meta.key] = result;
}
@@ -119,9 +300,6 @@ class BookManager {
}
async delBook(meta) {
if (!this.books)
await this.init();
await bmMetaStore.removeItem(`bmMeta-${meta.key}`);
await bmDataStore.removeItem(`bmData-${meta.key}`);
@@ -129,15 +307,12 @@ class BookManager {
}
async parseBook(meta, data, callback) {
if (!this.books)
await this.init();
const parsed = new BookParser();
const parsed = new BookParser(this.settings);
const parsedMeta = await parsed.parse(data, callback);
const result = Object.assign({}, meta, parsedMeta, {
length: data.length,
textLength: parsed.textLength,
data,
parsed
});
@@ -146,7 +321,6 @@ class BookManager {
metaOnly(book) {
let result = Object.assign({}, book);
delete result.data;
delete result.parsed;
return result;
}
@@ -155,85 +329,117 @@ class BookManager {
return utils.stringToHex(url);
}
async setRecentBook(value, noTouch) {
if (!this.recent)
await this.init();
const result = Object.assign({}, value);
if (!noTouch)
Object.assign(result, {touchTime: Date.now()});
//-- recent --------------------------------------------------------------
async recentSetItem(item = null, skipCheck = false) {
const rev = await bmRecentStoreNew.getItem('rev');
if (rev != this.recentRev && !skipCheck) {//если изменение произошло в другой вкладке барузера
const newRecent = await bmRecentStoreNew.getItem('recent');
Object.assign(this.recent, newRecent);
this.recentItem = await bmRecentStoreNew.getItem('recent-item');
this.recentRev = rev;
}
if (result.textLength && !result.bookPos && result.bookPosPercent)
result.bookPos = Math.round(result.bookPosPercent*result.textLength);
const prevKey = (this.recentItem ? this.recentItem.key : '');
if (item) {
this.recent[item.key] = item;
this.recentItem = item;
} else {
this.recentItem = null;
}
this.recent[result.key] = result;
this.saveRecentItem();
await bmRecentStore.setItem(result.key, result);
await this.cleanRecentBooks();
if (!item || prevKey != item.key) {
this.saveRecent();
}
this.recentChanged1 = true;
this.recentChanged2 = true;
this.recentChanged = true;
if (item) {
this.emit('recent-changed', item.key);
} else {
this.emit('recent-changed');
}
}
async recentSetLastKey(key) {
this.recentLastKey = key;
await bmRecentStoreNew.setItem('recent-last-key', this.recentLastKey);
}
async setRecentBook(value) {
let result = this.metaOnly(value);
result.touchTime = Date.now();
result.deleted = 0;
if (this.recent[result.key]) {
result = Object.assign({}, this.recent[result.key], result);
}
await this.recentSetLastKey(result.key);
await this.recentSetItem(result);
return result;
}
async getRecentBook(value) {
if (!this.recent)
await this.init();
return this.recent[value.key];
}
async delRecentBook(value) {
if (!this.recent)
await this.init();
const item = this.recent[value.key];
item.deleted = 1;
await bmRecentStore.removeItem(value.key);
delete this.recent[value.key];
this.recentChanged1 = true;
this.recentChanged2 = true;
if (this.recentLastKey == value.key) {
await this.recentSetLastKey(null);
}
await this.recentSetItem(item);
this.emit('recent-deleted', value.key);
}
async cleanRecentBooks() {
if (!this.recent)
await this.init();
const sorted = this.getSortedRecent();
if (Object.keys(this.recent).length > 1000) {
let min = Date.now();
let found = null;
for (let key in this.recent) {
const book = this.recent[key];
if (book.touchTime < min) {
min = book.touchTime;
found = book;
}
}
if (found) {
await this.delRecentBook(found);
await this.cleanRecentBooks();
}
let isDel = false;
for (let i = 1000; i < sorted.length; i++) {
delete this.recent[sorted[i].key];
isDel = true;
}
this.sortedRecentCached = null;
if (isDel)
await this.recentSetItem();
return isDel;
}
mostRecentBook() {
if (!this.recentChanged1 && this.mostRecentCached) {
return this.mostRecentCached;
if (this.recentLastKey) {
return this.recent[this.recentLastKey];
}
const oldKey = this.recentLastKey;
let max = 0;
let result = null;
for (let key in this.recent) {
const book = this.recent[key];
if (book.touchTime > max) {
if (!book.deleted && book.touchTime > max) {
max = book.touchTime;
result = book;
}
}
this.mostRecentCached = result;
this.recentChanged1 = false;
const newRecentLastKey = (result ? result.key : null);
this.recentSetLastKey(newRecentLastKey);//no await
if (newRecentLastKey !== oldKey)
this.emit('recent-changed');
return result;
}
getSortedRecent() {
if (!this.recentChanged2 && this.sortedRecentCached) {
if (!this.recentChanged && this.sortedRecentCached) {
return this.sortedRecentCached;
}
@@ -242,10 +448,48 @@ class BookManager {
result.sort((a, b) => b.touchTime - a.touchTime);
this.sortedRecentCached = result;
this.recentChanged2 = false;
this.recentChanged = false;
return result;
}
async setRecent(value) {
const mergedRecent = _.cloneDeep(this.recent);
Object.assign(mergedRecent, value);
//подстраховка от hotReload
for (let i of Object.keys(mergedRecent)) {
if (!mergedRecent[i].key || mergedRecent[i].key !== i)
delete mergedRecent[i];
}
this.recent = mergedRecent;
await this.recentSetLastKey(null);
await this.recentSetItem(null, true);
this.emit('set-recent');
}
addEventListener(listener) {
if (this.eventListeners.indexOf(listener) < 0)
this.eventListeners.push(listener);
}
removeEventListener(listener) {
const i = this.eventListeners.indexOf(listener);
if (i >= 0)
this.eventListeners.splice(i, 1);
}
emit(eventName, value) {
if (this.eventListeners) {
for (const listener of this.eventListeners) {
//console.log(eventName);
listener(eventName, value);
}
}
}
}

View File

@@ -1,70 +0,0 @@
export default async function restoreOldSettings(settings, bookManager, commit) {
const oldSets = localStorage['colorSetting'];
let isOld = false;
for (let i = 0; i < localStorage.length; i++) {
let key = unescape(localStorage.key(i));
if (key.indexOf('bpr-book-') == 0)
isOld = true;
}
if (isOld || oldSets) {
let newSettings = null;
if (oldSets) {
const [textColor, backgroundColor, lineStep, , , statusBarHeight, scInt] = unescape(oldSets).split('|');
const fontSize = Math.round(lineStep*0.8);
const scrollingDelay = fontSize*scInt;
newSettings = Object.assign({}, settings, {
textColor,
backgroundColor,
fontSize,
statusBarHeight: statusBarHeight*1,
scrollingDelay,
});
}
for (let i = 0; i < localStorage.length; i++) {
let key = localStorage.key(i);
if (key.indexOf('bpr-') == 0) {
let v = unescape(localStorage[key]);
key = unescape(key);
if (key.lastIndexOf('=timestamp') == key.length - 10) {
continue;
}
if (key.indexOf('bpr-book-') == 0) {
const url = key.substr(9);
const [scrollTop, scrollHeight, ] = v.split('|');
const bookPosPercent = scrollTop*1/(scrollHeight*1 + 1);
const title = unescape(localStorage[`bpr-title-${escape(url)}`]);
const author = unescape(localStorage[`bpr-author-${escape(url)}`]);
const time = unescape(localStorage[`bpr-book-${escape(url)}=timestamp`]).split(';')[0];
const touchTime = Date.parse(time);
const bookKey = bookManager.keyFromUrl(url);
const recent = await bookManager.getRecentBook({key: bookKey});
if (!recent) {
await bookManager.setRecentBook({
key: bookKey,
touchTime,
bookPosPercent,
url,
fb2: {
bookTitle: title,
lastName: author,
}
}, true);
}
}
}
}
localStorage.clear();
if (oldSets)
commit('reader/setSettings', newSettings);
}
}

View File

@@ -0,0 +1,40 @@
import localForage from 'localforage';
//import _ from 'lodash';
const wpStore = localForage.createInstance({
name: 'wallpaperStorage'
});
class WallpaperStorage {
constructor() {
this.cachedKeys = [];
}
async init() {
this.cachedKeys = await wpStore.keys();
}
async getLength() {
return await wpStore.length();
}
async setData(key, data) {
await wpStore.setItem(key, data);
this.cachedKeys = await wpStore.keys();
}
async getData(key) {
return await wpStore.getItem(key);
}
async removeData(key) {
await wpStore.removeItem(key);
this.cachedKeys = await wpStore.keys();
}
keyExists(key) {//не асинхронная
return this.cachedKeys.includes(key);
}
}
export default new WallpaperStorage();

View File

@@ -0,0 +1,622 @@
export const versionHistory = [
{
version: '0.11.3',
releaseDate: '2022-03-29',
showUntil: '2022-03-28',
content:
`
<ul>
<li>исправления багов</li>
</ul>
`
},
{
version: '0.11.2',
releaseDate: '2022-01-11',
showUntil: '2022-01-10',
content:
`
<ul>
<li>исправления багов</li>
</ul>
`
},
{
version: '0.11.1',
releaseDate: '2021-12-03',
showUntil: '2021-12-02',
content:
`
<ul>
<li>переход на JembaDb вместо SQLite</li>
</ul>
`
},
{
version: '0.11.0',
releaseDate: '2021-11-18',
showUntil: '2021-11-17',
content:
`
<ul>
<li>переход на Vue 3</li>
</ul>
`
},
{
version: '0.10.3',
releaseDate: '2021-10-24',
showUntil: '2021-10-23',
content:
`
<ul>
<li>исправления багов</li>
</ul>
`
},
{
version: '0.10.2',
releaseDate: '2021-10-19',
showUntil: '2021-10-18',
content:
`
<ul>
<li>актуализация версий пакетов и стека используемых технологий</li>
</ul>
`
},
{
version: '0.10.1',
releaseDate: '2021-10-10',
showUntil: '2021-10-09',
content:
`
<ul>
<li>исправления багов</li>
</ul>
`
},
{
version: '0.10.0',
releaseDate: '2021-02-09',
showUntil: '2021-02-16',
content:
`
<ul>
<li>добавлен двухстраничный режим</li>
<li>в настройки добавлены все кириллические веб-шрифты от google</li>
<li>в настройки добавлена возможность загрузки пользовательских обоев (пока без синхронизации)</li>
<li>немного улучшен парсинг fb2</li>
</ul>
`
},
{
version: '0.9.12',
releaseDate: '2020-12-18',
showUntil: '2020-12-17',
content:
`
<ul>
<li>добавлена вкладка "Изображения" в окно оглавления</li>
<li>настройки конвертирования вынесены в отдельную вкладку</li>
<li>добавлена кнопка для быстрого доступа к настройкам конвертирования</li>
<li>улучшения работы конвертеров</li>
</ul>
`
},
{
version: '0.9.11',
releaseDate: '2020-12-09',
showUntil: '2020-12-08',
content:
`
<ul>
<li>оптимизации, улучшения работы конвертеров</li>
</ul>
`
},
{
version: '0.9.10',
releaseDate: '2020-12-03',
showUntil: '2020-12-10',
content:
`
<ul>
<li>добавлена частичная поддержка формата Djvu</li>
<li>добавлена поддержка Rar-архивов</li>
<li>исправления багов</li>
</ul>
`
},
{
version: '0.9.9',
releaseDate: '2020-11-21',
showUntil: '2020-11-20',
content:
`
<ul>
<li>оптимизации, исправления багов</li>
</ul>
`
},
{
version: '0.9.8',
releaseDate: '2020-11-13',
showUntil: '2020-11-12',
content:
`
<ul>
<li>добавлено окно "Оглавление/закладки"</li>
</ul>
`
},
{
version: '0.9.7',
releaseDate: '2020-11-12',
showUntil: '2020-11-11',
content:
`
<ul>
<li>исправления багов</li>
</ul>
`
},
{
version: '0.9.6',
releaseDate: '2020-11-06',
showUntil: '2020-11-05',
content:
`
<ul>
<li>завершена работа над новым окном "Библиотека"</li>
<li>исправления багов</li>
</ul>
`
},
{
version: '0.9.5',
releaseDate: '2020-11-01',
showUntil: '2020-10-31',
content:
`
<ul>
<li>на панель инструментов добавлена новая кнопка "Обновить с разбиением на параграфы"</li>
<li>исправления багов</li>
</ul>
`
},
{
version: '0.9.4',
releaseDate: '2020-10-29',
showUntil: '2020-10-28',
content:
`
<ul>
<li>заработал новый сайт <a href="https://liberama.top">https://liberama.top</a>, где будет более свободный обмен книгами</li>
<li>для liberama.top добавлено новое окно: "Библиотека"</li>
<li>исправления багов</li>
</ul>
`
},
{
version: '0.9.3',
releaseDate: '2020-05-21',
showUntil: '2020-05-20',
content:
`
<ul>
<li>исправления багов</li>
</ul>
`
},
{
version: '0.9.2',
releaseDate: '2020-03-15',
showUntil: '2020-04-25',
content:
`
<ul>
<li>в настройки добавлена возможность назначать сочетания клавиш на команды в читалке</li>
<li>переход на Service Worker вместо AppCache для автономного режима работы</li>
<li>исправления багов</li>
</ul>
`
},
{
version: '0.9.1',
releaseDate: '2020-03-03',
showUntil: '2020-03-02',
content:
`
<ul>
<li>улучшение работы серверной части</li>
<li>незначительные изменения интерфейса</li>
</ul>
`
},
{
version: '0.9.0',
releaseDate: '2020-02-26',
showUntil: '2020-02-25',
content:
`
<ul>
<li>переход на UI-фреймфорк Quasar</li>
<li>незначительные изменения интерфейса</li>
</ul>
`
},
{
version: '0.8.4',
releaseDate: '2020-02-06',
showUntil: '2020-02-05',
content:
`
<ul>
<li>добавлен paypal-адрес для пожертвований</li>
<li>исправления багов</li>
</ul>
`
},
{
version: '0.8.3',
releaseDate: '2020-01-28',
showUntil: '2020-01-27',
content:
`
<ul>
<li>добавлено всплывающее окно с акцией "Оплатим хостинг вместе"</li>
<li>внутренние оптимизации</li>
</ul>
`
},
{
version: '0.8.2',
releaseDate: '2020-01-20',
showUntil: '2020-01-19',
content:
`
<ul>
<li>внутренние оптимизации</li>
</ul>
`
},
{
version: '0.8.1',
releaseDate: '2020-01-07',
showUntil: '2020-01-06',
content:
`
<ul>
<li>добавлена частичная поддержка формата FB3</li>
<li>исправлен баг "Request path contains unescaped characters"</li>
</ul>
`
},
{
version: '0.8.0',
releaseDate: '2020-01-02',
showUntil: '2020-01-05',
content:
`
<ul>
<li>окончательный переход на https</li>
<li>код проекта теперь Open Source: <a href="https://github.com/bookpauk/liberama" target="_blank">https://github.com/bookpauk/liberama</a></li>
</ul>
`
},
{
version: '0.7.9',
releaseDate: '2019-11-27',
showUntil: '2019-11-26',
content:
`
<ul>
<li>добавлен неубираемый баннер для http-версии о переходе на httpS</li>
<li>исправления багов</li>
</ul>
`
},
{
version: '0.7.8',
releaseDate: '2019-11-25',
showUntil: '2019-11-24',
content:
`
<ul>
<li>улучшение html-фильтров для сайтов</li>
<li>исправления багов</li>
</ul>
`
},
{
version: '0.7.7',
releaseDate: '2019-11-06',
showUntil: '2019-11-10',
content:
`
<ul>
<li>добавлены следующие жесты для тачскрина (только при включенной опции "управление кликом"):</li>
<ul>
<li style="list-style-type: square">от центра вверх: на весь экран</li>
<li style="list-style-type: square">от центра вниз: плавный скроллинг</li>
<li style="list-style-type: square">от центра вправо: увеличить скорость скроллинга</li>
<li style="list-style-type: square">от центра влево: уменьшить скорость скроллинга</li>
</ul>
</ul>
`
},
{
version: '0.7.6',
releaseDate: '2019-10-30',
showUntil: '2019-10-29',
content:
`
<ul>
<li>исправления багов</li>
</ul>
`
},
{
version: '0.7.5',
releaseDate: '2019-10-22',
showUntil: '2019-10-21',
content:
`
<ul>
<li>исправления багов</li>
</ul>
`
},
{
version: '0.7.3',
releaseDate: '2019-10-18',
showUntil: '2019-10-17',
content:
`
<ul>
<li>внутренние переделки механизма синхронизации с сервером</li>
<li>добавлен html-фильтр для сайтов www.fanfiction.net, archiveofourown.org</li>
<li>добавлен параметр "Включить html-фильтр для сайтов" в раздел "Вид"->"Текст" в настройках</li>
<li>исправления багов</li>
</ul>
`
},
{
version: '0.7.1',
releaseDate: '2019-09-20',
showUntil: '2019-09-19',
content:
`
<ul>
<li>исправления багов</li>
<li>на панель управления добавлена кнопка "Автономный режим"</li>
<li>актуализирована справка</li>
</ul>
`
},
{
version: '0.7.0',
releaseDate: '2019-09-07',
showUntil: '2019-10-01',
content:
`
<ul>
<li>налажена работа https-версии сайта, рекомендуется плавный переход</li>
<li>добавлена возможность загрузки и работы https-версии читалки в оффлайн-режиме (при отсутствии интернета)</li>
<li>упрощение механизма серверной синхронизации с целью повышения надежности и избавления от багов</li>
<li>окна теперь можно перемещать за заголовок</li>
<li>немного улучшен внешний вид и управление на смартфонах</li>
<li>добавлен параметр "Компактность" в раздел "Вид"->"Текст" в настройках</li>
</ul>
`
},
{
version: '0.6.10',
releaseDate: '2019-07-21',
showUntil: '2019-07-20',
content:
`
<ul>
<li>исправления багов</li>
</ul>
`
},
{
version: '0.6.9',
releaseDate: '2019-06-23',
showUntil: '2019-06-22',
content:
`
<ul>
<li>исправлен баг - падение сервера при распаковке битых архивов книг</li>
<li>исправлен баг - не распознавались некоторые книги формата fb2 в кодировке utf8</li>
<li>добавлены новые варианты анимации перелистывания</li>
<li>на страницу загрузки добавлен блок "Поделиться"</li>
<li>улучшены прогрессбары</li>
<li>исправления недочетов, небольшие оптимизации</li>
</ul>
`
},
{
version: '0.6.7',
releaseDate: '2019-05-30',
showUntil: '2019-06-05',
content:
`
<ul>
<li>добавлен диалог "Что нового"</li>
<li>в справку добавлена история версий проекта</li>
<li>добавлена возможность настройки отображаемых кнопок на панели управления</li>
<li>некоторые кнопки на панели управления были скрыты по умолчанию</li>
<li>на страницу загрузки добавлена возможность загрузки книги из буфера обмена</li>
<li>добавлен GET-параметр вида "/reader?__refresh=1&url=..." для принудительного обновления загружаемого текста</li>
<li>добавлен GET-параметр вида "/reader?__pp=50.5&url=..." для указания позиции в книге в процентах</li>
<li>исправления багов и недочетов</li>
</ul>
`
},
{
version: '0.6.6',
releaseDate: '2019-03-29',
showUntil: '2019-03-29',
content:
`
<ul>
<li>в справку добавлено описание настройки браузеров для автономной работы читалки (без доступа к интернету)</li>
<li>оптимизации процесса синхронизации, внутренние переделки</li>
</ul>
`
},
{
version: '0.6.4',
releaseDate: '2019-03-24',
showUntil: '2019-03-24',
content:
`
<ul>
<li>исправления багов, оптимизации</li>
<li>добавлена возможность синхронизации данных между устройствами</li>
</ul>
`
},
{
version: '0.5.4',
releaseDate: '2019-03-04',
showUntil: '2019-03-04',
content:
`
<ul>
<li>добавлена поддержка форматов pdf, epub, mobi</li>
<li>(0.5.2) добавлена поддержка форматов rtf, doc, docx</li>
<li>(0.4.2) фильтр для СИ больше не вырезает изображения</li>
<li>(0.4.0) добавлено отображение картинок в fb2</li>
</ul>
`
},
{
version: '0.3.0',
releaseDate: '2019-02-17',
showUntil: '2019-02-17',
content:
`
<ul>
<li>поправки багов</li>
<li>улучшено распознавание текста</li>
<li>изменена верстка страницы - убрано позиционирование каждого слова</li>
</ul>
`
},
{
version: '0.1.7',
releaseDate: '2019-02-14',
showUntil: '2019-02-14',
content:
`
<ul>
<li>увеличены верхние границы отступов и др.размеров</li>
<li>добавлена настройка для удаления/вставки пустых параграфов</li>
<li>добавлена настройка включения/отключения управления кликом</li>
<li>добавлена возможность сброса настроек</li>
<li>убран автоматический редирект на последнюю загруженную книгу, если не задан url в маршруте</li>
</ul>
`
},
{
version: '0.1.0',
releaseDate: '2019-02-12',
showUntil: '2019-02-12',
content:
`
<ul>
<li>первый деплой проекта, длительность разработки - 2 месяца</li>
</ul>
`
},
]

View File

@@ -1,20 +1,19 @@
<template>
<el-container>
<div>
Раздел Settings в разработке
</el-container>
</div>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import vueComponent from '../vueComponent.js';
export default @Component({
})
class Settings extends Vue {
class Settings {
created() {
}
}
export default vueComponent(Settings);
//-----------------------------------------------------------------------------
</script>

View File

@@ -1,20 +1,19 @@
<template>
<el-container>
<div>
Раздел Sources в разработке
</el-container>
</div>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import vueComponent from '../vueComponent.js';
export default @Component({
})
class Sources extends Vue {
class Sources {
created() {
}
}
export default vueComponent(Sources);
//-----------------------------------------------------------------------------
</script>

View File

@@ -0,0 +1,80 @@
<template>
<q-dialog v-model="active" no-route-dismiss @show="onShow" @hide="onHide">
<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 v-close-popup flat round dense>
<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 vueComponent from '../vueComponent.js';
import * as utils from '../../share/utils';
class Dialog {
_props = {
modelValue: Boolean,
};
shown = false;
get active() {
return this.modelValue;
}
set active(value) {
this.$emit('update:modelValue', value);
}
onShow() {
this.shown = true;
}
onHide() {
this.shown = false;
}
async waitShown() {
let i = 100;
while (!this.shown && i > 0) {
await utils.sleep(10);
i--;
}
}
}
export default vueComponent(Dialog);
//-----------------------------------------------------------------------------
</script>
<style scoped>
.header {
height: 50px;
}
.caption {
font-size: 110%;
overflow: hidden;
}
.close-icon {
width: 50px;
}
</style>

View File

@@ -0,0 +1,58 @@
<template>
<div class="hidden"></div>
</template>
<script>
//-----------------------------------------------------------------------------
import vueComponent from '../vueComponent.js';
class Notify {
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));
}
}
export default vueComponent(Notify);
//-----------------------------------------------------------------------------
</script>

View File

@@ -0,0 +1,188 @@
<template>
<q-input
v-model="filteredValue"
outlined dense
input-style="text-align: center"
class="no-mp"
:class="(error ? 'error' : '')"
:disable="disable"
>
<slot></slot>
<template #prepend>
<q-icon
v-ripple="validate(modelValue - step)"
:class="(validate(modelValue - step) ? '' : 'disable')"
name="la la-minus-circle"
class="button"
@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 #append>
<q-icon
v-ripple="validate(modelValue + step)"
:class="(validate(modelValue + step) ? '' : 'disable')"
name="la la-plus-circle"
class="button"
@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 vueComponent from '../vueComponent.js';
import * as utils from '../../share/utils';
const componentOptions = {
watch: {
filteredValue: function(newValue) {
if (this.validate(newValue)) {
this.error = false;
this.$emit('update:modelValue', this.string2number(newValue));
} else {
this.error = true;
}
},
modelValue: function(newValue) {
this.filteredValue = newValue;
},
}
};
class NumInput {
_options = componentOptions;
_props = {
modelValue: 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
};
filteredValue = 0;
error = false;
created() {
this.filteredValue = this.modelValue;
}
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.modelValue + this.step;
if (this.validate(newValue))
this.filteredValue = newValue;
}
minus() {
const newValue = this.modelValue - 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.$root.isMobileDevice)
return;
if (event.touches.length == 1) {
this.inTouch = true;
this.onMouseDown({button: 0}, way);
}
}
onTouchEnd() {
if (!this.$root.isMobileDevice)
return;
this.inTouch = false;
this.onMouseUp();
}
}
export default vueComponent(NumInput);
//-----------------------------------------------------------------------------
</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,361 @@
<template>
<q-dialog ref="dialog" v-model="active" no-route-dismiss @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="iconName" size="28px"></q-icon>
<div v-html="caption"></div>
</div>
<div class="close-icon column justify-center items-center">
<q-btn v-close-popup flat round dense>
<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="iconName" size="28px"></q-icon>
<div v-html="caption"></div>
</div>
<div class="close-icon column justify-center items-center">
<q-btn v-close-popup flat round dense>
<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 v-close-popup class="q-px-md q-ml-sm" dense no-caps>
Отмена
</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="iconName" size="28px"></q-icon>
<div v-html="caption"></div>
</div>
<div class="close-icon column justify-center items-center">
<q-btn v-close-popup flat round dense>
<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" v-model="inputValue" class="q-mt-xs" outlined dense />
<div class="error">
<span v-show="error != ''">{{ error }}</span>
</div>
</div>
<div class="buttons row justify-end q-pa-md">
<q-btn v-close-popup class="q-px-md q-ml-sm" dense no-caps>
Отмена
</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="iconName" size="28px"></q-icon>
<div v-html="caption"></div>
</div>
<div class="close-icon column justify-center items-center">
<q-btn v-close-popup flat round dense>
<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 v-close-popup class="q-px-md q-ml-sm" dense no-caps>
Отмена
</q-btn>
<q-btn class="q-px-md q-ml-sm" color="primary" dense no-caps :disabled="hotKeyCode == ''" @click="okClick">
OK
</q-btn>
</div>
</div>
</q-dialog>
</template>
<script>
//-----------------------------------------------------------------------------
import vueComponent from '../vueComponent.js';
import * as utils from '../../share/utils';
const componentOptions = {
watch: {
inputValue: function(newValue) {
this.validate(newValue);
},
}
};
class StdDialog {
_options = componentOptions;
caption = '';
message = '';
active = false;
type = '';
inputValue = '';
error = '';
iconColor = '';
iconName = '';
hotKeyCode = '';
created() {
if (this.$root.addEventHook) {
this.$root.addEventHook('key', 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.showed = false;
this.iconColor = 'text-warning';
if (opts && opts.color) {
this.iconColor = `text-${opts.color}`;
}
this.iconName = 'las la-exclamation-circle';
if (opts && opts.iconName) {
this.iconName = opts.iconName;
}
this.hotKeyCode = '';
if (opts && opts.hotKeyCode) {
this.hotKeyCode = opts.hotKeyCode;
}
}
onHide() {
if (this.hideTrigger) {
this.hideTrigger();
this.hideTrigger = null;
}
this.showed = false;
}
onShow() {
if (this.type == 'prompt') {
this.enableValidator = true;
if (this.inputValue)
this.validate(this.inputValue);
this.$refs.input.focus();
}
this.showed = true;
}
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 && this.showed) {
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();
}
}
}
}
export default vueComponent(StdDialog);
//-----------------------------------------------------------------------------
</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,34 +1,150 @@
<template>
<div class="window">
<div class="header">
<span class="header-text"><slot name="header"></slot></span>
<span class="close-button" @click="close"><i class="el-icon-close"></i></span>
<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 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>
<slot></slot>
</div>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import vueComponent from '../vueComponent.js';
export default @Component({
})
class Window extends Vue {
close() {
this.$emit('close');
class Window {
_props = {
height: { type: String, default: '100%' },
width: { type: String, default: '100%' },
maxWidth: { type: String, default: '' },
topShift: { type: Number, default: 0 },
margin: '',
};
init() {
this.$nextTick(() => {
this.$refs.main.style.top = 0;
this.$refs.main.style.left = 0;
this.$refs.windowBox.style.height = this.height;
this.$refs.windowBox.style.width = this.width;
if (this.maxWidth)
this.$refs.windowBox.style.maxWidth = this.maxWidth;
const left = (this.$refs.main.offsetWidth - this.$refs.windowBox.offsetWidth)/2;
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;
});
}
onMouseDown(event) {
if (this.$root.isMobileDevice)
return;
if (event.button == 0) {
this.$refs.header.style.cursor = 'move';
this.startX = event.screenX;
this.startY = event.screenY;
this.moving = true;
}
}
onMouseUp(event) {
if (event.button == 0) {
this.$refs.header.style.cursor = 'default';
this.moving = false;
}
}
onMouseMove(event) {
if (this.moving) {
const deltaX = event.screenX - this.startX;
const deltaY = event.screenY - this.startY;
this.startX = event.screenX;
this.startY = event.screenY;
this.$refs.windowBox.style.left = (this.$refs.windowBox.offsetLeft + deltaX) + 'px';
this.$refs.windowBox.style.top = (this.$refs.windowBox.offsetTop + deltaY) + 'px';
}
}
onTouchStart(event) {
if (!this.$root.isMobileDevice)
return;
if (event.touches.length == 1) {
const touch = event.touches[0];
this.$refs.header.style.cursor = 'move';
this.startX = touch.screenX;
this.startY = touch.screenY;
this.moving = true;
}
}
onTouchMove(event) {
if (!this.$root.isMobileDevice)
return;
if (event.touches.length == 1 && this.moving) {
const touch = event.touches[0];
const deltaX = touch.screenX - this.startX;
const deltaY = touch.screenY - this.startY;
this.startX = touch.screenX;
this.startY = touch.screenY;
this.$refs.windowBox.style.left = (this.$refs.windowBox.offsetLeft + deltaX) + 'px';
this.$refs.windowBox.style.top = (this.$refs.windowBox.offsetTop + deltaY) + 'px';
}
}
onTouchEnd() {
if (!this.$root.isMobileDevice)
return;
this.$refs.header.style.cursor = 'default';
this.moving = false;
}
close() {
if (!this.moving)
this.$emit('close');
}
}
export default vueComponent(Window);
//-----------------------------------------------------------------------------
</script>
<style scoped>
.window {
.main {
background-color: transparent !important;
z-index: 50;
}
.xyfit {
height: 100%;
width: 100%;
}
.flexfit {
flex: 1;
display: flex;
flex-direction: column;
}
.window {
margin: 10px;
background-color: #ffffff;
border: 3px double black;
@@ -37,25 +153,28 @@ class Window extends Vue {
}
.header {
display: flex;
justify-content: flex-end;
background-color: #e5e7ea;
background: linear-gradient(to bottom right, green, #59B04F);
align-items: center;
height: 40px;
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: 40px;
height: 40px;
width: 30px;
height: 30px;
cursor: pointer;
}
.close-button:hover {
background-color: #69C05F;
}
</style>

View File

@@ -0,0 +1,52 @@
import { defineComponent } from 'vue';
import _ from 'lodash';
export default function(componentClass) {
const comp = {};
const obj = new componentClass();
//data, options, props
const data = {};
for (const prop of Object.getOwnPropertyNames(obj)) {
if (['_options', '_props'].includes(prop)) {//meta props
if (prop === '_options') {
const options = obj[prop];
for (const optName of ['components', 'watch', 'emits']) {
if (options[optName]) {
comp[optName] = options[optName];
}
}
} else if (prop === '_props') {
comp['props'] = obj[prop];
}
} else {//usual prop
data[prop] = obj[prop];
}
}
comp.data = () => _.cloneDeep(data);
//methods
const classProto = Object.getPrototypeOf(obj);
const classMethods = Object.getOwnPropertyNames(classProto);
const methods = {};
const computed = {};
for (const method of classMethods) {
const desc = Object.getOwnPropertyDescriptor(classProto, method);
if (desc.get) {//has getter, computed
computed[method] = {get: desc.get};
if (desc.set)
computed[method].set = desc.set;
} else if ( ['beforeCreate', 'created', 'beforeMount', 'mounted', 'beforeUpdate', 'updated', 'activated',//life cycle hooks
'deactivated', 'beforeUnmount', 'unmounted', 'errorCaptured', 'renderTracked', 'renderTriggered',//life cycle hooks
'setup'].includes(method) ) {
comp[method] = obj[method];
} else if (method !== 'constructor') {//usual
methods[method] = obj[method];
}
}
comp.methods = methods;
comp.computed = computed;
//console.log(comp);
return defineComponent(comp);
}

View File

@@ -1,122 +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 './theme/icon.css';
import './theme/tooltip.css';
import ElMenu from 'element-ui/lib/menu';
import './theme/menu.css';
import ElMenuItem from 'element-ui/lib/menu-item';
import './theme/menu-item.css';
import ElButton from 'element-ui/lib/button';
import './theme/button.css';
import ElButtonGroup from 'element-ui/lib/button-group';
import './theme/button-group.css';
import ElCheckbox from 'element-ui/lib/checkbox';
import './theme/checkbox.css';
import ElTabs from 'element-ui/lib/tabs';
import './theme/tabs.css';
import ElTabPane from 'element-ui/lib/tab-pane';
import './theme/tab-pane.css';
import ElTooltip from 'element-ui/lib/tooltip';
import './theme/tooltip.css';
import ElCol from 'element-ui/lib/col';
import './theme/col.css';
import ElContainer from 'element-ui/lib/container';
import './theme/container.css';
import ElAside from 'element-ui/lib/aside';
import './theme/aside.css';
import ElHeader from 'element-ui/lib/header';
import './theme/header.css';
import ElMain from 'element-ui/lib/main';
import './theme/main.css';
import ElInput from 'element-ui/lib/input';
import './theme/input.css';
import ElInputNumber from 'element-ui/lib/input-number';
import './theme/input-number.css';
import ElSelect from 'element-ui/lib/select';
import './theme/select.css';
import ElOption from 'element-ui/lib/option';
import './theme/option.css';
import ElTable from 'element-ui/lib/table';
import './theme/table.css';
import ElTableColumn from 'element-ui/lib/table-column';
import './theme/table-column.css';
import ElProgress from 'element-ui/lib/progress';
import './theme/progress.css';
import ElSlider from 'element-ui/lib/slider';
import './theme/slider.css';
import ElForm from 'element-ui/lib/form';
import './theme/form.css';
import ElFormItem from 'element-ui/lib/form-item';
import './theme/form-item.css';
import ElColorPicker from 'element-ui/lib/color-picker';
import './theme/color-picker.css';
import Notification from 'element-ui/lib/notification';
import './theme/notification.css';
import Loading from 'element-ui/lib/loading';
import './theme/loading.css';
import MessageBox from 'element-ui/lib/message-box';
import './theme/message-box.css';
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,
};
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,11 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="description" content="браузерная онлайн-читалка книг из интернета и библиотека">
<meta name="keywords" content="библиотека,онлайн,читалка,книги,читать,браузер,интернет">
<title></title>
<meta name="description" content="Браузерная онлайн-читалка книг. Поддерживаются форматы: fb2, html, txt, rtf, doc, docx, pdf, epub, mobi.">
<meta name="keywords" content="онлайн,читалка,fb2,книги,читать,браузер,интернет">
<script src="/sw-register.js"></script>
</head>
<body>
<div id="app"></div>

View File

@@ -1,14 +1,16 @@
import Vue from 'vue';
import App from './components/App.vue';
import { createApp } from 'vue';
import router from './router';
import store from './store';
import './element';
import q from './quasar';
//Vue.config.productionTip = false;
import App from './components/App.vue';
new Vue({
router,
store,
render: h => h(App),
}).$mount('#app');
const app = createApp(App);
app.use(router);
app.use(store);
app.use(q.quasar, q.options);
q.init();
app.mount('#app');

95
client/quasar.js Normal file
View File

@@ -0,0 +1,95 @@
import 'quasar/dist/quasar.css';
//import Quasar from 'quasar/dist/quasar.umd.prod.js';
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} from 'quasar/src/components/item';
import {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';
import {QTree} from 'quasar/src/components/tree';
//import {QExpansionItem} from 'quasar/src/components/expansion-item';
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,
QTree,
//QExpansionItem,
};
//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,
};
//icons
//import '@quasar/extras/fontawesome-v5/fontawesome-v5.css';
//import fontawesomeV5 from 'quasar/icon-set/fontawesome-v5.js'
import '@quasar/extras/line-awesome/line-awesome.css';
import lineAwesome from 'quasar/icon-set/line-awesome.js'
export default {
quasar: Quasar,
options: { config, components, directives, plugins },
init: () => {
Quasar.iconSet.set(lineAwesome);
}
};

View File

@@ -1,17 +1,17 @@
import Vue from 'vue';
import VueRouter from 'vue-router';
import { createRouter, createWebHashHistory } from 'vue-router';
import _ from 'lodash';
import App from './components/App.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');
//немедленная загрузка
//import Reader from './components/Reader/Reader.vue';
const Reader = () => import('./components/Reader/Reader.vue');
//const Forum = () => import('./components/Forum/Forum.vue');
const ExternalLibs = () => import('./components/ExternalLibs/ExternalLibs.vue');
const Income = () => import('./components/Income/Income.vue');
const Sources = () => import('./components/Sources/Sources.vue');
const Settings = () => import('./components/Settings/Settings.vue');
@@ -20,20 +20,22 @@ 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],
['/:pathMatch(.*)*', null, null, '/cardindex'],
];
let routes = {};
@@ -60,8 +62,7 @@ for (let route of myRoutes) {
}
routes = routes.children;
Vue.use(VueRouter);
export default new VueRouter({
export default createRouter({
history: createWebHashHistory(),
routes
});

View File

@@ -0,0 +1,26 @@
//WebCrypto API (crypto.subtle) не работает без https, поэтому приходится извращаться через sjcl
import sjclWrapper from './sjclWrapper';
//не менять
const iv = 'B6E2XejNh2dS';
const salt = 'Liberama project is awesome';
export function aesEncrypt(data, password) {
return sjclWrapper.codec.bytes.fromBits(
sjclWrapper.encryptArray(
password, sjclWrapper.codec.bytes.toBits(data), {iv, salt}
).ct
);
}
export function aesDecrypt(data, password) {
return sjclWrapper.codec.bytes.fromBits(
sjclWrapper.decryptArray(
password, {ct: sjclWrapper.codec.bytes.toBits(data)}, {iv, salt}
)
);
}
export function sha256(str) {
return sjclWrapper.codec.bytes.fromBits(sjclWrapper.hash.sha256.hash(str));
}

View File

@@ -0,0 +1,22 @@
class DynamicCss {
constructor() {
this.cssNodes = {};
}
replace(name, cssText) {
const style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = cssText;
const parent = document.getElementsByTagName('head')[0];
if (this.cssNodes[name]) {
parent.removeChild(this.cssNodes[name]);
delete this.cssNodes[name];
}
this.cssNodes[name] = parent.appendChild(style);
}
}
export default new DynamicCss();

60
client/share/sjcl.js Normal file
View File

@@ -0,0 +1,60 @@
"use strict";var sjcl={cipher:{},hash:{},keyexchange:{},mode:{},misc:{},codec:{},exception:{corrupt:function(a){this.toString=function(){return"CORRUPT: "+this.message};this.message=a},invalid:function(a){this.toString=function(){return"INVALID: "+this.message};this.message=a},bug:function(a){this.toString=function(){return"BUG: "+this.message};this.message=a},notReady:function(a){this.toString=function(){return"NOT READY: "+this.message};this.message=a}}};
sjcl.cipher.aes=function(a){this.s[0][0][0]||this.O();var b,c,d,e,f=this.s[0][4],g=this.s[1];b=a.length;var h=1;if(4!==b&&6!==b&&8!==b)throw new sjcl.exception.invalid("invalid aes key size");this.b=[d=a.slice(0),e=[]];for(a=b;a<4*b+28;a++){c=d[a-1];if(0===a%b||8===b&&4===a%b)c=f[c>>>24]<<24^f[c>>16&255]<<16^f[c>>8&255]<<8^f[c&255],0===a%b&&(c=c<<8^c>>>24^h<<24,h=h<<1^283*(h>>7));d[a]=d[a-b]^c}for(b=0;a;b++,a--)c=d[b&3?a:a-4],e[b]=4>=a||4>b?c:g[0][f[c>>>24]]^g[1][f[c>>16&255]]^g[2][f[c>>8&255]]^g[3][f[c&
255]]};
sjcl.cipher.aes.prototype={encrypt:function(a){return t(this,a,0)},decrypt:function(a){return t(this,a,1)},s:[[[],[],[],[],[]],[[],[],[],[],[]]],O:function(){var a=this.s[0],b=this.s[1],c=a[4],d=b[4],e,f,g,h=[],k=[],l,n,m,p;for(e=0;0x100>e;e++)k[(h[e]=e<<1^283*(e>>7))^e]=e;for(f=g=0;!c[f];f^=l||1,g=k[g]||1)for(m=g^g<<1^g<<2^g<<3^g<<4,m=m>>8^m&255^99,c[f]=m,d[m]=f,n=h[e=h[l=h[f]]],p=0x1010101*n^0x10001*e^0x101*l^0x1010100*f,n=0x101*h[m]^0x1010100*m,e=0;4>e;e++)a[e][f]=n=n<<24^n>>>8,b[e][m]=p=p<<24^p>>>8;for(e=
0;5>e;e++)a[e]=a[e].slice(0),b[e]=b[e].slice(0)}};
function t(a,b,c){if(4!==b.length)throw new sjcl.exception.invalid("invalid aes block size");var d=a.b[c],e=b[0]^d[0],f=b[c?3:1]^d[1],g=b[2]^d[2];b=b[c?1:3]^d[3];var h,k,l,n=d.length/4-2,m,p=4,r=[0,0,0,0];h=a.s[c];a=h[0];var q=h[1],v=h[2],w=h[3],x=h[4];for(m=0;m<n;m++)h=a[e>>>24]^q[f>>16&255]^v[g>>8&255]^w[b&255]^d[p],k=a[f>>>24]^q[g>>16&255]^v[b>>8&255]^w[e&255]^d[p+1],l=a[g>>>24]^q[b>>16&255]^v[e>>8&255]^w[f&255]^d[p+2],b=a[b>>>24]^q[e>>16&255]^v[f>>8&255]^w[g&255]^d[p+3],p+=4,e=h,f=k,g=l;for(m=
0;4>m;m++)r[c?3&-m:m]=x[e>>>24]<<24^x[f>>16&255]<<16^x[g>>8&255]<<8^x[b&255]^d[p++],h=e,e=f,f=g,g=b,b=h;return r}
sjcl.bitArray={bitSlice:function(a,b,c){a=sjcl.bitArray.$(a.slice(b/32),32-(b&31)).slice(1);return void 0===c?a:sjcl.bitArray.clamp(a,c-b)},extract:function(a,b,c){var d=Math.floor(-b-c&31);return((b+c-1^b)&-32?a[b/32|0]<<32-d^a[b/32+1|0]>>>d:a[b/32|0]>>>d)&(1<<c)-1},concat:function(a,b){if(0===a.length||0===b.length)return a.concat(b);var c=a[a.length-1],d=sjcl.bitArray.getPartial(c);return 32===d?a.concat(b):sjcl.bitArray.$(b,d,c|0,a.slice(0,a.length-1))},bitLength:function(a){var b=a.length;return 0===
b?0:32*(b-1)+sjcl.bitArray.getPartial(a[b-1])},clamp:function(a,b){if(32*a.length<b)return a;a=a.slice(0,Math.ceil(b/32));var c=a.length;b=b&31;0<c&&b&&(a[c-1]=sjcl.bitArray.partial(b,a[c-1]&2147483648>>b-1,1));return a},partial:function(a,b,c){return 32===a?b:(c?b|0:b<<32-a)+0x10000000000*a},getPartial:function(a){return Math.round(a/0x10000000000)||32},equal:function(a,b){if(sjcl.bitArray.bitLength(a)!==sjcl.bitArray.bitLength(b))return!1;var c=0,d;for(d=0;d<a.length;d++)c|=a[d]^b[d];return 0===
c},$:function(a,b,c,d){var e;e=0;for(void 0===d&&(d=[]);32<=b;b-=32)d.push(c),c=0;if(0===b)return d.concat(a);for(e=0;e<a.length;e++)d.push(c|a[e]>>>b),c=a[e]<<32-b;e=a.length?a[a.length-1]:0;a=sjcl.bitArray.getPartial(e);d.push(sjcl.bitArray.partial(b+a&31,32<b+a?c:d.pop(),1));return d},i:function(a,b){return[a[0]^b[0],a[1]^b[1],a[2]^b[2],a[3]^b[3]]},byteswapM:function(a){var b,c;for(b=0;b<a.length;++b)c=a[b],a[b]=c>>>24|c>>>8&0xff00|(c&0xff00)<<8|c<<24;return a}};
sjcl.codec.utf8String={fromBits:function(a){var b="",c=sjcl.bitArray.bitLength(a),d,e;for(d=0;d<c/8;d++)0===(d&3)&&(e=a[d/4]),b+=String.fromCharCode(e>>>8>>>8>>>8),e<<=8;return decodeURIComponent(escape(b))},toBits:function(a){a=unescape(encodeURIComponent(a));var b=[],c,d=0;for(c=0;c<a.length;c++)d=d<<8|a.charCodeAt(c),3===(c&3)&&(b.push(d),d=0);c&3&&b.push(sjcl.bitArray.partial(8*(c&3),d));return b}};
sjcl.codec.hex={fromBits:function(a){var b="",c;for(c=0;c<a.length;c++)b+=((a[c]|0)+0xf00000000000).toString(16).substr(4);return b.substr(0,sjcl.bitArray.bitLength(a)/4)},toBits:function(a){var b,c=[],d;a=a.replace(/\s|0x/g,"");d=a.length;a=a+"00000000";for(b=0;b<a.length;b+=8)c.push(parseInt(a.substr(b,8),16)^0);return sjcl.bitArray.clamp(c,4*d)}};
sjcl.codec.base32={B:"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567",X:"0123456789ABCDEFGHIJKLMNOPQRSTUV",BITS:32,BASE:5,REMAINING:27,fromBits:function(a,b,c){var d=sjcl.codec.base32.BASE,e=sjcl.codec.base32.REMAINING,f="",g=0,h=sjcl.codec.base32.B,k=0,l=sjcl.bitArray.bitLength(a);c&&(h=sjcl.codec.base32.X);for(c=0;f.length*d<l;)f+=h.charAt((k^a[c]>>>g)>>>e),g<d?(k=a[c]<<d-g,g+=e,c++):(k<<=d,g-=d);for(;f.length&7&&!b;)f+="=";return f},toBits:function(a,b){a=a.replace(/\s|=/g,"").toUpperCase();var c=sjcl.codec.base32.BITS,
d=sjcl.codec.base32.BASE,e=sjcl.codec.base32.REMAINING,f=[],g,h=0,k=sjcl.codec.base32.B,l=0,n,m="base32";b&&(k=sjcl.codec.base32.X,m="base32hex");for(g=0;g<a.length;g++){n=k.indexOf(a.charAt(g));if(0>n){if(!b)try{return sjcl.codec.base32hex.toBits(a)}catch(p){}throw new sjcl.exception.invalid("this isn't "+m+"!");}h>e?(h-=e,f.push(l^n>>>h),l=n<<c-h):(h+=d,l^=n<<c-h)}h&56&&f.push(sjcl.bitArray.partial(h&56,l,1));return f}};
sjcl.codec.base32hex={fromBits:function(a,b){return sjcl.codec.base32.fromBits(a,b,1)},toBits:function(a){return sjcl.codec.base32.toBits(a,1)}};
sjcl.codec.base64={B:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",fromBits:function(a,b,c){var d="",e=0,f=sjcl.codec.base64.B,g=0,h=sjcl.bitArray.bitLength(a);c&&(f=f.substr(0,62)+"-_");for(c=0;6*d.length<h;)d+=f.charAt((g^a[c]>>>e)>>>26),6>e?(g=a[c]<<6-e,e+=26,c++):(g<<=6,e-=6);for(;d.length&3&&!b;)d+="=";return d},toBits:function(a,b){a=a.replace(/\s|=/g,"");var c=[],d,e=0,f=sjcl.codec.base64.B,g=0,h;b&&(f=f.substr(0,62)+"-_");for(d=0;d<a.length;d++){h=f.indexOf(a.charAt(d));
if(0>h)throw new sjcl.exception.invalid("this isn't base64!");26<e?(e-=26,c.push(g^h>>>e),g=h<<32-e):(e+=6,g^=h<<32-e)}e&56&&c.push(sjcl.bitArray.partial(e&56,g,1));return c}};sjcl.codec.base64url={fromBits:function(a){return sjcl.codec.base64.fromBits(a,1,1)},toBits:function(a){return sjcl.codec.base64.toBits(a,1)}};sjcl.hash.sha256=function(a){this.b[0]||this.O();a?(this.F=a.F.slice(0),this.A=a.A.slice(0),this.l=a.l):this.reset()};sjcl.hash.sha256.hash=function(a){return(new sjcl.hash.sha256).update(a).finalize()};
sjcl.hash.sha256.prototype={blockSize:512,reset:function(){this.F=this.Y.slice(0);this.A=[];this.l=0;return this},update:function(a){"string"===typeof a&&(a=sjcl.codec.utf8String.toBits(a));var b,c=this.A=sjcl.bitArray.concat(this.A,a);b=this.l;a=this.l=b+sjcl.bitArray.bitLength(a);if(0x1fffffffffffff<a)throw new sjcl.exception.invalid("Cannot hash more than 2^53 - 1 bits");if("undefined"!==typeof Uint32Array){var d=new Uint32Array(c),e=0;for(b=512+b-(512+b&0x1ff);b<=a;b+=512)u(this,d.subarray(16*e,
16*(e+1))),e+=1;c.splice(0,16*e)}else for(b=512+b-(512+b&0x1ff);b<=a;b+=512)u(this,c.splice(0,16));return this},finalize:function(){var a,b=this.A,c=this.F,b=sjcl.bitArray.concat(b,[sjcl.bitArray.partial(1,1)]);for(a=b.length+2;a&15;a++)b.push(0);b.push(Math.floor(this.l/0x100000000));for(b.push(this.l|0);b.length;)u(this,b.splice(0,16));this.reset();return c},Y:[],b:[],O:function(){function a(a){return 0x100000000*(a-Math.floor(a))|0}for(var b=0,c=2,d,e;64>b;c++){e=!0;for(d=2;d*d<=c;d++)if(0===c%d){e=
!1;break}e&&(8>b&&(this.Y[b]=a(Math.pow(c,.5))),this.b[b]=a(Math.pow(c,1/3)),b++)}}};
function u(a,b){var c,d,e,f=a.F,g=a.b,h=f[0],k=f[1],l=f[2],n=f[3],m=f[4],p=f[5],r=f[6],q=f[7];for(c=0;64>c;c++)16>c?d=b[c]:(d=b[c+1&15],e=b[c+14&15],d=b[c&15]=(d>>>7^d>>>18^d>>>3^d<<25^d<<14)+(e>>>17^e>>>19^e>>>10^e<<15^e<<13)+b[c&15]+b[c+9&15]|0),d=d+q+(m>>>6^m>>>11^m>>>25^m<<26^m<<21^m<<7)+(r^m&(p^r))+g[c],q=r,r=p,p=m,m=n+d|0,n=l,l=k,k=h,h=d+(k&l^n&(k^l))+(k>>>2^k>>>13^k>>>22^k<<30^k<<19^k<<10)|0;f[0]=f[0]+h|0;f[1]=f[1]+k|0;f[2]=f[2]+l|0;f[3]=f[3]+n|0;f[4]=f[4]+m|0;f[5]=f[5]+p|0;f[6]=f[6]+r|0;f[7]=
f[7]+q|0}
sjcl.mode.ccm={name:"ccm",G:[],listenProgress:function(a){sjcl.mode.ccm.G.push(a)},unListenProgress:function(a){a=sjcl.mode.ccm.G.indexOf(a);-1<a&&sjcl.mode.ccm.G.splice(a,1)},fa:function(a){var b=sjcl.mode.ccm.G.slice(),c;for(c=0;c<b.length;c+=1)b[c](a)},encrypt:function(a,b,c,d,e){var f,g=b.slice(0),h=sjcl.bitArray,k=h.bitLength(c)/8,l=h.bitLength(g)/8;e=e||64;d=d||[];if(7>k)throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");for(f=2;4>f&&l>>>8*f;f++);f<15-k&&(f=15-k);c=h.clamp(c,
8*(15-f));b=sjcl.mode.ccm.V(a,b,c,d,e,f);g=sjcl.mode.ccm.C(a,g,c,b,e,f);return h.concat(g.data,g.tag)},decrypt:function(a,b,c,d,e){e=e||64;d=d||[];var f=sjcl.bitArray,g=f.bitLength(c)/8,h=f.bitLength(b),k=f.clamp(b,h-e),l=f.bitSlice(b,h-e),h=(h-e)/8;if(7>g)throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");for(b=2;4>b&&h>>>8*b;b++);b<15-g&&(b=15-g);c=f.clamp(c,8*(15-b));k=sjcl.mode.ccm.C(a,k,c,l,e,b);a=sjcl.mode.ccm.V(a,k.data,c,d,e,b);if(!f.equal(k.tag,a))throw new sjcl.exception.corrupt("ccm: tag doesn't match");
return k.data},na:function(a,b,c,d,e,f){var g=[],h=sjcl.bitArray,k=h.i;d=[h.partial(8,(b.length?64:0)|d-2<<2|f-1)];d=h.concat(d,c);d[3]|=e;d=a.encrypt(d);if(b.length)for(c=h.bitLength(b)/8,65279>=c?g=[h.partial(16,c)]:0xffffffff>=c&&(g=h.concat([h.partial(16,65534)],[c])),g=h.concat(g,b),b=0;b<g.length;b+=4)d=a.encrypt(k(d,g.slice(b,b+4).concat([0,0,0])));return d},V:function(a,b,c,d,e,f){var g=sjcl.bitArray,h=g.i;e/=8;if(e%2||4>e||16<e)throw new sjcl.exception.invalid("ccm: invalid tag length");
if(0xffffffff<d.length||0xffffffff<b.length)throw new sjcl.exception.bug("ccm: can't deal with 4GiB or more data");c=sjcl.mode.ccm.na(a,d,c,e,g.bitLength(b)/8,f);for(d=0;d<b.length;d+=4)c=a.encrypt(h(c,b.slice(d,d+4).concat([0,0,0])));return g.clamp(c,8*e)},C:function(a,b,c,d,e,f){var g,h=sjcl.bitArray;g=h.i;var k=b.length,l=h.bitLength(b),n=k/50,m=n;c=h.concat([h.partial(8,f-1)],c).concat([0,0,0]).slice(0,4);d=h.bitSlice(g(d,a.encrypt(c)),0,e);if(!k)return{tag:d,data:[]};for(g=0;g<k;g+=4)g>n&&(sjcl.mode.ccm.fa(g/
k),n+=m),c[3]++,e=a.encrypt(c),b[g]^=e[0],b[g+1]^=e[1],b[g+2]^=e[2],b[g+3]^=e[3];return{tag:d,data:h.clamp(b,l)}}};
sjcl.mode.ocb2={name:"ocb2",encrypt:function(a,b,c,d,e,f){if(128!==sjcl.bitArray.bitLength(c))throw new sjcl.exception.invalid("ocb iv must be 128 bits");var g,h=sjcl.mode.ocb2.S,k=sjcl.bitArray,l=k.i,n=[0,0,0,0];c=h(a.encrypt(c));var m,p=[];d=d||[];e=e||64;for(g=0;g+4<b.length;g+=4)m=b.slice(g,g+4),n=l(n,m),p=p.concat(l(c,a.encrypt(l(c,m)))),c=h(c);m=b.slice(g);b=k.bitLength(m);g=a.encrypt(l(c,[0,0,0,b]));m=k.clamp(l(m.concat([0,0,0]),g),b);n=l(n,l(m.concat([0,0,0]),g));n=a.encrypt(l(n,l(c,h(c))));
d.length&&(n=l(n,f?d:sjcl.mode.ocb2.pmac(a,d)));return p.concat(k.concat(m,k.clamp(n,e)))},decrypt:function(a,b,c,d,e,f){if(128!==sjcl.bitArray.bitLength(c))throw new sjcl.exception.invalid("ocb iv must be 128 bits");e=e||64;var g=sjcl.mode.ocb2.S,h=sjcl.bitArray,k=h.i,l=[0,0,0,0],n=g(a.encrypt(c)),m,p,r=sjcl.bitArray.bitLength(b)-e,q=[];d=d||[];for(c=0;c+4<r/32;c+=4)m=k(n,a.decrypt(k(n,b.slice(c,c+4)))),l=k(l,m),q=q.concat(m),n=g(n);p=r-32*c;m=a.encrypt(k(n,[0,0,0,p]));m=k(m,h.clamp(b.slice(c),p).concat([0,
0,0]));l=k(l,m);l=a.encrypt(k(l,k(n,g(n))));d.length&&(l=k(l,f?d:sjcl.mode.ocb2.pmac(a,d)));if(!h.equal(h.clamp(l,e),h.bitSlice(b,r)))throw new sjcl.exception.corrupt("ocb: tag doesn't match");return q.concat(h.clamp(m,p))},pmac:function(a,b){var c,d=sjcl.mode.ocb2.S,e=sjcl.bitArray,f=e.i,g=[0,0,0,0],h=a.encrypt([0,0,0,0]),h=f(h,d(d(h)));for(c=0;c+4<b.length;c+=4)h=d(h),g=f(g,a.encrypt(f(h,b.slice(c,c+4))));c=b.slice(c);128>e.bitLength(c)&&(h=f(h,d(h)),c=e.concat(c,[-2147483648,0,0,0]));g=f(g,c);
return a.encrypt(f(d(f(h,d(h))),g))},S:function(a){return[a[0]<<1^a[1]>>>31,a[1]<<1^a[2]>>>31,a[2]<<1^a[3]>>>31,a[3]<<1^135*(a[0]>>>31)]}};
sjcl.mode.gcm={name:"gcm",encrypt:function(a,b,c,d,e){var f=b.slice(0);b=sjcl.bitArray;d=d||[];a=sjcl.mode.gcm.C(!0,a,f,d,c,e||128);return b.concat(a.data,a.tag)},decrypt:function(a,b,c,d,e){var f=b.slice(0),g=sjcl.bitArray,h=g.bitLength(f);e=e||128;d=d||[];e<=h?(b=g.bitSlice(f,h-e),f=g.bitSlice(f,0,h-e)):(b=f,f=[]);a=sjcl.mode.gcm.C(!1,a,f,d,c,e);if(!g.equal(a.tag,b))throw new sjcl.exception.corrupt("gcm: tag doesn't match");return a.data},ka:function(a,b){var c,d,e,f,g,h=sjcl.bitArray.i;e=[0,0,
0,0];f=b.slice(0);for(c=0;128>c;c++){(d=0!==(a[Math.floor(c/32)]&1<<31-c%32))&&(e=h(e,f));g=0!==(f[3]&1);for(d=3;0<d;d--)f[d]=f[d]>>>1|(f[d-1]&1)<<31;f[0]>>>=1;g&&(f[0]^=-0x1f000000)}return e},j:function(a,b,c){var d,e=c.length;b=b.slice(0);for(d=0;d<e;d+=4)b[0]^=0xffffffff&c[d],b[1]^=0xffffffff&c[d+1],b[2]^=0xffffffff&c[d+2],b[3]^=0xffffffff&c[d+3],b=sjcl.mode.gcm.ka(b,a);return b},C:function(a,b,c,d,e,f){var g,h,k,l,n,m,p,r,q=sjcl.bitArray;m=c.length;p=q.bitLength(c);r=q.bitLength(d);h=q.bitLength(e);
g=b.encrypt([0,0,0,0]);96===h?(e=e.slice(0),e=q.concat(e,[1])):(e=sjcl.mode.gcm.j(g,[0,0,0,0],e),e=sjcl.mode.gcm.j(g,e,[0,0,Math.floor(h/0x100000000),h&0xffffffff]));h=sjcl.mode.gcm.j(g,[0,0,0,0],d);n=e.slice(0);d=h.slice(0);a||(d=sjcl.mode.gcm.j(g,h,c));for(l=0;l<m;l+=4)n[3]++,k=b.encrypt(n),c[l]^=k[0],c[l+1]^=k[1],c[l+2]^=k[2],c[l+3]^=k[3];c=q.clamp(c,p);a&&(d=sjcl.mode.gcm.j(g,h,c));a=[Math.floor(r/0x100000000),r&0xffffffff,Math.floor(p/0x100000000),p&0xffffffff];d=sjcl.mode.gcm.j(g,d,a);k=b.encrypt(e);
d[0]^=k[0];d[1]^=k[1];d[2]^=k[2];d[3]^=k[3];return{tag:q.bitSlice(d,0,f),data:c}}};sjcl.misc.hmac=function(a,b){this.W=b=b||sjcl.hash.sha256;var c=[[],[]],d,e=b.prototype.blockSize/32;this.w=[new b,new b];a.length>e&&(a=b.hash(a));for(d=0;d<e;d++)c[0][d]=a[d]^909522486,c[1][d]=a[d]^1549556828;this.w[0].update(c[0]);this.w[1].update(c[1]);this.R=new b(this.w[0])};
sjcl.misc.hmac.prototype.encrypt=sjcl.misc.hmac.prototype.mac=function(a){if(this.aa)throw new sjcl.exception.invalid("encrypt on already updated hmac called!");this.update(a);return this.digest(a)};sjcl.misc.hmac.prototype.reset=function(){this.R=new this.W(this.w[0]);this.aa=!1};sjcl.misc.hmac.prototype.update=function(a){this.aa=!0;this.R.update(a)};sjcl.misc.hmac.prototype.digest=function(){var a=this.R.finalize(),a=(new this.W(this.w[1])).update(a).finalize();this.reset();return a};
sjcl.misc.pbkdf2=function(a,b,c,d,e){c=c||1E4;if(0>d||0>c)throw new sjcl.exception.invalid("invalid params to pbkdf2");"string"===typeof a&&(a=sjcl.codec.utf8String.toBits(a));"string"===typeof b&&(b=sjcl.codec.utf8String.toBits(b));e=e||sjcl.misc.hmac;a=new e(a);var f,g,h,k,l=[],n=sjcl.bitArray;for(k=1;32*l.length<(d||1);k++){e=f=a.encrypt(n.concat(b,[k]));for(g=1;g<c;g++)for(f=a.encrypt(f),h=0;h<f.length;h++)e[h]^=f[h];l=l.concat(e)}d&&(l=n.clamp(l,d));return l};
sjcl.prng=function(a){this.c=[new sjcl.hash.sha256];this.m=[0];this.P=0;this.H={};this.N=0;this.U={};this.Z=this.f=this.o=this.ha=0;this.b=[0,0,0,0,0,0,0,0];this.h=[0,0,0,0];this.L=void 0;this.M=a;this.D=!1;this.K={progress:{},seeded:{}};this.u=this.ga=0;this.I=1;this.J=2;this.ca=0x10000;this.T=[0,48,64,96,128,192,0x100,384,512,768,1024];this.da=3E4;this.ba=80};
sjcl.prng.prototype={randomWords:function(a,b){var c=[],d;d=this.isReady(b);var e;if(d===this.u)throw new sjcl.exception.notReady("generator isn't seeded");if(d&this.J){d=!(d&this.I);e=[];var f=0,g;this.Z=e[0]=(new Date).valueOf()+this.da;for(g=0;16>g;g++)e.push(0x100000000*Math.random()|0);for(g=0;g<this.c.length&&(e=e.concat(this.c[g].finalize()),f+=this.m[g],this.m[g]=0,d||!(this.P&1<<g));g++);this.P>=1<<this.c.length&&(this.c.push(new sjcl.hash.sha256),this.m.push(0));this.f-=f;f>this.o&&(this.o=
f);this.P++;this.b=sjcl.hash.sha256.hash(this.b.concat(e));this.L=new sjcl.cipher.aes(this.b);for(d=0;4>d&&(this.h[d]=this.h[d]+1|0,!this.h[d]);d++);}for(d=0;d<a;d+=4)0===(d+1)%this.ca&&y(this),e=z(this),c.push(e[0],e[1],e[2],e[3]);y(this);return c.slice(0,a)},setDefaultParanoia:function(a,b){if(0===a&&"Setting paranoia=0 will ruin your security; use it only for testing"!==b)throw new sjcl.exception.invalid("Setting paranoia=0 will ruin your security; use it only for testing");this.M=a},addEntropy:function(a,
b,c){c=c||"user";var d,e,f=(new Date).valueOf(),g=this.H[c],h=this.isReady(),k=0;d=this.U[c];void 0===d&&(d=this.U[c]=this.ha++);void 0===g&&(g=this.H[c]=0);this.H[c]=(this.H[c]+1)%this.c.length;switch(typeof a){case "number":void 0===b&&(b=1);this.c[g].update([d,this.N++,1,b,f,1,a|0]);break;case "object":c=Object.prototype.toString.call(a);if("[object Uint32Array]"===c){e=[];for(c=0;c<a.length;c++)e.push(a[c]);a=e}else for("[object Array]"!==c&&(k=1),c=0;c<a.length&&!k;c++)"number"!==typeof a[c]&&
(k=1);if(!k){if(void 0===b)for(c=b=0;c<a.length;c++)for(e=a[c];0<e;)b++,e=e>>>1;this.c[g].update([d,this.N++,2,b,f,a.length].concat(a))}break;case "string":void 0===b&&(b=a.length);this.c[g].update([d,this.N++,3,b,f,a.length]);this.c[g].update(a);break;default:k=1}if(k)throw new sjcl.exception.bug("random: addEntropy only supports number, array of numbers or string");this.m[g]+=b;this.f+=b;h===this.u&&(this.isReady()!==this.u&&A("seeded",Math.max(this.o,this.f)),A("progress",this.getProgress()))},
isReady:function(a){a=this.T[void 0!==a?a:this.M];return this.o&&this.o>=a?this.m[0]>this.ba&&(new Date).valueOf()>this.Z?this.J|this.I:this.I:this.f>=a?this.J|this.u:this.u},getProgress:function(a){a=this.T[a?a:this.M];return this.o>=a?1:this.f>a?1:this.f/a},startCollectors:function(){if(!this.D){this.a={loadTimeCollector:B(this,this.ma),mouseCollector:B(this,this.oa),keyboardCollector:B(this,this.la),accelerometerCollector:B(this,this.ea),touchCollector:B(this,this.qa)};if(window.addEventListener)window.addEventListener("load",
this.a.loadTimeCollector,!1),window.addEventListener("mousemove",this.a.mouseCollector,!1),window.addEventListener("keypress",this.a.keyboardCollector,!1),window.addEventListener("devicemotion",this.a.accelerometerCollector,!1),window.addEventListener("touchmove",this.a.touchCollector,!1);else if(document.attachEvent)document.attachEvent("onload",this.a.loadTimeCollector),document.attachEvent("onmousemove",this.a.mouseCollector),document.attachEvent("keypress",this.a.keyboardCollector);else throw new sjcl.exception.bug("can't attach event");
this.D=!0}},stopCollectors:function(){this.D&&(window.removeEventListener?(window.removeEventListener("load",this.a.loadTimeCollector,!1),window.removeEventListener("mousemove",this.a.mouseCollector,!1),window.removeEventListener("keypress",this.a.keyboardCollector,!1),window.removeEventListener("devicemotion",this.a.accelerometerCollector,!1),window.removeEventListener("touchmove",this.a.touchCollector,!1)):document.detachEvent&&(document.detachEvent("onload",this.a.loadTimeCollector),document.detachEvent("onmousemove",
this.a.mouseCollector),document.detachEvent("keypress",this.a.keyboardCollector)),this.D=!1)},addEventListener:function(a,b){this.K[a][this.ga++]=b},removeEventListener:function(a,b){var c,d,e=this.K[a],f=[];for(d in e)e.hasOwnProperty(d)&&e[d]===b&&f.push(d);for(c=0;c<f.length;c++)d=f[c],delete e[d]},la:function(){C(this,1)},oa:function(a){var b,c;try{b=a.x||a.clientX||a.offsetX||0,c=a.y||a.clientY||a.offsetY||0}catch(d){c=b=0}0!=b&&0!=c&&this.addEntropy([b,c],2,"mouse");C(this,0)},qa:function(a){a=
a.touches[0]||a.changedTouches[0];this.addEntropy([a.pageX||a.clientX,a.pageY||a.clientY],1,"touch");C(this,0)},ma:function(){C(this,2)},ea:function(a){a=a.accelerationIncludingGravity.x||a.accelerationIncludingGravity.y||a.accelerationIncludingGravity.z;if(window.orientation){var b=window.orientation;"number"===typeof b&&this.addEntropy(b,1,"accelerometer")}a&&this.addEntropy(a,2,"accelerometer");C(this,0)}};
function A(a,b){var c,d=sjcl.random.K[a],e=[];for(c in d)d.hasOwnProperty(c)&&e.push(d[c]);for(c=0;c<e.length;c++)e[c](b)}function C(a,b){"undefined"!==typeof window&&window.performance&&"function"===typeof window.performance.now?a.addEntropy(window.performance.now(),b,"loadtime"):a.addEntropy((new Date).valueOf(),b,"loadtime")}function y(a){a.b=z(a).concat(z(a));a.L=new sjcl.cipher.aes(a.b)}function z(a){for(var b=0;4>b&&(a.h[b]=a.h[b]+1|0,!a.h[b]);b++);return a.L.encrypt(a.h)}
function B(a,b){return function(){b.apply(a,arguments)}}sjcl.random=new sjcl.prng(6);
a:try{var D,E,F,G;if(G="undefined"!==typeof module&&module.exports){var H;try{H=null}catch(a){H=null}G=E=H}if(G&&E.randomBytes)D=E.randomBytes(128),D=new Uint32Array((new Uint8Array(D)).buffer),sjcl.random.addEntropy(D,1024,"crypto['randomBytes']");else if("undefined"!==typeof window&&"undefined"!==typeof Uint32Array){F=new Uint32Array(32);if(window.crypto&&window.crypto.getRandomValues)window.crypto.getRandomValues(F);else if(window.msCrypto&&window.msCrypto.getRandomValues)window.msCrypto.getRandomValues(F);
else break a;sjcl.random.addEntropy(F,1024,"crypto['getRandomValues']")}}catch(a){"undefined"!==typeof window&&window.console&&(console.log("There was an error collecting entropy from the browser:"),console.log(a))}
sjcl.json={defaults:{v:1,iter:1E4,ks:128,ts:64,mode:"ccm",adata:"",cipher:"aes"},ja:function(a,b,c,d){c=c||{};d=d||{};var e=sjcl.json,f=e.g({iv:sjcl.random.randomWords(4,0)},e.defaults),g;e.g(f,c);c=f.adata;"string"===typeof f.salt&&(f.salt=sjcl.codec.base64.toBits(f.salt));"string"===typeof f.iv&&(f.iv=sjcl.codec.base64.toBits(f.iv));if(!sjcl.mode[f.mode]||!sjcl.cipher[f.cipher]||"string"===typeof a&&100>=f.iter||64!==f.ts&&96!==f.ts&&128!==f.ts||128!==f.ks&&192!==f.ks&&0x100!==f.ks||2>f.iv.length||
4<f.iv.length)throw new sjcl.exception.invalid("json encrypt: invalid parameters");"string"===typeof a?(g=sjcl.misc.cachedPbkdf2(a,f),a=g.key.slice(0,f.ks/32),f.salt=g.salt):sjcl.ecc&&a instanceof sjcl.ecc.elGamal.publicKey&&(g=a.kem(),f.kemtag=g.tag,a=g.key.slice(0,f.ks/32));"string"===typeof b&&(b=sjcl.codec.utf8String.toBits(b));"string"===typeof c&&(f.adata=c=sjcl.codec.utf8String.toBits(c));g=new sjcl.cipher[f.cipher](a);e.g(d,f);d.key=a;f.ct="ccm"===f.mode&&sjcl.arrayBuffer&&sjcl.arrayBuffer.ccm&&
b instanceof ArrayBuffer?sjcl.arrayBuffer.ccm.encrypt(g,b,f.iv,c,f.ts):sjcl.mode[f.mode].encrypt(g,b,f.iv,c,f.ts);return f},encrypt:function(a,b,c,d){var e=sjcl.json,f=e.ja.apply(e,arguments);return e.encode(f)},ia:function(a,b,c,d){c=c||{};d=d||{};var e=sjcl.json;b=e.g(e.g(e.g({},e.defaults),b),c,!0);var f,g;f=b.adata;"string"===typeof b.salt&&(b.salt=sjcl.codec.base64.toBits(b.salt));"string"===typeof b.iv&&(b.iv=sjcl.codec.base64.toBits(b.iv));if(!sjcl.mode[b.mode]||!sjcl.cipher[b.cipher]||"string"===
typeof a&&100>=b.iter||64!==b.ts&&96!==b.ts&&128!==b.ts||128!==b.ks&&192!==b.ks&&0x100!==b.ks||!b.iv||2>b.iv.length||4<b.iv.length)throw new sjcl.exception.invalid("json decrypt: invalid parameters");"string"===typeof a?(g=sjcl.misc.cachedPbkdf2(a,b),a=g.key.slice(0,b.ks/32),b.salt=g.salt):sjcl.ecc&&a instanceof sjcl.ecc.elGamal.secretKey&&(a=a.unkem(sjcl.codec.base64.toBits(b.kemtag)).slice(0,b.ks/32));"string"===typeof f&&(f=sjcl.codec.utf8String.toBits(f));g=new sjcl.cipher[b.cipher](a);f="ccm"===
b.mode&&sjcl.arrayBuffer&&sjcl.arrayBuffer.ccm&&b.ct instanceof ArrayBuffer?sjcl.arrayBuffer.ccm.decrypt(g,b.ct,b.iv,b.tag,f,b.ts):sjcl.mode[b.mode].decrypt(g,b.ct,b.iv,f,b.ts);e.g(d,b);d.key=a;return 1===c.raw?f:sjcl.codec.utf8String.fromBits(f)},decrypt:function(a,b,c,d){var e=sjcl.json;return e.ia(a,e.decode(b),c,d)},encode:function(a){var b,c="{",d="";for(b in a)if(a.hasOwnProperty(b)){if(!b.match(/^[a-z0-9]+$/i))throw new sjcl.exception.invalid("json encode: invalid property name");c+=d+'"'+
b+'":';d=",";switch(typeof a[b]){case "number":case "boolean":c+=a[b];break;case "string":c+='"'+escape(a[b])+'"';break;case "object":c+='"'+sjcl.codec.base64.fromBits(a[b],0)+'"';break;default:throw new sjcl.exception.bug("json encode: unsupported type");}}return c+"}"},decode:function(a){a=a.replace(/\s/g,"");if(!a.match(/^\{.*\}$/))throw new sjcl.exception.invalid("json decode: this isn't json!");a=a.replace(/^\{|\}$/g,"").split(/,/);var b={},c,d;for(c=0;c<a.length;c++){if(!(d=a[c].match(/^\s*(?:(["']?)([a-z][a-z0-9]*)\1)\s*:\s*(?:(-?\d+)|"([a-z0-9+\/%*_.@=\-]*)"|(true|false))$/i)))throw new sjcl.exception.invalid("json decode: this isn't json!");
null!=d[3]?b[d[2]]=parseInt(d[3],10):null!=d[4]?b[d[2]]=d[2].match(/^(ct|adata|salt|iv)$/)?sjcl.codec.base64.toBits(d[4]):unescape(d[4]):null!=d[5]&&(b[d[2]]="true"===d[5])}return b},g:function(a,b,c){void 0===a&&(a={});if(void 0===b)return a;for(var d in b)if(b.hasOwnProperty(d)){if(c&&void 0!==a[d]&&a[d]!==b[d])throw new sjcl.exception.invalid("required parameter overridden");a[d]=b[d]}return a},sa:function(a,b){var c={},d;for(d in a)a.hasOwnProperty(d)&&a[d]!==b[d]&&(c[d]=a[d]);return c},ra:function(a,
b){var c={},d;for(d=0;d<b.length;d++)void 0!==a[b[d]]&&(c[b[d]]=a[b[d]]);return c}};sjcl.encrypt=sjcl.json.encrypt;sjcl.decrypt=sjcl.json.decrypt;sjcl.misc.pa={};sjcl.misc.cachedPbkdf2=function(a,b){var c=sjcl.misc.pa,d;b=b||{};d=b.iter||1E3;c=c[a]=c[a]||{};d=c[d]=c[d]||{firstSalt:b.salt&&b.salt.length?b.salt.slice(0):sjcl.random.randomWords(2,0)};c=void 0===b.salt?d.firstSalt:b.salt;d[c]=d[c]||sjcl.misc.pbkdf2(a,c,b.iter);return{key:d[c].slice(0),salt:c.slice(0)}};
"undefined"!==typeof module&&module.exports&&(module.exports=sjcl);"function"===typeof define&&define([],function(){return sjcl});

146
client/share/sjclWrapper.js Normal file
View File

@@ -0,0 +1,146 @@
//sjcl.js подправлен (убран лишний require, добавлявший +400kb к bundle) и скопирован локально
import sjcl from './sjcl';
//везде недоработки...
sjcl.codec.bytes = {
fromBits: function(arr) {
var out = [], bl = sjcl.bitArray.bitLength(arr), i, tmp;
for (i=0; i<bl/8; i++) {
if ((i&3) === 0) {
tmp = arr[i/4];
}
out.push(tmp >>> 24);
tmp <<= 8;
}
return out;
},
toBits: function(bytes) {
var out = [], i, tmp=0;
for (i=0; i<bytes.length; i++) {
tmp = tmp << 8 | bytes[i];
if ((i&3) === 3) {
out.push(tmp);
tmp = 0;
}
}
if (i&3) {
out.push(sjcl.bitArray.partial(8*(i&3), tmp));
}
return out;
}
};
sjcl.json._add = function(target, src, requireSame) {
if (target === undefined) { target = {}; }
if (src === undefined) { return target; }
var i;
for (i in src) {
if (src.hasOwnProperty(i)) {
if (requireSame && target[i] !== undefined && target[i] !== src[i]) {
throw new sjcl.exception.invalid("required parameter overridden");
}
target[i] = src[i];
}
}
return target;
}
sjcl.encryptArray = function(password, plaintext, params, rp) {
params = params || {};
rp = rp || {};
var j = sjcl.json, p = j._add({ iv: sjcl.random.randomWords(4,0) },
j.defaults), tmp, prp, adata;
j._add(p, params);
adata = p.adata;
if (typeof p.salt === "string") {
p.salt = sjcl.codec.base64.toBits(p.salt);
}
if (typeof p.iv === "string") {
p.iv = sjcl.codec.base64.toBits(p.iv);
}
if (!sjcl.mode[p.mode] ||
!sjcl.cipher[p.cipher] ||
(typeof password === "string" && p.iter <= 100) ||
(p.ts !== 64 && p.ts !== 96 && p.ts !== 128) ||
(p.ks !== 128 && p.ks !== 192 && p.ks !== 256) ||
(p.iv.length < 2 || p.iv.length > 4)) {
throw new sjcl.exception.invalid("json encrypt: invalid parameters");
}
if (typeof password === "string") {
tmp = sjcl.misc.cachedPbkdf2(password, p);
password = tmp.key.slice(0,p.ks/32);
p.salt = tmp.salt;
} else if (sjcl.ecc && password instanceof sjcl.ecc.elGamal.publicKey) {
tmp = password.kem();
p.kemtag = tmp.tag;
password = tmp.key.slice(0,p.ks/32);
}
if (typeof plaintext === "string") {
plaintext = sjcl.codec.utf8String.toBits(plaintext);
}
if (typeof adata === "string") {
p.adata = adata = sjcl.codec.utf8String.toBits(adata);
}
prp = new sjcl.cipher[p.cipher](password);
j._add(rp, p);
rp.key = password;
/* do the encryption */
if (p.mode === "ccm" && sjcl.arrayBuffer && sjcl.arrayBuffer.ccm && plaintext instanceof ArrayBuffer) {
p.ct = sjcl.arrayBuffer.ccm.encrypt(prp, plaintext, p.iv, adata, p.ts);
} else {
p.ct = sjcl.mode[p.mode].encrypt(prp, plaintext, p.iv, adata, p.ts);
}
return p;
}
sjcl.decryptArray = function(password, ciphertext, params) {
params = params || {};
var j = sjcl.json, p = j._add(j._add(j._add({},j.defaults),ciphertext), params, true), ct, tmp, prp, adata=p.adata;
if (typeof p.salt === "string") {
p.salt = sjcl.codec.base64.toBits(p.salt);
}
if (typeof p.iv === "string") {
p.iv = sjcl.codec.base64.toBits(p.iv);
}
if (!sjcl.mode[p.mode] ||
!sjcl.cipher[p.cipher] ||
(typeof password === "string" && p.iter <= 100) ||
(p.ts !== 64 && p.ts !== 96 && p.ts !== 128) ||
(p.ks !== 128 && p.ks !== 192 && p.ks !== 256) ||
(!p.iv) ||
(p.iv.length < 2 || p.iv.length > 4)) {
throw new sjcl.exception.invalid("json decrypt: invalid parameters");
}
if (typeof password === "string") {
tmp = sjcl.misc.cachedPbkdf2(password, p);
password = tmp.key.slice(0,p.ks/32);
p.salt = tmp.salt;
} else if (sjcl.ecc && password instanceof sjcl.ecc.elGamal.secretKey) {
password = password.unkem(sjcl.codec.base64.toBits(p.kemtag)).slice(0,p.ks/32);
}
if (typeof adata === "string") {
adata = sjcl.codec.utf8String.toBits(adata);
}
prp = new sjcl.cipher[p.cipher](password);
/* do the decryption */
if (p.mode === "ccm" && sjcl.arrayBuffer && sjcl.arrayBuffer.ccm && p.ct instanceof ArrayBuffer) {
ct = sjcl.arrayBuffer.ccm.decrypt(prp, p.ct, p.iv, p.tag, adata, p.ts);
} else {
ct = sjcl.mode[p.mode].decrypt(prp, p.ct, p.iv, adata, p.ts);
}
return ct;
}
export default sjcl;

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