mirror of
https://framagit.org/framasoft/mobilizon.git
synced 2024-12-21 23:44:30 +00:00
Merge branch 'bugs' into 'master'
Allow to search events by online status See merge request framasoft/mobilizon!1098
This commit is contained in:
commit
4f739d8672
11 changed files with 276 additions and 108 deletions
|
@ -39,34 +39,36 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="media-content">
|
<div class="media-content">
|
||||||
<p class="event-title" :title="event.title">{{ event.title }}</p>
|
<h3 class="event-title" :title="event.title">{{ event.title }}</h3>
|
||||||
<div class="event-organizer">
|
<div class="content-end">
|
||||||
<figure
|
<div class="event-organizer">
|
||||||
class="image is-24x24"
|
<figure
|
||||||
v-if="organizer(event) && organizer(event).avatar"
|
class="image is-24x24"
|
||||||
|
v-if="organizer(event) && organizer(event).avatar"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="is-rounded"
|
||||||
|
:src="organizer(event).avatar.url"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</figure>
|
||||||
|
<b-icon v-else icon="account-circle" />
|
||||||
|
<span class="organizer-name">
|
||||||
|
{{ organizerDisplayName(event) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<inline-address
|
||||||
|
v-if="event.physicalAddress"
|
||||||
|
class="event-subtitle"
|
||||||
|
:physical-address="event.physicalAddress"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="event-subtitle"
|
||||||
|
v-else-if="event.options && event.options.isOnline"
|
||||||
>
|
>
|
||||||
<img
|
<b-icon icon="video" />
|
||||||
class="is-rounded"
|
<span>{{ $t("Online") }}</span>
|
||||||
:src="organizer(event).avatar.url"
|
</div>
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
</figure>
|
|
||||||
<b-icon v-else icon="account-circle" />
|
|
||||||
<span class="organizer-name">
|
|
||||||
{{ organizerDisplayName(event) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<inline-address
|
|
||||||
v-if="event.physicalAddress"
|
|
||||||
class="event-subtitle"
|
|
||||||
:physical-address="event.physicalAddress"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="event-subtitle"
|
|
||||||
v-else-if="event.options && event.options.isOnline"
|
|
||||||
>
|
|
||||||
<b-icon icon="video" />
|
|
||||||
<span>{{ $t("Online") }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -201,12 +203,14 @@ a.card {
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-content {
|
.card-content {
|
||||||
|
height: 100%;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
|
|
||||||
& > .media {
|
& > .media {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
& > .media-left {
|
& > .media-left {
|
||||||
margin-top: -15px;
|
margin-top: -15px;
|
||||||
|
@ -222,6 +226,9 @@ a.card {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow-x: inherit;
|
overflow-x: inherit;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,9 +27,9 @@ export default class MultiCard extends Vue {
|
||||||
.multi-card-event {
|
.multi-card-event {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-auto-rows: 1fr;
|
grid-auto-rows: 1fr;
|
||||||
grid-column-gap: 30px;
|
grid-column-gap: 20px;
|
||||||
grid-row-gap: 30px;
|
grid-row-gap: 30px;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||||
.event-card {
|
.event-card {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="media mb-3">
|
<div class="media mb-2">
|
||||||
<div class="media-left">
|
<div class="media-left">
|
||||||
<figure class="image is-48x48" v-if="group.avatar">
|
<figure class="image is-48x48" v-if="group.avatar">
|
||||||
<img class="is-rounded" :src="group.avatar.url" alt="" />
|
<img class="is-rounded" :src="group.avatar.url" alt="" />
|
||||||
|
@ -24,12 +24,12 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="media-content">
|
<div class="media-content">
|
||||||
<h3 class="is-size-5 group-title">{{ displayName(group) }}</h3>
|
<h3 class="is-size-5 group-title">{{ displayName(group) }}</h3>
|
||||||
<span class="is-6 has-text-grey-dark">
|
<span class="is-6 has-text-grey-dark group-federated-username">
|
||||||
{{ `@${usernameWithDomain(group)}` }}
|
{{ `@${usernameWithDomain(group)}` }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="content" v-html="group.summary" />
|
<div class="content mb-2" v-html="group.summary" />
|
||||||
<div class="card-custom-footer">
|
<div class="card-custom-footer">
|
||||||
<inline-address
|
<inline-address
|
||||||
class="has-text-grey-dark"
|
class="has-text-grey-dark"
|
||||||
|
@ -37,6 +37,7 @@
|
||||||
:physicalAddress="group.physicalAddress"
|
:physicalAddress="group.physicalAddress"
|
||||||
/>
|
/>
|
||||||
<p class="has-text-grey-dark">
|
<p class="has-text-grey-dark">
|
||||||
|
<b-icon icon="account" />
|
||||||
{{
|
{{
|
||||||
$tc(
|
$tc(
|
||||||
"{count} members or followers",
|
"{count} members or followers",
|
||||||
|
@ -117,13 +118,16 @@ export default class GroupCard extends Vue {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
.group-title {
|
.group-title {
|
||||||
line-height: 1.75rem;
|
line-height: 1.5rem;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: 3;
|
-webkit-line-clamp: 3;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
.group-federated-username {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -158,8 +158,6 @@ export const FETCH_EVENTS = gql`
|
||||||
url
|
url
|
||||||
}
|
}
|
||||||
publishAt
|
publishAt
|
||||||
# online_address,
|
|
||||||
# phone_address,
|
|
||||||
physicalAddress {
|
physicalAddress {
|
||||||
...AdressFragment
|
...AdressFragment
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
import { ACTOR_FRAGMENT } from "./actor";
|
import { ACTOR_FRAGMENT } from "./actor";
|
||||||
import { ADDRESS_FRAGMENT } from "./address";
|
import { ADDRESS_FRAGMENT } from "./address";
|
||||||
|
import { EVENT_OPTIONS_FRAGMENT } from "./event_options";
|
||||||
import { TAG_FRAGMENT } from "./tags";
|
import { TAG_FRAGMENT } from "./tags";
|
||||||
|
|
||||||
export const SEARCH_EVENTS_AND_GROUPS = gql`
|
export const SEARCH_EVENTS_AND_GROUPS = gql`
|
||||||
|
@ -9,9 +10,11 @@ export const SEARCH_EVENTS_AND_GROUPS = gql`
|
||||||
$radius: Float
|
$radius: Float
|
||||||
$tags: String
|
$tags: String
|
||||||
$term: String
|
$term: String
|
||||||
|
$type: EventType
|
||||||
$beginsOn: DateTime
|
$beginsOn: DateTime
|
||||||
$endsOn: DateTime
|
$endsOn: DateTime
|
||||||
$page: Int
|
$eventPage: Int
|
||||||
|
$groupPage: Int
|
||||||
$limit: Int
|
$limit: Int
|
||||||
) {
|
) {
|
||||||
searchEvents(
|
searchEvents(
|
||||||
|
@ -19,9 +22,10 @@ export const SEARCH_EVENTS_AND_GROUPS = gql`
|
||||||
radius: $radius
|
radius: $radius
|
||||||
tags: $tags
|
tags: $tags
|
||||||
term: $term
|
term: $term
|
||||||
|
type: $type
|
||||||
beginsOn: $beginsOn
|
beginsOn: $beginsOn
|
||||||
endsOn: $endsOn
|
endsOn: $endsOn
|
||||||
page: $page
|
page: $eventPage
|
||||||
limit: $limit
|
limit: $limit
|
||||||
) {
|
) {
|
||||||
total
|
total
|
||||||
|
@ -46,6 +50,9 @@ export const SEARCH_EVENTS_AND_GROUPS = gql`
|
||||||
attributedTo {
|
attributedTo {
|
||||||
...ActorFragment
|
...ActorFragment
|
||||||
}
|
}
|
||||||
|
options {
|
||||||
|
...EventOptions
|
||||||
|
}
|
||||||
__typename
|
__typename
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +60,7 @@ export const SEARCH_EVENTS_AND_GROUPS = gql`
|
||||||
term: $term
|
term: $term
|
||||||
location: $location
|
location: $location
|
||||||
radius: $radius
|
radius: $radius
|
||||||
page: $page
|
page: $groupPage
|
||||||
limit: $limit
|
limit: $limit
|
||||||
) {
|
) {
|
||||||
total
|
total
|
||||||
|
@ -75,6 +82,7 @@ export const SEARCH_EVENTS_AND_GROUPS = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
${EVENT_OPTIONS_FRAGMENT}
|
||||||
${TAG_FRAGMENT}
|
${TAG_FRAGMENT}
|
||||||
${ADDRESS_FRAGMENT}
|
${ADDRESS_FRAGMENT}
|
||||||
${ACTOR_FRAGMENT}
|
${ACTOR_FRAGMENT}
|
||||||
|
|
|
@ -1230,5 +1230,7 @@
|
||||||
"Clear date filter field": "Clear date filter field",
|
"Clear date filter field": "Clear date filter field",
|
||||||
"{count} members or followers": "No members or followers|One member or follower|{count} members or followers",
|
"{count} members or followers": "No members or followers|One member or follower|{count} members or followers",
|
||||||
"This profile is from another instance, the informations shown here may be incomplete.": "This profile is from another instance, the informations shown here may be incomplete.",
|
"This profile is from another instance, the informations shown here may be incomplete.": "This profile is from another instance, the informations shown here may be incomplete.",
|
||||||
"View full profile": "View full profile"
|
"View full profile": "View full profile",
|
||||||
|
"Any type": "Any type",
|
||||||
|
"In person": "In person"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1334,5 +1334,7 @@
|
||||||
"Clear date filter field": "Vider le champ de filtre de la date",
|
"Clear date filter field": "Vider le champ de filtre de la date",
|
||||||
"{count} members or followers": "Aucun⋅e membre ou abonné⋅e|Un⋅e membre ou abonné⋅e|{count} membres ou abonné⋅es",
|
"{count} members or followers": "Aucun⋅e membre ou abonné⋅e|Un⋅e membre ou abonné⋅e|{count} membres ou abonné⋅es",
|
||||||
"This profile is from another instance, the informations shown here may be incomplete.": "Ce profil provient d'une autre instance, les informations montrées ici peuvent être incomplètes.",
|
"This profile is from another instance, the informations shown here may be incomplete.": "Ce profil provient d'une autre instance, les informations montrées ici peuvent être incomplètes.",
|
||||||
"View full profile": "Voir le profil complet"
|
"View full profile": "Voir le profil complet",
|
||||||
|
"Any type": "N'importe quel type",
|
||||||
|
"In person": "En personne"
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,8 @@ export interface IEventParticipantStats {
|
||||||
going: number;
|
going: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type EventType = "IN_PERSON" | "ONLINE" | null;
|
||||||
|
|
||||||
interface IEventEditJSON {
|
interface IEventEditJSON {
|
||||||
id?: string;
|
id?: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
|
|
@ -9,56 +9,84 @@
|
||||||
<section class="hero is-light" v-else>
|
<section class="hero is-light" v-else>
|
||||||
<div class="hero-body">
|
<div class="hero-body">
|
||||||
<form @submit.prevent="submit()">
|
<form @submit.prevent="submit()">
|
||||||
<b-field :label="$t('Key words')" label-for="search" expanded>
|
<b-field
|
||||||
|
class="searchQuery"
|
||||||
|
:label="$t('Key words')"
|
||||||
|
label-for="search"
|
||||||
|
>
|
||||||
<b-input
|
<b-input
|
||||||
icon="magnify"
|
icon="magnify"
|
||||||
type="search"
|
type="search"
|
||||||
id="search"
|
id="search"
|
||||||
size="is-large"
|
:value="search"
|
||||||
expanded
|
@input="debouncedUpdateSearchQuery"
|
||||||
v-model="search"
|
|
||||||
:placeholder="
|
:placeholder="
|
||||||
$t('For instance: London, Taekwondo, Architecture…')
|
$t('For instance: London, Taekwondo, Architecture…')
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</b-field>
|
</b-field>
|
||||||
<b-field grouped group-multiline position="is-right" expanded>
|
<full-address-auto-complete
|
||||||
<b-field :label="$t('Location')" label-for="location">
|
class="searchLocation"
|
||||||
<address-auto-complete
|
:label="$t('Location')"
|
||||||
v-model="location"
|
v-model="location"
|
||||||
id="location"
|
id="location"
|
||||||
ref="aac"
|
ref="aac"
|
||||||
:placeholder="$t('For instance: London')"
|
:placeholder="$t('For instance: London')"
|
||||||
@input="locchange"
|
@input="locchange"
|
||||||
/>
|
/>
|
||||||
</b-field>
|
<b-field
|
||||||
<b-field :label="$t('Radius')" label-for="radius">
|
:label="$t('Radius')"
|
||||||
<b-select v-model="radius" id="radius" expanded>
|
label-for="radius"
|
||||||
<option
|
class="searchRadius"
|
||||||
v-for="(radiusOption, index) in radiusOptions"
|
>
|
||||||
:key="index"
|
<b-select expanded v-model="radius" id="radius">
|
||||||
:value="radiusOption"
|
<option
|
||||||
>
|
v-for="(radiusOption, index) in radiusOptions"
|
||||||
{{ radiusString(radiusOption) }}
|
:key="index"
|
||||||
</option>
|
:value="radiusOption"
|
||||||
</b-select>
|
|
||||||
</b-field>
|
|
||||||
<b-field :label="$t('Date')" label-for="date">
|
|
||||||
<b-select
|
|
||||||
v-model="when"
|
|
||||||
id="date"
|
|
||||||
:disabled="activeTab !== 0"
|
|
||||||
expanded
|
|
||||||
>
|
>
|
||||||
<option
|
{{ radiusString(radiusOption) }}
|
||||||
v-for="(option, index) in options"
|
</option>
|
||||||
:key="index"
|
</b-select>
|
||||||
:value="index"
|
</b-field>
|
||||||
>
|
<b-field :label="$t('Date')" label-for="date" class="searchDate">
|
||||||
{{ option.label }}
|
<b-select
|
||||||
</option>
|
expanded
|
||||||
</b-select>
|
v-model="when"
|
||||||
</b-field>
|
id="date"
|
||||||
|
:disabled="activeTab !== 0"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="(option, index) in dateOptions"
|
||||||
|
:key="index"
|
||||||
|
:value="index"
|
||||||
|
>
|
||||||
|
{{ option.label }}
|
||||||
|
</option>
|
||||||
|
</b-select>
|
||||||
|
</b-field>
|
||||||
|
<b-field
|
||||||
|
expanded
|
||||||
|
:label="$t('Type')"
|
||||||
|
label-for="type"
|
||||||
|
class="searchType"
|
||||||
|
>
|
||||||
|
<b-select
|
||||||
|
expanded
|
||||||
|
v-model="type"
|
||||||
|
id="type"
|
||||||
|
:disabled="activeTab !== 0"
|
||||||
|
>
|
||||||
|
<option :value="null">
|
||||||
|
{{ $t("Any type") }}
|
||||||
|
</option>
|
||||||
|
<option :value="'ONLINE'">
|
||||||
|
{{ $t("Online") }}
|
||||||
|
</option>
|
||||||
|
<option :value="'IN_PERSON'">
|
||||||
|
{{ $t("In person") }}
|
||||||
|
</option>
|
||||||
|
</b-select>
|
||||||
</b-field>
|
</b-field>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -171,16 +199,17 @@ import {
|
||||||
import { SearchTabs } from "@/types/enums";
|
import { SearchTabs } from "@/types/enums";
|
||||||
import MultiCard from "../components/Event/MultiCard.vue";
|
import MultiCard from "../components/Event/MultiCard.vue";
|
||||||
import { FETCH_EVENTS } from "../graphql/event";
|
import { FETCH_EVENTS } from "../graphql/event";
|
||||||
import { IEvent } from "../types/event.model";
|
import { EventType, IEvent } from "../types/event.model";
|
||||||
import RouteName from "../router/name";
|
import RouteName from "../router/name";
|
||||||
import { IAddress, Address } from "../types/address.model";
|
import { IAddress, Address } from "../types/address.model";
|
||||||
import AddressAutoComplete from "../components/Event/AddressAutoComplete.vue";
|
import FullAddressAutoComplete from "../components/Event/FullAddressAutoComplete.vue";
|
||||||
import { SEARCH_EVENTS_AND_GROUPS } from "../graphql/search";
|
import { SEARCH_EVENTS_AND_GROUPS } from "../graphql/search";
|
||||||
import { Paginate } from "../types/paginate";
|
import { Paginate } from "../types/paginate";
|
||||||
import { IGroup } from "../types/actor";
|
import { IGroup } from "../types/actor";
|
||||||
import MultiGroupCard from "../components/Group/MultiGroupCard.vue";
|
import MultiGroupCard from "../components/Group/MultiGroupCard.vue";
|
||||||
import { CONFIG } from "../graphql/config";
|
import { CONFIG } from "../graphql/config";
|
||||||
import { REVERSE_GEOCODE } from "../graphql/address";
|
import { REVERSE_GEOCODE } from "../graphql/address";
|
||||||
|
import debounce from "lodash/debounce";
|
||||||
|
|
||||||
interface ISearchTimeOption {
|
interface ISearchTimeOption {
|
||||||
label: string;
|
label: string;
|
||||||
|
@ -198,12 +227,10 @@ const DEFAULT_ZOOM = 11; // zoom on a city
|
||||||
|
|
||||||
const GEOHASH_DEPTH = 9; // put enough accuracy, radius will be used anyway
|
const GEOHASH_DEPTH = 9; // put enough accuracy, radius will be used anyway
|
||||||
|
|
||||||
const THROTTLE = 2000; // minimum interval in ms between two requests
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
MultiCard,
|
MultiCard,
|
||||||
AddressAutoComplete,
|
FullAddressAutoComplete,
|
||||||
MultiGroupCard,
|
MultiGroupCard,
|
||||||
},
|
},
|
||||||
apollo: {
|
apollo: {
|
||||||
|
@ -217,7 +244,7 @@ const THROTTLE = 2000; // minimum interval in ms between two requests
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
search: {
|
searchElements: {
|
||||||
query: SEARCH_EVENTS_AND_GROUPS,
|
query: SEARCH_EVENTS_AND_GROUPS,
|
||||||
fetchPolicy: "cache-and-network",
|
fetchPolicy: "cache-and-network",
|
||||||
variables() {
|
variables() {
|
||||||
|
@ -228,15 +255,16 @@ const THROTTLE = 2000; // minimum interval in ms between two requests
|
||||||
beginsOn: this.start,
|
beginsOn: this.start,
|
||||||
endsOn: this.end,
|
endsOn: this.end,
|
||||||
radius: this.radius,
|
radius: this.radius,
|
||||||
page: this.eventPage,
|
eventPage: this.eventPage,
|
||||||
|
groupPage: this.groupPage,
|
||||||
limit: EVENT_PAGE_LIMIT,
|
limit: EVENT_PAGE_LIMIT,
|
||||||
|
type: this.type,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
update(data) {
|
update(data) {
|
||||||
this.searchEvents = data.searchEvents;
|
this.searchEvents = data.searchEvents;
|
||||||
this.searchGroups = data.searchGroups;
|
this.searchGroups = data.searchGroups;
|
||||||
},
|
},
|
||||||
throttle: THROTTLE,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
metaInfo() {
|
metaInfo() {
|
||||||
|
@ -261,11 +289,9 @@ export default class Search extends Vue {
|
||||||
|
|
||||||
searchGroups: Paginate<IGroup> = { total: 0, elements: [] };
|
searchGroups: Paginate<IGroup> = { total: 0, elements: [] };
|
||||||
|
|
||||||
groupPage = 1;
|
|
||||||
|
|
||||||
location: IAddress = new Address();
|
location: IAddress = new Address();
|
||||||
|
|
||||||
options: Record<string, ISearchTimeOption> = {
|
dateOptions: Record<string, ISearchTimeOption> = {
|
||||||
today: {
|
today: {
|
||||||
label: this.$t("Today") as string,
|
label: this.$t("Today") as string,
|
||||||
start: new Date(),
|
start: new Date(),
|
||||||
|
@ -315,9 +341,15 @@ export default class Search extends Vue {
|
||||||
GROUP_PAGE_LIMIT = GROUP_PAGE_LIMIT;
|
GROUP_PAGE_LIMIT = GROUP_PAGE_LIMIT;
|
||||||
|
|
||||||
$refs!: {
|
$refs!: {
|
||||||
aac: AddressAutoComplete;
|
aac: FullAddressAutoComplete;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
data(): Record<string, unknown> {
|
||||||
|
return {
|
||||||
|
debouncedUpdateSearchQuery: debounce(this.updateSearchQuery, 200),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
mounted(): void {
|
mounted(): void {
|
||||||
this.prepareLocation(this.$route.query.geohash as string);
|
this.prepareLocation(this.$route.query.geohash as string);
|
||||||
}
|
}
|
||||||
|
@ -335,6 +367,10 @@ export default class Search extends Vue {
|
||||||
this.$apollo.queries.searchEvents.refetch();
|
this.$apollo.queries.searchEvents.refetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateSearchQuery(searchQuery: string): void {
|
||||||
|
this.search = searchQuery;
|
||||||
|
}
|
||||||
|
|
||||||
get eventPage(): number {
|
get eventPage(): number {
|
||||||
return parseInt(this.$route.query.eventPage as string, 10) || 1;
|
return parseInt(this.$route.query.eventPage as string, 10) || 1;
|
||||||
}
|
}
|
||||||
|
@ -346,6 +382,17 @@ export default class Search extends Vue {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get groupPage(): number {
|
||||||
|
return parseInt(this.$route.query.groupPage as string, 10) || 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
set groupPage(page: number) {
|
||||||
|
this.$router.push({
|
||||||
|
name: this.$route.name || RouteName.SEARCH,
|
||||||
|
query: { ...this.$route.query, groupPage: page.toString() },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
get search(): string | undefined {
|
get search(): string | undefined {
|
||||||
return this.$route.query.term as string;
|
return this.$route.query.term as string;
|
||||||
}
|
}
|
||||||
|
@ -411,6 +458,23 @@ export default class Search extends Vue {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get type(): EventType {
|
||||||
|
return this.$route.query.type as EventType;
|
||||||
|
}
|
||||||
|
|
||||||
|
set type(type: EventType) {
|
||||||
|
const query = { ...this.$route.query, type };
|
||||||
|
if (type == null) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
delete query.type;
|
||||||
|
}
|
||||||
|
this.$router.replace({
|
||||||
|
name: RouteName.SEARCH,
|
||||||
|
query,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
get weekend(): { start: Date; end: Date } {
|
get weekend(): { start: Date; end: Date } {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const endOfWeekDate = endOfWeek(now, { locale: this.$dateFnsLocale });
|
const endOfWeekDate = endOfWeek(now, { locale: this.$dateFnsLocale });
|
||||||
|
@ -453,22 +517,24 @@ export default class Search extends Vue {
|
||||||
if (this.radius === undefined || this.radius === null) {
|
if (this.radius === undefined || this.radius === null) {
|
||||||
this.radius = DEFAULT_RADIUS;
|
this.radius = DEFAULT_RADIUS;
|
||||||
}
|
}
|
||||||
if (e.geom) {
|
if (e?.geom) {
|
||||||
const [lon, lat] = e.geom.split(";");
|
const [lon, lat] = e.geom.split(";");
|
||||||
this.geohash = ngeohash.encode(lat, lon, GEOHASH_DEPTH);
|
this.geohash = ngeohash.encode(lat, lon, GEOHASH_DEPTH);
|
||||||
|
} else {
|
||||||
|
this.geohash = undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
get start(): Date | undefined {
|
get start(): Date | undefined {
|
||||||
if (this.options[this.when]) {
|
if (this.dateOptions[this.when]) {
|
||||||
return this.options[this.when].start;
|
return this.dateOptions[this.when].start;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
get end(): Date | undefined | null {
|
get end(): Date | undefined | null {
|
||||||
if (this.options[this.when]) {
|
if (this.dateOptions[this.when]) {
|
||||||
return this.options[this.when].end;
|
return this.dateOptions[this.when].end;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -484,6 +550,7 @@ export default class Search extends Vue {
|
||||||
return (
|
return (
|
||||||
this.stringExists(this.search) ||
|
this.stringExists(this.search) ||
|
||||||
this.stringExists(this.tag) ||
|
this.stringExists(this.tag) ||
|
||||||
|
this.stringExists(this.type) ||
|
||||||
(this.stringExists(this.geohash) && this.valueExists(this.radius)) ||
|
(this.stringExists(this.geohash) && this.valueExists(this.radius)) ||
|
||||||
this.valueExists(this.end)
|
this.valueExists(this.end)
|
||||||
);
|
);
|
||||||
|
@ -494,13 +561,14 @@ export default class Search extends Vue {
|
||||||
return value !== undefined && value !== null;
|
return value !== undefined && value !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private stringExists(value: string | undefined): boolean {
|
private stringExists(value: string | null | undefined): boolean {
|
||||||
return this.valueExists(value) && (value as string).length > 0;
|
return this.valueExists(value) && (value as string).length > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
@import "~bulma/sass/utilities/mixins.sass";
|
||||||
main > .container {
|
main > .container {
|
||||||
background: $white;
|
background: $white;
|
||||||
|
|
||||||
|
@ -526,19 +594,73 @@ h3.title {
|
||||||
}
|
}
|
||||||
|
|
||||||
form {
|
form {
|
||||||
::v-deep .field label.label {
|
// ::v-deep .field label.label {
|
||||||
margin-bottom: 0;
|
// margin-bottom: 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// .field.is-expanded:last-child > .field-body > .field.is-grouped {
|
||||||
|
// flex-wrap: wrap;
|
||||||
|
// flex: 1;
|
||||||
|
// .field {
|
||||||
|
// flex: 1 0 auto;
|
||||||
|
// &:first-child {
|
||||||
|
// flex: 3 0 300px;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 0 15px;
|
||||||
|
grid-template-areas: "query" "location" "radius" "date" "type";
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
margin-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.field.is-expanded:last-child > .field-body > .field.is-grouped {
|
@include tablet {
|
||||||
flex-wrap: wrap;
|
grid-template-columns: max-content max-content max-content auto;
|
||||||
flex: 1;
|
grid-template-areas: "query . ." "location . ." "radius date type";
|
||||||
.field {
|
}
|
||||||
flex: 1 0 auto;
|
|
||||||
&:first-child {
|
@include desktop {
|
||||||
flex: 3 0 300px;
|
grid-template-columns: max-content max-content max-content 1fr 3fr;
|
||||||
}
|
grid-template-areas: "query . location" "radius date type";
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchQuery {
|
||||||
|
grid-area: query;
|
||||||
|
@include tablet {
|
||||||
|
grid-column: span 4;
|
||||||
}
|
}
|
||||||
|
@include desktop {
|
||||||
|
grid-column-start: 1;
|
||||||
|
grid-column-end: 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchLocation {
|
||||||
|
grid-area: location;
|
||||||
|
:v-deep .column {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
@include tablet {
|
||||||
|
grid-column: span 4;
|
||||||
|
}
|
||||||
|
@include desktop {
|
||||||
|
grid-column-start: 4;
|
||||||
|
grid-column-end: 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchRadius {
|
||||||
|
grid-area: radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchDate {
|
||||||
|
grid-area: date;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchType {
|
||||||
|
grid-area: type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -44,6 +44,15 @@ defmodule Mobilizon.GraphQL.Schema.SearchType do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
enum :event_type do
|
||||||
|
value(:in_person,
|
||||||
|
description:
|
||||||
|
"The event will happen in person. It can also be livestreamed, but has a physical address"
|
||||||
|
)
|
||||||
|
|
||||||
|
value(:online, description: "The event will only happen online. It has no physical address")
|
||||||
|
end
|
||||||
|
|
||||||
object :search_queries do
|
object :search_queries do
|
||||||
@desc "Search persons"
|
@desc "Search persons"
|
||||||
field :search_persons, :persons do
|
field :search_persons, :persons do
|
||||||
|
@ -83,6 +92,7 @@ defmodule Mobilizon.GraphQL.Schema.SearchType do
|
||||||
arg(:term, :string, default_value: "")
|
arg(:term, :string, default_value: "")
|
||||||
arg(:tags, :string, description: "A comma-separated string listing the tags")
|
arg(:tags, :string, description: "A comma-separated string listing the tags")
|
||||||
arg(:location, :string, description: "A geohash for coordinates")
|
arg(:location, :string, description: "A geohash for coordinates")
|
||||||
|
arg(:type, :event_type, description: "Whether the event is online or in person")
|
||||||
|
|
||||||
arg(:radius, :float,
|
arg(:radius, :float,
|
||||||
default_value: 50,
|
default_value: 50,
|
||||||
|
|
|
@ -506,6 +506,7 @@ defmodule Mobilizon.Events do
|
||||||
|> events_for_ends_on(args)
|
|> events_for_ends_on(args)
|
||||||
|> events_for_tags(args)
|
|> events_for_tags(args)
|
||||||
|> events_for_location(args)
|
|> events_for_location(args)
|
||||||
|
|> filter_online(args)
|
||||||
|> filter_draft()
|
|> filter_draft()
|
||||||
|> filter_local_or_from_followed_instances_events()
|
|> filter_local_or_from_followed_instances_events()
|
||||||
|> filter_public_visibility()
|
|> filter_public_visibility()
|
||||||
|
@ -1307,6 +1308,18 @@ defmodule Mobilizon.Events do
|
||||||
|
|
||||||
defp events_for_location(query, _args), do: query
|
defp events_for_location(query, _args), do: query
|
||||||
|
|
||||||
|
@spec filter_online(Ecto.Query.t(), map()) :: Ecto.Query.t()
|
||||||
|
defp filter_online(query, %{type: :online}), do: is_online_fragment(query, true)
|
||||||
|
|
||||||
|
defp filter_online(query, %{type: :in_person}), do: is_online_fragment(query, false)
|
||||||
|
|
||||||
|
defp filter_online(query, _), do: query
|
||||||
|
|
||||||
|
@spec is_online_fragment(Ecto.Query.t(), boolean()) :: Ecto.Query.t()
|
||||||
|
defp is_online_fragment(query, value) do
|
||||||
|
where(query, [q], fragment("(?->>'is_online')::bool = ?", q.options, ^value))
|
||||||
|
end
|
||||||
|
|
||||||
@spec normalize_search_string(String.t()) :: String.t()
|
@spec normalize_search_string(String.t()) :: String.t()
|
||||||
defp normalize_search_string(search_string) do
|
defp normalize_search_string(search_string) do
|
||||||
search_string
|
search_string
|
||||||
|
|
Loading…
Reference in a new issue