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.Config
alias Mobilizon.Federation.ActivityPub.{Activity, Federator, Relay, Transmogrifier, Visibility} alias Mobilizon.Federation.ActivityPub.{Activity, Federator, Relay, Transmogrifier, Visibility}
alias Mobilizon.Federation.HTTPSignatures.Signature alias Mobilizon.Federation.HTTPSignatures.Signature
alias Mobilizon.Service.HTTP.ActivityPub, as: ActivityPubClient
require Logger require Logger
import Mobilizon.Federation.ActivityPub.Utils, import Mobilizon.Federation.ActivityPub.Utils,
@ -95,16 +96,16 @@ defmodule Mobilizon.Federation.ActivityPub.Publisher do
date: date date: date
}) })
Tesla.post( headers = [
inbox, {"Content-Type", "application/activity+json"},
json, {"signature", signature},
headers: [ {"digest", digest},
{"Content-Type", "application/activity+json"}, {"date", date}
{"signature", signature}, ]
{"digest", digest},
{"date", date} client = ActivityPubClient.client(headers: headers)
]
) ActivityPubClient.post(client, inbox, json)
end end
@spec convert_followers_in_recipients(list(String.t())) :: {list(String.t()), list(String.t())} @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.Federation.ActivityStream
alias Mobilizon.Medias alias Mobilizon.Medias
alias Mobilizon.Medias.Media, as: MediaModel alias Mobilizon.Medias.Media, as: MediaModel
alias Mobilizon.Service.HTTP.RemoteMediaDownloaderClient
alias Mobilizon.Web.Upload alias Mobilizon.Web.Upload
@http_options [
ssl: [{:versions, [:"tlsv1.2"]}]
]
@doc """ @doc """
Convert a media struct to an ActivityStream representation. 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, ""), do: upload_media(media_url, "unknown")
defp upload_media(media_url, name) do 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}} -> {:ok, %{body: body}} ->
case Upload.store(%{body: body, name: name}) do case Upload.store(%{body: body, name: name}) do
{:ok, %{url: _url} = uploaded} -> {:ok, %{url: _url} = uploaded} ->

8
package-lock.json generated
View file

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

View file

@ -34,7 +34,7 @@
"@apollo/client": "^3.3.16", "@apollo/client": "^3.3.16",
"@framasoft/socket": "^1.0.0", "@framasoft/socket": "^1.0.0",
"@framasoft/socket-apollo-link": "^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", "@oruga-ui/theme-oruga": "^0.2.0",
"@fullcalendar/core": "^6.1.10", "@fullcalendar/core": "^6.1.10",
"@fullcalendar/daygrid": "^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 " "información na páxina <a href=\"/about/instance\">\"Acerca de esta "
"instancia\"</a>." "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" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-11-25 07:56+0000\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" "Last-Translator: Milo Ivir <mail@milotype.de>\n"
"Language-Team: Croatian <https://weblate.framasoft.org/projects/mobilizon/" "Language-Team: Croatian <https://weblate.framasoft.org/projects/mobilizon/"
"backend/hr/>\n" "backend/hr/>\n"
@ -11,9 +11,9 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "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" "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: Weblate 5.4.2\n" "X-Generator: Weblate 5.5\n"
#: lib/web/templates/email/password_reset.html.heex:66 #: lib/web/templates/email/password_reset.html.heex:66
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@ -2192,7 +2192,13 @@ msgid "Well done!"
msgstr "" msgstr ""
#: lib/web/templates/api/terms.html.heex:55 #: lib/web/templates/api/terms.html.heex:55
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format
msgctxt "terms" 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." 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 "" 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" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-11-25 07:56+0000\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" "Last-Translator: Milo Ivir <mail@milotype.de>\n"
"Language-Team: Croatian <https://weblate.framasoft.org/projects/mobilizon/" "Language-Team: Croatian <https://weblate.framasoft.org/projects/mobilizon/"
"backend-errors/hr/>\n" "backend-errors/hr/>\n"
@ -11,9 +11,9 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "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" "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: Weblate 5.4.2\n" "X-Generator: Weblate 5.5\n"
## This file is a PO Template file. ## This file is a PO Template file.
## ##
@ -837,52 +837,52 @@ msgstr "Moraš biti prijavljen/a"
#: lib/graphql/resolvers/member.ex:118 #: lib/graphql/resolvers/member.ex:118
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "You can't accept this invitation with this profile." 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 #: lib/graphql/resolvers/member.ex:139
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "You can't reject this invitation with this profile." 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 #: lib/graphql/resolvers/media.ex:71
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "File doesn't have an allowed MIME type." msgid "File doesn't have an allowed MIME type."
msgstr "" msgstr "Datoteka nema dozvoljenu MIME vrstu."
#: lib/graphql/resolvers/group.ex:244 #: lib/graphql/resolvers/group.ex:244
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Profile is not administrator for the group" msgid "Profile is not administrator for the group"
msgstr "" msgstr "Profil nije administrator za grupu"
#: lib/graphql/resolvers/event.ex:385 #: lib/graphql/resolvers/event.ex:385
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "You can't edit this event." msgid "You can't edit this event."
msgstr "" msgstr "Ovaj događaj ne možeš urediti."
#: lib/graphql/resolvers/event.ex:388 #: lib/graphql/resolvers/event.ex:388
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "You can't attribute this event to this profile." 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 #: lib/graphql/resolvers/member.ex:142
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "This invitation doesn't exist." msgid "This invitation doesn't exist."
msgstr "" msgstr "Ova pozivnica ne postoji."
#: lib/graphql/resolvers/member.ex:217 #: lib/graphql/resolvers/member.ex:217
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "This member already has been rejected." msgid "This member already has been rejected."
msgstr "" msgstr "Ovaj je član već odbijen."
#: lib/graphql/resolvers/member.ex:241 #: lib/graphql/resolvers/member.ex:241
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "You don't have the right to remove this member." 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 #: lib/mobilizon/actors/actor.ex:385
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "This username is already taken." msgid "This username is already taken."
msgstr "" msgstr "Ovo je korisničko ime već zauzeto."
#: lib/graphql/resolvers/discussion.ex:81 #: lib/graphql/resolvers/discussion.ex:81
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format

View file

@ -8,45 +8,52 @@
## to merge POT files into PO files. ## to merge POT files into PO files.
msgid "" msgid ""
msgstr "" 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" "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 #: lib/mobilizon/discussions/discussion.ex:68
#, elixir-autogen #, elixir-autogen
msgid "can't be blank" msgid "can't be blank"
msgstr "" msgstr "kan niet leeg zijn"
msgid "has already been taken" msgid "has already been taken"
msgstr "" msgstr "is al bezet"
msgid "is invalid" msgid "is invalid"
msgstr "" msgstr "is ongeldig"
msgid "must be accepted" msgid "must be accepted"
msgstr "" msgstr "moet worden geaccepteerd"
msgid "has invalid format" msgid "has invalid format"
msgstr "" msgstr "heeft ongeldig formaat"
msgid "has an invalid entry" msgid "has an invalid entry"
msgstr "" msgstr "heeft een ongeldige invoer"
msgid "is reserved" msgid "is reserved"
msgstr "" msgstr "is gereserveerd"
msgid "does not match confirmation" msgid "does not match confirmation"
msgstr "" msgstr "komt niet overeen met bevestiging"
msgid "is still associated with this entry" msgid "is still associated with this entry"
msgstr "" msgstr "is nog steeds gekoppeld aan deze invoer"
msgid "are still associated with this entry" msgid "are still associated with this entry"
msgstr "" msgstr "zijn nog steeds verbonden met deze invoer"
msgid "should be %{count} character(s)" msgid "should be %{count} character(s)"
msgid_plural "should be %{count} character(s)" msgid_plural "should be %{count} character(s)"
msgstr[0] "" msgstr[0] "moet %{count} karakter zijn"
msgstr[1] "" msgstr[1] "moet %{count} karakter(s) zijn"
msgid "should have %{count} item(s)" msgid "should have %{count} item(s)"
msgid_plural "should have %{count} item(s)" msgid_plural "should have %{count} item(s)"

View file

@ -22,7 +22,12 @@
</div> </div>
</template> </template>
<script lang="ts" setup> <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( const props = withDefaults(
defineProps<{ defineProps<{
@ -35,15 +40,15 @@ const props = withDefaults(
const dateObj = computed<Date>(() => new Date(props.date)); const dateObj = computed<Date>(() => new Date(props.date));
const month = computed<string>(() => const month = computed<string>(() =>
dateObj.value.toLocaleString(undefined, { month: "short" }) dateObj.value.toLocaleString(localeConverted, { month: "short" })
); );
const day = computed<string>(() => const day = computed<string>(() =>
dateObj.value.toLocaleString(undefined, { day: "numeric" }) dateObj.value.toLocaleString(localeConverted, { day: "numeric" })
); );
const weekday = computed<string>(() => 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")); const smallStyle = computed<string>(() => (props.small ? "1.2" : "2"));

View file

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

View file

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

View file

@ -15,7 +15,8 @@
</template> </template>
<o-taginput <o-taginput
:modelValue="tagsStrings" :modelValue="tagsStrings"
@update:modelValue="updateTags" @remove="remove"
@add="add"
:data="filteredTags" :data="filteredTags"
:allow-autocomplete="true" :allow-autocomplete="true"
:allow-new="true" :allow-new="true"
@ -69,6 +70,8 @@ const id = computed((): string => {
const { load: fetchTags } = useFetchTags(); const { load: fetchTags } = useFetchTags();
initTagsStringsValue();
const getFilteredTags = async (newText: string): Promise<void> => { const getFilteredTags = async (newText: string): Promise<void> => {
text.value = newText; text.value = newText;
const res = await fetchTags( const res = await fetchTags(
@ -91,11 +94,16 @@ const filteredTags = computed((): ITag[] => {
); );
}); });
watch(props.modelValue, (newValue, oldValue) => { // TODO It seems that '@update:modelValue="updateTags"' does not works anymore...
if (newValue != oldValue) { // so temporarily call the function updateTags() at remove and add tag event
tagsStrings.value = propsValue.value.map((tag: ITag) => tag.title); // https://github.com/oruga-ui/oruga/issues/967
} function remove() {
}); updateTags(tagsStrings.value);
}
function add() {
updateTags(tagsStrings.value);
}
const updateTags = (newTagsStrings: string[]) => { const updateTags = (newTagsStrings: string[]) => {
const tagEntities = newTagsStrings.map((tag: string | ITag) => { const tagEntities = newTagsStrings.map((tag: string | ITag) => {
@ -106,4 +114,34 @@ const updateTags = (newTagsStrings: string[]) => {
}); });
emit("update:modelValue", tagEntities); 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> </script>

View file

@ -33,16 +33,16 @@
"A resource has been created or updated": "Creouse ou actualizouse un recurso", "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 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 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}", "A validation email was sent to {email}": "Enviouse un correo de validación a {email}",
"API": "API", "API": "API",
"Abandon editing": "Saír da edición", "Abandon editing": "Saír da edición",
"About": "Acerca de", "About": "Sobre",
"About Mobilizon": "Acerca de Mobilizon", "About Mobilizon": "Sobre Mobilizon",
"About anonymous participation": "Acerca da participación anónima", "About anonymous participation": "Acerca da participación anónima",
"About instance": "Acerca da instancia", "About instance": "Acerca da instancia",
"About this event": "Acerca deste evento", "About this event": "Sobre este evento",
"About this instance": "Acerca desta instancia", "About this instance": "Sobre esta instancia",
"About {instance}": "Acerca de {instance}", "About {instance}": "Acerca de {instance}",
"Accept": "Aceptar", "Accept": "Aceptar",
"Accept follow": "Aceptar seguimento", "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 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 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 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 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 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}.", "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 memberships": "Pristupi članstvima grupa",
"Access group suggested events": "Pristupi predloženim grupnim događajima", "Access group suggested events": "Pristupi predloženim grupnim događajima",
"Access group todo-lists": "Pristupi popisu grupnih zadataka", "Access group todo-lists": "Pristupi popisu grupnih zadataka",
"Access group todo-lists": "Pristupi popisu zadataka grupe",
"Access organized events": "Pristupi organiziranim događajima", "Access organized events": "Pristupi organiziranim događajima",
"Access participations": "Pristupi sudjelovanjima", "Access participations": "Pristupi sudjelovanjima",
"Access your group's resources": "Pristupi resursima tvoje grupe", "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> <template #default>
<event-map <event-map
v-if="showMap"
:routingType="routingType ?? RoutingType.OPENSTREETMAP" :routingType="routingType ?? RoutingType.OPENSTREETMAP"
:address="event.physicalAddress" :address="event.physicalAddress"
@close="showMap = false" @close="showMap = false"

View file

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

View file

@ -195,6 +195,10 @@ onMounted(async () => {
pictureFile.value = await buildFileFromIMedia(post.value?.picture); 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) => { watch(post, async (newPost: IPost | undefined, oldPost: IPost | undefined) => {
if (oldPost?.picture !== newPost?.picture) { if (oldPost?.picture !== newPost?.picture) {
pictureFile.value = await buildFileFromIMedia(post.value?.picture); pictureFile.value = await buildFileFromIMedia(post.value?.picture);