Merge branch 'fixes' into 'main'
Little features Closes #1082, #1102, #1154 et #540 See merge request framasoft/mobilizon!1305
This commit is contained in:
commit
720c11c43f
@ -169,7 +169,8 @@ e2e:
|
|||||||
artifacts:
|
artifacts:
|
||||||
expire_in: 2 days
|
expire_in: 2 days
|
||||||
paths:
|
paths:
|
||||||
- js/playwright-report
|
- js/playwright-report/
|
||||||
|
- js/test-results/
|
||||||
|
|
||||||
pages:
|
pages:
|
||||||
stage: deploy
|
stage: deploy
|
||||||
|
@ -19,6 +19,7 @@ config :mobilizon, :instance,
|
|||||||
registrations_open: false,
|
registrations_open: false,
|
||||||
registration_email_allowlist: [],
|
registration_email_allowlist: [],
|
||||||
registration_email_denylist: [],
|
registration_email_denylist: [],
|
||||||
|
disable_database_login: false,
|
||||||
languages: [],
|
languages: [],
|
||||||
default_language: "en",
|
default_language: "en",
|
||||||
demo: false,
|
demo: false,
|
||||||
|
@ -54,6 +54,7 @@ import {
|
|||||||
defineAsyncComponent,
|
defineAsyncComponent,
|
||||||
computed,
|
computed,
|
||||||
watch,
|
watch,
|
||||||
|
onBeforeUnmount,
|
||||||
} from "vue";
|
} from "vue";
|
||||||
import { LocationType } from "@/types/user-location.model";
|
import { LocationType } from "@/types/user-location.model";
|
||||||
import { useMutation, useQuery } from "@vue/apollo-composable";
|
import { useMutation, useQuery } from "@vue/apollo-composable";
|
||||||
@ -123,7 +124,7 @@ const interval = ref<number>(0);
|
|||||||
|
|
||||||
const notifier = inject<Notifier>("notifier");
|
const notifier = inject<Notifier>("notifier");
|
||||||
|
|
||||||
interval.value = setInterval(async () => {
|
interval.value = window.setInterval(async () => {
|
||||||
const accessToken = localStorage.getItem(AUTH_ACCESS_TOKEN);
|
const accessToken = localStorage.getItem(AUTH_ACCESS_TOKEN);
|
||||||
if (accessToken) {
|
if (accessToken) {
|
||||||
const token = jwt_decode<JwtPayload>(accessToken);
|
const token = jwt_decode<JwtPayload>(accessToken);
|
||||||
@ -155,6 +156,7 @@ onBeforeMount(async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const snackbar = inject<Snackbar>("snackbar");
|
const snackbar = inject<Snackbar>("snackbar");
|
||||||
|
const darkModePreference = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
online.value = window.navigator.onLine;
|
online.value = window.navigator.onLine;
|
||||||
@ -187,6 +189,7 @@ onMounted(() => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
darkModePreference.addEventListener("change", changeTheme);
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
@ -289,6 +292,23 @@ watch(config, async (configWatched: IConfig | undefined) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const isDemoMode = computed(() => config.value?.demoMode);
|
const isDemoMode = computed(() => config.value?.demoMode);
|
||||||
|
|
||||||
|
const changeTheme = () => {
|
||||||
|
console.debug("changing theme");
|
||||||
|
if (
|
||||||
|
localStorage.getItem("theme") === "dark" ||
|
||||||
|
(!("theme" in localStorage) &&
|
||||||
|
window.matchMedia("(prefers-color-scheme: dark)").matches)
|
||||||
|
) {
|
||||||
|
document.documentElement.classList.add("dark");
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove("dark");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
darkModePreference.removeEventListener("change", changeTheme);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@ -2,6 +2,10 @@ body {
|
|||||||
@apply bg-body-background-color dark:bg-zinc-800 dark:text-white;
|
@apply bg-body-background-color dark:bg-zinc-800 dark:text-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.out {
|
||||||
|
@apply underline hover:decoration-2 hover:decoration-mbz-yellow-alt-600;
|
||||||
|
}
|
||||||
|
|
||||||
/* Button */
|
/* Button */
|
||||||
.btn {
|
.btn {
|
||||||
@apply font-bold py-2 px-4 bg-mbz-bluegreen hover:bg-mbz-bluegreen-600 text-white rounded h-10 outline-none focus:ring ring-offset-1 ring-offset-slate-50 ring-blue-300;
|
@apply font-bold py-2 px-4 bg-mbz-bluegreen hover:bg-mbz-bluegreen-600 text-white rounded h-10 outline-none focus:ring ring-offset-1 ring-offset-slate-50 ring-blue-300;
|
||||||
@ -194,6 +198,10 @@ body {
|
|||||||
@apply pl-2;
|
@apply pl-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.o-field--addons .o-radio:not(:only-child) input {
|
||||||
|
@apply rounded-full;
|
||||||
|
}
|
||||||
|
|
||||||
/* Editor */
|
/* Editor */
|
||||||
button.menubar__button {
|
button.menubar__button {
|
||||||
@apply dark:text-white;
|
@apply dark:text-white;
|
||||||
|
@ -3,9 +3,8 @@
|
|||||||
<div class="">
|
<div class="">
|
||||||
<o-field
|
<o-field
|
||||||
:label-for="id"
|
:label-for="id"
|
||||||
expanded
|
|
||||||
:message="fieldErrors"
|
:message="fieldErrors"
|
||||||
:type="{ 'is-danger': fieldErrors }"
|
:variant="{ danger: fieldErrors }"
|
||||||
class="!-mt-2"
|
class="!-mt-2"
|
||||||
:labelClass="labelClass"
|
:labelClass="labelClass"
|
||||||
>
|
>
|
||||||
@ -32,7 +31,6 @@
|
|||||||
v-model="queryText"
|
v-model="queryText"
|
||||||
:placeholder="placeholderWithDefault"
|
:placeholder="placeholderWithDefault"
|
||||||
:customFormatter="(elem: IAddress) => addressFullName(elem)"
|
:customFormatter="(elem: IAddress) => addressFullName(elem)"
|
||||||
:loading="isFetching"
|
|
||||||
:debounceTyping="debounceDelay"
|
:debounceTyping="debounceDelay"
|
||||||
@typing="asyncData"
|
@typing="asyncData"
|
||||||
:icon="canShowLocateMeButton ? null : 'map-marker'"
|
:icon="canShowLocateMeButton ? null : 'map-marker'"
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
/>
|
/>
|
||||||
<full-address-auto-complete
|
<full-address-auto-complete
|
||||||
:resultType="AddressSearchType.ADMINISTRATIVE"
|
:resultType="AddressSearchType.ADMINISTRATIVE"
|
||||||
:doGeoLocation="false"
|
|
||||||
v-model="location"
|
v-model="location"
|
||||||
:hide-map="true"
|
:hide-map="true"
|
||||||
:hide-selected="true"
|
:hide-selected="true"
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
@click="scrollLeft"
|
@click="scrollLeft"
|
||||||
class="absolute inset-y-0 my-auto z-10 rounded-full bg-white dark:bg-transparent w-10 h-10 border border-shadowColor -left-5"
|
class="absolute inset-y-0 my-auto z-10 rounded-full bg-white dark:bg-transparent w-10 h-10 border border-shadowColor -left-5"
|
||||||
>
|
>
|
||||||
<div class=""><</div>
|
<span class=""><</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-hidden">
|
<div class="overflow-hidden">
|
||||||
@ -41,7 +41,7 @@
|
|||||||
@click="scrollRight"
|
@click="scrollRight"
|
||||||
class="absolute inset-y-0 my-auto z-10 rounded-full bg-white dark:bg-transparent w-10 h-10 border border-shadowColor -right-5"
|
class="absolute inset-y-0 my-auto z-10 rounded-full bg-white dark:bg-transparent w-10 h-10 border border-shadowColor -right-5"
|
||||||
>
|
>
|
||||||
<div class="">></div>
|
<span class="">></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -29,9 +29,7 @@
|
|||||||
v-for="group in selectedGroups"
|
v-for="group in selectedGroups"
|
||||||
:key="group.id"
|
:key="group.id"
|
||||||
:group="group"
|
:group="group"
|
||||||
:view-mode="'column'"
|
:mode="'column'"
|
||||||
:minimal="true"
|
|
||||||
:has-border="true"
|
|
||||||
:showSummary="false"
|
:showSummary="false"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -19,9 +19,7 @@
|
|||||||
v-for="event in events?.elements"
|
v-for="event in events?.elements"
|
||||||
:key="event.id"
|
:key="event.id"
|
||||||
:event="event"
|
:event="event"
|
||||||
view-mode="column"
|
mode="column"
|
||||||
:has-border="true"
|
|
||||||
:minimal="true"
|
|
||||||
/>
|
/>
|
||||||
<more-content
|
<more-content
|
||||||
:to="{
|
:to="{
|
||||||
|
@ -185,7 +185,7 @@
|
|||||||
>{{ t("Login") }}</router-link
|
>{{ t("Login") }}</router-link
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="!currentActor?.id">
|
<li v-if="!currentActor?.id && canRegister">
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: RouteName.REGISTER }"
|
:to="{ name: RouteName.REGISTER }"
|
||||||
class="block py-2 pr-4 pl-3 text-zinc-700 border-b border-gray-100 hover:bg-zinc-50 md:hover:bg-transparent md:border-0 md:hover:text-mbz-purple-700 md:p-0 dark:text-zinc-400 md:dark:hover:text-white dark:hover:bg-zinc-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
|
class="block py-2 pr-4 pl-3 text-zinc-700 border-b border-gray-100 hover:bg-zinc-50 md:hover:bg-transparent md:border-0 md:hover:text-mbz-purple-700 md:p-0 dark:text-zinc-400 md:dark:hover:text-white dark:hover:bg-zinc-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
|
||||||
@ -374,7 +374,7 @@ import { ICurrentUserRole } from "@/types/enums";
|
|||||||
import { logout } from "../utils/auth";
|
import { logout } from "../utils/auth";
|
||||||
import { displayName } from "../types/actor";
|
import { displayName } from "../types/actor";
|
||||||
import RouteName from "../router/name";
|
import RouteName from "../router/name";
|
||||||
import { ref, watch } from "vue";
|
import { computed, ref, watch } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
|
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
|
||||||
@ -387,6 +387,7 @@ import {
|
|||||||
import { useMutation } from "@vue/apollo-composable";
|
import { useMutation } from "@vue/apollo-composable";
|
||||||
import { UPDATE_DEFAULT_ACTOR } from "@/graphql/actor";
|
import { UPDATE_DEFAULT_ACTOR } from "@/graphql/actor";
|
||||||
import { changeIdentity } from "@/utils/identity";
|
import { changeIdentity } from "@/utils/identity";
|
||||||
|
import { useRegistrationConfig } from "@/composition/apollo/config";
|
||||||
// import { useRestrictions } from "@/composition/apollo/config";
|
// import { useRestrictions } from "@/composition/apollo/config";
|
||||||
|
|
||||||
const { currentUser } = useCurrentUserClient();
|
const { currentUser } = useCurrentUserClient();
|
||||||
@ -399,6 +400,15 @@ const router = useRouter();
|
|||||||
// const route = useRoute();
|
// const route = useRoute();
|
||||||
|
|
||||||
const { identities } = useCurrentUserIdentities();
|
const { identities } = useCurrentUserIdentities();
|
||||||
|
const { registrationsOpen, registrationsAllowlist, databaseLogin } =
|
||||||
|
useRegistrationConfig();
|
||||||
|
|
||||||
|
const canRegister = computed(() => {
|
||||||
|
return (
|
||||||
|
(registrationsOpen.value || registrationsAllowlist.value) &&
|
||||||
|
databaseLogin.value
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// const mobileNavbarActive = ref(false);
|
// const mobileNavbarActive = ref(false);
|
||||||
|
|
||||||
|
@ -11,8 +11,7 @@
|
|||||||
<event-card
|
<event-card
|
||||||
v-if="instanceOfIEvent(activeElement)"
|
v-if="instanceOfIEvent(activeElement)"
|
||||||
:event="(activeElement as IEvent)"
|
:event="(activeElement as IEvent)"
|
||||||
:has-border="false"
|
mode="column"
|
||||||
view-mode="column"
|
|
||||||
:options="{
|
:options="{
|
||||||
isRemoteEvent: activeElement.__typename === 'EventResult',
|
isRemoteEvent: activeElement.__typename === 'EventResult',
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
@ -21,8 +20,7 @@
|
|||||||
<group-card
|
<group-card
|
||||||
v-else
|
v-else
|
||||||
:group="(activeElement as IGroup)"
|
:group="(activeElement as IGroup)"
|
||||||
:has-border="false"
|
mode="column"
|
||||||
view-mode="column"
|
|
||||||
:isRemoteGroup="activeElement.__typename === 'GroupResult'"
|
:isRemoteGroup="activeElement.__typename === 'GroupResult'"
|
||||||
:isLoggedIn="isLoggedIn"
|
:isLoggedIn="isLoggedIn"
|
||||||
/>
|
/>
|
||||||
@ -34,8 +32,7 @@
|
|||||||
<event-card
|
<event-card
|
||||||
v-if="instanceOfIEvent(activeElement)"
|
v-if="instanceOfIEvent(activeElement)"
|
||||||
:event="(activeElement as IEvent)"
|
:event="(activeElement as IEvent)"
|
||||||
view-mode="column"
|
mode="column"
|
||||||
:has-border="false"
|
|
||||||
:options="{
|
:options="{
|
||||||
isRemoteEvent: activeElement.__typename === 'EventResult',
|
isRemoteEvent: activeElement.__typename === 'EventResult',
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
@ -44,8 +41,7 @@
|
|||||||
<group-card
|
<group-card
|
||||||
v-else
|
v-else
|
||||||
:group="(activeElement as IGroup)"
|
:group="(activeElement as IGroup)"
|
||||||
:has-border="false"
|
mode="column"
|
||||||
view-mode="column"
|
|
||||||
:isRemoteGroup="activeElement.__typename === 'GroupResult'"
|
:isRemoteGroup="activeElement.__typename === 'GroupResult'"
|
||||||
:isLoggedIn="isLoggedIn"
|
:isLoggedIn="isLoggedIn"
|
||||||
/>
|
/>
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
GEOCODING_AUTOCOMPLETE,
|
GEOCODING_AUTOCOMPLETE,
|
||||||
LOCATION,
|
LOCATION,
|
||||||
MAPS_TILES,
|
MAPS_TILES,
|
||||||
|
REGISTRATIONS,
|
||||||
RESOURCE_PROVIDERS,
|
RESOURCE_PROVIDERS,
|
||||||
RESTRICTIONS,
|
RESTRICTIONS,
|
||||||
ROUTING_TYPE,
|
ROUTING_TYPE,
|
||||||
@ -204,3 +205,28 @@ export function useSearchConfig() {
|
|||||||
const searchConfig = computed(() => result.value?.config.search);
|
const searchConfig = computed(() => result.value?.config.search);
|
||||||
return { searchConfig, error, loading, onResult };
|
return { searchConfig, error, loading, onResult };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useRegistrationConfig() {
|
||||||
|
const { result, error, loading, onResult } = useQuery<{
|
||||||
|
config: Pick<
|
||||||
|
IConfig,
|
||||||
|
"registrationsOpen" | "registrationsAllowlist" | "auth"
|
||||||
|
>;
|
||||||
|
}>(REGISTRATIONS, undefined, { fetchPolicy: "cache-only" });
|
||||||
|
|
||||||
|
const registrationsOpen = computed(
|
||||||
|
() => result.value?.config.registrationsOpen
|
||||||
|
);
|
||||||
|
const registrationsAllowlist = computed(
|
||||||
|
() => result.value?.config.registrationsAllowlist
|
||||||
|
);
|
||||||
|
const databaseLogin = computed(() => result.value?.config.auth.databaseLogin);
|
||||||
|
return {
|
||||||
|
registrationsOpen,
|
||||||
|
registrationsAllowlist,
|
||||||
|
databaseLogin,
|
||||||
|
error,
|
||||||
|
loading,
|
||||||
|
onResult,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -79,6 +79,7 @@ export const CONFIG = gql`
|
|||||||
}
|
}
|
||||||
auth {
|
auth {
|
||||||
ldap
|
ldap
|
||||||
|
databaseLogin
|
||||||
oauthProviders {
|
oauthProviders {
|
||||||
id
|
id
|
||||||
label
|
label
|
||||||
@ -386,6 +387,7 @@ export const LOGIN_CONFIG = gql`
|
|||||||
query LoginConfig {
|
query LoginConfig {
|
||||||
config {
|
config {
|
||||||
auth {
|
auth {
|
||||||
|
databaseLogin
|
||||||
oauthProviders {
|
oauthProviders {
|
||||||
id
|
id
|
||||||
label
|
label
|
||||||
@ -444,3 +446,15 @@ export const SEARCH_CONFIG = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const REGISTRATIONS = gql`
|
||||||
|
query Registrations {
|
||||||
|
config {
|
||||||
|
registrationsOpen
|
||||||
|
registrationsAllowlist
|
||||||
|
auth {
|
||||||
|
databaseLogin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
@ -1408,5 +1408,14 @@
|
|||||||
"Most recently published": "Most recently published",
|
"Most recently published": "Most recently published",
|
||||||
"Least recently published": "Least recently published",
|
"Least recently published": "Least recently published",
|
||||||
"With the most participants": "With the most participants",
|
"With the most participants": "With the most participants",
|
||||||
"Number of members": "Number of members"
|
"Number of members": "Number of members",
|
||||||
}
|
"More options": "More options",
|
||||||
|
"Reported by someone anonymously": "Reported by someone anonymously",
|
||||||
|
"Back to homepage": "Back to homepage",
|
||||||
|
"Category list": "Category list",
|
||||||
|
"No categories with public upcoming events on this instance were found.": "No categories with public upcoming events on this instance were found.",
|
||||||
|
"Theme": "Theme",
|
||||||
|
"Adapt to system theme": "Adapt to system theme",
|
||||||
|
"Light": "Light",
|
||||||
|
"Dark": "Dark"
|
||||||
|
}
|
@ -1406,5 +1406,14 @@
|
|||||||
"{timezoneLongName} ({timezoneShortName})": "{timezoneLongName} ({timezoneShortName})",
|
"{timezoneLongName} ({timezoneShortName})": "{timezoneLongName} ({timezoneShortName})",
|
||||||
"{title} ({count} todos)": "{title} ({count} todos)",
|
"{title} ({count} todos)": "{title} ({count} todos)",
|
||||||
"{username} was invited to {group}": "{username} a été invité à {group}",
|
"{username} was invited to {group}": "{username} a été invité à {group}",
|
||||||
"© The OpenStreetMap Contributors": "© Les Contributeur⋅ices OpenStreetMap"
|
"© The OpenStreetMap Contributors": "© Les Contributeur⋅ices OpenStreetMap",
|
||||||
|
"More options": "Plus d'options",
|
||||||
|
"Reported by someone anonymously": "Signalé par quelqu'un anonymement",
|
||||||
|
"Back to homepage": "Retour à la page d'accueil",
|
||||||
|
"Category list": "Liste des catégories",
|
||||||
|
"No categories with public upcoming events on this instance were found.": "Aucune catégorie avec des événements publics à venir n'a été trouvée.",
|
||||||
|
"Theme": "Thème",
|
||||||
|
"Adapt to system theme": "S’adapter au thème du système",
|
||||||
|
"Light": "Clair",
|
||||||
|
"Dark": "Sombre"
|
||||||
}
|
}
|
||||||
|
@ -106,6 +106,7 @@ export interface IConfig {
|
|||||||
version: string;
|
version: string;
|
||||||
auth: {
|
auth: {
|
||||||
ldap: boolean;
|
ldap: boolean;
|
||||||
|
databaseLogin: boolean;
|
||||||
oauthProviders: IOAuthProvider[];
|
oauthProviders: IOAuthProvider[];
|
||||||
};
|
};
|
||||||
uploadLimits: {
|
uploadLimits: {
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
|
|
||||||
<div class="flex flex-wrap gap-4">
|
<div class="flex flex-wrap gap-4">
|
||||||
<o-field
|
<o-field
|
||||||
v-if="eventCategories"
|
v-if="orderedCategories"
|
||||||
:label="t('Category')"
|
:label="t('Category')"
|
||||||
label-for="categoryField"
|
label-for="categoryField"
|
||||||
class="w-full md:max-w-fit"
|
class="w-full md:max-w-fit"
|
||||||
@ -45,7 +45,7 @@
|
|||||||
expanded
|
expanded
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
v-for="category in eventCategories"
|
v-for="category in orderedCategories"
|
||||||
:value="category.id"
|
:value="category.id"
|
||||||
:key="category.id"
|
:key="category.id"
|
||||||
>
|
>
|
||||||
@ -595,6 +595,7 @@ import { Notifier } from "@/plugins/notifier";
|
|||||||
import { useHead } from "@vueuse/head";
|
import { useHead } from "@vueuse/head";
|
||||||
import { useProgrammatic } from "@oruga-ui/oruga-next";
|
import { useProgrammatic } from "@oruga-ui/oruga-next";
|
||||||
import type { Locale } from "date-fns";
|
import type { Locale } from "date-fns";
|
||||||
|
import sortBy from "lodash/sortBy";
|
||||||
|
|
||||||
const DEFAULT_LIMIT_NUMBER_OF_PLACES = 10;
|
const DEFAULT_LIMIT_NUMBER_OF_PLACES = 10;
|
||||||
|
|
||||||
@ -1331,6 +1332,11 @@ watch(group, () => {
|
|||||||
event.value.visibility = EventVisibility.PUBLIC;
|
event.value.visibility = EventVisibility.PUBLIC;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const orderedCategories = computed(() => {
|
||||||
|
if (!eventCategories.value) return undefined;
|
||||||
|
return sortBy(eventCategories.value, ["label"]);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@ -193,7 +193,7 @@
|
|||||||
<template #options>
|
<template #options>
|
||||||
<fieldset class="flex flex-col">
|
<fieldset class="flex flex-col">
|
||||||
<legend class="sr-only">{{ t("Categories") }}</legend>
|
<legend class="sr-only">{{ t("Categories") }}</legend>
|
||||||
<div v-for="category in eventCategories" :key="category.id">
|
<div v-for="category in orderedCategories" :key="category.id">
|
||||||
<input
|
<input
|
||||||
:id="category.id"
|
:id="category.id"
|
||||||
v-model="categoryOneOf"
|
v-model="categoryOneOf"
|
||||||
@ -692,6 +692,7 @@ import { IAddress } from "@/types/address.model";
|
|||||||
import { IConfig } from "@/types/config.model";
|
import { IConfig } from "@/types/config.model";
|
||||||
import { TypeNamed } from "@/types/apollo";
|
import { TypeNamed } from "@/types/apollo";
|
||||||
import { LatLngBounds } from "leaflet";
|
import { LatLngBounds } from "leaflet";
|
||||||
|
import lodashSortBy from "lodash/sortBy";
|
||||||
|
|
||||||
const EventMarkerMap = defineAsyncComponent(
|
const EventMarkerMap = defineAsyncComponent(
|
||||||
() => import("@/components/Search/EventMarkerMap.vue")
|
() => import("@/components/Search/EventMarkerMap.vue")
|
||||||
@ -825,6 +826,11 @@ const props = defineProps<{
|
|||||||
const { features } = useFeatures();
|
const { features } = useFeatures();
|
||||||
const { eventCategories } = useEventCategories();
|
const { eventCategories } = useEventCategories();
|
||||||
|
|
||||||
|
const orderedCategories = computed(() => {
|
||||||
|
if (!eventCategories.value) return [];
|
||||||
|
return lodashSortBy(eventCategories.value, ["label"]);
|
||||||
|
});
|
||||||
|
|
||||||
const searchEvents = computed(() => searchElementsResult.value?.searchEvents);
|
const searchEvents = computed(() => searchElementsResult.value?.searchEvents);
|
||||||
const searchGroups = computed(() => searchElementsResult.value?.searchGroups);
|
const searchGroups = computed(() => searchElementsResult.value?.searchGroups);
|
||||||
|
|
||||||
|
@ -13,6 +13,36 @@
|
|||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
|
<o-field :label="t('Theme')" addonsClass="flex flex-col">
|
||||||
|
<o-field>
|
||||||
|
<o-checkbox v-model="systemTheme">{{
|
||||||
|
t("Adapt to system theme")
|
||||||
|
}}</o-checkbox>
|
||||||
|
</o-field>
|
||||||
|
<o-field>
|
||||||
|
<fieldset>
|
||||||
|
<legend class="sr-only">{{ t("Theme") }}</legend>
|
||||||
|
<o-radio
|
||||||
|
:class="{ 'border-mbz-bluegreen border-2': theme === 'light' }"
|
||||||
|
class="p-4 bg-white text-zinc-800 rounded-md mt-2 mr-2"
|
||||||
|
:disabled="systemTheme"
|
||||||
|
v-model="theme"
|
||||||
|
name="theme"
|
||||||
|
native-value="light"
|
||||||
|
>{{ t("Light") }}</o-radio
|
||||||
|
>
|
||||||
|
<o-radio
|
||||||
|
:class="{ 'border-mbz-bluegreen border-2': theme === 'dark' }"
|
||||||
|
class="p-4 bg-zinc-800 rounded-md text-white mt-2 ml-2"
|
||||||
|
:disabled="systemTheme"
|
||||||
|
v-model="theme"
|
||||||
|
name="theme"
|
||||||
|
native-value="dark"
|
||||||
|
>{{ t("Dark") }}</o-radio
|
||||||
|
>
|
||||||
|
</fieldset>
|
||||||
|
</o-field>
|
||||||
|
</o-field>
|
||||||
<o-field :label="t('Language')" label-for="setting-language">
|
<o-field :label="t('Language')" label-for="setting-language">
|
||||||
<o-select
|
<o-select
|
||||||
:loading="loadingTimezones || loadingUserSettings"
|
:loading="loadingTimezones || loadingUserSettings"
|
||||||
@ -65,7 +95,6 @@
|
|||||||
<full-address-auto-complete
|
<full-address-auto-complete
|
||||||
v-if="loggedUser?.settings"
|
v-if="loggedUser?.settings"
|
||||||
:resultType="AddressSearchType.ADMINISTRATIVE"
|
:resultType="AddressSearchType.ADMINISTRATIVE"
|
||||||
:doGeoLocation="false"
|
|
||||||
v-model="address"
|
v-model="address"
|
||||||
:default-text="address?.description"
|
:default-text="address?.description"
|
||||||
id="setting-city"
|
id="setting-city"
|
||||||
@ -120,7 +149,7 @@ import { Address, IAddress } from "@/types/address.model";
|
|||||||
import { useTimezones } from "@/composition/apollo/config";
|
import { useTimezones } from "@/composition/apollo/config";
|
||||||
import { useUserSettings } from "@/composition/apollo/user";
|
import { useUserSettings } from "@/composition/apollo/user";
|
||||||
import { useHead } from "@vueuse/head";
|
import { useHead } from "@vueuse/head";
|
||||||
import { computed, defineAsyncComponent } from "vue";
|
import { computed, defineAsyncComponent, ref, watch } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useMutation } from "@vue/apollo-composable";
|
import { useMutation } from "@vue/apollo-composable";
|
||||||
|
|
||||||
@ -140,6 +169,44 @@ useHead({
|
|||||||
|
|
||||||
// langs: Record<string, string> = langs;
|
// langs: Record<string, string> = langs;
|
||||||
|
|
||||||
|
const theme = ref(localStorage.getItem("theme"));
|
||||||
|
const systemTheme = ref(!("theme" in localStorage));
|
||||||
|
|
||||||
|
watch(systemTheme, (newSystemTheme) => {
|
||||||
|
console.debug("changing system theme", newSystemTheme);
|
||||||
|
if (newSystemTheme) {
|
||||||
|
theme.value = null;
|
||||||
|
localStorage.removeItem("theme");
|
||||||
|
} else {
|
||||||
|
theme.value = "light";
|
||||||
|
localStorage.setItem("theme", theme.value);
|
||||||
|
}
|
||||||
|
changeTheme();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(theme, (newTheme) => {
|
||||||
|
console.debug("changing theme value", newTheme);
|
||||||
|
if (newTheme) {
|
||||||
|
localStorage.setItem("theme", newTheme);
|
||||||
|
}
|
||||||
|
changeTheme();
|
||||||
|
});
|
||||||
|
|
||||||
|
const changeTheme = () => {
|
||||||
|
console.debug("changing theme to apply");
|
||||||
|
if (
|
||||||
|
localStorage.getItem("theme") === "dark" ||
|
||||||
|
(!("theme" in localStorage) &&
|
||||||
|
window.matchMedia("(prefers-color-scheme: dark)").matches)
|
||||||
|
) {
|
||||||
|
console.debug("applying dark theme");
|
||||||
|
document.documentElement.classList.add("dark");
|
||||||
|
} else {
|
||||||
|
console.debug("removing dark theme");
|
||||||
|
document.documentElement.classList.remove("dark");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const selectedTimezone = computed({
|
const selectedTimezone = computed({
|
||||||
get() {
|
get() {
|
||||||
if (loggedUser.value?.settings?.timezone) {
|
if (loggedUser.value?.settings?.timezone) {
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
>
|
>
|
||||||
{{ error }}
|
{{ error }}
|
||||||
</o-notification>
|
</o-notification>
|
||||||
<form @submit="loginAction">
|
<form @submit="loginAction" v-if="config?.auth.databaseLogin">
|
||||||
<o-field
|
<o-field
|
||||||
:label="t('Email')"
|
:label="t('Email')"
|
||||||
label-for="email"
|
label-for="email"
|
||||||
@ -81,13 +81,6 @@
|
|||||||
</p>
|
</p>
|
||||||
<!-- <o-loading :is-full-page="false" v-model="submitted" /> -->
|
<!-- <o-loading :is-full-page="false" v-model="submitted" /> -->
|
||||||
|
|
||||||
<div
|
|
||||||
class="control"
|
|
||||||
v-if="config && config?.auth.oauthProviders.length > 0"
|
|
||||||
>
|
|
||||||
<auth-providers :oauthProviders="config.auth.oauthProviders" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-wrap gap-2 mt-3">
|
<div class="flex flex-wrap gap-2 mt-3">
|
||||||
<o-button
|
<o-button
|
||||||
tag="router-link"
|
tag="router-link"
|
||||||
@ -107,7 +100,7 @@
|
|||||||
}"
|
}"
|
||||||
>{{ t("Didn't receive the instructions?") }}</o-button
|
>{{ t("Didn't receive the instructions?") }}</o-button
|
||||||
>
|
>
|
||||||
<p class="control" v-if="config && config.registrationsOpen">
|
<p class="control" v-if="canRegister">
|
||||||
<o-button
|
<o-button
|
||||||
tag="router-link"
|
tag="router-link"
|
||||||
variant="text"
|
variant="text"
|
||||||
@ -123,6 +116,9 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<div v-if="config && config?.auth.oauthProviders.length > 0">
|
||||||
|
<auth-providers :oauthProviders="config.auth.oauthProviders" />
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -162,11 +158,21 @@ const route = useRoute();
|
|||||||
const { currentUser } = useCurrentUserClient();
|
const { currentUser } = useCurrentUserClient();
|
||||||
|
|
||||||
const { result: configResult } = useQuery<{
|
const { result: configResult } = useQuery<{
|
||||||
config: Pick<IConfig, "auth" | "registrationsOpen">;
|
config: Pick<
|
||||||
|
IConfig,
|
||||||
|
"auth" | "registrationsOpen" | "registrationsAllowlist"
|
||||||
|
>;
|
||||||
}>(LOGIN_CONFIG);
|
}>(LOGIN_CONFIG);
|
||||||
|
|
||||||
const config = computed(() => configResult.value?.config);
|
const config = computed(() => configResult.value?.config);
|
||||||
|
|
||||||
|
const canRegister = computed(() => {
|
||||||
|
return (
|
||||||
|
(config.value?.registrationsOpen || config.value?.registrationsAllowlist) &&
|
||||||
|
config.value?.auth.databaseLogin
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const errors = ref<string[]>([]);
|
const errors = ref<string[]>([]);
|
||||||
const submitted = ref(false);
|
const submitted = ref(false);
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container mx-auto pt-6">
|
<div class="container mx-auto py-6">
|
||||||
<section class="">
|
<section class="">
|
||||||
<h1>
|
<h1>
|
||||||
{{
|
{{
|
||||||
@ -123,7 +123,7 @@
|
|||||||
/>
|
/>
|
||||||
</o-field>
|
</o-field>
|
||||||
|
|
||||||
<div class="flex items-start mb-6">
|
<div class="flex items-start mb-6 mt-2">
|
||||||
<div class="flex items-center h-5">
|
<div class="flex items-center h-5">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@ -155,7 +155,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="create-account control has-text-centered">
|
<p>
|
||||||
<o-button
|
<o-button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
size="large"
|
size="large"
|
||||||
@ -166,19 +166,19 @@
|
|||||||
</o-button>
|
</o-button>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="control has-text-centered">
|
<p class="my-6">
|
||||||
<router-link
|
<o-button
|
||||||
class="button is-text"
|
tag="router-link"
|
||||||
|
variant="text"
|
||||||
:to="{
|
:to="{
|
||||||
name: RouteName.RESEND_CONFIRMATION,
|
name: RouteName.RESEND_CONFIRMATION,
|
||||||
params: { email: credentials.email },
|
params: { email: credentials.email },
|
||||||
}"
|
}"
|
||||||
>{{ t("Didn't receive the instructions?") }}</router-link
|
>{{ t("Didn't receive the instructions?") }}</o-button
|
||||||
>
|
>
|
||||||
</p>
|
<o-button
|
||||||
<p class="control has-text-centered">
|
tag="router-link"
|
||||||
<router-link
|
variant="text"
|
||||||
class="button is-text"
|
|
||||||
:to="{
|
:to="{
|
||||||
name: RouteName.LOGIN,
|
name: RouteName.LOGIN,
|
||||||
params: {
|
params: {
|
||||||
@ -186,7 +186,7 @@
|
|||||||
password: credentials.password,
|
password: credentials.password,
|
||||||
},
|
},
|
||||||
}"
|
}"
|
||||||
>{{ t("Login") }}</router-link
|
>{{ t("Login") }}</o-button
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@ -252,13 +252,13 @@ const title = computed((): string => {
|
|||||||
if (config.value) {
|
if (config.value) {
|
||||||
return t("Register an account on {instanceName}!", {
|
return t("Register an account on {instanceName}!", {
|
||||||
instanceName: config.value?.name,
|
instanceName: config.value?.name,
|
||||||
}) as string;
|
});
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
});
|
});
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
title: title.value,
|
title: () => title.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { onDone, onError, mutate } = useMutation(CREATE_USER);
|
const { onDone, onError, mutate } = useMutation(CREATE_USER);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
content: ["./public/**/*.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
|
content: ["./public/**/*.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
|
||||||
|
darkMode: "class",
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
|
@ -14,7 +14,9 @@ test("Login has everything we need", async ({ page }) => {
|
|||||||
hasText: "Didn't receive the instructions?",
|
hasText: "Didn't receive the instructions?",
|
||||||
});
|
});
|
||||||
|
|
||||||
const registerLink = page.locator("a", { hasText: "Create an account" });
|
const registerLink = page.locator("a > span > span", {
|
||||||
|
hasText: "Create an account",
|
||||||
|
});
|
||||||
|
|
||||||
await expect(forgotPasswordLink).toBeVisible();
|
await expect(forgotPasswordLink).toBeVisible();
|
||||||
await expect(reAskInstructionsLink).toBeVisible();
|
await expect(reAskInstructionsLink).toBeVisible();
|
||||||
|
@ -24,7 +24,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Address do
|
|||||||
}
|
}
|
||||||
|
|
||||||
res =
|
res =
|
||||||
if is_nil(object["address"]) do
|
if is_nil(object["address"]) or not is_map(object["address"]) do
|
||||||
res
|
res
|
||||||
else
|
else
|
||||||
Map.merge(res, %{
|
Map.merge(res, %{
|
||||||
|
@ -156,6 +156,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Config do
|
|||||||
federating: Config.instance_federating(),
|
federating: Config.instance_federating(),
|
||||||
auth: %{
|
auth: %{
|
||||||
ldap: Config.ldap_enabled?(),
|
ldap: Config.ldap_enabled?(),
|
||||||
|
database_login:
|
||||||
|
Application.get_env(:mobilizon, :instance) |> get_in([:disable_database_login]) == false,
|
||||||
oauth_providers: Config.oauth_consumer_strategies()
|
oauth_providers: Config.oauth_consumer_strategies()
|
||||||
},
|
},
|
||||||
upload_limits: %{
|
upload_limits: %{
|
||||||
|
@ -305,6 +305,7 @@ defmodule Mobilizon.GraphQL.Schema.ConfigType do
|
|||||||
"""
|
"""
|
||||||
object :auth do
|
object :auth do
|
||||||
field(:ldap, :boolean, description: "Whether or not LDAP auth is enabled")
|
field(:ldap, :boolean, description: "Whether or not LDAP auth is enabled")
|
||||||
|
field(:database_login, :boolean, description: "Whether or not database login is enabled")
|
||||||
field(:oauth_providers, list_of(:oauth_provider), description: "List of oauth providers")
|
field(:oauth_providers, list_of(:oauth_provider), description: "List of oauth providers")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -7,6 +7,13 @@
|
|||||||
<link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png" sizes="152x152" />
|
<link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png" sizes="152x152" />
|
||||||
<link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color={theme_color()} />
|
<link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color={theme_color()} />
|
||||||
<meta name="theme-color" content={theme_color()} />
|
<meta name="theme-color" content={theme_color()} />
|
||||||
|
<script>
|
||||||
|
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||||
|
document.documentElement.classList.add('dark')
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove('dark')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<%= if is_root(assigns) do %>
|
<%= if is_root(assigns) do %>
|
||||||
<link rel="preload" href="/img/shape-1.svg" as="image" />
|
<link rel="preload" href="/img/shape-1.svg" as="image" />
|
||||||
<link rel="preload" href="/img/shape-2.svg" as="image" />
|
<link rel="preload" href="/img/shape-2.svg" as="image" />
|
||||||
|
Loading…
Reference in New Issue
Block a user