Merge branch 'fixes' into 'main'

Various fixes

Closes #1236, #1280 et #1277

See merge request framasoft/mobilizon!1381
This commit is contained in:
Thomas Citharel 2023-04-26 15:07:42 +00:00
commit 94a51fbcab
19 changed files with 146 additions and 69 deletions

View File

@ -78,3 +78,5 @@ config :mobilizon, :exports,
config :tz_world, config :tz_world,
data_dir: System.get_env("MOBILIZON_TIMEZONES_DIR", "/var/lib/mobilizon/timezones") data_dir: System.get_env("MOBILIZON_TIMEZONES_DIR", "/var/lib/mobilizon/timezones")
config :tzdata, :data_dir, System.get_env("MOBILIZON_TIMEZONES_DIR", "/var/lib/mobilizon/tzdata")

View File

@ -272,11 +272,14 @@ button.menubar__button {
@apply px-3 dark:text-black; @apply px-3 dark:text-black;
} }
.pagination-link-current { .pagination-link-current {
@apply bg-primary cursor-not-allowed pointer-events-none border-primary text-white dark:text-zinc-900; @apply bg-primary dark:bg-primary cursor-not-allowed pointer-events-none border-primary text-white dark:text-zinc-900;
} }
.pagination-ellipsis { .pagination-ellipsis {
@apply text-center m-1 text-gray-300; @apply text-center m-1 text-gray-300;
} }
.pagination-link-disabled {
@apply bg-gray-200 dark:bg-gray-400;
}
/** Tabs */ /** Tabs */
.tabs-nav { .tabs-nav {

View File

@ -194,6 +194,8 @@
has-modal-card has-modal-card
ref="reportModal" ref="reportModal"
:close-button-aria-label="t('Close')" :close-button-aria-label="t('Close')"
:autoFocus="false"
:trapFocus="false"
> >
<ReportModal <ReportModal
:on-confirm="reportComment" :on-confirm="reportComment"

View File

@ -190,6 +190,8 @@
has-modal-card has-modal-card
ref="reportModal" ref="reportModal"
:close-button-aria-label="t('Close')" :close-button-aria-label="t('Close')"
:autoFocus="false"
:trapFocus="false"
> >
<ReportModal <ReportModal
:on-confirm="reportEvent" :on-confirm="reportEvent"

View File

@ -39,7 +39,7 @@ import { IAddress } from "@/types/address.model";
import { AddressSearchType } from "@/types/enums"; import { AddressSearchType } from "@/types/enums";
import { computed, defineAsyncComponent } from "vue"; import { computed, defineAsyncComponent } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useRouter } from "vue-router"; import { useRouter, useRoute } from "vue-router";
import RouteName from "@/router/name"; import RouteName from "@/router/name";
const FullAddressAutoComplete = defineAsyncComponent( const FullAddressAutoComplete = defineAsyncComponent(
@ -53,6 +53,7 @@ const props = defineProps<{
}>(); }>();
const router = useRouter(); const router = useRouter();
const route = useRoute();
const emit = defineEmits<{ const emit = defineEmits<{
(event: "update:location", location: IAddress | null): void; (event: "update:location", location: IAddress | null): void;
@ -89,6 +90,7 @@ const submit = () => {
router.push({ router.push({
name: RouteName.SEARCH, name: RouteName.SEARCH,
query: { query: {
...route.query,
locationName: location.value?.locality ?? location.value?.region, locationName: location.value?.locality ?? location.value?.region,
lat, lat,
lon, lon,

View File

@ -20,38 +20,40 @@
}} }}
</p> </p>
</div> </div>
<div class=""> <div>
<div class="" v-if="comment"> <article v-if="comment">
<article class=""> <div>
<div class=""> <figure class="h-8 w-8" v-if="comment?.actor?.avatar">
<figure class="h-8 w-8" v-if="comment?.actor?.avatar"> <img
<img :src="comment.actor.avatar.url"
:src="comment.actor.avatar.url" alt=""
alt="" width="48"
width="48" height="48"
height="48" />
/> </figure>
</figure> <AccountCircle v-else :size="48" />
<AccountCircle v-else :size="48" /> </div>
</div> <div class="prose dark:prose-invert">
<div class=""> <strong>{{ comment?.actor?.name }}</strong>
<div class="prose dark:prose-invert"> <small v-if="comment.actor"
<strong>{{ comment?.actor?.name }}</strong> >@{{ usernameWithDomain(comment?.actor) }}</small
<small v-if="comment.actor" >
>@{{ usernameWithDomain(comment?.actor) }}</small <br />
> <p v-html="comment.text"></p>
<br /> </div>
<p v-html="comment.text"></p> </article>
</div>
</div>
</article>
</div>
<o-field <o-field
:label="t('Additional comments')" :label="t('Additional comments')"
label-for="additional-comments" label-for="additional-comments"
> >
<o-input v-model="content" type="textarea" id="additional-comments" /> <o-input
v-model="content"
type="textarea"
id="additional-comments"
autofocus
ref="reportAdditionalCommentsInput"
/>
</o-field> </o-field>
<div class="control" v-if="outsideDomain"> <div class="control" v-if="outsideDomain">
@ -91,6 +93,7 @@ import { useI18n } from "vue-i18n";
import { IComment } from "../../types/comment.model"; import { IComment } from "../../types/comment.model";
import { usernameWithDomain } from "@/types/actor"; import { usernameWithDomain } from "@/types/actor";
import AccountCircle from "vue-material-design-icons/AccountCircle.vue"; import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
import { useFocus } from "@vueuse/core";
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
@ -108,18 +111,17 @@ const props = withDefaults(
const emit = defineEmits(["close"]); const emit = defineEmits(["close"]);
// @Component({
// mounted() {
// this.$data.isActive = true;
// },
// })
// isActive = false;
const content = ref(""); const content = ref("");
const forward = ref(false); const forward = ref(false);
const reportAdditionalCommentsInput = ref();
// https://github.com/oruga-ui/oruga/issues/339
const reportAdditionalCommentsInputComp = computed(
() => reportAdditionalCommentsInput.value?.$refs.textarea
);
useFocus(reportAdditionalCommentsInputComp, { initialValue: true });
const { t } = useI18n({ useScope: "global" }); const { t } = useI18n({ useScope: "global" });
const translatedCancelText = computed((): string => { const translatedCancelText = computed((): string => {

View File

@ -31,6 +31,7 @@
ref="input" ref="input"
v-bind="inputAttrs" v-bind="inputAttrs"
@keydown.enter="confirm" @keydown.enter="confirm"
autofocus
/> />
</o-field> </o-field>
</div> </div>
@ -48,6 +49,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useFocus } from "@vueuse/core";
import { computed, nextTick, ref } from "vue"; import { computed, nextTick, ref } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
@ -87,6 +89,12 @@ const { t } = useI18n({ useScope: "global" });
const prompt = ref<string>(props.hasInput ? props.inputAttrs?.value ?? "" : ""); const prompt = ref<string>(props.hasInput ? props.inputAttrs?.value ?? "" : "");
const input = ref(); const input = ref();
// https://github.com/oruga-ui/oruga/issues/339
const promptInputComp = computed(() => input.value?.$refs.input);
if (props.hasInput) {
useFocus(promptInputComp, { initialValue: true });
}
// const dialogClass = computed(() => { // const dialogClass = computed(() => {
// return [props.size]; // return [props.size];
// }); // });

View File

@ -230,6 +230,8 @@ const icons: Record<string, () => Promise<any>> = {
import( import(
`../../../node_modules/vue-material-design-icons/InboxArrowDown.vue` `../../../node_modules/vue-material-design-icons/InboxArrowDown.vue`
), ),
InboxArrowUp: () =>
import(`../../../node_modules/vue-material-design-icons/InboxArrowUp.vue`),
LanDisconnect: () => LanDisconnect: () =>
import(`../../../node_modules/vue-material-design-icons/LanDisconnect.vue`), import(`../../../node_modules/vue-material-design-icons/LanDisconnect.vue`),
CloudQuestion: () => CloudQuestion: () =>

View File

@ -48,6 +48,7 @@ export class Dialog {
inputAttrs, inputAttrs,
hasInput, hasInput,
}, },
autoFocus: false,
}); });
} }

View File

@ -66,7 +66,7 @@
/> />
</o-field> </o-field>
</div> </div>
<div v-if="instances && instances.elements.length > 0" class="mt-3"> <div v-if="instances && instances.elements.length > 0" class="my-3">
<router-link <router-link
:to="{ :to="{
name: RouteName.INSTANCE, name: RouteName.INSTANCE,
@ -133,7 +133,7 @@
<o-pagination <o-pagination
v-show="instances.total > INSTANCES_PAGE_LIMIT" v-show="instances.total > INSTANCES_PAGE_LIMIT"
:total="instances.total" :total="instances.total"
v-model="instancePage" v-model:current="instancePage"
:per-page="INSTANCES_PAGE_LIMIT" :per-page="INSTANCES_PAGE_LIMIT"
:aria-next-label="t('Next page')" :aria-next-label="t('Next page')"
:aria-previous-label="t('Previous page')" :aria-previous-label="t('Previous page')"

View File

@ -617,7 +617,12 @@
</div> </div>
</o-modal> </o-modal>
</div> </div>
<o-modal v-if="group" v-model:active="isReportModalActive"> <o-modal
v-if="group"
v-model:active="isReportModalActive"
:autoFocus="false"
:trapFocus="false"
>
<report-modal <report-modal
ref="reportModalRef" ref="reportModalRef"
:on-confirm="reportGroup" :on-confirm="reportGroup"

View File

@ -218,6 +218,8 @@
v-model:active="isReportModalActive" v-model:active="isReportModalActive"
has-modal-card has-modal-card
ref="reportModal" ref="reportModal"
:autoFocus="false"
:trapFocus="false"
> >
<ReportModal <ReportModal
:on-confirm="reportPost" :on-confirm="reportPost"

View File

@ -818,6 +818,7 @@ enum ViewMode {
enum EventSortValues { enum EventSortValues {
MATCH_DESC = "MATCH_DESC", MATCH_DESC = "MATCH_DESC",
START_TIME_ASC = "START_TIME_ASC",
START_TIME_DESC = "START_TIME_DESC", START_TIME_DESC = "START_TIME_DESC",
CREATED_AT_DESC = "CREATED_AT_DESC", CREATED_AT_DESC = "CREATED_AT_DESC",
CREATED_AT_ASC = "CREATED_AT_ASC", CREATED_AT_ASC = "CREATED_AT_ASC",
@ -831,6 +832,7 @@ enum GroupSortValues {
enum SortValues { enum SortValues {
MATCH_DESC = "MATCH_DESC", MATCH_DESC = "MATCH_DESC",
START_TIME_ASC = "START_TIME_ASC",
START_TIME_DESC = "START_TIME_DESC", START_TIME_DESC = "START_TIME_DESC",
CREATED_AT_DESC = "CREATED_AT_DESC", CREATED_AT_DESC = "CREATED_AT_DESC",
CREATED_AT_ASC = "CREATED_AT_ASC", CREATED_AT_ASC = "CREATED_AT_ASC",
@ -1157,7 +1159,7 @@ const sortOptions = computed(() => {
if (contentType.value == ContentType.EVENTS) { if (contentType.value == ContentType.EVENTS) {
options.push( options.push(
{ {
key: SortValues.START_TIME_DESC, key: SortValues.START_TIME_ASC,
label: t("Event date"), label: t("Event date"),
}, },
{ {
@ -1239,6 +1241,9 @@ const sortByForType = (
value: SortValues, value: SortValues,
allowed: typeof EventSortValues | typeof GroupSortValues allowed: typeof EventSortValues | typeof GroupSortValues
): SortValues | undefined => { ): SortValues | undefined => {
if (value === SortValues.START_TIME_ASC && when.value === "past") {
value = SortValues.START_TIME_DESC;
}
return Object.values(allowed).includes(value) ? value : undefined; return Object.values(allowed).includes(value) ? value : undefined;
}; };

View File

@ -102,7 +102,8 @@ defmodule Mobilizon.Federation.ActivityPub.Actor do
end end
@spec find_or_make_group_from_nickname(nick :: String.t()) :: @spec find_or_make_group_from_nickname(nick :: String.t()) ::
{:error, make_actor_errors | WebFinger.finger_errors()} {:ok, Mobilizon.Actors.Actor.t()}
| {:error, make_actor_errors | WebFinger.finger_errors()}
def find_or_make_group_from_nickname(nick), do: find_or_make_actor_from_nickname(nick, :Group) def find_or_make_group_from_nickname(nick), do: find_or_make_actor_from_nickname(nick, :Group)
@doc """ @doc """

View File

@ -527,6 +527,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do
Instances.refresh() Instances.refresh()
get_instance(parent, args, resolution) get_instance(parent, args, resolution)
{:error, :follow_pending} ->
{:error, dgettext("errors", "This instance is pending follow approval")}
{:error, :already_following} ->
{:error, dgettext("errors", "You are already following this instance")}
{:error, :http_error} -> {:error, :http_error} ->
{:error, dgettext("errors", "Unable to find an instance to follow at this address")} {:error, dgettext("errors", "Unable to find an instance to follow at this address")}

View File

@ -174,7 +174,8 @@ defmodule Mobilizon.GraphQL.Schema.SearchType do
enum :search_event_sort_options do enum :search_event_sort_options do
value(:match_desc, description: "The pertinence of the result") value(:match_desc, description: "The pertinence of the result")
value(:start_time_desc, description: "The start date of the result") value(:start_time_asc, description: "The start date of the result, ordered ascending")
value(:start_time_desc, description: "The start date of the result, ordered descending")
value(:created_at_desc, description: "When the event was published") value(:created_at_desc, description: "When the event was published")
value(:created_at_asc, description: "When the event was published") value(:created_at_asc, description: "When the event was published")
value(:participant_count_desc, description: "With the most participants") value(:participant_count_desc, description: "With the most participants")

View File

@ -1226,12 +1226,16 @@ defmodule Mobilizon.Actors do
approved :: boolean | nil approved :: boolean | nil
) :: ) ::
{:ok, Follower.t()} {:ok, Follower.t()}
| {:error, :already_following | :followed_suspended | Ecto.Changeset.t()} | {:error,
:already_following | :follow_pending | :followed_suspended | Ecto.Changeset.t()}
def follow(%Actor{} = followed, %Actor{} = follower, url \\ nil, approved \\ true) do def follow(%Actor{} = followed, %Actor{} = follower, url \\ nil, approved \\ true) do
if followed.suspended do if followed.suspended do
{:error, :followed_suspended} {:error, :followed_suspended}
else else
case check_follow(follower, followed) do case check_follow(follower, followed) do
%Follower{approved: false} ->
{:error, :follow_pending}
%Follower{} -> %Follower{} ->
{:error, :already_following} {:error, :already_following}

View File

@ -562,11 +562,12 @@ defmodule Mobilizon.Events do
""" """
@spec build_events_for_search(map(), integer | nil, integer | nil) :: Page.t(Event.t()) @spec build_events_for_search(map(), integer | nil, integer | nil) :: Page.t(Event.t())
def build_events_for_search(%{term: term} = args, page \\ nil, limit \\ nil) do def build_events_for_search(%{term: term} = args, page \\ nil, limit \\ nil) do
term search_string = normalize_search_string(term)
|> normalize_search_string()
search_string
|> events_for_search_query() |> events_for_search_query()
|> events_for_begins_on(args) |> events_for_begins_on(Map.get(args, :begins_on, DateTime.utc_now()))
|> events_for_ends_on(args) |> events_for_ends_on(Map.get(args, :ends_on))
|> events_for_category(args) |> events_for_category(args)
|> events_for_categories(args) |> events_for_categories(args)
|> events_for_languages(args) |> events_for_languages(args)
@ -579,7 +580,7 @@ defmodule Mobilizon.Events do
|> filter_draft() |> filter_draft()
|> filter_local_or_from_followed_instances_events() |> filter_local_or_from_followed_instances_events()
|> filter_public_visibility() |> filter_public_visibility()
|> event_order(Map.get(args, :sort_by, :match_desc)) |> event_order(Map.get(args, :sort_by, :match_desc), search_string)
|> Page.build_page(page, limit, :begins_on) |> Page.build_page(page, limit, :begins_on)
end end
@ -1321,18 +1322,16 @@ defmodule Mobilizon.Events do
end end
end end
defp events_for_search_query(""), do: Event
defp events_for_search_query(search_string) do defp events_for_search_query(search_string) do
Event join(Event, :inner, [e], id_and_rank in matching_event_ids_and_ranks(search_string),
|> distinct(:id)
|> join(:inner, [e], id_and_rank in matching_event_ids_and_ranks(search_string),
on: id_and_rank.id == e.id on: id_and_rank.id == e.id
) )
end end
@spec events_for_begins_on(Ecto.Queryable.t(), map()) :: Ecto.Query.t() @spec events_for_begins_on(Ecto.Queryable.t(), DateTime.t() | nil) :: Ecto.Query.t()
defp events_for_begins_on(query, args) do defp events_for_begins_on(query, begins_on) do
begins_on = Map.get(args, :begins_on, DateTime.utc_now())
if is_nil(begins_on) do if is_nil(begins_on) do
query query
else else
@ -1345,10 +1344,8 @@ defmodule Mobilizon.Events do
end end
end end
@spec events_for_ends_on(Ecto.Queryable.t(), map()) :: Ecto.Query.t() @spec events_for_ends_on(Ecto.Queryable.t(), DateTime.t() | nil) :: Ecto.Query.t()
defp events_for_ends_on(query, args) do defp events_for_ends_on(query, ends_on) do
ends_on = Map.get(args, :ends_on)
if is_nil(ends_on) do if is_nil(ends_on) do
query query
else else
@ -1759,7 +1756,6 @@ defmodule Mobilizon.Events do
|> distinct([e], e.id) |> distinct([e], e.id)
|> join(:left, [e], et in "events_tags", on: e.id == et.event_id) |> join(:left, [e], et in "events_tags", on: e.id == et.event_id)
|> join(:left, [e], a in Address, on: e.physical_address_id == a.id) |> join(:left, [e], a in Address, on: e.physical_address_id == a.id)
|> events_for_begins_on(%{})
|> filter_draft() |> filter_draft()
|> filter_local_or_from_followed_instances_events() |> filter_local_or_from_followed_instances_events()
|> filter_public_visibility() |> filter_public_visibility()
@ -1948,17 +1944,49 @@ defmodule Mobilizon.Events do
defp event_order_by(query, _order_by, _direction), do: event_order_by(query, :begins_on, :asc) defp event_order_by(query, _order_by, _direction), do: event_order_by(query, :begins_on, :asc)
defp event_order(query, :match_desc), defp event_order(query, :match_desc, "") do
do: order_by(query, [e, f], desc: f.rank, asc: e.begins_on) query
|> distinct([e], desc: e.begins_on, asc: e.id)
|> order_by([e], desc: e.begins_on)
end
defp event_order(query, :start_time_desc), do: order_by(query, [e], asc: e.begins_on) defp event_order(query, :match_desc, _) do
defp event_order(query, :created_at_desc), do: order_by(query, [e], desc: e.publish_at) query
defp event_order(query, :created_at_asc), do: order_by(query, [e], asc: e.publish_at) |> distinct([e, f], desc: f.rank, asc: e.begins_on, asc: e.id)
|> order_by([e, f], desc: f.rank, asc: e.begins_on)
end
defp event_order(query, :participant_count_desc), defp event_order(query, :start_time_desc, _) do
do: order_by(query, [e], fragment("participant_stats->>'participant' DESC")) query
|> distinct([e], desc: e.begins_on, asc: e.id)
|> order_by([e], desc: e.begins_on)
end
defp event_order(query, _), do: query defp event_order(query, :start_time_asc, _) do
query
|> distinct([e], asc: e.begins_on, asc: e.id)
|> order_by([e], asc: e.begins_on)
end
defp event_order(query, :created_at_desc, _) do
query
|> distinct([e], desc: e.publish_at, asc: e.id)
|> order_by([e], desc: e.publish_at)
end
defp event_order(query, :created_at_asc, _) do
query
|> distinct([e], asc: e.publish_at, asc: e.id)
|> order_by([e], asc: e.publish_at)
end
defp event_order(query, :participant_count_desc, _) do
query
|> distinct(:id)
|> order_by([e], fragment("participant_stats->>'participant' DESC"))
end
defp event_order(query, _, _), do: query
defp participation_filter_begins_on(query, nil, nil), defp participation_filter_begins_on(query, nil, nil),
do: participation_order_begins_on_desc(query) do: participation_order_begins_on_desc(query)

View File

@ -17,6 +17,7 @@ defmodule Mobilizon.Service.GlobalSearch.SearchMobilizon do
@sort_by_options %{ @sort_by_options %{
match_desc: "-match", match_desc: "-match",
start_time_asc: "startTime",
start_time_desc: "-startTime", start_time_desc: "-startTime",
created_at_desc: "-createdAt", created_at_desc: "-createdAt",
created_at_asc: "createdAt", created_at_asc: "createdAt",