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

This commit is contained in:
lesion
2022-11-15 17:37:13 +01:00
37 changed files with 663 additions and 107 deletions

View File

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

View File

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

View File

@@ -8,6 +8,8 @@ v-col(cols=12)
p {{ $t(`event.${type}_description`) }} 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-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 }} 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 }) this.$emit('input', { ...this.value, fromHour: null, dueHour: null })
} }
} else if (what === 'dueHour') { } 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 }) this.$emit('input', { ...this.value, dueHour: value })
// if (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> 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 }} .d-none(itemprop='address') {{ event.place.address }}
v-card-actions.pt-0.actions.justify-space-between v-card-actions
.tags v-chip.ml-1.mt-1(v-for='tag in event.tags.slice(0, 6)' small label :to='`/tag/${encodeURIComponent(tag)}`'
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 }}
:key='tag' outlined color='primary') {{ tag }}
</template> </template>
<script> <script>

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
<template lang="pug"> <template lang="pug">
v-row v-row.mb-4
v-col(cols=12 md=6) v-col(cols=12 md=6)
v-combobox(ref='place' v-combobox(ref='place'
:rules="[$validators.required('common.where')]" :rules="[$validators.required('common.where')]"
@@ -10,9 +10,8 @@ v-row
hide-no-data hide-no-data
@input.native='search' @input.native='search'
persistent-hint persistent-hint
:value='value' :value='value.name'
:items="places" :items="places"
item-text='name'
@focus='search' @focus='search'
@change='selectPlace') @change='selectPlace')
template(v-slot:item="{ item, attrs, on }") template(v-slot:item="{ item, attrs, on }")
@@ -23,19 +22,49 @@ v-row
v-list-item-title(v-text='item.name') v-list-item-title(v-text='item.name')
v-list-item-subtitle(v-text='item.address') 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-col(cols=12 md=6)
v-text-field(ref='address' v-combobox.mr-4(ref='address'
:prepend-icon='mdiMap' :prepend-icon='mdiMapSearch'
:disabled='disableAddress' :disabled='disableAddress'
:rules="[ v => disableAddress ? true : $validators.required('common.address')(v)]" @input.native='searchCoordinates'
:label="$t('common.address')" :label="$t('event.coordinates_search')"
@change="changeAddress" :value='value.address'
: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> </template>
<script> <script>
import { mdiMap, mdiMapMarker, mdiPlus } from '@mdi/js' import { mdiMap, mdiMapMarker, mdiPlus, mdiMapSearch, mdiLatitude, mdiLongitude } from '@mdi/js'
import debounce from 'lodash/debounce' import debounce from 'lodash/debounce'
import { mapState } from 'vuex'
import get from 'lodash/get'
export default { export default {
name: 'WhereInput', name: 'WhereInput',
@@ -44,14 +73,20 @@ export default {
}, },
data () { data () {
return { return {
mdiMap, mdiMapMarker, mdiPlus, mdiMap, mdiMapMarker, mdiPlus, mdiMapSearch, mdiLatitude, mdiLongitude,
place: { }, place: { },
placeName: '', placeName: '',
places: [], places: [],
disableAddress: true disableAddress: true,
details: { },
detailsView: '',
detailsList: [],
disableDetails: true,
loading: false
} }
}, },
computed: { computed: {
...mapState(['settings']),
filteredPlaces () { filteredPlaces () {
if (!this.placeName) { return this.places } if (!this.placeName) { return this.places }
const placeName = this.placeName.trim().toLowerCase() const placeName = this.placeName.trim().toLowerCase()
@@ -86,6 +121,11 @@ export default {
if (typeof p === 'object' && !p.create) { if (typeof p === 'object' && !p.create) {
this.place.name = p.name this.place.name = p.name
this.place.address = p.address 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.place.id = p.id
this.disableAddress = true this.disableAddress = true
} else { // this is a new place } else { // this is a new place
@@ -101,6 +141,11 @@ export default {
} else { } else {
delete this.place.id delete this.place.id
this.place.address = '' 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.disableAddress = false
this.$refs.place.blur() this.$refs.place.blur()
this.$refs.address.focus() this.$refs.address.focus()
@@ -111,7 +156,73 @@ export default {
changeAddress (v) { changeAddress (v) {
this.place.address = v this.place.address = v
this.$emit('input', { ...this.place }) 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> </script>

View File

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

View File

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

View File

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

View File

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

View File

@@ -454,7 +454,7 @@ function Re(t, e, i) {
}), t.$$set = (d) => { }), 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); "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.$$.update = () => {
t.$$.dirty & 974 && w(); t.$$.dirty & 975 && w();
}, [ }, [
n, n,
l, l,

View File

@@ -1,9 +1,9 @@
<template> <template>
<v-app> <v-app>
<Snackbar/>
<Confirm/>
<Nav/> <Nav/>
<v-main> <v-main >
<Snackbar/>
<Confirm/>
<!-- <div v-if='showCollections || showBack'> <!-- <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' 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> <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 Footer from '../components/Footer'
import Confirm from '../components/Confirm' import Confirm from '../components/Confirm'
import { mapState } from 'vuex' import { mapState } from 'vuex'
import { mdiChevronLeft } from '@mdi/js'
export default { export default {
head () { head () {

View File

@@ -89,7 +89,11 @@
"max_events": "Nre. màx. d'activitats", "max_events": "Nre. màx. d'activitats",
"close": "Tanca", "close": "Tanca",
"blobs": "Blobs", "blobs": "Blobs",
"collections": "Coŀleccions" "collections": "Coŀleccions",
"show_map": "Mostra el mapa",
"latitude": "Latitud",
"longitude": "Longitud",
"getting_there": "Com arribar"
}, },
"login": { "login": {
"description": "Amb la sessió iniciada pots afegir activitats noves.", "description": "Amb la sessió iniciada pots afegir activitats noves.",
@@ -128,6 +132,8 @@
"added": "S'ha afegit l'activitat", "added": "S'ha afegit l'activitat",
"added_anon": "S'ha afegit l'activitat però encara ha de ser confirmada.", "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.", "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", "confirmed": "S'ha confirmat l'activitat",
"not_found": "No s'ha trobat l'activitat", "not_found": "No s'ha trobat l'activitat",
"remove_confirmation": "Segur que vols esborrar l'activitat?", "remove_confirmation": "Segur que vols esborrar l'activitat?",
@@ -179,6 +185,7 @@
"allow_registration_description": "Vols deixar el registre obert?", "allow_registration_description": "Vols deixar el registre obert?",
"allow_anon_event": "Vols permetre activitats anònimes (s'han de confirmar manualment)?", "allow_anon_event": "Vols permetre activitats anònimes (s'han de confirmar manualment)?",
"allow_recurrent_event": "Habilitar activitats periòdiques", "allow_recurrent_event": "Habilitar activitats periòdiques",
"allow_geolocation": "Habilitar la geolocalització d' esdeveniments",
"recurrent_event_visible": "Mostra per defecte les activitats periòdiques", "recurrent_event_visible": "Mostra per defecte les activitats periòdiques",
"federation": "Federació / ActivityPub", "federation": "Federació / ActivityPub",
"enable_federation": "Activa la federació", "enable_federation": "Activa la federació",

View File

@@ -88,7 +88,11 @@
"associate": "Partner:in", "associate": "Partner:in",
"collections": "Sammlungen", "collections": "Sammlungen",
"max_events": "Maximale Anzahl an Veranstaltungen", "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": { "admin": {
"delete_footer_link_confirm": "Möchtest du diesen Link löschen?", "delete_footer_link_confirm": "Möchtest du diesen Link löschen?",
@@ -155,6 +159,7 @@
"enable_federation": "Federation aktivieren", "enable_federation": "Federation aktivieren",
"allow_anon_event": "Kann man auch anonyme Veranstaltungen (vorausgesetzt, diese werden genehmigt) eintragen?", "allow_anon_event": "Kann man auch anonyme Veranstaltungen (vorausgesetzt, diese werden genehmigt) eintragen?",
"allow_registration_description": "Möchtest du die Registrierung aktivieren?", "allow_registration_description": "Möchtest du die Registrierung aktivieren?",
"allow_geolocation": "Aktivieren der Ereignis-Geolokalisierung",
"federation": "Föderation / ActivityPub", "federation": "Föderation / ActivityPub",
"enable_federation_help": "Bei Aktivierung kann diese Instanz vom Fediverse aus verfolgt werden", "enable_federation_help": "Bei Aktivierung kann diese Instanz vom Fediverse aus verfolgt werden",
"add_instance": "Instanz hinzufügen", "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.", "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. ", "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.", "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)", "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", "media_description": "Du kannst (optional) einen Flyer hinzufügen",
"edit_recurrent": "Bearbeite eine sich wiederholende Veranstaltung :", "edit_recurrent": "Bearbeite eine sich wiederholende Veranstaltung :",

View File

@@ -87,7 +87,11 @@
"reset": "Reset", "reset": "Reset",
"theme": "Tema", "theme": "Tema",
"label": "Etiqueta", "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": { "login": {
"description": "Entrando podrás publicar nuevos eventos.", "description": "Entrando podrás publicar nuevos eventos.",
@@ -126,6 +130,8 @@
"added": "Evento agregado", "added": "Evento agregado",
"added_anon": "Evento agregado, será confirmado cuanto antes.", "added_anon": "Evento agregado, será confirmado cuanto antes.",
"where_description": "¿Dónde es? Si el lugar no está, escribilo.", "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", "confirmed": "Evento confirmado",
"not_found": "Evento no encontrado", "not_found": "Evento no encontrado",
"remove_confirmation": "¿Estás seguro/a de querér eliminar este evento?", "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_anon_event": "¿Se pueden ingresar eventos anónimos (sujeto a confirmación)?",
"allow_comments": "Habilitar comentarios", "allow_comments": "Habilitar comentarios",
"allow_recurrent_event": "Habilitar eventos fijos", "allow_recurrent_event": "Habilitar eventos fijos",
"allow_geolocation": "Habilitar la geolocalización de eventos",
"recurrent_event_visible": "Eventos fijos visibles por defecto", "recurrent_event_visible": "Eventos fijos visibles por defecto",
"federation": "Federación / ActivityPub", "federation": "Federación / ActivityPub",
"enable_federation": "Habilitar la federación", "enable_federation": "Habilitar la federación",

View File

@@ -89,7 +89,11 @@
"max_events": "Nb. max d'événements", "max_events": "Nb. max d'événements",
"blobs": "Blobs", "blobs": "Blobs",
"close": "Fermer", "close": "Fermer",
"collections": "Collections" "collections": "Collections",
"show_map": "Afficher la carte",
"latitude": "Latitude",
"longitude": "Longitude",
"getting_there": "Comment s'y rendre"
}, },
"event": { "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)", "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", "not_found": "Impossible de trouver l'évènement",
"confirmed": "Évènement confirmé", "confirmed": "Évènement confirmé",
"where_description": "Où est l'évènement ? S'il n'apparaît pas, vous pouvez le créer.", "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_anon": "Évènement ajouté, mais il doit encore être confirmé.",
"added": "Évènement ajouté", "added": "Évènement ajouté",
"media_description": "Vous pouvez ajouter un tract (facultatif)", "media_description": "Vous pouvez ajouter un tract (facultatif)",
@@ -204,6 +210,7 @@
"allow_recurrent_event": "Autoriser les évènements récurrents", "allow_recurrent_event": "Autoriser les évènements récurrents",
"allow_anon_event": "Autoriser les évènements anonymes (doivent être confirmés) ?", "allow_anon_event": "Autoriser les évènements anonymes (doivent être confirmés) ?",
"allow_registration_description": "Autoriser l'ouverture des inscriptions ?", "allow_registration_description": "Autoriser l'ouverture des inscriptions ?",
"allow_geolocation": "Autoriser la géolocalisation des événements",
"user_create_ok": "Utilisateur créé", "user_create_ok": "Utilisateur créé",
"user_remove_ok": "Utilisateur supprimé", "user_remove_ok": "Utilisateur supprimé",
"delete_user_confirm": "Êtes-vous sûr·e de vouloir supprimer {user} ?", "delete_user_confirm": "Êtes-vous sûr·e de vouloir supprimer {user} ?",

View File

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

View File

@@ -88,7 +88,11 @@
"max_events": "N. massimo eventi", "max_events": "N. massimo eventi",
"label": "Etichetta", "label": "Etichetta",
"collections": "Bolle", "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": { "login": {
"description": "Entrando puoi pubblicare nuovi eventi.", "description": "Entrando puoi pubblicare nuovi eventi.",
@@ -129,6 +133,8 @@
"added_anon": "Evento aggiunto, verrà confermato quanto prima.", "added_anon": "Evento aggiunto, verrà confermato quanto prima.",
"updated": "Evento aggiornato", "updated": "Evento aggiornato",
"where_description": "Dov'è il gancio? Se il posto non è presente potrai crearlo.", "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", "confirmed": "Evento confermato",
"not_found": "Evento non trovato", "not_found": "Evento non trovato",
"remove_confirmation": "Vuoi eliminare questo evento?", "remove_confirmation": "Vuoi eliminare questo evento?",
@@ -179,6 +185,7 @@
"allow_registration_description": "Vuoi abilitare la registrazione?", "allow_registration_description": "Vuoi abilitare la registrazione?",
"allow_anon_event": "Si possono inserire eventi anonimi (previa conferma)?", "allow_anon_event": "Si possono inserire eventi anonimi (previa conferma)?",
"allow_recurrent_event": "Abilita eventi ricorrenti", "allow_recurrent_event": "Abilita eventi ricorrenti",
"allow_geolocation": "Abilita la geolocalizzazione degli eventi",
"recurrent_event_visible": "Appuntamenti ricorrenti visibili di default", "recurrent_event_visible": "Appuntamenti ricorrenti visibili di default",
"federation": "Federazione / ActivityPub", "federation": "Federazione / ActivityPub",
"enable_federation": "Abilita la federazione", "enable_federation": "Abilita la federazione",

View File

@@ -81,6 +81,7 @@
"allow_recurrent_event": "Tillat gjentagende hendelser", "allow_recurrent_event": "Tillat gjentagende hendelser",
"allow_anon_event": "Tillat anonyme hendelser (må bekreftes)?", "allow_anon_event": "Tillat anonyme hendelser (må bekreftes)?",
"allow_registration_description": "Tillat selv-registrering?", "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", "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." "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", "recurrent_2w_days": "En {days} annenhver",
"multidate_description": "Er det en festival? Velg når den starter og slutter", "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.", "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_anon": "Hendelse lagt til, men ikke bekreftet enda.",
"added": "Hendelse lagt til", "added": "Hendelse lagt til",
"media_description": "Du kan legge til et flygeblad (valgfritt)", "media_description": "Du kan legge til et flygeblad (valgfritt)",
@@ -232,7 +235,11 @@
"federation": "Føderasjon", "federation": "Føderasjon",
"n_resources": "ingen ressurs|én ressurs|{n} ressurser", "n_resources": "ingen ressurs|én ressurs|{n} ressurser",
"associate": "Tilknytt", "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 ", "about": "\n <p><a href='https://gancio.org'>Gancio</a> er en delt agenda for lokale gemenskaper.</p>\n ",
"validators": { "validators": {

View File

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

View File

@@ -137,7 +137,7 @@ export default {
valid: false, valid: false,
openImportDialog: false, openImportDialog: false,
event: { event: {
place: { name: '', address: '' }, place: { name: '', address: '', latitude: null, longitude: null },
title: '', title: '',
description: '', description: '',
tags: [], tags: [],
@@ -217,8 +217,10 @@ export default {
if (this.event.place.id) { if (this.event.place.id) {
formData.append('place_id', 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_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('description', this.event.description)
formData.append('multidate', !!this.date.multidate) formData.append('multidate', !!this.date.multidate)
let [hour, minute] = this.date.fromHour.split(':') 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) //- gancio supports microformats (http://microformats.org/wiki/h-event)
//- and microdata https://schema.org/Event //- and microdata https://schema.org/Event
v-card.h-event(itemscope itemtype="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-row
v-col.col-12.col-md-8 v-col.col-12.col-md-8
MyPicture(v-if='hasMedia' :event='event') MyPicture(v-if='hasMedia' :event='event')
@@ -18,39 +15,74 @@ v-container#event.pa-0.pa-sm-2
v-card(outlined) v-card(outlined)
v-card-text v-card-text
v-icon.float-right(v-if='event.parentId' color='success' v-text='mdiRepeat') 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}} strong.p-name.text--primary(itemprop="name") {{event.title}}
v-divider
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-card-text
v-icon(v-text='mdiCalendar') 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}} 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')}} .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}}) small(v-if='event.parentId') ({{event|recurrentDetail}})
.text-h6.p-location.h-adr(itemprop="location" itemscope itemtype="https://schema.org/Place") .text-h6.p-location.h-adr(itemprop="location" itemscope itemtype="https://schema.org/Place")
v-icon(v-text='mdiMapMarker') v-icon(v-text='mdiMapMarker' small)
nuxt-link.vcard.ml-2.p-name.text-decoration-none(itemprop="name" :to='`/place/${event.place.name}`') {{event.place && event.place.name}} 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-subtitle-1.p-street-address(itemprop='address') {{event.place && event.place.address}} .text-caption.p-street-address(itemprop='address') {{event.place && event.place.address}}
//- tags, hashtags //- tags, hashtags
v-card-text.pt-0(v-if='event.tags && event.tags.length') 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}} outlined :key='tag' :to='`/tag/${tag}`') {{tag}}
v-divider
//- info & actions //- info & actions
v-toolbar v-list(dense nav)
v-btn.ml-2(large icon :title="$t('common.copy_link')" :aria-label="$t('common.copy_link')" color='primary' v-list-group(:append-icon='mdiChevronUp' :value='!!$vuetify.breakpoint.smAndUp')
@click='clipboard(`${settings.baseurl}/event/${event.slug || event.id}`)') template(v-slot:activator)
v-icon(v-text='mdiContentCopy') v-list-item.text-overline Actions
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') //- copy link
v-btn.ml-2(large icon :title="$t('common.add_to_calendar')" color='primary' :aria-label="$t('common.add_to_calendar')" v-list-item(@click='clipboard(`${settings.baseurl}/event/${event.slug || event.id}`)')
:href='`/api/event/${event.slug || event.id}.ics`') v-list-item-icon
v-icon(v-text='mdiCalendarExport') v-icon(v-text='mdiContentCopy')
v-btn.ml-2(v-if='hasMedia' large icon :title="$t('event.download_flyer')" color='primary' :aria-label="$t('event.download_flyer')" v-list-item-content
:href='event | mediaURL("download")') v-list-item-title(v-text="$t('common.copy_link')")
v-icon(v-text='mdiFileDownloadOutline')
//- 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') .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') v-dialog(v-model='showEmbed' width='700px' :fullscreen='$vuetify.breakpoint.xsOnly')
EmbedEvent(:event='event' @close='showEmbed=false') 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> </template>
<script> <script>
@@ -135,9 +170,9 @@ import EmbedEvent from '@/components/embedEvent'
const { htmlToText } = require('html-to-text') 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, mdiEye, mdiEyeOff, mdiDelete, mdiRepeat, mdiLock, mdiFileDownloadOutline,
mdiCalendarExport, mdiCalendar, mdiContentCopy, mdiMapMarker } from '@mdi/js' mdiCalendarExport, mdiCalendar, mdiContentCopy, mdiMapMarker, mdiChevronUp } from '@mdi/js'
export default { export default {
name: 'Event', name: 'Event',
@@ -145,7 +180,8 @@ export default {
components: { components: {
EventAdmin, EventAdmin,
EmbedEvent, EmbedEvent,
MyPicture MyPicture,
[process.client && 'Map']: () => import('@/components/Map.vue')
}, },
async asyncData ({ $axios, params, error }) { async asyncData ({ $axios, params, error }) {
try { try {
@@ -158,12 +194,14 @@ export default {
data () { data () {
return { return {
mdiArrowLeft, mdiArrowRight, mdiDotsVertical, mdiCodeTags, mdiCalendarExport, mdiCalendar, mdiFileDownloadOutline, 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, currentAttachment: 0,
event: {}, event: {},
diocane: '',
showEmbed: false, showEmbed: false,
showResources: false, showResources: false,
selectedResource: { data: { attachment: [] } } selectedResource: { data: { attachment: [] } },
mapModal: false
} }
}, },
head () { head () {

View File

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

View File

@@ -5,6 +5,8 @@ const exportController = require('./export')
const log = require('../../log') const log = require('../../log')
const { Op, where, col, fn, cast } = require('sequelize') const { Op, where, col, fn, cast } = require('sequelize')
const NOMINATIM_URL = 'https://nominatim.openstreetmap.org/search'
const axios = require('axios')
module.exports = { module.exports = {
@@ -60,7 +62,7 @@ module.exports = {
{ address: where(fn('LOWER', col('address')), 'LIKE', '%' + search + '%')}, { 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: [] }], include: [{ model: Event, where: { is_visible: true }, required: true, attributes: [] }],
group: ['place.id'], group: ['place.id'],
raw: true, raw: true,
@@ -70,6 +72,23 @@ module.exports = {
// TOFIX: don't know why limit does not work // TOFIX: don't know why limit does not work
return res.json(places.slice(0, 10)) 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_anon_event: true,
allow_recurrent_event: false, allow_recurrent_event: false,
recurrent_event_visible: false, recurrent_event_visible: false,
allow_geolocation: true,
enable_federation: true, enable_federation: true,
enable_resources: false, enable_resources: false,
hide_boosts: true, hide_boosts: true,

View File

@@ -90,12 +90,12 @@ if (config.status !== 'READY') {
* @param {integer} [end] - end timestamp (optional) * @param {integer} [end] - end timestamp (optional)
* @param {array} [tags] - List of tags * @param {array} [tags] - List of tags
* @param {array} [places] - List of places id * @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 {boolean} [show_recurrent] - Show also recurrent events (default: as choosen in admin settings)
* @param {integer} [page] - Pagination * @param {integer} [page] - Pagination
* @param {boolean} [older] - select <= start instead of >= * @param {boolean} [older] - select <= start instead of >=
* @example ***Example*** * @example ***Example***
* [https://demo.gancio.org/api/events](https://demo.gancio.org/api/events) * [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) * [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} description - event's description (html accepted and sanitized)
* @param {string} place_name - the name of the place * @param {string} place_name - the name of the place
* @param {string} [place_address] - the address 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} start_datetime - start timestamp
* @param {integer} multidate - is a multidate event? * @param {integer} multidate - is a multidate event?
* @param {array} tags - List of tags * @param {array} tags - List of tags
@@ -163,6 +165,7 @@ if (config.status !== 'READY') {
api.get('/place/all', isAdmin, placeController.getAll) api.get('/place/all', isAdmin, placeController.getAll)
api.get('/place/:placeName', cors, placeController.getEvents) api.get('/place/:placeName', cors, placeController.getEvents)
api.get('/place', cors, placeController.search) api.get('/place', cors, placeController.search)
api.get('/placeNominatim/:place_details', cors, placeController._nominatim)
api.put('/place', isAdmin, placeController.updatePlace) api.put('/place', isAdmin, placeController.updatePlace)
api.get('/tag', cors, tagController.search) 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, endTime: this.end_datetime ? dayjs.unix(this.end_datetime).tz().locale(locale).format() : null,
location: { location: {
name: this.place.name, name: this.place.name,
address: this.place.address address: this.place.address,
latitude: this.place.latitude,
longitude: this.place.longitude
}, },
attachment, attachment,
tag: tags && tags.map(tag => ({ tag: tags && tags.map(tag => ({

View File

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

View File

@@ -90,6 +90,7 @@ module.exports = {
'theme.primary': settings['theme.primary'], 'theme.primary': settings['theme.primary'],
hide_thumbs: settings.hide_thumbs, hide_thumbs: settings.hide_thumbs,
hide_calendar: settings.hide_calendar, hide_calendar: settings.hide_calendar,
allow_geolocation: settings.allow_geolocation,
footerLinks: settings.footerLinks, footerLinks: settings.footerLinks,
about: settings.about 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_anon_event: true,
allow_recurrent_event: true, allow_recurrent_event: true,
recurrent_event_visible: false, recurrent_event_visible: false,
allow_geolocation: false,
enable_federation: false, enable_federation: false,
enable_resources: false, enable_resources: false,
hide_boosts: true, hide_boosts: true,

View File

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

127
yarn.lock
View File

@@ -1177,6 +1177,16 @@
"@types/node" "*" "@types/node" "*"
jest-mock "^29.2.2" 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": "@jest/expect-utils@^29.2.2":
version "29.2.2" version "29.2.2"
resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.2.2.tgz#460a5b5a3caf84d4feb2668677393dd66ff98665" 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-mock "^29.2.2"
jest-util "^29.2.1" 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": "@jest/globals@^29.2.2":
version "29.2.2" version "29.2.2"
resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.2.2.tgz#205ff1e795aa774301c2c0ba0be182558471b845" resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.2.2.tgz#205ff1e795aa774301c2c0ba0be182558471b845"
@@ -1313,6 +1335,18 @@
"@types/yargs" "^17.0.8" "@types/yargs" "^17.0.8"
chalk "^4.0.0" 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": "@jridgewell/gen-mapping@^0.1.0":
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996"
@@ -1992,6 +2026,15 @@
dependencies: dependencies:
"@types/istanbul-lib-report" "*" "@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": "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
version "7.0.11" version "7.0.11"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" 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" resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.8.tgz#b94a4391c85666c7b73299fd3ad79d4faa435310"
integrity sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ== 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@*": "@types/uglify-js@*":
version "3.17.0" version "3.17.0"
resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.17.0.tgz#95271e7abe0bf7094c60284f76ee43232aef43b9" 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" jest-util "^29.2.1"
pretty-format "^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: jest-environment-node@^29.2.2:
version "29.2.2" version "29.2.2"
resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.2.2.tgz#a64b272773870c3a947cd338c25fd34938390bc2" 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" slash "^3.0.0"
stack-utils "^2.0.3" 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: jest-mock@^29.2.2:
version "29.2.2" version "29.2.2"
resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.2.2.tgz#9045618b3f9d27074bbcf2d55bdca6a5e2e8bca7" 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" "*" "@types/node" "*"
jest-util "^29.2.1" 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: jest-pnp-resolver@^1.2.2:
version "1.2.2" version "1.2.2"
resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" 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" graceful-fs "^4.2.9"
picomatch "^2.2.3" 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: jest-validate@^29.2.2:
version "29.2.2" version "29.2.2"
resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.2.2.tgz#e43ce1931292dfc052562a11bc681af3805eadce" 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" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==
jsdom@^20.0.0: jsdom@^20.0.0, jsdom@^20.0.2:
version "20.0.2" version "20.0.2"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-20.0.2.tgz#65ccbed81d5e877c433f353c58bb91ff374127db" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-20.0.2.tgz#65ccbed81d5e877c433f353c58bb91ff374127db"
integrity sha512-AHWa+QO/cgRg4N+DsmHg1Y7xnz+8KU3EflM0LVDTdmrYOc1WWTSkOjtpUveQH+1Bqd5rtcVnb/DuxV/UjDO4rA== integrity sha512-AHWa+QO/cgRg4N+DsmHg1Y7xnz+8KU3EflM0LVDTdmrYOc1WWTSkOjtpUveQH+1Bqd5rtcVnb/DuxV/UjDO4rA==
@@ -7481,6 +7579,11 @@ launch-editor@^2.6.0:
picocolors "^1.0.0" picocolors "^1.0.0"
shell-quote "^1.7.3" 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: leven@^3.1.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" 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" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
parse5@^7.1.1: parse5@^7.0.0, parse5@^7.1.1:
version "7.1.1" version "7.1.1"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.1.tgz#4649f940ccfb95d8754f37f73078ea20afe0c746" resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.1.tgz#4649f940ccfb95d8754f37f73078ea20afe0c746"
integrity sha512-kwpuwzB+px5WUg9pyK0IcK/shltJN5/OVhQagxhCQNtT9Y9QRZqNY2e1cmbu/paRh5LMnz/oVTVLBpjFmMZhSg== integrity sha512-kwpuwzB+px5WUg9pyK0IcK/shltJN5/OVhQagxhCQNtT9Y9QRZqNY2e1cmbu/paRh5LMnz/oVTVLBpjFmMZhSg==
@@ -10038,6 +10141,15 @@ pretty-format@^29.2.1:
ansi-styles "^5.0.0" ansi-styles "^5.0.0"
react-is "^18.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: pretty-time@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/pretty-time/-/pretty-time-1.1.0.tgz#ffb7429afabb8535c346a34e41873adf3d74dd0e" 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" resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw== 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: vue@^2.7.10:
version "2.7.10" version "2.7.10"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.7.10.tgz#ae516cc6c88e1c424754468844218fdd5e280f40" resolved "https://registry.yarnpkg.com/vue/-/vue-2.7.10.tgz#ae516cc6c88e1c424754468844218fdd5e280f40"
@@ -13040,9 +13157,9 @@ ws@^7.3.1:
integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==
ws@^8.9.0: ws@^8.9.0:
version "8.10.0" version "8.11.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.10.0.tgz#00a28c09dfb76eae4eb45c3b565f771d6951aa51" resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143"
integrity sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw== integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==
xml-name-validator@^4.0.0: xml-name-validator@^4.0.0:
version "4.0.0" version "4.0.0"