all developments of milestone 1

This commit is contained in:
setop
2024-04-10 12:36:21 +00:00
parent a78dc261e5
commit 7030d56864
266 changed files with 5391 additions and 2609 deletions

View File

@@ -168,7 +168,7 @@ const props = withDefaults(
defineProps<{
modelValue: IComment;
currentActor: IPerson;
canReport: boolean;
canReport?: boolean;
}>(),
{ canReport: false }
);

View File

@@ -2,21 +2,6 @@
<div class="container mx-auto" id="error-wrapper">
<div class="">
<section>
<div class="text-center">
<picture>
<source
:srcset="`/img/pics/error-480w.webp 1x, /img/pics/error-1024w.webp 2x`"
type="image/webp"
/>
<img
:src="`/img/pics/error-480w.webp`"
alt=""
width="480"
height="312"
loading="lazy"
/>
</picture>
</div>
<o-notification variant="danger" class="">
<h1>
{{
@@ -108,7 +93,7 @@ import { IAnalyticsConfig, IConfig } from "@/types/config.model";
import { computed, defineAsyncComponent, ref } from "vue";
import { useQuery, useQueryLoading } from "@vue/apollo-composable";
import { useI18n } from "vue-i18n";
import { useHead } from "@unhead/vue";
import { useHead } from "@/utils/head";
import { useAnalytics } from "@/composition/apollo/config";
import { INSTANCE_NAME } from "@/graphql/config";
const SentryFeedback = defineAsyncComponent(

View File

@@ -4,7 +4,11 @@
:class="{ small }"
:style="`--small: ${smallStyle}`"
>
<div class="datetime-container-header" />
<div class="datetime-container-header">
<time :datetime="dateObj.toISOString()" class="weekday">{{
weekday
}}</time>
</div>
<div class="datetime-container-content">
<time :datetime="dateObj.toISOString()" class="day block font-semibold">{{
day
@@ -38,6 +42,10 @@ const day = computed<string>(() =>
dateObj.value.toLocaleString(undefined, { day: "numeric" })
);
const weekday = computed<string>(() =>
dateObj.value.toLocaleString(undefined, { weekday: "short" })
);
const smallStyle = computed<string>(() => (props.small ? "1.2" : "2"));
</script>
@@ -51,6 +59,12 @@ div.datetime-container {
height: calc(10px * var(--small));
background: #f3425f;
}
.datetime-container-header .weekday {
font-size: calc(9px * var(--small));
font-weight: bold;
vertical-align: top;
line-height: calc(9px * var(--small));
}
.datetime-container-content {
height: calc(30px * var(--small));
}

View File

@@ -23,7 +23,10 @@
<div class="flex flex-col gap-1 mt-1">
<p
class="inline-flex gap-2 ml-auto"
v-if="event.joinOptions !== EventJoinOptions.EXTERNAL"
v-if="
event.joinOptions !== EventJoinOptions.EXTERNAL &&
!event.options.hideNumberOfParticipants
"
>
<TicketConfirmationOutline />
<router-link

View File

@@ -12,6 +12,31 @@
class="rounded-lg"
:class="{ 'sm:w-full sm:max-w-[20rem]': mode === 'row' }"
>
<div
class="-mt-3 h-0 mb-3 ltr:ml-0 rtl:mr-0 block relative z-10"
:class="{
'sm:hidden': mode === 'row',
'calendar-simple': !isDifferentBeginsEndsDate,
'calendar-double': isDifferentBeginsEndsDate,
}"
>
<date-calendar-icon
:small="true"
v-if="!mergedOptions.hideDate"
:date="event.beginsOn.toString()"
/>
<MenuDown
:small="true"
class="left-3 relative"
v-if="!mergedOptions.hideDate && isDifferentBeginsEndsDate"
/>
<date-calendar-icon
:small="true"
v-if="!mergedOptions.hideDate && isDifferentBeginsEndsDate"
:date="event.endsOn?.toString()"
/>
</div>
<figure class="block relative pt-40">
<lazy-image-wrapper
:picture="event.picture"
@@ -49,20 +74,29 @@
<div class="p-2 flex-auto" :class="{ 'sm:flex-1': mode === 'row' }">
<div class="relative flex flex-col h-full">
<div
class="-mt-3 h-0 flex mb-3 ltr:ml-0 rtl:mr-0 items-end self-start"
class="-mt-3 h-0 flex mb-3 ltr:ml-0 rtl:mr-0 items-end self-end"
:class="{ 'sm:hidden': mode === 'row' }"
>
<date-calendar-icon
<start-time-icon
:small="true"
v-if="!mergedOptions.hideDate"
v-if="!mergedOptions.hideDate && event.options.showStartTime"
:date="event.beginsOn.toString()"
/>
</div>
<span
class="text-gray-700 dark:text-white font-semibold hidden"
:class="{ 'sm:block': mode === 'row' }"
v-if="!isDifferentBeginsEndsDate"
>{{ formatDateTimeWithCurrentLocale }}</span
>
<span
class="text-gray-700 dark:text-white font-semibold hidden"
:class="{ 'sm:block': mode === 'row' }"
v-if="isDifferentBeginsEndsDate"
>{{ formatBeginsOnDateWithCurrentLocale }}
<ArrowRightThin :small="true" style="display: ruby" />
{{ formatEndsOnDateWithCurrentLocale }}</span
>
<div class="w-full flex flex-col justify-between h-full">
<h2
class="mt-0 mb-2 text-2xl line-clamp-3 font-bold text-violet-3 dark:text-white"
@@ -152,6 +186,16 @@
</div>
</LinkOrRouterLink>
</template>
<style scoped>
.calendar-simple {
bottom: -117px;
left: 5px;
}
.calendar-double {
bottom: -45px;
left: 5px;
}
</style>
<script lang="ts" setup>
import {
@@ -161,6 +205,9 @@ import {
organizerAvatarUrl,
} from "@/types/event.model";
import DateCalendarIcon from "@/components/Event/DateCalendarIcon.vue";
import StartTimeIcon from "@/components/Event/StartTimeIcon.vue";
import ArrowRightThin from "vue-material-design-icons/ArrowRightThin.vue";
import MenuDown from "vue-material-design-icons/MenuDown.vue";
import LazyImageWrapper from "@/components/Image/LazyImageWrapper.vue";
import { EventStatus } from "@/types/enums";
import RouteName from "../../router/name";
@@ -170,7 +217,7 @@ import { computed, inject } from "vue";
import MobilizonTag from "@/components/TagElement.vue";
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
import Video from "vue-material-design-icons/Video.vue";
import { formatDateTimeForEvent } from "@/utils/datetime";
import { formatDateForEvent, formatDateTimeForEvent } from "@/utils/datetime";
import type { Locale } from "date-fns";
import LinkOrRouterLink from "../core/LinkOrRouterLink.vue";
import { useI18n } from "vue-i18n";
@@ -212,6 +259,28 @@ const actorAvatarURL = computed<string | null>(() =>
const dateFnsLocale = inject<Locale>("dateFnsLocale");
const isDifferentBeginsEndsDate = computed(() => {
if (!dateFnsLocale) return;
const beginsOnStr = formatDateForEvent(
new Date(props.event.beginsOn),
dateFnsLocale
);
const endsOnStr = props.event.endsOn
? formatDateForEvent(new Date(props.event.endsOn), dateFnsLocale)
: null;
return endsOnStr && endsOnStr != beginsOnStr;
});
const formatBeginsOnDateWithCurrentLocale = computed(() => {
if (!dateFnsLocale) return;
return formatDateForEvent(new Date(props.event.beginsOn), dateFnsLocale);
});
const formatEndsOnDateWithCurrentLocale = computed(() => {
if (!dateFnsLocale) return;
return formatDateForEvent(new Date(props.event.endsOn), dateFnsLocale);
});
const formatDateTimeWithCurrentLocale = computed(() => {
if (!dateFnsLocale) return;
return formatDateTimeForEvent(new Date(props.event.beginsOn), dateFnsLocale);

View File

@@ -0,0 +1,51 @@
<template>
<div
class="starttime-container flex flex-col rounded-lg text-center justify-center overflow-hidden items-stretch bg-white dark:bg-gray-700 text-violet-3 dark:text-white"
:class="{ small }"
:style="`--small: ${smallStyle}`"
>
<div class="starttime-container-content font-semibold">
<Clock class="clock-icon" /><time :datetime="dateObj.toISOString()">{{
time
}}</time>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed } from "vue";
import Clock from "vue-material-design-icons/ClockTimeTenOutline.vue";
const props = withDefaults(
defineProps<{
date: string;
small?: boolean;
}>(),
{ small: false }
);
const dateObj = computed<Date>(() => new Date(props.date));
const time = computed<string>(() =>
dateObj.value.toLocaleTimeString(undefined, {
hour: "2-digit",
minute: "2-digit",
})
);
const smallStyle = computed<string>(() => (props.small ? "0.9" : "2"));
</script>
<style lang="scss" scoped>
div.starttime-container {
width: auto;
box-shadow: 0 0 12px rgba(0, 0, 0, 0.2);
padding: 0.25rem 0.25rem;
font-size: calc(1rem * var(--small));
}
.clock-icon {
vertical-align: middle;
padding-right: 0.2rem;
display: inline-block;
}
</style>

View File

@@ -14,7 +14,8 @@
</p>
</template>
<o-taginput
v-model="tagsStrings"
:modelValue="tagsStrings"
@update:modelValue="updateTags"
:data="filteredTags"
:allow-autocomplete="true"
:allow-new="true"
@@ -34,7 +35,7 @@
<script lang="ts" setup>
import differenceBy from "lodash/differenceBy";
import { ITag } from "../../types/tag.model";
import { computed, onBeforeMount, ref } from "vue";
import { computed, onBeforeMount, ref, watch } from "vue";
import HelpCircleOutline from "vue-material-design-icons/HelpCircleOutline.vue";
import { useFetchTags } from "@/composition/apollo/tags";
import { FILTER_TAGS } from "@/graphql/tags";
@@ -44,6 +45,10 @@ const props = defineProps<{
modelValue: ITag[];
}>();
const propsValue = computed(() => props.modelValue);
const tagsStrings = ref<string[]>([]);
const emit = defineEmits(["update:modelValue"]);
const text = ref("");
@@ -77,7 +82,7 @@ const getFilteredTags = async (newText: string): Promise<void> => {
};
const filteredTags = computed((): ITag[] => {
return differenceBy(tags.value, props.modelValue, "id").filter(
return differenceBy(tags.value, propsValue.value, "id").filter(
(option) =>
option.title.toString().toLowerCase().indexOf(text.value.toLowerCase()) >=
0 ||
@@ -86,19 +91,19 @@ const filteredTags = computed((): ITag[] => {
);
});
const tagsStrings = computed({
get(): string[] {
return props.modelValue.map((tag: ITag) => tag.title);
},
set(newTagsStrings: string[]) {
console.debug("tagsStrings", newTagsStrings);
const tagEntities = newTagsStrings.map((tag: string | ITag) => {
if (typeof tag !== "string") {
return tag;
}
return { title: tag, slug: tag } as ITag;
});
emit("update:modelValue", tagEntities);
},
watch(props.modelValue, (newValue, oldValue) => {
if (newValue != oldValue) {
tagsStrings.value = propsValue.value.map((tag: ITag) => tag.title);
}
});
const updateTags = (newTagsStrings: string[]) => {
const tagEntities = newTagsStrings.map((tag: string | ITag) => {
if (typeof tag !== "string") {
return tag;
}
return { title: tag, slug: tag } as ITag;
});
emit("update:modelValue", tagEntities);
};
</script>

View File

@@ -0,0 +1,228 @@
<template>
<FullCalendar
ref="calendarRef"
:options="calendarOptions"
class="agenda-view"
/>
<div v-if="listOfEventsByDate.date" class="my-4">
<b v-text="formatDateString(listOfEventsByDate.date)" />
<div v-if="listOfEventsByDate.events.length > 0">
<div
v-for="(event, index) in listOfEventsByDate.events"
v-bind:key="index"
>
<div class="scroll-ml-6 snap-center shrink-0 my-4">
<EventCard :event="event.event.extendedProps.event" />
</div>
</div>
</div>
<EmptyContent v-else icon="calendar" :inline="true">
<span>
{{ t("No events found") }}
</span>
</EmptyContent>
</div>
</template>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { locale } from "@/utils/i18n";
import { computed, ref } from "vue";
import { useLazyQuery } from "@vue/apollo-composable";
import { IEvent } from "@/types/event.model";
import { Paginate } from "@/types/paginate";
import { SEARCH_CALENDAR_EVENTS } from "@/graphql/search";
import FullCalendar from "@fullcalendar/vue3";
import { EventSegment } from "@fullcalendar/core";
import dayGridPlugin from "@fullcalendar/daygrid";
import interactionPlugin from "@fullcalendar/interaction";
import {
formatDateISOStringWithoutTime,
formatDateString,
} from "@/filters/datetime";
import EventCard from "../Event/EventCard.vue";
import EmptyContent from "../Utils/EmptyContent.vue";
const { t } = useI18n({ useScope: "global" });
const calendarRef = ref();
const lastSelectedDate = ref<string | undefined>(new Date().toISOString());
const listOfEventsByDate = ref<{ events: EventSegment[]; date?: string }>({
events: [],
date: undefined,
});
const showEventsByDate = (dateStr: string) => {
dateStr = formatDateISOStringWithoutTime(dateStr);
const moreLinkElement = document.querySelectorAll(
`td[data-date='${dateStr}'] a.fc-more-link`
)[0] as undefined | HTMLElement;
if (moreLinkElement) {
moreLinkElement.click();
} else {
listOfEventsByDate.value = {
events: [],
date: dateStr,
};
}
calendarRef.value.getApi().select(dateStr);
};
if (window.location.hash.length) {
lastSelectedDate.value = formatDateISOStringWithoutTime(
window.location.hash.replace("#_", "")
);
} else {
lastSelectedDate.value = formatDateISOStringWithoutTime(
new Date().toISOString()
);
}
const { load: searchEventsLoad, refetch: searchEventsRefetch } = useLazyQuery<{
searchEvents: Paginate<IEvent>;
}>(SEARCH_CALENDAR_EVENTS);
const calendarOptions = computed((): object => {
return {
plugins: [dayGridPlugin, interactionPlugin],
initialView: "dayGridMonth",
initialDate: lastSelectedDate.value,
events: async (
info: { start: Date; end: Date; startStr: string; endStr: string },
successCallback: (arg: object[]) => unknown,
failureCallback: (err: string) => unknown
) => {
const queryVars = {
limit: 999,
beginsOn: info.start,
endsOn: info.end,
};
const result =
(await searchEventsLoad(undefined, queryVars)) ||
(await searchEventsRefetch(queryVars))?.data;
if (!result) {
failureCallback("failed to fetch calendar events");
return;
}
successCallback(
(result.searchEvents.elements ?? []).map((event: IEvent) => {
return {
id: event.id,
title: event.title,
start: event.beginsOn,
end: event.endsOn,
startStr: event.beginsOn,
endStr: event.endsOn,
url: event.url,
extendedProps: {
event: event,
},
};
})
);
},
nextDayThreshold: "09:00:00",
dayMaxEventRows: 0,
moreLinkClassNames: "bg-mbz-yellow dark:bg-mbz-purple dark:text-white",
moreLinkContent: (arg: { num: number; text: string }) => {
return "+" + arg.num.toString();
},
contentHeight: "auto",
eventClassNames: "line-clamp-3 bg-mbz-yellow dark:bg-mbz-purple",
headerToolbar: {
left: "prev,next,customTodayButton",
center: "",
right: "title",
},
locale: locale,
firstDay: 1,
buttonText: {
today: t("Today"),
month: t("Month"),
week: t("Week"),
day: t("Day"),
list: t("List"),
},
customButtons: {
customTodayButton: {
text: t("Today"),
click: () => {
calendarRef.value.getApi().today();
lastSelectedDate.value = formatDateISOStringWithoutTime(
new Date().toISOString()
);
},
},
},
dateClick: (info: { dateStr: string }) => {
showEventsByDate(info.dateStr);
},
moreLinkClick: (info: {
date: Date;
allSegs: EventSegment[];
hiddenSegs: EventSegment[];
jsEvent: object;
}) => {
listOfEventsByDate.value = {
events: info.allSegs,
date: info.date.toISOString(),
};
if (info.allSegs.length) {
window.location.hash =
"_" + formatDateISOStringWithoutTime(info.date.toISOString());
}
return "none";
},
moreLinkDidMount: (arg: { el: Element }) => {
if (
lastSelectedDate.value &&
arg.el.closest(`td[data-date='${lastSelectedDate.value}']`)
) {
showEventsByDate(lastSelectedDate.value);
lastSelectedDate.value = undefined;
}
},
};
});
</script>
<style>
.agenda-view .fc-button {
font-size: 0.8rem !important;
}
.agenda-view .fc-toolbar-title {
font-size: 1rem !important;
}
.agenda-view .fc-daygrid-day-events {
min-height: 1.1rem !important;
margin-bottom: 0.2rem !important;
margin-left: 0.1rem !important;
}
.agenda-view .fc-more-link {
pointer-events: none !important;
}
.clock-icon {
display: inline-block;
vertical-align: middle;
}
.time {
font-size: 0.95rem !important;
}
</style>

View File

@@ -0,0 +1,104 @@
<template>
<FullCalendar ref="calendarRef" :options="calendarOptions">
<template v-slot:eventContent="arg">
<span
class="text-violet-3 dark:text-white font-bold m-2"
:title="arg.event.title"
>
{{ arg.event.title }}
</span>
</template>
</FullCalendar>
</template>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { locale } from "@/utils/i18n";
import { computed, ref } from "vue";
import { useLazyQuery } from "@vue/apollo-composable";
import { IEvent } from "@/types/event.model";
import { Paginate } from "@/types/paginate";
import { SEARCH_CALENDAR_EVENTS } from "@/graphql/search";
import FullCalendar from "@fullcalendar/vue3";
import dayGridPlugin from "@fullcalendar/daygrid";
import interactionPlugin from "@fullcalendar/interaction";
const calendarRef = ref();
const { t } = useI18n({ useScope: "global" });
const { load: searchEventsLoad, refetch: searchEventsRefetch } = useLazyQuery<{
searchEvents: Paginate<IEvent>;
}>(SEARCH_CALENDAR_EVENTS);
const calendarOptions = computed((): object => {
return {
plugins: [dayGridPlugin, interactionPlugin],
initialView: "dayGridMonth",
events: async (
info: { start: Date; end: Date; startStr: string; endStr: string },
successCallback: (arg: object[]) => unknown,
failureCallback: (err: string) => unknown
) => {
const queryVars = {
limit: 999,
beginsOn: info.start,
endsOn: info.end,
};
const result =
(await searchEventsLoad(undefined, queryVars)) ||
(await searchEventsRefetch(queryVars))?.data;
if (!result) {
failureCallback("failed to fetch calendar events");
return;
}
successCallback(
(result.searchEvents.elements ?? []).map((event: IEvent) => {
return {
id: event.id,
title: event.title,
start: event.beginsOn,
end: event.endsOn,
startStr: event.beginsOn,
endStr: event.endsOn,
url: `/events/${event.uuid}`,
extendedProps: {
event: event,
},
};
})
);
},
nextDayThreshold: "09:00:00",
dayMaxEventRows: 5,
moreLinkClassNames: "bg-mbz-yellow dark:bg-mbz-purple dark:text-white p-2",
moreLinkContent: (arg: { num: number; text: string }) => {
return "+" + arg.num.toString();
},
eventClassNames: "line-clamp-3 bg-mbz-yellow dark:bg-mbz-purple",
headerToolbar: {
left: "prev,next,today",
center: "title",
right: "dayGridWeek,dayGridMonth", // user can switch between the two
},
locale: locale,
firstDay: 1,
buttonText: {
today: t("Today"),
month: t("Month"),
week: t("Week"),
day: t("Day"),
list: t("List"),
},
};
});
</script>
<style>
.fc-popover-header {
color: black !important;
}
</style>

View File

@@ -16,7 +16,7 @@ import { useGroup } from "@/composition/apollo/group";
import { displayName } from "@/types/actor";
import { computed } from "vue";
import { useI18n } from "vue-i18n";
import { useHead } from "@unhead/vue";
import { useHead } from "@/utils/head";
const props = defineProps<{
preferredUsername: string;

View File

@@ -3,42 +3,12 @@
<h1 class="dark:text-white font-bold">
{{ config.slogan ?? t("Gather ⋅ Organize ⋅ Mobilize") }}
</h1>
<i18n-t
keypath="Join {instance}, a Mobilizon instance"
tag="p"
class="dark:text-white"
>
<template #instance>
<b>{{ config.name }}</b>
</template>
</i18n-t>
<p class="dark:text-white mb-2">{{ config.description }}</p>
<!-- We don't invite to find other instances yet -->
<!-- <p v-if="!config.registrationsOpen">
{{ t("This instance isn't opened to registrations, but you can register on other instances.") }}
</p>-->
<div class="flex flex-wrap gap-2 items-center">
<o-button
variant="primary"
tag="router-link"
:to="{ name: RouteName.REGISTER }"
v-if="config.registrationsOpen"
>{{ t("Create an account") }}</o-button
>
<!-- We don't invite to find other instances yet -->
<!-- <o-button v-else variant="link" tag="a" href="https://joinmastodon.org">{{ t('Find an instance') }}</o-button> -->
<router-link
:to="{ name: RouteName.ABOUT }"
class="py-2.5 px-5 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-violet-title focus:z-10 focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
>
{{ t("Learn more about {instance}", { instance: config.name }) }}
</router-link>
</div>
</section>
</template>
<script lang="ts" setup>
import { IConfig } from "@/types/config.model";
import RouteName from "@/router/name";
import { useI18n } from "vue-i18n";
defineProps<{

View File

@@ -11,8 +11,11 @@
<script lang="ts" setup>
import { computed } from "vue";
import { IMedia } from "@/types/media.model";
import { useDefaultPicture } from "@/composition/apollo/config";
import LazyImage from "../Image/LazyImage.vue";
const { defaultPicture } = useDefaultPicture();
const DEFAULT_CARD_URL = "/img/mobilizon_default_card.png";
const DEFAULT_BLURHASH = "MCHKI4El-P-U}+={R-WWoes,Iu-P=?R,xD";
const DEFAULT_WIDTH = 630;
@@ -38,6 +41,9 @@ const props = withDefaults(
const pictureOrDefault = computed(() => {
if (props.picture === null) {
if (defaultPicture?.value?.url) {
return defaultPicture.value;
}
return DEFAULT_PICTURE;
}
return {

View File

@@ -6,18 +6,7 @@
v-on="attrs"
>
<template #title>
{{ t("Last published events") }}
</template>
<template #subtitle>
<i18n-t
class="text-slate-700 dark:text-slate-300"
tag="p"
keypath="On {instance} and other federated instances"
>
<template #instance>
<b>{{ instanceName }}</b>
</template>
</i18n-t>
{{ t("Agenda") }}
</template>
<template #content>
<skeleton-event-result
@@ -69,8 +58,8 @@ const attrs = useAttrs();
const { result: resultEvents, loading: loadingEvents } = useQuery<{
events: Paginate<IEvent>;
}>(FETCH_EVENTS, {
orderBy: EventSortField.INSERTED_AT,
direction: SortDirection.DESC,
orderBy: EventSortField.BEGINS_ON,
direction: SortDirection.ASC,
});
const events = computed(
() => resultEvents.value?.events ?? { total: 0, elements: [] }

View File

@@ -1,6 +1,7 @@
<template>
<svg
class="bg-white dark:bg-zinc-900 dark:fill-white"
v-if="!instanceLogoUrl"
class="bg-white dark:bg-zinc-900 dark:fill-white max-h-12"
:class="{ 'bg-gray-900': invert }"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 248.16 46.78"
@@ -30,9 +31,14 @@
/>
</g>
</svg>
<img v-else alt="" class="max-h-12 w-auto" :src="instanceLogoUrl" />
</template>
<script lang="ts" setup>
import { useInstanceLogoUrl } from "@/composition/apollo/config";
const { instanceLogoUrl } = useInstanceLogoUrl();
withDefaults(
defineProps<{
invert?: boolean;

View File

@@ -3,9 +3,7 @@
class="bg-white border-gray-200 px-2 sm:px-4 py-2.5 dark:bg-zinc-900"
id="navbar"
>
<div
class="container mx-auto flex flex-wrap items-center mx-auto gap-2 sm:gap-4"
>
<div class="container mx-auto flex flex-wrap items-center gap-2 sm:gap-4">
<router-link
:to="{ name: RouteName.HOME }"
class="flex items-center"
@@ -181,34 +179,92 @@
<ul
class="flex flex-col md:flex-row md:space-x-8 mt-2 md:mt-0 md:font-lightbold"
>
<li v-if="currentActor?.id">
<search-fields
v-if="showMobileMenu"
class="m-auto w-auto"
v-model:search="search"
v-model:location="location"
/>
<li class="m-auto" v-if="islongEvents">
<router-link
:to="{
name: RouteName.SEARCH,
query: { contentType: 'SHORTEVENTS' },
}"
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"
>{{ t("Events") }}</router-link
>
</li>
<li class="m-auto" v-else>
<router-link
:to="{ name: RouteName.SEARCH, query: { contentType: 'EVENTS' } }"
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"
>{{ t("Events") }}</router-link
>
</li>
<li class="m-auto" v-if="islongEvents">
<router-link
:to="{
name: RouteName.SEARCH,
query: { contentType: 'LONGEVENTS' },
}"
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"
>{{ t("Activities") }}</router-link
>
</li>
<li class="m-auto">
<router-link
:to="{ name: RouteName.SEARCH, query: { contentType: 'GROUPS' } }"
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"
>{{ t("Groups") }}</router-link
>
</li>
<li class="m-auto">
<router-link
:to="{ name: RouteName.EVENT_CALENDAR }"
class="block relative 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"
>{{ t("Calendar")
}}<span class="absolute right-0 text-sm"
><br />(beta)</span
></router-link
>
</li>
<li class="m-auto" v-if="currentActor?.id">
<router-link
:to="{ name: RouteName.MY_EVENTS }"
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"
>{{ t("My events") }}</router-link
>
</li>
<li v-if="currentActor?.id">
<li class="m-auto" v-if="currentActor?.id">
<router-link
:to="{ name: RouteName.MY_GROUPS }"
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"
>{{ t("My groups") }}</router-link
>
</li>
<li v-if="!currentActor?.id">
<li class="m-auto" v-if="!currentActor?.id">
<router-link
:to="{ name: RouteName.LOGIN }"
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"
>{{ t("Login") }}</router-link
>
</li>
<li v-if="!currentActor?.id && canRegister">
<li class="m-auto" v-if="!currentActor?.id && canRegister">
<router-link
: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"
>{{ t("Register") }}</router-link
>
</li>
<search-fields
v-if="!showMobileMenu"
class="m-auto w-auto"
v-model:search="search"
v-model:location="location"
/>
</ul>
</div>
</div>
@@ -219,7 +275,7 @@
import MobilizonLogo from "@/components/MobilizonLogo.vue";
import { ICurrentUserRole } from "@/types/enums";
import { logout } from "../utils/auth";
import { IPerson, displayName } from "../types/actor";
import { displayName } from "../types/actor";
import RouteName from "../router/name";
import { computed, onMounted, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
@@ -234,7 +290,10 @@ import {
import { useLazyQuery, useMutation } from "@vue/apollo-composable";
import { UPDATE_DEFAULT_ACTOR } from "@/graphql/actor";
import { changeIdentity } from "@/utils/identity";
import { useRegistrationConfig } from "@/composition/apollo/config";
import {
useRegistrationConfig,
useIsLongEvents,
} from "@/composition/apollo/config";
import { useOruga } from "@oruga-ui/oruga-next";
import {
UNREAD_ACTOR_CONVERSATIONS,
@@ -242,6 +301,8 @@ import {
} from "@/graphql/user";
import { ICurrentUser } from "@/types/current-user.model";
const { islongEvents } = useIsLongEvents();
const { currentUser } = useCurrentUserClient();
const { currentActor } = useCurrentActorClient();

View File

@@ -90,7 +90,7 @@
</template>
<script lang="ts" setup>
import { useHead } from "@unhead/vue";
import { useHead } from "@/utils/head";
import { computed, inject, ref } from "vue";
import { useI18n } from "vue-i18n";
import { useMutation } from "@vue/apollo-composable";

View File

@@ -1,21 +1,8 @@
<template>
<footer
class="bg-violet-2 color-secondary flex flex-col items-center py-2 px-3"
class="bg-violet-2 color-secondary flex flex-col items-center py-3 px-3"
ref="footer"
>
<picture class="flex max-w-xl">
<source
:srcset="`/img/pics/footer_${random}-1024w.webp 1x, /img/pics/footer_${random}-1920w.webp 2x`"
type="image/webp"
/>
<img
:src="`/img/pics/footer_${random}-1024w.webp`"
alt=""
width="1024"
height="428"
loading="lazy"
/>
</picture>
<ul
class="inline-flex flex-wrap justify-around gap-3 text-lg text-white underline decoration-yellow-1"
>
@@ -92,15 +79,11 @@ import { saveLocaleData } from "@/utils/auth";
import { loadLanguageAsync } from "@/utils/i18n";
import RouteName from "../router/name";
import langs from "../i18n/langs.json";
import { computed, watch } from "vue";
import { watch } from "vue";
import { useI18n } from "vue-i18n";
const { locale, t } = useI18n({ useScope: "global" });
const random = computed((): number => {
return Math.floor(Math.random() * 4) + 1;
});
watch(locale, async () => {
if (locale) {
console.debug("Setting locale from footer");
@@ -113,3 +96,9 @@ const isLangSelected = (lang: string): boolean => {
return lang === locale.value;
};
</script>
<style lang="scss">
footer > ul > li {
margin: auto 0;
}
</style>

View File

@@ -45,7 +45,7 @@ import { LEAVE_EVENT } from "../../graphql/event";
import { computed, ref, watchEffect } from "vue";
import { useMutation } from "@vue/apollo-composable";
import { useI18n } from "vue-i18n";
import { useHead } from "@unhead/vue";
import { useHead } from "@/utils/head";
import { IActor } from "@/types/actor";
import { IEvent } from "@/types/event.model";
import { useAnonymousActorId } from "@/composition/apollo/config";

View File

@@ -70,7 +70,7 @@ import { CONFIRM_PARTICIPATION } from "../../graphql/event";
import { computed, ref, watchEffect } from "vue";
import { useMutation } from "@vue/apollo-composable";
import { useI18n } from "vue-i18n";
import { useHead } from "@unhead/vue";
import { useHead } from "@/utils/head";
const { t } = useI18n({ useScope: "global" });

View File

@@ -9,7 +9,7 @@
<script lang="ts" setup>
import RedirectWithAccount from "@/components/Utils/RedirectWithAccount.vue";
import { useFetchEvent } from "@/composition/apollo/event";
import { useHead } from "@unhead/vue";
import { useHead } from "@/utils/head";
import { computed } from "vue";
import { useI18n } from "vue-i18n";

View File

@@ -146,7 +146,7 @@ import { useFetchEventBasic } from "@/composition/apollo/event";
import { useAnonymousActorId } from "@/composition/apollo/config";
import { computed, reactive, ref } from "vue";
import { useI18n } from "vue-i18n";
import { useHead } from "@unhead/vue";
import { useHead } from "@/utils/head";
import { useMutation } from "@vue/apollo-composable";
const error = ref<boolean | string>(false);

View File

@@ -99,7 +99,7 @@ import { useFetchEvent } from "@/composition/apollo/event";
import { useAnonymousParticipationConfig } from "@/composition/apollo/config";
import { computed } from "vue";
import { useRouter } from "vue-router";
import { useHead } from "@unhead/vue";
import { useHead } from "@/utils/head";
import { useI18n } from "vue-i18n";
const props = defineProps<{ uuid: string }>();

View File

@@ -173,6 +173,8 @@ const icons: Record<string, () => Promise<any>> = {
import(
`../../../node_modules/vue-material-design-icons/CalendarRemove.vue`
),
CalendarStar: () =>
import(`../../../node_modules/vue-material-design-icons/CalendarStar.vue`),
FileDocumentEdit: () =>
import(
`../../../node_modules/vue-material-design-icons/FileDocumentEdit.vue`