Merge branch 'master' into gh
This commit is contained in:
commit
5f6cc46cdc
98 changed files with 3789 additions and 2539 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -1,6 +1,12 @@
|
|||
# Created by .ignore support plugin (hsz.mobi)
|
||||
|
||||
### Gancio dev configuration
|
||||
*.tgz
|
||||
*.sql
|
||||
*.dump
|
||||
*.sqlite
|
||||
_*.js
|
||||
config*.json
|
||||
tests/seeds/testdb.sqlite
|
||||
preso.md
|
||||
gancio.sqlite
|
||||
|
|
26
CHANGELOG
26
CHANGELOG
|
@ -1,19 +1,29 @@
|
|||
All notable changes to this project will be documented in this file.
|
||||
|
||||
### 1.5.0 - UNRELEASED
|
||||
- new Tag page!
|
||||
- new Place page!
|
||||
##### :warning: **BREAKING CHANGES**:
|
||||
- supported nodejs version >=14 <=16 (nodejs 12 reached End-of-Life on 30 April 2022)
|
||||
- minimum mariadb supported version >= 10.5.2
|
||||
|
||||
##### **CHANGES**:
|
||||
- new Tag page
|
||||
- new Place page
|
||||
- new search flow
|
||||
- new meta-tag-place / group / collection page!
|
||||
- new meta-tag-place / group / collection page
|
||||
- new time selection widget
|
||||
- allow footer links reordering
|
||||
- new Docker image
|
||||
- new Docker image (smaller and faster)
|
||||
- add GANCIO_DB_PORT environment
|
||||
- merge old duplicated tags, trim
|
||||
- add dynamic sitemap.xml !
|
||||
- restrict new tag entropy (trim, merge case insensitive)
|
||||
- add dynamic sitemap.xml
|
||||
- calendar attributes refactoring (a dot each day, colors represents n. events)
|
||||
- fix event mime type response
|
||||
- new **undocumented** gancio CLI accounts management (list / create / remove / modify accounts)
|
||||
|
||||
- fix mariadb JSON fields
|
||||
- new gancio CLI accounts management (list / create / remove / modify accounts)
|
||||
- improve smtp setup, allow local sendmail, smpt port, tls/starttls
|
||||
- redirect to path based on content type request
|
||||
- add Slovak translation
|
||||
- lot of fixes
|
||||
|
||||
### 1.4.4 - 10 may '22
|
||||
- better img rendering, make it easier to download flyer #153
|
||||
|
|
|
@ -48,6 +48,23 @@ li {
|
|||
max-width: 100%;
|
||||
}
|
||||
|
||||
.v-application p {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.v-application blockquote {
|
||||
font-style: italic;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.editor .content {
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.theme--dark .editor .content {
|
||||
background-color: #292929;
|
||||
}
|
||||
|
||||
.theme--dark.v-list {
|
||||
background-color: #333;
|
||||
}
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
<template lang="pug">
|
||||
#calendar
|
||||
vc-date-picker(
|
||||
v-model='selectedDate'
|
||||
title-position='left'
|
||||
:is-dark="settings['theme.is_dark']"
|
||||
:columns="$screens({ sm: 2 }, 1)"
|
||||
@input='click'
|
||||
@update:from-page='updatePage'
|
||||
:locale='$i18n.locale'
|
||||
:attributes='attributes'
|
||||
transition='fade'
|
||||
aria-label='Calendar'
|
||||
is-expanded
|
||||
is-inline)
|
||||
#calendar
|
||||
vc-date-picker(
|
||||
v-model='selectedDate'
|
||||
title-position='left'
|
||||
:is-dark="settings['theme.is_dark']"
|
||||
:columns="$screens({ sm: 2 }, 1)"
|
||||
@input='click'
|
||||
@update:from-page='updatePage'
|
||||
:locale='$i18n.locale'
|
||||
:attributes='attributes'
|
||||
:timezone='settings.instance_timezone'
|
||||
transition='fade'
|
||||
aria-label='Calendar'
|
||||
is-expanded
|
||||
is-inline)
|
||||
|
||||
</template>
|
||||
<script>
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
|
||||
<template lang="pug">
|
||||
v-dialog(v-model='show'
|
||||
:fullscreen='$vuetify.breakpoint.xsOnly'
|
||||
:color='options.color'
|
||||
:title='title'
|
||||
:max-width='options.width'
|
||||
:style="{ zIndex: options.zIndex, position: 'absolute' }"
|
||||
@keydown.esc='cancel')
|
||||
v-card
|
||||
v-card-title {{ title }}
|
||||
v-card-text(v-show='!!message') {{ message }}
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(text color='error' @click='cancel') {{$t('common.cancel')}}
|
||||
v-btn(text color='primary' @click='agree') {{$t('common.ok')}}
|
||||
v-dialog(v-model='show'
|
||||
:fullscreen='$vuetify.breakpoint.xsOnly'
|
||||
:color='options.color'
|
||||
:title='title'
|
||||
:max-width='options.width'
|
||||
:style="{ zIndex: options.zIndex, position: 'absolute' }"
|
||||
@keydown.esc='cancel')
|
||||
v-card
|
||||
v-card-title {{ title }}
|
||||
v-card-text(v-show='!!message') {{ message }}
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(text color='error' @click='cancel') {{$t('common.cancel')}}
|
||||
v-btn(text color='primary' @click='agree') {{$t('common.ok')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
|
@ -45,6 +45,7 @@ v-col(cols=12)
|
|||
:label="$t('event.from')"
|
||||
:value="fromHour"
|
||||
:disabled='!value.from'
|
||||
readonly
|
||||
:prepend-icon="mdiClockTimeFourOutline"
|
||||
:rules="[$validators.required('event.from')]"
|
||||
v-bind="attrs"
|
||||
|
@ -70,6 +71,7 @@ v-col(cols=12)
|
|||
:label="$t('event.due')"
|
||||
:value="dueHour"
|
||||
:disabled='!fromHour'
|
||||
readonly
|
||||
:prepend-icon="mdiClockTimeEightOutline"
|
||||
v-bind="attrs"
|
||||
v-on="on")
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
<template lang="pug">
|
||||
v-container
|
||||
v-card-title.text-h5 Database
|
||||
v-card-text
|
||||
v-form
|
||||
v-btn-toggle(text color='primary' v-model='db.dialect')
|
||||
v-btn(value='sqlite' text) sqlite
|
||||
v-btn(value='postgres' text) postgres
|
||||
v-btn(value='mariadb' text) mariadb
|
||||
template(v-if='db.dialect === "sqlite"')
|
||||
v-text-field(v-model='db.storage' label='Path')
|
||||
template(v-if='db.dialect !== "sqlite"')
|
||||
v-text-field(v-model='db.host' label='Hostname' :rules="[$validators.required('hostname')]")
|
||||
v-text-field(v-model='db.database' label='Database' :rules="[$validators.required('database')]")
|
||||
v-text-field(v-model='db.username' label='Username' :rules="[$validators.required('username')]")
|
||||
v-text-field(type='password' v-model='db.password' label='Password' :rules="[$validators.required('password')]")
|
||||
v-container
|
||||
v-card-title.text-h5 Database
|
||||
v-card-text
|
||||
v-form
|
||||
v-btn-toggle(text color='primary' v-model='db.dialect')
|
||||
v-btn(value='sqlite' text) sqlite
|
||||
v-btn(value='postgres' text) postgres
|
||||
v-btn(value='mariadb' text) mariadb
|
||||
template(v-if='db.dialect === "sqlite"')
|
||||
v-text-field(v-model='db.storage' label='Path')
|
||||
template(v-if='db.dialect !== "sqlite"')
|
||||
v-text-field(v-model='db.host' label='Hostname' :rules="[$validators.required('hostname')]")
|
||||
v-text-field(v-model='db.database' label='Database' :rules="[$validators.required('database')]")
|
||||
v-text-field(v-model='db.username' label='Username' :rules="[$validators.required('username')]")
|
||||
v-text-field(type='password' v-model='db.password' label='Password' :rules="[$validators.required('password')]")
|
||||
|
||||
v-card-actions
|
||||
v-btn(text @click='checkDb' color='primary' :loading='loading' :disabled='loading') {{$t('common.next')}}
|
||||
v-icon(v-text='mdiArrowRight')
|
||||
v-card-actions
|
||||
v-btn(text @click='checkDb' color='primary' :loading='loading' :disabled='loading') {{$t('common.next')}}
|
||||
v-icon(v-text='mdiArrowRight')
|
||||
</template>
|
||||
<script>
|
||||
import { mdiArrowRight } from '@mdi/js'
|
||||
|
|
|
@ -1,67 +1,67 @@
|
|||
<template lang='pug'>
|
||||
.editor(:class='focused')
|
||||
.label {{label}}
|
||||
editor-menu-bar.menubar.is-hidden(:editor='editor'
|
||||
:keep-in-bounds='true' v-slot='{ commands, isActive, getMarkAttrs, focused }')
|
||||
v-btn-toggle(dense)
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.bold() }"
|
||||
@click="commands.bold")
|
||||
v-icon(v-text='mdiFormatBold')
|
||||
.editor(:class='focused')
|
||||
.label {{label}}
|
||||
editor-menu-bar.menubar.is-hidden(:editor='editor'
|
||||
:keep-in-bounds='true' v-slot='{ commands, isActive, getMarkAttrs, focused }')
|
||||
v-btn-toggle(dense)
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.bold() }"
|
||||
@click="commands.bold")
|
||||
v-icon(v-text='mdiFormatBold')
|
||||
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.underline() }"
|
||||
@click="commands.underline")
|
||||
v-icon(v-text='mdiFormatUnderline')
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.underline() }"
|
||||
@click="commands.underline")
|
||||
v-icon(v-text='mdiFormatUnderline')
|
||||
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.strike() }"
|
||||
@click="commands.strike")
|
||||
v-icon(v-text='mdiFormatStrikethroughVariant')
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.strike() }"
|
||||
@click="commands.strike")
|
||||
v-icon(v-text='mdiFormatStrikethroughVariant')
|
||||
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.italic() }"
|
||||
@click="commands.italic")
|
||||
v-icon(v-text='mdiFormatItalic')
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.italic() }"
|
||||
@click="commands.italic")
|
||||
v-icon(v-text='mdiFormatItalic')
|
||||
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.heading({level: 1}) }"
|
||||
@click="commands.heading({level: 1})")
|
||||
v-icon(v-text='mdiFormatHeader1')
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.heading({level: 1}) }"
|
||||
@click="commands.heading({level: 1})")
|
||||
v-icon(v-text='mdiFormatHeader1')
|
||||
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.heading({level: 2}) }"
|
||||
@click="commands.heading({level: 2})")
|
||||
v-icon(v-text='mdiFormatHeader2')
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.heading({level: 2}) }"
|
||||
@click="commands.heading({level: 2})")
|
||||
v-icon(v-text='mdiFormatHeader2')
|
||||
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.heading({level: 3}) }"
|
||||
@click="commands.heading({level: 3})")
|
||||
v-icon(v-text='mdiFormatHeader3')
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.heading({level: 3}) }"
|
||||
@click="commands.heading({level: 3})")
|
||||
v-icon(v-text='mdiFormatHeader3')
|
||||
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.code() }"
|
||||
@click="commands.code")
|
||||
v-icon(v-text='mdiCodeTags')
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.code() }"
|
||||
@click="commands.code")
|
||||
v-icon(v-text='mdiCodeTags')
|
||||
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.blockquote() }"
|
||||
@click="commands.blockquote")
|
||||
v-icon(v-text='mdiFormatQuoteOpen')
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.blockquote() }"
|
||||
@click="commands.blockquote")
|
||||
v-icon(v-text='mdiFormatQuoteOpen')
|
||||
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.bullet_list() }"
|
||||
@click="commands.bullet_list")
|
||||
v-icon(v-text='mdiFormatListBulleted')
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.bullet_list() }"
|
||||
@click="commands.bullet_list")
|
||||
v-icon(v-text='mdiFormatListBulleted')
|
||||
|
||||
v-btn(icon text tabindex='-1' :class='{ primary: isActive.link() }'
|
||||
@click='commands.link({href: getMarkAttrs("link") && getMarkAttrs("link").href ? "" : "https://"}); $refs.link.focus();')
|
||||
v-icon(v-text='mdiLink')
|
||||
v-text-field.pt-0.ml-1(v-show='isActive.link()' ref='link' @focus='focus' @blur='blur' hide-details
|
||||
:value='isActive.link() && getMarkAttrs("link") && getMarkAttrs("link").href || ""'
|
||||
@keypress.enter='commands.link({ href: $event.target.value}); editor.focus()')
|
||||
v-btn(icon text tabindex='-1' :class='{ primary: isActive.link() }'
|
||||
@click='commands.link({href: getMarkAttrs("link") && getMarkAttrs("link").href ? "" : "https://"}); $refs.link.focus();')
|
||||
v-icon(v-text='mdiLink')
|
||||
v-text-field.pt-0.ml-1(v-show='isActive.link()' ref='link' @focus='focus' @blur='e => { blur(); commands.link({ href: e.target.value}) }' hide-details
|
||||
:value='isActive.link() && getMarkAttrs("link") && getMarkAttrs("link").href || ""'
|
||||
@keypress.enter='commands.link({ href: $event.target.value}); editor.focus()')
|
||||
|
||||
editor-content.content(:editor='editor' spellcheck='false' :style="{ 'max-height': maxHeight }" :aria-label='label' :label='label')
|
||||
editor-content.content(:editor='editor' spellcheck='false' :style="{ 'max-height': maxHeight }" :aria-label='label' :label='label')
|
||||
</template>
|
||||
<script>
|
||||
import debounce from 'lodash/debounce'
|
||||
|
|
|
@ -1,51 +1,51 @@
|
|||
<template lang="pug">
|
||||
v-card.h-event.event.d-flex(itemscope itemtype="https://schema.org/Event")
|
||||
nuxt-link(:to='`/event/${event.slug || event.id}`' itemprop="url")
|
||||
MyPicture(:event='event' thumb :lazy='lazy')
|
||||
v-icon.float-right.mr-1(v-if='event.parentId' color='success' v-text='mdiRepeat')
|
||||
.title.p-name(itemprop="name") {{event.title}}
|
||||
v-card.h-event.event.d-flex(itemscope itemtype="https://schema.org/Event")
|
||||
nuxt-link(:to='`/event/${event.slug || event.id}`' itemprop="url")
|
||||
MyPicture(:event='event' thumb :lazy='lazy')
|
||||
v-icon.float-right.mr-1(v-if='event.parentId' color='success' v-text='mdiRepeat')
|
||||
.title.p-name(itemprop="name") {{event.title}}
|
||||
|
||||
v-card-text.body.pt-0.pb-0
|
||||
time.dt-start.subtitle-1(:datetime='event.start_datetime|unixFormat("YYYY-MM-DD HH:mm")' itemprop="startDate" :content="event.start_datetime|unixFormat('YYYY-MM-DDTHH:mm')") <v-icon v-text='mdiCalendar'></v-icon> {{ event|when }}
|
||||
.d-none.dt-end(itemprop="endDate" :content="event.end_datetime|unixFormat('YYYY-MM-DDTHH:mm')") {{event.end_datetime|unixFormat('YYYY-MM-DD HH:mm')}}
|
||||
nuxt-link.place.d-block.p-location.pl-0(text color='primary' :to='`/place/${event.place.name}`' itemprop="location" :content="event.place.name") <v-icon v-text='mdiMapMarker'></v-icon> {{event.place.name}}
|
||||
.d-none(itemprop='location.address') {{event.place.address}}
|
||||
v-card-text.body.pt-0.pb-0
|
||||
time.dt-start.subtitle-1(:datetime='event.start_datetime|unixFormat("YYYY-MM-DD HH:mm")' itemprop="startDate" :content="event.start_datetime|unixFormat('YYYY-MM-DDTHH:mm')") <v-icon v-text='mdiCalendar'></v-icon> {{ event|when }}
|
||||
.d-none.dt-end(itemprop="endDate" :content="event.end_datetime|unixFormat('YYYY-MM-DDTHH:mm')") {{event.end_datetime|unixFormat('YYYY-MM-DD HH:mm')}}
|
||||
nuxt-link.place.d-block.p-location.pl-0(text color='primary' :to='`/place/${event.place.name}`' itemprop="location" :content="event.place.name") <v-icon v-text='mdiMapMarker'></v-icon> {{event.place.name}}
|
||||
.d-none(itemprop='location.address') {{event.place.address}}
|
||||
|
||||
v-card-actions.pt-0.actions.justify-space-between
|
||||
.tags
|
||||
v-chip.ml-1.mt-1(v-for='tag in event.tags.slice(0,6)' small :to='`/tag/${tag}`'
|
||||
:key='tag' outlined color='primary') {{tag}}
|
||||
v-card-actions.pt-0.actions.justify-space-between
|
||||
.tags
|
||||
v-chip.ml-1.mt-1(v-for='tag in event.tags.slice(0,6)' small :to='`/tag/${tag}`'
|
||||
:key='tag' outlined color='primary') {{tag}}
|
||||
|
||||
client-only
|
||||
v-menu(offset-y eager)
|
||||
template(v-slot:activator="{on}")
|
||||
v-btn.align-self-end(icon v-on='on' color='primary' title='more' aria-label='more')
|
||||
v-icon(v-text='mdiDotsVertical')
|
||||
v-list(dense)
|
||||
v-list-item-group
|
||||
v-list-item(@click='clipboard(`${settings.baseurl}/event/${event.slug || event.id}`)')
|
||||
v-list-item-icon
|
||||
v-icon(v-text='mdiContentCopy')
|
||||
v-list-item-content
|
||||
v-list-item-title {{$t('common.copy_link')}}
|
||||
v-list-item(:href='`/api/event/${event.slug || event.id}.ics`')
|
||||
v-list-item-icon
|
||||
v-icon(v-text='mdiCalendarExport')
|
||||
v-list-item-content
|
||||
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(v-text='mdiPencil')
|
||||
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' v-text='mdiDeleteForever')
|
||||
v-list-item-content
|
||||
v-list-item-title {{$t('common.remove')}}
|
||||
template(#placeholder)
|
||||
v-btn.align-self-end(icon color='primary' aria-label='more')
|
||||
client-only
|
||||
v-menu(offset-y eager)
|
||||
template(v-slot:activator="{on}")
|
||||
v-btn.align-self-end(icon v-on='on' color='primary' title='more' aria-label='more')
|
||||
v-icon(v-text='mdiDotsVertical')
|
||||
v-list(dense)
|
||||
v-list-item-group
|
||||
v-list-item(@click='clipboard(`${settings.baseurl}/event/${event.slug || event.id}`)')
|
||||
v-list-item-icon
|
||||
v-icon(v-text='mdiContentCopy')
|
||||
v-list-item-content
|
||||
v-list-item-title {{$t('common.copy_link')}}
|
||||
v-list-item(:href='`/api/event/${event.slug || event.id}.ics`')
|
||||
v-list-item-icon
|
||||
v-icon(v-text='mdiCalendarExport')
|
||||
v-list-item-content
|
||||
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(v-text='mdiPencil')
|
||||
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' v-text='mdiDeleteForever')
|
||||
v-list-item-content
|
||||
v-list-item-title {{$t('common.remove')}}
|
||||
template(#placeholder)
|
||||
v-btn.align-self-end(icon color='primary' aria-label='more')
|
||||
v-icon(v-text='mdiDotsVertical')
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
<template lang='pug'>
|
||||
v-card
|
||||
v-card-title(v-text="$t('common.follow_me_title')")
|
||||
v-card-text
|
||||
p(v-html="$t('event.follow_me_description', { title: settings.title, account: `@${settings.instance_name}@${settings.hostname}`})")
|
||||
v-text-field(
|
||||
:rules="[$validators.required('common.url')]"
|
||||
:loading='loading'
|
||||
:label="$t('common.url')"
|
||||
v-model='instance_hostname')
|
||||
v-btn(v-if='!isDialog' slot='prepend' text :disabled='(!couldGo || !proceed)' :href='link' target='_blank'
|
||||
:loading='loading' color="primary") {{$t("common.follow")}}
|
||||
|
||||
p(slot='append') <img class='instance_thumb' :src="instance.thumbnail"/> {{instance.title}}
|
||||
|
||||
v-card-actions(v-if='isDialog')
|
||||
v-spacer
|
||||
v-btn(v-if='isDialog' color='warning' @click="$emit('close')") {{$t("common.cancel")}}
|
||||
v-btn(:disabled='(!couldGo || !proceed)' :href='link' target='_blank'
|
||||
v-card
|
||||
v-card-title(v-text="$t('common.follow_me_title')")
|
||||
v-card-text
|
||||
p(v-html="$t('event.follow_me_description', { title: settings.title, account: `@${settings.instance_name}@${settings.hostname}`})")
|
||||
v-text-field(
|
||||
:rules="[$validators.required('common.url')]"
|
||||
:loading='loading'
|
||||
:label="$t('common.url')"
|
||||
v-model='instance_hostname')
|
||||
v-btn(v-if='!isDialog' slot='prepend' text :disabled='(!couldGo || !proceed)' :href='link' target='_blank'
|
||||
:loading='loading' color="primary") {{$t("common.follow")}}
|
||||
|
||||
p(slot='append') <img class='instance_thumb' :src="instance.thumbnail"/> {{instance.title}}
|
||||
|
||||
v-card-actions(v-if='isDialog')
|
||||
v-spacer
|
||||
v-btn(v-if='isDialog' color='warning' @click="$emit('close')") {{$t("common.cancel")}}
|
||||
v-btn(:disabled='(!couldGo || !proceed)' :href='link' target='_blank'
|
||||
:loading='loading' color="primary") {{$t("common.follow")}}
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
|
|
@ -1,31 +1,31 @@
|
|||
<template lang="pug">
|
||||
v-footer(aria-label='Footer')
|
||||
v-footer(aria-label='Footer')
|
||||
|
||||
v-dialog(v-model='showFollowMe' destroy-on-close max-width='700px' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||
FollowMe(@close='showFollowMe=false' is-dialog)
|
||||
v-dialog(v-model='showFollowMe' destroy-on-close max-width='700px' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||
FollowMe(@close='showFollowMe=false' is-dialog)
|
||||
|
||||
v-btn(color='primary' text href='https://gancio.org' target='_blank' rel="noopener") 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-btn(color='primary' text href='https://gancio.org' target='_blank' rel="noopener") 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'
|
||||
offset-y bottom open-on-hover transition="slide-y-transition")
|
||||
template(v-slot:activator="{ on, attrs }")
|
||||
v-btn.ml-1(v-bind='attrs' v-on='on' color='primary' text) {{$t('common.places')}}
|
||||
v-list(subheaders two-lines)
|
||||
v-list-item(v-for='instance in settings.trusted_instances'
|
||||
:key='instance.name'
|
||||
target='_blank'
|
||||
:href='instance.url'
|
||||
two-line)
|
||||
v-list-item-avatar
|
||||
v-img(:src='`${instance.url}/logo.png`')
|
||||
v-list-item-content
|
||||
v-list-item-title {{instance.name}}
|
||||
v-list-item-subtitle {{instance.label}}
|
||||
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")
|
||||
template(v-slot:activator="{ on, attrs }")
|
||||
v-btn.ml-1(v-bind='attrs' v-on='on' color='primary' text) {{$t('common.places')}}
|
||||
v-list(subheaders two-lines)
|
||||
v-list-item(v-for='instance in settings.trusted_instances'
|
||||
:key='instance.name'
|
||||
target='_blank'
|
||||
:href='instance.url'
|
||||
two-line)
|
||||
v-list-item-avatar
|
||||
v-img(:src='`${instance.url}/logo.png`')
|
||||
v-list-item-content
|
||||
v-list-item-title {{instance.name}}
|
||||
v-list-item-subtitle {{instance.label}}
|
||||
|
||||
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.ml-1(v-if='settings.enable_federation' color='primary' text rel='me' @click.prevent='showFollowMe=true') {{$t('event.interact_with_me')}}
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
|
|
@ -1,36 +1,35 @@
|
|||
<template lang="pug">
|
||||
v-card
|
||||
v-card-title {{$t('common.import')}}
|
||||
v-card-text
|
||||
p(v-html="$t('event.import_description')")
|
||||
v-form(v-model='valid' ref='form' lazy-validation @submit.prevent='importGeneric')
|
||||
v-row
|
||||
.col-xl-5.col-lg-5.col-md-7.col-sm-12.col-xs-12
|
||||
v-text-field(v-model='URL'
|
||||
:label="$t('common.url')"
|
||||
:hint="$t('event.import_URL')"
|
||||
persistent-hint
|
||||
:loading='loading' :error='error'
|
||||
:error-messages='errorMessage')
|
||||
.col
|
||||
v-file-input(
|
||||
v-model='file'
|
||||
accept=".ics"
|
||||
:label="$t('event.ics')"
|
||||
:hint="$t('event.import_ICS')"
|
||||
persistent-hint)
|
||||
v-card
|
||||
v-card-title {{$t('common.import')}}
|
||||
v-card-text
|
||||
p(v-html="$t('event.import_description')")
|
||||
v-form(v-model='valid' ref='form' lazy-validation @submit.prevent='importGeneric')
|
||||
v-row
|
||||
.col-xl-5.col-lg-5.col-md-7.col-sm-12.col-xs-12
|
||||
v-text-field(v-model='URL'
|
||||
:label="$t('common.url')"
|
||||
:hint="$t('event.import_URL')"
|
||||
persistent-hint
|
||||
:loading='loading' :error='error'
|
||||
:error-messages='errorMessage')
|
||||
.col
|
||||
v-file-input(
|
||||
v-model='file'
|
||||
accept=".ics"
|
||||
:label="$t('event.ics')"
|
||||
:hint="$t('event.import_ICS')"
|
||||
persistent-hint)
|
||||
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(text @click='$emit("close")' color='warning') {{$t('common.cancel')}}
|
||||
v-btn(text @click='importGeneric' :loading='loading' :disabled='loading'
|
||||
color='primary') {{$t('common.import')}}
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(text @click='$emit("close")' color='warning') {{$t('common.cancel')}}
|
||||
v-btn(text @click='importGeneric' :loading='loading' :disabled='loading'
|
||||
color='primary') {{$t('common.import')}}
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import ical from 'ical.js'
|
||||
import get from 'lodash/get'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'ImportDialog',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template lang='pug'>
|
||||
.loading-page(:class='{ loading }')
|
||||
v-progress-circular(:size="100" :width="10" style='color: orangered;' indeterminate)
|
||||
.loading-page(:class='{ loading }')
|
||||
v-progress-circular(:size="100" :width="10" style='color: orangered;' indeterminate)
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
|
@ -1,51 +1,51 @@
|
|||
<template lang="pug">
|
||||
span
|
||||
v-dialog(v-model='openMediaDetails' :fullscreen="$vuetify.breakpoint.xsOnly" width='1000px')
|
||||
v-card
|
||||
v-card-title {{$t('common.media')}}
|
||||
v-card-text
|
||||
v-row.mt-1
|
||||
v-col#focalPointSelector(
|
||||
@mousedown='handleStart' @touchstart='handleStart'
|
||||
@mousemove='handleMove' @touchmove='handleMove'
|
||||
@mouseup='handleStop' @touchend='handleStop'
|
||||
)
|
||||
div.focalPoint(:style="{ top, left }")
|
||||
img(v-if='mediaPreview' :src='mediaPreview')
|
||||
span
|
||||
v-dialog(v-model='openMediaDetails' :fullscreen="$vuetify.breakpoint.xsOnly" width='1000px')
|
||||
v-card
|
||||
v-card-title {{$t('common.media')}}
|
||||
v-card-text
|
||||
v-row.mt-1
|
||||
v-col#focalPointSelector(
|
||||
@mousedown='handleStart' @touchstart='handleStart'
|
||||
@mousemove='handleMove' @touchmove='handleMove'
|
||||
@mouseup='handleStop' @touchend='handleStop'
|
||||
)
|
||||
div.focalPoint(:style="{ top, left }")
|
||||
img(v-if='mediaPreview' :src='mediaPreview')
|
||||
|
||||
v-col.col-12.col-sm-4
|
||||
p {{$t('event.choose_focal_point')}}
|
||||
img.mediaPreview.d-none.d-sm-block(v-if='mediaPreview'
|
||||
:src='mediaPreview' :style="{ 'object-position': position }")
|
||||
v-col.col-12.col-sm-4
|
||||
p {{$t('event.choose_focal_point')}}
|
||||
img.mediaPreview.d-none.d-sm-block(v-if='mediaPreview'
|
||||
:src='mediaPreview' :style="{ 'object-position': position }")
|
||||
|
||||
v-textarea.mt-4(type='text'
|
||||
label='Alternative text'
|
||||
persistent-hint
|
||||
@input='v => name=v'
|
||||
:value='value.name' filled
|
||||
:hint='$t("event.alt_text_description")')
|
||||
br
|
||||
v-card-actions.justify-space-between
|
||||
v-btn(text @click='openMediaDetails=false' color='warning') Cancel
|
||||
v-btn(text color='primary' @click='save') Save
|
||||
v-textarea.mt-4(type='text'
|
||||
label='Alternative text'
|
||||
persistent-hint
|
||||
@input='v => name=v'
|
||||
:value='value.name' filled
|
||||
:hint='$t("event.alt_text_description")')
|
||||
br
|
||||
v-card-actions.justify-space-between
|
||||
v-btn(text @click='openMediaDetails=false' color='warning') Cancel
|
||||
v-btn(text color='primary' @click='save') Save
|
||||
|
||||
h3.mb-3.font-weight-regular(v-if='mediaPreview') {{$t('common.media')}}
|
||||
v-card-actions(v-if='mediaPreview')
|
||||
v-spacer
|
||||
v-btn(text color='primary' @click='openMediaDetails = true') {{$t('common.edit')}}
|
||||
v-btn(text color='error' @click='remove') {{$t('common.remove')}}
|
||||
div(v-if='mediaPreview')
|
||||
img.mediaPreview.col-12.ml-3(:src='mediaPreview' :style="{ 'object-position': savedPosition }")
|
||||
span.float-right {{event.media[0].name}}
|
||||
v-file-input(
|
||||
v-else
|
||||
:label="$t('common.media')"
|
||||
:hint="$t('event.media_description')"
|
||||
:prepend-icon="mdiCamera"
|
||||
:value='value.image'
|
||||
@change="selectMedia"
|
||||
persistent-hint
|
||||
accept='image/*')
|
||||
h3.mb-3.font-weight-regular(v-if='mediaPreview') {{$t('common.media')}}
|
||||
v-card-actions(v-if='mediaPreview')
|
||||
v-spacer
|
||||
v-btn(text color='primary' @click='openMediaDetails = true') {{$t('common.edit')}}
|
||||
v-btn(text color='error' @click='remove') {{$t('common.remove')}}
|
||||
div(v-if='mediaPreview')
|
||||
img.mediaPreview.col-12.ml-3(:src='mediaPreview' :style="{ 'object-position': savedPosition }")
|
||||
span.float-right {{event.media[0].name}}
|
||||
v-file-input(
|
||||
v-else
|
||||
:label="$t('common.media')"
|
||||
:hint="$t('event.media_description')"
|
||||
:prepend-icon="mdiCamera"
|
||||
:value='value.image'
|
||||
@change="selectMedia"
|
||||
persistent-hint
|
||||
accept='image/*')
|
||||
</template>
|
||||
<script>
|
||||
import { mdiCamera } from '@mdi/js'
|
||||
|
|
|
@ -11,17 +11,17 @@ v-app-bar(app aria-label='Menu' height=64)
|
|||
v-list-item-subtitle.d-none.d-sm-flex {{settings.description}}
|
||||
|
||||
v-spacer
|
||||
v-btn(v-if='$auth.loggedIn || settings.allow_anon_event' icon nuxt to='/add' :aria-label='$t("common.add_event")' :title='$t("common.add_event")')
|
||||
v-btn(v-if='loggedIn || settings.allow_anon_event' icon nuxt to='/add' :aria-label='$t("common.add_event")' :title='$t("common.add_event")')
|
||||
v-icon(large color='primary' v-text='mdiPlus')
|
||||
|
||||
v-btn(icon nuxt to='/export' :title='$t("common.share")' :aria-label='$t("common.share")')
|
||||
v-icon(v-text='mdiShareVariant')
|
||||
|
||||
v-btn(v-if='!$auth.loggedIn' icon nuxt to='/login' :title='$t("common.login")' :aria-label='$t("common.login")')
|
||||
v-btn(v-if='!loggedIn' icon nuxt to='/login' :title='$t("common.login")' :aria-label='$t("common.login")')
|
||||
v-icon(v-text='mdiLogin')
|
||||
|
||||
client-only
|
||||
v-menu(v-if='$auth.loggedIn' offset-y eager)
|
||||
v-menu(v-if='loggedIn' offset-y eager)
|
||||
template(v-slot:activator="{ on, attrs }")
|
||||
v-btn(icon v-bind='attrs' v-on='on' title='Menu' aria-label='Menu')
|
||||
v-icon(v-text='mdiDotsVertical')
|
||||
|
@ -44,7 +44,7 @@ v-app-bar(app aria-label='Menu' height=64)
|
|||
v-list-item-content
|
||||
v-list-item-title {{$t('common.logout')}}
|
||||
template(#placeholder)
|
||||
v-btn(v-if='$auth.loggedIn' icon aria-label='Menu' title='Menu')
|
||||
v-btn(v-if='loggedIn' icon aria-label='Menu' title='Menu')
|
||||
v-icon(v-text='mdiDotsVertical')
|
||||
|
||||
|
||||
|
@ -64,7 +64,12 @@ export default {
|
|||
return { mdiPlus, mdiShareVariant, mdiLogout, mdiLogin, mdiDotsVertical, mdiAccount, mdiCog, mdiRss }
|
||||
},
|
||||
mixins: [clipboard],
|
||||
computed: mapState(['settings']),
|
||||
computed: {
|
||||
loggedIn () {
|
||||
return this.$auth.loggedIn
|
||||
},
|
||||
...mapState(['settings']),
|
||||
},
|
||||
methods: {
|
||||
logout () {
|
||||
this.$root.$message('common.logout_ok')
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
<template lang='pug'>
|
||||
v-snackbar(
|
||||
v-model="active"
|
||||
:color="color"
|
||||
:bottom="bottom"
|
||||
:top="top"
|
||||
:left="left"
|
||||
:right="right"
|
||||
:timeout="timeout")
|
||||
v-icon.mr-3(color="white" v-text='icon')
|
||||
span {{ message }}
|
||||
template(v-slot:action="{ }")
|
||||
v-icon(size="16" @click="active = false" v-text='mdiCloseCircle')
|
||||
v-snackbar(
|
||||
v-model="active"
|
||||
:color="color"
|
||||
:bottom="bottom"
|
||||
:top="top"
|
||||
:left="left"
|
||||
:right="right"
|
||||
:timeout="timeout")
|
||||
v-icon.mr-3(color="white" v-text='icon')
|
||||
span {{ message }}
|
||||
template(v-slot:action="{ }")
|
||||
v-icon(size="16" @click="active = false" v-text='mdiCloseCircle')
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -19,7 +19,7 @@ import { mdiAlert, mdiCloseCircle, mdiInformation } from '@mdi/js'
|
|||
export default {
|
||||
data () {
|
||||
return {
|
||||
mdiAlert, mdiAlert, mdiCloseCircle, mdiInformation,
|
||||
mdiAlert, mdiCloseCircle, mdiInformation,
|
||||
icon: mdiInformation,
|
||||
color: 'secondary',
|
||||
bottom: true,
|
||||
|
@ -35,7 +35,7 @@ export default {
|
|||
this.$root.$message = (message, opts = {}) => {
|
||||
this.active = true
|
||||
this.message = this.$t(message, opts)
|
||||
this.color = opts.color || 'secondary'
|
||||
this.color = opts.color || 'primary'
|
||||
this.icon = opts.icon || (this.color === 'success' ? mdiInformation : mdiAlert)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,8 @@ v-row
|
|||
@change='selectPlace')
|
||||
template(v-slot:item="{ item, attrs, on }")
|
||||
v-list-item(v-bind='attrs' v-on='on')
|
||||
v-list-item-content(two-line v-if='item.create && search')
|
||||
v-list-item-title <v-icon color='primary' v-text='mdiPlus' :aria-label='$t("common.add")'></v-icon> {{item.name}}
|
||||
v-list-item-content(two-line v-if='item.create')
|
||||
v-list-item-title <v-icon color='primary' v-text='mdiPlus' :aria-label='$t("common.add")'></v-icon> {{$t('common.add')}} <strong>{{item.name}}</strong>
|
||||
v-list-item-content(two-line v-else)
|
||||
v-list-item-title(v-text='item.name')
|
||||
v-list-item-subtitle(v-text='item.address')
|
||||
|
@ -76,7 +76,7 @@ export default {
|
|||
this.places = await this.$axios.$get(`place?search=${search}`)
|
||||
if (!search && this.places.length) { return this.places }
|
||||
const matches = this.places.find(p => search === p.name.toLocaleLowerCase())
|
||||
if (!matches) {
|
||||
if (!matches && search) {
|
||||
this.places.unshift({ create: true, name: ev.target.value.trim() })
|
||||
}
|
||||
}, 100),
|
||||
|
|
|
@ -1,35 +1,34 @@
|
|||
<template lang='pug'>
|
||||
v-container
|
||||
v-card-title {{$t('common.announcements')}}
|
||||
v-card-subtitle(v-html="$t('admin.announcement_description')")
|
||||
v-dialog(v-model='dialog' width='800px' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||
v-card
|
||||
v-card-title {{$t('admin.new_announcement')}}
|
||||
v-card-text.px-0
|
||||
v-form(v-model='valid' ref='announcement' @submit.prevent='save' lazy-validation)
|
||||
v-text-field.col-12(v-model='announcement.title'
|
||||
: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')")
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(@click='dialog=false' color='error') {{$t('common.cancel')}}
|
||||
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 v-text='mdiPlus'></v-icon> {{$t('common.add')}}
|
||||
v-card-text
|
||||
v-data-table(
|
||||
v-if='announcements.length'
|
||||
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
|
||||
:headers='headers'
|
||||
:items='announcements')
|
||||
template(v-slot:item.actions='{ item }')
|
||||
v-btn(text small @click.stop='toggle(item)'
|
||||
:color='item.visible?"warning":"success"') {{item.visible?$t('common.disable'):$t('common.enable')}}
|
||||
v-btn(text small @click='edit(item)' color='primary') {{$t('common.edit')}}
|
||||
v-btn(text small @click='remove(item)' color='error') {{$t('common.delete')}}
|
||||
v-container
|
||||
v-card-title {{$t('common.announcements')}}
|
||||
v-card-subtitle(v-html="$t('admin.announcement_description')")
|
||||
v-dialog(v-model='dialog' width='800px' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||
v-card
|
||||
v-card-title {{$t('admin.new_announcement')}}
|
||||
v-card-text.px-0
|
||||
v-form(v-model='valid' ref='announcement' @submit.prevent='save' lazy-validation)
|
||||
v-text-field.col-12(v-model='announcement.title'
|
||||
: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')")
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(@click='dialog=false' color='error') {{$t('common.cancel')}}
|
||||
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 v-text='mdiPlus'></v-icon> {{$t('common.add')}}
|
||||
v-card-text
|
||||
v-data-table(
|
||||
v-if='announcements.length'
|
||||
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
|
||||
:headers='headers'
|
||||
:items='announcements')
|
||||
template(v-slot:item.actions='{ item }')
|
||||
v-btn(text small @click.stop='toggle(item)'
|
||||
:color='item.visible?"warning":"success"') {{item.visible?$t('common.disable'):$t('common.enable')}}
|
||||
v-btn(text small @click='edit(item)' color='primary') {{$t('common.edit')}}
|
||||
v-btn(text small @click='remove(item)' color='error') {{$t('common.delete')}}
|
||||
</template>
|
||||
<script>
|
||||
import { mapActions } from 'vuex'
|
||||
|
|
|
@ -14,7 +14,7 @@ v-container
|
|||
v-card(color='secondary')
|
||||
v-card-title {{$t('admin.edit_collection')}}
|
||||
v-card-text
|
||||
v-form(v-model='valid' ref='form')
|
||||
v-form(v-model='valid' ref='form' @submit.prevent.native='saveCollection')
|
||||
v-text-field(
|
||||
v-if='!collection.id'
|
||||
:rules="[$validators.required('common.name')]"
|
||||
|
@ -71,7 +71,6 @@ v-container
|
|||
v-col(cols=2)
|
||||
v-btn(color='primary' text @click='addFilter' :disabled='!collection.id || !filterPlaces.length && !filterTags.length') add <v-icon v-text='mdiPlus'></v-icon>
|
||||
|
||||
|
||||
v-data-table(
|
||||
:headers='filterHeaders'
|
||||
:items='filters'
|
||||
|
@ -153,7 +152,9 @@ export default {
|
|||
}, 100),
|
||||
collectionFilters (collection) {
|
||||
return collection.filters.map(f => {
|
||||
return '(' + f.tags?.join(', ') + f.places?.map(p => p.name).join(', ') + ')'
|
||||
const tags = f.tags?.join(', ')
|
||||
const places = f.places?.map(p => p.name).join(', ')
|
||||
return '(' + (tags && places ? tags + ' - ' + places : tags + places) + ')'
|
||||
}).join(' - ')
|
||||
},
|
||||
async addFilter () {
|
||||
|
|
|
@ -1,21 +1,20 @@
|
|||
<template lang='pug'>
|
||||
v-container
|
||||
v-card-title {{$t('common.events')}}
|
||||
v-card-subtitle {{$t('admin.event_confirm_description')}}
|
||||
v-card-text
|
||||
v-data-table(
|
||||
:hide-default-footer='unconfirmedEvents.length<10'
|
||||
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
|
||||
:items='unconfirmedEvents'
|
||||
:headers='headers')
|
||||
template(v-slot:item.when='{ item }') {{item|when}}
|
||||
template(v-slot:item.actions='{ item }')
|
||||
v-btn(text small @click='confirm(item)' color='success') {{$t('common.confirm')}}
|
||||
v-btn(text small :to='`/event/${item.slug || item.id}`' color='success') {{$t('common.preview')}}
|
||||
v-btn(text small :to='`/add/${item.id}`' color='warning') {{$t('common.edit')}}
|
||||
v-btn(text small @click='remove(item)'
|
||||
color='error') {{$t('common.delete')}}
|
||||
|
||||
v-container
|
||||
v-card-title {{$t('common.events')}}
|
||||
v-card-subtitle {{$t('admin.event_confirm_description')}}
|
||||
v-card-text
|
||||
v-data-table(
|
||||
:hide-default-footer='unconfirmedEvents.length<10'
|
||||
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
|
||||
:items='unconfirmedEvents'
|
||||
:headers='headers')
|
||||
template(v-slot:item.when='{ item }') {{item|when}}
|
||||
template(v-slot:item.actions='{ item }')
|
||||
v-btn(text small @click='confirm(item)' color='success') {{$t('common.confirm')}}
|
||||
v-btn(text small :to='`/event/${item.slug || item.id}`' color='success') {{$t('common.preview')}}
|
||||
v-btn(text small :to='`/add/${item.id}`' color='warning') {{$t('common.edit')}}
|
||||
v-btn(text small @click='remove(item)'
|
||||
color='error') {{$t('common.delete')}}
|
||||
</template>
|
||||
<script>
|
||||
import { mdiChevronLeft, mdiChevronRight } from '@mdi/js'
|
||||
|
|
|
@ -1,71 +1,71 @@
|
|||
<template lang="pug">
|
||||
v-container
|
||||
v-card-title {{$t('common.federation')}}
|
||||
v-card-text
|
||||
v-switch(v-model='enable_federation'
|
||||
:label="$t('admin.enable_federation')"
|
||||
v-container
|
||||
v-card-title {{$t('common.federation')}}
|
||||
v-card-text
|
||||
v-switch(v-model='enable_federation'
|
||||
:label="$t('admin.enable_federation')"
|
||||
persistent-hint
|
||||
inset
|
||||
:hint="$t('admin.enable_federation_help')")
|
||||
|
||||
template(v-if='enable_federation')
|
||||
|
||||
v-switch.mt-4(v-model='enable_resources'
|
||||
:label="$t('admin.enable_resources')"
|
||||
:hint="$t('admin.enable_resources_help')"
|
||||
persistent-hint inset)
|
||||
|
||||
v-switch.mt-4(v-model='hide_boosts'
|
||||
:label="$t('admin.hide_boost_bookmark')"
|
||||
:hint="$t('admin.hide_boost_bookmark_help')"
|
||||
persistent-hint inset)
|
||||
|
||||
//- div.mt-4 {{$t('admin.instance_name')}}
|
||||
v-text-field.mt-5(v-model='instance_name'
|
||||
:label="$t('admin.instance_name')"
|
||||
:hint="`${$t('admin.instance_name_help')} ${instance_ap_url}`"
|
||||
placeholder='Instance name' persistent-hint
|
||||
@blur='save("instance_name", instance_name)')
|
||||
|
||||
v-switch.mt-4(v-model='enable_trusted_instances'
|
||||
:label="$t('admin.enable_trusted_instances')"
|
||||
persistent-hint inset
|
||||
:hint="$t('admin.trusted_instances_help')")
|
||||
|
||||
template(v-if='enable_trusted_instances')
|
||||
v-text-field.mt-4(v-model='instance_place'
|
||||
:label="$t('admin.instance_place')"
|
||||
persistent-hint
|
||||
inset
|
||||
:hint="$t('admin.enable_federation_help')")
|
||||
:hint="$t('admin.instance_place_help')"
|
||||
@blur='save("instance_place", instance_place)'
|
||||
)
|
||||
|
||||
template(v-if='enable_federation')
|
||||
v-dialog(v-model='dialogAddInstance' width='500px' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||
v-card
|
||||
v-card-title {{$t('admin.add_trusted_instance')}}
|
||||
v-card-text
|
||||
v-form(v-model='valid' @submit.prevent='createTrustedInstance' ref='form' lazy-validation)
|
||||
v-text-field.mt-4(v-model='instance_url'
|
||||
persistent-hint
|
||||
:rules="[$validators.required('common.url')]"
|
||||
:loading='loading'
|
||||
:hint="$t('admin.add_trusted_instance')"
|
||||
:label="$t('common.url')")
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(color='error' @click='dialogAddInstance=false') {{$t('common.cancel')}}
|
||||
v-btn(color='primary' :disabled='!valid || loading' :loading='loading' @click='createTrustedInstance') {{$t('common.ok')}}
|
||||
|
||||
v-switch.mt-4(v-model='enable_resources'
|
||||
:label="$t('admin.enable_resources')"
|
||||
:hint="$t('admin.enable_resources_help')"
|
||||
persistent-hint inset)
|
||||
|
||||
v-switch.mt-4(v-model='hide_boosts'
|
||||
:label="$t('admin.hide_boost_bookmark')"
|
||||
:hint="$t('admin.hide_boost_bookmark_help')"
|
||||
persistent-hint inset)
|
||||
|
||||
//- div.mt-4 {{$t('admin.instance_name')}}
|
||||
v-text-field.mt-5(v-model='instance_name'
|
||||
:label="$t('admin.instance_name')"
|
||||
:hint="`${$t('admin.instance_name_help')} ${instance_ap_url}`"
|
||||
placeholder='Instance name' persistent-hint
|
||||
@blur='save("instance_name", instance_name)')
|
||||
|
||||
v-switch.mt-4(v-model='enable_trusted_instances'
|
||||
:label="$t('admin.enable_trusted_instances')"
|
||||
persistent-hint inset
|
||||
:hint="$t('admin.trusted_instances_help')")
|
||||
|
||||
template(v-if='enable_trusted_instances')
|
||||
v-text-field.mt-4(v-model='instance_place'
|
||||
:label="$t('admin.instance_place')"
|
||||
persistent-hint
|
||||
:hint="$t('admin.instance_place_help')"
|
||||
@blur='save("instance_place", instance_place)'
|
||||
)
|
||||
|
||||
v-dialog(v-model='dialogAddInstance' width='500px' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||
v-card
|
||||
v-card-title {{$t('admin.add_trusted_instance')}}
|
||||
v-card-text
|
||||
v-form(v-model='valid' @submit.prevent='createTrustedInstance' ref='form' lazy-validation)
|
||||
v-text-field.mt-4(v-model='instance_url'
|
||||
persistent-hint
|
||||
:rules="[$validators.required('common.url')]"
|
||||
:loading='loading'
|
||||
:hint="$t('admin.add_trusted_instance')"
|
||||
:label="$t('common.url')")
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(color='error' @click='dialogAddInstance=false') {{$t('common.cancel')}}
|
||||
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 v-text='mdiPlus'></v-icon> {{$t('admin.add_instance')}}
|
||||
v-data-table(
|
||||
v-if='settings.trusted_instances.length'
|
||||
:hide-default-footer='settings.trusted_instances.length<10'
|
||||
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
|
||||
:headers='headers'
|
||||
:items='settings.trusted_instances')
|
||||
template(v-slot:item.actions="{item}")
|
||||
v-btn(icon @click='deleteInstance(item)' color='error')
|
||||
v-icon(v-text='mdiDeleteForever')
|
||||
v-btn.mt-4(@click='dialogAddInstance = true' color='primary' text) <v-icon v-text='mdiPlus'></v-icon> {{$t('admin.add_instance')}}
|
||||
v-data-table(
|
||||
v-if='settings.trusted_instances.length'
|
||||
:hide-default-footer='settings.trusted_instances.length<10'
|
||||
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
|
||||
:headers='headers'
|
||||
:items='settings.trusted_instances')
|
||||
template(v-slot:item.actions="{item}")
|
||||
v-btn(icon @click='deleteInstance(item)' color='error')
|
||||
v-icon(v-text='mdiDeleteForever')
|
||||
|
||||
</template>
|
||||
<script>
|
||||
|
|
|
@ -1,62 +1,62 @@
|
|||
<template lang='pug'>
|
||||
v-container
|
||||
v-card-title {{$t('common.moderation')}}
|
||||
v-card-text
|
||||
v-row
|
||||
v-col(:span='12')
|
||||
span {{$t('common.instances')}}
|
||||
v-text-field(v-model='instancesFilter' :placeholder="$t('admin.filter_instances')")
|
||||
v-data-table(:items='instances'
|
||||
:items-per-page='5'
|
||||
:search='instancesFilter'
|
||||
:hide-default-footer='instances.length<5'
|
||||
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
|
||||
dense :headers='instancesHeader'
|
||||
@click:row='instanceSelected')
|
||||
template(v-slot:item.blocked="{ item }")
|
||||
v-icon(@click='toggleBlock(item)' v-text='item.blocked ? mdiCheckboxIntermediate : mdiCheckboxBlankOutline')
|
||||
v-container
|
||||
v-card-title {{$t('common.moderation')}}
|
||||
v-card-text
|
||||
v-row
|
||||
v-col(:span='12')
|
||||
span {{$t('common.instances')}}
|
||||
v-text-field(v-model='instancesFilter' :placeholder="$t('admin.filter_instances')")
|
||||
v-data-table(:items='instances'
|
||||
:items-per-page='5'
|
||||
:search='instancesFilter'
|
||||
:hide-default-footer='instances.length<5'
|
||||
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
|
||||
dense :headers='instancesHeader'
|
||||
@click:row='instanceSelected')
|
||||
template(v-slot:item.blocked="{ item }")
|
||||
v-icon(@click='toggleBlock(item)' v-text='item.blocked ? mdiCheckboxIntermediate : mdiCheckboxBlankOutline')
|
||||
|
||||
v-col(:span='11')
|
||||
span {{$t('common.users')}}
|
||||
v-text-field(v-model='usersFilter' :placeholder="$t('admin.filter_users')")
|
||||
v-data-table(:items='users'
|
||||
:items-per-page='5'
|
||||
:search='usersFilter'
|
||||
:hide-default-footer='users.length<5'
|
||||
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
|
||||
dense :headers='usersHeader')
|
||||
template(v-slot:item.blocked="{ item }")
|
||||
v-icon(@click='toggleUserBlock(item)' v-text='item.blocked ? mdiCheckboxIntermediate : mdiCheckboxBlankOutline')
|
||||
v-col(:span='11')
|
||||
span {{$t('common.users')}}
|
||||
v-text-field(v-model='usersFilter' :placeholder="$t('admin.filter_users')")
|
||||
v-data-table(:items='users'
|
||||
:items-per-page='5'
|
||||
:search='usersFilter'
|
||||
:hide-default-footer='users.length<5'
|
||||
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
|
||||
dense :headers='usersHeader')
|
||||
template(v-slot:item.blocked="{ item }")
|
||||
v-icon(@click='toggleUserBlock(item)' v-text='item.blocked ? mdiCheckboxIntermediate : mdiCheckboxBlankOutline')
|
||||
|
||||
div
|
||||
v-card-title {{$t('common.resources')}}
|
||||
v-data-table(:items='resources' dense
|
||||
:headers='resourcesHeader'
|
||||
:hide-default-footer='resources.length<10'
|
||||
:items-per-page='10'
|
||||
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }')
|
||||
template(v-slot:item.content='{ item }')
|
||||
span(v-html='item.data.content')
|
||||
template(v-slot:item.created='{ item }')
|
||||
span {{item.created | dateFormat('lll')}}
|
||||
template(v-slot:item.user='{ item }')
|
||||
a(:href='item.ap_user.url || item.ap_user.ap_id' target='_blank') {{item.ap_user.preferredUsername}}
|
||||
template(v-slot:item.event='{ item }')
|
||||
nuxt-link(:to='`/event/${item.event.slug || item.event.id}`') {{item.event.title}}
|
||||
template(v-slot:item.actions='{ item }')
|
||||
v-menu(offset-y)
|
||||
template(v-slot:activator="{ on }")
|
||||
v-btn.mr-2(v-on='on' color='primary' small icon)
|
||||
v-icon(v-text='mdiDotsVertical')
|
||||
v-list
|
||||
v-list-item(v-if='!item.hidden' @click='hideResource(item, true)')
|
||||
v-list-item-title <v-icon left v-text='mdiEyeOff'></v-icon> {{$t('admin.hide_resource')}}
|
||||
v-list-item(v-else @click='hideResource(item, false)')
|
||||
v-list-item-title <v-icon left v-text='mdiEye'></v-icon> {{$t('admin.show_resource')}}
|
||||
v-list-item(@click='deleteResource(item)')
|
||||
v-list-item-title <v-icon left v-text='mdiDelete'></v-icon> {{$t('admin.delete_resource')}}
|
||||
//- v-list-item(@click='toggleUserBlock(item.ap_user)')
|
||||
//- v-list-item-title <v-icon left>mdi-lock</v-icon> {{$t('admin.block_user')}}
|
||||
div
|
||||
v-card-title {{$t('common.resources')}}
|
||||
v-data-table(:items='resources' dense
|
||||
:headers='resourcesHeader'
|
||||
:hide-default-footer='resources.length<10'
|
||||
:items-per-page='10'
|
||||
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }')
|
||||
template(v-slot:item.content='{ item }')
|
||||
span(v-html='item.data.content')
|
||||
template(v-slot:item.created='{ item }')
|
||||
span {{item.created | dateFormat('lll')}}
|
||||
template(v-slot:item.user='{ item }')
|
||||
a(:href='item.ap_user.url || item.ap_user.ap_id' target='_blank') {{item.ap_user.preferredUsername}}
|
||||
template(v-slot:item.event='{ item }')
|
||||
nuxt-link(:to='`/event/${item.event.slug || item.event.id}`') {{item.event.title}}
|
||||
template(v-slot:item.actions='{ item }')
|
||||
v-menu(offset-y)
|
||||
template(v-slot:activator="{ on }")
|
||||
v-btn.mr-2(v-on='on' color='primary' small icon)
|
||||
v-icon(v-text='mdiDotsVertical')
|
||||
v-list
|
||||
v-list-item(v-if='!item.hidden' @click='hideResource(item, true)')
|
||||
v-list-item-title <v-icon left v-text='mdiEyeOff'></v-icon> {{$t('admin.hide_resource')}}
|
||||
v-list-item(v-else @click='hideResource(item, false)')
|
||||
v-list-item-title <v-icon left v-text='mdiEye'></v-icon> {{$t('admin.show_resource')}}
|
||||
v-list-item(@click='deleteResource(item)')
|
||||
v-list-item-title <v-icon left v-text='mdiDelete'></v-icon> {{$t('admin.delete_resource')}}
|
||||
//- v-list-item(@click='toggleUserBlock(item.ap_user)')
|
||||
//- v-list-item-title <v-icon left>mdi-lock</v-icon> {{$t('admin.block_user')}}
|
||||
</template>
|
||||
<script>
|
||||
import { mapState, mapActions } from 'vuex'
|
||||
|
|
|
@ -1,59 +1,94 @@
|
|||
<template lang="pug">
|
||||
v-card
|
||||
v-card-title SMTP Email configuration
|
||||
v-card-text
|
||||
p(v-html="$t('admin.smtp_description')")
|
||||
v-form(v-model='isValid')
|
||||
v-text-field(v-model='admin_email'
|
||||
@blur="save('admin_email', admin_email )"
|
||||
:label="$t('admin.admin_email')"
|
||||
:rules="$validators.email")
|
||||
v-text-field(v-model='smtp.host'
|
||||
:label="$t('admin.smtp_hostname')"
|
||||
:rules="[$validators.required('admin.smtp_hostname')]")
|
||||
v-card
|
||||
v-card-title SMTP Email configuration
|
||||
v-card-text
|
||||
p(v-html="$t('admin.smtp_description')")
|
||||
v-form(v-model='isValid')
|
||||
|
||||
v-text-field(v-model='smtp.auth.user'
|
||||
:label="$t('common.user')"
|
||||
:rules="[$validators.required('common.user')]")
|
||||
v-text-field(v-model='admin_email'
|
||||
@blur="save('admin_email', admin_email )"
|
||||
:label="$t('admin.sender_email')"
|
||||
:rules="$validators.email")
|
||||
|
||||
v-text-field(v-model='smtp.auth.pass'
|
||||
:label="$t('common.password')"
|
||||
:rules="[$validators.required('common.password')]"
|
||||
type='password')
|
||||
v-switch(v-model='smtp.sendmail'
|
||||
:label="$t('admin.smtp_use_sendmail')")
|
||||
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(color='primary' @click='testSMTP' :loading='loading' :disabled='loading || !isValid') {{$t('admin.smtp_test_button')}}
|
||||
v-btn(color='warning' @click="done") {{$t("common.ok")}}
|
||||
v-row(v-if='!smtp.sendmail')
|
||||
v-col(cols=12 md=9)
|
||||
v-text-field(v-model='smtp.host'
|
||||
:label="$t('admin.smtp_hostname')"
|
||||
:rules="[$validators.required('admin.smtp_hostname')]")
|
||||
v-col(cols=12 md=3)
|
||||
v-text-field(v-model='smtp.port'
|
||||
:label="$t('admin.smtp_port')"
|
||||
:rules="[$validators.required('admin.smtp_port')]")
|
||||
|
||||
v-col(cols=12)
|
||||
v-switch(v-model='smtp.secure'
|
||||
:label="$t('admin.smtp_secure')")
|
||||
|
||||
v-col(md=6)
|
||||
v-text-field(v-model='smtp.auth.user'
|
||||
:label="$t('common.user')")
|
||||
|
||||
v-col(md=6)
|
||||
v-text-field(v-model='smtp.auth.pass'
|
||||
:label="$t('common.password')"
|
||||
type='password')
|
||||
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(color='primary' @click='testSMTP' :loading='loading' :disabled='loading || !isValid' outlined) {{$t('admin.smtp_test_button')}}
|
||||
v-btn(color='warning' @click="done" outlined) {{$t("common.ok")}}
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import { mapActions, mapState } from 'vuex'
|
||||
export default {
|
||||
data ({ $store }) {
|
||||
const smtp = { host: '', auth: { user: '', pass: '' } }
|
||||
if ($store.state.settings.smtp) {
|
||||
smtp.host = $store.state.settings.smtp.host
|
||||
if ($store.state.settings.smtp.auth) {
|
||||
smtp.auth.user = $store.state.settings.smtp.auth.user
|
||||
smtp.auth.pass = $store.state.settings.smtp.auth.pass
|
||||
}
|
||||
}
|
||||
// if ($store.state.settings.smtp) {
|
||||
// smtp.host = $store.state.settings.smtp.host
|
||||
// if ($store.state.settings.smtp.auth) {
|
||||
// smtp.auth.user = $store.state.settings.smtp.auth.user
|
||||
// smtp.auth.pass = $store.state.settings.smtp.auth.pass
|
||||
// } else {
|
||||
// smtp.auth = {}
|
||||
// }
|
||||
// }
|
||||
return {
|
||||
isValid: false,
|
||||
loading: false,
|
||||
smtp,
|
||||
smtp: { auth: {} },
|
||||
admin_email: $store.state.settings.admin_email || ''
|
||||
}
|
||||
},
|
||||
async fetch () {
|
||||
this.smtp = await this.$axios.$get('/settings/smtp').catch(_e => ({ auth: {} }))
|
||||
if (!this.smtp.auth) {
|
||||
this.smtp.auth = {}
|
||||
}
|
||||
},
|
||||
computed: mapState(['settings']),
|
||||
watch: {
|
||||
'smtp.secure' (value) {
|
||||
this.smtp.port = value ? 465 : 25
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['setSetting']),
|
||||
async testSMTP () {
|
||||
this.loading = true
|
||||
try {
|
||||
await this.setSetting({ key: 'smtp', value: this.smtp })
|
||||
await this.$axios.$post('/settings/smtp', { smtp: this.smtp })
|
||||
const smtp = JSON.parse(JSON.stringify(this.smtp))
|
||||
if (!smtp.auth.user) {
|
||||
delete smtp.auth
|
||||
}
|
||||
if (!smtp.secure) {
|
||||
smtp.secure = false
|
||||
smtp.ignoreTLS = true
|
||||
}
|
||||
// await this.setSetting({ key: 'smtp', value: JSON.parse(JSON.stringify(this.smtp)) })
|
||||
await this.$axios.$post('/settings/smtp', { smtp })
|
||||
this.$root.$message(this.$t('admin.smtp_test_success', { admin_email: this.admin_email }), { color: 'success' })
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
@ -67,9 +102,15 @@ export default {
|
|||
}
|
||||
},
|
||||
done () {
|
||||
if (this.smtp.auth.pass) {
|
||||
this.setSetting({ key: 'smtp', value: JSON.parse(JSON.stringify(this.smtp)) })
|
||||
const smtp = JSON.parse(JSON.stringify(this.smtp))
|
||||
if (!smtp.auth.user) {
|
||||
delete smtp.auth
|
||||
}
|
||||
if (!smtp.secure) {
|
||||
smtp.secure = false
|
||||
smtp.ignoreTLS = true
|
||||
}
|
||||
this.setSetting({ key: 'smtp', value: smtp })
|
||||
this.$emit('close')
|
||||
},
|
||||
|
||||
|
|
|
@ -1,62 +1,61 @@
|
|||
<template lang="pug">
|
||||
v-container
|
||||
v-card-title {{$t('common.settings')}}
|
||||
v-card-text
|
||||
v-container
|
||||
v-card-title {{$t('common.settings')}}
|
||||
v-card-text
|
||||
|
||||
v-text-field(v-model='title'
|
||||
:label="$t('common.title')"
|
||||
:hint="$t('admin.title_description')"
|
||||
@blur='save("title", title)'
|
||||
persistent-hint)
|
||||
v-text-field(v-model='title'
|
||||
:label="$t('common.title')"
|
||||
:hint="$t('admin.title_description')"
|
||||
@blur='save("title", title)'
|
||||
persistent-hint)
|
||||
|
||||
v-text-field.mt-5(v-model='description'
|
||||
:label="$t('common.description')"
|
||||
:hint="$t('admin.description_description')"
|
||||
persistent-hint
|
||||
@blur='save("description", description)')
|
||||
v-text-field.mt-5(v-model='description'
|
||||
:label="$t('common.description')"
|
||||
:hint="$t('admin.description_description')"
|
||||
persistent-hint
|
||||
@blur='save("description", description)')
|
||||
|
||||
//- select timezone
|
||||
v-autocomplete.mt-5(v-model='instance_timezone'
|
||||
:label="$t('admin.select_instance_timezone')"
|
||||
:hint="$t('admin.instance_timezone_description')"
|
||||
:items="filteredTimezones"
|
||||
persistent-hint
|
||||
placeholder='Timezone, type to search')
|
||||
//- select timezone
|
||||
v-autocomplete.mt-5(v-model='instance_timezone'
|
||||
:label="$t('admin.select_instance_timezone')"
|
||||
:hint="$t('admin.instance_timezone_description')"
|
||||
:items="filteredTimezones"
|
||||
persistent-hint
|
||||
placeholder='Timezone, type to search')
|
||||
|
||||
v-select.mt-5(
|
||||
v-model='instance_locale'
|
||||
:label="$t('admin.instance_locale')"
|
||||
:hint="$t('admin.instance_locale_description')"
|
||||
persistent-hint
|
||||
:items='locales'
|
||||
)
|
||||
v-select.mt-5(
|
||||
v-model='instance_locale'
|
||||
:label="$t('admin.instance_locale')"
|
||||
:hint="$t('admin.instance_locale_description')"
|
||||
persistent-hint
|
||||
:items='locales'
|
||||
)
|
||||
|
||||
v-switch.mt-4(v-model='allow_registration'
|
||||
inset
|
||||
:label="$t('admin.allow_registration_description')")
|
||||
v-switch.mt-4(v-model='allow_registration'
|
||||
inset
|
||||
:label="$t('admin.allow_registration_description')")
|
||||
|
||||
v-switch.mt-1(v-model='allow_anon_event'
|
||||
inset
|
||||
:label="$t('admin.allow_anon_event')")
|
||||
v-switch.mt-1(v-model='allow_anon_event'
|
||||
inset
|
||||
:label="$t('admin.allow_anon_event')")
|
||||
|
||||
v-switch.mt-1(v-model='allow_recurrent_event'
|
||||
inset
|
||||
:label="$t('admin.allow_recurrent_event')")
|
||||
v-switch.mt-1(v-model='allow_recurrent_event'
|
||||
inset
|
||||
:label="$t('admin.allow_recurrent_event')")
|
||||
|
||||
v-switch.mt-1(v-if='allow_recurrent_event'
|
||||
v-model='recurrent_event_visible'
|
||||
inset
|
||||
:label="$t('admin.recurrent_event_visible')")
|
||||
v-switch.mt-1(v-if='allow_recurrent_event'
|
||||
v-model='recurrent_event_visible'
|
||||
inset
|
||||
:label="$t('admin.recurrent_event_visible')")
|
||||
|
||||
v-dialog(v-model='showSMTP' destroy-on-close max-width='700px' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||
SMTP(@close='showSMTP = false')
|
||||
|
||||
v-card-actions
|
||||
v-btn(text @click='showSMTP=true')
|
||||
<v-icon v-if='showSMTPAlert' color='error' v-text='mdiAlert'></v-icon> {{$t('admin.show_smtp_setup')}}
|
||||
v-btn(text @click='$emit("complete")' color='primary' v-if='setup') {{$t('common.next')}}
|
||||
v-icon(v-text='mdiArrowRight')
|
||||
v-dialog(v-model='showSMTP' destroy-on-close max-width='700px' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||
SMTP(@close='showSMTP = false')
|
||||
|
||||
v-card-actions
|
||||
v-btn(text @click='showSMTP=true')
|
||||
<v-icon v-if='!settings.admin_email' color='error' v-text='mdiAlert'></v-icon> {{$t('admin.show_smtp_setup')}}
|
||||
v-btn(text @click='$emit("complete")' color='primary' v-if='setup') {{$t('common.next')}}
|
||||
v-icon(v-text='mdiArrowRight')
|
||||
|
||||
</template>
|
||||
<script>
|
||||
|
@ -84,9 +83,6 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
...mapState(['settings']),
|
||||
showSMTPAlert () {
|
||||
return !this.setup && (!this.settings.admin_email || !this.settings.smtp || !this.settings.smtp.host || !this.settings.smtp.auth.user)
|
||||
},
|
||||
instance_locale: {
|
||||
get () { return this.settings.instance_locale },
|
||||
set (value) { this.setSetting({ key: 'instance_locale', value }) }
|
||||
|
|
|
@ -1,69 +1,69 @@
|
|||
<template lang="pug">
|
||||
v-container
|
||||
v-card-title {{$t('common.theme')}}
|
||||
v-card-text
|
||||
//- LOGO
|
||||
v-file-input.mt-5(ref='upload'
|
||||
:label="$t('admin.favicon')"
|
||||
@change='uploadLogo'
|
||||
accept='image/*')
|
||||
template(slot='append-outer')
|
||||
v-btn(color='warning' text @click='resetLogo') <v-icon v-text='mdiRestore'></v-icon> {{$t('common.reset')}}
|
||||
v-img(:src='`/logo.png?${logoKey}`'
|
||||
max-width="60px" max-height="60px" contain)
|
||||
v-container
|
||||
v-card-title {{$t('common.theme')}}
|
||||
v-card-text
|
||||
//- LOGO
|
||||
v-file-input.mt-5(ref='upload'
|
||||
:label="$t('admin.favicon')"
|
||||
@change='uploadLogo'
|
||||
accept='image/*')
|
||||
template(slot='append-outer')
|
||||
v-btn(color='warning' text @click='resetLogo') <v-icon v-text='mdiRestore'></v-icon> {{$t('common.reset')}}
|
||||
v-img(:src='`/logo.png?${logoKey}`'
|
||||
max-width="60px" max-height="60px" contain)
|
||||
|
||||
v-switch.mt-5(v-model='is_dark'
|
||||
inset
|
||||
:label="$t('admin.is_dark')")
|
||||
v-switch.mt-5(v-model='is_dark'
|
||||
inset
|
||||
:label="$t('admin.is_dark')")
|
||||
|
||||
//- TODO choose theme colors
|
||||
//- v-row
|
||||
//- v-col(v-for='(color, i) in colors' :key='i')
|
||||
//- v-menu(v-model='menu[i]'
|
||||
//- :close-on-content-click="false"
|
||||
//- transition="slide-x-transition"
|
||||
//- offset-y
|
||||
//- absolute
|
||||
//- bottom
|
||||
//- max-width="290px"
|
||||
//- min-width="290px")
|
||||
//- template(v-slot:activator='{ on }')
|
||||
//- v-btn(:color='i' small
|
||||
//- v-on='on') {{i}}
|
||||
//- v-color-picker(light @update:color='c => updateColor(i, c)')
|
||||
//- TODO choose theme colors
|
||||
//- v-row
|
||||
//- v-col(v-for='(color, i) in colors' :key='i')
|
||||
//- v-menu(v-model='menu[i]'
|
||||
//- :close-on-content-click="false"
|
||||
//- transition="slide-x-transition"
|
||||
//- offset-y
|
||||
//- absolute
|
||||
//- bottom
|
||||
//- max-width="290px"
|
||||
//- min-width="290px")
|
||||
//- template(v-slot:activator='{ on }')
|
||||
//- v-btn(:color='i' small
|
||||
//- v-on='on') {{i}}
|
||||
//- v-color-picker(light @update:color='c => updateColor(i, c)')
|
||||
|
||||
v-dialog(v-model='linkModal' width='500' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||
v-card
|
||||
v-card-title {{$t('admin.footer_links')}}
|
||||
v-card-text
|
||||
v-form(v-model='valid' ref='linkModalForm')
|
||||
v-text-field(v-model='link.label'
|
||||
:rules="[$validators.required('common.label')]"
|
||||
label='Label')
|
||||
v-text-field(v-model='link.href'
|
||||
:rules="[$validators.required('common.url')]"
|
||||
:label="$t('common.url')")
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(link @click='linkModal=false' color='error') {{$t('common.cancel')}}
|
||||
v-btn(link @click='addFooterLink' color='primary' :disabled='!valid') {{$t('common.add')}}
|
||||
|
||||
v-card-title {{$t('admin.footer_links')}}
|
||||
v-card-text
|
||||
v-btn(color='primary' text @click='openLinkModal') <v-icon v-text='mdiPlus'></v-icon> {{$t('admin.add_link')}}
|
||||
v-btn(color='warning' text @click='reset') <v-icon v-text='mdiRestore'></v-icon> {{$t('common.reset')}}
|
||||
v-dialog(v-model='linkModal' width='500' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||
v-card
|
||||
v-list.mt-1(two-line subheader)
|
||||
v-list-item(v-for='(link, idx) in settings.footerLinks'
|
||||
:key='`${link.label}`' @click='editFooterLink(link)')
|
||||
v-list-item-content
|
||||
v-list-item-title {{link.label}}
|
||||
v-list-item-subtitle {{link.href}}
|
||||
v-list-item-action
|
||||
v-btn.left(v-if='idx !== 0' icon color='warn' @click.stop='moveUpFooterLink(link, idx)')
|
||||
v-icon(v-text='mdiChevronUp')
|
||||
v-btn.float-right(icon color='error' @click.stop='removeFooterLink(link)')
|
||||
v-icon(v-text='mdiDeleteForever')
|
||||
v-card-title {{$t('admin.footer_links')}}
|
||||
v-card-text
|
||||
v-form(v-model='valid' ref='linkModalForm')
|
||||
v-text-field(v-model='link.label'
|
||||
:rules="[$validators.required('common.label')]"
|
||||
label='Label')
|
||||
v-text-field(v-model='link.href'
|
||||
:rules="[$validators.required('common.url')]"
|
||||
:label="$t('common.url')")
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(link @click='linkModal=false' color='error') {{$t('common.cancel')}}
|
||||
v-btn(link @click='addFooterLink' color='primary' :disabled='!valid') {{$t('common.add')}}
|
||||
|
||||
v-card-title {{$t('admin.footer_links')}}
|
||||
v-card-text
|
||||
v-btn(color='primary' text @click='openLinkModal') <v-icon v-text='mdiPlus'></v-icon> {{$t('admin.add_link')}}
|
||||
v-btn(color='warning' text @click='reset') <v-icon v-text='mdiRestore'></v-icon> {{$t('common.reset')}}
|
||||
v-card
|
||||
v-list.mt-1(two-line subheader)
|
||||
v-list-item(v-for='(link, idx) in settings.footerLinks'
|
||||
:key='`${link.label}`' @click='editFooterLink(link)')
|
||||
v-list-item-content
|
||||
v-list-item-title {{link.label}}
|
||||
v-list-item-subtitle {{link.href}}
|
||||
v-list-item-action
|
||||
v-btn.left(v-if='idx !== 0' icon color='warn' @click.stop='moveUpFooterLink(link, idx)')
|
||||
v-icon(v-text='mdiChevronUp')
|
||||
v-btn.float-right(icon color='error' @click.stop='removeFooterLink(link)')
|
||||
v-icon(v-text='mdiDeleteForever')
|
||||
|
||||
</template>
|
||||
<script>
|
||||
|
|
|
@ -1,50 +1,49 @@
|
|||
<template lang="pug">
|
||||
v-container
|
||||
v-card-title {{$t('common.users')}}
|
||||
v-spacer
|
||||
v-text-field(v-model='search'
|
||||
:append-icon='mdiMagnify' outlined rounded
|
||||
label='Search'
|
||||
single-line hide-details)
|
||||
v-container
|
||||
v-card-title {{$t('common.users')}}
|
||||
v-spacer
|
||||
v-text-field(v-model='search'
|
||||
:append-icon='mdiMagnify' outlined rounded
|
||||
label='Search'
|
||||
single-line hide-details)
|
||||
|
||||
v-btn(color='primary' text @click='newUserDialog = true') <v-icon v-text='mdiPlus'></v-icon> {{$t('common.new_user')}}
|
||||
v-btn(color='primary' text @click='newUserDialog = true') <v-icon v-text='mdiPlus'></v-icon> {{$t('common.new_user')}}
|
||||
|
||||
//- ADD NEW USER
|
||||
v-dialog(v-model='newUserDialog' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||
//- ADD NEW USER
|
||||
v-dialog(v-model='newUserDialog' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||
|
||||
v-card(color='secondary')
|
||||
v-card-title {{$t('common.new_user')}}
|
||||
v-card-text
|
||||
v-form(v-model='valid' ref='user_form' lazy-validation @submit.prevent='createUser')
|
||||
v-text-field(v-model='new_user.email'
|
||||
:label="$t('common.email')"
|
||||
:rules="$validators.email")
|
||||
v-switch(v-model='new_user.is_admin' :label="$t('common.admin')" inset)
|
||||
v-alert(type='info' :closable='false' :icon='mdiInformation') {{$t('admin.user_add_help')}}
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(@click='newUserDialog=false' color='error') {{$t('common.cancel')}}
|
||||
v-btn(@click='createUser' :disabled='!valid' color='primary') {{$t('common.send')}}
|
||||
v-card(color='secondary')
|
||||
v-card-title {{$t('common.new_user')}}
|
||||
v-card-text
|
||||
v-form(v-model='valid' ref='user_form' lazy-validation @submit.prevent='createUser')
|
||||
v-text-field(v-model='new_user.email'
|
||||
:label="$t('common.email')"
|
||||
:rules="$validators.email")
|
||||
v-switch(v-model='new_user.is_admin' :label="$t('common.admin')" inset)
|
||||
v-alert(type='info' :closable='false' :icon='mdiInformation') {{$t('admin.user_add_help')}}
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(@click='newUserDialog=false' color='error' outlined) {{$t('common.cancel')}}
|
||||
v-btn(@click='createUser' :disabled='!valid' color='primary' outlined) {{$t('common.send')}}
|
||||
|
||||
v-card-text
|
||||
//- USERS LIST
|
||||
v-data-table(
|
||||
:headers='headers'
|
||||
:items='users'
|
||||
:hide-default-footer='users.length<5'
|
||||
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
|
||||
:search='search')
|
||||
template(v-slot:item.is_active='{item}')
|
||||
v-icon(v-if='item.is_active' color='success' v-text='mdiCheck')
|
||||
v-icon(v-else color='warning' v-text='mdiClose')
|
||||
template(v-slot:item.actions='{item}')
|
||||
v-btn(v-if='item.recover_code' text small :to='`/user_confirm/${item.recover_code}`') {{$t('common.confirm')}}
|
||||
v-btn(text small @click='toggle(item)'
|
||||
:color='item.is_active?"warning":"success"') {{item.is_active?$t('common.disable'):$t('common.enable')}}
|
||||
v-btn(text small @click='toggleAdmin(item)'
|
||||
:color='item.is_admin?"warning":"error"') {{item.is_admin?$t('common.remove_admin'):$t('common.admin')}}
|
||||
v-btn(text small @click='deleteUser(item)'
|
||||
color='error') {{$t('admin.delete_user')}}
|
||||
v-card-text
|
||||
//- USERS LIST
|
||||
v-data-table(
|
||||
:headers='headers'
|
||||
:items='users'
|
||||
:hide-default-footer='users.length<5'
|
||||
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
|
||||
:search='search')
|
||||
template(v-slot:item.is_active='{item}')
|
||||
v-icon(v-if='item.is_active' color='success' v-text='mdiCheck')
|
||||
v-icon(v-else color='warning' v-text='mdiClose')
|
||||
template(v-slot:item.actions='{item}')
|
||||
v-btn(text small @click='toggle(item)'
|
||||
:color='item.is_active?"warning":"success"') {{item.is_active?$t('common.disable'):$t('common.enable')}}
|
||||
v-btn(text small @click='toggleAdmin(item)'
|
||||
:color='item.is_admin?"warning":"error"') {{item.is_admin?$t('common.remove_admin'):$t('common.admin')}}
|
||||
v-btn(text small @click='deleteUser(item)'
|
||||
color='error') {{$t('admin.delete_user')}}
|
||||
|
||||
</template>
|
||||
<script>
|
||||
|
@ -101,6 +100,9 @@ export default {
|
|||
},
|
||||
async toggleAdmin (user) {
|
||||
try {
|
||||
const configMsg = user.is_admin ? 'admin.disable_admin_user_confirm' : 'admin.enable_admin_user_confirm'
|
||||
const ret = await this.$root.$confirm(configMsg, { user: user.email })
|
||||
if (!ret) { return }
|
||||
user.is_admin = !user.is_admin
|
||||
await this.$axios.$put('/user', user)
|
||||
} catch (e) {
|
||||
|
|
17
docs/Dockerfile
Normal file
17
docs/Dockerfile
Normal file
|
@ -0,0 +1,17 @@
|
|||
FROM node:16-slim AS nodejs-base
|
||||
RUN apt-get -q update && \
|
||||
env DEBIAN_FRONTEND=noninteractive apt-get -y install git && \
|
||||
apt-get clean && rm -fr /var/lib/apt/lists/*
|
||||
|
||||
FROM nodejs-base AS build
|
||||
RUN yarnpkg global add --network-timeout 1000000000 --latest --production --silent https://gancio.org/latest.tgz && \
|
||||
apt-get clean && rm -fr /var/lib/apt/lists/* && \
|
||||
yarnpkg cache clean
|
||||
|
||||
FROM nodejs-base
|
||||
COPY --from=build /usr/local/share/.config/yarn/ /usr/local/share/.config/yarn/
|
||||
RUN ln -s ../share/.config/yarn/global/node_modules/.bin/gancio /usr/local/bin/gancio
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/gancio"]
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (6.0.5)
|
||||
activesupport (6.0.5.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
|
@ -18,11 +18,11 @@ GEM
|
|||
ffi (1.15.5)
|
||||
forwardable-extended (2.6.0)
|
||||
gemoji (3.0.1)
|
||||
html-pipeline (2.14.1)
|
||||
html-pipeline (2.14.2)
|
||||
activesupport (>= 2)
|
||||
nokogiri (>= 1.4)
|
||||
http_parser.rb (0.8.0)
|
||||
i18n (1.10.0)
|
||||
i18n (1.12.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jekyll (4.2.2)
|
||||
addressable (~> 2.4)
|
||||
|
@ -67,10 +67,13 @@ GEM
|
|||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
mercenary (0.4.0)
|
||||
mini_magick (4.11.0)
|
||||
minitest (5.15.0)
|
||||
nokogiri (1.13.6)
|
||||
mini_portile2 (2.8.0)
|
||||
minitest (5.16.2)
|
||||
nokogiri (1.13.8)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.13.8-x86_64-linux)
|
||||
racc (~> 1.4)
|
||||
pathutil (0.16.2)
|
||||
forwardable-extended (~> 2.6)
|
||||
premonition (4.0.2)
|
||||
|
@ -82,21 +85,21 @@ GEM
|
|||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
rexml (3.2.5)
|
||||
rouge (3.28.0)
|
||||
rouge (3.30.0)
|
||||
safe_yaml (1.0.5)
|
||||
sassc (2.4.0)
|
||||
ffi (~> 1.9)
|
||||
terminal-table (2.0.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
thread_safe (0.3.6)
|
||||
tzinfo (1.2.9)
|
||||
tzinfo (1.2.10)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo-data (1.2022.1)
|
||||
tzinfo (>= 1.0.0)
|
||||
unicode-display_width (1.8.0)
|
||||
wdm (0.1.1)
|
||||
webrick (1.7.0)
|
||||
zeitwerk (2.5.4)
|
||||
zeitwerk (2.6.0)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
|
|
@ -8,6 +8,32 @@ nav_order: 10
|
|||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
### 1.5.0 - UNRELEASED
|
||||
##### :warning: **BREAKING CHANGES**:
|
||||
- supported nodejs version >=14 <=16 (nodejs 12 reached End-of-Life on 30 April 2022)
|
||||
- minimum mariadb supported version >= 10.5.2
|
||||
|
||||
##### **CHANGES**:
|
||||
- new Tag page
|
||||
- new Place page
|
||||
- new search flow
|
||||
- new meta-tag-place / group / collection page
|
||||
- new time selection widget
|
||||
- allow footer links reordering
|
||||
- new Docker image (smaller and faster)
|
||||
- add GANCIO_DB_PORT environment
|
||||
- restrict new tag entropy (trim, merge case insensitive)
|
||||
- add dynamic sitemap.xml
|
||||
- calendar attributes refactoring (a dot each day, colors represents n. events)
|
||||
- fix event mime type response
|
||||
- fix mariadb JSON fields
|
||||
- new gancio CLI accounts management (list / create / remove / modify accounts)
|
||||
- improve smtp setup, allow local sendmail, smpt port, tls/starttls
|
||||
- redirect to path based on content type request
|
||||
- add Slovak translation
|
||||
- lot of fixes
|
||||
|
||||
|
||||
### 1.4.4 - 10 may '22
|
||||
- better img rendering, make it easier to download flyer #153
|
||||
- avoid place and tags duplication (remove white space, match case insensitive)
|
||||
|
|
|
@ -15,7 +15,7 @@ has_children: true
|
|||
- Express
|
||||
- Node.js
|
||||
- [Sequelize](https://sequelize.org/)
|
||||
- [Vuetify](vuetifyjs.com/)
|
||||
- [Vuetify](https://vuetifyjs.com/)
|
||||
|
||||
### Testing on your own machine
|
||||
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
FROM node:17-slim
|
||||
RUN bash -c "apt update -y && apt install git -y && apt clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp"
|
||||
RUN yarn global remove gancio || true
|
||||
RUN yarn cache clean
|
||||
RUN yarn global add --latest --production --silent https://gancio.org/latest.tgz
|
||||
ADD entrypoint.sh /
|
||||
RUN chmod 755 /entrypoint.sh
|
||||
ENTRYPOINT [ "/bin/sh", "/entrypoint.sh" ]
|
|
@ -2,9 +2,8 @@ version: '3'
|
|||
|
||||
services:
|
||||
gancio:
|
||||
build: .
|
||||
restart: always
|
||||
image: gancio
|
||||
image: cisti/gancio
|
||||
container_name: gancio
|
||||
environment:
|
||||
- PATH=$PATH:/home/node/.yarn/bin
|
||||
|
@ -12,8 +11,6 @@ services:
|
|||
- NODE_ENV=production
|
||||
- GANCIO_DB_DIALECT=sqlite
|
||||
- GANCIO_DB_STORAGE=./gancio.sqlite
|
||||
entrypoint: /entrypoint.sh
|
||||
command: gancio start
|
||||
volumes:
|
||||
- ./data:/home/node/data
|
||||
ports:
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
#!/bin/bash
|
||||
chown -R node:node /home/node
|
||||
su node -c "$*"
|
||||
|
|
@ -8,15 +8,15 @@ services:
|
|||
- ./db:/var/lib/mysql
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
environment:
|
||||
- MARIADB_AUTO_UPGRADE=true
|
||||
- MARIADB_USER=gancio
|
||||
- MARIADB_DATABASE=gancio
|
||||
- MARIADB_PASSWORD=gancio
|
||||
- MARIADB_RANDOM_ROOT_PASSWORD=yes
|
||||
restart: always
|
||||
gancio:
|
||||
build: .
|
||||
restart: always
|
||||
image: gancio
|
||||
image: cisti/gancio
|
||||
container_name: gancio
|
||||
environment:
|
||||
- PATH=$PATH:/home/node/.yarn/bin
|
||||
|
@ -28,8 +28,6 @@ services:
|
|||
- GANCIO_DB_DATABASE=gancio
|
||||
- GANCIO_DB_USERNAME=gancio
|
||||
- GANCIO_DB_PASSWORD=gancio
|
||||
command: gancio start
|
||||
entrypoint: /entrypoint.sh
|
||||
volumes:
|
||||
- ./data:/home/node/data
|
||||
ports:
|
||||
|
|
|
@ -16,9 +16,8 @@ services:
|
|||
ports:
|
||||
- 5432:5432
|
||||
gancio:
|
||||
build: .
|
||||
restart: always
|
||||
image: gancio
|
||||
image: cisti/gancio
|
||||
container_name: gancio
|
||||
environment:
|
||||
- PATH=$PATH:/home/node/.yarn/bin
|
||||
|
@ -30,8 +29,6 @@ services:
|
|||
- GANCIO_DB_DATABASE=gancio
|
||||
- GANCIO_DB_USERNAME=gancio
|
||||
- GANCIO_DB_PASSWORD=gancio
|
||||
command: gancio start
|
||||
entrypoint: /entrypoint.sh
|
||||
volumes:
|
||||
- ./data:/home/node/data
|
||||
ports:
|
||||
|
|
|
@ -2,9 +2,8 @@ version: '3'
|
|||
|
||||
services:
|
||||
gancio:
|
||||
build: .
|
||||
restart: always
|
||||
image: gancio
|
||||
image: cisti/gancio
|
||||
container_name: gancio
|
||||
environment:
|
||||
- PATH=$PATH:/home/node/.yarn/bin
|
||||
|
@ -12,8 +11,6 @@ services:
|
|||
- NODE_ENV=production
|
||||
- GANCIO_DB_DIALECT=sqlite
|
||||
- GANCIO_DB_STORAGE=./gancio.sqlite
|
||||
entrypoint: /entrypoint.sh
|
||||
command: gancio start
|
||||
volumes:
|
||||
- ./data:/home/node/data
|
||||
ports:
|
||||
|
|
|
@ -50,7 +50,7 @@ sudo adduser --group --system --shell /bin/false --home /opt/gancio gancio
|
|||
```
|
||||
1. Install Gancio
|
||||
```bash
|
||||
sudo yarn global add --silent {{site.url}}/latest.tgz
|
||||
sudo yarn global add --network-timeout 1000000000 --silent {{site.url}}/latest.tgz
|
||||
```
|
||||
|
||||
1. Setup systemd service and reload systemd
|
||||
|
@ -78,6 +78,6 @@ sudo systemctl start gancio
|
|||
```bash
|
||||
sudo yarn global remove gancio
|
||||
sudo yarn cache clean
|
||||
sudo yarn global add --silent {{site.url}}/latest.tgz
|
||||
sudo yarn global add --network-timeout 1000000000 --silent {{site.url}}/latest.tgz
|
||||
sudo systemctl restart gancio
|
||||
```
|
||||
|
|
|
@ -25,12 +25,6 @@ mkdir -p /opt/gancio
|
|||
cd /opt/gancio
|
||||
```
|
||||
|
||||
Download `Dockerfile` and `entrypoint.sh`:
|
||||
```bash
|
||||
wget {{site.url}}{% link /docker/Dockerfile %}
|
||||
wget {{site.url}}{% link /docker/entrypoint.sh %}
|
||||
```
|
||||
|
||||
Download `docker-compose.yml` choosing your preferred database dialect between `sqlite`, `postgres` and `mariadb`:
|
||||
```bash
|
||||
DB=sqlite
|
||||
|
|
|
@ -9,7 +9,11 @@ nav_order: 7
|
|||
- [gancio.cisti.org](https://gancio.cisti.org) (Turin, Italy)
|
||||
- [lapunta.org](https://lapunta.org) (Florence, Italy)
|
||||
- [sapratza.in](https://sapratza.in/) (Sardinia, Italy)
|
||||
- [ponente.rocks](https://ponente.rocks) (Ponente Ligure, Italy)
|
||||
- [puntello.org](https://puntello.org) (Milan, Italy)
|
||||
- [bcn.convoca.la](https://bcn.convoca.la/) (Barcelona)
|
||||
- [bonn.jetzt](https://bonn.jetzt/) (Digital-Events aus Bonn, Rhein-Sieg und der Region)
|
||||
- [quest.livellosegreto.it](https://quest.livellosegreto.it/)
|
||||
- [ezkerraldea.euskaragendak.eus](https://ezkerraldea.euskaragendak.eus/)
|
||||
- [lakelogaztetxea.net](https://lakelogaztetxea.net)
|
||||
- [agenda.eskoria.eus](https://agenda.eskoria.eus/)
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<template lang='pug'>
|
||||
v-app(app)
|
||||
Snackbar
|
||||
Confirm
|
||||
v-app(app)
|
||||
Snackbar
|
||||
Confirm
|
||||
|
||||
v-main(app)
|
||||
v-fade-transition(hide-on-leave)
|
||||
nuxt
|
||||
v-main(app)
|
||||
v-fade-transition(hide-on-leave)
|
||||
nuxt
|
||||
|
||||
</template>
|
||||
<script>
|
||||
|
|
|
@ -33,14 +33,20 @@ export default {
|
|||
return {
|
||||
htmlAttrs: {
|
||||
lang: this.locale
|
||||
}
|
||||
},
|
||||
link: [{ rel: 'icon', type: 'image/png', href: this.settings.baseurl + '/logo.png' }],
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return { collections: [], mdiChevronLeft }
|
||||
},
|
||||
async fetch () {
|
||||
this.collections = await this.$axios.$get('collections')
|
||||
if (this.$route.name && ['tag-tag', 'index', 'g-collection', 'p-place'].includes(this.$route.name)) {
|
||||
this.collections = await this.$axios.$get('collections').catch(_e => [])
|
||||
} else {
|
||||
this.collections = []
|
||||
}
|
||||
|
||||
},
|
||||
name: 'Default',
|
||||
components: { Nav, Snackbar, Footer, Confirm },
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<template lang='pug'>
|
||||
v-app(app)
|
||||
Snackbar
|
||||
Confirm
|
||||
Nav
|
||||
v-app(app)
|
||||
Snackbar
|
||||
Confirm
|
||||
Nav
|
||||
|
||||
v-main(app)
|
||||
v-fade-transition(hide-on-leave)
|
||||
nuxt
|
||||
v-main(app)
|
||||
v-fade-transition(hide-on-leave)
|
||||
nuxt
|
||||
|
||||
Footer
|
||||
Footer
|
||||
|
||||
</template>
|
||||
<script>
|
||||
|
|
|
@ -86,7 +86,10 @@
|
|||
"theme": "Tema",
|
||||
"tags": "Etiquetes",
|
||||
"label": "Etiqueta",
|
||||
"max_events": "Nre. màx. d'activitats"
|
||||
"max_events": "Nre. màx. d'activitats",
|
||||
"close": "Tanca",
|
||||
"blobs": "Blobs",
|
||||
"collections": "Coŀleccions"
|
||||
},
|
||||
"login": {
|
||||
"description": "Amb la sessió iniciada pots afegir activitats noves.",
|
||||
|
@ -232,7 +235,19 @@
|
|||
"smtp_test_button": "Envia un correu de prova",
|
||||
"widget": "Giny",
|
||||
"wrong_domain_warning": "La url base configurada a config.json <b>({baseurl})</b> difereix de la que esteu visitant <b>({url})</b>",
|
||||
"event_remove_ok": "S'ha suprimit l'esdeveniment"
|
||||
"event_remove_ok": "S'ha suprimit l'esdeveniment",
|
||||
"new_blob": "Nou blob",
|
||||
"blobs_description": "Els blobs són agrupacions d'activitats per etiquetes i llocs. Es mostren a la pàgina principal",
|
||||
"edit_blob": "Edita el blob",
|
||||
"enable_admin_user_confirm": "N'estàs segur que vols donar drets d'administrador a {user}?",
|
||||
"smtp_port": "Port SMTP",
|
||||
"smtp_use_sendmail": "Utilitza sendmail",
|
||||
"new_collection": "Col·lecció nova",
|
||||
"edit_collection": "Editar la col·lecció",
|
||||
"disable_admin_user_confirm": "N'estàs segur de suprimir els permisos d'administrador de {usuari}?",
|
||||
"collections_description": "Les col·leccions són agrupacions d'esdeveniments per etiquetes i llocs. Es mostraran a la pàgina d'inici",
|
||||
"smtp_secure": "SMTP segur (TLS o STARTTLS)",
|
||||
"sender_email": "Correu remitent"
|
||||
},
|
||||
"auth": {
|
||||
"not_confirmed": "Encara no s'ha confirmat…",
|
||||
|
|
195
locales/de.json
195
locales/de.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"common": {
|
||||
"title": "Titel",
|
||||
"user": "Nutzer:in",
|
||||
"user": "nutzende Person",
|
||||
"moderation": "Moderation",
|
||||
"follow": "Folgen",
|
||||
"feed_url_copied": "Öffne die kopierte Feed-URL in deinem RSS-Reader",
|
||||
|
@ -19,11 +19,11 @@
|
|||
"activate_user": "Bestätigt",
|
||||
"password_updated": "Passwort geändert.",
|
||||
"me": "Du",
|
||||
"disable": "ausschalten",
|
||||
"enable": "einschalten",
|
||||
"disable": "Deaktiviere",
|
||||
"enable": "Aktiviere",
|
||||
"cancel": "Abbrechen",
|
||||
"ok": "OK",
|
||||
"new_user": "Neue:r Nutzer:in",
|
||||
"new_user": "Neue nutzende Person",
|
||||
"new_password": "Neues Passwort",
|
||||
"recover_password": "Passwort wiederherstellen",
|
||||
"copy": "Kopieren",
|
||||
|
@ -42,7 +42,7 @@
|
|||
"settings": "Einstellungen",
|
||||
"places": "Orte",
|
||||
"events": "Veranstaltungen",
|
||||
"users": "Nutzer:innen",
|
||||
"users": "nutzende Personen",
|
||||
"admin": "Admin",
|
||||
"confirm": "Bestätigen",
|
||||
"edit": "Bearbeiten",
|
||||
|
@ -77,46 +77,120 @@
|
|||
"event": "Veranstaltung",
|
||||
"filter": "Filter",
|
||||
"authorize": "Autorisieren",
|
||||
"follow_me_title": "Folgen Sie den Aktualisierungen von Fediverse",
|
||||
"follow_me_title": "Folge den Aktualisierungen von Fediverse",
|
||||
"federation": "Föderation",
|
||||
"n_resources": "keine Ressource|eine Ressource|{n} Ressourcen",
|
||||
"resources": "Ressourcen",
|
||||
"info": "Infos",
|
||||
"theme": "Thema"
|
||||
"theme": "Thema",
|
||||
"related": "Zugehörig",
|
||||
"label": "Bezeichnung",
|
||||
"associate": "Partner:in",
|
||||
"collections": "Sammlungen",
|
||||
"max_events": "Maximale Anzahl an Veranstaltungen",
|
||||
"close": "Schließe"
|
||||
},
|
||||
"admin": {
|
||||
"delete_footer_link_confirm": "Sicher, diesen Link zu entfernen?",
|
||||
"delete_footer_link_confirm": "Möchtest du diesen Link löschen?",
|
||||
"footer_links": "Fußnotenlinks",
|
||||
"add_link": "Link hinzufügen",
|
||||
"is_dark": "Dunkles Design",
|
||||
"delete_trusted_instance_confirm": "Wollen Sie dieses Element wirklich aus dem Menü der Freundinstanz löschen?",
|
||||
"delete_trusted_instance_confirm": "Willst du diesen Eintrag wirklich aus dem Menü der befreundeten Instanz löschen?",
|
||||
"new_announcement": "Neue Ankündigung",
|
||||
"edit_place": "Ort bearbeiten"
|
||||
"edit_place": "Ort bearbeiten",
|
||||
"enable_resources": "Ressourcen freischalten",
|
||||
"hide_boost_bookmark_help": "Versteckt die kleinen Icons, die die Anzahl der eingehenden Boosts und Lesezeichen aus dem Fediverse anzeigen",
|
||||
"user_add_help": "Es wird eine E-Mail an die neue nutzende Person mit einem Hinweis auf die Bestätigung der Registrierung und die Wahl eines Passworts gesendet",
|
||||
"block_user": "nutzende Person sperren",
|
||||
"filter_instances": "Instanzen filtern",
|
||||
"filter_users": "nutzende Person filtern",
|
||||
"resources": "Ressourcen",
|
||||
"user_blocked": "Die nutzende Person {user} kann keine Ressourcen mehr hinzufügen",
|
||||
"user_block_confirm": "Bestätige, dass du die nutzende Person {user} sperren möchtest?",
|
||||
"instance_locale": "Vorgegebene Sprache",
|
||||
"instance_place": "Vorläufiger Ort dieser Instanz",
|
||||
"enable_trusted_instances": "Freigabe von befreundeten Instanzen",
|
||||
"smtp_port": "SMTP-Port",
|
||||
"smtp_secure": "SMTP Secure (TLS oder STARTTLS)",
|
||||
"smtp_use_sendmail": "Sendmail verwenden",
|
||||
"sender_email": "E-Mail der absendenden Person",
|
||||
"widget": "Widget",
|
||||
"new_collection": "Bubble anlegen",
|
||||
"edit_collection": "Bubble bearbeiten",
|
||||
"wrong_domain_warning": "Die \"baseurl\" die in config.json konfiguriert ist <b>({baseurl})</b> unterscheidet sich von derjenigen <b>({url})</b> die du besuchst",
|
||||
"instance_place_help": "Diese Textzeile wird im Menü der anderen freundlichen Instanzen angezeigt",
|
||||
"place_description": "Falls ein Ort falsch ist oder sich die Adresse ändert, kannst du ihn ändern.<br/>Bitte beachte, dass alle Veranstaltungen, die mit diesem Ort verbunden sind, die Adresse ändern (auch zurückliegende).",
|
||||
"enable_admin_user_confirm": "Achte darauf, dass du der nutzenden Person {user} Admin-Rechte hinzufügst",
|
||||
"trusted_instances_help": "Befreundete Instanzen werden in der Navigationsleiste oben auf der Seite angezeigt",
|
||||
"delete_announcement_confirm": "Möchtest du diesen Hinweis entfernen?",
|
||||
"instance_timezone_description": "Gancio wurde dazu entwickelt, um die Veranstaltungen eines bestimmten Ortes, z. B. einer Stadt, zu erfassen. Wenn du die Zeitzone dieses Ortes eingibst und auswählst, werden alle Zeiten angezeigt und wie ausgewählt eingegeben.",
|
||||
"instance_locale_description": "Die Sprache, in der die Seiten angezeigt werden, ist die von der nutzenden Person bevorzugte Sprache. In einigen Fällen ist es jedoch erforderlich, die Meldungen für alle auf dieselbe Weise anzuzeigen (z. B. bei der Veröffentlichung über ActivityPub oder beim Versand bestimmter E-Mails). In diesen Fällen wird die oben gewählte Sprache verwendet.",
|
||||
"add_trusted_instance": "Hinzufügen einer befreundeten Instanz",
|
||||
"collections_description": "Bubbles sind Gruppierungen von Ereignissen nach Tags und Orten",
|
||||
"disable_admin_user_confirm": "Möchtest du der nutzenden Person {user} die Adminrechte entziehen?",
|
||||
"announcement_description": "In diesem Bereich kannst du Hinweise schalten, die auf der Homepage bleiben",
|
||||
"instance_name_help": "Name des ActivityPub-Kontos dem gefolgt werden soll",
|
||||
"event_confirm_description": "Hier kannst du von anonymen Nutzern eingetragene Veranstaltungen bestätigen",
|
||||
"disable_user_confirm": "Möchtest du {user} deaktivieren?",
|
||||
"delete_user": "Lösche",
|
||||
"remove_admin": "Admin entfernen",
|
||||
"user_remove_ok": "Gelöschte nutzende Personen",
|
||||
"select_instance_timezone": "Zeitzone",
|
||||
"user_create_ok": "nutzende Person angelegt",
|
||||
"event_remove_ok": "Veranstaltung entfernt",
|
||||
"hide_boost_bookmark": "Versteckt Boost/Lesezeichen",
|
||||
"unblock": "Entsperre",
|
||||
"enable_resources_help": "Ermöglicht das Hinzufügen von Ressourcen zu Veranstaltungen aus dem Fediverse",
|
||||
"hide_resource": "Ressource ausblenden",
|
||||
"instance_name": "Name der Instanz",
|
||||
"delete_resource": "Ressource löschen",
|
||||
"delete_resource_confirm": "Möchtest du diese Ressource wirklich löschen?",
|
||||
"announcement_remove_ok": "Hinweis entfernt",
|
||||
"instance_block_confirm": "Bestätige, dass du die Instanz {instance} blockieren möchtest?",
|
||||
"favicon": "Logo",
|
||||
"title_description": "Es wird im Seitentitel, in der Betreffzeile von E-Mails, beim Export von RSS-Feeds und ICS verwendet.",
|
||||
"description_description": "Erscheint in der Kopfzeile neben dem Titel",
|
||||
"allow_recurrent_event": "Wiederkehrende Veranstaltungen erlauben",
|
||||
"recurrent_event_visible": "Wiederkehrende Veranstaltungen sind standardmäßig sichtbar",
|
||||
"enable_federation": "Federation aktivieren",
|
||||
"allow_anon_event": "Kann man auch anonyme Veranstaltungen (vorausgesetzt, diese werden genehmigt) eintragen?",
|
||||
"allow_registration_description": "Möchtest du die Registrierung aktivieren?",
|
||||
"federation": "Föderation / ActivityPub",
|
||||
"enable_federation_help": "Es wird möglich sein, diese Instanz vom Fediverse aus zu verfolgen",
|
||||
"add_instance": "Instanz hinzufügen",
|
||||
"block": "Sperre",
|
||||
"show_resource": "Ressource anzeigen",
|
||||
"delete_user_confirm": "Möchtest du {user} entfernen?",
|
||||
"show_smtp_setup": "E-Mail-Einstellungen",
|
||||
"smtp_hostname": "SMTP-Hostname",
|
||||
"smtp_description": "<ul><li>Der Admin erhält eine E-Mail, wenn ein anonymes Ereignis hinzugefügt wird (falls aktiviert).</li><li>Der Admin erhält E-Mails mit der Aufforderung zur Registrierung (falls aktiviert).</li><li>Die nutzenden Personen erhalten eine E-Mail zur Bestätigung der angeforderten Registrierung.</li><li>Die nutzenden Personen erhalten eine E-Mail mit der Anmeldebestätigung.</li><li>Nutzende Personen erhalten eine E-Mail-Benachrichtigung, wenn sie direkt vom Admin registriert werden.</li><li>Nutzende Personen erhalten eine E-Mail, um ihr Passwort zu ändern, wenn sie es vergessen haben</li></ul>",
|
||||
"smtp_test_button": "Versenden einer Test-E-Mail",
|
||||
"smtp_test_success": "Eine Test-E-Mail wurde an {admin_email} gesendet, überprüfe deine Mailbox"
|
||||
},
|
||||
"settings": {
|
||||
"update_confirm": "Wollen Sie Ihre Änderung speichern?",
|
||||
"update_confirm": "Willst du deine Änderung speichern?",
|
||||
"password_updated": "Passwort geändert.",
|
||||
"change_password": "Passwort ändern",
|
||||
"remove_account_confirm": "Sie sind dabei, Ihr Konto endgültig zu löschen",
|
||||
"remove_account": "Durch Drücken der folgenden Schaltfläche wird Ihr Benutzerkonto gelöscht. Von Ihnen veröffentlichte Ereignisse werden nicht veröffentlicht.",
|
||||
"danger_section": "Gefährlicher Abschnitt"
|
||||
"remove_account_confirm": "Du bist dabei, dein Konto endgültig zu löschen",
|
||||
"remove_account": "Durch Drücken der folgenden Schaltfläche wird dein Konto als nutzende Person gelöscht. Die von dir veröffentlichten Ereignisse werden nicht veröffentlicht.",
|
||||
"danger_section": "Gefährlicher Bereich"
|
||||
},
|
||||
"auth": {
|
||||
"fail": "Konnte sich nicht anmelden. Sind Sie sicher, dass das Passwort korrekt ist?",
|
||||
"fail": "Die Anmeldung ist fehlgeschlagen. Bist du sicher, dass das Passwort korrekt ist?",
|
||||
"not_confirmed": "Noch nicht bestätigt …"
|
||||
},
|
||||
"error": {
|
||||
"email_taken": "Diese E-Mail-Adresse wird schon verwendet.",
|
||||
"nick_taken": "Dieser Benutzername wird bereits verwendet."
|
||||
"nick_taken": "Dieser Name wird bereits für eine nutzende Person verwendet."
|
||||
},
|
||||
"confirm": {
|
||||
"title": "Benutzerbestätigung",
|
||||
"valid": "Ihr Konto ist bestätigt; Sie können sich jetzt <a href=\"/login\">anmelden</a>",
|
||||
"title": "Bestätigung der nutzenden Person",
|
||||
"valid": "Dein Konto ist bestätigt; du kannst dich sich jetzt <a href=\"/login\">anmelden</a>",
|
||||
"not_valid": "Etwas ist schief gelaufen."
|
||||
},
|
||||
"validators": {
|
||||
"required": "{fieldName} ist erforderlich",
|
||||
"email": "Fügen Sie eine gültige E-Mail ein"
|
||||
"email": "Füge eine gültige E-Mail ein"
|
||||
},
|
||||
"ordinal": {
|
||||
"-1": "letzte",
|
||||
|
@ -128,39 +202,94 @@
|
|||
},
|
||||
"oauth": {
|
||||
"scopes": {
|
||||
"event:write": "Fügen Sie Ihre Ereignisse hinzu und bearbeiten Sie sie"
|
||||
}
|
||||
"event:write": "Veröffentliche/Bearbeite deine Veranstaltungen"
|
||||
},
|
||||
"redirected_to": "Nach der Bestätigung wirst du zu <code>{url}</code> weitergeleitet",
|
||||
"authorization_request": "Die Anwendung <code>{app}</code> verlangt die Berechtigung die folgenden Aktionen auf <code>{instance_name}</code> ausüben zu können:"
|
||||
},
|
||||
"event": {
|
||||
"tag_description": "Markierung",
|
||||
"description_description": "Beschreibung",
|
||||
"what_description": "Titel",
|
||||
"same_day": "am selben Tag",
|
||||
"anon": "Anonym"
|
||||
"anon": "Anonym",
|
||||
"saved": "Veranstaltung gespeichert",
|
||||
"import_description": "Du kannst Veranstaltungen von anderen Plattformen und anderen Instanzen über Standardformate (ics und h-event) importieren",
|
||||
"updated": "Veranstaltung wurde aktualisiert",
|
||||
"not_found": "Eine solche Veranstaltung wurde nicht gefunden",
|
||||
"recurrent": "Wiederkehrende",
|
||||
"added": "Veranstaltung hinzugefügt",
|
||||
"image_too_big": "Das Bild darf nicht größer als 4MB sein",
|
||||
"import_URL": "Einlesen von URL",
|
||||
"confirmed": "Veranstaltung bestätigt",
|
||||
"added_anon": "Die Veranstaltung wurde hinzugefügt, muss aber noch bestätigt werden.",
|
||||
"remove_confirmation": "Möchtest du diese Veranstaltung wirklich entfernen?",
|
||||
"each_2w": "Jede zweite Woche",
|
||||
"ics": "ICS",
|
||||
"remove_recurrent_confirmation": "Bist du dir sicher, dass du diese wiederkehrende Veranstaltung entfernen möchtest?\nFrühere Veranstaltungen werden beibehalten, aber es werden keine zukünftigen Veranstaltungen mehr hinzugefügt.",
|
||||
"anon_description": "Du kannst eine Veranstaltung hinzufügen, ohne dich zu registrieren oder anzumelden,\nmusst dann aber warten, bis jemand es liest und bestätigt, dass es eine zulässige Veranstaltung ist.\nEs ist nicht möglich, den Eintrag zu verändern.<br/><br/>\nDu kannst dich stattdessen <a href='/login'>anmelden</a> oder <a href='/register'>registrieren</a>. In diesem Fall solltest du so schnell wie möglich eine Antwort erhalten. ",
|
||||
"where_description": "Wo ist die Veranstaltung? Wenn der Ort noch nicht beschrieben wurde, kannst du ihn selbst eintragen.",
|
||||
"follow_me_description": "Eine Möglichkeit, über die hier auf {title} veröffentlichten Veranstaltungen auf dem Laufenden zu bleiben, besteht darin, dem Account <u>{account}</u> aus dem Fediverse zu folgen, zum Beispiel über Mastodon, und von dort aus eventuell Ressourcen für eine Veranstaltung hinzuzufügen.<br/><br/>\nWenn Du noch nie von Mastodon und dem Fediverse gehört hast, empfehlen wir dir, diesen Artikel<a href='https://www.savjee.be/videos/simply-explained/mastodon-and-fediverse-explained/'>zu lesen</a>.<br/><br/>Gib unten deine Instanz ein (z.B. mastodon.social)",
|
||||
"media_description": "Du kannst (optional) einen Flyer hinzufügen",
|
||||
"edit_recurrent": "Bearbeite eine sich wiederholende Veranstaltung :",
|
||||
"show_recurrent": "regelmäßige Veranstaltungen",
|
||||
"show_past": "auch ältere Veranstaltungen",
|
||||
"only_future": "nur zukünftige Veranstaltungen",
|
||||
"recurrent_description": "Wähle die Häufigkeit und Tage aus",
|
||||
"multidate_description": "Ist es ein Festival? Bestimme den Start- und Endzeitpunkt",
|
||||
"multidate": "Weitere Tage",
|
||||
"normal": "Normal",
|
||||
"normal_description": "Wähle den Tag aus.",
|
||||
"recurrent_1w_days": "Jeden {days}",
|
||||
"recurrent_2w_days": "Jeden zweiten {days} eine",
|
||||
"recurrent_1m_days": "|Der {days} in jedem Monat|{days} in jedem Monat",
|
||||
"recurrent_2m_days": "Jeder {days} im Monat",
|
||||
"recurrent_1m_ordinal": "Jeden {n} {days} im Monat",
|
||||
"each_week": "Jede Woche",
|
||||
"recurrent_2m_ordinal": "Jeden {n} {days} im Monat",
|
||||
"each_month": "Jeden Monat",
|
||||
"due": "bis",
|
||||
"from": "von",
|
||||
"interact_with_me_at": "Tausche dich mit mir auf dem Fediverse aus unter",
|
||||
"interact_with_me": "Folge mir",
|
||||
"import_ICS": "Einlesen von ICS",
|
||||
"download_flyer": "Lade den Flyer herunter",
|
||||
"remove_media_confirmation": "Möchtest du das Bild löschen?",
|
||||
"alt_text_description": "Beschreibung für Menschen mit Sehbehinderungen",
|
||||
"choose_focal_point": "Auswahl des Schwerpunkts"
|
||||
},
|
||||
"register": {
|
||||
"first_user": "Administrator erstellt",
|
||||
"first_user": "Admin erstellt",
|
||||
"complete": "Die Registrierung muss bestätigt werden.",
|
||||
"error": "Fehler: "
|
||||
"error": "Fehler: ",
|
||||
"description": "Soziale Bewegungen sollten sich organisieren und durch sich selbst finanzieren.<br/>\n<br/>Vor deiner Veröffentlichung, <strong> muss dein Nutzer:innen-Konto freigeschaltet werden. </strong>Bitte denke daran, dass <strong>hinter dieser Seite echte Menschen zu finden sind</strong>, also schreibe zwei Zeilen, um uns mitzuteilen, welche Veranstaltungen du veröffentlichen möchtest."
|
||||
},
|
||||
"export": {
|
||||
"list_description": "Wenn Sie eine Website haben und eine Liste von Ereignissen anzeigen möchten, verwenden Sie den folgenden Code",
|
||||
"list_description": "Wenn Du eine Website hast und eine Liste von Ereignissen anzeigen möchtest, verwende den folgenden Code",
|
||||
"ical_description": "Computer und Smartphones sind üblicherweise mit einer Kalenderanwendung ausgestattet, mit der ein Fernkalender importiert werden kann.",
|
||||
"insert_your_address": "Geben Sie Ihre E-Mail-Adresse ein",
|
||||
"email_description": "Sie können interessante Ereignisse per E-Mail erhalten.",
|
||||
"insert_your_address": "Gib deine E-Mail-Adresse ein",
|
||||
"email_description": "Du kannst interessante Veranstaltungen per E-Mail erhalten.",
|
||||
"feed_description": "Um Updates auf deinem Computer oder Smartphone zu erhalten, ohne regelmäßig diese Seite zu öffnen, kannst du RSS-Feeds benutzen. </p>\n\n<p> Mit RSS-Feeds nutzt du eine spezielle App, um Updates von den Seiten, die dich interessieren, zu erhalten. Damit kannst du rasch vielen Seiten folgen, ohne einen\nAccount erstellen zu müssen und ohne sonstige Komplikationen. </p>\n\n<li> Für Android-Geräte empfehlen wir <a href=\"https://f-droid.org/en/packages/net.frju.flym/\">Flym</a> oder Feeder </li>\n<li> Auf iPhones / iPads kannst du <a href=\"https://itunes.apple.com/ua/app/feeds4u/id1038456442?mt=8\"> Feed4U </a> nutzen</li>\n<li> Auf Desktop-PCs oder Laptops empfehlen wir Feedbro, den du in <a href=\"https://addons.mozilla.org/en-GB/firefox/addon/feedbroreader/\"> Firefox </a> oder <a \nref=\"https://chrome.google.com/webstore/detail/feedbro/mefgmmbdailogpfhfblcnnjfmnpnmdfa\"> Chrome </a> installieren kannst. </li>\n<br/>\nIndem du diesen Link deinem RSS-Feed-Reader hinzufügst, wirst du auf dem Laufenden gehalten.",
|
||||
"intro": "Anders als unsoziale Netzwerke, die alles unternehmen, um Nutzer und deren Daten bei sich zu (be)halten, glauben wir, dass Informationen und Leute gleichermaßen frei sein müssen. Deshalb kannst du dich über Veranstaltungen auf dem Laufenden halten, ohne zwangsläufig über diese Seite zu gehen."
|
||||
"intro": "Anders als unsoziale Netzwerke, die alles unternehmen, um nutzende Personen und deren Daten bei sich zu (be)halten, glauben wir, dass Informationen und Menschen gleichermaßen frei sein müssen. Deshalb kannst du dich über Veranstaltungen aktuell informieren, ohne zwangsläufig über diese Seite zu gehen."
|
||||
},
|
||||
"recover": {
|
||||
"not_valid_code": "Etwas ist schief gelaufen."
|
||||
},
|
||||
"login": {
|
||||
"ok": "Angemeldet",
|
||||
"insert_email": "Geben Sie Ihre E-Mail-Adresse ein",
|
||||
"error": "Anmeldung unmöglich. Überprüfen Sie Ihre Anmeldeinformationen.",
|
||||
"insert_email": "Gib deine E-Mail-Adresse ein",
|
||||
"error": "Eine Anmeldung ist nicht möglich. Bitte überprüfe deine Informationen zur Anmeldung.",
|
||||
"forgot_password": "Passwort vergessen?",
|
||||
"not_registered": "Nicht registriert?",
|
||||
"check_email": "Überprüfen Sie Ihren E-Mail-Posteingang und Spam.",
|
||||
"description": "Durch Anmelden können Sie neue Veranstaltungen veröffentlichen."
|
||||
}
|
||||
"check_email": "Überprüfe deinen E-Mail-Posteingang und Spam.",
|
||||
"description": "Durch Anmelden kannst du neue Veranstaltungen veröffentlichen."
|
||||
},
|
||||
"setup": {
|
||||
"completed": "Einrichtung abgeschlossen",
|
||||
"completed_description": "<p>Du kannst dich mit den folgenden Anmeldedaten einloggen:<br/><br/>nutzende Person: <b>{email}</b><br/>Passwort: <b>{password}<b/></p>",
|
||||
"https_warning": "Du rufst gerade das Setup über HTTP auf; denke daran, die baseurl in der config.json zu ändern, wenn du zu HTTPS wechselst!",
|
||||
"copy_password_dialog": "Ja, du musst das Passwort kopieren!",
|
||||
"start": "Start"
|
||||
},
|
||||
"about": "\n <p><a href='https://gancio.org'>Gancio</a> ist eine gemeinsamer Veranstaltungsalender für lokale und regionale Gemeinschaften.</p>\n "
|
||||
}
|
||||
|
|
|
@ -18,5 +18,12 @@
|
|||
"register": {
|
||||
"content": "Wir haben die Registrierungsanfrage erhalten. Wir werden sie so bald wie möglich bestätigen.",
|
||||
"subject": "Registrierungsanfrage erhalten"
|
||||
},
|
||||
"event_confirm": {
|
||||
"content": "Du kannst diese Veranstaltung auf <a href='{{url}}'>dieser Seite</a> bestätigen"
|
||||
},
|
||||
"test": {
|
||||
"content": "Dieses ist eine Test-E-Mail. Wenn du das hier lesen kannst, dann funktioniert deine Konfiguration richtig.",
|
||||
"subject": "Deine SMTP Konfiguration funktioniert"
|
||||
}
|
||||
}
|
||||
|
|
29
locales/email/sk.json
Normal file
29
locales/email/sk.json
Normal file
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"register": {
|
||||
"subject": "Požiadavka na registráciu bola prijatá",
|
||||
"content": "Prijali sme požiadavku na registráciu. Potvrdíme ju čo najskôr."
|
||||
},
|
||||
"confirm": {
|
||||
"subject": "Teraz môžete začať publikovať podujatia",
|
||||
"content": "Ahoj, tvoj účet na <a href='{{config.baseurl}}'>{{config.title}}</a> bol potvdený. Pre akékoľvek informácie nám píšte na {{config.admin_email}}."
|
||||
},
|
||||
"user_confirm": {
|
||||
"subject": "Teraz môžete začať publikovať podujatia",
|
||||
"content": "Ahoj, tvoj účet na <a href='{{config.baseurl}}'>{{config.title}}</a> bol vytvorený. <a href='{{config.baseurl}}/user_confirm/{{user.recover_code}}'>Potvrď ho a zvol si heslo.</a>."
|
||||
},
|
||||
"recover": {
|
||||
"subject": "Obnova hesla",
|
||||
"content": "Ahoj, vyžiadal ste si obnovu hesla na{{config.title}}. <a href='{{config.baseurl}}/recover/{{user.recover_code}}'>Klikni sem</a> pre potvrdenie."
|
||||
},
|
||||
"admin_register": {
|
||||
"subject": "Nová registrácia",
|
||||
"content": "{{user.email}} si vyžiadal registráciu na {{config.title}}: <br/><pre>{{user.description}}</pre><br/> Potvrďte ju <a href='{{config.baseurl}}/admin'>tu</a>."
|
||||
},
|
||||
"event_confirm": {
|
||||
"content": "Potvrdiť podujatie môžete na <a href='{{url}}'>tejto stránke</a>"
|
||||
},
|
||||
"test": {
|
||||
"subject": "Vaša SMTP konfigurácia funguje",
|
||||
"content": "Toto je testovací e-mail, ak toto čítate, tak vaša konfigurácia funguje."
|
||||
}
|
||||
}
|
|
@ -174,6 +174,8 @@
|
|||
"remove_admin": "Remove admin",
|
||||
"disable_user_confirm": "Are you sure you want to disable {user}?",
|
||||
"delete_user_confirm": "Are you sure you want to remove {user}?",
|
||||
"disable_admin_user_confirm": "Are you sure to remove admin permissions from {user}?",
|
||||
"enable_admin_user_confirm": "Are sure to add admin permissions to {user}",
|
||||
"user_remove_ok": "User removed",
|
||||
"user_create_ok": "User created",
|
||||
"event_remove_ok": "Event removed",
|
||||
|
@ -229,10 +231,13 @@
|
|||
"new_announcement": "New announcement",
|
||||
"show_smtp_setup": "Email settings",
|
||||
"smtp_hostname": "SMTP Hostname",
|
||||
"smtp_port": "SMTP Port",
|
||||
"smtp_secure": "SMTP Secure (TLS or STARTTLS)",
|
||||
"smtp_description": "<ul><li>Admin should receive an email when anon event is added (if enabled).</li><li>Admin should receive email of registration request (if enabled).</li><li>User should receive an email of registration request.</li><li>User should receive email of confirmed registration.</li><li>User should receive a confirmation email when subscribed directly by admin.</li><li>Users should receive email to restore password when they forgot it</li></ul>",
|
||||
"smtp_test_success": "A test email is sent to {admin_email}, please check your inbox",
|
||||
"smtp_test_button": "Send a test email",
|
||||
"admin_email": "Admin e-mail",
|
||||
"smtp_use_sendmail": "Use sendmail",
|
||||
"sender_email": "Sender e-mail",
|
||||
"widget": "Widget",
|
||||
"wrong_domain_warning": "The baseurl configured in config.json <b>({baseurl})</b> differs from the one you're visiting <b>({url})</b>",
|
||||
"new_collection": "New collection",
|
||||
|
|
|
@ -9,4 +9,5 @@ export default {
|
|||
it: 'Italiano',
|
||||
nb: 'Norwegian Bokmål',
|
||||
pl: 'Polski',
|
||||
sk: 'Slovak'
|
||||
}
|
||||
|
|
|
@ -86,7 +86,9 @@
|
|||
"import": "Inportatu",
|
||||
"reset": "Berrezarri",
|
||||
"theme": "Itxura",
|
||||
"tags": "Etiketak"
|
||||
"tags": "Etiketak",
|
||||
"close": "Itxi",
|
||||
"blobs": "Mordoak"
|
||||
},
|
||||
"login": {
|
||||
"description": "Saioa hasten baduzu ekitaldi berriak sortu ahal izango dituzu.",
|
||||
|
@ -232,7 +234,10 @@
|
|||
"smtp_description": "<ul><li>Administratzaileak eposta bat jaso beharko luke anonimo batek ekitaldi bat gehitzen duenean (gaituta badago).</li><li>Administratzaileak eposta bat jaso beharko luke izena emateko eskari bakoitzeko (gaituta badago).</li><li>Erabiltzaileak eposta bat jaso beharko luke izena emateko eskariarekin.</li><li>Erabiltzaileak eposta bat jaso beharko luke izen ematea baieztatzean.</li><li>Erabiltzaileak eposta bat jaso beharko luke administratzaileak zuzenean izena emanez gero.</li><li>Erabiltzaileek eposta bat jaso beharko lukete pasahitza ahazten dutenean.</li></ul>",
|
||||
"widget": "Tresna",
|
||||
"event_remove_ok": "Ekitaldia ezabatu da",
|
||||
"wrong_domain_warning": "config.json-en konfiguratuta dagoen baseurl <b>({baseurl})</b> ez da bisitatzen ari zaren berbera <b>({url})</b>"
|
||||
"wrong_domain_warning": "config.json-en konfiguratuta dagoen baseurl <b>({baseurl})</b> ez da bisitatzen ari zaren berbera <b>({url})</b>",
|
||||
"new_blob": "Mordo berria",
|
||||
"blobs_description": "Mordoak etiketen eta lekuen arabera multzokatutako ekitaldiak dira. Hasiera-orrialdean bistaratzen dira",
|
||||
"edit_blob": "Editatu mordoa"
|
||||
},
|
||||
"auth": {
|
||||
"not_confirmed": "Oraindik baieztatu gabe dago…",
|
||||
|
|
|
@ -86,7 +86,10 @@
|
|||
"send": "Envoyer",
|
||||
"export": "Exporter",
|
||||
"label": "Nom",
|
||||
"max_events": "Nb. max d'événements"
|
||||
"max_events": "Nb. max d'événements",
|
||||
"blobs": "Blobs",
|
||||
"close": "Fermer",
|
||||
"collections": "Collections"
|
||||
},
|
||||
"event": {
|
||||
"follow_me_description": "Une des manières de rester informé sur les évènements publiés ici sur {title}\nest de suivre le compte <u>{account}</u> sur le fediverse, par exemple via Mastodon, et pourquoi pas d'ajouter des ressources à un évènement à partir de là.<br/><br/>\nSi vous n'avez jamais entendu parler de Mastodon and du fediverse, nous vous recommandons de lire <a href='https://www.savjee.be/videos/simply-explained/mastodon-and-fediverse-explained/'>cet article (en anglais)</a>.<br/><br/>Saisissez votre nom d'instance ci-dessous (par ex. mastodon.social)",
|
||||
|
@ -218,7 +221,20 @@
|
|||
"smtp_test_success": "Un e-mail de test a été envoyé à {admin_email}, veuillez vérifier votre boîte de réception",
|
||||
"admin_email": "E-mail de l'administrateur",
|
||||
"widget": "Vignette active",
|
||||
"event_remove_ok": "Événement supprimé"
|
||||
"event_remove_ok": "Événement supprimé",
|
||||
"new_blob": "Nouveau blob",
|
||||
"edit_blob": "Modifier le blob",
|
||||
"blobs_description": "Les blobs sont des regroupements d'événements par étiquette et par lieu. Ils seront affichés sur la page d'accueil",
|
||||
"disable_admin_user_confirm": "Voulez-vous vraiment retirer les droits d'administration pour {user} ?",
|
||||
"smtp_port": "Port SMTP",
|
||||
"smtp_secure": "Port SMTP sécurisé (TLS ou STARTTLS)",
|
||||
"wrong_domain_warning": "La propriété baseurl configurée dans le fichier config.json <b>{{baseurl}}</b> diffère de celle que vous consultez <b>{{url}}</b>",
|
||||
"smtp_use_sendmail": "Utiliser sendmail",
|
||||
"edit_collection": "Modifier la collection",
|
||||
"sender_email": "E-mail de l'envoyeur",
|
||||
"new_collection": "Nouvelle collection",
|
||||
"collections_description": "Les collections sont des événements groupés par étiquette et par lieu. Elles seront affichées sur la page d'accueil",
|
||||
"enable_admin_user_confirm": "Voulez-vous vraiment ajouter les droits d'administration pour {user} ?"
|
||||
},
|
||||
"oauth": {
|
||||
"scopes": {
|
||||
|
@ -278,6 +294,7 @@
|
|||
"completed": "Configuration terminée",
|
||||
"completed_description": "<p>Vous pouvez désormais vous connectez avec le compte utilisateur suivant :<br/><br/>Identifiant : <b>{email}</b><br/>Mot de passe : <b>{password}<b/></p>",
|
||||
"start": "Commencer",
|
||||
"copy_password_dialog": "Oui, vous devez copier le mot de passe !"
|
||||
"copy_password_dialog": "Oui, vous devez copier le mot de passe !",
|
||||
"https_warning": "Vous consultez le site en HTTP, n'oubliez pas de modifier la valeur de baseurl dans config.json si vous passez en HTTPS !"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,7 +86,10 @@
|
|||
"url": "URL",
|
||||
"theme": "Decorado",
|
||||
"import": "Importar",
|
||||
"tags": "Cancelos"
|
||||
"tags": "Cancelos",
|
||||
"close": "Pechar",
|
||||
"blobs": "Blobs",
|
||||
"collections": "Coleccións"
|
||||
},
|
||||
"recover": {
|
||||
"not_valid_code": "Algo fallou."
|
||||
|
@ -223,7 +226,19 @@
|
|||
"new_announcement": "Novo anuncio",
|
||||
"smtp_description": "<ul><li>Admin debería recibir un email cando se engade un evento anónimo (se está activo)</li><li>Admin debería recibir un email coas solicitudes de rexistro (se activo).</li><li>A usuaria debería recibir un email coa solicitude de rexistro.</li><li>A usuaria debería recibir un email confirmando o rexistro.</li><li>A usuaria debería recibir un email de confirmación cando fose subscrita directamente por Admin</li><li>As usuarias deberían recibir un email para restablecer o contrasinal se o esquecen</li></ul>",
|
||||
"wrong_domain_warning": "O url base configurado en config.json <b>({baseurl)</b> é diferente ao que estás a visitar <b>({url})</b>",
|
||||
"event_remove_ok": "Evento eliminado"
|
||||
"event_remove_ok": "Evento eliminado",
|
||||
"edit_blob": "Editar Blob",
|
||||
"new_blob": "Novo blob",
|
||||
"blobs_description": "Blobs son agrupamentos de eventos por etiquetas e lugares. Mostraranse na páxina de inicio",
|
||||
"smtp_port": "Porto SMTP",
|
||||
"smtp_secure": "SMTP Seguro (TLS ou STARTTLS)",
|
||||
"new_collection": "Nova colección",
|
||||
"edit_collection": "Editar Colección",
|
||||
"disable_admin_user_confirm": "Tes certeza de querer retirar os permisos de administración a {user}?",
|
||||
"sender_email": "Remitente",
|
||||
"collections_description": "As coleccións son agrupamentos de eventos por etiqueta ou lugar. Pode ser mostrado na páxina de inicio",
|
||||
"enable_admin_user_confirm": "Tes a certeza de querer darlle permisos de administración a {user}?",
|
||||
"smtp_use_sendmail": "Usar sendmail"
|
||||
},
|
||||
"auth": {
|
||||
"not_confirmed": "Aínda non foi confirmado…",
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
module.exports = {
|
||||
it: 'Italiano',
|
||||
en: 'English',
|
||||
es: 'Español',
|
||||
ca: 'Català',
|
||||
pl: 'Polski',
|
||||
eu: 'Euskara',
|
||||
nb: 'Norwegian Bokmål',
|
||||
fr: 'Francais',
|
||||
de: 'Deutsch',
|
||||
es: 'Español',
|
||||
gl: 'Galego',
|
||||
it: 'Italiano',
|
||||
ca: 'Català',
|
||||
fr: 'Francais',
|
||||
nb: 'Norwegian Bokmål',
|
||||
sk: 'Slovak',
|
||||
pl: 'Polski'
|
||||
}
|
||||
|
|
|
@ -227,7 +227,7 @@
|
|||
"new_announcement": "Nuovo annuncio",
|
||||
"show_smtp_setup": "Impostazioni email",
|
||||
"widget": "Widget",
|
||||
"smtp_description": "<ul><li>L'amministratore riceve una email quando viene aggiunto un evento anonimo (se abilitati).</li><li>L'amministratore riceve le mail di richiesta di registrazione (se abilitata).</li><li>L persone ricevono una mail di conferma della registrazione richiesta.</li><li>Le persone ricevono le email della registrazione confermata.</li><li>Le persone ricevono una mail di avviso quando vengono registrate direttamente da admin.</li><li>Le persone ricevono la mail per modificare la password quando l'hanno dimenticata</li></ul>",
|
||||
"smtp_description": "<ul><li>L'amministratore riceve una email quando viene aggiunto un evento anonimo (se abilitati).</li><li>L'amministratore riceve le mail di richiesta di registrazione (se abilitata).</li><li>Le persone ricevono una mail di conferma della registrazione richiesta.</li><li>Le persone ricevono le email della registrazione confermata.</li><li>Le persone ricevono una mail di avviso quando vengono registrate direttamente da admin.</li><li>Le persone ricevono la mail per modificare la password quando l'hanno dimenticata</li></ul>",
|
||||
"smtp_hostname": "SMTP Hostname",
|
||||
"smtp_test_success": "Una mail di test è stata inviata all'indirizzo {admin_email}, controlla la tua casella di posta",
|
||||
"smtp_test_button": "Invia una mail di prova",
|
||||
|
|
133
locales/sk.json
Normal file
133
locales/sk.json
Normal file
|
@ -0,0 +1,133 @@
|
|||
{
|
||||
"common": {
|
||||
"media": "Médiá",
|
||||
"login": "Login",
|
||||
"email": "E-mail",
|
||||
"password": "Heslo",
|
||||
"register": "Registrovať sa",
|
||||
"description": "Popis",
|
||||
"remove": "Odstrániť",
|
||||
"activate": "Zapnúť",
|
||||
"save": "Uložiť",
|
||||
"preview": "Náhľad",
|
||||
"logout": "Odhlásiť sa",
|
||||
"share": "Zdielať",
|
||||
"name": "Meno",
|
||||
"associate": "Spojiť",
|
||||
"edit_event": "Upraviť podujatie",
|
||||
"related": "Príbuzné",
|
||||
"add": "Pridať",
|
||||
"logout_ok": "Odhlásený",
|
||||
"copy": "Kopírovať",
|
||||
"recover_password": "Obnoviť heslo",
|
||||
"new_password": "Nové heslo",
|
||||
"new_user": "Nový používateľ",
|
||||
"ok": "Ok",
|
||||
"cancel": "Zrušiť",
|
||||
"enable": "Povoliť",
|
||||
"disable": "Zakázať",
|
||||
"me": "Ty",
|
||||
"password_updated": "Heslo zmenené.",
|
||||
"resources": "Zdroje",
|
||||
"n_resources": "žiaden zdroj|zdroj|{n} zdrojov",
|
||||
"activate_user": "Potvrdený",
|
||||
"displayname": "Zobrazovacie meno",
|
||||
"federation": "Federácia",
|
||||
"set_password": "Nastaviť heslo",
|
||||
"copy_link": "Kopírovať linku",
|
||||
"send_via_mail": "Poslať e-mail",
|
||||
"add_to_calendar": "Pridať do kalendára",
|
||||
"embed_help": "Skopírujte následujúci kód do vašej web stránky a zobrazí sa ako tu",
|
||||
"feed": "RSS Zdroj",
|
||||
"feed_url_copied": "Otvorte skopírovné zdrojové URL vo vašej RSS čítačke",
|
||||
"follow_me_title": "Sledujte novinky od fediverse",
|
||||
"follow": "Sledovať",
|
||||
"moderation": "Moderovanie",
|
||||
"user": "Užívateľ",
|
||||
"authorize": "Autorizovať",
|
||||
"title": "Nadpis",
|
||||
"filter": "Filter",
|
||||
"event": "Podujatie",
|
||||
"pause": "Pauza",
|
||||
"start": "Štart",
|
||||
"fediverse": "Fediverse",
|
||||
"skip": "Preskočiť",
|
||||
"delete": "Odstrániť",
|
||||
"announcements": "Oznámenia",
|
||||
"url": "URL",
|
||||
"tags": "Nálepky",
|
||||
"theme": "Téma",
|
||||
"reset": "Reset",
|
||||
"import": "Import",
|
||||
"max_events": "N. maximum podujatí",
|
||||
"label": "Označenie",
|
||||
"collections": "Kolekcia",
|
||||
"close": "Zavrieť",
|
||||
"what": "Čo",
|
||||
"info": "Info",
|
||||
"when": "Kedy",
|
||||
"send": "Poslať",
|
||||
"add_event": "Pridať podujatie",
|
||||
"export": "Export",
|
||||
"where": "Kde",
|
||||
"address": "Adresa",
|
||||
"hide": "Schovať",
|
||||
"search": "Vyhľadať",
|
||||
"events": "Podujatia",
|
||||
"next": "Ďalší",
|
||||
"admin": "Admin",
|
||||
"users": "Užívatielia",
|
||||
"edit": "Upraviť",
|
||||
"confirm": "Potvrdiť",
|
||||
"places": "Lokality",
|
||||
"settings": "Možnosti",
|
||||
"actions": "Akcie",
|
||||
"deactivate": "Vypnúť",
|
||||
"remove_admin": "Odstrániť admina",
|
||||
"place": "Lokácia",
|
||||
"copied": "Skopírované",
|
||||
"embed": "Vložiť",
|
||||
"instances": "Inštancie",
|
||||
"embed_title": "Vložiť toto podujatie na tvoju web stránku"
|
||||
},
|
||||
"login": {
|
||||
"description": "Po prihlásení sa môžete publikovat nové podujatia.",
|
||||
"check_email": "Skontrolujte si svoju doručenú poštu a spam.",
|
||||
"not_registered": "Nie ste zaregistrovaný?",
|
||||
"forgot_password": "Zabudli ste heslo?",
|
||||
"error": "Nepodarilo sa prihlásiť. Skontrolujte si prihlasovacie údaje.",
|
||||
"insert_email": "Vložte svoju e-mailovú adresu",
|
||||
"ok": "Prihlásený"
|
||||
},
|
||||
"recover": {
|
||||
"not_valid_code": "Niečo sa pokazilo."
|
||||
},
|
||||
"export": {
|
||||
"intro": "Narozdieľ od nesociálnych sietí ktoré robia všetko preto aby si udržali uživateľov a dáta o nich, my veríme v to že informácie, tak ako ľudia, musia byť voľné. Preto môžete zostať v obraze o podujatiach ktoré vás zaujímaju bez toho aby ste nutne išli cez túto stránku.",
|
||||
"insert_your_address": "Vložte svoju e-mailovú adresu",
|
||||
"email_description": "Môžete dostávať podujatia ktoré vás zaujímajú cez e-mail.",
|
||||
"feed_description": "Aby ste sledovali zmeny z počítača alebo smartphonu bez nutnosti pravidelne otvárať túto stránku, použite RSS zdroje. </p>\n\n<p> S RSS zdrojmi používate špeciálnu appku na príjem aktualít zo stránok ktoré vás zaujímajú. Je to dobrý spôsob akým môžte sledovať viacero stranok naraz bez toho aby ste si museli vytvárať účet, či iných komplikácií. </p>\n\n<li> Ak máte Android, tak odporúčame <a href=\"https://f-droid.org/en/packages/net.frju.flym/\">Flym</a> alebo Feeder </li>\n<li> Pre iPhone / iPad môžete použiť <a href=\"https://itunes.apple.com/ua/app/feeds4u/id1038456442?mt=8\"> Feed4U </a> </li>\n<li> Pre desktop / laptop odporúčame Feedbro, ktorý môžete nainštalovať do <a href=\"https://addons.mozilla.org/en-GB/firefox/addon/feedbroreader/\"> Firefoxu </a> alebo <a href=\"https://chrome.google.com/webstore/detail/feedbro/mefgmmbdailogpfhfblcnnjfmnpnmdfa\"> Chrome </a>. </li>\n<br/>\nPridaním tejto linky do vášej čítačky RSS zdrojov vás bude držaj v obraze."
|
||||
},
|
||||
"register": {
|
||||
"error": "Chyba: ",
|
||||
"complete": "Registrácia musí byť potvrdená.",
|
||||
"first_user": "Administrátor bol vytvorený"
|
||||
},
|
||||
"event": {
|
||||
"anon": "Anonym",
|
||||
"same_day": "v ten istý deň",
|
||||
"what_description": "Nadpis",
|
||||
"description_description": "Popis",
|
||||
"tag_description": "Nálepka",
|
||||
"media_description": "Môžete pridať leták (voliteľné)",
|
||||
"added": "Podujatie pridané",
|
||||
"saved": "Podujatie uložené",
|
||||
"added_anon": "Podujatie pridané, ale ešte musí byť potvrdené.",
|
||||
"updated": "Podujatie upravené",
|
||||
"where_description": "Kde je podujatie? Ak nie je prítomné tak to môžte vytvorit.",
|
||||
"confirmed": "Podujatie potvrdené",
|
||||
"not_found": "Nenašlo sa podujatie",
|
||||
"remove_confirmation": "Ste si istý že chcete odstrániť toto podujatie?",
|
||||
"recurrent": "Opakovaný"
|
||||
}
|
||||
}
|
|
@ -13,7 +13,6 @@ module.exports = {
|
|||
{ charset: 'utf-8' },
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
|
||||
],
|
||||
link: [{ rel: 'icon', type: 'image/png', href: config.baseurl + '/logo.png' }],
|
||||
script: [{ src: '/gancio-events.es.js', async: true, body: true }],
|
||||
},
|
||||
dev: isDev,
|
||||
|
@ -65,9 +64,13 @@ module.exports = {
|
|||
],
|
||||
routes: async () => {
|
||||
if (config.status === 'READY') {
|
||||
const Event = require('./server/api/models/event')
|
||||
const events = await Event.findAll({where: { is_visible: true }})
|
||||
return events.map(e => `/event/${e.slug}`)
|
||||
try {
|
||||
const Event = require('./server/api/models/event')
|
||||
const events = await Event.findAll({where: { is_visible: true }})
|
||||
return events.map(e => `/event/${e.slug}`)
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
|
|
53
package.json
53
package.json
|
@ -1,13 +1,15 @@
|
|||
{
|
||||
"name": "gancio",
|
||||
"version": "1.5.0-rc.5",
|
||||
"version": "1.5.0",
|
||||
"description": "A shared agenda for local communities",
|
||||
"author": "lesion",
|
||||
"scripts": {
|
||||
"build": "nuxt build --modern",
|
||||
"start:inspect": "NODE_ENV=production node --inspect node_modules/.bin/nuxt start --modern",
|
||||
"dev": "nuxt dev",
|
||||
"test": "cd tests/seeds; jest ; cd ../..",
|
||||
"test-sqlite": "export NODE_ENV=test; export DB=sqlite; jest --bail=1",
|
||||
"test-mariadb": "export NODE_ENV=test; export DB=mariadb; jest --bail=1",
|
||||
"test-postgresql": "export NODE_ENV=test; export DB=postgresql; jest --bail=1",
|
||||
"start": "nuxt start --modern",
|
||||
"doc": "cd docs && bundle exec jekyll b",
|
||||
"doc:dev": "cd docs && bundle exec jekyll s --drafts",
|
||||
|
@ -27,8 +29,11 @@
|
|||
".nuxt/",
|
||||
"yarn.lock"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14 <=16"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdi/js": "^6.7.96",
|
||||
"@mdi/js": "^7.0.96",
|
||||
"@nuxtjs/auth": "^4.9.1",
|
||||
"@nuxtjs/axios": "^5.13.5",
|
||||
"@nuxtjs/sitemap": "^2.4.0",
|
||||
|
@ -38,53 +43,61 @@
|
|||
"body-parser": "^1.20.0",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "^2.8.5",
|
||||
"dayjs": "^1.11.3",
|
||||
"dompurify": "^2.3.8",
|
||||
"dayjs": "^1.11.4",
|
||||
"dompurify": "^2.3.10",
|
||||
"email-templates": "^8.0.9",
|
||||
"express": "^4.18.1",
|
||||
"express-oauth-server": "lesion/express-oauth-server#master",
|
||||
"http-signature": "^1.3.6",
|
||||
"https-proxy-agent": "^5.0.1",
|
||||
"ical.js": "^1.5.0",
|
||||
"ics": "^2.35.0",
|
||||
"jsdom": "^19.0.0",
|
||||
"ics": "^2.37.0",
|
||||
"jsdom": "^20.0.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"linkify-html": "^3.0.4",
|
||||
"linkifyjs": "3.0.5",
|
||||
"lodash": "^4.17.21",
|
||||
"mariadb": "^3.0.0",
|
||||
"mariadb": "^3.0.1",
|
||||
"microformat-node": "^2.0.1",
|
||||
"minify-css-string": "^1.0.0",
|
||||
"mkdirp": "^1.0.4",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"nuxt-edge": "^2.16.0-27358576.777a4b7f",
|
||||
"mysql2": "^2.3.3",
|
||||
"nuxt-edge": "2.16.0-27616340.013f051b",
|
||||
"pg": "^8.6.0",
|
||||
"sequelize": "^6.20.1",
|
||||
"sequelize": "^6.21.3",
|
||||
"sequelize-slugify": "^1.6.1",
|
||||
"sharp": "^0.27.2",
|
||||
"sqlite3": "^5.0.8",
|
||||
"sqlite3": "^5.0.11",
|
||||
"tiptap": "^1.32.0",
|
||||
"tiptap-extensions": "^1.35.0",
|
||||
"umzug": "^2.3.0",
|
||||
"v-calendar": "^2.4.1",
|
||||
"vue": "^2.6.14",
|
||||
"vue": "2.7.8",
|
||||
"vue-i18n": "^8.26.7",
|
||||
"vue-template-compiler": "^2.6.14",
|
||||
"vuetify": "2.6.6",
|
||||
"winston": "^3.7.2",
|
||||
"vue-template-compiler": "2.7.8",
|
||||
"vuetify": "2.6.8",
|
||||
"winston": "^3.8.1",
|
||||
"winston-daily-rotate-file": "^4.7.1",
|
||||
"yargs": "^17.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxtjs/vuetify": "^1.12.3",
|
||||
"jest": "^28.1.1",
|
||||
"prettier": "^2.6.2",
|
||||
"jest": "^28.1.3",
|
||||
"prettier": "^2.7.1",
|
||||
"pug": "^3.0.2",
|
||||
"pug-plain-loader": "^1.1.0",
|
||||
"sass": "^1.52.2",
|
||||
"sass": "^1.53.0",
|
||||
"sequelize-cli": "^6.3.0",
|
||||
"supertest": "^6.2.2",
|
||||
"supertest": "^6.2.4",
|
||||
"webpack": "4",
|
||||
"webpack-cli": "^4.7.2"
|
||||
"webpack-cli": "^4.10.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"nth-check": "^2.0.1",
|
||||
"express-oauth-server/**/lodash": "^4.17.21",
|
||||
"glob-parent": "^5.1.2",
|
||||
"moment": "^2.29.2"
|
||||
},
|
||||
"bin": {
|
||||
"gancio": "server/cli.js"
|
||||
|
|
|
@ -1,58 +1,57 @@
|
|||
<template lang="pug">
|
||||
v-container.container.pa-0.pa-md-3
|
||||
v-card
|
||||
v-alert(v-if='url!==settings.baseurl' outlined type='warning' color='red' show-icon :icon='mdiAlert')
|
||||
span(v-html="$t('admin.wrong_domain_warning', { url, baseurl: settings.baseurl })")
|
||||
v-tabs(v-model='selectedTab' show-arrows :next-icon='mdiChevronRight' :prev-icon='mdiChevronLeft')
|
||||
v-container.container.pa-0.pa-md-3
|
||||
v-card
|
||||
v-alert(v-if='url!==settings.baseurl' outlined type='warning' color='red' show-icon :icon='mdiAlert')
|
||||
span(v-html="$t('admin.wrong_domain_warning', { url, baseurl: settings.baseurl })")
|
||||
v-tabs(v-model='selectedTab' show-arrows :next-icon='mdiChevronRight' :prev-icon='mdiChevronLeft')
|
||||
|
||||
//- SETTINGS
|
||||
v-tab {{$t('common.settings')}}
|
||||
v-tab-item
|
||||
Settings
|
||||
//- SETTINGS
|
||||
v-tab {{$t('common.settings')}}
|
||||
v-tab-item
|
||||
Settings
|
||||
|
||||
//- THEME
|
||||
v-tab {{$t('common.theme')}}
|
||||
v-tab-item
|
||||
Theme
|
||||
//- THEME
|
||||
v-tab {{$t('common.theme')}}
|
||||
v-tab-item
|
||||
Theme
|
||||
|
||||
//- USERS
|
||||
v-tab
|
||||
v-badge(:value='!!unconfirmedUsers.length' :content='unconfirmedUsers.length') {{$t('common.users')}}
|
||||
v-tab-item
|
||||
Users(:users='users' @update='updateUsers')
|
||||
//- USERS
|
||||
v-tab
|
||||
v-badge(:value='!!unconfirmedUsers.length' :content='unconfirmedUsers.length') {{$t('common.users')}}
|
||||
v-tab-item
|
||||
Users(:users='users' @update='updateUsers')
|
||||
|
||||
//- PLACES
|
||||
v-tab {{$t('common.places')}}
|
||||
v-tab-item
|
||||
Places
|
||||
//- PLACES
|
||||
v-tab {{$t('common.places')}}
|
||||
v-tab-item
|
||||
Places
|
||||
|
||||
//- Collections
|
||||
v-tab {{$t('common.collections')}}
|
||||
v-tab-item
|
||||
Collections
|
||||
//- Collections
|
||||
v-tab {{$t('common.collections')}}
|
||||
v-tab-item
|
||||
Collections
|
||||
|
||||
//- EVENTS
|
||||
v-tab
|
||||
v-badge(:value='!!unconfirmedEvents.length' :content='unconfirmedEvents.length') {{$t('common.events')}}
|
||||
v-tab-item
|
||||
Events(:unconfirmedEvents='unconfirmedEvents'
|
||||
@confirmed='id => { unconfirmedEvents = unconfirmedEvents.filter(e => e.id !== id)}')
|
||||
//- EVENTS
|
||||
v-tab
|
||||
v-badge(:value='!!unconfirmedEvents.length' :content='unconfirmedEvents.length') {{$t('common.events')}}
|
||||
v-tab-item
|
||||
Events(:unconfirmedEvents='unconfirmedEvents'
|
||||
@confirmed='id => { unconfirmedEvents = unconfirmedEvents.filter(e => e.id !== id)}')
|
||||
|
||||
//- ANNOUNCEMENTS
|
||||
v-tab {{$t('common.announcements')}}
|
||||
v-tab-item
|
||||
Announcement
|
||||
//- ANNOUNCEMENTS
|
||||
v-tab {{$t('common.announcements')}}
|
||||
v-tab-item
|
||||
Announcement
|
||||
|
||||
//- FEDERATION
|
||||
v-tab {{$t('common.federation')}}
|
||||
v-tab-item
|
||||
Federation
|
||||
|
||||
//- MODERATION
|
||||
v-tab(v-if='settings.enable_federation') {{$t('common.moderation')}}
|
||||
v-tab-item
|
||||
Moderation
|
||||
//- FEDERATION
|
||||
v-tab {{$t('common.federation')}}
|
||||
v-tab-item
|
||||
Federation
|
||||
|
||||
//- MODERATION
|
||||
v-tab(v-if='settings.enable_federation') {{$t('common.moderation')}}
|
||||
v-tab-item
|
||||
Moderation
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
<template lang='pug'>
|
||||
.d-flex.justify-space-around
|
||||
v-card.mt-5(max-width='600px')
|
||||
v-card-title {{settings.title}} - {{$t('common.authorize')}}
|
||||
v-card-text
|
||||
u {{$auth.user.email}}
|
||||
div
|
||||
p(v-html="$t('oauth.authorization_request', { app: client.name, instance_name: settings.title })")
|
||||
ul.mb-2
|
||||
li(v-for="s in scope.split(' ')") {{$t(`oauth.scopes.${scope}`)}}
|
||||
span(v-html="$t('oauth.redirected_to', {url: $route.query.redirect_uri})")
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(color='error' to='/') {{$t('common.cancel')}}
|
||||
v-btn(:href='authorizeURL' color='success') {{$t('common.authorize')}}
|
||||
.d-flex.justify-space-around
|
||||
v-card.mt-5(max-width='600px')
|
||||
v-card-title {{settings.title}} - {{$t('common.authorize')}}
|
||||
v-card-text
|
||||
u {{$auth.user.email}}
|
||||
div
|
||||
p(v-html="$t('oauth.authorization_request', { app: client.name, instance_name: settings.title })")
|
||||
ul.mb-2
|
||||
li(v-for="s in scope.split(' ')") {{$t(`oauth.scopes.${scope}`)}}
|
||||
span(v-html="$t('oauth.redirected_to', {url: $route.query.redirect_uri})")
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(color='error' to='/') {{$t('common.cancel')}}
|
||||
v-btn(:href='authorizeURL' color='success') {{$t('common.authorize')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
|
@ -1,40 +1,40 @@
|
|||
<template lang='pug'>
|
||||
v-container.pa-0.pa-md-3
|
||||
v-row.mt-md-5.ma-0(align='center' justify='center')
|
||||
v-col.pa-0.pa-md-3(cols='12' md="6" lg="5" xl="4")
|
||||
v-form(v-model='valid' ref='form' lazy-validation @submit.prevent='submit')
|
||||
v-card
|
||||
v-card-title {{$t('common.login')}}
|
||||
v-card-subtitle(v-text="$t('login.description')")
|
||||
v-container.pa-0.pa-md-3
|
||||
v-row.mt-md-5.ma-0(align='center' justify='center')
|
||||
v-col.pa-0.pa-md-3(cols='12' md="6" lg="5" xl="4")
|
||||
v-form(v-model='valid' ref='form' lazy-validation @submit.prevent='submit')
|
||||
v-card
|
||||
v-card-title {{$t('common.login')}}
|
||||
v-card-subtitle(v-text="$t('login.description')")
|
||||
|
||||
v-card-text
|
||||
v-text-field(v-model='email' type='email'
|
||||
validate-on-blur
|
||||
:rules='$validators.email' autofocus
|
||||
:label='$t("common.email")'
|
||||
ref='email')
|
||||
v-card-text
|
||||
v-text-field(v-model='email' type='email'
|
||||
validate-on-blur
|
||||
:rules='$validators.email' autofocus
|
||||
:label='$t("common.email")'
|
||||
ref='email')
|
||||
|
||||
v-text-field(v-model='password'
|
||||
:rules='$validators.password'
|
||||
type='password'
|
||||
:label='$t("common.password")')
|
||||
v-text-field(v-model='password'
|
||||
:rules='$validators.password'
|
||||
type='password'
|
||||
:label='$t("common.password")')
|
||||
|
||||
v-card-actions
|
||||
v-btn(text
|
||||
tabindex="1"
|
||||
@click='forgot' small) {{$t('login.forgot_password')}}
|
||||
v-card-actions
|
||||
v-btn(text
|
||||
tabindex="1"
|
||||
@click='forgot' small) {{$t('login.forgot_password')}}
|
||||
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-card-actions
|
||||
v-spacer
|
||||
|
||||
v-btn(v-if='settings.allow_registration'
|
||||
to='/register'
|
||||
text
|
||||
color='orange') {{$t('login.not_registered')}}
|
||||
v-btn(v-if='settings.allow_registration'
|
||||
to='/register'
|
||||
text
|
||||
color='orange') {{$t('login.not_registered')}}
|
||||
|
||||
v-btn(color='success'
|
||||
type='submit'
|
||||
:disabled='!valid || loading' :loading='loading') {{$t('common.login')}}
|
||||
v-btn(color='success'
|
||||
type='submit' outlined
|
||||
:disabled='!valid || loading' :loading='loading') {{$t('common.login')}}
|
||||
|
||||
</template>
|
||||
|
||||
|
@ -51,9 +51,7 @@ export default {
|
|||
valid: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['settings'])
|
||||
},
|
||||
computed: mapState(['settings']),
|
||||
methods: {
|
||||
async forgot () {
|
||||
if (!this.email) {
|
||||
|
|
|
@ -25,7 +25,7 @@ v-container.pa-0.pa-md-3
|
|||
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(@click='register'
|
||||
v-btn(@click='register' outlined
|
||||
:disabled='!valid || loading' :loading='loading'
|
||||
color='primary') {{$t('common.send')}}
|
||||
v-icon(v-text='mdiChevronRight')
|
||||
|
@ -53,10 +53,6 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
...mapState(['settings'])
|
||||
// disabled () {
|
||||
// if (process.server) { return false }
|
||||
// return !this.user.password || !this.user.email || !this.user.description
|
||||
// }
|
||||
},
|
||||
mounted () {
|
||||
this.$refs.email.focus()
|
||||
|
@ -70,7 +66,7 @@ export default {
|
|||
const user = await this.$axios.$post('/user/register', this.user)
|
||||
// this is the first user registered
|
||||
const first_user = user && user.is_admin && user.is_active
|
||||
this.$root.$message(first_user ? 'register.first_user': 'register.complete')
|
||||
this.$root.$message(first_user ? 'register.first_user': 'register.complete', { color: 'success' })
|
||||
this.$router.replace('/')
|
||||
} catch (e) {
|
||||
const error = get(e, 'response.data.errors[0].message', String(e))
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<template lang="pug">
|
||||
v-container
|
||||
v-card
|
||||
v-card-text(v-if='$auth.user && $auth.user.is_admin')
|
||||
Editor(v-model='about' label="About")
|
||||
v-card-text(v-else v-html='about')
|
||||
v-card-actions(v-if='$auth.user && $auth.user.is_admin')
|
||||
v-spacer
|
||||
v-btn(color='primary' text
|
||||
@click='save') {{$t('common.save')}}
|
||||
v-container
|
||||
v-card
|
||||
v-card-text(v-if='$auth.user && $auth.user.is_admin')
|
||||
Editor.px-3.ma-0(v-model='about' label="About")
|
||||
v-card-text(v-else v-html='about')
|
||||
v-card-actions(v-if='$auth.user && $auth.user.is_admin')
|
||||
v-spacer
|
||||
v-btn(color='primary' text
|
||||
@click='save') {{$t('common.save')}}
|
||||
</template>
|
||||
<script>
|
||||
import Editor from '@/components/Editor'
|
||||
|
|
|
@ -1,71 +1,70 @@
|
|||
<template lang="pug">
|
||||
v-container.container.pa-0.pa-md-3
|
||||
v-card
|
||||
v-card-title
|
||||
h4 {{edit?$t('common.edit_event'):$t('common.add_event')}}
|
||||
v-spacer
|
||||
v-btn(link text color='primary' @click='openImportDialog=true')
|
||||
<v-icon v-text='mdiFileImport'></v-icon> {{$t('common.import')}}
|
||||
v-dialog(v-model='openImportDialog' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||
ImportDialog(@close='openImportDialog=false' @imported='eventImported')
|
||||
v-container.container.pa-0.pa-md-3
|
||||
v-card
|
||||
v-card-title
|
||||
h4 {{edit?$t('common.edit_event'):$t('common.add_event')}}
|
||||
v-spacer
|
||||
v-btn(link text color='primary' @click='openImportDialog=true')
|
||||
<v-icon v-text='mdiFileImport'></v-icon> {{$t('common.import')}}
|
||||
v-dialog(v-model='openImportDialog' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||
ImportDialog(@close='openImportDialog=false' @imported='eventImported')
|
||||
|
||||
v-card-text.px-0.px-xs-2
|
||||
v-form(v-model='valid' ref='form' lazy-validation)
|
||||
v-container
|
||||
v-row
|
||||
//- Not logged event
|
||||
v-col(v-if='!$auth.loggedIn' cols=12)
|
||||
p(v-html="$t('event.anon_description')")
|
||||
v-card-text.px-0.px-xs-2
|
||||
v-form(v-model='valid' ref='form' lazy-validation)
|
||||
v-container
|
||||
v-row
|
||||
//- Not logged event
|
||||
v-col(v-if='!$auth.loggedIn' cols=12)
|
||||
p(v-html="$t('event.anon_description')")
|
||||
|
||||
//- Title
|
||||
v-col(cols=12)
|
||||
v-text-field(
|
||||
@change='v => event.title = v'
|
||||
:value = 'event.title'
|
||||
:rules="[$validators.required('common.title')]"
|
||||
:prepend-icon='mdiFormatTitle'
|
||||
:label="$t('common.title')"
|
||||
autofocus
|
||||
ref='title')
|
||||
//- Title
|
||||
v-col(cols=12)
|
||||
v-text-field(
|
||||
@change='v => event.title = v'
|
||||
:value = 'event.title'
|
||||
:rules="[$validators.required('common.title')]"
|
||||
:prepend-icon='mdiFormatTitle'
|
||||
:label="$t('common.title')"
|
||||
autofocus
|
||||
ref='title')
|
||||
|
||||
//- Where
|
||||
v-col(cols=12)
|
||||
WhereInput(ref='where' v-model='event.place')
|
||||
//- Where
|
||||
v-col(cols=12)
|
||||
WhereInput(ref='where' v-model='event.place')
|
||||
|
||||
//- When
|
||||
DateInput(v-model='date' :event='event')
|
||||
//- Description
|
||||
v-col.px-0(cols='12')
|
||||
Editor.px-3.ma-0(
|
||||
:label="$t('event.description_description')"
|
||||
v-model='event.description'
|
||||
:placeholder="$t('event.description_description')"
|
||||
max-height='400px')
|
||||
//- When
|
||||
DateInput(v-model='date' :event='event')
|
||||
//- Description
|
||||
v-col.px-0(cols='12')
|
||||
Editor.px-3.ma-0(
|
||||
:label="$t('event.description_description')"
|
||||
v-model='event.description'
|
||||
:placeholder="$t('event.description_description')"
|
||||
max-height='400px')
|
||||
|
||||
//- MEDIA / FLYER / POSTER
|
||||
v-col(cols=12 md=6)
|
||||
MediaInput(v-model='event.media[0]' :event='event' @remove='event.media=[]')
|
||||
//- MEDIA / FLYER / POSTER
|
||||
v-col(cols=12 md=6)
|
||||
MediaInput(v-model='event.media[0]' :event='event' @remove='event.media=[]')
|
||||
|
||||
//- tags
|
||||
v-col(cols=12 md=6)
|
||||
v-combobox(v-model='event.tags'
|
||||
:prepend-icon="mdiTagMultiple"
|
||||
chips small-chips multiple deletable-chips hide-no-data hide-selected persistent-hint
|
||||
cache-items
|
||||
@input.native='searchTags'
|
||||
:delimiters="[',', ';', '#']"
|
||||
:items="tags"
|
||||
:menu-props="{ maxWidth: 400, eager: true }"
|
||||
:label="$t('common.tags')")
|
||||
template(v-slot:selection="{ item, on, attrs, selected, parent}")
|
||||
v-chip(v-bind="attrs" close :close-icon='mdiCloseCircle' @click:close='parent.selectItem(item)'
|
||||
:input-value="selected" label small) {{item}}
|
||||
</v-chip>
|
||||
//- tags
|
||||
v-col(cols=12 md=6)
|
||||
v-combobox(v-model='event.tags'
|
||||
:prepend-icon="mdiTagMultiple"
|
||||
chips small-chips multiple deletable-chips hide-no-data hide-selected persistent-hint
|
||||
cache-items
|
||||
@input.native='searchTags'
|
||||
:delimiters="[',', ';', '#']"
|
||||
:items="tags"
|
||||
:menu-props="{ maxWidth: 400, eager: true }"
|
||||
:label="$t('common.tags')")
|
||||
template(v-slot:selection="{ item, on, attrs, selected, parent}")
|
||||
v-chip(v-bind="attrs" close :close-icon='mdiCloseCircle' @click:close='parent.selectItem(item)'
|
||||
:input-value="selected" label small) {{item}}
|
||||
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(@click='done' :loading='loading' :disabled='!valid || loading'
|
||||
color='primary') {{edit?$t('common.save'):$t('common.send')}}
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(@click='done' :loading='loading' :disabled='!valid || loading' outlined
|
||||
color='primary') {{edit?$t('common.save'):$t('common.send')}}
|
||||
|
||||
</template>
|
||||
<script>
|
||||
|
@ -173,7 +172,7 @@ export default {
|
|||
}, 100),
|
||||
eventImported (event) {
|
||||
this.event = Object.assign(this.event, event)
|
||||
this.$refs.where.selectPlace({ name: event.place.name, create: true })
|
||||
this.$refs.where.selectPlace({ name: event.place.name || event.place, create: true })
|
||||
this.date = {
|
||||
recurrent: this.event.recurrent || null,
|
||||
from: new Date(dayjs.unix(this.event.start_datetime)),
|
||||
|
@ -202,7 +201,9 @@ export default {
|
|||
|
||||
if (this.event.media.length) {
|
||||
formData.append('image', this.event.media[0].image)
|
||||
// formData.append('image_url', this.event.media[0].url)
|
||||
if (this.event.media[0].url) {
|
||||
formData.append('image_url', this.event.media[0].url)
|
||||
}
|
||||
formData.append('image_name', this.event.media[0].name)
|
||||
formData.append('image_focalpoint', this.event.media[0].focalpoint)
|
||||
}
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
<template lang="pug">
|
||||
v-container
|
||||
v-card
|
||||
v-card-title {{announcement.title}}
|
||||
v-card-text(v-html='announcement.announcement')
|
||||
|
||||
v-container
|
||||
v-card
|
||||
v-card-title {{announcement.title}}
|
||||
v-card-text(v-html='announcement.announcement')
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'Announcement',
|
||||
asyncData ({ $axios, params, error, store }) {
|
||||
asyncData ({ params, error, store }) {
|
||||
try {
|
||||
const id = Number(params.id)
|
||||
const announcement = store.state.announcements.find(a => a.id === id)
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
<template lang="pug">
|
||||
nuxt-link.embed_event(:to='`/event/${event.slug || event.id}`' target='_blank' :class='{ withImg: event.media }')
|
||||
nuxt-link.embed_event(:to='`/event/${event.slug || event.id}`' target='_blank' :class='{ withImg: event.media }')
|
||||
|
||||
//- image
|
||||
img.float-left(:src='event | mediaURL("thumb")')
|
||||
.event-info
|
||||
//- title
|
||||
.date {{event|when}}<br/>
|
||||
h4 {{event.title}}
|
||||
//- image
|
||||
img.float-left(:src='event | mediaURL("thumb")')
|
||||
.event-info
|
||||
//- title
|
||||
.date {{event|when}}<br/>
|
||||
h4 {{event.title}}
|
||||
|
||||
//- date / place
|
||||
.date {{event.place.name}}
|
||||
//- date / place
|
||||
.date {{event.place.name}}
|
||||
</template>
|
||||
<script>
|
||||
|
||||
export default {
|
||||
layout: 'iframe',
|
||||
async asyncData ({ $axios, params, error, store }) {
|
||||
async asyncData ({ $axios, params, error }) {
|
||||
try {
|
||||
const event = await $axios.$get(`/event/${params.event_id}`)
|
||||
return { event }
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template lang="pug">
|
||||
List(:events="events" :title='title')
|
||||
<template>
|
||||
<List :events="events" :title='title'/>
|
||||
</template>
|
||||
<script>
|
||||
import List from '../../components/List'
|
||||
|
|
152
pages/export.vue
152
pages/export.vue
|
@ -1,83 +1,83 @@
|
|||
<template lang="pug">
|
||||
v-container.pa-0.pa-md-3
|
||||
v-card
|
||||
v-card-title {{$t('common.share')}}
|
||||
v-card-text
|
||||
p.text-body-1 {{$t('export.intro')}}
|
||||
v-row
|
||||
v-col(:md='2' :cols='12')
|
||||
v-card-title.py-0 {{$t('common.filter')}}
|
||||
v-col
|
||||
Search(
|
||||
:filters='filters'
|
||||
@update='f => filters = f')
|
||||
v-tabs(v-model='type' show-arrows :next-icon='mdiChevronRight' :prev-icon='mdiChevronLeft')
|
||||
v-container.pa-0.pa-md-3
|
||||
v-card
|
||||
v-card-title {{$t('common.share')}}
|
||||
v-card-text
|
||||
p.text-body-1 {{$t('export.intro')}}
|
||||
v-row
|
||||
v-col(:md='2' :cols='12')
|
||||
v-card-title.py-0 {{$t('common.filter')}}
|
||||
v-col
|
||||
Search(
|
||||
:filters='filters'
|
||||
@update='f => filters = f')
|
||||
v-tabs(v-model='type' show-arrows :next-icon='mdiChevronRight' :prev-icon='mdiChevronLeft')
|
||||
|
||||
//- TOFIX
|
||||
//- v-tab {{$t('common.email')}}
|
||||
//- v-tab-item
|
||||
v-card
|
||||
v-card-text
|
||||
p(v-html='$t(`export.email_description`)')
|
||||
v-switch.mt-0(inset :label="$t('notify_on_insert')")
|
||||
v-switch.mt-0(inset :label="$t('morning_notification')")
|
||||
v-text-field(v-model='notification.email' :placeholder="$t('export.insert_your_address')" ref='email')
|
||||
v-btn(slot='prepend' text color='primary' @click='add_notification') {{$t('common.send')}} <v-icon>mdi-email</v-icon>
|
||||
|
||||
v-tab {{$t('common.feed')}}
|
||||
v-tab-item
|
||||
v-card
|
||||
v-card-text
|
||||
p(v-html='$t(`export.feed_description`)')
|
||||
v-text-field(v-model='link' readonly)
|
||||
v-btn(slot='prepend' text color='primary' @click='clipboard(link)') {{$t("common.copy")}}
|
||||
v-icon.ml-1(v-text='mdiContentCopy')
|
||||
|
||||
v-tab ics/ical
|
||||
v-tab-item
|
||||
v-card
|
||||
v-card-text
|
||||
p(v-html='$t(`export.ical_description`)')
|
||||
v-text-field(v-model='link')
|
||||
v-btn(slot='prepend' text color='primary' @click='clipboard(link)') {{$t("common.copy")}}
|
||||
v-icon.ml-1(v-text='mdiContentCopy')
|
||||
|
||||
v-tab List
|
||||
v-tab-item
|
||||
v-card
|
||||
v-card-text
|
||||
p(v-html='$t(`export.list_description`)')
|
||||
|
||||
v-row
|
||||
v-col.col-12.col-lg-4
|
||||
v-text-field(v-model='list.title' :label='$t("common.title")')
|
||||
v-text-field(v-model='list.maxEvents' type='number' min='1' :label='$t("common.max_events")')
|
||||
v-switch(v-model='list.theme' inset true-value='dark' false-value='light' :label="$t('admin.is_dark')")
|
||||
v-switch(v-model='list.sidebar' inset true-value='true' false-value='false' :label="$t('admin.widget')")
|
||||
v-col.col-12.col-lg-8
|
||||
gancio-events(:baseurl='settings.baseurl'
|
||||
:maxlength='list.maxEvents && Number(list.maxEvents)'
|
||||
:title='list.title'
|
||||
:theme='list.theme'
|
||||
:places='filters.places.join(",")'
|
||||
:tags='filters.tags.join(",")'
|
||||
:show_recurrent='filters.show_recurrent'
|
||||
:sidebar="list.sidebar")
|
||||
v-alert.pa-5.my-4.blue-grey.darken-4.text-body-1.lime--text.text--lighten-3 <pre>{{code}}</pre>
|
||||
v-btn.float-end(text color='primary' @click='clipboard(code)') {{$t("common.copy")}}
|
||||
v-icon.ml-1(v-text='mdiContentCopy')
|
||||
|
||||
v-tab(v-if='settings.enable_federation') {{$t('common.fediverse')}}
|
||||
v-tab-item(v-if='settings.enable_federation')
|
||||
FollowMe
|
||||
//- TOFIX
|
||||
//- v-tab {{$t('common.email')}}
|
||||
//- v-tab.pt-1(label='calendar' name='calendar')
|
||||
//- v-tab-item
|
||||
v-card
|
||||
v-card-text
|
||||
p(v-html='$t(`export.email_description`)')
|
||||
v-switch.mt-0(inset :label="$t('notify_on_insert')")
|
||||
v-switch.mt-0(inset :label="$t('morning_notification')")
|
||||
v-text-field(v-model='notification.email' :placeholder="$t('export.insert_your_address')" ref='email')
|
||||
v-btn(slot='prepend' text color='primary' @click='add_notification') {{$t('common.send')}} <v-icon>mdi-email</v-icon>
|
||||
|
||||
v-tab {{$t('common.feed')}}
|
||||
v-tab-item
|
||||
v-card
|
||||
v-card-text
|
||||
p(v-html='$t(`export.feed_description`)')
|
||||
v-text-field(v-model='link' readonly)
|
||||
v-btn(slot='prepend' text color='primary' @click='clipboard(link)') {{$t("common.copy")}}
|
||||
v-icon.ml-1(v-text='mdiContentCopy')
|
||||
|
||||
v-tab ics/ical
|
||||
v-tab-item
|
||||
v-card
|
||||
v-card-text
|
||||
p(v-html='$t(`export.ical_description`)')
|
||||
v-text-field(v-model='link')
|
||||
v-btn(slot='prepend' text color='primary' @click='clipboard(link)') {{$t("common.copy")}}
|
||||
v-icon.ml-1(v-text='mdiContentCopy')
|
||||
|
||||
v-tab List
|
||||
v-tab-item
|
||||
v-card
|
||||
v-card-text
|
||||
p(v-html='$t(`export.list_description`)')
|
||||
|
||||
v-row
|
||||
v-col.col-12.col-lg-4
|
||||
v-text-field(v-model='list.title' :label='$t("common.title")')
|
||||
v-text-field(v-model='list.maxEvents' type='number' min='1' :label='$t("common.max_events")')
|
||||
v-switch(v-model='list.theme' inset true-value='dark' false-value='light' :label="$t('admin.is_dark')")
|
||||
v-switch(v-model='list.sidebar' inset true-value='true' false-value='false' :label="$t('admin.widget')")
|
||||
v-col.col-12.col-lg-8
|
||||
gancio-events(:baseurl='settings.baseurl'
|
||||
:maxlength='list.maxEvents && Number(list.maxEvents)'
|
||||
:title='list.title'
|
||||
:theme='list.theme'
|
||||
:places='filters.places.join(",")'
|
||||
:tags='filters.tags.join(",")'
|
||||
:show_recurrent='filters.show_recurrent'
|
||||
:sidebar="list.sidebar")
|
||||
v-alert.pa-5.my-4.blue-grey.darken-4.text-body-1.lime--text.text--lighten-3 <pre>{{code}}</pre>
|
||||
v-btn.float-end(text color='primary' @click='clipboard(code)') {{$t("common.copy")}}
|
||||
v-icon.ml-1(v-text='mdiContentCopy')
|
||||
|
||||
v-tab(v-if='settings.enable_federation') {{$t('common.fediverse')}}
|
||||
v-tab-item(v-if='settings.enable_federation')
|
||||
FollowMe
|
||||
//- TOFIX
|
||||
//- v-tab.pt-1(label='calendar' name='calendar')
|
||||
//- v-tab-item
|
||||
//- p(v-html='$t(`export.calendar_description`)')
|
||||
//- //- no-ssr
|
||||
//- Calendar.mb-1
|
||||
//- v-text-field.mb-1(type='textarea' v-model='script')
|
||||
//- el-button.float-right(plain type="primary" icon='el-icon-document') Copy
|
||||
//- p(v-html='$t(`export.calendar_description`)')
|
||||
//- //- no-ssr
|
||||
//- Calendar.mb-1
|
||||
//- v-text-field.mb-1(type='textarea' v-model='script')
|
||||
//- el-button.float-right(plain type="primary" icon='el-icon-document') Copy
|
||||
|
||||
</template>
|
||||
<script>
|
||||
|
|
|
@ -78,8 +78,8 @@ export default {
|
|||
visibleEvents () {
|
||||
const now = dayjs().unix()
|
||||
if (this.selectedDay) {
|
||||
const min = dayjs(this.selectedDay).startOf('day').unix()
|
||||
const max = dayjs(this.selectedDay).endOf('day').unix()
|
||||
const min = dayjs.tz(this.selectedDay).startOf('day').unix()
|
||||
const max = dayjs.tz(this.selectedDay).endOf('day').unix()
|
||||
return this.events.filter(e => (e.start_datetime <= max && e.start_datetime >= min))
|
||||
} else if (this.isCurrentMonth) {
|
||||
return this.events.filter(e => e.end_datetime ? e.end_datetime > now : e.start_datetime + 2 * 60 * 60 > now)
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template lang="pug">
|
||||
v-container
|
||||
v-card
|
||||
v-card-title.text-h5 {{$auth.user.email}}
|
||||
v-card-text
|
||||
p {{$t('settings.remove_account')}}
|
||||
v-btn.black--text(color='warning' @click='remove_account') {{$t('common.remove')}}
|
||||
v-container
|
||||
v-card
|
||||
v-card-title.text-h5 {{$auth.user.email}}
|
||||
v-card-text
|
||||
p {{$t('settings.remove_account')}}
|
||||
v-btn.black--text(color='warning' @click='remove_account') {{$t('common.remove')}}
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
|
|
@ -2,20 +2,20 @@
|
|||
v-container
|
||||
v-row.mt-5(align='center' justify='center')
|
||||
v-col(cols='12' md="6" lg="5" xl="4")
|
||||
v-card
|
||||
v-card-title {{$t('common.set_password')}}
|
||||
template(v-if='user')
|
||||
v-card-subtitle {{user.email}}
|
||||
v-card-text
|
||||
v-form
|
||||
v-form(ref='form' @submit.prevent='change_password')
|
||||
v-card
|
||||
v-card-title {{$t('common.set_password')}}
|
||||
template(v-if='user')
|
||||
v-card-subtitle {{user.email}}
|
||||
v-card-text
|
||||
v-text-field(type='password' v-model='new_password' :label="$t('common.new_password')" :rules='$validators.password' autofocus)
|
||||
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(text color="primary" :disabled='!new_password' @click='change_password') {{$t('common.send')}}
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(color="primary" type='submit' :disabled='!new_password' @click='change_password' outlined) {{$t('common.send')}}
|
||||
|
||||
v-card-text(v-else)
|
||||
v-alert.ma-5(type='error') {{$t('recover.not_valid_code')}}
|
||||
v-card-text(v-else)
|
||||
v-alert.ma-5(type='error') {{$t('recover.not_valid_code')}}
|
||||
|
||||
</template>
|
||||
<script>
|
||||
|
@ -36,10 +36,11 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
async change_password () {
|
||||
if (!this.$refs.form.validate()) return
|
||||
try {
|
||||
await this.$axios.$post('/user/recover_password', { recover_code: this.code, password: this.new_password })
|
||||
this.$root.$message('common.password_updated', { color: 'success' })
|
||||
this.$router.replace('/login')
|
||||
this.$router.push('/login')
|
||||
} catch (e) {
|
||||
this.$root.$message(e, { color: 'warning' })
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import utc from 'dayjs/plugin/utc'
|
|||
import timezone from 'dayjs/plugin/timezone'
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat'
|
||||
|
||||
|
||||
import 'dayjs/locale/it'
|
||||
import 'dayjs/locale/en'
|
||||
import 'dayjs/locale/es'
|
||||
|
@ -16,6 +15,7 @@ import 'dayjs/locale/nb'
|
|||
import 'dayjs/locale/fr'
|
||||
import 'dayjs/locale/de'
|
||||
import 'dayjs/locale/gl'
|
||||
import 'dayjs/locale/sk'
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
dayjs.extend(utc)
|
||||
|
@ -37,10 +37,10 @@ export default ({ app, store }) => {
|
|||
Vue.filter('url2host', url => url.match(/^https?:\/\/(.[^/:]+)/i)[1])
|
||||
Vue.filter('datetime', value => dayjs.tz(value).locale(locale).format('ddd, D MMMM HH:mm'))
|
||||
Vue.filter('dateFormat', (value, format) => dayjs.tz(value).format(format))
|
||||
Vue.filter('unixFormat', (timestamp, format) => dayjs.unix(timestamp).tz(instance_timezone).format(format))
|
||||
Vue.filter('unixFormat', (timestamp, format) => dayjs.unix(timestamp).tz().format(format))
|
||||
|
||||
// shown in mobile homepage
|
||||
Vue.filter('day', value => dayjs.unix(value).tz(instance_timezone).locale(store.state.locale).format('dddd, D MMM'))
|
||||
Vue.filter('day', value => dayjs.unix(value).tz().locale(store.state.locale).format('dddd, D MMM'))
|
||||
Vue.filter('mediaURL', (event, type, format = '.jpg') => {
|
||||
if (event.media && event.media.length) {
|
||||
if (type === 'alt') {
|
||||
|
@ -54,16 +54,16 @@ export default ({ app, store }) => {
|
|||
return ''
|
||||
})
|
||||
|
||||
Vue.filter('from', timestamp => dayjs.unix(timestamp).tz(instance_timezone).fromNow())
|
||||
Vue.filter('from', timestamp => dayjs.unix(timestamp).tz().fromNow())
|
||||
|
||||
Vue.filter('recurrentDetail', event => {
|
||||
const parent = event.parent
|
||||
const { frequency, type } = parent.recurrent
|
||||
let recurrent
|
||||
if (frequency === '1w' || frequency === '2w') {
|
||||
recurrent = app.i18n.t(`event.recurrent_${frequency}_days`, { days: dayjs.unix(parent.start_datetime).tz(instance_timezone).format('dddd') })
|
||||
recurrent = app.i18n.t(`event.recurrent_${frequency}_days`, { days: dayjs.unix(parent.start_datetime).tz().format('dddd') })
|
||||
} else if (frequency === '1m' || frequency === '2m') {
|
||||
const d = type === 'ordinal' ? dayjs.unix(parent.start_datetime).date() : dayjs.unix(parent.start_datetime).tz(instance_timezone).format('dddd')
|
||||
const d = type === 'ordinal' ? dayjs.unix(parent.start_datetime).date() : dayjs.unix(parent.start_datetime).tz().format('dddd')
|
||||
if (type === 'ordinal') {
|
||||
recurrent = app.i18n.t(`event.recurrent_${frequency}_days`, { days: d })
|
||||
} else {
|
||||
|
@ -75,8 +75,8 @@ export default ({ app, store }) => {
|
|||
})
|
||||
|
||||
Vue.filter('when', (event) => {
|
||||
const start = dayjs.unix(event.start_datetime).tz(instance_timezone)
|
||||
const end = dayjs.unix(event.end_datetime).tz(instance_timezone)
|
||||
const start = dayjs.unix(event.start_datetime).tz()
|
||||
const end = dayjs.unix(event.end_datetime).tz()
|
||||
|
||||
// const normal = `${start.format('dddd, D MMMM (HH:mm-')}${end.format('HH:mm) ')}`
|
||||
// // recurrent event
|
||||
|
@ -95,10 +95,10 @@ export default ({ app, store }) => {
|
|||
|
||||
// multidate
|
||||
if (event.multidate) {
|
||||
return `${start.format('ddd, D MMM HH:mm')} - ${end.format('ddd, D MMM HH:mm')}`
|
||||
return `${start.format('ddd, D MMM, HH:mm')} - ${end.format('ddd, D MMM, HH:mm')}`
|
||||
}
|
||||
|
||||
// normal event
|
||||
return `${start.format('ddd, D MMM HH:mm')} - ${end.format('HH:mm')}`
|
||||
return `${start.format('dddd, D MMMM, HH:mm')}-${end.format('HH:mm')}`
|
||||
})
|
||||
}
|
||||
|
|
|
@ -7,9 +7,11 @@ Vue.use(VueI18n)
|
|||
export default async ({ app, store, res }) => {
|
||||
const messages = {}
|
||||
if (process.server) {
|
||||
store.commit('setLocale', res.locals.acceptedLocale)
|
||||
if (res.locals.user_locale) {
|
||||
store.commit('setUserLocale', res.locals.user_locale)
|
||||
if (res.locals) {
|
||||
store.commit('setLocale', res.locals.acceptedLocale)
|
||||
if (res.locals.user_locale) {
|
||||
store.commit('setUserLocale', res.locals.user_locale)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ const Auth = {
|
|||
})
|
||||
},
|
||||
|
||||
isAuth (req, res, next) {
|
||||
isAuth (_req, res, next) {
|
||||
if (res.locals.user) {
|
||||
next()
|
||||
} else {
|
||||
|
|
|
@ -14,8 +14,7 @@ const collectionController = {
|
|||
const withFilters = req.query.withFilters
|
||||
let collections
|
||||
if (withFilters) {
|
||||
collections = await Collection.findAll({ include: [Filter] })
|
||||
|
||||
collections = await Collection.findAll({ include: [ Filter ] })
|
||||
} else {
|
||||
collections = await Collection.findAll()
|
||||
}
|
||||
|
@ -109,9 +108,14 @@ const collectionController = {
|
|||
}
|
||||
|
||||
// TODO: validation
|
||||
log.info('Create collection: ' + req.body.name)
|
||||
const collection = await Collection.create(collectionDetail)
|
||||
res.json(collection)
|
||||
log.info(`Create collection: ${req.body.name}`)
|
||||
try {
|
||||
const collection = await Collection.create(collectionDetail)
|
||||
res.json(collection)
|
||||
} catch (e) {
|
||||
log.error(`Create collection failed ${e}`)
|
||||
res.sendStatus(400)
|
||||
}
|
||||
},
|
||||
|
||||
async remove (req, res) {
|
||||
|
@ -122,7 +126,7 @@ const collectionController = {
|
|||
await collection.destroy()
|
||||
res.sendStatus(200)
|
||||
} catch (e) {
|
||||
log.error('Remove collection failed:', e)
|
||||
log.error('Remove collection failed:' + String(e))
|
||||
res.sendStatus(404)
|
||||
}
|
||||
},
|
||||
|
@ -148,9 +152,12 @@ const collectionController = {
|
|||
|
||||
async removeFilter (req, res) {
|
||||
const filter_id = req.params.id
|
||||
log.info('Remove filter', filter_id)
|
||||
log.info(`Remove filter ${filter_id}`)
|
||||
try {
|
||||
const filter = await Filter.findByPk(filter_id)
|
||||
if (!filter) {
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
await filter.destroy()
|
||||
res.sendStatus(200)
|
||||
} catch (e) {
|
||||
|
|
|
@ -391,7 +391,13 @@ const eventController = {
|
|||
let place
|
||||
if (body.place_id) {
|
||||
place = await Place.findByPk(body.place_id)
|
||||
if (!place) {
|
||||
return res.status(400).send(`Place not found`)
|
||||
}
|
||||
} else {
|
||||
if (!body.place_name) {
|
||||
return res.status(400).send(`Place not found`)
|
||||
}
|
||||
place = await Place.findOne({ where: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), Op.eq, body.place_name.trim().toLocaleLowerCase() )})
|
||||
if (!place) {
|
||||
if (!body.place_address || !body.place_name) {
|
||||
|
@ -422,14 +428,14 @@ const eventController = {
|
|||
}
|
||||
|
||||
let focalpoint = body.image_focalpoint ? body.image_focalpoint.split(',') : ['0', '0']
|
||||
focalpoint = [parseFloat(focalpoint[0]).toFixed(2), parseFloat(focalpoint[1]).toFixed(2)]
|
||||
focalpoint = [parseFloat(parseFloat(focalpoint[0]).toFixed(2)), parseFloat(parseFloat(focalpoint[1]).toFixed(2))]
|
||||
eventDetails.media = [{
|
||||
url: req.file.filename,
|
||||
height: req.file.height,
|
||||
width: req.file.width,
|
||||
name: body.image_name || body.title || '',
|
||||
size: req.file.size || 0,
|
||||
focalpoint: [parseFloat(focalpoint[0]), parseFloat(focalpoint[1])]
|
||||
focalpoint
|
||||
}]
|
||||
} else {
|
||||
eventDetails.media = []
|
||||
|
@ -474,8 +480,9 @@ const eventController = {
|
|||
},
|
||||
|
||||
async update (req, res) {
|
||||
if (res.locals.err) {
|
||||
return res.status(400).json(res.locals.err.toString())
|
||||
if (res.err) {
|
||||
log.warn(req.err)
|
||||
return res.status(400).json(req.err.toString())
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -490,9 +497,9 @@ const eventController = {
|
|||
const eventDetails = {
|
||||
title: body.title || event.title,
|
||||
// sanitize and linkify html
|
||||
description: helpers.sanitizeHTML(linkifyHtml(body.description, { target: '_blank' })) || event.description,
|
||||
description: helpers.sanitizeHTML(linkifyHtml(body.description || '', { target: '_blank' })) || event.description,
|
||||
multidate: body.multidate,
|
||||
start_datetime: body.start_datetime,
|
||||
start_datetime: body.start_datetime || event.start_datetime,
|
||||
end_datetime: body.end_datetime,
|
||||
recurrent
|
||||
}
|
||||
|
@ -511,27 +518,51 @@ const eventController = {
|
|||
|
||||
// modify associated media only if a new file is uploaded or remote image_url is used
|
||||
if (req.file || (body.image_url && /^https?:\/\//.test(body.image_url))) {
|
||||
if (body.image_url) {
|
||||
if (!req.file && body.image_url) {
|
||||
req.file = await helpers.getImageFromURL(body.image_url)
|
||||
}
|
||||
|
||||
const focalpoint = body.image_focalpoint ? body.image_focalpoint.split(',') : ['0', '0']
|
||||
let focalpoint = body.image_focalpoint ? body.image_focalpoint.split(',') : ['0', '0']
|
||||
focalpoint = [parseFloat(parseFloat(focalpoint[0]).toFixed(2)), parseFloat(parseFloat(focalpoint[1]).toFixed(2))]
|
||||
eventDetails.media = [{
|
||||
url: req.file.filename,
|
||||
height: req.file.height,
|
||||
width: req.file.width,
|
||||
name: body.image_name || body.title || '',
|
||||
size: req.file.size || 0,
|
||||
focalpoint: [parseFloat(focalpoint[0].slice(0, 6)), parseFloat(focalpoint[1].slice(0, 6))]
|
||||
focalpoint
|
||||
}]
|
||||
} else if (!body.image) {
|
||||
eventDetails.media = []
|
||||
} else if (body.image_focalpoint && event.media.length) {
|
||||
let focalpoint = body.image_focalpoint ? body.image_focalpoint.split(',') : ['0', '0']
|
||||
focalpoint = [parseFloat(parseFloat(focalpoint[0]).toFixed(2)), parseFloat(parseFloat(focalpoint[1]).toFixed(2))]
|
||||
eventDetails.media = [ { ...event.media[0], focalpoint } ] // [0].focalpoint = focalpoint
|
||||
}
|
||||
await event.update(eventDetails)
|
||||
const [place] = await Place.findOrCreate({
|
||||
where: { name: body.place_name },
|
||||
defaults: { address: body.place_address }
|
||||
})
|
||||
|
||||
// find or create the place
|
||||
let place
|
||||
if (body.place_id) {
|
||||
place = await Place.findByPk(body.place_id)
|
||||
if (!place) {
|
||||
return res.status(400).send(`Place not found`)
|
||||
}
|
||||
} else {
|
||||
if (!body.place_name) {
|
||||
return res.status(400).send(`Place not found`)
|
||||
}
|
||||
place = await Place.findOne({ where: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), Op.eq, body.place_name.trim().toLocaleLowerCase() )})
|
||||
if (!place) {
|
||||
if (!body.place_address || !body.place_name) {
|
||||
return res.status(400).send(`place_id or place_name and place_address required`)
|
||||
}
|
||||
place = await Place.create({
|
||||
name: body.place_name,
|
||||
address: body.place_address
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
await event.setPlace(place)
|
||||
|
||||
|
@ -542,7 +573,10 @@ const eventController = {
|
|||
await event.setTags(tags)
|
||||
}
|
||||
|
||||
const newEvent = await Event.findByPk(event.id, { include: [Tag, Place] })
|
||||
let newEvent = await Event.findByPk(event.id, { include: [Tag, Place] })
|
||||
newEvent = newEvent.get()
|
||||
newEvent.tags = tags.map(t => t.tag)
|
||||
newEvent.place = place
|
||||
res.json(newEvent)
|
||||
|
||||
// create recurrent instances of event if needed
|
||||
|
@ -633,11 +667,11 @@ const eventController = {
|
|||
if (tags && places) {
|
||||
where[Op.and] = [
|
||||
{ placeId: places ? places.split(',') : []},
|
||||
Sequelize.fn('EXISTS', Sequelize.literal(`SELECT 1 FROM event_tags WHERE ${Col('event_tags.eventId')}=event.id AND LOWER(${Col('tagTag')}) in (?)`))
|
||||
Sequelize.fn('EXISTS', Sequelize.literal(`SELECT 1 FROM event_tags WHERE ${Col('event_tags.eventId')}=${Col('event.id')} AND LOWER(${Col('tagTag')}) in (?)`))
|
||||
]
|
||||
replacements.push(tags)
|
||||
} else if (tags) {
|
||||
where[Op.and] = Sequelize.fn('EXISTS', Sequelize.literal(`SELECT 1 FROM event_tags WHERE ${Col('event_tags.eventId')}=event.id AND LOWER(${Col('tagTag')}) in (?)`))
|
||||
where[Op.and] = Sequelize.fn('EXISTS', Sequelize.literal(`SELECT 1 FROM event_tags WHERE ${Col('event_tags.eventId')}=${Col('event.id')} AND LOWER(${Col('tagTag')}) in (?)`))
|
||||
replacements.push(tags)
|
||||
} else if (places) {
|
||||
where.placeId = places.split(',')
|
||||
|
|
|
@ -54,7 +54,7 @@ const settingsController = {
|
|||
secretSettings: {},
|
||||
|
||||
async load () {
|
||||
if (config.status !== 'READY') {
|
||||
if (config.status !== 'CONFIGURED') {
|
||||
settingsController.settings = defaultSettings
|
||||
return
|
||||
}
|
||||
|
@ -171,6 +171,14 @@ const settingsController = {
|
|||
}
|
||||
},
|
||||
|
||||
getSMTPSettings (_req, res) {
|
||||
return res.json(settingsController['settings']['smtp'])
|
||||
},
|
||||
|
||||
getAll (_req, res) {
|
||||
return res.json(settingsController.settings)
|
||||
},
|
||||
|
||||
setLogo (req, res) {
|
||||
if (!req.file) {
|
||||
settingsController.set('logo', false)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const Tag = require('../models/tag')
|
||||
const Event = require('../models/event')
|
||||
const uniq = require('lodash/uniq')
|
||||
|
||||
const { where, fn, col, Op } = require('sequelize')
|
||||
const exportController = require('./export')
|
||||
|
@ -14,7 +15,7 @@ module.exports = {
|
|||
// search for already existing tags (tag is the same as TaG)
|
||||
const existingTags = await Tag.findAll({ where: { [Op.and]: where(fn('LOWER', col('tag')), { [Op.in]: lowercaseTags }) } })
|
||||
const lowercaseExistingTags = existingTags.map(t => t.tag.toLocaleLowerCase())
|
||||
const remainingTags = trimmedTags.filter(t => ! lowercaseExistingTags.includes(t.toLocaleLowerCase()))
|
||||
const remainingTags = uniq(trimmedTags.filter(t => ! lowercaseExistingTags.includes(t.toLocaleLowerCase())))
|
||||
|
||||
// create remaining tags (cannot use updateOnDuplicate or manage conflicts)
|
||||
return [].concat(
|
||||
|
|
|
@ -80,7 +80,6 @@ const userController = {
|
|||
if (!settingsController.settings.allow_registration) { return res.sendStatus(404) }
|
||||
const n_users = await User.count()
|
||||
try {
|
||||
req.body.recover_code = crypto.randomBytes(16).toString('hex')
|
||||
|
||||
// the first registered user will be an active admin
|
||||
if (n_users === 0) {
|
||||
|
@ -104,7 +103,7 @@ const userController = {
|
|||
res.sendStatus(200)
|
||||
} catch (e) {
|
||||
log.error('Registration error:', e)
|
||||
res.status(404).json(e)
|
||||
res.status(400).json(e)
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -138,8 +138,10 @@ if (config.status !== 'READY') {
|
|||
api.delete('/event/notification/:code', eventController.delNotification)
|
||||
|
||||
api.post('/settings', isAdmin, settingsController.setRequest)
|
||||
api.get('/settings', isAdmin, settingsController.getAll)
|
||||
api.post('/settings/logo', isAdmin, multer({ dest: config.upload_path }).single('logo'), settingsController.setLogo)
|
||||
api.post('/settings/smtp', isAdmin, settingsController.testSMTP)
|
||||
api.get('/settings/smtp', isAdmin, settingsController.getSMTPSettings)
|
||||
|
||||
// get unconfirmed events
|
||||
api.get('/event/unconfirmed', isAdmin, eventController.getUnconfirmed)
|
||||
|
|
|
@ -9,7 +9,7 @@ const locales = require('../../locales')
|
|||
const mail = {
|
||||
send (addresses, template, locals, locale) {
|
||||
locale = locale || settingsController.settings.instance_locale
|
||||
if (process.env.NODE_ENV === 'production' && (!settingsController.settings.admin_email || !settingsController.settings.smtp || !settingsController.settings.smtp.user)) {
|
||||
if (process.env.NODE_ENV === 'production' && (!settingsController.settings.admin_email || !settingsController.settings.smtp)) {
|
||||
log.error(`Cannot send any email: SMTP Email configuration not completed!`)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ const sequelize = require('./index').sequelize
|
|||
|
||||
class Collection extends Model {}
|
||||
|
||||
// TODO: slugify!
|
||||
Collection.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
|
|
|
@ -111,7 +111,7 @@ Event.prototype.toAP = function (username, locale, to = []) {
|
|||
tag: tags && tags.map(tag => ({
|
||||
type: 'Hashtag',
|
||||
name: '#' + tag,
|
||||
href: '/tags/' + tag
|
||||
href: `${config.baseurl}/tag/${tag}`
|
||||
})),
|
||||
published: dayjs(this.createdAt).utc().format(),
|
||||
attributedTo: `${config.baseurl}/federation/u/${username}`,
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
const Sequelize = require('sequelize')
|
||||
|
||||
// this is an hack: https://github.com/sequelize/sequelize/pull/14800
|
||||
const livePatchMariaDBDialect = require('sequelize/lib/dialects/mariadb/query')
|
||||
livePatchMariaDBDialect.prototype.handleJsonSelectQuery = () => null
|
||||
|
||||
const Umzug = require('umzug')
|
||||
const path = require('path')
|
||||
const config = require('../../config')
|
||||
|
@ -13,8 +18,8 @@ const db = {
|
|||
}
|
||||
},
|
||||
connect (dbConf = config.db) {
|
||||
dbConf.dialectOptions = { autoJsonMap: true }
|
||||
log.debug(`Connecting to DB: ${JSON.stringify(dbConf)}`)
|
||||
dbConf.dialectOptions = { autoJsonMap: false }
|
||||
if (dbConf.dialect === 'sqlite') {
|
||||
dbConf.retry = {
|
||||
match: [
|
||||
|
@ -30,8 +35,12 @@ const db = {
|
|||
return db.sequelize.authenticate()
|
||||
},
|
||||
async isEmpty () {
|
||||
const users = await db.sequelize.query('SELECT * from users').catch(e => {})
|
||||
return !(users && users.length)
|
||||
try {
|
||||
const users = await db.sequelize.query('SELECT * from users')
|
||||
return !(users && users.length)
|
||||
} catch (e) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
async runMigrations () {
|
||||
const logging = config.status !== 'READY' ? false : log.debug.bind(log)
|
||||
|
@ -53,7 +62,7 @@ const db = {
|
|||
return umzug.up()
|
||||
},
|
||||
async initialize () {
|
||||
if (config.status === 'READY') {
|
||||
if (config.status === 'CONFIGURED') {
|
||||
try {
|
||||
await db.connect()
|
||||
log.debug('Running migrations')
|
||||
|
|
|
@ -12,7 +12,7 @@ const oauthServer = new OAuthServer({
|
|||
debug: true,
|
||||
requireClientAuthentication: { password: false },
|
||||
authenticateHandler: {
|
||||
handle (req, res) {
|
||||
handle (_req, res) {
|
||||
if (!res.locals.user) {
|
||||
throw new Error('Not authenticated!')
|
||||
}
|
||||
|
|
|
@ -23,6 +23,12 @@ async function modify (args) {
|
|||
await user.save()
|
||||
console.log(`New password for user ${user.email} is '${password}'`)
|
||||
}
|
||||
|
||||
if (args.admin) {
|
||||
user.is_admin = true
|
||||
await user.save()
|
||||
}
|
||||
await db.close()
|
||||
}
|
||||
|
||||
async function create (args) {
|
||||
|
@ -71,13 +77,14 @@ const accountsCLI = yargs => yargs
|
|||
'reset-password': {
|
||||
describe: 'Resets the password of the given account ',
|
||||
type: 'boolean'
|
||||
}
|
||||
},
|
||||
admin: { describe: 'Define this account as administrator', type: 'boolean' }
|
||||
}, modify)
|
||||
.command('create <email|username>', 'Create an account', {
|
||||
.command('create <email|username> [admin]', 'Create an account', {
|
||||
admin: { describe: 'Define this account as administrator', type: 'boolean' }
|
||||
}, create)
|
||||
.positional('email', { describe: '', type: 'string', demandOption: true })
|
||||
.command('remove <email|username>', 'Remove an account', {}, remove)
|
||||
.command('remove <email|username>', 'Remove an account', {}, remove)
|
||||
.positional('email', { describe: 'account email or username', type: 'string', demandOption: true })
|
||||
.recommendCommands()
|
||||
.demandCommand(1, '')
|
||||
.argv
|
||||
|
|
|
@ -26,7 +26,7 @@ let config = {
|
|||
if (fs.existsSync(config_path)) {
|
||||
const configContent = fs.readFileSync(config_path)
|
||||
config = Object.assign(config, JSON.parse(configContent))
|
||||
config.status = 'READY'
|
||||
config.status = 'CONFIGURED'
|
||||
if (!config.hostname) {
|
||||
config.hostname = new URL.URL(config.baseurl).hostname
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ const settingsController = require('./api/controller/settings')
|
|||
const acceptLanguage = require('accept-language')
|
||||
const express = require('express')
|
||||
const dayjs = require('dayjs')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
dayjs.extend(timezone)
|
||||
|
||||
const config = require('./config')
|
||||
const log = require('./log')
|
||||
|
@ -19,7 +21,7 @@ const DOMPurify = require('dompurify')
|
|||
const { JSDOM } = require('jsdom')
|
||||
const { window } = new JSDOM('<!DOCTYPE html>')
|
||||
const domPurify = DOMPurify(window)
|
||||
const URL = require('url')
|
||||
const url = require('url')
|
||||
const locales = require('../locales')
|
||||
|
||||
domPurify.addHook('beforeSanitizeElements', node => {
|
||||
|
@ -30,7 +32,7 @@ domPurify.addHook('beforeSanitizeElements', node => {
|
|||
// remove FB tracking param
|
||||
if (href.includes('fbclid=')) {
|
||||
try {
|
||||
const url = new URL.URL(href)
|
||||
const url = new url.URL(href)
|
||||
url.searchParams.delete('fbclid')
|
||||
node.setAttribute('href', url.href)
|
||||
if (text.includes('fbclid=')) {
|
||||
|
@ -69,26 +71,19 @@ module.exports = {
|
|||
next()
|
||||
},
|
||||
|
||||
async initSettings (req, res, next) {
|
||||
async initSettings (_req, res, next) {
|
||||
// initialize settings
|
||||
res.locals.settings = cloneDeep(settingsController.settings)
|
||||
|
||||
if (res.locals.settings.smtp && res.locals.settings.smtp.auth) {
|
||||
if (res.locals.user && res.locals.user.is_admin) {
|
||||
delete res.locals.settings.smtp.auth.pass
|
||||
} else {
|
||||
delete res.locals.settings.smtp
|
||||
}
|
||||
}
|
||||
delete res.locals.settings.smtp
|
||||
delete res.locals.settings.publicKey
|
||||
res.locals.settings.baseurl = config.baseurl
|
||||
res.locals.settings.hostname = config.hostname
|
||||
res.locals.settings.title = res.locals.settings.title || config.title
|
||||
res.locals.settings.description = res.locals.settings.description || config.description
|
||||
res.locals.settings.version = pkg.version
|
||||
|
||||
// set user locale
|
||||
res.locals.user_locale = settingsController.user_locale[res.locals.acceptedLocale]
|
||||
dayjs.tz.setDefault(res.locals.settings.instance_timezone)
|
||||
next()
|
||||
},
|
||||
|
||||
|
@ -119,6 +114,8 @@ module.exports = {
|
|||
col (field) {
|
||||
if (config.db.dialect === 'postgres') {
|
||||
return '"' + field.split('.').join('"."') + '"'
|
||||
} else if (config.db.dialect === 'mariadb') {
|
||||
return '`' + field.split('.').join('`.`') + '`'
|
||||
} else {
|
||||
return field
|
||||
}
|
||||
|
@ -126,9 +123,6 @@ module.exports = {
|
|||
|
||||
async getImageFromURL (url) {
|
||||
log.debug(`getImageFromURL ${url}`)
|
||||
if(!/^https?:\/\//.test(url)) {
|
||||
throw Error('Hacking attempt?')
|
||||
}
|
||||
|
||||
const filename = crypto.randomBytes(16).toString('hex')
|
||||
const sharpStream = sharp({ failOnError: true })
|
||||
|
@ -176,18 +170,21 @@ module.exports = {
|
|||
}
|
||||
const events = data.items.map(e => {
|
||||
const props = e.properties
|
||||
const media = get(props, 'featured[0]')
|
||||
let media = get(props, 'featured[0]')
|
||||
if (media) {
|
||||
media = url.resolve(URL, media)
|
||||
}
|
||||
return {
|
||||
title: get(props, 'name[0]', ''),
|
||||
description: get(props, 'description[0]', ''),
|
||||
place: {
|
||||
name: get(props, 'location[0].properties.name', '') || get(props, 'location[0]'),
|
||||
address: get(props, 'location[0].properties.street-address')
|
||||
name: get(props, 'location[0].properties.name[0].value', '') || get(props, 'location[0].properties.name', '') || get(props, 'location[0]'),
|
||||
address: get(props, 'location[0].properties.street-address[0]') || get(props, 'location[0].properties.street-address')
|
||||
},
|
||||
start_datetime: dayjs(get(props, 'start[0]', '')).unix(),
|
||||
end_datetime: dayjs(get(props, 'end[0]', '')).unix(),
|
||||
tags: get(props, 'category', []),
|
||||
media: media ? [{ name: get(props, 'name[0]', ''), url: get(props, 'featured[0]'), focalpoint: [0, 0] }] : []
|
||||
media: media ? [{ name: get(props, 'name[0]', ''), url: media, focalpoint: [0, 0] }] : []
|
||||
}
|
||||
})
|
||||
return res.json(events)
|
||||
|
@ -201,9 +198,9 @@ module.exports = {
|
|||
return {
|
||||
title: get(event, 'summary', ''),
|
||||
description: get(event, 'description', ''),
|
||||
place: get(event, 'location', ''),
|
||||
start: get(event, 'dtstart', ''),
|
||||
end: get(event, 'dtend', '')
|
||||
place: { name: get(event, 'location', '') },
|
||||
start_datetime: dayjs(get(event, 'startDate', null)).unix(),
|
||||
end_datetime: dayjs(get(event, 'endDate', null)).unix()
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -3,12 +3,14 @@ const config = require('../server/config')
|
|||
const initialize = {
|
||||
// close connections/port/unix socket
|
||||
async shutdown (exit = true) {
|
||||
const log = require('../server/log')
|
||||
const TaskManager = require('../server/taskManager').TaskManager
|
||||
if (TaskManager) { TaskManager.stop() }
|
||||
log.info('Closing DB')
|
||||
const sequelize = require('../server/api/models')
|
||||
await sequelize.close()
|
||||
if (config.status == 'READY') {
|
||||
const log = require('../server/log')
|
||||
const TaskManager = require('../server/taskManager').TaskManager
|
||||
if (TaskManager) { TaskManager.stop() }
|
||||
log.info('Closing DB')
|
||||
const sequelize = require('../server/api/models')
|
||||
await sequelize.close()
|
||||
}
|
||||
process.off('SIGTERM', initialize.shutdown)
|
||||
process.off('SIGINT', initialize.shutdown)
|
||||
if (exit) {
|
||||
|
@ -23,8 +25,9 @@ const initialize = {
|
|||
const dayjs = require('dayjs')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
dayjs.extend(timezone)
|
||||
if (config.status == 'READY') {
|
||||
if (config.status == 'CONFIGURED') {
|
||||
await db.initialize()
|
||||
config.status = 'READY'
|
||||
} else {
|
||||
if (process.env.GANCIO_DB_DIALECT) {
|
||||
const setupController = require('./api/controller/setup')
|
||||
|
@ -38,7 +41,7 @@ const initialize = {
|
|||
password: process.env.GANCIO_DB_PASSWORD,
|
||||
}
|
||||
|
||||
setupController._setupDb(dbConf)
|
||||
await setupController._setupDb(dbConf)
|
||||
.catch(e => {
|
||||
log.warn(String(e))
|
||||
process.exit(1)
|
||||
|
|
|
@ -3,13 +3,6 @@ module.exports = {
|
|||
await queryInterface.addColumn('fed_users', 'follower', {
|
||||
type: Sequelize.BOOLEAN
|
||||
})
|
||||
|
||||
// const users = queryInterface.sequelize.query('SELECT ')
|
||||
// const followers = users.reduce((followers, user) => followers.concat(user.followers), [])
|
||||
// console.error(followers)
|
||||
// const followers = await Sequelize.models.user_followers.findAll()
|
||||
// console.error(followers)
|
||||
// get current followers
|
||||
},
|
||||
|
||||
down: (queryInterface, Sequelize) => {
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
up: (queryInterface, Sequelize) => {
|
||||
return queryInterface.sequelize.query('ALTER TYPE "enum_event_notifications_status" ADD VALUE \'sending\'').catch(e => {})
|
||||
},
|
||||
|
||||
down: (queryInterface, Sequelize) => {
|
||||
}
|
||||
}
|
151
server/routes.js
151
server/routes.js
|
@ -1,99 +1,98 @@
|
|||
const express = require('express')
|
||||
const cookieParser = require('cookie-parser')
|
||||
|
||||
const initialize = require('./initialize.server')
|
||||
initialize.start()
|
||||
|
||||
// const metricsController = require('./metrics')
|
||||
// const promBundle = require('express-prom-bundle')
|
||||
// const metricsMiddleware = promBundle({ includeMethod: true })
|
||||
const config = require('./config')
|
||||
|
||||
const helpers = require('./helpers')
|
||||
const log = require('./log')
|
||||
const api = require('./api')
|
||||
|
||||
|
||||
const app = express()
|
||||
app.enable('trust proxy')
|
||||
app.use(helpers.logRequest)
|
||||
const initialize = require('./initialize.server')
|
||||
|
||||
app.use(helpers.initSettings)
|
||||
const config = require('./config')
|
||||
const helpers = require('./helpers')
|
||||
app.use(helpers.setUserLocale)
|
||||
app.use(helpers.initSettings)
|
||||
app.use(helpers.logRequest)
|
||||
app.use(helpers.serveStatic())
|
||||
|
||||
app.use(cookieParser())
|
||||
|
||||
async function main () {
|
||||
|
||||
// do not handle all routes on setup
|
||||
if (config.status === 'READY') {
|
||||
const cors = require('cors')
|
||||
const { spamFilter } = require('./federation/helpers')
|
||||
const oauth = require('./api/oauth')
|
||||
const auth = require('./api/auth')
|
||||
const federation = require('./federation')
|
||||
const webfinger = require('./federation/webfinger')
|
||||
const exportController = require('./api/controller/export')
|
||||
const tagController = require('./api/controller/tag')
|
||||
const placeController = require('./api/controller/place')
|
||||
const collectionController = require('./api/controller/collection')
|
||||
await initialize.start()
|
||||
|
||||
// rss / ics feed
|
||||
app.use(helpers.feedRedirect)
|
||||
app.get('/feed/:format/tag/:tag', cors(), tagController.getEvents)
|
||||
app.get('/feed/:format/place/:placeName', cors(), placeController.getEvents)
|
||||
app.get('/feed/:format/collection/:name', cors(), collectionController.getEvents)
|
||||
app.get('/feed/:format', cors(), exportController.export)
|
||||
// const metricsController = require('./metrics')
|
||||
// const promBundle = require('express-prom-bundle')
|
||||
// const metricsMiddleware = promBundle({ includeMethod: true })
|
||||
|
||||
|
||||
app.use('/event/:slug', helpers.APRedirect)
|
||||
const log = require('./log')
|
||||
const api = require('./api')
|
||||
|
||||
// federation api / activitypub / webfinger / nodeinfo
|
||||
app.use('/federation', federation)
|
||||
app.use('/.well-known', webfinger)
|
||||
app.enable('trust proxy')
|
||||
|
||||
// ignore unimplemented ping url from fediverse
|
||||
app.use(spamFilter)
|
||||
// do not handle all routes on setup
|
||||
if (config.status === 'READY') {
|
||||
const cors = require('cors')
|
||||
const { spamFilter } = require('./federation/helpers')
|
||||
const oauth = require('./api/oauth')
|
||||
const auth = require('./api/auth')
|
||||
const federation = require('./federation')
|
||||
const webfinger = require('./federation/webfinger')
|
||||
const exportController = require('./api/controller/export')
|
||||
const tagController = require('./api/controller/tag')
|
||||
const placeController = require('./api/controller/place')
|
||||
const collectionController = require('./api/controller/collection')
|
||||
|
||||
// fill res.locals.user if request is authenticated
|
||||
app.use(auth.fillUser)
|
||||
// rss / ics feed
|
||||
app.use(helpers.feedRedirect)
|
||||
app.get('/feed/:format/tag/:tag', cors(), tagController.getEvents)
|
||||
app.get('/feed/:format/place/:placeName', cors(), placeController.getEvents)
|
||||
app.get('/feed/:format/collection/:name', cors(), collectionController.getEvents)
|
||||
app.get('/feed/:format', cors(), exportController.export)
|
||||
|
||||
app.use('/oauth', oauth)
|
||||
// app.use(metricsMiddleware)
|
||||
|
||||
app.use('/event/:slug', helpers.APRedirect)
|
||||
|
||||
// federation api / activitypub / webfinger / nodeinfo
|
||||
app.use('/federation', federation)
|
||||
app.use('/.well-known', webfinger)
|
||||
|
||||
// ignore unimplemented ping url from fediverse
|
||||
app.use(spamFilter)
|
||||
|
||||
// fill res.locals.user if request is authenticated
|
||||
app.use(auth.fillUser)
|
||||
|
||||
app.use('/oauth', oauth)
|
||||
// app.use(metricsMiddleware)
|
||||
}
|
||||
|
||||
// api!
|
||||
app.use('/api', api)
|
||||
|
||||
// // Handle 500
|
||||
app.use((error, _req, res, _next) => {
|
||||
log.error('[ERROR]' + error)
|
||||
return res.status(500).send('500: Internal Server Error')
|
||||
})
|
||||
|
||||
// remaining request goes to nuxt
|
||||
// first nuxt component is ./pages/index.vue (with ./layouts/default.vue)
|
||||
// prefill current events, tags, places and announcements (used in every path)
|
||||
app.use(async (_req, res, next) => {
|
||||
if (config.status === 'READY') {
|
||||
|
||||
const announceController = require('./api/controller/announce')
|
||||
res.locals.announcements = await announceController._getVisible()
|
||||
}
|
||||
res.locals.status = config.status
|
||||
next()
|
||||
})
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
// api!
|
||||
app.use('/api', api)
|
||||
|
||||
// // Handle 500
|
||||
app.use((error, _req, res, _next) => {
|
||||
log.error('[ERROR]' + error)
|
||||
return res.status(500).send('500: Internal Server Error')
|
||||
})
|
||||
|
||||
// remaining request goes to nuxt
|
||||
// first nuxt component is ./pages/index.vue (with ./layouts/default.vue)
|
||||
// prefill current events, tags, places and announcements (used in every path)
|
||||
app.use(async (req, res, next) => {
|
||||
if (config.status === 'READY') {
|
||||
|
||||
const announceController = require('./api/controller/announce')
|
||||
res.locals.announcements = await announceController._getVisible()
|
||||
}
|
||||
res.locals.status = config.status
|
||||
next()
|
||||
})
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
main()
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
main,
|
||||
handler: app,
|
||||
load () {
|
||||
console.error('dentro load !')
|
||||
},
|
||||
unload: () => initialize.shutdown(false)
|
||||
// async unload () {
|
||||
// const db = require('./api/models/index')
|
||||
// await db.close()
|
||||
// process.off('SIGTERM')
|
||||
// process.off('SIGINT')
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -41,7 +41,6 @@ export const actions = {
|
|||
// we use it to get configuration from db, set locale, etc...
|
||||
nuxtServerInit ({ commit }, { _req, res }) {
|
||||
commit('setSettings', res.locals.settings)
|
||||
// dayjs.tz.(res.locals.settings.instance_timezone)
|
||||
if (res.locals.status === 'READY') {
|
||||
commit('setAnnouncements', res.locals.announcements)
|
||||
}
|
||||
|
|
|
@ -1,21 +1,39 @@
|
|||
const request = require('supertest')
|
||||
const fs = require('fs')
|
||||
const dayjs = require('dayjs')
|
||||
const path = require('path')
|
||||
|
||||
const admin = { username: 'admin', password: 'test', grant_type: 'password', client_id: 'self' }
|
||||
|
||||
const admin = { username: 'admin', password: 'JqFuXEnkTyOR', grant_type: 'password', client_id: 'self' }
|
||||
let token
|
||||
// - event list should be empty
|
||||
// - try to write without auth
|
||||
// - registration should be not allowed when disabled
|
||||
// - registration should create a new user (not active) when enabled
|
||||
// - unconfirmed user cannot login
|
||||
// - should not login without auth data
|
||||
// - should login with correct authentication
|
||||
let app
|
||||
let places = []
|
||||
|
||||
beforeAll( async () => {
|
||||
fs.copyFileSync('./starter.sqlite', './testdb.sqlite')
|
||||
await require('../server/initialize.server.js').start()
|
||||
app = require('../server/routes.js').handler
|
||||
switch (process.env.DB) {
|
||||
case 'mariadb':
|
||||
process.env.config_path = path.resolve(__dirname, './seeds/config.mariadb.json')
|
||||
break
|
||||
case 'postgresql':
|
||||
process.env.config_path = path.resolve(__dirname, './seeds/config.postgres.json')
|
||||
break
|
||||
case 'sqlite':
|
||||
default:
|
||||
process.env.config_path = path.resolve(__dirname, './seeds/config.sqlite.json')
|
||||
}
|
||||
app = await require('../server/routes.js').main()
|
||||
const { sequelize } = require('../server/api/models/index')
|
||||
await sequelize.query('DELETE FROM settings')
|
||||
await sequelize.query('DELETE FROM events')
|
||||
await sequelize.query('DELETE FROM users')
|
||||
await sequelize.query('DELETE FROM ap_users')
|
||||
await sequelize.query('DELETE FROM tags')
|
||||
await sequelize.query('DELETE FROM places')
|
||||
await sequelize.query('DELETE FROM collections')
|
||||
await sequelize.query('DELETE FROM filters')
|
||||
})
|
||||
|
||||
afterAll( async () => {
|
||||
await require('../server/initialize.server.js').shutdown(false)
|
||||
})
|
||||
|
||||
describe('Basic', () => {
|
||||
|
@ -35,9 +53,19 @@ describe('Authentication / Authorization', () => {
|
|||
|
||||
test('should not authenticate with wrong user/password', () => {
|
||||
return request(app).post('/oauth/login')
|
||||
.set('Content-Type', 'application/x-www-form-urlencoded')
|
||||
.expect(500)
|
||||
})
|
||||
|
||||
test('should register an admin as first user', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/user/register')
|
||||
.send({ email: 'admin', password: 'test' })
|
||||
.expect(200)
|
||||
expect(response.body.id).toBeDefined()
|
||||
return response
|
||||
})
|
||||
|
||||
test('should authenticate with correct user/password', async () => {
|
||||
const response = await request(app)
|
||||
.post('/oauth/login')
|
||||
|
@ -57,6 +85,9 @@ describe('Authentication / Authorization', () => {
|
|||
expect(response.body.email).toBe(admin.username)
|
||||
expect(response.body.is_admin).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Settings', () => {
|
||||
|
||||
test('should not change settings when not allowed', async () => {
|
||||
return request(app).post('/api/settings')
|
||||
|
@ -64,6 +95,58 @@ describe('Authentication / Authorization', () => {
|
|||
.expect(403)
|
||||
})
|
||||
|
||||
test('should change settings when allowed', () => {
|
||||
return request(app).post('/api/settings')
|
||||
.send({ key: 'allow_anon_event', value: true })
|
||||
.auth(token.access_token, { type: 'bearer' })
|
||||
.expect(200)
|
||||
})
|
||||
|
||||
test('should retrieve stored array settings', async () => {
|
||||
await request(app).post('/api/settings')
|
||||
.auth(token.access_token, { type: 'bearer' })
|
||||
.send({ key: 'test', value: [1,2,'test'] })
|
||||
.expect(200)
|
||||
|
||||
const response = await request(app)
|
||||
.get('/api/settings')
|
||||
.auth(token.access_token, { type: 'bearer' })
|
||||
.expect(200)
|
||||
|
||||
expect(response.body.test.length).toBe(3)
|
||||
expect(response.body.test).toStrictEqual([1,2,'test'])
|
||||
})
|
||||
|
||||
|
||||
test('should retrieve stored object settings', async () => {
|
||||
await request(app).post('/api/settings')
|
||||
.auth(token.access_token, { type: 'bearer' })
|
||||
.send({ key: 'test', value: { name: 'test object' } })
|
||||
.expect(200)
|
||||
|
||||
const response = await request(app)
|
||||
.get('/api/settings')
|
||||
.auth(token.access_token, { type: 'bearer' })
|
||||
.expect(200)
|
||||
|
||||
expect(response.body.test.name).toBe('test object')
|
||||
})
|
||||
|
||||
|
||||
test('should retrieve stored string settings', async () => {
|
||||
await request(app).post('/api/settings')
|
||||
.auth(token.access_token, { type: 'bearer' })
|
||||
.send({ key: 'test', value: 'test string' })
|
||||
.expect(200)
|
||||
|
||||
const response = await request(app)
|
||||
.get('/api/settings')
|
||||
.auth(token.access_token, { type: 'bearer' })
|
||||
.expect(200)
|
||||
|
||||
expect(response.body.test).toBe('test string')
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('Events', () => {
|
||||
|
@ -72,7 +155,7 @@ describe('Events', () => {
|
|||
const required_fields = {
|
||||
'title': {},
|
||||
'start_datetime': { title: 'test title' },
|
||||
'place_id or place_name and place_address': { title: 'test title', start_datetime: new Date().getTime() * 1000, place_name: 'test place name'},
|
||||
'place_id or place_name and place_address': { title: 'test title', start_datetime: dayjs().unix()+1000, place_name: 'test place name'},
|
||||
}
|
||||
|
||||
const promises = Object.keys(required_fields).map(async field => {
|
||||
|
@ -95,29 +178,33 @@ describe('Events', () => {
|
|||
await request(app).post('/api/event')
|
||||
.expect(403)
|
||||
|
||||
await request(app).post('/api/event')
|
||||
.send({ title: 'test title', place_name: 'place name', place_address: 'address', tags: ['test'], start_datetime: new Date().getTime() * 1000 })
|
||||
let response = await request(app).post('/api/event')
|
||||
.send({ title: 'test title 2', place_name: 'place name', place_address: 'address', tags: ['test'], start_datetime: dayjs().unix()+1000 })
|
||||
.auth(token.access_token, { type: 'bearer' })
|
||||
.expect(200)
|
||||
|
||||
expect(response.body.place.id).toBeDefined()
|
||||
places.push(response.body.place.id)
|
||||
|
||||
await request(app).post('/api/settings')
|
||||
.send({ key: 'allow_anon_event', value: true })
|
||||
.auth(token.access_token, { type: 'bearer' })
|
||||
.expect(200)
|
||||
|
||||
return request(app).post('/api/event')
|
||||
.send({ title: 'test title', place_name: 'place name 2', place_address: 'address 2', tags: ['test'], start_datetime: new Date().getTime() * 1000 })
|
||||
response = await request(app).post('/api/event')
|
||||
.send({ title: 'test title 3', place_name: 'place name 2', place_address: 'address 2', tags: ['test'], start_datetime: dayjs().unix()+1000 })
|
||||
.expect(200)
|
||||
|
||||
expect(response.body.place.id).toBeDefined()
|
||||
places.push(response.body.place.id)
|
||||
|
||||
})
|
||||
|
||||
|
||||
|
||||
test('should trim tags', async () => {
|
||||
const event = {
|
||||
title: 'test title',
|
||||
place_id: 1,
|
||||
start_datetime: dayjs().unix(),
|
||||
title: 'test title 4',
|
||||
place_id: places[0],
|
||||
start_datetime: dayjs().unix()+1000,
|
||||
tags: [' test tag ']
|
||||
}
|
||||
|
||||
|
@ -130,33 +217,43 @@ describe('Events', () => {
|
|||
})
|
||||
})
|
||||
|
||||
let event = {}
|
||||
describe('Tags', () => {
|
||||
test('should create event with tags', async () => {
|
||||
const event = await request(app).post('/api/event')
|
||||
.send({ title: 'test tags', place_id: 2, start_datetime: new Date().getTime() * 1000, tags: ['tag1', 'Tag2', 'tAg3'] })
|
||||
event = await request(app).post('/api/event')
|
||||
.send({ title: 'test tags', place_id: places[1], start_datetime: dayjs().unix()+1000 , tags: ['tag1', 'Tag2', 'tAg3'] })
|
||||
.auth(token.access_token, { type: 'bearer' })
|
||||
.expect(200)
|
||||
|
||||
expect(event.body.tags.length).toBe(3)
|
||||
expect(event.body.tags).toStrictEqual(['tag1', 'Tag2', 'tAg3'])
|
||||
})
|
||||
|
||||
test('should create event trimming tags / ignore sensitiviness', async () => {
|
||||
const event = await request(app).post('/api/event')
|
||||
.send({ title: 'test trimming tags', place_id: 2, start_datetime: new Date().getTime() * 1000, tags: ['Tag1', 'taG2 '] })
|
||||
const ret = await request(app).post('/api/event')
|
||||
.send({ title: 'test trimming tags', place_id: places[1], start_datetime: dayjs().unix()+1000, tags: ['Tag1', 'taG2 '] })
|
||||
.auth(token.access_token, { type: 'bearer' })
|
||||
.expect(200)
|
||||
|
||||
expect(event.body.tags.length).toBe(2)
|
||||
expect(event.body.tags[0]).toBe('tag1')
|
||||
expect(event.body.tags[1]).toBe('Tag2')
|
||||
expect(ret.body.tags.length).toBe(2)
|
||||
// expect(ret.body.tags).toStrictEqual(['Tag1', 'taG2'])
|
||||
expect(ret.body.tags[0]).toBe('tag1')
|
||||
expect(ret.body.tags[1]).toBe('Tag2')
|
||||
})
|
||||
|
||||
test('should modify event tags', async () => {
|
||||
const ret = await request(app).put('/api/event')
|
||||
.send({ id: event.body.id, tags: ['tag1', 'tag3', 'tag4'], place_id: places[1] })
|
||||
.auth(token.access_token, { type: 'bearer' })
|
||||
.expect(200)
|
||||
|
||||
expect(ret.body.tags).toStrictEqual(['tag1', 'tAg3', 'tag4'])
|
||||
})
|
||||
|
||||
test('should return events searching for tags', async () => {
|
||||
const response = await request(app).get('/api/events?tags=tAg3')
|
||||
.expect(200)
|
||||
|
||||
// console.error(response.body)
|
||||
// console.error(response.body[0].tags)
|
||||
expect(response.body.length).toBe(1)
|
||||
// expect(response.body[0].title).toBe('test tags')
|
||||
expect(response.body[0].tags.length).toBe(3)
|
||||
|
@ -194,6 +291,8 @@ describe('Place', () => {
|
|||
|
||||
})
|
||||
|
||||
let collections = []
|
||||
let filters = []
|
||||
describe ('Collection', () => {
|
||||
test('should not create a new collection if not allowed', () => {
|
||||
return request(app).post('/api/collections')
|
||||
|
@ -206,7 +305,8 @@ describe ('Collection', () => {
|
|||
.send({ name: 'test collection' })
|
||||
.auth(token.access_token, { type: 'bearer' })
|
||||
.expect(200)
|
||||
expect(response.body.id).toBe(1)
|
||||
expect(response.body.id).toBeDefined()
|
||||
collections.push(response.body.id)
|
||||
})
|
||||
|
||||
test('should do not have any event when no filters', async () => {
|
||||
|
@ -216,21 +316,34 @@ describe ('Collection', () => {
|
|||
expect(response.body.length).toBe(0)
|
||||
})
|
||||
|
||||
|
||||
test('should add a new filter', async () => {
|
||||
await request(app)
|
||||
.post('/api/filter')
|
||||
.send({ collectionId: collections[0], tags: ['test'] })
|
||||
.expect(403)
|
||||
|
||||
const response = await request(app).post('/api/filter')
|
||||
.send({ collectionId: 1, tags: ['test'] })
|
||||
.send({ collectionId: collections[0], tags: ['test'] })
|
||||
.auth(token.access_token, { type: 'bearer' })
|
||||
.expect(200)
|
||||
|
||||
expect(response.body.id).toBe(1)
|
||||
expect(response.body.id).toBeDefined()
|
||||
filters.push(response.body.id)
|
||||
|
||||
})
|
||||
|
||||
test('shoud get collection\'s filters using withFilters parameter', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/collections?withFilters=true')
|
||||
.expect(200)
|
||||
|
||||
expect(response.body.length).toBe(1)
|
||||
expect(response.body[0].name).toBe('test collection')
|
||||
expect(response.body[0].filters.length).toBe(1)
|
||||
expect(response.body[0].filters[0].tags.length).toBe(1)
|
||||
expect(response.body[0].filters[0].tags[0]).toBe('test')
|
||||
})
|
||||
|
||||
test('should get collection events', async () => {
|
||||
const response = await request(app)
|
||||
.get(`/api/collections/test collection`)
|
||||
|
@ -241,16 +354,16 @@ describe ('Collection', () => {
|
|||
|
||||
test('should remove filter', async () => {
|
||||
await request(app)
|
||||
.delete('/api/filter/1')
|
||||
.delete(`/api/filter/${filters[0]}`)
|
||||
.expect(403)
|
||||
|
||||
await request(app)
|
||||
.delete('/api/filter/1')
|
||||
.delete(`/api/filter/${filters[0]}`)
|
||||
.auth(token.access_token, { type: 'bearer' })
|
||||
.expect(200)
|
||||
|
||||
const response = await request(app)
|
||||
.get('/api/filter/1')
|
||||
.get(`/api/filter/${filters[0]}`)
|
||||
.auth(token.access_token, { type: 'bearer' })
|
||||
.expect(200)
|
||||
|
||||
|
@ -258,19 +371,19 @@ describe ('Collection', () => {
|
|||
})
|
||||
|
||||
test('shoud filter for tags', async () => {
|
||||
await request(app)
|
||||
let response = await request(app)
|
||||
.post('/api/filter')
|
||||
.send({ collectionId: 1, tags: ['test'] })
|
||||
.send({ collectionId: collections[0], tags: ['test'] })
|
||||
.auth(token.access_token, { type: 'bearer' })
|
||||
.expect(200)
|
||||
|
||||
|
||||
let response = await request(app)
|
||||
.get('/api/filter/1')
|
||||
.auth(token.access_token, { type: 'bearer' })
|
||||
.expect(200)
|
||||
// response = await request(app)
|
||||
// .get(`/api/filter/${response.body.id}`)
|
||||
// .auth(token.access_token, { type: 'bearer' })
|
||||
// .expect(200)
|
||||
// expect(response.body.length).toBe(1)
|
||||
|
||||
expect(response.body.length).toBe(1)
|
||||
response = await request(app)
|
||||
.get(`/api/collections/test collection`)
|
||||
.expect(200)
|
||||
|
|
19
tests/seeds/config.mariadb.json
Normal file
19
tests/seeds/config.mariadb.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"baseurl": "http://localhost:13120",
|
||||
"hostname": "localhost",
|
||||
"server": {
|
||||
"host": "0.0.0.0",
|
||||
"port": 13120
|
||||
},
|
||||
"log_level": "warn",
|
||||
"log_path": "./logs",
|
||||
"db": {
|
||||
"dialect": "mariadb",
|
||||
"host": "localhost",
|
||||
"database": "gancio",
|
||||
"username": "gancio",
|
||||
"password": "gancio",
|
||||
"logging": false
|
||||
},
|
||||
"upload_path": "./uploads"
|
||||
}
|
19
tests/seeds/config.postgres.json
Normal file
19
tests/seeds/config.postgres.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"baseurl": "http://localhost:13120",
|
||||
"hostname": "localhost",
|
||||
"server": {
|
||||
"host": "0.0.0.0",
|
||||
"port": 13120
|
||||
},
|
||||
"log_level": "warn",
|
||||
"log_path": "./logs",
|
||||
"db": {
|
||||
"dialect": "postgres",
|
||||
"host": "localhost",
|
||||
"database": "gancio",
|
||||
"username": "gancio",
|
||||
"password": "gancio",
|
||||
"logging": false
|
||||
},
|
||||
"upload_path": "./uploads"
|
||||
}
|
16
tests/seeds/config.sqlite.json
Normal file
16
tests/seeds/config.sqlite.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"baseurl": "http://localhost:13120",
|
||||
"hostname": "127.0.0.1",
|
||||
"server": {
|
||||
"host": "0.0.0.0",
|
||||
"port": 13120
|
||||
},
|
||||
"log_level": "warn",
|
||||
"log_path": "./logs",
|
||||
"db": {
|
||||
"dialect": "sqlite",
|
||||
"storage": "./tests/seeds/testdb.sqlite",
|
||||
"logging": false
|
||||
},
|
||||
"upload_path": "./uploads"
|
||||
}
|
|
@ -9,7 +9,7 @@ rss(version='2.0' xmlns:atom="http://www.w3.org/2005/Atom")
|
|||
item
|
||||
if (event.media && event.media.length)
|
||||
<enclosure url="#{settings.baseurl}/media/#{event.media[0].url}" type='image/jpeg' length="#{event.media[0].size||1}"></enclosure>
|
||||
title [#{moment.unix(event.start_datetime).format("YY-MM-DD")}] #{event.title} @#{event.place.name}
|
||||
title [#{moment.unix(event.start_datetime).format("YY-MM-DD")}] #{event.title} @ #{event.place.name}
|
||||
link #{settings.baseurl}/event/#{event.slug || event.id}
|
||||
each tag in event.tags
|
||||
category #{tag}
|
||||
|
|
|
@ -4,7 +4,7 @@ Donate link: https://gancio.org
|
|||
Tags: events, gancio, fediverse, AP, activity pub
|
||||
Requires at least: 4.7
|
||||
Tested up to: 5.9
|
||||
Stable tag: 1.3
|
||||
Stable tag: 1.4
|
||||
Requires PHP: 7.0
|
||||
License: AGPLv3 or later
|
||||
License URI: https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
@ -18,6 +18,11 @@ for this to work an event manager plugin is required, only [Event Organiser](htt
|
|||
|
||||
|
||||
== Changelog ==
|
||||
= 1.4 =
|
||||
use `WP_GANCIO_DEFAULT_INSTANCEURL` as default instance url
|
||||
|
||||
= 1.3 =
|
||||
* Update [webcomponent](https://gancio.org/usage/embed) script
|
||||
|
||||
= 1.2 =
|
||||
* Enqueue [webcomponent](https://gancio.org/usage/embed) script
|
||||
|
|
Loading…
Reference in a new issue