Merge branch 'master' into gh
This commit is contained in:
commit
616c54229a
110 changed files with 5909 additions and 2199 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,6 +1,8 @@
|
||||||
# Created by .ignore support plugin (hsz.mobi)
|
# Created by .ignore support plugin (hsz.mobi)
|
||||||
|
|
||||||
### Gancio dev configuration
|
### Gancio dev configuration
|
||||||
|
tests/seeds/testdb.sqlite
|
||||||
|
preso.md
|
||||||
gancio.sqlite
|
gancio.sqlite
|
||||||
db.sqlite
|
db.sqlite
|
||||||
releases
|
releases
|
||||||
|
|
34
CHANGELOG
34
CHANGELOG
|
@ -1,7 +1,37 @@
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
### UNRELEASED
|
### 1.5.0 - UNRELEASED
|
||||||
- add CLI support to manage accounts (list / modify / add accounts)
|
- new Tag page!
|
||||||
|
- new Place page!
|
||||||
|
- new search flow
|
||||||
|
- new meta-tag-place / group / cohort page!
|
||||||
|
- allow footer links reordering
|
||||||
|
- new Docker image
|
||||||
|
- add GANCIO_DB_PORT environment
|
||||||
|
- merge old duplicated tags, trim
|
||||||
|
- add dynamic sitemap.xml !
|
||||||
|
- calendar attributes refactoring (a dot each day, colors represents n. events)
|
||||||
|
- fix event mime type response
|
||||||
|
|
||||||
|
|
||||||
|
### 1.4.4 - 10 may '22
|
||||||
|
- better img rendering, make it easier to download flyer #153
|
||||||
|
- avoid place and tags duplication (remove white space, match case insensitive)
|
||||||
|
- show date and place to unconfirmed events
|
||||||
|
- add warning when visiting from different hostname or protocol #149
|
||||||
|
- add tags and fix html description in ics export
|
||||||
|
- add git dependency in Dockerfile #148
|
||||||
|
- add external_style param to gancio-events webcomponent
|
||||||
|
- add GANCIO_HOST and GANCIO_PORT environment vars
|
||||||
|
- fix place and address when importing from url #147
|
||||||
|
- fix user account removal
|
||||||
|
- fix timezone issue #151
|
||||||
|
- fix scrolling behavior
|
||||||
|
- fix adding event on disabled anon posting
|
||||||
|
- fix plain description meta
|
||||||
|
- fix recurrent events always shown #150
|
||||||
|
- remove `less` and `less-loader` dependency
|
||||||
|
|
||||||
### 1.4.3 - 10 mar '22
|
### 1.4.3 - 10 mar '22
|
||||||
- fix [#140](https://framagit.org/les/gancio/-/issues/140) - Invalid date
|
- fix [#140](https://framagit.org/les/gancio/-/issues/140) - Invalid date
|
||||||
- fix [#141](https://framagit.org/les/gancio/-/issues/141) - Cannot change logo
|
- fix [#141](https://framagit.org/les/gancio/-/issues/141) - Cannot change logo
|
||||||
|
|
6
app/router.scrollBehavior.js
Normal file
6
app/router.scrollBehavior.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
/**
|
||||||
|
* https://nuxtjs.org/docs/configuration-glossary/configuration-router/#scrollbehavior
|
||||||
|
*/
|
||||||
|
export default function (to, _from, savedPosition) {
|
||||||
|
return { x: 0, y: 0 }
|
||||||
|
}
|
1262
assets/gancio-events.es.js
Normal file
1262
assets/gancio-events.es.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,56 +1,39 @@
|
||||||
import take from 'lodash/take'
|
|
||||||
import get from 'lodash/get'
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
export function attributesFromEvents (_events, _tags) {
|
export function attributesFromEvents (_events) {
|
||||||
const colors = ['blue', 'orange', 'yellow', 'teal', 'indigo', 'green', 'red', 'purple', 'pink', 'gray']
|
|
||||||
const tags = take(_tags, 10).map(t => t.tag)
|
|
||||||
let attributes = []
|
|
||||||
attributes.push({ key: 'today', dates: new Date(), bar: { color: 'green', fillMode: 'outline' } })
|
|
||||||
const now = dayjs().unix()
|
|
||||||
|
|
||||||
function getColor (event, where) {
|
// const colors = ['teal', 'green', 'yellow', 'teal', 'indigo', 'green', 'red', 'purple', 'pink', 'gray']
|
||||||
const color = { class: 'vc-rounded-full', color: 'blue', fillMode: where === 'base' ? 'light' : 'solid' }
|
// merge events with same date
|
||||||
const tag = get(event, 'tags[0]')
|
let attributes = []
|
||||||
if (event.start_datetime < now) {
|
const now = dayjs().unix()
|
||||||
if (event.multidate) {
|
for(let e of _events) {
|
||||||
color.fillMode = where === 'base' ? 'light' : 'outline'
|
const key = dayjs.unix(e.start_datetime).format('YYYYMMDD')
|
||||||
if (where === 'base') {
|
const c = e.start_datetime < now ? 'vc-past' : ''
|
||||||
color.class += ' vc-past'
|
|
||||||
}
|
const i = attributes.find(a => a.day === key)
|
||||||
} else {
|
if (!i) {
|
||||||
color.class += ' vc-past'
|
attributes.push({ day: key, key: e.id, n: 1, dates: new Date(e.start_datetime * 1000),
|
||||||
}
|
dot: { color: 'teal', class: c } })
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if (!tag) { return color }
|
|
||||||
const idx = tags.indexOf(tag)
|
i.n++
|
||||||
if (idx < 0) { return color }
|
if (i.n >= 20 ) {
|
||||||
color.color = colors[idx]
|
i.dot = { color: 'purple', class: c }
|
||||||
// if (event.start_datetime < now) { color.class += ' vc-past' }
|
} else if ( i.n >= 10 ) {
|
||||||
return color
|
i.dot = { color: 'red', class: c}
|
||||||
|
} else if ( i.n >= 5 ) {
|
||||||
|
i.dot = { color: 'orange', class: c}
|
||||||
|
} else if ( i.n >= 3 ) {
|
||||||
|
i.dot = { color: 'yellow', class: c}
|
||||||
|
} else {
|
||||||
|
i.dot = { color: 'teal', class: c }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
attributes = attributes.concat(_events
|
// add a bar to highlight today
|
||||||
.filter(e => !e.multidate)
|
attributes.push({ key: 'today', dates: new Date(), highlight: { color: 'green', fillMode: 'outline' } })
|
||||||
.map(e => {
|
|
||||||
return {
|
|
||||||
key: e.id,
|
|
||||||
dot: getColor(e),
|
|
||||||
dates: new Date(e.start_datetime * 1000)
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
attributes = attributes.concat(_events
|
|
||||||
.filter(e => e.multidate)
|
|
||||||
.map(e => ({
|
|
||||||
key: e.id,
|
|
||||||
highlight: {
|
|
||||||
start: getColor(e),
|
|
||||||
base: getColor(e, 'base'),
|
|
||||||
end: getColor(e)
|
|
||||||
},
|
|
||||||
dates: { start: new Date(e.start_datetime * 1000), end: new Date(e.end_datetime * 1000) }
|
|
||||||
})))
|
|
||||||
|
|
||||||
return attributes
|
return attributes
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,8 @@ li {
|
||||||
}
|
}
|
||||||
|
|
||||||
#calh {
|
#calh {
|
||||||
height: 292px;
|
/* this is to avoid content shift layout as v-calendar does not support SSR */
|
||||||
|
height: 268px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
|
@ -55,7 +56,6 @@ li {
|
||||||
scrollbar-color: #FF4511 #111;
|
scrollbar-color: #FF4511 #111;
|
||||||
}
|
}
|
||||||
|
|
||||||
// EVENT
|
|
||||||
.event {
|
.event {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -67,42 +67,32 @@ li {
|
||||||
margin-right: .4em;
|
margin-right: .4em;
|
||||||
transition: all .5s;
|
transition: all .5s;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.title {
|
.event .title {
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: 0.5rem 1rem 0.5rem 1rem;
|
margin: 0.5rem 1rem 0.5rem 1rem;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
-webkit-line-clamp: 3;
|
-webkit-line-clamp: 3;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
font-size: 1.1em !important;
|
font-size: 1.1em !important;
|
||||||
line-height: 1.2em !important;
|
line-height: 1.2em !important;
|
||||||
}
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
.body {
|
.event .body {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.img {
|
.event .place span {
|
||||||
width: 100%;
|
white-space: nowrap;
|
||||||
max-height: 250px;
|
overflow: hidden;
|
||||||
min-height: 160px;
|
text-overflow: ellipsis;
|
||||||
object-fit: cover;
|
}
|
||||||
object-position: top;
|
|
||||||
aspect-ratio: 1.7778;
|
|
||||||
}
|
|
||||||
|
|
||||||
.place {
|
.event a {
|
||||||
max-width: 100%;
|
text-decoration: none;
|
||||||
span {
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-past {
|
.vc-past {
|
|
@ -1,8 +0,0 @@
|
||||||
// assets/variables.scss
|
|
||||||
|
|
||||||
// Variables you want to modify
|
|
||||||
// $btn-border-radius: 0px;
|
|
||||||
|
|
||||||
// If you need to extend Vuetify SASS lists
|
|
||||||
// $material-light: ( cards: blue );
|
|
||||||
@import '~vuetify/src/styles/styles.sass';
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template lang="pug">
|
<template>
|
||||||
nuxt-link(:to='`/announcement/${announcement.id}`')
|
<nuxt-link :to='`/announcement/${announcement.id}`'>
|
||||||
v-alert.mb-1(border='left' type='info' color="primary" :icon='mdiInformation') {{announcement.title}}
|
<v-alert class='mb-1' outlined type='info' color="primary" :icon='mdiInformation'>{{announcement.title}}</v-alert>
|
||||||
|
</nuxt-link>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { mdiInformation } from '@mdi/js'
|
import { mdiInformation } from '@mdi/js'
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { mapState, mapActions } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { attributesFromEvents } from '../assets/helper'
|
import { attributesFromEvents } from '../assets/helper'
|
||||||
|
|
||||||
|
@ -26,25 +26,22 @@ export default {
|
||||||
events: { type: Array, default: () => [] }
|
events: { type: Array, default: () => [] }
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
const month = dayjs().month() + 1
|
const month = dayjs.tz().month() + 1
|
||||||
const year = dayjs().year()
|
const year = dayjs.tz().year()
|
||||||
return {
|
return {
|
||||||
selectedDate: null,
|
selectedDate: null,
|
||||||
page: { month, year }
|
page: { month, year }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['tags', 'filters', 'in_past', 'settings']),
|
...mapState(['settings']),
|
||||||
attributes () {
|
attributes () {
|
||||||
return attributesFromEvents(this.events, this.tags)
|
return attributesFromEvents(this.events)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['updateEvents', 'showPastEvents']),
|
|
||||||
updatePage (page) {
|
updatePage (page) {
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.$emit('monthchange', page)
|
this.$emit('monthchange', page)
|
||||||
})
|
|
||||||
},
|
},
|
||||||
click (day) {
|
click (day) {
|
||||||
this.$emit('dayclick', day)
|
this.$emit('dayclick', day)
|
||||||
|
|
45
components/Completed.vue
Normal file
45
components/Completed.vue
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
<template lang="pug">
|
||||||
|
v-container
|
||||||
|
v-card-title.d-block.text-h5.text-center(v-text="$t('setup.completed')")
|
||||||
|
v-card-text(v-html="$t('setup.completed_description', user)")
|
||||||
|
v-alert.mb-3.mt-1(v-if='isHttp' outlined type='warning' color='red' show-icon :icon='mdiAlert') {{$t('setup.https_warning')}}
|
||||||
|
v-alert.mb-3.mt-1(outlined type='warning' color='red' show-icon :icon='mdiAlert') {{$t('setup.copy_password_dialog')}}
|
||||||
|
v-card-actions
|
||||||
|
v-btn(text @click='next' color='primary' :loading='loading' :disabled='loading') {{$t('setup.start')}}
|
||||||
|
v-icon(v-text='mdiArrowRight')
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { mdiArrowRight, mdiAlert } from '@mdi/js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
isHttp: { type: Boolean, default: false },
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
mdiArrowRight, mdiAlert,
|
||||||
|
loading: false,
|
||||||
|
user: {
|
||||||
|
email: 'admin',
|
||||||
|
password: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
next () {
|
||||||
|
window.location='/admin'
|
||||||
|
},
|
||||||
|
async start (user) {
|
||||||
|
this.user = { ...user }
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.$axios.$get('/ping')
|
||||||
|
this.loading = false
|
||||||
|
} catch (e) {
|
||||||
|
setTimeout(() => this.start(user), 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -34,16 +34,54 @@ v-col(cols=12)
|
||||||
|
|
||||||
v-row.mt-3.col-md-6.mx-auto
|
v-row.mt-3.col-md-6.mx-auto
|
||||||
v-col.col-12.col-sm-6
|
v-col.col-12.col-sm-6
|
||||||
v-select(dense :label="$t('event.from')" :value='fromHour' clearable
|
v-menu(
|
||||||
:disabled='!value.from'
|
v-model="menuFromHour"
|
||||||
:rules="[$validators.required('event.from')]"
|
:close-on-content-click="false"
|
||||||
:items='hourList' @change='hr => change("fromHour", hr)')
|
offset-y
|
||||||
|
:value="fromHour"
|
||||||
|
transition="scale-transition")
|
||||||
|
template(v-slot:activator="{ on, attrs }")
|
||||||
|
v-text-field(
|
||||||
|
:label="$t('event.from')"
|
||||||
|
:value="fromHour"
|
||||||
|
:disabled='!value.from'
|
||||||
|
:prepend-icon="mdiClockTimeFourOutline"
|
||||||
|
:rules="[$validators.required('event.from')]"
|
||||||
|
readonly
|
||||||
|
v-bind="attrs"
|
||||||
|
v-on="on")
|
||||||
|
v-time-picker(
|
||||||
|
v-if="menuFromHour"
|
||||||
|
:value="fromHour"
|
||||||
|
:allowedMinutes='allowedMinutes'
|
||||||
|
format='24hr'
|
||||||
|
@click:minute='menuFromHour=false'
|
||||||
|
@change='hr => change("fromHour", hr)')
|
||||||
|
|
||||||
|
|
||||||
v-col.col-12.col-sm-6
|
v-col.col-12.col-sm-6
|
||||||
v-select(dense :label="$t('event.due')"
|
v-menu(
|
||||||
:disabled='!fromHour'
|
v-model="menuDueHour"
|
||||||
:value='dueHour' clearable
|
:close-on-content-click="false"
|
||||||
:items='hourList' @change='hr => change("dueHour", hr)')
|
offset-y
|
||||||
|
:value="dueHour"
|
||||||
|
transition="scale-transition")
|
||||||
|
template(v-slot:activator="{ on, attrs }")
|
||||||
|
v-text-field(
|
||||||
|
:label="$t('event.due')"
|
||||||
|
:value="dueHour"
|
||||||
|
:disabled='!fromHour'
|
||||||
|
:prepend-icon="mdiClockTimeEightOutline"
|
||||||
|
readonly
|
||||||
|
v-bind="attrs"
|
||||||
|
v-on="on")
|
||||||
|
v-time-picker(
|
||||||
|
v-if="menuDueHour"
|
||||||
|
:value="dueHour"
|
||||||
|
:allowedMinutes='allowedMinutes'
|
||||||
|
format='24hr'
|
||||||
|
@click:minute='menuDueHour=false'
|
||||||
|
@change='hr => change("dueHour", hr)')
|
||||||
|
|
||||||
List(v-if='type==="normal" && todayEvents.length' :events='todayEvents' :title='$t("event.same_day")')
|
List(v-if='type==="normal" && todayEvents.length' :events='todayEvents' :title='$t("event.same_day")')
|
||||||
|
|
||||||
|
@ -52,7 +90,8 @@ v-col(cols=12)
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import List from '@/components/List'
|
import List from '@/components/List'
|
||||||
import { attributesFromEvents } from '../../assets/helper'
|
import { attributesFromEvents } from '../assets/helper'
|
||||||
|
import { mdiClockTimeFourOutline, mdiClockTimeEightOutline } from '@mdi/js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DateInput',
|
name: 'DateInput',
|
||||||
|
@ -63,6 +102,10 @@ export default {
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
mdiClockTimeFourOutline, mdiClockTimeEightOutline,
|
||||||
|
allowedMinutes: [0, 15, 30, 45],
|
||||||
|
menuFromHour: false,
|
||||||
|
menuDueHour: false,
|
||||||
type: 'normal',
|
type: 'normal',
|
||||||
page: null,
|
page: null,
|
||||||
events: [],
|
events: [],
|
||||||
|
@ -74,15 +117,14 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['settings', 'tags']),
|
...mapState(['settings']),
|
||||||
todayEvents () {
|
todayEvents () {
|
||||||
const start = dayjs(this.value.from).startOf('day').unix()
|
const start = dayjs(this.value.from).startOf('day').unix()
|
||||||
const end = dayjs(this.value.from).endOf('day').unix()
|
const end = dayjs(this.value.from).endOf('day').unix()
|
||||||
const events = this.events.filter(e => e.start_datetime >= start && e.start_datetime <= end)
|
return this.events.filter(e => e.start_datetime >= start && e.start_datetime <= end)
|
||||||
return events
|
|
||||||
},
|
},
|
||||||
attributes () {
|
attributes () {
|
||||||
return attributesFromEvents(this.events, this.tags)
|
return attributesFromEvents(this.events)
|
||||||
},
|
},
|
||||||
fromDate () {
|
fromDate () {
|
||||||
if (this.value.multidate) {
|
if (this.value.multidate) {
|
||||||
|
@ -92,21 +134,10 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
fromHour () {
|
fromHour () {
|
||||||
return this.value.from && this.value.fromHour ? dayjs(this.value.from).format('HH:mm') : null
|
return this.value.from && this.value.fromHour ? dayjs.tz(this.value.from).format('HH:mm') : null
|
||||||
},
|
},
|
||||||
dueHour () {
|
dueHour () {
|
||||||
return this.value.due && this.value.dueHour ? dayjs(this.value.due).format('HH:mm') : null
|
return this.value.due && this.value.dueHour ? dayjs.tz(this.value.due).format('HH:mm') : null
|
||||||
},
|
|
||||||
hourList () {
|
|
||||||
const hourList = []
|
|
||||||
const leftPad = h => ('00' + h).slice(-2)
|
|
||||||
for (let h = 0; h < 24; h++) {
|
|
||||||
const textHour = leftPad(h < 13 ? h : h - 12)
|
|
||||||
hourList.push({ text: textHour + ':00 ' + (h <= 12 ? 'AM' : 'PM'), value: leftPad(h) + ':00' })
|
|
||||||
hourList.push({ text: textHour + ':30 ' + (h <= 12 ? 'AM' : 'PM'), value: leftPad(h) + ':30' })
|
|
||||||
}
|
|
||||||
|
|
||||||
return hourList
|
|
||||||
},
|
},
|
||||||
whenPatterns () {
|
whenPatterns () {
|
||||||
if (!this.value.from) { return }
|
if (!this.value.from) { return }
|
||||||
|
@ -196,7 +227,7 @@ export default {
|
||||||
} else if (what === 'fromHour') {
|
} else if (what === 'fromHour') {
|
||||||
if (value) {
|
if (value) {
|
||||||
const [hour, minute] = value.split(':')
|
const [hour, minute] = value.split(':')
|
||||||
const from = dayjs(this.value.from).hour(hour).minute(minute).second(0)
|
const from = dayjs.tz(this.value.from).hour(hour).minute(minute).second(0)
|
||||||
this.$emit('input', { ...this.value, from, fromHour: true })
|
this.$emit('input', { ...this.value, from, fromHour: true })
|
||||||
} else {
|
} else {
|
||||||
this.$emit('input', { ...this.value, fromHour: false })
|
this.$emit('input', { ...this.value, fromHour: false })
|
||||||
|
@ -204,7 +235,7 @@ export default {
|
||||||
} else if (what === 'dueHour') {
|
} else if (what === 'dueHour') {
|
||||||
if (value) {
|
if (value) {
|
||||||
const [hour, minute] = value.split(':')
|
const [hour, minute] = value.split(':')
|
||||||
const fromHour = dayjs(this.value.from).hour()
|
const fromHour = dayjs.tz(this.value.from).hour()
|
||||||
|
|
||||||
// add a day
|
// add a day
|
||||||
let due = dayjs(this.value.from)
|
let due = dayjs(this.value.from)
|
||||||
|
@ -226,20 +257,20 @@ export default {
|
||||||
let from = value.start
|
let from = value.start
|
||||||
let due = value.end
|
let due = value.end
|
||||||
if (this.value.fromHour) {
|
if (this.value.fromHour) {
|
||||||
from = dayjs(value.start).hour(dayjs(this.value.from).hour())
|
from = dayjs.tz(value.start).hour(dayjs.tz(this.value.from).hour())
|
||||||
}
|
}
|
||||||
if (this.value.dueHour) {
|
if (this.value.dueHour) {
|
||||||
due = dayjs(value.end).hour(dayjs(this.value.due).hour())
|
due = dayjs.tz(value.end).hour(dayjs.tz(this.value.due).hour())
|
||||||
}
|
}
|
||||||
this.$emit('input', { ...this.value, from, due })
|
this.$emit('input', { ...this.value, from, due })
|
||||||
} else {
|
} else {
|
||||||
let from = value
|
let from = value
|
||||||
let due = this.value.due
|
let due = this.value.due
|
||||||
if (this.value.fromHour) {
|
if (this.value.fromHour) {
|
||||||
from = dayjs(value).hour(dayjs(this.value.from).hour())
|
from = dayjs.tz(value).hour(dayjs.tz(this.value.from).hour())
|
||||||
}
|
}
|
||||||
if (this.value.dueHour && this.value.due) {
|
if (this.value.dueHour && this.value.due) {
|
||||||
due = dayjs(value).hour(dayjs(this.value.due).hour())
|
due = dayjs.tz(value).hour(dayjs.tz(this.value.due).hour())
|
||||||
}
|
}
|
||||||
this.$emit('input', { ...this.value, from, due })
|
this.$emit('input', { ...this.value, from, due })
|
||||||
}
|
}
|
|
@ -176,7 +176,7 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang='less'>
|
<style lang='scss'>
|
||||||
|
|
||||||
.editor {
|
.editor {
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
v-card.h-event.event.d-flex(itemscope itemtype="https://schema.org/Event")
|
v-card.h-event.event.d-flex(itemscope itemtype="https://schema.org/Event")
|
||||||
nuxt-link(:to='`/event/${event.slug || event.id}`' itemprop="url")
|
nuxt-link(:to='`/event/${event.slug || event.id}`' itemprop="url")
|
||||||
img.img.u-featured(:src='thumbnail' :alt='alt' :loading='this.lazy?"lazy":"eager"' itemprop="image" :style="{ 'object-position': thumbnailPosition }")
|
MyPicture(:event='event' thumb :lazy='lazy')
|
||||||
v-icon.float-right.mr-1(v-if='event.parentId' color='success' v-text='mdiRepeat')
|
v-icon.float-right.mr-1(v-if='event.parentId' color='success' v-text='mdiRepeat')
|
||||||
.title.p-name(itemprop="name") {{event.title}}
|
.title.p-name(itemprop="name") {{event.title}}
|
||||||
|
|
||||||
v-card-text.body.pt-0.pb-0
|
v-card-text.body.pt-0.pb-0
|
||||||
time.dt-start.subtitle-1(:datetime='event.start_datetime|unixFormat("YYYY-MM-DD HH:mm")' itemprop="startDate" :content="event.start_datetime|unixFormat('YYYY-MM-DDTHH:mm')") <v-icon v-text='mdiCalendar'></v-icon> {{ event|when }}
|
time.dt-start.subtitle-1(:datetime='event.start_datetime|unixFormat("YYYY-MM-DD HH:mm")' itemprop="startDate" :content="event.start_datetime|unixFormat('YYYY-MM-DDTHH:mm')") <v-icon v-text='mdiCalendar'></v-icon> {{ event|when }}
|
||||||
.d-none.dt-end(itemprop="endDate" :content="event.end_datetime|unixFormat('YYYY-MM-DDTHH:mm')") {{event.end_datetime|unixFormat('YYYY-MM-DD HH:mm')}}
|
.d-none.dt-end(itemprop="endDate" :content="event.end_datetime|unixFormat('YYYY-MM-DDTHH:mm')") {{event.end_datetime|unixFormat('YYYY-MM-DD HH:mm')}}
|
||||||
a.place.d-block.p-location.pl-0(text color='primary' @click="$emit('placeclick', event.place.id)" itemprop="location" :content="event.place.name") <v-icon v-text='mdiMapMarker'></v-icon> {{event.place.name}}
|
nuxt-link.place.d-block.p-location.pl-0(text color='primary' :to='`/p/${event.place.name}`' itemprop="location" :content="event.place.name") <v-icon v-text='mdiMapMarker'></v-icon> {{event.place.name}}
|
||||||
.d-none(itemprop='location.address') {{event.place.address}}
|
.d-none(itemprop='location.address') {{event.place.address}}
|
||||||
|
|
||||||
v-card-actions.pt-0.actions.justify-space-between
|
v-card-actions.pt-0.actions.justify-space-between
|
||||||
.tags
|
.tags
|
||||||
v-chip.ml-1.mt-1(v-for='tag in event.tags.slice(0,6)' small
|
v-chip.ml-1.mt-1(v-for='tag in event.tags.slice(0,6)' small :to='`/tag/${tag}`'
|
||||||
:key='tag' outlined color='primary' @click="$emit('tagclick', tag)") {{tag}}
|
:key='tag' outlined color='primary') {{tag}}
|
||||||
|
|
||||||
client-only
|
client-only
|
||||||
v-menu(offset-y)
|
v-menu(offset-y eager)
|
||||||
template(v-slot:activator="{on}")
|
template(v-slot:activator="{on}")
|
||||||
v-btn.align-self-end(icon v-on='on' color='primary' title='more' aria-label='more')
|
v-btn.align-self-end(icon v-on='on' color='primary' title='more' aria-label='more')
|
||||||
v-icon(v-text='mdiDotsVertical')
|
v-icon(v-text='mdiDotsVertical')
|
||||||
|
@ -50,6 +50,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import clipboard from '../assets/clipboard'
|
import clipboard from '../assets/clipboard'
|
||||||
|
import MyPicture from '~/components/MyPicture'
|
||||||
import { mdiRepeat, mdiPencil, mdiDotsVertical, mdiContentCopy,
|
import { mdiRepeat, mdiPencil, mdiDotsVertical, mdiContentCopy,
|
||||||
mdiCalendarExport, mdiDeleteForever, mdiCalendar, mdiMapMarker } from '@mdi/js'
|
mdiCalendarExport, mdiDeleteForever, mdiCalendar, mdiMapMarker } from '@mdi/js'
|
||||||
|
|
||||||
|
@ -58,6 +59,9 @@ export default {
|
||||||
return { mdiRepeat, mdiPencil, mdiDotsVertical, mdiContentCopy, mdiCalendarExport,
|
return { mdiRepeat, mdiPencil, mdiDotsVertical, mdiContentCopy, mdiCalendarExport,
|
||||||
mdiDeleteForever, mdiMapMarker, mdiCalendar }
|
mdiDeleteForever, mdiMapMarker, mdiCalendar }
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
MyPicture
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
event: { type: Object, default: () => ({}) },
|
event: { type: Object, default: () => ({}) },
|
||||||
lazy: Boolean
|
lazy: Boolean
|
||||||
|
@ -65,25 +69,6 @@ export default {
|
||||||
mixins: [clipboard],
|
mixins: [clipboard],
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['settings']),
|
...mapState(['settings']),
|
||||||
thumbnail () {
|
|
||||||
let path
|
|
||||||
if (this.event.media && this.event.media.length) {
|
|
||||||
path = '/media/thumb/' + this.event.media[0].url
|
|
||||||
} else {
|
|
||||||
path = '/noimg.svg'
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
},
|
|
||||||
alt () {
|
|
||||||
return this.event.media && this.event.media.length ? this.event.media[0].name : ''
|
|
||||||
},
|
|
||||||
thumbnailPosition () {
|
|
||||||
if (this.event.media && this.event.media.length && this.event.media[0].focalpoint) {
|
|
||||||
const focalpoint = this.event.media[0].focalpoint
|
|
||||||
return `${(focalpoint[0] + 1) * 50}% ${(focalpoint[1] + 1) * 50}%`
|
|
||||||
}
|
|
||||||
return 'center center'
|
|
||||||
},
|
|
||||||
is_mine () {
|
is_mine () {
|
||||||
if (!this.$auth.user) {
|
if (!this.$auth.user) {
|
||||||
return false
|
return false
|
||||||
|
@ -99,6 +84,8 @@ export default {
|
||||||
if (!ret) { return }
|
if (!ret) { return }
|
||||||
await this.$axios.delete(`/event/${this.event.id}`)
|
await this.$axios.delete(`/event/${this.event.id}`)
|
||||||
this.$emit('destroy', this.event.id)
|
this.$emit('destroy', this.event.id)
|
||||||
|
this.$root.$message('admin.event_remove_ok')
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,6 @@ export default {
|
||||||
event: {}
|
event: {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: mapState(['places']),
|
|
||||||
methods: {
|
methods: {
|
||||||
importGeneric () {
|
importGeneric () {
|
||||||
if (this.file) {
|
if (this.file) {
|
|
@ -61,12 +61,12 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang='less'>
|
<style>
|
||||||
#list {
|
#list {
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
.v-list-item__title {
|
}
|
||||||
white-space: normal !important;
|
#list .v-list-item__title {
|
||||||
}
|
white-space: normal !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
v-col.col-12.col-sm-4
|
v-col.col-12.col-sm-4
|
||||||
p {{$t('event.choose_focal_point')}}
|
p {{$t('event.choose_focal_point')}}
|
||||||
img.img.d-none.d-sm-block(v-if='mediaPreview'
|
img.mediaPreview.d-none.d-sm-block(v-if='mediaPreview'
|
||||||
:src='mediaPreview' :style="{ 'object-position': position }")
|
:src='mediaPreview' :style="{ 'object-position': position }")
|
||||||
|
|
||||||
v-textarea.mt-4(type='text'
|
v-textarea.mt-4(type='text'
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
v-btn(text color='primary' @click='openMediaDetails = true') {{$t('common.edit')}}
|
v-btn(text color='primary' @click='openMediaDetails = true') {{$t('common.edit')}}
|
||||||
v-btn(text color='error' @click='remove') {{$t('common.remove')}}
|
v-btn(text color='error' @click='remove') {{$t('common.remove')}}
|
||||||
div(v-if='mediaPreview')
|
div(v-if='mediaPreview')
|
||||||
img.img.col-12.ml-3(:src='mediaPreview' :style="{ 'object-position': savedPosition }")
|
img.mediaPreview.col-12.ml-3(:src='mediaPreview' :style="{ 'object-position': savedPosition }")
|
||||||
span.float-right {{event.media[0].name}}
|
span.float-right {{event.media[0].name}}
|
||||||
v-file-input(
|
v-file-input(
|
||||||
v-else
|
v-else
|
||||||
|
@ -53,7 +53,7 @@ export default {
|
||||||
name: 'MediaInput',
|
name: 'MediaInput',
|
||||||
props: {
|
props: {
|
||||||
value: { type: Object, default: () => ({ image: null }) },
|
value: { type: Object, default: () => ({ image: null }) },
|
||||||
event: { type: Object, default: () => {} }
|
event: { type: Object, default: () => ({}) }
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
@ -142,7 +142,7 @@ export default {
|
||||||
cursor: crosshair;
|
cursor: crosshair;
|
||||||
}
|
}
|
||||||
|
|
||||||
.img {
|
.mediaPreview {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
object-position: top;
|
object-position: top;
|
97
components/MyPicture.vue
Normal file
97
components/MyPicture.vue
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
<template>
|
||||||
|
<div :class='{ img: true, thumb }'>
|
||||||
|
<img
|
||||||
|
v-if='media'
|
||||||
|
:class='{ "u-featured": true }'
|
||||||
|
:alt='media.name' :loading='lazy?"lazy":"eager"'
|
||||||
|
:src="src"
|
||||||
|
:srcset="srcset"
|
||||||
|
itemprop="image"
|
||||||
|
:height="height" :width="width"
|
||||||
|
:style="{ 'object-position': thumbnailPosition }">
|
||||||
|
|
||||||
|
<img v-else-if='!media && thumb' class='thumb' src="/noimg.svg" alt=''>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
event: { type: Object, default: () => ({}) },
|
||||||
|
thumb: { type: Boolean, default: false },
|
||||||
|
lazy: { type: Boolean, default: false },
|
||||||
|
showPreview: { type: Boolean, default: true }
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
backgroundPreview () {
|
||||||
|
if (this.media && this.media.preview) {
|
||||||
|
return {
|
||||||
|
backgroundPosition: this.thumbnailPosition,
|
||||||
|
backgroundImage: "url('data:image/png;base64," + this.media.preview + "')" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
srcset () {
|
||||||
|
if (this.thumb) return ''
|
||||||
|
return `/media/thumb/${this.media.url} 500w, /media/${this.media.url} 1200w`
|
||||||
|
},
|
||||||
|
media () {
|
||||||
|
return this.event.media && this.event.media[0]
|
||||||
|
},
|
||||||
|
height () {
|
||||||
|
return this.media ? this.media.height : 'auto'
|
||||||
|
},
|
||||||
|
width () {
|
||||||
|
return this.media ? this.media.width : 'auto'
|
||||||
|
},
|
||||||
|
src () {
|
||||||
|
if (this.media) {
|
||||||
|
return '/media/thumb/' + this.media.url
|
||||||
|
}
|
||||||
|
if (this.thumb) {
|
||||||
|
return '/noimg.svg'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
thumbnailPosition () {
|
||||||
|
if (this.media.focalpoint) {
|
||||||
|
const focalpoint = this.media.focalpoint
|
||||||
|
return `${(focalpoint[0] + 1) * 50}% ${(focalpoint[1] + 1) * 50}%`
|
||||||
|
}
|
||||||
|
return 'center center'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
|
||||||
|
.img {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
background-size: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img img {
|
||||||
|
object-fit: contain;
|
||||||
|
max-height: 125vh;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: opacity .5s;
|
||||||
|
opacity: 1;
|
||||||
|
background-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img.thumb img {
|
||||||
|
display: flex;
|
||||||
|
max-height: 250px;
|
||||||
|
min-height: 160px;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: top;
|
||||||
|
aspect-ratio: 1.7778;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -2,13 +2,13 @@
|
||||||
v-app-bar(app aria-label='Menu' height=64)
|
v-app-bar(app aria-label='Menu' height=64)
|
||||||
|
|
||||||
//- logo, title and description
|
//- logo, title and description
|
||||||
v-list-item(:to='$route.name==="index"?"/about":"/"')
|
v-list-item.pa-0(:to='$route.name==="index"?"/about":"/"')
|
||||||
v-list-item-avatar(tile)
|
v-list-item-avatar.ma-xs-1(tile)
|
||||||
v-img(src='/logo.png' alt='home')
|
img(src='/logo.png' height='40')
|
||||||
v-list-item-content.d-none.d-sm-flex
|
v-list-item-content.d-flex
|
||||||
v-list-item-title
|
v-list-item-title.d-flex
|
||||||
h2 {{settings.title}}
|
h2 {{settings.title}}
|
||||||
v-list-item-subtitle {{settings.description}}
|
v-list-item-subtitle.d-none.d-sm-flex {{settings.description}}
|
||||||
|
|
||||||
v-spacer
|
v-spacer
|
||||||
v-btn(v-if='$auth.loggedIn || settings.allow_anon_event' icon nuxt to='/add' :aria-label='$t("common.add_event")' :title='$t("common.add_event")')
|
v-btn(v-if='$auth.loggedIn || settings.allow_anon_event' icon nuxt to='/add' :aria-label='$t("common.add_event")' :title='$t("common.add_event")')
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
v-icon(v-text='mdiLogin')
|
v-icon(v-text='mdiLogin')
|
||||||
|
|
||||||
client-only
|
client-only
|
||||||
v-menu(v-if='$auth.loggedIn' offset-y)
|
v-menu(v-if='$auth.loggedIn' offset-y eager)
|
||||||
template(v-slot:activator="{ on, attrs }")
|
template(v-slot:activator="{ on, attrs }")
|
||||||
v-btn(icon v-bind='attrs' v-on='on' title='Menu' aria-label='Menu')
|
v-btn(icon v-bind='attrs' v-on='on' title='Menu' aria-label='Menu')
|
||||||
v-icon(v-text='mdiDotsVertical')
|
v-icon(v-text='mdiDotsVertical')
|
||||||
|
@ -48,7 +48,7 @@
|
||||||
v-icon(v-text='mdiDotsVertical')
|
v-icon(v-text='mdiDotsVertical')
|
||||||
|
|
||||||
|
|
||||||
v-btn(icon target='_blank' :href='feedLink' title='RSS' aria-label='RSS')
|
v-btn(icon target='_blank' :href='`${settings.baseurl}/feed/rss`' title='RSS' aria-label='RSS')
|
||||||
v-icon(color='orange' v-text='mdiRss')
|
v-icon(color='orange' v-text='mdiRss')
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
@ -64,25 +64,7 @@ export default {
|
||||||
return { mdiPlus, mdiShareVariant, mdiLogout, mdiLogin, mdiDotsVertical, mdiAccount, mdiCog, mdiRss }
|
return { mdiPlus, mdiShareVariant, mdiLogout, mdiLogin, mdiDotsVertical, mdiAccount, mdiCog, mdiRss }
|
||||||
},
|
},
|
||||||
mixins: [clipboard],
|
mixins: [clipboard],
|
||||||
computed: {
|
computed: mapState(['settings']),
|
||||||
...mapState(['filters', 'settings']),
|
|
||||||
feedLink () {
|
|
||||||
const tags = this.filters.tags && this.filters.tags.map(encodeURIComponent).join(',')
|
|
||||||
const places = this.filters.places && this.filters.places.join(',')
|
|
||||||
let query = ''
|
|
||||||
if (tags || places) {
|
|
||||||
query = '?'
|
|
||||||
if (tags) {
|
|
||||||
query += 'tags=' + tags
|
|
||||||
if (places) { query += '&places=' + places }
|
|
||||||
} else {
|
|
||||||
query += 'places=' + places
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${this.settings.baseurl}/feed/rss${query}`
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
logout () {
|
logout () {
|
||||||
this.$root.$message('common.logout_ok')
|
this.$root.$message('common.logout_ok')
|
||||||
|
|
|
@ -1,98 +1,89 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
v-container.pt-0.pt-md-2
|
v-container.pt-0.pt-md-2
|
||||||
v-switch.mt-0(
|
v-switch.mt-0(
|
||||||
v-if='recurrentFilter && settings.allow_recurrent_event'
|
v-if='settings.allow_recurrent_event'
|
||||||
v-model='showRecurrent'
|
v-model='showRecurrent'
|
||||||
inset color='primary'
|
inset color='primary'
|
||||||
hide-details
|
hide-details
|
||||||
:label="$t('event.show_recurrent')")
|
:label="$t('event.show_recurrent')")
|
||||||
v-autocomplete(
|
v-autocomplete(
|
||||||
:label='$t("common.search")'
|
v-model='meta'
|
||||||
:items='keywords'
|
:label='$t("common.search")'
|
||||||
hide-details
|
:filter='filter'
|
||||||
@change='change'
|
cache-items
|
||||||
:value='selectedFilters'
|
hide-details
|
||||||
clearable
|
color='primary'
|
||||||
:search-input.sync='search'
|
hide-selected
|
||||||
item-text='label'
|
small-chips
|
||||||
return-object
|
:items='items'
|
||||||
chips single-line
|
@change='change'
|
||||||
multiple)
|
hide-no-data
|
||||||
template(v-slot:selection="data")
|
@input.native='search'
|
||||||
v-chip(v-bind="data.attrs"
|
item-text='label'
|
||||||
close
|
return-object
|
||||||
:close-icon='mdiCloseCircle'
|
chips
|
||||||
@click:close='remove(data.item)'
|
multiple)
|
||||||
:input-value="data.selected")
|
template(v-slot:selection="{ attrs, item }")
|
||||||
v-avatar(left)
|
v-chip(v-bind="attrs"
|
||||||
v-icon(v-text="data.item.type === 'place' ? mdiMapMarker : mdiTag")
|
close
|
||||||
span {{ data.item.label }}
|
@click:close='remove(item)'
|
||||||
template(v-slot:item='{ item }')
|
:close-icon='mdiCloseCircle')
|
||||||
v-list-item-avatar
|
v-avatar(left)
|
||||||
v-icon(v-text="item.type === 'place' ? mdiMapMarker : mdiTag")
|
v-icon(v-text="item.type === 'place' ? mdiMapMarker : mdiTag")
|
||||||
v-list-item-content
|
span {{ item.label }}
|
||||||
v-list-item-title(v-text='item.label')
|
template(v-slot:item='{ item }')
|
||||||
|
v-list-item-avatar
|
||||||
|
v-icon(v-text="item.type === 'place' ? mdiMapMarker : mdiTag")
|
||||||
|
v-list-item-content
|
||||||
|
v-list-item-title(v-text='item.label')
|
||||||
|
v-list-item-subtitle(v-if='item.type ==="place"' v-text='item.address')
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import { mdiMapMarker, mdiTag, mdiCloseCircle } from '@mdi/js'
|
import { mdiMapMarker, mdiTag, mdiCloseCircle } from '@mdi/js'
|
||||||
|
import debounce from 'lodash/debounce'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Search',
|
name: 'Search',
|
||||||
props: {
|
props: {
|
||||||
recurrentFilter: { type: Boolean, default: true },
|
filters: { type: Object, default: () => ({}) }
|
||||||
filters: { type: Object, default: () => {} }
|
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
mdiTag, mdiMapMarker, mdiCloseCircle,
|
mdiTag, mdiMapMarker, mdiCloseCircle,
|
||||||
tmpfilter: null,
|
meta: [],
|
||||||
search: ''
|
items: [],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['tags', 'places', 'settings']),
|
...mapState(['settings']),
|
||||||
showRecurrent: {
|
showRecurrent: {
|
||||||
get () {
|
get () {
|
||||||
return this.filters.show_recurrent
|
return this.filters.show_recurrent
|
||||||
},
|
},
|
||||||
set (v) {
|
set (v) {
|
||||||
const filters = {
|
this.change(v)
|
||||||
tags: this.filters.tags,
|
|
||||||
places: this.filters.places,
|
|
||||||
show_recurrent: v
|
|
||||||
}
|
|
||||||
this.$emit('update', filters)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
selectedFilters () {
|
|
||||||
const tags = this.tags.filter(t => this.filters.tags.includes(t.tag)).map(t => ({ type: 'tag', label: t.tag, weigth: t.weigth, id: t.tag }))
|
|
||||||
const places = this.places.filter(p => this.filters.places.includes(p.id))
|
|
||||||
.map(p => ({ type: 'place', label: p.name, weigth: p.weigth, id: p.id }))
|
|
||||||
const keywords = tags.concat(places).sort((a, b) => b.weigth - a.weigth)
|
|
||||||
return keywords
|
|
||||||
},
|
|
||||||
keywords () {
|
|
||||||
const tags = this.tags.map(t => ({ type: 'tag', label: t.tag, weigth: t.weigth, id: t.tag }))
|
|
||||||
const places = this.places.map(p => ({ type: 'place', label: p.name, weigth: p.weigth, id: p.id }))
|
|
||||||
const keywords = tags.concat(places).sort((a, b) => b.weigth - a.weigth)
|
|
||||||
return keywords
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
remove (item) {
|
filter (item, queryText, itemText) {
|
||||||
const filters = {
|
return itemText.toLocaleLowerCase().indexOf(queryText.toLocaleLowerCase()) > -1 ||
|
||||||
tags: item.type === 'tag' ? this.filters.tags.filter(f => f !== item.id) : this.filters.tags,
|
item.address && item.address.toLocaleLowerCase().indexOf(queryText.toLocaleLowerCase()) > -1
|
||||||
places: item.type === 'place' ? this.filters.places.filter(f => f !== item.id) : this.filters.places,
|
|
||||||
show_recurrent: this.filters.show_recurrent
|
|
||||||
}
|
|
||||||
this.$emit('update', filters)
|
|
||||||
},
|
},
|
||||||
change (filters) {
|
search: debounce(async function(search) {
|
||||||
filters = {
|
this.items = await this.$axios.$get(`/event/meta?search=${search.target.value}`)
|
||||||
tags: filters.filter(t => t.type === 'tag').map(t => t.id),
|
}, 100),
|
||||||
places: filters.filter(p => p.type === 'place').map(p => p.id),
|
remove (item) {
|
||||||
show_recurrent: this.filters.show_recurrent
|
this.meta = this.meta.filter(m => m.type !== item.type || m.type === 'place' ? m.id !== item.id : m.tag !== item.tag)
|
||||||
|
this.change()
|
||||||
|
},
|
||||||
|
change (show_recurrent) {
|
||||||
|
const filters = {
|
||||||
|
tags: this.meta.filter(t => t.type === 'tag').map(t => t.label),
|
||||||
|
places: this.meta.filter(p => p.type === 'place').map(p => p.id),
|
||||||
|
show_recurrent: typeof show_recurrent !== 'undefined' ? show_recurrent : this.filters.show_recurrent
|
||||||
}
|
}
|
||||||
this.$emit('update', filters)
|
this.$emit('update', filters)
|
||||||
}
|
}
|
||||||
|
|
115
components/WhereInput.vue
Normal file
115
components/WhereInput.vue
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
<template lang="pug">
|
||||||
|
v-row
|
||||||
|
v-col(cols=12 md=6)
|
||||||
|
v-combobox(ref='place'
|
||||||
|
:rules="[$validators.required('common.where')]"
|
||||||
|
:label="$t('common.where')"
|
||||||
|
:hint="$t('event.where_description')"
|
||||||
|
:prepend-icon='mdiMapMarker'
|
||||||
|
no-filter
|
||||||
|
:value='value.name'
|
||||||
|
hide-no-data
|
||||||
|
@input.native='search'
|
||||||
|
persistent-hint
|
||||||
|
:items="places"
|
||||||
|
@change='selectPlace')
|
||||||
|
template(v-slot:item="{ item, attrs, on }")
|
||||||
|
v-list-item(v-bind='attrs' v-on='on')
|
||||||
|
v-list-item-content(two-line v-if='item.create')
|
||||||
|
v-list-item-title <v-icon color='primary' v-text='mdiPlus' :aria-label='$t("common.add")'></v-icon> {{item.name}}
|
||||||
|
v-list-item-content(two-line v-else)
|
||||||
|
v-list-item-title(v-text='item.name')
|
||||||
|
v-list-item-subtitle(v-text='item.address')
|
||||||
|
|
||||||
|
v-col(cols=12 md=6)
|
||||||
|
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")
|
||||||
|
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { mdiMap, mdiMapMarker, mdiPlus } from '@mdi/js'
|
||||||
|
import debounce from 'lodash/debounce'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'WhereInput',
|
||||||
|
props: {
|
||||||
|
value: { type: Object, default: () => ({}) }
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
mdiMap, mdiMapMarker, mdiPlus,
|
||||||
|
place: { },
|
||||||
|
placeName: '',
|
||||||
|
places: [],
|
||||||
|
disableAddress: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filteredPlaces () {
|
||||||
|
if (!this.placeName) { return this.places }
|
||||||
|
const placeName = this.placeName.trim().toLowerCase()
|
||||||
|
let nameMatch = false
|
||||||
|
const matches = this.places.filter(p => {
|
||||||
|
const tmpName = p.name.toLowerCase()
|
||||||
|
const tmpAddress = p.address.toLowerCase()
|
||||||
|
if (tmpName.includes(placeName)) {
|
||||||
|
if (tmpName === placeName) { nameMatch = true }
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return tmpAddress.includes(placeName)
|
||||||
|
})
|
||||||
|
if (!nameMatch) {
|
||||||
|
matches.unshift({ create: true, name: this.placeName })
|
||||||
|
}
|
||||||
|
return matches
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
search: debounce(async function(ev) {
|
||||||
|
const search = ev.target.value.trim().toLowerCase()
|
||||||
|
this.places = await this.$axios.$get(`place?search=${search}`)
|
||||||
|
if (!search) { return this.places }
|
||||||
|
const matches = this.places.find(p => search === p.name.toLocaleLowerCase())
|
||||||
|
if (!matches) {
|
||||||
|
this.places.unshift({ create: true, name: ev.target.value.trim() })
|
||||||
|
}
|
||||||
|
}, 100),
|
||||||
|
selectPlace (p) {
|
||||||
|
if (!p) { return }
|
||||||
|
if (typeof p === 'object' && !p.create) {
|
||||||
|
this.place.name = p.name.trim()
|
||||||
|
this.place.address = p.address
|
||||||
|
this.place.id = p.id
|
||||||
|
this.disableAddress = true
|
||||||
|
} else { // this is a new place
|
||||||
|
this.place.name = p.name || p
|
||||||
|
const tmpPlace = this.place.name.trim().toLocaleLowerCase()
|
||||||
|
// search for a place with the same name
|
||||||
|
const place = this.places.find(p => !p.create && p.name.trim().toLocaleLowerCase() === tmpPlace)
|
||||||
|
if (place) {
|
||||||
|
this.place.name = place.name
|
||||||
|
this.place.id = place.id
|
||||||
|
this.place.address = place.address
|
||||||
|
this.disableAddress = true
|
||||||
|
} else {
|
||||||
|
delete this.place.id
|
||||||
|
this.place.address = ''
|
||||||
|
this.disableAddress = false
|
||||||
|
this.$refs.place.blur()
|
||||||
|
this.$refs.address.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.$emit('input', { ...this.place })
|
||||||
|
},
|
||||||
|
changeAddress (v) {
|
||||||
|
this.place.address = v
|
||||||
|
this.$emit('input', { ...this.place })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
207
components/admin/Cohorts.vue
Normal file
207
components/admin/Cohorts.vue
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
<template lang='pug'>
|
||||||
|
v-container
|
||||||
|
v-card-title {{$t('common.cohort')}}
|
||||||
|
v-spacer
|
||||||
|
v-text-field(v-model='search'
|
||||||
|
:append-icon='mdiMagnify' outlined rounded
|
||||||
|
label='Search'
|
||||||
|
single-line hide-details)
|
||||||
|
v-card-subtitle(v-html="$t('admin.cohort_description')")
|
||||||
|
|
||||||
|
v-btn(color='primary' text @click='newCohort') <v-icon v-text='mdiPlus'></v-icon> {{$t('common.new')}}
|
||||||
|
|
||||||
|
v-dialog(v-model='dialog' width='800' destroy-on-close :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||||
|
v-card(color='secondary')
|
||||||
|
v-card-title {{$t('admin.edit_cohort')}}
|
||||||
|
v-card-text
|
||||||
|
v-form(v-model='valid' ref='form')
|
||||||
|
v-text-field(
|
||||||
|
v-if='!cohort.id'
|
||||||
|
:rules="[$validators.required('common.name')]"
|
||||||
|
:label="$t('common.name')"
|
||||||
|
v-model='cohort.name'
|
||||||
|
:placeholder='$t("common.name")')
|
||||||
|
template(v-slot:append-outer v-if='!cohort.id')
|
||||||
|
v-btn(text @click='saveCohort' color='primary' :loading='loading'
|
||||||
|
:disabled='!valid || loading || !!cohort.id') {{$t('common.save')}}
|
||||||
|
h3(v-else class='text-h5' v-text='cohort.name')
|
||||||
|
|
||||||
|
v-row
|
||||||
|
v-col(cols=5)
|
||||||
|
v-autocomplete(v-model='filterTags'
|
||||||
|
cache-items
|
||||||
|
:prepend-icon="mdiTagMultiple"
|
||||||
|
|
||||||
|
chips small-chips multiple deletable-chips hide-no-data hide-selected persistent-hint
|
||||||
|
:disabled="!cohort.id"
|
||||||
|
placeholder='Tutte'
|
||||||
|
@input.native='searchTags'
|
||||||
|
:delimiters="[',', ';']"
|
||||||
|
:items="tags"
|
||||||
|
:label="$t('common.tags')")
|
||||||
|
|
||||||
|
v-col(cols=5)
|
||||||
|
v-autocomplete(v-model='filterPlaces'
|
||||||
|
cache-items
|
||||||
|
:prepend-icon="mdiMapMarker"
|
||||||
|
chips small-chips multiple deletable-chips hide-no-data hide-selected persistent-hint
|
||||||
|
auto-select-first
|
||||||
|
clearable
|
||||||
|
return-object
|
||||||
|
item-text='name'
|
||||||
|
:disabled="!cohort.id"
|
||||||
|
@input.native="searchPlaces"
|
||||||
|
:delimiters="[',', ';']"
|
||||||
|
:items="places"
|
||||||
|
:label="$t('common.places')")
|
||||||
|
//- template(v-slot:item="{ item, attrs, on }")
|
||||||
|
//- v-list-item(v-bind='attrs' v-on='on')
|
||||||
|
//- v-list-item-content(two-line)
|
||||||
|
//- v-list-item-title(v-text='item.name')
|
||||||
|
//- v-list-item-subtitle(v-text='item.address')
|
||||||
|
|
||||||
|
v-col(cols=2)
|
||||||
|
v-btn(color='primary' text @click='addFilter' :disabled='!cohort.id || !filterPlaces.length && !filterTags.length') add <v-icon v-text='mdiPlus'></v-icon>
|
||||||
|
|
||||||
|
|
||||||
|
v-data-table(
|
||||||
|
:headers='filterHeaders'
|
||||||
|
:items='filters'
|
||||||
|
:hide-default-footer='filters.length<5'
|
||||||
|
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }')
|
||||||
|
template(v-slot:item.actions='{item}')
|
||||||
|
v-btn(@click='removeFilter(item)' color='error' icon)
|
||||||
|
v-icon(v-text='mdiDeleteForever')
|
||||||
|
template(v-slot:item.tags='{item}')
|
||||||
|
v-chip.ma-1(small v-for='tag in item.tags' v-text='tag' :key='tag')
|
||||||
|
template(v-slot:item.places='{item}')
|
||||||
|
v-chip.ma-1(small v-for='place in item.places' v-text='place.name' :key='place.id' )
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
v-card-actions
|
||||||
|
v-spacer
|
||||||
|
v-btn(text @click='dialog=false' color='warning') {{$t('common.close')}}
|
||||||
|
//- v-btn(text @click='saveCohort' color='primary' :loading='loading'
|
||||||
|
//- :disable='!valid || loading') {{$t('common.save')}}
|
||||||
|
|
||||||
|
v-card-text
|
||||||
|
v-data-table(
|
||||||
|
:headers='cohortHeaders'
|
||||||
|
:items='cohorts'
|
||||||
|
:hide-default-footer='cohorts.length<5'
|
||||||
|
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
|
||||||
|
:search='search')
|
||||||
|
template(v-slot:item.filters='{item}')
|
||||||
|
span {{cohortFilters(item)}}
|
||||||
|
template(v-slot:item.actions='{item}')
|
||||||
|
v-btn(@click='editCohort(item)' color='primary' icon)
|
||||||
|
v-icon(v-text='mdiPencil')
|
||||||
|
v-btn(@click='removeCohort(item)' color='error' icon)
|
||||||
|
v-icon(v-text='mdiDeleteForever')
|
||||||
|
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import get from 'lodash/get'
|
||||||
|
import debounce from 'lodash/debounce'
|
||||||
|
import { mdiPencil, mdiChevronLeft, mdiChevronRight, mdiMagnify, mdiPlus, mdiTagMultiple, mdiMapMarker, mdiDeleteForever, mdiCloseCircle } from '@mdi/js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
mdiPencil, mdiChevronRight, mdiChevronLeft, mdiMagnify, mdiPlus, mdiTagMultiple, mdiMapMarker, mdiDeleteForever, mdiCloseCircle,
|
||||||
|
loading: false,
|
||||||
|
dialog: false,
|
||||||
|
valid: false,
|
||||||
|
search: '',
|
||||||
|
cohort: { name: '', id: null },
|
||||||
|
filterTags: [],
|
||||||
|
filterPlaces: [],
|
||||||
|
tags: [],
|
||||||
|
places: [],
|
||||||
|
cohorts: [],
|
||||||
|
filters: [],
|
||||||
|
tagName: '',
|
||||||
|
placeName: '',
|
||||||
|
cohortHeaders: [
|
||||||
|
{ value: 'name', text: 'Name' },
|
||||||
|
{ value: 'filters', text: 'Filters' },
|
||||||
|
{ value: 'actions', text: 'Actions', align: 'right' }
|
||||||
|
],
|
||||||
|
filterHeaders: [
|
||||||
|
{ value: 'tags', text: 'Tags' },
|
||||||
|
{ value: 'places', text: 'Places' },
|
||||||
|
{ value: 'actions', text: 'Actions', align: 'right' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async fetch () {
|
||||||
|
this.cohorts = await this.$axios.$get('/cohorts?withFilters=true')
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
searchTags: debounce(async function (ev) {
|
||||||
|
this.tags = await this.$axios.$get(`/tag?search=${ev.target.value}`)
|
||||||
|
}, 100),
|
||||||
|
searchPlaces: debounce(async function (ev) {
|
||||||
|
this.places = await this.$axios.$get(`/place?search=${ev.target.value}`)
|
||||||
|
}, 100),
|
||||||
|
cohortFilters (cohort) {
|
||||||
|
return cohort.filters.map(f => {
|
||||||
|
return '(' + f.tags?.join(', ') + f.places?.map(p => p.name).join(', ') + ')'
|
||||||
|
}).join(' - ')
|
||||||
|
},
|
||||||
|
async addFilter () {
|
||||||
|
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', { cohortId: this.cohort.id, tags, places })
|
||||||
|
this.$fetch()
|
||||||
|
this.filters.push(filter)
|
||||||
|
this.filterTags = []
|
||||||
|
this.filterPlaces = []
|
||||||
|
this.loading = false
|
||||||
|
},
|
||||||
|
async editCohort (cohort) {
|
||||||
|
this.cohort = { ...cohort }
|
||||||
|
this.filters = await this.$axios.$get(`/filter/${cohort.id}`)
|
||||||
|
this.dialog = true
|
||||||
|
},
|
||||||
|
newCohort () {
|
||||||
|
this.cohort = { name: '', id: null },
|
||||||
|
this.filters = []
|
||||||
|
this.dialog = true
|
||||||
|
},
|
||||||
|
async saveCohort () {
|
||||||
|
if (!this.$refs.form.validate()) return
|
||||||
|
this.loading = true
|
||||||
|
this.cohort = await this.$axios.$post('/cohorts', this.cohort)
|
||||||
|
this.$fetch()
|
||||||
|
this.loading = false
|
||||||
|
},
|
||||||
|
async removeFilter(filter) {
|
||||||
|
try {
|
||||||
|
await this.$axios.$delete(`/filter/${filter.id}`)
|
||||||
|
this.filters = this.filters.filter(f => f.id !== filter.id)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async removeCohort (cohort) {
|
||||||
|
const ret = await this.$root.$confirm('admin.delete_cohort_confirm', { cohort: cohort.name })
|
||||||
|
if (!ret) { return }
|
||||||
|
try {
|
||||||
|
await this.$axios.$delete(`/cohort/${cohort.id}`)
|
||||||
|
this.cohorts = this.cohorts.filter(c => c.id !== cohort.id)
|
||||||
|
} catch (e) {
|
||||||
|
const err = get(e, 'response.data.errors[0].message', e)
|
||||||
|
this.$root.$message(this.$t(err), { color: 'error' })
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -8,6 +8,7 @@
|
||||||
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
|
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
|
||||||
:items='unconfirmedEvents'
|
:items='unconfirmedEvents'
|
||||||
:headers='headers')
|
:headers='headers')
|
||||||
|
template(v-slot:item.when='{ item }') {{item|when}}
|
||||||
template(v-slot:item.actions='{ item }')
|
template(v-slot:item.actions='{ item }')
|
||||||
v-btn(text small @click='confirm(item)' color='success') {{$t('common.confirm')}}
|
v-btn(text small @click='confirm(item)' color='success') {{$t('common.confirm')}}
|
||||||
v-btn(text small :to='`/event/${item.slug || item.id}`' color='success') {{$t('common.preview')}}
|
v-btn(text small :to='`/event/${item.slug || item.id}`' color='success') {{$t('common.preview')}}
|
||||||
|
@ -31,6 +32,8 @@ export default {
|
||||||
editing: false,
|
editing: false,
|
||||||
headers: [
|
headers: [
|
||||||
{ value: 'title', text: 'Title' },
|
{ value: 'title', text: 'Title' },
|
||||||
|
{ value: 'place.name', text: 'Place' },
|
||||||
|
{ value: 'when', text: 'When' },
|
||||||
{ value: 'actions', text: 'Actions', align: 'right' }
|
{ value: 'actions', text: 'Actions', align: 'right' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,6 +126,7 @@ export default {
|
||||||
if (!this.instance_url.startsWith('http')) {
|
if (!this.instance_url.startsWith('http')) {
|
||||||
this.instance_url = `https://${this.instance_url}`
|
this.instance_url = `https://${this.instance_url}`
|
||||||
}
|
}
|
||||||
|
this.instance_url = this.instance_url.replace(/\/$/, '')
|
||||||
const instance = await axios.get(`${this.instance_url}/.well-known/nodeinfo/2.1`)
|
const instance = await axios.get(`${this.instance_url}/.well-known/nodeinfo/2.1`)
|
||||||
this.setSetting({
|
this.setSetting({
|
||||||
key: 'trusted_instances',
|
key: 'trusted_instances',
|
||||||
|
|
|
@ -41,18 +41,21 @@
|
||||||
template(v-slot:item.actions='{item}')
|
template(v-slot:item.actions='{item}')
|
||||||
v-btn(@click='editPlace(item)' color='primary' icon)
|
v-btn(@click='editPlace(item)' color='primary' icon)
|
||||||
v-icon(v-text='mdiPencil')
|
v-icon(v-text='mdiPencil')
|
||||||
|
nuxt-link(:to='`/p/${item.name}`')
|
||||||
|
v-icon(v-text='mdiEye')
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { mapState, mapActions } from 'vuex'
|
import { mdiPencil, mdiChevronLeft, mdiChevronRight, mdiMagnify, mdiEye } from '@mdi/js'
|
||||||
import { mdiPencil, mdiChevronLeft, mdiChevronRight } from '@mdi/js'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
mdiPencil, mdiChevronRight, mdiChevronLeft,
|
mdiPencil, mdiChevronRight, mdiChevronLeft, mdiMagnify, mdiEye,
|
||||||
loading: false,
|
loading: false,
|
||||||
dialog: false,
|
dialog: false,
|
||||||
valid: false,
|
valid: false,
|
||||||
|
places: [],
|
||||||
search: '',
|
search: '',
|
||||||
place: { name: '', address: '', id: null },
|
place: { name: '', address: '', id: null },
|
||||||
headers: [
|
headers: [
|
||||||
|
@ -62,9 +65,10 @@ export default {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: mapState(['places']),
|
async fetch () {
|
||||||
|
this.places = await this.$axios.$get('/place/all')
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['updateMeta']),
|
|
||||||
editPlace (item) {
|
editPlace (item) {
|
||||||
this.place.name = item.name
|
this.place.name = item.name
|
||||||
this.place.address = item.address
|
this.place.address = item.address
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
accept='image/*')
|
accept='image/*')
|
||||||
template(slot='append-outer')
|
template(slot='append-outer')
|
||||||
v-btn(color='warning' text @click='resetLogo') <v-icon v-text='mdiRestore'></v-icon> {{$t('common.reset')}}
|
v-btn(color='warning' text @click='resetLogo') <v-icon v-text='mdiRestore'></v-icon> {{$t('common.reset')}}
|
||||||
v-img(:src='`${settings.baseurl}/logo.png?${logoKey}`'
|
v-img(:src='`/logo.png?${logoKey}`'
|
||||||
max-width="60px" max-height="60px" contain)
|
max-width="60px" max-height="60px" contain)
|
||||||
|
|
||||||
v-switch.mt-5(v-model='is_dark'
|
v-switch.mt-5(v-model='is_dark'
|
||||||
|
@ -54,25 +54,27 @@
|
||||||
v-btn(color='warning' text @click='reset') <v-icon v-text='mdiRestore'></v-icon> {{$t('common.reset')}}
|
v-btn(color='warning' text @click='reset') <v-icon v-text='mdiRestore'></v-icon> {{$t('common.reset')}}
|
||||||
v-card
|
v-card
|
||||||
v-list.mt-1(two-line subheader)
|
v-list.mt-1(two-line subheader)
|
||||||
v-list-item(v-for='link in settings.footerLinks'
|
v-list-item(v-for='(link, idx) in settings.footerLinks'
|
||||||
:key='`${link.label}`' @click='editFooterLink(link)')
|
:key='`${link.label}`' @click='editFooterLink(link)')
|
||||||
v-list-item-content
|
v-list-item-content
|
||||||
v-list-item-title {{link.label}}
|
v-list-item-title {{link.label}}
|
||||||
v-list-item-subtitle {{link.href}}
|
v-list-item-subtitle {{link.href}}
|
||||||
v-list-item-action
|
v-list-item-action
|
||||||
v-btn(icon color='error' @click.stop='removeFooterLink(link)')
|
v-btn.left(v-if='idx !== 0' icon color='warn' @click.stop='moveUpFooterLink(link, idx)')
|
||||||
|
v-icon(v-text='mdiChevronUp')
|
||||||
|
v-btn.float-right(icon color='error' @click.stop='removeFooterLink(link)')
|
||||||
v-icon(v-text='mdiDeleteForever')
|
v-icon(v-text='mdiDeleteForever')
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { mapActions, mapState } from 'vuex'
|
import { mapActions, mapState } from 'vuex'
|
||||||
import { mdiDeleteForever, mdiRestore, mdiPlus } from '@mdi/js'
|
import { mdiDeleteForever, mdiRestore, mdiPlus, mdiChevronUp } from '@mdi/js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Theme',
|
name: 'Theme',
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
mdiDeleteForever, mdiRestore, mdiPlus,
|
mdiDeleteForever, mdiRestore, mdiPlus, mdiChevronUp,
|
||||||
valid: false,
|
valid: false,
|
||||||
logoKey: 0,
|
logoKey: 0,
|
||||||
link: { href: '', label: '' },
|
link: { href: '', label: '' },
|
||||||
|
@ -152,6 +154,12 @@ export default {
|
||||||
const footerLinks = this.settings.footerLinks.filter(l => l.label !== item.label)
|
const footerLinks = this.settings.footerLinks.filter(l => l.label !== item.label)
|
||||||
this.setSetting({ key: 'footerLinks', value: footerLinks })
|
this.setSetting({ key: 'footerLinks', value: footerLinks })
|
||||||
},
|
},
|
||||||
|
async moveUpFooterLink (item, idx) {
|
||||||
|
const footerLinks = [...this.settings.footerLinks]
|
||||||
|
footerLinks[idx] = footerLinks[idx-1]
|
||||||
|
footerLinks[idx-1] = this.settings.footerLinks[idx]
|
||||||
|
this.setSetting({ key: 'footerLinks', value: footerLinks })
|
||||||
|
},
|
||||||
editFooterLink (item) {
|
editFooterLink (item) {
|
||||||
this.link = { href: item.href, label: item.label }
|
this.link = { href: item.href, label: item.label }
|
||||||
this.linkModal = true
|
this.linkModal = true
|
||||||
|
|
|
@ -16,7 +16,7 @@ v-card
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import clipboard from '../../assets/clipboard'
|
import clipboard from '../assets/clipboard'
|
||||||
import { mdiContentCopy, mdiInformation } from '@mdi/js'
|
import { mdiContentCopy, mdiInformation } from '@mdi/js'
|
||||||
|
|
||||||
export default {
|
export default {
|
|
@ -37,3 +37,4 @@ end
|
||||||
# Performance-booster for watching directories on Windows
|
# Performance-booster for watching directories on Windows
|
||||||
gem "wdm", "~> 0.1.0", :install_if => Gem.win_platform?
|
gem "wdm", "~> 0.1.0", :install_if => Gem.win_platform?
|
||||||
|
|
||||||
|
gem "webrick", "~> 1.7"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
activesupport (6.0.4.7)
|
activesupport (6.0.4.8)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
i18n (>= 0.7, < 2)
|
i18n (>= 0.7, < 2)
|
||||||
minitest (~> 5.1)
|
minitest (~> 5.1)
|
||||||
|
@ -10,7 +10,7 @@ GEM
|
||||||
addressable (2.8.0)
|
addressable (2.8.0)
|
||||||
public_suffix (>= 2.0.2, < 5.0)
|
public_suffix (>= 2.0.2, < 5.0)
|
||||||
colorator (1.1.0)
|
colorator (1.1.0)
|
||||||
concurrent-ruby (1.1.9)
|
concurrent-ruby (1.1.10)
|
||||||
em-websocket (0.5.3)
|
em-websocket (0.5.3)
|
||||||
eventmachine (>= 0.12.9)
|
eventmachine (>= 0.12.9)
|
||||||
http_parser.rb (~> 0)
|
http_parser.rb (~> 0)
|
||||||
|
@ -18,7 +18,7 @@ GEM
|
||||||
ffi (1.15.5)
|
ffi (1.15.5)
|
||||||
forwardable-extended (2.6.0)
|
forwardable-extended (2.6.0)
|
||||||
gemoji (3.0.1)
|
gemoji (3.0.1)
|
||||||
html-pipeline (2.14.0)
|
html-pipeline (2.14.1)
|
||||||
activesupport (>= 2)
|
activesupport (>= 2)
|
||||||
nokogiri (>= 1.4)
|
nokogiri (>= 1.4)
|
||||||
http_parser.rb (0.8.0)
|
http_parser.rb (0.8.0)
|
||||||
|
@ -57,7 +57,7 @@ GEM
|
||||||
jekyll (>= 3.8.5)
|
jekyll (>= 3.8.5)
|
||||||
jekyll-seo-tag (~> 2.0)
|
jekyll-seo-tag (~> 2.0)
|
||||||
rake (>= 12.3.1, < 13.1.0)
|
rake (>= 12.3.1, < 13.1.0)
|
||||||
kramdown (2.3.1)
|
kramdown (2.4.0)
|
||||||
rexml
|
rexml
|
||||||
kramdown-parser-gfm (1.1.0)
|
kramdown-parser-gfm (1.1.0)
|
||||||
kramdown (~> 2.0)
|
kramdown (~> 2.0)
|
||||||
|
@ -68,13 +68,13 @@ GEM
|
||||||
mercenary (0.4.0)
|
mercenary (0.4.0)
|
||||||
mini_magick (4.11.0)
|
mini_magick (4.11.0)
|
||||||
minitest (5.15.0)
|
minitest (5.15.0)
|
||||||
nokogiri (1.13.3-x86_64-linux)
|
nokogiri (1.13.6-x86_64-linux)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
pathutil (0.16.2)
|
pathutil (0.16.2)
|
||||||
forwardable-extended (~> 2.6)
|
forwardable-extended (~> 2.6)
|
||||||
premonition (4.0.2)
|
premonition (4.0.2)
|
||||||
jekyll (>= 3.7, < 5.0)
|
jekyll (>= 3.7, < 5.0)
|
||||||
public_suffix (4.0.6)
|
public_suffix (4.0.7)
|
||||||
racc (1.6.0)
|
racc (1.6.0)
|
||||||
rake (13.0.6)
|
rake (13.0.6)
|
||||||
rb-fsevent (0.11.1)
|
rb-fsevent (0.11.1)
|
||||||
|
@ -90,10 +90,11 @@ GEM
|
||||||
thread_safe (0.3.6)
|
thread_safe (0.3.6)
|
||||||
tzinfo (1.2.9)
|
tzinfo (1.2.9)
|
||||||
thread_safe (~> 0.1)
|
thread_safe (~> 0.1)
|
||||||
tzinfo-data (1.2021.5)
|
tzinfo-data (1.2022.1)
|
||||||
tzinfo (>= 1.0.0)
|
tzinfo (>= 1.0.0)
|
||||||
unicode-display_width (1.8.0)
|
unicode-display_width (1.8.0)
|
||||||
wdm (0.1.1)
|
wdm (0.1.1)
|
||||||
|
webrick (1.7.0)
|
||||||
zeitwerk (2.5.4)
|
zeitwerk (2.5.4)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
|
@ -110,6 +111,7 @@ DEPENDENCIES
|
||||||
tzinfo (~> 1.2)
|
tzinfo (~> 1.2)
|
||||||
tzinfo-data
|
tzinfo-data
|
||||||
wdm (~> 0.1.0)
|
wdm (~> 0.1.0)
|
||||||
|
webrick (~> 1.7)
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.2.27
|
2.2.27
|
||||||
|
|
|
@ -1,19 +1,41 @@
|
||||||
---
|
---
|
||||||
layout: default
|
layout: default
|
||||||
title: Admin
|
title: Admin
|
||||||
permalink: /admin
|
permalink: /usage/admin
|
||||||
nav_order: 5
|
nav_order: 1
|
||||||
|
parent: Usage
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
# Admin
|
### CLI
|
||||||
{: .no_toc }
|
|
||||||
|
#### Manage accounts
|
||||||
|
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ gancio accounts
|
||||||
|
|
||||||
|
Manage accounts
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
gancio accounts list List all accounts
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
#### List accounts
|
||||||
|
|
||||||
|
```
|
||||||
|
$ gancio accounts list
|
||||||
|
📅 gancio - v1.4.3 - A shared agenda for local communities (nodejs: v16.13.0)
|
||||||
|
> Reading configuration from: ./config.json
|
||||||
|
|
||||||
|
1 admin: true enabled: true email: admin
|
||||||
|
2 admin: false enabled: true email: lesion@autistici.org
|
||||||
|
```
|
||||||
|
|
||||||
1. TOC
|
|
||||||
{:toc}
|
|
||||||
|
|
||||||
## Basics
|
## Basics
|
||||||
## Add user
|
## Add user
|
||||||
## Enable registration
|
## Enable registration
|
||||||
## Confirm registration
|
## Confirm registration
|
||||||
## Confirm event
|
## Confirm event
|
|
@ -7,6 +7,12 @@
|
||||||
<meta name="Description" content="{{ page.description }}">
|
<meta name="Description" content="{{ page.description }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<link href="https://github.com/lesion" rel="me">
|
||||||
|
<link href="eventi@cisti.org" rel="me">
|
||||||
|
|
||||||
|
<link rel="webmention" href="https://webmention.io/gancio.org/webmention" />
|
||||||
|
<link rel="pingback" href="https://webmention.io/gancio.org/xmlrpc" />
|
||||||
|
|
||||||
<link rel="shortcut icon" href="{{ '/favicon.ico' | absolute_url }}" type="image/x-icon">
|
<link rel="shortcut icon" href="{{ '/favicon.ico' | absolute_url }}" type="image/x-icon">
|
||||||
<link rel="stylesheet" href="{{ '/assets/css/just-the-docs-default.css' | absolute_url }}">
|
<link rel="stylesheet" href="{{ '/assets/css/just-the-docs-default.css' | absolute_url }}">
|
||||||
<link rel="stylesheet" href="{{ '/assets/css/premonition.css' | absolute_url }}">
|
<link rel="stylesheet" href="{{ '/assets/css/premonition.css' | absolute_url }}">
|
||||||
|
|
|
@ -4,7 +4,7 @@ function run(fn) {
|
||||||
return fn();
|
return fn();
|
||||||
}
|
}
|
||||||
function blank_object() {
|
function blank_object() {
|
||||||
return Object.create(null);
|
return /* @__PURE__ */ Object.create(null);
|
||||||
}
|
}
|
||||||
function run_all(fns) {
|
function run_all(fns) {
|
||||||
fns.forEach(run);
|
fns.forEach(run);
|
||||||
|
@ -104,7 +104,7 @@ function schedule_update() {
|
||||||
function add_render_callback(fn) {
|
function add_render_callback(fn) {
|
||||||
render_callbacks.push(fn);
|
render_callbacks.push(fn);
|
||||||
}
|
}
|
||||||
const seen_callbacks = new Set();
|
const seen_callbacks = /* @__PURE__ */ new Set();
|
||||||
let flushidx = 0;
|
let flushidx = 0;
|
||||||
function flush() {
|
function flush() {
|
||||||
const saved_component = current_component;
|
const saved_component = current_component;
|
||||||
|
@ -146,7 +146,7 @@ function update($$) {
|
||||||
$$.after_update.forEach(add_render_callback);
|
$$.after_update.forEach(add_render_callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const outroing = new Set();
|
const outroing = /* @__PURE__ */ new Set();
|
||||||
function transition_in(block, local) {
|
function transition_in(block, local) {
|
||||||
if (block && block.i) {
|
if (block && block.i) {
|
||||||
outroing.delete(block);
|
outroing.delete(block);
|
||||||
|
@ -282,19 +282,41 @@ if (typeof HTMLElement === "function") {
|
||||||
}
|
}
|
||||||
function get_each_context(ctx, list, i) {
|
function get_each_context(ctx, list, i) {
|
||||||
const child_ctx = ctx.slice();
|
const child_ctx = ctx.slice();
|
||||||
child_ctx[11] = list[i];
|
child_ctx[12] = list[i];
|
||||||
return child_ctx;
|
return child_ctx;
|
||||||
}
|
}
|
||||||
function get_each_context_1(ctx, list, i) {
|
function get_each_context_1(ctx, list, i) {
|
||||||
const child_ctx = ctx.slice();
|
const child_ctx = ctx.slice();
|
||||||
child_ctx[14] = list[i];
|
child_ctx[15] = list[i];
|
||||||
return child_ctx;
|
return child_ctx;
|
||||||
}
|
}
|
||||||
|
function create_if_block_5(ctx) {
|
||||||
|
let link;
|
||||||
|
return {
|
||||||
|
c() {
|
||||||
|
link = element("link");
|
||||||
|
attr(link, "rel", "stylesheet");
|
||||||
|
attr(link, "href", ctx[4]);
|
||||||
|
},
|
||||||
|
m(target, anchor) {
|
||||||
|
insert(target, link, anchor);
|
||||||
|
},
|
||||||
|
p(ctx2, dirty) {
|
||||||
|
if (dirty & 16) {
|
||||||
|
attr(link, "href", ctx2[4]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
d(detaching) {
|
||||||
|
if (detaching)
|
||||||
|
detach(link);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
function create_if_block$1(ctx) {
|
function create_if_block$1(ctx) {
|
||||||
let div;
|
let div;
|
||||||
let t;
|
let t;
|
||||||
let if_block = ctx[1] && ctx[3] === "true" && create_if_block_4(ctx);
|
let if_block = ctx[1] && ctx[3] === "true" && create_if_block_4(ctx);
|
||||||
let each_value = ctx[4];
|
let each_value = ctx[5];
|
||||||
let each_blocks = [];
|
let each_blocks = [];
|
||||||
for (let i = 0; i < each_value.length; i += 1) {
|
for (let i = 0; i < each_value.length; i += 1) {
|
||||||
each_blocks[i] = create_each_block(get_each_context(ctx, each_value, i));
|
each_blocks[i] = create_each_block(get_each_context(ctx, each_value, i));
|
||||||
|
@ -336,8 +358,8 @@ function create_if_block$1(ctx) {
|
||||||
if_block.d(1);
|
if_block.d(1);
|
||||||
if_block = null;
|
if_block = null;
|
||||||
}
|
}
|
||||||
if (dirty & 25) {
|
if (dirty & 41) {
|
||||||
each_value = ctx2[4];
|
each_value = ctx2[5];
|
||||||
let i;
|
let i;
|
||||||
for (i = 0; i < each_value.length; i += 1) {
|
for (i = 0; i < each_value.length; i += 1) {
|
||||||
const child_ctx = get_each_context(ctx2, each_value, i);
|
const child_ctx = get_each_context(ctx2, each_value, i);
|
||||||
|
@ -395,7 +417,7 @@ function create_if_block_4(ctx) {
|
||||||
attr(div0, "class", "title");
|
attr(div0, "class", "title");
|
||||||
attr(img, "id", "logo");
|
attr(img, "id", "logo");
|
||||||
attr(img, "alt", "logo");
|
attr(img, "alt", "logo");
|
||||||
if (!src_url_equal(img.src, img_src_value = "" + (ctx[0] + "/logo.png")))
|
if (!src_url_equal(img.src, img_src_value = ctx[0] + "/logo.png"))
|
||||||
attr(img, "src", img_src_value);
|
attr(img, "src", img_src_value);
|
||||||
attr(div1, "class", "content");
|
attr(div1, "class", "content");
|
||||||
attr(a, "href", ctx[0]);
|
attr(a, "href", ctx[0]);
|
||||||
|
@ -413,7 +435,7 @@ function create_if_block_4(ctx) {
|
||||||
p(ctx2, dirty) {
|
p(ctx2, dirty) {
|
||||||
if (dirty & 2)
|
if (dirty & 2)
|
||||||
set_data(t0, ctx2[1]);
|
set_data(t0, ctx2[1]);
|
||||||
if (dirty & 1 && !src_url_equal(img.src, img_src_value = "" + (ctx2[0] + "/logo.png"))) {
|
if (dirty & 1 && !src_url_equal(img.src, img_src_value = ctx2[0] + "/logo.png")) {
|
||||||
attr(img, "src", img_src_value);
|
attr(img, "src", img_src_value);
|
||||||
}
|
}
|
||||||
if (dirty & 1) {
|
if (dirty & 1) {
|
||||||
|
@ -429,7 +451,7 @@ function create_if_block_4(ctx) {
|
||||||
function create_if_block_2(ctx) {
|
function create_if_block_2(ctx) {
|
||||||
let div;
|
let div;
|
||||||
function select_block_type(ctx2, dirty) {
|
function select_block_type(ctx2, dirty) {
|
||||||
if (ctx2[11].media.length)
|
if (ctx2[12].media.length)
|
||||||
return create_if_block_3;
|
return create_if_block_3;
|
||||||
return create_else_block;
|
return create_else_block;
|
||||||
}
|
}
|
||||||
|
@ -472,7 +494,7 @@ function create_else_block(ctx) {
|
||||||
c() {
|
c() {
|
||||||
img = element("img");
|
img = element("img");
|
||||||
attr(img, "style", "aspect-ratio=1.7778;");
|
attr(img, "style", "aspect-ratio=1.7778;");
|
||||||
attr(img, "alt", img_alt_value = ctx[11].title);
|
attr(img, "alt", img_alt_value = ctx[12].title);
|
||||||
if (!src_url_equal(img.src, img_src_value = ctx[0] + "/noimg.svg"))
|
if (!src_url_equal(img.src, img_src_value = ctx[0] + "/noimg.svg"))
|
||||||
attr(img, "src", img_src_value);
|
attr(img, "src", img_src_value);
|
||||||
attr(img, "loading", "lazy");
|
attr(img, "loading", "lazy");
|
||||||
|
@ -481,7 +503,7 @@ function create_else_block(ctx) {
|
||||||
insert(target, img, anchor);
|
insert(target, img, anchor);
|
||||||
},
|
},
|
||||||
p(ctx2, dirty) {
|
p(ctx2, dirty) {
|
||||||
if (dirty & 16 && img_alt_value !== (img_alt_value = ctx2[11].title)) {
|
if (dirty & 32 && img_alt_value !== (img_alt_value = ctx2[12].title)) {
|
||||||
attr(img, "alt", img_alt_value);
|
attr(img, "alt", img_alt_value);
|
||||||
}
|
}
|
||||||
if (dirty & 1 && !src_url_equal(img.src, img_src_value = ctx2[0] + "/noimg.svg")) {
|
if (dirty & 1 && !src_url_equal(img.src, img_src_value = ctx2[0] + "/noimg.svg")) {
|
||||||
|
@ -502,9 +524,9 @@ function create_if_block_3(ctx) {
|
||||||
return {
|
return {
|
||||||
c() {
|
c() {
|
||||||
img = element("img");
|
img = element("img");
|
||||||
attr(img, "style", img_style_value = "object-position: " + position$1(ctx[11]) + "; aspect-ratio=1.7778;");
|
attr(img, "style", img_style_value = "object-position: " + position$1(ctx[12]) + "; aspect-ratio=1.7778;");
|
||||||
attr(img, "alt", img_alt_value = ctx[11].media[0].name);
|
attr(img, "alt", img_alt_value = ctx[12].media[0].name);
|
||||||
if (!src_url_equal(img.src, img_src_value = ctx[0] + "/media/thumb/" + ctx[11].media[0].url))
|
if (!src_url_equal(img.src, img_src_value = ctx[0] + "/media/thumb/" + ctx[12].media[0].url))
|
||||||
attr(img, "src", img_src_value);
|
attr(img, "src", img_src_value);
|
||||||
attr(img, "loading", "lazy");
|
attr(img, "loading", "lazy");
|
||||||
},
|
},
|
||||||
|
@ -512,13 +534,13 @@ function create_if_block_3(ctx) {
|
||||||
insert(target, img, anchor);
|
insert(target, img, anchor);
|
||||||
},
|
},
|
||||||
p(ctx2, dirty) {
|
p(ctx2, dirty) {
|
||||||
if (dirty & 16 && img_style_value !== (img_style_value = "object-position: " + position$1(ctx2[11]) + "; aspect-ratio=1.7778;")) {
|
if (dirty & 32 && img_style_value !== (img_style_value = "object-position: " + position$1(ctx2[12]) + "; aspect-ratio=1.7778;")) {
|
||||||
attr(img, "style", img_style_value);
|
attr(img, "style", img_style_value);
|
||||||
}
|
}
|
||||||
if (dirty & 16 && img_alt_value !== (img_alt_value = ctx2[11].media[0].name)) {
|
if (dirty & 32 && img_alt_value !== (img_alt_value = ctx2[12].media[0].name)) {
|
||||||
attr(img, "alt", img_alt_value);
|
attr(img, "alt", img_alt_value);
|
||||||
}
|
}
|
||||||
if (dirty & 17 && !src_url_equal(img.src, img_src_value = ctx2[0] + "/media/thumb/" + ctx2[11].media[0].url)) {
|
if (dirty & 33 && !src_url_equal(img.src, img_src_value = ctx2[0] + "/media/thumb/" + ctx2[12].media[0].url)) {
|
||||||
attr(img, "src", img_src_value);
|
attr(img, "src", img_src_value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -530,7 +552,7 @@ function create_if_block_3(ctx) {
|
||||||
}
|
}
|
||||||
function create_if_block_1$1(ctx) {
|
function create_if_block_1$1(ctx) {
|
||||||
let div;
|
let div;
|
||||||
let each_value_1 = ctx[11].tags;
|
let each_value_1 = ctx[12].tags;
|
||||||
let each_blocks = [];
|
let each_blocks = [];
|
||||||
for (let i = 0; i < each_value_1.length; i += 1) {
|
for (let i = 0; i < each_value_1.length; i += 1) {
|
||||||
each_blocks[i] = create_each_block_1(get_each_context_1(ctx, each_value_1, i));
|
each_blocks[i] = create_each_block_1(get_each_context_1(ctx, each_value_1, i));
|
||||||
|
@ -550,8 +572,8 @@ function create_if_block_1$1(ctx) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
p(ctx2, dirty) {
|
p(ctx2, dirty) {
|
||||||
if (dirty & 16) {
|
if (dirty & 32) {
|
||||||
each_value_1 = ctx2[11].tags;
|
each_value_1 = ctx2[12].tags;
|
||||||
let i;
|
let i;
|
||||||
for (i = 0; i < each_value_1.length; i += 1) {
|
for (i = 0; i < each_value_1.length; i += 1) {
|
||||||
const child_ctx = get_each_context_1(ctx2, each_value_1, i);
|
const child_ctx = get_each_context_1(ctx2, each_value_1, i);
|
||||||
|
@ -579,7 +601,7 @@ function create_if_block_1$1(ctx) {
|
||||||
function create_each_block_1(ctx) {
|
function create_each_block_1(ctx) {
|
||||||
let span;
|
let span;
|
||||||
let t0;
|
let t0;
|
||||||
let t1_value = ctx[14] + "";
|
let t1_value = ctx[15] + "";
|
||||||
let t1;
|
let t1;
|
||||||
return {
|
return {
|
||||||
c() {
|
c() {
|
||||||
|
@ -594,7 +616,7 @@ function create_each_block_1(ctx) {
|
||||||
append(span, t1);
|
append(span, t1);
|
||||||
},
|
},
|
||||||
p(ctx2, dirty) {
|
p(ctx2, dirty) {
|
||||||
if (dirty & 16 && t1_value !== (t1_value = ctx2[14] + ""))
|
if (dirty & 32 && t1_value !== (t1_value = ctx2[15] + ""))
|
||||||
set_data(t1, t1_value);
|
set_data(t1, t1_value);
|
||||||
},
|
},
|
||||||
d(detaching) {
|
d(detaching) {
|
||||||
|
@ -608,27 +630,27 @@ function create_each_block(ctx) {
|
||||||
let t0;
|
let t0;
|
||||||
let div2;
|
let div2;
|
||||||
let div0;
|
let div0;
|
||||||
let t1_value = when$1(ctx[11].start_datetime) + "";
|
let t1_value = when$1(ctx[12].start_datetime) + "";
|
||||||
let t1;
|
let t1;
|
||||||
let t2;
|
let t2;
|
||||||
let div1;
|
let div1;
|
||||||
let t3_value = ctx[11].title + "";
|
let t3_value = ctx[12].title + "";
|
||||||
let t3;
|
let t3;
|
||||||
let t4;
|
let t4;
|
||||||
let span1;
|
let span1;
|
||||||
let t5;
|
let t5;
|
||||||
let t6_value = ctx[11].place.name + "";
|
let t6_value = ctx[12].place.name + "";
|
||||||
let t6;
|
let t6;
|
||||||
let t7;
|
let t7;
|
||||||
let span0;
|
let span0;
|
||||||
let t8_value = ctx[11].place.address + "";
|
let t8_value = ctx[12].place.address + "";
|
||||||
let t8;
|
let t8;
|
||||||
let t9;
|
let t9;
|
||||||
let t10;
|
let t10;
|
||||||
let a_href_value;
|
let a_href_value;
|
||||||
let a_title_value;
|
let a_title_value;
|
||||||
let if_block0 = ctx[3] !== "true" && create_if_block_2(ctx);
|
let if_block0 = ctx[3] !== "true" && create_if_block_2(ctx);
|
||||||
let if_block1 = ctx[11].tags.length && create_if_block_1$1(ctx);
|
let if_block1 = ctx[12].tags.length && create_if_block_1$1(ctx);
|
||||||
return {
|
return {
|
||||||
c() {
|
c() {
|
||||||
a = element("a");
|
a = element("a");
|
||||||
|
@ -657,9 +679,9 @@ function create_each_block(ctx) {
|
||||||
attr(span0, "class", "subtitle");
|
attr(span0, "class", "subtitle");
|
||||||
attr(span1, "class", "place");
|
attr(span1, "class", "place");
|
||||||
attr(div2, "class", "content");
|
attr(div2, "class", "content");
|
||||||
attr(a, "href", a_href_value = "" + (ctx[0] + "/event/" + (ctx[11].slug || ctx[11].id)));
|
attr(a, "href", a_href_value = ctx[0] + "/event/" + (ctx[12].slug || ctx[12].id));
|
||||||
attr(a, "class", "event");
|
attr(a, "class", "event");
|
||||||
attr(a, "title", a_title_value = ctx[11].title);
|
attr(a, "title", a_title_value = ctx[12].title);
|
||||||
attr(a, "target", "_blank");
|
attr(a, "target", "_blank");
|
||||||
},
|
},
|
||||||
m(target, anchor) {
|
m(target, anchor) {
|
||||||
|
@ -698,15 +720,15 @@ function create_each_block(ctx) {
|
||||||
if_block0.d(1);
|
if_block0.d(1);
|
||||||
if_block0 = null;
|
if_block0 = null;
|
||||||
}
|
}
|
||||||
if (dirty & 16 && t1_value !== (t1_value = when$1(ctx2[11].start_datetime) + ""))
|
if (dirty & 32 && t1_value !== (t1_value = when$1(ctx2[12].start_datetime) + ""))
|
||||||
set_data(t1, t1_value);
|
set_data(t1, t1_value);
|
||||||
if (dirty & 16 && t3_value !== (t3_value = ctx2[11].title + ""))
|
if (dirty & 32 && t3_value !== (t3_value = ctx2[12].title + ""))
|
||||||
set_data(t3, t3_value);
|
set_data(t3, t3_value);
|
||||||
if (dirty & 16 && t6_value !== (t6_value = ctx2[11].place.name + ""))
|
if (dirty & 32 && t6_value !== (t6_value = ctx2[12].place.name + ""))
|
||||||
set_data(t6, t6_value);
|
set_data(t6, t6_value);
|
||||||
if (dirty & 16 && t8_value !== (t8_value = ctx2[11].place.address + ""))
|
if (dirty & 32 && t8_value !== (t8_value = ctx2[12].place.address + ""))
|
||||||
set_data(t8, t8_value);
|
set_data(t8, t8_value);
|
||||||
if (ctx2[11].tags.length) {
|
if (ctx2[12].tags.length) {
|
||||||
if (if_block1) {
|
if (if_block1) {
|
||||||
if_block1.p(ctx2, dirty);
|
if_block1.p(ctx2, dirty);
|
||||||
} else {
|
} else {
|
||||||
|
@ -718,10 +740,10 @@ function create_each_block(ctx) {
|
||||||
if_block1.d(1);
|
if_block1.d(1);
|
||||||
if_block1 = null;
|
if_block1 = null;
|
||||||
}
|
}
|
||||||
if (dirty & 17 && a_href_value !== (a_href_value = "" + (ctx2[0] + "/event/" + (ctx2[11].slug || ctx2[11].id)))) {
|
if (dirty & 33 && a_href_value !== (a_href_value = ctx2[0] + "/event/" + (ctx2[12].slug || ctx2[12].id))) {
|
||||||
attr(a, "href", a_href_value);
|
attr(a, "href", a_href_value);
|
||||||
}
|
}
|
||||||
if (dirty & 16 && a_title_value !== (a_title_value = ctx2[11].title)) {
|
if (dirty & 32 && a_title_value !== (a_title_value = ctx2[12].title)) {
|
||||||
attr(a, "title", a_title_value);
|
attr(a, "title", a_title_value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -736,41 +758,65 @@ function create_each_block(ctx) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function create_fragment$1(ctx) {
|
function create_fragment$1(ctx) {
|
||||||
let if_block_anchor;
|
let t;
|
||||||
let if_block = ctx[4].length && create_if_block$1(ctx);
|
let if_block1_anchor;
|
||||||
|
let if_block0 = ctx[4] && create_if_block_5(ctx);
|
||||||
|
let if_block1 = ctx[5].length && create_if_block$1(ctx);
|
||||||
return {
|
return {
|
||||||
c() {
|
c() {
|
||||||
if (if_block)
|
if (if_block0)
|
||||||
if_block.c();
|
if_block0.c();
|
||||||
if_block_anchor = empty();
|
t = space();
|
||||||
|
if (if_block1)
|
||||||
|
if_block1.c();
|
||||||
|
if_block1_anchor = empty();
|
||||||
this.c = noop;
|
this.c = noop;
|
||||||
},
|
},
|
||||||
m(target, anchor) {
|
m(target, anchor) {
|
||||||
if (if_block)
|
if (if_block0)
|
||||||
if_block.m(target, anchor);
|
if_block0.m(target, anchor);
|
||||||
insert(target, if_block_anchor, anchor);
|
insert(target, t, anchor);
|
||||||
|
if (if_block1)
|
||||||
|
if_block1.m(target, anchor);
|
||||||
|
insert(target, if_block1_anchor, anchor);
|
||||||
},
|
},
|
||||||
p(ctx2, [dirty]) {
|
p(ctx2, [dirty]) {
|
||||||
if (ctx2[4].length) {
|
if (ctx2[4]) {
|
||||||
if (if_block) {
|
if (if_block0) {
|
||||||
if_block.p(ctx2, dirty);
|
if_block0.p(ctx2, dirty);
|
||||||
} else {
|
} else {
|
||||||
if_block = create_if_block$1(ctx2);
|
if_block0 = create_if_block_5(ctx2);
|
||||||
if_block.c();
|
if_block0.c();
|
||||||
if_block.m(if_block_anchor.parentNode, if_block_anchor);
|
if_block0.m(t.parentNode, t);
|
||||||
}
|
}
|
||||||
} else if (if_block) {
|
} else if (if_block0) {
|
||||||
if_block.d(1);
|
if_block0.d(1);
|
||||||
if_block = null;
|
if_block0 = null;
|
||||||
|
}
|
||||||
|
if (ctx2[5].length) {
|
||||||
|
if (if_block1) {
|
||||||
|
if_block1.p(ctx2, dirty);
|
||||||
|
} else {
|
||||||
|
if_block1 = create_if_block$1(ctx2);
|
||||||
|
if_block1.c();
|
||||||
|
if_block1.m(if_block1_anchor.parentNode, if_block1_anchor);
|
||||||
|
}
|
||||||
|
} else if (if_block1) {
|
||||||
|
if_block1.d(1);
|
||||||
|
if_block1 = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
i: noop,
|
i: noop,
|
||||||
o: noop,
|
o: noop,
|
||||||
d(detaching) {
|
d(detaching) {
|
||||||
if (if_block)
|
if (if_block0)
|
||||||
if_block.d(detaching);
|
if_block0.d(detaching);
|
||||||
if (detaching)
|
if (detaching)
|
||||||
detach(if_block_anchor);
|
detach(t);
|
||||||
|
if (if_block1)
|
||||||
|
if_block1.d(detaching);
|
||||||
|
if (detaching)
|
||||||
|
detach(if_block1_anchor);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -799,6 +845,7 @@ function instance$1($$self, $$props, $$invalidate) {
|
||||||
let { theme = "light" } = $$props;
|
let { theme = "light" } = $$props;
|
||||||
let { show_recurrent = false } = $$props;
|
let { show_recurrent = false } = $$props;
|
||||||
let { sidebar = "true" } = $$props;
|
let { sidebar = "true" } = $$props;
|
||||||
|
let { external_style = "" } = $$props;
|
||||||
let mounted = false;
|
let mounted = false;
|
||||||
let events = [];
|
let events = [];
|
||||||
function update2(v) {
|
function update2(v) {
|
||||||
|
@ -814,11 +861,9 @@ function instance$1($$self, $$props, $$invalidate) {
|
||||||
if (places) {
|
if (places) {
|
||||||
params.push(`places=${places}`);
|
params.push(`places=${places}`);
|
||||||
}
|
}
|
||||||
if (show_recurrent) {
|
params.push(`show_recurrent=${show_recurrent ? "true" : "false"}`);
|
||||||
params.push(`show_recurrent=true`);
|
|
||||||
}
|
|
||||||
fetch(`${baseurl}/api/events?${params.join("&")}`).then((res) => res.json()).then((e) => {
|
fetch(`${baseurl}/api/events?${params.join("&")}`).then((res) => res.json()).then((e) => {
|
||||||
$$invalidate(4, events = e);
|
$$invalidate(5, events = e);
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
console.error("Error loading Gancio API -> ", e);
|
console.error("Error loading Gancio API -> ", e);
|
||||||
});
|
});
|
||||||
|
@ -833,20 +878,22 @@ function instance$1($$self, $$props, $$invalidate) {
|
||||||
if ("title" in $$props2)
|
if ("title" in $$props2)
|
||||||
$$invalidate(1, title = $$props2.title);
|
$$invalidate(1, title = $$props2.title);
|
||||||
if ("maxlength" in $$props2)
|
if ("maxlength" in $$props2)
|
||||||
$$invalidate(5, maxlength = $$props2.maxlength);
|
$$invalidate(6, maxlength = $$props2.maxlength);
|
||||||
if ("tags" in $$props2)
|
if ("tags" in $$props2)
|
||||||
$$invalidate(6, tags = $$props2.tags);
|
$$invalidate(7, tags = $$props2.tags);
|
||||||
if ("places" in $$props2)
|
if ("places" in $$props2)
|
||||||
$$invalidate(7, places = $$props2.places);
|
$$invalidate(8, places = $$props2.places);
|
||||||
if ("theme" in $$props2)
|
if ("theme" in $$props2)
|
||||||
$$invalidate(2, theme = $$props2.theme);
|
$$invalidate(2, theme = $$props2.theme);
|
||||||
if ("show_recurrent" in $$props2)
|
if ("show_recurrent" in $$props2)
|
||||||
$$invalidate(8, show_recurrent = $$props2.show_recurrent);
|
$$invalidate(9, show_recurrent = $$props2.show_recurrent);
|
||||||
if ("sidebar" in $$props2)
|
if ("sidebar" in $$props2)
|
||||||
$$invalidate(3, sidebar = $$props2.sidebar);
|
$$invalidate(3, sidebar = $$props2.sidebar);
|
||||||
|
if ("external_style" in $$props2)
|
||||||
|
$$invalidate(4, external_style = $$props2.external_style);
|
||||||
};
|
};
|
||||||
$$self.$$.update = () => {
|
$$self.$$.update = () => {
|
||||||
if ($$self.$$.dirty & 494) {
|
if ($$self.$$.dirty & 974) {
|
||||||
update2();
|
update2();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -855,6 +902,7 @@ function instance$1($$self, $$props, $$invalidate) {
|
||||||
title,
|
title,
|
||||||
theme,
|
theme,
|
||||||
sidebar,
|
sidebar,
|
||||||
|
external_style,
|
||||||
events,
|
events,
|
||||||
maxlength,
|
maxlength,
|
||||||
tags,
|
tags,
|
||||||
|
@ -873,12 +921,13 @@ class GancioEvents extends SvelteElement {
|
||||||
}, instance$1, create_fragment$1, safe_not_equal, {
|
}, instance$1, create_fragment$1, safe_not_equal, {
|
||||||
baseurl: 0,
|
baseurl: 0,
|
||||||
title: 1,
|
title: 1,
|
||||||
maxlength: 5,
|
maxlength: 6,
|
||||||
tags: 6,
|
tags: 7,
|
||||||
places: 7,
|
places: 8,
|
||||||
theme: 2,
|
theme: 2,
|
||||||
show_recurrent: 8,
|
show_recurrent: 9,
|
||||||
sidebar: 3
|
sidebar: 3,
|
||||||
|
external_style: 4
|
||||||
}, null);
|
}, null);
|
||||||
if (options) {
|
if (options) {
|
||||||
if (options.target) {
|
if (options.target) {
|
||||||
|
@ -899,7 +948,8 @@ class GancioEvents extends SvelteElement {
|
||||||
"places",
|
"places",
|
||||||
"theme",
|
"theme",
|
||||||
"show_recurrent",
|
"show_recurrent",
|
||||||
"sidebar"
|
"sidebar",
|
||||||
|
"external_style"
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
get baseurl() {
|
get baseurl() {
|
||||||
|
@ -917,21 +967,21 @@ class GancioEvents extends SvelteElement {
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
get maxlength() {
|
get maxlength() {
|
||||||
return this.$$.ctx[5];
|
return this.$$.ctx[6];
|
||||||
}
|
}
|
||||||
set maxlength(maxlength) {
|
set maxlength(maxlength) {
|
||||||
this.$$set({ maxlength });
|
this.$$set({ maxlength });
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
get tags() {
|
get tags() {
|
||||||
return this.$$.ctx[6];
|
return this.$$.ctx[7];
|
||||||
}
|
}
|
||||||
set tags(tags) {
|
set tags(tags) {
|
||||||
this.$$set({ tags });
|
this.$$set({ tags });
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
get places() {
|
get places() {
|
||||||
return this.$$.ctx[7];
|
return this.$$.ctx[8];
|
||||||
}
|
}
|
||||||
set places(places) {
|
set places(places) {
|
||||||
this.$$set({ places });
|
this.$$set({ places });
|
||||||
|
@ -945,7 +995,7 @@ class GancioEvents extends SvelteElement {
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
get show_recurrent() {
|
get show_recurrent() {
|
||||||
return this.$$.ctx[8];
|
return this.$$.ctx[9];
|
||||||
}
|
}
|
||||||
set show_recurrent(show_recurrent) {
|
set show_recurrent(show_recurrent) {
|
||||||
this.$$set({ show_recurrent });
|
this.$$set({ show_recurrent });
|
||||||
|
@ -958,6 +1008,13 @@ class GancioEvents extends SvelteElement {
|
||||||
this.$$set({ sidebar });
|
this.$$set({ sidebar });
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
|
get external_style() {
|
||||||
|
return this.$$.ctx[4];
|
||||||
|
}
|
||||||
|
set external_style(external_style) {
|
||||||
|
this.$$set({ external_style });
|
||||||
|
flush();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
customElements.define("gancio-events", GancioEvents);
|
customElements.define("gancio-events", GancioEvents);
|
||||||
function create_if_block(ctx) {
|
function create_if_block(ctx) {
|
||||||
|
@ -996,7 +1053,7 @@ function create_if_block(ctx) {
|
||||||
t6 = text(t6_value);
|
t6 = text(t6_value);
|
||||||
attr(div1, "class", "place");
|
attr(div1, "class", "place");
|
||||||
attr(div2, "class", "container");
|
attr(div2, "class", "container");
|
||||||
attr(a, "href", a_href_value = "" + (ctx[0] + "/event/" + (ctx[1].slug || ctx[1].id)));
|
attr(a, "href", a_href_value = ctx[0] + "/event/" + (ctx[1].slug || ctx[1].id));
|
||||||
attr(a, "class", "card");
|
attr(a, "class", "card");
|
||||||
attr(a, "target", "_blank");
|
attr(a, "target", "_blank");
|
||||||
},
|
},
|
||||||
|
@ -1035,7 +1092,7 @@ function create_if_block(ctx) {
|
||||||
set_data(t3, t3_value);
|
set_data(t3, t3_value);
|
||||||
if (dirty & 2 && t6_value !== (t6_value = ctx2[1].place.name + ""))
|
if (dirty & 2 && t6_value !== (t6_value = ctx2[1].place.name + ""))
|
||||||
set_data(t6, t6_value);
|
set_data(t6, t6_value);
|
||||||
if (dirty & 3 && a_href_value !== (a_href_value = "" + (ctx2[0] + "/event/" + (ctx2[1].slug || ctx2[1].id)))) {
|
if (dirty & 3 && a_href_value !== (a_href_value = ctx2[0] + "/event/" + (ctx2[1].slug || ctx2[1].id))) {
|
||||||
attr(a, "href", a_href_value);
|
attr(a, "href", a_href_value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,6 +8,24 @@ nav_order: 10
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
### 1.4.4 - 10 may '22
|
||||||
|
- better img rendering, make it easier to download flyer #153
|
||||||
|
- avoid place and tags duplication (remove white space, match case insensitive)
|
||||||
|
- show date and place to unconfirmed events
|
||||||
|
- add warning when visiting from different hostname or protocol #149
|
||||||
|
- add tags and fix html description in ics export
|
||||||
|
- add git dependency in Dockerfile #148
|
||||||
|
- add external_style param to gancio-events webcomponent
|
||||||
|
- add GANCIO_HOST and GANCIO_PORT environment vars
|
||||||
|
- fix place and address when importing from url #147
|
||||||
|
- fix user account removal
|
||||||
|
- fix timezone issue #151
|
||||||
|
- fix scrolling behavior
|
||||||
|
- fix adding event on disabled anon posting
|
||||||
|
- fix plain description meta
|
||||||
|
- fix recurrent events always shown #150
|
||||||
|
- remove `less` and `less-loader` dependency
|
||||||
|
|
||||||
### 1.4.3 - 10 mar '22
|
### 1.4.3 - 10 mar '22
|
||||||
- fix [#140](https://framagit.org/les/gancio/-/issues/140) - Invalid date
|
- fix [#140](https://framagit.org/les/gancio/-/issues/140) - Invalid date
|
||||||
- fix [#141](https://framagit.org/les/gancio/-/issues/141) - Cannot change logo
|
- fix [#141](https://framagit.org/les/gancio/-/issues/141) - Cannot change logo
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
FROM node:17.4-slim
|
FROM node:17-slim
|
||||||
|
RUN bash -c "apt update -y && apt install git -y && apt clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp"
|
||||||
RUN yarn global remove gancio || true
|
RUN yarn global remove gancio || true
|
||||||
RUN yarn cache clean
|
RUN yarn cache clean
|
||||||
RUN yarn global add --latest --production --silent https://gancio.org/latest.tgz
|
RUN yarn global add --latest --production --silent https://gancio.org/latest.tgz
|
||||||
|
|
|
@ -4,7 +4,7 @@ services:
|
||||||
gancio:
|
gancio:
|
||||||
build: .
|
build: .
|
||||||
restart: always
|
restart: always
|
||||||
image: node:17.4-slim
|
image: gancio
|
||||||
container_name: gancio
|
container_name: gancio
|
||||||
environment:
|
environment:
|
||||||
- PATH=$PATH:/home/node/.yarn/bin
|
- PATH=$PATH:/home/node/.yarn/bin
|
||||||
|
@ -13,7 +13,7 @@ services:
|
||||||
- GANCIO_DB_DIALECT=sqlite
|
- GANCIO_DB_DIALECT=sqlite
|
||||||
- GANCIO_DB_STORAGE=./gancio.sqlite
|
- GANCIO_DB_STORAGE=./gancio.sqlite
|
||||||
entrypoint: /entrypoint.sh
|
entrypoint: /entrypoint.sh
|
||||||
command: gancio start --docker
|
command: gancio start
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/home/node/data
|
- ./data:/home/node/data
|
||||||
ports:
|
ports:
|
||||||
|
|
|
@ -16,7 +16,7 @@ services:
|
||||||
gancio:
|
gancio:
|
||||||
build: .
|
build: .
|
||||||
restart: always
|
restart: always
|
||||||
image: node:17.4-slim
|
image: gancio
|
||||||
container_name: gancio
|
container_name: gancio
|
||||||
environment:
|
environment:
|
||||||
- PATH=$PATH:/home/node/.yarn/bin
|
- PATH=$PATH:/home/node/.yarn/bin
|
||||||
|
@ -24,10 +24,11 @@ services:
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
- GANCIO_DB_DIALECT=mariadb
|
- GANCIO_DB_DIALECT=mariadb
|
||||||
- GANCIO_DB_HOST=db
|
- GANCIO_DB_HOST=db
|
||||||
|
- GANCIO_DB_PORT=3306
|
||||||
- GANCIO_DB_DATABASE=gancio
|
- GANCIO_DB_DATABASE=gancio
|
||||||
- GANCIO_DB_USERNAME=gancio
|
- GANCIO_DB_USERNAME=gancio
|
||||||
- GANCIO_DB_PASSWORD=gancio
|
- GANCIO_DB_PASSWORD=gancio
|
||||||
command: gancio start --docker
|
command: gancio start
|
||||||
entrypoint: /entrypoint.sh
|
entrypoint: /entrypoint.sh
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/home/node/data
|
- ./data:/home/node/data
|
||||||
|
|
|
@ -18,7 +18,7 @@ services:
|
||||||
gancio:
|
gancio:
|
||||||
build: .
|
build: .
|
||||||
restart: always
|
restart: always
|
||||||
image: node:17.4-slim
|
image: gancio
|
||||||
container_name: gancio
|
container_name: gancio
|
||||||
environment:
|
environment:
|
||||||
- PATH=$PATH:/home/node/.yarn/bin
|
- PATH=$PATH:/home/node/.yarn/bin
|
||||||
|
@ -26,10 +26,11 @@ services:
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
- GANCIO_DB_DIALECT=postgres
|
- GANCIO_DB_DIALECT=postgres
|
||||||
- GANCIO_DB_HOST=db
|
- GANCIO_DB_HOST=db
|
||||||
|
- GANCIO_DB_PORT=5432
|
||||||
- GANCIO_DB_DATABASE=gancio
|
- GANCIO_DB_DATABASE=gancio
|
||||||
- GANCIO_DB_USERNAME=gancio
|
- GANCIO_DB_USERNAME=gancio
|
||||||
- GANCIO_DB_PASSWORD=gancio
|
- GANCIO_DB_PASSWORD=gancio
|
||||||
command: gancio start --docker
|
command: gancio start
|
||||||
entrypoint: /entrypoint.sh
|
entrypoint: /entrypoint.sh
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/home/node/data
|
- ./data:/home/node/data
|
||||||
|
|
|
@ -4,7 +4,7 @@ services:
|
||||||
gancio:
|
gancio:
|
||||||
build: .
|
build: .
|
||||||
restart: always
|
restart: always
|
||||||
image: node:17.4-slim
|
image: gancio
|
||||||
container_name: gancio
|
container_name: gancio
|
||||||
environment:
|
environment:
|
||||||
- PATH=$PATH:/home/node/.yarn/bin
|
- PATH=$PATH:/home/node/.yarn/bin
|
||||||
|
@ -13,7 +13,7 @@ services:
|
||||||
- GANCIO_DB_DIALECT=sqlite
|
- GANCIO_DB_DIALECT=sqlite
|
||||||
- GANCIO_DB_STORAGE=./gancio.sqlite
|
- GANCIO_DB_STORAGE=./gancio.sqlite
|
||||||
entrypoint: /entrypoint.sh
|
entrypoint: /entrypoint.sh
|
||||||
command: gancio start --docker
|
command: gancio start
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/home/node/data
|
- ./data:/home/node/data
|
||||||
ports:
|
ports:
|
||||||
|
|
|
@ -24,14 +24,14 @@ nowhere on gancio does the identity of who posted an event appear, not even unde
|
||||||
|
|
||||||
- **Anonymous events**: optionally a visitor can create events without being registered (an administrator must confirm them)
|
- **Anonymous events**: optionally a visitor can create events without being registered (an administrator must confirm them)
|
||||||
|
|
||||||
- **We don't care about making hits** so we export events in many ways: via RSS feeds, via global or individual ics, allowing you to embed list of events or single event via [iframe or webcomponent]({% link embed.md %}) on other websites, via [AP]({% link federation.md %}), [microdata](https://developer.mozilla.org/en-US/docs/Web/HTML/Microdata) and [microformat](https://developer.mozilla.org/en-US/docs/Web/HTML/microformats#h-event)
|
- **We don't care about making hits** so we export events in many ways: via RSS feeds, via global or individual ics, allowing you to embed list of events or single event via [iframe or webcomponent]({% link usage/embed.md %}) on other websites, via [AP]({% link federation.md %}), [microdata](https://developer.mozilla.org/en-US/docs/Web/HTML/Microdata) and [microformat](https://developer.mozilla.org/en-US/docs/Web/HTML/microformats#h-event)
|
||||||
|
|
||||||
- Very easy UI
|
- Very easy UI
|
||||||
- Multi-day events (festival, conferences...)
|
- Multi-day events (festival, conferences...)
|
||||||
- Recurring events (each monday, each two monday, each monday and friday, each two saturday, etc.)
|
- Recurring events (each monday, each two monday, each monday and friday, each two saturday, etc.)
|
||||||
- Filter events for tags or places
|
- Filter events for tags or places
|
||||||
- RSS and ICS export (with filters)
|
- RSS and ICS export (with filters)
|
||||||
- embed your events in your website with [webcomponents]({% link embed.md %}) or iframe ([example](https://gancio.cisti.org/embed/list?title=Upcoming events))
|
- embed your events in your website with [webcomponents]({% link usage/embed.md %}) or iframe ([example](https://gancio.cisti.org/embed/list?title=Upcoming events))
|
||||||
- boost / bookmark / comment events from the fediverse!
|
- boost / bookmark / comment events from the fediverse!
|
||||||
- Lot of configurations available (dark/light theme, user registration open/close, enable federation, enable recurring events)
|
- Lot of configurations available (dark/light theme, user registration open/close, enable federation, enable recurring events)
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,7 @@ You'll need to [setup nginx as a proxy]({% link install/nginx.md %}) then you ca
|
||||||
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /opt/gancio
|
cd /opt/gancio # or where your installation is
|
||||||
|
wget https://gancio.org/docker/Dockerfile -O Dockerfile
|
||||||
docker-compose up -d --no-deps --build
|
docker-compose up -d --no-deps --build
|
||||||
```
|
```
|
||||||
|
|
|
@ -9,9 +9,12 @@ nav_order: 7
|
||||||
- [gancio.cisti.org](https://gancio.cisti.org) (Turin, Italy)
|
- [gancio.cisti.org](https://gancio.cisti.org) (Turin, Italy)
|
||||||
- [lapunta.org](https://lapunta.org) (Florence, Italy)
|
- [lapunta.org](https://lapunta.org) (Florence, Italy)
|
||||||
- [sapratza.in](https://sapratza.in/) (Sardinia, Italy)
|
- [sapratza.in](https://sapratza.in/) (Sardinia, Italy)
|
||||||
- [termine.161.social](https://termine.161.social) (Germany)
|
- [bcn.convoca.la](https://bcn.convoca.la/) (Barcelona)
|
||||||
- [ezkerraldea.euskaragendak.eus](https://ezkerraldea.euskaragendak.eus/)
|
- [ezkerraldea.euskaragendak.eus](https://ezkerraldea.euskaragendak.eus/)
|
||||||
- [lakelogaztetxea.net](https://lakelogaztetxea.net)
|
- [lakelogaztetxea.net](https://lakelogaztetxea.net)
|
||||||
|
- [agenda.eskoria.eus](https://agenda.eskoria.eus/)
|
||||||
|
- [lubakiagenda.net](https://lubakiagenda.net/)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<small>Do you want your instance to appear here? [Write us]({% link contact.md %}).</small>
|
<small>Do you want your instance to appear here? [Write us]({% link contact.md %}).</small>
|
||||||
|
|
|
@ -9,3 +9,5 @@ has_children: true
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
ehmmm, help needed here :smile: feel free to send a PR => [here](https://framagit.org/les/gancio/tree/master/docs)
|
ehmmm, help needed here :smile: feel free to send a PR => [here](https://framagit.org/les/gancio/tree/master/docs)
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,21 @@
|
||||||
<template lang='pug'>
|
<template>
|
||||||
v-app(app)
|
<v-app app>
|
||||||
Snackbar
|
<Snackbar/>
|
||||||
Confirm
|
<Confirm/>
|
||||||
Nav
|
<Nav/>
|
||||||
|
<v-main app>
|
||||||
|
<div class="ml-1 mb-1 mt-1" v-if='showCohorts || showBack'>
|
||||||
|
<v-btn v-show='showBack' text color='primary' to='/'><v-icon v-text='mdiChevronLeft'/></v-btn>
|
||||||
|
<v-btn v-for='cohort in cohorts' text color='primary' :key='cohort.id' :to='`/g/${cohort.name}`'>{{cohort.name}}</v-btn>
|
||||||
|
</div>
|
||||||
|
<v-fade-transition hide-on-leave>
|
||||||
|
<nuxt />
|
||||||
|
</v-fade-transition>
|
||||||
|
</v-main>
|
||||||
|
<Footer/>
|
||||||
|
|
||||||
v-main(app)
|
</v-app>
|
||||||
v-fade-transition(hide-on-leave)
|
|
||||||
nuxt
|
|
||||||
|
|
||||||
Footer
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
@ -17,6 +24,7 @@ import Snackbar from '../components/Snackbar'
|
||||||
import Footer from '../components/Footer'
|
import Footer from '../components/Footer'
|
||||||
import Confirm from '../components/Confirm'
|
import Confirm from '../components/Confirm'
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
|
import { mdiChevronLeft } from '@mdi/js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
head () {
|
head () {
|
||||||
|
@ -26,9 +34,24 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
data () {
|
||||||
|
return { cohorts: [], mdiChevronLeft }
|
||||||
|
},
|
||||||
|
async fetch () {
|
||||||
|
this.cohorts = await this.$axios.$get('cohorts')
|
||||||
|
},
|
||||||
name: 'Default',
|
name: 'Default',
|
||||||
components: { Nav, Snackbar, Footer, Confirm },
|
components: { Nav, Snackbar, Footer, Confirm },
|
||||||
computed: mapState(['settings', 'locale']),
|
computed: {
|
||||||
|
...mapState(['settings', 'locale']),
|
||||||
|
showBack () {
|
||||||
|
return ['tag-tag', 'g-cohort', 'p-place', 'search', 'announcement-id'].includes(this.$route.name)
|
||||||
|
},
|
||||||
|
showCohorts () {
|
||||||
|
if (!this.cohorts || this.cohorts.length === 0) return false
|
||||||
|
return ['tag-tag', 'index', 'g-cohort', 'p-place'].includes(this.$route.name)
|
||||||
|
}
|
||||||
|
},
|
||||||
created () {
|
created () {
|
||||||
this.$vuetify.theme.dark = this.settings['theme.is_dark']
|
this.$vuetify.theme.dark = this.settings['theme.is_dark']
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<template lang='pug'>
|
<template lang='pug'>
|
||||||
v-app#iframe
|
v-app#iframe
|
||||||
nuxt
|
nuxt
|
||||||
</template>
|
</template>
|
||||||
<style lang='less'>
|
<style>
|
||||||
#iframe.v-application {
|
#iframe.v-application {
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,7 +109,7 @@
|
||||||
"list_description": "Si tens una web i vols encastar una llista d'activitats, pots fer servir el codi de sota"
|
"list_description": "Si tens una web i vols encastar una llista d'activitats, pots fer servir el codi de sota"
|
||||||
},
|
},
|
||||||
"register": {
|
"register": {
|
||||||
"description": "Els moviments socials necessitem organitzar-nos i auto-finançar-nos.\n<br/> Abans que puguis publicar, <strong> hem d'aprovar el teu compte </strong>, tingues en comtpe que <strong> darrere d'aquesta web hi ha persones </strong> de carn i ossos, així que escriviu dues línies per fer-nos saber quins esdeveniments voleu publicar.",
|
"description": "Els moviments socials necessitem organitzar-nos i auto-finançar-nos.<br/>\n<br/>Abans que puguis publicar, <strong> hem d'aprovar el teu compte </strong>, tingues en compte que <strong> darrere d'aquesta web hi ha persones </strong> de carn i ossos, així que escriviu dues línies per fer-nos saber quins esdeveniments voleu publicar.",
|
||||||
"error": "Error: ",
|
"error": "Error: ",
|
||||||
"complete": "El registre ha de ser confirmat.",
|
"complete": "El registre ha de ser confirmat.",
|
||||||
"first_user": "S'ha creat i activat un compte administrador"
|
"first_user": "S'ha creat i activat un compte administrador"
|
||||||
|
@ -124,7 +124,7 @@
|
||||||
"media_description": "Pots adjuntar un cartell (opcional)",
|
"media_description": "Pots adjuntar un cartell (opcional)",
|
||||||
"added": "S'ha afegit l'activitat",
|
"added": "S'ha afegit l'activitat",
|
||||||
"added_anon": "S'ha afegit l'activitat però encara ha de ser confirmada.",
|
"added_anon": "S'ha afegit l'activitat però encara ha de ser confirmada.",
|
||||||
"where_description": "On es farà? Si no està posat, escriu-ho i <b>prem Enter</b>.",
|
"where_description": "On es farà? Si no està posat, escriu-ho i prem Enter.",
|
||||||
"confirmed": "S'ha confirmat l'activitat",
|
"confirmed": "S'ha confirmat l'activitat",
|
||||||
"not_found": "No s'ha trobat l'activitat",
|
"not_found": "No s'ha trobat l'activitat",
|
||||||
"remove_confirmation": "Segur que vols esborrar l'activitat?",
|
"remove_confirmation": "Segur que vols esborrar l'activitat?",
|
||||||
|
@ -150,7 +150,7 @@
|
||||||
"from": "Des de les",
|
"from": "Des de les",
|
||||||
"image_too_big": "La imatge és massa gran! Max 4 MB",
|
"image_too_big": "La imatge és massa gran! Max 4 MB",
|
||||||
"interact_with_me_at": "Interacciona amb mi a",
|
"interact_with_me_at": "Interacciona amb mi a",
|
||||||
"follow_me_description": "Entre les diverses maneres d'estar al dia de les activitats que es publiquen aquí a {title},\n pots seguir-nos al compte <u>{account}</u> des de Mastodon o altres, i afegir recursos des d'allà. <br/> <br/>\nSi no has sentit mai sobre «Mastodon» o «Fedivers», recomanem mirar <a href='https://peertube.social/videos/watch/d9bd2ee9-b7a4-44e3-8d65-61badd15c6e6'> aquest vídeo (subtitulat en català)</a>. <br/> <br/> Introdueix la teva instància a sota (ex: red.confederac.io o mastodont.cat)",
|
"follow_me_description": "Entre les diverses maneres d'estar al dia de les activitats que es publiquen aquí a {title},\n pots seguir-nos al compte <u>{account}</u> des de Mastodon o altres, i afegir recursos des d'allà. <br/> <br/>\nSi no has sentit mai sobre «Mastodon» o «Fedivers», recomanem fer un cop d'ull a <a href='https://equipamentslliures.cat/divulgacio/fediverse'>aquesta breu introducció al Fedivers</a>. <br/> <br/> Introdueix la teva instància a sota (ex: kolektiva.social o mastodont.cat)",
|
||||||
"interact_with_me": "Segueix-nos al fedivers",
|
"interact_with_me": "Segueix-nos al fedivers",
|
||||||
"remove_recurrent_confirmation": "Estàs segur/a d'esborrar aquesta activitat periòdica?\nNo s'esborraran les ocurrències antigues, només es deixaran de crear les futures.",
|
"remove_recurrent_confirmation": "Estàs segur/a d'esborrar aquesta activitat periòdica?\nNo s'esborraran les ocurrències antigues, només es deixaran de crear les futures.",
|
||||||
"ics": "ICS",
|
"ics": "ICS",
|
||||||
|
@ -159,7 +159,11 @@
|
||||||
"edit_recurrent": "Edita l'activitat periòdica:",
|
"edit_recurrent": "Edita l'activitat periòdica:",
|
||||||
"updated": "S'ha actualitzat l'activitat",
|
"updated": "S'ha actualitzat l'activitat",
|
||||||
"saved": "S'ha desat l'activitat",
|
"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)"
|
"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",
|
||||||
|
"alt_text_description": "Descripció per a persones amb discapacitat visual",
|
||||||
|
"choose_focal_point": "Tria el punt focal"
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"place_description": "En el cas que un lloc és incorrecte o l'adreça ha de canviar, pots arreglar-ho.<br/>Tingues en compte que totes les activitats passades i futures associades amb aquest lloc també canviaran d'adreça.",
|
"place_description": "En el cas que un lloc és incorrecte o l'adreça ha de canviar, pots arreglar-ho.<br/>Tingues en compte que totes les activitats passades i futures associades amb aquest lloc també canviaran d'adreça.",
|
||||||
|
@ -226,7 +230,9 @@
|
||||||
"smtp_hostname": "Amfitrió SMTP (hostname)",
|
"smtp_hostname": "Amfitrió SMTP (hostname)",
|
||||||
"smtp_description": "<ul><li>L'admin hauria de rebre un correu cada cop que es pengi alguna una activitat anònima (si estan activades).</li><li>L'admin hauria de rebre un correu per cada soŀlicitud de registre (si estan actives).</li><li>La usuària hauria de rebre un correu després de soŀlicitar registrar-se.</li><li>La usuària hauria de rebre un correu quan se li hagi confirmat el registre.</li><li>La usuària hauria de rebre un correu si l'admin la registra directament.</li><li>La usuària hauria de rebre un correu de restabliment de contrasenya si ho demana</li></ul>",
|
"smtp_description": "<ul><li>L'admin hauria de rebre un correu cada cop que es pengi alguna una activitat anònima (si estan activades).</li><li>L'admin hauria de rebre un correu per cada soŀlicitud de registre (si estan actives).</li><li>La usuària hauria de rebre un correu després de soŀlicitar registrar-se.</li><li>La usuària hauria de rebre un correu quan se li hagi confirmat el registre.</li><li>La usuària hauria de rebre un correu si l'admin la registra directament.</li><li>La usuària hauria de rebre un correu de restabliment de contrasenya si ho demana</li></ul>",
|
||||||
"smtp_test_button": "Envia un correu de prova",
|
"smtp_test_button": "Envia un correu de prova",
|
||||||
"widget": "Giny"
|
"widget": "Giny",
|
||||||
|
"wrong_domain_warning": "La url base configurada a config.json <b>({baseurl})</b> difereix de la que esteu visitant <b>({url})</b>",
|
||||||
|
"event_remove_ok": "S'ha suprimit l'esdeveniment"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"not_confirmed": "Encara no s'ha confirmat…",
|
"not_confirmed": "Encara no s'ha confirmat…",
|
||||||
|
@ -272,6 +278,8 @@
|
||||||
"setup": {
|
"setup": {
|
||||||
"completed": "S'ha completat la configuració inicial",
|
"completed": "S'ha completat la configuració inicial",
|
||||||
"completed_description": "<p>Ara ja pots entrar amb aquesta usuària:<br/><br/>Nom: <b>{email}</b><br/>Contrasenya: <b>{password}<b/></p>",
|
"completed_description": "<p>Ara ja pots entrar amb aquesta usuària:<br/><br/>Nom: <b>{email}</b><br/>Contrasenya: <b>{password}<b/></p>",
|
||||||
"start": "Comença"
|
"start": "Comença",
|
||||||
|
"copy_password_dialog": "Sí, has de copiar la contrasenya!",
|
||||||
|
"https_warning": "Esteu visitant des d'HTTP, recordeu canviar baseurl a config.json si canvieu a HTTPS!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,5 +24,9 @@
|
||||||
},
|
},
|
||||||
"event_confirm": {
|
"event_confirm": {
|
||||||
"content": "Puede confirmar este evento <a href='{{url}}'>aquí</a>"
|
"content": "Puede confirmar este evento <a href='{{url}}'>aquí</a>"
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"subject": "Tu configuración SMTP funciona",
|
||||||
|
"content": "Esto es un email de prueba. Si estás leyendo esto es que tu configuración funciona."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,7 +86,8 @@
|
||||||
"reset": "Reset",
|
"reset": "Reset",
|
||||||
"import": "Import",
|
"import": "Import",
|
||||||
"max_events": "N. max events",
|
"max_events": "N. max events",
|
||||||
"label": "Label"
|
"label": "Label",
|
||||||
|
"blobs": "Blobs"
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"description": "By logging in you can publish new events.",
|
"description": "By logging in you can publish new events.",
|
||||||
|
@ -151,7 +152,7 @@
|
||||||
"each_month": "Each month",
|
"each_month": "Each month",
|
||||||
"due": "until",
|
"due": "until",
|
||||||
"from": "From",
|
"from": "From",
|
||||||
"image_too_big": "The image can't be bigger than 4 MB",
|
"image_too_big": "The image can't be bigger than 4MB",
|
||||||
"interact_with_me_at": "Interact with me on fediverse at",
|
"interact_with_me_at": "Interact with me on fediverse at",
|
||||||
"follow_me_description": "One of the ways to stay up to date on events published here on {title},\nis following the account <u>{account}</u> from the fediverse, for example via Mastodon, and possibly add resources to an event from there.<br/><br/>\nIf you have never heard of Mastodon and the fediverse we recommend reading <a href='https://www.savjee.be/videos/simply-explained/mastodon-and-fediverse-explained/'>this article</a>.<br/><br/>Enter your instance below (e.g. mastodon.social)",
|
"follow_me_description": "One of the ways to stay up to date on events published here on {title},\nis following the account <u>{account}</u> from the fediverse, for example via Mastodon, and possibly add resources to an event from there.<br/><br/>\nIf you have never heard of Mastodon and the fediverse we recommend reading <a href='https://www.savjee.be/videos/simply-explained/mastodon-and-fediverse-explained/'>this article</a>.<br/><br/>Enter your instance below (e.g. mastodon.social)",
|
||||||
"interact_with_me": "Follow me",
|
"interact_with_me": "Follow me",
|
||||||
|
@ -159,7 +160,11 @@
|
||||||
"import_URL": "Import from URL",
|
"import_URL": "Import from URL",
|
||||||
"import_ICS": "Import from ICS",
|
"import_ICS": "Import from ICS",
|
||||||
"ics": "ICS",
|
"ics": "ICS",
|
||||||
"import_description": "You can import events from other platforms and other instances through standard formats (ics and h-event)"
|
"import_description": "You can import events from other platforms and other instances through standard formats (ics and h-event)",
|
||||||
|
"alt_text_description": "Description for people with visual impairments",
|
||||||
|
"choose_focal_point": "Choose the focal point",
|
||||||
|
"remove_media_confirmation": "Do you confirm the image removal?",
|
||||||
|
"download_flyer": "Download flyer"
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"place_description": "If you have gotten the place or address wrong, you can change it.<br/>All current and past events associated with this place will change address.",
|
"place_description": "If you have gotten the place or address wrong, you can change it.<br/>All current and past events associated with this place will change address.",
|
||||||
|
@ -170,6 +175,7 @@
|
||||||
"delete_user_confirm": "Are you sure you want to remove {user}?",
|
"delete_user_confirm": "Are you sure you want to remove {user}?",
|
||||||
"user_remove_ok": "User removed",
|
"user_remove_ok": "User removed",
|
||||||
"user_create_ok": "User created",
|
"user_create_ok": "User created",
|
||||||
|
"event_remove_ok": "Event removed",
|
||||||
"allow_registration_description": "Allow open registrations?",
|
"allow_registration_description": "Allow open registrations?",
|
||||||
"allow_anon_event": "Allow anonymous events (has to be confirmed)?",
|
"allow_anon_event": "Allow anonymous events (has to be confirmed)?",
|
||||||
"allow_recurrent_event": "Allow recurring events",
|
"allow_recurrent_event": "Allow recurring events",
|
||||||
|
@ -226,7 +232,8 @@
|
||||||
"smtp_test_success": "A test email is sent to {admin_email}, please check your inbox",
|
"smtp_test_success": "A test email is sent to {admin_email}, please check your inbox",
|
||||||
"smtp_test_button": "Send a test email",
|
"smtp_test_button": "Send a test email",
|
||||||
"admin_email": "Admin e-mail",
|
"admin_email": "Admin e-mail",
|
||||||
"widget": "Widget"
|
"widget": "Widget",
|
||||||
|
"wrong_domain_warning": "The baseurl configured in config.json <b>({baseurl})</b> differs from the one you're visiting <b>({url})</b>"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"not_confirmed": "Not confirmed yet…",
|
"not_confirmed": "Not confirmed yet…",
|
||||||
|
@ -273,6 +280,7 @@
|
||||||
"completed": "Setup completed",
|
"completed": "Setup completed",
|
||||||
"completed_description": "<p>You can now login with the following user:<br/><br/>User: <b>{email}</b><br/>Password: <b>{password}<b/></p>",
|
"completed_description": "<p>You can now login with the following user:<br/><br/>User: <b>{email}</b><br/>Password: <b>{password}<b/></p>",
|
||||||
"copy_password_dialog": "Yes, you have to copy the password!",
|
"copy_password_dialog": "Yes, you have to copy the password!",
|
||||||
"start": "Start"
|
"start": "Start",
|
||||||
|
"https_warning": "You're visiting from HTTP, remember to change baseurl in config.json if you switch to HTTPS!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,19 +105,19 @@
|
||||||
"intro": "A diferencia de las plataformas del capitalismo, que hacen todo lo posible para mantener datos y usuarios dentro de ellas, creemos las informaciones, así como las personas, deben ser libres. Para ello, puedes mantenerte enterado sobre los eventos que te interesan como mejor te parezca, sin necesariamente tener que pasar por este sitio.",
|
"intro": "A diferencia de las plataformas del capitalismo, que hacen todo lo posible para mantener datos y usuarios dentro de ellas, creemos las informaciones, así como las personas, deben ser libres. Para ello, puedes mantenerte enterado sobre los eventos que te interesan como mejor te parezca, sin necesariamente tener que pasar por este sitio.",
|
||||||
"email_description": "Puedes recibir por mail los eventos que te interesan.",
|
"email_description": "Puedes recibir por mail los eventos que te interesan.",
|
||||||
"insert_your_address": "Casilla de correo",
|
"insert_your_address": "Casilla de correo",
|
||||||
"feed_description": "Para seguir las actualizaciones desde un ordenador o teléfono inteligente sin la necesidad de abrir periódicamente el sitio, el método recomendado es usar los feeds RSS.</p>\n\n <p>Con rss feeds, utilizas una aplicación especial para recibir actualizaciones de los sitios que más te interesan, como por ejemplo éste. Es una buena manera de seguir muchos sitios muy rápidamente, sin la necesidad de crear una cuenta u otras complicaciones.</p>\n \n <li>Si tienes Android, te sugerimos <a href=\"https://f-droid.org/es/packages/com.nononsenseapps.feeder/\">Feeder</a> o Feeder</li>\n <li>Para iPhone/iPad puedes usar <a href=\"https://itunes.apple.com/ua/app/feeds4u/id1038456442?mt=8\">Feed4U</a></li>\n <li>En el caso de un ordenador aconsejamos Feedbro, se instala como plugin <a href=\"https://addons.mozilla.org/es-ES/firefox/addon/feedbroreader/\">de Firefox </a>o <a href=\"https://chrome.google.com/webstore/detail/feedbro/mefgmmbdailogpfhfblcnnjfmnpnmdfa\">de Chrome</a> y funciona con todos los principales sistemas.</li>\n <br/>\n Agregando este link a tu lector de feed, estarás siempre actualizado/a.",
|
"feed_description": "Para seguir las actualizaciones desde un ordenador o teléfono inteligente sin la necesidad de abrir periódicamente el sitio, el método recomendado es usar los feeds RSS.</p>\n\n<p>Con rss feeds, utilizas una aplicación especial para recibir actualizaciones de los sitios que más te interesan, como por ejemplo éste. Es una buena manera de seguir muchos sitios muy rápidamente, sin la necesidad de crear una cuenta u otras complicaciones.</p>\n\n<li>Si tienes Android, te sugerimos <a href=\"https://f-droid.org/es/packages/com.nononsenseapps.feeder/\">Feeder</a> o Feeder</li>\n<li>Para iPhone/iPad puedes usar <a href=\"https://itunes.apple.com/ua/app/feeds4u/id1038456442?mt=8\">Feed4U</a></li>\n<li>En el caso de un ordenador aconsejamos Feedbro, se instala como plugin <a href=\"https://addons.mozilla.org/es-ES/firefox/addon/feedbroreader/\">de Firefox </a>o <a href=\"https://chrome.google.com/webstore/detail/feedbro/mefgmmbdailogpfhfblcnnjfmnpnmdfa\">de Chrome</a> y funciona con todos los principales sistemas.</li>\n<br/>\nAgregando este link a tu lector de feed, estarás siempre actualizado/a.",
|
||||||
"ical_description": "Las computadoras y los teléfonos inteligentes suelen estar equipados con una aplicación para administrar un calendario. Estos programas generalmente se pueden usar para importar un calendario remoto.",
|
"ical_description": "Las computadoras y los teléfonos inteligentes suelen estar equipados con una aplicación para administrar un calendario. Estos programas generalmente se pueden usar para importar un calendario remoto.",
|
||||||
"list_description": "Si tienes un sitio web y quieres mostrar una lista de eventos, puedes usar el siguiente código"
|
"list_description": "Si tienes un sitio web y quieres mostrar una lista de eventos, puedes usar el siguiente código"
|
||||||
},
|
},
|
||||||
"register": {
|
"register": {
|
||||||
"description": "Los movimientos sociales necesitan organizarse y autofinanciarse. <br/> Este es un regalo para ustedes, úsenlo solamente para eventos con fines no comerciales y obviamente antifascistas, antisexistas y antirracistas.\n<br/> Antes de que puedas publicar <strong> debemos aprobar la cuenta </strong>. Como imaginarás, <strong> detrás de este sitio hay personas </strong> de carne y hueso, por esto te pedimos escribir algo para hacernos saber que tipos de eventos te gustaría publicar.",
|
"description": "Los movimientos sociales necesitan organizarse y autofinanciarse. <br/>\nEste es un regalo para ustedes, úsenlo solamente para eventos con fines no comerciales y obviamente antifascistas, antisexistas y antirracistas.\n<br/> Antes de que puedas publicar, <strong> debemos aprobar la cuenta </strong>. Como imaginarás, <strong> detrás de este sitio hay personas de carne y hueso</strong>, por esto te pedimos escribir algo para hacernos saber que tipos de eventos te gustaría publicar.",
|
||||||
"error": "Error: ",
|
"error": "Error: ",
|
||||||
"complete": "Confirmaremos el registro lo antes posible.",
|
"complete": "Confirmaremos el registro lo antes posible.",
|
||||||
"first_user": "Administrador creado y activado"
|
"first_user": "Administrador creado y activado"
|
||||||
},
|
},
|
||||||
"event": {
|
"event": {
|
||||||
"anon": "Anónimo",
|
"anon": "Anónimo",
|
||||||
"anon_description": "Puedes ingresar un evento sin registrarte o iniciar sesión, pero en este caso tendrás que esperar a que alguien lo lea para confirmar que es un evento adecuado para este espacio,\ndelegando esta elección. Además, no será posible modificarlo. <br/> <br/>\nSi no te gusta, puedes <a href='/login'> iniciar sesión </a> o <a href='/register'> registrarte </a>,\nde lo contrario, continúa y recibirás una respuesta lo antes posible. ",
|
"anon_description": "Puedes ingresar un evento sin registrarte o iniciar sesión, pero en este caso tendrás que esperar a que alguien lo lea para confirmar que es un evento adecuado para este espacio,\ndelegando esta elección. Además, no será posible modificarlo. <br/> <br/>\nSi no te gusta, puedes <a href='/login'> iniciar sesión </a> o <a href='/register'> registrarte </a>. De lo contrario, continúa y recibirás una respuesta lo antes posible. ",
|
||||||
"same_day": "Mismo día",
|
"same_day": "Mismo día",
|
||||||
"what_description": "Nombre evento",
|
"what_description": "Nombre evento",
|
||||||
"description_description": "Descripción, puedes copiar y pegar",
|
"description_description": "Descripción, puedes copiar y pegar",
|
||||||
|
@ -148,7 +148,7 @@
|
||||||
"from": "Desde las",
|
"from": "Desde las",
|
||||||
"image_too_big": "La imagén es demasiado grande! Tamaño máx 4M",
|
"image_too_big": "La imagén es demasiado grande! Tamaño máx 4M",
|
||||||
"interact_with_me_at": "Sígueme en el fediverso en",
|
"interact_with_me_at": "Sígueme en el fediverso en",
|
||||||
"show_recurrent": "Eventos recurrientes",
|
"show_recurrent": "Eventos recurrentes",
|
||||||
"show_past": "eventos pasados",
|
"show_past": "eventos pasados",
|
||||||
"follow_me_description": "Entre las diversas formas de mantenerse al día con los eventos publicados aquí en {title},\npuedes seguir la cuenta <u>{account}</u> desde el fediverso, por ejemplo, a través de un Mastodon, y posiblemente añadir recursos a un evento desde allí.<br/><br/>\nSi nunca has oído hablar del Mastodon y el fediverso te sugerimos que leas <a href='https://cagizero.wordpress.com/2018/10/25/cose-mastodon/'>este artículo</a>.<br/><br/> Introduce tu instancia abajo (por ejemplo mastodon.cisti.org o mastodon.bida.im)",
|
"follow_me_description": "Entre las diversas formas de mantenerse al día con los eventos publicados aquí en {title},\npuedes seguir la cuenta <u>{account}</u> desde el fediverso, por ejemplo, a través de un Mastodon, y posiblemente añadir recursos a un evento desde allí.<br/><br/>\nSi nunca has oído hablar del Mastodon y el fediverso te sugerimos que leas <a href='https://cagizero.wordpress.com/2018/10/25/cose-mastodon/'>este artículo</a>.<br/><br/> Introduce tu instancia abajo (por ejemplo mastodon.cisti.org o mastodon.bida.im)",
|
||||||
"interact_with_me": "Sigueme en el fediverso",
|
"interact_with_me": "Sigueme en el fediverso",
|
||||||
|
@ -221,7 +221,14 @@
|
||||||
"is_dark": "Tema oscuro",
|
"is_dark": "Tema oscuro",
|
||||||
"instance_block_confirm": "¿Estás seguro/a que quieres bloquear la instancia {instance}?",
|
"instance_block_confirm": "¿Estás seguro/a que quieres bloquear la instancia {instance}?",
|
||||||
"add_instance": "Añadir instancia",
|
"add_instance": "Añadir instancia",
|
||||||
"disable_user_confirm": "Estas seguro de que quieres deshabilitar a {user}?"
|
"disable_user_confirm": "Estas seguro de que quieres deshabilitar a {user}?",
|
||||||
|
"widget": "Widget",
|
||||||
|
"show_smtp_setup": "Ajustes de correo",
|
||||||
|
"smtp_hostname": "Nombre del equipo SMTP",
|
||||||
|
"smtp_test_success": "Un correo de prueba se ha enviado a {admin_email}. Por favos, comprueba tu bandeja de entrada",
|
||||||
|
"smtp_test_button": "Enviar correo de prueba",
|
||||||
|
"admin_email": "Correo del administrador",
|
||||||
|
"smtp_description": "<ul><li>El administrador debería recibir un correo cuando son añadidos eventos anónimos (si está habilitado).</li><li>El administrador debería recibir un correo de petición de registro (si está habilitado).</li><li>El usuario debería recibir un correo de petición de registro.</li><li>El usuario debería recibir un correo de confirmación de registro.</li><li>El usuario debería recibir un correo de confirmación cuando el administrador le subscriba directamente.</li><li>El usuario debería recibir un correo para restaurar la contraseña cuando la haya olvidado.</li></ul>"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"not_confirmed": "Todavía no hemos confirmado este email…",
|
"not_confirmed": "Todavía no hemos confirmado este email…",
|
||||||
|
@ -263,5 +270,10 @@
|
||||||
"validators": {
|
"validators": {
|
||||||
"email": "Introduce un correo electrónico valido",
|
"email": "Introduce un correo electrónico valido",
|
||||||
"required": "{fieldName} es requerido"
|
"required": "{fieldName} es requerido"
|
||||||
|
},
|
||||||
|
"setup": {
|
||||||
|
"completed": "Configuración completada",
|
||||||
|
"start": "Inicio",
|
||||||
|
"completed_description": "<p>Puedes ingresar con el siguiente usuario:<br/><br/>Usuario: <b>{email}</b><br/>Contraseña: <b>{password}<b/></p>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,7 +109,7 @@
|
||||||
"list_description": "Webgune bat baduzu eta ekitaldien zerrenda erakutsi nahi baduzu, ondorengo kodea erabili"
|
"list_description": "Webgune bat baduzu eta ekitaldien zerrenda erakutsi nahi baduzu, ondorengo kodea erabili"
|
||||||
},
|
},
|
||||||
"register": {
|
"register": {
|
||||||
"description": "Herri mugimenduek autoantolaketaren bidean diru-iturrien beharrak dauzkatela badakigu.<br/>Honako hauxe oparitxoa da, hortaz erabili ezazue ekitaldi ez-komertzialak iragartzeko, eta esan gabe doa, ekitaldi antifaxistak, antisexistak eta antiarriztetarako :) .\n<br/>Argitaratzen hasi baino lehen<strong> zure kontu berriak onarpena jaso beharko du </strong>beraz, <strong>webgune honen atzean hezur-haragizko pertsonak gaudela jakinda </strong>, (momenutz euskal 'AI'-rik ez daukagu baina adi, agertuko direla) idatzi iezaguzu lerro batzuk argitaratu nahi dituzun ekitaldiei buruz.",
|
"description": "Gizarte mugimenduak beraien kabuz antolatu behar dira.<br/>\n<br/>Argitaratzen hasi baino lehen <strong>zure kontu berria onartua izan behar da</strong>, beraz, <strong>webgune honen atzean hezur-haragizko pertsonak</strong> gaudela kontuan izanik, azal iezaguzu mesedez pare bat lerrotan zer nolako ekitaldiak argitaratu nahi dituzun.",
|
||||||
"error": "Errorea: ",
|
"error": "Errorea: ",
|
||||||
"complete": "Izen-ematea baieztatua izan behar da.",
|
"complete": "Izen-ematea baieztatua izan behar da.",
|
||||||
"first_user": "Administratzailea sortu da"
|
"first_user": "Administratzailea sortu da"
|
||||||
|
@ -159,7 +159,11 @@
|
||||||
"only_future": "datozen ekitaldiak bakarrik",
|
"only_future": "datozen ekitaldiak bakarrik",
|
||||||
"edit_recurrent": "Editatu ekitaldi errepikaria:",
|
"edit_recurrent": "Editatu ekitaldi errepikaria:",
|
||||||
"updated": "Ekitaldia eguneratu da",
|
"updated": "Ekitaldia eguneratu da",
|
||||||
"saved": "Ekitaldia gorde da"
|
"saved": "Ekitaldia gorde da",
|
||||||
|
"remove_media_confirmation": "Irudiaren ezabaketa baieztatzen duzu?",
|
||||||
|
"alt_text_description": "Ikusmen-urritasunak dituztenentzako deskripzioa",
|
||||||
|
"choose_focal_point": "Aukeratu arretagunea",
|
||||||
|
"download_flyer": "Deskargatu eskuorria"
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"place_description": "Helbidea oker badago, alda dezakezu.<br/>Leku honekin lotutako iraganeko eta etorkizuneko ekitaldien helbidea aldatuko da.",
|
"place_description": "Helbidea oker badago, alda dezakezu.<br/>Leku honekin lotutako iraganeko eta etorkizuneko ekitaldien helbidea aldatuko da.",
|
||||||
|
@ -225,7 +229,10 @@
|
||||||
"smtp_test_success": "Probako eposta bidali da {admin_email}-(e)ra, begiratu zure sarrera-ontzia",
|
"smtp_test_success": "Probako eposta bidali da {admin_email}-(e)ra, begiratu zure sarrera-ontzia",
|
||||||
"admin_email": "Administratzailearen eposta",
|
"admin_email": "Administratzailearen eposta",
|
||||||
"smtp_hostname": "SMTP hostname",
|
"smtp_hostname": "SMTP hostname",
|
||||||
"smtp_description": "<ul><li>Administratzaileak eposta bat jaso beharko luke anonimo batek ekitaldi bat gehitzen duenean (gaituta badago).</li><li>Administratzaileak eposta bat jaso beharko luke izena emateko eskari bakoitzeko (gaituta badago).</li><li>Erabiltzaileak eposta bat jaso beharko luke izena emateko eskariarekin.</li><li>Erabiltzaileak eposta bat jaso beharko luke izen ematea baieztatzean.</li><li>Erabiltzaileak eposta bat jaso beharko luke administratzaileak zuzenean izena emanez gero.</li><li>Erabiltzaileek eposta bat jaso beharko lukete pasahitza ahazten dutenean.</li></ul>"
|
"smtp_description": "<ul><li>Administratzaileak eposta bat jaso beharko luke anonimo batek ekitaldi bat gehitzen duenean (gaituta badago).</li><li>Administratzaileak eposta bat jaso beharko luke izena emateko eskari bakoitzeko (gaituta badago).</li><li>Erabiltzaileak eposta bat jaso beharko luke izena emateko eskariarekin.</li><li>Erabiltzaileak eposta bat jaso beharko luke izen ematea baieztatzean.</li><li>Erabiltzaileak eposta bat jaso beharko luke administratzaileak zuzenean izena emanez gero.</li><li>Erabiltzaileek eposta bat jaso beharko lukete pasahitza ahazten dutenean.</li></ul>",
|
||||||
|
"widget": "Tresna",
|
||||||
|
"event_remove_ok": "Ekitaldia ezabatu da",
|
||||||
|
"wrong_domain_warning": "config.json-en konfiguratuta dagoen baseurl <b>({baseurl})</b> ez da bisitatzen ari zaren berbera <b>({url})</b>"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"not_confirmed": "Oraindik baieztatu gabe dago…",
|
"not_confirmed": "Oraindik baieztatu gabe dago…",
|
||||||
|
@ -271,6 +278,8 @@
|
||||||
"setup": {
|
"setup": {
|
||||||
"start": "Hasi",
|
"start": "Hasi",
|
||||||
"completed": "Instalazioa bukatu da",
|
"completed": "Instalazioa bukatu da",
|
||||||
"completed_description": "<p>Erabiltzaile honekin saioa has dezakezu orain:<br/><br/>Erabiltzailea: <b>{email}</b><br/>Pasahitza: <b>{password}<b/></p>"
|
"completed_description": "<p>Erabiltzaile honekin saioa has dezakezu orain:<br/><br/>Erabiltzailea: <b>{email}</b><br/>Pasahitza: <b>{password}<b/></p>",
|
||||||
|
"copy_password_dialog": "Bai, pasahitza kopiatu behar duzu!",
|
||||||
|
"https_warning": "HTTP bidez ari zarela kontuan izan. HTTPSra pasatzen bazara gogoratu config.json-en baseurl aldatzeaz!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,7 +133,10 @@
|
||||||
"edit_recurrent": "Modifier l’évènement récurrent :",
|
"edit_recurrent": "Modifier l’évènement récurrent :",
|
||||||
"updated": "Évènement mis à jour",
|
"updated": "Évènement mis à jour",
|
||||||
"import_description": "Vous pouvez importer des événements depuis d'autres plateformes ou d'autres instances à travers des formats standards (ics et h-event)",
|
"import_description": "Vous pouvez importer des événements depuis d'autres plateformes ou d'autres instances à travers des formats standards (ics et h-event)",
|
||||||
"saved": "Événement enregistré"
|
"saved": "Événement enregistré",
|
||||||
|
"alt_text_description": "Description pour les personnes avec une déficience visuelle",
|
||||||
|
"remove_media_confirmation": "Confirmer la suppression de l'image ?",
|
||||||
|
"download_flyer": "Télécharger le flyer"
|
||||||
},
|
},
|
||||||
"register": {
|
"register": {
|
||||||
"description": "Les mouvements sociaux doivent s'organiser et s'autofinancer.<br/>\n<br/>Avant de pouvoir publier, <strong> le compte doit être approuvé</strong>, considérez que <strong> derrière ce site vous trouverez de vraies personnes</strong>, à qui vous pouvez écrire en deux lignes pour exprimer les évènements que vous souhaiteriez publier.",
|
"description": "Les mouvements sociaux doivent s'organiser et s'autofinancer.<br/>\n<br/>Avant de pouvoir publier, <strong> le compte doit être approuvé</strong>, considérez que <strong> derrière ce site vous trouverez de vraies personnes</strong>, à qui vous pouvez écrire en deux lignes pour exprimer les évènements que vous souhaiteriez publier.",
|
||||||
|
@ -273,6 +276,7 @@
|
||||||
"check_db": "Vérifier la base de données",
|
"check_db": "Vérifier la base de données",
|
||||||
"completed": "Configuration terminée",
|
"completed": "Configuration terminée",
|
||||||
"completed_description": "<p>Vous pouvez désormais vous connectez avec le compte utilisateur suivant :<br/><br/>Identifiant : <b>{email}</b><br/>Mot de passe : <b>{password}<b/></p>",
|
"completed_description": "<p>Vous pouvez désormais vous connectez avec le compte utilisateur suivant :<br/><br/>Identifiant : <b>{email}</b><br/>Mot de passe : <b>{password}<b/></p>",
|
||||||
"start": "Commencer"
|
"start": "Commencer",
|
||||||
|
"copy_password_dialog": "Oui, vous devez copier le mot de passe !"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,12 +145,16 @@
|
||||||
"normal_description": "Elixe o día.",
|
"normal_description": "Elixe o día.",
|
||||||
"recurrent_1w_days": "Cada {days}",
|
"recurrent_1w_days": "Cada {days}",
|
||||||
"recurrent_1m_ordinal": "O {n} {days} de cada mes",
|
"recurrent_1m_ordinal": "O {n} {days} de cada mes",
|
||||||
"image_too_big": "A imaxe non pode superar os 4 MB",
|
"image_too_big": "A imaxe non pode superar os 4MB",
|
||||||
"recurrent_1m_days": "|O {days} de cada mes|{days} de cada mes",
|
"recurrent_1m_days": "|O {days} de cada mes|{days} de cada mes",
|
||||||
"each_2w": "Cada dúas semanas",
|
"each_2w": "Cada dúas semanas",
|
||||||
"follow_me_description": "Un dos xeitos de recibir actualizacións dos eventos que se publican aquí en {title},\né seguindo a conta <u>{account}</u> no fediverso, por exemplo a través de Mastodon, e posiblemente tamén engadir recursos para un evento desde alí.<br/><br/>\nSe nunco escoitaches falar de Mastodon e o fediverso recomendámosche ler <a href='https://www.savjee.be/videos/simply-explained/mastodon-and-fediverse-explained/'>este artigo</a>.<br/><br/>Escribe aquí a túa instancia (ex. mastodon.social)",
|
"follow_me_description": "Un dos xeitos de recibir actualizacións dos eventos que se publican aquí en {title},\né seguindo a conta <u>{account}</u> no fediverso, por exemplo a través de Mastodon, e posiblemente tamén engadir recursos para un evento desde alí.<br/><br/>\nSe nunco escoitaches falar de Mastodon e o fediverso recomendámosche ler <a href='https://www.savjee.be/videos/simply-explained/mastodon-and-fediverse-explained/'>este artigo</a>.<br/><br/>Escribe aquí a túa instancia (ex. mastodon.social)",
|
||||||
"ics": "ICS",
|
"ics": "ICS",
|
||||||
"import_description": "Podes importar eventos desde outras plataformas e outras instancias usando formatos estándar (ics e h-event)"
|
"import_description": "Podes importar eventos desde outras plataformas e outras instancias usando formatos estándar (ics e h-event)",
|
||||||
|
"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"
|
||||||
},
|
},
|
||||||
"admin": {
|
"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.",
|
"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.",
|
||||||
|
@ -217,7 +221,9 @@
|
||||||
"show_smtp_setup": "Axustes do email",
|
"show_smtp_setup": "Axustes do email",
|
||||||
"smtp_hostname": "Servidor SMTP",
|
"smtp_hostname": "Servidor SMTP",
|
||||||
"new_announcement": "Novo anuncio",
|
"new_announcement": "Novo anuncio",
|
||||||
"smtp_description": "<ul><li>Admin debería recibir un email cando se engade un evento anónimo (se está activo)</li><li>Admin debería recibir un email coas solicitudes de rexistro (se activo).</li><li>A usuaria debería recibir un email coa solicitude de rexistro.</li><li>A usuaria debería recibir un email confirmando o rexistro.</li><li>A usuaria debería recibir un email de confirmación cando fose subscrita directamente por Admin</li><li>As usuarias deberían recibir un email para restablecer o contrasinal se o esquecen</li></ul>"
|
"smtp_description": "<ul><li>Admin debería recibir un email cando se engade un evento anónimo (se está activo)</li><li>Admin debería recibir un email coas solicitudes de rexistro (se activo).</li><li>A usuaria debería recibir un email coa solicitude de rexistro.</li><li>A usuaria debería recibir un email confirmando o rexistro.</li><li>A usuaria debería recibir un email de confirmación cando fose subscrita directamente por Admin</li><li>As usuarias deberían recibir un email para restablecer o contrasinal se o esquecen</li></ul>",
|
||||||
|
"wrong_domain_warning": "O url base configurado en config.json <b>({baseurl)</b> é diferente ao que estás a visitar <b>({url})</b>",
|
||||||
|
"event_remove_ok": "Evento eliminado"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"not_confirmed": "Aínda non foi confirmado…",
|
"not_confirmed": "Aínda non foi confirmado…",
|
||||||
|
@ -263,7 +269,9 @@
|
||||||
"setup": {
|
"setup": {
|
||||||
"completed": "Configuración completada",
|
"completed": "Configuración completada",
|
||||||
"completed_description": "<p>Xa podes acceder con estas credenciais:<br/><br/>Identificador: <b>{email}</b><br/>Contrasinal: <b>{password}</b></p>",
|
"completed_description": "<p>Xa podes acceder con estas credenciais:<br/><br/>Identificador: <b>{email}</b><br/>Contrasinal: <b>{password}</b></p>",
|
||||||
"start": "Comezar"
|
"start": "Comezar",
|
||||||
|
"https_warning": "Estás entrando con HTTP, lembra cambiar baseurl no config.json se cambias a HTTPS!",
|
||||||
|
"copy_password_dialog": "Si, tes que copiar o contrasinal!"
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"forgot_password": "Esqueceches o contrasinal?",
|
"forgot_password": "Esqueceches o contrasinal?",
|
||||||
|
|
|
@ -86,7 +86,8 @@
|
||||||
"reset": "Reset",
|
"reset": "Reset",
|
||||||
"import": "Importa",
|
"import": "Importa",
|
||||||
"max_events": "N. massimo eventi",
|
"max_events": "N. massimo eventi",
|
||||||
"label": "Etichetta"
|
"label": "Etichetta",
|
||||||
|
"blobs": "Bolle"
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"description": "Entrando puoi pubblicare nuovi eventi.",
|
"description": "Entrando puoi pubblicare nuovi eventi.",
|
||||||
|
@ -151,7 +152,7 @@
|
||||||
"each_month": "Ogni mese",
|
"each_month": "Ogni mese",
|
||||||
"due": "alle",
|
"due": "alle",
|
||||||
"from": "Dalle",
|
"from": "Dalle",
|
||||||
"image_too_big": "L'immagine non può essere più grande di 4 MB",
|
"image_too_big": "L'immagine non può essere più grande di 4MB",
|
||||||
"interact_with_me": "Seguimi dal fediverso",
|
"interact_with_me": "Seguimi dal fediverso",
|
||||||
"follow_me_description": "Tra i vari modi di rimanere aggiornati degli eventi pubblicati qui su {title},\npuoi seguire l'account <u>{account}</u> dal fediverso, ad esempio via Mastodon, ed eventualmente aggiungere risorse ad un evento da lì.<br/><br/>\nSe non hai mai sentito parlare di Mastodon e del fediverso ti consigliamo di leggere <a href='https://cagizero.wordpress.com/2018/10/25/cose-mastodon/'>questo articolo</a>.<br/><br/> Inserisci la tua istanza qui sotto (es. mastodon.cisti.org o mastodon.bida.im)",
|
"follow_me_description": "Tra i vari modi di rimanere aggiornati degli eventi pubblicati qui su {title},\npuoi seguire l'account <u>{account}</u> dal fediverso, ad esempio via Mastodon, ed eventualmente aggiungere risorse ad un evento da lì.<br/><br/>\nSe non hai mai sentito parlare di Mastodon e del fediverso ti consigliamo di leggere <a href='https://cagizero.wordpress.com/2018/10/25/cose-mastodon/'>questo articolo</a>.<br/><br/> Inserisci la tua istanza qui sotto (es. mastodon.cisti.org o mastodon.bida.im)",
|
||||||
"only_future": "solo eventi futuri",
|
"only_future": "solo eventi futuri",
|
||||||
|
@ -160,9 +161,10 @@
|
||||||
"import_URL": "Importa da URL (ics o h-event)",
|
"import_URL": "Importa da URL (ics o h-event)",
|
||||||
"ics": "ICS",
|
"ics": "ICS",
|
||||||
"import_description": "Puoi importare eventi da altre piattaforme e da altre istanze attraverso i formati standard (ics e h-event)",
|
"import_description": "Puoi importare eventi da altre piattaforme e da altre istanze attraverso i formati standard (ics e h-event)",
|
||||||
"alt_text_description": "Descrizione per utenti con disabilità visive",
|
"alt_text_description": "Descrizione per persone con disabilità visive",
|
||||||
"choose_focal_point": "Scegli il punto centrale cliccando",
|
"choose_focal_point": "Scegli il punto centrale cliccando",
|
||||||
"remove_media_confirmation": "Confermi l'eliminazione dell'immagine?"
|
"remove_media_confirmation": "Confermi l'eliminazione dell'immagine?",
|
||||||
|
"download_flyer": "Scarica volantino"
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"place_description": "Nel caso in cui un luogo sia errato o cambi indirizzo, puoi modificarlo.<br/>Considera che tutti gli eventi associati a questo luogo cambieranno indirizzo (anche quelli passati).",
|
"place_description": "Nel caso in cui un luogo sia errato o cambi indirizzo, puoi modificarlo.<br/>Considera che tutti gli eventi associati a questo luogo cambieranno indirizzo (anche quelli passati).",
|
||||||
|
@ -276,6 +278,7 @@
|
||||||
"completed": "Setup completato",
|
"completed": "Setup completato",
|
||||||
"completed_description": "<p>Puoi entrare con le seguenti credenziali:<br/><br/>Utente: <b>{email}</b><br/>Password: <b>{password}<b/></p>",
|
"completed_description": "<p>Puoi entrare con le seguenti credenziali:<br/><br/>Utente: <b>{email}</b><br/>Password: <b>{password}<b/></p>",
|
||||||
"copy_password_dialog": "Sì, devi copiare la password!",
|
"copy_password_dialog": "Sì, devi copiare la password!",
|
||||||
"start": "Inizia"
|
"start": "Inizia",
|
||||||
|
"https_warning": "Stai visitando il setup da HTTP, ricorda di cambiare il baseurl nel config.json quando passerai ad HTTPS!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ module.exports = {
|
||||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
|
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
|
||||||
],
|
],
|
||||||
link: [{ rel: 'icon', type: 'image/png', href: '/logo.png' }],
|
link: [{ rel: 'icon', type: 'image/png', href: '/logo.png' }],
|
||||||
link: [{ rel: 'preload', type: 'image/png', href: '/logo.png', as: 'image' }],
|
|
||||||
script: [{ src: '/gancio-events.es.js', async: true, body: true }],
|
script: [{ src: '/gancio-events.es.js', async: true, body: true }],
|
||||||
},
|
},
|
||||||
dev: isDev,
|
dev: isDev,
|
||||||
|
@ -27,13 +26,12 @@ module.exports = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
css: ['./assets/style.less'],
|
css: ['./assets/style.css'],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Customize the progress-bar component
|
** Customize the progress-bar component
|
||||||
*/
|
*/
|
||||||
loading: '~/components/Loading.vue',
|
loading: '~/components/Loading.vue',
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Plugins to load before mounting the App
|
** Plugins to load before mounting the App
|
||||||
*/
|
*/
|
||||||
|
@ -53,9 +51,27 @@ module.exports = {
|
||||||
// Doc: https://axios.nuxtjs.org/usage
|
// Doc: https://axios.nuxtjs.org/usage
|
||||||
'@nuxtjs/axios',
|
'@nuxtjs/axios',
|
||||||
'@nuxtjs/auth',
|
'@nuxtjs/auth',
|
||||||
'@/server/initialize.server.js'
|
'@nuxtjs/sitemap'
|
||||||
],
|
],
|
||||||
|
|
||||||
|
sitemap: {
|
||||||
|
hostname: config.baseurl,
|
||||||
|
gzip: true,
|
||||||
|
exclude: [
|
||||||
|
'/Admin',
|
||||||
|
'/settings',
|
||||||
|
'/export',
|
||||||
|
'/setup'
|
||||||
|
],
|
||||||
|
routes: async () => {
|
||||||
|
if (config.status === 'READY') {
|
||||||
|
const Event = require('./server/api/models/event')
|
||||||
|
const events = await Event.findAll({where: { is_visible: true }})
|
||||||
|
return events.map(e => `/event/${e.slug}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
serverMiddleware: ['server/routes'],
|
serverMiddleware: ['server/routes'],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -93,7 +109,6 @@ module.exports = {
|
||||||
},
|
},
|
||||||
buildModules: ['@nuxtjs/vuetify'],
|
buildModules: ['@nuxtjs/vuetify'],
|
||||||
vuetify: {
|
vuetify: {
|
||||||
customVariables: ['~/assets/variables.scss'],
|
|
||||||
treeShake: true,
|
treeShake: true,
|
||||||
theme: {
|
theme: {
|
||||||
options: {
|
options: {
|
||||||
|
|
60
package.json
60
package.json
|
@ -1,18 +1,19 @@
|
||||||
{
|
{
|
||||||
"name": "gancio",
|
"name": "gancio",
|
||||||
"version": "1.4.3",
|
"version": "1.5.0-rc.2",
|
||||||
"description": "A shared agenda for local communities",
|
"description": "A shared agenda for local communities",
|
||||||
"author": "lesion",
|
"author": "lesion",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nuxt build --modern",
|
"build": "nuxt build --modern",
|
||||||
"start:inspect": "NODE_ENV=production node --inspect node_modules/.bin/nuxt start --modern",
|
"start:inspect": "NODE_ENV=production node --inspect node_modules/.bin/nuxt start --modern",
|
||||||
"dev": "nuxt dev",
|
"dev": "nuxt dev",
|
||||||
"test": "cd tests/seeds; jest; cd ../..",
|
"test": "cd tests/seeds; jest ; cd ../..",
|
||||||
"start": "nuxt start --modern",
|
"start": "nuxt start --modern",
|
||||||
"doc": "cd docs && bundle exec jekyll b",
|
"doc": "cd docs && bundle exec jekyll b",
|
||||||
"doc:dev": "cd docs && bundle exec jekyll s --drafts",
|
"doc:dev": "cd docs && bundle exec jekyll s --drafts",
|
||||||
"migrate": "NODE_ENV=production sequelize db:migrate",
|
"migrate": "NODE_ENV=production sequelize db:migrate",
|
||||||
"migrate:dev": "sequelize db:migrate"
|
"migrate:dev": "sequelize db:migrate",
|
||||||
|
"build:wc": "cd webcomponents; yarn build:lib; cp dist/gancio-events.es.js ../wp-plugin/js/; cp dist/gancio-events.es.js ../assets/; cp dist/gancio-events.es.js ../docs/assets/js/"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"server/",
|
"server/",
|
||||||
|
@ -27,19 +28,20 @@
|
||||||
"yarn.lock"
|
"yarn.lock"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdi/js": "^6.5.95",
|
"@mdi/js": "^6.7.96",
|
||||||
"@nuxtjs/auth": "^4.9.1",
|
"@nuxtjs/auth": "^4.9.1",
|
||||||
"@nuxtjs/axios": "^5.13.5",
|
"@nuxtjs/axios": "^5.13.5",
|
||||||
|
"@nuxtjs/sitemap": "^2.4.0",
|
||||||
"accept-language": "^3.0.18",
|
"accept-language": "^3.0.18",
|
||||||
"axios": "^0.26.0",
|
"axios": "^0.27.2",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"body-parser": "^1.19.2",
|
"body-parser": "^1.20.0",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dayjs": "^1.10.7",
|
"dayjs": "^1.11.3",
|
||||||
"dompurify": "^2.3.6",
|
"dompurify": "^2.3.8",
|
||||||
"email-templates": "^8.0.9",
|
"email-templates": "^8.0.9",
|
||||||
"express": "^4.17.3",
|
"express": "^4.18.1",
|
||||||
"express-oauth-server": "lesion/express-oauth-server#master",
|
"express-oauth-server": "lesion/express-oauth-server#master",
|
||||||
"http-signature": "^1.3.6",
|
"http-signature": "^1.3.6",
|
||||||
"ical.js": "^1.5.0",
|
"ical.js": "^1.5.0",
|
||||||
|
@ -49,55 +51,41 @@
|
||||||
"linkify-html": "^3.0.4",
|
"linkify-html": "^3.0.4",
|
||||||
"linkifyjs": "3.0.5",
|
"linkifyjs": "3.0.5",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mariadb": "^2.5.6",
|
"mariadb": "^3.0.0",
|
||||||
"microformat-node": "^2.0.1",
|
"microformat-node": "^2.0.1",
|
||||||
"minify-css-string": "^1.0.0",
|
"minify-css-string": "^1.0.0",
|
||||||
"mkdirp": "^1.0.4",
|
"mkdirp": "^1.0.4",
|
||||||
"multer": "^1.4.3",
|
"multer": "^1.4.5-lts.1",
|
||||||
"nuxt-edge": "^2.16.0-27305297.ab1c6cb4",
|
"nuxt-edge": "^2.16.0-27358576.777a4b7f",
|
||||||
"pg": "^8.6.0",
|
"pg": "^8.6.0",
|
||||||
"sequelize": "^6.17.0",
|
"sequelize": "^6.20.1",
|
||||||
"sequelize-slugify": "^1.6.0",
|
"sequelize-slugify": "^1.6.1",
|
||||||
"sharp": "^0.27.2",
|
"sharp": "^0.27.2",
|
||||||
"sqlite3": "mapbox/node-sqlite3#918052b",
|
"sqlite3": "^5.0.8",
|
||||||
"tiptap": "^1.32.0",
|
"tiptap": "^1.32.0",
|
||||||
"tiptap-extensions": "^1.35.0",
|
"tiptap-extensions": "^1.35.0",
|
||||||
"umzug": "^2.3.0",
|
"umzug": "^2.3.0",
|
||||||
"v-calendar": "2.4.1",
|
"v-calendar": "^2.4.1",
|
||||||
"vue": "^2.6.14",
|
"vue": "^2.6.14",
|
||||||
"vue-i18n": "^8.26.7",
|
"vue-i18n": "^8.26.7",
|
||||||
"vue-template-compiler": "^2.6.14",
|
"vue-template-compiler": "^2.6.14",
|
||||||
"vuetify": "npm:@vuetify/nightly@dev",
|
"vuetify": "npm:@vuetify/nightly@dev",
|
||||||
"winston": "^3.6.0",
|
"winston": "^3.7.2",
|
||||||
"winston-daily-rotate-file": "^4.6.1",
|
"winston-daily-rotate-file": "^4.7.1",
|
||||||
"yargs": "^17.2.0"
|
"yargs": "^17.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nuxtjs/vuetify": "^1.12.3",
|
"@nuxtjs/vuetify": "^1.12.3",
|
||||||
"jest": "^27.5.1",
|
"jest": "^28.1.0",
|
||||||
"less": "^4.1.1",
|
"prettier": "^2.6.2",
|
||||||
"less-loader": "^7",
|
|
||||||
"prettier": "^2.3.0",
|
|
||||||
"pug": "^3.0.2",
|
"pug": "^3.0.2",
|
||||||
"pug-plain-loader": "^1.1.0",
|
"pug-plain-loader": "^1.1.0",
|
||||||
"sass": "^1.49.4",
|
"sass": "^1.52.2",
|
||||||
"sequelize-cli": "^6.3.0",
|
"sequelize-cli": "^6.3.0",
|
||||||
"supertest": "^6.2.2",
|
"supertest": "^6.2.2",
|
||||||
"webpack": "4",
|
"webpack": "4",
|
||||||
"webpack-cli": "^4.7.2"
|
"webpack-cli": "^4.7.2"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
|
||||||
"source-map-resolve": "0.6.0",
|
|
||||||
"lodash": "4.17.21",
|
|
||||||
"minimist": "1.2.5",
|
|
||||||
"jimp": "0.16.1",
|
|
||||||
"resize-img": "2.0.0",
|
|
||||||
"underscore": "1.13.1",
|
|
||||||
"postcss": "7.0.36",
|
|
||||||
"glob-parent": "5.1.2",
|
|
||||||
"chokidar": "3.5.2",
|
|
||||||
"core-js": "3.19.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"gancio": "server/cli.js"
|
"gancio": "server/cli.js"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
v-container.container.pa-0.pa-md-3
|
v-container.container.pa-0.pa-md-3
|
||||||
v-card
|
v-card
|
||||||
v-tabs(v-model='selectedTab' show-arrows)
|
v-alert(v-if='url!==settings.baseurl' outlined type='warning' color='red' show-icon :icon='mdiAlert')
|
||||||
|
span(v-html="$t('admin.wrong_domain_warning', { url, baseurl: settings.baseurl })")
|
||||||
|
v-tabs(v-model='selectedTab' show-arrows :next-icon='mdiChevronRight' :prev-icon='mdiChevronLeft')
|
||||||
|
|
||||||
//- SETTINGS
|
//- SETTINGS
|
||||||
v-tab {{$t('common.settings')}}
|
v-tab {{$t('common.settings')}}
|
||||||
|
@ -24,6 +26,11 @@
|
||||||
v-tab-item
|
v-tab-item
|
||||||
Places
|
Places
|
||||||
|
|
||||||
|
//- Cohorts
|
||||||
|
v-tab {{$t('common.blobs')}}
|
||||||
|
v-tab-item
|
||||||
|
Cohorts
|
||||||
|
|
||||||
//- EVENTS
|
//- EVENTS
|
||||||
v-tab
|
v-tab
|
||||||
v-badge(:value='!!unconfirmedEvents.length' :content='unconfirmedEvents.length') {{$t('common.events')}}
|
v-badge(:value='!!unconfirmedEvents.length' :content='unconfirmedEvents.length') {{$t('common.events')}}
|
||||||
|
@ -49,32 +56,42 @@
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
|
import { mdiAlert, mdiChevronRight, mdiChevronLeft } from '@mdi/js'
|
||||||
|
import Settings from '@/components/admin/Settings'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Admin',
|
name: 'Admin',
|
||||||
components: {
|
components: {
|
||||||
|
Settings,
|
||||||
Users: () => import(/* webpackChunkName: "admin" */'../components/admin/Users'),
|
Users: () => import(/* webpackChunkName: "admin" */'../components/admin/Users'),
|
||||||
Events: () => import(/* webpackChunkName: "admin" */'../components/admin/Events'),
|
Events: () => import(/* webpackChunkName: "admin" */'../components/admin/Events'),
|
||||||
Places: () => import(/* webpackChunkName: "admin" */'../components/admin/Places'),
|
Places: () => import(/* webpackChunkName: "admin" */'../components/admin/Places'),
|
||||||
Settings: () => import(/* webpackChunkName: "admin" */'../components/admin/Settings'),
|
Cohorts: () => import(/* webpackChunkName: "admin" */'../components/admin/Cohorts'),
|
||||||
Federation: () => import(/* webpackChunkName: "admin" */'../components/admin/Federation.vue'),
|
Federation: () => import(/* webpackChunkName: "admin" */'../components/admin/Federation.vue'),
|
||||||
Moderation: () => import(/* webpackChunkName: "admin" */'../components/admin/Moderation.vue'),
|
Moderation: () => import(/* webpackChunkName: "admin" */'../components/admin/Moderation.vue'),
|
||||||
Announcement: () => import(/* webpackChunkName: "admin" */'../components/admin/Announcement.vue'),
|
Announcement: () => import(/* webpackChunkName: "admin" */'../components/admin/Announcement.vue'),
|
||||||
Theme: () => import(/* webpackChunkName: "admin" */'../components/admin/Theme.vue')
|
Theme: () => import(/* webpackChunkName: "admin" */'../components/admin/Theme.vue')
|
||||||
},
|
},
|
||||||
middleware: ['auth'],
|
middleware: ['auth'],
|
||||||
async asyncData ({ $axios, params, store }) {
|
async asyncData ({ $axios, req }) {
|
||||||
|
let url
|
||||||
|
if (process.client) {
|
||||||
|
url = window.location.protocol + '//' + window.location.host
|
||||||
|
} else {
|
||||||
|
url = req.protocol + '://' + req.headers.host
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const users = await $axios.$get('/users')
|
const users = await $axios.$get('/users')
|
||||||
const unconfirmedEvents = await $axios.$get('/event/unconfirmed')
|
const unconfirmedEvents = await $axios.$get('/event/unconfirmed')
|
||||||
return { users, unconfirmedEvents, selectedTab: 0 }
|
return { users, unconfirmedEvents, selectedTab: 0, url }
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
return { users: [], unconfirmedEvents: [], selectedTab: 0, url }
|
||||||
return { users: [], unconfirmedEvents: [], selectedTab: 0 }
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
mdiAlert, mdiChevronRight, mdiChevronLeft,
|
||||||
|
users: [],
|
||||||
description: '',
|
description: '',
|
||||||
unconfirmedEvents: [],
|
unconfirmedEvents: [],
|
||||||
selectedTab: 0
|
selectedTab: 0
|
||||||
|
@ -100,7 +117,7 @@ export default {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
await this.$axios.$get(`/event/confirm/${id}`)
|
await this.$axios.$get(`/event/confirm/${id}`)
|
||||||
this.loading = false
|
this.loading = false
|
||||||
this.$root.$message('event.confirmed', { color: 'succes' })
|
this.$root.$message('event.confirmed', { color: 'success' })
|
||||||
this.unconfirmedEvents = this.unconfirmedEvents.filter(e => e.id !== id)
|
this.unconfirmedEvents = this.unconfirmedEvents.filter(e => e.id !== id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
<template lang="pug">
|
|
||||||
v-row
|
|
||||||
v-col.col-6
|
|
||||||
v-menu(v-model='startTimeMenu'
|
|
||||||
:close-on-content-click="false"
|
|
||||||
transition="slide-x-transition"
|
|
||||||
ref='startTimeMenu'
|
|
||||||
:return-value.sync="value.start"
|
|
||||||
offset-y
|
|
||||||
absolute
|
|
||||||
top
|
|
||||||
max-width="290px"
|
|
||||||
min-width="290px")
|
|
||||||
template(v-slot:activator='{ on }')
|
|
||||||
v-text-field(
|
|
||||||
:label="$t('event.from')"
|
|
||||||
prepend-icon='mdi-clock'
|
|
||||||
:rules="[$validators.required('event.from')]"
|
|
||||||
:value='value.start'
|
|
||||||
v-on='on'
|
|
||||||
clearable)
|
|
||||||
v-time-picker(
|
|
||||||
v-if='startTimeMenu'
|
|
||||||
:label="$t('event.from')"
|
|
||||||
format="24hr"
|
|
||||||
ref='time_start'
|
|
||||||
:allowed-minutes="[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55]"
|
|
||||||
v-model='value.start'
|
|
||||||
@click:minute="selectTime('start')")
|
|
||||||
|
|
||||||
v-col.col-6
|
|
||||||
v-menu(v-model='endTimeMenu'
|
|
||||||
:close-on-content-click="false"
|
|
||||||
transition="slide-x-transition"
|
|
||||||
ref='endTimeMenu'
|
|
||||||
:return-value.sync="time.end"
|
|
||||||
offset-y
|
|
||||||
absolute
|
|
||||||
top
|
|
||||||
max-width="290px"
|
|
||||||
min-width="290px")
|
|
||||||
template(v-slot:activator='{ on }')
|
|
||||||
v-text-field(
|
|
||||||
prepend-icon='mdi-clock'
|
|
||||||
:label="$t('event.due')"
|
|
||||||
:value='value.end'
|
|
||||||
v-on='on'
|
|
||||||
clearable
|
|
||||||
readonly)
|
|
||||||
v-time-picker(
|
|
||||||
v-if='endTimeMenu'
|
|
||||||
:label="$t('event.due')"
|
|
||||||
format="24hr"
|
|
||||||
:allowed-minutes="[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55]"
|
|
||||||
v-model='value.end'
|
|
||||||
@click:minute="selectTime('end')")
|
|
||||||
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'HourInput',
|
|
||||||
props: {
|
|
||||||
value: { type: Object, default: () => { } }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
// time: { start: this.value.start, end: this.value.end },
|
|
||||||
time: {},
|
|
||||||
startTimeMenu: false,
|
|
||||||
endTimeMenu: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
selectTime (type) {
|
|
||||||
this.$refs[`${type}TimeMenu`].save(this.value[type])
|
|
||||||
this.$emit('input', this.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,102 +0,0 @@
|
||||||
<template lang="pug">
|
|
||||||
v-row
|
|
||||||
v-col(cols=12 md=6)
|
|
||||||
v-combobox(ref='place'
|
|
||||||
:rules="[$validators.required('common.where')]"
|
|
||||||
:label="$t('common.where')"
|
|
||||||
:hint="$t('event.where_description')"
|
|
||||||
:search-input.sync="placeName"
|
|
||||||
:prepend-icon='mdiMapMarker'
|
|
||||||
persistent-hint
|
|
||||||
:value="value.name"
|
|
||||||
:items="filteredPlaces"
|
|
||||||
no-filter
|
|
||||||
item-text='name'
|
|
||||||
@change='selectPlace')
|
|
||||||
template(v-slot:item="{ item, attrs, on }")
|
|
||||||
v-list-item(v-bind='attrs' v-on='on')
|
|
||||||
v-list-item-content(two-line v-if='item.create')
|
|
||||||
v-list-item-title <v-icon color='primary' v-text='mdiPlus' :aria-label='add'></v-icon> {{item.name}}
|
|
||||||
v-list-item-content(two-line v-else)
|
|
||||||
v-list-item-title(v-text='item.name')
|
|
||||||
v-list-item-subtitle(v-text='item.address')
|
|
||||||
|
|
||||||
v-col(cols=12 md=6)
|
|
||||||
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")
|
|
||||||
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import { mapState } from 'vuex'
|
|
||||||
import { mdiMap, mdiMapMarker, mdiPlus } from '@mdi/js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'WhereInput',
|
|
||||||
props: {
|
|
||||||
value: { type: Object, default: () => {} }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
mdiMap, mdiMapMarker, mdiPlus,
|
|
||||||
place: { },
|
|
||||||
placeName: '',
|
|
||||||
disableAddress: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState(['places']),
|
|
||||||
filteredPlaces () {
|
|
||||||
if (!this.placeName) { return this.places }
|
|
||||||
const placeName = this.placeName.toLowerCase()
|
|
||||||
let nameMatch = false
|
|
||||||
const matches = this.places.filter(p => {
|
|
||||||
const tmpName = p.name.toLowerCase()
|
|
||||||
const tmpAddress = p.address.toLowerCase()
|
|
||||||
if (tmpName.includes(placeName)) {
|
|
||||||
if (tmpName === placeName) { nameMatch = true }
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if (tmpAddress.includes(placeName)) { return true }
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
if (!nameMatch) {
|
|
||||||
matches.unshift({ create: true, name: this.placeName })
|
|
||||||
}
|
|
||||||
return matches
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
selectPlace (p) {
|
|
||||||
if (!p) { return }
|
|
||||||
if (typeof p === 'object' && !p.create) {
|
|
||||||
this.place.name = p.name
|
|
||||||
this.place.address = p.address
|
|
||||||
this.disableAddress = true
|
|
||||||
} else { // this is a new place
|
|
||||||
this.place.name = p.name || p
|
|
||||||
// search for a place with the same name
|
|
||||||
const place = this.places.find(p => p.name === this.place.name)
|
|
||||||
if (place) {
|
|
||||||
this.place.address = place.address
|
|
||||||
this.disableAddress = true
|
|
||||||
} else {
|
|
||||||
this.place.address = ''
|
|
||||||
this.disableAddress = false
|
|
||||||
this.$refs.place.blur()
|
|
||||||
this.$refs.address.focus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.$emit('input', { ...this.place })
|
|
||||||
},
|
|
||||||
changeAddress (v) {
|
|
||||||
this.place.address = v
|
|
||||||
this.$emit('input', { ...this.place })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -51,8 +51,11 @@
|
||||||
v-combobox(v-model='event.tags'
|
v-combobox(v-model='event.tags'
|
||||||
:prepend-icon="mdiTagMultiple"
|
:prepend-icon="mdiTagMultiple"
|
||||||
chips small-chips multiple deletable-chips hide-no-data hide-selected persistent-hint
|
chips small-chips multiple deletable-chips hide-no-data hide-selected persistent-hint
|
||||||
|
cache-items
|
||||||
|
@input.native='searchTags'
|
||||||
:delimiters="[',', ';']"
|
:delimiters="[',', ';']"
|
||||||
:items="tags.map(t => t.tag)"
|
:items="tags"
|
||||||
|
:menu-props="{ maxWidth: 400, eager: true }"
|
||||||
:label="$t('common.tags')")
|
:label="$t('common.tags')")
|
||||||
template(v-slot:selection="{ item, on, attrs, selected, parent}")
|
template(v-slot:selection="{ item, on, attrs, selected, parent}")
|
||||||
v-chip(v-bind="attrs" close :close-icon='mdiCloseCircle' @click:close='parent.selectItem(item)'
|
v-chip(v-bind="attrs" close :close-icon='mdiCloseCircle' @click:close='parent.selectItem(item)'
|
||||||
|
@ -66,25 +69,33 @@
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { mapActions, mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
|
import debounce from 'lodash/debounce'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
import { mdiFileImport, mdiFormatTitle, mdiTagMultiple, mdiCloseCircle } from '@mdi/js'
|
import { mdiFileImport, mdiFormatTitle, mdiTagMultiple, mdiCloseCircle } from '@mdi/js'
|
||||||
|
|
||||||
|
import List from '@/components/List'
|
||||||
|
import Editor from '@/components/Editor'
|
||||||
|
import ImportDialog from '@/components/ImportDialog'
|
||||||
|
import MediaInput from '@/components/MediaInput'
|
||||||
|
import WhereInput from '@/components/WhereInput'
|
||||||
|
import DateInput from '@/components/DateInput'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'NewEvent',
|
name: 'NewEvent',
|
||||||
components: {
|
components: {
|
||||||
List: () => import(/* webpackChunkName: "add" */'@/components/List'),
|
List,
|
||||||
Editor: () => import(/* webpackChunkName: "add" */'@/components/Editor'),
|
Editor,
|
||||||
ImportDialog: () => import(/* webpackChunkName: "add" */'./ImportDialog.vue'),
|
ImportDialog,
|
||||||
MediaInput: () => import(/* webpackChunkName: "add" */'./MediaInput.vue'),
|
MediaInput,
|
||||||
WhereInput: () => import(/* webpackChunkName: "add" */'./WhereInput.vue'),
|
WhereInput,
|
||||||
DateInput: () => import(/* webpackChunkName: "add" */'./DateInput.vue')
|
DateInput
|
||||||
},
|
},
|
||||||
validate ({ store }) {
|
validate ({ store }) {
|
||||||
return (store.state.auth.loggedIn || store.state.settings.allow_anon_event)
|
return (store.state.auth.loggedIn || store.state.settings.allow_anon_event)
|
||||||
},
|
},
|
||||||
async asyncData ({ params, $axios, error, store }) {
|
async asyncData ({ params, $axios, error }) {
|
||||||
if (params.edit) {
|
if (params.edit) {
|
||||||
const data = { event: { place: {}, media: [] } }
|
const data = { event: { place: {}, media: [] } }
|
||||||
data.id = params.edit
|
data.id = params.edit
|
||||||
|
@ -101,8 +112,8 @@ export default {
|
||||||
data.event.place.address = event.place.address || ''
|
data.event.place.address = event.place.address || ''
|
||||||
data.date = {
|
data.date = {
|
||||||
recurrent: event.recurrent,
|
recurrent: event.recurrent,
|
||||||
from: new Date(dayjs.unix(event.start_datetime)),
|
from: dayjs.unix(event.start_datetime).toDate(),
|
||||||
due: new Date(dayjs.unix(event.end_datetime)),
|
due: dayjs.unix(event.end_datetime).toDate(),
|
||||||
multidate: event.multidate,
|
multidate: event.multidate,
|
||||||
fromHour: true,
|
fromHour: true,
|
||||||
dueHour: true
|
dueHour: true
|
||||||
|
@ -118,8 +129,8 @@ export default {
|
||||||
return {}
|
return {}
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
const month = dayjs().month() + 1
|
const month = dayjs.tz().month() + 1
|
||||||
const year = dayjs().year()
|
const year = dayjs.tz().year()
|
||||||
return {
|
return {
|
||||||
mdiFileImport, mdiFormatTitle, mdiTagMultiple, mdiCloseCircle,
|
mdiFileImport, mdiFormatTitle, mdiTagMultiple, mdiCloseCircle,
|
||||||
valid: false,
|
valid: false,
|
||||||
|
@ -131,6 +142,7 @@ export default {
|
||||||
tags: [],
|
tags: [],
|
||||||
media: []
|
media: []
|
||||||
},
|
},
|
||||||
|
tags: [],
|
||||||
page: { month, year },
|
page: { month, year },
|
||||||
fileList: [],
|
fileList: [],
|
||||||
id: null,
|
id: null,
|
||||||
|
@ -145,9 +157,20 @@ export default {
|
||||||
title: `${this.settings.title} - ${this.$t('common.add_event')}`
|
title: `${this.settings.title} - ${this.$t('common.add_event')}`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: mapState(['tags', 'places', 'settings']),
|
computed: {
|
||||||
|
...mapState(['settings']),
|
||||||
|
filteredTags () {
|
||||||
|
if (!this.tagName) { return this.tags.slice(0, 10).map(t => t.tag) }
|
||||||
|
const tagName = this.tagName.trim().toLowerCase()
|
||||||
|
return this.tags.filter(t => t.tag.toLowerCase().includes(tagName)).map(t => t.tag)
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['updateMeta']),
|
searchTags: debounce( async function(ev) {
|
||||||
|
const search = ev.target.value
|
||||||
|
if (!search) return
|
||||||
|
this.tags = await this.$axios.$get(`/tag?search=${search}`)
|
||||||
|
}, 100),
|
||||||
eventImported (event) {
|
eventImported (event) {
|
||||||
this.event = Object.assign(this.event, event)
|
this.event = Object.assign(this.event, event)
|
||||||
this.$refs.where.selectPlace({ name: event.place.name, create: true })
|
this.$refs.where.selectPlace({ name: event.place.name, create: true })
|
||||||
|
@ -165,7 +188,9 @@ export default {
|
||||||
if (!this.$refs.form.validate()) {
|
if (!this.$refs.form.validate()) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
const el = document.querySelector('.v-input.error--text:first-of-type')
|
const el = document.querySelector('.v-input.error--text:first-of-type')
|
||||||
el.scrollIntoView()
|
if (el) {
|
||||||
|
el.scrollIntoView(false)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -177,12 +202,15 @@ export default {
|
||||||
|
|
||||||
if (this.event.media.length) {
|
if (this.event.media.length) {
|
||||||
formData.append('image', this.event.media[0].image)
|
formData.append('image', this.event.media[0].image)
|
||||||
formData.append('image_url', this.event.media[0].url)
|
// formData.append('image_url', this.event.media[0].url)
|
||||||
formData.append('image_name', this.event.media[0].name)
|
formData.append('image_name', this.event.media[0].name)
|
||||||
formData.append('image_focalpoint', this.event.media[0].focalpoint)
|
formData.append('image_focalpoint', this.event.media[0].focalpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
formData.append('title', this.event.title)
|
formData.append('title', this.event.title)
|
||||||
|
if (this.event.place.id) {
|
||||||
|
formData.append('place_id', this.event.place.id)
|
||||||
|
}
|
||||||
formData.append('place_name', this.event.place.name)
|
formData.append('place_name', this.event.place.name)
|
||||||
formData.append('place_address', this.event.place.address)
|
formData.append('place_address', this.event.place.address)
|
||||||
formData.append('description', this.event.description)
|
formData.append('description', this.event.description)
|
||||||
|
@ -200,7 +228,6 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
await this.$axios.$post('/event', formData)
|
await this.$axios.$post('/event', formData)
|
||||||
}
|
}
|
||||||
this.updateMeta()
|
|
||||||
this.$router.push('/')
|
this.$router.push('/')
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$root.$message(this.$auth.loggedIn ? (this.edit ? 'event.saved' : 'event.added') : 'event.added_anon', { color: 'success' })
|
this.$root.$message(this.$auth.loggedIn ? (this.edit ? 'event.saved' : 'event.added') : 'event.added_anon', { color: 'success' })
|
||||||
|
|
|
@ -33,7 +33,7 @@ export default {
|
||||||
|
|
||||||
// <iframe src='http://localhost:13120/embed/1' class='embedded_gancio'></iframe>
|
// <iframe src='http://localhost:13120/embed/1' class='embedded_gancio'></iframe>
|
||||||
</script>
|
</script>
|
||||||
<style lang='less'>
|
<style lang='scss'>
|
||||||
.embed_event {
|
.embed_event {
|
||||||
display: flex;
|
display: flex;
|
||||||
transition: margin .1s;
|
transition: margin .1s;
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
v-container#event.pa-0.pa-sm-2
|
v-container#event.pa-0.pa-sm-2
|
||||||
//- EVENT PAGE
|
//- EVENT PAGE
|
||||||
//- gancio supports microformats (http://microformats.org/wiki/h-event)
|
//- 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-card-actions
|
v-card-actions
|
||||||
//- admin controls
|
//- admin controls
|
||||||
|
@ -9,41 +10,33 @@ v-container#event.pa-0.pa-sm-2
|
||||||
v-card-text
|
v-card-text
|
||||||
|
|
||||||
v-row
|
v-row
|
||||||
v-col.col-12.col-lg-8
|
v-col.col-12.col-md-8
|
||||||
//- fake image to use u-featured in h-event microformat
|
MyPicture(v-if='hasMedia' :event='event')
|
||||||
img.u-featured(v-show='false' v-if='hasMedia' :src='event | mediaURL' itemprop="image")
|
|
||||||
v-img.main_image.mb-3(
|
|
||||||
contain
|
|
||||||
:alt='event | mediaURL("alt")'
|
|
||||||
:src='event | mediaURL'
|
|
||||||
:lazy-src='event | mediaURL("thumb")'
|
|
||||||
v-if='hasMedia')
|
|
||||||
.p-description.text-body-1.pa-3.rounded(v-if='!hasMedia && event.description' itemprop='description' v-html='event.description')
|
.p-description.text-body-1.pa-3.rounded(v-if='!hasMedia && event.description' itemprop='description' v-html='event.description')
|
||||||
|
|
||||||
v-col.col-12.col-lg-4
|
v-col.col-12.col-md-4
|
||||||
v-card(outlined)
|
v-card(outlined)
|
||||||
v-card-text
|
v-card-text
|
||||||
v-icon.float-right(v-if='event.parentId' color='success' v-text='mdiRepeat')
|
v-icon.float-right(v-if='event.parentId' color='success' v-text='mdiRepeat')
|
||||||
.title.text-h5
|
.title.text-h5.mb-5
|
||||||
b.p-name(itemprop="name") {{event.title}}
|
strong.p-name.text--primary(itemprop="name") {{event.title}}
|
||||||
|
|
||||||
time.dt-start.text-h6(:datetime='event.start_datetime|unixFormat("YYYY-MM-DD HH:mm")' itemprop="startDate" :content="event.start_datetime|unixFormat('YYYY-MM-DDTHH:mm')")
|
time.dt-start.text-h6(:datetime='event.start_datetime|unixFormat("YYYY-MM-DD HH:mm")' itemprop="startDate" :content="event.start_datetime|unixFormat('YYYY-MM-DDTHH:mm')")
|
||||||
v-icon(v-text='mdiCalendar')
|
v-icon(v-text='mdiCalendar')
|
||||||
b.ml-2 {{event|when}}
|
strong.ml-2 {{event|when}}
|
||||||
.d-none.dt-end(itemprop="endDate" :content="event.end_datetime|unixFormat('YYYY-MM-DDTHH:mm')") {{event.end_datetime|unixFormat('YYYY-MM-DD HH:mm')}}
|
.d-none.dt-end(itemprop="endDate" :content="event.end_datetime|unixFormat('YYYY-MM-DDTHH:mm')") {{event.end_datetime|unixFormat('YYYY-MM-DD HH:mm')}}
|
||||||
div.text-subtitle-1 {{event.start_datetime|from}}
|
div.text-subtitle-1.mb-5 {{event.start_datetime|from}}
|
||||||
small(v-if='event.parentId') ({{event|recurrentDetail}})
|
small(v-if='event.parentId') ({{event|recurrentDetail}})
|
||||||
|
|
||||||
.text-h6.p-location(itemprop="location" itemscope itemtype="https://schema.org/Place")
|
.text-h6.p-location.h-adr(itemprop="location" itemscope itemtype="https://schema.org/Place")
|
||||||
v-icon(v-text='mdiMapMarker')
|
v-icon(v-text='mdiMapMarker')
|
||||||
b.vcard.ml-2(itemprop="name") {{event.place && event.place.name}}
|
b.vcard.ml-2.p-name(itemprop="name") {{event.place && event.place.name}}
|
||||||
.text-subtitle-1.adr(itemprop='address') {{event.place && event.place.address}}
|
.text-subtitle-1.p-street-address(itemprop='address') {{event.place && event.place.address}}
|
||||||
|
|
||||||
//- tags, hashtags
|
//- tags, hashtags
|
||||||
v-card-text(v-if='event.tags.length')
|
v-card-text.pt-0(v-if='event.tags && event.tags.length')
|
||||||
v-chip.p-category.ml-1.mt-3(v-for='tag in event.tags' color='primary'
|
v-chip.p-category.ml-1.mt-3(v-for='tag in event.tags' color='primary'
|
||||||
outlined :key='tag')
|
outlined :key='tag' :to='`/tag/${tag}`') {{tag}}
|
||||||
span(v-text='tag')
|
|
||||||
|
|
||||||
//- info & actions
|
//- info & actions
|
||||||
v-toolbar
|
v-toolbar
|
||||||
|
@ -55,6 +48,9 @@ v-container#event.pa-0.pa-sm-2
|
||||||
v-btn.ml-2(large icon :title="$t('common.add_to_calendar')" color='primary' :aria-label="$t('common.add_to_calendar')"
|
v-btn.ml-2(large icon :title="$t('common.add_to_calendar')" color='primary' :aria-label="$t('common.add_to_calendar')"
|
||||||
:href='`/api/event/${event.slug || event.id}.ics`')
|
:href='`/api/event/${event.slug || event.id}.ics`')
|
||||||
v-icon(v-text='mdiCalendarExport')
|
v-icon(v-text='mdiCalendarExport')
|
||||||
|
v-btn.ml-2(v-if='hasMedia' large icon :title="$t('event.download_flyer')" color='primary' :aria-label="$t('event.download_flyer')"
|
||||||
|
:href='event | mediaURL')
|
||||||
|
v-icon(v-text='mdiFileDownloadOutline')
|
||||||
|
|
||||||
.p-description.text-body-1.pa-3.rounded(v-if='hasMedia && event.description' itemprop='description' v-html='event.description')
|
.p-description.text-body-1.pa-3.rounded(v-if='hasMedia && event.description' itemprop='description' v-html='event.description')
|
||||||
|
|
||||||
|
@ -133,20 +129,25 @@ import { mapState } from 'vuex'
|
||||||
import get from 'lodash/get'
|
import get from 'lodash/get'
|
||||||
import moment from 'dayjs'
|
import moment from 'dayjs'
|
||||||
import clipboard from '../../assets/clipboard'
|
import clipboard from '../../assets/clipboard'
|
||||||
const htmlToText = require('html-to-text')
|
import MyPicture from '~/components/MyPicture'
|
||||||
|
import EventAdmin from '@/components/eventAdmin'
|
||||||
|
import EmbedEvent from '@/components/embedEvent'
|
||||||
|
|
||||||
|
const { htmlToText } = require('html-to-text')
|
||||||
|
|
||||||
import { mdiArrowLeft, mdiArrowRight, mdiDotsVertical, mdiCodeTags, mdiClose,
|
import { mdiArrowLeft, mdiArrowRight, mdiDotsVertical, mdiCodeTags, mdiClose,
|
||||||
mdiEye, mdiEyeOff, mdiDelete, mdiRepeat, mdiLock,
|
mdiEye, mdiEyeOff, mdiDelete, mdiRepeat, mdiLock, mdiFileDownloadOutline,
|
||||||
mdiCalendarExport, mdiCalendar, mdiContentCopy, mdiMapMarker } from '@mdi/js'
|
mdiCalendarExport, mdiCalendar, mdiContentCopy, mdiMapMarker } from '@mdi/js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Event',
|
name: 'Event',
|
||||||
mixins: [clipboard],
|
mixins: [clipboard],
|
||||||
components: {
|
components: {
|
||||||
EventAdmin: () => import(/* webpackChunkName: "event" */'./eventAdmin'),
|
EventAdmin,
|
||||||
EmbedEvent: () => import(/* webpackChunkName: "event" */'./embedEvent'),
|
EmbedEvent,
|
||||||
|
MyPicture
|
||||||
},
|
},
|
||||||
async asyncData ({ $axios, params, error, store }) {
|
async asyncData ({ $axios, params, error }) {
|
||||||
try {
|
try {
|
||||||
const event = await $axios.$get(`/event/${params.slug}`)
|
const event = await $axios.$get(`/event/${params.slug}`)
|
||||||
return { event }
|
return { event }
|
||||||
|
@ -156,7 +157,7 @@ export default {
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
mdiArrowLeft, mdiArrowRight, mdiDotsVertical, mdiCodeTags, mdiCalendarExport, mdiCalendar,
|
mdiArrowLeft, mdiArrowRight, mdiDotsVertical, mdiCodeTags, mdiCalendarExport, mdiCalendar, mdiFileDownloadOutline,
|
||||||
mdiMapMarker, mdiContentCopy, mdiClose, mdiDelete, mdiEye, mdiEyeOff, mdiRepeat, mdiLock,
|
mdiMapMarker, mdiContentCopy, mdiClose, mdiDelete, mdiEye, mdiEyeOff, mdiRepeat, mdiLock,
|
||||||
currentAttachment: 0,
|
currentAttachment: 0,
|
||||||
event: {},
|
event: {},
|
||||||
|
@ -169,7 +170,7 @@ export default {
|
||||||
if (!this.event) {
|
if (!this.event) {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
const tags_feed = this.event.tags.map(tag => ({
|
const tags_feed = this.event.tags && this.event.tags.map(tag => ({
|
||||||
rel: 'alternate',
|
rel: 'alternate',
|
||||||
type: 'application/rss+xml',
|
type: 'application/rss+xml',
|
||||||
title: `${this.settings.title} events tagged ${tag}`,
|
title: `${this.settings.title} events tagged ${tag}`,
|
||||||
|
@ -247,7 +248,7 @@ export default {
|
||||||
return this.event.media && this.event.media.length
|
return this.event.media && this.event.media.length
|
||||||
},
|
},
|
||||||
plainDescription () {
|
plainDescription () {
|
||||||
return htmlToText.fromString(this.event.description.replace('\n', '').slice(0, 1000))
|
return htmlToText(this.event.description && this.event.description.replace('\n', '').slice(0, 1000))
|
||||||
},
|
},
|
||||||
currentAttachmentLabel () {
|
currentAttachmentLabel () {
|
||||||
return get(this.selectedResource, `data.attachment[${this.currentAttachment}].name`, '')
|
return get(this.selectedResource, `data.attachment[${this.currentAttachment}].name`, '')
|
||||||
|
@ -330,11 +331,4 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
|
||||||
.main_image {
|
|
||||||
margin: 0 auto;
|
|
||||||
border-radius: 5px;
|
|
||||||
transition: max-height 0.2s;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -11,7 +11,7 @@
|
||||||
Search(
|
Search(
|
||||||
:filters='filters'
|
:filters='filters'
|
||||||
@update='f => filters = f')
|
@update='f => filters = f')
|
||||||
v-tabs(v-model='type' show-arrows)
|
v-tabs(v-model='type' show-arrows :next-icon='mdiChevronRight' :prev-icon='mdiChevronLeft')
|
||||||
|
|
||||||
//- TOFIX
|
//- TOFIX
|
||||||
//- v-tab {{$t('common.email')}}
|
//- v-tab {{$t('common.email')}}
|
||||||
|
@ -86,7 +86,7 @@ import { mapState } from 'vuex'
|
||||||
import FollowMe from '../components/FollowMe'
|
import FollowMe from '../components/FollowMe'
|
||||||
import Search from '@/components/Search'
|
import Search from '@/components/Search'
|
||||||
import clipboard from '../assets/clipboard'
|
import clipboard from '../assets/clipboard'
|
||||||
import { mdiContentCopy } from '@mdi/js'
|
import { mdiContentCopy, mdiChevronRight, mdiChevronLeft } from '@mdi/js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Exports',
|
name: 'Exports',
|
||||||
|
@ -104,7 +104,7 @@ export default {
|
||||||
},
|
},
|
||||||
data ({ $store }) {
|
data ({ $store }) {
|
||||||
return {
|
return {
|
||||||
mdiContentCopy,
|
mdiContentCopy, mdiChevronLeft, mdiChevronRight,
|
||||||
type: 'rss',
|
type: 'rss',
|
||||||
notification: { email: '' },
|
notification: { email: '' },
|
||||||
list: {
|
list: {
|
||||||
|
|
31
pages/g/_cohort.vue
Normal file
31
pages/g/_cohort.vue
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<template>
|
||||||
|
<v-container id='home' fluid>
|
||||||
|
|
||||||
|
<h1 class='d-block text-h3 font-weight-black text-center align-center text-uppercase mt-10 mb-12 mx-auto w-100 text-underline'><u>{{cohort}}</u></h1>
|
||||||
|
|
||||||
|
<!-- Events -->
|
||||||
|
<div class='mb-2 mt-1 pl-1 pl-sm-2' id="events">
|
||||||
|
<Event :event='event' v-for='(event, idx) in events' :lazy='idx>2' :key='event.id'></Event>
|
||||||
|
</div>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import Event from '@/components/Event'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Tag',
|
||||||
|
components: { Event },
|
||||||
|
async asyncData ({ $axios, params, error }) {
|
||||||
|
try {
|
||||||
|
const cohort = params.cohort
|
||||||
|
const events = await $axios.$get(`/cohorts/${cohort}`)
|
||||||
|
return { events, cohort }
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
error({ statusCode: 400, message: 'Error!' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
111
pages/index.vue
111
pages/index.vue
|
@ -1,43 +1,41 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
v-container#home(fluid)
|
v-container#home(fluid)
|
||||||
|
|
||||||
//- Announcements
|
//- Announcements
|
||||||
#announcements.mx-1.mt-1(v-if='announcements.length')
|
#announcements.mx-1.mt-1(v-if='announcements.length')
|
||||||
Announcement(v-for='announcement in announcements' :key='`a_${announcement.id}`' :announcement='announcement')
|
Announcement(v-for='announcement in announcements' :key='`a_${announcement.id}`' :announcement='announcement')
|
||||||
|
|
||||||
//- Calendar and search bar
|
//- Calendar and search bar
|
||||||
v-row.pt-0.pt-sm-2.pl-0.pl-sm-2
|
v-row.ma-2
|
||||||
#calh.col-xl-5.col-lg-5.col-md-7.col-sm-12.col-xs-12.pa-4.pa-sm-3
|
#calh.col-xl-5.col-lg-5.col-md-7.col-sm-12.col-xs-12.pa-0.ma-0
|
||||||
//- this is needed as v-calendar does not support SSR
|
//- this is needed as v-calendar does not support SSR
|
||||||
//- https://github.com/nathanreyes/v-calendar/issues/336
|
//- https://github.com/nathanreyes/v-calendar/issues/336
|
||||||
client-only(placeholder='Calendar unavailable without js')
|
client-only(placeholder='Loading...')
|
||||||
Calendar(@dayclick='dayChange' @monthchange='monthChange' :events='filteredEvents')
|
Calendar(@dayclick='dayChange' @monthchange='monthChange' :events='events')
|
||||||
|
|
||||||
.col.pt-0.pt-md-2
|
.col.pt-0.pt-md-2.mt-4.ma-md-0.pb-0
|
||||||
Search(:filters='filters' @update='updateFilters')
|
//- v-btn(to='/search' color='primary' ) {{$t('common.search')}}
|
||||||
v-chip(v-if='selectedDay' close :close-icon='mdiCloseCircle' @click:close='dayChange()') {{selectedDay}}
|
v-form(to='/search' action='/search' method='GET')
|
||||||
|
v-text-field(name='search' :label='$t("common.search")' outlined rounded hide-details :append-icon='mdiMagnify')
|
||||||
//- Events
|
|
||||||
#events.mb-2.mt-1.pl-1.pl-sm-2
|
|
||||||
Event(:event='event' @destroy='destroy' v-for='(event, idx) in visibleEvents' :lazy='idx>2' :key='event.id' @tagclick='tagClick' @placeclick='placeClick')
|
|
||||||
|
|
||||||
|
//- Events
|
||||||
|
#events.mb-2.mt-1.pl-1.pl-sm-2
|
||||||
|
Event(:event='event' @destroy='destroy' v-for='(event, idx) in visibleEvents' :lazy='idx>2' :key='event.id')
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState, mapActions } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import intersection from 'lodash/intersection'
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import Event from '@/components/Event'
|
import Event from '@/components/Event'
|
||||||
import Announcement from '@/components/Announcement'
|
import Announcement from '@/components/Announcement'
|
||||||
import Search from '@/components/Search'
|
|
||||||
import Calendar from '@/components/Calendar'
|
import Calendar from '@/components/Calendar'
|
||||||
import { mdiCloseCircle } from '@mdi/js'
|
import { mdiMagnify } from '@mdi/js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Index',
|
name: 'Index',
|
||||||
components: { Event, Search, Announcement, Calendar },
|
components: { Event, Announcement, Calendar },
|
||||||
middleware: 'setup',
|
middleware: 'setup',
|
||||||
async asyncData ({ params, $api, store }) {
|
async asyncData ({ $api }) {
|
||||||
const events = await $api.getEvents({
|
const events = await $api.getEvents({
|
||||||
start: dayjs().startOf('month').unix(),
|
start: dayjs().startOf('month').unix(),
|
||||||
end: null,
|
end: null,
|
||||||
|
@ -45,13 +43,13 @@ export default {
|
||||||
})
|
})
|
||||||
return { events }
|
return { events }
|
||||||
},
|
},
|
||||||
data ({ $store }) {
|
data () {
|
||||||
return {
|
return {
|
||||||
mdiCloseCircle,
|
mdiMagnify,
|
||||||
first: true,
|
first: true,
|
||||||
isCurrentMonth: true,
|
isCurrentMonth: true,
|
||||||
now: dayjs().unix(),
|
now: dayjs().unix(),
|
||||||
date: dayjs().format('YYYY-MM-DD'),
|
date: dayjs.tz().format('YYYY-MM-DD'),
|
||||||
events: [],
|
events: [],
|
||||||
start: dayjs().startOf('month').unix(),
|
start: dayjs().startOf('month').unix(),
|
||||||
end: null,
|
end: null,
|
||||||
|
@ -74,49 +72,22 @@ export default {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['settings', 'announcements', 'filters']),
|
...mapState(['settings', 'announcements']),
|
||||||
filteredEvents () {
|
|
||||||
let events = this.events
|
|
||||||
if (!this.filters.places.length && !this.filters.tags.length) {
|
|
||||||
if (this.filters.show_recurrent) {
|
|
||||||
return this.events
|
|
||||||
}
|
|
||||||
events = events.filter(e => !e.parentId)
|
|
||||||
}
|
|
||||||
|
|
||||||
return events.filter(e => {
|
|
||||||
// check tags intersection
|
|
||||||
if (this.filters.tags.length) {
|
|
||||||
const ret = intersection(this.filters.tags, e.tags)
|
|
||||||
if (!ret.length) { return false }
|
|
||||||
}
|
|
||||||
// check if place is in filtered places
|
|
||||||
if (this.filters.places.length && !this.filters.places.includes(e.place.id)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
},
|
|
||||||
visibleEvents () {
|
visibleEvents () {
|
||||||
const now = dayjs().unix()
|
const now = dayjs().unix()
|
||||||
if (this.selectedDay) {
|
if (this.selectedDay) {
|
||||||
const min = dayjs(this.selectedDay).startOf('day').unix()
|
const min = dayjs(this.selectedDay).startOf('day').unix()
|
||||||
const max = dayjs(this.selectedDay).endOf('day').unix()
|
const max = dayjs(this.selectedDay).endOf('day').unix()
|
||||||
return this.filteredEvents.filter(e => (e.start_datetime < max && e.start_datetime > min))
|
return this.events.filter(e => (e.start_datetime <= max && e.start_datetime >= min))
|
||||||
} else if (this.isCurrentMonth) {
|
} else if (this.isCurrentMonth) {
|
||||||
return this.filteredEvents.filter(e => e.end_datetime ? e.end_datetime > now : e.start_datetime + 2 * 60 * 60 > now)
|
return this.events.filter(e => e.end_datetime ? e.end_datetime > now : e.start_datetime + 2 * 60 * 60 > now)
|
||||||
} else {
|
} else {
|
||||||
return this.filteredEvents
|
return this.events
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// onIntersect (isIntersecting, eventId) {
|
|
||||||
// this.intersecting[eventId] = isIntersecting
|
|
||||||
// },
|
|
||||||
...mapActions(['setFilters']),
|
|
||||||
destroy (id) {
|
destroy (id) {
|
||||||
this.events = this.events.filter(e => e.id !== id)
|
this.events = this.events.filter(e => e.id !== id)
|
||||||
},
|
},
|
||||||
|
@ -131,20 +102,6 @@ export default {
|
||||||
this.$nuxt.$loading.finish()
|
this.$nuxt.$loading.finish()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
placeClick (place_id) {
|
|
||||||
if (this.filters.places.includes(place_id)) {
|
|
||||||
this.setFilters({ ...this.filters, places: this.filters.places.filter(p_id => p_id !== place_id) })
|
|
||||||
} else {
|
|
||||||
this.setFilters({ ...this.filters, places: [].concat(this.filters.places, place_id) })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tagClick (tag) {
|
|
||||||
if (this.filters.tags.includes(tag)) {
|
|
||||||
this.setFilters({ ...this.filters, tags: this.filters.tags.filter(t => t !== tag) })
|
|
||||||
} else {
|
|
||||||
this.setFilters({ ...this.filters, tags: [].concat(this.filters.tags, tag) })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
monthChange ({ year, month }) {
|
monthChange ({ year, month }) {
|
||||||
// avoid first time monthChange event (onload)
|
// avoid first time monthChange event (onload)
|
||||||
if (this.first) {
|
if (this.first) {
|
||||||
|
@ -158,24 +115,20 @@ export default {
|
||||||
this.selectedDay = null
|
this.selectedDay = null
|
||||||
|
|
||||||
// check if current month is selected
|
// check if current month is selected
|
||||||
if (month - 1 === dayjs().month() && year === dayjs().year()) {
|
if (month - 1 === dayjs.tz().month() && year === dayjs.tz().year()) {
|
||||||
this.isCurrentMonth = true
|
this.isCurrentMonth = true
|
||||||
this.start = dayjs().startOf('month').unix()
|
this.start = dayjs().startOf('month').unix()
|
||||||
this.date = dayjs().format('YYYY-MM-DD')
|
this.date = dayjs.tz().format('YYYY-MM-DD')
|
||||||
} else {
|
} else {
|
||||||
this.isCurrentMonth = false
|
this.isCurrentMonth = false
|
||||||
this.date = ''
|
this.date = ''
|
||||||
this.start = dayjs().year(year).month(month - 1).startOf('month').unix() // .startOf('week').unix()
|
this.start = dayjs().year(year).month(month - 1).startOf('month').unix() // .startOf('week').unix()
|
||||||
}
|
}
|
||||||
// TODO: check if calendar view is double
|
|
||||||
this.end = dayjs().year(year).month(month).endOf('month').unix() // .endOf('week').unix()
|
this.end = dayjs().year(year).month(month).endOf('month').unix() // .endOf('week').unix()
|
||||||
this.updateEvents()
|
this.updateEvents()
|
||||||
},
|
},
|
||||||
updateFilters (filters) {
|
|
||||||
this.setFilters(filters)
|
|
||||||
},
|
|
||||||
dayChange (day) {
|
dayChange (day) {
|
||||||
this.selectedDay = day ? dayjs(day).format('YYYY-MM-DD') : null
|
this.selectedDay = day ? dayjs.tz(day).format('YYYY-MM-DD') : null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
30
pages/p/_place.vue
Normal file
30
pages/p/_place.vue
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<template>
|
||||||
|
<v-container id='home' fluid>
|
||||||
|
|
||||||
|
<h1 class='d-block text-h4 font-weight-black text-center text-uppercase mt-5 mx-auto w-100 text-underline'><u>{{place.name}}</u></h1>
|
||||||
|
<span class="d-block text-subtitle text-center w-100 mb-14">{{place.address}}</span>
|
||||||
|
|
||||||
|
<!-- Events -->
|
||||||
|
<div class="mb-2 mt-1 pl-1 pl-sm-2" id="events">
|
||||||
|
<Event :event='event' v-for='(event, idx) in events' :lazy='idx>2' :key='event.id'></Event>
|
||||||
|
</div>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import Event from '@/components/Event'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Tag',
|
||||||
|
components: { Event },
|
||||||
|
asyncData ({ $axios, params, error }) {
|
||||||
|
try {
|
||||||
|
const place = params.place
|
||||||
|
return $axios.$get(`/place/${place}/events`)
|
||||||
|
} catch (e) {
|
||||||
|
error({ statusCode: 400, message: 'Error!' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
42
pages/search.vue
Normal file
42
pages/search.vue
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<template lang="pug">
|
||||||
|
v-container#home(fluid)
|
||||||
|
|
||||||
|
v-form.ma-5(to='/search' action='/search' method='GET')
|
||||||
|
v-text-field(name='search' :label='$t("common.search")' :value='$route.query.search' hide-details outlined rounded :append-icon='mdiMagnify')
|
||||||
|
|
||||||
|
//- Events
|
||||||
|
#events.mb-2.mt-1.pl-1.pl-sm-2
|
||||||
|
Event(:event='event' @destroy='destroy' v-for='(event, idx) in events' :lazy='idx>2' :key='event.id')
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import Event from '@/components/Event'
|
||||||
|
import Announcement from '@/components/Announcement'
|
||||||
|
import Calendar from '@/components/Calendar'
|
||||||
|
import { mdiMagnify } from '@mdi/js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Index',
|
||||||
|
components: { Event, Announcement, Calendar },
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
mdiMagnify,
|
||||||
|
events: [],
|
||||||
|
start: dayjs().startOf('month').unix(),
|
||||||
|
end: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async fetch () {
|
||||||
|
const search = this.$route.query.search
|
||||||
|
this.events = await this.$axios.$get(`/event/search?search=${search}`)
|
||||||
|
},
|
||||||
|
computed: mapState(['settings']),
|
||||||
|
methods: {
|
||||||
|
destroy (id) {
|
||||||
|
this.events = this.events.filter(e => e.id !== id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,41 +0,0 @@
|
||||||
<template lang="pug">
|
|
||||||
v-container
|
|
||||||
v-card-title.d-block.text-h5.text-center(v-text="$t('setup.completed')")
|
|
||||||
v-card-text(v-html="$t('setup.completed_description', user)")
|
|
||||||
v-alert.mb-3.mt-1(outlined type='warning' color='red' show-icon :icon='mdiAlert') {{$t('setup.copy_password_dialog')}}
|
|
||||||
v-card-actions
|
|
||||||
v-btn(text @click='next' color='primary' :loading='loading' :disabled='loading') {{$t('setup.start')}}
|
|
||||||
v-icon(v-text='mdiArrowRight')
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import { mdiArrowRight, mdiAlert } from '@mdi/js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
mdiArrowRight, mdiAlert,
|
|
||||||
loading: false,
|
|
||||||
user: {
|
|
||||||
email: 'admin',
|
|
||||||
password: ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
next () {
|
|
||||||
window.location='/admin'
|
|
||||||
},
|
|
||||||
async start (user) {
|
|
||||||
this.user = { ...user }
|
|
||||||
this.loading = true
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.$axios.$get('/ping')
|
|
||||||
this.loading = false
|
|
||||||
} catch (e) {
|
|
||||||
setTimeout(() => this.start(user), 1000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,32 +1,26 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
|
v-container.pa-6
|
||||||
|
h2.mb-2.text-center Gancio Setup
|
||||||
|
v-stepper.grey.lighten-5(v-model='step')
|
||||||
|
v-stepper-header
|
||||||
|
v-stepper-step(v-show='!dbdone' :complete='step > 1' step='1') Database
|
||||||
|
v-divider(v-show='!dbdone')
|
||||||
|
v-stepper-step(:complete='step > 2' step='2') Configuration
|
||||||
|
v-divider
|
||||||
|
v-stepper-step(:complete='step > 3' step='3') Finish
|
||||||
|
|
||||||
v-container.pa-6
|
v-stepper-items
|
||||||
h2.mb-2.text-center Gancio Setup
|
v-stepper-content(v-show='!dbdone' step='1')
|
||||||
v-stepper.grey.lighten-5(v-model='step')
|
DbStep(@complete='dbCompleted')
|
||||||
v-stepper-header
|
v-stepper-content(step='2')
|
||||||
v-stepper-step(v-show='!dbdone' :complete='step > 1' step='1') Database
|
Settings(setup, @complete='configCompleted')
|
||||||
v-divider(v-show='!dbdone')
|
v-stepper-content(step='3')
|
||||||
v-stepper-step(:complete='step > 2' step='2') Configuration
|
Completed(ref='completed' :isHttp='isHttp')
|
||||||
v-divider
|
|
||||||
v-stepper-step(:complete='step > 3' step='3') Finish
|
|
||||||
|
|
||||||
v-stepper-items
|
|
||||||
v-stepper-content(v-show='!dbdone' step='1')
|
|
||||||
DbStep(@complete='dbCompleted')
|
|
||||||
v-stepper-content(step='2')
|
|
||||||
Settings(setup, @complete='configCompleted')
|
|
||||||
v-stepper-content(step='3')
|
|
||||||
Completed(ref='completed')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import DbStep from './DbStep'
|
import DbStep from '@/components/DbStep'
|
||||||
import Settings from '../../components/admin/Settings'
|
import Settings from '@/components/admin/Settings'
|
||||||
import Completed from './Completed'
|
import Completed from '@/components/Completed'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { DbStep, Settings, Completed },
|
components: { DbStep, Settings, Completed },
|
||||||
|
@ -36,9 +30,10 @@ export default {
|
||||||
title: 'Setup',
|
title: 'Setup',
|
||||||
},
|
},
|
||||||
auth: false,
|
auth: false,
|
||||||
asyncData ({ params }) {
|
asyncData ({ params, req }) {
|
||||||
|
const protocol = process.client ? window.location.protocol : req.protocol + ':'
|
||||||
return {
|
return {
|
||||||
|
isHttp: protocol === 'http:',
|
||||||
dbdone: !!Number(params.db),
|
dbdone: !!Number(params.db),
|
||||||
config: {
|
config: {
|
||||||
db: {
|
db: {
|
||||||
|
|
30
pages/tag/_tag.vue
Normal file
30
pages/tag/_tag.vue
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<template>
|
||||||
|
<v-container id='home' fluid>
|
||||||
|
|
||||||
|
<h1 class='d-block text-h3 font-weight-black text-center align-center text-uppercase mt-5 mb-16 mx-auto w-100 text-underline'><u>{{tag}}</u></h1>
|
||||||
|
|
||||||
|
<!-- Events -->
|
||||||
|
<div class="mb-2 mt-1 pl-1 pl-sm-2" id="events">
|
||||||
|
<Event :event='event' v-for='(event, idx) in events' :lazy='idx>2' :key='event.id'></Event>
|
||||||
|
</div>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import Event from '@/components/Event'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Tag',
|
||||||
|
components: { Event },
|
||||||
|
async asyncData ({ $axios, params, error }) {
|
||||||
|
try {
|
||||||
|
const tag = params.tag
|
||||||
|
const events = await $axios.$get(`/events?tags=${tag}`)
|
||||||
|
return { events, tag }
|
||||||
|
} catch (e) {
|
||||||
|
error({ statusCode: 400, message: 'Error!' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -10,7 +10,7 @@ export default ({ $axios }, inject) => {
|
||||||
* end_datetime: unix_timestamp
|
* end_datetime: unix_timestamp
|
||||||
* tags: [tag, list],
|
* tags: [tag, list],
|
||||||
* places: [place_id],
|
* places: [place_id],
|
||||||
* limit: (default ∞)
|
* max: (default ∞)
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
@ -22,7 +22,8 @@ export default ({ $axios }, inject) => {
|
||||||
end: params.end,
|
end: params.end,
|
||||||
places: params.places && params.places.join(','),
|
places: params.places && params.places.join(','),
|
||||||
tags: params.tags && params.tags.join(','),
|
tags: params.tags && params.tags.join(','),
|
||||||
show_recurrent: !!params.show_recurrent
|
show_recurrent: !!params.show_recurrent,
|
||||||
|
max: params.maxs
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return events.map(e => Object.freeze(e))
|
return events.map(e => Object.freeze(e))
|
||||||
|
|
|
@ -7,12 +7,15 @@ import localizedFormat from 'dayjs/plugin/localizedFormat'
|
||||||
|
|
||||||
|
|
||||||
import 'dayjs/locale/it'
|
import 'dayjs/locale/it'
|
||||||
|
import 'dayjs/locale/en'
|
||||||
import 'dayjs/locale/es'
|
import 'dayjs/locale/es'
|
||||||
import 'dayjs/locale/ca'
|
import 'dayjs/locale/ca'
|
||||||
import 'dayjs/locale/pl'
|
import 'dayjs/locale/pl'
|
||||||
import 'dayjs/locale/eu'
|
import 'dayjs/locale/eu'
|
||||||
import 'dayjs/locale/nb'
|
import 'dayjs/locale/nb'
|
||||||
import 'dayjs/locale/fr'
|
import 'dayjs/locale/fr'
|
||||||
|
import 'dayjs/locale/de'
|
||||||
|
import 'dayjs/locale/gl'
|
||||||
|
|
||||||
dayjs.extend(relativeTime)
|
dayjs.extend(relativeTime)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
@ -23,25 +26,27 @@ export default ({ app, store }) => {
|
||||||
// set timezone to instance_timezone!!
|
// set timezone to instance_timezone!!
|
||||||
// to show local time relative to event's place
|
// to show local time relative to event's place
|
||||||
// not where in the world I'm looking at the page from
|
// not where in the world I'm looking at the page from
|
||||||
dayjs.tz.setDefault(store.state.settings.instance_timezone)
|
const instance_timezone = store.state.settings.instance_timezone
|
||||||
dayjs.locale(store.state.locale)
|
const locale = store.state.locale
|
||||||
|
dayjs.tz.setDefault(instance_timezone)
|
||||||
|
dayjs.locale(locale)
|
||||||
|
|
||||||
// replace links with anchors
|
// replace links with anchors
|
||||||
// TODO: remove fb tracking id?
|
// TODO: remove fb tracking id?
|
||||||
Vue.filter('linkify', value => value.replace(/(https?:\/\/([^\s]+))/g, '<a href="$1">$2</a>'))
|
Vue.filter('linkify', value => value.replace(/(https?:\/\/([^\s]+))/g, '<a href="$1">$2</a>'))
|
||||||
Vue.filter('url2host', url => url.match(/^https?:\/\/(.[^/:]+)/i)[1])
|
Vue.filter('url2host', url => url.match(/^https?:\/\/(.[^/:]+)/i)[1])
|
||||||
Vue.filter('datetime', value => dayjs(value).locale(store.state.locale).format('ddd, D MMMM HH:mm'))
|
Vue.filter('datetime', value => dayjs.tz(value).locale(locale).format('ddd, D MMMM HH:mm'))
|
||||||
Vue.filter('dateFormat', (value, format) => dayjs(value).format(format))
|
Vue.filter('dateFormat', (value, format) => dayjs.tz(value).format(format))
|
||||||
Vue.filter('unixFormat', (timestamp, format) => dayjs.unix(timestamp).format(format))
|
Vue.filter('unixFormat', (timestamp, format) => dayjs.unix(timestamp).tz(instance_timezone).format(format))
|
||||||
|
|
||||||
// shown in mobile homepage
|
// shown in mobile homepage
|
||||||
Vue.filter('day', value => dayjs.unix(value).locale(store.state.locale).format('dddd, D MMM'))
|
Vue.filter('day', value => dayjs.unix(value).tz(instance_timezone).locale(store.state.locale).format('dddd, D MMM'))
|
||||||
Vue.filter('mediaURL', (event, type) => {
|
Vue.filter('mediaURL', (event, type, format = '.jpg') => {
|
||||||
if (event.media && event.media.length) {
|
if (event.media && event.media.length) {
|
||||||
if (type === 'alt') {
|
if (type === 'alt') {
|
||||||
return event.media[0].name
|
return event.media[0].name
|
||||||
} else {
|
} else {
|
||||||
return store.state.settings.baseurl + '/media/' + (type === 'thumb' ? 'thumb/' : '') + event.media[0].url
|
return store.state.settings.baseurl + '/media/' + (type === 'thumb' ? 'thumb/' : '') + event.media[0].url.replace(/.jpg$/, format)
|
||||||
}
|
}
|
||||||
} else if (type !== 'alt') {
|
} else if (type !== 'alt') {
|
||||||
return store.state.settings.baseurl + '/media/' + (type === 'thumb' ? 'thumb/' : '') + 'logo.svg'
|
return store.state.settings.baseurl + '/media/' + (type === 'thumb' ? 'thumb/' : '') + 'logo.svg'
|
||||||
|
@ -49,16 +54,16 @@ export default ({ app, store }) => {
|
||||||
return ''
|
return ''
|
||||||
})
|
})
|
||||||
|
|
||||||
Vue.filter('from', timestamp => dayjs.unix(timestamp).fromNow())
|
Vue.filter('from', timestamp => dayjs.unix(timestamp).tz(instance_timezone).fromNow())
|
||||||
|
|
||||||
Vue.filter('recurrentDetail', event => {
|
Vue.filter('recurrentDetail', event => {
|
||||||
const parent = event.parent
|
const parent = event.parent
|
||||||
const { frequency, type } = parent.recurrent
|
const { frequency, type } = parent.recurrent
|
||||||
let recurrent
|
let recurrent
|
||||||
if (frequency === '1w' || frequency === '2w') {
|
if (frequency === '1w' || frequency === '2w') {
|
||||||
recurrent = app.i18n.t(`event.recurrent_${frequency}_days`, { days: dayjs.unix(parent.start_datetime).format('dddd') })
|
recurrent = app.i18n.t(`event.recurrent_${frequency}_days`, { days: dayjs.unix(parent.start_datetime).tz(instance_timezone).format('dddd') })
|
||||||
} else if (frequency === '1m' || frequency === '2m') {
|
} else if (frequency === '1m' || frequency === '2m') {
|
||||||
const d = type === 'ordinal' ? dayjs.unix(parent.start_datetime).date() : dayjs.unix(parent.start_datetime).format('dddd')
|
const d = type === 'ordinal' ? dayjs.unix(parent.start_datetime).date() : dayjs.unix(parent.start_datetime).tz(instance_timezone).format('dddd')
|
||||||
if (type === 'ordinal') {
|
if (type === 'ordinal') {
|
||||||
recurrent = app.i18n.t(`event.recurrent_${frequency}_days`, { days: d })
|
recurrent = app.i18n.t(`event.recurrent_${frequency}_days`, { days: d })
|
||||||
} else {
|
} else {
|
||||||
|
@ -70,8 +75,8 @@ export default ({ app, store }) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
Vue.filter('when', (event) => {
|
Vue.filter('when', (event) => {
|
||||||
const start = dayjs.unix(event.start_datetime)
|
const start = dayjs.unix(event.start_datetime).tz(instance_timezone)
|
||||||
const end = dayjs.unix(event.end_datetime)
|
const end = dayjs.unix(event.end_datetime).tz(instance_timezone)
|
||||||
|
|
||||||
// const normal = `${start.format('dddd, D MMMM (HH:mm-')}${end.format('HH:mm) ')}`
|
// const normal = `${start.format('dddd, D MMMM (HH:mm-')}${end.format('HH:mm) ')}`
|
||||||
// // recurrent event
|
// // recurrent event
|
||||||
|
@ -90,10 +95,10 @@ export default ({ app, store }) => {
|
||||||
|
|
||||||
// multidate
|
// multidate
|
||||||
if (event.multidate) {
|
if (event.multidate) {
|
||||||
return `${start.format('ddd, D MMM HH:mm')} - ${end.format('ddd, D MMM')}`
|
return `${start.format('ddd, D MMM HH:mm')} - ${end.format('ddd, D MMM HH:mm')}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// normal event
|
// normal event
|
||||||
return start.format('ddd, D MMMM HH:mm')
|
return `${start.format('ddd, D MMM HH:mm')} - ${end.format('HH:mm')}`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
191
server/api/controller/cohort.js
Normal file
191
server/api/controller/cohort.js
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
const Cohort = require('../models/cohort')
|
||||||
|
const Filter = require('../models/filter')
|
||||||
|
const Event = require('../models/event')
|
||||||
|
const Tag = require('../models/tag')
|
||||||
|
const Place = require('../models/place')
|
||||||
|
const log = require('../../log')
|
||||||
|
const dayjs = require('dayjs')
|
||||||
|
|
||||||
|
// const { sequelize } = require('../models/index')
|
||||||
|
|
||||||
|
|
||||||
|
const { Op, Sequelize } = require('sequelize')
|
||||||
|
|
||||||
|
const cohortController = {
|
||||||
|
|
||||||
|
async getAll (req, res) {
|
||||||
|
const withFilters = req.query.withFilters
|
||||||
|
let cohorts
|
||||||
|
if (withFilters) {
|
||||||
|
cohorts = await Cohort.findAll({ include: [Filter] })
|
||||||
|
|
||||||
|
} else {
|
||||||
|
cohorts = await Cohort.findAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json(cohorts)
|
||||||
|
},
|
||||||
|
|
||||||
|
// return events from cohort
|
||||||
|
async getEvents (req, res) {
|
||||||
|
const name = req.params.name
|
||||||
|
|
||||||
|
const cohort = await Cohort.findOne({ where: { name } })
|
||||||
|
if (!cohort) {
|
||||||
|
return res.sendStatus(404)
|
||||||
|
}
|
||||||
|
const filters = await Filter.findAll({ where: { cohortId: cohort.id } })
|
||||||
|
|
||||||
|
const start = dayjs().unix()
|
||||||
|
const where = {
|
||||||
|
// do not include parent recurrent event
|
||||||
|
recurrent: null,
|
||||||
|
|
||||||
|
// confirmed event only
|
||||||
|
is_visible: true,
|
||||||
|
|
||||||
|
// [Op.or]: {
|
||||||
|
start_datetime: { [Op.gte]: start },
|
||||||
|
// end_datetime: { [Op.gte]: start }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (!show_recurrent) {
|
||||||
|
// where.parentId = null
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (end) {
|
||||||
|
// where.start_datetime = { [Op.lte]: end }
|
||||||
|
// }
|
||||||
|
|
||||||
|
const replacements = []
|
||||||
|
const ors = []
|
||||||
|
filters.forEach(f => {
|
||||||
|
if (f.tags && f.tags.length) {
|
||||||
|
const tags = Sequelize.fn('EXISTS', Sequelize.literal('SELECT 1 FROM event_tags WHERE "event_tags"."eventId"="event".id AND "tagTag" in (?)'))
|
||||||
|
replacements.push(f.tags)
|
||||||
|
if (f.places && f.places.length) {
|
||||||
|
ors.push({ [Op.and]: [ { placeId: f.places.map(p => p.id) },tags] })
|
||||||
|
} else {
|
||||||
|
ors.push(tags)
|
||||||
|
}
|
||||||
|
} else if (f.places && f.places.length) {
|
||||||
|
ors.push({ placeId: f.places.map(p => p.id) })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// if (tags && places) {
|
||||||
|
// where[Op.or] = {
|
||||||
|
// placeId: places ? places.split(',') : [],
|
||||||
|
// // '$tags.tag$': Sequelize.literal(`EXISTS (SELECT 1 FROM event_tags WHERE tagTag in ( ${Sequelize.QueryInterface.escape(tags)} ) )`)
|
||||||
|
// }
|
||||||
|
// } else if (tags) {
|
||||||
|
// where[Op.and] = Sequelize.literal(`EXISTS (SELECT 1 FROM event_tags WHERE event_tags.eventId=event.id AND tagTag in (?))`)
|
||||||
|
// replacements.push(tags)
|
||||||
|
// } else if (places) {
|
||||||
|
// where.placeId = places.split(',')
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (ors.length) {
|
||||||
|
where[Op.or] = ors
|
||||||
|
}
|
||||||
|
|
||||||
|
const events = await Event.findAll({
|
||||||
|
logging: console.log,
|
||||||
|
where,
|
||||||
|
attributes: {
|
||||||
|
exclude: ['likes', 'boost', 'userId', 'is_visible', 'createdAt', 'updatedAt', 'description', 'resources']
|
||||||
|
},
|
||||||
|
order: ['start_datetime'],
|
||||||
|
include: [
|
||||||
|
// { model: Resource, required: false, attributes: ['id'] },
|
||||||
|
{
|
||||||
|
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'] }
|
||||||
|
],
|
||||||
|
// limit: max,
|
||||||
|
replacements
|
||||||
|
}).catch(e => {
|
||||||
|
log.error('[EVENT]', e)
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
async add (req, res) {
|
||||||
|
const cohortDetail = {
|
||||||
|
name: req.body.name,
|
||||||
|
isActor: true,
|
||||||
|
isTop: true
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: validation
|
||||||
|
log.info('Create cohort: ' + req.body.name)
|
||||||
|
const cohort = await Cohort.create(cohortDetail)
|
||||||
|
res.json(cohort)
|
||||||
|
},
|
||||||
|
|
||||||
|
async remove (req, res) {
|
||||||
|
const cohort_id = req.params.id
|
||||||
|
log.info('Remove cohort', cohort_id)
|
||||||
|
try {
|
||||||
|
const cohort = await Cohort.findByPk(cohort_id)
|
||||||
|
await cohort.destroy()
|
||||||
|
res.sendStatus(200)
|
||||||
|
} catch (e) {
|
||||||
|
log.error('Remove cohort failed:', e)
|
||||||
|
res.sendStatus(404)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async getFilters (req, res) {
|
||||||
|
const cohortId = req.params.cohort_id
|
||||||
|
const filters = await Filter.findAll({ where: { cohortId } })
|
||||||
|
return res.json(filters)
|
||||||
|
},
|
||||||
|
|
||||||
|
async addFilter (req, res) {
|
||||||
|
const cohortId = req.body.cohortId
|
||||||
|
const tags = req.body.tags
|
||||||
|
const places = req.body.places
|
||||||
|
try {
|
||||||
|
const filter = await Filter.create({ cohortId, tags, places })
|
||||||
|
return res.json(filter)
|
||||||
|
} catch (e) {
|
||||||
|
log.error(String(e))
|
||||||
|
return res.status(500)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async removeFilter (req, res) {
|
||||||
|
const filter_id = req.params.id
|
||||||
|
log.info('Remove filter', filter_id)
|
||||||
|
try {
|
||||||
|
const filter = await Filter.findByPk(filter_id)
|
||||||
|
await filter.destroy()
|
||||||
|
res.sendStatus(200)
|
||||||
|
} catch (e) {
|
||||||
|
log.error('Remove filter failed:', e)
|
||||||
|
res.sendStatus(404)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = cohortController
|
|
@ -8,7 +8,6 @@ const linkifyHtml = require('linkify-html')
|
||||||
const Sequelize = require('sequelize')
|
const Sequelize = require('sequelize')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const helpers = require('../../helpers')
|
const helpers = require('../../helpers')
|
||||||
const settingsController = require('./settings')
|
|
||||||
|
|
||||||
const Event = require('../models/event')
|
const Event = require('../models/event')
|
||||||
const Resource = require('../models/resource')
|
const Resource = require('../models/resource')
|
||||||
|
@ -23,31 +22,110 @@ const log = require('../../log')
|
||||||
|
|
||||||
const eventController = {
|
const eventController = {
|
||||||
|
|
||||||
async _getMeta () {
|
async searchMeta (req, res) {
|
||||||
|
const search = req.query.search
|
||||||
|
|
||||||
const places = await Place.findAll({
|
const places = await Place.findAll({
|
||||||
order: [[Sequelize.literal('weigth'), 'DESC']],
|
order: [[Sequelize.col('w'), 'DESC']],
|
||||||
attributes: {
|
where: {
|
||||||
include: [[Sequelize.fn('count', Sequelize.col('events.placeId')), 'weigth']],
|
[Op.or]: [
|
||||||
exclude: ['createdAt', 'updatedAt']
|
{ name: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), 'LIKE', '%' + search + '%' )},
|
||||||
|
{ address: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('address')), 'LIKE', '%' + search + '%')},
|
||||||
|
]
|
||||||
},
|
},
|
||||||
|
attributes: [['name', 'label'], 'address', 'id', [Sequelize.cast(Sequelize.fn('COUNT', Sequelize.col('events.placeId')),'INTEGER'), 'w']],
|
||||||
include: [{ model: Event, where: { is_visible: true }, required: true, attributes: [] }],
|
include: [{ model: Event, where: { is_visible: true }, required: true, attributes: [] }],
|
||||||
group: ['place.id']
|
group: ['place.id'],
|
||||||
|
raw: true
|
||||||
})
|
})
|
||||||
|
|
||||||
const tags = await Tag.findAll({
|
const tags = await Tag.findAll({
|
||||||
order: [[Sequelize.literal('w'), 'DESC']],
|
order: [[Sequelize.col('w'), 'DESC']],
|
||||||
attributes: {
|
where: {
|
||||||
include: [[Sequelize.fn('COUNT', Sequelize.col('tag.tag')), 'w']]
|
tag: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('tag')), 'LIKE', '%' + search + '%'),
|
||||||
},
|
},
|
||||||
|
attributes: [['tag','label'], [Sequelize.cast(Sequelize.fn('COUNT', Sequelize.col('tag.tag')), 'INTEGER'), 'w']],
|
||||||
include: [{ model: Event, where: { is_visible: true }, attributes: [], through: { attributes: [] }, required: true }],
|
include: [{ model: Event, where: { is_visible: true }, attributes: [], through: { attributes: [] }, required: true }],
|
||||||
group: ['tag.tag']
|
group: ['tag.tag'],
|
||||||
|
raw: true
|
||||||
})
|
})
|
||||||
|
|
||||||
return { places, tags }
|
const ret = places.map(p => {
|
||||||
|
p.type = 'place'
|
||||||
|
return p
|
||||||
|
}).concat(tags.map(t => {
|
||||||
|
t.type = 'tag'
|
||||||
|
return t
|
||||||
|
})).sort( (a, b) => b.w - a.w).slice(0, 10)
|
||||||
|
|
||||||
|
return res.json(ret)
|
||||||
},
|
},
|
||||||
|
|
||||||
async getMeta (req, res) {
|
|
||||||
res.json(await eventController._getMeta())
|
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,
|
||||||
|
|
||||||
|
// confirmed event only
|
||||||
|
is_visible: true,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!show_recurrent) {
|
||||||
|
where.parentId = null
|
||||||
|
}
|
||||||
|
|
||||||
|
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 "event_tags"."eventId"="event".id AND "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'] }
|
||||||
|
],
|
||||||
|
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
|
||||||
|
})
|
||||||
|
|
||||||
|
return res.json(ret)
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async getNotifications (event, action) {
|
async getNotifications (event, action) {
|
||||||
|
@ -75,14 +153,7 @@ const eventController = {
|
||||||
const notifications = await Notification.findAll({ where: { action }, include: [Event] })
|
const notifications = await Notification.findAll({ where: { action }, include: [Event] })
|
||||||
|
|
||||||
// get notification that matches with selected event
|
// get notification that matches with selected event
|
||||||
const ret = notifications.filter(notification => match(event, notification.filters))
|
return notifications.filter(notification => match(event, notification.filters))
|
||||||
return ret
|
|
||||||
},
|
|
||||||
|
|
||||||
async updatePlace (req, res) {
|
|
||||||
const place = await Place.findByPk(req.body.id)
|
|
||||||
await place.update(req.body)
|
|
||||||
res.json(place)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async _get(slug) {
|
async _get(slug) {
|
||||||
|
@ -290,8 +361,8 @@ const eventController = {
|
||||||
res.sendStatus(200)
|
res.sendStatus(200)
|
||||||
},
|
},
|
||||||
|
|
||||||
async isAnonEventAllowed (req, res, next) {
|
async isAnonEventAllowed (_req, res, next) {
|
||||||
if (!res.locals.settings.allow_anon_event) {
|
if (!res.locals.settings.allow_anon_event && !res.locals.user) {
|
||||||
return res.sendStatus(403)
|
return res.sendStatus(403)
|
||||||
}
|
}
|
||||||
next()
|
next()
|
||||||
|
@ -308,16 +379,33 @@ const eventController = {
|
||||||
const body = req.body
|
const body = req.body
|
||||||
const recurrent = body.recurrent ? JSON.parse(body.recurrent) : null
|
const recurrent = body.recurrent ? JSON.parse(body.recurrent) : null
|
||||||
|
|
||||||
const required_fields = [ 'title', 'place_name', 'start_datetime']
|
const required_fields = [ 'title', 'start_datetime']
|
||||||
const missing_field = required_fields.find(required_field => !body[required_field])
|
let missing_field = required_fields.find(required_field => !body[required_field])
|
||||||
if (missing_field) {
|
if (missing_field) {
|
||||||
log.warn(`${missing_field} is required`)
|
log.warn(`${missing_field} required`)
|
||||||
return res.status(400).send(`${missing_field} is required`)
|
return res.status(400).send(`${missing_field} required`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// find or create the place
|
||||||
|
let place
|
||||||
|
if (body.place_id) {
|
||||||
|
place = await Place.findByPk(body.place_id)
|
||||||
|
} else {
|
||||||
|
place = await Place.findOne({ where: { name: body.place_name.trim() }})
|
||||||
|
if (!place) {
|
||||||
|
if (!body.place_address || !body.place_name) {
|
||||||
|
return res.status(400).send(`place_id or place_name and place_address required`)
|
||||||
|
}
|
||||||
|
place = await Place.create({
|
||||||
|
name: body.place_name,
|
||||||
|
address: body.place_address
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventDetails = {
|
const eventDetails = {
|
||||||
title: body.title,
|
title: body.title,
|
||||||
// remove html tags
|
// sanitize and linkify html
|
||||||
description: helpers.sanitizeHTML(linkifyHtml(body.description || '')),
|
description: helpers.sanitizeHTML(linkifyHtml(body.description || '')),
|
||||||
multidate: body.multidate,
|
multidate: body.multidate,
|
||||||
start_datetime: body.start_datetime,
|
start_datetime: body.start_datetime,
|
||||||
|
@ -328,17 +416,16 @@ const eventController = {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.file || body.image_url) {
|
if (req.file || body.image_url) {
|
||||||
let url
|
if (!req.file && body.image_url) {
|
||||||
if (req.file) {
|
req.file = await helpers.getImageFromURL(body.image_url)
|
||||||
url = req.file.filename
|
|
||||||
} else {
|
|
||||||
url = await helpers.getImageFromURL(body.image_url)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let focalpoint = body.image_focalpoint ? body.image_focalpoint.split(',') : ['0', '0']
|
let focalpoint = body.image_focalpoint ? body.image_focalpoint.split(',') : ['0', '0']
|
||||||
focalpoint = [parseFloat(focalpoint[0]).toFixed(2), parseFloat(focalpoint[1]).toFixed(2)]
|
focalpoint = [parseFloat(focalpoint[0]).toFixed(2), parseFloat(focalpoint[1]).toFixed(2)]
|
||||||
eventDetails.media = [{
|
eventDetails.media = [{
|
||||||
url,
|
url: req.file.filename,
|
||||||
|
height: req.file.height,
|
||||||
|
width: req.file.width,
|
||||||
name: body.image_name || body.title || '',
|
name: body.image_name || body.title || '',
|
||||||
focalpoint: [parseFloat(focalpoint[0]), parseFloat(focalpoint[1])]
|
focalpoint: [parseFloat(focalpoint[0]), parseFloat(focalpoint[1])]
|
||||||
}]
|
}]
|
||||||
|
@ -346,24 +433,16 @@ const eventController = {
|
||||||
eventDetails.media = []
|
eventDetails.media = []
|
||||||
}
|
}
|
||||||
|
|
||||||
const event = await Event.create(eventDetails)
|
let event = await Event.create(eventDetails)
|
||||||
|
|
||||||
const [place] = await Place.findOrCreate({
|
|
||||||
where: { name: body.place_name },
|
|
||||||
defaults: {
|
|
||||||
address: body.place_address
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
await event.setPlace(place)
|
await event.setPlace(place)
|
||||||
event.place = place
|
|
||||||
|
|
||||||
// create/assign tags
|
// create/assign tags
|
||||||
if (body.tags) {
|
if (body.tags) {
|
||||||
|
body.tags = body.tags.map(t => t.trim())
|
||||||
await Tag.bulkCreate(body.tags.map(t => ({ tag: t })), { ignoreDuplicates: true })
|
await Tag.bulkCreate(body.tags.map(t => ({ tag: t })), { ignoreDuplicates: true })
|
||||||
const tags = await Tag.findAll({ where: { tag: { [Op.in]: body.tags } } })
|
const tags = await Tag.findAll({ where: { tag: { [Op.in]: body.tags } } })
|
||||||
await event.addTags(tags)
|
await event.addTags(tags)
|
||||||
event.tags = tags
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// associate user to event and reverse
|
// associate user to event and reverse
|
||||||
|
@ -372,6 +451,9 @@ const eventController = {
|
||||||
await event.setUser(res.locals.user)
|
await event.setUser(res.locals.user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
event = event.get()
|
||||||
|
event.tags = body.tags
|
||||||
|
event.place = place
|
||||||
// return created event to the client
|
// return created event to the client
|
||||||
res.json(event)
|
res.json(event)
|
||||||
|
|
||||||
|
@ -405,47 +487,44 @@ const eventController = {
|
||||||
|
|
||||||
const recurrent = body.recurrent ? JSON.parse(body.recurrent) : null
|
const recurrent = body.recurrent ? JSON.parse(body.recurrent) : null
|
||||||
const eventDetails = {
|
const eventDetails = {
|
||||||
title: body.title,
|
title: body.title || event.title,
|
||||||
// remove html tags
|
// sanitize and linkify html
|
||||||
description: helpers.sanitizeHTML(linkifyHtml(body.description, { target: '_blank' })),
|
description: helpers.sanitizeHTML(linkifyHtml(body.description, { target: '_blank' })) || event.description,
|
||||||
multidate: body.multidate,
|
multidate: body.multidate,
|
||||||
start_datetime: body.start_datetime,
|
start_datetime: body.start_datetime,
|
||||||
end_datetime: body.end_datetime,
|
end_datetime: body.end_datetime,
|
||||||
recurrent
|
recurrent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remove old media in case a new one is uploaded
|
||||||
if ((req.file || /^https?:\/\//.test(body.image_url)) && !event.recurrent && event.media && event.media.length) {
|
if ((req.file || /^https?:\/\//.test(body.image_url)) && !event.recurrent && event.media && event.media.length) {
|
||||||
const old_path = path.resolve(config.upload_path, event.media[0].url)
|
|
||||||
const old_thumb_path = path.resolve(config.upload_path, 'thumb', event.media[0].url)
|
|
||||||
try {
|
try {
|
||||||
|
const old_path = path.resolve(config.upload_path, event.media[0].url)
|
||||||
|
const old_thumb_path = path.resolve(config.upload_path, 'thumb', event.media[0].url)
|
||||||
fs.unlinkSync(old_path)
|
fs.unlinkSync(old_path)
|
||||||
fs.unlinkSync(old_thumb_path)
|
fs.unlinkSync(old_thumb_path)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.info(e.toString())
|
log.info(e.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let url
|
|
||||||
if (req.file) {
|
|
||||||
url = req.file.filename
|
|
||||||
} else if (body.image_url) {
|
|
||||||
if (/^https?:\/\//.test(body.image_url)) {
|
|
||||||
url = await helpers.getImageFromURL(body.image_url)
|
|
||||||
} else {
|
|
||||||
url = body.image_url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url && !event.recurrent) {
|
// modify associated media only if a new file is uploaded or remote image_url is used
|
||||||
|
if (req.file || (body.image_url && /^https?:\/\//.test(body.image_url))) {
|
||||||
|
if (body.image_url) {
|
||||||
|
req.file = await helpers.getImageFromURL(body.image_url)
|
||||||
|
}
|
||||||
|
|
||||||
const focalpoint = body.image_focalpoint ? body.image_focalpoint.split(',') : ['0', '0']
|
const focalpoint = body.image_focalpoint ? body.image_focalpoint.split(',') : ['0', '0']
|
||||||
eventDetails.media = [{
|
eventDetails.media = [{
|
||||||
url,
|
url: req.file.filename,
|
||||||
name: body.image_name || '',
|
height: req.file.height,
|
||||||
|
width: req.file.width,
|
||||||
|
name: body.image_name || body.title || '',
|
||||||
focalpoint: [parseFloat(focalpoint[0].slice(0, 6)), parseFloat(focalpoint[1].slice(0, 6))]
|
focalpoint: [parseFloat(focalpoint[0].slice(0, 6)), parseFloat(focalpoint[1].slice(0, 6))]
|
||||||
}]
|
}]
|
||||||
} else {
|
} else if (!body.image) {
|
||||||
eventDetails.media = []
|
eventDetails.media = []
|
||||||
}
|
}
|
||||||
|
|
||||||
await event.update(eventDetails)
|
await event.update(eventDetails)
|
||||||
const [place] = await Place.findOrCreate({
|
const [place] = await Place.findOrCreate({
|
||||||
where: { name: body.place_name },
|
where: { name: body.place_name },
|
||||||
|
@ -481,9 +560,9 @@ const eventController = {
|
||||||
// check if event is mine (or user is admin)
|
// check if event is mine (or user is admin)
|
||||||
if (event && (res.locals.user.is_admin || res.locals.user.id === event.userId)) {
|
if (event && (res.locals.user.is_admin || res.locals.user.id === event.userId)) {
|
||||||
if (event.media && event.media.length && !event.recurrent) {
|
if (event.media && event.media.length && !event.recurrent) {
|
||||||
const old_path = path.join(config.upload_path, event.media[0].url)
|
|
||||||
const old_thumb_path = path.join(config.upload_path, 'thumb', event.media[0].url)
|
|
||||||
try {
|
try {
|
||||||
|
const old_path = path.join(config.upload_path, event.media[0].url)
|
||||||
|
const old_thumb_path = path.join(config.upload_path, 'thumb', event.media[0].url)
|
||||||
fs.unlinkSync(old_thumb_path)
|
fs.unlinkSync(old_thumb_path)
|
||||||
fs.unlinkSync(old_path)
|
fs.unlinkSync(old_path)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -523,22 +602,22 @@ const eventController = {
|
||||||
if (!show_recurrent) {
|
if (!show_recurrent) {
|
||||||
where.parentId = null
|
where.parentId = null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (end) {
|
if (end) {
|
||||||
where.start_datetime = { [Op.lte]: end }
|
where.start_datetime = { [Op.lte]: end }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const replacements = []
|
||||||
if (tags && places) {
|
if (tags && places) {
|
||||||
where[Op.or] = {
|
where[Op.or] = {
|
||||||
placeId: places ? places.split(',') : [],
|
placeId: places ? places.split(',') : [],
|
||||||
'$tags.tag$': tags.split(',')
|
// '$tags.tag$': Sequelize.literal(`EXISTS (SELECT 1 FROM event_tags WHERE tagTag in ( ${Sequelize.QueryInterface.escape(tags)} ) )`)
|
||||||
}
|
}
|
||||||
}
|
} else if (tags) {
|
||||||
|
// where[Op.and] = Sequelize.literal(`EXISTS (SELECT 1 FROM event_tags WHERE eventId=event.id AND tagTag in (?))`)
|
||||||
if (tags) {
|
where[Op.and] = Sequelize.fn('EXISTS', Sequelize.literal('SELECT 1 FROM event_tags WHERE "event_tags"."eventId"="event".id AND "tagTag" in (?)'))
|
||||||
where['$tags.tag$'] = tags.split(',')
|
replacements.push(tags)
|
||||||
}
|
} else if (places) {
|
||||||
|
|
||||||
if (places) {
|
|
||||||
where.placeId = places.split(',')
|
where.placeId = places.split(',')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -554,12 +633,12 @@ const eventController = {
|
||||||
model: Tag,
|
model: Tag,
|
||||||
order: [Sequelize.literal('(SELECT COUNT("tagTag") FROM event_tags WHERE tagTag = tag) DESC')],
|
order: [Sequelize.literal('(SELECT COUNT("tagTag") FROM event_tags WHERE tagTag = tag) DESC')],
|
||||||
attributes: ['tag'],
|
attributes: ['tag'],
|
||||||
required: !!tags,
|
|
||||||
through: { attributes: [] }
|
through: { attributes: [] }
|
||||||
},
|
},
|
||||||
{ model: Place, required: true, attributes: ['id', 'name', 'address'] }
|
{ model: Place, required: true, attributes: ['id', 'name', 'address'] }
|
||||||
],
|
],
|
||||||
limit: max
|
limit: max,
|
||||||
|
replacements
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
log.error('[EVENT]', e)
|
log.error('[EVENT]', e)
|
||||||
return []
|
return []
|
||||||
|
|
|
@ -2,6 +2,7 @@ const Event = require('../models/event')
|
||||||
const Place = require('../models/place')
|
const Place = require('../models/place')
|
||||||
const Tag = require('../models/tag')
|
const Tag = require('../models/tag')
|
||||||
|
|
||||||
|
const { htmlToText } = require('html-to-text')
|
||||||
const { Op, literal } = require('sequelize')
|
const { Op, literal } = require('sequelize')
|
||||||
const moment = require('dayjs')
|
const moment = require('dayjs')
|
||||||
const ics = require('ics')
|
const ics = require('ics')
|
||||||
|
@ -68,7 +69,7 @@ const exportController = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
feed (req, res, events) {
|
feed (_req, res, events) {
|
||||||
const settings = res.locals.settings
|
const settings = res.locals.settings
|
||||||
res.type('application/rss+xml; charset=UTF-8')
|
res.type('application/rss+xml; charset=UTF-8')
|
||||||
res.render('feed/rss.pug', { events, settings, moment })
|
res.render('feed/rss.pug', { events, settings, moment })
|
||||||
|
@ -79,7 +80,7 @@ const exportController = {
|
||||||
* @param {*} events array of events from sequelize
|
* @param {*} events array of events from sequelize
|
||||||
* @param {*} alarms https://github.com/adamgibbons/ics#attributes (alarms)
|
* @param {*} alarms https://github.com/adamgibbons/ics#attributes (alarms)
|
||||||
*/
|
*/
|
||||||
ics (req, res, events, alarms = []) {
|
ics (_req, res, events, alarms = []) {
|
||||||
const settings = res.locals.settings
|
const settings = res.locals.settings
|
||||||
const eventsMap = events.map(e => {
|
const eventsMap = events.map(e => {
|
||||||
const tmpStart = moment.unix(e.start_datetime)
|
const tmpStart = moment.unix(e.start_datetime)
|
||||||
|
@ -88,13 +89,14 @@ const exportController = {
|
||||||
const end = tmpEnd.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 {
|
return {
|
||||||
start,
|
start,
|
||||||
// startOutputType: 'utc',
|
|
||||||
end,
|
end,
|
||||||
// endOutputType: 'utc',
|
|
||||||
title: `[${settings.title}] ${e.title}`,
|
title: `[${settings.title}] ${e.title}`,
|
||||||
description: e.description,
|
description: htmlToText(e.description),
|
||||||
|
htmlContent: e.description,
|
||||||
location: `${e.place.name} - ${e.place.address}`,
|
location: `${e.place.name} - ${e.place.address}`,
|
||||||
url: `${settings.baseurl}/event/${e.slug || e.id}`,
|
url: `${settings.baseurl}/event/${e.slug || e.id}`,
|
||||||
|
status: 'CONFIRMED',
|
||||||
|
categories: e.tags.map(t => t.tag),
|
||||||
alarms
|
alarms
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -137,8 +137,7 @@ const oauthController = {
|
||||||
code.userId = user.id
|
code.userId = user.id
|
||||||
code.clientId = client.id
|
code.clientId = client.id
|
||||||
code.expiresAt = dayjs(code.expiresAt).toDate()
|
code.expiresAt = dayjs(code.expiresAt).toDate()
|
||||||
const ret = await OAuthCode.create(code)
|
return OAuthCode.create(code)
|
||||||
return ret
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
|
|
59
server/api/controller/place.js
Normal file
59
server/api/controller/place.js
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
const dayjs = require('dayjs')
|
||||||
|
const Place = require('../models/place')
|
||||||
|
const Event = require('../models/event')
|
||||||
|
const eventController = require('./event')
|
||||||
|
const log = require('../../log')
|
||||||
|
const { Op, where, col, fn, cast } = require('sequelize')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
async getEvents (req, res) {
|
||||||
|
const name = req.params.placeName
|
||||||
|
const place = await Place.findOne({ where: { name }})
|
||||||
|
if (!place) {
|
||||||
|
log.warn(`Place ${name} not found`)
|
||||||
|
return res.sendStatus(404)
|
||||||
|
}
|
||||||
|
const start = dayjs().unix()
|
||||||
|
const events = await eventController._select({ start, places: `${place.id}`, show_recurrent: true})
|
||||||
|
|
||||||
|
return res.json({ events, place })
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
async updatePlace (req, res) {
|
||||||
|
const place = await Place.findByPk(req.body.id)
|
||||||
|
await place.update(req.body)
|
||||||
|
res.json(place)
|
||||||
|
},
|
||||||
|
|
||||||
|
async getAll (_req, res) {
|
||||||
|
const places = await Place.findAll({
|
||||||
|
order: [[cast(fn('COUNT', col('events.placeId')),'INTEGER'), 'DESC']],
|
||||||
|
include: [{ model: Event, where: { is_visible: true }, required: true, attributes: [] }],
|
||||||
|
group: ['place.id'],
|
||||||
|
raw: true
|
||||||
|
})
|
||||||
|
return res.json(places)
|
||||||
|
},
|
||||||
|
|
||||||
|
async get (req, res) {
|
||||||
|
const search = req.query.search.toLocaleLowerCase()
|
||||||
|
const places = await Place.findAll({
|
||||||
|
order: [[cast(fn('COUNT', col('events.placeId')),'INTEGER'), 'DESC']],
|
||||||
|
where: {
|
||||||
|
[Op.or]: [
|
||||||
|
{ name: where(fn('LOWER', col('name')), 'LIKE', '%' + search + '%' )},
|
||||||
|
{ address: where(fn('LOWER', col('address')), 'LIKE', '%' + search + '%')},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
attributes: ['name', 'address', 'id'],
|
||||||
|
include: [{ model: Event, where: { is_visible: true }, required: true, attributes: [] }],
|
||||||
|
group: ['place.id'],
|
||||||
|
raw: true
|
||||||
|
})
|
||||||
|
|
||||||
|
// TOFIX: don't know why limit does not work
|
||||||
|
return res.json(places.slice(0, 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -5,7 +5,6 @@ const crypto = require('crypto')
|
||||||
const { promisify } = require('util')
|
const { promisify } = require('util')
|
||||||
const sharp = require('sharp')
|
const sharp = require('sharp')
|
||||||
const config = require('../../config')
|
const config = require('../../config')
|
||||||
const pkg = require('../../../package.json')
|
|
||||||
const generateKeyPair = promisify(crypto.generateKeyPair)
|
const generateKeyPair = promisify(crypto.generateKeyPair)
|
||||||
const log = require('../../log')
|
const log = require('../../log')
|
||||||
const locales = require('../../../locales/index')
|
const locales = require('../../../locales/index')
|
||||||
|
@ -42,7 +41,7 @@ const defaultSettings = {
|
||||||
{ href: '/about', label: 'about' }
|
{ href: '/about', label: 'about' }
|
||||||
],
|
],
|
||||||
admin_email: config.admin_email || '',
|
admin_email: config.admin_email || '',
|
||||||
smtp: config.smtp || false
|
smtp: config.smtp || {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -185,7 +184,7 @@ const settingsController = {
|
||||||
return sharp(uploadedPath)
|
return sharp(uploadedPath)
|
||||||
.resize(400)
|
.resize(400)
|
||||||
.png({ quality: 90 })
|
.png({ quality: 90 })
|
||||||
.toFile(baseImgPath + '.png', (err, info) => {
|
.toFile(baseImgPath + '.png', (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
log.error('[LOGO] ' + err)
|
log.error('[LOGO] ' + err)
|
||||||
}
|
}
|
||||||
|
|
47
server/api/controller/tag.js
Normal file
47
server/api/controller/tag.js
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
const dayjs = require('dayjs')
|
||||||
|
const Tag = require('../models/tag')
|
||||||
|
const Event = require('../models/event')
|
||||||
|
const eventController = require('./event')
|
||||||
|
const Sequelize = require('sequelize')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
// async getEvents (req, res) {
|
||||||
|
// const name = req.params.placeName
|
||||||
|
// const place = await Place.findOne({ where: { name }})
|
||||||
|
// if (!place) {
|
||||||
|
// log.warn(`Place ${name} not found`)
|
||||||
|
// return res.sendStatus(404)
|
||||||
|
// }
|
||||||
|
// const start = dayjs().unix()
|
||||||
|
// const events = await eventController._select({ start, places: `${place.id}`, show_recurrent: true})
|
||||||
|
|
||||||
|
// return res.json({ events, place })
|
||||||
|
// },
|
||||||
|
|
||||||
|
async get (req, res) {
|
||||||
|
const search = req.query.search
|
||||||
|
console.error(search)
|
||||||
|
const tags = await Tag.findAll({
|
||||||
|
order: [[Sequelize.fn('COUNT', Sequelize.col('tag.tag')), 'DESC']],
|
||||||
|
attributes: ['tag'],
|
||||||
|
where: {
|
||||||
|
tag: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('tag')), 'LIKE', '%' + search + '%'),
|
||||||
|
},
|
||||||
|
include: [{ model: Event, where: { is_visible: true }, attributes: [], through: { attributes: [] }, required: true }],
|
||||||
|
group: ['tag.tag'],
|
||||||
|
limit: 10,
|
||||||
|
subQuery:false
|
||||||
|
})
|
||||||
|
|
||||||
|
return res.json(tags.map(t => t.tag))
|
||||||
|
}
|
||||||
|
|
||||||
|
// async getPlaces (req, res) {
|
||||||
|
// const search = req.params.search
|
||||||
|
// const places = await Place.findAll({ where: {
|
||||||
|
// [Op.or]: [
|
||||||
|
// { name: }
|
||||||
|
// ]
|
||||||
|
// }})
|
||||||
|
// }
|
||||||
|
}
|
|
@ -123,7 +123,12 @@ const userController = {
|
||||||
|
|
||||||
async remove (req, res) {
|
async remove (req, res) {
|
||||||
try {
|
try {
|
||||||
const user = await User.findByPk(req.params.id)
|
let user
|
||||||
|
if (res.locals.user.is_admin && req.params.id) {
|
||||||
|
user = await User.findByPk(req.params.id)
|
||||||
|
} else {
|
||||||
|
user = await User.findByPk(res.locals.user.id)
|
||||||
|
}
|
||||||
await user.destroy()
|
await user.destroy()
|
||||||
log.warn(`User ${user.email} removed!`)
|
log.warn(`User ${user.email} removed!`)
|
||||||
res.sendStatus(200)
|
res.sendStatus(200)
|
||||||
|
|
|
@ -23,6 +23,8 @@ if (config.status !== 'READY') {
|
||||||
|
|
||||||
const { isAuth, isAdmin } = require('./auth')
|
const { isAuth, isAdmin } = require('./auth')
|
||||||
const eventController = require('./controller/event')
|
const eventController = require('./controller/event')
|
||||||
|
const placeController = require('./controller/place')
|
||||||
|
const tagController = require('./controller/tag')
|
||||||
const settingsController = require('./controller/settings')
|
const settingsController = require('./controller/settings')
|
||||||
const exportController = require('./controller/export')
|
const exportController = require('./controller/export')
|
||||||
const userController = require('./controller/user')
|
const userController = require('./controller/user')
|
||||||
|
@ -31,6 +33,7 @@ if (config.status !== 'READY') {
|
||||||
const resourceController = require('./controller/resource')
|
const resourceController = require('./controller/resource')
|
||||||
const oauthController = require('./controller/oauth')
|
const oauthController = require('./controller/oauth')
|
||||||
const announceController = require('./controller/announce')
|
const announceController = require('./controller/announce')
|
||||||
|
const cohortController = require('./controller/cohort')
|
||||||
const helpers = require('../helpers')
|
const helpers = require('../helpers')
|
||||||
const storage = require('./storage')
|
const storage = require('./storage')
|
||||||
const upload = multer({ storage })
|
const upload = multer({ storage })
|
||||||
|
@ -72,14 +75,11 @@ if (config.status !== 'READY') {
|
||||||
|
|
||||||
// delete user
|
// delete user
|
||||||
api.delete('/user/:id', isAdmin, userController.remove)
|
api.delete('/user/:id', isAdmin, userController.remove)
|
||||||
api.delete('/user', isAdmin, userController.remove)
|
api.delete('/user', isAuth, userController.remove)
|
||||||
|
|
||||||
// get all users
|
// get all users
|
||||||
api.get('/users', isAdmin, userController.getAll)
|
api.get('/users', isAdmin, userController.getAll)
|
||||||
|
|
||||||
// update a place (modify address..)
|
|
||||||
api.put('/place', isAdmin, eventController.updatePlace)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get events
|
* Get events
|
||||||
* @category Event
|
* @category Event
|
||||||
|
@ -120,6 +120,8 @@ if (config.status !== 'READY') {
|
||||||
// allow anyone to add an event (anon event has to be confirmed, TODO: flood protection)
|
// 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, upload.single('image'), eventController.add)
|
||||||
|
|
||||||
|
api.get('/event/search', eventController.search)
|
||||||
|
|
||||||
api.put('/event', isAuth, upload.single('image'), eventController.update)
|
api.put('/event', isAuth, upload.single('image'), eventController.update)
|
||||||
api.get('/event/import', isAuth, helpers.importURL)
|
api.get('/event/import', isAuth, helpers.importURL)
|
||||||
|
|
||||||
|
@ -127,7 +129,7 @@ if (config.status !== 'READY') {
|
||||||
api.delete('/event/:id', isAuth, eventController.remove)
|
api.delete('/event/:id', isAuth, eventController.remove)
|
||||||
|
|
||||||
// get tags/places
|
// get tags/places
|
||||||
api.get('/event/meta', eventController.getMeta)
|
api.get('/event/meta', eventController.searchMeta)
|
||||||
|
|
||||||
// get unconfirmed events
|
// get unconfirmed events
|
||||||
api.get('/event/unconfirmed', isAdmin, eventController.getUnconfirmed)
|
api.get('/event/unconfirmed', isAdmin, eventController.getUnconfirmed)
|
||||||
|
@ -150,6 +152,15 @@ if (config.status !== 'READY') {
|
||||||
// export events (rss/ics)
|
// export events (rss/ics)
|
||||||
api.get('/export/:type', cors, exportController.export)
|
api.get('/export/:type', cors, exportController.export)
|
||||||
|
|
||||||
|
|
||||||
|
api.get('/place/:placeName/events', cors, placeController.getEvents)
|
||||||
|
api.get('/place/all', isAdmin, placeController.getAll)
|
||||||
|
api.get('/place', cors, placeController.get)
|
||||||
|
api.put('/place', isAdmin, placeController.updatePlace)
|
||||||
|
|
||||||
|
api.get('/tag', cors, tagController.get)
|
||||||
|
|
||||||
|
// - FEDIVERSE INSTANCES, MODERATION, RESOURCES
|
||||||
api.get('/instances', isAdmin, instanceController.getAll)
|
api.get('/instances', isAdmin, instanceController.getAll)
|
||||||
api.get('/instances/:instance_domain', isAdmin, instanceController.get)
|
api.get('/instances/:instance_domain', isAdmin, instanceController.get)
|
||||||
api.post('/instances/toggle_block', isAdmin, instanceController.toggleBlock)
|
api.post('/instances/toggle_block', isAdmin, instanceController.toggleBlock)
|
||||||
|
@ -164,16 +175,25 @@ if (config.status !== 'READY') {
|
||||||
api.put('/announcements/:announce_id', isAdmin, announceController.update)
|
api.put('/announcements/:announce_id', isAdmin, announceController.update)
|
||||||
api.delete('/announcements/:announce_id', isAdmin, announceController.remove)
|
api.delete('/announcements/:announce_id', isAdmin, announceController.remove)
|
||||||
|
|
||||||
|
// - COHORT
|
||||||
|
api.get('/cohorts/:name', cohortController.getEvents)
|
||||||
|
api.get('/cohorts', cohortController.getAll)
|
||||||
|
api.post('/cohorts', isAdmin, cohortController.add)
|
||||||
|
api.delete('/cohort/:id', isAdmin, cohortController.remove)
|
||||||
|
api.get('/filter/:cohort_id', isAdmin, cohortController.getFilters)
|
||||||
|
api.post('/filter', isAdmin, cohortController.addFilter)
|
||||||
|
api.delete('/filter/:id', isAdmin, cohortController.removeFilter)
|
||||||
|
|
||||||
// OAUTH
|
// OAUTH
|
||||||
api.get('/clients', isAuth, oauthController.getClients)
|
api.get('/clients', isAuth, oauthController.getClients)
|
||||||
api.get('/client/:client_id', isAuth, oauthController.getClient)
|
api.get('/client/:client_id', isAuth, oauthController.getClient)
|
||||||
api.post('/client', oauthController.createClient)
|
api.post('/client', oauthController.createClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
api.use((req, res) => res.sendStatus(404))
|
api.use((_req, res) => res.sendStatus(404))
|
||||||
|
|
||||||
// Handle 500
|
// Handle 500
|
||||||
api.use((error, req, res, next) => {
|
api.use((error, _req, res, _next) => {
|
||||||
log.error('[API ERROR]', error)
|
log.error('[API ERROR]', error)
|
||||||
res.status(500).send('500: Internal Server Error')
|
res.status(500).send('500: Internal Server Error')
|
||||||
})
|
})
|
||||||
|
|
|
@ -9,7 +9,7 @@ const locales = require('../../locales')
|
||||||
const mail = {
|
const mail = {
|
||||||
send (addresses, template, locals, locale) {
|
send (addresses, template, locals, locale) {
|
||||||
locale = locale || settingsController.settings.instance_locale
|
locale = locale || settingsController.settings.instance_locale
|
||||||
if (process.env.NODE_ENV === 'production' && (!settingsController.settings.admin_email || !settingsController.settings.smtp)) {
|
if (process.env.NODE_ENV === 'production' && (!settingsController.settings.admin_email || !settingsController.settings.smtp || !settingsController.settings.smtp.user)) {
|
||||||
log.error(`Cannot send any email: SMTP Email configuration not completed!`)
|
log.error(`Cannot send any email: SMTP Email configuration not completed!`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
27
server/api/models/cohort.js
Normal file
27
server/api/models/cohort.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
const { Model, DataTypes } = require('sequelize')
|
||||||
|
const sequelize = require('./index').sequelize
|
||||||
|
|
||||||
|
class Cohort extends Model {}
|
||||||
|
|
||||||
|
Cohort.init({
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
unique: true,
|
||||||
|
index: true,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
isActor: {
|
||||||
|
type: DataTypes.BOOLEAN
|
||||||
|
},
|
||||||
|
isTop: {
|
||||||
|
type: DataTypes.BOOLEAN
|
||||||
|
}
|
||||||
|
}, { sequelize, modelName: 'cohort', timestamps: false })
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = Cohort
|
|
@ -1,5 +1,4 @@
|
||||||
const config = require('../../config')
|
const config = require('../../config')
|
||||||
const moment = require('dayjs')
|
|
||||||
const { htmlToText } = require('html-to-text')
|
const { htmlToText } = require('html-to-text')
|
||||||
|
|
||||||
const { Model, DataTypes } = require('sequelize')
|
const { Model, DataTypes } = require('sequelize')
|
||||||
|
@ -14,9 +13,12 @@ const Place = require('./place')
|
||||||
const User = require('./user')
|
const User = require('./user')
|
||||||
const Tag = require('./tag')
|
const Tag = require('./tag')
|
||||||
|
|
||||||
const utc = require('dayjs/plugin/utc')
|
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
|
const utc = require('dayjs/plugin/utc')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
dayjs.extend(timezone)
|
||||||
|
|
||||||
class Event extends Model {}
|
class Event extends Model {}
|
||||||
|
|
||||||
|
@ -76,7 +78,7 @@ Event.prototype.toAP = function (username, locale, to = []) {
|
||||||
const plainDescription = htmlToText(this.description && this.description.replace('\n', '').slice(0, 1000))
|
const plainDescription = htmlToText(this.description && this.description.replace('\n', '').slice(0, 1000))
|
||||||
const content = `
|
const content = `
|
||||||
📍 ${this.place && this.place.name}
|
📍 ${this.place && this.place.name}
|
||||||
📅 ${moment.unix(this.start_datetime).locale(locale).format('dddd, D MMMM (HH:mm)')}
|
📅 ${dayjs.unix(this.start_datetime).tz().locale(locale).format('dddd, D MMMM (HH:mm)')}
|
||||||
|
|
||||||
${plainDescription}
|
${plainDescription}
|
||||||
`
|
`
|
||||||
|
@ -99,8 +101,8 @@ Event.prototype.toAP = function (username, locale, to = []) {
|
||||||
name: this.title,
|
name: this.title,
|
||||||
url: `${config.baseurl}/event/${this.slug || this.id}`,
|
url: `${config.baseurl}/event/${this.slug || this.id}`,
|
||||||
type: 'Event',
|
type: 'Event',
|
||||||
startTime: moment.unix(this.start_datetime).locale(locale).format(),
|
startTime: dayjs.unix(this.start_datetime).tz().locale(locale).format(),
|
||||||
endTime: this.end_datetime ? moment.unix(this.end_datetime).locale(locale).format() : null,
|
endTime: this.end_datetime ? dayjs.unix(this.end_datetime).tz().locale(locale).format() : null,
|
||||||
location: {
|
location: {
|
||||||
name: this.place.name,
|
name: this.place.name,
|
||||||
address: this.place.address
|
address: this.place.address
|
||||||
|
|
24
server/api/models/filter.js
Normal file
24
server/api/models/filter.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
const { Model, DataTypes } = require('sequelize')
|
||||||
|
const Cohort = require('./cohort')
|
||||||
|
const sequelize = require('./index').sequelize
|
||||||
|
|
||||||
|
class Filter extends Model {}
|
||||||
|
|
||||||
|
Filter.init({
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
primaryKey: true,
|
||||||
|
autoIncrement: true,
|
||||||
|
},
|
||||||
|
tags: {
|
||||||
|
type: DataTypes.JSON,
|
||||||
|
},
|
||||||
|
places: {
|
||||||
|
type: DataTypes.JSON,
|
||||||
|
}
|
||||||
|
}, { sequelize, modelName: 'filter', timestamps: false })
|
||||||
|
|
||||||
|
Filter.belongsTo(Cohort)
|
||||||
|
Cohort.hasMany(Filter)
|
||||||
|
|
||||||
|
module.exports = Filter
|
|
@ -15,6 +15,17 @@ const db = {
|
||||||
connect (dbConf = config.db) {
|
connect (dbConf = config.db) {
|
||||||
log.debug(`Connecting to DB: ${JSON.stringify(dbConf)}`)
|
log.debug(`Connecting to DB: ${JSON.stringify(dbConf)}`)
|
||||||
dbConf.dialectOptions = { autoJsonMap: false }
|
dbConf.dialectOptions = { autoJsonMap: false }
|
||||||
|
if (dbConf.dialect === 'sqlite') {
|
||||||
|
dbConf.retry = {
|
||||||
|
match: [
|
||||||
|
Sequelize.ConnectionError,
|
||||||
|
Sequelize.ConnectionTimedOutError,
|
||||||
|
Sequelize.TimeoutError,
|
||||||
|
/Deadlock/i,
|
||||||
|
/SQLITE_BUSY/],
|
||||||
|
max: 15
|
||||||
|
}
|
||||||
|
}
|
||||||
db.sequelize = new Sequelize(dbConf)
|
db.sequelize = new Sequelize(dbConf)
|
||||||
return db.sequelize.authenticate()
|
return db.sequelize.authenticate()
|
||||||
},
|
},
|
||||||
|
@ -39,7 +50,7 @@ const db = {
|
||||||
path: path.resolve(__dirname, '..', '..', 'migrations')
|
path: path.resolve(__dirname, '..', '..', 'migrations')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return await umzug.up()
|
return umzug.up()
|
||||||
},
|
},
|
||||||
async initialize () {
|
async initialize () {
|
||||||
if (config.status === 'READY') {
|
if (config.status === 'READY') {
|
||||||
|
|
|
@ -12,48 +12,33 @@ try {
|
||||||
|
|
||||||
const DiskStorage = {
|
const DiskStorage = {
|
||||||
_handleFile (req, file, cb) {
|
_handleFile (req, file, cb) {
|
||||||
const filename = crypto.randomBytes(16).toString('hex') + '.jpg'
|
const filename = crypto.randomBytes(16).toString('hex')
|
||||||
const finalPath = path.resolve(config.upload_path, filename)
|
const sharpStream = sharp({ failOnError: true })
|
||||||
const thumbPath = path.resolve(config.upload_path, 'thumb', filename)
|
const promises = [
|
||||||
const outStream = fs.createWriteStream(finalPath)
|
sharpStream.clone().resize(500, null, { withoutEnlargement: true }).jpeg({ mozjpeg: true, progressive: true }).toFile(path.resolve(config.upload_path, 'thumb', filename + '.jpg')),
|
||||||
const thumbStream = fs.createWriteStream(thumbPath)
|
sharpStream.clone().resize(1200, null, { withoutEnlargement: true } ).jpeg({ quality: 95, mozjpeg: true, progressive: true }).toFile(path.resolve(config.upload_path, filename + '.jpg')),
|
||||||
|
]
|
||||||
|
|
||||||
const resizer = sharp().resize(1200).jpeg({ quality: 98 })
|
file.stream.pipe(sharpStream)
|
||||||
const thumbnailer = sharp().resize(500).jpeg({ quality: 98 })
|
Promise.all(promises)
|
||||||
let onError = false
|
.then(res => {
|
||||||
const err = e => {
|
const info = res[1]
|
||||||
if (onError) {
|
cb(null, {
|
||||||
log.error('[UPLOAD]', err)
|
destination: config.upload_path,
|
||||||
return
|
filename: filename + '.jpg',
|
||||||
}
|
path: path.resolve(config.upload_path, filename + '.jpg'),
|
||||||
onError = true
|
height: info.height,
|
||||||
log.error('[UPLOAD]', e)
|
width: info.width,
|
||||||
req.err = e
|
size: info.size,
|
||||||
cb(null)
|
})
|
||||||
}
|
})
|
||||||
|
.catch(err => {
|
||||||
file.stream
|
console.error(err)
|
||||||
.pipe(thumbnailer)
|
req.err = err
|
||||||
.on('error', err)
|
cb(null)
|
||||||
.pipe(thumbStream)
|
|
||||||
.on('error', err)
|
|
||||||
|
|
||||||
file.stream
|
|
||||||
.pipe(resizer)
|
|
||||||
.on('error', err)
|
|
||||||
.pipe(outStream)
|
|
||||||
.on('error', err)
|
|
||||||
|
|
||||||
outStream.on('finish', () => {
|
|
||||||
cb(null, {
|
|
||||||
destination: config.upload_path,
|
|
||||||
filename,
|
|
||||||
path: finalPath,
|
|
||||||
size: outStream.bytesWritten
|
|
||||||
})
|
})
|
||||||
})
|
|
||||||
},
|
},
|
||||||
_removeFile (req, file, cb) {
|
_removeFile (_req, file, cb) {
|
||||||
delete file.destination
|
delete file.destination
|
||||||
delete file.filename
|
delete file.filename
|
||||||
fs.unlink(file.path, cb)
|
fs.unlink(file.path, cb)
|
||||||
|
|
|
@ -22,16 +22,17 @@ require('yargs')
|
||||||
.option('config', {
|
.option('config', {
|
||||||
alias: 'c',
|
alias: 'c',
|
||||||
describe: 'Configuration file',
|
describe: 'Configuration file',
|
||||||
default: path.resolve(process.env.cwd, 'config.json')
|
default: path.resolve(process.env.cwd, 'config.json'),
|
||||||
})
|
coerce: config_path => {
|
||||||
.coerce('config', config_path => {
|
const absolute_config_path = path.resolve(process.env.cwd, config_path)
|
||||||
const absolute_config_path = path.resolve(process.env.cwd, config_path)
|
process.env.config_path = absolute_config_path
|
||||||
process.env.config_path = absolute_config_path
|
return absolute_config_path
|
||||||
return absolute_config_path
|
}})
|
||||||
})
|
|
||||||
.command(['accounts'], 'Manage accounts', accountsCLI)
|
|
||||||
.command(['start', 'run', '$0'], 'Start gancio', {}, start)
|
.command(['start', 'run', '$0'], 'Start gancio', {}, start)
|
||||||
|
.command(['accounts'], 'Manage accounts', accountsCLI)
|
||||||
.help('h')
|
.help('h')
|
||||||
.alias('h', 'help')
|
.alias('h', 'help')
|
||||||
.epilog('Made with ❤ by underscore hacklab - https://gancio.org')
|
.epilog('Made with ❤ by underscore hacklab - https://gancio.org')
|
||||||
|
.recommendCommands()
|
||||||
|
.demandCommand(1, '')
|
||||||
.argv
|
.argv
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
let db
|
||||||
function _initializeDB () {
|
function _initializeDB () {
|
||||||
const config = require('../config')
|
const config = require('../config')
|
||||||
config.load()
|
config.load()
|
||||||
config.log_level = 'error'
|
config.log_level = 'error'
|
||||||
const db = require('../api/models/index')
|
db = require('../api/models/index')
|
||||||
return db.initialize()
|
return db.initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +26,30 @@ async function modify (args) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function add (args) {
|
async function create (args) {
|
||||||
|
await _initializeDB()
|
||||||
|
const User = require('../api/models/user')
|
||||||
|
console.error(args)
|
||||||
|
const user = await User.create({
|
||||||
|
email: args.email,
|
||||||
|
is_active: true,
|
||||||
|
is_admin: args.admin || false
|
||||||
|
})
|
||||||
|
console.error(user)
|
||||||
|
await db.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function remove (args) {
|
||||||
|
await _initializeDB()
|
||||||
|
const User = require('../api/models/user')
|
||||||
|
const user = await User.findOne({
|
||||||
|
where: { email: args.email }
|
||||||
|
})
|
||||||
|
if (user) {
|
||||||
|
await user.destroy()
|
||||||
|
}
|
||||||
|
await db.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function list () {
|
async function list () {
|
||||||
|
@ -33,22 +57,31 @@ async function list () {
|
||||||
const User = require('../api/models/user')
|
const User = require('../api/models/user')
|
||||||
const users = await User.findAll()
|
const users = await User.findAll()
|
||||||
console.log()
|
console.log()
|
||||||
users.forEach(u => console.log(`${u.id}\tadmin: ${u.is_admin}\tenabled: ${u.is_active}\temail: ${u.email} - ${u.password}`))
|
users.forEach(u => console.log(`${u.id}\tadmin: ${u.is_admin}\tenabled: ${u.is_active}\temail: ${u.email}`))
|
||||||
console.log()
|
console.log()
|
||||||
|
await db.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
const accountsCLI = yargs => {
|
const accountsCLI = yargs => yargs
|
||||||
return yargs
|
|
||||||
.command('list', 'List all accounts', list)
|
.command('list', 'List all accounts', list)
|
||||||
.command('modify', 'Modify', {
|
.command('modify', 'Modify', {
|
||||||
account: {
|
account: {
|
||||||
describe: 'Account to modify'
|
describe: 'Account to modify',
|
||||||
|
type: 'string',
|
||||||
|
demandOption: true
|
||||||
},
|
},
|
||||||
'reset-password': {
|
'reset-password': {
|
||||||
describe: 'Resets the password of the given accoun '
|
describe: 'Resets the password of the given account ',
|
||||||
|
type: 'boolean'
|
||||||
}
|
}
|
||||||
}, modify)
|
}, modify)
|
||||||
.command('add', 'Add an account', {}, add)
|
.command('create <email|username>', 'Create an account', {
|
||||||
}
|
admin: { describe: 'Define this account as administrator', type: 'boolean' }
|
||||||
|
}, create)
|
||||||
|
.positional('email', { describe: '', type: 'string', demandOption: true })
|
||||||
|
.command('remove <email|username>', 'Remove an account', {}, remove)
|
||||||
|
.recommendCommands()
|
||||||
|
.demandCommand(1, '')
|
||||||
|
.argv
|
||||||
|
|
||||||
module.exports = accountsCLI
|
module.exports = accountsCLI
|
|
@ -7,8 +7,8 @@ let config = {
|
||||||
baseurl: '',
|
baseurl: '',
|
||||||
hostname: '',
|
hostname: '',
|
||||||
server: {
|
server: {
|
||||||
host: '0.0.0.0',
|
host: process.env.GANCIO_HOST || '0.0.0.0',
|
||||||
port: 13120
|
port: process.env.GANCIO_PORT || 13120
|
||||||
},
|
},
|
||||||
log_level: 'debug',
|
log_level: 'debug',
|
||||||
log_path: path.resolve(process.env.cwd || '', 'logs'),
|
log_path: path.resolve(process.env.cwd || '', 'logs'),
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
// needed by sequelize
|
// needed by sequelize
|
||||||
const config = require('./config')
|
const config = require('./config')
|
||||||
|
config.load()
|
||||||
module.exports = config.db
|
module.exports = config.db
|
||||||
|
|
|
@ -20,7 +20,7 @@ const log = require('../log')
|
||||||
router.use(cors())
|
router.use(cors())
|
||||||
|
|
||||||
// is federation enabled? middleware
|
// is federation enabled? middleware
|
||||||
router.use((req, res, next) => {
|
router.use((_req, res, next) => {
|
||||||
if (settingsController.settings.enable_federation) { return next() }
|
if (settingsController.settings.enable_federation) { return next() }
|
||||||
log.debug('Federation disabled!')
|
log.debug('Federation disabled!')
|
||||||
return res.status(401).send('Federation disabled')
|
return res.status(401).send('Federation disabled')
|
||||||
|
@ -29,14 +29,20 @@ router.use((req, res, next) => {
|
||||||
router.use(express.json({ type: ['application/json', 'application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'] }))
|
router.use(express.json({ type: ['application/json', 'application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'] }))
|
||||||
|
|
||||||
router.get('/m/:event_id', async (req, res) => {
|
router.get('/m/:event_id', async (req, res) => {
|
||||||
const settingsController = require('../api/controller/settings')
|
|
||||||
log.debug('[AP] Get event details ')
|
log.debug('[AP] Get event details ')
|
||||||
const event_id = req.params.event_id
|
const event_id = req.params.event_id
|
||||||
if (req.accepts('html')) { return res.redirect(301, `/event/${event_id}`) }
|
const acceptHtml = req.accepts('html', 'application/activity+json') === 'html'
|
||||||
|
if (acceptHtml) { return res.redirect(301, `/event/${event_id}`) }
|
||||||
|
|
||||||
const event = await Event.findByPk(req.params.event_id, { include: [User, Tag, Place] })
|
const event = await Event.findByPk(req.params.event_id, { include: [User, Tag, Place] })
|
||||||
if (!event) { return res.status(404).send('Not found') }
|
if (!event) { return res.status(404).send('Not found') }
|
||||||
return res.json(event.toAP(settingsController.settings.instance_name, settingsController.settings.instance_locale))
|
const eventAp = event.toAP(settingsController.settings.instance_name, settingsController.settings.instance_locale)
|
||||||
|
eventAp['@context'] = [
|
||||||
|
"https://www.w3.org/ns/activitystreams"
|
||||||
|
]
|
||||||
|
|
||||||
|
res.type('application/activity+json; charset=utf-8')
|
||||||
|
return res.json(eventAp)
|
||||||
})
|
})
|
||||||
|
|
||||||
// get any message coming from federation
|
// get any message coming from federation
|
||||||
|
|
|
@ -5,6 +5,7 @@ const APUser = require('../api/models/ap_user')
|
||||||
const log = require('../log')
|
const log = require('../log')
|
||||||
const helpers = require('../helpers')
|
const helpers = require('../helpers')
|
||||||
const linkifyHtml = require('linkify-html')
|
const linkifyHtml = require('linkify-html')
|
||||||
|
const get = require('lodash/get')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
|
@ -59,7 +60,7 @@ module.exports = {
|
||||||
|
|
||||||
async remove (req, res) {
|
async remove (req, res) {
|
||||||
const resource = await Resource.findOne({
|
const resource = await Resource.findOne({
|
||||||
where: { activitypub_id: req.body.object.id },
|
where: { activitypub_id: get(req.body, 'object.id', req.body.object) },
|
||||||
include: [{ model: APUser, required: true, attributes: ['ap_id'] }]
|
include: [{ model: APUser, required: true, attributes: ['ap_id'] }]
|
||||||
})
|
})
|
||||||
if (!resource) {
|
if (!resource) {
|
||||||
|
|
|
@ -28,7 +28,7 @@ router.get('/webfinger', allowFederation, (req, res) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const resource = req.query.resource
|
const resource = req.query.resource
|
||||||
const domain = (new url.URL(settings.baseurl)).host
|
const domain = (new url.URL(res.locals.settings.baseurl)).host
|
||||||
const [, name, req_domain] = resource.match(/acct:(.*)@(.*)/)
|
const [, name, req_domain] = resource.match(/acct:(.*)@(.*)/)
|
||||||
if (domain !== req_domain) {
|
if (domain !== req_domain) {
|
||||||
log.warn(`Bad webfinger request, requested domain "${req_domain}" instead of "${domain}"`)
|
log.warn(`Bad webfinger request, requested domain "${req_domain}" instead of "${domain}"`)
|
||||||
|
|
|
@ -7,7 +7,6 @@ const dayjs = require('dayjs')
|
||||||
const config = require('./config')
|
const config = require('./config')
|
||||||
const log = require('./log')
|
const log = require('./log')
|
||||||
const pkg = require('../package.json')
|
const pkg = require('../package.json')
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const sharp = require('sharp')
|
const sharp = require('sharp')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
|
@ -95,8 +94,8 @@ module.exports = {
|
||||||
|
|
||||||
serveStatic () {
|
serveStatic () {
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
// serve event's images/thumb
|
// serve images/thumb
|
||||||
router.use('/media/', express.static(config.upload_path, { immutable: true, maxAge: '1y' } ))
|
router.use('/media/', express.static(config.upload_path, { immutable: true, maxAge: '1y' } ), (_req, res) => res.sendStatus(404))
|
||||||
router.use('/noimg.svg', express.static('./static/noimg.svg'))
|
router.use('/noimg.svg', express.static('./static/noimg.svg'))
|
||||||
|
|
||||||
router.use('/logo.png', (req, res, next) => {
|
router.use('/logo.png', (req, res, next) => {
|
||||||
|
@ -112,7 +111,7 @@ module.exports = {
|
||||||
return router
|
return router
|
||||||
},
|
},
|
||||||
|
|
||||||
logRequest (req, res, next) {
|
logRequest (req, _res, next) {
|
||||||
log.debug(`${req.method} ${req.path}`)
|
log.debug(`${req.method} ${req.path}`)
|
||||||
next()
|
next()
|
||||||
},
|
},
|
||||||
|
@ -122,41 +121,34 @@ module.exports = {
|
||||||
if(!/^https?:\/\//.test(url)) {
|
if(!/^https?:\/\//.test(url)) {
|
||||||
throw Error('Hacking attempt?')
|
throw Error('Hacking attempt?')
|
||||||
}
|
}
|
||||||
const filename = crypto.randomBytes(16).toString('hex') + '.jpg'
|
|
||||||
const finalPath = path.resolve(config.upload_path, filename)
|
|
||||||
const thumbPath = path.resolve(config.upload_path, 'thumb', filename)
|
|
||||||
const outStream = fs.createWriteStream(finalPath)
|
|
||||||
const thumbStream = fs.createWriteStream(thumbPath)
|
|
||||||
|
|
||||||
const resizer = sharp().resize(1200).jpeg({ quality: 95 })
|
const filename = crypto.randomBytes(16).toString('hex')
|
||||||
const thumbnailer = sharp().resize(400).jpeg({ quality: 90 })
|
const sharpStream = sharp({ failOnError: true })
|
||||||
|
const promises = [
|
||||||
|
sharpStream.clone().resize(500, null, { withoutEnlargement: true }).jpeg({ effort: 6, mozjpeg: true }).toFile(path.resolve(config.upload_path, 'thumb', filename + '.jpg')),
|
||||||
|
sharpStream.clone().resize(1200, null, { withoutEnlargement: true } ).jpeg({ quality: 95, effort: 6, mozjpeg: true}).toFile(path.resolve(config.upload_path, filename + '.jpg')),
|
||||||
|
]
|
||||||
|
|
||||||
const response = await axios({ method: 'GET', url, responseType: 'stream' })
|
const response = await axios({ method: 'GET', url: encodeURI(url), responseType: 'stream' })
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
response.data.pipe(sharpStream)
|
||||||
let onError = false
|
return Promise.all(promises)
|
||||||
const err = e => {
|
.then(res => {
|
||||||
if (onError) {
|
const info = res[1]
|
||||||
return
|
return {
|
||||||
|
destination: config.upload_path,
|
||||||
|
filename: filename + '.jpg',
|
||||||
|
path: path.resolve(config.upload_path, filename + '.jpg'),
|
||||||
|
height: info.height,
|
||||||
|
width: info.width,
|
||||||
|
size: info.size,
|
||||||
}
|
}
|
||||||
onError = true
|
})
|
||||||
reject(e)
|
.catch(err => {
|
||||||
}
|
log.error(err)
|
||||||
|
req.err = err
|
||||||
response.data
|
cb(null)
|
||||||
.pipe(thumbnailer)
|
})
|
||||||
.on('error', err)
|
|
||||||
.pipe(thumbStream)
|
|
||||||
.on('error', err)
|
|
||||||
|
|
||||||
response.data
|
|
||||||
.pipe(resizer)
|
|
||||||
.on('error', err)
|
|
||||||
.pipe(outStream)
|
|
||||||
.on('error', err)
|
|
||||||
|
|
||||||
outStream.on('finish', () => resolve(filename))
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -232,7 +224,8 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async APRedirect (req, res, next) {
|
async APRedirect (req, res, next) {
|
||||||
if (!req.accepts('html')) {
|
const acceptJson = req.accepts('html', 'application/activity+json') === 'application/activity+json'
|
||||||
|
if (acceptJson) {
|
||||||
const eventController = require('../server/api/controller/event')
|
const eventController = require('../server/api/controller/event')
|
||||||
const event = await eventController._get(req.params.slug)
|
const event = await eventController._get(req.params.slug)
|
||||||
if (event) {
|
if (event) {
|
||||||
|
|
|
@ -1,14 +1,29 @@
|
||||||
|
const config = require('../server/config')
|
||||||
|
config.load()
|
||||||
|
|
||||||
module.exports = function () {
|
const initialize = {
|
||||||
const config = require('../server/config')
|
// close connections/port/unix socket
|
||||||
config.load()
|
async shutdown (exit = true) {
|
||||||
const log = require('../server/log')
|
const log = require('../server/log')
|
||||||
const settingsController = require('./api/controller/settings')
|
const TaskManager = require('../server/taskManager').TaskManager
|
||||||
const db = require('./api/models/index')
|
if (TaskManager) { TaskManager.stop() }
|
||||||
const dayjs = require('dayjs')
|
log.info('Closing DB')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const sequelize = require('../server/api/models')
|
||||||
async function start (nuxt) {
|
await sequelize.close()
|
||||||
|
process.off('SIGTERM', initialize.shutdown)
|
||||||
|
process.off('SIGINT', initialize.shutdown)
|
||||||
|
if (exit) {
|
||||||
|
process.exit()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
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 == 'READY') {
|
if (config.status == 'READY') {
|
||||||
await db.initialize()
|
await db.initialize()
|
||||||
} else {
|
} else {
|
||||||
|
@ -18,41 +33,33 @@ module.exports = function () {
|
||||||
dialect: process.env.GANCIO_DB_DIALECT,
|
dialect: process.env.GANCIO_DB_DIALECT,
|
||||||
storage: process.env.GANCIO_DB_STORAGE,
|
storage: process.env.GANCIO_DB_STORAGE,
|
||||||
host: process.env.GANCIO_DB_HOST,
|
host: process.env.GANCIO_DB_HOST,
|
||||||
|
port: process.env.GANCIO_DB_PORT,
|
||||||
database: process.env.GANCIO_DB_DATABASE,
|
database: process.env.GANCIO_DB_DATABASE,
|
||||||
username: process.env.GANCIO_DB_USERNAME,
|
username: process.env.GANCIO_DB_USERNAME,
|
||||||
password: process.env.GANCIO_DB_PASSWORD,
|
password: process.env.GANCIO_DB_PASSWORD,
|
||||||
}
|
}
|
||||||
|
|
||||||
setupController._setupDb(dbConf)
|
setupController._setupDb(dbConf)
|
||||||
.catch(e => { process.exit(1) })
|
.catch(e => {
|
||||||
|
log.warn(String(e))
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
await settingsController.load()
|
await settingsController.load()
|
||||||
}
|
}
|
||||||
|
|
||||||
dayjs.extend(timezone)
|
|
||||||
dayjs.tz.setDefault(settingsController.settings.instance_timezone)
|
dayjs.tz.setDefault(settingsController.settings.instance_timezone)
|
||||||
|
|
||||||
let TaskManager
|
let TaskManager
|
||||||
if (config.status === 'READY' && process.env.NODE_ENV == 'production') {
|
if (config.status === 'READY' && process.env.NODE_ENV == 'production') {
|
||||||
TaskManager = require('../server/taskManager').TaskManager
|
TaskManager = require('../server/taskManager').TaskManager
|
||||||
TaskManager.start()
|
TaskManager.start()
|
||||||
}
|
}
|
||||||
log.info(`Listen on ${config.server.host}:${config.server.port}`)
|
|
||||||
|
process.on('SIGTERM', initialize.shutdown)
|
||||||
// close connections/port/unix socket
|
process.on('SIGINT', initialize.shutdown)
|
||||||
async function shutdown () {
|
|
||||||
if (TaskManager) { TaskManager.stop() }
|
|
||||||
log.info('Closing DB')
|
|
||||||
const sequelize = require('../server/api/models')
|
|
||||||
await sequelize.close()
|
|
||||||
process.off('SIGTERM', shutdown)
|
|
||||||
process.off('SIGINT', shutdown)
|
|
||||||
nuxt.close()
|
|
||||||
process.exit()
|
|
||||||
}
|
|
||||||
process.on('SIGTERM', shutdown)
|
|
||||||
process.on('SIGINT', shutdown)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return start(this.nuxt)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = initialize
|
30
server/migrations/20220512195507-cohort.js
Normal file
30
server/migrations/20220512195507-cohort.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up (queryInterface, Sequelize) {
|
||||||
|
return queryInterface.createTable('cohorts', {
|
||||||
|
id: {
|
||||||
|
type: Sequelize.INTEGER,
|
||||||
|
primaryKey: true,
|
||||||
|
allowNull: false,
|
||||||
|
autoIncrement: true,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
unique: true,
|
||||||
|
index: true,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
isActor: {
|
||||||
|
type: Sequelize.BOOLEAN
|
||||||
|
},
|
||||||
|
isTop: {
|
||||||
|
type: Sequelize.BOOLEAN
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
down (queryInterface, Sequelize) {
|
||||||
|
return queryInterface.dropTable('cohorts')
|
||||||
|
}
|
||||||
|
};
|
35
server/migrations/20220512195953-filter.js
Normal file
35
server/migrations/20220512195953-filter.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up (queryInterface, Sequelize) {
|
||||||
|
return queryInterface.createTable('filters', {
|
||||||
|
id: {
|
||||||
|
type: Sequelize.INTEGER,
|
||||||
|
primaryKey: true,
|
||||||
|
autoIncrement: true,
|
||||||
|
},
|
||||||
|
cohortId: {
|
||||||
|
type: Sequelize.INTEGER,
|
||||||
|
allowNull: true,
|
||||||
|
references: {
|
||||||
|
model: 'cohorts',
|
||||||
|
key: 'id'
|
||||||
|
},
|
||||||
|
onUpdate: 'CASCADE',
|
||||||
|
onDelete: 'SET NULL'
|
||||||
|
},
|
||||||
|
tags: {
|
||||||
|
type: Sequelize.JSON,
|
||||||
|
},
|
||||||
|
places: {
|
||||||
|
type: Sequelize.JSON,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
down (queryInterface, _Sequelize) {
|
||||||
|
return queryInterface.dropTable('filters')
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,6 +1,9 @@
|
||||||
const express = require('express')
|
const express = require('express')
|
||||||
const cookieParser = require('cookie-parser')
|
const cookieParser = require('cookie-parser')
|
||||||
|
|
||||||
|
const initialize = require('./initialize.server')
|
||||||
|
initialize.start()
|
||||||
|
|
||||||
// const metricsController = require('./metrics')
|
// const metricsController = require('./metrics')
|
||||||
// const promBundle = require('express-prom-bundle')
|
// const promBundle = require('express-prom-bundle')
|
||||||
// const metricsMiddleware = promBundle({ includeMethod: true })
|
// const metricsMiddleware = promBundle({ includeMethod: true })
|
||||||
|
@ -34,13 +37,14 @@ if (config.status === 'READY') {
|
||||||
|
|
||||||
// rss/ics/atom feed
|
// rss/ics/atom feed
|
||||||
app.get('/feed/:type', cors(), exportController.export)
|
app.get('/feed/:type', cors(), exportController.export)
|
||||||
app.use('/.well-known', webfinger)
|
|
||||||
|
|
||||||
app.use('/event/:slug', helpers.APRedirect)
|
app.use('/event/:slug', helpers.APRedirect)
|
||||||
|
|
||||||
// federation api / activitypub / webfinger / nodeinfo
|
// federation api / activitypub / webfinger / nodeinfo
|
||||||
app.use('/federation', federation)
|
app.use('/federation', federation)
|
||||||
|
app.use('/.well-known', webfinger)
|
||||||
|
|
||||||
// ignore unimplemented ping url from fediverse
|
// ignore unimplemented ping url from fediverse
|
||||||
app.use(spamFilter)
|
app.use(spamFilter)
|
||||||
|
|
||||||
// fill res.locals.user if request is authenticated
|
// fill res.locals.user if request is authenticated
|
||||||
|
@ -54,26 +58,34 @@ if (config.status === 'READY') {
|
||||||
app.use('/api', api)
|
app.use('/api', api)
|
||||||
|
|
||||||
// // Handle 500
|
// // Handle 500
|
||||||
app.use((error, req, res, next) => {
|
app.use((error, _req, res, _next) => {
|
||||||
log.error('[ERROR]', error)
|
log.error('[ERROR]', error)
|
||||||
res.status(500).send('500: Internal Server Error')
|
return res.status(500).send('500: Internal Server Error')
|
||||||
})
|
})
|
||||||
|
|
||||||
// remaining request goes to nuxt
|
// remaining request goes to nuxt
|
||||||
// first nuxt component is ./pages/index.vue (with ./layouts/default.vue)
|
// first nuxt component is ./pages/index.vue (with ./layouts/default.vue)
|
||||||
// prefill current events, tags, places and announcements (used in every path)
|
// prefill current events, tags, places and announcements (used in every path)
|
||||||
app.use(async (req, res, next) => {
|
app.use(async (req, res, next) => {
|
||||||
// const start_datetime = getUnixTime(startOfWeek(startOfMonth(new Date())))
|
|
||||||
// req.events = await eventController._select(start_datetime, 100)
|
|
||||||
if (config.status === 'READY') {
|
if (config.status === 'READY') {
|
||||||
|
|
||||||
const eventController = require('./api/controller/event')
|
|
||||||
const announceController = require('./api/controller/announce')
|
const announceController = require('./api/controller/announce')
|
||||||
res.locals.meta = await eventController._getMeta()
|
|
||||||
res.locals.announcements = await announceController._getVisible()
|
res.locals.announcements = await announceController._getVisible()
|
||||||
}
|
}
|
||||||
res.locals.status = config.status
|
res.locals.status = config.status
|
||||||
next()
|
next()
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = app
|
module.exports = {
|
||||||
|
handler: app,
|
||||||
|
load () {
|
||||||
|
console.error('dentro load !')
|
||||||
|
},
|
||||||
|
unload: () => initialize.shutdown(false)
|
||||||
|
// async unload () {
|
||||||
|
// const db = require('./api/models/index')
|
||||||
|
// await db.close()
|
||||||
|
// process.off('SIGTERM')
|
||||||
|
// process.off('SIGINT')
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ function run(fn) {
|
||||||
return fn();
|
return fn();
|
||||||
}
|
}
|
||||||
function blank_object() {
|
function blank_object() {
|
||||||
return Object.create(null);
|
return /* @__PURE__ */ Object.create(null);
|
||||||
}
|
}
|
||||||
function run_all(fns) {
|
function run_all(fns) {
|
||||||
fns.forEach(run);
|
fns.forEach(run);
|
||||||
|
@ -104,7 +104,7 @@ function schedule_update() {
|
||||||
function add_render_callback(fn) {
|
function add_render_callback(fn) {
|
||||||
render_callbacks.push(fn);
|
render_callbacks.push(fn);
|
||||||
}
|
}
|
||||||
const seen_callbacks = new Set();
|
const seen_callbacks = /* @__PURE__ */ new Set();
|
||||||
let flushidx = 0;
|
let flushidx = 0;
|
||||||
function flush() {
|
function flush() {
|
||||||
const saved_component = current_component;
|
const saved_component = current_component;
|
||||||
|
@ -146,7 +146,7 @@ function update($$) {
|
||||||
$$.after_update.forEach(add_render_callback);
|
$$.after_update.forEach(add_render_callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const outroing = new Set();
|
const outroing = /* @__PURE__ */ new Set();
|
||||||
function transition_in(block, local) {
|
function transition_in(block, local) {
|
||||||
if (block && block.i) {
|
if (block && block.i) {
|
||||||
outroing.delete(block);
|
outroing.delete(block);
|
||||||
|
@ -282,19 +282,41 @@ if (typeof HTMLElement === "function") {
|
||||||
}
|
}
|
||||||
function get_each_context(ctx, list, i) {
|
function get_each_context(ctx, list, i) {
|
||||||
const child_ctx = ctx.slice();
|
const child_ctx = ctx.slice();
|
||||||
child_ctx[11] = list[i];
|
child_ctx[12] = list[i];
|
||||||
return child_ctx;
|
return child_ctx;
|
||||||
}
|
}
|
||||||
function get_each_context_1(ctx, list, i) {
|
function get_each_context_1(ctx, list, i) {
|
||||||
const child_ctx = ctx.slice();
|
const child_ctx = ctx.slice();
|
||||||
child_ctx[14] = list[i];
|
child_ctx[15] = list[i];
|
||||||
return child_ctx;
|
return child_ctx;
|
||||||
}
|
}
|
||||||
|
function create_if_block_5(ctx) {
|
||||||
|
let link;
|
||||||
|
return {
|
||||||
|
c() {
|
||||||
|
link = element("link");
|
||||||
|
attr(link, "rel", "stylesheet");
|
||||||
|
attr(link, "href", ctx[4]);
|
||||||
|
},
|
||||||
|
m(target, anchor) {
|
||||||
|
insert(target, link, anchor);
|
||||||
|
},
|
||||||
|
p(ctx2, dirty) {
|
||||||
|
if (dirty & 16) {
|
||||||
|
attr(link, "href", ctx2[4]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
d(detaching) {
|
||||||
|
if (detaching)
|
||||||
|
detach(link);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
function create_if_block$1(ctx) {
|
function create_if_block$1(ctx) {
|
||||||
let div;
|
let div;
|
||||||
let t;
|
let t;
|
||||||
let if_block = ctx[1] && ctx[3] === "true" && create_if_block_4(ctx);
|
let if_block = ctx[1] && ctx[3] === "true" && create_if_block_4(ctx);
|
||||||
let each_value = ctx[4];
|
let each_value = ctx[5];
|
||||||
let each_blocks = [];
|
let each_blocks = [];
|
||||||
for (let i = 0; i < each_value.length; i += 1) {
|
for (let i = 0; i < each_value.length; i += 1) {
|
||||||
each_blocks[i] = create_each_block(get_each_context(ctx, each_value, i));
|
each_blocks[i] = create_each_block(get_each_context(ctx, each_value, i));
|
||||||
|
@ -336,8 +358,8 @@ function create_if_block$1(ctx) {
|
||||||
if_block.d(1);
|
if_block.d(1);
|
||||||
if_block = null;
|
if_block = null;
|
||||||
}
|
}
|
||||||
if (dirty & 25) {
|
if (dirty & 41) {
|
||||||
each_value = ctx2[4];
|
each_value = ctx2[5];
|
||||||
let i;
|
let i;
|
||||||
for (i = 0; i < each_value.length; i += 1) {
|
for (i = 0; i < each_value.length; i += 1) {
|
||||||
const child_ctx = get_each_context(ctx2, each_value, i);
|
const child_ctx = get_each_context(ctx2, each_value, i);
|
||||||
|
@ -395,7 +417,7 @@ function create_if_block_4(ctx) {
|
||||||
attr(div0, "class", "title");
|
attr(div0, "class", "title");
|
||||||
attr(img, "id", "logo");
|
attr(img, "id", "logo");
|
||||||
attr(img, "alt", "logo");
|
attr(img, "alt", "logo");
|
||||||
if (!src_url_equal(img.src, img_src_value = "" + (ctx[0] + "/logo.png")))
|
if (!src_url_equal(img.src, img_src_value = ctx[0] + "/logo.png"))
|
||||||
attr(img, "src", img_src_value);
|
attr(img, "src", img_src_value);
|
||||||
attr(div1, "class", "content");
|
attr(div1, "class", "content");
|
||||||
attr(a, "href", ctx[0]);
|
attr(a, "href", ctx[0]);
|
||||||
|
@ -413,7 +435,7 @@ function create_if_block_4(ctx) {
|
||||||
p(ctx2, dirty) {
|
p(ctx2, dirty) {
|
||||||
if (dirty & 2)
|
if (dirty & 2)
|
||||||
set_data(t0, ctx2[1]);
|
set_data(t0, ctx2[1]);
|
||||||
if (dirty & 1 && !src_url_equal(img.src, img_src_value = "" + (ctx2[0] + "/logo.png"))) {
|
if (dirty & 1 && !src_url_equal(img.src, img_src_value = ctx2[0] + "/logo.png")) {
|
||||||
attr(img, "src", img_src_value);
|
attr(img, "src", img_src_value);
|
||||||
}
|
}
|
||||||
if (dirty & 1) {
|
if (dirty & 1) {
|
||||||
|
@ -429,7 +451,7 @@ function create_if_block_4(ctx) {
|
||||||
function create_if_block_2(ctx) {
|
function create_if_block_2(ctx) {
|
||||||
let div;
|
let div;
|
||||||
function select_block_type(ctx2, dirty) {
|
function select_block_type(ctx2, dirty) {
|
||||||
if (ctx2[11].media.length)
|
if (ctx2[12].media.length)
|
||||||
return create_if_block_3;
|
return create_if_block_3;
|
||||||
return create_else_block;
|
return create_else_block;
|
||||||
}
|
}
|
||||||
|
@ -472,7 +494,7 @@ function create_else_block(ctx) {
|
||||||
c() {
|
c() {
|
||||||
img = element("img");
|
img = element("img");
|
||||||
attr(img, "style", "aspect-ratio=1.7778;");
|
attr(img, "style", "aspect-ratio=1.7778;");
|
||||||
attr(img, "alt", img_alt_value = ctx[11].title);
|
attr(img, "alt", img_alt_value = ctx[12].title);
|
||||||
if (!src_url_equal(img.src, img_src_value = ctx[0] + "/noimg.svg"))
|
if (!src_url_equal(img.src, img_src_value = ctx[0] + "/noimg.svg"))
|
||||||
attr(img, "src", img_src_value);
|
attr(img, "src", img_src_value);
|
||||||
attr(img, "loading", "lazy");
|
attr(img, "loading", "lazy");
|
||||||
|
@ -481,7 +503,7 @@ function create_else_block(ctx) {
|
||||||
insert(target, img, anchor);
|
insert(target, img, anchor);
|
||||||
},
|
},
|
||||||
p(ctx2, dirty) {
|
p(ctx2, dirty) {
|
||||||
if (dirty & 16 && img_alt_value !== (img_alt_value = ctx2[11].title)) {
|
if (dirty & 32 && img_alt_value !== (img_alt_value = ctx2[12].title)) {
|
||||||
attr(img, "alt", img_alt_value);
|
attr(img, "alt", img_alt_value);
|
||||||
}
|
}
|
||||||
if (dirty & 1 && !src_url_equal(img.src, img_src_value = ctx2[0] + "/noimg.svg")) {
|
if (dirty & 1 && !src_url_equal(img.src, img_src_value = ctx2[0] + "/noimg.svg")) {
|
||||||
|
@ -502,9 +524,9 @@ function create_if_block_3(ctx) {
|
||||||
return {
|
return {
|
||||||
c() {
|
c() {
|
||||||
img = element("img");
|
img = element("img");
|
||||||
attr(img, "style", img_style_value = "object-position: " + position$1(ctx[11]) + "; aspect-ratio=1.7778;");
|
attr(img, "style", img_style_value = "object-position: " + position$1(ctx[12]) + "; aspect-ratio=1.7778;");
|
||||||
attr(img, "alt", img_alt_value = ctx[11].media[0].name);
|
attr(img, "alt", img_alt_value = ctx[12].media[0].name);
|
||||||
if (!src_url_equal(img.src, img_src_value = ctx[0] + "/media/thumb/" + ctx[11].media[0].url))
|
if (!src_url_equal(img.src, img_src_value = ctx[0] + "/media/thumb/" + ctx[12].media[0].url))
|
||||||
attr(img, "src", img_src_value);
|
attr(img, "src", img_src_value);
|
||||||
attr(img, "loading", "lazy");
|
attr(img, "loading", "lazy");
|
||||||
},
|
},
|
||||||
|
@ -512,13 +534,13 @@ function create_if_block_3(ctx) {
|
||||||
insert(target, img, anchor);
|
insert(target, img, anchor);
|
||||||
},
|
},
|
||||||
p(ctx2, dirty) {
|
p(ctx2, dirty) {
|
||||||
if (dirty & 16 && img_style_value !== (img_style_value = "object-position: " + position$1(ctx2[11]) + "; aspect-ratio=1.7778;")) {
|
if (dirty & 32 && img_style_value !== (img_style_value = "object-position: " + position$1(ctx2[12]) + "; aspect-ratio=1.7778;")) {
|
||||||
attr(img, "style", img_style_value);
|
attr(img, "style", img_style_value);
|
||||||
}
|
}
|
||||||
if (dirty & 16 && img_alt_value !== (img_alt_value = ctx2[11].media[0].name)) {
|
if (dirty & 32 && img_alt_value !== (img_alt_value = ctx2[12].media[0].name)) {
|
||||||
attr(img, "alt", img_alt_value);
|
attr(img, "alt", img_alt_value);
|
||||||
}
|
}
|
||||||
if (dirty & 17 && !src_url_equal(img.src, img_src_value = ctx2[0] + "/media/thumb/" + ctx2[11].media[0].url)) {
|
if (dirty & 33 && !src_url_equal(img.src, img_src_value = ctx2[0] + "/media/thumb/" + ctx2[12].media[0].url)) {
|
||||||
attr(img, "src", img_src_value);
|
attr(img, "src", img_src_value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -530,7 +552,7 @@ function create_if_block_3(ctx) {
|
||||||
}
|
}
|
||||||
function create_if_block_1$1(ctx) {
|
function create_if_block_1$1(ctx) {
|
||||||
let div;
|
let div;
|
||||||
let each_value_1 = ctx[11].tags;
|
let each_value_1 = ctx[12].tags;
|
||||||
let each_blocks = [];
|
let each_blocks = [];
|
||||||
for (let i = 0; i < each_value_1.length; i += 1) {
|
for (let i = 0; i < each_value_1.length; i += 1) {
|
||||||
each_blocks[i] = create_each_block_1(get_each_context_1(ctx, each_value_1, i));
|
each_blocks[i] = create_each_block_1(get_each_context_1(ctx, each_value_1, i));
|
||||||
|
@ -550,8 +572,8 @@ function create_if_block_1$1(ctx) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
p(ctx2, dirty) {
|
p(ctx2, dirty) {
|
||||||
if (dirty & 16) {
|
if (dirty & 32) {
|
||||||
each_value_1 = ctx2[11].tags;
|
each_value_1 = ctx2[12].tags;
|
||||||
let i;
|
let i;
|
||||||
for (i = 0; i < each_value_1.length; i += 1) {
|
for (i = 0; i < each_value_1.length; i += 1) {
|
||||||
const child_ctx = get_each_context_1(ctx2, each_value_1, i);
|
const child_ctx = get_each_context_1(ctx2, each_value_1, i);
|
||||||
|
@ -579,7 +601,7 @@ function create_if_block_1$1(ctx) {
|
||||||
function create_each_block_1(ctx) {
|
function create_each_block_1(ctx) {
|
||||||
let span;
|
let span;
|
||||||
let t0;
|
let t0;
|
||||||
let t1_value = ctx[14] + "";
|
let t1_value = ctx[15] + "";
|
||||||
let t1;
|
let t1;
|
||||||
return {
|
return {
|
||||||
c() {
|
c() {
|
||||||
|
@ -594,7 +616,7 @@ function create_each_block_1(ctx) {
|
||||||
append(span, t1);
|
append(span, t1);
|
||||||
},
|
},
|
||||||
p(ctx2, dirty) {
|
p(ctx2, dirty) {
|
||||||
if (dirty & 16 && t1_value !== (t1_value = ctx2[14] + ""))
|
if (dirty & 32 && t1_value !== (t1_value = ctx2[15] + ""))
|
||||||
set_data(t1, t1_value);
|
set_data(t1, t1_value);
|
||||||
},
|
},
|
||||||
d(detaching) {
|
d(detaching) {
|
||||||
|
@ -608,27 +630,27 @@ function create_each_block(ctx) {
|
||||||
let t0;
|
let t0;
|
||||||
let div2;
|
let div2;
|
||||||
let div0;
|
let div0;
|
||||||
let t1_value = when$1(ctx[11].start_datetime) + "";
|
let t1_value = when$1(ctx[12].start_datetime) + "";
|
||||||
let t1;
|
let t1;
|
||||||
let t2;
|
let t2;
|
||||||
let div1;
|
let div1;
|
||||||
let t3_value = ctx[11].title + "";
|
let t3_value = ctx[12].title + "";
|
||||||
let t3;
|
let t3;
|
||||||
let t4;
|
let t4;
|
||||||
let span1;
|
let span1;
|
||||||
let t5;
|
let t5;
|
||||||
let t6_value = ctx[11].place.name + "";
|
let t6_value = ctx[12].place.name + "";
|
||||||
let t6;
|
let t6;
|
||||||
let t7;
|
let t7;
|
||||||
let span0;
|
let span0;
|
||||||
let t8_value = ctx[11].place.address + "";
|
let t8_value = ctx[12].place.address + "";
|
||||||
let t8;
|
let t8;
|
||||||
let t9;
|
let t9;
|
||||||
let t10;
|
let t10;
|
||||||
let a_href_value;
|
let a_href_value;
|
||||||
let a_title_value;
|
let a_title_value;
|
||||||
let if_block0 = ctx[3] !== "true" && create_if_block_2(ctx);
|
let if_block0 = ctx[3] !== "true" && create_if_block_2(ctx);
|
||||||
let if_block1 = ctx[11].tags.length && create_if_block_1$1(ctx);
|
let if_block1 = ctx[12].tags.length && create_if_block_1$1(ctx);
|
||||||
return {
|
return {
|
||||||
c() {
|
c() {
|
||||||
a = element("a");
|
a = element("a");
|
||||||
|
@ -657,9 +679,9 @@ function create_each_block(ctx) {
|
||||||
attr(span0, "class", "subtitle");
|
attr(span0, "class", "subtitle");
|
||||||
attr(span1, "class", "place");
|
attr(span1, "class", "place");
|
||||||
attr(div2, "class", "content");
|
attr(div2, "class", "content");
|
||||||
attr(a, "href", a_href_value = "" + (ctx[0] + "/event/" + (ctx[11].slug || ctx[11].id)));
|
attr(a, "href", a_href_value = ctx[0] + "/event/" + (ctx[12].slug || ctx[12].id));
|
||||||
attr(a, "class", "event");
|
attr(a, "class", "event");
|
||||||
attr(a, "title", a_title_value = ctx[11].title);
|
attr(a, "title", a_title_value = ctx[12].title);
|
||||||
attr(a, "target", "_blank");
|
attr(a, "target", "_blank");
|
||||||
},
|
},
|
||||||
m(target, anchor) {
|
m(target, anchor) {
|
||||||
|
@ -698,15 +720,15 @@ function create_each_block(ctx) {
|
||||||
if_block0.d(1);
|
if_block0.d(1);
|
||||||
if_block0 = null;
|
if_block0 = null;
|
||||||
}
|
}
|
||||||
if (dirty & 16 && t1_value !== (t1_value = when$1(ctx2[11].start_datetime) + ""))
|
if (dirty & 32 && t1_value !== (t1_value = when$1(ctx2[12].start_datetime) + ""))
|
||||||
set_data(t1, t1_value);
|
set_data(t1, t1_value);
|
||||||
if (dirty & 16 && t3_value !== (t3_value = ctx2[11].title + ""))
|
if (dirty & 32 && t3_value !== (t3_value = ctx2[12].title + ""))
|
||||||
set_data(t3, t3_value);
|
set_data(t3, t3_value);
|
||||||
if (dirty & 16 && t6_value !== (t6_value = ctx2[11].place.name + ""))
|
if (dirty & 32 && t6_value !== (t6_value = ctx2[12].place.name + ""))
|
||||||
set_data(t6, t6_value);
|
set_data(t6, t6_value);
|
||||||
if (dirty & 16 && t8_value !== (t8_value = ctx2[11].place.address + ""))
|
if (dirty & 32 && t8_value !== (t8_value = ctx2[12].place.address + ""))
|
||||||
set_data(t8, t8_value);
|
set_data(t8, t8_value);
|
||||||
if (ctx2[11].tags.length) {
|
if (ctx2[12].tags.length) {
|
||||||
if (if_block1) {
|
if (if_block1) {
|
||||||
if_block1.p(ctx2, dirty);
|
if_block1.p(ctx2, dirty);
|
||||||
} else {
|
} else {
|
||||||
|
@ -718,10 +740,10 @@ function create_each_block(ctx) {
|
||||||
if_block1.d(1);
|
if_block1.d(1);
|
||||||
if_block1 = null;
|
if_block1 = null;
|
||||||
}
|
}
|
||||||
if (dirty & 17 && a_href_value !== (a_href_value = "" + (ctx2[0] + "/event/" + (ctx2[11].slug || ctx2[11].id)))) {
|
if (dirty & 33 && a_href_value !== (a_href_value = ctx2[0] + "/event/" + (ctx2[12].slug || ctx2[12].id))) {
|
||||||
attr(a, "href", a_href_value);
|
attr(a, "href", a_href_value);
|
||||||
}
|
}
|
||||||
if (dirty & 16 && a_title_value !== (a_title_value = ctx2[11].title)) {
|
if (dirty & 32 && a_title_value !== (a_title_value = ctx2[12].title)) {
|
||||||
attr(a, "title", a_title_value);
|
attr(a, "title", a_title_value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -736,41 +758,65 @@ function create_each_block(ctx) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function create_fragment$1(ctx) {
|
function create_fragment$1(ctx) {
|
||||||
let if_block_anchor;
|
let t;
|
||||||
let if_block = ctx[4].length && create_if_block$1(ctx);
|
let if_block1_anchor;
|
||||||
|
let if_block0 = ctx[4] && create_if_block_5(ctx);
|
||||||
|
let if_block1 = ctx[5].length && create_if_block$1(ctx);
|
||||||
return {
|
return {
|
||||||
c() {
|
c() {
|
||||||
if (if_block)
|
if (if_block0)
|
||||||
if_block.c();
|
if_block0.c();
|
||||||
if_block_anchor = empty();
|
t = space();
|
||||||
|
if (if_block1)
|
||||||
|
if_block1.c();
|
||||||
|
if_block1_anchor = empty();
|
||||||
this.c = noop;
|
this.c = noop;
|
||||||
},
|
},
|
||||||
m(target, anchor) {
|
m(target, anchor) {
|
||||||
if (if_block)
|
if (if_block0)
|
||||||
if_block.m(target, anchor);
|
if_block0.m(target, anchor);
|
||||||
insert(target, if_block_anchor, anchor);
|
insert(target, t, anchor);
|
||||||
|
if (if_block1)
|
||||||
|
if_block1.m(target, anchor);
|
||||||
|
insert(target, if_block1_anchor, anchor);
|
||||||
},
|
},
|
||||||
p(ctx2, [dirty]) {
|
p(ctx2, [dirty]) {
|
||||||
if (ctx2[4].length) {
|
if (ctx2[4]) {
|
||||||
if (if_block) {
|
if (if_block0) {
|
||||||
if_block.p(ctx2, dirty);
|
if_block0.p(ctx2, dirty);
|
||||||
} else {
|
} else {
|
||||||
if_block = create_if_block$1(ctx2);
|
if_block0 = create_if_block_5(ctx2);
|
||||||
if_block.c();
|
if_block0.c();
|
||||||
if_block.m(if_block_anchor.parentNode, if_block_anchor);
|
if_block0.m(t.parentNode, t);
|
||||||
}
|
}
|
||||||
} else if (if_block) {
|
} else if (if_block0) {
|
||||||
if_block.d(1);
|
if_block0.d(1);
|
||||||
if_block = null;
|
if_block0 = null;
|
||||||
|
}
|
||||||
|
if (ctx2[5].length) {
|
||||||
|
if (if_block1) {
|
||||||
|
if_block1.p(ctx2, dirty);
|
||||||
|
} else {
|
||||||
|
if_block1 = create_if_block$1(ctx2);
|
||||||
|
if_block1.c();
|
||||||
|
if_block1.m(if_block1_anchor.parentNode, if_block1_anchor);
|
||||||
|
}
|
||||||
|
} else if (if_block1) {
|
||||||
|
if_block1.d(1);
|
||||||
|
if_block1 = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
i: noop,
|
i: noop,
|
||||||
o: noop,
|
o: noop,
|
||||||
d(detaching) {
|
d(detaching) {
|
||||||
if (if_block)
|
if (if_block0)
|
||||||
if_block.d(detaching);
|
if_block0.d(detaching);
|
||||||
if (detaching)
|
if (detaching)
|
||||||
detach(if_block_anchor);
|
detach(t);
|
||||||
|
if (if_block1)
|
||||||
|
if_block1.d(detaching);
|
||||||
|
if (detaching)
|
||||||
|
detach(if_block1_anchor);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -799,6 +845,7 @@ function instance$1($$self, $$props, $$invalidate) {
|
||||||
let { theme = "light" } = $$props;
|
let { theme = "light" } = $$props;
|
||||||
let { show_recurrent = false } = $$props;
|
let { show_recurrent = false } = $$props;
|
||||||
let { sidebar = "true" } = $$props;
|
let { sidebar = "true" } = $$props;
|
||||||
|
let { external_style = "" } = $$props;
|
||||||
let mounted = false;
|
let mounted = false;
|
||||||
let events = [];
|
let events = [];
|
||||||
function update2(v) {
|
function update2(v) {
|
||||||
|
@ -814,11 +861,9 @@ function instance$1($$self, $$props, $$invalidate) {
|
||||||
if (places) {
|
if (places) {
|
||||||
params.push(`places=${places}`);
|
params.push(`places=${places}`);
|
||||||
}
|
}
|
||||||
if (show_recurrent) {
|
params.push(`show_recurrent=${show_recurrent ? "true" : "false"}`);
|
||||||
params.push(`show_recurrent=true`);
|
|
||||||
}
|
|
||||||
fetch(`${baseurl}/api/events?${params.join("&")}`).then((res) => res.json()).then((e) => {
|
fetch(`${baseurl}/api/events?${params.join("&")}`).then((res) => res.json()).then((e) => {
|
||||||
$$invalidate(4, events = e);
|
$$invalidate(5, events = e);
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
console.error("Error loading Gancio API -> ", e);
|
console.error("Error loading Gancio API -> ", e);
|
||||||
});
|
});
|
||||||
|
@ -833,20 +878,22 @@ function instance$1($$self, $$props, $$invalidate) {
|
||||||
if ("title" in $$props2)
|
if ("title" in $$props2)
|
||||||
$$invalidate(1, title = $$props2.title);
|
$$invalidate(1, title = $$props2.title);
|
||||||
if ("maxlength" in $$props2)
|
if ("maxlength" in $$props2)
|
||||||
$$invalidate(5, maxlength = $$props2.maxlength);
|
$$invalidate(6, maxlength = $$props2.maxlength);
|
||||||
if ("tags" in $$props2)
|
if ("tags" in $$props2)
|
||||||
$$invalidate(6, tags = $$props2.tags);
|
$$invalidate(7, tags = $$props2.tags);
|
||||||
if ("places" in $$props2)
|
if ("places" in $$props2)
|
||||||
$$invalidate(7, places = $$props2.places);
|
$$invalidate(8, places = $$props2.places);
|
||||||
if ("theme" in $$props2)
|
if ("theme" in $$props2)
|
||||||
$$invalidate(2, theme = $$props2.theme);
|
$$invalidate(2, theme = $$props2.theme);
|
||||||
if ("show_recurrent" in $$props2)
|
if ("show_recurrent" in $$props2)
|
||||||
$$invalidate(8, show_recurrent = $$props2.show_recurrent);
|
$$invalidate(9, show_recurrent = $$props2.show_recurrent);
|
||||||
if ("sidebar" in $$props2)
|
if ("sidebar" in $$props2)
|
||||||
$$invalidate(3, sidebar = $$props2.sidebar);
|
$$invalidate(3, sidebar = $$props2.sidebar);
|
||||||
|
if ("external_style" in $$props2)
|
||||||
|
$$invalidate(4, external_style = $$props2.external_style);
|
||||||
};
|
};
|
||||||
$$self.$$.update = () => {
|
$$self.$$.update = () => {
|
||||||
if ($$self.$$.dirty & 494) {
|
if ($$self.$$.dirty & 974) {
|
||||||
update2();
|
update2();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -855,6 +902,7 @@ function instance$1($$self, $$props, $$invalidate) {
|
||||||
title,
|
title,
|
||||||
theme,
|
theme,
|
||||||
sidebar,
|
sidebar,
|
||||||
|
external_style,
|
||||||
events,
|
events,
|
||||||
maxlength,
|
maxlength,
|
||||||
tags,
|
tags,
|
||||||
|
@ -873,12 +921,13 @@ class GancioEvents extends SvelteElement {
|
||||||
}, instance$1, create_fragment$1, safe_not_equal, {
|
}, instance$1, create_fragment$1, safe_not_equal, {
|
||||||
baseurl: 0,
|
baseurl: 0,
|
||||||
title: 1,
|
title: 1,
|
||||||
maxlength: 5,
|
maxlength: 6,
|
||||||
tags: 6,
|
tags: 7,
|
||||||
places: 7,
|
places: 8,
|
||||||
theme: 2,
|
theme: 2,
|
||||||
show_recurrent: 8,
|
show_recurrent: 9,
|
||||||
sidebar: 3
|
sidebar: 3,
|
||||||
|
external_style: 4
|
||||||
}, null);
|
}, null);
|
||||||
if (options) {
|
if (options) {
|
||||||
if (options.target) {
|
if (options.target) {
|
||||||
|
@ -899,7 +948,8 @@ class GancioEvents extends SvelteElement {
|
||||||
"places",
|
"places",
|
||||||
"theme",
|
"theme",
|
||||||
"show_recurrent",
|
"show_recurrent",
|
||||||
"sidebar"
|
"sidebar",
|
||||||
|
"external_style"
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
get baseurl() {
|
get baseurl() {
|
||||||
|
@ -917,21 +967,21 @@ class GancioEvents extends SvelteElement {
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
get maxlength() {
|
get maxlength() {
|
||||||
return this.$$.ctx[5];
|
return this.$$.ctx[6];
|
||||||
}
|
}
|
||||||
set maxlength(maxlength) {
|
set maxlength(maxlength) {
|
||||||
this.$$set({ maxlength });
|
this.$$set({ maxlength });
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
get tags() {
|
get tags() {
|
||||||
return this.$$.ctx[6];
|
return this.$$.ctx[7];
|
||||||
}
|
}
|
||||||
set tags(tags) {
|
set tags(tags) {
|
||||||
this.$$set({ tags });
|
this.$$set({ tags });
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
get places() {
|
get places() {
|
||||||
return this.$$.ctx[7];
|
return this.$$.ctx[8];
|
||||||
}
|
}
|
||||||
set places(places) {
|
set places(places) {
|
||||||
this.$$set({ places });
|
this.$$set({ places });
|
||||||
|
@ -945,7 +995,7 @@ class GancioEvents extends SvelteElement {
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
get show_recurrent() {
|
get show_recurrent() {
|
||||||
return this.$$.ctx[8];
|
return this.$$.ctx[9];
|
||||||
}
|
}
|
||||||
set show_recurrent(show_recurrent) {
|
set show_recurrent(show_recurrent) {
|
||||||
this.$$set({ show_recurrent });
|
this.$$set({ show_recurrent });
|
||||||
|
@ -958,6 +1008,13 @@ class GancioEvents extends SvelteElement {
|
||||||
this.$$set({ sidebar });
|
this.$$set({ sidebar });
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
|
get external_style() {
|
||||||
|
return this.$$.ctx[4];
|
||||||
|
}
|
||||||
|
set external_style(external_style) {
|
||||||
|
this.$$set({ external_style });
|
||||||
|
flush();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
customElements.define("gancio-events", GancioEvents);
|
customElements.define("gancio-events", GancioEvents);
|
||||||
function create_if_block(ctx) {
|
function create_if_block(ctx) {
|
||||||
|
@ -996,7 +1053,7 @@ function create_if_block(ctx) {
|
||||||
t6 = text(t6_value);
|
t6 = text(t6_value);
|
||||||
attr(div1, "class", "place");
|
attr(div1, "class", "place");
|
||||||
attr(div2, "class", "container");
|
attr(div2, "class", "container");
|
||||||
attr(a, "href", a_href_value = "" + (ctx[0] + "/event/" + (ctx[1].slug || ctx[1].id)));
|
attr(a, "href", a_href_value = ctx[0] + "/event/" + (ctx[1].slug || ctx[1].id));
|
||||||
attr(a, "class", "card");
|
attr(a, "class", "card");
|
||||||
attr(a, "target", "_blank");
|
attr(a, "target", "_blank");
|
||||||
},
|
},
|
||||||
|
@ -1035,7 +1092,7 @@ function create_if_block(ctx) {
|
||||||
set_data(t3, t3_value);
|
set_data(t3, t3_value);
|
||||||
if (dirty & 2 && t6_value !== (t6_value = ctx2[1].place.name + ""))
|
if (dirty & 2 && t6_value !== (t6_value = ctx2[1].place.name + ""))
|
||||||
set_data(t6, t6_value);
|
set_data(t6, t6_value);
|
||||||
if (dirty & 3 && a_href_value !== (a_href_value = "" + (ctx2[0] + "/event/" + (ctx2[1].slug || ctx2[1].id)))) {
|
if (dirty & 3 && a_href_value !== (a_href_value = ctx2[0] + "/event/" + (ctx2[1].slug || ctx2[1].id))) {
|
||||||
attr(a, "href", a_href_value);
|
attr(a, "href", a_href_value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue