keep migrating to vuetify

This commit is contained in:
les 2021-01-11 00:17:56 +01:00
parent 539c0fa933
commit 3abb39f62b
24 changed files with 1382 additions and 1389 deletions

View file

@ -1,6 +1,7 @@
html, body { html, body {
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: orange; scrollbar-color: orange;
overflow: auto !important;
} }
li { li {

View file

@ -3,7 +3,7 @@
v-calendar( v-calendar(
title-position='left' title-position='left'
:is-dark="settings['theme.is_dark']" :is-dark="settings['theme.is_dark']"
:columns="$screens({ default: 1, md: 2 })" :columns="2"
@update:from-page='updatePage' @update:from-page='updatePage'
:locale='$i18n.locale' :locale='$i18n.locale'
:attributes='attributes' :attributes='attributes'
@ -21,7 +21,7 @@ import { take, get } from 'lodash'
export default { export default {
name: 'Calendar', name: 'Calendar',
props: { props: {
events: { type: Array, default: [] } events: { type: Array, default: () => [] }
}, },
data () { data () {
const month = dayjs().month() + 1 const month = dayjs().month() + 1
@ -35,13 +35,14 @@ export default {
// TODO: could be better // TODO: could be better
attributes () { attributes () {
return []
const colors = ['blue', 'orange', 'yellow', 'teal', 'indigo', 'green', 'red', 'purple', 'pink', 'gray'] const colors = ['blue', 'orange', 'yellow', 'teal', 'indigo', 'green', 'red', 'purple', 'pink', 'gray']
const tags = take(this.tags, 10).map(t => t.tag) const tags = take(this.tags, 10).map(t => t.tag)
let attributes = [] let attributes = []
attributes.push({ key: 'today', dates: new Date(), highlight: { color: 'green', fillMode: 'outline' } }) attributes.push({ key: 'today', dates: new Date(), highlight: { color: 'green', fillMode: 'outline' } })
function getColor (event) { function getColor (event) {
const color = { class: 'vc-rounded-full', color: 'blue', fillMode: 'outline' } const color = { class: 'vc-rounded-full', color: 'blue', fillMode: 'normal' }
const tag = get(event, 'tags[0]') const tag = get(event, 'tags[0]')
if (!tag) { return color } if (!tag) { return color }
const idx = tags.indexOf(tag) const idx = tags.indexOf(tag)

View file

@ -1,78 +1,83 @@
<template lang="pug"> <template lang="pug" functional>
v-card.h-event.event.mt-1 v-card.h-event.event.mt-1
nuxt-link(:to='`/event/${event.id}`') template(v-if='props.show')
v-img.align-end.white--text(:src="`/media/thumb/${event.image_path}`" nuxt-link(:to='`/event/${props.event.id}`')
gradient="to bottom, rgba(0,0,0,.1), rgba(0,0,0,.7), rgba(0,0,0,.9)" v-img.align-end.white--text(:src="`/media/thumb/${props.event.image_path}`"
height="250" position="top top") gradient="to bottom, rgba(0,0,0,.1), rgba(0,0,0,.7), rgba(0,0,0,.9)"
v-card-title.text-h5 {{event.title}} height="250" position="top top")
v-card-title.text-h5.p-name {{props.event.title}}
v-card-text v-card-text
v-icon.float-right(v-if='props.event.parentId' color='success') mdi-repeat
//- time.text-h6.dt-start(:datetime='props.event.start_datetime|unixFormat("YYYY-MM-DD HH:mm")') <v-icon>mdi-event</v-icon> {{ event|when }}
.d-none.dt-end {{props.event.end_datetime|unixFormat('YYYY-MM-DD HH:mm')}}
v-btn.d-block.text-h6.p-location(text color='primary' big @click="$emit('placeclick', props.event.place.id)") <v-icon>mdi-map-marker</v-icon> {{props.event.place.name}}
time.text-h6(:datetime='event.start_datetime|unixFormat("YYYY-MM-DD HH:mm")') <v-icon>mdi-event</v-icon> {{ event|when }} v-card-actions
v-btn.d-block.text-h6(text color='primary' big @click="$emit('placeclick', event.place.id)") <v-icon>mdi-map-marker</v-icon> {{event.place.name}} v-chip.ml-1(v-for='tag in props.event.tags' link
:key='tag' outlined color='primary' @click="$emit('tagclick',tag)") {{tag}}
v-spacer
v-card-actions v-menu(offset-y)
v-chip.ml-1(v-for='tag in event.tags' link template(v-slot:activator="{on}")
:key='tag' outlined color='primary' @click="$emit('tagclick',tag)") {{tag}} v-btn(icon v-on='on' color='primary')
v-spacer v-icon mdi-dots-vertical
v-list(dense)
v-list-item-group
v-list-item(v-clipboard:success='copyLink'
v-clipboard:copy='`${parent.settings.baseurl}/event/${props.event.id}`')
v-list-item-icon
v-icon mdi-content-copy
v-list-item-content
v-list-item-title {{parent.$t('common.copy_link')}}
v-list-item(:href='`/api/event/${props.event.id}.ics`')
v-list-item-icon
v-icon mdi-calendar-export
v-list-item-content
v-list-item-title {{parent.$t('common.add_to_calendar')}}
v-menu(offset-y)
template(v-slot:activator="{on}")
v-btn(icon v-on='on' color='primary')
v-icon mdi-dots-vertical
v-list(dense)
v-list-item-group
v-list-item(v-clipboard:success='copyLink'
v-clipboard:copy='`${settings.baseurl}/event/${event.id}`')
v-list-item-icon
v-icon mdi-content-copy
v-list-item-content
v-list-item-title {{$t('common.copy_link')}}
v-list-item(:href='`/api/event/${event.id}.ics`')
v-list-item-icon
v-icon mdi-calendar-export
v-list-item-content
v-list-item-title {{$t('common.add_to_calendar')}}
</template> </template>
<script> <script>
import { mapState, mapActions } from 'vuex' // import { mapState, mapActions } from 'vuex'
export default { export default {
props: { props: {
event: { type: Object, default: () => ({}) } event: { type: Object, default: () => ({}) },
}, show: { type: Boolean }
computed: {
...mapState(['settings']),
show_footer () {
return (this.event.tags.length || this.event.resources.length)
}
},
methods: {
...mapActions(['setSearchTags', 'setSearchPlaces']),
copyLink () {
this.$root.$message('common.copied', { color: 'success' })
},
addTag (tag) {
if (this.filters.tags.includes(tag)) {
this.setSearchTags(this.filters.tags.filter(t => t !== tag))
} else {
this.setSearchTags(this.filters.tags.concat([tag]))
}
},
addPlace () {
const place = this.event.place.id
if (this.filters.places.includes(place)) {
this.setSearchPlaces(this.filters.places.filter(p => p !== place))
} else {
this.setSearchPlaces(this.filters.places.concat(place))
}
}
} }
// computed: {
// ...mapState(['settings']),
// show_footer () {
// return (this.event.tags.length || this.event.resources.length)
// }
// },
// methods: {
// ...mapActions(['setSearchTags', 'setSearchPlaces']),
// copyLink () {
// this.$root.$message('common.copied', { color: 'success' })
// },
// addTag (tag) {
// if (this.filters.tags.includes(tag)) {
// this.setSearchTags(this.filters.tags.filter(t => t !== tag))
// } else {
// this.setSearchTags(this.filters.tags.concat([tag]))
// }
// },
// addPlace () {
// const place = this.event.place.id
// if (this.filters.places.includes(place)) {
// this.setSearchPlaces(this.filters.places.filter(p => p !== place))
// } else {
// this.setSearchPlaces(this.filters.places.concat(place))
// }
// }
// }
} }
</script> </script>
<style lang="less" scoped> <style lang="less">
.event { .event {
width: 330px; width: 330px;
height: 370px;
max-width: 450px; max-width: 450px;
flex-grow: 1; flex-grow: 1;
margin: .2em; margin: .2em;

View file

@ -22,24 +22,10 @@
v-btn(icon nuxt to='/export' v-on='on') v-btn(icon nuxt to='/export' v-on='on')
v-icon mdi-share-variant v-icon mdi-share-variant
//- v-menu(v-if='settings.enable_trusted_instances && settings.trusted_instances && settings.trusted_instances.length' v-tooltip(v-if='!$auth.loggedIn' bottom) {{$t('common.login')}}
offset-y bottom open-on-hover transition="slide-y-transition") template(v-slot:activator='{ on }')
template(v-slot:activator="{ on, attrs }") v-btn(icon nuxt to='/login' v-on='on')
v-btn(icon v-bind='attrs' v-on='on') v-icon mdi-login
v-icon mdi-map-marker-path
v-list
v-list-item(v-for='instance in settings.trusted_instances'
:key='instance.name'
:href='instance.url'
two-line)
v-list-item-avatar
v-img(:src='`${instance.url}/favicon.ico`')
v-list-item-content
v-list-item-title {{instance.name}}
v-list-item-subtitle {{instance.label}}
v-btn(v-if='!$auth.loggedIn' icon nuxt to='/login')
v-icon mdi-login
v-menu(v-else v-menu(v-else
offset-y bottom open-on-hover transition="slide-y-transition") offset-y bottom open-on-hover transition="slide-y-transition")

View file

@ -2,9 +2,10 @@
v-container v-container
v-switch.mt-0( v-switch.mt-0(
v-if='recurrentFilter && settings.allow_recurrent_event' v-if='recurrentFilter && settings.allow_recurrent_event'
v-model='filters.show_recurrent'
inset color='primary' inset color='primary'
:label="$t('event.show_recurrent')" :label="$t('event.show_recurrent')"
@change="v => $emit('showrecurrent', v)") @change="toggleShowRecurrent")
v-autocomplete.mt-0( v-autocomplete.mt-0(
:label='$t("common.search")' :label='$t("common.search")'
@ -37,7 +38,6 @@ import { mapState } from 'vuex'
export default { export default {
name: 'Search', name: 'Search',
props: { props: {
pastFilter: { type: Boolean, default: true },
recurrentFilter: { type: Boolean, default: true }, recurrentFilter: { type: Boolean, default: true },
filters: { type: Object, default: () => {} } filters: { type: Object, default: () => {} }
}, },
@ -67,14 +67,24 @@ export default {
remove (item) { remove (item) {
const filters = { const filters = {
tags: item.type === 'tag' ? this.filters.tags.filter(f => f !== item.id) : this.filters.tags, tags: item.type === 'tag' ? this.filters.tags.filter(f => f !== item.id) : this.filters.tags,
places: item.type === 'place' ? this.filters.places.filter(f => f !== item.id) : this.filters.places 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)
},
toggleShowRecurrent (v) {
const filters = {
tags: this.filters.tags,
places: this.filters.places,
show_recurrent: v
} }
this.$emit('update', filters) this.$emit('update', filters)
}, },
change (filters) { change (filters) {
filters = { filters = {
tags: filters.filter(t => t.type === 'tag').map(t => t.id), tags: filters.filter(t => t.type === 'tag').map(t => t.id),
places: filters.filter(p => p.type === 'place').map(p => p.id) places: filters.filter(p => p.type === 'place').map(p => p.id),
show_recurrent: this.filters.show_recurrent
} }
this.$emit('update', filters) this.$emit('update', filters)
} }

View file

@ -5,7 +5,7 @@ export default {
ca: 'Català', ca: 'Català',
pl: 'Polski', pl: 'Polski',
eu: 'Euskara', eu: 'Euskara',
nb_NO: 'Norwegian Bokmål', no: 'Norwegian Bokmål',
fr: 'Francais', fr: 'Francais',
de: 'Deutsch' de: 'Deutsch'
} }

View file

@ -5,7 +5,7 @@ module.exports = {
ca: 'Català', ca: 'Català',
pl: 'Polski', pl: 'Polski',
eu: 'Euskara', eu: 'Euskara',
nb_NO: 'Norwegian Bokmål', no: 'Norwegian Bokmål',
fr: 'Francais', fr: 'Francais',
de: 'Deutsch' de: 'Deutsch'
} }

View file

@ -61,8 +61,8 @@
"embed": "Incorpora", "embed": "Incorpora",
"embed_title": "Mostra questo evento sul tuo sito web", "embed_title": "Mostra questo evento sul tuo sito web",
"embed_help": "Copiando il seguente codice sul tuo sito web l'evento verrà incluso come qui di lato", "embed_help": "Copiando il seguente codice sul tuo sito web l'evento verrà incluso come qui di lato",
"feed": "Flusso RSS", "feed": "Feed RSS",
"feed_url_copied": "URL copiato, incollalo nel tuo lettore di flusso RSS", "feed_url_copied": "URL copiato, incollalo nel tuo lettore di Feed RSS",
"follow_me_title": "Segui gli aggiornamenti dal fediverso", "follow_me_title": "Segui gli aggiornamenti dal fediverso",
"follow": "Segui", "follow": "Segui",
"n_resources": "nessuna risorsa|una risorsa|{n} risorse", "n_resources": "nessuna risorsa|una risorsa|{n} risorse",
@ -122,7 +122,7 @@
"media_description": "Puoi aggiungere un volantino (opzionale)", "media_description": "Puoi aggiungere un volantino (opzionale)",
"added": "Evento aggiunto", "added": "Evento aggiunto",
"added_anon": "Evento aggiunto, verrà confermato quanto prima.", "added_anon": "Evento aggiunto, verrà confermato quanto prima.",
"where_description": "Dov'è il gancio? Se il posto non è presente, scrivilo e <b>premi invio</b>. ", "where_description": "Dov'è il gancio? Se il posto non è presente potrai crearlo. ",
"confirmed": "Evento confermato", "confirmed": "Evento confermato",
"not_found": "Evento non trovato", "not_found": "Evento non trovato",
"remove_confirmation": "Sei sicuro/a di voler eliminare questo evento?", "remove_confirmation": "Sei sicuro/a di voler eliminare questo evento?",
@ -153,7 +153,8 @@
"interact_with_me_at": "Seguimi nel fediverso su", "interact_with_me_at": "Seguimi nel fediverso su",
"import_ICS": "Importa da ICS", "import_ICS": "Importa da ICS",
"import_URL": "Importa da URL", "import_URL": "Importa da URL",
"ics": "ICS" "ics": "ICS",
"import_description": "Puoi importare eventi da altre piattaforme e da altre istanze attraverso i formati standard (ics e h-event)"
}, },
"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).",

View file

@ -28,9 +28,9 @@
], ],
"dependencies": { "dependencies": {
"@nuxtjs/auth": "^4.9.1", "@nuxtjs/auth": "^4.9.1",
"@nuxtjs/axios": "^5.12.3", "@nuxtjs/axios": "^5.12.4",
"accept-language": "^3.0.18", "accept-language": "^3.0.18",
"axios": "^0.21.0", "axios": "^0.21.1",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"body-parser": "^1.18.3", "body-parser": "^1.18.3",
"config": "^3.3.3", "config": "^3.3.3",
@ -40,7 +40,7 @@
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"date-fns": "^2.16.1", "date-fns": "^2.16.1",
"dayjs": "^1.9.6", "dayjs": "^1.9.6",
"dompurify": "^2.2.2", "dompurify": "^2.2.6",
"email-templates": "^8.0.2", "email-templates": "^8.0.2",
"express": "^4.17.1", "express": "^4.17.1",
"express-oauth-server": "^2.0.0", "express-oauth-server": "^2.0.0",
@ -51,7 +51,7 @@
"inquirer": "^7.3.3", "inquirer": "^7.3.3",
"jsdom": "^16.4.0", "jsdom": "^16.4.0",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"less": "^3.12.2", "less": "^4.0.0",
"linkifyjs": "^2.1.9", "linkifyjs": "^2.1.9",
"lodash": "^4.17.20", "lodash": "^4.17.20",
"microformat-node": "^2.0.1", "microformat-node": "^2.0.1",
@ -59,18 +59,18 @@
"moment-timezone": "^0.5.32", "moment-timezone": "^0.5.32",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"multer": "^1.4.2", "multer": "^1.4.2",
"nuxt": "^2.14.9", "nuxt": "^2.14.12",
"nuxt-express-module": "^0.0.11", "nuxt-express-module": "^0.0.11",
"pg": "^8.5.1", "pg": "^8.5.1",
"sequelize": "^6.3.5", "sequelize": "^6.3.5",
"sequelize-cli": "^6.2.0", "sequelize-cli": "^6.2.0",
"sharp": "^0.26.3", "sharp": "^0.27.0",
"sqlite3": "^5.0.0", "sqlite3": "^5.0.0",
"tiptap": "^1.30.0", "tiptap": "^1.30.0",
"tiptap-extensions": "^1.33.2", "tiptap-extensions": "^1.33.2",
"to-ico": "^1.1.5", "to-ico": "^1.1.5",
"url": "^0.11.0", "url": "^0.11.0",
"v-calendar": "^2.1.1", "v-calendar": "^2.1.3",
"vue-clipboard2": "^0.3.1", "vue-clipboard2": "^0.3.1",
"vue-i18n": "^8.22.2", "vue-i18n": "^8.22.2",
"yargs": "^16.1.1" "yargs": "^16.1.1"
@ -80,29 +80,29 @@
"@nuxtjs/eslint-config": "^5.0.0", "@nuxtjs/eslint-config": "^5.0.0",
"@nuxtjs/vuetify": "^1.11.2", "@nuxtjs/vuetify": "^1.11.2",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"eslint": "^7.14.0", "eslint": "^7.16.0",
"eslint-config-prettier": "^6.15.0", "eslint-config-prettier": "^7.1.0",
"eslint-config-standard": "^16.0.2", "eslint-config-standard": "^16.0.2",
"eslint-loader": "^4.0.2", "eslint-loader": "^4.0.2",
"eslint-plugin-import": "^2.22.1", "eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": ">=11.1.0", "eslint-plugin-node": ">=11.1.0",
"eslint-plugin-nuxt": "^2.0.0", "eslint-plugin-nuxt": "^2.0.0",
"eslint-plugin-prettier": "^3.2.0", "eslint-plugin-prettier": "^3.3.0",
"eslint-plugin-promise": ">=4.0.1", "eslint-plugin-promise": ">=4.0.1",
"eslint-plugin-standard": "^5.0.0", "eslint-plugin-standard": "^5.0.0",
"eslint-plugin-vue": "^7.2.0", "eslint-plugin-vue": "^7.3.0",
"less-loader": "^7.1.0", "less-loader": "^7.2.0",
"nodemon": "^2.0.6", "nodemon": "^2.0.6",
"prettier": "^2.2.1", "prettier": "^2.2.1",
"pug-plain-loader": "^1.0.0", "pug-plain-loader": "^1.1.0",
"sass": "^1.29.0", "sass": "^1.30.0",
"sass-loader": "^10.1.0", "sass-loader": "^10.1.0",
"vue-cli-plugin-vuetify": "~2.0.8", "vue-cli-plugin-vuetify": "~2.0.8",
"vuetify-loader": "^1.3.0", "vuetify-loader": "^1.3.0",
"webpack-cli": "^4.2.0" "webpack-cli": "^4.3.0"
}, },
"resolutions": { "resolutions": {
"prosemirror-model": "1.12.0" "prosemirror-model": "1.13.1"
}, },
"bin": { "bin": {
"gancio": "server/cli.js" "gancio": "server/cli.js"

View file

@ -1,7 +1,7 @@
<template lang="pug"> <template lang="pug">
v-container v-container
v-card v-card
v-tabs v-tabs(v-model='selectedTab')
//- SETTINGS //- SETTINGS
v-tab {{$t('common.settings')}} v-tab {{$t('common.settings')}}

View file

@ -2,6 +2,7 @@
v-row v-row
v-date-picker.col-md-6( v-date-picker.col-md-6(
mode='dateTime' mode='dateTime'
is24hr
:input-debounce="200" :input-debounce="200"
:min='today' :min='today'
:minute-increment="5" :minute-increment="5"
@ -15,7 +16,6 @@ v-row
:value='inputValue' :value='inputValue'
:label="$t('event.from')" :label="$t('event.from')"
:rules="[$validators.required('common.when')]" :rules="[$validators.required('common.when')]"
:hint="$t(`event.description`)"
ref='date' ref='date'
prepend-icon='mdi-calendar' prepend-icon='mdi-calendar'
persistent-hint persistent-hint
@ -24,6 +24,7 @@ v-row
v-date-picker.col-md-4( v-date-picker.col-md-4(
mode='dateTime' mode='dateTime'
is24hr
:minute-increment="5" :minute-increment="5"
:input-debounce="200" :input-debounce="200"
:min='today' :min='today'
@ -35,8 +36,7 @@ v-row
template(v-slot="{ inputValue, inputEvents }") template(v-slot="{ inputValue, inputEvents }")
v-text-field( v-text-field(
:value='inputValue' :value='inputValue'
:label="$t('event.from')" :label="$t('event.due')"
:hint="$t(`event.description`)"
ref='date' ref='date'
prepend-icon='mdi-calendar' prepend-icon='mdi-calendar'
persistent-hint persistent-hint
@ -60,20 +60,19 @@ v-row
//- v-btn-toggle.col-md-4(@change='changeType' color='primary' :value='value.type') //- v-btn-toggle.col-md-4(@change='changeType' color='primary' :value='value.type')
//- v-btn(value='normal') {{$t('event.normal')}} //- v-btn(value='normal') {{$t('event.normal')}}
//- v-btn(value='multidate') {{$t('event.multidate')}} //- v-btn(value='multidate') {{$t('event.multidate')}}
v-switch.col-md-2(v-model='is_recurrent' :label="$t('event.recurrent')" inset) v-switch.col-md-2(:input-value='isRecurrent' :label="$t('event.recurrent')" inset @change='updateRecurrent')
v-menu(v-if='settings.allow_recurrent_event && is_recurrent' offset-y open-on-hover) //- v-menu(v-if='settings.allow_recurrent_event && isRecurrent' offset-y open-on-hover)
template(v-slot:activator="{ on, attrs }") //- template(v-slot:activator="{ on, attrs }")
v-btn.col-md-2.mt-2(color='primary' value='recurrent' v-on='on') {{$t('event.recurrent')}} //- v-btn.col-md-2.mt-2(color='primary' value='recurrent' v-on='on') {{$t('event.recurrent')}}
v-list //- v-btn-group(v-if='settings.allow_recurrent_event && isRecurrent')
v-list-item-group(color='primary' v-model='frequency') v-btn-toggle.col-md-12(dense group text link v-if='isRecurrent' color='primary' v-model='value.recurrent.frequency')
v-list-item(v-for='f in frequencies' :key='f.value' v-btn(text link v-for='f in frequencies' :key='f.value' :value='f.value'
@click='selectFrequency(f.value)') {{f.text}} @click='selectFrequency(f.value)') {{f.text}}
//- div.text-center.mb-2(v-if='value.type === "recurrent"') div.col-md-12(v-if='isRecurrent')
//- span(v-if='value.recurrent.frequency !== "1m" && value.recurrent.frequency !== "2m"') {{whenPatterns}} p(v-if='value.recurrent.frequency !== "1m" && value.recurrent.frequency !== "2m"') 🡲 {{whenPatterns}}
//- v-btn-toggle.mt-1(v-else v-model='value.recurrent.type' color='primary') v-btn-toggle(v-else dense group v-model='value.recurrent.type' color='primary')
//- v-btn(v-for='whenPattern in whenPatterns' :value='whenPattern.key' :key='whenPatterns.key' small) v-btn(text link v-for='whenPattern in whenPatterns' :value='whenPattern.key' :key='whenPatterns.key') {{whenPattern.label}}
//- span {{whenPattern.label}}
//- 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")')
@ -91,7 +90,6 @@ export default {
}, },
data () { data () {
return { return {
is_recurrent: false,
today: dayjs().format('YYYY-MM-DD'), today: dayjs().format('YYYY-MM-DD'),
frequency: '', frequency: '',
frequencies: [ frequencies: [
@ -103,17 +101,19 @@ export default {
}, },
computed: { computed: {
...mapState(['settings', 'events']), ...mapState(['settings', 'events']),
isRecurrent () {
return !!this.value.recurrent
},
whenPatterns () { whenPatterns () {
if (!this.value.date) { return } if (!this.value.from) { return }
const date = dayjs(this.value.date) const date = dayjs(this.value.from)
const freq = this.value.recurrent.frequency const freq = this.value.recurrent.frequency
const weekDay = date.format('dddd') const weekDay = date.format('dddd')
if (freq === '1w' || freq === '2w') { if (freq === '1w' || freq === '2w') {
return this.$t(`event.recurrent_${freq}_days`, { days: weekDay }) return this.$t(`event.recurrent_${freq}_days`, { days: weekDay }).toUpperCase()
} else if (freq === '1m' || freq === '2m') { } else if (freq === '1m' || freq === '2m') {
const monthDay = date.format('D') const monthDay = date.format('D')
const n = Math.floor((monthDay - 1) / 7) + 1 const n = Math.floor((monthDay - 1) / 7) + 1
const patterns = [ const patterns = [
@ -150,19 +150,16 @@ export default {
} }
}, },
methods: { methods: {
updateRecurrent (value) {
this.$emit('input', { ...this.value, recurrent: value ? {} : null })
},
change (what, date) { change (what, date) {
const v = this.value const v = this.value
v[what] = date v[what] = date
this.$emit('input', v) this.$emit('input', v)
}, },
changeType (type) {
this.$emit('input', { date: null, recurrent: {} })
if (type !== 'recurrent') {
this.frequency = ''
}
},
selectFrequency (f) { selectFrequency (f) {
this.$emit('input', { recurrent: { frequency: f }, date: null }) this.$emit('input', { recurrent: { frequency: f }, from: this.value.from, due: this.value.due })
} }
// pick (date) { // pick (date) {
// if (this.value.type === 'normal' || this.value.type === 'recurrent' || (this.value.date && this.value.date.length === 2)) { // if (this.value.type === 'normal' || this.value.type === 'recurrent' || (this.value.date && this.value.date.length === 2)) {

View file

@ -2,7 +2,8 @@
v-card(color='secondary') v-card(color='secondary')
v-card-title {{$t('common.import')}} v-card-title {{$t('common.import')}}
v-card-text v-card-text
v-form(v-model='valid' ref='form' lazy-validation) p(v-html="$t('event.import_description')")
v-form(v-model='valid' ref='form' lazy-validation @submit.prevent='importGeneric')
v-text-field(v-model='URL' v-text-field(v-model='URL'
:label="$t('common.url')" :label="$t('common.url')"
:hint="$t('event.import_URL')" :hint="$t('event.import_URL')"
@ -18,7 +19,7 @@
persistent-hint persistent-hint
) )
p {{event}} p {{events}}
v-card-actions v-card-actions
v-spacer v-spacer
v-btn(@click='$emit("close")' color='warning') {{$t('common.cancel')}} v-btn(@click='$emit("close")' color='warning') {{$t('common.cancel')}}
@ -39,7 +40,7 @@ export default {
loading: false, loading: false,
valid: false, valid: false,
URL: '', URL: '',
event: {} events: {}
} }
}, },
methods: { methods: {
@ -54,8 +55,7 @@ export default {
const reader = new FileReader() const reader = new FileReader()
reader.readAsText(this.file) reader.readAsText(this.file)
reader.onload = () => { reader.onload = () => {
const data = reader.result const event = ical.parse(reader.result)
const event = ical.parse(data)
this.event = { this.event = {
title: event.name title: event.name
} }
@ -73,9 +73,9 @@ export default {
try { try {
const ret = await this.$axios.$get('/event/import', { params: { URL: this.URL } }) const ret = await this.$axios.$get('/event/import', { params: { URL: this.URL } })
this.event = ret this.events = ret
// check if contain an h-event // check if contain an h-event
this.$emit('imported', ret) // this.$emit('imported', ret)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
this.error = true this.error = true

View file

@ -23,7 +23,6 @@
@change='v => event.title = v' @change='v => event.title = v'
:value = 'event.title' :value = 'event.title'
:rules="[$validators.required('common.title')]" :rules="[$validators.required('common.title')]"
:hint="$t('event.what_description')"
prepend-icon='mdi-format-title' prepend-icon='mdi-format-title'
:label="$t('common.title')" :label="$t('common.title')"
autofocus autofocus

View file

@ -2,7 +2,7 @@
v-container v-container
//- EVENT PAGE //- EVENT PAGE
//- gancio supports microformats (http://microformats.org/wiki/h-event) //- gancio supports microformats (http://microformats.org/wiki/h-event)
v-card.h-event(v-on:keyup="$router.push(`/event/${event.next}`)") v-card.h-event
v-card-text v-card-text
//- admin controls //- admin controls
@ -32,7 +32,8 @@ v-container
time.dt-start.text-h5(:datetime='event.start_datetime|unixFormat("YYYY-MM-DD HH:mm")') time.dt-start.text-h5(:datetime='event.start_datetime|unixFormat("YYYY-MM-DD HH:mm")')
v-icon mdi-calendar v-icon mdi-calendar
b.ml-2 {{event|when}} b.ml-2 {{event|when}}
p.subtitle-1 {{event.start_datetime|from}} div.subtitle-1 {{event.start_datetime|from}}
small(v-if='event.parentId') ({{event|recurrentDetail}})
.text-h5.p-location .text-h5.p-location
v-icon mdi-map-marker v-icon mdi-map-marker

View file

@ -2,11 +2,16 @@
v-card(color='secondary') v-card(color='secondary')
v-card-title(v-text="$t('common.embed_title')") v-card-title(v-text="$t('common.embed_title')")
v-card-text v-card-text
v-row(:gutter='10') v-row
v-col(:span='12' :xs='24') v-col.col-12
v-alert.mb-1.mt-1(type='info' show-icon) {{$t('common.embed_help')}} v-alert.mb-1.mt-1(type='info' show-icon) {{$t('common.embed_help')}}
v-text-field(v-model='code') v-text-field(v-model='code')
v-col.mt-2(:span='12' :xs='24' v-html='code') v-btn(slot='prepend' plain text color='primary'
v-clipboard:copy='code'
v-clipboard:success='copyLink') {{$t("common.copy")}}
v-icon.ml-1 mdi-content-copy
v-col.mt-2(v-html='code')
v-card-actions v-card-actions
v-spacer v-spacer
v-btn(color='warning' @click="$emit('close')") {{$t("common.cancel")}} v-btn(color='warning' @click="$emit('close')") {{$t("common.cancel")}}

View file

@ -6,10 +6,7 @@ div
v-btn(text color='primary' v-if='!event.parentId' @click='remove(false)') {{$t('common.remove')}} v-btn(text color='primary' v-if='!event.parentId' @click='remove(false)') {{$t('common.remove')}}
template(v-if='event.parentId') template(v-if='event.parentId')
v-divider {{$t('event.recurrent')}} //- v-divider {{$t('event.recurrent')}}
p.text-secondary
i.el-icon-refresh
small {{event|recurrentDetail}}
v-btn(text color='primary' v-if='event.parent.is_visible' @click='toggle(true)') {{$t('common.pause')}} v-btn(text color='primary' v-if='event.parent.is_visible' @click='toggle(true)') {{$t('common.pause')}}
v-btn(text color='primary' v-else @click='toggle(true)') {{$t('common.start')}} v-btn(text color='primary' v-else @click='toggle(true)') {{$t('common.start')}}
v-btn(text color='primary' @click='$router.push(`/add/${event.parentId}`)') {{$t('common.edit')}} v-btn(text color='primary' @click='$router.push(`/add/${event.parentId}`)') {{$t('common.edit')}}

View file

@ -29,10 +29,6 @@
v-clipboard:success='copyLink') {{$t("common.copy")}} v-clipboard:success='copyLink') {{$t("common.copy")}}
v-icon.ml-1 mdi-content-copy v-icon.ml-1 mdi-content-copy
v-tab(v-if='settings.enable_federation') {{$t('common.fediverse')}}
v-tab-item
FollowMe
v-tab ics/ical v-tab ics/ical
v-tab-item v-tab-item
v-card v-card
@ -62,6 +58,9 @@
color='primary' v-clipboard:copy='listScript' v-clipboard:success='copyLink') {{$t('common.copy')}} color='primary' v-clipboard:copy='listScript' v-clipboard:success='copyLink') {{$t('common.copy')}}
v-icon.ml-1 mdi-content-copy v-icon.ml-1 mdi-content-copy
v-tab(v-if='settings.enable_federation') {{$t('common.fediverse')}}
v-tab-item(v-if='settings.enable_federation')
FollowMe
//- TOFIX //- TOFIX
//- v-tab.pt-1(label='calendar' name='calendar') //- v-tab.pt-1(label='calendar' name='calendar')
//- v-tab-item //- v-tab-item
@ -125,6 +124,7 @@ export default {
return `<iframe style='border: 0px; width: 100%;' src="${this.settings.baseurl}/embed/list?${params.join('&')}"></iframe>` return `<iframe style='border: 0px; width: 100%;' src="${this.settings.baseurl}/embed/list?${params.join('&')}"></iframe>`
}, },
link () { link () {
const typeMap = ['rss', 'ics', 'list']
const tags = this.filters.tags.join(',') const tags = this.filters.tags.join(',')
const places = this.filters.places.join(',') const places = this.filters.places.join(',')
let query = '' let query = ''
@ -138,7 +138,7 @@ export default {
} }
} }
return `${this.settings.baseurl}/feed/${this.type}${query}` return `${this.settings.baseurl}/feed/${typeMap[this.type]}${query}`
}, },
showLink () { showLink () {
return (['rss', 'ics'].includes(this.type)) return (['rss', 'ics'].includes(this.type))

View file

@ -6,7 +6,7 @@
//- Calendar and search bar //- Calendar and search bar
v-row#calbarmb-2 v-row#calbarmb-2
.col-xl-5.col-lg-5.col-md-6.col-sm-12.col-xs-12 .col-xl-5.col-lg-5.col-md-7.col-sm-12.col-xs-12
//- 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 client-only
@ -14,12 +14,11 @@
.col .col
Search(:filters='filters' @update='updateFilters') Search(:filters='filters' @update='updateFilters')
v-chip(v-if='selectedDay' close @click:close='dayChange(selectedDay)') {{selectedDay}} v-chip(v-if='selectedDay' close @click:close='dayChange({ date: selectedDay})') {{selectedDay}}
//- Events //- Events
#events #events
Event(v-for='event in events' Event(v-for='(event, idx) in events' :key='event.id' :event='event' :show='idx>=firstVisibleItem && idx<=lastVisibleItem'
:key='event.id' :event='event'
@tagclick='tagClick' @placeclick='placeClick') @tagclick='tagClick' @placeclick='placeClick')
</template> </template>
@ -35,33 +34,68 @@ import Calendar from '@/components/Calendar'
export default { export default {
name: 'Index', name: 'Index',
components: { Event, Search, Announcement, Calendar }, components: { Event, Search, Announcement, Calendar },
async asyncData ({ params, $api }) { async asyncData ({ params, $api, store }) {
const events = await $api.getEvents({ const events = await $api.getEvents({
start: dayjs().unix() start: dayjs().unix(),
end: null,
filters: { show_recurrent: store.state.settings.allow_recurrent_event && store.state.settings.recurrent_event_visible }
}) })
return { events } return { events }
}, },
data () { data ({ $store }) {
return { return {
date: dayjs().format('YYYY-MM-DD'), date: dayjs().format('YYYY-MM-DD'),
events: [], events: [],
start: dayjs().unix(), start: dayjs().unix(),
end: null, end: null,
filters: { tags: [], places: [] }, filters: { tags: [], places: [], show_recurrent: $store.state.settings.allow_recurrent_event && $store.state.settings.recurrent_event_visible },
selectedDay: null selectedDay: null,
firstVisibleItem: 0,
lastVisibleItem: 20
} }
}, },
computed: { computed: {
...mapState(['settings', 'announcements']), ...mapState(['settings', 'announcements'])
},
mounted () {
let last_known_scroll_position = 0
let ticking = false
document.addEventListener('scroll', e => {
last_known_scroll_position = window.scrollY
if (!ticking) {
window.requestAnimationFrame(() => {
this.scroll(last_known_scroll_position)
ticking = false
})
ticking = true
}
})
}, },
methods: { methods: {
...mapActions(['setFilters']), ...mapActions(['setFilters']),
scroll (y) {
const rowHeight = 370
const nItems = this.events.length
const fullHeight = document.getElementById('events').offsetHeight
const nRows = fullHeight / rowHeight
const itemPerRow = nItems / nRows
const visibleRows = 10
this.firstVisibleItem = Math.trunc(((y - 370) / rowHeight) * itemPerRow) - (5 * itemPerRow)
this.lastVisibleItem = this.firstVisibleItem + (visibleRows * itemPerRow)
console.error('Scrolled to ', y, ' nItems', nItems, 'fullHeight', fullHeight, ' itemPerRow', itemPerRow, ' nRow', nRows)
console.error('mostro dal ', this.firstVisibleItem, this.lastVisibleItem)
},
async updateEvents () { async updateEvents () {
this.events = await this.$api.getEvents({ this.events = await this.$api.getEvents({
start: this.start, start: this.start,
end: this.end, end: this.end,
places: this.filters.places, places: this.filters.places,
tags: this.filters.tags tags: this.filters.tags,
show_recurrent: this.filters.show_recurrent
}) })
this.setFilters(this.filters) this.setFilters(this.filters)
}, },
@ -101,18 +135,17 @@ export default {
this.updateEvents() this.updateEvents()
}, },
dayChange (day) { dayChange (day) {
if (this.selectedDay === day) { const date = dayjs(day.date).format('YYYY-MM-DD')
if (this.selectedDay === date) {
this.selectedDay = null this.selectedDay = null
this.date = dayjs().format('YYYY-MM-DD')
this.start = dayjs().unix() // .startOf('week').unix() this.start = dayjs().unix() // .startOf('week').unix()
this.end = null this.end = null
this.updateEvents() this.updateEvents()
return return
} }
this.start = dayjs(day).unix() this.start = dayjs(date).startOf('day').unix()
this.end = dayjs(day).endOf('day').unix() this.end = dayjs(day).endOf('day').unix()
this.date = dayjs(day).format('YYYY-MM-DD') this.selectedDay = date
this.selectedDay = day
this.updateEvents() this.updateEvents()
} }
}, },

View file

@ -21,10 +21,11 @@ export default ({ $axios, store }, inject) => {
start: params.start, start: params.start,
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
} }
}) })
return events return events.map(e => Object.freeze(e))
} catch (e) { } catch (e) {
console.error(e) console.error(e)
return [] return []

View file

@ -38,13 +38,13 @@ export default ({ app, store }) => {
Vue.filter('recurrentDetail', event => { Vue.filter('recurrentDetail', event => {
const parent = event.parent const parent = event.parent
const { frequency, days, 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).format('dddd') })
} else if (frequency === '1m' || frequency === '2m') { } else if (frequency === '1m' || frequency === '2m') {
const d = type === 'ordinal' ? days : days.map(d => dayjs().day(d - 1).format('dddd')) const d = type === 'ordinal' ? dayjs.unix(parent.start_datetime).date() : dayjs.unix(parent.start_datetime).format('dddd')
recurrent = app.i18n.tc(`event.recurrent_${frequency}_${type}`, days.length, { days: d }) recurrent = app.i18n.tc(`event.recurrent_${frequency}_${type}`, d)
} }
return recurrent return recurrent
}) })

View file

@ -4,10 +4,11 @@ const config = require('config')
const fs = require('fs') const fs = require('fs')
const { Op } = require('sequelize') const { Op } = require('sequelize')
const _ = require('lodash') const _ = require('lodash')
const helpers = require('../../helpers')
const linkifyHtml = require('linkifyjs/html') const linkifyHtml = require('linkifyjs/html')
const Sequelize = require('sequelize') const Sequelize = require('sequelize')
const dayjs = require('dayjs') const dayjs = require('dayjs')
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')
@ -417,19 +418,25 @@ const eventController = {
} }
}, },
async _select ({ start, end, tags, places }) { async _select ({ start, end, tags, places, show_recurrent }) {
const where = { const where = {
// do not include parent recurrent event
recurrent: null, recurrent: null,
// confirmed event only // confirmed event only
is_visible: true, is_visible: true,
[Op.or]: { [Op.or]: {
start_datetime: { [Op.gt]: start }, start_datetime: { [Op.gte]: start },
end_datetime: { [Op.gt]: start } end_datetime: { [Op.gte]: start }
} }
} }
if (!show_recurrent) {
where.parentId = null
}
if (end) { if (end) {
where.start_datetime = { [Op.lt]: end } where.start_datetime = { [Op.lte]: end }
} }
if (places) { if (places) {
@ -470,9 +477,11 @@ const eventController = {
const end = req.query.end const end = req.query.end
const tags = req.query.tags const tags = req.query.tags
const places = req.query.places const places = req.query.places
const show_recurrent = settingsController.settings.allow_recurrent_event &&
(typeof req.query.show_recurrent !== 'undefined' ? req.query.show_recurrent === 'true' : settingsController.settings.recurrent_event_visible)
res.json(await eventController._select({ res.json(await eventController._select({
start, end, places, tags start, end, places, tags, show_recurrent
})) }))
}, },
@ -497,20 +506,37 @@ const eventController = {
const frequency = recurrent.frequency const frequency = recurrent.frequency
const type = recurrent.type const type = recurrent.type
debug(`NOW IS ${cursor} while event is at ${start_date} (freq: ${frequency})`)
cursor = cursor.hour(start_date.hour()).minute(start_date.minute()).second(0) cursor = cursor.hour(start_date.hour()).minute(start_date.minute()).second(0)
debug(`set cursor to correct date and hour => ${cursor}`)
// each week or 2 // each week or 2
if (frequency[1] === 'w') { if (frequency[1] === 'w') {
cursor = cursor.day(start_date.day()) cursor = cursor.day(start_date.day())
debug(`Imposto il giorno della settimana ${cursor}`)
if (cursor.isBefore(dayjs())) { if (cursor.isBefore(dayjs())) {
cursor = cursor.add(7, 'day') cursor = cursor.add(7, 'day')
} }
if (frequency[0] === 2) { if (frequency[0] === '2') {
cursor = cursor.add(7, 'day') cursor = cursor.add(7, 'day')
} }
} else if (frequency === '1m') { } else if (frequency === '1m') {
if (type === 'ordinal') { if (type === 'ordinal') {
cursor = cursor.date(start_date.date()) cursor = cursor.date(start_date.date())
if (cursor.isBefore(dayjs())) {
cursor = cursor.add(1, 'month')
}
} else { // weekday
const monthDay = start_date.format('D')
const n = Math.floor((monthDay - 1) / 7) + 1
cursor = cursor.startOf('month')
cursor = cursor.add(n, 'week')
cursor = cursor.day(start_date.day())
if (cursor.isBefore(dayjs())) {
cursor = cursor.add(1, 'month')
}
} }
} }
@ -529,7 +555,6 @@ const eventController = {
include: [{ model: Event, as: 'child', required: false, where: { start_datetime: { [Op.gte]: start_datetime } } }], include: [{ model: Event, as: 'child', required: false, where: { start_datetime: { [Op.gte]: start_datetime } } }],
order: ['start_datetime'] order: ['start_datetime']
}) })
// filter events that as no instance in future yet // filter events that as no instance in future yet
const creations = events const creations = events
.filter(e => e.child.length === 0) .filter(e => e.child.length === 0)

View file

@ -1,3 +1,4 @@
const ical = require('ical.js')
const settingsController = require('./api/controller/settings') const settingsController = require('./api/controller/settings')
const acceptLanguage = require('accept-language') const acceptLanguage = require('accept-language')
@ -114,31 +115,56 @@ module.exports = {
}) })
}, },
/**
* Import events from url
* It does supports ICS and H-EVENT
*/
async importURL (req, res) { async importURL (req, res) {
const URL = req.query.URL const URL = req.query.URL
try { try {
const response = await axios.get(URL) const response = await axios.get(URL)
Microformats.get({ html: response.data, filter: ['h-event'] }, (err, data) => { const contentType = response.headers['content-type']
if (err || !data.items.length || !data.items[0].properties) {
return res.sendStatus(404) if (contentType.includes('text/html')) {
} Microformats.get({ html: response.data, filter: ['h-event'] }, (err, data) => {
const event = data.items[0].properties if (err || !data.items.length || !data.items[0].properties) {
return res.json({ return res.sendStatus(404)
title: get(event, 'name[0]', ''), }
description: get(event, 'content[0]', ''), const events = data.items.map(e => {
place: get(event, 'location[0].properties.name', ''), const props = e.properties
address: get(event, 'location[0].properties.street-address'), return {
start: get(event, 'start[0]', ''), title: get(props, 'name[0]', ''),
end: get(event, 'end[0]', ''), description: get(props, 'description[0]', ''),
tags: get(event, 'category', []), place: get(props, 'location[0].properties.name', ''),
image: get(event, 'featured[0]') address: get(props, 'location[0].properties.street-address'),
start: get(props, 'start[0]', ''),
end: get(props, 'end[0]', ''),
tags: get(props, 'category', []),
image: get(props, 'featured[0]')
}
})
return res.json(events)
}) })
}) } else if (contentType.includes('text/calendar')) {
const ret = ical.parse(response.data)
const component = new ical.Component(ret)
const events = component.getAllSubcomponents('vevent')
return res.json(events.map(e => {
const event = new ical.Event(e)
return {
title: get(event, 'summary', ''),
description: get(event, 'description', ''),
place: get(event, 'location', ''),
start: get(event, 'dtstart', ''),
end: get(event, 'dtend', '')
}
}))
}
// const event = dom.window.document.querySelected(".h-event") // const event = dom.window.document.querySelected(".h-event")
// console.error(event) // console.error(event)
// console.error(response) // console.error(response)
} catch (e) { } catch (e) {
console.error(e) debug(e)
} }
// res.json('ok') // res.json('ok')

View file

@ -41,7 +41,6 @@ class Task {
class TaskManager { class TaskManager {
constructor () { constructor () {
this.interval = 60 * 1000
this.tasks = [] this.tasks = []
} }
@ -90,7 +89,7 @@ const TS = new TaskManager()
TS.add(new Task({ TS.add(new Task({
name: 'RECURRENT_EVENT', name: 'RECURRENT_EVENT',
method: eventController._createRecurrent, method: eventController._createRecurrent,
repeatEach: 10 // check each 10 minutes repeatEach: 1 // check each 10 minutes
})) }))
// daily morning notification // daily morning notification

2272
yarn.lock

File diff suppressed because it is too large Load diff