diff --git a/config/dev.exs b/config/dev.exs index ac5f5a921..ce28a21d0 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -52,7 +52,7 @@ config :mobilizon, MobilizonWeb.Endpoint, # Do not include metadata nor timestamps in development logs config :logger, :console, format: "[$level] $message\n", level: :debug -config :mobilizon, Mobilizon.Service.Geospatial, service: Mobilizon.Service.Geospatial.Nominatim +config :mobilizon, Mobilizon.Service.Geospatial, service: Mobilizon.Service.Geospatial.GoogleMaps # Set a higher stacktrace during development. Avoid configuring such # in production as building large stacktraces may be expensive. diff --git a/js/src/assets/Circle-icons-calendar.svg b/js/src/assets/Circle-icons-calendar.svg index 2f44e710e..381a5ec32 100644 --- a/js/src/assets/Circle-icons-calendar.svg +++ b/js/src/assets/Circle-icons-calendar.svg @@ -1,134 +1 @@ - - - -image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/js/src/assets/mobilizon_logo.svg b/js/src/assets/mobilizon_logo.svg index 3e755d528..df6363deb 100644 --- a/js/src/assets/mobilizon_logo.svg +++ b/js/src/assets/mobilizon_logo.svg @@ -1,31 +1 @@ - - Mobilizon Logo - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/js/src/assets/profile.svg b/js/src/assets/profile.svg index ddb82adb6..8ae198cf5 100644 --- a/js/src/assets/profile.svg +++ b/js/src/assets/profile.svg @@ -1,65 +1 @@ - - - - - - image/svg+xml - - - - - - - 47 all - ">"> - -"> - - - +">">"> \ No newline at end of file diff --git a/js/src/assets/texting.svg b/js/src/assets/texting.svg index 11dbca332..fa1307f73 100644 --- a/js/src/assets/texting.svg +++ b/js/src/assets/texting.svg @@ -1,748 +1 @@ - - - - - - image/svg+xml - - - - - - - texting - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/js/src/assets/undraw_events.svg b/js/src/assets/undraw_events.svg index 4166e453e..7256b98bc 100644 --- a/js/src/assets/undraw_events.svg +++ b/js/src/assets/undraw_events.svg @@ -1 +1 @@ -events \ No newline at end of file + \ No newline at end of file diff --git a/js/src/components/SearchField.vue b/js/src/components/SearchField.vue index 275e91948..1c7eb77b4 100644 --- a/js/src/components/SearchField.vue +++ b/js/src/components/SearchField.vue @@ -25,5 +25,9 @@ export default class SearchField extends Vue { input.searchField { box-shadow: none; border-color: #b5b5b5; + + &::placeholder { + color: gray; + } } - \ No newline at end of file + diff --git a/js/src/i18n/en_US.json b/js/src/i18n/en_US.json index 5d00b9e4a..fd0f92998 100644 --- a/js/src/i18n/en_US.json +++ b/js/src/i18n/en_US.json @@ -298,6 +298,7 @@ "You are already a participant of this event.": "You are already a participant of this event.", "You are already logged-in.": "You are already logged-in.", "You can add tags by hitting the Enter key or by adding a comma": "You can add tags by hitting the Enter key or by adding a comma", + "You can't remove your last identity.": "You can't remove your last identity.", "You have been disconnected": "You have been disconnected", "You have cancelled your participation": "You have cancelled your participation", "You have one event in {days} days.": "You have no events in {days} days | You have one event in {days} days. | You have {count} events in {days} days", diff --git a/js/src/i18n/fr_FR.json b/js/src/i18n/fr_FR.json index 4c68d77bd..59b066a5c 100644 --- a/js/src/i18n/fr_FR.json +++ b/js/src/i18n/fr_FR.json @@ -298,6 +298,7 @@ "You are already a participant of this event.": "Vous participez déjà à cet événement.", "You are already logged-in.": "Vous êtes déjà connecté.", "You can add tags by hitting the Enter key or by adding a comma": "Vous pouvez ajouter des tags en appuyant sur la touche Entrée ou bien en ajoutant une virgule", + "You can't remove your last identity.": "Vous ne pouvez pas supprimer votre dernière identité", "You have been disconnected": "Vous avez été déconnecté⋅e", "You have cancelled your participation": "Vous avez annulé votre participation", "You have one event in {days} days.": "Vous n'avez pas d'événements dans {days} jours | Vous avez un événement dans {days} jours. | Vous avez {count} événements dans {days} jours", diff --git a/js/src/utils/errors.ts b/js/src/utils/errors.ts index 1fd723b3b..95f8ead37 100644 --- a/js/src/utils/errors.ts +++ b/js/src/utils/errors.ts @@ -48,6 +48,11 @@ export const errors: IError[] = [ value: i18n.t("The current identity doesn't have any permission on this event. You should probably change it.") as string, suggestRefresh: false, }, + { + match: /Cannot remove the last identity of a user/, + value: i18n.t("You can't remove your last identity.") as string, + suggestRefresh: false, + }, { match: /^No user with this email was found$/, value: null, diff --git a/js/src/views/Account/children/EditIdentity.vue b/js/src/views/Account/children/EditIdentity.vue index edcafe955..34052b0b7 100644 --- a/js/src/views/Account/children/EditIdentity.vue +++ b/js/src/views/Account/children/EditIdentity.vue @@ -134,9 +134,14 @@ export default class EditIdentity extends Vue { this.resetFields(); this.identityName = val; + const identity = await this.getIdentity(); - if (this.identityName) { - this.identity = await this.getIdentity(); + if (!identity) { + return await this.$router.push({ name: 'CreateIdentity' }); + } + + if (this.identityName && identity) { + this.identity = identity; this.avatarFile = await buildFileFromIPicture(this.identity.avatar); } @@ -280,15 +285,18 @@ export default class EditIdentity extends Vue { }); } - private async getIdentity() { - const result = await this.$apollo.query({ - query: FETCH_PERSON, - variables: { - username: this.identityName, - }, - }); - - return new Person(result.data.fetchPerson); + private async getIdentity(): Promise { + try { + const result = await this.$apollo.query({ + query: FETCH_PERSON, + variables: { + username: this.identityName, + }, + }); + return new Person(result.data.fetchPerson); + } catch (e) { + return null; + } } private handleError(err: any) { diff --git a/js/src/views/Event/Event.vue b/js/src/views/Event/Event.vue index 14fe43f80..f96290106 100644 --- a/js/src/views/Event/Event.vue +++ b/js/src/views/Event/Event.vue @@ -173,8 +173,8 @@ import {ParticipantRole} from "@/types/event.model";
-
-
+
+

{{ $t('Share this event') }}

{{ $t('All the places have already been taken') }} @@ -189,7 +189,8 @@ import {ParticipantRole} from "@/types/event.model";

-
+
+

{{ $t('Add to my calendar') }}

@@ -911,20 +912,21 @@ export default class Event extends EventMixin { h3 { margin-right: 0; - margin-left: auto; } } .add-to-calendar { - background-repeat: no-repeat; - background-size: 400px; - background-position: 10% 50%; - background-image: url('../../assets/undraw_events.svg'); - position: relative; + display: flex; + h3 { + margin-left: 0; cursor: pointer; } + img { + max-width: 400px; + } + &::before { content:""; background: #B3B3B2; diff --git a/lib/mobilizon/addresses/address.ex b/lib/mobilizon/addresses/address.ex index 63a455952..c7c6453e1 100644 --- a/lib/mobilizon/addresses/address.ex +++ b/lib/mobilizon/addresses/address.ex @@ -69,4 +69,20 @@ defmodule Mobilizon.Addresses.Address do put_change(changeset, :url, url) end + + def coords(nil), do: nil + + def coords(%__MODULE__{} = address) do + with %Geo.Point{coordinates: {latitude, longitude}, srid: 4326} <- address.geom do + {latitude, longitude} + end + end + + def representation(nil), do: nil + + def representation(%__MODULE__{} = address) do + "#{address.street} #{address.postal_code} #{address.locality} #{address.region} #{ + address.country + }" + end end diff --git a/lib/mobilizon/events/event.ex b/lib/mobilizon/events/event.ex index fcd1ea745..2978e785a 100644 --- a/lib/mobilizon/events/event.ex +++ b/lib/mobilizon/events/event.ex @@ -187,8 +187,9 @@ defmodule Mobilizon.Events.Event do # In case the provided addresses is an existing one @spec put_address(Changeset.t(), map) :: Changeset.t() - defp put_address(%Changeset{} = changeset, %{physical_address: %{id: id} = _physical_address}) do - case Addresses.get_address!(id) do + defp put_address(%Changeset{} = changeset, %{physical_address: %{id: id} = _physical_address}) + when not is_nil(id) do + case Addresses.get_address(id) do %Address{} = address -> put_assoc(changeset, :physical_address, address) diff --git a/lib/service/export/icalendar.ex b/lib/service/export/icalendar.ex index 2d65e519e..32ec1625e 100644 --- a/lib/service/export/icalendar.ex +++ b/lib/service/export/icalendar.ex @@ -6,6 +6,7 @@ defmodule Mobilizon.Service.Export.ICalendar do alias Mobilizon.{Actors, Events, Users} alias Mobilizon.Actors.Actor alias Mobilizon.Events.{Event, FeedToken} + alias Mobilizon.Addresses.Address alias Mobilizon.Users.User @doc """ @@ -31,7 +32,10 @@ defmodule Mobilizon.Service.Export.ICalendar do dtend: event.ends_on, description: HtmlSanitizeEx.strip_tags(event.description), uid: event.uuid, - categories: event.tags |> Enum.map(& &1.slug) + url: event.url, + geo: Address.coords(event.physical_address), + location: Address.representation(event.physical_address), + categories: event.tags |> Enum.map(& &1.title) } end diff --git a/test/mobilizon/service/export/icalendar_test.exs b/test/mobilizon/service/export/icalendar_test.exs new file mode 100644 index 000000000..aae737b63 --- /dev/null +++ b/test/mobilizon/service/export/icalendar_test.exs @@ -0,0 +1,37 @@ +defmodule Mobilizon.Service.ICalendarTest do + alias Mobilizon.Service.Export.ICalendar, as: ICalendarService + alias Mobilizon.Events.Event + alias Mobilizon.Addresses.Address + alias ICalendar.Value + use Mobilizon.DataCase + + import Mobilizon.Factory + + describe "export an event to ics" do + test "export basic infos" do + %Event{} = event = insert(:event) + + ics = """ + BEGIN:VCALENDAR + CALSCALE:GREGORIAN + VERSION:2.0 + PRODID:-//ICalendar//Mobilizon//EN + BEGIN:VEVENT + CATEGORIES:#{event.tags |> Enum.map(& &1.title) |> Enum.join(",")} + DESCRIPTION:Ceci est une description avec une première phrase assez longue\\,\\n puis sur une seconde ligne + DTEND:#{Value.to_ics(event.ends_on)} + DTSTAMP:#{Value.to_ics(event.publish_at)} + DTSTART:#{Value.to_ics(event.begins_on)} + GEO:#{event.physical_address |> Address.coords() |> Tuple.to_list() |> Enum.join(";")} + LOCATION:#{Address.representation(event.physical_address)} + SUMMARY:#{event.title} + UID:#{event.uuid} + URL:#{event.url} + END:VEVENT + END:VCALENDAR + """ + + assert {:ok, ics} == ICalendarService.export_public_event(event) + end + end +end diff --git a/test/mobilizon_web/controllers/feed_controller_test.exs b/test/mobilizon_web/controllers/feed_controller_test.exs index d793586b3..a9d4423bd 100644 --- a/test/mobilizon_web/controllers/feed_controller_test.exs +++ b/test/mobilizon_web/controllers/feed_controller_test.exs @@ -36,8 +36,10 @@ defmodule MobilizonWeb.FeedControllerTest do assert entry.title in [event1.title, event2.title] end) - assert entry1.categories == [tag2.slug, tag1.slug] - assert entry2.categories == [tag1.slug] + # It seems categories takes term instead of Label + # + assert entry1.categories == [tag2.title, tag1.title] |> Enum.map(&String.downcase/1) + assert entry2.categories == [tag1.title] |> Enum.map(&String.downcase/1) end test "it returns a 404 for the actor's public events Atom feed if the actor is not publicly visible", @@ -112,8 +114,8 @@ defmodule MobilizonWeb.FeedControllerTest do assert entry.summary in [event1.title, event2.title] end) - assert entry1.categories == [tag1.slug] - assert entry2.categories == [tag1.slug, tag2.slug] + assert entry1.categories == [tag1.title] + assert entry2.categories == [tag1.title, tag2.title] end test "it returns a 404 page for the actor's public events iCal feed with an actor not publicly visible", @@ -183,7 +185,7 @@ defmodule MobilizonWeb.FeedControllerTest do assert entry1.summary == event1.title - assert entry1.categories == [tag1.slug, tag2.slug] + assert entry1.categories == [tag1.title, tag2.title] end end @@ -325,7 +327,7 @@ defmodule MobilizonWeb.FeedControllerTest do [entry1] = ExIcal.parse(conn.resp_body) assert entry1.summary == event1.title - assert entry1.categories == event1.tags |> Enum.map(& &1.slug) + assert entry1.categories == event1.tags |> Enum.map(& &1.title) end test "it returns 404 for an not existing feed", %{conn: conn} do diff --git a/test/support/factory.ex b/test/support/factory.ex index bbb0d2250..336eccdf2 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -124,6 +124,7 @@ defmodule Mobilizon.Factory do visibility: :public, tags: build_list(3, :tag), mentions: [], + publish_at: DateTime.utc_now(), url: Routes.page_url(Endpoint, :event, uuid), picture: insert(:picture), uuid: uuid,