2022-07-12 10:55:28 +02:00
|
|
|
<template>
|
2022-08-26 16:08:58 +02:00
|
|
|
<div class="max-w-4xl mx-auto">
|
|
|
|
<SearchFields
|
|
|
|
class="md:ml-10 mr-2"
|
2022-09-26 10:29:20 +02:00
|
|
|
v-model:search="search"
|
2022-08-26 16:08:58 +02:00
|
|
|
v-model:location="location"
|
|
|
|
:locationDefaultText="locationName"
|
|
|
|
/>
|
|
|
|
</div>
|
2022-08-22 12:12:09 +02:00
|
|
|
<div
|
|
|
|
class="container mx-auto md:py-3 md:px-4 flex flex-col lg:flex-row gap-x-5 gap-y-1"
|
|
|
|
>
|
|
|
|
<aside
|
2022-08-26 16:08:58 +02:00
|
|
|
class="flex-none lg:block lg:sticky top-8 rounded-md w-full lg:w-80 flex-col justify-between mt-2 lg:pb-10 lg:px-8 overflow-y-auto dark:text-slate-100 bg-white dark:bg-mbz-purple"
|
2022-08-22 12:12:09 +02:00
|
|
|
>
|
|
|
|
<o-button
|
|
|
|
@click="toggleFilters"
|
|
|
|
icon-left="filter"
|
|
|
|
class="w-full inline-flex lg:!hidden text-white px-4 py-2 justify-center"
|
|
|
|
>
|
|
|
|
<span v-if="!filtersPanelOpened">{{ t("Hide filters") }}</span>
|
|
|
|
<span v-else>{{ t("Show filters") }}</span>
|
|
|
|
</o-button>
|
|
|
|
<form
|
|
|
|
@submit.prevent="doNewSearch"
|
|
|
|
:class="{ hidden: filtersPanelOpened }"
|
2022-08-26 16:08:58 +02:00
|
|
|
class="lg:block mt-4 px-2"
|
2022-08-22 12:12:09 +02:00
|
|
|
>
|
|
|
|
<p class="sr-only">{{ t("Type") }}</p>
|
|
|
|
<ul
|
|
|
|
class="font-medium text-gray-900 dark:text-slate-100 space-y-4 pb-4 border-b border-gray-200 dark:border-gray-500"
|
|
|
|
>
|
|
|
|
<li
|
|
|
|
v-for="content in contentTypeMapping"
|
|
|
|
:key="content.contentType"
|
|
|
|
class="flex gap-1"
|
2022-07-12 10:55:28 +02:00
|
|
|
>
|
2022-08-22 12:12:09 +02:00
|
|
|
<Magnify
|
|
|
|
v-if="content.contentType === ContentType.ALL"
|
|
|
|
:size="24"
|
2022-07-12 10:55:28 +02:00
|
|
|
/>
|
2022-08-22 12:12:09 +02:00
|
|
|
|
|
|
|
<Calendar
|
|
|
|
v-if="content.contentType === ContentType.EVENTS"
|
|
|
|
:size="24"
|
|
|
|
/>
|
|
|
|
|
|
|
|
<AccountMultiple
|
|
|
|
v-if="content.contentType === ContentType.GROUPS"
|
|
|
|
:size="24"
|
|
|
|
/>
|
|
|
|
|
|
|
|
<router-link
|
|
|
|
:to="{
|
|
|
|
...$route,
|
|
|
|
query: { ...$route.query, contentType: content.contentType },
|
|
|
|
}"
|
|
|
|
>
|
|
|
|
{{ content.label }}
|
|
|
|
</router-link>
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
|
2022-08-26 16:08:58 +02:00
|
|
|
<div
|
|
|
|
class="py-4 border-b border-gray-200 dark:border-gray-500"
|
|
|
|
v-show="globalSearchEnabled"
|
|
|
|
>
|
|
|
|
<fieldset class="flex flex-col">
|
|
|
|
<legend class="sr-only">{{ t("Search target") }}</legend>
|
|
|
|
|
|
|
|
<div>
|
|
|
|
<input
|
|
|
|
id="internalTarget"
|
|
|
|
v-model="searchTarget"
|
|
|
|
type="radio"
|
|
|
|
name="searchTarget"
|
|
|
|
:value="SearchTargets.INTERNAL"
|
|
|
|
class="w-4 h-4 border-gray-300 focus:ring-2 focus:ring-blue-300 dark:focus:ring-blue-600 dark:focus:bg-blue-600 dark:bg-gray-700 dark:border-gray-600"
|
|
|
|
/>
|
|
|
|
<label
|
|
|
|
for="internalTarget"
|
|
|
|
class="ml-3 font-medium text-gray-900 dark:text-gray-300"
|
|
|
|
>{{ t("In this instance's network") }}</label
|
|
|
|
>
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<input
|
|
|
|
id="globalTarget"
|
|
|
|
v-model="searchTarget"
|
|
|
|
type="radio"
|
|
|
|
name="searchTarget"
|
|
|
|
:value="SearchTargets.GLOBAL"
|
|
|
|
class="w-4 h-4 border-gray-300 focus:ring-2 focus:ring-blue-300 dark:focus:ring-blue-600 dark:focus:bg-blue-600 dark:bg-gray-700 dark:border-gray-600"
|
|
|
|
/>
|
|
|
|
<label
|
|
|
|
for="globalTarget"
|
|
|
|
class="ml-3 font-medium text-gray-900 dark:text-gray-300"
|
|
|
|
>{{ t("On the Fediverse") }}</label
|
|
|
|
>
|
|
|
|
</div>
|
|
|
|
</fieldset>
|
|
|
|
</div>
|
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
<div
|
|
|
|
class="py-4 border-b border-gray-200 dark:border-gray-500"
|
|
|
|
v-show="contentType !== 'GROUPS'"
|
|
|
|
>
|
|
|
|
<o-switch v-model="isOnline">{{ t("Online events") }}</o-switch>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<filter-section
|
|
|
|
v-show="contentType !== 'GROUPS'"
|
|
|
|
v-model:opened="searchFilterSectionsOpenStatus.eventDate"
|
|
|
|
:title="t('Event date')"
|
|
|
|
>
|
|
|
|
<template #options>
|
|
|
|
<fieldset class="flex flex-col">
|
|
|
|
<legend class="sr-only">{{ t("Event date") }}</legend>
|
|
|
|
<div
|
|
|
|
v-for="(eventStartDateRangeOption, key) in dateOptions"
|
|
|
|
:key="key"
|
2022-07-12 10:55:28 +02:00
|
|
|
>
|
2022-08-22 12:12:09 +02:00
|
|
|
<input
|
|
|
|
:id="key"
|
|
|
|
v-model="when"
|
|
|
|
type="radio"
|
|
|
|
name="eventStartDateRange"
|
|
|
|
:value="key"
|
|
|
|
class="w-4 h-4 border-gray-300 focus:ring-2 focus:ring-blue-300 dark:focus:ring-blue-600 dark:focus:bg-blue-600 dark:bg-gray-700 dark:border-gray-600"
|
|
|
|
/>
|
|
|
|
<label
|
|
|
|
:for="key"
|
|
|
|
class="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300"
|
|
|
|
>{{ eventStartDateRangeOption.label }}</label
|
|
|
|
>
|
|
|
|
</div>
|
|
|
|
</fieldset>
|
|
|
|
</template>
|
|
|
|
<template #preview>
|
|
|
|
<span
|
|
|
|
class="bg-blue-100 text-blue-800 text-sm font-semibold p-0.5 rounded dark:bg-blue-200 dark:text-blue-800 grow-0"
|
2022-07-12 10:55:28 +02:00
|
|
|
>
|
2022-08-22 12:12:09 +02:00
|
|
|
{{
|
|
|
|
Object.entries(dateOptions).find(([key]) => key === when)?.[1]
|
|
|
|
?.label
|
|
|
|
}}
|
|
|
|
</span>
|
|
|
|
</template>
|
|
|
|
</filter-section>
|
|
|
|
|
|
|
|
<filter-section
|
|
|
|
v-show="!isOnline"
|
|
|
|
v-model:opened="searchFilterSectionsOpenStatus.eventDistance"
|
|
|
|
:title="t('Distance')"
|
|
|
|
>
|
|
|
|
<template #options>
|
|
|
|
<fieldset class="flex flex-col">
|
|
|
|
<legend class="sr-only">{{ t("Distance") }}</legend>
|
|
|
|
<div
|
|
|
|
v-for="distanceOption in eventDistance"
|
|
|
|
:key="distanceOption.id"
|
2022-07-12 10:55:28 +02:00
|
|
|
>
|
2022-08-22 12:12:09 +02:00
|
|
|
<input
|
|
|
|
:id="distanceOption.id"
|
|
|
|
v-model="distance"
|
|
|
|
type="radio"
|
|
|
|
name="eventDistance"
|
|
|
|
:value="distanceOption.id"
|
|
|
|
class="w-4 h-4 border-gray-300 focus:ring-2 focus:ring-blue-300 dark:focus:ring-blue-600 dark:focus:bg-blue-600 dark:bg-gray-700 dark:border-gray-600"
|
|
|
|
/>
|
|
|
|
<label
|
|
|
|
:for="distanceOption.id"
|
|
|
|
class="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300"
|
|
|
|
>{{ distanceOption.label }}</label
|
|
|
|
>
|
|
|
|
</div>
|
|
|
|
</fieldset>
|
|
|
|
</template>
|
|
|
|
<template #preview>
|
|
|
|
<span
|
|
|
|
class="bg-blue-100 text-blue-800 text-sm font-semibold p-0.5 rounded dark:bg-blue-200 dark:text-blue-800 grow-0"
|
2022-07-12 10:55:28 +02:00
|
|
|
>
|
2022-08-22 12:12:09 +02:00
|
|
|
{{ eventDistance.find(({ id }) => id === distance)?.label }}
|
|
|
|
</span>
|
|
|
|
</template>
|
|
|
|
</filter-section>
|
|
|
|
|
|
|
|
<filter-section
|
|
|
|
v-show="contentType !== 'GROUPS'"
|
|
|
|
v-model:opened="searchFilterSectionsOpenStatus.eventCategory"
|
|
|
|
:title="t('Categories')"
|
|
|
|
>
|
|
|
|
<template #options>
|
|
|
|
<fieldset class="flex flex-col">
|
|
|
|
<legend class="sr-only">{{ t("Categories") }}</legend>
|
2022-10-28 10:39:42 +02:00
|
|
|
<div v-for="category in orderedCategories" :key="category.id">
|
2022-08-22 12:12:09 +02:00
|
|
|
<input
|
|
|
|
:id="category.id"
|
|
|
|
v-model="categoryOneOf"
|
|
|
|
type="checkbox"
|
|
|
|
name="category"
|
|
|
|
:value="category.id"
|
|
|
|
class="w-4 h-4 border-gray-300 focus:ring-2 focus:ring-blue-300 dark:focus:ring-blue-600 dark:focus:bg-blue-600 dark:bg-gray-700 dark:border-gray-600"
|
|
|
|
/>
|
|
|
|
<label
|
|
|
|
:for="category.id"
|
|
|
|
class="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300"
|
|
|
|
>{{ category.label }}</label
|
|
|
|
>
|
|
|
|
</div>
|
|
|
|
</fieldset>
|
|
|
|
</template>
|
|
|
|
<template #preview>
|
|
|
|
<span
|
|
|
|
class="bg-blue-100 text-blue-800 text-sm font-semibold p-0.5 rounded dark:bg-blue-200 dark:text-blue-800 grow-0"
|
|
|
|
v-if="categoryOneOf.length > 2"
|
|
|
|
>
|
|
|
|
{{
|
|
|
|
t("{numberOfCategories} selected", {
|
|
|
|
numberOfCategories: categoryOneOf.length,
|
|
|
|
})
|
|
|
|
}}
|
|
|
|
</span>
|
|
|
|
<span
|
|
|
|
class="bg-blue-100 text-blue-800 text-sm font-semibold p-0.5 rounded dark:bg-blue-200 dark:text-blue-800 grow-0"
|
|
|
|
v-else-if="categoryOneOf.length > 0"
|
2022-07-12 10:55:28 +02:00
|
|
|
>
|
2022-08-22 12:12:09 +02:00
|
|
|
{{
|
|
|
|
listShortDisjunctionFormatter(
|
|
|
|
categoryOneOf.map(
|
|
|
|
(category) =>
|
|
|
|
(eventCategories ?? []).find(({ id }) => id === category)
|
|
|
|
?.label ?? ""
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}}
|
|
|
|
</span>
|
|
|
|
<span
|
|
|
|
class="bg-blue-100 text-blue-800 text-sm font-semibold p-0.5 rounded dark:bg-blue-200 dark:text-blue-800 grow-0"
|
|
|
|
v-else-if="categoryOneOf.length === 0"
|
|
|
|
>
|
|
|
|
{{ t("Categories", "All") }}
|
|
|
|
</span>
|
|
|
|
</template>
|
|
|
|
</filter-section>
|
|
|
|
<filter-section
|
|
|
|
v-show="contentType !== 'GROUPS'"
|
|
|
|
v-model:opened="searchFilterSectionsOpenStatus.eventStatus"
|
|
|
|
:title="t('Event status')"
|
|
|
|
>
|
|
|
|
<template #options>
|
|
|
|
<fieldset class="flex flex-col">
|
|
|
|
<legend class="sr-only">{{ t("Event status") }}</legend>
|
|
|
|
<div
|
|
|
|
v-for="eventStatusOption in eventStatuses"
|
|
|
|
:key="eventStatusOption.id"
|
2022-07-12 10:55:28 +02:00
|
|
|
>
|
2022-08-22 12:12:09 +02:00
|
|
|
<input
|
|
|
|
:id="eventStatusOption.id"
|
|
|
|
v-model="statusOneOf"
|
|
|
|
type="checkbox"
|
|
|
|
name="eventStatus"
|
|
|
|
:value="eventStatusOption.id"
|
|
|
|
class="w-4 h-4 border-gray-300 focus:ring-2 focus:ring-blue-300 dark:focus:ring-blue-600 dark:focus:bg-blue-600 dark:bg-gray-700 dark:border-gray-600"
|
|
|
|
/>
|
|
|
|
<label
|
|
|
|
:for="eventStatusOption.id"
|
|
|
|
class="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300"
|
|
|
|
>{{ eventStatusOption.label }}</label
|
|
|
|
>
|
|
|
|
</div>
|
|
|
|
</fieldset>
|
|
|
|
</template>
|
|
|
|
<template #preview>
|
|
|
|
<span
|
|
|
|
class="bg-blue-100 text-blue-800 text-sm font-semibold p-0.5 rounded dark:bg-blue-200 dark:text-blue-800 grow-0"
|
|
|
|
v-if="statusOneOf.length === Object.values(EventStatus).length"
|
|
|
|
>
|
|
|
|
{{ t("Statuses", "All") }}
|
|
|
|
</span>
|
|
|
|
<span
|
|
|
|
class="bg-blue-100 text-blue-800 text-sm font-semibold p-0.5 rounded dark:bg-blue-200 dark:text-blue-800 grow-0"
|
|
|
|
v-else-if="statusOneOf.length > 0"
|
|
|
|
>
|
|
|
|
{{
|
|
|
|
listShortDisjunctionFormatter(
|
|
|
|
statusOneOf.map(
|
|
|
|
(status) =>
|
|
|
|
eventStatuses.find(({ id }) => id === status)?.label ?? ""
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}}
|
|
|
|
</span>
|
|
|
|
</template>
|
|
|
|
</filter-section>
|
|
|
|
|
|
|
|
<filter-section
|
|
|
|
v-model:opened="searchFilterSectionsOpenStatus.eventLanguage"
|
|
|
|
:title="t('Languages')"
|
2022-07-12 10:55:28 +02:00
|
|
|
>
|
2022-08-22 12:12:09 +02:00
|
|
|
<template #options>
|
|
|
|
<fieldset class="flex flex-col">
|
|
|
|
<legend class="sr-only">{{ t("Languages") }}</legend>
|
|
|
|
<div v-for="(language, key) in langs" :key="key">
|
|
|
|
<input
|
|
|
|
:id="key"
|
|
|
|
type="checkbox"
|
|
|
|
name="eventStartDateRange"
|
|
|
|
:value="key"
|
|
|
|
v-model="languageOneOf"
|
|
|
|
class="w-4 h-4 border-gray-300 focus:ring-2 focus:ring-blue-300 dark:focus:ring-blue-600 dark:focus:bg-blue-600 dark:bg-gray-700 dark:border-gray-600"
|
|
|
|
/>
|
|
|
|
<label
|
|
|
|
:for="key"
|
|
|
|
class="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300"
|
|
|
|
>{{ language }}</label
|
|
|
|
>
|
|
|
|
</div>
|
|
|
|
</fieldset>
|
|
|
|
</template>
|
|
|
|
<template #preview>
|
|
|
|
<span
|
|
|
|
class="bg-blue-100 text-blue-800 text-sm font-semibold p-0.5 rounded dark:bg-blue-200 dark:text-blue-800 grow-0"
|
|
|
|
v-if="languageOneOf.length > 2"
|
|
|
|
>
|
|
|
|
{{
|
|
|
|
t("{numberOfLanguages} selected", {
|
|
|
|
numberOfLanguages: languageOneOf.length,
|
|
|
|
})
|
|
|
|
}}
|
|
|
|
</span>
|
|
|
|
<span
|
|
|
|
class="bg-blue-100 text-blue-800 text-sm font-semibold p-0.5 rounded dark:bg-blue-200 dark:text-blue-800 grow-0"
|
|
|
|
v-else-if="languageOneOf.length > 0"
|
|
|
|
>
|
|
|
|
{{
|
|
|
|
listShortDisjunctionFormatter(
|
|
|
|
languageOneOf.map((lang) => langs[lang])
|
|
|
|
)
|
|
|
|
}}
|
|
|
|
</span>
|
|
|
|
<span
|
|
|
|
class="bg-blue-100 text-blue-800 text-sm font-semibold p-0.5 rounded dark:bg-blue-200 dark:text-blue-800 grow-0"
|
|
|
|
v-else-if="languageOneOf.length === 0"
|
|
|
|
>
|
|
|
|
{{ t("Languages", "All") }}
|
|
|
|
</span>
|
|
|
|
</template>
|
|
|
|
</filter-section>
|
|
|
|
|
|
|
|
<!--
|
|
|
|
|
|
|
|
<div class="">
|
|
|
|
<label v-translate class="font-bold" for="host">Mobilizon instance</label>
|
|
|
|
|
|
|
|
<input
|
|
|
|
id="host"
|
|
|
|
v-model="formHost"
|
|
|
|
type="text"
|
|
|
|
name="host"
|
|
|
|
placeholder="mobilizon.fr"
|
|
|
|
class="dark:text-black md:max-w-fit w-full"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="">
|
|
|
|
<label v-translate class="inline font-bold" for="tagsAllOf">All of these tags</label>
|
|
|
|
<button
|
|
|
|
v-if="formTagsAllOf.length !== 0"
|
|
|
|
v-translate
|
|
|
|
class="text-sm ml-2"
|
|
|
|
@click="resetField('tagsAllOf')"
|
|
|
|
>
|
|
|
|
Reset
|
|
|
|
</button>
|
|
|
|
|
|
|
|
<vue-tags-input
|
|
|
|
v-model="formTagAllOf"
|
|
|
|
:placeholder="tagsPlaceholder"
|
|
|
|
:tags="formTagsAllOf"
|
|
|
|
@tags-changed="(newTags) => (formTagsAllOf = newTags)"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div>
|
|
|
|
<div>
|
|
|
|
<label v-translate class="inline font-bold" for="tagsOneOf">One of these tags</label>
|
|
|
|
<button
|
|
|
|
v-if="formTagsOneOf.length !== 0"
|
|
|
|
v-translate
|
|
|
|
class="text-sm ml-2"
|
|
|
|
@click="resetField('tagsOneOf')"
|
|
|
|
>
|
|
|
|
Reset
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<vue-tags-input
|
|
|
|
v-model="formTagOneOf"
|
|
|
|
:placeholder="tagsPlaceholder"
|
|
|
|
:tags="formTagsOneOf"
|
|
|
|
@tags-changed="(newTags) => (formTagsOneOf = newTags)"
|
|
|
|
/>
|
|
|
|
</div>-->
|
|
|
|
|
|
|
|
<div class="sr-only">
|
|
|
|
<button
|
|
|
|
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center mr-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
|
|
|
|
type="submit"
|
2022-07-12 10:55:28 +02:00
|
|
|
>
|
2022-08-22 12:12:09 +02:00
|
|
|
{{ t("Apply filters") }}
|
|
|
|
</button>
|
2022-07-12 10:55:28 +02:00
|
|
|
</div>
|
2022-08-22 12:12:09 +02:00
|
|
|
|
|
|
|
<o-button
|
|
|
|
@click="toggleFilters"
|
|
|
|
icon-left="filter"
|
|
|
|
class="w-full inline-flex lg:!hidden text-white px-4 py-2 justify-center"
|
|
|
|
>
|
|
|
|
{{ t("Hide filters") }}
|
|
|
|
</o-button>
|
|
|
|
</form>
|
|
|
|
</aside>
|
|
|
|
<div class="flex-1 px-2">
|
2022-09-01 10:00:17 +02:00
|
|
|
<div
|
|
|
|
id="results-anchor"
|
2022-09-01 10:42:05 +02:00
|
|
|
class="hidden sm:flex items-center justify-between dark:text-slate-100 mb-2"
|
2022-09-01 10:00:17 +02:00
|
|
|
>
|
|
|
|
<p v-if="totalCount === 0">
|
|
|
|
<span v-if="contentType === ContentType.EVENTS">{{
|
|
|
|
t("No events found")
|
|
|
|
}}</span>
|
|
|
|
<span v-else-if="contentType === ContentType.GROUPS">{{
|
|
|
|
t("No groups found")
|
|
|
|
}}</span>
|
|
|
|
<span v-else>{{ t("No results found") }}</span>
|
|
|
|
</p>
|
|
|
|
<p v-else>
|
|
|
|
<span v-if="contentType === 'EVENTS'">
|
2022-07-12 10:55:28 +02:00
|
|
|
{{
|
2022-09-01 10:00:17 +02:00
|
|
|
t(
|
|
|
|
"{eventsCount} events found",
|
|
|
|
{ eventsCount: searchEvents?.total },
|
|
|
|
searchEvents?.total ?? 0
|
|
|
|
)
|
2022-07-12 10:55:28 +02:00
|
|
|
}}
|
2022-09-01 10:00:17 +02:00
|
|
|
</span>
|
|
|
|
<span v-else-if="contentType === 'GROUPS'">
|
2022-08-26 16:08:58 +02:00
|
|
|
{{
|
2022-09-01 10:00:17 +02:00
|
|
|
t(
|
|
|
|
"{groupsCount} groups found",
|
|
|
|
{ groupsCount: searchGroups?.total },
|
|
|
|
searchGroups?.total ?? 0
|
|
|
|
)
|
2022-08-26 16:08:58 +02:00
|
|
|
}}
|
2022-09-01 10:00:17 +02:00
|
|
|
</span>
|
|
|
|
<span v-else>
|
|
|
|
{{
|
|
|
|
t(
|
|
|
|
"{resultsCount} results found",
|
|
|
|
{ resultsCount: totalCount },
|
|
|
|
totalCount
|
|
|
|
)
|
|
|
|
}}
|
|
|
|
</span>
|
|
|
|
</p>
|
|
|
|
<div class="flex gap-2">
|
2022-09-01 10:42:05 +02:00
|
|
|
<label class="sr-only" for="sortOptionSelect">{{
|
|
|
|
t("Sort by")
|
|
|
|
}}</label>
|
|
|
|
<o-select
|
|
|
|
:placeholder="t('Sort by')"
|
|
|
|
v-model="sortBy"
|
|
|
|
id="sortOptionSelect"
|
|
|
|
>
|
2022-09-01 10:00:17 +02:00
|
|
|
<option
|
|
|
|
v-for="sortOption in sortOptions"
|
|
|
|
:key="sortOption.key"
|
|
|
|
:value="sortOption.key"
|
|
|
|
>
|
|
|
|
{{ sortOption.label }}
|
|
|
|
</option>
|
|
|
|
</o-select>
|
|
|
|
<o-button
|
|
|
|
v-show="!isOnline"
|
|
|
|
@click="
|
|
|
|
() =>
|
|
|
|
(mode = mode === ViewMode.MAP ? ViewMode.LIST : ViewMode.MAP)
|
|
|
|
"
|
|
|
|
:icon-left="mode === ViewMode.MAP ? 'view-list' : 'map'"
|
2022-08-22 12:12:09 +02:00
|
|
|
>
|
2022-09-01 10:00:17 +02:00
|
|
|
<span v-if="mode === ViewMode.LIST">
|
|
|
|
{{ t("Map") }}
|
|
|
|
</span>
|
|
|
|
<span v-else-if="mode === ViewMode.MAP">
|
|
|
|
{{ t("List") }}
|
|
|
|
</span>
|
|
|
|
</o-button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div v-if="mode === ViewMode.LIST">
|
|
|
|
<template v-if="contentType === ContentType.ALL">
|
2022-10-31 18:07:14 +01:00
|
|
|
<template v-if="searchLoading">
|
|
|
|
<SkeletonGroupResultList v-for="i in 2" :key="i" />
|
|
|
|
<SkeletonEventResultList v-for="i in 4" :key="i" />
|
|
|
|
</template>
|
2022-09-01 10:00:17 +02:00
|
|
|
<o-notification v-if="features && !features.groups" variant="danger">
|
|
|
|
{{ t("Groups are not enabled on this instance.") }}
|
|
|
|
</o-notification>
|
2022-10-12 19:30:34 +02:00
|
|
|
<div v-else-if="searchGroups && searchGroups?.total > 0">
|
|
|
|
<GroupCard
|
2022-10-31 18:07:14 +01:00
|
|
|
class="my-2"
|
2022-10-12 19:30:34 +02:00
|
|
|
v-for="group in searchGroups?.elements"
|
|
|
|
:group="group"
|
|
|
|
:key="group.id"
|
|
|
|
:isRemoteGroup="group.__typename === 'GroupResult'"
|
|
|
|
:isLoggedIn="currentUser?.isLoggedIn"
|
|
|
|
mode="row"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div v-if="searchEvents && searchEvents.total > 0">
|
|
|
|
<event-card
|
|
|
|
mode="row"
|
|
|
|
v-for="event in searchEvents?.elements"
|
|
|
|
:event="event"
|
|
|
|
:key="event.uuid"
|
|
|
|
:options="{
|
|
|
|
isRemoteEvent: event.__typename === 'EventResult',
|
|
|
|
isLoggedIn: currentUser?.isLoggedIn,
|
|
|
|
}"
|
|
|
|
class="my-4"
|
|
|
|
/>
|
|
|
|
</div>
|
2022-10-31 18:07:14 +01:00
|
|
|
<EmptyContent v-else-if="searchLoading === false" icon="magnify">
|
|
|
|
<span v-if="searchIsUrl">
|
|
|
|
{{ t("No event found at this address") }}
|
|
|
|
</span>
|
|
|
|
<span v-else-if="!search">
|
|
|
|
{{ t("No results found") }}
|
|
|
|
</span>
|
|
|
|
<i18n-t keypath="No results found for {search}" tag="span" v-else>
|
|
|
|
<template #search>
|
|
|
|
<b class="">{{ search }}</b>
|
|
|
|
</template>
|
|
|
|
</i18n-t>
|
|
|
|
<template #desc v-if="searchIsUrl && !currentUser?.id">
|
2022-10-25 10:35:59 +02:00
|
|
|
{{
|
|
|
|
t(
|
|
|
|
"Only registered users may fetch remote events from their URL."
|
|
|
|
)
|
|
|
|
}}
|
2022-10-31 18:07:14 +01:00
|
|
|
</template>
|
|
|
|
<template #desc v-else>
|
|
|
|
<p class="my-2 text-start">
|
|
|
|
{{ t("Suggestions:") }}
|
|
|
|
</p>
|
|
|
|
<ul class="list-disc list-inside text-start">
|
|
|
|
<li>
|
|
|
|
{{ t("Make sure that all words are spelled correctly.") }}
|
|
|
|
</li>
|
|
|
|
<li>{{ t("Try different keywords.") }}</li>
|
|
|
|
<li>{{ t("Try more general keywords.") }}</li>
|
|
|
|
<li>{{ t("Try fewer keywords.") }}</li>
|
|
|
|
<li>{{ t("Change the filters.") }}</li>
|
|
|
|
</ul>
|
|
|
|
</template>
|
|
|
|
</EmptyContent>
|
2022-10-12 19:10:39 +02:00
|
|
|
<o-pagination
|
|
|
|
v-if="
|
|
|
|
(searchEvents && searchEvents?.total > EVENT_PAGE_LIMIT) ||
|
|
|
|
(searchGroups && searchGroups?.total > GROUP_PAGE_LIMIT)
|
|
|
|
"
|
|
|
|
:total="
|
|
|
|
Math.max(searchEvents?.total ?? 0, searchGroups?.total ?? 0)
|
|
|
|
"
|
|
|
|
v-model:current="page"
|
|
|
|
:per-page="EVENT_PAGE_LIMIT"
|
|
|
|
:aria-next-label="t('Next page')"
|
|
|
|
:aria-previous-label="t('Previous page')"
|
|
|
|
:aria-page-label="t('Page')"
|
|
|
|
:aria-current-label="t('Current page')"
|
|
|
|
/>
|
2022-08-26 16:08:58 +02:00
|
|
|
</template>
|
2022-09-01 10:00:17 +02:00
|
|
|
<template v-else-if="contentType === ContentType.EVENTS">
|
2022-10-31 18:07:14 +01:00
|
|
|
<template v-if="searchLoading">
|
|
|
|
<SkeletonEventResultList v-for="i in 8" :key="i" />
|
|
|
|
</template>
|
2022-09-01 10:00:17 +02:00
|
|
|
<template v-if="searchEvents && searchEvents.total > 0">
|
|
|
|
<event-card
|
|
|
|
mode="row"
|
|
|
|
v-for="event in searchEvents?.elements"
|
|
|
|
:event="event"
|
|
|
|
:key="event.uuid"
|
|
|
|
:options="{
|
|
|
|
isRemoteEvent: event.__typename === 'EventResult',
|
|
|
|
isLoggedIn: currentUser?.isLoggedIn,
|
|
|
|
}"
|
|
|
|
class="my-4"
|
|
|
|
/>
|
|
|
|
<o-pagination
|
|
|
|
v-show="searchEvents && searchEvents?.total > EVENT_PAGE_LIMIT"
|
|
|
|
:total="searchEvents.total"
|
|
|
|
v-model:current="eventPage"
|
|
|
|
:per-page="EVENT_PAGE_LIMIT"
|
|
|
|
:aria-next-label="t('Next page')"
|
|
|
|
:aria-previous-label="t('Previous page')"
|
|
|
|
:aria-page-label="t('Page')"
|
|
|
|
:aria-current-label="t('Current page')"
|
|
|
|
>
|
|
|
|
</o-pagination>
|
|
|
|
</template>
|
2022-10-31 18:07:14 +01:00
|
|
|
<EmptyContent v-else-if="searchLoading === false" icon="calendar">
|
|
|
|
<span v-if="searchIsUrl">
|
|
|
|
{{ t("No event found at this address") }}
|
|
|
|
</span>
|
|
|
|
<span v-else-if="!search">
|
|
|
|
{{ t("No events found") }}
|
|
|
|
</span>
|
|
|
|
<i18n-t keypath="No events found for {search}" tag="span" v-else>
|
|
|
|
<template #search>
|
|
|
|
<b>{{ search }}</b>
|
|
|
|
</template>
|
|
|
|
</i18n-t>
|
|
|
|
<template #desc v-if="searchIsUrl && !currentUser?.id">
|
2022-09-01 10:00:17 +02:00
|
|
|
{{
|
|
|
|
t(
|
|
|
|
"Only registered users may fetch remote events from their URL."
|
|
|
|
)
|
|
|
|
}}
|
2022-10-31 18:07:14 +01:00
|
|
|
</template>
|
|
|
|
<template #desc v-else>
|
|
|
|
<p class="my-2 text-start">
|
|
|
|
{{ t("Suggestions:") }}
|
|
|
|
</p>
|
|
|
|
<ul class="list-disc list-inside text-start">
|
|
|
|
<li>
|
|
|
|
{{ t("Make sure that all words are spelled correctly.") }}
|
|
|
|
</li>
|
|
|
|
<li>{{ t("Try different keywords.") }}</li>
|
|
|
|
<li>{{ t("Try more general keywords.") }}</li>
|
|
|
|
<li>{{ t("Try fewer keywords.") }}</li>
|
|
|
|
<li>{{ t("Change the filters.") }}</li>
|
|
|
|
</ul>
|
|
|
|
</template>
|
|
|
|
</EmptyContent>
|
2022-09-01 10:00:17 +02:00
|
|
|
</template>
|
|
|
|
<template v-else-if="contentType === ContentType.GROUPS">
|
|
|
|
<o-notification v-if="features && !features.groups" variant="danger">
|
|
|
|
{{ t("Groups are not enabled on this instance.") }}
|
|
|
|
</o-notification>
|
2022-10-31 18:07:14 +01:00
|
|
|
<template v-else-if="searchLoading">
|
|
|
|
<SkeletonGroupResultList v-for="i in 6" :key="i" />
|
|
|
|
</template>
|
2022-09-01 10:00:17 +02:00
|
|
|
<template v-else-if="searchGroups && searchGroups?.total > 0">
|
|
|
|
<GroupCard
|
2022-10-31 18:07:14 +01:00
|
|
|
class="my-2"
|
2022-09-01 10:00:17 +02:00
|
|
|
v-for="group in searchGroups?.elements"
|
|
|
|
:group="group"
|
|
|
|
:key="group.id"
|
|
|
|
:isRemoteGroup="group.__typename === 'GroupResult'"
|
|
|
|
:isLoggedIn="currentUser?.isLoggedIn"
|
|
|
|
mode="row"
|
|
|
|
/>
|
|
|
|
<o-pagination
|
|
|
|
v-show="searchGroups && searchGroups?.total > GROUP_PAGE_LIMIT"
|
|
|
|
:total="searchGroups?.total"
|
|
|
|
v-model:current="groupPage"
|
|
|
|
:per-page="GROUP_PAGE_LIMIT"
|
|
|
|
:aria-next-label="t('Next page')"
|
|
|
|
:aria-previous-label="t('Previous page')"
|
|
|
|
:aria-page-label="t('Page')"
|
|
|
|
:aria-current-label="t('Current page')"
|
|
|
|
>
|
|
|
|
</o-pagination>
|
|
|
|
</template>
|
2022-10-31 18:07:14 +01:00
|
|
|
<EmptyContent
|
|
|
|
v-else-if="searchLoading === false"
|
|
|
|
icon="account-multiple"
|
|
|
|
>
|
|
|
|
<span v-if="!search">
|
|
|
|
{{ t("No events found") }}
|
|
|
|
</span>
|
|
|
|
<i18n-t keypath="No groups found for {search}" tag="span" v-else>
|
|
|
|
<template #search>
|
|
|
|
<b>{{ search }}</b>
|
|
|
|
</template>
|
|
|
|
</i18n-t>
|
|
|
|
<template #desc>
|
|
|
|
<p class="my-2 text-start">
|
|
|
|
{{ t("Suggestions:") }}
|
|
|
|
</p>
|
|
|
|
<ul class="list-disc list-inside text-start">
|
|
|
|
<li>
|
|
|
|
{{ t("Make sure that all words are spelled correctly.") }}
|
|
|
|
</li>
|
|
|
|
<li>{{ t("Try different keywords.") }}</li>
|
|
|
|
<li>{{ t("Try more general keywords.") }}</li>
|
|
|
|
<li>{{ t("Try fewer keywords.") }}</li>
|
|
|
|
<li>{{ t("Change the filters.") }}</li>
|
|
|
|
</ul>
|
|
|
|
</template>
|
|
|
|
</EmptyContent>
|
2022-09-01 10:00:17 +02:00
|
|
|
</template>
|
|
|
|
</div>
|
|
|
|
<event-marker-map
|
|
|
|
v-if="mode === ViewMode.MAP"
|
|
|
|
:contentType="contentType"
|
|
|
|
:latitude="latitude"
|
|
|
|
:longitude="longitude"
|
|
|
|
:locationName="locationName"
|
|
|
|
@map-updated="setBounds"
|
|
|
|
:events="searchEvents"
|
|
|
|
:groups="searchGroups"
|
|
|
|
:isLoggedIn="currentUser?.isLoggedIn"
|
|
|
|
/>
|
2022-08-22 12:12:09 +02:00
|
|
|
</div>
|
2022-07-12 10:55:28 +02:00
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="ts" setup>
|
|
|
|
import {
|
|
|
|
endOfToday,
|
|
|
|
addDays,
|
|
|
|
startOfDay,
|
|
|
|
endOfDay,
|
|
|
|
endOfWeek,
|
|
|
|
addWeeks,
|
|
|
|
startOfWeek,
|
|
|
|
endOfMonth,
|
|
|
|
addMonths,
|
|
|
|
startOfMonth,
|
|
|
|
eachWeekendOfInterval,
|
|
|
|
} from "date-fns";
|
2022-08-26 16:08:58 +02:00
|
|
|
import { ContentType, EventStatus, SearchTargets } from "@/types/enums";
|
|
|
|
import EventCard from "@/components/Event/EventCard.vue";
|
|
|
|
import { IEvent } from "@/types/event.model";
|
|
|
|
import { SEARCH_EVENTS_AND_GROUPS } from "@/graphql/search";
|
|
|
|
import { Paginate } from "@/types/paginate";
|
|
|
|
import { IGroup } from "@/types/actor";
|
|
|
|
import GroupCard from "@/components/Group/GroupCard.vue";
|
2022-07-12 10:55:28 +02:00
|
|
|
import { CURRENT_USER_CLIENT } from "@/graphql/user";
|
|
|
|
import { ICurrentUser } from "@/types/current-user.model";
|
|
|
|
import { useQuery } from "@vue/apollo-composable";
|
2022-10-05 12:13:19 +02:00
|
|
|
import { computed, defineAsyncComponent, inject, ref, watch } from "vue";
|
2022-07-12 10:55:28 +02:00
|
|
|
import { useI18n } from "vue-i18n";
|
|
|
|
import {
|
|
|
|
floatTransformer,
|
|
|
|
integerTransformer,
|
|
|
|
useRouteQuery,
|
2022-08-22 12:12:09 +02:00
|
|
|
enumTransformer,
|
|
|
|
booleanTransformer,
|
|
|
|
RouteQueryTransformer,
|
2022-07-12 10:55:28 +02:00
|
|
|
} from "vue-use-route-query";
|
|
|
|
import Calendar from "vue-material-design-icons/Calendar.vue";
|
|
|
|
import AccountMultiple from "vue-material-design-icons/AccountMultiple.vue";
|
2022-08-22 12:12:09 +02:00
|
|
|
import Magnify from "vue-material-design-icons/Magnify.vue";
|
|
|
|
|
2022-07-12 10:55:28 +02:00
|
|
|
import { useHead } from "@vueuse/head";
|
|
|
|
import type { Locale } from "date-fns";
|
2022-08-22 12:12:09 +02:00
|
|
|
import FilterSection from "@/components/Search/filters/FilterSection.vue";
|
|
|
|
import { listShortDisjunctionFormatter } from "@/utils/listFormat";
|
|
|
|
import langs from "@/i18n/langs.json";
|
2022-08-26 16:08:58 +02:00
|
|
|
import {
|
|
|
|
useEventCategories,
|
|
|
|
useFeatures,
|
|
|
|
useSearchConfig,
|
|
|
|
} from "@/composition/apollo/config";
|
2022-08-22 13:47:10 +02:00
|
|
|
import { coordsToGeoHash } from "@/utils/location";
|
2022-08-26 16:08:58 +02:00
|
|
|
import SearchFields from "@/components/Home/SearchFields.vue";
|
|
|
|
import { refDebounced } from "@vueuse/core";
|
|
|
|
import { IAddress } from "@/types/address.model";
|
|
|
|
import { IConfig } from "@/types/config.model";
|
2022-09-01 10:00:17 +02:00
|
|
|
import { TypeNamed } from "@/types/apollo";
|
|
|
|
import { LatLngBounds } from "leaflet";
|
2022-10-28 10:39:42 +02:00
|
|
|
import lodashSortBy from "lodash/sortBy";
|
2022-10-31 18:07:14 +01:00
|
|
|
import EmptyContent from "@/components/Utils/EmptyContent.vue";
|
|
|
|
import SkeletonGroupResultList from "@/components/Group/SkeletonGroupResultList.vue";
|
|
|
|
import SkeletonEventResultList from "@/components/Event/SkeletonEventResultList.vue";
|
2022-07-12 10:55:28 +02:00
|
|
|
|
2022-10-05 12:13:19 +02:00
|
|
|
const EventMarkerMap = defineAsyncComponent(
|
|
|
|
() => import("@/components/Search/EventMarkerMap.vue")
|
|
|
|
);
|
|
|
|
|
2022-07-12 10:55:28 +02:00
|
|
|
const search = useRouteQuery("search", "");
|
2022-08-26 16:08:58 +02:00
|
|
|
const searchDebounced = refDebounced(search, 1000);
|
|
|
|
const locationName = useRouteQuery("locationName", null);
|
|
|
|
const location = ref<IAddress | null>(null);
|
|
|
|
|
|
|
|
watch(location, (newLocation) => {
|
2022-09-01 10:00:17 +02:00
|
|
|
console.debug("location change", newLocation);
|
2022-08-26 16:08:58 +02:00
|
|
|
if (newLocation?.geom) {
|
|
|
|
latitude.value = parseFloat(newLocation?.geom.split(";")[1]);
|
|
|
|
longitude.value = parseFloat(newLocation?.geom.split(";")[0]);
|
|
|
|
locationName.value = newLocation?.description;
|
2022-09-01 10:00:17 +02:00
|
|
|
console.debug("set location", [
|
|
|
|
latitude.value,
|
|
|
|
longitude.value,
|
|
|
|
locationName.value,
|
|
|
|
]);
|
2022-08-26 16:08:58 +02:00
|
|
|
} else {
|
|
|
|
console.debug("location emptied");
|
|
|
|
latitude.value = undefined;
|
|
|
|
longitude.value = undefined;
|
|
|
|
locationName.value = null;
|
|
|
|
}
|
|
|
|
});
|
2022-07-12 10:55:28 +02:00
|
|
|
|
|
|
|
interface ISearchTimeOption {
|
|
|
|
label: string;
|
2022-08-22 12:12:09 +02:00
|
|
|
start?: string | null;
|
|
|
|
end?: string | null;
|
2022-07-12 10:55:28 +02:00
|
|
|
}
|
|
|
|
|
2022-09-01 10:00:17 +02:00
|
|
|
enum ViewMode {
|
|
|
|
LIST = "list",
|
|
|
|
MAP = "map",
|
|
|
|
}
|
|
|
|
|
2022-09-26 10:29:20 +02:00
|
|
|
enum EventSortValues {
|
|
|
|
MATCH_DESC = "MATCH_DESC",
|
|
|
|
START_TIME_DESC = "START_TIME_DESC",
|
|
|
|
CREATED_AT_DESC = "CREATED_AT_DESC",
|
|
|
|
CREATED_AT_ASC = "CREATED_AT_ASC",
|
|
|
|
PARTICIPANT_COUNT_DESC = "PARTICIPANT_COUNT_DESC",
|
|
|
|
}
|
|
|
|
|
|
|
|
enum GroupSortValues {
|
|
|
|
MATCH_DESC = "MATCH_DESC",
|
|
|
|
MEMBER_COUNT_DESC = "MEMBER_COUNT_DESC",
|
|
|
|
}
|
|
|
|
|
2022-09-01 10:00:17 +02:00
|
|
|
enum SortValues {
|
2022-09-26 10:29:20 +02:00
|
|
|
MATCH_DESC = "MATCH_DESC",
|
|
|
|
START_TIME_DESC = "START_TIME_DESC",
|
|
|
|
CREATED_AT_DESC = "CREATED_AT_DESC",
|
|
|
|
CREATED_AT_ASC = "CREATED_AT_ASC",
|
|
|
|
PARTICIPANT_COUNT_DESC = "PARTICIPANT_COUNT_DESC",
|
|
|
|
MEMBER_COUNT_DESC = "MEMBER_COUNT_DESC",
|
2022-09-01 10:00:17 +02:00
|
|
|
}
|
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const arrayTransformer: RouteQueryTransformer<string[]> = {
|
|
|
|
fromQuery(query: string) {
|
|
|
|
return query.split(",");
|
|
|
|
},
|
|
|
|
toQuery(value: string[]) {
|
|
|
|
return value.join(",");
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2022-10-31 18:07:14 +01:00
|
|
|
const props = defineProps<{
|
|
|
|
tag?: string;
|
|
|
|
}>();
|
|
|
|
|
2022-10-12 19:10:39 +02:00
|
|
|
const page = useRouteQuery("page", 1, integerTransformer);
|
2022-07-12 10:55:28 +02:00
|
|
|
const eventPage = useRouteQuery("eventPage", 1, integerTransformer);
|
|
|
|
const groupPage = useRouteQuery("groupPage", 1, integerTransformer);
|
2022-08-22 12:12:09 +02:00
|
|
|
|
2022-08-22 13:47:10 +02:00
|
|
|
const latitude = useRouteQuery("lat", undefined, floatTransformer);
|
|
|
|
const longitude = useRouteQuery("lon", undefined, floatTransformer);
|
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const distance = useRouteQuery("distance", "10_km");
|
2022-07-12 10:55:28 +02:00
|
|
|
const when = useRouteQuery("when", "any");
|
2022-08-22 12:12:09 +02:00
|
|
|
const contentType = useRouteQuery(
|
|
|
|
"contentType",
|
2022-10-31 18:07:14 +01:00
|
|
|
props.tag ? ContentType.EVENTS : ContentType.ALL,
|
2022-08-22 12:12:09 +02:00
|
|
|
enumTransformer(ContentType)
|
|
|
|
);
|
2022-10-12 19:10:39 +02:00
|
|
|
|
|
|
|
watch(contentType, (newContentType: ContentType) => {
|
|
|
|
switch (newContentType) {
|
|
|
|
case ContentType.ALL:
|
|
|
|
page.value = 1;
|
2022-10-12 19:30:34 +02:00
|
|
|
break;
|
2022-10-12 19:10:39 +02:00
|
|
|
case ContentType.EVENTS:
|
|
|
|
eventPage.value = 1;
|
2022-10-12 19:30:34 +02:00
|
|
|
break;
|
2022-10-12 19:10:39 +02:00
|
|
|
case ContentType.GROUPS:
|
|
|
|
groupPage.value = 1;
|
2022-10-12 19:30:34 +02:00
|
|
|
break;
|
2022-10-12 19:10:39 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const isOnline = useRouteQuery("isOnline", false, booleanTransformer);
|
|
|
|
const categoryOneOf = useRouteQuery("categoryOneOf", [], arrayTransformer);
|
|
|
|
const statusOneOf = useRouteQuery(
|
|
|
|
"statusOneOf",
|
|
|
|
[EventStatus.CONFIRMED],
|
|
|
|
arrayTransformer
|
|
|
|
);
|
|
|
|
const languageOneOf = useRouteQuery("languageOneOf", [], arrayTransformer);
|
2022-08-26 16:08:58 +02:00
|
|
|
const searchTarget = useRouteQuery(
|
|
|
|
"target",
|
|
|
|
SearchTargets.INTERNAL,
|
|
|
|
enumTransformer(SearchTargets)
|
|
|
|
);
|
2022-09-01 10:00:17 +02:00
|
|
|
const mode = useRouteQuery("mode", ViewMode.LIST, enumTransformer(ViewMode));
|
|
|
|
const sortBy = useRouteQuery(
|
|
|
|
"sortBy",
|
|
|
|
SortValues.MATCH_DESC,
|
|
|
|
enumTransformer(SortValues)
|
|
|
|
);
|
|
|
|
const bbox = useRouteQuery("bbox", undefined);
|
|
|
|
const zoom = useRouteQuery("zoom", undefined, integerTransformer);
|
2022-07-12 10:55:28 +02:00
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const EVENT_PAGE_LIMIT = 16;
|
2022-07-12 10:55:28 +02:00
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const GROUP_PAGE_LIMIT = 16;
|
2022-07-12 10:55:28 +02:00
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const { features } = useFeatures();
|
|
|
|
const { eventCategories } = useEventCategories();
|
2022-07-12 10:55:28 +02:00
|
|
|
|
2022-10-28 10:39:42 +02:00
|
|
|
const orderedCategories = computed(() => {
|
|
|
|
if (!eventCategories.value) return [];
|
|
|
|
return lodashSortBy(eventCategories.value, ["label"]);
|
|
|
|
});
|
|
|
|
|
2022-07-12 10:55:28 +02:00
|
|
|
const searchEvents = computed(() => searchElementsResult.value?.searchEvents);
|
|
|
|
const searchGroups = computed(() => searchElementsResult.value?.searchGroups);
|
|
|
|
|
|
|
|
const { result: currentUserResult } = useQuery<{ currentUser: ICurrentUser }>(
|
|
|
|
CURRENT_USER_CLIENT
|
|
|
|
);
|
|
|
|
|
|
|
|
const currentUser = computed(() => currentUserResult.value?.currentUser);
|
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const { t } = useI18n({ useScope: "global" });
|
2022-07-12 10:55:28 +02:00
|
|
|
|
|
|
|
useHead({
|
|
|
|
title: computed(() => t("Explore events")),
|
|
|
|
});
|
|
|
|
|
|
|
|
const dateFnsLocale = inject<Locale>("dateFnsLocale");
|
|
|
|
|
|
|
|
const weekend = computed((): { start: Date; end: Date } => {
|
|
|
|
const now = new Date();
|
|
|
|
const endOfWeekDate = endOfWeek(now, { locale: dateFnsLocale });
|
|
|
|
const startOfWeekDate = startOfWeek(now, { locale: dateFnsLocale });
|
|
|
|
const [start, end] = eachWeekendOfInterval({
|
|
|
|
start: startOfWeekDate,
|
|
|
|
end: endOfWeekDate,
|
|
|
|
});
|
|
|
|
return { start: startOfDay(start), end: endOfDay(end) };
|
|
|
|
});
|
|
|
|
|
|
|
|
const dateOptions: Record<string, ISearchTimeOption> = {
|
|
|
|
past: {
|
|
|
|
label: t("In the past") as string,
|
|
|
|
start: null,
|
2022-08-22 12:12:09 +02:00
|
|
|
end: new Date().toISOString(),
|
2022-07-12 10:55:28 +02:00
|
|
|
},
|
|
|
|
today: {
|
|
|
|
label: t("Today") as string,
|
2022-08-22 12:12:09 +02:00
|
|
|
start: new Date().toISOString(),
|
|
|
|
end: endOfToday().toISOString(),
|
2022-07-12 10:55:28 +02:00
|
|
|
},
|
|
|
|
tomorrow: {
|
|
|
|
label: t("Tomorrow") as string,
|
2022-08-22 12:12:09 +02:00
|
|
|
start: startOfDay(addDays(new Date(), 1)).toISOString(),
|
|
|
|
end: endOfDay(addDays(new Date(), 1)).toISOString(),
|
2022-07-12 10:55:28 +02:00
|
|
|
},
|
|
|
|
weekend: {
|
|
|
|
label: t("This weekend") as string,
|
2022-08-22 12:12:09 +02:00
|
|
|
start: weekend.value.start.toISOString(),
|
|
|
|
end: weekend.value.end.toISOString(),
|
2022-07-12 10:55:28 +02:00
|
|
|
},
|
|
|
|
week: {
|
|
|
|
label: t("This week") as string,
|
2022-08-22 12:12:09 +02:00
|
|
|
start: new Date().toISOString(),
|
|
|
|
end: endOfWeek(new Date(), { locale: dateFnsLocale }).toISOString(),
|
2022-07-12 10:55:28 +02:00
|
|
|
},
|
|
|
|
next_week: {
|
|
|
|
label: t("Next week") as string,
|
|
|
|
start: startOfWeek(addWeeks(new Date(), 1), {
|
|
|
|
locale: dateFnsLocale,
|
2022-08-22 12:12:09 +02:00
|
|
|
}).toISOString(),
|
|
|
|
end: endOfWeek(addWeeks(new Date(), 1), {
|
|
|
|
locale: dateFnsLocale,
|
|
|
|
}).toISOString(),
|
2022-07-12 10:55:28 +02:00
|
|
|
},
|
|
|
|
month: {
|
|
|
|
label: t("This month") as string,
|
2022-08-22 12:12:09 +02:00
|
|
|
start: new Date().toISOString(),
|
|
|
|
end: endOfMonth(new Date()).toISOString(),
|
2022-07-12 10:55:28 +02:00
|
|
|
},
|
|
|
|
next_month: {
|
|
|
|
label: t("Next month") as string,
|
2022-08-22 12:12:09 +02:00
|
|
|
start: startOfMonth(addMonths(new Date(), 1)).toISOString(),
|
|
|
|
end: endOfMonth(addMonths(new Date(), 1)).toISOString(),
|
2022-07-12 10:55:28 +02:00
|
|
|
},
|
|
|
|
any: {
|
|
|
|
label: t("Any day") as string,
|
2022-08-26 16:08:58 +02:00
|
|
|
start: new Date().toISOString(),
|
|
|
|
end: null,
|
2022-07-12 10:55:28 +02:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const start = computed((): string | undefined | null => {
|
2022-07-12 10:55:28 +02:00
|
|
|
if (dateOptions[when.value]) {
|
|
|
|
return dateOptions[when.value].start;
|
|
|
|
}
|
|
|
|
return undefined;
|
|
|
|
});
|
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const end = computed((): string | undefined | null => {
|
2022-07-12 10:55:28 +02:00
|
|
|
if (dateOptions[when.value]) {
|
|
|
|
return dateOptions[when.value].end;
|
|
|
|
}
|
|
|
|
return undefined;
|
|
|
|
});
|
|
|
|
|
|
|
|
const searchIsUrl = computed((): boolean => {
|
|
|
|
let url;
|
2022-08-26 16:08:58 +02:00
|
|
|
if (!searchDebounced.value) return false;
|
2022-07-12 10:55:28 +02:00
|
|
|
try {
|
2022-08-26 16:08:58 +02:00
|
|
|
url = new URL(searchDebounced.value);
|
2022-07-12 10:55:28 +02:00
|
|
|
} catch (_) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return url.protocol === "http:" || url.protocol === "https:";
|
|
|
|
});
|
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const contentTypeMapping = computed(() => {
|
|
|
|
return [
|
|
|
|
{
|
|
|
|
contentType: "ALL",
|
|
|
|
label: t("Everything"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
contentType: "EVENTS",
|
|
|
|
label: t("Events"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
contentType: "GROUPS",
|
|
|
|
label: t("Groups"),
|
|
|
|
},
|
|
|
|
];
|
|
|
|
});
|
2022-07-12 10:55:28 +02:00
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const eventDistance = computed(() => {
|
|
|
|
return [
|
|
|
|
{
|
|
|
|
id: "anywhere",
|
|
|
|
label: t("Any distance"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: "5_km",
|
|
|
|
label: t(
|
|
|
|
"{number} kilometers",
|
|
|
|
{
|
|
|
|
number: 5,
|
|
|
|
},
|
|
|
|
5
|
|
|
|
),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: "10_km",
|
|
|
|
label: t(
|
|
|
|
"{number} kilometers",
|
|
|
|
{
|
|
|
|
number: 10,
|
|
|
|
},
|
|
|
|
10
|
|
|
|
),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: "25_km",
|
|
|
|
label: t(
|
|
|
|
"{number} kilometers",
|
|
|
|
{
|
|
|
|
number: 25,
|
|
|
|
},
|
|
|
|
25
|
|
|
|
),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: "50_km",
|
|
|
|
label: t(
|
|
|
|
"{number} kilometers",
|
|
|
|
{
|
|
|
|
number: 50,
|
|
|
|
},
|
|
|
|
50
|
|
|
|
),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: "100_km",
|
|
|
|
label: t(
|
|
|
|
"{number} kilometers",
|
|
|
|
{
|
|
|
|
number: 100,
|
|
|
|
},
|
|
|
|
100
|
|
|
|
),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: "150_km",
|
|
|
|
label: t(
|
|
|
|
"{number} kilometers",
|
|
|
|
{
|
|
|
|
number: 150,
|
|
|
|
},
|
|
|
|
150
|
|
|
|
),
|
|
|
|
},
|
|
|
|
];
|
|
|
|
});
|
2022-07-12 10:55:28 +02:00
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const eventStatuses = computed(() => {
|
|
|
|
return [
|
|
|
|
{
|
|
|
|
id: EventStatus.CONFIRMED,
|
|
|
|
label: t("Confirmed"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: EventStatus.TENTATIVE,
|
|
|
|
label: t("Tentative"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: EventStatus.CANCELLED,
|
|
|
|
label: t("Cancelled"),
|
|
|
|
},
|
|
|
|
];
|
2022-07-12 10:55:28 +02:00
|
|
|
});
|
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const searchFilterSectionsOpenStatus = ref({
|
|
|
|
eventDate: true,
|
|
|
|
eventLanguage: false,
|
|
|
|
eventCategory: false,
|
|
|
|
eventStatus: false,
|
|
|
|
eventDistance: false,
|
|
|
|
});
|
|
|
|
|
|
|
|
const filtersPanelOpened = ref(true);
|
|
|
|
|
|
|
|
const toggleFilters = () =>
|
|
|
|
(filtersPanelOpened.value = !filtersPanelOpened.value);
|
|
|
|
|
2022-08-22 13:47:10 +02:00
|
|
|
const geoHashLocation = computed(() =>
|
|
|
|
coordsToGeoHash(latitude.value, longitude.value)
|
|
|
|
);
|
|
|
|
|
|
|
|
const radius = computed(() => Number.parseInt(distance.value.slice(0, -3)));
|
|
|
|
|
2022-09-01 10:00:17 +02:00
|
|
|
const totalCount = computed(() => {
|
|
|
|
return (searchEvents.value?.total ?? 0) + (searchGroups.value?.total ?? 0);
|
|
|
|
});
|
|
|
|
|
|
|
|
const sortOptions = computed(() => {
|
|
|
|
const options = [
|
|
|
|
{
|
|
|
|
key: SortValues.MATCH_DESC,
|
|
|
|
label: t("Best match"),
|
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
if (contentType.value == ContentType.EVENTS) {
|
|
|
|
options.push(
|
|
|
|
{
|
|
|
|
key: SortValues.START_TIME_DESC,
|
|
|
|
label: t("Event date"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
key: SortValues.CREATED_AT_DESC,
|
|
|
|
label: t("Most recently published"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
key: SortValues.CREATED_AT_ASC,
|
|
|
|
label: t("Least recently published"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
key: SortValues.PARTICIPANT_COUNT_DESC,
|
|
|
|
label: t("With the most participants"),
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (contentType.value == ContentType.GROUPS) {
|
|
|
|
options.push({
|
|
|
|
key: SortValues.MEMBER_COUNT_DESC,
|
|
|
|
label: t("Number of members"),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return options;
|
|
|
|
});
|
|
|
|
|
2022-08-26 16:08:58 +02:00
|
|
|
const { searchConfig, onResult: onSearchConfigResult } = useSearchConfig();
|
|
|
|
|
|
|
|
onSearchConfigResult(({ data }) =>
|
|
|
|
handleSearchConfigChanged(data?.config?.search)
|
|
|
|
);
|
|
|
|
|
|
|
|
const handleSearchConfigChanged = (
|
|
|
|
searchConfigChanged: IConfig["search"] | undefined
|
|
|
|
) => {
|
|
|
|
if (
|
|
|
|
searchConfigChanged?.global?.isEnabled &&
|
|
|
|
searchConfigChanged?.global?.isDefault
|
|
|
|
) {
|
|
|
|
searchTarget.value = SearchTargets.GLOBAL;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
watch(searchConfig, (newSearchConfig) =>
|
|
|
|
handleSearchConfigChanged(newSearchConfig)
|
|
|
|
);
|
|
|
|
|
|
|
|
const globalSearchEnabled = computed(
|
|
|
|
() => searchConfig.value?.global?.isEnabled
|
|
|
|
);
|
|
|
|
|
2022-09-01 10:00:17 +02:00
|
|
|
const setBounds = ({
|
|
|
|
bounds,
|
|
|
|
zoom: boundsZoom,
|
|
|
|
}: {
|
|
|
|
bounds: LatLngBounds;
|
|
|
|
zoom: number;
|
|
|
|
}) => {
|
|
|
|
bbox.value = `${bounds.getNorthWest().lat}, ${bounds.getNorthWest().lng}:${
|
|
|
|
bounds.getSouthEast().lat
|
|
|
|
}, ${bounds.getSouthEast().lng}`;
|
|
|
|
zoom.value = boundsZoom;
|
|
|
|
};
|
|
|
|
|
|
|
|
watch(mode, (newMode) => {
|
|
|
|
if (newMode === ViewMode.MAP) {
|
|
|
|
isOnline.value = false;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
watch(isOnline, (newIsOnline) => {
|
|
|
|
if (newIsOnline) {
|
|
|
|
mode.value = ViewMode.LIST;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2022-09-26 10:29:20 +02:00
|
|
|
const sortByForType = (
|
|
|
|
value: SortValues,
|
|
|
|
allowed: typeof EventSortValues | typeof GroupSortValues
|
|
|
|
): SortValues | undefined => {
|
|
|
|
return Object.values(allowed).includes(value) ? value : undefined;
|
|
|
|
};
|
|
|
|
|
|
|
|
const boostLanguagesQuery = computed((): string[] => {
|
|
|
|
const languages = new Set<string>();
|
|
|
|
|
|
|
|
for (const completeLanguage of navigator.languages) {
|
|
|
|
const language = completeLanguage.split("-")[0];
|
|
|
|
|
|
|
|
if (Object.keys(langs).find((langKey) => langKey === language)) {
|
|
|
|
languages.add(language);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return Array.from(languages);
|
|
|
|
});
|
|
|
|
|
2022-08-22 12:12:09 +02:00
|
|
|
const { result: searchElementsResult, loading: searchLoading } = useQuery<{
|
2022-09-01 10:00:17 +02:00
|
|
|
searchEvents: Paginate<TypeNamed<IEvent>>;
|
|
|
|
searchGroups: Paginate<TypeNamed<IGroup>>;
|
2022-08-22 12:12:09 +02:00
|
|
|
}>(SEARCH_EVENTS_AND_GROUPS, () => ({
|
2022-08-26 16:08:58 +02:00
|
|
|
term: searchDebounced.value,
|
2022-08-22 12:12:09 +02:00
|
|
|
tags: props.tag,
|
2022-08-22 13:47:10 +02:00
|
|
|
location: geoHashLocation.value,
|
2022-08-22 12:12:09 +02:00
|
|
|
beginsOn: start.value,
|
|
|
|
endsOn: end.value,
|
2022-08-26 16:08:58 +02:00
|
|
|
radius: geoHashLocation.value ? radius.value : undefined,
|
2022-10-12 19:10:39 +02:00
|
|
|
eventPage:
|
|
|
|
contentType.value === ContentType.ALL ? page.value : eventPage.value,
|
|
|
|
groupPage:
|
|
|
|
contentType.value === ContentType.ALL ? page.value : groupPage.value,
|
2022-08-22 12:12:09 +02:00
|
|
|
limit: EVENT_PAGE_LIMIT,
|
2022-08-26 16:08:58 +02:00
|
|
|
type: isOnline.value ? "ONLINE" : undefined,
|
2022-08-22 12:12:09 +02:00
|
|
|
categoryOneOf: categoryOneOf.value,
|
|
|
|
statusOneOf: statusOneOf.value,
|
2022-08-26 16:08:58 +02:00
|
|
|
languageOneOf: languageOneOf.value,
|
|
|
|
searchTarget: searchTarget.value,
|
2022-09-26 10:29:20 +02:00
|
|
|
bbox: mode.value === ViewMode.MAP ? bbox.value : undefined,
|
2022-09-01 10:00:17 +02:00
|
|
|
zoom: zoom.value,
|
2022-09-26 10:29:20 +02:00
|
|
|
sortByEvents: sortByForType(sortBy.value, EventSortValues),
|
|
|
|
sortByGroups: sortByForType(sortBy.value, GroupSortValues),
|
|
|
|
boostLanguages: boostLanguagesQuery.value,
|
2022-08-22 12:12:09 +02:00
|
|
|
}));
|
2022-07-12 10:55:28 +02:00
|
|
|
</script>
|