diff --git a/components/NavSearch.vue b/components/NavSearch.vue
index 05b9e292..9eaf2973 100644
--- a/components/NavSearch.vue
+++ b/components/NavSearch.vue
@@ -1,20 +1,51 @@
- #navsearch.mt-2.mt-sm-4(v-if='showCollectionsBar || showSearchBar')
- v-text-field.mx-2(v-if='showSearchBar' outlined dense hide-details :placeholder='$t("common.search")' :append-icon='mdiMagnify' @input='search' clearable :clear-icon='mdiClose')
- template(v-slot:prepend-inner)
- Calendar(v-if='!settings.hide_calendar')
- v-btn.ml-2.mt-2.gap-2(v-if='showCollectionsBar' small outlined v-for='collection in collections' color='primary' :key='collection.id' :to='`/collection/${encodeURIComponent(collection.name)}`') {{collection.name}}
+ #navsearch.mt-2.mt-sm-4(v-if='showCollectionsBar || showSearchBar || showCalendar')
+
+ div.mx-2
+ client-only(v-if='showSearchBar')
+ v-menu(offset-y :close-on-content-click='false' tile)
+ template(v-slot:activator="{on ,attrs}")
+ v-text-field(hide-details outlined
+ :placeholder='$t("common.search")'
+ @input="v => setFilter(['query', v])" clearable :clear-icon='mdiClose')
+ template(v-slot:append)
+ v-icon(v-text='mdiCog' v-bind='attrs' v-on='on')
+ v-card(outlined :rounded='"0"')
+ v-card-text
+ v-row(dense)
+ v-col(v-if='settings.allow_recurrent_event')
+ v-switch.mt-0(v-model='show_recurrent' @change="v => setFilter(['show_recurrent', v])"
+ hide-details :label="$t('event.show_recurrent')" inset)
+ v-col(v-if='settings.allow_multidate_event')
+ v-switch.mt-0(v-model='show_multidate' @change="v => setFilter(['show_multidate', v])"
+ hide-details :label="$t('event.show_multidate')" inset)
+ v-row(v-if='!showCalendar')
+ v-col
+ Calendar.mt-2
+ v-text-field(slot='placeholder' outlined hide-details :placeholder="$t('common.search')" :append-icon='mdiCog')
+
+ span(v-if='showCollectionsBar')
+ v-btn.mr-2.mt-2(small outlined v-for='collection in collections'
+ color='primary' :key='collection.id'
+ :to='`/collection/${encodeURIComponent(collection.name)}`') {{collection.name}}
+
+ Calendar.mt-2(v-if='showCalendar')
+
+
\ No newline at end of file
+
diff --git a/components/WhereInput.vue b/components/WhereInput.vue
index 1b41a0ee..75eb3615 100644
--- a/components/WhereInput.vue
+++ b/components/WhereInput.vue
@@ -125,9 +125,14 @@ export default {
return matches
}
},
+ mounted () {
+ this.$nextTick( () => {
+ this.search()
+ })
+ },
methods: {
search: debounce(async function(ev) {
- const search = ev.target.value.trim().toLowerCase()
+ const search = ev ? ev.target.value.trim().toLowerCase() : ''
this.places = await this.$axios.$get(`place?search=${search}`)
if (!search && this.places.length) { return this.places }
const matches = this.places.find(p => search === p.name.toLocaleLowerCase())
diff --git a/locales/en.json b/locales/en.json
index cc9756c5..ae87414a 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -147,6 +147,7 @@
"recurrent": "Recurring",
"edit_recurrent": "Edit recurring event:",
"show_recurrent": "recurring events",
+ "show_multidate": "multidate events",
"show_past": "also prior events",
"only_future": "only upcoming events",
"recurrent_description": "Choose frequency and select days",
diff --git a/pages/add/_edit.vue b/pages/add/_edit.vue
index 5c58d976..8132de1a 100644
--- a/pages/add/_edit.vue
+++ b/pages/add/_edit.vue
@@ -179,7 +179,6 @@ export default {
filteredTags() {
if (!this.tagName) { return this.tags.slice(0, 10).map(t => t.tag) }
const tagName = this.tagName.trim().toLowerCase()
- console.log(tagName)
return this.tags.filter(t => t.tag.toLowerCase().includes(tagName)).map(t => t.tag)
}
},
@@ -245,6 +244,8 @@ export default {
if (this.date.dueHour) {
[hour, minute] = this.date.dueHour.split(':')
formData.append('end_datetime', dayjs(this.date.due).hour(Number(hour)).minute(Number(minute)).second(0).unix())
+ } else if (!!this.date.multidate) {
+ formData.append('end_datetime', dayjs(this.date.due).hour(24).minute(0).second(0).unix())
}
if (this.edit) {
diff --git a/pages/index.vue b/pages/index.vue
index b127c77a..33e1dac6 100644
--- a/pages/index.vue
+++ b/pages/index.vue
@@ -1,6 +1,5 @@
v-container.px-2.px-sm-6.pt-0
-
//- Announcements
#announcements.mt-2.mt-sm-4(v-if='announcements.length')
Announcement(v-for='announcement in announcements' :key='`a_${announcement.id}`' :announcement='announcement')
@@ -41,7 +40,8 @@ export default {
searching: false,
tmpEvents: [],
selectedDay: null,
- show_recurrent: $store.state.settings.recurrent_event_visible,
+ storeUnsubscribe: null
+
}
},
head () {
@@ -63,53 +63,61 @@ export default {
}
},
computed: {
- ...mapState(['settings', 'announcements', 'events']),
+ ...mapState(['settings', 'announcements', 'events', 'filter']),
visibleEvents () {
- if (this.searching) {
+ if (this.filter.query) {
return this.tmpEvents
}
const now = dayjs().unix()
if (this.selectedDay) {
const min = dayjs.tz(this.selectedDay).startOf('day').unix()
const max = dayjs.tz(this.selectedDay).endOf('day').unix()
- return this.events.filter(e => (e.start_datetime <= max && (e.end_datetime || e.start_datetime) >= min) && (this.show_recurrent || !e.parentId))
+ return this.events.filter(e => (e.start_datetime <= max && (e.end_datetime || e.start_datetime) >= min) && (this.filter.show_recurrent || !e.parentId))
} else if (this.isCurrentMonth) {
- return this.events.filter(e => ((e.end_datetime ? e.end_datetime > now : e.start_datetime + 3 * 60 * 60 > now) && (this.show_recurrent || !e.parentId)))
+ return this.events.filter(e => ((e.end_datetime ? e.end_datetime > now : e.start_datetime + 3 * 60 * 60 > now) && (this.filter.show_recurrent || !e.parentId)))
} else {
- return this.events.filter(e => this.show_recurrent || !e.parentId)
+ return this.events.filter(e => this.filter.show_recurrent || !e.parentId)
}
}
},
- created () {
+ mounted () {
this.$root.$on('dayclick', this.dayChange)
this.$root.$on('monthchange', this.monthChange)
- this.$root.$on('search', debounce(this.search, 100))
+ this.storeUnsubscribe = this.$store.subscribeAction( { after: (action, state) => {
+ if (action.type === 'setFilter') {
+ if (this.filter.query) {
+ this.search()
+ } else {
+ this.updateEvents()
+ }
+ }
+ }})
+ console.error(this.storeUnsubscribe)
},
destroyed () {
this.$root.$off('dayclick')
this.$root.$off('monthchange')
- this.$root.$off('search')
+ if (typeof this.storeUnsubscribe === 'function') {
+ this.storeUnsubscribe()
+ }
},
methods: {
...mapActions(['getEvents']),
- async search (query) {
- if (query) {
- this.tmpEvents = await this.$axios.$get(`/event/search?search=${query}`)
- this.searching = true
- } else {
- this.tmpEvents = null
- this.searching = false
- }
- },
+ search: debounce(async function() {
+ this.tmpEvents = await this.$api.getEvents({
+ start: 0,
+ show_recurrent: this.filter.show_recurrent,
+ show_multidate: this.filter.show_multidate,
+ query: this.filter.query
+ })
+ }, 100),
updateEvents () {
return this.getEvents({
start: this.start,
- end: this.end,
- show_recurrent: true
+ end: this.end
})
},
async monthChange ({ year, month }) {
-
this.$nuxt.$loading.start()
this.$nextTick( async () => {
diff --git a/plugins/api.js b/plugins/api.js
index 8ddec24f..94bd3cbf 100644
--- a/plugins/api.js
+++ b/plugins/api.js
@@ -18,12 +18,15 @@ export default ({ $axios }, inject) => {
try {
const events = await $axios.$get('/events', {
params: {
- start: params.start,
- end: params.end,
+ ...params,
+ // start: params.start,
+ // end: params.end,
places: params.places && params.places.join(','),
tags: params.tags && params.tags.join(','),
- show_recurrent: !!params.show_recurrent,
- max: params.maxs
+ // ...(params.show_recurrent !== && {show_recurrent: !!params.show_recurrent}),
+ // show_multidate: !!params.show_multidate,
+ // query: params.query,
+ // max: params.maxs
}
})
return events.map(e => Object.freeze(e))
diff --git a/server/api/controller/event.js b/server/api/controller/event.js
index 9a16aacf..e84f03dc 100644
--- a/server/api/controller/event.js
+++ b/server/api/controller/event.js
@@ -3,7 +3,6 @@ const path = require('path')
const config = require('../../config')
const fs = require('fs')
const { Op } = require('sequelize')
-const intersection = require('lodash/intersection')
const linkifyHtml = require('linkify-html')
const Sequelize = require('sequelize')
const dayjs = require('dayjs')
@@ -87,71 +86,71 @@ const eventController = {
},
- async search(req, res) {
- const search = req.query.search.trim().toLocaleLowerCase()
- const show_recurrent = req.query.show_recurrent || false
- const end = req.query.end
- const replacements = []
+ // async search(req, res) {
+ // const search = req.query.search.trim().toLocaleLowerCase()
+ // const show_recurrent = req.query.show_recurrent || false
+ // const end = req.query.end
+ // const replacements = []
- const where = {
- // do not include parent recurrent event
- recurrent: null,
+ // const where = {
+ // // do not include parent recurrent event
+ // recurrent: null,
- // confirmed event only
- is_visible: true,
+ // // confirmed event only
+ // is_visible: true,
- }
+ // }
- if (!show_recurrent) {
- where.parentId = null
- }
+ // if (!show_recurrent) {
+ // where.parentId = null
+ // }
- if (end) {
- where.start_datetime = { [Op.lte]: end }
- }
+ // if (end) {
+ // where.start_datetime = { [Op.lte]: end }
+ // }
- if (search) {
- replacements.push(search)
- where[Op.or] =
- [
- { title: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('title')), 'LIKE', '%' + search + '%') },
- Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), 'LIKE', '%' + search + '%'),
- Sequelize.fn('EXISTS', Sequelize.literal(`SELECT 1 FROM event_tags WHERE ${Col('event_tags.eventId')}=${Col('event.id')} AND LOWER(${Col('tagTag')}) = ?`))
- ]
- }
+ // if (search) {
+ // replacements.push(search)
+ // where[Op.or] =
+ // [
+ // { title: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('title')), 'LIKE', '%' + search + '%') },
+ // Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), 'LIKE', '%' + search + '%'),
+ // Sequelize.fn('EXISTS', Sequelize.literal(`SELECT 1 FROM event_tags WHERE ${Col('event_tags.eventId')}=${Col('event.id')} AND LOWER(${Col('tagTag')}) = ?`))
+ // ]
+ // }
- const events = await Event.findAll({
- where,
- attributes: {
- exclude: ['likes', 'boost', 'userId', 'is_visible', 'createdAt', 'updatedAt', 'description', 'resources']
- },
- order: [['start_datetime', 'DESC']],
- include: [
- {
- model: Tag,
- // order: [Sequelize.literal('(SELECT COUNT("tagTag") FROM event_tags WHERE tagTag = tag) DESC')],
- attributes: ['tag'],
- through: { attributes: [] }
- },
- { model: Place, required: true, attributes: ['id', 'name', 'address', 'latitude', 'longitude'] }
- ],
- replacements,
- limit: 30,
- }).catch(e => {
- log.error('[EVENT]', e)
- return res.json([])
- })
+ // const events = await Event.findAll({
+ // where,
+ // attributes: {
+ // exclude: ['likes', 'boost', 'userId', 'is_visible', 'createdAt', 'updatedAt', 'description', 'resources']
+ // },
+ // order: [['start_datetime', 'DESC']],
+ // include: [
+ // {
+ // model: Tag,
+ // // order: [Sequelize.literal('(SELECT COUNT("tagTag") FROM event_tags WHERE tagTag = tag) DESC')],
+ // attributes: ['tag'],
+ // through: { attributes: [] }
+ // },
+ // { model: Place, required: true, attributes: ['id', 'name', 'address', 'latitude', 'longitude'] }
+ // ],
+ // replacements,
+ // limit: 30,
+ // }).catch(e => {
+ // log.error('[EVENT]', e)
+ // return res.json([])
+ // })
- const ret = events.map(e => {
- e = e.get()
- e.tags = e.tags ? e.tags.map(t => t && t.tag) : []
- return e
- })
+ // const ret = events.map(e => {
+ // e = e.get()
+ // e.tags = e.tags ? e.tags.map(t => t && t.tag) : []
+ // return e
+ // })
- return res.json(ret)
+ // return res.json(ret)
- },
+ // },
async _get(slug) {
// retrocompatibility, old events URL does not use slug, use id as fallback
@@ -600,9 +599,11 @@ const eventController = {
async _select({
start = dayjs().unix(),
end,
+ query,
tags,
places,
show_recurrent,
+ show_multidate,
limit,
page,
older }) {
@@ -625,6 +626,10 @@ const eventController = {
where.parentId = null
}
+ if (!show_multidate) {
+ where.multidate = { [Op.not]: true }
+ }
+
if (end) {
where.start_datetime = { [older ? Op.gte : Op.lte]: end }
}
@@ -648,6 +653,16 @@ const eventController = {
where.placeId = places.split(',')
}
+ if (query) {
+ replacements.push(query)
+ where[Op.or] =
+ [
+ { title: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('title')), 'LIKE', '%' + query + '%') },
+ Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), 'LIKE', '%' + query + '%'),
+ Sequelize.fn('EXISTS', Sequelize.literal(`SELECT 1 FROM event_tags WHERE ${Col('event_tags.eventId')}=${Col('event.id')} AND LOWER(${Col('tagTag')}) = ?`))
+ ]
+ }
+
let pagination = {}
if (limit) {
pagination = {
@@ -692,17 +707,21 @@ const eventController = {
const settings = res.locals.settings
const start = req.query.start || dayjs().unix()
const end = req.query.end
+ const query = req.query.query
const tags = req.query.tags
const places = req.query.places
const limit = Number(req.query.max) || 0
const page = Number(req.query.page) || 0
const older = req.query.older || false
+ const show_multidate = settings.allow_multidate_event &&
+ typeof req.query.show_multidate !== 'undefined' ? req.query.show_multidate !== 'false' : true
+
const show_recurrent = settings.allow_recurrent_event &&
typeof req.query.show_recurrent !== 'undefined' ? req.query.show_recurrent === 'true' : settings.recurrent_event_visible
res.json(await eventController._select({
- start, end, places, tags, show_recurrent, limit, page, older
+ start, end, query, places, tags, show_recurrent, show_multidate, limit, page, older
}))
},
diff --git a/server/api/index.js b/server/api/index.js
index 8d1ccbc9..a9a53ce9 100644
--- a/server/api/index.js
+++ b/server/api/index.js
@@ -91,6 +91,7 @@ module.exports = () => {
* @type GET
* @param {integer} [start] - start timestamp (default: now)
* @param {integer} [end] - end timestamp (optional)
+ * @param {string} [query] - search for this string
* @param {array} [tags] - List of tags
* @param {array} [places] - List of places id
* @param {integer} [max] - Limit events
@@ -128,7 +129,7 @@ module.exports = () => {
// allow anyone to add an event (anon event has to be confirmed, TODO: flood protection)
api.post('/event', eventController.isAnonEventAllowed, upload.single('image'), eventController.add)
- api.get('/event/search', eventController.search)
+ // api.get('/event/search', eventController.search)
api.put('/event', isAuth, upload.single('image'), eventController.update)
api.get('/event/import', isAuth, helpers.importURL)
diff --git a/store/index.js b/store/index.js
index 090ac2ca..b9586bb1 100644
--- a/store/index.js
+++ b/store/index.js
@@ -23,6 +23,11 @@ export const state = () => ({
trusted_instances_label: '',
footerLinks: []
},
+ filter: {
+ query: '',
+ show_recurrent: null,
+ show_multidate: null
+ },
announcements: [],
events: []
})
@@ -39,6 +44,9 @@ export const mutations = {
},
setEvents (state, events) {
state.events = events
+ },
+ setFilter (state, { type, value }) {
+ state.filter[type] = value
}
}
@@ -47,6 +55,9 @@ export const actions = {
// we use it to get configuration from db, set locale, etc...
nuxtServerInit ({ commit }, { _req, res }) {
commit('setSettings', res.locals.settings)
+ commit('setFilter', { type: 'show_recurrent',
+ value: res.locals.settings.allow_recurrent_event && res.locals.settings.recurrent_event_visible })
+
if (res.locals.status === 'READY') {
commit('setAnnouncements', res.locals.announcements)
}
@@ -62,11 +73,15 @@ export const actions = {
await this.$axios.$post('/settings', setting)
commit('setSetting', setting)
},
+ setFilter ({ commit }, [type, value]) {
+ commit('setFilter', { type, value })
+ },
async getEvents ({ commit, state }, params = {}) {
const events = await this.$api.getEvents({
start: params.start || dayjs().startOf('month').unix(),
end: params.end || null,
- show_recurrent: params.show_recurrent || state.settings.recurrent_event_visible
+ show_recurrent: state.filter.show_recurrent,
+ show_multidate: state.filter.show_multidate
})
commit('setEvents', events)
return events