mirror of
https://framagit.org/les/gancio.git
synced 2025-01-31 08:32:23 +01:00
Squashed commit of the following:
commitf31e7ae2c1
Author: joenepraat <joenepraat@posteo.org> Date: Thu Mar 30 13:17:01 2023 +0000 Translated using Weblate (Dutch) Currently translated at 94.5% (297 of 314 strings) Translation: Gancio/Web Translate-URL: https://hosted.weblate.org/projects/gancio/web/nl/ commit96486b5945
Author: lesion <lesion@autistici.org> Date: Thu Mar 30 11:18:39 2023 +0200 minor commit9524fd52f3
Author: lesion <lesion@autistici.org> Date: Thu Mar 30 00:08:27 2023 +0200 update changelog, releasing v1.6.8 commitdf4ec69128
Author: lesion <lesion@autistici.org> Date: Wed Mar 29 12:41:52 2023 +0200 force vuetify to not use google fonts commite35595df82
Author: lesion <lesion@autistici.org> Date: Tue Mar 28 21:54:06 2023 +0200 layout commitee8a9843b3
Author: lesion <lesion@autistici.org> Date: Tue Mar 28 21:48:26 2023 +0200 minor commit2608761a44
Author: lesion <lesion@autistici.org> Date: Tue Mar 28 19:04:49 2023 +0200 update deps commitd7c8de7580
Author: lesion <lesion@autistici.org> Date: Tue Mar 28 19:04:41 2023 +0200 minor commit6b55ba1708
Author: lesion <lesion@autistici.org> Date: Tue Mar 28 19:04:16 2023 +0200 use new luxon in rss template, fixing locale and timezone #254 commitfc52107bd9
Author: lesion <lesion@autistici.org> Date: Tue Mar 28 19:02:08 2023 +0200 use luxon instead of dayjs server side too commitf5604a03bc
Author: lesion <lesion@autistici.org> Date: Tue Mar 28 18:55:57 2023 +0200 unit test for recurrent events commit3e81d1dfb3
Merge:f960400
e750fc8
Author: lesion <lesion@autistici.org> Date: Tue Mar 28 18:51:34 2023 +0200 Merge remote-tracking branch 'weblate/master' commitf960400085
Author: lesion <lesion@autistici.org> Date: Mon Mar 27 17:19:27 2023 +0200 improve index/tag/place layout commit0682feaaf8
Author: lesion <lesion@autistici.org> Date: Mon Mar 27 17:18:57 2023 +0200 minor with theme admin colors commite750fc8e81
Author: josé m <correoxm@disroot.org> Date: Sun Mar 26 07:48:06 2023 +0000 Translated using Weblate (Galician) Currently translated at 100.0% (314 of 314 strings) Translation: Gancio/Web Translate-URL: https://hosted.weblate.org/projects/gancio/web/gl/ commit4c74fd3227
Author: gallegonovato <fran-carro@hotmail.es> Date: Sat Mar 25 11:23:48 2023 +0000 Translated using Weblate (Spanish) Currently translated at 100.0% (314 of 314 strings) Translation: Gancio/Web Translate-URL: https://hosted.weblate.org/projects/gancio/web/es/ commit428a94290f
Author: joenepraat <joenepraat@posteo.org> Date: Thu Mar 23 15:15:58 2023 +0000 Translated using Weblate (Dutch) Currently translated at 95.5% (299 of 313 strings) Translation: Gancio/Web Translate-URL: https://hosted.weblate.org/projects/gancio/web/nl/ commit373f78cd4e
Author: joenepraat <joenepraat@posteo.org> Date: Thu Mar 23 17:47:21 2023 +0000 Translated using Weblate (Dutch) Currently translated at 7.6% (1 of 13 strings) Translation: Gancio/Email Translate-URL: https://hosted.weblate.org/projects/gancio/email/nl/ commit2d11d88e8f
Merge:ea3066c
e2fd5f8
Author: lesion <lesion@autistici.org> Date: Sat Mar 25 09:34:44 2023 +0100 Merge remote-tracking branch 'weblate/master' commite2fd5f8b93
Author: joenepraat <joenepraat@posteo.org> Date: Thu Mar 23 15:15:58 2023 +0000 Translated using Weblate (Dutch) Currently translated at 95.5% (299 of 313 strings) Translation: Gancio/Web Translate-URL: https://hosted.weblate.org/projects/gancio/web/nl/ commit8280eb7c7a
Author: joenepraat <joenepraat@posteo.org> Date: Thu Mar 23 17:47:21 2023 +0000 Translated using Weblate (Dutch) Currently translated at 7.6% (1 of 13 strings) Translation: Gancio/Email Translate-URL: https://hosted.weblate.org/projects/gancio/email/nl/ commitea3066c34e
Author: lesion <lesion@autistici.org> Date: Fri Mar 24 16:03:08 2023 +0100 people could choose custom colors commit8149ea23da
Author: lesion <lesion@autistici.org> Date: Wed Mar 22 15:39:02 2023 +0100 moving vuetify configuration to "middleware" commit7e6130155a
Author: lesion <lesion@autistici.org> Date: Fri Mar 10 21:58:02 2023 +0100 start with custom color commite2b07a06bd
Author: lesion <lesion@autistici.org> Date: Thu Mar 23 13:11:15 2023 +0100 release WPGancio 1.7 commitb91774ac39
Author: lesion <lesion@autistici.org> Date: Thu Mar 23 13:09:10 2023 +0100 fix tags merge in wp plugin and end_datetime commitc8493d0810
Author: lesion <lesion@autistici.org> Date: Wed Mar 22 18:45:48 2023 +0100 use new $time plugin instead of filters, fix #252 commit780938ef91
Author: lesion <lesion@autistici.org> Date: Wed Mar 22 16:51:02 2023 +0100 v1.6.7 commit442f88f322
Author: lesion <lesion@autistici.org> Date: Wed Mar 22 16:49:37 2023 +0100 minor commit9f90df2bfc
Author: lesion <lesion@autistici.org> Date: Wed Mar 22 16:24:08 2023 +0100 v.1.6.6 commit71fdeb6ff8
Author: lesion <lesion@autistici.org> Date: Wed Mar 22 16:17:02 2023 +0100 some old event was flagged has multidate but without an end_datetime? fix #245 commite6977368c5
Author: lesion <lesion@autistici.org> Date: Wed Mar 22 13:41:50 2023 +0100 revert Intl, node is not ready, fix #250 commiteb2bf32162
Author: lesion <lesion@autistici.org> Date: Tue Mar 21 16:47:47 2023 +0100 release v1.6.5 commit0ebb467630
Author: lesion <lesion@autistici.org> Date: Tue Mar 21 01:15:35 2023 +0100 minor commit934466b2ec
Author: lesion <lesion@autistici.org> Date: Mon Mar 20 21:09:58 2023 +0100 add modules in package commitec62ad9ba8
Author: lesion <lesion@autistici.org> Date: Mon Mar 20 21:09:43 2023 +0100 v-lazy in collection page too commitd9f093fdbb
Author: lesion <lesion@autistici.org> Date: Mon Mar 20 12:55:17 2023 +0100 releasing v1.6.5 commit2ffd2aff82
Author: lesion <lesion@autistici.org> Date: Mon Mar 20 12:53:41 2023 +0100 enable task manager in dev mode commit02f138e0f2
Author: lesion <lesion@autistici.org> Date: Mon Mar 20 12:40:39 2023 +0100 update CHANGELOG commitb8e096ee39
Author: lesion <lesion@autistici.org> Date: Mon Mar 20 12:40:19 2023 +0100 minor with 2w recurrent event frequency commit8f221fb69c
Author: lesion <lesion@autistici.org> Date: Sun Mar 19 23:33:55 2023 +0100 minor commit079bcd4af2
Merge:99d78e2
ae990fc
Author: lesion <lesion@autistici.org> Date: Sun Mar 19 23:28:44 2023 +0100 Merge remote-tracking branch 'sedum/feat/ssr-proxy' commit99d78e2492
Author: lesion <lesion@autistici.org> Date: Sun Mar 19 23:26:57 2023 +0100 Squashed commit of the following: commit5c0d380740
Author: lesion <lesion@autistici.org> Date: Sun Mar 19 23:22:25 2023 +0100 update yarn.lock commit909ee71ecb
Author: lesion <lesion@autistici.org> Date: Sun Mar 19 23:22:09 2023 +0100 Squashed commit of the following: commitfc8a9f4506
Author: lesion <lesion@autistici.org> Date: Tue Mar 14 16:42:24 2023 +0100 address some issues with recurrent events, fix #247 commitf7357666ca
Author: lesion <lesion@autistici.org> Date: Tue Mar 14 16:16:52 2023 +0100 fix event import from URL commite1bca6f46a
Author: lesion <lesion@autistici.org> Date: Tue Mar 14 16:15:42 2023 +0100 add Duch (nl) locale (thanks @jeoenepraat) commit5f8afdbc12
Merge:57a052a
92ca5ab
Author: lesion <lesion@autistici.org> Date: Tue Mar 14 11:39:50 2023 +0100 Merge remote-tracking branch 'weblate/master' commit57a052a7fa
Merge:63d1d2e
55137d2
Author: lesion <lesion@autistici.org> Date: Tue Mar 14 11:39:33 2023 +0100 Merge commit '55137d2ac23549e633f36ad10139fd4168c2645f' commit92ca5abf5e
Author: joenepraat <joenepraat@posteo.org> Date: Fri Mar 10 23:16:32 2023 +0000 Translated using Weblate (Dutch) Currently translated at 68.3% (214 of 313 strings) Translation: Gancio/Web Translate-URL: https://hosted.weblate.org/projects/gancio/web/nl/ commit63d1d2ee53
Author: lesion <lesion@autistici.org> Date: Thu Mar 9 21:41:06 2023 +0100 minor commitd2759a55a5
Author: lesion <lesion@autistici.org> Date: Thu Mar 9 21:38:39 2023 +0100 wrong user / admin merge dark theme settings - fix #244 commitb401d829db
Author: lesion <lesion@autistici.org> Date: Thu Mar 9 21:24:45 2023 +0100 remove a small warning commitccffe5f7b0
Author: lesion <lesion@autistici.org> Date: Fri Feb 24 11:40:36 2023 +0100 push tags on release commit55137d2ac2
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu Feb 23 23:56:16 2023 +0000 Bump sequelize from 6.28.0 to 6.29.0 Bumps [sequelize](https://github.com/sequelize/sequelize) from 6.28.0 to 6.29.0. - [Release notes](https://github.com/sequelize/sequelize/releases) - [Commits](https://github.com/sequelize/sequelize/compare/v6.28.0...v6.29.0) --- updated-dependencies: - dependency-name: sequelize dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> commitb654f29d8b
Author: lesion <lesion@autistici.org> Date: Wed Feb 22 13:21:17 2023 +0100 update changelog commit0cd1ee9d89
Author: lesion <lesion@autistici.org> Date: Wed Feb 22 13:17:29 2023 +0100 increase rate limit max requests per minutes commitb6dafc082e
Author: lesion <lesion@autistici.org> Date: Wed Feb 22 08:45:39 2023 +0100 minor commit0fa7769844
Author: lesion <lesion@autistici.org> Date: Wed Feb 22 08:45:18 2023 +0100 location saving is not working when geocoding is disabled, fix #238 commit07f9e2d9ee
Author: lesion <lesion@autistici.org> Date: Wed Feb 22 08:33:40 2023 +0100 really fix #232 commitbae930799e
Author: lesion <lesion@autistici.org> Date: Wed Feb 22 08:33:09 2023 +0100 downgrade mariadb (sequelize is not ready) commitd733d7fef1
Author: lesion <lesion@autistici.org> Date: Wed Feb 22 00:16:28 2023 +0100 aargh commit98b22aad70
Author: lesion <lesion@autistici.org> Date: Tue Feb 21 00:56:06 2023 +0100 minor commitfc098b603d
Author: lesion <lesion@autistici.org> Date: Tue Feb 21 00:55:44 2023 +0100 missing i18n in setup, fix #239 commit3eaf72af19
Merge:bba196b
d6c6034
Author: lesion <lesion@autistici.org> Date: Mon Feb 20 21:17:37 2023 +0100 Merge remote-tracking branch 'weblate/master' commitbba196b068
Author: lesion <lesion@autistici.org> Date: Sat Feb 18 00:05:52 2023 +0100 update changelog, v1.6.3 commitbb9f7cca47
Author: lesion <lesion@autistici.org> Date: Sat Feb 18 00:04:28 2023 +0100 minor commit80d2dbd06b
Author: lesion <lesion@autistici.org> Date: Fri Feb 17 23:40:28 2023 +0100 minor commitd6c6034630
Author: fadelkon <fadelkon@posteo.net> Date: Thu Feb 16 22:09:23 2023 +0000 Translated using Weblate (Catalan) Currently translated at 100.0% (313 of 313 strings) Translation: Gancio/Web Translate-URL: https://hosted.weblate.org/projects/gancio/web/ca/ commitd125cf1506
Author: lesion <lesion@autistici.org> Date: Fri Feb 17 21:56:31 2023 +0100 set a default user_locale path commit4367960a62
Merge:c8cc5c6
87dd179
Author: lesion <lesion@autistici.org> Date: Tue Feb 7 17:46:58 2023 +0100 Merge branch 'master' into gh commitc8cc5c6c97
Merge:88e0c90
550e221
Author: lesion <lesion@autistici.org> Date: Mon Jan 9 17:15:21 2023 +0100 Merge branch 'master' into gh commit88e0c90a66
Merge:421aa12
f212ac1
Author: lesion <lesion@autistici.org> Date: Thu Dec 15 09:54:41 2022 +0100 Merge branch 'master' into gh commit421aa12781
Merge:5f6cc46
b3488e7
Author: lesion <lesion@autistici.org> Date: Wed Sep 28 12:26:08 2022 +0200 Merge branch 'master' into gh commit5f6cc46cdc
Merge:b66feb9
171d968
Author: lesion <lesion@autistici.org> Date: Mon Aug 8 00:08:12 2022 +0200 Merge branch 'master' into gh commitb66feb92e2
Merge:80c55d5
05d068f
Author: lesion <lesion@autistici.org> Date: Tue Jun 21 23:48:40 2022 +0200 Merge branch 'master' into gh commit80c55d5601
Merge:814090e
a154fdf
Author: lesion <lesion@autistici.org> Date: Mon Jun 6 17:27:00 2022 +0200 Merge branch 'master' into gh commit814090e9b6
Merge:616c542
2e3aba9
Author: lesion <lesion@autistici.org> Date: Mon Jun 6 17:19:31 2022 +0200 Merge branch 'master' into gh commit616c54229a
Merge:e4cb22e
82dcaf9
Author: lesion <lesion@autistici.org> Date: Mon Jun 6 16:57:05 2022 +0200 Merge branch 'master' into gh commite4cb22ee33
Merge:5dddfbd
8657937
Author: lesion <lesion@autistici.org> Date: Fri Mar 11 23:41:22 2022 +0100 Merge branch 'master' into gh commit5dddfbd29e
Merge:60e9d95
10c6b0d
Author: lesion <lesion@autistici.org> Date: Fri Mar 11 23:22:12 2022 +0100 Merge branch 'master' into gh commit60e9d95ba8
Merge:79445ca
ad93f83
Author: lesion <lesion@autistici.org> Date: Tue Dec 7 01:35:18 2021 +0100 Merge branch 'master' into gh commit79445ca8a7
Merge:9472d8d
cd313ef
Author: les <lesion@autistici.org> Date: Thu Jun 24 21:52:25 2021 +0200 Merge branch 'master' into gh commit9472d8d919
Merge:f960149
9e9643e
Author: les <lesion@autistici.org> Date: Fri Mar 26 22:27:41 2021 +0100 Merge branch 'dev' into gh commitf9601492dc
Author: les <lesion@autistici.org> Date: Fri Dec 6 11:30:41 2019 +0100 update dependencies commitf8c7fa2b45
Author: les <lesion@autistici.org> Date: Fri Dec 6 11:41:13 2019 +0100 minor commit33ca266535
Author: les <lesion@autistici.org> Date: Fri Dec 6 11:38:15 2019 +0100 prepare gh as a mirror commit5c88754116
Author: lesion <lesion@autistici.org> Date: Sun Mar 19 23:18:40 2023 +0100 update deps commit7eac4fce32
Author: lesion <lesion@autistici.org> Date: Sun Mar 19 23:18:25 2023 +0100 refactoring event detail page commitdc9ca88bc6
Author: lesion <lesion@autistici.org> Date: Sun Mar 19 23:17:35 2023 +0100 show hide boosts/bookmarks, fix #241 commitd4a25b1dd0
Author: lesion <lesion@autistici.org> Date: Sun Mar 19 23:13:58 2023 +0100 minor with unixFormat commit239d6bcab1
Author: lesion <lesion@autistici.org> Date: Sun Mar 19 23:12:25 2023 +0100 minor commitb149f980db
Author: lesion <lesion@autistici.org> Date: Sun Mar 19 23:12:05 2023 +0100 minor commit6f2955c584
Author: lesion <lesion@autistici.org> Date: Sun Mar 19 23:11:49 2023 +0100 minor commitdd586c38c9
Author: lesion <lesion@autistici.org> Date: Sun Mar 19 23:11:31 2023 +0100 minor on style commit544823717b
Author: lesion <lesion@autistici.org> Date: Sun Mar 19 23:11:15 2023 +0100 fix multidate issue, go to event on save commit9ef0c75d03
Author: lesion <lesion@autistici.org> Date: Sun Mar 19 23:09:47 2023 +0100 use v-lazy, improve search, full tag/place events commitac91072b79
Author: lesion <lesion@autistici.org> Date: Sun Mar 19 22:47:51 2023 +0100 increase DDOS limiter to 250 req/min commitd0ca92efb4
Author: lesion <lesion@autistici.org> Date: Sun Mar 19 22:47:14 2023 +0100 update changelog commit2d54f19225
Author: lesion <lesion@autistici.org> Date: Sun Mar 19 22:46:51 2023 +0100 use luxon instead of dayjs, new $time plugin commitae990fc370
Author: sedum <sedum@oziosi.org> Date: Thu Mar 16 17:29:48 2023 +0100 add support for server side http proxy, close #240 commitfc8a9f4506
Author: lesion <lesion@autistici.org> Date: Tue Mar 14 16:42:24 2023 +0100 address some issues with recurrent events, fix #247 commitf7357666ca
Author: lesion <lesion@autistici.org> Date: Tue Mar 14 16:16:52 2023 +0100 fix event import from URL commite1bca6f46a
Author: lesion <lesion@autistici.org> Date: Tue Mar 14 16:15:42 2023 +0100 add Duch (nl) locale (thanks @jeoenepraat) commit5f8afdbc12
Merge:57a052a
92ca5ab
Author: lesion <lesion@autistici.org> Date: Tue Mar 14 11:39:50 2023 +0100 Merge remote-tracking branch 'weblate/master' commit57a052a7fa
Merge:63d1d2e
55137d2
Author: lesion <lesion@autistici.org> Date: Tue Mar 14 11:39:33 2023 +0100 Merge commit '55137d2ac23549e633f36ad10139fd4168c2645f' commit92ca5abf5e
Author: joenepraat <joenepraat@posteo.org> Date: Fri Mar 10 23:16:32 2023 +0000 Translated using Weblate (Dutch) Currently translated at 68.3% (214 of 313 strings) Translation: Gancio/Web Translate-URL: https://hosted.weblate.org/projects/gancio/web/nl/ commit63d1d2ee53
Author: lesion <lesion@autistici.org> Date: Thu Mar 9 21:41:06 2023 +0100 minor commitd2759a55a5
Author: lesion <lesion@autistici.org> Date: Thu Mar 9 21:38:39 2023 +0100 wrong user / admin merge dark theme settings - fix #244 commitb401d829db
Author: lesion <lesion@autistici.org> Date: Thu Mar 9 21:24:45 2023 +0100 remove a small warning commit55137d2ac2
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu Feb 23 23:56:16 2023 +0000 Bump sequelize from 6.28.0 to 6.29.0 Bumps [sequelize](https://github.com/sequelize/sequelize) from 6.28.0 to 6.29.0. - [Release notes](https://github.com/sequelize/sequelize/releases) - [Commits](https://github.com/sequelize/sequelize/compare/v6.28.0...v6.29.0) --- updated-dependencies: - dependency-name: sequelize dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> commit4367960a62
Merge:c8cc5c6
87dd179
Author: lesion <lesion@autistici.org> Date: Tue Feb 7 17:46:58 2023 +0100 Merge branch 'master' into gh commitc8cc5c6c97
Merge:88e0c90
550e221
Author: lesion <lesion@autistici.org> Date: Mon Jan 9 17:15:21 2023 +0100 Merge branch 'master' into gh commit88e0c90a66
Merge:421aa12
f212ac1
Author: lesion <lesion@autistici.org> Date: Thu Dec 15 09:54:41 2022 +0100 Merge branch 'master' into gh commit421aa12781
Merge:5f6cc46
b3488e7
Author: lesion <lesion@autistici.org> Date: Wed Sep 28 12:26:08 2022 +0200 Merge branch 'master' into gh commit5f6cc46cdc
Merge:b66feb9
171d968
Author: lesion <lesion@autistici.org> Date: Mon Aug 8 00:08:12 2022 +0200 Merge branch 'master' into gh commitb66feb92e2
Merge:80c55d5
05d068f
Author: lesion <lesion@autistici.org> Date: Tue Jun 21 23:48:40 2022 +0200 Merge branch 'master' into gh commit80c55d5601
Merge:814090e
a154fdf
Author: lesion <lesion@autistici.org> Date: Mon Jun 6 17:27:00 2022 +0200 Merge branch 'master' into gh commit814090e9b6
Merge:616c542
2e3aba9
Author: lesion <lesion@autistici.org> Date: Mon Jun 6 17:19:31 2022 +0200 Merge branch 'master' into gh commit616c54229a
Merge:e4cb22e
82dcaf9
Author: lesion <lesion@autistici.org> Date: Mon Jun 6 16:57:05 2022 +0200 Merge branch 'master' into gh commite4cb22ee33
Merge:5dddfbd
8657937
Author: lesion <lesion@autistici.org> Date: Fri Mar 11 23:41:22 2022 +0100 Merge branch 'master' into gh commit5dddfbd29e
Merge:60e9d95
10c6b0d
Author: lesion <lesion@autistici.org> Date: Fri Mar 11 23:22:12 2022 +0100 Merge branch 'master' into gh commit60e9d95ba8
Merge:79445ca
ad93f83
Author: lesion <lesion@autistici.org> Date: Tue Dec 7 01:35:18 2021 +0100 Merge branch 'master' into gh commit79445ca8a7
Merge:9472d8d
cd313ef
Author: les <lesion@autistici.org> Date: Thu Jun 24 21:52:25 2021 +0200 Merge branch 'master' into gh commit9472d8d919
Merge:f960149
9e9643e
Author: les <lesion@autistici.org> Date: Fri Mar 26 22:27:41 2021 +0100 Merge branch 'dev' into gh commitf9601492dc
Author: les <lesion@autistici.org> Date: Fri Dec 6 11:30:41 2019 +0100 update dependencies commitf8c7fa2b45
Author: les <lesion@autistici.org> Date: Fri Dec 6 11:41:13 2019 +0100 minor commit33ca266535
Author: les <lesion@autistici.org> Date: Fri Dec 6 11:38:15 2019 +0100 prepare gh as a mirror
This commit is contained in:
parent
4da715dd39
commit
08e4fe856c
72 changed files with 2912 additions and 2254 deletions
28
CHANGELOG
28
CHANGELOG
|
@ -1,5 +1,33 @@
|
|||
All notable changes to this project will be documented in this file.
|
||||
|
||||
### 1.6.8
|
||||
- use new $time in admins table, fix #252
|
||||
- fix some WPGancio issues
|
||||
- new custom color feature!
|
||||
- update locales (durch, spanish, galician)
|
||||
- improve index, tag, place page layout
|
||||
- use luxon instead of dayjs server side, fix #254
|
||||
|
||||
### 1.6.7 - 22 mar '23
|
||||
- node is not Intl ready, fix #250
|
||||
- fix old smultidate events without an end_datetime!
|
||||
- fix a small issue during setup
|
||||
|
||||
### 1.6.5 - 21 mar '23
|
||||
- optimize home page using lazy loading
|
||||
- add support for server side http proxy (thanks @sedum)
|
||||
- add Duch (nl) locale (thanks @jeoenepraat)
|
||||
- fix #244, dark theme user / admin preference merge issue
|
||||
- fix some issues with recurrent events, #247
|
||||
- improve search flow (order by, press enter or icon...)
|
||||
- filters / helpers refactoring
|
||||
- tag and place pages list all events (past too)
|
||||
- show boosts/bookmarks, fix #241
|
||||
- go to event on save/update
|
||||
- use luxon instead of dayjs, new $time plugin
|
||||
- fix event import from URL
|
||||
- remove an annoying warning from logs
|
||||
|
||||
### 1.6.4 - 22 feb '23
|
||||
- add missing i18n during setup
|
||||
- really fix #232
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
import dayjs from 'dayjs'
|
||||
|
||||
export function attributesFromEvents(_events) {
|
||||
// merge events with same date
|
||||
let attributes = []
|
||||
const now = dayjs().unix()
|
||||
for (let e of _events) {
|
||||
const key = dayjs.unix(e.start_datetime).tz().format('MMDD') // Math.floor(e.start_datetime/(3600*24)) // dayjs.unix(e.start_datetime).tz().format('YYYYMMDD')
|
||||
const c = (e.end_datetime || e.start_datetime) < now ? 'vc-past' : ''
|
||||
|
||||
if (e.multidate === true) {
|
||||
attributes.push({
|
||||
dates: { start: new Date(e.start_datetime * 1000), end: new Date(e.end_datetime * 1000) },
|
||||
highlight: {
|
||||
start: { fillMode: 'outline' },
|
||||
base: { fillMode: 'light' },
|
||||
end: { fillMode: 'outline' },
|
||||
}
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
const i = attributes.find(a => a.day === key)
|
||||
if (!i) {
|
||||
attributes.push({
|
||||
day: key, key: e.id, n: 1, dates: new Date(e.start_datetime * 1000),
|
||||
dot: { color: 'teal', class: c }
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
i.n++
|
||||
if (i.n >= 20) {
|
||||
i.dot = { color: 'purple', class: c }
|
||||
} else if (i.n >= 10) {
|
||||
i.dot = { color: 'red', class: c }
|
||||
} else if (i.n >= 5) {
|
||||
i.dot = { color: 'orange', class: c }
|
||||
} else if (i.n >= 3) {
|
||||
i.dot = { color: 'yellow', class: c }
|
||||
} else {
|
||||
i.dot = { color: 'teal', class: c }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// add a bar to highlight today
|
||||
attributes.push({ key: 'today', dates: new Date(), highlight: { color: 'green', fillMode: 'outline' } })
|
||||
|
||||
return attributes
|
||||
}
|
|
@ -108,7 +108,11 @@ li {
|
|||
/* margin-top: 16px;
|
||||
margin-right: 16px; */
|
||||
transition: all .5s;
|
||||
min-height: 288px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12);
|
||||
border-radius: 4px;
|
||||
|
||||
}
|
||||
|
||||
.event .title {
|
||||
|
@ -127,11 +131,10 @@ li {
|
|||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.event .place span {
|
||||
white-space: nowrap;
|
||||
/* .event .place span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
} */
|
||||
|
||||
.event a {
|
||||
text-decoration: none;
|
||||
|
@ -148,6 +151,7 @@ li {
|
|||
|
||||
#event {
|
||||
max-width: 1200px;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
/* .tags .v-chip .v-chip__content {
|
||||
|
|
|
@ -9,29 +9,26 @@
|
|||
@input='click'
|
||||
@update:from-page='updatePage'
|
||||
:locale='$i18n.locale'
|
||||
:popover="{ visibility: 'click' }"
|
||||
:attributes='attributes'
|
||||
transition='fade'
|
||||
aria-label='Calendar'
|
||||
is-expanded
|
||||
is-inline)
|
||||
.calh.d-flex.justify-center.align-center(slot='placeholder')
|
||||
v-progress-circular(indeterminate)
|
||||
.calh.text-center(slot='placeholder')
|
||||
v-progress-circular.mt-5(indeterminate color='primary')
|
||||
|
||||
</template>
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
import dayjs from 'dayjs'
|
||||
import { mdiChevronDown, mdiClose } from '@mdi/js'
|
||||
import { attributesFromEvents } from '../assets/helper'
|
||||
|
||||
export default {
|
||||
name: 'Calendar',
|
||||
data () {
|
||||
const month = dayjs.tz().month() + 1
|
||||
const year = dayjs.tz().year()
|
||||
data ({$time}) {
|
||||
const month = $time.currentMonth()
|
||||
const year = $time.currentYear()
|
||||
return {
|
||||
mdiChevronDown, mdiClose,
|
||||
selectedDate: null,
|
||||
|
@ -42,15 +39,15 @@ export default {
|
|||
...mapState(['settings', 'events']),
|
||||
...mapGetters(['is_dark']),
|
||||
attributes () {
|
||||
return attributesFromEvents(this.events)
|
||||
return this.$time.attributesFromEvents(this.events)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updatePage (page) {
|
||||
if (page.month !== this.page.month || page.year !== this.page.year) {
|
||||
this.$root.$emit('monthchange', page)
|
||||
this.page.month = page.month
|
||||
this.page.year = page.year
|
||||
this.$root.$emit('monthchange', page)
|
||||
}
|
||||
},
|
||||
click (day) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template lang="pug">
|
||||
v-col(cols=12)
|
||||
.text-center
|
||||
v-btn-toggle.v-col-6.flex-column.flex-sm-row(v-model='type' color='primary' @change='type => change("type", type)')
|
||||
v-btn-toggle.v-col-6.flex-column.flex-sm-row(v-if="!event.parentId && !event.recurrent" v-model='type' color='primary' @change='type => change("type", type)')
|
||||
v-btn(value='normal' label="normal") {{ $t('event.normal') }}
|
||||
v-btn(v-if='settings.allow_multidate_event' value='multidate' label='multidate') {{ $t('event.multidate') }}
|
||||
v-btn(v-if='settings.allow_recurrent_event' value='recurrent' label="recurrent") {{ $t('event.recurrent') }}
|
||||
|
@ -10,7 +10,6 @@ v-col(cols=12)
|
|||
|
||||
v-btn-toggle.v-col-6.flex-column.flex-sm-row(v-if='type === "recurrent"' color='primary' :value='value.recurrent.frequency' @change='fq => change("frequency", fq)')
|
||||
v-btn(v-for='f in frequencies' :key='f.value' :value='f.value') {{ f.text }}
|
||||
|
||||
client-only
|
||||
.datePicker.mt-3
|
||||
v-input(:value='fromDate' :rules="[$validators.required('common.when')]")
|
||||
|
@ -23,10 +22,9 @@ v-col(cols=12)
|
|||
:is-dark="is_dark"
|
||||
is-inline
|
||||
is-expanded
|
||||
:min-date='type !== "recurrent" && new Date()')
|
||||
//- template(#placeholder)
|
||||
.d-flex.calh.justify-center(slot='placeholder')
|
||||
v-progress-circular(indeterminate)
|
||||
:min-date='new Date()')
|
||||
.calh.text-center(slot='placeholder')
|
||||
v-progress-circular(indeterminate color='primary')
|
||||
|
||||
div.text-center.mb-2(v-if='type === "recurrent"')
|
||||
span(v-if='value.recurrent.frequency !== "1m" && value.recurrent.frequency !== "2m"') {{ whenPatterns }}
|
||||
|
@ -94,10 +92,9 @@ v-col(cols=12)
|
|||
|
||||
</template>
|
||||
<script>
|
||||
import dayjs from 'dayjs'
|
||||
import { DateTime } from 'luxon'
|
||||
import { mapState, mapActions, mapGetters } from 'vuex'
|
||||
import List from '@/components/List'
|
||||
import { attributesFromEvents } from '../assets/helper'
|
||||
import { mdiClockTimeFourOutline, mdiClockTimeEightOutline, mdiClose } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
|
@ -127,34 +124,33 @@ export default {
|
|||
fromDate () {
|
||||
if (this.value.from) {
|
||||
if (this.value.multidate) {
|
||||
return ({ start: dayjs(this.value.from).toDate(), end: dayjs(this.value.due).toDate() })
|
||||
return ({ start: new Date(this.value.from), end: new Date(this.value.due) })
|
||||
} else {
|
||||
return new Date(this.value.from)
|
||||
}
|
||||
}
|
||||
},
|
||||
todayEvents() {
|
||||
const start = dayjs.tz(this.value.from).startOf('day').unix()
|
||||
const end = dayjs.tz(this.value.from).endOf('day').unix()
|
||||
const start = this.$time.startOfDay(this.value.from)
|
||||
const end = this.$time.endOfDay(this.value.from)
|
||||
return this.events.filter(e => e.start_datetime >= start && e.start_datetime <= end)
|
||||
},
|
||||
attributes() {
|
||||
return attributesFromEvents(this.events.filter(e => e.id !== this.event.id))
|
||||
return this.$time.attributesFromEvents(this.events.filter(e => e.id !== this.event.id))
|
||||
},
|
||||
whenPatterns() {
|
||||
if (!this.value.from) { return }
|
||||
const date = dayjs(this.value.from)
|
||||
const date = DateTime.fromJSDate(this.value.from)
|
||||
|
||||
const freq = this.value.recurrent.frequency
|
||||
const weekDay = date.format('dddd')
|
||||
const weekDay = date.toFormat('EEEE')
|
||||
if (freq === '1w' || freq === '2w') {
|
||||
return this.$t(`event.recurrent_${freq}_days`, { days: weekDay }).toUpperCase()
|
||||
} else if (freq === '1m' || freq === '2m') {
|
||||
const monthDay = date.format('D')
|
||||
const n = Math.floor((monthDay - 1) / 7) + 1
|
||||
const n = Math.floor((date.day) / 7) + 1
|
||||
|
||||
const patterns = [
|
||||
{ label: this.$t(`event.recurrent_${freq}_days`, { days: monthDay }), key: 'ordinal' }
|
||||
{ label: this.$t(`event.recurrent_${freq}_days`, { days: date.day }), key: 'ordinal' }
|
||||
// { label: this.$tc(`event.recurrent_${freq}_ordinal`, { n, days: weekDay }), key: 'weekday' }
|
||||
]
|
||||
|
||||
|
@ -168,7 +164,7 @@ export default {
|
|||
}
|
||||
|
||||
// if selected day is in last week, propose also this type of selection
|
||||
const lastWeek = date.daysInMonth() - monthDay < 7
|
||||
const lastWeek = date.endOf('month').day - date.day < 7
|
||||
if (lastWeek) {
|
||||
patterns.push(
|
||||
{
|
||||
|
@ -215,13 +211,14 @@ export default {
|
|||
if (from && from.start) {
|
||||
from = from.start
|
||||
}
|
||||
let due = this.value.due
|
||||
let due = this.value.multidate ? null : this.value.due
|
||||
if (due && due.start) {
|
||||
due = due.start
|
||||
}
|
||||
this.$emit('input', { ...this.value, from, due, recurrent: null, multidate: false })
|
||||
}
|
||||
} else if (what === 'frequency') {
|
||||
if (typeof value === 'undefined') { value = '1w' }
|
||||
this.$emit('input', { ...this.value, recurrent: { ...this.value.recurrent, frequency: value } })
|
||||
} else if (what === 'recurrentType') {
|
||||
this.$emit('input', { ...this.value, recurrent: { ...this.value.recurrent, type: value } })
|
||||
|
@ -238,28 +235,22 @@ export default {
|
|||
const [fromHour, fromMinute] = this.value.fromHour.split(':')
|
||||
if (!this.value.multidate) {
|
||||
if (hour < fromHour) {
|
||||
this.value.due = dayjs(this.value.from).add(1, 'day').toDate()
|
||||
this.value.due = DateTime.fromJSDate(this.value.from, {zone: this.settings.instance_timezone}).plus({day: 1}).toJSDate()
|
||||
} else {
|
||||
this.value.due = dayjs(this.value.from).toDate()
|
||||
this.value.due = DateTime.fromJSDate(this.value.from, {zone: this.settings.instance_timezone}).toJSDate()
|
||||
}
|
||||
} else {
|
||||
this.value.due = DateTime.fromJSDate(this.value.due, {zone: this.settings.instance_timezone}).set({ hour, minute }).toJSDate()
|
||||
}
|
||||
} else {
|
||||
this.value.due = null
|
||||
if (!this.value.multidate) {
|
||||
this.value.due = null
|
||||
} else {
|
||||
this.value.due = DateTime.fromJSDate(this.value.due, {zone: this.settings.instance_timezone}).set({ hour: 23, minute:59 }).toJSDate()
|
||||
}
|
||||
}
|
||||
this.$emit('input', { ...this.value, dueHour: value })
|
||||
|
||||
// if (value) {
|
||||
// // const [hour, minute] = value.split(':')
|
||||
// // let due = dayjs.tz(this.value.due || this.value.from).hour(Number(hour)).minute(Number(minute)).second(0)
|
||||
|
||||
// // add a day
|
||||
// // if (dayjs(this.value.from).hour() > Number(hour) && !this.value.multidate) {
|
||||
// // due = due.add(1, 'day')
|
||||
// // }
|
||||
// // due = due.hour(hour).minute(minute).second(0)
|
||||
// } else {
|
||||
// this.$emit('input', { ...this.value, dueHour: null })
|
||||
// }
|
||||
// change date in calendar (could be a range or a recurrent event...)
|
||||
} else if (what === 'date') {
|
||||
if (value === null) {
|
||||
|
|
|
@ -224,7 +224,7 @@ export default {
|
|||
transition: opacity .5s;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
overflow: scroll;
|
||||
overflow: auto;
|
||||
// position: absolute;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<template lang="pug">
|
||||
v-card.h-event.event.d-flex(itemscope itemtype="https://schema.org/Event")
|
||||
div.h-event(itemscope itemtype="https://schema.org/Event")
|
||||
nuxt-link(:to='`/event/${event.slug || event.id}`' itemprop="url")
|
||||
MyPicture(v-if='!hide_thumbs' :event='event' thumb :lazy='lazy')
|
||||
v-icon.float-right.mr-1(v-if='event.parentId' color='success' v-text='mdiRepeat')
|
||||
.title.p-name(itemprop="name") {{ event.title }}
|
||||
|
||||
v-card-text.body.pt-0.pb-0
|
||||
time.dt-start.subtitle-1(:datetime='event.start_datetime | unixFormat("YYYY-MM-DD HH:mm")' itemprop="startDate" :content="event.start_datetime | unixFormat('YYYY-MM-DDTHH:mm')") <v-icon v-text='mdiCalendar'></v-icon> {{ event | when }}
|
||||
.d-none.dt-end(v-if='event.end_datetime' itemprop="endDate" :content="event.end_datetime | unixFormat('YYYY-MM-DDTHH:mm')") {{ event.end_datetime | unixFormat('YYYY-MM-DD HH:mm') }}
|
||||
time.dt-start.subtitle-1(:datetime='$time.unixFormat(event.start_datetime, "yyyy-MM-dd HH:mm")' itemprop="startDate" :content="$time.unixFormat(event.start_datetime, \"yyyy-MM-dd'T'HH:mm\")") <v-icon v-text='mdiCalendar'></v-icon> {{ $time.when(event) }}
|
||||
.d-none.dt-end(v-if='event.end_datetime' itemprop="endDate" :content="$time.unixFormat(event.end_datetime,\"yyyy-MM-dd'T'HH:mm\")") {{ $time.unixFormat(event.end_datetime)}}
|
||||
nuxt-link.place.d-block.p-location.pl-0(text :to='`/place/${encodeURIComponent(event.place.name)}`' itemprop="location" itemscope itemtype="https://schema.org/Place") <v-icon v-text='mdiMapMarker'></v-icon> <span itemprop='name'>{{ event.place.name }}</span>
|
||||
.d-none(itemprop='address') {{ event.place.address }}
|
||||
|
||||
|
@ -22,7 +22,7 @@ import MyPicture from '~/components/MyPicture'
|
|||
import { mdiRepeat, mdiCalendar, mdiMapMarker } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
data({ $store }) {
|
||||
data() {
|
||||
return { mdiRepeat, mdiMapMarker, mdiCalendar }
|
||||
},
|
||||
components: {
|
||||
|
|
|
@ -9,7 +9,7 @@ div#list
|
|||
v-for='event in computedEvents'
|
||||
:key='`${event.id}_${event.start_datetime}`' small)
|
||||
v-list-item-content
|
||||
v-list-item-subtitle <v-icon small color='success' v-if='event.parentId' v-text='mdiRepeat'></v-icon> {{event|when}}
|
||||
v-list-item-subtitle <v-icon small color='success' v-if='event.parentId' v-text='mdiRepeat'></v-icon> {{$time.when(event)}}
|
||||
span.primary--text.ml-1 @{{event.place.name}}
|
||||
v-list-item-title(v-text='event.title')
|
||||
</template>
|
||||
|
|
|
@ -5,11 +5,12 @@
|
|||
client-only(v-if='showSearchBar')
|
||||
v-menu(offset-y :close-on-content-click='false' tile)
|
||||
template(v-slot:activator="{on ,attrs}")
|
||||
v-text-field(hide-details outlined
|
||||
:placeholder='$t("common.search")'
|
||||
@input="v => setFilter(['query', v])" clearable :clear-icon='mdiClose')
|
||||
template(v-slot:append v-if='settings.allow_recurrent_event || settings.allow_multidate_event')
|
||||
v-icon(v-text='mdiCog' v-bind='attrs' v-on='on')
|
||||
v-text-field(hide-details outlined v-model='query'
|
||||
:placeholder='$t("common.search")' @click:clear="setFilter(['query', null])"
|
||||
@keypress.enter="setFilter(['query', query])" clearable :clear-icon='mdiClose')
|
||||
template(v-slot:append)
|
||||
v-icon.mr-2(v-if='query' v-text='mdiMagnify' @click="setFilter(['query', query])")
|
||||
v-icon(v-if='settings.allow_recurrent_event || settings.allow_multidate_event' v-text='mdiCog' v-bind='attrs' v-on='on')
|
||||
v-card(outlined :rounded='"0"')
|
||||
v-card-text
|
||||
v-row(dense)
|
||||
|
@ -36,12 +37,12 @@
|
|||
<script>
|
||||
import { mapState, mapActions } from 'vuex'
|
||||
import Calendar from '@/components/Calendar'
|
||||
import { mdiClose, mdiCog } from '@mdi/js'
|
||||
import { mdiClose, mdiCog, mdiMagnify } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
data: ({ $store }) => ({
|
||||
oldRoute: '',
|
||||
mdiClose, mdiCog,
|
||||
mdiClose, mdiCog, mdiMagnify,
|
||||
collections: [],
|
||||
show_recurrent: $store.state.settings.recurrent_event_visible,
|
||||
show_multidate: true,
|
||||
|
|
|
@ -9,7 +9,7 @@ v-container
|
|||
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
|
||||
:items='unconfirmedEvents'
|
||||
:headers='headers')
|
||||
template(v-slot:item.when='{ item }') {{item|when}}
|
||||
template(v-slot:item.when='{ item }') {{$time.when(item)}}
|
||||
template(v-slot:item.actions='{ item }')
|
||||
v-btn(text small @click='confirm(item)' color='success') {{$t('common.confirm')}}
|
||||
v-btn(text small :to='`/event/${item.slug || item.id}`' color='success') {{$t('common.preview')}}
|
||||
|
|
|
@ -41,7 +41,7 @@ v-container
|
|||
template(v-slot:item.content='{ item }')
|
||||
span(v-html='item.data.content')
|
||||
template(v-slot:item.created='{ item }')
|
||||
span {{item.created | dateFormat('lll')}}
|
||||
span {{$time.format(item.created, 'ff')}}
|
||||
template(v-slot:item.user='{ item }')
|
||||
a(:href='item.ap_user.url || item.ap_user.ap_id' target='_blank') {{item.ap_user.preferredUsername}}
|
||||
template(v-slot:item.event='{ item }')
|
||||
|
|
|
@ -28,7 +28,7 @@ v-container
|
|||
:disable='!valid || loading') {{ $t('common.save') }}
|
||||
|
||||
v-card-text
|
||||
v-card(v-for='plugin in plugins' :key='plugin.name' max-width="400" elevation='10' color='secondary' dark)
|
||||
v-card(v-for='plugin in plugins' :key='plugin.name' max-width="400" elevation='10')
|
||||
v-card-title {{ plugin.name }}
|
||||
v-card-text
|
||||
p {{ plugin.description }}
|
||||
|
|
|
@ -79,7 +79,7 @@ v-container
|
|||
import SMTP from './SMTP.vue'
|
||||
import Geolocation from './Geolocation.vue'
|
||||
import { mapActions, mapState } from 'vuex'
|
||||
import moment from 'dayjs'
|
||||
import { DateTime } from 'luxon'
|
||||
import tzNames from './tz.json'
|
||||
import { mdiAlert, mdiArrowRight, mdiMap } from '@mdi/js'
|
||||
const locales = require('../../locales/index')
|
||||
|
@ -147,7 +147,7 @@ export default {
|
|||
}
|
||||
},
|
||||
filteredTimezones () {
|
||||
const current_timezone = moment.tz.guess()
|
||||
const current_timezone = DateTime.local().zoneName
|
||||
tzNames.unshift(current_timezone)
|
||||
return tzNames
|
||||
}
|
||||
|
|
|
@ -52,38 +52,55 @@ v-container
|
|||
v-img.mt-2(:src='`/headerimage.png?${headerImageKey}`' max-height="150px" contain)
|
||||
|
||||
|
||||
|
||||
//- TODO choose theme colors
|
||||
//- v-row
|
||||
//- v-col(v-for='(color, i) in colors' :key='i')
|
||||
//- v-menu(v-model='menu[i]'
|
||||
//- :close-on-content-click="false"
|
||||
//- transition="slide-x-transition"
|
||||
//- offset-y
|
||||
//- absolute
|
||||
//- bottom
|
||||
//- max-width="290px"
|
||||
//- min-width="290px")
|
||||
//- template(v-slot:activator='{ on }')
|
||||
//- v-btn(:color='i' small
|
||||
//- v-on='on') {{i}}
|
||||
//- v-color-picker(light @update:color='c => updateColor(i, c)')
|
||||
|
||||
v-dialog(v-model='linkModal' width='500' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||
v-card
|
||||
v-card-title {{$t('admin.footer_links')}}
|
||||
v-card-title {{$t('admin.colors')}}
|
||||
//- choose theme colors
|
||||
v-card-text
|
||||
v-theme-provider(dark)
|
||||
v-card(max-width='465')
|
||||
v-card-text
|
||||
v-form(v-model='valid' ref='linkModalForm')
|
||||
v-text-field(v-model='link.label'
|
||||
:rules="[$validators.required('common.label')]"
|
||||
label='Label')
|
||||
v-text-field(v-model='link.href'
|
||||
:rules="[$validators.required('common.url')]"
|
||||
:label="$t('common.url')")
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(outlined @click='linkModal=false' color='error') {{$t('common.cancel')}}
|
||||
v-btn(outlined @click='addFooterLink' color='primary' :disabled='!valid') {{$t('common.add')}}
|
||||
span.mr-2(v-for='(color, i) in settings.dark_colors' :key='i')
|
||||
v-menu(v-model='dark_menu[i]'
|
||||
:close-on-content-click="false"
|
||||
transition="slide-y-transition"
|
||||
offset-y
|
||||
top right
|
||||
max-width="290px"
|
||||
min-width="290px")
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn(:color='color' dark small v-on='on') {{i}}
|
||||
v-color-picker(mode='hexa' :value='color' @update:color='c => updateColor("dark", i, c)')
|
||||
|
||||
v-theme-provider(light)
|
||||
v-card.mt-4(max-width='465')
|
||||
v-card-text
|
||||
span.mr-2(v-for='(color, i) in settings.light_colors' :key='i')
|
||||
v-menu(v-model='light_menu[i]'
|
||||
:close-on-content-click="false"
|
||||
transition="slide-y-transition"
|
||||
offset-y
|
||||
top right
|
||||
max-width="290px"
|
||||
min-width="290px")
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn(:color='color' small v-on='on') {{i}}
|
||||
v-color-picker(mode='hexa' :value='color' @update:color='c => updateColor("light", i, c)')
|
||||
|
||||
|
||||
v-dialog(v-model='linkModal' width='500' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||
v-card
|
||||
v-card-title {{$t('admin.footer_links')}}
|
||||
v-card-text
|
||||
v-form(v-model='valid' ref='linkModalForm')
|
||||
v-text-field(v-model='link.label'
|
||||
:rules="[$validators.required('common.label')]"
|
||||
label='Label')
|
||||
v-text-field(v-model='link.href'
|
||||
:rules="[$validators.required('common.url')]"
|
||||
:label="$t('common.url')")
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(outlined @click='linkModal=false' color='error') {{$t('common.cancel')}}
|
||||
v-btn(outlined @click='addFooterLink' color='primary' :disabled='!valid') {{$t('common.add')}}
|
||||
|
||||
v-card-title {{$t('admin.footer_links')}}
|
||||
v-card-text
|
||||
|
@ -106,10 +123,11 @@ v-container
|
|||
<script>
|
||||
import { mapActions, mapState } from 'vuex'
|
||||
import { mdiDeleteForever, mdiRestore, mdiPlus, mdiChevronUp } from '@mdi/js'
|
||||
import debounce from 'lodash/debounce'
|
||||
|
||||
export default {
|
||||
name: 'Theme',
|
||||
data () {
|
||||
data ({ $store }) {
|
||||
const t = new Date().getMilliseconds()
|
||||
return {
|
||||
mdiDeleteForever, mdiRestore, mdiPlus, mdiChevronUp,
|
||||
|
@ -118,24 +136,19 @@ export default {
|
|||
fallbackImageKey: t,
|
||||
headerImageKey: t,
|
||||
link: { href: '', label: '' },
|
||||
linkModal: false
|
||||
// menu: [false, false, false, false]
|
||||
// colors: { primary: '', secondary: '', accent: '', error: '', info: '', success: '', warning: '' }
|
||||
// primary: {},
|
||||
// secondary: {}
|
||||
// }
|
||||
linkModal: false,
|
||||
dark_menu: [false, false, false, false],
|
||||
light_menu: [false, false, false, false],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['settings']),
|
||||
// 'colors.primary': this.color('primary'),
|
||||
// 'colors.secondary': this.color('primary'),
|
||||
// 'colors.tertiary': this.color('primary'),
|
||||
is_dark: {
|
||||
get () { return this.settings['theme.is_dark'] },
|
||||
set (value) {
|
||||
this.$vuetify.theme.dark = value
|
||||
this.setSetting({ key: 'theme.is_dark', value })
|
||||
this.setLocalSetting({ key: 'theme.is_dark', value })
|
||||
}
|
||||
},
|
||||
hide_thumbs: {
|
||||
|
@ -145,25 +158,10 @@ export default {
|
|||
hide_calendar: {
|
||||
get () { return this.settings.hide_calendar },
|
||||
set (value) { this.setSetting({ key: 'hide_calendar', value }) }
|
||||
},
|
||||
// 'colors[0]': {
|
||||
// get () {
|
||||
// return this.settings['theme.colors'] || [0, 0]
|
||||
// },
|
||||
// set (value) {
|
||||
// console.error(value)
|
||||
// if (!value) { return }
|
||||
// this.setSetting({ key: 'theme.primary', value })
|
||||
// if (this.settings['theme.is_dark']) {
|
||||
// this.$vuetify.theme.themes.dark.primary = value
|
||||
// } else {
|
||||
// this.$vuetify.theme.themes.light.primary = value
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['setSetting']),
|
||||
...mapActions(['setSetting', 'setLocalSetting']),
|
||||
reset () {
|
||||
this.setSetting({
|
||||
key: 'footerLinks',
|
||||
|
@ -196,13 +194,16 @@ export default {
|
|||
this.setSetting({ key: 'header_image', value: null })
|
||||
.then(this.forceHeaderImageReload)
|
||||
e.stopPropagation()
|
||||
},
|
||||
updateColor (i, v) {
|
||||
this.colors[i] = v.hex
|
||||
this.$vuetify.theme.themes.dark[i] = v.hex
|
||||
},
|
||||
updateSettingColor: debounce( async function (theme, color, value) {
|
||||
const key = `${theme}_colors`
|
||||
this.setSetting({ key, value: { ...this.settings[key], [color]: value.hex } })
|
||||
}, 200),
|
||||
updateColor (theme, color, value) {
|
||||
this.$vuetify.theme.themes[theme][color] = value.hex
|
||||
this.updateSettingColor(theme, color, value)
|
||||
},
|
||||
openLinkModal () {
|
||||
// this.link = { href: '', label: '' }
|
||||
this.linkModal = true
|
||||
this.$nextTick(() => this.$refs.linkModalForm.reset())
|
||||
},
|
||||
|
|
|
@ -12,7 +12,7 @@ v-container
|
|||
//- ADD NEW USER
|
||||
v-dialog(v-model='newUserDialog' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||
|
||||
v-card(color='secondary')
|
||||
v-card
|
||||
v-card-title {{$t('common.new_user')}}
|
||||
v-card-text
|
||||
v-form(v-model='valid' ref='user_form' lazy-validation @submit.prevent='createUser')
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template lang='pug'>
|
||||
span
|
||||
v-list(dense nav)
|
||||
v-list(dense nav color='transparent')
|
||||
v-list-group(:append-icon='mdiChevronUp' :value='true')
|
||||
template(v-slot:activator)
|
||||
v-list-item.text-overline {{$t('common.admin_actions')}}
|
||||
|
@ -11,7 +11,7 @@ span
|
|||
v-icon(v-if='event.is_visible' v-text='mdiEyeOff')
|
||||
v-icon(v-else='event.is_visible' v-text='mdiEye')
|
||||
v-list-item-content
|
||||
v-list-item-title(v-text="$t(`common.${event.is_visible?'hide':'confirm'}`)")
|
||||
v-list-item-title(v-text="$t(`common.${event.is_visible?(event.parentId?'skip':'hide'):'confirm'}`)")
|
||||
|
||||
//- Edit event
|
||||
v-list-item(:to='`/add/${event.id}`')
|
||||
|
@ -21,7 +21,7 @@ span
|
|||
v-list-item-title(v-text="$t('common.edit')")
|
||||
|
||||
//- Remove
|
||||
v-list-item(@click='remove(false)')
|
||||
v-list-item(v-if='!event.parentId' @click='remove(false)')
|
||||
v-list-item-icon
|
||||
v-icon(v-text='mdiDelete')
|
||||
v-list-item-content
|
||||
|
|
2
docs/.bundle/config
Normal file
2
docs/.bundle/config
Normal file
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
BUNDLE_PATH: "vendor/bundle"
|
|
@ -8,6 +8,35 @@ nav_order: 10
|
|||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
|
||||
### 1.6.8
|
||||
- use new $time in admins table, fix #252
|
||||
- fix some WPGancio issues
|
||||
- new custom color feature!
|
||||
- update locales (durch, spanish, galician)
|
||||
- improve index, tag, place page layout
|
||||
- use luxon instead of dayjs server side, fix #254
|
||||
|
||||
### 1.6.7 - 22 mar '23
|
||||
- node is not Intl ready, fix #250
|
||||
- fix old smultidate events without an end_datetime!
|
||||
- fix a small issue during setup
|
||||
|
||||
### 1.6.5 - 21 mar '23
|
||||
- optimize home page using lazy loading
|
||||
- add support for server side http proxy (thanks @sedum)
|
||||
- add Duch (nl) locale (thanks @jeoenepraat)
|
||||
- fix #244, dark theme user / admin preference merge issue
|
||||
- fix some issues with recurrent events, #247
|
||||
- improve search flow (order by, press enter or icon...)
|
||||
- filters / helpers refactoring
|
||||
- tag and place pages list all events (past too)
|
||||
- show boosts/bookmarks, fix #241
|
||||
- go to event on save/update
|
||||
- use luxon instead of dayjs, new $time plugin
|
||||
- fix event import from URL
|
||||
- remove an annoying warning from logs
|
||||
|
||||
### 1.6.4 - 22 feb '23
|
||||
- add missing i18n during setup
|
||||
- really fix #232
|
||||
|
|
|
@ -10,7 +10,7 @@ permalink: /
|
|||
|
||||
A shared agenda for local communities.
|
||||
{: .fs-6 }
|
||||
Last release **[1.6.3 - 17 Feb 2023](/changelog#163---17-feb-23)**
|
||||
Last release **[1.6.8 - 30 Mar 2023](/changelog#168---30-mar-23)**
|
||||
|
||||
[Install]({% link install/install.md %}){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 }
|
||||
[Demo](https://demo.gancio.org){: .btn .btn-green .fs-5 .mb-4 .mb-md-0 .mr-2 }
|
||||
|
|
|
@ -67,3 +67,24 @@ list of strings you can override.
|
|||
|
||||
> warning "Restart needed"
|
||||
> Note that a restart is needed when you change user_locale's content.
|
||||
|
||||
### Proxy
|
||||
Proxy outward request from gancio.
|
||||
Look [here](https://www.npmjs.com/package/https-proxy-agent) for options.
|
||||
Note: `hostname` option takes precedence over `host`.
|
||||
|
||||
```json
|
||||
"proxy": {
|
||||
"protocol": "http:",
|
||||
"hostname": "",
|
||||
"host": "127.0.0.1",
|
||||
"port": "8118",
|
||||
"auth": {
|
||||
"username": "user",
|
||||
"password": "password"
|
||||
},
|
||||
"headers": {
|
||||
"X-Proxy-Header": "example"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
@ -1 +1,5 @@
|
|||
{}
|
||||
{
|
||||
"register": {
|
||||
"subject": "Registratieverzoek ontvangen"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -162,8 +162,8 @@
|
|||
"recurrent_2w_days": "A {days} every other",
|
||||
"recurrent_1m_days": "The {days} of each month",
|
||||
"recurrent_2m_days": "The {days} a month every other",
|
||||
"recurrent_1m_ordinal": "The {n} {days} of each month",
|
||||
"recurrent_2m_ordinal": "The {n} {days} a month every other",
|
||||
"recurrent_1m_ordinal": "Each {n} {days} of the month",
|
||||
"recurrent_2m_ordinal": "Each {n} {days} a month every other",
|
||||
"each_week": "Each week",
|
||||
"each_2w": "Every other weeks",
|
||||
"each_month": "Each month",
|
||||
|
@ -309,7 +309,8 @@
|
|||
"tilelayer_test_error": "The tilelayer service is not reachable at {service_name}",
|
||||
"geolocation": "Geolocation",
|
||||
"allow_event_only_online": "Allow event only online",
|
||||
"allow_event_also_online": "Allow event also online"
|
||||
"allow_event_also_online": "Allow event also online",
|
||||
"colors": "Colors"
|
||||
},
|
||||
"auth": {
|
||||
"not_confirmed": "Not confirmed yet…",
|
||||
|
|
|
@ -156,8 +156,8 @@
|
|||
"recurrent_2w_days": "Un {days} cada dos",
|
||||
"recurrent_1m_days": "El día {days} de cada mes",
|
||||
"recurrent_2m_days": "El día {days} cada dos meses",
|
||||
"recurrent_1m_ordinal": "El {n} {days} de cada mes",
|
||||
"recurrent_2m_ordinal": "El {n} {days} un mes sí y el otro no",
|
||||
"recurrent_1m_ordinal": "Cada {n} {days} del mes",
|
||||
"recurrent_2m_ordinal": "Cada {n} {días} un mes de cada dos",
|
||||
"each_week": "Cada semana",
|
||||
"each_2w": "Cada dos semanas",
|
||||
"each_month": "Cada mes",
|
||||
|
@ -301,7 +301,8 @@
|
|||
"edit_tag": "Editar la etiqueta",
|
||||
"edit_tag_help": "Puede cambiar la etiqueta sustituyéndola por una nueva o fusionándola con una existente. Los {n} eventos asociados también se modificarán.",
|
||||
"geolocation_description": "<b>1. Defina un proveedor para el servicio de codificación geográfica</b>.<br>Actualmente, entre los enumerados en el <a href=\"https://wiki.openstreetmap.org/wiki/Nominatim#Alternatives_.2F_Third-party_providers\">wiki de OpenStreetMap </a>, hay soporte para el software <a href=\"https://github.com/osm-search/Nominatim\">Nominatim</a> y <a href=\"https://github.com/komoot /photon\">Photon</a>.<br>Puede usar una de las demostraciones oficiales relacionadas copiando el enlace en el campo 'Proveedor de codificación geográfica':<ul><li>https://nominatim.openstreetmap.org/search (<a href=\"https://operations.osmfoundation.org/policies/nominatim/\">Términos de servicio</a>)</li><li>https://photon.komoot.io/api/ (<a href=\"https://photon.komoot.io/\"> Condiciones de servicio</a>)</li></ul><br><b>2. Defina un proveedor para las capas del mapa.</b><br>Puede encontrar una lista de ellos aquí: <a href=\"https://leaflet-extras.github.io/leaflet-providers/preview/\">https://leaflet-extras.github.io/leaflet-providers/preview/</a>",
|
||||
"geocoding_test_error": "No se puede acceder al servicio de geocodificación en {service_name}"
|
||||
"geocoding_test_error": "No se puede acceder al servicio de geocodificación en {service_name}",
|
||||
"colors": "Colores"
|
||||
},
|
||||
"auth": {
|
||||
"not_confirmed": "Todavía no hemos confirmado este email…",
|
||||
|
|
|
@ -294,7 +294,8 @@
|
|||
"edit_tag": "Editar etiqueta",
|
||||
"edit_tag_help": "Podes cambiar a etiqueta substituíndoa por unha nova ou fusionándoa cunha existente. Tamén se cambiarán os {n} eventos asociados.",
|
||||
"delete_tag_confirm": "Estás seguro de que queres eliminar a etiqueta \"{tag}\"? A etiqueta eliminarase de {n} eventos.",
|
||||
"delete_collection_confirm": "Estás seguro de que queres eliminar a colección <u>{collection}</u>?"
|
||||
"delete_collection_confirm": "Estás seguro de que queres eliminar a colección <u>{collection}</u>?",
|
||||
"colors": "Cores"
|
||||
},
|
||||
"auth": {
|
||||
"not_confirmed": "Aínda non foi confirmado…",
|
||||
|
|
|
@ -8,6 +8,7 @@ module.exports = {
|
|||
gl: 'Galego',
|
||||
it: 'Italiano',
|
||||
nb: 'Norwegian Bokmål',
|
||||
nl: 'Dutch',
|
||||
pl: 'Polski',
|
||||
pt: 'Português',
|
||||
ru: 'Русский',
|
||||
|
|
|
@ -96,7 +96,9 @@
|
|||
"getting_there": "Come arrivare",
|
||||
"calendar": "Calendario",
|
||||
"home": "Home",
|
||||
"about": "Cos'è"
|
||||
"about": "Cos'è",
|
||||
"admin_actions": "Gestisci",
|
||||
"recurring_event_actions": "Gestisci ricorrenza"
|
||||
},
|
||||
"login": {
|
||||
"description": "Entrando puoi pubblicare nuovi eventi.",
|
||||
|
@ -158,8 +160,8 @@
|
|||
"recurrent_2w_days": "Un {days} ogni due",
|
||||
"recurrent_1m_days": "Il giorno {days} di ogni mese",
|
||||
"recurrent_2m_days": "Il giorno {days} ogni due mesi",
|
||||
"recurrent_1m_ordinal": "Il {n} {days} di ogni mese",
|
||||
"recurrent_2m_ordinal": "Il {n} {days} un mese sì e uno no",
|
||||
"recurrent_1m_ordinal": "Ogni {n} {days} del mese",
|
||||
"recurrent_2m_ordinal": "Ogni {n} {days} un mese sì e uno no",
|
||||
"each_week": "Ogni settimana",
|
||||
"each_2w": "Ogni due settimane",
|
||||
"each_month": "Ogni mese",
|
||||
|
@ -191,6 +193,7 @@
|
|||
"allow_registration_description": "Vuoi abilitare la registrazione?",
|
||||
"allow_anon_event": "Si possono inserire eventi anonimi (previa conferma)?",
|
||||
"allow_recurrent_event": "Abilita eventi ricorrenti",
|
||||
"allow_multidate_event": "Abilita eventi di più giorni",
|
||||
"allow_geolocation": "Abilita la geolocalizzazione degli eventi",
|
||||
"recurrent_event_visible": "Appuntamenti ricorrenti visibili di default",
|
||||
"federation": "Federazione / ActivityPub",
|
||||
|
@ -283,7 +286,8 @@
|
|||
"tilelayer_test_button": "Test tilelayer",
|
||||
"tilelayer_test_success": "Il servizio di tilelayer all'indirizzo {service_name} sta funzionando",
|
||||
"tilelayer_test_error": "Il servizio non è raggiungibile all'indirizzo: {service_name}",
|
||||
"geolocation": "Geo e mappe"
|
||||
"geolocation": "Geo e mappe",
|
||||
"colors": "Colori"
|
||||
},
|
||||
"auth": {
|
||||
"not_confirmed": "Non ancora confermato…",
|
||||
|
|
333
locales/nl.json
333
locales/nl.json
|
@ -1 +1,332 @@
|
|||
{}
|
||||
{
|
||||
"admin": {
|
||||
"block_user": "Gebruiker verwijderen",
|
||||
"filter_users": "Gebruikers filteren",
|
||||
"add_link": "Link toevoegen",
|
||||
"blocked": "Geblokkeerd",
|
||||
"delete_user": "Verwijderen",
|
||||
"remove_admin": "Beheerder verwijderen",
|
||||
"user_remove_ok": "Gebruiker verwijderd",
|
||||
"user_create_ok": "Gebruiker geregistreerd",
|
||||
"announcement_remove_ok": "Mededeling verwijderd",
|
||||
"instance_locale": "Standaardtaal",
|
||||
"new_announcement": "Nieuwe mededeling",
|
||||
"show_smtp_setup": "E-mailinstellingen",
|
||||
"smtp_port": "SMTP-poort",
|
||||
"config_plugin": "Pluginconfiguratie",
|
||||
"event_remove_ok": "Evenement verwijderd",
|
||||
"select_instance_timezone": "Tijdzone",
|
||||
"add_instance": "Server toevoegen",
|
||||
"hide_boost_bookmark": "Boost/favorieten verbergen",
|
||||
"instance_name": "Servernaam",
|
||||
"show_resource": "Reactie tonen",
|
||||
"hide_resource": "Reactie verbergen",
|
||||
"delete_resource": "Reactie verwijderen",
|
||||
"filter_instances": "Servers filteren",
|
||||
"is_dark": "Donker thema",
|
||||
"footer_links": "Footerlinks",
|
||||
"edit_place": "Locatie bewerken",
|
||||
"smtp_hostname": "SMTP-server",
|
||||
"domain": "Domein",
|
||||
"tilelayer_provider_attribution": "Naamsvermelding",
|
||||
"geolocation": "Geolocatie",
|
||||
"trusted_instances_label_default": "Vriendschappelijke servers",
|
||||
"edit_collection": "Collectie bewerken",
|
||||
"new_collection": "Nieuwe collectie",
|
||||
"smtp_use_sendmail": "Sendmail gebruiken",
|
||||
"resources": "Reacties",
|
||||
"favicon": "Logo",
|
||||
"user_blocked": "Gebruiker {user} geblokkeerd",
|
||||
"user_block_confirm": "Weet je zeker dat je de gebruiker {user} wilt verwijderen?",
|
||||
"instance_block_confirm": "Weet je zeker dat je de server {instance} wilt verwijderen?",
|
||||
"instance_place": "Naam van deze server",
|
||||
"description_description": "Valt in de header, onder de naam van de server te lezen",
|
||||
"smtp_test_button": "Test-e-mail verzenden",
|
||||
"widget": "Widget",
|
||||
"smtp_test_success": "Er is een test-e-mail naar {admin_email} verstuurd. Check je inbox!",
|
||||
"event_confirm_description": "Je kunt hier anonieme evenementen beoordelen",
|
||||
"allow_geolocation": "Geolocatie voor evenementen toestaan",
|
||||
"allow_multidate_event": "Evenementen voor meerdere dagen toestaan",
|
||||
"allow_recurrent_event": "Herhalende evenementen toestaan",
|
||||
"enable_admin_user_confirm": "Weet je zeker dat je {user} beheerdersrechten wilt geven?",
|
||||
"delete_tag_confirm": "Weet je zeker dat je de tag \"{tag}\" wilt verwijderen? De tag wordt uit {n} evenementen verwijderd.",
|
||||
"delete_announcement_confirm": "Weet je zeker dat je de mededeling wilt verwijderen?",
|
||||
"announcement_description": "Hier kun je mededelingen kwijt die op de startpagina worden getoond",
|
||||
"add_trusted_instance": "Vriendschappelijke server toevoegen",
|
||||
"trusted_instances_label_help": "Het standaardlabel is 'Vriendschappelijke servers'",
|
||||
"edit_tag": "Tag bewerken",
|
||||
"instance_place_help": "De naam van deze server die op andere servers wordt getoond",
|
||||
"smtp_secure": "SMTP Secure (TLS of STARTTLS)",
|
||||
"admin_email": "E-mailadres beheerder",
|
||||
"delete_collection_confirm": "Weet je zeker dat je de collectie <u>{collection}</u> wilt verwijderen?",
|
||||
"fallback_image": "Terugvalflyer",
|
||||
"header_image": "Header",
|
||||
"default_images_help": "Je moet de pagina <a href='/admin?tab=theme'>vernieuwen</a> om de veranderingen te kunnen zien.",
|
||||
"known_users": "Bekende gebruikers",
|
||||
"created_at": "Aangemaakt op",
|
||||
"geocoding_test_button": "Geocoding testen",
|
||||
"tilelayer_test_success": "De tilelayer-dienst op {service_name} werkt",
|
||||
"tilelayer_test_error": "De tilelayer-dienst is niet bereikbaar op {service_name}",
|
||||
"disable_user_confirm": "Weet je zeker dat je {user} wilt uitschakelen?",
|
||||
"disable_admin_user_confirm": "Weet je zeker dat je de beheerdersrechten van {user} wilt verwijderen?",
|
||||
"delete_user_confirm": "Weet je zeker dat je {user} wilt verwijderen?",
|
||||
"allow_anon_event": "Anonieme evenementen toestaan (moeten worden beoordeeld)?",
|
||||
"allow_registration_description": "Open registraties toestaan?",
|
||||
"enable_federation_help": "Het is mogelijk om deze server vanaf de fediverse te volgen",
|
||||
"federation": "Federatie / ActivityPub",
|
||||
"enable_federation": "Federatie inschakelen",
|
||||
"recurrent_event_visible": "Herhalende evenementen standaard tonen",
|
||||
"enable_resources": "Reacties inschakelen",
|
||||
"enable_resources_help": "Reacties op het evenement vanuit de fediverse toestaan",
|
||||
"block": "Blokkeren",
|
||||
"unblock": "Deblokkeren",
|
||||
"user_add_help": "Een e-mail met instructies hoe het account te bevestigen en hoe een wachtwoord in te stellen, wordt naar de nieuwe gebruiker gestuurd",
|
||||
"delete_resource_confirm": "Weet je zeker dat je deze reactie wilt verwijderen?",
|
||||
"instance_name_help": "Account om via ActivityPub te volgen",
|
||||
"trusted_instances_label": "Alternatief navigatielabel voor vriendschappelijke servers",
|
||||
"enable_trusted_instances": "Vriendschappelijke servers inschakelen",
|
||||
"trusted_instances_help": "De lijst met vriendschappelijke servers wordt onder een menu in de footer getoond",
|
||||
"delete_trusted_instance_confirm": "Weet je zeker dat je dit item uit het menu met vriendschappelijke servers wilt verwijderen?",
|
||||
"delete_footer_link_confirm": "Wil je deze link verwijderen?",
|
||||
"hide_thumbs": "Thumbnails verbergen",
|
||||
"default_images": "Standaardafbeeldingen",
|
||||
"hide_calendar": "Agenda verbergen",
|
||||
"geocoding_provider_type_help": "De standaardsoftware is Nominatim",
|
||||
"geocoding_provider_type": "Geocodingsoftware",
|
||||
"geocoding_provider_help": "De standaardprovider is Nominatim",
|
||||
"geocoding_provider": "Geocodingprovider",
|
||||
"geocoding_countrycodes": "Landcodes",
|
||||
"geocoding_countrycodes_help": "Hiermee kun je alleen filteren op bepaalde landen",
|
||||
"tilelayer_provider": "Tilelayer-provider",
|
||||
"tilelayer_provider_help": "De standaardprovider is OpenStreetMap",
|
||||
"geocoding_test_error": "De geocodingdienst is niet bereikbaar op {service_name}",
|
||||
"geocoding_test_success": "De geocodingdienst op {service_name} werkt",
|
||||
"tilelayer_test_button": "Tilelayer testen"
|
||||
},
|
||||
"ordinal": {
|
||||
"4": "vierde",
|
||||
"1": "eerste",
|
||||
"3": "derde",
|
||||
"-1": "laatste",
|
||||
"2": "tweede",
|
||||
"5": "vijfde"
|
||||
},
|
||||
"common": {
|
||||
"plugins": "Plugins",
|
||||
"help_translate": "Help vertalen",
|
||||
"tag": "Tag",
|
||||
"close": "Sluiten",
|
||||
"show_map": "Kaart tonen",
|
||||
"calendar": "Agenda",
|
||||
"home": "Home",
|
||||
"about": "Over",
|
||||
"content": "Inhoud",
|
||||
"recurring_event_actions": "Herhalende evenementacties",
|
||||
"admin_actions": "Beheeracties",
|
||||
"add_event": "Evenement toevoegen",
|
||||
"next": "Volgende",
|
||||
"export": "Exporteren",
|
||||
"send": "Verzenden",
|
||||
"where": "Waar",
|
||||
"address": "Adres",
|
||||
"when": "Wanneer",
|
||||
"what": "Wat",
|
||||
"media": "Media",
|
||||
"login": "Inloggen",
|
||||
"email": "E-mail",
|
||||
"password": "Wachtwoord",
|
||||
"register": "Registreren",
|
||||
"description": "Omschrijving",
|
||||
"remove": "Verwijderen",
|
||||
"hide": "Verbergen",
|
||||
"search": "Zoeken",
|
||||
"edit": "Bewerken",
|
||||
"info": "Info",
|
||||
"confirm": "Goedkeuren",
|
||||
"admin": "Beheerder",
|
||||
"users": "Gebruikers",
|
||||
"events": "Evenementen",
|
||||
"places": "Locaties",
|
||||
"settings": "Opties",
|
||||
"actions": "Acties",
|
||||
"deactivate": "Uitschakelen",
|
||||
"remove_admin": "Beheerder verwijderen",
|
||||
"activate": "Activeren",
|
||||
"save": "Opslaan",
|
||||
"preview": "Voorbeeld",
|
||||
"logout": "Uitloggen",
|
||||
"share": "Delen",
|
||||
"name": "Naam",
|
||||
"associate": "Associate",
|
||||
"edit_event": "Evenement bewerken",
|
||||
"related": "Gerelateerd",
|
||||
"add": "Toevoegen",
|
||||
"logout_ok": "Uitgelogd",
|
||||
"copy": "Kopiëren",
|
||||
"recover_password": "Wachtwoord herstellen",
|
||||
"new_password": "Nieuw wachtwoord",
|
||||
"new_user": "Nieuwe gebruiker",
|
||||
"ok": "OK",
|
||||
"cancel": "Annuleren",
|
||||
"enable": "Inschakelen",
|
||||
"disable": "Uitschakelen",
|
||||
"me": "Jij",
|
||||
"password_updated": "Wachtwoord veranderd.",
|
||||
"resources": "Reacties",
|
||||
"n_resources": "geen reactie|één reactie|{n} reacties",
|
||||
"activate_user": "Goedgekeurd",
|
||||
"displayname": "Weergavenaam",
|
||||
"federation": "Federatie",
|
||||
"set_password": "Wachtwoord instellen",
|
||||
"copy_link": "Link kopiëren",
|
||||
"send_via_mail": "E-mail verzenden",
|
||||
"add_to_calendar": "Aan agenda toevoegen",
|
||||
"instances": "Servers",
|
||||
"copied": "Gekopieerd",
|
||||
"embed": "Embedden",
|
||||
"embed_title": "Embed dit evenement op jouw website",
|
||||
"embed_help": "Kopieer en plak de volgende code op jouw website en het evenement wordt zoals hier daar getoond",
|
||||
"max_events": "Max. aantal evenementen",
|
||||
"feed": "RSS-feed",
|
||||
"feed_url_copied": "Open de gekopieerde feed-URL in jouw RSS-feedreader",
|
||||
"follow_me_title": "Volg updates op de fediverse",
|
||||
"follow": "Volgen",
|
||||
"moderation": "Moderatie",
|
||||
"user": "Gebruiker",
|
||||
"authorize": "Goedkeuren",
|
||||
"title": "Naam",
|
||||
"filter": "Filter",
|
||||
"event": "Evenement",
|
||||
"pause": "Pauze",
|
||||
"start": "Start",
|
||||
"fediverse": "Fediverse",
|
||||
"skip": "Overslaan",
|
||||
"delete": "Verwijderen",
|
||||
"announcements": "Mededelingen",
|
||||
"url": "URL",
|
||||
"place": "Locatie",
|
||||
"tags": "Tags",
|
||||
"theme": "Thema",
|
||||
"reset": "Resetten",
|
||||
"import": "Importeren",
|
||||
"label": "Label",
|
||||
"collections": "Collecties"
|
||||
},
|
||||
"login": {
|
||||
"not_registered": "Niet geregistreerd?",
|
||||
"error": "Kon niet inloggen. Controleer jouw inloginformatie.",
|
||||
"ok": "Ingelogd",
|
||||
"insert_email": "Vul je e-mailadres in.",
|
||||
"description": "Door in te loggen kun je nieuwe evenementen aanmaken.",
|
||||
"check_email": "Controleer de inbox en spamfolder van jouw e-mail.",
|
||||
"forgot_password": "Jouw wachtwoord vergeten?"
|
||||
},
|
||||
"recover": {
|
||||
"not_valid_code": "Er ging iets fout."
|
||||
},
|
||||
"export": {
|
||||
"email_description": "Je kunt evenementen die jou interesseren naar je laten e-mailen.",
|
||||
"insert_your_address": "Vul je e-mailadres in",
|
||||
"intro": "In tegenstelling tot asociale platformen die alles doen om gebruikers en hun gegevens voor zichzelf te houden, geloven wij dat informatie, zoals mensen, vrij moet zijn. Daarom kun je op te hoogte blijven van de door jou gewenste evenementen, zonder verplicht door deze website te hoeven ploegen.",
|
||||
"feed_description": "Om updates te volgen met een computer of smartphone, zonder regelmatig deze website te hoeven bezoeken, kun je RSS-feeds gebruiken.</p>\n\n<p> Om RSS-feeds te kunnen bekijken gebruik je een speciale app om updates te ontvangen van websites die jou interesseren. Het is een goede manier om veel websites snel te kunnen volgen, zonder een account aan te hoeven maken of andere tijdrovende dingen te hoeven doen. </p>\n\n<li> Wanneer je Android gebruikt raden wij <a href=\"https://f-droid.org/en/packages/net.frju.flym/\">Flym</a> of Feeder aan</li>\n<li> Voor iPhone / iPad kun je <a href=\"https://itunes.apple.com/ua/app/feeds4u/id1038456442?mt=8\"> Feed4U </a> gebruiken </li>\n<li> Voor een desktop / laptop raden wij Feedbro aan, die je dient toe te voegen aan <a href=\"https://addons.mozilla.org/en-GB/firefox/addon/feedbroreader/\"> Firefox </a> of <a href=\"https://chrome.google.com/webstore/detail/feedbro/mefgmmbdailogpfhfblcnnjfmnpnmdfa\"> Chrome </a>. </li>\n<br/>\nDoor het toevoegen van deze link aan jouw RSS-feedreader blijf je up-to-date.",
|
||||
"ical_description": "Computers en smartphones hebben meestal een agenda-applicatie die in staat is om een externe agenda te importeren.",
|
||||
"list_description": "Wanneer je een website hebt en je wilt een lijst met evenementen tonen, gebruik dan de volgende code"
|
||||
},
|
||||
"event": {
|
||||
"recurrent": "Herhalend",
|
||||
"only_future": "alleen toekomstige evenementen",
|
||||
"recurrent_description": "Kies de frequentie en selecteer dagen",
|
||||
"multidate": "Meer dagen",
|
||||
"normal": "Normaal",
|
||||
"import_ICS": "Vanuit ICS-bestand importeren",
|
||||
"alt_text_description": "Omschrijving voor blinde of slechtziende mensen",
|
||||
"what_description": "Titel",
|
||||
"description_description": "Omschrijving",
|
||||
"tag_description": "Tag",
|
||||
"media_description": "Je kunt een flyer toevoegen (optioneel)",
|
||||
"added": "Evenement toegevoegd",
|
||||
"saved": "Evenement opgeslagen",
|
||||
"added_anon": "Evenement toegevoegd, maar moet nog wel worden beoordeeld.",
|
||||
"where_description": "Waar is het evenement? Je kunt de locatie aanmaken wanneer deze nog niet aanwezig is.",
|
||||
"address_description": "Wat is het adres?",
|
||||
"address_description_osm": "Wat is het adres? (door <a href='https://osm.org/copyright'>OpenStreetMap</a>-bijdragers)",
|
||||
"confirmed": "Evenement goedgekeurd",
|
||||
"not_found": "Kon het evenement niet vinden",
|
||||
"remove_confirmation": "Weet je zeker dat je dit evenement wilt verwijderen?",
|
||||
"edit_recurrent": "Herhalend evenement bewerken:",
|
||||
"show_recurrent": "herhalende evenementen",
|
||||
"show_past": "ook eerdere evenementen",
|
||||
"ics": "ICS",
|
||||
"anon_description": "Je kunt een evenement toevoegen zonder te registreren of in te loggen, maar je moet wel wachten dat iemand het leest\nen beoordeeld of het een geschikt evenement is. Het is niet mogelijk om het evenement daarna te wijzigen.<br/><br/>\nJe kunt in plaats daarvan ook <a href='/login'>inloggen</a> of <a href='/register'>registreren</a>. Ga anders gewoon je gang en krijg zo snel mogelijk een antwoord. ",
|
||||
"show_multidate": "meerdaagse evenementen",
|
||||
"multidate_description": "Is het een festival? Kies wanneer het begint en het eindigt",
|
||||
"normal_description": "Kies de dag.",
|
||||
"remove_recurrent_confirmation": "Weet je zeker dat je dit herhalende evenement wilt verwijderen?\nEerdere evenementen blijven bewaard, maar er worden geen nieuwe evenementen aangemaakt.",
|
||||
"import_description": "Je kunt evenementen vanuit andere platformen en servers importeren met behulp van standaardformaten (ics en h-event)",
|
||||
"choose_focal_point": "Kies het punt van aandacht",
|
||||
"remove_media_confirmation": "Ga je akkoord met het verwijderen van de afbeelding?",
|
||||
"download_flyer": "Flyer downloaden",
|
||||
"same_day": "op dezelfde dag",
|
||||
"anon": "Anoniem",
|
||||
"updated": "Evenement bijgewerkt",
|
||||
"recurrent_1w_days": "Elke {days}",
|
||||
"recurrent_1m_days": "Dag {days} van elke maand",
|
||||
"recurrent_1m_ordinal": "De {n} {days} van elke maand",
|
||||
"recurrent_2m_days": "Dag {days} om de twee maanden",
|
||||
"recurrent_2w_days": "Elke {days} om de twee weken",
|
||||
"recurrent_2m_ordinal": "De {n} {days} om de twee maanden",
|
||||
"each_week": "Elke week",
|
||||
"each_2w": "Om de week",
|
||||
"each_month": "Elke maand",
|
||||
"due": "tot",
|
||||
"from": "Van",
|
||||
"image_too_big": "De afbeelding kan niet groter zijn dan 4MB",
|
||||
"interact_with_me_at": "Heb interactie met mij op de fediverse via",
|
||||
"interact_with_me": "Volg me",
|
||||
"import_URL": "Vanuit URL importeren"
|
||||
},
|
||||
"confirm": {
|
||||
"title": "Gebruiker beoordelen",
|
||||
"not_valid": "Er ging iets mis.",
|
||||
"valid": "Jouw account is goedgekeurd. Je kunt nu <a href=\"/login\">inloggen</a>"
|
||||
},
|
||||
"setup": {
|
||||
"completed": "Instellen compleet",
|
||||
"start": "Start",
|
||||
"completed_description": "<p>Je kunt nu met de volgende gegevens inloggen:<br/><br/>Gebruiker: <b>{email}</b><br/>Wachtwoord: <b>{password}<b/></p>",
|
||||
"copy_password_dialog": "Ja, je moet het wachtwoord kopiëren!"
|
||||
},
|
||||
"register": {
|
||||
"complete": "Registraties moeten worden beoordeeld.",
|
||||
"description": "Sociale bewegingen moeten zich organiseren en zichzelf financieren.<br/>\n<br/>Voordat je evenementen kunt aanmaken <strong>moet dit account worden goedgekeurd</strong>. Besef je dat <strong>achter deze website zich echt mensen bevinden</strong>, dus schrijf twee regels om ons te laten weten wat voor evenementen je wilt aanmaken.",
|
||||
"error": "Fout: ",
|
||||
"first_user": "Beheerder aangemaakt"
|
||||
},
|
||||
"settings": {
|
||||
"danger_section": "Gevaarlijk onderdeel",
|
||||
"password_updated": "Wachtwoord veranderd.",
|
||||
"remove_account_confirm": "Je staat op het punt om je account permanent te verwijderen",
|
||||
"update_confirm": "Wil je jouw wijziging opslaan?",
|
||||
"change_password": "Jouw wachtwoord veranderen"
|
||||
},
|
||||
"auth": {
|
||||
"fail": "Je kon niet inloggen. Weet je zeker dat je wachtwoord klopt?",
|
||||
"not_confirmed": "Nog niet bevestigd…"
|
||||
},
|
||||
"validators": {
|
||||
"required": "{fieldName} is vereist",
|
||||
"email": "Een geldig e-mailadres invullen"
|
||||
},
|
||||
"about": "\n <p><a href='https://gancio.org'>Gancio</a> is een gedeelde agenda voor lokale community's.</p>\n ",
|
||||
"oauth": {
|
||||
"authorization_request": "De applicatie <code>{app}</code> vraagt om de volgende autorisatie voor <code>{instance_name}</code>:",
|
||||
"scopes": {
|
||||
"event:write": "Jouw evenementen toevoegen en bewerken"
|
||||
},
|
||||
"redirected_to": "Je wordt na bevestiging doorgestuurd naar <code>{url}</code>"
|
||||
},
|
||||
"error": {
|
||||
"nick_taken": "Deze gebruikersnaam is al in gebruik.",
|
||||
"email_taken": "Dit e-mailadres is al in gebruik."
|
||||
}
|
||||
}
|
||||
|
|
26
modules/axios-proxy.js
Normal file
26
modules/axios-proxy.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
const HttpsProxyAgent = require("https-proxy-agent"),
|
||||
axios = require("axios"),
|
||||
config = require('../server/config')
|
||||
|
||||
function axiosProxy (_moduleOptions) {
|
||||
|
||||
if (config.proxy && !(!config.proxy.hostname && !config.proxy.host)) {
|
||||
const httpsAgent = new HttpsProxyAgent({
|
||||
protocol: config.proxy.protocol,
|
||||
hostname: config.proxy.hostname,
|
||||
host: config.proxy.host,
|
||||
port: config.proxy.port,
|
||||
auth: config.proxy.auth.username + ':' + config.proxy.auth.password,
|
||||
headers: config.proxy.headers
|
||||
})
|
||||
|
||||
// Use axios as you normally would, but specify httpsAgent in the default configs
|
||||
// https://github.com/nuxt-community/axios-module/pull/428#issuecomment-743313813
|
||||
// Nuxt 2: https://github.com/axios/axios/issues/925#issuecomment-513028175
|
||||
// Nuxt 3: https://github.com/unjs/ofetch#-adding-https-agent
|
||||
axios.defaults.httpsAgent = httpsAgent
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = axiosProxy
|
|
@ -1,8 +1,6 @@
|
|||
const config = require('./server/config.js')
|
||||
const minifyTheme = require('minify-css-string').default
|
||||
const locales = require('./locales/index')
|
||||
|
||||
import { ca, de, en, es, eu, fr, gl, it, nb, pl, pt, sk, ru, zhHans } from 'vuetify/lib/locale'
|
||||
|
||||
const isDev = (process.env.NODE_ENV !== 'production')
|
||||
module.exports = {
|
||||
|
@ -41,7 +39,8 @@ module.exports = {
|
|||
** Plugins to load before mounting the App
|
||||
*/
|
||||
plugins: [
|
||||
'@/plugins/filters', // text filters, datetime filters, generic transformation helpers etc.
|
||||
'@/plugins/helpers',
|
||||
'@/plugins/time', // datetime filters
|
||||
'@/plugins/axios', // axios baseurl configuration
|
||||
'@/plugins/validators', // inject validators
|
||||
'@/plugins/api', // api helpers
|
||||
|
@ -55,6 +54,7 @@ module.exports = {
|
|||
modules: [
|
||||
// Doc: https://axios.nuxtjs.org/usage
|
||||
'@nuxtjs/i18n',
|
||||
'~/modules/axios-proxy.js', // Note: import this before @nuxtjs/axios to override defaults of both instances: `$axios` available in context, and `axios` used in controllers
|
||||
'@nuxtjs/axios',
|
||||
'@nuxtjs/auth',
|
||||
'@nuxtjs/sitemap',
|
||||
|
@ -140,27 +140,7 @@ module.exports = {
|
|||
}
|
||||
},
|
||||
buildModules: ['@nuxtjs/vuetify'],
|
||||
vuetify: {
|
||||
lang: { locales: { ca, de, en, es, eu, fr, gl, it, nb, pl, pt, sk, ru, zhHans } },
|
||||
treeShake: true,
|
||||
theme: {
|
||||
options: {
|
||||
customProperties: false,
|
||||
variations: false,
|
||||
minifyTheme,
|
||||
},
|
||||
dark: true,
|
||||
themes: {
|
||||
dark: {
|
||||
primary: '#FF6E40'
|
||||
},
|
||||
light: {
|
||||
primary: '#FF4500'
|
||||
}
|
||||
}
|
||||
},
|
||||
defaultAssets: false
|
||||
},
|
||||
vuetify: { defaultAssets: false, optionsPath: './vuetify.options.js' },
|
||||
build: {
|
||||
extend(config, { isDev, isClient }) {
|
||||
// ..
|
||||
|
|
43
package.json
43
package.json
|
@ -1,15 +1,15 @@
|
|||
{
|
||||
"name": "gancio",
|
||||
"version": "1.6.4",
|
||||
"version": "1.6.8",
|
||||
"description": "A shared agenda for local communities",
|
||||
"author": "lesion",
|
||||
"scripts": {
|
||||
"build": "nuxt build --modern",
|
||||
"start:inspect": "NODE_ENV=production node --inspect node_modules/.bin/nuxt start --modern",
|
||||
"dev": "nuxt dev",
|
||||
"test-sqlite": "export NODE_ENV=test; export DB=sqlite; jest --testEnvironment=jest-environment-node --bail=1",
|
||||
"test-mariadb": "export NODE_ENV=test; export DB=mariadb; jest --testEnvironment=jest-environment-node --bail=1",
|
||||
"test-postgresql": "export NODE_ENV=test; export DB=postgresql; jest --testEnvironment=jest-environment-node --bail=1",
|
||||
"test-sqlite": "export NODE_ENV=test; export DB=sqlite; jest --testEnvironment=jest-environment-node --forceExit --runInBand --bail=1",
|
||||
"test-mariadb": "export NODE_ENV=test; export DB=mariadb; jest --testEnvironment=jest-environment-node --runInBand --bail=1",
|
||||
"test-postgresql": "export NODE_ENV=test; export DB=postgresql; jest --testEnvironment=jest-environment-node --runInBand --bail=1",
|
||||
"start": "nuxt start --modern",
|
||||
"doc": "cd docs && bundle exec jekyll b",
|
||||
"doc:dev": "cd docs && bundle exec jekyll s --drafts",
|
||||
|
@ -20,6 +20,7 @@
|
|||
"files": [
|
||||
"server/",
|
||||
"assets/",
|
||||
"modules/",
|
||||
"nuxt.config.js",
|
||||
"static/",
|
||||
"views/",
|
||||
|
@ -42,7 +43,7 @@
|
|||
"accept-language": "^3.0.18",
|
||||
"axios": "^0.27.2",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"body-parser": "^1.20.0",
|
||||
"body-parser": "^1.20.2",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cookie-session": "^2.0.0",
|
||||
"cookie-universal-nuxt": "^2.2.2",
|
||||
|
@ -55,20 +56,21 @@
|
|||
"http-signature": "^1.3.6",
|
||||
"https-proxy-agent": "^5.0.1",
|
||||
"ical.js": "^1.5.0",
|
||||
"ics": "^3.0.1",
|
||||
"ics": "^3.1.0",
|
||||
"jsdom": "^21.1.0",
|
||||
"leaflet": "^1.9.2",
|
||||
"linkify-html": "^4.0.2",
|
||||
"linkifyjs": "4.1.0",
|
||||
"linkify-html": "^4.1.1",
|
||||
"linkifyjs": "4.1.1",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^3.3.0",
|
||||
"mariadb": "^2.5.6",
|
||||
"memory-cache": "^0.2.0",
|
||||
"microformat-node": "^2.0.1",
|
||||
"minify-css-string": "^1.0.0",
|
||||
"mkdirp": "^2.1.3",
|
||||
"mkdirp": "^2.1.6",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"mysql2": "^2.3.3",
|
||||
"nuxt-edge": "2.17.0-27941778.c493723",
|
||||
"nuxt-edge": "2.17.0-27999381.ff351a6",
|
||||
"oauth2orize": "^1.11.1",
|
||||
"passport": "^0.6.0",
|
||||
"passport-anonymous": "^1.0.1",
|
||||
|
@ -77,12 +79,12 @@
|
|||
"passport-http-bearer": "^1.0.1",
|
||||
"passport-oauth2-client-password": "^0.1.2",
|
||||
"passport-oauth2-client-public": "^0.0.1",
|
||||
"pg": "^8.9.0",
|
||||
"sequelize": "^6.28.0",
|
||||
"pg": "^8.10.0",
|
||||
"sequelize": "^6.30.0",
|
||||
"sequelize-slugify": "^1.6.2",
|
||||
"sharp": "^0.27.2",
|
||||
"sqlite3": "^5.1.4",
|
||||
"telegraf": "^4.9.1",
|
||||
"telegraf": "^4.12.2",
|
||||
"tiptap": "^1.32.0",
|
||||
"tiptap-extensions": "^1.35.0",
|
||||
"umzug": "^2.3.0",
|
||||
|
@ -91,26 +93,21 @@
|
|||
"vuetify": "2.6.14",
|
||||
"winston": "^3.8.2",
|
||||
"winston-daily-rotate-file": "^4.7.1",
|
||||
"yargs": "^17.7.0"
|
||||
"yargs": "^17.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxtjs/vuetify": "^1.12.3",
|
||||
"jest": "^29.4.2",
|
||||
"jest-environment-node": "^29.4.2",
|
||||
"prettier": "^2.8.1",
|
||||
"jest": "^29.5.0",
|
||||
"jest-environment-node": "^29.5.0",
|
||||
"prettier": "^2.8.7",
|
||||
"pug": "^3.0.2",
|
||||
"pug-plain-loader": "^1.1.0",
|
||||
"sass": "^1.56.2",
|
||||
"sass": "^1.60.0",
|
||||
"sequelize-cli": "^6.3.0",
|
||||
"supertest": "^6.3.3",
|
||||
"webpack": "4",
|
||||
"webpack-cli": "^4.10.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"nth-check": "^2.0.1",
|
||||
"glob-parent": "^5.1.2",
|
||||
"moment": "^2.29.2"
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "jsdom"
|
||||
},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template lang="pug">
|
||||
v-container.container.pa-0.pa-md-3
|
||||
v-card
|
||||
v-alert(v-if='url!==settings.baseurl' outlined type='warning' color='red' show-icon :icon='mdiAlert')
|
||||
v-alert(v-if='url!==settings.baseurl' outlined type='warning' show-icon :icon='mdiAlert')
|
||||
span(v-html="$t('admin.wrong_domain_warning', { url, baseurl: settings.baseurl })")
|
||||
v-tabs(v-model='selectedTab' show-arrows :next-icon='mdiChevronRight' :prev-icon='mdiChevronLeft')
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ v-container.container.pa-0.pa-md-3
|
|||
|
||||
//- When
|
||||
DateInput(ref='when' v-model='date' :event='event')
|
||||
|
||||
//- Description
|
||||
v-col.px-0(cols='12')
|
||||
Editor.px-3.ma-0(
|
||||
|
@ -70,7 +71,6 @@ v-container.container.pa-0.pa-md-3
|
|||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import debounce from 'lodash/debounce'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
import { mdiFileImport, mdiFormatTitle, mdiTagMultiple, mdiCloseCircle } from '@mdi/js'
|
||||
|
||||
|
@ -103,7 +103,7 @@ export default {
|
|||
return true
|
||||
|
||||
},
|
||||
async asyncData({ params, $axios, error, $auth, store }) {
|
||||
async asyncData({ params, $axios, error, $auth, $time }) {
|
||||
if (params.edit) {
|
||||
|
||||
const data = { event: { place: {}, media: [] } }
|
||||
|
@ -123,15 +123,15 @@ export default {
|
|||
|
||||
data.event.place.name = event.place.name
|
||||
data.event.place.address = event.place.address || ''
|
||||
const from = dayjs.unix(event.start_datetime).tz()
|
||||
const due = event.end_datetime && dayjs.unix(event.end_datetime).tz()
|
||||
const from = $time.fromUnix(event.start_datetime)
|
||||
const due = event.end_datetime && $time.fromUnix(event.end_datetime)
|
||||
data.date = {
|
||||
recurrent: event.recurrent,
|
||||
from: from.toDate(),
|
||||
due: due && due.toDate(),
|
||||
from: from.toJSDate(),
|
||||
due: due && due.toJSDate(),
|
||||
multidate: event.multidate,
|
||||
fromHour: from.format('HH:mm'),
|
||||
dueHour: due && due.format('HH:mm')
|
||||
fromHour: from.toFormat('HH:mm'),
|
||||
dueHour: due && (due.toFormat('HH:mm') === '23:59' ? null : due.toFormat('HH:mm'))
|
||||
}
|
||||
|
||||
data.event.title = event.title
|
||||
|
@ -139,13 +139,15 @@ export default {
|
|||
data.event.id = event.id
|
||||
data.event.tags = event.tags
|
||||
data.event.media = event.media || []
|
||||
data.event.parentId = event.parentId
|
||||
data.event.recurrent = event.recurrent
|
||||
return data
|
||||
}
|
||||
return {}
|
||||
},
|
||||
data() {
|
||||
const month = dayjs.tz().month() + 1
|
||||
const year = dayjs.tz().year()
|
||||
data({ $time }) {
|
||||
const month = $time.currentMonth()
|
||||
const year = $time.currentYear()
|
||||
return {
|
||||
mdiFileImport, mdiFormatTitle, mdiTagMultiple, mdiCloseCircle,
|
||||
valid: false,
|
||||
|
@ -189,15 +191,15 @@ export default {
|
|||
this.event = Object.assign(this.event, event)
|
||||
|
||||
this.$refs.where.selectPlace({ name: event.place.name || event.place, address: event.place.address })
|
||||
const from = dayjs.unix(this.event.start_datetime)
|
||||
const due = this.event.end_datetime && dayjs.unix(this.event.end_datetime)
|
||||
const from = this.$time.fromUnix(this.event.start_datetime)
|
||||
const due = this.event.end_datetime && this.$time.fromUnix(this.event.end_datetime)
|
||||
this.date = {
|
||||
recurrent: this.event.recurrent || null,
|
||||
from: from.toDate(),
|
||||
due: due && due.toDate(),
|
||||
from: from.toJSDate(),
|
||||
due: due && due.toJSDate(),
|
||||
multidate: event.multidate,
|
||||
fromHour: from.format('HH:mm'),
|
||||
dueHour: due && due.format('HH:mm')
|
||||
fromHour: from.toFormat('HH:mm'),
|
||||
dueHour: due && due.toFormat('HH:mm')
|
||||
}
|
||||
this.openImportDialog = false
|
||||
},
|
||||
|
@ -244,13 +246,11 @@ export default {
|
|||
|
||||
formData.append('description', this.event.description)
|
||||
formData.append('multidate', !!this.date.multidate)
|
||||
let [hour, minute] = this.date.fromHour.split(':')
|
||||
formData.append('start_datetime', dayjs(this.date.from).tz().hour(Number(hour)).minute(Number(minute)).second(0).unix())
|
||||
if (this.date.dueHour) {
|
||||
[hour, minute] = this.date.dueHour.split(':')
|
||||
formData.append('end_datetime', dayjs(this.date.due).tz().hour(Number(hour)).minute(Number(minute)).second(0).unix())
|
||||
} else if (!!this.date.multidate) {
|
||||
formData.append('end_datetime', dayjs(this.date.due).tz().hour(24).minute(0).second(0).unix())
|
||||
formData.append('start_datetime', this.$time.fromDateInput(this.date.from, this.date.fromHour))
|
||||
if (!!this.date.multidate) {
|
||||
formData.append('end_datetime', this.$time.fromDateInput(this.date.due, this.date.dueHour || '23:59'))
|
||||
} else if (this.date.dueHour) {
|
||||
formData.append('end_datetime', this.$time.fromDateInput(this.date.from, this.date.dueHour))
|
||||
}
|
||||
|
||||
if (this.edit) {
|
||||
|
@ -258,12 +258,12 @@ export default {
|
|||
}
|
||||
if (this.event.tags) { this.event.tags.forEach(tag => formData.append('tags[]', tag.tag || tag)) }
|
||||
try {
|
||||
if (this.edit) {
|
||||
await this.$axios.$put('/event', formData)
|
||||
const ret = this.edit ? await this.$axios.$put('/event', formData) : await this.$axios.$post('/event', formData)
|
||||
if (!this.date.recurrent) {
|
||||
this.$router.push(`/event/${ret.slug}`)
|
||||
} else {
|
||||
await this.$axios.$post('/event', formData)
|
||||
this.$router.push('/')
|
||||
}
|
||||
this.$router.push('/')
|
||||
this.$nextTick(() => {
|
||||
this.$root.$message(this.$auth.loggedIn ? (this.edit ? 'event.saved' : 'event.added') : 'event.added_anon', { color: 'success' })
|
||||
})
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
|
||||
<!-- Events -->
|
||||
<div class='mb-2 mt-1 pl-1 pl-sm-2' id="events">
|
||||
<Event :event='event' v-for='(event, idx) in events' :lazy='idx>2' :key='event.id'></Event>
|
||||
<v-lazy class='event' :value='idx<9' v-for='(event, idx) in events' :key='event.id' :min-height='hide_thumbs ? 105 : undefined' :options="{ threshold: .5, rootMargin: '500px' }">
|
||||
<Event :event='event' :lazy='idx>9' />
|
||||
</v-lazy>
|
||||
</div>
|
||||
</v-container>
|
||||
</template>
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
nuxt-link.embed_event(:to='`/event/${event.slug || event.id}`' target='_blank' :class='{ withImg: event.media }')
|
||||
|
||||
//- image
|
||||
img.float-left(:src='event | mediaURL("thumb")')
|
||||
img.float-left(:src='$helper.mediaURL(event, "thumb")')
|
||||
.event-info
|
||||
//- title
|
||||
.date {{event|when}}<br/>
|
||||
.date {{$time.when(event)}}<br/>
|
||||
h4 {{event.title}}
|
||||
|
||||
//- date / place
|
||||
|
|
|
@ -1,40 +1,38 @@
|
|||
<template lang="pug">
|
||||
v-container#event.pa-0.pa-sm-2
|
||||
//- EVENT PAGE
|
||||
//- gancio supports microformats (http://microformats.org/wiki/h-event)
|
||||
//- and microdata https://schema.org/Event
|
||||
v-card.h-event(itemscope itemtype="https://schema.org/Event" v-touch="{ left: goNext, right: goPrev }")
|
||||
v-container#event.pa-2.pa-sm-2(itemscope itemtype="https://schema.org/Event" v-touch="{ left: goNext, right: goPrev }")
|
||||
//- EVENT PAGE
|
||||
//- gancio supports microformats (http://microformats.org/wiki/h-event)
|
||||
//- and microdata https://schema.org/Event
|
||||
|
||||
v-card-text
|
||||
v-row
|
||||
v-col.col-12.col-md-8
|
||||
MyPicture(v-if='hasMedia' :event='event')
|
||||
.p-description.text-body-1.pa-3.rounded(v-if='!hasMedia && event.description' itemprop='description' v-html='event.description')
|
||||
v-row
|
||||
v-col.col-12.col-md-8
|
||||
MyPicture(v-if='hasMedia' :event='event')
|
||||
.p-description.text-body-1.pa-3.rounded(v-if='!hasMedia && event.description' itemprop='description' v-html='event.description')
|
||||
|
||||
v-col.col-12.col-md-4
|
||||
v-card(outlined)
|
||||
v-card-text
|
||||
v-icon.float-right(v-if='event.parentId' color='success' v-text='mdiRepeat')
|
||||
.title.text-h5
|
||||
strong.p-name.text--primary(itemprop="name") {{event.title}}
|
||||
v-divider
|
||||
v-card-text
|
||||
time.dt-start.text-button(:datetime='event.start_datetime|unixFormat("YYYY-MM-DD HH:mm")' itemprop="startDate" :content="event.start_datetime|unixFormat('YYYY-MM-DDTHH:mm')")
|
||||
v-icon(v-text='mdiCalendar' small)
|
||||
strong.ml-2 {{event|when}}
|
||||
.d-none.dt-end(itemprop="endDate" :content="event.end_datetime|unixFormat('YYYY-MM-DDTHH:mm')") {{event.end_datetime|unixFormat('YYYY-MM-DD HH:mm')}}
|
||||
div.text-caption.mb-3 {{event.start_datetime|from}}
|
||||
small(v-if='event.parentId') ({{event|recurrentDetail}})
|
||||
v-col.col-12.col-md-4
|
||||
v-card(outlined)
|
||||
v-card-text
|
||||
v-icon.float-right(v-if='event.parentId' color='success' v-text='mdiRepeat')
|
||||
.title.text-h5
|
||||
strong.p-name.text--primary(itemprop="name") {{event.title}}
|
||||
v-divider
|
||||
v-container.eventDetails
|
||||
time.dt-start(:datetime='$time.unixFormat(event.start_datetime, "yyyy-MM-dd HH:mm")' itemprop="startDate" :content="$time.unixFormat(event.start_datetime, \"yyyy-MM-dd'T'HH:mm\")")
|
||||
v-icon(v-text='mdiCalendar' small)
|
||||
strong.ml-2.text-uppercase {{$time.when(event)}}
|
||||
.d-none.dt-end(v-if='event.end_datetime' itemprop="endDate" :content="$time.unixFormat(event.end_datetime,\"yyyy-MM-dd'T'HH:mm\")") {{$time.unixFormat(event.end_datetime,"yyyy-MM-dd'T'HH:mm")}}
|
||||
div.font-weight-light.mb-3 {{$time.from(event.start_datetime)}}
|
||||
small(v-if='event.parentId') ({{$time.recurrentDetail(event)}})
|
||||
|
||||
.text-h6.p-location.h-adr(itemprop="location" itemscope itemtype="https://schema.org/Place")
|
||||
v-icon(v-text='mdiMapMarker' small)
|
||||
nuxt-link.vcard.ml-2.p-name.text-decoration-none.text-button(itemprop="name" :to='`/place/${encodeURIComponent(event.place.name)}`') {{event.place && event.place.name}}
|
||||
.text-caption.p-street-address(itemprop='address') {{event.place && event.place.address}}
|
||||
.p-location.h-adr(itemprop="location" itemscope itemtype="https://schema.org/Place")
|
||||
v-icon(v-text='mdiMapMarker' small)
|
||||
nuxt-link.vcard.ml-2.p-name.text-decoration-none.text-uppercase(itemprop="name" :to='`/place/${encodeURIComponent(event.place.name)}`') {{event.place && event.place.name}}
|
||||
.font-weight-light.p-street-address(itemprop='address') {{event.place && event.place.address}}
|
||||
|
||||
//- tags, hashtags
|
||||
v-card-text.pt-0(v-if='event.tags && event.tags.length')
|
||||
v-chip.p-category.ml-1.mt-1(v-for='tag in event.tags' small label color='primary'
|
||||
outlined :key='tag' :to='`/tag/${encodeURIComponent(tag)}`') {{tag}}
|
||||
//- tags, hashtags
|
||||
v-container.pt-0(v-if='event.tags && event.tags.length')
|
||||
v-chip.p-category.ml-1.mt-1(v-for='tag in event.tags' small label color='primary'
|
||||
outlined :key='tag' :to='`/tag/${encodeURIComponent(tag)}`') {{tag}}
|
||||
|
||||
//- online events
|
||||
v-divider(v-if='onlineSectionEnabled && event.online_locations && event.online_locations.length')
|
||||
|
@ -52,133 +50,147 @@ v-container#event.pa-0.pa-sm-2
|
|||
v-chip(v-for='(item, index) in event.online_locations' v-if="index > 0" target='_blank' :href="`${item}`"
|
||||
v-bind:key="index" small label v-text="`${item}`" outlined )
|
||||
|
||||
v-divider
|
||||
//- info & actions
|
||||
v-list(dense nav)
|
||||
//- v-list-group(:append-icon='mdiChevronUp' :value='true')
|
||||
//- template(v-slot:activator)
|
||||
//- v-list-item.text-overline {{$t('common.actions')}}
|
||||
v-divider
|
||||
//- info & actions
|
||||
v-list(dense nav color='transparent')
|
||||
//- v-list-group(:append-icon='mdiChevronUp' :value='true')
|
||||
//- template(v-slot:activator)
|
||||
//- v-list-item.text-overline {{$t('common.actions')}}
|
||||
|
||||
//- copy link
|
||||
v-list-item(@click='clipboard(`${settings.baseurl}/event/${event.slug || event.id}`)')
|
||||
v-list-item-icon
|
||||
v-icon(v-text='mdiContentCopy')
|
||||
v-list-item-content
|
||||
v-list-item-title(v-text="$t('common.copy_link')")
|
||||
//- copy link
|
||||
v-list-item(@click='clipboard(`${settings.baseurl}/event/${event.slug || event.id}`)')
|
||||
v-list-item-icon
|
||||
v-icon(v-text='mdiContentCopy')
|
||||
v-list-item-content
|
||||
v-list-item-title(v-text="$t('common.copy_link')")
|
||||
|
||||
//- map
|
||||
v-list-item(v-if='settings.allow_geolocation && event.place.latitude && event.place.longitude' @click="mapModal = true")
|
||||
v-list-item-icon
|
||||
v-icon(v-text='mdiMap')
|
||||
v-list-item-content
|
||||
v-list-item-title(v-text="$t('common.show_map')")
|
||||
//- map
|
||||
v-list-item(v-if='settings.allow_geolocation && event.place.latitude && event.place.longitude' @click="mapModal = true")
|
||||
v-list-item-icon
|
||||
v-icon(v-text='mdiMap')
|
||||
v-list-item-content
|
||||
v-list-item-title(v-text="$t('common.show_map')")
|
||||
|
||||
//- embed
|
||||
v-list-item(@click='showEmbed=true')
|
||||
v-list-item-icon
|
||||
v-icon(v-text='mdiCodeTags')
|
||||
v-list-item-content
|
||||
v-list-item-title(v-text="$t('common.embed')")
|
||||
|
||||
//- calendar
|
||||
v-list-item(:href='`/api/event/detail/${event.slug || event.id}.ics`')
|
||||
v-list-item-icon
|
||||
v-icon(v-text='mdiCalendarExport')
|
||||
v-list-item-content
|
||||
v-list-item-title(v-text="$t('common.add_to_calendar')")
|
||||
//- embed
|
||||
v-list-item(@click='showEmbed=true')
|
||||
v-list-item-icon
|
||||
v-icon(v-text='mdiCodeTags')
|
||||
v-list-item-content
|
||||
v-list-item-title(v-text="$t('common.embed')")
|
||||
|
||||
//- calendar
|
||||
v-list-item(:href='`/api/event/detail/${event.slug || event.id}.ics`')
|
||||
v-list-item-icon
|
||||
v-icon(v-text='mdiCalendarExport')
|
||||
v-list-item-content
|
||||
v-list-item-title(v-text="$t('common.add_to_calendar')")
|
||||
|
||||
//- download flyer
|
||||
v-list-item(v-if='hasMedia' :href='event | mediaURL("download")')
|
||||
v-list-item-icon
|
||||
v-icon(v-text='mdiFileDownloadOutline')
|
||||
v-list-item-content
|
||||
v-list-item-title(v-text="$t('event.download_flyer')")
|
||||
//- download flyer
|
||||
v-list-item(v-if='hasMedia' :href='$helper.mediaURL(event, "download")')
|
||||
v-list-item-icon
|
||||
v-icon(v-text='mdiFileDownloadOutline')
|
||||
v-list-item-content
|
||||
v-list-item-title(v-text="$t('event.download_flyer')")
|
||||
|
||||
v-divider
|
||||
v-divider
|
||||
|
||||
//- admin actions
|
||||
eventAdmin(v-if='is_mine' :event='event')
|
||||
//- admin actions
|
||||
eventAdmin(v-if='is_mine' :event='event')
|
||||
|
||||
|
||||
.p-description.text-body-1.pa-3.rounded(v-if='hasMedia && event.description' itemprop='description' v-html='event.description')
|
||||
.p-description.text-body-1.pa-3.rounded(v-if='hasMedia && event.description' itemprop='description' v-html='event.description')
|
||||
|
||||
//- resources from fediverse
|
||||
#resources.mt-1(v-if='settings.enable_federation')
|
||||
//- div.float-right(v-if='settings.hide_boosts')
|
||||
//- small.mr-3 🔖 {{event.likes.length}}
|
||||
//- small ✊ {{event.boost.length}}<br/>
|
||||
//- resources from fediverse
|
||||
#resources.mt-1(v-if='settings.enable_federation')
|
||||
div.mb-3(v-if='!settings.hide_boosts && (event.boost?.length || event.likes?.length) ')
|
||||
client-only
|
||||
v-menu(open-on-hover top offset-y)
|
||||
template( v-slot:activator="{ on, attrs }")
|
||||
span.mr-3(v-bind='attrs' v-on='on') <v-icon color='primary' v-text='mdiBookmark' /> {{event.likes.length}}
|
||||
v-list
|
||||
v-list-item(v-for='(like, idx) in event.likes' :key='idx')
|
||||
v-list-item-title(v-text='like')
|
||||
v-menu(open-on-hover top offset-y)
|
||||
template( v-slot:activator="{ on, attrs }")
|
||||
span(v-bind='attrs' v-on='on') <v-icon v-text='mdiShareAll' /> {{event.boost.length}}
|
||||
v-list
|
||||
v-list-item(v-for='(boost, idx) in event.boost' :key='idx')
|
||||
v-list-item-title(v-text='boost')
|
||||
template(slot='placeholder')
|
||||
span.mr-3 <v-icon color='primary' v-text='mdiBookmark' /> {{event.likes.length}}
|
||||
span <v-icon v-text='mdiShareAll' /> {{event.boost.length}}
|
||||
|
||||
v-dialog(v-model='showResources' max-width="900" width="900" :fullscreen='$vuetify.breakpoint.xsOnly'
|
||||
destroy-on-close)
|
||||
v-card
|
||||
v-btn.ma-2(icon dark @click='showResources = false')
|
||||
v-icon(v-text='mdiClose')
|
||||
v-carousel.pa-5(:interval='10000'
|
||||
:next-icon='mdiArrowRight'
|
||||
:prev-icon='mdiArrowLeft'
|
||||
ref='carousel' hide-delimiters v-model='currentAttachment'
|
||||
height='100%' show-arrows-on-over)
|
||||
v-carousel-item(v-for='attachment in selectedResource.data.attachment'
|
||||
v-if='isImg(attachment)'
|
||||
:key='attachment.url')
|
||||
v-img(:src='attachment.url' contain max-height='90%')
|
||||
v-card-actions.align-center.justify-center
|
||||
span {{currentAttachmentLabel}}
|
||||
v-dialog(v-model='showResources' max-width="900" width="900" :fullscreen='$vuetify.breakpoint.xsOnly'
|
||||
destroy-on-close)
|
||||
v-card
|
||||
v-btn.ma-2(icon dark @click='showResources = false')
|
||||
v-icon(v-text='mdiClose')
|
||||
v-carousel.pa-5(:interval='10000'
|
||||
:next-icon='mdiArrowRight'
|
||||
:prev-icon='mdiArrowLeft'
|
||||
ref='carousel' hide-delimiters v-model='currentAttachment'
|
||||
height='100%' show-arrows-on-over)
|
||||
v-carousel-item(v-for='attachment in selectedResource.data.attachment'
|
||||
v-if='isImg(attachment)'
|
||||
:key='attachment.url')
|
||||
v-img(:src='attachment.url' contain max-height='90%')
|
||||
v-card-actions.align-center.justify-center
|
||||
span {{currentAttachmentLabel}}
|
||||
|
||||
v-card.mb-3.resources(v-if='settings.enable_resources' v-for='resource in event.resources'
|
||||
:key='resource.id' elevation='10' :flat='resource.hidden' outlined)
|
||||
v-card-title
|
||||
v-menu(v-if='$auth.user && $auth.user.is_admin' offset-y)
|
||||
template(v-slot:activator="{ on }")
|
||||
v-btn.mr-2(v-on='on' color='primary' small icon)
|
||||
v-icon(v-text='mdiDotsVertical')
|
||||
v-list
|
||||
v-list-item(v-if='!resource.hidden' @click='hideResource(resource, true)')
|
||||
v-list-item-title <v-icon left v-text='mdiEyeOff'></v-icon> {{$t('admin.hide_resource')}}
|
||||
v-list-item(v-else @click='hideResource(resource, false)')
|
||||
v-list-item-title <v-icon left v-text='mdiEye'></v-icon> {{$t('admin.show_resource')}}
|
||||
v-list-item(@click='deleteResource(resource)')
|
||||
v-list-item-title <v-icon left v-text='mdiDelete'></v-icon> {{$t('admin.delete_resource')}}
|
||||
v-list-item(@click='blockUser(resource)')
|
||||
v-list-item-title <v-icon left v-text='mdiLock'></v-icon> {{$t('admin.block_user')}}
|
||||
v-card.mb-3.resources(v-if='settings.enable_resources' v-for='resource in event.resources'
|
||||
:key='resource.id' elevation='10' :flat='resource.hidden' outlined)
|
||||
v-card-title
|
||||
v-menu(v-if='$auth.user && $auth.user.is_admin' offset-y)
|
||||
template(v-slot:activator="{ on }")
|
||||
v-btn.mr-2(v-on='on' color='primary' small icon)
|
||||
v-icon(v-text='mdiDotsVertical')
|
||||
v-list
|
||||
v-list-item(v-if='!resource.hidden' @click='hideResource(resource, true)')
|
||||
v-list-item-title <v-icon left v-text='mdiEyeOff'></v-icon> {{$t('admin.hide_resource')}}
|
||||
v-list-item(v-else @click='hideResource(resource, false)')
|
||||
v-list-item-title <v-icon left v-text='mdiEye'></v-icon> {{$t('admin.show_resource')}}
|
||||
v-list-item(@click='deleteResource(resource)')
|
||||
v-list-item-title <v-icon left v-text='mdiDelete'></v-icon> {{$t('admin.delete_resource')}}
|
||||
v-list-item(@click='blockUser(resource)')
|
||||
v-list-item-title <v-icon left v-text='mdiLock'></v-icon> {{$t('admin.block_user')}}
|
||||
|
||||
v-icon.mr-1(v-show='resource.hidden' v-text='mdiEyeOff')
|
||||
v-icon.mr-1(v-show='resource.hidden' v-text='mdiEyeOff')
|
||||
|
||||
a(:href='resource.data.url || resource.data.context')
|
||||
small {{resource.data.published|dateFormat('ddd, D MMMM HH:mm')}}
|
||||
a(:href='resource.data.url || resource.data.context')
|
||||
small {{$time.format(resource.data.published,'ff')}}
|
||||
|
||||
v-card-text
|
||||
v-card-text
|
||||
|
||||
div.mt-1(v-html='resource_filter(resource.data.content)')
|
||||
div.d-flex.flex-wrap
|
||||
span.mr-1(v-for='attachment in resource.data.attachment' :key='attachment.url')
|
||||
audio(v-if='isAudio(attachment)' controls)
|
||||
source(:src='attachment.url')
|
||||
v-img.cursorPointer(v-if='isImg(attachment)' :src='attachment.url' @click='showResource(resource)'
|
||||
max-height="250px"
|
||||
max-width="250px"
|
||||
contain :alt='attachment.name')
|
||||
div.mt-1(v-html='resource_filter(resource.data.content)')
|
||||
div.d-flex.flex-wrap
|
||||
span.mr-1(v-for='attachment in resource.data.attachment' :key='attachment.url')
|
||||
audio(v-if='isAudio(attachment)' controls)
|
||||
source(:src='attachment.url')
|
||||
v-img.cursorPointer(v-if='isImg(attachment)' :src='attachment.url' @click='showResource(resource)'
|
||||
max-height="250px"
|
||||
max-width="250px"
|
||||
contain :alt='attachment.name')
|
||||
|
||||
//- Next/prev arrow
|
||||
.text-center.mt-5.mb-5
|
||||
v-btn.mr-2(nuxt icon outlined color='primary'
|
||||
:to='`/event/${event.prev}`' :disabled='!event.prev')
|
||||
v-icon(v-text='mdiArrowLeft')
|
||||
v-btn(nuxt bottom right outlined icon color='primary'
|
||||
:to='`/event/${event.next}`' :disabled='!event.next')
|
||||
v-icon(v-text='mdiArrowRight')
|
||||
//- Next/prev arrow
|
||||
.text-center.mt-5.mb-5
|
||||
v-btn.mr-2(nuxt icon outlined color='primary'
|
||||
:to='`/event/${event.prev}`' :disabled='!event.prev')
|
||||
v-icon(v-text='mdiArrowLeft')
|
||||
v-btn(nuxt bottom right outlined icon color='primary'
|
||||
:to='`/event/${event.next}`' :disabled='!event.next')
|
||||
v-icon(v-text='mdiArrowRight')
|
||||
|
||||
v-dialog(v-model='showEmbed' width='700px' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||
EmbedEvent(:event='event' @close='showEmbed=false')
|
||||
|
||||
v-dialog(v-show='settings.allow_geolocation && event.place.latitude && event.place.longitude' v-model='mapModal' :fullscreen='$vuetify.breakpoint.xsOnly' destroy-on-close)
|
||||
Map(:event='event' @close='mapModal=false')
|
||||
v-dialog(v-model='showEmbed' width='700px' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||
EmbedEvent(:event='event' @close='showEmbed=false')
|
||||
|
||||
v-dialog(v-show='settings.allow_geolocation && event.place.latitude && event.place.longitude' v-model='mapModal' :fullscreen='$vuetify.breakpoint.xsOnly' destroy-on-close)
|
||||
Map(:event='event' @close='mapModal=false')
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import get from 'lodash/get'
|
||||
import moment from 'dayjs'
|
||||
import { DateTime } from 'luxon'
|
||||
import clipboard from '../../assets/clipboard'
|
||||
import MyPicture from '~/components/MyPicture'
|
||||
import EventAdmin from '@/components/eventAdmin'
|
||||
|
@ -187,8 +199,8 @@ import EmbedEvent from '@/components/embedEvent'
|
|||
const { htmlToText } = require('html-to-text')
|
||||
|
||||
import { mdiArrowLeft, mdiArrowRight, mdiDotsVertical, mdiCodeTags, mdiClose, mdiMap,
|
||||
mdiEye, mdiEyeOff, mdiDelete, mdiRepeat, mdiLock, mdiFileDownloadOutline,
|
||||
mdiCalendarExport, mdiCalendar, mdiContentCopy, mdiMapMarker, mdiChevronUp, mdiMonitorAccount } from '@mdi/js'
|
||||
mdiEye, mdiEyeOff, mdiDelete, mdiRepeat, mdiLock, mdiFileDownloadOutline, mdiShareAll,
|
||||
mdiCalendarExport, mdiCalendar, mdiContentCopy, mdiMapMarker, mdiChevronUp, mdiMonitorAccount, mdiBookmark } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
name: 'Event',
|
||||
|
@ -210,7 +222,7 @@ export default {
|
|||
data ({$store}) {
|
||||
return {
|
||||
mdiArrowLeft, mdiArrowRight, mdiDotsVertical, mdiCodeTags, mdiCalendarExport, mdiCalendar, mdiFileDownloadOutline,
|
||||
mdiMapMarker, mdiContentCopy, mdiClose, mdiDelete, mdiEye, mdiEyeOff, mdiRepeat, mdiLock, mdiMap, mdiChevronUp, mdiMonitorAccount,
|
||||
mdiMapMarker, mdiContentCopy, mdiClose, mdiDelete, mdiEye, mdiEyeOff, mdiRepeat, mdiLock, mdiMap, mdiChevronUp, mdiMonitorAccount, mdiBookmark, mdiShareAll,
|
||||
currentAttachment: 0,
|
||||
event: {},
|
||||
diocane: '',
|
||||
|
@ -261,23 +273,23 @@ export default {
|
|||
{ property: 'og:type', content: 'event' },
|
||||
{
|
||||
property: 'og:image',
|
||||
content: this.$options.filters.mediaURL(this.event)
|
||||
content: this.$helper.mediaURL(this.event)
|
||||
},
|
||||
{ property: 'og:site_name', content: this.settings.title },
|
||||
{
|
||||
property: 'og:updated_time',
|
||||
content: moment.unix(this.event.start_datetime).format()
|
||||
content: DateTime.fromSeconds(this.event.start_datetime, { zone: this.settings.instance_timezone }).toISO()
|
||||
},
|
||||
{
|
||||
property: 'article:published_time',
|
||||
content: moment.unix(this.event.start_datetime).format()
|
||||
content: DateTime.fromSeconds(this.event.start_datetime, { zone: this.settings.instance_timezone }).toISO()
|
||||
},
|
||||
{ property: 'article:section', content: 'event' },
|
||||
{ property: 'twitter:card', content: 'summary' },
|
||||
{ property: 'twitter:title', content: this.event.title },
|
||||
{
|
||||
property: 'twitter:image',
|
||||
content: this.$options.filters.mediaURL(this.event)
|
||||
content: this.$helper.mediaURL(this.event)
|
||||
},
|
||||
{
|
||||
property: 'twitter:description',
|
||||
|
@ -285,7 +297,7 @@ export default {
|
|||
}
|
||||
],
|
||||
link: [
|
||||
{ rel: 'image_src', href: this.$options.filters.mediaURL(this.event) },
|
||||
{ rel: 'image_src', href: this.$helper.mediaURL(this.event) },
|
||||
{
|
||||
rel: 'alternate',
|
||||
type: 'application/rss+xml',
|
||||
|
@ -320,7 +332,7 @@ export default {
|
|||
mounted () {
|
||||
window.addEventListener('keydown', this.keyDown)
|
||||
},
|
||||
destroyed () {
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.keyDown)
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -81,7 +81,6 @@ v-container.pa-0.pa-md-3
|
|||
|
||||
</template>
|
||||
<script>
|
||||
import dayjs from 'dayjs'
|
||||
import { mapState } from 'vuex'
|
||||
import FollowMe from '../components/FollowMe'
|
||||
import Search from '@/components/Search'
|
||||
|
@ -95,9 +94,9 @@ export default {
|
|||
Search
|
||||
},
|
||||
mixins: [clipboard],
|
||||
async asyncData ({ $axios, params, store, $api }) {
|
||||
async asyncData ({ $axios, params, store, $api, $time }) {
|
||||
const events = await $api.getEvents({
|
||||
start: dayjs().unix(),
|
||||
start: $time.currentTimestamp(),
|
||||
show_recurrent: false
|
||||
})
|
||||
return { events }
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template lang="pug">
|
||||
v-container.px-2.px-sm-6.pt-0
|
||||
v-container.px-2.px-sm-6.pt-0#home
|
||||
|
||||
//- View
|
||||
#themeview.mt-sm-4.mt-2
|
||||
|
@ -10,14 +10,17 @@ v-container.px-2.px-sm-6.pt-0
|
|||
Announcement(v-for='announcement in announcements' :key='`a_${announcement.id}`' :announcement='announcement')
|
||||
|
||||
//- Events
|
||||
#events.mt-sm-4.mt-2
|
||||
Event(:event='event' v-for='(event, idx) in visibleEvents' :lazy='idx>2' :key='event.id')
|
||||
#events.mt-sm-4.mt-2(v-if='!$fetchState.pending')
|
||||
v-lazy.event.v-card(:value='idx<9' v-for='(event, idx) in visibleEvents' :key='event.id' :min-height='hide_thumbs ? 105 : undefined' :options="{ threshold: .5, rootMargin: '500px' }" :class="{ 'theme--dark': is_dark }")
|
||||
Event(:event='event' :lazy='idx>9')
|
||||
.text-center(v-else)
|
||||
v-progress-circular.justify-center.align-center(color='primary' indeterminate model-value='20')
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapActions } from 'vuex'
|
||||
import debounce from 'lodash/debounce'
|
||||
import dayjs from 'dayjs'
|
||||
import { mapState, mapActions, mapGetters } from 'vuex'
|
||||
import { DateTime } from 'luxon'
|
||||
import Event from '@/components/Event'
|
||||
import Announcement from '@/components/Announcement'
|
||||
import ThemeView from '@/components/ThemeView'
|
||||
|
@ -28,22 +31,24 @@ export default {
|
|||
components: { Event, Announcement, ThemeView },
|
||||
middleware: 'setup',
|
||||
fetch () {
|
||||
return this.getEvents({
|
||||
start: this.start,
|
||||
end: this.end
|
||||
})
|
||||
},
|
||||
activated() {
|
||||
if (this.$fetchState.timestamp <= Date.now() - 60000) {
|
||||
this.$fetch()
|
||||
if (this.filter.query) {
|
||||
return this.getEvents({
|
||||
query: this.filter.query,
|
||||
older: true
|
||||
})
|
||||
} else {
|
||||
return this.getEvents({
|
||||
start: this.start,
|
||||
end: this.end,
|
||||
})
|
||||
}
|
||||
},
|
||||
data ({ $store }) {
|
||||
data ({ $time }) {
|
||||
return {
|
||||
mdiMagnify, mdiCloseCircle,
|
||||
isCurrentMonth: true,
|
||||
now: dayjs().unix(),
|
||||
start: dayjs().startOf('month').unix(),
|
||||
now: $time.nowUnix(),
|
||||
start: $time.startMonth(),
|
||||
end: null,
|
||||
tmpEvents: [],
|
||||
selectedDay: null,
|
||||
|
@ -70,16 +75,14 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
...mapState(['settings', 'announcements', 'events', 'filter']),
|
||||
...mapGetters(['hide_thumbs', 'is_dark']),
|
||||
visibleEvents () {
|
||||
if (this.filter.query && this.filter.query.length > 2) {
|
||||
return this.tmpEvents
|
||||
}
|
||||
const now = dayjs().unix()
|
||||
const now = this.$time.nowUnix()
|
||||
if (this.selectedDay) {
|
||||
const min = dayjs.tz(this.selectedDay).startOf('day').unix()
|
||||
const max = dayjs.tz(this.selectedDay).endOf('day').unix()
|
||||
return this.events.filter(e => (e.start_datetime <= max && (e.end_datetime || e.start_datetime) >= min) && (this.filter.show_recurrent || !e.parentId))
|
||||
} else if (this.isCurrentMonth) {
|
||||
const min = this.selectedDay.startOf('day').toUnixInteger()
|
||||
const max = this.selectedDay.endOf('day').toUnixInteger()
|
||||
return this.events.filter(e => (e.start_datetime < max && (e.end_datetime || e.start_datetime) > min) && (this.filter.show_recurrent || !e.parentId))
|
||||
} else if (this.isCurrentMonth && !this.filter.query) {
|
||||
return this.events.filter(e => ((e.end_datetime ? e.end_datetime > now : e.start_datetime + 3 * 60 * 60 > now) && (this.filter.show_recurrent || !e.parentId)))
|
||||
} else {
|
||||
return this.events.filter(e => this.filter.show_recurrent || !e.parentId)
|
||||
|
@ -89,16 +92,11 @@ export default {
|
|||
created () {
|
||||
this.$root.$on('dayclick', this.dayChange)
|
||||
this.$root.$on('monthchange', this.monthChange)
|
||||
this.storeUnsubscribe = this.$store.subscribeAction( { after: (action, state) => {
|
||||
if (action.type === 'setFilter') {
|
||||
if (this.filter.query && this.filter.query.length > 2) {
|
||||
this.search()
|
||||
} else {
|
||||
this.tmpEvents = []
|
||||
this.$fetch()
|
||||
}
|
||||
}
|
||||
}})
|
||||
if (process.client) {
|
||||
this.storeUnsubscribe = this.$store.subscribeAction( { after: (action, state) => {
|
||||
if (action.type === 'setFilter') { this.$fetch() }
|
||||
}})
|
||||
}
|
||||
},
|
||||
destroyed () {
|
||||
this.$root.$off('dayclick')
|
||||
|
@ -109,37 +107,36 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
...mapActions(['getEvents']),
|
||||
search: debounce(async function() {
|
||||
this.tmpEvents = await this.$api.getEvents({
|
||||
start: 0,
|
||||
show_recurrent: this.filter.show_recurrent,
|
||||
show_multidate: this.filter.show_multidate,
|
||||
query: this.filter.query
|
||||
})
|
||||
}, 200),
|
||||
async monthChange ({ year, month }) {
|
||||
if (this.filter.query) return
|
||||
this.$nuxt.$loading.start()
|
||||
let isCurrentMonth
|
||||
|
||||
// unselect current selected day
|
||||
this.selectedDay = null
|
||||
|
||||
const now = DateTime.local({zone: this.settings.instance_timezone})
|
||||
// check if current month is selected
|
||||
if (month - 1 === dayjs.tz().month() && year === dayjs.tz().year()) {
|
||||
if (month === now.month && year === now.year) {
|
||||
isCurrentMonth = true
|
||||
this.start = dayjs().startOf('month').unix()
|
||||
this.start = now.startOf('month').toUnixInteger()
|
||||
this.end = null
|
||||
} else {
|
||||
isCurrentMonth = false
|
||||
this.start = dayjs().year(year).month(month - 1).startOf('month').unix() // .startOf('week').unix()
|
||||
this.start = DateTime.local(year, month, { zone: this.settings.instance_timezone }).toUnixInteger()
|
||||
this.end = DateTime.local(year, month, { zone: this.settings.instance_timezone }).plus({ month: !this.$vuetify.breakpoint.smAndDown ? 1 : 0 }).endOf('month').toUnixInteger() // .endOf('week').unix()
|
||||
}
|
||||
this.end = dayjs().year(year).month(month).endOf('month').unix() // .endOf('week').unix()
|
||||
await this.$fetch()
|
||||
this.$nuxt.$loading.finish()
|
||||
this.$nextTick( () => this.isCurrentMonth = isCurrentMonth)
|
||||
|
||||
},
|
||||
dayChange (day) {
|
||||
this.selectedDay = day ? dayjs.tz(day).format('YYYY-MM-DD') : null
|
||||
if (!day) {
|
||||
this.selectedDay = null
|
||||
return
|
||||
}
|
||||
const date = DateTime.fromJSDate(day)
|
||||
this.selectedDay = day ? DateTime.local({ zone: this.settings.instance_timezone }).set({ year: date.year, month: date.month, day: date.day}) : null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
<template>
|
||||
<v-container class='px-0' fluid>
|
||||
<v-container id='home' class='px-2 px-sm-6 pt-0'>
|
||||
<h1 class='d-block text-h4 font-weight-black text-center text-uppercase mt-10 mx-auto w-100 text-underline'>
|
||||
<u>{{ place.name }}</u>
|
||||
</h1>
|
||||
<span class="d-block text-subtitle text-center w-100 mb-14">{{ place.address }}</span>
|
||||
|
||||
<!-- Events -->
|
||||
<div class="mb-2 mt-1 pl-1 pl-sm-2" id="events">
|
||||
<Event :event='event' v-for='(event, idx) in events' :lazy='idx > 2' :key='event.id'></Event>
|
||||
<div id="events">
|
||||
<v-lazy class='event v-card' :value='idx<9' v-for='(event, idx) in events' :key='event.id' :min-height='hide_thumbs ? 105 : undefined' :options="{ threshold: .5, rootMargin: '500px' }" :class="{ 'theme--dark': is_dark }">
|
||||
<Event :event='event' :lazy='idx > 9' />
|
||||
</v-lazy>
|
||||
</div>
|
||||
</v-container>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import { mapState } from 'vuex'
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
import Event from '@/components/Event'
|
||||
|
||||
export default {
|
||||
|
@ -29,7 +31,10 @@ export default {
|
|||
]
|
||||
}
|
||||
},
|
||||
computed: mapState(['settings']),
|
||||
computed: {
|
||||
...mapState(['settings']),
|
||||
...mapGetters(['hide_thumbs', 'is_dark']),
|
||||
},
|
||||
asyncData({ $axios, params, error }) {
|
||||
try {
|
||||
const place = params.place
|
||||
|
|
|
@ -11,7 +11,6 @@ v-container#home(fluid)
|
|||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import dayjs from 'dayjs'
|
||||
import Event from '@/components/Event'
|
||||
import Announcement from '@/components/Announcement'
|
||||
import Calendar from '@/components/Calendar'
|
||||
|
@ -20,11 +19,11 @@ import { mdiMagnify } from '@mdi/js'
|
|||
export default {
|
||||
name: 'Index',
|
||||
components: { Event, Announcement, Calendar },
|
||||
data () {
|
||||
data ({ $time }) {
|
||||
return {
|
||||
mdiMagnify,
|
||||
events: [],
|
||||
start: dayjs().startOf('month').unix(),
|
||||
start: $time.startMonth(),
|
||||
end: null,
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
<template>
|
||||
<v-container class='px-0' fluid>
|
||||
<v-container id='home' class='px-2 px-sm-6 pt-0' fluid>
|
||||
|
||||
<h1 class='d-block text-h3 font-weight-black text-center text-uppercase mt-10 mb-16 mx-auto w-100 text-underline'><u>{{tag}}</u></h1>
|
||||
|
||||
<!-- Events -->
|
||||
<div class="mb-2 mt-1 pl-1 pl-sm-2" id="events">
|
||||
<Event :event='event' v-for='(event, idx) in events' :lazy='idx>2' :key='event.id'></Event>
|
||||
<v-lazy class='event v-card' :value='idx<9' v-for='(event, idx) in events' :key='event.id' :min-height='hide_thumbs ? 105 : undefined' :options="{ threshold: .5, rootMargin: '500px' }" :class="{ 'theme--dark': is_dark }">
|
||||
<Event :event='event' :lazy='idx>9' />
|
||||
</v-lazy>
|
||||
</div>
|
||||
</v-container>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import { mapState } from 'vuex'
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
import Event from '@/components/Event'
|
||||
|
||||
export default {
|
||||
|
@ -28,7 +30,10 @@ export default {
|
|||
]
|
||||
}
|
||||
},
|
||||
computed: mapState(['settings']),
|
||||
computed: {
|
||||
...mapState(['settings']),
|
||||
...mapGetters(['hide_thumbs', 'is_dark']),
|
||||
},
|
||||
async asyncData ({ $axios, params, error }) {
|
||||
try {
|
||||
const tag = params.tag
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
import Vue from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import utc from 'dayjs/plugin/utc'
|
||||
import timezone from 'dayjs/plugin/timezone'
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat'
|
||||
|
||||
import 'dayjs/locale/it'
|
||||
import 'dayjs/locale/en'
|
||||
import 'dayjs/locale/es'
|
||||
import 'dayjs/locale/ca'
|
||||
import 'dayjs/locale/pl'
|
||||
import 'dayjs/locale/eu'
|
||||
import 'dayjs/locale/nb'
|
||||
import 'dayjs/locale/fr'
|
||||
import 'dayjs/locale/de'
|
||||
import 'dayjs/locale/gl'
|
||||
import 'dayjs/locale/sk'
|
||||
import 'dayjs/locale/ru'
|
||||
import 'dayjs/locale/pt'
|
||||
import 'dayjs/locale/zh'
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(localizedFormat)
|
||||
|
||||
export default ({ app, store }) => {
|
||||
// set timezone to instance_timezone!!
|
||||
// to show local time relative to event's place
|
||||
// not where in the world I'm looking at the page from
|
||||
app.i18n.defaultLocale = store.state.settings.instance_locale
|
||||
const instance_timezone = store.state.settings.instance_timezone
|
||||
dayjs.tz.setDefault(instance_timezone)
|
||||
dayjs.locale(app.i18n.locale || store.state.settings.instance_locale)
|
||||
|
||||
// replace links with anchors
|
||||
// TODO: remove fb tracking id?
|
||||
Vue.filter('linkify', value => value.replace(/(https?:\/\/([^\s]+))/g, '<a href="$1">$2</a>'))
|
||||
Vue.filter('url2host', url => url.match(/^https?:\/\/(.[^/:]+)/i)[1])
|
||||
Vue.filter('datetime', value => dayjs.tz(value).locale(app.i18n.locale || store.state.settings.instance_locale).format('ddd, D MMMM HH:mm'))
|
||||
Vue.filter('dateFormat', (value, format) => dayjs.tz(value).format(format))
|
||||
Vue.filter('unixFormat', (timestamp, format) => dayjs.unix(timestamp).tz().format(format))
|
||||
|
||||
// shown in mobile homepage
|
||||
Vue.filter('day', value => dayjs.unix(value).tz().locale(app.i18n.locale || store.state.settings.instance_locale).format('dddd, D MMM'))
|
||||
Vue.filter('mediaURL', (event, type, format = '.jpg') => {
|
||||
const mediaPath = type === 'download' ? '/download/' : '/media/'
|
||||
if (event.media && event.media.length) {
|
||||
if (type === 'alt') {
|
||||
return event.media[0].name
|
||||
} else {
|
||||
return store.state.settings.baseurl + mediaPath + (type === 'thumb' ? 'thumb/' : '') + event.media[0].url.replace(/.jpg$/, format)
|
||||
}
|
||||
} else if (type !== 'alt') {
|
||||
return store.state.settings.baseurl + mediaPath + (type === 'thumb' ? 'thumb/' : '') + 'logo.svg'
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
Vue.filter('from', timestamp => dayjs.unix(timestamp).tz().locale(app.i18n.locale || store.state.settings.instance_locale).fromNow())
|
||||
|
||||
Vue.filter('recurrentDetail', event => {
|
||||
const parent = event.parent
|
||||
if (!parent.recurrent || !parent.recurrent.frequency) return 'error!'
|
||||
const { frequency, type } = parent.recurrent
|
||||
let recurrent
|
||||
if (frequency === '1w' || frequency === '2w') {
|
||||
recurrent = app.i18n.t(`event.recurrent_${frequency}_days`, { days: dayjs.unix(parent.start_datetime).tz().format('dddd') })
|
||||
} else if (frequency === '1m' || frequency === '2m') {
|
||||
const d = type === 'ordinal' ? dayjs.unix(parent.start_datetime).date() : dayjs.unix(parent.start_datetime).tz().format('dddd')
|
||||
if (type === 'ordinal') {
|
||||
recurrent = app.i18n.t(`event.recurrent_${frequency}_days`, { days: d })
|
||||
} else {
|
||||
recurrent = app.i18n.t(`event.recurrent_${frequency}_ordinal`,
|
||||
{ n: app.i18n.t('ordinal.' + type), days: d })
|
||||
}
|
||||
}
|
||||
return recurrent
|
||||
})
|
||||
|
||||
Vue.filter('when', (event) => {
|
||||
const start = dayjs.unix(event.start_datetime).tz().locale(app.i18n.locale || store.state.settings.instance_locale)
|
||||
const end = event.end_datetime && dayjs.unix(event.end_datetime).tz().locale(app.i18n.locale || store.state.settings.instance_locale)
|
||||
|
||||
let time = start.format('dddd D MMMM HH:mm')
|
||||
if (end) {
|
||||
time += event.multidate ? ` → ${end.format('dddd D MMMM HH:mm')}` : `-${end.format('HH:mm')}`
|
||||
}
|
||||
return time
|
||||
})
|
||||
}
|
21
plugins/helpers.js
Normal file
21
plugins/helpers.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
export default ({ store }, inject) => {
|
||||
|
||||
const helper = {
|
||||
mediaURL (event, type, format = '.jpg') {
|
||||
const mediaPath = type === 'download' ? '/download/' : '/media/'
|
||||
if (event.media && event.media.length) {
|
||||
if (type === 'alt') {
|
||||
return event.media[0].name
|
||||
} else {
|
||||
return store.state.settings.baseurl + mediaPath + (type === 'thumb' ? 'thumb/' : '') + event.media[0].url.replace(/.jpg$/, format)
|
||||
}
|
||||
} else if (type !== 'alt') {
|
||||
return store.state.settings.baseurl + mediaPath + (type === 'thumb' ? 'thumb/' : '') + 'logo.svg'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inject('helper', helper)
|
||||
}
|
170
plugins/time.js
Normal file
170
plugins/time.js
Normal file
|
@ -0,0 +1,170 @@
|
|||
import { DateTime, Settings } from 'luxon'
|
||||
export default ({ app, store }, inject) => {
|
||||
const zone = Settings.defaultZoneName = store.state.settings.instance_timezone
|
||||
Settings.defaultLocale = app.i18n.locale || store.state.settings.instance_locale
|
||||
const time = {
|
||||
|
||||
format (date, format) {
|
||||
return DateTime.fromISO(date, {
|
||||
zone: store.state.settings.instance_timezone,
|
||||
locale: app.i18n.locale || store.state.settings.instance_locale
|
||||
}).toFormat(format || '')
|
||||
},
|
||||
|
||||
unixFormat (timestamp, format='EEEE d MMMM HH:mm') {
|
||||
return DateTime.fromSeconds(timestamp, {
|
||||
zone: store.state.settings.instance_timezone,
|
||||
locale: app.i18n.locale || store.state.settings.instance_locale
|
||||
}).toFormat(format)
|
||||
},
|
||||
|
||||
fromUnix (timestamp) {
|
||||
return DateTime.fromSeconds(timestamp, { zone })
|
||||
},
|
||||
|
||||
fromDateInput (date, time) {
|
||||
const [hour, minute] = time.split(':')
|
||||
return DateTime.fromJSDate(date, { zone })
|
||||
.set({ hour: Number(hour), minute: Number(minute), second: 0 })
|
||||
.toUnixInteger()
|
||||
},
|
||||
|
||||
currentMonth () {
|
||||
return DateTime.local({ zone }).month
|
||||
},
|
||||
|
||||
currentYear () {
|
||||
return DateTime.local({ zone }).year
|
||||
},
|
||||
|
||||
when (event) {
|
||||
const currentYear = app.$time.currentYear()
|
||||
|
||||
const opt = {
|
||||
zone: store.state.settings.instance_timezone,
|
||||
locale: app.i18n.locale || store.state.settings.instance_locale
|
||||
}
|
||||
|
||||
const start = DateTime.fromSeconds(event.start_datetime, opt)
|
||||
let time = start.toFormat('EEEE d MMMM HH:mm')
|
||||
const end = event.end_datetime && DateTime.fromSeconds(event.end_datetime, opt)
|
||||
|
||||
if (end) {
|
||||
time += event.multidate ? ` → ${end.toFormat('EEEE d MMMM')}` : `-${end.toFormat('HH:mm')}`
|
||||
}
|
||||
|
||||
if (currentYear !== start.year) {
|
||||
time += ` (${start.year})`
|
||||
}
|
||||
return time
|
||||
},
|
||||
|
||||
nowUnix () {
|
||||
const opt = {
|
||||
zone,
|
||||
locale: app.i18n.locale || store.state.settings.instance_locale
|
||||
}
|
||||
return DateTime.local(opt).toUnixInteger()
|
||||
},
|
||||
|
||||
startMonth () { return DateTime.local({ zone }).startOf('month').toUnixInteger() },
|
||||
startOfDay (date) { return DateTime.fromJSDate(date, { zone }).startOf('day').toUnixInteger()},
|
||||
endOfDay (date) { return DateTime.fromJSDate(date, { zone }).endOf('day').toUnixInteger()},
|
||||
|
||||
|
||||
recurrentDetail (event) {
|
||||
const parent = event.parent
|
||||
if (!parent.recurrent || !parent.recurrent.frequency) return 'error!'
|
||||
const { frequency, type } = parent.recurrent
|
||||
let recurrent
|
||||
if (frequency === '1w' || frequency === '2w') {
|
||||
recurrent = app.i18n.t(`event.recurrent_${frequency}_days`, { days: DateTime.fromSeconds(parent.start_datetime).toFormat('EEEE') })
|
||||
} else if (frequency === '1m' || frequency === '2m') {
|
||||
const d = type === 'ordinal' ? DateTime.fromSeconds(parent.start_datetime).day : DateTime.fromSeconds(parent.start_datetime).toFormat('EEEE')
|
||||
if (type === 'ordinal') {
|
||||
recurrent = app.i18n.t(`event.recurrent_${frequency}_days`, { days: d })
|
||||
} else {
|
||||
recurrent = app.i18n.t(`event.recurrent_${frequency}_ordinal`,
|
||||
{ n: app.i18n.t('ordinal.' + type), days: d })
|
||||
}
|
||||
}
|
||||
return recurrent
|
||||
},
|
||||
|
||||
currentTimestamp () {
|
||||
return DateTime.local().toUnixInteger()
|
||||
},
|
||||
|
||||
from (timestamp) {
|
||||
const opt = {
|
||||
zone: store.state.settings.instance_timezone,
|
||||
locale: app.i18n.locale || store.state.settings.instance_locale
|
||||
}
|
||||
return DateTime.fromSeconds(timestamp, opt).toRelative()
|
||||
},
|
||||
|
||||
/**
|
||||
* @description build v-calendar attributes
|
||||
* @link https://vcalendar.io/attributes.html
|
||||
*/
|
||||
attributesFromEvents(events) {
|
||||
const attributes = []
|
||||
const opt = {
|
||||
zone: store.state.settings.instance_timezone,
|
||||
locale: app.i18n.locale || store.state.settings.instance_locale
|
||||
}
|
||||
const now = DateTime.local(opt).toUnixInteger()
|
||||
for (const e of events) {
|
||||
const tmp = DateTime.fromSeconds(e.start_datetime, opt)
|
||||
const start = DateTime.local().set({ year: tmp.year, month: tmp.month, day: tmp.day })
|
||||
// merge events with same date
|
||||
const key = `${start.month}${start.day}`
|
||||
const c = (e.end_datetime || e.start_datetime) < now ? 'vc-past' : ''
|
||||
|
||||
if (e.multidate === true && e.end_datetime) {
|
||||
attributes.push({
|
||||
dates: { start: start.toJSDate(), end: DateTime.fromSeconds(e.end_datetime).toJSDate() },
|
||||
highlight: {
|
||||
start: { fillMode: 'outline' },
|
||||
base: { fillMode: 'light' },
|
||||
end: { fillMode: 'outline' },
|
||||
}
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
const i = attributes.find(a => a.day === key)
|
||||
if (!i) {
|
||||
attributes.push({
|
||||
day: key,
|
||||
key: e.id, n: 1,
|
||||
dates: start.toJSDate(),
|
||||
dot: { color: 'teal', class: c }
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
i.n++
|
||||
if (i.n >= 20) {
|
||||
i.dot = { color: 'purple', class: c }
|
||||
} else if (i.n >= 10) {
|
||||
i.dot = { color: 'red', class: c }
|
||||
} else if (i.n >= 5) {
|
||||
i.dot = { color: 'orange', class: c }
|
||||
} else if (i.n >= 3) {
|
||||
i.dot = { color: 'yellow', class: c }
|
||||
} else {
|
||||
i.dot = { color: 'teal', class: c }
|
||||
}
|
||||
|
||||
}
|
||||
// add a bar to highlight today
|
||||
attributes.push({ key: 'today', dates: new Date(), highlight: { color: 'green', fillMode: 'outline' }})
|
||||
|
||||
return attributes
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inject('time', time)
|
||||
}
|
|
@ -1,9 +1,3 @@
|
|||
import Vue from 'vue'
|
||||
import VCalendar from 'v-calendar'
|
||||
export default () => {
|
||||
Vue.use(VCalendar, {
|
||||
componentPrefix: 'vc',
|
||||
// why is that ?!
|
||||
// firstDayOfWeek: 2
|
||||
})
|
||||
}
|
||||
Vue.use(VCalendar, { componentPrefix: 'vc' })
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const { Collection, Filter, Event, Tag, Place } = require('../models/models')
|
||||
|
||||
const log = require('../../log')
|
||||
const dayjs = require('dayjs')
|
||||
const { DateTime } = require('luxon')
|
||||
const { col: Col } = require('../../helpers')
|
||||
const { Op, Sequelize } = require('sequelize')
|
||||
|
||||
|
@ -21,7 +21,6 @@ const collectionController = {
|
|||
|
||||
// return events from collection
|
||||
async getEvents (req, res) {
|
||||
const format = req.params.format || 'json'
|
||||
const name = req.params.name
|
||||
|
||||
const collection = await Collection.findOne({ where: { name } })
|
||||
|
@ -33,7 +32,7 @@ const collectionController = {
|
|||
if (!filters.length) {
|
||||
return res.json([])
|
||||
}
|
||||
const start = dayjs().unix()
|
||||
const start = DateTime.local().toUnixInteger()
|
||||
const where = {
|
||||
// do not include parent recurrent event
|
||||
recurrent: null,
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
const crypto = require('crypto')
|
||||
const path = require('path')
|
||||
const config = require('../../config')
|
||||
const fs = require('fs')
|
||||
const fs = require('fs/promises')
|
||||
const { Op } = require('sequelize')
|
||||
const linkifyHtml = require('linkify-html')
|
||||
const Sequelize = require('sequelize')
|
||||
const dayjs = require('dayjs')
|
||||
const { DateTime } = require('luxon')
|
||||
const helpers = require('../../helpers')
|
||||
const Col = helpers.col
|
||||
const notifier = require('../../notifier')
|
||||
|
@ -85,73 +85,6 @@ const eventController = {
|
|||
return res.json(ret)
|
||||
},
|
||||
|
||||
|
||||
// async search(req, res) {
|
||||
// const search = req.query.search.trim().toLocaleLowerCase()
|
||||
// const show_recurrent = req.query.show_recurrent || false
|
||||
// const end = req.query.end
|
||||
// const replacements = []
|
||||
|
||||
// const where = {
|
||||
// // do not include parent recurrent event
|
||||
// recurrent: null,
|
||||
|
||||
// // confirmed event only
|
||||
// is_visible: true,
|
||||
|
||||
// }
|
||||
|
||||
// if (!show_recurrent) {
|
||||
// where.parentId = null
|
||||
// }
|
||||
|
||||
// if (end) {
|
||||
// where.start_datetime = { [Op.lte]: end }
|
||||
// }
|
||||
|
||||
// if (search) {
|
||||
// replacements.push(search)
|
||||
// where[Op.or] =
|
||||
// [
|
||||
// { title: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('title')), 'LIKE', '%' + search + '%') },
|
||||
// Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), 'LIKE', '%' + search + '%'),
|
||||
// Sequelize.fn('EXISTS', Sequelize.literal(`SELECT 1 FROM event_tags WHERE ${Col('event_tags.eventId')}=${Col('event.id')} AND LOWER(${Col('tagTag')}) = ?`))
|
||||
// ]
|
||||
// }
|
||||
|
||||
|
||||
// const events = await Event.findAll({
|
||||
// where,
|
||||
// attributes: {
|
||||
// exclude: ['likes', 'boost', 'userId', 'is_visible', 'createdAt', 'updatedAt', 'description', 'resources']
|
||||
// },
|
||||
// order: [['start_datetime', 'DESC']],
|
||||
// include: [
|
||||
// {
|
||||
// model: Tag,
|
||||
// // order: [Sequelize.literal('(SELECT COUNT("tagTag") FROM event_tags WHERE tagTag = tag) DESC')],
|
||||
// attributes: ['tag'],
|
||||
// through: { attributes: [] }
|
||||
// },
|
||||
// { model: Place, required: true, attributes: ['id', 'name', 'address', 'latitude', 'longitude'] }
|
||||
// ],
|
||||
// replacements,
|
||||
// limit: 30,
|
||||
// }).catch(e => {
|
||||
// log.error('[EVENT]', e)
|
||||
// return res.json([])
|
||||
// })
|
||||
|
||||
// const ret = events.map(e => {
|
||||
// e = e.get()
|
||||
// e.tags = e.tags ? e.tags.map(t => t && t.tag) : []
|
||||
// return e
|
||||
// })
|
||||
|
||||
// return res.json(ret)
|
||||
|
||||
// },
|
||||
|
||||
async _get(slug) {
|
||||
// retrocompatibility, old events URL does not use slug, use id as fallback
|
||||
const id = Number(slug) || -1
|
||||
|
@ -319,7 +252,7 @@ const eventController = {
|
|||
where: {
|
||||
parentId: null,
|
||||
is_visible: false,
|
||||
start_datetime: { [Op.gt]: dayjs().unix() }
|
||||
start_datetime: { [Op.gt]: DateTime.local().toUnixInteger() }
|
||||
},
|
||||
order: [['start_datetime', 'ASC']],
|
||||
include: [{ model: Tag, required: false }, Place]
|
||||
|
@ -496,8 +429,8 @@ const eventController = {
|
|||
try {
|
||||
const old_path = path.resolve(config.upload_path, event.media[0].url)
|
||||
const old_thumb_path = path.resolve(config.upload_path, 'thumb', event.media[0].url)
|
||||
fs.unlinkSync(old_path)
|
||||
fs.unlinkSync(old_thumb_path)
|
||||
await fs.unlink(old_path)
|
||||
await fs.unlink(old_thumb_path)
|
||||
} catch (e) {
|
||||
log.info(e.toString())
|
||||
}
|
||||
|
@ -575,8 +508,8 @@ const eventController = {
|
|||
try {
|
||||
const old_path = path.join(config.upload_path, event.media[0].url)
|
||||
const old_thumb_path = path.join(config.upload_path, 'thumb', event.media[0].url)
|
||||
fs.unlinkSync(old_thumb_path)
|
||||
fs.unlinkSync(old_path)
|
||||
await fs.unlink(old_thumb_path)
|
||||
await fs.unlink(old_path)
|
||||
} catch (e) {
|
||||
log.info(e.toString())
|
||||
}
|
||||
|
@ -601,7 +534,7 @@ const eventController = {
|
|||
* @returns
|
||||
*/
|
||||
async _select({
|
||||
start = dayjs().unix(),
|
||||
start = DateTime.local().toUnixInteger(),
|
||||
end,
|
||||
query,
|
||||
tags,
|
||||
|
@ -610,7 +543,8 @@ const eventController = {
|
|||
show_multidate,
|
||||
limit,
|
||||
page,
|
||||
older }) {
|
||||
older,
|
||||
reverse }) {
|
||||
|
||||
const where = {
|
||||
// do not include _parent_ recurrent event
|
||||
|
@ -680,7 +614,7 @@ const eventController = {
|
|||
attributes: {
|
||||
exclude: ['likes', 'boost', 'userId', 'is_visible', 'createdAt', 'description', 'resources', 'recurrent', 'placeId', 'image_path']
|
||||
},
|
||||
order: [['start_datetime', older ? 'DESC' : 'ASC']],
|
||||
order: [['start_datetime', reverse ? 'DESC' : 'ASC']],
|
||||
include: [
|
||||
{
|
||||
model: Tag,
|
||||
|
@ -709,7 +643,7 @@ const eventController = {
|
|||
*/
|
||||
async select(req, res) {
|
||||
const settings = res.locals.settings
|
||||
const start = req.query.start || dayjs().unix()
|
||||
const start = req.query.start || DateTime.local().toUnixInteger()
|
||||
const end = req.query.end
|
||||
const query = req.query.query
|
||||
const tags = req.query.tags
|
||||
|
@ -730,10 +664,12 @@ const eventController = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Ensure we have the next instance of a recurrent event
|
||||
* Ensure we have the next occurrence of a recurrent event
|
||||
*/
|
||||
async _createRecurrentOccurrence(e, startAt) {
|
||||
async _createRecurrentOccurrence(e, startAt = DateTime.local(), firstOccurrence = true) {
|
||||
log.debug(`Create recurrent event [${e.id}] ${e.title}"`)
|
||||
|
||||
// prepare the new event occurrence copying the parent's properties
|
||||
const event = {
|
||||
parentId: e.id,
|
||||
title: e.title,
|
||||
|
@ -744,55 +680,67 @@ const eventController = {
|
|||
placeId: e.placeId
|
||||
}
|
||||
|
||||
const recurrent = e.recurrent
|
||||
const start_date = dayjs.unix(e.start_datetime)
|
||||
let cursor = start_date > startAt ? start_date : startAt
|
||||
const recurrentDetails = e.recurrent
|
||||
const parentStartDatetime = DateTime.fromSeconds(e.start_datetime)
|
||||
|
||||
// cursor is when start to count
|
||||
// sets it to
|
||||
let cursor = parentStartDatetime > startAt ? parentStartDatetime : startAt
|
||||
startAt = cursor
|
||||
const duration = dayjs.unix(e.end_datetime).diff(start_date, 's')
|
||||
const frequency = recurrent.frequency
|
||||
const type = recurrent.type
|
||||
|
||||
cursor = cursor.hour(start_date.hour()).minute(start_date.minute()).second(0)
|
||||
const duration = e.end_datetime ? e.end_datetime-e.start_datetime : 0
|
||||
const frequency = recurrentDetails.frequency
|
||||
const type = recurrentDetails.type
|
||||
if (!frequency) {
|
||||
log.warn(`Recurrent event ${e.id} - ${e.title} does not have a frequency specified`)
|
||||
return
|
||||
}
|
||||
|
||||
if (!frequency) { return }
|
||||
cursor = cursor.set({ hour: parentStartDatetime.hour, minute: parentStartDatetime.minute, second: 0 })
|
||||
|
||||
// each week or 2
|
||||
if (frequency[1] === 'w') {
|
||||
cursor = cursor.day(start_date.day())
|
||||
if (cursor.isBefore(startAt)) {
|
||||
cursor = cursor.add(7, 'day')
|
||||
}
|
||||
if (frequency[0] === '2') {
|
||||
cursor = cursor.add(7, 'day')
|
||||
cursor = cursor.set({ weekday: parentStartDatetime.weekday }) //day(parentStartDatetime.day())
|
||||
if (cursor < startAt) {
|
||||
cursor = cursor.plus({ days: 7 * Number(frequency[0]) })
|
||||
}
|
||||
} else if (frequency === '1m') {
|
||||
if (type === 'ordinal') {
|
||||
cursor = cursor.date(start_date.date())
|
||||
cursor = cursor.set({ day: parentStartDatetime.day })
|
||||
|
||||
if (cursor.isBefore(startAt)) {
|
||||
cursor = cursor.add(1, 'month')
|
||||
if (cursor< startAt) {
|
||||
cursor = cursor.plus({ months: 1 })
|
||||
}
|
||||
} else { // weekday
|
||||
// get weekday
|
||||
// get recurrent freq details
|
||||
cursor = helpers.getWeekdayN(cursor, type, start_date.day())
|
||||
if (cursor.isBefore(startAt)) {
|
||||
cursor = cursor.add(4, 'week')
|
||||
cursor = helpers.getWeekdayN(cursor, type, start_date.day())
|
||||
cursor = helpers.getWeekdayN(cursor, type, parentStartDatetime.weekday)
|
||||
if (cursor< startAt) {
|
||||
cursor = cursor.plus({ months: 1 })
|
||||
cursor = helpers.getWeekdayN(cursor, type, parentStartDatetime.weekday)
|
||||
}
|
||||
}
|
||||
}
|
||||
log.debug(cursor)
|
||||
event.start_datetime = cursor.unix()
|
||||
event.end_datetime = event.start_datetime + duration
|
||||
const newEvent = await Event.create(event)
|
||||
return newEvent.addTags(e.tags)
|
||||
event.start_datetime = cursor.toUnixInteger()
|
||||
event.end_datetime = e.end_datetime ? event.start_datetime + duration : null
|
||||
try {
|
||||
const newEvent = await Event.create(event)
|
||||
if (e.tags) {
|
||||
return newEvent.addTags(e.tags)
|
||||
} else {
|
||||
return newEvent
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(event)
|
||||
log.error('[RECURRENT EVENT]', e)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Create instances of recurrent events
|
||||
*/
|
||||
async _createRecurrent(start_datetime = dayjs().unix()) {
|
||||
async _createRecurrent(start_datetime = DateTime.local().toUnixInteger()) {
|
||||
// select recurrent events and its childs
|
||||
const events = await Event.findAll({
|
||||
where: { is_visible: true, recurrent: { [Op.ne]: null } },
|
||||
|
@ -805,9 +753,9 @@ const eventController = {
|
|||
const creations = events.map(e => {
|
||||
if (e.child.length) {
|
||||
if (e.child.find(c => c.is_visible)) return
|
||||
return eventController._createRecurrentOccurrence(e, dayjs.unix(e.child[0].start_datetime + 1))
|
||||
return eventController._createRecurrentOccurrence(e, DateTime.fromSeconds(e.child[0].start_datetime + 1), false)
|
||||
}
|
||||
return eventController._createRecurrentOccurrence(e, dayjs())
|
||||
return eventController._createRecurrentOccurrence(e, DateTime.local(), true)
|
||||
})
|
||||
|
||||
return Promise.all(creations)
|
||||
|
|
|
@ -2,7 +2,7 @@ const { Event, Place, Tag } = require('../models/models')
|
|||
|
||||
const { htmlToText } = require('html-to-text')
|
||||
const { Op, literal } = require('sequelize')
|
||||
const moment = require('dayjs')
|
||||
const { DateTime } = require('luxon')
|
||||
const ics = require('ics')
|
||||
|
||||
const exportController = {
|
||||
|
@ -13,8 +13,13 @@ const exportController = {
|
|||
const places = req.query.places
|
||||
const show_recurrent = !!req.query.show_recurrent
|
||||
|
||||
const opt = {
|
||||
zone: res.locals.settings.instance_timezone,
|
||||
locale: res.locals.settings.instance_locale
|
||||
}
|
||||
|
||||
const where = {}
|
||||
const yesterday = moment().subtract('1', 'day').unix()
|
||||
const yesterday = DateTime.local(opt).minus({day: 1}).toUnixInteger()
|
||||
|
||||
|
||||
if (tags && places) {
|
||||
|
@ -69,8 +74,18 @@ const exportController = {
|
|||
|
||||
feed (_req, res, events, title = res.locals.settings.title, link = `${res.locals.settings.baseurl}/feed/rss`) {
|
||||
const settings = res.locals.settings
|
||||
|
||||
const opt = {
|
||||
zone: settings.instance_timezone,
|
||||
locale: settings.instance_locale
|
||||
}
|
||||
|
||||
function unixFormat (timestamp, format='EEEE d MMMM HH:mm') {
|
||||
return DateTime.fromSeconds(timestamp, opt).toFormat(format)
|
||||
}
|
||||
|
||||
res.type('application/rss+xml; charset=UTF-8')
|
||||
res.render('feed/rss.pug', { events, settings, moment, title, link })
|
||||
res.render('feed/rss.pug', { events, settings, unixFormat, title, link })
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -82,8 +97,8 @@ const exportController = {
|
|||
const settings = res.locals.settings
|
||||
const eventsMap = events.map(e => {
|
||||
|
||||
const tmpStart = moment.unix(e.start_datetime)
|
||||
const start = tmpStart.utc(true).format('YYYY-M-D-H-m').split('-').map(Number)
|
||||
const tmpStart = DateTime.fromSeconds(e.start_datetime)
|
||||
const start = [ tmpStart.year, tmpStart.month, tmpStart.day, tmpStart.hour, tmpStart.minute ]
|
||||
|
||||
const ret = {
|
||||
uid: `${e.id}@${settings.hostname}`,
|
||||
|
@ -99,8 +114,8 @@ const exportController = {
|
|||
}
|
||||
|
||||
if (e.end_datetime) {
|
||||
const tmpEnd = moment.unix(e.end_datetime)
|
||||
const end = tmpEnd.utc(true).format('YYYY-M-D-H-m').split('-').map(Number)
|
||||
const tmpEnd = DateTime.fromSeconds(e.end_datetime)
|
||||
const end = [ tmpEnd.year, tmpEnd.month, tmpEnd.day, tmpEnd.hour, tmpEnd.minute ]
|
||||
ret.end = end
|
||||
}
|
||||
|
||||
|
|
|
@ -56,11 +56,11 @@ const geocodingController = {
|
|||
},
|
||||
|
||||
async nominatimRateLimit(req, res, next) {
|
||||
geocodingController.providerRateLimit(req, res, next, nominatim.cache)
|
||||
process.env.NODE_ENV === 'test' ? next() : geocodingController.providerRateLimit(req, res, next, nominatim.cache)
|
||||
},
|
||||
|
||||
async photonRateLimit(req, res, next) {
|
||||
geocodingController.providerRateLimit(req, res, next, photon.cache)
|
||||
process.env.NODE_ENV === 'test' ? next() : geocodingController.providerRateLimit(req, res, next, photon.cache)
|
||||
},
|
||||
|
||||
async checkInCache (req, res, details, providerCache) {
|
||||
|
|
|
@ -18,7 +18,8 @@ const localeController = {
|
|||
|
||||
// check if we have a user custom messages
|
||||
let customLocaleMessages = {}
|
||||
const customLocalePath = path.resolve(config.user_locale, `${locale}.json`)
|
||||
|
||||
const customLocalePath = config.user_locale && path.resolve(config.user_locale, `${locale}.json`)
|
||||
if (config.user_locale && fs.existsSync(customLocalePath)) {
|
||||
try {
|
||||
customLocaleMessages = require(customLocalePath)
|
||||
|
|
|
@ -18,7 +18,7 @@ module.exports = {
|
|||
|
||||
const format = req.params.format || 'json'
|
||||
log.debug(`Events for place: ${placeName}`)
|
||||
const events = await eventController._select({ places: String(place.id), show_recurrent: true })
|
||||
const events = await eventController._select({ places: String(place.id), show_recurrent: true, reverse: true, start_datetime: 0 })
|
||||
|
||||
switch (format) {
|
||||
case 'rss':
|
||||
|
|
|
@ -88,7 +88,7 @@ const pluginController = {
|
|||
try {
|
||||
const plugin = require(pluginFile)
|
||||
const name = plugin.configuration.name
|
||||
console.log(`Found plugin '${name}'`)
|
||||
log.info(`Found plugin '${name}'`)
|
||||
pluginController.plugins.push(plugin)
|
||||
if (settingsController.settings['plugin_' + name]) {
|
||||
const pluginSetting = settingsController.settings['plugin_' + name]
|
||||
|
|
|
@ -6,7 +6,6 @@ const sharp = require('sharp')
|
|||
const config = require('../../config')
|
||||
const generateKeyPair = promisify(crypto.generateKeyPair)
|
||||
const log = require('../../log')
|
||||
// const locales = require('../../../locales/index')
|
||||
const escape = require('lodash/escape')
|
||||
const DB = require('../models/models')
|
||||
|
||||
|
@ -43,7 +42,8 @@ const defaultSettings = {
|
|||
enable_trusted_instances: true,
|
||||
trusted_instances: [],
|
||||
'theme.is_dark': true,
|
||||
'theme.primary': '#FF4500',
|
||||
dark_colors: { primary: '#FF6E40', error: '#FF5252', info: '#2196F3', success: '#4CAF50', warning: '#FB8C00' },
|
||||
light_colors: { primary: '#FF4500', error: '#FF5252', info: '#2196F3', success: '#4CAF50', warning: '#FB8C00' },
|
||||
trusted_instances_label: '',
|
||||
hide_thumbs: false,
|
||||
hide_calendar: false,
|
||||
|
@ -104,19 +104,6 @@ const settingsController = {
|
|||
await settingsController.set('privateKey', privateKey, true)
|
||||
}
|
||||
|
||||
// initialize user_locale
|
||||
// if (config.user_locale && fs.existsSync(path.resolve(config.user_locale))) {
|
||||
// const user_locales_files = fs.readdirSync(path.resolve(config.user_locale))
|
||||
// user_locales_files.forEach( f => {
|
||||
// const locale = path.basename(f ,'.json')
|
||||
// if (locales[locale]) {
|
||||
// log.info(`Adding custom locale ${locale}`)
|
||||
// settingsController.user_locale[locale] = require(path.resolve(config.user_locale, f)).default
|
||||
// } else {
|
||||
// log.warning(`Unknown custom user locale: ${locale} [valid locales are ${locales}]`)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
const pluginController = require('./plugins')
|
||||
pluginController._load()
|
||||
},
|
||||
|
|
|
@ -33,7 +33,7 @@ module.exports = {
|
|||
const eventController = require('./event')
|
||||
const format = req.params.format || 'json'
|
||||
const tags = req.params.tag
|
||||
const events = await eventController._select({ tags: tags.toLocaleLowerCase(), show_recurrent: true })
|
||||
const events = await eventController._select({ tags: tags.toLocaleLowerCase(), show_recurrent: true, start_datetime: 0, reverse: true })
|
||||
switch (format) {
|
||||
case 'rss':
|
||||
return exportController.feed(req, res, events,
|
||||
|
|
|
@ -7,11 +7,11 @@ const instanceApiRateLimiter = {
|
|||
|
||||
DDOSProtectionApiRateLimiter: (process.env.NODE_ENV === 'test' ? next : rateLimit({
|
||||
windowMs: 60 * 1000, // 1 minutes
|
||||
max: 150, // Limit each IP to 150 requests per `window`
|
||||
max: 250, // Limit each IP to 150 requests per `window`
|
||||
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
|
||||
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
||||
handler: (request, response, next, options) => {
|
||||
log.warn(`DDOS protection api rate limiter: > 150req/minute/ip ${request.ip}`)
|
||||
log.warn(`DDOS protection api rate limiter: > 250req/minute/ip ${request.ip}`)
|
||||
return response.status(options.statusCode).send(options.message)
|
||||
}
|
||||
})),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const Email = require('email-templates')
|
||||
const path = require('path')
|
||||
const moment = require('dayjs')
|
||||
const { DateTime } = require('luxon')
|
||||
const settingsController = require('./controller/settings')
|
||||
const log = require('../log')
|
||||
const { Task, TaskManager } = require('../taskManager')
|
||||
|
@ -52,6 +52,11 @@ const mail = {
|
|||
transport: settings.smtp || {}
|
||||
})
|
||||
|
||||
const opt = {
|
||||
zone: settings.instance_timezone,
|
||||
locale
|
||||
}
|
||||
|
||||
const msg = {
|
||||
template,
|
||||
message: {
|
||||
|
@ -61,7 +66,7 @@ const mail = {
|
|||
...locals,
|
||||
locale,
|
||||
config: { title: settings.title, baseurl: settings.baseurl, description: settings.description, admin_email: settings.admin_email },
|
||||
datetime: datetime => moment.unix(datetime).tz().locale(locale).format('ddd, D MMMM HH:mm')
|
||||
datetime: timestamp => DateTime.fromSeconds(timestamp, opt).toFormat('EEEE, d MMMM HH:mm')
|
||||
}
|
||||
}
|
||||
return email.send(msg)
|
||||
|
|
|
@ -1,13 +1,7 @@
|
|||
const config = require('../../config')
|
||||
const { htmlToText } = require('html-to-text')
|
||||
const dayjs = require('dayjs')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const { DateTime } = require('luxon')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
|
||||
// class Event extends Model {}
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
const Event = sequelize.define('event', {
|
||||
id: {
|
||||
|
@ -41,12 +35,18 @@ module.exports = (sequelize, DataTypes) => {
|
|||
online_locations: { type: DataTypes.JSON, defaultValue: [] }
|
||||
})
|
||||
|
||||
Event.prototype.toAP = function (username, locale, to = []) {
|
||||
Event.prototype.toAP = function (settings, to = []) {
|
||||
|
||||
const username = settings.instance_name
|
||||
const opt = {
|
||||
zone: settings.instance_timezone,
|
||||
locale: settings.instance_locale
|
||||
}
|
||||
const tags = this.tags && this.tags.map(t => t.tag.replace(/[ #]/g, '_'))
|
||||
const plainDescription = htmlToText(this.description && this.description.replace('\n', '').slice(0, 1000))
|
||||
const content = `
|
||||
📍 ${this.place && this.place.name}
|
||||
📅 ${dayjs.unix(this.start_datetime).tz().locale(locale).format('dddd, D MMMM (HH:mm)')}
|
||||
📅 ${DateTime.fromSeconds(this.start_datetime).toFormat('EEEE, d MMMM (HH:mm)')}
|
||||
|
||||
${plainDescription}
|
||||
`
|
||||
|
@ -69,8 +69,8 @@ module.exports = (sequelize, DataTypes) => {
|
|||
name: this.title,
|
||||
url: `${config.baseurl}/event/${this.slug || this.id}`,
|
||||
type: 'Event',
|
||||
startTime: dayjs.unix(this.start_datetime).tz().locale(locale).format(),
|
||||
...( this.end_datetime ? { endTime : dayjs.unix(this.end_datetime).tz().locale(locale).format() } : {} ),
|
||||
startTime: DateTime.fromSeconds(this.start_datetime, opt).toISO(),
|
||||
...( this.end_datetime ? { endTime : DateTime.fromSeconds(this.end_datetime, opt).toISO() } : {} ),
|
||||
location: {
|
||||
name: this.place.name,
|
||||
address: this.place.address,
|
||||
|
@ -83,7 +83,7 @@ module.exports = (sequelize, DataTypes) => {
|
|||
name: '#' + tag,
|
||||
href: `${config.baseurl}/tag/${tag}`
|
||||
})),
|
||||
published: dayjs(this.createdAt).utc().format(),
|
||||
published: this.createdAt,
|
||||
attributedTo: `${config.baseurl}/federation/u/${username}`,
|
||||
to: ['https://www.w3.org/ns/activitystreams#Public'],
|
||||
cc: [`${config.baseurl}/federation/u/${username}/followers`],
|
||||
|
|
|
@ -15,6 +15,17 @@ let config = {
|
|||
db: {},
|
||||
user_locale: path.resolve(process.env.cwd || '', 'user_locale'),
|
||||
upload_path: path.resolve(process.env.cwd || '', 'uploads'),
|
||||
proxy: {
|
||||
protocol: process.env.GANCIO_PROXY_PROTOCOL || '',
|
||||
hostname: process.env.GANCIO_PROXY_HOSTNAME || '',
|
||||
host: process.env.GANCIO_PROXY_HOST || '',
|
||||
port: process.env.GANCIO_PROXY_PORT || '',
|
||||
auth: {
|
||||
username: process.env.GANCIO_PROXY_USERNAME || '',
|
||||
password: process.env.GANCIO_PROXY_PASSWORD || ''
|
||||
},
|
||||
headers: process.env.GANCIO_PROXY_HEADERS && JSON.parse(process.env.GANCIO_PROXY_HEADERS) || {}
|
||||
},
|
||||
write (config_path= process.env.config_path || './config.json') {
|
||||
delete config.status
|
||||
return fs.writeFileSync(config_path, JSON.stringify(config, null, 2))
|
||||
|
|
|
@ -89,9 +89,7 @@ const Helpers = {
|
|||
to: ['https://www.w3.org/ns/activitystreams#Public'],
|
||||
cc: [...recipients[sharedInbox], `${config.baseurl}/federation/u/${settingsController.settings.instance_name}/followers`],
|
||||
actor: `${config.baseurl}/federation/u/${settingsController.settings.instance_name}`,
|
||||
object: event.toAP(settingsController.settings.instance_name,
|
||||
settingsController.settings.instance_locale,
|
||||
recipients[sharedInbox])
|
||||
object: event.toAP(settingsController.settings, recipients[sharedInbox])
|
||||
}
|
||||
body['@context'] = [
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
|
|
|
@ -34,7 +34,7 @@ router.get('/m/:event_id', async (req, res) => {
|
|||
|
||||
const event = await Event.findByPk(req.params.event_id, { include: [User, Tag, Place] })
|
||||
if (!event) { return res.status(404).send('Not found') }
|
||||
const eventAp = event.toAP(settingsController.settings.instance_name, settingsController.settings.instance_locale)
|
||||
const eventAp = event.toAP(settingsController.settings)
|
||||
eventAp['@context'] = [
|
||||
"https://www.w3.org/ns/activitystreams"
|
||||
]
|
||||
|
|
|
@ -3,10 +3,8 @@ const { Event, Place, APUser, Tag } = require('../api/models/models')
|
|||
const escape = require('lodash/escape')
|
||||
const config = require('../config')
|
||||
const log = require('../log')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const dayjs = require('dayjs')
|
||||
const settingsController = require('../api/controller/settings')
|
||||
dayjs.extend(utc)
|
||||
const { DateTime } = require('luxon')
|
||||
|
||||
module.exports = {
|
||||
get (req, res) {
|
||||
|
@ -132,9 +130,9 @@ module.exports = {
|
|||
type: 'Create',
|
||||
to: ['https://www.w3.org/ns/activitystreams#Public'],
|
||||
cc: [`${settings.baseurl}/federation/u/${name}/followers`],
|
||||
published: dayjs(e.createdAt).utc().format(),
|
||||
published: e.createdAt,
|
||||
actor: `${settings.baseurl}/federation/u/${name}`,
|
||||
object: e.toAP(name, settings.instance_locale)
|
||||
object: e.toAP(settings)
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
|
|
@ -87,7 +87,8 @@ module.exports = {
|
|||
trusted_instances: settings.trusted_instances,
|
||||
trusted_instances_label: settings.trusted_instances_label,
|
||||
'theme.is_dark': settings['theme.is_dark'],
|
||||
'theme.primary': settings['theme.primary'],
|
||||
dark_colors: settings.dark_colors,
|
||||
light_colors: settings.light_colors,
|
||||
hide_thumbs: settings.hide_thumbs,
|
||||
hide_calendar: settings.hide_calendar,
|
||||
allow_geolocation: settings.allow_geolocation,
|
||||
|
@ -101,9 +102,6 @@ module.exports = {
|
|||
allow_event_only_online: settings.allow_event_only_online,
|
||||
allow_event_also_online: settings.allow_event_also_online
|
||||
}
|
||||
// set user locale
|
||||
// res.locals.user_locale = settingsController.user_locale[res.locals.acceptedLocale]
|
||||
dayjs.tz.setDefault(res.locals.settings.instance_timezone)
|
||||
next()
|
||||
},
|
||||
|
||||
|
@ -199,9 +197,9 @@ module.exports = {
|
|||
* It does supports ICS and H-EVENT
|
||||
*/
|
||||
async importURL(req, res) {
|
||||
const URL = req.query.URL
|
||||
const url = req.query.URL
|
||||
try {
|
||||
const response = await axios.get(URL)
|
||||
const response = await axios.get(url)
|
||||
const contentType = response.headers['content-type']
|
||||
|
||||
if (contentType.includes('text/html')) {
|
||||
|
@ -213,7 +211,7 @@ module.exports = {
|
|||
const props = e.properties
|
||||
let media = get(props, 'featured[0]')
|
||||
if (media) {
|
||||
media = url.resolve(URL, media)
|
||||
media = URL.resolve(url, media)
|
||||
}
|
||||
return {
|
||||
title: get(props, 'name[0]', ''),
|
||||
|
@ -255,16 +253,17 @@ module.exports = {
|
|||
let cursor
|
||||
if (n === -1) {
|
||||
cursor = date.endOf('month')
|
||||
cursor = cursor.day(weekday)
|
||||
if (cursor.month() !== date.month()) {
|
||||
cursor = cursor.subtract(1, 'week')
|
||||
cursor = cursor.set({ weekday })
|
||||
if (cursor.month !== date.month) {
|
||||
cursor = cursor.minus({ days: 7 })
|
||||
}
|
||||
} else {
|
||||
cursor = date.startOf('month')
|
||||
cursor = cursor.add(cursor.day() <= date.day() ? n - 1 : n, 'week')
|
||||
cursor = cursor.day(weekday)
|
||||
cursor = cursor.plus({ days: cursor.weekday <= date.weekday ? (n-1) * 7 : n * 7})
|
||||
cursor = cursor.set({ weekday })
|
||||
}
|
||||
cursor = cursor.hour(date.hour()).minute(date.minute()).second(0)
|
||||
cursor = cursor.set({ hour: date.hour, minute: date.minute, second: 0 })
|
||||
log.debug(cursor)
|
||||
return cursor
|
||||
},
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
const config = require('../server/config')
|
||||
const db = require('./api/models/index')
|
||||
const log = require('../server/log')
|
||||
const { Settings } = require('luxon')
|
||||
|
||||
db.initialize()
|
||||
|
||||
const settingsController = require('./api/controller/settings')
|
||||
|
||||
|
||||
const initialize = {
|
||||
// close connections/port/unix socket
|
||||
async shutdown (exit = true) {
|
||||
|
@ -26,9 +26,6 @@ const initialize = {
|
|||
},
|
||||
|
||||
async start () {
|
||||
const dayjs = require('dayjs')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
dayjs.extend(timezone)
|
||||
if (config.status == 'CONFIGURED') {
|
||||
await db.sequelize.authenticate()
|
||||
log.debug('Running migrations')
|
||||
|
@ -57,10 +54,11 @@ const initialize = {
|
|||
await settingsController.load()
|
||||
}
|
||||
|
||||
dayjs.tz.setDefault(settingsController.settings.instance_timezone)
|
||||
Settings.defaultLocale = settingsController.settings.instance_locale
|
||||
Settings.defaultZone = settingsController.settings.instance_timezone
|
||||
|
||||
let TaskManager
|
||||
if (config.status === 'READY' && process.env.NODE_ENV == 'production') {
|
||||
if (config.status === 'READY' && process.env.NODE_ENV != 'test') {
|
||||
TaskManager = require('../server/taskManager').TaskManager
|
||||
TaskManager.start()
|
||||
}
|
||||
|
|
|
@ -7,8 +7,13 @@ const helpers = require('./helpers')
|
|||
const api = require('./api')
|
||||
|
||||
async function main () {
|
||||
const log = require('./log')
|
||||
|
||||
await initialize.start()
|
||||
try {
|
||||
await initialize.start()
|
||||
} catch (e) {
|
||||
log.error('[ERROR]' + e)
|
||||
}
|
||||
|
||||
app.use([
|
||||
helpers.initSettings,
|
||||
|
@ -19,7 +24,6 @@ async function main () {
|
|||
// const promBundle = require('express-prom-bundle')
|
||||
// const metricsMiddleware = promBundle({ includeMethod: true })
|
||||
|
||||
const log = require('./log')
|
||||
|
||||
app.enable('trust proxy')
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import dayjs from 'dayjs'
|
||||
|
||||
export const state = () => ({
|
||||
localSettings : {
|
||||
hide_thumbs: null,
|
||||
|
@ -43,10 +41,10 @@ export const state = () => ({
|
|||
|
||||
export const getters = {
|
||||
hide_thumbs (state) {
|
||||
return (state.localSettings['hide_thumbs'] === null) ? state.settings.hide_thumbs : state.localSettings.hide_thumbs
|
||||
return (![true, false].includes(state.localSettings['hide_thumbs'])) ? state.settings.hide_thumbs : state.localSettings.hide_thumbs
|
||||
},
|
||||
is_dark (state) {
|
||||
return (state.localSettings['theme.is_dark'] === null) ? state.settings['theme.is_dark'] : state.localSettings['theme.is_dark']
|
||||
return (![true, false].includes(state.localSettings['theme.is_dark'])) ? state.settings['theme.is_dark'] : state.localSettings['theme.is_dark']
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,12 +73,11 @@ export const actions = {
|
|||
// this method is called server side only for each request for nuxt
|
||||
// we use it to get configuration from db, set locale, etc...
|
||||
nuxtServerInit ({ commit }, { res, app }) {
|
||||
|
||||
if (res.locals && res.locals.settings) {
|
||||
commit('setSettings', res.locals.settings)
|
||||
commit('setFilter', { type: 'show_recurrent',
|
||||
value: res.locals.settings.allow_recurrent_event && res.locals.settings.recurrent_event_visible })
|
||||
}
|
||||
commit('setFilter', { type: 'show_recurrent',
|
||||
value: res.locals.settings.allow_recurrent_event && res.locals.settings.recurrent_event_visible })
|
||||
|
||||
commit('setLocalSetting', { key: 'hide_thumbs', value: app.$cookies.get('hide_thumbs') })
|
||||
commit('setLocalSetting', { key: 'theme.is_dark', value: app.$cookies.get('theme.is_dark') })
|
||||
|
@ -112,10 +109,12 @@ export const actions = {
|
|||
},
|
||||
async getEvents ({ commit, state }, params = {}) {
|
||||
const events = await this.$api.getEvents({
|
||||
start: params.start || dayjs().startOf('month').unix(),
|
||||
start: params.start || this.$time.startMonth(),
|
||||
end: params.end || null,
|
||||
show_recurrent: state.filter.show_recurrent,
|
||||
show_multidate: state.filter.show_multidate
|
||||
show_multidate: state.filter.show_multidate,
|
||||
...( params.query && { query: params.query }),
|
||||
...( params.older && { older: params.older })
|
||||
})
|
||||
commit('setEvents', events)
|
||||
}
|
||||
|
|
|
@ -419,11 +419,6 @@ describe('Collection', () => {
|
|||
|
||||
describe('Geocoding', () => {
|
||||
test('should not be enabled by default', async () => {
|
||||
await request(app)
|
||||
.post('/api/settings')
|
||||
.send({ key: 'allow_geolocation', value: false })
|
||||
.auth(token.access_token, { type: 'bearer' })
|
||||
.expect(200)
|
||||
|
||||
const response = await request(app).get('/api/placeOSM/Nominatim/test')
|
||||
.expect(403)
|
||||
|
|
184
tests/recurrent.test.js
Normal file
184
tests/recurrent.test.js
Normal file
|
@ -0,0 +1,184 @@
|
|||
const path = require('path')
|
||||
const { DateTime } = require('luxon')
|
||||
|
||||
beforeAll(async () => {
|
||||
|
||||
switch (process.env.DB) {
|
||||
case 'mariadb':
|
||||
process.env.config_path = path.resolve(__dirname, './seeds/config.mariadb.json')
|
||||
break
|
||||
case 'postgresql':
|
||||
process.env.config_path = path.resolve(__dirname, './seeds/config.postgres.json')
|
||||
break
|
||||
case 'sqlite':
|
||||
default:
|
||||
process.env.config_path = path.resolve(__dirname, './seeds/config.sqlite.json')
|
||||
}
|
||||
|
||||
try {
|
||||
await require('../server/initialize.server.js').start()
|
||||
const { sequelize } = require('../server/api/models/index')
|
||||
await sequelize.query('DELETE FROM settings')
|
||||
await sequelize.query('DELETE FROM events')
|
||||
await sequelize.query('DELETE FROM user_followers')
|
||||
await sequelize.query('DELETE FROM users')
|
||||
await sequelize.query('DELETE FROM ap_users')
|
||||
await sequelize.query('DELETE FROM tags')
|
||||
await sequelize.query('DELETE FROM places')
|
||||
await sequelize.query('DELETE FROM filters')
|
||||
await sequelize.query('DELETE FROM collections')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await require('../server/initialize.server.js').shutdown(false)
|
||||
})
|
||||
|
||||
describe('Recurrent events', () => {
|
||||
test('shoud create an occurrence in future when start date time is in past', async () => {
|
||||
const eventController = require('../server/api/controller/event')
|
||||
const { Event } = require('../server/api/models/models')
|
||||
|
||||
// each week starting from past
|
||||
let ret = await Event.create({
|
||||
title: 'each week starting from past',
|
||||
is_visible: true,
|
||||
recurrent: { frequency: '1w' },
|
||||
start_datetime: DateTime.local(2023, 3, 27, 8).toUnixInteger(),
|
||||
})
|
||||
|
||||
// 27 March 2023 08:00 -> 1w -> 3 April 2023 08:00
|
||||
let ev = await eventController._createRecurrentOccurrence(ret)
|
||||
expect(ev.start_datetime).toBe(DateTime.local(2023, 4, 3, 8).toUnixInteger())
|
||||
|
||||
// 3 April 2023 08:00 -> 1w -> 10 April 2023 08:00
|
||||
ev = await eventController._createRecurrentOccurrence(ret, DateTime.fromSeconds(ev.start_datetime+1), false)
|
||||
expect(ev.start_datetime).toBe(DateTime.local(2023, 4, 10, 8).toUnixInteger())
|
||||
|
||||
// weekly test
|
||||
// data di inizio prima di oggi
|
||||
// data di inizio dopo di oggi
|
||||
// test creazione evento successivo (quando quello prima e' skipped)
|
||||
|
||||
// stessa cosa per il bisettimanale
|
||||
|
||||
// stessa cosa per il mensile (primo, secondo, ultimo mercoldi' del mese)
|
||||
|
||||
// stessa cosa per il mensile ordinal (il 4 di ogni mese)
|
||||
|
||||
|
||||
})
|
||||
|
||||
test('shoud create an occurrence when start date time is in future', async () => {
|
||||
const eventController = require('../server/api/controller/event')
|
||||
const { Event } = require('../server/api/models/models')
|
||||
|
||||
// each week starting from future
|
||||
ret = await Event.create({
|
||||
title: 'each week starting from future',
|
||||
is_visible: true,
|
||||
recurrent: { frequency: '1w' },
|
||||
start_datetime: DateTime.local(2033, 3, 27, 8).toUnixInteger(),
|
||||
})
|
||||
|
||||
// 27 March 2033 08:00 -> 1w -> 27 March 2033 08:00
|
||||
ev = await eventController._createRecurrentOccurrence(ret)
|
||||
expect(ev.start_datetime).toBe(DateTime.local(2033, 3, 27, 8).toUnixInteger())
|
||||
|
||||
// 27 March 2033 08:00 -> 1w -> 3 April 2023 08:00
|
||||
ev = await eventController._createRecurrentOccurrence(ret,DateTime.fromSeconds(ev.start_datetime+1), false)
|
||||
expect(ev.start_datetime).toBe(DateTime.local(2033, 4, 3, 8).toUnixInteger())
|
||||
|
||||
})
|
||||
|
||||
test('shoud create a 2 week occurrence in future when start date time is in past', async () => {
|
||||
const eventController = require('../server/api/controller/event')
|
||||
const { Event } = require('../server/api/models/models')
|
||||
|
||||
// each week starting from past
|
||||
let ret = await Event.create({
|
||||
title: 'each 2 weeks starting from past',
|
||||
is_visible: true,
|
||||
recurrent: { frequency: '2w' },
|
||||
start_datetime: DateTime.local(2023, 3, 27, 8).toUnixInteger(),
|
||||
})
|
||||
|
||||
// 27 March 2023 08:00 -> 2w -> 10 April 2023 08:00
|
||||
let ev = await eventController._createRecurrentOccurrence(ret)
|
||||
expect(ev.start_datetime).toBe(DateTime.local(2023, 4, 10, 8).toUnixInteger())
|
||||
|
||||
// 10 April 2023 08:00 -> 2w -> 24 April 2023 08:00
|
||||
ev = await eventController._createRecurrentOccurrence(ret, DateTime.fromSeconds(ev.start_datetime+1), false)
|
||||
expect(ev.start_datetime).toBe(DateTime.local(2023, 4, 24, 8).toUnixInteger())
|
||||
})
|
||||
|
||||
test('shoud create a 2 week occurrence in future when start date time is in future', async () => {
|
||||
const eventController = require('../server/api/controller/event')
|
||||
const { Event } = require('../server/api/models/models')
|
||||
|
||||
// each week starting from past
|
||||
let ret = await Event.create({
|
||||
title: 'each 2 weeks starting from future',
|
||||
is_visible: true,
|
||||
recurrent: { frequency: '2w' },
|
||||
start_datetime: DateTime.local(2033, 3, 27, 8).toUnixInteger(),
|
||||
})
|
||||
|
||||
// 27 March 2023 08:00 -> 2w -> 10 April 2023 08:00
|
||||
let ev = await eventController._createRecurrentOccurrence(ret)
|
||||
expect(ev.start_datetime).toBe(DateTime.local(2033, 3, 27, 8).toUnixInteger())
|
||||
|
||||
// 27 March 2033 08:00 -> 2w -> 10 April 2033 08:00
|
||||
ev = await eventController._createRecurrentOccurrence(ret, DateTime.fromSeconds(ev.start_datetime+1), false)
|
||||
expect(ev.start_datetime).toBe(DateTime.local(2033, 4, 10, 8).toUnixInteger())
|
||||
})
|
||||
|
||||
|
||||
test('shoud create an occurrence each month in future when start date time is in past', async () => {
|
||||
const eventController = require('../server/api/controller/event')
|
||||
const { Event } = require('../server/api/models/models')
|
||||
|
||||
// each week starting from past
|
||||
let ret = await Event.create({
|
||||
title: 'each month starting from future',
|
||||
is_visible: true,
|
||||
recurrent: { frequency: '1m', type: 'ordinal' },
|
||||
start_datetime: DateTime.local(2033, 3, 27, 8).toUnixInteger(),
|
||||
})
|
||||
|
||||
// 27 March 2023 08:00 -> 2w -> 27 March 2023 08:00
|
||||
let ev = await eventController._createRecurrentOccurrence(ret)
|
||||
expect(ev.start_datetime).toBe(DateTime.local(2033, 3, 27, 8).toUnixInteger())
|
||||
|
||||
// 27 March 2033 08:00 -> 1m -> 27 April 2033 08:00
|
||||
ev = await eventController._createRecurrentOccurrence(ret, DateTime.fromSeconds(ev.start_datetime+1), false)
|
||||
expect(ev.start_datetime).toBe(DateTime.local(2033, 4, 27, 8).toUnixInteger())
|
||||
})
|
||||
|
||||
|
||||
test('shoud create an occurrence each last monday', async () => {
|
||||
const eventController = require('../server/api/controller/event')
|
||||
const { Event } = require('../server/api/models/models')
|
||||
|
||||
// each week starting from past
|
||||
let ret = await Event.create({
|
||||
title: 'each last monday starting from past',
|
||||
is_visible: true,
|
||||
recurrent: { frequency: '1m', type: -1 },
|
||||
start_datetime: DateTime.local(2023, 3, 27, 8).toUnixInteger(),
|
||||
})
|
||||
|
||||
// 27 March 2033 08:00 -> 1m -> 24 April 2033 08:00
|
||||
ev = await eventController._createRecurrentOccurrence(ret)
|
||||
expect(ev.start_datetime).toBe(DateTime.local(2023, 4, 24, 8).toUnixInteger())
|
||||
|
||||
|
||||
// 24 April 2033 08:00 -> 1m -> 29 May 2033 08:00
|
||||
ev = await eventController._createRecurrentOccurrence(ret, DateTime.fromSeconds(ev.start_datetime+1), false)
|
||||
expect(ev.start_datetime).toBe(DateTime.local(2023, 5, 29, 8).toUnixInteger())
|
||||
})
|
||||
|
||||
})
|
|
@ -9,15 +9,15 @@ rss(version='2.0' xmlns:atom="http://www.w3.org/2005/Atom")
|
|||
item
|
||||
if (event.media && event.media.length)
|
||||
<enclosure url="#{settings.baseurl}/media/#{event.media[0].url}" type='image/jpeg' length="#{event.media[0].size||1}"></enclosure>
|
||||
title [#{moment.unix(event.start_datetime).format("YY-MM-DD")}] #{event.title} @ #{event.place.name}
|
||||
title [#{unixFormat(event.start_datetime,"yy-MM-dd")}] #{event.title} @ #{event.place.name}
|
||||
link #{settings.baseurl}/event/#{event.slug || event.id}
|
||||
each tag in event.tags
|
||||
category #{tag.tag}
|
||||
category #{tag.tag}
|
||||
description
|
||||
| <![CDATA[
|
||||
| <h4>#{event.title}</h4>
|
||||
| <strong>#{event.place.name} - #{event.place.address}</strong>
|
||||
| <small>(#{moment.unix(event.start_datetime).format("dddd, D MMMM HH:mm")})</small><br/>
|
||||
| <small>(#{unixFormat(event.start_datetime,"EEEE, d MMMM HH:mm")})</small><br/>
|
||||
if (event.media && event.media.length)
|
||||
| <img alt="#{event.media[0].name || ''}" src="#{settings.baseurl}/media/#{event.media[0].url}"/>
|
||||
| !{event.description}
|
||||
|
|
25
vuetify.options.js
Normal file
25
vuetify.options.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
const minifyTheme = require('minify-css-string').default
|
||||
import { ca, de, en, es, eu, fr, gl, it, nb, nl, pl, pt, sk, ru, zhHans } from 'vuetify/es5/locale'
|
||||
|
||||
export default ({ res, nuxtState }) => {
|
||||
|
||||
const settings = process.server ? res.locals.settings : nuxtState.state.settings
|
||||
|
||||
return {
|
||||
lang: { locales: { ca, de, en, es, eu, fr, gl, it, nb, nl, pl, pt, sk, ru, zhHans } },
|
||||
treeShake: true,
|
||||
theme: {
|
||||
options: {
|
||||
customProperties: false,
|
||||
variations: false,
|
||||
minifyTheme,
|
||||
},
|
||||
dark: settings['theme.is_dark'],
|
||||
themes: {
|
||||
dark: settings.dark_colors,
|
||||
light: settings.light_colors
|
||||
}
|
||||
},
|
||||
defaultAssets: false
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
Plugin Name: WPGancio
|
||||
Plugin URI: https://gancio.org
|
||||
Description: Connects an user of a gancio instance to a Wordpress user so that published events are automatically pushed with Gancio API.
|
||||
Version: 1.5
|
||||
Version: 1.7
|
||||
Author: Gancio
|
||||
License: AGPL 3.0
|
||||
|
||||
|
@ -34,10 +34,4 @@ require_once(WPGANCIO_DIR . 'oauth.php');
|
|||
* on Wordpress are automatically inserted.
|
||||
* It requires an event manager plugin, only Event Organiser (https://wp-event-organiser.com/) is
|
||||
* supported until now but to add another plugin it's easy.
|
||||
*/
|
||||
|
||||
/**
|
||||
* - Add Gancio Settings page to select an instance URL and...
|
||||
* - start an OAuth 2.0 authentication flow with the selected instance
|
||||
* - Send each new / updated events to the selected instance via Gancio API
|
||||
*/
|
||||
*/
|
|
@ -35,9 +35,10 @@ function wpgancio_save_event ($post_id) {
|
|||
return sanitize_title($tag->name);
|
||||
}
|
||||
|
||||
// TODO: merge event tags with post tags
|
||||
$tmp_tags = get_the_terms( $event, 'event-tag' );
|
||||
$tags = array_map('tagName', $tmp_tags);
|
||||
if ($tmp_tags) {
|
||||
$tags = array_map('tagName', $tmp_tags);
|
||||
}
|
||||
|
||||
|
||||
$gancio_id = get_post_meta($post_id, 'wpgancio_gancio_id', TRUE);
|
||||
|
@ -55,9 +56,8 @@ function wpgancio_save_event ($post_id) {
|
|||
'tags' => $tags,
|
||||
'description' => $event->post_content,
|
||||
'start_datetime' => intval($start_datetime),
|
||||
'end_datetime' => intval($end_datetime),
|
||||
'place_name' => $place_name,
|
||||
'place_address' => "${place_address['address']}, ${place_address['city']}"
|
||||
'place_address' => $place_address['address'] . ", " . $place_address['city']
|
||||
);
|
||||
|
||||
// add image if specified
|
||||
|
|
|
@ -4,7 +4,7 @@ Donate link: https://gancio.org
|
|||
Tags: events, gancio, fediverse, AP, activity pub
|
||||
Requires at least: 4.7
|
||||
Tested up to: 6.0
|
||||
Stable tag: 1.6
|
||||
Stable tag: 1.7
|
||||
Requires PHP: 7.0
|
||||
License: AGPLv3 or later
|
||||
License URI: https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
@ -18,6 +18,9 @@ for this to work an event manager plugin is required, only [Event Organiser](htt
|
|||
|
||||
|
||||
== Changelog ==
|
||||
= 1.7 =
|
||||
* Fix merge event tags while posting to an instance
|
||||
|
||||
= 1.6 =
|
||||
* Support MU installation
|
||||
|
||||
|
|
Loading…
Reference in a new issue