Compare commits

...

263 Commits

Author SHA1 Message Date
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
151 changed files with 6633 additions and 4021 deletions

View File

@@ -9,6 +9,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const AppCachePlugin = require('appcache-webpack-plugin');
const publicDir = path.resolve(__dirname, '../dist/tmp/public');
const clientDir = path.resolve(__dirname, '../client');
@@ -53,6 +54,7 @@ module.exports = merge(baseWpConfig, {
template: `${clientDir}/index.html.template`,
filename: `${publicDir}/index.html`
}),
new CopyWebpackPlugin([{from: `${clientDir}/assets/*`, to: `${publicDir}/`, flatten: true}])
new CopyWebpackPlugin([{from: `${clientDir}/assets/*`, to: `${publicDir}/`, flatten: true}]),
new AppCachePlugin({exclude: ['../index.html']})
]
});

View File

@@ -1,23 +1,21 @@
import _ from 'lodash';
import axios from 'axios';
import {Buffer} from 'safe-buffer';
import * as utils from '../share/utils';
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) {
async loadBook(opts, callback) {
const refreshPause = 300;
if (!callback) callback = () => {};
let response = await api.post('/load-book', {type: 'url', url});
let response = await api.post('/load-book', opts);
const workerId = response.data.workerId;
if (!workerId)
@@ -66,12 +64,13 @@ class Reader {
estSize = response.headers['content-length'];
}
callback({state: 'loading', progress: 0});
const options = {
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)});
}
}
//загрузка

View File

@@ -47,14 +47,12 @@
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import * as utils from '../share/utils';
export default @Component({
watch: {
rootRoute: function() {
this.setAppTitle();
this.redirectIfNeeded();
},
mode: function() {
this.setAppTitle();
this.redirectIfNeeded();
}
},
@@ -113,13 +111,19 @@ class App extends Vue {
this.dispatch('config/loadConfig');
this.$watch('apiError', function(newError) {
if (newError) {
let mes = newError.message;
if (newError.response && newError.response.config)
mes = newError.response.config.url + '<br>' + newError.response.statusText;
this.$notify.error({
title: 'Ошибка API',
dangerouslyUseHTMLString: true,
message: newError.response.config.url + '<br>' + newError.response.statusText
message: mes
});
}
});
this.setAppTitle();
this.redirectIfNeeded();
}
toggleCollapse() {
@@ -198,15 +202,18 @@ class App extends Vue {
}
redirectIfNeeded() {
if ((this.mode == 'reader' || this.mode == 'omnireader') && (this.rootRoute != '/reader')) {
if ((this.mode == 'reader' || this.mode == 'omnireader') && (!this.isReaderActive)) {
//старый url
const search = window.location.search.substr(1);
const url = search.split('url=')[1] || '';
const s = search.split('url=');
const url = s[1] || '';
const q = utils.parseQuery(s[0] || '');
if (url) {
window.location = `/#/reader?url=${url}`;
} else {
this.$router.replace('/reader');
q.url = decodeURIComponent(url);
}
window.history.replaceState({}, '', '/');
this.$router.replace({ path: '/reader', query: q });
}
//yandex-метрика для omnireader

View File

@@ -1,17 +1,13 @@
<template>
<div ref="main" class="main" @click="close">
<div class="mainWindow" @click.stop>
<Window @close="close">
<template slot="header">
Скопировать текст
</template>
<Window @close="close">
<template slot="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>
@@ -109,23 +105,6 @@ class CopyTextPage extends Vue {
</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

@@ -3,8 +3,10 @@
<h4>Возможности читалки:</h4>
<ul>
<li>загрузка любой страницы интернета</li>
<li>работа в автономном режиме (без связи)</li>
<li>изменение цвета фона, текста, размер и тип шрифта и прочее</li>
<li>установка и запоминание текущей позиции и настроек в браузере (в будущем планируется сохранение и на сервер)</li>
<li>установка и запоминание текущей позиции и настроек в браузере и на сервере</li>
<li>синхронизация данных (настроек и читаемых книг) между различными устройствами</li>
<li>кэширование файлов книг на клиенте и на сервере</li>
<li>открытие книг с локального диска</li>
<li>плавный скроллинг текста</li>
@@ -12,16 +14,27 @@
<li>поиск по тексту и копирование фрагмента</li>
<li>запоминание недавних книг, скачивание книги из читалки в формате fb2</li>
<li>управление кликом и с клавиатуры</li>
<li>подключение к интернету не обязательно для чтения книги после ее загрузки</li>
<li>регистрация не требуется</li>
<li>поддерживаемые браузеры: Google Chrome, Mozilla Firefox последних версий</li>
</ul>
<p>В качестве URL можно задавать html-страничку с книгой, либо прямую ссылку
<p>В качестве URL книги можно задавать html-страничку с книгой, либо прямую ссылку
на файл из онлайн-библиотеки (например, скопировав адрес ссылки или кнопки "скачать fb2").</p>
<p>Поддерживаемые форматы: <b>fb2, fb2.zip, html, txt</b> и другие</p>
<p>Поддерживаемые форматы: <b>fb2, fb2.zip, html, txt</b> и другие.</p>
<div v-html="automationHtml"></div>
<div v-show="mode == 'omnireader'">
<p>Вы можете добавить в свой браузер закладку, указав в ее свойствах вместо адреса следующий код:
<br><strong>javascript:location.href='https://omnireader.ru/?url='+location.href;</strong>
&nbsp;
<span class="clickable" @click="copyText('javascript:location.href=\'https://omnireader.ru/?url=\'+location.href;', 'Код для адреса закладки успешно скопирован в буфер обмена')">
(скопировать)
</span>
<br>или перетащив на панель закладок следующую ссылку:
<br><a style="margin-left: 50px" href="javascript:location.href='https://omnireader.ru/?url='+location.href;">Omni Reader</a>
<br>Тогда, активировав получившуюся закладку на любой странице интернета, вы автоматически загрузите эту страницу в Omni Reader.
<br>В Chrome для Android можно вызывать такую закладку по имени прямо в адресной строке браузера (имя стоит сделать попроще).
</p>
</div>
<p>Связаться с разработчиком: <a href="mailto:bookpauk@gmail.com">bookpauk@gmail.com</a></p>
</div>
</template>
@@ -31,21 +44,25 @@
import Vue from 'vue';
import Component from 'vue-class-component';
import {copyTextToClipboard} from '../../../../share/utils';
export default @Component({
})
class CommonHelpPage extends Vue {
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;
}
async copyText(text, mes) {
const result = await copyTextToClipboard(text);
const msg = (result ? mes : 'Копирование не удалось');
if (result)
this.$notify.success({message: msg});
else
this.$notify.error({message: msg});
}
}
//-----------------------------------------------------------------------------
@@ -63,4 +80,10 @@ class CommonHelpPage extends Vue {
h4 {
margin: 0;
}
.clickable {
color: blue;
text-decoration: underline;
cursor: pointer;
}
</style>

View File

@@ -53,11 +53,10 @@ class DonateHelpPage extends Vue {
async copyAddress(address, prefix) {
const result = await copyTextToClipboard(address);
const msg = (result ? `${prefix}-адрес ${address} успешно скопирован в буфер обмена` : 'Копирование не удалось');
if (result)
this.$notify.success({message: msg});
this.$notify.success({message: `${prefix}-адрес ${address} успешно скопирован в буфер обмена`});
else
this.$notify.error({message: msg});
this.$notify.error({message: 'Копирование не удалось'});
}
}
//-----------------------------------------------------------------------------

View File

@@ -1,29 +1,27 @@
<template>
<div ref="main" class="main" @click="close">
<div class="mainWindow" @click.stop>
<Window @close="close">
<template slot="header">
Справка
</template>
<Window @close="close">
<template slot="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>
</el-tabs>
</Window>
</div>
</div>
<el-tabs type="border-card" v-model="selectedTab">
<el-tab-pane class="tab" label="Общее">
<CommonHelpPage></CommonHelpPage>
</el-tab-pane>
<el-tab-pane label="Клавиатура">
<HotkeysHelpPage></HotkeysHelpPage>
</el-tab-pane>
<el-tab-pane label="Мышь/тачскрин">
<MouseHelpPage></MouseHelpPage>
</el-tab-pane>
<el-tab-pane label="История версий" name="releases">
<VersionHistoryPage></VersionHistoryPage>
</el-tab-pane>
<el-tab-pane label="Помочь проекту" name="donate">
<DonateHelpPage></DonateHelpPage>
</el-tab-pane>
</el-tabs>
</Window>
</template>
<script>
@@ -36,6 +34,7 @@ import CommonHelpPage from './CommonHelpPage/CommonHelpPage.vue';
import HotkeysHelpPage from './HotkeysHelpPage/HotkeysHelpPage.vue';
import MouseHelpPage from './MouseHelpPage/MouseHelpPage.vue';
import DonateHelpPage from './DonateHelpPage/DonateHelpPage.vue';
import VersionHistoryPage from './VersionHistoryPage/VersionHistoryPage.vue';
export default @Component({
components: {
@@ -44,6 +43,7 @@ export default @Component({
HotkeysHelpPage,
MouseHelpPage,
DonateHelpPage,
VersionHistoryPage,
},
})
class HelpPage extends Vue {
@@ -57,6 +57,10 @@ class HelpPage extends Vue {
this.selectedTab = 'donate';
}
activateVersionHistoryHelpPage() {
this.selectedTab = 'releases';
}
keyHook(event) {
if (event.type == 'keydown' && (event.code == 'Escape')) {
this.close();
@@ -68,23 +72,6 @@ class HelpPage extends Vue {
</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;

View File

@@ -20,6 +20,7 @@
<li><b>Ctrl+C</b> - скопировать текст со страницы</li>
<li><b>R</b> - принудительно обновить книгу в обход кэша</li>
<li><b>X</b> - открыть недавние</li>
<li><b>O</b> - автономный режим</li>
<li><b>S</b> - открыть окно настроек</li>
</ul>
</div>

View File

@@ -1,15 +1,25 @@
<template>
<div class="page">
<h4>Управление с помощью мыши/тачпада:</h4>
<h4>Управление с помощью мыши/тачскрина:</h4>
<ul>
<li><b>ЛКМ/ТАЧ</b> по экрану в одну из областей - активация действия:</li>
<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>

View File

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

View File

@@ -1,287 +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"
:row-key = "rowKey"
>
<el-table-column
type="index"
width="35px"
>
</el-table-column>
<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"
ref="input"
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() {
}
init() {
this.updateTableData();
this.$nextTick(() => {
this.$refs.input.focus();
});
}
rowKey(row) {
return row.key;
}
updateTableData() {
let result = [];
const sorted = bookManager.getSortedRecent();
for (let i = 0; i < sorted.length; i++) {
const book = sorted[i];
if (book.deleted)
continue;
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 = '';
if (fb2.author) {
const authorNames = fb2.author.map(a => _.compact([
a.lastName,
a.firstName,
a.middleName
]).join(' '));
author = authorNames.join(', ');
} else {
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,
});
if (result.length >= 100)
break;
}
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;
}
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();
if (!bookManager.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

@@ -1,6 +1,6 @@
<template>
<div ref="main" class="main">
<div class="part">
<div class="part top">
<span class="greeting bold-font">{{ title }}</span>
<div class="space"></div>
<span class="greeting">Добро пожаловать!</span>
@@ -14,18 +14,39 @@
</el-input>
<div class="space"></div>
<input type="file" id="file" ref="file" @change="loadFile" style='display: none;'/>
<el-button size="mini" @click="loadFileClick">
Загрузить файл с диска
</el-button>
<div class="space"></div>
<span v-if="mode == 'omnireader'" class="bottom-span clickable" @click="openComments">Комментарии</span>
<el-button size="mini" @click="loadBufferClick">
Из буфера обмена
</el-button>
<div class="space"></div>
<div class="space"></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="space"></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">
<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>
@@ -33,12 +54,18 @@
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import PasteTextPage from './PasteTextPage/PasteTextPage.vue';
import {versionHistory} from '../versionHistory';
export default @Component({
components: {
PasteTextPage,
},
})
class LoaderPage extends Vue {
bookUrl = null;
loadPercent = 0;
pasteTextActive = false;
created() {
this.commit = this.$store.commit;
@@ -46,6 +73,8 @@ class LoaderPage extends Vue {
mounted() {
this.progress = this.$refs.progress;
if (this.mode == 'omnireader')
Ya.share2(this.$refs.yaShare2);// eslint-disable-line no-undef
}
activated() {
@@ -53,7 +82,7 @@ class LoaderPage extends Vue {
}
get title() {
if (this.$store.state.config.mode == 'omnireader')
if (this.mode == 'omnireader')
return 'Omni Reader - браузерная онлайн-читалка.';
return 'Универсальная читалка книг и ресурсов интернета.';
@@ -64,13 +93,19 @@ class LoaderPage extends Vue {
}
get version() {
return `v${this.$store.state.config.version}`;
return this.$store.state.config.version;
}
get isExternalConverter() {
return this.$store.state.config.useExternalBookConverter;
}
get clientVersion() {
let v = versionHistory[0].header;
v = v.split(' ')[0];
return v;
}
submitUrl() {
if (this.bookUrl) {
this.$emit('load-book', {url: this.bookUrl});
@@ -83,12 +118,27 @@ 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});
}
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() {
this.$emit('help-toggle');
}
@@ -101,7 +151,15 @@ class LoaderPage extends Vue {
window.open('http://samlib.ru/comment/b/bookpauk/bookpauk_reader', '_blank');
}
openOldVersion() {
window.open('http://old.omnireader.ru', '_blank');
}
keyHook(event) {
if (this.pasteTextActive) {
return this.$refs.pasteTextPage.keyHook(event);
}
//недостатки сторонних ui
const input = this.$refs.input.$refs.input;
if (document.activeElement === input && event.type == 'keydown' && event.code == 'Enter') {
@@ -130,7 +188,7 @@ class LoaderPage extends Vue {
flex: 1;
display: flex;
flex-direction: column;
min-height: 340px;
min-height: 480px;
}
.part {
@@ -156,9 +214,14 @@ class LoaderPage extends Vue {
cursor: pointer;
}
.top {
min-height: 120px;
}
.center {
justify-content: flex-start;
padding: 0 10px 0 10px;
min-height: 250px;
}
.bottom {

View File

@@ -0,0 +1,123 @@
<template>
<Window @close="close">
<template slot="header">
<span style="position: relative; top: -3px">
Вставьте текст и нажмите
<span class="clickable" style="font-size: 150%; position: relative; top: 1px" @click="loadBuffer">загрузить</span>
или F2
</span>
</template>
<div>
<el-input placeholder="Введите название текста" class="input" v-model="bookTitle"></el-input>
</div>
<hr/>
<textarea ref="textArea" class="text" @paste="calcTitle"></textarea>
</Window>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import Window from '../../../share/Window.vue';
import _ from 'lodash';
import * as utils from '../../../../share/utils';
export default @Component({
components: {
Window,
},
})
class PasteTextPage extends Vue {
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 == '') {
let text = event.clipboardData.getData('text');
this.bookTitle = `Из буфера обмена ${utils.formatDate(new Date(), 'noDate')}: ` + _.compact([
this.getNonEmptyLine3words(text, 1),
this.getNonEmptyLine3words(text, 2)
]).join(' - ');
}
}
loadBuffer() {
this.$emit('load-buffer', {buffer: `<cut-title>${this.bookTitle}</cut-title>${this.$refs.textArea.value}`});
this.close();
}
close() {
this.$emit('paste-text-toggle');
}
keyHook(event) {
if (event.type == 'keydown') {
switch (event.code) {
case 'F2':
this.loadBuffer();
break;
case 'Escape':
this.close();
break;
}
}
return true;
}
}
//-----------------------------------------------------------------------------
</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

@@ -94,6 +94,6 @@ class ProgressPage extends Vue {
</style>
<style>
.el-progress__text {
color: lightgreen;
color: lightgreen !important;
}
</style>

View File

@@ -1,42 +1,45 @@
<template>
<el-container>
<el-header v-show="toolBarActive" height='50px'>
<div class="header">
<div ref="header" class="header">
<el-tooltip content="Загрузить книгу" :open-delay="1000" effect="light">
<el-button ref="loader" class="tool-button" :class="buttonActiveClass('loader')" @click="buttonClick('loader')"><i class="el-icon-back"></i></el-button>
</el-tooltip>
<div>
<el-tooltip content="Действие назад" :open-delay="1000" effect="light">
<el-tooltip v-show="showToolButton['undoAction']" content="Действие назад" :open-delay="1000" effect="light">
<el-button ref="undoAction" class="tool-button" :class="buttonActiveClass('undoAction')" @click="buttonClick('undoAction')" ><i class="el-icon-arrow-left"></i></el-button>
</el-tooltip>
<el-tooltip content="Действие вперед" :open-delay="1000" effect="light">
<el-tooltip v-show="showToolButton['redoAction']" content="Действие вперед" :open-delay="1000" effect="light">
<el-button ref="redoAction" class="tool-button" :class="buttonActiveClass('redoAction')" @click="buttonClick('redoAction')" ><i class="el-icon-arrow-right"></i></el-button>
</el-tooltip>
<div class="space"></div>
<el-tooltip content="На весь экран" :open-delay="1000" effect="light">
<el-tooltip v-show="showToolButton['fullScreen']" content="На весь экран" :open-delay="1000" effect="light">
<el-button ref="fullScreen" class="tool-button" :class="buttonActiveClass('fullScreen')" @click="buttonClick('fullScreen')"><i class="el-icon-rank"></i></el-button>
</el-tooltip>
<el-tooltip content="Плавный скроллинг" :open-delay="1000" effect="light">
<el-tooltip v-show="showToolButton['scrolling']" content="Плавный скроллинг" :open-delay="1000" effect="light">
<el-button ref="scrolling" class="tool-button" :class="buttonActiveClass('scrolling')" @click="buttonClick('scrolling')"><i class="el-icon-sort"></i></el-button>
</el-tooltip>
<el-tooltip content="Перелистнуть" :open-delay="1000" effect="light">
<el-tooltip v-show="showToolButton['setPosition']" content="На страницу" :open-delay="1000" effect="light">
<el-button ref="setPosition" class="tool-button" :class="buttonActiveClass('setPosition')" @click="buttonClick('setPosition')"><i class="el-icon-d-arrow-right"></i></el-button>
</el-tooltip>
<el-tooltip content="Найти в тексте" :open-delay="1000" effect="light">
<el-tooltip v-show="showToolButton['search']" content="Найти в тексте" :open-delay="1000" effect="light">
<el-button ref="search" class="tool-button" :class="buttonActiveClass('search')" @click="buttonClick('search')"><i class="el-icon-search"></i></el-button>
</el-tooltip>
<el-tooltip content="Скопировать текст со страницы" :open-delay="1000" effect="light">
<el-tooltip v-show="showToolButton['copyText']" content="Скопировать текст со страницы" :open-delay="1000" effect="light">
<el-button ref="copyText" class="tool-button" :class="buttonActiveClass('copyText')" @click="buttonClick('copyText')"><i class="el-icon-edit-outline"></i></el-button>
</el-tooltip>
<el-tooltip content="Принудительно обновить книгу в обход кэша" :open-delay="1000" effect="light">
<el-tooltip v-show="showToolButton['refresh']" content="Принудительно обновить книгу в обход кэша" :open-delay="1000" effect="light">
<el-button ref="refresh" class="tool-button" :class="buttonActiveClass('refresh')" @click="buttonClick('refresh')">
<i class="el-icon-refresh" :class="{clear: !showRefreshIcon}"></i>
</el-button>
</el-tooltip>
<div class="space"></div>
<el-tooltip content="Открыть недавние" :open-delay="1000" effect="light">
<el-button ref="history" class="tool-button" :class="buttonActiveClass('history')" @click="buttonClick('history')"><i class="el-icon-document"></i></el-button>
<el-tooltip v-show="showToolButton['offlineMode']" content="Автономный режим (без интернета)" :open-delay="1000" effect="light">
<el-button ref="offlineMode" class="tool-button" :class="buttonActiveClass('offlineMode')" @click="buttonClick('offlineMode')"><i class="el-icon-connection"></i></el-button>
</el-tooltip>
<el-tooltip v-show="showToolButton['recentBooks']" content="Открыть недавние" :open-delay="1000" effect="light">
<el-button ref="recentBooks" class="tool-button" :class="buttonActiveClass('recentBooks')" @click="buttonClick('recentBooks')"><i class="el-icon-document"></i></el-button>
</el-tooltip>
</div>
@@ -68,13 +71,108 @@
@start-text-search="startTextSearch"
@stop-text-search="stopTextSearch">
</SearchPage>
<CopyTextPage v-if="copyTextActive" ref="copyTextPage" @copy-text-toggle="copyTextToggle"></CopyTextPage>
<HistoryPage v-show="historyActive" ref="historyPage" @load-book="loadBook" @history-toggle="historyToggle"></HistoryPage>
<CopyTextPage v-if="copyTextActive" ref="copyTextPage" @copy-text-toggle="copyTextToggle"></CopyTextPage>
<RecentBooksPage v-show="recentBooksActive" ref="recentBooksPage" @load-book="loadBook" @recent-books-toggle="recentBooksToggle"></RecentBooksPage>
<SettingsPage v-if="settingsActive" ref="settingsPage" @settings-toggle="settingsToggle"></SettingsPage>
<HelpPage v-if="helpActive" ref="helpPage" @help-toggle="helpToggle"></HelpPage>
<ClickMapPage v-show="clickMapActive" ref="clickMapPage"></ClickMapPage>
<ServerStorage v-show="hidden" ref="serverStorage"></ServerStorage>
<el-dialog
title="Что нового:"
:visible.sync="whatsNewVisible"
width="80%">
<div style="line-height: 20px" v-html="whatsNewContent"></div>
<span class="clickable" @click="openVersionHistory">Посмотреть историю версий</span>
<span slot="footer" class="dialog-footer">
<el-button @click="whatsNewDisable">Больше не показывать</el-button>
</span>
</el-dialog>
<el-dialog
title="Внимание!"
:visible.sync="migrationVisible1"
width="90%">
<div>
Появилась httpS-версия сайта по адресу <a href="https://omnireader.ru" target="_blank">https://omnireader.ru</a><br>
Работа по httpS-протоколу, помимо безопасности соединения, позволяет воспользоваться всеми возможностями
современных браузеров, а именно, применительно к нашему ресурсу:
<ul>
<li>возможность автономной работы с читалкой (без доступа к интернету), кеширование сайта через appcache</li>
<li>безопасная передача на сервер данных о настройках и читаемых книгах при включенной синхронизации; все данные шифруются на стороне
браузера ключом доступа и никто (в т.ч. администратор) не имеет возможности их прочитать
<li>использование встроенных в JS функций шифрования и других</li>
</ul>
Для того, чтобы перейти на новую версию с сохранением настроек и читаемых книг необходимо синхронизировать обе читалки:
<ul>
<li>зайти в "Настройки"->"Профили" и поставить галочку "Включить синхронизацию с сервером"</li>
<li>там же добавить профиль устройства с любым именем для синхронизации настроек<br>
<span style="margin-left: 20px"><i style="font-size: 90%" class="el-icon-info"></i>
после этого все данные будут автоматически сохранены на сервер
</span>
</li>
<li>далее нажать на кнопку "Показать ключ доступа" и кликнуть по ссылке "Ссылка для ввода ключа"<br>
<span style="margin-left: 20px"><i style="font-size: 90%" class="el-icon-info"></i>
произойдет переход на https-версию читалки и откроется окно для ввода ключа
</span><br>
<span style="margin-left: 20px"><i style="font-size: 90%" class="el-icon-info"></i>
подтвердив ввод ключа нажатием "OK", включив синхронизацию с сервером и выбрав профиль устройства, вы восстановите все ваши настройки в новой версии
</span>
</li>
</ul>
Старая http-версия сайта будет доступна до конца 2019 года.<br>
Приносим извинения за доставленные неудобства.
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="migrationDialogDisable">Больше не показывать</el-button>
<el-button @click="migrationDialogRemind">Напомнить позже</el-button>
</span>
</el-dialog>
<el-dialog
title="Внимание!"
:visible.sync="migrationVisible2"
width="90%">
<div>
Информация для пользователей старой версии читалки по адресу <a href="http://omnireader.ru" target="_blank">http://omnireader.ru</a><br>
Для того, чтобы перейти на новую httpS-версию с сохранением настроек и читаемых книг необходимо синхронизировать обе читалки:
<ul>
<li>перейти на старую версию ресурса <a href="http://omnireader.ru" target="_blank">http://omnireader.ru</a></li>
<li>зайти в "Настройки"->"Профили" и поставить галочку "Включить синхронизацию с сервером"</li>
<li>там же добавить профиль устройства с любым именем для синхронизации настроек<br>
<span style="margin-left: 20px"><i style="font-size: 90%" class="el-icon-info"></i>
после этого все данные будут автоматически сохранены на сервер
</span>
</li>
<li>далее нажать на кнопку "Показать ключ доступа" и кликнуть по ссылке "Ссылка для ввода ключа"<br>
<span style="margin-left: 20px"><i style="font-size: 90%" class="el-icon-info"></i>
произойдет переход на https-версию читалки и откроется окно для ввода ключа
</span><br>
<span style="margin-left: 20px"><i style="font-size: 90%" class="el-icon-info"></i>
подтвердив ввод ключа нажатием "OK", включив синхронизацию с сервером и выбрав профиль устройства, вы восстановите все ваши настройки в новой версии
</span>
</li>
</ul>
Старая http-версия сайта будет доступна до конца 2019 года.<br>
Приносим извинения за доставленные неудобства.
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="migrationDialogDisable">Больше не показывать</el-button>
<el-button @click="migrationDialogRemind">Напомнить позже</el-button>
</span>
</el-dialog>
</el-main>
</el-container>
</template>
@@ -92,7 +190,7 @@ import ProgressPage from './ProgressPage/ProgressPage.vue';
import SetPositionPage from './SetPositionPage/SetPositionPage.vue';
import SearchPage from './SearchPage/SearchPage.vue';
import CopyTextPage from './CopyTextPage/CopyTextPage.vue';
import HistoryPage from './HistoryPage/HistoryPage.vue';
import RecentBooksPage from './RecentBooksPage/RecentBooksPage.vue';
import SettingsPage from './SettingsPage/SettingsPage.vue';
import HelpPage from './HelpPage/HelpPage.vue';
import ClickMapPage from './ClickMapPage/ClickMapPage.vue';
@@ -101,6 +199,7 @@ import ServerStorage from './ServerStorage/ServerStorage.vue';
import bookManager from './share/bookManager';
import readerApi from '../../api/reader';
import * as utils from '../../share/utils';
import {versionHistory} from './versionHistory';
export default @Component({
components: {
@@ -111,7 +210,7 @@ export default @Component({
SetPositionPage,
SearchPage,
CopyTextPage,
HistoryPage,
RecentBooksPage,
SettingsPage,
HelpPage,
ClickMapPage,
@@ -121,14 +220,19 @@ export default @Component({
bookPos: function(newValue) {
if (newValue !== undefined && this.activePage == 'TextPage') {
const textPage = this.$refs.page;
if (textPage.bookPos != newValue) {
textPage.bookPos = newValue;
}
this.debouncedSetRecentBook(newValue);
if (!this.scrollingActive)
this.debouncedSetRecentBook(newValue);
else
this.scrollingSetRecentBook(newValue);
}
},
routeParamPos: function(newValue) {
if (newValue !== undefined && newValue != this.bookPos) {
if (!this.paramPosIgnore && newValue !== undefined && newValue != this.bookPos) {
this.bookPos = newValue;
}
},
@@ -142,10 +246,12 @@ export default @Component({
this.updateRoute();
},
loaderActive: function(newValue) {
const recent = this.mostRecentBook();
if (!newValue && !this.loading && recent && !bookManager.hasBookParsed(recent)) {
this.loadBook(recent);
}
(async() => {
const recent = this.mostRecentBook();
if (!newValue && !this.loading && recent && !await bookManager.hasBookParsed(recent)) {
this.loadBook(recent);
}
})();
},
},
})
@@ -158,7 +264,8 @@ class Reader extends Vue {
setPositionActive = false;
searchActive = false;
copyTextActive = false;
historyActive = false;
recentBooksActive = false;
offlineModeActive = false;
settingsActive = false;
helpActive = false;
clickMapActive = false;
@@ -167,11 +274,17 @@ class Reader extends Vue {
allowUrlParamBookPos = false;
showRefreshIcon = true;
mostRecentBookReactive = null;
showToolButton = {};
actionList = [];
actionCur = -1;
hidden = false;
whatsNewVisible = false;
whatsNewContent = '';
migrationVisible1 = false;
migrationVisible2 = false;
created() {
this.loading = true;
this.commit = this.$store.commit;
@@ -183,10 +296,6 @@ class Reader extends Vue {
this.lastActivePage = false;
this.debouncedUpdateRoute = _.debounce(() => {
this.updateRoute();
}, 1000);
this.debouncedSetRecentBook = _.debounce(async(newValue) => {
const recent = this.mostRecentBook();
if (recent && (recent.bookPos != newValue || recent.bookPosSeen !== this.bookPosSeen)) {
@@ -194,43 +303,58 @@ class Reader extends Vue {
if (this.actionCur < 0 || (this.actionCur >= 0 && this.actionList[this.actionCur] != newValue))
this.addAction(newValue);
this.paramPosIgnore = true;
this.updateRoute();
await this.$nextTick();
this.paramPosIgnore = false;
}
}, 500);
}, 500, {'maxWait':5000});
this.debouncedSaveRecent = _.debounce(async() => {
const serverStorage = this.$refs.serverStorage;
while (!serverStorage.inited) await utils.sleep(1000);
await serverStorage.saveRecent();
}, 1000);
this.debouncedSaveRecentLast = _.debounce(async() => {
const serverStorage = this.$refs.serverStorage;
while (!serverStorage.inited) await utils.sleep(1000);
await serverStorage.saveRecentLast();
}, 1000);
this.scrollingSetRecentBook = _.debounce((newValue) => {
this.debouncedSetRecentBook(newValue);
}, 15000, {'maxWait':20000});
document.addEventListener('fullscreenchange', () => {
this.fullScreenActive = (document.fullscreenElement !== null);
});
this.loadSettings();
//TODO: убрать в будущем
if (this.showToolButton['history']) {
const newShowToolButton = Object.assign({}, this.showToolButton);
newShowToolButton['recentBooks'] = true;
delete newShowToolButton['history'];
const newSettings = Object.assign({}, this.settings, { showToolButton: newShowToolButton });
this.commit('reader/setSettings', newSettings);
}
}
mounted() {
this.updateHeaderMinWidth();
(async() => {
await bookManager.init(this.settings);
bookManager.addEventListener(this.bookManagerEvent);
if (this.$root.rootRoute == '/reader') {
if (this.routeParamUrl) {
await this.loadBook({url: this.routeParamUrl, bookPos: this.routeParamPos});
await this.loadBook({url: this.routeParamUrl, bookPos: this.routeParamPos, force: this.routeParamRefresh});
} else {
this.loaderActive = true;
}
}
await this.$refs.serverStorage.init();
this.checkSetStorageAccessKey();
this.checkActivateDonateHelpPage();
this.loading = false;
await this.showWhatsNew();
await this.showMigration();
this.updateRoute();
})();
}
@@ -241,6 +365,18 @@ class Reader extends Vue {
this.showClickMapPage = settings.showClickMapPage;
this.clickControl = settings.clickControl;
this.blinkCachedLoad = settings.blinkCachedLoad;
this.showWhatsNewDialog = settings.showWhatsNewDialog;
this.showMigrationDialog = settings.showMigrationDialog;
this.showToolButton = settings.showToolButton;
this.enableSitesFilter = settings.enableSitesFilter;
this.updateHeaderMinWidth();
}
updateHeaderMinWidth() {
const showButtonCount = Object.values(this.showToolButton).reduce((a, b) => a + (b ? 1 : 0), 0);
if (this.$refs.header)
this.$refs.header.style.minWidth = 65*showButtonCount + 'px';
}
checkSetStorageAccessKey() {
@@ -257,6 +393,83 @@ class Reader extends Vue {
}
}
checkActivateDonateHelpPage() {
const q = this.$route.query;
if (q['donate']) {
this.$router.replace(`/reader`);
this.helpToggle();
this.$nextTick(() => {
this.$refs.helpPage.activateDonateHelpPage();
});
}
}
checkBookPosPercent() {
const q = this.$route.query;
if (q['__pp']) {
let pp = q['__pp'];
if (pp) {
pp = parseFloat(pp) || 0;
const recent = this.mostRecentBook();
(async() => {
await utils.sleep(100);
this.bookPos = Math.floor(recent.textLength*pp/100);
})();
}
}
}
async showWhatsNew() {
await utils.sleep(2000);
const whatsNew = versionHistory[0];
if (this.showWhatsNewDialog &&
whatsNew.showUntil >= utils.formatDate(new Date(), 'coDate') &&
whatsNew.header != this.whatsNewContentHash) {
this.whatsNewContent = 'Версия ' + whatsNew.header + whatsNew.content;
this.whatsNewVisible = true;
}
}
async showMigration() {
await utils.sleep(3000);
if (!this.settingsActive &&
this.mode == 'omnireader' && this.showMigrationDialog && this.migrationRemindDate != utils.formatDate(new Date(), 'coDate')) {
if (window.location.protocol == 'http:') {
this.migrationVisible1 = true;
} else if (window.location.protocol == 'https:') {
this.migrationVisible2 = true;
}
}
}
migrationDialogDisable() {
this.migrationVisible1 = false;
this.migrationVisible2 = false;
if (this.showMigrationDialog) {
const newSettings = Object.assign({}, this.settings, { showMigrationDialog: false });
this.commit('reader/setSettings', newSettings);
}
}
migrationDialogRemind() {
this.migrationVisible1 = false;
this.migrationVisible2 = false;
this.commit('reader/setMigrationRemindDate', utils.formatDate(new Date(), 'coDate'));
}
openVersionHistory() {
this.whatsNewVisible = false;
this.versionHistoryToggle();
}
whatsNewDisable() {
this.whatsNewVisible = false;
const whatsNew = versionHistory[0];
this.commit('reader/setWhatsNewContentHash', whatsNew.header);
}
get routeParamPos() {
let result = undefined;
const q = this.$route.query;
@@ -276,12 +489,16 @@ class Reader extends Vue {
const pos = (recent && recent.bookPos && this.allowUrlParamBookPos ? `__p=${recent.bookPos}&` : '');
const url = (recent ? `url=${recent.url}` : '');
if (isNewRoute)
this.$router.push(`/reader?${pos}${url}`);
this.$router.push(`/reader?${pos}${url}`).catch(() => {});
else
this.$router.replace(`/reader?${pos}${url}`);
this.$router.replace(`/reader?${pos}${url}`).catch(() => {});
}
get mode() {
return this.$store.state.config.mode;
}
get routeParamUrl() {
let result = '';
const path = this.$route.fullPath;
@@ -293,48 +510,51 @@ class Reader extends Vue {
return decodeURIComponent(result);
}
get routeParamRefresh() {
const q = this.$route.query;
return !!q['__refresh'];
}
bookPosChanged(event) {
if (event.bookPosSeen !== undefined)
this.bookPosSeen = event.bookPosSeen;
this.bookPos = event.bookPos;
this.debouncedUpdateRoute();
}
async bookManagerEvent(eventName) {
const serverStorage = this.$refs.serverStorage;
if (eventName == 'load-meta-finish') {
serverStorage.init();
const result = await bookManager.cleanRecentBooks();
if (result)
this.debouncedSaveRecent();
}
if (eventName == 'recent-changed' || eventName == 'save-recent') {
if (this.historyActive) {
this.$refs.historyPage.updateTableData();
}
const oldBook = this.mostRecentBookReactive;
async bookManagerEvent(eventName, value) {
if (eventName == 'set-recent' || eventName == 'recent-deleted') {
const oldBook = (this.textPage ? this.textPage.lastBook : null);
const oldPos = (this.textPage ? this.textPage.bookPos : null);
const newBook = bookManager.mostRecentBook();
if (!(oldBook && newBook && oldBook.key == newBook.key)) {
this.mostRecentBook();
}
if (oldBook && newBook) {
if (oldBook.key != newBook.key) {
if (oldBook.key != newBook.key || oldBook.path != newBook.path) {
this.loadingBook = true;
try {
await this.loadBook(newBook);
} finally {
this.loadingBook = false;
}
} else if (oldBook.bookPos != newBook.bookPos) {
} else if (oldPos != newBook.bookPos) {
while (this.loadingBook) await utils.sleep(100);
this.bookPosChanged({bookPos: newBook.bookPos});
}
}
}
if (eventName == 'recent-changed') {
this.debouncedSaveRecentLast();
} else {
this.debouncedSaveRecent();
if (eventName == 'recent-changed') {
if (this.recentBooksActive) {
await this.$refs.recentBooksPage.updateTableData();
}
//сохранение в serverStorage
if (value) {
await utils.sleep(500);
await this.$refs.serverStorage.saveRecent(value);
}
}
}
@@ -353,6 +573,14 @@ class Reader extends Vue {
return this.$store.state.reader.settings;
}
get whatsNewContentHash() {
return this.$store.state.reader.whatsNewContentHash;
}
get migrationRemindDate() {
return this.$store.state.reader.migrationRemindDate;
}
addAction(pos) {
let a = this.actionList;
if (!a.length || a[a.length - 1] != pos) {
@@ -393,7 +621,7 @@ class Reader extends Vue {
closeAllTextPages() {
this.setPositionActive = false;
this.copyTextActive = false;
this.historyActive = false;
this.recentBooksActive = false;
this.settingsActive = false;
this.stopScrolling();
this.stopSearch();
@@ -437,6 +665,10 @@ class Reader extends Vue {
page.stopTextScrolling();
}
}
if (!this.scrollingActive) {
this.scrollingSetRecentBook.flush();
}
}
stopSearch() {
@@ -485,22 +717,31 @@ class Reader extends Vue {
}
}
historyToggle() {
this.historyActive = !this.historyActive;
if (this.historyActive) {
recentBooksToggle() {
this.recentBooksActive = !this.recentBooksActive;
if (this.recentBooksActive) {
this.closeAllTextPages();
this.$refs.historyPage.init();
this.historyActive = true;
this.$refs.recentBooksPage.init();
this.recentBooksActive = true;
} else {
this.historyActive = false;
this.recentBooksActive = false;
}
}
offlineModeToggle() {
this.offlineModeActive = !this.offlineModeActive;
this.$refs.serverStorage.offlineModeActive = this.offlineModeActive;
}
settingsToggle() {
this.settingsActive = !this.settingsActive;
if (this.settingsActive) {
this.closeAllTextPages();
this.settingsActive = true;
this.$nextTick(() => {
this.$refs.settingsPage.init();
});
} else {
this.settingsActive = false;
}
@@ -523,6 +764,15 @@ class Reader extends Vue {
}
}
versionHistoryToggle() {
this.helpToggle();
if (this.helpActive) {
this.$nextTick(() => {
this.$refs.helpPage.activateVersionHistoryHelpPage();
});
}
}
refreshBook() {
if (this.mostRecentBook()) {
this.loadBook({url: this.mostRecentBook().url, force: true});
@@ -568,12 +818,15 @@ class Reader extends Vue {
case 'copyText':
this.copyTextToggle();
break;
case 'history':
this.historyToggle();
break;
case 'refresh':
this.refreshBook();
break;
case 'recentBooks':
this.recentBooksToggle();
break;
case 'offlineMode':
this.offlineModeToggle();
break;
case 'settings':
this.settingsToggle();
break;
@@ -592,7 +845,8 @@ class Reader extends Vue {
case 'scrolling':
case 'search':
case 'copyText':
case 'history':
case 'recentBooks':
case 'offlineMode':
case 'settings':
if (this[`${button}Active`])
classResult = classActive;
@@ -610,7 +864,7 @@ class Reader extends Vue {
break;
}
if (this.activePage == 'LoaderPage' || !this.mostRecentBook()) {
if (this.activePage == 'LoaderPage' || !this.mostRecentBookReactive) {
switch (button) {
case 'undoAction':
case 'redoAction':
@@ -620,9 +874,9 @@ class Reader extends Vue {
case 'copyText':
classResult = classDisabled;
break;
case 'history':
case 'recentBooks':
case 'refresh':
if (!this.mostRecentBook())
if (!this.mostRecentBookReactive)
classResult = classDisabled;
break;
}
@@ -667,7 +921,8 @@ class Reader extends Vue {
//акивируем страницу с текстом
this.$nextTick(async() => {
const last = this.mostRecentBookReactive;
const isParsed = bookManager.hasBookParsed(last);
const isParsed = await bookManager.hasBookParsed(last);
if (!isParsed) {
this.$root.$emit('set-app-title');
return;
@@ -676,6 +931,7 @@ class Reader extends Vue {
this.updateRoute();
const textPage = this.$refs.page;
if (textPage.showBook) {
this.textPage = textPage;
textPage.lastBook = last;
textPage.bookPos = (last.bookPos !== undefined ? last.bookPos : 0);
@@ -700,15 +956,17 @@ class Reader extends Vue {
url = 'http://' + url;
// уже просматривается сейчас
const lastBook = (this.$refs.page ? this.$refs.page.lastBook : null);
if (!opts.force && lastBook && lastBook.url == url && bookManager.hasBookParsed(lastBook)) {
const lastBook = (this.textPage ? this.textPage.lastBook : null);
if (!opts.force && lastBook && lastBook.url == url &&
(!opts.path || opts.path == lastBook.path) &&
await bookManager.hasBookParsed(lastBook)) {
this.loaderActive = false;
return;
}
this.progressActive = true;
await this.$nextTick()
await this.$nextTick();
const progress = this.$refs.page;
@@ -730,7 +988,7 @@ class Reader extends Vue {
if (!opts.force) {
// пытаемся загрузить и распарсить книгу в менеджере из локального кэша
const bookParsed = await bookManager.getBook({url}, (prog) => {
const bookParsed = await bookManager.getBook({url, path: opts.path}, (prog) => {
progress.setState({progress: prog});
});
@@ -743,6 +1001,7 @@ class Reader extends Vue {
progress.hide(); this.progressActive = false;
this.blinkCachedLoadMessage();
this.checkBookPosPercent();
await this.activateClickMapPage();
return;
}
@@ -750,6 +1009,7 @@ class Reader extends Vue {
// иначе идем на сервер
// пытаемся загрузить готовый файл с сервера
if (wasOpened.path) {
progress.setState({totalSteps: 5});
try {
const resp = await readerApi.loadCachedBook(wasOpened.path, (state) => {
progress.setState(state);
@@ -762,11 +1022,10 @@ class Reader extends Vue {
}
progress.setState({totalSteps: 5});
// не удалось, скачиваем книгу полностью с конвертацией
let loadCached = true;
if (!book) {
book = await readerApi.loadBook(url, (state) => {
book = await readerApi.loadBook({url, enableSitesFilter: this.enableSitesFilter}, (state) => {
progress.setState(state);
});
loadCached = false;
@@ -791,6 +1050,7 @@ class Reader extends Vue {
} else
this.stopBlink = true;
this.checkBookPosPercent();
await this.activateClickMapPage();
} catch (e) {
progress.hide(); this.progressActive = false;
@@ -835,7 +1095,7 @@ class Reader extends Vue {
let page = this.$refs.page;
while (this.blinkCount) {
this.showRefreshIcon = !this.showRefreshIcon;
if (page.blinkCachedLoadMessage)
if (page && page.blinkCachedLoadMessage)
page.blinkCachedLoadMessage(this.showRefreshIcon);
await utils.sleep(500);
if (this.stopBlink)
@@ -845,7 +1105,7 @@ class Reader extends Vue {
}
this.showRefreshIcon = true;
this.inBlink = false;
if (page.blinkCachedLoadMessage)
if (page && page.blinkCachedLoadMessage)
page.blinkCachedLoadMessage('finish');
});
}
@@ -860,8 +1120,8 @@ class Reader extends Vue {
if (!handled && this.settingsActive)
handled = this.$refs.settingsPage.keyHook(event);
if (!handled && this.historyActive)
handled = this.$refs.historyPage.keyHook(event);
if (!handled && this.recentBooksActive)
handled = this.$refs.recentBooksPage.keyHook(event);
if (!handled && this.setPositionActive)
handled = this.$refs.setPositionPage.keyHook(event);
@@ -911,10 +1171,13 @@ class Reader extends Vue {
this.refreshBook();
break;
case 'KeyX':
this.historyToggle();
this.recentBooksToggle();
event.preventDefault();
event.stopPropagation();
break;
case 'KeyO':
this.offlineModeToggle();
break;
case 'KeyS':
this.settingsToggle();
break;
@@ -942,11 +1205,10 @@ class Reader extends Vue {
overflow-x: auto;
overflow-y: hidden;
}
.header {
display: flex;
justify-content: space-between;
min-width: 550px;
}
.el-main {
@@ -970,6 +1232,10 @@ class Reader extends Vue {
box-shadow: 3px 3px 5px black;
}
.tool-button + .tool-button {
margin: 0 2px 0 2px;
}
.tool-button:hover {
background-color: white;
}
@@ -1010,4 +1276,10 @@ i {
.clear {
color: rgba(0,0,0,0);
}
</style>
.clickable {
color: blue;
text-decoration: underline;
cursor: pointer;
}
</style>

View File

@@ -0,0 +1,321 @@
<template>
<Window width="600px" ref="window" @close="close">
<template slot="header">
<span v-show="!loading">Последние {{tableData ? tableData.length : 0}} открытых книг</span>
<span v-show="loading"><i class="el-icon-loading" style="font-size: 25px"></i> <span style="position: relative; top: -4px">Список загружается</span></span>
</template>
<el-table
:data="tableData"
style="width: 570px"
size="mini"
height="1px"
stripe
border
:default-sort = "{prop: 'touchDateTime', order: 'descending'}"
:header-cell-style = "headerCellStyle"
:row-key = "rowKey"
>
<el-table-column
type="index"
width="35px"
>
</el-table-column>
<el-table-column
prop="touchDateTime"
min-width="85px"
sortable
>
<template slot="header" slot-scope="scope"><!-- eslint-disable-line vue/no-unused-vars -->
<span style="font-size: 90%">Время<br>просм.</span>
</template>
<template slot-scope="scope"><!-- eslint-disable-line vue/no-unused-vars -->
<div class="desc" @click="loadBook(scope.row.url)">
{{ scope.row.touchDate }}<br>
{{ scope.row.touchTime }}
</div>
</template>
</el-table-column>
<el-table-column
>
<template slot="header" slot-scope="scope"><!-- eslint-disable-line vue/no-unused-vars -->
<!--el-input ref="input"
:value="search" @input="search = $event"
size="mini"
style="margin: 0; padding: 0; vertical-align: bottom; margin-top: 10px"
placeholder="Найти"/-->
<div class="el-input el-input--mini">
<input class="el-input__inner"
ref="input"
placeholder="Найти"
style="margin: 0; vertical-align: bottom; margin-top: 20px; padding: 0 10px 0 10px"
:value="search" @input="search = $event.target.value"
/>
</div>
</template>
<el-table-column
min-width="280px"
>
<template slot-scope="scope">
<div class="desc" @click="loadBook(scope.row.url)">
<span style="color: green">{{ scope.row.desc.author }}</span><br>
<span>{{ scope.row.desc.title }}</span>
</div>
</template>
</el-table-column>
<el-table-column
min-width="90px"
>
<template slot-scope="scope">
<a v-show="isUrl(scope.row.url)" :href="scope.row.url" target="_blank">Оригинал</a><br>
<a :href="scope.row.path" :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>
</template>
<script>
//-----------------------------------------------------------------------------
import Vue from 'vue';
import Component from 'vue-class-component';
import path from 'path';
import _ from 'lodash';
import * as utils 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 RecentBooksPage extends Vue {
loading = false;
search = null;
tableData = [];
created() {
}
init() {
this.$refs.window.init();
this.$nextTick(() => {
//this.$refs.input.focus();
});
(async() => {//отбражение подгрузки списка, иначе тормозит
if (this.initing)
return;
this.initing = true;
await this.updateTableData(3);
await utils.sleep(200);
if (bookManager.loaded) {
const t = Date.now();
await this.updateTableData(10);
if (bookManager.getSortedRecent().length > 10)
await utils.sleep(10*(Date.now() - t));
} else {
let i = 0;
let j = 5;
while (i < 500 && !bookManager.loaded) {
if (i % j == 0) {
bookManager.sortedRecentCached = null;
await this.updateTableData(100);
j *= 2;
}
await utils.sleep(100);
i++;
}
}
await this.updateTableData();
this.initing = false;
})();
}
rowKey(row) {
return row.key;
}
async updateTableData(limit) {
while (this.updating) await utils.sleep(100);
this.updating = true;
let result = [];
this.loading = !!limit;
const sorted = bookManager.getSortedRecent();
for (let i = 0; i < sorted.length; i++) {
const book = sorted[i];
if (book.deleted)
continue;
if (limit && result.length >= limit)
break;
let d = new Date();
d.setTime(book.touchTime);
const t = utils.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 = '';
if (fb2.author) {
const authorNames = fb2.author.map(a => _.compact([
a.lastName,
a.firstName,
a.middleName
]).join(' '));
author = authorNames.join(', ');
} else {
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,
});
if (result.length >= 100)
break;
}
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())
});
/*for (let i = 0; i < result.length; i++) {
if (!_.isEqual(this.tableData[i], result[i])) {
this.$set(this.tableData, i, result[i]);
await utils.sleep(10);
}
}
if (this.tableData.length > result.length)
this.tableData.splice(result.length);*/
this.tableData = result;
this.updating = false;
}
headerCellStyle(cell) {
let result = {margin: 0, padding: 0};
if (cell.columnIndex > 0) {
result['border-bottom'] = 0;
}
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();
if (!bookManager.mostRecentBook())
this.close();
}
loadBook(url) {
this.$emit('load-book', {url});
this.close();
}
isUrl(url) {
if (url)
return (url.indexOf('file://') != 0);
else
return false;
}
close() {
this.$emit('recent-books-toggle');
}
keyHook(event) {
if (event.type == 'keydown' && event.code == 'Escape') {
this.close();
}
return true;
}
}
//-----------------------------------------------------------------------------
</script>
<style scoped>
.desc {
cursor: pointer;
}
</style>

View File

@@ -1,28 +1,24 @@
<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 slot="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>
</Window>
<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>
</div>
</Window>
</template>
<script>
@@ -61,6 +57,8 @@ class SearchPage extends Vue {
}
async init(parsed) {
this.$refs.window.init();
if (this.parsed != parsed) {
this.initStep = true;
this.stopInit = false;
@@ -178,32 +176,13 @@ class SearchPage extends Vue {
</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 {
@@ -222,6 +201,7 @@ class SearchPage extends Vue {
.el-button {
padding: 9px 17px 9px 17px;
width: 55px;
}
i {

View File

@@ -13,7 +13,10 @@ import readerApi from '../../../api/reader';
import * as utils from '../../../share/utils';
import * as cryptoUtils from '../../../share/cryptoUtils';
const maxSetTries = 5;
import localForage from 'localforage';
const ssCacheStore = localForage.createInstance({
name: 'ssCacheStore'
});
export default @Component({
watch: {
@@ -37,6 +40,7 @@ export default @Component({
class ServerStorage extends Vue {
created() {
this.inited = false;
this.keyInited = false;
this.commit = this.$store.commit;
this.prevServerStorageKey = null;
this.$root.$on('generateNewServerStorageKey', () => {this.generateNewServerStorageKey()});
@@ -45,27 +49,54 @@ class ServerStorage extends Vue {
this.saveSettings();
}, 500);
this.debouncedNotifySuccess = _.debounce(() => {
this.success('Данные синхронизированы с сервером');
}, 1000);
this.oldProfiles = {};
this.oldSettings = {};
this.oldRecent = {};
this.oldRecentLast = {};
}
async init() {
try {
this.cachedRecent = await ssCacheStore.getItem('recent');
if (!this.cachedRecent)
await this.setCachedRecent({rev: 0, data: {}});
this.cachedRecentPatch = await ssCacheStore.getItem('recent-patch');
if (!this.cachedRecentPatch)
await this.setCachedRecentPatch({rev: 0, data: {}});
this.cachedRecentMod = await ssCacheStore.getItem('recent-mod');
if (!this.cachedRecentMod)
await this.setCachedRecentMod({rev: 0, data: {}});
if (!this.serverStorageKey) {
//генерируем новый ключ
await this.generateNewServerStorageKey();
} else {
await this.serverStorageKeyChanged();
}
this.oldRecent = _.cloneDeep(bookManager.recent);
this.oldRecentLast = _.cloneDeep(bookManager.recentLast) || {};
} 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 generateNewServerStorageKey() {
const key = utils.toBase58(utils.randomArray(32));
this.commit('reader/setServerStorageKey', key);
@@ -88,12 +119,13 @@ class ServerStorage extends Vue {
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.loadRecent(force);
if (force)
const loadSuccess = await this.loadRecent();
if (loadSuccess && force)
await this.saveRecent();
}
}
@@ -143,27 +175,23 @@ class ServerStorage extends Vue {
}
}
notifySuccess() {
this.success('Данные синхронизированы с сервером');
}
success(message) {
if (this.showServerStorageMessages)
this.$notify.success({message});
}
warning(message) {
if (this.showServerStorageMessages)
if (this.showServerStorageMessages && !this.offlineModeActive)
this.$notify.warning({message});
}
error(message) {
if (this.showServerStorageMessages)
if (this.showServerStorageMessages && !this.offlineModeActive)
this.$notify.error({message});
}
async loadSettings(force) {
if (!this.serverSyncEnabled || !this.currentProfile)
async loadSettings(force = false, doNotifySuccess = true) {
if (!this.keyInited || !this.serverSyncEnabled || !this.currentProfile)
return;
const setsId = `settings-${this.currentProfile}`;
@@ -199,14 +227,15 @@ class ServerStorage extends Vue {
this.commit('reader/setSettings', sets.data);
this.commit('reader/setSettingsRev', {[setsId]: sets.rev});
this.notifySuccess();
if (doNotifySuccess)
this.debouncedNotifySuccess();
} else {
this.warning(`Неверный ответ сервера: ${sets.state}`);
}
}
async saveSettings() {
if (!this.serverSyncEnabled || !this.currentProfile || this.savingSettings)
if (!this.keyInited || !this.serverSyncEnabled || !this.currentProfile || this.savingSettings)
return;
const diff = utils.getObjDiff(this.oldSettings, this.settings);
@@ -217,32 +246,18 @@ class ServerStorage extends Vue {
try {
const setsId = `settings-${this.currentProfile}`;
let result = {state: ''};
let tries = 0;
while (result.state != 'success' && tries < maxSetTries) {
const oldRev = this.settingsRev[setsId] || 0;
try {
result = await this.storageSet({[setsId]: {rev: oldRev + 1, data: this.settings}});
} catch(e) {
this.savingSettings = false;
this.error(`Ошибка соединения с сервером (${e.message}). Данные не сохранены и могут быть перезаписаны.`);
return;
}
if (result.state == 'reject') {
await this.loadSettings(true);
const newSettings = utils.applyObjDiff(this.settings, diff);
this.commit('reader/setSettings', newSettings);
}
tries++;
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 (tries >= maxSetTries) {
//отменять изменения не будем, просто предупредим
//this.commit('reader/setSettings', this.oldSettings);
console.error(result);
this.error('Не удалось отправить настройки на сервер. Данные не сохранены и могут быть перезаписаны.');
} else {
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});
}
@@ -251,8 +266,8 @@ class ServerStorage extends Vue {
}
}
async loadProfiles(force) {
if (!this.serverSyncEnabled)
async loadProfiles(force = false, doNotifySuccess = true) {
if (!this.keyInited || !this.serverSyncEnabled)
return;
const oldRev = this.profilesRev;
@@ -286,22 +301,24 @@ class ServerStorage extends Vue {
this.oldProfiles = _.cloneDeep(prof.data);
this.commit('reader/setProfiles', prof.data);
this.commit('reader/setProfilesRev', prof.rev);
this.checkCurrentProfile();
this.notifySuccess();
if (doNotifySuccess)
this.debouncedNotifySuccess();
} else {
this.warning(`Неверный ответ сервера: ${prof.state}`);
}
}
async saveProfiles() {
if (!this.serverSyncEnabled || this.savingProfiles)
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;
@@ -310,33 +327,16 @@ class ServerStorage extends Vue {
this.savingProfiles = true;
try {
let result = {state: ''};
let tries = 0;
while (result.state != 'success' && tries < maxSetTries) {
try {
result = await this.storageSet({profiles: {rev: this.profilesRev + 1, data: this.profiles}});
} catch(e) {
this.savingProfiles = false;
this.commit('reader/setProfiles', this.oldProfiles);
this.checkCurrentProfile();
this.error(`Ошибка соединения с сервером: (${e.message}). Изменения отменены.`);
return;
}
if (result.state == 'reject') {
await this.loadProfiles(true);
const newProfiles = utils.applyObjDiff(this.profiles, diff);
this.commit('reader/setProfiles', newProfiles);
}
tries++;
try {
result = await this.storageSet({profiles: {rev: this.profilesRev + 1, data: this.profiles}});
} catch(e) {
this.error(`Ошибка соединения с сервером (${e.message}). Данные не сохранены и могут быть перезаписаны.`);
}
if (tries >= maxSetTries) {
this.commit('reader/setProfiles', this.oldProfiles);
this.checkCurrentProfile();
console.error(result);
this.error('Не удалось отправить данные на сервер. Изменения отменены.');
} else {
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);
}
@@ -345,180 +345,193 @@ class ServerStorage extends Vue {
}
}
async loadRecent(force) {
if (!this.serverSyncEnabled)
async loadRecent(skipRevCheck = false, doNotifySuccess = true) {
if (!this.keyInited || !this.serverSyncEnabled || this.loadingRecent)
return;
const oldRev = bookManager.recentRev;
const oldLastRev = bookManager.recentLastRev;
//проверим ревизию на сервере
let revs = null;
if (!force) {
try {
revs = await this.storageCheck({recent: {}, recentLast: {}});
if (revs.state == 'success' && revs.items.recent.rev == oldRev &&
revs.items.recentLast.rev == oldLastRev) {
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;
}
} catch(e) {
this.error(`Ошибка соединения с сервером: ${e.message}`);
return;
}
}
if (force || revs.items.recent.rev != oldRev) {
let recent = null;
try {
recent = await this.storageGet({recent: {}});
recent = await this.storageGet(query);
} catch(e) {
this.error(`Ошибка соединения с сервером: ${e.message}`);
return;
}
if (recent.state == 'success') {
recent = recent.items.recent;
let newRecent = recent.items.recent;
let newRecentPatch = recent.items.recentPatch;
let newRecentMod = recent.items.recentMod;
if (recent.rev == 0)
recent.data = {};
if (!newRecent) {
newRecent = _.cloneDeep(this.cachedRecent);
}
if (!newRecentPatch) {
newRecentPatch = _.cloneDeep(this.cachedRecentPatch);
}
if (!newRecentMod) {
newRecentMod = _.cloneDeep(this.cachedRecentMod);
}
this.oldRecent = _.cloneDeep(recent.data);
await bookManager.setRecent(recent.data);
await bookManager.setRecentRev(recent.rev);
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, 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}`);
}
}
if (force || revs.items.recentLast.rev != oldLastRev) {
let recentLast = null;
try {
recentLast = await this.storageGet({recentLast: {}});
} catch(e) {
this.error(`Ошибка соединения с сервером: ${e.message}`);
return;
}
if (recentLast.state == 'success') {
recentLast = recentLast.items.recentLast;
if (recentLast.rev == 0)
recentLast.data = {};
this.oldRecentLast = _.cloneDeep(recentLast.data);
await bookManager.setRecentLast(recentLast.data);
await bookManager.setRecentLastRev(recentLast.rev);
} else {
this.warning(`Неверный ответ сервера: ${recentLast.state}`);
}
if (doNotifySuccess)
this.debouncedNotifySuccess();
} finally {
this.loadingRecent = false;
}
this.notifySuccess();
return true;
}
async saveRecent() {
if (!this.serverSyncEnabled || this.savingRecent)
return;
async saveRecent(itemKey, recurse) {
while (!this.inited || this.savingRecent)
await utils.sleep(100);
const bm = bookManager;
const diff = utils.getObjDiff(this.oldRecent, bm.recent);
if (utils.isEmptyObjDiff(diff))
if (!this.keyInited || !this.serverSyncEnabled || this.savingRecent)
return;
this.savingRecent = true;
try {
let result = {state: ''};
let tries = 0;
while (result.state != 'success' && tries < maxSetTries) {
try {
result = await this.storageSet({recent: {rev: bm.recentRev + 1, data: bm.recent}});
} catch(e) {
this.savingRecent = false;
this.error(`Ошибка соединения с сервером: (${e.message}). Изменения не сохранены.`);
return;
}
try {
const bm = bookManager;
if (result.state == 'reject') {
await this.loadRecent(true);
//похоже это лишнее
/*const newRecent = utils.applyObjDiff(bm.recent, diff);
await bm.setRecent(newRecent);*/
}
let needSaveRecent = false;
let needSaveRecentPatch = false;
let needSaveRecentMod = false;
tries++;
//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]);
let applyMod = this.cachedRecentMod.data;
if (applyMod && applyMod.key && newRecentPatch.data[applyMod.key])
newRecentPatch.data[applyMod.key] = utils.applyObjDiff(newRecentPatch.data[applyMod.key], applyMod.mod, true);
newRecentMod = {rev: this.cachedRecentMod.rev + 1, data: {}};
needSaveRecentPatch = true;
needSaveRecentMod = true;
}
if (tries >= maxSetTries) {
console.error(result);
this.error('Не удалось отправить данные на сервер. Данные не сохранены и могут быть перезаписаны.');
//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 {
this.oldRecent = _.cloneDeep(bm.recent);
await bm.setRecentRev(bm.recentRev + 1);
await this.saveRecentLast(true);
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;
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 saveRecentLast(force = false) {
if (!this.serverSyncEnabled || this.savingRecentLast)
return;
const bm = bookManager;
let recentLast = bm.recentLast;
recentLast = (recentLast ? recentLast : {});
let lastRev = bm.recentLastRev;
const diff = utils.getObjDiff(this.oldRecentLast, recentLast);
if (utils.isEmptyObjDiff(diff))
return;
this.savingRecentLast = true;
try {
let result = {state: ''};
let tries = 0;
while (result.state != 'success' && tries < maxSetTries) {
if (force) {
try {
const revs = await this.storageCheck({recentLast: {}});
if (revs.items.recentLast.rev)
lastRev = revs.items.recentLast.rev;
} catch(e) {
this.error(`Ошибка соединения с сервером: ${e.message}`);
return;
}
}
try {
result = await this.storageSet({recentLast: {rev: lastRev + 1, data: recentLast}}, force);
} catch(e) {
this.savingRecentLast = false;
this.error(`Ошибка соединения с сервером: (${e.message}). Изменения не сохранены.`);
return;
}
if (result.state == 'reject') {
await this.loadRecent(false);
this.savingRecentLast = false;//!!!
return;//!!!
}
tries++;
}
if (tries >= maxSetTries) {
console.error(result);
this.error('Не удалось отправить данные на сервер. Данные не сохранены и могут быть перезаписаны.');
} else {
this.oldRecentLast = _.cloneDeep(recentLast);
await bm.setRecentLastRev(lastRev + 1);
}
} finally {
this.savingRecentLast = false;
}
}
async storageCheck(items) {
return await this.storageApi('check', items);
}

View File

@@ -1,17 +1,13 @@
<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 slot="header">
Установить позицию
</template>
<div class="slider">
<el-slider v-model="sliderValue" :max="sliderMax" :format-tooltip="formatTooltip"></el-slider>
</div>
</Window>
<div class="slider">
<el-slider v-model="sliderValue" :max="sliderMax" :format-tooltip="formatTooltip"></el-slider>
</div>
</div>
</Window>
</template>
<script>
@@ -43,6 +39,8 @@ class SetPositionPage extends Vue {
}
init(sliderValue, sliderMax) {
this.$refs.window.init();
this.sliderMax = sliderMax;
this.sliderValue = sliderValue;
this.initialized = true;
@@ -70,26 +68,6 @@ class SetPositionPage extends Vue {
</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;

File diff suppressed because it is too large Load Diff

View File

@@ -317,4 +317,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

@@ -19,11 +19,10 @@
</div>
<div v-show="clickControl" ref="layoutEvents" class="layout events" @mousedown.prevent.stop="onMouseDown" @mouseup.prevent.stop="onMouseUp"
@wheel.prevent.stop="onMouseWheel"
@touchstart.stop="onTouchStart" @touchend.stop="onTouchEnd" @touchcancel.prevent.stop="onTouchCancel"
@touchstart.stop="onTouchStart" @touchend.stop="onTouchEnd" @touchmove.stop="onTouchMove" @touchcancel.prevent.stop="onTouchCancel"
oncontextmenu="return false;">
<div v-show="showStatusBar" v-html="statusBarClickable" @mousedown.prevent.stop @touchstart.stop
@click.prevent.stop="onStatusBarClick"></div>
<div v-show="fontsLoading" ref="fontsLoading"></div>
</div>
<div v-show="!clickControl && showStatusBar" class="layout" v-html="statusBarClickable" @mousedown.prevent.stop @touchstart.stop
@click.prevent.stop="onStatusBarClick"></div>
@@ -77,7 +76,6 @@ class TextPage extends Vue {
page2 = null;
statusBar = null;
statusBarClickable = null;
fontsLoading = null;
lastBook = null;
bookPos = 0;
@@ -133,7 +131,6 @@ class TextPage extends Vue {
}, 10);
this.$root.$on('resize', () => {this.$nextTick(this.onResize)});
this.mobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent);
}
mounted() {
@@ -171,6 +168,12 @@ class TextPage extends Vue {
this.fontShift = this.fontVertShift/100;
this.textShift = this.textVertShift/100 + this.fontShift;
//statusBar
this.$refs.statusBar.style.left = '0px';
this.$refs.statusBar.style.top = (this.statusBarTop ? 1 : this.realHeight - this.statusBarHeight) + 'px';
this.statusBarColor = this.hex2rgba(this.textColor || '#000000', this.statusBarColorAlpha);
//drawHelper
this.drawHelper.realWidth = this.realWidth;
this.drawHelper.realHeight = this.realHeight;
@@ -196,14 +199,8 @@ class TextPage extends Vue {
this.drawHelper.lineHeight = this.lineHeight;
this.drawHelper.context = this.context;
//сообщение "Загрузка шрифтов..."
const flText = 'Загрузка шрифта...';
this.$refs.fontsLoading.innerHTML = flText;
const fontsLoadingStyle = this.$refs.fontsLoading.style;
fontsLoadingStyle.position = 'absolute';
fontsLoadingStyle.fontSize = this.fontSize + 'px';
fontsLoadingStyle.top = (this.realHeight/2 - 2*this.fontSize) + 'px';
fontsLoadingStyle.left = (this.realWidth - this.drawHelper.measureText(flText, {}))/2 + 'px';
//statusBar
this.statusBarClickable = this.drawHelper.statusBarClickable(this.statusBarTop, this.statusBarHeight);
//parsed
if (this.parsed) {
@@ -223,15 +220,9 @@ class TextPage extends Vue {
this.parsed.showInlineImagesInCenter = this.showInlineImagesInCenter;
this.parsed.imageHeightLines = this.imageHeightLines;
this.parsed.imageFitWidth = this.imageFitWidth;
this.parsed.compactTextPerc = this.compactTextPerc;
}
//statusBar
this.$refs.statusBar.style.left = '0px';
this.$refs.statusBar.style.top = (this.statusBarTop ? 1 : this.realHeight - this.statusBarHeight) + 'px';
this.statusBarColor = this.hex2rgba(this.textColor || '#000000', this.statusBarColorAlpha);
this.statusBarClickable = this.drawHelper.statusBarClickable(this.statusBarTop, this.statusBarHeight);
//scrolling page
const pageSpace = this.scrollHeight - this.pageLineCount*this.lineHeight;
let y = pageSpace/2;
@@ -239,6 +230,10 @@ class TextPage extends Vue {
y += this.statusBarHeight*(this.statusBarTop ? 1 : 0);
let page1 = this.$refs.scrollBox1;
let page2 = this.$refs.scrollBox2;
page1.style.perspective = '3072px';
page2.style.perspective = '3072px';
page1.style.width = this.w + this.indentLR + 'px';
page2.style.width = this.w + this.indentLR + 'px';
page1.style.height = this.scrollHeight - (pageSpace > 0 ? pageSpace : 0) + 'px';
@@ -268,6 +263,18 @@ class TextPage extends Vue {
async loadFonts() {
this.fontsLoading = true;
let inst = null;
(async() => {
await sleep(500);
if (this.fontsLoading)
inst = this.$notify({
title: '',
dangerouslyUseHTMLString: true,
message: 'Загрузка шрифта &nbsp;<i class="el-icon-loading"></i>',
duration: 0
});
})();
if (!this.fontsLoaded)
this.fontsLoaded = {};
//загрузка дин.шрифта
@@ -298,6 +305,8 @@ class TextPage extends Vue {
}
this.fontsLoading = false;
if (inst)
inst.close();
}
getSettings() {
@@ -468,7 +477,7 @@ class TextPage extends Vue {
generateWaitingFunc(waitingHandlerName, stopPropertyName) {
const func = (timeout) => {
return new Promise(async(resolve) => {
return new Promise((resolve, reject) => { (async() => {
this[waitingHandlerName] = resolve;
let wait = (timeout + 201)/100;
while (wait > 0 && !this[stopPropertyName]) {
@@ -476,7 +485,7 @@ class TextPage extends Vue {
await sleep(100);
}
resolve();
});
})().catch(reject); });
};
return func;
}
@@ -625,7 +634,7 @@ class TextPage extends Vue {
const animation1Finish = this.generateWaitingFunc('resolveAnimation1Finish', 'stopAnimation');
const animation2Finish = this.generateWaitingFunc('resolveAnimation2Finish', 'stopAnimation');
const transition1Finish = this.generateWaitingFunc('resolveTransition1Finish', 'stopAnimation');
//const transition2Finish = this.generateWaitingFunc('resolveTransition2Finish', 'stopAnimation');
const transition2Finish = this.generateWaitingFunc('resolveTransition2Finish', 'stopAnimation');
const duration = Math.round(3000*(1 - this.pageChangeAnimationSpeed/100));
let page1 = this.$refs.scrollingPage1;
@@ -654,6 +663,14 @@ class TextPage extends Vue {
page1.style.height = this.scrollHeight + this.lineHeight + 'px';
page2.style.height = this.scrollHeight + this.lineHeight + 'px';
break;
case 'rotate':
await this.drawHelper.doPageAnimationRotate(page1, page2,
duration, this.pageChangeDirectionDown, transition1Finish, transition2Finish);
break;
case 'flip':
await this.drawHelper.doPageAnimationFlip(page1, page2,
duration, this.pageChangeDirectionDown, transition1Finish, transition2Finish, this.backgroundColor);
break;
}
this.resolveAnimation1Finish = null;
@@ -860,6 +877,14 @@ class TextPage extends Vue {
this.$emit('tool-bar-toggle');
}
doScrollingToggle() {
this.$emit('scrolling-toggle');
}
doFullScreenToggle() {
this.$emit('full-screen-toogle');
}
async doFontSizeInc() {
if (!this.settingsChanging) {
this.settingsChanging = true;
@@ -951,7 +976,7 @@ class TextPage extends Vue {
case 'Enter':
case 'Backquote'://`
case 'KeyF':
this.$emit('full-screen-toogle');
this.doFullScreenToggle();
break;
case 'Tab':
case 'KeyQ':
@@ -989,57 +1014,100 @@ class TextPage extends Vue {
}
onTouchStart(event) {
if (!this.mobile)
if (!this.$isMobileDevice)
return;
this.endClickRepeat();
if (event.touches.length == 1) {
const touch = event.touches[0];
const rect = event.target.getBoundingClientRect();
const x = touch.pageX - rect.left;
const y = touch.pageY - rect.top;
if (this.handleClick(x, y)) {
this.repDoing = true;
this.debouncedStartClickRepeat(x, y);
const hc = this.handleClick(x, y, new Set(['Menu']));
if (hc) {
if (hc != 'Menu') {
this.repDoing = true;
this.debouncedStartClickRepeat(x, y);
} else {
this.startTouch = {x, y};
}
}
}
}
onTouchEnd() {
if (!this.mobile)
onTouchMove(event) {
if (this.startTouch) {
event.preventDefault();
}
}
onTouchEnd(event) {
if (!this.$isMobileDevice)
return;
this.endClickRepeat();
if (event.changedTouches.length == 1) {
const touch = event.changedTouches[0];
const rect = event.target.getBoundingClientRect();
const x = touch.pageX - rect.left;
const y = touch.pageY - rect.top;
if (this.startTouch) {
const dy = this.startTouch.y - y;
const dx = this.startTouch.x - x;
const moveDelta = 30;
const touchDelta = 15;
if (dy > 0 && Math.abs(dy) >= moveDelta && Math.abs(dy) > Math.abs(dx)) {
//движение вверх
this.doFullScreenToggle();
} else if (dy < 0 && Math.abs(dy) >= moveDelta && Math.abs(dy) > Math.abs(dx)) {
//движение вниз
this.doScrollingToggle();
} else if (dx > 0 && Math.abs(dx) >= moveDelta && Math.abs(dy) < Math.abs(dx)) {
//движение влево
this.doScrollingSpeedDown();
} else if (dx < 0 && Math.abs(dx) >= moveDelta && Math.abs(dy) < Math.abs(dx)) {
//движение вправо
this.doScrollingSpeedUp();
} else if (Math.abs(dy) < touchDelta && Math.abs(dx) < touchDelta) {
this.doToolBarToggle();
}
this.startTouch = null;
}
}
}
onTouchCancel() {
if (!this.mobile)
if (!this.$isMobileDevice)
return;
this.endClickRepeat();
}
onMouseDown(event) {
if (this.mobile)
if (this.$isMobileDevice)
return;
this.endClickRepeat();
if (event.button == 0) {
if (this.handleClick(event.offsetX, event.offsetY)) {
const hc = this.handleClick(event.offsetX, event.offsetY);
if (hc && hc != 'Menu') {
this.repDoing = true;
this.debouncedStartClickRepeat(event.offsetX, event.offsetY);
}
} else if (event.button == 1) {
this.$emit('scrolling-toggle');
this.doScrollingToggle();
} else if (event.button == 2) {
this.doToolBarToggle();
}
}
onMouseUp() {
if (this.mobile)
if (this.$isMobileDevice)
return;
this.endClickRepeat();
}
onMouseWheel(event) {
if (this.mobile)
if (this.$isMobileDevice)
return;
if (event.deltaY > 0) {
this.doDown();
@@ -1057,7 +1125,7 @@ class TextPage extends Vue {
}
}
handleClick(pointX, pointY) {
getClickAction(pointX, pointY) {
const w = pointX/this.realWidth*100;
const h = pointY/this.realHeight*100;
@@ -1073,27 +1141,35 @@ class TextPage extends Vue {
}
}
switch (action) {
case 'Down' ://Down
this.doDown();
break;
case 'Up' ://Up
this.doUp();
break;
case 'PgDown' ://PgDown
this.doPageDown();
break;
case 'PgUp' ://PgUp
this.doPageUp();
break;
case 'Menu' :
this.doToolBarToggle();
break;
default :
// Nothing
return action;
}
handleClick(pointX, pointY, exclude) {
const action = this.getClickAction(pointX, pointY);
if (!exclude || !exclude.has(action)) {
switch (action) {
case 'Down' ://Down
this.doDown();
break;
case 'Up' ://Up
this.doUp();
break;
case 'PgDown' ://PgDown
this.doPageDown();
break;
case 'PgUp' ://PgUp
this.doPageUp();
break;
case 'Menu' :
this.doToolBarToggle();
break;
default :
// Nothing
}
}
return (action && action != 'Menu');
return action;
}
}
@@ -1120,6 +1196,10 @@ class TextPage extends Vue {
overflow: hidden;
}
.on-top {
z-index: 100;
}
.back {
z-index: 5;
}
@@ -1185,4 +1265,5 @@ class TextPage extends Vue {
0% { opacity: 1; }
100% { opacity: 0; }
}
</style>

View File

@@ -1,5 +1,5 @@
import he from 'he';
import sax from '../../../../server/core/BookConverter/sax';
import sax from '../../../../server/core/sax';
import {sleep} from '../../../share/utils';
const maxImageLineCount = 100;
@@ -67,7 +67,7 @@ export default class BookParser {
}
*/
const getImageDimensions = (binaryId, binaryType, data) => {
return new Promise (async(resolve, reject) => {
return new Promise ((resolve, reject) => { (async() => {
const i = new Image();
let resolved = false;
i.onload = () => {
@@ -81,19 +81,17 @@ export default class BookParser {
resolve();
};
i.onerror = (e) => {
reject(e);
};
i.onerror = reject;
i.src = `data:${binaryType};base64,${data}`;
await sleep(30*1000);
if (!resolved)
reject('Не удалось получить размер изображения');
});
})().catch(reject); });
};
const getExternalImageDimensions = (src) => {
return new Promise (async(resolve, reject) => {
return new Promise ((resolve, reject) => { (async() => {
const i = new Image();
let resolved = false;
i.onload = () => {
@@ -105,15 +103,13 @@ export default class BookParser {
resolve();
};
i.onerror = (e) => {
reject(e);
};
i.onerror = reject;
i.src = src;
await sleep(30*1000);
if (!resolved)
reject('Не удалось получить размер изображения');
});
})().catch(reject); });
};
const newParagraph = (text, len, addIndex) => {
@@ -179,7 +175,7 @@ export default class BookParser {
if (tag == 'binary') {
let attrs = sax.getAttrsSync(tail);
binaryType = (attrs['content-type'] && attrs['content-type'].value ? attrs['content-type'].value : '');
if (binaryType == 'image/jpeg' || binaryType == 'image/png')
if (binaryType == 'image/jpeg' || binaryType == 'image/png' || binaryType == 'application/octet-stream')
binaryId = (attrs.id.value ? attrs.id.value : '');
}
@@ -620,7 +616,8 @@ export default class BookParser {
para.parsed.addEmptyParagraphs === this.addEmptyParagraphs &&
para.parsed.showImages === this.showImages &&
para.parsed.imageHeightLines === this.imageHeightLines &&
para.parsed.imageFitWidth === this.imageFitWidth
para.parsed.imageFitWidth === this.imageFitWidth &&
para.parsed.compactTextPerc === this.compactTextPerc
)
return para.parsed;
@@ -635,6 +632,7 @@ export default class BookParser {
showImages: this.showImages,
imageHeightLines: this.imageHeightLines,
imageFitWidth: this.imageFitWidth,
compactTextPerc: this.compactTextPerc,
visible: !(
(this.cutEmptyParagraphs && para.cut) ||
(para.addIndex > this.addEmptyParagraphs)
@@ -665,6 +663,7 @@ export default class BookParser {
let style = {};
let ofs = 0;//смещение от начала параграфа para.offset
let imgW = 0;
const compactWidth = this.measureText('W', {})*this.compactTextPerc/100;
// тут начинается самый замес, перенос по слогам и стилизация, а также изображения
for (const part of parts) {
style = part.style;
@@ -749,7 +748,7 @@ export default class BookParser {
p = (style.space ? p + parsed.p*style.space : p);
let w = this.measureText(str, style) + p;
let wordTail = word;
if (w > parsed.w && prevStr != '') {
if (w > parsed.w + compactWidth && prevStr != '') {
if (parsed.wordWrap) {//по слогам
let slogi = this.splitToSlogi(word);
@@ -762,7 +761,7 @@ export default class BookParser {
for (let k = 0; k < slogiLen - 1; k++) {
let slog = slogi[0];
let ww = this.measureText(s + slog + (slog[slog.length - 1] == '-' ? '' : '-'), style) + p;
if (ww <= parsed.w) {
if (ww <= parsed.w + compactWidth) {
s += slog;
ss += slog;
} else

View File

@@ -18,48 +18,38 @@ const bmRecentStore = localForage.createInstance({
name: 'bmRecentStore'
});
const bmCacheStore = localForage.createInstance({
name: 'bmCacheStore'
});
class BookManager {
async init(settings) {
this.loaded = false;
this.settings = settings;
this.eventListeners = [];
this.books = {};
this.recent = {};
//bmCacheStore нужен только для ускорения загрузки читалки
this.booksCached = await bmCacheStore.getItem('books');
if (!this.booksCached)
this.booksCached = {};
this.recent = await bmCacheStore.getItem('recent');
this.recentLast = await bmCacheStore.getItem('recent-last');
if (this.recentLast)
this.recentLast = await bmRecentStore.getItem('recent-last');
if (this.recentLast) {
this.recent[this.recentLast.key] = this.recentLast;
this.recentRev = await bmRecentStore.getItem('recent-rev') || 0;
this.recentLastRev = await bmRecentStore.getItem('recent-last-rev') || 0;
this.books = Object.assign({}, this.booksCached);
this.recentChanged2 = true;
if (!this.books || !this.recent) {
this.books = {};
this.recent = {};
await this.loadMeta(true);
} else {
this.loadMeta(false);
const meta = await bmMetaStore.getItem(`bmMeta-${this.recentLast.key}`);
if (_.isObject(meta)) {
this.books[meta.key] = meta;
}
}
this.recentChanged = true;
this.loadStored();//no await
}
//долгая загрузка из хранилища,
//хранение в отдельных записях дает относительно
//нормальное поведение при нескольких вкладках с читалкой в браузере
async loadMeta(immediate) {
if (!immediate)
await utils.sleep(2000);
//Долгая асинхронная загрузка из хранилища.
//Хранение в отдельных записях дает относительно
//нормальное поведение при нескольких вкладках с читалкой в браузере.
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('-');
@@ -67,6 +57,7 @@ class BookManager {
let meta = await bmMetaStore.getItem(key);
if (_.isObject(meta)) {
//уже может быть распарсена книга
const oldBook = this.books[meta.key];
this.books[meta.key] = meta;
@@ -81,7 +72,7 @@ class BookManager {
let key = null;
len = await bmRecentStore.length();
for (let i = 0; i < len; i++) {
for (let i = len - 1; i >= 0; i--) {
key = await bmRecentStore.key(i);
if (key) {
let r = await bmRecentStore.getItem(key);
@@ -97,21 +88,16 @@ class BookManager {
/*if (key) {
for (let i = 0; i < 1000; i++) {
const k = this.keyFromUrl(i.toString());
this.recent[k] = Object.assign({}, _.cloneDeep(this.recent[key]), {key: k, touchTime: Date.now() - 1000000});
this.recent[k] = Object.assign({}, _.cloneDeep(this.recent[key]), {key: k, touchTime: Date.now() - 1000000, url: utils.randomHexString(300)});
}
}*/
await this.cleanBooks();
await this.cleanRecentBooks();
//очистка позже
//await this.cleanRecentBooks();
this.booksCached = {};
for (const key in this.books) {
this.booksCached[key] = this.metaOnly(this.books[key]);
}
await bmCacheStore.setItem('books', this.booksCached);
await bmCacheStore.setItem('recent', this.recent);
this.emit('load-meta-finish');
this.recentChanged = true;
this.loaded = true;
this.emit('load-stored-finish');
}
async cleanBooks() {
@@ -131,22 +117,93 @@ class BookManager {
}
if (size > maxDataSize && toDel) {
await this._delBook(toDel);
await this.delBook(toDel);
} else {
break;
}
}
}
async addBook(newBook, callback) {
if (!this.books)
await this.init();
async deflateWithProgress(data, callback) {
const chunkSize = 128*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 = 64*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 cb = (perc) => {
const p = Math.round(80*perc/100);
const p = Math.round(30*perc/100);
callback(p);
};
const cb2 = (perc) => {
const p = Math.round(30 + 65*perc/100);
callback(p);
};
@@ -155,53 +212,80 @@ class BookManager {
let data = newBook.data;
if (result.dataCompressed) {
data = utils.pako.deflate(data, {level: 9});
//data = utils.pako.deflate(data, {level: 5});
data = await this.deflateWithProgress(data, cb2);
result.dataCompressedLength = data.byteLength;
}
callback(90);
callback(95);
this.books[meta.key] = result;
this.booksCached[meta.key] = this.metaOnly(result);
await bmMetaStore.setItem(`bmMeta-${meta.key}`, this.metaOnly(result));
await bmDataStore.setItem(`bmData-${meta.key}`, data);
await bmCacheStore.setItem('books', this.booksCached);
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 bmDataStore.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 = await bmDataStore.getItem(`bmMeta-${meta.key}`);
if (result)
this.books[meta.key] = result;
}
//Если файл на сервере изменился, считаем, что в кеше его нету
if (meta.path && result && meta.path != result.path) {
return;
}
if (result && !result.parsed) {
let data = await bmDataStore.getItem(`bmData-${meta.key}`);
callback(10);
callback(5);
await utils.sleep(10);
let cb = (perc) => {
const p = 5 + Math.round(15*perc/100);
callback(p);
};
if (result.dataCompressed) {
data = utils.pako.inflate(data, {to: 'string'});
try {
//data = utils.pako.inflate(data, {to: 'string'});
data = await this.inflateWithProgress(data, cb);
} catch (e) {
this.delBook(meta);
throw e;
}
}
callback(20);
const cb = (perc) => {
cb = (perc) => {
const p = 20 + Math.round(80*perc/100);
callback(p);
};
@@ -213,27 +297,14 @@ class BookManager {
return result;
}
async _delBook(meta) {
async delBook(meta) {
await bmMetaStore.removeItem(`bmMeta-${meta.key}`);
await bmDataStore.removeItem(`bmData-${meta.key}`);
delete this.books[meta.key];
delete this.booksCached[meta.key];
}
async delBook(meta) {
if (!this.books)
await this.init();
await this._delBook(meta);
await bmCacheStore.setItem('books', this.booksCached);
}
async parseBook(meta, data, callback) {
if (!this.books)
await this.init();
const parsed = new BookParser(this.settings);
const parsedMeta = await parsed.parse(data, callback);
@@ -257,9 +328,8 @@ class BookManager {
return utils.stringToHex(url);
}
//-- recent --------------------------------------------------------------
async setRecentBook(value) {
if (!this.recent)
await this.init();
const result = this.metaOnly(value);
result.touchTime = Date.now();
result.deleted = 0;
@@ -276,67 +346,59 @@ class BookManager {
await bmRecentStore.setItem(result.key, result);
//кэшируем, аккуратно
let saveRecent = false;
if (!(this.recentLast && this.recentLast.key == result.key)) {
await bmCacheStore.setItem('recent', this.recent);
saveRecent = true;
}
this.recentLast = result;
await bmCacheStore.setItem('recent-last', this.recentLast);
await bmRecentStore.setItem('recent-last', this.recentLast);
this.mostRecentCached = result;
this.recentChanged2 = true;
if (saveRecent)
this.emit('save-recent');
this.emit('recent-changed');
this.recentChanged = true;
this.emit('recent-changed', result.key);
return result;
}
async getRecentBook(value) {
if (!this.recent)
await this.init();
return this.recent[value.key];
let result = this.recent[value.key];
if (!result) {
result = await bmRecentStore.getItem(value.key);
if (result)
this.recent[value.key] = result;
}
return result;
}
async delRecentBook(value) {
if (!this.recent)
await this.init();
this.recent[value.key].deleted = 1;
await bmRecentStore.setItem(value.key, this.recent[value.key]);
await bmCacheStore.setItem('recent', this.recent);
this.mostRecentCached = null;
this.recentChanged2 = true;
this.emit('save-recent');
if (this.recentLast.key == value.key) {
this.recentLast = null;
await bmRecentStore.setItem('recent-last', this.recentLast);
}
this.emit('recent-deleted', value.key);
this.emit('recent-changed', value.key);
}
async cleanRecentBooks() {
if (!this.recent)
await this.init();
const sorted = this.getSortedRecent();
let isDel = false;
for (let i = 1000; i < sorted.length; i++) {
await bmRecentStore.removeItem(sorted[i].key);
delete this.recent[sorted[i].key];
await bmRecentStore.removeItem(sorted[i].key);
isDel = true;
}
this.sortedRecentCached = null;
await bmCacheStore.setItem('recent', this.recent);
if (isDel)
this.emit('recent-changed');
return isDel;
}
mostRecentBook() {
if (this.mostRecentCached) {
return this.mostRecentCached;
if (this.recentLast) {
return this.recentLast;
}
const oldRecentLast = this.recentLast;
let max = 0;
let result = null;
@@ -347,12 +409,17 @@ class BookManager {
result = book;
}
}
this.mostRecentCached = result;
this.recentLast = result;
bmRecentStore.setItem('recent-last', this.recentLast);//no await
if (this.recentLast !== oldRecentLast)
this.emit('recent-changed');
return result;
}
getSortedRecent() {
if (!this.recentChanged2 && this.sortedRecentCached) {
if (!this.recentChanged && this.sortedRecentCached) {
return this.sortedRecentCached;
}
@@ -361,7 +428,7 @@ class BookManager {
result.sort((a, b) => b.touchTime - a.touchTime);
this.sortedRecentCached = result;
this.recentChanged2 = false;
this.recentChanged = false;
return result;
}
@@ -369,50 +436,33 @@ class BookManager {
const mergedRecent = _.cloneDeep(this.recent);
Object.assign(mergedRecent, value);
const newRecent = {};
for (const rec of Object.values(mergedRecent)) {
if (rec.key) {
await bmRecentStore.setItem(rec.key, rec);
newRecent[rec.key] = rec;
}
}
this.recent = newRecent;
await bmCacheStore.setItem('recent', this.recent);
//подстраховка от hotReload
for (let i of Object.keys(mergedRecent)) {
if (!mergedRecent[i].key || mergedRecent[i].key !== i)
delete mergedRecent[i];
}
//"ленивое" обновление хранилища
(async() => {
for (const rec of Object.values(mergedRecent)) {
if (rec.key) {
await bmRecentStore.setItem(rec.key, rec);
await utils.sleep(1);
}
}
})();
this.recent = mergedRecent;
this.recentLast = null;
await bmCacheStore.setItem('recent-last', this.recentLast);
await bmRecentStore.setItem('recent-last', this.recentLast);
this.mostRecentCached = null;
this.recentChanged = true;
this.emit('set-recent');
this.emit('recent-changed');
}
async setRecentRev(value) {
await bmRecentStore.setItem('recent-rev', value);
this.recentRev = value;
}
async setRecentLast(value) {
if (!value.key)
value = null;
this.recentLast = value;
await bmCacheStore.setItem('recent-last', this.recentLast);
if (value && value.key) {
this.recent[value.key] = value;
await bmRecentStore.setItem(value.key, value);
await bmCacheStore.setItem('recent', this.recent);
}
this.mostRecentCached = null;
this.emit('recent-changed');
}
async setRecentLastRev(value) {
bmRecentStore.setItem('recent-last-rev', value);
this.recentLastRev = value;
}
addEventListener(listener) {
if (this.eventListeners.indexOf(listener) < 0)
this.eventListeners.push(listener);
@@ -425,8 +475,12 @@ class BookManager {
}
emit(eventName, value) {
for (const listener of this.eventListeners)
listener(eventName, value);
if (this.eventListeners) {
for (const listener of this.eventListeners) {
//console.log(eventName);
listener(eventName, value);
}
}
}
}

View File

@@ -0,0 +1,206 @@
export const versionHistory = [
{
showUntil: '2019-11-10',
header: '0.7.7 (2019-11-06)',
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>
`
},
{
showUntil: '2019-10-29',
header: '0.7.6 (2019-10-30)',
content:
`
<ul>
<li>исправления багов</li>
</ul>
`
},
{
showUntil: '2019-10-21',
header: '0.7.5 (2019-10-22)',
content:
`
<ul>
<li>исправления багов</li>
</ul>
`
},
{
showUntil: '2019-10-17',
header: '0.7.3 (2019-10-18)',
content:
`
<ul>
<li>внутренние переделки механизма синхронизации с сервером</li>
<li>добавлен html-фильтр для сайтов www.fanfiction.net, archiveofourown.org</li>
<li>добавлен параметр "Включить html-фильтр для сайтов" в раздел "Вид"->"Текст" в настройках</li>
<li>исправления багов</li>
</ul>
`
},
{
showUntil: '2019-09-19',
header: '0.7.1 (2019-09-20)',
content:
`
<ul>
<li>исправления багов</li>
<li>на панель управления добавлена кнопка "Автономный режим"</li>
<li>актуализирована справка</li>
</ul>
`
},
{
showUntil: '2019-10-01',
header: '0.7.0 (2019-09-07)',
content:
`
<ul>
<li>налажена работа https-версии сайта, рекомендуется плавный переход</li>
<li>добавлена возможность загрузки и работы https-версии читалки в оффлайн-режиме (при отсутствии интернета)</li>
<li>упрощение механизма серверной синхронизации с целью повышения надежности и избавления от багов</li>
<li>окна теперь можно перемещать за заголовок</li>
<li>немного улучшен внешний вид и управление на смартфонах</li>
<li>добавлен параметр "Компактность" в раздел "Вид"->"Текст" в настройках</li>
</ul>
`
},
{
showUntil: '2019-07-20',
header: '0.6.10 (2019-07-21)',
content:
`
<ul>
<li>исправления багов</li>
</ul>
`
},
{
showUntil: '2019-06-22',
header: '0.6.9 (2019-06-23)',
content:
`
<ul>
<li>исправлен баг - падение сервера при распаковке битых архивов книг</li>
<li>исправлен баг - не распознавались некоторые книги формата fb2 в кодировке utf8</li>
<li>добавлены новые варианты анимации перелистывания</li>
<li>на страницу загрузки добавлен блок "Поделиться"</li>
<li>улучшены прогрессбары</li>
<li>исправления недочетов, небольшие оптимизации</li>
</ul>
`
},
{
showUntil: '2019-06-05',
header: '0.6.7 (2019-05-30)',
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>
`
},
{
showUntil: '2019-03-29',
header: '0.6.6 (2019-03-29)',
content:
`
<ul>
<li>в справку добавлено описание настройки браузеров для автономной работы читалки (без доступа к интернету)</li>
<li>оптимизации процесса синхронизации, внутренние переделки</li>
</ul>
`
},
{
showUntil: '2019-03-24',
header: '0.6.4 (2019-03-24)',
content:
`
<ul>
<li>исправления багов, оптимизации</li>
<li>добавлена возможность синхронизации данных между устройствами</li>
</ul>
`
},
{
showUntil: '2019-03-04',
header: '0.5.4 (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>
`
},
{
showUntil: '2019-02-17',
header: '0.3.0 (2019-02-17)',
content:
`
<ul>
<li>поправки багов</li>
<li>улучшено распознавание текста</li>
<li>изменена верстка страницы - убрано позиционирование каждого слова</li>
</ul>
`
},
{
showUntil: '2019-02-14',
header: '0.1.7 (2019-02-14)',
content:
`
<ul>
<li>увеличены верхние границы отступов и др.размеров</li>
<li>добавлена настройка для удаления/вставки пустых параграфов</li>
<li>добавлена настройка включения/отключения управления кликом</li>
<li>добавлена возможность сброса настроек</li>
<li>убран автоматический редирект на последнюю загруженную книгу, если не задан url в маршруте</li>
</ul>
`
},
{
showUntil: '2019-02-12',
header: '0.1.0 (2019-02-12)',
content:
`
<ul>
<li>первый деплой проекта, длительность разработки - 2 месяца</li>
</ul>
`
},
]

View File

@@ -1,10 +1,15 @@
<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" @click="close" @mouseup="onMouseUp" @mousemove="onMouseMove">
<div ref="windowBox" class="windowBox" @click.stop>
<div class="window">
<div ref="header" class="header" @mousedown.prevent.stop="onMouseDown"
@touchstart.stop="onTouchStart" @touchend.stop="onTouchEnd" @touchmove.stop="onTouchMove">
<span class="header-text"><slot name="header"></slot></span>
<span class="close-button" @mousedown.stop @click="close"><i class="el-icon-close"></i></span>
</div>
<slot></slot>
</div>
</div>
<slot></slot>
</div>
</template>
@@ -14,17 +19,116 @@ import Vue from 'vue';
import Component from 'vue-class-component';
export default @Component({
props: {
height: { type: String, default: '100%' },
width: { type: String, default: '100%' },
maxWidth: { type: String, default: '' },
topShift: { type: Number, default: 0 },
}
})
class Window extends Vue {
close() {
this.$emit('close');
init() {
this.$nextTick(() => {
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';
});
}
onMouseDown(event) {
if (this.$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.$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.$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.$isMobileDevice)
return;
this.$refs.header.style.cursor = 'default';
this.moving = false;
}
close() {
if (!this.moving)
this.$emit('close');
}
}
//-----------------------------------------------------------------------------
</script>
<style scoped>
.main {
position: absolute;
width: 100%;
height: 100%;
z-index: 50;
}
.windowBox {
position: absolute;
display: flex;
height: 100%;
width: 100%;
}
.window {
flex: 1;
display: flex;
@@ -39,9 +143,9 @@ class Window extends Vue {
.header {
display: flex;
justify-content: flex-end;
background-color: #e5e7ea;
background-color: #59B04F;
align-items: center;
height: 40px;
height: 30px;
}
.header-text {
@@ -54,8 +158,12 @@ class Window extends Vue {
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

@@ -9,104 +9,44 @@ Vue.use(ElementUI, { locale });
*/
//------------------------------------------------------
//import './theme/index.css';
import './theme/icon.css';
import './theme/tooltip.css';
import './theme/index.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 ElDialog from 'element-ui/lib/dialog';
//import './theme/dialog.css';
import ElDialog from 'element-ui/lib/dialog';
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';
//import Message from 'element-ui/lib/message';
//import './theme/message.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,
ElColorPicker, ElDialog,
};
for (let name in components) {

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html manifest="/app/manifest.appcache">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
@@ -9,5 +9,6 @@
</head>
<body>
<div id="app"></div>
<script src="https://yastatic.net/share2/share.js" async="async"></script>
</body>
</html>

View File

@@ -1,11 +1,12 @@
import Vue from 'vue';
import App from './components/App.vue';
import router from './router';
import store from './store';
import './element';
import App from './components/App.vue';
//Vue.config.productionTip = false;
Vue.prototype.$isMobileDevice = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent);
new Vue({
router,

View File

@@ -2,21 +2,25 @@ import Vue from 'vue';
import VueRouter from 'vue-router';
import _ from 'lodash';
import App from './components/App.vue';
//немедленная загрузка
import CardIndex from './components/CardIndex/CardIndex.vue';
//const CardIndex = () => import('./components/CardIndex/CardIndex.vue');
const CardIndex = () => import('./components/CardIndex/CardIndex.vue');
const Search = () => import('./components/CardIndex/Search/Search.vue');
const Card = () => import('./components/CardIndex/Card/Card.vue');
const Book = () => import('./components/CardIndex/Book/Book.vue');
const History = () => import('./components/CardIndex/History/History.vue');
const Reader = () => import('./components/Reader/Reader.vue');
//немедленная загрузка
//const Reader = () => import('./components/Reader/Reader.vue');
import Reader from './components/Reader/Reader.vue';
//const Forum = () => import('./components/Forum/Forum.vue');
const Income = () => import('./components/Income/Income.vue');
const Sources = () => import('./components/Sources/Sources.vue');
const Settings = () => import('./components/Settings/Settings.vue');
const Help = () => import('./components/Help/Help.vue');
const NotFound404 = () => import('./components/NotFound404/NotFound404.vue');
//const NotFound404 = () => import('./components/NotFound404/NotFound404.vue');
const myRoutes = [
['/', null, null, '/cardindex'],

View File

@@ -2,13 +2,12 @@ import _ from 'lodash';
import baseX from 'base-x';
import PAKO from 'pako';
import {Buffer} from 'safe-buffer';
import sjclWrapper from './sjclWrapper';
export const pako = PAKO;
const BASE58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
const BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
const bs58 = baseX(BASE58);
const bs64 = baseX(BASE64);
export function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
@@ -40,6 +39,10 @@ export function formatDate(d, format) {
case 'normal':
return `${d.getDate().toString().padStart(2, '0')}.${(d.getMonth() + 1).toString().padStart(2, '0')}.${d.getFullYear()} ` +
`${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}`;
case 'coDate':
return `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, '0')}-${d.getDate().toString().padStart(2, '0')}`;
case 'noDate':
return `${d.getDate().toString().padStart(2, '0')}.${(d.getMonth() + 1).toString().padStart(2, '0')}.${d.getFullYear()}`;
}
}
@@ -86,12 +89,18 @@ export function fromBase58(data) {
return bs58.decode(data);
}
//base-x слишком тормозит, используем sjcl
export function toBase64(data) {
return bs64.encode(Buffer.from(data));
return sjclWrapper.codec.base64.fromBits(
sjclWrapper.codec.bytes.toBits(Buffer.from(data))
);
}
//base-x слишком тормозит, используем sjcl
export function fromBase64(data) {
return bs64.decode(data);
return Buffer.from(sjclWrapper.codec.bytes.fromBits(
sjclWrapper.codec.base64.toBits(data)
));
}
export function getObjDiff(oldObj, newObj) {
@@ -120,6 +129,10 @@ export function getObjDiff(oldObj, newObj) {
return result;
}
export function isObjDiff(diff) {
return (_.isObject(diff) && diff.__isDiff);
}
export function isEmptyObjDiff(diff) {
return (!_.isObject(diff) || !diff.__isDiff ||
(!Object.keys(diff.change).length &&
@@ -157,3 +170,27 @@ export function applyObjDiff(obj, diff, isAddChanged) {
return result;
}
export function parseQuery(str) {
if (typeof str != 'string' || str.length == 0)
return {};
let s = str.split('&');
let s_length = s.length;
let bit, query = {}, first, second;
for (let i = 0; i < s_length; i++) {
bit = s[i].split('=');
first = decodeURIComponent(bit[0]);
if (first.length == 0)
continue;
second = decodeURIComponent(bit[1]);
if (typeof query[first] == 'undefined')
query[first] = second;
else
if (query[first] instanceof Array)
query[first].push(second);
else
query[first] = [query[first], second];
}
return query;
}

View File

@@ -1,3 +1,17 @@
//занчение toolButtons.name не должно совпадать с settingDefaults-propertyName
const toolButtons = [
{name: 'undoAction', show: true, text: 'Действие назад'},
{name: 'redoAction', show: true, text: 'Действие вперед'},
{name: 'fullScreen', show: true, text: 'На весь экран'},
{name: 'scrolling', show: false, text: 'Плавный скроллинг'},
{name: 'setPosition', show: true, text: 'На страницу'},
{name: 'search', show: true, text: 'Найти в тексте'},
{name: 'copyText', show: false, text: 'Скопировать текст со страницы'},
{name: 'refresh', show: true, text: 'Принудительно обновить книгу'},
{name: 'offlineMode', show: false, text: 'Автономный режим (без интернета)'},
{name: 'recentBooks', show: true, text: 'Открыть недавние'},
];
const fonts = [
{name: 'ReaderDefault', label: 'По-умолчанию', fontVertShift: 0},
{name: 'GEO_1', label: 'BPG Arial', fontVertShift: 10},
@@ -132,7 +146,7 @@ const settingDefaults = {
fontName: 'ReaderDefault',
webFontName: '',
fontVertShift: 0,
textVertShift: -20,
textVertShift: 0,
lineInterval: 3,// px, межстрочный интервал
textAlignJustify: true,// выравнивание по ширине
@@ -140,7 +154,7 @@ const settingDefaults = {
indentLR: 15,// px, отступ всего текста слева и справа
indentTB: 0,// px, отступ всего текста сверху и снизу
wordWrap: true,//перенос по слогам
keepLastToFirst: true,// перенос последней строки в первую при листании
keepLastToFirst: false,// перенос последней строки в первую при листании
showStatusBar: true,
statusBarTop: false,// top, bottom
@@ -150,7 +164,7 @@ const settingDefaults = {
scrollingDelay: 3000,// замедление, ms
scrollingType: 'ease-in-out', //linear, ease, ease-in, ease-out, ease-in-out
pageChangeAnimation: 'blink',// '' - нет, downShift, rightShift, thaw - протаивание, blink - мерцание
pageChangeAnimation: 'flip',// '' - нет, downShift, rightShift, thaw - протаивание, blink - мерцание, rotate - вращение, flip - листание
pageChangeAnimationSpeed: 80, //0-100%
allowUrlParamBookPos: false,
@@ -163,17 +177,24 @@ const settingDefaults = {
blinkCachedLoad: true,
showImages: true,
showInlineImagesInCenter: true,
compactTextPerc: 0,
imageHeightLines: 100,
imageFitWidth: true,
showServerStorageMessages: true,
showWhatsNewDialog: true,
showMigrationDialog: true,
enableSitesFilter: true,
fontShifts: {},
showToolButton: {},
};
for (const font of fonts)
settingDefaults.fontShifts[font.name] = font.fontVertShift;
for (const font of webFonts)
settingDefaults.fontShifts[font.name] = font.fontVertShift;
for (const button of toolButtons)
settingDefaults.showToolButton[button.name] = button.show;
// initial state
const state = {
@@ -183,6 +204,8 @@ const state = {
profiles: {},
profilesRev: 0,
allowProfilesSave: false,//подстраховка для разработки
whatsNewContentHash: '',
migrationRemindDate: '',
currentProfile: '',
settings: Object.assign({}, settingDefaults),
settingsRev: {},
@@ -214,6 +237,12 @@ const mutations = {
setAllowProfilesSave(state, value) {
state.allowProfilesSave = value;
},
setWhatsNewContentHash(state, value) {
state.whatsNewContentHash = value;
},
setMigrationRemindDate(state, value) {
state.migrationRemindDate = value;
},
setCurrentProfile(state, value) {
state.currentProfile = value;
},
@@ -226,6 +255,7 @@ const mutations = {
};
export default {
toolButtons,
fonts,
webFonts,
settingDefaults,

View File

@@ -1 +0,0 @@
.el-alert{width:100%;padding:8px 16px;margin:0;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:4px;position:relative;background-color:#fff;overflow:hidden;opacity:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-transition:opacity .2s;transition:opacity .2s}.el-alert.is-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.el-alert--success{background-color:#f0f9eb;color:#67c23a}.el-alert--success .el-alert__description{color:#67c23a}.el-alert--info{background-color:#f4f4f5;color:#909399}.el-alert--info .el-alert__description{color:#909399}.el-alert--warning{background-color:#fdf6ec;color:#e6a23c}.el-alert--warning .el-alert__description{color:#e6a23c}.el-alert--error{background-color:#fef0f0;color:#f56c6c}.el-alert--error .el-alert__description{color:#f56c6c}.el-alert__content{display:table-cell;padding:0 8px}.el-alert__icon{font-size:16px;width:16px}.el-alert__icon.is-big{font-size:28px;width:28px}.el-alert__title{font-size:13px;line-height:18px}.el-alert__title.is-bold{font-weight:700}.el-alert .el-alert__description{font-size:12px;margin:5px 0 0}.el-alert__closebtn{font-size:12px;color:#c0c4cc;opacity:1;position:absolute;top:12px;right:15px;cursor:pointer}.el-alert__closebtn.is-customed{font-style:normal;font-size:13px;top:9px}.el-alert-fade-enter,.el-alert-fade-leave-active{opacity:0}

View File

@@ -1 +0,0 @@
.el-aside{overflow:auto;-webkit-box-sizing:border-box;box-sizing:border-box;-ms-flex-negative:0;flex-shrink:0}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.el-badge{position:relative;vertical-align:middle;display:inline-block}.el-badge__content{background-color:#f56c6c;border-radius:10px;color:#fff;display:inline-block;font-size:12px;height:18px;line-height:18px;padding:0 6px;text-align:center;white-space:nowrap;border:1px solid #fff}.el-badge__content.is-fixed{position:absolute;top:0;right:10px;-webkit-transform:translateY(-50%) translateX(100%);transform:translateY(-50%) translateX(100%)}.el-badge__content.is-fixed.is-dot{right:5px}.el-badge__content.is-dot{height:8px;width:8px;padding:0;right:0;border-radius:50%}.el-badge__content--primary{background-color:#00468F}.el-badge__content--success{background-color:#67c23a}.el-badge__content--warning{background-color:#e6a23c}.el-badge__content--info{background-color:#909399}.el-badge__content--danger{background-color:#f56c6c}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.el-breadcrumb{font-size:14px;line-height:1}.el-breadcrumb::after,.el-breadcrumb::before{display:table;content:""}.el-breadcrumb::after{clear:both}.el-breadcrumb__separator{margin:0 9px;font-weight:700;color:#c0c4cc}.el-breadcrumb__separator[class*=icon]{margin:0 6px;font-weight:400}.el-breadcrumb__item{float:left}.el-breadcrumb__inner{color:#606266}.el-breadcrumb__inner a,.el-breadcrumb__inner.is-link{font-weight:700;text-decoration:none;-webkit-transition:color .2s cubic-bezier(.645,.045,.355,1);transition:color .2s cubic-bezier(.645,.045,.355,1);color:#303133}.el-breadcrumb__inner a:hover,.el-breadcrumb__inner.is-link:hover{color:#00468F;cursor:pointer}.el-breadcrumb__item:last-child .el-breadcrumb__inner,.el-breadcrumb__item:last-child .el-breadcrumb__inner a,.el-breadcrumb__item:last-child .el-breadcrumb__inner a:hover,.el-breadcrumb__item:last-child .el-breadcrumb__inner:hover{font-weight:400;color:#606266;cursor:text}.el-breadcrumb__item:last-child .el-breadcrumb__separator{display:none}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.el-card{border-radius:4px;border:1px solid #ebeef5;background-color:#fff;overflow:hidden;color:#303133;-webkit-transition:.3s;transition:.3s}.el-card.is-always-shadow,.el-card.is-hover-shadow:focus,.el-card.is-hover-shadow:hover{-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-card__header{padding:18px 20px;border-bottom:1px solid #ebeef5;-webkit-box-sizing:border-box;box-sizing:border-box}.el-card__body{padding:20px}

View File

@@ -1 +0,0 @@
.el-carousel__item,.el-carousel__mask{position:absolute;height:100%;top:0;left:0}.el-carousel__item{width:100%;display:inline-block;overflow:hidden;z-index:0}.el-carousel__item.is-active{z-index:2}.el-carousel__item.is-animating{-webkit-transition:-webkit-transform .4s ease-in-out;transition:-webkit-transform .4s ease-in-out;transition:transform .4s ease-in-out;transition:transform .4s ease-in-out,-webkit-transform .4s ease-in-out}.el-carousel__item--card{width:50%;-webkit-transition:-webkit-transform .4s ease-in-out;transition:-webkit-transform .4s ease-in-out;transition:transform .4s ease-in-out;transition:transform .4s ease-in-out,-webkit-transform .4s ease-in-out}.el-carousel__item--card.is-in-stage{cursor:pointer;z-index:1}.el-carousel__item--card.is-in-stage.is-hover .el-carousel__mask,.el-carousel__item--card.is-in-stage:hover .el-carousel__mask{opacity:.12}.el-carousel__item--card.is-active{z-index:2}.el-carousel__mask{width:100%;background-color:#fff;opacity:.24;-webkit-transition:.2s;transition:.2s}

View File

@@ -1 +0,0 @@
.el-carousel{overflow-x:hidden;position:relative}.el-carousel__container{position:relative;height:300px}.el-carousel__arrow{border:none;outline:0;padding:0;margin:0;height:36px;width:36px;cursor:pointer;-webkit-transition:.3s;transition:.3s;border-radius:50%;background-color:rgba(31,45,61,.11);color:#fff;position:absolute;top:50%;z-index:10;-webkit-transform:translateY(-50%);transform:translateY(-50%);text-align:center;font-size:12px}.el-carousel__arrow--left{left:16px}.el-carousel__arrow--right{right:16px}.el-carousel__arrow:hover{background-color:rgba(31,45,61,.23)}.el-carousel__arrow i{cursor:pointer}.el-carousel__indicators{position:absolute;list-style:none;bottom:0;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%);margin:0;padding:0;z-index:2}.el-carousel__indicators--outside{bottom:26px;text-align:center;position:static;-webkit-transform:none;transform:none}.el-carousel__indicators--outside .el-carousel__indicator:hover button{opacity:.64}.el-carousel__indicators--outside button{background-color:#c0c4cc;opacity:.24}.el-carousel__indicators--labels{left:0;right:0;-webkit-transform:none;transform:none;text-align:center}.el-carousel__indicators--labels .el-carousel__button{height:auto;width:auto;padding:2px 18px;font-size:12px}.el-carousel__indicators--labels .el-carousel__indicator{padding:6px 4px}.el-carousel__indicator{display:inline-block;background-color:transparent;padding:12px 4px;cursor:pointer}.el-carousel__indicator:hover button{opacity:.72}.el-carousel__indicator.is-active button{opacity:1}.el-carousel__button{display:block;opacity:.48;width:30px;height:2px;background-color:#fff;border:none;outline:0;padding:0;margin:0;cursor:pointer;-webkit-transition:.3s;transition:.3s}.carousel-arrow-left-enter,.carousel-arrow-left-leave-active{-webkit-transform:translateY(-50%) translateX(-10px);transform:translateY(-50%) translateX(-10px);opacity:0}.carousel-arrow-right-enter,.carousel-arrow-right-leave-active{-webkit-transform:translateY(-50%) translateX(10px);transform:translateY(-50%) translateX(10px);opacity:0}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.el-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-flex:1;-ms-flex:1;flex:1;-ms-flex-preferred-size:auto;flex-basis:auto;-webkit-box-sizing:border-box;box-sizing:border-box;min-width:0}.el-container.is-vertical{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.v-modal-enter{-webkit-animation:v-modal-in .2s ease;animation:v-modal-in .2s ease}.v-modal-leave{-webkit-animation:v-modal-out .2s ease forwards;animation:v-modal-out .2s ease forwards}@-webkit-keyframes v-modal-in{0%{opacity:0}}@keyframes v-modal-in{0%{opacity:0}}@-webkit-keyframes v-modal-out{100%{opacity:0}}@keyframes v-modal-out{100%{opacity:0}}.v-modal{position:fixed;left:0;top:0;width:100%;height:100%;opacity:.5;background:#000}.el-popup-parent--hidden{overflow:hidden}.el-dialog{position:relative;margin:0 auto 50px;background:#fff;border-radius:2px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,.3);box-shadow:0 1px 3px rgba(0,0,0,.3);-webkit-box-sizing:border-box;box-sizing:border-box;width:50%}.el-dialog.is-fullscreen{width:100%;margin-top:0;margin-bottom:0;height:100%;overflow:auto}.el-dialog__wrapper{position:fixed;top:0;right:0;bottom:0;left:0;overflow:auto;margin:0}.el-dialog__header{padding:20px 20px 10px}.el-dialog__headerbtn{position:absolute;top:20px;right:20px;padding:0;background:0 0;border:none;outline:0;cursor:pointer;font-size:16px}.el-dialog__headerbtn .el-dialog__close{color:#909399}.el-dialog__headerbtn:focus .el-dialog__close,.el-dialog__headerbtn:hover .el-dialog__close{color:#00468F}.el-dialog__title{line-height:24px;font-size:18px;color:#303133}.el-dialog__body{padding:30px 20px;color:#606266;font-size:14px}.el-dialog__footer{padding:10px 20px 20px;text-align:right;-webkit-box-sizing:border-box;box-sizing:border-box}.el-dialog--center{text-align:center}.el-dialog--center .el-dialog__body{text-align:initial;padding:25px 25px 30px}.el-dialog--center .el-dialog__footer{text-align:inherit}.dialog-fade-enter-active{-webkit-animation:dialog-fade-in .3s;animation:dialog-fade-in .3s}.dialog-fade-leave-active{-webkit-animation:dialog-fade-out .3s;animation:dialog-fade-out .3s}@-webkit-keyframes dialog-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@keyframes dialog-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@-webkit-keyframes dialog-fade-out{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}100%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}@keyframes dialog-fade-out{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}100%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}

View File

@@ -1 +0,0 @@
@media only screen and (max-width:767px){.hidden-xs-only{display:none!important}}@media only screen and (min-width:768px){.hidden-sm-and-up{display:none!important}}@media only screen and (min-width:768px) and (max-width:991px){.hidden-sm-only{display:none!important}}@media only screen and (max-width:991px){.hidden-sm-and-down{display:none!important}}@media only screen and (min-width:992px){.hidden-md-and-up{display:none!important}}@media only screen and (min-width:992px) and (max-width:1199px){.hidden-md-only{display:none!important}}@media only screen and (max-width:1199px){.hidden-md-and-down{display:none!important}}@media only screen and (min-width:1200px){.hidden-lg-and-up{display:none!important}}@media only screen and (min-width:1200px) and (max-width:1919px){.hidden-lg-only{display:none!important}}@media only screen and (max-width:1919px){.hidden-lg-and-down{display:none!important}}@media only screen and (min-width:1920px){.hidden-xl-only{display:none!important}}

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -1 +0,0 @@
.el-footer{padding:0 20px;-webkit-box-sizing:border-box;box-sizing:border-box;-ms-flex-negative:0;flex-shrink:0}

View File

@@ -1 +0,0 @@
.el-form--inline .el-form-item,.el-form--inline .el-form-item__content{display:inline-block;vertical-align:top}.el-form-item::after,.el-form-item__content::after{clear:both}.el-form--label-left .el-form-item__label{text-align:left}.el-form--label-top .el-form-item__label{float:none;display:inline-block;text-align:left;padding:0 0 10px}.el-form--inline .el-form-item{margin-right:10px}.el-form--inline .el-form-item__label{float:none;display:inline-block}.el-form-item__content .el-input-group,.el-form-item__label{vertical-align:middle}.el-form--inline.el-form--label-top .el-form-item__content{display:block}.el-form-item{margin-bottom:22px}.el-form-item::after,.el-form-item::before{display:table;content:""}.el-form-item .el-form-item{margin-bottom:0}.el-form-item--mini.el-form-item,.el-form-item--small.el-form-item{margin-bottom:18px}.el-form-item .el-input__validateIcon{display:none}.el-form-item--medium .el-form-item__content,.el-form-item--medium .el-form-item__label{line-height:36px}.el-form-item--small .el-form-item__content,.el-form-item--small .el-form-item__label{line-height:32px}.el-form-item--small .el-form-item__error{padding-top:2px}.el-form-item--mini .el-form-item__content,.el-form-item--mini .el-form-item__label{line-height:28px}.el-form-item--mini .el-form-item__error{padding-top:1px}.el-form-item__label{text-align:right;float:left;font-size:14px;color:#606266;line-height:40px;padding:0 12px 0 0;-webkit-box-sizing:border-box;box-sizing:border-box}.el-form-item__content{line-height:40px;position:relative;font-size:14px}.el-form-item__content::after,.el-form-item__content::before{display:table;content:""}.el-form-item__error{color:#f56c6c;font-size:12px;line-height:1;padding-top:4px;position:absolute;top:100%;left:0}.el-form-item__error--inline{position:relative;top:auto;left:auto;display:inline-block;margin-left:10px}.el-form-item.is-required:not(.is-no-asterisk)>.el-form-item__label:before{content:'*';color:#f56c6c;margin-right:4px}.el-form-item.is-error .el-input__inner,.el-form-item.is-error .el-input__inner:focus,.el-form-item.is-error .el-textarea__inner,.el-form-item.is-error .el-textarea__inner:focus{border-color:#f56c6c}.el-form-item.is-error .el-input-group__append .el-input__inner,.el-form-item.is-error .el-input-group__prepend .el-input__inner{border-color:transparent}.el-form-item.is-error .el-input__validateIcon{color:#f56c6c}.el-form-item.is-success .el-input__inner,.el-form-item.is-success .el-input__inner:focus,.el-form-item.is-success .el-textarea__inner,.el-form-item.is-success .el-textarea__inner:focus{border-color:#67c23a}.el-form-item.is-success .el-input-group__append .el-input__inner,.el-form-item.is-success .el-input-group__prepend .el-input__inner{border-color:transparent}.el-form-item.is-success .el-input__validateIcon{color:#67c23a}.el-form-item--feedback .el-input__validateIcon{display:inline-block}

View File

@@ -1 +0,0 @@
.el-header{padding:0 20px;-webkit-box-sizing:border-box;box-sizing:border-box;-ms-flex-negative:0;flex-shrink:0}

View File

@@ -1 +0,0 @@
@font-face{font-family:element-icons;src:url(fonts/element-icons.woff) format("woff"),url(fonts/element-icons.ttf) format("truetype");font-weight:400;font-style:normal}[class*=" el-icon-"],[class^=el-icon-]{font-family:element-icons!important;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;vertical-align:baseline;display:inline-block;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.el-icon-info:before{content:"\e61a"}.el-icon-error:before{content:"\e62c"}.el-icon-success:before{content:"\e62d"}.el-icon-warning:before{content:"\e62e"}.el-icon-question:before{content:"\e634"}.el-icon-back:before{content:"\e606"}.el-icon-arrow-left:before{content:"\e600"}.el-icon-arrow-down:before{content:"\e603"}.el-icon-arrow-right:before{content:"\e604"}.el-icon-arrow-up:before{content:"\e605"}.el-icon-caret-left:before{content:"\e60a"}.el-icon-caret-bottom:before{content:"\e60b"}.el-icon-caret-top:before{content:"\e60c"}.el-icon-caret-right:before{content:"\e60e"}.el-icon-d-arrow-left:before{content:"\e610"}.el-icon-d-arrow-right:before{content:"\e613"}.el-icon-minus:before{content:"\e621"}.el-icon-plus:before{content:"\e62b"}.el-icon-remove:before{content:"\e635"}.el-icon-circle-plus:before{content:"\e601"}.el-icon-remove-outline:before{content:"\e63c"}.el-icon-circle-plus-outline:before{content:"\e602"}.el-icon-close:before{content:"\e60f"}.el-icon-check:before{content:"\e611"}.el-icon-circle-close:before{content:"\e607"}.el-icon-circle-check:before{content:"\e639"}.el-icon-circle-close-outline:before{content:"\e609"}.el-icon-circle-check-outline:before{content:"\e63e"}.el-icon-zoom-out:before{content:"\e645"}.el-icon-zoom-in:before{content:"\e641"}.el-icon-d-caret:before{content:"\e615"}.el-icon-sort:before{content:"\e640"}.el-icon-sort-down:before{content:"\e630"}.el-icon-sort-up:before{content:"\e631"}.el-icon-tickets:before{content:"\e63f"}.el-icon-document:before{content:"\e614"}.el-icon-goods:before{content:"\e618"}.el-icon-sold-out:before{content:"\e63b"}.el-icon-news:before{content:"\e625"}.el-icon-message:before{content:"\e61b"}.el-icon-date:before{content:"\e608"}.el-icon-printer:before{content:"\e62f"}.el-icon-time:before{content:"\e642"}.el-icon-bell:before{content:"\e622"}.el-icon-mobile-phone:before{content:"\e624"}.el-icon-service:before{content:"\e63a"}.el-icon-view:before{content:"\e643"}.el-icon-menu:before{content:"\e620"}.el-icon-more:before{content:"\e646"}.el-icon-more-outline:before{content:"\e626"}.el-icon-star-on:before{content:"\e637"}.el-icon-star-off:before{content:"\e63d"}.el-icon-location:before{content:"\e61d"}.el-icon-location-outline:before{content:"\e61f"}.el-icon-phone:before{content:"\e627"}.el-icon-phone-outline:before{content:"\e628"}.el-icon-picture:before{content:"\e629"}.el-icon-picture-outline:before{content:"\e62a"}.el-icon-delete:before{content:"\e612"}.el-icon-search:before{content:"\e619"}.el-icon-edit:before{content:"\e61c"}.el-icon-edit-outline:before{content:"\e616"}.el-icon-rank:before{content:"\e632"}.el-icon-refresh:before{content:"\e633"}.el-icon-share:before{content:"\e636"}.el-icon-setting:before{content:"\e638"}.el-icon-upload:before{content:"\e60d"}.el-icon-upload2:before{content:"\e644"}.el-icon-download:before{content:"\e617"}.el-icon-loading:before{content:"\e61e"}.el-icon-loading{-webkit-animation:rotating 2s linear infinite;animation:rotating 2s linear infinite}.el-icon--right{margin-left:5px}.el-icon--left{margin-right:5px}@-webkit-keyframes rotating{0%{-webkit-transform:rotateZ(0);transform:rotateZ(0)}100%{-webkit-transform:rotateZ(360deg);transform:rotateZ(360deg)}}@keyframes rotating{0%{-webkit-transform:rotateZ(0);transform:rotateZ(0)}100%{-webkit-transform:rotateZ(360deg);transform:rotateZ(360deg)}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.el-loading-parent--relative{position:relative!important}.el-loading-parent--hidden{overflow:hidden!important}.el-loading-mask{position:absolute;z-index:2000;background-color:rgba(255,255,255,.9);margin:0;top:0;right:0;bottom:0;left:0;-webkit-transition:opacity .3s;transition:opacity .3s}.el-loading-mask.is-fullscreen{position:fixed}.el-loading-mask.is-fullscreen .el-loading-spinner{margin-top:-25px}.el-loading-mask.is-fullscreen .el-loading-spinner .circular{height:50px;width:50px}.el-loading-spinner{top:50%;margin-top:-21px;width:100%;text-align:center;position:absolute}.el-loading-spinner .el-loading-text{color:#00468F;margin:3px 0;font-size:14px}.el-loading-spinner .circular{height:42px;width:42px;-webkit-animation:loading-rotate 2s linear infinite;animation:loading-rotate 2s linear infinite}.el-loading-spinner .path{-webkit-animation:loading-dash 1.5s ease-in-out infinite;animation:loading-dash 1.5s ease-in-out infinite;stroke-dasharray:90,150;stroke-dashoffset:0;stroke-width:2;stroke:#00468F;stroke-linecap:round}.el-loading-spinner i{color:#00468F}.el-loading-fade-enter,.el-loading-fade-leave-active{opacity:0}@-webkit-keyframes loading-rotate{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes loading-rotate{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes loading-dash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-40px}100%{stroke-dasharray:90,150;stroke-dashoffset:-120px}}@keyframes loading-dash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-40px}100%{stroke-dasharray:90,150;stroke-dashoffset:-120px}}

View File

@@ -1 +0,0 @@
.el-main{display:block;-webkit-box-flex:1;-ms-flex:1;flex:1;-ms-flex-preferred-size:auto;flex-basis:auto;overflow:auto;-webkit-box-sizing:border-box;box-sizing:border-box;padding:20px}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.el-message__closeBtn:focus,.el-message__content:focus{outline-width:0}.el-message{min-width:380px;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:4px;border-width:1px;border-style:solid;border-color:#ebeef5;position:fixed;left:50%;top:20px;-webkit-transform:translateX(-50%);transform:translateX(-50%);background-color:#edf2fc;-webkit-transition:opacity .3s,-webkit-transform .4s;transition:opacity .3s,-webkit-transform .4s;transition:opacity .3s,transform .4s;transition:opacity .3s,transform .4s,-webkit-transform .4s;overflow:hidden;padding:15px 15px 15px 20px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-message.is-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.el-message.is-closable .el-message__content{padding-right:16px}.el-message p{margin:0}.el-message--info .el-message__content{color:#909399}.el-message--success{background-color:#f0f9eb;border-color:#e1f3d8}.el-message--success .el-message__content{color:#67c23a}.el-message--warning{background-color:#fdf6ec;border-color:#faecd8}.el-message--warning .el-message__content{color:#e6a23c}.el-message--error{background-color:#fef0f0;border-color:#fde2e2}.el-message--error .el-message__content{color:#f56c6c}.el-message__icon{margin-right:10px}.el-message__content{padding:0;font-size:14px;line-height:1}.el-message__closeBtn{position:absolute;top:50%;right:15px;-webkit-transform:translateY(-50%);transform:translateY(-50%);cursor:pointer;color:#c0c4cc;font-size:16px}.el-message__closeBtn:hover{color:#909399}.el-message .el-icon-success{color:#67c23a}.el-message .el-icon-error{color:#f56c6c}.el-message .el-icon-info{color:#909399}.el-message .el-icon-warning{color:#e6a23c}.el-message-fade-enter,.el-message-fade-leave-active{opacity:0;-webkit-transform:translate(-50%,-100%);transform:translate(-50%,-100%)}

View File

@@ -1 +0,0 @@
.el-notification{display:-webkit-box;display:-ms-flexbox;display:flex;width:330px;padding:14px 26px 14px 13px;border-radius:8px;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid #ebeef5;position:fixed;background-color:#fff;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);-webkit-transition:opacity .3s,left .3s,right .3s,top .4s,bottom .3s,-webkit-transform .3s;transition:opacity .3s,left .3s,right .3s,top .4s,bottom .3s,-webkit-transform .3s;transition:opacity .3s,transform .3s,left .3s,right .3s,top .4s,bottom .3s;transition:opacity .3s,transform .3s,left .3s,right .3s,top .4s,bottom .3s,-webkit-transform .3s;overflow:hidden}.el-notification.right{right:16px}.el-notification.left{left:16px}.el-notification__group{margin-left:13px}.el-notification__title{font-weight:700;font-size:16px;color:#303133;margin:0}.el-notification__content{font-size:14px;line-height:21px;margin:6px 0 0;color:#606266;text-align:justify}.el-notification__content p{margin:0}.el-notification__icon{height:24px;width:24px;font-size:24px}.el-notification__closeBtn{position:absolute;top:18px;right:15px;cursor:pointer;color:#909399;font-size:16px}.el-notification__closeBtn:hover{color:#606266}.el-notification .el-icon-success{color:#67c23a}.el-notification .el-icon-error{color:#f56c6c}.el-notification .el-icon-info{color:#909399}.el-notification .el-icon-warning{color:#e6a23c}.el-notification-fade-enter.right{right:0;-webkit-transform:translateX(100%);transform:translateX(100%)}.el-notification-fade-enter.left{left:0;-webkit-transform:translateX(-100%);transform:translateX(-100%)}.el-notification-fade-leave-active{opacity:0}

View File

@@ -1 +0,0 @@
.el-select-group{margin:0;padding:0}.el-select-group__wrap{position:relative;list-style:none;margin:0;padding:0}.el-select-group__wrap:not(:last-of-type){padding-bottom:24px}.el-select-group__wrap:not(:last-of-type)::after{content:'';position:absolute;display:block;left:20px;right:20px;bottom:12px;height:1px;background:#e4e7ed}.el-select-group__title{padding-left:20px;font-size:12px;color:#909399;line-height:30px}.el-select-group .el-select-dropdown__item{padding-left:20px}

View File

@@ -1 +0,0 @@
.el-select-dropdown__item{font-size:14px;padding:0 20px;position:relative;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#606266;height:34px;line-height:34px;-webkit-box-sizing:border-box;box-sizing:border-box;cursor:pointer}.el-select-dropdown__item.is-disabled{color:#c0c4cc;cursor:not-allowed}.el-select-dropdown__item.is-disabled:hover{background-color:#fff}.el-select-dropdown__item.hover,.el-select-dropdown__item:hover{background-color:#f5f7fa}.el-select-dropdown__item.selected{color:#00468F;font-weight:700}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.el-popper .popper__arrow,.el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.el-popper .popper__arrow::after{content:" ";border-width:6px}.el-popper[x-placement^=top]{margin-bottom:12px}.el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#ebeef5;border-bottom-width:0}.el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.el-popper[x-placement^=bottom]{margin-top:12px}.el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#ebeef5}.el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.el-popper[x-placement^=right]{margin-left:12px}.el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#ebeef5;border-left-width:0}.el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.el-popper[x-placement^=left]{margin-right:12px}.el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#ebeef5}.el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}.el-popover{position:absolute;background:#fff;min-width:150px;border-radius:4px;border:1px solid #ebeef5;padding:12px;z-index:2000;color:#606266;line-height:1.4;text-align:justify;font-size:14px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-popover--plain{padding:18px 20px}.el-popover__title{color:#303133;font-size:16px;line-height:1;margin-bottom:12px}.el-popover:focus,.el-popover:focus:active,.el-popover__reference:focus:hover,.el-popover__reference:focus:not(.focusing){outline-width:0}

View File

@@ -1 +0,0 @@
.el-popper .popper__arrow,.el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.el-popper .popper__arrow::after{content:" ";border-width:6px}.el-popper[x-placement^=top]{margin-bottom:12px}.el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#ebeef5;border-bottom-width:0}.el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.el-popper[x-placement^=bottom]{margin-top:12px}.el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#ebeef5}.el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.el-popper[x-placement^=right]{margin-left:12px}.el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#ebeef5;border-left-width:0}.el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.el-popper[x-placement^=left]{margin-right:12px}.el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#ebeef5}.el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}

View File

@@ -1 +0,0 @@
.el-progress{position:relative;line-height:1}.el-progress__text{font-size:14px;color:#606266;display:inline-block;vertical-align:middle;margin-left:10px;line-height:1}.el-progress__text i{vertical-align:middle;display:block}.el-progress--circle{display:inline-block}.el-progress--circle .el-progress__text{position:absolute;top:50%;left:0;width:100%;text-align:center;margin:0;-webkit-transform:translate(0,-50%);transform:translate(0,-50%)}.el-progress--circle .el-progress__text i{vertical-align:middle;display:inline-block}.el-progress--without-text .el-progress__text{display:none}.el-progress--without-text .el-progress-bar{padding-right:0;margin-right:0;display:block}.el-progress-bar,.el-progress-bar__inner::after,.el-progress-bar__innerText{display:inline-block;vertical-align:middle}.el-progress--text-inside .el-progress-bar{padding-right:0;margin-right:0}.el-progress.is-success .el-progress-bar__inner{background-color:#67c23a}.el-progress.is-success .el-progress__text{color:#67c23a}.el-progress.is-exception .el-progress-bar__inner{background-color:#f56c6c}.el-progress.is-exception .el-progress__text{color:#f56c6c}.el-progress-bar{padding-right:50px;width:100%;margin-right:-55px;-webkit-box-sizing:border-box;box-sizing:border-box}.el-progress-bar__outer{height:6px;border-radius:100px;background-color:#ebeef5;overflow:hidden;position:relative;vertical-align:middle}.el-progress-bar__inner{position:absolute;left:0;top:0;height:100%;background-color:#00468F;text-align:right;border-radius:100px;line-height:1;white-space:nowrap;-webkit-transition:width .6s ease;transition:width .6s ease}.el-progress-bar__inner::after{content:"";height:100%}.el-progress-bar__innerText{color:#fff;font-size:12px;margin:0 5px}@-webkit-keyframes progress{0%{background-position:0 0}100%{background-position:32px 0}}@keyframes progress{0%{background-position:0 0}100%{background-position:32px 0}}

View File

@@ -1 +0,0 @@
@charset "UTF-8";.el-radio-button,.el-radio-button__inner{display:inline-block;position:relative;outline:0}.el-radio-button__inner{line-height:1;white-space:nowrap;vertical-align:middle;background:#fff;border:1px solid #dcdfe6;font-weight:500;border-left:0;color:#606266;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;cursor:pointer;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);padding:12px 20px;font-size:14px;border-radius:0}.el-radio-button__inner.is-round{padding:12px 20px}.el-radio-button__inner:hover{color:#00468F}.el-radio-button__inner [class*=el-icon-]{line-height:.9}.el-radio-button__inner [class*=el-icon-]+span{margin-left:5px}.el-radio-button:first-child .el-radio-button__inner{border-left:1px solid #dcdfe6;border-radius:4px 0 0 4px;-webkit-box-shadow:none!important;box-shadow:none!important}.el-radio-button__orig-radio{opacity:0;outline:0;position:absolute;z-index:-1}.el-radio-button__orig-radio:checked+.el-radio-button__inner{color:#fff;background-color:#00468F;border-color:#00468F;-webkit-box-shadow:-1px 0 0 0 #00468F;box-shadow:-1px 0 0 0 #00468F}.el-radio-button__orig-radio:disabled+.el-radio-button__inner{color:#c0c4cc;cursor:not-allowed;background-image:none;background-color:#fff;border-color:#ebeef5;-webkit-box-shadow:none;box-shadow:none}.el-radio-button__orig-radio:disabled:checked+.el-radio-button__inner{background-color:#f2f6fc}.el-radio-button:last-child .el-radio-button__inner{border-radius:0 4px 4px 0}.el-radio-button:first-child:last-child .el-radio-button__inner{border-radius:4px}.el-radio-button--medium .el-radio-button__inner{padding:10px 20px;font-size:14px;border-radius:0}.el-radio-button--medium .el-radio-button__inner.is-round{padding:10px 20px}.el-radio-button--small .el-radio-button__inner{padding:9px 15px;font-size:12px;border-radius:0}.el-radio-button--small .el-radio-button__inner.is-round{padding:9px 15px}.el-radio-button--mini .el-radio-button__inner{padding:7px 15px;font-size:12px;border-radius:0}.el-radio-button--mini .el-radio-button__inner.is-round{padding:7px 15px}.el-radio-button:focus:not(.is-focus):not(:active):not(.is-disabled){-webkit-box-shadow:0 0 2px 2px #00468F;box-shadow:0 0 2px 2px #00468F}

View File

@@ -1 +0,0 @@
.el-radio-group{display:inline-block;line-height:1;vertical-align:middle;font-size:0}

View File

@@ -1 +0,0 @@
@charset "UTF-8";.el-radio,.el-radio--medium.is-bordered .el-radio__label{font-size:14px}.el-radio,.el-radio__input{white-space:nowrap;line-height:1;outline:0}.el-radio,.el-radio__inner,.el-radio__input{position:relative;display:inline-block}.el-radio{color:#606266;font-weight:500;cursor:pointer;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.el-radio.is-bordered{padding:12px 20px 0 10px;border-radius:4px;border:1px solid #dcdfe6;-webkit-box-sizing:border-box;box-sizing:border-box;height:40px}.el-radio.is-bordered.is-checked{border-color:#00468F}.el-radio.is-bordered.is-disabled{cursor:not-allowed;border-color:#ebeef5}.el-radio__input.is-disabled .el-radio__inner,.el-radio__input.is-disabled.is-checked .el-radio__inner{background-color:#f5f7fa;border-color:#e4e7ed}.el-radio.is-bordered+.el-radio.is-bordered{margin-left:10px}.el-radio--medium.is-bordered{padding:10px 20px 0 10px;border-radius:4px;height:36px}.el-radio--mini.is-bordered .el-radio__label,.el-radio--small.is-bordered .el-radio__label{font-size:12px}.el-radio--medium.is-bordered .el-radio__inner{height:14px;width:14px}.el-radio--small.is-bordered{padding:8px 15px 0 10px;border-radius:3px;height:32px}.el-radio--small.is-bordered .el-radio__inner{height:12px;width:12px}.el-radio--mini.is-bordered{padding:6px 15px 0 10px;border-radius:3px;height:28px}.el-radio--mini.is-bordered .el-radio__inner{height:12px;width:12px}.el-radio+.el-radio{margin-left:30px}.el-radio__input{cursor:pointer;vertical-align:middle}.el-radio__input.is-disabled .el-radio__inner{cursor:not-allowed}.el-radio__input.is-disabled .el-radio__inner::after{cursor:not-allowed;background-color:#f5f7fa}.el-radio__input.is-disabled .el-radio__inner+.el-radio__label{cursor:not-allowed}.el-radio__input.is-disabled.is-checked .el-radio__inner::after{background-color:#c0c4cc}.el-radio__input.is-disabled+span.el-radio__label{color:#c0c4cc;cursor:not-allowed}.el-radio__input.is-checked .el-radio__inner{border-color:#00468F;background:#00468F}.el-radio__input.is-checked .el-radio__inner::after{-webkit-transform:translate(-50%,-50%) scale(1);transform:translate(-50%,-50%) scale(1)}.el-radio__input.is-checked+.el-radio__label{color:#00468F}.el-radio__input.is-focus .el-radio__inner{border-color:#00468F}.el-radio__inner{border:1px solid #dcdfe6;border-radius:100%;width:14px;height:14px;background-color:#fff;cursor:pointer;-webkit-box-sizing:border-box;box-sizing:border-box}.el-radio__inner:hover{border-color:#00468F}.el-radio__inner::after{width:4px;height:4px;border-radius:100%;background-color:#fff;content:"";position:absolute;left:50%;top:50%;-webkit-transform:translate(-50%,-50%) scale(0);transform:translate(-50%,-50%) scale(0);-webkit-transition:-webkit-transform .15s ease-in;transition:-webkit-transform .15s ease-in;transition:transform .15s ease-in;transition:transform .15s ease-in,-webkit-transform .15s ease-in}.el-radio__original{opacity:0;outline:0;position:absolute;z-index:-1;top:0;left:0;right:0;bottom:0;margin:0}.el-radio:focus:not(.is-focus):not(:active):not(.is-disabled) .el-radio__inner{-webkit-box-shadow:0 0 2px 2px #00468F;box-shadow:0 0 2px 2px #00468F}.el-radio__label{font-size:14px;padding-left:10px}

View File

@@ -1 +0,0 @@
.el-rate__icon,.el-rate__item{position:relative;display:inline-block}.el-rate{height:20px;line-height:1}.el-rate:active,.el-rate:focus{outline-width:0}.el-rate__item{font-size:0;vertical-align:middle}.el-rate__icon{font-size:18px;margin-right:6px;color:#c0c4cc;-webkit-transition:.3s;transition:.3s}.el-rate__decimal,.el-rate__icon .path2{position:absolute;top:0;left:0}.el-rate__icon.hover{-webkit-transform:scale(1.15);transform:scale(1.15)}.el-rate__decimal{display:inline-block;overflow:hidden}.el-rate__text{font-size:14px;vertical-align:middle}

View File

@@ -1 +0,0 @@
@charset "UTF-8";body{font-family:"Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif;font-weight:400;font-size:14px;color:#000;-webkit-font-smoothing:antialiased}a{color:#00468F;text-decoration:none}a:focus,a:hover{color:rgb(51, 107, 165)}a:active{color:rgb(0, 63, 129)}h1,h2,h3,h4,h5,h6{color:#606266;font-weight:inherit}h1:first-child,h2:first-child,h3:first-child,h4:first-child,h5:first-child,h6:first-child,p:first-child{margin-top:0}h1:last-child,h2:last-child,h3:last-child,h4:last-child,h5:last-child,h6:last-child,p:last-child{margin-bottom:0}h1{font-size:20px}h2{font-size:18px}h3{font-size:16px}h4,h5,h6,p{font-size:inherit}p{line-height:1.8}sub,sup{font-size:13px}small{font-size:12px}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}

View File

@@ -1 +0,0 @@
.el-row{position:relative;-webkit-box-sizing:border-box;box-sizing:border-box}.el-row::after,.el-row::before{display:table;content:""}.el-row::after{clear:both}.el-row--flex{display:-webkit-box;display:-ms-flexbox;display:flex}.el-row--flex:after,.el-row--flex:before{display:none}.el-row--flex.is-justify-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.el-row--flex.is-justify-end{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.el-row--flex.is-justify-space-between{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.el-row--flex.is-justify-space-around{-ms-flex-pack:distribute;justify-content:space-around}.el-row--flex.is-align-middle{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-row--flex.is-align-bottom{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}

View File

@@ -1 +0,0 @@
.el-scrollbar{overflow:hidden;position:relative}.el-scrollbar:active>.el-scrollbar__bar,.el-scrollbar:focus>.el-scrollbar__bar,.el-scrollbar:hover>.el-scrollbar__bar{opacity:1;-webkit-transition:opacity 340ms ease-out;transition:opacity 340ms ease-out}.el-scrollbar__wrap{overflow:scroll;height:100%}.el-scrollbar__wrap--hidden-default::-webkit-scrollbar{width:0;height:0}.el-scrollbar__thumb{position:relative;display:block;width:0;height:0;cursor:pointer;border-radius:inherit;background-color:rgba(144,147,153,.3);-webkit-transition:.3s background-color;transition:.3s background-color}.el-scrollbar__thumb:hover{background-color:rgba(144,147,153,.5)}.el-scrollbar__bar{position:absolute;right:2px;bottom:2px;z-index:1;border-radius:4px;opacity:0;-webkit-transition:opacity 120ms ease-out;transition:opacity 120ms ease-out}.el-scrollbar__bar.is-vertical{width:6px;top:2px}.el-scrollbar__bar.is-vertical>div{width:100%}.el-scrollbar__bar.is-horizontal{height:6px;left:2px}.el-scrollbar__bar.is-horizontal>div{height:100%}

View File

@@ -1 +0,0 @@
.el-popper .popper__arrow,.el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.el-popper .popper__arrow::after{content:" ";border-width:6px}.el-popper[x-placement^=top]{margin-bottom:12px}.el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#ebeef5;border-bottom-width:0}.el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.el-popper[x-placement^=bottom]{margin-top:12px}.el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#ebeef5}.el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.el-popper[x-placement^=right]{margin-left:12px}.el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#ebeef5;border-left-width:0}.el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.el-popper[x-placement^=left]{margin-right:12px}.el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#ebeef5}.el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}.el-select-dropdown{position:absolute;z-index:1001;border:1px solid #e4e7ed;border-radius:4px;background-color:#fff;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);-webkit-box-sizing:border-box;box-sizing:border-box;margin:5px 0}.el-select-dropdown.is-multiple .el-select-dropdown__item.selected{color:#00468F;background-color:#fff}.el-select-dropdown.is-multiple .el-select-dropdown__item.selected.hover{background-color:#f5f7fa}.el-select-dropdown.is-multiple .el-select-dropdown__item.selected::after{position:absolute;right:20px;font-family:element-icons;content:"\E611";font-size:12px;font-weight:700;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.el-select-dropdown .el-scrollbar.is-empty .el-select-dropdown__list{padding:0}.el-select-dropdown__empty{padding:10px 0;margin:0;text-align:center;color:#999;font-size:14px}.el-select-dropdown__wrap{max-height:274px}.el-select-dropdown__list{list-style:none;padding:6px 0;margin:0;-webkit-box-sizing:border-box;box-sizing:border-box}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.el-time-spinner{width:100%;white-space:nowrap}.el-spinner{display:inline-block;vertical-align:middle}.el-spinner-inner{-webkit-animation:rotate 2s linear infinite;animation:rotate 2s linear infinite;width:50px;height:50px}.el-spinner-inner .path{stroke:#ececec;stroke-linecap:round;-webkit-animation:dash 1.5s ease-in-out infinite;animation:dash 1.5s ease-in-out infinite}@-webkit-keyframes rotate{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes rotate{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes dash{0%{stroke-dasharray:1,150;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-35}100%{stroke-dasharray:90,150;stroke-dashoffset:-124}}@keyframes dash{0%{stroke-dasharray:1,150;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-35}100%{stroke-dasharray:90,150;stroke-dashoffset:-124}}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.el-steps{display:-webkit-box;display:-ms-flexbox;display:flex}.el-steps--simple{padding:13px 8%;border-radius:4px;background:#f5f7fa}.el-steps--horizontal{white-space:nowrap}.el-steps--vertical{height:100%;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-flow:column;flex-flow:column}

View File

@@ -1 +0,0 @@
.el-switch{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;position:relative;font-size:14px;line-height:20px;height:20px;vertical-align:middle}.el-switch.is-disabled .el-switch__core,.el-switch.is-disabled .el-switch__label{cursor:not-allowed}.el-switch__core,.el-switch__label{display:inline-block;cursor:pointer;vertical-align:middle}.el-switch__label{-webkit-transition:.2s;transition:.2s;height:20px;font-size:14px;font-weight:500;color:#303133}.el-switch__label.is-active{color:#00468F}.el-switch__label--left{margin-right:10px}.el-switch__label--right{margin-left:10px}.el-switch__label *{line-height:1;font-size:14px;display:inline-block}.el-switch__input{position:absolute;width:0;height:0;opacity:0;margin:0}.el-switch__core{margin:0;position:relative;width:40px;height:20px;border:1px solid #dcdfe6;outline:0;border-radius:10px;-webkit-box-sizing:border-box;box-sizing:border-box;background:#dcdfe6;-webkit-transition:border-color .3s,background-color .3s;transition:border-color .3s,background-color .3s}.el-switch__core:after{content:"";position:absolute;top:1px;left:1px;border-radius:100%;-webkit-transition:all .3s;transition:all .3s;width:16px;height:16px;background-color:#fff}.el-switch.is-checked .el-switch__core{border-color:#00468F;background-color:#00468F}.el-switch.is-checked .el-switch__core::after{left:100%;margin-left:-17px}.el-switch.is-disabled{opacity:.6}.el-switch--wide .el-switch__label.el-switch__label--left span{left:10px}.el-switch--wide .el-switch__label.el-switch__label--right span{right:10px}.el-switch .label-fade-enter,.el-switch .label-fade-leave-active{opacity:0}

File diff suppressed because one or more lines are too long

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