diff --git a/components/MapEdit.vue b/components/MapEdit.vue new file mode 100644 index 00000000..e6cf1bec --- /dev/null +++ b/components/MapEdit.vue @@ -0,0 +1,70 @@ + + + + diff --git a/components/WhereInput.vue b/components/WhereInput.vue index 1baea189..abf1625a 100644 --- a/components/WhereInput.vue +++ b/components/WhereInput.vue @@ -24,7 +24,22 @@ v-row.mb-4 v-col(cols=12 md=6) - v-text-field(v-if="!settings.allow_geolocation" + v-row.mx-0.my-0.align-center.justify-center + v-combobox.mr-4(v-model="virtualLocations" v-if="settings.allow_event_only_online && value.name === 'online'" + :prepend-icon='mdiLink' + :hint="`Online locations, for instance a url to a videconference room`" + :label="$t('event.online_event_urls')" + clearable chips small-chips multiple deletable-chips hide-no-data hide-selected persistent-hint + :delimiters="[',', ';', '; ']" + :items="virtualLocations" + @change='selectLocations') + template(v-slot:selection="{ item, on, attrs, selected, parent }") + v-chip(v-bind="attrs" close :close-icon='mdiCloseCircle' @click:close='parent.selectItem(item)' + :input-value="selected" label small) {{ item }} + template(v-slot:append) + v-icon(v-text='mdiCog' :disabled='!value.name' @click="whereInputAdvancedDialog = true") + + v-text-field.mr-4(v-if="!settings.allow_geolocation && value.name !== 'online'" ref='address' :prepend-icon='mdiMap' :disabled='disableAddress' @@ -34,57 +49,66 @@ v-row.mb-4 persistent-hint @change="changeAddress" :value="value.address") - v-combobox(ref='address' v-else - :prepend-icon='mdiMapSearch' - :disabled='disableAddress' - @input.native='searchAddress' - :label="$t('common.address')" - :rules="[ v => disableAddress ? true : $validators.required('common.address')(v)]" - :value='value.address' - item-text='address' - persistent-hint hide-no-data clearable no-filter - :loading='loading' - @change='selectAddress' - @focus='searchAddress' - :items="addressList" - :hint="$t('event.address_description_osm')") - template(v-slot:message="{message, key}") - span(v-html='message' :key="key") - template(v-slot:item="{ item, attrs, on }") - v-list-item(v-bind='attrs' v-on='on') - v-icon.pr-4(v-text='loadCoordinatesResultIcon(item)') - v-list-item-content(two-line v-if='item') - v-list-item-title(v-text='item.name') - v-list-item-subtitle(v-text='`${item.address}`') - //- v-col(cols=12 md=3 v-if='settings.allow_geolocation') - //- v-text-field(ref='latitude' :value='value.latitude' - //- :prepend-icon='mdiLatitude' - //- :disabled='disableDetails' - //- :label="$t('common.latitude')" ) - //- v-col(cols=12 md=3 v-if='settings.allow_geolocation') - //- v-text-field(ref='longitude' :value='value.longitude' - //- :prepend-icon='mdiLongitude' - //- :disabled='disableDetails' - //- :label="$t('common.longitude')") + template(v-slot:append v-if="settings.allow_event_also_online && place.name !== 'online'") + v-icon(v-text='mdiCog' :disabled='!value.name' @click="whereInputAdvancedDialog = true") + + v-combobox(ref='address' v-if="settings.allow_geolocation && value.name !== 'online' || (!settings.allow_event_only_online && value.name === 'online')" + :prepend-icon='mdiMapSearch' + :disabled='disableAddress' + @input.native='searchAddress' + :label="$t('common.address')" + :rules="[ v => disableAddress ? true : $validators.required('common.address')(v)]" + :value='value.address' + item-text='address' + persistent-hint hide-no-data clearable no-filter + :loading='loading' + @change='selectAddress' + @focus='searchAddress' + :items="addressList" + :hint="$t('event.address_description_osm')") + template(v-slot:message="{message, key}") + span(v-html='message' :key="key") + template(v-slot:item="{ item, attrs, on }") + v-list-item(v-bind='attrs' v-on='on') + v-icon.pr-4(v-text='loadCoordinatesResultIcon(item)') + v-list-item-content(two-line v-if='item') + v-list-item-title(v-text='item.name') + v-list-item-subtitle(v-text='`${item.address}`') + template(v-slot:append v-if="settings.allow_event_also_online || settings.allow_geolocation") + v-icon(v-text='mdiCog' :disabled='!value.name || (!value.isNew && !settings.allow_event_also_online) ' @click="whereInputAdvancedDialog = true") + v-dialog(v-model='whereInputAdvancedDialog' :key="whereAdvancedId" destroy-on-close max-width='700px' :fullscreen='$vuetify.breakpoint.xsOnly' dense) + WhereInputAdvanced(ref='whereAdvanced' :place.sync='value' :event='event' @close='whereInputAdvancedDialog = false && this.$refs.address.blur()' + :virtualLocations.sync="virtualLocations" + :online_event_only_value.sync='online_event_only' + @update:onlineEvent="changeOnlineEvent" + @update:virtualLocations="selectLocations" + ) + + diff --git a/components/WhereInputAdvanced.vue b/components/WhereInputAdvanced.vue new file mode 100644 index 00000000..665e7088 --- /dev/null +++ b/components/WhereInputAdvanced.vue @@ -0,0 +1,104 @@ + + diff --git a/components/admin/Places.vue b/components/admin/Places.vue index 6c8c26b7..7edf1204 100644 --- a/components/admin/Places.vue +++ b/components/admin/Places.vue @@ -11,32 +11,57 @@ v-container v-dialog(v-model='dialog' width='600' :fullscreen='$vuetify.breakpoint.xsOnly') v-card v-card-title {{ $t('admin.edit_place') }} - v-card-text + v-card-text.mb-4 v-form(v-model='valid' ref='form' lazy-validation) v-text-field( :rules="[$validators.required('common.name')]" :label="$t('common.name')" v-model='place.name' :placeholder='$t("common.name")') - - v-combobox(ref='address' + + v-text-field( + :rules="[ v => $validators.required('common.address')(v)]" + :label="$t('common.address')" + v-model='place.address' + persistent-hint) + + v-combobox.mt-0.mb-4(ref='geocodedAddress' + v-if="(settings.allow_geolocation && place.name !== 'online')" + :disabled="!(settings.allow_geolocation && place.name !== 'online')" :prepend-icon='mdiMapSearch' @input.native='searchAddress' - :label="$t('common.address')" - :rules="[ v => $validators.required('common.address')(v)]" - :value='place.address' + :label="$t('admin.search_address')" + :value='place.latitude && place.longitude && place.geocodedAddress' persistent-hint hide-no-data clearable no-filter :loading='loading' @change='selectAddress' @focus='searchAddress' :items="addressList" - :hint="$t('event.address_description')") + :hint="$t('event.address_description_osm')") + template(v-slot:message="{message, key}") + span(v-html='message' :key="key") template(v-slot:item="{ item, attrs, on }") v-list-item(v-bind='attrs' v-on='on') v-list-item-content(two-line v-if='item') v-list-item-title(v-text='item.name') v-list-item-subtitle(v-text='`${item.address}`') + v-row.mt-4(v-if="(settings.allow_geolocation && place.name !== 'online')") + v-col.py-0(cols=12 md=6) + v-text-field(v-model="place.latitude" + :value="place.latitude" + :prepend-icon='mdiLatitude' + :disabled="(!settings.allow_geolocation || place.name === 'online')" + :label="$t('common.latitude')" + :rules="$validators.latitude") + v-col.py-0(cols=12 md=6) + v-text-field(v-model="place.longitude" + :prepend-icon='mdiLongitude' + :disabled="!settings.allow_geolocation || place.name === 'online'" + :label="$t('common.longitude')" + :rules="$validators.longitude") + + MapEdit.mt-4(:place='place' :key="mapEdit" v-if="settings.allow_geolocation && place.name !== 'online' && place.latitude && place.longitude") v-card-actions v-spacer @@ -62,15 +87,22 @@ v-container diff --git a/components/admin/Settings.vue b/components/admin/Settings.vue index 4bb3cd97..2b457976 100644 --- a/components/admin/Settings.vue +++ b/components/admin/Settings.vue @@ -56,6 +56,14 @@ v-container inset :label="$t('admin.allow_geolocation')") + v-switch.mt-1(v-model='allow_event_only_online' + inset + :label="$t('admin.allow_event_only_online')") + + v-switch.mt-1(v-model='allow_event_also_online' + inset + :label="$t('admin.allow_event_also_online')") + v-dialog(v-model='showSMTP' destroy-on-close max-width='700px' :fullscreen='$vuetify.breakpoint.xsOnly') SMTP(@close='showSMTP = false') @@ -126,6 +134,18 @@ export default { get () { return this.settings.allow_geolocation }, set (value) { this.setSetting({ key: 'allow_geolocation', value }) } }, + allow_event_only_online: { + get () { return this.settings.allow_event_only_online }, + set (value) { this.setSetting({ key: 'allow_event_only_online', value }) + if (value == true) { this.allow_event_also_online = value } + } + }, + allow_event_also_online: { + get () { return this.settings.allow_event_also_online }, + set (value) { this.setSetting({ key: 'allow_event_also_online', value }) + if (value == false) { this.setSetting({ key: 'allow_event_only_online', value }) } + } + }, filteredTimezones () { const current_timezone = moment.tz.guess() tzNames.unshift(current_timezone) diff --git a/locales/en.json b/locales/en.json index ae87414a..d9064a6b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -98,7 +98,9 @@ "about": "About", "content": "Content", "admin_actions": "Admin actions", - "recurring_event_actions": "Recurring event actions" + "recurring_event_actions": "Recurring event actions", + "latitude": "Latitude", + "longitude": "Longitude" }, "login": { "description": "By logging in you can publish new events.", @@ -178,7 +180,21 @@ "alt_text_description": "Description for people with visual impairments", "choose_focal_point": "Choose the focal point", "remove_media_confirmation": "Do you confirm the image removal?", - "download_flyer": "Download flyer" + "download_flyer": "Download flyer", + "where_advanced_options": "Place - Advanced options", + "where_advanced_options_description": "Define here additional place properties to the event", + "online_event_only": "Online event", + "event_only_online_label": "Event only online", + "online_event_only_help": "For online-only event, the default place name 'online' is applied", + "online_event_too": "Partecipate remotely", + "online_event_label": "Online locations", + "online_event_urls": "Online locations", + "online_event_fallback_urls": "Fallback links", + "additional_online_locations": "Additional online locations", + "additional_online_locations_help": "Online locations, for instance a url to a videconference room", + "address_geocoded_disclaimer": "Didn't you found the address or housenumber you are looking for? The OpenStreetMap project is open to contributions. If you have Android, we recommend StreetComplete ", + "address_overwrite": "Overwrite Address", + "address_overwrite_help": "Overwrite the geocoded address, for instance to add missing housenumber or a floor information" }, "admin": { "place_description": "If you have gotten the place or address wrong, you can change it.
All current and past events associated with this place will change address.", @@ -294,7 +310,10 @@ "tilelayer_test_button": "Test tilelayer", "tilelayer_test_success": "The tilelayer service at {service_name} is working", "tilelayer_test_error": "The tilelayer service is not reachable at {service_name}", - "geolocation": "Geolocation" + "geolocation": "Geolocation", + "allow_event_only_online": "Allow event only online", + "allow_event_also_online": "Allow event also online", + "search_address": "Search address" }, "auth": { "not_confirmed": "Not confirmed yet…", @@ -327,7 +346,9 @@ }, "validators": { "required": "{fieldName} is required", - "email": "Insert a valid email" + "email": "Insert a valid email", + "latitude": "Insert a valid latitude (-90 < latitude < 90)", + "longitude": "Insert a valid latitude (-180 < latitude < 180)" }, "about": "\n

Gancio is a shared agenda for local communities.

\n ", "oauth": { diff --git a/pages/add/_edit.vue b/pages/add/_edit.vue index b5162cf8..fcfc0553 100644 --- a/pages/add/_edit.vue +++ b/pages/add/_edit.vue @@ -30,7 +30,7 @@ v-container.container.pa-0.pa-md-3 //- Where v-col(cols=12) - WhereInput(ref='where' v-model='event.place') + WhereInput(ref='where' v-model='event.place' :event='event') //- When DateInput(ref='when' v-model='date' :event='event') @@ -125,6 +125,9 @@ export default { data.event.place.name = event.place.name data.event.place.address = event.place.address || '' + data.event.place.latitude = event.place.latitude || '' + data.event.place.longitude = event.place.longitude || '' + data.event.locations = event.locations || [] const from = dayjs.unix(event.start_datetime) const due = event.end_datetime && dayjs.unix(event.end_datetime) data.date = { @@ -154,6 +157,7 @@ export default { openImportDialog: false, event: { place: { name: '', address: '', latitude: null, longitude: null }, + locations: [], title: '', description: '', tags: [], @@ -237,6 +241,15 @@ export default { formData.append('place_address', this.event.place.address) formData.append('place_latitude', this.event.place.latitude) formData.append('place_longitude', this.event.place.longitude) + + if (this.event.locations.length) { + this.event.locations.forEach(location => formData.append('locations[]', location.url)) + } else { + // delete + this.event.locations = [] + formData.append('locations', this.event.locations ) + } + formData.append('description', this.event.description) formData.append('multidate', !!this.date.multidate) let [hour, minute] = this.date.fromHour.split(':') diff --git a/pages/event/_slug.vue b/pages/event/_slug.vue index 244a2010..99ea6df0 100644 --- a/pages/event/_slug.vue +++ b/pages/event/_slug.vue @@ -36,6 +36,22 @@ v-container#event.pa-0.pa-sm-2 v-chip.p-category.ml-1.mt-1(v-for='tag in event.tags' small label color='primary' outlined :key='tag' :to='`/tag/${encodeURIComponent(tag)}`') {{tag}} + //- online events + v-divider(v-if='event.locations && event.locations.length') + v-card(v-if='event.locations && event.locations.length') + v-card-text.text-caption.pb-0(v-text="event.place.name === 'online' && $t('event.online_event_only') || $t('event.online_event_too') ") + v-list-item(target='_blank' :href='`${event.locations[0]}`') + v-list-item-icon + v-icon.my-auto(v-text='mdiMonitorAccount') + v-list-item-content.py-0 + v-text(small label v-text='`${event.locations[0]}`' outlined color='primary') + v-card.pb-2(v-if='event.locations.length > 1') + v-card-text.text-caption.pt-0.pb-0(v-text="$t('event.online_event_fallback_urls')") + v-list-item + v-list-item-content + v-chip(v-for='(item, index) in event.locations' v-if="index > 0" target='_blank' :href="`${item}`" + v-bind:key="index" small label v-text="`${item}`" outlined ) + v-divider //- info & actions v-list(dense nav) @@ -172,7 +188,7 @@ const { htmlToText } = require('html-to-text') import { mdiArrowLeft, mdiArrowRight, mdiDotsVertical, mdiCodeTags, mdiClose, mdiMap, mdiEye, mdiEyeOff, mdiDelete, mdiRepeat, mdiLock, mdiFileDownloadOutline, - mdiCalendarExport, mdiCalendar, mdiContentCopy, mdiMapMarker, mdiChevronUp } from '@mdi/js' + mdiCalendarExport, mdiCalendar, mdiContentCopy, mdiMapMarker, mdiChevronUp, mdiMonitorAccount } from '@mdi/js' export default { name: 'Event', @@ -194,7 +210,7 @@ export default { data () { return { mdiArrowLeft, mdiArrowRight, mdiDotsVertical, mdiCodeTags, mdiCalendarExport, mdiCalendar, mdiFileDownloadOutline, - mdiMapMarker, mdiContentCopy, mdiClose, mdiDelete, mdiEye, mdiEyeOff, mdiRepeat, mdiLock, mdiMap, mdiChevronUp, + mdiMapMarker, mdiContentCopy, mdiClose, mdiDelete, mdiEye, mdiEyeOff, mdiRepeat, mdiLock, mdiMap, mdiChevronUp, mdiMonitorAccount, currentAttachment: 0, event: {}, diocane: '', diff --git a/plugins/validators.js b/plugins/validators.js index 8014b41c..b693ba74 100644 --- a/plugins/validators.js +++ b/plugins/validators.js @@ -12,6 +12,12 @@ export default ({ app }, inject) => { ], password: [ v => !!v || $t('validators.required', { fieldName: $t('common.password') }) + ], + latitude: [ + v => (v < 90 && v > -90) || $t('validators.latitude') + ], + longitude: [ + v => (v < 180 && v > -180) || $t('validators.longitude') ] } diff --git a/server/api/controller/event.js b/server/api/controller/event.js index 73592c04..4eabf379 100644 --- a/server/api/controller/event.js +++ b/server/api/controller/event.js @@ -401,6 +401,7 @@ const eventController = { multidate: body.multidate, start_datetime: body.start_datetime, end_datetime: body.end_datetime, + locations: body.locations, recurrent, // publish this event only if authenticated is_visible: !!req.user @@ -485,6 +486,7 @@ const eventController = { multidate: body.multidate, start_datetime: body.start_datetime || event.start_datetime, end_datetime: body.end_datetime || null, + locations: body.locations, recurrent } diff --git a/server/api/controller/settings.js b/server/api/controller/settings.js index 0645bb26..52067e5e 100644 --- a/server/api/controller/settings.js +++ b/server/api/controller/settings.js @@ -29,6 +29,8 @@ const defaultSettings = { allow_multidate_event: true, allow_recurrent_event: false, recurrent_event_visible: false, + allow_event_only_online: false, + allow_event_also_online: false, allow_geolocation: false, geocoding_provider_type: 'Nominatim', geocoding_provider: 'https://nominatim.openstreetmap.org/search', diff --git a/server/api/index.js b/server/api/index.js index 9fb8ca52..81036151 100644 --- a/server/api/index.js +++ b/server/api/index.js @@ -100,6 +100,7 @@ module.exports = () => { * @param {string} [query] - search for this string * @param {array} [tags] - List of tags * @param {array} [places] - List of places id + * @param {array} [locations] - List of locations * @param {integer} [max] - Limit events * @param {boolean} [show_recurrent] - Show also recurrent events (default: as choosen in admin settings) * @param {integer} [page] - Pagination diff --git a/server/api/models/event.js b/server/api/models/event.js index 362d9134..d94426e1 100644 --- a/server/api/models/event.js +++ b/server/api/models/event.js @@ -37,7 +37,8 @@ module.exports = (sequelize, DataTypes) => { is_visible: DataTypes.BOOLEAN, recurrent: DataTypes.JSON, likes: { type: DataTypes.JSON, defaultValue: [] }, - boost: { type: DataTypes.JSON, defaultValue: [] } + boost: { type: DataTypes.JSON, defaultValue: [] }, + locations: { type: DataTypes.JSON, defaultValue: [] } }) Event.prototype.toAP = function (username, locale, to = []) { diff --git a/server/helpers.js b/server/helpers.js index 6eab2b3c..e734d43d 100644 --- a/server/helpers.js +++ b/server/helpers.js @@ -97,7 +97,9 @@ module.exports = { tilelayer_provider: settings.tilelayer_provider, tilelayer_provider_attribution: settings.tilelayer_provider_attribution, footerLinks: settings.footerLinks, - about: settings.about + about: settings.about, + allow_event_only_online: settings.allow_event_only_online, + allow_event_also_online: settings.allow_event_also_online } // set user locale // res.locals.user_locale = settingsController.user_locale[res.locals.acceptedLocale] diff --git a/server/helpers/geolocation/index.js b/server/helpers/geolocation/index.js new file mode 100644 index 00000000..412083ec --- /dev/null +++ b/server/helpers/geolocation/index.js @@ -0,0 +1,17 @@ +const isoCountries = require('./isoCountries') +const nominatim = require('../../../server/services/geocoding/nominatim') +const photon = require('../../../server/services/geocoding/photon') + +// const geocodingProviders = [ nominatim, photon ] + +// const geolocation = { +// getGeocodingProvider(providerName) { +// geocodingProviders.forEach((item) => { +// if (item.commonName === settings.geocoding_provider_type) { +// return item +// } +// }) +// } +// } + +// module.exports = geolocation \ No newline at end of file diff --git a/server/helpers/geolocation/isoCountries.js b/server/helpers/geolocation/isoCountries.js new file mode 100644 index 00000000..0ba6e700 --- /dev/null +++ b/server/helpers/geolocation/isoCountries.js @@ -0,0 +1,987 @@ +// Iso conversions + +var isoCountries = [ + { + "code": "af", + "name": "Afghanistan" + }, + { + "code": "ax", + "name": "Aland Islands" + }, + { + "code": "al", + "name": "Albania" + }, + { + "code": "dz", + "name": "Algeria" + }, + { + "code": "as", + "name": "American Samoa" + }, + { + "code": "ad", + "name": "Andorra" + }, + { + "code": "ao", + "name": "Angola" + }, + { + "code": "ai", + "name": "Anguilla" + }, + { + "code": "aq", + "name": "Antarctica" + }, + { + "code": "ag", + "name": "Antigua And Barbuda" + }, + { + "code": "ar", + "name": "Argentina" + }, + { + "code": "am", + "name": "Armenia" + }, + { + "code": "aw", + "name": "Aruba" + }, + { + "code": "au", + "name": "Australia" + }, + { + "code": "at", + "name": "Austria" + }, + { + "code": "az", + "name": "Azerbaijan" + }, + { + "code": "bs", + "name": "Bahamas" + }, + { + "code": "bh", + "name": "Bahrain" + }, + { + "code": "bd", + "name": "Bangladesh" + }, + { + "code": "bb", + "name": "Barbados" + }, + { + "code": "by", + "name": "Belarus" + }, + { + "code": "be", + "name": "Belgium" + }, + { + "code": "bz", + "name": "Belize" + }, + { + "code": "bj", + "name": "Benin" + }, + { + "code": "bm", + "name": "Bermuda" + }, + { + "code": "bt", + "name": "Bhutan" + }, + { + "code": "bo", + "name": "Bolivia" + }, + { + "code": "ba", + "name": "Bosnia And Herzegovina" + }, + { + "code": "bw", + "name": "Botswana" + }, + { + "code": "bv", + "name": "Bouvet Island" + }, + { + "code": "br", + "name": "Brazil" + }, + { + "code": "io", + "name": "British Indian Ocean Territory" + }, + { + "code": "bn", + "name": "Brunei Darussalam" + }, + { + "code": "bg", + "name": "Bulgaria" + }, + { + "code": "bf", + "name": "Burkina Faso" + }, + { + "code": "bi", + "name": "Burundi" + }, + { + "code": "kh", + "name": "Cambodia" + }, + { + "code": "cm", + "name": "Cameroon" + }, + { + "code": "ca", + "name": "Canada" + }, + { + "code": "cv", + "name": "Cape Verde" + }, + { + "code": "ky", + "name": "Cayman Islands" + }, + { + "code": "cf", + "name": "Central African Republic" + }, + { + "code": "td", + "name": "Chad" + }, + { + "code": "cl", + "name": "Chile" + }, + { + "code": "cn", + "name": "China" + }, + { + "code": "cx", + "name": "Christmas Island" + }, + { + "code": "cc", + "name": "Cocos (Keeling) Islands" + }, + { + "code": "co", + "name": "Colombia" + }, + { + "code": "km", + "name": "Comoros" + }, + { + "code": "cg", + "name": "Congo" + }, + { + "code": "cd", + "name": "Congo, Democratic Republic" + }, + { + "code": "ck", + "name": "Cook Islands" + }, + { + "code": "cr", + "name": "Costa Rica" + }, + { + "code": "ci", + "name": "Cote D'Ivoire" + }, + { + "code": "hr", + "name": "Croatia" + }, + { + "code": "cu", + "name": "Cuba" + }, + { + "code": "cy", + "name": "Cyprus" + }, + { + "code": "cz", + "name": "Czech Republic" + }, + { + "code": "dk", + "name": "Denmark" + }, + { + "code": "dj", + "name": "Djibouti" + }, + { + "code": "dm", + "name": "Dominica" + }, + { + "code": "do", + "name": "Dominican Republic" + }, + { + "code": "ec", + "name": "Ecuador" + }, + { + "code": "eg", + "name": "Egypt" + }, + { + "code": "sv", + "name": "El Salvador" + }, + { + "code": "gq", + "name": "Equatorial Guinea" + }, + { + "code": "er", + "name": "Eritrea" + }, + { + "code": "ee", + "name": "Estonia" + }, + { + "code": "et", + "name": "Ethiopia" + }, + { + "code": "fk", + "name": "Falkland Islands (Malvinas)" + }, + { + "code": "fo", + "name": "Faroe Islands" + }, + { + "code": "fj", + "name": "Fiji" + }, + { + "code": "fi", + "name": "Finland" + }, + { + "code": "fr", + "name": "France" + }, + { + "code": "gf", + "name": "French Guiana" + }, + { + "code": "pf", + "name": "French Polynesia" + }, + { + "code": "tf", + "name": "French Southern Territories" + }, + { + "code": "ga", + "name": "Gabon" + }, + { + "code": "gm", + "name": "Gambia" + }, + { + "code": "ge", + "name": "Georgia" + }, + { + "code": "de", + "name": "Germany" + }, + { + "code": "gh", + "name": "Ghana" + }, + { + "code": "gi", + "name": "Gibraltar" + }, + { + "code": "gr", + "name": "Greece" + }, + { + "code": "gl", + "name": "Greenland" + }, + { + "code": "gd", + "name": "Grenada" + }, + { + "code": "gp", + "name": "Guadeloupe" + }, + { + "code": "gu", + "name": "Guam" + }, + { + "code": "gt", + "name": "Guatemala" + }, + { + "code": "gg", + "name": "Guernsey" + }, + { + "code": "gn", + "name": "Guinea" + }, + { + "code": "gw", + "name": "Guinea-Bissau" + }, + { + "code": "gy", + "name": "Guyana" + }, + { + "code": "ht", + "name": "Haiti" + }, + { + "code": "hm", + "name": "Heard Island & Mcdonald Islands" + }, + { + "code": "va", + "name": "Holy See (Vatican City State)" + }, + { + "code": "hn", + "name": "Honduras" + }, + { + "code": "hk", + "name": "Hong Kong" + }, + { + "code": "hu", + "name": "Hungary" + }, + { + "code": "is", + "name": "Iceland" + }, + { + "code": "in", + "name": "India" + }, + { + "code": "id", + "name": "Indonesia" + }, + { + "code": "ir", + "name": "Iran, Islamic Republic Of" + }, + { + "code": "iq", + "name": "Iraq" + }, + { + "code": "ie", + "name": "Ireland" + }, + { + "code": "im", + "name": "Isle Of Man" + }, + { + "code": "il", + "name": "Israel" + }, + { + "code": "it", + "name": "Italy" + }, + { + "code": "jm", + "name": "Jamaica" + }, + { + "code": "jp", + "name": "Japan" + }, + { + "code": "je", + "name": "Jersey" + }, + { + "code": "jo", + "name": "Jordan" + }, + { + "code": "kz", + "name": "Kazakhstan" + }, + { + "code": "ke", + "name": "Kenya" + }, + { + "code": "ki", + "name": "Kiribati" + }, + { + "code": "kr", + "name": "Korea" + }, + { + "code": "kw", + "name": "Kuwait" + }, + { + "code": "kg", + "name": "Kyrgyzstan" + }, + { + "code": "la", + "name": "Lao People's Democratic Republic" + }, + { + "code": "lv", + "name": "Latvia" + }, + { + "code": "lb", + "name": "Lebanon" + }, + { + "code": "ls", + "name": "Lesotho" + }, + { + "code": "lr", + "name": "Liberia" + }, + { + "code": "ly", + "name": "Libyan Arab Jamahiriya" + }, + { + "code": "li", + "name": "Liechtenstein" + }, + { + "code": "lt", + "name": "Lithuania" + }, + { + "code": "lu", + "name": "Luxembourg" + }, + { + "code": "mo", + "name": "Macao" + }, + { + "code": "mk", + "name": "Macedonia" + }, + { + "code": "mg", + "name": "Madagascar" + }, + { + "code": "mw", + "name": "Malawi" + }, + { + "code": "my", + "name": "Malaysia" + }, + { + "code": "mv", + "name": "Maldives" + }, + { + "code": "ml", + "name": "Mali" + }, + { + "code": "mt", + "name": "Malta" + }, + { + "code": "mh", + "name": "Marshall Islands" + }, + { + "code": "mq", + "name": "Martinique" + }, + { + "code": "mr", + "name": "Mauritania" + }, + { + "code": "mu", + "name": "Mauritius" + }, + { + "code": "yt", + "name": "Mayotte" + }, + { + "code": "mx", + "name": "Mexico" + }, + { + "code": "fm", + "name": "Micronesia, Federated States Of" + }, + { + "code": "md", + "name": "Moldova" + }, + { + "code": "mc", + "name": "Monaco" + }, + { + "code": "mn", + "name": "Mongolia" + }, + { + "code": "me", + "name": "Montenegro" + }, + { + "code": "ms", + "name": "Montserrat" + }, + { + "code": "ma", + "name": "Morocco" + }, + { + "code": "mz", + "name": "Mozambique" + }, + { + "code": "mm", + "name": "Myanmar" + }, + { + "code": "na", + "name": "Namibia" + }, + { + "code": "nr", + "name": "Nauru" + }, + { + "code": "np", + "name": "Nepal" + }, + { + "code": "nl", + "name": "Netherlands" + }, + { + "code": "an", + "name": "Netherlands Antilles" + }, + { + "code": "nc", + "name": "New Caledonia" + }, + { + "code": "nz", + "name": "New Zealand" + }, + { + "code": "ni", + "name": "Nicaragua" + }, + { + "code": "ne", + "name": "Niger" + }, + { + "code": "ng", + "name": "Nigeria" + }, + { + "code": "nu", + "name": "Niue" + }, + { + "code": "nf", + "name": "Norfolk Island" + }, + { + "code": "mp", + "name": "Northern Mariana Islands" + }, + { + "code": "no", + "name": "Norway" + }, + { + "code": "om", + "name": "Oman" + }, + { + "code": "pk", + "name": "Pakistan" + }, + { + "code": "pw", + "name": "Palau" + }, + { + "code": "ps", + "name": "Palestinian Territory, Occupied" + }, + { + "code": "pa", + "name": "Panama" + }, + { + "code": "pg", + "name": "Papua New Guinea" + }, + { + "code": "py", + "name": "Paraguay" + }, + { + "code": "pe", + "name": "Peru" + }, + { + "code": "ph", + "name": "Philippines" + }, + { + "code": "pn", + "name": "Pitcairn" + }, + { + "code": "pl", + "name": "Poland" + }, + { + "code": "pt", + "name": "Portugal" + }, + { + "code": "pr", + "name": "Puerto Rico" + }, + { + "code": "qa", + "name": "Qatar" + }, + { + "code": "re", + "name": "Reunion" + }, + { + "code": "ro", + "name": "Romania" + }, + { + "code": "ru", + "name": "Russian Federation" + }, + { + "code": "rw", + "name": "Rwanda" + }, + { + "code": "bl", + "name": "Saint Barthelemy" + }, + { + "code": "sh", + "name": "Saint Helena" + }, + { + "code": "kn", + "name": "Saint Kitts And Nevis" + }, + { + "code": "lc", + "name": "Saint Lucia" + }, + { + "code": "mf", + "name": "Saint Martin" + }, + { + "code": "pm", + "name": "Saint Pierre And Miquelon" + }, + { + "code": "vc", + "name": "Saint Vincent And Grenadines" + }, + { + "code": "ws", + "name": "Samoa" + }, + { + "code": "sm", + "name": "San Marino" + }, + { + "code": "st", + "name": "Sao Tome And Principe" + }, + { + "code": "sa", + "name": "Saudi Arabia" + }, + { + "code": "sn", + "name": "Senegal" + }, + { + "code": "rs", + "name": "Serbia" + }, + { + "code": "sc", + "name": "Seychelles" + }, + { + "code": "sl", + "name": "Sierra Leone" + }, + { + "code": "sg", + "name": "Singapore" + }, + { + "code": "sk", + "name": "Slovakia" + }, + { + "code": "si", + "name": "Slovenia" + }, + { + "code": "sb", + "name": "Solomon Islands" + }, + { + "code": "so", + "name": "Somalia" + }, + { + "code": "za", + "name": "South Africa" + }, + { + "code": "gs", + "name": "South Georgia And Sandwich Isl." + }, + { + "code": "es", + "name": "Spain" + }, + { + "code": "lk", + "name": "Sri Lanka" + }, + { + "code": "sd", + "name": "Sudan" + }, + { + "code": "sr", + "name": "Suriname" + }, + { + "code": "sj", + "name": "Svalbard And Jan Mayen" + }, + { + "code": "sz", + "name": "Swaziland" + }, + { + "code": "se", + "name": "Sweden" + }, + { + "code": "ch", + "name": "Switzerland" + }, + { + "code": "sy", + "name": "Syrian Arab Republic" + }, + { + "code": "tw", + "name": "Taiwan" + }, + { + "code": "tj", + "name": "Tajikistan" + }, + { + "code": "tz", + "name": "Tanzania" + }, + { + "code": "th", + "name": "Thailand" + }, + { + "code": "tl", + "name": "Timor-Leste" + }, + { + "code": "tg", + "name": "Togo" + }, + { + "code": "tk", + "name": "Tokelau" + }, + { + "code": "to", + "name": "Tonga" + }, + { + "code": "tt", + "name": "Trinidad And Tobago" + }, + { + "code": "tn", + "name": "Tunisia" + }, + { + "code": "tr", + "name": "Turkey" + }, + { + "code": "tm", + "name": "Turkmenistan" + }, + { + "code": "tc", + "name": "Turks And Caicos Islands" + }, + { + "code": "tv", + "name": "Tuvalu" + }, + { + "code": "ug", + "name": "Uganda" + }, + { + "code": "ua", + "name": "Ukraine" + }, + { + "code": "ae", + "name": "United Arab Emirates" + }, + { + "code": "gb", + "name": "United Kingdom" + }, + { + "code": "us", + "name": "United States" + }, + { + "code": "um", + "name": "United States Outlying Islands" + }, + { + "code": "uy", + "name": "Uruguay" + }, + { + "code": "uz", + "name": "Uzbekistan" + }, + { + "code": "vu", + "name": "Vanuatu" + }, + { + "code": "ve", + "name": "Venezuela" + }, + { + "code": "vn", + "name": "Viet Nam" + }, + { + "code": "vg", + "name": "Virgin Islands, British" + }, + { + "code": "vi", + "name": "Virgin Islands, U.S." + }, + { + "code": "wf", + "name": "Wallis And Futuna" + }, + { + "code": "eh", + "name": "Western Sahara" + }, + { + "code": "ye", + "name": "Yemen" + }, + { + "code": "zm", + "name": "Zambia" + }, + { + "code": "zw", + "name": "Zimbabwe" + } +] + + +module.exports = { isoCountries } diff --git a/server/migrations/20221215110244-event-locations.js b/server/migrations/20221215110244-event-locations.js new file mode 100644 index 00000000..469d1f25 --- /dev/null +++ b/server/migrations/20221215110244-event-locations.js @@ -0,0 +1,29 @@ +'use strict'; + +module.exports = { + async up (queryInterface, Sequelize) { + /** + * Add altering commands here. + * + * Example: + * await queryInterface.createTable('users', { id: Sequelize.INTEGER }); + */ + return Promise.all( + [ + await queryInterface.addColumn('events', 'locations', { type: Sequelize.JSON }), + ]) + }, + + async down (queryInterface, Sequelize) { + /** + * Add reverting commands here. + * + * Example: + * await queryInterface.dropTable('users'); + */ + return Promise.all( + [ + await queryInterface.removeColumn('events', 'locations'), + ]) + } +}; diff --git a/server/services/geocoding/nominatim.js b/server/services/geocoding/nominatim.js index 9e8b09fd..b6050bd6 100644 --- a/server/services/geocoding/nominatim.js +++ b/server/services/geocoding/nominatim.js @@ -1,5 +1,6 @@ const cache = require('memory-cache') const providerCache = new cache.Cache() +const get = require('lodash/get') const nominatim = { commonName: 'Nominatim', @@ -23,6 +24,39 @@ const nominatim = { } }, + /* + * Icons to nominatim `osm_type` and `class` conversion + */ + searchIcons_nominatim_osm_type: { + way: 'mdiRoadVariant', + house: 'mdiHome', + node: 'mdiMapMarker', + relation: 'mdiCityVariant', + }, + searchIcons_nominatim_class: { + mdiHome: ['place', 'amenity', 'shop', 'tourism', 'leisure', 'building'] + }, + + filterNameFromAddress: ['place', 'amenity', 'shop', 'tourism', 'leisure', 'building'], + + mapQueryResults (ret, addressList = []) { + if (ret && ret.length) { + addressList = ret.map(v => { + const name = get(v.namedetails, 'alt_name', get(v.namedetails, 'name')) + const address = this.filterNameFromAddress.includes(v.class) ? v.display_name.replace(name, '').replace(/^, ?/, '') : v.display_name.replace(/^, ?/, '') + return { + class: v.class, + type: v.osm_type, + lat: v.lat, + lon: v.lon, + name, + address + } + }) + } + return addressList + } + } module.exports = nominatim \ No newline at end of file diff --git a/server/services/geocoding/photon.js b/server/services/geocoding/photon.js index 4eced7db..4170c543 100644 --- a/server/services/geocoding/photon.js +++ b/server/services/geocoding/photon.js @@ -16,6 +16,51 @@ const photon = { q: details, limit: 3, } + }, + + /* + * Icons to nominatim `osm_type` and `class` conversion + */ + searchIcons_nominatim_osm_type: { + 'W': 'mdiRoadVariant', + 'N': 'mdiMapMarker', + 'R': 'mdiCityVariant', + }, + searchIcons_nominatim_class: { + mdiHome: ['amenity', 'shop', 'tourism', 'leisure', 'building'], + }, + + + fullAddressMapping: ['housenumber', 'street', 'locality', 'district', 'city', 'county', 'state', 'postcode', 'country'], + + mapQueryResults(ret, addressList = []) { + if (ret) { + addressList = ret.features.map(v => { + let pre_name = v.properties.name || v.properties.street || '' + let pre_address = '' + + this.fullAddressMapping.forEach((item, i) => { + let last = i == (this.fullAddressMapping.length - 1) + if (v.properties[item] && !last) { + pre_address += v.properties[item]+', ' + } else if (v.properties[item]) { + pre_address += v.properties[item] + } + }); + + let name = pre_name + let address = pre_address + return { + class: v.properties.osm_key, + type: v.properties.osm_type, + lat: v.geometry.coordinates[1], + lon: v.geometry.coordinates[0], + name, + address + } + }) + } + return addressList } } diff --git a/store/index.js b/store/index.js index 09a7b07e..e56592f8 100644 --- a/store/index.js +++ b/store/index.js @@ -13,6 +13,8 @@ export const state = () => ({ allow_multidate_event: true, allow_recurrent_event: true, recurrent_event_visible: false, + allow_event_only_online: false, + allow_event_also_online: false, allow_geolocation: false, geocoding_provider_type: '', geocoding_provider: '', diff --git a/yarn.lock b/yarn.lock index 10f97e71..3a042a45 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3456,11 +3456,6 @@ buffer-alloc@^1.2.0: buffer-alloc-unsafe "^1.1.0" buffer-fill "^1.0.0" -buffer-equal-constant-time@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" - integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== - buffer-fill@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" @@ -4999,13 +4994,6 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -ecdsa-sig-formatter@1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" - integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== - dependencies: - safe-buffer "^5.0.1" - editorconfig@^0.15.3: version "0.15.3" resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.3.tgz#bef84c4e75fb8dcb0ce5cee8efd51c15999befc5" @@ -7552,16 +7540,6 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -jsonwebtoken@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz#d0faf9ba1cc3a56255fe49c0961a67e520c1926d" - integrity sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw== - dependencies: - jws "^3.2.2" - lodash "^4.17.21" - ms "^2.1.1" - semver "^7.3.8" - jsprim@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d" @@ -7591,23 +7569,6 @@ juice@^8.0.0: slick "^1.12.2" web-resource-inliner "^6.0.1" -jwa@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" - integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== - dependencies: - buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.11" - safe-buffer "^5.0.1" - -jws@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" - integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== - dependencies: - jwa "^1.4.1" - safe-buffer "^5.0.1" - keygrip@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.1.0.tgz#871b1681d5e159c62a445b0c74b615e0917e7226" @@ -12687,10 +12648,8 @@ watchpack@^1.7.4: resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.5.tgz#1267e6c55e0b9b5be44c2023aed5437a2c26c453" integrity sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ== dependencies: - chokidar "^3.4.1" graceful-fs "^4.1.2" neo-async "^2.5.0" - watchpack-chokidar2 "^2.0.1" optionalDependencies: chokidar "^3.4.1" watchpack-chokidar2 "^2.0.1"