Merge branch 'master' into gh

This commit is contained in:
les 2021-06-24 21:52:25 +02:00
commit 79445ca8a7
No known key found for this signature in database
GPG key ID: 352918250B012177
134 changed files with 3896 additions and 4272 deletions

2
.gitignore vendored
View file

@ -63,7 +63,7 @@ typings/
.node_repl_history .node_repl_history
# Output of 'npm pack' # Output of 'npm pack'
*.tgz #*.tgz
# Yarn Integrity file # Yarn Integrity file
.yarn-integrity .yarn-integrity

View file

@ -1,5 +1,21 @@
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.0 (alpha)
This release is a complete rewrite of frontend UI and many internals, main changes are:
- Switch UI framework from [element](https://element.eleme.io/) to [vuetify](https://vuetifyjs.com/)
- Distribute package directly from site instead of using npm registry
- Improve docker setup (run as user, fix some data export)
- New logging system (based on [winston](https://github.com/winstonjs/winston))
- Slugify event URL (keeping old one valid)
- Import events from ics and external website using h-event (microformat) improving [`h-event`](https://microformats.org/wiki/h-event) export
- Hide unconfirmed tags and places
- Clean unused places and tags
- Fix tons of issues
### 0.24.0 ### 0.24.0
- New Euskara language from Basque Country, thanks @hacklabkelo - New Euskara language from Basque Country, thanks @hacklabkelo
- fix feed with filters - fix feed with filters

View file

@ -0,0 +1,3 @@
export default function (to, from, savedPosition) {
return { x: 0, y: 0 }
}

View file

@ -1,19 +1,32 @@
import take from 'lodash/take' import take from 'lodash/take'
import get from 'lodash/get' import get from 'lodash/get'
import dayjs from 'dayjs'
export function attributesFromEvents (_events, _tags) { export function attributesFromEvents (_events, _tags) {
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(_tags, 10).map(t => t.tag) const tags = take(_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(), bar: { color: 'green', fillMode: 'outline' } })
const now = dayjs().unix()
function getColor (event) { function getColor (event, where) {
const color = { class: 'vc-rounded-full', color: 'blue', fillMode: 'normal' } const color = { class: 'vc-rounded-full', color: 'blue', fillMode: where === 'base' ? 'light' : 'solid' }
const tag = get(event, 'tags[0]') const tag = get(event, 'tags[0]')
if (event.start_datetime < now) {
if (event.multidate) {
color.fillMode = where === 'base' ? 'light' : 'outline'
if (where === 'base') {
color.class += ' vc-past'
}
} else {
color.class += ' vc-past'
}
}
if (!tag) { return color } if (!tag) { return color }
const idx = tags.indexOf(tag) const idx = tags.indexOf(tag)
if (idx < 0) { return color } if (idx < 0) { return color }
color.color = colors[idx] color.color = colors[idx]
// if (event.start_datetime < now) { color.class += ' vc-past' }
return color return color
} }
@ -31,7 +44,11 @@ export function attributesFromEvents (_events, _tags) {
.filter(e => e.multidate) .filter(e => e.multidate)
.map(e => ({ .map(e => ({
key: e.id, key: e.id,
highlight: getColor(e), 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) } dates: { start: new Date(e.start_datetime * 1000), end: new Date(e.end_datetime * 1000) }
}))) })))

View file

@ -19,7 +19,7 @@ li {
#home { #home {
max-width: 1400px; max-width: 1400px;
padding-right: 0px; padding: 0px;
overflow: hidden; overflow: hidden;
} }
@ -54,22 +54,22 @@ li {
position: relative; position: relative;
flex-direction: column; flex-direction: column;
width: 330px; width: 330px;
max-width: 500px; max-width: 500px !important;
flex-grow: 1; flex-grow: 1;
margin-top: .4em; margin-top: .4em;
margin-right: .4em; margin-right: .4em;
transition: all .5s;
overflow: hidden; overflow: hidden;
.title { .title {
transition: all .5s;
display: block; display: block;
max-height: 3.3em; max-height: 3em;
color: white;
overflow: hidden; overflow: hidden;
margin: 0.5rem 1rem 0.5rem 1rem; margin: 0.5rem 1rem 0.5rem 1rem;
// color: white; font-size: 1.1em !important;
border-bottom: 1px solid rgba(4,4,4,0.2); line-height: 1em !important;
font-size: 1.2em !important;
line-height: 1.1em;
font-weight: 500;
} }
.body { .body {
@ -79,6 +79,8 @@ li {
.img { .img {
width: 100%; width: 100%;
max-height: 250px; max-height: 250px;
min-height: 160px;
background-color: #222;
object-fit: cover; object-fit: cover;
object-position: top; object-position: top;
} }
@ -95,4 +97,23 @@ li {
a { a {
text-decoration: none; text-decoration: none;
} }
}
.v-list {
background-color: #333 !important;
}
.vc-past {
opacity: 0.4;
}
#event {
max-width: 1200px;
}
.tags .v-chip .v-chip__content {
max-width: 120px;
white-space: nowrap;
overflow: hidden;
display: block;
} }

View file

@ -10,11 +10,6 @@ export default {
props: { props: {
announcement: { type: Object, default: () => ({}) } announcement: { type: Object, default: () => ({}) }
}, },
computed: { computed: mapState(['announcements'])
...mapState(['announcements']),
description () {
return this.announcement.announcement.replace(/(<br>)+/g, '<br>')
}
}
} }
</script> </script>

View file

@ -208,6 +208,7 @@ export default {
} }
.menubar { .menubar {
visibility: visible;
opacity: 1 !important; opacity: 1 !important;
} }
@ -220,6 +221,7 @@ export default {
.menubar { .menubar {
transition: opacity .5s; transition: opacity .5s;
opacity: 0; opacity: 0;
visibility: hidden;
// position: absolute; // position: absolute;
} }
@ -257,63 +259,5 @@ export default {
} }
} }
} }
// position: relative;
// overflow-y: auto;
// padding-top: 1.7em;
// &.with-border {
// border: 1px solid #ddd;
// border-radius: 5px;
// }
// .content {
// padding: 0px 5px 0px 5px;
// flex: 1;
// scrollbar-width: thin;
// overflow-y: auto;
// }
// .menububble {
// position: absolute;
// display: flex;
// overflow: hidden;
// opacity: 0;
// z-index: 1;
// background: #dddddd;
// transform: translateX(-50%);
// border-radius: 3px;
// padding: 0.07rem;
// transition: opacity 0.2s, visibility 0.2s, left .2s, bottom .2s;
// visibility: hidden;
// &.is-active {
// opacity: 1;
// visibility: visible;
// }
// input {
// padding: 0;
// margin: 1px;
// display: block;
// border: 0;
// color: #444;
// font-size: .8em;
// border-radius: 3px;
// line-height: 100%;
// transition: width .2s;
// padding-left: 5px;
// flex-grow: 1;
// }
// .fa-icon {
// width: auto;
// font-size: 10px;
// height: 1.4em; /* or any other relative font sizes */
// /* You would have to include the following two lines to make this work in Safari */
// // max-width: 100%;
// max-height: 100%;
// }
// }
// }
</style> </style>

View file

@ -1,7 +1,7 @@
<template lang="pug"> <template lang="pug">
v-card.h-event.event v-card.h-event.event.d-flex
nuxt-link(:to='`/event/${event.id}`') nuxt-link(:to='`/event/${event.slug || event.id}`')
v-img.img(:src="`/media/thumb/${event.image_path || 'logo.svg' }`") v-img.u-featured.img(:src="`/media/thumb/${event.image_path || 'logo.svg' }`")
v-icon.float-right.mr-1(v-if='event.parentId' color='success') mdi-repeat v-icon.float-right.mr-1(v-if='event.parentId' color='success') mdi-repeat
.title.p-name {{event.title}} .title.p-name {{event.title}}
@ -10,9 +10,9 @@
.d-none.dt-end {{event.end_datetime|unixFormat('YYYY-MM-DD HH:mm')}} .d-none.dt-end {{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)") <v-icon>mdi-map-marker</v-icon> {{event.place.name}} a.place.d-block.p-location.pl-0(text color='primary' @click="$emit('placeclick', event.place.id)") <v-icon>mdi-map-marker</v-icon> {{event.place.name}}
v-card-actions.actions.justify-space-between v-card-actions.pt-0.actions.justify-space-between
.tags .tags
v-chip.ml-1.px-2(v-for='tag in event.tags' small v-chip.ml-1.mt-1(v-for='tag in event.tags.slice(0,6)' small
:key='tag' outlined color='primary' @click="$emit('tagclick', tag)") {{tag}} :key='tag' outlined color='primary' @click="$emit('tagclick', tag)") {{tag}}
v-menu(offset-y) v-menu(offset-y)
@ -32,7 +32,16 @@
v-icon mdi-calendar-export v-icon mdi-calendar-export
v-list-item-content v-list-item-content
v-list-item-title {{$t('common.add_to_calendar')}} v-list-item-title {{$t('common.add_to_calendar')}}
v-list-item(v-if='is_mine' :to='`/add/${event.id}`')
v-list-item-icon
v-icon mdi-pencil
v-list-item-content
v-list-item-title {{$t('common.edit')}}
v-list-item(v-if='is_mine' @click='remove(false)')
v-list-item-icon
v-icon(color='error') mdi-delete-forever
v-list-item-content
v-list-item-title {{$t('common.remove')}}
</template> </template>
<script> <script>
import { mapState } from 'vuex' import { mapState } from 'vuex'
@ -41,6 +50,24 @@ export default {
props: { props: {
event: { type: Object, default: () => ({}) } event: { type: Object, default: () => ({}) }
}, },
computed: mapState(['settings']) computed: {
...mapState(['settings']),
is_mine () {
if (!this.$auth.user) {
return false
}
return (
this.event.userId === this.$auth.user.id || this.$auth.user.is_admin
)
}
},
methods: {
async remove () {
const ret = await this.$root.$confirm('event.remove_confirmation')
if (!ret) { return }
await this.$axios.delete(`/event/${this.event.id}`)
this.$emit('destroy', this.event.id)
}
}
} }
</script> </script>

View file

@ -5,20 +5,23 @@
p(v-html="$t('event.follow_me_description', { title: settings.title, account: `@${settings.instance_name}@${domain}`})") p(v-html="$t('event.follow_me_description', { title: settings.title, account: `@${settings.instance_name}@${domain}`})")
v-text-field( v-text-field(
:rules="[$validators.required('common.url')]" :rules="[$validators.required('common.url')]"
:loading='loading'
:label="$t('common.url')" :label="$t('common.url')"
v-model='instance_hostname') v-model='instance_hostname')
p <img class='instance_thumb' :src="instance.thumbnail"/> {{instance.title}} v-btn(v-if='!isDialog' slot='prepend' text :disabled='(!couldGo || !proceed)' :href='link' target='_blank'
:loading='loading' color="primary") {{$t("common.follow")}}
v-card-actions p(slot='append') <img class='instance_thumb' :src="instance.thumbnail"/> {{instance.title}}
v-card-actions(v-if='isDialog')
v-spacer v-spacer
v-btn(color='warning' @click="$emit('close')") {{$t("common.cancel")}} v-btn(v-if='isDialog' color='warning' @click="$emit('close')") {{$t("common.cancel")}}
v-btn(:disabled='(!couldGo || !proceed)' v-btn(:disabled='(!couldGo || !proceed)' :href='link' target='_blank'
:loading='loading' color="primary") {{$t("common.follow")}} :loading='loading' color="primary") {{$t("common.follow")}}
</template> </template>
<script> <script>
import { mapState } from 'vuex' import { mapState } from 'vuex'
import debounce from 'lodash/debounce' import debounce from 'lodash/debounce'
import url from 'url'
export default { export default {
name: 'FollowMe', name: 'FollowMe',
@ -30,19 +33,19 @@ export default {
proceed: false, proceed: false,
instance: {}, instance: {},
loading: false, loading: false,
get_instance_info: debounce(this.getInstanceInfo, 500) get_instance_info: debounce(this.getInstanceInfo, 300)
} }
}, },
computed: { computed: {
...mapState(['settings']), ...mapState(['settings']),
domain () { domain () {
const URL = url.parse(this.settings.baseurl) const URL = new window.URL(this.settings.baseurl)
return URL.hostname return URL.hostname
}, },
couldGo () { couldGo () {
// check if is mastodon // check if is mastodon
this.get_instance_info() this.get_instance_info(this.instance_hostname)
return true return true
}, },
link () { link () {
@ -55,14 +58,19 @@ export default {
if (!this.instance_hostname) { if (!this.instance_hostname) {
return return
} }
this.loading = true
const instance_url = `https://${this.instance_hostname}/api/v1/instance` const instance_url = `https://${this.instance_hostname}/api/v1/instance`
this.$axios.$get(instance_url) this.$axios.$get(instance_url)
.then(ret => { .then(ret => {
this.instance = ret this.instance = ret
this.proceed = true this.proceed = true
this.loading = false
}) })
.catch(e => { .catch(e => {
this.instance = {}
this.proceed = false this.proceed = false
this.loading = false
}) })
} }
} }

View file

@ -1,16 +1,22 @@
<template lang="pug"> <template lang="pug">
v-footer(color='secondary') v-footer(color='secondary')
v-btn(color='primary' text href='https://gancio.org') Gancio {{settings.version}}
v-btn(v-for='link in settings.footerLinks' v-dialog(v-model='showFollowMe' destroy-on-close max-width='700px')
:key='link.label' color='primary' text :href='link.href') {{link.label}} FollowMe(@close='showFollowMe=false' is-dialog)
v-btn(color='primary' text href='https://gancio.org' target='_blank') Gancio <small>{{settings.version}}</small>
v-btn.ml-1(v-for='link in footerLinks'
:key='link.label' color='primary' text
:href='link.href' :to='link.to' :target="link.href && '_blank'") {{link.label}}
v-menu(v-if='settings.enable_trusted_instances && settings.trusted_instances && settings.trusted_instances.length' v-menu(v-if='settings.enable_trusted_instances && settings.trusted_instances && settings.trusted_instances.length'
offset-y bottom open-on-hover transition="slide-y-transition") offset-y bottom open-on-hover transition="slide-y-transition")
template(v-slot:activator="{ on, attrs }") template(v-slot:activator="{ on, attrs }")
v-btn(v-bind='attrs' v-on='on' color='primary' text) {{$t('common.places')}} v-btn.ml-1(v-bind='attrs' v-on='on' color='primary' text) {{$t('common.places')}}
v-list v-list(subheaders two-lines)
v-list-item(v-for='instance in settings.trusted_instances' v-list-item(v-for='instance in settings.trusted_instances'
:key='instance.name' :key='instance.name'
target='_blank'
:href='instance.url' :href='instance.url'
two-line) two-line)
v-list-item-avatar v-list-item-avatar
@ -19,15 +25,30 @@
v-list-item-title {{instance.name}} v-list-item-title {{instance.name}}
v-list-item-subtitle {{instance.label}} v-list-item-subtitle {{instance.label}}
//- v-btn(v-if='settings.enable_federation' text rel='me' @click.prevent='showFollowMe=true') follow me v-btn.ml-1(v-if='settings.enable_federation' color='primary' text rel='me' @click.prevent='showFollowMe=true') {{$t('event.interact_with_me')}}
//- v-btn(nuxt to='/about' text) about
//- v-btn(href='https://blog.gancio.org' text) blog
//- v-btn(href='https://framagit.org/les/gancio' text) source
</template> </template>
<script> <script>
import { mapState } from 'vuex' import { mapState } from 'vuex'
import FollowMe from '../components/FollowMe'
export default { export default {
computed: mapState(['settings']) components: { FollowMe },
data () {
return {
showFollowMe: false
}
},
computed: {
...mapState(['settings']),
footerLinks () {
return this.settings.footerLinks.map(link => {
if (/^https?:\/\//.test(link.href)) {
return { href: link.href, label: link.label }
} else {
return { to: link.href, label: link.label }
}
})
}
}
} }
</script> </script>

View file

@ -5,7 +5,7 @@ div#list
h3(v-if='title') {{title}} h3(v-if='title') {{title}}
v-list-item( v-list-item(
target='_blank' target='_blank'
:to='`/event/${event.id}`' :to='`/event/${event.slug || event.id}`'
v-for='event in computedEvents' v-for='event in computedEvents'
:key='`${event.id}_${event.start_datetime}`' small) :key='`${event.id}_${event.start_datetime}`' small)
v-list-item-content v-list-item-content

View file

@ -2,13 +2,11 @@
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='recurrentFilter && settings.allow_recurrent_event'
v-model='filters.show_recurrent' v-model='showRecurrent'
inset color='primary' inset color='primary'
hide-details hide-details
:label="$t('event.show_recurrent')" :label="$t('event.show_recurrent')")
@change="toggleShowRecurrent") v-autocomplete(
v-autocomplete.mt-0(
:label='$t("common.search")' :label='$t("common.search")'
:items='keywords' :items='keywords'
hide-details hide-details
@ -51,6 +49,19 @@ export default {
}, },
computed: { computed: {
...mapState(['tags', 'places', 'settings']), ...mapState(['tags', 'places', 'settings']),
showRecurrent: {
get () {
return this.filters.show_recurrent
},
set (v) {
const filters = {
tags: this.filters.tags,
places: this.filters.places,
show_recurrent: v
}
this.$emit('update', filters)
}
},
selectedFilters () { 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 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)) const places = this.places.filter(p => this.filters.places.includes(p.id))
@ -74,14 +85,6 @@ export default {
} }
this.$emit('update', filters) this.$emit('update', filters)
}, },
toggleShowRecurrent (v) {
const filters = {
tags: this.filters.tags,
places: this.filters.places,
show_recurrent: v
}
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),

View file

@ -5,15 +5,17 @@
v-dialog(v-model='dialog' width='800px') v-dialog(v-model='dialog' width='800px')
v-card v-card
v-card-title {{$t('admin.new_announcement')}} v-card-title {{$t('admin.new_announcement')}}
v-card-text v-card-text.px-0
v-form(v-model='valid' ref='announcement' @submit.prevent='save') v-form(v-model='valid' ref='announcement' @submit.prevent='save' lazy-validation)
v-text-field(v-model='announcement.title' :label='$t("common.title")') v-text-field.col-12(v-model='announcement.title'
Editor.mt-2(v-model='announcement.announcement' :rules="[$validators.required('common.title')]"
:label='$t("common.title")')
Editor.col-12(v-model='announcement.announcement'
border no-save max-height='400px' :placeholder="$t('common.description')") border no-save max-height='400px' :placeholder="$t('common.description')")
v-card-actions v-card-actions
v-spacer v-spacer
v-btn(@click='dialog=false' color='error') {{$t('common.cancel')}} v-btn(@click='dialog=false' color='error') {{$t('common.cancel')}}
v-btn(@click='save' color='primary' :disabled='loading' :loading='loading') {{$t(`common.${editing?'save':'send'}`)}} v-btn(@click='save' color='primary' :disabled='!valid || loading' :loading='loading') {{$t(`common.${editing?'save':'send'}`)}}
v-btn(@click='openDialog' text color='primary') <v-icon>mdi-plus</v-icon> {{$t('common.add')}} v-btn(@click='openDialog' text color='primary') <v-icon>mdi-plus</v-icon> {{$t('common.add')}}
v-card-text v-card-text
@ -86,6 +88,7 @@ export default {
}) })
}, },
async save () { async save () {
if (!this.$refs.announcement.validate()) { return }
this.loading = true this.loading = true
try { try {
let announcement = null let announcement = null

View file

@ -44,18 +44,19 @@
v-card v-card
v-card-title {{$t('admin.add_trusted_instance')}} v-card-title {{$t('admin.add_trusted_instance')}}
v-card-text v-card-text
v-form(v-model='valid' ref='form' lazy-validation) v-form(v-model='valid' @submit.prevent='createTrustedInstance' ref='form' lazy-validation)
v-text-field.mt-4(v-model='instance_url' v-text-field.mt-4(v-model='instance_url'
persistent-hint persistent-hint
:rules="[$validators.required('common.url')]" :rules="[$validators.required('common.url')]"
:loading='loading'
:hint="$t('admin.add_trusted_instance')" :hint="$t('admin.add_trusted_instance')"
:label="$t('common.url')") :label="$t('common.url')")
v-card-actions v-card-actions
v-spacer v-spacer
v-btn(color='error' @click='dialogAddInstance=false') {{$t('common.cancel')}} v-btn(color='error' @click='dialogAddInstance=false') {{$t('common.cancel')}}
v-btn(color='primary' :disabled='!valid' @click='createTrustedInstance') {{$t('common.ok')}} v-btn(color='primary' :disabled='!valid || loading' :loading='loading' @click='createTrustedInstance') {{$t('common.ok')}}
v-btn.mt-4(@click='dialogAddInstance = true' color='primary' text) <v-icon>mdi-plus</v-icon> Add instance v-btn.mt-4(@click='dialogAddInstance = true' color='primary' text) <v-icon>mdi-plus</v-icon> {{$t('admin.add_instance')}}
v-data-table( v-data-table(
v-if='settings.trusted_instances.length' v-if='settings.trusted_instances.length'
:hide-default-footer='settings.trusted_instances.length<10' :hide-default-footer='settings.trusted_instances.length<10'
@ -79,6 +80,7 @@ export default {
instance_place: $store.state.settings.instance_place, instance_place: $store.state.settings.instance_place,
url2host: $options.filters.url2host, url2host: $options.filters.url2host,
dialogAddInstance: false, dialogAddInstance: false,
loading: false,
valid: false, valid: false,
headers: [ headers: [
{ value: 'name', text: 'Name' }, { value: 'name', text: 'Name' },
@ -115,6 +117,7 @@ export default {
...mapActions(['setSetting']), ...mapActions(['setSetting']),
async createTrustedInstance () { async createTrustedInstance () {
if (!this.$refs.form.validate()) { return } if (!this.$refs.form.validate()) { return }
this.loading = true
try { try {
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}`
@ -133,6 +136,7 @@ export default {
} catch (e) { } catch (e) {
this.$root.$message(e, { color: 'error' }) this.$root.$message(e, { color: 'error' })
} }
this.loading = false
}, },
async deleteInstance (instance) { async deleteInstance (instance) {
const ret = await this.$root.$confirm('admin.delete_trusted_instance_confirm') const ret = await this.$root.$confirm('admin.delete_trusted_instance_confirm')

View file

@ -5,37 +5,27 @@
v-row v-row
v-col(:span='12') v-col(:span='12')
span {{$t('common.instances')}} span {{$t('common.instances')}}
//- v-text-field(v-model='instancesFilter' :placeholder="$t('admin.filter_instances')") v-text-field(v-model='instancesFilter' :placeholder="$t('admin.filter_instances')")
v-data-table(:items='instances' v-data-table(:items='instances'
:items-per-page='5' :items-per-page='5'
:search='instancesFilter'
:hide-default-footer='instances.length<5' :hide-default-footer='instances.length<5'
dense :headers='instancesHeader' dense :headers='instancesHeader'
@click:row='instanceSelected') @click:row='instanceSelected')
//- el-table-column(label='Domain' width='180') template(v-slot:item.blocked="{ item }")
//- template(slot-scope='data') v-icon(v-if='item.blocked') mdi-checkbox-intermediate
//- span(slot='reference') {{data.row.domain}} v-icon(v-else) mdi-checkbox-blank-outline
//- el-table-column(label='Name' width='100')
//- template(slot-scope='data')
//- span(slot='reference') {{data.row.name}}
//- el-table-column(:label="$t('common.users')" width='70')
//- template(slot-scope='data')
//- span(slot='reference') {{data.row.users}}
//- el-table-column(:label="$t('common.actions')" width='120')
//- template(slot-scope='data')
//- el-button-group
//- el-button(size='mini'
//- :type='data.row.blocked?"danger":"warning"'
//- @click='toggleBlock(data.row)') {{data.row.blocked?$t('admin.unblock'):$t('admin.block')}}
v-col(:span='11') v-col(:span='11')
span {{$t('common.users')}} span {{$t('common.users')}}
//- v-text-field(v-model='usersFilter' :placeholder="$t('admin.filter_users')") v-text-field(v-model='usersFilter' :placeholder="$t('admin.filter_users')")
v-data-table(:items='users' v-data-table(:items='users'
:items-per-page='5' :items-per-page='5'
:search='usersFilter'
:hide-default-footer='users.length<5' :hide-default-footer='users.length<5'
dense :headers='usersHeader') dense :headers='usersHeader')
template(v-slot:item.username="{item}") //- template(v-slot:item.username="{item}")
a(:href='item.ap_id') {{item.object.preferredUsername}} //- a(:href='item.ap_id') {{item.object.preferredUsername}}
//- el-table-column(:label="$t('common.user')" width='150') //- el-table-column(:label="$t('common.user')" width='150')
//- template(slot-scope='data') //- template(slot-scope='data')
//- span(slot='reference') //- span(slot='reference')
@ -86,16 +76,19 @@ export default {
resources: [], resources: [],
users: [], users: [],
usersHeader: [ usersHeader: [
{ value: 'username', text: 'Name' } { value: 'object.preferredUsername', text: 'Name' }
], ],
instancesHeader: [ instancesHeader: [
{ value: 'domain', text: 'Domain' }, { value: 'domain', text: 'Domain' },
{ value: 'name', text: 'Name' }, { value: 'name', text: 'Name' },
{ value: 'users', text: 'N' } { value: 'blocked', text: 'Blocked' },
{ value: 'users', text: 'known users' }
], ],
resourcesHeader: [ resourcesHeader: [
{ value: '', text: '' } { value: '', text: '' }
] ],
usersFilter: '',
instancesFilter: ''
} }
}, },
computed: { computed: {

View file

@ -38,19 +38,19 @@
persistent-hint persistent-hint
@blur='save("description", description)') @blur='save("description", description)')
v-switch.mt-5(v-model='allow_registration' v-switch.mt-4(v-model='allow_registration'
inset inset
:label="$t('admin.allow_registration_description')") :label="$t('admin.allow_registration_description')")
v-switch.mt-4(v-model='allow_anon_event' v-switch.mt-1(v-model='allow_anon_event'
inset inset
:label="$t('admin.allow_anon_event')") :label="$t('admin.allow_anon_event')")
v-switch.mt-4(v-model='allow_recurrent_event' v-switch.mt-1(v-model='allow_recurrent_event'
inset inset
:label="$t('admin.allow_recurrent_event')") :label="$t('admin.allow_recurrent_event')")
v-switch.mt-4(v-if='allow_recurrent_event' v-switch.mt-1(v-if='allow_recurrent_event'
v-model='recurrent_event_visible' v-model='recurrent_event_visible'
inset inset
:label="$t('admin.recurrent_event_visible')") :label="$t('admin.recurrent_event_visible')")

View file

@ -12,9 +12,9 @@
v-img(:src='`${settings.baseurl}/favicon.ico?${logoKey}`' v-img(:src='`${settings.baseurl}/favicon.ico?${logoKey}`'
max-width="100px" max-height="80px" contain) max-width="100px" max-height="80px" contain)
v-switch.mt-5(v-model='is_dark' //- v-switch.mt-5(v-model='is_dark'
inset //- inset
:label="$t('admin.is_dark')") //- :label="$t('admin.is_dark')")
//- TODO choose theme colors //- TODO choose theme colors
//- v-row //- v-row
@ -34,7 +34,7 @@
v-dialog(v-model='linkModal' width='500') v-dialog(v-model='linkModal' width='500')
v-card v-card
v-card-title {{$t('admin.add_footer_link')}} v-card-title {{$t('admin.footer_links')}}
v-card-text v-card-text
v-form(v-model='valid' ref='linkModalForm') v-form(v-model='valid' ref='linkModalForm')
v-text-field(v-model='link.label' v-text-field(v-model='link.label'
@ -52,16 +52,16 @@
v-card-text v-card-text
v-btn(color='primary' text @click='openLinkModal') <v-icon>mdi-plus</v-icon> {{$t('admin.add_link')}} v-btn(color='primary' text @click='openLinkModal') <v-icon>mdi-plus</v-icon> {{$t('admin.add_link')}}
v-btn(color='warning' text @click='reset') <v-icon>mdi-restore</v-icon> {{$t('common.reset')}} v-btn(color='warning' text @click='reset') <v-icon>mdi-restore</v-icon> {{$t('common.reset')}}
v-list v-list.mt-1(two-line subheader)
v-list-item(v-for='link in settings.footerLinks' v-list-item(v-for='link in settings.footerLinks'
:key='`${link.label}`') :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.float-right(icon color='accent' @click='editFooterLink(link)') //- v-btn.float-right(icon color='accent' @click='editFooterLink(link)')
v-icon mdi-pencil //- v-icon mdi-pencil
v-btn(icon color='error' @click='removeFooterLink(link)') v-btn(icon color='error' @click.stop='removeFooterLink(link)')
v-icon mdi-delete-forever v-icon mdi-delete-forever
</template> </template>
@ -75,9 +75,9 @@ export default {
valid: false, valid: false,
logoKey: 0, logoKey: 0,
link: { href: '', label: '' }, link: { href: '', label: '' },
linkModal: false, linkModal: false
menu: [false, false, false, false], // menu: [false, false, false, false]
colors: { primary: '', secondary: '', accent: '', error: '', info: '', success: '', warning: '' } // colors: { primary: '', secondary: '', accent: '', error: '', info: '', success: '', warning: '' }
// primary: {}, // primary: {},
// secondary: {} // secondary: {}
// } // }
@ -95,29 +95,33 @@ export default {
this.setSetting({ key: 'theme.is_dark', value }) this.setSetting({ key: 'theme.is_dark', value })
} }
} }
// 'colors[0]': { // 'colors[0]': {
// get () { // get () {
// return this.settings['theme.colors'] || [0, 0] // return this.settings['theme.colors'] || [0, 0]
// }, // },
// set (value) { // set (value) {
// console.error(value) // console.error(value)
// if (!value) { return } // if (!value) { return }
// this.setSetting({ key: 'theme.primary', value }) // this.setSetting({ key: 'theme.primary', value })
// if (this.settings['theme.is_dark']) { // if (this.settings['theme.is_dark']) {
// this.$vuetify.theme.themes.dark.primary = value // this.$vuetify.theme.themes.dark.primary = value
// } else { // } else {
// this.$vuetify.theme.themes.light.primary = value // this.$vuetify.theme.themes.light.primary = value
// } // }
// } // }
// } // }
}, },
methods: { methods: {
...mapActions(['setSetting']), ...mapActions(['setSetting']),
reset () { reset () {
this.setSetting({ key: 'footerLinks', value: [{ href: '/about', label: 'about' }] }) this.setSetting({
key: 'footerLinks',
value: [
{ href: '/about', label: 'about' },
{ href: '/', label: 'home' }]
})
}, },
forceLogoReload () { forceLogoReload () {
this.$refs.upload.reset()
this.logoKey++ this.logoKey++
}, },
resetLogo (e) { resetLogo (e) {

View file

@ -33,6 +33,9 @@
:items='users' :items='users'
:hide-default-footer='users.length<5' :hide-default-footer='users.length<5'
:search='search') :search='search')
template(v-slot:item.is_active='{item}')
v-icon(v-if='item.is_active' color='success') mdi-check
v-icon(v-else color='warning') mdi-close
template(v-slot:item.actions='{item}') template(v-slot:item.actions='{item}')
v-btn(text small @click='toggle(item)' v-btn(text small @click='toggle(item)'
:color='item.is_active?"warning":"success"') {{item.is_active?$t('common.disable'):$t('common.enable')}} :color='item.is_active?"warning":"success"') {{item.is_active?$t('common.disable'):$t('common.enable')}}
@ -62,6 +65,7 @@ export default {
search: '', search: '',
headers: [ headers: [
{ value: 'email', text: 'Email' }, { value: 'email', text: 'Email' },
{ value: 'description', text: 'Description' },
{ value: 'is_active', text: 'Active' }, { value: 'is_active', text: 'Active' },
{ value: 'actions', text: 'Actions', align: 'right' } { value: 'actions', text: 'Actions', align: 'right' }
] ]
@ -70,13 +74,17 @@ export default {
computed: mapState(['settings']), computed: mapState(['settings']),
methods: { methods: {
async deleteUser (user) { async deleteUser (user) {
const ret = await this.$root.$confirm('admin.delete_user_confirm') const ret = await this.$root.$confirm('admin.delete_user_confirm', { user: user.email })
if (!ret) { return } if (!ret) { return }
await this.$axios.delete(`/user/${user.id}`) await this.$axios.delete(`/user/${user.id}`)
this.$root.$message('admin.user_remove_ok') this.$root.$message('admin.user_remove_ok')
this.users_ = this.users_.filter(u => u.id !== user.id) this.users_ = this.users_.filter(u => u.id !== user.id)
}, },
toggle (user) { async toggle (user) {
if (user.is_active) {
const ret = await this.$root.$confirm('admin.disable_user_confirm', { user: user.email })
if (!ret) { return }
}
user.is_active = !user.is_active user.is_active = !user.is_active
this.$axios.$put('/user', user) this.$axios.$put('/user', user)
}, },

View file

@ -6,6 +6,8 @@
"host": "localhost", "host": "localhost",
"port": 13120 "port": 13120
}, },
"log_level": "debug",
"log_path": "./logs",
"db": { "db": {
"dialect": "sqlite", "dialect": "sqlite",
"storage": "./db.sqlite", "storage": "./db.sqlite",
@ -20,6 +22,5 @@
"secure": true, "secure": true,
"host": "" "host": ""
}, },
"admin_email": "admin", "admin_email": "admin"
"secret": "notsosecret"
} }

View file

@ -7,7 +7,7 @@ POST
**`/event`** **`/event`**
> info "info" > info "info"
> `Content-Type` has to be `multipart/form-data` 'cause support image upload > `Content-Type` has to be `multipart/form-data` to support image upload
**Params** **Params**

3
docs/.gitignore vendored
View file

@ -1,2 +1,5 @@
_site
.sass-cache .sass-cache
.jekyll-cache
.jekyll-metadata .jekyll-metadata
vendor

View file

@ -8,7 +8,7 @@ source "https://rubygems.org"
# #
# This will help ensure the proper Jekyll version is running. # This will help ensure the proper Jekyll version is running.
# Happy Jekylling! # Happy Jekylling!
gem "jekyll", "~> 3.8.6" gem "jekyll"
# This is the default theme for new Jekyll sites. You may change this to anything you like. # This is the default theme for new Jekyll sites. You may change this to anything you like.
#gem "minima", "~> 2.0" #gem "minima", "~> 2.0"
@ -21,9 +21,10 @@ gem "mini_magick"
# If you have any plugins, put them here! # If you have any plugins, put them here!
group :jekyll_plugins do group :jekyll_plugins do
gem "jekyll-feed", "~> 0.6" gem "jekyll-feed"
gem "jemoji" gem "jemoji"
gem "premonition", "~> 2.0.0" gem "premonition"
gem "jekyll-default-layout"
end end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem # Windows does not include zoneinfo files, so bundle the tzinfo-data gem

View file

@ -1,105 +1,117 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
activesupport (5.2.3) activesupport (6.0.3.7)
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)
tzinfo (~> 1.1) tzinfo (~> 1.1)
addressable (2.6.0) zeitwerk (~> 2.2, >= 2.2.2)
public_suffix (>= 2.0.2, < 4.0) addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
colorator (1.1.0) colorator (1.1.0)
concurrent-ruby (1.1.5) concurrent-ruby (1.1.8)
em-websocket (0.5.1) em-websocket (0.5.2)
eventmachine (>= 0.12.9) eventmachine (>= 0.12.9)
http_parser.rb (~> 0.6.0) http_parser.rb (~> 0.6.0)
eventmachine (1.2.7) eventmachine (1.2.7)
ffi (1.11.1) ffi (1.15.1)
forwardable-extended (2.6.0) forwardable-extended (2.6.0)
gemoji (3.0.1) gemoji (3.0.1)
html-pipeline (2.11.0) html-pipeline (2.14.0)
activesupport (>= 2) activesupport (>= 2)
nokogiri (>= 1.4) nokogiri (>= 1.4)
http_parser.rb (0.6.0) http_parser.rb (0.6.0)
i18n (0.9.5) i18n (1.8.10)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
jekyll (3.8.6) jekyll (4.2.0)
addressable (~> 2.4) addressable (~> 2.4)
colorator (~> 1.0) colorator (~> 1.0)
em-websocket (~> 0.5) em-websocket (~> 0.5)
i18n (~> 0.7) i18n (~> 1.0)
jekyll-sass-converter (~> 1.0) jekyll-sass-converter (~> 2.0)
jekyll-watch (~> 2.0) jekyll-watch (~> 2.0)
kramdown (~> 1.14) kramdown (~> 2.3)
kramdown-parser-gfm (~> 1.0)
liquid (~> 4.0) liquid (~> 4.0)
mercenary (~> 0.3.3) mercenary (~> 0.4.0)
pathutil (~> 0.9) pathutil (~> 0.9)
rouge (>= 1.7, < 4) rouge (~> 3.0)
safe_yaml (~> 1.0) safe_yaml (~> 1.0)
jekyll-feed (0.12.1) terminal-table (~> 2.0)
jekyll-default-layout (0.1.5)
jekyll (>= 3.0, < 5.0)
jekyll-feed (0.15.1)
jekyll (>= 3.7, < 5.0) jekyll (>= 3.7, < 5.0)
jekyll-sass-converter (1.5.2) jekyll-sass-converter (2.1.0)
sass (~> 3.4) sassc (> 2.0.1, < 3.0)
jekyll-seo-tag (2.6.1) jekyll-seo-tag (2.7.1)
jekyll (>= 3.3, < 5.0) jekyll (>= 3.8, < 5.0)
jekyll-watch (2.2.1) jekyll-watch (2.2.1)
listen (~> 3.0) listen (~> 3.0)
jemoji (0.11.0) jemoji (0.12.0)
gemoji (~> 3.0) gemoji (~> 3.0)
html-pipeline (~> 2.2) html-pipeline (~> 2.2)
jekyll (>= 3.0, < 5.0) jekyll (>= 3.0, < 5.0)
just-the-docs (0.2.5) just-the-docs (0.3.3)
jekyll (~> 3.8.5) jekyll (>= 3.8.5)
jekyll-seo-tag (~> 2.0) jekyll-seo-tag (~> 2.0)
rake (~> 12.3.1) rake (>= 12.3.1, < 13.1.0)
kramdown (1.17.0) kramdown (2.3.1)
rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
liquid (4.0.3) liquid (4.0.3)
listen (3.1.5) listen (3.5.1)
rb-fsevent (~> 0.9, >= 0.9.4) rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.7) rb-inotify (~> 0.9, >= 0.9.10)
ruby_dep (~> 1.2) mercenary (0.4.0)
mercenary (0.3.6) mini_magick (4.11.0)
mini_magick (4.9.5) mini_portile2 (2.5.3)
mini_portile2 (2.4.0) minitest (5.14.4)
minitest (5.11.3) nokogiri (1.11.7)
nokogiri (1.10.4) mini_portile2 (~> 2.5.0)
mini_portile2 (~> 2.4.0) racc (~> 1.4)
pathutil (0.16.2) pathutil (0.16.2)
forwardable-extended (~> 2.6) forwardable-extended (~> 2.6)
premonition (2.0.0) premonition (4.0.1)
public_suffix (3.1.1) jekyll (>= 3.7, < 5.0)
rake (12.3.1) public_suffix (4.0.6)
rb-fsevent (0.10.3) racc (1.5.2)
rb-inotify (0.10.0) rake (13.0.3)
rb-fsevent (0.11.0)
rb-inotify (0.10.1)
ffi (~> 1.0) ffi (~> 1.0)
rouge (3.6.0) rexml (3.2.5)
ruby_dep (1.5.0) rouge (3.26.0)
safe_yaml (1.0.5) safe_yaml (1.0.5)
sass (3.7.4) sassc (2.4.0)
sass-listen (~> 4.0.0) ffi (~> 1.9)
sass-listen (4.0.0) terminal-table (2.0.0)
rb-fsevent (~> 0.9, >= 0.9.4) unicode-display_width (~> 1.1, >= 1.1.1)
rb-inotify (~> 0.9, >= 0.9.7)
thread_safe (0.3.6) thread_safe (0.3.6)
tzinfo (1.2.5) tzinfo (1.2.9)
thread_safe (~> 0.1) thread_safe (~> 0.1)
tzinfo-data (1.2019.2) tzinfo-data (1.2021.1)
tzinfo (>= 1.0.0) tzinfo (>= 1.0.0)
unicode-display_width (1.7.0)
wdm (0.1.1) wdm (0.1.1)
zeitwerk (2.4.2)
PLATFORMS PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
jekyll (~> 3.8.6) jekyll
jekyll-feed (~> 0.6) jekyll-default-layout
jekyll-feed
jemoji jemoji
just-the-docs just-the-docs
mini_magick mini_magick
premonition (~> 2.0.0) premonition
tzinfo (~> 1.2) tzinfo (~> 1.2)
tzinfo-data tzinfo-data
wdm (~> 0.1.0) wdm (~> 0.1.0)
BUNDLED WITH BUNDLED WITH
2.0.2 2.1.4

View file

@ -17,7 +17,7 @@ title: Gancio
email: lesion@autistici.org email: lesion@autistici.org
description: >- # this means to ignore newlines until "baseurl:" description: >- # this means to ignore newlines until "baseurl:"
A shared agenda for local communities with AP support A shared agenda for local communities with AP support
baseurl: "/" # the subpath of your site, e.g. /blog baseurl: "" # the subpath of your site, e.g. /blog
url: "https://gancio.org" # the base hostname & protocol for your site, e.g. http://example.com url: "https://gancio.org" # the base hostname & protocol for your site, e.g. http://example.com
#twitter_username: jekyllrb #twitter_username: jekyllrb
#github_username: jekyll #github_username: jekyll
@ -29,6 +29,7 @@ plugins:
- jekyll-feed - jekyll-feed
- jemoji - jemoji
- premonition - premonition
- jekyll-default-layout
search_enabled: true search_enabled: true
@ -37,11 +38,15 @@ aux_links:
- https://blog.gancio.org - https://blog.gancio.org
"Source": "Source":
- https://framagit.org/les/gancio - https://framagit.org/les/gancio
"Forum":
- https://socialhub.activitypub.rocks/c/software/gancio
"Mastodon": "Mastodon":
- https://mastodon.cisti.org/@gancio - https://mastodon.cisti.org/@gancio
gh_edit_link: true # show or hide edit this page link
gh_edit_link_text: "Edit this page"
gh_edit_repository: "https://framagit.org/les/gancio" # the github URL for your repo
gh_edit_branch: "master/docs" # the branch that your docs is served from
# gh_edit_source: docs # the source that your files originate from
gh_edit_view_mode: "-/tree" # "tree" or "edit" if you want the user to jump into the editor immediately

View file

@ -34,11 +34,11 @@ and use the calendar to select one or more days.
## How does it work ## How does it work
Behind the scene, gancio ensures that at least the next three occurrences Behind the scene, gancio ensures that at least the next occurrence
of the event are always created. It creates that single events by copying of the event are created. It creates that single event by copying
the properties of the parent event, so if you modify the parent the properties of the parent event, so if you modify the parent
event's title, or the day of the week, each newly created occurrence will took event's title, or the day of the week, each newly created occurrence will took
the new title and the new selected day. Old occurrences will be preserved. the new title and the new selected day while old occurrences will be preserved.
You can edit a specific event occurrence with more details, a different You can edit a specific event occurrence with more details, a different
poster/flyer, a different title/description or decide to completely hide it. poster/flyer, a different title/description or decide to completely hide it.

View file

@ -2,9 +2,10 @@
layout: default layout: default
title: Usage title: Usage
permalink: /usage permalink: /usage
nav_order: 4 nav_order: 1
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)

View file

@ -0,0 +1,72 @@
---
permalink: /assets/js/search-data.json
---
{
{%- assign i = 0 -%}
{%- assign pages_array = | split: -%}
{%- assign pages_array = pages_array | push: site.html_pages -%}
{%- if site.just_the_docs.collections -%}
{%- for collection_entry in site.just_the_docs.collections -%}
{%- assign collection_key = collection_entry[0] -%}
{%- assign collection_value = collection_entry[1] -%}
{%- assign collection = site[collection_key] -%}
{%- if collection_value.search_exclude != true -%}
{%- assign pages_array = pages_array | push: collection -%}
{%- endif -%}
{%- endfor -%}
{%- endif -%}
{%- for pages in pages_array -%}
{%- for page in pages -%}
{%- if page.title and page.search_exclude != true -%}
{%- assign page_content = page.content -%}
{%- assign heading_level = site.search.heading_level | default: 2 -%}
{%- for j in (2..heading_level) -%}
{%- assign tag = '<h' | append: j -%}
{%- assign closing_tag = '</h' | append: j -%}
{%- assign page_content = page_content | replace: tag, '<h1' | replace: closing_tag, '</h1' -%}
{%- endfor -%}
{%- assign parts = page_content | split: '<h1' -%}
{%- assign title_found = false -%}
{%- for part in parts offset: 1 -%}
{%- assign titleAndContent = part | split: '</h1>' -%}
{%- assign title = titleAndContent[0] | replace_first: '>', '<h1>' | split: '<h1>' -%}
{%- assign title = title[1] | strip_html -%}
{%- assign content = titleAndContent[1] -%}
{%- assign url = page.url -%}
{%- if title == page.title and parts[0] == '' -%}
{%- assign title_found = true -%}
{%- else -%}
{%- assign id = titleAndContent[0] -%}
{%- assign id = id | split: 'id="' -%}
{%- if id.size == 2 -%}
{%- assign id = id[1] -%}
{%- assign id = id | split: '"' -%}
{%- assign id = id[0] -%}
{%- capture url -%}{{ url | append: '#' | append: id }}{%- endcapture -%}
{%- endif -%}
{%- endif -%}
{%- unless i == 0 -%},{%- endunless -%}
"{{ i }}": {
"doc": {{ page.title | jsonify }},
"title": {{ title | jsonify }},
"content": {{ content | replace: '</h', ' . </h' | replace: '<hr', ' . <hr' | replace: '</p', ' . </p' | replace: '<ul', ' . <ul' | replace: '</ul', ' . </ul' | replace: '<ol', ' . <ol' | replace: '</ol', ' . </ol' | replace: '</tr', ' . </tr' | replace: '<li', ' | <li' | replace: '</li', ' | </li' | replace: '</td', ' | </td' | replace: '<td', ' | <td' | replace: '</th', ' | </th' | replace: '<th', ' | <th' | strip_html | remove: 'Table of contents' | normalize_whitespace | replace: '. . .', '.' | replace: '. .', '.' | replace: '| |', '|' | append: ' ' | jsonify }},
"url": "{{ url | relative_url }}",
"relUrl": "{{ url }}"
}
{%- assign i = i | plus: 1 -%}
{%- endfor -%}
{%- unless title_found -%}
{%- unless i == 0 -%},{%- endunless -%}
"{{ i }}": {
"doc": {{ page.title | jsonify }},
"title": {{ page.title | jsonify }},
"content": {{ parts[0] | replace: '</h', ' . </h' | replace: '<hr', ' . <hr' | replace: '</p', ' . </p' | replace: '<ul', ' . <ul' | replace: '</ul', ' . </ul' | replace: '<ol', ' . <ol' | replace: '</ol', ' . </ol' | replace: '</tr', ' . </tr' | replace: '<li', ' | <li' | replace: '</li', ' | </li' | replace: '</td', ' | </td' | replace: '<td', ' | <td' | replace: '</th', ' | </th' | replace: '<th', ' | <th' | strip_html | remove: 'Table of contents' | normalize_whitespace | replace: '. . .', '.' | replace: '. .', '.' | replace: '| |', '|' | append: ' ' | jsonify }},
"url": "{{ page.url | relative_url }}",
"relUrl": "{{ page.url }}"
}
{%- assign i = i | plus: 1 -%}
{%- endunless -%}
{%- endif -%}
{%- endfor -%}
{%- endfor %}
}

View file

@ -1,30 +1,21 @@
<head> <!DOCTYPE html> <html lang="en-US">
<meta charset="UTF-8"> <head> <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge"> <meta http-equiv="X-UA-Compatible" content="IE=Edge">
<title>{{ page.title }} - {{ site.title }}</title>
{% if page.description %}
<meta name="Description" content="{{ page.description }}">
{% endif %}
<title>{{ page.title }} - {{ site.title }}</title> <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 }}">
{% if page.description %} <link rel="stylesheet" href="{{ '/assets/css/premonition.css' | absolute_url }}">
<meta name="Description" content="{{ page.description }}"> <script type="text/javascript" src="{{ '/assets/js/vendor/lunr.min.js' | absolute_url }}"></script>
{% endif %} <script type="text/javascript" src="{{ '/assets/js/just-the-docs.js' | absolute_url }}"></script>
<script src="{{ '/assets/js/jquery-3.3.1.min.js' | absolute_url }}"></script>
<link rel="shortcut icon" href="{{ "favicon.ico" | absolute_url }}" type="image/x-icon"> <link rel="stylesheet" href="{{ '/assets/css/jquery.fancybox.min.css' | absolute_url }}">
<link rel="stylesheet" href="{{ "/assets/css/just-the-docs.css" | absolute_url }}"> <script src="{{ '/assets/js/jquery.fancybox.min.js' | absolute_url }}"></script>
<link rel="stylesheet" href="{{ "/assets/css/style.css" | absolute_url }}"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="{{ "/assets/css/premonition.css" | absolute_url }}">
<link rel="stylesheet" href="{{ "/assets/css/fa.min.css" | absolute_url }}">
{% if site.search_enabled != nil %}
<script type="text/javascript" src="{{ "/assets/js/vendor/lunr.min.js" | absolute_url }}"></script>
{% endif %}
<script type="text/javascript" src="{{ "/assets/js/just-the-docs.js" | absolute_url }}"></script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="{{ "/assets/js/jquery-3.3.1.min.js" | absolute_url }}"></script>
<link rel="stylesheet" href="{{ "/assets/css/jquery.fancybox.min.css" | absolute_url }}"/>
<script src="{{ "/assets/js/jquery.fancybox.min.js" | absolute_url }}"></script>
{% seo %} {% seo %}
</head> </head>

View file

@ -0,0 +1,168 @@
$default-color: #5bc0de;
$default-light-color: #e3edf2;
$info-color: #50af51;
$info-light-color: #f3f8f3;
$warning-color: #f0ad4e;
$warning-light-color: #fcf8f2;
$error-color: #d9534f;
$error-light-color: #fdf7f7;
$content-color: rgba(0, 0, 0, 0.5);
$citation-color: #495057;
$citation-light-color: #f8f9fa;
$svg-default-color: "5bc0de";
$svg-info-color: "50af51";
$svg-warning-color: "f0ad4e";
$svg-error-color: "d9534f";
$svg-citation-color: "495057";
.nav-list .nav-list-item {
font-size: 18px !important;
padding: 4px 0px 0px 0px !important;
}
.main {
max-width: 1200px;
}
.premonition {
display: grid;
grid-template-columns: 43px auto;
padding-top: 13px;
padding-bottom: 13px;
margin: 30px 0 30px 0;
background-color: $default-light-color;
border-left: 4px solid $default-color;
color: $default-color;
code {
background-color: #fff;
color: $default-color;
}
.header {
font-weight: 500;
font-size: 1.1rem;
color: $default-color;
padding-bottom: 6px;
}
.content {
color: $content-color;
padding-left: 20px;
padding-right: 40px;
}
p {
margin-top: 0;
margin-bottom: 0;
}
@mixin box-type($c, $lc) {
background-color: $lc;
color: $c;
border-color: $c;
a {
color: $c;
text-decoration: underline;
}
code {
color: $c;
}
.header {
color: $c;
}
}
&.info {
@include box-type($info-color, $info-light-color);
}
&.warning {
@include box-type($warning-color, $warning-light-color);
}
&.error {
@include box-type($error-color, $error-light-color);
}
&.citation {
@include box-type($citation-color, $citation-light-color);
blockquote {
border-left: 0;
}
}
.fa,
.fas,
.far,
.fal,
.fab {
font-size: 28px;
opacity: 0.3;
padding-top: 2px;
padding-left: 20px;
}
& > svg {
opacity: 0.6;
margin-top: 0.36rem;
margin-left: 0.7rem;
}
@mixin pn-icon($pre, $color, $post) {
border: 0;
margin: 3px 0 0 14px;
background-repeat: no-repeat;
background-color: transparent;
background-image: url($pre + "%23" + $color + $post);
background-size: 28px 28px;
width: 28px;
height: 28px;
opacity: 0.3;
}
/* Autogenerated code */
&.pn-note {
@include pn-icon(
"data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='165px' height='165px' viewBox='0 0 165 165' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Cg id='Page-1' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E%3Cpath d='M40,0.5 C18.1847524,0.5 0.5,18.1847524 0.5,40 L0.5,125 C0.5,146.815248 18.1847524,164.5 40,164.5 L125,164.5 C146.815248,164.5 164.5,146.815248 164.5,125 L164.5,40 C164.5,18.1847524 146.815248,0.5 125,0.5 L40,0.5 Z M73.9421225,101.035652 C105.680247,64.0622419 122.973943,44.3076275 125.890221,41.6952841 C129.340278,37.6445263 135.770506,37.5263132 140.538208,40.8455453 C145.631474,44.3914319 146.755991,50.3287958 142.263833,56.0881327 C114.758351,89.0776641 89.30795,118.028061 81.5674939,125.633994 C76.464822,130.398827 70.5909248,130.398827 66.4355344,125.58961 C62.9024905,121.371642 58.9333122,116.710237 54.3854087,111.427565 C53.8350288,110.788264 53.2758998,110.139548 52.706934,109.480131 C49.8512069,106.170414 46.9143172,102.783286 43.1506474,98.4546038 C43.1657573,98.4719821 36.1709078,90.431646 34.3564576,88.341891 C27.8799723,80.882735 24.2336656,76.6160672 22.1013335,73.9633891 L22.095737,73.9562966 C15.4200148,65.3371074 30.5778334,52.1721209 38.5786063,60.512576 C48.9690719,71.5242952 60.7566779,85.0318321 73.9420929,101.035687 Z' id='Note' stroke='%23979797' fill='",
$svg-default-color,
"'%3E%3C/path%3E%3C/g%3E%3C/svg%3E"
);
}
&.pn-info {
@include pn-icon(
"data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='165px' height='165px' viewBox='0 0 165 165' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Cg id='Page-1' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E%3Cpath d='M82.5,165 C36.9365081,165 0,128.063492 0,82.5 C0,36.9365081 36.9365081,0 82.5,0 C128.063492,0 165,36.9365081 165,82.5 C165,128.063492 128.063492,165 82.5,165 Z M71.3481445,44.7539062 C71.3481445,47.7402493 72.400136,50.2853899 74.5041504,52.3894043 C76.6081648,54.4934187 79.1533054,55.5454102 82.1396484,55.5454102 C85.0807439,55.5454102 87.603261,54.4934187 89.7072754,52.3894043 C91.8112898,50.2853899 92.8632812,47.7402493 92.8632812,44.7539062 C92.8632812,41.7675632 91.8112898,39.2224226 89.7072754,37.1184082 C87.603261,35.0143938 85.0807439,33.9624023 82.1396484,33.9624023 C79.1533054,33.9624023 76.6081648,35.0143938 74.5041504,37.1184082 C72.400136,39.2224226 71.3481445,41.7675632 71.3481445,44.7539062 Z M65.2397461,126.674316 L65.2397461,130 L98.3608398,130 L98.3608398,126.674316 C95.8722206,126.176593 94.1754603,125.497888 93.2705078,124.638184 C92.3655554,123.778479 91.9130859,121.83286 91.9130859,118.80127 L91.9130859,65.9975586 L65.2397461,65.9975586 L65.2397461,69.3911133 C68.0450987,69.8888371 69.9228468,70.6467234 70.8730469,71.6647949 C71.8232469,72.6828664 72.2983398,74.5945498 72.2983398,77.3999023 L72.2983398,118.258301 C72.2983398,121.425634 71.6196357,123.620111 70.262207,124.841797 C69.3572546,125.656254 67.6831177,126.267088 65.2397461,126.674316 Z' id='Info' fill='",
$svg-info-color,
"'%3E%3C/path%3E%3C/g%3E%3C/svg%3E"
);
}
&.pn-warn {
@include pn-icon(
"data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='165px' height='165px' viewBox='0 0 165 165' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Cg id='Page-1' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E%3Cpath d='M82.5,165 C36.9365081,165 0,128.063492 0,82.5 C0,36.9365081 36.9365081,0 82.5,0 C128.063492,0 165,36.9365081 165,82.5 C165,128.063492 128.063492,165 82.5,165 Z M70.0629883,121.226562 C70.0629883,124.484391 71.2054736,127.255767 73.4904785,129.540771 C75.7754834,131.825776 78.5468587,132.968262 81.8046875,132.968262 C85.0625163,132.968262 87.8338916,131.825776 90.1188965,129.540771 C92.4039014,127.255767 93.5463867,124.484391 93.5463867,121.226562 C93.5463867,117.968734 92.4039014,115.197358 90.1188965,112.912354 C87.8338916,110.627349 85.0625163,109.484863 81.8046875,109.484863 C78.5468587,109.484863 75.7754834,110.627349 73.4904785,112.912354 C71.2054736,115.197358 70.0629883,117.968734 70.0629883,121.226562 Z M70.0629883,49.1474609 C70.0629883,50.8668706 70.4475873,53.2423351 71.2167969,56.2739258 C71.7145207,58.2195735 72.8230708,62.1108107 74.5424805,67.9477539 C75.8999091,72.5177637 76.804848,76.0696488 77.2573242,78.6035156 C77.7098004,81.1373825 78.5921158,87.7886831 79.9042969,98.5576172 L83.9086914,98.5576172 C84.8588915,90.0058166 85.5489074,84.3160135 85.9787598,81.4880371 C86.4086122,78.6600607 87.2117454,75.1194874 88.3881836,70.8662109 C90.3338313,63.8075819 91.6799279,58.7286125 92.4265137,55.6291504 C93.1730994,52.5296883 93.5463867,50.2107824 93.5463867,48.6723633 C93.5463867,43.7856201 92.3586545,40.278982 89.9831543,38.1523438 C87.6076541,36.0257055 84.8815258,34.9624023 81.8046875,34.9624023 C78.637354,34.9624023 75.8886021,36.0370172 73.5583496,38.1862793 C71.2280971,40.3355413 70.0629883,43.989232 70.0629883,49.1474609 Z' id='Warning' fill='",
$svg-warning-color,
"'%3E%3C/path%3E%3C/g%3E%3C/svg%3E"
);
}
&.pn-error {
@include pn-icon(
"data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='165px' height='165px' viewBox='0 0 165 165' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Cg id='Page-1' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E%3Cpath d='M94.8695048,7.74207645 L163.217029,144.437125 C166.674878,151.352824 163.871738,159.762246 156.956039,163.220096 C155.012063,164.192084 152.86848,164.698115 150.695048,164.698115 L14,164.698115 C6.2680135,164.698115 -4.08562073e-14,158.430102 -4.26325641e-14,150.698115 C-4.26325641e-14,148.524684 0.506031285,146.381101 1.47801933,144.437125 L69.8255435,7.74207645 C73.283393,0.826377491 81.6928155,-1.97676336 88.6085145,1.48108612 C91.3178981,2.83577793 93.514813,5.03269282 94.8695048,7.74207645 Z M70.4105124,130.924678 C70.4105124,134.182506 71.5529978,136.953882 73.8380027,139.238887 C76.1230076,141.523892 78.8943829,142.666377 82.1522117,142.666377 C85.4100404,142.666377 88.1814157,141.523892 90.4664206,139.238887 C92.7514256,136.953882 93.8939109,134.182506 93.8939109,130.924678 C93.8939109,127.666849 92.7514256,124.895474 90.4664206,122.610469 C88.1814157,120.325464 85.4100404,119.182978 82.1522117,119.182978 C78.8943829,119.182978 76.1230076,120.325464 73.8380027,122.610469 C71.5529978,124.895474 70.4105124,127.666849 70.4105124,130.924678 Z M70.4105124,58.845576 C70.4105124,60.5649857 70.7951115,62.9404502 71.564321,65.9720409 C72.0620449,67.9176886 73.170595,71.8089258 74.8900046,77.645869 C76.2474333,82.2158788 77.1523722,85.7677639 77.6048484,88.3016307 C78.0573246,90.8354976 78.93964,97.4867982 80.251821,108.255732 L84.2562156,108.255732 C85.2064156,99.7039317 85.8964315,94.0141286 86.3262839,91.1861522 C86.7561363,88.3581758 87.5592696,84.8176025 88.7357078,80.564326 C90.6813555,73.505697 92.0274521,68.4267276 92.7740378,65.3272655 C93.5206236,62.2278034 93.8939109,59.9088975 93.8939109,58.3704784 C93.8939109,53.4837352 92.7061786,49.9770971 90.3306785,47.8504589 C87.9551783,45.7238206 85.22905,44.6605175 82.1522117,44.6605175 C78.9848781,44.6605175 76.2361263,45.7351324 73.9058738,47.8843944 C71.5756212,50.0336565 70.4105124,53.6873471 70.4105124,58.845576 Z' id='Error' fill='",
$svg-error-color,
"'%3E%3C/path%3E%3C/g%3E%3C/svg%3E"
);
}
&.pn-quote {
@include pn-icon(
"data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='165px' height='165px' viewBox='0 0 165 165' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Cg id='Page-1' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E%3Cpath d='M104.546838,164.525333 C97.1871585,164.350607 90.6368822,160.915227 90.6512001,150.013018 C90.4479076,131.842639 90.4697154,98.303237 90.6512001,49.7828789 C91.9844555,2.36817118 138.064959,0.504907944 148.576644,0.0692731383 C152.479575,0.302510658 153.780675,2.21617827 154.578947,4.17356105 C155.831948,9.88458567 155.831948,17.6357453 154.578947,27.4270401 C153.93686,32.7057192 151.936092,35.3224781 148.576644,35.2773166 C143.472082,35.2236794 151.862467,35.2263624 140.927765,35.2773166 C128.559674,35.7091823 122.660334,39.3672244 122.615074,56.9085817 C122.635604,63.1213926 122.635604,71.5842998 122.615074,82.2973033 C138.48496,82.4101196 149.139584,82.4488979 154.578947,82.4136382 C159.435737,82.5353733 163.923774,84.3352392 164.565789,96.288498 C164.874062,119.857257 164.829662,136.387115 164.782895,150.013018 C164.664253,157.17723 161.233392,164.356416 151.753558,164.525333 C127.51005,164.615729 113.455097,164.525333 104.546838,164.525333 Z M14.0400451,164.45606 C6.68036548,164.281334 0.130089247,160.845954 0.144407166,149.943745 C-0.058885353,131.773366 -0.0370775896,98.2339638 0.144407166,49.7136058 C1.47766255,2.29889804 47.5581663,0.435634806 58.0698511,-9.9475983e-14 C61.9727821,0.233237519 63.2738816,2.14690514 64.0721544,4.10428791 C65.3251551,9.81531253 65.3251551,17.5664722 64.0721544,27.3577669 C63.4300669,32.6364461 61.4292991,35.2532049 58.0698511,35.2080434 C52.9652887,35.1544062 61.3556736,35.1570892 50.4209719,35.2080434 C38.0528815,35.6399092 32.153541,39.2979513 32.1082808,56.8393085 C32.1288111,63.0521194 32.1288111,71.5150266 32.1082808,82.2280302 C47.9781667,82.3408464 58.6327912,82.3796247 64.0721544,82.3443651 C68.9289443,82.4661002 73.4169814,84.265966 74.0589965,96.2192249 C74.367269,119.787984 74.3228688,136.317842 74.2761018,149.943745 C74.1574604,157.107957 70.7265987,164.287143 61.2467647,164.45606 C37.0032571,164.546456 22.9483044,164.45606 14.0400451,164.45606 Z' id='Quote' fill='",
$svg-citation-color,
"'%3E%3C/path%3E%3C/g%3E%3C/svg%3E"
);
}
&.pn-square {
@include pn-icon(
"data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='165px' height='165px' viewBox='0 0 165 165' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Cg id='Page-1' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E%3Cpath d='M82.5,165 C36.9365081,165 0,128.063492 0,82.5 C0,36.9365081 36.9365081,0 82.5,0 C128.063492,0 165,36.9365081 165,82.5 C165,128.063492 128.063492,165 82.5,165 Z M115.5,99 C124.612698,99 132,91.3888407 132,82 C132,72.6111593 124.612698,65 115.5,65 C106.387302,65 99,72.6111593 99,82 C99,91.3888407 106.387302,99 115.5,99 Z M49.5,99 C58.6126984,99 66,91.3888407 66,82 C66,72.6111593 58.6126984,65 49.5,65 C40.3873016,65 33,72.6111593 33,82 C33,91.3888407 40.3873016,99 49.5,99 Z M66,114 L66,129 L99,129 L99,114 L66,114 Z' id='Default' fill='",
$svg-default-color,
"'%3E%3C/path%3E%3C/g%3E%3C/svg%3E"
);
}
/* End of Autogenerated code */
}

BIN
docs/assets/add.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

View file

@ -1,16 +0,0 @@
/* .navigation-list-item {
font-size: 18px !important;
padding: 4px;
}
html, body {
font-family: sans-serif;
}
.page-content h1:first-of-type {
font-weight: 500;
}
li {
margin-left: 10px;
} */

BIN
docs/assets/event.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
docs/assets/federation.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
docs/assets/follow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

View file

@ -0,0 +1,72 @@
---
permalink: /assets/js/search-data.json
---
{
{%- assign i = 0 -%}
{%- assign pages_array = '' | split: '' -%}
{%- assign pages_array = pages_array | push: site.html_pages -%}
{%- if site.just_the_docs.collections -%}
{%- for collection_entry in site.just_the_docs.collections -%}
{%- assign collection_key = collection_entry[0] -%}
{%- assign collection_value = collection_entry[1] -%}
{%- assign collection = site[collection_key] -%}
{%- if collection_value.search_exclude != true -%}
{%- assign pages_array = pages_array | push: collection -%}
{%- endif -%}
{%- endfor -%}
{%- endif -%}
{%- for pages in pages_array -%}
{%- for page in pages -%}
{%- if page.title and page.search_exclude != true -%}
{%- assign page_content = page.content -%}
{%- assign heading_level = site.search.heading_level | default: 2 -%}
{%- for j in (2..heading_level) -%}
{%- assign tag = '<h' | append: j -%}
{%- assign closing_tag = '</h' | append: j -%}
{%- assign page_content = page_content | replace: tag, '<h1' | replace: closing_tag, '</h1' -%}
{%- endfor -%}
{%- assign parts = page_content | split: '<h1' -%}
{%- assign title_found = false -%}
{%- for part in parts offset: 1 -%}
{%- assign titleAndContent = part | split: '</h1>' -%}
{%- assign title = titleAndContent[0] | replace_first: '>', '<h1>' | split: '<h1>' -%}
{%- assign title = title[1] | strip_html -%}
{%- assign content = titleAndContent[1] -%}
{%- assign url = page.url -%}
{%- if title == page.title and parts[0] == '' -%}
{%- assign title_found = true -%}
{%- else -%}
{%- assign id = titleAndContent[0] -%}
{%- assign id = id | split: 'id="' -%}
{%- if id.size == 2 -%}
{%- assign id = id[1] -%}
{%- assign id = id | split: '"' -%}
{%- assign id = id[0] -%}
{%- capture url -%}{{ url | append: '#' | append: id }}{%- endcapture -%}
{%- endif -%}
{%- endif -%}
{%- unless i == 0 -%},{%- endunless -%}
"{{ i }}": {
"doc": {{ page.title | jsonify }},
"title": {{ title | jsonify }},
"content": {{ content | replace: '</h', ' . </h' | replace: '<hr', ' . <hr' | replace: '</p', ' . </p' | replace: '<ul', ' . <ul' | replace: '</ul', ' . </ul' | replace: '<ol', ' . <ol' | replace: '</ol', ' . </ol' | replace: '</tr', ' . </tr' | replace: '<li', ' | <li' | replace: '</li', ' | </li' | replace: '</td', ' | </td' | replace: '<td', ' | <td' | replace: '</th', ' | </th' | replace: '<th', ' | <th' | strip_html | remove: 'Table of contents' | normalize_whitespace | replace: '. . .', '.' | replace: '. .', '.' | replace: '| |', '|' | append: ' ' | jsonify }},
"url": "{{ url | absolute_url }}",
"relUrl": "{{ url }}"
}
{%- assign i = i | plus: 1 -%}
{%- endfor -%}
{%- unless title_found -%}
{%- unless i == 0 -%},{%- endunless -%}
"{{ i }}": {
"doc": {{ page.title | jsonify }},
"title": {{ page.title | jsonify }},
"content": {{ parts[0] | replace: '</h', ' . </h' | replace: '<hr', ' . <hr' | replace: '</p', ' . </p' | replace: '<ul', ' . <ul' | replace: '</ul', ' . </ul' | replace: '<ol', ' . <ol' | replace: '</ol', ' . </ol' | replace: '</tr', ' . </tr' | replace: '<li', ' | <li' | replace: '</li', ' | </li' | replace: '</td', ' | </td' | replace: '<td', ' | <td' | replace: '</th', ' | </th' | replace: '<th', ' | <th' | strip_html | remove: 'Table of contents' | normalize_whitespace | replace: '. . .', '.' | replace: '. .', '.' | replace: '| |', '|' | append: ' ' | jsonify }},
"url": "{{ page.url | absolute_url }}",
"relUrl": "{{ page.url }}"
}
{%- assign i = i | plus: 1 -%}
{%- endunless -%}
{%- endif -%}
{%- endfor -%}
{%- endfor %}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/assets/mobile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

BIN
docs/assets/options.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
docs/assets/share.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
docs/assets/thumbs/add.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -8,6 +8,19 @@ 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.0 (alpha)
This release is a complete rewrite of frontend UI and many internals, main changes are:
- Switch UI framework from [element](https://element.eleme.io/) to [vuetify](https://vuetifyjs.com/)
- Distribute package directly from site instead of using npm registry
- Improve docker setup (run as user, fix some data export)
- New logging system (based on [winston](https://github.com/winstonjs/winston))
- Slugify event URL (keeping old one valid)
- Import events from ics and external website using h-event (microformat) improving [`h-event`](https://microformats.org/wiki/h-event) export
- Hide unconfirmed tags and places
- Clean unused places and tags
- Fix tons of issues
### 0.24.0 ### 0.24.0
- New Euskara language from Basque Country, thanks @hacklabkelo - New Euskara language from Basque Country, thanks @hacklabkelo
- fix feed with filters - fix feed with filters

View file

@ -21,7 +21,7 @@ POST
**`/event`** **`/event`**
> info "info" > info "info"
> `Content-Type` has to be `multipart/form-data` 'cause support image upload > `Content-Type` has to be `multipart/form-data` to support image upload
**Params** **Params**

View file

@ -15,7 +15,7 @@ has_children: true
- Express - Express
- Node.js - Node.js
- [Sequelize](https://sequelize.org/) - [Sequelize](https://sequelize.org/)
- Element.ui - [Vuetify](vuetifyjs.com/)
### Testing on your own machine ### Testing on your own machine
@ -42,4 +42,4 @@ yarn dev
> warning "Warning" > warning "Warning"
> You need to register a first user, this will be an active administrator! > You need to register a first user, this will be an active administrator!
Please use the [issue board](https://framagit.org/les/gancio/-/boards) and the [forum](https://framavox.org/g/hMXTDgtJ/gancio) to discuss any modification. Please use the [issues](https://framagit.org/les/gancio/-/issues) to discuss any modification.

View file

@ -1,3 +1,2 @@
FROM node:latest FROM node:buster
WORKDIR / RUN yarn global add --silent https://gancio.org/latest.tgz 2> /dev/null
RUN yarn global add gancio

View file

@ -17,16 +17,17 @@ services:
- 5432:5432 - 5432:5432
gancio: gancio:
build: . build: .
image: node:latest
environment:
- DEBUG=*,-babel,-follow-redirects,-send,-body-parser:*,-express:*,-connect:*,-sequelize:*
container_name: gancio
restart: always restart: always
command: gancio start --docker --db=postgres image: node:buster
user: node
container_name: gancio
environment:
- PATH=$PATH:/home/node/.yarn/bin
- GANCIO_DATA=/home/node/data
command: gancio start --docker
volumes: volumes:
- ./config.json:/opt/gancio/config.json - ./data:/home/node/data
- ./uploads:/opt/gancio/uploads ports:
- "127.0.0.1:13120:13120"
depends_on: depends_on:
- db - db
ports:
- 127.0.0.1:13120:13120

View file

@ -4,14 +4,14 @@ services:
gancio: gancio:
build: . build: .
restart: always restart: always
image: node:latest image: node:buster
user: node
container_name: gancio container_name: gancio
command: gancio start --docker --db=sqlite
environment: environment:
- DEBUG=*,-babel,-follow-redirects,-send,-body-parser:*,-express:*,-connect:*,-sequelize:* - PATH=$PATH:/home/node/.yarn/bin
- GANCIO_DATA=/home/node/data
command: gancio start --docker
volumes: volumes:
- ./db.sqlite:/opt/gancio/db.sqlite - ./data:/home/node/data
- ./config.json:/opt/gancio/config.json
- ./uploads:/opt/gancio/uploads
ports: ports:
- "127.0.0.1:13120:13120" - "127.0.0.1:13120:13120"

View file

@ -1,5 +1,4 @@
--- ---
layout: default
title: Home title: Home
nav_order: 1 nav_order: 1
description: "Gancio is a shared agenda for local communities." description: "Gancio is a shared agenda for local communities."
@ -12,14 +11,9 @@ permalink: /
A shared agenda for local communities. A shared agenda for local communities.
{: .fs-6 } {: .fs-6 }
[Get started now](install){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 } [Demo](https://demo.gancio.org){: .btn .btn-green .fs-5 .mb-4 .mb-md-0 } [Install]({% link install/install.md %}){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 } [Demo](https://demo.gancio.org){: .btn .btn-green .fs-5 .mb-4 .mb-md-0 }
[Source](https://framagit.org/les/gancio){: .btn .fs-5 } [Source](https://framagit.org/les/gancio){: .btn .fs-5 }
[![assets/thumbs/home1.png](assets/thumbs/home1.png)](assets/home1.png){: data-fancybox="group" data-caption="Home of the first gancio instance"}
[![assets/thumbs/mobile1.png](assets/thumbs/mobile1.png)](assets/mobile1.png){: data-fancybox="group" data-caption="Home mobile"}
[![assets/thumbs/mobile2.png](assets/thumbs/mobile2.png)](assets/mobile2.png){: data-fancybox="group" data-caption="Home mobile"}
[![assets/thumbs/admin_users.png](assets/thumbs/admin_users.png)](assets/admin_users.png){: data-fancybox="group" data-caption="Admin interface"}
## Some relevant key features: ## Some relevant key features:
@ -30,7 +24,7 @@ nowhere on gancio appears the identity of who published the event, not even unde
- **Anonymous events**: optionally a visitor can create events without being registered (an administrator has to confirm them) - **Anonymous events**: optionally a visitor can create events without being registered (an administrator has to confirm them)
- **We are not interested in making hits** so we export events in many ways, via RSS feeds, via global or individual ics, incorporating lists of events or single event via iframe on other websites and via [AP](/federation) - **We are not interested in making hits** so we export events in many ways, via RSS feeds, via global or individual ics, incorporating lists of events or single event via iframe on other websites and via [AP]({% link federation.md %})
- Very easy UI - Very easy UI
- Multidays events support (festival, conferences...) - Multidays events support (festival, conferences...)

23
docs/install/backup.md Normal file
View file

@ -0,0 +1,23 @@
---
layout: default
title: Backup
permalink: /install/backup
nav_order: 5
parent: Install
---
## Backup
The following commands should be valid for every setup (docker/debian/sqlite/postgres) but check your installation directory first.
This includes database, configuration, custom user locales, logs, images and thumbnails.
```bash
cd /opt/gancio/ # or /home/gancio or where your installation is
tar -czf gancio-$(date +%Y-%m-%d-%H%M%S)-backup.tgz \
$(ls -d config.json uploads user_locale db.sqlite postgres data logs 2> /dev/null)
```
> warning "Permission denied"
> `postgres` directory could have different permission or owner, in this case you need to be root or use `sudo` instead.
> info "Automatic backup"
> To periodically backup your data you should probably use something like [restic](https://restic.net) or [borg](https://www.borgbackup.org/)

View file

@ -1,14 +1,14 @@
--- ---
layout: default
title: Configuration title: Configuration
permalink: /config permalink: /install/config
nav_order: 3 nav_order: 6
parent: Install
--- ---
## Configuration ## Configuration
{: .no_toc } {: .no_toc }
Main `gancio` configuration is done with a configuration file. `gancio` configuration is done during installation process but you can change it editing the configuration file. Note that you can always re-run gancio with `--setup` flag to use the interactive setup.
This shoud be a `.json` or a `.js` file and could be specified using the `--config` flag. The configuration file shoud be a `.json` or a `.js` file and could be specified using the `--config` flag.
- <small>eg. `gancio start --config ./config.json`</small> - <small>eg. `gancio start --config ./config.json`</small>
- <small>eg. `pm2 start gancio start -- --config ~/config.json`</small> - <small>eg. `pm2 start gancio start -- --config ~/config.json`</small>
@ -21,7 +21,7 @@ The title will be in rss feed, in html head and in emails:
`"title": "Gancio"` `"title": "Gancio"`
![title](assets/title.png) ![title](../assets/title.png)
- ### Description - ### Description
`"description": "a shared agenda for local communities"` `"description": "a shared agenda for local communities"`
@ -77,11 +77,6 @@ Email of administrator. Note that email from gancio comes from this email and th
the SMTP configuration above should allow to use this address as from. the SMTP configuration above should allow to use this address as from.
- ### Favicon
You could specify another favicon. This is also used as logo (top-left
corner):
`"favicon": "./favicon.ico"`
- ### User locale - ### User locale
Probably you want to modify some text for your specific community, that's Probably you want to modify some text for your specific community, that's
why we thought the `user_locale` configuration: you can specify your version of why we thought the `user_locale` configuration: you can specify your version of
@ -105,8 +100,6 @@ list of strings you can override.
<small>:warning: Note that a restart is needed when you change <small>:warning: Note that a restart is needed when you change
user_locale's content.</small> user_locale's content.</small>
- ### Secret
## Default settings ## Default settings
```json ```json
@ -115,15 +108,14 @@ user_locale's content.</small>
"description": "A shared agenda for local communities", "description": "A shared agenda for local communities",
"baseurl": "http://localhost:13120", "baseurl": "http://localhost:13120",
"server": { "server": {
"host": "0.0.0.0", "host": "127.0.0.1",
"port": 13120 "port": 13120
}, },
"db": { "db": {
"dialect": "sqlite", "dialect": "sqlite",
"storage": "/tmp/db.sqlite" "storage": "./db.sqlite"
}, },
"upload_path": "./", "upload_path": "./",
"favicon": "../dist/favicon.ico",
"smtp": { "smtp": {
"auth": { "auth": {
"user": "", "user": "",
@ -133,6 +125,5 @@ user_locale's content.</small>
"host": "" "host": ""
}, },
"admin_email": "", "admin_email": "",
"secret": "notsosecret"
} }
``` ```

View file

@ -1,7 +1,7 @@
--- ---
layout: default
title: Debian title: Debian
permalink: /install/debian permalink: /install/debian
nav_order: 1
parent: Install parent: Install
--- ---
@ -9,7 +9,7 @@ parent: Install
1. Install Node.js & yarn (**from root**) 1. Install Node.js & yarn (**from root**)
```bash ```bash
curl -sL https://deb.nodesource.com/setup_12.x | bash - curl -sL https://deb.nodesource.com/setup_16.x | bash -
apt-get install -y nodejs apt-get install -y nodejs
curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list
@ -19,7 +19,7 @@ apt-get update && apt-get install yarn
1. Install Gancio 1. Install Gancio
```bash ```bash
yarn global add gancio --prod yarn global add --silent {{site.url}}{% link /latest.tgz %} 2> /dev/null
``` ```
1. Setup with postgreSQL __(optional as you can choose sqlite)__ 1. Setup with postgreSQL __(optional as you can choose sqlite)__
@ -49,7 +49,7 @@ gancio start --config config.json
``` ```
1. Point your web browser to [http://localhost:13120](http://localhost:13120) or where you selected during setup. 1. Point your web browser to [http://localhost:13120](http://localhost:13120) or where you selected during setup.
1. [Setup nginx as a proxy](/install/nginx) 1. [Setup nginx as a proxy]({% link install/nginx.md %})
1. To deploy gancio in production you should use something like **[pm2](http://pm2.keymetrics.io/)**: 1. To deploy gancio in production you should use something like **[pm2](http://pm2.keymetrics.io/)**:
@ -62,8 +62,8 @@ pm2 startup # read the output!
sudo pm2 startup -u gancio sudo pm2 startup -u gancio
``` ```
1. Upgrade ## Upgrade
```bash ```bash
sudo yarn global add gancio sudo yarn global add --silent {{site.url}}{% link /latest.tgz %} 2> /dev/null
sudo service pm2 restart sudo service pm2 restart
``` ```

View file

@ -3,39 +3,35 @@ layout: default
title: Docker title: Docker
permalink: /install/docker permalink: /install/docker
parent: Install parent: Install
nav_order: 2
--- ---
## Table of contents ## Docker installation
{: .no_toc .text-delta } {: .no_toc }
1. TOC 1. TOC
{:toc} {:toc}
## Initial setup ## Initial setup
**You do not need to clone the full repo as we distribute gancio via npm.**
A Dockerfile and a docker-compose.yml are the only files needed.
- __Create a directory where everything related to gancio is stored (db, images, config)__ > info "Clone not needed"
> You do not need to clone the full repo, a `Dockerfile` and a `docker-compose.yml` are enough.
- __Create a directory where everything related to gancio is stored__
```bash ```bash
mkdir /opt/gancio mkdir -p /opt/gancio/data
cd /opt/gancio cd /opt/gancio
``` ```
<small>note that you can choose a different directory.</small>
## Use sqlite ## Use sqlite
<div class='code-example bg-grey-lt-100' markdown="1"> <div class='code-example bg-grey-lt-100' markdown="1">
1. **Download docker-compose.yml and Dockerfile** 1. **Download docker-compose.yml and Dockerfile**
```bash ```bash
wget https://gancio.org/docker/Dockerfile wget {{site.url}}{% link /docker/Dockerfile %}
wget https://gancio.org/docker/sqlite/docker-compose.yml wget {{site.url}}{% link /docker/sqlite/docker-compose.yml %}
``` ```
1. Create an empty db and config (**this is needed**)
```
touch config.json db.sqlite
mkdir user_locale
```
1. Build docker image and launch interactive setup in one step 1. Build docker image and launch interactive setup
``` ```
docker-compose build docker-compose build
docker-compose run --rm gancio gancio setup --docker --db=sqlite docker-compose run --rm gancio gancio setup --docker --db=sqlite
@ -47,17 +43,11 @@ docker-compose run --rm gancio gancio setup --docker --db=sqlite
1. **Download docker-compose.yml and Dockerfile** 1. **Download docker-compose.yml and Dockerfile**
```bash ```bash
wget https://gancio.org/docker/Dockerfile wget {{site.url}}{% link /docker/Dockerfile %}
wget https://gancio.org/docker/postgres/docker-compose.yml wget {{site.url}}{% link /docker/postgres/docker-compose.yml %}
``` ```
1. Create an empty configuration (**this is needed**) 1. Build docker image and launch interactive setup
```
touch config.json
mkdir user_locale
```
1. Build docker image and launch interactive setup in one step
``` ```
docker-compose build docker-compose build
docker-compose run --rm gancio gancio setup --docker --db=postgres docker-compose run --rm gancio gancio setup --docker --db=postgres
@ -72,20 +62,37 @@ docker-compose run --rm gancio gancio setup --docker --db=postgres
docker-compose up -d docker-compose up -d
``` ```
1. Look at logs with 1. Look at logs
```bash ```bash
docker-compose logs tail -f data/logs/gancio.log
``` ```
1. [Setup nginx as a proxy](/install/nginx) 1. [Setup nginx as a proxy]({% link install/nginx.md %}
1. Point your web browser to [http://localhost:13120](http://localhost:13120) or where you specified during setup and enjoy :tada: 1. Point your web browser to [http://localhost:13120](http://localhost:13120) or where you specified during setup and enjoy :tada:
1. You can edit `config.json` file and restart the container on your needs, see [Configuration](/config) for more details. 1. Edit `data/config.json` and restart the container on your needs, see [Configuration]({% link install/configuration.md %}) for more details.
## Upgrade ## Upgrade
> warning "Backup your data"
> Backup your data is generally a good thing to do and this is especially true before upgrading.
> Don't be lazy and [backup]({% link install/backup.md %}) your data!
> error "Upgrade from a version < 1.0"
> Since v1.0 our docker setup is changed and a new container has to be built:
>
> 1. `cd /opt/gancio`
> 1. [Backup your data]({% link install/backup.md %})
> 1. Download new `Dockerfile` <br/> `wget {{site.url}}{% link /docker/Dockerfile %}`
> 1. Download new `docker-compose.yml` (substitute `sqlite` with `postgres` in case): <br/>`wget {{site.url}}{% link /docker/sqlite/docker-compose.yml %}`
> 1. Build the new container `docker-compose build`
> 1. Extract your backup into `./data` <br/>`mkdir data; tar xvzf gancio-<yourLastBackup>-backup.tgz -C data`
> 1. Stop your old container `docker-compose stop`
> 1. Start your new container `docker-compose up`
```bash ```bash
cd /opt/gancio cd /opt/gancio
docker-compose up -d --no-deps --build docker-compose up -d --no-deps --build

View file

@ -3,17 +3,20 @@ layout: default
title: Install title: Install
permalink: /install permalink: /install
has_children: true has_children: true
nav_order: 2 nav_order: 3
has_toc: false has_toc: false
--- ---
## Install
## Install (production) You can install gancio on a cheap VPS (500mb of ram will be enough)
- [Install on Debian](/install/debian) - [Install on Debian]({% link install/debian.md %})
- [Install using docker](/install/docker) - [Install using docker]({% link install/docker.md %})
### Post installation ### Post installation
- [Nginx as a proxy](/install/nginx) - [Setup Nginx as a proxy]({% link install/nginx.md %})
- [Configuration]({% link install/configuration.md %})
- [Backup]({% link install/backup.md %})
If you wanna hack or run the current develop release take a look at [Hacking & contribute](../dev) If you wanna hack or run the current development release take a look at [Hacking & contribute]({% link dev/dev.md %})

View file

@ -1,6 +1,6 @@
--- ---
layout: default layout: default
title: Nginx title: Nginx setup
permalink: /install/nginx permalink: /install/nginx
parent: Install parent: Install
--- ---

View file

@ -1,5 +1,4 @@
--- ---
layout: default
title: Instances title: Instances
permalink: /instances permalink: /instances
nav_order: 7 nav_order: 7
@ -12,4 +11,4 @@ nav_order: 7
- [chesefa.org](https://chesefa.org) (Naples, Italy) - [chesefa.org](https://chesefa.org) (Naples, Italy)
<small>Do you want your instance to appear here? [Write us](/contacts).</small> <small>Do you want your instance to appear here? [Write us]({% link contact.md %}).</small>

BIN
docs/latest.tgz Normal file

Binary file not shown.

16
docs/screenshot.md Normal file
View file

@ -0,0 +1,16 @@
---
title: Screenshots
nav_order: 2
permalink: /screenshot
---
## Screenshots
[![assets/thumbs/home_desktop.png](assets/thumbs/home_desktop.png)](assets/home_desktop.png){: data-fancybox="group" data-caption="Home"}
[![assets/thumbs/event.png](assets/thumbs/event.png)](assets/event.png){: data-fancybox="group" data-caption="Event"}
[![assets/thumbs/mobile.png](assets/thumbs/mobile.png)](assets/mobile.png){: data-fancybox="group" data-caption="Mobile"}
[![assets/thumbs/add.png](assets/thumbs/add.png)](assets/add.png){: data-fancybox="group" data-caption="Add event"}
[![assets/thumbs/share.png](assets/thumbs/share.png)](assets/share.png){: data-fancybox="group" data-caption="Share"}
[![assets/thumbs/options.png](assets/thumbs/options.png)](assets/options.png){: data-fancybox="group" data-caption="Admin options"}
[![assets/thumbs/federation.png](assets/thumbs/federation.png)](assets/federation.png){: data-fancybox="group" data-caption="Admin federation"}
[![assets/thumbs/follow.png](assets/thumbs/follow.png)](assets/follow.png){: data-fancybox="group" data-caption="Follow"}

View file

@ -1,11 +0,0 @@
---
layout: default
title: Usage
permalink: /usage
nav_order: 1
has_children: true
---
# Usage
ehmmm, help needed here :smile: feel free to send a PR => [here](https://framagit.org/les/gancio/tree/master/docs)

View file

@ -13,7 +13,6 @@
</template> </template>
<script> <script>
import Nav from '~/components/Nav.vue' import Nav from '~/components/Nav.vue'
import FollowMe from '../components/FollowMe'
import Snackbar from '../components/Snackbar' 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'
@ -21,7 +20,7 @@ import { mapState } from 'vuex'
export default { export default {
name: 'Default', name: 'Default',
components: { Nav, FollowMe, Snackbar, Footer, Confirm }, components: { Nav, Snackbar, Footer, Confirm },
computed: mapState(['settings']), computed: mapState(['settings']),
created () { created () {
this.$vuetify.theme.dark = this.settings['theme.is_dark'] this.$vuetify.theme.dark = this.settings['theme.is_dark']
@ -29,4 +28,4 @@ export default {
this.$vuetify.theme.themes.light.primary = this.settings['theme.primary'] this.$vuetify.theme.themes.light.primary = this.settings['theme.primary']
} }
} }
</script> </script>

View file

@ -13,13 +13,12 @@
</template> </template>
<script> <script>
import Nav from '~/components/Nav.vue' import Nav from '~/components/Nav.vue'
import FollowMe from '../components/FollowMe'
import Snackbar from '../components/Snackbar' 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'
export default { export default {
name: 'Default', name: 'Default',
components: { Nav, FollowMe, Snackbar, Footer, Confirm }, components: { Nav, Snackbar, Footer, Confirm }
} }
</script> </script>

View file

@ -84,14 +84,16 @@
"tags": "Tags", "tags": "Tags",
"theme": "Theme", "theme": "Theme",
"reset": "Reset", "reset": "Reset",
"import": "Import" "import": "Import",
"max_events": "N. max events",
"label": "Label"
}, },
"login": { "login": {
"description": "By logging in you can publish new events.", "description": "By logging in you can publish new events.",
"check_email": "Check your e-mail inbox and spam.", "check_email": "Check your e-mail inbox and spam.",
"not_registered": "Not registered?", "not_registered": "Not registered?",
"forgot_password": "Forgot your password?", "forgot_password": "Forgot your password?",
"error": "Could not log in. Check your credentials.", "error": "Could not log in. Check your login info.",
"insert_email": "Enter your e-mail address", "insert_email": "Enter your e-mail address",
"ok": "Logged in" "ok": "Logged in"
}, },
@ -155,7 +157,8 @@
"remove_recurrent_confirmation": "Are you sure you want to remove this recurring event?\nPast events will be maintained, but no further events will be created.", "remove_recurrent_confirmation": "Are you sure you want to remove this recurring event?\nPast events will be maintained, but no further events will be created.",
"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)"
}, },
"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.",
@ -172,6 +175,7 @@
"federation": "Federation / ActivityPub", "federation": "Federation / ActivityPub",
"enable_federation": "Turn on federation", "enable_federation": "Turn on federation",
"enable_federation_help": "It will be possible to follow this instance from the fediverse", "enable_federation_help": "It will be possible to follow this instance from the fediverse",
"add_instance": "Add instance",
"select_instance_timezone": "Time zone", "select_instance_timezone": "Time zone",
"enable_resources": "Turn on resources", "enable_resources": "Turn on resources",
"enable_resources_help": "Allows adding resources to the event from the fediverse", "enable_resources_help": "Allows adding resources to the event from the fediverse",

View file

@ -123,7 +123,7 @@
"media_description": "Puedes agregar un panfleto (opcionál)", "media_description": "Puedes agregar un panfleto (opcionál)",
"added": "Evento agregado", "added": "Evento agregado",
"added_anon": "Evento agregado, será confirmado cuanto antes.", "added_anon": "Evento agregado, será confirmado cuanto antes.",
"where_description": "¿Dónde es? Si el lugar no está, escribilo y <b>presiona enter</b>. ", "where_description": "¿Dónde es? Si el lugar no está, escribilo.",
"confirmed": "Evento confirmado", "confirmed": "Evento confirmado",
"not_found": "Evento no encontrado", "not_found": "Evento no encontrado",
"remove_confirmation": "¿Estás seguro/a de querér eliminar este evento?", "remove_confirmation": "¿Estás seguro/a de querér eliminar este evento?",

View file

@ -249,7 +249,7 @@
"login": { "login": {
"ok": "Connecté", "ok": "Connecté",
"insert_email": "Saisissez votre adresse courriel", "insert_email": "Saisissez votre adresse courriel",
"error": "Impossible de se connecter. Veuillez vérifier vos identifiants.", "error": "Impossible de se connecter. Veuillez vérifier vos informations de connexion.",
"forgot_password": "Mot de passe oublié ?", "forgot_password": "Mot de passe oublié ?",
"not_registered": "Pas encore inscrit·e ?", "not_registered": "Pas encore inscrit·e ?",
"check_email": "Vérifiez votre boîte de réception et les indésirables.", "check_email": "Vérifiez votre boîte de réception et les indésirables.",

View file

@ -85,7 +85,8 @@
"theme": "Tema", "theme": "Tema",
"reset": "Reset", "reset": "Reset",
"import": "Importa", "import": "Importa",
"max_events": "N. massimo eventi" "max_events": "N. massimo eventi",
"label": "Etichetta"
}, },
"login": { "login": {
"description": "Entrando puoi pubblicare nuovi eventi.", "description": "Entrando puoi pubblicare nuovi eventi.",
@ -102,7 +103,7 @@
"export": { "export": {
"intro": "Contrariamente alle piattaforme del capitalismo, che fanno di tutto per tenere i dati e gli utenti al loro interno, crediamo che le informazioni, come le persone, debbano essere libere. Per questo puoi rimanere aggiornata sugli eventi che vuoi, come meglio credi, senza necessariamente passare da questo sito.", "intro": "Contrariamente alle piattaforme del capitalismo, che fanno di tutto per tenere i dati e gli utenti al loro interno, crediamo che le informazioni, come le persone, debbano essere libere. Per questo puoi rimanere aggiornata sugli eventi che vuoi, come meglio credi, senza necessariamente passare da questo sito.",
"email_description": "Puoi ricevere via posta elettronica gli eventi che ti interessano.", "email_description": "Puoi ricevere via posta elettronica gli eventi che ti interessano.",
"insert_your_address": "Indirizzo e-mail", "insert_your_address": "Inserisci il tuo indirizzo e-mail",
"feed_description": "Per seguire gli aggiornamenti da computer o smartphone senza la necessità di aprire periodicamente il sito, il metodo consigliato è quello dei Feed RSS.</p>\n\n <p>Con i feed rss utilizzi un'apposita applicazione per ricevere aggiornamenti dai siti che più ti interessano. È un buon metodo per seguire anche molti siti in modo molto rapido, senza necessità di creare un account o altre complicazioni.</p>\n\n<li>Se hai Android, ti consigliamo <a href=\"https://f-droid.org/en/packages/net.frju.flym\">Flym</a> o Feeder</li>\n<li>Per iPhone/iPad puoi usare <a href=\"https://itunes.apple.com/ua/app/feeds4u/id1038456442?mt=8\">Feed4U</a></li>\n<li>Per il computer fisso/portatile consigliamo Brief, da installare all'interno <a href=\"https://addons.mozilla.org/it/firefox/addon/brief/\">di Firefox </a> e compatibile con tutti i principali sistemi operativi.</li>\n<br/>\nAggiungendo questo link al tuo lettore di feed, rimarrai aggiornata.", "feed_description": "Per seguire gli aggiornamenti da computer o smartphone senza la necessità di aprire periodicamente il sito, il metodo consigliato è quello dei Feed RSS.</p>\n\n <p>Con i feed rss utilizzi un'apposita applicazione per ricevere aggiornamenti dai siti che più ti interessano. È un buon metodo per seguire anche molti siti in modo molto rapido, senza necessità di creare un account o altre complicazioni.</p>\n\n<li>Se hai Android, ti consigliamo <a href=\"https://f-droid.org/en/packages/net.frju.flym\">Flym</a> o Feeder</li>\n<li>Per iPhone/iPad puoi usare <a href=\"https://itunes.apple.com/ua/app/feeds4u/id1038456442?mt=8\">Feed4U</a></li>\n<li>Per il computer fisso/portatile consigliamo Brief, da installare all'interno <a href=\"https://addons.mozilla.org/it/firefox/addon/brief/\">di Firefox </a> e compatibile con tutti i principali sistemi operativi.</li>\n<br/>\nAggiungendo questo link al tuo lettore di feed, rimarrai aggiornata.",
"ical_description": "I computer e gli smartphone sono comunemente attrezzati con un'applicazione per gestire un calendario. A questi programmi solitamente è possibile far importare un calendario remoto.", "ical_description": "I computer e gli smartphone sono comunemente attrezzati con un'applicazione per gestire un calendario. A questi programmi solitamente è possibile far importare un calendario remoto.",
"list_description": "Se hai un sito web e vuoi mostrare una lista di eventi, puoi usare il seguente codice" "list_description": "Se hai un sito web e vuoi mostrare una lista di eventi, puoi usare il seguente codice"
@ -156,7 +157,7 @@
"only_future": "solo eventi futuri", "only_future": "solo eventi futuri",
"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 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)"
}, },
@ -165,7 +166,8 @@
"event_confirm_description": "Puoi confermare qui gli eventi inseriti da utenti anonimi", "event_confirm_description": "Puoi confermare qui gli eventi inseriti da utenti anonimi",
"delete_user": "Elimina", "delete_user": "Elimina",
"remove_admin": "Rimuovi admin", "remove_admin": "Rimuovi admin",
"delete_user_confirm": "Sei sicuro/a di rimuovere questo utente?", "disable_user_confirm": "Vuoi disabilitare {user}?",
"delete_user_confirm": "Vuoi rimuovere {user}?",
"user_remove_ok": "Utente eliminato", "user_remove_ok": "Utente eliminato",
"user_create_ok": "Utente creato", "user_create_ok": "Utente creato",
"allow_registration_description": "Vuoi abilitare la registrazione?", "allow_registration_description": "Vuoi abilitare la registrazione?",
@ -175,6 +177,7 @@
"federation": "Federazione / ActivityPub", "federation": "Federazione / ActivityPub",
"enable_federation": "Abilita la federazione", "enable_federation": "Abilita la federazione",
"enable_federation_help": "Sarà possibile seguire questa istanza dal fediverso", "enable_federation_help": "Sarà possibile seguire questa istanza dal fediverso",
"add_instance": "Aggiungi istanza",
"select_instance_timezone": "Fuso orario", "select_instance_timezone": "Fuso orario",
"instance_timezone_description": "Gancio è pensato per raccogliere gli eventi di un luogo specifico come ad esempio una città. Scrivendo e selezionando il fuso orario di questo luogo, tutti gli orari saranno mostrati e inseriti secondo quanto scelto.", "instance_timezone_description": "Gancio è pensato per raccogliere gli eventi di un luogo specifico come ad esempio una città. Scrivendo e selezionando il fuso orario di questo luogo, tutti gli orari saranno mostrati e inseriti secondo quanto scelto.",
"enable_resources": "Abilita risorse", "enable_resources": "Abilita risorse",

View file

@ -1,258 +0,0 @@
{
"oauth": {
"scopes": {
"event:write": "Legg til og rediger dine hendelser"
},
"redirected_to": "Etter bekreftelse vil du bli videresendt til <code>{url}</code>",
"authorization_request": "Programmet <code>{app}</code> ber om følgende autorisering på <code>{instance_name}</code>:"
},
"confirm": {
"title": "Brukerbekreftelse",
"valid": "Kontoen din er bekreftet, du kan nå <a href=\"/login\">logge inn</a>",
"not_valid": "Noe gikk galt."
},
"error": {
"email_taken": "Denne e-postadressen er allerede i bruk",
"nick_taken": "Dette kallenavnet er allerede i bruk."
},
"settings": {
"password_updated": "Passord endret",
"change_password": "Endre passord",
"remove_account_confirm": "Du er i ferd med å slette kontoen din for godt",
"remove_account": "Ved å trykke på følgende knapp vil din brukerkonto slettes. Hendelser du har offentliggjort vil ikke bli det.",
"danger_section": "Farlig del",
"update_confirm": "Ønsker du å lagre endringen?"
},
"auth": {
"not_confirmed": "Ikke bekreftet enda…",
"fail": "Kunne ikke logge inn. Er du sikker på at passordet stemmer?"
},
"admin": {
"new_announcement": "Ny kunngjøring",
"edit_place": "Rediger sted",
"delete_footer_link_confirm": "Er du sikker på at du vil fjerne denne lenken?",
"add_link": "Legg til lenke",
"is_dark": "Mørk drakt",
"instance_locale": "Forvalgt språk",
"announcement_remove_ok": "Kunngjøring fjernet",
"delete_announcement_confirm": "Er du sikker på at du vil fjerne denne kunngjøringen?",
"user_block_confirm": "Er du sikker på at du vil blokkere denne brukeren?",
"favicon": "Logo",
"resources": "Ressurser",
"filter_users": "Filtrer brukere",
"filter_instances": "Filtrer instanser",
"block_user": "Blokker bruker",
"delete_resource_confirm": "Er du sikker på at du vil slette denne ressursen?",
"delete_resource": "Slett ressurs",
"hide_resource": "Skjul ressurs",
"show_resource": "Vis ressurs",
"instance_name": "Instansens navn",
"unblock": "Opphev blokkering",
"block": "Blokker",
"enable_resources": "Skru på ressurser",
"select_instance_timezone": "Tidssone",
"user_create_ok": "Bruker opprettet",
"user_remove_ok": "Bruker fjernet",
"delete_user_confirm": "Er du sikker på at du vil fjerne denne brukeren?",
"remove_admin": "Fjern administrator",
"delete_user": "Fjern",
"footer_links": "Bunntekst-lenker",
"delete_trusted_instance_confirm": "Ønsker du virkelig å slette dette elementet fra venneinstansmenyen?",
"instance_place_help": "Etikett å vise i andres instanser",
"add_trusted_instance": "Legg til en vennlig instans",
"trusted_instances_help": "Liste over vennlige instanser vises i toppteksten",
"enable_trusted_instances": "Skru på vennlige instanser",
"instance_name_help": "ActivityPub-konto å følge",
"instance_place": "Indiker sted for denne instansen",
"description_description": "Vises i toppteksten ved siden av tittelen",
"title_description": "Det brukes som overskrift på siden, i emnet av e-posten for eksportering til RSS- og ICS-informasjonsstrømmer.",
"instance_locale_description": "Foretrukket brukerspråk for sider. Noen meldinger vises påsamme bruk for alle (for eksempel ved publisering via ActivityPub, eller ved forsendelse av noen e-poster). I sådant fall vil språket ovenfor bli brukt.",
"instance_timezone_description": "Gancio er designet for å samle hendelser fra et gitt sted, som en by. Alle hendelser på dette stedet vil bli vist i tidssonen valgt for det.",
"announcement_description": "I denne delen kan du smette inn kunngjøringer som forblir på hjemmesiden",
"user_blocked": "Brukeren {user} blokkert",
"user_add_help": "En e-post med instruks om bekreftelse av abonnementet og valg av passord vil bli sendt til den nye brukeren",
"hide_boost_bookmark_help": "Skjuler de små ikonene som viser antall framhevelser og bokmerker som kommer fra fediverset",
"hide_boost_bookmark": "Skjuler framhevelser/bokmerker",
"enable_resources_help": "Tillat tillegg av ressurser til hendelsen fra fediverset",
"enable_federation_help": "Det vil bli mulig å følge denne instansen fra fediverset",
"enable_federation": "Skru på føderasjon",
"federation": "Føderasjon/ActivityPub",
"recurrent_event_visible": "Vis gjentagende hendelser som forvalg",
"allow_recurrent_event": "Tillat gjentagende hendelser",
"allow_anon_event": "Tillat anonyme hendelser (må bekreftes)?",
"allow_registration_description": "Tillat selv-registrering?",
"event_confirm_description": "Du kan bekrefte hendelser som oppføres av anonyme brukere her",
"place_description": "Hvis du har valgt feil sted eller adresse, kan du endre det. <br/>Alle nåværende og foregående hendelser tilknyttet dette stedet vil endre adresse."
},
"event": {
"interact_with_me": "Følg meg",
"from": "Fra",
"due": "til",
"each_month": "Hver måed",
"each_2w": "Annenhver uke",
"each_week": "Hver uke",
"recurrent_1w_days": "Hver {days}",
"normal_description": "Velg dagen.",
"normal": "Normal",
"multidate": "Flere dager",
"recurrent_description": "Velg hyppighet og velg dagene",
"only_future": "kun kommende hendelser",
"show_past": "også i fortid",
"show_recurrent": "gjentagende hendelser",
"recurrent": "Gjentagende",
"remove_confirmation": "Er du sikker på at du vil fjerne denne hendelsen?",
"not_found": "Fant ikke hendelse",
"confirmed": "Hendelse bekreftet",
"tag_description": "Etikett",
"description_description": "Beskrivelse",
"what_description": "Tittel",
"same_day": "på samme dag",
"anon": "Anon",
"follow_me_description": "Én av måtene å holde deg oppdatert på hendelser som publiseres her på {title}\ner å følge kontoen <u>{account}</u> fra fediverset, for eksempel via Gab, og også legge til ressurser til en hendelser derfra.<br/><br/>\nHvis du aldri har hørt om Gab eller fediverset anbefales <a href='https://www.savjee.be/videos/simply-explained/mastodon-and-fediverse-explained/'>denne artikkelen</a>.<br/><br/> Skriv inn din instans nedenfor (f.eks. social.librem.one)",
"remove_recurrent_confirmation": "Er du sikker på at du ønsker å fjerne denne gjentagende hendelsen?\nHendelser i fortiden vil forbli, men ingen videre hendelser vil bli opprettet.",
"interact_with_me_at": "Snakk til meg i fediverset på",
"image_too_big": "Bildet kan ikke være større enn 4 MB",
"recurrent_2m_ordinal": "|Den {n} {days} i måneden annenhver|Den {n} {days} i måneden annenhver",
"recurrent_1m_ordinal": "På {n} {days} i hver måned",
"recurrent_2m_days": "|På {days} i hver måned annenhver|{days} i hver måned annenhver",
"recurrent_1m_days": "|På {days} i hver måned|{days} i hver måned",
"recurrent_2w_days": "En {days} annenhver",
"multidate_description": "Er det en festival? Velg når den starter og slutter",
"where_description": "Hvor finner hendelsen sted? Hvis den ikke finnes kan du opprette den.",
"added_anon": "Hendelse lagt til, men ikke bekreftet enda.",
"added": "Hendelse lagt til",
"media_description": "Du kan legge til et flygeblad (valgfritt)",
"anon_description": "Du kan legge til en hendelse uten å registrere deg eller logge inn, og den vil bli lagt ut etter at den er bekreftet å være passende. Det vil ikke være mulig å endre den.<br/><br/>\nDu kan istedenfor <a href='/login'>logge inn</a>, eller <a href='/register'>registrere deg</a>. Ellers kan du forsette for å få et svar så snart som mulig. ",
"ics": "ICS",
"import_ICS": "Importer fra ICS",
"import_URL": "Importer fra nettadresse",
"edit_recurrent": "Rediger gjentagende hendelse",
"updated": "Handelse oppdatert"
},
"register": {
"first_user": "Administrator opprettet",
"complete": "Registrering må bekreftes.",
"error": "Feil: ",
"description": "Sosiale bevegelser bør organisere og finansiere seg selv.<br/>\n<br/>Før du kan publisere, <strong>må kontoen godkjennes</strong>, ha i minnet <strong> at bak denne siden er det mennesker, så skriv to linjer om hvilke hendelser du ønsker å publisere."
},
"recover": {
"not_valid_code": "Noe gikk galt."
},
"login": {
"ok": "Innlogget",
"forgot_password": "Glemt passordet?",
"not_registered": "Ikke registrert?",
"description": "Ved å logge inn kan du publisere nye hendelser.",
"error": "Kunne ikke logge inn. Sjekk dine innloggingsdetaljer.",
"check_email": "Sjekk din e-postinnboks og søppelpost.",
"insert_email": "Skriv inn din e-postadresse"
},
"common": {
"reset": "Tilbakestill",
"theme": "Drakt",
"tags": "Etiketter",
"place": "Sted",
"url": "Nettadresse",
"announcements": "Kunngjøringer",
"delete": "Fjern",
"skip": "Hopp over",
"fediverse": "Fediverset",
"start": "Start",
"pause": "Pause",
"event": "Hendelse",
"filter": "Filter",
"title": "Tittel",
"user": "Bruker",
"moderation": "Moderering",
"follow": "Følg",
"follow_me_title": "Følg oppdateringer fra fediverset",
"feed_url_copied": "Informasjonskanalsnettadresse kopiert, lim den inn i din RSS-leser",
"feed": "RSS-informasjonskanal",
"embed_help": "Kopiering av følgende kode til nettsiden din vil vises som her",
"embed_title": "Bygg inn denne hendelsen på nettsiden din",
"embed": "Innebygg",
"copied": "Kopiert",
"instances": "Instanser",
"add_to_calendar": "Legg til i kalender",
"send_via_mail": "Send e-post",
"copy_link": "Kopier lenke",
"set_password": "Sett passord",
"displayname": "Visningsnavn",
"activate_user": "Bekreftet",
"resources": "Ressurser",
"password_updated": "Passord endret.",
"me": "Du",
"disable": "Skru av",
"enable": "Skru på",
"cancel": "Avbryt",
"ok": "OK",
"new_user": "Ny bruker",
"new_password": "Nytt passord",
"recover_password": "Gjenopprett passord",
"copy": "Kopier",
"logout_ok": "Utlogget",
"add": "Legg til",
"related": "Relatert",
"edit_event": "Rediger hendelse",
"name": "Navn",
"share": "Del",
"logout": "Logg ut",
"preview": "Forhåndsvis",
"save": "Lagre",
"activate": "Aktiver",
"remove_admin": "Fjern administrator",
"deactivate": "Skru av",
"actions": "Handlinger",
"settings": "Valg",
"places": "Steder",
"events": "Handelser",
"admin": "Administrator",
"users": "Brukere",
"confirm": "Bekreft",
"info": "Info",
"edit": "Rediger",
"search": "Søk",
"hide": "Skjul",
"remove": "Fjern",
"description": "Beskrivelse",
"register": "Registrer",
"password": "Passord",
"email": "E-post",
"login": "Logg inn",
"media": "Media",
"what": "Hva",
"when": "Når",
"address": "Adresse",
"where": "Hvor",
"send": "Send",
"export": "Eksporter",
"next": "Neste",
"add_event": "Legg til hendelse",
"authorize": "Autoriser",
"federation": "Føderasjon",
"n_resources": "ingen ressurs|én ressurs|{n} ressurser",
"associate": "Tilknytt",
"import": "Importer"
},
"about": "\n <p><a href='https://gancio.org'>Gancio</a> er en delt agenda for lokale gemenskaper.</p>\n ",
"validators": {
"email": "Skriv inn en gyldig e-postadresse",
"required": "{fieldName} kreves"
},
"ordinal": {
"-1": "siste",
"5": "femte",
"4": "fjerde",
"3": "tredje",
"2": "andre",
"1": "første"
},
"export": {
"list_description": "Hvis du har en nettside og ønsker å vise en liste over hendelser, bruk følgende kode",
"ical_description": "Datamaskiner og smarttelefoner er vanligvis utstyrt med et kalenderprogram som kan importere kalendere.",
"feed_description": "For å følge oppdateringer fra en datamaskin eller smarttelefon uten å trenge å åpne denne siden, kan du bruke en RSS-leser. </p>\n\n<p> Med en RSS-informasjonskanal kan du bruke et egnet program for å motta oppdateringer fra sider som interesserer deg. Det er en bra måte å følge mange sider raskt, uten å måtte opprette en konto eller annet plunder. </p>\n\n<li> Hvis du har Android, anbefales <a href=\"https://f-droid.org/en/packages/net.frju.flym/\">Flym</a> or Feeder </li>\n<li> For iPhone/iPad kan du bruke <a href=\"https://itunes.apple.com/ua/app/feeds4u/id1038456442?mt=8\"> Feed4U </a> </li>\n<li> For skrivebord/bærbar anbefales Feedbro, installert på <a href=\"https://addons.mozilla.org/en-GB/firefox/addon/feedbroreader/\"> Firefox </a> eller <a href=\"https://chrome.google.com/webstore/detail/feedbro/mefgmmbdailogpfhfblcnnjfmnpnmdfa\">Chrome </a>.</li>\n<br/>\nÅ legge denne lenken til i din RSS-leser vil holde deg oppdatert.",
"insert_your_address": "Skriv inn din e-postadresse",
"email_description": "Du kan få hendelser som interesserer deg tilsendt per e-post.",
"intro": "Ulikt usosiale plattformer som gjør det de kan for å beholde brukere og data om dem, tror vi at den infoen, som folk, må ha sin frihet. Du kan holde deg oppdatert om hendelsene du ønsker, uten å nødvendigvis gå gjennom denne siden."
}
}

View file

@ -1,5 +1,4 @@
const conf = require('config') const conf = require('config')
const { format, transports } = require('winston')
module.exports = { module.exports = {
telemetry: false, telemetry: false,
@ -56,28 +55,11 @@ module.exports = {
*/ */
modules: [ modules: [
// Doc: https://axios.nuxtjs.org/usage // Doc: https://axios.nuxtjs.org/usage
'@nuxtjs/axios', './@nuxtjs/axios',
'@nuxtjs/auth', './@nuxtjs/auth',
['nuxt-express-module', { expressPath: 'server/', routesPath: 'server/routes' }] ['nuxt-express-module', { expressPath: 'server/', routesPath: 'server/routes' }]
], ],
// configure nuxt-winston-log module
winstonLog: {
skipRequestMiddlewareHandler: true,
useDefaultLogger: false,
loggerOptions: {
transports: process.env.NODE_ENV !== 'production'
? [new transports.Console(
{ level: 'debug', format: format.combine(format.colorize(), format.simple(), format.errors({ stack: true })) }
)]
: [new transports.File(
{
filename: 'logs/gancio.log',
format: format.combine(format.simple(), format.errors({ stack: true }))
}
)]
}
},
/* /*
** Axios module configuration ** Axios module configuration
* See https://github.com/nuxt-community/axios-module#options * See https://github.com/nuxt-community/axios-module#options
@ -117,14 +99,22 @@ module.exports = {
], ],
vuetify: { vuetify: {
defaultAssets: false, defaultAssets: false,
optionsPath: './vuetify.options.js' optionsPath: './vuetify.options.js',
treeShake: true
/* module options */ /* module options */
}, },
/* /*
** Build configuration ** Build configuration
*/ */
build: { build: {
presets: ['@nuxt/babel-preset-app'], presets: ['@nuxt/babel-preset-app', {
useBuiltIns: 'usage', // or "entry"
corejs: 3
}],
babel: {
plugins: [['@babel/plugin-proposal-private-methods', { loose: true }]]
},
cache: true cache: true
} }
} }

View file

@ -1,10 +1,10 @@
{ {
"name": "gancio", "name": "gancio",
"version": "1.0.4-dev", "version": "1.0.0-alpha",
"description": "A shared agenda for local communities", "description": "A shared agenda for local communities",
"author": "lesion", "author": "lesion",
"scripts": { "scripts": {
"build": "cross-env NODE_ENV=production nuxt build --modern", "build": "cross-env nuxt build --modern",
"lint": "eslint --ext .js,.vue --ignore-path .gitignore .", "lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
"dev": "NODE_ENV=development node server/index.js", "dev": "NODE_ENV=development node server/index.js",
"dev:nuxt": "cross-env NODE_ENV=development nuxt dev --modern", "dev:nuxt": "cross-env NODE_ENV=development nuxt dev --modern",
@ -24,24 +24,30 @@
"locales/email/", "locales/email/",
"locales/", "locales/",
"store/", "store/",
"config/", "config/default.json",
".nuxt/" "config/production.js",
".nuxt/",
"yarn.lock"
], ],
"dependencies": { "dependencies": {
"@nuxtjs/auth": "^4.9.1", "@nuxtjs/auth": "^4.9.1",
"@nuxtjs/axios": "^5.13.1", "@nuxtjs/axios": "^5.13.5",
"@popperjs/core": "2.9.2",
"accept-language": "^3.0.18", "accept-language": "^3.0.18",
"axios": "^0.21.1", "axios": "^0.21.1",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"body-parser": "^1.18.3", "body-parser": "^1.18.3",
"bufferutil": "^4.0.1",
"config": "^3.3.6", "config": "^3.3.6",
"consola": "^2.15.3",
"cookie-parser": "^1.4.5", "cookie-parser": "^1.4.5",
"core-js": "3.14.0",
"cors": "^2.8.5", "cors": "^2.8.5",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"date-fns": "^2.19.0", "date-fns": "^2.21.3",
"dayjs": "^1.10.4", "dayjs": "^1.10.5",
"dompurify": "^2.2.7", "dompurify": "^2.2.9",
"email-templates": "^8.0.3", "email-templates": "^8.0.7",
"express": "^4.17.1", "express": "^4.17.1",
"express-oauth-server": "^2.0.0", "express-oauth-server": "^2.0.0",
"express-prom-bundle": "^6.3.4", "express-prom-bundle": "^6.3.4",
@ -50,80 +56,81 @@
"http-signature": "^1.3.5", "http-signature": "^1.3.5",
"ical.js": "^1.4.0", "ical.js": "^1.4.0",
"ics": "^2.27.0", "ics": "^2.27.0",
"inquirer": "^8.0.0", "inquirer": "^8.1.1",
"jsdom": "^16.5.1", "jsdom": "^16.6.0",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"less": "^4.1.1", "less": "^4.1.1",
"linkifyjs": "2.1.9", "linkifyjs": "3.0.0-beta.3",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"microformat-node": "^2.0.1", "microformat-node": "^2.0.1",
"mkdirp": "^1.0.4", "mkdirp": "^1.0.4",
"moment-timezone": "^0.5.33",
"morgan": "^1.10.0",
"multer": "^1.4.2", "multer": "^1.4.2",
"nuxt": "^2.15.3", "nuxt": "^2.15.7",
"nuxt-express-module": "^0.0.11", "nuxt-express-module": "^0.0.11",
"pg": "^8.5.1", "pg": "^8.6.0",
"pg-native": "3.0.0",
"prom-client": "^13.1.0", "prom-client": "^13.1.0",
"sequelize": "^6.6.2", "sequelize": "^6.6.2",
"sequelize-cli": "^6.2.0", "sequelize-cli": "^6.2.0",
"sharp": "^0.27.2", "sequelize-slugify": "^1.5.0",
"sharp": "^0.28.2",
"sqlite3": "^5.0.2", "sqlite3": "^5.0.2",
"tiptap": "^1.32.0", "tiptap": "^1.32.0",
"tiptap-extensions": "^1.35.0", "tiptap-extensions": "^1.35.0",
"to-ico": "^1.1.5", "to-ico": "^1.1.5",
"url": "^0.11.0", "url": "^0.11.0",
"utf-8-validate": "^5.0.5",
"v-calendar": "2.3.0", "v-calendar": "2.3.0",
"vue": "^2.6.14",
"vue-clipboard2": "^0.3.1", "vue-clipboard2": "^0.3.1",
"vue-i18n": "^8.24.2", "vue-i18n": "^8.24.4",
"vue-server-renderer": "^2.6.14",
"vue-template-compiler": "^2.6.14",
"winston": "^3.3.3", "winston": "^3.3.3",
"yargs": "^16.1.1" "winston-daily-rotate-file": "^4.5.5",
"yargs": "^17.0.1"
}, },
"devDependencies": { "devDependencies": {
"@mdi/font": "^5.9.55", "@mdi/font": "^5.9.55",
"@nuxtjs/eslint-config": "^6.0.0", "@nuxtjs/eslint-config": "^6.0.1",
"@nuxtjs/vuetify": "^1.11.3", "@nuxtjs/vuetify": "^1.12.1",
"@popperjs/core": "^2.9.1", "@typescript-eslint/eslint-plugin": "^4.26.0",
"@typescript-eslint/parser": "^4.26.0",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"bufferutil": "^4.0.3", "eslint": "^7.27.0",
"canvas": "^2.7.0", "eslint-config-prettier": "^8.3.0",
"eslint": "^7.22.0", "eslint-config-standard": "^16.0.3",
"eslint-config-prettier": "^8.1.0",
"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.23.4",
"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.3.1", "eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-promise": "^4.3.1", "eslint-plugin-promise": "^5.1.0",
"eslint-plugin-standard": "^5.0.0", "eslint-plugin-standard": "^5.0.0",
"eslint-plugin-vue": "^7.8.0", "eslint-plugin-vue": "^7.10.0",
"fibers": "^5.0.0", "fibers": "^5.0.0",
"jquery": "^3.6.0",
"less-loader": "7", "less-loader": "7",
"node-gyp": "3.x", "prettier": "^2.3.0",
"node-sass": "^5.0.0",
"nodemon": "^2.0.7",
"pg-native": "^3.0.0",
"prettier": "^2.2.1",
"pug": "^3.0.2", "pug": "^3.0.2",
"pug-plain-loader": "^1.1.0", "pug-plain-loader": "^1.1.0",
"react": "^17.0.2", "sass": "^1.32.12",
"react-dom": "^17.0.2",
"sass": "^1.32.7",
"sass-loader": "10", "sass-loader": "10",
"typescript": "^4.2.3", "typescript": "^4.3.4",
"utf-8-validate": "^5.0.4", "vue-cli-plugin-vuetify": "~2.4.0",
"vue": "^2.6.12", "vuetify": "^2.5.4",
"vue-cli-plugin-vuetify": "~2.3.1",
"vue-template-compiler": "^2.6.12",
"vuetify": "^2.4.8",
"vuetify-loader": "^1.7.1", "vuetify-loader": "^1.7.1",
"webpack": "4", "webpack": "4",
"webpack-cli": "^4.5.0" "webpack-cli": "^4.7.2"
}, },
"resolutions": { "resolutions": {
"prosemirror-model": "1.13.3" "prosemirror-model": "1.14.1",
"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",
"@nuxtjs/vuetify/**/sass": "1.32.12"
}, },
"bin": { "bin": {
"gancio": "server/cli.js" "gancio": "server/cli.js"

View file

@ -1,39 +1,40 @@
<template lang='pug'> <template lang='pug'>
v-row.mt-5(align='center' justify='center') v-container
v-col(cols='12' md="6" lg="5" xl="4") v-row.mt-5(align='center' justify='center')
v-form(v-model='valid' ref='form' lazy-validation @submit.prevent='submit') v-col(cols='12' md="6" lg="5" xl="4")
v-card v-form(v-model='valid' ref='form' lazy-validation @submit.prevent='submit')
v-card-title {{$t('common.login')}} v-card
v-card-subtitle(v-text="$t('login.description')") v-card-title {{$t('common.login')}}
v-card-subtitle(v-text="$t('login.description')")
v-card-text v-card-text
v-text-field(v-model='email' type='email' v-text-field(v-model='email' type='email'
validate-on-blur validate-on-blur
:rules='$validators.email' autofocus :rules='$validators.email' autofocus
:placeholder='$t("common.email")' :placeholder='$t("common.email")'
ref='email') ref='email')
v-text-field(v-model='password' v-text-field(v-model='password'
:rules='$validators.password' :rules='$validators.password'
type='password' type='password'
:placeholder='$t("common.password")') :placeholder='$t("common.password")')
v-card-actions v-card-actions
v-btn(text v-btn(text
tabindex="1" tabindex="1"
@click='forgot' small) {{$t('login.forgot_password')}} @click='forgot' small) {{$t('login.forgot_password')}}
v-card-actions v-card-actions
v-spacer v-spacer
v-btn(v-if='settings.allow_registration' v-btn(v-if='settings.allow_registration'
to='/register' to='/register'
text text
color='orange') {{$t('login.not_registered')}} color='orange') {{$t('login.not_registered')}}
v-btn(color='success' v-btn(color='success'
type='submit' type='submit'
:disabled='!valid || loading' :loading='loading') {{$t('common.login')}} :disabled='!valid || loading' :loading='loading') {{$t('common.login')}}
</template> </template>

View file

@ -1,4 +1,5 @@
<template lang='pug'> <template lang='pug'>
v-container
v-row.mt-5(align='center' justify='center') v-row.mt-5(align='center' justify='center')
v-col(cols='12' md="6" lg="5" xl="4") v-col(cols='12' md="6" lg="5" xl="4")

View file

@ -1,5 +1,5 @@
<template lang="pug"> <template lang="pug">
.when v-col(cols=12)
.text-center .text-center
v-btn-toggle.v-col-6.flex-column.flex-sm-row(v-model='type' color='primary' @change='type => change("type", type)') v-btn-toggle.v-col-6.flex-column.flex-sm-row(v-model='type' color='primary' @change='type => change("type", type)')
v-btn(value='normal' label="normal") {{$t('event.normal')}} v-btn(value='normal' label="normal") {{$t('event.normal')}}
@ -9,7 +9,6 @@
p {{$t(`event.${type}_description`)}} p {{$t(`event.${type}_description`)}}
v-btn-toggle.v-col-6.flex-column.flex-sm-row(v-if='type === "recurrent"' color='primary' :value='value.recurrent.frequency' @change='fq => change("frequency", fq)') v-btn-toggle.v-col-6.flex-column.flex-sm-row(v-if='type === "recurrent"' color='primary' :value='value.recurrent.frequency' @change='fq => change("frequency", fq)')
v-btn(v-for='f in frequencies' :key='f.value' :value='f.value') {{f.text}} v-btn(v-for='f in frequencies' :key='f.value' :value='f.value') {{f.text}}
client-only client-only
.datePicker.mt-3 .datePicker.mt-3
v-input(:value='fromDate' v-input(:value='fromDate'
@ -28,17 +27,20 @@
div.text-center.mb-2(v-if='type === "recurrent"') div.text-center.mb-2(v-if='type === "recurrent"')
span(v-if='value.recurrent.frequency !== "1m" && value.recurrent.frequency !== "2m"') {{whenPatterns}} span(v-if='value.recurrent.frequency !== "1m" && value.recurrent.frequency !== "2m"') {{whenPatterns}}
v-btn-toggle.mt-1.flex-column.flex-sm-row(v-else :value='value.recurrent.type' color='primary' @change='fq => change("", fq)') v-btn-toggle.mt-1.flex-column.flex-sm-row(v-else :value='value.recurrent.type' color='primary' @change='fq => change("recurrentType", fq)')
v-btn(v-for='whenPattern in whenPatterns' :value='whenPattern.key' :key='whenPatterns.key' small) {{whenPattern.label}} v-btn(v-for='whenPattern in whenPatterns' :value='whenPattern.key' :key='whenPatterns.key' small) {{whenPattern.label}}
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-select(dense :label="$t('event.from')" :value='fromHour' clearable
:disabled='!value.from'
:rules="[$validators.required('event.from')]" :rules="[$validators.required('event.from')]"
:items='hourList' @change='hr => change("fromHour", hr)') :items='hourList' @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')" :value='dueHour' clearable v-select(dense :label="$t('event.due')"
:disabled='!fromHour'
:value='dueHour' clearable
:items='hourList' @change='hr => change("dueHour", hr)') :items='hourList' @change='hr => change("dueHour", hr)')
//- div.col-md-12(v-if='isRecurrent') //- div.col-md-12(v-if='isRecurrent')
@ -131,27 +133,26 @@ export default {
// { label: this.$tc(`event.recurrent_${freq}_ordinal`, { n, days: weekDay }), key: 'weekday' } // { label: this.$tc(`event.recurrent_${freq}_ordinal`, { n, days: weekDay }), key: 'weekday' }
] ]
if (n < 5) {
patterns.push(
{
label: this.$t(`event.recurrent_${freq}_ordinal`, { n: this.$t(`ordinal.${n}`), days: weekDay }),
key: n
}
)
}
// if selected day is in last week, propose also this type of selection // if selected day is in last week, propose also this type of selection
const lastWeek = date.daysInMonth() - monthDay < 7 const lastWeek = date.daysInMonth() - monthDay < 7
if (lastWeek) { if (lastWeek) {
patterns.push( patterns.push(
{ {
label: this.$t(`event.recurrent_${freq}_ordinal`, label: this.$t(`event.recurrent_${freq}_ordinal`, { n: this.$t('ordinal.-1'), days: weekDay }),
{ n: this.$t('ordinal.-1'), days: weekDay }), key: -1
key: 'weekday'
} }
) )
} }
if (n < 5) {
patterns.push(
{
label: this.$t(`event.recurrent_${freq}_ordinal`,
{ n: this.$t(`ordinal.${n}`), days: weekDay }),
key: 'weekday'
}
)
}
return patterns return patterns
} else if (freq === '1d') { } else if (freq === '1d') {
return this.$t('event.recurrent_each_day') return this.$t('event.recurrent_each_day')
@ -196,6 +197,8 @@ export default {
} }
} else if (what === 'frequency') { } else if (what === 'frequency') {
this.$emit('input', { ...this.value, recurrent: { ...this.value.recurrent, frequency: value } }) this.$emit('input', { ...this.value, recurrent: { ...this.value.recurrent, frequency: value } })
} else if (what === 'recurrentType') {
this.$emit('input', { ...this.value, recurrent: { ...this.value.recurrent, type: value } })
} else if (what === 'fromHour') { } else if (what === 'fromHour') {
if (value) { if (value) {
const [hour, minute] = value.split(':') const [hour, minute] = value.split(':')
@ -219,7 +222,12 @@ export default {
} else { } else {
this.$emit('input', { ...this.value, dueHour: false }) this.$emit('input', { ...this.value, dueHour: false })
} }
// change date in calendar (could be a range or a recurrent event...)
} else if (what === 'date') { } else if (what === 'date') {
if (value === null) {
this.$emit('input', { ...this.value, from: null, fromHour: false })
return
}
if (this.value.multidate) { if (this.value.multidate) {
let from = value.start let from = value.start
let due = value.end let due = value.end

View file

@ -4,22 +4,22 @@
v-card-text v-card-text
p(v-html="$t('event.import_description')") p(v-html="$t('event.import_description')")
v-form(v-model='valid' ref='form' lazy-validation @submit.prevent='importGeneric') v-form(v-model='valid' ref='form' lazy-validation @submit.prevent='importGeneric')
v-text-field(v-model='URL' v-row
:label="$t('common.url')" v-col
:hint="$t('event.import_URL')" v-text-field(v-model='URL'
persistent-hint :label="$t('common.url')"
:loading='loading' :error='error' :hint="$t('event.import_URL')"
:error-messages='errorMessage') persistent-hint
:loading='loading' :error='error'
:error-messages='errorMessage')
v-col
v-file-input(
v-model='file'
accept=".ics"
:label="$t('event.ics')"
:hint="$t('event.import_ICS')"
persistent-hint)
v-file-input(
v-model='file'
accept=".ics"
:label="$t('event.ics')"
:hint="$t('event.import_ICS')"
persistent-hint
)
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')}}
@ -29,6 +29,8 @@
</template> </template>
<script> <script>
import ical from 'ical.js' import ical from 'ical.js'
import get from 'lodash/get'
import { mapState } from 'vuex'
export default { export default {
name: 'ImportDialog', name: 'ImportDialog',
@ -40,9 +42,10 @@ export default {
loading: false, loading: false,
valid: false, valid: false,
URL: '', URL: '',
events: {} event: {}
} }
}, },
computed: mapState(['places']),
methods: { methods: {
importGeneric () { importGeneric () {
if (this.file) { if (this.file) {
@ -55,18 +58,30 @@ export default {
const reader = new FileReader() const reader = new FileReader()
reader.readAsText(this.file) reader.readAsText(this.file)
reader.onload = () => { reader.onload = () => {
const event = ical.parse(reader.result) const ret = ical.parse(reader.result)
const component = new ical.Component(ret)
const events = component.getAllSubcomponents('vevent')
const event = new ical.Event(events[0])
this.event = { this.event = {
title: event.name title: get(event, 'summary', ''),
description: get(event, 'description', ''),
place: { name: get(event, 'location', '') },
start_datetime: get(event, 'startDate', '').toUnixTime(),
end_datetime: get(event, 'endDate', '').toUnixTime()
} }
this.$emit('imported', this.event)
} }
}, },
async importURL () { async importURL () {
if (!this.URL) { if (!this.URL) {
this.errorMessage = this.$validators.required('common.URL')('') this.errorMessage = this.$validators.required('common.url')('')
this.error = true this.error = true
return return
} }
if (!this.URL.match(/^https?:\/\//)) {
this.URL = `https://${this.URL}`
}
this.error = false this.error = false
this.errorMessage = '' this.errorMessage = ''
this.loading = true this.loading = true
@ -74,11 +89,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.events = ret this.events = ret
// check if contain an h-event // check if contain an h-event
this.$emit('imported', ret[0]) this.$emit('imported', ret[0])
} catch (e) { } catch (e) {
console.error(e)
this.error = true this.error = true
this.errorMessage = String(e) this.errorMessage = String(e)
} }

View file

@ -1,31 +1,33 @@
<template lang="pug"> <template lang="pug">
v-row v-row
v-combobox.col-md-6(ref='place' v-col(cols=12 md=6)
:rules="[$validators.required('common.where')]" v-combobox(ref='place'
:label="$t('common.where')" :rules="[$validators.required('common.where')]"
:hint="$t('event.where_description')" :label="$t('common.where')"
:search-input.sync="placeName" :hint="$t('event.where_description')"
prepend-icon='mdi-map-marker' :search-input.sync="placeName"
persistent-hint prepend-icon='mdi-map-marker'
:value="value.name" persistent-hint
:items="filteredPlaces" :value="value.name"
no-filter :items="filteredPlaces"
item-text='name' no-filter
@change='selectPlace') item-text='name'
template(v-slot:item="{ item }") @change='selectPlace')
v-list-item-content(two-line v-if='item.create') template(v-slot:item="{ item }")
v-list-item-title <v-icon color='primary'>mdi-plus</v-icon> {{item.name}} v-list-item-content(two-line v-if='item.create')
v-list-item-content(two-line v-else) v-list-item-title <v-icon color='primary'>mdi-plus</v-icon> {{item.name}}
v-list-item-title {{item.name}} v-list-item-content(two-line v-else)
v-list-item-subtitle {{item.address}} v-list-item-title {{item.name}}
v-list-item-subtitle {{item.address}}
v-text-field.col-md-6(ref='address' v-col(cols=12 md=6)
prepend-icon='mdi-map' v-text-field(ref='address'
v-show='!disableAddress' prepend-icon='mdi-map'
:rules="[$validators.required('common.address')]" :disabled='disableAddress'
:label="$t('common.address')" :rules="[ v => disableAddress ? true : $validators.required('common.address')(v)]"
@change="changeAddress" :label="$t('common.address')"
:value="value.address") @change="changeAddress"
:value="value.address")
</template> </template>
<script> <script>
@ -74,10 +76,17 @@ export default {
this.disableAddress = true this.disableAddress = true
} else { // this is a new place } else { // this is a new place
this.place.name = p.name || p this.place.name = p.name || p
this.place.address = '' // search for a place with the same name
this.disableAddress = false const place = this.places.find(p => p.name === this.place.name)
this.$refs.place.blur() if (place) {
this.$refs.address.focus() 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 }) this.$emit('input', { ...this.place })
}, },

View file

@ -14,48 +14,54 @@
v-container v-container
v-row v-row
//- Not logged event //- Not logged event
v-col.col-12(v-if='!$auth.loggedIn') v-col(v-if='!$auth.loggedIn' cols=12)
p(v-html="$t('event.anon_description')") p(v-html="$t('event.anon_description')")
//- Title //- Title
v-text-field.col-12( v-col(cols=12)
@change='v => event.title = v' v-text-field(
:value = 'event.title' @change='v => event.title = v'
:rules="[$validators.required('common.title')]" :value = 'event.title'
prepend-icon='mdi-format-title' :rules="[$validators.required('common.title')]"
:label="$t('common.title')" prepend-icon='mdi-format-title'
autofocus :label="$t('common.title')"
ref='title') autofocus
ref='title')
//- Where //- Where
WhereInput.col-12(v-model='event.place') v-col(cols=12)
WhereInput(ref='where' v-model='event.place')
//- When //- When
DateInput.col-12(v-model='date') DateInput(v-model='date')
//- Description //- Description
Editor.col-12.mb-3( v-col.px-0(cols='12')
:label="$t('event.description_description')" Editor.px-3.ma-0(
v-model='event.description' :label="$t('event.description_description')"
:placeholder="$t('event.description_description')" v-model='event.description'
max-height='400px') :placeholder="$t('event.description_description')"
max-height='400px')
//- MEDIA / FLYER / POSTER //- MEDIA / FLYER / POSTER
v-file-input.col-12.col-sm-6.mt-3( v-col(cols=12 md=6)
:label="$t('common.media')" v-file-input(
:hint="$t('event.media_description')" :label="$t('common.media')"
prepend-icon="mdi-camera" :hint="$t('event.media_description')"
v-model='event.image' prepend-icon="mdi-camera"
persistent-hint v-model='event.image'
accept='image/*') persistent-hint
accept='image/*')
v-img.col-12.col-sm-2.ml-3(v-if='mediaPreview' :src='mediaPreview')
//- tags //- tags
v-combobox.col-12.col-sm-6.mt-3(v-model='event.tags' v-col(cols=12 md=6)
prepend-icon="mdi-tag-multiple" v-combobox(v-model='event.tags'
chips small-chips multiple deletable-chips hide-no-data hide-selected persistent-hint prepend-icon="mdi-tag-multiple"
:delimiters="[',', ' ']" chips small-chips multiple deletable-chips hide-no-data hide-selected persistent-hint
:items="tags.map(t => t.tag)" :delimiters="[',', ' ']"
:label="$t('common.tags')") :items="tags.map(t => t.tag)"
:label="$t('common.tags')")
v-card-actions v-card-actions
v-spacer v-spacer
@ -70,12 +76,11 @@ import Editor from '@/components/Editor'
import List from '@/components/List' import List from '@/components/List'
import ImportDialog from './ImportDialog' import ImportDialog from './ImportDialog'
import DateInput from './DateInput' import DateInput from './DateInput'
import HourInput from './HourInput'
import WhereInput from './WhereInput' import WhereInput from './WhereInput'
export default { export default {
name: 'NewEvent', name: 'NewEvent',
components: { List, Editor, ImportDialog, WhereInput, HourInput, DateInput }, components: { List, Editor, ImportDialog, WhereInput, 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)
}, },
@ -107,6 +112,7 @@ export default {
data.event.description = event.description data.event.description = event.description
data.event.id = event.id data.event.id = event.id
data.event.tags = event.tags data.event.tags = event.tags
data.event.image_path = event.image_path
return data return data
} }
return {} return {}
@ -140,18 +146,41 @@ export default {
} }
}, },
computed: { computed: {
...mapState(['tags', 'places', 'settings']) ...mapState(['tags', 'places', 'settings']),
mediaPreview () {
if (!this.event.image && !this.event.image_path) {
return false
}
const url = this.event.image ? URL.createObjectURL(this.event.image) : `/media/thumb/${this.event.image_path}`
return url
}
}, },
methods: { methods: {
...mapActions(['updateMeta']), ...mapActions(['updateMeta']),
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.date = {
recurrent: this.event.recurrent || null,
from: new Date(dayjs.unix(this.event.start_datetime)),
due: new Date(dayjs.unix(this.event.end_datetime)),
multidate: event.multidate,
fromHour: true,
dueHour: true
}
this.openImportDialog = false
}, },
cleanFile () { cleanFile () {
this.event.image = {} this.event.image = {}
}, },
async done () { async done () {
if (!this.$refs.form.validate()) { return } if (!this.$refs.form.validate()) {
this.$nextTick(() => {
const el = document.querySelector('.v-input.error--text:first-of-type')
el.scrollIntoView()
})
return
}
this.loading = true this.loading = true
const formData = new FormData() const formData = new FormData()

View file

@ -14,6 +14,9 @@ export default {
try { try {
const id = Number(params.id) const id = Number(params.id)
const announcement = store.state.announcements.find(a => a.id === id) const announcement = store.state.announcements.find(a => a.id === id)
if (!announcement) {
error({ statusCode: 404, message: 'Announcement not found' })
}
return { announcement } return { announcement }
} catch (e) { } catch (e) {
error({ statusCode: 404, message: 'Announcement not found' }) error({ statusCode: 404, message: 'Announcement not found' })
@ -22,50 +25,14 @@ export default {
data () { data () {
return { announcement: { title: '' } } return { announcement: { title: '' } }
}, },
computed: mapState(['announcements']), head () {
methods: { if (!this.announcement) {
showResource (resource) { return {}
this.showResources = true
this.selectedResource = resource
document.getElementById('resourceDialog').focus()
} }
} return {
title: `${this.settings.title} - ${this.announcement.title}`
}
},
computed: mapState(['announcements', 'settings'])
} }
</script> </script>
<style lang='less'>
// .announcement-page {
// .el-header {
// height: auto !important;
// padding-top: 1em;
// border-bottom: 1px solid lightgray;
// }
// .title {
// max-width: 80%;
// max-height: 0.1rem;
// overflow: hidden;
// font-size: 1.6rem;
// line-height: 1;
// padding-right: 40px;
// }
// pre {
// white-space: pre-line;
// word-break: break-word;
// color: #aaa;
// font-size: 1.2em;
// font-family: inherit;
// }
// }
// @media only screen and (max-width: 768px) {
// #eventDetail {
// .title {
// font-size: 1em;
// font-weight: bold;
// }
// }
// }
</style>

View file

@ -1,5 +1,5 @@
<template lang="pug"> <template lang="pug">
nuxt-link.embed_event(:to='`/event/${id}`' target='_blank' :class='{ withImg: event.image_path }') nuxt-link.embed_event(:to='`/event/${event.slug || event.id}`' target='_blank' :class='{ withImg: event.image_path }')
//- image //- image
img.float-left(:src='`/media/thumb/${event.image_path || "logo.png"}`') img.float-left(:src='`/media/thumb/${event.image_path || "logo.png"}`')
@ -18,7 +18,7 @@ export default {
async asyncData ({ $axios, params, error, store }) { async asyncData ({ $axios, params, error, store }) {
try { try {
const event = await $axios.$get(`/event/${params.event_id}`) const event = await $axios.$get(`/event/${params.event_id}`)
return { event, id: Number(params.event_id) } return { event }
} catch (e) { } catch (e) {
error({ statusCode: 404, message: 'Event not found' }) error({ statusCode: 404, message: 'Event not found' })
} }

View file

@ -1,5 +1,5 @@
<template lang="pug"> <template lang="pug">
v-container 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)
v-card.h-event v-card.h-event
@ -10,20 +10,14 @@ v-container
v-row v-row
v-col.col-12.col-lg-8 v-col.col-12.col-lg-8
//- fake image to use u-featured in h-event microformat
img.u-featured(v-show='false' :src='`${settings.baseurl}${imgPath}`')
v-img.main_image.mb-3( v-img.main_image.mb-3(
contain contain
:src='imgPath' :src='imgPath'
:lazy-src='thumbImgPath' :lazy-src='thumbImgPath'
v-if='event.image_path') v-if='event.image_path')
.p-description.text-body-1(v-else v-html='event.description') .p-description.text-body-1.pa-3.grey.darken-4.rounded(v-else v-html='event.description')
//- template(v-slot:placeholder)
//- v-row(
//- class="fill-height ma-0"
//- align="center"
//- justify="center")
//- v-progress-circular(indeterminate
//- color="grey lighten-5")
v-col.col-12.col-lg-4 v-col.col-12.col-lg-4
v-card v-card
@ -54,7 +48,7 @@ v-container
template(v-slot:activator="{on, attrs} ") template(v-slot:activator="{on, attrs} ")
v-btn.ml-2(large icon v-on='on' color='primary' v-btn.ml-2(large icon v-on='on' color='primary'
v-clipboard:success='copyLink' v-clipboard:success='copyLink'
v-clipboard:copy='`${settings.baseurl}/event/${event.id}`') v-clipboard:copy='`${settings.baseurl}/event/${event.slug || event.id}`')
v-icon mdi-content-copy v-icon mdi-content-copy
v-tooltip(bottom) {{$t('common.embed')}} v-tooltip(bottom) {{$t('common.embed')}}
template(v-slot:activator="{on, attrs} ") template(v-slot:activator="{on, attrs} ")
@ -63,10 +57,10 @@ v-container
v-tooltip(bottom) {{$t('common.add_to_calendar')}} v-tooltip(bottom) {{$t('common.add_to_calendar')}}
template(v-slot:activator="{on, attrs} ") template(v-slot:activator="{on, attrs} ")
v-btn.ml-2(large icon v-on='on' color='primary' v-btn.ml-2(large icon v-on='on' color='primary'
:href='`/api/event/${event.id}.ics`') :href='`/api/event/${event.slug || event.id}.ics`')
v-icon mdi-calendar-export v-icon mdi-calendar-export
.p-description.text-body-1(v-if='event.image_path' v-html='event.description') .p-description.text-body-1.pa-3.grey.darken-4.rounded(v-if='event.image_path && event.description' v-html='event.description')
//- resources from fediverse //- resources from fediverse
#resources.mt-1(v-if='settings.enable_federation') #resources.mt-1(v-if='settings.enable_federation')
@ -74,14 +68,6 @@ v-container
small.mr-3 🔖 {{event.likes.length}} small.mr-3 🔖 {{event.likes.length}}
small {{event.boost.length}}<br/> small {{event.boost.length}}<br/>
//- p.p-2
//- v-btn(type='text' @click='showFollowMe=true') {{$t('event.interact_with_me')}}
//- span(v-if='settings.enable_resources && event.resources.length') - {{$tc('common.n_resources', event.resources.length)}}
//- v-dialog(v-model='showFollowMe' destroy-on-close max-width='500px')
h4(slot='title') {{$t('common.follow_me_title')}}
FollowMe(@close='showFollowMe=false' is-dialog)
v-dialog.showResource#resourceDialog(v-model='showResources' fullscreen v-dialog.showResource#resourceDialog(v-model='showResources' fullscreen
width='95vw' width='95vw'
destroy-on-close destroy-on-close
@ -132,13 +118,12 @@ v-container
import { mapState } from 'vuex' import { mapState } from 'vuex'
import EventAdmin from './eventAdmin' import EventAdmin from './eventAdmin'
import EmbedEvent from './embedEvent' import EmbedEvent from './embedEvent'
import FollowMe from '../../components/FollowMe'
import moment from 'dayjs' import moment from 'dayjs'
const htmlToText = require('html-to-text') const htmlToText = require('html-to-text')
export default { export default {
name: 'Event', name: 'Event',
components: { EventAdmin, EmbedEvent, FollowMe }, components: { EventAdmin, EmbedEvent },
async asyncData ({ $axios, params, error, store }) { async asyncData ({ $axios, params, error, store }) {
try { try {
const event = await $axios.$get(`/event/${params.id}`) const event = await $axios.$get(`/event/${params.id}`)
@ -151,7 +136,6 @@ export default {
return { return {
event: {}, event: {},
showEmbed: false, showEmbed: false,
showFollowMe: false,
showResources: false, showResources: false,
selectedResource: { data: { attachment: [] } } selectedResource: { data: { attachment: [] } }
} }
@ -191,7 +175,7 @@ export default {
{ {
hid: 'og-url', hid: 'og-url',
property: 'og:url', property: 'og:url',
content: `${this.settings.baseurl}/event/${this.event.id}` content: `${this.settings.baseurl}/event/${this.event.slug || this.event.id}`
}, },
{ property: 'og:type', content: 'event' }, { property: 'og:type', content: 'event' },
{ {
@ -317,6 +301,8 @@ export default {
<style lang='less'> <style lang='less'>
.title { .title {
margin-bottom: 25px; margin-bottom: 25px;
color: yellow;
font-weight: 300 !important;
} }
.main_image { .main_image {
// width: 100%; // width: 100%;

View file

@ -6,7 +6,7 @@ v-card
v-col.col-12 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-btn(slot='prepend' plain text color='primary' v-btn(slot='prepend' text color='primary'
v-clipboard:copy='code' v-clipboard:copy='code'
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
@ -23,13 +23,13 @@ import { mapState } from 'vuex'
export default { export default {
name: 'EmbedEvent', name: 'EmbedEvent',
props: { props: {
event: { type: Object, default: () => ({})} event: { type: Object, default: () => ({}) }
}, },
computed: { computed: {
...mapState(['settings']), ...mapState(['settings']),
code () { code () {
const style = "style='border: 0; width: 100%; height: 215px;'" const style = "style='border: 0; width: 100%; height: 215px;'"
const src = `${this.settings.baseurl}/embed/${this.event.id}` const src = `${this.settings.baseurl}/embed/${this.event.slug || this.event.id}`
const code = `<iframe ${style} src="${src}"></iframe>` const code = `<iframe ${style} src="${src}"></iframe>`
return code return code
} }

View file

@ -1,6 +1,7 @@
<template lang="pug"> <template lang="pug">
v-container v-container
v-card(outlined) v-card(outlined)
v-card-title {{$t('common.share')}}
v-card-text v-card-text
p.text-body-1 {{$t('export.intro')}} p.text-body-1 {{$t('export.intro')}}
v-row v-row
@ -64,9 +65,9 @@
color='primary' v-clipboard:copy='listScript' v-clipboard:success='copyLink.bind(this,"list")') {{$t('common.copy')}} color='primary' v-clipboard:copy='listScript' v-clipboard:success='copyLink.bind(this,"list")') {{$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(v-if='settings.enable_federation') {{$t('common.fediverse')}}
//- v-tab-item(v-if='settings.enable_federation') v-tab-item(v-if='settings.enable_federation')
//- FollowMe 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,27 +126,31 @@ export default {
} }
if (this.filters.tags.length) { if (this.filters.tags.length) {
params.push(`tags=${this.filters.tags.map(t => t.id)}`) params.push(`tags=${this.filters.tags.join(',')}`)
} }
if (this.filters.show_recurrent) {
params.push('show_recurrent=true')
}
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 typeMap = ['rss', 'ics', 'list']
const tags = this.filters.tags.join(',') const params = []
const places = this.filters.places.join(',')
let query = '' if (this.filters.tags.length) {
if (tags || places) { params.push(`tags=${this.filters.tags.join(',')}`)
query = '?'
if (tags) {
query += 'tags=' + tags
if (places) { query += '&places=' + places }
} else {
query += 'places=' + places
}
} }
return `${this.settings.baseurl}/feed/${typeMap[this.type]}${query}` if (this.filters.places.length) {
params.push(`places=${this.filters.places.join(',')}`)
}
if (this.filters.show_recurrent) {
params.push('show_recurrent=true')
}
return `${this.settings.baseurl}/feed/${typeMap[this.type]}?${params.join('&')}`
}, },
showLink () { showLink () {
return (['rss', 'ics'].includes(this.type)) return (['rss', 'ics'].includes(this.type))
@ -158,7 +163,7 @@ export default {
start: dayjs().unix(), start: dayjs().unix(),
places: this.filters.places, places: this.filters.places,
tags: this.filters.tags, tags: this.filters.tags,
show_recurrent: this.filters.show_recurrent show_recurrent: !!this.filters.show_recurrent
}) })
}, },
copyLink (type) { copyLink (type) {

View file

@ -2,30 +2,31 @@
v-container#home(fluid) v-container#home(fluid)
//- Announcements //- Announcements
#announcements.mr-1 #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 v-row.pt-0.pt-sm-2.pl-0.pl-sm-2
.col-xl-5.col-lg-5.col-md-7.col-sm-12.col-xs-12 .col-xl-5.col-lg-5.col-md-7.col-sm-12.col-xs-12.pa-4.pa-sm-3
//- 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
Calendar(@dayclick='dayChange' @monthchange='monthChange' :events='events') Calendar(@dayclick='dayChange' @monthchange='monthChange' :events='filteredEvents')
.col.pt-0.pt-md-2 .col.pt-0.pt-md-2
Search(:filters='filters' @update='updateFilters') Search(:filters='filters' @update='updateFilters')
v-chip(v-if='selectedDay' close @click:close='dayChange({ date: selectedDay})') {{selectedDay}} v-chip(v-if='selectedDay' close @click:close='dayChange({ date: selectedDay})') {{selectedDay}}
//- Events //- Events
#events.mt-1 #events.mb-2.mt-1.pl-1.pl-sm-2
//- div.event(v-for='(event, idx) in events' :key='event.id' v-intersect="(entries, observer, isIntersecting) => intersecting[event.id] = isIntersecting") //- div.event(v-for='(event, idx) in events' :key='event.id' v-intersect="(entries, observer, isIntersecting) => intersecting[event.id] = isIntersecting")
Event(:event='event' v-for='(event, idx) in events' :key='event.id' @tagclick='tagClick' @placeclick='placeClick') Event(:event='event' @destroy='destroy' v-for='(event, idx) in visibleEvents' :key='event.id' @tagclick='tagClick' @placeclick='placeClick')
</template> </template>
<script> <script>
import { mapState, mapActions } from 'vuex' import { mapState, mapActions } 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'
@ -37,21 +38,22 @@ export default {
components: { Event, Search, Announcement, Calendar }, components: { Event, Search, Announcement, Calendar },
async asyncData ({ params, $api, store }) { async asyncData ({ params, $api, store }) {
const events = await $api.getEvents({ const events = await $api.getEvents({
start: dayjs().unix(), start: dayjs().startOf('month').unix(),
end: null, end: null,
...store.state.filters show_recurrent: true
}) })
return { events, first: true } return { events }
}, },
data ({ $store }) { data ({ $store }) {
return { return {
first: true, first: true,
isCurrentMonth: true,
now: dayjs().unix(),
date: dayjs().format('YYYY-MM-DD'), date: dayjs().format('YYYY-MM-DD'),
events: [], events: [],
start: dayjs().unix(), start: dayjs().startOf('month').unix(),
end: null, end: null,
selectedDay: null selectedDay: null
// intersecting: {}
} }
}, },
head () { head () {
@ -70,17 +72,58 @@ export default {
] ]
} }
}, },
computed: mapState(['settings', 'announcements', 'filters']),
computed: {
...mapState(['settings', 'announcements', 'filters']),
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 () {
const now = dayjs().unix()
if (this.selectedDay) {
const min = dayjs(this.selectedDay).startOf('day').unix()
const max = dayjs(this.selectedDay).endOf('day').unix()
return this.filteredEvents.filter(e => (e.start_datetime < max && e.start_datetime > min))
} else if (this.isCurrentMonth) {
return this.filteredEvents.filter(e => e.end_datetime ? e.end_datetime > now : e.start_datetime + 2 * 60 * 60 > now)
} else {
return this.filteredEvents
}
}
},
methods: { methods: {
// onIntersect (isIntersecting, eventId) { // onIntersect (isIntersecting, eventId) {
// this.intersecting[eventId] = isIntersecting // this.intersecting[eventId] = isIntersecting
// }, // },
...mapActions(['setFilters']), ...mapActions(['setFilters']),
destroy (id) {
this.events = this.events.filter(e => e.id !== id)
},
updateEvents () { updateEvents () {
this.events = []
return this.$api.getEvents({ return this.$api.getEvents({
start: this.start, start: this.start,
end: this.end, end: this.end,
...this.filters show_recurrent: true
}).then(events => { }).then(events => {
this.events = events this.events = events
this.$nuxt.$loading.finish() this.$nuxt.$loading.finish()
@ -92,30 +135,33 @@ export default {
} else { } else {
this.setFilters({ ...this.filters, places: [].concat(this.filters.places, place_id) }) this.setFilters({ ...this.filters, places: [].concat(this.filters.places, place_id) })
} }
this.updateEvents()
}, },
tagClick (tag) { tagClick (tag) {
if (this.filters.tags.includes(tag)) { if (this.filters.tags.includes(tag)) {
this.filters.tags = this.filters.tags.filter(t => t !== tag)
this.setFilters({ ...this.filters, tags: this.filters.tags.filter(t => t !== tag) }) this.setFilters({ ...this.filters, tags: this.filters.tags.filter(t => t !== tag) })
} else { } else {
this.setFilters({ ...this.filters, tags: [].concat(this.filters.tags, tag) }) this.setFilters({ ...this.filters, tags: [].concat(this.filters.tags, tag) })
} }
this.updateEvents()
}, },
monthChange ({ year, month }) { monthChange ({ year, month }) {
// avoid first time monthChange event (onload)
if (this.first) { if (this.first) {
this.first = false this.first = false
return return
} }
this.$nuxt.$loading.start() this.$nuxt.$loading.start()
// unselect current selected day
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().month() && year === dayjs().year()) {
this.start = dayjs().unix() this.isCurrentMonth = true
this.start = dayjs().startOf('month').unix()
this.date = dayjs().format('YYYY-MM-DD') this.date = dayjs().format('YYYY-MM-DD')
} else { } else {
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()
} }
@ -125,21 +171,14 @@ export default {
}, },
updateFilters (filters) { updateFilters (filters) {
this.setFilters(filters) this.setFilters(filters)
this.updateEvents()
}, },
dayChange (day) { dayChange (day) {
const date = dayjs(day.date).format('YYYY-MM-DD') const date = dayjs(day.date).format('YYYY-MM-DD')
if (this.selectedDay === date) { if (this.selectedDay === date) {
this.selectedDay = null this.selectedDay = null
this.start = dayjs().unix() // .startOf('week').unix()
this.end = null
this.updateEvents()
return return
} }
this.start = dayjs(date).startOf('day').unix()
this.end = dayjs(date).endOf('day').unix()
this.selectedDay = date this.selectedDay = date
this.updateEvents()
} }
} }
} }

View file

@ -22,7 +22,7 @@ export default ({ $axios, store }, 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
} }
}) })
return events.map(e => Object.freeze(e)) return events.map(e => Object.freeze(e))

View file

@ -44,7 +44,12 @@ export default ({ app, store }) => {
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' ? 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).format('dddd')
recurrent = app.i18n.tc(`event.recurrent_${frequency}_${type}`, d) if (type === 'ordinal') {
recurrent = app.i18n.t(`event.recurrent_${frequency}_days`, { days: d })
} else {
recurrent = app.i18n.t(`event.recurrent_${frequency}_ordinal`,
{ n: app.i18n.t('ordinal.' + type), days: d })
}
} }
return recurrent return recurrent
}) })

View file

@ -41,9 +41,12 @@ const Auth = {
hasPerm (scope) { hasPerm (scope) {
return (req, res, next) => { return (req, res, next) => {
log.debug(scope, req.path) log.debug(scope, req.path)
oauth.oauthServer.authenticate({ scope })(req, res, () => { oauth.oauthServer.authenticate({ scope })(req, res, err => {
log.debug('has perm') if (err) {
next() next()
} else {
next(Error(err))
}
}) })
} }
} }

Some files were not shown because too many files have changed in this diff Show more