mirror of
https://framagit.org/les/gancio.git
synced 2025-01-31 16:42:22 +01:00
parent
9be6fbc19c
commit
2492f6b545
7 changed files with 171 additions and 45 deletions
|
@ -15,7 +15,7 @@ li {
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-dialog .theme--dark.v-card {
|
.v-dialog .theme--dark.v-card {
|
||||||
background-color: #1e1e1e;
|
background-color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-application {
|
.v-application {
|
||||||
|
|
20
components/TBtn.vue
Normal file
20
components/TBtn.vue
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<template>
|
||||||
|
<v-tooltip top>
|
||||||
|
<template v-slot:activator="{ on, attrs}">
|
||||||
|
<v-btn v-bind="attrs" :to='to' icon v-on="on" @click='$emit("click")' :color='color'>
|
||||||
|
<slot />
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
<span>{{ tooltip }}</span>
|
||||||
|
</v-tooltip>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'TBtn',
|
||||||
|
props: {
|
||||||
|
tooltip: String,
|
||||||
|
color: String,
|
||||||
|
to: String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -109,7 +109,8 @@
|
||||||
"pin": "Pin",
|
"pin": "Pin",
|
||||||
"trusted_instances": "Trusted instances",
|
"trusted_instances": "Trusted instances",
|
||||||
"actors": "Node",
|
"actors": "Node",
|
||||||
"collection_in_home": "Show a collection in home"
|
"collection_in_home": "Show a collection in home",
|
||||||
|
"my_events": "My Events"
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"description": "By logging in you can publish new events.",
|
"description": "By logging in you can publish new events.",
|
||||||
|
|
|
@ -1,54 +1,119 @@
|
||||||
<template lang="pug">
|
<template>
|
||||||
v-container
|
<v-container class="pa-0 pa-md-3">
|
||||||
v-card
|
<v-card>
|
||||||
v-card-title.text-h5 {{$auth.user.email}}
|
<v-card-title>{{$auth.user.email}}</v-card-title>
|
||||||
v-card-text
|
<v-card-text>
|
||||||
p {{$t('settings.remove_account')}}
|
<v-tabs v-model='selectedTab'>
|
||||||
v-btn.black--text(color='warning' @click='remove_account') {{$t('common.remove')}}
|
<v-tab href="#mine">{{$t('common.my_events')}}</v-tab>
|
||||||
|
<v-tab href='#settings'>{{$t('common.settings')}}</v-tab>
|
||||||
|
<v-tab-item value="mine">
|
||||||
|
<v-container>
|
||||||
|
<v-card-text class="pa-0 pa-md-3">
|
||||||
|
<v-text-field v-model="search" :label="$t('common.search')"/>
|
||||||
|
<v-data-table :items="events" :hide-default-footer='events.length<10' dense :search="search"
|
||||||
|
:header-props='{ sortIcon: mdiChevronDown }'
|
||||||
|
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
|
||||||
|
:headers='headers'>
|
||||||
|
<template v-slot:item.data-table-select="{ isSelected, select }">
|
||||||
|
<v-simple-checkbox size='small' small dense :on-icon="mdiCheckboxOutline" :off-icon="mdiCheckboxBlankOutline" :value="isSelected" @input="select($event)" />
|
||||||
|
</template>
|
||||||
|
<template v-slot:item.when='{ item }'>
|
||||||
|
<span v-if='!item.recurrent'>{{$time.when(item)}}</span>
|
||||||
|
<span v-else><v-icon color='success' v-text='mdiRepeat' /> {{$time.recurrentDetail({ parent: item }, 'EEEE, HH:mm')}}</span>
|
||||||
|
</template>
|
||||||
|
<template v-slot:item.actions='{ item }'>
|
||||||
|
<template v-if="!item.recurrent">
|
||||||
|
<t-btn @click='toggle(item)' v-if='!item.is_visible' color='success' :tooltip="$t('common.confirm')"><v-icon v-text='mdiEye' /></t-btn>
|
||||||
|
<t-btn @click='toggle(item)' v-else-if="!item.parentId" color='info' :tooltip="$t('common.hide')"><v-icon v-text='mdiEyeOff' /></t-btn>
|
||||||
|
<t-btn @click='toggle(item)' v-else color='info' :tooltip="$t('common.skip')"><v-icon v-text='mdiDebugStepOver' /></t-btn>
|
||||||
|
<t-btn :to='`/event/${item.slug || item.id}`' :tooltip="$t('common.preview')"><v-icon v-text='mdiArrowRight' /></t-btn>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<t-btn @click='toggle(item)' v-if='!item.is_visible' color='success' :tooltip="$t('common.start')"><v-icon v-text='mdiPlay' /></t-btn>
|
||||||
|
<t-btn @click='toggle(item)' v-else color='info' :tooltip="$t('common.pause')"><v-icon v-text='mdiPause' /></t-btn>
|
||||||
|
</template>
|
||||||
|
<t-btn :to='`/add/${item.id}`' color='warning' :tooltip="$t('common.edit')"><v-icon v-text='mdiPencil' /></t-btn>
|
||||||
|
<t-btn @click='remove(item, item.recurrent)' color='error' :tooltip="$t('common.delete')"> <v-icon v-text='item.recurrent ? mdiDeleteForever : mdiDelete' /></t-btn>
|
||||||
|
</template>
|
||||||
|
</v-data-table>
|
||||||
|
</v-card-text>
|
||||||
|
</v-container>
|
||||||
|
</v-tab-item>
|
||||||
|
<v-tab-item value='settings'>
|
||||||
|
<v-container>
|
||||||
|
<v-btn @click='forgot'>{{$t('login.forgot_password')}}</v-btn><br/><br/>
|
||||||
|
<v-divider />
|
||||||
|
<p>{{$t('settings.remove_account')}}</p>
|
||||||
|
<v-btn color='warning' @click='remove_account'>{{$t('common.remove')}}</v-btn>
|
||||||
|
</v-container>
|
||||||
|
</v-tab-item>
|
||||||
|
</v-tabs>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
import { mdiChevronLeft, mdiChevronRight, mdiChevronDown, mdiCheckboxOutline, mdiCheckboxBlankOutline,
|
||||||
|
mdiPencil, mdiDelete, mdiArrowRight, mdiEye, mdiEyeOff, mdiRepeat, mdiPause, mdiPlay, mdiDebugStepOver, mdiDeleteForever } from '@mdi/js'
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
|
import TBtn from '../components/TBtn.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Settings',
|
name: 'Settings',
|
||||||
middleware: ['auth'],
|
middleware: ['auth'],
|
||||||
|
components: { TBtn },
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
password: '',
|
mdiChevronLeft, mdiChevronRight, mdiChevronDown, mdiCheckboxOutline, mdiCheckboxBlankOutline,
|
||||||
user: { }
|
mdiPencil, mdiDelete, mdiArrowRight, mdiEye, mdiEyeOff, mdiRepeat, mdiPause, mdiPlay, mdiDebugStepOver, mdiDeleteForever,
|
||||||
|
selectedTab: 'mine',
|
||||||
|
events: [],
|
||||||
|
selectedEvents: [],
|
||||||
|
search: '',
|
||||||
|
headers: [
|
||||||
|
{ value: 'title', text: this.$t('common.title') },
|
||||||
|
{ value: 'place.name', text: this.$t('common.place') },
|
||||||
|
{ value: 'when', text: this.$t('common.when') },
|
||||||
|
{ value: 'actions', text: this.$t('common.actions'), align: 'right' }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: mapState(['settings']),
|
computed: mapState(['settings']),
|
||||||
|
async fetch () {
|
||||||
|
this.events = await this.$axios.$get('/events/mine')
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// async change_password () {
|
async forgot () {
|
||||||
// if (!this.password) { return }
|
this.loading = true
|
||||||
// const user_data = { id: this.$auth.user.id, password: this.password }
|
await this.$axios.$post('/user/recover', { email: this.$auth.user.email })
|
||||||
// try {
|
this.loading = false
|
||||||
// await this.$axios.$put('/user', user_data)
|
this.$root.$message('login.check_email', { color: 'success' })
|
||||||
// Message({ message: this.$t('settings.password_updated'), showClose: true, type: 'success' })
|
},
|
||||||
// this.$router.replace('/')
|
|
||||||
// } catch (e) {
|
|
||||||
// console.log(e)
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// update_settings () {
|
|
||||||
// MessageBox.confirm(this.$t('settings.update_confirm'),
|
|
||||||
// this.$t('common.confirm'), {
|
|
||||||
// confirmButtonText: this.$t('common.ok'),
|
|
||||||
// cancelButtonText: this.$t('common.cancel'),
|
|
||||||
// type: 'error'
|
|
||||||
// }).then(async () => {
|
|
||||||
// this.user = await this.$axios.$put('/user', { ...this.user, password: this.password })
|
|
||||||
// }).catch(e => {
|
|
||||||
// Message({ message: e, showClose: true, type: 'warning' })
|
|
||||||
// })
|
|
||||||
// },
|
|
||||||
async remove_account () {
|
async remove_account () {
|
||||||
const ret = await this.$root.$confirm('settings.remove_account_confirm', { color: 'error' })
|
const ret = await this.$root.$confirm('settings.remove_account_confirm', { color: 'error' })
|
||||||
if (!ret) return
|
if (!ret) return
|
||||||
this.$axios.$delete('/user')
|
this.$axios.$delete('/user')
|
||||||
this.$auth.logout()
|
this.$auth.logout()
|
||||||
this.$router.replace('/')
|
this.$router.replace('/')
|
||||||
|
},
|
||||||
|
async toggle (event) {
|
||||||
|
const id = event.id
|
||||||
|
const is_visible = event.is_visible
|
||||||
|
const method = is_visible ? 'unconfirm' : 'confirm'
|
||||||
|
try {
|
||||||
|
await this.$axios.$put(`/event/${method}/${id}`)
|
||||||
|
event.is_visible = !is_visible
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async remove (event, parent) {
|
||||||
|
const ret = await this.$root.$confirm(`event.remove_${parent ? 'recurrent_' : ''}confirmation`)
|
||||||
|
if (!ret) { return }
|
||||||
|
await this.$axios.delete(`/event/${event.id}`)
|
||||||
|
this.$fetch()
|
||||||
|
this.$root.$message('admin.event_remove_ok')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
head () {
|
head () {
|
||||||
|
|
|
@ -88,7 +88,7 @@ export default ({ app, store }, inject) => {
|
||||||
endOfDay (date) { return DateTime.fromJSDate(date, { zone }).endOf('day').toUnixInteger()},
|
endOfDay (date) { return DateTime.fromJSDate(date, { zone }).endOf('day').toUnixInteger()},
|
||||||
|
|
||||||
|
|
||||||
recurrentDetail (event) {
|
recurrentDetail (event, format='EEEE') {
|
||||||
const opt = {
|
const opt = {
|
||||||
zone,
|
zone,
|
||||||
locale: app.i18n.locale || store.state.settings.instance_locale
|
locale: app.i18n.locale || store.state.settings.instance_locale
|
||||||
|
@ -98,9 +98,9 @@ export default ({ app, store }, inject) => {
|
||||||
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: DateTime.fromSeconds(parent.start_datetime, opt).toFormat('EEEE')})
|
recurrent = app.i18n.t(`event.recurrent_${frequency}_days`, { days: DateTime.fromSeconds(parent.start_datetime, opt).toFormat(format)})
|
||||||
} else if (frequency === '1m' || frequency === '2m') {
|
} else if (frequency === '1m' || frequency === '2m') {
|
||||||
const d = type === 'ordinal' ? DateTime.fromSeconds(parent.start_datetime, opt).day : DateTime.fromSeconds(parent.start_datetime, opt).toFormat('EEEE')
|
const d = type === 'ordinal' ? DateTime.fromSeconds(parent.start_datetime, opt).day : DateTime.fromSeconds(parent.start_datetime, opt).toFormat(format)
|
||||||
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 {
|
||||||
|
|
|
@ -437,8 +437,13 @@ const eventController = {
|
||||||
try {
|
try {
|
||||||
const body = req.body
|
const body = req.body
|
||||||
const event = await Event.findByPk(body.id)
|
const event = await Event.findByPk(body.id)
|
||||||
if (!event) { return res.sendStatus(404) }
|
if (!event) {
|
||||||
|
log.debug('[UPDATE] Event not found: %s', body?.id)
|
||||||
|
return res.sendStatus(404)
|
||||||
|
}
|
||||||
|
|
||||||
if (!req.user.is_admin && event.userId !== req.user.id) {
|
if (!req.user.is_admin && event.userId !== req.user.id) {
|
||||||
|
log.debug('[UPDATE] the user is neither an admin nor the owner of the event')
|
||||||
return res.sendStatus(403)
|
return res.sendStatus(403)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -448,19 +453,23 @@ const eventController = {
|
||||||
// // validate start_datetime and end_datetime
|
// // validate start_datetime and end_datetime
|
||||||
if (end_datetime) {
|
if (end_datetime) {
|
||||||
if (start_datetime > end_datetime) {
|
if (start_datetime > end_datetime) {
|
||||||
|
log.debug('[UPDATE] start_datetime is greated than end_datetime')
|
||||||
return res.status(400).send(`start datetime is greater than end datetime`)
|
return res.status(400).send(`start datetime is greater than end datetime`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Number(end_datetime) > 1000*24*60*60*365) {
|
if (Number(end_datetime) > 1000*24*60*60*365) {
|
||||||
|
log.debug('[UPDATE] end_datetime is too much in the future')
|
||||||
return res.status(400).send('are you sure?')
|
return res.status(400).send('are you sure?')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Number(start_datetime)) {
|
if (!Number(start_datetime)) {
|
||||||
|
log.debug('[UPDATE] start_datetime has to be a number')
|
||||||
return res.status(400).send(`Wrong format for start datetime`)
|
return res.status(400).send(`Wrong format for start datetime`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Number(start_datetime) > 1000*24*60*60*365) {
|
if (Number(start_datetime) > 1000*24*60*60*365) {
|
||||||
|
log.debug('[UPDATE] start_datetime is too much in the future')
|
||||||
return res.status(400).send('are you sure?')
|
return res.status(400).send('are you sure?')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -523,9 +532,11 @@ const eventController = {
|
||||||
try {
|
try {
|
||||||
place = await eventController._findOrCreatePlace(body)
|
place = await eventController._findOrCreatePlace(body)
|
||||||
if (!place) {
|
if (!place) {
|
||||||
|
log.info('[UPDATE] Place not found')
|
||||||
return res.status(400).send(`Place not found`)
|
return res.status(400).send(`Place not found`)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
log.info('[UPDATE] %s', e?.message ?? String(e))
|
||||||
return res.status(400).send(e.message)
|
return res.status(400).send(e.message)
|
||||||
}
|
}
|
||||||
await event.setPlace(place)
|
await event.setPlace(place)
|
||||||
|
@ -618,14 +629,12 @@ const eventController = {
|
||||||
page,
|
page,
|
||||||
older,
|
older,
|
||||||
reverse,
|
reverse,
|
||||||
|
user_id,
|
||||||
|
include_unconfirmed = false,
|
||||||
|
include_parent = false,
|
||||||
include_description=false }) {
|
include_description=false }) {
|
||||||
|
|
||||||
const where = {
|
const where = {
|
||||||
// do not include _parent_ recurrent event
|
|
||||||
recurrent: null,
|
|
||||||
|
|
||||||
// confirmed event only
|
|
||||||
is_visible: true,
|
|
||||||
|
|
||||||
apUserApId: null,
|
apUserApId: null,
|
||||||
|
|
||||||
|
@ -635,6 +644,20 @@ const eventController = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (user_id) {
|
||||||
|
where.userId = user_id
|
||||||
|
}
|
||||||
|
|
||||||
|
if (include_parent !== true) {
|
||||||
|
// do not include _parent_ recurrent event
|
||||||
|
where.recurrent = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (include_unconfirmed !== true) {
|
||||||
|
// confirmed event only
|
||||||
|
where.is_visible = true
|
||||||
|
}
|
||||||
|
|
||||||
// include recurrent events?
|
// include recurrent events?
|
||||||
if (!show_recurrent) {
|
if (!show_recurrent) {
|
||||||
where.parentId = null
|
where.parentId = null
|
||||||
|
@ -689,7 +712,12 @@ const eventController = {
|
||||||
const events = await Event.findAll({
|
const events = await Event.findAll({
|
||||||
where,
|
where,
|
||||||
attributes: {
|
attributes: {
|
||||||
exclude: ['likes', 'boost', 'userId', 'is_visible', 'createdAt', 'resources', 'recurrent', 'placeId', 'image_path', ...(!include_description ? ['description']: [])]
|
exclude: [
|
||||||
|
'likes', 'boost', 'userId', 'createdAt', 'resources', 'placeId', 'image_path',
|
||||||
|
...(!include_parent ? ['recurrent']: []),
|
||||||
|
...(!include_unconfirmed ? ['is_visible']: []),
|
||||||
|
...(!include_description ? ['description']: [])
|
||||||
|
]
|
||||||
},
|
},
|
||||||
order: [['start_datetime', reverse ? 'DESC' : 'ASC']],
|
order: [['start_datetime', reverse ? 'DESC' : 'ASC']],
|
||||||
include: [
|
include: [
|
||||||
|
@ -715,6 +743,17 @@ const eventController = {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async mine (req, res) {
|
||||||
|
const events = await eventController._select({
|
||||||
|
user_id: req.user.id,
|
||||||
|
include_parent: true,
|
||||||
|
include_unconfirmed: true,
|
||||||
|
show_recurrent: true,
|
||||||
|
show_multidate: true
|
||||||
|
})
|
||||||
|
return res.json(events)
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select events based on params
|
* Select events based on params
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -117,6 +117,7 @@ module.exports = () => {
|
||||||
* [usage example](https://framagit.org/les/gancio/-/blob/master/webcomponents/src/GancioEvents.svelte#L18-42)
|
* [usage example](https://framagit.org/les/gancio/-/blob/master/webcomponents/src/GancioEvents.svelte#L18-42)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
api.get('/events/mine', isAuth, eventController.mine)
|
||||||
api.get('/events', cors, eventController.select)
|
api.get('/events', cors, eventController.select)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue