Merge branch 'master' into gh
This commit is contained in:
commit
5dddfbd29e
126 changed files with 6962 additions and 2739 deletions
9
.gitignore
vendored
9
.gitignore
vendored
|
@ -1,13 +1,14 @@
|
|||
# Created by .ignore support plugin (hsz.mobi)
|
||||
|
||||
### Gancio dev configuration
|
||||
*.sqlite
|
||||
gancio.sqlite
|
||||
db.sqlite
|
||||
releases
|
||||
wp-plugin/wpgancio
|
||||
config/development.json
|
||||
gancio_config.json
|
||||
config.json
|
||||
db.sqlite
|
||||
/gancio_config.json
|
||||
/config.json
|
||||
/assets/config.json
|
||||
thumb
|
||||
docs/_site
|
||||
.vscode
|
||||
|
|
68
CHANGELOG
68
CHANGELOG
|
@ -1,11 +1,77 @@
|
|||
All notable changes to this project will be documented in this file.
|
||||
|
||||
### UNRELEASED
|
||||
- add CLI support to manage accounts (list / modify / add accounts)
|
||||
### 1.4.3 - 10 mar '22
|
||||
- fix [#140](https://framagit.org/les/gancio/-/issues/140) - Invalid date
|
||||
- fix [#141](https://framagit.org/les/gancio/-/issues/141) - Cannot change logo
|
||||
- fix same day events
|
||||
- add missing icons in admin
|
||||
- prepare multisite settings
|
||||
- improve initialization
|
||||
- start unit testing API (it's never too late)
|
||||
|
||||
### 1.4.1 - 4 mar '22
|
||||
- add gl/galego locale, thanks @xosem
|
||||
- fix import redirect loop
|
||||
- add missing icons (close, repeat, arrows ...)
|
||||
- turn rss icon into a real link to improve a11y
|
||||
- force seconds to 0 for each events, fix recurring events starting date issue
|
||||
- fix next/prev selection on same datetime events
|
||||
- improve moderation UI (add author and event link + format creation date)
|
||||
- refactoring resource UI from fedi
|
||||
|
||||
### 1.4.0 - 9 feb '22
|
||||
- improve Cumulative Layout Shift
|
||||
- remove filename as default media label to avoid leak metadata
|
||||
- add endData to microdata
|
||||
- security fix with filtering settings, avoid sharing SMTP pass with front-end
|
||||
- fix broken SMTP
|
||||
- remove global materialicons / vuetify css, use threeshake and @nuxt/vuetify (really improve lighthouse score)
|
||||
- new Dockerfile using node:17.4-slim as base img (from 1.5Gb to ~800Mb)
|
||||
- add XSS and path traversal mitigation
|
||||
- improve a11y
|
||||
- update deps
|
||||
|
||||
### 1.3.3 - 1 feb '22
|
||||
- security fix, avoid sharing smtp pass with front-end
|
||||
|
||||
### 1.3.2 - 1 feb '22
|
||||
- fix webcomponent for event without img
|
||||
|
||||
### 1.3.1 - 1 feb '22
|
||||
- inherits tags in recurring events [#138](https://framagit.org/les/gancio/-/issues/138)
|
||||
- you can now skip an occurrence of a recurring event
|
||||
- fix `show_recurrent` event in webcomponent and API
|
||||
- add new webcomponent `sidebar` attribute and a [`fullwith` layout](https://gancio.org/usage/embed#embed-event-lists)
|
||||
|
||||
### 1.3.0 - 26 gen '22
|
||||
- add mariadb support
|
||||
- add [microdata](https://developer.mozilla.org/en-US/docs/Web/HTML/Microdata) support
|
||||
- support db setup via environment variables (used in updated `docker-compose.yml` files)
|
||||
- improve rss feed:
|
||||
- fix validation
|
||||
- add enclusure media for featured image
|
||||
- add categories
|
||||
- fix typo in export code
|
||||
- add theme attribute to gancio-events webcomponent (dark/light)
|
||||
- increase thumbs quality
|
||||
- improve logo for light theme
|
||||
- improve [wordpress plugin](https://wordpress.org/plugins/wpgancio/)
|
||||
- add \[gancio-event\] and \[gancio-events\] shortcode
|
||||
- allow gancio-events / gancio-event tags in editor
|
||||
- automatically enqueue webcomponent script
|
||||
- tags/places filters are now inclusive not exclusive
|
||||
- fix image undefined alternative text
|
||||
- update documentation, dependencies, translations
|
||||
|
||||
### 1.2.2 - 7 dic '21
|
||||
- shiny new gancio-event\[s\] webcomponents => [docs](https://gancio.org/usage/embed)
|
||||
- new backend plugin system
|
||||
- new backend plugin system => [docs](https://gancio.org/dev/plugins)
|
||||
- improve media focal point selection
|
||||
- improve non-js experience (load img, use native lazy loading)
|
||||
- improve user_confirm / recover code flow
|
||||
- permit admins to choose user password (usefull on instance without SMTP configuration)
|
||||
- fix task manager exception
|
||||
- fix db initialization when a custom setup is used, #131
|
||||
- remove vue-clipboard2 dependency due to [this](https://github.com/euvl/v-clipboard/issues/18) bug and using a [native with fallback mixin instead](./assets/clipboard.js)
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
- change version in package.json
|
||||
- add changes to CHANGELOG / changelog.md
|
||||
- yarn build
|
||||
- yarn pack
|
||||
- yarn publish
|
||||
- yarn doc
|
||||
./release.sh
|
||||
- git add .
|
||||
- git ci -m 'v...'
|
||||
- git tag ...
|
||||
- git push --tags
|
||||
-
|
||||
|
|
|
@ -12,7 +12,7 @@ export default {
|
|||
document.execCommand('copy')
|
||||
document.body.removeChild(el)
|
||||
}
|
||||
this.$root.$message(msg)
|
||||
this.$root.$message(msg, { color: 'success'})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
|
||||
.editor {
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
padding-top: 1.7em;
|
||||
scrollbar-width: thin;
|
||||
|
||||
&.with-border {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0px 5px 0px 5px;
|
||||
flex: 1;
|
||||
scrollbar-width: thin;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.menububble {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
z-index: 1;
|
||||
background: #dddddd;
|
||||
transform: translateX(-50%);
|
||||
border-radius: 3px;
|
||||
padding: 0.07rem;
|
||||
transition: opacity 0.2s, visibility 0.2s, left .2s, bottom .2s;
|
||||
visibility: hidden;
|
||||
|
||||
&.is-active {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
input {
|
||||
padding: 0;
|
||||
margin: 1px;
|
||||
display: block;
|
||||
border: 0;
|
||||
color: #444;
|
||||
font-size: .8em;
|
||||
border-radius: 3px;
|
||||
line-height: 100%;
|
||||
transition: width .2s;
|
||||
padding-left: 5px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.fa-icon {
|
||||
width: auto;
|
||||
font-size: 10px;
|
||||
height: 1.4em; /* or any other relative font sizes */
|
||||
/* You would have to include the following two lines to make this work in Safari */
|
||||
// max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
// .event {
|
||||
// width: 320px;
|
||||
// max-width: 450px;
|
||||
// flex-grow: 1;
|
||||
// margin: .2em;
|
||||
// background-color: #202020;
|
||||
// overflow: hidden;
|
||||
|
||||
// a:hover {
|
||||
// text-decoration: none;
|
||||
// .title {
|
||||
// border-bottom: 1px solid #888;
|
||||
// color: white;
|
||||
// }
|
||||
// }
|
||||
|
||||
// .title {
|
||||
// margin-left: 1rem;
|
||||
// margin-top: 1rem;
|
||||
// margin-right: 1rem;
|
||||
// border-bottom: 1px solid #333;
|
||||
// transition: border-color .5s;
|
||||
// font-size: 1.2em;
|
||||
// max-height: 3em;
|
||||
// overflow: hidden;
|
||||
// color: white;
|
||||
// font-weight: bold;
|
||||
// }
|
||||
|
||||
// .card-footer {
|
||||
// max-height: 4.5em;
|
||||
// overflow: hidden;
|
||||
// padding: .25rem 0.5rem;
|
||||
// line-height: 1.8rem;
|
||||
// min-height: 2.2rem;
|
||||
// }
|
||||
|
||||
// .card-body {
|
||||
// overflow: hidden;
|
||||
// }
|
||||
|
||||
// .description {
|
||||
// color: #999;
|
||||
// font-size: 0.8em;
|
||||
// overflow: hidden;
|
||||
// max-height: 100%;
|
||||
// }
|
||||
|
||||
// .el-image { width: 100% }
|
||||
// img {
|
||||
// width: 100%;
|
||||
// max-height: 250px;
|
||||
// object-fit: cover;
|
||||
// object-position: top;
|
||||
// }
|
||||
// }
|
|
@ -30,6 +30,10 @@ li {
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
#calh {
|
||||
height: 292px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
}
|
||||
|
@ -91,7 +95,6 @@ li {
|
|||
.place {
|
||||
max-width: 100%;
|
||||
span {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
|
8
assets/variables.scss
Normal file
8
assets/variables.scss
Normal file
|
@ -0,0 +1,8 @@
|
|||
// assets/variables.scss
|
||||
|
||||
// Variables you want to modify
|
||||
// $btn-border-radius: 0px;
|
||||
|
||||
// If you need to extend Vuetify SASS lists
|
||||
// $material-light: ( cards: blue );
|
||||
@import '~vuetify/src/styles/styles.sass';
|
|
@ -1,15 +1,16 @@
|
|||
<template lang="pug">
|
||||
nuxt-link(:to='`/announcement/${announcement.id}`')
|
||||
v-alert.mb-1(border='left' type='info' color="primary" show-icon) {{announcement.title}}
|
||||
v-alert.mb-1(border='left' type='info' color="primary" :icon='mdiInformation') {{announcement.title}}
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
import { mdiInformation } from '@mdi/js'
|
||||
export default {
|
||||
data () {
|
||||
return { mdiInformation }
|
||||
},
|
||||
props: {
|
||||
announcement: { type: Object, default: () => ({}) }
|
||||
},
|
||||
computed: mapState(['announcements'])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
<template lang="pug">
|
||||
#calendar
|
||||
vc-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
|
||||
@dayclick='click')
|
||||
is-inline)
|
||||
|
||||
</template>
|
||||
<script>
|
||||
|
@ -28,6 +29,7 @@ export default {
|
|||
const month = dayjs().month() + 1
|
||||
const year = dayjs().year()
|
||||
return {
|
||||
selectedDate: null,
|
||||
page: { month, year }
|
||||
}
|
||||
},
|
||||
|
|
|
@ -7,64 +7,67 @@
|
|||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.bold() }"
|
||||
@click="commands.bold")
|
||||
v-icon mdi-format-bold
|
||||
v-icon(v-text='mdiFormatBold')
|
||||
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.underline() }"
|
||||
@click="commands.underline")
|
||||
v-icon mdi-format-underline
|
||||
v-icon(v-text='mdiFormatUnderline')
|
||||
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.strike() }"
|
||||
@click="commands.strike")
|
||||
v-icon mdi-format-strikethrough-variant
|
||||
v-icon(v-text='mdiFormatStrikethroughVariant')
|
||||
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.italic() }"
|
||||
@click="commands.italic")
|
||||
v-icon mdi-format-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 mdi-format-header-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 mdi-format-header-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 mdi-format-header-3
|
||||
v-icon(v-text='mdiFormatHeader3')
|
||||
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.code() }"
|
||||
@click="commands.code")
|
||||
v-icon mdi-code-tags
|
||||
v-icon(v-text='mdiCodeTags')
|
||||
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.blockquote() }"
|
||||
@click="commands.blockquote")
|
||||
v-icon mdi-format-quote-open
|
||||
v-icon(v-text='mdiFormatQuoteOpen')
|
||||
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.bullet_list() }"
|
||||
@click="commands.bullet_list")
|
||||
v-icon mdi-format-list-bulleted
|
||||
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 mdi-link
|
||||
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()')
|
||||
|
||||
editor-content.content(:editor='editor' spellcheck='false' :style="{ 'max-height': maxHeight }")
|
||||
editor-content.content(:editor='editor' spellcheck='false' :style="{ 'max-height': maxHeight }" :aria-label='label' :label='label')
|
||||
</template>
|
||||
<script>
|
||||
import debounce from 'lodash/debounce'
|
||||
import { mdiLink, mdiFormatListBulleted, mdiFormatQuoteOpen, mdiCodeTags,
|
||||
mdiFormatHeader1, mdiFormatHeader2, mdiFormatHeader3, mdiFormatItalic,
|
||||
mdiFormatStrikethroughVariant, mdiFormatBold, mdiFormatUnderline } from '@mdi/js'
|
||||
import { Editor, EditorContent, EditorMenuBar, EditorMenuBubble } from 'tiptap'
|
||||
import {
|
||||
Blockquote,
|
||||
|
@ -97,6 +100,9 @@ export default {
|
|||
},
|
||||
data () {
|
||||
return {
|
||||
mdiLink, mdiFormatListBulleted, mdiFormatQuoteOpen, mdiCodeTags,
|
||||
mdiFormatHeader1, mdiFormatHeader2, mdiFormatHeader3, mdiFormatItalic,
|
||||
mdiFormatStrikethroughVariant, mdiFormatBold, mdiFormatUnderline,
|
||||
options: [],
|
||||
linkActive: false,
|
||||
editor: null,
|
||||
|
|
|
@ -1,54 +1,66 @@
|
|||
<template lang="pug">
|
||||
v-card.h-event.event.d-flex
|
||||
nuxt-link(:to='`/event/${event.slug || event.id}`')
|
||||
img.img.u-featured(:src='thumbnail' :alt='alt' loading='lazy' :style="{ 'object-position': thumbnailPosition }")
|
||||
v-icon.float-right.mr-1(v-if='event.parentId' color='success') mdi-repeat
|
||||
.title.p-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")
|
||||
img.img.u-featured(:src='thumbnail' :alt='alt' :loading='this.lazy?"lazy":"eager"' itemprop="image" :style="{ 'object-position': thumbnailPosition }")
|
||||
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")') <v-icon>mdi-calendar</v-icon> {{ event|when }}
|
||||
.d-none.dt-end {{event.end_datetime|unixFormat('YYYY-MM-DD HH:mm')}}
|
||||
a.place.d-block.p-location.pl-0(text color='primary' @click="$emit('placeclick', event.place.id)") <v-icon>mdi-map-marker</v-icon> {{event.place.name}}
|
||||
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')}}
|
||||
a.place.d-block.p-location.pl-0(text color='primary' @click="$emit('placeclick', event.place.id)" itemprop="location" :content="event.place.name") <v-icon v-text='mdiMapMarker'></v-icon> {{event.place.name}}
|
||||
.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
|
||||
:key='tag' outlined color='primary' @click="$emit('tagclick', tag)") {{tag}}
|
||||
|
||||
client-only
|
||||
v-menu(offset-y)
|
||||
template(v-slot:activator="{on}")
|
||||
v-btn.align-self-end(icon v-on='on' color='primary' alt='more')
|
||||
v-icon mdi-dots-vertical
|
||||
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 mdi-content-copy
|
||||
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 mdi-calendar-export
|
||||
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 mdi-pencil
|
||||
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') mdi-delete-forever
|
||||
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'
|
||||
import clipboard from '../assets/clipboard'
|
||||
import { mdiRepeat, mdiPencil, mdiDotsVertical, mdiContentCopy,
|
||||
mdiCalendarExport, mdiDeleteForever, mdiCalendar, mdiMapMarker } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return { mdiRepeat, mdiPencil, mdiDotsVertical, mdiContentCopy, mdiCalendarExport,
|
||||
mdiDeleteForever, mdiMapMarker, mdiCalendar }
|
||||
},
|
||||
props: {
|
||||
event: { type: Object, default: () => ({}) }
|
||||
event: { type: Object, default: () => ({}) },
|
||||
lazy: Boolean
|
||||
},
|
||||
mixins: [clipboard],
|
||||
computed: {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
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') Gancio <small>{{settings.version}}</small>
|
||||
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}}
|
||||
|
|
|
@ -9,14 +9,18 @@ div#list
|
|||
v-for='event in computedEvents'
|
||||
:key='`${event.id}_${event.start_datetime}`' small)
|
||||
v-list-item-content
|
||||
v-list-item-subtitle <v-icon small color='success' v-if='event.parentId'>mdi-repeat</v-icon> {{event|when}}
|
||||
v-list-item-subtitle <v-icon small color='success' v-if='event.parentId' v-text='mdiRepeat'></v-icon> {{event|when}}
|
||||
span.primary--text.ml-1 @{{event.place.name}}
|
||||
v-list-item-title(v-text='event.title')
|
||||
</template>
|
||||
<script>
|
||||
import { mdiRepeat } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
name: 'List',
|
||||
data () {
|
||||
return { mdiRepeat }
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
|
|
|
@ -1,71 +1,73 @@
|
|||
<template lang="pug">
|
||||
v-app-bar(app aria-label='Menu')
|
||||
v-app-bar(app aria-label='Menu' height=64)
|
||||
|
||||
//- logo, title and description
|
||||
v-list-item(:to='$route.name==="index"?"/about":"/"')
|
||||
v-list-item-avatar(tile)
|
||||
v-img(src='/logo.png')
|
||||
v-img(src='/logo.png' alt='home')
|
||||
v-list-item-content.d-none.d-sm-flex
|
||||
v-list-item-title
|
||||
h2 {{settings.title}}
|
||||
v-list-item-subtitle {{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-icon(large color='primary' v-text='mdiPlus')
|
||||
|
||||
v-tooltip(bottom) {{$t('common.add_event')}}
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn(v-if='could_add' icon nuxt to='/add' v-on='on' :aria-label='$t("common.add_event")')
|
||||
v-icon(large color='primary') mdi-plus
|
||||
v-btn(icon nuxt to='/export' :title='$t("common.share")' :aria-label='$t("common.share")')
|
||||
v-icon(v-text='mdiShareVariant')
|
||||
|
||||
v-tooltip(bottom) {{$t('common.share')}}
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn(icon nuxt to='/export' v-on='on' :aria-label='$t("common.share")')
|
||||
v-icon mdi-share-variant
|
||||
v-btn(v-if='!$auth.loggedIn' icon nuxt to='/login' :title='$t("common.login")' :aria-label='$t("common.login")')
|
||||
v-icon(v-text='mdiLogin')
|
||||
|
||||
v-tooltip(v-if='!$auth.loggedIn' bottom) {{$t('common.login')}}
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn(icon nuxt to='/login' v-on='on' :aria-label='$t("common.login")')
|
||||
v-icon mdi-login
|
||||
|
||||
v-menu(v-else
|
||||
offset-y bottom open-on-hover transition="slide-y-transition")
|
||||
client-only
|
||||
v-menu(v-if='$auth.loggedIn' offset-y)
|
||||
template(v-slot:activator="{ on, attrs }")
|
||||
v-btn(icon v-bind='attrs' v-on='on' aria-label='Menu')
|
||||
v-icon mdi-dots-vertical
|
||||
v-btn(icon v-bind='attrs' v-on='on' title='Menu' aria-label='Menu')
|
||||
v-icon(v-text='mdiDotsVertical')
|
||||
v-list
|
||||
v-list-item(nuxt to='/settings')
|
||||
v-list-item-icon
|
||||
v-icon mdi-cog
|
||||
v-icon(v-text='mdiCog')
|
||||
v-list-item-content
|
||||
v-list-item-title {{$t('common.settings')}}
|
||||
|
||||
v-list-item(v-if='$auth.user.is_admin' nuxt to='/admin')
|
||||
v-list-item-icon
|
||||
v-icon mdi-account
|
||||
v-icon(v-text='mdiAccount')
|
||||
v-list-item-content
|
||||
v-list-item-title {{$t('common.admin')}}
|
||||
|
||||
v-list-item(@click='logout')
|
||||
v-list-item-icon
|
||||
v-icon mdi-logout
|
||||
v-icon(v-text='mdiLogout')
|
||||
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-icon(v-text='mdiDotsVertical')
|
||||
|
||||
v-btn(icon @click='clipboard(feedLink, "common.feed_url_copied")' aria-label='RSS')
|
||||
v-icon(color='orange') mdi-rss
|
||||
|
||||
v-btn(icon target='_blank' :href='feedLink' title='RSS' aria-label='RSS')
|
||||
v-icon(color='orange' v-text='mdiRss')
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import clipboard from '../assets/clipboard'
|
||||
import { mdiPlus, mdiShareVariant, mdiLogin, mdiDotsVertical, mdiLogout, mdiAccount, mdiCog, mdiRss } from '@mdi/js'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'Nav',
|
||||
data () {
|
||||
return { mdiPlus, mdiShareVariant, mdiLogout, mdiLogin, mdiDotsVertical, mdiAccount, mdiCog, mdiRss }
|
||||
},
|
||||
mixins: [clipboard],
|
||||
computed: {
|
||||
...mapState(['filters', 'settings']),
|
||||
feedLink () {
|
||||
const tags = this.filters.tags && this.filters.tags.join(',')
|
||||
const tags = this.filters.tags && this.filters.tags.map(encodeURIComponent).join(',')
|
||||
const places = this.filters.places && this.filters.places.join(',')
|
||||
let query = ''
|
||||
if (tags || places) {
|
||||
|
@ -80,32 +82,11 @@ export default {
|
|||
|
||||
return `${this.settings.baseurl}/feed/rss${query}`
|
||||
},
|
||||
could_add () {
|
||||
return (this.$auth.loggedIn || this.settings.allow_anon_event)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
logout () {
|
||||
this.$root.$message('common.logout_ok')
|
||||
this.$auth.logout()
|
||||
},
|
||||
async createTrustedInstance () {
|
||||
let url = this.instance_url
|
||||
if (!url.match(/^https?:\/\//)) {
|
||||
url = `https://${url}`
|
||||
}
|
||||
try {
|
||||
const instance = await this.$axios.$get(`${url}/.well-known/nodeinfo/2.0`)
|
||||
const trusted_instance = {
|
||||
url,
|
||||
name: instance.metadata.nodeName,
|
||||
description: instance.metadata.nodeDescription,
|
||||
place: instance.metadata.placeDescription
|
||||
}
|
||||
this.setSetting({ key: 'trusted_instances', value: this.settings.trusted_instances.concat(trusted_instance) })
|
||||
} catch (e) {
|
||||
this.$root.$message(e, { color: 'error' })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,20 +21,22 @@
|
|||
template(v-slot:selection="data")
|
||||
v-chip(v-bind="data.attrs"
|
||||
close
|
||||
:close-icon='mdiCloseCircle'
|
||||
@click:close='remove(data.item)'
|
||||
:input-value="data.selected")
|
||||
v-avatar(left)
|
||||
v-icon {{data.item.type === 'place' ? 'mdi-map-marker' : 'mdi-tag' }}
|
||||
v-icon(v-text="data.item.type === 'place' ? mdiMapMarker : mdiTag")
|
||||
span {{ data.item.label }}
|
||||
template(v-slot:item='{ item }')
|
||||
v-list-item-avatar
|
||||
v-icon {{item.type === 'place' ? 'mdi-map-marker' : 'mdi-tag' }}
|
||||
v-icon(v-text="item.type === 'place' ? mdiMapMarker : mdiTag")
|
||||
v-list-item-content
|
||||
v-list-item-title(v-text='item.label')
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { mdiMapMarker, mdiTag, mdiCloseCircle } from '@mdi/js'
|
||||
export default {
|
||||
name: 'Search',
|
||||
props: {
|
||||
|
@ -43,6 +45,7 @@ export default {
|
|||
},
|
||||
data () {
|
||||
return {
|
||||
mdiTag, mdiMapMarker, mdiCloseCircle,
|
||||
tmpfilter: null,
|
||||
search: ''
|
||||
}
|
||||
|
|
|
@ -7,17 +7,20 @@
|
|||
:left="left"
|
||||
:right="right"
|
||||
:timeout="timeout")
|
||||
v-icon.mr-3(color="white") {{icon}}
|
||||
v-icon.mr-3(color="white" v-text='icon')
|
||||
span {{ message }}
|
||||
template(v-slot:action="{ }")
|
||||
v-icon(size="16" @click="active = false") mdi-close-circle
|
||||
v-icon(size="16" @click="active = false" v-text='mdiCloseCircle')
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mdiAlert, mdiCloseCircle, mdiInformation } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
icon: 'md-alert',
|
||||
mdiAlert, mdiAlert, mdiCloseCircle, mdiInformation,
|
||||
icon: mdiInformation,
|
||||
color: 'secondary',
|
||||
bottom: true,
|
||||
top: false,
|
||||
|
@ -33,7 +36,7 @@ export default {
|
|||
this.active = true
|
||||
this.message = this.$t(message, opts)
|
||||
this.color = opts.color || 'secondary'
|
||||
this.icon = opts.icon || 'md-alert'
|
||||
this.icon = opts.icon || (this.color === 'success' ? mdiInformation : mdiAlert)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,11 +17,11 @@
|
|||
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>mdi-plus</v-icon> {{$t('common.add')}}
|
||||
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'
|
||||
:hide-default-footer='announcements.length<10'
|
||||
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
|
||||
:headers='headers'
|
||||
:items='announcements')
|
||||
template(v-slot:item.actions='{ item }')
|
||||
|
@ -36,11 +36,13 @@ import { mapActions } from 'vuex'
|
|||
import cloneDeep from 'lodash/cloneDeep'
|
||||
import Editor from '../Editor'
|
||||
import Announcement from '../Announcement'
|
||||
import { mdiPlus, mdiChevronRight, mdiChevronLeft } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
components: { Editor, Announcement },
|
||||
data () {
|
||||
return {
|
||||
mdiPlus, mdiChevronRight, mdiChevronLeft,
|
||||
valid: false,
|
||||
dialog: false,
|
||||
editing: false,
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
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.actions='{ item }')
|
||||
|
@ -16,6 +17,7 @@
|
|||
|
||||
</template>
|
||||
<script>
|
||||
import { mdiChevronLeft, mdiChevronRight } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
@ -23,6 +25,7 @@ export default {
|
|||
},
|
||||
data () {
|
||||
return {
|
||||
mdiChevronLeft, mdiChevronRight,
|
||||
valid: false,
|
||||
dialog: false,
|
||||
editing: false,
|
||||
|
@ -40,7 +43,7 @@ export default {
|
|||
try {
|
||||
await this.$axios.$put(`/event/confirm/${event.id}`)
|
||||
this.$emit('confirmed', event.id)
|
||||
this.$root.$message('event.confirmed')
|
||||
this.$root.$message('event.confirmed', { color: 'success' })
|
||||
} catch (e) {}
|
||||
},
|
||||
async remove (event) {
|
||||
|
|
|
@ -56,25 +56,29 @@
|
|||
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>mdi-plus</v-icon> {{$t('admin.add_instance')}}
|
||||
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 mdi-delete-forever
|
||||
v-icon(v-text='mdiDeleteForever')
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import { mapActions, mapState } from 'vuex'
|
||||
import get from 'lodash/get'
|
||||
import axios from 'axios'
|
||||
import { mdiDeleteForever, mdiPlus, mdiChevronLeft, mdiChevronRight } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
name: 'Federation',
|
||||
data ({ $store, $options }) {
|
||||
return {
|
||||
mdiDeleteForever, mdiPlus, mdiChevronLeft, mdiChevronRight,
|
||||
instance_url: '',
|
||||
instance_name: $store.state.settings.instance_name,
|
||||
instance_place: $store.state.settings.instance_place,
|
||||
|
@ -127,8 +131,8 @@ export default {
|
|||
key: 'trusted_instances',
|
||||
value: this.settings.trusted_instances.concat({
|
||||
url: this.instance_url,
|
||||
name: instance.data.metadata.nodeName,
|
||||
label: instance.data.metadata.nodeLabel
|
||||
name: get(instance, 'data.metadata.nodeName', ''),
|
||||
label: get(instance, 'data.metadata.nodeLabel', '')
|
||||
})
|
||||
})
|
||||
this.$refs.form.reset()
|
||||
|
|
|
@ -10,10 +10,11 @@
|
|||
: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)') {{item.blocked ? 'mdi-checkbox-intermediate' : 'mdi-checkbox-blank-outline'}}
|
||||
v-icon(@click='toggleBlock(item)' v-text='item.blocked ? mdiCheckboxIntermediate : mdiCheckboxBlankOutline')
|
||||
|
||||
v-col(:span='11')
|
||||
span {{$t('common.users')}}
|
||||
|
@ -22,45 +23,53 @@
|
|||
: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)') {{item.blocked?'mdi-checkbox-intermediate':'mdi-checkbox-blank-outline'}}
|
||||
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')
|
||||
: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 }')
|
||||
span {{item.ap_user.preferredUsername}}
|
||||
a(:href='item.ap_user.url || item.ap_user.ap_id' target='_blank') {{item.ap_user.preferredUsername}}
|
||||
template(v-slot:item.event='{ item }')
|
||||
span {{item.event.title}}
|
||||
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 mdi-dots-vertical
|
||||
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>mdi-eye-off</v-icon> {{$t('admin.hide_resource')}}
|
||||
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>mdi-eye</v-icon> {{$t('admin.show_resource')}}
|
||||
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>mdi-delete</v-icon> {{$t('admin.delete_resource')}}
|
||||
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'
|
||||
import get from 'lodash/get'
|
||||
import { mdiDelete, mdiEye, mdiEyeOff, mdiDotsVertical, mdiCheckboxIntermediate,
|
||||
mdiCheckboxBlankOutline, mdiChevronLeft, mdiChevronRight } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
name: 'Moderation',
|
||||
data () {
|
||||
return {
|
||||
mdiDelete, mdiEye, mdiEyeOff, mdiDotsVertical, mdiCheckboxIntermediate,
|
||||
mdiCheckboxBlankOutline, mdiChevronLeft, mdiChevronRight,
|
||||
instances: [],
|
||||
resources: [],
|
||||
users: [],
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
<template lang='pug'>
|
||||
v-container
|
||||
v-card-title {{$t('common.places')}}
|
||||
v-spacer
|
||||
v-text-field(v-model='search'
|
||||
:append-icon='mdiMagnify' outlined rounded
|
||||
label='Search'
|
||||
single-line hide-details)
|
||||
v-card-subtitle(v-html="$t('admin.place_description')")
|
||||
|
||||
v-dialog(v-model='dialog' width='600' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||
|
@ -29,19 +34,26 @@
|
|||
v-card-text
|
||||
v-data-table(
|
||||
:headers='headers'
|
||||
:items='places')
|
||||
:items='places'
|
||||
:hide-default-footer='places.length<5'
|
||||
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
|
||||
:search='search')
|
||||
template(v-slot:item.actions='{item}')
|
||||
v-btn(@click='editPlace(item)' color='primary' icon)
|
||||
v-icon mdi-pencil
|
||||
v-icon(v-text='mdiPencil')
|
||||
</template>
|
||||
<script>
|
||||
import { mapState, mapActions } from 'vuex'
|
||||
import { mdiPencil, mdiChevronLeft, mdiChevronRight } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
mdiPencil, mdiChevronRight, mdiChevronLeft,
|
||||
loading: false,
|
||||
dialog: false,
|
||||
valid: false,
|
||||
search: '',
|
||||
place: { name: '', address: '', id: null },
|
||||
headers: [
|
||||
{ value: 'name', text: 'Name' },
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
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 )"
|
||||
|
@ -53,7 +52,7 @@ export default {
|
|||
async testSMTP () {
|
||||
this.loading = true
|
||||
try {
|
||||
this.setSetting({ key: 'smtp', value: this.smtp })
|
||||
await this.setSetting({ key: 'smtp', value: this.smtp })
|
||||
await this.$axios.$post('/settings/smtp', { smtp: this.smtp })
|
||||
this.$root.$message(this.$t('admin.smtp_test_success', { admin_email: this.admin_email }), { color: 'success' })
|
||||
} catch (e) {
|
||||
|
@ -68,7 +67,9 @@ export default {
|
|||
}
|
||||
},
|
||||
done () {
|
||||
if (this.smtp.auth.pass) {
|
||||
this.setSetting({ key: 'smtp', value: JSON.parse(JSON.stringify(this.smtp)) })
|
||||
}
|
||||
this.$emit('close')
|
||||
},
|
||||
|
||||
|
|
|
@ -53,9 +53,9 @@
|
|||
|
||||
v-card-actions
|
||||
v-btn(text @click='showSMTP=true')
|
||||
<v-icon v-if='showSMTPAlert' color='error'>mdi-alert</v-icon> {{$t('admin.show_smtp_setup')}}
|
||||
<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 mdi-arrow-right
|
||||
v-icon(v-text='mdiArrowRight')
|
||||
|
||||
|
||||
</template>
|
||||
|
@ -65,6 +65,7 @@ import { mapActions, mapState } from 'vuex'
|
|||
import moment from 'dayjs'
|
||||
import tzNames from './tz.json'
|
||||
import locales from '../../locales/esm'
|
||||
import { mdiAlert, mdiArrowRight } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
@ -74,6 +75,7 @@ export default {
|
|||
name: 'Settings',
|
||||
data ({ $store }) {
|
||||
return {
|
||||
mdiAlert, mdiArrowRight,
|
||||
title: $store.state.settings.title,
|
||||
description: $store.state.settings.description,
|
||||
locales: Object.keys(locales).map(locale => ({ value: locale, text: locales[locale] })),
|
||||
|
@ -83,7 +85,7 @@ export default {
|
|||
computed: {
|
||||
...mapState(['settings']),
|
||||
showSMTPAlert () {
|
||||
return !this.setup && (!this.settings.admin_email || !this.settings.smtp || !this.settings.smtp.host || !this.settings.smtp.user)
|
||||
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 },
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
@change='uploadLogo'
|
||||
accept='image/*')
|
||||
template(slot='append-outer')
|
||||
v-btn(color='warning' text @click='resetLogo') <v-icon>mdi-restore</v-icon> {{$t('common.reset')}}
|
||||
v-btn(color='warning' text @click='resetLogo') <v-icon v-text='mdiRestore'></v-icon> {{$t('common.reset')}}
|
||||
v-img(:src='`${settings.baseurl}/logo.png?${logoKey}`'
|
||||
max-width="60px" max-height="60px" contain)
|
||||
|
||||
|
@ -50,8 +50,8 @@
|
|||
|
||||
v-card-title {{$t('admin.footer_links')}}
|
||||
v-card-text
|
||||
v-btn(color='primary' text @click='openLinkModal') <v-icon>mdi-plus</v-icon> {{$t('admin.add_link')}}
|
||||
v-btn(color='warning' text @click='reset') <v-icon>mdi-restore</v-icon> {{$t('common.reset')}}
|
||||
v-btn(color='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 in settings.footerLinks'
|
||||
|
@ -61,16 +61,18 @@
|
|||
v-list-item-subtitle {{link.href}}
|
||||
v-list-item-action
|
||||
v-btn(icon color='error' @click.stop='removeFooterLink(link)')
|
||||
v-icon mdi-delete-forever
|
||||
v-icon(v-text='mdiDeleteForever')
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import { mapActions, mapState } from 'vuex'
|
||||
import { mdiDeleteForever, mdiRestore, mdiPlus } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
name: 'Theme',
|
||||
data () {
|
||||
return {
|
||||
mdiDeleteForever, mdiRestore, mdiPlus,
|
||||
valid: false,
|
||||
logoKey: 0,
|
||||
link: { href: '', label: '' },
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
v-card-title {{$t('common.users')}}
|
||||
v-spacer
|
||||
v-text-field(v-model='search'
|
||||
append-icon='mdi-magnify' outlined rounded
|
||||
:append-icon='mdiMagnify' outlined rounded
|
||||
label='Search'
|
||||
single-line hide-details)
|
||||
|
||||
v-btn(color='primary' text @click='newUserDialog = true') <v-icon>mdi-plus</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')
|
||||
|
@ -20,7 +20,7 @@
|
|||
: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') {{$t('admin.user_add_help')}}
|
||||
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')}}
|
||||
|
@ -32,10 +32,11 @@
|
|||
: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') mdi-check
|
||||
v-icon(v-else color='warning') mdi-close
|
||||
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)'
|
||||
|
@ -49,6 +50,7 @@
|
|||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import get from 'lodash/get'
|
||||
import { mdiClose, mdiMagnify, mdiCheck, mdiPlus, mdiInformation, mdiChevronLeft, mdiChevronRight } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
name: 'Users',
|
||||
|
@ -57,6 +59,7 @@ export default {
|
|||
},
|
||||
data () {
|
||||
return {
|
||||
mdiClose, mdiMagnify, mdiCheck, mdiPlus, mdiInformation, mdiChevronLeft, mdiChevronRight,
|
||||
newUserDialog: false,
|
||||
valid: false,
|
||||
new_user: {
|
||||
|
|
27
docs/.api.md
27
docs/.api.md
|
@ -1,10 +1,34 @@
|
|||
|
||||
## Get events
|
||||
|
||||
GET
|
||||
{: .label .label-green}
|
||||
|
||||
**`/api/events`**
|
||||
|
||||
|
||||
|
||||
**Params**
|
||||
|
||||
| start | `integer` | start timestamp (default: now) |
|
||||
| end | `integer` | end timestamp (optional) |
|
||||
| tags | `array` | List of tags |
|
||||
| places | `array` | List of places |
|
||||
| max | `integer` | Max events |
|
||||
| show_recurrent | `boolean` | Show also recurrent events (default: as choosen in admin settings) |
|
||||
|
||||
|
||||
***Example***
|
||||
[https://demo.gancio.org/api/events](https://demo.gancio.org/api/events)
|
||||
[usage example](https://framagit.org/les/gancio/-/blob/master/webcomponents/src/GancioEvents.svelte#L18-42)
|
||||
---
|
||||
|
||||
## Add a new event
|
||||
|
||||
POST
|
||||
{: .label .label-orange}
|
||||
|
||||
**`/event`**
|
||||
**`/api/event`**
|
||||
|
||||
> info "info"
|
||||
> `Content-Type` has to be `multipart/form-data` to support image upload
|
||||
|
@ -21,7 +45,6 @@ POST
|
|||
| tags | `array` | List of tags |
|
||||
| recurrent | `object` | Recurrent event details |
|
||||
| recurrent.frequency | `string` | could be `1w` or `2w` |
|
||||
| recurrent.type | `string` | not used |
|
||||
| recurrent.days | `array` | array of days |
|
||||
| image | `image` | Image |
|
||||
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (6.0.3.7)
|
||||
activesupport (6.0.4.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
zeitwerk (~> 2.2, >= 2.2.2)
|
||||
addressable (2.7.0)
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
colorator (1.1.0)
|
||||
concurrent-ruby (1.1.8)
|
||||
em-websocket (0.5.2)
|
||||
concurrent-ruby (1.1.9)
|
||||
em-websocket (0.5.3)
|
||||
eventmachine (>= 0.12.9)
|
||||
http_parser.rb (~> 0.6.0)
|
||||
http_parser.rb (~> 0)
|
||||
eventmachine (1.2.7)
|
||||
ffi (1.15.1)
|
||||
ffi (1.15.4)
|
||||
forwardable-extended (2.6.0)
|
||||
gemoji (3.0.1)
|
||||
html-pipeline (2.14.0)
|
||||
activesupport (>= 2)
|
||||
nokogiri (>= 1.4)
|
||||
http_parser.rb (0.6.0)
|
||||
i18n (1.8.10)
|
||||
http_parser.rb (0.8.0)
|
||||
i18n (1.8.11)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jekyll (4.2.0)
|
||||
jekyll (4.2.1)
|
||||
addressable (~> 2.4)
|
||||
colorator (~> 1.0)
|
||||
em-websocket (~> 0.5)
|
||||
|
@ -62,28 +62,24 @@ GEM
|
|||
kramdown-parser-gfm (1.1.0)
|
||||
kramdown (~> 2.0)
|
||||
liquid (4.0.3)
|
||||
listen (3.5.1)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
listen (3.7.0)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
mercenary (0.4.0)
|
||||
mini_magick (4.11.0)
|
||||
mini_portile2 (2.5.3)
|
||||
minitest (5.14.4)
|
||||
nokogiri (1.11.7)
|
||||
mini_portile2 (~> 2.5.0)
|
||||
nokogiri (1.12.5-x86_64-linux)
|
||||
racc (~> 1.4)
|
||||
pathutil (0.16.2)
|
||||
forwardable-extended (~> 2.6)
|
||||
premonition (4.0.1)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
public_suffix (4.0.6)
|
||||
racc (1.5.2)
|
||||
rake (13.0.3)
|
||||
rb-fsevent (0.11.0)
|
||||
racc (1.6.0)
|
||||
rake (13.0.6)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
rexml (3.2.5)
|
||||
rouge (3.26.0)
|
||||
rouge (3.26.1)
|
||||
safe_yaml (1.0.5)
|
||||
sassc (2.4.0)
|
||||
ffi (~> 1.9)
|
||||
|
@ -92,14 +88,14 @@ GEM
|
|||
thread_safe (0.3.6)
|
||||
tzinfo (1.2.9)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo-data (1.2021.1)
|
||||
tzinfo-data (1.2021.5)
|
||||
tzinfo (>= 1.0.0)
|
||||
unicode-display_width (1.7.0)
|
||||
unicode-display_width (1.8.0)
|
||||
wdm (0.1.1)
|
||||
zeitwerk (2.4.2)
|
||||
zeitwerk (2.5.1)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
jekyll
|
||||
|
@ -114,4 +110,4 @@ DEPENDENCIES
|
|||
wdm (~> 0.1.0)
|
||||
|
||||
BUNDLED WITH
|
||||
2.1.4
|
||||
2.2.27
|
||||
|
|
1205
docs/assets/js/gancio-events.es.js
Normal file
1205
docs/assets/js/gancio-events.es.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -8,12 +8,77 @@ nav_order: 10
|
|||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
### 1.4.3 - 10 mar '22
|
||||
- fix [#140](https://framagit.org/les/gancio/-/issues/140) - Invalid date
|
||||
- fix [#141](https://framagit.org/les/gancio/-/issues/141) - Cannot change logo
|
||||
- fix same day events
|
||||
- add missing icons in admin
|
||||
- prepare multisite settings
|
||||
- improve initialization
|
||||
- start unit testing API (it's never too late)
|
||||
- make it clear that the admin password must be saved at the end of setup
|
||||
|
||||
### 1.4.1 - 4 mar '22
|
||||
- add gl/galego locale, thanks @xosem
|
||||
- fix import redirect loop
|
||||
- add missing icons (close, repeat, arrows ...)
|
||||
- turn rss icon into a real link to improve a11y
|
||||
- force seconds to 0 for each events, fix recurring events starting date issue
|
||||
- fix next/prev selection on same datetime events
|
||||
- improve moderation UI (add author and event link + format creation date)
|
||||
- refactoring resource UI from fedi
|
||||
|
||||
### 1.4.0 - 9 feb '22
|
||||
- improve Cumulative Layout Shift
|
||||
- remove filename as default media label to avoid leak metadata
|
||||
- add endData to microdata
|
||||
- security fix with filtering settings, avoid sharing SMTP pass with front-end
|
||||
- fix broken SMTP
|
||||
- remove global materialicons / vuetify css, use threeshake and @nuxt/vuetify (really improve lighthouse score)
|
||||
- new Dockerfile using node:17.4-slim as base img (from 1.5Gb to ~800Mb)
|
||||
- add XSS and path traversal mitigation
|
||||
- improve a11y
|
||||
- update deps
|
||||
|
||||
### 1.3.3 - 1 feb '22
|
||||
- security fix, avoid sharing smtp pass with front-end
|
||||
|
||||
### 1.3.2 - 1 feb '22
|
||||
- fix webcomponent for event without img
|
||||
|
||||
### 1.3.1 - 1 feb '22
|
||||
- inherits tags in recurring events [#138](https://framagit.org/les/gancio/-/issues/138)
|
||||
- you can now skip an occurrence of a recurring event
|
||||
- fix `show_recurrent` event in webcomponent and API
|
||||
- add new webcomponent `sidebar` attribute and a [`fullwith` layout](https://gancio.org/usage/embed#embed-event-lists)
|
||||
|
||||
### 1.3.0 - 26 gen '22
|
||||
- add mariadb support
|
||||
- add [microdata](https://developer.mozilla.org/en-US/docs/Web/HTML/Microdata) support
|
||||
- support db setup via environment variables (used in updated `docker-compose.yml` files)
|
||||
- improve rss feed:
|
||||
- fix validation
|
||||
- add enclusure media for featured image
|
||||
- add categories
|
||||
- fix typo in export code
|
||||
- add theme attribute to gancio-events webcomponent (dark/light)
|
||||
- increase thumbs quality
|
||||
- improve logo for light theme
|
||||
- improve [wordpress plugin](https://wordpress.org/plugins/wpgancio/)
|
||||
- add \[gancio-event\] and \[gancio-events\] shortcode
|
||||
- allow gancio-events / gancio-event tags in editor
|
||||
- automatically enqueue webcomponent script
|
||||
- tags/places filters are now inclusive not exclusive
|
||||
- fix image undefined alternative text
|
||||
- update documentation, dependencies, translations
|
||||
|
||||
### 1.2.2 - 7 dic '21
|
||||
- shiny new gancio-event\[s\] webcomponents => [docs](https://gancio.org/usage/embed)
|
||||
- new backend plugin system
|
||||
- new backend plugin system => [docs](https://gancio.org/dev/plugins)
|
||||
- improve media focal point selection
|
||||
- improve non-js experience (load img, use native lazy loading)
|
||||
- improve user_confirm / recover code flow
|
||||
- permit admins to choose user password (usefull on instance without SMTP configuration)
|
||||
- fix task manager exception
|
||||
- fix db initialization when a custom setup is used, #131
|
||||
- remove vue-clipboard2 dependency due to [this](https://github.com/euvl/v-clipboard/issues/18) bug and using a [native with fallback mixin instead](./assets/clipboard.js)
|
||||
|
|
|
@ -10,15 +10,37 @@ nav_order: 8
|
|||
1. TOC
|
||||
{:toc}
|
||||
|
||||
## Get events
|
||||
|
||||
GET
|
||||
{: .label .label-green}
|
||||
|
||||
**`/api/events`**
|
||||
|
||||
|
||||
|
||||
**Params**
|
||||
|
||||
| start | `integer` | start timestamp (default: now) |
|
||||
| end | `integer` | end timestamp (optional) |
|
||||
| tags | `array` | List of tags |
|
||||
| places | `array` | List of places |
|
||||
| max | `integer` | Max events |
|
||||
| show_recurrent | `boolean` | Show also recurrent events (default: as choosen in admin settings) |
|
||||
|
||||
|
||||
***Example***
|
||||
[https://demo.gancio.org/api/events](https://demo.gancio.org/api/events)
|
||||
[usage example](https://framagit.org/les/gancio/-/blob/master/webcomponents/src/GancioEvents.svelte#L18-42)
|
||||
|
||||
---
|
||||
|
||||
## Add a new event
|
||||
|
||||
POST
|
||||
{: .label .label-orange}
|
||||
|
||||
**`/event`**
|
||||
**`/api/event`**
|
||||
|
||||
> info "info"
|
||||
> `Content-Type` has to be `multipart/form-data` to support image upload
|
||||
|
@ -35,7 +57,6 @@ POST
|
|||
| tags | `array` | List of tags |
|
||||
| recurrent | `object` | Recurrent event details |
|
||||
| recurrent.frequency | `string` | could be `1w` or `2w` |
|
||||
| recurrent.type | `string` | not used |
|
||||
| recurrent.days | `array` | array of days |
|
||||
| image | `image` | Image |
|
||||
|
||||
|
|
51
docs/dev/plugin.md
Normal file
51
docs/dev/plugin.md
Normal file
|
@ -0,0 +1,51 @@
|
|||
---
|
||||
layout: default
|
||||
title: Plugins
|
||||
permalink: /dev/plugins
|
||||
nav_order: 2
|
||||
parent: Hacking
|
||||
---
|
||||
|
||||
## Plugins
|
||||
Since **v.1.2.2** you can write your own plugin that react to event related action (create,update,delete).
|
||||
|
||||
> info "What this is useful for?"
|
||||
> - Do you want to create a post in your wordpress website each time an event is published? [hint](http://wp-api.org/node-wpapi/using-the-client/#creating-posts)
|
||||
> - Do you want to send a summary notification of daily events via mail?
|
||||
> - Notify a telegram group or share via twitter?
|
||||
>
|
||||
> [**<u>Please share your plugins or your needs</u>**](/contacts)
|
||||
|
||||
Plugins should be inside `./plugins` directory, this is an example:
|
||||
|
||||
```js
|
||||
const plugin = {
|
||||
gancio: null,
|
||||
load (gancio) {
|
||||
console.error('Plugin GancioPluginExample loaded!')
|
||||
plugin.gancio = gancio
|
||||
},
|
||||
|
||||
onEventCreate (event) {
|
||||
const eventLink = `${plugin.gancio.settings.baseurl}/event/${event.slug}`
|
||||
if (!event.is_visible) {
|
||||
console.error(`Unconfirmed event created: ${event.title} / ${eventLink}`)
|
||||
} else {
|
||||
console.error(`Event created: ${event.title} / ${eventLink}`)
|
||||
}
|
||||
},
|
||||
|
||||
onEventUpdate (event) {
|
||||
console.error(`Event "${event.title}" updated`)
|
||||
},
|
||||
|
||||
onEventDelete (event) {
|
||||
console.error(`Event "${event.title}" deleted`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = plugin
|
||||
```
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
FROM node:buster
|
||||
FROM node:17.4-slim
|
||||
RUN yarn global remove gancio || true
|
||||
RUN yarn cache clean
|
||||
RUN yarn global add --latest --production --silent https://gancio.org/latest.tgz 2> /dev/null
|
||||
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" ]
|
||||
|
|
20
docs/docker/docker-compose.yml
Normal file
20
docs/docker/docker-compose.yml
Normal file
|
@ -0,0 +1,20 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
gancio:
|
||||
build: .
|
||||
restart: always
|
||||
image: node:17.4-slim
|
||||
container_name: gancio
|
||||
environment:
|
||||
- PATH=$PATH:/home/node/.yarn/bin
|
||||
- GANCIO_DATA=/home/node/data
|
||||
- NODE_ENV=production
|
||||
- GANCIO_DB_DIALECT=sqlite
|
||||
- GANCIO_DB_STORAGE=./gancio.sqlite
|
||||
entrypoint: /entrypoint.sh
|
||||
command: gancio start --docker
|
||||
volumes:
|
||||
- ./data:/home/node/data
|
||||
ports:
|
||||
- "127.0.0.1:13120:13120"
|
37
docs/docker/mariadb/docker-compose.yml
Normal file
37
docs/docker/mariadb/docker-compose.yml
Normal file
|
@ -0,0 +1,37 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: mariadb
|
||||
container_name: mariadb
|
||||
volumes:
|
||||
- ./db:/var/lib/mysql
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
environment:
|
||||
- MARIADB_USER=gancio
|
||||
- MARIADB_DATABASE=gancio
|
||||
- MARIADB_PASSWORD=gancio
|
||||
- MARIADB_RANDOM_ROOT_PASSWORD=yes
|
||||
restart: always
|
||||
gancio:
|
||||
build: .
|
||||
restart: always
|
||||
image: node:17.4-slim
|
||||
container_name: gancio
|
||||
environment:
|
||||
- PATH=$PATH:/home/node/.yarn/bin
|
||||
- GANCIO_DATA=/home/node/data
|
||||
- NODE_ENV=production
|
||||
- GANCIO_DB_DIALECT=mariadb
|
||||
- GANCIO_DB_HOST=db
|
||||
- GANCIO_DB_DATABASE=gancio
|
||||
- GANCIO_DB_USERNAME=gancio
|
||||
- GANCIO_DB_PASSWORD=gancio
|
||||
command: gancio start --docker
|
||||
entrypoint: /entrypoint.sh
|
||||
volumes:
|
||||
- ./data:/home/node/data
|
||||
ports:
|
||||
- "127.0.0.1:13120:13120"
|
||||
depends_on:
|
||||
- db
|
|
@ -18,12 +18,17 @@ services:
|
|||
gancio:
|
||||
build: .
|
||||
restart: always
|
||||
image: node:buster
|
||||
image: node:17.4-slim
|
||||
container_name: gancio
|
||||
environment:
|
||||
- PATH=$PATH:/home/node/.yarn/bin
|
||||
- GANCIO_DATA=/home/node/data
|
||||
- NODE_ENV=production
|
||||
- GANCIO_DB_DIALECT=postgres
|
||||
- GANCIO_DB_HOST=db
|
||||
- GANCIO_DB_DATABASE=gancio
|
||||
- GANCIO_DB_USERNAME=gancio
|
||||
- GANCIO_DB_PASSWORD=gancio
|
||||
command: gancio start --docker
|
||||
entrypoint: /entrypoint.sh
|
||||
volumes:
|
||||
|
|
|
@ -4,12 +4,14 @@ services:
|
|||
gancio:
|
||||
build: .
|
||||
restart: always
|
||||
image: node:buster
|
||||
image: node:17.4-slim
|
||||
container_name: gancio
|
||||
environment:
|
||||
- PATH=$PATH:/home/node/.yarn/bin
|
||||
- GANCIO_DATA=/home/node/data
|
||||
- NODE_ENV=production
|
||||
- GANCIO_DB_DIALECT=sqlite
|
||||
- GANCIO_DB_STORAGE=./gancio.sqlite
|
||||
entrypoint: /entrypoint.sh
|
||||
command: gancio start --docker
|
||||
volumes:
|
||||
|
|
|
@ -1,33 +1,100 @@
|
|||
---
|
||||
layout: default
|
||||
title: Embed events
|
||||
title: Embed events in webpages
|
||||
permalink: /usage/embed
|
||||
nav_order: 1
|
||||
parent: Usage
|
||||
---
|
||||
|
||||
## Embed event
|
||||
|
||||
|
||||
## Embed a single event or a list of events in your webpage
|
||||
{: .no_toc }
|
||||
|
||||
You can embed a list of filtered events or a specific event card in your webpage using a classic old-school `iframe` or a shiny new webcomponent.
|
||||
|
||||
### Webcomponents
|
||||
The webcomponent require a small js to be loaded in your page:
|
||||
```javascript
|
||||
1. TOC
|
||||
{:toc}
|
||||
## Webcomponents
|
||||
[Webcomponents](https://www.webcomponents.org/introduction) usage requires a small (~5kB gzipped) js script to be loaded in your page (note that you should use your instance name):
|
||||
```html
|
||||
<script src='https://demo.gancio.org/gancio-events.es.js'></script>
|
||||
```
|
||||
|
||||
#### embed a single event
|
||||
> you can copy the code in **event page > Embed > Copy**
|
||||
### Embed a single event
|
||||
To embed an event in webpages you use `<gancio-event>` custom element, you can copy the required code in **event's page > Embed > Copy**, should be like the following:
|
||||
|
||||
<script src='https://demo.gancio.org/gancio-events.es.js'></script>
|
||||
<gancio-event id=17 baseurl='https://demo.gancio.org'></gancio-event>
|
||||
```javascript
|
||||
```html
|
||||
<gancio-event id=17 baseurl='https://demo.gancio.org'></gancio-event>
|
||||
```
|
||||
|
||||
#### embed event lists
|
||||
> you can copy the code in **Export > List > Copy**
|
||||
<gancio-events baseurl='https://demo.gancio.org'></gancio-events>
|
||||
```javascript
|
||||
<gancio-event baseurl='https://demo.gancio.org'></gancio-event>
|
||||
<script src='/assets/js/gancio-events.es.js'></script>
|
||||
<gancio-event id=17 baseurl='https://demo.gancio.org'></gancio-event>
|
||||
|
||||
|
||||
### Embed event lists
|
||||
You can also embed a list of events using `<gancio-events>` custom element, you can copy the required code in **Export > List > Copy**
|
||||
|
||||
|
||||
```html
|
||||
<gancio-events baseurl='https://gancio.cisti.org'>
|
||||
<a href='https://gancio.cisti.org'>Gancio Events</a>
|
||||
</gancio-events>
|
||||
```
|
||||
|
||||
<script>
|
||||
var theme = 'light';
|
||||
var sidebar = 'true';
|
||||
function toggleDark() {
|
||||
var items = document.getElementsByTagName('gancio-events');
|
||||
theme = theme === 'dark' ? 'light' : 'dark';
|
||||
items[0].setAttribute('theme', theme );
|
||||
}
|
||||
|
||||
function toggleSidebar() {
|
||||
var items = document.getElementsByTagName('gancio-events');
|
||||
sidebar = sidebar === 'true' ? 'false' : 'true';
|
||||
items[0].setAttribute('sidebar', sidebar );
|
||||
}
|
||||
|
||||
|
||||
function changeMax(value) {
|
||||
var items = document.getElementsByTagName('gancio-events');
|
||||
items[0].setAttribute('maxlength', value);
|
||||
}
|
||||
|
||||
function changeTitle (title) {
|
||||
var items = document.getElementsByTagName('gancio-events');
|
||||
items[0].setAttribute('title', title)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
label {
|
||||
display: block;
|
||||
margin: 0px;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
<label><input type='checkbox' onchange="toggleDark()"/> Dark</label>
|
||||
<label><input type='checkbox' checked onchange="toggleSidebar()"/> Sidebar</label>
|
||||
<label>Max items <input value=4 type='number' label='Max items' onchange="changeMax(this.value)"/></label>
|
||||
<label>Title <input value='Gancio' type='text' onkeyup="changeTitle(this.value)"/></label>
|
||||
<gancio-events sidebar='true' title='Gancio' theme='light' maxlength=4 baseurl='https://gancio.cisti.org'><a href='https://gancio.cisti.org'>Gancio Events</a></gancio-events>
|
||||
|
||||
> info "Customize"
|
||||
> Note that you can modify the title (or completely remove it using an empty `title` param),
|
||||
> you can limit the list to a maximum number of events using the `maxlength` parameter and filter events by `tags` or `places` using that parameters (it's easier using **gancio** than to explain it here)
|
||||
|
||||
|
||||
## IFrame
|
||||
You can also use the old iframe method
|
||||
<iframe src='https://demo.gancio.org/embed/17' style="width: 410px; border: none; height: 210px; overflow: hidden;"></iframe>
|
||||
|
||||
```html
|
||||
<iframe src='https://demo.gancio.org/embed/17' style="width: 410px; border: none; height: 210px; overflow: hidden;"></iframe>
|
||||
```
|
||||
|
||||
|
||||
## Wordpress
|
||||
To embed an event or a list of events into a [WordPress](https://wordpress.com) website you can use the [WPGancio](https://wordpress.org/plugins/wpgancio/) plugin, this allows you to use webcomponents and shortcodes and automatically includes the needed script in each page and post.
|
|
@ -18,22 +18,22 @@ A shared agenda for local communities.
|
|||
## Some relevant key features:
|
||||
|
||||
- **Focus on content** not on people:
|
||||
nowhere on gancio appears the identity of who published the event, not even under a nickname, not even to administrators (except in the db). This is not an ego-friendly platform.
|
||||
nowhere on gancio does the identity of who posted an event appear, not even under a nickname, not even to administrators (except in the db). This is not an ego-friendly platform.
|
||||
|
||||
- **Visitors first**. We do not want logged user to get more features than random visitor. We do not want users to register, except to publish events and even in this case you can publish an anonymous event.
|
||||
- **Visitors first**. We do not want logged user to get more features than random visitor. We don't want users to register, except to post events and even then you can post an anonymous event.
|
||||
|
||||
- **Anonymous events**: optionally a visitor can create events without being registered (an administrator has to confirm them)
|
||||
- **Anonymous events**: optionally a visitor can create events without being registered (an administrator must confirm them)
|
||||
|
||||
- **We are not interested in making hits** so we export events in many ways, via RSS feeds, via global or individual ics, incorporating lists of events or single event via iframe on other websites and via [AP]({% link federation.md %})
|
||||
- **We don't care about making hits** so we export events in many ways: via RSS feeds, via global or individual ics, allowing you to embed list of events or single event via [iframe or webcomponent]({% link embed.md %}) on other websites, via [AP]({% link federation.md %}), [microdata](https://developer.mozilla.org/en-US/docs/Web/HTML/Microdata) and [microformat](https://developer.mozilla.org/en-US/docs/Web/HTML/microformats#h-event)
|
||||
|
||||
- Very easy UI
|
||||
- Multidays events support (festival, conferences...)
|
||||
- Recurrent events support (each monday, each two monday, each monday and friday, each two saturday, etc.)
|
||||
- Filters events for tags or places
|
||||
- Multi-day events (festival, conferences...)
|
||||
- Recurring events (each monday, each two monday, each monday and friday, each two saturday, etc.)
|
||||
- Filter events for tags or places
|
||||
- RSS and ICS export (with filters)
|
||||
- embeddable iframe ([example](https://gancio.cisti.org/embed/list?title=Upcoming events))
|
||||
- embed your events in your website with [webcomponents]({% link embed.md %}) or iframe ([example](https://gancio.cisti.org/embed/list?title=Upcoming events))
|
||||
- boost / bookmark / comment events from the fediverse!
|
||||
- Lot of configurations available (user registration open/close, enable federation, enable recurrent events)
|
||||
- Lot of configurations available (dark/light theme, user registration open/close, enable federation, enable recurring events)
|
||||
|
||||
### License
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ sudo -u postgres pg_dump -Fc gancio > gancio.dump
|
|||
1. Archive database, configuration, custom user locales, logs, images and thumbnails
|
||||
```bash
|
||||
sudo tar -czf gancio-$(date +%Y-%m-%d-%H%M%S)-backup.tgz \
|
||||
$(ls -d config.json uploads user_locale db.sqlite gancio.dump postgres data logs 2> /dev/null)
|
||||
$(ls -d config.json uploads user_locale db.sqlite gancio.dump postgres data db logs 2> /dev/null)
|
||||
```
|
||||
|
||||
> info "Automatic backup"
|
||||
|
|
|
@ -7,7 +7,7 @@ parent: Install
|
|||
|
||||
## Configuration
|
||||
{: .no_toc }
|
||||
`gancio` configuration is done during installation process but you can change it editing the configuration file. Note that you can always re-run gancio with `--setup` flag to use the interactive setup.
|
||||
`gancio` configuration is done during installation process but you can change it editing the configuration file.
|
||||
The configuration file shoud be a `.json` or a `.js` file and could be specified using the `--config` flag.
|
||||
|
||||
- <small>eg. `gancio start --config ./config.json`</small>
|
||||
|
@ -27,7 +27,7 @@ This probably support unix socket too
|
|||
```
|
||||
|
||||
- ### Database
|
||||
DB configuration, look [here](https://sequelize.org/master/class/lib/sequelize.js~Sequelize.html#instance-constructor-constructor) for options.
|
||||
DB configuration, look [here](https://sequelize.org/v6/class/src/sequelize.js~Sequelize.html#instance-constructor-constructor) for options.
|
||||
```json
|
||||
"db": {
|
||||
"dialect": "sqlite",
|
||||
|
|
|
@ -21,8 +21,8 @@ sudo npm install -g yarn
|
|||
```
|
||||
<small>[source](https://github.com/nodesource/distributions/blob/master/README.md)</small>
|
||||
|
||||
|
||||
1. Setup with postgreSQL __(optional as you can choose sqlite)__
|
||||
#### Choose you database (sqlite, postgresql, mariadb, mysql)
|
||||
1. Setup using postgreSQL __(optional)__
|
||||
```bash
|
||||
sudo apt-get install postgresql
|
||||
# Create the database
|
||||
|
@ -32,13 +32,25 @@ postgres=# create user gancio with encrypted password 'gancio';
|
|||
postgres=# grant all privileges on database gancio to gancio;
|
||||
```
|
||||
|
||||
1. Setup using MariaDB (__optional__)
|
||||
```bash
|
||||
sudo apt-get install mariadb
|
||||
sudo mysql
|
||||
MariaDB [(none)]> create database gancio;
|
||||
Query OK, 1 row affected (0.001 sec)
|
||||
MariaDB [(none)]> create user gancio identified by 'gancio';
|
||||
Query OK, 0 rows affected (0.011 sec)
|
||||
MariaDB [(none)]> grant all privileges on gancio.* to gancio;
|
||||
Query OK, 0 rows affected (0.009 sec)
|
||||
```
|
||||
|
||||
1. Create a user to run gancio from
|
||||
```bash
|
||||
sudo adduser --group --system --shell /bin/false --home /opt/gancio gancio
|
||||
```
|
||||
1. Install Gancio
|
||||
```bash
|
||||
sudo yarn global add --silent {{site.url}}/latest.tgz 2> /dev/null
|
||||
sudo yarn global add --silent {{site.url}}/latest.tgz
|
||||
```
|
||||
|
||||
1. Setup systemd service and reload systemd
|
||||
|
@ -66,6 +78,6 @@ sudo systemctl start gancio
|
|||
```bash
|
||||
yarn global remove gancio
|
||||
yarn cache clean
|
||||
yarn global add --silent {{site.url}}/latest.tgz 2> /dev/null
|
||||
sudo service gancio restart
|
||||
yarn global add --silent {{site.url}}/latest.tgz
|
||||
sudo systemctl restart gancio
|
||||
```
|
||||
|
|
|
@ -11,76 +11,51 @@ nav_order: 2
|
|||
1. TOC
|
||||
{:toc}
|
||||
|
||||
## Initial setup
|
||||
|
||||
|
||||
- __You must have the following dependencies installed: Docker, Docker Compose and Nginx__
|
||||
## Setup
|
||||
|
||||
Make sure to have [Docker Engine](https://docs.docker.com/engine/install/),
|
||||
[Docker Compose](https://docs.docker.com/compose/install/) and [nginx](https://nginx.org/en/docs/install.html) installed:
|
||||
```bash
|
||||
sudo apt install docker docker-compose nginx
|
||||
```
|
||||
or
|
||||
1. [Install docker](https://docs.docker.com/engine/install/)
|
||||
1. [Install docker-compose](https://docs.docker.com/compose/install/)
|
||||
1. [Install nginx](https://nginx.org/en/docs/install.html)
|
||||
|
||||
- __Create a directory where everything related to gancio is stored__
|
||||
Create a directory where everything related to gancio is stored:
|
||||
```bash
|
||||
mkdir -p /opt/gancio
|
||||
cd /opt/gancio
|
||||
```
|
||||
|
||||
## Use sqlite
|
||||
<div class='code-example bg-grey-lt-100' markdown="1">
|
||||
|
||||
1. **Download docker-compose.yml and Dockerfile**
|
||||
Download `Dockerfile` and `entrypoint.sh`:
|
||||
```bash
|
||||
wget {{site.url}}{% link /docker/Dockerfile %}
|
||||
wget {{site.url}}{% link /docker/entrypoint.sh %}
|
||||
wget {{site.url}}{% link /docker/sqlite/docker-compose.yml %}
|
||||
```
|
||||
|
||||
|
||||
1. Build docker image
|
||||
```
|
||||
docker-compose build
|
||||
```
|
||||
</div>
|
||||
|
||||
## Use postgreSQL
|
||||
<div class='code-example bg-grey-lt-100' markdown="1">
|
||||
|
||||
1. **Download docker-compose.yml and Dockerfile**
|
||||
Download `docker-compose.yml` choosing your preferred database dialect between `sqlite`, `postgres` and `mariadb`:
|
||||
```bash
|
||||
wget {{site.url}}{% link /docker/Dockerfile %}
|
||||
wget {{site.url}}{% link /docker/entrypoint.sh %}
|
||||
wget {{site.url}}{% link /docker/postgres/docker-compose.yml %}
|
||||
DB=sqlite
|
||||
wget {{site.url}}/docker/$DB/docker-compose.yml
|
||||
```
|
||||
|
||||
1. Build docker image
|
||||
```
|
||||
Build docker image
|
||||
```bash
|
||||
docker-compose build
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
## Start gancio
|
||||
|
||||
1. Run your container
|
||||
Start your container:
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
1. Look at logs
|
||||
You can take a look at logs using:
|
||||
```bash
|
||||
tail -f data/logs/gancio.log
|
||||
```
|
||||
|
||||
1. [Setup nginx as a proxy]({% link install/nginx.md %})
|
||||
You'll need to [setup nginx as a proxy]({% link install/nginx.md %}) then you can point your web browser to your domain :tada:
|
||||
|
||||
1. Point your web browser to your domain :tada:
|
||||
|
||||
1. Edit `data/config.json` and restart the container on your needs, see [Configuration]({% link install/configuration.md %}) for more details.
|
||||
|
||||
## Upgrade
|
||||
|
||||
|
@ -89,20 +64,6 @@ tail -f data/logs/gancio.log
|
|||
> Don't be lazy and [backup]({% link install/backup.md %}) your data!
|
||||
|
||||
|
||||
> error "Upgrade from a version < 1.0"
|
||||
> Since v1.0 our docker setup is changed and a new container has to be built:
|
||||
>
|
||||
> 1. `cd /opt/gancio`
|
||||
> 1. [Backup your data]({% link install/backup.md %})
|
||||
> 1. Download new `Dockerfile` <br/> `wget {{site.url}}{% link /docker/Dockerfile %}`
|
||||
> 1. Download new `entrypoint.sh` <br/> `wget {{site.url}}{% link /docker/entrypoint.sh %}`
|
||||
> 1. Download new `docker-compose.yml` (substitute `sqlite` with `postgres` in case): <br/>`wget {{site.url}}{% link /docker/sqlite/docker-compose.yml %}`
|
||||
> 1. Build the new container `docker-compose build`
|
||||
> 1. Extract your backup into `./data` <br/>`mkdir data; tar xvzf gancio-<yourLastBackup>-backup.tgz -C data`
|
||||
> 1. Stop your old container `docker-compose stop`
|
||||
> 1. Start your new container `docker-compose up`
|
||||
|
||||
|
||||
```bash
|
||||
cd /opt/gancio
|
||||
docker-compose up -d --no-deps --build
|
||||
|
|
|
@ -9,7 +9,7 @@ has_toc: false
|
|||
## Pre-requisites
|
||||
- a Linux machine with <strong>root access</strong> (a VPS with 500MB of RAM and a cpu should be enough but do not use docker on a small machine :stuck_out_tongue_winking_eye:)
|
||||
- a domain name or subdomain (eg. gancio.mydomain.org, subpath are not supported)
|
||||
- an SMTP server to deliver emails
|
||||
- optionally an SMTP server to deliver emails
|
||||
|
||||
## Install
|
||||
|
||||
|
|
|
@ -8,7 +8,10 @@ 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)
|
||||
- [termine.161.social](https://termine.161.social) (Germany)
|
||||
- [ezkerraldea.euskaragendak.eus](https://ezkerraldea.euskaragendak.eus/)
|
||||
- [lakelogaztetxea.net](https://lakelogaztetxea.net)
|
||||
|
||||
|
||||
<small>Do you want your instance to appear here? [Write us]({% link contact.md %}).</small>
|
||||
|
|
83
docs/public.key
Normal file
83
docs/public.key
Normal file
|
@ -0,0 +1,83 @@
|
|||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQINBFjwEUkBEADGkRYEgSGQHEi/YIKvJCuDDB8TYGT3Gzuz0BfgwWlSiupjzkTv
|
||||
eJAZTX+rH1MCnlwV7QJ+Y7v2I/REA9acc+8IapvcUarBJsEwtdp9nIyjKnDR5eiF
|
||||
UOkh4BxLsi541cm6UreSPOMGEEviTiv4CXYeIg+V+Q20Sp+2dy8vwBe3xSYW5aVg
|
||||
c+d6IFeq40CtYyqheAPpJKQop/DKtRRklz3bIOiObXHIYZNzjCDSQyjbBAF1DapS
|
||||
4Z6pqR4+6l5+ylXbWGI7Cg+JFmGl4jH1zI3lX5utWbqsIS/cDkLG+pYNwKDoP8o9
|
||||
VY7SSQbr/n8BaPRNsLP6zw3xLok0+i8GGYUyeNN0Yua3IIB8Bu503+4kZTgl2J3G
|
||||
sxf+zGS6cWA6u9ROo+plDN9fip7XW29hfVCjc7lsdsbvVGHLI2ZScF0gfXuI2AMf
|
||||
kLeoI32ZMlPT6BSWmYO0fAoZyZgIG7u+DH59pEqM1mc4ChSzhSxtDDzsOenbAstH
|
||||
xkJWB9Y/P77OfmwiNkUkCBb7uk/n092g/0xf8CqE82sj2P4LccBt0J1qi4wSyAZQ
|
||||
Nfm2I5dVmW9A21AoLjuNoTLbB0EEcORyrYYcA13b3DIj0UcSlo/KsP0ZZe6Xu915
|
||||
smUixqWJ8j9FPGAnIGY7FJEkHmnhXc/wj+qVxPFKgMfDvbdtIXIzT6eMFwARAQAB
|
||||
tDJVbmRlcnNjb3JlIF9UTyogSGFja2xhYiA8dW5kZXJzY29yZUBhdXRpc3RpY2ku
|
||||
b3JnPokCNwQTAQgAIQUCWPARSQIbAwULCQgHAwUVCgkICwUWAgMBAAIeAQIXgAAK
|
||||
CRC7603TmsbMqQUSD/4p0WHK51l4SULlAGYVUCd8gM/OGfvA8FkzBJaPwvGAJTGL
|
||||
Pj9U876R4MxBSxsI0UhajKDQBRlswpSOxTGknBVDJ9O1RPhXYgRPQt+piiiGIExT
|
||||
n3be+lxw8DDj/t5MGQDXMbXR/UfVlO5X573/2XSmHVWB9RWdfL3XYGYusxPB1kLO
|
||||
jSeC8ETcV/tka8kToPFW8Fshg8xVkBkzvXiwXrbdKwreDAo/Ukms+8MkI8Qlyhhh
|
||||
vfiUb+GnGuEnHSUom7Sn2YMW5qkOKsVZ4Ca5DhqjbXZEDCA2VhnaS5TrRaBb0odk
|
||||
B9rg399pI36cOAO9qtejchG88JbK6Tr7Dh56/Lr6T0mZBPUWk4nJLAWcF6U5k9kT
|
||||
jVD8K9zsGL/4Zex0a7GCcS0bVmXPt8+G9n4VoFMP5rjVkE9Mi7ugBzMNzEDd2crT
|
||||
GMjPpe77arfEaLfg/2mnDCmqz3osRqV0L+Ef3S4GNjmT0LNiEWbB7eGEOVf2CEBb
|
||||
IHoexd74DF6Dz96tjaN7xeDgvm/hYLdioyCgf6cccZp4xdE2SVcEWOLYoG5B77qr
|
||||
W4b+ssOotx7U0pEpn9A+sYno73tHfzNhoC1UXMcaVh7+nWJAQ0AhVkF6EHUXlb7J
|
||||
p1SDmtUajBYeOEfWDfJZ/3p9Z4ARiE/CEo08O59xtJ5p8XNxBEU1TbpNADHdm4kC
|
||||
HAQQAQgABgUCWPAVQQAKCRA1KRglCwEhd/ciD/9k4fxrgH4ZkEbjwZ4r7HrZ8A29
|
||||
ZsP/ThAaSiLgoKCDjpes2hYir8jwp5QI3aEINseL4+Uzh9st6y3WthgH6aTTPUvP
|
||||
QTjpLfre16YTTWS7ztfLMGUGvXpruXncF8rQUeRwjc/FK0/yzMFcY8iX9DpSSKSc
|
||||
evNlfbDblzQhzo0ibEQ5t49HsaFXNgBlY74PrdFrAde8gI5PPaBWJcg94s1hsCx5
|
||||
Epv7Vfl/wQI8mcnRBAfKfnqVl3AYoqwwCFFuxhdjEyNz35otWJgBfufd5jFVmY8a
|
||||
dT5ZDieFabOdcp0qlGQaPkXO9cIMkbloIxMFiID8kArn4cEWx/hEHa6iHqcBhXgN
|
||||
hZqiTONYlaRZGcWyRh+hFOzVfVdgy/Q7Mqo6VRqbuWWAdMJUrT29xuL7HP2M+Kd6
|
||||
/Fk3C0cfupw7sjhcPlWq/rhvLM+rjfury6cCpGP5Ff22VZebddgGuOGOcY09Q+p+
|
||||
nUaEcU2x/p/JWwyZC8j0cDTBEvzOHZDi2avs3rYjydkbi2syRn0IslbRnrKV45tw
|
||||
amKm9vaI8CWXzvEaGfAUclwBcXw5wES/x2h0Mt6CR2Kt5zNnbieSXjWN/5YLQpQ1
|
||||
YcHEsVdB1lv9jsFzls1xg9hFyLx7DuIsCrZSMQ1wiBWHkvmU9DCH9sCN4KFzI58Q
|
||||
hexGgVf6e8ACRaUsoYkCMwQQAQgAHRYhBLSjt7RWdzvKZ9Uz1CZCVK0JvPrZBQJY
|
||||
/SKHAAoJECZCVK0JvPrZVIUP/2+EB2MCFQRUq1jS7Ud7zQVVneCj1jpBoQqrQOa3
|
||||
ntfIwr0hIJkAuNqsObkgVtS5GThW5w1x0d6MGvSD80n18ipKzsfp7XLU2FBUwU6D
|
||||
nHgUuTNe092aIXPhln/qIRA7EieCSTyvAvE6P+d/VlhylY/R97TN13jhYz+3wBV0
|
||||
IQ4fkfgU08N9MGJ/cBk8CQQAPM+3tDcWQCyefaOVX1JW93gROwcUeRSYO1qQVwUZ
|
||||
iykTPDfWDccKSLM4sAz0uYxo5tJTVdt+1s7pmsAWDadZOxILcurz3zRqrpyjPcqU
|
||||
+i4c1JBhWX1j0tYz6p5gs3GnhEmyrluALNmLEn6x9G671iDjmkyqK/WLefzyeo95
|
||||
Yj3b2+EDq0wKwpL+yuiSvKEBHqbgmCwbQ8+D6Fp+hNEScgmt5efQ0iyQYDu/p4K7
|
||||
0qOfVpzJYT+Sv+aVwXzclD/18W8rLfz+PV80Au87vseQW53BiuI4C5FqtFe1t3OY
|
||||
kys2lVATeruwtlep+2Py1cyMgYyLjENc0h0sxmzhOid/KKCGWTeMB9SKX7rVI7Op
|
||||
J9ErN58GeDyrhAiTur0F4cdvTmxZFGXaUvVoQCkWNo9iYngCeQ8TxCTGzVPRGMNv
|
||||
hm4YYJAPw0P4H2+riAJFd0gqJ9B70bDaFep+/WnGOIBwpW3R7ElGzUpAGEWKSMTu
|
||||
fgfSiQGzBBABCAAdFiEEmu4Bhq6CdRPO82CyQf9TSC2eROYFAlnRWtoACgkQQf9T
|
||||
SC2eROYyOwv/WyC13Ae81ElGDefFyO8hu/d4xRx2wiWG4X6Dft6dyu5pv628l5Kd
|
||||
ZUeowm/TdRL58yLpciJCn9NOGJ83Xwu3Fo5/1qhckuJyOcKuxaYoTahH8PQDtvCZ
|
||||
Xb8lsvYGA7SOrp5jIihW72Bfz3Se5IibBUSyEKXE6Ro7qmR1drNIrTYldRo5YDMY
|
||||
y4M00BEWcfi7z/KFWVdmwusxMz6/raW+sw9kPQbEsh7aTAORHqc0/vf7cdmIue+F
|
||||
SU6GFcKojIkb/7AZlQzyfuaLogJ+80sBQVJDNSBYvmgNwH61BXAKs1divL4e/p52
|
||||
eF7cJe1p66QrNHJXvBR4vxcS5novSVtO2exL0cAbF/e/f7+vBGViPzDi9fdKeyF0
|
||||
KmW9KSncpiePQ1qkDvmHFfejV5bWa8hdrPc6X8PQ/AOPsNPw/8TBeXCTjg/nNZvC
|
||||
pl9m12ISiIOhpURq1srpzw0JygxV+z+K/fkZuPo6QJFy9hSXUwZ1QzV8yLTyKz3i
|
||||
aHrcwQRcK/jwuQINBFjwEUkBEAC1GrFLnjU4hKx3dEiEYQWgIAdKezZzLGYr7KgU
|
||||
riNflGUEouWHg0OJoSD8rJ8IySeFKMUGHfPRci6i8FBgGGOUEBv4bwCvZeNSG5/S
|
||||
mIiB5PDSSJhdqBGM3tGq72FWdRaLxDHyooGnjeMtwtSEGL7XmWtFd4daJL3GFnc0
|
||||
arfCA0w+jBVGlUrVOy4VQ1hLgT6Yiu4mxdg3oun96fHaWkRh1YxGfWzlDx5963uS
|
||||
LVUmZSatRcQHaNLEUeYYfFCUepKpz8bbQdPv0eF/A9mPn3zqZQypSdhDQPnTUCgv
|
||||
omAEZhR4dghNXmXf/TmyBc+2oRXynv5HUfnVUr+ynjcz4k3HeYHjEst0Uue2Rz48
|
||||
O9yP+VRSzOgl2HG8TfuD750UArk9WF06GFAnwWcv9AJalgLzQ1qJisurtyan0fkI
|
||||
PWOgdQJwXgC7lGNZXE2xQMkcihWmDBdfK9YKpkyPhi4aSW8M///QP4WT4LFQe/nu
|
||||
irhEQh8zuujA02HlzTbfdqlecpSkkMu2u+bgXHG/kC7wzMcCa+I7HyxKtXBd1MOC
|
||||
+4c6PY0Am5QkwvLQVlAg7WWDW9kRpymN6xQc8hwlZChziXe3K2nv0rmM2+K1UVPB
|
||||
Ub69wUHVUnXCCOAEfjiIFMloQa39+LLp2DamTrVaIkLDr21KXH90FXD+pydMlPBU
|
||||
FkCXrQARAQABiQIfBBgBCAAJBQJY8BFJAhsMAAoJELvrTdOaxsypf7AP/0LMlW9v
|
||||
WkTlFK8Q3QOmRadwwbrjwdPEBaoN2Ll4SwxQ2KBMBAZqyGnbV6f2JOvDep0S9Xwy
|
||||
RvyUnTWlZb+Hz62xT/RJwhorRxBTEFAgkEbGK8jq5oSiixjOslKv8Qe1W6Qggh0+
|
||||
dCbo2LVEDnlOmQlMpmf9k3fL8mDrOiYany/un1x7Fdtux6B7CtCIib4c84RrotuY
|
||||
vFwS+bLKV2oHroHKqdt7iY5kZ4LBtrKuoo/2fYtIk+2jCmligOpsrI27zpS82SWp
|
||||
wGspeOIukiNOtYA8kbjUxx4K7qGF5f35o+6wVOwPnWTjKtKm6BaltlUk7mgtXwYG
|
||||
GRO44Nkw3sPE/9MAKhIJNm4Oq+2/av4ooE8dYXERUY0g5dR5PHX0O8lQBjDyGYoz
|
||||
H/m1DDVoxOwkVbQP6alpie6XvM03mOWOklj20mlFAnyjLzdxw2m7vySOnXnvZq3s
|
||||
SZUYTh0rsWmXt/ye1/E8aXtTqu9ZnuSAJh+8TLmGYppyjqr6PhENtZcOxUZyoKzc
|
||||
A8ZrRzyuelHqiAWQBNDhOde+QYcBgYXaD1BQUO/9dWanUnP9Lbhu4zkxIC353m1e
|
||||
xAYreSmvD5paqoL4jH5eJakdIasQTjyT8FJ/CI131fbxuVT9P2oaKt/7mjk0JKcz
|
||||
/mmL8sP2wfO515ZBxq6+nuzOel+4IHFFqRFv
|
||||
=XDDO
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
|
@ -1,16 +1,19 @@
|
|||
<template lang='pug'>
|
||||
v-container.p-4.text-center
|
||||
v-alert(v-if="error.statusCode === 404") ¯\_(ツ)_/¯ {{error.message}}
|
||||
v-alert(v-else type='error') <v-icon>mdi-warning</v-icon> An error occurred: {{error.message}}
|
||||
v-alert(v-if="error.statusCode === 404" type='error' :icon='mdiAlert') ¯\_(ツ)_/¯ {{error.message}}
|
||||
v-alert(v-else type='error' :icon='mdiAlert') An error occurred: {{error.message}}
|
||||
nuxt-link(to='/')
|
||||
v-btn Back to home
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
import { mdiAlert } from '@mdi/js'
|
||||
export default {
|
||||
props: { error: { type: Object, default: () => ({ }) } },
|
||||
data () {
|
||||
return { mdiAlert }
|
||||
},
|
||||
head () {
|
||||
return { title: `${this.settings.title} - Error` }
|
||||
},
|
||||
|
|
|
@ -109,7 +109,7 @@
|
|||
"list_description": "Si tens una web i vols encastar una llista d'activitats, pots fer servir el codi de sota"
|
||||
},
|
||||
"register": {
|
||||
"description": "Els moviments socials necessitem organitzar-nos i auto-finançar-nos. <br/> Aquest és un regal per vosaltres, feu-lo servir només per usos no-comercials i òbviament per activitats antifeixistes, antisexistes, i antiracistes.\n <br/> Abans que puguis publicar, <strong> hem d'aprovar el teu compte </strong>, tingues en comtpe que <strong> darrere d'aquesta web hi ha persones </strong> de carn i ossos. Ens agradaria saber quin tipus d'activitats vols publicar, ens escrius dues línies explicant-ho?",
|
||||
"description": "Els moviments socials necessitem organitzar-nos i auto-finançar-nos.\n<br/> Abans que puguis publicar, <strong> hem d'aprovar el teu compte </strong>, tingues en comtpe que <strong> darrere d'aquesta web hi ha persones </strong> de carn i ossos, així que escriviu dues línies per fer-nos saber quins esdeveniments voleu publicar.",
|
||||
"error": "Error: ",
|
||||
"complete": "El registre ha de ser confirmat.",
|
||||
"first_user": "S'ha creat i activat un compte administrador"
|
||||
|
@ -124,7 +124,7 @@
|
|||
"media_description": "Pots adjuntar un cartell (opcional)",
|
||||
"added": "S'ha afegit l'activitat",
|
||||
"added_anon": "S'ha afegit l'activitat però encara ha de ser confirmada.",
|
||||
"where_description": "On es farà? Si no apareix el lloc el pots crear.",
|
||||
"where_description": "On es farà? Si no està posat, escriu-ho i <b>prem Enter</b>.",
|
||||
"confirmed": "S'ha confirmat l'activitat",
|
||||
"not_found": "No s'ha trobat l'activitat",
|
||||
"remove_confirmation": "Segur que vols esborrar l'activitat?",
|
||||
|
@ -156,10 +156,10 @@
|
|||
"ics": "ICS",
|
||||
"import_ICS": "Importa des d'un ICS",
|
||||
"import_URL": "Importa des d'una URL",
|
||||
"saved": "S'ha desat l'activitat",
|
||||
"import_description": "Pots importar activitats des d'altres instàncies o plataformes que facin servir formats estàndards (ics o h-event)",
|
||||
"edit_recurrent": "Edita l'activitat periòdica:",
|
||||
"updated": "S'ha actualitzat l'activitat"
|
||||
"updated": "S'ha actualitzat l'activitat",
|
||||
"saved": "S'ha desat l'activitat",
|
||||
"import_description": "Pots importar activitats des d'altres instàncies o plataformes que facin servir formats estàndards (ics o h-event)"
|
||||
},
|
||||
"admin": {
|
||||
"place_description": "En el cas que un lloc és incorrecte o l'adreça ha de canviar, pots arreglar-ho.<br/>Tingues en compte que totes les activitats passades i futures associades amb aquest lloc també canviaran d'adreça.",
|
||||
|
@ -221,11 +221,12 @@
|
|||
"add_instance": "Afegeix una instància",
|
||||
"instance_block_confirm": "Segur que vols bloquejar la instància {instance}?",
|
||||
"show_smtp_setup": "Configuració de correu",
|
||||
"admin_email": "Correu d'admin",
|
||||
"smtp_test_success": "S'ha enviat un correu de prova a {admin_email}, comprova que hagi arribat bé",
|
||||
"smtp_hostname": "Amfitrió SMTP (hostname)",
|
||||
"smtp_description": "<ul><li>L'admin hauria de rebre un correu cada cop que es pengi alguna una activitat anònima (si estan activades).</li><li>L'admin hauria de rebre un correu per cada soŀlicitud de registre (si estan actives).</li><li>La usuària hauria de rebre un correu després de soŀlicitar registrar-se.</li><li>La usuària hauria de rebre un correu quan se li hagi confirmat el registre.</li><li>La usuària hauria de rebre un correu si l'admin la registra directament.</li><li>La usuària hauria de rebre un correu de restabliment de contrasenya si ho demana</li></ul>",
|
||||
"smtp_test_success": "S'ha enviat un correu de prova a {admin_email}, comprova que hagi arribat bé",
|
||||
"smtp_test_button": "Envia un correu de prova",
|
||||
"admin_email": "Correu d'admin"
|
||||
"widget": "Giny"
|
||||
},
|
||||
"auth": {
|
||||
"not_confirmed": "Encara no s'ha confirmat…",
|
||||
|
@ -270,7 +271,6 @@
|
|||
},
|
||||
"setup": {
|
||||
"completed": "S'ha completat la configuració inicial",
|
||||
"check_db": "Comprova la BD",
|
||||
"completed_description": "<p>Ara ja pots entrar amb aquesta usuària:<br/><br/>Nom: <b>{email}</b><br/>Contrasenya: <b>{password}<b/></p>",
|
||||
"start": "Comença"
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"register": {
|
||||
"subject": "Hem rebut una soŀlicitud de registre",
|
||||
"content": "Hem rebut una soŀlicitud de registre. Hi respondrem tan aviat com ens sigui possible."
|
||||
"content": "Hem rebut una soŀlicitud de registre. Hi respondrem tan aviat com ens sigui possible.<br/>Salut."
|
||||
},
|
||||
"confirm": {
|
||||
"subject": "Ja pots publicar activitats",
|
||||
|
@ -9,7 +9,7 @@
|
|||
},
|
||||
"user_confirm": {
|
||||
"subject": "Ja pots publicar activitats",
|
||||
"content": "Hola, que creat un compte a <a href='{{config.baseurl}}'>{{config.title}}</a>? Si és així, <a href='{{config.baseurl}}/user_confirm/{{user.recover_code}}'>confirma-ho i tria una contrasenya</a>, si us plau."
|
||||
"content": "Hola, que has creat un compte a <a href='{{config.baseurl}}'>{{config.title}}</a>? Si és així, <a href='{{config.baseurl}}/user_confirm/{{user.recover_code}}'>confirma-ho i tria una contrasenya</a>, si us plau."
|
||||
},
|
||||
"recover": {
|
||||
"subject": "Recupera la contrasenya",
|
||||
|
@ -20,7 +20,7 @@
|
|||
"content": "{{user.email}} ha soŀlicitat regsitrar-se a {{config.title}}: <br/><pre>{{user.description}}</pre><br/> Respon a la soŀlicitud <a href='{{config.baseurl}}/admin'>aquí</a>."
|
||||
},
|
||||
"event_confirm": {
|
||||
"content": "Pots acceptar aquesta activitat a <a href='{{url}}'>la pàgina de confirmació</a>"
|
||||
"content": "Pots confirmar aquesta activitat a <a href='{{url}}'>la pàgina de confirmació</a>"
|
||||
},
|
||||
"test": {
|
||||
"subject": "La configuració SMTP funciona",
|
||||
|
|
|
@ -18,5 +18,12 @@
|
|||
"admin_register": {
|
||||
"subject": "Izen-emate berria",
|
||||
"content": "{{user.email}}-(e)k {{config.title}}-n izena ematea eskatu du: <br/><pre>{{user.description}}</pre><br/> Baieztatu ezazu horrela dela <a href='{{config.baseurl}}/admin'>hemen</a>."
|
||||
},
|
||||
"event_confirm": {
|
||||
"content": "Ekitaldi hau <a href='{{url}}'>hemen</a> baieztatu dezakezu"
|
||||
},
|
||||
"test": {
|
||||
"subject": "Zure SMTP konfigurazioa badabil",
|
||||
"content": "Hau probako eposta bat da, mezu hau irakurtzen ari bazara konfigurazioa badabilela esan nahi du."
|
||||
}
|
||||
}
|
||||
|
|
29
locales/email/gl.json
Normal file
29
locales/email/gl.json
Normal file
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"register": {
|
||||
"subject": "Recibida solicitude de rexistro",
|
||||
"content": "Recibimos unha solicitude de rexistro. Confirmarémola o antes posible."
|
||||
},
|
||||
"recover": {
|
||||
"subject": "Recuperación do contrasinal",
|
||||
"content": "Ola, pediches recuperar o contrasinal en {{config.title}}. <a href='{{config.baseurl}}/recover/{{user.recover_code}}'>Preme aquí</a> para confirmalo."
|
||||
},
|
||||
"admin_register": {
|
||||
"content": "{{user.email}} solicitou rexistrarse en {{config.title}}: <br/><pre>{{user.description}}</pre><br/> Confírmao <a href='{{config.baseurl}}/admin'>aquí</a>.",
|
||||
"subject": "Nova conta"
|
||||
},
|
||||
"test": {
|
||||
"subject": "A configuración SMTP é correcta",
|
||||
"content": "Este é un email de proba, se estás lendo isto é que a configuración funciona."
|
||||
},
|
||||
"confirm": {
|
||||
"subject": "Xa podes comezar a publicar eventos",
|
||||
"content": "Ola, a túa conta en <a href='{{config.baseurl}}'>{{config.title}}</a> foi confirmada. Escríbenos a {{config.admin_email}} se precisas máis información."
|
||||
},
|
||||
"user_confirm": {
|
||||
"content": "Ola, creouse a túa conta en <a href='{{config.baseurl}}'>{{config.title}}</a>. <a href='{{config.baseurl}}/user_confirm/{{user.recover_code}}'>Debes confirmala e elexir un contrasinal</a>.",
|
||||
"subject": "Xa podes comezar a publicar eventos"
|
||||
},
|
||||
"event_confirm": {
|
||||
"content": "Podes confirmar este evento <a href='{{url}}'>nesta páxina</a>"
|
||||
}
|
||||
}
|
|
@ -21,5 +21,9 @@
|
|||
},
|
||||
"event_confirm": {
|
||||
"content": "Puoi confermare questo evento premendo il tasto conferma in <a href='{{url}}'>questa pagina</a>"
|
||||
},
|
||||
"test": {
|
||||
"subject": "La tua configurazione SMTP sta funzionando",
|
||||
"content": "Questa è una email di test, se la stai leggendo significa che la tua configurazione sta funzionando."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,7 +109,7 @@
|
|||
"list_description": "If you have a website and want to show a list of events, use the following code"
|
||||
},
|
||||
"register": {
|
||||
"description": "Social movements should organize and self-finance.<br/>\n<br/>Before you can publish, <strong> the account must be approved</strong>, consider that <strong> behind this site you will find real people, so write two lines to let us know what events you would like to publish.",
|
||||
"description": "Social movements should organize and self-finance.<br/>\n<br/>Before you can publish, <strong> the account must be approved</strong>, consider that <strong> behind this site you will find real people</strong>, so write two lines to let us know what events you would like to publish.",
|
||||
"error": "Error: ",
|
||||
"complete": "Registration has to be confirmed.",
|
||||
"first_user": "Administrator created"
|
||||
|
@ -225,8 +225,8 @@
|
|||
"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"
|
||||
|
||||
"admin_email": "Admin e-mail",
|
||||
"widget": "Widget"
|
||||
},
|
||||
"auth": {
|
||||
"not_confirmed": "Not confirmed yet…",
|
||||
|
@ -272,6 +272,7 @@
|
|||
"setup": {
|
||||
"completed": "Setup completed",
|
||||
"completed_description": "<p>You can now login with the following user:<br/><br/>User: <b>{email}</b><br/>Password: <b>{password}<b/></p>",
|
||||
"copy_password_dialog": "Yes, you have to copy the password!",
|
||||
"start": "Start"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
export default {
|
||||
it: 'Italiano',
|
||||
ca: 'Català',
|
||||
de: 'Deutsch',
|
||||
en: 'English',
|
||||
es: 'Español',
|
||||
ca: 'Català',
|
||||
pl: 'Polski',
|
||||
eu: 'Euskara',
|
||||
nb: 'Norwegian Bokmål',
|
||||
fr: 'Francais',
|
||||
de: 'Deutsch'
|
||||
gl: 'Galego',
|
||||
it: 'Italiano',
|
||||
nb: 'Norwegian Bokmål',
|
||||
pl: 'Polski',
|
||||
}
|
||||
|
|
261
locales/eu.json
261
locales/eu.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"common": {
|
||||
"add_event": "Ekitaldia sortu",
|
||||
"add_event": "Sortu ekitaldia",
|
||||
"next": "Jarraitu",
|
||||
"export": "Esportatu",
|
||||
"send": "Bidali",
|
||||
|
@ -10,58 +10,58 @@
|
|||
"what": "Zer",
|
||||
"media": "Media",
|
||||
"login": "Saioa hasi",
|
||||
"email": "Email",
|
||||
"email": "Eposta",
|
||||
"password": "Pasahitza",
|
||||
"register": "Izena eman",
|
||||
"description": "Deskribapena",
|
||||
"remove": "Ezabatu",
|
||||
"hide": "Ezkutatu",
|
||||
"search": "Bilatu",
|
||||
"edit": "Aldatu",
|
||||
"edit": "Editatu",
|
||||
"info": "Informazioa",
|
||||
"confirm": "Baieztatu",
|
||||
"admin": "Kudeatu",
|
||||
"admin": "Administratu",
|
||||
"users": "Erabiltzaileak",
|
||||
"events": "Ekitaldiak",
|
||||
"places": "Lekuak",
|
||||
"settings": "Ezarpenak",
|
||||
"settings": "Aukerak",
|
||||
"actions": "Ekintzak",
|
||||
"deactivate": "Desaktibatu",
|
||||
"remove_admin": "Administratzailea kanporatu",
|
||||
"remove_admin": "Kendu administratzaile baimena",
|
||||
"activate": "Aktibatu",
|
||||
"save": "Gorde",
|
||||
"preview": "Aurrebista",
|
||||
"logout": "Saioa amaitu",
|
||||
"logout": "Itxi saioa",
|
||||
"share": "Partekatu",
|
||||
"name": "Izena",
|
||||
"associate": "Elkartu",
|
||||
"edit_event": "Ekitaldia aldatu",
|
||||
"edit_event": "Editatu ekitaldia",
|
||||
"related": "Erlazionatuta",
|
||||
"add": "Gehitu",
|
||||
"logout_ok": "Saioa ondo itxi da",
|
||||
"copy": "Kopiatu",
|
||||
"recover_password": "Pasahitza berreskuratu",
|
||||
"recover_password": "Berreskuratu pasahitza",
|
||||
"new_password": "Pasahitz berria",
|
||||
"new_user": "Erabiltzaile berria",
|
||||
"ok": "Ados",
|
||||
"cancel": "Ezeztatzea",
|
||||
"cancel": "Utzi",
|
||||
"enable": "Gaitu",
|
||||
"disable": "Desgaitu",
|
||||
"me": "Zu",
|
||||
"password_updated": "Pasahitza eguneratuta.",
|
||||
"activate_user": "Egiaztatuta",
|
||||
"password_updated": "Pasahitza aldatu da.",
|
||||
"activate_user": "Baieztatuta",
|
||||
"displayname": "Erakutsitako izena",
|
||||
"federation": "Federazioa",
|
||||
"set_password": "Pasahitza ezarri",
|
||||
"copy_link": "Lotura kopiatu",
|
||||
"send_via_mail": "Posta elektronikoa bidali",
|
||||
"add_to_calendar": "Egutegira gehitu",
|
||||
"set_password": "Ezarri pasahitza",
|
||||
"copy_link": "Kopiatu esteka",
|
||||
"send_via_mail": "Bidali eposta",
|
||||
"add_to_calendar": "Gehitu egutegira",
|
||||
"instances": "Instantziak",
|
||||
"copied": "Kopiatuta",
|
||||
"embed": "Txertatuta",
|
||||
"embed_title": "Txertatu ekitaldi hau zure web-gunean",
|
||||
"embed_help": "Hurrengo kodea zure web-orrian kopiatuz gero ekitaldia txertatuko da hemen ikusten den bezala",
|
||||
"feed": "RSS Jarioa",
|
||||
"embed": "Kapsulatu",
|
||||
"embed_title": "Kapsulatu ekitaldi hau zure webgunean",
|
||||
"embed_help": "Kode hau zure webgunean kopiatu eta ekitaldia bertan honela ikusiko da",
|
||||
"feed": "RSS jarioa",
|
||||
"feed_url_copied": "Erabili RSS jarioaren esteka zure RSS jario irakurgailuan",
|
||||
"follow_me_title": "Fedibertsoko eguneraketak jarraitu",
|
||||
"follow": "Jarraitu",
|
||||
|
@ -78,65 +78,65 @@
|
|||
"fediverse": "Fedibertsoa",
|
||||
"skip": "Saltatu",
|
||||
"delete": "Ezabatu",
|
||||
"announcements": "Iragarkiak",
|
||||
"url": "URL esteka",
|
||||
"announcements": "Iragarpenak",
|
||||
"url": "URLa",
|
||||
"place": "Lekua",
|
||||
"label": "Etiketa",
|
||||
"max_events": "Max zenbakidun gertaerak",
|
||||
"label": "Izena",
|
||||
"max_events": "Gehienezko ekitaldi kopurua",
|
||||
"import": "Inportatu",
|
||||
"reset": "Zeroan jarri",
|
||||
"theme": "Gai",
|
||||
"tags": "Tags"
|
||||
"reset": "Berrezarri",
|
||||
"theme": "Itxura",
|
||||
"tags": "Etiketak"
|
||||
},
|
||||
"login": {
|
||||
"description": "Saioa hasiz gero, ekitaldi berriak sortu ahal izango dituzu.",
|
||||
"check_email": "Begiratu zure postontzi elektronikoan, baita mezu baztergarrietan.",
|
||||
"not_registered": "Ez duzu izena eman?",
|
||||
"description": "Saioa hasten baduzu ekitaldi berriak sortu ahal izango dituzu.",
|
||||
"check_email": "Begiratu zure epostaren sarrera-ontzia eta zabor karpeta.",
|
||||
"not_registered": "Ez duzu izenik eman?",
|
||||
"forgot_password": "Pasahitza ahaztu duzu?",
|
||||
"error": "Ezin da saioa hasi, egiaztatu zure datuok.",
|
||||
"insert_email": "Sartu zure helbide elektronikoa",
|
||||
"error": "Ezin izan da saioa hasi. Egiaztatu datuak.",
|
||||
"insert_email": "Sartu zure eposta helbidea",
|
||||
"ok": "Saioa hasi duzu"
|
||||
},
|
||||
"recover": {
|
||||
"not_valid_code": "Mmmmm zerbaitek huts egin du..."
|
||||
"not_valid_code": "Zerbaitek huts egin du."
|
||||
},
|
||||
"export": {
|
||||
"intro": "Kapitalismoaren plataformek edozer egingo dute erabiltzaileak eta haien datuak gordetzeko. Guk aldiz, informazioak, pertsonen antzera askeak izan behar dutela sinesten dugu. Horretarako gogoko dituzun ekitaldietaz info eguneratuak jaso ditzakezu webgune honetatik pasatzeko beharrik gabe.",
|
||||
"email_description": "Interesatzen zaizkizun ekitaldiak jaso ditzakezu posta elektronikoan.",
|
||||
"insert_your_address": "Sartu zure helbide elektronikoa",
|
||||
"intro": "Kapitalismoaren plataformek edozer egingo dute erabiltzaileak eta haien datuak gordetzeko. Guk aldiz, informazioa, pertsonak bezala, askea izan behar duela uste dugu. Horregatik hemengo ekitaldien informazio eguneratua jaso dezakezu webgune honetatik pasatu beharrik ere izan gabe.",
|
||||
"email_description": "Zure intereseko ekitaldiak epostaz jaso ditzakezu.",
|
||||
"insert_your_address": "Sartu zure eposta helbidea",
|
||||
"feed_description": "Eguneraketak sakelekoan edo ordenagailuan jaso nahi badituzu webgune hau bisitatu gabe, RSS jarioa erabiltzea gomendatzen dizugu.</p>\n\n<p>RSS jarioarentzat aplikazio berezi bat erabiliko duzu gogoko dituzun weguneetatik berriak jasotzeko. Oso modu egokia da gune askotako berriak erraz eta azkar jasotzeko eta ez da konturik sortu behar! </p>\n\n<li>Android baldin badaukazu <a href=\"https://play.google.com/store/apps/details?id=net.frju.flym\">Flym</a> edo Feeder gomendatzen dizugu</li>\n<li>iPhone/iPad-erako eskuragarri daukazu <a href=\"https://itunes.apple.com/ua/app/feeds4u/id1038456442?mt=8\">Feed4U</a></li>\n<li>Ordenagailuaren kasuan Feedbro iradokitzen dugu, <a href=\"https://addons.mozilla.org/en-GB/firefox/addon/feedbroreader/\">Firefoxeko</a> edo <a href=\"https://chrome.google.com/webstore/detail/feedbro/mefgmmbdailogpfhfblcnnjfmnpnmdfa\">Chromeko</a> gehigarri gisa instalatzen da eta sistema gehienetan dabil.</li>\n<br/>\nHonako esteka jario irakurgailuan sartuta, eguneraketa guztiak jasoko dituzu.",
|
||||
"ical_description": "Normalean ordenagailuak eta smartphoneak egutegiak inportatu eta kudeatzeko aplikazioekin etorri ohi dira.",
|
||||
"list_description": "Webgune bat baduzu eta ekitaldien zerrenda erakutsi nahi baduzu, ondorengo kodea erabili dezakezu"
|
||||
"ical_description": "Ordenagailuak eta telefonoak maiz egutegiak inportatzeko eta kudeatzeko aplikazioekin datoz.",
|
||||
"list_description": "Webgune bat baduzu eta ekitaldien zerrenda erakutsi nahi baduzu, ondorengo kodea erabili"
|
||||
},
|
||||
"register": {
|
||||
"description": "Herri mugimenduek autoantolaketaren bidean diru-iturrien beharrak dauzkatela badakigu.<br/>Honako hauxe oparitxoa da, hortaz erabili ezazue ekitaldi ez-komertzialak iragartzeko, eta esan gabe doa, ekitaldi antifaxistak, antisexistak eta antiarriztetarako :) .\n<br/>Argitaratzen hasi baino lehen<strong> zure kontu berriak onarpena jaso beharko du </strong>beraz, <strong>webgune honen atzean hezur-haragizko pertsonak gaudela jakinda </strong>, (momenutz euskal 'AI'-rik ez daukagu baina adi, agertuko direla) idatzi iezaguzu lerro batzuk argitaratu nahi dituzun ekitaldiei buruz.",
|
||||
"error": "Hutsa: ",
|
||||
"complete": "Izen-ematea baieztatu behar dute.",
|
||||
"error": "Errorea: ",
|
||||
"complete": "Izen-ematea baieztatua izan behar da.",
|
||||
"first_user": "Administratzailea sortu da"
|
||||
},
|
||||
"event": {
|
||||
"anon": "Ezezaguna",
|
||||
"anon_description": "Ekitaldia sortu dezakezu <a href='/login'>saioa hasi</a> edo <a href='/register'>izena eman</a> gabe,\nbaina kasu honetan norbaitek egiaztatu beharko du ekitaldia gune honetarako egokia dela eta itxaron beharko duzu. Gainera, behin egiaztatuta hura aldatzea ez da posiblea izango.<br/><br/>\nDena den, ahalik eta azkarren erantzuten saiatuko gara. ",
|
||||
"anon": "Anonimoa",
|
||||
"anon_description": "Ekitaldi bat sor dezakezu saioa hasi edo izena eman gabe, baina norbaitek berau irakurri eta\ngune honetarako egokia dela baieztatu arte itxaron beharko duzu. Ez da posible izango ekitaldia aldatzerik.<br/><br/>\nHorren ordez <a href='/login'>saioa hasi</a> edo <a href='/register'>izena eman</a> dezakezu. Bestela, jarraitu aurrera eta ahalik azkarren erantzuten saiatuko gara. ",
|
||||
"same_day": "egun berean",
|
||||
"what_description": "Ekitaldiaren izena",
|
||||
"description_description": "Ekitaldiaren azalpena",
|
||||
"tag_description": "Etiketak (tag)...",
|
||||
"media_description": "Eskuorria edo irudia gehitu dezakezu (aukerakoa)",
|
||||
"added": "Ekitaldia sortu da",
|
||||
"added_anon": "Ekitaldia sortu da, baina baieztatzear dago.",
|
||||
"where_description": "Non da ekitaldia? Lekua ez bada zerrendan agertzen idatzi ezazu eta <b>enter sakatu</b>.",
|
||||
"what_description": "Izenburua",
|
||||
"description_description": "Deskripzioa",
|
||||
"tag_description": "Etiketa",
|
||||
"media_description": "Irudi bat gehi dezakezu (hautazkoa)",
|
||||
"added": "Ekitaldia gehitu da",
|
||||
"added_anon": "Ekitaldia gehitu da, baina baieztatzeko zain dago.",
|
||||
"where_description": "Non da ekitaldia? Agertzen ez bada zuk sor dezakezu.",
|
||||
"confirmed": "Ekitaldia egiaztatu da",
|
||||
"not_found": "Ezin da ekitaldia aurkitu",
|
||||
"remove_confirmation": "Ziur zaude ekitaldi hau ezabatu nahi duzula?",
|
||||
"remove_recurrent_confirmation": "Ziur zaude ekitaldi errepikari hau ezabatu nahi duzula?\nIragan diren ekitaldiak mantenduko dira, baina ez da ekitaldi berririk sortuko.",
|
||||
"remove_recurrent_confirmation": "Ziur zaude ekitaldi errepikari hau ezabatu nahi duzula?\nIragandako ekitaldiak mantenduko dira, baina ez da ekitaldi berririk sortuko.",
|
||||
"recurrent": "Errepikaria",
|
||||
"show_recurrent": "Ekitaldi errepikariak",
|
||||
"show_past": "Erakutsi iraganeko ekitaldiak",
|
||||
"recurrent_description": "Aukera ezazu maiztasuna eta hautatu egunak",
|
||||
"multidate_description": "Egun bat baino gehiagoko jaialdia da? Aukeratu noiz hasten den eta noiz amaitzen den",
|
||||
"multidate": "Egun gehiagotan",
|
||||
"normal": "Egunekoa",
|
||||
"normal_description": "Eguna aukeratu.",
|
||||
"show_recurrent": "ekitaldi errepikariak",
|
||||
"show_past": "iragandako ekitaldiak ere",
|
||||
"recurrent_description": "Aukera maiztasuna eta hautatu egunak",
|
||||
"multidate_description": "Jaialdi bat da? Aukeratu noiz hasten eta amaitzen den",
|
||||
"multidate": "Egun gehiagokoa",
|
||||
"normal": "Normala",
|
||||
"normal_description": "Aukeratu eguna.",
|
||||
"recurrent_1w_days": "{days}(e)ro",
|
||||
"recurrent_2w_days": "Bi {days}(e)z behin",
|
||||
"recurrent_1m_days": "|Hilabetero {days}etan|Hilabetero {days}etan",
|
||||
|
@ -148,118 +148,129 @@
|
|||
"each_month": "Hilero",
|
||||
"due": "Amaiera ordua",
|
||||
"from": "Hasiera ordua",
|
||||
"image_too_big": "Irudia handiegia omen da (4mb gehienez)",
|
||||
"interact_with_me": "Elkar gaitezen fedibertsoan",
|
||||
"follow_me_description": "{title}n argitaratutako ekitaldien berri izateko aukeren artean,\n fedibertsoko <u>{account}</u> kontuari jarraitzea daukazu. Horretarako Mastodon erabili dezakezu, eta bertatik baliabideak gehitu ekitaldi baten.<br/><br/>\n Mastodon eta Fedibertsoa zer diren ez badakizu <a href='https://es.wikipedia.org/wiki/Fediverso'>artikulu hau</a> irakurtzea iradokitzen dizugu.<br/><br/> Sartu zure instantzia behean (adibidez mastodon.eus edo mastodon.jalgi.eus)",
|
||||
"import_description": "Beste plataforma eta adibide batzuetako gertaerak formatu estandarren bidez inportatu ditzakezu (ics eta h-event)",
|
||||
"image_too_big": "Irudia ezin da 4 MB baino handiagoa izan",
|
||||
"interact_with_me": "Jarrai nazazu",
|
||||
"follow_me_description": "{title}(e)n argitaratutako ekitaldien berri izateko aukeren artean,\nfedibertsoko <u>{account}</u> kontua jarraitzea daukazu. Horretarako, Mastodon erabil dezakezu eta bertatik ekitaldi bati baliabideak gehitu.<br/><br/>\nMastodon eta fedibertsoa zer diren ez badakizu <a href='https://eu.wikipedia.org/wiki/Fedibertso'>artikulu hau</a> irakurtzea gomendatzen dizugu.<br/><br/>Sartu zure instantzia behean (adibidez, mastodon.eus edo mastodon.jalgi.eus)",
|
||||
"import_description": "Beste plataforma eta instantzietatik inportatu ditzakezu ekitaldiak formatu estandarrak erabiliz (ics eta h-event)",
|
||||
"ics": "ICS",
|
||||
"import_ICS": "ICS-ko inportazioa",
|
||||
"import_URL": "URL-ko inportazioa",
|
||||
"interact_with_me_at": "Hitz egin nirekin fediversoan",
|
||||
"only_future": "gertakizunak besterik ez",
|
||||
"edit_recurrent": "Gertaera errepikakorra:",
|
||||
"updated": "Gertaera eguneratua",
|
||||
"saved": "Gertaera salbatua"
|
||||
"import_ICS": "ICS-tik inportatu",
|
||||
"import_URL": "URL-tik inportatu",
|
||||
"interact_with_me_at": "Mintzatu nirekin fedibertsoko leku honetan:",
|
||||
"only_future": "datozen ekitaldiak bakarrik",
|
||||
"edit_recurrent": "Editatu ekitaldi errepikaria:",
|
||||
"updated": "Ekitaldia eguneratu da",
|
||||
"saved": "Ekitaldia gorde da"
|
||||
},
|
||||
"admin": {
|
||||
"place_description": "Lekuaren zehaztapenak aldatu ditzakezu, bai gaizki idatzita dagoelako, bai helbidez aldatu delako.<br/> Ondorioz, leku horrekin lotutako ekitaldi guztiak helbidez aldatuko direla kontuan hartu behar da (baita iraganekoak ere!)",
|
||||
"event_confirm_description": "Erabiltzaile ezezagunek sortutako ekitaldiak hemen egiaztatu ditzakezu",
|
||||
"delete_user": "Erabiltzailea ezabatu",
|
||||
"remove_admin": "Administratzailea ezabatu",
|
||||
"delete_user_confirm": "Ziur zaude {user} ezabatu nahi duzula?",
|
||||
"place_description": "Helbidea oker badago, alda dezakezu.<br/>Leku honekin lotutako iraganeko eta etorkizuneko ekitaldien helbidea aldatuko da.",
|
||||
"event_confirm_description": "Erabiltzaile anonimoek sortutako ekitaldiak hemen baieztatu ditzakezu",
|
||||
"delete_user": "Ezabatu",
|
||||
"remove_admin": "Kendu administratzaile baimena",
|
||||
"delete_user_confirm": "Ziur al zaude {user} ezabatu nahi duzula?",
|
||||
"user_remove_ok": "Erabiltzailea ezabatu da",
|
||||
"user_create_ok": "Erabiltzailea sortu da",
|
||||
"allow_registration_description": "Izen-emateak ahalbidetu nahi dituzu?",
|
||||
"allow_anon_event": "Ezezagunek ekitaldiak sortzea ahalbidetu nahi duzu? (Beti ere baieztapenarekin)",
|
||||
"allow_anon_event": "Anonimoek ekitaldiak sortzea ahalbidetu (baieztatu ondoren)?",
|
||||
"allow_recurrent_event": "Ekitaldi errepikariak ahalbidetu",
|
||||
"recurrent_event_visible": "Erakutsi ekitaldi errepikariak modu lehenetsian",
|
||||
"federation": "Federazioa / ActivityPub",
|
||||
"enable_federation": "Federatzea gaitu",
|
||||
"enable_federation_help": "Instantzia hau fedibertsoan jarraitzea gaituko duzu",
|
||||
"enable_federation": "Federazioa gaitu",
|
||||
"enable_federation_help": "Instantzia hau fedibertsotik jarraitzea posible izango da",
|
||||
"select_instance_timezone": "Ordu-eremua",
|
||||
"instance_timezone_description": "Gancio hiri baten moduko lekuen ekitaldiak biltzeko diseinatuta dago. Leku honen ordu-eremua hautatuz gero ekitaldi gutziek ordu-eremu horrekiko adieraziko dira..",
|
||||
"instance_timezone_description": "Gancio gune bateko ekitaldiak biltzeko diseinatuta dago, adibidez hiri batekoak. Hemengo ekitaldi guztiak aukeratzen duzun ordu-eremuan erakutsiko dira.",
|
||||
"enable_resources": "Baliabideak gaitu",
|
||||
"enable_resources_help": "Fedibertsotik ekitaldietan baliabideak gehitzea ahalbidetzen du",
|
||||
"hide_boost_bookmark": "Bultzadak eta laster-markak ezkutatu",
|
||||
"hide_boost_bookmark_help": "Fedibertsotik datozen bultzaden eta laster-marken ikonotxoak ezkutatzen ditu",
|
||||
"enable_resources_help": "Fedibertsotik ekitaldiei baliabideak gehitzea ahalbidetzen du",
|
||||
"hide_boost_bookmark": "Bultzadak eta laster-markak ezkutatzen ditu",
|
||||
"hide_boost_bookmark_help": "Fedibertsotik datozen bultzada eta laster-marka kopuruaren ikonotxoak ezkutatzen ditu",
|
||||
"block": "Blokeatu",
|
||||
"unblock": "Desblokeatu",
|
||||
"user_add_help": "Mezu elektroniko bat bidaliko diogu erabiltzaile berriari harpidetza baieztatzeko eta pasahitz bat aukeratzeko argibideekin",
|
||||
"user_add_help": "Eposta bat bidaliko diogu erabiltzaile berriari harpidetza baieztatzeko eta pasahitz bat aukeratzeko argibideekin",
|
||||
"resources": "Baliabideak",
|
||||
"hide_resource": "Baliabidea ezkutatu",
|
||||
"show_resource": "Baliabidea erakutsi",
|
||||
"delete_resource": "Baliabidea ezabatu",
|
||||
"hide_resource": "Ezkutatu baliabidea",
|
||||
"show_resource": "Erakutsi baliabidea",
|
||||
"delete_resource": "Ezabatu baliabidea",
|
||||
"delete_resource_confirm": "Ziur zaude baliabide hau ezabatu nahi duzula?",
|
||||
"block_user": "Erabiltzailea blokeatu",
|
||||
"block_user": "Blokeatu erabiltzailea",
|
||||
"user_blocked": "{user} erabiltzailea blokeatuta dago",
|
||||
"filter_instances": "Instantziak iragazi",
|
||||
"filter_users": "Erabiltzaileak iragazi",
|
||||
"filter_instances": "Iragazi instantziak",
|
||||
"filter_users": "Iragazi erabiltzaileak",
|
||||
"instance_name": "Instantziaren izena",
|
||||
"favicon": "Iruditxoa",
|
||||
"user_block_confirm": "Ziur zaude {user} blokeatu nahi duzula?",
|
||||
"delete_announcement_confirm": "Ziur zaude iragarkia ezabatu nahi duzula?",
|
||||
"announcement_remove_ok": "Iragarkia ezabatu da",
|
||||
"announcement_description": "Atal honetan iragarkiak txertatu ditzakezu hasiera-orrian ager daitezen",
|
||||
"instance_locale": "Instantziaren hizkuntza lehenetsia",
|
||||
"instance_locale_description": "Orriak erakusteko erabilitako hizkuntza erabiltzaileak nahiago duen hizkuntza da. Hala ere, kasu batzuetan mezuak modu berean erakutsi behar ditugu guztiontzat (adibidez ActivityPub-etik argitaratzen dugunean edo posta elektroniko batzuk bidaltzerakoan). Kasu hauetan goian hautatutako hizkuntza erabiliko dugu.",
|
||||
"instance_place": "Instantziaren kokalekua",
|
||||
"title_description": "Orriaren izenburuan, jario eta ics-en esportazioan eta mezu elektronikoen gaian erabiliko da.",
|
||||
"description_description": "Orriburuan agertuko da, izenburuarekin batera",
|
||||
"instance_name_help": "Instantziaren kontua ActivityPub-en",
|
||||
"enable_trusted_instances": "Kideko instantziak gaitu",
|
||||
"trusted_instances_help": "Kideko instantzien zerrenda orri-buruan agertuko dira",
|
||||
"add_trusted_instance": "Gehitu kideko instantzia bat",
|
||||
"instance_place_help": "Beste instantzien zerrendetan agertuko den izena",
|
||||
"delete_trusted_instance_confirm": "Ziur zaude kideko instantzia hau zerrendatik ezabatu nahi duzula?",
|
||||
"favicon": "Logoa",
|
||||
"user_block_confirm": "Ziur al zaude {user} blokeatu nahi duzula?",
|
||||
"delete_announcement_confirm": "Ziur al zaude iragarpena ezabatu nahi duzula?",
|
||||
"announcement_remove_ok": "Iragarpena ezabatu da",
|
||||
"announcement_description": "Atal honetan iragarpenak txertatu ditzakezu hasiera-orrialdean ager daitezen",
|
||||
"instance_locale": "Hizkuntza lehenetsia",
|
||||
"instance_locale_description": "Orrialdeak erabiltzailearen gogoko hizkuntzan bistaratzen dira. Batzuetan mezuak jende guztiari hizkuntza berean bistaratu beharra dago (adibidez, ActivityPub bidez argitaratzen dugunean edo eposta batzuetan). Kasu hauetan goian hautatutako hizkuntza erabiliko dugu.",
|
||||
"instance_place": "Instantziaren kokalekua adierazten du",
|
||||
"title_description": "Orrialdearen izenburuan, ICS jarioetan eta RSS-ak esportatzeko epostaren gaian erabiltzen da.",
|
||||
"description_description": "Goiburuan agertzen da izenburutik gertu",
|
||||
"instance_name_help": "Jarraitu beharreko ActivityPub kontua",
|
||||
"enable_trusted_instances": "Gaitu instantzia adiskidetsuak",
|
||||
"trusted_instances_help": "Kideak diren instantzien zerrenda goiburuan erakutsiko da",
|
||||
"add_trusted_instance": "Gehitu kide den instantzia bat",
|
||||
"instance_place_help": "Beste instantzietan agertuko den izena",
|
||||
"delete_trusted_instance_confirm": "Ziur al zaude instantzia hau menuko zerrendatik kendu nahi duzula?",
|
||||
"new_announcement": "Iragarpen berria",
|
||||
"edit_place": "Leku ederrean",
|
||||
"delete_footer_link_confirm": "Ziur lotura kenduko duzula?",
|
||||
"footer_links": "Oinezkoen konexioak",
|
||||
"add_link": "Gehitu lotura",
|
||||
"is_dark": "Gai iluna",
|
||||
"instance_block_confirm": "Ziur al zaude blokearen adibidea {instance} nahi duzula?",
|
||||
"add_instance": "Gehitu adibidea",
|
||||
"disable_user_confirm": "Ziur zaude {user} deskonektatu nahi duzula?"
|
||||
"edit_place": "Editatu lekua",
|
||||
"delete_footer_link_confirm": "Ziur al zaude esteka hau kendu nahi duzula?",
|
||||
"footer_links": "Oineko estekak",
|
||||
"add_link": "Gehitu esteka",
|
||||
"is_dark": "Itxura iluna",
|
||||
"instance_block_confirm": "Ziur al zaude {instance} instantzia blokeatu nahi duzula?",
|
||||
"add_instance": "Gehitu instantzia",
|
||||
"disable_user_confirm": "Ziur zaude {user} deskonektatu nahi duzula?",
|
||||
"show_smtp_setup": "Eposta ezarpenak",
|
||||
"smtp_test_button": "Bidali probako eposta bat",
|
||||
"smtp_test_success": "Probako eposta bidali da {admin_email}-(e)ra, begiratu zure sarrera-ontzia",
|
||||
"admin_email": "Administratzailearen eposta",
|
||||
"smtp_hostname": "SMTP hostname",
|
||||
"smtp_description": "<ul><li>Administratzaileak eposta bat jaso beharko luke anonimo batek ekitaldi bat gehitzen duenean (gaituta badago).</li><li>Administratzaileak eposta bat jaso beharko luke izena emateko eskari bakoitzeko (gaituta badago).</li><li>Erabiltzaileak eposta bat jaso beharko luke izena emateko eskariarekin.</li><li>Erabiltzaileak eposta bat jaso beharko luke izen ematea baieztatzean.</li><li>Erabiltzaileak eposta bat jaso beharko luke administratzaileak zuzenean izena emanez gero.</li><li>Erabiltzaileek eposta bat jaso beharko lukete pasahitza ahazten dutenean.</li></ul>"
|
||||
},
|
||||
"auth": {
|
||||
"not_confirmed": "Oraindik baieztatu gabe dago…",
|
||||
"fail": "Saioa hasteak huts egin du! Ziur zaude datuok ondo daudela?"
|
||||
"fail": "Ezin izan da saioa hasi. Ziur al zaude pasahitza zuzena dela?"
|
||||
},
|
||||
"settings": {
|
||||
"update_confirm": "Aldaketak gorde nahi duzu?",
|
||||
"change_password": "Pasahitza aldatu",
|
||||
"password_updated": "Pasahitza eguneratu da.",
|
||||
"change_password": "Aldatu pasahitza",
|
||||
"password_updated": "Pasahitza aldatu da.",
|
||||
"danger_section": "Atal arriskutsua",
|
||||
"remove_account": "Ondorengo botoia zapalduz gero zure erabiltzailea ezabatuko da. Argitaratutako ekitaldiak ordea, ez dira ezabatuko.",
|
||||
"remove_account": "Ondorengo botoia sakatuz gero zure erabiltzailea ezabatuko da. Argitaratutako ekitaldiak ordea, ez dira ezabatuko.",
|
||||
"remove_account_confirm": "Zure kontua behin betiko ezabatzear zaude"
|
||||
},
|
||||
"error": {
|
||||
"nick_taken": "Dagoeneko ezizen hau hartuta dago.",
|
||||
"email_taken": "Dagoeneko posta elektroniko hau hartuta dago."
|
||||
"nick_taken": "Ezizen hau dagoeneko hartuta dago.",
|
||||
"email_taken": "Eposta hau dageneko hartuta dago."
|
||||
},
|
||||
"confirm": {
|
||||
"title": "Erabiltzaile-baieztapena",
|
||||
"not_valid": "Mmmmm zerbaitek huts egiten du.",
|
||||
"not_valid": "Zerbaitek huts egiten du.",
|
||||
"valid": "Zure kontua baieztatua izan da, orain <a href=\"/login\">saioa hasi</a> dezakezu"
|
||||
},
|
||||
"ordinal": {
|
||||
"1": "lehen",
|
||||
"2": "bigarren",
|
||||
"3": "hirugarren",
|
||||
"4": "laugarrena",
|
||||
"5": "bostgarrena",
|
||||
"-1": "azkena"
|
||||
"4": "laugarren",
|
||||
"5": "bosgarren",
|
||||
"-1": "azken"
|
||||
},
|
||||
"about": "\n <p><a href='https://gancio.org'>Gancio</a> Tokiko komunitateentzako agenda partekatua da.</p>\n ",
|
||||
"oauth": {
|
||||
"authorization_request": "<code>{app}</code> aplikazioak baimena eskatu du <code>{instance_name}</code>-n ondorengo lanak egiteko:",
|
||||
"redirected_to": "Baieztapenaren ondoren <code>{url}</code> helbidera berbideratua izango zara",
|
||||
"authorization_request": "<code>{app}</code> aplikazioak ondorengo baimena eskatu du <code>{instance_name}</code>(e)n:",
|
||||
"redirected_to": "Baieztatu ondoren hona birbideratua izango zara: <code>{url}</code>",
|
||||
"scopes": {
|
||||
"event:write": "Zure ekitaldiak sortu eta aldatu"
|
||||
"event:write": "Sortu eta editatu zure ekitaldiak"
|
||||
}
|
||||
},
|
||||
"validators": {
|
||||
"email": "Sar ezazu posta elektroniko baliozko bat",
|
||||
"email": "Sartu baliozko eposta bat",
|
||||
"required": "{fieldName} beharrezkoa da"
|
||||
},
|
||||
"setup": {
|
||||
"start": "Hasi",
|
||||
"completed": "Instalazioa bukatu da",
|
||||
"completed_description": "<p>Erabiltzaile honekin saioa has dezakezu orain:<br/><br/>Erabiltzailea: <b>{email}</b><br/>Pasahitza: <b>{password}<b/></p>"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -136,7 +136,7 @@
|
|||
"saved": "Événement enregistré"
|
||||
},
|
||||
"register": {
|
||||
"description": "Les mouvements sociaux doivent s'organiser et s'autofinancer.<br/>\n<br/>Avant de pouvoir publier, <strong> le compte doit être approuvé</strong>, considérez que <strong> derrière ce site vous trouverez de vraies personnes, à qui vous pouvez écrire en deux lignes pour exprimer les évènements que vous souhaiteriez publier.",
|
||||
"description": "Les mouvements sociaux doivent s'organiser et s'autofinancer.<br/>\n<br/>Avant de pouvoir publier, <strong> le compte doit être approuvé</strong>, considérez que <strong> derrière ce site vous trouverez de vraies personnes</strong>, à qui vous pouvez écrire en deux lignes pour exprimer les évènements que vous souhaiteriez publier.",
|
||||
"first_user": "Administrateur créé",
|
||||
"complete": "L'inscription doit être confirmée.",
|
||||
"error": "Erreur : "
|
||||
|
@ -213,7 +213,8 @@
|
|||
"smtp_description": "<ul><li>L'administrateur reçoit un e-mail lorsqu'un événement anonyme est ajouté (si activé).</li><li>L'administrateur reçoit un e-mail pour chaque demande d'inscription (si activé).</li><li>L'utilisateur reçoit un e-mail suite à sa demande d'inscription.</li><li>L'utilisateur reçoit un e-mail lorsque son inscription est confirmée.</li><li>L'utilisateur reçoit un e-mail de confirmation s'il est inscrit directement par l'administrateur.</li><li>Les utilisateurs reçoivent un e-mail pour restaurer leur mot de passe s'ils l'oublient.</li></ul>",
|
||||
"show_smtp_setup": "Paramètres d'e-mail",
|
||||
"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"
|
||||
"admin_email": "E-mail de l'administrateur",
|
||||
"widget": "Vignette active"
|
||||
},
|
||||
"oauth": {
|
||||
"scopes": {
|
||||
|
|
277
locales/gl.json
Normal file
277
locales/gl.json
Normal file
|
@ -0,0 +1,277 @@
|
|||
{
|
||||
"common": {
|
||||
"export": "Exportar",
|
||||
"send": "Enviar",
|
||||
"address": "Enderezo",
|
||||
"when": "Cando",
|
||||
"what": "Que",
|
||||
"media": "Multimedia",
|
||||
"login": "Acceder",
|
||||
"email": "Email",
|
||||
"password": "Contrasinal",
|
||||
"register": "Rexistro",
|
||||
"description": "Descrición",
|
||||
"remove": "Eliminar",
|
||||
"hide": "Agochar",
|
||||
"search": "Buscar",
|
||||
"edit": "Editar",
|
||||
"events": "Eventos",
|
||||
"enable": "Activar",
|
||||
"disable": "Desactivar",
|
||||
"me": "Ti",
|
||||
"password_updated": "Cambiou o contrasinal.",
|
||||
"resources": "Recursos",
|
||||
"activate_user": "Confirmado",
|
||||
"instances": "Instancias",
|
||||
"copied": "Copiado",
|
||||
"embed_help": "Copia o seguinte código ao teu sitio web e o evento será mostrado como aquí",
|
||||
"feed_url_copied": "Abrir o URL da fonte copiado no teu lector de RSS",
|
||||
"event": "Evento",
|
||||
"pause": "Pausa",
|
||||
"start": "Inicio",
|
||||
"fediverse": "Fediverso",
|
||||
"announcements": "Anuncios",
|
||||
"reset": "Restablecer",
|
||||
"label": "Etiqueta",
|
||||
"confirm": "Confirmar",
|
||||
"admin": "Admin",
|
||||
"users": "Usuarias",
|
||||
"displayname": "Nome público",
|
||||
"activate": "Activar",
|
||||
"actions": "Accións",
|
||||
"title": "Título",
|
||||
"n_resources": "sen recurso|un recurso|{n} recursos",
|
||||
"federation": "Federación",
|
||||
"place": "Lugar",
|
||||
"delete": "Eliminar",
|
||||
"info": "Info",
|
||||
"add_event": "Engadir evento",
|
||||
"associate": "Asociar",
|
||||
"next": "Seguinte",
|
||||
"where": "Onde",
|
||||
"places": "Lugares",
|
||||
"settings": "Opcións",
|
||||
"deactivate": "Apagar",
|
||||
"remove_admin": "Eliminar admin",
|
||||
"logout_ok": "Non entraches",
|
||||
"new_user": "Nova usuaria",
|
||||
"ok": "Ok",
|
||||
"cancel": "Cancelar",
|
||||
"save": "Gardar",
|
||||
"preview": "Vista previa",
|
||||
"logout": "Saír",
|
||||
"share": "Compartir",
|
||||
"name": "Nome",
|
||||
"edit_event": "Editar evento",
|
||||
"copy": "Copiar",
|
||||
"new_password": "Novo contrasinal",
|
||||
"send_via_mail": "Enviar email",
|
||||
"embed": "Incrustado",
|
||||
"embed_title": "Inclúe este evento no teu sitio web",
|
||||
"related": "Relacionado",
|
||||
"add": "Engadir",
|
||||
"recover_password": "Recuperar contrasinal",
|
||||
"moderation": "Moderación",
|
||||
"set_password": "Establecer contrasinal",
|
||||
"copy_link": "Copiar ligazón",
|
||||
"add_to_calendar": "Engadir ao calendario",
|
||||
"follow_me_title": "Seguir actualizacións desde o fediverso",
|
||||
"follow": "Seguir",
|
||||
"user": "Usuaria",
|
||||
"filter": "Filtro",
|
||||
"skip": "Omitir",
|
||||
"max_events": "N. máx. eventos",
|
||||
"feed": "Fonte RSS",
|
||||
"authorize": "Autorizar",
|
||||
"url": "URL",
|
||||
"theme": "Decorado",
|
||||
"import": "Importar",
|
||||
"tags": "Cancelos"
|
||||
},
|
||||
"recover": {
|
||||
"not_valid_code": "Algo fallou."
|
||||
},
|
||||
"export": {
|
||||
"email_description": "Podes ter eventos que che interesen a través do email.",
|
||||
"insert_your_address": "Escribe o enderezo de email",
|
||||
"ical_description": "As computadoras e teléfonos traen normalmente instalada unha aplicación de calendario que pode importar un calendario remoto.",
|
||||
"list_description": "Se tes un sitio web e queres mostrar unha lista de eventos, usa o seguinte código",
|
||||
"intro": "Ao contrario que nas plataformas asociais que fan todo o posible para obter e gardar datos das persoas usuarias, cremos que esta información, como as persoas, deben ser libres. Por isto, podes recibir actualizacións dos eventos que elixas, sen precisar vir a esta web.",
|
||||
"feed_description": "Para ter actualizacións usando unha computadora ou un teléfono intelixente sen ter que vir a esta web, usa fontes RSS. </p>\n\n<p> Coas fontes RSS usas unha app especial para recibir información das webs que che interesan. É un bo xeito de seguir moitas webs de xeito rápido, se ter que crear unha conta ou outras complicacións. </p>\n\n<li> Se usas Android, recomendámosche <a href=\"https://f-droid.org/en/packages/net.frju.flym/\">Flym</a> ou Feeder </li>\n<li> Para iPhone / iPad podes usar <a href=\"https://itunes.apple.com/ua/app/feeds4u/id1038456442?mt=8\"> Feed4U </a> </li>\n<li> Na computadora recomendamos Feedbro, que se pode instalar en <a href=\"https://addons.mozilla.org/en-GB/firefox/addon/feedbroreader/\"> Firefox </a> ou en <a href=\"https://chrome.google.com/webstore/detail/feedbro/mefgmmbdailogpfhfblcnnjfmnpnmdfa\"> Chrome </a>. </li>\n<br/>\nSe engades esta ligazón ao teu lector de RSS poderás recibir as actualizacións."
|
||||
},
|
||||
"register": {
|
||||
"complete": "É preciso confirmar o rexistro.",
|
||||
"first_user": "Creada Admin",
|
||||
"description": "Os movementos sociais deberían organizarse e auto-financiarse.<br/>\n<br/>Antes de poder publicar, <strong> a conta debe ser aprobada</strong>, ten en conta que <strong> detrás desta web hai persoas reais</strong>, así que escribe un par de liñas para coñecer o tipo de eventos que che gustaría publicar.",
|
||||
"error": "Erro: "
|
||||
},
|
||||
"event": {
|
||||
"media_description": "Podes engadir un panfleto (optativo)",
|
||||
"added": "Evento engadido",
|
||||
"saved": "Evento gardado",
|
||||
"updated": "Evento actualizado",
|
||||
"where_description": "Onde será o evento? Se non existe podes crealo.",
|
||||
"not_found": "Non atopamos o evento",
|
||||
"show_recurrent": "eventos recurrentes",
|
||||
"show_past": "tamén eventos previos",
|
||||
"only_future": "só eventos futuros",
|
||||
"recurrent_2w_days": "Cada {days} días",
|
||||
"recurrent_2m_ordinal": "|O {n} {days} en meses alternos|O {n} {days} en meses alternos",
|
||||
"recurrent_2m_days": "|O {days} cada dous meses|O {days} cada dous meses",
|
||||
"each_month": "Cada mes",
|
||||
"due": "ata",
|
||||
"from": "Desde",
|
||||
"interact_with_me_at": "Contacta comigo no fediverso en",
|
||||
"interact_with_me": "Sígueme",
|
||||
"remove_recurrent_confirmation": "Tes a certeza de querer eliminar este evento recurrente?\nOs eventos pasados permanecerán, pero non se crearán novos eventos.",
|
||||
"import_URL": "Importar desde URL",
|
||||
"anon": "Anon",
|
||||
"anon_description": "Podes engadir un evento sen rexistrarte nen acceder, pero deberás agardar a que alguén o lea e\nconfirme que é un evento axeitado. Non será posible modificalo.<br/><br/>\nPodes tamén <a href='/login'>acceder</a> ou <a href='/register'>crear unha conta</a>. Mais podes continuar e agardar a que se comprobe. ",
|
||||
"same_day": "no mesmo día",
|
||||
"tag_description": "Cancelo",
|
||||
"description_description": "Descrición",
|
||||
"what_description": "Título",
|
||||
"added_anon": "Evento engadido, mais ten que ser confirmado.",
|
||||
"confirmed": "Evento confirmado",
|
||||
"remove_confirmation": "Tes a certeza de querer eliminar este evento?",
|
||||
"multidate_description": "É un festival? Elixe cando comeza e remata",
|
||||
"import_ICS": "Importar desde ICS",
|
||||
"edit_recurrent": "Editar evento recurrente:",
|
||||
"recurrent_description": "Elixe a frecuencia e os días",
|
||||
"multidate": "Máis días",
|
||||
"normal": "Normal",
|
||||
"each_week": "Cada semana",
|
||||
"recurrent": "Recurrente",
|
||||
"normal_description": "Elixe o día.",
|
||||
"recurrent_1w_days": "Cada {days}",
|
||||
"recurrent_1m_ordinal": "O {n} {days} de cada mes",
|
||||
"image_too_big": "A imaxe non pode superar os 4 MB",
|
||||
"recurrent_1m_days": "|O {days} de cada mes|{days} de cada mes",
|
||||
"each_2w": "Cada dúas semanas",
|
||||
"follow_me_description": "Un dos xeitos de recibir actualizacións dos eventos que se publican aquí en {title},\né seguindo a conta <u>{account}</u> no fediverso, por exemplo a través de Mastodon, e posiblemente tamén engadir recursos para un evento desde alí.<br/><br/>\nSe nunco escoitaches falar de Mastodon e o fediverso recomendámosche ler <a href='https://www.savjee.be/videos/simply-explained/mastodon-and-fediverse-explained/'>este artigo</a>.<br/><br/>Escribe aquí a túa instancia (ex. mastodon.social)",
|
||||
"ics": "ICS",
|
||||
"import_description": "Podes importar eventos desde outras plataformas e outras instancias usando formatos estándar (ics e h-event)"
|
||||
},
|
||||
"admin": {
|
||||
"place_description": "Se escribiches mal o lugar ou enderezo, podes cambialo.<br/>Cambiará o enderezo de tódolos eventos actuais e pasados asociados a este lugar.",
|
||||
"disable_user_confirm": "Tes a certeza de querer desactivar a {user}?",
|
||||
"federation": "Federación / ActivityPub",
|
||||
"enable_federation": "Activar a federación",
|
||||
"add_instance": "Engadir instancia",
|
||||
"select_instance_timezone": "Zona horaria",
|
||||
"enable_resources_help": "Permite engadir recursos ao evento desde o fediverso",
|
||||
"user_add_help": "Vaise enviar un email á nova usuaria con instruccións para confirmar a subscrición e elexir un contrasinal",
|
||||
"instance_name": "Nome da instancia",
|
||||
"filter_users": "Filtrar usuarias",
|
||||
"title_description": "Utilízase no título da páxina, no asunto do email e para as fontes RSS e ICS exportadas.",
|
||||
"description_description": "Aparece na cabeceira a carón do título",
|
||||
"instance_name_help": "Conta ActivityPub a seguir",
|
||||
"add_trusted_instance": "Engade unha instancia amiga",
|
||||
"edit_place": "Editar lugar",
|
||||
"smtp_test_success": "Enviado un email de proba a {admin_email}, comproba a caixa de correo",
|
||||
"smtp_test_button": "Enviar un email de proba",
|
||||
"admin_email": "Email de Admin",
|
||||
"widget": "Trebello",
|
||||
"delete_user_confirm": "Tes a certeza de querer eliminar a {user}?",
|
||||
"enable_resources": "Activar recursos",
|
||||
"hide_boost_bookmark_help": "Agocha as pequenas iconas que indican o número de promocións e marcacións que chegan desde o fediverso",
|
||||
"show_resource": "Mostrar recurso",
|
||||
"filter_instances": "Filtrar instancias",
|
||||
"resources": "Recursos",
|
||||
"allow_registration_description": "Permitir o rexistro libre?",
|
||||
"allow_anon_event": "Permitir eventos anónimos (haberá que confirmalos)?",
|
||||
"event_confirm_description": "Aquí podes confirmar os eventos engadidos por usuarias anónimas",
|
||||
"remove_admin": "Eliminar admin",
|
||||
"delete_user": "Eliminar",
|
||||
"user_create_ok": "Usuaria creada",
|
||||
"hide_boost_bookmark": "Agochar promoción/marcador",
|
||||
"delete_resource": "Eliminar recurso",
|
||||
"delete_resource_confirm": "Tes a certeza de querer eliminar este recurso?",
|
||||
"favicon": "Logotipo",
|
||||
"user_remove_ok": "Usuaria eliminada",
|
||||
"allow_recurrent_event": "Permitir eventos recurrentes",
|
||||
"recurrent_event_visible": "Mostrar por defecto os eventos recurrentes",
|
||||
"enable_federation_help": "Vai ser posible seguir esta instancia desde o fediverso",
|
||||
"block": "Bloquear",
|
||||
"block_user": "Bloquear usuaria",
|
||||
"user_blocked": "Usuaria {user} bloqueada",
|
||||
"unblock": "Desbloquear",
|
||||
"hide_resource": "Agochar recurso",
|
||||
"announcement_remove_ok": "Anuncio eliminado",
|
||||
"instance_timezone_description": "Gancio está deseñada para recoller os eventos dun lugar concreto, como unha cidade. Tódolos eventos neste lugar mostraranse na zona horaria elexida para el.",
|
||||
"user_block_confirm": "Tes a certeza de querer bloquear a usuaria {user}?",
|
||||
"instance_block_confirm": "Tes a certeza de querer bloquear a instancia {instance}?",
|
||||
"announcement_description": "Nesta sección podes incluír anuncios que aparecerán na páxina de inicio",
|
||||
"instance_locale": "Idioma por defecto",
|
||||
"delete_announcement_confirm": "Tes a certeza de querer eliminar o anuncio?",
|
||||
"instance_locale_description": "Idioma preferido para as páxinas. A veces as mensaxes teñen que mostrarse no mesmo idioma para tódalas persoas (por exemplo cando publicas vía ActivityPub ou cando envías os emails). Nestos casos usarase o idioma elexido aquí arriba.",
|
||||
"enable_trusted_instances": "Activar instancias amigas",
|
||||
"trusted_instances_help": "A lista das instancias amigas será mostrada na cabeceira",
|
||||
"delete_trusted_instance_confirm": "Tes a certeza de querer eliminar este elemento do menú de instancias amigas?",
|
||||
"instance_place_help": "A etiqueta a mostrar nas instancias de outras",
|
||||
"add_link": "Engadir ligazón",
|
||||
"instance_place": "Lugar de referencia para esta instancia",
|
||||
"is_dark": "Decorado escuro",
|
||||
"footer_links": "Ligazóns do rodapé",
|
||||
"delete_footer_link_confirm": "Tes certeza de eliminar a ligazón?",
|
||||
"show_smtp_setup": "Axustes do email",
|
||||
"smtp_hostname": "Servidor SMTP",
|
||||
"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>"
|
||||
},
|
||||
"auth": {
|
||||
"not_confirmed": "Aínda non foi confirmado…",
|
||||
"fail": "Non accedeches. Tes certeza acerca do contrasinal?"
|
||||
},
|
||||
"settings": {
|
||||
"change_password": "Cambia o contrasinal",
|
||||
"danger_section": "Sección perigosa",
|
||||
"remove_account": "Ao premer o seguinte botón vas eliminar túa conta de usuaria. Os eventos que publicaches permanecerán.",
|
||||
"password_updated": "Contrasinal cambiado.",
|
||||
"remove_account_confirm": "Vas eliminar permanentemente a túa conta",
|
||||
"update_confirm": "Queres gardar as modificacións?"
|
||||
},
|
||||
"error": {
|
||||
"nick_taken": "Este alcume xa está a ser utilizado.",
|
||||
"email_taken": "Este email xa está a ser utilizado."
|
||||
},
|
||||
"confirm": {
|
||||
"title": "Confirmación da usuaria",
|
||||
"not_valid": "Algo fallou.",
|
||||
"valid": "A túa conta foi confirmada, xa podes <a href=\"/login\">acceder</a>"
|
||||
},
|
||||
"ordinal": {
|
||||
"1": "primeiro",
|
||||
"3": "terceiro",
|
||||
"4": "cuarto",
|
||||
"5": "quinto",
|
||||
"-1": "último",
|
||||
"2": "segundo"
|
||||
},
|
||||
"validators": {
|
||||
"required": "{fieldName} é requerido",
|
||||
"email": "Escribe un email válido"
|
||||
},
|
||||
"about": "\n <p><a href='https://gancio.org'>Gancio</a> é unha axenda compartida para comunidades locais.</p>\n ",
|
||||
"oauth": {
|
||||
"authorization_request": "A aplicación <code>{app}</code> solicita a seguinte autorización para <code>{intance_name}</code>:",
|
||||
"redirected_to": "Após a confirmación vas ser enviada a <code>{url}</code>",
|
||||
"scopes": {
|
||||
"event:write": "Engade e edita os teus eventos"
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"completed": "Configuración completada",
|
||||
"completed_description": "<p>Xa podes acceder con estas credenciais:<br/><br/>Identificador: <b>{email}</b><br/>Contrasinal: <b>{password}</b></p>",
|
||||
"start": "Comezar"
|
||||
},
|
||||
"login": {
|
||||
"forgot_password": "Esqueceches o contrasinal?",
|
||||
"check_email": "Comproba a caixa de correo e a de spam.",
|
||||
"insert_email": "Escribe o enderezo de email",
|
||||
"ok": "Entraches en",
|
||||
"description": "Ao acceder poderás publicar novos eventos.",
|
||||
"not_registered": "Non te rexistraches?",
|
||||
"error": "Non entraches. Comproba as credenciais."
|
||||
}
|
||||
}
|
|
@ -7,5 +7,6 @@ module.exports = {
|
|||
eu: 'Euskara',
|
||||
nb: 'Norwegian Bokmål',
|
||||
fr: 'Francais',
|
||||
de: 'Deutsch'
|
||||
de: 'Deutsch',
|
||||
gl: 'Galego',
|
||||
}
|
||||
|
|
|
@ -223,7 +223,13 @@
|
|||
"delete_footer_link_confirm": "Vuoi eliminare questo collegamento?",
|
||||
"edit_place": "Modifica luogo",
|
||||
"new_announcement": "Nuovo annuncio",
|
||||
"show_smtp_setup": "Impostazioni email"
|
||||
"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_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",
|
||||
"admin_email": "E-mail dell'admin"
|
||||
},
|
||||
"auth": {
|
||||
"not_confirmed": "Non ancora confermato…",
|
||||
|
@ -269,6 +275,7 @@
|
|||
"setup": {
|
||||
"completed": "Setup completato",
|
||||
"completed_description": "<p>Puoi entrare con le seguenti credenziali:<br/><br/>Utente: <b>{email}</b><br/>Password: <b>{password}<b/></p>",
|
||||
"copy_password_dialog": "Sì, devi copiare la password!",
|
||||
"start": "Inizia"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
export default function ({ req, redirect, route }) {
|
||||
export default async function ({ $config, req, res, redirect, route, error }) {
|
||||
if (process.server) {
|
||||
if (req.firstrun && route.path !== '/setup') {
|
||||
return redirect('/setup')
|
||||
if (res.locals.status === 'SETUP' && route.path !== '/setup/0') {
|
||||
return redirect('/setup/0')
|
||||
}
|
||||
if (!req.firstrun && route.path === '/setup') {
|
||||
|
||||
if (res.locals.status === 'DBCONF' && route.path !== '/setup/1') {
|
||||
return redirect('/setup/1')
|
||||
}
|
||||
|
||||
if (res.locals.status === 'READY' && route.path.startsWith('/setup')) {
|
||||
return redirect('/')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
const config = require('./server/config.js')
|
||||
const minifyTheme = require('minify-css-string').default
|
||||
|
||||
const isDev = (process.env.NODE_ENV !== 'production')
|
||||
module.exports = {
|
||||
telemetry: false,
|
||||
modern: (process.env.NODE_ENV === 'production') && 'client',
|
||||
|
@ -11,31 +13,26 @@ module.exports = {
|
|||
{ charset: 'utf-8' },
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
|
||||
],
|
||||
script: [{ src: '/gancio-events.es.js' }],
|
||||
link: [{ rel: 'icon', type: 'image/png', href: '/logo.png' }]
|
||||
link: [{ rel: 'icon', type: 'image/png', href: '/logo.png' }],
|
||||
link: [{ rel: 'preload', type: 'image/png', href: '/logo.png', as: 'image' }],
|
||||
script: [{ src: '/gancio-events.es.js', async: true, body: true }],
|
||||
},
|
||||
dev: (process.env.NODE_ENV !== 'production'),
|
||||
dev: isDev,
|
||||
server: config.server,
|
||||
|
||||
|
||||
vue: {
|
||||
config: {
|
||||
ignoredElements: ['gancio-events']
|
||||
ignoredElements: ['gancio-events', 'gancio-event']
|
||||
}
|
||||
},
|
||||
|
||||
css: ['./assets/style.less'],
|
||||
|
||||
/*
|
||||
** Customize the progress-bar component
|
||||
*/
|
||||
loading: '~/components/Loading.vue',
|
||||
/*
|
||||
** Global CSS
|
||||
*/
|
||||
css: [
|
||||
'vuetify/dist/vuetify.min.css',
|
||||
'@mdi/font/css/materialdesignicons.css',
|
||||
'@/assets/style.less'
|
||||
],
|
||||
|
||||
/*
|
||||
** Plugins to load before mounting the App
|
||||
|
@ -43,7 +40,6 @@ module.exports = {
|
|||
plugins: [
|
||||
'@/plugins/i18n.js',
|
||||
'@/plugins/filters', // text filters, datetime filters, generic transformation helpers etc.
|
||||
'@/plugins/vuetify', // vuetify
|
||||
'@/plugins/axios', // axios baseurl configuration
|
||||
'@/plugins/validators', // inject validators
|
||||
'@/plugins/api', // api helpers
|
||||
|
@ -95,9 +91,33 @@ module.exports = {
|
|||
}
|
||||
}
|
||||
},
|
||||
buildModules: ['@nuxtjs/vuetify'],
|
||||
vuetify: {
|
||||
customVariables: ['~/assets/variables.scss'],
|
||||
treeShake: true,
|
||||
theme: {
|
||||
options: {
|
||||
customProperties: false,
|
||||
variations: false,
|
||||
minifyTheme,
|
||||
},
|
||||
dark: true,
|
||||
themes: {
|
||||
dark: {
|
||||
primary: '#FF6E40'
|
||||
},
|
||||
light: {
|
||||
primary: '#FF4500'
|
||||
}
|
||||
}
|
||||
},
|
||||
defaultAssets: false
|
||||
},
|
||||
build: {
|
||||
corejs: 3,
|
||||
cache: true,
|
||||
hardSource: true
|
||||
hardSource: !isDev,
|
||||
extractCSS: !isDev,
|
||||
optimizeCSS: !isDev
|
||||
},
|
||||
}
|
||||
|
|
39
package.json
39
package.json
|
@ -1,12 +1,13 @@
|
|||
{
|
||||
"name": "gancio",
|
||||
"version": "1.2.2",
|
||||
"version": "1.4.3",
|
||||
"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 ../..",
|
||||
"start": "nuxt start --modern",
|
||||
"doc": "cd docs && bundle exec jekyll b",
|
||||
"doc:dev": "cd docs && bundle exec jekyll s --drafts",
|
||||
|
@ -26,57 +27,62 @@
|
|||
"yarn.lock"
|
||||
],
|
||||
"dependencies": {
|
||||
"@mdi/js": "^6.5.95",
|
||||
"@nuxtjs/auth": "^4.9.1",
|
||||
"@nuxtjs/axios": "^5.13.5",
|
||||
"accept-language": "^3.0.18",
|
||||
"axios": "^0.24.0",
|
||||
"axios": "^0.26.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"body-parser": "^1.18.3",
|
||||
"body-parser": "^1.19.2",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "^2.8.5",
|
||||
"dayjs": "^1.10.7",
|
||||
"dompurify": "^2.3.3",
|
||||
"email-templates": "^8.0.8",
|
||||
"express": "^4.17.1",
|
||||
"dompurify": "^2.3.6",
|
||||
"email-templates": "^8.0.9",
|
||||
"express": "^4.17.3",
|
||||
"express-oauth-server": "lesion/express-oauth-server#master",
|
||||
"http-signature": "^1.3.6",
|
||||
"ical.js": "^1.4.0",
|
||||
"ical.js": "^1.5.0",
|
||||
"ics": "^2.35.0",
|
||||
"jsdom": "^18.1.1",
|
||||
"jsdom": "^19.0.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"linkify-html": "^3.0.4",
|
||||
"linkifyjs": "3.0.4",
|
||||
"linkifyjs": "3.0.5",
|
||||
"lodash": "^4.17.21",
|
||||
"mariadb": "^2.5.6",
|
||||
"microformat-node": "^2.0.1",
|
||||
"minify-css-string": "^1.0.0",
|
||||
"mkdirp": "^1.0.4",
|
||||
"multer": "^1.4.3",
|
||||
"nuxt-edge": "^2.16.0-27305297.ab1c6cb4",
|
||||
"pg": "^8.6.0",
|
||||
"sequelize": "^6.12.0-alpha.1",
|
||||
"sequelize": "^6.17.0",
|
||||
"sequelize-slugify": "^1.6.0",
|
||||
"sharp": "^0.27.2",
|
||||
"sqlite3": "mapbox/node-sqlite3#918052b",
|
||||
"tiptap": "^1.32.0",
|
||||
"tiptap-extensions": "^1.35.0",
|
||||
"umzug": "^2.3.0",
|
||||
"v-calendar": "2.3.4",
|
||||
"v-calendar": "2.4.1",
|
||||
"vue": "^2.6.14",
|
||||
"vue-i18n": "^8.26.7",
|
||||
"vue-template-compiler": "^2.6.14",
|
||||
"vuetify": "^2.6.1",
|
||||
"winston": "^3.3.3",
|
||||
"winston-daily-rotate-file": "^4.5.5",
|
||||
"vuetify": "npm:@vuetify/nightly@dev",
|
||||
"winston": "^3.6.0",
|
||||
"winston-daily-rotate-file": "^4.6.1",
|
||||
"yargs": "^17.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mdi/font": "^6.5.95",
|
||||
"@nuxtjs/vuetify": "^1.12.3",
|
||||
"jest": "^27.5.1",
|
||||
"less": "^4.1.1",
|
||||
"less-loader": "^7",
|
||||
"prettier": "^2.3.0",
|
||||
"pug": "^3.0.2",
|
||||
"pug-plain-loader": "^1.1.0",
|
||||
"sass": "^1.43.5",
|
||||
"sass": "^1.49.4",
|
||||
"sequelize-cli": "^6.3.0",
|
||||
"supertest": "^6.2.2",
|
||||
"webpack": "4",
|
||||
"webpack-cli": "^4.7.2"
|
||||
},
|
||||
|
@ -87,7 +93,6 @@
|
|||
"jimp": "0.16.1",
|
||||
"resize-img": "2.0.0",
|
||||
"underscore": "1.13.1",
|
||||
"@nuxtjs/vuetify/**/sass": "1.32.12",
|
||||
"postcss": "7.0.36",
|
||||
"glob-parent": "5.1.2",
|
||||
"chokidar": "3.5.2",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template lang="pug">
|
||||
v-container
|
||||
v-container.container.pa-0.pa-md-3
|
||||
v-card
|
||||
v-tabs(v-model='selectedTab')
|
||||
v-tabs(v-model='selectedTab' show-arrows)
|
||||
|
||||
//- SETTINGS
|
||||
v-tab {{$t('common.settings')}}
|
||||
|
@ -49,18 +49,19 @@
|
|||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import Users from '../components/admin/Users'
|
||||
import Events from '../components/admin/Events'
|
||||
import Places from '../components/admin/Places'
|
||||
import Settings from '../components/admin/Settings'
|
||||
import Federation from '../components/admin/Federation'
|
||||
import Moderation from '../components/admin/Moderation'
|
||||
import Announcement from '../components/admin/Announcement'
|
||||
import Theme from '../components/admin/Theme'
|
||||
|
||||
export default {
|
||||
name: 'Admin',
|
||||
components: { Users, Events, Places, Settings, Federation, Moderation, Announcement, Theme },
|
||||
components: {
|
||||
Users: () => import(/* webpackChunkName: "admin" */'../components/admin/Users'),
|
||||
Events: () => import(/* webpackChunkName: "admin" */'../components/admin/Events'),
|
||||
Places: () => import(/* webpackChunkName: "admin" */'../components/admin/Places'),
|
||||
Settings: () => import(/* webpackChunkName: "admin" */'../components/admin/Settings'),
|
||||
Federation: () => import(/* webpackChunkName: "admin" */'../components/admin/Federation.vue'),
|
||||
Moderation: () => import(/* webpackChunkName: "admin" */'../components/admin/Moderation.vue'),
|
||||
Announcement: () => import(/* webpackChunkName: "admin" */'../components/admin/Announcement.vue'),
|
||||
Theme: () => import(/* webpackChunkName: "admin" */'../components/admin/Theme.vue')
|
||||
},
|
||||
middleware: ['auth'],
|
||||
async asyncData ({ $axios, params, store }) {
|
||||
try {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template lang='pug'>
|
||||
v-container
|
||||
v-row.mt-5(align='center' justify='center')
|
||||
v-col(cols='12' md="6" lg="5" xl="4")
|
||||
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')}}
|
||||
|
@ -57,7 +57,6 @@ export default {
|
|||
methods: {
|
||||
async forgot () {
|
||||
if (!this.email) {
|
||||
// this.$root.$message({ message: this.$t('login.insert_email'), color: 'error' })
|
||||
this.$refs.email.focus()
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template lang='pug'>
|
||||
v-container
|
||||
v-row.mt-5(align='center' justify='center')
|
||||
v-col(cols='12' md="6" lg="5" xl="4")
|
||||
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-card
|
||||
v-card-title {{$t('common.register')}}
|
||||
|
@ -28,17 +28,19 @@ v-container
|
|||
v-btn(@click='register'
|
||||
:disabled='!valid || loading' :loading='loading'
|
||||
color='primary') {{$t('common.send')}}
|
||||
v-icon mdi-chevron-right
|
||||
v-icon(v-text='mdiChevronRight')
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import get from 'lodash/get'
|
||||
import { mdiChevronRight } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
name: 'Register',
|
||||
data () {
|
||||
return {
|
||||
mdiChevronRight,
|
||||
loading: false,
|
||||
user: {},
|
||||
valid: true
|
||||
|
|
|
@ -78,7 +78,7 @@ export default {
|
|||
todayEvents () {
|
||||
const start = dayjs(this.value.from).startOf('day').unix()
|
||||
const end = dayjs(this.value.from).endOf('day').unix()
|
||||
const events = this.events.filter(e => (this.event.id && e.id !== this.event.id) && e.start_datetime >= start && e.start_datetime <= end)
|
||||
const events = this.events.filter(e => e.start_datetime >= start && e.start_datetime <= end)
|
||||
return events
|
||||
},
|
||||
attributes () {
|
||||
|
@ -161,8 +161,10 @@ export default {
|
|||
this.type = 'normal'
|
||||
}
|
||||
this.events = await this.$api.getEvents({
|
||||
start: dayjs().unix()
|
||||
start: dayjs().unix(),
|
||||
show_recurrent: true
|
||||
})
|
||||
this.events = this.events.filter(e => e.id !== this.event.id)
|
||||
},
|
||||
methods: {
|
||||
updateRecurrent (value) {
|
||||
|
@ -194,7 +196,7 @@ export default {
|
|||
} else if (what === 'fromHour') {
|
||||
if (value) {
|
||||
const [hour, minute] = value.split(':')
|
||||
const from = dayjs(this.value.from).hour(hour).minute(minute)
|
||||
const from = dayjs(this.value.from).hour(hour).minute(minute).second(0)
|
||||
this.$emit('input', { ...this.value, from, fromHour: true })
|
||||
} else {
|
||||
this.$emit('input', { ...this.value, fromHour: false })
|
||||
|
@ -209,7 +211,7 @@ export default {
|
|||
if (fromHour > Number(hour) && !this.value.multidate) {
|
||||
due = due.add(1, 'day')
|
||||
}
|
||||
due = due.hour(hour).minute(minute)
|
||||
due = due.hour(hour).minute(minute).second(0)
|
||||
this.$emit('input', { ...this.value, due, dueHour: true })
|
||||
} else {
|
||||
this.$emit('input', { ...this.value, due: null, dueHour: false })
|
||||
|
|
|
@ -41,13 +41,14 @@
|
|||
v-else
|
||||
:label="$t('common.media')"
|
||||
:hint="$t('event.media_description')"
|
||||
prepend-icon="mdi-camera"
|
||||
:prepend-icon="mdiCamera"
|
||||
:value='value.image'
|
||||
@change="selectMedia"
|
||||
persistent-hint
|
||||
accept='image/*')
|
||||
</template>
|
||||
<script>
|
||||
import { mdiCamera } from '@mdi/js'
|
||||
export default {
|
||||
name: 'MediaInput',
|
||||
props: {
|
||||
|
@ -56,6 +57,7 @@ export default {
|
|||
},
|
||||
data () {
|
||||
return {
|
||||
mdiCamera,
|
||||
openMediaDetails: false,
|
||||
name: this.value.name || '',
|
||||
focalpoint: this.value.focalpoint || [0, 0],
|
||||
|
@ -87,7 +89,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
save () {
|
||||
this.$emit('input', { url: this.value.url, image: this.value.image, name: this.name || this.value.image.name || '', focalpoint: [...this.focalpoint] })
|
||||
this.$emit('input', { url: this.value.url, image: this.value.image, name: this.name || (this.event.title) || '', focalpoint: [...this.focalpoint] })
|
||||
this.openMediaDetails = false
|
||||
},
|
||||
async remove () {
|
||||
|
@ -96,7 +98,7 @@ export default {
|
|||
this.$emit('remove')
|
||||
},
|
||||
selectMedia (v) {
|
||||
this.$emit('input', { image: v, name: v.name, focalpoint: [0, 0] })
|
||||
this.$emit('input', { image: v, name: this.event.title, focalpoint: [0, 0] })
|
||||
},
|
||||
handleStart (ev) {
|
||||
ev.preventDefault()
|
||||
|
|
|
@ -6,23 +6,24 @@
|
|||
:label="$t('common.where')"
|
||||
:hint="$t('event.where_description')"
|
||||
:search-input.sync="placeName"
|
||||
prepend-icon='mdi-map-marker'
|
||||
:prepend-icon='mdiMapMarker'
|
||||
persistent-hint
|
||||
:value="value.name"
|
||||
:items="filteredPlaces"
|
||||
no-filter
|
||||
item-text='name'
|
||||
@change='selectPlace')
|
||||
template(v-slot:item="{ item }")
|
||||
template(v-slot:item="{ item, attrs, on }")
|
||||
v-list-item(v-bind='attrs' v-on='on')
|
||||
v-list-item-content(two-line v-if='item.create')
|
||||
v-list-item-title <v-icon color='primary'>mdi-plus</v-icon> {{item.name}}
|
||||
v-list-item-title <v-icon color='primary' v-text='mdiPlus' :aria-label='add'></v-icon> {{item.name}}
|
||||
v-list-item-content(two-line v-else)
|
||||
v-list-item-title {{item.name}}
|
||||
v-list-item-subtitle {{item.address}}
|
||||
v-list-item-title(v-text='item.name')
|
||||
v-list-item-subtitle(v-text='item.address')
|
||||
|
||||
v-col(cols=12 md=6)
|
||||
v-text-field(ref='address'
|
||||
prepend-icon='mdi-map'
|
||||
:prepend-icon='mdiMap'
|
||||
:disabled='disableAddress'
|
||||
:rules="[ v => disableAddress ? true : $validators.required('common.address')(v)]"
|
||||
:label="$t('common.address')"
|
||||
|
@ -32,6 +33,7 @@
|
|||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { mdiMap, mdiMapMarker, mdiPlus } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
name: 'WhereInput',
|
||||
|
@ -40,6 +42,7 @@ export default {
|
|||
},
|
||||
data () {
|
||||
return {
|
||||
mdiMap, mdiMapMarker, mdiPlus,
|
||||
place: { },
|
||||
placeName: '',
|
||||
disableAddress: true
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<template lang="pug">
|
||||
v-container.container.px-0.px-md-3
|
||||
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>mdi-file-import</v-icon> {{$t('common.import')}}
|
||||
<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')
|
||||
|
||||
|
@ -23,7 +23,7 @@
|
|||
@change='v => event.title = v'
|
||||
:value = 'event.title'
|
||||
:rules="[$validators.required('common.title')]"
|
||||
prepend-icon='mdi-format-title'
|
||||
:prepend-icon='mdiFormatTitle'
|
||||
:label="$t('common.title')"
|
||||
autofocus
|
||||
ref='title')
|
||||
|
@ -49,11 +49,15 @@
|
|||
//- tags
|
||||
v-col(cols=12 md=6)
|
||||
v-combobox(v-model='event.tags'
|
||||
prepend-icon="mdi-tag-multiple"
|
||||
:prepend-icon="mdiTagMultiple"
|
||||
chips small-chips multiple deletable-chips hide-no-data hide-selected persistent-hint
|
||||
:delimiters="[',', ' ']"
|
||||
:delimiters="[',', ';']"
|
||||
:items="tags.map(t => t.tag)"
|
||||
: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>
|
||||
|
||||
v-card-actions
|
||||
v-spacer
|
||||
|
@ -64,16 +68,19 @@
|
|||
<script>
|
||||
import { mapActions, mapState } from 'vuex'
|
||||
import dayjs from 'dayjs'
|
||||
import Editor from '@/components/Editor'
|
||||
import List from '@/components/List'
|
||||
import ImportDialog from './ImportDialog'
|
||||
import DateInput from './DateInput'
|
||||
import WhereInput from './WhereInput'
|
||||
import MediaInput from './MediaInput'
|
||||
|
||||
import { mdiFileImport, mdiFormatTitle, mdiTagMultiple, mdiCloseCircle } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
name: 'NewEvent',
|
||||
components: { List, Editor, ImportDialog, MediaInput, WhereInput, DateInput },
|
||||
components: {
|
||||
List: () => import(/* webpackChunkName: "add" */'@/components/List'),
|
||||
Editor: () => import(/* webpackChunkName: "add" */'@/components/Editor'),
|
||||
ImportDialog: () => import(/* webpackChunkName: "add" */'./ImportDialog.vue'),
|
||||
MediaInput: () => import(/* webpackChunkName: "add" */'./MediaInput.vue'),
|
||||
WhereInput: () => import(/* webpackChunkName: "add" */'./WhereInput.vue'),
|
||||
DateInput: () => import(/* webpackChunkName: "add" */'./DateInput.vue')
|
||||
},
|
||||
validate ({ store }) {
|
||||
return (store.state.auth.loggedIn || store.state.settings.allow_anon_event)
|
||||
},
|
||||
|
@ -114,6 +121,7 @@ export default {
|
|||
const month = dayjs().month() + 1
|
||||
const year = dayjs().year()
|
||||
return {
|
||||
mdiFileImport, mdiFormatTitle, mdiTagMultiple, mdiCloseCircle,
|
||||
valid: false,
|
||||
openImportDialog: false,
|
||||
event: {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
v-container#event.pa-0.pa-sm-2
|
||||
//- EVENT PAGE
|
||||
//- gancio supports microformats (http://microformats.org/wiki/h-event)
|
||||
v-card.h-event
|
||||
v-card.h-event(itemscope itemtype="https://schema.org/Event")
|
||||
v-card-actions
|
||||
//- admin controls
|
||||
EventAdmin.mb-1(v-if='is_mine' :event='event')
|
||||
|
@ -11,32 +11,33 @@ v-container#event.pa-0.pa-sm-2
|
|||
v-row
|
||||
v-col.col-12.col-lg-8
|
||||
//- fake image to use u-featured in h-event microformat
|
||||
img.u-featured(v-show='false' v-if='hasMedia' :src='event | mediaURL')
|
||||
img.u-featured(v-show='false' v-if='hasMedia' :src='event | mediaURL' itemprop="image")
|
||||
v-img.main_image.mb-3(
|
||||
contain
|
||||
:alt='event | mediaURL("alt")'
|
||||
:src='event | mediaURL'
|
||||
:lazy-src='event | mediaURL("thumb")'
|
||||
v-if='hasMedia')
|
||||
.p-description.text-body-1.pa-3.rounded(v-if='!hasMedia && event.description' v-html='event.description')
|
||||
.p-description.text-body-1.pa-3.rounded(v-if='!hasMedia && event.description' itemprop='description' v-html='event.description')
|
||||
|
||||
v-col.col-12.col-lg-4
|
||||
v-card
|
||||
v-card(outlined)
|
||||
v-card-text
|
||||
v-icon.float-right(v-if='event.parentId' color='success') mdi-repeat
|
||||
v-icon.float-right(v-if='event.parentId' color='success' v-text='mdiRepeat')
|
||||
.title.text-h5
|
||||
b.p-name {{event.title}}
|
||||
b.p-name(itemprop="name") {{event.title}}
|
||||
|
||||
time.dt-start.text-h6(:datetime='event.start_datetime|unixFormat("YYYY-MM-DD HH:mm")')
|
||||
v-icon mdi-calendar
|
||||
time.dt-start.text-h6(:datetime='event.start_datetime|unixFormat("YYYY-MM-DD HH:mm")' itemprop="startDate" :content="event.start_datetime|unixFormat('YYYY-MM-DDTHH:mm')")
|
||||
v-icon(v-text='mdiCalendar')
|
||||
b.ml-2 {{event|when}}
|
||||
.d-none.dt-end(itemprop="endDate" :content="event.end_datetime|unixFormat('YYYY-MM-DDTHH:mm')") {{event.end_datetime|unixFormat('YYYY-MM-DD HH:mm')}}
|
||||
div.text-subtitle-1 {{event.start_datetime|from}}
|
||||
small(v-if='event.parentId') ({{event|recurrentDetail}})
|
||||
|
||||
.text-h6.p-location
|
||||
v-icon mdi-map-marker
|
||||
b.vcard.ml-2 {{event.place && event.place.name}}
|
||||
.text-subtitle-1.adr {{event.place && event.place.address}}
|
||||
.text-h6.p-location(itemprop="location" itemscope itemtype="https://schema.org/Place")
|
||||
v-icon(v-text='mdiMapMarker')
|
||||
b.vcard.ml-2(itemprop="name") {{event.place && event.place.name}}
|
||||
.text-subtitle-1.adr(itemprop='address') {{event.place && event.place.address}}
|
||||
|
||||
//- tags, hashtags
|
||||
v-card-text(v-if='event.tags.length')
|
||||
|
@ -46,21 +47,16 @@ v-container#event.pa-0.pa-sm-2
|
|||
|
||||
//- info & actions
|
||||
v-toolbar
|
||||
v-tooltip(bottom) {{$t('common.copy_link')}}
|
||||
template(v-slot:activator="{on, attrs} ")
|
||||
v-btn.ml-2(large icon v-on='on' color='primary' @click='clipboard(`${settings.baseurl}/event/${event.slug || event.id}`)')
|
||||
v-icon mdi-content-copy
|
||||
v-tooltip(bottom) {{$t('common.embed')}}
|
||||
template(v-slot:activator="{on, attrs} ")
|
||||
v-btn.ml-2(large icon v-on='on' @click='showEmbed=true' color='primary')
|
||||
v-icon mdi-code-tags
|
||||
v-tooltip(bottom) {{$t('common.add_to_calendar')}}
|
||||
template(v-slot:activator="{on, attrs} ")
|
||||
v-btn.ml-2(large icon v-on='on' color='primary'
|
||||
v-btn.ml-2(large icon :title="$t('common.copy_link')" :aria-label="$t('common.copy_link')" color='primary'
|
||||
@click='clipboard(`${settings.baseurl}/event/${event.slug || event.id}`)')
|
||||
v-icon(v-text='mdiContentCopy')
|
||||
v-btn.ml-2(large icon :title="$t('common.embed')" :aria-label="$t('common.embed')" @click='showEmbed=true' color='primary')
|
||||
v-icon(v-text='mdiCodeTags')
|
||||
v-btn.ml-2(large icon :title="$t('common.add_to_calendar')" color='primary' :aria-label="$t('common.add_to_calendar')"
|
||||
:href='`/api/event/${event.slug || event.id}.ics`')
|
||||
v-icon mdi-calendar-export
|
||||
v-icon(v-text='mdiCalendarExport')
|
||||
|
||||
.p-description.text-body-1.pa-3.rounded(v-if='hasMedia && event.description' v-html='event.description')
|
||||
.p-description.text-body-1.pa-3.rounded(v-if='hasMedia && event.description' itemprop='description' v-html='event.description')
|
||||
|
||||
//- resources from fediverse
|
||||
#resources.mt-1(v-if='settings.enable_federation')
|
||||
|
@ -68,39 +64,41 @@ v-container#event.pa-0.pa-sm-2
|
|||
//- small.mr-3 🔖 {{event.likes.length}}
|
||||
//- small ✊ {{event.boost.length}}<br/>
|
||||
|
||||
v-dialog(v-model='showResources'
|
||||
fullscreen
|
||||
destroy-on-close
|
||||
scrollable
|
||||
transition='dialog-bottom-transition')
|
||||
v-dialog(v-model='showResources' max-width="900" width="900" :fullscreen='$vuetify.breakpoint.xsOnly'
|
||||
destroy-on-close)
|
||||
v-card
|
||||
v-btn.ma-2(icon dark @click='showResources = false')
|
||||
v-icon mdi-close
|
||||
v-carousel.pa-5(:interval='10000' ref='carousel' hide-delimiters v-model='currentAttachment'
|
||||
v-icon(v-text='mdiClose')
|
||||
v-carousel.pa-5(:interval='10000'
|
||||
:next-icon='mdiArrowRight'
|
||||
:prev-icon='mdiArrowLeft'
|
||||
ref='carousel' hide-delimiters v-model='currentAttachment'
|
||||
height='100%' show-arrows-on-over)
|
||||
v-carousel-item(v-for='attachment in selectedResource.data.attachment'
|
||||
v-if='isImg(attachment)'
|
||||
:key='attachment.url')
|
||||
v-img(:src='attachment.url' contain max-width='100%' max-height='100%')
|
||||
v-img(:src='attachment.url' contain max-height='90%')
|
||||
v-card-actions.align-center.justify-center
|
||||
span {{currentAttachmentLabel}}
|
||||
|
||||
v-card.grey.darken-4.mb-3#resources(v-if='settings.enable_resources' v-for='resource in event.resources'
|
||||
:key='resource.id' :class='{disabled: resource.hidden}' elevation='10' outlined)
|
||||
v-card.mb-3.resources(v-if='settings.enable_resources' v-for='resource in event.resources'
|
||||
:key='resource.id' elevation='10' :flat='resource.hidden' outlined)
|
||||
v-card-title
|
||||
v-menu(v-if='$auth.user && $auth.user.is_admin' offset-y)
|
||||
template(v-slot:activator="{ on }")
|
||||
v-btn.mr-2(v-on='on' color='primary' small icon)
|
||||
v-icon mdi-dots-vertical
|
||||
v-icon(v-text='mdiDotsVertical')
|
||||
v-list
|
||||
v-list-item(v-if='!resource.hidden' @click='hideResource(resource, true)')
|
||||
v-list-item-title <v-icon left>mdi-eye-off</v-icon> {{$t('admin.hide_resource')}}
|
||||
v-list-item-title <v-icon left v-text='mdiEyeOff'></v-icon> {{$t('admin.hide_resource')}}
|
||||
v-list-item(v-else @click='hideResource(resource, false)')
|
||||
v-list-item-title <v-icon left>mdi-eye</v-icon> {{$t('admin.show_resource')}}
|
||||
v-list-item-title <v-icon left v-text='mdiEye'></v-icon> {{$t('admin.show_resource')}}
|
||||
v-list-item(@click='deleteResource(resource)')
|
||||
v-list-item-title <v-icon left>mdi-delete</v-icon> {{$t('admin.delete_resource')}}
|
||||
v-list-item-title <v-icon left v-text='mdiDelete'></v-icon> {{$t('admin.delete_resource')}}
|
||||
v-list-item(@click='blockUser(resource)')
|
||||
v-list-item-title <v-icon left>mdi-lock</v-icon> {{$t('admin.block_user')}}
|
||||
v-list-item-title <v-icon left v-text='mdiLock'></v-icon> {{$t('admin.block_user')}}
|
||||
|
||||
v-icon.mr-1(v-show='resource.hidden' v-text='mdiEyeOff')
|
||||
|
||||
a(:href='resource.data.url || resource.data.context')
|
||||
small {{resource.data.published|dateFormat('ddd, D MMMM HH:mm')}}
|
||||
|
@ -108,7 +106,8 @@ v-container#event.pa-0.pa-sm-2
|
|||
v-card-text
|
||||
|
||||
div.mt-1(v-html='resource_filter(resource.data.content)')
|
||||
span(v-for='attachment in resource.data.attachment' :key='attachment.url')
|
||||
div.d-flex.flex-wrap
|
||||
span.mr-1(v-for='attachment in resource.data.attachment' :key='attachment.url')
|
||||
audio(v-if='isAudio(attachment)' controls)
|
||||
source(:src='attachment.url')
|
||||
v-img.cursorPointer(v-if='isImg(attachment)' :src='attachment.url' @click='showResource(resource)'
|
||||
|
@ -120,10 +119,10 @@ v-container#event.pa-0.pa-sm-2
|
|||
.text-center.mt-5.mb-5
|
||||
v-btn.mr-2(nuxt icon outlined color='primary'
|
||||
:to='`/event/${event.prev}`' :disabled='!event.prev')
|
||||
v-icon mdi-arrow-left
|
||||
v-icon(v-text='mdiArrowLeft')
|
||||
v-btn(nuxt bottom right outlined icon color='primary'
|
||||
:to='`/event/${event.next}`' :disabled='!event.next')
|
||||
v-icon mdi-arrow-right
|
||||
v-icon(v-text='mdiArrowRight')
|
||||
|
||||
v-dialog(v-model='showEmbed' width='700px' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||
EmbedEvent(:event='event' @close='showEmbed=false')
|
||||
|
@ -131,18 +130,22 @@ v-container#event.pa-0.pa-sm-2
|
|||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import EventAdmin from './eventAdmin'
|
||||
import EmbedEvent from './embedEvent'
|
||||
import get from 'lodash/get'
|
||||
import moment from 'dayjs'
|
||||
import clipboard from '../../assets/clipboard'
|
||||
|
||||
const htmlToText = require('html-to-text')
|
||||
|
||||
import { mdiArrowLeft, mdiArrowRight, mdiDotsVertical, mdiCodeTags, mdiClose,
|
||||
mdiEye, mdiEyeOff, mdiDelete, mdiRepeat, mdiLock,
|
||||
mdiCalendarExport, mdiCalendar, mdiContentCopy, mdiMapMarker } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
name: 'Event',
|
||||
mixins: [clipboard],
|
||||
components: { EventAdmin, EmbedEvent },
|
||||
components: {
|
||||
EventAdmin: () => import(/* webpackChunkName: "event" */'./eventAdmin'),
|
||||
EmbedEvent: () => import(/* webpackChunkName: "event" */'./embedEvent'),
|
||||
},
|
||||
async asyncData ({ $axios, params, error, store }) {
|
||||
try {
|
||||
const event = await $axios.$get(`/event/${params.slug}`)
|
||||
|
@ -153,6 +156,8 @@ export default {
|
|||
},
|
||||
data () {
|
||||
return {
|
||||
mdiArrowLeft, mdiArrowRight, mdiDotsVertical, mdiCodeTags, mdiCalendarExport, mdiCalendar,
|
||||
mdiMapMarker, mdiContentCopy, mdiClose, mdiDelete, mdiEye, mdiEyeOff, mdiRepeat, mdiLock,
|
||||
currentAttachment: 0,
|
||||
event: {},
|
||||
showEmbed: false,
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
v-card
|
||||
v-card-title(v-text="$t('common.embed_title')")
|
||||
v-card-text
|
||||
v-alert.mb-3.mt-1(type='info' show-icon) {{$t('common.embed_help')}}
|
||||
v-alert.mb-3.mt-1(type='info' show-icon :icon='mdiInformation') {{$t('common.embed_help')}}
|
||||
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 mdi-content-copy
|
||||
v-icon.ml-1(v-text='mdiContentCopy')
|
||||
p.mx-auto
|
||||
.mx-auto
|
||||
gancio-event(:id='event.id' :baseurl='settings.baseurl')
|
||||
|
@ -17,9 +17,13 @@ v-card
|
|||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import clipboard from '../../assets/clipboard'
|
||||
import { mdiContentCopy, mdiInformation } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
name: 'EmbedEvent',
|
||||
data() {
|
||||
return { mdiContentCopy, mdiInformation }
|
||||
},
|
||||
mixins: [clipboard],
|
||||
props: {
|
||||
event: { type: Object, default: () => ({}) }
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
<template lang='pug'>
|
||||
div
|
||||
v-btn(text color='primary' v-if='event.is_visible' @click='toggle(false)') {{$t(`common.${event.parentId?'skip':'hide'}`)}}
|
||||
v-btn(text color='success' v-else @click='toggle(false)') <v-icon color='yellow'>mdi-alert</v-icon> {{$t('common.confirm')}}
|
||||
v-btn(text color='success' v-else @click='toggle(false)') <v-icon color='yellow' v-text='mdiAlert'></v-icon> {{$t('common.confirm')}}
|
||||
v-btn(text color='primary' @click='$router.push(`/add/${event.id}`)') {{$t('common.edit')}}
|
||||
v-btn(text color='primary' v-if='!event.parentId' @click='remove(false)') {{$t('common.remove')}}
|
||||
|
||||
template(v-if='event.parentId')
|
||||
v-divider
|
||||
span.mr-1 <v-icon>mdi-repeat</v-icon> {{$t('event.edit_recurrent')}}
|
||||
span.mr-1 <v-icon v-text='mdiRepeat'></v-icon> {{$t('event.edit_recurrent')}}
|
||||
v-btn(text color='primary' v-if='event.parent.is_visible' @click='toggle(true)') {{$t('common.pause')}}
|
||||
v-btn(text color='primary' v-else @click='toggle(true)') {{$t('common.start')}}
|
||||
v-btn(text color='primary' @click='$router.push(`/add/${event.parentId}`)') {{$t('common.edit')}}
|
||||
v-btn(text color='primary' @click='remove(true)') {{$t('common.remove')}}
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import { mdiAlert, mdiRepeat } from '@mdi/js'
|
||||
export default {
|
||||
name: 'EventAdmin',
|
||||
data () {
|
||||
return { mdiAlert, mdiRepeat }
|
||||
},
|
||||
props: {
|
||||
event: {
|
||||
type: Object,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template lang="pug">
|
||||
v-container
|
||||
v-card(outlined)
|
||||
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')}}
|
||||
|
@ -11,7 +11,7 @@
|
|||
Search(
|
||||
:filters='filters'
|
||||
@update='f => filters = f')
|
||||
v-tabs(v-model='type')
|
||||
v-tabs(v-model='type' show-arrows)
|
||||
|
||||
//- TOFIX
|
||||
//- v-tab {{$t('common.email')}}
|
||||
|
@ -31,7 +31,7 @@
|
|||
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 mdi-content-copy
|
||||
v-icon.ml-1(v-text='mdiContentCopy')
|
||||
|
||||
v-tab ics/ical
|
||||
v-tab-item
|
||||
|
@ -40,7 +40,7 @@
|
|||
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 mdi-content-copy
|
||||
v-icon.ml-1(v-text='mdiContentCopy')
|
||||
|
||||
v-tab List
|
||||
v-tab-item
|
||||
|
@ -49,19 +49,23 @@
|
|||
p(v-html='$t(`export.list_description`)')
|
||||
|
||||
v-row
|
||||
v-col.mr-2(:span='11')
|
||||
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-col.float-right(:span='12')
|
||||
span {{filters.places.join(',')}}
|
||||
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(",")')
|
||||
: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 mdi-content-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')
|
||||
|
@ -79,14 +83,17 @@
|
|||
<script>
|
||||
import dayjs from 'dayjs'
|
||||
import { mapState } from 'vuex'
|
||||
import List from '@/components/List'
|
||||
import FollowMe from '../components/FollowMe'
|
||||
import Search from '@/components/Search'
|
||||
import clipboard from '../assets/clipboard'
|
||||
import { mdiContentCopy } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
name: 'Exports',
|
||||
components: { List, FollowMe, Search },
|
||||
components: {
|
||||
FollowMe,
|
||||
Search
|
||||
},
|
||||
mixins: [clipboard],
|
||||
async asyncData ({ $axios, params, store, $api }) {
|
||||
const events = await $api.getEvents({
|
||||
|
@ -95,18 +102,24 @@ export default {
|
|||
})
|
||||
return { events }
|
||||
},
|
||||
data () {
|
||||
data ({ $store }) {
|
||||
return {
|
||||
mdiContentCopy,
|
||||
type: 'rss',
|
||||
notification: { email: '' },
|
||||
list: { title: 'Gancio', maxEvents: null },
|
||||
list: {
|
||||
title: $store.state.settings.title,
|
||||
maxEvents: null,
|
||||
theme: $store.state.settings['theme.is_dark'] ? 'dark' : 'light',
|
||||
sidebar: 'true'
|
||||
},
|
||||
filters: { tags: [], places: [], show_recurrent: false },
|
||||
events: []
|
||||
}
|
||||
},
|
||||
head () {
|
||||
return {
|
||||
title: `${this.settings.title} - ${this.$t('common.export')}`
|
||||
title: `${this.settings.title} - ${this.$t('common.export')}`,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -114,7 +127,7 @@ export default {
|
|||
code () {
|
||||
const params = [`baseurl="${this.settings.baseurl}"`]
|
||||
|
||||
if (this.list.title) {
|
||||
if (this.list.title && this.list.sidebar === 'true') {
|
||||
params.push(`title="${this.list.title}"`)
|
||||
}
|
||||
|
||||
|
@ -127,23 +140,27 @@ export default {
|
|||
}
|
||||
|
||||
if (this.filters.show_recurrent) {
|
||||
params.push('show_recurrent')
|
||||
params.push(`show_recurrent="${this.filters.show_recurrent}"`)
|
||||
}
|
||||
|
||||
if (this.list.maxEvents) {
|
||||
params.push('maxlength=' + this.list.maxEvents)
|
||||
}
|
||||
|
||||
return `<script src="${this.settings.baseurl}\/gancio-events.es.js'><\/script>\n<gancio-events ${params.join(' ')}></gancio-events>\n\n`
|
||||
params.push('sidebar="' + this.list.sidebar + '"')
|
||||
|
||||
params.push(`theme="${this.list.theme}"`)
|
||||
|
||||
return `<script src="${this.settings.baseurl}\/gancio-events.es.js"><\/script>\n<gancio-events ${params.join(' ')}></gancio-events>\n\n`
|
||||
|
||||
|
||||
},
|
||||
link () {
|
||||
const typeMap = ['rss', 'ics', 'list']
|
||||
const typeMap = ['rss', 'ics']
|
||||
const params = []
|
||||
|
||||
if (this.filters.tags.length) {
|
||||
params.push(`tags=${this.filters.tags.join(',')}`)
|
||||
params.push(`tags=${this.filters.tags.map(encodeURIComponent).join(',')}`)
|
||||
}
|
||||
|
||||
if (this.filters.places.length) {
|
||||
|
@ -154,7 +171,7 @@ export default {
|
|||
params.push('show_recurrent=true')
|
||||
}
|
||||
|
||||
return `${this.settings.baseurl}/feed/${typeMap[this.type]}?${params.join('&')}`
|
||||
return `${this.settings.baseurl}/feed/${typeMap[this.type]}${params.length ? '?' : ''}${params.join('&')}`
|
||||
},
|
||||
showLink () {
|
||||
return (['rss', 'ics'].includes(this.type))
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
//- Calendar and search bar
|
||||
v-row.pt-0.pt-sm-2.pl-0.pl-sm-2
|
||||
.col-xl-5.col-lg-5.col-md-7.col-sm-12.col-xs-12.pa-4.pa-sm-3
|
||||
#calh.col-xl-5.col-lg-5.col-md-7.col-sm-12.col-xs-12.pa-4.pa-sm-3
|
||||
//- this is needed as v-calendar does not support SSR
|
||||
//- https://github.com/nathanreyes/v-calendar/issues/336
|
||||
client-only(placeholder='Calendar unavailable without js')
|
||||
|
@ -15,12 +15,11 @@
|
|||
|
||||
.col.pt-0.pt-md-2
|
||||
Search(:filters='filters' @update='updateFilters')
|
||||
v-chip(v-if='selectedDay' close @click:close='dayChange({ date: selectedDay})') {{selectedDay}}
|
||||
v-chip(v-if='selectedDay' close :close-icon='mdiCloseCircle' @click:close='dayChange()') {{selectedDay}}
|
||||
|
||||
//- Events
|
||||
#events.mb-2.mt-1.pl-1.pl-sm-2
|
||||
//- div.event(v-for='(event, idx) in events' :key='event.id' v-intersect="(entries, observer, isIntersecting) => intersecting[event.id] = isIntersecting")
|
||||
Event(:event='event' @destroy='destroy' v-for='(event, idx) in visibleEvents' :key='event.id' @tagclick='tagClick' @placeclick='placeClick')
|
||||
Event(:event='event' @destroy='destroy' v-for='(event, idx) in visibleEvents' :lazy='idx>2' :key='event.id' @tagclick='tagClick' @placeclick='placeClick')
|
||||
|
||||
</template>
|
||||
|
||||
|
@ -32,6 +31,7 @@ import Event from '@/components/Event'
|
|||
import Announcement from '@/components/Announcement'
|
||||
import Search from '@/components/Search'
|
||||
import Calendar from '@/components/Calendar'
|
||||
import { mdiCloseCircle } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
name: 'Index',
|
||||
|
@ -47,6 +47,7 @@ export default {
|
|||
},
|
||||
data ({ $store }) {
|
||||
return {
|
||||
mdiCloseCircle,
|
||||
first: true,
|
||||
isCurrentMonth: true,
|
||||
now: dayjs().unix(),
|
||||
|
@ -174,12 +175,7 @@ export default {
|
|||
this.setFilters(filters)
|
||||
},
|
||||
dayChange (day) {
|
||||
const date = dayjs(day.date).format('YYYY-MM-DD')
|
||||
if (this.selectedDay === date) {
|
||||
this.selectedDay = null
|
||||
return
|
||||
}
|
||||
this.selectedDay = date
|
||||
this.selectedDay = day ? dayjs(day).format('YYYY-MM-DD') : null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,15 +2,18 @@
|
|||
v-container
|
||||
v-card-title.d-block.text-h5.text-center(v-text="$t('setup.completed')")
|
||||
v-card-text(v-html="$t('setup.completed_description', user)")
|
||||
v-alert.mb-3.mt-1(outlined type='warning' color='red' show-icon :icon='mdiAlert') {{$t('setup.copy_password_dialog')}}
|
||||
v-card-actions
|
||||
v-btn(text @click='next' color='primary' :loading='loading' :disabled='loading') {{$t('setup.start')}}
|
||||
v-icon mdi-arrow-right
|
||||
v-icon(v-text='mdiArrowRight')
|
||||
</template>
|
||||
<script>
|
||||
import { mdiArrowRight, mdiAlert } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
mdiArrowRight, mdiAlert,
|
||||
loading: false,
|
||||
user: {
|
||||
email: 'admin',
|
||||
|
|
|
@ -6,25 +6,30 @@
|
|||
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 === "postgres"')
|
||||
v-text-field(v-model='db.hostname' label='Hostname' :rules="[$validators.required('hostname')]")
|
||||
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 mdi-arrow-right
|
||||
v-icon(v-text='mdiArrowRight')
|
||||
</template>
|
||||
<script>
|
||||
import { mdiArrowRight } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
mdiArrowRight,
|
||||
db: {
|
||||
dialect: 'sqlite',
|
||||
storage: './gancio.sqlite',
|
||||
hostname: 'localhost',
|
||||
host: 'localhost',
|
||||
database: 'gancio'
|
||||
},
|
||||
loading: false
|
||||
|
|
|
@ -4,14 +4,14 @@
|
|||
h2.mb-2.text-center Gancio Setup
|
||||
v-stepper.grey.lighten-5(v-model='step')
|
||||
v-stepper-header
|
||||
v-stepper-step(:complete='step > 1' step='1') Database
|
||||
v-divider
|
||||
v-stepper-step(v-show='!dbdone' :complete='step > 1' step='1') Database
|
||||
v-divider(v-show='!dbdone')
|
||||
v-stepper-step(:complete='step > 2' step='2') Configuration
|
||||
v-divider
|
||||
v-stepper-step(:complete='step > 3' step='3') Finish
|
||||
|
||||
v-stepper-items
|
||||
v-stepper-content(step='1')
|
||||
v-stepper-content(v-show='!dbdone' step='1')
|
||||
DbStep(@complete='dbCompleted')
|
||||
v-stepper-content(step='2')
|
||||
Settings(setup, @complete='configCompleted')
|
||||
|
@ -36,14 +36,16 @@ export default {
|
|||
title: 'Setup',
|
||||
},
|
||||
auth: false,
|
||||
data () {
|
||||
asyncData ({ params }) {
|
||||
|
||||
return {
|
||||
dbdone: !!Number(params.db),
|
||||
config: {
|
||||
db: {
|
||||
dialect: ''
|
||||
}
|
||||
},
|
||||
step: 1
|
||||
step: 1 + Number(params.db)
|
||||
}
|
||||
},
|
||||
methods: {
|
|
@ -3,6 +3,8 @@ import dayjs from 'dayjs'
|
|||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
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/es'
|
||||
|
@ -15,6 +17,7 @@ import 'dayjs/locale/fr'
|
|||
dayjs.extend(relativeTime)
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(localizedFormat)
|
||||
|
||||
export default ({ app, store }) => {
|
||||
// set timezone to instance_timezone!!
|
||||
|
|
|
@ -4,12 +4,12 @@ import merge from 'lodash/merge'
|
|||
|
||||
Vue.use(VueI18n)
|
||||
|
||||
export default async ({ app, store, req }) => {
|
||||
export default async ({ app, store, res }) => {
|
||||
const messages = {}
|
||||
if (process.server) {
|
||||
store.commit('setLocale', req.acceptedLocale)
|
||||
if (req.user_locale) {
|
||||
store.commit('setUserLocale', req.user_locale)
|
||||
store.commit('setLocale', res.locals.acceptedLocale)
|
||||
if (res.locals.user_locale) {
|
||||
store.commit('setUserLocale', res.locals.user_locale)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
import Vue from 'vue'
|
||||
import Vuetify from 'vuetify'
|
||||
|
||||
// import it from 'vuetify/lib/locale/it.js'
|
||||
// import en from 'vuetify/lib/locale/en.js'
|
||||
// import es from 'vuetify/lib/locale/es'
|
||||
// import no from 'vuetify/lib/locale/no'
|
||||
// import fr from 'vuetify/lib/locale/fr'
|
||||
// import ca from 'vuetify/lib/locale/ca'
|
||||
|
||||
|
||||
export default ({ app }) => {
|
||||
Vue.use(Vuetify)
|
||||
app.vuetify = new Vuetify({
|
||||
// lang: {
|
||||
// locales: { en, it }, //, es, fr, no, ca },
|
||||
// current: 'en'
|
||||
// },
|
||||
icons: {
|
||||
iconfont: 'mdi'
|
||||
},
|
||||
theme: {
|
||||
dark: true,
|
||||
themes: {
|
||||
dark: {
|
||||
primary: '#FF6E40'
|
||||
},
|
||||
light: {
|
||||
primary: '#FF4500'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
12
release.sh
Executable file
12
release.sh
Executable file
|
@ -0,0 +1,12 @@
|
|||
RELEASE=v$(cat package.json | jq ".version" | sed -e 's/"//g')
|
||||
echo "Releasing $RELEASE"
|
||||
yarn build
|
||||
yarn pack
|
||||
yarn publish
|
||||
yarn doc
|
||||
gpg --detach-sign --local-user 5DAC477D5441B7A15ACBF680BBEB4DD39AC6CCA9 gancio-$RELEASE.tgz
|
||||
cp gancio-$RELEASE.tgz releases/
|
||||
mv gancio-$RELEASE.tgz releases/latest.tgz
|
||||
cp gancio-$RELEASE.tgz.sig releases/
|
||||
mv gancio-$RELEASE.tgz.sig releases/latest.tgz.sig
|
||||
rsync -a docs/_site/ gancio.org:/var/www/gancio/
|
|
@ -16,24 +16,24 @@ const Auth = {
|
|||
}
|
||||
|
||||
oauth.oauthServer.authenticate()(req, res, () => {
|
||||
req.user = get(res, 'locals.oauth.token.user', null)
|
||||
res.locals.user = get(res, 'locals.oauth.token.user', null)
|
||||
next()
|
||||
})
|
||||
},
|
||||
|
||||
isAuth (req, res, next) {
|
||||
if (req.user) {
|
||||
if (res.locals.user) {
|
||||
next()
|
||||
} else {
|
||||
res.sendStatus(404)
|
||||
res.sendStatus(403)
|
||||
}
|
||||
},
|
||||
|
||||
isAdmin (req, res, next) {
|
||||
if (req.user.is_admin) {
|
||||
if (res.locals.user && res.locals.user.is_admin) {
|
||||
next()
|
||||
} else {
|
||||
res.status(404)
|
||||
res.sendStatus(403)
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -100,7 +100,7 @@ const eventController = {
|
|||
|
||||
async get (req, res) {
|
||||
const format = req.params.format || 'json'
|
||||
const is_admin = req.user && req.user.is_admin
|
||||
const is_admin = res.locals.user && res.locals.user.is_admin
|
||||
const slug = req.params.event_slug
|
||||
|
||||
// retrocompatibility, old events URL does not use slug, use id as fallback
|
||||
|
@ -145,21 +145,35 @@ const eventController = {
|
|||
const next = await Event.findOne({
|
||||
attributes: ['id', 'slug'],
|
||||
where: {
|
||||
id: { [Op.not]: event.id },
|
||||
is_visible: true,
|
||||
recurrent: null,
|
||||
start_datetime: { [Op.gt]: event.start_datetime }
|
||||
[Op.or]: [
|
||||
{ start_datetime: { [Op.gt]: event.start_datetime } },
|
||||
{
|
||||
start_datetime: event.start_datetime,
|
||||
id: { [Op.gt]: event.id }
|
||||
}
|
||||
]
|
||||
},
|
||||
order: [['start_datetime', 'ASC']]
|
||||
order: [['start_datetime', 'ASC'], ['id', 'ASC']]
|
||||
})
|
||||
|
||||
const prev = await Event.findOne({
|
||||
attributes: ['id', 'slug'],
|
||||
where: {
|
||||
is_visible: true,
|
||||
id: { [Op.not]: event.id },
|
||||
recurrent: null,
|
||||
start_datetime: { [Op.lt]: event.start_datetime }
|
||||
[Op.or]: [
|
||||
{ start_datetime: { [Op.lt]: event.start_datetime } },
|
||||
{
|
||||
start_datetime: event.start_datetime,
|
||||
id: { [Op.lt]: event.id }
|
||||
}
|
||||
]
|
||||
},
|
||||
order: [['start_datetime', 'DESC']]
|
||||
order: [['start_datetime', 'DESC'], ['id', 'DESC']]
|
||||
})
|
||||
|
||||
// TODO: also check if event is mine
|
||||
|
@ -192,7 +206,7 @@ const eventController = {
|
|||
log.warn(`Trying to confirm a unknown event, id: ${id}`)
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
if (!req.user.is_admin && req.user.id !== event.userId) {
|
||||
if (!res.locals.user.is_admin && res.locals.user.id !== event.userId) {
|
||||
log.warn(`Someone unallowed is trying to confirm -> "${event.title} `)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
@ -218,7 +232,7 @@ const eventController = {
|
|||
const id = Number(req.params.event_id)
|
||||
const event = await Event.findByPk(id)
|
||||
if (!event) { return req.sendStatus(404) }
|
||||
if (!req.user.is_admin && req.user.id !== event.userId) {
|
||||
if (!res.locals.user.is_admin && res.locals.user.id !== event.userId) {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
|
@ -276,6 +290,13 @@ const eventController = {
|
|||
res.sendStatus(200)
|
||||
},
|
||||
|
||||
async isAnonEventAllowed (req, res, next) {
|
||||
if (!res.locals.settings.allow_anon_event) {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
next()
|
||||
},
|
||||
|
||||
async add (req, res) {
|
||||
// req.err comes from multer streaming error
|
||||
if (req.err) {
|
||||
|
@ -287,21 +308,23 @@ const eventController = {
|
|||
const body = req.body
|
||||
const recurrent = body.recurrent ? JSON.parse(body.recurrent) : null
|
||||
|
||||
if (!body.place_name) {
|
||||
log.warn('Place is required')
|
||||
return res.status(400).send('Place is required')
|
||||
const required_fields = [ 'title', 'place_name', 'start_datetime']
|
||||
const missing_field = required_fields.find(required_field => !body[required_field])
|
||||
if (missing_field) {
|
||||
log.warn(`${missing_field} is required`)
|
||||
return res.status(400).send(`${missing_field} is required`)
|
||||
}
|
||||
|
||||
const eventDetails = {
|
||||
title: body.title,
|
||||
// remove html tags
|
||||
description: helpers.sanitizeHTML(linkifyHtml(body.description)),
|
||||
description: helpers.sanitizeHTML(linkifyHtml(body.description || '')),
|
||||
multidate: body.multidate,
|
||||
start_datetime: body.start_datetime,
|
||||
end_datetime: body.end_datetime,
|
||||
recurrent,
|
||||
// publish this event only if authenticated
|
||||
is_visible: !!req.user
|
||||
is_visible: !!res.locals.user
|
||||
}
|
||||
|
||||
if (req.file || body.image_url) {
|
||||
|
@ -344,9 +367,9 @@ const eventController = {
|
|||
}
|
||||
|
||||
// associate user to event and reverse
|
||||
if (req.user) {
|
||||
await req.user.addEvent(event)
|
||||
await event.setUser(req.user)
|
||||
if (res.locals.user) {
|
||||
await res.locals.user.addEvent(event)
|
||||
await event.setUser(res.locals.user)
|
||||
}
|
||||
|
||||
// return created event to the client
|
||||
|
@ -368,15 +391,15 @@ const eventController = {
|
|||
},
|
||||
|
||||
async update (req, res) {
|
||||
if (req.err) {
|
||||
return res.status(400).json(req.err.toString())
|
||||
if (res.locals.err) {
|
||||
return res.status(400).json(res.locals.err.toString())
|
||||
}
|
||||
|
||||
try {
|
||||
const body = req.body
|
||||
const event = await Event.findByPk(body.id)
|
||||
if (!event) { return res.sendStatus(404) }
|
||||
if (!req.user.is_admin && event.userId !== req.user.id) {
|
||||
if (!res.locals.user.is_admin && event.userId !== res.locals.user.id) {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
|
@ -456,7 +479,7 @@ const eventController = {
|
|||
async remove (req, res) {
|
||||
const event = await Event.findByPk(req.params.id)
|
||||
// check if event is mine (or user is admin)
|
||||
if (event && (req.user.is_admin || req.user.id === event.userId)) {
|
||||
if (event && (res.locals.user.is_admin || res.locals.user.id === event.userId)) {
|
||||
if (event.media && event.media.length && !event.recurrent) {
|
||||
const old_path = path.join(config.upload_path, event.media[0].url)
|
||||
const old_thumb_path = path.join(config.upload_path, 'thumb', event.media[0].url)
|
||||
|
@ -504,13 +527,19 @@ const eventController = {
|
|||
where.start_datetime = { [Op.lte]: end }
|
||||
}
|
||||
|
||||
if (places) {
|
||||
where.placeId = places.split(',')
|
||||
if (tags && places) {
|
||||
where[Op.or] = {
|
||||
placeId: places ? places.split(',') : [],
|
||||
'$tags.tag$': tags.split(',')
|
||||
}
|
||||
}
|
||||
|
||||
let where_tags = {}
|
||||
if (tags) {
|
||||
where_tags = { where: { [Op.or]: { tag: tags.split(',') } } }
|
||||
where['$tags.tag$'] = tags.split(',')
|
||||
}
|
||||
|
||||
if (places) {
|
||||
where.placeId = places.split(',')
|
||||
}
|
||||
|
||||
const events = await Event.findAll({
|
||||
|
@ -526,7 +555,6 @@ const eventController = {
|
|||
order: [Sequelize.literal('(SELECT COUNT("tagTag") FROM event_tags WHERE tagTag = tag) DESC')],
|
||||
attributes: ['tag'],
|
||||
required: !!tags,
|
||||
...where_tags,
|
||||
through: { attributes: [] }
|
||||
},
|
||||
{ model: Place, required: true, attributes: ['id', 'name', 'address'] }
|
||||
|
@ -548,13 +576,15 @@ const eventController = {
|
|||
* Select events based on params
|
||||
*/
|
||||
async select (req, res) {
|
||||
const settings = res.locals.settings
|
||||
const start = req.query.start || dayjs().unix()
|
||||
const end = req.query.end
|
||||
const tags = req.query.tags
|
||||
const places = req.query.places
|
||||
const max = req.query.max
|
||||
const show_recurrent = settingsController.settings.allow_recurrent_event &&
|
||||
(typeof req.query.show_recurrent !== 'undefined' ? req.query.show_recurrent === 'true' : settingsController.settings.recurrent_event_visible)
|
||||
|
||||
const show_recurrent = settings.allow_recurrent_event &&
|
||||
typeof req.query.show_recurrent !== 'undefined' ? req.query.show_recurrent === 'true' : settings.recurrent_event_visible
|
||||
|
||||
res.json(await eventController._select({
|
||||
start, end, places, tags, show_recurrent, max
|
||||
|
@ -563,9 +593,8 @@ const eventController = {
|
|||
|
||||
/**
|
||||
* Ensure we have the next instance of a recurrent event
|
||||
* TODO: create a future instance if the next one is skipped
|
||||
*/
|
||||
_createRecurrentOccurrence (e) {
|
||||
async _createRecurrentOccurrence (e, startAt) {
|
||||
log.debug(`Create recurrent event [${e.id}] ${e.title}"`)
|
||||
const event = {
|
||||
parentId: e.id,
|
||||
|
@ -579,23 +608,20 @@ const eventController = {
|
|||
|
||||
const recurrent = e.recurrent
|
||||
const start_date = dayjs.unix(e.start_datetime)
|
||||
const now = dayjs()
|
||||
let cursor = start_date > now ? start_date : now
|
||||
let cursor = start_date > startAt ? start_date : startAt
|
||||
startAt = cursor
|
||||
const duration = dayjs.unix(e.end_datetime).diff(start_date, 's')
|
||||
const frequency = recurrent.frequency
|
||||
const type = recurrent.type
|
||||
|
||||
log.info(`NOW IS ${cursor} while event is at ${start_date} (freq: ${frequency})`)
|
||||
|
||||
cursor = cursor.hour(start_date.hour()).minute(start_date.minute()).second(0)
|
||||
log.info(`set cursor to correct date and hour => ${cursor}`)
|
||||
|
||||
if (!frequency) { return }
|
||||
|
||||
// each week or 2
|
||||
if (frequency[1] === 'w') {
|
||||
cursor = cursor.day(start_date.day())
|
||||
if (cursor.isBefore(dayjs())) {
|
||||
if (cursor.isBefore(startAt)) {
|
||||
cursor = cursor.add(7, 'day')
|
||||
}
|
||||
if (frequency[0] === '2') {
|
||||
|
@ -605,24 +631,24 @@ const eventController = {
|
|||
if (type === 'ordinal') {
|
||||
cursor = cursor.date(start_date.date())
|
||||
|
||||
if (cursor.isBefore(dayjs())) {
|
||||
if (cursor.isBefore(startAt)) {
|
||||
cursor = cursor.add(1, 'month')
|
||||
}
|
||||
} else { // weekday
|
||||
// get weekday
|
||||
log.info(type)
|
||||
// get recurrent freq details
|
||||
cursor = helpers.getWeekdayN(cursor, type, start_date.day())
|
||||
if (cursor.isBefore(dayjs())) {
|
||||
if (cursor.isBefore(startAt)) {
|
||||
cursor = cursor.add(4, 'week')
|
||||
cursor = helpers.getWeekdayN(cursor, type, start_date.day())
|
||||
}
|
||||
}
|
||||
}
|
||||
log.info(cursor)
|
||||
log.debug(cursor)
|
||||
event.start_datetime = cursor.unix()
|
||||
event.end_datetime = event.start_datetime + duration
|
||||
Event.create(event)
|
||||
const newEvent = await Event.create(event)
|
||||
return newEvent.addTags(e.tags)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -632,13 +658,19 @@ const eventController = {
|
|||
// select recurrent events and its childs
|
||||
const events = await Event.findAll({
|
||||
where: { is_visible: true, recurrent: { [Op.ne]: null } },
|
||||
include: [{ model: Event, as: 'child', required: false, where: { start_datetime: { [Op.gte]: start_datetime } } }],
|
||||
order: ['start_datetime']
|
||||
include: [{ model: Tag, required: false },
|
||||
{ model: Event, as: 'child', required: false, where: { start_datetime: { [Op.gte]: start_datetime } }}],
|
||||
order: [['child', 'start_datetime', 'DESC']]
|
||||
})
|
||||
|
||||
// create a new occurrence for each recurring events but the one's that has an already visible occurrence coming
|
||||
const creations = events.map(e => {
|
||||
if (e.child.length) {
|
||||
if (e.child.find(c => c.is_visible)) return
|
||||
return eventController._createRecurrentOccurrence(e, dayjs.unix(e.child[0].start_datetime+1))
|
||||
}
|
||||
return eventController._createRecurrentOccurrence(e, dayjs())
|
||||
})
|
||||
// filter events that as no instance in future yet
|
||||
const creations = events
|
||||
.filter(e => e.child.length === 0)
|
||||
.map(eventController._createRecurrentOccurrence)
|
||||
|
||||
return Promise.all(creations)
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ const Event = require('../models/event')
|
|||
const Place = require('../models/place')
|
||||
const Tag = require('../models/tag')
|
||||
|
||||
const { Op } = require('sequelize')
|
||||
const { Op, literal } = require('sequelize')
|
||||
const moment = require('dayjs')
|
||||
const ics = require('ics')
|
||||
|
||||
|
@ -16,10 +16,17 @@ const exportController = {
|
|||
|
||||
const where = {}
|
||||
const yesterday = moment().subtract('1', 'day').unix()
|
||||
let where_tags = {}
|
||||
|
||||
|
||||
if (tags && places) {
|
||||
where[Op.or] = {
|
||||
placeId: places ? places.split(',') : [],
|
||||
'$tags.tag$': tags.split(',')
|
||||
}
|
||||
}
|
||||
|
||||
if (tags) {
|
||||
where_tags = { where: { tag: tags.split(',') } }
|
||||
where['$tags.tag$'] = tags.split(',')
|
||||
}
|
||||
|
||||
if (places) {
|
||||
|
@ -39,7 +46,15 @@ const exportController = {
|
|||
start_datetime: { [Op.gte]: yesterday },
|
||||
...where
|
||||
},
|
||||
include: [{ model: Tag, required: false, ...where_tags }, { model: Place, attributes: ['name', 'id', 'address'] }]
|
||||
include: [
|
||||
{
|
||||
model: Tag,
|
||||
order: [literal('(SELECT COUNT("tagTag") FROM event_tags WHERE tagTag = tag) DESC')],
|
||||
attributes: ['tag'],
|
||||
required: !!tags,
|
||||
through: { attributes: [] }
|
||||
},
|
||||
{ model: Place, attributes: ['name', 'id', 'address'] }]
|
||||
})
|
||||
|
||||
switch (type) {
|
||||
|
@ -54,8 +69,9 @@ const exportController = {
|
|||
},
|
||||
|
||||
feed (req, res, events) {
|
||||
const settings = res.locals.settings
|
||||
res.type('application/rss+xml; charset=UTF-8')
|
||||
res.render('feed/rss.pug', { events, settings: req.settings, moment })
|
||||
res.render('feed/rss.pug', { events, settings, moment })
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -64,6 +80,7 @@ const exportController = {
|
|||
* @param {*} alarms https://github.com/adamgibbons/ics#attributes (alarms)
|
||||
*/
|
||||
ics (req, res, events, alarms = []) {
|
||||
const settings = res.locals.settings
|
||||
const eventsMap = events.map(e => {
|
||||
const tmpStart = moment.unix(e.start_datetime)
|
||||
const tmpEnd = moment.unix(e.end_datetime)
|
||||
|
@ -74,10 +91,10 @@ const exportController = {
|
|||
// startOutputType: 'utc',
|
||||
end,
|
||||
// endOutputType: 'utc',
|
||||
title: `[${req.settings.title}] ${e.title}`,
|
||||
title: `[${settings.title}] ${e.title}`,
|
||||
description: e.description,
|
||||
location: `${e.place.name} - ${e.place.address}`,
|
||||
url: `${req.settings.baseurl}/event/${e.slug || e.id}`,
|
||||
url: `${settings.baseurl}/event/${e.slug || e.id}`,
|
||||
alarms
|
||||
}
|
||||
})
|
||||
|
|
|
@ -63,7 +63,7 @@ const oauthController = {
|
|||
|
||||
async getClients (req, res) {
|
||||
const tokens = await OAuthToken.findAll({
|
||||
include: [{ model: User, where: { id: req.user.id } }, { model: OAuthClient, as: 'client' }],
|
||||
include: [{ model: User, where: { id: res.locals.user.id } }, { model: OAuthClient, as: 'client' }],
|
||||
raw: true,
|
||||
nest: true
|
||||
})
|
||||
|
|
|
@ -35,9 +35,11 @@ const resourceController = {
|
|||
},
|
||||
event: {
|
||||
id: r.event.id,
|
||||
title: r.event.title
|
||||
title: r.event.title,
|
||||
slug: r.event.slug
|
||||
},
|
||||
ap_user: {
|
||||
url: get(r, 'ap_user.object.url', ''),
|
||||
ap_id: get(r, 'ap_user.ap_id', ''),
|
||||
preferredUsername: get(r, 'ap_user.object.preferredUsername', '')
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ const pkg = require('../../../package.json')
|
|||
const generateKeyPair = promisify(crypto.generateKeyPair)
|
||||
const log = require('../../log')
|
||||
const locales = require('../../../locales/index')
|
||||
const escape = require('lodash/escape')
|
||||
|
||||
|
||||
let defaultHostname
|
||||
|
@ -54,7 +55,7 @@ const settingsController = {
|
|||
secretSettings: {},
|
||||
|
||||
async load () {
|
||||
if (config.firstrun) {
|
||||
if (config.status !== 'READY') {
|
||||
settingsController.settings = defaultSettings
|
||||
return
|
||||
}
|
||||
|
@ -109,7 +110,7 @@ const settingsController = {
|
|||
|
||||
// load custom plugins
|
||||
const plugins_path = path.resolve(process.env.cwd || '', 'plugins')
|
||||
if (fs.existsSync(plugins_path)) {
|
||||
if (process.env.NODE_ENV === 'production' && fs.existsSync(plugins_path)) {
|
||||
const notifier = require('../../notifier')
|
||||
const pluginsFile = fs.readdirSync(plugins_path).filter(e => path.extname(e).toLowerCase() === '.js')
|
||||
pluginsFile.forEach( pluginFile => {
|
||||
|
@ -162,11 +163,12 @@ const settingsController = {
|
|||
await settingsController.set('smtp', smtp.smtp)
|
||||
const mail = require('../mail')
|
||||
try {
|
||||
await mail._send(settingsController.settings.admin_email, 'test', null, 'en')
|
||||
await mail._send(settingsController.settings.admin_email, 'test')
|
||||
|
||||
return res.sendStatus(200)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return res.status(400).send(String(e))
|
||||
return res.status(400).send(escape(String(e)))
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -190,11 +192,6 @@ const settingsController = {
|
|||
settingsController.set('logo', baseImgPath)
|
||||
res.sendStatus(200)
|
||||
})
|
||||
},
|
||||
|
||||
getAllRequest (req, res) {
|
||||
// get public settings and public configuration
|
||||
res.json({ ...settingsController.settings, version: pkg.version })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,21 +5,22 @@ const db = require('../models/index.js')
|
|||
const config = require('../../config')
|
||||
const settingsController = require('./settings')
|
||||
const path = require('path')
|
||||
const escape = require('lodash/escape')
|
||||
|
||||
const setupController = {
|
||||
|
||||
async setupDb (req, res, next) {
|
||||
log.debug('[SETUP] Check db')
|
||||
const dbConf = req.body.db
|
||||
async _setupDb (dbConf) {
|
||||
|
||||
if (!dbConf) {
|
||||
return res.sendStatus(400)
|
||||
throw Error('Empty DB configuration')
|
||||
}
|
||||
|
||||
if (dbConf.storage) {
|
||||
if (dbConf.dialect === 'sqlite' && dbConf.storage) {
|
||||
dbConf.storage = path.resolve(process.env.cwd || '', dbConf.storage)
|
||||
} else {
|
||||
dbConf.storage = ''
|
||||
}
|
||||
|
||||
try {
|
||||
// try to connect
|
||||
dbConf.logging = false
|
||||
await db.connect(dbConf)
|
||||
|
@ -28,29 +29,37 @@ const setupController = {
|
|||
const isEmpty = await db.isEmpty()
|
||||
if (!isEmpty) {
|
||||
log.warn(' ⚠ Non empty db! Please move your current db elsewhere than retry.')
|
||||
return res.status(400).send(' ⚠ Non empty db! Please move your current db elsewhere than retry.')
|
||||
throw Error(' ⚠ Non empty db! Please move your current db elsewhere than retry.')
|
||||
}
|
||||
|
||||
await db.runMigrations()
|
||||
|
||||
config.db = dbConf
|
||||
config.firstrun = false
|
||||
config.status = 'DBCONF'
|
||||
config.db.logging = false
|
||||
config.baseurl = req.protocol + '://' + req.headers.host
|
||||
config.hostname = new URL.URL(config.baseurl).hostname
|
||||
|
||||
const settingsController = require('./settings')
|
||||
await settingsController.load()
|
||||
return res.sendStatus(200)
|
||||
},
|
||||
|
||||
async setupDb (req, res) {
|
||||
log.debug('[SETUP] Check db')
|
||||
const dbConf = req.body.db
|
||||
|
||||
try {
|
||||
await setupController._setupDb(dbConf)
|
||||
} catch (e) {
|
||||
return res.status(400).send(String(e))
|
||||
}
|
||||
|
||||
return res.sendStatus(200)
|
||||
},
|
||||
|
||||
async restart (req, res) {
|
||||
|
||||
try {
|
||||
|
||||
config.baseurl = req.protocol + '://' + req.headers.host
|
||||
config.hostname = new URL.URL(config.baseurl).hostname
|
||||
|
||||
// write configuration
|
||||
config.write()
|
||||
|
||||
|
@ -72,12 +81,13 @@ const setupController = {
|
|||
log.info('Restart needed')
|
||||
|
||||
res.end()
|
||||
|
||||
// exit process so pm2 || docker could restart me || service
|
||||
process.kill(process.pid)
|
||||
setTimeout(() => process.kill(process.pid), 1000)
|
||||
|
||||
} catch (e) {
|
||||
log.error(String(e))
|
||||
return res.status(400).send(String(e))
|
||||
return res.status(400).send(escape(String(e)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ const userController = {
|
|||
if (!user) { return res.sendStatus(200) }
|
||||
|
||||
user.recover_code = crypto.randomBytes(16).toString('hex')
|
||||
mail.send(user.email, 'recover', { user, config }, req.settings.locale)
|
||||
mail.send(user.email, 'recover', { user, config }, res.locals.locale)
|
||||
|
||||
await user.save()
|
||||
res.sendStatus(200)
|
||||
|
@ -44,13 +44,13 @@ const userController = {
|
|||
},
|
||||
|
||||
async current (req, res) {
|
||||
if (!req.user) { return res.status(400).send('Not logged') }
|
||||
const user = await User.scope('withoutPassword').findByPk(req.user.id)
|
||||
if (!res.locals.user) { return res.status(400).send('Not logged') }
|
||||
const user = await User.scope('withoutPassword').findByPk(res.locals.user.id)
|
||||
res.json(user)
|
||||
},
|
||||
|
||||
async getAll (req, res) {
|
||||
const users = await User.scope(req.user.is_admin ? 'withRecover' : 'withoutPassword').findAll({
|
||||
const users = await User.scope(res.locals.user.is_admin ? 'withRecover' : 'withoutPassword').findAll({
|
||||
order: [['is_admin', 'DESC'], ['createdAt', 'DESC']]
|
||||
})
|
||||
res.json(users)
|
||||
|
@ -62,14 +62,14 @@ const userController = {
|
|||
|
||||
if (!user) { return res.status(404).json({ success: false, message: 'User not found!' }) }
|
||||
|
||||
if (req.body.id !== req.user.id && !req.user.is_admin) {
|
||||
if (req.body.id !== res.locals.user.id && !res.locals.user.is_admin) {
|
||||
return res.status(400).json({ succes: false, message: 'Not allowed' })
|
||||
}
|
||||
|
||||
if (!req.body.password) { delete req.body.password }
|
||||
|
||||
if (!user.is_active && req.body.is_active && user.recover_code) {
|
||||
mail.send(user.email, 'confirm', { user, config }, req.settings.locale)
|
||||
mail.send(user.email, 'confirm', { user, config }, res.locals.settings.locale)
|
||||
}
|
||||
|
||||
await user.update(req.body)
|
||||
|
@ -99,7 +99,7 @@ const userController = {
|
|||
log.info('Register user ', req.body.email)
|
||||
const user = await User.create(req.body)
|
||||
log.info(`Sending registration email to ${user.email}`)
|
||||
mail.send(user.email, 'register', { user, config }, req.settings.locale)
|
||||
mail.send(user.email, 'register', { user, config }, res.locals.locale)
|
||||
mail.send(settingsController.settings.admin_email, 'admin_register', { user, config })
|
||||
res.sendStatus(200)
|
||||
} catch (e) {
|
||||
|
@ -113,7 +113,7 @@ const userController = {
|
|||
req.body.is_active = true
|
||||
req.body.recover_code = crypto.randomBytes(16).toString('hex')
|
||||
const user = await User.scope('withRecover').create(req.body)
|
||||
mail.send(user.email, 'user_confirm', { user, config }, req.settings.locale)
|
||||
mail.send(user.email, 'user_confirm', { user, config }, res.locals.locale)
|
||||
res.json(user)
|
||||
} catch (e) {
|
||||
log.error('User creation error:', e)
|
||||
|
|
|
@ -10,7 +10,7 @@ api.use(express.urlencoded({ extended: false }))
|
|||
api.use(express.json())
|
||||
|
||||
|
||||
if (config.firstrun) {
|
||||
if (config.status !== 'READY') {
|
||||
|
||||
const setupController = require('./controller/setup')
|
||||
const settingsController = require('./controller/settings')
|
||||
|
@ -56,7 +56,7 @@ if (config.firstrun) {
|
|||
```
|
||||
*/
|
||||
api.get('/ping', (req, res) => res.sendStatus(200))
|
||||
api.get('/user', isAuth, (req, res) => res.json(req.user))
|
||||
api.get('/user', isAuth, (req, res) => res.json(res.locals.user))
|
||||
|
||||
|
||||
api.post('/user/recover', userController.forgotPassword)
|
||||
|
@ -80,10 +80,28 @@ if (config.firstrun) {
|
|||
// update a place (modify address..)
|
||||
api.put('/place', isAdmin, eventController.updatePlace)
|
||||
|
||||
/**
|
||||
* Get events
|
||||
* @category Event
|
||||
* @name /api/events
|
||||
* @type GET
|
||||
* @param {integer} [start] - start timestamp (default: now)
|
||||
* @param {integer} [end] - end timestamp (optional)
|
||||
* @param {array} [tags] - List of tags
|
||||
* @param {array} [places] - List of places
|
||||
* @param {integer} [max] - Max events
|
||||
* @param {boolean} [show_recurrent] - Show also recurrent events (default: as choosen in admin settings)
|
||||
* @example ***Example***
|
||||
* [https://demo.gancio.org/api/events](https://demo.gancio.org/api/events)
|
||||
* [usage example](https://framagit.org/les/gancio/-/blob/master/webcomponents/src/GancioEvents.svelte#L18-42)
|
||||
*/
|
||||
|
||||
api.get('/events', cors, eventController.select)
|
||||
|
||||
/**
|
||||
* Add a new event
|
||||
* @category Event
|
||||
* @name /event
|
||||
* @name /api/event
|
||||
* @type POST
|
||||
* @info `Content-Type` has to be `multipart/form-data` to support image upload
|
||||
* @param {string} title - event's title
|
||||
|
@ -95,13 +113,12 @@ if (config.firstrun) {
|
|||
* @param {array} tags - List of tags
|
||||
* @param {object} [recurrent] - Recurrent event details
|
||||
* @param {string} [recurrent.frequency] - could be `1w` or `2w`
|
||||
* @param {string} [recurrent.type] - not used
|
||||
* @param {array} [recurrent.days] - array of days
|
||||
* @param {image} [image] - Image
|
||||
*/
|
||||
|
||||
// allow anyone to add an event (anon event has to be confirmed, TODO: flood protection)
|
||||
api.post('/event', upload.single('image'), eventController.add)
|
||||
api.post('/event', eventController.isAnonEventAllowed, upload.single('image'), eventController.add)
|
||||
|
||||
api.put('/event', isAuth, upload.single('image'), eventController.update)
|
||||
api.get('/event/import', isAuth, helpers.importURL)
|
||||
|
@ -119,7 +136,6 @@ if (config.firstrun) {
|
|||
api.post('/event/notification', eventController.addNotification)
|
||||
api.delete('/event/notification/:code', eventController.delNotification)
|
||||
|
||||
api.get('/settings', settingsController.getAllRequest)
|
||||
api.post('/settings', isAdmin, settingsController.setRequest)
|
||||
api.post('/settings/logo', isAdmin, multer({ dest: config.upload_path }).single('logo'), settingsController.setLogo)
|
||||
api.post('/settings/smtp', isAdmin, settingsController.testSMTP)
|
||||
|
@ -134,9 +150,6 @@ if (config.firstrun) {
|
|||
// export events (rss/ics)
|
||||
api.get('/export/:type', cors, exportController.export)
|
||||
|
||||
// get events in this range
|
||||
api.get('/events', cors, eventController.select)
|
||||
|
||||
api.get('/instances', isAdmin, instanceController.getAll)
|
||||
api.get('/instances/:instance_domain', isAdmin, instanceController.get)
|
||||
api.post('/instances/toggle_block', isAdmin, instanceController.toggleBlock)
|
||||
|
|
|
@ -7,7 +7,8 @@ const { Task, TaskManager } = require('../taskManager')
|
|||
const locales = require('../../locales')
|
||||
|
||||
const mail = {
|
||||
send (addresses, template, locals, locale = settingsController.settings.instance_locale) {
|
||||
send (addresses, template, locals, locale) {
|
||||
locale = locale || settingsController.settings.instance_locale
|
||||
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
|
||||
|
@ -21,7 +22,8 @@ const mail = {
|
|||
TaskManager.add(task)
|
||||
},
|
||||
|
||||
_send (addresses, template, locals, locale = settingsController.settings.instance_locale) {
|
||||
_send (addresses, template, locals, locale) {
|
||||
locale = locale || settingsController.settings.instance_locale
|
||||
const settings = settingsController.settings
|
||||
log.info(`Send ${template} email to ${addresses} with locale ${locale}`)
|
||||
const email = new Email({
|
||||
|
|
|
@ -3,6 +3,7 @@ const Umzug = require('umzug')
|
|||
const path = require('path')
|
||||
const config = require('../../config')
|
||||
const log = require('../../log')
|
||||
const settingsController = require('../controller/settings')
|
||||
|
||||
const db = {
|
||||
sequelize: null,
|
||||
|
@ -13,6 +14,7 @@ const db = {
|
|||
},
|
||||
connect (dbConf = config.db) {
|
||||
log.debug(`Connecting to DB: ${JSON.stringify(dbConf)}`)
|
||||
dbConf.dialectOptions = { autoJsonMap: false }
|
||||
db.sequelize = new Sequelize(dbConf)
|
||||
return db.sequelize.authenticate()
|
||||
},
|
||||
|
@ -21,7 +23,7 @@ const db = {
|
|||
return !(users && users.length)
|
||||
},
|
||||
async runMigrations () {
|
||||
const logging = config.firstrun ? false : log.debug.bind(log)
|
||||
const logging = config.status !== 'READY' ? false : log.debug.bind(log)
|
||||
const umzug = new Umzug({
|
||||
storage: 'sequelize',
|
||||
storageOptions: { sequelize: db.sequelize },
|
||||
|
@ -40,11 +42,12 @@ const db = {
|
|||
return await umzug.up()
|
||||
},
|
||||
async initialize () {
|
||||
if (!config.firstrun) {
|
||||
if (config.status === 'READY') {
|
||||
try {
|
||||
await db.connect()
|
||||
log.debug('Running migrations')
|
||||
return db.runMigrations()
|
||||
await db.runMigrations()
|
||||
return settingsController.load()
|
||||
} catch (e) {
|
||||
log.warn(` ⚠️ Cannot connect to db, check your configuration => ${e}`)
|
||||
process.exit(1)
|
||||
|
|
|
@ -12,11 +12,11 @@ const oauthServer = new OAuthServer({
|
|||
debug: true,
|
||||
requireClientAuthentication: { password: false },
|
||||
authenticateHandler: {
|
||||
handle (req) {
|
||||
if (!req.user) {
|
||||
handle (req, res) {
|
||||
if (!res.locals.user) {
|
||||
throw new Error('Not authenticated!')
|
||||
}
|
||||
return req.user
|
||||
return res.locals.user
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -34,7 +34,7 @@ oauth.use((req, res) => res.sendStatus(404))
|
|||
|
||||
oauth.use((err, req, res, next) => {
|
||||
const error_msg = err.toString()
|
||||
log.error('[OAUTH USE] ' + error_msg)
|
||||
log.warn('[OAUTH USE] ' + error_msg)
|
||||
res.status(500).send(error_msg)
|
||||
})
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ const DiskStorage = {
|
|||
const thumbStream = fs.createWriteStream(thumbPath)
|
||||
|
||||
const resizer = sharp().resize(1200).jpeg({ quality: 98 })
|
||||
const thumbnailer = sharp().resize(400).jpeg({ quality: 90 })
|
||||
const thumbnailer = sharp().resize(500).jpeg({ quality: 98 })
|
||||
let onError = false
|
||||
const err = e => {
|
||||
if (onError) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#!/usr/bin/env node
|
||||
const pkg = require('../package.json')
|
||||
const path = require('path')
|
||||
const accountsCLI = require('./cli/accounts')
|
||||
|
||||
process.env.cwd = process.env.GANCIO_DATA || path.resolve('./')
|
||||
|
||||
|
@ -28,6 +29,7 @@ require('yargs')
|
|||
process.env.config_path = absolute_config_path
|
||||
return absolute_config_path
|
||||
})
|
||||
.command(['accounts'], 'Manage accounts', accountsCLI)
|
||||
.command(['start', 'run', '$0'], 'Start gancio', {}, start)
|
||||
.help('h')
|
||||
.alias('h', 'help')
|
||||
|
|
54
server/cli/accounts.js
Normal file
54
server/cli/accounts.js
Normal file
|
@ -0,0 +1,54 @@
|
|||
function _initializeDB () {
|
||||
const config = require('../config')
|
||||
config.load()
|
||||
config.log_level = 'error'
|
||||
const db = require('../api/models/index')
|
||||
return db.initialize()
|
||||
}
|
||||
|
||||
async function modify (args) {
|
||||
await _initializeDB()
|
||||
const helpers = require('../helpers')
|
||||
const User = require('../api/models/user')
|
||||
const user = await User.findOne({ where: { email: args.account } })
|
||||
console.log()
|
||||
if (!user) {
|
||||
console.error(`User ${args.account} not found`)
|
||||
return
|
||||
}
|
||||
|
||||
if (args['reset-password']) {
|
||||
const password = helpers.randomString()
|
||||
user.password = password
|
||||
await user.save()
|
||||
console.log(`New password for user ${user.email} is '${password}'`)
|
||||
}
|
||||
}
|
||||
|
||||
async function add (args) {
|
||||
}
|
||||
|
||||
async function list () {
|
||||
await _initializeDB()
|
||||
const User = require('../api/models/user')
|
||||
const users = await User.findAll()
|
||||
console.log()
|
||||
users.forEach(u => console.log(`${u.id}\tadmin: ${u.is_admin}\tenabled: ${u.is_active}\temail: ${u.email} - ${u.password}`))
|
||||
console.log()
|
||||
}
|
||||
|
||||
const accountsCLI = yargs => {
|
||||
return yargs
|
||||
.command('list', 'List all accounts', list)
|
||||
.command('modify', 'Modify', {
|
||||
account: {
|
||||
describe: 'Account to modify'
|
||||
},
|
||||
'reset-password': {
|
||||
describe: 'Resets the password of the given accoun '
|
||||
}
|
||||
}, modify)
|
||||
.command('add', 'Add an account', {}, add)
|
||||
}
|
||||
|
||||
module.exports = accountsCLI
|
|
@ -3,7 +3,7 @@ const path = require('path')
|
|||
const URL = require('url')
|
||||
|
||||
let config = {
|
||||
firstrun: true,
|
||||
status: 'SETUP',
|
||||
baseurl: '',
|
||||
hostname: '',
|
||||
server: {
|
||||
|
@ -15,7 +15,7 @@ let config = {
|
|||
db: {},
|
||||
upload_path: path.resolve(process.env.cwd || '', 'uploads'),
|
||||
write (config_path= process.env.config_path || './config.json') {
|
||||
delete config.firstrun
|
||||
delete config.status
|
||||
return fs.writeFileSync(config_path, JSON.stringify(config, null, 2))
|
||||
},
|
||||
|
||||
|
@ -26,17 +26,15 @@ let config = {
|
|||
if (fs.existsSync(config_path)) {
|
||||
const configContent = fs.readFileSync(config_path)
|
||||
config = Object.assign(config, JSON.parse(configContent))
|
||||
config.firstrun = false
|
||||
config.status = 'READY'
|
||||
if (!config.hostname) {
|
||||
config.hostname = new URL.URL(config.baseurl).hostname
|
||||
}
|
||||
} else {
|
||||
config.firstrun = true
|
||||
config.status = 'SETUP'
|
||||
console.info('> Configuration file does not exists, running setup..')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config.load()
|
||||
|
||||
module.exports = config
|
|
@ -2,15 +2,17 @@ const config = require('../config')
|
|||
const Helpers = require('./helpers')
|
||||
const crypto = require('crypto')
|
||||
const log = require('../log')
|
||||
const settingsController = require('../api/controller/settings')
|
||||
|
||||
module.exports = {
|
||||
// follow request from fediverse
|
||||
async follow (req, res) {
|
||||
const body = req.body
|
||||
const settings = res.locals.settings
|
||||
if (typeof body.object !== 'string') { return }
|
||||
const username = body.object.replace(`${config.baseurl}/federation/u/`, '')
|
||||
if (username !== req.settings.instance_name) {
|
||||
log.warn(`Following the wrong user: ${username} instead of ${req.settings.instance_name} (could be a wrong config.baseurl)`)
|
||||
if (username !== settings.instance_name) {
|
||||
log.warn(`Following the wrong user: ${username} instead of ${settings.instance_name} (could be a wrong config.baseurl)`)
|
||||
return res.status(404).send('User not found')
|
||||
}
|
||||
|
||||
|
@ -18,7 +20,7 @@ module.exports = {
|
|||
// if (!user.followers.includes(body.actor)) {
|
||||
// await user.addFollowers([req.fedi_user.id])
|
||||
// await user.update({ followers: [...user.followers, body.actor] })
|
||||
await req.fedi_user.update({ follower: true })
|
||||
await res.locals.fedi_user.update({ follower: true })
|
||||
log.info(`Followed by ${body.actor}`)
|
||||
const guid = crypto.randomBytes(16).toString('hex')
|
||||
const message = {
|
||||
|
@ -28,24 +30,25 @@ module.exports = {
|
|||
actor: `${config.baseurl}/federation/u/${username}`,
|
||||
object: body
|
||||
}
|
||||
Helpers.signAndSend(JSON.stringify(message), req.fedi_user.object.inbox)
|
||||
Helpers.signAndSend(JSON.stringify(message), res.locals.fedi_user.object.inbox)
|
||||
res.sendStatus(200)
|
||||
},
|
||||
|
||||
// unfollow request from fediverse
|
||||
async unfollow (req, res) {
|
||||
const settings = res.locals.settings
|
||||
const body = req.body
|
||||
const username = body.object.object.replace(`${config.baseurl}/federation/u/`, '')
|
||||
if (username !== req.settings.instance_name) {
|
||||
log.warn(`Unfollowing wrong user: ${username} instead of ${req.settings.instance_name}`)
|
||||
if (username !== settings.instance_name) {
|
||||
log.warn(`Unfollowing wrong user: ${username} instead of ${settings.instance_name}`)
|
||||
return res.status(404).send('User not found')
|
||||
}
|
||||
|
||||
if (body.actor !== body.object.actor || body.actor !== req.fedi_user.ap_id) {
|
||||
if (body.actor !== body.object.actor || body.actor !== res.locals.fedi_user.ap_id) {
|
||||
log.info('Unfollow an user created by a different actor !?!?')
|
||||
return res.status(400).send('Bad things')
|
||||
}
|
||||
await req.fedi_user.update({ follower: false })
|
||||
await res.locals.fedi_user.update({ follower: false })
|
||||
log.info(`Unfollowed by ${body.actor}`)
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
|
|
@ -192,7 +192,7 @@ const Helpers = {
|
|||
return res.status(401).send('User blocked')
|
||||
}
|
||||
|
||||
req.fedi_user = user
|
||||
res.locals.fedi_user = user
|
||||
|
||||
// TODO: check Digest // cannot do this with json bodyparser
|
||||
// const digest = crypto.createHash('sha256')
|
||||
|
|
|
@ -6,6 +6,7 @@ const Event = require('../api/models/event')
|
|||
const User = require('../api/models/user')
|
||||
const Tag = require('../api/models/tag')
|
||||
const Place = require('../api/models/place')
|
||||
const settingsController = require('../api/controller/settings')
|
||||
|
||||
const Helpers = require('./helpers')
|
||||
const Inbox = require('./inbox')
|
||||
|
@ -20,7 +21,6 @@ router.use(cors())
|
|||
|
||||
// is federation enabled? middleware
|
||||
router.use((req, res, next) => {
|
||||
const settingsController = require('../api/controller/settings')
|
||||
if (settingsController.settings.enable_federation) { return next() }
|
||||
log.debug('Federation disabled!')
|
||||
return res.status(401).send('Federation disabled')
|
||||
|
@ -36,7 +36,7 @@ router.get('/m/:event_id', async (req, res) => {
|
|||
|
||||
const event = await Event.findByPk(req.params.event_id, { include: [User, Tag, Place] })
|
||||
if (!event) { return res.status(404).send('Not found') }
|
||||
return res.json(event.toAP(settingsController.settings.instance_name, req.settings.instance_locale))
|
||||
return res.json(event.toAP(settingsController.settings.instance_name, settingsController.settings.instance_locale))
|
||||
})
|
||||
|
||||
// get any message coming from federation
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue