Merge remote-tracking branch 'sedum/feat/allowgeoloc' into dev_geo

This commit is contained in:
lesion 2022-11-15 17:37:13 +01:00
commit 4bef32afa2
No known key found for this signature in database
GPG key ID: 352918250B012177
37 changed files with 663 additions and 107 deletions

View file

@ -149,12 +149,12 @@ li {
max-width: 1200px;
}
.tags .v-chip .v-chip__content {
/* .tags .v-chip .v-chip__content {
max-width: 120px;
white-space: nowrap;
overflow: hidden;
display: block;
}
} */
.cursorPointer {

View file

@ -12,8 +12,8 @@ v-dialog(v-model='show'
v-card-text(v-show='!!message') {{ message }}
v-card-actions
v-spacer
v-btn(text color='error' @click='cancel') {{$t('common.cancel')}}
v-btn(text color='primary' @click='agree') {{$t('common.ok')}}
v-btn(outlined color='error' @click='cancel') {{$t('common.cancel')}}
v-btn(outlined color='primary' @click='agree') {{$t('common.ok')}}
</template>
<script>

View file

@ -8,6 +8,8 @@ v-col(cols=12)
p {{ $t(`event.${type}_description`) }}
p {{value}}
v-btn-toggle.v-col-6.flex-column.flex-sm-row(v-if='type === "recurrent"' color='primary' :value='value.recurrent.frequency' @change='fq => change("frequency", fq)')
v-btn(v-for='f in frequencies' :key='f.value' :value='f.value') {{ f.text }}
@ -232,6 +234,11 @@ export default {
this.$emit('input', { ...this.value, fromHour: null, dueHour: null })
}
} else if (what === 'dueHour') {
if (value) {
this.value.due = this.value.due ? this.value.due : this.value.from
} else {
this.value.due = null
}
this.$emit('input', { ...this.value, dueHour: value })
// if (value) {

View file

@ -11,10 +11,9 @@ v-card.h-event.event.d-flex(itemscope itemtype="https://schema.org/Event")
nuxt-link.place.d-block.p-location.pl-0(text :to='`/place/${encodeURIComponent(event.place.name)}`' itemprop="location" itemscope itemtype="https://schema.org/Place") <v-icon v-text='mdiMapMarker'></v-icon> <span itemprop='name'>{{ event.place.name }}</span>
.d-none(itemprop='address') {{ event.place.address }}
v-card-actions.pt-0.actions.justify-space-between
.tags
v-chip.ml-1.mt-1(v-for='tag in event.tags.slice(0, 6)' small :to='`/tag/${encodeURIComponent(tag)}`'
:key='tag' outlined color='primary') {{ tag }}
v-card-actions
v-chip.ml-1.mt-1(v-for='tag in event.tags.slice(0, 6)' small label :to='`/tag/${encodeURIComponent(tag)}`'
:key='tag' outlined color='primary') {{ tag }}
</template>
<script>

View file

@ -15,8 +15,8 @@ v-card
v-card-actions(v-if='isDialog')
v-spacer
v-btn(v-if='isDialog' color='warning' @click="$emit('close')") {{$t("common.cancel")}}
v-btn(:disabled='(!couldGo || !proceed)' :href='link' target='_blank'
v-btn(v-if='isDialog' outlined color='warning' @click="$emit('close')") {{$t("common.cancel")}}
v-btn(:disabled='(!couldGo || !proceed)' outlined :href='link' target='_blank'
:loading='loading' color="primary") {{$t("common.follow")}}
</template>
<script>

116
components/Map.vue Normal file
View file

@ -0,0 +1,116 @@
<template lang="pug">
client-only(placeholder='Loading...' )
v-card
v-card-text
v-container
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")
v-row.my-4.d-flex.flex-column.align-center.text-center
.text-h6
v-icon(v-text='mdiMapMarker' )
nuxt-link.ml-2.text-decoration-none(v-text="event.place.name" :to='`/place/${event.place.name}`')
.mx-2(v-text="`${event.place.address}`")
v-card-actions
v-row
//- p.my-4(v-text="$t('common.getting_there')")
v-btn.ml-2(icon large :href="routeBy('foot')")
v-icon(v-text='mdiWalk' color='white')
v-btn.ml-2(icon large :href="routeBy('bike')")
v-icon(v-text='mdiBike' color='white')
v-btn.ml-2(icon large :href="routeBy('car')")
v-icon(v-text='mdiCar' color='white')
v-spacer
v-btn(@click='$emit("close")' outlined) Close
</template>
<script>
import "leaflet/dist/leaflet.css"
import { LMap, LTileLayer, LMarker, LPopup, LControlAttribution } from 'vue2-leaflet'
import { mapActions, mapState } from 'vuex'
import { Icon } from 'leaflet'
import { mdiWalk, mdiBike, mdiCar, mdiMapMarker } from '@mdi/js'
export default {
components: {
LMap,
LTileLayer,
LMarker,
LPopup,
LControlAttribution
},
data ({ $store }) {
return {
mdiWalk, mdiBike, mdiCar, mdiMapMarker,
url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
attribution: '<a target="_blank" href="http://osm.org/copyright">OpenStreetMap</a> contributors',
zoom: 14,
center: [this.event.place.latitude, this.event.place.longitude],
marker: {
address: this.event.place.address,
coordinates: {lat: this.event.place.latitude, lon: this.event.place.longitude}
},
routingProvider: 'openstreetmap',
}
},
props: {
event: { type: Object, default: () => ({}) }
},
mounted() {
delete Icon.Default.prototype._getIconUrl;
Icon.Default.mergeOptions({
iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
iconUrl: require('leaflet/dist/images/marker-icon.png'),
shadowUrl: require('leaflet/dist/images/marker-shadow.png'),
});
setTimeout(() => {
this.$refs.map.mapObject.invalidateSize();
}, 200);
},
computed: {
...mapState(['settings']),
},
methods: {
...mapActions(['setSetting']),
// mountLocateControl() {
// this.$refs.map.mapObject.locate({
// locateOptions: {
// maxZoom: 10
// }
// });
// this.$refs.map.mapObject.MyLocate();
// },
routeBy () {
const lat = this.event.place.latitude
const lon = this.event.place.longitude
const routingType = {
foot: "engine=fossgis_osrm_foot",
bike: "engine=fossgis_osrm_bike",
transit: null,
car: "engine=fossgis_osrm_car"
}
return `https://www.openstreetmap.org/directions?from=&to=${lat},${lon}&${routingType}#map=14/${lat}/${lon}`
},
}
}
</script>
<style>
#leaflet-map {
height: 55vh;
width: 100%;
border-radius: .3rem;
border: 1px solid #fff;
z-index: 1;
}
</style>

View file

@ -1,5 +1,5 @@
<template>
<v-app-bar prominent fade-img-on-scroll app hide-on-scroll
<v-app-bar absolute prominent app hide-on-scroll
src="/headerimage.png">
<template v-slot:img="{ props }">
<v-img

View file

@ -6,6 +6,7 @@ v-snackbar(
:top="top"
:left="left"
:right="right"
transition='scroll-x-reverse-transition'
:timeout="timeout")
v-icon.mr-3(color="white" v-text='icon')
span {{ message }}
@ -25,7 +26,7 @@ export default {
bottom: true,
top: false,
left: false,
right: false,
right: true,
active: false,
timeout: 5000,
message: ''

View file

@ -1,5 +1,5 @@
<template lang="pug">
v-row
v-row.mb-4
v-col(cols=12 md=6)
v-combobox(ref='place'
:rules="[$validators.required('common.where')]"
@ -10,9 +10,8 @@ v-row
hide-no-data
@input.native='search'
persistent-hint
:value='value'
:value='value.name'
:items="places"
item-text='name'
@focus='search'
@change='selectPlace')
template(v-slot:item="{ item, attrs, on }")
@ -23,19 +22,49 @@ v-row
v-list-item-title(v-text='item.name')
v-list-item-subtitle(v-text='item.address')
//- v-text-field(
//- ref='address'
//- :prepend-icon='mdiMap'
//- :disabled='disableAddress'
//- :rules="[ v => disableAddress ? true : $validators.required('common.address')(v)]"
//- :label="$t('common.address')"
//- @change="changeAddress"
//- :value="value.address")
v-col(cols=12 md=6)
v-text-field(ref='address'
:prepend-icon='mdiMap'
v-combobox.mr-4(ref='address'
:prepend-icon='mdiMapSearch'
:disabled='disableAddress'
:rules="[ v => disableAddress ? true : $validators.required('common.address')(v)]"
:label="$t('common.address')"
@change="changeAddress"
:value="value.address")
@input.native='searchCoordinates'
:label="$t('event.coordinates_search')"
:value='value.address'
persistent-hint hide-no-data clearable no-filter
:loading='loading'
@change='selectDetails'
@focus='searchCoordinates'
:items="detailsList"
:hint="$t('event.coordinates_search_description')")
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-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>
<script>
import { mdiMap, mdiMapMarker, mdiPlus } from '@mdi/js'
import { mdiMap, mdiMapMarker, mdiPlus, mdiMapSearch, mdiLatitude, mdiLongitude } from '@mdi/js'
import debounce from 'lodash/debounce'
import { mapState } from 'vuex'
import get from 'lodash/get'
export default {
name: 'WhereInput',
@ -44,14 +73,20 @@ export default {
},
data () {
return {
mdiMap, mdiMapMarker, mdiPlus,
mdiMap, mdiMapMarker, mdiPlus, mdiMapSearch, mdiLatitude, mdiLongitude,
place: { },
placeName: '',
places: [],
disableAddress: true
disableAddress: true,
details: { },
detailsView: '',
detailsList: [],
disableDetails: true,
loading: false
}
},
computed: {
...mapState(['settings']),
filteredPlaces () {
if (!this.placeName) { return this.places }
const placeName = this.placeName.trim().toLowerCase()
@ -86,6 +121,11 @@ export default {
if (typeof p === 'object' && !p.create) {
this.place.name = p.name
this.place.address = p.address
if (this.settings.allow_geolocation) {
this.place.details = p.details
this.place.latitude = p.latitude
this.place.longitude = p.longitude
}
this.place.id = p.id
this.disableAddress = true
} else { // this is a new place
@ -101,6 +141,11 @@ export default {
} else {
delete this.place.id
this.place.address = ''
if (this.settings.allow_geolocation) {
this.place.details = p.details
this.place.latitude = p.latitude
this.place.longitude = p.longitude
}
this.disableAddress = false
this.$refs.place.blur()
this.$refs.address.focus()
@ -111,7 +156,73 @@ export default {
changeAddress (v) {
this.place.address = v
this.$emit('input', { ...this.place })
}
this.disableDetails = false
},
selectDetails (v) {
if (!v) { return }
if (typeof v === 'object') {
this.place.latitude = v.lat
this.place.longitude = v.lon
this.place.address = v.address
// }
} else {
this.place.address = v
this.place.latitude = this.place.longitude = null
}
this.$emit('input', { ...this.place })
},
searchCoordinates: debounce(async function(ev) {
const pre_searchCoordinates = ev.target.value.trim().toLowerCase()
// allow pasting coordinates lat/lon and lat,lon
const searchCoordinates = pre_searchCoordinates.replace('/', ',')
// const regex_coords_comma = "-?[1-9][0-9]*(\\.[0-9]+)?,\\s*-?[1-9][0-9]*(\\.[0-9]+)?";
// const regex_coords_slash = "-?[1-9][0-9]*(\\.[0-9]+)?/\\s*-?[1-9][0-9]*(\\.[0-9]+)?";
// const setCoords = (v) => {
// const lat = v[0].trim()
// const lon = v[1].trim()
// // check coordinates are valid
// if ((lat < 90 && lat > -90)
// && (lon < 180 && lon > -180)) {
// this.place.latitude = lat
// this.place.longitude = lon
// } else {
// this.$root.$message("Non existent coordinates", { color: 'error' })
// return
// }
// }
// if (pre_searchCoordinates.match(regex_coords_comma)) {
// let v = pre_searchCoordinates.split(",")
// setCoords(v)
// return
// }
// if (pre_searchCoordinates.match(regex_coords_slash)) {
// let v = pre_searchCoordinates.split("/")
// setCoords(v)
// return
// }
if (searchCoordinates.length) {
this.loading = true
const ret = await this.$axios.$get(`placeNominatim/${searchCoordinates}`)
if (ret && ret.length) {
this.detailsList = ret.map(v => {
const name = get(v.namedetails, 'alt_name', get(v.namedetails, 'name'))
const address = v.display_name ? v.display_name.replace(name, '').replace(/^, ?/, '') : ''
return {
lat: v.lat,
lon: v.lon,
name,
address
}
})
} else {
this.detailsList = []
}
this.loading = false
}
}, 300)
}
}
</script>

View file

@ -25,6 +25,19 @@ v-container
v-model='place.address'
:placeholder='$t("common.address")')
v-text-field(v-if="settings.allow_geolocation"
:rules="[$validators.required('common.latitude')]"
:label="$t('common.latitude')"
v-model='place.latitude'
:placeholder='$t("common.latitude")')
v-text-field(v-if="settings.allow_geolocation"
:rules="[$validators.required('common.longitude')]"
:label="$t('common.longitude')"
v-model='place.longitude'
:placeholder='$t("common.longitude")')
v-card-actions
v-spacer
v-btn(@click='dialog = false' outlined color='warning') {{ $t('common.cancel') }}
@ -47,6 +60,7 @@ v-container
</template>
<script>
import { mdiPencil, mdiChevronLeft, mdiChevronRight, mdiMagnify, mdiEye } from '@mdi/js'
import { mapState } from 'vuex'
export default {
data() {
@ -68,10 +82,17 @@ export default {
async fetch() {
this.places = await this.$axios.$get('/place/all')
},
computed: {
...mapState(['settings']),
},
methods: {
editPlace(item) {
this.place.name = item.name
this.place.address = item.address
if (this.settings.allow_geolocation) {
this.place.latitude = item.latitude
this.place.longitude = item.longitude
}
this.place.id = item.id
this.dialog = true
},

View file

@ -48,6 +48,10 @@ v-container
inset
:label="$t('admin.recurrent_event_visible')")
v-switch.mt-1(v-model='allow_geolocation'
inset
:label="$t('admin.allow_geolocation')")
v-dialog(v-model='showSMTP' destroy-on-close max-width='700px' :fullscreen='$vuetify.breakpoint.xsOnly')
SMTP(@close='showSMTP = false')
@ -107,6 +111,10 @@ export default {
get () { return this.settings.recurrent_event_visible },
set (value) { this.setSetting({ key: 'recurrent_event_visible', value }) }
},
allow_geolocation: {
get () { return this.settings.allow_geolocation },
set (value) { this.setSetting({ key: 'allow_geolocation', value }) }
},
filteredTimezones () {
const current_timezone = moment.tz.guess()
tzNames.unshift(current_timezone)

View file

@ -11,8 +11,8 @@ v-card
gancio-event(:id='event.id' :baseurl='settings.baseurl')
v-card-actions
v-spacer
v-btn(text color='warning' @click="$emit('close')") {{$t("common.cancel")}}
v-btn(text @click='clipboard(code)' color="primary") {{$t("common.copy")}}
v-btn(outlined color='warning' @click="$emit('close')") {{$t("common.close")}}
v-btn(outlined @click='clipboard(code)' color="primary") {{$t("common.copy")}}
</template>
<script>
import { mapState } from 'vuex'

View file

@ -1,24 +1,43 @@
<template lang='pug'>
div
v-btn(text color='primary' v-if='event.is_visible' @click='toggle(false)') {{$t(`common.${event.parentId?'skip':'hide'}`)}}
v-btn(text color='success' v-else @click='toggle(false)') <v-icon color='yellow' v-text='mdiAlert'></v-icon> {{$t('common.confirm')}}
v-btn(text color='primary' @click='$router.push(`/add/${event.id}`)') {{$t('common.edit')}}
v-btn(text color='primary' v-if='!event.parentId' @click='remove(false)') {{$t('common.remove')}}
v-list(dense nav)
v-list-group(:append-icon='mdiChevronUp' :value='true')
template(v-slot:activator)
v-list-item.text-overline Admin actions
template(v-if='event.parentId')
v-divider
span.mr-1 <v-icon v-text='mdiRepeat'></v-icon> {{$t('event.edit_recurrent')}}
v-btn(text color='primary' v-if='event.parent.is_visible' @click='toggle(true)') {{$t('common.pause')}}
v-btn(text color='primary' v-else @click='toggle(true)') {{$t('common.start')}}
v-btn(text color='primary' @click='$router.push(`/add/${event.parentId}`)') {{$t('common.edit')}}
v-btn(text color='primary' @click='remove(true)') {{$t('common.remove')}}
//- Hide / confirm event
v-list-item(@click='toggle(false)')
v-list-item-content
v-list-item-title(v-text="$t(`common.${event.is_visible?'hide':'confirm'}`)")
//- Edit event
v-list-item(:to='`/add/${event.id}`')
v-list-item-content
v-list-item-title(v-text="$t('common.edit')")
//- Remove
v-list-item(@click='remove(false)')
v-list-item-content
v-list-item-title(v-text="$t('common.remove')")
//- v-btn(text color='primary' v-if='event.is_visible' @click='toggle(false)') {{$t(`common.${event.parentId?'skip':'hide'}`)}}
//- v-btn(text color='success' v-else @click='toggle(false)') <v-icon color='yellow' v-text='mdiAlert'></v-icon> {{$t('common.confirm')}}
//- v-btn(text color='primary' @click='$router.push(`/add/${event.id}`)') {{$t('common.edit')}}
//- v-btn(text color='primary' v-if='!event.parentId' @click='remove(false)') {{$t('common.remove')}}
//- template(v-if='event.parentId')
//- v-divider
//- span.mr-1 <v-icon v-text='mdiRepeat'></v-icon> {{$t('event.edit_recurrent')}}
//- v-btn(text color='primary' v-if='event.parent.is_visible' @click='toggle(true)') {{$t('common.pause')}}
//- v-btn(text color='primary' v-else @click='toggle(true)') {{$t('common.start')}}
//- v-btn(text color='primary' @click='$router.push(`/add/${event.parentId}`)') {{$t('common.edit')}}
//- v-btn(text color='primary' @click='remove(true)') {{$t('common.remove')}}
</template>
<script>
import { mdiAlert, mdiRepeat } from '@mdi/js'
import { mdiChevronUp, mdiRepeat } from '@mdi/js'
export default {
name: 'EventAdmin',
data () {
return { mdiAlert, mdiRepeat }
return { mdiChevronUp, mdiRepeat }
},
props: {
event: {

View file

@ -454,7 +454,7 @@ function Re(t, e, i) {
}), t.$$set = (d) => {
"baseurl" in d && i(0, n = d.baseurl), "title" in d && i(1, l = d.title), "maxlength" in d && i(6, o = d.maxlength), "tags" in d && i(7, r = d.tags), "places" in d && i(8, f = d.places), "theme" in d && i(2, c = d.theme), "show_recurrent" in d && i(9, s = d.show_recurrent), "sidebar" in d && i(3, k = d.sidebar), "external_style" in d && i(4, m = d.external_style);
}, t.$$.update = () => {
t.$$.dirty & 974 && w();
t.$$.dirty & 975 && w();
}, [
n,
l,

@ -1 +1 @@
Subproject commit f4c0bdfa09874c48653417a274196ddbc00244b5
Subproject commit 12640461481cc39cdee8efa05e7fd02a6a60e99c

View file

@ -1,9 +1,9 @@
<template>
<v-app>
<Snackbar/>
<Confirm/>
<Nav/>
<v-main>
<v-main >
<Snackbar/>
<Confirm/>
<!-- <div v-if='showCollections || showBack'>
<v-btn class='ml-2 mt-2' v-if='showBack' outlined color='primary' to='/'><v-icon v-text='mdiChevronLeft'></v-icon></v-btn>
<v-btn class='ml-2 mt-2' outlined v-for='collection in collections' color='primary' :key='collection.id' :to='`/collection/${collection.name}`'>{{collection.name}}</v-btn>
@ -24,7 +24,6 @@ import Snackbar from '../components/Snackbar'
import Footer from '../components/Footer'
import Confirm from '../components/Confirm'
import { mapState } from 'vuex'
import { mdiChevronLeft } from '@mdi/js'
export default {
head () {

View file

@ -89,7 +89,11 @@
"max_events": "Nre. màx. d'activitats",
"close": "Tanca",
"blobs": "Blobs",
"collections": "Coŀleccions"
"collections": "Coŀleccions",
"show_map": "Mostra el mapa",
"latitude": "Latitud",
"longitude": "Longitud",
"getting_there": "Com arribar"
},
"login": {
"description": "Amb la sessió iniciada pots afegir activitats noves.",
@ -128,6 +132,8 @@
"added": "S'ha afegit l'activitat",
"added_anon": "S'ha afegit l'activitat però encara ha de ser confirmada.",
"where_description": "On es farà? Si no està posat, escriu-ho i prem Enter.",
"coordinates_search": "Cerca de coordenades",
"coordinates_search_description": "Podeu cercar el lloc pel nom o enganxar el parell de coordenades",
"confirmed": "S'ha confirmat l'activitat",
"not_found": "No s'ha trobat l'activitat",
"remove_confirmation": "Segur que vols esborrar l'activitat?",
@ -179,6 +185,7 @@
"allow_registration_description": "Vols deixar el registre obert?",
"allow_anon_event": "Vols permetre activitats anònimes (s'han de confirmar manualment)?",
"allow_recurrent_event": "Habilitar activitats periòdiques",
"allow_geolocation": "Habilitar la geolocalització d' esdeveniments",
"recurrent_event_visible": "Mostra per defecte les activitats periòdiques",
"federation": "Federació / ActivityPub",
"enable_federation": "Activa la federació",

View file

@ -88,7 +88,11 @@
"associate": "Partner:in",
"collections": "Sammlungen",
"max_events": "Maximale Anzahl an Veranstaltungen",
"close": "Schließe"
"close": "Schließe",
"show_map": "Karte anzeigen",
"latitude": "Breite",
"longitude": "Länge",
"getting_there": "Anreise"
},
"admin": {
"delete_footer_link_confirm": "Möchtest du diesen Link löschen?",
@ -155,6 +159,7 @@
"enable_federation": "Federation aktivieren",
"allow_anon_event": "Kann man auch anonyme Veranstaltungen (vorausgesetzt, diese werden genehmigt) eintragen?",
"allow_registration_description": "Möchtest du die Registrierung aktivieren?",
"allow_geolocation": "Aktivieren der Ereignis-Geolokalisierung",
"federation": "Föderation / ActivityPub",
"enable_federation_help": "Bei Aktivierung kann diese Instanz vom Fediverse aus verfolgt werden",
"add_instance": "Instanz hinzufügen",
@ -229,6 +234,8 @@
"remove_recurrent_confirmation": "Bist du dir sicher, dass du diese wiederkehrende Veranstaltung entfernen möchtest?\nFrühere Veranstaltungen werden beibehalten, aber es werden keine zukünftigen Veranstaltungen mehr hinzugefügt.",
"anon_description": "Du kannst eine Veranstaltung hinzufügen, ohne dich zu registrieren oder anzumelden,\nmusst dann aber warten, bis jemand es liest und bestätigt, dass es eine zulässige Veranstaltung ist.\nEs ist nicht möglich, den Eintrag zu verändern.<br/><br/>\nDu kannst dich stattdessen <a href='/login'>anmelden</a> oder <a href='/register'>registrieren</a>. In diesem Fall solltest du so schnell wie möglich eine Antwort erhalten. ",
"where_description": "Wo ist die Veranstaltung? Wenn der Ort noch nicht beschrieben wurde, kannst du ihn selbst eintragen.",
"coordinates_search": "Suche nach Koordinaten",
"coordinates_search_description": "Sie können den Ort anhand des Namens suchen oder das Koordinatenpaar einfügen.",
"follow_me_description": "Eine Möglichkeit, über die hier auf {title} veröffentlichten Veranstaltungen auf dem Laufenden zu bleiben, besteht darin, dem Account <u>{account}</u> aus dem Fediverse zu folgen, zum Beispiel über Mastodon, und von dort aus eventuell Ressourcen für eine Veranstaltung hinzuzufügen.<br/><br/>\nWenn Du noch nie von Mastodon und dem Fediverse gehört hast, empfehlen wir dir, diesen Artikel <a href='https://www.savjee.be/videos/simply-explained/mastodon-and-fediverse-explained/'> zu lesen</a>.<br/><br/>Gib unten deine Instanz ein (z.B. mastodon.social)",
"media_description": "Du kannst (optional) einen Flyer hinzufügen",
"edit_recurrent": "Bearbeite eine sich wiederholende Veranstaltung :",

View file

@ -87,7 +87,11 @@
"reset": "Reset",
"theme": "Tema",
"label": "Etiqueta",
"max_events": "Número de eventos máximo"
"max_events": "Número de eventos máximo",
"show_map": "Mostrar mapa",
"latitude": "Latitud",
"longitude": "Longitud",
"getting_there": "Cómo llegar"
},
"login": {
"description": "Entrando podrás publicar nuevos eventos.",
@ -126,6 +130,8 @@
"added": "Evento agregado",
"added_anon": "Evento agregado, será confirmado cuanto antes.",
"where_description": "¿Dónde es? Si el lugar no está, escribilo.",
"coordinates_search": "Buscar coordenadas",
"coordinates_search_description": "Puede buscar el lugar por nombre o pegar el par de coordenadas.",
"confirmed": "Evento confirmado",
"not_found": "Evento no encontrado",
"remove_confirmation": "¿Estás seguro/a de querér eliminar este evento?",
@ -174,6 +180,7 @@
"allow_anon_event": "¿Se pueden ingresar eventos anónimos (sujeto a confirmación)?",
"allow_comments": "Habilitar comentarios",
"allow_recurrent_event": "Habilitar eventos fijos",
"allow_geolocation": "Habilitar la geolocalización de eventos",
"recurrent_event_visible": "Eventos fijos visibles por defecto",
"federation": "Federación / ActivityPub",
"enable_federation": "Habilitar la federación",

View file

@ -89,7 +89,11 @@
"max_events": "Nb. max d'événements",
"blobs": "Blobs",
"close": "Fermer",
"collections": "Collections"
"collections": "Collections",
"show_map": "Afficher la carte",
"latitude": "Latitude",
"longitude": "Longitude",
"getting_there": "Comment s'y rendre"
},
"event": {
"follow_me_description": "Une des manières de rester informé sur les évènements publiés ici sur {title}\nest de suivre le compte <u>{account}</u> sur le fediverse, par exemple via Mastodon, et pourquoi pas d'ajouter des ressources à un évènement à partir de là.<br/><br/>\nSi vous n'avez jamais entendu parler de Mastodon and du fediverse, nous vous recommandons de lire <a href='https://www.savjee.be/videos/simply-explained/mastodon-and-fediverse-explained/'>cet article (en anglais)</a>.<br/><br/>Saisissez votre nom d'instance ci-dessous (par ex. mastodon.social)",
@ -119,6 +123,8 @@
"not_found": "Impossible de trouver l'évènement",
"confirmed": "Évènement confirmé",
"where_description": "Où est l'évènement ? S'il n'apparaît pas, vous pouvez le créer.",
"coordinates_search": "Recherche de coordonnées",
"coordinates_search_description": "Vous pouvez rechercher le lieu par son nom ou coller la paire de coordonnées",
"added_anon": "Évènement ajouté, mais il doit encore être confirmé.",
"added": "Évènement ajouté",
"media_description": "Vous pouvez ajouter un tract (facultatif)",
@ -204,6 +210,7 @@
"allow_recurrent_event": "Autoriser les évènements récurrents",
"allow_anon_event": "Autoriser les évènements anonymes (doivent être confirmés) ?",
"allow_registration_description": "Autoriser l'ouverture des inscriptions ?",
"allow_geolocation": "Autoriser la géolocalisation des événements",
"user_create_ok": "Utilisateur créé",
"user_remove_ok": "Utilisateur supprimé",
"delete_user_confirm": "Êtes-vous sûr·e de vouloir supprimer {user} ?",

View file

@ -89,7 +89,11 @@
"tags": "Cancelos",
"close": "Pechar",
"blobs": "Blobs",
"collections": "Coleccións"
"collections": "Coleccións",
"show_map": "Mostrar mapa",
"latitude": "Latitude",
"longitude": "Longitude",
"getting_there": "Chegar lá"
},
"recover": {
"not_valid_code": "Algo fallou."
@ -114,6 +118,8 @@
"saved": "Evento gardado",
"updated": "Evento actualizado",
"where_description": "Onde será o evento? Se non existe podes crealo.",
"coordinates_search": "Procurar coordenadas",
"coordinates_search_description": "Pode procurar o lugar pelo nome ou colar o par de coordenadas.",
"not_found": "Non atopamos o evento",
"show_recurrent": "eventos recurrentes",
"show_past": "tamén eventos previos",
@ -187,6 +193,7 @@
"resources": "Recursos",
"allow_registration_description": "Permitir o rexistro libre?",
"allow_anon_event": "Permitir eventos anónimos (haberá que confirmalos)?",
"allow_geolocation": "Permitir a geolocalização de eventos",
"event_confirm_description": "Aquí podes confirmar os eventos engadidos por usuarias anónimas",
"remove_admin": "Eliminar admin",
"delete_user": "Eliminar",

View file

@ -88,7 +88,11 @@
"max_events": "N. massimo eventi",
"label": "Etichetta",
"collections": "Bolle",
"help_translate": "Aiuta a tradurre"
"help_translate": "Aiuta a tradurre",
"show_map": "Mostra mappa",
"latitude": "Latitudine",
"longitude": "Longitudine",
"getting_there": "Come arrivare"
},
"login": {
"description": "Entrando puoi pubblicare nuovi eventi.",
@ -129,6 +133,8 @@
"added_anon": "Evento aggiunto, verrà confermato quanto prima.",
"updated": "Evento aggiornato",
"where_description": "Dov'è il gancio? Se il posto non è presente potrai crearlo.",
"coordinates_search": "Ricerca coordinate",
"coordinates_search_description": "Puoi ricercare il posto per nome, o incollare la coppia di coordinate.",
"confirmed": "Evento confermato",
"not_found": "Evento non trovato",
"remove_confirmation": "Vuoi eliminare questo evento?",
@ -179,6 +185,7 @@
"allow_registration_description": "Vuoi abilitare la registrazione?",
"allow_anon_event": "Si possono inserire eventi anonimi (previa conferma)?",
"allow_recurrent_event": "Abilita eventi ricorrenti",
"allow_geolocation": "Abilita la geolocalizzazione degli eventi",
"recurrent_event_visible": "Appuntamenti ricorrenti visibili di default",
"federation": "Federazione / ActivityPub",
"enable_federation": "Abilita la federazione",

View file

@ -81,6 +81,7 @@
"allow_recurrent_event": "Tillat gjentagende hendelser",
"allow_anon_event": "Tillat anonyme hendelser (må bekreftes)?",
"allow_registration_description": "Tillat selv-registrering?",
"allow_geolocation": "Tillat geolokalisering av hendelser",
"event_confirm_description": "Du kan bekrefte hendelser som oppføres av anonyme brukere her",
"place_description": "Hvis du har valgt feil sted eller adresse, kan du endre det. <br/>Alle nåværende og foregående hendelser tilknyttet dette stedet vil endre adresse."
},
@ -119,6 +120,8 @@
"recurrent_2w_days": "En {days} annenhver",
"multidate_description": "Er det en festival? Velg når den starter og slutter",
"where_description": "Hvor finner hendelsen sted? Hvis den ikke finnes kan du opprette den.",
"coordinates_search": "Søk etter koordinater",
"coordinates_search_description": "Du kan søke etter sted etter navn, eller lime inn koordinatparet.",
"added_anon": "Hendelse lagt til, men ikke bekreftet enda.",
"added": "Hendelse lagt til",
"media_description": "Du kan legge til et flygeblad (valgfritt)",
@ -232,7 +235,11 @@
"federation": "Føderasjon",
"n_resources": "ingen ressurs|én ressurs|{n} ressurser",
"associate": "Tilknytt",
"import": "Importer"
"import": "Importer",
"show_map": "Vis kart",
"latitude": "breddegrad",
"longitude": "Lengdegrad",
"getting_there": "Slik kommer du deg dit"
},
"about": "\n <p><a href='https://gancio.org'>Gancio</a> er en delt agenda for lokale gemenskaper.</p>\n ",
"validators": {

View file

@ -52,10 +52,12 @@
"https-proxy-agent": "^5.0.1",
"ical.js": "^1.5.0",
"ics": "^2.40.0",
"jsdom": "^20.0.0",
"jest-environment-jsdom": "^29.3.1",
"jsdom": "^20.0.2",
"jsonwebtoken": "^8.5.1",
"linkify-html": "^4.0.2",
"linkifyjs": "4.0.2",
"leaflet": "^1.8.0",
"lodash": "^4.17.21",
"mariadb": "^3.0.1",
"microformat-node": "^2.0.1",
@ -83,6 +85,7 @@
"tiptap-extensions": "^1.35.0",
"umzug": "^2.3.0",
"v-calendar": "^2.4.1",
"vue2-leaflet": "^2.7.1",
"vuetify": "2.6.10",
"winston": "^3.8.2",
"winston-daily-rotate-file": "^4.7.1",
@ -105,6 +108,9 @@
"glob-parent": "^5.1.2",
"moment": "^2.29.2"
},
"jest": {
"testEnvironment": "jsdom"
},
"bin": {
"gancio": "server/cli.js"
},

View file

@ -137,7 +137,7 @@ export default {
valid: false,
openImportDialog: false,
event: {
place: { name: '', address: '' },
place: { name: '', address: '', latitude: null, longitude: null },
title: '',
description: '',
tags: [],
@ -217,8 +217,10 @@ export default {
if (this.event.place.id) {
formData.append('place_id', this.event.place.id)
}
formData.append('place_name', this.event.place.name)
formData.append('place_name', this.event.place.name.trim())
formData.append('place_address', this.event.place.address)
formData.append('place_latitude', this.event.place.latitude)
formData.append('place_longitude', this.event.place.longitude)
formData.append('description', this.event.description)
formData.append('multidate', !!this.date.multidate)
let [hour, minute] = this.date.fromHour.split(':')

View file

@ -4,11 +4,8 @@ v-container#event.pa-0.pa-sm-2
//- gancio supports microformats (http://microformats.org/wiki/h-event)
//- and microdata https://schema.org/Event
v-card.h-event(itemscope itemtype="https://schema.org/Event")
v-card-actions
//- admin controls
EventAdmin.mb-1(v-if='is_mine' :event='event')
v-card-text
v-card-text
v-row
v-col.col-12.col-md-8
MyPicture(v-if='hasMedia' :event='event')
@ -18,39 +15,74 @@ v-container#event.pa-0.pa-sm-2
v-card(outlined)
v-card-text
v-icon.float-right(v-if='event.parentId' color='success' v-text='mdiRepeat')
.title.text-h5.mb-5
.title.text-h5
strong.p-name.text--primary(itemprop="name") {{event.title}}
time.dt-start.text-h6(:datetime='event.start_datetime|unixFormat("YYYY-MM-DD HH:mm")' itemprop="startDate" :content="event.start_datetime|unixFormat('YYYY-MM-DDTHH:mm')")
v-icon(v-text='mdiCalendar')
v-divider
v-card-text
time.dt-start.text-button(:datetime='event.start_datetime|unixFormat("YYYY-MM-DD HH:mm")' itemprop="startDate" :content="event.start_datetime|unixFormat('YYYY-MM-DDTHH:mm')")
v-icon(v-text='mdiCalendar' small)
strong.ml-2 {{event|when}}
.d-none.dt-end(itemprop="endDate" :content="event.end_datetime|unixFormat('YYYY-MM-DDTHH:mm')") {{event.end_datetime|unixFormat('YYYY-MM-DD HH:mm')}}
div.text-subtitle-1.mb-5 {{event.start_datetime|from}}
div.text-caption.mb-3 {{event.start_datetime|from}}
small(v-if='event.parentId') ({{event|recurrentDetail}})
.text-h6.p-location.h-adr(itemprop="location" itemscope itemtype="https://schema.org/Place")
v-icon(v-text='mdiMapMarker')
nuxt-link.vcard.ml-2.p-name.text-decoration-none(itemprop="name" :to='`/place/${event.place.name}`') {{event.place && event.place.name}}
.text-subtitle-1.p-street-address(itemprop='address') {{event.place && event.place.address}}
v-icon(v-text='mdiMapMarker' small)
nuxt-link.vcard.ml-2.p-name.text-decoration-none.text-button(itemprop="name" :to='`/place/${event.place.name}`') {{event.place && event.place.name}}
.text-caption.p-street-address(itemprop='address') {{event.place && event.place.address}}
//- tags, hashtags
v-card-text.pt-0(v-if='event.tags && event.tags.length')
v-chip.p-category.ml-1.mt-3(v-for='tag in event.tags' color='primary'
v-chip.p-category.ml-1.mt-1(v-for='tag in event.tags' small label color='primary'
outlined :key='tag' :to='`/tag/${tag}`') {{tag}}
v-divider
//- info & actions
v-toolbar
v-btn.ml-2(large icon :title="$t('common.copy_link')" :aria-label="$t('common.copy_link')" color='primary'
@click='clipboard(`${settings.baseurl}/event/${event.slug || event.id}`)')
v-icon(v-text='mdiContentCopy')
v-btn.ml-2(large icon :title="$t('common.embed')" :aria-label="$t('common.embed')" @click='showEmbed=true' color='primary')
v-icon(v-text='mdiCodeTags')
v-btn.ml-2(large icon :title="$t('common.add_to_calendar')" color='primary' :aria-label="$t('common.add_to_calendar')"
:href='`/api/event/${event.slug || event.id}.ics`')
v-icon(v-text='mdiCalendarExport')
v-btn.ml-2(v-if='hasMedia' large icon :title="$t('event.download_flyer')" color='primary' :aria-label="$t('event.download_flyer')"
:href='event | mediaURL("download")')
v-icon(v-text='mdiFileDownloadOutline')
v-list(dense nav)
v-list-group(:append-icon='mdiChevronUp' :value='!!$vuetify.breakpoint.smAndUp')
template(v-slot:activator)
v-list-item.text-overline Actions
//- copy link
v-list-item(@click='clipboard(`${settings.baseurl}/event/${event.slug || event.id}`)')
v-list-item-icon
v-icon(v-text='mdiContentCopy')
v-list-item-content
v-list-item-title(v-text="$t('common.copy_link')")
//- map
v-list-item(v-if='settings.allow_geolocation && event.place.latitude && event.place.longitude' @click="mapModal = true")
v-list-item-icon
v-icon(v-text='mdiMap')
v-list-item-content
v-list-item-title(v-text="$t('common.show_map')")
//- embed
v-list-item(@click='showEmbed=true')
v-list-item-icon
v-icon(v-text='mdiCodeTags')
v-list-item-content
v-list-item-title(v-text="$t('common.embed')")
//- calendar
v-list-item(:href='`/api/event/${event.slug || event.id}.ics`')
v-list-item-icon
v-icon(v-text='mdiCalendarExport')
v-list-item-content
v-list-item-title(v-text="$t('common.add_to_calendar')")
//- download flyer
v-list-item(v-if='hasMedia' :href='event | mediaURL("download")')
v-list-item-icon
v-icon(v-text='mdiFileDownloadOutline')
v-list-item-content
v-list-item-title(v-text="$t('event.download_flyer')")
v-divider
//- admin actions
eventAdmin(v-if='is_mine' :event='event')
.p-description.text-body-1.pa-3.rounded(v-if='hasMedia && event.description' itemprop='description' v-html='event.description')
@ -122,6 +154,9 @@ v-container#event.pa-0.pa-sm-2
v-dialog(v-model='showEmbed' width='700px' :fullscreen='$vuetify.breakpoint.xsOnly')
EmbedEvent(:event='event' @close='showEmbed=false')
v-dialog(v-show='settings.allow_geolocation && event.place.latitude && event.place.longitude' v-model='mapModal' :fullscreen='$vuetify.breakpoint.xsOnly' destroy-on-close)
Map(:event='event' @close='mapModal=false')
</template>
<script>
@ -135,9 +170,9 @@ import EmbedEvent from '@/components/embedEvent'
const { htmlToText } = require('html-to-text')
import { mdiArrowLeft, mdiArrowRight, mdiDotsVertical, mdiCodeTags, mdiClose,
import { mdiArrowLeft, mdiArrowRight, mdiDotsVertical, mdiCodeTags, mdiClose, mdiMap,
mdiEye, mdiEyeOff, mdiDelete, mdiRepeat, mdiLock, mdiFileDownloadOutline,
mdiCalendarExport, mdiCalendar, mdiContentCopy, mdiMapMarker } from '@mdi/js'
mdiCalendarExport, mdiCalendar, mdiContentCopy, mdiMapMarker, mdiChevronUp } from '@mdi/js'
export default {
name: 'Event',
@ -145,7 +180,8 @@ export default {
components: {
EventAdmin,
EmbedEvent,
MyPicture
MyPicture,
[process.client && 'Map']: () => import('@/components/Map.vue')
},
async asyncData ({ $axios, params, error }) {
try {
@ -158,12 +194,14 @@ export default {
data () {
return {
mdiArrowLeft, mdiArrowRight, mdiDotsVertical, mdiCodeTags, mdiCalendarExport, mdiCalendar, mdiFileDownloadOutline,
mdiMapMarker, mdiContentCopy, mdiClose, mdiDelete, mdiEye, mdiEyeOff, mdiRepeat, mdiLock,
mdiMapMarker, mdiContentCopy, mdiClose, mdiDelete, mdiEye, mdiEyeOff, mdiRepeat, mdiLock, mdiMap, mdiChevronUp,
currentAttachment: 0,
event: {},
diocane: '',
showEmbed: false,
showResources: false,
selectedResource: { data: { attachment: [] } }
selectedResource: { data: { attachment: [] } },
mapModal: false
}
},
head () {

View file

@ -41,7 +41,9 @@ const eventController = {
if (!place) {
place = await Place.create({
name: place_name,
address: place_address
address: place_address,
latitude: body.place_latitude,
longitude: body.place_longitude
})
}
return place
@ -58,7 +60,7 @@ const eventController = {
Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('address')), 'LIKE', '%' + search + '%')
]
},
attributes: [['name', 'label'], 'address', 'id', [Sequelize.cast(Sequelize.fn('COUNT', Sequelize.col('events.placeId')), 'INTEGER'), 'w']],
attributes: [['name', 'label'], 'address', 'latitude', 'longitude', 'id', [Sequelize.cast(Sequelize.fn('COUNT', Sequelize.col('events.placeId')), 'INTEGER'), 'w']],
include: [{ model: Event, where: { is_visible: true }, required: true, attributes: [] }],
group: ['place.id'],
raw: true
@ -134,7 +136,7 @@ const eventController = {
attributes: ['tag'],
through: { attributes: [] }
},
{ model: Place, required: true, attributes: ['id', 'name', 'address'] }
{ model: Place, required: true, attributes: ['id', 'name', 'address', 'latitude', 'longitude'] }
],
replacements,
limit: 30,
@ -216,7 +218,7 @@ const eventController = {
},
include: [
{ model: Tag, required: false, attributes: ['tag'], through: { attributes: [] } },
{ model: Place, attributes: ['name', 'address', 'id'] },
{ model: Place, attributes: ['name', 'address', 'latitude', 'longitude', 'id'] },
{
model: Resource,
where: !is_admin && { hidden: false },
@ -513,7 +515,7 @@ const eventController = {
description: helpers.sanitizeHTML(linkifyHtml(body.description || '', { target: '_blank' })) || event.description,
multidate: body.multidate,
start_datetime: body.start_datetime || event.start_datetime,
end_datetime: body.end_datetime,
end_datetime: body.end_datetime || null,
recurrent
}
@ -624,7 +626,7 @@ const eventController = {
/**
* Method to search for events with pagination and filtering
* @returns
* @returns
*/
async _select({
start = dayjs().unix(),
@ -698,7 +700,7 @@ const eventController = {
attributes: ['tag'],
through: { attributes: [] }
},
{ model: Place, required: true, attributes: ['id', 'name', 'address'] }
{ model: Place, required: true, attributes: ['id', 'name', 'address', 'latitude', 'longitude'] }
],
...pagination,
replacements

View file

@ -5,6 +5,8 @@ const exportController = require('./export')
const log = require('../../log')
const { Op, where, col, fn, cast } = require('sequelize')
const NOMINATIM_URL = 'https://nominatim.openstreetmap.org/search'
const axios = require('axios')
module.exports = {
@ -60,7 +62,7 @@ module.exports = {
{ address: where(fn('LOWER', col('address')), 'LIKE', '%' + search + '%')},
]
},
attributes: ['name', 'address', 'id'],
attributes: ['name', 'address', 'latitude', 'longitude', 'id'],
include: [{ model: Event, where: { is_visible: true }, required: true, attributes: [] }],
group: ['place.id'],
raw: true,
@ -70,6 +72,23 @@ module.exports = {
// TOFIX: don't know why limit does not work
return res.json(places.slice(0, 10))
}
},
}
async _nominatim (req, res) {
const details = req.params.place_details
// ?limit=3&format=json&namedetails=1&addressdetails=1&q=
const ret = await axios.get(`${NOMINATIM_URL}`, {
params: {
q: details,
limit: 3,
format: 'json',
addressdetails: 1,
namedetails: 1
},
headers: { 'User-Agent': 'gancio 1.6.0' }
})
return res.json(ret.data)
},
}

View file

@ -29,6 +29,7 @@ const defaultSettings = {
allow_anon_event: true,
allow_recurrent_event: false,
recurrent_event_visible: false,
allow_geolocation: true,
enable_federation: true,
enable_resources: false,
hide_boosts: true,

View file

@ -90,12 +90,12 @@ if (config.status !== 'READY') {
* @param {integer} [end] - end timestamp (optional)
* @param {array} [tags] - List of tags
* @param {array} [places] - List of places id
* @param {integer} [max] - Limit events
* @param {integer} [max] - Limit events
* @param {boolean} [show_recurrent] - Show also recurrent events (default: as choosen in admin settings)
* @param {integer} [page] - Pagination
* @param {boolean} [older] - select <= start instead of >=
* @example ***Example***
* [https://demo.gancio.org/api/events](https://demo.gancio.org/api/events)
* @example ***Example***
* [https://demo.gancio.org/api/events](https://demo.gancio.org/api/events)
* [usage example](https://framagit.org/les/gancio/-/blob/master/webcomponents/src/GancioEvents.svelte#L18-42)
*/
@ -111,6 +111,8 @@ if (config.status !== 'READY') {
* @param {string} description - event's description (html accepted and sanitized)
* @param {string} place_name - the name of the place
* @param {string} [place_address] - the address of the place
* @param {float} [place_latitude] - the latitude of the place
* @param {float} [place_longitude] - the longitude of the place
* @param {integer} start_datetime - start timestamp
* @param {integer} multidate - is a multidate event?
* @param {array} tags - List of tags
@ -163,6 +165,7 @@ if (config.status !== 'READY') {
api.get('/place/all', isAdmin, placeController.getAll)
api.get('/place/:placeName', cors, placeController.getEvents)
api.get('/place', cors, placeController.search)
api.get('/placeNominatim/:place_details', cors, placeController._nominatim)
api.put('/place', isAdmin, placeController.updatePlace)
api.get('/tag', cors, tagController.search)

View file

@ -105,7 +105,9 @@ Event.prototype.toAP = function (username, locale, to = []) {
endTime: this.end_datetime ? dayjs.unix(this.end_datetime).tz().locale(locale).format() : null,
location: {
name: this.place.name,
address: this.place.address
address: this.place.address,
latitude: this.place.latitude,
longitude: this.place.longitude
},
attachment,
tag: tags && tags.map(tag => ({

View file

@ -10,7 +10,9 @@ Place.init({
index: true,
allowNull: false
},
address: DataTypes.STRING
address: DataTypes.STRING,
latitude: DataTypes.FLOAT,
longitude: DataTypes.FLOAT,
}, { sequelize, modelName: 'place' })
module.exports = Place

View file

@ -90,6 +90,7 @@ module.exports = {
'theme.primary': settings['theme.primary'],
hide_thumbs: settings.hide_thumbs,
hide_calendar: settings.hide_calendar,
allow_geolocation: settings.allow_geolocation,
footerLinks: settings.footerLinks,
about: settings.about
}

View file

@ -0,0 +1,31 @@
'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('places', 'latitude', { type: Sequelize.FLOAT }),
await queryInterface.addColumn('places', 'longitude', { type: Sequelize.FLOAT })
])
},
async down (queryInterface, Sequelize) {
/**
* Add reverting commands here.
*
* Example:
* await queryInterface.dropTable('users');
*/
return Promise.all(
[
await queryInterface.removeColumn('places', 'latitude'),
await queryInterface.removeColumn('places', 'longitude')
])
}
};

View file

@ -7,6 +7,7 @@ export const state = () => ({
allow_anon_event: true,
allow_recurrent_event: true,
recurrent_event_visible: false,
allow_geolocation: false,
enable_federation: false,
enable_resources: false,
hide_boosts: true,

View file

@ -9,6 +9,7 @@ let app
let places = []
beforeAll(async () => {
switch (process.env.DB) {
case 'mariadb':
process.env.config_path = path.resolve(__dirname, './seeds/config.mariadb.json')

127
yarn.lock
View file

@ -1177,6 +1177,16 @@
"@types/node" "*"
jest-mock "^29.2.2"
"@jest/environment@^29.3.1":
version "29.3.1"
resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.3.1.tgz#eb039f726d5fcd14698acd072ac6576d41cfcaa6"
integrity sha512-pMmvfOPmoa1c1QpfFW0nXYtNLpofqo4BrCIk6f2kW4JFeNlHV2t3vd+3iDLf31e2ot2Mec0uqZfmI+U0K2CFag==
dependencies:
"@jest/fake-timers" "^29.3.1"
"@jest/types" "^29.3.1"
"@types/node" "*"
jest-mock "^29.3.1"
"@jest/expect-utils@^29.2.2":
version "29.2.2"
resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.2.2.tgz#460a5b5a3caf84d4feb2668677393dd66ff98665"
@ -1204,6 +1214,18 @@
jest-mock "^29.2.2"
jest-util "^29.2.1"
"@jest/fake-timers@^29.3.1":
version "29.3.1"
resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.3.1.tgz#b140625095b60a44de820876d4c14da1aa963f67"
integrity sha512-iHTL/XpnDlFki9Tq0Q1GGuVeQ8BHZGIYsvCO5eN/O/oJaRzofG9Xndd9HuSDBI/0ZS79pg0iwn07OMTQ7ngF2A==
dependencies:
"@jest/types" "^29.3.1"
"@sinonjs/fake-timers" "^9.1.2"
"@types/node" "*"
jest-message-util "^29.3.1"
jest-mock "^29.3.1"
jest-util "^29.3.1"
"@jest/globals@^29.2.2":
version "29.2.2"
resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.2.2.tgz#205ff1e795aa774301c2c0ba0be182558471b845"
@ -1313,6 +1335,18 @@
"@types/yargs" "^17.0.8"
chalk "^4.0.0"
"@jest/types@^29.3.1":
version "29.3.1"
resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.3.1.tgz#7c5a80777cb13e703aeec6788d044150341147e3"
integrity sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA==
dependencies:
"@jest/schemas" "^29.0.0"
"@types/istanbul-lib-coverage" "^2.0.0"
"@types/istanbul-reports" "^3.0.0"
"@types/node" "*"
"@types/yargs" "^17.0.8"
chalk "^4.0.0"
"@jridgewell/gen-mapping@^0.1.0":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996"
@ -1992,6 +2026,15 @@
dependencies:
"@types/istanbul-lib-report" "*"
"@types/jsdom@^20.0.0":
version "20.0.1"
resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-20.0.1.tgz#07c14bc19bd2f918c1929541cdaacae894744808"
integrity sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==
dependencies:
"@types/node" "*"
"@types/tough-cookie" "*"
parse5 "^7.0.0"
"@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
version "7.0.11"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
@ -2059,6 +2102,11 @@
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.8.tgz#b94a4391c85666c7b73299fd3ad79d4faa435310"
integrity sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==
"@types/tough-cookie@*":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397"
integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==
"@types/uglify-js@*":
version "3.17.0"
resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.17.0.tgz#95271e7abe0bf7094c60284f76ee43232aef43b9"
@ -6951,6 +6999,20 @@ jest-each@^29.2.1:
jest-util "^29.2.1"
pretty-format "^29.2.1"
jest-environment-jsdom@^29.3.1:
version "29.3.1"
resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.3.1.tgz#14ca63c3e0ef5c63c5bcb46033e50bc649e3b639"
integrity sha512-G46nKgiez2Gy4zvYNhayfMEAFlVHhWfncqvqS6yCd0i+a4NsSUD2WtrKSaYQrYiLQaupHXxCRi8xxVL2M9PbhA==
dependencies:
"@jest/environment" "^29.3.1"
"@jest/fake-timers" "^29.3.1"
"@jest/types" "^29.3.1"
"@types/jsdom" "^20.0.0"
"@types/node" "*"
jest-mock "^29.3.1"
jest-util "^29.3.1"
jsdom "^20.0.0"
jest-environment-node@^29.2.2:
version "29.2.2"
resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.2.2.tgz#a64b272773870c3a947cd338c25fd34938390bc2"
@ -7020,6 +7082,21 @@ jest-message-util@^29.2.1:
slash "^3.0.0"
stack-utils "^2.0.3"
jest-message-util@^29.3.1:
version "29.3.1"
resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.3.1.tgz#37bc5c468dfe5120712053dd03faf0f053bd6adb"
integrity sha512-lMJTbgNcDm5z+6KDxWtqOFWlGQxD6XaYwBqHR8kmpkP+WWWG90I35kdtQHY67Ay5CSuydkTBbJG+tH9JShFCyA==
dependencies:
"@babel/code-frame" "^7.12.13"
"@jest/types" "^29.3.1"
"@types/stack-utils" "^2.0.0"
chalk "^4.0.0"
graceful-fs "^4.2.9"
micromatch "^4.0.4"
pretty-format "^29.3.1"
slash "^3.0.0"
stack-utils "^2.0.3"
jest-mock@^29.2.2:
version "29.2.2"
resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.2.2.tgz#9045618b3f9d27074bbcf2d55bdca6a5e2e8bca7"
@ -7029,6 +7106,15 @@ jest-mock@^29.2.2:
"@types/node" "*"
jest-util "^29.2.1"
jest-mock@^29.3.1:
version "29.3.1"
resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.3.1.tgz#60287d92e5010979d01f218c6b215b688e0f313e"
integrity sha512-H8/qFDtDVMFvFP4X8NuOT3XRDzOUTz+FeACjufHzsOIBAxivLqkB1PoLCaJx9iPPQ8dZThHPp/G3WRWyMgA3JA==
dependencies:
"@jest/types" "^29.3.1"
"@types/node" "*"
jest-util "^29.3.1"
jest-pnp-resolver@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c"
@ -7159,6 +7245,18 @@ jest-util@^29.2.1:
graceful-fs "^4.2.9"
picomatch "^2.2.3"
jest-util@^29.3.1:
version "29.3.1"
resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.3.1.tgz#1dda51e378bbcb7e3bc9d8ab651445591ed373e1"
integrity sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ==
dependencies:
"@jest/types" "^29.3.1"
"@types/node" "*"
chalk "^4.0.0"
ci-info "^3.2.0"
graceful-fs "^4.2.9"
picomatch "^2.2.3"
jest-validate@^29.2.2:
version "29.2.2"
resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.2.2.tgz#e43ce1931292dfc052562a11bc681af3805eadce"
@ -7262,7 +7360,7 @@ jsbn@~0.1.0:
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==
jsdom@^20.0.0:
jsdom@^20.0.0, jsdom@^20.0.2:
version "20.0.2"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-20.0.2.tgz#65ccbed81d5e877c433f353c58bb91ff374127db"
integrity sha512-AHWa+QO/cgRg4N+DsmHg1Y7xnz+8KU3EflM0LVDTdmrYOc1WWTSkOjtpUveQH+1Bqd5rtcVnb/DuxV/UjDO4rA==
@ -7481,6 +7579,11 @@ launch-editor@^2.6.0:
picocolors "^1.0.0"
shell-quote "^1.7.3"
leaflet@^1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.8.0.tgz#4615db4a22a304e8e692cae9270b983b38a2055e"
integrity sha512-gwhMjFCQiYs3x/Sf+d49f10ERXaEFCPr+nVTryhAW8DWbMGqJqt9G4XuIaHmFW08zYvhgdzqXGr8AlW8v8dQkA==
leven@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
@ -9040,7 +9143,7 @@ parse5@^6.0.1:
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
parse5@^7.1.1:
parse5@^7.0.0, parse5@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.1.tgz#4649f940ccfb95d8754f37f73078ea20afe0c746"
integrity sha512-kwpuwzB+px5WUg9pyK0IcK/shltJN5/OVhQagxhCQNtT9Y9QRZqNY2e1cmbu/paRh5LMnz/oVTVLBpjFmMZhSg==
@ -10038,6 +10141,15 @@ pretty-format@^29.2.1:
ansi-styles "^5.0.0"
react-is "^18.0.0"
pretty-format@^29.3.1:
version "29.3.1"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.3.1.tgz#1841cac822b02b4da8971dacb03e8a871b4722da"
integrity sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg==
dependencies:
"@jest/schemas" "^29.0.0"
ansi-styles "^5.0.0"
react-is "^18.0.0"
pretty-time@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/pretty-time/-/pretty-time-1.1.0.tgz#ffb7429afabb8535c346a34e41873adf3d74dd0e"
@ -12643,6 +12755,11 @@ vue-template-es2015-compiler@^1.9.0:
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
vue2-leaflet@^2.7.1:
version "2.7.1"
resolved "https://registry.yarnpkg.com/vue2-leaflet/-/vue2-leaflet-2.7.1.tgz#2f95c287621bf778f10804c88223877f5c049257"
integrity sha512-K7HOlzRhjt3Z7+IvTqEavIBRbmCwSZSCVUlz9u4Rc+3xGCLsHKz4TAL4diAmfHElCQdPPVdZdJk8wPUt2fu6WQ==
vue@^2.7.10:
version "2.7.10"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.7.10.tgz#ae516cc6c88e1c424754468844218fdd5e280f40"
@ -13040,9 +13157,9 @@ ws@^7.3.1:
integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==
ws@^8.9.0:
version "8.10.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.10.0.tgz#00a28c09dfb76eae4eb45c3b565f771d6951aa51"
integrity sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw==
version "8.11.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143"
integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==
xml-name-validator@^4.0.0:
version "4.0.0"