Compare commits

...

425 Commits

Author SHA1 Message Date
Tykayn 8b9b1f7acc Merge branch 'chapril' of https://forge.april.org/Chapril/mobilizon.chapril.org-mobilizon into chapril 2021-12-09 16:02:54 +01:00
Tykayn fa372387aa Please read the [UPGRADE.md](https://framagit.org/framasoft/mobilizon/-/blob/main/UPGRADE.md#upgrading-from-13-to-20) file as well.
- Added possibility to follow groups and be notified from new upcoming events
 - Export list of participants to CSV, `PDF` and `ODS`
 - Allow to set timezone for an event. The timezone is automatically defined from the address if one is defined. If the event timezone is different than the user's current one, a toggle is shown to switch between the two.
 - Added initial support for Right To Left languages (such as arabic) and [BiDi](https://en.wikipedia.org/wiki/Bidirectional_text)
 - Group followers and members get an notification email by default when a group publishes a new event (subject to activity notification settings)
 - Group admins can now approve or deny new memberships
 - Build releases in `arm` and `arm64` format in addition to `amd64`
 - Build Docker images in `arm` and `arm64` format in addition to `amd64`
 - Added possibility to indicate the event is fully online
 - Added possibility to search only for online events
 - Added possibility to search only in past events
 - Detect event, comments and posts languages automatically. Allows setting language
 - Allow to change an user's password through the users.modify mix task
 - Add instance setting so that only the admin can create groups
 - Add instance setting so that only groups can create events
 - Added JSON-LD metadata about the event in emails
 - Added a quick link to email notification settings at the bottom of emails
 - Allow to access Mobilizon with a specific language directly by using `https://instance.tld/lang` where `lang` is a language supported by Mobilizon
 - Added organizer actor name (profile or group) in the icalendar export
 - Add initial support for federation with Gancio
 
 - Multiple UI improvements, including post, event and participation cards, discussions and emails. The « My Events » page was also redesigned to allow showing events from your groups.
 - Various accessibility improvements
 - Event update notification is send to participants ~30 minutes after the event update, so that successive edits are throttled.
 - Event, post and comments titles and content now have expose their detected language in HTML, for improved screen reader experience
 - Delete current actor ID as well from local storage when unlogging
 - Show a default text for instance contact in default terms text when no instance contact is set
 - Only show locatecontrol button in leaflet map when we can do geolocation
 - Disable push column in notification settings when push is not available
 - Show actual language instead of language code in Users admin view
 - Empty old & new passwords fields when successful password change
 - Don't link to the group page from admin when actor is suspended
 - Warn participants when the event organizer is suspended (and therefore the event cancelled)
 - Improve metadata on public page
 - Make sure some event action pages (participate remotely or without an account) don't get indexed by search engines
 - Only send `Tombstone` element in `Delete` activities, not the whole previous deleted element.
 - Make sure `Delete` activity are send correctly to everyone
 - Only add address and tags to event icalendar export if they exist
 - `master` branch has been renamed to `main`
 - Mention following groups on the registration page
 - Add missing group name to activity notifications
 - Warn while registering and logging when the email contains uppercase characters
 - Improve json-ld metadata on event live streams
 - Add "eventAttendanceMode" to JSON-ld schema.org event representation
 - Improve sending pending participation notifications
 - Add "formerType" and "delete" attributes on Tombstones ActivityPub objects representation
 - Improve MyEvents page description text
 
 - Support for Elixir < 1.12 and OTP < 22
 
 - Fix tags autocomplete
 - Fix config onboarding after LDAP initial connexion
 - Fix events pagination on tags page
 - Fixed deduplicated files from orphan media being deleted as well
 - Fix deleting own account
 - Fix search returning user profiles instead of only groups
 - Fix federating geo coordinates
 - Fix an issue with group activity items when moving resources
 - Fix an issue with Identity Picker
 - Fix an issue with TagInput
 - Fix an issue when leaving a group
 - Fix admin settings edition
 - Fix an issue when showing public page of suspended group
 - Removed non existing page (`/about/mobilizon`) from sitemap
 - Fix action logs containing group suspension events
 - Fixed group physical address not exposed to ActivityPub
 - Release front-end files are no longer in duplicate
 - Only show datetime timezone toggle on event if the timezone offset is different from our own
 - Fix error when determining audience for Discussion when deleting a comment
 - Fix a couple of accessibility issues
 - Limit to acceptable tags when pasting raw HTML into comment fields on front-end
 - Fixed group map display
 - Fixed updating group physical address
 - Allow group members to access group drafts
 - Improve group refreshment workflow
 - Fixed date signature generation for federation
 - Fixed an issue when duplicating a group event from another profile
 - Fixed event metadata not saved on eventcreation
 - Use a different pagination parameter for searched events and featured events on search page
 - Fixed creating group activities when creating events with some fields
 - Move release package at correct path for CI upload
 - Fixed event contacts that were not exposed and fetched over federation
 - Don't sign fetch when fetching actor for a given signature
 - Some various HTTP signatures issues
 - Fixed actor AP representation of avatar
 - Handle errors when fetching actor pictures
 - Fixed sending group events to followers on Mastodon
 - Fixed actors avatars and banners being deleted if the same file was also an orphan media
 - Fix spacing in organizer picker
 - Increase number of close events and follow group events
 - Fix accessing user profile in admin section
 - Set initial values for some EventMetadata elements, fixing submitting them right away with no value
 - Avoid giving an error page if the apollo futureParticipations query is undefined
 - Fixed path to exports in production
 - Fixed padding below truncated title of event cards
 - Fixed exports that weren't enabled in Docker
 - Fixed error page when event end date is null
 - Fixed event language not being allowed to be null
 
 - Fixed private messages sent as event replies from Mastodon that were shown publically as public comments. They are now discarded.
 
 - Czech
 - Gaelic
 - German
 - Hungarian
 - Indonesian
 - Norwegian Nynorsk
 - Occitan
 - Persian
 - Portuguese (Brazil)
 - Russian
 - Slovenian
 - Spanish
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEExMITpfxOHHCvHn8FoGG53eDKB3MFAmGcqEMACgkQoGG53eDK
 B3NlaA/8DoAi7WtC+//xJn8tRSP04PuTRDi2+9/D9a6gjTsiPXDZdb7LtEHuCg1H
 PXsHNQOTafUM/T7b7XTGmKOST3Sz2kw4eKJArCyF+NuvCwiO/Iw/v1wev2Mo8vvC
 eDBbMkfiCnMHcytqheeM9gvGuRyIOfgQ9uPk54snfa9a+SGELR5XDRKhwBlGAs6i
 nkUPbOB72oCou79HZ6CjyLTG6CoWUVsheuvAEhYw52e5JlWSJb9yOUdnOYUV1sr6
 OzLct996Z3IOQX4ToaQ+Re99tOaEyqO98aHsv+Wbz128sku0WrfseKn9zi3PL6cF
 LYjtZ9+0dwdNi3MfgKoEoWJaMlN3+6WUw/blcVP+6b6Ibn5YV/HkVacke/rGoAry
 oiEjP4HFKnvT83dTBn+LRcU6MY3MrZsarjUACjcKIwpTiylw9gaqA0i7dPBdW35p
 Q4c1gIh1Q/aE5OKCxXGLrg6s1SNZ754cAyEVo85UnF8Iu4wiaY++ImvyG8xIoOWf
 vQuya3LcDT8Gj9KY/LWMrVT8LJ2ij5t8oRMFLIHLlfiWbq05m6QaBIWr5sLjLO/N
 w2N1//ZHNM3sJNl0bZgc9g4lhcdj52VhOcHLot4fdcg5RTektCK0ky5uTD4WJ3GP
 keihAR8ZtFLaiAQ+XN6ng3IhrgQcpIdJBLlHzgFBx2rTBy8a0Tw=
 =jyrf
 -----END PGP SIGNATURE-----

Merge tag '2.0.0' into chapril

Please read the [UPGRADE.md](https://framagit.org/framasoft/mobilizon/-/blob/main/UPGRADE.md#upgrading-from-13-to-20) file as well.

- Added possibility to follow groups and be notified from new upcoming events
- Export list of participants to CSV, `PDF` and `ODS`
- Allow to set timezone for an event. The timezone is automatically defined from the address if one is defined. If the event timezone is different than the user's current one, a toggle is shown to switch between the two.
- Added initial support for Right To Left languages (such as arabic) and [BiDi](https://en.wikipedia.org/wiki/Bidirectional_text)
- Group followers and members get an notification email by default when a group publishes a new event (subject to activity notification settings)
- Group admins can now approve or deny new memberships
- Build releases in `arm` and `arm64` format in addition to `amd64`
- Build Docker images in `arm` and `arm64` format in addition to `amd64`
- Added possibility to indicate the event is fully online
- Added possibility to search only for online events
- Added possibility to search only in past events
- Detect event, comments and posts languages automatically. Allows setting language
- Allow to change an user's password through the users.modify mix task
- Add instance setting so that only the admin can create groups
- Add instance setting so that only groups can create events
- Added JSON-LD metadata about the event in emails
- Added a quick link to email notification settings at the bottom of emails
- Allow to access Mobilizon with a specific language directly by using `https://instance.tld/lang` where `lang` is a language supported by Mobilizon
- Added organizer actor name (profile or group) in the icalendar export
- Add initial support for federation with Gancio

- Multiple UI improvements, including post, event and participation cards, discussions and emails. The « My Events » page was also redesigned to allow showing events from your groups.
- Various accessibility improvements
- Event update notification is send to participants ~30 minutes after the event update, so that successive edits are throttled.
- Event, post and comments titles and content now have expose their detected language in HTML, for improved screen reader experience
- Delete current actor ID as well from local storage when unlogging
- Show a default text for instance contact in default terms text when no instance contact is set
- Only show locatecontrol button in leaflet map when we can do geolocation
- Disable push column in notification settings when push is not available
- Show actual language instead of language code in Users admin view
- Empty old & new passwords fields when successful password change
- Don't link to the group page from admin when actor is suspended
- Warn participants when the event organizer is suspended (and therefore the event cancelled)
- Improve metadata on public page
- Make sure some event action pages (participate remotely or without an account) don't get indexed by search engines
- Only send `Tombstone` element in `Delete` activities, not the whole previous deleted element.
- Make sure `Delete` activity are send correctly to everyone
- Only add address and tags to event icalendar export if they exist
- `master` branch has been renamed to `main`
- Mention following groups on the registration page
- Add missing group name to activity notifications
- Warn while registering and logging when the email contains uppercase characters
- Improve json-ld metadata on event live streams
- Add "eventAttendanceMode" to JSON-ld schema.org event representation
- Improve sending pending participation notifications
- Add "formerType" and "delete" attributes on Tombstones ActivityPub objects representation
- Improve MyEvents page description text

- Support for Elixir < 1.12 and OTP < 22

- Fix tags autocomplete
- Fix config onboarding after LDAP initial connexion
- Fix events pagination on tags page
- Fixed deduplicated files from orphan media being deleted as well
- Fix deleting own account
- Fix search returning user profiles instead of only groups
- Fix federating geo coordinates
- Fix an issue with group activity items when moving resources
- Fix an issue with Identity Picker
- Fix an issue with TagInput
- Fix an issue when leaving a group
- Fix admin settings edition
- Fix an issue when showing public page of suspended group
- Removed non existing page (`/about/mobilizon`) from sitemap
- Fix action logs containing group suspension events
- Fixed group physical address not exposed to ActivityPub
- Release front-end files are no longer in duplicate
- Only show datetime timezone toggle on event if the timezone offset is different from our own
- Fix error when determining audience for Discussion when deleting a comment
- Fix a couple of accessibility issues
- Limit to acceptable tags when pasting raw HTML into comment fields on front-end
- Fixed group map display
- Fixed updating group physical address
- Allow group members to access group drafts
- Improve group refreshment workflow
- Fixed date signature generation for federation
- Fixed an issue when duplicating a group event from another profile
- Fixed event metadata not saved on eventcreation
- Use a different pagination parameter for searched events and featured events on search page
- Fixed creating group activities when creating events with some fields
- Move release package at correct path for CI upload
- Fixed event contacts that were not exposed and fetched over federation
- Don't sign fetch when fetching actor for a given signature
- Some various HTTP signatures issues
- Fixed actor AP representation of avatar
- Handle errors when fetching actor pictures
- Fixed sending group events to followers on Mastodon
- Fixed actors avatars and banners being deleted if the same file was also an orphan media
- Fix spacing in organizer picker
- Increase number of close events and follow group events
- Fix accessing user profile in admin section
- Set initial values for some EventMetadata elements, fixing submitting them right away with no value
- Avoid giving an error page if the apollo futureParticipations query is undefined
- Fixed path to exports in production
- Fixed padding below truncated title of event cards
- Fixed exports that weren't enabled in Docker
- Fixed error page when event end date is null
- Fixed event language not being allowed to be null

- Fixed private messages sent as event replies from Mastodon that were shown publically as public comments. They are now discarded.

- Czech
- Gaelic
- German
- Hungarian
- Indonesian
- Norwegian Nynorsk
- Occitan
- Persian
- Portuguese (Brazil)
- Russian
- Slovenian
- Spanish
2021-12-09 15:51:26 +01:00
Thomas Citharel 147ea64483
Fix event language not allowed to be null
If the event language wasn't previously defined, it's not "und"

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-23 09:36:19 +01:00
Thomas Citharel 2b399ac1df
Bump version to 2.0.0
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-23 09:19:18 +01:00
Thomas Citharel c89ce87541
Add CHANGELOG entry for 2.0.0
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-23 09:19:18 +01:00
Thomas Citharel 921f4b577d Merge branch 'weblate-mobilizon-frontend' into 'main'
Translations update from Weblate

See merge request framasoft/mobilizon!1121
2021-11-23 07:55:55 +00:00
Jiri Podhorecky b3353dd4dc Translated using Weblate (Czech)
Currently translated at 1.3% (1 of 76 strings)

Translation: Mobilizon/Activity
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/activity/cs/
2021-11-23 08:54:39 +01:00
lu pa d66787aa66 Translated using Weblate (Spanish)
Currently translated at 100.0% (1255 of 1255 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/es/
2021-11-23 08:54:39 +01:00
Thomas Citharel 2d6fa93906 Merge branch 'bugs' into 'main'
Mobilizon 2.0.0-rc.3

Closes #936

See merge request framasoft/mobilizon!1120
2021-11-22 19:28:08 +00:00
Thomas Citharel 20eea58ac9
Bump version to 2.0.0-rc.3
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-22 20:01:41 +01:00
Thomas Citharel 0ec4218636
Update CHANGELOG.md for 2.0.0-rc.3
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-22 20:01:41 +01:00
Thomas Citharel d4dfba48d8
Fix error page when ends_on date is null
Closes #936

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-22 20:00:57 +01:00
Thomas Citharel a54b879c2d
Enable exports on Docker
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-22 19:38:53 +01:00
Thomas Citharel 233cf1026a
Improve the UPGRADE.md file
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-22 19:38:36 +01:00
Thomas Citharel 377b83e02d
Fix export being outputted in the wrong directory in release mode
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-22 18:43:59 +01:00
Thomas Citharel ed7e6e4d4b
Fix extra padding on event truncated title
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-22 18:31:13 +01:00
Thomas Citharel ab724b0893
Bump version to 2.0.0-rc.2
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-22 11:09:20 +01:00
Thomas Citharel 4f769926d7
Add CHANGELOG.md entry for 2.0.0-rc.2
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-22 11:08:52 +01:00
Thomas Citharel c6812dd672 Merge branch 'weblate-mobilizon-frontend' into 'main'
Translations update from Weblate

See merge request framasoft/mobilizon!1119
2021-11-22 09:38:41 +00:00
Balázs Úr 0af36e19f8 Translated using Weblate (Hungarian)
Currently translated at 100.0% (76 of 76 strings)

Translation: Mobilizon/Activity
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/activity/hu/
2021-11-22 10:37:36 +01:00
Balázs Úr 2dbc92b0cd Translated using Weblate (Hungarian)
Currently translated at 100.0% (204 of 204 strings)

Translation: Mobilizon/Backend errors
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/backend-errors/hu/
2021-11-22 10:37:36 +01:00
Thomas Frenzel 82af0f10b0 Translated using Weblate (German)
Currently translated at 83.5% (1048 of 1255 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/de/
2021-11-22 10:37:36 +01:00
Thomas Citharel f5a75c0af3 Merge branch 'bugs' into 'main'
Fix various issues in 2.0.0-rc.1

Closes #929, #930, #931 et #932

See merge request framasoft/mobilizon!1118
2021-11-22 09:37:20 +00:00
Thomas Citharel 506a6dd4c7
Avoid giving an error page if the apollo futureParticipations query is
undefined

Closes #932

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-22 10:08:12 +01:00
Thomas Citharel 83783ad34b
Remove type validation in AddressAutoComplete mixin
Because it can be null

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-22 10:04:12 +01:00
Thomas Citharel 49ad1637f9
Set initial values for some EventMetadata elements
Otherwise it fails backend validation

Closes #931

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-22 10:04:00 +01:00
Thomas Citharel 6167d8c416
Improve MyEvents page description text
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-22 09:40:34 +01:00
Thomas Citharel c4d9101302
Make second parameter to Mobilizon.FollowedGroupActivity.user_followed_group_events/4 default to nil
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-22 09:40:34 +01:00
Thomas Citharel 3fd79f89a8
Fix accessing user profile in admin section
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-22 09:40:33 +01:00
Thomas Citharel 1d1574b426
Increate number of close events and follow group events
Closes #930

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-22 09:40:22 +01:00
Thomas Citharel 83e8136b0e
Fix spacing in organizer picker
Closes #929

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-22 08:36:48 +01:00
Thomas Citharel 0307cbb201 Merge branch '2.0.0-rc.1' into 'main'
Release 2.0.0-rc.1

See merge request framasoft/mobilizon!1116
2021-11-21 11:48:24 +00:00
Thomas Citharel 785bc539d6
Release 2.0.0-rc.1
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-21 12:20:20 +01:00
Thomas Citharel 1ee0dd5ca8
Changelog for 2.0.0-rc.1
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-21 12:20:19 +01:00
Thomas Citharel e64f89142e Merge branch 'email-fixes' into 'main'
Emails small UI fixes

Closes #917

See merge request framasoft/mobilizon!1117
2021-11-21 10:50:55 +00:00
Thomas Citharel 0348826994
Emails small UI fixes
Closes #917

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-21 11:16:13 +01:00
Thomas Citharel 93acdeda20 Merge branch 'bugs' into 'main'
Orphan media fixes

See merge request framasoft/mobilizon!1115
2021-11-20 18:36:55 +00:00
Thomas Citharel 07a11d792c
Fix updating a group's avatar and banner picture
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-20 19:04:54 +01:00
Thomas Citharel e8da59f4a5
Cleanup clean_orphan_test.exs
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-20 18:36:26 +01:00
Thomas Citharel 2154457be3
Take profile files into account when deleting orphan media
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-20 18:30:51 +01:00
Thomas Citharel 2b99b48258
Set database timeout to infinity when trying to detect orphan media
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-20 18:30:18 +01:00
Thomas Citharel 07d7f908c9 Merge branch 'bugs' into 'main'
Federation fixes

See merge request framasoft/mobilizon!1114
2021-11-20 17:22:29 +00:00
Thomas Citharel 08f35169d1
Fix sending group events to followers on Mastodon
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-19 19:25:49 +01:00
Thomas Citharel d7fd30f8e6
Federation fixes
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-19 19:25:49 +01:00
Thomas Citharel fbf2d480a3 Merge branch 'weblate-mobilizon-frontend' into 'main'
Translations update from Weblate

See merge request framasoft/mobilizon!1113
2021-11-19 09:48:17 +00:00
GunChleoc 2b99267f2d Translated using Weblate (Gaelic)
Currently translated at 100.0% (76 of 76 strings)

Translation: Mobilizon/Activity
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/activity/gd/
2021-11-19 10:22:07 +01:00
Berto Te eaae72d678 Translated using Weblate (Spanish)
Currently translated at 100.0% (76 of 76 strings)

Translation: Mobilizon/Activity
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/activity/es/
2021-11-19 10:22:06 +01:00
GunChleoc 60484ef426 Translated using Weblate (Gaelic)
Currently translated at 99.8% (1253 of 1255 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/gd/
2021-11-19 10:22:06 +01:00
Berto Te 6f637afdbd Translated using Weblate (Spanish)
Currently translated at 100.0% (204 of 204 strings)

Translation: Mobilizon/Backend errors
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/backend-errors/es/
2021-11-19 10:22:05 +01:00
Thomas Citharel 76cefca4d4
Forgot activity notification change
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-17 17:17:42 +01:00
Thomas Citharel ebcf2bc387
Merge remote-tracking branch 'weblate/main' 2021-11-17 17:13:42 +01:00
Berto Te 535feb29bf Translated using Weblate (Spanish)
Currently translated at 100.0% (296 of 296 strings)

Translation: Mobilizon/Backend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/backend/es/
2021-11-17 16:53:56 +01:00
Berto Te 07a6f1c584 Translated using Weblate (Spanish)
Currently translated at 100.0% (1255 of 1255 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/es/
2021-11-17 16:53:56 +01:00
Thomas Citharel 14fe16dc06 Merge branch 'bugs' into 'main'
Fix some HTTP signatures issues

Closes #799

See merge request framasoft/mobilizon!1112
2021-11-17 15:53:42 +00:00
Thomas Citharel ba7f03610c
Add missing group name to activity notifications
Closes #799

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-17 16:25:21 +01:00
Thomas Citharel cc9c2c878c
Fix some HTTP signatures issues
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-17 16:01:39 +01:00
Thomas Citharel 5489c54f10 Merge branch 'bugs' into 'main'
Various bugs

Closes #803, #884 et #829

See merge request framasoft/mobilizon!1111
2021-11-16 16:13:16 +00:00
Thomas Citharel 0cae2bb84d
Fix return from EventDelayedNotificationWorker being nil
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-16 16:45:38 +01:00
Thomas Citharel d1f0a9224a
Cleanup some dead code
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-16 16:45:38 +01:00
Thomas Citharel 702c7fa6d4
Rename is_local to is_local?
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-16 16:45:38 +01:00
Thomas Citharel 5b85d96e06
Handle errors when fetching actor pictures
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-16 16:45:38 +01:00
Thomas Citharel f35db6540b
Various HTTP signature code improvements
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-16 16:45:38 +01:00
Thomas Citharel 6f6d617eba
Fix actor AP representation of avatar
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-16 15:47:14 +01:00
Thomas Citharel 84bd1ccfad
Don't sign fetch when fetching actor for a given signature
Otherwise it's doing a loop

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-16 15:46:23 +01:00
Thomas Citharel 88067bd217
Expose and fetch event contacts
Close #829

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-16 15:45:05 +01:00
Thomas Citharel d7ef8f3280
Adding some debug logs
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-16 15:43:53 +01:00
Thomas Citharel 446c5f00ab
Add "formerType" and "delete" attributes on Tombstones objects
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-16 11:56:15 +01:00
Thomas Citharel 2032586352
Show the warning about casing on the login screen as well
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-16 11:44:13 +01:00
Thomas Citharel 19794bde23
Mention following groups on the registration page
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-16 11:40:22 +01:00
Thomas Citharel d291a83cc9
Warn when registering with email containing uppercase characters
Closes #884 and #803

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-16 11:38:17 +01:00
Thomas Citharel be1664ec85
Improve sending pending participation notifications
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-15 16:59:40 +01:00
Thomas Citharel 200adf272c
Move release package at correct path for CI upload
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-15 13:37:00 +01:00
Thomas Citharel 85ceb1de47
Improve json-ld metadata on event live streams
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-15 12:11:29 +01:00
Thomas Citharel bcf17fe30b
Add "eventAttendanceMode" to JSON-ld schema.org event representation
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-15 11:08:18 +01:00
Thomas Citharel fea97f3713
Fix creating an mz activity when updating an event with some subfields
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-15 11:03:06 +01:00
Thomas Citharel d31bdc99f8 Merge branch 'weblate-mobilizon-frontend' into 'main'
Translations update from Weblate

See merge request framasoft/mobilizon!1110
2021-11-15 08:27:33 +00:00
Berto Te 425d5f61fc Translated using Weblate (Spanish)
Currently translated at 100.0% (54 of 54 strings)

Translation: Mobilizon/Activity
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/activity/es/
2021-11-15 09:26:08 +01:00
Berto Te 060f644e09 Translated using Weblate (Spanish)
Currently translated at 100.0% (203 of 203 strings)

Translation: Mobilizon/Backend errors
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/backend-errors/es/
2021-11-15 09:26:08 +01:00
Berto Te ba0af5d0af Translated using Weblate (Spanish)
Currently translated at 100.0% (296 of 296 strings)

Translation: Mobilizon/Backend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/backend/es/
2021-11-15 09:26:08 +01:00
Berto Te 9c8b01c2a5 Translated using Weblate (Spanish)
Currently translated at 100.0% (1253 of 1253 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/es/
2021-11-15 09:26:08 +01:00
Thomas Citharel 94652bbf44
Version 2.0.0-beta.2
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-15 09:25:45 +01:00
Thomas Citharel 42d6b8a9ab
Upgrade deps
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-15 09:24:55 +01:00
Thomas Citharel 56bb28373b Merge branch 'bugs' into 'main'
Multiple bugs fixed

Closes #836, #843, #910, #890 et #894

See merge request framasoft/mobilizon!1109
2021-11-15 08:17:21 +00:00
Thomas Citharel e5e95b81b4
Update changelog for 2.0.0-beta.2
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-15 08:48:29 +01:00
Thomas Citharel b547f1f1ee
Handle Webfinger endpoint not returning 200
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-15 08:48:29 +01:00
Thomas Citharel 3f9e1c8e19
Remove exvcr dependency
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-15 08:48:28 +01:00
Thomas Citharel df2c184bc0
Refactor transmogrifier Delete to avoid spoofed Delete being accepted
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-15 08:48:28 +01:00
Thomas Citharel 2e869c1ade
Improve the UPGRADE.md file
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-14 16:28:28 +01:00
Thomas Citharel f3b97e44e5
Use different pagination for featured events and searched events
Closes #894

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-14 16:28:28 +01:00
Thomas Citharel 5fa9eb68b1
Fixed event metadata not saved on event creation
Closes #890

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-14 16:28:28 +01:00
Thomas Citharel 819f2af518
Fix duplicating group event not using the current actor as event
organizer

Closes #910

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-14 16:28:27 +01:00
Thomas Citharel 346cd6cfac
Fix some french translation bindings
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-14 16:28:27 +01:00
Thomas Citharel 55af776df9
Improve group refreshment and fixed date signature generation
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-14 16:28:27 +01:00
Thomas Citharel 6d599441a9
Allow group members to access group drafts
Closes #843

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-13 19:57:14 +01:00
Thomas Citharel 6a63ece91f
Fix updating group physical address
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-13 19:57:14 +01:00
Thomas Citharel 63e7807f0d
Fix group map display
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-13 19:57:13 +01:00
Thomas Citharel 0a37719029
Expose content language in HTML
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-13 19:57:13 +01:00
Thomas Citharel cf7744ab51
Add sanitize-html on front-end when pasting into a comment field
So that only allowed tags are inputted into a comment, when copying from
elsewhere

Closes #836

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-13 19:57:12 +01:00
Thomas Citharel 3d2fea6bb8 Merge branch 'bugs' into 'main'
Various bugs

Closes #873 et #862

See merge request framasoft/mobilizon!1108
2021-11-13 15:12:25 +00:00
Thomas Citharel 0e98010906
Update translations
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-13 14:38:59 +01:00
Thomas Citharel 3dc8663c3e
Improve report modal accessibility
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-13 14:38:59 +01:00
Thomas Citharel 4a8284fa6d
Improve comment tree
Closes #862

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-13 14:38:59 +01:00
Thomas Citharel b1a9c28a97
Add closeButtonAriaLabel to everymodal
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-13 12:33:14 +01:00
Thomas Citharel e6b186026d
Fix a couple of Apollo cache issues
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-13 11:47:56 +01:00
Thomas Citharel 18cd7c11f1
Fix error when determining audience for Discussion
Closes #873

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-13 11:46:25 +01:00
Thomas Citharel b4bfa0856f
Merge remote-tracking branch 'weblate/main' 2021-11-13 10:50:44 +01:00
Berto Te c284c78034 Translated using Weblate (Spanish)
Currently translated at 100.0% (200 of 200 strings)

Translation: Mobilizon/Backend errors
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/backend-errors/es/
2021-11-13 10:48:36 +01:00
Berto Te 08e782860d Translated using Weblate (Spanish)
Currently translated at 100.0% (287 of 287 strings)

Translation: Mobilizon/Backend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/backend/es/
2021-11-13 10:48:35 +01:00
Thomas Citharel fbb167983d Merge branch 'bugs' into 'main'
Allow group admins to moderate new members and other things

Closes #881 et #886

See merge request framasoft/mobilizon!1107
2021-11-12 16:47:01 +00:00
Thomas Citharel d653b038c5
Also send ARM links in releases
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-12 17:20:27 +01:00
Thomas Citharel bd2f5f8c1b
Update translations
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-12 17:17:06 +01:00
Thomas Citharel b940f3c49e
Set same version in docker-compose.test.yml
Closes #886

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-12 17:17:06 +01:00
Thomas Citharel f287b9126e
Improve the AP representation and move endpoints to property
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-12 17:17:05 +01:00
Thomas Citharel 6eba531c89
Allow group admins to moderate new members
Closes #881

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-12 17:17:05 +01:00
Thomas Citharel ae24fa17d5
Refactor Mobilizon.Service.Activity.Member
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-12 17:16:55 +01:00
Thomas Citharel e484728c3d Merge branch 'weblate-mobilizon-frontend' into 'main'
Translations update from Weblate

See merge request framasoft/mobilizon!1105
2021-11-11 16:26:10 +00:00
GunChleoc 38468e06d9 Translated using Weblate (Gaelic)
Currently translated at 100.0% (52 of 52 strings)

Translation: Mobilizon/Activity
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/activity/gd/
2021-11-11 17:22:06 +01:00
GunChleoc 34ba8bd298 Translated using Weblate (Gaelic)
Currently translated at 99.7% (1232 of 1235 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/gd/
2021-11-11 17:22:06 +01:00
Berto Te 97bddd70ec Translated using Weblate (Spanish)
Currently translated at 100.0% (1235 of 1235 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/es/
2021-11-11 17:22:06 +01:00
GunChleoc c571d8336e Added translation using Weblate (Gaelic) 2021-11-11 17:22:06 +01:00
GunChleoc c68a7e8c0c Translated using Weblate (Gaelic)
Currently translated at 97.5% (1205 of 1235 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/gd/
2021-11-11 17:22:06 +01:00
Thomas Citharel 012dbe6419 Merge branch 'notify-followers' into 'main'
Send notification emails to followers and members when a group publishes a new event

See merge request framasoft/mobilizon!1106
2021-11-11 16:05:36 +00:00
Thomas Citharel 4a2805d43e
Extract translations
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-11 16:29:39 +01:00
Berto Te a04ffcd6ce
Translated using Weblate (Spanish)
Currently translated at 100.0% (1234 of 1234 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/es/
2021-11-11 16:29:39 +01:00
Thomas Citharel 9583804890
Only show datetime tz toggle if offset is different
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-11 16:29:38 +01:00
Thomas Citharel 38a3ffc19f
Send event creation and event update notifications in a background task
The event update notification is made unique so that repeated changes
only trigger one notificate every 30 minutes

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-11 16:29:38 +01:00
Thomas Citharel 8250d34597
Debug log the output of an Create Event error
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-10 20:44:52 +01:00
Thomas Citharel c15123e5ea
Add tests for Gancio compatibility
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-10 20:44:51 +01:00
Thomas Citharel afff01d6d2
Set an event UUID if default is nil
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-10 20:44:51 +01:00
Thomas Citharel aaf9c2c931
Refactor fetching medias from event attachements
Use first attachement Document if none has the Banner name

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-10 20:44:51 +01:00
Thomas Citharel 6822cfabf1
Add a default name for media if none is specified in AP
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-10 20:44:51 +01:00
Thomas Citharel 41bddebda2
Add ru and ar to dev locales
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-10 20:44:50 +01:00
Thomas Citharel ccce64d6cb
Update arm/arm64 packages as well
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-10 20:44:50 +01:00
Thomas Citharel 89bbafb44c
Don't digest release files
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-10 20:44:50 +01:00
Thomas Citharel 5c7067b22b
Send notification emails to followers and members when a group publishes
a new event

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-10 16:36:32 +01:00
Thomas Citharel 800060a926 Merge branch 'bugs' into 'main'
Include organizer actor in icalendar export

Closes #907 et #913

See merge request framasoft/mobilizon!1104
2021-11-09 13:33:52 +00:00
Thomas Citharel cf69c8b375
Break line after notification settings in emails
Closes #913

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-09 12:59:53 +01:00
Thomas Citharel 2401abedb5
Include organizer actor in icalendar export
Closes #907

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-09 11:50:35 +01:00
Thomas Citharel 6d9fe639d6
Add option to yarn in CI to avoid network timeouts
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-09 10:39:04 +01:00
Thomas Citharel e4a64e8ad6
Rename changelog entry
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-09 09:50:29 +01:00
Thomas Citharel 16925ebf77 Merge branch '2.0-beta.1' into 'main'
2.0-beta.1

Closes #892

See merge request framasoft/mobilizon!1102
2021-11-09 08:46:42 +00:00
Thomas Citharel 0bc78ad26e
Mention the main branch name change in the changelog
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-09 09:17:38 +01:00
Thomas Citharel 184b1cbdd2
Bump version to 2.0.0-beta.1
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-09 09:15:33 +01:00
Thomas Citharel cd8a5552bb
Improve converting to username from non-latin characters
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-09 09:15:33 +01:00
Thomas Citharel a54607a57d
Upgrade deps
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-09 09:15:33 +01:00
Thomas Citharel 90665f0294
Fix some new eslint rules
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-09 09:15:33 +01:00
Thomas Citharel 6a03e49b63
Remove obsolete configuration
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-09 09:15:32 +01:00
Thomas Citharel 7e15b5fb7a
Set tzworld default dir in config
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-09 09:15:32 +01:00
Thomas Citharel 3961a2067b
Fix search exposing events to unlogged users
Closes #892

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-09 09:15:32 +01:00
Thomas Citharel 729a6a7113
Add custom task to update tz_world data
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-09 09:15:31 +01:00
Thomas Citharel 41eb62bc2b
Remove tzworld loading dynamically
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-09 09:15:31 +01:00
Thomas Citharel e7662d3c77
Add necessary dependencies for exports into Docker image
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-09 09:15:31 +01:00
Thomas Citharel ea276fbe73
Detect if Python3 is installed before launching PythonPort Genserver
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-09 09:15:31 +01:00
Thomas Citharel df21729ba0
Add UPGRADE.md and CHANGELOG.md instructions for 2.0
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-09 09:15:30 +01:00
Thomas Citharel 4fe5625597
Fix map height
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-09 09:15:30 +01:00
Thomas Citharel 73a7ef988f
Rename master branch to main
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-09 09:12:04 +01:00
Thomas Citharel 1bd32b4e36
Merge branch '1.3.x' 2021-11-08 09:04:54 +01:00
Thomas Citharel 7bf0a5f5cc Merge branch 'weblate-mobilizon-frontend' into 'master'
Translations update from Weblate

See merge request framasoft/mobilizon!1099
2021-11-07 21:08:21 +00:00
Berto Te 81f189b385 Translated using Weblate (Spanish)
Currently translated at 100.0% (1234 of 1234 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/es/
2021-11-07 21:37:02 +01:00
Thomas Citharel c0dfb1bec6 Merge branch 'bugs' into 'master'
More bidi improvements

See merge request framasoft/mobilizon!1101
2021-11-07 20:36:56 +00:00
Thomas Citharel 5de0cee025
Allow to access to a language directly though instance.tld/:lang
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-07 21:09:31 +01:00
Thomas Citharel 7c4a76cc89
More bidi improvements
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-07 21:09:30 +01:00
Thomas Citharel 7a30c92651 Merge branch 'bugs' into 'master'
Add dir="auto" to most user generated content

See merge request framasoft/mobilizon!1100
2021-11-07 17:40:13 +00:00
Thomas Citharel f55ca90c35
Allow to search for past events
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-07 18:12:23 +01:00
Thomas Citharel a46372094c
Add dir="auto" to most user generated content
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-07 18:12:22 +01:00
Thomas Citharel 4f739d8672 Merge branch 'bugs' into 'master'
Allow to search events by online status

See merge request framasoft/mobilizon!1098
2021-11-06 14:13:23 +00:00
Thomas Citharel 69e91e89f5
Allow to search events by online status
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-06 14:39:23 +01:00
Thomas Citharel 9ac3da618d
Improve components
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-06 14:39:22 +01:00
Thomas Citharel 1cc776622a Merge branch 'bugs' into 'master'
Various bugs

See merge request framasoft/mobilizon!1097
2021-11-06 10:44:23 +00:00
Thomas Citharel a2017a3546
Rename event address to inline address
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-06 11:15:50 +01:00
Thomas Citharel 53de80dfee
Add a warning message on the group page that the group is remote
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-06 11:15:50 +01:00
Thomas Citharel 9290ab9487
Make PostListItem post title an actual title
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-06 11:15:49 +01:00
Thomas Citharel 4340cf7569
Group map fixes
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-06 10:50:48 +01:00
Thomas Citharel 4de39d5850
Improve searching for group actors
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-06 10:50:48 +01:00
Thomas Citharel 4fc044c595
Expose group physical address to AP
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-06 10:28:08 +01:00
Thomas Citharel 096c3a435a
Improve some components
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-06 10:08:20 +01:00
Thomas Citharel c806beddcd
Add a specific timeout for the Docker multiarch build
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-06 10:06:48 +01:00
Thomas Citharel 0625e7f3e0 Merge branch 'bugs' into 'master'
Various bugfixes

See merge request framasoft/mobilizon!1096
2021-11-04 18:11:46 +00:00
Thomas Citharel 8519364e77
Fix action logs containing group suspension events
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-04 18:38:47 +01:00
Thomas Citharel 39ea05a04a
Add mixins to handle RTL languages
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-04 18:38:47 +01:00
Thomas Citharel 547e222f5f
Add dir="auto" to index.html
See #882

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-04 18:38:47 +01:00
Thomas Citharel 52e90fb320
Only add address and tags to event icalendar export if present
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-04 18:38:25 +01:00
Thomas Citharel 5d2d4f0fa6 Merge branch 'weblate-mobilizon-frontend' into 'master'
Translations update from Weblate

See merge request framasoft/mobilizon!1095
2021-11-04 16:58:34 +00:00
Berto Te 8c2e4e21ea Translated using Weblate (Spanish)
Currently translated at 100.0% (1228 of 1228 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/es/
2021-11-04 16:47:56 +01:00
Thomas Citharel f6aaf493c0 Merge branch 'ap-improvements' into 'master'
ActivityPub bug fixes

Closes #889

See merge request framasoft/mobilizon!1094
2021-11-04 08:36:38 +00:00
Thomas Citharel 52ea2ff579
Add relay followers to recipients when sending public entity
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-04 09:08:05 +01:00
Thomas Citharel 75fbcb985c
Only send Tombstone with Delete activity, not the object itself
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-04 09:04:59 +01:00
Thomas Citharel 26644c1677 Merge branch 'mayel/mobilizon-master' into 'master'
ignore incoming federated private comments

Closes #896

See merge request framasoft/mobilizon!1093
2021-11-03 14:04:48 +00:00
Mayel 5e64d0515e
format 2021-11-03 14:27:19 +01:00
Mayel 475c72597e
ignore incoming federated private comments 2021-11-03 14:27:19 +01:00
Mayel 8cb7df16d0
test case for incoming private comments 2021-11-03 14:27:19 +01:00
Mayel f4736bd1b9
little things to setup & run tests (using docker) 2021-11-03 14:07:32 +01:00
Thomas Citharel 3dc1707e01 Merge branch 'follow-groups' into 'master'
Group follows

Closes #512

See merge request framasoft/mobilizon!1092
2021-11-03 09:41:49 +00:00
Thomas Citharel 497bac8453
Update front-end snapshots
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-03 10:14:44 +01:00
Thomas Citharel d89c0fc414
Make sure some action pages don't get indexed
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-03 10:14:44 +01:00
Thomas Citharel 54116686c8
Remove non existing page from sitemap
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-03 10:14:43 +01:00
Thomas Citharel 20999c4ef4
Update deps
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-03 10:14:43 +01:00
Thomas Citharel 2b3a0f19b9
Fix backend tests
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-03 10:14:43 +01:00
Thomas Citharel f44e466194
Increase complexity
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-02 19:50:39 +01:00
Thomas Citharel b12cea5ead
Allow to get group followed events after a certain date
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-02 19:50:39 +01:00
Thomas Citharel 4923c52f3b
Improve post & events cards, homepage and my events page
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-02 19:50:38 +01:00
Thomas Citharel 39f40a86f7
Upgrade deps
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-02 19:50:37 +01:00
Thomas Citharel ea4116c207
Refactor GraphQL queries and event cards
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-02 19:50:37 +01:00
Thomas Citharel 807b1084b0
Show group followed/member events on homepage
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-02 19:50:27 +01:00
Thomas Citharel 23fddaedf1
[GraphQL] Expose events from followed/member group activity
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-02 19:50:26 +01:00
Thomas Citharel 88b078ebce
Increase the number of related events to 4 and refactor function
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-02 19:50:26 +01:00
Thomas Citharel 2a9098975d
Remove last week's events from homepage
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-02 19:50:26 +01:00
Thomas Citharel 6cdc68e1a8
Allow to fetch Application actors
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-02 19:50:25 +01:00
Thomas Citharel d0b125064f
Improvements to group following
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-02 19:50:25 +01:00
Thomas Citharel 3e74982ec4
Don't install Cypress in production Docker image building
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-02 19:50:25 +01:00
Thomas Citharel 59edfe26cc
Improve notifier filter handing to avoid compiler cycle
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-02 19:50:24 +01:00
Thomas Citharel bac6628aea
Improve changing current actor
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-02 19:50:24 +01:00
Thomas Citharel d0b45de175
Expose person follows
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-02 19:50:23 +01:00
Thomas Citharel f315685deb
Handle already following group
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-02 19:50:23 +01:00
Thomas Citharel f8eda4aac5
Add front-end for managing group follow
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-02 19:50:23 +01:00
Thomas Citharel 244a349b7d
Bump deps
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-02 19:50:22 +01:00
Thomas Citharel 358be2aac6
Remove reloading tzworld
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-02 19:50:22 +01:00
Thomas Citharel 44e8ac7e9a
Add support for GraphQL handling of group follows
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-02 19:50:21 +01:00
Thomas Citharel cf9ba47b69 Merge branch 'weblate-mobilizon-frontend' into 'master'
Translations update from Weblate

See merge request framasoft/mobilizon!1091
2021-11-01 16:44:12 +00:00
GunChleoc cf2c260e79 Translated using Weblate (Gaelic)
Currently translated at 100.0% (1205 of 1205 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/gd/
2021-11-01 17:22:07 +01:00
Eivind Ødegård c46e0a367a Translated using Weblate (Norwegian Nynorsk)
Currently translated at 100.0% (192 of 192 strings)

Translation: Mobilizon/Backend errors
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/backend-errors/nn/
2021-11-01 17:22:07 +01:00
Eivind Ødegård 3f5d44e5fb Translated using Weblate (Norwegian Nynorsk)
Currently translated at 100.0% (271 of 271 strings)

Translation: Mobilizon/Backend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/backend/nn/
2021-11-01 17:22:07 +01:00
Eivind Ødegård df8e3e913d Translated using Weblate (Norwegian Nynorsk)
Currently translated at 100.0% (1205 of 1205 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/nn/
2021-11-01 17:22:07 +01:00
Miroslav Mazel f4faf9f85c Translated using Weblate (Czech)
Currently translated at 16.8% (203 of 1205 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/cs/
2021-11-01 17:22:05 +01:00
Thomas Citharel cbb6ba6f52 Merge branch 'weblate-mobilizon-frontend' into 'master'
Translations update from Weblate

See merge request framasoft/mobilizon!1090
2021-10-28 07:44:09 +00:00
GunChleoc 2131a774fb Translated using Weblate (Gaelic)
Currently translated at 99.9% (1204 of 1205 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/gd/
2021-10-28 00:22:03 +02:00
deadmorose 4a1fa55475 Translated using Weblate (Russian)
Currently translated at 100.0% (192 of 192 strings)

Translation: Mobilizon/Backend errors
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/backend-errors/ru/
2021-10-28 00:22:00 +02:00
deadmorose 8d855e3108 Translated using Weblate (Russian)
Currently translated at 100.0% (271 of 271 strings)

Translation: Mobilizon/Backend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/backend/ru/
2021-10-28 00:21:59 +02:00
deadmorose 693545d3c6 Translated using Weblate (Russian)
Currently translated at 100.0% (1205 of 1205 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/ru/
2021-10-28 00:21:59 +02:00
Thomas Citharel 89ead286ad Merge branch 'docker-cross-compile' into 'master'
Build releases and Docker images for arm and arm64 arch

Closes #598

See merge request framasoft/mobilizon!1088
2021-10-22 15:02:06 +00:00
Thomas Citharel ef1e4124de
Allow release and Docker multiarch building
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-22 16:29:58 +02:00
Thomas Citharel 7c27ba4000 Merge branch 'more-metadata' into 'master'
Add more metadata elements

See merge request framasoft/mobilizon!1089
2021-10-22 08:14:31 +00:00
Thomas Citharel 3b63c2928e
Add more metadata elements
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-22 09:41:36 +02:00
Thomas Citharel e47f38691d Merge branch 'weblate-mobilizon-frontend' into 'master'
Translations update from Weblate

See merge request framasoft/mobilizon!1086
2021-10-21 08:45:57 +00:00
Berto Te f87fb0f579 Translated using Weblate (Spanish)
Currently translated at 100.0% (274 of 274 strings)

Translation: Mobilizon/Backend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/backend/es/
2021-10-21 10:24:32 +02:00
Berto Te acef56ee7c Translated using Weblate (Spanish)
Currently translated at 100.0% (1205 of 1205 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/es/
2021-10-21 10:24:32 +02:00
Thomas Citharel a9105f82da Merge branch 'remove-custom-tz-storage' into 'master'
Remove custom tz storage for prod

See merge request framasoft/mobilizon!1087
2021-10-21 08:24:24 +00:00
Thomas Citharel 7f2118346c
Remove custom tz storage for prod
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-21 09:54:22 +02:00
Thomas Citharel e227a75337 Merge branch 'fixes' into 'master'
Fix welcome stepper after validating account

See merge request framasoft/mobilizon!1085
2021-10-20 16:09:57 +00:00
Thomas Citharel 8eddc2cf88
Fix welcome stepper after validating account
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-20 17:31:07 +02:00
Thomas Citharel 8acca91dd5 Merge branch 'fixes' into 'master'
Fix event options being undefined in event view

See merge request framasoft/mobilizon!1084
2021-10-20 14:46:40 +00:00
Thomas Citharel 70b4fd5f6e
Ignore demo folder
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-20 12:37:28 +02:00
Thomas Citharel df22082f3e
Fix posts not able to accept existing tags
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-20 11:50:39 +02:00
Thomas Citharel 3ecc2cbad7
Fix group profile being inaccessible
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-20 11:50:22 +02:00
Thomas Citharel 52e69bb2a3
Fix event options being undefined in event view
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-20 11:49:55 +02:00
Thomas Citharel 12e0b8a30f Merge branch 'tz-improvements' into 'master'
TZ and emails improvements

See merge request framasoft/mobilizon!1081
2021-10-19 10:46:24 +00:00
Thomas Citharel 9b4a92077f
Add TimezoneDetectorTest
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-19 12:18:03 +02:00
Thomas Citharel 0792bf5445
Update tzworld at runtime
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-19 12:18:03 +02:00
Thomas Citharel 312e71d3ec
Compress the CI release artifacts before passing to upload
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-18 14:46:04 +02:00
Thomas Citharel 3f5f86db99
Echo size of artifact after packaging
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-18 14:16:48 +02:00
Thomas Citharel 1df9f84c20
Fix jest tests by adding tz to mocks
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-18 11:49:41 +02:00
Thomas Citharel f24ec89408
Fix event notification tests
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-18 11:14:46 +02:00
Thomas Citharel f31454c692
Add json_ld info to more emails
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-18 11:14:46 +02:00
Thomas Citharel f9ddeb80fb
Add offer_unsupscription parameter to emails
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-18 11:14:45 +02:00
Thomas Citharel ea76805a90
Update translation files
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-18 11:14:43 +02:00
Thomas Citharel bd4fad3c58
Improve emails
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-18 11:13:37 +02:00
Thomas Citharel aca2ed612e
Add datetime_to_date_string/3 and is_same_day?/2 to Mobilizon.Service.DateTime
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-18 11:13:37 +02:00
Thomas Citharel 7ecf2e1da0
Add isOnline event option to mark event as fully online
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-18 11:13:37 +02:00
Thomas Citharel d2ccc21f91
Add tz_world.update to release scripts
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-18 11:13:36 +02:00
Thomas Citharel 88e9ae8214
Rollback eblurhash to 1.2.0
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-18 11:13:36 +02:00
Thomas Citharel 8a1dfb0612
Extract vue-announcer and vue-skip-to styles (so that they're nt inline)
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-18 11:13:36 +02:00
Thomas Citharel 676fab7871
Embed json-ld metadata on events in emails
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-18 11:13:36 +02:00
Thomas Citharel 555ae867ea
Add code to participants
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-18 11:13:35 +02:00
Thomas Citharel 4de78f58e0
Allow anonymous participants to have timezone metadata
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-18 11:13:35 +02:00
Thomas Citharel 44f90c7b0b
Fix focustarget in some cases
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-18 11:13:35 +02:00
Thomas Citharel a9e36aaacb
Show correct timezone in event-related emails
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-18 11:13:33 +02:00
Thomas Citharel cd89efa1e3
Export timezone in ICS files
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-18 11:12:18 +02:00
Thomas Citharel 4ca831a5b3
Federate timezone
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-18 11:12:18 +02:00
Thomas Citharel 38cb9d3c9f Merge branch 'weblate-mobilizon-frontend' into 'master'
Translations update from Weblate

See merge request framasoft/mobilizon!1080
2021-10-14 09:08:41 +00:00
Jurij Podgoršek 46feb40908 Translated using Weblate (Slovenian)
Currently translated at 81.7% (984 of 1204 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/sl/
2021-10-14 06:21:55 +02:00
Berto Te 348af2ab7f Translated using Weblate (Spanish)
Currently translated at 100.0% (192 of 192 strings)

Translation: Mobilizon/Backend errors
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/backend-errors/es/
2021-10-14 06:21:55 +02:00
Berto Te 779851d7af Translated using Weblate (Spanish)
Currently translated at 100.0% (264 of 264 strings)

Translation: Mobilizon/Backend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/backend/es/
2021-10-14 06:21:54 +02:00
Berto Te 895728e4b0 Translated using Weblate (Spanish)
Currently translated at 100.0% (1204 of 1204 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/es/
2021-10-14 06:21:54 +02:00
Thomas Citharel 358dd63b92 Merge branch 'deps' into 'master'
Update deps

See merge request framasoft/mobilizon!1079
2021-10-12 08:36:15 +00:00
Thomas Citharel 6925420e97
Update deps
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-12 09:48:39 +02:00
Thomas Citharel 76aeac2989 Merge branch 'timezone' into 'master'
Timezones !

See merge request framasoft/mobilizon!1077
2021-10-12 07:30:52 +00:00
Thomas Citharel b383d11f51
Fix backend tests
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-12 08:49:12 +02:00
Thomas Citharel 9621caf661
Bump required version to Elixir 1.12 and remove legacy tests
Drops support for OTP < 22

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-11 18:46:16 +02:00
Thomas Citharel e280820e9c
Fix front-end tests
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-11 18:29:01 +02:00
Thomas Citharel e606090f17
Fix legacy tests missing the python deps
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-11 18:28:42 +02:00
Thomas Citharel 6e136ff8b5
Update tz_world before running tests
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-11 18:28:28 +02:00
Thomas Citharel d4b6fb2b49
Update translations
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-11 17:56:23 +02:00
Thomas Citharel 62af1e72d6
Some fixes
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-11 17:56:23 +02:00
Thomas Citharel d58ca5743d
Add timezone handling
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-11 17:37:17 +02:00
Thomas Citharel eba3c70c9b
Various accessibility improvements
* Add announcement element with `aria-live`
* Add skip to main content element

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-11 17:37:16 +02:00
Thomas Citharel 6113836e29 Merge branch 'weblate-mobilizon-frontend' into 'master'
Translations update from Weblate

See merge request framasoft/mobilizon!1076
2021-10-11 15:25:38 +00:00
z4m 034b9ad9f0 Translated using Weblate (Portuguese (Brazil))
Currently translated at 67.3% (781 of 1160 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/pt_BR/
2021-10-11 16:36:34 +02:00
Eivind Ødegård f958d291a0 Translated using Weblate (Norwegian Nynorsk)
Currently translated at 100.0% (188 of 188 strings)

Translation: Mobilizon/Backend errors
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/backend-errors/nn/
2021-10-11 16:36:34 +02:00
Eivind Ødegård 463d8bf86e Translated using Weblate (Norwegian Nynorsk)
Currently translated at 100.0% (264 of 264 strings)

Translation: Mobilizon/Backend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/backend/nn/
2021-10-11 16:36:34 +02:00
Eivind Ødegård cdfef60414 Translated using Weblate (Norwegian Nynorsk)
Currently translated at 99.8% (1158 of 1160 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/nn/
2021-10-11 16:36:34 +02:00
Berto Te a8cbb0386c Translated using Weblate (Spanish)
Currently translated at 100.0% (188 of 188 strings)

Translation: Mobilizon/Backend errors
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/backend-errors/es/
2021-10-11 16:36:34 +02:00
Berto Te 8580281fb4 Translated using Weblate (Spanish)
Currently translated at 100.0% (264 of 264 strings)

Translation: Mobilizon/Backend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/backend/es/
2021-10-11 16:36:34 +02:00
Thomas Citharel db55859855 Merge branch 'usage-restrictions' into 'master'
add "only platform admin can create groups" and "only groups can create events" restrictions

See merge request framasoft/mobilizon!1078
2021-10-11 14:36:24 +00:00
setop 7940d69d5a
add "only platform admin can create groups" and "only groups can create events" restrictions 2021-10-11 15:59:59 +02:00
Thomas Citharel 7885151220 Merge branch 'unused' into 'master'
Improvements

See merge request framasoft/mobilizon!1074
2021-10-11 13:51:37 +00:00
Thomas Citharel dee7c58449
Spec fixes
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-11 15:14:54 +02:00
Thomas Citharel 76bc409f68
Remove mix_unused
It slows down compiling too much, and most of the cleaning has been done

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-11 14:30:24 +02:00
Thomas Citharel ba61880e50 Merge branch 'unused' into 'master'
Remove unused functions

See merge request framasoft/mobilizon!1073
2021-10-05 14:37:59 +00:00
Thomas Citharel 98449b9cfd
Spec fixes
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-05 16:05:52 +02:00
Thomas Citharel f4284e1d3a
Remove unused functions
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-05 16:05:51 +02:00
Thomas Citharel 5d8d2e80a5
Add mix_unused to detect unused functions
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-05 16:05:51 +02:00
Thomas Citharel 2f83e5a3d5 Merge branch 'update-deps' into 'master'
Update deps

See merge request framasoft/mobilizon!1072
2021-10-05 10:53:50 +00:00
Thomas Citharel f4237dc469
Update deps
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-05 12:23:00 +02:00
Thomas Citharel 14922c1479
Merge remote-tracking branch 'weblate/master'
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-05 11:26:39 +02:00
deadmorose 32e9b0a838 Translated using Weblate (Russian)
Currently translated at 100.0% (184 of 184 strings)

Translation: Mobilizon/Backend errors
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/backend-errors/ru/
2021-10-05 11:23:23 +02:00
deadmorose a05c3a20ca Translated using Weblate (Russian)
Currently translated at 100.0% (248 of 248 strings)

Translation: Mobilizon/Backend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/backend/ru/
2021-10-05 11:23:23 +02:00
Thomas Citharel 739dcc2405 Merge branch 'export-participants' into 'master'
Export participants

Closes #132

See merge request framasoft/mobilizon!1070
2021-10-05 09:23:12 +00:00
Thomas Citharel 9d5bf806df
Adding icons to exports
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-05 10:52:56 +02:00
Thomas Citharel 60c77eb355
Adding uploads folder configuration to gitignore
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-05 10:52:55 +02:00
Thomas Citharel 0c667b13ae
Export participants to different formats
* CSV
* PDF (requires Python dependency `weasyprint`)
* ODS (requires Python dependency `pyexcel_ods3`)

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-05 10:52:55 +02:00
Thomas Citharel 5dd24e1c9e
Allow to change an user's password through the users.modify mix task
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-10-04 17:38:37 +02:00
Thomas Citharel 4c66162e7e Merge branch 'weblate-mobilizon-frontend' into 'master'
Translations update from Weblate

See merge request framasoft/mobilizon!1071
2021-10-02 06:48:11 +00:00
Berto Te ae098f8cc7 Translated using Weblate (Spanish)
Currently translated at 100.0% (184 of 184 strings)

Translation: Mobilizon/Backend errors
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/backend-errors/es/
2021-10-02 08:16:02 +02:00
Berto Te 10430d9cb6 Translated using Weblate (Spanish)
Currently translated at 100.0% (248 of 248 strings)

Translation: Mobilizon/Backend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/backend/es/
2021-10-02 08:16:02 +02:00
Thomas Citharel 6a43812977 Merge branch 'migrate-templates-to-heex' into 'master'
Migrate .html.eex template files to .html.heex

See merge request framasoft/mobilizon!1069
2021-10-01 18:38:28 +00:00
Thomas Citharel b0ed979ed1 Merge branch 'heex' into 'master'
Migrate .html.eex template files to .html.heex

See merge request framasoft/mobilizon!1068
2021-09-30 15:13:59 +00:00
Thomas Citharel e09811bd95
Migrate .html.eex template files to .html.heex
See https://gist.github.com/chrismccord/2ab350f154235ad4a4d0f4de6decba7b#rename-your-htmleex-and-htmlleex-templates-to-htmlheex-optional

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-30 16:44:24 +02:00
Thomas Citharel 12987068ed Merge branch 'lang' into 'master'
Various fixes

See merge request framasoft/mobilizon!1067
2021-09-30 14:43:39 +00:00
Thomas Citharel 8235f4c7b5
Migrate .html.eex template files to .html.heex
See https://gist.github.com/chrismccord/2ab350f154235ad4a4d0f4de6decba7b#rename-your-htmleex-and-htmlleex-templates-to-htmlheex-optional

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-30 16:17:44 +02:00
Thomas Citharel 2b808d998e
Delete participations after warning participants
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-30 15:58:12 +02:00
Thomas Citharel 98b840190d
Refactor HTTP signatures
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-30 15:58:12 +02:00
Thomas Citharel 74145dc520
Fix templates from suspension information emails
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-30 15:58:11 +02:00
Thomas Citharel 4a7c6a861e
Improve the way the instance name is injected into emails
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-30 15:58:10 +02:00
Thomas Citharel 6edeeef9a9
Fix styling issue when informing from actor suspension
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-30 15:58:09 +02:00
Thomas Citharel fd980176bc
Add some debug logging to actor suspension process
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-30 15:58:09 +02:00
Thomas Citharel 0f8358b96a
Add & improve some typespecs
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-30 15:58:08 +02:00
Thomas Citharel 35b83950d4
Fix an issue when showing public page of suspended group
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-30 15:58:07 +02:00
Thomas Citharel 5ef50766b1
Don't link to the group page from admin when actor is suspended
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-30 15:58:06 +02:00
Thomas Citharel 4d7281e9e0
Update backend translations
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-30 15:58:06 +02:00
Thomas Citharel 3fcdba9c1f
Add a config file for vue-i18n-extract
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-30 15:58:05 +02:00
Thomas Citharel 4ef70e7d0f
Cleanup en_US.json i18n file
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-30 08:31:33 +02:00
Thomas Citharel 9c0bc9dcd0
Bump deps
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-30 08:31:21 +02:00
Thomas Citharel 393c92bd03 Merge branch 'fixes' into 'master'
Upgrade deps and fixes

Closes #893

See merge request framasoft/mobilizon!1066
2021-09-29 18:56:54 +00:00
Thomas Citharel 81c319bdf8
Adapt to new Oban result
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-29 20:28:03 +02:00
Thomas Citharel 327dfbb4c2
Rollback Phoenix client to 1.5 because of issues with Jest
See https://github.com/phoenixframework/phoenix/pull/4512

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-29 20:24:55 +02:00
Thomas Citharel 773a29d1f4
Add Telegram Logo component (icon was removed from MDI)
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-29 19:12:34 +02:00
Thomas Citharel fa25ffbc83
Add some typespecs
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-29 19:12:33 +02:00
Thomas Citharel 9d910db040
Fix admin settings edition
Closes #893

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-29 19:06:57 +02:00
Thomas Citharel c924975d40
Fix a typespec issue
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-29 18:22:47 +02:00
Thomas Citharel c5624ae33d
Fix an issue when leaving a group
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-29 18:22:32 +02:00
Thomas Citharel 120d79d580
Fix an issue with TagInput
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-29 18:21:44 +02:00
Thomas Citharel 9e4378d4db
Fix an issue with Identity Picker
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-29 18:21:24 +02:00
Thomas Citharel 20ffd2ad0f
Fix a color issue with the comment author name when comment is selected
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-29 18:20:55 +02:00
Thomas Citharel dc52cd042b
Fix a typescript issue with catch variable type
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-29 18:20:33 +02:00
Thomas Citharel 7bf745edf7
Upgrade deps
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-29 18:19:26 +02:00
Thomas Citharel d8b64e9a19 Merge branch 'dializer' into 'master'
Various typespec and compilation improvements

See merge request framasoft/mobilizon!1062
2021-09-29 15:11:43 +00:00
Thomas Citharel 8226007a94
Clean unused i18n string
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-29 16:31:12 +02:00
Thomas Citharel b5d9b82bdd
Refactor Mobilizon.Federation.ActivityPub and add typespecs
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-29 16:31:11 +02:00
Thomas Citharel 14fbe3fecb Merge branch 'weblate-mobilizon-frontend' into 'master'
Translations update from Weblate

See merge request framasoft/mobilizon!1065
2021-09-29 08:52:55 +00:00
Eivind Ødegård 89b3e12ace Translated using Weblate (Norwegian Nynorsk)
Currently translated at 100.0% (247 of 247 strings)

Translation: Mobilizon/Backend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/backend/nn/
2021-09-29 10:16:07 +02:00
Eivind Ødegård 10bb228ce9 Translated using Weblate (Norwegian Nynorsk)
Currently translated at 99.8% (1172 of 1174 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/nn/
2021-09-29 10:16:06 +02:00
Thomas Citharel 41f086e2c9
Spec improvements
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-27 09:42:12 +02:00
Thomas Citharel cc3106e425
Add Doctor to check docs & specs
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-27 09:42:12 +02:00
Thomas Citharel 4f2dd048c6
Update to Phoenix 1.6
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-27 09:42:12 +02:00
Thomas Citharel 55e7696230
Absinthe middleware actor provider
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-27 09:42:11 +02:00
Thomas Citharel ae97339353
Dev locales change
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-26 17:52:24 +02:00
Thomas Citharel 1893d9f55b
Various refactoring and typespec improvements
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-26 17:52:24 +02:00
Thomas Citharel d235653876
Update deps
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-26 17:52:23 +02:00
Thomas Citharel 734e4684f7
Empty old & new passwords fields when successful change
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-26 17:52:23 +02:00
Thomas Citharel 75e254d8b4
Actor suspension refactoring
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-26 17:52:23 +02:00
Thomas Citharel e9fecc4d24
Disable support for e2e tests for now
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-26 17:52:22 +02:00
Thomas Citharel 28a2efc6eb
Add request context to Sentry
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-26 17:52:22 +02:00
Thomas Citharel acbe0e6c59
Fix rendering metadata from deleted (empty) comments
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-26 17:52:22 +02:00
Thomas Citharel e9e12500dc
Fix tags autocomplete
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-26 17:52:21 +02:00
Thomas Citharel 6bb0b6d08a
Improve Gettext compilation
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-26 17:52:21 +02:00
Thomas Citharel de047c8939
Various typespec and compilation improvements
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-26 17:52:20 +02:00
Thomas Citharel 029a4ea194 Merge branch 'weblate-mobilizon-frontend' into 'master'
Translations update from Weblate

See merge request framasoft/mobilizon!1064
2021-09-26 14:55:31 +00:00
Chimpy 61f430bad8 Translated using Weblate (Persian)
Currently translated at 9.7% (114 of 1174 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/fa/
2021-09-26 16:16:03 +02:00
Thomas Citharel b0a6b82829 Merge branch 'weblate-mobilizon-frontend' into 'master'
Translations update from Weblate

See merge request framasoft/mobilizon!1063
2021-09-25 12:30:48 +00:00
Eivind Ødegård 0c622303d1 Translated using Weblate (Norwegian Nynorsk)
Currently translated at 100.0% (52 of 52 strings)

Translation: Mobilizon/Activity
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/activity/nn/
2021-09-25 12:16:21 +02:00
Eivind Ødegård 01c7222a79 Translated using Weblate (Norwegian Nynorsk)
Currently translated at 100.0% (172 of 172 strings)

Translation: Mobilizon/Backend errors
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/backend-errors/nn/
2021-09-25 12:16:21 +02:00
Eivind Ødegård 3811c10995 Translated using Weblate (Norwegian Nynorsk)
Currently translated at 99.5% (246 of 247 strings)

Translation: Mobilizon/Backend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/backend/nn/
2021-09-25 12:16:20 +02:00
Eivind Ødegård 7e8b1a06f9 Translated using Weblate (Norwegian Nynorsk)
Currently translated at 93.1% (1093 of 1174 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/nn/
2021-09-25 12:16:20 +02:00
Thomas Citharel d5082c1b6c Merge branch 'weblate-mobilizon-frontend' into 'master'
Translations update from Weblate

See merge request framasoft/mobilizon!1061
2021-09-21 09:50:21 +00:00
Quentin PAGÈS ccf2423c42 Translated using Weblate (Occitan)
Currently translated at 70.9% (833 of 1174 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/oc/
2021-09-21 11:15:59 +02:00
Thomas Citharel 09cc5d1bd1 Merge branch 'weblate-mobilizon-frontend' into 'master'
Translations update from Weblate

See merge request framasoft/mobilizon!1060
2021-09-12 11:12:45 +00:00
TA e6dfaf8950 Translated using Weblate (Indonesian)
Currently translated at 60.1% (706 of 1174 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/id/
2021-09-12 12:15:57 +02:00
deadmorose 4eceacde97 Translated using Weblate (French (France) (fr_FR))
Currently translated at 100.0% (1174 of 1174 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/fr_FR/
2021-09-12 12:15:56 +02:00
deadmorose b8dd858a0e Translated using Weblate (Russian)
Currently translated at 100.0% (1174 of 1174 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/ru/
2021-09-12 12:15:56 +02:00
Thomas Frenzel 77108e1a97 Translated using Weblate (German)
Currently translated at 88.3% (1037 of 1174 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/de/
2021-09-12 12:15:54 +02:00
Thomas Citharel 3c035b613b Merge branch 'weblate-mobilizon-frontend' into 'master'
Translations update from Weblate

See merge request framasoft/mobilizon!1059
2021-09-10 07:53:01 +00:00
Berto Te 06c680b541 Translated using Weblate (Spanish)
Currently translated at 100.0% (1174 of 1174 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/es/
2021-09-10 09:15:54 +02:00
Thomas Citharel f4293b028a Merge branch 'weblate-mobilizon-frontend' into 'master'
Translations update from Weblate

See merge request framasoft/mobilizon!1058
2021-09-08 12:24:12 +00:00
deadmorose 2fde7e2591 Translated using Weblate (Russian)
Currently translated at 100.0% (1144 of 1144 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/ru/
2021-09-08 10:41:32 +02:00
Thomas Citharel 4f4c92e917 Merge branch 'front-end-issues' into 'master'
A11y improvements

Closes #821

See merge request framasoft/mobilizon!1057
2021-09-08 08:41:24 +00:00
Thomas Citharel 39238c4b8a
Translation updates
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-08 10:04:40 +02:00
Thomas Citharel b1ab0dabbe
Discussions views improvements
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-08 10:04:40 +02:00
Thomas Citharel d81c479aa6
Show actual language instead of language code in Users admin view
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-08 10:04:40 +02:00
Thomas Citharel 68065a611a
Cleanup getting instance's languages in AboutInstance
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-08 10:04:37 +02:00
Thomas Citharel 9c7a4f0079
Refactor TagInput.vue
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-08 10:04:08 +02:00
Thomas Citharel 291a788438
Various accessibility improvements
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-08 10:04:06 +02:00
Thomas Citharel d36f1d4b5e
Refactor notification view to disable push column when push is not
currently possible

Closes #821

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-07 17:46:28 +02:00
Thomas Citharel 3564b69db8
Only show locatecontrol button in leaflet map when we can do geolocation
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-07 17:43:16 +02:00
Thomas Citharel c198b21587
Revert text color from error messages
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-07 17:42:21 +02:00
Thomas Citharel c2c3014c2b
Show a default text for contact in default terms text when no contact is
filled

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-07 17:41:27 +02:00
Thomas Citharel bc1f71e742
Refactor addressautocomplete components into a mixin
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-07 17:40:31 +02:00
Thomas Citharel 8a58f5ba7c
Cleanup Followers.vue
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-07 17:38:47 +02:00
Thomas Citharel 9e2e071609
Fix an issue with group activity items when moving resources
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-07 17:37:41 +02:00
Thomas Citharel 08abaf56be Merge branch 'fix-geo-coordinates' into 'master'
Fix federating geo-coordinates

Closes #872

See merge request framasoft/mobilizon!1056
2021-09-06 11:59:15 +00:00
Thomas Citharel cc197d7638
Fix federating geo coordinates
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-06 12:46:29 +02:00
Thomas Citharel 7c71e9e04f
Upgrade deps
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-06 12:28:10 +02:00
Thomas Citharel 2d1facbf39
Merge branch 'cornerot/mobilizon-cornerot-master-patch-62182' 2021-09-06 09:30:49 +02:00
Thomas Citharel f5340e32a7
Update translation files after fixed typo
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-09-06 09:29:35 +02:00
Thomas Citharel efb72f4751 Merge branch 'weblate-mobilizon-frontend' into 'master'
Translations update from Weblate

See merge request framasoft/mobilizon!1053
2021-09-06 07:27:27 +00:00
deadmorose 796fd4ffbe Translated using Weblate (Russian)
Currently translated at 100.0% (1144 of 1144 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/ru/
2021-09-02 21:15:55 +02:00
cornerot 7958c109ca "Jisti Meet" -> "Jitsi Meet" in EventMetadata.ts 2021-09-01 19:20:24 +00:00
Tykayn faa0e2fd3e add more things 2021-09-01 12:54:11 +02:00
Mostafa Ahangarha bea1015b44 Translated using Weblate (Persian)
Currently translated at 8.2% (94 of 1144 strings)

Translation: Mobilizon/Frontend
Translate-URL: https://weblate.framasoft.org/projects/mobilizon/frontend/fa/
2021-08-29 11:15:49 +02:00
Thomas Citharel a97ac84b2c Merge branch 'release-1.3.2' into 'master'
Release 1.3.2

See merge request framasoft/mobilizon!1052
2021-08-23 13:01:56 +00:00
Thomas Citharel a53690fbe5
Release 1.3.2
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-08-23 13:39:08 +02:00
Thomas Citharel bcad13dd28 Merge branch 'search-return-only-groups' into 'master'
Search should return only groups, don't show user profiles

Closes #845

See merge request framasoft/mobilizon!1051
2021-08-23 10:10:22 +00:00
Thomas Citharel 24ff99ffaf
Search should return only groups, don't show user profiles
Closes #845

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-08-23 11:17:32 +02:00
Thomas Citharel f3bb1f8976 Merge branch 'delete-account-fixes' into 'master'
Fix deleting own account

See merge request framasoft/mobilizon!1050
2021-08-23 09:15:58 +00:00
Thomas Citharel 3a79806c0f
Accessibility improvements in AccountSettings
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-08-23 10:20:55 +02:00
Thomas Citharel c2b72b45d3
Fix deleting own account
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-08-23 10:20:31 +02:00
Thomas Citharel 89da83dce4 Merge branch 'fix-orphan-media-cleaning' into 'master'
Fixed deduplicated files from orphan media being deleted as well

See merge request framasoft/mobilizon!1049
2021-08-22 14:54:05 +00:00
Thomas Citharel ab843dff4c
Fixed deduplicated files from orphan media being deleted as well
Happens when a file is uploaded, then orphaned, and a similar file is
used somewhere. The CleanMedia job service didn't consider that case

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-08-22 16:17:20 +02:00
Thomas Citharel 1ac2990cda Merge branch 'fix-tags-event-pagination' into 'master'
Fix events pagination on tags page

Closes #840 et #841

See merge request framasoft/mobilizon!1046
2021-08-20 17:58:24 +00:00
Thomas Citharel d35041bd4c Merge branch 'fix-config-onboarding-after-ldap' into 'master'
Fix config onboarding after LDAP initial connexion

Closes #840

See merge request framasoft/mobilizon!1045
2021-08-20 17:30:34 +00:00
Thomas Citharel 846a4c376a
Fix events pagination on tags page
Not a perfect fix because of a blinking issue, but works properly

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-08-20 19:05:17 +02:00
Thomas Citharel e2496536e9 Merge branch 'save-event-language' into 'master'
Introduce event language detection

Closes #636

See merge request framasoft/mobilizon!1042
2021-08-20 16:55:29 +00:00
Thomas Citharel 5cf962fd8f
Delete current actor ID as well from local storage when unlogging
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-08-20 18:49:17 +02:00
Thomas Citharel ba075c1973
Fix config onboarding after LDAP initial connexion
Closes #840

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-08-20 18:49:03 +02:00
Thomas Citharel d577b07c6e
Introduce event language detection
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-08-20 18:01:29 +02:00
Thomas Citharel 7c9b76765f
Cleanup
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-08-20 18:00:21 +02:00
839 changed files with 53531 additions and 30248 deletions

15
.doctor.exs Normal file
View File

@ -0,0 +1,15 @@
%Doctor.Config{
exception_moduledoc_required: true,
failed: false,
ignore_modules: [Mobilizon.Web, Mobilizon.GraphQL.Schema, Mobilizon.Service.Activity.Renderer, Mobilizon.Service.Workers.Helper],
ignore_paths: [],
min_module_doc_coverage: 100,
min_module_spec_coverage: 50,
min_overall_doc_coverage: 100,
min_overall_spec_coverage: 90,
moduledoc_required: true,
raise: false,
reporter: Doctor.Reporters.Full,
struct_type_spec_required: true,
umbrella: false
}

6
.gitignore vendored
View File

@ -27,6 +27,7 @@ priv/data/*
priv/errors/*
!priv/errors/.gitkeep
priv/cert/
priv/python/__pycache__/
.vscode/
cover/
site/
@ -37,6 +38,8 @@ test/uploads/
uploads/*
release/
!uploads/.gitkeep
!uploads/exports/.gitkeep
!uploads/exports/**/.gitkeep
.idea
*.mo
*.po~
@ -44,4 +47,5 @@ release/
docker/production/.env
test-junit-report.xml
js/junit.xml
.env
.env
demo/

View File

@ -28,6 +28,10 @@ variables:
# Release elements
PACKAGE_REGISTRY_URL: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${CI_PROJECT_NAME}"
ARCH: "amd64"
EXPORT_FORMATS: "csv,ods,pdf"
APP_VERSION: "${CI_COMMIT_REF_NAME}"
APP_ASSET: "${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_${ARCH}.tar.gz"
CYPRESS_INSTALL_BINARY: 0
cache:
key: "${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA}"
@ -100,23 +104,6 @@ deps:
needs:
- install
exunit-1.11:
stage: test
image: tcitworld/mobilizon-ci:legacy
services:
- name: postgis/postgis:11-3.0
alias: postgres
variables:
MIX_ENV: test
before_script:
- mix deps.clean --all
- mix deps.get
- mix ecto.create
- mix ecto.migrate
script:
- mix coveralls
allow_failure: true
exunit:
stage: test
services:
@ -125,7 +112,7 @@ exunit:
variables:
MIX_ENV: test
before_script:
- mix deps.get
- mix deps.get && mix tz_world.update
- mix ecto.create
- mix ecto.migrate
script:
@ -185,7 +172,7 @@ pages:
# #- yarn run --cwd "js" styleguide:build
# #- mv js/styleguide public/frontend
rules:
- if: '$CI_COMMIT_BRANCH == "master"'
- if: '$CI_COMMIT_BRANCH == "main"'
artifacts:
expire_in: 1 hour
paths:
@ -193,24 +180,50 @@ pages:
.docker: &docker
stage: docker
image: docker:stable
variables:
DOCKER_TLS_CERTDIR: "/certs"
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_VERIFY: 1
DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client"
DOCKER_DRIVER: overlay2
services:
- docker:stable-dind
cache: {}
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
before_script:
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"auth\":\"$CI_REGISTRY_AUTH\",\"email\":\"$CI_REGISTRY_EMAIL\"}}}" > /kaniko/.docker/config.json
# Install buildx
- wget https://github.com/docker/buildx/releases/download/v0.6.3/buildx-v0.6.3.linux-amd64
- mkdir -p ~/.docker/cli-plugins/
- mv buildx-v0.6.3.linux-amd64 ~/.docker/cli-plugins/docker-buildx
- chmod a+x ~/.docker/cli-plugins/docker-buildx
# Create env
- docker context create tls-environment
- docker buildx create --use tls-environment
# Install qemu/binfmt
- docker pull tonistiigi/binfmt:latest
- docker run --rm --privileged tonistiigi/binfmt:latest --install all
# Login to DockerHub
- mkdir -p ~/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"auth\":\"$CI_REGISTRY_AUTH\",\"email\":\"$CI_REGISTRY_EMAIL\"}}}" > ~/.docker/config.json
script:
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/docker/production/Dockerfile --destination $DOCKER_IMAGE_NAME --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP
- >
docker buildx build
--push
--platform linux/amd64,linux/arm64,linux/arm
-t $DOCKER_IMAGE_NAME
-f docker/production/Dockerfile .
tags:
- "privileged"
timeout: 3 hours
build-docker-master:
build-docker-main:
<<: *docker
rules:
- if: '$CI_PROJECT_NAMESPACE != "framasoft"'
when: never
- if: '$CI_PIPELINE_SOURCE == "schedule"'
variables:
DOCKER_IMAGE_NAME: framasoft/mobilizon:master
DOCKER_IMAGE_NAME: framasoft/mobilizon:main
build-docker-tag:
<<: *docker
@ -221,6 +234,7 @@ build-docker-tag:
variables:
DOCKER_IMAGE_NAME: framasoft/mobilizon:$CI_COMMIT_TAG
# Packaging app for amd64
package-app:
stage: package
variables: &release-variables
@ -228,16 +242,21 @@ package-app:
script: &release-script
- mix local.hex --force
- mix local.rebar --force
- mix deps.get
- mix phx.digest
- mix deps.get --only-prod
- mix compile
- mix phx.digest.clean --all && \
- mix release --path release/mobilizon
- cd release/mobilizon && ln -s lib/mobilizon-*/priv priv
- cd release/mobilizon && ln -s lib/mobilizon-*/priv priv && cd ../../
- du -sh release/
- 'echo "Artifact: ${APP_ASSET}"'
- tar czf ${APP_ASSET} -C release mobilizon
- du -sh ${APP_ASSET}
only:
- tags@framasoft/mobilizon
artifacts:
expire_in: 30 days
paths:
- release
- ${APP_ASSET}
package-app-dev:
stage: package
@ -248,20 +267,63 @@ package-app-dev:
artifacts:
expire_in: 2 days
paths:
- release
- ${APP_ASSET}
# Packaging app for multi-arch
multi-arch-release:
stage: package
image: docker:stable
variables:
DOCKER_TLS_CERTDIR: "/certs"
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_VERIFY: 1
DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client"
DOCKER_DRIVER: overlay2
APP_ASSET: "${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_${ARCH}.tar.gz"
services:
- docker:stable-dind
cache: {}
before_script:
# Install buildx
- wget https://github.com/docker/buildx/releases/download/v0.6.3/buildx-v0.6.3.linux-amd64
- mkdir -p ~/.docker/cli-plugins/
- mv buildx-v0.6.3.linux-amd64 ~/.docker/cli-plugins/docker-buildx
- chmod a+x ~/.docker/cli-plugins/docker-buildx
# Create env
- docker context create tls-environment
- docker buildx create --use tls-environment
# Install qemu/binfmt
- docker pull tonistiigi/binfmt:latest
- docker run --rm --privileged tonistiigi/binfmt:latest --install all
script:
- docker buildx build --platform linux/${ARCH} --output type=local,dest=releases --build-arg APP_ASSET=${APP_ASSET} -f docker/multiarch/Dockerfile .
- ls -alh releases/mobilizon/
- du -sh releases/mobilizon/${APP_ASSET}
- mv releases/mobilizon/${APP_ASSET} .
tags:
- "privileged"
artifacts:
expire_in: 30 days
paths:
- ${APP_ASSET}
parallel:
matrix:
- ARCH: ["arm", "arm64"]
rules:
- if: '$CI_PROJECT_NAMESPACE != "framasoft"'
when: never
- if: '$CI_PIPELINE_SOURCE == "schedule"'
- if: $CI_COMMIT_TAG
timeout: 3h
# Release
release-upload:
stage: upload
image: framasoft/yakforms-assets-deploy:latest
variables:
APP_ASSET: "${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_${ARCH}.tar.gz"
rules: *tag-rules
script:
- APP_VERSION="${CI_COMMIT_TAG}"
- APP_ASSET="${CI_PROJECT_NAME}_${APP_VERSION}_${ARCH}.tar.gz"
- 'echo "Artifact: ${APP_ASSET}"'
- tar czf ${APP_ASSET} -C release mobilizon
- ls -al ${APP_ASSET}
- eval `ssh-agent -s`
- ssh-add <(echo "${DEPLOYEMENT_KEY}" | base64 --decode -i)
- echo "put -r ${APP_ASSET}" | sftp -o "VerifyHostKeyDNS yes" ${DEPLOYEMENT_USER}@${DEPLOYEMENT_HOST}:public/
@ -270,20 +332,27 @@ release-upload:
when: on_success
paths:
- mobilizon_*.tar.gz
parallel:
matrix:
- ARCH: ["amd64", "arm", "arm64"]
release-create:
stage: deploy
image: registry.gitlab.com/gitlab-org/release-cli:latest
rules: *tag-rules
variables:
APP_ASSET_AMD64: "${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_amd64.tar.gz"
APP_ASSET_ARM: "${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_arm.tar.gz"
APP_ASSET_ARM64: "${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_arm64.tar.gz"
before_script:
- apk --no-cache add gawk sed grep
script: |
APP_VERSION="${CI_COMMIT_TAG}"
APP_ASSET="${CI_PROJECT_NAME}_${APP_VERSION}_${ARCH}.tar.gz"
CHANGELOG=$(awk -v version="$APP_VERSION" '/^## / { printit = $2 == version }; printit' CHANGELOG.md | grep -v "## $APP_VERSION" | sed '1{/^$/d}')
ENDPOINT="https://packages.joinmobilizon.org"
release-cli create --name "$CI_COMMIT_TAG" \
--description "$CHANGELOG" \
--tag-name "$CI_COMMIT_TAG" \
--assets-link "{\"name\":\"${APP_ASSET}\",\"url\":\"${ENDPOINT}/${APP_ASSET}\"}"
--assets-link "{\"name\":\"${APP_ASSET_AMD64}\",\"url\":\"${ENDPOINT}/${APP_ASSET_AMD64}\"}" \
--assets-link "{\"name\":\"${APP_ASSET_ARM}\",\"url\":\"${ENDPOINT}/${APP_ASSET_ARM}\"}" \
--assets-link "{\"name\":\"${APP_ASSET_ARM64}\",\"url\":\"${ENDPOINT}/${APP_ASSET_ARM64}\"}"

View File

@ -8,4 +8,5 @@
73B351E4CB3AF715AD450A085F5E6304
BBACD7F0BACD4A6D3010C26604671692
6D4D4A4821B93BCFAC9CDBB367B34C4B
5674F0D127852889ED0132DC2F442AAB
5674F0D127852889ED0132DC2F442AAB
1600B7206E47F630D94AB54C360906F0

View File

@ -1,10 +1,317 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 2.0.0 - 2021-11-23
Please read the [UPGRADE.md](https://framagit.org/framasoft/mobilizon/-/blob/main/UPGRADE.md#upgrading-from-13-to-20) file as well.
### Added
- Added possibility to follow groups and be notified from new upcoming events
- Export list of participants to CSV, `PDF` and `ODS`
- Allow to set timezone for an event. The timezone is automatically defined from the address if one is defined. If the event timezone is different than the user's current one, a toggle is shown to switch between the two.
- Added initial support for Right To Left languages (such as arabic) and [BiDi](https://en.wikipedia.org/wiki/Bidirectional_text)
- Group followers and members get an notification email by default when a group publishes a new event (subject to activity notification settings)
- Group admins can now approve or deny new memberships
- Build releases in `arm` and `arm64` format in addition to `amd64`
- Build Docker images in `arm` and `arm64` format in addition to `amd64`
- Added possibility to indicate the event is fully online
- Added possibility to search only for online events
- Added possibility to search only in past events
- Detect event, comments and posts languages automatically. Allows setting language
- Allow to change an user's password through the users.modify mix task
- Add instance setting so that only the admin can create groups
- Add instance setting so that only groups can create events
- Added JSON-LD metadata about the event in emails
- Added a quick link to email notification settings at the bottom of emails
- Allow to access Mobilizon with a specific language directly by using `https://instance.tld/lang` where `lang` is a language supported by Mobilizon
- Added organizer actor name (profile or group) in the icalendar export
- Add initial support for federation with Gancio
### Changed
- Multiple UI improvements, including post, event and participation cards, discussions and emails. The « My Events » page was also redesigned to allow showing events from your groups.
- Various accessibility improvements
- Event update notification is send to participants ~30 minutes after the event update, so that successive edits are throttled.
- Event, post and comments titles and content now have expose their detected language in HTML, for improved screen reader experience
- Delete current actor ID as well from local storage when unlogging
- Show a default text for instance contact in default terms text when no instance contact is set
- Only show locatecontrol button in leaflet map when we can do geolocation
- Disable push column in notification settings when push is not available
- Show actual language instead of language code in Users admin view
- Empty old & new passwords fields when successful password change
- Don't link to the group page from admin when actor is suspended
- Warn participants when the event organizer is suspended (and therefore the event cancelled)
- Improve metadata on public page
- Make sure some event action pages (participate remotely or without an account) don't get indexed by search engines
- Only send `Tombstone` element in `Delete` activities, not the whole previous deleted element.
- Make sure `Delete` activity are send correctly to everyone
- Only add address and tags to event icalendar export if they exist
- `master` branch has been renamed to `main`
- Mention following groups on the registration page
- Add missing group name to activity notifications
- Warn while registering and logging when the email contains uppercase characters
- Improve json-ld metadata on event live streams
- Add "eventAttendanceMode" to JSON-ld schema.org event representation
- Improve sending pending participation notifications
- Add "formerType" and "delete" attributes on Tombstones ActivityPub objects representation
- Improve MyEvents page description text
### Removed
- Support for Elixir < 1.12 and OTP < 22
### Fixed
- Fix tags autocomplete
- Fix config onboarding after LDAP initial connexion
- Fix events pagination on tags page
- Fixed deduplicated files from orphan media being deleted as well
- Fix deleting own account
- Fix search returning user profiles instead of only groups
- Fix federating geo coordinates
- Fix an issue with group activity items when moving resources
- Fix an issue with Identity Picker
- Fix an issue with TagInput
- Fix an issue when leaving a group
- Fix admin settings edition
- Fix an issue when showing public page of suspended group
- Removed non existing page (`/about/mobilizon`) from sitemap
- Fix action logs containing group suspension events
- Fixed group physical address not exposed to ActivityPub
- Release front-end files are no longer in duplicate
- Only show datetime timezone toggle on event if the timezone offset is different from our own
- Fix error when determining audience for Discussion when deleting a comment
- Fix a couple of accessibility issues
- Limit to acceptable tags when pasting raw HTML into comment fields on front-end
- Fixed group map display
- Fixed updating group physical address
- Allow group members to access group drafts
- Improve group refreshment workflow
- Fixed date signature generation for federation
- Fixed an issue when duplicating a group event from another profile
- Fixed event metadata not saved on eventcreation
- Use a different pagination parameter for searched events and featured events on search page
- Fixed creating group activities when creating events with some fields
- Move release package at correct path for CI upload
- Fixed event contacts that were not exposed and fetched over federation
- Don't sign fetch when fetching actor for a given signature
- Some various HTTP signatures issues
- Fixed actor AP representation of avatar
- Handle errors when fetching actor pictures
- Fixed sending group events to followers on Mastodon
- Fixed actors avatars and banners being deleted if the same file was also an orphan media
- Fix spacing in organizer picker
- Increase number of close events and follow group events
- Fix accessing user profile in admin section
- Set initial values for some EventMetadata elements, fixing submitting them right away with no value
- Avoid giving an error page if the apollo futureParticipations query is undefined
- Fixed path to exports in production
- Fixed padding below truncated title of event cards
- Fixed exports that weren't enabled in Docker
- Fixed error page when event end date is null
- Fixed event language not being allowed to be null
### Security
- Fixed private messages sent as event replies from Mastodon that were shown publically as public comments. They are now discarded.
### Translations
- Czech
- Gaelic
- German
- Hungarian
- Indonesian
- Norwegian Nynorsk
- Occitan
- Persian
- Portuguese (Brazil)
- Russian
- Slovenian
- Spanish
## 2.0.0-rc.3 - 2021-11-22
This lists changes since 2.0.0-rc.3. Please read the [UPGRADE.md](https://framagit.org/framasoft/mobilizon/-/blob/main/UPGRADE.md#upgrading-from-13-to-20) file as well.
### Fixed
- Fixed path to exports in production
- Fixed padding below truncated title of event cards
- Fixed exports that weren't enabled in Docker
- Fixed error page when event end date is null
## 2.0.0-rc.2 - 2021-11-22
This lists changes since 2.0.0-rc.1. Please read the [UPGRADE.md](https://framagit.org/framasoft/mobilizon/-/blob/main/UPGRADE.md#upgrading-from-13-to-20) file as well.
### Changed
- Improve MyEvents page description text
### Fixed
- Fix spacing in organizer picker
- Increase number of close events and follow group events
- Fix accessing user profile in admin section
- Set initial values for some EventMetadata elements, fixing submitting them right away with no value
- Avoid giving an error page if the apollo futureParticipations query is undefined
### Translations
- German
- Hungarian
## 2.0.0-rc.1 - 2021-11-20
This lists changes since 2.0.0-beta.2. Please read the [UPGRADE.md](https://framagit.org/framasoft/mobilizon/-/blob/main/UPGRADE.md#upgrading-from-13-to-20) file as well.
### Changed
- Mention following groups on the registration page
- Add missing group name to activity notifications
- Warn while registering and logging when the email contains uppercase characters
- Improve json-ld metadata on event live streams
- Add "eventAttendanceMode" to JSON-ld schema.org event representation
- Improve sending pending participation notifications
- Add "formerType" and "delete" attributes on Tombstones ActivityPub objects representation
### Fixed
- Fixed creating group activities when creating events with some fields
- Move release package at correct path for CI upload
- Fixed event contacts that were not exposed and fetched over federation
- Don't sign fetch when fetching actor for a given signature
- Some various HTTP signatures issues
- Fixed actor AP representation of avatar
- Handle errors when fetching actor pictures
- Fixed sending group events to followers on Mastodon
- Fixed actors avatars and banners being deleted if the same file was also an orphan media
### Translations
- Gaelic
- Spanish
## 2.0.0-beta.2 - 2021-11-15
This lists changes since 2.0.0-beta.1. Please read the [UPGRADE.md](https://framagit.org/framasoft/mobilizon/-/blob/main/UPGRADE.md#upgrading-from-13-to-20) file as well.
### Added
- Group followers and members get an notification email by default when a group publishes a new event (subject to activity notification settings)
- Group admins can now approve or deny new memberships
- Added organizer actor name (profile or group) in the icalendar export
- Add initial support for federation with Gancio
### Changed
- Event update notification is send to participants ~30 minutes after the event update, so that successive edits are throttled.
- Event, post and comments titles and content now have expose their detected language in HTML, for improved screen reader experience
### Fixed
- Release front-end files are no longer in duplicate
- Only show datetime timezone toggle on event if the timezone offset is different from our own
- Fix error when determining audience for Discussion when deleting a comment
- Fix a couple of accessibility issues
- Limit to acceptable tags when pasting raw HTML into comment fields on front-end
- Fixed group map display
- Fixed updating group physical address
- Allow group members to access group drafts
- Improve group refreshment workflow
- Fixed date signature generation for federation
- Fixed an issue when duplicating a group event from another profile
- Fixed event metadata not saved on eventcreation
- Use a different pagination parameter for searched events and featured events on search page
### Translations
- Gaelic
- Spanish
## 2.0.0-beta.1 - 2021-11-09
Please read the [UPGRADE.md](https://framagit.org/framasoft/mobilizon/-/blob/main/UPGRADE.md#upgrading-from-13-to-20) file as well.
### Added
- Added possibility to follow groups and be notified from new upcoming events
- Export list of participants to CSV, `PDF` and `ODS`
- Allow to set timezone for an event. The timezone is automatically defined from the address if one is defined. If the event timezone is different than the user's current one, a toggle is shown to switch between the two.
- Added initial support for Right To Left languages (such as arabic) and [BiDi](https://en.wikipedia.org/wiki/Bidirectional_text)
- Build releases in `arm` and `arm64` format in addition to `amd64`
- Build Docker images in `arm` and `arm64` format in addition to `amd64`
- Added possibility to indicate the event is fully online
- Added possibility to search only for online events
- Added possibility to search only in past events
- Detect event, comments and posts languages automatically. Allows setting language
- Allow to change an user's password through the users.modify mix task
- Add instance setting so that only the admin can create groups
- Add instance setting so that only groups can create events
- Added JSON-LD metadata about the event in emails
- Added a quick link to email notification settings at the bottom of emails
- Allow to access Mobilizon with a specific language directly by using `https://instance.tld/lang` where `lang` is a language supported by Mobilizon
### Changed
- Multiple UI improvements, including post, event and participation cards, discussions and emails. The « My Events » page was also redesigned to allow showing events from your groups.
- Various accessibility improvements
- Delete current actor ID as well from local storage when unlogging
- Show a default text for instance contact in default terms text when no instance contact is set
- Only show locatecontrol button in leaflet map when we can do geolocation
- Disable push column in notification settings when push is not available
- Show actual language instead of language code in Users admin view
- Empty old & new passwords fields when successful password change
- Don't link to the group page from admin when actor is suspended
- Warn participants when the event organizer is suspended (and therefore the event cancelled)
- Improve metadata on public page
- Make sure some event action pages (participate remotely or without an account) don't get indexed by search engines
- Only send `Tombstone` element in `Delete` activities, not the whole previous deleted element.
- Make sure `Delete` activity are send correctly to everyone
- Only add address and tags to event icalendar export if they exist
- `master` branch has been renamed to `main`
### Removed
- Support for Elixir < 1.12 and OTP < 22
### Fixed
- Fix tags autocomplete
- Fix config onboarding after LDAP initial connexion
- Fix events pagination on tags page
- Fixed deduplicated files from orphan media being deleted as well
- Fix deleting own account
- Fix search returning user profiles instead of only groups
- Fix federating geo coordinates
- Fix an issue with group activity items when moving resources
- Fix an issue with Identity Picker
- Fix an issue with TagInput
- Fix an issue when leaving a group
- Fix admin settings edition
- Fix an issue when showing public page of suspended group
- Removed non existing page (`/about/mobilizon`) from sitemap
- Fix action logs containing group suspension events
- Fixed group physical address not exposed to ActivityPub
### Security
- Fixed private messages sent as event replies from Mastodon that were shown publically as public comments. They are now discarded.
### Translations
- Czech
- Gaelic
- German
- Indonesian
- Norwegian Nynorsk
- Occitan
- Persian
- Portuguese (Brazil)
- Russian
- Slovenian
- Spanish
## 1.3.2 - 2021-08-23
### Fixed
@ -714,23 +1021,23 @@ This version introduces a new way to install and host Mobilizon : Elixir releas
### Special operations
- **Reattach media files to their entity.**
* **Reattach media files to their entity.**
When media files were uploaded and added in events and posts bodies, they were only attached to the profile that uploaded them, not to the event or post. This task attaches them back to their entity so that the command to clean orphan media files doesn't remove them.
- Source install
* Source install
`MIX_ENV=prod mix mobilizon.maintenance.fix_unattached_media_in_body`
- Docker
* Docker
`docker-compose exec mobilizon mobilizon_ctl maintenance.fix_unattached_media_in_body`
- **Refresh remote profiles to save avatars locally**
* **Refresh remote profiles to save avatars locally**
Profile avatars and banners were previously only proxified and cached. Now we save them locally. Refreshing all remote actors will save profile media locally instead.
- Source install
* Source install
`MIX_ENV=prod mix mobilizon.actors.refresh --all`
- Docker
* Docker
`docker-compose exec mobilizon mobilizon_ctl actors.refresh --all`
- **imagemagick and webp are now a required dependency** to build Mobilizon.
* **imagemagick and webp are now a required dependency** to build Mobilizon.
Optimized versions of Mobilizon's pictures are now produced during front-end build.
See [the documentation](https://docs.joinmobilizon.org/administration/dependencies/#misc) to make sure these dependencies are installed.
@ -773,7 +1080,7 @@ This version introduces a new way to install and host Mobilizon : Elixir releas
- Fixed error message not showing up when you are already an anonymous participant for an event
- Fixed error message not showing up when you pick an username already in user for a new profile or a group
- Fixed translations not fallbacking properly to english when not found
-
-
### Security
@ -782,7 +1089,6 @@ This version introduces a new way to install and host Mobilizon : Elixir releas
### Translations
Updated translations:
- Catalan
- Dutch
- English
@ -955,21 +1261,20 @@ Updated translations:
### Special operations
- We added `application/ld+json` as acceptable MIME type for ActivityPub requests, so you'll need to recompile the `mime` library we use before recompiling Mobilizon:
* We added `application/ld+json` as acceptable MIME type for ActivityPub requests, so you'll need to recompile the `mime` library we use before recompiling Mobilizon:
```
MIX_ENV=prod mix deps.clean mime --build
```
```
MIX_ENV=prod mix deps.clean mime --build
```
* The [nginx configuration](https://framagit.org/framasoft/mobilizon/-/blob/main/support/nginx/mobilizon.conf) has been changed with improvements and support for custom error pages.
- The [nginx configuration](https://framagit.org/framasoft/mobilizon/-/blob/master/support/nginx/mobilizon.conf) has been changed with improvements and support for custom error pages.
- The cmake dependency has been added (see [our documentation](https://docs.joinmobilizon.org/administration/dependencies/#basic-tools))
* The cmake dependency has been added (see [our documentation](https://docs.joinmobilizon.org/administration/dependencies/#basic-tools))
### Added
- Possibility to login using LDAP
- Possibility to login using OAuth providers
- Enabled group features in production mode
- Enabled group features in production mode
- including posts (that can be public, unlisted, or restricted to your group members)
- resources (collections of links, with folders, accessible to your group members)
- discussions (group private and organized chats)
@ -993,22 +1298,20 @@ Updated translations:
### Security
- Fix group settings being accessible and editable by non-group-admins (thx @pigpig for reporting this responsibly)
- Fix events being editable by profiles without permissions (thx @pigpig for reporting this responsibly)
- Fix events being editable by profiles without permissions (thx @pigpig for reporting this responsibly)
## [1.0.0-beta.3] - 2020-06-24
### Special operations
Config has moved from `.env` files to a more traditional way to handle things in the Elixir world, with `.exs` files.
To migrate existing configuration, you can simply run `mix mobilizon.instance gen` and fill in the adequate values previously in `.env` files (you don't need to perform the operations to create the database).
A minimal file template [is available](https://framagit.org/framasoft/mobilizon/blob/master/priv/templates/config.template.eex) to check for missing configuration.
A minimal file template [is available](https://framagit.org/framasoft/mobilizon/blob/main/priv/templates/config.template.eex) to check for missing configuration.
Also make sure to remove the `EnvironmentFile=` line from the systemd service and set `Environment=MIX_ENV=prod` instead. See [the updated file](https://framagit.org/framasoft/mobilizon/blob/master/support/systemd/mobilizon.service).
Also make sure to remove the `EnvironmentFile=` line from the systemd service and set `Environment=MIX_ENV=prod` instead. See [the updated file](https://framagit.org/framasoft/mobilizon/blob/main/support/systemd/mobilizon.service).
### Added
- Possibility to participate to an event without an account (confirmation through email required)
- Possibility to participate to a remote event (being redirected by providing federated identity)
- Possibility to add a note as a participant when event participation is manually validated (required when participating without an account)
@ -1025,7 +1328,6 @@ Also make sure to remove the `EnvironmentFile=` line from the systemd service an
- Allow user to change language
### Changed
- Configuration handling (see above)
- Improved a bit color theme
- Signature validation also now checks if `Date` header has acceptable values
@ -1036,7 +1338,6 @@ Also make sure to remove the `EnvironmentFile=` line from the systemd service an
- Improved public event page
### Fixed
- Fixed URL search
- Fixed content accessed through URL search being public
- Fix event links in some emails
@ -1044,21 +1345,17 @@ Also make sure to remove the `EnvironmentFile=` line from the systemd service an
## [1.0.0-beta.2] - 2019-12-18
### Special operations
These two operations couldn't be handled during migrations.
They are optional, but you won't be able to search or get participant stats on existing events if they are not executed.
These commands will be removed in Mobilizon 1.0.0-beta.3.
In order to populate search index for existing events, you need to run the following command (with prod environment):
- `mix mobilizon.setup_search`
* `mix mobilizon.setup_search`
In order to move participant stats to the event table for existing events, you need to run the following command (with prod environment):
- `mix mobilizon.move_participant_stats`
* `mix mobilizon.move_participant_stats`
### Added
- Federation is active
- Added an interface for admins to view and manage instance followers and followings
- Ability to comment below events
@ -1083,7 +1380,6 @@ In order to move participant stats to the event table for existing events, you n
- Upgraded frontend and backend dependencies
### Changed
- Move participant stats to event table **(read special instructions above)**
- Limit length (20 characters) and number (10) of tags allowed
- Added some backend changes and validation for field length
@ -1097,7 +1393,6 @@ In order to move participant stats to the event table for existing events, you n
- Also consider the PeerTube `CommentsEnabled` property to know if you can reply to an event
### Fixed
- Fix event URL validation and check if hostname is correct before showing it
- Fix participations stats on the MyEvents page
- Fix event description lists margin
@ -1127,11 +1422,8 @@ In order to move participant stats to the event table for existing events, you n
- Fixed event HTML representation when `GET` request has no `Accept` header
### Security
- Sanitize event title to avoid XSS
## [1.0.0-beta.1] - 2019-10-15
### Added
- Initial release

View File

@ -1,6 +1,6 @@
FROM elixir:alpine
RUN apk add --no-cache inotify-tools postgresql-client yarn file make gcc libc-dev argon2 imagemagick cmake build-base libwebp-tools bash ncurses git
RUN apk add --no-cache inotify-tools postgresql-client yarn file make gcc libc-dev argon2 imagemagick cmake build-base libwebp-tools bash ncurses git python3
RUN mix local.hex --force && mix local.rebar --force

View File

@ -1,5 +1,5 @@
init:
@bash docker/message.sh "start"
@bash docker/message.sh "Start"
make start
setup: stop
@ -10,16 +10,18 @@ migrate:
logs:
docker-compose logs -f
start: stop
@bash docker/message.sh "starting Mobilizon with docker"
@bash docker/message.sh "Starting Mobilizon with Docker"
docker-compose up -d api
@bash docker/message.sh "Docker server started."
@bash docker/message.sh "Docker server started"
stop:
@bash docker/message.sh "stopping Mobilizon"
@bash docker/message.sh "Stopping Mobilizon"
docker-compose down
@bash docker/message.sh "stopped"
@bash docker/message.sh "Mobilizon is stopped"
test: stop
@bash docker/message.sh "Running tests"
docker-compose -f docker-compose.yml -f docker-compose.test.yml run api mix test
@bash docker/message.sh "Tests runned"
docker-compose -f docker-compose.yml -f docker-compose.test.yml run api mix test $(only)
@bash docker/message.sh "Done running tests"
format:
docker-compose run --rm api bash -c "mix format && mix credo --strict"
@bash docker/message.sh "Code is now ready to commit :)"
target: init

View File

@ -1,3 +1,91 @@
# Upgrading from 1.3 to 2.0
Requirements dependencies depend on the way Mobilizon is installed.
## New Elixir version requirement
### Docker and Release install
You are already using latest Elixir version in the release tarball and Docker images.
### Source install
**Elixir 1.12 and Erlang OTP 22 are now required**. If your distribution or the repositories from Erlang Solutions don't provide these versions, you need to uninstall the current versions and install [Elixir](https://github.com/asdf-vm/asdf-elixir) through the [ASDF tool](https://asdf-vm.com/).
## Geographic timezone data
Mobilizon 2.0 uses data based on [timezone-boundary-builder](https://github.com/evansiroky/timezone-boundary-builder) (which is based itself on OpenStreetMap data) to determine the timezone of an event automatically, based on it's geocoordinates. However, this needs ~700Mio of disk, so we don't redistribute data directly, depending on the case. It's possible to skip this part, but users will need to manually pick the timezone for every event they created when it has a different timezone from their own.
### Docker install
The geographic timezone data is already bundled into the image, you have nothing to do.
### Release install
In order to keep the release tarballs light, the geographic timezone data is not bundled directly. You need to download the data :
* either raw from Github, but **requires an extra ~1Gio of memory** to process the data
```sh
sudo -u mobilizon mkdir /var/lib/mobilizon/timezones
sudo -u mobilizon ./bin/mobilizon_ctl tz_world.update
```
* either already processed from our own distribution server
```sh
sudo -u mobilizon mkdir /var/lib/mobilizon/timezones
sudo -u mobilizon curl -L 'https://packages.joinmobilizon.org/tz_world/timezones-geodata.dets' -o /var/lib/mobilizon/timezones/timezones-geodata.dets
```
In both cases, ~700Mio of disk will be used. You may use the following configuration to specify where the data is expected:
```elixir
config :tz_world, data_dir: "/some/place"
```
### Source install
You need to download the data :
* either raw from Github, but **requires an extra ~1Gio of memory** to process the data
```sh
sudo -u mobilizon mkdir /var/lib/mobilizon/timezones
sudo -u mobilizon mix mobilizon.tz_world.update
```
* either already processed from our own distribution server
```sh
sudo -u mobilizon mkdir /var/lib/mobilizon/timezones
sudo -u mobilizon curl -L 'https://packages.joinmobilizon.org/tz_world/timezones-geodata.dets' -o /var/lib/mobilizon/timezones/timezones-geodata.dets
```
In both cases, ~700Mio of disk will be used. You may use the following configuration to specify where the data is expected:
```elixir
config :tz_world, data_dir: "/some/place"
```
## Exports folder
Create the folder for default CSV export:
```sh
sudo -u mobilizon mkdir -p /var/lib/mobilizon/uploads/exports/csv
```
This path can be configured, see [the dedicated docs page about this](https://docs.joinmobilizon.org/administration/configure/exports/).
Files in this folder are temporary and are cleaned once an hour.
## New optional dependencies
These are optional, installing them will allow Mobilizon to export to PDF and ODS as well. Mobilizon 2.0 allows to export the participant list, but more is planned.
### Docker
Everything is included in our Docker image.
### Release and source install
New optional Python dependencies:
* `Python` >= 3.6
* `weasyprint` for PDF export (with [a few extra dependencies](https://doc.courtbouillon.org/weasyprint/stable/first_steps.html))
* `pyexcel-ods3` for ODS export (no extra dependencies)
Both can be installed through pip. You need to enable and configure exports for PDF and ODS in the configuration afterwards. Read [the dedicated docs page about this](https://docs.joinmobilizon.org/administration/configure/exports/).
# Upgrading from 1.0 to 1.1
The 1.1 version of Mobilizon brings Elixir releases support. An Elixir release is a self-contained directory that contains all of Mobilizon's code (front-end and backend), it's dependencies, as well as the Erlang Virtual Machine and runtime (only the parts you need). As long as the release has been assembled on the same OS and architecture, it can be deploy and run straight away. [Read more about releases](https://elixir-lang.org/getting-started/mix-otp/config-and-releases.html#releases).

View File

@ -40,9 +40,11 @@ config :mobilizon, :instance,
email_reply_to: "noreply@localhost"
config :mobilizon, :groups, enabled: true
config :mobilizon, :events, creation: true
config :mobilizon, :restrictions, only_admin_can_create_groups: false
config :mobilizon, :restrictions, only_groups_can_create_events: false
# Configures the endpoint
config :mobilizon, Mobilizon.Web.Endpoint,
url: [
@ -86,6 +88,10 @@ config :mobilizon, Mobilizon.Web.Upload,
config :mobilizon, Mobilizon.Web.Upload.Uploader.Local, uploads: "/var/lib/mobilizon/uploads"
config :tz_world, data_dir: "/var/lib/mobilizon/timezones"
config :mobilizon, Timex.Gettext, default_locale: "en"
config :mobilizon, :media_proxy,
enabled: true,
proxy_opts: [
@ -179,6 +185,8 @@ config :phoenix, :filter_parameters, ["password", "token"]
config :absinthe, schema: Mobilizon.GraphQL.Schema
config :absinthe, Absinthe.Logger, filter_variables: ["token", "password", "secret"]
config :mobilizon, Mobilizon.Web.Gettext, one_module_per_locale: true
config :ex_cldr,
default_locale: "en",
default_backend: Mobilizon.Cldr
@ -189,7 +197,9 @@ config :http_signatures,
config :mobilizon, :cldr,
locales: [
"fr",
"en"
"en",
"ru",
"ar"
]
config :mobilizon, :activitypub,
@ -282,6 +292,7 @@ config :mobilizon, Oban,
{"17 4 * * *", Mobilizon.Service.Workers.RefreshGroups, queue: :background},
{"@hourly", Mobilizon.Service.Workers.CleanOrphanMediaWorker, queue: :background},
{"@hourly", Mobilizon.Service.Workers.CleanUnconfirmedUsersWorker, queue: :background},
{"@hourly", Mobilizon.Service.Workers.ExportCleanerWorker, queue: :background},
{"@hourly", Mobilizon.Service.Workers.SendActivityRecapWorker, queue: :notifications},
{"@daily", Mobilizon.Service.Workers.CleanOldActivityWorker, queue: :background}
]},
@ -317,6 +328,12 @@ config :mobilizon, Mobilizon.Service.Notifier.Email, enabled: true
config :mobilizon, Mobilizon.Service.Notifier.Push, enabled: true
config :mobilizon, :exports,
path: "/var/lib/mobilizon/uploads/exports",
formats: [
Mobilizon.Service.Export.Participants.CSV
]
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{config_env()}.exs"

View File

@ -58,6 +58,8 @@ config :logger, :console, format: "[$level] $message\n", level: :debug
config :mobilizon, Mobilizon.Service.Geospatial, service: Mobilizon.Service.Geospatial.Nominatim
config :mobilizon, Mobilizon.Web.Gettext, allowed_locales: ["fr", "en", "ru", "ar"]
# Set a higher stacktrace during development. Avoid configuring such
# in production as building large stacktraces may be expensive.
config :phoenix, :stacktrace_depth, 20
@ -92,6 +94,10 @@ config :mobilizon, Mobilizon.Web.Auth.Guardian,
config :mobilizon, Mobilizon.Web.Upload.Uploader.Local, uploads: "uploads"
config :mobilizon, :exports, path: "uploads/exports"
config :tz_world, data_dir: "_build/dev/lib/tz_world/priv"
config :mobilizon, :anonymous,
reports: [
allowed: true

View File

@ -33,9 +33,6 @@ config :mobilizon, :instance,
email_from: System.get_env("MOBILIZON_INSTANCE_EMAIL", "noreply@mobilizon.lan"),
email_reply_to: System.get_env("MOBILIZON_REPLY_EMAIL", "noreply@mobilizon.lan")
config :mobilizon, Mobilizon.Web.Upload.Uploader.Local,
uploads: System.get_env("MOBILIZON_UPLOADS", "/app/uploads")
config :mobilizon, Mobilizon.Storage.Repo,
adapter: Ecto.Adapters.Postgres,
username: System.get_env("MOBILIZON_DATABASE_USERNAME", "username"),
@ -68,4 +65,16 @@ config :geolix,
}
]
config :mobilizon, Mobilizon.Web.Upload.Uploader.Local, uploads: "/var/lib/mobilizon/uploads"
config :mobilizon, Mobilizon.Web.Upload.Uploader.Local,
uploads: System.get_env("MOBILIZON_UPLOADS", "/var/lib/mobilizon/uploads")
config :mobilizon, :exports,
path: System.get_env("MOBILIZON_UPLOADS_EXPORTS", "/var/lib/mobilizon/uploads/exports"),
formats: [
Mobilizon.Service.Export.Participants.CSV,
Mobilizon.Service.Export.Participants.PDF,
Mobilizon.Service.Export.Participants.ODS
]
config :tz_world,
data_dir: System.get_env("MOBILIZON_TIMEZONES_DIR", "/var/lib/mobilizon/timezones")

View File

@ -37,12 +37,3 @@ config :mobilizon, :cldr,
"ru",
"sv"
]
cond do
System.get_env("INSTANCE_CONFIG") &&
File.exists?("./config/#{System.get_env("INSTANCE_CONFIG")}") ->
import_config System.get_env("INSTANCE_CONFIG")
true ->
:ok
end

View File

@ -60,15 +60,22 @@ config :mobilizon, Mobilizon.Web.Upload, filters: [], link_name: false
config :mobilizon, Mobilizon.Web.Upload.Uploader.Local, uploads: "test/uploads"
config :exvcr,
vcr_cassette_library_dir: "test/fixtures/vcr_cassettes"
config :mobilizon, :exports, path: "test/uploads/exports"
config :tz_world, data_dir: "_build/test/lib/tz_world/priv"
config :tesla, Mobilizon.Service.HTTP.ActivityPub,
adapter: Mobilizon.Service.HTTP.ActivityPub.Mock
config :tesla, Mobilizon.Service.HTTP.WebfingerClient,
adapter: Mobilizon.Service.HTTP.WebfingerClient.Mock
config :tesla, Mobilizon.Service.HTTP.GeospatialClient,
adapter: Mobilizon.Service.HTTP.GeospatialClient.Mock
config :tesla, Mobilizon.Service.HTTP.HostMetaClient,
adapter: Mobilizon.Service.HTTP.HostMetaClient.Mock
config :mobilizon, Mobilizon.Service.Geospatial, service: Mobilizon.Service.Geospatial.Mock
config :mobilizon, Oban, queues: false, plugins: false
@ -77,6 +84,8 @@ config :mobilizon, Mobilizon.Web.Auth.Guardian, secret_key: "some secret"
config :mobilizon, :activitypub, sign_object_fetches: false
config :mobilizon, Mobilizon.Web.Gettext, allowed_locales: ["fr", "en", "es", "ru"]
config :junit_formatter, report_dir: "."
if System.get_env("DOCKER", "false") == "false" && File.exists?("./config/test.secret.exs") do

View File

@ -1,4 +1,4 @@
version: "3"
version: "3.2"
services:
postgres:

View File

@ -0,0 +1,44 @@
FROM elixir as build
SHELL ["/bin/bash", "-c"]
ENV MIX_ENV prod
# ENV LANG en_US.UTF-8
ARG APP_ASSET
# Set the right versions
ENV ELIXIR_VERSION latest
ENV ERLANG_VERSION latest
ENV NODE_VERSION 16
# Install system dependencies
RUN apt-get update -yq && apt-get install -yq build-essential cmake postgresql-client git curl gnupg unzip exiftool webp imagemagick gifsicle
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# # Install Node & yarn
# RUN curl -sL https://deb.nodesource.com/setup_16.x | bash && apt-get install nodejs -yq
# RUN npm install -g yarn
# Install build tools
RUN source /root/.bashrc && \
mix local.rebar --force && \
mix local.hex -if-missing --force
RUN mkdir /mobilizon
COPY ./ /mobilizon
WORKDIR /mobilizon
# # Build front-end
# RUN yarn --cwd "js" install --frozen-lockfile
# RUN yarn --cwd "js" run build
# Elixir release
RUN source /root/.bashrc && \
mix deps.get --only prod && \
mix compile && \
mix phx.digest.clean --all && \
mix release --path release/mobilizon && \
cd release/mobilizon && \
ln -s lib/mobilizon-*/priv priv && \
cd ../../
# Make a release archive
RUN tar -zcf /mobilizon/${APP_ASSET} -C release mobilizon

View File

@ -0,0 +1 @@
Contains the Dockerfile used to generate multi-arch Elixir releases

View File

@ -4,7 +4,11 @@ FROM node:16-alpine as assets
RUN apk add --no-cache python3 build-base libwebp-tools bash imagemagick ncurses
WORKDIR /build
COPY js .
RUN yarn install \
ENV CYPRESS_INSTALL_BINARY 0
# Network timeout because it's slow when cross-compiling
RUN yarn install --network-timeout 100000 \
&& yarn run build
# Then, build the application binary
@ -26,7 +30,7 @@ COPY rel ./rel
COPY support ./support
COPY --from=assets ./priv/static ./priv/static
RUN mix phx.digest \
RUN mix phx.digest.clean --all \
&& mix release
# Finally setup the app
@ -45,9 +49,11 @@ LABEL org.opencontainers.image.title="mobilizon" \
org.opencontainers.image.revision=$VCS_REF \
org.opencontainers.image.created=$BUILD_DATE
RUN apk add --no-cache openssl ca-certificates ncurses-libs file postgresql-client libgcc libstdc++ imagemagick
RUN apk add --no-cache openssl ca-certificates ncurses-libs file postgresql-client libgcc libstdc++ imagemagick python3 py3-pip py3-pillow py3-cffi py3-brotli gcc musl-dev python3-dev pango libxslt-dev
RUN pip install weasyprint pyexcel-ods3
RUN mkdir -p /app/uploads && chown nobody:nobody /app/uploads
RUN mkdir -p /var/lib/mobilizon/uploads && chown nobody:nobody /var/lib/mobilizon/uploads
RUN mkdir -p /var/lib/mobilizon/timezones && chown nobody:nobody /var/lib/mobilizon/timezones
RUN mkdir -p /etc/mobilizon && chown nobody:nobody /etc/mobilizon
USER nobody

View File

@ -1,10 +1,15 @@
FROM elixir:latest
LABEL maintainer="Thomas Citharel <tcit@tcit.fr>"
ENV REFRESHED_AT=2021-06-07
RUN apt-get update -yq && apt-get install -yq build-essential inotify-tools postgresql-client git curl gnupg xvfb libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 cmake exiftool
ENV REFRESHED_AT=2021-10-04
RUN apt-get update -yq && apt-get install -yq build-essential inotify-tools postgresql-client git curl gnupg xvfb libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 cmake exiftool python3-pip python3-setuptools
RUN curl -sL https://deb.nodesource.com/setup_16.x | bash && apt-get install nodejs -yq
RUN npm install -g yarn wait-on
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
RUN mix local.hex --force && mix local.rebar --force
# Weasyprint 53 requires pango >= 1.44.0, which is not available in Stretch.
# TODO: Remove the version requirement when elixir:latest is based on Bullseye
# https://github.com/erlang/docker-erlang-otp/issues/362
# https://github.com/Kozea/WeasyPrint/issues/1384
RUN pip3 install -Iv weasyprint==52 pyexcel_ods3
RUN curl https://dbip.mirror.framasoft.org/files/dbip-city-lite-latest.mmdb --output GeoLite2-City.mmdb -s && mkdir -p /usr/share/GeoIP && mv GeoLite2-City.mmdb /usr/share/GeoIP/

View File

@ -1,28 +0,0 @@
# We build Elixir manually to have the oldest acceptable version of OTP
FROM erlang:21
LABEL maintainer="Thomas Citharel <tcit@tcit.fr>"
# elixir expects utf8.
ENV ELIXIR_VERSION="v1.11.4" \
LANG=C.UTF-8
RUN set -xe \
&& ELIXIR_DOWNLOAD_URL="https://github.com/elixir-lang/elixir/archive/${ELIXIR_VERSION}.tar.gz" \
&& ELIXIR_DOWNLOAD_SHA256="85c7118a0db6007507313db5bddf370216d9394ed7911fe80f21e2fbf7f54d29" \
&& curl -fSL -o elixir-src.tar.gz $ELIXIR_DOWNLOAD_URL \
&& echo "$ELIXIR_DOWNLOAD_SHA256 elixir-src.tar.gz" | sha256sum -c - \
&& mkdir -p /usr/local/src/elixir \
&& tar -xzC /usr/local/src/elixir --strip-components=1 -f elixir-src.tar.gz \
&& rm elixir-src.tar.gz \
&& cd /usr/local/src/elixir \
&& make install clean
CMD ["iex"]
ENV REFRESHED_AT=2021-06-07
RUN apt-get update -yq && apt-get install -yq build-essential inotify-tools postgresql-client git curl gnupg xvfb libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 cmake exiftool
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash && apt-get install nodejs -yq
RUN npm install -g yarn wait-on
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
RUN mix local.hex --force && mix local.rebar --force
RUN curl https://dbip.mirror.framasoft.org/files/dbip-city-lite-latest.mmdb --output GeoLite2-City.mmdb -s && mkdir -p /usr/share/GeoIP && mv GeoLite2-City.mmdb /usr/share/GeoIP/

1
docker/tests/README.md Normal file
View File

@ -0,0 +1 @@
Contains the Dockerfile for the image used to run the tests

View File

@ -9,8 +9,7 @@ module.exports = {
"plugin:vue/essential",
"eslint:recommended",
"@vue/typescript/recommended",
"@vue/prettier",
"@vue/prettier/@typescript-eslint",
"plugin:prettier/recommended",
],
plugins: ["prettier"],

View File

@ -5,79 +5,41 @@
"kind": "INTERFACE",
"name": "ActionLogObject",
"possibleTypes": [
{
"name": "Comment"
},
{
"name": "Event"
},
{
"name": "Person"
},
{
"name": "Report"
},
{
"name": "ReportNote"
},
{
"name": "User"
}
{ "name": "Comment" },
{ "name": "Event" },
{ "name": "Group" },
{ "name": "Person" },
{ "name": "Report" },
{ "name": "ReportNote" },
{ "name": "User" }
]
},
{
"kind": "INTERFACE",
"name": "ActivityObject",
"possibleTypes": [
{
"name": "Comment"
},
{
"name": "Discussion"
},
{
"name": "Event"
},
{
"name": "Group"
},
{
"name": "Member"
},
{
"name": "Post"
},
{
"name": "Resource"
}
{ "name": "Comment" },
{ "name": "Discussion" },
{ "name": "Event" },
{ "name": "Group" },
{ "name": "Member" },
{ "name": "Post" },
{ "name": "Resource" }
]
},
{
"kind": "INTERFACE",
"name": "Actor",
"possibleTypes": [
{
"name": "Person"
},
{
"name": "Group"
},
{
"name": "Application"
}
{ "name": "Application" },
{ "name": "Group" },
{ "name": "Person" }
]
},
{
"kind": "INTERFACE",
"name": "Interactable",
"possibleTypes": [
{
"name": "Event"
},
{
"name": "Group"
}
]
"possibleTypes": [{ "name": "Event" }, { "name": "Group" }]
}
]
}

View File

@ -1,11 +1,11 @@
{
"name": "mobilizon",
"version": "1.3.2",
"version": "2.0.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "yarn run build:assets && yarn run build:pictures",
"test:unit": "LANG=en_US.UTF-8 LANGUAGE=en_US:en LC_ALL=en_US.UTF-8 vue-cli-service test:unit",
"test:unit": "LANG=en_US.UTF-8 LANGUAGE=en_US:en LC_ALL=en_US.UTF-8 TZ=UTC vue-cli-service test:unit",
"test:e2e": "vue-cli-service test:e2e",
"lint": "vue-cli-service lint",
"build:assets": "vue-cli-service build",
@ -15,7 +15,7 @@
"@absinthe/socket": "^0.2.1",
"@absinthe/socket-apollo-link": "^0.2.1",
"@apollo/client": "^3.3.16",
"@mdi/font": "^5.0.45",
"@mdi/font": "^6.1.95",
"@tiptap/core": "^2.0.0-beta.41",
"@tiptap/extension-blockquote": "^2.0.0-beta.6",
"@tiptap/extension-bubble-menu": "^2.0.0-beta.9",
@ -29,6 +29,8 @@
"@tiptap/extension-underline": "^2.0.0-beta.7",
"@tiptap/starter-kit": "^2.0.0-beta.37",
"@tiptap/vue-2": "^2.0.0-beta.21",
"@vue-a11y/announcer": "^2.1.0",
"@vue-a11y/skip-to": "^2.1.2",
"@vue/apollo-option": "4.0.0-alpha.11",
"apollo-absinthe-upload-link": "^1.5.0",
"blurhash": "^1.1.3",
@ -36,7 +38,8 @@
"bulma-divider": "^0.2.0",
"core-js": "^3.6.4",
"date-fns": "^2.16.0",
"graphql": "^15.0.0",
"date-fns-tz": "^1.1.6",
"graphql": "^16.0.0",
"graphql-tag": "^2.10.3",
"intersection-observer": "^0.12.0",
"jwt-decode": "^3.1.2",
@ -45,8 +48,9 @@
"lodash": "^4.17.11",
"ngeohash": "^0.6.3",
"p-debounce": "^4.0.0",
"phoenix": "^1.4.11",
"phoenix": "^1.6",
"register-service-worker": "^1.7.2",
"sanitize-html": "^2.5.3",
"tippy.js": "^6.2.3",
"unfetch": "^4.2.0",
"v-tooltip": "^2.1.3",
@ -61,49 +65,50 @@
"vuedraggable": "^2.24.3"
},
"devDependencies": {
"@types/jest": "^26.0.18",
"@types/jest": "^27.0.2",
"@types/leaflet": "^1.5.2",
"@types/leaflet.locatecontrol": "^0.60.7",
"@types/lodash": "^4.14.141",
"@types/ngeohash": "^0.6.2",
"@types/phoenix": "^1.5.2",
"@types/prosemirror-inputrules": "^1.0.2",
"@types/prosemirror-model": "^1.7.2",
"@types/prosemirror-state": "^1.2.4",
"@types/prosemirror-view": "^1.11.4",
"@typescript-eslint/eslint-plugin": "^4.18.0",
"@typescript-eslint/parser": "^4.18.0",
"@vue/cli-plugin-babel": "~5.0.0-beta.3",
"@vue/cli-plugin-e2e-cypress": "~5.0.0-beta.3",
"@vue/cli-plugin-eslint": "~5.0.0-beta.3",
"@vue/cli-plugin-pwa": "~5.0.0-beta.3",
"@vue/cli-plugin-router": "~5.0.0-beta.3",
"@vue/cli-plugin-typescript": "~5.0.0-beta.3",
"@vue/cli-plugin-unit-jest": "~5.0.0-beta.3",
"@vue/cli-service": "~5.0.0-beta.3",
"@vue/eslint-config-prettier": "^6.0.0",
"@vue/eslint-config-typescript": "^7.0.0",
"@types/sanitize-html": "^2.5.0",
"@typescript-eslint/eslint-plugin": "^5.3.0",
"@typescript-eslint/parser": "^5.3.0",
"@vue/cli-plugin-babel": "~5.0.0-rc.0",
"@vue/cli-plugin-e2e-cypress": "~5.0.0-rc.0",
"@vue/cli-plugin-eslint": "~5.0.0-rc.0",
"@vue/cli-plugin-pwa": "~5.0.0-rc.0",
"@vue/cli-plugin-router": "~5.0.0-rc.0",
"@vue/cli-plugin-typescript": "~5.0.0-rc.0",
"@vue/cli-plugin-unit-jest": "~5.0.0-rc.0",
"@vue/cli-service": "~5.0.0-rc.0",
"@vue/eslint-config-typescript": "^9.0.0",
"@vue/test-utils": "^1.1.0",
"eslint": "^7.20.0",
"@vue/vue2-jest": "^27.0.0-alpha.3",
"@vue/vue3-jest": "^27.0.0-alpha.1",
"cypress": "^8.3.0",
"eslint": "^8.2.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-cypress": "^2.10.3",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-vue": "^7.6.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.0.3",
"flush-promises": "^1.0.2",
"jest": "^26.6.3",
"jest-junit": "^12.0.0",
"jest": "^27.1.0",
"jest-junit": "^13.0.0",
"mock-apollo-client": "^1.1.0",
"prettier": "^2.2.1",
"prettier-eslint": "^13.0.0",
"sass": "^1.34.1",
"sass-loader": "^12.0.0",
"ts-jest": "^26.5.3",
"typescript": "~4.1.5",
"vue-i18n-extract": "^1.0.2",
"vue-jest": "^4.0.1",
"ts-jest": "27",
"typescript": "~4.4.3",
"vue-i18n-extract": "^2.0.4",
"vue-template-compiler": "^2.6.11",
"webpack-cli": "^4.7.0"
},
"resolutions": {
"webpack": "5.44.0"
}
}

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" dir="auto">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />

View File

@ -1,5 +1,7 @@
<template>
<div id="mobilizon">
<VueAnnouncer />
<VueSkipTo to="#main" :label="$t('Skip to main content')" />
<NavBar />
<div v-if="config && config.demoMode">
<b-message
@ -7,7 +9,7 @@
type="is-danger"
:title="$t('Warning').toLocaleUpperCase()"
closable
aria-close-label="Close"
:aria-close-label="$t('Close')"
>
<p>
{{ $t("This is a demonstration site to test Mobilizon.") }}
@ -22,9 +24,9 @@
</div>
<error v-if="error" :error="error" />
<main v-else>
<main id="main" v-else>
<transition name="fade" mode="out-in">
<router-view />
<router-view ref="routerView" />
</transition>
</main>
<mobilizon-footer />
@ -32,7 +34,7 @@
</template>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { Component, Ref, Vue, Watch } from "vue-property-decorator";
import NavBar from "./components/NavBar.vue";
import {
AUTH_ACCESS_TOKEN,
@ -52,6 +54,7 @@ import { IConfig } from "./types/config.model";
import { ICurrentUser } from "./types/current-user.model";
import jwt_decode, { JwtPayload } from "jwt-decode";
import { refreshAccessToken } from "./apollo/utils";
import { Route } from "vue-router";
@Component({
apollo: {
@ -82,6 +85,8 @@ export default class App extends Vue {
interval: number | undefined = undefined;
@Ref("routerView") routerView!: Vue;
async created(): Promise<void> {
if (await this.initializeCurrentUser()) {
await initializeCurrentActor(this.$apollo.provider.defaultClient);
@ -197,6 +202,41 @@ export default class App extends Vue {
clearInterval(this.interval);
this.interval = undefined;
}
@Watch("$route", { immediate: true })
updateAnnouncement(route: Route): void {
const pageTitle = this.extractPageTitleFromRoute(route);
if (pageTitle) {
this.$announcer.polite(
this.$t("Navigated to {pageTitle}", {
pageTitle,
}) as string
);
}
// Set the focus to the router view
// https://marcus.io/blog/accessible-routing-vuejs
setTimeout(() => {
const focusTarget = this.routerView?.$el as HTMLElement;
if (focusTarget) {
// Make focustarget programmatically focussable
focusTarget.setAttribute("tabindex", "-1");
// Focus element
focusTarget.focus();
// Remove tabindex from focustarget.
// Reason: https://axesslab.com/skip-links/#update-3-a-comment-from-gov-uk
focusTarget.removeAttribute("tabindex");
}
}, 0);
}
extractPageTitleFromRoute(route: Route): string {
if (route.meta?.announcer?.message) {
return route.meta?.announcer?.message();
}
return document.title;
}
}
</script>
@ -206,7 +246,6 @@ export default class App extends Vue {
/* Icons */
$mdi-font-path: "~@mdi/font/fonts";
@import "~@mdi/font/scss/materialdesignicons";
@import "common";
#mobilizon {
@ -218,4 +257,8 @@ $mdi-font-path: "~@mdi/font/fonts";
flex-grow: 1;
}
}
.vue-skip-to {
z-index: 40;
}
</style>

View File

@ -1,8 +1,11 @@
@use "@/styles/_mixins" as *;
@import "variables.scss";
@import "~bulma";
@import "~bulma-divider";
@import "~buefy/src/scss/buefy";
@import "styles/vue-announcer.scss";
@import "styles/vue-skip-to.scss";
// a {
// color: $violet-2;
@ -79,7 +82,7 @@ $color-black: #000;
border-radius: 5px;
padding: 0.2rem;
white-space: nowrap;
margin-right: 0.2rem;
@include margin-right(0.2rem);
}
.mention-suggestion {
@ -88,7 +91,7 @@ $color-black: #000;
.mention .mention {
background: initial;
margin-right: 0;
@include margin-right(0);
}
.select select {
@ -240,3 +243,63 @@ footer.footer[data-v-40ab164b] span.select select {
background: $chapril_blue_light;
color: $footer-text-color;
}
@mixin focus() {
&:focus {
border: 2px solid black;
border-radius: 5px;
}
}
ul.menu-list > li,
p {
@include focus;
}
.navbar-item {
@include focus;
}
.navbar-dropdown span.navbar-item:hover {
background-color: whitesmoke;
color: #0a0a0a;
}
/**
* Bulma/Buefy fixes
*/
.icon {
vertical-align: middle;
}
.tags .tag:not(:last-child) {
margin-right: unset;
@include margin-right(0.5rem);
}
.button .icon {
&:first-child:not(:last-child) {
@include margin-left(calc(-0.5em - 1px));
@include margin-right(0.25em);
}
&:last-child:not(:first-child) {
@include margin-right(calc(-0.5em - 1px));
@include margin-left(0.25em);
}
}
.buttons .button:not(:last-child):not(.is-fullwidth) {
margin-right: unset;
@include margin-right(0.5rem);
}
.breadcrumb li:first-child a {
padding-left: unset;
@include padding-left(0);
@include padding-right(0.75em);
}
.media-left {
@include margin-left(1rem);
}
a.dropdown-item {
@include padding-right(3rem);
}

View File

@ -1,9 +1,9 @@
<template>
<p>
<a :title="contact" v-if="configLink" :href="configLink.uri">{{
<a dir="auto" :title="contact" v-if="configLink" :href="configLink.uri">{{
configLink.text
}}</a>
<span v-else-if="contact">{{ contact }}</span>
<span dir="auto" v-else-if="contact">{{ contact }}</span>
<span v-else>{{ $t("contact uninformed") }}</span>
</p>
</template>

View File

@ -1,27 +1,25 @@
<template>
<div>
<div class="media" style="align-items: top">
<div class="media-left">
<figure class="image is-32x32" v-if="actor.avatar">
<img class="is-rounded" :src="actor.avatar.url" alt="" />
</figure>
<b-icon v-else size="is-medium" icon="account-circle" />
</div>
<div class="media" style="align-items: top" dir="auto">
<div class="media-left">
<figure class="image is-32x32" v-if="actor.avatar">
<img class="is-rounded" :src="actor.avatar.url" alt="" />
</figure>
<b-icon v-else size="is-medium" icon="account-circle" />
</div>
<div class="media-content">
<p>
{{ actor.name || `@${usernameWithDomain(actor)}` }}
</p>
<p class="has-text-grey-dark" v-if="actor.name">
@{{ usernameWithDomain(actor) }}
</p>
<div
v-if="full"
class="summary"
:class="{ limit: limit }"
v-html="actor.summary"
/>
</div>
<div class="media-content">
<p>
{{ actor.name || `@${usernameWithDomain(actor)}` }}
</p>
<p class="has-text-grey-dark" v-if="actor.name">
<span dir="ltr">@{{ usernameWithDomain(actor) }}</span>
</p>
<div
v-if="full"
class="summary"
:class="{ limit: limit }"
v-html="actor.summary"
/>
</div>
</div>
</template>
@ -53,6 +51,15 @@ export default class ActorCard extends Vue {
</style>
<style lang="scss">
@use "@/styles/_mixins" as *;
.media {
.media-left {
margin-right: initial;
@include margin-right(1rem);
}
}
.tooltip {
display: block !important;
z-index: 10000;
@ -105,7 +112,7 @@ export default class ActorCard extends Vue {
}
&[x-placement^="right"] {
margin-left: 5px;
@include margin-left(5px);
.tooltip-arrow {
border-width: 5px 5px 5px 0;
@ -114,13 +121,13 @@ export default class ActorCard extends Vue {
border-bottom-color: transparent !important;
left: -5px;
top: calc(50% - 5px);
margin-left: 0;
margin-right: 0;
@include margin-left(0);
@include margin-right(0);
}
}
&[x-placement^="left"] {
margin-right: 5px;
@include margin-right(5px);
.tooltip-arrow {
border-width: 5px 0 5px 5px;
@ -129,8 +136,8 @@ export default class ActorCard extends Vue {
border-bottom-color: transparent !important;
right: -5px;
top: calc(50% - 5px);
margin-left: 0;
margin-right: 0;
@include margin-left(0);
@include margin-right(0);
}
}

View File

@ -26,6 +26,7 @@ export default class ActorInline extends Vue {
}
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
div.actor-inline {
align-items: flex-start;
display: inline-flex;
@ -36,7 +37,7 @@ div.actor-inline {
flex-basis: auto;
flex-grow: 0;
flex-shrink: 0;
margin-right: 0.5rem;
@include margin-right(0.5rem);
}
div.actor-name {
flex-basis: auto;

View File

@ -6,7 +6,7 @@
:class="{ inline, clickable: actor && actor.type === ActorType.GROUP }"
>
<slot></slot>
<template slot="popover" class="popover">
<template slot="popover">
<actor-card :full="true" :actor="actor" :popover="true" />
</template>
</v-popover>

View File

@ -11,7 +11,7 @@
)
}}
</p>
<hr />
<hr role="presentation" />
<p class="content">
<span>
{{
@ -33,7 +33,7 @@
"
/>
</p>
<hr />
<hr role="presentation" />
<p class="content">
{{
$t(

View File

@ -148,6 +148,11 @@ export default class GroupActivityItem extends mixins(ActivityMixin) {
case Openness.INVITE_ONLY:
details.push("The group can now only be joined with an invite.");
break;
case Openness.MODERATED:
details.push(
"The group can now be joined by anyone, but new members need to be approved by an administrator."
);
break;
case Openness.OPEN:
details.push("The group can now be joined by anyone.");
break;

View File

@ -9,13 +9,7 @@
:inline="true"
slot="member"
>
<b>
{{
$t("@{username}", {
username: usernameWithDomain(activity.object.actor),
})
}}</b
></popover-actor-card
<b> {{ displayName(activity.object.actor) }}</b></popover-actor-card
>
<b slot="member" v-else>{{
subjectParams.member_actor_federated_username
@ -25,13 +19,7 @@
:inline="true"
slot="profile"
>
<b>
{{
$t("@{username}", {
username: usernameWithDomain(activity.author),
})
}}</b
></popover-actor-card
<b> {{ displayName(activity.author) }}</b></popover-actor-card
></i18n
>
<small class="has-text-grey-dark activity-date">{{
@ -41,7 +29,7 @@
</div>
</template>
<script lang="ts">
import { usernameWithDomain } from "@/types/actor";
import { displayName } from "@/types/actor";
import { ActivityMemberSubject, MemberRole } from "@/types/enums";
import { Component } from "vue-property-decorator";
import RouteName from "../../router/name";
@ -62,7 +50,7 @@ export const MEMBER_ROLE_VALUE: Record<string, number> = {
},
})
export default class MemberActivityItem extends mixins(ActivityMixin) {
usernameWithDomain = usernameWithDomain;
displayName = displayName;
RouteName = RouteName;
ActivityMemberSubject = ActivityMemberSubject;
@ -83,6 +71,14 @@ export default class MemberActivityItem extends mixins(ActivityMixin) {
return "You added the member {member}.";
}
return "{profile} added the member {member}.";
case ActivityMemberSubject.MEMBER_APPROVED:
if (this.isAuthorCurrentActor) {
return "You approved {member}'s membership.";
}
if (this.isObjectMemberCurrentActor) {
return "Your membership was approved by {profile}.";
}
return "{profile} approved {member}'s membership.";
case ActivityMemberSubject.MEMBER_JOINED:
return "{member} joined the group.";
case ActivityMemberSubject.MEMBER_UPDATED:
@ -94,6 +90,12 @@ export default class MemberActivityItem extends mixins(ActivityMixin) {
}
return "{profile} updated the member {member}.";
case ActivityMemberSubject.MEMBER_REMOVED:
if (this.subjectParams.member_role === MemberRole.NOT_APPROVED) {
if (this.isAuthorCurrentActor) {
return "You rejected {member}'s membership request.";
}
return "{profile} rejected {member}'s membership request.";
}
if (this.isAuthorCurrentActor) {
return "You excluded member {member}.";
}

View File

@ -172,7 +172,8 @@ export default class ResourceActivityItem extends mixins(ActivityMixin) {
if (this.subjectParams.resource_path) {
const parentPath = this.parentPath(this.subjectParams.resource_path);
const directory = parentPath.split("/");
return directory.pop();
const res = directory.pop();
res === "" ? null : res;
}
return null;
}

View File

@ -0,0 +1,125 @@
<template>
<address dir="auto">
<b-icon
v-if="showIcon"
:icon="address.poiInfos.poiIcon.icon"
size="is-medium"
class="icon"
/>
<p>
<span
class="addressDescription"
:title="address.poiInfos.name"
v-if="address.poiInfos.name"
>
{{ address.poiInfos.name }}
</span>
<br v-if="address.poiInfos.name" />
<span class="has-text-grey-dark">
{{ address.poiInfos.alternativeName }}
</span>
<br />
<small
v-if="
userTimezoneDifferent &&
longShortTimezoneNamesDifferent &&
timezoneLongNameValid
"
class="has-text-grey-dark"
>
🌐
{{
$t("{timezoneLongName} ({timezoneShortName})", {
timezoneLongName,
timezoneShortName,
})
}}
</small>
<small v-else-if="userTimezoneDifferent" class="has-text-grey-dark">
🌐 {{ timezoneShortName }}
</small>
</p>
</address>
</template>
<script lang="ts">
import { IAddress } from "@/types/address.model";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
@Component
export default class AddressInfo extends Vue {
@Prop({ required: true, type: Object as PropType<IAddress> })
address!: IAddress;
@Prop({ required: false, default: false, type: Boolean }) showIcon!: boolean;
@Prop({ required: false, default: false, type: Boolean })
showTimezone!: boolean;
@Prop({ required: false, type: String }) userTimezone!: string;
get userTimezoneDifferent(): boolean {
return (
this.userTimezone != undefined &&
this.address.timezone != undefined &&
this.userTimezone !== this.address.timezone
);
}
get longShortTimezoneNamesDifferent(): boolean {
return (
this.timezoneLongName != undefined &&
this.timezoneShortName != undefined &&
this.timezoneLongName !== this.timezoneShortName
);
}
get timezoneLongName(): string | undefined {
return this.timezoneName("long");
}
get timezoneShortName(): string | undefined {
return this.timezoneName("short");
}
get timezoneLongNameValid(): boolean {
return (
this.timezoneLongName != undefined && !this.timezoneLongName.match(/UTC/)
);
}
private timezoneName(format: "long" | "short"): string | undefined {
return this.extractTimezone(
new Intl.DateTimeFormat(undefined, {
timeZoneName: format,
timeZone: this.address.timezone,
}).formatToParts()
);
}
private extractTimezone(
parts: Intl.DateTimeFormatPart[]
): string | undefined {
return parts.find((part) => part.type === "timeZoneName")?.value;
}
}
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
address {
font-style: normal;
display: flex;
justify-content: flex-start;
span.addressDescription {
text-overflow: ellipsis;
white-space: nowrap;
flex: 1 0 auto;
min-width: 100%;
max-width: 4rem;
overflow: hidden;
}
span.icon {
@include padding-right(1rem);
}
}
</style>

View File

@ -0,0 +1,45 @@
<template>
<div
class="ellipsis"
:title="
isDescriptionDifferentFromLocality
? `${physicalAddress.description}, ${physicalAddress.locality}`
: physicalAddress.description
"
>
<b-icon icon="map-marker" />
<span v-if="isDescriptionDifferentFromLocality">
{{ physicalAddress.description }},
{{ physicalAddress.locality }}
</span>
<span v-else>
{{ physicalAddress.description }}
</span>
</div>
</template>
<script lang="ts">
import { IAddress } from "@/types/address.model";
import { PropType } from "vue";
import { Prop, Vue, Component } from "vue-property-decorator";
@Component
export default class InlineAddress extends Vue {
@Prop({ required: true, type: Object as PropType<IAddress> })
physicalAddress!: IAddress;
get isDescriptionDifferentFromLocality(): boolean {
return (
this.physicalAddress?.description !== this.physicalAddress?.locality &&
this.physicalAddress?.description !== undefined
);
}
}
</script>
<style lang="scss" scoped>
.ellipsis {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
}
</style>

View File

@ -128,7 +128,7 @@
</div>
</template>
<script lang="ts">
import { Component, Mixins, Ref } from "vue-property-decorator";
import { Component, Mixins } from "vue-property-decorator";
import { SnackbarProgrammatic as Snackbar } from "buefy";
import { formatDistanceToNow } from "date-fns";
import {
@ -173,8 +173,6 @@ export default class Followers extends Mixins(RelayMixin) {
FOLLOWERS_PER_PAGE = FOLLOWERS_PER_PAGE;
@Ref("table") readonly table!: any;
toggle(row: Record<string, unknown>): void {
this.table.toggleDetails(row);
}
@ -211,12 +209,14 @@ export default class Followers extends Mixins(RelayMixin) {
});
await this.$apollo.queries.relayFollowers.refetch();
this.checkedRows = [];
} catch (e) {
Snackbar.open({
message: e.message,
type: "is-danger",
position: "is-bottom",
});
} catch (e: any) {
if (e.message) {
Snackbar.open({
message: e.message,
type: "is-danger",
position: "is-bottom",
});
}
}
}
@ -230,12 +230,14 @@ export default class Followers extends Mixins(RelayMixin) {
});
await this.$apollo.queries.relayFollowers.refetch();
this.checkedRows = [];
} catch (e) {
Snackbar.open({
message: e.message,
type: "is-danger",
position: "is-bottom",
});
} catch (e: any) {
if (e.message) {
Snackbar.open({
message: e.message,
type: "is-danger",
position: "is-bottom",
});
}
}
}
@ -252,7 +254,7 @@ export default class Followers extends Mixins(RelayMixin) {
limit: FOLLOWERS_PER_PAGE,
},
});
} catch (err) {
} catch (err: any) {
console.error(err);
}
}

View File

@ -203,7 +203,7 @@ export default class Followings extends Mixins(RelayMixin) {
limit: FOLLOWINGS_PER_PAGE,
},
});
} catch (err) {
} catch (err: any) {
console.error(err);
}
}
@ -254,12 +254,14 @@ export default class Followings extends Mixins(RelayMixin) {
},
});
this.newRelayAddress = "";
} catch (err) {
Snackbar.open({
message: err.message,
type: "is-danger",
position: "is-bottom",
});
} catch (err: any) {
if (err.message) {
Snackbar.open({
message: err.message,
type: "is-danger",
position: "is-bottom",
});
}
}
}
@ -295,12 +297,14 @@ export default class Followings extends Mixins(RelayMixin) {
});
await this.$apollo.queries.relayFollowings.refetch();
this.checkedRows = [];
} catch (e) {
Snackbar.open({
message: e.message,
type: "is-danger",
position: "is-bottom",
});
} catch (e: any) {
if (e.message) {
Snackbar.open({
message: e.message,
type: "is-danger",
position: "is-bottom",
});
}
}
}
}

View File

@ -7,7 +7,7 @@
}"
class="comment-element"
>
<article class="media" :id="commentId">
<article class="media" :id="commentId" dir="auto">
<popover-actor-card
:actor="comment.actor"
:inline="true"
@ -32,11 +32,11 @@
</div>
<div class="media-content">
<div class="content">
<span class="first-line" v-if="!comment.deletedAt">
<span class="first-line" v-if="!comment.deletedAt" dir="auto">
<strong :class="{ organizer: commentFromOrganizer }">{{
comment.actor.name
}}</strong>
<small>{{ usernameWithDomain(comment.actor) }}</small>
<small dir="ltr">@{{ usernameWithDomain(comment.actor) }}</small>
</span>
<a v-else class="comment-link" :href="commentURL">
<span>{{ $t("[deleted]") }}</span>
@ -63,7 +63,12 @@
</button>
</span>
<br />
<div v-if="!comment.deletedAt" v-html="comment.text" />
<div
v-if="!comment.deletedAt"
v-html="comment.text"
dir="auto"
:lang="comment.language"
/>
<div v-else>{{ $t("[This comment has been deleted]") }}</div>
<div class="load-replies" v-if="comment.totalReplies">
<p v-if="!showReplies" @click="fetchReplies">
@ -128,7 +133,7 @@
<div class="content">
<span class="first-line">
<strong>{{ currentActor.name }}</strong>
<small>@{{ currentActor.preferredUsername }}</small>
<small dir="ltr">@{{ currentActor.preferredUsername }}</small>
</span>
<br />
<span class="editor-line">
@ -137,6 +142,7 @@
ref="commentEditor"
v-model="newComment.text"
mode="comment"
:aria-label="$t('Comment body')"
/>
<b-button
:disabled="newComment.text.trim().length === 0"
@ -298,6 +304,10 @@ export default class Comment extends Vue {
onConfirm: this.reportComment,
outsideDomain: this.comment.actor.domain,
},
// https://github.com/buefy/buefy/pull/3589
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
closeButtonAriaLabel: this.$t("Close"),
});
}
@ -322,17 +332,20 @@ export default class Comment extends Vue {
position: "is-bottom-right",
duration: 5000,
});
} catch (e) {
Snackbar.open({
message: e.message,
type: "is-danger",
position: "is-bottom",
});
} catch (e: any) {
if (e.message) {
Snackbar.open({
message: e.message,
type: "is-danger",
position: "is-bottom",
});
}
}
}
}
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
form.reply {
padding-bottom: 1rem;
}
@ -352,7 +365,7 @@ form.reply {
}
& > small {
margin-left: 0.3rem;
@include margin-left(0.3rem);
}
}
@ -362,14 +375,14 @@ form.reply {
.editor {
flex: 1;
padding-right: 10px;
@include padding-right(10px);
margin-bottom: 0;
}
}
a.comment-link {
text-decoration: none;
margin-left: 5px;
@include margin-left(5px);
color: $text;
&:hover {
text-decoration: underline;
@ -398,6 +411,7 @@ a.comment-link {
color: $white;
.reply-btn,
small,
span,
strong,
.icons button {
color: $white;
@ -412,7 +426,7 @@ a.comment-link {
}
.media-left {
margin-right: 0.5rem;
@include margin-right(5px);
}
}
@ -423,7 +437,7 @@ a.comment-link {
display: flex;
flex-direction: column;
align-items: center;
margin-right: 10px;
@include margin-right(10px);
.vertical-border {
width: 3px;
@ -441,9 +455,12 @@ a.comment-link {
.media .media-content {
overflow-x: initial;
.content .editor-line {
display: flex;
align-items: center;
.content {
text-align: start;
.editor-line {
display: flex;
align-items: center;
}
}
.icons {
@ -512,7 +529,7 @@ article {
}
.reply-action .icon {
padding-right: 0.4rem;
@include padding-right(0.4rem);
}
.visually-hidden {

View File

@ -12,7 +12,7 @@
>{{ $t("Comments are closed for everybody else.") }}</b-notification
>
<article class="media">
<figure class="media-left">
<figure class="media-left" v-if="newComment.actor">
<identity-picker-wrapper :inline="false" v-model="newComment.actor" />
</figure>
<div class="media-content">
@ -23,6 +23,7 @@
ref="commenteditor"
mode="comment"
v-model="newComment.text"
:aria-label="$t('Comment body')"
/>
</p>
<p class="help is-danger" v-if="emptyCommentError">
@ -30,9 +31,11 @@
</p>
</div>
<div class="field notify-participants" v-if="isEventOrganiser">
<b-switch v-model="newComment.isAnnouncement">{{
$t("Notify participants")
}}</b-switch>
<b-switch
aria-labelledby="notify-participants-toggle"
v-model="newComment.isAnnouncement"
>{{ $t("Notify participants") }}</b-switch
>
</div>
</div>
</div>
@ -42,8 +45,8 @@
type="is-primary"
class="comment-button-submit"
icon-left="send"
:aria-label="$t('Post a comment')"
/>
>{{ $t("Send") }}</b-button
>
</div>
</article>
</form>
@ -56,11 +59,11 @@
>
{{ $t("Loading comments…") }}
</p>
<transition-group name="comment-empty-list" mode="out-in" v-else>
<transition-group tag="div" name="comment-empty-list" v-else>
<transition-group
key="list"
name="comment-list"
v-if="comments.length"
v-if="filteredOrderedComments.length"
class="comment-list"
tag="ul"
>
@ -74,9 +77,9 @@
@delete-comment="deleteComment"
/>
</transition-group>
<div v-else class="no-comments" key="no-comments">
<empty-content v-else icon="comment" key="no-comments" :inline="true">
<span>{{ $t("No comments yet") }}</span>
</div>
</empty-content>
</transition-group>
</div>
</template>
@ -96,6 +99,7 @@ import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor";
import { IPerson } from "../../types/actor";
import { IEvent } from "../../types/event.model";
import { ApolloCache, FetchResult, InMemoryCache } from "@apollo/client/core";
import EmptyContent from "@/components/Utils/EmptyContent.vue";
@Component({
apollo: {
@ -116,6 +120,7 @@ import { ApolloCache, FetchResult, InMemoryCache } from "@apollo/client/core";
components: {
Comment,
IdentityPickerWrapper,
EmptyContent,
editor: () =>
import(/* webpackChunkName: "editor" */ "@/components/Editor.vue"),
},
@ -213,7 +218,7 @@ export default class CommentTree extends Vue {
// and reset the new comment field
this.newComment = new CommentModel();
} catch (errors) {
} catch (errors: any) {
console.error(errors);
if (errors.graphQLErrors && errors.graphQLErrors.length > 0) {
const error = errors.graphQLErrors[0];
@ -295,7 +300,7 @@ export default class CommentTree extends Vue {
},
});
// this.comments = this.comments.filter(commentItem => commentItem.id !== comment.id);
} catch (error) {
} catch (error: any) {
console.error(error);
if (error.graphQLErrors && error.graphQLErrors.length > 0) {
this.$notifier.error(error.graphQLErrors[0].message);
@ -360,21 +365,35 @@ export default class CommentTree extends Vue {
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
@import "~bulma/sass/utilities/mixins.sass";
form.new-comment {
padding-bottom: 1rem;
.media-content {
display: flex;
align-items: center;
align-content: center;
.media {
flex-wrap: wrap;
justify-content: center;
.media-left {
@include mobile {
@include margin-right(0.5rem);
@include margin-left(0.5rem);
}
}
.field {
flex: 1;
padding-right: 10px;
margin-bottom: 0;
.media-content {
display: flex;
align-items: center;
align-content: center;
width: min-content;
&.notify-participants {
margin-top: 0.5rem;
.field {
flex: 1;
@include padding-right(10px);
margin-bottom: 0;
&.notify-participants {
margin-top: 0.5rem;
}
}
}
}

View File

@ -10,7 +10,7 @@
<b-icon v-else size="is-large" icon="account-circle" />
</div>
<div class="body">
<div class="meta">
<div class="meta" dir="auto">
<span
class="first-line name"
v-if="comment.actor && !comment.deletedAt"
@ -64,7 +64,11 @@
>
</div>
</div>
<div v-if="!editMode && !comment.deletedAt" class="text-wrapper">
<div
v-if="!editMode && !comment.deletedAt"
class="text-wrapper"
dir="auto"
>
<div class="description-content" v-html="comment.text"></div>
<p
v-if="
@ -88,7 +92,7 @@
{{ $t("[This comment has been deleted by it's author]") }}
</div>
<form v-else class="edition" @submit.prevent="updateComment">
<editor v-model="updatedComment" />
<editor v-model="updatedComment" :aria-label="$t('Comment body')" />
<div class="buttons">
<b-button
native-type="submit"
@ -141,13 +145,16 @@ export default class DiscussionComment extends Vue {
}
updateComment(): void {
this.comment.text = this.updatedComment;
this.$emit("update-comment", this.comment);
this.$emit("update-comment", {
...this.comment,
text: this.updatedComment,
});
this.toggleEditMode();
}
}
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
article.comment {
display: flex;
border-top: 1px solid #e9e9e9;
@ -163,7 +170,7 @@ article.comment {
padding: 0 1rem 0.3em;
.name {
margin-right: auto;
@include margin-right(auto);
flex: 1 1 auto;
overflow: hidden;
@ -216,7 +223,7 @@ article.comment {
::v-deep blockquote {
border-left: 0.2em solid #333;
display: block;
padding-left: 1em;
@include padding-left(1em);
}
::v-deep p {

View File

@ -1,6 +1,7 @@
<template>
<router-link
class="discussion-minimalist-card-wrapper"
dir="auto"
:to="{
name: RouteName.DISCUSSION,
params: { slug: discussion.slug, id: discussion.id },
@ -37,6 +38,7 @@
</div>
<div
class="ellipsis has-text-grey-dark"
dir="auto"
v-if="!discussion.lastComment.deletedAt"
>
{{ htmlTextEllipsis }}
@ -83,6 +85,7 @@ export default class DiscussionListItem extends Vue {
}
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
.discussion-minimalist-card-wrapper {
text-decoration: none;
display: flex;
@ -92,7 +95,7 @@ export default class DiscussionListItem extends Vue {
align-items: center;
.calendar-icon {
margin-right: 1rem;
@include margin-right(1rem);
}
.title-info-wrapper {

View File

@ -16,6 +16,7 @@
:class="{ 'is-active': editor.isActive('bold') }"
@click="editor.chain().focus().toggleBold().run()"
type="button"
:title="$t('Bold')"
>
<b-icon icon="format-bold" />
</button>
@ -25,6 +26,7 @@
:class="{ 'is-active': editor.isActive('italic') }"
@click="editor.chain().focus().toggleItalic().run()"
type="button"
:title="$t('Italic')"
>
<b-icon icon="format-italic" />
</button>
@ -34,6 +36,7 @@
:class="{ 'is-active': editor.isActive('underline') }"
@click="editor.chain().focus().toggleUnderline().run()"
type="button"
:title="$t('Underline')"
>
<b-icon icon="format-underline" />
</button>
@ -44,6 +47,7 @@
:class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"
@click="editor.chain().focus().toggleHeading({ level: 1 }).run()"
type="button"
:title="$t('Heading Level 1')"
>
<b-icon icon="format-header-1" />
</button>
@ -54,6 +58,7 @@
:class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"
@click="editor.chain().focus().toggleHeading({ level: 2 }).run()"
type="button"
:title="$t('Heading Level 2')"
>
<b-icon icon="format-header-2" />
</button>
@ -64,6 +69,7 @@
:class="{ 'is-active': editor.isActive('heading', { level: 3 }) }"
@click="editor.chain().focus().toggleHeading({ level: 3 }).run()"
type="button"
:title="$t('Heading Level 3')"
>
<b-icon icon="format-header-3" />
</button>
@ -73,6 +79,7 @@
@click="showLinkMenu()"
:class="{ 'is-active': editor.isActive('link') }"
type="button"
:title="$t('Add link')"
>
<b-icon icon="link" />
</button>
@ -82,6 +89,7 @@
class="menubar__button"
@click="editor.chain().focus().unsetLink().run()"
type="button"
:title="$t('Remove link')"
>
<b-icon icon="link-off" />
</button>
@ -91,6 +99,7 @@
v-if="!isBasicMode"
@click="showImagePrompt()"
type="button"
:title="$t('Add picture')"
>
<b-icon icon="image" />
</button>
@ -101,6 +110,7 @@
:class="{ 'is-active': editor.isActive('bulletList') }"
@click="editor.chain().focus().toggleBulletList().run()"
type="button"
:title="$t('Bullet list')"
>
<b-icon icon="format-list-bulleted" />
</button>
@ -111,6 +121,7 @@
:class="{ 'is-active': editor.isActive('orderedList') }"
@click="editor.chain().focus().toggleOrderedList().run()"
type="button"
:title="$t('Ordered list')"
>
<b-icon icon="format-list-numbered" />
</button>
@ -121,6 +132,7 @@
:class="{ 'is-active': editor.isActive('blockquote') }"
@click="editor.chain().focus().toggleBlockquote().run()"
type="button"
:title="$t('Quote')"
>
<b-icon icon="format-quote-close" />
</button>
@ -130,6 +142,7 @@
class="menubar__button"
@click="editor.chain().focus().undo().run()"
type="button"
:title="$t('Undo')"
>
<b-icon icon="undo" />
</button>
@ -139,6 +152,7 @@
class="menubar__button"
@click="editor.chain().focus().redo().run()"
type="button"
:title="$t('Redo')"
>
<b-icon icon="redo" />
</button>
@ -155,6 +169,7 @@
:class="{ 'is-active': editor.isActive('bold') }"
@click="editor.chain().focus().toggleBold().run()"
type="button"
:title="$t('Bold')"
>
<b-icon icon="format-bold" />
<span class="visually-hidden">{{ $t("Bold") }}</span>
@ -165,6 +180,7 @@
:class="{ 'is-active': editor.isActive('italic') }"
@click="editor.chain().focus().toggleItalic().run()"
type="button"
:title="$t('Italic')"
>
<b-icon icon="format-italic" />
<span class="visually-hidden">{{ $t("Italic") }}</span>
@ -195,6 +211,8 @@ import ListItem from "@tiptap/extension-list-item";
import Underline from "@tiptap/extension-underline";
import Link from "@tiptap/extension-link";
import CharacterCount from "@tiptap/extension-character-count";
import { AutoDir } from "./Editor/Autodir";
import sanitizeHtml from "sanitize-html";
@Component({
components: { EditorContent, BubbleMenu },
@ -211,6 +229,8 @@ export default class EditorComponent extends Vue {
@Prop({ required: false, default: 100_000_000 }) maxSize!: number;
@Prop({ required: false }) ariaLabel!: string;
currentActor!: IPerson;
editor: Editor | null = null;
@ -240,6 +260,14 @@ export default class EditorComponent extends Vue {
mounted(): void {
this.editor = new Editor({
editorProps: {
attributes: {
"aria-multiline": this.isShortMode.toString(),
"aria-label": this.ariaLabel,
role: "textbox",
},
transformPastedHTML: this.transformPastedHTML,
},
extensions: [
StarterKit,
Document,
@ -249,6 +277,7 @@ export default class EditorComponent extends Vue {
ListItem,
Mention.configure(MentionOptions),
CustomImage,
AutoDir,
Underline,
Link.configure({
HTMLAttributes: { target: "_blank", rel: "noopener noreferrer ugc" },
@ -265,6 +294,19 @@ export default class EditorComponent extends Vue {
});
}
transformPastedHTML(html: string): string {
// When using comment mode, limit to acceptable tags
if (this.isCommentMode) {
return sanitizeHtml(html, {
allowedTags: ["b", "i", "em", "strong", "a"],
allowedAttributes: {
a: ["href", "rel", "target"],
},
});
}
return html;
}
@Watch("value")
onValueChanged(val: string): void {
if (!this.editor) return;
@ -315,7 +357,7 @@ export default class EditorComponent extends Vue {
})
.run();
}
} catch (error) {
} catch (error: any) {
console.error(error);
if (error.graphQLErrors && error.graphQLErrors.length > 0) {
this.$notifier.error(error.graphQLErrors[0].message);
@ -351,6 +393,7 @@ export default class EditorComponent extends Vue {
}
</script>
<style lang="scss">
@use "@/styles/_mixins" as *;
@import "./Editor/style.scss";
$color-black: #000;
@ -367,7 +410,7 @@ $color-white: #eee;
border: 0;
color: $color-black;
padding: 0.2rem 0.5rem;
margin-right: 0.2rem;
@include margin-right(0.2rem);
border-radius: 3px;
cursor: pointer;
@ -439,7 +482,7 @@ $color-white: #eee;
ul,
ol {
padding-left: 1rem;
@include padding-left(1rem);
}
ul {
@ -455,7 +498,7 @@ $color-white: #eee;
blockquote {
border-left: 3px solid rgba($color-black, 0.1);
color: rgba($color-black, 0.8);
padding-left: 0.8rem;
@include padding-left(0.8rem);
font-style: italic;
p {

View File

@ -0,0 +1,30 @@
import { Extension } from "@tiptap/core";
/**
* Allows to set dir="auto" on top nodes
* Taken from https://github.com/ueberdosis/tiptap/issues/1621#issuecomment-918990408
*/
export const AutoDir = Extension.create({
name: "AutoDir",
addGlobalAttributes() {
return [
{
types: [
"heading",
"paragraph",
"bulletList",
"orderedList",
"blockquote",
],
attributes: {
autoDir: {
renderHTML: () => ({
dir: "auto",
}),
parseHTML: (element) => element.dir || "auto",
},
},
},
];
},
});

View File

@ -27,6 +27,7 @@ const debouncedFetchItems = pDebounce(fetchItems, 200);
const mentionOptions: Partial<any> = {
HTMLAttributes: {
class: "mention",
dir: "ltr",
},
suggestion: {
items: async (query: string): Promise<IPerson[]> => {

View File

@ -103,9 +103,11 @@
:active="copied !== false"
always
>
<b-button @click="copyErrorToClipboard">{{
$t("Copy details to clipboard")
}}</b-button>
<b-button
@click="copyErrorToClipboard"
@keyup.enter="copyErrorToClipboard"
>{{ $t("Copy details to clipboard") }}</b-button
>
</b-tooltip>
</div>
</section>

View File

@ -11,6 +11,8 @@
icon="map-marker"
expanded
@select="updateSelected"
v-bind="$attrs"
dir="auto"
>
<template #default="{ option }">
<b-icon :icon="option.poiInfos.poiIcon.icon" />
@ -20,12 +22,17 @@
</template>
</b-autocomplete>
</b-field>
<b-field v-if="canDoGeoLocation">
<b-field
v-if="canDoGeoLocation"
:message="fieldErrors"
:type="{ 'is-danger': fieldErrors.length }"
>
<b-button
type="is-text"
v-if="!gettingLocation"
icon-right="target"
@click="locateMe"
@keyup.enter="locateMe"
>{{ $t("Use my location") }}</b-button
>
<span v-else>{{ $t("Getting location") }}</span>
@ -52,26 +59,16 @@
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import { LatLng } from "leaflet";
import debounce from "lodash/debounce";
import { DebouncedFunc } from "lodash";
import { Component, Mixins, Prop, Watch } from "vue-property-decorator";
import { Address, IAddress } from "../../types/address.model";
import { ADDRESS, REVERSE_GEOCODE } from "../../graphql/address";
import { CONFIG } from "../../graphql/config";
import { IConfig } from "../../types/config.model";
import AddressAutoCompleteMixin from "@/mixins/AddressAutoCompleteMixin";
@Component({
components: {
"map-leaflet": () =>
import(/* webpackChunkName: "map" */ "@/components/Map.vue"),
},
apollo: {
config: CONFIG,
},
inheritAttrs: false,
})
export default class AddressAutoComplete extends Vue {
@Prop({ required: true }) value!: IAddress;
export default class AddressAutoComplete extends Mixins(
AddressAutoCompleteMixin
) {
@Prop({ required: false, default: false }) type!: string | false;
@Prop({ required: false, default: true, type: Boolean })
doGeoLocation!: boolean;
@ -80,84 +77,20 @@ export default class AddressAutoComplete extends Vue {
selected: IAddress = new Address();
isFetching = false;
initialQueryText = "";
addressModalActive = false;
showmap = false;
private gettingLocation = false;
// eslint-disable-next-line no-undef
private location!: GeolocationPosition;
private gettingLocationError: any;
private mapDefaultZoom = 15;
config!: IConfig;
fetchAsyncData!: DebouncedFunc<(query: string) => Promise<void>>;
// We put this in data because of issues like
// https://github.com/vuejs/vue-class-component/issues/263
data(): Record<string, unknown> {
return {
fetchAsyncData: debounce(this.asyncData, 200),
};
}
async asyncData(query: string): Promise<void> {
if (!query.length) {
this.addressData = [];
this.selected = new Address();
return;
}
if (query.length < 3) {
this.addressData = [];
return;
}
this.isFetching = true;
const variables: { query: string; locale: string; type?: string } = {
query,
locale: this.$i18n.locale,
};
if (this.type) {
variables.type = this.type;
}
const result = await this.$apollo.query({
query: ADDRESS,
fetchPolicy: "network-only",
variables,
});
this.addressData = result.data.searchAddress.map(
(address: IAddress) => new Address(address)
);
this.isFetching = false;
}
@Watch("config")
watchConfig(config: IConfig): void {
if (!config.geocoding.autocomplete) {
// If autocomplete is disabled, we put a larger debounce value
// so that we don't request with incomplete address
this.fetchAsyncData = debounce(this.asyncData, 2000);
}
}
get queryText(): string {
get queryText2(): string {
if (this.value !== undefined) {
return new Address(this.value).fullName;
}
return this.initialQueryText;
}
set queryText(queryText: string) {
set queryText2(queryText: string) {
this.initialQueryText = queryText;
}
@ -186,80 +119,6 @@ export default class AddressAutoComplete extends Vue {
this.showmap = !this.showmap;
}
async reverseGeoCode(e: LatLng, zoom: number): Promise<void> {
// If the position has been updated through autocomplete selection, no need to geocode it!
if (this.checkCurrentPosition(e)) return;
const result = await this.$apollo.query({
query: REVERSE_GEOCODE,
variables: {
latitude: e.lat,
longitude: e.lng,
zoom,
locale: this.$i18n.locale,
},
});
this.addressData = result.data.reverseGeocode.map(
(address: IAddress) => new Address(address)
);
if (this.addressData.length > 0) {
const defaultAddress = new Address(this.addressData[0]);
this.selected = defaultAddress;
this.$emit("input", this.selected);
this.queryText = `${defaultAddress.poiInfos.name} ${defaultAddress.poiInfos.alternativeName}`;
}
}
checkCurrentPosition(e: LatLng): boolean {
if (!this.selected || !this.selected.geom) return false;
const lat = parseFloat(this.selected.geom.split(";")[1]);
const lon = parseFloat(this.selected.geom.split(";")[0]);
return e.lat === lat && e.lng === lon;
}
async locateMe(): Promise<void> {
this.gettingLocation = true;
try {
this.location = await AddressAutoComplete.getLocation();
this.mapDefaultZoom = 12;
this.reverseGeoCode(
new LatLng(
this.location.coords.latitude,
this.location.coords.longitude
),
12
);
} catch (e) {
console.error(e);
this.gettingLocationError = e.message;
}
this.gettingLocation = false;
}
// eslint-disable-next-line no-undef
static async getLocation(): Promise<GeolocationPosition> {
return new Promise((resolve, reject) => {
if (!("geolocation" in navigator)) {
reject(new Error("Geolocation is not available."));
}
navigator.geolocation.getCurrentPosition(
(pos) => {
resolve(pos);
},
(err) => {
reject(err);
}
);
});
}
// eslint-disable-next-line class-methods-use-this
get isSecureContext(): boolean {
return window.isSecureContext;
}
get canDoGeoLocation(): boolean {
return this.isSecureContext && this.doGeoLocation;
}

View File

@ -12,18 +12,17 @@
</docs>
<template>
<time
<div
class="datetime-container"
:class="{ small }"
:datetime="dateObj.getUTCSeconds()"
:style="`--small: ${smallStyle}`"
>
<div class="datetime-container-header" />
<div class="datetime-container-content">
<span class="day">{{ day }}</span>
<span class="month">{{ month }}</span>
<time :datetime="dateObj.toISOString()" class="day">{{ day }}</time>
<time :datetime="dateObj.toISOString()" class="month">{{ month }}</time>
</div>
</time>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
@ -54,14 +53,13 @@ export default class DateCalendarIcon extends Vue {
</script>
<style lang="scss" scoped>
time.datetime-container {
div.datetime-container {
background: $chapril_blue_light;
border: 1px solid $borders;
border-radius: 8px;
display: flex;
flex-direction: column;
justify-content: center;
width: 50px;
padding: 8px;
text-align: center;
overflow-y: hidden;
@ -80,7 +78,7 @@ time.datetime-container {
height: calc(30px * var(--small));
}
span {
time {
display: block;
font-weight: 600;
color: $violet-3;

View File

@ -1,7 +1,7 @@
<template>
<router-link
class="card"
:to="{ name: 'Event', params: { uuid: event.uuid } }"
:to="{ name: RouteName.EVENT, params: { uuid: event.uuid } }"
>
<div class="card-image">
<figure class="image is-16by9">
@ -24,7 +24,7 @@
v-for="tag in (event.tags || []).slice(0, 3)"
:key="tag.slug"
>
<b-tag type="is-light">{{ tag.title }}</b-tag>
<b-tag type="is-light" dir="auto">{{ tag.title }}</b-tag>
</router-link>
</div>
</figure>
@ -39,79 +39,72 @@
/>
</div>
<div class="media-content">
<p class="event-title" :title="event.title">{{ event.title }}</p>
<div
class="event-subtitle"
v-if="event.physicalAddress"
:title="
isDescriptionDifferentFromLocality
? `${event.physicalAddress.description}, ${event.physicalAddress.locality}`
: event.physicalAddress.description
"
<h3
class="event-title"
:title="event.title"
dir="auto"
:lang="event.language"
>
<!-- <p>{{ $t('By @{username}', { username: actor.preferredUsername }) }}</p>-->
<span v-if="isDescriptionDifferentFromLocality">
{{ event.physicalAddress.description }},
{{ event.physicalAddress.locality }}
</span>
<span v-else>
{{ event.physicalAddress.description }}
</span>
{{ event.title }}
</h3>
<div class="content-end">
<div class="event-organizer" dir="auto">
<figure
class="image is-24x24"
v-if="organizer(event) && organizer(event).avatar"
>
<img
class="is-rounded"
:src="organizer(event).avatar.url"
alt=""
/>
</figure>
<b-icon v-else icon="account-circle" />
<span class="organizer-name">
{{ organizerDisplayName(event) }}
</span>
</div>
<inline-address
dir="auto"
v-if="event.physicalAddress"
class="event-subtitle"
:physical-address="event.physicalAddress"
/>
<div
class="event-subtitle"
dir="auto"
v-else-if="event.options && event.options.isOnline"
>
<b-icon icon="video" />
<span>{{ $t("Online") }}</span>
</div>
</div>
</div>
</div>
</div>
<!-- <div class="date-and-title">-->
<!-- <div class="date-component">-->
<!-- <date-calendar-icon v-if="!mergedOptions.hideDate" :date="event.beginsOn" />-->
<!-- </div>-->
<!-- <div class="title-wrapper">-->
<!-- <h4>{{ event.title }}</h4>-->
<!-- <div class="organizer-place-wrapper has-text-grey">-->
<!-- <span>{{ $t('By @{username}', { username: actor.preferredUsername }) }}</span>-->
<!-- ·-->
<!-- <span v-if="event.physicalAddress">-->
<!-- {{ event.physicalAddress.description }}, {{ event.physicalAddress.locality }}-->
<!-- </span>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div v-if="!mergedOptions.hideDetails" class="details">-->
<!-- <div v-if="event.participants.length > 0 &&-->
<!-- mergedOptions.loggedPerson &&-->
<!-- event.participants[0].actor.id === mergedOptions.loggedPerson.id">-->
<!-- <b-tag type="is-info"><translate>Organizer</translate></b-tag>-->
<!-- </div>-->
<!-- <div v-else-if="event.participants.length === 1">-->
<!-- <translate-->
<!-- :translate-params="{name: event.participants[0].actor.preferredUsername}"-->
<!-- >{name} organizes this event</translate>-->
<!-- </div>-->
<!-- <div v-else>-->
<!-- <span v-for="participant in event.participants" :key="participant.actor.uuid">-->
<!-- {{ participant.actor.preferredUsername }}-->
<!-- <span v-if="participant.role === ParticipantRole.CREATOR">(organizer)</span>,-->
<!-- &lt;!&ndash; <translate-->
<!-- :translate-params="{name: participant.actor.preferredUsername}"-->
<!-- >&nbsp;{name} is in,</translate>&ndash;&gt;-->
<!-- </span>-->
<!-- </div>-->
</router-link>
</template>
<script lang="ts">
import { IEvent, IEventCardOptions } from "@/types/event.model";
import {
IEvent,
IEventCardOptions,
organizerDisplayName,
organizer,
} from "@/types/event.model";
import { Component, Prop, Vue } from "vue-property-decorator";
import DateCalendarIcon from "@/components/Event/DateCalendarIcon.vue";
import LazyImageWrapper from "@/components/Image/LazyImageWrapper.vue";
import { Actor, Person } from "@/types/actor";
import { EventStatus, ParticipantRole } from "@/types/enums";
import RouteName from "../../router/name";
import InlineAddress from "@/components/Address/InlineAddress.vue";
@Component({
components: {
DateCalendarIcon,
LazyImageWrapper,
InlineAddress,
},
})
export default class EventCard extends Vue {
@ -125,6 +118,10 @@ export default class EventCard extends Vue {
RouteName = RouteName;
organizerDisplayName = organizerDisplayName;
organizer = organizer;
defaultOptions: IEventCardOptions = {
hideDate: false,
loggedPerson: false,
@ -143,18 +140,13 @@ export default class EventCard extends Vue {
this.event.organizerActor || this.mergedOptions.organizerActor
);
}
get isDescriptionDifferentFromLocality(): boolean {
return (
this.event?.physicalAddress?.description !==
this.event?.physicalAddress?.locality &&
this.event?.physicalAddress?.description !== undefined
);
}
}
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
@use "@/styles/_event-card";
a.card {
display: block;
background: $secondary;
@ -190,7 +182,7 @@ a.card {
position: absolute;
top: 10px;
right: 0;
margin-right: -3px;
@include margin-right(-3px);
z-index: 10;
max-width: 40%;
@ -220,12 +212,14 @@ a.card {
}
.card-content {
height: 100%;
padding: 0.5rem;
& > .media {
position: relative;
display: flex;
flex-direction: column;
height: 100%;
& > .media-left {
margin-top: -15px;
@ -234,37 +228,39 @@ a.card {
align-items: flex-end;
align-self: flex-start;
margin-bottom: 15px;
margin-left: 0rem;
@include margin-left(0);
}
& > .media-content {
flex: 1;
width: 100%;
overflow-x: inherit;
display: flex;
flex-direction: column;
justify-content: space-between;
}
}
.event-title {
font-size: 1.2rem;
line-height: 1.25rem;
font-size: 18px;
line-height: 24px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
min-height: 2.4rem;
font-weight: bold;
}
.content-end {
padding-top: 8px;
}
.event-subtitle {
font-size: 0.85rem;
display: inline-flex;
flex-wrap: wrap;
color: #3c376e;
}
span {
width: 14rem;
display: block;
overflow: hidden;
flex-grow: 1;
text-overflow: ellipsis;
white-space: nowrap;
}
.organizer-name {
font-size: 14px;
}
}
}

View File

@ -18,66 +18,100 @@
</docs>
<template>
<span v-if="!endsOn">{{
beginsOn | formatDateTimeString(showStartTime)
}}</span>
<span v-else-if="isSameDay() && showStartTime && showEndTime">
{{
<p v-if="!endsOn">
<span>{{
formatDateTimeString(beginsOn, timezoneToShow, showStartTime)
}}</span>
<br />
<b-switch
size="is-small"
v-model="showLocalTimezone"
v-if="differentFromUserTimezone"
>
{{ singleTimeZone }}
</b-switch>
</p>
<p v-else-if="isSameDay() && showStartTime && showEndTime">
<span>{{
$t("On {date} from {startTime} to {endTime}", {
date: formatDate(beginsOn),
startTime: formatTime(beginsOn),
endTime: formatTime(endsOn),
startTime: formatTime(beginsOn, timezoneToShow),
endTime: formatTime(endsOn, timezoneToShow),
})
}}
</span>
<span v-else-if="isSameDay() && !showStartTime && showEndTime">
{{
$t("On {date} ending at {endTime}", {
date: formatDate(beginsOn),
endTime: formatTime(endsOn),
})
}}
</span>
<span v-else-if="isSameDay() && showStartTime && !showEndTime">
}}</span>
<br />
<b-switch
size="is-small"
v-model="showLocalTimezone"
v-if="differentFromUserTimezone"
>
{{ singleTimeZone }}
</b-switch>
</p>
<p v-else-if="isSameDay() && showStartTime && !showEndTime">
{{
$t("On {date} starting at {startTime}", {
date: formatDate(beginsOn),
startTime: formatTime(beginsOn),
})
}}
</span>
<span v-else-if="isSameDay()">{{
$t("On {date}", { date: formatDate(beginsOn) })
}}</span>
<span v-else-if="endsOn && showStartTime && showEndTime">
{{
$t("From the {startDate} at {startTime} to the {endDate} at {endTime}", {
startDate: formatDate(beginsOn),
startTime: formatTime(beginsOn),
endDate: formatDate(endsOn),
endTime: formatTime(endsOn),
})
}}
</span>
<span v-else-if="endsOn && showStartTime">
{{
$t("From the {startDate} at {startTime} to the {endDate}", {
startDate: formatDate(beginsOn),
startTime: formatTime(beginsOn),
endDate: formatDate(endsOn),
})
}}
</span>
<span v-else-if="endsOn">
</p>
<p v-else-if="isSameDay()">
{{ $t("On {date}", { date: formatDate(beginsOn) }) }}
</p>
<p v-else-if="endsOn && showStartTime && showEndTime">
<span>
{{
$t(
"From the {startDate} at {startTime} to the {endDate} at {endTime}",
{
startDate: formatDate(beginsOn),
startTime: formatTime(beginsOn, timezoneToShow),
endDate: formatDate(endsOn),
endTime: formatTime(endsOn, timezoneToShow),
}
)
}}
</span>
<br />
<b-switch
size="is-small"
v-model="showLocalTimezone"
v-if="differentFromUserTimezone"
>
{{ multipleTimeZones }}
</b-switch>
</p>
<p v-else-if="endsOn && showStartTime">
<span>
{{
$t("From the {startDate} at {startTime} to the {endDate}", {
startDate: formatDate(beginsOn),
startTime: formatTime(beginsOn, timezoneToShow),
endDate: formatDate(endsOn),
})
}}
</span>
<br />
<b-switch
size="is-small"
v-model="showLocalTimezone"
v-if="differentFromUserTimezone"
>
{{ singleTimeZone }}
</b-switch>
</p>
<p v-else-if="endsOn">
{{
$t("From the {startDate} to the {endDate}", {
startDate: formatDate(beginsOn),
endDate: formatDate(endsOn),
})
}}
</span>
</p>
</template>
<script lang="ts">
import { getTimezoneOffset } from "date-fns-tz";
import { Component, Prop, Vue } from "vue-property-decorator";
@Component
@ -90,14 +124,47 @@ export default class EventFullDate extends Vue {
@Prop({ required: false, default: true }) showEndTime!: boolean;
@Prop({ required: false }) timezone!: string;
@Prop({ required: false }) userTimezone!: string;
showLocalTimezone = true;
get timezoneToShow(): string {
if (this.showLocalTimezone) {
return this.timezone;
}
return this.userActualTimezone;
}
get userActualTimezone(): string {
if (this.userTimezone) {
return this.userTimezone;
}
return Intl.DateTimeFormat().resolvedOptions().timeZone;
}
formatDate(value: Date): string | undefined {
if (!this.$options.filters) return undefined;
return this.$options.filters.formatDateString(value);
}
formatTime(value: Date): string | undefined {
formatTime(value: Date, timezone: string): string | undefined {
if (!this.$options.filters) return undefined;
return this.$options.filters.formatTimeString(value);
return this.$options.filters.formatTimeString(value, timezone || undefined);
}
formatDateTimeString(
value: Date,
timezone: string,
showTime: boolean
): string | undefined {
if (!this.$options.filters) return undefined;
return this.$options.filters.formatDateTimeString(
value,
timezone,
showTime
);
}
isSameDay(): boolean {
@ -106,5 +173,37 @@ export default class EventFullDate extends Vue {
new Date(this.endsOn).toDateString();
return this.endsOn !== undefined && sameDay;
}
get differentFromUserTimezone(): boolean {
return (
!!this.timezone &&
!!this.userActualTimezone &&
getTimezoneOffset(this.timezone) !==
getTimezoneOffset(this.userActualTimezone) &&
this.timezone !== this.userActualTimezone
);
}
get singleTimeZone(): string {
if (this.showLocalTimezone) {
return this.$t("Local time ({timezone})", {
timezone: this.timezoneToShow,
}) as string;
}
return this.$t("Time in your timezone ({timezone})", {
timezone: this.timezoneToShow,
}) as string;
}
get multipleTimeZones(): string {
if (this.showLocalTimezone) {
return this.$t("Local time ({timezone})", {
timezone: this.timezoneToShow,
}) as string;
}
return this.$t("Times in your timezone ({timezone})", {
timezone: this.timezoneToShow,
}) as string;
}
}
</script>

View File

@ -12,7 +12,7 @@
<h2 class="title">{{ event.title }}</h2>
</router-link>
</div>
<div class="participation-actor has-text-grey">
<div class="participation-actor has-text-grey-dark">
<span v-if="event.physicalAddress && event.physicalAddress.locality">
{{ event.physicalAddress.locality }}
</span>
@ -128,6 +128,7 @@ export default class EventListViewCard extends mixins(ActorMixin, EventMixin) {
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
article.box {
div.content {
padding: 5px;
@ -148,7 +149,7 @@ article.box {
div.date-component {
flex: 0;
margin-right: 16px;
@include margin-right(16px);
}
.title {

View File

@ -0,0 +1,176 @@
<template>
<div class="modal-card">
<header class="modal-card-head">
<button type="button" class="delete" @click="$emit('close')" />
</header>
<div class="modal-card-body">
<section class="map">
<map-leaflet
:coords="physicalAddress.geom"
:marker="{
text: physicalAddress.fullName,
icon: physicalAddress.poiInfos.poiIcon.icon,
}"
/>
</section>
<section class="columns is-centered map-footer">
<div class="column is-half has-text-centered">
<p class="address">
<i class="mdi mdi-map-marker"></i>
{{ physicalAddress.fullName }}
</p>
<p class="getting-there">{{ $t("Getting there") }}</p>
<div
class="buttons"
v-if="
addressLinkToRouteByCar ||
addressLinkToRouteByBike ||
addressLinkToRouteByFeet
"
>
<a
class="button"
target="_blank"
v-if="addressLinkToRouteByFeet"
:href="addressLinkToRouteByFeet"
>
<i class="mdi mdi-walk"></i
></a>
<a
class="button"
target="_blank"
v-if="addressLinkToRouteByBike"
:href="addressLinkToRouteByBike"
>
<i class="mdi mdi-bike"></i
></a>
<a
class="button"
target="_blank"
v-if="addressLinkToRouteByTransit"
:href="addressLinkToRouteByTransit"
>
<i class="mdi mdi-bus"></i
></a>
<a
class="button"
target="_blank"
v-if="addressLinkToRouteByCar"
:href="addressLinkToRouteByCar"
>
<i class="mdi mdi-car"></i>
</a>
</div>
</div>
</section>
</div>
</div>
</template>
<script lang="ts">
import { Address, IAddress } from "@/types/address.model";
import { RoutingTransportationType, RoutingType } from "@/types/enums";
import { PropType } from "vue";
import { Component, Vue, Prop } from "vue-property-decorator";
const RoutingParamType = {
[RoutingType.OPENSTREETMAP]: {
[RoutingTransportationType.FOOT]: "engine=fossgis_osrm_foot",
[RoutingTransportationType.BIKE]: "engine=fossgis_osrm_bike",
[RoutingTransportationType.TRANSIT]: null,
[RoutingTransportationType.CAR]: "engine=fossgis_osrm_car",
},
[RoutingType.GOOGLE_MAPS]: {
[RoutingTransportationType.FOOT]: "dirflg=w",
[RoutingTransportationType.BIKE]: "dirflg=b",
[RoutingTransportationType.TRANSIT]: "dirflg=r",
[RoutingTransportationType.CAR]: "driving",
},
};
@Component({
components: {
"map-leaflet": () =>
import(/* webpackChunkName: "map" */ "../../components/Map.vue"),
},
})
export default class EventMap extends Vue {
@Prop({ type: Object as PropType<IAddress> }) address!: IAddress;
@Prop({ type: String }) routingType!: RoutingType;
get physicalAddress(): Address | null {
if (!this.address) return null;
return new Address(this.address);
}
makeNavigationPath(
transportationType: RoutingTransportationType
): string | undefined {
const geometry = this.physicalAddress?.geom;
if (geometry) {
/**
* build urls to routing map
*/
if (!RoutingParamType[this.routingType][transportationType]) {
return;
}
const urlGeometry = geometry.split(";").reverse().join(",");
switch (this.routingType) {
case RoutingType.GOOGLE_MAPS:
return `https://maps.google.com/?saddr=Current+Location&daddr=${urlGeometry}&${
RoutingParamType[this.routingType][transportationType]
}`;
case RoutingType.OPENSTREETMAP:
default: {
const bboxX = geometry.split(";").reverse()[0];
const bboxY = geometry.split(";").reverse()[1];
return `https://www.openstreetmap.org/directions?from=&to=${urlGeometry}&${
RoutingParamType[this.routingType][transportationType]
}#map=14/${bboxX}/${bboxY}`;
}
}
}
}
get addressLinkToRouteByCar(): undefined | string {
return this.makeNavigationPath(RoutingTransportationType.CAR);
}
get addressLinkToRouteByBike(): undefined | string {
return this.makeNavigationPath(RoutingTransportationType.BIKE);
}
get addressLinkToRouteByFeet(): undefined | string {
return this.makeNavigationPath(RoutingTransportationType.FOOT);
}
get addressLinkToRouteByTransit(): undefined | string {
return this.makeNavigationPath(RoutingTransportationType.TRANSIT);
}
}
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
.modal-card-head {
justify-content: flex-end;
button.delete {
@include margin-right(1rem);
}
}
section.map {
height: calc(100% - 8rem);
width: calc(100% - 20px);
}
section.map-footer {
p.address {
margin: 1rem auto;
}
div.buttons {
justify-content: center;
}
}
</style>

View File

@ -14,9 +14,9 @@
/>
</span>
<b-icon v-else-if="icon" :icon="icon" size="is-medium" />
<p :class="{ 'padding-left': icon }">
<div class="content-wrapper" :class="{ 'padding-left': icon }">
<slot></slot>
</p>
</div>
</div>
</div>
</template>
@ -42,8 +42,9 @@ div.eventMetadataBlock {
align-items: center;
margin-bottom: 1.75rem;
p {
.content-wrapper {
overflow: hidden;
width: 100%;
&.padding-left {
padding: 0 20px;

View File

@ -9,6 +9,7 @@
:src="`/img/${metadataItem.icon.substring(8)}_monochrome.svg`"
width="24"
height="24"
alt=""
/>
<b-icon v-else-if="metadataItem.icon" :icon="metadataItem.icon" />
@ -130,11 +131,12 @@ export default class EventMetadataItem extends Vue {
}
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
.card .media {
align-items: center;
& > button {
margin-left: 1rem;
@include margin-left(1rem);
}
}
</style>

View File

@ -9,9 +9,14 @@
/>
</div>
</div>
<b-field grouped :label="$t('Find or add an element')">
<b-field
grouped
:label="$t('Find or add an element')"
label-for="event-metadata-autocomplete"
>
<b-autocomplete
expanded
:clear-on-select="true"
v-model="search"
ref="autocomplete"
:data="filteredDataArray"
@ -19,7 +24,9 @@
group-options="items"
open-on-focus
:placeholder="$t('e.g. Accessibility, Twitch, PeerTube')"
id="event-metadata-autocomplete"
@select="(option) => addElement(option)"
dir="auto"
>
<template slot-scope="props">
<div class="media">
@ -32,6 +39,7 @@
:src="`/img/${props.option.icon.substring(8)}_monochrome.svg`"
width="24"
height="24"
alt=""
/>
<b-icon v-else-if="props.option.icon" :icon="props.option.icon" />
<b-icon v-else icon="help-circle" />
@ -55,7 +63,11 @@
</b-button>
</p>
</b-field>
<b-modal has-modal-card v-model="showNewElementModal">
<b-modal
has-modal-card
v-model="showNewElementModal"
:close-button-aria-label="$t('Close')"
>
<div class="modal-card">
<header class="modal-card-head">
<button
@ -129,7 +141,10 @@ export default class EventMetadataList extends Vue {
}
set metadata(metadata: IEventMetadata[]) {
this.$emit("input", metadata);
this.$emit(
"input",
metadata.filter((elem) => elem)
);
}
localizedCategories: Record<EventMetadataCategories, string> = {

View File

@ -1,31 +1,22 @@
<template>
<div>
<event-metadata-block
v-if="!event.options.isOnline"
:title="$t('Location')"
:icon="physicalAddress ? physicalAddress.poiInfos.poiIcon.icon : 'earth'"
>
<div class="address-wrapper">
<span v-if="!physicalAddress">{{ $t("No address defined") }}</span>
<div class="address" v-if="physicalAddress">
<div>
<address>
<p
class="addressDescription"
:title="physicalAddress.poiInfos.name"
>
{{ physicalAddress.poiInfos.name }}
</p>
<p class="has-text-grey-dark">
{{ physicalAddress.poiInfos.alternativeName }}
</p>
</address>
</div>
<span
<address-info :address="physicalAddress" />
<b-button
type="is-text"
class="map-show-button"
@click="showMap = !showMap"
@click="$emit('showMapModal', true)"
v-if="physicalAddress.geom"
>{{ $t("Show map") }}</span
>
{{ $t("Show map") }}
</b-button>
</div>
</div>
</event-metadata-block>
@ -34,6 +25,8 @@
:beginsOn="event.beginsOn"
:show-start-time="event.options.showStartTime"
:show-end-time="event.options.showEndTime"
:timezone="event.options.timezone"
:userTimezone="userTimezone"
:endsOn="event.endsOn"
/>
</event-metadata-block>
@ -140,91 +133,12 @@
>
<span v-else>{{ extra.value }}</span>
</event-metadata-block>
<b-modal
class="map-modal"
v-if="physicalAddress && physicalAddress.geom"
:active.sync="showMap"
has-modal-card
full-screen
>
<div class="modal-card">
<header class="modal-card-head">
<button type="button" class="delete" @click="showMap = false" />
</header>
<div class="modal-card-body">
<section class="map">
<map-leaflet
:coords="physicalAddress.geom"
:marker="{
text: physicalAddress.fullName,
icon: physicalAddress.poiInfos.poiIcon.icon,
}"
/>
</section>
<section class="columns is-centered map-footer">
<div class="column is-half has-text-centered">
<p class="address">
<i class="mdi mdi-map-marker"></i>
{{ physicalAddress.fullName }}
</p>
<p class="getting-there">{{ $t("Getting there") }}</p>
<div
class="buttons"
v-if="
addressLinkToRouteByCar ||
addressLinkToRouteByBike ||
addressLinkToRouteByFeet
"
>
<a
class="button"
target="_blank"
v-if="addressLinkToRouteByFeet"
:href="addressLinkToRouteByFeet"
>
<i class="mdi mdi-walk"></i
></a>
<a
class="button"
target="_blank"
v-if="addressLinkToRouteByBike"
:href="addressLinkToRouteByBike"
>
<i class="mdi mdi-bike"></i
></a>
<a
class="button"
target="_blank"
v-if="addressLinkToRouteByTransit"
:href="addressLinkToRouteByTransit"
>
<i class="mdi mdi-bus"></i
></a>
<a
class="button"
target="_blank"
v-if="addressLinkToRouteByCar"
:href="addressLinkToRouteByCar"
>
<i class="mdi mdi-car"></i>
</a>
</div>
</div>
</section>
</div>
</div>
</b-modal>
</div>
</template>
<script lang="ts">
import { Address } from "@/types/address.model";
import { IConfig } from "@/types/config.model";
import {
EventMetadataKeyType,
EventMetadataType,
RoutingTransportationType,
RoutingType,
} from "@/types/enums";
import { EventMetadataKeyType, EventMetadataType } from "@/types/enums";
import { IEvent } from "@/types/event.model";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
@ -234,11 +148,13 @@ import EventMetadataBlock from "./EventMetadataBlock.vue";
import EventFullDate from "./EventFullDate.vue";
import PopoverActorCard from "../Account/PopoverActorCard.vue";
import ActorCard from "../../components/Account/ActorCard.vue";
import AddressInfo from "../../components/Address/AddressInfo.vue";
import {
IEventMetadata,
IEventMetadataDescription,
} from "@/types/event-metadata";
import { eventMetaDataList } from "../../services/EventMetadata";
import { IUser } from "@/types/current-user.model";
@Component({
components: {
@ -246,15 +162,14 @@ import { eventMetaDataList } from "../../services/EventMetadata";
EventFullDate,
PopoverActorCard,
ActorCard,
"map-leaflet": () =>
import(/* webpackChunkName: "map" */ "../../components/Map.vue"),
AddressInfo,
},
})
export default class EventMetadataSidebar extends Vue {
@Prop({ type: Object as PropType<IEvent>, required: true }) event!: IEvent;
@Prop({ type: Object as PropType<IConfig>, required: true }) config!: IConfig;
showMap = false;
@Prop({ required: true }) user!: IUser | undefined;
@Prop({ required: false, default: false }) showMap!: boolean;
RouteName = RouteName;
@ -265,21 +180,6 @@ export default class EventMetadataSidebar extends Vue {
EventMetadataType = EventMetadataType;
EventMetadataKeyType = EventMetadataKeyType;
RoutingParamType = {
[RoutingType.OPENSTREETMAP]: {
[RoutingTransportationType.FOOT]: "engine=fossgis_osrm_foot",
[RoutingTransportationType.BIKE]: "engine=fossgis_osrm_bike",
[RoutingTransportationType.TRANSIT]: null,
[RoutingTransportationType.CAR]: "engine=fossgis_osrm_car",
},
[RoutingType.GOOGLE_MAPS]: {
[RoutingTransportationType.FOOT]: "dirflg=w",
[RoutingTransportationType.BIKE]: "dirflg=b",
[RoutingTransportationType.TRANSIT]: "dirflg=r",
[RoutingTransportationType.CAR]: "driving",
},
};
get physicalAddress(): Address | null {
if (!this.event.physicalAddress) return null;
@ -296,50 +196,6 @@ export default class EventMetadataSidebar extends Vue {
});
}
makeNavigationPath(
transportationType: RoutingTransportationType
): string | undefined {
const geometry = this.physicalAddress?.geom;
if (geometry) {
const routingType = this.config.maps.routing.type;
/**
* build urls to routing map
*/
if (!this.RoutingParamType[routingType][transportationType]) {
return;
}
const urlGeometry = geometry.split(";").reverse().join(",");
switch (routingType) {
case RoutingType.GOOGLE_MAPS:
return `https://maps.google.com/?saddr=Current+Location&daddr=${urlGeometry}&${this.RoutingParamType[routingType][transportationType]}`;
case RoutingType.OPENSTREETMAP:
default: {
const bboxX = geometry.split(";").reverse()[0];
const bboxY = geometry.split(";").reverse()[1];
return `https://www.openstreetmap.org/directions?from=&to=${urlGeometry}&${this.RoutingParamType[routingType][transportationType]}#map=14/${bboxX}/${bboxY}`;
}
}
}
}
get addressLinkToRouteByCar(): undefined | string {
return this.makeNavigationPath(RoutingTransportationType.CAR);
}
get addressLinkToRouteByBike(): undefined | string {
return this.makeNavigationPath(RoutingTransportationType.BIKE);
}
get addressLinkToRouteByFeet(): undefined | string {
return this.makeNavigationPath(RoutingTransportationType.FOOT);
}
get addressLinkToRouteByTransit(): undefined | string {
return this.makeNavigationPath(RoutingTransportationType.TRANSIT);
}
urlToHostname(url: string): string | null {
try {
return new URL(url).hostname;
@ -372,6 +228,10 @@ export default class EventMetadataSidebar extends Vue {
}
}
}
get userTimezone(): string | undefined {
return this.user?.settings?.timezone;
}
}
</script>
<style lang="scss" scoped>
@ -401,50 +261,6 @@ div.address-wrapper {
.map-show-button {
cursor: pointer;
}
address {
font-style: normal;
flex-wrap: wrap;
display: flex;
justify-content: flex-start;
span.addressDescription {
text-overflow: ellipsis;
white-space: nowrap;
flex: 1 0 auto;
min-width: 100%;
max-width: 4rem;
overflow: hidden;
}
:not(.addressDescription) {
flex: 1;
min-width: 100%;
}
}
}
}
.map-modal {
.modal-card-head {
justify-content: flex-end;
button.delete {
margin-right: 1rem;
}
}
section.map {
height: calc(100% - 8rem);
width: calc(100% - 20px);
}
section.map-footer {
p.address {
margin: 1rem auto;
}
div.buttons {
justify-content: center;
}
}
}
</style>

View File

@ -1,19 +1,58 @@
<template>
<router-link
class="event-minimalist-card-wrapper"
dir="auto"
:to="{ name: RouteName.EVENT, params: { uuid: event.uuid } }"
>
<date-calendar-icon
class="calendar-icon"
:date="event.beginsOn"
:small="true"
/>
<div class="title-info-wrapper">
<p class="event-minimalist-title">{{ event.title }}</p>
<p v-if="event.physicalAddress" class="has-text-grey">
{{ event.physicalAddress.description }}
</p>
<p v-else>
<div class="event-preview mr-0 ml-0">
<div>
<div class="date-component">
<date-calendar-icon :date="event.beginsOn" :small="true" />
</div>
<lazy-image-wrapper
:picture="event.picture"
:rounded="true"
style="height: 100%; position: absolute; top: 0; left: 0; width: 100%"
/>
</div>
</div>
<div class="title-info-wrapper has-text-grey-dark">
<h3 class="event-minimalist-title" :lang="event.language" dir="auto">
<b-tag
class="mr-2"
type="is-warning"
size="is-medium"
v-if="event.draft"
>{{ $t("Draft") }}</b-tag
>
{{ event.title }}
</h3>
<inline-address
v-if="event.physicalAddress"
class="event-subtitle"
:physical-address="event.physicalAddress"
/>
<div
class="event-subtitle"
v-else-if="event.options && event.options.isOnline"
>
<b-icon icon="video" />
<span>{{ $t("Online") }}</span>
</div>
<div class="event-subtitle event-organizer" v-if="showOrganizer">
<figure
class="image is-24x24"
v-if="organizer(event) && organizer(event).avatar"
>
<img class="is-rounded" :src="organizer(event).avatar.url" alt="" />
</figure>
<b-icon v-else icon="account-circle" />
<span class="organizer-name">
{{ organizerDisplayName(event) }}
</span>
</div>
<p class="participant-metadata">
<b-icon icon="account-multiple" />
<span v-if="event.options.maximumAttendeeCapacity !== 0">
{{
$tc(
@ -64,44 +103,88 @@
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { IEvent } from "@/types/event.model";
import { IEvent, organizer, organizerDisplayName } from "@/types/event.model";
import DateCalendarIcon from "@/components/Event/DateCalendarIcon.vue";
import { ParticipantRole } from "@/types/enums";
import RouteName from "../../router/name";
import LazyImageWrapper from "@/components/Image/LazyImageWrapper.vue";
import InlineAddress from "@/components/Address/InlineAddress.vue";
@Component({
components: {
DateCalendarIcon,
LazyImageWrapper,
InlineAddress,
},
})
export default class EventMinimalistCard extends Vue {
@Prop({ required: true, type: Object }) event!: IEvent;
@Prop({ required: false, type: Boolean, default: false })
showOrganizer!: boolean;
RouteName = RouteName;
ParticipantRole = ParticipantRole;
organizerDisplayName = organizerDisplayName;
organizer = organizer;
}
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
@use "@/styles/_event-card";
@import "~bulma/sass/utilities/mixins.sass";
@import "@/variables.scss";
.event-minimalist-card-wrapper {
display: flex;
width: 100%;
display: grid;
grid-gap: 5px 10px;
grid-template-areas: "preview" "body";
color: initial;
align-items: flex-start;
@include desktop {
grid-template-columns: 200px 3fr;
grid-template-areas: "preview body";
}
.event-preview {
& > div {
position: relative;
height: 120px;
width: 100%;
div.date-component {
display: flex;
position: absolute;
bottom: 5px;
left: 5px;
z-index: 1;
}
}
}
.calendar-icon {
margin-right: 1rem;
@include margin-right(1rem);
}
.title-info-wrapper {
flex: 2;
.event-minimalist-title {
color: #3c376e;
font-family: "Liberation Sans", "Helvetica Neue", Roboto, Helvetica, Arial,
serif;
font-size: 1.25rem;
font-weight: 700;
padding-bottom: 5px;
font-size: 18px;
line-height: 24px;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
font-weight: bold;
color: $title-color;
}
::v-deep .icon {
vertical-align: middle;
}
}
}

View File

@ -1,6 +1,6 @@
<template>
<article class="box">
<div class="identity-header">
<article class="box mb-5 mt-4">
<div class="identity-header" dir="auto">
<figure class="image is-24x24" v-if="participation.actor.avatar">
<img
class="is-rounded"
@ -10,80 +10,107 @@
width="24"
/>
</figure>
<b-icon v-else icon="account-circle" />
{{ displayNameAndUsername(participation.actor) }}
</div>
<div class="list-card">
<div class="date-component">
<date-calendar-icon
:date="participation.event.beginsOn"
:small="true"
/>
</div>
<div class="content-and-actions">
<div class="list-card-content">
<div class="title-wrapper">
<div class="event-preview mr-0 ml-0">
<div>
<div class="date-component">
<date-calendar-icon
:date="participation.event.beginsOn"
:small="true"
/>
</div>
<router-link
:to="{
name: RouteName.EVENT,
params: { uuid: participation.event.uuid },
}"
>
<h3 class="title">{{ participation.event.title }}</h3>
<lazy-image-wrapper
:rounded="true"
:picture="participation.event.picture"
style="
height: 100%;
position: absolute;
top: 0;
left: 0;
width: 100%;
"
/>
</router-link>
</div>
<div class="participation-actor">
<span>
<b-icon
icon="earth"
v-if="participation.event.visibility === EventVisibility.PUBLIC"
/>
<b-icon
icon="link"
v-else-if="
participation.event.visibility === EventVisibility.UNLISTED
"
/>
<b-icon
icon="lock"
v-else-if="
participation.event.visibility === EventVisibility.PRIVATE
"
/>
</span>
<span
v-if="
participation.event.physicalAddress &&
participation.event.physicalAddress.locality
"
>{{ participation.event.physicalAddress.locality }} -</span
</div>
<div class="list-card-content">
<div class="title-wrapper" dir="auto">
<router-link
:to="{
name: RouteName.EVENT,
params: { uuid: participation.event.uuid },
}"
>
<i18n
tag="span"
path="Organized by {name}"
v-if="organizerActor.id !== currentActor.id"
>
<popover-actor-card
slot="name"
:actor="organizerActor"
:inline="true"
>
{{ organizerActor.displayName() }}
</popover-actor-card>
</i18n>
<span v-else>{{ $t("Organized by you") }}</span>
<h3 class="title" :lang="participation.event.language">
{{ participation.event.title }}
</h3>
</router-link>
</div>
<div>
<inline-address
v-if="participation.event.physicalAddress"
class="event-subtitle"
:physical-address="participation.event.physicalAddress"
/>
<div
class="event-subtitle"
v-else-if="
participation.event.options &&
participation.event.options.isOnline
"
>
<b-icon icon="video" />
<span>{{ $t("Online") }}</span>
</div>
<div class="event-subtitle event-organizer">
<figure
class="image is-24x24"
v-if="
organizer(participation.event) &&
organizer(participation.event).avatar
"
>
<img
class="is-rounded"
:src="organizer(participation.event).avatar.url"
alt=""
/>
</figure>
<b-icon v-else icon="account-circle" />
<span class="organizer-name">
{{ organizerDisplayName(participation.event) }}
</span>
</div>
<div class="event-subtitle event-participants">
<b-icon
:class="{ 'has-text-danger': lastSeatsLeft }"
icon="account-group"
/>
<span
class="participant-stats"
v-if="
![
ParticipantRole.PARTICIPANT,
ParticipantRole.NOT_APPROVED,
].includes(participation.role)
"
v-if="participation.role !== ParticipantRole.NOT_APPROVED"
>
<!-- Less than 10 seats left -->
<span class="has-text-danger" v-if="lastSeatsLeft">
{{
$t("{number} seats left", {
number: seatsLeft,
})
}}
</span>
<span
v-if="participation.event.options.maximumAttendeeCapacity !== 0"
v-else-if="
participation.event.options.maximumAttendeeCapacity !== 0
"
>
{{
$tc(
@ -111,28 +138,27 @@
)
}}
</span>
<span v-if="participation.event.participantStats.notApproved > 0">
<b-button
type="is-text"
@click="
gotToWithCheck(participation, {
name: RouteName.PARTICIPATIONS,
query: { role: ParticipantRole.NOT_APPROVED },
params: { eventId: participation.event.uuid },
})
"
>
{{
$tc(
"{count} requests waiting",
participation.event.participantStats.notApproved,
{
count: participation.event.participantStats.notApproved,
}
)
}}
</b-button>
</span>
<b-button
v-if="participation.event.participantStats.notApproved > 0"
type="is-text"
@click="
gotToWithCheck(participation, {
name: RouteName.PARTICIPATIONS,
query: { role: ParticipantRole.NOT_APPROVED },
params: { eventId: participation.event.uuid },
})
"
>
{{
$tc(
"{count} requests waiting",
participation.event.participantStats.notApproved,
{
count: participation.event.participantStats.notApproved,
}
)
}}
</b-button>
</span>
</div>
</div>
@ -233,7 +259,11 @@ import { mixins } from "vue-class-component";
import { RawLocation, Route } from "vue-router";
import { EventVisibility, ParticipantRole } from "@/types/enums";
import { IParticipant } from "../../types/participant.model";
import { IEventCardOptions } from "../../types/event.model";
import {
IEventCardOptions,
organizer,
organizerDisplayName,
} from "../../types/event.model";
import { displayNameAndUsername, IActor, IPerson } from "../../types/actor";
import ActorMixin from "../../mixins/actor";
import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor";
@ -241,6 +271,9 @@ import EventMixin from "../../mixins/event";
import RouteName from "../../router/name";
import { changeIdentity } from "../../utils/auth";
import PopoverActorCard from "../Account/PopoverActorCard.vue";
import LazyImageWrapper from "@/components/Image/LazyImageWrapper.vue";
import InlineAddress from "@/components/Address/InlineAddress.vue";
import { PropType } from "vue";
const defaultOptions: IEventCardOptions = {
hideDate: true,
@ -254,6 +287,8 @@ const defaultOptions: IEventCardOptions = {
components: {
DateCalendarIcon,
PopoverActorCard,
LazyImageWrapper,
InlineAddress,
},
apollo: {
currentActor: {
@ -261,11 +296,15 @@ const defaultOptions: IEventCardOptions = {
},
},
})
export default class EventListCard extends mixins(ActorMixin, EventMixin) {
export default class EventParticipationCard extends mixins(
ActorMixin,
EventMixin
) {
/**
* The participation associated
*/
@Prop({ required: true }) participation!: IParticipant;
@Prop({ required: true, type: Object as PropType<IParticipant> })
participation!: IParticipant;
/**
* Options are merged with default options
@ -281,6 +320,10 @@ export default class EventListCard extends mixins(ActorMixin, EventMixin) {
displayNameAndUsername = displayNameAndUsername;
organizerDisplayName = organizerDisplayName;
organizer = organizer;
RouteName = RouteName;
get mergedOptions(): IEventCardOptions {
@ -304,13 +347,13 @@ export default class EventListCard extends mixins(ActorMixin, EventMixin) {
participation.actor.id !== this.currentActor.id &&
participation.event.organizerActor
) {
const organizer = participation.event.organizerActor as IPerson;
await changeIdentity(this.$apollo.provider.defaultClient, organizer);
const organizerActor = participation.event.organizerActor as IPerson;
await changeIdentity(this.$apollo.provider.defaultClient, organizerActor);
this.$buefy.notification.open({
message: this.$t(
"Current identity has been changed to {identityName} in order to manage this event.",
{
identityName: organizer.preferredUsername,
identityName: organizerActor.preferredUsername,
}
) as string,
type: "is-info",
@ -330,16 +373,37 @@ export default class EventListCard extends mixins(ActorMixin, EventMixin) {
}
return this.participation.event.organizerActor;
}
get seatsLeft(): number | null {
if (this.participation.event.options.maximumAttendeeCapacity > 0) {
return (
this.participation.event.options.maximumAttendeeCapacity -
this.participation.event.participantStats.participant
);
}
return null;
}
get lastSeatsLeft(): boolean {
if (this.seatsLeft) {
return this.seatsLeft < 10;
}
return false;
}
}
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
@use "@/styles/_event-card";
@import "~bulma/sass/utilities/mixins.sass";
article.box {
div.tag-container {
position: absolute;
top: 10px;
right: 0;
margin-right: -5px;
@include margin-left(-5px);
z-index: 10;
max-width: 40%;
@ -359,49 +423,67 @@ article.box {
.list-card {
display: flex;
padding: 0 6px;
padding: 0 6px 0 0;
position: relative;
flex-direction: column;
div.date-component {
align-self: flex-start;
padding: 5px;
position: absolute;
top: 0;
left: 0;
margin-top: 1px;
height: 0;
display: flex;
align-items: flex-end;
margin-bottom: 15px;
margin-left: 0rem;
}
.content-and-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
padding-bottom: 1rem;
display: grid;
grid-gap: 5px 10px;
grid-template-areas: "preview" "body" "actions";
@include tablet {
grid-template-columns: 1fr 3fr;
grid-template-areas: "preview body" "actions actions";
}
@include desktop {
grid-template-columns: 1fr 3fr 1fr;
grid-template-areas: "preview body actions";
}
.event-preview {
grid-area: preview;
& > div {
height: 128px;
width: 100%;
position: relative;
div.date-component {
display: flex;
position: absolute;
bottom: 5px;
left: 5px;
z-index: 1;
}
img {
width: 100%;
object-position: center;
object-fit: cover;
height: 100%;
}
}
}
.actions {
padding-right: 7.5px;
padding: 7px;
cursor: pointer;
align-self: center;
justify-self: center;
grid-area: actions;
}
div.list-card-content {
flex: 1;
padding: 5px;
min-width: 350px;
grid-area: body;
.participation-actor span,
.participant-stats span {
.participant-stats {
display: flex;
align-items: center;
padding: 0 5px;
button {
height: auto;
padding-top: 0;
}
}
div.title-wrapper {
@ -419,11 +501,11 @@ article.box {
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
font-weight: 400;
line-height: 1em;
font-size: 1.4em;
padding-bottom: 5px;
font-size: 18px;
line-height: 24px;
margin: auto 0;
font-weight: bold;
color: $title-color;
}
}
}
@ -434,10 +516,10 @@ article.box {
background: $yellow-2;
display: flex;
padding: 5px;
padding-left: calc(48px + 15px);
figure {
padding-right: 3px;
figure,
span.icon {
@include padding-right(3px);
}
}

View File

@ -1,59 +1,94 @@
<template>
<div class="address-autocomplete">
<b-field expanded>
<template slot="label">
{{ actualLabel }}
<b-button
v-if="canShowLocateMeButton && !gettingLocation"
size="is-small"
icon-right="map-marker"
@click="locateMe"
/>
<span v-else-if="gettingLocation">{{ $t("Getting location") }}</span>
</template>
<b-autocomplete
:data="addressData"
v-model="queryText"
:placeholder="$t('e.g. 10 Rue Jangot')"
field="fullName"
:loading="isFetching"
@typing="fetchAsyncData"
icon="map-marker"
<div class="address-autocomplete columns is-desktop">
<div class="column">
<b-field
:label-for="id"
expanded
@select="updateSelected"
:message="fieldErrors"
:type="{ 'is-danger': fieldErrors.length }"
>
<template #default="{ option }">
<b-icon :icon="option.poiInfos.poiIcon.icon" />
<b>{{ option.poiInfos.name }}</b
><br />
<small>{{ option.poiInfos.alternativeName }}</small>
<template slot="label">
{{ actualLabel }}
<span
class="is-size-6 has-text-weight-normal"
v-if="gettingLocation"
>{{ $t("Getting location") }}</span
>
</template>
<template slot="empty">
<span v-if="isFetching">{{ $t("Searching") }}</span>
<div v-else-if="queryText.length >= 3" class="is-enabled">
<span>{{ $t('No results for "{queryText}"', { queryText }) }}</span>
<span>{{
$t(
"You can try another search term or drag and drop the marker on the map",
{
queryText,
}
)
}}</span>
<!-- <p class="control" @click="openNewAddressModal">-->
<!-- <button type="button" class="button is-primary">{{ $t('Add') }}</button>-->
<!-- </p>-->
</div>
</template>
</b-autocomplete>
<b-button
:disabled="!queryText"
@click="resetAddress"
class="reset-area"
icon-left="close"
/>
</b-field>
<div class="map" v-if="selected && selected.geom && selected.poiInfos">
<p class="control" v-if="canShowLocateMeButton && !gettingLocation">
<b-button
icon-right="map-marker"
@click="locateMe"
:title="$t('Use my location')"
/>
</p>
<b-autocomplete
:data="addressData"
v-model="queryText"
:placeholder="$t('e.g. 10 Rue Jangot')"
field="fullName"
:loading="isFetching"
@typing="fetchAsyncData"
:icon="canShowLocateMeButton ? null : 'map-marker'"
expanded
@select="updateSelected"
v-bind="$attrs"
:id="id"
:disabled="disabled"
dir="auto"
>
<template #default="{ option }">
<b-icon :icon="option.poiInfos.poiIcon.icon" />
<b>{{ option.poiInfos.name }}</b
><br />
<small>{{ option.poiInfos.alternativeName }}</small>
</template>
<template #empty>
<span v-if="isFetching">{{ $t("Searching") }}</span>
<div v-else-if="queryText.length >= 3" class="is-enabled">
<span>{{
$t('No results for "{queryText}"', { queryText })
}}</span>
<span>{{
$t(
"You can try another search term or drag and drop the marker on the map",
{
queryText,
}
)
}}</span>
<!-- <p class="control" @click="openNewAddressModal">-->
<!-- <button type="button" class="button is-primary">{{ $t('Add') }}</button>-->
<!-- </p>-->
</div>
</template>
</b-autocomplete>
<b-button
:disabled="!queryText"
@click="resetAddress"
class="reset-area"
icon-left="close"
:title="$t('Clear address field')"
/>
</b-field>
<div
class="card"
v-if="!hideSelected && (selected.originId || selected.url)"
>
<div class="card-content">
<address-info
:address="selected"
:show-icon="true"
:show-timezone="true"
:user-timezone="userTimezone"
/>
</div>
</div>
</div>
<div
class="map column"
v-if="!hideMap && selected && selected.geom && selected.poiInfos"
>
<map-leaflet
:coords="selected.geom"
:marker="{
@ -65,149 +100,47 @@
:readOnly="false"
/>
</div>
<!-- <b-modal v-if="selected" :active.sync="addressModalActive" :width="640" has-modal-card scroll="keep">-->
<!-- <div class="modal-card" style="width: auto">-->
<!-- <header class="modal-card-head">-->
<!-- <p class="modal-card-title">{{ $t('Add an address') }}</p>-->
<!-- </header>-->
<!-- <section class="modal-card-body">-->
<!-- <form>-->
<!-- <b-field :label="$t('Name')">-->
<!-- <b-input aria-required="true" required v-model="selected.description" />-->
<!-- </b-field>-->
<!-- <b-field :label="$t('Street')">-->
<!-- <b-input v-model="selected.street" />-->
<!-- </b-field>-->
<!-- <b-field grouped>-->
<!-- <b-field :label="$t('Postal Code')">-->
<!-- <b-input v-model="selected.postalCode" />-->
<!-- </b-field>-->
<!-- <b-field :label="$t('Locality')">-->
<!-- <b-input v-model="selected.locality" />-->
<!-- </b-field>-->
<!-- </b-field>-->
<!-- <b-field grouped>-->
<!-- <b-field :label="$t('Region')">-->
<!-- <b-input v-model="selected.region" />-->
<!-- </b-field>-->
<!-- <b-field :label="$t('Country')">-->
<!-- <b-input v-model="selected.country" />-->
<!-- </b-field>-->
<!-- </b-field>-->
<!-- </form>-->
<!-- </section>-->
<!-- <footer class="modal-card-foot">-->
<!-- <button class="button" type="button" @click="resetPopup()">{{ $t('Clear') }}</button>-->
<!-- </footer>-->
<!-- </div>-->
<!-- </b-modal>-->
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import { Component, Prop, Watch, Mixins } from "vue-property-decorator";
import { LatLng } from "leaflet";
import debounce from "lodash/debounce";
import { DebouncedFunc } from "lodash";
import { Address, IAddress } from "../../types/address.model";
import { ADDRESS, REVERSE_GEOCODE } from "../../graphql/address";
import { CONFIG } from "../../graphql/config";
import { IConfig } from "../../types/config.model";
import AddressAutoCompleteMixin from "../../mixins/AddressAutoCompleteMixin";
import AddressInfo from "../../components/Address/AddressInfo.vue";
@Component({
inheritAttrs: false,
components: {
"map-leaflet": () =>
import(/* webpackChunkName: "map" */ "@/components/Map.vue"),
},
apollo: {
config: CONFIG,
AddressInfo,
},
})
export default class FullAddressAutoComplete extends Vue {
@Prop({ required: true }) value!: IAddress;
export default class FullAddressAutoComplete extends Mixins(
AddressAutoCompleteMixin
) {
@Prop({ required: false, default: "" }) label!: string;
addressData: IAddress[] = [];
selected: IAddress = new Address();
isFetching = false;
queryText: string = (this.value && new Address(this.value).fullName) || "";
@Prop({ required: false }) userTimezone!: string;
@Prop({ required: false, default: false, type: Boolean }) disabled!: boolean;
@Prop({ required: false, default: false, type: Boolean }) hideMap!: boolean;
@Prop({ required: false, default: false, type: Boolean })
hideSelected!: boolean;
addressModalActive = false;
private gettingLocation = false;
private static componentId = 0;
// eslint-disable-next-line no-undef
private location!: GeolocationPosition;
private gettingLocationError: any;
private mapDefaultZoom = 15;
config!: IConfig;
fetchAsyncData!: DebouncedFunc<(query: string) => Promise<void>>;
// We put this in data because of issues like
// https://github.com/vuejs/vue-class-component/issues/263
data(): Record<string, unknown> {
return {
fetchAsyncData: debounce(this.asyncData, 200),
};
created(): void {
FullAddressAutoComplete.componentId += 1;
}
async asyncData(query: string): Promise<void> {
if (!query.length) {
this.addressData = [];
this.selected = new Address();
return;
}
if (query.length < 3) {
this.addressData = [];
return;
}
this.isFetching = true;
const result = await this.$apollo.query({
query: ADDRESS,
fetchPolicy: "network-only",
variables: {
query,
locale: this.$i18n.locale,
},
});
this.addressData = result.data.searchAddress.map(
(address: IAddress) => new Address(address)
);
this.isFetching = false;
}
@Watch("config")
watchConfig(config: IConfig): void {
if (!config.geocoding.autocomplete) {
// If autocomplete is disabled, we put a larger debounce value
// so that we don't request with incomplete address
this.fetchAsyncData = debounce(this.asyncData, 2000);
}
get id(): string {
return `full-address-autocomplete-${FullAddressAutoComplete.componentId}`;
}
@Watch("value")
updateEditing(): void {
if (!(this.value && this.value.id)) return;
this.selected = this.value;
const address = new Address(this.selected);
if (address.poiInfos) {
this.queryText = `${address.poiInfos.name} ${address.poiInfos.alternativeName}`;
}
}
updateSelected(option: IAddress): void {
@ -225,30 +158,6 @@ export default class FullAddressAutoComplete extends Vue {
this.addressModalActive = true;
}
async reverseGeoCode(e: LatLng, zoom: number): Promise<void> {
// If the position has been updated through autocomplete selection, no need to geocode it!
if (this.checkCurrentPosition(e)) return;
const result = await this.$apollo.query({
query: REVERSE_GEOCODE,
variables: {
latitude: e.lat,
longitude: e.lng,
zoom,
locale: this.$i18n.locale,
},
});
this.addressData = result.data.reverseGeocode.map(
(address: IAddress) => new Address(address)
);
if (this.addressData.length > 0) {
const defaultAddress = new Address(this.addressData[0]);
this.selected = defaultAddress;
this.$emit("input", this.selected);
this.queryText = `${defaultAddress.poiInfos.name} ${defaultAddress.poiInfos.alternativeName}`;
}
}
checkCurrentPosition(e: LatLng): boolean {
if (!this.selected || !this.selected.geom) return false;
const lat = parseFloat(this.selected.geom.split(";")[1]);
@ -257,25 +166,6 @@ export default class FullAddressAutoComplete extends Vue {
return e.lat === lat && e.lng === lon;
}
async locateMe(): Promise<void> {
this.gettingLocation = true;
try {
this.gettingLocation = false;
this.location = await FullAddressAutoComplete.getLocation();
this.mapDefaultZoom = 12;
this.reverseGeoCode(
new LatLng(
this.location.coords.latitude,
this.location.coords.longitude
),
12
);
} catch (e) {
this.gettingLocation = false;
this.gettingLocationError = e.message;
}
}
get actualLabel(): string {
return this.label || (this.$t("Find an address") as string);
}
@ -284,38 +174,6 @@ export default class FullAddressAutoComplete extends Vue {
get canShowLocateMeButton(): boolean {
return window.isSecureContext;
}
// eslint-disable-next-line no-undef
static async getLocation(): Promise<GeolocationPosition> {
return new Promise((resolve, reject) => {
if (!("geolocation" in navigator)) {
reject(new Error("Geolocation is not available."));
}
navigator.geolocation.getCurrentPosition(
(pos) => {
resolve(pos);
},
(err) => {
reject(err);
}
);
});
}
@Watch("queryText")
resetAddressOnEmptyField(queryText: string): void {
if (queryText === "" && this.selected?.id) {
console.log("doing reset");
this.resetAddress();
}
}
resetAddress(): void {
this.$emit("input", null);
this.queryText = "";
this.selected = new Address();
}
}
</script>
<style lang="scss">

View File

@ -0,0 +1,90 @@
<template>
<div class="events-wrapper">
<div class="month-group" v-for="key of keys" :key="key">
<h2 class="is-size-5 month-name">
{{ monthName(groupEvents(key)[0]) }}
</h2>
<event-minimalist-card
class="py-4"
v-for="event in groupEvents(key)"
:key="event.id"
:event="event"
:isCurrentActorMember="isCurrentActorMember"
/>
</div>
</div>
</template>
<script lang="ts">
import { IEvent } from "@/types/event.model";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
import EventMinimalistCard from "./EventMinimalistCard.vue";
@Component({
components: {
EventMinimalistCard,
},
})
export default class GroupedMultiEventMinimalistCard extends Vue {
@Prop({ type: Array as PropType<IEvent[]>, required: true })
events!: IEvent[];
@Prop({ required: false, type: Boolean, default: false })
isCurrentActorMember!: boolean;
get monthlyGroupedEvents(): Map<string, IEvent[]> {
return this.events.reduce((acc: Map<string, IEvent[]>, event: IEvent) => {
const beginsOn = new Date(event.beginsOn);
const month = `${beginsOn.getUTCMonth()}-${beginsOn.getUTCFullYear()}`;
const monthEvents = acc.get(month) || [];
acc.set(month, [...monthEvents, event]);
return acc;
}, new Map());
}
get keys(): string[] {
return Array.from(this.monthlyGroupedEvents.keys()).sort((a, b) =>
b.localeCompare(a)
);
}
groupEvents(key: string): IEvent[] {
return this.monthlyGroupedEvents.get(key) || [];
}
monthName(event: IEvent): string {
const beginsOn = new Date(event.beginsOn);
return new Intl.DateTimeFormat(undefined, {
month: "long",
year: "numeric",
}).format(beginsOn);
}
}
</script>
<style lang="scss" scoped>
.events-wrapper {
display: grid;
grid-gap: 20px;
grid-template: 1fr;
}
.month-group {
.month-name {
text-transform: capitalize;
text-transform: capitalize;
display: inline-block;
position: relative;
font-size: 1.3rem;
&::after {
background: $orange-3;
position: absolute;
left: 0;
right: 0;
top: 100%;
content: "";
width: calc(100% + 30px);
height: 3px;
max-width: 150px;
}
}
}
</style>

View File

@ -0,0 +1,39 @@
<template>
<div class="multi-card-event">
<event-card
class="event-card"
v-for="event in events"
:event="event"
:key="event.uuid"
/>
</div>
</template>
<script lang="ts">
import { IEvent } from "@/types/event.model";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
import EventCard from "./EventCard.vue";
@Component({
components: {
EventCard,
},
})
export default class MultiCard extends Vue {
@Prop({ type: Array as PropType<IEvent[]>, required: true })
events!: IEvent[];
}
</script>
<style lang="scss" scoped>
.multi-card-event {
display: grid;
grid-auto-rows: 1fr;
grid-column-gap: 20px;
grid-row-gap: 30px;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
.event-card {
height: 100%;
display: flex;
flex-direction: column;
}
}
</style>

View File

@ -0,0 +1,38 @@
<template>
<div class="events-wrapper">
<event-minimalist-card
v-for="event in events"
:key="event.id"
:event="event"
:isCurrentActorMember="isCurrentActorMember"
:showOrganizer="showOrganizer"
/>
</div>
</template>
<script lang="ts">
import { IEvent } from "@/types/event.model";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
import EventMinimalistCard from "./EventMinimalistCard.vue";
@Component({
components: {
EventMinimalistCard,
},
})
export default class MultiEventMinimalistCard extends Vue {
@Prop({ type: Array as PropType<IEvent[]>, required: true })
events!: IEvent[];
@Prop({ required: false, type: Boolean, default: false })
isCurrentActorMember!: boolean;
@Prop({ required: false, type: Boolean, default: false })
showOrganizer!: boolean;
}
</script>
<style lang="scss" scoped>
.events-wrapper {
display: grid;
grid-gap: 20px;
grid-template: 1fr;
}
</style>

View File

@ -1,6 +1,7 @@
<template>
<div class="list is-hoverable">
<b-input
dir="auto"
:placeholder="$t('Filter by profile or group name')"
v-model="actorFilter"
/>
@ -11,10 +12,10 @@
v-for="availableActor in actualFilteredAvailableActors"
:key="availableActor.id"
>
<div class="media">
<div class="media" dir="auto">
<figure class="image is-48x48" v-if="availableActor.avatar">
<img
class="media-left is-rounded"
class="image is-rounded"
:src="availableActor.avatar.url"
alt=""
/>
@ -121,6 +122,7 @@ export default class OrganizerPicker extends Vue {
}
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
::v-deep .list-item {
box-sizing: content-box;
@ -133,11 +135,11 @@ export default class OrganizerPicker extends Vue {
figure.image,
span.icon.media-left {
margin-right: 0.5rem;
@include margin-right(0.5rem);
}
span.icon.media-left {
margin-left: -0.25rem;
@include margin-left(-0.25rem);
}
}
}

View File

@ -4,6 +4,7 @@
<div
v-if="inline && selectedActor.id"
class="inline box"
dir="auto"
@click="isComponentModalActive = true"
>
<div class="media">
@ -12,14 +13,14 @@
<img
class="image is-rounded"
:src="selectedActor.avatar.url"
:alt="selectedActor.avatar.alt"
:alt="selectedActor.avatar.alt || ''"
/>
</figure>
<b-icon v-else size="is-large" icon="account-circle" />
</div>
<div class="media-content" v-if="selectedActor.name">
<p class="is-4">{{ selectedActor.name }}</p>
<p class="is-6 has-text-grey">
<p class="is-6 has-text-grey-dark">
{{ `@${selectedActor.preferredUsername}` }}
</p>
</div>
@ -45,7 +46,11 @@
/>
<b-icon v-else size="is-large" icon="account-circle" />
</span>
<b-modal :active.sync="isComponentModalActive" has-modal-card>
<b-modal
:active.sync="isComponentModalActive"
has-modal-card
:close-button-aria-label="$t('Close')"
>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">{{ $t("Pick a profile or a group") }}</p>
@ -65,6 +70,7 @@
<b-input
:placeholder="$t('Filter by name')"
v-model="contactFilter"
dir="auto"
/>
<p
class="field"

View File

@ -30,18 +30,22 @@ A button to set your participation
position="is-bottom-left"
v-if="participation && participation.role === ParticipantRole.PARTICIPANT"
>
<button class="button is-success is-large" type="button" slot="trigger">
<b-icon icon="check" />
<template>
<span>{{ $t("I participate") }}</span>
</template>
<b-icon icon="menu-down" />
</button>
<template #trigger="{ active }">
<b-button
type="is-success"
size="is-large"
icon-left="check"
:icon-right="active ? 'menu-up' : 'menu-down'"
>
{{ $t("I participate") }}
</b-button>
</template>
<b-dropdown-item
:value="false"
aria-role="listitem"
@click="confirmLeave"
@keyup.enter="confirmLeave"
class="has-text-danger"
>{{ $t("Cancel my participation…") }}</b-dropdown-item
>
@ -73,6 +77,7 @@ A button to set your participation
:value="false"
aria-role="listitem"
@click="confirmLeave"
@keyup.enter="confirmLeave"
class="has-text-danger"
>{{ $t("Cancel my participation request…") }}</b-dropdown-item
>
@ -101,21 +106,25 @@ A button to set your participation
position="is-bottom-left"
v-else-if="!participation && currentActor.id"
>
<button class="button is-primary is-large" type="button" slot="trigger">
<template>
<span>{{ $t("Participate") }}</span>
</template>
<b-icon icon="menu-down" />
</button>
<template #trigger="{ active }">
<b-button
type="is-primary"
size="is-large"
:icon-right="active ? 'menu-up' : 'menu-down'"
>
{{ $t("Participate") }}
</b-button>
</template>
<b-dropdown-item
:value="true"
aria-role="listitem"
@click="joinEvent(currentActor)"
@keyup.enter="joinEvent(currentActor)"
>
<div class="media">
<div class="media-left">
<figure class="image is-32x32" v-if="currentActor.avatar">
<div class="media-left" v-if="currentActor.avatar">
<figure class="image is-32x32">
<img class="is-rounded" :src="currentActor.avatar.url" alt />
</figure>
</div>
@ -136,11 +145,13 @@ A button to set your participation
:value="false"
aria-role="listitem"
@click="joinModal"
@keyup.enter="joinModal"
v-if="identities.length > 1"
>{{ $t("with another identity…") }}</b-dropdown-item
>
</b-dropdown>
<b-button
rel="nofollow"
tag="router-link"
:to="{
name: RouteName.EVENT_PARTICIPATE_LOGGED_OUT,
@ -154,6 +165,7 @@ A button to set your participation
>
<b-button
tag="router-link"
rel="nofollow"
:to="{
name: RouteName.EVENT_PARTICIPATE_WITH_ACCOUNT,
params: { uuid: event.uuid },

View File

@ -86,11 +86,13 @@
/></a>
<a
:href="telegramShareUrl"
class="telegram"
target="_blank"
rel="nofollow noopener"
title="Telegram"
><b-icon icon="telegram" size="is-large" type="is-primary"
/></a>
>
<telegram-logo />
</a>
<a
:href="linkedInShareUrl"
target="_blank"
@ -126,11 +128,13 @@ import { EventStatus, EventVisibility } from "@/types/enums";
import { IEvent } from "../../types/event.model";
import DiasporaLogo from "../Share/DiasporaLogo.vue";
import MastodonLogo from "../Share/MastodonLogo.vue";
import TelegramLogo from "../Share/TelegramLogo.vue";
@Component({
components: {
DiasporaLogo,
MastodonLogo,
TelegramLogo,
},
})
export default class ShareEventModal extends Vue {
@ -207,7 +211,8 @@ export default class ShareEventModal extends Vue {
</script>
<style lang="scss" scoped>
.diaspora,
.mastodon {
.mastodon,
.telegram {
::v-deep span svg {
width: 2.25rem;
}

View File

@ -1,5 +1,5 @@
<template>
<b-field>
<b-field :label-for="id">
<template slot="label">
{{ $t("Add some tags") }}
<b-tooltip
@ -16,57 +16,82 @@
:data="filteredTags"
autocomplete
:allow-new="true"
:field="path"
:field="'title'"
icon="label"
maxlength="20"
maxtags="10"
:placeholder="$t('Eg: Stockholm, Dance, Chess…')"
@typing="getFilteredTags"
:id="id"
dir="auto"
>
</b-taginput>
</b-field>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import get from "lodash/get";
import differenceBy from "lodash/differenceBy";
import { ITag } from "../../types/tag.model";
import { FILTER_TAGS } from "@/graphql/tags";
@Component({
computed: {
tagsStrings: {
get() {
return this.$props.value.map((tag: ITag) => tag.title);
},
set(tagStrings) {
const tagEntities = tagStrings.map((tag: string | ITag) => {
if (typeof tag !== "string") {
return tag;
}
return { title: tag, slug: tag } as ITag;
});
this.$emit("input", tagEntities);
apollo: {
tags: {
query: FILTER_TAGS,
variables() {
return {
filter: this.text,
};
},
},
},
})
export default class TagInput extends Vue {
@Prop({ required: false, default: () => [] }) data!: ITag[];
@Prop({ required: true, default: "value" }) path!: string;
@Prop({ required: true }) value!: ITag[];
filteredTags: ITag[] = [];
tags!: ITag[];
getFilteredTags(text: string): void {
this.filteredTags = differenceBy(this.data, this.value, "id").filter(
text = "";
private static componentId = 0;
created(): void {
TagInput.componentId += 1;
}
get id(): string {
return `tag-input-${TagInput.componentId}`;
}
async getFilteredTags(text: string): Promise<void> {
this.text = text;
await this.$apollo.queries.tags.refetch();
}
get filteredTags(): ITag[] {
return differenceBy(this.tags, this.value, "id").filter(
(option) =>
get(option, this.path)
option.title
.toString()
.toLowerCase()
.indexOf(text.toLowerCase()) >= 0
.indexOf(this.text.toLowerCase()) >= 0 ||
option.slug.toString().toLowerCase().indexOf(this.text.toLowerCase()) >=
0
);
}
get tagsStrings(): string[] {
return (this.value || []).map((tag: ITag) => tag.title);
}
set tagsStrings(tagsStrings: string[]) {
const tagEntities = tagsStrings.map((tag: string | ITag) => {
if (typeof tag !== "string") {
return tag;
}
return { title: tag, slug: tag } as ITag;
});
this.$emit("input", tagEntities);
}
}
</script>

View File

@ -20,11 +20,17 @@
<ul>
<li>
<b-select
:aria-label="$t('Language')"
v-if="$i18n"
v-model="locale"
:placeholder="$t('Select a language')"
>
<option v-for="(language, lang) in langs" :value="lang" :key="lang">
<option
v-for="(language, lang) in langs"
:value="lang"
:key="lang"
:selected="isLangSelected(lang)"
>
{{ language }}
</option>
</b-select>
@ -46,6 +52,7 @@
</li>
<li>
<a
rel="external"
hreflang="en"
href="https://forge.april.org/Chapril/mobilizon.chapril.org-mobilizon/src/branch/chapril/LICENSE"
>
@ -55,19 +62,25 @@
<li>
<a href="mailto:mobilizon-support@chapril.org">{{ $t("Contact") }}</a>
</li>
<li>
<a href="#navbar">{{ $t("Back to top") }}</a>
</li>
</ul>
<div class="content has-text-centered">
<i18n
tag="span"
path="Powered by {mobilizon}. © 2018 - {date} The Mobilizon Contributors - Made with the financial support of {contributors}."
>
<a slot="mobilizon" href="https://joinmobilizon.org">{{
<a rel="external" slot="mobilizon" href="https://joinmobilizon.org">{{
$t("Mobilizon")
}}</a>
<span slot="date">{{ new Date().getFullYear() }}</span>
<a href="https://joinmobilizon.org/hall-of-fame" slot="contributors">{{
$t("more than 1360 contributors")
}}</a>
<a
rel="external"
href="https://joinmobilizon.org/hall-of-fame"
slot="contributors"
>{{ $t("more than 1360 contributors") }}</a
>
</i18n>
</div>
</footer>
@ -108,6 +121,10 @@ export default class Footer extends Vue {
this.locale = locale;
}
}
isLangSelected(lang: string): boolean {
return lang === this.locale;
}
}
</script>
<style lang="scss" scoped>
@ -152,6 +169,13 @@ footer.footer {
color: $white;
text-decoration: underline;
text-decoration-color: $secondary;
&:focus {
background-color: #000;
color: #fff;
outline: 3px solid #000;
text-decoration: none;
}
}
::v-deep span.select {

View File

@ -1,7 +1,21 @@
<template>
<div class="card">
<router-link
:to="{
name: RouteName.GROUP,
params: { preferredUsername: usernameWithDomain(group) },
}"
class="card"
>
<div class="card-image">
<figure class="image is-16by9">
<lazy-image-wrapper
:picture="group.banner"
style="height: 100%; position: absolute; top: 0; left: 0; width: 100%"
/>
</figure>
</div>
<div class="card-content">
<div class="media">
<div class="media mb-2">
<div class="media-left">
<figure class="image is-48x48" v-if="group.avatar">
<img class="is-rounded" :src="group.avatar.url" alt="" />
@ -9,40 +23,114 @@
<b-icon v-else size="is-large" icon="account-group" />
</div>
<div class="media-content">
<router-link
:to="{
name: RouteName.GROUP,
params: { preferredUsername: usernameWithDomain(group) },
}"
>
<h3>{{ group.name }}</h3>
<p class="is-6 has-text-grey">
<span v-if="group.domain">{{
`@${group.preferredUsername}@${group.domain}`
}}</span>
<span v-else>{{ `@${group.preferredUsername}` }}</span>
</p>
</router-link>
<h3 class="is-size-5 group-title" dir="auto">
{{ displayName(group) }}
</h3>
<span class="is-6 has-text-grey-dark group-federated-username">
{{ `@${usernameWithDomain(group)}` }}
</span>
</div>
</div>
<div class="content">
<p>{{ group.summary }}</p>
<div class="content mb-2" dir="auto" v-html="group.summary" />
<div class="card-custom-footer">
<inline-address
class="has-text-grey-dark"
v-if="group.physicalAddress"
:physicalAddress="group.physicalAddress"
/>
<p class="has-text-grey-dark">
<b-icon icon="account" />
{{
$tc(
"{count} members or followers",
group.members.total + group.followers.total,
{
count: group.members.total + group.followers.total,
}
)
}}
</p>
</div>
</div>
</div>
</router-link>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { IGroup, usernameWithDomain } from "@/types/actor";
import { displayName, IGroup, usernameWithDomain } from "@/types/actor";
import LazyImageWrapper from "@/components/Image/LazyImageWrapper.vue";
import RouteName from "../../router/name";
import InlineAddress from "@/components/Address/InlineAddress.vue";
@Component
@Component({
components: {
LazyImageWrapper,
InlineAddress,
},
})
export default class GroupCard extends Vue {
@Prop({ required: true }) group!: IGroup;
RouteName = RouteName;
usernameWithDomain = usernameWithDomain;
displayName = displayName;
}
</script>
<style lang="scss" scoped>
.card {
.card-content {
padding: 0.75rem;
display: flex;
flex-direction: column;
height: 100%;
.content {
flex: 1;
}
::v-deep .content {
& > *:first-child {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
margin-bottom: 0;
* {
font-weight: normal;
text-transform: none;
font-style: normal;
text-decoration: none;
}
}
& > *:not(:first-child) {
display: none;
}
}
.media-left {
margin-right: inherit;
margin-inline-end: 0.5rem;
}
.media-content {
overflow: hidden;
text-overflow: ellipsis;
.group-title {
line-height: 1.5rem;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
font-weight: bold;
}
.group-federated-username {
font-size: 14px;
}
}
}
}
</style>

View File

@ -1,12 +1,13 @@
<template>
<div class="card">
<div class="identity-header">
<div class="identity-header" dir="auto">
<figure class="image is-24x24" v-if="member.actor.avatar">
<img class="is-rounded" :src="member.actor.avatar.url" alt="" />
</figure>
<b-icon v-else icon="account-circle" />
{{ displayNameAndUsername(member.actor) }}
</div>
<div class="card-content">
<div class="card-content" dir="auto">
<div>
<div class="media">
<div class="media-left">
@ -15,7 +16,7 @@
</figure>
<b-icon v-else size="is-large" icon="account-group" />
</div>
<div class="media-content">
<div class="media-content" dir="auto">
<router-link
:to="{
name: RouteName.GROUP,
@ -24,12 +25,9 @@
},
}"
>
<h3>{{ member.parent.name }}</h3>
<p class="is-6 has-text-grey">
<span v-if="member.parent.domain">{{
`@${member.parent.preferredUsername}@${member.parent.domain}`
}}</span>
<span v-else>{{ `@${member.parent.preferredUsername}` }}</span>
<h2>{{ member.parent.name }}</h2>
<p class="is-6 has-text-grey-dark">
<span>{{ `@${usernameWithDomain(member.parent)}` }}</span>
<b-taglist>
<b-tag
type="is-info"
@ -47,7 +45,7 @@
</div>
</div>
<div class="content" v-if="member.parent.summary">
<p>{{ member.parent.summary }}</p>
<p v-html="member.parent.summary" />
</div>
</div>
<div>
@ -85,6 +83,7 @@ export default class GroupMemberCard extends Vue {
}
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
.card {
.card-content {
display: flex;
@ -110,8 +109,9 @@ export default class GroupMemberCard extends Vue {
display: flex;
padding: 5px;
figure {
padding-right: 3px;
figure,
span.icon {
@include padding-right(3px);
}
}
}

View File

@ -33,6 +33,7 @@ export default class GroupSection extends Vue {
}
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
section {
display: flex;
flex-direction: column;
@ -44,7 +45,7 @@ section {
display: flex;
justify-content: flex-end;
padding-bottom: 0.5rem;
padding-right: 0.5rem;
@include padding-right(0.5rem);
}
.main-slot {
@ -68,7 +69,7 @@ div.group-section-title {
::v-deep & > a {
align-self: center;
margin-right: 5px;
@include margin-right(5px);
color: var(--title-color);
}

View File

@ -1,57 +1,65 @@
<template>
<div class="media">
<div class="media-content">
<div class="content">
<i18n
tag="p"
path="You have been invited by {invitedBy} to the following group:"
>
<b slot="invitedBy">{{ member.invitedBy.name }}</b>
</i18n>
</div>
<div class="media subfield">
<div class="media-left">
<figure class="image is-48x48" v-if="member.parent.avatar">
<img class="is-rounded" :src="member.parent.avatar.url" alt="" />
</figure>
<b-icon v-else size="is-large" icon="account-group" />
<div class="card">
<div class="card-content media">
<div class="media-content">
<div class="content">
<i18n
tag="p"
path="You have been invited by {invitedBy} to the following group:"
>
<b slot="invitedBy">{{ member.invitedBy.name }}</b>
</i18n>
</div>
<div class="media-content">
<div class="level">
<div class="level-left">
<div class="level-item">
<router-link
:to="{
name: RouteName.GROUP,
params: {
preferredUsername: usernameWithDomain(member.parent),
},
}"
>
<h3>{{ member.parent.name }}</h3>
<p class="is-6 has-text-grey">
<span v-if="member.parent.domain">
{{
`@${member.parent.preferredUsername}@${member.parent.domain}`
}}
</span>
<span v-else>{{
`@${member.parent.preferredUsername}`
}}</span>
</p>
</router-link>
<div class="media subfield">
<div class="media-left">
<figure class="image is-48x48" v-if="member.parent.avatar">
<img class="is-rounded" :src="member.parent.avatar.url" alt="" />
</figure>
<b-icon v-else size="is-large" icon="account-group" />
</div>
<div class="media-content">
<div class="level">
<div class="level-left">
<div class="level-item mr-3">
<router-link
:to="{
name: RouteName.GROUP,
params: {
preferredUsername: usernameWithDomain(member.parent),
},
}"
>
<h3 class="is-size-5">{{ member.parent.name }}</h3>
<p class="is-size-7 has-text-grey-dark">
<span v-if="member.parent.domain">
{{
`@${member.parent.preferredUsername}@${member.parent.domain}`
}}
</span>
<span v-else>{{
`@${member.parent.preferredUsername}`
}}</span>
</p>
</router-link>
</div>
</div>
</div>
<div class="level-right">
<div class="level-item">
<b-button type="is-success" @click="$emit('accept', member.id)">
{{ $t("Accept") }}
</b-button>
</div>
<div class="level-item">
<b-button type="is-danger" @click="$emit('reject', member.id)">
{{ $t("Decline") }}
</b-button>
<div class="level-right">
<div class="level-item">
<b-button
type="is-success"
@click="$emit('accept', member.id)"
>
{{ $t("Accept") }}
</b-button>
</div>
<div class="level-item">
<b-button
type="is-danger"
@click="$emit('reject', member.id)"
>
{{ $t("Decline") }}
</b-button>
</div>
</div>
</div>
</div>
@ -82,4 +90,7 @@ export default class InvitationCard extends Vue {
background: lighten($primary, 40%);
padding: 10px;
}
h3 {
color: $violet-3;
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<section v-if="invitations && invitations.length > 0">
<section class="card my-3" v-if="invitations && invitations.length > 0">
<InvitationCard
v-for="member in invitations"
:key="member.id"
@ -13,8 +13,9 @@
import { ACCEPT_INVITATION, REJECT_INVITATION } from "@/graphql/member";
import { Component, Prop, Vue } from "vue-property-decorator";
import InvitationCard from "@/components/Group/InvitationCard.vue";
import { LOGGED_USER_MEMBERSHIPS } from "@/graphql/actor";
import { PERSON_STATUS_GROUP } from "@/graphql/actor";
import { IMember } from "@/types/actor/member.model";
import { IGroup, IPerson, usernameWithDomain } from "@/types/actor";
@Component({
components: {
@ -26,19 +27,26 @@ export default class Invitations extends Vue {
async acceptInvitation(id: string): Promise<void> {
try {
const { data } = await this.$apollo.mutate<{ acceptInvitation: IMember }>(
{
mutation: ACCEPT_INVITATION,
variables: {
id,
},
refetchQueries: [{ query: LOGGED_USER_MEMBERSHIPS }],
}
);
if (data) {
this.$emit("accept-invitation", data.acceptInvitation);
}
} catch (error) {
await this.$apollo.mutate<{ acceptInvitation: IMember }>({
mutation: ACCEPT_INVITATION,
variables: {
id,
},
refetchQueries({ data }) {
const profile = data?.acceptInvitation?.actor as IPerson;
const group = data?.acceptInvitation?.parent as IGroup;
if (profile && group) {
return [
{
query: PERSON_STATUS_GROUP,
variables: { id: profile.id, group: usernameWithDomain(group) },
},
];
}
return [];
},
});
} catch (error: any) {
console.error(error);
if (error.graphQLErrors && error.graphQLErrors.length > 0) {
this.$notifier.error(error.graphQLErrors[0].message);
@ -48,19 +56,26 @@ export default class Invitations extends Vue {
async rejectInvitation(id: string): Promise<void> {
try {
const { data } = await this.$apollo.mutate<{ rejectInvitation: IMember }>(
{
mutation: REJECT_INVITATION,
variables: {
id,
},
refetchQueries: [{ query: LOGGED_USER_MEMBERSHIPS }],
}
);
if (data) {
this.$emit("reject-invitation", data.rejectInvitation);
}
} catch (error) {
await this.$apollo.mutate<{ rejectInvitation: IMember }>({
mutation: REJECT_INVITATION,
variables: {
id,
},
refetchQueries({ data }) {
const profile = data?.rejectInvitation?.actor as IPerson;
const group = data?.rejectInvitation?.parent as IGroup;
if (profile && group) {
return [
{
query: PERSON_STATUS_GROUP,
variables: { id: profile.id, group: usernameWithDomain(group) },
},
];
}
return [];
},
});
} catch (error: any) {
console.error(error);
if (error.graphQLErrors && error.graphQLErrors.length > 0) {
this.$notifier.error(error.graphQLErrors[0].message);

View File

@ -0,0 +1,39 @@
<template>
<div class="multi-card-group">
<group-card
class="group-card"
v-for="group in groups"
:group="group"
:key="group.id"
/>
</div>
</template>
<script lang="ts">
import { IGroup } from "@/types/actor";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
import GroupCard from "./GroupCard.vue";
@Component({
components: {
GroupCard,
},
})
export default class MultiGroupCard extends Vue {
@Prop({ type: Array as PropType<IGroup[]>, required: true })
groups!: IGroup[];
}
</script>
<style lang="scss" scoped>
.multi-card-group {
display: grid;
grid-auto-rows: 1fr;
grid-column-gap: 30px;
grid-row-gap: 30px;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
.group-card {
height: 100%;
display: flex;
flex-direction: column;
}
}
</style>

View File

@ -83,11 +83,13 @@
/></a>
<a
:href="telegramShareUrl"
class="telegram"
target="_blank"
rel="nofollow noopener"
title="Telegram"
><b-icon icon="telegram" size="is-large" type="is-primary"
/></a>
>
<telegram-logo />
</a>
<a
title="Diaspora"
:href="diasporaShareUrl"
@ -115,12 +117,14 @@ import { Component, Prop, Vue, Ref } from "vue-property-decorator";
import { GroupVisibility } from "@/types/enums";
import DiasporaLogo from "../Share/DiasporaLogo.vue";
import MastodonLogo from "../Share/MastodonLogo.vue";
import TelegramLogo from "../Share/MastodonLogo.vue";
import { displayName, IGroup } from "@/types/actor";
@Component({
components: {
DiasporaLogo,
MastodonLogo,
TelegramLogo,
},
})
export default class ShareGroupModal extends Vue {
@ -194,7 +198,8 @@ export default class ShareGroupModal extends Vue {
</script>
<style lang="scss" scoped>
.diaspora,
.mastodon {
.mastodon,
.telegram {
::v-deep span svg {
width: 2.25rem;
}

View File

@ -16,8 +16,9 @@
:width="width"
:height="height"
class="absolute top-0 left-0 transition-opacity duration-500"
:class="isLoaded ? 'opacity-100' : 'opacity-0'"
:class="{ isLoaded: isLoaded ? 'opacity-100' : 'opacity-0', rounded }"
alt=""
src=""
/>
</div>
</div>
@ -37,6 +38,7 @@ export default class LazyImage extends Vue {
@Prop({ type: String, required: false, default: null }) blurhash!: string;
@Prop({ type: Number, default: 1 }) width!: number;
@Prop({ type: Number, default: 1 }) height!: number;
@Prop({ type: Boolean, default: false }) rounded!: boolean;
inheritAttrs = false;
isLoaded = false;
@ -63,12 +65,14 @@ export default class LazyImage extends Vue {
onEnter(): void {
// Image is visible (means: has entered the viewport),
// so start loading by setting the src attribute
this.image.src = this.src;
if (this.image) {
this.image.src = this.src;
this.image.onload = () => {
// Image is loaded, so start fading in
this.isLoaded = true;
};
this.image.onload = () => {
// Image is loaded, so start fading in
this.isLoaded = true;
};
}
}
@Watch("src")
@ -113,5 +117,8 @@ img {
height: 100%;
object-fit: cover;
object-position: 50% 50%;
&.rounded {
border-radius: 8px;
}
}
</style>

View File

@ -5,6 +5,7 @@
:width="pictureOrDefault.metadata.width"
:height="pictureOrDefault.metadata.height"
:blurhash="pictureOrDefault.metadata.blurhash"
:rounded="rounded"
/>
</template>
<script lang="ts">
@ -34,6 +35,7 @@ const DEFAULT_PICTURE = {
export default class LazyImageWrapper extends Vue {
@Prop({ required: false, type: Object as PropType<IMedia | null> })
picture!: IMedia | null;
@Prop({ required: false, type: Boolean, default: false }) rounded!: boolean;
get pictureOrDefault(): Partial<IMedia> {
if (this.picture === null) {

View File

@ -19,7 +19,10 @@
:zoomInTitle="$t('Zoom in')"
:zoomOutTitle="$t('Zoom out')"
></l-control-zoom>
<v-locatecontrol :options="{ icon: 'mdi mdi-map-marker' }" />
<v-locatecontrol
v-if="canDoGeoLocation"
:options="{ icon: 'mdi mdi-map-marker' }"
/>
<l-marker
:lat-lng="[lat, lon]"
@add="openPopup"
@ -152,6 +155,10 @@ export default class Map extends Vue {
(this.$t("© The OpenStreetMap Contributors") as string)
);
}
get canDoGeoLocation(): boolean {
return window.isSecureContext;
}
}
</script>
<style lang="scss" scoped>

View File

@ -1,5 +1,6 @@
<template>
<b-navbar
id="navbar"
type="is-secondary"
wrapper-class="container"
:active.sync="mobileNavbarActive"
@ -48,6 +49,7 @@
"
>
<b-button
v-if="!hideCreateEventsButton"
tag="router-link"
:to="{ name: RouteName.CREATE_EVENT }"
type="is-primary"
@ -60,7 +62,8 @@
tag="a"
href="https://mediation.koena.net/framasoft/mobilizon/"
target="_blank"
rel="noopener"
rel="noopener external"
hreflang="fr"
>
<img
src="/img/koena-a11y.svg"
@ -87,12 +90,12 @@
v-if="currentActor.id && currentUser.isLoggedIn"
right
collapsible
ref="user-dropdown"
tabindex="0"
tag="span"
@keyup.enter="toggleMenu"
>
<template
slot="label"
v-if="currentActor"
class="navbar-dropdown-profile"
>
<template slot="label" v-if="currentActor">
<div class="identity-wrapper">
<div>
<figure class="image is-32x32" v-if="currentActor.avatar">
@ -121,8 +124,11 @@
v-else
:active="identity.id === currentActor.id"
:key="identity.id"
tabindex="0"
@click="setIdentity(identity)"
@keyup.enter="setIdentity(identity)"
>
<span @click="setIdentity(identity)">
<span>
<div class="media-left">
<figure class="image is-32x32" v-if="identity.avatar">
<img
@ -143,7 +149,7 @@
</div>
</span>
<hr class="navbar-divider" />
<hr class="navbar-divider" role="presentation" />
</b-navbar-item>
<b-navbar-item
@ -158,8 +164,13 @@
>{{ $t("Administration") }}
</b-navbar-item>
<b-navbar-item tag="span">
<span @click="logout">{{ $t("Log out") }}</span>
<b-navbar-item
tag="span"
tabindex="0"
@click="logout"
@keyup.enter="logout"
>
<span>{{ $t("Log out") }}</span>
</b-navbar-item>
</b-navbar-dropdown>
@ -185,7 +196,7 @@
</template>
<script lang="ts">
import { Component, Vue, Watch } from "vue-property-decorator";
import { Component, Ref, Vue, Watch } from "vue-property-decorator";
import Logo from "@/components/Logo.vue";
import { GraphQLError } from "graphql";
import { loadLanguageAsync } from "@/utils/i18n";
@ -206,12 +217,8 @@ import RouteName from "../router/name";
@Component({
apollo: {
currentUser: {
query: CURRENT_USER_CLIENT,
},
currentActor: {
query: CURRENT_ACTOR_CLIENT,
},
currentUser: CURRENT_USER_CLIENT,
currentActor: CURRENT_ACTOR_CLIENT,
identities: {
query: IDENTITIES,
update: ({ identities }) =>
@ -257,6 +264,13 @@ export default class NavBar extends Vue {
displayName = displayName;
@Ref("user-dropdown") userDropDown!: any;
toggleMenu(): void {
console.debug("called toggleMenu");
this.userDropDown.showMenu();
}
@Watch("currentActor")
async initializeListOfIdentities(): Promise<void> {
if (!this.currentUser.isLoggedIn) return;
@ -326,9 +340,14 @@ export default class NavBar extends Vue {
});
return changeIdentity(this.$apollo.provider.defaultClient, identity);
}
get hideCreateEventsButton(): boolean {
return !!this.config?.restrictions?.onlyGroupsCanCreateEvents;
}
}
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
nav {
.navbar-item {
a.button {
@ -361,7 +380,7 @@ nav {
}
.navbar-item.has-dropdown a.navbar-link figure {
margin-right: 0.75rem;
@include margin-right(0.75rem);
display: flex;
align-items: center;
}

View File

@ -71,7 +71,13 @@ import { IParticipant } from "../../types/participant.model";
import RouteName from "../../router/name";
import { CONFIRM_PARTICIPATION } from "../../graphql/event";
@Component
@Component({
metaInfo() {
return {
title: this.$t("Confirm participation") as string,
};
},
})
export default class ConfirmParticipation extends Vue {
@Prop({ type: String, required: true }) token!: string;

View File

@ -65,6 +65,7 @@
<b-modal
:active.sync="isAnonymousParticipationModalOpen"
has-modal-card
:close-button-aria-label="$t('Close')"
ref="anonymous-participation-modal"
>
<div class="modal-card">

View File

@ -28,6 +28,12 @@ import { IEvent } from "@/types/event.model";
},
},
},
metaInfo() {
return {
title: this.$t("Participation with account") as string,
meta: [{ name: "robots", content: "noindex" }],
};
},
})
export default class ParticipationWithAccount extends Vue {
@Prop({ type: String, required: true }) uuid!: string;

View File

@ -155,6 +155,12 @@ import { ApolloCache, FetchResult } from "@apollo/client/core";
},
config: CONFIG,
},
metaInfo() {
return {
title: this.$t("Participation without account") as string,
meta: [{ name: "robots", content: "noindex" }],
};
},
})
export default class ParticipationWithoutAccount extends Vue {
@Prop({ type: String, required: true }) uuid!: string;
@ -195,6 +201,7 @@ export default class ParticipationWithoutAccount extends Vue {
email: this.anonymousParticipation.email,
message: this.anonymousParticipation.message,
locale: this.$i18n.locale,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
},
update: (
store: ApolloCache<{ joinEvent: IParticipant }>,
@ -249,7 +256,7 @@ export default class ParticipationWithoutAccount extends Vue {
data.joinEvent.metadata.cancellationToken
);
}
} catch (e) {
} catch (e: any) {
if (
["TextEncoder is not defined", "crypto.subtle is undefined"].includes(
e.message

View File

@ -130,6 +130,12 @@ import RouteName from "../../router/name";
},
config: CONFIG,
},
metaInfo() {
return {
title: this.$t("Unlogged participation") as string,
meta: [{ name: "robots", content: "noindex" }],
};
},
})
export default class UnloggedParticipation extends Vue {
@Prop({ type: String, required: true }) uuid!: string;

View File

@ -38,7 +38,12 @@
</span>
</b-upload>
</b-field>
<b-button type="is-text" v-if="imageSrc" @click="removeOrClearPicture">
<b-button
type="is-text"
v-if="imageSrc"
@click="removeOrClearPicture"
@keyup.enter="removeOrClearPicture"
>
{{ $t("Clear") }}
</b-button>
</div>
@ -46,13 +51,14 @@
</template>
<style scoped lang="scss">
@use "@/styles/_mixins" as *;
.root {
display: flex;
align-items: center;
}
figure.image {
margin-right: 30px;
@include margin-right(30px);
max-height: 200px;
max-width: 200px;
overflow: hidden;
@ -94,7 +100,7 @@ figure.image {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
margin-right: 5px;
@include margin-right(5px);
}
}
}

View File

@ -0,0 +1,34 @@
<template>
<div class="posts-wrapper">
<post-list-item
v-for="post in posts"
:key="post.id"
:post="post"
:isCurrentActorMember="isCurrentActorMember"
/>
</div>
</template>
<script lang="ts">
import { IPost } from "@/types/post.model";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
import PostListItem from "./PostListItem.vue";
@Component({
components: {
PostListItem,
},
})
export default class MultiPostListItem extends Vue {
@Prop({ type: Array as PropType<IPost[]>, required: true }) posts!: IPost[];
@Prop({ required: false, type: Boolean, default: false })
isCurrentActorMember!: boolean;
}
</script>
<style lang="scss" scoped>
.posts-wrapper {
display: grid;
grid-gap: 20px;
grid-template: 1fr;
}
</style>

View File

@ -1,133 +0,0 @@
<template>
<router-link
class="post-minimalist-card-wrapper"
:to="{ name: RouteName.POST, params: { slug: post.slug } }"
>
<div class="title-info-wrapper">
<div class="media">
<div class="media-left">
<figure class="image is-96x96" v-if="post.picture">
<img :src="post.picture.url" alt="" />
</figure>
<b-icon v-else size="is-large" icon="post" />
</div>
<div class="media-content">
<p class="post-minimalist-title">{{ post.title }}</p>
<div class="metadata">
<b-tag type="is-warning" size="is-small" v-if="post.draft">{{
$t("Draft")
}}</b-tag>
<small
v-if="
post.visibility === PostVisibility.PUBLIC &&
isCurrentActorMember
"
class="has-text-grey"
>
<b-icon icon="earth" size="is-small" />{{ $t("Public") }}</small
>
<small
v-else-if="post.visibility === PostVisibility.UNLISTED"
class="has-text-grey"
>
<b-icon icon="link" size="is-small" />{{
$t("Accessible through link")
}}</small
>
<small
v-else-if="post.visibility === PostVisibility.PRIVATE"
class="has-text-grey"
>
<b-icon icon="lock" size="is-small" />{{
$t("Accessible only to members", {
group: post.attributedTo.name,
})
}}</small
>
<small class="has-text-grey">{{
$options.filters.formatDateTimeString(
new Date(post.insertedAt),
false
)
}}</small>
<small class="has-text-grey" v-if="isCurrentActorMember">{{
$t("Created by {username}", {
username: `@${usernameWithDomain(post.author)}`,
})
}}</small>
</div>
</div>
</div>
</div>
</router-link>
</template>
<script lang="ts">
import { usernameWithDomain } from "@/types/actor";
import { PostVisibility } from "@/types/enums";
import { Component, Prop, Vue } from "vue-property-decorator";
import RouteName from "../../router/name";
import { IPost } from "../../types/post.model";
@Component
export default class PostElementItem extends Vue {
@Prop({ required: true, type: Object }) post!: IPost;
@Prop({ required: false, type: Boolean, default: false })
isCurrentActorMember!: boolean;
RouteName = RouteName;
usernameWithDomain = usernameWithDomain;
PostVisibility = PostVisibility;
}
</script>
<style lang="scss" scoped>
.post-minimalist-card-wrapper {
text-decoration: none;
display: flex;
width: 100%;
color: initial;
border-bottom: 1px solid #e9e9e9;
align-items: center;
.title-info-wrapper {
flex: 2;
.post-minimalist-title {
color: #3c376e;
font-family: "Liberation Sans", "Helvetica Neue", Roboto, Helvetica, Arial,
serif;
font-size: 1rem;
font-weight: 700;
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.media .media-left {
& > span.icon {
height: 96px;
width: 96px;
}
& > figure.image > img {
object-fit: cover;
height: 100%;
object-position: center;
width: 100%;
}
}
.metadata {
& > span.tag {
margin-right: 5px;
}
& > small:not(:last-child):after {
content: "·";
padding: 0 5px;
}
}
}
}
</style>

View File

@ -1,55 +1,122 @@
<template>
<router-link
class="post-minimalist-card-wrapper"
dir="auto"
:to="{ name: RouteName.POST, params: { slug: post.slug } }"
>
<div class="title-info-wrapper">
<p class="post-minimalist-title">{{ post.title }}</p>
<small class="has-text-grey-dark">{{
formatDistanceToNow(new Date(post.publishAt || post.insertedAt), {
locale: $dateFnsLocale,
addSuffix: true,
})
}}</small>
<lazy-image-wrapper
:picture="post.picture"
:rounded="true"
style="height: 120px"
/>
<div class="title-info-wrapper has-text-grey-dark">
<h3 class="post-minimalist-title" :lang="post.language">
{{ post.title }}
</h3>
<p class="post-publication-date">
<b-icon icon="clock" />
<span dir="auto" class="has-text-grey-dark" v-if="isBeforeLastWeek">{{
publishedAt | formatDateTimeString(undefined, false, "short")
}}</span>
<span v-else>{{
formatDistanceToNow(publishedAt, {
locale: $dateFnsLocale,
addSuffix: true,
})
}}</span>
</p>
<b-taglist v-if="post.tags.length > 0" style="display: inline">
<b-icon icon="tag" />
<b-tag v-for="tag in post.tags" :key="tag.slug">{{ tag.title }}</b-tag>
</b-taglist>
<p class="post-publisher has-text-grey-dark" v-if="isCurrentActorMember">
<b-icon icon="account-edit" />
<i18n path="Published by {name}">
<b class="has-text-weight-medium" slot="name">{{
displayName(post.author)
}}</b>
</i18n>
</p>
</div>
</router-link>
</template>
<script lang="ts">
import { formatDistanceToNow } from "date-fns";
import { formatDistanceToNow, subWeeks, isBefore } from "date-fns";
import { Component, Prop, Vue } from "vue-property-decorator";
import RouteName from "../../router/name";
import { IPost } from "../../types/post.model";
import LazyImageWrapper from "@/components/Image/LazyImageWrapper.vue";
import { displayName } from "@/types/actor";
@Component
@Component({
components: {
LazyImageWrapper,
},
})
export default class PostListItem extends Vue {
@Prop({ required: true, type: Object }) post!: IPost;
@Prop({ required: false, type: Boolean, default: false })
isCurrentActorMember!: boolean;
RouteName = RouteName;
formatDistanceToNow = formatDistanceToNow;
displayName = displayName;
get publishedAt(): Date {
return new Date((this.post.publishAt || this.post.insertedAt) as Date);
}
get isBeforeLastWeek(): boolean {
return isBefore(this.publishedAt, subWeeks(new Date(), 1));
}
}
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
@import "~bulma/sass/utilities/mixins.sass";
.post-minimalist-card-wrapper {
display: grid;
grid-gap: 5px 10px;
grid-template-areas: "preview" "body";
text-decoration: none;
display: flex;
width: 100%;
color: initial;
border-bottom: 1px solid #e9e9e9;
align-items: center;
@include desktop {
grid-template-columns: 200px 3fr;
grid-template-areas: "preview body";
}
.title-info-wrapper {
flex: 2;
.post-minimalist-title {
color: #3c376e;
font-family: Roboto, Helvetica, Arial, serif;
font-size: 16px;
font-weight: 500;
font-size: 18px;
line-height: 24px;
font-weight: 700;
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
-webkit-line-clamp: 3;
}
}
::v-deep .icon {
vertical-align: middle;
@include margin-right(5px);
}
::v-deep .tags {
display: inline;
span.tag {
max-width: 200px;
& > span {
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}

View File

@ -0,0 +1,219 @@
<template>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">{{ $t("Share this post") }}</p>
</header>
<section class="modal-card-body is-flex" v-if="post">
<div class="container has-text-centered">
<b-notification
type="is-warning"
v-if="post.visibility !== PostVisibility.PUBLIC"
:closable="false"
>
{{
$t(
"This post is accessible only through it's link. Be careful where you post this link."
)
}}
</b-notification>
<b-field :label="$t('Post URL')" label-for="post-url-text">
<b-input
id="post-url-text"
ref="postURLInput"
:value="postURL"
expanded
/>
<p class="control">
<b-tooltip
:label="$t('URL copied to clipboard')"
:active="showCopiedTooltip"
always
type="is-success"
position="is-left"
>
<b-button
type="is-primary"
icon-right="content-paste"
native-type="button"
@click="copyURL"
@keyup.enter="copyURL"
:title="$t('Copy URL to clipboard')"
/>
</b-tooltip>
</p>
</b-field>
<div>
<a
:href="twitterShareUrl"
target="_blank"
rel="nofollow noopener"
title="Twitter"
><b-icon icon="twitter" size="is-large" type="is-primary"
/></a>
<a
:href="mastodonShareUrl"
class="mastodon"
target="_blank"
rel="nofollow noopener"
title="Mastodon"
>
<mastodon-logo />
</a>
<a
:href="facebookShareUrl"
target="_blank"
rel="nofollow noopener"
title="Facebook"
><b-icon icon="facebook" size="is-large" type="is-primary"
/></a>
<a
:href="whatsAppShareUrl"
target="_blank"
rel="nofollow noopener"
title="WhatsApp"
><b-icon icon="whatsapp" size="is-large" type="is-primary"
/></a>
<a
:href="telegramShareUrl"
class="telegram"
target="_blank"
rel="nofollow noopener"
title="Telegram"
>
<telegram-logo />
</a>
<a
:href="linkedInShareUrl"
target="_blank"
rel="nofollow noopener"
title="LinkedIn"
><b-icon icon="linkedin" size="is-large" type="is-primary"
/></a>
<a
:href="diasporaShareUrl"
class="diaspora"
target="_blank"
rel="nofollow noopener"
title="Diaspora"
>
<diaspora-logo />
</a>
<a
:href="emailShareUrl"
target="_blank"
rel="nofollow noopener"
title="Email"
><b-icon icon="email" size="is-large" type="is-primary"
/></a>
</div>
</div>
</section>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue, Ref } from "vue-property-decorator";
import { PostVisibility } from "@/types/enums";
import { IPost } from "../../types/post.model";
import DiasporaLogo from "../Share/DiasporaLogo.vue";
import MastodonLogo from "../Share/MastodonLogo.vue";
import TelegramLogo from "../Share/TelegramLogo.vue";
import { PropType } from "vue";
import RouteName from "@/router/name";
@Component({
components: {
DiasporaLogo,
MastodonLogo,
TelegramLogo,
},
})
export default class SharePostModal extends Vue {
@Prop({ type: Object as PropType<IPost>, required: true }) post!: IPost;
@Ref("postURLInput") readonly postURLInput!: any;
PostVisibility = PostVisibility;
RouteName = RouteName;
showCopiedTooltip = false;
get twitterShareUrl(): string {
return `https://twitter.com/intent/tweet?url=${encodeURIComponent(
this.postURL
)}&text=${this.post.title}`;
}
get facebookShareUrl(): string {
return `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(
this.postURL
)}`;
}
get linkedInShareUrl(): string {
return `https://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent(
this.postURL
)}&title=${this.post.title}`;
}
get whatsAppShareUrl(): string {
return `https://wa.me/?text=${encodeURIComponent(this.basicTextToEncode)}`;
}
get telegramShareUrl(): string {
return `https://t.me/share/url?url=${encodeURIComponent(
this.postURL
)}&text=${encodeURIComponent(this.post.title)}`;
}
get emailShareUrl(): string {
return `mailto:?to=&body=${this.postURL}&subject=${this.post.title}`;
}
get diasporaShareUrl(): string {
return `https://share.diasporafoundation.org/?title=${encodeURIComponent(
this.post.title
)}&url=${encodeURIComponent(this.postURL)}`;
}
get mastodonShareUrl(): string {
return `https://toot.karamoff.dev/?text=${encodeURIComponent(
this.basicTextToEncode
)}`;
}
get basicTextToEncode(): string {
return `${this.post.title}\r\n${this.postURL}`;
}
get postURL(): string {
if (this.post.id) {
return this.$router.resolve({
name: RouteName.POST,
params: { id: this.post.id },
}).href;
}
return "";
}
copyURL(): void {
this.postURLInput.$refs.input.select();
document.execCommand("copy");
this.showCopiedTooltip = true;
setTimeout(() => {
this.showCopiedTooltip = false;
}, 2000);
}
}
</script>
<style lang="scss" scoped>
.diaspora,
.mastodon,
.telegram {
::v-deep span svg {
width: 2.25rem;
}
}
</style>

View File

@ -1,7 +1,7 @@
<template>
<div class="modal-card">
<header class="modal-card-head" v-if="title">
<p class="modal-card-title">{{ title }}</p>
<h2 class="modal-card-title">{{ title }}</h2>
</header>
<section
@ -17,7 +17,7 @@
<article class="media">
<div class="media-left">
<figure class="image is-48x48" v-if="comment.actor.avatar">
<img :src="comment.actor.avatar.url" alt="Image" />
<img :src="comment.actor.avatar.url" alt="" />
</figure>
<b-icon
class="media-left"
@ -45,12 +45,16 @@
</p>
<div class="control">
<b-input
v-model="content"
type="textarea"
@keyup.enter="confirm"
:placeholder="$t('Additional comments')"
/>
<b-field
:label="$t('Additional comments')"
label-for="additonal-comments"
>
<b-input
v-model="content"
type="textarea"
id="additonal-comments"
/>
</b-field>
</div>
<div class="control" v-if="outsideDomain">
@ -73,7 +77,12 @@
<button class="button" ref="cancelButton" @click="close">
{{ translatedCancelText }}
</button>
<button class="button is-primary" ref="confirmButton" @click="confirm">
<button
class="button is-primary"
ref="confirmButton"
@click="confirm"
@keyup.enter="confirm"
>
{{ translatedConfirmText }}
</button>
</footer>

View File

@ -1,5 +1,5 @@
<template>
<div class="resource-wrapper">
<div class="resource-wrapper" dir="auto">
<router-link
:to="{
name: RouteName.RESOURCE_FOLDER,
@ -61,7 +61,7 @@ export default class FolderItem extends Mixins(ResourceMixin) {
list = [];
groupObject: Record<string, unknown> = {
name: `folder-${this.resource.title}`,
name: `folder-${this.resource?.title}`,
pull: false,
put: ["resources"],
};
@ -110,7 +110,7 @@ export default class FolderItem extends Mixins(ResourceMixin) {
return undefined;
}
return data.updateResource;
} catch (e) {
} catch (e: any) {
Snackbar.open({
message: e.message,
type: "is-danger",

View File

@ -1,5 +1,5 @@
<template>
<div class="resource-wrapper">
<div class="resource-wrapper" dir="auto">
<a :href="resource.resourceUrl" target="_blank">
<div class="preview">
<div
@ -82,6 +82,7 @@ export default class ResourceItem extends Vue {
}
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
.resource-wrapper {
display: flex;
flex: 1;
@ -137,7 +138,7 @@ a {
display: inline-block;
width: 16px;
height: 16px;
margin-right: 6px;
@include margin-right(6px);
vertical-align: middle;
}

View File

@ -7,6 +7,7 @@
icon="magnify"
type="search"
rounded
dir="auto"
:placeholder="defaultPlaceHolder"
v-model="search"
@keyup.native.enter="enter"

View File

@ -60,7 +60,7 @@ export default class NotificationsOnboarding extends mixins(Onboarding) {
async updateSetting(variables: Record<string, unknown>): Promise<void> {
try {
this.doUpdateSetting(variables);
} catch (e) {
} catch (e: any) {
Snackbar.open({
message: e.message,
type: "is-danger",

View File

@ -0,0 +1,16 @@
<template>
<span class="icon has-text-primary is-large">
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<title>Telegram</title>
<path
d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z"
/>
</svg>
</span>
</template>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
@Component
export default class TelegramLogo extends Vue {}
</script>

View File

@ -55,7 +55,7 @@ export default class Todo extends Vue {
},
});
this.editMode = false;
} catch (e) {
} catch (e: any) {
Snackbar.open({
message: e.message,
type: "is-danger",
@ -66,7 +66,8 @@ export default class Todo extends Vue {
}
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
span.details {
margin-left: 1rem;
@include margin-left(1rem);
}
</style>

View File

@ -91,7 +91,7 @@ export default class Todo extends Vue {
},
});
this.editMode = false;
} catch (e) {
} catch (e: any) {
Snackbar.open({
message: e.message,
type: "is-danger",

View File

@ -32,6 +32,7 @@ export default class EmptyContent extends Vue {
}
&.inline {
margin-top: 5vh;
margin-bottom: 2vh;
}
}
</style>

View File

@ -0,0 +1,15 @@
<template>
<div>a</div>
</template>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import RouteName from "@/router/name";
@Component
export default class HomepageRedirectComponent extends Vue {
created(): void {
this.$router.replace({ name: RouteName.HOME });
}
}
</script>

View File

@ -14,10 +14,11 @@ function formatDateString(value: string): string {
});
}
function formatTimeString(value: string): string {
function formatTimeString(value: string, timeZone: string): string {
return parseDateTime(value).toLocaleTimeString(locale(), {
hour: "numeric",
minute: "numeric",
timeZone,
});
}
@ -55,6 +56,7 @@ const SHORT_TIME_FORMAT_OPTIONS: DateTimeFormatOptions = {
function formatDateTimeString(
value: string,
timeZone: string | null | undefined = undefined,
showTime = true,
dateFormat = "long"
): string {
@ -66,6 +68,7 @@ function formatDateTimeString(
options = {
...options,
...(isLongFormat ? LONG_TIME_FORMAT_OPTIONS : SHORT_TIME_FORMAT_OPTIONS),
timeZone: timeZone || undefined,
};
}
const format = new Intl.DateTimeFormat(locale(), options);

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