diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1883f8ef0..797504dec 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,7 +4,6 @@ stages: - install - check - build-js - - sentry - test - build - upload @@ -93,21 +92,6 @@ build-frontend: needs: - lint-front -sentry-commit: - stage: sentry - image: getsentry/sentry-cli - script: - - echo "Create a new release $CI_COMMIT_TAG" - - sentry-cli releases new $CI_COMMIT_TAG - - sentry-cli releases set-commits $CI_COMMIT_TAG --auto - - sentry-cli releases files $CI_COMMIT_TAG upload-sourcemaps priv/static/assets/ - - sentry-cli releases finalize $CI_COMMIT_TAG - - echo "Finalized release for $CI_COMMIT_TAG" - needs: - - build-frontend - only: - - tags@framasoft/mobilizon - deps: stage: check before_script: @@ -162,6 +146,8 @@ vitest: e2e: stage: test + except: + - tags@framasoft/mobilizon services: - name: postgis/postgis:16-3.4 alias: postgres diff --git a/default.nix b/default.nix index 3f83816f3..dcfe2ec1d 100644 --- a/default.nix +++ b/default.nix @@ -17,7 +17,7 @@ mixRelease rec { # This has to be kept in sync with the version in mix.exs and package.json! # Otherwise the nginx routing isn't going to work properly. - version = "5.0.0-beta.1"; + version = "5.1.0"; inherit src; diff --git a/integration-test.nix b/integration-test.nix index 57794041f..d98f8dda5 100644 --- a/integration-test.nix +++ b/integration-test.nix @@ -28,6 +28,8 @@ in }; }; + systemd.services.mobilizon-postgresql.serviceConfig.Restart = "on-failure"; + services.postgresql.package = pkgs.postgresql_14; security.pki.certificateFiles = [ certs.ca.cert ]; diff --git a/lib/graphql/api/search.ex b/lib/graphql/api/search.ex index eba18c20f..d7ef8da0b 100644 --- a/lib/graphql/api/search.ex +++ b/lib/graphql/api/search.ex @@ -57,7 +57,8 @@ defmodule Mobilizon.GraphQL.API.Search do current_actor_id: Map.get(args, :current_actor_id), exclude_my_groups: Map.get(args, :exclude_my_groups, false), exclude_stale_actors: true, - local_only: Map.get(args, :search_target, :internal) == :self + local_only: Map.get(args, :search_target, :internal) == :self, + sort_by: Map.get(args, :sort_by) ], page, limit diff --git a/lib/graphql/resolvers/event.ex b/lib/graphql/resolvers/event.ex index fe7aaec69..04ab9b8c5 100644 --- a/lib/graphql/resolvers/event.ex +++ b/lib/graphql/resolvers/event.ex @@ -76,7 +76,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do limit: limit, order_by: order_by, direction: direction, - longevents: longevents, + long_events: long_events, location: location, radius: radius }, @@ -84,7 +84,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do ) when limit < @event_max_limit do {:ok, - Events.list_events(page, limit, order_by, direction, true, longevents, location, radius)} + Events.list_events(page, limit, order_by, direction, true, long_events, location, radius)} end def list_events( diff --git a/lib/graphql/schema/event.ex b/lib/graphql/schema/event.ex index 4ecd682fe..253756319 100644 --- a/lib/graphql/schema/event.ex +++ b/lib/graphql/schema/event.ex @@ -32,6 +32,11 @@ defmodule Mobilizon.GraphQL.Schema.EventType do field(:description, :string, description: "The event's description") field(:begins_on, :datetime, description: "Datetime for when the event begins") field(:ends_on, :datetime, description: "Datetime for when the event ends") + + field(:long_event, :boolean, + description: "Whether the event is a long event (activity) or not" + ) + field(:status, :event_status, description: "Status of the event") field(:visibility, :event_visibility, description: "The event's visibility") field(:join_options, :event_join_options, description: "The event's visibility") @@ -395,7 +400,7 @@ defmodule Mobilizon.GraphQL.Schema.EventType do description: "Direction for the sort" ) - arg(:longevents, :boolean, + arg(:long_events, :boolean, default_value: nil, description: "if mention filter in or out long events" ) diff --git a/lib/graphql/schema/search.ex b/lib/graphql/schema/search.ex index f518c13dc..7d8448102 100644 --- a/lib/graphql/schema/search.ex +++ b/lib/graphql/schema/search.ex @@ -17,6 +17,11 @@ defmodule Mobilizon.GraphQL.Schema.SearchType do field(:title, :string, description: "The event's title") field(:begins_on, :datetime, description: "Datetime for when the event begins") field(:ends_on, :datetime, description: "Datetime for when the event ends") + + field(:long_event, :boolean, + description: "Whether the event is a long event (activity) or not" + ) + field(:status, :event_status, description: "Status of the event") field(:picture, :media, description: "The event's picture") field(:physical_address, :address, description: "The event's physical address") @@ -52,6 +57,11 @@ defmodule Mobilizon.GraphQL.Schema.SearchType do field(:title, :string, description: "The event's title") field(:begins_on, :datetime, description: "Datetime for when the event begins") field(:ends_on, :datetime, description: "Datetime for when the event ends") + + field(:long_event, :boolean, + description: "Whether the event is a long event (activity) or not" + ) + field(:status, :event_status, description: "Status of the event") field(:picture, :media, description: "The event's picture") field(:physical_address, :address, description: "The event's physical address") @@ -171,7 +181,11 @@ defmodule Mobilizon.GraphQL.Schema.SearchType do enum :search_group_sort_options do value(:match_desc, description: "The pertinence of the result") - value(:member_count_desc, description: "The members count of the group") + value(:member_count_asc, description: "The members count of the group ascendant order") + value(:member_count_desc, description: "The members count of the group descendent order") + value(:created_at_asc, description: "When the group was created ascendant order") + value(:created_at_desc, description: "When the group was created descendent order") + value(:last_event_activity, description: "Last event activity of the group") end enum :search_event_sort_options do @@ -273,7 +287,7 @@ defmodule Mobilizon.GraphQL.Schema.SearchType do description: "Radius around the location to search in" ) - arg(:longevents, :boolean, description: "if mention filter in or out long events") + arg(:long_events, :boolean, description: "if mention filter in or out long events") arg(:bbox, :string, description: "The bbox to search events into") arg(:zoom, :integer, description: "The zoom level for searching events") diff --git a/lib/mobilizon/actors/actors.ex b/lib/mobilizon/actors/actors.ex index cc78c0807..1349b32f7 100644 --- a/lib/mobilizon/actors/actors.ex +++ b/lib/mobilizon/actors/actors.ex @@ -13,6 +13,7 @@ defmodule Mobilizon.Actors do alias Mobilizon.Actors.{Actor, Bot, Follower, Member} alias Mobilizon.Addresses.Address alias Mobilizon.Crypto + alias Mobilizon.Events.Event alias Mobilizon.Events.FeedToken alias Mobilizon.Medias alias Mobilizon.Service.Workers @@ -518,7 +519,6 @@ defmodule Mobilizon.Actors do query = from(a in Actor) query - |> distinct([q], q.id) |> actor_by_username_or_name_query(term) |> maybe_join_address( Keyword.get(options, :location), @@ -532,8 +532,56 @@ defmodule Mobilizon.Actors do |> filter_by_minimum_visibility(Keyword.get(options, :minimum_visibility, :public)) |> filter_suspended(false) |> filter_out_anonymous_actor_id(anonymous_actor_id) + # order_by + |> actor_order(Keyword.get(options, :sort_by, :match_desc)) end + # sort by most recent id if "best match" + defp actor_order(query, :match_desc) do + query + |> order_by([q], desc: q.id) + end + + defp actor_order(query, :last_event_activity) do + query + |> join(:left, [q], e in Event, on: e.attributed_to_id == q.id) + |> group_by([q, e], q.id) + |> order_by([q, e], [ + # put groups with no events at the end of the list + fragment("MAX(?) IS NULL", e.updated_at), + # last edited event of the group + desc: max(e.updated_at), + # sort group with no event by id + desc: q.id + ]) + end + + defp actor_order(query, :member_count_asc) do + query + |> join(:left, [q], m in Member, on: m.parent_id == q.id) + |> group_by([q, m], q.id) + |> order_by([q, m], asc: count(m.id), asc: q.id) + end + + defp actor_order(query, :member_count_desc) do + query + |> join(:left, [q], m in Member, on: m.parent_id == q.id) + |> group_by([q, m], q.id) + |> order_by([q, m], desc: count(m.id), desc: q.id) + end + + defp actor_order(query, :created_at_asc) do + query + |> order_by([q], asc: q.inserted_at) + end + + defp actor_order(query, :created_at_desc) do + query + |> order_by([q], desc: q.inserted_at) + end + + defp actor_order(query, _), do: query + @doc """ Gets a group by its title. """ @@ -1394,16 +1442,6 @@ defmodule Mobilizon.Actors do ^username ) ) - |> order_by( - [a], - fragment( - "word_similarity(?, ?) + word_similarity(coalesce(?, ''), ?) desc", - a.preferred_username, - ^username, - a.name, - ^username - ) - ) end @spec maybe_join_address( diff --git a/lib/mobilizon/events/event.ex b/lib/mobilizon/events/event.ex index ff76568ae..8e39f5e70 100644 --- a/lib/mobilizon/events/event.ex +++ b/lib/mobilizon/events/event.ex @@ -66,6 +66,7 @@ defmodule Mobilizon.Events.Event do participants: [Actor.t()], contacts: [Actor.t()], language: String.t(), + long_event: boolean, metadata: [EventMetadata.t()] } @@ -89,7 +90,8 @@ defmodule Mobilizon.Events.Event do :picture_id, :physical_address_id, :attributed_to_id, - :language + :language, + :long_event ] @attrs @required_attrs ++ @optional_attrs @@ -102,6 +104,7 @@ defmodule Mobilizon.Events.Event do field(:slug, :string) field(:description, :string) field(:ends_on, :utc_datetime) + field(:long_event, :boolean, virtual: true, default: nil) field(:title, :string) field(:status, EventStatus, default: :confirmed) field(:draft, :boolean, default: false) diff --git a/lib/mobilizon/events/events.ex b/lib/mobilizon/events/events.ex index 4d68b9d41..f741354c3 100644 --- a/lib/mobilizon/events/events.ex +++ b/lib/mobilizon/events/events.ex @@ -12,6 +12,8 @@ defmodule Mobilizon.Events do import Mobilizon.Storage.Ecto import Mobilizon.Events.Utils, only: [calculate_notification_time: 1] + require Logger + alias Ecto.{Changeset, Multi} alias Mobilizon.Actors.{Actor, Follower} @@ -141,6 +143,7 @@ defmodule Mobilizon.Events do url |> event_by_url_query() |> Repo.one() + |> with_virtual_fields() end @doc """ @@ -153,6 +156,7 @@ defmodule Mobilizon.Events do |> event_by_url_query() |> preload_for_event() |> Repo.one!() + |> with_virtual_fields() end @doc """ @@ -167,6 +171,7 @@ defmodule Mobilizon.Events do |> filter_draft() |> preload_for_event() |> Repo.one!() + |> with_virtual_fields() end @doc """ @@ -180,6 +185,7 @@ defmodule Mobilizon.Events do |> filter_draft() |> preload_for_event() |> Repo.one() + |> with_virtual_fields() end @spec check_if_event_has_instance_follow(String.t(), integer()) :: boolean() @@ -199,6 +205,7 @@ defmodule Mobilizon.Events do |> event_by_uuid_query() |> preload_for_event() |> Repo.one() + |> with_virtual_fields() end @doc """ @@ -212,6 +219,7 @@ defmodule Mobilizon.Events do |> filter_not_event_uuid(not_event_uuid) |> filter_draft() |> Repo.one() + |> with_virtual_fields() end @doc """ @@ -366,7 +374,7 @@ defmodule Mobilizon.Events do atom, boolean, boolean | nil, - string | nil, + String.t() | nil, float | nil ) :: Page.t(Event.t()) def list_events( @@ -375,7 +383,7 @@ defmodule Mobilizon.Events do sort \\ :begins_on, direction \\ :asc, is_future \\ true, - longevents \\ nil, + long_events \\ nil, location \\ nil, radius \\ nil ) do @@ -386,12 +394,13 @@ defmodule Mobilizon.Events do |> maybe_join_address(%{location: location, radius: radius}) |> events_for_location(%{location: location, radius: radius}) |> filter_future_events(is_future) - |> events_for_longevents(longevents) + |> events_for_long_events(long_events) |> filter_public_visibility() |> filter_draft() |> filter_cancelled_events() |> filter_local_or_from_followed_instances_events() |> Page.build_page(page, limit) + |> with_virtual_fields() end @spec stream_events_for_sitemap :: Enum.t() @@ -413,6 +422,7 @@ defmodule Mobilizon.Events do |> preload_for_event() |> event_order_by(sort, direction) |> Page.build_page(page, limit) + |> with_virtual_fields() end @doc """ @@ -425,6 +435,7 @@ defmodule Mobilizon.Events do |> events_by_tags_query(limit) |> filter_draft() |> Repo.all() + |> with_virtual_fields() end @doc """ @@ -440,6 +451,7 @@ defmodule Mobilizon.Events do actor_id |> do_list_public_events_for_actor() |> Page.build_page(page, limit) + |> with_virtual_fields() end @doc """ @@ -467,6 +479,7 @@ defmodule Mobilizon.Events do |> do_list_public_events_for_actor() |> event_filter_begins_on(DateTime.utc_now(), nil) |> Page.build_page(page, limit) + |> with_virtual_fields() end @spec do_list_public_events_for_actor(integer()) :: Ecto.Query.t() @@ -485,6 +498,7 @@ defmodule Mobilizon.Events do |> event_for_actor_query(desc: :begins_on) |> preload_for_event() |> Page.build_page(page, limit) + |> with_virtual_fields() end @spec list_simple_organized_events_for_group(Actor.t(), integer | nil, integer | nil) :: @@ -516,10 +530,13 @@ defmodule Mobilizon.Events do group_id |> event_for_group_query() |> event_filter_visibility(visibility) - |> event_filter_begins_on(after_datetime, before_datetime) + # We want future and ongoing events, so we use ends_on + # See issue #1567 + |> event_filter_ends_on(after_datetime, before_datetime) |> event_order_by(order_by, order_direction) |> preload_for_event() |> Page.build_page(page, limit) + |> with_virtual_fields() end @spec list_drafts_for_user(integer, integer | nil, integer | nil) :: Page.t(Event.t()) @@ -529,6 +546,7 @@ defmodule Mobilizon.Events do |> filter_draft(true) |> order_by(desc: :updated_at) |> Page.build_page(page, limit) + |> with_virtual_fields() end @spec user_moderator_for_event?(integer | String.t(), integer | String.t()) :: boolean @@ -552,6 +570,7 @@ defmodule Mobilizon.Events do |> close_events_query(radius) |> filter_draft() |> Repo.all() + |> with_virtual_fields() end @doc """ @@ -587,7 +606,7 @@ defmodule Mobilizon.Events do |> events_for_search_query() |> events_for_begins_on(Map.get(args, :begins_on, DateTime.utc_now())) |> events_for_ends_on(Map.get(args, :ends_on)) - |> events_for_longevents(Map.get(args, :longevents)) + |> events_for_long_events(Map.get(args, :long_events)) |> events_for_category(args) |> events_for_categories(args) |> events_for_languages(args) @@ -602,7 +621,8 @@ defmodule Mobilizon.Events do |> filter_local_or_from_followed_instances_events() |> filter_public_visibility() |> event_order(Map.get(args, :sort_by, :match_desc), search_string) - |> Page.build_page(page, limit, :begins_on) + |> Page.build_page(page, limit) + |> with_virtual_fields() end @doc """ @@ -978,6 +998,7 @@ defmodule Mobilizon.Events do actor_id |> event_participations_for_actor_query() |> Page.build_page(page, limit) + |> with_virtual_fields() end @doc """ @@ -993,6 +1014,7 @@ defmodule Mobilizon.Events do actor_id |> event_participations_for_actor_query(DateTime.utc_now()) |> Page.build_page(page, limit) + |> with_virtual_fields() end @doc """ @@ -1394,14 +1416,14 @@ defmodule Mobilizon.Events do end end - @spec events_for_longevents(Ecto.Queryable.t(), Boolean.t() | nil) :: Ecto.Query.t() - defp events_for_longevents(query, longevents) do + @spec events_for_long_events(Ecto.Queryable.t(), Boolean.t() | nil) :: Ecto.Query.t() + defp events_for_long_events(query, long_events) do duration = Config.get([:instance, :duration_of_long_event], 0) if duration <= 0 do query else - case longevents do + case long_events do nil -> query @@ -1871,6 +1893,7 @@ defmodule Mobilizon.Events do ) ) |> Repo.all() + |> with_virtual_fields() end @spec list_participations_for_user_query(integer()) :: Ecto.Query.t() @@ -2003,6 +2026,35 @@ defmodule Mobilizon.Events do |> where([e], e.begins_on > ^after_datetime) end + defp event_filter_ends_on(query, nil, nil), do: query + + defp event_filter_ends_on(query, %DateTime{} = after_datetime, nil) do + where( + query, + [e], + (is_nil(e.ends_on) and e.begins_on >= ^after_datetime) or + e.ends_on >= ^after_datetime + ) + end + + defp event_filter_ends_on(query, nil, %DateTime{} = before_datetime) do + where( + query, + [e], + (is_nil(e.ends_on) and e.begins_on <= ^before_datetime) or e.ends_on <= ^before_datetime + ) + end + + defp event_filter_ends_on( + query, + %DateTime{} = after_datetime, + %DateTime{} = before_datetime + ) do + query + |> event_filter_ends_on(after_datetime, nil) + |> event_filter_ends_on(nil, before_datetime) + end + defp event_order_by(query, order_by, direction) when order_by in [:begins_on, :inserted_at, :updated_at] and direction in [:asc, :desc] do order_by_instruction = Keyword.new([{direction, order_by}]) @@ -2098,4 +2150,44 @@ defmodule Mobilizon.Events do |> preload_for_event() |> Page.chunk(chunk_size) end + + # Handling the case where Repo.XXXX() return nil + def with_virtual_fields(nil), do: nil + + # if envent has no end date, it can not be a long events + def with_virtual_fields(%Event{} = event) when is_nil(event.ends_on), + do: %{event | long_event: false} + + # Handling the case where there is an event + # Using Repo.one(), for example + def with_virtual_fields(%Event{} = event) do + duration = Config.get([:instance, :duration_of_long_event], 0) + + event_duration = DateTime.diff(event.ends_on, event.begins_on, :day) + + # duration need to be > 0 for long event to be activated + long_event = duration > 0 && event_duration > duration + + %{event | long_event: long_event} + end + + # Handling the case where there is a list of events + # Using Repo.all(), for example + def with_virtual_fields(events) when is_list(events) do + Enum.map(events, &with_virtual_fields/1) + end + + # Handling the case of a paginated list of events + def with_virtual_fields(%Page{total: _total, elements: elements} = page) do + elements_with_virtual_fields = Enum.map(elements, &with_virtual_fields/1) + %{page | elements: elements_with_virtual_fields} + end + + # In case the function is called on an element without virtual_fields + def with_virtual_fields(invalid) do + Logger.warning("with_virtual_fields called on invalid element : #{inspect(invalid)}") + + # Return the element without modification + invalid + end end diff --git a/lib/mobilizon/instances/instances.ex b/lib/mobilizon/instances/instances.ex index db0dafd29..8d6557877 100644 --- a/lib/mobilizon/instances/instances.ex +++ b/lib/mobilizon/instances/instances.ex @@ -73,7 +73,7 @@ defmodule Mobilizon.Instances do query end - %Page{elements: elements} = paged_instances = Page.build_page(query, page, limit, :domain) + %Page{elements: elements} = paged_instances = Page.build_page(query, page, limit) %Page{ paged_instances diff --git a/lib/mobilizon/storage/page.ex b/lib/mobilizon/storage/page.ex index 6bdf90dc6..cc1ff6de6 100644 --- a/lib/mobilizon/storage/page.ex +++ b/lib/mobilizon/storage/page.ex @@ -19,15 +19,23 @@ defmodule Mobilizon.Storage.Page do @doc """ Returns a Page struct for a query. - - `field` is used to define the field that will be used for the count aggregate, which should be the same as the field used for order_by - See https://stackoverflow.com/q/12693089/10204399 """ - @spec build_page(Ecto.Queryable.t(), integer | nil, integer | nil, atom()) :: t(any) - def build_page(query, page, limit, field \\ :id) do + @spec build_page(Ecto.Queryable.t(), integer | nil, integer) :: t(any) + def build_page(query, page, limit) do + count_query = + query + # Exclude select because we add a new one below + |> exclude(:select) + # Exclude order_by for perf + |> exclude(:order_by) + # Exclude preloads to avoid error "cannot preload associations in subquery" + |> exclude(:preload) + |> subquery() + |> select([r], count(fragment("*"))) + [total, elements] = [ - fn -> Repo.aggregate(query, :count, field) end, + fn -> Repo.one(count_query) end, fn -> Repo.all(paginate(query, page, limit)) end ] |> Enum.map(&Task.async/1) diff --git a/mix.exs b/mix.exs index d5901e806..e8d2bdd86 100644 --- a/mix.exs +++ b/mix.exs @@ -1,7 +1,7 @@ defmodule Mobilizon.Mixfile do use Mix.Project - @version "5.0.0-beta.1" + @version "5.1.0" def project do [ diff --git a/package-lock.json b/package-lock.json index bdf530331..9c25baef7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,13 @@ { "name": "mobilizon", - "version": "5.0.0-beta.1", + "version": "5.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mobilizon", - "version": "5.0.0-beta.1", + "version": "5.1.0", + "hasInstallScript": true, "dependencies": { "@apollo/client": "^3.9.5", "@framasoft/socket": "^1.0.0", diff --git a/package.json b/package.json index 64c143ba1..ebee164cb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mobilizon", - "version": "5.0.0-beta.1", + "version": "5.1.0", "private": true, "scripts": { "dev": "vite", diff --git a/priv/repo/migrations/20240725130410_add_event_physical_address_index.exs b/priv/repo/migrations/20240725130410_add_event_physical_address_index.exs new file mode 100644 index 000000000..b9201c901 --- /dev/null +++ b/priv/repo/migrations/20240725130410_add_event_physical_address_index.exs @@ -0,0 +1,11 @@ +defmodule Mobilizon.Storage.Repo.Migrations.AddEventPhysicalAddressIndex do + use Ecto.Migration + + def up do + create(index("events", [:physical_address_id], name: :events_phys_addr_id)) + end + + def down do + drop(index("events", [:physical_address_id], name: :events_phys_addr_id)) + end +end diff --git a/schema.graphql b/schema.graphql index 372868580..1e5d1e707 100644 --- a/schema.graphql +++ b/schema.graphql @@ -2268,7 +2268,7 @@ type RootQueryType { endsOn: DateTime "Filter for long events in function of configuration parameter 'duration_of_long_event'" - longevents: Boolean + longEvents: Boolean ): Events "Interact with an URI" diff --git a/src/assets/oruga-tailwindcss.css b/src/assets/oruga-tailwindcss.css index 8f33ffe8b..0d5941ebe 100644 --- a/src/assets/oruga-tailwindcss.css +++ b/src/assets/oruga-tailwindcss.css @@ -210,7 +210,7 @@ body { } .checkbox-check { - @apply appearance-none bg-primary border-primary; + @apply appearance-none border-primary; } .checkbox-checked { @@ -250,7 +250,7 @@ body { @apply mr-2; } .form-radio { - @apply bg-none text-primary accent-primary; + @apply bg-none text-primary border-primary accent-primary; } .radio-label { @apply pl-2; diff --git a/src/components/Event/EventActionSection.vue b/src/components/Event/EventActionSection.vue index eb8e3d41b..a7b855ff4 100644 --- a/src/components/Event/EventActionSection.vue +++ b/src/components/Event/EventActionSection.vue @@ -110,67 +110,66 @@ {{ t("Actions") }} - - - - {{ t("Participations") }} - + }) + " + > + + {{ t("Participations") }} - - - - {{ t("Announcements") }} - + }) + " + > + + {{ t("Announcements") }} - - - {{ t("Edit") }} - + }) + " + > + + {{ t("Edit") }} - - - {{ t("Duplicate") }} - + }) + " + > + + {{ t("Duplicate") }} {{ t("Delete") }} diff --git a/src/components/Event/EventDatePicker.vue b/src/components/Event/EventDatePicker.vue new file mode 100644 index 000000000..fdd37b8f6 --- /dev/null +++ b/src/components/Event/EventDatePicker.vue @@ -0,0 +1,66 @@ + + diff --git a/src/components/Event/EventFullDate.vue b/src/components/Event/EventFullDate.vue index aaa1d886c..f71405ee8 100644 --- a/src/components/Event/EventFullDate.vue +++ b/src/components/Event/EventFullDate.vue @@ -3,83 +3,74 @@ {{ formatDateTimeString(beginsOn, timezoneToShow, showStartTime) }} -
- - {{ singleTimeZone }} -

-

- {{ - t("On {date} from {startTime} to {endTime}", { - date: formatDate(beginsOn), - startTime: formatTime(beginsOn, timezoneToShow), - endTime: formatTime(endsOn, timezoneToShow), - }) - }} -
- - {{ singleTimeZone }} - -

-

- {{ - t("On {date} starting at {startTime}", { - date: formatDate(beginsOn), - startTime: formatTime(beginsOn, timezoneToShow), - }) - }} -

-

- {{ t("On {date}", { date: formatDate(beginsOn) }) }} -

-

+ + + +

{{ t("From the {startDate} at {startTime} to the {endDate} at {endTime}", { startDate: formatDate(beginsOn), - startTime: formatTime(beginsOn, timezoneToShow), + startTime: formatTime(beginsOn), endDate: formatDate(endsOn), - endTime: formatTime(endsOn, timezoneToShow), + endTime: formatTime(endsOn), }) }} -
- - {{ multipleTimeZones }} -

-

+

{{ t("From the {startDate} at {startTime} to the {endDate}", { startDate: formatDate(beginsOn), - startTime: formatTime(beginsOn, timezoneToShow), + startTime: formatTime(beginsOn), endDate: formatDate(endsOn), }) }} -
- - {{ singleTimeZone }} -

-

+

+ + {{ + t("From the {startDate} to the {endDate} at {endTime}", { + startDate: formatDate(beginsOn), + endDate: formatDate(endsOn), + endTime: formatTime(endsOn), + }) + }} + +

+

{{ t("From the {startDate} to the {endDate}", { startDate: formatDate(beginsOn), @@ -87,6 +78,13 @@ }) }}

+ + {{ singleTimeZone }} + diff --git a/src/components/Event/EventParticipationCard.vue b/src/components/Event/EventParticipationCard.vue index 0376ee13e..2356a9888 100644 --- a/src/components/Event/EventParticipationCard.vue +++ b/src/components/Event/EventParticipationCard.vue @@ -124,9 +124,13 @@ {{ - t("{number} seats left", { - number: seatsLeft, - }) + t( + "{number} seats left", + { + number: seatsLeft, + }, + seatsLeft ?? 0 + ) }} -
+
{{ t("Edit") }}
@@ -220,16 +222,14 @@ -
+
{{ t("Duplicate") }}
@@ -243,8 +243,9 @@ ParticipantRole.NOT_APPROVED, ].includes(participation.role) " + @click="openDeleteEventModalWrapper" > -
+
{{ t("Delete") }}
@@ -258,16 +259,14 @@ ParticipantRole.NOT_APPROVED, ].includes(participation.role) " + @click=" + gotToWithCheck(participation, { + name: RouteName.PARTICIPATIONS, + params: { eventId: participation.event.uuid }, + }) + " > -
+
{{ t("Manage participations") }}
@@ -282,30 +281,28 @@ ParticipantRole.NOT_APPROVED, ].includes(participation.role) " - > - - - {{ t("Announcements") }} - + }) + " + > + + {{ t("Announcements") }} - - - - {{ t("View event page") }} - + params: { eventId: participation.event.uuid }, + }) + " + > + + {{ t("View event page") }}
diff --git a/src/components/Event/FullAddressAutoComplete.vue b/src/components/Event/FullAddressAutoComplete.vue index f4e7f7c9a..7e2836a29 100644 --- a/src/components/Event/FullAddressAutoComplete.vue +++ b/src/components/Event/FullAddressAutoComplete.vue @@ -68,6 +68,7 @@ + => { }; const selectedAddressText = computed(() => { - if (!selected) return undefined; + if (!selected || !selected.id) return undefined; return addressFullName(selected); }); diff --git a/src/components/Event/OrganizerPicker.vue b/src/components/Event/OrganizerPicker.vue index d2797a0de..912e9320c 100644 --- a/src/components/Event/OrganizerPicker.vue +++ b/src/components/Event/OrganizerPicker.vue @@ -1,6 +1,7 @@ diff --git a/src/components/Group/Sections/EventsSection.vue b/src/components/Group/Sections/EventsSection.vue index 3aa84cf07..0931f711e 100644 --- a/src/components/Group/Sections/EventsSection.vue +++ b/src/components/Group/Sections/EventsSection.vue @@ -1,8 +1,7 @@ @@ -50,5 +70,9 @@ import GroupSection from "@/components/Group/GroupSection.vue"; const { t } = useI18n({ useScope: "global" }); -defineProps<{ group: IGroup; isModerator: boolean }>(); +defineProps<{ + group: IGroup; + isModerator: boolean; + longEvent: boolean; +}>(); diff --git a/src/components/Group/Sections/PostsSection.vue b/src/components/Group/Sections/PostsSection.vue index 3ee6812f8..4f2ac7f6e 100644 --- a/src/components/Group/Sections/PostsSection.vue +++ b/src/components/Group/Sections/PostsSection.vue @@ -2,16 +2,28 @@ diff --git a/src/components/Local/LastEvents.vue b/src/components/Local/LastEvents.vue deleted file mode 100644 index 959cec8a0..000000000 --- a/src/components/Local/LastEvents.vue +++ /dev/null @@ -1,68 +0,0 @@ - - - diff --git a/src/components/Local/OnlineEvents.vue b/src/components/Local/OnlineEvents.vue deleted file mode 100644 index bf4036764..000000000 --- a/src/components/Local/OnlineEvents.vue +++ /dev/null @@ -1,75 +0,0 @@ - - - diff --git a/src/components/NavBar.vue b/src/components/NavBar.vue index 60ca38270..9317c564f 100644 --- a/src/components/NavBar.vue +++ b/src/components/NavBar.vue @@ -11,8 +11,24 @@ > - -
+
+