Add global search

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2022-08-26 16:08:58 +02:00
parent bfc936f57c
commit 48935e2168
216 changed files with 3646 additions and 2806 deletions

View File

@@ -14,6 +14,9 @@
<Variant title="cancelled">
<EventCard :event="cancelledEvent" />
</Variant>
<Variant title="Row mode">
<EventCard :event="longEvent" mode="row" />
</Variant>
</Story>
</template>
@@ -53,8 +56,8 @@ const baseEvent: IEvent = {
uuid: "",
title: "A very interesting event",
description: "Things happen",
beginsOn: new Date(),
endsOn: new Date(),
beginsOn: new Date().toISOString(),
endsOn: new Date().toISOString(),
physicalAddress: {
description: "Somewhere",
street: "",
@@ -74,7 +77,7 @@ const baseEvent: IEvent = {
url: "",
local: true,
slug: "",
publishAt: new Date(),
publishAt: new Date().toISOString(),
status: EventStatus.CONFIRMED,
visibility: EventVisibility.PUBLIC,
joinOptions: EventJoinOptions.FREE,
@@ -130,7 +133,7 @@ const event = reactive<IEvent>(baseEvent);
const longEvent = reactive<IEvent>({
...baseEvent,
title:
"A very long title that will have trouble to display because it will take multiple lines but where will it stop ?! Maybe after 3 lines is enough. Let's say so.",
"A very long title that will have trouble to display because it will take multiple lines but where will it stop ?! Maybe after 3 lines is enough. Let's say so. But if it doesn't work, we really need to truncate it at some point. Definitively.",
});
const tentativeEvent = reactive<IEvent>({

View File

@@ -1,16 +1,25 @@
<template>
<router-link
class="mbz-card max-w-xs shrink-0 w-[18rem] snap-center dark:bg-mbz-purple"
:to="{ name: RouteName.EVENT, params: { uuid: event.uuid } }"
<LinkOrRouterLink
class="mbz-card snap-center dark:bg-mbz-purple"
:class="{
'sm:flex sm:items-start': mode === 'row',
'max-w-xs w-[18rem] shrink-0 flex flex-col': mode === 'column',
}"
:to="to"
:isInternal="isInternal"
>
<div class="bg-secondary rounded-lg">
<div
class="bg-secondary rounded-lg"
:class="{ 'sm:w-full sm:max-w-[20rem]': mode === 'row' }"
>
<figure class="block relative pt-40">
<lazy-image-wrapper
:picture="event.picture"
style="height: 100%; position: absolute; top: 0; left: 0; width: 100%"
/>
<div
class="absolute top-3 right-0 ltr:-mr-1 rtl:-ml-1 z-10 max-w-xs no-underline flex flex-col gap-1"
class="absolute top-3 right-0 ltr:-mr-1 rtl:-ml-1 z-10 max-w-xs no-underline flex flex-col gap-1 items-end"
v-show="mode === 'column'"
v-if="event.tags || event.status !== EventStatus.CONFIRMED"
>
<mobilizon-tag
@@ -30,30 +39,39 @@
v-for="tag in (event.tags || []).slice(0, 3)"
:key="tag.slug"
>
<mobilizon-tag dir="auto">{{ tag.title }}</mobilizon-tag>
<mobilizon-tag dir="auto" :with-hash-tag="true">{{
tag.title
}}</mobilizon-tag>
</router-link>
</div>
</figure>
</div>
<div class="p-2">
<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">
<div
class="-mt-3 h-0 flex mb-3 ltr:ml-0 rtl:mr-0 items-end self-start"
:class="{ 'sm:hidden': mode === 'row' }"
>
<date-calendar-icon
:small="true"
v-if="!mergedOptions.hideDate"
:date="event.beginsOn.toString()"
/>
</div>
<div class="w-full flex flex-col justify-between">
<h3
class="text-lg leading-5 line-clamp-3 font-bold text-violet-3 dark:text-white"
:title="event.title"
<span
class="text-gray-700 dark:text-white font-semibold hidden"
:class="{ 'sm:block': mode === 'row' }"
>{{ formatDateTimeWithCurrentLocale }}</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"
dir="auto"
:lang="event.language"
>
{{ event.title }}
</h3>
<div class="pt-3">
</h2>
<div class="">
<div
class="flex items-center text-violet-3 dark:text-white"
dir="auto"
@@ -68,7 +86,7 @@
/>
</figure>
<account-circle v-else />
<span class="text-sm font-semibold ltr:pl-2 rtl:pr-2">
<span class="font-semibold ltr:pl-2 rtl:pr-2">
{{ organizerDisplayName(event) }}
</span>
</div>
@@ -84,11 +102,38 @@
<Video />
<span class="ltr:pl-2 rtl:pr-2">{{ $t("Online") }}</span>
</div>
<div
class="mt-1 no-underline gap-1 items-center hidden"
:class="{ 'sm:flex': mode === 'row' }"
v-if="event.tags || event.status !== EventStatus.CONFIRMED"
>
<mobilizon-tag
variant="info"
v-if="event.status === EventStatus.TENTATIVE"
>
{{ $t("Tentative") }}
</mobilizon-tag>
<mobilizon-tag
variant="danger"
v-if="event.status === EventStatus.CANCELLED"
>
{{ $t("Cancelled") }}
</mobilizon-tag>
<router-link
:to="{ name: RouteName.TAG, params: { tag: tag.title } }"
v-for="tag in (event.tags || []).slice(0, 3)"
:key="tag.slug"
>
<mobilizon-tag :with-hash-tag="true" dir="auto">{{
tag.title
}}</mobilizon-tag>
</router-link>
</div>
</div>
</div>
</div>
</div>
</router-link>
</LinkOrRouterLink>
</template>
<script lang="ts" setup>
@@ -104,17 +149,29 @@ import { EventStatus } from "@/types/enums";
import RouteName from "../../router/name";
import InlineAddress from "@/components/Address/InlineAddress.vue";
import { computed } from "vue";
import MobilizonTag from "../Tag.vue";
import { computed, inject } from "vue";
import MobilizonTag from "@/components/Tag.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 type { Locale } from "date-fns";
import LinkOrRouterLink from "../core/LinkOrRouterLink.vue";
const props = defineProps<{ event: IEvent; options?: IEventCardOptions }>();
const props = withDefaults(
defineProps<{
event: IEvent;
options?: IEventCardOptions;
mode?: "row" | "column";
}>(),
{ mode: "column" }
);
const defaultOptions: IEventCardOptions = {
hideDate: false,
loggedPerson: false,
hideDetails: false,
organizerActor: null,
isRemoteEvent: false,
isLoggedIn: true,
};
const mergedOptions = computed<IEventCardOptions>(() => ({
@@ -132,4 +189,31 @@ const mergedOptions = computed<IEventCardOptions>(() => ({
const actorAvatarURL = computed<string | null>(() =>
organizerAvatarUrl(props.event)
);
const dateFnsLocale = inject<Locale>("dateFnsLocale");
const formatDateTimeWithCurrentLocale = computed(() => {
if (!dateFnsLocale) return;
return formatDateTimeForEvent(new Date(props.event.beginsOn), dateFnsLocale);
});
const isInternal = computed(() => {
return (
mergedOptions.value.isRemoteEvent &&
mergedOptions.value.isLoggedIn === false
);
});
const to = computed(() => {
if (mergedOptions.value.isRemoteEvent) {
if (mergedOptions.value.isLoggedIn === false) {
return props.event.url;
}
return {
name: RouteName.INTERACT,
query: { uri: encodeURI(props.event.url) },
};
}
return { name: RouteName.EVENT, params: { uuid: props.event.uuid } };
});
</script>

View File

@@ -14,7 +14,7 @@
</p>
<p v-else-if="isSameDay() && showStartTime && showEndTime">
<span>{{
$t("On {date} from {startTime} to {endTime}", {
t("On {date} from {startTime} to {endTime}", {
date: formatDate(beginsOn),
startTime: formatTime(beginsOn, timezoneToShow),
endTime: formatTime(endsOn, timezoneToShow),
@@ -31,27 +31,24 @@
</p>
<p v-else-if="isSameDay() && showStartTime && !showEndTime">
{{
$t("On {date} starting at {startTime}", {
t("On {date} starting at {startTime}", {
date: formatDate(beginsOn),
startTime: formatTime(beginsOn),
})
}}
</p>
<p v-else-if="isSameDay()">
{{ $t("On {date}", { date: formatDate(beginsOn) }) }}
{{ t("On {date}", { date: formatDate(beginsOn) }) }}
</p>
<p v-else-if="endsOn && showStartTime && showEndTime">
<span>
{{
$t(
"From the {startDate} at {startTime} to the {endDate} at {endTime}",
{
startDate: formatDate(beginsOn),
startTime: formatTime(beginsOn, timezoneToShow),
endDate: formatDate(endsOn),
endTime: formatTime(endsOn, timezoneToShow),
}
)
t("From the {startDate} at {startTime} to the {endDate} at {endTime}", {
startDate: formatDate(beginsOn),
startTime: formatTime(beginsOn, timezoneToShow),
endDate: formatDate(endsOn),
endTime: formatTime(endsOn, timezoneToShow),
})
}}
</span>
<br />
@@ -66,7 +63,7 @@
<p v-else-if="endsOn && showStartTime">
<span>
{{
$t("From the {startDate} at {startTime} to the {endDate}", {
t("From the {startDate} at {startTime} to the {endDate}", {
startDate: formatDate(beginsOn),
startTime: formatTime(beginsOn, timezoneToShow),
endDate: formatDate(endsOn),
@@ -169,22 +166,22 @@ const differentFromUserTimezone = computed((): boolean => {
const singleTimeZone = computed((): string => {
if (showLocalTimezone.value) {
return t("Local time ({timezone})", {
timezone: timezoneToShow,
}) as string;
timezone: timezoneToShow.value,
});
}
return t("Time in your timezone ({timezone})", {
timezone: timezoneToShow,
}) as string;
timezone: timezoneToShow.value,
});
});
const multipleTimeZones = computed((): string => {
if (showLocalTimezone.value) {
return t("Local time ({timezone})", {
timezone: timezoneToShow,
}) as string;
return t("Local times ({timezone})", {
timezone: timezoneToShow.value,
});
}
return t("Times in your timezone ({timezone})", {
timezone: timezoneToShow,
}) as string;
timezone: timezoneToShow.value,
});
});
</script>

View File

@@ -87,7 +87,7 @@ const RoutingParamType = {
},
};
const MapLeaflet = import("../../components/Map.vue");
const MapLeaflet = import("@/components/LeafletMap.vue");
const props = defineProps<{
address: IAddress;

View File

@@ -136,10 +136,10 @@ const metadata = computed({
};
}) as any[];
},
set(metadata: IEventMetadataDescription[]) {
set(newMetadata: IEventMetadataDescription[]) {
emit(
"update:modelValue",
metadata.filter((elem) => elem)
newMetadata.filter((elem) => elem)
);
},
});

View File

@@ -10,7 +10,7 @@
<div class="address" v-if="physicalAddress">
<address-info :address="physicalAddress" />
<o-button
type="is-text"
variant="text"
class="map-show-button"
@click="$emit('showMapModal', true)"
v-if="physicalAddress.geom"

View File

@@ -22,27 +22,23 @@
:lang="event.language"
dir="auto"
>
<b-tag
<tag
variant="info"
class="mr-1"
v-if="event.status === EventStatus.TENTATIVE"
>
{{ $t("Tentative") }}
</b-tag>
<b-tag
</tag>
<tag
variant="danger"
class="mr-1"
v-if="event.status === EventStatus.CANCELLED"
>
{{ $t("Cancelled") }}
</b-tag>
<b-tag
class="mr-2"
variant="warning"
size="is-medium"
v-if="event.draft"
>{{ $t("Draft") }}</b-tag
>
</tag>
<tag class="mr-2" variant="warning" size="medium" v-if="event.draft">{{
$t("Draft")
}}</tag>
{{ event.title }}
</h3>
<inline-address
@@ -99,7 +95,7 @@
</span>
<span v-if="event.participantStats.notApproved > 0">
<o-button
type="is-text"
variant="text"
@click="
gotToWithCheck(participation, {
name: RouteName.PARTICIPATIONS,
@@ -134,6 +130,7 @@ import InlineAddress from "@/components/Address/InlineAddress.vue";
import Video from "vue-material-design-icons/Video.vue";
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
import AccountMultiple from "vue-material-design-icons/AccountMultiple.vue";
import Tag from "@/components/Tag.vue";
withDefaults(
defineProps<{

View File

@@ -1,12 +1,17 @@
<template>
<article class="bg-white dark:bg-mbz-purple mb-5 mt-4 pb-2 md:p-0">
<div class="bg-yellow-2 flex p-2 text-violet-title rounded-t-lg" dir="auto">
<article
class="bg-white dark:bg-mbz-purple dark:hover:bg-mbz-purple-400 mb-5 mt-4 pb-2 md:p-0 rounded-t-lg"
>
<div
class="bg-mbz-yellow-alt-100 flex p-2 text-violet-title rounded-t-lg"
dir="auto"
>
<figure
class="image is-24x24 ltr:pr-1 rtl:pl-1"
v-if="participation.actor.avatar"
>
<img
class="is-rounded"
class="rounded"
:src="participation.actor.avatar.url"
alt=""
height="24"
@@ -157,7 +162,7 @@
</span>
<o-button
v-if="participation.event.participantStats.notApproved > 0"
type="is-text"
variant="text"
@click="
gotToWithCheck(participation, {
name: RouteName.PARTICIPATIONS,
@@ -330,7 +335,7 @@ const defaultOptions: IEventCardOptions = {
const props = withDefaults(
defineProps<{
participation: IParticipant;
options: IEventCardOptions;
options?: IEventCardOptions;
}>(),
{
options: () => ({

View File

@@ -7,14 +7,11 @@
:message="fieldErrors"
:type="{ 'is-danger': fieldErrors }"
class="!-mt-2"
:labelClass="labelClass"
>
<template #label>
{{ actualLabel }}
<span
class="is-size-6 has-text-weight-normal"
v-if="gettingLocation"
>{{ t("Getting location") }}</span
>
<span v-if="gettingLocation">{{ t("Getting location") }}</span>
</template>
<p class="control" v-if="canShowLocateMeButton">
<o-loading
@@ -54,7 +51,7 @@
</template>
<template #empty>
<span v-if="isFetching">{{ t("Searching") }}</span>
<div v-else-if="queryText.length >= 3" class="is-enabled">
<div v-else-if="queryText.length >= 3" class="enabled">
<span>{{
t('No results for "{queryText}"', { queryText })
}}</span>
@@ -121,12 +118,16 @@ import { useGeocodingAutocomplete } from "@/composition/apollo/config";
import { ADDRESS } from "@/graphql/address";
import { useReverseGeocode } from "@/composition/apollo/address";
import { useLazyQuery } from "@vue/apollo-composable";
const MapLeaflet = defineAsyncComponent(() => import("../Map.vue"));
const MapLeaflet = defineAsyncComponent(
() => import("@/components/LeafletMap.vue")
);
const props = withDefaults(
defineProps<{
modelValue: IAddress | null;
defaultText?: string | null;
label?: string;
labelClass?: string;
userTimezone?: string;
disabled?: boolean;
hideMap?: boolean;
@@ -134,7 +135,8 @@ const props = withDefaults(
placeholder?: string;
}>(),
{
label: "",
labelClass: "",
defaultText: "",
disabled: false,
hideMap: false,
hideSelected: false,
@@ -204,7 +206,7 @@ const checkCurrentPosition = (e: LatLng): boolean => {
const { t, locale } = useI18n({ useScope: "global" });
const actualLabel = computed((): string => {
return props.label ?? (t("Find an address") as string);
return props.label ?? t("Find an address");
});
// eslint-disable-next-line class-methods-use-this
@@ -253,11 +255,14 @@ const asyncData = async (query: string): Promise<void> => {
const queryText = computed({
get() {
return selected.value ? addressFullName(selected.value) : "";
return (
(selected.value ? addressFullName(selected.value) : props.defaultText) ??
""
);
},
set(text) {
if (text === "" && selected.value?.id) {
console.log("doing reset");
console.debug("doing reset");
resetAddress();
}
},

View File

@@ -1,7 +1,7 @@
<template>
<div class="events-wrapper">
<div class="flex flex-col gap-4" v-for="key of keys" :key="key">
<h2 class="is-size-5 month-name">
<h2 class="month-name">
{{ monthName(groupEvents(key)[0]) }}
</h2>
<event-minimalist-card

View File

@@ -27,10 +27,6 @@ const videoDetails = computed((): { host: string; uuid: string } | null => {
}
return null;
});
const origin = computed((): string => {
return window.location.hostname;
});
</script>
<style lang="scss" scoped>
.peertube {

View File

@@ -28,10 +28,6 @@ const videoID = computed((): string | null => {
}
return null;
});
const origin = computed((): string => {
return window.location.hostname;
});
</script>
<style lang="scss" scoped>
.youtube {

View File

@@ -34,9 +34,9 @@
class="flex flex-wrap p-3 bg-white hover:bg-gray-50 dark:bg-violet-3 dark:hover:bg-violet-3/60 border border-gray-300 rounded-lg cursor-pointer peer-checked:ring-primary peer-checked:ring-2 peer-checked:border-transparent"
:for="`availableActor-${availableActor?.id}`"
>
<figure class="" v-if="availableActor?.avatar">
<figure class="h-12 w-12" v-if="availableActor?.avatar">
<img
class="rounded"
class="rounded-full h-full w-full object-cover"
:src="availableActor.avatar.url"
alt=""
width="48"

View File

@@ -12,9 +12,9 @@
>
<div class="flex gap-1 p-4">
<div class="">
<figure class="" v-if="selectedActor.avatar">
<figure class="h-12 w-12" v-if="selectedActor.avatar">
<img
class="rounded"
class="rounded-full h-full w-full object-cover"
:src="selectedActor.avatar.url"
:alt="selectedActor.avatar.alt ?? ''"
height="48"
@@ -207,7 +207,7 @@ const props = withDefaults(
{ inline: true, contacts: () => [] }
);
const emit = defineEmits(["update:modelValue", "update:Contacts"]);
const emit = defineEmits(["update:modelValue", "update:contacts"]);
const selectedActor = computed({
get(): IActor | undefined {
@@ -252,7 +252,7 @@ const actualContacts = computed({
},
set(contactsIds: (string | undefined)[]) {
emit(
"update:Contacts",
"update:contacts",
actorMembers.value.filter(({ id }) => contactsIds.includes(id))
);
},

View File

@@ -68,7 +68,7 @@
</template>
<script lang="ts" setup>
import { IActor, IPerson } from "@/types/actor";
import { IPerson } from "@/types/actor";
import { EventJoinOptions, ParticipantRole } from "@/types/enums";
import { IEvent } from "@/types/event.model";
import ParticipationButton from "./ParticipationButton.vue";

View File

@@ -37,7 +37,7 @@ import { useI18n } from "vue-i18n";
import { IEvent } from "@/types/event.model";
import ShareModal from "@/components/Share/ShareModal.vue";
const props = withDefaults(
withDefaults(
defineProps<{
event: IEvent;
eventCapacityOK?: boolean;

View File

@@ -16,8 +16,8 @@ import TagInput from "./TagInput.vue";
const tags = reactive<ITag[]>([{ title: "Hello", slug: "hello" }]);
const fetchTags = async (text: string) =>
new Promise<ITag[]>((resolve, reject) => {
const fetchTags = async () =>
new Promise<ITag[]>((resolve) => {
resolve([{ title: "Welcome", slug: "welcome" }]);
});
</script>

View File

@@ -3,7 +3,7 @@
<template #label>
{{ $t("Add some tags") }}
<o-tooltip
type="dark"
variant="dark"
:label="
$t('You can add tags by hitting the Enter key or by adding a comma')
"
@@ -77,9 +77,9 @@ const tagsStrings = computed({
get(): string[] {
return props.modelValue.map((tag: ITag) => tag.title);
},
set(tagsStrings: string[]) {
console.debug("tagsStrings", tagsStrings);
const tagEntities = tagsStrings.map((tag: string | ITag) => {
set(newTagsStrings: string[]) {
console.debug("tagsStrings", newTagsStrings);
const tagEntities = newTagsStrings.map((tag: string | ITag) => {
if (typeof tag !== "string") {
return tag;
}