Merge branch 'main' into '1481-trying-to-contact-the-organizer-contact-crashes-the-app'

# Conflicts:
#   package.json
This commit is contained in:
setop 2024-07-06 16:21:43 +00:00
commit 320c40e511
18 changed files with 537 additions and 224 deletions

View file

@ -7,6 +7,7 @@ defmodule Mobilizon.Federation.ActivityPub.Publisher do
alias Mobilizon.Config
alias Mobilizon.Federation.ActivityPub.{Activity, Federator, Relay, Transmogrifier, Visibility}
alias Mobilizon.Federation.HTTPSignatures.Signature
alias Mobilizon.Service.HTTP.ActivityPub, as: ActivityPubClient
require Logger
import Mobilizon.Federation.ActivityPub.Utils,
@ -95,16 +96,16 @@ defmodule Mobilizon.Federation.ActivityPub.Publisher do
date: date
})
Tesla.post(
inbox,
json,
headers: [
{"Content-Type", "application/activity+json"},
{"signature", signature},
{"digest", digest},
{"date", date}
]
)
headers = [
{"Content-Type", "application/activity+json"},
{"signature", signature},
{"digest", digest},
{"date", date}
]
client = ActivityPubClient.client(headers: headers)
ActivityPubClient.post(client, inbox, json)
end
@spec convert_followers_in_recipients(list(String.t())) :: {list(String.t()), list(String.t())}

View file

@ -9,13 +9,9 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Media do
alias Mobilizon.Federation.ActivityStream
alias Mobilizon.Medias
alias Mobilizon.Medias.Media, as: MediaModel
alias Mobilizon.Service.HTTP.RemoteMediaDownloaderClient
alias Mobilizon.Web.Upload
@http_options [
ssl: [{:versions, [:"tlsv1.2"]}]
]
@doc """
Convert a media struct to an ActivityStream representation.
"""
@ -65,7 +61,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Media do
defp upload_media(media_url, ""), do: upload_media(media_url, "unknown")
defp upload_media(media_url, name) do
case Tesla.get(media_url, opts: @http_options) do
case RemoteMediaDownloaderClient.get(media_url) do
{:ok, %{body: body}} ->
case Upload.store(%{body: body, name: name}) do
{:ok, %{url: _url} = uploaded} ->

8
package-lock.json generated
View file

@ -16,7 +16,7 @@
"@fullcalendar/daygrid": "^6.1.10",
"@fullcalendar/interaction": "^6.1.10",
"@fullcalendar/vue3": "^6.1.10",
"@oruga-ui/oruga-next": "^0.8.2",
"@oruga-ui/oruga-next": "^0.8.10",
"@oruga-ui/theme-oruga": "^0.2.0",
"@sentry/tracing": "^7.1",
"@sentry/vue": "^7.1",
@ -3138,9 +3138,9 @@
"dev": true
},
"node_modules/@oruga-ui/oruga-next": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/@oruga-ui/oruga-next/-/oruga-next-0.8.5.tgz",
"integrity": "sha512-HnODRTrurmke7O5rRNdrbqYuNIdMrnBJ+P3jh6J7/Lk/zgMnpsObSGj/6JfQRvdf5Wq++Ch5yVUys0V4Lm08JQ==",
"version": "0.8.10",
"resolved": "https://registry.npmjs.org/@oruga-ui/oruga-next/-/oruga-next-0.8.10.tgz",
"integrity": "sha512-ETPSoGZu1parbj8C3V2ZojQnN4ptQMiJEwS9Hx44NcaDzu4q/FDsYkKYiz6G9kx8cDceXXxvydfOUpZePVVdzw==",
"peerDependencies": {
"vue": "^3.0.0"
}

View file

@ -34,7 +34,7 @@
"@apollo/client": "^3.3.16",
"@framasoft/socket": "^1.0.0",
"@framasoft/socket-apollo-link": "^1.0.0",
"@oruga-ui/oruga-next": "0.8.12",
"@oruga-ui/oruga-next": "^0.8.10",
"@oruga-ui/theme-oruga": "^0.2.0",
"@fullcalendar/core": "^6.1.10",
"@fullcalendar/daygrid": "^6.1.10",

View file

@ -2316,24 +2316,3 @@ msgstr ""
"información na páxina <a href=\"/about/instance\">\"Acerca de esta "
"instancia\"</a>."
#: lib/web/templates/api/terms.html.heex:55
msgctxt "terms"
msgid ""
"When we say “we”, “our”, or “us” in this document, we are referring to the "
"owners, operators and administrators of this Mobilizon instance. The "
"Mobilizon software is provided by the team of Mobilizon contributors, "
"supported by <a href=\"https://framasoft.org\">Framasoft</a>, a French not-"
"for-profit organization advocating for Free/Libre Software. Unless "
"explicitly stated, this Mobilizon instance is an independent service using "
"Mobilizon's source code. You may find more information about this instance "
"on the <a href=\"/about/instance\">\"About this instance\"</a> page."
msgstr ""
"Cando dicimos \"nós\", \"noso\" ou \"a nós\" neste documento, referímonos ás "
"persoas donas, operadoras e administradoras desta instancia Mobilizon. O "
"software Mobilizon está proporcionado polo equipo de colaboradoras de "
"Mobilizon, coa axuda de <a href=\"https://framasoft.org\">Framasoft</a>, "
"unha organización francesa sen ánimo de lucro que fomenta o Software Libre. "
"A non ser que se indique o contrario, esta instancia Mobilizon é un servizo "
"independente que está a usar o código fonte de Mobilizon. Podes atopar máis "
"información sobre esta instancia na páxina <a href=\"/about/"
"instance\">Acerca desta instancia</a>."

View file

@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-11-25 07:56+0000\n"
"PO-Revision-Date: 2024-03-08 17:59+0000\n"
"PO-Revision-Date: 2024-04-25 13:45+0000\n"
"Last-Translator: Milo Ivir <mail@milotype.de>\n"
"Language-Team: Croatian <https://weblate.framasoft.org/projects/mobilizon/"
"backend/hr/>\n"
@ -11,9 +11,9 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 5.4.2\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: Weblate 5.5\n"
#: lib/web/templates/email/password_reset.html.heex:66
#, elixir-autogen, elixir-format
@ -2192,7 +2192,13 @@ msgid "Well done!"
msgstr ""
#: lib/web/templates/api/terms.html.heex:55
#, elixir-autogen, elixir-format, fuzzy
#, elixir-autogen, elixir-format
msgctxt "terms"
msgid "When we say “we”, “our”, or “us” in this document, we are referring to the owners, operators and administrators of this Mobilizon instance. The Mobilizon software is provided by the team of Mobilizon contributors. Unless explicitly stated, this Mobilizon instance is an independent service using Mobilizon's source code. You may find more information about this instance on the <a href=\"/about/instance\">\"About this instance\"</a> page."
msgstr ""
"Kada u ovom dokumentu kažemo „mi”, „naš” ili „nas”, mislimo na vlasnike, "
"operatere i administratore ove Mobilizon instance. Softver Mobilizon "
"osigurava tim dopinositelja Mobilizona. Ukoliko nije izričito navedeno, ova "
"je Mobilizon instanca neovisna usluga koja koristi Mobilizonov izvorni kod. "
"Više informacija o ovoj instanci se mogu pronaći na stranici <a href=\"/"
"about/instance\">„O ovoj instanci”</a>."

View file

@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-11-25 07:56+0000\n"
"PO-Revision-Date: 2024-03-08 15:25+0000\n"
"PO-Revision-Date: 2024-04-25 13:45+0000\n"
"Last-Translator: Milo Ivir <mail@milotype.de>\n"
"Language-Team: Croatian <https://weblate.framasoft.org/projects/mobilizon/"
"backend-errors/hr/>\n"
@ -11,9 +11,9 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 5.4.2\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: Weblate 5.5\n"
## This file is a PO Template file.
##
@ -837,52 +837,52 @@ msgstr "Moraš biti prijavljen/a"
#: lib/graphql/resolvers/member.ex:118
#, elixir-autogen, elixir-format
msgid "You can't accept this invitation with this profile."
msgstr ""
msgstr "Ovu pozivnicu ne možeš prihvatiti s ovim profilom."
#: lib/graphql/resolvers/member.ex:139
#, elixir-autogen, elixir-format
msgid "You can't reject this invitation with this profile."
msgstr ""
msgstr "Ovu pozivnicu ne možeš odbiti s ovim profilom."
#: lib/graphql/resolvers/media.ex:71
#, elixir-autogen, elixir-format
msgid "File doesn't have an allowed MIME type."
msgstr ""
msgstr "Datoteka nema dozvoljenu MIME vrstu."
#: lib/graphql/resolvers/group.ex:244
#, elixir-autogen, elixir-format
msgid "Profile is not administrator for the group"
msgstr ""
msgstr "Profil nije administrator za grupu"
#: lib/graphql/resolvers/event.ex:385
#, elixir-autogen, elixir-format
msgid "You can't edit this event."
msgstr ""
msgstr "Ovaj događaj ne možeš urediti."
#: lib/graphql/resolvers/event.ex:388
#, elixir-autogen, elixir-format
msgid "You can't attribute this event to this profile."
msgstr ""
msgstr "Ovaj događaj ne možeš pripisati ovom profilu."
#: lib/graphql/resolvers/member.ex:142
#, elixir-autogen, elixir-format
msgid "This invitation doesn't exist."
msgstr ""
msgstr "Ova pozivnica ne postoji."
#: lib/graphql/resolvers/member.ex:217
#, elixir-autogen, elixir-format
msgid "This member already has been rejected."
msgstr ""
msgstr "Ovaj je član već odbijen."
#: lib/graphql/resolvers/member.ex:241
#, elixir-autogen, elixir-format
msgid "You don't have the right to remove this member."
msgstr ""
msgstr "Nemaš prava za uklanjanje ovog člana."
#: lib/mobilizon/actors/actor.ex:385
#, elixir-autogen, elixir-format
msgid "This username is already taken."
msgstr ""
msgstr "Ovo je korisničko ime već zauzeto."
#: lib/graphql/resolvers/discussion.ex:81
#, elixir-autogen, elixir-format

View file

@ -8,45 +8,52 @@
## to merge POT files into PO files.
msgid ""
msgstr ""
"PO-Revision-Date: 2024-05-14 22:26+0000\n"
"Last-Translator: Coen Holten <coenholten@proton.me>\n"
"Language-Team: Dutch <https://weblate.framasoft.org/projects/mobilizon/"
"backend-errors/nl/>\n"
"Language: nl\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Weblate 5.5.5\n"
#: lib/mobilizon/discussions/discussion.ex:68
#, elixir-autogen
msgid "can't be blank"
msgstr ""
msgstr "kan niet leeg zijn"
msgid "has already been taken"
msgstr ""
msgstr "is al bezet"
msgid "is invalid"
msgstr ""
msgstr "is ongeldig"
msgid "must be accepted"
msgstr ""
msgstr "moet worden geaccepteerd"
msgid "has invalid format"
msgstr ""
msgstr "heeft ongeldig formaat"
msgid "has an invalid entry"
msgstr ""
msgstr "heeft een ongeldige invoer"
msgid "is reserved"
msgstr ""
msgstr "is gereserveerd"
msgid "does not match confirmation"
msgstr ""
msgstr "komt niet overeen met bevestiging"
msgid "is still associated with this entry"
msgstr ""
msgstr "is nog steeds gekoppeld aan deze invoer"
msgid "are still associated with this entry"
msgstr ""
msgstr "zijn nog steeds verbonden met deze invoer"
msgid "should be %{count} character(s)"
msgid_plural "should be %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "moet %{count} karakter zijn"
msgstr[1] "moet %{count} karakter(s) zijn"
msgid "should have %{count} item(s)"
msgid_plural "should have %{count} item(s)"

View file

@ -22,7 +22,12 @@
</div>
</template>
<script lang="ts" setup>
import { computed } from "vue";
import { computed, watch } from "vue";
import { useI18n } from "vue-i18n";
const { locale } = useI18n({ useScope: "global" });
const localeConverted = locale.replace("_", "-");
const props = withDefaults(
defineProps<{
@ -35,15 +40,15 @@ const props = withDefaults(
const dateObj = computed<Date>(() => new Date(props.date));
const month = computed<string>(() =>
dateObj.value.toLocaleString(undefined, { month: "short" })
dateObj.value.toLocaleString(localeConverted, { month: "short" })
);
const day = computed<string>(() =>
dateObj.value.toLocaleString(undefined, { day: "numeric" })
dateObj.value.toLocaleString(localeConverted, { day: "numeric" })
);
const weekday = computed<string>(() =>
dateObj.value.toLocaleString(undefined, { weekday: "short" })
dateObj.value.toLocaleString(localeConverted, { weekday: "short" })
);
const smallStyle = computed<string>(() => (props.small ? "1.2" : "2"));

View file

@ -32,7 +32,7 @@
<div class="flex-1" v-else>
{{ `@${selectedActor.preferredUsername}` }}
</div>
<o-button type="text" @click="isComponentModalActive = true">
<o-button @click="isComponentModalActive = true">
{{ $t("Change") }}
</o-button>
</div>

View file

@ -13,8 +13,14 @@
</template>
<script lang="ts" setup>
import { computed } from "vue";
import { useI18n } from "vue-i18n";
import Clock from "vue-material-design-icons/ClockTimeTenOutline.vue";
const { locale } = useI18n({ useScope: "global" });
const localeConverted = locale.replace("_", "-");
const props = withDefaults(
defineProps<{
date: string;
@ -26,7 +32,7 @@ const props = withDefaults(
const dateObj = computed<Date>(() => new Date(props.date));
const time = computed<string>(() =>
dateObj.value.toLocaleTimeString(undefined, {
dateObj.value.toLocaleTimeString(localeConverted, {
hour: "2-digit",
minute: "2-digit",
})

View file

@ -15,7 +15,8 @@
</template>
<o-taginput
:modelValue="tagsStrings"
@update:modelValue="updateTags"
@remove="remove"
@add="add"
:data="filteredTags"
:allow-autocomplete="true"
:allow-new="true"
@ -69,6 +70,8 @@ const id = computed((): string => {
const { load: fetchTags } = useFetchTags();
initTagsStringsValue();
const getFilteredTags = async (newText: string): Promise<void> => {
text.value = newText;
const res = await fetchTags(
@ -91,11 +94,16 @@ const filteredTags = computed((): ITag[] => {
);
});
watch(props.modelValue, (newValue, oldValue) => {
if (newValue != oldValue) {
tagsStrings.value = propsValue.value.map((tag: ITag) => tag.title);
}
});
// TODO It seems that '@update:modelValue="updateTags"' does not works anymore...
// so temporarily call the function updateTags() at remove and add tag event
// https://github.com/oruga-ui/oruga/issues/967
function remove() {
updateTags(tagsStrings.value);
}
function add() {
updateTags(tagsStrings.value);
}
const updateTags = (newTagsStrings: string[]) => {
const tagEntities = newTagsStrings.map((tag: string | ITag) => {
@ -106,4 +114,34 @@ const updateTags = (newTagsStrings: string[]) => {
});
emit("update:modelValue", tagEntities);
};
function isArraysEquals(array1: string[], array2: string[]) {
if (array1.length !== array2.length) {
return false;
}
for (let i = 0; i < array1.length; i++) {
if (array1[i] !== array2[i]) {
return false;
}
}
return true;
}
function initTagsStringsValue() {
// This is useful when tag data is already cached from the API during navigation inside the app
tagsStrings.value = propsValue.value.map((tag: ITag) => tag.title);
// This watch() function is useful when tag data loads directly from the API upon page load
watch(propsValue, () => {
const newTagsStrings = propsValue.value.map((tag: ITag) => tag.title);
// Changing tagsStrings.value triggers updateTags(), updateTags() triggers this watch() function again.
// To stop the loop, edit tagsStrings.value only if it has changed !
if (!isArraysEquals(tagsStrings.value, newTagsStrings)) {
tagsStrings.value = newTagsStrings;
}
});
}
</script>

View file

@ -33,16 +33,16 @@
"A resource has been created or updated": "Creouse ou actualizouse un recurso",
"A short tagline for your instance homepage. Defaults to \"Gather ⋅ Organize ⋅ Mobilize\"": "Un pequeno subtítulo para o inicio da instancia. Por omisión \"Xuntar ⋅ Organizar ⋅ Mobilizar\"",
"A twitter account handle to follow for event updates": "Alcume da conta de twitter para seguir actualizacións do evento",
"A user-friendly, emancipatory and ethical tool for gathering, organising, and mobilising.": "Unha ferramenta amigable, emancipatoria e ética para xuntar, organizr e mobilizar.",
"A user-friendly, emancipatory and ethical tool for gathering, organising, and mobilising.": "Unha ferramenta amigábel, emancipadora e ética para xuntar, organizar e mobilizar.",
"A validation email was sent to {email}": "Enviouse un correo de validación a {email}",
"API": "API",
"Abandon editing": "Saír da edición",
"About": "Acerca de",
"About Mobilizon": "Acerca de Mobilizon",
"About": "Sobre",
"About Mobilizon": "Sobre Mobilizon",
"About anonymous participation": "Acerca da participación anónima",
"About instance": "Acerca da instancia",
"About this event": "Acerca deste evento",
"About this instance": "Acerca desta instancia",
"About this event": "Sobre este evento",
"About this instance": "Sobre esta instancia",
"About {instance}": "Acerca de {instance}",
"Accept": "Aceptar",
"Accept follow": "Aceptar seguimento",
@ -873,7 +873,7 @@
"Please add as many details as possible to help identify the problem.": "Por favor engade tódolos detalles posibles para axudarnos a identificar o problema.",
"Please check your spam folder if you didn't receive the email.": "Comproba o cartafol de spam se non recibiches o correo.",
"Please contact this instance's Mobilizon admin if you think this is a mistake.": "Contacta coa administración da instancia Mobilizon se cres que é un erro.",
"Please do not use it in any real way.": "Non o utilices para eventos reais.",
"Please do not use it in any real way.": "Non o utilice para eventos reais.",
"Please enter your password to confirm this action.": "Escribe o teu contrasinal para confirmar a acción.",
"Please make sure the address is correct and that the page hasn't been moved.": "Comproba ben o enderezo e que a páxina non foi movida a outro lugar.",
"Please read the {fullRules} published by {instance}'s administrators.": "Por favor le as {fullRules} publicadas pola administración de {instance}.",

View file

@ -57,6 +57,7 @@
"Access group memberships": "Pristupi članstvima grupa",
"Access group suggested events": "Pristupi predloženim grupnim događajima",
"Access group todo-lists": "Pristupi popisu grupnih zadataka",
"Access group todo-lists": "Pristupi popisu zadataka grupe",
"Access organized events": "Pristupi organiziranim događajima",
"Access participations": "Pristupi sudjelovanjima",
"Access your group's resources": "Pristupi resursima tvoje grupe",

File diff suppressed because it is too large Load diff

View file

@ -271,6 +271,7 @@
>
<template #default>
<event-map
v-if="showMap"
:routingType="routingType ?? RoutingType.OPENSTREETMAP"
:address="event.physicalAddress"
@close="showMap = false"

View file

@ -23,10 +23,27 @@
<div
class="rounded p-3 flex-auto md:flex-none bg-zinc-300 dark:bg-zinc-700"
>
<o-field>
<o-switch v-model="showUpcoming">{{
showUpcoming ? t("Upcoming events") : t("Past events")
}}</o-switch>
<o-field
class="date-filter"
expanded
:label="
showUpcoming
? t('Showing events starting on')
: t('Showing events before')
"
labelFor="events-start-datepicker"
>
<o-datepicker
v-model="datePick"
:first-day-of-week="firstDayOfWeek"
id="events-start-datepicker"
/>
<o-button
@click="datePick = new Date()"
class="reset-area !h-auto"
icon-left="close"
:title="t('Clear date filter field')"
/>
</o-field>
<o-field v-if="showUpcoming">
<o-checkbox v-model="showDrafts">{{ t("Drafts") }}</o-checkbox>
@ -50,28 +67,6 @@
)
}}
</p>
<o-field
class="date-filter"
expanded
:label="
showUpcoming
? t('Showing events starting on')
: t('Showing events before')
"
labelFor="events-start-datepicker"
>
<o-datepicker
v-model="dateFilter"
:first-day-of-week="firstDayOfWeek"
id="events-start-datepicker"
/>
<o-button
@click="dateFilter = new Date()"
class="reset-area !h-auto"
icon-left="close"
:title="t('Clear date filter field')"
/>
</o-field>
</div>
<div class="flex-1 min-w-[300px]">
<section
@ -250,27 +245,43 @@ const futurePage = ref(1);
const pastPage = ref(1);
const limit = ref(10);
function startOfDay(d: Date): string {
const pad = (n: int): string => {
return (n > 9 ? "" : "0") + n.toString();
};
return (
d.getFullYear() +
"-" +
pad(d.getMonth() + 1) +
"-" +
pad(d.getDate()) +
"T00:00:00Z"
);
}
const showUpcoming = useRouteQuery("showUpcoming", true, booleanTransformer);
const showDrafts = useRouteQuery("showDrafts", true, booleanTransformer);
const showAttending = useRouteQuery("showAttending", true, booleanTransformer);
const showMyGroups = useRouteQuery("showMyGroups", false, booleanTransformer);
const dateFilter = useRouteQuery("dateFilter", new Date(), {
const dateFilter = useRouteQuery("dateFilter", startOfDay(new Date()), {
fromQuery(query) {
if (query && /(\d{4}-\d{2}-\d{2})/.test(query)) {
return new Date(`${query}T00:00:00Z`);
return `${query}T00:00:00Z`;
}
return new Date();
return startOfDay(new Date());
},
toQuery(value: Date) {
const pad = (number: number) => {
if (number < 10) {
return "0" + number;
}
return number;
};
return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(
value.getDate()
)}`;
toQuery(value: string) {
return value.slice(0, 10);
},
});
// bridge between datepicker expecting a Date object and dateFilter being a string
const datePick = computed({
get: () => {
return new Date(dateFilter.value);
},
set: (d: Date) => {
dateFilter.value = startOfDay(d);
},
});
@ -323,10 +334,7 @@ const pastParticipations = computed(
}
);
const monthlyEvents = (
elements: Eventable[],
revertSort = false
): Map<string, Eventable[]> => {
const monthlyEvents = (elements: Eventable[]): Map<string, Eventable[]> => {
const res = elements.filter((element: Eventable) => {
if ("role" in element) {
return (
@ -336,19 +344,12 @@ const monthlyEvents = (
}
return element.beginsOn != null;
});
if (revertSort) {
res.sort((a: Eventable, b: Eventable) => {
const aTime = "role" in a ? a.event.beginsOn : a.beginsOn;
const bTime = "role" in b ? b.event.beginsOn : b.beginsOn;
return new Date(bTime).getTime() - new Date(aTime).getTime();
});
} else {
res.sort((a: Eventable, b: Eventable) => {
const aTime = "role" in a ? a.event.beginsOn : a.beginsOn;
const bTime = "role" in b ? b.event.beginsOn : b.beginsOn;
return new Date(aTime).getTime() - new Date(bTime).getTime();
});
}
// sort by start date, ascending
res.sort((a: Eventable, b: Eventable) => {
const aTime = "role" in a ? a.event.beginsOn : a.beginsOn;
const bTime = "role" in b ? b.event.beginsOn : b.beginsOn;
return new Date(aTime).getTime() - new Date(bTime).getTime();
});
return res.reduce((acc: Map<string, Eventable[]>, element: Eventable) => {
const month = new Date(
"role" in element ? element.event.beginsOn : element.beginsOn

View file

@ -195,6 +195,10 @@ onMounted(async () => {
pictureFile.value = await buildFileFromIMedia(post.value?.picture);
});
// This is useful when post data is already cached from the API during navigation inside the app
editablePost.value = { ...editablePost.value, ...post.value };
// This watch() function is useful when post data loads directly from the API upon page load
watch(post, async (newPost: IPost | undefined, oldPost: IPost | undefined) => {
if (oldPost?.picture !== newPost?.picture) {
pictureFile.value = await buildFileFromIMedia(post.value?.picture);