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 @@
+
+client-only(placeholder='Loading...' )
+ LMap(ref="map"
+ id="leaflet-map"
+ :zoom="zoom"
+ :options="{attributionControl: false}"
+ :center="center")
+ LControlAttribution(position='bottomright' prefix="")
+ LTileLayer(
+ :url="url"
+ :attribution="attribution")
+ LMarker(
+ :lat-lng="marker.coordinates")
+
+
+
+
+
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 @@
+
+v-card
+ v-card-title {{ $t('event.where_advanced_options') }}
+ v-card-subtitle {{ $t('event.where_advanced_options_description') }}
+
+ v-card-text(v-if="settings.allow_event_only_online")
+ v-switch.mt-0.mb-2(v-model='online_event_only_update'
+ persistent-hint
+ :label="$t('event.event_only_online_label')"
+ :hint="$t('event.online_event_only_help')")
+
+ v-combobox.mt-0.mb-0.mr-4.my-5(v-model="virtualLocations_update"
+ v-if="place.name !== 'online' && settings.allow_event_also_online"
+ :prepend-icon='mdiLink'
+ :hint="$t('event.additional_online_locations_help')"
+ :label="$t('event.additional_online_locations')"
+ clearable chips small-chips multiple deletable-chips hide-no-data hide-selected persistent-hint
+ :delimiters="[',', ';', '; ']"
+ :items="virtualLocations_update")
+ 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 }}
+
+ v-divider(v-if='showGeocoded && showOnline')
+
+ v-card-text.mt-5(v-if='showGeocoded')
+ v-text-field.mt-0.mb-0(v-model='place.address'
+ :prepend-icon='mdiMap'
+ :disabled="!settings.allow_geolocation || place.name === 'online'"
+ persistent-hint
+ :hint="$t('event.address_overwrite_help')"
+ :label="$t('event.address_overwrite')")
+
+ v-row.mt-4
+ v-col.py-0(cols=12 md=6)
+ v-text-field(v-model="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")
+ p.mt-4(v-html="$t('event.address_geocoded_disclaimer')")
+
+ MapEdit.mt-4(:place='place' v-if="mapEdit && (settings.allow_geolocation && place.name !== 'online' && place.latitude && place.longitude)" )
+
+ v-card-actions
+ v-spacer
+ v-btn(@click='close' outlined) Close
+
+
+
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"