Merge branch 'dev' into feat/add_user_theme_view_controls
This commit is contained in:
commit
3eacc7ea33
117 changed files with 4890 additions and 2443 deletions
38
CHANGELOG
38
CHANGELOG
|
@ -1,27 +1,45 @@
|
|||
All notable changes to this project will be documented in this file.
|
||||
|
||||
### UNRELEASED
|
||||
### 1.6.2 - 12 jan '23
|
||||
- add swipe gesture to move to next/prev event
|
||||
- fix refresh collections, fix #219
|
||||
- add russian translation (thanks @drunkod)
|
||||
- refactor search / filter / selection fix #225, 227, #224
|
||||
- models initialization refactored, better dev experience, fix backend HMR
|
||||
|
||||
### 1.6.1 - 15 dec '22
|
||||
- allow edit tags in admin panel, fix #170
|
||||
- fix header / fallback image upload, fix #222
|
||||
- fix WPGancio MU
|
||||
- fix recurrent events label
|
||||
- update translations (de, es, eu, gl)
|
||||
|
||||
### 1.6.0 - 11 dec '22
|
||||
- new plugin system - fix #177
|
||||
- new "publish on telegram" plugin: (thanks @fadelkon)
|
||||
- i18n refactoring
|
||||
- people can now choose the language displayed - fix #171
|
||||
- fix place "[Object]" issue - #194
|
||||
- live search
|
||||
- admin could choose a custom fallback image - fix #195
|
||||
- it is now possible NOT to enter the end time of an event - fix #188
|
||||
- Wordpress plugin now supports MU installation
|
||||
- add nominatim / openstreetmap feature (thanks @sedum)
|
||||
- live search
|
||||
- improve event import
|
||||
- new chinese translation
|
||||
- new portuguese translation
|
||||
- add Apple touch icon - fix #200
|
||||
- improve navbar layout
|
||||
- improve event layout
|
||||
- add nominatim / openstreetmap search feature (thanks @sedum)
|
||||
- new hide calendar option
|
||||
- new hide thumbs from homepage option
|
||||
- linkable admin tab
|
||||
- friendly instances label is now customizable (thanks @sedum)
|
||||
- i18n refactoring
|
||||
- Wordpress plugin now supports MU installation
|
||||
- new chinese translation
|
||||
- new portuguese translation
|
||||
- improved navbar layout
|
||||
- improved event layout
|
||||
- complete oauth2 refactoring
|
||||
- fix ics unique uuid
|
||||
- fix place "[Object]" issue - #194
|
||||
- fix random restart while downloading random media
|
||||
- fix mobile dialog layout
|
||||
- urlencode place and tag urls
|
||||
|
||||
|
||||
### 1.5.6 - 22 set '22
|
||||
|
|
|
@ -8,7 +8,7 @@ export function attributesFromEvents(_events) {
|
|||
const key = dayjs.unix(e.start_datetime).tz().format('MMDD') // Math.floor(e.start_datetime/(3600*24)) // dayjs.unix(e.start_datetime).tz().format('YYYYMMDD')
|
||||
const c = (e.end_datetime || e.start_datetime) < now ? 'vc-past' : ''
|
||||
|
||||
if (e.multidate) {
|
||||
if (e.multidate === true) {
|
||||
attributes.push({
|
||||
dates: { start: new Date(e.start_datetime * 1000), end: new Date(e.end_datetime * 1000) },
|
||||
highlight: {
|
||||
|
|
|
@ -1,22 +1,20 @@
|
|||
<template>
|
||||
<nav>
|
||||
|
||||
<NavHeader />
|
||||
|
||||
<!-- title -->
|
||||
<div class='text-center'>
|
||||
<nuxt-link id='title' v-text='settings.title' to='/' />
|
||||
<div class='text-body-1 font-weight-light' v-text='settings.description' />
|
||||
<div class="text-center">
|
||||
<nuxt-link id="title" v-text="settings.title" to="/" />
|
||||
<div
|
||||
class="text-body-1 font-weight-light"
|
||||
v-text="settings.description"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<NavSearch />
|
||||
|
||||
<NavBar />
|
||||
|
||||
</nav>
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
@ -27,18 +25,24 @@ import NavSearch from './NavSearch.vue'
|
|||
export default {
|
||||
name: 'Appbar',
|
||||
components: { NavHeader, NavBar, NavSearch },
|
||||
computed: mapState(['settings'])
|
||||
computed: mapState(['settings']),
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
nav {
|
||||
background-image: linear-gradient(rgba(59, 0, 0, 0.4), rgba(0, 0, 0, 0.4)), url(/headerimage.png);
|
||||
background-image: linear-gradient(rgba(0, 0, 0, 0.8), rgba(20, 20, 20, 0.7)),
|
||||
url(/headerimage.png);
|
||||
background-position: center center;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.theme--light nav {
|
||||
background-image: linear-gradient(to bottom, rgba(255,230,230,.95), rgba(250,250,250,.95)), url(/headerimage.png);
|
||||
background-image: linear-gradient(
|
||||
to bottom,
|
||||
rgba(230, 230, 230, 0.95),
|
||||
rgba(250, 250, 250, 0.95)
|
||||
),
|
||||
url(/headerimage.png);
|
||||
}
|
||||
|
||||
#title {
|
||||
|
@ -46,5 +50,4 @@ nav {
|
|||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -15,13 +15,14 @@
|
|||
aria-label='Calendar'
|
||||
is-expanded
|
||||
is-inline)
|
||||
template(v-slot="{ inputValue, inputEvents }")
|
||||
//- template(v-slot="{ inputValue, inputEvents }")
|
||||
v-btn#calendarButton(v-on='inputEvents' text tile :color='selectedDate ? "primary" : "" ') {{inputValue || $t('common.calendar')}}
|
||||
v-icon(v-if='selectedDate' v-text='mdiClose' right small icon @click.prevent.stop='selectedDate = null')
|
||||
v-icon(v-else v-text='mdiChevronDown' right small icon)
|
||||
template(v-slot:placeholder)
|
||||
v-btn#calendarButton(text tile) {{$t('common.calendar')}}
|
||||
v-icon(v-text='mdiChevronDown' right small icon)
|
||||
.calh.d-flex.justify-center.align-center(slot='placeholder')
|
||||
v-progress-circular(indeterminate)
|
||||
//- v-btn#calendarButton(text tile) {{$t('common.calendar')}}
|
||||
//- v-icon(v-text='mdiChevronDown' right small icon)
|
||||
|
||||
</template>
|
||||
|
||||
|
@ -65,6 +66,16 @@ export default {
|
|||
</script>
|
||||
|
||||
<style>
|
||||
.vc-container.vc-is-dark {
|
||||
--gray-900: #111;
|
||||
--gray-700: #333;
|
||||
}
|
||||
|
||||
.vc-container {
|
||||
--gray-400: #999 !important;
|
||||
--rounded-lg: 4px !important;
|
||||
}
|
||||
|
||||
.vc-opacity-0 {
|
||||
opacity: 0.3 !important;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ v-dialog(v-model='show'
|
|||
@keydown.esc='cancel')
|
||||
v-card
|
||||
v-card-title {{ title }}
|
||||
v-card-text(v-show='!!message') {{ message }}
|
||||
v-card-text(v-show='!!message' v-html='message')
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(outlined color='error' @click='cancel') {{$t('common.cancel')}}
|
||||
|
|
|
@ -3,12 +3,11 @@ v-col(cols=12)
|
|||
.text-center
|
||||
v-btn-toggle.v-col-6.flex-column.flex-sm-row(v-model='type' color='primary' @change='type => change("type", type)')
|
||||
v-btn(value='normal' label="normal") {{ $t('event.normal') }}
|
||||
v-btn(value='multidate' label='multidate') {{ $t('event.multidate') }}
|
||||
v-btn(v-if='settings.allow_multidate_event' value='multidate' label='multidate') {{ $t('event.multidate') }}
|
||||
v-btn(v-if='settings.allow_recurrent_event' value='recurrent' label="recurrent") {{ $t('event.recurrent') }}
|
||||
|
||||
p {{ $t(`event.${type}_description`) }}
|
||||
|
||||
|
||||
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 }}
|
||||
|
||||
|
@ -25,8 +24,9 @@ v-col(cols=12)
|
|||
is-inline
|
||||
is-expanded
|
||||
:min-date='type !== "recurrent" && new Date()')
|
||||
template(#placeholder)
|
||||
span.calc Loading
|
||||
//- template(#placeholder)
|
||||
.d-flex.calh.justify-center(slot='placeholder')
|
||||
v-progress-circular(indeterminate)
|
||||
|
||||
div.text-center.mb-2(v-if='type === "recurrent"')
|
||||
span(v-if='value.recurrent.frequency !== "1m" && value.recurrent.frequency !== "2m"') {{ whenPatterns }}
|
||||
|
@ -60,7 +60,7 @@ v-col(cols=12)
|
|||
:allowedMinutes='allowedMinutes'
|
||||
format='24hr'
|
||||
@click:minute='menuFromHour = false'
|
||||
@change='hr => change("fromHour", hr)')
|
||||
@input='hr => change("fromHour", hr)')
|
||||
|
||||
|
||||
v-col.col-12.col-sm-6
|
||||
|
@ -88,14 +88,14 @@ v-col(cols=12)
|
|||
:allowedMinutes='allowedMinutes'
|
||||
format='24hr'
|
||||
@click:minute='menuDueHour = false'
|
||||
@change='hr => change("dueHour", hr)')
|
||||
@input='hr => change("dueHour", hr)')
|
||||
|
||||
List(v-if='type === "normal" && todayEvents.length' :events='todayEvents' :title='$t("event.same_day")')
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import dayjs from 'dayjs'
|
||||
import { mapState } from 'vuex'
|
||||
import { mapState, mapActions } from 'vuex'
|
||||
import List from '@/components/List'
|
||||
import { attributesFromEvents } from '../assets/helper'
|
||||
import { mdiClockTimeFourOutline, mdiClockTimeEightOutline, mdiClose } from '@mdi/js'
|
||||
|
@ -114,7 +114,6 @@ export default {
|
|||
menuFromHour: false,
|
||||
menuDueHour: false,
|
||||
type: this.value.type || 'normal',
|
||||
events: [],
|
||||
frequencies: [
|
||||
{ value: '1w', text: this.$t('event.each_week') },
|
||||
{ value: '2w', text: this.$t('event.each_2w') },
|
||||
|
@ -123,7 +122,7 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['settings']),
|
||||
...mapState(['settings', 'events']),
|
||||
fromDate () {
|
||||
if (this.value.from) {
|
||||
if (this.value.multidate) {
|
||||
|
@ -139,7 +138,7 @@ export default {
|
|||
return this.events.filter(e => e.start_datetime >= start && e.start_datetime <= end)
|
||||
},
|
||||
attributes() {
|
||||
return attributesFromEvents(this.events)
|
||||
return attributesFromEvents(this.events.filter(e => e.id !== this.event.id))
|
||||
},
|
||||
whenPatterns() {
|
||||
if (!this.value.from) { return }
|
||||
|
@ -193,13 +192,12 @@ export default {
|
|||
} else {
|
||||
this.type = 'normal'
|
||||
}
|
||||
this.events = await this.$api.getEvents({
|
||||
start: dayjs().unix(),
|
||||
show_recurrent: true
|
||||
})
|
||||
this.events = this.events.filter(e => e.id !== this.event.id)
|
||||
if (!this.events) {
|
||||
this.getEvents()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['getEvents']),
|
||||
updateRecurrent(value) {
|
||||
this.$emit('input', { ...this.value, recurrent: value || null })
|
||||
},
|
||||
|
@ -235,6 +233,15 @@ export default {
|
|||
} else if (what === 'dueHour') {
|
||||
if (value) {
|
||||
this.value.due = this.value.due ? this.value.due : this.value.from
|
||||
const [hour, minute] = value.split(':')
|
||||
const [fromHour, fromMinute] = this.value.fromHour.split(':')
|
||||
if (!this.value.multidate) {
|
||||
if (hour < fromHour) {
|
||||
this.value.due = dayjs(this.value.from).add(1, 'day').toDate()
|
||||
} else {
|
||||
this.value.due = dayjs(this.value.from).toDate()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.value.due = null
|
||||
}
|
||||
|
|
|
@ -50,8 +50,8 @@ export default {
|
|||
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',
|
||||
url: $store.state.settings.tilelayer_provider || 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
attribution: $store.state.settings.tilelayer_provider_attribution || "<a target=\"_blank\" href=\"http://osm.org/copyright\">OpenStreetMap</a> contributors",
|
||||
zoom: 14,
|
||||
center: [this.event.place.latitude, this.event.place.longitude],
|
||||
marker: {
|
||||
|
|
|
@ -1,19 +1,51 @@
|
|||
<template lang="pug">
|
||||
#navsearch.mt-2.mt-sm-4(v-if='showCollectionsBar || showSearchBar')
|
||||
v-text-field.mx-2(v-if='showSearchBar' outlined dense hide-details :placeholder='$t("common.search")' :append-icon='mdiMagnify' @input='search' clearable :clear-icon='mdiClose')
|
||||
template(v-slot:prepend-inner)
|
||||
Calendar(v-if='!settings.hide_calendar')
|
||||
v-btn.ml-2.mt-2.gap-2(v-if='showCollectionsBar' small outlined v-for='collection in collections' color='primary' :key='collection.id' :to='`/collection/${encodeURIComponent(collection.name)}`') {{collection.name}}
|
||||
#navsearch.mt-2.mt-sm-4(v-if='showCollectionsBar || showSearchBar || showCalendar')
|
||||
|
||||
div.mx-2
|
||||
client-only(v-if='showSearchBar')
|
||||
v-menu(offset-y :close-on-content-click='false' tile)
|
||||
template(v-slot:activator="{on ,attrs}")
|
||||
v-text-field(hide-details outlined
|
||||
:placeholder='$t("common.search")'
|
||||
@input="v => setFilter(['query', v])" clearable :clear-icon='mdiClose')
|
||||
template(v-slot:append v-if='settings.allow_recurrent_event || settings.allow_multidate_event')
|
||||
v-icon(v-text='mdiCog' v-bind='attrs' v-on='on')
|
||||
v-card(outlined :rounded='"0"')
|
||||
v-card-text
|
||||
v-row(dense)
|
||||
v-col(v-if='settings.allow_recurrent_event')
|
||||
v-switch.mt-0(v-model='show_recurrent' @change="v => setFilter(['show_recurrent', v])"
|
||||
hide-details :label="$t('event.show_recurrent')" inset)
|
||||
v-col(v-if='settings.allow_multidate_event')
|
||||
v-switch.mt-0(v-model='show_multidate' @change="v => setFilter(['show_multidate', v])"
|
||||
hide-details :label="$t('event.show_multidate')" inset)
|
||||
v-row(v-if='!showCalendar')
|
||||
v-col
|
||||
Calendar.mt-2
|
||||
v-text-field(slot='placeholder' outlined hide-details :placeholder="$t('common.search')" :append-icon='mdiCog')
|
||||
|
||||
span(v-if='showCollectionsBar')
|
||||
v-btn.mr-2.mt-2(small outlined v-for='collection in collections'
|
||||
color='primary' :key='collection.id'
|
||||
:to='`/collection/${encodeURIComponent(collection.name)}`') {{collection.name}}
|
||||
|
||||
Calendar.mt-2(v-if='showCalendar')
|
||||
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { mapState, mapActions } from 'vuex'
|
||||
import Calendar from '@/components/Calendar'
|
||||
import { mdiMagnify, mdiClose } from '@mdi/js'
|
||||
import { mdiClose, mdiCog } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
mdiMagnify, mdiClose,
|
||||
collections: []
|
||||
data: ({ $store }) => ({
|
||||
oldRoute: '',
|
||||
mdiClose, mdiCog,
|
||||
collections: [],
|
||||
show_recurrent: $store.state.settings.recurrent_event_visible,
|
||||
show_multidate: true,
|
||||
query: ''
|
||||
}),
|
||||
async fetch () {
|
||||
this.collections = await this.$axios.$get('collections').catch(_e => [])
|
||||
|
@ -23,21 +55,27 @@ export default {
|
|||
showSearchBar () {
|
||||
return this.$route.name === 'index'
|
||||
},
|
||||
showCollectionsBar () {
|
||||
return ['index', 'collection-collection'].includes(this.$route.name)
|
||||
showCalendar () {
|
||||
return (!this.settings.hide_calendar && this.$route.name === 'index')
|
||||
},
|
||||
...mapState(['settings'])
|
||||
showCollectionsBar () {
|
||||
const show = ['index', 'collection-collection'].includes(this.$route.name)
|
||||
if (show && this.oldRoute !== this.$route.name) {
|
||||
this.oldRoute = this.$route.name
|
||||
this.$fetch()
|
||||
}
|
||||
return show
|
||||
},
|
||||
...mapState(['settings', 'filter'])
|
||||
},
|
||||
methods: {
|
||||
search (ev) {
|
||||
this.$root.$emit('search', ev)
|
||||
}
|
||||
...mapActions(['setFilter']),
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
#navsearch {
|
||||
margin: 0 auto;
|
||||
max-width: 800px;
|
||||
max-width: 700px;
|
||||
}
|
||||
</style>
|
|
@ -22,16 +22,19 @@ v-row.mb-4
|
|||
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-combobox(ref='address'
|
||||
v-text-field(v-if="!settings.allow_geolocation"
|
||||
ref='address'
|
||||
:prepend-icon='mdiMap'
|
||||
:disabled='disableAddress'
|
||||
:rules="[ v => disableAddress ? true : $validators.required('common.address')(v)]"
|
||||
:label="$t('common.address')"
|
||||
:hint="$t('event.address_description')"
|
||||
persistent-hint
|
||||
@change="changeAddress"
|
||||
:value="value.address")
|
||||
v-combobox(ref='address' v-else
|
||||
:prepend-icon='mdiMapSearch'
|
||||
:disabled='disableAddress'
|
||||
@input.native='searchAddress'
|
||||
|
@ -44,7 +47,7 @@ v-row.mb-4
|
|||
@change='selectAddress'
|
||||
@focus='searchAddress'
|
||||
:items="addressList"
|
||||
:hint="$t('event.address_description' + (settings.allow_geolocation && '_osm'))")
|
||||
:hint="$t('event.address_description_osm')")
|
||||
template(v-slot:message="{message, key}")
|
||||
span(v-html='message' :key="key")
|
||||
template(v-slot:item="{ item, attrs, on }")
|
||||
|
@ -76,7 +79,7 @@ export default {
|
|||
props: {
|
||||
value: { type: Object, default: () => ({}) }
|
||||
},
|
||||
data () {
|
||||
data ( {$store} ) {
|
||||
return {
|
||||
mdiMap, mdiMapMarker, mdiPlus, mdiMapSearch, mdiLatitude, mdiLongitude, mdiRoadVariant, mdiHome, mdiCityVariant,
|
||||
place: { },
|
||||
|
@ -91,7 +94,14 @@ export default {
|
|||
node: mdiMapMarker,
|
||||
relation: mdiCityVariant,
|
||||
},
|
||||
nominatim_class: ['amenity', 'shop', 'tourism', 'leisure', 'building']
|
||||
nominatim_class: ['amenity', 'shop', 'tourism', 'leisure', 'building'],
|
||||
photon_osm_key: ['amenity', 'shop', 'tourism', 'leisure', 'building'],
|
||||
photon_osm_type: {
|
||||
'W': mdiRoadVariant,
|
||||
'N': mdiMapMarker,
|
||||
'R': mdiCityVariant,
|
||||
},
|
||||
geocoding_provider_type: $store.state.settings.geocoding_provider_type || 'Nominatim'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -115,21 +125,33 @@ export default {
|
|||
return matches
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.$nextTick( () => {
|
||||
this.search()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
search: debounce(async function(ev) {
|
||||
const search = ev.target.value.trim().toLowerCase()
|
||||
const search = ev ? ev.target.value.trim().toLowerCase() : ''
|
||||
this.places = await this.$axios.$get(`place?search=${search}`)
|
||||
if (!search && this.places.length) { return this.places }
|
||||
const matches = this.places.find(p => search === p.name.toLocaleLowerCase())
|
||||
if (!matches && search) {
|
||||
this.places.unshift({ create: true, name: ev.target.value.trim() })
|
||||
}
|
||||
}, 100),
|
||||
}, 200),
|
||||
loadCoordinatesResultIcon(item) {
|
||||
if (this.geocoding_provider_type == "Nominatim") {
|
||||
if ( this.nominatim_class.includes(item.class)) {
|
||||
return this.mdiHome
|
||||
}
|
||||
return this.nominatim_osm_type[item.type]
|
||||
} else if (this.geocoding_provider_type == "Photon") {
|
||||
if ( this.photon_osm_key.includes(item.class)) {
|
||||
return this.mdiHome
|
||||
}
|
||||
return this.photon_osm_type[item.type]
|
||||
}
|
||||
},
|
||||
selectPlace (p) {
|
||||
if (!p) { return }
|
||||
|
@ -168,11 +190,11 @@ export default {
|
|||
}
|
||||
this.$emit('input', { ...this.place })
|
||||
},
|
||||
// changeAddress (v) {
|
||||
// this.place.address = v
|
||||
// this.$emit('input', { ...this.place })
|
||||
// this.disableDetails = false
|
||||
// },
|
||||
changeAddress (v) {
|
||||
this.place.address = v
|
||||
this.$emit('input', { ...this.place })
|
||||
this.disableDetails = false
|
||||
},
|
||||
selectAddress (v) {
|
||||
if (!v) { return }
|
||||
if (typeof v === 'object') {
|
||||
|
@ -220,7 +242,8 @@ export default {
|
|||
|
||||
if (searchCoordinates.length) {
|
||||
this.loading = true
|
||||
const ret = await this.$axios.$get(`placeNominatim/${searchCoordinates}`)
|
||||
const ret = await this.$axios.$get(`placeOSM/${this.geocoding_provider_type}/${searchCoordinates}`)
|
||||
if (this.geocoding_provider_type == "Nominatim") {
|
||||
if (ret && ret.length) {
|
||||
this.addressList = ret.map(v => {
|
||||
const name = get(v.namedetails, 'alt_name', get(v.namedetails, 'name'))
|
||||
|
@ -237,6 +260,38 @@ export default {
|
|||
} else {
|
||||
this.addressList = []
|
||||
}
|
||||
} else if (this.geocoding_provider_type == "Photon") {
|
||||
let photon_properties = ['housenumber', 'street', 'locality', 'district', 'city', 'county', 'state', 'postcode', 'country']
|
||||
|
||||
if (ret) {
|
||||
this.addressList = ret.features.map(v => {
|
||||
let pre_name = v.properties.name || v.properties.street || ''
|
||||
let pre_address = ''
|
||||
|
||||
photon_properties.forEach((item, i) => {
|
||||
let last = i == (photon_properties.length - 1)
|
||||
if (v.properties[item] && !last) {
|
||||
pre_address += v.properties[item]+', '
|
||||
} else if (v.properties[item]) {
|
||||
pre_address += v.properties[item]
|
||||
}
|
||||
});
|
||||
|
||||
let name = pre_name
|
||||
let address = pre_address
|
||||
return {
|
||||
class: v.properties.osm_key,
|
||||
type: v.properties.osm_type,
|
||||
lat: v.geometry.coordinates[1],
|
||||
lon: v.geometry.coordinates[0],
|
||||
name,
|
||||
address
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.addressList = []
|
||||
}
|
||||
}
|
||||
this.loading = false
|
||||
}
|
||||
}, 1000)
|
||||
|
|
|
@ -33,7 +33,7 @@ v-container
|
|||
:prepend-icon="mdiTagMultiple"
|
||||
chips small-chips multiple deletable-chips hide-no-data hide-selected persistent-hint
|
||||
:disabled="!collection.id"
|
||||
placeholder='Tutte'
|
||||
placeholder='All'
|
||||
@input.native='searchTags'
|
||||
@focus='searchTags'
|
||||
:delimiters="[',', ';']"
|
||||
|
@ -69,7 +69,7 @@ v-container
|
|||
//- v-list-item-subtitle(v-text='item.address')
|
||||
|
||||
v-col(cols=2)
|
||||
v-btn(color='primary' text @click='addFilter' :disabled='!collection.id || !filterPlaces.length && !filterTags.length') add <v-icon v-text='mdiPlus'></v-icon>
|
||||
v-btn(color='primary' :loading='loading' text @click='addFilter' :disabled='loading || !collection.id || !filterPlaces.length && !filterTags.length') add <v-icon v-text='mdiPlus'></v-icon>
|
||||
|
||||
v-data-table(
|
||||
:headers='filterHeaders'
|
||||
|
@ -110,6 +110,9 @@ v-container
|
|||
<script>
|
||||
import get from 'lodash/get'
|
||||
import debounce from 'lodash/debounce'
|
||||
import isEqual from 'lodash/isEqual'
|
||||
import sortBy from 'lodash/sortBy'
|
||||
|
||||
import { mdiPencil, mdiChevronLeft, mdiChevronRight, mdiMagnify, mdiPlus, mdiTagMultiple, mdiMapMarker, mdiDeleteForever, mdiCloseCircle, mdiChevronDown } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
|
@ -147,7 +150,7 @@ export default {
|
|||
|
||||
methods: {
|
||||
searchTags: debounce(async function (ev) {
|
||||
this.tags = await this.$axios.$get(`/tag?search=${ev.target.value}`)
|
||||
this.tags = await this.$axios.$get(`/tag?search=${encodeURIComponent(ev.target.value)}`)
|
||||
}, 100),
|
||||
searchPlaces: debounce(async function (ev) {
|
||||
this.places = await this.$axios.$get(`/place?search=${ev.target.value}`)
|
||||
|
@ -163,9 +166,20 @@ export default {
|
|||
this.loading = true
|
||||
const tags = this.filterTags
|
||||
const places = this.filterPlaces.map(p => ({ id: p.id, name: p.name }))
|
||||
const filter = await this.$axios.$post('/filter', { collectionId: this.collection.id, tags, places })
|
||||
|
||||
const filter = { collectionId: this.collection.id, tags, places }
|
||||
|
||||
// tags and places are JSON field and there's no way to use them inside a unique constrain
|
||||
//
|
||||
const alreadyExists = this.filters.find(f =>
|
||||
isEqual(sortBy(f.places, 'id'), sortBy(filter.places, 'id')) && isEqual(sortBy(f.tags), sortBy(filter.tags))
|
||||
)
|
||||
|
||||
if (alreadyExists) return
|
||||
|
||||
const ret = await this.$axios.$post('/filter', filter )
|
||||
this.$fetch()
|
||||
this.filters.push(filter)
|
||||
this.filters.push(ret)
|
||||
this.filterTags = []
|
||||
this.filterPlaces = []
|
||||
this.loading = false
|
||||
|
|
169
components/admin/Geolocation.vue
Normal file
169
components/admin/Geolocation.vue
Normal file
|
@ -0,0 +1,169 @@
|
|||
<template lang="pug">
|
||||
v-card
|
||||
v-card-title {{$t('admin.geolocation')}}
|
||||
v-card-text
|
||||
p.mb-6(v-html="$t('admin.geolocation_description')")
|
||||
|
||||
v-form
|
||||
v-row
|
||||
v-col(md=3)
|
||||
v-autocomplete.mb-4(v-model='geocoding_provider_type'
|
||||
@blur="save('geocoding_provider_type', geocoding_provider_type )"
|
||||
:label="$t('admin.geocoding_provider_type')"
|
||||
:hint="$t('admin.geocoding_provider_type_help')"
|
||||
persistent-hint
|
||||
:items="geocoding_provider_type_items"
|
||||
:placeholder="geocoding_provider_type_default")
|
||||
|
||||
v-col(md=5)
|
||||
v-text-field.mb-4(v-model='geocoding_provider'
|
||||
@blur="save('geocoding_provider', geocoding_provider )"
|
||||
:label="$t('admin.geocoding_provider')"
|
||||
:hint="$t('admin.geocoding_provider_help')"
|
||||
persistent-hint
|
||||
:placeholder="geocoding_provider_default")
|
||||
|
||||
v-col(md=4)
|
||||
v-autocomplete.mb-6(v-model="geocoding_countrycodes" :disabled="!(geocoding_provider_type === null || geocoding_provider_type === 'Nominatim')"
|
||||
:append-icon='mdiChevronDown'
|
||||
@blur="save('geocoding_countrycodes', geocoding_countrycodes )"
|
||||
:label="$t('admin.geocoding_countrycodes')"
|
||||
:items="countries"
|
||||
multiple chips small-chips persistent-hint
|
||||
item-value="code"
|
||||
item-text="name"
|
||||
:hint="$t('admin.geocoding_countrycodes_help')")
|
||||
|
||||
v-row
|
||||
v-col(md=6)
|
||||
v-text-field.mb-4(v-model='tilelayer_provider'
|
||||
@blur="save('tilelayer_provider', tilelayer_provider )"
|
||||
:label="$t('admin.tilelayer_provider')"
|
||||
:hint="$t('admin.tilelayer_provider_help')"
|
||||
persistent-hint
|
||||
:placeholder="tilelayer_provider_default")
|
||||
|
||||
v-col(md=6)
|
||||
v-text-field(v-model='tilelayer_provider_attribution'
|
||||
@blur="save('tilelayer_provider_attribution', tilelayer_provider_attribution )"
|
||||
:label="$t('admin.tilelayer_provider_attribution')"
|
||||
:placeholder="tilelayer_provider_attribution_default")
|
||||
|
||||
div(id="leaflet-map-preview" max-height='10px')
|
||||
//- Map
|
||||
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(color='primary' @click='testGeocodingProvider' :loading='testGeocodingLoading' outlined ) {{$t('admin.geocoding_test_button')}}
|
||||
v-btn(color='primary' @click='testTileLayerProvider' :loading='testTileLayerLoading' outlined ) {{$t('admin.tilelayer_test_button')}}
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import { mapActions, mapState } from 'vuex'
|
||||
import { isoCountries } from '../../server/helpers/geolocation'
|
||||
import { mdiChevronDown } from '@mdi/js'
|
||||
// import Map from '~/components/Map'
|
||||
import "leaflet/dist/leaflet.css"
|
||||
|
||||
export default {
|
||||
props: {
|
||||
setup: { type: Boolean, default: false }
|
||||
},
|
||||
// components: { Map },
|
||||
data ({ $store }) {
|
||||
return {
|
||||
mdiChevronDown,
|
||||
loading: false,
|
||||
testGeocodingLoading: false,
|
||||
testTileLayerLoading: false,
|
||||
geocoding_provider_type_items: ['Nominatim', 'Photon'],
|
||||
geocoding_provider_type: $store.state.settings.geocoding_provider_type || '',
|
||||
geocoding_provider_type_default: 'Nominatim',
|
||||
geocoding_provider: $store.state.settings.geocoding_provider || '',
|
||||
geocoding_provider_default: "https://nominatim.openstreetmap.org/search" ,
|
||||
geocoding_countrycodes: $store.state.settings.geocoding_countrycodes || [],
|
||||
tilelayer_provider: $store.state.settings.tilelayer_provider || '',
|
||||
tilelayer_provider_default: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
tilelayer_provider_attribution: $store.state.settings.tilelayer_provider_attribution || '',
|
||||
tilelayer_provider_attribution_default: '<a target=\'_blank\' href=\"http://osm.org/copyright\">OpenStreetMap</a> contributors',
|
||||
countries: isoCountries,
|
||||
mapPreviewTest: null,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (process.client) {
|
||||
const L = require('leaflet')
|
||||
}
|
||||
},
|
||||
computed: mapState(['settings', 'events']),
|
||||
methods: {
|
||||
...mapActions(['setSetting']),
|
||||
async testGeocodingProvider () {
|
||||
this.testGeocodingLoading = true
|
||||
const geocodingProviderTest = this.geocoding_provider || this.geocoding_provider_default
|
||||
const geocodingSoftwareTest = this.geocoding_provider_type || this.geocoding_provider_type_default
|
||||
const geocodingQuery = 'building'
|
||||
|
||||
try {
|
||||
if (geocodingSoftwareTest === 'Nominatim') {
|
||||
const geolocation = await this.$axios.$get(`${geocodingProviderTest}`, {timeout: 3000, params: {q: `${geocodingQuery}`, format: 'json', limit: 1 }} )
|
||||
} else if (geocodingSoftwareTest === 'Photon') {
|
||||
const geolocation = await this.$axios.$get(`${geocodingProviderTest}`, {timeout: 3000, params: {q: `${geocodingQuery}`, limit: 1}} )
|
||||
}
|
||||
|
||||
this.$root.$message(this.$t('admin.geocoding_test_success', { service_name: geocodingProviderTest }), { color: 'success' })
|
||||
} catch (e) {
|
||||
this.$root.$message(this.$t('admin.tilelayer_test_error', { service_name: geocodingProviderTest }), { color: 'error' })
|
||||
}
|
||||
this.testGeocodingLoading = false
|
||||
},
|
||||
async testTileLayerProvider () {
|
||||
this.testTileLayerLoading = true
|
||||
const tileThis = this
|
||||
const tileLayerTest = this.tilelayer_provider || this.tilelayer_provider_default
|
||||
const tileLayerAttributionTest = this.tilelayer_provider_attribution || this.tilelayer_provider_attribution_default
|
||||
|
||||
// init tilelayer
|
||||
if (this.mapPreviewTest == null) {
|
||||
this.mapPreviewTest = L.map("leaflet-map-preview").setView([40,40],10);
|
||||
}
|
||||
this.tileLayer = L.tileLayer(`${tileLayerTest}`, {attribution: `${tileLayerAttributionTest}`})
|
||||
this.tileLayer.addTo(this.mapPreviewTest)
|
||||
|
||||
// tilelayer events inherited from gridlayer https://leafletjs.com/reference.html#gridlayer
|
||||
this.tileLayer.on('tileload', function (event) {
|
||||
tileThis.tileLayerTestSucess(event, tileLayerTest)
|
||||
});
|
||||
this.tileLayer.on('tileerror', function(error, tile) {
|
||||
tileThis.tileLayerTestError(event, tileLayerTest)
|
||||
tileThis.tileLayer = null
|
||||
});
|
||||
this.testTileLayerLoading = false
|
||||
},
|
||||
save (key, value) {
|
||||
if (this.settings[key] !== value) {
|
||||
this.setSetting({ key, value })
|
||||
}
|
||||
},
|
||||
done () {
|
||||
this.$emit('close')
|
||||
},
|
||||
geocodingTestError(event, tileLayerTest) {
|
||||
this.$root.$message(this.$t('admin.geocoding_test_error', { service_name: geocodingTest }), { color: 'error' })
|
||||
},
|
||||
tileLayerTestSucess(event, tileLayerTest) {
|
||||
this.$root.$message(this.$t('admin.tilelayer_test_success', { service_name: tileLayerTest }), { color: 'success' })
|
||||
},
|
||||
tileLayerTestError(event, tileLayerTest) {
|
||||
this.$root.$message(this.$t('admin.tilelayer_test_error', { service_name: tileLayerTest }), { color: 'error' })
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#leaflet-map-preview {
|
||||
height: 20rem;
|
||||
}
|
||||
</style>
|
|
@ -68,7 +68,7 @@ import debounce from 'lodash/debounce'
|
|||
import get from 'lodash/get'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
data( {$store} ) {
|
||||
return {
|
||||
mdiPencil, mdiChevronRight, mdiChevronLeft, mdiMagnify, mdiEye, mdiMapSearch, mdiChevronDown,
|
||||
loading: false,
|
||||
|
@ -84,11 +84,12 @@ export default {
|
|||
{ value: 'address', text: this.$t('common.address') },
|
||||
{ value: 'map', text: 'Map' },
|
||||
{ value: 'actions', text: this.$t('common.actions'), align: 'right' }
|
||||
]
|
||||
],
|
||||
geocoding_provider_type: $store.state.settings.geocoding_provider_type || 'Nominatim'
|
||||
}
|
||||
},
|
||||
async fetch() {
|
||||
this.places = await this.$axios.$get('/place/all')
|
||||
this.places = await this.$axios.$get('/places')
|
||||
},
|
||||
computed: {
|
||||
...mapState(['settings']),
|
||||
|
@ -159,12 +160,15 @@ export default {
|
|||
|
||||
if (searchCoordinates.length) {
|
||||
this.loading = true
|
||||
const ret = await this.$axios.$get(`placeNominatim/${searchCoordinates}`)
|
||||
const ret = await this.$axios.$get(`placeOSM/${this.geocoding_provider_type}/${searchCoordinates}`)
|
||||
if (this.geocoding_provider_type == "Nominatim") {
|
||||
if (ret && ret.length) {
|
||||
this.addressList = 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 {
|
||||
class: v.class,
|
||||
type: v.osm_type,
|
||||
lat: v.lat,
|
||||
lon: v.lon,
|
||||
name,
|
||||
|
@ -174,6 +178,38 @@ export default {
|
|||
} else {
|
||||
this.addressList = []
|
||||
}
|
||||
} else if (this.geocoding_provider_type == "Photon") {
|
||||
let photon_properties = ['housenumber', 'street', 'district', 'city', 'county', 'state', 'postcode', 'country']
|
||||
|
||||
if (ret) {
|
||||
this.addressList = ret.features.map(v => {
|
||||
let pre_name = v.properties.name || v.properties.street || ''
|
||||
let pre_address = ''
|
||||
|
||||
photon_properties.forEach((item, i) => {
|
||||
let last = i == (photon_properties.length - 1)
|
||||
if (v.properties[item] && !last) {
|
||||
pre_address += v.properties[item]+', '
|
||||
} else if (v.properties[item]) {
|
||||
pre_address += v.properties[item]
|
||||
}
|
||||
});
|
||||
|
||||
let name = pre_name
|
||||
let address = pre_address
|
||||
return {
|
||||
class: v.properties.osm_key,
|
||||
type: v.properties.osm_type,
|
||||
lat: v.geometry.coordinates[1],
|
||||
lon: v.geometry.coordinates[0],
|
||||
name,
|
||||
address
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.addressList = []
|
||||
}
|
||||
}
|
||||
this.loading = false
|
||||
}
|
||||
}, 300)
|
||||
|
|
|
@ -7,7 +7,9 @@ v-card
|
|||
|
||||
v-text-field(v-model='admin_email'
|
||||
@blur="save('admin_email', admin_email )"
|
||||
:label="$t('admin.sender_email')"
|
||||
:label="$t('admin.admin_email')"
|
||||
:hint="$t('admin.admin_email_help')"
|
||||
persistent-hint
|
||||
:rules="$validators.email")
|
||||
|
||||
v-switch(v-model='smtp.sendmail'
|
||||
|
|
|
@ -39,6 +39,10 @@ v-container
|
|||
inset
|
||||
:label="$t('admin.allow_anon_event')")
|
||||
|
||||
v-switch.mt-1(v-model='allow_multidate_event'
|
||||
inset
|
||||
:label="$t('admin.allow_multidate_event')")
|
||||
|
||||
v-switch.mt-1(v-model='allow_recurrent_event'
|
||||
inset
|
||||
:label="$t('admin.allow_recurrent_event')")
|
||||
|
@ -57,32 +61,35 @@ v-container
|
|||
|
||||
v-card-actions
|
||||
v-btn(text @click='showSMTP=true')
|
||||
<v-icon v-if='!settings.admin_email' color='error' v-text='mdiAlert'></v-icon> {{$t('admin.show_smtp_setup')}}
|
||||
<v-icon v-if='!settings.admin_email' color='error' class="mr-2" v-text='mdiAlert'></v-icon> {{$t('admin.show_smtp_setup')}}
|
||||
|
||||
v-btn(text @click='$emit("complete")' color='primary' v-if='setup') {{$t('common.next')}}
|
||||
v-icon(v-text='mdiArrowRight')
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import SMTP from './SMTP.vue'
|
||||
import Geolocation from './Geolocation.vue'
|
||||
import { mapActions, mapState } from 'vuex'
|
||||
import moment from 'dayjs'
|
||||
import tzNames from './tz.json'
|
||||
import { mdiAlert, mdiArrowRight } from '@mdi/js'
|
||||
import { mdiAlert, mdiArrowRight, mdiMap } from '@mdi/js'
|
||||
const locales = require('../../locales/index')
|
||||
|
||||
export default {
|
||||
props: {
|
||||
setup: { type: Boolean, default: false }
|
||||
},
|
||||
components: { SMTP },
|
||||
components: { SMTP, Geolocation },
|
||||
name: 'Settings',
|
||||
data ({ $store }) {
|
||||
return {
|
||||
mdiAlert, mdiArrowRight,
|
||||
mdiAlert, mdiArrowRight, mdiMap,
|
||||
title: $store.state.settings.title,
|
||||
description: $store.state.settings.description,
|
||||
locales: Object.keys(locales).map(locale => ({ value: locale, text: locales[locale] })),
|
||||
showSMTP: false,
|
||||
showGeolocationConfigs: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -107,6 +114,10 @@ export default {
|
|||
get () { return this.settings.allow_recurrent_event },
|
||||
set (value) { this.setSetting({ key: 'allow_recurrent_event', value }) }
|
||||
},
|
||||
allow_multidate_event: {
|
||||
get () { return this.settings.allow_multidate_event },
|
||||
set (value) { this.setSetting({ key: 'allow_multidate_event', value }) }
|
||||
},
|
||||
recurrent_event_visible: {
|
||||
get () { return this.settings.recurrent_event_visible },
|
||||
set (value) { this.setSetting({ key: 'recurrent_event_visible', value }) }
|
||||
|
|
114
components/admin/Tags.vue
Normal file
114
components/admin/Tags.vue
Normal file
|
@ -0,0 +1,114 @@
|
|||
<template lang='pug'>
|
||||
v-container
|
||||
v-card-title {{ $t('common.tags') }}
|
||||
v-spacer
|
||||
v-text-field(v-model='search'
|
||||
:append-icon='mdiMagnify' outlined rounded
|
||||
:label="$t('common.search')"
|
||||
single-line hide-details)
|
||||
|
||||
v-dialog(v-model='dialog' width='600' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||
v-card
|
||||
v-card-title {{$t('admin.edit_tag')}} -
|
||||
strong.ml-2 {{tag.tag}}
|
||||
v-card-subtitle {{$tc('admin.edit_tag_help', tag.count)}}
|
||||
v-card-text
|
||||
v-form(v-model='valid' ref='form' lazy-validation)
|
||||
v-combobox(v-model='newTag'
|
||||
:prepend-icon="mdiTag"
|
||||
hide-no-data
|
||||
persistent-hint
|
||||
:items="tags"
|
||||
:return-object='false'
|
||||
item-value='tag'
|
||||
item-text='tag'
|
||||
:label="$t('common.tags')")
|
||||
template(v-slot:item="{ item, on, attrs }")
|
||||
span "{{item.tag}}" <small>({{item.count}})</small>
|
||||
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(@click='dialog = false' outlined color='warning') {{ $t('common.cancel') }}
|
||||
v-btn(@click='saveTag' color='primary' outlined :loading='loading'
|
||||
:disable='!valid || loading') {{ $t('common.save') }}
|
||||
|
||||
v-card-text
|
||||
v-data-table(
|
||||
:headers='headers'
|
||||
:items='tags'
|
||||
:hide-default-footer='tags.length < 5'
|
||||
:header-props='{ sortIcon: mdiChevronDown }'
|
||||
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
|
||||
:search='search')
|
||||
template(v-slot:item.map='{ item }')
|
||||
span {{item.latitude && item.longitude && 'YEP' }}
|
||||
template(v-slot:item.actions='{ item }')
|
||||
v-btn(@click='editTag(item)' color='primary' icon)
|
||||
v-icon(v-text='mdiPencil')
|
||||
nuxt-link(:to='`/tag/${item.tag}`')
|
||||
v-icon(v-text='mdiEye')
|
||||
v-btn(@click='removeTag(item)' color='primary' icon)
|
||||
v-icon(v-text='mdiDeleteForever')
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import { mdiPencil, mdiChevronLeft, mdiChevronRight, mdiMagnify, mdiEye, mdiMapSearch, mdiChevronDown, mdiDeleteForever, mdiTag } from '@mdi/js'
|
||||
import { mapState } from 'vuex'
|
||||
import get from 'lodash/get'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
mdiPencil, mdiChevronRight, mdiChevronLeft, mdiMagnify, mdiEye, mdiMapSearch, mdiChevronDown, mdiDeleteForever, mdiTag,
|
||||
loading: false,
|
||||
dialog: false,
|
||||
valid: false,
|
||||
tag: {},
|
||||
newTag: '',
|
||||
tags: [],
|
||||
search: '',
|
||||
headers: [
|
||||
{ value: 'tag', text: this.$t('common.tag') },
|
||||
{ value: 'count', text: 'N.' },
|
||||
{ value: 'actions', text: this.$t('common.actions'), align: 'right' }
|
||||
]
|
||||
}
|
||||
},
|
||||
async fetch() {
|
||||
this.tags = await this.$axios.$get('/tags')
|
||||
},
|
||||
computed: {
|
||||
...mapState(['settings']),
|
||||
},
|
||||
methods: {
|
||||
editTag(item) {
|
||||
this.tag.tag = item.tag
|
||||
this.tag.count = item.count
|
||||
this.dialog = true
|
||||
},
|
||||
async saveTag() {
|
||||
if (!this.$refs.form.validate()) return
|
||||
this.loading = true
|
||||
this.$nextTick( async () => {
|
||||
await this.$axios.$put('/tag', { tag: this.tag.tag, newTag: this.newTag })
|
||||
await this.$fetch()
|
||||
this.newTag = ''
|
||||
this.loading = false
|
||||
this.dialog = false
|
||||
})
|
||||
},
|
||||
async removeTag(tag) {
|
||||
const ret = await this.$root.$confirm('admin.delete_tag_confirm', { tag: tag.tag, n: tag.count })
|
||||
if (!ret) { return }
|
||||
try {
|
||||
await this.$axios.$delete(`/tag/${encodeURIComponent(tag.tag)}`)
|
||||
await this.$fetch()
|
||||
} catch (e) {
|
||||
const err = get(e, 'response.data.errors[0].message', e)
|
||||
this.$root.$message(this.$t(err), { color: 'error' })
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -16,6 +16,7 @@ v-container
|
|||
:label="$t('admin.hide_calendar')")
|
||||
|
||||
v-card-title {{$t('admin.default_images')}}
|
||||
v-card-subtitle(v-html="$t('admin.default_images_help')")
|
||||
v-card-text
|
||||
v-row
|
||||
v-col(cols='4')
|
||||
|
@ -109,12 +110,13 @@ import { mdiDeleteForever, mdiRestore, mdiPlus, mdiChevronUp } from '@mdi/js'
|
|||
export default {
|
||||
name: 'Theme',
|
||||
data () {
|
||||
const t = new Date().getMilliseconds()
|
||||
return {
|
||||
mdiDeleteForever, mdiRestore, mdiPlus, mdiChevronUp,
|
||||
valid: false,
|
||||
logoKey: 0,
|
||||
fallbackImageKey: 0,
|
||||
headerImageKey: 0,
|
||||
logoKey: t,
|
||||
fallbackImageKey: t,
|
||||
headerImageKey: t,
|
||||
link: { href: '', label: '' },
|
||||
linkModal: false
|
||||
// menu: [false, false, false, false]
|
||||
|
|
|
@ -117,9 +117,9 @@ const Ce = /* @__PURE__ */ new Set();
|
|||
function Ae(t, e) {
|
||||
t && t.i && (Ce.delete(t), t.i(e));
|
||||
}
|
||||
function Me(t, e, i, n) {
|
||||
const { fragment: l, on_mount: o, on_destroy: r, after_update: f } = t.$$;
|
||||
l && l.m(e, i), n || Q(() => {
|
||||
function Me(t, e, i, l) {
|
||||
const { fragment: n, on_mount: o, on_destroy: r, after_update: f } = t.$$;
|
||||
n && n.m(e, i), l || Q(() => {
|
||||
const c = o.map(W).filter(_e);
|
||||
r ? r.push(...c) : O(c), t.$$.on_mount = [];
|
||||
}), f.forEach(Q);
|
||||
|
@ -131,7 +131,7 @@ function Ne(t, e) {
|
|||
function Te(t, e) {
|
||||
t.$$.dirty[0] === -1 && (H.push(t), Se(), t.$$.dirty.fill(0)), t.$$.dirty[e / 31 | 0] |= 1 << e % 31;
|
||||
}
|
||||
function ye(t, e, i, n, l, o, r, f = [-1]) {
|
||||
function ye(t, e, i, l, n, o, r, f = [-1]) {
|
||||
const c = I;
|
||||
R(t);
|
||||
const s = t.$$ = {
|
||||
|
@ -139,7 +139,7 @@ function ye(t, e, i, n, l, o, r, f = [-1]) {
|
|||
ctx: null,
|
||||
props: o,
|
||||
update: L,
|
||||
not_equal: l,
|
||||
not_equal: n,
|
||||
bound: ee(),
|
||||
on_mount: [],
|
||||
on_destroy: [],
|
||||
|
@ -156,8 +156,8 @@ function ye(t, e, i, n, l, o, r, f = [-1]) {
|
|||
let k = !1;
|
||||
if (s.ctx = i ? i(t, e.props || {}, (m, _, ...C) => {
|
||||
const w = C.length ? C[0] : _;
|
||||
return s.ctx && l(s.ctx[m], s.ctx[m] = w) && (!s.skip_bound && s.bound[m] && s.bound[m](w), k && Te(t, m)), _;
|
||||
}) : [], s.update(), k = !0, O(s.before_update), s.fragment = n ? n(s.ctx) : !1, e.target) {
|
||||
return s.ctx && n(s.ctx[m], s.ctx[m] = w) && (!s.skip_bound && s.bound[m] && s.bound[m](w), k && Te(t, m)), _;
|
||||
}) : [], s.update(), k = !0, O(s.before_update), s.fragment = l ? l(s.ctx) : !1, e.target) {
|
||||
if (e.hydrate) {
|
||||
const m = $e(e.target);
|
||||
s.fragment && s.fragment.l(m), m.forEach(x);
|
||||
|
@ -190,8 +190,8 @@ typeof HTMLElement == "function" && (X = class extends HTMLElement {
|
|||
$on(t, e) {
|
||||
const i = this.$$.callbacks[t] || (this.$$.callbacks[t] = []);
|
||||
return i.push(e), () => {
|
||||
const n = i.indexOf(e);
|
||||
n !== -1 && i.splice(n, 1);
|
||||
const l = i.indexOf(e);
|
||||
l !== -1 && i.splice(l, 1);
|
||||
};
|
||||
}
|
||||
$set(t) {
|
||||
|
@ -211,13 +211,13 @@ function F(t, e = "long") {
|
|||
function V(t) {
|
||||
return t.multidate ? F(t.start_datetime) + " - " + F(t.end_datetime) : F(t.start_datetime) + (t.end_datetime ? "-" + F(t.end_datetime, "short") : "");
|
||||
}
|
||||
function ne(t, e, i) {
|
||||
const n = t.slice();
|
||||
return n[12] = e[i], n;
|
||||
}
|
||||
function le(t, e, i) {
|
||||
const n = t.slice();
|
||||
return n[15] = e[i], n;
|
||||
const l = t.slice();
|
||||
return l[12] = e[i], l;
|
||||
}
|
||||
function ne(t, e, i) {
|
||||
const l = t.slice();
|
||||
return l[15] = e[i], l;
|
||||
}
|
||||
function re(t) {
|
||||
let e;
|
||||
|
@ -225,11 +225,11 @@ function re(t) {
|
|||
c() {
|
||||
e = g("link"), a(e, "rel", "stylesheet"), a(e, "href", t[4]);
|
||||
},
|
||||
m(i, n) {
|
||||
v(i, e, n);
|
||||
m(i, l) {
|
||||
v(i, e, l);
|
||||
},
|
||||
p(i, n) {
|
||||
n & 16 && a(e, "href", i[4]);
|
||||
p(i, l) {
|
||||
l & 16 && a(e, "href", i[4]);
|
||||
},
|
||||
d(i) {
|
||||
i && x(e);
|
||||
|
@ -237,51 +237,51 @@ function re(t) {
|
|||
};
|
||||
}
|
||||
function oe(t) {
|
||||
let e, i, n = t[1] && t[3] === "true" && ae(t), l = t[5], o = [];
|
||||
for (let r = 0; r < l.length; r += 1)
|
||||
o[r] = ue(ne(t, l, r));
|
||||
let e, i, l = t[1] && t[3] === "true" && ae(t), n = t[5], o = [];
|
||||
for (let r = 0; r < n.length; r += 1)
|
||||
o[r] = ue(le(t, n, r));
|
||||
return {
|
||||
c() {
|
||||
e = g("div"), n && n.c(), i = z();
|
||||
e = g("div"), l && l.c(), i = z();
|
||||
for (let r = 0; r < o.length; r += 1)
|
||||
o[r].c();
|
||||
a(e, "id", "gancioEvents"), T(e, "dark", t[2] === "dark"), T(e, "light", t[2] === "light"), T(e, "sidebar", t[3] === "true"), T(e, "nosidebar", t[3] !== "true");
|
||||
},
|
||||
m(r, f) {
|
||||
v(r, e, f), n && n.m(e, null), u(e, i);
|
||||
v(r, e, f), l && l.m(e, null), u(e, i);
|
||||
for (let c = 0; c < o.length; c += 1)
|
||||
o[c].m(e, null);
|
||||
},
|
||||
p(r, f) {
|
||||
if (r[1] && r[3] === "true" ? n ? n.p(r, f) : (n = ae(r), n.c(), n.m(e, i)) : n && (n.d(1), n = null), f & 41) {
|
||||
l = r[5];
|
||||
if (r[1] && r[3] === "true" ? l ? l.p(r, f) : (l = ae(r), l.c(), l.m(e, i)) : l && (l.d(1), l = null), f & 41) {
|
||||
n = r[5];
|
||||
let c;
|
||||
for (c = 0; c < l.length; c += 1) {
|
||||
const s = ne(r, l, c);
|
||||
for (c = 0; c < n.length; c += 1) {
|
||||
const s = le(r, n, c);
|
||||
o[c] ? o[c].p(s, f) : (o[c] = ue(s), o[c].c(), o[c].m(e, null));
|
||||
}
|
||||
for (; c < o.length; c += 1)
|
||||
o[c].d(1);
|
||||
o.length = l.length;
|
||||
o.length = n.length;
|
||||
}
|
||||
f & 4 && T(e, "dark", r[2] === "dark"), f & 4 && T(e, "light", r[2] === "light"), f & 8 && T(e, "sidebar", r[3] === "true"), f & 8 && T(e, "nosidebar", r[3] !== "true");
|
||||
},
|
||||
d(r) {
|
||||
r && x(e), n && n.d(), pe(o, r);
|
||||
r && x(e), l && l.d(), pe(o, r);
|
||||
}
|
||||
};
|
||||
}
|
||||
function ae(t) {
|
||||
let e, i, n, l, o, r, f;
|
||||
let e, i, l, n, o, r, f;
|
||||
return {
|
||||
c() {
|
||||
e = g("a"), i = g("div"), n = g("div"), l = j(t[1]), o = z(), r = g("img"), a(n, "class", "title"), a(r, "id", "logo"), a(r, "alt", "logo"), G(r.src, f = t[0] + "/logo.png") || a(r, "src", f), a(i, "class", "content"), a(e, "href", t[0]), a(e, "target", "_blank"), a(e, "id", "header");
|
||||
e = g("a"), i = g("div"), l = g("div"), n = j(t[1]), o = z(), r = g("img"), a(l, "class", "title"), a(r, "id", "logo"), a(r, "alt", "logo"), G(r.src, f = t[0] + "/logo.png") || a(r, "src", f), a(i, "class", "content"), a(e, "href", t[0]), a(e, "target", "_blank"), a(e, "id", "header");
|
||||
},
|
||||
m(c, s) {
|
||||
v(c, e, s), u(e, i), u(i, n), u(n, l), u(i, o), u(i, r);
|
||||
v(c, e, s), u(e, i), u(i, l), u(l, n), u(i, o), u(i, r);
|
||||
},
|
||||
p(c, s) {
|
||||
s & 2 && N(l, c[1]), s & 1 && !G(r.src, f = c[0] + "/logo.png") && a(r, "src", f), s & 1 && a(e, "href", c[0]);
|
||||
s & 2 && N(n, c[1]), s & 1 && !G(r.src, f = c[0] + "/logo.png") && a(r, "src", f), s & 1 && a(e, "href", c[0]);
|
||||
},
|
||||
d(c) {
|
||||
c && x(e);
|
||||
|
@ -293,50 +293,50 @@ function se(t) {
|
|||
function i(o, r) {
|
||||
return o[12].media.length ? Ge : Le;
|
||||
}
|
||||
let n = i(t), l = n(t);
|
||||
let l = i(t), n = l(t);
|
||||
return {
|
||||
c() {
|
||||
e = g("div"), l.c(), a(e, "class", "img");
|
||||
e = g("div"), n.c(), a(e, "class", "img");
|
||||
},
|
||||
m(o, r) {
|
||||
v(o, e, r), l.m(e, null);
|
||||
v(o, e, r), n.m(e, null);
|
||||
},
|
||||
p(o, r) {
|
||||
n === (n = i(o)) && l ? l.p(o, r) : (l.d(1), l = n(o), l && (l.c(), l.m(e, null)));
|
||||
l === (l = i(o)) && n ? n.p(o, r) : (n.d(1), n = l(o), n && (n.c(), n.m(e, null)));
|
||||
},
|
||||
d(o) {
|
||||
o && x(e), l.d();
|
||||
o && x(e), n.d();
|
||||
}
|
||||
};
|
||||
}
|
||||
function Le(t) {
|
||||
let e, i, n;
|
||||
let e, i, l;
|
||||
return {
|
||||
c() {
|
||||
e = g("img"), a(e, "style", "aspect-ratio=1.7778;"), a(e, "alt", i = t[12].title), G(e.src, n = t[0] + "/fallbackimage.png") || a(e, "src", n), a(e, "loading", "lazy");
|
||||
e = g("img"), a(e, "style", "aspect-ratio=1.7778;"), a(e, "alt", i = t[12].title), G(e.src, l = t[0] + "/fallbackimage.png") || a(e, "src", l), a(e, "loading", "lazy");
|
||||
},
|
||||
m(l, o) {
|
||||
v(l, e, o);
|
||||
m(n, o) {
|
||||
v(n, e, o);
|
||||
},
|
||||
p(l, o) {
|
||||
o & 32 && i !== (i = l[12].title) && a(e, "alt", i), o & 1 && !G(e.src, n = l[0] + "/fallbackimage.png") && a(e, "src", n);
|
||||
p(n, o) {
|
||||
o & 32 && i !== (i = n[12].title) && a(e, "alt", i), o & 1 && !G(e.src, l = n[0] + "/fallbackimage.png") && a(e, "src", l);
|
||||
},
|
||||
d(l) {
|
||||
l && x(e);
|
||||
d(n) {
|
||||
n && x(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
function Ge(t) {
|
||||
let e, i, n, l;
|
||||
let e, i, l, n;
|
||||
return {
|
||||
c() {
|
||||
e = g("img"), a(e, "style", i = "object-position: " + de(t[12]) + "; aspect-ratio=1.7778;"), a(e, "alt", n = t[12].media[0].name), G(e.src, l = t[0] + "/media/thumb/" + t[12].media[0].url) || a(e, "src", l), a(e, "loading", "lazy");
|
||||
e = g("img"), a(e, "style", i = "object-position: " + de(t[12]) + "; aspect-ratio=1.7778;"), a(e, "alt", l = t[12].media[0].name), G(e.src, n = t[0] + "/media/thumb/" + t[12].media[0].url) || a(e, "src", n), a(e, "loading", "lazy");
|
||||
},
|
||||
m(o, r) {
|
||||
v(o, e, r);
|
||||
},
|
||||
p(o, r) {
|
||||
r & 32 && i !== (i = "object-position: " + de(o[12]) + "; aspect-ratio=1.7778;") && a(e, "style", i), r & 32 && n !== (n = o[12].media[0].name) && a(e, "alt", n), r & 33 && !G(e.src, l = o[0] + "/media/thumb/" + o[12].media[0].url) && a(e, "src", l);
|
||||
r & 32 && i !== (i = "object-position: " + de(o[12]) + "; aspect-ratio=1.7778;") && a(e, "style", i), r & 32 && l !== (l = o[12].media[0].name) && a(e, "alt", l), r & 33 && !G(e.src, n = o[0] + "/media/thumb/" + o[12].media[0].url) && a(e, "src", n);
|
||||
},
|
||||
d(o) {
|
||||
o && x(e);
|
||||
|
@ -344,50 +344,50 @@ function Ge(t) {
|
|||
};
|
||||
}
|
||||
function ce(t) {
|
||||
let e, i = t[12].tags, n = [];
|
||||
for (let l = 0; l < i.length; l += 1)
|
||||
n[l] = fe(le(t, i, l));
|
||||
let e, i = t[12].tags, l = [];
|
||||
for (let n = 0; n < i.length; n += 1)
|
||||
l[n] = fe(ne(t, i, n));
|
||||
return {
|
||||
c() {
|
||||
e = g("div");
|
||||
for (let l = 0; l < n.length; l += 1)
|
||||
n[l].c();
|
||||
for (let n = 0; n < l.length; n += 1)
|
||||
l[n].c();
|
||||
a(e, "class", "tags");
|
||||
},
|
||||
m(l, o) {
|
||||
v(l, e, o);
|
||||
for (let r = 0; r < n.length; r += 1)
|
||||
n[r].m(e, null);
|
||||
m(n, o) {
|
||||
v(n, e, o);
|
||||
for (let r = 0; r < l.length; r += 1)
|
||||
l[r].m(e, null);
|
||||
},
|
||||
p(l, o) {
|
||||
p(n, o) {
|
||||
if (o & 32) {
|
||||
i = l[12].tags;
|
||||
i = n[12].tags;
|
||||
let r;
|
||||
for (r = 0; r < i.length; r += 1) {
|
||||
const f = le(l, i, r);
|
||||
n[r] ? n[r].p(f, o) : (n[r] = fe(f), n[r].c(), n[r].m(e, null));
|
||||
const f = ne(n, i, r);
|
||||
l[r] ? l[r].p(f, o) : (l[r] = fe(f), l[r].c(), l[r].m(e, null));
|
||||
}
|
||||
for (; r < n.length; r += 1)
|
||||
n[r].d(1);
|
||||
n.length = i.length;
|
||||
for (; r < l.length; r += 1)
|
||||
l[r].d(1);
|
||||
l.length = i.length;
|
||||
}
|
||||
},
|
||||
d(l) {
|
||||
l && x(e), pe(n, l);
|
||||
d(n) {
|
||||
n && x(e), pe(l, n);
|
||||
}
|
||||
};
|
||||
}
|
||||
function fe(t) {
|
||||
let e, i, n = t[15] + "", l;
|
||||
let e, i, l = t[15] + "", n;
|
||||
return {
|
||||
c() {
|
||||
e = g("span"), i = j("#"), l = j(n), a(e, "class", "tag");
|
||||
e = g("span"), i = j("#"), n = j(l), a(e, "class", "tag");
|
||||
},
|
||||
m(o, r) {
|
||||
v(o, e, r), u(e, i), u(e, l);
|
||||
v(o, e, r), u(e, i), u(e, n);
|
||||
},
|
||||
p(o, r) {
|
||||
r & 32 && n !== (n = o[15] + "") && N(l, n);
|
||||
r & 32 && l !== (l = o[15] + "") && N(n, l);
|
||||
},
|
||||
d(o) {
|
||||
o && x(e);
|
||||
|
@ -395,16 +395,16 @@ function fe(t) {
|
|||
};
|
||||
}
|
||||
function ue(t) {
|
||||
let e, i, n, l, o = V(t[12]) + "", r, f, c, s = t[12].title + "", k, m, _, C, w = t[12].place.name + "", d, S, h, b = t[12].place.address + "", A, Y, Z, U, q, $ = t[3] !== "true" && se(t), E = t[12].tags.length && ce(t);
|
||||
let e, i, l, n, o = V(t[12]) + "", r, f, c, s = t[12].title + "", k, m, _, C, w = t[12].place.name + "", d, S, h, b = t[12].place.address + "", A, Y, Z, U, q, $ = t[3] !== "true" && se(t), E = t[12].tags.length && ce(t);
|
||||
return {
|
||||
c() {
|
||||
e = g("a"), $ && $.c(), i = z(), n = g("div"), l = g("div"), r = j(o), f = z(), c = g("div"), k = j(s), m = z(), _ = g("span"), C = j("@"), d = j(w), S = z(), h = g("span"), A = j(b), Y = z(), E && E.c(), Z = z(), a(l, "class", "subtitle"), a(c, "class", "title"), a(h, "class", "subtitle"), a(_, "class", "place"), a(n, "class", "content"), a(e, "href", U = t[0] + "/event/" + (t[12].slug || t[12].id)), a(e, "class", "event"), a(e, "title", q = t[12].title), a(e, "target", "_blank");
|
||||
e = g("a"), $ && $.c(), i = z(), l = g("div"), n = g("div"), r = j(o), f = z(), c = g("div"), k = j(s), m = z(), _ = g("span"), C = j("@"), d = j(w), S = z(), h = g("span"), A = j(b), Y = z(), E && E.c(), Z = z(), a(n, "class", "subtitle"), a(c, "class", "title"), a(h, "class", "subtitle"), a(_, "class", "place"), a(l, "class", "content"), a(e, "href", U = t[0] + "/event/" + (t[12].slug || t[12].id)), a(e, "class", "event"), a(e, "title", q = t[12].title), a(e, "target", "_blank");
|
||||
},
|
||||
m(p, M) {
|
||||
v(p, e, M), $ && $.m(e, null), u(e, i), u(e, n), u(n, l), u(l, r), u(n, f), u(n, c), u(c, k), u(n, m), u(n, _), u(_, C), u(_, d), u(_, S), u(_, h), u(h, A), u(n, Y), E && E.m(n, null), u(e, Z);
|
||||
v(p, e, M), $ && $.m(e, null), u(e, i), u(e, l), u(l, n), u(n, r), u(l, f), u(l, c), u(c, k), u(l, m), u(l, _), u(_, C), u(_, d), u(_, S), u(_, h), u(h, A), u(l, Y), E && E.m(l, null), u(e, Z);
|
||||
},
|
||||
p(p, M) {
|
||||
p[3] !== "true" ? $ ? $.p(p, M) : ($ = se(p), $.c(), $.m(e, i)) : $ && ($.d(1), $ = null), M & 32 && o !== (o = V(p[12]) + "") && N(r, o), M & 32 && s !== (s = p[12].title + "") && N(k, s), M & 32 && w !== (w = p[12].place.name + "") && N(d, w), M & 32 && b !== (b = p[12].place.address + "") && N(A, b), p[12].tags.length ? E ? E.p(p, M) : (E = ce(p), E.c(), E.m(n, null)) : E && (E.d(1), E = null), M & 33 && U !== (U = p[0] + "/event/" + (p[12].slug || p[12].id)) && a(e, "href", U), M & 32 && q !== (q = p[12].title) && a(e, "title", q);
|
||||
p[3] !== "true" ? $ ? $.p(p, M) : ($ = se(p), $.c(), $.m(e, i)) : $ && ($.d(1), $ = null), M & 32 && o !== (o = V(p[12]) + "") && N(r, o), M & 32 && s !== (s = p[12].title + "") && N(k, s), M & 32 && w !== (w = p[12].place.name + "") && N(d, w), M & 32 && b !== (b = p[12].place.address + "") && N(A, b), p[12].tags.length ? E ? E.p(p, M) : (E = ce(p), E.c(), E.m(l, null)) : E && (E.d(1), E = null), M & 33 && U !== (U = p[0] + "/event/" + (p[12].slug || p[12].id)) && a(e, "href", U), M & 32 && q !== (q = p[12].title) && a(e, "title", q);
|
||||
},
|
||||
d(p) {
|
||||
p && x(e), $ && $.d(), E && E.d();
|
||||
|
@ -412,21 +412,21 @@ function ue(t) {
|
|||
};
|
||||
}
|
||||
function He(t) {
|
||||
let e, i, n = t[4] && re(t), l = t[5].length && oe(t);
|
||||
let e, i, l = t[4] && re(t), n = t[5].length && oe(t);
|
||||
return {
|
||||
c() {
|
||||
n && n.c(), e = z(), l && l.c(), i = ve(), this.c = L;
|
||||
l && l.c(), e = z(), n && n.c(), i = ve(), this.c = L;
|
||||
},
|
||||
m(o, r) {
|
||||
n && n.m(o, r), v(o, e, r), l && l.m(o, r), v(o, i, r);
|
||||
l && l.m(o, r), v(o, e, r), n && n.m(o, r), v(o, i, r);
|
||||
},
|
||||
p(o, [r]) {
|
||||
o[4] ? n ? n.p(o, r) : (n = re(o), n.c(), n.m(e.parentNode, e)) : n && (n.d(1), n = null), o[5].length ? l ? l.p(o, r) : (l = oe(o), l.c(), l.m(i.parentNode, i)) : l && (l.d(1), l = null);
|
||||
o[4] ? l ? l.p(o, r) : (l = re(o), l.c(), l.m(e.parentNode, e)) : l && (l.d(1), l = null), o[5].length ? n ? n.p(o, r) : (n = oe(o), n.c(), n.m(i.parentNode, i)) : n && (n.d(1), n = null);
|
||||
},
|
||||
i: L,
|
||||
o: L,
|
||||
d(o) {
|
||||
n && n.d(o), o && x(e), l && l.d(o), o && x(i);
|
||||
l && l.d(o), o && x(e), n && n.d(o), o && x(i);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -438,12 +438,12 @@ function de(t) {
|
|||
return "center center";
|
||||
}
|
||||
function Re(t, e, i) {
|
||||
let { baseurl: n = "" } = e, { title: l = "" } = e, { maxlength: o = !1 } = e, { tags: r = "" } = e, { places: f = "" } = e, { theme: c = "light" } = e, { show_recurrent: s = !1 } = e, { sidebar: k = "true" } = e, { external_style: m = "" } = e, _ = !1, C = [];
|
||||
let { baseurl: l = "" } = e, { title: n = "" } = e, { maxlength: o = !1 } = e, { tags: r = "" } = e, { places: f = "" } = e, { theme: c = "light" } = e, { show_recurrent: s = !1 } = e, { sidebar: k = "true" } = e, { external_style: m = "" } = e, _ = !1, C = [];
|
||||
function w(d) {
|
||||
if (!_)
|
||||
return;
|
||||
const S = [];
|
||||
o && S.push(`max=${o}`), r && S.push(`tags=${r}`), f && S.push(`places=${f}`), S.push(`show_recurrent=${s ? "true" : "false"}`), fetch(`${n}/api/events?${S.join("&")}`).then((h) => h.json()).then((h) => {
|
||||
o && S.push(`max=${o}`), r && S.push(`tags=${r}`), f && S.push(`places=${f}`), S.push(`show_recurrent=${s ? "true" : "false"}`), fetch(`${l}/api/events?${S.join("&")}`).then((h) => h.json()).then((h) => {
|
||||
i(5, C = h);
|
||||
}).catch((h) => {
|
||||
console.error("Error loading Gancio API -> ", h);
|
||||
|
@ -452,12 +452,12 @@ function Re(t, e, i) {
|
|||
return we(() => {
|
||||
_ = !0, w();
|
||||
}), 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, l = d.baseurl), "title" in d && i(1, n = 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 & 975 && w();
|
||||
}, [
|
||||
n,
|
||||
l,
|
||||
n,
|
||||
c,
|
||||
k,
|
||||
m,
|
||||
|
@ -570,13 +570,13 @@ class Ie extends X {
|
|||
}
|
||||
customElements.define("gancio-events", Ie);
|
||||
function he(t) {
|
||||
let e, i, n, l, o = t[1].title + "", r, f, c, s = V(t[1]) + "", k, m, _, C, w = t[1].place.name + "", d, S, h = t[1].media.length && ge(t);
|
||||
let e, i, l, n, o = t[1].title + "", r, f, c, s = V(t[1]) + "", k, m, _, C, w = t[1].place.name + "", d, S, h = t[1].media.length && ge(t);
|
||||
return {
|
||||
c() {
|
||||
e = g("a"), h && h.c(), i = z(), n = g("div"), l = g("strong"), r = j(o), f = z(), c = g("div"), k = j(s), m = z(), _ = g("div"), C = j("@"), d = j(w), a(_, "class", "place"), a(n, "class", "container"), a(e, "href", S = t[0] + "/event/" + (t[1].slug || t[1].id)), a(e, "class", "card"), a(e, "target", "_blank");
|
||||
e = g("a"), h && h.c(), i = z(), l = g("div"), n = g("strong"), r = j(o), f = z(), c = g("div"), k = j(s), m = z(), _ = g("div"), C = j("@"), d = j(w), a(_, "class", "place"), a(l, "class", "container"), a(e, "href", S = t[0] + "/event/" + (t[1].slug || t[1].id)), a(e, "class", "card"), a(e, "target", "_blank");
|
||||
},
|
||||
m(b, A) {
|
||||
v(b, e, A), h && h.m(e, null), u(e, i), u(e, n), u(n, l), u(l, r), u(n, f), u(n, c), u(c, k), u(n, m), u(n, _), u(_, C), u(_, d);
|
||||
v(b, e, A), h && h.m(e, null), u(e, i), u(e, l), u(l, n), u(n, r), u(l, f), u(l, c), u(c, k), u(l, m), u(l, _), u(_, C), u(_, d);
|
||||
},
|
||||
p(b, A) {
|
||||
b[1].media.length ? h ? h.p(b, A) : (h = ge(b), h.c(), h.m(e, i)) : h && (h.d(1), h = null), A & 2 && o !== (o = b[1].title + "") && N(r, o), A & 2 && s !== (s = V(b[1]) + "") && N(k, s), A & 2 && w !== (w = b[1].place.name + "") && N(d, w), A & 3 && S !== (S = b[0] + "/event/" + (b[1].slug || b[1].id)) && a(e, "href", S);
|
||||
|
@ -587,16 +587,16 @@ function he(t) {
|
|||
};
|
||||
}
|
||||
function ge(t) {
|
||||
let e, i, n, l;
|
||||
let e, i, l, n;
|
||||
return {
|
||||
c() {
|
||||
e = g("img"), G(e.src, i = t[2](t[1])) || a(e, "src", i), a(e, "alt", n = t[1].media[0].name), a(e, "style", l = "object-position: " + me(t[1]) + "; aspect-ratio=1.7778;");
|
||||
e = g("img"), G(e.src, i = t[2](t[1])) || a(e, "src", i), a(e, "alt", l = t[1].media[0].name), a(e, "style", n = "object-position: " + me(t[1]) + "; aspect-ratio=1.7778;");
|
||||
},
|
||||
m(o, r) {
|
||||
v(o, e, r);
|
||||
},
|
||||
p(o, r) {
|
||||
r & 2 && !G(e.src, i = o[2](o[1])) && a(e, "src", i), r & 2 && n !== (n = o[1].media[0].name) && a(e, "alt", n), r & 2 && l !== (l = "object-position: " + me(o[1]) + "; aspect-ratio=1.7778;") && a(e, "style", l);
|
||||
r & 2 && !G(e.src, i = o[2](o[1])) && a(e, "src", i), r & 2 && l !== (l = o[1].media[0].name) && a(e, "alt", l), r & 2 && n !== (n = "object-position: " + me(o[1]) + "; aspect-ratio=1.7778;") && a(e, "style", n);
|
||||
},
|
||||
d(o) {
|
||||
o && x(e);
|
||||
|
@ -609,16 +609,16 @@ function Oe(t) {
|
|||
c() {
|
||||
i && i.c(), e = ve(), this.c = L;
|
||||
},
|
||||
m(n, l) {
|
||||
i && i.m(n, l), v(n, e, l);
|
||||
m(l, n) {
|
||||
i && i.m(l, n), v(l, e, n);
|
||||
},
|
||||
p(n, [l]) {
|
||||
n[1] ? i ? i.p(n, l) : (i = he(n), i.c(), i.m(e.parentNode, e)) : i && (i.d(1), i = null);
|
||||
p(l, [n]) {
|
||||
l[1] ? i ? i.p(l, n) : (i = he(l), i.c(), i.m(e.parentNode, e)) : i && (i.d(1), i = null);
|
||||
},
|
||||
i: L,
|
||||
o: L,
|
||||
d(n) {
|
||||
i && i.d(n), n && x(e);
|
||||
d(l) {
|
||||
i && i.d(l), l && x(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -630,21 +630,21 @@ function me(t) {
|
|||
return "center center";
|
||||
}
|
||||
function Ue(t, e, i) {
|
||||
let { baseurl: n = "https://demo.gancio.org" } = e, { id: l } = e, o = !1, r;
|
||||
let { baseurl: l = "https://demo.gancio.org" } = e, { id: n } = e, o = !1, r;
|
||||
function f(s, k) {
|
||||
o && fetch(`${k}/api/event/${s}`).then((m) => m.json()).then((m) => i(1, r = m));
|
||||
o && fetch(`${k}/api/event/detail/${s}`).then((m) => m.json()).then((m) => i(1, r = m));
|
||||
}
|
||||
we(() => {
|
||||
o = !0, f(l, n);
|
||||
o = !0, f(n, l);
|
||||
});
|
||||
function c(s) {
|
||||
return `${n}/media/thumb/${s.media[0].url}`;
|
||||
return `${l}/media/thumb/${s.media[0].url}`;
|
||||
}
|
||||
return t.$$set = (s) => {
|
||||
"baseurl" in s && i(0, n = s.baseurl), "id" in s && i(3, l = s.id);
|
||||
"baseurl" in s && i(0, l = s.baseurl), "id" in s && i(3, n = s.id);
|
||||
}, t.$$.update = () => {
|
||||
t.$$.dirty & 9 && f(l, n);
|
||||
}, [n, r, c, l];
|
||||
t.$$.dirty & 9 && f(n, l);
|
||||
}, [l, r, c, n];
|
||||
}
|
||||
class qe extends X {
|
||||
constructor(e) {
|
||||
|
|
|
@ -8,6 +8,47 @@ nav_order: 10
|
|||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
### 1.6.2 - 12 jan '23
|
||||
- add swipe gesture to move to next/prev event
|
||||
- fix refresh collections, fix #219
|
||||
- add russian translation (thanks @drunkod)
|
||||
- refactor search / filter / selection fix #225, 227, #224
|
||||
- models initialization refactored, better dev experience, fix backend HMR
|
||||
|
||||
### 1.6.1 - 15 dec '22
|
||||
- allow edit tags in admin panel, fix #170
|
||||
- fix header / fallback image upload, fix #222
|
||||
- fix WPGancio MU
|
||||
- fix recurrent events label
|
||||
- update translations (de, es, eu, gl)
|
||||
|
||||
### 1.6.0 - 11 dec '22
|
||||
- new plugin system - fix #177
|
||||
- new "publish on telegram" plugin: (thanks @fadelkon)
|
||||
- people can now choose the language displayed - fix #171
|
||||
- admin could choose a custom fallback image - fix #195
|
||||
- it is now possible NOT to enter the end time of an event - fix #188
|
||||
- live search
|
||||
- improve event import
|
||||
- add Apple touch icon - fix #200
|
||||
- add nominatim / openstreetmap search feature (thanks @sedum)
|
||||
- new hide calendar option
|
||||
- new hide thumbs from homepage option
|
||||
- linkable admin tab
|
||||
- friendly instances label is now customizable (thanks @sedum)
|
||||
- i18n refactoring
|
||||
- Wordpress plugin now supports MU installation
|
||||
- new chinese translation
|
||||
- new portuguese translation
|
||||
- improved navbar layout
|
||||
- improved event layout
|
||||
- complete oauth2 refactoring
|
||||
- fix ics unique uuid
|
||||
- fix place "[Object]" issue - #194
|
||||
- fix random restart while downloading random media
|
||||
- fix mobile dialog layout
|
||||
- urlencode place and tag urls
|
||||
|
||||
### 1.5.6 - 22 set '22
|
||||
- update linkifyjs, sequelizem, nuxt deps
|
||||
- improve homepage loading time
|
||||
|
|
|
@ -11,5 +11,5 @@ nav_order: 9
|
|||
- :elephant: Mastodon ⇒ [@gancio@mastodon.cisti.org](https://mastodon.cisti.org/@gancio)
|
||||
- :email: Email ⇒ [info@cisti.org](mailto:info@cisti.org)
|
||||
- IRC ⇒ #gancio @ irc.autistici.org (sometimes...)
|
||||
- Issues ⇒ https://framagit.org/les/gancio/-/issues
|
||||
- Issues ⇒ [https://framagit.org/les/gancio/-/issues](https://framagit.org/les/gancio/-/issues)
|
||||
|
||||
|
|
7
docs/docker/nominatim/.env.example
Normal file
7
docs/docker/nominatim/.env.example
Normal file
|
@ -0,0 +1,7 @@
|
|||
|
||||
NOMINATIM_PASSWORD=CeMA4M1kiDo0k
|
||||
|
||||
# Choose PBF_PATH to import a local file
|
||||
PBF_PATH=/nominatim/data/default.osm.pbf
|
||||
# PBF_URL= https://download.geofabrik.de/europe/italy/nord-est-latest.osm.pbf
|
||||
# REPLICATION_URL= https://download.geofabrik.de/europe/italy/nord-est-updates/
|
19
docs/docker/nominatim/docker-compose.yml
Normal file
19
docs/docker/nominatim/docker-compose.yml
Normal file
|
@ -0,0 +1,19 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
nominatim:
|
||||
container_name: nominatim
|
||||
image: mediagis/nominatim:4.2
|
||||
restart: always
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
# see https://github.com/mediagis/nominatim-docker/tree/master/4.2#configuration for more options
|
||||
PBF_PATH: "${PBF_PATH}"
|
||||
PBF_URL: "${PBF_URL}"
|
||||
REPLICATION_URL: "${REPLICATION_URL}"
|
||||
NOMINATIM_PASSWORD: ${NOMINATIM_PASSWORD}
|
||||
volumes:
|
||||
- ./nominatim-data:/var/lib/postgresql/14/main
|
||||
- ./nominatim/data/"${PBF_PATH}":/nominatim/data/"${PBF_PATH}"
|
||||
shm_size: 1gb
|
|
@ -16,7 +16,7 @@ The configuration file shoud be a `.json` or a `.js` file and could be specified
|
|||
1. TOC
|
||||
{:toc}
|
||||
|
||||
- ### Server
|
||||
### Server
|
||||
This probably support unix socket too
|
||||
|
||||
```json
|
||||
|
@ -26,7 +26,7 @@ This probably support unix socket too
|
|||
}
|
||||
```
|
||||
|
||||
- ### Database
|
||||
### Database
|
||||
DB configuration, look [here](https://sequelize.org/v6/class/src/sequelize.js~Sequelize.html#instance-constructor-constructor) for options.
|
||||
```json
|
||||
"db": {
|
||||
|
@ -34,11 +34,15 @@ DB configuration, look [here](https://sequelize.org/v6/class/src/sequelize.js~Se
|
|||
"storage": "/tmp/db.sqlite"
|
||||
}
|
||||
```
|
||||
- ### Upload path
|
||||
### Upload path
|
||||
Where to save images
|
||||
`"upload_path": "./uploads"`
|
||||
|
||||
- ### User locale
|
||||
### Plugins path
|
||||
Where to search for [plugins](/usage/plugins)
|
||||
`"plugins_path": "./plugins"`
|
||||
|
||||
### User locale
|
||||
Probably you want to modify some text for your specific community, that's
|
||||
why we thought the `user_locale` configuration: you can specify your version of
|
||||
each string of **gancio** making a directory with your locales inside.
|
||||
|
|
139
docs/install/nominatim.md
Normal file
139
docs/install/nominatim.md
Normal file
|
@ -0,0 +1,139 @@
|
|||
---
|
||||
layout: default
|
||||
title: Nominatim
|
||||
permalink: /install/nominatim
|
||||
parent: Install
|
||||
nav_order: 7
|
||||
---
|
||||
|
||||
## Nominatim installation
|
||||
{: .no_toc }
|
||||
|
||||
1. TOC
|
||||
{:toc}
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
For testing purposes you could skip the nominatim installation and use one of this geocoding providers that run a server for free:
|
||||
|
||||
- [https://photon.komoot.io/](https://photon.komoot.io/) [Terms of service](https://photon.komoot.io/)
|
||||
- [https://nominatim.openstreetmap.org/](https://nominatim.openstreetmap.org/) [Terms of service](https://operations.osmfoundation.org/policies/nominatim/)
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
From [https://nominatim.org/release-docs/latest/admin/Installation/](https://nominatim.org/release-docs/latest/admin/Installation/)
|
||||
|
||||
"A minimum of 2GB of RAM is required or installation will fail. For a full planet import 128GB of RAM or more are strongly recommended. Do not report out of memory problems if you have less than 64GB RAM."
|
||||
|
||||
### Planet mirrors
|
||||
There is a list of planet mirror at [https://wiki.openstreetmap.org/wiki/Planet.osm#Planet.osm_mirrors](https://wiki.openstreetmap.org/wiki/Planet.osm#Planet.osm_mirrors)
|
||||
There you can also find `Country and area extracts`, divided by `Worldwide extract sources` and `Regional extract sources`
|
||||
|
||||
### Download an extract
|
||||
For Nominatim to work, you will needs to import files in [PBF Format](https://wiki.openstreetmap.org/wiki/PBF_Format) in the PostGis database. Those files have extension `*.osm.pbf`.
|
||||
|
||||
Some of these mirrors provide also incremental updates via [OsmChange](https://wiki.openstreetmap.org/wiki/OsmChange), for example:
|
||||
- Provides updates but with a lower detail
|
||||
[https://download.geofabrik.de/europe/italy/nord-ovest-updates/nord-ovest-latest.osm.pbf](http://download.geofabrik.de/europe/italy/nord-ovest-latest.osm.pbf)
|
||||
[https://download.geofabrik.de/europe/italy/nord-ovest-updates/](https://download.geofabrik.de/europe/italy/nord-ovest-updates/)
|
||||
- Does not provide updates but as higher level of detail
|
||||
[https://osmit-estratti-test.wmcloud.org/dati/poly/province/pbf/015_Milano_poly.osm.pbf](https://osmit-estratti-test.wmcloud.org/dati/poly/province/pbf/015_Milano_poly.osm.pbf)
|
||||
|
||||
Needs to host multiple areas? Checkout [Osmium](https://osmcode.org/osmium-tool/manual.html), to merge multiple PBF files into one.
|
||||
|
||||
---
|
||||
|
||||
## Install on Debian
|
||||
There is a [detailed documentaion](https://nominatim.org/release-docs/latest/appendix/Install-on-Ubuntu-22/) for installing nominatim on `Ubuntu 22` that should be valid also to install on `Debian`.
|
||||
|
||||
### Setup
|
||||
[https://nominatim.org/release-docs/latest/appendix/Install-on-Ubuntu-22/#installing-the-required-software](https://nominatim.org/release-docs/latest/appendix/Install-on-Ubuntu-22/#installing-the-required-software)
|
||||
|
||||
### Building and Configuration
|
||||
|
||||
Get the source code from Github and change into the source directory
|
||||
```
|
||||
cd $USERHOME
|
||||
wget https://nominatim.org/release/Nominatim-4.2.0.tar.bz2
|
||||
tar xf Nominatim-4.2.0.tar.bz2
|
||||
```
|
||||
|
||||
The code must be built in a separate directory. Create this directory, then configure and build Nominatim in there:
|
||||
```
|
||||
mkdir $USERHOME/build
|
||||
cd $USERHOME/build
|
||||
cmake $USERHOME/Nominatim-4.2.0
|
||||
make
|
||||
```
|
||||
|
||||
### Setting up the webserver
|
||||
[https://nominatim.org/release-docs/latest/appendix/Install-on-Ubuntu-22/#setting-up-a-webserver](https://nominatim.org/release-docs/latest/appendix/Install-on-Ubuntu-22/#setting-up-a-webserver)
|
||||
|
||||
### Import the database
|
||||
[https://nominatim.org/release-docs/latest/admin/Import/](https://nominatim.org/release-docs/latest/admin/Import/)
|
||||
|
||||
---
|
||||
|
||||
## Install using docker
|
||||
|
||||
### Setup
|
||||
Make sure to have [Docker Engine](https://docs.docker.com/engine/install/),
|
||||
[Docker Compose](https://docs.docker.com/compose/install/) and [git](https://git-scm.com/downloads) installed:
|
||||
```bash
|
||||
sudo apt install docker docker-compose git
|
||||
```
|
||||
|
||||
### Clone the project
|
||||
From [https://github.com/mediagis/nominatim-docker](https://github.com/mediagis/nominatim-docker)
|
||||
|
||||
- Clone the project from sources
|
||||
```bash
|
||||
git clone git@github.com:mediagis/nominatim-docker.git
|
||||
# cd nominatim-docker/<version>
|
||||
cd nominatim-docker/4.2/contrib # released Nov 29, 2022
|
||||
docker-compose pull
|
||||
```
|
||||
|
||||
- Or, use the template at `docs/docker/nominatim`
|
||||
```
|
||||
cd /opt/gancio/docs/docker/nominatim
|
||||
docker-compose pull
|
||||
```
|
||||
|
||||
### Import the database
|
||||
See [Requirements](#requirements) about downloading the `.osm.pbf` files
|
||||
```bash
|
||||
cd docs/docker/nominatim/
|
||||
wget https://download.geofabrik.de/europe/italy/nord-ovest-latest.osm.pbf \
|
||||
./nominatim/data/default.osm.pbf
|
||||
```
|
||||
|
||||
### Configure the environment file
|
||||
```
|
||||
cd docs/docker/nominatim/
|
||||
cp .env.example .env
|
||||
```
|
||||
Create a random password for nominatim a add it to .env file
|
||||
```bash
|
||||
NOMINATIM_PASSWORD=random_password;
|
||||
NOMINATIM_PASSWORD=$(echo $NOMINATIM_PASSWORD | openssl passwd --stdin);
|
||||
echo $NOMINATIM_PASSWORD;
|
||||
sed -i -e 's/\(NOMINATIM_PASSWORD=\)\(.*\)/\1'$NOMINATIM_PASSWORD'/g' .env
|
||||
```
|
||||
|
||||
### Start nominatim-docker
|
||||
|
||||
Start your container:
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
Checkout the logs to see when data are imported to the database:
|
||||
```bash
|
||||
docker-compose logs -f
|
||||
```
|
||||
|
||||
Try out the search:
|
||||
|
||||
[http://0.0.0.0:8080/search?q=building](http://0.0.0.0:8080/search?q=building)
|
|
@ -11,7 +11,11 @@ nav_order: 7
|
|||
- [sapratza.in](https://sapratza.in/) (Sardinia, Italy)
|
||||
- [ponente.rocks](https://ponente.rocks) (Ponente Ligure, Italy)
|
||||
- [puntello.org](https://puntello.org) (Milan, Italy)
|
||||
- [lasitua.org](https://lasitua.org) (Brescia, Italy)
|
||||
- [balotta.org](https://balotta.org) (Bologna, Italy)
|
||||
- [gancio.daghe.xyz](https://gancio.daghe.xyz/) (Trento, Italy)
|
||||
- [bcn.convoca.la](https://bcn.convoca.la/) (Barcelona)
|
||||
- [mad.convoca.la](https://bcn.convoca.la/) (Madrid)
|
||||
- [bonn.jetzt](https://bonn.jetzt/) (Digital-Events aus Bonn, Rhein-Sieg und der Region)
|
||||
- [quest.livellosegreto.it](https://quest.livellosegreto.it/)
|
||||
- [ezkerraldea.euskaragendak.eus](https://ezkerraldea.euskaragendak.eus/)
|
||||
|
@ -20,6 +24,7 @@ nav_order: 7
|
|||
- [lubakiagenda.net](https://lubakiagenda.net/)
|
||||
- [eventos.coletivos.org](https://eventos.coletivos.org/)
|
||||
- [calendario.extinctionrebellion.es](https://calendario.extinctionrebellion.es/)
|
||||
- [cloudspeakers.org](https://cloudspeakers.org/) (Utrecht?)
|
||||
|
||||
|
||||
<small>Do you want your instance to appear here? [Write us]({% link contact.md %}).</small>
|
||||
|
|
62
docs/usage/plugins.md
Normal file
62
docs/usage/plugins.md
Normal file
|
@ -0,0 +1,62 @@
|
|||
---
|
||||
layout: default
|
||||
title: Plugins
|
||||
permalink: /usage/plugins
|
||||
nav_order: 2
|
||||
parent: Usage
|
||||
has_toc: true
|
||||
---
|
||||
|
||||
# Plugins
|
||||
{: .no_toc }
|
||||
|
||||
This page is a guide to install plugins, if you want to develop one instead look [here](/dev/plugins)
|
||||
|
||||
1. TOC
|
||||
{:toc}
|
||||
|
||||
|
||||
|
||||
## Install
|
||||
|
||||
To install a plugin you have to:
|
||||
|
||||
1. **download the .zip archive (look for the url on the plugin list below)**
|
||||
```
|
||||
wget https://framagit.org/les/gancio-plugin-telegram-bridge/-/archive/v0.2.0/gancio-plugin-telegram-bridge-v0.2.0.zip
|
||||
```
|
||||
|
||||
2. **unpack it in the `./plugins` directory.**
|
||||
```
|
||||
cd plugins
|
||||
unzip https://framagit.org/les/gancio-plugin-telegram-bridge/-/archive/v0.2.0/gancio-plugin-telegram-bridge-v0.2.0.zip
|
||||
```
|
||||
|
||||
|
||||
3. **install the dependencies with `yarn`**
|
||||
```
|
||||
cd plugins/gancio-plugin-telegram-bridge
|
||||
yarn
|
||||
```
|
||||
|
||||
4. **restart gancio**
|
||||
__with debian__
|
||||
```
|
||||
sudo sytemctl restart gancio
|
||||
```
|
||||
__with docker__
|
||||
```
|
||||
docker-compose restart
|
||||
```
|
||||
|
||||
# List of plugins
|
||||
|
||||
## __Telegram__
|
||||
|
||||
This plugin republishes events to Telegram channels or groups.
|
||||
The goal is to spread the info of our networks to the capitalist cyberspace, and pull otherwise isolated people to our radical and free part of the internet.
|
||||
|
||||
- **Website**: [https://framagit.org/bcn.convocala/gancio-plugin-telegram-bridge](https://framagit.org/bcn.convocala/gancio-plugin-telegram-bridge)
|
||||
- **Download**: [gancio-plugin-telegram-bridge-v0.2.0.zip](https://framagit.org/les/gancio-plugin-telegram-bridge/-/archive/v0.2.0/gancio-plugin-telegram-bridge-v0.2.0.zip)
|
||||
- **Release**: v0.2.0 / 10 Dec '22
|
||||
|
|
@ -4,7 +4,9 @@
|
|||
<v-main>
|
||||
<Snackbar/>
|
||||
<Confirm/>
|
||||
<nuxt :keep-alive='$route.name === "index"'/>
|
||||
<v-fade-transition hide-on-leave>
|
||||
<nuxt />
|
||||
</v-fade-transition>
|
||||
</v-main>
|
||||
<Footer/>
|
||||
|
||||
|
|
|
@ -98,7 +98,11 @@
|
|||
"home": "Inici",
|
||||
"help_translate": "Ajuda amb la traducció",
|
||||
"calendar": "Calendari",
|
||||
"about": "Sobre aquesta agenda"
|
||||
"about": "Sobre aquesta agenda",
|
||||
"content": "Contingut",
|
||||
"admin_actions": "Accions d'administració",
|
||||
"recurring_event_actions": "Accions d'activitats recorrents",
|
||||
"tag": "Etiqueta"
|
||||
},
|
||||
"login": {
|
||||
"description": "Amb la sessió iniciada pots afegir activitats noves.",
|
||||
|
@ -175,7 +179,7 @@
|
|||
"saved": "S'ha desat l'activitat",
|
||||
"import_description": "Pots importar activitats des d'altres instàncies o plataformes que facin servir formats estàndards (ics o h-event)",
|
||||
"remove_media_confirmation": "Confirmeu l'eliminació de la imatge?",
|
||||
"download_flyer": "Baixa el flyer",
|
||||
"download_flyer": "Baixa el cartell",
|
||||
"alt_text_description": "Descripció per a persones amb discapacitat visual",
|
||||
"choose_focal_point": "Tria el punt focal",
|
||||
"address_description": "Quina és l'adreça completa del lloc?",
|
||||
|
@ -270,7 +274,35 @@
|
|||
"fallback_image": "Cartell per defecte",
|
||||
"config_plugin": "Configura el complement",
|
||||
"header_image": "Imatge de capçalera",
|
||||
"default_images": "Cartell per defecte"
|
||||
"default_images": "Cartell per defecte",
|
||||
"blocked": "Bloquejat/da",
|
||||
"domain": "Domini",
|
||||
"known_users": "Usuàries conegudes",
|
||||
"created_at": "Creada",
|
||||
"default_images_help": "<a href='/admin?tab=theme'>Actualitza la pàgina</a> per veure els canvis.",
|
||||
"geocoding_countrycodes": "Codis d'estats",
|
||||
"admin_email_help": "L'adreça que es posa de remitent per enviar correus. També és l'adreça a la qual s'envien els correus d'administració",
|
||||
"delete_collection_confirm": "Segur que vols eliminar la coŀlecció <u>{collection}</u>?",
|
||||
"geocoding_provider_type_help": "Per defecte es resolen les adreces amb Nominatim",
|
||||
"geocoding_provider": "Proveïdora de geocodificació",
|
||||
"geocoding_provider_type": "Programari de geocodificació",
|
||||
"geocoding_provider_help": "Per defecte es geocodifiquen les adreces amb Nominatim",
|
||||
"geocoding_countrycodes_help": "Permet aplicar un filtre per cercar només dins de les fronteres de l'estat",
|
||||
"geocoding_test_button": "Prova la geocodificació",
|
||||
"geocoding_test_success": "La geocodificació de {service_name} funciona",
|
||||
"geocoding_test_error": "El servei de geocodificació de {service_name} no està funcionant",
|
||||
"tilelayer_provider": "Proveïdora de tesseŀles del mapa base",
|
||||
"tilelayer_provider_help": "Per defecte les tesseŀles del mapa es baixen del servidor central d'OpenStreetMap",
|
||||
"tilelayer_test_button": "Prova el mapa base",
|
||||
"tilelayer_test_success": "El servei de tesseŀles de {service_name} està funcionant",
|
||||
"tilelayer_test_error": "El servei de tesseŀles de {service_name} no està funcionant",
|
||||
"geolocation": "Geolocalització",
|
||||
"edit_tag_help": "Pots canviar l'etiqueta reanomenant-la o bé fusionant-la amb una d'existent. S'actualitzaran les {n} activitats que la fan servir.",
|
||||
"allow_multidate_event": "Permet activitats de diversos dies",
|
||||
"delete_tag_confirm": "Segur que vols eliminar l'etiqueta \"{tag}\"? S'esborrarà del sistema i de {n} activitats.",
|
||||
"edit_tag": "Canvia l'etiqueta",
|
||||
"tilelayer_provider_attribution": "Atribució",
|
||||
"geolocation_description": "<b>1. Defineix una proveïdora de geocodificació</b>.<br>Actualment, de totes les llistades en <a href=\"https://wiki.openstreetmap.org/wiki/Nominatim#Alternatives_.2F_Third-party_providers\">la wiki d'OpenStreetMap</a>, són compatibles amb Gancio <a href=\"https://github.com/osm-search/Nominatim\">Nominatim</a> i <a href=\"https://github.com/komoot/photon\">Photon</a>.<br>Pots fer servir les instaŀlacions d'aquestes proveïdores copiant l'adreça de la instaŀlació al camp de 'Proveïdora de geolocodificació'<ul><li>https://nominatim.openstreetmap.org/search (<a href=\"https://operations.osmfoundation.org/policies/nominatim/\">Condicions del servei</a>)</li><li>https://photon.komoot.io/api/ (<a href=\"https://photon.komoot.io/\">Condicions del servei</a>)</li></ul><br><b>2. Defineix una proveïdora per mapes base.</b><br>Pots trobar-ne una llista aquí: <a href=\"https://leaflet-extras.github.io/leaflet-providers/preview/\">https://leaflet-extras.github.io/leaflet-providers/preview/</a>"
|
||||
},
|
||||
"auth": {
|
||||
"not_confirmed": "Encara no s'ha confirmat…",
|
||||
|
|
|
@ -97,7 +97,10 @@
|
|||
"home": "Startseite",
|
||||
"about": "Über",
|
||||
"plugins": "Plugins",
|
||||
"help_translate": "Hilf beim Übersetzen mit"
|
||||
"help_translate": "Hilf beim Übersetzen mit",
|
||||
"content": "Inhalt",
|
||||
"admin_actions": "Aktionen der Administrierenden",
|
||||
"recurring_event_actions": "Einstellungen für regelmäßige Veranstaltungen"
|
||||
},
|
||||
"admin": {
|
||||
"delete_footer_link_confirm": "Möchtest du diesen Link löschen?",
|
||||
|
@ -129,7 +132,7 @@
|
|||
"wrong_domain_warning": "Die \"baseurl\" die in config.json konfiguriert ist <b>({baseurl})</b> unterscheidet sich von derjenigen <b>({url})</b> die du besuchst",
|
||||
"instance_place_help": "Diese Textzeile wird im Menü der anderen befreundeten Instanzen angezeigt",
|
||||
"place_description": "Falls ein Ort falsch ist oder sich die Adresse ändert, kannst du ihn ändern.<br/>Bitte beachte, dass alle Veranstaltungen, die mit diesem Ort verbunden sind, die Adresse ändern (auch zurückliegende).",
|
||||
"enable_admin_user_confirm": "Achte darauf, dass du der nutzenden Person {user} Admin-Rechte hinzufügst?",
|
||||
"enable_admin_user_confirm": "Bist du dir sicher, dass du der nutzenden Person {user} Admin-Rechte gewährst?",
|
||||
"trusted_instances_help": "Befreundete Instanzen werden in der Navigationsleiste oben auf der Seite angezeigt",
|
||||
"trusted_instances_label": "Navigationsbezeichnung zu Friend-Instanzen",
|
||||
"trusted_instances_label_default": "Freundliche Instanzen",
|
||||
|
@ -184,7 +187,32 @@
|
|||
"default_images": "Standard Bilder",
|
||||
"config_plugin": "Plugin Konfiguration",
|
||||
"hide_thumbs": "Vorschaubilder ausblenden",
|
||||
"hide_calendar": "Kalender verstecken"
|
||||
"hide_calendar": "Kalender verstecken",
|
||||
"admin_email_help": "Die Adresse, die wir als Absender für den Versand von E-Mails verwenden. Sie ist auch die Adresse, an die deine E-Mails an die Administrator:innen geschickt werden.",
|
||||
"blocked": "Geblockt",
|
||||
"domain": "Domain",
|
||||
"known_users": "Bekannte Nutzer:innen",
|
||||
"created_at": "Erstellt am",
|
||||
"geocoding_provider_type": "Software für Georeferenzierung",
|
||||
"geocoding_provider_type_help": "Die Standard-Software ist Nominatim",
|
||||
"geocoding_provider": "Anbieter von Geocodierung",
|
||||
"geocoding_provider_help": "Der Standard-Anbieter ist Nominatim",
|
||||
"geocoding_countrycodes": "Gebietskennziffern",
|
||||
"geocoding_countrycodes_help": "Ermöglicht die Einrichtung eines Filters für die Suche auf der Grundlage von Ländercodes",
|
||||
"geocoding_test_button": "Geokodierung testen",
|
||||
"geocoding_test_success": "Der Geokodierdienst unter {service_name} funktioniert",
|
||||
"geocoding_test_error": "Der Dienst ist unter der angegebenen Adresse nicht zu erreichen: {service_name}",
|
||||
"tilelayer_provider": "Kachel-LayerAnbieter",
|
||||
"tilelayer_provider_help": "Der Standard-Anbieter ist OpenStreetMap",
|
||||
"tilelayer_provider_attribution": "Namensnennung",
|
||||
"tilelayer_test_button": "Kachel-Layer testen",
|
||||
"tilelayer_test_success": "Der Kachel-Layer-Dienst unter {service_name} funktioniert",
|
||||
"tilelayer_test_error": "Der Dienst ist unter der angegebenen Adresse nicht zu erreichen: {service_name}",
|
||||
"geolocation": "Geolokation",
|
||||
"allow_multidate_event": "Lasse mehrtägige Veranstaltungen zu",
|
||||
"admin_email": "E-Mail von der administrierenden Person",
|
||||
"geolocation_description": "<b>1. Bestimme einen Anbieter für einen Geokodierdienst</b>.<br>Derzeit gibt es unter den im <a href=\"https://wiki.openstreetmap.org/wiki/Nominatim#Alternatives_.2F_Third-party_providers\">Wiki von OpenStreetMap</a>, Anbietern Unterstützung für die Software <a href=\"https://github.com/osm-search/Nominatim\">Nominatim</a> und <a href=\"https://github.com/komoot/photon\">Photon</a>.<br>Du kannst eine der entsprechenden offiziellen Demos verwenden, indem du den Link in das Feld \"Geocoding provider\" kopierst:<ul><li>https://nominatim.openstreetmap.org/search (<a href=\"https://operations.osmfoundation.org/policies/nominatim/\">Terms of Service</a>)</li><li>https://photon.komoot.io/api/ (<a href=\"https://photon.komoot.io/\">Terms of Service</a>)</li></ul><br><b>2. Definiere einen Anbieter für Kartenebenen.</b><br>Eine Liste von ihnen findest du hier: <a href=\"https://leaflet-extras.github.io/leaflet-providers/preview/\">https://leaflet-extras.github.io/leaflet-providers/preview/</a>",
|
||||
"default_images_help": "Du musst <a href='/admin?tab=theme'>die Seite neu laden</a>, um die Änderungen sehen zu können."
|
||||
},
|
||||
"settings": {
|
||||
"update_confirm": "Willst du deine Änderung speichern?",
|
||||
|
|
29
locales/email/ru.json
Normal file
29
locales/email/ru.json
Normal file
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"recover": {
|
||||
"subject": "Восстановление пароля",
|
||||
"content": "Здравствуйте, вы запросили восстановление пароля на {{config.title}}. <a href='{{config.baseurl}}/recover/{{user.recover_code}}'>Нажмите здесь</a> для подтверждения."
|
||||
},
|
||||
"confirm": {
|
||||
"subject": "Теперь вы можете начать публиковать события",
|
||||
"content": "Здравствуйте, ваша учетная запись <a href='{{config.baseurl}}'>{{config.title}}</a> была подтверждена. Пишите нам по адресу {{config.admin_email}} для получения любой информации."
|
||||
},
|
||||
"admin_register": {
|
||||
"subject": "Новая регистрация",
|
||||
"content": "{{user.email}} запросил регистрацию на {{config.title}}: <br/><pre>{{user.description}}</pre><br/> Подтвердите это <a href='{{config.baseurl}}/admin'>здесь</a>."
|
||||
},
|
||||
"user_confirm": {
|
||||
"subject": "Теперь вы можете начать публиковать события",
|
||||
"content": "Здравствуйте, ваша учетная запись <a href='{{config.baseurl}}'>{{config.title}}</a> была создана. <a href='{{config.baseurl}}/user_confirm/{{user.recover_code}}'>Подтвердите это и измените пароль.</a>."
|
||||
},
|
||||
"register": {
|
||||
"content": "Мы получили запрос на регистрацию. Мы подтвердим его как можно скорее.",
|
||||
"subject": "Запрос на регистрацию получен"
|
||||
},
|
||||
"event_confirm": {
|
||||
"content": "Вы можете подтвердить это событие по адресу <a href='{{url}}'>на этой странице</a>"
|
||||
},
|
||||
"test": {
|
||||
"subject": "Ваша конфигурация SMTP работает",
|
||||
"content": "Это тестовое письмо, если вы читаете его, ваша конфигурация работает."
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
{}
|
|
@ -82,6 +82,7 @@
|
|||
"url": "URL",
|
||||
"place": "Place",
|
||||
"tags": "Tags",
|
||||
"tag": "Tag",
|
||||
"theme": "Theme",
|
||||
"reset": "Reset",
|
||||
"import": "Import",
|
||||
|
@ -146,6 +147,7 @@
|
|||
"recurrent": "Recurring",
|
||||
"edit_recurrent": "Edit recurring event:",
|
||||
"show_recurrent": "recurring events",
|
||||
"show_multidate": "multidate events",
|
||||
"show_past": "also prior events",
|
||||
"only_future": "only upcoming events",
|
||||
"recurrent_description": "Choose frequency and select days",
|
||||
|
@ -192,6 +194,7 @@
|
|||
"event_remove_ok": "Event removed",
|
||||
"allow_registration_description": "Allow open registrations?",
|
||||
"allow_anon_event": "Allow anonymous events (has to be confirmed)?",
|
||||
"allow_multidate_event": "Allow multi-day events",
|
||||
"allow_recurrent_event": "Allow recurring events",
|
||||
"allow_geolocation": "Allow events geolocation",
|
||||
"recurrent_event_visible": "Show recurring events by default",
|
||||
|
@ -212,6 +215,7 @@
|
|||
"hide_resource": "Hide resource",
|
||||
"delete_resource": "Delete resource",
|
||||
"delete_resource_confirm": "Are you sure you want to delete this resource?",
|
||||
"delete_tag_confirm": "Are you sure you want to remove the tag \"{tag}\"? The tag will be removed from {n} events.",
|
||||
"block_user": "Block user",
|
||||
"filter_instances": "Filter instances",
|
||||
"filter_users": "Filter users",
|
||||
|
@ -243,6 +247,8 @@
|
|||
"footer_links": "Footer links",
|
||||
"delete_footer_link_confirm": "Sure to remove this link?",
|
||||
"edit_place": "Edit place",
|
||||
"edit_tag": "Edit tag",
|
||||
"edit_tag_help": "You can change the tag by replacing it with a new one or merging it with an existing one. The {n} associated events will also be changed.",
|
||||
"new_announcement": "New announcement",
|
||||
"show_smtp_setup": "Email settings",
|
||||
"smtp_hostname": "SMTP Hostname",
|
||||
|
@ -252,12 +258,14 @@
|
|||
"smtp_test_success": "A test email is sent to {admin_email}, please check your inbox",
|
||||
"smtp_test_button": "Send a test email",
|
||||
"smtp_use_sendmail": "Use sendmail",
|
||||
"sender_email": "Sender e-mail",
|
||||
"admin_email": "Admin e-mail",
|
||||
"admin_email_help": "The address we use as the sender to send emails. This is also the address to which admin emails are sent",
|
||||
"widget": "Widget",
|
||||
"wrong_domain_warning": "The baseurl configured in config.json <b>({baseurl})</b> differs from the one you're visiting <b>({url})</b>",
|
||||
"new_collection": "New collection",
|
||||
"collections_description": "Collections are groupings of events by tags and places. They will be displayed on the home page",
|
||||
"edit_collection": "Edit Collection",
|
||||
"delete_collection_confirm": "Are you sure you want to remove the collection <u>{collection}</u>?",
|
||||
"config_plugin": "Plugin configuration",
|
||||
"plugins_description": "",
|
||||
"fallback_image": "Fallback image",
|
||||
|
@ -265,10 +273,28 @@
|
|||
"hide_thumbs": "Hide thumbs",
|
||||
"hide_calendar": "Hide calendar",
|
||||
"default_images": "Default images",
|
||||
"default_images_help": "You have to <a href='/admin?tab=theme'>refresh</a> the page to see the changes.",
|
||||
"blocked": "Blocked",
|
||||
"domain": "Domain",
|
||||
"known_users": "Known users",
|
||||
"created_at": "Created at"
|
||||
"created_at": "Created at",
|
||||
"geolocation_description": "<b>1. Define a provider for geocoding service</b>.<br>Currently, among those listed in the <a href=\"https://wiki.openstreetmap.org/wiki/Nominatim#Alternatives_.2F_Third-party_providers\">wiki of OpenStreetMap</a>, there is support for software <a href=\"https://github.com/osm-search/Nominatim\">Nominatim</a> and <a href=\"https://github.com/komoot/photon\">Photon</a>.<br>You can use one of the related official demos by copying the link in the 'Geocoding provider' field:<ul><li>https://nominatim.openstreetmap.org/search (<a href=\"https://operations.osmfoundation.org/policies/nominatim/\">Terms of Service</a>)</li><li>https://photon.komoot.io/api/ (<a href=\"https://photon.komoot.io/\">Terms of Service</a>)</li></ul><br><b>2. Define a provider for map layers.</b><br>You can find a list of them here: <a href=\"https://leaflet-extras.github.io/leaflet-providers/preview/\">https://leaflet-extras.github.io/leaflet-providers/preview/</a>",
|
||||
"geocoding_provider_type": "Geocoding software",
|
||||
"geocoding_provider_type_help": "The default software is Nominatim",
|
||||
"geocoding_provider": "Geocoding provider",
|
||||
"geocoding_provider_help": "The default provider is Nominatim",
|
||||
"geocoding_countrycodes": "Country codes",
|
||||
"geocoding_countrycodes_help": "Allows you to set a filter to searches based on area codes",
|
||||
"geocoding_test_button": "Test geocoding",
|
||||
"geocoding_test_success": "The geocoding service at {service_name} is working",
|
||||
"geocoding_test_error": "The geocoding service is not reachable at {service_name}",
|
||||
"tilelayer_provider": "Tilelayer provider",
|
||||
"tilelayer_provider_help": "The default provider is OpenStreetMap",
|
||||
"tilelayer_provider_attribution": "Attribution",
|
||||
"tilelayer_test_button": "Test tilelayer",
|
||||
"tilelayer_test_success": "The tilelayer service at {service_name} is working",
|
||||
"tilelayer_test_error": "The tilelayer service is not reachable at {service_name}",
|
||||
"geolocation": "Geolocation"
|
||||
},
|
||||
"auth": {
|
||||
"not_confirmed": "Not confirmed yet…",
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
"embed": "Incorporar",
|
||||
"embed_title": "Publica este evento en tu página web",
|
||||
"embed_help": "Copiando el siguiente código en tu página web, el evento será incluido como puedes ver aquí al lado",
|
||||
"feed": "RSS Feed",
|
||||
"feed": "Canal RSS",
|
||||
"feed_url_copied": "Copiada feed url, pegala en tu lector de feeds",
|
||||
"follow_me_title": "Sigue las actualizaciones en el fediverso",
|
||||
"follow": "Sigue",
|
||||
|
@ -98,7 +98,11 @@
|
|||
"about": "Sobre el sitio",
|
||||
"close": "Cerrar",
|
||||
"help_translate": "Ayuda a traducir",
|
||||
"calendar": "Calendario"
|
||||
"calendar": "Calendario",
|
||||
"content": "Contenido",
|
||||
"admin_actions": "Acciones de administrador",
|
||||
"recurring_event_actions": "Acciones de eventos recurrentes",
|
||||
"tag": "Etiqueta"
|
||||
},
|
||||
"login": {
|
||||
"description": "Entrando podrás publicar nuevos eventos.",
|
||||
|
@ -179,7 +183,8 @@
|
|||
"address_description": "¿Cuál es la dirección?",
|
||||
"alt_text_description": "Descripción para personas con baja visión",
|
||||
"download_flyer": "Descargar folleto",
|
||||
"remove_media_confirmation": "¿Confirmas la eliminación de la imagen?"
|
||||
"remove_media_confirmation": "¿Confirmas la eliminación de la imagen?",
|
||||
"show_multidate": "eventos de varios días"
|
||||
},
|
||||
"admin": {
|
||||
"place_description": "En el caso de que un lugar sea incorrecto o cambie de dirección, puedes cambiarlo. <br/> Todos los eventos presentes y pasados asociados con este lugar cambiarán de dirección.",
|
||||
|
@ -268,7 +273,35 @@
|
|||
"smtp_use_sendmail": "Usar sendmail",
|
||||
"wrong_domain_warning": "El parámetro baseurl configurado en config.json <b>({baseurl})</b> difiere del que estás visitando <b>({url})</b>",
|
||||
"new_collection": "Nueva colección",
|
||||
"collections_description": "Las colecciones son agrupaciones de eventos por etiquetas y ubicaciones. Serán desplegadas en la página de inicio"
|
||||
"collections_description": "Las colecciones son agrupaciones de eventos por etiquetas y ubicaciones. Serán desplegadas en la página de inicio",
|
||||
"domain": "Dominio",
|
||||
"created_at": "Creado a las",
|
||||
"blocked": "Bloqueado",
|
||||
"known_users": "Usuarios conocidos",
|
||||
"default_images_help": "Tienes que <a href='/admin?tab=theme'>actualizas</a> la página para ver los cambios.",
|
||||
"geocoding_provider": "Proveedor de geocodificación",
|
||||
"admin_email_help": "La dirección que usamos como remitente para enviar emails. Esta es también la dirección a la cual los emails de administración son enviados",
|
||||
"allow_multidate_event": "Permitir eventos de múltiples días",
|
||||
"geocoding_countrycodes": "Códigos de país",
|
||||
"geocoding_provider_help": "El proveedor por defecto es Nominatim",
|
||||
"geocoding_provider_type": "Software de geocodificación",
|
||||
"geocoding_provider_type_help": "El programa por defecto es Nominatim",
|
||||
"geocoding_countrycodes_help": "Permite establecer un filtro para las búsquedas basadas en códigos de área",
|
||||
"geocoding_test_button": "Prueba de geocodificación",
|
||||
"delete_collection_confirm": "¿Estás seguro de que quieres eliminar la colección <u>{collection}</u>?",
|
||||
"geocoding_test_success": "El servicio de geocodificación de {service_name} funciona",
|
||||
"tilelayer_provider": "Proveedor de mosaicos de mapas",
|
||||
"tilelayer_provider_help": "El proveedor por defecto es OpenStreetMap",
|
||||
"tilelayer_provider_attribution": "Atribución",
|
||||
"tilelayer_test_button": "Capa de mosaicos de prueba",
|
||||
"tilelayer_test_success": "El servicio de mosaicos en {service_name} está funcionando",
|
||||
"tilelayer_test_error": "No se puede acceder al servicio de mosaico en {service_name}",
|
||||
"geolocation": "Geolocalización",
|
||||
"delete_tag_confirm": "¿Está seguro de que desea eliminar la etiqueta \"{tag}\"? La etiqueta se eliminará de {n} eventos.",
|
||||
"edit_tag": "Editar la etiqueta",
|
||||
"edit_tag_help": "Puede cambiar la etiqueta sustituyéndola por una nueva o fusionándola con una existente. Los {n} eventos asociados también se modificarán.",
|
||||
"geolocation_description": "<b>1. Defina un proveedor para el servicio de codificación geográfica</b>.<br>Actualmente, entre los enumerados en el <a href=\"https://wiki.openstreetmap.org/wiki/Nominatim#Alternatives_.2F_Third-party_providers\">wiki de OpenStreetMap </a>, hay soporte para el software <a href=\"https://github.com/osm-search/Nominatim\">Nominatim</a> y <a href=\"https://github.com/komoot /photon\">Photon</a>.<br>Puede usar una de las demostraciones oficiales relacionadas copiando el enlace en el campo 'Proveedor de codificación geográfica':<ul><li>https://nominatim.openstreetmap.org/search (<a href=\"https://operations.osmfoundation.org/policies/nominatim/\">Términos de servicio</a>)</li><li>https://photon.komoot.io/api/ (<a href=\"https://photon.komoot.io/\"> Condiciones de servicio</a>)</li></ul><br><b>2. Defina un proveedor para las capas del mapa.</b><br>Puede encontrar una lista de ellos aquí: <a href=\"https://leaflet-extras.github.io/leaflet-providers/preview/\">https://leaflet-extras.github.io/leaflet-providers/preview/</a>",
|
||||
"geocoding_test_error": "No se puede acceder al servicio de geocodificación en {service_name}"
|
||||
},
|
||||
"auth": {
|
||||
"not_confirmed": "Todavía no hemos confirmado este email…",
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
"events": "Ekitaldiak",
|
||||
"places": "Lekuak",
|
||||
"settings": "Aukerak",
|
||||
"actions": "Ekintzak",
|
||||
"actions": "Eragiketak",
|
||||
"deactivate": "Desaktibatu",
|
||||
"remove_admin": "Kendu administratzaile baimena",
|
||||
"activate": "Aktibatu",
|
||||
|
@ -95,7 +95,10 @@
|
|||
"show_map": "Erakutsi mapa",
|
||||
"calendar": "Egutegia",
|
||||
"home": "Etxea",
|
||||
"about": "Honi buruz"
|
||||
"about": "Honi buruz",
|
||||
"recurring_event_actions": "Ekitaldi errepikarien eragiketak",
|
||||
"content": "Edukia",
|
||||
"admin_actions": "Administratzaile eragiketak"
|
||||
},
|
||||
"login": {
|
||||
"description": "Saioa hasten baduzu ekitaldi berriak sortu ahal izango dituzu.",
|
||||
|
@ -265,7 +268,30 @@
|
|||
"config_plugin": "Pluginaren konfigurazioa",
|
||||
"fallback_image": "Lehenetsitako irudia",
|
||||
"header_image": "Goiburuko irudia",
|
||||
"default_images": "Lehenetsitako irudiak"
|
||||
"default_images": "Lehenetsitako irudiak",
|
||||
"blocked": "Blokeatuta",
|
||||
"domain": "Domeinua",
|
||||
"default_images_help": "Orrialdea <a href='/admin?tab=theme'>freskatu</a> behar duzu aldaketak ikusteko.",
|
||||
"known_users": "Erabiltzaile ezagunak",
|
||||
"created_at": "Noiz sortua:",
|
||||
"geocoding_provider_type_help": "Software lehenetsia Nominatim da",
|
||||
"geocoding_provider": "Geokodeketaren hornitzailea",
|
||||
"geocoding_countrycodes": "Estatuen kodeak",
|
||||
"geocoding_countrycodes_help": "Baimendu iragazki bat ezartzen zonalde kodearen arabera",
|
||||
"geocoding_test_button": "Probatu geokodeketa",
|
||||
"tilelayer_provider_help": "Lehenetsitako hornitzailea OpenStreetMap da",
|
||||
"tilelayer_provider_attribution": "Atribuzioa",
|
||||
"geocoding_test_error": "Geokodeketa zerbitzua {service_name}-(e)n ez dago atzigarri",
|
||||
"tilelayer_provider": "Lauza-geruzen hornitzailea",
|
||||
"tilelayer_test_button": "Probatu lauza-geruza",
|
||||
"tilelayer_test_error": "Lauza-geruzen zerbitzua {service_name}(e)n ez dago atzigarri",
|
||||
"admin_email_help": "Epostak bidaltzeko erabiltzen dugun helbidea. Administrazio epostak ere horra bidaltzen dira",
|
||||
"allow_multidate_event": "Baimendu egun anitzeko ekitaldiak",
|
||||
"geocoding_provider_type": "Geokodeketarako softwarea",
|
||||
"geocoding_provider_help": "Lehenetsitako hornitzailea Nominatim da",
|
||||
"geolocation": "Geokokapena",
|
||||
"geocoding_test_success": "Geokodeketa zerbitzua {service_name}-(e)n martxan dago",
|
||||
"tilelayer_test_success": "Lauza-geruzen zerbitzua {service_name}(e)n martxan dago"
|
||||
},
|
||||
"auth": {
|
||||
"not_confirmed": "Oraindik baieztatu gabe dago…",
|
||||
|
|
|
@ -93,7 +93,16 @@
|
|||
"show_map": "Mostrar mapa",
|
||||
"latitude": "Latitude",
|
||||
"longitude": "Longitude",
|
||||
"getting_there": "Chegar lá"
|
||||
"getting_there": "Chegar lá",
|
||||
"plugins": "Complementos",
|
||||
"help_translate": "Axuda coa tradución",
|
||||
"calendar": "Calendario",
|
||||
"home": "Inicio",
|
||||
"about": "Acerca de",
|
||||
"content": "Contido",
|
||||
"admin_actions": "Accións de Admin",
|
||||
"recurring_event_actions": "Accións de eventos recurrentes",
|
||||
"tag": "Etiqueta"
|
||||
},
|
||||
"recover": {
|
||||
"not_valid_code": "Algo fallou."
|
||||
|
@ -134,7 +143,7 @@
|
|||
"interact_with_me": "Sígueme",
|
||||
"remove_recurrent_confirmation": "Tes a certeza de querer eliminar este evento recurrente?\nOs eventos pasados permanecerán, pero non se crearán novos eventos.",
|
||||
"import_URL": "Importar desde URL",
|
||||
"anon": "Anon",
|
||||
"anon": "Anónimo",
|
||||
"anon_description": "Podes engadir un evento sen rexistrarte nen acceder, pero deberás agardar a que alguén o lea e\nconfirme que é un evento axeitado. Non será posible modificalo.<br/><br/>\nPodes tamén <a href='/login'>acceder</a> ou <a href='/register'>crear unha conta</a>. Mais podes continuar e agardar a que se comprobe. ",
|
||||
"same_day": "no mesmo día",
|
||||
"tag_description": "Cancelo",
|
||||
|
@ -163,7 +172,10 @@
|
|||
"alt_text_description": "Descrición para persoas con problemas de visión",
|
||||
"choose_focal_point": "Elixe onde centrar a atención",
|
||||
"remove_media_confirmation": "Confirmas a eliminación da imaxe?",
|
||||
"download_flyer": "Descargar folleto"
|
||||
"download_flyer": "Descargar folleto",
|
||||
"address_description": "Cal é o enderezo?",
|
||||
"address_description_osm": "Cal é o enderezo? (Contribuíntes a <a href='http://osm.org/copyright'>OpenStreetMap</a>)",
|
||||
"show_multidate": "eventos con varias datas"
|
||||
},
|
||||
"admin": {
|
||||
"place_description": "Se escribiches mal o lugar ou enderezo, podes cambialo.<br/>Cambiará o enderezo de tódolos eventos actuais e pasados asociados a este lugar.",
|
||||
|
@ -247,8 +259,42 @@
|
|||
"disable_admin_user_confirm": "Tes certeza de querer retirar os permisos de administración a {user}?",
|
||||
"sender_email": "Remitente",
|
||||
"collections_description": "As coleccións son agrupamentos de eventos por etiqueta ou lugar. Pode ser mostrado na páxina de inicio",
|
||||
"enable_admin_user_confirm": "Tes a certeza de querer darlle permisos de administración a {user}?",
|
||||
"smtp_use_sendmail": "Usar sendmail"
|
||||
"enable_admin_user_confirm": "Tes a certeza de engadir permiso de admin a {user}?",
|
||||
"smtp_use_sendmail": "Usar sendmail",
|
||||
"config_plugin": "Configuración do complemento",
|
||||
"known_users": "Usuarias coñecidas",
|
||||
"created_at": "Creado o",
|
||||
"fallback_image": "Imaxe por omisión",
|
||||
"header_image": "Imaxe da cabeceira",
|
||||
"hide_thumbs": "Agochar miniaturas",
|
||||
"hide_calendar": "Agochar calendario",
|
||||
"default_images": "Imaxes por defecto",
|
||||
"blocked": "Bloqueado",
|
||||
"domain": "Dominio",
|
||||
"default_images_help": "Tes que <a href='/admin?tab=theme'>actualizar</a> a páxina para ver os cambios.",
|
||||
"geocoding_provider_type": "Software Geocoding",
|
||||
"geocoding_provider_type_help": "O software por defecto é Nominatim",
|
||||
"geocoding_provider": "Provedor Geocoding",
|
||||
"geocoding_provider_help": "O provedor por defecto é Nominatim",
|
||||
"geocoding_countrycodes": "Códigos de país",
|
||||
"geocoding_countrycodes_help": "Permíteche establecer un filtro para as buscas en función do código",
|
||||
"geocoding_test_button": "Comproba a codificación",
|
||||
"geocoding_test_success": "O servizo geocoding en {service_name} funciona",
|
||||
"geocoding_test_error": "O servizo geocoding non está accesible en {service_name}",
|
||||
"tilelayer_provider": "Provedor de teselas do mapa",
|
||||
"tilelayer_provider_help": "O provedor por defecto é OpenStreetMap",
|
||||
"tilelayer_provider_attribution": "Atribución",
|
||||
"tilelayer_test_button": "Comproba as capas",
|
||||
"tilelayer_test_success": "O servizo de capas en {service_name} funciona",
|
||||
"tilelayer_test_error": "O servizo de capas en {service_name} non está accesible",
|
||||
"geolocation": "Xeolocalización",
|
||||
"allow_multidate_event": "Permitir eventos de varios días",
|
||||
"admin_email_help": "O enderezo que se usará como remitente para os emails. Tamén é o enderezo ao que se envían emails de administración",
|
||||
"geolocation_description": "<b>1. Define un provedor para o servizo geocoding</b>.<br>Actualmente, entre os que aparecen na <a href=\"https://wiki.openstreetmap.org/wiki/Nominatim#Alternatives_.2F_Third-party_providers\">wiki de OpenStreetMap</a>, temos soporte para <a href=\"https://github.com/osm-search/Nominatim\">Nominatim</a> e <a href=\"https://github.com/komoot/photon\">Photon</a>.<br>Podes usar unha das demos oficiais indicadas copiando a ligazón no campo 'Provedor Geocoding':<ul><li>https://nominatim.openstreetmap.org/search (<a href=\"https://operations.osmfoundation.org/policies/nominatim/\">Termos do Servizo</a>)</li><li>https://photon.komoot.io/api/ (<a href=\"https://photon.komoot.io/\">Termos do Servizo</a>)</li></ul><br><b>2. Define un provedor para capas do mapa.</b><br>Aquí hai unha lista: <a href=\"https://leaflet-extras.github.io/leaflet-providers/preview/\">https://leaflet-extras.github.io/leaflet-providers/preview/</a>",
|
||||
"edit_tag": "Editar etiqueta",
|
||||
"edit_tag_help": "Podes cambiar a etiqueta substituíndoa por unha nova ou fusionándoa cunha existente. Tamén se cambiarán os {n} eventos asociados.",
|
||||
"delete_tag_confirm": "Estás seguro de que queres eliminar a etiqueta \"{tag}\"? A etiqueta eliminarase de {n} eventos.",
|
||||
"delete_collection_confirm": "Estás seguro de que queres eliminar a colección <u>{collection}</u>?"
|
||||
},
|
||||
"auth": {
|
||||
"not_confirmed": "Aínda non foi confirmado…",
|
||||
|
|
|
@ -10,6 +10,7 @@ module.exports = {
|
|||
nb: 'Norwegian Bokmål',
|
||||
pl: 'Polski',
|
||||
pt: 'Português',
|
||||
ru: 'Русский',
|
||||
sk: 'Slovak',
|
||||
zh: '中国'
|
||||
}
|
||||
|
|
|
@ -136,9 +136,9 @@
|
|||
"saved": "Evento salvato",
|
||||
"added_anon": "Evento aggiunto, verrà confermato quanto prima.",
|
||||
"updated": "Evento aggiornato",
|
||||
"where_description": "Dov'è il gancio? Se il posto non è presente potrai crearlo.",
|
||||
"where_description": "Dov'è l'evento? Se il posto non è presente potrai crearlo.",
|
||||
"address_description": "A che indirizzo?",
|
||||
"address_description_osm": "A che indirizzo? ((<a href='http://osm.org/copyright'>OpenStreetMap</a>)",
|
||||
"address_description_osm": "A che indirizzo? (<a href='http://osm.org/copyright'>OpenStreetMap</a>)",
|
||||
"coordinates_search_description": "Puoi ricercare il posto per nome, o incollare la coppia di coordinate.",
|
||||
"confirmed": "Evento confermato",
|
||||
"not_found": "Evento non trovato",
|
||||
|
@ -147,6 +147,7 @@
|
|||
"recurrent": "Ricorrente",
|
||||
"edit_recurrent": "Modifica evento ricorrente:",
|
||||
"show_recurrent": "appuntamenti ricorrenti",
|
||||
"show_multidate": "eventi di più giorni",
|
||||
"show_past": "eventi passati",
|
||||
"recurrent_description": "Scegli la frequenza e seleziona i giorni",
|
||||
"multidate_description": "Un festival o una tre giorni? Scegli quando comincia e quando finisce",
|
||||
|
@ -240,6 +241,8 @@
|
|||
"footer_links": "Collegamenti del piè di pagina",
|
||||
"delete_footer_link_confirm": "Vuoi eliminare questo collegamento?",
|
||||
"edit_place": "Modifica luogo",
|
||||
"edit_tag": "Modifica tag",
|
||||
"edit_tag_help": "Puoi cambiare il tag sostituendolo con uno nuovo o unendolo ad uno gia' esistente. Verranno modificati anche i {n} eventi associati",
|
||||
"new_announcement": "Nuovo annuncio",
|
||||
"show_smtp_setup": "Impostazioni email",
|
||||
"widget": "Widget",
|
||||
|
@ -248,6 +251,7 @@
|
|||
"smtp_test_success": "Una mail di test è stata inviata all'indirizzo {admin_email}, controlla la tua casella di posta",
|
||||
"smtp_test_button": "Invia una mail di prova",
|
||||
"admin_email": "E-mail dell'admin",
|
||||
"admin_email_help": "L'indirizzo che usiamo come mittente per inviare le e-mail. È anche l'indirizzo a cui vengono spedite le e-mail di amministrazione",
|
||||
"new_collection": "Crea bolla",
|
||||
"wrong_domain_warning": "Il \"baseurl\" configurato in config.json <b>({baseurl})</b> è diverso da quello che stai visitando <b>({url})</b>",
|
||||
"collections_description": "Le bolle sono raggruppamenti di eventi per tag e posti.",
|
||||
|
@ -257,7 +261,29 @@
|
|||
"header_image": "Immagine di intestazione",
|
||||
"hide_thumbs": "Nascondi immaginine",
|
||||
"hide_calendar": "Nascondi calendario",
|
||||
"default_images": "Immagini preimpostate"
|
||||
"default_images": "Immagini preimpostate",
|
||||
"default_images_help": "Devi <a href='/admin?tab=theme'>aggiornare</a> la pagina per vedere le modifiche.",
|
||||
"blocked": "Bloccato",
|
||||
"domain": "Domini",
|
||||
"known_users": "Utenti conosciuti",
|
||||
"created_at": "Creato il",
|
||||
"geolocation_description": "<b>1. Definisci un fornitore per il servizio di georeferenziazione (geocodifica)</b>.<br>Al momento, tra quelli elencati nella <a href=\"https://wiki.openstreetmap.org/wiki/Nominatim#Alternatives_.2F_Third-party_providers\">wiki di OpenStreetMap</a>, è presente il supporto per i software <a href=\"https://github.com/osm-search/Nominatim\">Nominatim</a> e <a href=\"https://github.com/komoot/photon\">Photon</a>.<br>Puoi utilizzare una delle relative demo ufficiali copiandone il link nel campo 'Fornitore georeferenziazione':<ul><li>https://nominatim.openstreetmap.org/search (<a href=\"https://operations.osmfoundation.org/policies/nominatim/\">Terms of Service</a>)</li><li>https://photon.komoot.io/api/ (<a href=\"https://photon.komoot.io/\">Terms of Service</a>)</li></ul><br><b>2. Definisci un fornitore di layers per la mappa.</b><br>Qui puoi trovarne una lista: <a href=\"https://leaflet-extras.github.io/leaflet-providers/preview/\">https://leaflet-extras.github.io/leaflet-providers/preview/</a>",
|
||||
"geocoding_provider_type": "Software fornitore georeferenziazione",
|
||||
"geocoding_provider_type_help": "Il software di default è Nominatim",
|
||||
"geocoding_provider": "Fornitore georeferenziazione",
|
||||
"geocoding_provider_help": "Il fornitore di default è Nominatim",
|
||||
"geocoding_countrycodes": "Codici territoriali",
|
||||
"geocoding_countrycodes_help": "Permette di impostare un filtro alle ricerche basato su codici territori nazionali",
|
||||
"geocoding_test_button": "Test geocoding",
|
||||
"geocoding_test_success": "Il servizio di geocoding all'indirizzo {service_name} sta funzionando",
|
||||
"geocoding_test_error": "Il servizio non è raggiungibile all'indirizzo: {service_name}",
|
||||
"tilelayer_provider": "Fornitore tilelayer",
|
||||
"tilelayer_provider_help": "Il fornitore di default è OpenStreetMap",
|
||||
"tilelayer_provider_attribution": "Attribuzione",
|
||||
"tilelayer_test_button": "Test tilelayer",
|
||||
"tilelayer_test_success": "Il servizio di tilelayer all'indirizzo {service_name} sta funzionando",
|
||||
"tilelayer_test_error": "Il servizio non è raggiungibile all'indirizzo: {service_name}",
|
||||
"geolocation": "Geo e mappe"
|
||||
},
|
||||
"auth": {
|
||||
"not_confirmed": "Non ancora confermato…",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"common": {
|
||||
"add_event": "Adicionar Evento",
|
||||
"add_event": "Adicionar evento",
|
||||
"description": "Descrição",
|
||||
"send": "Enviar",
|
||||
"address": "Endereço",
|
||||
|
@ -13,7 +13,7 @@
|
|||
"what": "O que",
|
||||
"media": "Media",
|
||||
"password": "Senha",
|
||||
"register": "Registro",
|
||||
"register": "Registrar",
|
||||
"remove": "Remover",
|
||||
"confirm": "Confirmar",
|
||||
"events": "Eventos",
|
||||
|
@ -22,8 +22,8 @@
|
|||
"edit": "Editar",
|
||||
"admin": "Admin",
|
||||
"places": "Lugares",
|
||||
"hide": "Esconder",
|
||||
"search": "Procurar",
|
||||
"hide": "Ocultar",
|
||||
"search": "Buscar",
|
||||
"info": "Info",
|
||||
"users": "Usuários",
|
||||
"share": "Compartilhar",
|
||||
|
@ -76,11 +76,11 @@
|
|||
"logout_ok": "Deslogado",
|
||||
"n_resources": "nenhum recurso|um recurso|{n} recursos",
|
||||
"embed": "Incorporar",
|
||||
"embed_title": "Incorpore este evento na sua página",
|
||||
"embed_help": "Copie o código seguinte em sua página e o evento será apresentado desta maneira",
|
||||
"embed_title": "Incorpore este evento em sua página",
|
||||
"embed_help": "Copie o seguinte código em sua página e o evento será exibido desta maneira",
|
||||
"displayname": "Nome de exibição",
|
||||
"feed_url_copied": "Abra a URL do feed no seu leitor RSS",
|
||||
"follow_me_title": "Seguir atualizações a partir do Fediverso",
|
||||
"feed_url_copied": "Abra a URL copiada do feed em seu leitor de RSS",
|
||||
"follow_me_title": "Siga as atualizações pelo Fediverso",
|
||||
"tags": "Marcadores",
|
||||
"theme": "Tema",
|
||||
"reset": "Reiniciar",
|
||||
|
@ -88,18 +88,27 @@
|
|||
"collections": "Coleções",
|
||||
"max_events": "N. máximo de eventos",
|
||||
"label": "Etiqueta",
|
||||
"close": "Fechar"
|
||||
"close": "Fechar",
|
||||
"plugins": "Plugins",
|
||||
"help_translate": "Ajude a traduzir",
|
||||
"show_map": "Mostrar mapa",
|
||||
"calendar": "Calendário",
|
||||
"home": "Início",
|
||||
"about": "Sobre",
|
||||
"content": "Conteúdo",
|
||||
"admin_actions": "Ações de admin",
|
||||
"recurring_event_actions": "Ações de eventos recorrentes"
|
||||
},
|
||||
"admin": {
|
||||
"user_block_confirm": "Você está certo que quer bloquer o usuário {user}?",
|
||||
"user_block_confirm": "Você está certo que quer bloquear o usuário {user}?",
|
||||
"filter_instances": "Filtrar instâncias",
|
||||
"user_add_help": "Um e-mail con instruções para confirmar a inscrição e escolher uma senha será enviada ao novo usuário",
|
||||
"user_add_help": "Um e-mail com instruções para confirmar a inscrição e escolher uma senha será enviada ao novo usuário",
|
||||
"show_resource": "Mostrar recurso",
|
||||
"block_user": "Bloquear usuário",
|
||||
"filter_users": "Filtrar usuários",
|
||||
"hide_resource": "Ocultar recurso",
|
||||
"delete_resource": "Remover recurso",
|
||||
"delete_resource_confirm": "Você está certo que quer remover esse recurso?",
|
||||
"delete_resource_confirm": "Você está certo que quer remover este recurso?",
|
||||
"show_smtp_setup": "Configurações de e-mail",
|
||||
"resources": "Recursos",
|
||||
"delete_announcement_confirm": "Você está certo que quer remover o anúncio?",
|
||||
|
@ -107,7 +116,7 @@
|
|||
"collections_description": "Coleções são agrupamentos de eventos por marcadores e locais. Eles serão exibidos na página principal",
|
||||
"new_collection": "Nova coleção",
|
||||
"disable_admin_user_confirm": "Você está certo que quer remover permissões de administração de {user}?",
|
||||
"enable_admin_user_confirm": "Você está certo que quer adicionar permissões de administrador para {user}",
|
||||
"enable_admin_user_confirm": "Você está certo que quer adicionar permissões de administrador para {user}?",
|
||||
"event_remove_ok": "Evento removido",
|
||||
"smtp_description": "<ul><li>Administrador deve receber um e-mail quando um evento anônimo for adicionado (se habilitado).</li><li>Administrador deve receber um e-mail de requisição de registro (se habilitado).</li><li>Usuário deve receber um e-mail de solicitação de registro.</li><li>Usuário deve receber um e-mail de confirmação de registro.</li><li>Usuário deve recever um e-mail de confirmação quando registrado diretamente por um administrador.</li><li>Usuários devem receber um e-mail para recuperar a senha quando eles esquecerem ela</li></ul>",
|
||||
"allow_registration_description": "Permitir registro aberto de usuários?",
|
||||
|
@ -138,7 +147,7 @@
|
|||
"smtp_secure": "SMTP Seguro (TLS ou STARTTLS)",
|
||||
"smtp_use_sendmail": "Utilizar sendmail",
|
||||
"instance_place_help": "A etiqueta para exibir em outras instâncias",
|
||||
"delete_trusted_instance_confirm": "Você quer realmente remover esse item do menu de instâncias amigas?",
|
||||
"delete_trusted_instance_confirm": "Você quer realmente remover este item do menu de instâncias amigas?",
|
||||
"sender_email": "E-mail do remetente",
|
||||
"widget": "Widget",
|
||||
"wrong_domain_warning": "A baseurl configurado em config.json <b>({baseurl})</b> é diferente da que você está visitando <b>({url})</b>",
|
||||
|
@ -160,7 +169,7 @@
|
|||
"allow_recurrent_event": "Permitir eventos recorrentes",
|
||||
"recurrent_event_visible": "Exibir eventos recorrentes por padrão",
|
||||
"user_blocked": "Usuário {user} bloqueado",
|
||||
"announcement_description": "Nesta seção você pode inserir um anúncio para ser exibido na página principal",
|
||||
"announcement_description": "Nesta seção você pode inserir anúncios que serão exibidos na página principal",
|
||||
"description_description": "Exibido no cabeçalho próximo ao título",
|
||||
"instance_place": "Local indicativo desta instância",
|
||||
"is_dark": "Tema escuro",
|
||||
|
@ -168,14 +177,29 @@
|
|||
"new_announcement": "Novo anúncio",
|
||||
"federation": "Federação / ActivityPub",
|
||||
"smtp_test_success": "Um e-mail de teste foi enviado para {admin_email}, por favor verifique sua caixa de entrada",
|
||||
"smtp_test_button": "Enviar e-mail de teste"
|
||||
"smtp_test_button": "Enviar e-mail de teste",
|
||||
"allow_geolocation": "Permitir geolocalização de eventos",
|
||||
"config_plugin": "Configuração de plugin",
|
||||
"fallback_image": "Imagem alternativa",
|
||||
"header_image": "Imagem de cabeçalho",
|
||||
"hide_thumbs": "Ocultar miniaturas",
|
||||
"default_images_help": "Você precisa <a href='/admin?tab=theme'>recarregar</a> a página para ver as mudanças.",
|
||||
"domain": "Domínio",
|
||||
"default_images": "Imagens padrão",
|
||||
"known_users": "Usuários conhecidos",
|
||||
"created_at": "Criado em",
|
||||
"hide_calendar": "Ocultar calendário",
|
||||
"blocked": "Bloqueado",
|
||||
"admin_email": "E-mail do admin",
|
||||
"tilelayer_provider_attribution": "Atribuição",
|
||||
"geolocation": "Geolocalização"
|
||||
},
|
||||
"event": {
|
||||
"follow_me_description": "Uma das maneiras de se manter atualizado com os eventos publicados aqui em {title},\né seguir a conta <u>{account}</u> no Fediverso, por exemplo via Mastodon, e possivelmente adicionar recursos para um evento a partir de lá.<br/><br/>\nSe você nunca ouviu falar sobre Mastodon ou do Fediverso nós recomendamos ler <a href='https://www.savjee.be/videos/simply-explained/mastodon-and-fediverse-explained/'>este artigo</a>.<br/><br/>Entre com sua instância abaixo (e.g. mastodon.social)",
|
||||
"saved": "Evento salvo",
|
||||
"recurrent": "Recorrente",
|
||||
"ics": "ICS",
|
||||
"recurrent_1m_days": "|O {days} de cada mês|{days} de cada mês",
|
||||
"recurrent_1m_days": "Dia {days} de cada mês",
|
||||
"interact_with_me": "Siga-me",
|
||||
"media_description": "Você pode adicionar um flyer (opcional)",
|
||||
"same_day": "no mesmo dia",
|
||||
|
@ -202,12 +226,12 @@
|
|||
"normal_description": "Escolha o dia.",
|
||||
"recurrent_1w_days": "A cada {days}",
|
||||
"each_week": "Toda semana",
|
||||
"each_2w": "Todas outras semanas",
|
||||
"each_month": "Cada mês",
|
||||
"each_2w": "A cada duas semanas",
|
||||
"each_month": "Todo mês",
|
||||
"recurrent_2w_days": "{days} a cada dois",
|
||||
"recurrent_2m_days": "|Dia {days} a cada dois meses|Os dias {days} a cada dois meses",
|
||||
"recurrent_1m_ordinal": "O {n} {days} de cada mês",
|
||||
"recurrent_2m_ordinal": "|O {n} {days} a cada dois meses|O {n} {days} a cada dois meses",
|
||||
"recurrent_2m_days": "Dia {days} a cada dois meses",
|
||||
"recurrent_1m_ordinal": "{n} {days} de cada mês",
|
||||
"recurrent_2m_ordinal": "{n} {days} a cada dois meses",
|
||||
"due": "até",
|
||||
"from": "De",
|
||||
"image_too_big": "A imagem não pode ser maior que 4MB",
|
||||
|
@ -219,7 +243,9 @@
|
|||
"alt_text_description": "Descrição para pessoas com deficiências visuais",
|
||||
"choose_focal_point": "Escolha o ponto focal",
|
||||
"remove_media_confirmation": "Você confirma a remoção da imagem?",
|
||||
"download_flyer": "Baixar flyer"
|
||||
"download_flyer": "Baixar flyer",
|
||||
"address_description": "Qual é o endereço?",
|
||||
"address_description_osm": "Qual é o endereço? (contribuidores do <a href='http://osm.org/copyright'>OpenStreetMap</a>)"
|
||||
},
|
||||
"confirm": {
|
||||
"not_valid": "Algo deu errado.",
|
||||
|
@ -231,7 +257,7 @@
|
|||
"insert_your_address": "Informe seu endereço de e-mail",
|
||||
"feed_description": "Para seguir as atualizações de um computador ou smartphone sem que você precise acessar essa página periodicamente, utilize um feeds RSS. </p>\n\n<p> Com feeds RSS você pode utilizar um app especial para receber atualizações de páginas que te interessam. É uma boa maneira de seguir muitas páginas rapidamente, sem a necessidade de criar contas de usuários ou outras complicações. </p>\n\n<li> Se você possui um Android, recomendamos <a href=\"https://f-droid.org/en/packages/net.frju.flym/\">Flym</a> ou Feeder </li>\n<li> Para iPhone / iPad você pode utilizar <a href=\"https://itunes.apple.com/ua/app/feeds4u/id1038456442?mt=8\"> Feed4U </a> </li>\n<li> Para desktop / laptop nós recomendamos Feedbro, que pode ser instalado no <a href=\"https://addons.mozilla.org/en-GB/firefox/addon/feedbroreader/\"> Firefox </a> ou <a href=\"https://chrome.google.com/webstore/detail/feedbro/mefgmmbdailogpfhfblcnnjfmnpnmdfa\"> Chrome </a>. </li>\n<br/>\nAdicionado este link ao seu leitor de RSS irá mantê-lo atualizado.",
|
||||
"ical_description": "Computadores e smartphones normalmente possuem aplicações de calendário capazes de importar um calendário remoto.",
|
||||
"list_description": "Se você tem uma página e quer exibir uma lista de eventos, use o código seguinte",
|
||||
"list_description": "Se você tem uma página e quer exibir uma lista de eventos, use o seguinte código",
|
||||
"email_description": "Você pode obter os eventos que te interessam através de e-mail."
|
||||
},
|
||||
"oauth": {
|
||||
|
@ -263,7 +289,7 @@
|
|||
},
|
||||
"setup": {
|
||||
"https_warning": "Você está acessando por HTTP, lembre-se de alterar o valor de baseurl em config.json se você mudar para HTTPS!",
|
||||
"completed": "Configuração completada",
|
||||
"completed": "Configuração completa",
|
||||
"start": "Iniciar",
|
||||
"completed_description": "<p>Você pode agora autenticar-se com o seguinte usuário:<br/><br/>Usuário: <b>{email}</b><br/>Senha: <b>{password}<b/></p>",
|
||||
"copy_password_dialog": "Sim, você precisa copiar a senha!"
|
||||
|
|
333
locales/ru.json
Normal file
333
locales/ru.json
Normal file
|
@ -0,0 +1,333 @@
|
|||
{
|
||||
"common": {
|
||||
"send": "Отправить",
|
||||
"where": "Где",
|
||||
"address": "Адрес",
|
||||
"when": "Когда",
|
||||
"what": "Что",
|
||||
"media": "Медиа",
|
||||
"login": "Логин",
|
||||
"email": "E-mail",
|
||||
"password": "Пароль",
|
||||
"register": "Зарегистрироваться",
|
||||
"description": "Описание",
|
||||
"remove": "Удалить",
|
||||
"search": "Поиск",
|
||||
"edit": "Ред.",
|
||||
"info": "Инфо",
|
||||
"add_event": "Доб. событие",
|
||||
"hide": "Скрыть",
|
||||
"confirm": "Подтвердить",
|
||||
"admin": "Администратор",
|
||||
"users": "Пользователи",
|
||||
"events": "События",
|
||||
"places": "Места",
|
||||
"settings": "Опции",
|
||||
"actions": "Действия",
|
||||
"deactivate": "Выключить",
|
||||
"remove_admin": "Удалить администратора",
|
||||
"activate": "Активировать",
|
||||
"save": "Сохранить",
|
||||
"preview": "Предв. просмотр",
|
||||
"logout": "Выйти",
|
||||
"share": "Поделиться",
|
||||
"name": "Имя",
|
||||
"edit_event": "Ред. событие",
|
||||
"related": "Связанные",
|
||||
"add": "Доб.",
|
||||
"logout_ok": "Вышел из системы",
|
||||
"copy": "Копировать",
|
||||
"recover_password": "Восстановить пароль",
|
||||
"new_password": "Новый пароль",
|
||||
"new_user": "Новый пользователь",
|
||||
"ok": "Ок",
|
||||
"cancel": "Отмена",
|
||||
"enable": "Включить",
|
||||
"disable": "Отключить",
|
||||
"me": "Вы",
|
||||
"password_updated": "Пароль изменен.",
|
||||
"resources": "Ресурсы",
|
||||
"n_resources": "нет ресурсов|ресурс|{n} ресурсы",
|
||||
"activate_user": "Подтверждено",
|
||||
"send_via_mail": "Отправить e-mail письмо",
|
||||
"add_to_calendar": "Доб. в календарь",
|
||||
"copied": "Скопировано",
|
||||
"embed": "Встроить",
|
||||
"embed_title": "Вставьте это событие на свой сайт",
|
||||
"embed_help": "Скопируйте следующий код на свой сайт, и событие будет отображаться как здесь",
|
||||
"feed": "RSS-канал",
|
||||
"feed_url_copied": "Откройте скопированный URL канала в программе чтения RSS-каналов",
|
||||
"follow_me_title": "Следите за обновлениями от fediverse",
|
||||
"follow": "Подписаться",
|
||||
"moderation": "Модерация",
|
||||
"authorize": "Авторизация",
|
||||
"filter": "Фильтр",
|
||||
"start": "Начало",
|
||||
"fediverse": "Fediverse",
|
||||
"skip": "Пропустить",
|
||||
"delete": "Удалить",
|
||||
"import": "Импорт",
|
||||
"max_events": "N. максимум событий",
|
||||
"label": "Этикетка",
|
||||
"close": "Закрыть",
|
||||
"plugins": "Плагины",
|
||||
"home": "Главная",
|
||||
"content": "Содержание",
|
||||
"admin_actions": "Действия админа",
|
||||
"instances": "Экземпляры",
|
||||
"title": "Название",
|
||||
"set_password": "Установите пароль",
|
||||
"copy_link": "Копировать ссылку",
|
||||
"show_map": "Показать карту",
|
||||
"about": "О сайте",
|
||||
"event": "Событие",
|
||||
"url": "URL",
|
||||
"tags": "Теги",
|
||||
"theme": "Тема",
|
||||
"reset": "Сброс",
|
||||
"collections": "Коллекции",
|
||||
"help_translate": "Помогите перевести",
|
||||
"displayname": "Отображаемое имя",
|
||||
"federation": "Федерация",
|
||||
"announcements": "Объявления",
|
||||
"place": "Место",
|
||||
"user": "Пользователь",
|
||||
"pause": "Пауза",
|
||||
"tag": "Тег",
|
||||
"calendar": "Календарь",
|
||||
"next": "След.",
|
||||
"export": "Экспорт"
|
||||
},
|
||||
"login": {
|
||||
"description": "Войдя в систему, вы можете публиковать новые события.",
|
||||
"forgot_password": "Забыли пароль?",
|
||||
"insert_email": "Введите свой адрес электронной почты",
|
||||
"check_email": "Проверьте свой почтовый ящик и спам.",
|
||||
"not_registered": "Не зарегистрированы?",
|
||||
"error": "Не удалось войти в систему. Проверьте информацию для входа.",
|
||||
"ok": "Вошел в систему"
|
||||
},
|
||||
"event": {
|
||||
"each_2w": "Каждую вторую неделю",
|
||||
"due": "до",
|
||||
"from": "От",
|
||||
"image_too_big": "Изображение не может быть больше 4 МБ",
|
||||
"interact_with_me_at": "Общайтесь со мной на fediverse по адресу",
|
||||
"each_month": "Каждый месяц",
|
||||
"anon": "Анон",
|
||||
"same_day": "в тот же день",
|
||||
"what_description": "Название",
|
||||
"description_description": "Описание",
|
||||
"tag_description": "Тег",
|
||||
"media_description": "Вы можете добавить флаер (необязательно)",
|
||||
"added": "Событие добавлено",
|
||||
"saved": "Событие сохранено",
|
||||
"added_anon": "Событие добавлено, но еще не подтверждено.",
|
||||
"updated": "Событие обновлено",
|
||||
"where_description": "Где проходит мероприятие? Если его нет, вы можете его создать.",
|
||||
"address_description": "Какой адрес?",
|
||||
"address_description_osm": "Какой адрес? (<a href='http://osm.org/copyright'>OpenStreetMap</a> участники)",
|
||||
"confirmed": "Событие одобрено",
|
||||
"not_found": "Не удалось найти событие",
|
||||
"remove_confirmation": "Вы уверены, что хотите удалить это событие?",
|
||||
"recurrent": "Повторяющийся",
|
||||
"edit_recurrent": "Ред. повтор. события:",
|
||||
"show_recurrent": "повторяющ. события",
|
||||
"show_past": "также предшеств. события",
|
||||
"only_future": "только предстоящие события",
|
||||
"recurrent_description": "Выберите частоту и выберите дни",
|
||||
"multidate_description": "Это фестиваль? Выберите время начала и окончания",
|
||||
"multidate": "Больше дней",
|
||||
"normal": "Нормальный",
|
||||
"normal_description": "Выберите день.",
|
||||
"recurrent_1w_days": "Каждый {days}",
|
||||
"interact_with_me": "Подпишись",
|
||||
"remove_recurrent_confirmation": "Вы уверены, что хотите удалить это повторяющееся событие?\nPast events will be maintained, but no further events will be created.",
|
||||
"import_URL": "Импорт из URL",
|
||||
"import_ICS": "Импорт из ICS",
|
||||
"ics": "ICS",
|
||||
"alt_text_description": "Описание для людей с нарушениями зрения",
|
||||
"choose_focal_point": "Выберите фокусную точку",
|
||||
"remove_media_confirmation": "Вы подтверждаете удаление изображения?",
|
||||
"download_flyer": "Скачать флаер",
|
||||
"anon_description": "Вы можете добавить событие, не регистрируясь и не входя в систему, но вам придется подождать, пока кто-то его прочитает,\nподтверждение того, что это подходящее мероприятие. Изменить его будет невозможно.<br/><br/>\nВместо этого вы можете <a href='/login'>войти</a> или <a href='/register'>зарегистрироваться</a>. В противном случае действуйте и получите ответ как можно скорее. ",
|
||||
"each_week": "Каждую неделю",
|
||||
"follow_me_description": "Один из способов оставаться в курсе событий, публикуемых здесь на {title},\nследит за аккаунтом<u>{account}</u>из fediverse, например, через Mastodon, и, возможно, добавляет ресурсы в событие оттуда.<br/><br/>\nЕсли вы никогда не слышали о Mastodon и fediverse, рекомендуем прочитать <a href='https://www.savjee.be/videos/simply-explained/mastodon-and-fediverse-explained/'>эта статья</a>.<br/><br/>Введите ниже свой экземпляр (например, mastodon.social)",
|
||||
"import_description": "Вы можете импортировать события из других платформ и других инстанций с помощью стандартных форматов (ics и h-event)",
|
||||
"recurrent_1m_ordinal": "{n} {days} каждого месяца",
|
||||
"recurrent_2w_days": "Каждый второй {days}",
|
||||
"recurrent_1m_days": "Каждое {days} число месяца"
|
||||
},
|
||||
"admin": {
|
||||
"delete_resource_confirm": "Вы уверены, что хотите удалить этот ресурс?",
|
||||
"delete_tag_confirm": "Вы уверены, что хотите удалить тег \"{tag}\"? Метка будет удалена из {n} событий.",
|
||||
"block_user": "Заблок. пользователя",
|
||||
"filter_instances": "Фильтр экземпляров",
|
||||
"filter_users": "Фильтр пользователей",
|
||||
"user_blocked": "Пользователь {user} заблокирован",
|
||||
"user_block_confirm": "Вы уверены, что хотите заблокировать пользователя {user}?",
|
||||
"enable_resources_help": "Позволяет добавлять ресурсы в событие из федерации",
|
||||
"place_description": "Если вы ошиблись с местом или адресом, вы можете изменить его.<br/>Все текущие и прошлые события, связанные с этим местом, изменят адрес.",
|
||||
"hide_boost_bookmark": "Скрывает буст/закладки",
|
||||
"favicon": "Логотип",
|
||||
"resources": "Ресурсы",
|
||||
"disable_admin_user_confirm": "Вы уверены, что удалили права администратора у {user}?",
|
||||
"allow_anon_event": "Разрешить анонимные мероприятия (должно быть подтверждение)?",
|
||||
"recurrent_event_visible": "Показывать повторяющиеся события по умолчанию",
|
||||
"select_instance_timezone": "Часовой пояс",
|
||||
"event_confirm_description": "Вы можете подтвердить события, введенные анонимными пользователями, здесь",
|
||||
"delete_user": "Удалить",
|
||||
"remove_admin": "Удалить админа",
|
||||
"disable_user_confirm": "Вы уверены, что хотите отключить {user}?",
|
||||
"delete_user_confirm": "Вы уверены, что хотите удалить {user}?",
|
||||
"enable_admin_user_confirm": "Вы уверены, добавить права администратора для {user}?",
|
||||
"user_remove_ok": "Пользователь удален",
|
||||
"user_create_ok": "Пользователь создан",
|
||||
"event_remove_ok": "Событие удалено",
|
||||
"allow_registration_description": "Разрешить открытую регистрацию?",
|
||||
"allow_multidate_event": "Разрешить многодневные мероприятия",
|
||||
"allow_recurrent_event": "Разрешить повторяющиеся события",
|
||||
"allow_geolocation": "Разрешить геолокацию событий",
|
||||
"federation": "",
|
||||
"enable_federation": "Включить федерацию",
|
||||
"enable_federation_help": "За этим экземпляром можно будет следить из федиверса",
|
||||
"add_instance": "Доб. экземпляр",
|
||||
"enable_resources": "Включить ресурсы",
|
||||
"unblock": "Разблокировать",
|
||||
"block": "Блокировать",
|
||||
"user_add_help": "Новому пользователю будет отправлено электронное письмо с инструкциями по подтверждению подписки и выбору пароля",
|
||||
"instance_name": "Имя экземпляра",
|
||||
"show_resource": "Показать ресурс",
|
||||
"hide_resource": "Скрыть ресурс",
|
||||
"delete_resource": "Удалить ресурс",
|
||||
"delete_announcement_confirm": "Вы уверены, что хотите удалить объявление?",
|
||||
"instance_locale": "Язык по умолчанию",
|
||||
"domain": "Домен",
|
||||
"known_users": "Известные пользователи",
|
||||
"delete_footer_link_confirm": "Вы хотите удалить эту ссылку?",
|
||||
"new_collection": "Новая коллекция",
|
||||
"default_images_help": "Вы должны <a href='/admin?tab=theme'>обновить</a> страницу, чтобы увидеть изменения.",
|
||||
"delete_collection_confirm": "Вы уверены, что хотите удалить коллекцию <u>{collection}</u>?",
|
||||
"geocoding_provider_type_help": "Программное обеспечение по умолчанию - Nominatim",
|
||||
"created_at": "Создан в",
|
||||
"instance_block_confirm": "Вы уверены, что хотите блокировать экземпляр {instance}?",
|
||||
"announcement_remove_ok": "Объявление удалено",
|
||||
"description_description": "Появляется в заголовке рядом с названием",
|
||||
"trusted_instances_label_default": "Дружественные экземпляры",
|
||||
"blocked": "Блокирован",
|
||||
"geocoding_provider_type": "Программа для геокодирования",
|
||||
"geocoding_provider": "Провайдер геокодирования",
|
||||
"geocoding_provider_help": "Поставщиком по умолчанию является Nominatim",
|
||||
"geolocation": "Геолокация",
|
||||
"title_description": "Он используется в заголовке страницы, в теме письма для экспорта RSS- и ICS-каналов.",
|
||||
"instance_place": "Ориентировочное местонахождение данного экземпляра",
|
||||
"instance_name_help": "Аккаунт ActivityPub, для подписки",
|
||||
"enable_trusted_instances": "Включите дружественные экземпляры",
|
||||
"trusted_instances_label": "Навигационная метка для дружественных экземпляров",
|
||||
"trusted_instances_label_help": "Метка по умолчанию в 'Дружеств. экземплярах'",
|
||||
"add_trusted_instance": "Доб. дружественный экземпляр",
|
||||
"instance_place_help": "Метка для отображения в других экземплярах",
|
||||
"is_dark": "Темная тема",
|
||||
"add_link": "Добавить ссылку",
|
||||
"footer_links": "Ссылки в нижнем колонтитуле",
|
||||
"edit_place": "Ред. место",
|
||||
"edit_tag": "Ред. тег",
|
||||
"new_announcement": "Новое объявление",
|
||||
"show_smtp_setup": "Настройки электронной почты",
|
||||
"smtp_hostname": "Имя хоста SMTP",
|
||||
"smtp_port": "Порт SMTP",
|
||||
"smtp_test_success": "Тестовое письмо отправлено на {admin_email}, пожалуйста, проверьте свой почтовый ящик",
|
||||
"smtp_test_button": "Отправьте тестовое электронное письмо",
|
||||
"smtp_use_sendmail": "Используйте sendmail",
|
||||
"admin_email": "Электронная почта администратора",
|
||||
"admin_email_help": "Адрес, который мы используем в качестве отправителя для отправки электронных писем. Это также адрес, на который отправляются электронные письма администраторов",
|
||||
"widget": "Виджет",
|
||||
"wrong_domain_warning": "Baseurl, настроенный в config.json <b>({baseurl})</b> отличается от того, который вы посещаете <b>({url})</b>",
|
||||
"collections_description": "Коллекции - это группировка событий по тегам и местам. Они будут отображаться на главной странице",
|
||||
"edit_collection": "Ред. коллекцию",
|
||||
"config_plugin": "Конфигурация плагина",
|
||||
"header_image": "Изображение заголовка",
|
||||
"hide_thumbs": "Скрыть превью",
|
||||
"hide_calendar": "Скрыть календарь",
|
||||
"default_images": "Изображения по умолчанию",
|
||||
"geocoding_countrycodes": "Коды стран",
|
||||
"geocoding_countrycodes_help": "Позволяет установить фильтр для поиска на основе кодов городов",
|
||||
"geocoding_test_button": "Тест геокодирования",
|
||||
"geocoding_test_success": "Служба геокодирования на {service_name} работает",
|
||||
"geocoding_test_error": "Служба геокодирования недоступна по адресу {service_name}",
|
||||
"tilelayer_provider_attribution": "Атрибуция",
|
||||
"announcement_description": "В этом разделе вы можете вставлять объявления, которые будут оставаться на главной странице",
|
||||
"delete_trusted_instance_confirm": "Вы действительно хотите удалить этот пункт из меню дружественных экземпляров?",
|
||||
"edit_tag_help": "Вы можете изменить тег, заменив его новым или объединив с существующим. Связанные с ним {n} события также будут изменены.",
|
||||
"instance_timezone_description": "Gancio предназначен для сбора событий определенного места, например, города. Все события в этом месте будут отображаться в выбранном для него часовом поясе.",
|
||||
"instance_locale_description": "Предпочитаемый язык пользователя для страниц. Иногда сообщения должны отображаться на одном языке для всех (например, при публикации через ActivityPub или при отправке некоторых электронных писем). В этих случаях будет использоваться язык, выбранный выше.",
|
||||
"trusted_instances_help": "Список дружественных экземпляров будет показан в заголовке",
|
||||
"smtp_description": "<ul><li>Администратор должен получать электронное письмо при добавлении события анонса (если включено).</li><li>Администратор должен получить электронное письмо с запросом на регистрацию (если включено).</li><li>Пользователь должен получить электронное письмо с запросом на регистрацию.</li><li>Пользователь должен получить электронное письмо с подтверждением регистрации.</li><li>Пользователь должен получить подтверждение по электронной почте при подписке непосредственно администратором.</li><li>Пользователи должны получать электронное письмо для восстановления пароля, если они его забыли</li></ul>",
|
||||
"hide_boost_bookmark_help": "Скрывает маленькие иконки, показывающие количество бустов и закладок, поступающих из fediverse"
|
||||
},
|
||||
"recover": {
|
||||
"not_valid_code": "Что-то пошло не так."
|
||||
},
|
||||
"export": {
|
||||
"email_description": "Вы можете получать интересующие вас события на электронной почте.",
|
||||
"insert_your_address": "Введите свой адрес электронной почты",
|
||||
"ical_description": "Компьютеры и смартфоны обычно оснащены приложением календаря, способным импортировать календарь (ical файл).",
|
||||
"list_description": "Если у вас есть веб-сайт и вы хотите показать список событий, используйте следующий код",
|
||||
"intro": "В отличие от несоциальных платформ, которые делают все, чтобы удержать пользователей и данные о них, мы считаем, что информация, как и люди, должна быть свободной. Для этого вы можете оставаться в курсе нужных вам событий, без обязательного посещения этого сайта.",
|
||||
"feed_description": "Чтобы следить за обновлениями с компьютера или смартфона без необходимости периодически открывать этот сайт, используйте RSS-каналы.\n\n<p> С помощью RSS-каналов вы используете специальное приложение для получения обновлений с интересующих вас сайтов. Это хороший способ быстро следить за многими сайтами, без необходимости создавать учетную запись или других сложностей. </p>\n\n<li> Если у вас Android, мы рекомендуем <a href=\"https://f-droid.org/en/packages/net.frju.flym/\">Flym</a> или Feeder </li>\n<li> Для iPhone / iPad вы можете использовать <a href=\"https://itunes.apple.com/ua/app/feeds4u/id1038456442?mt=8\"> Feed4U </a> </li>\n<li> Для настольных компьютеров / ноутбуков мы рекомендуем Feedbro, который должен быть установлен на <a href=\"https://addons.mozilla.org/en-GB/firefox/addon/feedbroreader/\"> Firefox </a> или <a href=\"https://chrome.google.com/webstore/detail/feedbro/mefgmmbdailogpfhfblcnnjfmnpnmdfa\"> Chrome </a>. </li>\n<br/>\nДобавив эту ссылку в свой RSS-канал, вы всегда будете в курсе последних событий."
|
||||
},
|
||||
"register": {
|
||||
"error": "Ошибка: ",
|
||||
"complete": "Регистрация должна быть подтверждена.",
|
||||
"first_user": "Администратор создан",
|
||||
"description": "Общественные движения должны организовываться и самофинансироваться.<br/>\n<br/>Перед публикацией, <strong> аккаунт должен быть подтвержден</strong>, подумайте о том, что <strong> на этом сайте вы найдете реальных людей</strong>, поэтому напишите две строчки, чтобы сообщить нам, какие события вы хотели бы опубликовать."
|
||||
},
|
||||
"setup": {
|
||||
"copy_password_dialog": "Да, вы можете скопировать пароль!",
|
||||
"https_warning": "Вы посещаете сайт с HTTP, не забудьте изменить baseurl в config.json, если вы перейдете на HTTPS!",
|
||||
"start": "Начало",
|
||||
"completed_description": "<p>Вы можете войти в систему под следующим пользователем:<br/><br/>Логин: <b>{email}</b><br/>Пароль: <b>{password}<b/></p>",
|
||||
"completed": "Установка завершена"
|
||||
},
|
||||
"confirm": {
|
||||
"title": "Подтверждение пользователя",
|
||||
"not_valid": "Что-то пошло не так.",
|
||||
"valid": "Ваша учетная запись подтверждена, теперь вы можете <a href=\"/login\">войти</a>"
|
||||
},
|
||||
"settings": {
|
||||
"password_updated": "Пароль изменен.",
|
||||
"update_confirm": "Хотите ли вы сохранить свою изменение?",
|
||||
"change_password": "Изменить пароль",
|
||||
"danger_section": "Опасная опция",
|
||||
"remove_account": "При нажатии следующей кнопки ваша учетная запись пользователя будет удалена. Опубликованные вами события не будут удалены.",
|
||||
"remove_account_confirm": "Вы собираетесь навсегда удалить свой аккаунт"
|
||||
},
|
||||
"error": {
|
||||
"email_taken": "Эта электронная почта уже используется.",
|
||||
"nick_taken": "Это имя пользователя уже используется."
|
||||
},
|
||||
"auth": {
|
||||
"not_confirmed": "Не подтверждено…",
|
||||
"fail": "Не удалось войти в систему. Вы уверены, что пароль правильный?"
|
||||
},
|
||||
"validators": {
|
||||
"required": "{fieldName} заполните",
|
||||
"email": "Доб. действующий адрес электронной почты"
|
||||
},
|
||||
"oauth": {
|
||||
"authorization_request": "Приложение <code>{app}</code> запрашивает следующую авторизацию на <code>{instance_name}</code>:",
|
||||
"scopes": {
|
||||
"event:write": "Добавляйте и редактируйте свои события"
|
||||
},
|
||||
"redirected_to": "После подтверждения вы будете перенаправлены на <code>{url}</code>"
|
||||
},
|
||||
"about": "\n <p><a href='https://gancio.org'>Gancio</a> это афиша для местных сообществ.</p>\n ",
|
||||
"ordinal": {
|
||||
"1": "первый",
|
||||
"2": "второй",
|
||||
"3": "третий",
|
||||
"4": "четвертый",
|
||||
"5": "пятый",
|
||||
"-1": "последний"
|
||||
}
|
||||
}
|
|
@ -1,293 +0,0 @@
|
|||
{
|
||||
"common": {
|
||||
"embed_help": "将以下代码复制并粘贴到你的网站,此事件将像这样显示",
|
||||
"follow": "关注",
|
||||
"add_event": "添加事件",
|
||||
"send": "发送",
|
||||
"where": "地点",
|
||||
"address": "地址",
|
||||
"when": "时间",
|
||||
"what": "事件",
|
||||
"media": "媒体",
|
||||
"login": "登录",
|
||||
"email": "电子邮箱",
|
||||
"register": "注册",
|
||||
"description": "描述",
|
||||
"hide": "隐藏",
|
||||
"search": "搜索",
|
||||
"confirm": "确认",
|
||||
"admin": "管理员",
|
||||
"users": "用户",
|
||||
"events": "事件",
|
||||
"actions": "操作",
|
||||
"deactivate": "取消",
|
||||
"remove_admin": "移除管理员",
|
||||
"activate": "激活",
|
||||
"save": "该村",
|
||||
"logout": "登出",
|
||||
"name": "名称",
|
||||
"associate": "合作者",
|
||||
"add": "添加",
|
||||
"recover_password": "重置密码",
|
||||
"enable": "启用",
|
||||
"me": "你",
|
||||
"ok": "完成",
|
||||
"resources": "资源",
|
||||
"n_resources": "无资源|1 个资源|{n} 个资源",
|
||||
"displayname": "显示名称",
|
||||
"copy_link": "复制链接",
|
||||
"send_via_mail": "发送电子邮件",
|
||||
"embed": "嵌入式页面",
|
||||
"feed_url_copied": "在你的 RSS 阅读器中打开复制的链接",
|
||||
"follow_me_title": "在 Fediverse 网络中关注更新",
|
||||
"feed": "RSS 源",
|
||||
"moderation": "中等",
|
||||
"authorize": "认证",
|
||||
"title": "标题",
|
||||
"filter": "筛选",
|
||||
"pause": "暂停",
|
||||
"start": "开始",
|
||||
"announcements": "公告",
|
||||
"url": "URL",
|
||||
"place": "地点",
|
||||
"theme": "主题",
|
||||
"label": "标签",
|
||||
"collections": "收藏",
|
||||
"max_events": "最大事件数",
|
||||
"next": "下一个",
|
||||
"export": "导出",
|
||||
"remove": "移除",
|
||||
"settings": "选项",
|
||||
"logout_ok": "已登出",
|
||||
"new_password": "新密码",
|
||||
"new_user": "新用户",
|
||||
"places": "地点",
|
||||
"edit": "编辑",
|
||||
"cancel": "取消",
|
||||
"password": "密码",
|
||||
"info": "信息",
|
||||
"preview": "预览",
|
||||
"share": "分享",
|
||||
"edit_event": "编辑事件",
|
||||
"copy": "复制",
|
||||
"related": "相关",
|
||||
"set_password": "设置密码",
|
||||
"instances": "实例",
|
||||
"activate_user": "已确认",
|
||||
"federation": "联盟",
|
||||
"add_to_calendar": "添加到日历",
|
||||
"copied": "已复制",
|
||||
"embed_title": "在你的网页上嵌入此事件",
|
||||
"user": "用户",
|
||||
"event": "事件",
|
||||
"fediverse": "Fediverse 网络",
|
||||
"skip": "跳过",
|
||||
"delete": "移除",
|
||||
"import": "导入",
|
||||
"tags": "标签",
|
||||
"close": "关闭",
|
||||
"disable": "禁用",
|
||||
"password_updated": "密码已修改。",
|
||||
"reset": "重置"
|
||||
},
|
||||
"export": {
|
||||
"list_description": "如果你有一个网站并希望展示一个事件列表,使用以下代码",
|
||||
"email_description": "你可以通过发给你的电子邮件了解你感兴趣的事件。",
|
||||
"insert_your_address": "输入你的电子邮箱地址",
|
||||
"ical_description": "电脑和智能手机通常预装了能够导入远程日历的日历应用。",
|
||||
"intro": "与那些竭尽全力保留用户和数据的非社交平台不同,我们认为信息和人一样,都必须是自由的。因此,即使不通过此网站,你仍可随时了解你想了解的事件之最新情况。",
|
||||
"feed_description": "要想从电脑或智能手机上关注更新,而无需定期打开本网站,请使用RSS订阅。</p>\n\n<p>通过RSS订阅,您可以使用一个特殊的应用程序来接收你感兴趣的网站的更新。这是一个快速关注许多网站的好方法,不需要创建账户或做其他事。</p>\n\n<li> 如果您使用 Android,我们推荐 <a href=\"https://f-droid.org/en/packages/net.frju.flym/\">Flym</a> 或 Feeder。</li>\n<li> 对于iPhone/iPad,您可以使用 <a href=\"https://itunes.apple.com/ua/app/feeds4u/id1038456442?mt=8\">Feed4U</a> </li>\n<li> 对于台式机/笔记本电脑,我们推荐可在 <a href=\"https://addons.mozilla.org/en-GB/firefox/addon/feedbroreader/\">Firefox</a> 或 <a href=\"https://chrome.google.com/webstore/detail/feedbro/mefgmmbdailogpfhfblcnnjfmnpnmdfa\">Chrome</a> 上安装的 Feedbro。</li>\n<br/>\n将此链接添加到你的RSS阅读器中,以获得最新信息。"
|
||||
},
|
||||
"register": {
|
||||
"description": "社会运动应该组织起来,并自筹资金<br/>\n<br/>在你能发布内容之前,<strong>你的账户必须审核通过</strong>,考虑到<strong>你可以通过此网站发现现实中的人</strong>,请写一些东西告诉我们你希望发布什么。",
|
||||
"error": "错误: ",
|
||||
"complete": "注册必须经过确认。",
|
||||
"first_user": "管理员已创建"
|
||||
},
|
||||
"event": {
|
||||
"anon_description": "即使不登录或注册,你也可以创建事件,但必须等待一些人看到它,\n并确认这是一个合适的事件。此外你也不能够修改它。<br/><br/>\n你也可以 <a href='/login'>登录</a> 或 <a href='/register'>注册</a>。或者继续浏览以得到答案。 ",
|
||||
"anon": "匿名",
|
||||
"same_day": "在同一天",
|
||||
"what_description": "标题",
|
||||
"description_description": "描述",
|
||||
"added": "事件已添加",
|
||||
"saved": "事件已保存",
|
||||
"added_anon": "事件已添加,等待确认。",
|
||||
"updated": "事件已更新",
|
||||
"where_description": "事件的地点在哪里?如果不存在,你可以创建一个。",
|
||||
"confirmed": "事件已确认",
|
||||
"not_found": "找不到事件",
|
||||
"recurrent": "日常事件",
|
||||
"edit_recurrent": "编辑日常事件:",
|
||||
"show_recurrent": "日常事件",
|
||||
"show_past": "以及过往的事件",
|
||||
"multidate_description": "这是一个节日吗?选择它开始和结束的时间",
|
||||
"multidate": "更多日期",
|
||||
"normal_description": "选择日期。",
|
||||
"recurrent_2w_days": "每 {days} 天一次",
|
||||
"each_week": "每周",
|
||||
"each_2w": "隔周一次",
|
||||
"due": "直到",
|
||||
"from": "来自",
|
||||
"image_too_big": "图片不能大于 4MB",
|
||||
"interact_with_me_at": "在 Fediverse 网络上与我互动",
|
||||
"interact_with_me": "关注我",
|
||||
"remove_recurrent_confirmation": "你确定要移除这个日常事件吗?\n过去的事件仍将被维护,但不会再添加新事件。",
|
||||
"import_URL": "从 URL 导入",
|
||||
"import_ICS": "从 ICS 导入",
|
||||
"ics": "ICS",
|
||||
"alt_text_description": "为视觉障碍者提供的说明",
|
||||
"choose_focal_point": "选择联络点",
|
||||
"download_flyer": "下载传单",
|
||||
"tag_description": "标签",
|
||||
"media_description": "你可以添加一份传单(可选)",
|
||||
"recurrent_description": "选择频率和日期",
|
||||
"only_future": "仅限即将到来的事件",
|
||||
"normal": "普通",
|
||||
"recurrent_1w_days": "每 {days} 天",
|
||||
"recurrent_1m_days": "|每月的第 {days} 天|每月的第 {days} 天",
|
||||
"recurrent_1m_ordinal": "每月的第 {n} 个 {days}",
|
||||
"each_month": "每月",
|
||||
"follow_me_description": "一种对这里发布的 {title} 事件保持关注的方法,\n是在 Fediverse 网络,比如 Mastodon,上关注 <u>{account}</u>,亦有可能通过此方式给此事件添加资源。<br/><br/>\n如果你没听说过 Mastodon 和 Fediverse,我们建议你阅读 <a href='https://www.savjee.be/videos/simply-explained/mastodon-and-fediverse-explained/'>这篇文章</a>。<br/><br/>在下方输入你的实例名称(例如:mastodon.social)",
|
||||
"import_description": "你可以从其他平台和实例通过标准格式(ICS 和 hCalendar)导入事件",
|
||||
"remove_media_confirmation": "你确认要删除图片吗?",
|
||||
"remove_confirmation": "你确定要移除此事件吗?"
|
||||
},
|
||||
"login": {
|
||||
"check_email": "检查你的电子邮箱收件箱和垃圾邮件箱。",
|
||||
"not_registered": "还未注册?",
|
||||
"forgot_password": "忘记密码了?",
|
||||
"insert_email": "输入你的电子邮箱地址",
|
||||
"error": "无法登录,检查你的登录信息。",
|
||||
"ok": "已登录",
|
||||
"description": "登录以发布新事件。"
|
||||
},
|
||||
"recover": {
|
||||
"not_valid_code": "发生了一些错误。"
|
||||
},
|
||||
"admin": {
|
||||
"delete_user": "移除",
|
||||
"remove_admin": "移除管理员",
|
||||
"disable_user_confirm": "你确定禁用 {user} 吗?",
|
||||
"disable_admin_user_confirm": "你确定移除 {user} 的管理员权限吗?",
|
||||
"user_remove_ok": "用户已移除",
|
||||
"user_create_ok": "用户已创建",
|
||||
"event_remove_ok": "事件已移除",
|
||||
"allow_registration_description": "允许公众注册?",
|
||||
"allow_anon_event": "允许发布匿名事件(需要确认)?",
|
||||
"allow_recurrent_event": "允许日常事件",
|
||||
"federation": "联邦社交网络 / ActivityPub",
|
||||
"enable_federation": "启用联邦社交网络",
|
||||
"add_instance": "添加实例",
|
||||
"select_instance_timezone": "时区",
|
||||
"enable_resources": "启用资源",
|
||||
"hide_boost_bookmark": "隐藏助力/书签",
|
||||
"block": "屏蔽",
|
||||
"unblock": "解除屏蔽",
|
||||
"instance_name": "实例名称",
|
||||
"hide_resource": "隐藏资源",
|
||||
"delete_resource_confirm": "你确定要删除此资源吗?",
|
||||
"filter_instances": "筛选实例",
|
||||
"resources": "资源",
|
||||
"favicon": "图标",
|
||||
"user_block_confirm": "你确定要屏蔽用户 {user} 吗?",
|
||||
"delete_announcement_confirm": "你确定要移除这个公告吗?",
|
||||
"announcement_remove_ok": "公告已移除",
|
||||
"instance_locale": "默认语言",
|
||||
"title_description": "这将被用作页面的标题和电子邮件的主题,以导出 RSS 和 ICS 源。",
|
||||
"description_description": "在标题旁的页眉中显示",
|
||||
"instance_place_help": "在其他的实例中显示的标签",
|
||||
"delete_trusted_instance_confirm": "你确定要从友好实例菜单中删除此项目吗?",
|
||||
"edit_place": "编辑地点",
|
||||
"new_announcement": "新公告",
|
||||
"show_smtp_setup": "电子邮件设置",
|
||||
"smtp_port": "SMTP 端口",
|
||||
"smtp_secure": "SMTP 安全协议(TLS 或 STARTTLS)",
|
||||
"smtp_test_success": "一封测试邮件已发送至 {admin_mail},请检查你的收件箱",
|
||||
"sender_email": "发件人",
|
||||
"widget": "小组件",
|
||||
"wrong_domain_warning": "在 config.json 中设置的 baseurl <b>{baseurl}</b> 与你正在访问的 <b>{url}</b> 不同",
|
||||
"edit_collection": "编辑收藏",
|
||||
"event_confirm_description": "你可以在此确认匿名用户提交的事件",
|
||||
"recurrent_event_visible": "默认显示日常事件",
|
||||
"place_description": "如果你弄错了地点或地址,你可以修改。<br/>与这个地点相关的当前和过去的所有事件都会改变地址。",
|
||||
"delete_user_confirm": "你确定移除 {user} 吗?",
|
||||
"enable_admin_user_confirm": "你确定授予 {user} 管理员权限吗",
|
||||
"enable_federation_help": "这将允许从 Fediverse 上关注此实例",
|
||||
"enable_resources_help": "允许从 Fediverse 为此事件添加资源",
|
||||
"hide_boost_bookmark_help": "隐藏来自 Fediverse 上的助力和书签数量图标",
|
||||
"block_user": "屏蔽用户",
|
||||
"filter_users": "筛选用户",
|
||||
"user_add_help": "一封带有确认订阅和设置密码指引的邮件将被发送给新用户",
|
||||
"show_resource": "显示资源",
|
||||
"delete_resource": "删除资源",
|
||||
"user_blocked": "用户 {user} 已屏蔽",
|
||||
"instance_block_confirm": "你确定要屏蔽实例 {instance} 吗?",
|
||||
"announcement_description": "你可以在此段落插入显示于首页的公告",
|
||||
"instance_timezone_description": "Gancio 被设计用来收集特定区域,比如一座城市所发生的事件。这里的所有事件将以所选择的时区显示。",
|
||||
"instance_locale_description": "特定页面偏好的用户语言。有时信息必须以相同的语言对所有人显示(比如通过 ActivityPub 发布内容或发送一些电子邮件时)。在这种情况下将使用上面选择的语言。",
|
||||
"instance_place": "该实例的指示性位置",
|
||||
"trusted_instances_help": "友好实例的列表将被显示于页眉",
|
||||
"footer_links": "页脚链接",
|
||||
"smtp_description": "<ul><li>当匿名事件被添加时(如果启用),管理员应当收到邮件</li>管理员应当会受到注册请求邮件(如果启用)。<li></li><li>用户应当会受到注册请求邮件。</li><li>用户应当收到注册确认邮件。</li><li>当管理员直接订阅时,用户应当受到邮件。</li><li>用户忘记密码时应当收到密码重置邮件。</li></ul>",
|
||||
"smtp_use_sendmail": "使用 sendmail",
|
||||
"smtp_test_button": "发送测试邮件",
|
||||
"new_collection": "新建收藏",
|
||||
"collections_description": "收藏是按标签和地点分组的事件。它们将于主页上显示",
|
||||
"enable_trusted_instances": "启用友好实例",
|
||||
"add_trusted_instance": "添加一个友好实例",
|
||||
"add_link": "添加链接",
|
||||
"delete_footer_link_confirm": "确定移除此链接吗?",
|
||||
"instance_name_help": "要关注的 ActivityPub 账号",
|
||||
"is_dark": "暗色主题",
|
||||
"smtp_hostname": "SMTP 主机名"
|
||||
},
|
||||
"auth": {
|
||||
"fail": "无法登录。你确定密码正确吗?",
|
||||
"not_confirmed": "尚未确认……"
|
||||
},
|
||||
"settings": {
|
||||
"change_password": "修改密码",
|
||||
"password_updated": "密码已修改。",
|
||||
"remove_account_confirm": "你即将永久删除你的账号",
|
||||
"update_confirm": "你希望保存你的修改吗?",
|
||||
"remove_account": "按下下方的按钮后你的账号将被删除。你发布的事件不会删除。",
|
||||
"danger_section": "危险段落"
|
||||
},
|
||||
"error": {
|
||||
"email_taken": "此电子邮箱地址已被使用。",
|
||||
"nick_taken": "此昵称已被使用。"
|
||||
},
|
||||
"confirm": {
|
||||
"title": "用户确认",
|
||||
"not_valid": "出现了一些错误。",
|
||||
"valid": "你的账户已被确认,你现在可以 <a href=\"/login\">登录</a>"
|
||||
},
|
||||
"ordinal": {
|
||||
"4": "第四",
|
||||
"5": "第五",
|
||||
"2": "第二",
|
||||
"-1": "最后",
|
||||
"1": "第一",
|
||||
"3": "第三"
|
||||
},
|
||||
"validators": {
|
||||
"required": "{fieldName} 是必填项",
|
||||
"email": "输入有效的电子邮箱地址"
|
||||
},
|
||||
"oauth": {
|
||||
"authorization_request": "应用 <code>{app}</code> 申请在 <code>{instance_name}</code> 上获得以下权限:",
|
||||
"redirected_to": "在确认后你将被重定向到 <code>{url}</code>",
|
||||
"scopes": {
|
||||
"event:write": "添加与编辑你的事件"
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"completed_description": "<p>你现在可以以以下用户登录<br/><br/>用户名:<b>{email}</b><br/>密码:<b>{password}<b/></p>",
|
||||
"copy_password_dialog": "没错,你必须复制密码!",
|
||||
"start": "开始",
|
||||
"https_warning": "你正在使用 HTTP 访问,如果你切换到 HTTPS,记得在 config.json 中修改 baseurl!",
|
||||
"completed": "安装完成"
|
||||
},
|
||||
"about": "\n <p><a href='https://gancio.org'>Gancio</a> 是为本地社区设计的的共享日程表。</p>\n "
|
||||
}
|
|
@ -2,7 +2,7 @@ const config = require('./server/config.js')
|
|||
const minifyTheme = require('minify-css-string').default
|
||||
const locales = require('./locales/index')
|
||||
|
||||
import { ca, de, en, es, eu, fr, gl, it, nb, pl, pt, sk, zhHans } from 'vuetify/lib/locale'
|
||||
import { ca, de, en, es, eu, fr, gl, it, nb, pl, pt, sk, ru, zhHans } from 'vuetify/lib/locale'
|
||||
|
||||
const isDev = (process.env.NODE_ENV !== 'production')
|
||||
module.exports = {
|
||||
|
@ -142,7 +142,7 @@ module.exports = {
|
|||
},
|
||||
buildModules: ['@nuxtjs/vuetify'],
|
||||
vuetify: {
|
||||
lang: { locales: { ca, de, en, es, eu, fr, gl, it, nb, pl, pt, sk, zhHans } },
|
||||
lang: { locales: { ca, de, en, es, eu, fr, gl, it, nb, pl, pt, sk, ru, zhHans } },
|
||||
treeShake: true,
|
||||
theme: {
|
||||
options: {
|
||||
|
|
28
package.json
28
package.json
|
@ -1,15 +1,15 @@
|
|||
{
|
||||
"name": "gancio",
|
||||
"version": "1.6.0-rc2",
|
||||
"version": "1.6.2",
|
||||
"description": "A shared agenda for local communities",
|
||||
"author": "lesion",
|
||||
"scripts": {
|
||||
"build": "nuxt build --modern",
|
||||
"start:inspect": "NODE_ENV=production node --inspect node_modules/.bin/nuxt start --modern",
|
||||
"dev": "nuxt dev",
|
||||
"test-sqlite": "export NODE_ENV=test; export DB=sqlite; jest --bail=1",
|
||||
"test-mariadb": "export NODE_ENV=test; export DB=mariadb; jest --bail=1",
|
||||
"test-postgresql": "export NODE_ENV=test; export DB=postgresql; jest --bail=1",
|
||||
"test-sqlite": "export NODE_ENV=test; export DB=sqlite; jest --testEnvironment=jest-environment-node --bail=1",
|
||||
"test-mariadb": "export NODE_ENV=test; export DB=mariadb; jest --testEnvironment=jest-environment-node --bail=1",
|
||||
"test-postgresql": "export NODE_ENV=test; export DB=postgresql; jest --testEnvironment=jest-environment-node --bail=1",
|
||||
"start": "nuxt start --modern",
|
||||
"doc": "cd docs && bundle exec jekyll b",
|
||||
"doc:dev": "cd docs && bundle exec jekyll s --drafts",
|
||||
|
@ -34,7 +34,7 @@
|
|||
"node": ">=14 <=16"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdi/js": "^7.0.96",
|
||||
"@mdi/js": "^7.1.96",
|
||||
"@nuxtjs/auth": "^4.9.1",
|
||||
"@nuxtjs/axios": "^5.13.5",
|
||||
"@nuxtjs/i18n": "^7.3.0",
|
||||
|
@ -47,15 +47,15 @@
|
|||
"cookie-session": "^2.0.0",
|
||||
"cookie-universal-nuxt": "^2.2.2",
|
||||
"cors": "^2.8.5",
|
||||
"dayjs": "^1.11.5",
|
||||
"dayjs": "^1.11.7",
|
||||
"dompurify": "^2.4.1",
|
||||
"email-templates": "^10.0.1",
|
||||
"express": "^4.18.1",
|
||||
"express-rate-limit": "^6.7.0",
|
||||
"http-signature": "^1.3.6",
|
||||
"https-proxy-agent": "^5.0.1",
|
||||
"ical.js": "^1.5.0",
|
||||
"ics": "^2.40.0",
|
||||
"jest-environment-jsdom": "^29.3.1",
|
||||
"jsdom": "^20.0.2",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"leaflet": "^1.9.2",
|
||||
|
@ -63,6 +63,7 @@
|
|||
"linkifyjs": "4.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"mariadb": "^3.0.1",
|
||||
"memory-cache": "^0.2.0",
|
||||
"microformat-node": "^2.0.1",
|
||||
"minify-css-string": "^1.0.0",
|
||||
"mkdirp": "^1.0.4",
|
||||
|
@ -78,17 +79,17 @@
|
|||
"passport-oauth2-client-password": "^0.1.2",
|
||||
"passport-oauth2-client-public": "^0.0.1",
|
||||
"pg": "^8.8.0",
|
||||
"sequelize": "^6.25.6",
|
||||
"sequelize": "^6.28.0",
|
||||
"sequelize-slugify": "^1.6.2",
|
||||
"sharp": "^0.27.2",
|
||||
"sqlite3": "^5.0.11",
|
||||
"sqlite3": "^5.1.4",
|
||||
"telegraf": "^4.9.1",
|
||||
"tiptap": "^1.32.0",
|
||||
"tiptap-extensions": "^1.35.0",
|
||||
"umzug": "^2.3.0",
|
||||
"v-calendar": "^2.4.1",
|
||||
"vue2-leaflet": "^2.7.1",
|
||||
"vuetify": "2.6.12",
|
||||
"vuetify": "2.6.13",
|
||||
"winston": "^3.8.2",
|
||||
"winston-daily-rotate-file": "^4.7.1",
|
||||
"yargs": "^17.5.0"
|
||||
|
@ -96,12 +97,13 @@
|
|||
"devDependencies": {
|
||||
"@nuxtjs/vuetify": "^1.12.3",
|
||||
"jest": "^29.3.1",
|
||||
"prettier": "^2.7.1",
|
||||
"jest-environment-node": "^29.3.1",
|
||||
"prettier": "^2.8.1",
|
||||
"pug": "^3.0.2",
|
||||
"pug-plain-loader": "^1.1.0",
|
||||
"sass": "^1.56.1",
|
||||
"sass": "^1.56.2",
|
||||
"sequelize-cli": "^6.3.0",
|
||||
"supertest": "^6.2.4",
|
||||
"supertest": "^6.3.3",
|
||||
"webpack": "4",
|
||||
"webpack-cli": "^4.10.0"
|
||||
},
|
||||
|
|
|
@ -26,6 +26,18 @@ v-container.container.pa-0.pa-md-3
|
|||
v-tab-item(value='places')
|
||||
Places
|
||||
|
||||
//- TAGS
|
||||
v-tab(href='#tags') {{$t('common.tags')}}
|
||||
v-tab-item(value='tags')
|
||||
Tags
|
||||
|
||||
//- GEOCODING / MAPS
|
||||
v-tab(href='#geolocation' v-if='settings.allow_geolocation') {{$t('admin.geolocation')}}
|
||||
v-tab-item(value='geolocation')
|
||||
client-only(placeholder='Loading...')
|
||||
Geolocation
|
||||
|
||||
|
||||
//- Collections
|
||||
v-tab(href='#collections') {{$t('common.collections')}}
|
||||
v-tab-item(value='collections')
|
||||
|
@ -70,7 +82,9 @@ export default {
|
|||
Users: () => import(/* webpackChunkName: "admin" */'../components/admin/Users'),
|
||||
Events: () => import(/* webpackChunkName: "admin" */'../components/admin/Events'),
|
||||
Places: () => import(/* webpackChunkName: "admin" */'../components/admin/Places'),
|
||||
Tags: () => import(/* webpackChunkName: "admin" */'../components/admin/Tags'),
|
||||
Collections: () => import(/* webpackChunkName: "admin" */'../components/admin/Collections'),
|
||||
[process.client && 'Geolocation']: () => import(/* webpackChunkName: "admin" */'../components/admin/Geolocation.vue'),
|
||||
Federation: () => import(/* webpackChunkName: "admin" */'../components/admin/Federation.vue'),
|
||||
Moderation: () => import(/* webpackChunkName: "admin" */'../components/admin/Moderation.vue'),
|
||||
Plugin: () => import(/* webpackChunkName: "admin" */'../components/admin/Plugin.vue'),
|
||||
|
|
|
@ -91,17 +91,33 @@ export default {
|
|||
WhereInput,
|
||||
DateInput
|
||||
},
|
||||
validate({ store }) {
|
||||
return (store.state.auth.loggedIn || store.state.settings.allow_anon_event)
|
||||
validate({ store, params, error }) {
|
||||
// should we allow anon event?
|
||||
if(!store.state.settings.allow_anon_event && !store.state.auth.loggedIn) {
|
||||
return error({ statusCode: 401, message: 'Not allowed'})
|
||||
}
|
||||
|
||||
// do not allow edit to anon users
|
||||
if (params.edit && !store.state.auth.loggedIn) {
|
||||
return error({ statusCode: 401, message: 'Not allowed'})
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
},
|
||||
async asyncData({ params, $axios, error }) {
|
||||
async asyncData({ params, $axios, error, $auth, store }) {
|
||||
if (params.edit) {
|
||||
|
||||
const data = { event: { place: {}, media: [] } }
|
||||
data.id = params.edit
|
||||
data.edit = true
|
||||
let event
|
||||
try {
|
||||
event = await $axios.$get('/event/' + data.id)
|
||||
event = await $axios.$get('/event/detail/' + data.id)
|
||||
if (!$auth.user.is_admin && $auth.user.id !== event.userId) {
|
||||
error({ statusCode: 401, message: 'Not allowed' })
|
||||
return {}
|
||||
}
|
||||
} catch (e) {
|
||||
error({ statusCode: 404, message: 'Event not found!' })
|
||||
return {}
|
||||
|
@ -228,6 +244,8 @@ export default {
|
|||
if (this.date.dueHour) {
|
||||
[hour, minute] = this.date.dueHour.split(':')
|
||||
formData.append('end_datetime', dayjs(this.date.due).hour(Number(hour)).minute(Number(minute)).second(0).unix())
|
||||
} else if (!!this.date.multidate) {
|
||||
formData.append('end_datetime', dayjs(this.date.due).hour(24).minute(0).second(0).unix())
|
||||
}
|
||||
|
||||
if (this.edit) {
|
||||
|
|
|
@ -31,7 +31,7 @@ export default {
|
|||
async asyncData ({ $axios, params, error }) {
|
||||
try {
|
||||
const collection = params.collection
|
||||
const events = await $axios.$get(`/collections/${collection}`)
|
||||
const events = await $axios.$get(`/collections/${encodeURIComponent(collection)}`)
|
||||
return { events, collection }
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
|
|
@ -17,7 +17,7 @@ export default {
|
|||
layout: 'iframe',
|
||||
async asyncData ({ $axios, params, error }) {
|
||||
try {
|
||||
const event = await $axios.$get(`/event/${params.event_id}`)
|
||||
const event = await $axios.$get(`/event/detail/${params.event_id}`)
|
||||
return { event }
|
||||
} catch (e) {
|
||||
error({ statusCode: 404, message: 'Event not found' })
|
||||
|
|
|
@ -3,7 +3,7 @@ v-container#event.pa-0.pa-sm-2
|
|||
//- EVENT PAGE
|
||||
//- 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.h-event(itemscope itemtype="https://schema.org/Event" v-touch="{ left: goNext, right: goPrev }")
|
||||
|
||||
v-card-text
|
||||
v-row
|
||||
|
@ -28,13 +28,13 @@ v-container#event.pa-0.pa-sm-2
|
|||
|
||||
.text-h6.p-location.h-adr(itemprop="location" itemscope itemtype="https://schema.org/Place")
|
||||
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}}
|
||||
nuxt-link.vcard.ml-2.p-name.text-decoration-none.text-button(itemprop="name" :to='`/place/${encodeURIComponent(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-1(v-for='tag in event.tags' small label color='primary'
|
||||
outlined :key='tag' :to='`/tag/${tag}`') {{tag}}
|
||||
outlined :key='tag' :to='`/tag/${encodeURIComponent(tag)}`') {{tag}}
|
||||
|
||||
v-divider
|
||||
//- info & actions
|
||||
|
@ -185,7 +185,7 @@ export default {
|
|||
},
|
||||
async asyncData ({ $axios, params, error }) {
|
||||
try {
|
||||
const event = await $axios.$get(`/event/${params.slug}`)
|
||||
const event = await $axios.$get(`/event/detail/${params.slug}`)
|
||||
return { event }
|
||||
} catch (e) {
|
||||
error({ statusCode: 404, message: 'Event not found' })
|
||||
|
@ -318,12 +318,22 @@ export default {
|
|||
keyDown (ev) {
|
||||
if (ev.altKey || ev.ctrlKey || ev.metaKey || ev.shiftKey) { return }
|
||||
if (ev.key === 'ArrowRight' && this.event.next) {
|
||||
this.$router.replace(`/event/${this.event.next}`)
|
||||
this.goNext()
|
||||
}
|
||||
if (ev.key === 'ArrowLeft' && this.event.prev) {
|
||||
this.goPrev()
|
||||
}
|
||||
},
|
||||
goPrev () {
|
||||
if (this.event.prev) {
|
||||
this.$router.replace(`/event/${this.event.prev}`)
|
||||
}
|
||||
},
|
||||
goNext () {
|
||||
if (this.event.next) {
|
||||
this.$router.replace(`/event/${this.event.next}`)
|
||||
}
|
||||
},
|
||||
showResource (resource) {
|
||||
this.showResources = true
|
||||
this.selectedResource = resource
|
||||
|
|
|
@ -27,8 +27,11 @@ export default {
|
|||
name: 'Index',
|
||||
components: { Event, Announcement, ThemeView },
|
||||
middleware: 'setup',
|
||||
async fetch () {
|
||||
return this.getEvents()
|
||||
fetch () {
|
||||
return this.getEvents({
|
||||
start: this.start,
|
||||
end: this.end
|
||||
})
|
||||
},
|
||||
activated() {
|
||||
if (this.$fetchState.timestamp <= Date.now() - 60000) {
|
||||
|
@ -40,13 +43,11 @@ export default {
|
|||
mdiMagnify, mdiCloseCircle,
|
||||
isCurrentMonth: true,
|
||||
now: dayjs().unix(),
|
||||
date: dayjs.tz().format('YYYY-MM-DD'),
|
||||
start: dayjs().startOf('month').unix(),
|
||||
end: null,
|
||||
searching: false,
|
||||
tmpEvents: [],
|
||||
selectedDay: null,
|
||||
show_recurrent: $store.state.settings.recurrent_event_visible,
|
||||
storeUnsubscribe: null,
|
||||
reload_events: 0
|
||||
}
|
||||
},
|
||||
|
@ -69,76 +70,77 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['settings', 'announcements', 'events']),
|
||||
...mapState(['settings', 'announcements', 'events', 'filter']),
|
||||
visibleEvents () {
|
||||
if (this.searching) {
|
||||
if (this.filter.query && this.filter.query.length > 2) {
|
||||
return this.tmpEvents
|
||||
}
|
||||
const now = dayjs().unix()
|
||||
if (this.selectedDay) {
|
||||
const min = dayjs.tz(this.selectedDay).startOf('day').unix()
|
||||
const max = dayjs.tz(this.selectedDay).endOf('day').unix()
|
||||
return this.events.filter(e => (e.start_datetime <= max && (e.end_datetime || e.start_datetime) >= min) && (this.show_recurrent || !e.parentId))
|
||||
return this.events.filter(e => (e.start_datetime <= max && (e.end_datetime || e.start_datetime) >= min) && (this.filter.show_recurrent || !e.parentId))
|
||||
} else if (this.isCurrentMonth) {
|
||||
return this.events.filter(e => ((e.end_datetime ? e.end_datetime > now : e.start_datetime + 3 * 60 * 60 > now) && (this.show_recurrent || !e.parentId)))
|
||||
return this.events.filter(e => ((e.end_datetime ? e.end_datetime > now : e.start_datetime + 3 * 60 * 60 > now) && (this.filter.show_recurrent || !e.parentId)))
|
||||
} else {
|
||||
return this.events.filter(e => this.show_recurrent || !e.parentId)
|
||||
return this.events.filter(e => this.filter.show_recurrent || !e.parentId)
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.$root.$on('dayclick', this.dayChange)
|
||||
this.$root.$on('monthchange', this.monthChange)
|
||||
this.$root.$on('search', debounce(this.search, 100))
|
||||
this.$root.$on('layout_loaded', () => {
|
||||
this.reload_events++
|
||||
})
|
||||
this.storeUnsubscribe = this.$store.subscribeAction( { after: (action, state) => {
|
||||
if (action.type === 'setFilter') {
|
||||
if (this.filter.query && this.filter.query.length > 2) {
|
||||
this.search()
|
||||
} else {
|
||||
this.tmpEvents = []
|
||||
this.$fetch()
|
||||
}
|
||||
}
|
||||
}})
|
||||
// this.$root.$on('search', debounce(this.search, 100))
|
||||
// this.$root.$on('layout_loaded', () => {
|
||||
// this.reload_events++
|
||||
// })
|
||||
},
|
||||
destroyed () {
|
||||
this.$root.$off('dayclick')
|
||||
this.$root.$off('monthchange')
|
||||
this.$root.$off('search')
|
||||
if (typeof this.storeUnsubscribe === 'function') {
|
||||
this.storeUnsubscribe()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['getEvents']),
|
||||
async search (query) {
|
||||
if (query) {
|
||||
this.tmpEvents = await this.$axios.$get(`/event/search?search=${query}`)
|
||||
this.searching = true
|
||||
} else {
|
||||
this.tmpEvents = null
|
||||
this.searching = false
|
||||
}
|
||||
},
|
||||
updateEvents () {
|
||||
return this.getEvents({
|
||||
start: this.start,
|
||||
end: this.end,
|
||||
show_recurrent: true
|
||||
search: debounce(async function() {
|
||||
this.tmpEvents = await this.$api.getEvents({
|
||||
start: 0,
|
||||
show_recurrent: this.filter.show_recurrent,
|
||||
show_multidate: this.filter.show_multidate,
|
||||
query: this.filter.query
|
||||
})
|
||||
},
|
||||
}, 200),
|
||||
async monthChange ({ year, month }) {
|
||||
|
||||
this.$nuxt.$loading.start()
|
||||
this.$nextTick( async () => {
|
||||
let isCurrentMonth
|
||||
|
||||
// unselect current selected day
|
||||
this.selectedDay = null
|
||||
|
||||
// check if current month is selected
|
||||
if (month - 1 === dayjs.tz().month() && year === dayjs.tz().year()) {
|
||||
this.isCurrentMonth = true
|
||||
isCurrentMonth = true
|
||||
this.start = dayjs().startOf('month').unix()
|
||||
this.date = dayjs.tz().format('YYYY-MM-DD')
|
||||
} else {
|
||||
this.isCurrentMonth = false
|
||||
this.date = ''
|
||||
isCurrentMonth = false
|
||||
this.start = dayjs().year(year).month(month - 1).startOf('month').unix() // .startOf('week').unix()
|
||||
}
|
||||
this.end = dayjs().year(year).month(month).endOf('month').unix() // .endOf('week').unix()
|
||||
await this.updateEvents()
|
||||
await this.$fetch()
|
||||
this.$nuxt.$loading.finish()
|
||||
})
|
||||
this.$nextTick( () => this.isCurrentMonth = isCurrentMonth)
|
||||
|
||||
},
|
||||
dayChange (day) {
|
||||
|
|
|
@ -32,7 +32,7 @@ export default {
|
|||
async asyncData ({ $axios, params, error }) {
|
||||
try {
|
||||
const tag = params.tag
|
||||
const events = await $axios.$get(`/tag/${tag}`)
|
||||
const events = await $axios.$get(`/tag/${encodeURIComponent(tag)}`)
|
||||
return { events, tag }
|
||||
} catch (e) {
|
||||
error({ statusCode: 400, message: 'Error!' })
|
||||
|
|
|
@ -18,12 +18,15 @@ export default ({ $axios }, inject) => {
|
|||
try {
|
||||
const events = await $axios.$get('/events', {
|
||||
params: {
|
||||
start: params.start,
|
||||
end: params.end,
|
||||
...params,
|
||||
// start: params.start,
|
||||
// end: params.end,
|
||||
places: params.places && params.places.join(','),
|
||||
tags: params.tags && params.tags.join(','),
|
||||
show_recurrent: !!params.show_recurrent,
|
||||
max: params.maxs
|
||||
// ...(params.show_recurrent !== && {show_recurrent: !!params.show_recurrent}),
|
||||
// show_multidate: !!params.show_multidate,
|
||||
// query: params.query,
|
||||
// max: params.maxs
|
||||
}
|
||||
})
|
||||
return events.map(e => Object.freeze(e))
|
||||
|
|
|
@ -16,6 +16,7 @@ import 'dayjs/locale/fr'
|
|||
import 'dayjs/locale/de'
|
||||
import 'dayjs/locale/gl'
|
||||
import 'dayjs/locale/sk'
|
||||
import 'dayjs/locale/ru'
|
||||
import 'dayjs/locale/pt'
|
||||
import 'dayjs/locale/zh'
|
||||
|
||||
|
@ -57,11 +58,11 @@ export default ({ app, store }) => {
|
|||
return ''
|
||||
})
|
||||
|
||||
Vue.filter('from', timestamp => dayjs.unix(timestamp).tz().fromNow())
|
||||
Vue.filter('from', timestamp => dayjs.unix(timestamp).tz().locale(app.i18n.locale || store.state.settings.instance_locale).fromNow())
|
||||
|
||||
Vue.filter('recurrentDetail', event => {
|
||||
const parent = event.parent
|
||||
if (!parent.recurrent || parent.recurrent.frequency) return 'error!'
|
||||
if (!parent.recurrent || !parent.recurrent.frequency) return 'error!'
|
||||
const { frequency, type } = parent.recurrent
|
||||
let recurrent
|
||||
if (frequency === '1w' || frequency === '2w') {
|
||||
|
|
|
@ -4,7 +4,7 @@ rm -fr node_modules
|
|||
yarn
|
||||
yarn build
|
||||
yarn pack
|
||||
# yarn publish
|
||||
yarn publish
|
||||
gpg --pinentry-mode loopback --passphrase `pass underscore/pgp` --detach-sign --local-user 5DAC477D5441B7A15ACBF680BBEB4DD39AC6CCA9 gancio-$RELEASE.tgz
|
||||
cp gancio-$RELEASE.tgz releases/
|
||||
mv gancio-$RELEASE.tgz releases/latest.tgz
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const Announcement = require('../models/announcement')
|
||||
const { Announcement } = require('../models/models')
|
||||
|
||||
const log = require('../../log')
|
||||
|
||||
const announceController = {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const APUser = require('../models/ap_user')
|
||||
const { APUser } = require('../models/models')
|
||||
|
||||
const apUserController = {
|
||||
async toggleBlock (req, res) {
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
const Collection = require('../models/collection')
|
||||
const Filter = require('../models/filter')
|
||||
const Event = require('../models/event')
|
||||
const Tag = require('../models/tag')
|
||||
const Place = require('../models/place')
|
||||
const { Collection, Filter, Event, Tag, Place } = require('../models/models')
|
||||
|
||||
const log = require('../../log')
|
||||
const dayjs = require('dayjs')
|
||||
const { col: Col } = require('../../helpers')
|
||||
|
@ -114,7 +111,7 @@ const collectionController = {
|
|||
res.json(collection)
|
||||
} catch (e) {
|
||||
log.error(`Create collection failed ${e}`)
|
||||
res.sendStatus(400)
|
||||
res.status(400).send(e)
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -138,15 +135,14 @@ const collectionController = {
|
|||
},
|
||||
|
||||
async addFilter (req, res) {
|
||||
const collectionId = req.body.collectionId
|
||||
const tags = req.body.tags
|
||||
const places = req.body.places
|
||||
const { collectionId, tags, places } = req.body
|
||||
|
||||
try {
|
||||
const filter = await Filter.create({ collectionId, tags, places })
|
||||
filter = await Filter.create({ collectionId, tags, places })
|
||||
return res.json(filter)
|
||||
} catch (e) {
|
||||
log.error(String(e))
|
||||
return res.status(500)
|
||||
return res.sendStatus(400)
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -170,6 +166,4 @@ const collectionController = {
|
|||
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = collectionController
|
|
@ -3,18 +3,15 @@ const path = require('path')
|
|||
const config = require('../../config')
|
||||
const fs = require('fs')
|
||||
const { Op } = require('sequelize')
|
||||
const intersection = require('lodash/intersection')
|
||||
const linkifyHtml = require('linkify-html')
|
||||
const Sequelize = require('sequelize')
|
||||
const dayjs = require('dayjs')
|
||||
const helpers = require('../../helpers')
|
||||
const Col = helpers.col
|
||||
const Event = require('../models/event')
|
||||
const Resource = require('../models/resource')
|
||||
const Tag = require('../models/tag')
|
||||
const Place = require('../models/place')
|
||||
const Notification = require('../models/notification')
|
||||
const APUser = require('../models/ap_user')
|
||||
const notifier = require('../../notifier')
|
||||
|
||||
const { Event, Resource, Tag, Place, Notification, APUser } = require('../models/models')
|
||||
|
||||
|
||||
const exportController = require('./export')
|
||||
const tagController = require('./tag')
|
||||
|
@ -89,99 +86,71 @@ const eventController = {
|
|||
},
|
||||
|
||||
|
||||
async search(req, res) {
|
||||
const search = req.query.search.trim().toLocaleLowerCase()
|
||||
const show_recurrent = req.query.show_recurrent || false
|
||||
const end = req.query.end
|
||||
const replacements = []
|
||||
// async search(req, res) {
|
||||
// const search = req.query.search.trim().toLocaleLowerCase()
|
||||
// const show_recurrent = req.query.show_recurrent || false
|
||||
// const end = req.query.end
|
||||
// const replacements = []
|
||||
|
||||
const where = {
|
||||
// do not include parent recurrent event
|
||||
recurrent: null,
|
||||
// const where = {
|
||||
// // do not include parent recurrent event
|
||||
// recurrent: null,
|
||||
|
||||
// confirmed event only
|
||||
is_visible: true,
|
||||
// // confirmed event only
|
||||
// is_visible: true,
|
||||
|
||||
}
|
||||
// }
|
||||
|
||||
if (!show_recurrent) {
|
||||
where.parentId = null
|
||||
}
|
||||
// if (!show_recurrent) {
|
||||
// where.parentId = null
|
||||
// }
|
||||
|
||||
if (end) {
|
||||
where.start_datetime = { [Op.lte]: end }
|
||||
}
|
||||
// if (end) {
|
||||
// where.start_datetime = { [Op.lte]: end }
|
||||
// }
|
||||
|
||||
if (search) {
|
||||
replacements.push(search)
|
||||
where[Op.or] =
|
||||
[
|
||||
{ title: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('title')), 'LIKE', '%' + search + '%') },
|
||||
Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), 'LIKE', '%' + search + '%'),
|
||||
Sequelize.fn('EXISTS', Sequelize.literal(`SELECT 1 FROM event_tags WHERE ${Col('event_tags.eventId')}=${Col('event.id')} AND LOWER(${Col('tagTag')}) = ?`))
|
||||
]
|
||||
}
|
||||
// if (search) {
|
||||
// replacements.push(search)
|
||||
// where[Op.or] =
|
||||
// [
|
||||
// { title: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('title')), 'LIKE', '%' + search + '%') },
|
||||
// Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), 'LIKE', '%' + search + '%'),
|
||||
// Sequelize.fn('EXISTS', Sequelize.literal(`SELECT 1 FROM event_tags WHERE ${Col('event_tags.eventId')}=${Col('event.id')} AND LOWER(${Col('tagTag')}) = ?`))
|
||||
// ]
|
||||
// }
|
||||
|
||||
|
||||
const events = await Event.findAll({
|
||||
where,
|
||||
attributes: {
|
||||
exclude: ['likes', 'boost', 'userId', 'is_visible', 'createdAt', 'updatedAt', 'description', 'resources']
|
||||
},
|
||||
order: [['start_datetime', 'DESC']],
|
||||
include: [
|
||||
{
|
||||
model: Tag,
|
||||
// order: [Sequelize.literal('(SELECT COUNT("tagTag") FROM event_tags WHERE tagTag = tag) DESC')],
|
||||
attributes: ['tag'],
|
||||
through: { attributes: [] }
|
||||
},
|
||||
{ model: Place, required: true, attributes: ['id', 'name', 'address', 'latitude', 'longitude'] }
|
||||
],
|
||||
replacements,
|
||||
limit: 30,
|
||||
}).catch(e => {
|
||||
log.error('[EVENT]', e)
|
||||
return res.json([])
|
||||
})
|
||||
// const events = await Event.findAll({
|
||||
// where,
|
||||
// attributes: {
|
||||
// exclude: ['likes', 'boost', 'userId', 'is_visible', 'createdAt', 'updatedAt', 'description', 'resources']
|
||||
// },
|
||||
// order: [['start_datetime', 'DESC']],
|
||||
// include: [
|
||||
// {
|
||||
// model: Tag,
|
||||
// // order: [Sequelize.literal('(SELECT COUNT("tagTag") FROM event_tags WHERE tagTag = tag) DESC')],
|
||||
// attributes: ['tag'],
|
||||
// through: { attributes: [] }
|
||||
// },
|
||||
// { model: Place, required: true, attributes: ['id', 'name', 'address', 'latitude', 'longitude'] }
|
||||
// ],
|
||||
// replacements,
|
||||
// limit: 30,
|
||||
// }).catch(e => {
|
||||
// log.error('[EVENT]', e)
|
||||
// return res.json([])
|
||||
// })
|
||||
|
||||
const ret = events.map(e => {
|
||||
e = e.get()
|
||||
e.tags = e.tags ? e.tags.map(t => t && t.tag) : []
|
||||
return e
|
||||
})
|
||||
// const ret = events.map(e => {
|
||||
// e = e.get()
|
||||
// e.tags = e.tags ? e.tags.map(t => t && t.tag) : []
|
||||
// return e
|
||||
// })
|
||||
|
||||
return res.json(ret)
|
||||
// return res.json(ret)
|
||||
|
||||
},
|
||||
|
||||
async getNotifications(event, action) {
|
||||
log.debug(`getNotifications ${event.title} ${action}`)
|
||||
function match(event, filters) {
|
||||
// matches if no filter specified
|
||||
if (!filters) { return true }
|
||||
|
||||
// check for visibility
|
||||
if (typeof filters.is_visible !== 'undefined' && filters.is_visible !== event.is_visible) { return false }
|
||||
|
||||
if (!filters.tags && !filters.places) { return true }
|
||||
if (!filters.tags.length && !filters.places.length) { return true }
|
||||
if (filters.tags.length) {
|
||||
const m = intersection(event.tags.map(t => t.tag), filters.tags)
|
||||
if (m.length > 0) { return true }
|
||||
}
|
||||
if (filters.places.length) {
|
||||
if (filters.places.find(p => p === event.place.name)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const notifications = await Notification.findAll({ where: { action }, include: [Event] })
|
||||
|
||||
// get notification that matches with selected event
|
||||
return notifications.filter(notification => match(event, notification.filters))
|
||||
},
|
||||
// },
|
||||
|
||||
async _get(slug) {
|
||||
// retrocompatibility, old events URL does not use slug, use id as fallback
|
||||
|
@ -317,7 +286,6 @@ const eventController = {
|
|||
res.sendStatus(200)
|
||||
|
||||
// send notification
|
||||
const notifier = require('../../notifier')
|
||||
notifier.notifyEvent('Create', event.id)
|
||||
} catch (e) {
|
||||
log.error('[EVENT]', e)
|
||||
|
@ -631,9 +599,11 @@ const eventController = {
|
|||
async _select({
|
||||
start = dayjs().unix(),
|
||||
end,
|
||||
query,
|
||||
tags,
|
||||
places,
|
||||
show_recurrent,
|
||||
show_multidate,
|
||||
limit,
|
||||
page,
|
||||
older }) {
|
||||
|
@ -656,6 +626,10 @@ const eventController = {
|
|||
where.parentId = null
|
||||
}
|
||||
|
||||
if (!show_multidate) {
|
||||
where.multidate = { [Op.not]: true }
|
||||
}
|
||||
|
||||
if (end) {
|
||||
where.start_datetime = { [older ? Op.gte : Op.lte]: end }
|
||||
}
|
||||
|
@ -679,6 +653,16 @@ const eventController = {
|
|||
where.placeId = places.split(',')
|
||||
}
|
||||
|
||||
if (query) {
|
||||
replacements.push(query)
|
||||
where[Op.or] =
|
||||
[
|
||||
{ title: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('title')), 'LIKE', '%' + query + '%') },
|
||||
Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), 'LIKE', '%' + query + '%'),
|
||||
Sequelize.fn('EXISTS', Sequelize.literal(`SELECT 1 FROM event_tags WHERE ${Col('event_tags.eventId')}=${Col('event.id')} AND LOWER(${Col('tagTag')}) = ?`))
|
||||
]
|
||||
}
|
||||
|
||||
let pagination = {}
|
||||
if (limit) {
|
||||
pagination = {
|
||||
|
@ -705,7 +689,7 @@ const eventController = {
|
|||
...pagination,
|
||||
replacements
|
||||
}).catch(e => {
|
||||
log.error('[EVENT]', e)
|
||||
log.error('[EVENT]' + String(e))
|
||||
return []
|
||||
})
|
||||
|
||||
|
@ -723,17 +707,21 @@ const eventController = {
|
|||
const settings = res.locals.settings
|
||||
const start = req.query.start || dayjs().unix()
|
||||
const end = req.query.end
|
||||
const query = req.query.query
|
||||
const tags = req.query.tags
|
||||
const places = req.query.places
|
||||
const limit = Number(req.query.max) || 0
|
||||
const page = Number(req.query.page) || 0
|
||||
const older = req.query.older || false
|
||||
|
||||
const show_multidate = settings.allow_multidate_event &&
|
||||
typeof req.query.show_multidate !== 'undefined' ? req.query.show_multidate !== 'false' : true
|
||||
|
||||
const show_recurrent = settings.allow_recurrent_event &&
|
||||
typeof req.query.show_recurrent !== 'undefined' ? req.query.show_recurrent === 'true' : settings.recurrent_event_visible
|
||||
|
||||
res.json(await eventController._select({
|
||||
start, end, places, tags, show_recurrent, limit, page, older
|
||||
start, end, query, places, tags, show_recurrent, show_multidate, limit, page, older
|
||||
}))
|
||||
},
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
const Event = require('../models/event')
|
||||
const Place = require('../models/place')
|
||||
const Tag = require('../models/tag')
|
||||
const { Event, Place, Tag } = require('../models/models')
|
||||
|
||||
const { htmlToText } = require('html-to-text')
|
||||
const { Op, literal } = require('sequelize')
|
||||
|
@ -88,6 +86,7 @@ const exportController = {
|
|||
const start = tmpStart.utc(true).format('YYYY-M-D-H-m').split('-').map(Number)
|
||||
const end = tmpEnd.utc(true).format('YYYY-M-D-H-m').split('-').map(Number)
|
||||
return {
|
||||
uid: `${e.id}@${settings.hostname}`,
|
||||
start,
|
||||
end,
|
||||
title: `[${settings.title}] ${e.title}`,
|
||||
|
|
122
server/api/controller/geocoding.js
Normal file
122
server/api/controller/geocoding.js
Normal file
|
@ -0,0 +1,122 @@
|
|||
const log = require('../../log')
|
||||
const nominatim = require('../../services/geocoding/nominatim')
|
||||
const photon = require('../../services/geocoding/photon')
|
||||
const axios = require('axios')
|
||||
const { version } = require('../../../package.json')
|
||||
let d = 0 // departure time
|
||||
let h = 0 // hit geocoding provider time (aka Latency)
|
||||
|
||||
const geocodingController = {
|
||||
|
||||
/**
|
||||
* Limit provider api usage.
|
||||
* From https://operations.osmfoundation.org/policies/nominatim/
|
||||
* [Requirements] No heavy uses (an absolute maximum of 1 request per second).
|
||||
* [Websites and Apps]
|
||||
* - Note that the usage limits above apply per website/application: the sum of traffic by all your users should not exceed the limits.
|
||||
* - If at all possible, set up a proxy and also enable caching of requests.
|
||||
*/
|
||||
providerRateLimit (req, res, next, providerCache) {
|
||||
let a = Date.now(); // arrival time
|
||||
let dprev = d
|
||||
d = dprev + 1000 + h
|
||||
|
||||
// console.log('a: ' + a)
|
||||
// console.log('dprev: ' + dprev)
|
||||
// console.log('d: ' + d)
|
||||
|
||||
// if the same request was already cached skip the delay mechanism
|
||||
if (providerCache.get(req.params.place_details)) {
|
||||
if (a < d) {
|
||||
log.warn('More than 1 request per second to geocoding api. This from ' + req.ip + ' . The response data is served from memory-cache.')
|
||||
}
|
||||
// reset departure time because there is no need to ask provider
|
||||
d = dprev
|
||||
return next()
|
||||
}
|
||||
|
||||
if (d === 0 || a > d) {
|
||||
// no-queue or old-queue
|
||||
// console.log('No queue or Old queue')
|
||||
// arrival time + 10ms estimated computing time
|
||||
d = a + 10
|
||||
next()
|
||||
} else {
|
||||
// fresh queue
|
||||
// console.log('Fresh queue')
|
||||
let wait = d - a
|
||||
// console.log('Waiting '+ wait)
|
||||
log.warn('More than 1 request per second to geocoding api. This from ' + req.ip + ' . Applying ToS padding before asking to provider. The response data is now cached.')
|
||||
|
||||
setTimeout(() => {
|
||||
next()
|
||||
}, wait)
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
async nominatimRateLimit(req, res, next) {
|
||||
geocodingController.providerRateLimit(req, res, next, nominatim.cache)
|
||||
},
|
||||
|
||||
async photonRateLimit(req, res, next) {
|
||||
geocodingController.providerRateLimit(req, res, next, photon.cache)
|
||||
},
|
||||
|
||||
async checkInCache (req, res, details, providerCache) {
|
||||
const ret = await providerCache.get(details)
|
||||
if (ret) {
|
||||
return ret
|
||||
} else {
|
||||
return
|
||||
}
|
||||
},
|
||||
|
||||
async queryProvider(req, res, details, provider) {
|
||||
let RTTstart = Date.now()
|
||||
// console.log('Asking Provider: ' + RTTstart)
|
||||
|
||||
const ret = await axios.get(`${provider.endpoint(req, res)}`, {
|
||||
params: provider.getParams(req, res),
|
||||
headers: { 'User-Agent': `gancio ${version}` }
|
||||
})
|
||||
|
||||
if (ret) {
|
||||
let RTTend = Date.now()
|
||||
// console.log('Asking Provider: ' + RTTend)
|
||||
// Save the hit time (aka Latency)
|
||||
// console.log('Saving latency h: ' + h)
|
||||
h = (RTTend - RTTstart) / 2
|
||||
}
|
||||
|
||||
// Cache the response data
|
||||
provider.cache.put(details, ret.data, 1000 * 60 * 60 * 24);
|
||||
// console.log(cache.keys())
|
||||
// console.log(cache.exportJson())
|
||||
return ret.data
|
||||
},
|
||||
|
||||
|
||||
async _nominatim (req, res) {
|
||||
const details = req.params.place_details
|
||||
|
||||
const ret = await geocodingController.checkInCache(req, res, details, nominatim.cache) ||
|
||||
await geocodingController.queryProvider(req, res, details, nominatim)
|
||||
|
||||
return res.json(ret)
|
||||
|
||||
},
|
||||
|
||||
async _photon (req, res) {
|
||||
const details = req.params.place_details
|
||||
|
||||
const ret = await geocodingController.checkInCache(req, res, details, photon.cache) ||
|
||||
await geocodingController.queryProvider(req, res, details, photon)
|
||||
|
||||
return res.json(ret)
|
||||
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
module.exports = geocodingController
|
|
@ -1,6 +1,5 @@
|
|||
const APUser = require('../models/ap_user')
|
||||
const Instance = require('../models/instance')
|
||||
const Resource = require('../models/resource')
|
||||
const { APUser, Instance, Resource } = require('../models/models')
|
||||
|
||||
const Sequelize = require('sequelize')
|
||||
|
||||
const instancesController = {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const User = require('../models/user')
|
||||
const User = require('../models/modles')
|
||||
|
||||
const metrics = {
|
||||
|
||||
|
|
|
@ -2,12 +2,9 @@ const bodyParser = require('body-parser')
|
|||
const cookieParser = require('cookie-parser')
|
||||
const session = require('cookie-session')
|
||||
|
||||
const OAuthClient = require('../models/oauth_client')
|
||||
const OAuthToken = require('../models/oauth_token')
|
||||
const OAuthCode = require('../models/oauth_code')
|
||||
const { OAuthClient, OAuthToken, OAuthCode, User } = require('../models/models')
|
||||
|
||||
const helpers = require('../../helpers.js')
|
||||
const User = require('../models/user')
|
||||
const passport = require('passport')
|
||||
|
||||
const get = require('lodash/get')
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
const Place = require('../models/place')
|
||||
const Event = require('../models/event')
|
||||
const { Place, Event } = require('../models/models')
|
||||
|
||||
const eventController = require('./event')
|
||||
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 = {
|
||||
|
||||
|
@ -72,23 +70,6 @@ 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)
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,11 +2,12 @@ const path = require('path')
|
|||
const fs = require('fs')
|
||||
const log = require('../../log')
|
||||
const config = require('../../config')
|
||||
const settingsController = require('./settings')
|
||||
const notifier = require('../../notifier')
|
||||
|
||||
const pluginController = {
|
||||
plugins: [],
|
||||
getAll(_req, res) {
|
||||
const settingsController = require('./settings')
|
||||
// return plugins and inner settings
|
||||
const plugins = pluginController.plugins.map( ({ configuration }) => {
|
||||
if (settingsController.settings['plugin_' + configuration.name]) {
|
||||
|
@ -18,7 +19,6 @@ const pluginController = {
|
|||
},
|
||||
|
||||
togglePlugin(req, res) {
|
||||
const settingsController = require('./settings')
|
||||
const pluginName = req.params.plugin
|
||||
const pluginSettings = settingsController.settings['plugin_' + pluginName]
|
||||
if (!pluginSettings) { return res.sendStatus(404) }
|
||||
|
@ -33,7 +33,6 @@ const pluginController = {
|
|||
},
|
||||
|
||||
unloadPlugin(pluginName) {
|
||||
const settingsController = require('./settings')
|
||||
const plugin = pluginController.plugins.find(p => p.configuration.name === pluginName)
|
||||
const settings = settingsController.settings['plugin_' + pluginName]
|
||||
if (!plugin) {
|
||||
|
@ -59,14 +58,12 @@ const pluginController = {
|
|||
},
|
||||
|
||||
loadPlugin(pluginName) {
|
||||
const settingsController = require('./settings')
|
||||
const plugin = pluginController.plugins.find(p => p.configuration.name === pluginName)
|
||||
const settings = settingsController.settings['plugin_' + pluginName]
|
||||
if (!plugin) {
|
||||
log.warn(`Plugin ${pluginName} not found`)
|
||||
return
|
||||
}
|
||||
const notifier = require('../../notifier')
|
||||
log.info('Load plugin ' + pluginName)
|
||||
if (typeof plugin.onEventCreate === 'function') {
|
||||
notifier.emitter.on('Create', plugin.onEventCreate)
|
||||
|
@ -79,20 +76,15 @@ const pluginController = {
|
|||
}
|
||||
|
||||
if (plugin.load && typeof plugin.load === 'function') {
|
||||
plugin.load({ helpers: require('../../helpers'), settings: settingsController.settings }, settings)
|
||||
plugin.load({
|
||||
helpers: require('../../helpers'),
|
||||
settings: settingsController.settings
|
||||
},
|
||||
settings)
|
||||
}
|
||||
},
|
||||
|
||||
_load() {
|
||||
const settingsController = require('./settings')
|
||||
// load custom plugins
|
||||
const plugins_path = config.plugins_path || path.resolve(process.env.cwd || '', 'gancio_plugins')
|
||||
log.info(`Loading plugin ${plugins_path}`)
|
||||
if (fs.existsSync(plugins_path)) {
|
||||
const plugins = fs.readdirSync(plugins_path)
|
||||
.map(e => path.resolve(plugins_path, e, 'index.js'))
|
||||
.filter(index => fs.existsSync(index))
|
||||
plugins.forEach(pluginFile => {
|
||||
_loadPlugin (pluginFile) {
|
||||
try {
|
||||
const plugin = require(pluginFile)
|
||||
const name = plugin.configuration.name
|
||||
|
@ -109,7 +101,22 @@ const pluginController = {
|
|||
} catch (e) {
|
||||
log.warn(`Unable to load plugin ${pluginFile}: ${String(e)}`)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
_load() {
|
||||
// load custom plugins
|
||||
const system_plugins_path = path.resolve(__dirname || '', '../../../gancio_plugins')
|
||||
const custom_plugins_path = config.plugins_path || path.resolve(process.env.cwd || '', 'plugins')
|
||||
const plugins_paths = custom_plugins_path === system_plugins_path ? [custom_plugins_path] : [custom_plugins_path, system_plugins_path]
|
||||
|
||||
log.info(`Loading plugins from ${plugins_paths.join(' and ')}`)
|
||||
for (const plugins_path of plugins_paths) {
|
||||
if (fs.existsSync(plugins_path)) {
|
||||
fs.readdirSync(plugins_path)
|
||||
.map(e => path.resolve(plugins_path, e, 'index.js'))
|
||||
.filter(index => fs.existsSync(index))
|
||||
.forEach(pluginController._loadPlugin)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
const Resource = require('../models/resource')
|
||||
const APUser = require('../models/ap_user')
|
||||
const Event = require('../models/event')
|
||||
const { Resource, APUser, Event } = require('../models/models')
|
||||
const get = require('lodash/get')
|
||||
|
||||
const resourceController = {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
const path = require('path')
|
||||
const URL = require('url')
|
||||
const fs = require('fs')
|
||||
const crypto = require('crypto')
|
||||
const { promisify } = require('util')
|
||||
const sharp = require('sharp')
|
||||
|
@ -9,7 +8,7 @@ const generateKeyPair = promisify(crypto.generateKeyPair)
|
|||
const log = require('../../log')
|
||||
// const locales = require('../../../locales/index')
|
||||
const escape = require('lodash/escape')
|
||||
const pluginController = require('./plugins')
|
||||
const DB = require('../models/models')
|
||||
|
||||
let defaultHostname
|
||||
try {
|
||||
|
@ -27,9 +26,15 @@ const defaultSettings = {
|
|||
instance_place: '',
|
||||
allow_registration: true,
|
||||
allow_anon_event: true,
|
||||
allow_multidate_event: true,
|
||||
allow_recurrent_event: false,
|
||||
recurrent_event_visible: false,
|
||||
allow_geolocation: true,
|
||||
allow_geolocation: false,
|
||||
geocoding_provider_type: 'Nominatim',
|
||||
geocoding_provider: 'https://nominatim.openstreetmap.org/search',
|
||||
geocoding_countrycodes: [],
|
||||
tilelayer_provider: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
tilelayer_provider_attribution: "<a target=\"_blank\" href=\"http://osm.org/copyright\">OpenStreetMap</a> contributors",
|
||||
enable_federation: true,
|
||||
enable_resources: false,
|
||||
hide_boosts: true,
|
||||
|
@ -68,8 +73,7 @@ const settingsController = {
|
|||
// initialize instance settings from db
|
||||
// note that this is done only once when the server starts
|
||||
// and not for each request
|
||||
const Setting = require('../models/setting')
|
||||
const settings = await Setting.findAll()
|
||||
const settings = await DB.Setting.findAll()
|
||||
settingsController.settings = defaultSettings
|
||||
settings.forEach(s => {
|
||||
if (s.is_secret) {
|
||||
|
@ -111,15 +115,14 @@ const settingsController = {
|
|||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
const pluginController = require('./plugins')
|
||||
pluginController._load()
|
||||
},
|
||||
|
||||
async set (key, value, is_secret = false) {
|
||||
const Setting = require('../models/setting')
|
||||
log.info(`SET ${key} ${is_secret ? '*****' : value}`)
|
||||
try {
|
||||
const [setting, created] = await Setting.findOrCreate({
|
||||
const [setting, created] = await DB.Setting.findOrCreate({
|
||||
where: { key },
|
||||
defaults: { value, is_secret }
|
||||
})
|
||||
|
@ -211,7 +214,7 @@ const settingsController = {
|
|||
}
|
||||
|
||||
const uploadedPath = path.join(req.file.destination, req.file.filename)
|
||||
const baseImgPath = path.resolve(config.upload_path, 'fallbackImage.png')
|
||||
const baseImgPath = path.resolve(config.upload_path, 'headerImage.png')
|
||||
|
||||
// convert and resize to png
|
||||
return sharp(uploadedPath)
|
||||
|
|
|
@ -7,6 +7,8 @@ const settingsController = require('./settings')
|
|||
const path = require('path')
|
||||
const escape = require('lodash/escape')
|
||||
|
||||
const DB = require('../models/models')
|
||||
|
||||
const setupController = {
|
||||
|
||||
async _setupDb (dbConf) {
|
||||
|
@ -23,7 +25,10 @@ const setupController = {
|
|||
|
||||
// try to connect
|
||||
dbConf.logging = false
|
||||
await db.connect(dbConf)
|
||||
db.connect(dbConf)
|
||||
db.loadModels()
|
||||
db.associates()
|
||||
await db.sequelize.authenticate()
|
||||
|
||||
// is empty ?
|
||||
const isEmpty = await db.isEmpty()
|
||||
|
@ -69,8 +74,7 @@ const setupController = {
|
|||
// create admin
|
||||
const password = helpers.randomString()
|
||||
const email = `admin`
|
||||
const User = require('../models/user')
|
||||
await User.create({
|
||||
await DB.User.create({
|
||||
email,
|
||||
password,
|
||||
is_admin: true,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
const Tag = require('../models/tag')
|
||||
const Event = require('../models/event')
|
||||
const { Tag, Event } = require('../models/models')
|
||||
const uniq = require('lodash/uniq')
|
||||
const log = require('../../log')
|
||||
|
||||
|
||||
const { where, fn, col, Op } = require('sequelize')
|
||||
const exportController = require('./export')
|
||||
|
@ -45,6 +46,20 @@ module.exports = {
|
|||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
async getAll (_req, res) {
|
||||
const tags = await Tag.findAll({
|
||||
order: [[fn('COUNT', col('tag.tag')), 'DESC']],
|
||||
attributes: ['tag', [fn('COUNT', col('tag.tag')), 'count']],
|
||||
include: [{ model: Event, where: { is_visible: true }, attributes: [], through: { attributes: [] }, required: true }],
|
||||
group: ['tag.tag'],
|
||||
raw: true,
|
||||
})
|
||||
return res.json(tags)
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* search for tags by query string
|
||||
* sorted by usage
|
||||
|
@ -64,5 +79,50 @@ module.exports = {
|
|||
})
|
||||
|
||||
return res.json(tags.map(t => t.tag))
|
||||
},
|
||||
|
||||
// async updateTag (req, res) {
|
||||
// const tag = await Tag.findByPk(req.body.tag)
|
||||
// await tag.update(req.body)
|
||||
// res.json(place)
|
||||
// },
|
||||
|
||||
async updateTag (req, res) {
|
||||
try {
|
||||
const oldtag = await Tag.findByPk(req.body.tag)
|
||||
const newtag = await Tag.findByPk(req.body.newTag)
|
||||
|
||||
// if the new tag does not exists, just rename the old one
|
||||
if (!newtag) {
|
||||
log.info(`Rename tag ${oldtag.tag} to ${req.body.newTag}`)
|
||||
await Tag.update({ tag: req.body.newTag }, { where: { tag: req.body.tag }, raw: true })
|
||||
|
||||
} else {
|
||||
// in case it exists:
|
||||
// - search for events with old tag
|
||||
const events = await oldtag.getEvents()
|
||||
// - substitute it with the new one
|
||||
await oldtag.removeEvents(events)
|
||||
await newtag.addEvents(events)
|
||||
}
|
||||
res.sendStatus(200)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
res.sendStatus(400)
|
||||
}
|
||||
},
|
||||
|
||||
async remove (req, res) {
|
||||
log.info('Remove tag', req.params.tag)
|
||||
const tagName = req.params.tag
|
||||
try {
|
||||
const tag = await Tag.findByPk(tagName)
|
||||
await tag.destroy()
|
||||
res.sendStatus(200)
|
||||
} catch (e) {
|
||||
log.error('Tag removal failed:', e)
|
||||
res.sendStatus(404)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -2,7 +2,7 @@ const crypto = require('crypto')
|
|||
const { Op } = require('sequelize')
|
||||
const config = require('../../config')
|
||||
const mail = require('../mail')
|
||||
const User = require('../models/user')
|
||||
const { User } = require('../models/models')
|
||||
const settingsController = require('./settings')
|
||||
const log = require('../../log')
|
||||
const linkify = require('linkifyjs')
|
||||
|
|
|
@ -5,15 +5,39 @@ const cors = require('cors')()
|
|||
const config = require('../config')
|
||||
const log = require('../log')
|
||||
|
||||
const collectionController = require('./controller/collection')
|
||||
const setupController = require('./controller/setup')
|
||||
const settingsController = require('./controller/settings')
|
||||
const eventController = require('./controller/event')
|
||||
const placeController = require('./controller/place')
|
||||
const tagController = require('./controller/tag')
|
||||
const exportController = require('./controller/export')
|
||||
const userController = require('./controller/user')
|
||||
const instanceController = require('./controller/instance')
|
||||
const apUserController = require('./controller/ap_user')
|
||||
const resourceController = require('./controller/resource')
|
||||
const oauthController = require('./controller/oauth')
|
||||
const announceController = require('./controller/announce')
|
||||
const pluginController = require('./controller/plugins')
|
||||
const geocodingController = require('./controller/geocoding')
|
||||
const { DDOSProtectionApiRateLimiter, SPAMProtectionApiRateLimiter } = require('./limiter')
|
||||
const helpers = require('../helpers')
|
||||
const storage = require('./storage')
|
||||
|
||||
|
||||
module.exports = () => {
|
||||
|
||||
const api = express.Router()
|
||||
api.use(express.urlencoded({ extended: false }))
|
||||
api.use(express.json())
|
||||
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
api.use(DDOSProtectionApiRateLimiter)
|
||||
}
|
||||
|
||||
|
||||
if (config.status !== 'READY') {
|
||||
|
||||
const setupController = require('./controller/setup')
|
||||
const settingsController = require('./controller/settings')
|
||||
api.post('/settings', settingsController.setRequest)
|
||||
api.post('/setup/db', setupController.setupDb)
|
||||
api.post('/setup/restart', setupController.restart)
|
||||
|
@ -22,21 +46,6 @@ if (config.status !== 'READY') {
|
|||
} else {
|
||||
|
||||
const { isAuth, isAdmin } = require('./auth')
|
||||
const eventController = require('./controller/event')
|
||||
const placeController = require('./controller/place')
|
||||
const tagController = require('./controller/tag')
|
||||
const settingsController = require('./controller/settings')
|
||||
const exportController = require('./controller/export')
|
||||
const userController = require('./controller/user')
|
||||
const instanceController = require('./controller/instance')
|
||||
const apUserController = require('./controller/ap_user')
|
||||
const resourceController = require('./controller/resource')
|
||||
const oauthController = require('./controller/oauth')
|
||||
const announceController = require('./controller/announce')
|
||||
const collectionController = require('./controller/collection')
|
||||
const pluginController = require('./controller/plugins')
|
||||
const helpers = require('../helpers')
|
||||
const storage = require('./storage')
|
||||
const upload = multer({ storage })
|
||||
|
||||
/**
|
||||
|
@ -62,13 +71,12 @@ if (config.status !== 'READY') {
|
|||
api.get('/ping', (_req, res) => res.sendStatus(200))
|
||||
api.get('/user', isAuth, (req, res) => res.json(req.user))
|
||||
|
||||
|
||||
api.post('/user/recover', userController.forgotPassword)
|
||||
api.post('/user/recover', SPAMProtectionApiRateLimiter, userController.forgotPassword)
|
||||
api.post('/user/check_recover_code', userController.checkRecoverCode)
|
||||
api.post('/user/recover_password', userController.updatePasswordWithRecoverCode)
|
||||
api.post('/user/recover_password', SPAMProtectionApiRateLimiter, userController.updatePasswordWithRecoverCode)
|
||||
|
||||
// register and add users
|
||||
api.post('/user/register', userController.register)
|
||||
api.post('/user/register', SPAMProtectionApiRateLimiter, userController.register)
|
||||
api.post('/user', isAdmin, userController.create)
|
||||
|
||||
// update user
|
||||
|
@ -88,6 +96,7 @@ if (config.status !== 'READY') {
|
|||
* @type GET
|
||||
* @param {integer} [start] - start timestamp (default: now)
|
||||
* @param {integer} [end] - end timestamp (optional)
|
||||
* @param {string} [query] - search for this string
|
||||
* @param {array} [tags] - List of tags
|
||||
* @param {array} [places] - List of places id
|
||||
* @param {integer} [max] - Limit events
|
||||
|
@ -123,9 +132,9 @@ if (config.status !== 'READY') {
|
|||
*/
|
||||
|
||||
// allow anyone to add an event (anon event has to be confirmed, TODO: flood protection)
|
||||
api.post('/event', eventController.isAnonEventAllowed, upload.single('image'), eventController.add)
|
||||
api.post('/event', eventController.isAnonEventAllowed, SPAMProtectionApiRateLimiter, upload.single('image'), eventController.add)
|
||||
|
||||
api.get('/event/search', eventController.search)
|
||||
// api.get('/event/search', eventController.search)
|
||||
|
||||
api.put('/event', isAuth, upload.single('image'), eventController.update)
|
||||
api.get('/event/import', isAuth, helpers.importURL)
|
||||
|
@ -137,8 +146,8 @@ if (config.status !== 'READY') {
|
|||
api.get('/event/meta', eventController.searchMeta)
|
||||
|
||||
// add event notification TODO
|
||||
api.post('/event/notification', eventController.addNotification)
|
||||
api.delete('/event/notification/:code', eventController.delNotification)
|
||||
// api.post('/event/notification', eventController.addNotification)
|
||||
// api.delete('/event/notification/:code', eventController.delNotification)
|
||||
|
||||
api.post('/settings', isAdmin, settingsController.setRequest)
|
||||
api.get('/settings', isAdmin, settingsController.getAll)
|
||||
|
@ -156,20 +165,29 @@ if (config.status !== 'READY') {
|
|||
api.put('/event/unconfirm/:event_id', isAuth, eventController.unconfirm)
|
||||
|
||||
// get event
|
||||
api.get('/event/:event_slug.:format?', cors, eventController.get)
|
||||
api.get('/event/detail/:event_slug.:format?', cors, eventController.get)
|
||||
|
||||
// export events (rss/ics)
|
||||
api.get('/export/:type', cors, exportController.export)
|
||||
|
||||
|
||||
api.get('/place/all', isAdmin, placeController.getAll)
|
||||
// - PLACES
|
||||
api.get('/places', 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)
|
||||
|
||||
// - GEOCODING
|
||||
api.get('/placeOSM/Nominatim/:place_details', helpers.isGeocodingEnabled, geocodingController.nominatimRateLimit, geocodingController._nominatim)
|
||||
api.get('/placeOSM/Photon/:place_details', helpers.isGeocodingEnabled, geocodingController.photonRateLimit, geocodingController._photon)
|
||||
|
||||
// - TAGS
|
||||
api.get('/tags', isAdmin, tagController.getAll)
|
||||
api.get('/tag', cors, tagController.search)
|
||||
api.get('/tag/:tag', cors, tagController.getEvents)
|
||||
api.delete('/tag/:tag', isAdmin, tagController.remove)
|
||||
api.put('/tag', isAdmin, tagController.updateTag)
|
||||
|
||||
|
||||
// - FEDIVERSE INSTANCES, MODERATION, RESOURCES
|
||||
api.get('/instances', isAdmin, instanceController.getAll)
|
||||
|
@ -202,7 +220,7 @@ if (config.status !== 'READY') {
|
|||
// OAUTH
|
||||
api.get('/clients', isAuth, oauthController.getClients)
|
||||
api.get('/client/:client_id', isAuth, oauthController.getClient)
|
||||
api.post('/client', oauthController.createClient)
|
||||
api.post('/client', SPAMProtectionApiRateLimiter, oauthController.createClient)
|
||||
}
|
||||
|
||||
api.use((_req, res) => res.sendStatus(404))
|
||||
|
@ -213,4 +231,5 @@ api.use((error, _req, res, _next) => {
|
|||
res.status(500).send('500: Internal Server Error')
|
||||
})
|
||||
|
||||
module.exports = api
|
||||
return api
|
||||
}
|
||||
|
|
32
server/api/limiter.js
Normal file
32
server/api/limiter.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
const rateLimit = require('express-rate-limit')
|
||||
const log = require('../log')
|
||||
|
||||
const next = (req, res, next) => next()
|
||||
|
||||
const instanceApiRateLimiter = {
|
||||
|
||||
DDOSProtectionApiRateLimiter: (process.env.NODE_ENV === 'test' ? next : rateLimit({
|
||||
windowMs: 60 * 1000, // 5 minutes
|
||||
max: 100, // Limit each IP to 100 requests per `window` (here, per 5 minutes)
|
||||
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
|
||||
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
||||
handler: (request, response, next, options) => {
|
||||
log.warn(`DDOS protection api rate limiter: > 100req/minute/ip ${request.ip}`)
|
||||
return response.status(options.statusCode).send(options.message)
|
||||
}
|
||||
})),
|
||||
|
||||
SPAMProtectionApiRateLimiter: (process.env.NODE_ENV === 'test' ? next : rateLimit({
|
||||
windowMs: 5 * 60 * 1000, // 10 minutes
|
||||
max: 3, // Limit each IP to 3 requests per `window` (here, per 15 minutes)
|
||||
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
|
||||
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
||||
handler: (request, response, next, options) => {
|
||||
log.warn(`SPAM protection api rate limiter: 3req/5min/ip ${request.ip}`)
|
||||
return response.status(options.statusCode).send(options.message)
|
||||
}
|
||||
}))
|
||||
|
||||
}
|
||||
|
||||
module.exports = instanceApiRateLimiter
|
|
@ -1,12 +1,6 @@
|
|||
const sequelize = require('./index').sequelize
|
||||
const { Model, DataTypes } = require('sequelize')
|
||||
|
||||
class Announcement extends Model {}
|
||||
|
||||
Announcement.init({
|
||||
module.exports = (sequelize, DataTypes) =>
|
||||
sequelize.define('announcement', {
|
||||
title: DataTypes.STRING,
|
||||
announcement: DataTypes.STRING,
|
||||
visible: DataTypes.BOOLEAN
|
||||
}, { sequelize, modelName: 'announcement' })
|
||||
|
||||
module.exports = Announcement
|
||||
})
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
const sequelize = require('./index').sequelize
|
||||
const { Model, DataTypes } = require('sequelize')
|
||||
|
||||
class APUser extends Model {}
|
||||
|
||||
APUser.init({
|
||||
module.exports = (sequelize, DataTypes) =>
|
||||
sequelize.define('ap_user', {
|
||||
ap_id: {
|
||||
type: DataTypes.STRING,
|
||||
primaryKey: true
|
||||
|
@ -11,6 +8,4 @@ APUser.init({
|
|||
follower: DataTypes.BOOLEAN,
|
||||
blocked: DataTypes.BOOLEAN,
|
||||
object: DataTypes.JSON
|
||||
}, { sequelize, modelName: 'ap_user' })
|
||||
|
||||
module.exports = APUser
|
||||
})
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
const { Model, DataTypes } = require('sequelize')
|
||||
const sequelize = require('./index').sequelize
|
||||
|
||||
class Collection extends Model {}
|
||||
|
||||
// TODO: slugify!
|
||||
Collection.init({
|
||||
module.exports = (sequelize, DataTypes) =>
|
||||
sequelize.define('collection', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
|
@ -22,7 +17,4 @@ Collection.init({
|
|||
isTop: {
|
||||
type: DataTypes.BOOLEAN
|
||||
}
|
||||
}, { sequelize, modelName: 'collection', timestamps: false })
|
||||
|
||||
|
||||
module.exports = Collection
|
||||
}, { timestamps: false })
|
||||
|
|
|
@ -1,18 +1,5 @@
|
|||
const config = require('../../config')
|
||||
const { htmlToText } = require('html-to-text')
|
||||
|
||||
const { Model, DataTypes } = require('sequelize')
|
||||
const SequelizeSlugify = require('sequelize-slugify')
|
||||
|
||||
const sequelize = require('./index').sequelize
|
||||
|
||||
const Resource = require('./resource')
|
||||
const Notification = require('./notification')
|
||||
const EventNotification = require('./eventnotification')
|
||||
const Place = require('./place')
|
||||
const User = require('./user')
|
||||
const Tag = require('./tag')
|
||||
|
||||
const dayjs = require('dayjs')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
|
@ -20,9 +7,9 @@ const utc = require('dayjs/plugin/utc')
|
|||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
|
||||
class Event extends Model {}
|
||||
|
||||
Event.init({
|
||||
// class Event extends Model {}
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
const Event = sequelize.define('event', {
|
||||
id: {
|
||||
allowNull: false,
|
||||
type: DataTypes.INTEGER,
|
||||
|
@ -51,27 +38,7 @@ Event.init({
|
|||
recurrent: DataTypes.JSON,
|
||||
likes: { type: DataTypes.JSON, defaultValue: [] },
|
||||
boost: { type: DataTypes.JSON, defaultValue: [] }
|
||||
}, { sequelize, modelName: 'event' })
|
||||
|
||||
Event.belongsTo(Place)
|
||||
Place.hasMany(Event)
|
||||
|
||||
Event.belongsTo(User)
|
||||
User.hasMany(Event)
|
||||
|
||||
Event.belongsToMany(Tag, { through: 'event_tags' })
|
||||
Tag.belongsToMany(Event, { through: 'event_tags' })
|
||||
|
||||
Event.belongsToMany(Notification, { through: EventNotification })
|
||||
Notification.belongsToMany(Event, { through: EventNotification })
|
||||
|
||||
Event.hasMany(Resource)
|
||||
Resource.belongsTo(Event)
|
||||
|
||||
Event.hasMany(Event, { as: 'child', foreignKey: 'parentId' })
|
||||
Event.belongsTo(Event, { as: 'parent' })
|
||||
|
||||
SequelizeSlugify.slugifyModel(Event, { source: ['title'], overwrite: false })
|
||||
})
|
||||
|
||||
Event.prototype.toAP = function (username, locale, to = []) {
|
||||
const tags = this.tags && this.tags.map(t => t.tag.replace(/[ #]/g, '_'))
|
||||
|
@ -123,5 +90,5 @@ Event.prototype.toAP = function (username, locale, to = []) {
|
|||
summary: content
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Event
|
||||
return Event
|
||||
}
|
|
@ -1,15 +1,9 @@
|
|||
const sequelize = require('./index').sequelize
|
||||
const { Model, DataTypes } = require('sequelize')
|
||||
|
||||
class EventNotification extends Model {}
|
||||
|
||||
EventNotification.init({
|
||||
module.exports = (sequelize, DataTypes) =>
|
||||
sequelize.define('event_notification', {
|
||||
status: {
|
||||
type: DataTypes.ENUM,
|
||||
values: ['new', 'sent', 'error', 'sending'],
|
||||
defaultValue: 'new',
|
||||
index: true
|
||||
}
|
||||
}, { sequelize, modelName: 'event_notification' })
|
||||
|
||||
module.exports = EventNotification
|
||||
})
|
|
@ -1,10 +1,6 @@
|
|||
const { Model, DataTypes } = require('sequelize')
|
||||
const Collection = require('./collection')
|
||||
const sequelize = require('./index').sequelize
|
||||
|
||||
class Filter extends Model {}
|
||||
|
||||
Filter.init({
|
||||
module.exports = (sequelize, DataTypes) =>
|
||||
sequelize.define('filter',
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
|
@ -16,9 +12,9 @@ Filter.init({
|
|||
places: {
|
||||
type: DataTypes.JSON,
|
||||
}
|
||||
}, { sequelize, modelName: 'filter', timestamps: false })
|
||||
|
||||
Filter.belongsTo(Collection)
|
||||
Collection.hasMany(Filter)
|
||||
|
||||
module.exports = Filter
|
||||
}, {
|
||||
indexes: [
|
||||
{ fields: ['collectionId', 'tags', 'places'], unique: true }
|
||||
],
|
||||
timestamps: false
|
||||
})
|
|
@ -4,10 +4,77 @@ const Umzug = require('umzug')
|
|||
const path = require('path')
|
||||
const config = require('../../config')
|
||||
const log = require('../../log')
|
||||
const settingsController = require('../controller/settings')
|
||||
const SequelizeSlugify = require('sequelize-slugify')
|
||||
const DB = require('./models')
|
||||
|
||||
const models = {
|
||||
Announcement: require('./announcement'),
|
||||
APUser: require('./ap_user'),
|
||||
Collection: require('./collection'),
|
||||
Event: require('./event'),
|
||||
EventNotification: require('./eventnotification'),
|
||||
Filter: require('./filter'),
|
||||
Instance: require('./instance'),
|
||||
Notification: require('./notification'),
|
||||
OAuthClient: require('./oauth_client'),
|
||||
OAuthCode: require('./oauth_code'),
|
||||
OAuthToken: require('./oauth_token'),
|
||||
Place: require('./place'),
|
||||
Resource: require('./resource'),
|
||||
Setting: require('./setting'),
|
||||
Tag: require('./tag'),
|
||||
User: require('./user'),
|
||||
}
|
||||
|
||||
const db = {
|
||||
sequelize: null,
|
||||
loadModels () {
|
||||
for (const modelName in models) {
|
||||
const m = models[modelName](db.sequelize, Sequelize.DataTypes)
|
||||
DB[modelName] = m
|
||||
}
|
||||
|
||||
},
|
||||
associates () {
|
||||
const { Filter, Collection, APUser, Instance, User, Event, EventNotification, Tag,
|
||||
OAuthCode, OAuthClient, OAuthToken, Resource, Place, Notification } = DB
|
||||
|
||||
Filter.belongsTo(Collection)
|
||||
Collection.hasMany(Filter)
|
||||
|
||||
Instance.hasMany(APUser)
|
||||
APUser.belongsTo(Instance)
|
||||
|
||||
OAuthCode.belongsTo(User)
|
||||
OAuthCode.belongsTo(OAuthClient, { as: 'client' })
|
||||
|
||||
OAuthToken.belongsTo(User)
|
||||
OAuthToken.belongsTo(OAuthClient, { as: 'client' })
|
||||
|
||||
APUser.hasMany(Resource)
|
||||
Resource.belongsTo(APUser)
|
||||
|
||||
Event.belongsTo(Place)
|
||||
Place.hasMany(Event)
|
||||
|
||||
Event.belongsTo(User)
|
||||
User.hasMany(Event)
|
||||
|
||||
Event.belongsToMany(Tag, { through: 'event_tags' })
|
||||
Tag.belongsToMany(Event, { through: 'event_tags' })
|
||||
|
||||
Event.belongsToMany(Notification, { through: EventNotification })
|
||||
Notification.belongsToMany(Event, { through: EventNotification })
|
||||
|
||||
Event.hasMany(Resource)
|
||||
Resource.belongsTo(Event)
|
||||
|
||||
Event.hasMany(Event, { as: 'child', foreignKey: 'parentId' })
|
||||
Event.belongsTo(Event, { as: 'parent' })
|
||||
|
||||
SequelizeSlugify.slugifyModel(Event, { source: ['title'], overwrite: false })
|
||||
|
||||
},
|
||||
close() {
|
||||
if (db.sequelize) {
|
||||
return db.sequelize.close()
|
||||
|
@ -28,7 +95,6 @@ const db = {
|
|||
}
|
||||
}
|
||||
db.sequelize = new Sequelize(dbConf)
|
||||
return db.sequelize.authenticate()
|
||||
},
|
||||
async isEmpty() {
|
||||
try {
|
||||
|
@ -57,13 +123,12 @@ const db = {
|
|||
})
|
||||
return umzug.up()
|
||||
},
|
||||
async initialize() {
|
||||
initialize() {
|
||||
if (config.status === 'CONFIGURED') {
|
||||
try {
|
||||
await db.connect()
|
||||
log.debug('Running migrations')
|
||||
await db.runMigrations()
|
||||
return settingsController.load()
|
||||
db.connect()
|
||||
db.loadModels()
|
||||
db.associates()
|
||||
} catch (e) {
|
||||
log.warn(` ⚠️ Cannot connect to db, check your configuration => ${e}`)
|
||||
process.exit(1)
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
|
||||
const sequelize = require('./index').sequelize
|
||||
const { Model, DataTypes } = require('sequelize')
|
||||
const APUser = require('./ap_user')
|
||||
|
||||
class Instance extends Model {}
|
||||
|
||||
Instance.init({
|
||||
module.exports = (sequelize, DataTypes) =>
|
||||
sequelize.define('instance', {
|
||||
domain: {
|
||||
primaryKey: true,
|
||||
allowNull: false,
|
||||
|
@ -14,9 +8,4 @@ Instance.init({
|
|||
name: DataTypes.STRING,
|
||||
blocked: DataTypes.BOOLEAN,
|
||||
data: DataTypes.JSON
|
||||
}, { sequelize, modelName: 'instance' })
|
||||
|
||||
Instance.hasMany(APUser)
|
||||
APUser.belongsTo(Instance)
|
||||
|
||||
module.exports = Instance
|
||||
})
|
20
server/api/models/models.js
Normal file
20
server/api/models/models.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
// export default models
|
||||
|
||||
// Announcement: require('./announcement'),
|
||||
// APUser: require('./ap_user'),
|
||||
// Collection: require('./collection'),
|
||||
// Event: require('./event'),
|
||||
// EventNotification: require('./eventnotification'),
|
||||
// Filter: require('./filter'),
|
||||
// Instance: require('./instance'),
|
||||
// Notification: require('./notification'),
|
||||
// OAuthClient: require('./oauth_client'),
|
||||
// OAuthCode: require('./oauth_code'),
|
||||
// OAuthToken: require('./oauth_token'),
|
||||
// Place: require('./place'),
|
||||
// Resource: require('./resource'),
|
||||
// Setting: require('./setting'),
|
||||
// Tag: require('./tag'),
|
||||
// User: require('./user'),
|
||||
|
||||
module.exports = {}
|
|
@ -1,10 +1,5 @@
|
|||
|
||||
const sequelize = require('./index').sequelize
|
||||
const { Model, DataTypes } = require('sequelize')
|
||||
|
||||
class Notification extends Model {}
|
||||
|
||||
Notification.init({
|
||||
module.exports = (sequelize, DataTypes) =>
|
||||
sequelize.define('notification', {
|
||||
filters: DataTypes.JSON,
|
||||
email: DataTypes.STRING,
|
||||
remove_code: DataTypes.STRING,
|
||||
|
@ -25,5 +20,3 @@ Notification.init({
|
|||
fields: ['action', 'type']
|
||||
}]
|
||||
})
|
||||
|
||||
module.exports = Notification
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
|
||||
const sequelize = require('./index').sequelize
|
||||
const { Model, DataTypes } = require('sequelize')
|
||||
|
||||
class OAuthClient extends Model {}
|
||||
|
||||
OAuthClient.init({
|
||||
module.exports = (sequelize, DataTypes) =>
|
||||
sequelize.define('oauth_client', {
|
||||
id: {
|
||||
type: DataTypes.STRING,
|
||||
primaryKey: true,
|
||||
|
@ -15,6 +10,4 @@ OAuthClient.init({
|
|||
scopes: DataTypes.STRING,
|
||||
redirectUris: DataTypes.STRING,
|
||||
website: DataTypes.STRING
|
||||
}, { sequelize, modelName: 'oauth_client' })
|
||||
|
||||
module.exports = OAuthClient
|
||||
})
|
|
@ -1,13 +1,5 @@
|
|||
|
||||
const sequelize = require('./index').sequelize
|
||||
const { Model, DataTypes } = require('sequelize')
|
||||
|
||||
const User = require('./user')
|
||||
const OAuthClient = require('./oauth_client')
|
||||
|
||||
class OAuthCode extends Model {}
|
||||
|
||||
OAuthCode.init({
|
||||
module.exports = (sequelize, DataTypes) =>
|
||||
sequelize.define('oauth_code', {
|
||||
authorizationCode: {
|
||||
type: DataTypes.STRING,
|
||||
primaryKey: true
|
||||
|
@ -15,9 +7,4 @@ OAuthCode.init({
|
|||
expiresAt: DataTypes.DATE,
|
||||
scope: DataTypes.STRING,
|
||||
redirect_uri: DataTypes.STRING
|
||||
}, { sequelize, modelName: 'oauth_code' })
|
||||
|
||||
OAuthCode.belongsTo(User)
|
||||
OAuthCode.belongsTo(OAuthClient, { as: 'client' })
|
||||
|
||||
module.exports = OAuthCode
|
||||
})
|
|
@ -1,13 +1,5 @@
|
|||
|
||||
const sequelize = require('./index').sequelize
|
||||
const { Model, DataTypes } = require('sequelize')
|
||||
|
||||
const User = require('./user')
|
||||
const OAuthClient = require('./oauth_client')
|
||||
|
||||
class OAuthToken extends Model {}
|
||||
|
||||
OAuthToken.init({
|
||||
module.exports = (sequelize, DataTypes) =>
|
||||
sequelize.define('oauth_token', {
|
||||
accessToken: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
|
@ -27,9 +19,4 @@ OAuthToken.init({
|
|||
}
|
||||
},
|
||||
scope: DataTypes.STRING
|
||||
}, { sequelize, modelName: 'oauth_token' })
|
||||
|
||||
OAuthToken.belongsTo(User)
|
||||
OAuthToken.belongsTo(OAuthClient, { as: 'client' })
|
||||
|
||||
module.exports = OAuthToken
|
||||
})
|
|
@ -1,9 +1,5 @@
|
|||
const { Model, DataTypes } = require('sequelize')
|
||||
const sequelize = require('./index').sequelize
|
||||
|
||||
class Place extends Model {}
|
||||
|
||||
Place.init({
|
||||
module.exports = (sequelize, DataTypes) =>
|
||||
sequelize.define('place', {
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
unique: true,
|
||||
|
@ -13,6 +9,4 @@ Place.init({
|
|||
address: DataTypes.STRING,
|
||||
latitude: DataTypes.FLOAT,
|
||||
longitude: DataTypes.FLOAT,
|
||||
}, { sequelize, modelName: 'place' })
|
||||
|
||||
module.exports = Place
|
||||
})
|
|
@ -1,11 +1,5 @@
|
|||
const { Model, DataTypes } = require('sequelize')
|
||||
const sequelize = require('./index').sequelize
|
||||
|
||||
const APUser = require('./ap_user')
|
||||
|
||||
class Resource extends Model {}
|
||||
|
||||
Resource.init({
|
||||
module.exports = (sequelize, DataTypes) =>
|
||||
sequelize.define('resource', {
|
||||
activitypub_id: {
|
||||
type: DataTypes.STRING,
|
||||
index: true,
|
||||
|
@ -13,9 +7,4 @@ Resource.init({
|
|||
},
|
||||
hidden: DataTypes.BOOLEAN,
|
||||
data: DataTypes.JSON
|
||||
}, { sequelize, modelName: 'resource' })
|
||||
|
||||
APUser.hasMany(Resource)
|
||||
Resource.belongsTo(APUser)
|
||||
|
||||
module.exports = Resource
|
||||
})
|
|
@ -1,9 +1,5 @@
|
|||
const { Model, DataTypes } = require('sequelize')
|
||||
const sequelize = require('./index').sequelize
|
||||
|
||||
class Setting extends Model {}
|
||||
|
||||
Setting.init({
|
||||
module.exports = (sequelize, DataTypes) =>
|
||||
sequelize.define('setting', {
|
||||
key: {
|
||||
type: DataTypes.STRING,
|
||||
primaryKey: true,
|
||||
|
@ -12,6 +8,4 @@ Setting.init({
|
|||
},
|
||||
value: DataTypes.JSON,
|
||||
is_secret: DataTypes.BOOLEAN
|
||||
}, { sequelize, modelName: 'setting' })
|
||||
|
||||
module.exports = Setting
|
||||
})
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
const { Model, DataTypes } = require('sequelize')
|
||||
const sequelize = require('./index').sequelize
|
||||
|
||||
class Tag extends Model {}
|
||||
|
||||
Tag.init({
|
||||
module.exports = (sequelize, DataTypes) =>
|
||||
sequelize.define('tag', {
|
||||
tag: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
index: true,
|
||||
primaryKey: true
|
||||
}
|
||||
}, { sequelize, modelName: 'tag' })
|
||||
|
||||
module.exports = Tag
|
||||
})
|
|
@ -1,11 +1,8 @@
|
|||
|
||||
const bcrypt = require('bcryptjs')
|
||||
const { Model, DataTypes } = require('sequelize')
|
||||
const sequelize = require('./index').sequelize
|
||||
|
||||
class User extends Model {}
|
||||
|
||||
User.init({
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
const User = sequelize.define('user', {
|
||||
settings: {
|
||||
type: DataTypes.JSON,
|
||||
defaultValue: []
|
||||
|
@ -25,8 +22,6 @@ User.init({
|
|||
is_admin: DataTypes.BOOLEAN,
|
||||
is_active: DataTypes.BOOLEAN
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'user',
|
||||
scopes: {
|
||||
withoutPassword: {
|
||||
attributes: { exclude: ['password', 'recover_code'] }
|
||||
|
@ -50,4 +45,5 @@ User.beforeSave(async (user, _options) => {
|
|||
}
|
||||
})
|
||||
|
||||
module.exports = User
|
||||
return User
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ function _initializeDB () {
|
|||
async function modify (args) {
|
||||
await _initializeDB()
|
||||
const helpers = require('../helpers')
|
||||
const User = require('../api/models/user')
|
||||
const { User } = require('../api/models/models')
|
||||
const user = await User.findOne({ where: { email: args.account } })
|
||||
console.log()
|
||||
if (!user) {
|
||||
|
@ -33,7 +33,7 @@ async function modify (args) {
|
|||
|
||||
async function create (args) {
|
||||
await _initializeDB()
|
||||
const User = require('../api/models/user')
|
||||
const { User } = require('../api/models/models')
|
||||
const user = await User.create({
|
||||
email: args.email,
|
||||
is_active: true,
|
||||
|
@ -46,7 +46,7 @@ async function create (args) {
|
|||
|
||||
async function remove (args) {
|
||||
await _initializeDB()
|
||||
const User = require('../api/models/user')
|
||||
const { User } = require('../api/models/models')
|
||||
const user = await User.findOne({
|
||||
where: { email: args.email }
|
||||
})
|
||||
|
@ -58,7 +58,7 @@ async function remove (args) {
|
|||
|
||||
async function list () {
|
||||
await _initializeDB()
|
||||
const User = require('../api/models/user')
|
||||
const { User } = require('../api/models/models')
|
||||
const users = await User.findAll()
|
||||
console.log()
|
||||
users.forEach(u => console.log(`${u.id}\tadmin: ${u.is_admin}\tenabled: ${u.is_active}\temail: ${u.email}`))
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const Event = require('../api/models/event')
|
||||
const { Event } = require('../api/models/models')
|
||||
const config = require('../config')
|
||||
const log = require('../log')
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
const axios = require('axios')
|
||||
// const request = require('request')
|
||||
const crypto = require('crypto')
|
||||
const config = require('../config')
|
||||
const httpSignature = require('http-signature')
|
||||
const APUser = require('../api/models/ap_user')
|
||||
const Instance = require('../api/models/instance')
|
||||
|
||||
const { APUser, Instance } = require('../api/models/models')
|
||||
|
||||
const url = require('url')
|
||||
const settingsController = require('../api/controller/settings')
|
||||
const log = require('../log')
|
||||
|
|
|
@ -2,10 +2,8 @@ const express = require('express')
|
|||
const router = express.Router()
|
||||
const cors = require('cors')
|
||||
const Users = require('./users')
|
||||
const Event = require('../api/models/event')
|
||||
const User = require('../api/models/user')
|
||||
const Tag = require('../api/models/tag')
|
||||
const Place = require('../api/models/place')
|
||||
const { Event, User, Tag, Place } = require('../api/models/models')
|
||||
|
||||
const settingsController = require('../api/controller/settings')
|
||||
|
||||
const Helpers = require('./helpers')
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const cors = require('cors')
|
||||
const settingsController = require('../api/controller/settings')
|
||||
const config = require('../config')
|
||||
const version = require('../../package.json').version
|
||||
|
||||
router.use(cors())
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
const ret = {
|
||||
version: '1.0',
|
||||
server: {
|
||||
baseUrl: config.baseurl,
|
||||
name: config.title,
|
||||
software: 'Gancio',
|
||||
version
|
||||
},
|
||||
protocols: ['activitypub'],
|
||||
openRegistrations: settingsController.settings.allow_registration,
|
||||
usage: {
|
||||
users: {
|
||||
total: 10
|
||||
}
|
||||
},
|
||||
localPost: 3,
|
||||
localComments: 0
|
||||
}
|
||||
res.json(ret)
|
||||
})
|
||||
|
||||
module.exports = router
|
|
@ -1,6 +1,4 @@
|
|||
const Event = require('../api/models/event')
|
||||
const Resource = require('../api/models/resource')
|
||||
const APUser = require('../api/models/ap_user')
|
||||
const { Event, Resource, APUser } = require('../api/models/models')
|
||||
|
||||
const log = require('../log')
|
||||
const helpers = require('../helpers')
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
const Event = require('../api/models/event')
|
||||
const Place = require('../api/models/place')
|
||||
const APUser = require('../api/models/ap_user')
|
||||
const Tag = require('../api/models/tag')
|
||||
const { Event, Place, APUser, Tag } = require('../api/models/models')
|
||||
|
||||
const escape = require('lodash/escape')
|
||||
const config = require('../config')
|
||||
const log = require('../log')
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const Event = require('../api/models/event')
|
||||
const Resource = require('../api/models/resource')
|
||||
const User = require('../api/models/user')
|
||||
const { Event, Resource, User } = require('../api/models/models')
|
||||
|
||||
const cors = require('cors')
|
||||
const settingsController = require('../api/controller/settings')
|
||||
|
@ -65,7 +63,9 @@ router.get('/nodeinfo/:nodeinfo_version', async (req, res) => {
|
|||
metadata: {
|
||||
nodeDescription: settings.description,
|
||||
nodeName: settings.title,
|
||||
nodeLabel: settings.instance_place
|
||||
nodeLabel: settings.instance_place,
|
||||
nodeTimezone: settings.instance_timezone,
|
||||
nodeActor: settings.instance_name
|
||||
},
|
||||
openRegistrations: settings.allow_registration,
|
||||
protocols: ['activitypub'],
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
const ical = require('ical.js')
|
||||
const settingsController = require('./api/controller/settings')
|
||||
const acceptLanguage = require('accept-language')
|
||||
const express = require('express')
|
||||
const dayjs = require('dayjs')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
|
@ -15,7 +14,6 @@ const axios = require('axios')
|
|||
const crypto = require('crypto')
|
||||
const Microformats = require('microformat-node')
|
||||
const get = require('lodash/get')
|
||||
const cloneDeep = require('lodash/cloneDeep')
|
||||
|
||||
const DOMPurify = require('dompurify')
|
||||
const { JSDOM } = require('jsdom')
|
||||
|
@ -80,6 +78,7 @@ module.exports = {
|
|||
allow_registration: settings.allow_registration,
|
||||
allow_anon_event: settings.allow_anon_event,
|
||||
allow_recurrent_event: settings.allow_recurrent_event,
|
||||
allow_multidate_event: settings.allow_multidate_event,
|
||||
recurrent_event_visible: settings.recurrent_event_visible,
|
||||
enable_federation: settings.enable_federation,
|
||||
enable_resources: settings.enable_resources,
|
||||
|
@ -92,6 +91,11 @@ module.exports = {
|
|||
hide_thumbs: settings.hide_thumbs,
|
||||
hide_calendar: settings.hide_calendar,
|
||||
allow_geolocation: settings.allow_geolocation,
|
||||
geocoding_provider_type: settings.geocoding_provider_type,
|
||||
geocoding_provider: settings.geocoding_provider,
|
||||
geocoding_countrycodes: settings.geocoding_countrycodes,
|
||||
tilelayer_provider: settings.tilelayer_provider,
|
||||
tilelayer_provider_attribution: settings.tilelayer_provider_attribution,
|
||||
footerLinks: settings.footerLinks,
|
||||
about: settings.about
|
||||
}
|
||||
|
@ -113,14 +117,14 @@ module.exports = {
|
|||
log.warn(err)
|
||||
} else {
|
||||
res.status(404).send('Not found (but nice try 😊)')
|
||||
// }
|
||||
}
|
||||
}})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
router.use('/fallbackimage.png', (req, res, next) => {
|
||||
const fallbackImagePath = settingsController.settings.fallback_image || './static/noimg.svg'
|
||||
return express.static(fallbackImagePath, { maxAge: '1d' })(req, res, next)
|
||||
return express.static(fallbackImagePath)(req, res, next)
|
||||
})
|
||||
|
||||
router.use('/headerimage.png', (req, res, next) => {
|
||||
|
@ -130,12 +134,12 @@ module.exports = {
|
|||
|
||||
router.use('/logo.png', (req, res, next) => {
|
||||
const logoPath = settingsController.settings.logo || './static/gancio'
|
||||
return express.static(logoPath + '.png', {maxAge: '1d'})(req, res, next)
|
||||
return express.static(logoPath + '.png')(req, res, next)
|
||||
})
|
||||
|
||||
router.use('/favicon.ico', (req, res, next) => {
|
||||
const faviconPath = res.locals.settings.logo ? res.locals.settings.logo + '.png' : './assets/favicon.ico'
|
||||
return express.static(faviconPath, {maxAge: '1d'})(req, res, next)
|
||||
return express.static(faviconPath)(req, res, next)
|
||||
})
|
||||
|
||||
return router
|
||||
|
@ -264,9 +268,9 @@ module.exports = {
|
|||
},
|
||||
|
||||
async APRedirect(req, res, next) {
|
||||
const eventController = require('../server/api/controller/event')
|
||||
const acceptJson = req.accepts('html', 'application/activity+json') === 'application/activity+json'
|
||||
if (acceptJson) {
|
||||
const eventController = require('../server/api/controller/event')
|
||||
const event = await eventController._get(req.params.slug)
|
||||
if (event) {
|
||||
return res.redirect(`/federation/m/${event.id}`)
|
||||
|
@ -281,5 +285,13 @@ module.exports = {
|
|||
return res.redirect((accepted === 'application/rss+xml' ? '/feed/rss' : '/feed/ics') + req.path)
|
||||
}
|
||||
next()
|
||||
},
|
||||
|
||||
async isGeocodingEnabled(req, res, next) {
|
||||
if (res.locals.settings.allow_geolocation) {
|
||||
next()
|
||||
} else {
|
||||
res.sendStatus(403)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
987
server/helpers/geolocation.js
Normal file
987
server/helpers/geolocation.js
Normal file
|
@ -0,0 +1,987 @@
|
|||
// Iso conversions
|
||||
|
||||
var isoCountries = [
|
||||
{
|
||||
"code": "af",
|
||||
"name": "Afghanistan"
|
||||
},
|
||||
{
|
||||
"code": "ax",
|
||||
"name": "Aland Islands"
|
||||
},
|
||||
{
|
||||
"code": "al",
|
||||
"name": "Albania"
|
||||
},
|
||||
{
|
||||
"code": "dz",
|
||||
"name": "Algeria"
|
||||
},
|
||||
{
|
||||
"code": "as",
|
||||
"name": "American Samoa"
|
||||
},
|
||||
{
|
||||
"code": "ad",
|
||||
"name": "Andorra"
|
||||
},
|
||||
{
|
||||
"code": "ao",
|
||||
"name": "Angola"
|
||||
},
|
||||
{
|
||||
"code": "ai",
|
||||
"name": "Anguilla"
|
||||
},
|
||||
{
|
||||
"code": "aq",
|
||||
"name": "Antarctica"
|
||||
},
|
||||
{
|
||||
"code": "ag",
|
||||
"name": "Antigua And Barbuda"
|
||||
},
|
||||
{
|
||||
"code": "ar",
|
||||
"name": "Argentina"
|
||||
},
|
||||
{
|
||||
"code": "am",
|
||||
"name": "Armenia"
|
||||
},
|
||||
{
|
||||
"code": "aw",
|
||||
"name": "Aruba"
|
||||
},
|
||||
{
|
||||
"code": "au",
|
||||
"name": "Australia"
|
||||
},
|
||||
{
|
||||
"code": "at",
|
||||
"name": "Austria"
|
||||
},
|
||||
{
|
||||
"code": "az",
|
||||
"name": "Azerbaijan"
|
||||
},
|
||||
{
|
||||
"code": "bs",
|
||||
"name": "Bahamas"
|
||||
},
|
||||
{
|
||||
"code": "bh",
|
||||
"name": "Bahrain"
|
||||
},
|
||||
{
|
||||
"code": "bd",
|
||||
"name": "Bangladesh"
|
||||
},
|
||||
{
|
||||
"code": "bb",
|
||||
"name": "Barbados"
|
||||
},
|
||||
{
|
||||
"code": "by",
|
||||
"name": "Belarus"
|
||||
},
|
||||
{
|
||||
"code": "be",
|
||||
"name": "Belgium"
|
||||
},
|
||||
{
|
||||
"code": "bz",
|
||||
"name": "Belize"
|
||||
},
|
||||
{
|
||||
"code": "bj",
|
||||
"name": "Benin"
|
||||
},
|
||||
{
|
||||
"code": "bm",
|
||||
"name": "Bermuda"
|
||||
},
|
||||
{
|
||||
"code": "bt",
|
||||
"name": "Bhutan"
|
||||
},
|
||||
{
|
||||
"code": "bo",
|
||||
"name": "Bolivia"
|
||||
},
|
||||
{
|
||||
"code": "ba",
|
||||
"name": "Bosnia And Herzegovina"
|
||||
},
|
||||
{
|
||||
"code": "bw",
|
||||
"name": "Botswana"
|
||||
},
|
||||
{
|
||||
"code": "bv",
|
||||
"name": "Bouvet Island"
|
||||
},
|
||||
{
|
||||
"code": "br",
|
||||
"name": "Brazil"
|
||||
},
|
||||
{
|
||||
"code": "io",
|
||||
"name": "British Indian Ocean Territory"
|
||||
},
|
||||
{
|
||||
"code": "bn",
|
||||
"name": "Brunei Darussalam"
|
||||
},
|
||||
{
|
||||
"code": "bg",
|
||||
"name": "Bulgaria"
|
||||
},
|
||||
{
|
||||
"code": "bf",
|
||||
"name": "Burkina Faso"
|
||||
},
|
||||
{
|
||||
"code": "bi",
|
||||
"name": "Burundi"
|
||||
},
|
||||
{
|
||||
"code": "kh",
|
||||
"name": "Cambodia"
|
||||
},
|
||||
{
|
||||
"code": "cm",
|
||||
"name": "Cameroon"
|
||||
},
|
||||
{
|
||||
"code": "ca",
|
||||
"name": "Canada"
|
||||
},
|
||||
{
|
||||
"code": "cv",
|
||||
"name": "Cape Verde"
|
||||
},
|
||||
{
|
||||
"code": "ky",
|
||||
"name": "Cayman Islands"
|
||||
},
|
||||
{
|
||||
"code": "cf",
|
||||
"name": "Central African Republic"
|
||||
},
|
||||
{
|
||||
"code": "td",
|
||||
"name": "Chad"
|
||||
},
|
||||
{
|
||||
"code": "cl",
|
||||
"name": "Chile"
|
||||
},
|
||||
{
|
||||
"code": "cn",
|
||||
"name": "China"
|
||||
},
|
||||
{
|
||||
"code": "cx",
|
||||
"name": "Christmas Island"
|
||||
},
|
||||
{
|
||||
"code": "cc",
|
||||
"name": "Cocos (Keeling) Islands"
|
||||
},
|
||||
{
|
||||
"code": "co",
|
||||
"name": "Colombia"
|
||||
},
|
||||
{
|
||||
"code": "km",
|
||||
"name": "Comoros"
|
||||
},
|
||||
{
|
||||
"code": "cg",
|
||||
"name": "Congo"
|
||||
},
|
||||
{
|
||||
"code": "cd",
|
||||
"name": "Congo, Democratic Republic"
|
||||
},
|
||||
{
|
||||
"code": "ck",
|
||||
"name": "Cook Islands"
|
||||
},
|
||||
{
|
||||
"code": "cr",
|
||||
"name": "Costa Rica"
|
||||
},
|
||||
{
|
||||
"code": "ci",
|
||||
"name": "Cote D'Ivoire"
|
||||
},
|
||||
{
|
||||
"code": "hr",
|
||||
"name": "Croatia"
|
||||
},
|
||||
{
|
||||
"code": "cu",
|
||||
"name": "Cuba"
|
||||
},
|
||||
{
|
||||
"code": "cy",
|
||||
"name": "Cyprus"
|
||||
},
|
||||
{
|
||||
"code": "cz",
|
||||
"name": "Czech Republic"
|
||||
},
|
||||
{
|
||||
"code": "dk",
|
||||
"name": "Denmark"
|
||||
},
|
||||
{
|
||||
"code": "dj",
|
||||
"name": "Djibouti"
|
||||
},
|
||||
{
|
||||
"code": "dm",
|
||||
"name": "Dominica"
|
||||
},
|
||||
{
|
||||
"code": "do",
|
||||
"name": "Dominican Republic"
|
||||
},
|
||||
{
|
||||
"code": "ec",
|
||||
"name": "Ecuador"
|
||||
},
|
||||
{
|
||||
"code": "eg",
|
||||
"name": "Egypt"
|
||||
},
|
||||
{
|
||||
"code": "sv",
|
||||
"name": "El Salvador"
|
||||
},
|
||||
{
|
||||
"code": "gq",
|
||||
"name": "Equatorial Guinea"
|
||||
},
|
||||
{
|
||||
"code": "er",
|
||||
"name": "Eritrea"
|
||||
},
|
||||
{
|
||||
"code": "ee",
|
||||
"name": "Estonia"
|
||||
},
|
||||
{
|
||||
"code": "et",
|
||||
"name": "Ethiopia"
|
||||
},
|
||||
{
|
||||
"code": "fk",
|
||||
"name": "Falkland Islands (Malvinas)"
|
||||
},
|
||||
{
|
||||
"code": "fo",
|
||||
"name": "Faroe Islands"
|
||||
},
|
||||
{
|
||||
"code": "fj",
|
||||
"name": "Fiji"
|
||||
},
|
||||
{
|
||||
"code": "fi",
|
||||
"name": "Finland"
|
||||
},
|
||||
{
|
||||
"code": "fr",
|
||||
"name": "France"
|
||||
},
|
||||
{
|
||||
"code": "gf",
|
||||
"name": "French Guiana"
|
||||
},
|
||||
{
|
||||
"code": "pf",
|
||||
"name": "French Polynesia"
|
||||
},
|
||||
{
|
||||
"code": "tf",
|
||||
"name": "French Southern Territories"
|
||||
},
|
||||
{
|
||||
"code": "ga",
|
||||
"name": "Gabon"
|
||||
},
|
||||
{
|
||||
"code": "gm",
|
||||
"name": "Gambia"
|
||||
},
|
||||
{
|
||||
"code": "ge",
|
||||
"name": "Georgia"
|
||||
},
|
||||
{
|
||||
"code": "de",
|
||||
"name": "Germany"
|
||||
},
|
||||
{
|
||||
"code": "gh",
|
||||
"name": "Ghana"
|
||||
},
|
||||
{
|
||||
"code": "gi",
|
||||
"name": "Gibraltar"
|
||||
},
|
||||
{
|
||||
"code": "gr",
|
||||
"name": "Greece"
|
||||
},
|
||||
{
|
||||
"code": "gl",
|
||||
"name": "Greenland"
|
||||
},
|
||||
{
|
||||
"code": "gd",
|
||||
"name": "Grenada"
|
||||
},
|
||||
{
|
||||
"code": "gp",
|
||||
"name": "Guadeloupe"
|
||||
},
|
||||
{
|
||||
"code": "gu",
|
||||
"name": "Guam"
|
||||
},
|
||||
{
|
||||
"code": "gt",
|
||||
"name": "Guatemala"
|
||||
},
|
||||
{
|
||||
"code": "gg",
|
||||
"name": "Guernsey"
|
||||
},
|
||||
{
|
||||
"code": "gn",
|
||||
"name": "Guinea"
|
||||
},
|
||||
{
|
||||
"code": "gw",
|
||||
"name": "Guinea-Bissau"
|
||||
},
|
||||
{
|
||||
"code": "gy",
|
||||
"name": "Guyana"
|
||||
},
|
||||
{
|
||||
"code": "ht",
|
||||
"name": "Haiti"
|
||||
},
|
||||
{
|
||||
"code": "hm",
|
||||
"name": "Heard Island & Mcdonald Islands"
|
||||
},
|
||||
{
|
||||
"code": "va",
|
||||
"name": "Holy See (Vatican City State)"
|
||||
},
|
||||
{
|
||||
"code": "hn",
|
||||
"name": "Honduras"
|
||||
},
|
||||
{
|
||||
"code": "hk",
|
||||
"name": "Hong Kong"
|
||||
},
|
||||
{
|
||||
"code": "hu",
|
||||
"name": "Hungary"
|
||||
},
|
||||
{
|
||||
"code": "is",
|
||||
"name": "Iceland"
|
||||
},
|
||||
{
|
||||
"code": "in",
|
||||
"name": "India"
|
||||
},
|
||||
{
|
||||
"code": "id",
|
||||
"name": "Indonesia"
|
||||
},
|
||||
{
|
||||
"code": "ir",
|
||||
"name": "Iran, Islamic Republic Of"
|
||||
},
|
||||
{
|
||||
"code": "iq",
|
||||
"name": "Iraq"
|
||||
},
|
||||
{
|
||||
"code": "ie",
|
||||
"name": "Ireland"
|
||||
},
|
||||
{
|
||||
"code": "im",
|
||||
"name": "Isle Of Man"
|
||||
},
|
||||
{
|
||||
"code": "il",
|
||||
"name": "Israel"
|
||||
},
|
||||
{
|
||||
"code": "it",
|
||||
"name": "Italy"
|
||||
},
|
||||
{
|
||||
"code": "jm",
|
||||
"name": "Jamaica"
|
||||
},
|
||||
{
|
||||
"code": "jp",
|
||||
"name": "Japan"
|
||||
},
|
||||
{
|
||||
"code": "je",
|
||||
"name": "Jersey"
|
||||
},
|
||||
{
|
||||
"code": "jo",
|
||||
"name": "Jordan"
|
||||
},
|
||||
{
|
||||
"code": "kz",
|
||||
"name": "Kazakhstan"
|
||||
},
|
||||
{
|
||||
"code": "ke",
|
||||
"name": "Kenya"
|
||||
},
|
||||
{
|
||||
"code": "ki",
|
||||
"name": "Kiribati"
|
||||
},
|
||||
{
|
||||
"code": "kr",
|
||||
"name": "Korea"
|
||||
},
|
||||
{
|
||||
"code": "kw",
|
||||
"name": "Kuwait"
|
||||
},
|
||||
{
|
||||
"code": "kg",
|
||||
"name": "Kyrgyzstan"
|
||||
},
|
||||
{
|
||||
"code": "la",
|
||||
"name": "Lao People's Democratic Republic"
|
||||
},
|
||||
{
|
||||
"code": "lv",
|
||||
"name": "Latvia"
|
||||
},
|
||||
{
|
||||
"code": "lb",
|
||||
"name": "Lebanon"
|
||||
},
|
||||
{
|
||||
"code": "ls",
|
||||
"name": "Lesotho"
|
||||
},
|
||||
{
|
||||
"code": "lr",
|
||||
"name": "Liberia"
|
||||
},
|
||||
{
|
||||
"code": "ly",
|
||||
"name": "Libyan Arab Jamahiriya"
|
||||
},
|
||||
{
|
||||
"code": "li",
|
||||
"name": "Liechtenstein"
|
||||
},
|
||||
{
|
||||
"code": "lt",
|
||||
"name": "Lithuania"
|
||||
},
|
||||
{
|
||||
"code": "lu",
|
||||
"name": "Luxembourg"
|
||||
},
|
||||
{
|
||||
"code": "mo",
|
||||
"name": "Macao"
|
||||
},
|
||||
{
|
||||
"code": "mk",
|
||||
"name": "Macedonia"
|
||||
},
|
||||
{
|
||||
"code": "mg",
|
||||
"name": "Madagascar"
|
||||
},
|
||||
{
|
||||
"code": "mw",
|
||||
"name": "Malawi"
|
||||
},
|
||||
{
|
||||
"code": "my",
|
||||
"name": "Malaysia"
|
||||
},
|
||||
{
|
||||
"code": "mv",
|
||||
"name": "Maldives"
|
||||
},
|
||||
{
|
||||
"code": "ml",
|
||||
"name": "Mali"
|
||||
},
|
||||
{
|
||||
"code": "mt",
|
||||
"name": "Malta"
|
||||
},
|
||||
{
|
||||
"code": "mh",
|
||||
"name": "Marshall Islands"
|
||||
},
|
||||
{
|
||||
"code": "mq",
|
||||
"name": "Martinique"
|
||||
},
|
||||
{
|
||||
"code": "mr",
|
||||
"name": "Mauritania"
|
||||
},
|
||||
{
|
||||
"code": "mu",
|
||||
"name": "Mauritius"
|
||||
},
|
||||
{
|
||||
"code": "yt",
|
||||
"name": "Mayotte"
|
||||
},
|
||||
{
|
||||
"code": "mx",
|
||||
"name": "Mexico"
|
||||
},
|
||||
{
|
||||
"code": "fm",
|
||||
"name": "Micronesia, Federated States Of"
|
||||
},
|
||||
{
|
||||
"code": "md",
|
||||
"name": "Moldova"
|
||||
},
|
||||
{
|
||||
"code": "mc",
|
||||
"name": "Monaco"
|
||||
},
|
||||
{
|
||||
"code": "mn",
|
||||
"name": "Mongolia"
|
||||
},
|
||||
{
|
||||
"code": "me",
|
||||
"name": "Montenegro"
|
||||
},
|
||||
{
|
||||
"code": "ms",
|
||||
"name": "Montserrat"
|
||||
},
|
||||
{
|
||||
"code": "ma",
|
||||
"name": "Morocco"
|
||||
},
|
||||
{
|
||||
"code": "mz",
|
||||
"name": "Mozambique"
|
||||
},
|
||||
{
|
||||
"code": "mm",
|
||||
"name": "Myanmar"
|
||||
},
|
||||
{
|
||||
"code": "na",
|
||||
"name": "Namibia"
|
||||
},
|
||||
{
|
||||
"code": "nr",
|
||||
"name": "Nauru"
|
||||
},
|
||||
{
|
||||
"code": "np",
|
||||
"name": "Nepal"
|
||||
},
|
||||
{
|
||||
"code": "nl",
|
||||
"name": "Netherlands"
|
||||
},
|
||||
{
|
||||
"code": "an",
|
||||
"name": "Netherlands Antilles"
|
||||
},
|
||||
{
|
||||
"code": "nc",
|
||||
"name": "New Caledonia"
|
||||
},
|
||||
{
|
||||
"code": "nz",
|
||||
"name": "New Zealand"
|
||||
},
|
||||
{
|
||||
"code": "ni",
|
||||
"name": "Nicaragua"
|
||||
},
|
||||
{
|
||||
"code": "ne",
|
||||
"name": "Niger"
|
||||
},
|
||||
{
|
||||
"code": "ng",
|
||||
"name": "Nigeria"
|
||||
},
|
||||
{
|
||||
"code": "nu",
|
||||
"name": "Niue"
|
||||
},
|
||||
{
|
||||
"code": "nf",
|
||||
"name": "Norfolk Island"
|
||||
},
|
||||
{
|
||||
"code": "mp",
|
||||
"name": "Northern Mariana Islands"
|
||||
},
|
||||
{
|
||||
"code": "no",
|
||||
"name": "Norway"
|
||||
},
|
||||
{
|
||||
"code": "om",
|
||||
"name": "Oman"
|
||||
},
|
||||
{
|
||||
"code": "pk",
|
||||
"name": "Pakistan"
|
||||
},
|
||||
{
|
||||
"code": "pw",
|
||||
"name": "Palau"
|
||||
},
|
||||
{
|
||||
"code": "ps",
|
||||
"name": "Palestinian Territory, Occupied"
|
||||
},
|
||||
{
|
||||
"code": "pa",
|
||||
"name": "Panama"
|
||||
},
|
||||
{
|
||||
"code": "pg",
|
||||
"name": "Papua New Guinea"
|
||||
},
|
||||
{
|
||||
"code": "py",
|
||||
"name": "Paraguay"
|
||||
},
|
||||
{
|
||||
"code": "pe",
|
||||
"name": "Peru"
|
||||
},
|
||||
{
|
||||
"code": "ph",
|
||||
"name": "Philippines"
|
||||
},
|
||||
{
|
||||
"code": "pn",
|
||||
"name": "Pitcairn"
|
||||
},
|
||||
{
|
||||
"code": "pl",
|
||||
"name": "Poland"
|
||||
},
|
||||
{
|
||||
"code": "pt",
|
||||
"name": "Portugal"
|
||||
},
|
||||
{
|
||||
"code": "pr",
|
||||
"name": "Puerto Rico"
|
||||
},
|
||||
{
|
||||
"code": "qa",
|
||||
"name": "Qatar"
|
||||
},
|
||||
{
|
||||
"code": "re",
|
||||
"name": "Reunion"
|
||||
},
|
||||
{
|
||||
"code": "ro",
|
||||
"name": "Romania"
|
||||
},
|
||||
{
|
||||
"code": "ru",
|
||||
"name": "Russian Federation"
|
||||
},
|
||||
{
|
||||
"code": "rw",
|
||||
"name": "Rwanda"
|
||||
},
|
||||
{
|
||||
"code": "bl",
|
||||
"name": "Saint Barthelemy"
|
||||
},
|
||||
{
|
||||
"code": "sh",
|
||||
"name": "Saint Helena"
|
||||
},
|
||||
{
|
||||
"code": "kn",
|
||||
"name": "Saint Kitts And Nevis"
|
||||
},
|
||||
{
|
||||
"code": "lc",
|
||||
"name": "Saint Lucia"
|
||||
},
|
||||
{
|
||||
"code": "mf",
|
||||
"name": "Saint Martin"
|
||||
},
|
||||
{
|
||||
"code": "pm",
|
||||
"name": "Saint Pierre And Miquelon"
|
||||
},
|
||||
{
|
||||
"code": "vc",
|
||||
"name": "Saint Vincent And Grenadines"
|
||||
},
|
||||
{
|
||||
"code": "ws",
|
||||
"name": "Samoa"
|
||||
},
|
||||
{
|
||||
"code": "sm",
|
||||
"name": "San Marino"
|
||||
},
|
||||
{
|
||||
"code": "st",
|
||||
"name": "Sao Tome And Principe"
|
||||
},
|
||||
{
|
||||
"code": "sa",
|
||||
"name": "Saudi Arabia"
|
||||
},
|
||||
{
|
||||
"code": "sn",
|
||||
"name": "Senegal"
|
||||
},
|
||||
{
|
||||
"code": "rs",
|
||||
"name": "Serbia"
|
||||
},
|
||||
{
|
||||
"code": "sc",
|
||||
"name": "Seychelles"
|
||||
},
|
||||
{
|
||||
"code": "sl",
|
||||
"name": "Sierra Leone"
|
||||
},
|
||||
{
|
||||
"code": "sg",
|
||||
"name": "Singapore"
|
||||
},
|
||||
{
|
||||
"code": "sk",
|
||||
"name": "Slovakia"
|
||||
},
|
||||
{
|
||||
"code": "si",
|
||||
"name": "Slovenia"
|
||||
},
|
||||
{
|
||||
"code": "sb",
|
||||
"name": "Solomon Islands"
|
||||
},
|
||||
{
|
||||
"code": "so",
|
||||
"name": "Somalia"
|
||||
},
|
||||
{
|
||||
"code": "za",
|
||||
"name": "South Africa"
|
||||
},
|
||||
{
|
||||
"code": "gs",
|
||||
"name": "South Georgia And Sandwich Isl."
|
||||
},
|
||||
{
|
||||
"code": "es",
|
||||
"name": "Spain"
|
||||
},
|
||||
{
|
||||
"code": "lk",
|
||||
"name": "Sri Lanka"
|
||||
},
|
||||
{
|
||||
"code": "sd",
|
||||
"name": "Sudan"
|
||||
},
|
||||
{
|
||||
"code": "sr",
|
||||
"name": "Suriname"
|
||||
},
|
||||
{
|
||||
"code": "sj",
|
||||
"name": "Svalbard And Jan Mayen"
|
||||
},
|
||||
{
|
||||
"code": "sz",
|
||||
"name": "Swaziland"
|
||||
},
|
||||
{
|
||||
"code": "se",
|
||||
"name": "Sweden"
|
||||
},
|
||||
{
|
||||
"code": "ch",
|
||||
"name": "Switzerland"
|
||||
},
|
||||
{
|
||||
"code": "sy",
|
||||
"name": "Syrian Arab Republic"
|
||||
},
|
||||
{
|
||||
"code": "tw",
|
||||
"name": "Taiwan"
|
||||
},
|
||||
{
|
||||
"code": "tj",
|
||||
"name": "Tajikistan"
|
||||
},
|
||||
{
|
||||
"code": "tz",
|
||||
"name": "Tanzania"
|
||||
},
|
||||
{
|
||||
"code": "th",
|
||||
"name": "Thailand"
|
||||
},
|
||||
{
|
||||
"code": "tl",
|
||||
"name": "Timor-Leste"
|
||||
},
|
||||
{
|
||||
"code": "tg",
|
||||
"name": "Togo"
|
||||
},
|
||||
{
|
||||
"code": "tk",
|
||||
"name": "Tokelau"
|
||||
},
|
||||
{
|
||||
"code": "to",
|
||||
"name": "Tonga"
|
||||
},
|
||||
{
|
||||
"code": "tt",
|
||||
"name": "Trinidad And Tobago"
|
||||
},
|
||||
{
|
||||
"code": "tn",
|
||||
"name": "Tunisia"
|
||||
},
|
||||
{
|
||||
"code": "tr",
|
||||
"name": "Turkey"
|
||||
},
|
||||
{
|
||||
"code": "tm",
|
||||
"name": "Turkmenistan"
|
||||
},
|
||||
{
|
||||
"code": "tc",
|
||||
"name": "Turks And Caicos Islands"
|
||||
},
|
||||
{
|
||||
"code": "tv",
|
||||
"name": "Tuvalu"
|
||||
},
|
||||
{
|
||||
"code": "ug",
|
||||
"name": "Uganda"
|
||||
},
|
||||
{
|
||||
"code": "ua",
|
||||
"name": "Ukraine"
|
||||
},
|
||||
{
|
||||
"code": "ae",
|
||||
"name": "United Arab Emirates"
|
||||
},
|
||||
{
|
||||
"code": "gb",
|
||||
"name": "United Kingdom"
|
||||
},
|
||||
{
|
||||
"code": "us",
|
||||
"name": "United States"
|
||||
},
|
||||
{
|
||||
"code": "um",
|
||||
"name": "United States Outlying Islands"
|
||||
},
|
||||
{
|
||||
"code": "uy",
|
||||
"name": "Uruguay"
|
||||
},
|
||||
{
|
||||
"code": "uz",
|
||||
"name": "Uzbekistan"
|
||||
},
|
||||
{
|
||||
"code": "vu",
|
||||
"name": "Vanuatu"
|
||||
},
|
||||
{
|
||||
"code": "ve",
|
||||
"name": "Venezuela"
|
||||
},
|
||||
{
|
||||
"code": "vn",
|
||||
"name": "Viet Nam"
|
||||
},
|
||||
{
|
||||
"code": "vg",
|
||||
"name": "Virgin Islands, British"
|
||||
},
|
||||
{
|
||||
"code": "vi",
|
||||
"name": "Virgin Islands, U.S."
|
||||
},
|
||||
{
|
||||
"code": "wf",
|
||||
"name": "Wallis And Futuna"
|
||||
},
|
||||
{
|
||||
"code": "eh",
|
||||
"name": "Western Sahara"
|
||||
},
|
||||
{
|
||||
"code": "ye",
|
||||
"name": "Yemen"
|
||||
},
|
||||
{
|
||||
"code": "zm",
|
||||
"name": "Zambia"
|
||||
},
|
||||
{
|
||||
"code": "zw",
|
||||
"name": "Zimbabwe"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
module.exports = { isoCountries }
|
|
@ -1,5 +1,4 @@
|
|||
const Place = require('../api/models/place')
|
||||
const Event = require('../api/models/event')
|
||||
const { Event, Place } = require('../api/models/models')
|
||||
const Sequelize = require('sequelize')
|
||||
const log = require('../log')
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const Tag = require('../api/models/tag')
|
||||
const Event = require('../api/models/event')
|
||||
const { Event, Tag } = require('../api/models/models')
|
||||
|
||||
const Sequelize = require('sequelize')
|
||||
const log = require('../log')
|
||||
|
||||
|
@ -13,7 +13,7 @@ module.exports = {
|
|||
})
|
||||
|
||||
if (!tags.length) { return }
|
||||
log.info(`Remove ${tags.length} unrelated tags`)
|
||||
log.info(`Remove ${tags.length} orphan tags (${tags.join(', ')})`)
|
||||
|
||||
await Tag.destroy({
|
||||
where: { tag: { [Sequelize.Op.in]: tags.map(p => p.tag) } }
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
const config = require('../server/config')
|
||||
const db = require('./api/models/index')
|
||||
const log = require('../server/log')
|
||||
|
||||
db.initialize()
|
||||
|
||||
const settingsController = require('./api/controller/settings')
|
||||
|
||||
|
||||
const initialize = {
|
||||
// close connections/port/unix socket
|
||||
|
@ -19,14 +26,14 @@ const initialize = {
|
|||
},
|
||||
|
||||
async start () {
|
||||
const log = require('../server/log')
|
||||
const settingsController = require('./api/controller/settings')
|
||||
const db = require('./api/models/index')
|
||||
const dayjs = require('dayjs')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
dayjs.extend(timezone)
|
||||
if (config.status == 'CONFIGURED') {
|
||||
await db.initialize()
|
||||
await db.sequelize.authenticate()
|
||||
log.debug('Running migrations')
|
||||
await db.runMigrations()
|
||||
await settingsController.load()
|
||||
config.status = 'READY'
|
||||
} else {
|
||||
if (process.env.GANCIO_DB_DIALECT) {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue