Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
7d7abd0dda
|
@ -105,3 +105,8 @@ config :tz_world,
|
|||
data_dir: System.get_env("MOBILIZON_TIMEZONES_DIR", "/var/lib/mobilizon/timezones")
|
||||
|
||||
config :tzdata, :data_dir, System.get_env("MOBILIZON_TZDATA_DIR", "/var/lib/mobilizon/tzdata")
|
||||
|
||||
config :web_push_encryption, :vapid_details,
|
||||
subject: System.get_env("MOBILIZON_WEB_PUSH_ENCRYPTION_SUBJECT", nil),
|
||||
public_key: System.get_env("MOBILIZON_WEB_PUSH_ENCRYPTION_PUBLIC_KEY", nil),
|
||||
private_key: System.get_env("MOBILIZON_WEB_PUSH_ENCRYPTION_PRIVATE_KEY", nil)
|
||||
|
|
|
@ -85,6 +85,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Config do
|
|||
|
||||
@spec build_config_cache :: map()
|
||||
defp build_config_cache do
|
||||
webpush_public_key =
|
||||
get_in(Application.get_env(:web_push_encryption, :vapid_details), [:public_key])
|
||||
|
||||
%{
|
||||
name: Config.instance_name(),
|
||||
registrations_open: Config.instance_registrations_open?(),
|
||||
|
@ -170,9 +173,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Config do
|
|||
enabled: Config.get([:instance, :enable_instance_feeds])
|
||||
},
|
||||
web_push: %{
|
||||
enabled: !is_nil(Application.get_env(:web_push_encryption, :vapid_details)),
|
||||
enabled: is_binary(webpush_public_key) && String.trim(webpush_public_key) != "",
|
||||
public_key:
|
||||
get_in(Application.get_env(:web_push_encryption, :vapid_details), [:public_key])
|
||||
if(is_binary(webpush_public_key), do: String.trim(webpush_public_key), else: nil)
|
||||
},
|
||||
export_formats: Config.instance_export_formats(),
|
||||
analytics: FrontEndAnalytics.config(),
|
||||
|
|
|
@ -5,7 +5,6 @@ defmodule Mix.Tasks.Mobilizon.WebPush.Gen.Keypair do
|
|||
Taken from https://github.com/danhper/elixir-web-push-encryption/blob/8fd0f71f3222b466d389f559be9800c49f9bb641/lib/mix/tasks/web_push_gen_keypair.ex
|
||||
"""
|
||||
use Mix.Task
|
||||
import Mix.Tasks.Mobilizon.Common, only: [mix_shell?: 0]
|
||||
|
||||
@shortdoc "Manages Mobilizon users"
|
||||
|
||||
|
@ -13,20 +12,28 @@ defmodule Mix.Tasks.Mobilizon.WebPush.Gen.Keypair do
|
|||
def run(_) do
|
||||
{public, private} = :crypto.generate_key(:ecdh, :prime256v1)
|
||||
|
||||
IO.puts("# Put the following in your #{file_name()} config file:")
|
||||
IO.puts("")
|
||||
IO.puts("config :web_push_encryption, :vapid_details,")
|
||||
IO.puts(" subject: \"mailto:administrator@example.com\",")
|
||||
IO.puts(" public_key: \"#{ub64(public)}\",")
|
||||
IO.puts(" private_key: \"#{ub64(private)}\"")
|
||||
IO.puts("Public and private VAPID keys have been generated.")
|
||||
IO.puts("")
|
||||
|
||||
if is_nil(System.get_env("MOBILIZON_DOCKER")) do
|
||||
IO.puts("# Put the following in your runtime.exs config file:")
|
||||
IO.puts("")
|
||||
IO.puts("config :web_push_encryption, :vapid_details,")
|
||||
IO.puts(" subject: \"mailto:administrator@example.com\",")
|
||||
IO.puts(" public_key: \"#{ub64(public)}\",")
|
||||
IO.puts(" private_key: \"#{ub64(private)}\"")
|
||||
IO.puts("")
|
||||
else
|
||||
IO.puts("# Set the following environment variables in your .env file:")
|
||||
IO.puts("")
|
||||
IO.puts("MOBILIZON_WEB_PUSH_ENCRYPTION_SUBJECT=\"mailto:administrator@example.com\"")
|
||||
IO.puts("MOBILIZON_WEB_PUSH_ENCRYPTION_PUBLIC_KEY=\"#{ub64(public)}\"")
|
||||
IO.puts("MOBILIZON_WEB_PUSH_ENCRYPTION_PRIVATE_KEY=\"#{ub64(private)}\"")
|
||||
IO.puts("")
|
||||
end
|
||||
end
|
||||
|
||||
defp ub64(value) do
|
||||
Base.url_encode64(value, padding: false)
|
||||
end
|
||||
|
||||
defp file_name do
|
||||
if mix_shell?(), do: "runtime.exs", else: "config.exs"
|
||||
end
|
||||
end
|
||||
|
|
1760
package-lock.json
generated
1760
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -70,6 +70,7 @@ import { CONFIG } from "@/graphql/config";
|
|||
import { IConfig } from "@/types/config.model";
|
||||
import { useRouter } from "vue-router";
|
||||
import RouteName from "@/router/name";
|
||||
import { useLazyCurrentUserIdentities } from "./composition/apollo/actor";
|
||||
|
||||
const { result: configResult } = useQuery<{ config: IConfig }>(
|
||||
CONFIG,
|
||||
|
@ -138,11 +139,15 @@ interval.value = window.setInterval(async () => {
|
|||
}
|
||||
}, 60000) as unknown as number;
|
||||
|
||||
const { load: loadIdentities } = useLazyCurrentUserIdentities();
|
||||
|
||||
onBeforeMount(async () => {
|
||||
console.debug("Before mount App");
|
||||
if (initializeCurrentUser()) {
|
||||
try {
|
||||
await initializeCurrentActor();
|
||||
const result = await loadIdentities();
|
||||
if (!result) return;
|
||||
await initializeCurrentActor(result.loggedUser.actors);
|
||||
} catch (err) {
|
||||
if (err instanceof NoIdentitiesException) {
|
||||
await router.push({
|
||||
|
@ -223,7 +228,7 @@ const initializeCurrentUser = () => {
|
|||
console.debug("Initialized current user", userData);
|
||||
return true;
|
||||
}
|
||||
console.debug("Failed to initialize current user");
|
||||
console.debug("We don't seem to have a currently logged-in user");
|
||||
return false;
|
||||
};
|
||||
|
||||
|
|
|
@ -9,7 +9,12 @@ let isRefreshing = false;
|
|||
let pendingRequests: any[] = [];
|
||||
|
||||
const resolvePendingRequests = () => {
|
||||
pendingRequests.map((callback) => callback());
|
||||
console.debug("resolving pending requests");
|
||||
pendingRequests.map((callback) => {
|
||||
console.debug("calling callback", callback);
|
||||
return callback();
|
||||
});
|
||||
console.debug("emptying pending requests after resolving them all");
|
||||
pendingRequests = [];
|
||||
};
|
||||
|
||||
|
@ -21,7 +26,23 @@ const isAuthError = (graphQLError: GraphQLError | undefined) => {
|
|||
|
||||
const errorLink = onError(
|
||||
({ graphQLErrors, networkError, forward, operation }) => {
|
||||
console.debug("We have an apollo error", [graphQLErrors, networkError]);
|
||||
if (graphQLErrors) {
|
||||
graphQLErrors.map(
|
||||
(graphQLError: GraphQLError & { status_code?: number }) => {
|
||||
if (graphQLError?.status_code !== 401) {
|
||||
console.debug(
|
||||
`[GraphQL error]: Message: ${graphQLError.message}, Location: ${graphQLError.locations}, Path: ${graphQLError.path}`
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (networkError) {
|
||||
console.error(`[Network error]: ${networkError}`);
|
||||
console.debug(JSON.stringify(networkError));
|
||||
}
|
||||
|
||||
if (
|
||||
graphQLErrors?.some((graphQLError) => isAuthError(graphQLError)) ||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
|
@ -67,6 +88,9 @@ const errorLink = onError(
|
|||
})
|
||||
).filter((value) => Boolean(value));
|
||||
} else {
|
||||
console.debug(
|
||||
"Skipping refreshing as isRefreshing is already to true, adding requests to pending"
|
||||
);
|
||||
forwardOperation = fromPromise(
|
||||
new Promise((resolve) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
|
@ -78,23 +102,6 @@ const errorLink = onError(
|
|||
|
||||
return forwardOperation.flatMap(() => forward(operation));
|
||||
}
|
||||
|
||||
if (graphQLErrors) {
|
||||
graphQLErrors.map(
|
||||
(graphQLError: GraphQLError & { status_code?: number }) => {
|
||||
if (graphQLError?.status_code !== 401) {
|
||||
console.debug(
|
||||
`[GraphQL error]: Message: ${graphQLError.message}, Location: ${graphQLError.locations}, Path: ${graphQLError.path}`
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (networkError) {
|
||||
console.error(`[Network error]: ${networkError}`);
|
||||
console.debug(JSON.stringify(networkError));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ if (!import.meta.env.VITE_HISTOIRE_ENV) {
|
|||
|
||||
const retryLink = new RetryLink();
|
||||
|
||||
export const fullLink = authMiddleware
|
||||
.concat(retryLink)
|
||||
export const fullLink = retryLink
|
||||
.concat(errorLink)
|
||||
.concat(authMiddleware)
|
||||
.concat(link ?? uploadLink);
|
||||
|
|
|
@ -26,13 +26,13 @@ body {
|
|||
@apply opacity-50 cursor-not-allowed;
|
||||
}
|
||||
.btn-danger {
|
||||
@apply bg-mbz-danger hover:bg-mbz-danger/90;
|
||||
@apply border-2 bg-mbz-danger hover:bg-mbz-danger/90 text-white;
|
||||
}
|
||||
.btn-success {
|
||||
@apply bg-mbz-success;
|
||||
@apply border-2 bg-mbz-success text-white;
|
||||
}
|
||||
.btn-warning {
|
||||
@apply bg-mbz-warning text-black hover:bg-mbz-warning/90 hover:text-slate-800;
|
||||
@apply border-2 bg-mbz-warning text-black hover:bg-mbz-warning/90 hover:text-slate-800;
|
||||
}
|
||||
.btn-text {
|
||||
@apply bg-transparent border-transparent text-black dark:text-white font-normal underline hover:bg-zinc-200 hover:text-black;
|
||||
|
@ -42,13 +42,13 @@ body {
|
|||
@apply bg-transparent text-black dark:text-white font-semibold py-2 px-4 border border-mbz-bluegreen dark:border-violet-3;
|
||||
}
|
||||
.btn-outlined-success {
|
||||
@apply border-2 border-mbz-success bg-transparent text-mbz-success hover:bg-mbz-success;
|
||||
@apply border-2 border-mbz-success bg-transparent text-mbz-success hover:bg-mbz-success hover:text-white;
|
||||
}
|
||||
.btn-outlined-warning {
|
||||
@apply bg-transparent border dark:text-white hover:dark:text-slate-900 hover:bg-mbz-warning border-mbz-warning;
|
||||
}
|
||||
.btn-outlined-danger {
|
||||
@apply border-2 bg-transparent border-mbz-danger text-mbz-danger hover:bg-mbz-danger;
|
||||
@apply border-2 bg-transparent border-mbz-danger text-mbz-danger hover:bg-mbz-danger hover:text-white;
|
||||
}
|
||||
.btn-outlined-text {
|
||||
@apply bg-transparent hover:text-slate-900;
|
||||
|
@ -97,6 +97,15 @@ body {
|
|||
.input {
|
||||
@apply appearance-none box-border rounded border w-full py-2 px-3 text-black leading-tight dark:bg-zinc-600 dark:placeholder:text-zinc-400 dark:text-zinc-50;
|
||||
}
|
||||
.input-size-small {
|
||||
@apply text-sm;
|
||||
}
|
||||
.input-size-medium {
|
||||
@apply text-base;
|
||||
}
|
||||
.input-size-large {
|
||||
@apply text-xl;
|
||||
}
|
||||
.input-danger {
|
||||
@apply border-red-500;
|
||||
}
|
||||
|
@ -205,7 +214,7 @@ body {
|
|||
|
||||
/* Select */
|
||||
.select {
|
||||
@apply dark:bg-zinc-600 dark:placeholder:text-zinc-400 dark:text-zinc-50 rounded pl-2 pr-8 border-2 border-transparent h-10 shadow-none border rounded w-full;
|
||||
@apply dark:bg-zinc-600 dark:placeholder:text-zinc-400 dark:text-zinc-50 pl-2 pr-8 border-2 border-transparent h-10 shadow-none rounded w-full;
|
||||
}
|
||||
|
||||
/* Radio */
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
keypath="{instanceName} is an instance of {mobilizon_link}, a free software built with the community."
|
||||
>
|
||||
<template #instanceName>
|
||||
<b>{{ config?.name }}</b>
|
||||
<b>{{ instanceName }}</b>
|
||||
</template>
|
||||
<template #mobilizon_link>
|
||||
<a href="https://joinmobilizon.org">{{ t("Mobilizon") }}</a>
|
||||
|
@ -57,7 +57,10 @@
|
|||
}}
|
||||
</span>
|
||||
</p>
|
||||
<SentryFeedback />
|
||||
<SentryFeedback
|
||||
v-if="sentryProvider"
|
||||
:providerConfig="sentryProvider"
|
||||
/>
|
||||
|
||||
<p class="prose dark:prose-invert" v-if="!sentryEnabled">
|
||||
{{
|
||||
|
@ -84,7 +87,7 @@
|
|||
<div class="buttons" v-if="!sentryEnabled">
|
||||
<o-tooltip
|
||||
:label="tooltipConfig.label"
|
||||
:type="tooltipConfig.type"
|
||||
:variant="tooltipConfig.variant"
|
||||
:active="copied !== false"
|
||||
always
|
||||
>
|
||||
|
@ -101,12 +104,13 @@
|
|||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { checkProviderConfig } from "@/services/statistics";
|
||||
import { IAnalyticsConfig } from "@/types/config.model";
|
||||
import { IAnalyticsConfig, IConfig } from "@/types/config.model";
|
||||
import { computed, defineAsyncComponent, ref } from "vue";
|
||||
import { useQueryLoading } from "@vue/apollo-composable";
|
||||
import { useQuery, useQueryLoading } from "@vue/apollo-composable";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useHead } from "@unhead/vue";
|
||||
import { useAnalytics } from "@/composition/apollo/config";
|
||||
import { INSTANCE_NAME } from "@/graphql/config";
|
||||
const SentryFeedback = defineAsyncComponent(
|
||||
() => import("./Feedback/SentryFeedback.vue")
|
||||
);
|
||||
|
@ -126,6 +130,12 @@ useHead({
|
|||
title: computed(() => t("Error")),
|
||||
});
|
||||
|
||||
const { result: instanceConfig } = useQuery<{ config: Pick<IConfig, "name"> }>(
|
||||
INSTANCE_NAME
|
||||
);
|
||||
|
||||
const instanceName = computed(() => instanceConfig.value?.config.name);
|
||||
|
||||
const copyErrorToClipboard = async (): Promise<void> => {
|
||||
try {
|
||||
if (window.isSecureContext && navigator.clipboard) {
|
||||
|
|
|
@ -100,13 +100,12 @@
|
|||
<span v-if="event.participantStats.notApproved > 0">
|
||||
<o-button
|
||||
variant="text"
|
||||
@click="
|
||||
gotToWithCheck(participation, {
|
||||
name: RouteName.PARTICIPATIONS,
|
||||
query: { role: ParticipantRole.NOT_APPROVED },
|
||||
params: { eventId: event.uuid },
|
||||
})
|
||||
"
|
||||
tag="router-link"
|
||||
:to="{
|
||||
name: RouteName.PARTICIPATIONS,
|
||||
query: { role: ParticipantRole.NOT_APPROVED },
|
||||
params: { eventId: event.uuid },
|
||||
}"
|
||||
>
|
||||
{{
|
||||
$t(
|
||||
|
|
|
@ -273,6 +273,28 @@
|
|||
</div>
|
||||
</o-dropdown-item>
|
||||
|
||||
<o-dropdown-item
|
||||
aria-role="listitem"
|
||||
has-link
|
||||
v-if="
|
||||
![
|
||||
ParticipantRole.PARTICIPANT,
|
||||
ParticipantRole.NOT_APPROVED,
|
||||
].includes(participation.role)
|
||||
"
|
||||
>
|
||||
<router-link
|
||||
class="flex gap-1"
|
||||
:to="{
|
||||
name: RouteName.ANNOUNCEMENTS,
|
||||
params: { eventId: participation.event?.uuid },
|
||||
}"
|
||||
>
|
||||
<Bullhorn />
|
||||
{{ t("Announcements") }}
|
||||
</router-link>
|
||||
</o-dropdown-item>
|
||||
|
||||
<o-dropdown-item aria-role="listitem">
|
||||
<router-link
|
||||
class="flex gap-1"
|
||||
|
@ -302,7 +324,6 @@ import {
|
|||
organizerDisplayName,
|
||||
} from "@/types/event.model";
|
||||
import { displayNameAndUsername, IPerson } from "@/types/actor";
|
||||
import { CURRENT_ACTOR_CLIENT } from "@/graphql/actor";
|
||||
import RouteName from "@/router/name";
|
||||
import { changeIdentity } from "@/utils/identity";
|
||||
import LazyImageWrapper from "@/components/Image/LazyImageWrapper.vue";
|
||||
|
@ -318,12 +339,14 @@ import AccountGroup from "vue-material-design-icons/AccountGroup.vue";
|
|||
import Video from "vue-material-design-icons/Video.vue";
|
||||
import { useProgrammatic } from "@oruga-ui/oruga-next";
|
||||
import { computed, inject } from "vue";
|
||||
import { useQuery } from "@vue/apollo-composable";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Dialog } from "@/plugins/dialog";
|
||||
import { Snackbar } from "@/plugins/snackbar";
|
||||
import { useDeleteEvent } from "@/composition/apollo/event";
|
||||
import Tag from "@/components/TagElement.vue";
|
||||
import { escapeHtml } from "@/utils/html";
|
||||
import Bullhorn from "vue-material-design-icons/Bullhorn.vue";
|
||||
import { useCurrentActorClient } from "@/composition/apollo/actor";
|
||||
|
||||
const props = defineProps<{
|
||||
participation: IParticipant;
|
||||
|
@ -332,8 +355,7 @@ const props = defineProps<{
|
|||
|
||||
const emit = defineEmits(["eventDeleted"]);
|
||||
|
||||
const { result: currentActorResult } = useQuery(CURRENT_ACTOR_CLIENT);
|
||||
const currentActor = computed(() => currentActorResult.value?.currentActor);
|
||||
const { currentActor } = useCurrentActorClient();
|
||||
const { t } = useI18n({ useScope: "global" });
|
||||
|
||||
const dialog = inject<Dialog>("dialog");
|
||||
|
@ -365,7 +387,7 @@ const openDeleteEventModal = (
|
|||
)}
|
||||
<br><br>
|
||||
${t('To confirm, type your event title "{eventTitle}"', {
|
||||
eventTitle: event.title,
|
||||
eventTitle: escapeHtml(event.title),
|
||||
})}`,
|
||||
confirmText: t("Delete {eventTitle}", {
|
||||
eventTitle: event.title,
|
||||
|
@ -374,6 +396,7 @@ const openDeleteEventModal = (
|
|||
placeholder: event.title,
|
||||
pattern: escapeRegExp(event.title),
|
||||
},
|
||||
hasInput: true,
|
||||
onConfirm: () => callback(event),
|
||||
});
|
||||
};
|
||||
|
@ -432,7 +455,7 @@ const gotToWithCheck = async (
|
|||
route: RouteLocationRaw
|
||||
): Promise<any> => {
|
||||
if (
|
||||
participation.actor.id !== currentActor.value.id &&
|
||||
participation.actor.id !== currentActor.value?.id &&
|
||||
participation.event.organizerActor
|
||||
) {
|
||||
const organizerActor = participation.event.organizerActor as IPerson;
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
drag-drop
|
||||
>
|
||||
<div
|
||||
class="w-100 rounded text-center p-4 rounded-xl border-dashed border-2 border-gray-600"
|
||||
class="w-100 text-center p-4 rounded-xl border-dashed border-2 border-gray-600"
|
||||
>
|
||||
<span class="mx-auto flex w-fit">
|
||||
<Upload />
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
} from "@/graphql/actor";
|
||||
import { IPerson } from "@/types/actor";
|
||||
import { ICurrentUser } from "@/types/current-user.model";
|
||||
import { useQuery } from "@vue/apollo-composable";
|
||||
import { useLazyQuery, useQuery } from "@vue/apollo-composable";
|
||||
import { computed, Ref, unref } from "vue";
|
||||
import { useCurrentUserClient } from "./user";
|
||||
|
||||
|
@ -22,6 +22,12 @@ export function useCurrentActorClient() {
|
|||
return { currentActor, error, loading };
|
||||
}
|
||||
|
||||
export function useLazyCurrentUserIdentities() {
|
||||
return useLazyQuery<{
|
||||
loggedUser: Pick<ICurrentUser, "actors">;
|
||||
}>(IDENTITIES);
|
||||
}
|
||||
|
||||
export function useCurrentUserIdentities() {
|
||||
const { currentUser } = useCurrentUserClient();
|
||||
|
||||
|
|
|
@ -453,7 +453,7 @@ export const DELETE_PERSON = gql`
|
|||
* Prefer CREATE_PERSON when creating another identity
|
||||
*/
|
||||
export const REGISTER_PERSON = gql`
|
||||
mutation (
|
||||
mutation RegisterPerson(
|
||||
$preferredUsername: String!
|
||||
$name: String!
|
||||
$summary: String!
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { provide, createApp, h, computed, ref } from "vue";
|
||||
import { provide, createApp, h, ref } from "vue";
|
||||
import VueScrollTo from "vue-scrollto";
|
||||
// import VueAnnouncer from "@vue-a11y/announcer";
|
||||
// import VueSkipTo from "@vue-a11y/skip-to";
|
||||
|
@ -59,11 +59,7 @@ apolloClient
|
|||
instanceName.value = configData.config?.name;
|
||||
});
|
||||
|
||||
const head = createHead({
|
||||
titleTemplate: computed(() =>
|
||||
instanceName.value ? `%s | ${instanceName.value}` : "%s"
|
||||
).value,
|
||||
});
|
||||
const head = createHead();
|
||||
app.use(head);
|
||||
|
||||
app.mount("#app");
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
import { AUTH_USER_ACTOR_ID } from "@/constants";
|
||||
import { UPDATE_CURRENT_ACTOR_CLIENT, IDENTITIES } from "@/graphql/actor";
|
||||
import { UPDATE_CURRENT_ACTOR_CLIENT } from "@/graphql/actor";
|
||||
import { IPerson } from "@/types/actor";
|
||||
import { ICurrentUser } from "@/types/current-user.model";
|
||||
import { apolloClient } from "@/vue-apollo";
|
||||
import {
|
||||
provideApolloClient,
|
||||
useLazyQuery,
|
||||
useMutation,
|
||||
} from "@vue/apollo-composable";
|
||||
import { computed } from "vue";
|
||||
import { provideApolloClient, useMutation } from "@vue/apollo-composable";
|
||||
|
||||
export class NoIdentitiesException extends Error {}
|
||||
|
||||
|
@ -38,38 +32,31 @@ export async function changeIdentity(identity: IPerson): Promise<void> {
|
|||
});
|
||||
}
|
||||
|
||||
const { load: loadIdentities } = provideApolloClient(apolloClient)(() =>
|
||||
useLazyQuery<{ loggedUser: Pick<ICurrentUser, "actors"> }>(IDENTITIES)
|
||||
);
|
||||
|
||||
/**
|
||||
* We fetch from localStorage the latest actor ID used,
|
||||
* then fetch the current identities to set in cache
|
||||
* the current identity used
|
||||
*/
|
||||
export async function initializeCurrentActor(): Promise<void> {
|
||||
export async function initializeCurrentActor(
|
||||
identities: IPerson[] | undefined
|
||||
): Promise<void> {
|
||||
const actorId = localStorage.getItem(AUTH_USER_ACTOR_ID);
|
||||
console.debug("Initializing current actor", actorId);
|
||||
|
||||
try {
|
||||
const result = await loadIdentities();
|
||||
if (!result) return;
|
||||
if (!identities) {
|
||||
console.debug("Failed to load user's identities", identities);
|
||||
return;
|
||||
}
|
||||
|
||||
console.debug("got identities", result);
|
||||
const identities = computed(() => result.loggedUser?.actors);
|
||||
console.debug(
|
||||
"initializing current actor based on identities",
|
||||
identities.value
|
||||
);
|
||||
|
||||
if (identities.value && identities.value.length < 1) {
|
||||
if (identities && identities.length < 1) {
|
||||
console.warn("Logged user has no identities!");
|
||||
throw new NoIdentitiesException();
|
||||
}
|
||||
const activeIdentity =
|
||||
(identities.value || []).find(
|
||||
(identities || []).find(
|
||||
(identity: IPerson | undefined) => identity?.id === actorId
|
||||
) || ((identities.value || [])[0] as IPerson);
|
||||
) || ((identities || [])[0] as IPerson);
|
||||
|
||||
if (activeIdentity) {
|
||||
await changeIdentity(activeIdentity);
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
</li>
|
||||
</breadcrumbs-nav>
|
||||
<DraggableList
|
||||
v-if="resource.actor"
|
||||
:resources="resource.children.elements"
|
||||
:isRoot="resource.path === '/'"
|
||||
:group="resource.actor"
|
||||
|
@ -98,20 +99,34 @@
|
|||
v-model:active="createResourceModal"
|
||||
has-modal-card
|
||||
:close-button-aria-label="t('Close')"
|
||||
trap-focus
|
||||
:autoFocus="false"
|
||||
>
|
||||
<section class="w-full md:w-[640px]">
|
||||
<o-notification variant="danger" v-if="modalError">
|
||||
{{ modalError }}
|
||||
</o-notification>
|
||||
<form @submit.prevent="createResource">
|
||||
<p v-if="newResource.type !== 'folder'">
|
||||
<p v-if="newResource.type === 'pad'">
|
||||
{{
|
||||
t("The pad will be created on {service}", {
|
||||
service: newResourceHost,
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
<p v-else-if="newResource.type === 'calc'">
|
||||
{{
|
||||
t("The calc will be created on {service}", {
|
||||
service: newResourceHost,
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
<p v-else-if="newResource.type === 'visio'">
|
||||
{{
|
||||
t("The videoconference will be created on {service}", {
|
||||
service: newResourceHost,
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
<o-field :label="t('Title')" label-for="new-resource-title">
|
||||
<o-input
|
||||
ref="modalNewResourceInput"
|
||||
|
@ -132,7 +147,7 @@
|
|||
has-modal-card
|
||||
aria-modal
|
||||
:close-button-aria-label="t('Close')"
|
||||
trap-focus
|
||||
:autoFocus="false"
|
||||
:width="640"
|
||||
>
|
||||
<div class="w-full md:w-[640px]">
|
||||
|
@ -298,8 +313,12 @@ const modalError = ref("");
|
|||
const modalFieldErrors: Record<string, string> = reactive({});
|
||||
|
||||
const resourceRenameInput = ref<any>();
|
||||
const modalNewResourceInput = ref<HTMLElement>();
|
||||
const modalNewResourceLinkInput = ref<HTMLElement>();
|
||||
const modalNewResourceInput = ref<{
|
||||
$refs: { inputRef: HTMLInputElement };
|
||||
} | null>();
|
||||
const modalNewResourceLinkInput = ref<{
|
||||
$refs: { inputRef: HTMLInputElement };
|
||||
} | null>();
|
||||
|
||||
const actualPath = computed((): string => {
|
||||
const path = Array.isArray(props.path) ? props.path.join("/") : props.path;
|
||||
|
@ -419,14 +438,14 @@ const createSentenceForType = (type: string): string => {
|
|||
const createLinkModal = async (): Promise<void> => {
|
||||
createLinkResourceModal.value = true;
|
||||
await nextTick();
|
||||
modalNewResourceLinkInput.value?.focus();
|
||||
modalNewResourceLinkInput.value?.$refs.inputRef?.focus();
|
||||
};
|
||||
|
||||
const createFolderModal = async (): Promise<void> => {
|
||||
newResource.type = "folder";
|
||||
createResourceModal.value = true;
|
||||
await nextTick();
|
||||
modalNewResourceInput.value?.focus();
|
||||
modalNewResourceInput.value?.$refs.inputRef?.focus();
|
||||
};
|
||||
|
||||
const createResourceFromProvider = async (
|
||||
|
@ -436,7 +455,7 @@ const createResourceFromProvider = async (
|
|||
newResource.type = provider.software;
|
||||
createResourceModal.value = true;
|
||||
await nextTick();
|
||||
modalNewResourceInput.value?.focus();
|
||||
modalNewResourceInput.value?.$refs.inputRef?.focus();
|
||||
};
|
||||
|
||||
const generateFullResourceUrl = (provider: IProvider): string => {
|
||||
|
|
|
@ -143,6 +143,7 @@ import { LoginError, LoginErrorCode } from "@/types/enums";
|
|||
import { useCurrentUserClient } from "@/composition/apollo/user";
|
||||
import { useHead } from "@unhead/vue";
|
||||
import { enumTransformer, useRouteQuery } from "vue-use-route-query";
|
||||
import { useLazyCurrentUserIdentities } from "@/composition/apollo/actor";
|
||||
|
||||
const { t } = useI18n({ useScope: "global" });
|
||||
const router = useRouter();
|
||||
|
@ -235,12 +236,17 @@ const loginAction = (e: Event) => {
|
|||
});
|
||||
};
|
||||
|
||||
const { load: loadIdentities } = useLazyCurrentUserIdentities();
|
||||
|
||||
const { onDone: onCurrentUserMutationDone, mutate: updateCurrentUserMutation } =
|
||||
useMutation(UPDATE_CURRENT_USER_CLIENT);
|
||||
|
||||
onCurrentUserMutationDone(async () => {
|
||||
console.debug("Current user mutation done, now setuping actors…");
|
||||
try {
|
||||
await initializeCurrentActor();
|
||||
const result = await loadIdentities();
|
||||
if (!result) return;
|
||||
await initializeCurrentActor(result.loggedUser.actors);
|
||||
} catch (err: any) {
|
||||
if (err instanceof NoIdentitiesException && currentUser.value) {
|
||||
await router.push({
|
||||
|
@ -257,6 +263,7 @@ onCurrentUserMutationDone(async () => {
|
|||
});
|
||||
|
||||
const setupClientUserAndActors = async (login: ILogin): Promise<void> => {
|
||||
console.debug("Setuping client user and actors");
|
||||
updateCurrentUserMutation({
|
||||
id: login.user.id,
|
||||
email: credentials.email,
|
||||
|
@ -298,7 +305,7 @@ onMounted(() => {
|
|||
if (currentUser.value?.isLoggedIn) {
|
||||
console.debug(
|
||||
"Current user is already logged-in, redirecting to Homepage",
|
||||
currentUser
|
||||
currentUser.value
|
||||
);
|
||||
router.push("/");
|
||||
}
|
||||
|
|
|
@ -26,15 +26,19 @@
|
|||
required
|
||||
type="email"
|
||||
v-model="emailValue"
|
||||
expanded
|
||||
/>
|
||||
</o-field>
|
||||
<p class="control">
|
||||
<p class="my-4 flex gap-2">
|
||||
<o-button variant="primary" native-type="submit">
|
||||
{{ t("Submit") }}
|
||||
</o-button>
|
||||
<router-link :to="{ name: RouteName.LOGIN }" class="button is-text">{{
|
||||
t("Cancel")
|
||||
}}</router-link>
|
||||
<o-button
|
||||
tag="router-link"
|
||||
:to="{ name: RouteName.LOGIN }"
|
||||
variant="text"
|
||||
>{{ t("Cancel") }}</o-button
|
||||
>
|
||||
</p>
|
||||
</form>
|
||||
<div v-else>
|
||||
|
|
Loading…
Reference in a new issue