Merge branch 'dev'
2
.gitignore
vendored
|
@ -63,7 +63,7 @@ typings/
|
|||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
#*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
|
16
CHANGELOG
|
@ -1,5 +1,21 @@
|
|||
All notable changes to this project will be documented in this file.
|
||||
|
||||
### Unreleased
|
||||
|
||||
### 1.0 (beta)
|
||||
This release is a complete rewrite of frontend UI and many internals, main changes are:
|
||||
|
||||
- Switch UI framework from [element](https://element.eleme.io/) to [vuetify](https://vuetifyjs.com/)
|
||||
- Distribute package directly from site instead of using npm registry
|
||||
- Improve docker setup (run as user, fix some data export)
|
||||
- New logging system (based on [winston](https://github.com/winstonjs/winston))
|
||||
- Slugify event URL (keeping old one valid)
|
||||
- Import events from ics and external website using h-event (microformat) improving [`h-event`](https://microformats.org/wiki/h-event) export
|
||||
- Hide unconfirmed tags and places
|
||||
- Clean unused places and tags
|
||||
- Fix tons of issues
|
||||
|
||||
|
||||
### 0.24.0
|
||||
- New Euskara language from Basque Country, thanks @hacklabkelo
|
||||
- fix feed with filters
|
||||
|
|
3
app/router.scrollBehavior.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default function (to, from, savedPosition) {
|
||||
return { x: 0, y: 0 }
|
||||
}
|
|
@ -1,56 +1,56 @@
|
|||
.event {
|
||||
width: 320px;
|
||||
max-width: 450px;
|
||||
flex-grow: 1;
|
||||
margin: .2em;
|
||||
background-color: #202020;
|
||||
overflow: hidden;
|
||||
// .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;
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
// .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-footer {
|
||||
// max-height: 4.5em;
|
||||
// overflow: hidden;
|
||||
// padding: .25rem 0.5rem;
|
||||
// line-height: 1.8rem;
|
||||
// min-height: 2.2rem;
|
||||
// }
|
||||
|
||||
.card-body {
|
||||
overflow: hidden;
|
||||
}
|
||||
// .card-body {
|
||||
// overflow: hidden;
|
||||
// }
|
||||
|
||||
.description {
|
||||
color: #999;
|
||||
font-size: 0.8em;
|
||||
overflow: hidden;
|
||||
max-height: 100%;
|
||||
}
|
||||
// .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;
|
||||
}
|
||||
}
|
||||
// .el-image { width: 100% }
|
||||
// img {
|
||||
// width: 100%;
|
||||
// max-height: 250px;
|
||||
// object-fit: cover;
|
||||
// object-position: top;
|
||||
// }
|
||||
// }
|
||||
|
|
56
assets/helper.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
import take from 'lodash/take'
|
||||
import get from 'lodash/get'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
export function attributesFromEvents (_events, _tags) {
|
||||
const colors = ['blue', 'orange', 'yellow', 'teal', 'indigo', 'green', 'red', 'purple', 'pink', 'gray']
|
||||
const tags = take(_tags, 10).map(t => t.tag)
|
||||
let attributes = []
|
||||
attributes.push({ key: 'today', dates: new Date(), bar: { color: 'green', fillMode: 'outline' } })
|
||||
const now = dayjs().unix()
|
||||
|
||||
function getColor (event, where) {
|
||||
const color = { class: 'vc-rounded-full', color: 'blue', fillMode: where === 'base' ? 'light' : 'solid' }
|
||||
const tag = get(event, 'tags[0]')
|
||||
if (event.start_datetime < now) {
|
||||
if (event.multidate) {
|
||||
color.fillMode = where === 'base' ? 'light' : 'outline'
|
||||
if (where === 'base') {
|
||||
color.class += ' vc-past'
|
||||
}
|
||||
} else {
|
||||
color.class += ' vc-past'
|
||||
}
|
||||
}
|
||||
if (!tag) { return color }
|
||||
const idx = tags.indexOf(tag)
|
||||
if (idx < 0) { return color }
|
||||
color.color = colors[idx]
|
||||
// if (event.start_datetime < now) { color.class += ' vc-past' }
|
||||
return color
|
||||
}
|
||||
|
||||
attributes = attributes.concat(_events
|
||||
.filter(e => !e.multidate)
|
||||
.map(e => {
|
||||
return {
|
||||
key: e.id,
|
||||
dot: getColor(e),
|
||||
dates: new Date(e.start_datetime * 1000)
|
||||
}
|
||||
}))
|
||||
|
||||
attributes = attributes.concat(_events
|
||||
.filter(e => e.multidate)
|
||||
.map(e => ({
|
||||
key: e.id,
|
||||
highlight: {
|
||||
start: getColor(e),
|
||||
base: getColor(e, 'base'),
|
||||
end: getColor(e)
|
||||
},
|
||||
dates: { start: new Date(e.start_datetime * 1000), end: new Date(e.end_datetime * 1000) }
|
||||
})))
|
||||
|
||||
return attributes
|
||||
}
|
|
@ -1,215 +1,119 @@
|
|||
:focus {outline:none;}
|
||||
::-moz-focus-inner {border:0;}
|
||||
|
||||
blockquote {
|
||||
border-left: 3px solid grey;
|
||||
font-style: italic;
|
||||
color: #666;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: sans-serif;
|
||||
display: inline-block;
|
||||
padding: 0 .4rem;
|
||||
border-radius: 5px;
|
||||
font-size: .8rem;
|
||||
font-weight: 700;
|
||||
background: rgba(0,0,0,.1);
|
||||
color: #888;
|
||||
}
|
||||
|
||||
html, body {
|
||||
scrollbar-width: thin;
|
||||
overflow: auto !important;
|
||||
scrollbar-color: #FF4511 #111;
|
||||
font-family: sans-serif;
|
||||
scroll-behavior: smooth;
|
||||
text-rendering: optimizeSpeed;
|
||||
background-color: #111;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #555 #111;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
#__nuxt, #__layout {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
li {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
#main {
|
||||
min-height: 200px;
|
||||
overflow: hidden;
|
||||
scrollbar-width: thin;
|
||||
transition: background-color .5s, opacity .5s; //, color 1s;
|
||||
background-color: white;
|
||||
&.dark {
|
||||
background-color: #111;
|
||||
}
|
||||
.v-dialog .theme--dark.v-card {
|
||||
background-color: #434343;
|
||||
}
|
||||
|
||||
#content {
|
||||
.v-application .p-description.text-body-1 {
|
||||
letter-spacing: normal !important;
|
||||
}
|
||||
|
||||
#home {
|
||||
max-width: 1400px;
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
padding: 0px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#events {
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#header, #footer {
|
||||
color: white;
|
||||
background-color: #222;
|
||||
font-size: 18px;
|
||||
padding: 0 15px;
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
}
|
||||
|
||||
.v-dialog {
|
||||
width: 600px;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
#footer {
|
||||
a {
|
||||
color: orangered;
|
||||
transition: color .4s;
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
color: orange;
|
||||
.theme--dark.v-list {
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
.v-autocomplete__content.v-menu__content {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #FF4511 #111;
|
||||
}
|
||||
|
||||
// EVENT
|
||||
.event {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
width: 330px;
|
||||
max-width: 500px !important;
|
||||
flex-grow: 1;
|
||||
margin-top: .4em;
|
||||
margin-right: .4em;
|
||||
transition: all .5s;
|
||||
overflow: hidden;
|
||||
|
||||
.title {
|
||||
transition: all .5s;
|
||||
display: block;
|
||||
max-height: 3em;
|
||||
color: white;
|
||||
overflow: hidden;
|
||||
margin: 0.5rem 1rem 0.5rem 1rem;
|
||||
font-size: 1.1em !important;
|
||||
line-height: 1em !important;
|
||||
}
|
||||
|
||||
.body {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.img {
|
||||
width: 100%;
|
||||
max-height: 250px;
|
||||
min-height: 160px;
|
||||
background-color: #222;
|
||||
object-fit: cover;
|
||||
object-position: top;
|
||||
}
|
||||
|
||||
.place {
|
||||
max-width: 100%;
|
||||
span {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
#links a {
|
||||
margin-left: 15px;
|
||||
}
|
||||
min-height: 6em;
|
||||
padding-top: 2em;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
#header .el-menu--horizontal {
|
||||
background-color: #222;
|
||||
color: white;
|
||||
.el-menu-item, .el-submenu__title {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.el-submenu .el-submenu__icon-arrow {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.el-submenu .el-submenu__title {
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.el-menu-item:not(.is-disabled):focus,
|
||||
.el-menu-item.is-active,
|
||||
.el-menu-item:not(.is-disabled):hover,
|
||||
.el-submenu:focus > .el-submenu__title,
|
||||
.el-submenu:hover > .el-submenu__title,
|
||||
.el-submenu.is-active > .el-submenu__title,
|
||||
.el-submenu.is-opened {
|
||||
color: white;
|
||||
background-color: #111;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.page-enter-active {
|
||||
transition: opacity .05s;
|
||||
}
|
||||
|
||||
.page-enter, .page-leave-active {
|
||||
transition: opacity .3s; //, transform .3s;
|
||||
opacity: 0;
|
||||
// transform: translateX(30px);
|
||||
}
|
||||
|
||||
.el-card {
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.el-menu-item {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
a {
|
||||
color: #303133;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.el-message-box {
|
||||
max-width: 90%;
|
||||
.v-list {
|
||||
background-color: #333 !important;
|
||||
}
|
||||
|
||||
//TODO: refactoring
|
||||
.el-button.is-plain,
|
||||
.el-button.is-plain:focus,
|
||||
.el-button.is-plain {
|
||||
border-color: #ff450075;
|
||||
background-color: transparent;
|
||||
color: orangered;
|
||||
|
||||
&:hover {
|
||||
border-color: #ff450075;
|
||||
background-color: transparent;
|
||||
color: orange;
|
||||
}
|
||||
.vc-past {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
// TODO: this should be a link
|
||||
|
||||
.event .p-location {
|
||||
transition: color .2s;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
color: orangered !important;
|
||||
}
|
||||
#event {
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
.el-button--text {
|
||||
color: orangered !important;
|
||||
&:hover {
|
||||
color: orange !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-button--success.is-plain {
|
||||
color: #2c8600;
|
||||
border-color: #9de27b;
|
||||
background-color: #f9fff6;
|
||||
}
|
||||
|
||||
.el-button--mini, .el-button--mini.is-round {
|
||||
padding: 6px 12px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.bg-dark {
|
||||
background-color: #292929 !important;
|
||||
}
|
||||
|
||||
.el-dialog {
|
||||
min-height: 300px;
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
.el-dialog__body {
|
||||
word-break: normal !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-switch__label span {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
// .el-input {
|
||||
// max-width: 350px;
|
||||
// }
|
||||
|
||||
// @media only screen and (max-width: 768px) {
|
||||
// .el-card {
|
||||
// padding: 0px !important;
|
||||
// border-radius: 0px;
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
@import './event.less';
|
||||
@import './editor.less';
|
||||
.tags .v-chip .v-chip__content {
|
||||
max-width: 120px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
}
|
|
@ -1,10 +1,6 @@
|
|||
<template lang="pug">
|
||||
.announcement.announcement.text-white(body-style='padding: 0px;')
|
||||
nuxt-link(:to='`/announcement/${announcement.id}`')
|
||||
.title <i class='el-icon-info'/> {{announcement.title}}
|
||||
|
||||
//- .card-body
|
||||
//- .description(v-html='description')
|
||||
nuxt-link(:to='`/announcement/${announcement.id}`')
|
||||
v-alert.mb-1(border='left' type='info' color="primary" show-icon) {{announcement.title}}
|
||||
|
||||
</template>
|
||||
<script>
|
||||
|
@ -14,32 +10,6 @@ export default {
|
|||
props: {
|
||||
announcement: { type: Object, default: () => ({}) }
|
||||
},
|
||||
computed: {
|
||||
...mapState(['announcements']),
|
||||
description () {
|
||||
return this.announcement.announcement.replace(/(<br>)+/g, '<br>')
|
||||
}
|
||||
}
|
||||
computed: mapState(['announcements'])
|
||||
}
|
||||
</script>
|
||||
<style lang='less'>
|
||||
.announcement {
|
||||
padding: 2%;
|
||||
background-color: #511;
|
||||
margin: 1rem 0;
|
||||
border-radius: 5px;
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
.title {
|
||||
font-size: 1.2em;
|
||||
color: white;
|
||||
transition: color .2s;
|
||||
&:hover {
|
||||
color: #fbd6b5;
|
||||
}
|
||||
}
|
||||
border: 2px solid #ff4500ba;
|
||||
// box-shadow: inset 0px 0px 10px 0px orangered;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template lang="pug">
|
||||
#calendar
|
||||
v-calendar(
|
||||
vc-calendar(
|
||||
title-position='left'
|
||||
is-dark
|
||||
:is-dark="settings['theme.is_dark']"
|
||||
:columns="$screens({ sm: 2 }, 1)"
|
||||
@update:from-page='updatePage'
|
||||
:columns="$screens({ default: 1, lg: 2 })"
|
||||
:locale='$i18n.locale'
|
||||
:attributes='attributes'
|
||||
transition='fade'
|
||||
|
@ -14,77 +14,43 @@
|
|||
|
||||
</template>
|
||||
<script>
|
||||
import { mapState, mapActions, mapGetters } from 'vuex'
|
||||
import moment from 'moment-timezone'
|
||||
import { take, get } from 'lodash'
|
||||
import { mapState, mapActions } from 'vuex'
|
||||
import dayjs from 'dayjs'
|
||||
import { attributesFromEvents } from '../assets/helper'
|
||||
|
||||
export default {
|
||||
name: 'Calendar',
|
||||
props: {
|
||||
events: { type: Array, default: () => [] }
|
||||
},
|
||||
data () {
|
||||
const month = moment().month() + 1
|
||||
const year = moment().year()
|
||||
const month = dayjs().month() + 1
|
||||
const year = dayjs().year()
|
||||
return {
|
||||
page: { month, year }
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['filteredEventsWithPast']),
|
||||
...mapState(['tags', 'filters', 'in_past']),
|
||||
|
||||
// TODO: could be better
|
||||
...mapState(['tags', 'filters', 'in_past', 'settings']),
|
||||
attributes () {
|
||||
const colors = ['green', 'orange', 'yellow', 'teal', 'indigo', 'blue', 'red', 'purple', 'pink', 'gray']
|
||||
const tags = take(this.tags, 10).map(t => t.tag)
|
||||
let attributes = []
|
||||
attributes.push({ key: 'today', dates: new Date(), highlight: { color: 'green' } })
|
||||
|
||||
const that = this
|
||||
function getColor (event) {
|
||||
const color = { class: event.past && !that.filters.show_past_events && !that.in_past ? 'past-event vc-rounded-full' : 'vc-rounded-full', color: 'blue' }
|
||||
const tag = get(event, 'tags[0]')
|
||||
if (!tag) { return color }
|
||||
const idx = tags.indexOf(tag)
|
||||
if (idx < 0) { return color }
|
||||
color.color = colors[idx]
|
||||
return color
|
||||
}
|
||||
|
||||
attributes = attributes.concat(this.filteredEventsWithPast
|
||||
.filter(e => !e.multidate)
|
||||
.map(e => {
|
||||
return {
|
||||
key: e.id,
|
||||
dot: getColor(e),
|
||||
dates: new Date(e.start_datetime * 1000)
|
||||
}
|
||||
}))
|
||||
|
||||
attributes = attributes.concat(this.filteredEventsWithPast
|
||||
.filter(e => e.multidate)
|
||||
.map(e => ({
|
||||
key: e.id,
|
||||
highlight: getColor(e),
|
||||
dates: { start: new Date(e.start_datetime * 1000), end: new Date(e.end_datetime * 1000) }
|
||||
})))
|
||||
|
||||
return attributes
|
||||
return attributesFromEvents(this.events, this.tags)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['updateEvents', 'showPastEvents']),
|
||||
updatePage (page) {
|
||||
this.updateEvents(page)
|
||||
return new Promise((resolve, reject) => {
|
||||
this.$emit('monthchange', page)
|
||||
})
|
||||
},
|
||||
click (day) {
|
||||
const element = document.getElementById(day.day)
|
||||
if (element) { element.scrollIntoView() } // Even IE6 supports this
|
||||
this.$emit('dayclick', day)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
.vc-opacity-0 {
|
||||
opacity: 0.3 !important;
|
||||
}
|
||||
|
|
84
components/Confirm.vue
Normal file
|
@ -0,0 +1,84 @@
|
|||
|
||||
<template lang="pug">
|
||||
v-dialog(v-model='show'
|
||||
:color='options.color'
|
||||
:title='title'
|
||||
:max-width='options.width'
|
||||
:style="{ zIndex: options.zIndex, position: 'absolute' }"
|
||||
@keydown.esc='cancel')
|
||||
v-card
|
||||
v-card-title {{ title }}
|
||||
v-card-text(v-show='!!message') {{ message }}
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(color='error' @click='cancel') {{$t('common.cancel')}}
|
||||
v-btn(color='primary' @click='agree') {{$t('common.ok')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Vuetify Confirm Dialog component
|
||||
*
|
||||
* Call it:
|
||||
* this.$refs.confirm.open('Delete', 'Are you sure?', { color: 'red' }).then((confirm) => {})
|
||||
*
|
||||
* Or use await:
|
||||
* if (await this.$refs.confirm.open('Delete', 'Are you sure?', { color: 'red' })) {
|
||||
* // yes
|
||||
* }
|
||||
* else {
|
||||
* // cancel
|
||||
* }
|
||||
*
|
||||
*/
|
||||
export default {
|
||||
data: () => ({
|
||||
dialog: false,
|
||||
resolve: null,
|
||||
reject: null,
|
||||
message: null,
|
||||
title: null,
|
||||
options: {
|
||||
color: 'danger',
|
||||
width: 450,
|
||||
zIndex: 500
|
||||
}
|
||||
}),
|
||||
computed: {
|
||||
show: {
|
||||
get () {
|
||||
return this.dialog
|
||||
},
|
||||
set (value) {
|
||||
this.dialog = value
|
||||
if (value === false) {
|
||||
this.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.$root.$confirm = this.open
|
||||
},
|
||||
methods: {
|
||||
open (message, options = {}) {
|
||||
this.dialog = true
|
||||
this.title = options.title || this.$t('common.confirm')
|
||||
this.message = this.$t(message, options)
|
||||
this.options = Object.assign(this.options, options)
|
||||
return new Promise((resolve, reject) => {
|
||||
this.resolve = resolve
|
||||
this.reject = reject
|
||||
})
|
||||
},
|
||||
agree () {
|
||||
this.resolve(true)
|
||||
this.dialog = false
|
||||
},
|
||||
cancel () {
|
||||
this.resolve(false)
|
||||
this.dialog = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,43 +1,70 @@
|
|||
<template lang='pug'>
|
||||
.editor(:class='{ "with-border": border }')
|
||||
editor-menu-bubble(:editor='editor' :keep-in-bounds='true' v-slot='{ commands, isActive, getMarkAttrs, menu }')
|
||||
el-button-group.menububble(:class="{ 'is-active': menu.isActive }" :style="`left: ${menu.left}px; bottom: ${menu.bottom}px;`")
|
||||
el-popover(trigger='hover' placement='bottom-start')
|
||||
el-button.float-left(slot='reference' size='mini') <v-icon name='question'/>
|
||||
template
|
||||
span This editor supports inline <code>markdown</code>
|
||||
div <v-icon name='heading'/> → Title ⇒ Start a line with <code>#</code>
|
||||
div <v-icon name='bold'/> → Bold ⇒ <code>ctrl+b</code>
|
||||
div <v-icon name='italic'/> → Italic ⇒ <code>ctrl+i</code>
|
||||
div <v-icon name='underline'/> → Underline ⇒ <code>ctrl+u</code>
|
||||
div <v-icon name='list-ul'/> → List ⇒ Start a line with <code>-</code>
|
||||
div <v-icon name='list-ol'/> → Ordered List ⇒ Start a line with <code>1.</code>
|
||||
div <v-icon name='quote-right'/> → Quote ⇒ Start a line with <code>></code>
|
||||
div <v-icon name='code'/> → Code ⇒ Use backtick <code>`</code>
|
||||
div <v-icon name='link'/> → Link ⇒ Select a word and fill the input
|
||||
//- el-button(size='mini' :class='{ "is-active": isActive.heading({level:4})}' @click='commands.heading({level: 4})') <v-icon name='heading'/>
|
||||
//- el-button(size='mini' :class='{ "is-active": isActive.bold() }' @click='commands.bold')
|
||||
<v-icon name='bold' />
|
||||
//- el-button(size='mini' :class='{ "is-active": isActive.italic() }' @click='commands.italic') <v-icon name='italic'/>
|
||||
//- el-button(size='mini' :class='{ "is-active": isActive.underline() }' @click='commands.underline') <v-icon name='underline'/>
|
||||
el-button(size='mini' :class='{ "is-active": isActive.link() }' @click='commands.link({href: ""}); $refs.link.focus(); linkActive=true') <v-icon name='link'/>
|
||||
input(:value='isActive.link() && getMarkAttrs("link") && getMarkAttrs("link").href || ""' ref='link' :class='{ "is-active": isActive.link() || linkActive }'
|
||||
placeholder='https://' @keypress.enter='commands.link({ href: $event.target.value})')
|
||||
//- el-button(size='mini' :class='{ "is-active": isActive.strike() }' @click='commands.strike') <v-icon name='strikethrough'/>
|
||||
//- br
|
||||
//- el-button-group
|
||||
//- el-button(size='mini' :class='{ "is-active": isActive.code() }' @click='commands.code') <v-icon size=16 name='code'/>
|
||||
//- el-button(size='mini' :class='{ "is-active": isActive.bullet_list() }' @click='commands.bullet_list') <v-icon name='list-ul'/>
|
||||
//- //- el-button(size='mini' :class='{ "is-active": isActive.ordered_list() }' @click='commands.ordered_list') <v-icon name='list-ol'/>
|
||||
//- el-button(size='mini' :class='{ "is-active": isActive.blockquote() }' @click='commands.blockquote') <v-icon name='quote-right'/>
|
||||
.editor.grey.darken-4(:class='focused')
|
||||
.label {{label}}
|
||||
editor-menu-bar.menubar.is-hidden(:editor='editor'
|
||||
:keep-in-bounds='true' v-slot='{ commands, isActive, getMarkAttrs, focused }')
|
||||
v-btn-toggle(dense)
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.bold() }"
|
||||
@click="commands.bold")
|
||||
v-icon mdi-format-bold
|
||||
|
||||
//- el-button.float-right(v-if='!noSave' size='mini' type='success' plain icon='el-icon-check'
|
||||
//- @click='$emit("save", editor.getHTML())') {{$t('common.save')}}
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.underline() }"
|
||||
@click="commands.underline")
|
||||
v-icon mdi-format-underline
|
||||
|
||||
editor-content.content(:editor='editor' spellcheck='false')
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.strike() }"
|
||||
@click="commands.strike")
|
||||
v-icon mdi-format-strikethrough-variant
|
||||
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.italic() }"
|
||||
@click="commands.italic")
|
||||
v-icon mdi-format-italic
|
||||
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.heading({level: 1}) }"
|
||||
@click="commands.heading({level: 1})")
|
||||
v-icon mdi-format-header-1
|
||||
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.heading({level: 2}) }"
|
||||
@click="commands.heading({level: 2})")
|
||||
v-icon mdi-format-header-2
|
||||
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.heading({level: 3}) }"
|
||||
@click="commands.heading({level: 3})")
|
||||
v-icon mdi-format-header-3
|
||||
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.code() }"
|
||||
@click="commands.code")
|
||||
v-icon mdi-code-tags
|
||||
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.blockquote() }"
|
||||
@click="commands.blockquote")
|
||||
v-icon mdi-format-quote-open
|
||||
|
||||
v-btn(icon text tabindex='-1'
|
||||
:class="{ primary: isActive.bullet_list() }"
|
||||
@click="commands.bullet_list")
|
||||
v-icon mdi-format-list-bulleted
|
||||
|
||||
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-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 }")
|
||||
</template>
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import debounce from 'lodash/debounce'
|
||||
import { Editor, EditorContent, EditorMenuBar, EditorMenuBubble } from 'tiptap'
|
||||
import {
|
||||
Blockquote,
|
||||
|
@ -53,22 +80,29 @@ import {
|
|||
Link,
|
||||
History,
|
||||
Strike,
|
||||
Underline
|
||||
Underline,
|
||||
Placeholder
|
||||
} from 'tiptap-extensions'
|
||||
|
||||
export default {
|
||||
name: 'Editor',
|
||||
components: { EditorContent, EditorMenuBar, EditorMenuBubble },
|
||||
props: {
|
||||
label: { type: String, default: 'Editor' },
|
||||
value: { type: String, default: '' },
|
||||
border: { type: Boolean, default: false },
|
||||
noSave: { type: Boolean, default: false }
|
||||
noSave: { type: Boolean, default: false },
|
||||
maxHeight: { type: String, Number, default: '' },
|
||||
placeholder: { type: String, default: '' }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
options: [],
|
||||
linkActive: false,
|
||||
editor: null,
|
||||
update: false
|
||||
blurring: false,
|
||||
update: false,
|
||||
focused: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
@ -82,10 +116,12 @@ export default {
|
|||
},
|
||||
mounted () {
|
||||
this.editor = new Editor({
|
||||
onUpdate: _.debounce(({ getHTML }) => {
|
||||
onFocus: () => this.focus(),
|
||||
onBlur: () => this.blur(),
|
||||
onUpdate: debounce(({ getHTML }) => {
|
||||
this.update = true
|
||||
this.$emit('input', getHTML())
|
||||
}, 300),
|
||||
}, 1000),
|
||||
content: this.value,
|
||||
extensions: [
|
||||
new Blockquote(),
|
||||
|
@ -97,79 +133,130 @@ export default {
|
|||
new ListItem(),
|
||||
new Code(),
|
||||
new History(),
|
||||
new Link({ openOnClick: false }),
|
||||
new Link({ openOnClick: false, target: '_blank' }),
|
||||
new Bold(),
|
||||
new Italic(),
|
||||
new Strike(),
|
||||
new Underline()
|
||||
new Underline(),
|
||||
new Placeholder({
|
||||
emptyEditorClass: 'is-editor-empty',
|
||||
emptyNodeClass: 'is-empty',
|
||||
emptyNodeText: this.placeholder,
|
||||
showOnlyWhenEditable: true,
|
||||
showOnlyCurrent: true
|
||||
})
|
||||
]
|
||||
})
|
||||
},
|
||||
beforeDestroy () {
|
||||
if (this.editor) { this.editor.destroy() }
|
||||
},
|
||||
methods: {
|
||||
blur () {
|
||||
this.blurring = true
|
||||
window.setTimeout(() => {
|
||||
if (this.blurring) {
|
||||
this.focused = ''
|
||||
this.blurring = false
|
||||
}
|
||||
}, 200)
|
||||
},
|
||||
focus () {
|
||||
this.focused = 'editor--focused'
|
||||
this.$nextTick(() => {
|
||||
this.blurring = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang='less'>
|
||||
|
||||
.editor {
|
||||
position: relative;
|
||||
margin-top: 4px;
|
||||
padding-top: 12px;
|
||||
padding-bottom: 22px;
|
||||
overflow-y: auto;
|
||||
padding-top: 1.7em;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #FF4500 transparent;
|
||||
scroll-behavior: smooth;
|
||||
font-family: sans-serif;
|
||||
font-size: 1.1em;
|
||||
|
||||
&.with-border {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
.editor p.is-editor-empty:first-child::before {
|
||||
content: attr(data-empty-text);
|
||||
float: left;
|
||||
color: #aaa;
|
||||
// opacity: .4;
|
||||
pointer-events: none;
|
||||
height: 0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0px 5px 0px 5px;
|
||||
flex: 1;
|
||||
scrollbar-width: thin;
|
||||
overflow-y: auto;
|
||||
.label {
|
||||
left: 0px;
|
||||
position: relative;
|
||||
transform-origin: top left;
|
||||
transition: transform .3s, scale .3s, color .3s;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
.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;
|
||||
&.editor--focused {
|
||||
.label {
|
||||
color: #FF4500;
|
||||
transform: translateY(0px) scale(0.75);
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
opacity: 1;
|
||||
.menubar {
|
||||
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;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.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%;
|
||||
.ProseMirror::after {
|
||||
width : 100% !important;
|
||||
transform: scaleX(1) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.menubar {
|
||||
transition: opacity .5s;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
// position: absolute;
|
||||
}
|
||||
|
||||
.focused .ProseMirror::after {
|
||||
width: 100%;
|
||||
}
|
||||
.ProseMirror {
|
||||
padding: 15px;
|
||||
outline: 0;
|
||||
&::before {
|
||||
bottom: 0px;
|
||||
content: "";
|
||||
left: 0;
|
||||
position: absolute;
|
||||
transition: 0.3s cubic-bezier(0.25, 0.8, 0.5, 1);
|
||||
width: 100%;
|
||||
border-width: thin 0 0 0;
|
||||
border-style: solid;
|
||||
height: 0px;
|
||||
border-color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
&::after {
|
||||
bottom: 0px;
|
||||
content: "";
|
||||
left: 0;
|
||||
position: absolute;
|
||||
height: 0px;
|
||||
transition: 0.3s cubic-bezier(0.25, 0.8, 0.5, 1);
|
||||
width: 100%;
|
||||
border-width: 2px 0 0 0;
|
||||
border-style: solid;
|
||||
border-color: #FF4500;
|
||||
transform: scaleX(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,67 +1,72 @@
|
|||
<template lang="pug">
|
||||
.card.event.h-event.mt-1.text-white
|
||||
nuxt-link(:to='`/event/${event.id}`')
|
||||
el-image(v-if='showImage && event.image_path'
|
||||
lazy :src='`/media/thumb/${event.image_path}`')
|
||||
.float-right
|
||||
i.text-danger.el-icon-refresh(v-if='event.parentId')
|
||||
.badge.text-info(v-if='settings.enable_resources && event.resources && event.resources.length') {{event.resources.length}}
|
||||
//- title
|
||||
.p-name.p-summary.title {{event.title}}
|
||||
v-card.h-event.event.d-flex
|
||||
nuxt-link(:to='`/event/${event.slug || event.id}`')
|
||||
v-img.u-featured.img(:src="`/media/thumb/${event.image_path || 'logo.svg' }`")
|
||||
v-icon.float-right.mr-1(v-if='event.parentId' color='success') mdi-repeat
|
||||
.title.p-name {{event.title}}
|
||||
|
||||
.card-body
|
||||
//- div.d-flex.justify-content-between
|
||||
//- when
|
||||
time.d-block.dt-start.mt-0(:datetime='event.start_datetime|unixFormat("YYYY-MM-DD HH:mm")') <i class='el-icon-date'/> {{event|when}}
|
||||
//- place
|
||||
.p-location.mt-1.text-warning(plain size='mini' round type='text' @click='addPlace') <i class='el-icon-location-outline'/> {{event.place.name}}
|
||||
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}}
|
||||
|
||||
//- description
|
||||
//- .p-description.description.mt-3(v-html='description')
|
||||
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}}
|
||||
|
||||
.card-footer(v-if='event.tags.length')
|
||||
el-button.ml-1(type='text' plain round size='mini' v-for='tag in event.tags' :key='tag' @click='addTag(tag)') {{tag}}
|
||||
v-menu(offset-y)
|
||||
template(v-slot:activator="{on}")
|
||||
v-btn.align-self-end(icon v-on='on' color='primary')
|
||||
v-icon mdi-dots-vertical
|
||||
v-list(dense)
|
||||
v-list-item-group
|
||||
v-list-item(v-clipboard:success="() => $root.$message('common.copied', { color: 'success' })"
|
||||
v-clipboard:copy='`${settings.baseurl}/event/${event.id}`')
|
||||
v-list-item-icon
|
||||
v-icon mdi-content-copy
|
||||
v-list-item-content
|
||||
v-list-item-title {{$t('common.copy_link')}}
|
||||
v-list-item(:href='`/api/event/${event.id}.ics`')
|
||||
v-list-item-icon
|
||||
v-icon mdi-calendar-export
|
||||
v-list-item-content
|
||||
v-list-item-title {{$t('common.add_to_calendar')}}
|
||||
v-list-item(v-if='is_mine' :to='`/add/${event.id}`')
|
||||
v-list-item-icon
|
||||
v-icon mdi-pencil
|
||||
v-list-item-content
|
||||
v-list-item-title {{$t('common.edit')}}
|
||||
v-list-item(v-if='is_mine' @click='remove(false)')
|
||||
v-list-item-icon
|
||||
v-icon(color='error') mdi-delete-forever
|
||||
v-list-item-content
|
||||
v-list-item-title {{$t('common.remove')}}
|
||||
</template>
|
||||
<script>
|
||||
import { mapState, mapActions } from 'vuex'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
event: { type: Object, default: () => ({}) },
|
||||
showTags: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showImage: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
event: { type: Object, default: () => ({}) }
|
||||
},
|
||||
computed: {
|
||||
...mapState(['settings', 'filters']),
|
||||
description () {
|
||||
return this.event.description.replace(/(<br>)+/g, '<br>')
|
||||
},
|
||||
show_footer () {
|
||||
return (this.event.tags.length || this.event.resources.length)
|
||||
...mapState(['settings']),
|
||||
is_mine () {
|
||||
if (!this.$auth.user) {
|
||||
return false
|
||||
}
|
||||
return (
|
||||
this.event.userId === this.$auth.user.id || this.$auth.user.is_admin
|
||||
)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['setSearchTags', 'setSearchPlaces']),
|
||||
addTag (tag) {
|
||||
if (this.filters.tags.includes(tag)) {
|
||||
this.setSearchTags(this.filters.tags.filter(t => t !== tag))
|
||||
} else {
|
||||
this.setSearchTags(this.filters.tags.concat([tag]))
|
||||
}
|
||||
},
|
||||
addPlace () {
|
||||
const place = this.event.place.id
|
||||
if (this.filters.places.includes(place)) {
|
||||
this.setSearchPlaces(this.filters.places.filter(p => p !== place))
|
||||
} else {
|
||||
this.setSearchPlaces(this.filters.places.concat(place))
|
||||
}
|
||||
async remove () {
|
||||
const ret = await this.$root.$confirm('event.remove_confirmation')
|
||||
if (!ret) { return }
|
||||
await this.$axios.delete(`/event/${this.event.id}`)
|
||||
this.$emit('destroy', this.event.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,37 +1,51 @@
|
|||
<template lang='pug'>
|
||||
div
|
||||
p(v-html="$t('event.follow_me_description', { title: settings.title, account: `@${settings.instance_name}@${domain}`})")
|
||||
el-input(v-model='instance_hostname' ref='instance')
|
||||
a(slot='append' :href='link' target='_blank')
|
||||
el-button(:disabled='(!couldGo || !proceed)' plain type="primary" icon='el-icon-document') {{$t("common.follow")}}
|
||||
p.mt-2 <img class='instance_thumb' :src="instance.thumbnail"/> {{instance.title}}
|
||||
v-card
|
||||
v-card-title(v-text="$t('common.follow_me_title')")
|
||||
v-card-text
|
||||
p(v-html="$t('event.follow_me_description', { title: settings.title, account: `@${settings.instance_name}@${domain}`})")
|
||||
v-text-field(
|
||||
:rules="[$validators.required('common.url')]"
|
||||
:loading='loading'
|
||||
:label="$t('common.url')"
|
||||
v-model='instance_hostname')
|
||||
v-btn(v-if='!isDialog' slot='prepend' text :disabled='(!couldGo || !proceed)' :href='link' target='_blank'
|
||||
:loading='loading' color="primary") {{$t("common.follow")}}
|
||||
|
||||
p(slot='append') <img class='instance_thumb' :src="instance.thumbnail"/> {{instance.title}}
|
||||
|
||||
v-card-actions(v-if='isDialog')
|
||||
v-spacer
|
||||
v-btn(v-if='isDialog' color='warning' @click="$emit('close')") {{$t("common.cancel")}}
|
||||
v-btn(:disabled='(!couldGo || !proceed)' :href='link' target='_blank'
|
||||
:loading='loading' color="primary") {{$t("common.follow")}}
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import debounce from 'lodash/debounce'
|
||||
import url from 'url'
|
||||
|
||||
export default {
|
||||
name: 'FollowMe',
|
||||
props:
|
||||
{ isDialog: { type: Boolean, default: false } },
|
||||
data () {
|
||||
return {
|
||||
instance_hostname: '',
|
||||
proceed: false,
|
||||
instance: {},
|
||||
get_instance_info: debounce(this.getInstanceInfo, 500)
|
||||
loading: false,
|
||||
get_instance_info: debounce(this.getInstanceInfo, 300)
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['settings']),
|
||||
domain () {
|
||||
const URL = url.parse(this.settings.baseurl)
|
||||
const URL = new window.URL(this.settings.baseurl)
|
||||
return URL.hostname
|
||||
},
|
||||
couldGo () {
|
||||
console.error(this.instance_hostname)
|
||||
// check if is mastodon
|
||||
this.get_instance_info()
|
||||
this.get_instance_info(this.instance_hostname)
|
||||
return true
|
||||
},
|
||||
link () {
|
||||
|
@ -44,14 +58,19 @@ export default {
|
|||
if (!this.instance_hostname) {
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
|
||||
const instance_url = `https://${this.instance_hostname}/api/v1/instance`
|
||||
this.$axios.$get(instance_url)
|
||||
.then(ret => {
|
||||
this.instance = ret
|
||||
this.proceed = true
|
||||
this.loading = false
|
||||
})
|
||||
.catch(e => {
|
||||
this.instance = {}
|
||||
this.proceed = false
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
54
components/Footer.vue
Normal file
|
@ -0,0 +1,54 @@
|
|||
<template lang="pug">
|
||||
v-footer(color='secondary')
|
||||
|
||||
v-dialog(v-model='showFollowMe' destroy-on-close max-width='700px')
|
||||
FollowMe(@close='showFollowMe=false' is-dialog)
|
||||
|
||||
v-btn(color='primary' text href='https://gancio.org' target='_blank') Gancio <small>{{settings.version}}</small>
|
||||
v-btn.ml-1(v-for='link in footerLinks'
|
||||
:key='link.label' color='primary' text
|
||||
:href='link.href' :to='link.to' :target="link.href && '_blank'") {{link.label}}
|
||||
|
||||
v-menu(v-if='settings.enable_trusted_instances && settings.trusted_instances && settings.trusted_instances.length'
|
||||
offset-y bottom open-on-hover transition="slide-y-transition")
|
||||
template(v-slot:activator="{ on, attrs }")
|
||||
v-btn.ml-1(v-bind='attrs' v-on='on' color='primary' text) {{$t('common.places')}}
|
||||
v-list(subheaders two-lines)
|
||||
v-list-item(v-for='instance in settings.trusted_instances'
|
||||
:key='instance.name'
|
||||
target='_blank'
|
||||
:href='instance.url'
|
||||
two-line)
|
||||
v-list-item-avatar
|
||||
v-img(:src='`${instance.url}/favicon.ico`')
|
||||
v-list-item-content
|
||||
v-list-item-title {{instance.name}}
|
||||
v-list-item-subtitle {{instance.label}}
|
||||
|
||||
v-btn.ml-1(v-if='settings.enable_federation' color='primary' text rel='me' @click.prevent='showFollowMe=true') {{$t('event.interact_with_me')}}
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import FollowMe from '../components/FollowMe'
|
||||
|
||||
export default {
|
||||
components: { FollowMe },
|
||||
data () {
|
||||
return {
|
||||
showFollowMe: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['settings']),
|
||||
footerLinks () {
|
||||
return this.settings.footerLinks.map(link => {
|
||||
if (/^https?:\/\//.test(link.href)) {
|
||||
return { href: link.href, label: link.label }
|
||||
} else {
|
||||
return { to: link.href, label: link.label }
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,65 +0,0 @@
|
|||
<template lang="pug">
|
||||
section#home
|
||||
Announcement(v-for='announcement in announcements' :key='`a_${announcement.id}`' :announcement='announcement')
|
||||
#calbar.row.mt-2.mb-2
|
||||
.col-xl-7.col-lg-7.col-sm-6.col-xs-12
|
||||
|
||||
client-only
|
||||
Calendar
|
||||
.col
|
||||
Search(past-filter recurrent-filter)
|
||||
|
||||
#events
|
||||
//- Announcement(v-for='announcement in announcements' :key='`a_${announcement.id}`' :announcement='announcement')
|
||||
Event(v-for='event in events' :key='event.id' :event='event')
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
import Event from '@/components/Event'
|
||||
import Announcement from '@/components/Announcement'
|
||||
import Calendar from '@/components/Calendar'
|
||||
import Search from '@/components/Search'
|
||||
|
||||
export default {
|
||||
name: 'Home',
|
||||
components: { Calendar, Event, Search, Announcement },
|
||||
computed: {
|
||||
events () {
|
||||
return this.in_past ? this.filteredEventsWithPast : this.filteredEvents
|
||||
},
|
||||
...mapGetters(['filteredEvents', 'filteredEventsWithPast']),
|
||||
...mapState(['settings', 'in_past', 'announcements'])
|
||||
},
|
||||
head () {
|
||||
return {
|
||||
title: this.settings.title,
|
||||
meta: [
|
||||
// hid is used as unique identifier. Do not use `vmid` for it as it will not work
|
||||
{ hid: 'description', name: 'description', content: this.settings.description },
|
||||
{ hid: 'og-description', name: 'og:description', content: this.settings.description },
|
||||
{ hid: 'og-title', property: 'og:title', content: this.settings.title },
|
||||
{ hid: 'og-url', property: 'og:url', content: this.settings.baseurl },
|
||||
{ property: 'og:image', content: this.settings.baseurl + '/favicon.ico' }
|
||||
],
|
||||
link: [
|
||||
{ rel: 'alternate', type: 'application/rss+xml', title: this.settings.title, href: this.settings.baseurl + '/feed/rss' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang='less'>
|
||||
#calbar {
|
||||
max-width: 1000px;
|
||||
// margin: 0 auto;
|
||||
}
|
||||
#events {
|
||||
max-width: 1600px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
|
@ -1,18 +1,18 @@
|
|||
<template lang='pug'>
|
||||
div#list
|
||||
el-divider(v-if='title') {{title}}
|
||||
el-timeline
|
||||
el-timeline-item(
|
||||
v-for='event in events'
|
||||
:key='`${event.id}_${event.start_datetime}`'
|
||||
:timestamp='event|when'
|
||||
placement='top' icon='el-icon-arrow-down' size='large')
|
||||
|
||||
div.float-right
|
||||
small @{{event.place.name}}
|
||||
|
||||
a(:href='`/event/${event.id}`' target='_blank') {{event.title}}
|
||||
hr
|
||||
v-list(dense)
|
||||
v-list-item
|
||||
h3(v-if='title') {{title}}
|
||||
v-list-item(
|
||||
target='_blank'
|
||||
:to='`/event/${event.slug || event.id}`'
|
||||
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}}
|
||||
span.primary--text.ml-1 @{{event.place.name}}
|
||||
v-list-item-title(v-text='event.title')
|
||||
//- a.text-body-1(:href='`/event/${event.id}`' target='_blank') {{event.title}}
|
||||
</template>
|
||||
<script>
|
||||
|
||||
|
@ -49,6 +49,12 @@ export default {
|
|||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
computedEvents () {
|
||||
if (!this.maxEvents) { return this.events }
|
||||
return this.events.slice(0, this.maxEvents)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -56,23 +62,8 @@ export default {
|
|||
#list {
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
|
||||
.el-timeline {
|
||||
padding-left: 5px;
|
||||
|
||||
hr {
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-timeline-item {
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
|
||||
.el-timeline-item__timestamp {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
.v-list-item__title {
|
||||
white-space: normal !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
44
components/Loading.vue
Normal file
|
@ -0,0 +1,44 @@
|
|||
<template lang='pug'>
|
||||
.loading-page(:class='{ loading }')
|
||||
v-progress-circular(:size="100" :width="10" style='color: orangered;' indeterminate)
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data: () => ({
|
||||
loading: false
|
||||
}),
|
||||
methods: {
|
||||
start () {
|
||||
this.loading = true
|
||||
},
|
||||
finish () {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.loading-page {
|
||||
z-index: -10;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: white;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
text-align: center;
|
||||
padding-top: 200px;
|
||||
font-size: 4rem;
|
||||
font-family: sans-serif;
|
||||
transition: opacity .1s;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.loading {
|
||||
z-index: 10;
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
|
@ -1,53 +1,61 @@
|
|||
<template lang="pug">
|
||||
el-header#header
|
||||
nuxt-link#logo(:to='$route.name==="index"?"/about":"/"')
|
||||
img(src='/favicon.ico')
|
||||
span.ml-1.hidden-xs-only {{settings.title}}
|
||||
small.hidden-sm-only {{settings.description}}
|
||||
v-app-bar(app)
|
||||
|
||||
el-menu#menu(mode='horizontal' router )
|
||||
el-menu-item(v-if='could_add' index='/add')
|
||||
i.el-icon-plus
|
||||
span.hidden-xs-only {{$t('common.add_event')}}
|
||||
//- logo, title and description
|
||||
v-list-item(:to='$route.name==="index"?"/about":"/"')
|
||||
v-list-item-avatar(tile)
|
||||
v-img(src='/logo.png')
|
||||
v-list-item-content.d-none.d-sm-flex
|
||||
v-list-item-title
|
||||
h2 {{settings.title}}
|
||||
v-list-item-subtitle {{settings.description}}
|
||||
|
||||
//- nuxt-link(to='/export')
|
||||
el-menu-item(index='/export')
|
||||
i.el-icon-share
|
||||
span.hidden-xs-only {{$t('common.share')}}
|
||||
v-spacer
|
||||
|
||||
el-submenu(v-if='settings.enable_trusted_instances && settings.trusted_instances && settings.trusted_instances.length' index=4)
|
||||
template(slot='title')
|
||||
i.el-icon-guide
|
||||
span.hidden-xs-only {{$t('common.places')}}
|
||||
el-menu-item(v-for='instance in settings.trusted_instances' :key='instance.name')
|
||||
a(:href='instance.url' target='_link')
|
||||
img.mr-1(:src='`${instance.url}/favicon.ico`' style='height: 25px;')
|
||||
span.ml-1 {{instance.label || instance.name}}
|
||||
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')
|
||||
v-icon(large color='primary') mdi-plus
|
||||
|
||||
el-menu-item(v-if='!$auth.loggedIn' index='/login')
|
||||
i.el-icon-user
|
||||
span.hidden-xs-only {{$t('common.login')}}
|
||||
v-tooltip(bottom) {{$t('common.share')}}
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn(icon nuxt to='/export' v-on='on')
|
||||
v-icon mdi-share-variant
|
||||
|
||||
el-submenu(v-if='$auth.loggedIn' index=3)
|
||||
template(slot='title')
|
||||
i.el-icon-user
|
||||
span.hidden-xs-only {{$t('common.user')}}
|
||||
el-menu-item(divided index='/settings')
|
||||
i.el-icon-s-tools
|
||||
span {{$t('common.settings')}}
|
||||
el-menu-item(v-if='$auth.user.is_admin' index='/admin')
|
||||
i.el-icon-s-operation
|
||||
span {{$t('common.admin')}}
|
||||
el-menu-item(@click='logout')
|
||||
i.el-icon-switch-button
|
||||
span {{$t('common.logout')}}
|
||||
v-tooltip(v-if='!$auth.loggedIn' bottom) {{$t('common.login')}}
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn(icon nuxt to='/login' v-on='on')
|
||||
v-icon mdi-login
|
||||
|
||||
el-menu-item(type='text' v-clipboard:copy='feedLink' v-clipboard:success='copyLink')
|
||||
v-icon(color='orange' name='rss')
|
||||
v-menu(v-else
|
||||
offset-y bottom open-on-hover transition="slide-y-transition")
|
||||
template(v-slot:activator="{ on, attrs }")
|
||||
v-btn(icon v-bind='attrs' v-on='on')
|
||||
v-icon mdi-dots-vertical
|
||||
v-list
|
||||
v-list-item(nuxt to='/settings')
|
||||
v-list-item-icon
|
||||
v-icon mdi-cog
|
||||
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-list-item-content
|
||||
v-list-item-title {{$t('common.admin')}}
|
||||
|
||||
v-list-item(@click='logout')
|
||||
v-list-item-icon
|
||||
v-icon mdi-logout
|
||||
v-list-item-content
|
||||
v-list-item-title {{$t('common.logout')}}
|
||||
|
||||
v-btn(icon v-clipboard:copy='feedLink' v-clipboard:success='copyLink')
|
||||
v-icon(color='orange') mdi-rss
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import { Message } from 'element-ui'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
|
@ -55,8 +63,8 @@ export default {
|
|||
computed: {
|
||||
...mapState(['filters', 'settings']),
|
||||
feedLink () {
|
||||
const tags = this.filters.tags.join(',')
|
||||
const places = this.filters.places.join(',')
|
||||
const tags = this.filters.tags && this.filters.tags.join(',')
|
||||
const places = this.filters.places && this.filters.places.join(',')
|
||||
let query = ''
|
||||
if (tags || places) {
|
||||
query = '?'
|
||||
|
@ -72,14 +80,14 @@ export default {
|
|||
},
|
||||
could_add () {
|
||||
return (this.$auth.loggedIn || this.settings.allow_anon_event)
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
copyLink () {
|
||||
Message({ message: this.$t('common.feed_url_copied'), type: 'success', showClose: true })
|
||||
this.$root.$message('common.feed_url_copied')
|
||||
},
|
||||
logout () {
|
||||
Message({ showClose: true, message: this.$t('common.logout_ok'), type: 'success' })
|
||||
this.$root.$message('common.logout_ok')
|
||||
this.$auth.logout()
|
||||
},
|
||||
async createTrustedInstance () {
|
||||
|
@ -97,45 +105,9 @@ export default {
|
|||
}
|
||||
this.setSetting({ key: 'trusted_instances', value: this.settings.trusted_instances.concat(trusted_instance) })
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
Message({
|
||||
showClose: true,
|
||||
type: 'error',
|
||||
message: e
|
||||
})
|
||||
this.$root.$message(e, { color: 'error' })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='less'>
|
||||
#header {
|
||||
display: inline;
|
||||
#logo {
|
||||
img {
|
||||
max-height: 60px;
|
||||
}
|
||||
float: left;
|
||||
line-height: 60px;
|
||||
color: white;
|
||||
font-size: 1.5em;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
small {
|
||||
font-size: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
#menu {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 0px;
|
||||
border-bottom: none;
|
||||
.el-menu-item {
|
||||
padding: 0px 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -1,126 +1,98 @@
|
|||
<template lang="pug">
|
||||
el-main#search
|
||||
el-switch.mt-1.mb-2.ml-2.d-block(
|
||||
v-container.pt-0.pt-md-2
|
||||
v-switch.mt-0(
|
||||
v-if='recurrentFilter && settings.allow_recurrent_event'
|
||||
:active-text="$t('event.show_recurrent')"
|
||||
v-model='showRecurrent')
|
||||
|
||||
el-switch.mt-1.mb-2.ml-2.d-block(
|
||||
v-if='pastFilter'
|
||||
:active-text="$t('event.show_past')"
|
||||
v-model='showPast')
|
||||
|
||||
el-autocomplete.mb-1#searchInput.inline-input(:placeholder='$t("common.filter")' prefix-icon='el-icon-search'
|
||||
highlight-first-item
|
||||
v-model='search' :debounce='200'
|
||||
:fetch-suggestions='querySearch' clearable
|
||||
@select='addFilter')
|
||||
template(slot-scope='{ item }')
|
||||
span.float-left {{ item.label }}
|
||||
i.float-right.el-icon-place(v-if='item.type==="place"')
|
||||
i.float-right.el-icon-collection-tag(v-if='item.type==="tag"')
|
||||
#filters
|
||||
el-button.mr-1.bg-dark(type='text' round plain v-for='t in filters.tags' size='mini'
|
||||
:key='t' @click='removeTag(t)') {{t}}
|
||||
el-button.mr-1.bg-dark.text-warning(type='text' round plain v-for='p in selectedPlaces' size='mini'
|
||||
:key='p.id' @click='removePlace(p.id)') {{p.name}}
|
||||
v-model='showRecurrent'
|
||||
inset color='primary'
|
||||
hide-details
|
||||
:label="$t('event.show_recurrent')")
|
||||
v-autocomplete(
|
||||
:label='$t("common.search")'
|
||||
:items='keywords'
|
||||
hide-details
|
||||
@change='change'
|
||||
:value='selectedFilters'
|
||||
clearable
|
||||
:search-input.sync='search'
|
||||
item-text='label'
|
||||
return-object
|
||||
chips single-line
|
||||
multiple)
|
||||
template(v-slot:selection="data")
|
||||
v-chip(v-bind="data.attrs"
|
||||
close
|
||||
@click:close='remove(data.item)'
|
||||
:input-value="data.selected")
|
||||
v-avatar(left)
|
||||
v-icon {{data.item.type === 'place' ? 'mdi-map-marker' : 'mdi-tag' }}
|
||||
span {{ data.item.label }}
|
||||
template(v-slot:item='{ item }')
|
||||
v-list-item-avatar
|
||||
v-icon {{item.type === 'place' ? 'mdi-map-marker' : 'mdi-tag' }}
|
||||
v-list-item-content
|
||||
v-list-item-title(v-text='item.label')
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapActions } from 'vuex'
|
||||
import { mapState } from 'vuex'
|
||||
export default {
|
||||
name: 'Search',
|
||||
props: {
|
||||
pastFilter: Boolean,
|
||||
recurrentFilter: Boolean
|
||||
recurrentFilter: { type: Boolean, default: true },
|
||||
filters: { type: Object, default: () => {} }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
tmpfilter: null,
|
||||
search: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['tags', 'places', 'filters', 'settings']),
|
||||
selectedPlaces () {
|
||||
return this.places.filter(p => this.filters.places.includes(p.id))
|
||||
...mapState(['tags', 'places', 'settings']),
|
||||
showRecurrent: {
|
||||
get () {
|
||||
return this.filters.show_recurrent
|
||||
},
|
||||
set (v) {
|
||||
const filters = {
|
||||
tags: this.filters.tags,
|
||||
places: this.filters.places,
|
||||
show_recurrent: v
|
||||
}
|
||||
this.$emit('update', filters)
|
||||
}
|
||||
},
|
||||
keywords () {
|
||||
const tags = this.tags.filter(t => !this.filters.tags.includes(t.tag)).map(t => ({ type: 'tag', label: t.tag, weigth: t.weigth, id: t.tag }))
|
||||
const places = this.places.filter(p => !this.filters.places.includes(p.id))
|
||||
selectedFilters () {
|
||||
const tags = this.tags.filter(t => this.filters.tags.includes(t.tag)).map(t => ({ type: 'tag', label: t.tag, weigth: t.weigth, id: t.tag }))
|
||||
const places = this.places.filter(p => this.filters.places.includes(p.id))
|
||||
.map(p => ({ type: 'place', label: p.name, weigth: p.weigth, id: p.id }))
|
||||
const keywords = tags.concat(places).sort((a, b) => b.weigth - a.weigth)
|
||||
return keywords
|
||||
},
|
||||
showPast: {
|
||||
set (value) { this.showPastEvents(value) },
|
||||
get () { return this.filters.show_past_events }
|
||||
},
|
||||
showRecurrent: {
|
||||
set (value) { this.showRecurrentEvents(value) },
|
||||
get () { return this.filters.show_recurrent_events }
|
||||
},
|
||||
filter () {
|
||||
return this.filters.tags.concat(this.filters.places)
|
||||
keywords () {
|
||||
const tags = this.tags.map(t => ({ type: 'tag', label: t.tag, weigth: t.weigth, id: t.tag }))
|
||||
const places = this.places.map(p => ({ type: 'place', label: p.name, weigth: p.weigth, id: p.id }))
|
||||
const keywords = tags.concat(places).sort((a, b) => b.weigth - a.weigth)
|
||||
return keywords
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['setSearchPlaces', 'setSearchTags',
|
||||
'showPastEvents', 'showRecurrentEvents', 'updateEvent']),
|
||||
removeTag (tag) {
|
||||
this.setSearchTags(this.filters.tags.filter(t => t !== tag))
|
||||
},
|
||||
removePlace (place) {
|
||||
this.setSearchPlaces(this.filters.places.filter(p => p !== place))
|
||||
},
|
||||
querySearch (queryString, cb) {
|
||||
const ret = this.keywords
|
||||
.filter(k => !this.filter.map(f => f.id).includes(k.id))
|
||||
.filter(k => k.label.toLowerCase().includes(queryString))
|
||||
.slice(0, 5)
|
||||
cb(ret)
|
||||
},
|
||||
addFilter (item) {
|
||||
if (item.type === 'tag') {
|
||||
this.setSearchTags(this.filters.tags.concat(item.id))
|
||||
} else {
|
||||
this.setSearchPlaces(this.filters.places.concat(item.id))
|
||||
remove (item) {
|
||||
const filters = {
|
||||
tags: item.type === 'tag' ? this.filters.tags.filter(f => f !== item.id) : this.filters.tags,
|
||||
places: item.type === 'place' ? this.filters.places.filter(f => f !== item.id) : this.filters.places,
|
||||
show_recurrent: this.filters.show_recurrent
|
||||
}
|
||||
this.search = ''
|
||||
this.$emit('update', filters)
|
||||
},
|
||||
change (filters) {
|
||||
filters = {
|
||||
tags: filters.filter(t => t.type === 'tag').map(t => t.id),
|
||||
places: filters.filter(p => p.type === 'place').map(p => p.id),
|
||||
show_recurrent: this.filters.show_recurrent
|
||||
}
|
||||
this.$emit('update', filters)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang='less'>
|
||||
#search {
|
||||
#searchInput {
|
||||
border: none;
|
||||
border-radius: 0px;
|
||||
border-bottom: 2px solid lightgray;
|
||||
color: white;
|
||||
background-color: #111;
|
||||
}
|
||||
|
||||
.el-switch__label {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.el-switch__label.is-active {
|
||||
color: lightgreen;
|
||||
}
|
||||
|
||||
.el-switch__core {
|
||||
background-color: #555;
|
||||
border-color: #777;
|
||||
}
|
||||
|
||||
.is-checked .el-switch__core {
|
||||
background-color: lightgreen;
|
||||
}
|
||||
|
||||
#filters {
|
||||
line-height: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
|
40
components/Snackbar.vue
Normal file
|
@ -0,0 +1,40 @@
|
|||
<template lang='pug'>
|
||||
v-snackbar(
|
||||
v-model="active"
|
||||
:color="color"
|
||||
:bottom="bottom"
|
||||
:top="top"
|
||||
:left="left"
|
||||
:right="right"
|
||||
:timeout="timeout")
|
||||
v-icon.mr-3(color="white") {{icon}}
|
||||
span {{ message }}
|
||||
template(v-slot:action="{ }")
|
||||
v-icon(size="16" @click="active = false") mdi-close-circle
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
icon: 'md-alert',
|
||||
color: 'secondary',
|
||||
bottom: true,
|
||||
top: false,
|
||||
left: false,
|
||||
right: false,
|
||||
active: false,
|
||||
timeout: 5000,
|
||||
message: ''
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.$root.$message = (message, opts = {}) => {
|
||||
this.active = true
|
||||
this.message = this.$t(message, opts)
|
||||
this.color = opts.color || 'secondary'
|
||||
this.icon = opts.icon || 'md-alert'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,30 +1,37 @@
|
|||
<template lang='pug'>
|
||||
div
|
||||
p(v-html="$t('admin.announcement_description')")
|
||||
br
|
||||
el-input(v-model='announcement.title' :placeholder='$t("common.title")')
|
||||
Editor.mt-2(v-model='announcement.announcement' border no-save style='max-height: 400px;')
|
||||
el-button.mt-2.float-right(@click='save' type='success' plain) {{$t(`common.${editing?'save':'send'}`)}}
|
||||
v-container
|
||||
v-card-title {{$t('common.announcements')}}
|
||||
v-card-subtitle(v-html="$t('admin.announcement_description')")
|
||||
v-dialog(v-model='dialog' width='800px')
|
||||
v-card
|
||||
v-card-title {{$t('admin.new_announcement')}}
|
||||
v-card-text.px-0
|
||||
v-form(v-model='valid' ref='announcement' @submit.prevent='save' lazy-validation)
|
||||
v-text-field.col-12(v-model='announcement.title'
|
||||
:rules="[$validators.required('common.title')]"
|
||||
:label='$t("common.title")')
|
||||
Editor.col-12(v-model='announcement.announcement'
|
||||
border no-save max-height='400px' :placeholder="$t('common.description')")
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(@click='dialog=false' color='error') {{$t('common.cancel')}}
|
||||
v-btn(@click='save' color='primary' :disabled='!valid || loading' :loading='loading') {{$t(`common.${editing?'save':'send'}`)}}
|
||||
|
||||
el-table(:data='announcements' small)
|
||||
el-table-column(:label="$t('common.title')" width='250')
|
||||
template(slot-scope='data')
|
||||
span(slot='reference') {{data.row.title}}
|
||||
|
||||
el-table-column(:label="$t('common.actions')")
|
||||
template(slot-scope='data')
|
||||
el-button-group
|
||||
el-button(size='mini' type='primary'
|
||||
@click='edit(data.row)') {{$t('common.edit')}}
|
||||
el-button(size='mini'
|
||||
:type='data.row.visible?"warning":"success"'
|
||||
@click='toggle(data.row)') {{data.row.visible?$t('common.deactivate'):$t('common.activate')}}
|
||||
el-button(size='mini' type='danger'
|
||||
@click='remove(data.row)') {{$t('common.delete')}}
|
||||
v-btn(@click='openDialog' text color='primary') <v-icon>mdi-plus</v-icon> {{$t('common.add')}}
|
||||
v-card-text
|
||||
v-data-table(
|
||||
v-if='announcements.length'
|
||||
:hide-default-footer='announcements.length<10'
|
||||
:headers='headers'
|
||||
:items='announcements')
|
||||
template(v-slot:item.actions='{ item }')
|
||||
v-btn(text small @click.stop='toggle(item)'
|
||||
:color='item.visible?"warning":"success"') {{item.visible?$t('common.disable'):$t('common.enable')}}
|
||||
v-btn(text small @click='edit(item)' color='primary') {{$t('common.edit')}}
|
||||
v-btn(text small @click='remove(item)' color='error') {{$t('common.delete')}}
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import { Message, MessageBox } from 'element-ui'
|
||||
import { mapActions } from 'vuex'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
import Editor from '../Editor'
|
||||
|
@ -34,8 +41,15 @@ export default {
|
|||
components: { Editor, Announcement },
|
||||
data () {
|
||||
return {
|
||||
valid: false,
|
||||
dialog: false,
|
||||
editing: false,
|
||||
announcements: [],
|
||||
loading: false,
|
||||
headers: [
|
||||
{ value: 'title', text: 'Title' },
|
||||
{ value: 'actions', text: 'Actions', align: 'right' }
|
||||
],
|
||||
announcement: { title: '', announcement: '' }
|
||||
}
|
||||
},
|
||||
|
@ -49,6 +63,12 @@ export default {
|
|||
this.announcement.announcement = announcement.announcement
|
||||
this.announcement.id = announcement.id
|
||||
this.editing = true
|
||||
this.dialog = true
|
||||
},
|
||||
openDialog () {
|
||||
this.announcement = { title: '', announcement: '' }
|
||||
this.dialog = true
|
||||
this.$nextTick(() => this.$refs.announcement.reset())
|
||||
},
|
||||
async toggle (announcement) {
|
||||
try {
|
||||
|
@ -58,24 +78,18 @@ export default {
|
|||
this.setAnnouncements(cloneDeep(this.announcements.filter(a => a.visible)))
|
||||
} catch (e) {}
|
||||
},
|
||||
remove (announcement) {
|
||||
MessageBox.confirm(this.$t('admin.delete_announcement_confirm'),
|
||||
this.$t('common.confirm'), {
|
||||
confirmButtonText: this.$t('common.ok'),
|
||||
cancelButtonText: this.$t('common.cancel'),
|
||||
type: 'error'
|
||||
})
|
||||
.then(() => this.$axios.delete(`/announcements/${announcement.id}`))
|
||||
async remove (announcement) {
|
||||
const ret = await this.$root.$confirm('admin.delete_announcement_confirm')
|
||||
if (!ret) { return }
|
||||
this.$axios.delete(`/announcements/${announcement.id}`)
|
||||
.then(() => {
|
||||
Message({
|
||||
showClose: true,
|
||||
type: 'success',
|
||||
message: this.$t('admin.announcement_remove_ok')
|
||||
})
|
||||
this.$root.$message('admin.announcement_remove_ok')
|
||||
this.announcements = this.announcements.filter(a => a.id !== announcement.id)
|
||||
})
|
||||
},
|
||||
async save () {
|
||||
if (!this.$refs.announcement.validate()) { return }
|
||||
this.loading = true
|
||||
try {
|
||||
let announcement = null
|
||||
if (this.editing) {
|
||||
|
@ -87,10 +101,13 @@ export default {
|
|||
}
|
||||
this.setAnnouncements(cloneDeep(this.announcements))
|
||||
this.announcement = { title: '', announcement: '' }
|
||||
this.$refs.announcement.reset()
|
||||
this.editing = false
|
||||
this.dialog = false
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
54
components/admin/Events.vue
Normal file
|
@ -0,0 +1,54 @@
|
|||
<template lang='pug'>
|
||||
v-container
|
||||
v-card-title {{$t('common.events')}}
|
||||
v-card-subtitle {{$t('admin.event_confirm_description')}}
|
||||
v-card-text
|
||||
v-data-table(
|
||||
:hide-default-footer='unconfirmedEvents.length<10'
|
||||
:items='unconfirmedEvents'
|
||||
:headers='headers')
|
||||
template(v-slot:item.actions='{ item }')
|
||||
v-btn(text small @click='confirm(item)' color='success') {{$t('common.confirm')}}
|
||||
v-btn(text small :to='`/event/${item.id}`' color='success') {{$t('common.preview')}}
|
||||
v-btn(text small :to='`/add/${item.id}`' color='warning') {{$t('common.edit')}}
|
||||
v-btn(text small @click='remove(item)'
|
||||
color='error') {{$t('common.delete')}}
|
||||
|
||||
</template>
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
unconfirmedEvents: { type: Array, default: () => [] }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
valid: false,
|
||||
dialog: false,
|
||||
editing: false,
|
||||
headers: [
|
||||
{ value: 'title', text: 'Title' },
|
||||
{ value: 'actions', text: 'Actions', align: 'right' }
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
edit (event) {
|
||||
this.$router.push(`/add/${event.id}`)
|
||||
},
|
||||
async confirm (event) {
|
||||
try {
|
||||
await this.$axios.$put(`/event/confirm/${event.id}`)
|
||||
this.$emit('confirmed', event.id)
|
||||
this.$root.$message('event.confirmed')
|
||||
} catch (e) {}
|
||||
},
|
||||
async remove (event) {
|
||||
const ret = await this.$root.$confirm('event.remove_confirmation')
|
||||
if (!ret) { return }
|
||||
await this.$axios.delete(`/event/${event.id}`)
|
||||
this.$root.$message('admin.event_remove_ok')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,61 +1,93 @@
|
|||
<template lang="pug">
|
||||
el-main
|
||||
el-switch.d-block(v-model='enable_federation' :active-text="$t('admin.enable_federation')")
|
||||
small.text-secondary {{$t('admin.enable_federation_help')}}
|
||||
v-container
|
||||
v-card-title {{$t('common.federation')}}
|
||||
v-card-text
|
||||
v-switch(v-model='enable_federation'
|
||||
:label="$t('admin.enable_federation')"
|
||||
persistent-hint
|
||||
inset
|
||||
:hint="$t('admin.enable_federation_help')")
|
||||
|
||||
template(v-if='enable_federation')
|
||||
template(v-if='enable_federation')
|
||||
|
||||
el-switch.d-block.mt-4(v-model='enable_resources' :active-text="$t('admin.enable_resources')")
|
||||
small.text-secondary {{$t('admin.enable_resources_help')}}
|
||||
v-switch.mt-4(v-model='enable_resources'
|
||||
:label="$t('admin.enable_resources')"
|
||||
:hint="$t('admin.enable_resources_help')"
|
||||
persistent-hint inset)
|
||||
|
||||
el-switch.d-block.mt-4(v-model='hide_boosts' :active-text="$t('admin.hide_boost_bookmark')")
|
||||
small.text-secondary {{$t('admin.hide_boost_bookmark_help')}}
|
||||
v-switch.mt-4(v-model='hide_boosts'
|
||||
:label="$t('admin.hide_boost_bookmark')"
|
||||
:hint="$t('admin.hide_boost_bookmark_help')"
|
||||
persistent-hint inset)
|
||||
|
||||
div.mt-4 {{$t('admin.instance_name')}}
|
||||
el-input(v-model='instance_name' placeholder='Instance name' @blur='save("instance_name", instance_name)')
|
||||
small.d-block.text-secondary {{$t('admin.instance_name_help')}} (<u>@{{instance_name}}@{{settings.baseurl|url2host}}</u>)
|
||||
//- div.mt-4 {{$t('admin.instance_name')}}
|
||||
v-text-field.mt-5(v-model='instance_name'
|
||||
:label="$t('admin.instance_name')"
|
||||
:hint="`${$t('admin.instance_name_help')} ${instance_ap_url}`"
|
||||
placeholder='Instance name' persistent-hint
|
||||
@blur='save("instance_name", instance_name)')
|
||||
|
||||
el-switch.d-block.mt-4(v-model='enable_trusted_instances' :active-text="$t('admin.enable_trusted_instances')")
|
||||
small.text-secondary {{$t('admin.trusted_instances_help')}}
|
||||
v-switch.mt-4(v-model='enable_trusted_instances'
|
||||
:label="$t('admin.enable_trusted_instances')"
|
||||
persistent-hint inset
|
||||
:hint="$t('admin.trusted_instances_help')")
|
||||
|
||||
template(v-if='enable_trusted_instances')
|
||||
div.mt-4 {{$t('admin.instance_place')}}
|
||||
el-input(v-model='instance_place' @blur='save("instance_place", instance_place)')
|
||||
small.d-block.text-secondary {{$t('admin.instance_place_help')}}
|
||||
template(v-if='enable_trusted_instances')
|
||||
v-text-field.mt-4(v-model='instance_place'
|
||||
:label="$t('admin.instance_place')"
|
||||
persistent-hint
|
||||
:hint="$t('admin.instance_place_help')"
|
||||
@blur='save("instance_place", instance_place)'
|
||||
)
|
||||
|
||||
div.mt-4 {{$t('admin.add_trusted_instance')}}
|
||||
el-input(v-model='instance_url' :placeholder="$t('common.url')")
|
||||
el-button(slot='append' @click='createTrustedInstance') {{$t('common.send')}}
|
||||
v-dialog(v-model='dialogAddInstance' width="500px")
|
||||
v-card
|
||||
v-card-title {{$t('admin.add_trusted_instance')}}
|
||||
v-card-text
|
||||
v-form(v-model='valid' @submit.prevent='createTrustedInstance' ref='form' lazy-validation)
|
||||
v-text-field.mt-4(v-model='instance_url'
|
||||
persistent-hint
|
||||
:rules="[$validators.required('common.url')]"
|
||||
:loading='loading'
|
||||
:hint="$t('admin.add_trusted_instance')"
|
||||
:label="$t('common.url')")
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(color='error' @click='dialogAddInstance=false') {{$t('common.cancel')}}
|
||||
v-btn(color='primary' :disabled='!valid || loading' :loading='loading' @click='createTrustedInstance') {{$t('common.ok')}}
|
||||
|
||||
el-table(:data='settings.trusted_instances')
|
||||
el-table-column(:label="$t('common.name')")
|
||||
template(slot-scope='data')
|
||||
span {{data.row.name}}
|
||||
el-table-column(:label="$t('common.url')")
|
||||
template(slot-scope='data')
|
||||
span {{data.row.url}}
|
||||
el-table-column(:label="$t('common.place')")
|
||||
template(slot-scope='data')
|
||||
span {{data.row.label}}
|
||||
el-table-column(:label="$t('common.actions')")
|
||||
template(slot-scope='data')
|
||||
el-button(size='mini'
|
||||
type='danger'
|
||||
@click='deleteInstance(data.row)') {{$t('admin.delete_user')}}
|
||||
v-btn.mt-4(@click='dialogAddInstance = true' color='primary' text) <v-icon>mdi-plus</v-icon> {{$t('admin.add_instance')}}
|
||||
v-data-table(
|
||||
v-if='settings.trusted_instances.length'
|
||||
:hide-default-footer='settings.trusted_instances.length<10'
|
||||
: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
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import { mapActions, mapState } from 'vuex'
|
||||
import { Message, MessageBox } from 'element-ui'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'Federation',
|
||||
data ({ $store }) {
|
||||
data ({ $store, $options }) {
|
||||
return {
|
||||
instance_url: '',
|
||||
instance_name: $store.state.settings.instance_name,
|
||||
instance_place: $store.state.settings.instance_place
|
||||
instance_place: $store.state.settings.instance_place,
|
||||
url2host: $options.filters.url2host,
|
||||
dialogAddInstance: false,
|
||||
loading: false,
|
||||
valid: false,
|
||||
headers: [
|
||||
{ value: 'name', text: 'Name' },
|
||||
{ value: 'url', text: 'URL' },
|
||||
{ value: 'label', text: 'Place' },
|
||||
{ value: 'actions', text: 'Actions', align: 'right' }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -75,12 +107,21 @@ export default {
|
|||
enable_trusted_instances: {
|
||||
get () { return this.settings.enable_trusted_instances },
|
||||
set (value) { this.setSetting({ key: 'enable_trusted_instances', value }) }
|
||||
},
|
||||
instance_ap_url () {
|
||||
const instance_url = this.settings.baseurl.match(/^https?:\/\/(.[^/:]+)/i)[1]
|
||||
return `(@${this.instance_name}@${instance_url})`
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['setSetting']),
|
||||
async createTrustedInstance () {
|
||||
if (!this.$refs.form.validate()) { return }
|
||||
this.loading = true
|
||||
try {
|
||||
if (!this.instance_url.startsWith('http')) {
|
||||
this.instance_url = `https://${this.instance_url}`
|
||||
}
|
||||
const instance = await axios.get(`${this.instance_url}/.well-known/nodeinfo/2.1`)
|
||||
this.setSetting({
|
||||
key: 'trusted_instances',
|
||||
|
@ -90,27 +131,19 @@ export default {
|
|||
label: instance.data.metadata.nodeLabel
|
||||
})
|
||||
})
|
||||
this.instance_url = ''
|
||||
this.$refs.form.reset()
|
||||
this.dialogAddInstance = false
|
||||
} catch (e) {
|
||||
Message({
|
||||
showClose: true,
|
||||
type: 'error',
|
||||
message: e
|
||||
})
|
||||
this.$root.$message(e, { color: 'error' })
|
||||
}
|
||||
this.loading = false
|
||||
},
|
||||
deleteInstance (instance) {
|
||||
MessageBox.confirm(this.$t('admin.delete_trusted_instance_confirm'),
|
||||
this.$t('common.confirm'), {
|
||||
confirmButtonText: this.$t('common.ok'),
|
||||
cancelButtonText: this.$t('common.cancel'),
|
||||
type: 'error'
|
||||
}
|
||||
).then(() => {
|
||||
this.setSetting({
|
||||
key: 'trusted_instances',
|
||||
value: this.settings.trusted_instances.filter(i => i.url !== instance.url)
|
||||
})
|
||||
async deleteInstance (instance) {
|
||||
const ret = await this.$root.$confirm('admin.delete_trusted_instance_confirm')
|
||||
if (!ret) { return }
|
||||
this.setSetting({
|
||||
key: 'trusted_instances',
|
||||
value: this.settings.trusted_instances.filter(i => i.url !== instance.url)
|
||||
})
|
||||
},
|
||||
save (key, value) {
|
||||
|
|
|
@ -1,117 +1,123 @@
|
|||
<template lang='pug'>
|
||||
div
|
||||
el-row
|
||||
el-col(:span='12')
|
||||
el-divider {{$t('common.instances')}}
|
||||
el-input(v-model='instancesFilter' :placeholder="$t('admin.filter_instances')")
|
||||
client-only
|
||||
el-pagination(v-if='instances.length>perPage' :page-size='perPage' :currentPage.sync='instancePage' :total='instances.length')
|
||||
el-table(:data='paginatedInstances' small @row-click='instanceSelected')
|
||||
el-table-column(label='Domain' width='180')
|
||||
template(slot-scope='data')
|
||||
span(slot='reference') {{data.row.domain}}
|
||||
el-table-column(label='Name' width='100')
|
||||
template(slot-scope='data')
|
||||
span(slot='reference') {{data.row.name}}
|
||||
el-table-column(:label="$t('common.users')" width='70')
|
||||
template(slot-scope='data')
|
||||
span(slot='reference') {{data.row.users}}
|
||||
el-table-column(:label="$t('common.actions')" width='120')
|
||||
template(slot-scope='data')
|
||||
el-button-group
|
||||
el-button(size='mini'
|
||||
:type='data.row.blocked?"danger":"warning"'
|
||||
@click='toggleBlock(data.row)') {{data.row.blocked?$t('admin.unblock'):$t('admin.block')}}
|
||||
v-container
|
||||
v-card-title {{$t('common.moderation')}}
|
||||
v-card-text
|
||||
v-row
|
||||
v-col(:span='12')
|
||||
span {{$t('common.instances')}}
|
||||
v-text-field(v-model='instancesFilter' :placeholder="$t('admin.filter_instances')")
|
||||
v-data-table(:items='instances'
|
||||
:items-per-page='5'
|
||||
:search='instancesFilter'
|
||||
:hide-default-footer='instances.length<5'
|
||||
dense :headers='instancesHeader'
|
||||
@click:row='instanceSelected')
|
||||
template(v-slot:item.blocked="{ item }")
|
||||
v-icon(v-if='item.blocked') mdi-checkbox-intermediate
|
||||
v-icon(v-else) mdi-checkbox-blank-outline
|
||||
|
||||
el-col.float-right(:span='11' align='right')
|
||||
el-divider {{$t('common.users')}}
|
||||
el-input(v-model='usersFilter' :placeholder="$t('admin.filter_users')")
|
||||
client-only
|
||||
el-pagination(v-if='users.length>perPage' :page-size='perPage' :currentPage.sync='userPage' :total='users.length')
|
||||
el-table(:data='paginatedSelectedUsers' small)
|
||||
el-table-column(:label="$t('common.user')" width='150')
|
||||
template(slot-scope='data')
|
||||
span(slot='reference')
|
||||
a(:href='data.row.object.id' target='_blank') {{data.row.object.name}}
|
||||
small ({{data.row.object.preferredUsername}})
|
||||
el-table-column(:label="$t('common.resources')" width='90')
|
||||
template(slot-scope='data')
|
||||
span {{data.row.resources.length}}
|
||||
el-table-column(:label="$t('common.actions')" width='200')
|
||||
template(slot-scope='data')
|
||||
el-button-group
|
||||
el-button(size='mini'
|
||||
:type='data.row.blocked?"danger":"warning"'
|
||||
@click='toggleUserBlock(data.row)') {{data.row.blocked?$t('admin.unblock'):$t('admin.block')}}
|
||||
v-col(:span='11')
|
||||
span {{$t('common.users')}}
|
||||
v-text-field(v-model='usersFilter' :placeholder="$t('admin.filter_users')")
|
||||
v-data-table(:items='users'
|
||||
:items-per-page='5'
|
||||
:search='usersFilter'
|
||||
:hide-default-footer='users.length<5'
|
||||
dense :headers='usersHeader')
|
||||
//- template(v-slot:item.username="{item}")
|
||||
//- a(:href='item.ap_id') {{item.object.preferredUsername}}
|
||||
//- el-table-column(:label="$t('common.user')" width='150')
|
||||
//- template(slot-scope='data')
|
||||
//- span(slot='reference')
|
||||
//- a(:href='data.row.object.id' target='_blank') {{data.row.object.name}}
|
||||
//- small ({{data.row.object.preferredUsername}})
|
||||
//- el-table-column(:label="$t('common.resources')" width='90')
|
||||
//- template(slot-scope='data')
|
||||
//- span {{data.row.resources.length}}
|
||||
//- el-table-column(:label="$t('common.actions')" width='200')
|
||||
//- template(slot-scope='data')
|
||||
//- el-button-group
|
||||
//- el-button(size='mini'
|
||||
//- :type='data.row.blocked?"danger":"warning"'
|
||||
//- @click='toggleUserBlock(data.row)') {{data.row.blocked?$t('admin.unblock'):$t('admin.block')}}
|
||||
|
||||
div
|
||||
el-divider {{$t('common.resources')}}
|
||||
el-table(:data='paginatedResources' small :row-style='resourceStyle')
|
||||
el-table-column(:label="$t('common.event')")
|
||||
template(slot-scope='data')
|
||||
span {{data.row.event}}
|
||||
el-table-column(:label="$t('common.resources')")
|
||||
template(slot-scope='data')
|
||||
span(:class='{disabled: data.row.hidden}' v-html='data.row.data.content')
|
||||
el-table-column(:label="$t('common.user')" width='200')
|
||||
template(slot-scope='data')
|
||||
span(:class='{disabled: data.row.hidden}' v-html='data.row.data.actor')
|
||||
el-table-column(:label="$t('common.actions')" width="150")
|
||||
template(slot-scope='data')
|
||||
el-dropdown
|
||||
el-button(type="primary" icon="el-icon-arrow-down" size='mini') {{$t('common.moderation')}}
|
||||
el-dropdown-menu(slot='dropdown')
|
||||
el-dropdown-item(v-if='!data.row.hidden' icon='el-icon-remove' @click.native='hideResource(data.row, true)') {{$t('admin.hide_resource')}}
|
||||
el-dropdown-item(v-else icon='el-icon-success' @click.native='hideResource(data.row, false)') {{$t('admin.show_resource')}}
|
||||
el-dropdown-item(icon='el-icon-delete' @click.native='deleteResource(data.row)') {{$t('admin.delete_resource')}}
|
||||
el-dropdown-item(icon='el-icon-lock' @click.native='toggleUserBlock(data.row.ap_user)') {{$t('admin.block_user')}}
|
||||
div
|
||||
v-card-title {{$t('common.resources')}}
|
||||
v-data-table(:items='resources'
|
||||
:hide-default-footer='resources.length<10'
|
||||
)
|
||||
//- el-table-column(:label="$t('common.event')")
|
||||
//- template(slot-scope='data')
|
||||
//- span {{data.row.event}}
|
||||
//- el-table-column(:label="$t('common.resources')")
|
||||
//- template(slot-scope='data')
|
||||
//- span(:class='{disabled: data.row.hidden}' v-html='data.row.data.content')
|
||||
//- el-table-column(:label="$t('common.user')" width='200')
|
||||
//- template(slot-scope='data')
|
||||
//- span(:class='{disabled: data.row.hidden}' v-html='data.row.data.actor')
|
||||
//- el-table-column(:label="$t('common.actions')" width="150")
|
||||
//- template(slot-scope='data')
|
||||
//- el-dropdown
|
||||
//- el-button(type="primary" icon="el-icon-arrow-down" size='mini') {{$t('common.moderation')}}
|
||||
//- el-dropdown-menu(slot='dropdown')
|
||||
//- el-dropdown-item(v-if='!data.row.hidden' icon='el-icon-remove' @click.native='hideResource(data.row, true)') {{$t('admin.hide_resource')}}
|
||||
//- el-dropdown-item(v-else icon='el-icon-success' @click.native='hideResource(data.row, false)') {{$t('admin.show_resource')}}
|
||||
//- el-dropdown-item(icon='el-icon-delete' @click.native='deleteResource(data.row)') {{$t('admin.delete_resource')}}
|
||||
//- el-dropdown-item(icon='el-icon-lock' @click.native='toggleUserBlock(data.row.ap_user)') {{$t('admin.block_user')}}
|
||||
</template>
|
||||
<script>
|
||||
import { mapState, mapActions } from 'vuex'
|
||||
import { MessageBox } from 'element-ui'
|
||||
|
||||
export default {
|
||||
name: 'Moderation',
|
||||
data () {
|
||||
return {
|
||||
perPage: 10,
|
||||
instancePage: 1,
|
||||
instancesFilter: '',
|
||||
instances: [],
|
||||
resources: [],
|
||||
resourcePage: 1,
|
||||
usersFilter: '',
|
||||
users: [],
|
||||
userPage: 1
|
||||
usersHeader: [
|
||||
{ value: 'object.preferredUsername', text: 'Name' }
|
||||
],
|
||||
instancesHeader: [
|
||||
{ value: 'domain', text: 'Domain' },
|
||||
{ value: 'name', text: 'Name' },
|
||||
{ value: 'blocked', text: 'Blocked' },
|
||||
{ value: 'users', text: 'known users' }
|
||||
],
|
||||
resourcesHeader: [
|
||||
{ value: '', text: '' }
|
||||
],
|
||||
usersFilter: '',
|
||||
instancesFilter: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['settings']),
|
||||
paginatedResources () {
|
||||
return this.resources.slice((this.resourcePage - 1) * this.perPage,
|
||||
this.resourcePage * this.perPage)
|
||||
},
|
||||
paginatedInstances () {
|
||||
return this.filteredInstances.slice((this.instancePage - 1) * this.perPage,
|
||||
this.instancePage * this.perPage)
|
||||
},
|
||||
filteredUsers () {
|
||||
if (!this.usersFilter) { return this.users }
|
||||
const usersFilter = this.usersFilter.toLowerCase()
|
||||
return this.users.filter(user => user.name.includes(usersFilter) || user.preferredName.includes(usersFilter))
|
||||
},
|
||||
filteredInstances () {
|
||||
if (!this.instancesFilter) { return this.instances }
|
||||
const instancesFilter = this.instancesFilter.toLowerCase()
|
||||
return this.instances.filter(instance =>
|
||||
(instance.name && instance.name.includes(instancesFilter)) ||
|
||||
(instance.domain && instance.domain.includes(instancesFilter))
|
||||
)
|
||||
},
|
||||
paginatedSelectedUsers () {
|
||||
return this.filteredUsers.slice((this.userPage - 1) * this.perPage,
|
||||
this.userPage * this.perPage)
|
||||
}
|
||||
...mapState(['settings'])
|
||||
// paginatedResources () {
|
||||
// return this.resources.slice((this.resourcePage - 1) * this.perPage,
|
||||
// this.resourcePage * this.perPage)
|
||||
// },
|
||||
// paginatedInstances () {
|
||||
// return this.filteredInstances.slice((this.instancePage - 1) * this.perPage,
|
||||
// this.instancePage * this.perPage)
|
||||
// },
|
||||
// filteredUsers () {
|
||||
// if (!this.usersFilter) { return this.users }
|
||||
// const usersFilter = this.usersFilter.toLowerCase()
|
||||
// return this.users.filter(user => user.name.includes(usersFilter) || user.preferredName.includes(usersFilter))
|
||||
// },
|
||||
// filteredInstances () {
|
||||
// if (!this.instancesFilter) { return this.instances }
|
||||
// const instancesFilter = this.instancesFilter.toLowerCase()
|
||||
// return this.instances.filter(instance =>
|
||||
// (instance.name && instance.name.includes(instancesFilter)) ||
|
||||
// (instance.domain && instance.domain.includes(instancesFilter))
|
||||
// )
|
||||
// },
|
||||
// paginatedSelectedUsers () {
|
||||
// return this.filteredUsers.slice((this.userPage - 1) * this.perPage,
|
||||
// this.userPage * this.perPage)
|
||||
// }
|
||||
},
|
||||
async mounted () {
|
||||
this.instances = await this.$axios.$get('/instances')
|
||||
|
@ -133,29 +139,18 @@ export default {
|
|||
resource.hidden = hidden
|
||||
},
|
||||
async toggleUserBlock (ap_user) {
|
||||
try {
|
||||
if (!ap_user.blocked) {
|
||||
await MessageBox.confirm(this.$t('admin.user_block_confirm'), {
|
||||
confirmButtonText: this.$t('common.ok'),
|
||||
cancelButtonText: this.$t('common.cancel'),
|
||||
type: 'error'
|
||||
})
|
||||
}
|
||||
await this.$axios.post('/instances/toggle_user_block', { ap_id: ap_user.ap_id })
|
||||
ap_user.blocked = !ap_user.blocked
|
||||
} catch (e) { }
|
||||
if (!ap_user.blocked) {
|
||||
const ret = await this.$root.$confirm('admin.user_block_confirm')
|
||||
if (!ret) { return }
|
||||
}
|
||||
await this.$axios.post('/instances/toggle_user_block', { ap_id: ap_user.ap_id })
|
||||
ap_user.blocked = !ap_user.blocked
|
||||
},
|
||||
deleteResource (resource) {
|
||||
MessageBox.confirm(this.$t('admin.delete_resource_confirm'),
|
||||
this.$t('common.confirm'), {
|
||||
confirmButtonText: this.$t('common.ok'),
|
||||
cancelButtonText: this.$t('common.cancel'),
|
||||
type: 'error'
|
||||
}
|
||||
).then(() => {
|
||||
this.$axios.delete(`/resources/${resource.id}`)
|
||||
this.resources = this.resources.filter(r => r.id !== resource.id)
|
||||
})
|
||||
async deleteResource (resource) {
|
||||
const ret = await this.$root.$confirm('admin.delete_resource_confirm')
|
||||
if (!ret) { return }
|
||||
await this.$axios.delete(`/resources/${resource.id}`)
|
||||
this.resources = this.resources.filter(r => r.id !== resource.id)
|
||||
},
|
||||
async toggleBlock (instance) {
|
||||
await this.$axios.post('/instances/toggle_block', { instance: instance.domain, blocked: !instance.blocked })
|
||||
|
@ -164,8 +159,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.instance_thumb {
|
||||
height: 20px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,57 +1,71 @@
|
|||
<template lang='pug'>
|
||||
div
|
||||
p(v-html="$t('admin.place_description')")
|
||||
el-form.mb-2(inline label-width='120px')
|
||||
el-form-item(:label="$t('common.name')")
|
||||
el-input.mr-1(:placeholder='$t("common.name")' v-model='place.name')
|
||||
el-form-item(:label="$t('common.address')")
|
||||
el-input.mr-1(:placeholder='$t("common.address")' v-model='place.address')
|
||||
el-button(variant='primary' @click='savePlace') {{$t('common.save')}}
|
||||
v-container
|
||||
v-card-title {{$t('common.places')}}
|
||||
v-card-subtitle(v-html="$t('admin.place_description')")
|
||||
|
||||
el-table(:data='paginatedPlaces' small)
|
||||
el-table-column(:label="$t('common.name')" width='200')
|
||||
template(slot-scope='data') {{data.row.name}}
|
||||
el-table-column(:label="$t('common.address')" width='400')
|
||||
template(slot-scope='data') {{data.row.address}}
|
||||
el-table-column(:label="$t('common.actions')" width='200')
|
||||
template(slot-scope='data')
|
||||
el-button(size='mini'
|
||||
type='success'
|
||||
@click='place = data.row') {{$t('common.edit')}}
|
||||
v-dialog(v-model='dialog' width='600')
|
||||
v-card(color='secondary')
|
||||
v-card-title {{$t('admin.edit_place')}}
|
||||
v-card-text
|
||||
v-form(v-model='valid' ref='form' lazy-validation)
|
||||
v-text-field(
|
||||
:rules="[$validators.required('common.name')]"
|
||||
:label="$t('common.name')"
|
||||
v-model='place.name'
|
||||
:placeholder='$t("common.name")')
|
||||
|
||||
client-only
|
||||
el-pagination(v-if='places.length>perPage' :page-size='perPage' :currentPage.sync='placePage' :total='places.length')
|
||||
v-text-field(
|
||||
:rules="[$validators.required('common.address')]"
|
||||
:label="$t('common.address')"
|
||||
v-model='place.address'
|
||||
:placeholder='$t("common.address")')
|
||||
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(@click='dialog=false' color='warning') {{$t('common.cancel')}}
|
||||
v-btn(@click='savePlace' color='primary' :loading='loading'
|
||||
:disable='!valid || loading') {{$t('common.save')}}
|
||||
|
||||
v-card-text
|
||||
v-data-table(
|
||||
:headers='headers'
|
||||
:items='places')
|
||||
template(v-slot:item.actions='{item}')
|
||||
v-btn(@click='editPlace(item)' color='primary' icon)
|
||||
v-icon mdi-pencil
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { mapState, mapActions } from 'vuex'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
perPage: 10,
|
||||
placePage: 1,
|
||||
place: { name: '', address: '', id: null }
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['places']),
|
||||
paginatedPlaces () {
|
||||
return this.places.slice((this.placePage - 1) * this.perPage,
|
||||
this.placePage * this.perPage)
|
||||
loading: false,
|
||||
dialog: false,
|
||||
valid: false,
|
||||
place: { name: '', address: '', id: null },
|
||||
headers: [
|
||||
{ value: 'name', text: 'Name' },
|
||||
{ value: 'address', text: 'Address' },
|
||||
{ value: 'actions', text: 'Actions', align: 'right' }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: mapState(['places']),
|
||||
methods: {
|
||||
placeSelected (items) {
|
||||
if (items.length === 0) {
|
||||
this.place.name = this.place.address = ''
|
||||
return
|
||||
}
|
||||
const item = items[0]
|
||||
...mapActions(['updateMeta']),
|
||||
editPlace (item) {
|
||||
this.place.name = item.name
|
||||
this.place.address = item.address
|
||||
this.place.id = item.id
|
||||
this.dialog = true
|
||||
},
|
||||
async savePlace () {
|
||||
const place = await this.$axios.$put('/place', this.place)
|
||||
if (!this.$refs.form.validate()) return
|
||||
this.loading = true
|
||||
await this.$axios.$put('/place', this.place)
|
||||
this.updateMeta()
|
||||
this.loading = false
|
||||
this.dialog = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,48 +1,59 @@
|
|||
<template lang="pug">
|
||||
el-main
|
||||
v-container
|
||||
v-card-title {{$t('common.settings')}}
|
||||
v-card-text
|
||||
|
||||
//- select timezone
|
||||
div {{$t('admin.select_instance_timezone')}}
|
||||
el-select(v-model='instance_timezone' filterable
|
||||
@input.native='queryTz=$event.target.value' @change='queryTz=""'
|
||||
default-first-option placeholder='Timezone, type to search')
|
||||
el-option(v-for='timezone in filteredTimezones' :key='timezone.value' :value='timezone.value')
|
||||
span.float-left {{timezone.value}}
|
||||
small.float-right.text-danger {{timezone.offset}}
|
||||
small.d-block.text-secondary {{$t('admin.instance_timezone_description')}}
|
||||
v-autocomplete(v-model='instance_timezone'
|
||||
:label="$t('admin.select_instance_timezone')"
|
||||
:hint="$t('admin.instance_timezone_description')"
|
||||
:items="filteredTimezones"
|
||||
persistent-hint
|
||||
item-text='value'
|
||||
item-value='value'
|
||||
placeholder='Timezone, type to search')
|
||||
template(v-slot:item='{ item }')
|
||||
v-list-item-content
|
||||
v-list-item-title {{item.value}}
|
||||
v-list-item-subtitle {{item.offset}}
|
||||
|
||||
div.mt-4 {{$t('admin.instance_locale')}}
|
||||
el-select(v-model='instance_locale')
|
||||
el-option(v-for='locale in Object.keys(locales)' :key='locale' :label='locales[locale]' :value='locale')
|
||||
small.d-block.text-secondary {{$t('admin.instance_locale_description')}}
|
||||
v-select.mt-5(
|
||||
v-model='instance_locale'
|
||||
:label="$t('admin.instance_locale')"
|
||||
:hint="$t('admin.instance_locale_description')"
|
||||
persistent-hint
|
||||
:items='locales'
|
||||
)
|
||||
|
||||
div.mt-4 {{$t('common.title')}}
|
||||
el-input(v-model='title' @blur='save("title", title)')
|
||||
small.d-block.text-secondary {{$t('admin.title_description')}}
|
||||
v-text-field.mt-5(v-model='title'
|
||||
:label="$t('common.title')"
|
||||
:hint="$t('admin.title_description')"
|
||||
@blur='save("title", title)'
|
||||
persistent-hint
|
||||
)
|
||||
|
||||
div.mt-4 {{$t('common.description')}}
|
||||
el-input(v-model='description' @blur='save("description", description)')
|
||||
small.d-block.text-secondary {{$t('admin.description_description')}}
|
||||
v-text-field.mt-5(v-model='description'
|
||||
:label="$t('common.description')"
|
||||
:hint="$t('admin.description_description')"
|
||||
persistent-hint
|
||||
@blur='save("description", description)')
|
||||
|
||||
div.mt-4 {{$t('admin.favicon')}}
|
||||
el-upload(ref='upload'
|
||||
:action='`${settings.baseurl}/api/settings/logo`'
|
||||
:on-success="forceLogoReload"
|
||||
name='logo'
|
||||
:show-file-list="true"
|
||||
accept='image/png'
|
||||
:limit='1'
|
||||
:multiple='false')
|
||||
el-button(slot='trigger' size='small' type='primary' plain) Select file
|
||||
.el-upload__tip(slot='tip') png files with a size less than 500kb
|
||||
el-image(:src='`${settings.baseurl}/favicon.ico?${logoKey}`')
|
||||
el-switch.d-block.mt-4(v-model='allow_registration'
|
||||
:active-text="$t('admin.allow_registration_description')")
|
||||
el-switch.d-block.mt-4(v-model='allow_anon_event' :active-text="$t('admin.allow_anon_event')")
|
||||
v-switch.mt-4(v-model='allow_registration'
|
||||
inset
|
||||
:label="$t('admin.allow_registration_description')")
|
||||
|
||||
el-switch.d-block.mt-4(v-model='allow_recurrent_event' :active-text="$t('admin.allow_recurrent_event')")
|
||||
v-switch.mt-1(v-model='allow_anon_event'
|
||||
inset
|
||||
:label="$t('admin.allow_anon_event')")
|
||||
|
||||
el-switch.d-block.mt-4(v-if='allow_recurrent_event'
|
||||
v-model='recurrent_event_visible' :active-text="$t('admin.recurrent_event_visible')")
|
||||
v-switch.mt-1(v-model='allow_recurrent_event'
|
||||
inset
|
||||
:label="$t('admin.allow_recurrent_event')")
|
||||
|
||||
v-switch.mt-1(v-if='allow_recurrent_event'
|
||||
v-model='recurrent_event_visible'
|
||||
inset
|
||||
:label="$t('admin.recurrent_event_visible')")
|
||||
|
||||
</template>
|
||||
<script>
|
||||
|
@ -55,11 +66,9 @@ export default {
|
|||
name: 'Settings',
|
||||
data ({ $store }) {
|
||||
return {
|
||||
queryTz: '',
|
||||
title: $store.state.settings.title,
|
||||
description: $store.state.settings.description,
|
||||
locales,
|
||||
logoKey: 0
|
||||
locales: Object.keys(locales).map(locale => ({ value: locale, text: locales[locale] }))
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -90,10 +99,7 @@ export default {
|
|||
},
|
||||
filteredTimezones () {
|
||||
const current_timezone = moment.tz.guess()
|
||||
const query = this.queryTz.toLowerCase()
|
||||
const ret = _(moment.tz.names())
|
||||
.filter(tz => tz !== current_timezone && (!query || tz.toLowerCase().includes(query)))
|
||||
.take(10)
|
||||
.unshift(current_timezone)
|
||||
.map(tz => ({ value: tz, offset: moment().tz(tz).format('z Z') }))
|
||||
.value()
|
||||
|
@ -102,10 +108,6 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
...mapActions(['setSetting']),
|
||||
forceLogoReload () {
|
||||
this.$refs.upload.clearFiles()
|
||||
this.logoKey++
|
||||
},
|
||||
save (key, value) {
|
||||
if (this.settings[key] !== value) {
|
||||
this.setSetting({ key, value })
|
||||
|
|
178
components/admin/Theme.vue
Normal file
|
@ -0,0 +1,178 @@
|
|||
<template lang="pug">
|
||||
v-container
|
||||
v-card-title {{$t('common.theme')}}
|
||||
v-card-text
|
||||
//- LOGO
|
||||
v-file-input.mt-5(ref='upload'
|
||||
:label="$t('admin.favicon')"
|
||||
@change='uploadLogo'
|
||||
accept='image/*')
|
||||
template(slot='append-outer')
|
||||
v-btn(color='warning' text @click='resetLogo') <v-icon>mdi-restore</v-icon> {{$t('common.reset')}}
|
||||
v-img(:src='`${settings.baseurl}/favicon.ico?${logoKey}`'
|
||||
max-width="100px" max-height="80px" contain)
|
||||
|
||||
//- v-switch.mt-5(v-model='is_dark'
|
||||
//- inset
|
||||
//- :label="$t('admin.is_dark')")
|
||||
|
||||
//- TODO choose theme colors
|
||||
//- v-row
|
||||
//- v-col(v-for='(color, i) in colors' :key='i')
|
||||
//- v-menu(v-model='menu[i]'
|
||||
//- :close-on-content-click="false"
|
||||
//- transition="slide-x-transition"
|
||||
//- offset-y
|
||||
//- absolute
|
||||
//- bottom
|
||||
//- max-width="290px"
|
||||
//- min-width="290px")
|
||||
//- template(v-slot:activator='{ on }')
|
||||
//- v-btn(:color='i' small
|
||||
//- v-on='on') {{i}}
|
||||
//- v-color-picker(light @update:color='c => updateColor(i, c)')
|
||||
|
||||
v-dialog(v-model='linkModal' width='500')
|
||||
v-card
|
||||
v-card-title {{$t('admin.footer_links')}}
|
||||
v-card-text
|
||||
v-form(v-model='valid' ref='linkModalForm')
|
||||
v-text-field(v-model='link.label'
|
||||
:rules="[$validators.required('common.label')]"
|
||||
label='Label')
|
||||
v-text-field(v-model='link.href'
|
||||
:rules="[$validators.required('common.url')]"
|
||||
:label="$t('common.url')")
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(link @click='linkModal=false' color='error') {{$t('common.cancel')}}
|
||||
v-btn(link @click='addFooterLink' color='primary' :disabled='!valid') {{$t('common.add')}}
|
||||
|
||||
v-card-title {{$t('admin.footer_links')}}
|
||||
v-card-text
|
||||
v-btn(color='primary' text @click='openLinkModal') <v-icon>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-list.mt-1(two-line subheader)
|
||||
v-list-item(v-for='link in settings.footerLinks'
|
||||
:key='`${link.label}`' @click='editFooterLink(link)')
|
||||
v-list-item-content
|
||||
v-list-item-title {{link.label}}
|
||||
v-list-item-subtitle {{link.href}}
|
||||
v-list-item-action
|
||||
//- v-btn.float-right(icon color='accent' @click='editFooterLink(link)')
|
||||
//- v-icon mdi-pencil
|
||||
v-btn(icon color='error' @click.stop='removeFooterLink(link)')
|
||||
v-icon mdi-delete-forever
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import { mapActions, mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'Theme',
|
||||
data () {
|
||||
return {
|
||||
valid: false,
|
||||
logoKey: 0,
|
||||
link: { href: '', label: '' },
|
||||
linkModal: false
|
||||
// menu: [false, false, false, false]
|
||||
// colors: { primary: '', secondary: '', accent: '', error: '', info: '', success: '', warning: '' }
|
||||
// primary: {},
|
||||
// secondary: {}
|
||||
// }
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['settings']),
|
||||
// 'colors.primary': this.color('primary'),
|
||||
// 'colors.secondary': this.color('primary'),
|
||||
// 'colors.tertiary': this.color('primary'),
|
||||
is_dark: {
|
||||
get () { return this.settings['theme.is_dark'] },
|
||||
set (value) {
|
||||
this.$vuetify.theme.dark = value
|
||||
this.setSetting({ key: 'theme.is_dark', value })
|
||||
}
|
||||
}
|
||||
// 'colors[0]': {
|
||||
// get () {
|
||||
// return this.settings['theme.colors'] || [0, 0]
|
||||
// },
|
||||
// set (value) {
|
||||
// console.error(value)
|
||||
// if (!value) { return }
|
||||
// this.setSetting({ key: 'theme.primary', value })
|
||||
// if (this.settings['theme.is_dark']) {
|
||||
// this.$vuetify.theme.themes.dark.primary = value
|
||||
// } else {
|
||||
// this.$vuetify.theme.themes.light.primary = value
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['setSetting']),
|
||||
reset () {
|
||||
this.setSetting({
|
||||
key: 'footerLinks',
|
||||
value: [
|
||||
{ href: '/about', label: 'about' },
|
||||
{ href: '/', label: 'home' }]
|
||||
})
|
||||
},
|
||||
forceLogoReload () {
|
||||
this.logoKey++
|
||||
},
|
||||
resetLogo (e) {
|
||||
this.setSetting({ key: 'logo', value: null })
|
||||
.then(this.forceLogoReload)
|
||||
e.stopPropagation()
|
||||
},
|
||||
updateColor (i, v) {
|
||||
this.colors[i] = v.hex
|
||||
this.$vuetify.theme.themes.dark[i] = v.hex
|
||||
},
|
||||
openLinkModal () {
|
||||
// this.link = { href: '', label: '' }
|
||||
this.linkModal = true
|
||||
this.$nextTick(() => this.$refs.linkModalForm.reset())
|
||||
},
|
||||
addFooterLink () {
|
||||
const link = Object.assign({}, this.link)
|
||||
this.setSetting({ key: 'footerLinks', value: this.settings.footerLinks.concat(link) })
|
||||
// this.link = { href: '', label: '' }
|
||||
this.$refs.linkModalForm.reset()
|
||||
this.linkModal = false
|
||||
},
|
||||
async removeFooterLink (item) {
|
||||
const ret = await this.$root.$confirm('admin.delete_footer_link_confirm')
|
||||
if (!ret) { return }
|
||||
const footerLinks = this.settings.footerLinks.filter(l => l.label !== item.label)
|
||||
this.setSetting({ key: 'footerLinks', value: footerLinks })
|
||||
},
|
||||
editFooterLink (item) {
|
||||
this.link = { href: item.href, label: item.label }
|
||||
this.linkModal = true
|
||||
},
|
||||
async uploadLogo (file) {
|
||||
const formData = new FormData()
|
||||
formData.append('logo', file)
|
||||
try {
|
||||
await this.$axios.$post('/settings/logo', formData)
|
||||
this.$root.$emit('message', {
|
||||
message: 'Logo updated'
|
||||
})
|
||||
this.forceLogoReload()
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
},
|
||||
save (key, value) {
|
||||
if (this.settings[key] !== value) {
|
||||
this.setSetting({ key, value })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,87 +1,90 @@
|
|||
<template lang="pug">
|
||||
div
|
||||
//- ADD NEW USER
|
||||
el-collapse
|
||||
el-collapse-item
|
||||
template(slot='title')
|
||||
el-button(type='text' mini size='mini') <v-icon name='plus'/> {{$t('common.new_user')}}
|
||||
el-form(inline @submit.native.prevent='create_user')
|
||||
el-form-item(:label="$t('common.email')")
|
||||
el-input(v-model='new_user.email')
|
||||
el-form-item(:label="$t('common.admin')")
|
||||
el-switch(v-model='new_user.is_admin')
|
||||
el-button.float-right(@click='create_user' type='success' plain) {{$t('common.send')}}
|
||||
el-alert.mb-1(type='info' show-icon :closable='false') {{$t('admin.user_add_help')}}
|
||||
v-container
|
||||
v-card-title {{$t('common.users')}}
|
||||
v-spacer
|
||||
v-text-field(v-model='search'
|
||||
append-icon='mdi-magnify' outlined rounded
|
||||
label='Search'
|
||||
single-line hide-details)
|
||||
|
||||
//- USERS LIST
|
||||
el-table(:data='paginatedUsers' small)
|
||||
el-table-column(label='Email' width='220')
|
||||
template(slot-scope='data')
|
||||
el-popover(trigger='hover' :content='data.row.description' width='400')
|
||||
span(slot='reference') {{data.row.email}}
|
||||
el-table-column(:label="$t('common.actions')")
|
||||
template(slot-scope='data')
|
||||
div(v-if='data.row.id!==$auth.user.id')
|
||||
el-button-group
|
||||
el-button(size='mini'
|
||||
:type='data.row.is_active?"warning":"success"'
|
||||
@click='toggle(data.row)') {{data.row.is_active?$t('common.deactivate'):$t('common.activate')}}
|
||||
el-button(size='mini'
|
||||
:type='data.row.is_admin?"danger":"warning"'
|
||||
@click='toggleAdmin(data.row)') {{data.row.is_admin?$t('admin.remove_admin'):$t('common.admin')}}
|
||||
el-button(size='mini'
|
||||
type='danger'
|
||||
@click='delete_user(data.row)') {{$t('admin.delete_user')}}
|
||||
div(v-else)
|
||||
span {{$t('common.me')}}
|
||||
client-only
|
||||
el-pagination(:page-size='perPage' :currentPage.sync='userPage' v-if='perPage<users_.length' :total='users_.length')
|
||||
v-btn(color='primary' text @click='newUserDialog = true') <v-icon>mdi-plus</v-icon> {{$t('common.new_user')}}
|
||||
|
||||
//- ADD NEW USER
|
||||
v-dialog(v-model='newUserDialog' :fullscreen="$vuetify.breakpoint.xsOnly")
|
||||
|
||||
v-card(color='secondary')
|
||||
v-card-title {{$t('common.new_user')}}
|
||||
v-card-text
|
||||
v-form(v-model='valid' ref='user_form' lazy-validation @submit.prevent='createUser')
|
||||
v-text-field(v-model='new_user.email'
|
||||
:label="$t('common.email')"
|
||||
:rules="$validators.email")
|
||||
v-switch(v-model='new_user.is_admin' :label="$t('common.admin')" inset)
|
||||
v-alert(type='info' :closable='false') {{$t('admin.user_add_help')}}
|
||||
v-card-actions
|
||||
v-spacer
|
||||
v-btn(@click='newUserDialog=false' color='error') {{$t('common.cancel')}}
|
||||
v-btn(@click='createUser' :disabled='!valid' color='primary') {{$t('common.send')}}
|
||||
|
||||
v-card-text
|
||||
//- USERS LIST
|
||||
v-data-table(
|
||||
:headers='headers'
|
||||
:items='users'
|
||||
:hide-default-footer='users.length<5'
|
||||
:search='search')
|
||||
template(v-slot:item.is_active='{item}')
|
||||
v-icon(v-if='item.is_active' color='success') mdi-check
|
||||
v-icon(v-else color='warning') mdi-close
|
||||
template(v-slot:item.actions='{item}')
|
||||
v-btn(text small @click='toggle(item)'
|
||||
:color='item.is_active?"warning":"success"') {{item.is_active?$t('common.disable'):$t('common.enable')}}
|
||||
v-btn(text small @click='toggleAdmin(item)'
|
||||
:color='item.is_admin?"warning":"error"') {{item.is_admin?$t('common.remove_admin'):$t('common.admin')}}
|
||||
v-btn(text small @click='deleteUser(item)'
|
||||
color='error') {{$t('admin.delete_user')}}
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import { Message, MessageBox } from 'element-ui'
|
||||
import { mapState } from 'vuex'
|
||||
import get from 'lodash/get'
|
||||
|
||||
export default {
|
||||
name: 'Users',
|
||||
props: ['users'],
|
||||
props: {
|
||||
users: { type: Array, default: () => [] }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
perPage: 10,
|
||||
userPage: 1,
|
||||
newUserDialog: false,
|
||||
valid: false,
|
||||
new_user: {
|
||||
email: '',
|
||||
is_admin: false
|
||||
},
|
||||
users_: this.users
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['settings']),
|
||||
paginatedUsers () {
|
||||
return this.users_.slice((this.userPage - 1) * this.perPage,
|
||||
this.userPage * this.perPage)
|
||||
search: '',
|
||||
headers: [
|
||||
{ value: 'email', text: 'Email' },
|
||||
{ value: 'description', text: 'Description' },
|
||||
{ value: 'is_active', text: 'Active' },
|
||||
{ value: 'actions', text: 'Actions', align: 'right' }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: mapState(['settings']),
|
||||
methods: {
|
||||
delete_user (user) {
|
||||
MessageBox.confirm(this.$t('admin.delete_user_confirm'),
|
||||
this.$t('common.confirm'), {
|
||||
confirmButtonText: this.$t('common.ok'),
|
||||
cancelButtonText: this.$t('common.cancel'),
|
||||
type: 'error'
|
||||
})
|
||||
.then(() => this.$axios.delete(`/user/${user.id}`))
|
||||
.then(() => {
|
||||
Message({
|
||||
showClose: true,
|
||||
type: 'success',
|
||||
message: this.$t('admin.user_remove_ok')
|
||||
})
|
||||
this.users_ = this.users_.filter(u => u.id !== user.id)
|
||||
})
|
||||
async deleteUser (user) {
|
||||
const ret = await this.$root.$confirm('admin.delete_user_confirm', { user: user.email })
|
||||
if (!ret) { return }
|
||||
await this.$axios.delete(`/user/${user.id}`)
|
||||
this.$root.$message('admin.user_remove_ok')
|
||||
this.users_ = this.users_.filter(u => u.id !== user.id)
|
||||
},
|
||||
toggle (user) {
|
||||
async toggle (user) {
|
||||
if (user.is_active) {
|
||||
const ret = await this.$root.$confirm('admin.disable_user_confirm', { user: user.email })
|
||||
if (!ret) { return }
|
||||
}
|
||||
user.is_active = !user.is_active
|
||||
this.$axios.$put('/user', user)
|
||||
},
|
||||
|
@ -90,26 +93,22 @@ export default {
|
|||
user.is_admin = !user.is_admin
|
||||
await this.$axios.$put('/user', user)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
},
|
||||
async create_user () {
|
||||
async createUser () {
|
||||
if (!this.$refs.user_form.validate()) { return }
|
||||
try {
|
||||
this.loading = true
|
||||
const user = await this.$axios.$post('/user', this.new_user)
|
||||
await this.$axios.$post('/user', this.new_user)
|
||||
this.new_user = { email: '', is_admin: false }
|
||||
Message({
|
||||
showClose: true,
|
||||
type: 'success',
|
||||
message: this.$t('admin.user_create_ok')
|
||||
})
|
||||
this.users_.push(user)
|
||||
this.$root.$message('admin.user_create_ok', { color: 'success' })
|
||||
this.$emit('update')
|
||||
this.loading = false
|
||||
this.newUserDialog = false
|
||||
} catch (e) {
|
||||
Message({
|
||||
showClose: true,
|
||||
type: 'error',
|
||||
message: this.$t(e)
|
||||
})
|
||||
const err = get(e, 'response.data.errors[0].message', e)
|
||||
this.$root.$message(this.$t(err), { color: 'error' })
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
"host": "localhost",
|
||||
"port": 13120
|
||||
},
|
||||
"log_level": "debug",
|
||||
"log_path": "./logs",
|
||||
"db": {
|
||||
"dialect": "sqlite",
|
||||
"storage": "./db.sqlite",
|
||||
|
@ -20,6 +22,5 @@
|
|||
"secure": true,
|
||||
"host": ""
|
||||
},
|
||||
"admin_email": "admin",
|
||||
"secret": "notsosecret"
|
||||
"admin_email": "admin"
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ POST
|
|||
**`/event`**
|
||||
|
||||
> info "info"
|
||||
> `Content-Type` has to be `multipart/form-data` 'cause support image upload
|
||||
> `Content-Type` has to be `multipart/form-data` to support image upload
|
||||
|
||||
|
||||
**Params**
|
||||
|
|
3
docs/.gitignore
vendored
|
@ -1,2 +1,5 @@
|
|||
_site
|
||||
.sass-cache
|
||||
.jekyll-cache
|
||||
.jekyll-metadata
|
||||
vendor
|
||||
|
|
|
@ -8,7 +8,7 @@ source "https://rubygems.org"
|
|||
#
|
||||
# This will help ensure the proper Jekyll version is running.
|
||||
# Happy Jekylling!
|
||||
gem "jekyll", "~> 3.8.6"
|
||||
gem "jekyll"
|
||||
|
||||
# This is the default theme for new Jekyll sites. You may change this to anything you like.
|
||||
#gem "minima", "~> 2.0"
|
||||
|
@ -21,9 +21,10 @@ gem "mini_magick"
|
|||
|
||||
# If you have any plugins, put them here!
|
||||
group :jekyll_plugins do
|
||||
gem "jekyll-feed", "~> 0.6"
|
||||
gem "jekyll-feed"
|
||||
gem "jemoji"
|
||||
gem "premonition", "~> 2.0.0"
|
||||
gem "premonition"
|
||||
gem "jekyll-default-layout"
|
||||
end
|
||||
|
||||
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
||||
|
|
|
@ -1,105 +1,117 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (5.2.3)
|
||||
activesupport (6.0.3.7)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.6.0)
|
||||
public_suffix (>= 2.0.2, < 4.0)
|
||||
zeitwerk (~> 2.2, >= 2.2.2)
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
colorator (1.1.0)
|
||||
concurrent-ruby (1.1.5)
|
||||
em-websocket (0.5.1)
|
||||
concurrent-ruby (1.1.8)
|
||||
em-websocket (0.5.2)
|
||||
eventmachine (>= 0.12.9)
|
||||
http_parser.rb (~> 0.6.0)
|
||||
eventmachine (1.2.7)
|
||||
ffi (1.11.1)
|
||||
ffi (1.15.1)
|
||||
forwardable-extended (2.6.0)
|
||||
gemoji (3.0.1)
|
||||
html-pipeline (2.11.0)
|
||||
html-pipeline (2.14.0)
|
||||
activesupport (>= 2)
|
||||
nokogiri (>= 1.4)
|
||||
http_parser.rb (0.6.0)
|
||||
i18n (0.9.5)
|
||||
i18n (1.8.10)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jekyll (3.8.6)
|
||||
jekyll (4.2.0)
|
||||
addressable (~> 2.4)
|
||||
colorator (~> 1.0)
|
||||
em-websocket (~> 0.5)
|
||||
i18n (~> 0.7)
|
||||
jekyll-sass-converter (~> 1.0)
|
||||
i18n (~> 1.0)
|
||||
jekyll-sass-converter (~> 2.0)
|
||||
jekyll-watch (~> 2.0)
|
||||
kramdown (~> 1.14)
|
||||
kramdown (~> 2.3)
|
||||
kramdown-parser-gfm (~> 1.0)
|
||||
liquid (~> 4.0)
|
||||
mercenary (~> 0.3.3)
|
||||
mercenary (~> 0.4.0)
|
||||
pathutil (~> 0.9)
|
||||
rouge (>= 1.7, < 4)
|
||||
rouge (~> 3.0)
|
||||
safe_yaml (~> 1.0)
|
||||
jekyll-feed (0.12.1)
|
||||
terminal-table (~> 2.0)
|
||||
jekyll-default-layout (0.1.5)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
jekyll-feed (0.15.1)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-sass-converter (1.5.2)
|
||||
sass (~> 3.4)
|
||||
jekyll-seo-tag (2.6.1)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-sass-converter (2.1.0)
|
||||
sassc (> 2.0.1, < 3.0)
|
||||
jekyll-seo-tag (2.7.1)
|
||||
jekyll (>= 3.8, < 5.0)
|
||||
jekyll-watch (2.2.1)
|
||||
listen (~> 3.0)
|
||||
jemoji (0.11.0)
|
||||
jemoji (0.12.0)
|
||||
gemoji (~> 3.0)
|
||||
html-pipeline (~> 2.2)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
just-the-docs (0.2.5)
|
||||
jekyll (~> 3.8.5)
|
||||
just-the-docs (0.3.3)
|
||||
jekyll (>= 3.8.5)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
rake (~> 12.3.1)
|
||||
kramdown (1.17.0)
|
||||
rake (>= 12.3.1, < 13.1.0)
|
||||
kramdown (2.3.1)
|
||||
rexml
|
||||
kramdown-parser-gfm (1.1.0)
|
||||
kramdown (~> 2.0)
|
||||
liquid (4.0.3)
|
||||
listen (3.1.5)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
ruby_dep (~> 1.2)
|
||||
mercenary (0.3.6)
|
||||
mini_magick (4.9.5)
|
||||
mini_portile2 (2.4.0)
|
||||
minitest (5.11.3)
|
||||
nokogiri (1.10.4)
|
||||
mini_portile2 (~> 2.4.0)
|
||||
listen (3.5.1)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
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)
|
||||
racc (~> 1.4)
|
||||
pathutil (0.16.2)
|
||||
forwardable-extended (~> 2.6)
|
||||
premonition (2.0.0)
|
||||
public_suffix (3.1.1)
|
||||
rake (12.3.1)
|
||||
rb-fsevent (0.10.3)
|
||||
rb-inotify (0.10.0)
|
||||
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)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
rouge (3.6.0)
|
||||
ruby_dep (1.5.0)
|
||||
rexml (3.2.5)
|
||||
rouge (3.26.0)
|
||||
safe_yaml (1.0.5)
|
||||
sass (3.7.4)
|
||||
sass-listen (~> 4.0.0)
|
||||
sass-listen (4.0.0)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
sassc (2.4.0)
|
||||
ffi (~> 1.9)
|
||||
terminal-table (2.0.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
thread_safe (0.3.6)
|
||||
tzinfo (1.2.5)
|
||||
tzinfo (1.2.9)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo-data (1.2019.2)
|
||||
tzinfo-data (1.2021.1)
|
||||
tzinfo (>= 1.0.0)
|
||||
unicode-display_width (1.7.0)
|
||||
wdm (0.1.1)
|
||||
zeitwerk (2.4.2)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
jekyll (~> 3.8.6)
|
||||
jekyll-feed (~> 0.6)
|
||||
jekyll
|
||||
jekyll-default-layout
|
||||
jekyll-feed
|
||||
jemoji
|
||||
just-the-docs
|
||||
mini_magick
|
||||
premonition (~> 2.0.0)
|
||||
premonition
|
||||
tzinfo (~> 1.2)
|
||||
tzinfo-data
|
||||
wdm (~> 0.1.0)
|
||||
|
||||
BUNDLED WITH
|
||||
2.0.2
|
||||
2.1.4
|
||||
|
|
|
@ -17,7 +17,7 @@ title: Gancio
|
|||
email: lesion@autistici.org
|
||||
description: >- # this means to ignore newlines until "baseurl:"
|
||||
A shared agenda for local communities with AP support
|
||||
baseurl: "/" # the subpath of your site, e.g. /blog
|
||||
baseurl: "/next" # the subpath of your site, e.g. /blog
|
||||
url: "https://gancio.org" # the base hostname & protocol for your site, e.g. http://example.com
|
||||
#twitter_username: jekyllrb
|
||||
#github_username: jekyll
|
||||
|
@ -29,6 +29,7 @@ plugins:
|
|||
- jekyll-feed
|
||||
- jemoji
|
||||
- premonition
|
||||
- jekyll-default-layout
|
||||
|
||||
search_enabled: true
|
||||
|
||||
|
@ -37,11 +38,15 @@ aux_links:
|
|||
- https://blog.gancio.org
|
||||
"Source":
|
||||
- https://framagit.org/les/gancio
|
||||
"Forum":
|
||||
- https://socialhub.activitypub.rocks/c/software/gancio
|
||||
"Mastodon":
|
||||
- https://mastodon.cisti.org/@gancio
|
||||
|
||||
gh_edit_link: true # show or hide edit this page link
|
||||
gh_edit_link_text: "Edit this page"
|
||||
gh_edit_repository: "https://framagit.org/les/gancio" # the github URL for your repo
|
||||
gh_edit_branch: "master/docs" # the branch that your docs is served from
|
||||
# gh_edit_source: docs # the source that your files originate from
|
||||
gh_edit_view_mode: "-/tree" # "tree" or "edit" if you want the user to jump into the editor immediately
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -34,11 +34,11 @@ and use the calendar to select one or more days.
|
|||
|
||||
## How does it work
|
||||
|
||||
Behind the scene, gancio ensures that at least the next three occurrences
|
||||
of the event are always created. It creates that single events by copying
|
||||
Behind the scene, gancio ensures that at least the next occurrence
|
||||
of the event are created. It creates that single event by copying
|
||||
the properties of the parent event, so if you modify the parent
|
||||
event's title, or the day of the week, each newly created occurrence will took
|
||||
the new title and the new selected day. Old occurrences will be preserved.
|
||||
the new title and the new selected day while old occurrences will be preserved.
|
||||
|
||||
You can edit a specific event occurrence with more details, a different
|
||||
poster/flyer, a different title/description or decide to completely hide it.
|
|
@ -2,9 +2,10 @@
|
|||
layout: default
|
||||
title: Usage
|
||||
permalink: /usage
|
||||
nav_order: 4
|
||||
nav_order: 1
|
||||
has_children: true
|
||||
---
|
||||
|
||||
# Usage
|
||||
|
||||
ehmmm, help needed here :smile: feel free to send a PR => [here](https://framagit.org/les/gancio/tree/master/docs)
|
||||
ehmmm, help needed here :smile: feel free to send a PR => [here](https://framagit.org/les/gancio/tree/master/docs)
|
||||
|
|
72
docs/_includes/assets/js/zzzz-search-data.json
Normal file
|
@ -0,0 +1,72 @@
|
|||
---
|
||||
permalink: /assets/js/search-data.json
|
||||
---
|
||||
{
|
||||
{%- assign i = 0 -%}
|
||||
{%- assign pages_array = | split: -%}
|
||||
{%- assign pages_array = pages_array | push: site.html_pages -%}
|
||||
{%- if site.just_the_docs.collections -%}
|
||||
{%- for collection_entry in site.just_the_docs.collections -%}
|
||||
{%- assign collection_key = collection_entry[0] -%}
|
||||
{%- assign collection_value = collection_entry[1] -%}
|
||||
{%- assign collection = site[collection_key] -%}
|
||||
{%- if collection_value.search_exclude != true -%}
|
||||
{%- assign pages_array = pages_array | push: collection -%}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{%- endif -%}
|
||||
{%- for pages in pages_array -%}
|
||||
{%- for page in pages -%}
|
||||
{%- if page.title and page.search_exclude != true -%}
|
||||
{%- assign page_content = page.content -%}
|
||||
{%- assign heading_level = site.search.heading_level | default: 2 -%}
|
||||
{%- for j in (2..heading_level) -%}
|
||||
{%- assign tag = '<h' | append: j -%}
|
||||
{%- assign closing_tag = '</h' | append: j -%}
|
||||
{%- assign page_content = page_content | replace: tag, '<h1' | replace: closing_tag, '</h1' -%}
|
||||
{%- endfor -%}
|
||||
{%- assign parts = page_content | split: '<h1' -%}
|
||||
{%- assign title_found = false -%}
|
||||
{%- for part in parts offset: 1 -%}
|
||||
{%- assign titleAndContent = part | split: '</h1>' -%}
|
||||
{%- assign title = titleAndContent[0] | replace_first: '>', '<h1>' | split: '<h1>' -%}
|
||||
{%- assign title = title[1] | strip_html -%}
|
||||
{%- assign content = titleAndContent[1] -%}
|
||||
{%- assign url = page.url -%}
|
||||
{%- if title == page.title and parts[0] == '' -%}
|
||||
{%- assign title_found = true -%}
|
||||
{%- else -%}
|
||||
{%- assign id = titleAndContent[0] -%}
|
||||
{%- assign id = id | split: 'id="' -%}
|
||||
{%- if id.size == 2 -%}
|
||||
{%- assign id = id[1] -%}
|
||||
{%- assign id = id | split: '"' -%}
|
||||
{%- assign id = id[0] -%}
|
||||
{%- capture url -%}{{ url | append: '#' | append: id }}{%- endcapture -%}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{%- unless i == 0 -%},{%- endunless -%}
|
||||
"{{ i }}": {
|
||||
"doc": {{ page.title | jsonify }},
|
||||
"title": {{ title | jsonify }},
|
||||
"content": {{ content | replace: '</h', ' . </h' | replace: '<hr', ' . <hr' | replace: '</p', ' . </p' | replace: '<ul', ' . <ul' | replace: '</ul', ' . </ul' | replace: '<ol', ' . <ol' | replace: '</ol', ' . </ol' | replace: '</tr', ' . </tr' | replace: '<li', ' | <li' | replace: '</li', ' | </li' | replace: '</td', ' | </td' | replace: '<td', ' | <td' | replace: '</th', ' | </th' | replace: '<th', ' | <th' | strip_html | remove: 'Table of contents' | normalize_whitespace | replace: '. . .', '.' | replace: '. .', '.' | replace: '| |', '|' | append: ' ' | jsonify }},
|
||||
"url": "{{ url | relative_url }}",
|
||||
"relUrl": "{{ url }}"
|
||||
}
|
||||
{%- assign i = i | plus: 1 -%}
|
||||
{%- endfor -%}
|
||||
{%- unless title_found -%}
|
||||
{%- unless i == 0 -%},{%- endunless -%}
|
||||
"{{ i }}": {
|
||||
"doc": {{ page.title | jsonify }},
|
||||
"title": {{ page.title | jsonify }},
|
||||
"content": {{ parts[0] | replace: '</h', ' . </h' | replace: '<hr', ' . <hr' | replace: '</p', ' . </p' | replace: '<ul', ' . <ul' | replace: '</ul', ' . </ul' | replace: '<ol', ' . <ol' | replace: '</ol', ' . </ol' | replace: '</tr', ' . </tr' | replace: '<li', ' | <li' | replace: '</li', ' | </li' | replace: '</td', ' | </td' | replace: '<td', ' | <td' | replace: '</th', ' | </th' | replace: '<th', ' | <th' | strip_html | remove: 'Table of contents' | normalize_whitespace | replace: '. . .', '.' | replace: '. .', '.' | replace: '| |', '|' | append: ' ' | jsonify }},
|
||||
"url": "{{ page.url | relative_url }}",
|
||||
"relUrl": "{{ page.url }}"
|
||||
}
|
||||
{%- assign i = i | plus: 1 -%}
|
||||
{%- endunless -%}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{%- endfor %}
|
||||
}
|
|
@ -1,30 +1,21 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
|
||||
<!DOCTYPE html> <html lang="en-US">
|
||||
<head> <meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
|
||||
<title>{{ page.title }} - {{ site.title }}</title>
|
||||
|
||||
{% if page.description %}
|
||||
<meta name="Description" content="{{ page.description }}">
|
||||
{% endif %}
|
||||
|
||||
<title>{{ page.title }} - {{ site.title }}</title>
|
||||
|
||||
{% if page.description %}
|
||||
<meta name="Description" content="{{ page.description }}">
|
||||
{% endif %}
|
||||
|
||||
<link rel="shortcut icon" href="{{ "favicon.ico" | absolute_url }}" type="image/x-icon">
|
||||
<link rel="stylesheet" href="{{ "/assets/css/just-the-docs.css" | absolute_url }}">
|
||||
<link rel="stylesheet" href="{{ "/assets/css/style.css" | absolute_url }}">
|
||||
<link rel="stylesheet" href="{{ "/assets/css/premonition.css" | absolute_url }}">
|
||||
<link rel="stylesheet" href="{{ "/assets/css/fa.min.css" | absolute_url }}">
|
||||
|
||||
{% if site.search_enabled != nil %}
|
||||
<script type="text/javascript" src="{{ "/assets/js/vendor/lunr.min.js" | absolute_url }}"></script>
|
||||
{% endif %}
|
||||
|
||||
<script type="text/javascript" src="{{ "/assets/js/just-the-docs.js" | absolute_url }}"></script>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="{{ "/assets/js/jquery-3.3.1.min.js" | absolute_url }}"></script>
|
||||
<link rel="stylesheet" href="{{ "/assets/css/jquery.fancybox.min.css" | absolute_url }}"/>
|
||||
<script src="{{ "/assets/js/jquery.fancybox.min.js" | absolute_url }}"></script>
|
||||
<link rel="shortcut icon" href="{{ '/favicon.ico' | absolute_url }}" type="image/x-icon">
|
||||
<link rel="stylesheet" href="{{ '/assets/css/just-the-docs-default.css' | absolute_url }}">
|
||||
<link rel="stylesheet" href="{{ '/assets/css/premonition.css' | absolute_url }}">
|
||||
<script type="text/javascript" src="{{ '/assets/js/vendor/lunr.min.js' | absolute_url }}"></script>
|
||||
<script type="text/javascript" src="{{ '/assets/js/just-the-docs.js' | absolute_url }}"></script>
|
||||
<script src="{{ '/assets/js/jquery-3.3.1.min.js' | absolute_url }}"></script>
|
||||
<link rel="stylesheet" href="{{ '/assets/css/jquery.fancybox.min.css' | absolute_url }}">
|
||||
<script src="{{ '/assets/js/jquery.fancybox.min.js' | absolute_url }}"></script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
{% seo %}
|
||||
</head>
|
||||
</head>
|
||||
|
|
168
docs/_sass/custom/custom.scss
Normal file
|
@ -0,0 +1,168 @@
|
|||
$default-color: #5bc0de;
|
||||
$default-light-color: #e3edf2;
|
||||
$info-color: #50af51;
|
||||
$info-light-color: #f3f8f3;
|
||||
$warning-color: #f0ad4e;
|
||||
$warning-light-color: #fcf8f2;
|
||||
$error-color: #d9534f;
|
||||
$error-light-color: #fdf7f7;
|
||||
$content-color: rgba(0, 0, 0, 0.5);
|
||||
$citation-color: #495057;
|
||||
$citation-light-color: #f8f9fa;
|
||||
|
||||
$svg-default-color: "5bc0de";
|
||||
$svg-info-color: "50af51";
|
||||
$svg-warning-color: "f0ad4e";
|
||||
$svg-error-color: "d9534f";
|
||||
$svg-citation-color: "495057";
|
||||
|
||||
.nav-list .nav-list-item {
|
||||
font-size: 18px !important;
|
||||
padding: 4px 0px 0px 0px !important;
|
||||
}
|
||||
|
||||
.main {
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
.premonition {
|
||||
display: grid;
|
||||
grid-template-columns: 43px auto;
|
||||
padding-top: 13px;
|
||||
padding-bottom: 13px;
|
||||
margin: 30px 0 30px 0;
|
||||
background-color: $default-light-color;
|
||||
border-left: 4px solid $default-color;
|
||||
color: $default-color;
|
||||
|
||||
code {
|
||||
background-color: #fff;
|
||||
color: $default-color;
|
||||
}
|
||||
|
||||
.header {
|
||||
font-weight: 500;
|
||||
font-size: 1.1rem;
|
||||
color: $default-color;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.content {
|
||||
color: $content-color;
|
||||
padding-left: 20px;
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@mixin box-type($c, $lc) {
|
||||
background-color: $lc;
|
||||
color: $c;
|
||||
border-color: $c;
|
||||
|
||||
a {
|
||||
color: $c;
|
||||
text-decoration: underline;
|
||||
}
|
||||
code {
|
||||
color: $c;
|
||||
}
|
||||
.header {
|
||||
color: $c;
|
||||
}
|
||||
}
|
||||
|
||||
&.info {
|
||||
@include box-type($info-color, $info-light-color);
|
||||
}
|
||||
&.warning {
|
||||
@include box-type($warning-color, $warning-light-color);
|
||||
}
|
||||
&.error {
|
||||
@include box-type($error-color, $error-light-color);
|
||||
}
|
||||
&.citation {
|
||||
@include box-type($citation-color, $citation-light-color);
|
||||
|
||||
blockquote {
|
||||
border-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.fa,
|
||||
.fas,
|
||||
.far,
|
||||
.fal,
|
||||
.fab {
|
||||
font-size: 28px;
|
||||
opacity: 0.3;
|
||||
padding-top: 2px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
& > svg {
|
||||
opacity: 0.6;
|
||||
margin-top: 0.36rem;
|
||||
margin-left: 0.7rem;
|
||||
}
|
||||
|
||||
@mixin pn-icon($pre, $color, $post) {
|
||||
border: 0;
|
||||
margin: 3px 0 0 14px;
|
||||
background-repeat: no-repeat;
|
||||
background-color: transparent;
|
||||
background-image: url($pre + "%23" + $color + $post);
|
||||
background-size: 28px 28px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
/* Autogenerated code */
|
||||
&.pn-note {
|
||||
@include pn-icon(
|
||||
"data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='165px' height='165px' viewBox='0 0 165 165' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Cg id='Page-1' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E%3Cpath d='M40,0.5 C18.1847524,0.5 0.5,18.1847524 0.5,40 L0.5,125 C0.5,146.815248 18.1847524,164.5 40,164.5 L125,164.5 C146.815248,164.5 164.5,146.815248 164.5,125 L164.5,40 C164.5,18.1847524 146.815248,0.5 125,0.5 L40,0.5 Z M73.9421225,101.035652 C105.680247,64.0622419 122.973943,44.3076275 125.890221,41.6952841 C129.340278,37.6445263 135.770506,37.5263132 140.538208,40.8455453 C145.631474,44.3914319 146.755991,50.3287958 142.263833,56.0881327 C114.758351,89.0776641 89.30795,118.028061 81.5674939,125.633994 C76.464822,130.398827 70.5909248,130.398827 66.4355344,125.58961 C62.9024905,121.371642 58.9333122,116.710237 54.3854087,111.427565 C53.8350288,110.788264 53.2758998,110.139548 52.706934,109.480131 C49.8512069,106.170414 46.9143172,102.783286 43.1506474,98.4546038 C43.1657573,98.4719821 36.1709078,90.431646 34.3564576,88.341891 C27.8799723,80.882735 24.2336656,76.6160672 22.1013335,73.9633891 L22.095737,73.9562966 C15.4200148,65.3371074 30.5778334,52.1721209 38.5786063,60.512576 C48.9690719,71.5242952 60.7566779,85.0318321 73.9420929,101.035687 Z' id='Note' stroke='%23979797' fill='",
|
||||
$svg-default-color,
|
||||
"'%3E%3C/path%3E%3C/g%3E%3C/svg%3E"
|
||||
);
|
||||
}
|
||||
&.pn-info {
|
||||
@include pn-icon(
|
||||
"data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='165px' height='165px' viewBox='0 0 165 165' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Cg id='Page-1' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E%3Cpath d='M82.5,165 C36.9365081,165 0,128.063492 0,82.5 C0,36.9365081 36.9365081,0 82.5,0 C128.063492,0 165,36.9365081 165,82.5 C165,128.063492 128.063492,165 82.5,165 Z M71.3481445,44.7539062 C71.3481445,47.7402493 72.400136,50.2853899 74.5041504,52.3894043 C76.6081648,54.4934187 79.1533054,55.5454102 82.1396484,55.5454102 C85.0807439,55.5454102 87.603261,54.4934187 89.7072754,52.3894043 C91.8112898,50.2853899 92.8632812,47.7402493 92.8632812,44.7539062 C92.8632812,41.7675632 91.8112898,39.2224226 89.7072754,37.1184082 C87.603261,35.0143938 85.0807439,33.9624023 82.1396484,33.9624023 C79.1533054,33.9624023 76.6081648,35.0143938 74.5041504,37.1184082 C72.400136,39.2224226 71.3481445,41.7675632 71.3481445,44.7539062 Z M65.2397461,126.674316 L65.2397461,130 L98.3608398,130 L98.3608398,126.674316 C95.8722206,126.176593 94.1754603,125.497888 93.2705078,124.638184 C92.3655554,123.778479 91.9130859,121.83286 91.9130859,118.80127 L91.9130859,65.9975586 L65.2397461,65.9975586 L65.2397461,69.3911133 C68.0450987,69.8888371 69.9228468,70.6467234 70.8730469,71.6647949 C71.8232469,72.6828664 72.2983398,74.5945498 72.2983398,77.3999023 L72.2983398,118.258301 C72.2983398,121.425634 71.6196357,123.620111 70.262207,124.841797 C69.3572546,125.656254 67.6831177,126.267088 65.2397461,126.674316 Z' id='Info' fill='",
|
||||
$svg-info-color,
|
||||
"'%3E%3C/path%3E%3C/g%3E%3C/svg%3E"
|
||||
);
|
||||
}
|
||||
&.pn-warn {
|
||||
@include pn-icon(
|
||||
"data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='165px' height='165px' viewBox='0 0 165 165' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Cg id='Page-1' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E%3Cpath d='M82.5,165 C36.9365081,165 0,128.063492 0,82.5 C0,36.9365081 36.9365081,0 82.5,0 C128.063492,0 165,36.9365081 165,82.5 C165,128.063492 128.063492,165 82.5,165 Z M70.0629883,121.226562 C70.0629883,124.484391 71.2054736,127.255767 73.4904785,129.540771 C75.7754834,131.825776 78.5468587,132.968262 81.8046875,132.968262 C85.0625163,132.968262 87.8338916,131.825776 90.1188965,129.540771 C92.4039014,127.255767 93.5463867,124.484391 93.5463867,121.226562 C93.5463867,117.968734 92.4039014,115.197358 90.1188965,112.912354 C87.8338916,110.627349 85.0625163,109.484863 81.8046875,109.484863 C78.5468587,109.484863 75.7754834,110.627349 73.4904785,112.912354 C71.2054736,115.197358 70.0629883,117.968734 70.0629883,121.226562 Z M70.0629883,49.1474609 C70.0629883,50.8668706 70.4475873,53.2423351 71.2167969,56.2739258 C71.7145207,58.2195735 72.8230708,62.1108107 74.5424805,67.9477539 C75.8999091,72.5177637 76.804848,76.0696488 77.2573242,78.6035156 C77.7098004,81.1373825 78.5921158,87.7886831 79.9042969,98.5576172 L83.9086914,98.5576172 C84.8588915,90.0058166 85.5489074,84.3160135 85.9787598,81.4880371 C86.4086122,78.6600607 87.2117454,75.1194874 88.3881836,70.8662109 C90.3338313,63.8075819 91.6799279,58.7286125 92.4265137,55.6291504 C93.1730994,52.5296883 93.5463867,50.2107824 93.5463867,48.6723633 C93.5463867,43.7856201 92.3586545,40.278982 89.9831543,38.1523438 C87.6076541,36.0257055 84.8815258,34.9624023 81.8046875,34.9624023 C78.637354,34.9624023 75.8886021,36.0370172 73.5583496,38.1862793 C71.2280971,40.3355413 70.0629883,43.989232 70.0629883,49.1474609 Z' id='Warning' fill='",
|
||||
$svg-warning-color,
|
||||
"'%3E%3C/path%3E%3C/g%3E%3C/svg%3E"
|
||||
);
|
||||
}
|
||||
&.pn-error {
|
||||
@include pn-icon(
|
||||
"data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='165px' height='165px' viewBox='0 0 165 165' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Cg id='Page-1' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E%3Cpath d='M94.8695048,7.74207645 L163.217029,144.437125 C166.674878,151.352824 163.871738,159.762246 156.956039,163.220096 C155.012063,164.192084 152.86848,164.698115 150.695048,164.698115 L14,164.698115 C6.2680135,164.698115 -4.08562073e-14,158.430102 -4.26325641e-14,150.698115 C-4.26325641e-14,148.524684 0.506031285,146.381101 1.47801933,144.437125 L69.8255435,7.74207645 C73.283393,0.826377491 81.6928155,-1.97676336 88.6085145,1.48108612 C91.3178981,2.83577793 93.514813,5.03269282 94.8695048,7.74207645 Z M70.4105124,130.924678 C70.4105124,134.182506 71.5529978,136.953882 73.8380027,139.238887 C76.1230076,141.523892 78.8943829,142.666377 82.1522117,142.666377 C85.4100404,142.666377 88.1814157,141.523892 90.4664206,139.238887 C92.7514256,136.953882 93.8939109,134.182506 93.8939109,130.924678 C93.8939109,127.666849 92.7514256,124.895474 90.4664206,122.610469 C88.1814157,120.325464 85.4100404,119.182978 82.1522117,119.182978 C78.8943829,119.182978 76.1230076,120.325464 73.8380027,122.610469 C71.5529978,124.895474 70.4105124,127.666849 70.4105124,130.924678 Z M70.4105124,58.845576 C70.4105124,60.5649857 70.7951115,62.9404502 71.564321,65.9720409 C72.0620449,67.9176886 73.170595,71.8089258 74.8900046,77.645869 C76.2474333,82.2158788 77.1523722,85.7677639 77.6048484,88.3016307 C78.0573246,90.8354976 78.93964,97.4867982 80.251821,108.255732 L84.2562156,108.255732 C85.2064156,99.7039317 85.8964315,94.0141286 86.3262839,91.1861522 C86.7561363,88.3581758 87.5592696,84.8176025 88.7357078,80.564326 C90.6813555,73.505697 92.0274521,68.4267276 92.7740378,65.3272655 C93.5206236,62.2278034 93.8939109,59.9088975 93.8939109,58.3704784 C93.8939109,53.4837352 92.7061786,49.9770971 90.3306785,47.8504589 C87.9551783,45.7238206 85.22905,44.6605175 82.1522117,44.6605175 C78.9848781,44.6605175 76.2361263,45.7351324 73.9058738,47.8843944 C71.5756212,50.0336565 70.4105124,53.6873471 70.4105124,58.845576 Z' id='Error' fill='",
|
||||
$svg-error-color,
|
||||
"'%3E%3C/path%3E%3C/g%3E%3C/svg%3E"
|
||||
);
|
||||
}
|
||||
&.pn-quote {
|
||||
@include pn-icon(
|
||||
"data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='165px' height='165px' viewBox='0 0 165 165' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Cg id='Page-1' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E%3Cpath d='M104.546838,164.525333 C97.1871585,164.350607 90.6368822,160.915227 90.6512001,150.013018 C90.4479076,131.842639 90.4697154,98.303237 90.6512001,49.7828789 C91.9844555,2.36817118 138.064959,0.504907944 148.576644,0.0692731383 C152.479575,0.302510658 153.780675,2.21617827 154.578947,4.17356105 C155.831948,9.88458567 155.831948,17.6357453 154.578947,27.4270401 C153.93686,32.7057192 151.936092,35.3224781 148.576644,35.2773166 C143.472082,35.2236794 151.862467,35.2263624 140.927765,35.2773166 C128.559674,35.7091823 122.660334,39.3672244 122.615074,56.9085817 C122.635604,63.1213926 122.635604,71.5842998 122.615074,82.2973033 C138.48496,82.4101196 149.139584,82.4488979 154.578947,82.4136382 C159.435737,82.5353733 163.923774,84.3352392 164.565789,96.288498 C164.874062,119.857257 164.829662,136.387115 164.782895,150.013018 C164.664253,157.17723 161.233392,164.356416 151.753558,164.525333 C127.51005,164.615729 113.455097,164.525333 104.546838,164.525333 Z M14.0400451,164.45606 C6.68036548,164.281334 0.130089247,160.845954 0.144407166,149.943745 C-0.058885353,131.773366 -0.0370775896,98.2339638 0.144407166,49.7136058 C1.47766255,2.29889804 47.5581663,0.435634806 58.0698511,-9.9475983e-14 C61.9727821,0.233237519 63.2738816,2.14690514 64.0721544,4.10428791 C65.3251551,9.81531253 65.3251551,17.5664722 64.0721544,27.3577669 C63.4300669,32.6364461 61.4292991,35.2532049 58.0698511,35.2080434 C52.9652887,35.1544062 61.3556736,35.1570892 50.4209719,35.2080434 C38.0528815,35.6399092 32.153541,39.2979513 32.1082808,56.8393085 C32.1288111,63.0521194 32.1288111,71.5150266 32.1082808,82.2280302 C47.9781667,82.3408464 58.6327912,82.3796247 64.0721544,82.3443651 C68.9289443,82.4661002 73.4169814,84.265966 74.0589965,96.2192249 C74.367269,119.787984 74.3228688,136.317842 74.2761018,149.943745 C74.1574604,157.107957 70.7265987,164.287143 61.2467647,164.45606 C37.0032571,164.546456 22.9483044,164.45606 14.0400451,164.45606 Z' id='Quote' fill='",
|
||||
$svg-citation-color,
|
||||
"'%3E%3C/path%3E%3C/g%3E%3C/svg%3E"
|
||||
);
|
||||
}
|
||||
&.pn-square {
|
||||
@include pn-icon(
|
||||
"data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='165px' height='165px' viewBox='0 0 165 165' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Cg id='Page-1' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E%3Cpath d='M82.5,165 C36.9365081,165 0,128.063492 0,82.5 C0,36.9365081 36.9365081,0 82.5,0 C128.063492,0 165,36.9365081 165,82.5 C165,128.063492 128.063492,165 82.5,165 Z M115.5,99 C124.612698,99 132,91.3888407 132,82 C132,72.6111593 124.612698,65 115.5,65 C106.387302,65 99,72.6111593 99,82 C99,91.3888407 106.387302,99 115.5,99 Z M49.5,99 C58.6126984,99 66,91.3888407 66,82 C66,72.6111593 58.6126984,65 49.5,65 C40.3873016,65 33,72.6111593 33,82 C33,91.3888407 40.3873016,99 49.5,99 Z M66,114 L66,129 L99,129 L99,114 L66,114 Z' id='Default' fill='",
|
||||
$svg-default-color,
|
||||
"'%3E%3C/path%3E%3C/g%3E%3C/svg%3E"
|
||||
);
|
||||
}
|
||||
/* End of Autogenerated code */
|
||||
}
|
BIN
docs/assets/add.png
Normal file
After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 33 KiB |
|
@ -1,12 +0,0 @@
|
|||
.navigation-list-item {
|
||||
font-size: 18px !important;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
html, body {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.page-content h1:first-of-type {
|
||||
font-weight: 500;
|
||||
}
|
BIN
docs/assets/event.png
Normal file
After Width: | Height: | Size: 118 KiB |
BIN
docs/assets/federation.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
docs/assets/follow.png
Normal file
After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 255 KiB |
BIN
docs/assets/home_desktop.png
Normal file
After Width: | Height: | Size: 234 KiB |
72
docs/assets/js/zzzz-search-data.json
Normal file
|
@ -0,0 +1,72 @@
|
|||
---
|
||||
permalink: /assets/js/search-data.json
|
||||
---
|
||||
{
|
||||
{%- assign i = 0 -%}
|
||||
{%- assign pages_array = '' | split: '' -%}
|
||||
{%- assign pages_array = pages_array | push: site.html_pages -%}
|
||||
{%- if site.just_the_docs.collections -%}
|
||||
{%- for collection_entry in site.just_the_docs.collections -%}
|
||||
{%- assign collection_key = collection_entry[0] -%}
|
||||
{%- assign collection_value = collection_entry[1] -%}
|
||||
{%- assign collection = site[collection_key] -%}
|
||||
{%- if collection_value.search_exclude != true -%}
|
||||
{%- assign pages_array = pages_array | push: collection -%}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{%- endif -%}
|
||||
{%- for pages in pages_array -%}
|
||||
{%- for page in pages -%}
|
||||
{%- if page.title and page.search_exclude != true -%}
|
||||
{%- assign page_content = page.content -%}
|
||||
{%- assign heading_level = site.search.heading_level | default: 2 -%}
|
||||
{%- for j in (2..heading_level) -%}
|
||||
{%- assign tag = '<h' | append: j -%}
|
||||
{%- assign closing_tag = '</h' | append: j -%}
|
||||
{%- assign page_content = page_content | replace: tag, '<h1' | replace: closing_tag, '</h1' -%}
|
||||
{%- endfor -%}
|
||||
{%- assign parts = page_content | split: '<h1' -%}
|
||||
{%- assign title_found = false -%}
|
||||
{%- for part in parts offset: 1 -%}
|
||||
{%- assign titleAndContent = part | split: '</h1>' -%}
|
||||
{%- assign title = titleAndContent[0] | replace_first: '>', '<h1>' | split: '<h1>' -%}
|
||||
{%- assign title = title[1] | strip_html -%}
|
||||
{%- assign content = titleAndContent[1] -%}
|
||||
{%- assign url = page.url -%}
|
||||
{%- if title == page.title and parts[0] == '' -%}
|
||||
{%- assign title_found = true -%}
|
||||
{%- else -%}
|
||||
{%- assign id = titleAndContent[0] -%}
|
||||
{%- assign id = id | split: 'id="' -%}
|
||||
{%- if id.size == 2 -%}
|
||||
{%- assign id = id[1] -%}
|
||||
{%- assign id = id | split: '"' -%}
|
||||
{%- assign id = id[0] -%}
|
||||
{%- capture url -%}{{ url | append: '#' | append: id }}{%- endcapture -%}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{%- unless i == 0 -%},{%- endunless -%}
|
||||
"{{ i }}": {
|
||||
"doc": {{ page.title | jsonify }},
|
||||
"title": {{ title | jsonify }},
|
||||
"content": {{ content | replace: '</h', ' . </h' | replace: '<hr', ' . <hr' | replace: '</p', ' . </p' | replace: '<ul', ' . <ul' | replace: '</ul', ' . </ul' | replace: '<ol', ' . <ol' | replace: '</ol', ' . </ol' | replace: '</tr', ' . </tr' | replace: '<li', ' | <li' | replace: '</li', ' | </li' | replace: '</td', ' | </td' | replace: '<td', ' | <td' | replace: '</th', ' | </th' | replace: '<th', ' | <th' | strip_html | remove: 'Table of contents' | normalize_whitespace | replace: '. . .', '.' | replace: '. .', '.' | replace: '| |', '|' | append: ' ' | jsonify }},
|
||||
"url": "{{ url | absolute_url }}",
|
||||
"relUrl": "{{ url }}"
|
||||
}
|
||||
{%- assign i = i | plus: 1 -%}
|
||||
{%- endfor -%}
|
||||
{%- unless title_found -%}
|
||||
{%- unless i == 0 -%},{%- endunless -%}
|
||||
"{{ i }}": {
|
||||
"doc": {{ page.title | jsonify }},
|
||||
"title": {{ page.title | jsonify }},
|
||||
"content": {{ parts[0] | replace: '</h', ' . </h' | replace: '<hr', ' . <hr' | replace: '</p', ' . </p' | replace: '<ul', ' . <ul' | replace: '</ul', ' . </ul' | replace: '<ol', ' . <ol' | replace: '</ol', ' . </ol' | replace: '</tr', ' . </tr' | replace: '<li', ' | <li' | replace: '</li', ' | </li' | replace: '</td', ' | </td' | replace: '<td', ' | <td' | replace: '</th', ' | </th' | replace: '<th', ' | <th' | strip_html | remove: 'Table of contents' | normalize_whitespace | replace: '. . .', '.' | replace: '. .', '.' | replace: '| |', '|' | append: ' ' | jsonify }},
|
||||
"url": "{{ page.url | absolute_url }}",
|
||||
"relUrl": "{{ page.url }}"
|
||||
}
|
||||
{%- assign i = i | plus: 1 -%}
|
||||
{%- endunless -%}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{%- endfor %}
|
||||
}
|
Before Width: | Height: | Size: 16 KiB |
BIN
docs/assets/mobile.png
Normal file
After Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 188 KiB |
Before Width: | Height: | Size: 152 KiB |
BIN
docs/assets/options.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
docs/assets/share.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
docs/assets/thumbs/add.png
Normal file
After Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 10 KiB |
BIN
docs/assets/thumbs/event.png
Normal file
After Width: | Height: | Size: 53 KiB |
BIN
docs/assets/thumbs/federation.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
docs/assets/thumbs/follow.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
docs/assets/thumbs/gancio.png
Normal file
After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 130 KiB |
BIN
docs/assets/thumbs/home_desktop.png
Normal file
After Width: | Height: | Size: 76 KiB |
BIN
docs/assets/thumbs/mobile.png
Normal file
After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 27 KiB |
BIN
docs/assets/thumbs/options.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
docs/assets/thumbs/share.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
docs/assets/thumbs/title.png
Normal file
After Width: | Height: | Size: 25 KiB |
|
@ -8,6 +8,19 @@ nav_order: 10
|
|||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
### 1.0 (beta)
|
||||
This release is a complete rewrite of frontend UI and many internals, main changes are:
|
||||
|
||||
- Switch UI framework from [element](https://element.eleme.io/) to [vuetify](https://vuetifyjs.com/)
|
||||
- Distribute package directly from site instead of using npm registry
|
||||
- Improve docker setup (run as user, fix some data export)
|
||||
- New logging system (based on [winston](https://github.com/winstonjs/winston))
|
||||
- Slugify event URL (keeping old one valid)
|
||||
- Import events from ics and external website using h-event (microformat) improving [`h-event`](https://microformats.org/wiki/h-event) export
|
||||
- Hide unconfirmed tags and places
|
||||
- Clean unused places and tags
|
||||
- Fix tons of issues
|
||||
|
||||
### 0.24.0
|
||||
- New Euskara language from Basque Country, thanks @hacklabkelo
|
||||
- fix feed with filters
|
||||
|
|
|
@ -21,7 +21,7 @@ POST
|
|||
**`/event`**
|
||||
|
||||
> info "info"
|
||||
> `Content-Type` has to be `multipart/form-data` 'cause support image upload
|
||||
> `Content-Type` has to be `multipart/form-data` to support image upload
|
||||
|
||||
|
||||
**Params**
|
||||
|
|
|
@ -15,7 +15,7 @@ has_children: true
|
|||
- Express
|
||||
- Node.js
|
||||
- [Sequelize](https://sequelize.org/)
|
||||
- Element.ui
|
||||
- [Vuetify](vuetifyjs.com/)
|
||||
|
||||
### Testing on your own machine
|
||||
|
||||
|
@ -42,4 +42,4 @@ yarn dev
|
|||
> warning "Warning"
|
||||
> You need to register a first user, this will be an active administrator!
|
||||
|
||||
Please use the [issue board](https://framagit.org/les/gancio/-/boards) and the [forum](https://framavox.org/g/hMXTDgtJ/gancio) to discuss any modification.
|
||||
Please use the [issues](https://framagit.org/les/gancio/-/issues) to discuss any modification.
|
||||
|
|
|
@ -8,11 +8,10 @@ nav_order: 7
|
|||
|
||||
## Internationalization
|
||||
|
||||
We're self-hosting an instance of [weblate](https://weblate.gancio.org) you can use to help us with translations.
|
||||
We're using [weblate](https://hosted.weblate.org/engage/gancio/) for localization, you can use it to help us with translations.
|
||||
|
||||
Currently supported languages:
|
||||
|
||||
|
||||
|
||||
<a href="http://weblate.gancio.org/engage/gancio/?utm_source=widget">
|
||||
<img src="http://weblate.gancio.org/widgets/gancio/-/multi-auto.svg" alt="Stato traduzione" />
|
||||
<a href="https://hosted.weblate.org/engage/gancio/">
|
||||
<img src="https://hosted.weblate.org/widgets/gancio/-/multi-blue.svg" alt="Stato traduzione" />
|
||||
</a>
|
|
@ -1,3 +1,2 @@
|
|||
FROM node:latest
|
||||
WORKDIR /
|
||||
RUN yarn global add gancio
|
||||
FROM node:buster
|
||||
RUN yarn global add --silent https://gancio.org/latest.tgz 2> /dev/null
|
||||
|
|
|
@ -17,16 +17,17 @@ services:
|
|||
- 5432:5432
|
||||
gancio:
|
||||
build: .
|
||||
image: node:latest
|
||||
environment:
|
||||
- DEBUG=*,-babel,-follow-redirects,-send,-body-parser:*,-express:*,-connect:*,-sequelize:*
|
||||
container_name: gancio
|
||||
restart: always
|
||||
command: gancio start --docker --db=postgres
|
||||
image: node:buster
|
||||
user: node
|
||||
container_name: gancio
|
||||
environment:
|
||||
- PATH=$PATH:/home/node/.yarn/bin
|
||||
- GANCIO_DATA=/home/node/data
|
||||
command: gancio start --docker
|
||||
volumes:
|
||||
- ./config.json:/opt/gancio/config.json
|
||||
- ./uploads:/opt/gancio/uploads
|
||||
- ./data:/home/node/data
|
||||
ports:
|
||||
- "127.0.0.1:13120:13120"
|
||||
depends_on:
|
||||
- db
|
||||
ports:
|
||||
- 127.0.0.1:13120:13120
|
||||
|
|
|
@ -4,14 +4,14 @@ services:
|
|||
gancio:
|
||||
build: .
|
||||
restart: always
|
||||
image: node:latest
|
||||
image: node:buster
|
||||
user: node
|
||||
container_name: gancio
|
||||
command: gancio start --docker --db=sqlite
|
||||
environment:
|
||||
- DEBUG=*,-babel,-follow-redirects,-send,-body-parser:*,-express:*,-connect:*,-sequelize:*
|
||||
- PATH=$PATH:/home/node/.yarn/bin
|
||||
- GANCIO_DATA=/home/node/data
|
||||
command: gancio start --docker
|
||||
volumes:
|
||||
- ./db.sqlite:/opt/gancio/db.sqlite
|
||||
- ./config.json:/opt/gancio/config.json
|
||||
- ./uploads:/opt/gancio/uploads
|
||||
- ./data:/home/node/data
|
||||
ports:
|
||||
- "127.0.0.1:13120:13120"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
---
|
||||
layout: default
|
||||
title: Home
|
||||
nav_order: 1
|
||||
description: "Gancio is a shared agenda for local communities."
|
||||
|
@ -12,14 +11,9 @@ permalink: /
|
|||
A shared agenda for local communities.
|
||||
{: .fs-6 }
|
||||
|
||||
[Get started now](install){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 } [Demo](https://demo.gancio.org){: .btn .btn-green .fs-5 .mb-4 .mb-md-0 }
|
||||
[Install]({% link install/install.md %}){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 } [Demo](https://demo.gancio.org){: .btn .btn-green .fs-5 .mb-4 .mb-md-0 }
|
||||
[Source](https://framagit.org/les/gancio){: .btn .fs-5 }
|
||||
|
||||
[![assets/thumbs/home1.png](assets/thumbs/home1.png)](assets/home1.png){: data-fancybox="group" data-caption="Home of the first gancio instance"}
|
||||
[![assets/thumbs/mobile1.png](assets/thumbs/mobile1.png)](assets/mobile1.png){: data-fancybox="group" data-caption="Home mobile"}
|
||||
[![assets/thumbs/mobile2.png](assets/thumbs/mobile2.png)](assets/mobile2.png){: data-fancybox="group" data-caption="Home mobile"}
|
||||
[![assets/thumbs/admin_users.png](assets/thumbs/admin_users.png)](assets/admin_users.png){: data-fancybox="group" data-caption="Admin interface"}
|
||||
|
||||
|
||||
## Some relevant key features:
|
||||
|
||||
|
@ -30,7 +24,7 @@ nowhere on gancio appears the identity of who published the event, not even unde
|
|||
|
||||
- **Anonymous events**: optionally a visitor can create events without being registered (an administrator has to confirm them)
|
||||
|
||||
- **We are not interested in making hits** so we export events in many ways, via RSS feeds, via global or individual ics, incorporating lists of events or single event via iframe on other websites and via [AP](/federation)
|
||||
- **We are not interested in making hits** so we export events in many ways, via RSS feeds, via global or individual ics, incorporating lists of events or single event via iframe on other websites and via [AP]({% link federation.md %})
|
||||
|
||||
- Very easy UI
|
||||
- Multidays events support (festival, conferences...)
|
||||
|
|
23
docs/install/backup.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
layout: default
|
||||
title: Backup
|
||||
permalink: /install/backup
|
||||
nav_order: 5
|
||||
parent: Install
|
||||
---
|
||||
|
||||
## Backup
|
||||
|
||||
The following commands should be valid for every setup (docker/debian/sqlite/postgres) but check your installation directory first.
|
||||
This includes database, configuration, custom user locales, logs, images and thumbnails.
|
||||
|
||||
```bash
|
||||
cd /opt/gancio/ # or /home/gancio or where your installation is
|
||||
tar -czf gancio-$(date +%Y-%m-%d-%H%M%S)-backup.tgz \
|
||||
$(ls -d config.json uploads user_locale db.sqlite postgres data logs 2> /dev/null)
|
||||
```
|
||||
> warning "Permission denied"
|
||||
> `postgres` directory could have different permission or owner, in this case you need to be root or use `sudo` instead.
|
||||
|
||||
> info "Automatic backup"
|
||||
> To periodically backup your data you should probably use something like [restic](https://restic.net) or [borg](https://www.borgbackup.org/)
|
|
@ -1,14 +1,14 @@
|
|||
---
|
||||
layout: default
|
||||
title: Configuration
|
||||
permalink: /config
|
||||
nav_order: 3
|
||||
permalink: /install/config
|
||||
nav_order: 6
|
||||
parent: Install
|
||||
---
|
||||
|
||||
## Configuration
|
||||
{: .no_toc }
|
||||
Main `gancio` configuration is done with a configuration file.
|
||||
This shoud be a `.json` or a `.js` file and could be specified using the `--config` flag.
|
||||
`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.
|
||||
The configuration file shoud be a `.json` or a `.js` file and could be specified using the `--config` flag.
|
||||
|
||||
- <small>eg. `gancio start --config ./config.json`</small>
|
||||
- <small>eg. `pm2 start gancio start -- --config ~/config.json`</small>
|
||||
|
@ -21,7 +21,7 @@ The title will be in rss feed, in html head and in emails:
|
|||
|
||||
`"title": "Gancio"`
|
||||
|
||||
![title](assets/title.png)
|
||||
![title](../assets/title.png)
|
||||
|
||||
- ### Description
|
||||
`"description": "a shared agenda for local communities"`
|
||||
|
@ -77,11 +77,6 @@ Email of administrator. Note that email from gancio comes from this email and th
|
|||
the SMTP configuration above should allow to use this address as from.
|
||||
|
||||
|
||||
- ### Favicon
|
||||
You could specify another favicon. This is also used as logo (top-left
|
||||
corner):
|
||||
`"favicon": "./favicon.ico"`
|
||||
|
||||
- ### User locale
|
||||
Probably you want to modify some text for your specific community, that's
|
||||
why we thought the `user_locale` configuration: you can specify your version of
|
||||
|
@ -105,8 +100,6 @@ list of strings you can override.
|
|||
<small>:warning: Note that a restart is needed when you change
|
||||
user_locale's content.</small>
|
||||
|
||||
- ### Secret
|
||||
|
||||
|
||||
## Default settings
|
||||
```json
|
||||
|
@ -115,15 +108,14 @@ user_locale's content.</small>
|
|||
"description": "A shared agenda for local communities",
|
||||
"baseurl": "http://localhost:13120",
|
||||
"server": {
|
||||
"host": "0.0.0.0",
|
||||
"host": "127.0.0.1",
|
||||
"port": 13120
|
||||
},
|
||||
"db": {
|
||||
"dialect": "sqlite",
|
||||
"storage": "/tmp/db.sqlite"
|
||||
"storage": "./db.sqlite"
|
||||
},
|
||||
"upload_path": "./",
|
||||
"favicon": "../dist/favicon.ico",
|
||||
"smtp": {
|
||||
"auth": {
|
||||
"user": "",
|
||||
|
@ -133,6 +125,5 @@ user_locale's content.</small>
|
|||
"host": ""
|
||||
},
|
||||
"admin_email": "",
|
||||
"secret": "notsosecret"
|
||||
}
|
||||
```
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
layout: default
|
||||
title: Debian
|
||||
permalink: /install/debian
|
||||
nav_order: 1
|
||||
parent: Install
|
||||
---
|
||||
|
||||
|
@ -9,7 +9,7 @@ parent: Install
|
|||
|
||||
1. Install Node.js & yarn (**from root**)
|
||||
```bash
|
||||
curl -sL https://deb.nodesource.com/setup_12.x | bash -
|
||||
curl -sL https://deb.nodesource.com/setup_16.x | bash -
|
||||
apt-get install -y nodejs
|
||||
curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
|
||||
echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list
|
||||
|
@ -19,7 +19,7 @@ apt-get update && apt-get install yarn
|
|||
|
||||
1. Install Gancio
|
||||
```bash
|
||||
yarn global add gancio --prod
|
||||
yarn global add --silent {{site.url}}{% link /latest.tgz %} 2> /dev/null
|
||||
```
|
||||
|
||||
1. Setup with postgreSQL __(optional as you can choose sqlite)__
|
||||
|
@ -49,7 +49,7 @@ gancio start --config config.json
|
|||
```
|
||||
1. Point your web browser to [http://localhost:13120](http://localhost:13120) or where you selected during setup.
|
||||
|
||||
1. [Setup nginx as a proxy](/install/nginx)
|
||||
1. [Setup nginx as a proxy]({% link install/nginx.md %})
|
||||
|
||||
1. To deploy gancio in production you should use something like **[pm2](http://pm2.keymetrics.io/)**:
|
||||
|
||||
|
@ -62,8 +62,8 @@ pm2 startup # read the output!
|
|||
sudo pm2 startup -u gancio
|
||||
```
|
||||
|
||||
1. Upgrade
|
||||
## Upgrade
|
||||
```bash
|
||||
sudo yarn global add gancio
|
||||
sudo yarn global add --silent {{site.url}}{% link /latest.tgz %} 2> /dev/null
|
||||
sudo service pm2 restart
|
||||
```
|
||||
|
|
|
@ -3,39 +3,35 @@ layout: default
|
|||
title: Docker
|
||||
permalink: /install/docker
|
||||
parent: Install
|
||||
nav_order: 2
|
||||
---
|
||||
## Table of contents
|
||||
{: .no_toc .text-delta }
|
||||
## Docker installation
|
||||
{: .no_toc }
|
||||
|
||||
1. TOC
|
||||
{:toc}
|
||||
|
||||
## Initial setup
|
||||
**You do not need to clone the full repo as we distribute gancio via npm.**
|
||||
A Dockerfile and a docker-compose.yml are the only files needed.
|
||||
|
||||
- __Create a directory where everything related to gancio is stored (db, images, config)__
|
||||
> info "Clone not needed"
|
||||
> You do not need to clone the full repo, a `Dockerfile` and a `docker-compose.yml` are enough.
|
||||
|
||||
- __Create a directory where everything related to gancio is stored__
|
||||
```bash
|
||||
mkdir /opt/gancio
|
||||
mkdir -p /opt/gancio/data
|
||||
cd /opt/gancio
|
||||
```
|
||||
<small>note that you can choose a different directory.</small>
|
||||
|
||||
## Use sqlite
|
||||
<div class='code-example bg-grey-lt-100' markdown="1">
|
||||
1. **Download docker-compose.yml and Dockerfile**
|
||||
```bash
|
||||
wget https://gancio.org/docker/Dockerfile
|
||||
wget https://gancio.org/docker/sqlite/docker-compose.yml
|
||||
wget {{site.url}}{% link /docker/Dockerfile %}
|
||||
wget {{site.url}}{% link /docker/sqlite/docker-compose.yml %}
|
||||
```
|
||||
|
||||
1. Create an empty db and config (**this is needed**)
|
||||
```
|
||||
touch config.json db.sqlite
|
||||
mkdir user_locale
|
||||
```
|
||||
|
||||
1. Build docker image and launch interactive setup in one step
|
||||
1. Build docker image and launch interactive setup
|
||||
```
|
||||
docker-compose build
|
||||
docker-compose run --rm gancio gancio setup --docker --db=sqlite
|
||||
|
@ -47,17 +43,11 @@ docker-compose run --rm gancio gancio setup --docker --db=sqlite
|
|||
|
||||
1. **Download docker-compose.yml and Dockerfile**
|
||||
```bash
|
||||
wget https://gancio.org/docker/Dockerfile
|
||||
wget https://gancio.org/docker/postgres/docker-compose.yml
|
||||
wget {{site.url}}{% link /docker/Dockerfile %}
|
||||
wget {{site.url}}{% link /docker/postgres/docker-compose.yml %}
|
||||
```
|
||||
|
||||
1. Create an empty configuration (**this is needed**)
|
||||
```
|
||||
touch config.json
|
||||
mkdir user_locale
|
||||
```
|
||||
|
||||
1. Build docker image and launch interactive setup in one step
|
||||
1. Build docker image and launch interactive setup
|
||||
```
|
||||
docker-compose build
|
||||
docker-compose run --rm gancio gancio setup --docker --db=postgres
|
||||
|
@ -72,20 +62,37 @@ docker-compose run --rm gancio gancio setup --docker --db=postgres
|
|||
docker-compose up -d
|
||||
```
|
||||
|
||||
1. Look at logs with
|
||||
1. Look at logs
|
||||
```bash
|
||||
docker-compose logs
|
||||
tail -f data/logs/gancio.log
|
||||
```
|
||||
|
||||
1. [Setup nginx as a proxy](/install/nginx)
|
||||
1. [Setup nginx as a proxy]({% link install/nginx.md %}
|
||||
|
||||
1. Point your web browser to [http://localhost:13120](http://localhost:13120) or where you specified during setup and enjoy :tada:
|
||||
|
||||
1. You can edit `config.json` file and restart the container on your needs, see [Configuration](/config) for more details.
|
||||
|
||||
1. Edit `data/config.json` and restart the container on your needs, see [Configuration]({% link install/configuration.md %}) for more details.
|
||||
|
||||
## Upgrade
|
||||
|
||||
> warning "Backup your data"
|
||||
> Backup your data is generally a good thing to do and this is especially true before upgrading.
|
||||
> Don't be lazy and [backup]({% link install/backup.md %}) your data!
|
||||
|
||||
|
||||
> error "Upgrade from a version < 1.0"
|
||||
> Since v1.0 our docker setup is changed and a new container has to be built:
|
||||
>
|
||||
> 1. `cd /opt/gancio`
|
||||
> 1. [Backup your data]({% link install/backup.md %})
|
||||
> 1. Download new `Dockerfile` <br/> `wget {{site.url}}{% link /docker/Dockerfile %}`
|
||||
> 1. Download new `docker-compose.yml` (substitute `sqlite` with `postgres` in case): <br/>`wget {{site.url}}{% link /docker/sqlite/docker-compose.yml %}`
|
||||
> 1. Build the new container `docker-compose build`
|
||||
> 1. Extract your backup into `./data` <br/>`mkdir data; tar xvzf gancio-<yourLastBackup>-backup.tgz -C data`
|
||||
> 1. Stop your old container `docker-compose stop`
|
||||
> 1. Start your new container `docker-compose up`
|
||||
|
||||
|
||||
```bash
|
||||
cd /opt/gancio
|
||||
docker-compose up -d --no-deps --build
|
||||
|
|
|
@ -3,17 +3,20 @@ layout: default
|
|||
title: Install
|
||||
permalink: /install
|
||||
has_children: true
|
||||
nav_order: 2
|
||||
nav_order: 3
|
||||
has_toc: false
|
||||
---
|
||||
## Install
|
||||
|
||||
## Install (production)
|
||||
You can install gancio on a cheap VPS (500mb of ram will be enough)
|
||||
|
||||
- [Install on Debian](/install/debian)
|
||||
- [Install using docker](/install/docker)
|
||||
- [Install on Debian]({% link install/debian.md %})
|
||||
- [Install using docker]({% link install/docker.md %})
|
||||
|
||||
### Post installation
|
||||
- [Nginx as a proxy](/install/nginx)
|
||||
- [Setup Nginx as a proxy]({% link install/nginx.md %})
|
||||
- [Configuration]({% link install/configuration.md %})
|
||||
- [Backup]({% link install/backup.md %})
|
||||
|
||||
|
||||
If you wanna hack or run the current develop release take a look at [Hacking & contribute](../dev)
|
||||
If you wanna hack or run the current development release take a look at [Hacking & contribute]({% link dev/dev.md %})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
layout: default
|
||||
title: Nginx
|
||||
title: Nginx setup
|
||||
permalink: /install/nginx
|
||||
parent: Install
|
||||
---
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
---
|
||||
layout: default
|
||||
title: Instances
|
||||
permalink: /instances
|
||||
nav_order: 7
|
||||
|
@ -12,4 +11,4 @@ nav_order: 7
|
|||
- [chesefa.org](https://chesefa.org) (Naples, Italy)
|
||||
|
||||
|
||||
<small>Do you want your instance to appear here? [Write us](/contacts).</small>
|
||||
<small>Do you want your instance to appear here? [Write us]({% link contact.md %}).</small>
|
16
docs/screenshot.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: Screenshots
|
||||
nav_order: 2
|
||||
permalink: /screenshot
|
||||
---
|
||||
|
||||
## Screenshots
|
||||
|
||||
[![assets/thumbs/home_desktop.png](assets/thumbs/home_desktop.png)](assets/home_desktop.png){: data-fancybox="group" data-caption="Home"}
|
||||
[![assets/thumbs/event.png](assets/thumbs/event.png)](assets/event.png){: data-fancybox="group" data-caption="Event"}
|
||||
[![assets/thumbs/mobile.png](assets/thumbs/mobile.png)](assets/mobile.png){: data-fancybox="group" data-caption="Mobile"}
|
||||
[![assets/thumbs/add.png](assets/thumbs/add.png)](assets/add.png){: data-fancybox="group" data-caption="Add event"}
|
||||
[![assets/thumbs/share.png](assets/thumbs/share.png)](assets/share.png){: data-fancybox="group" data-caption="Share"}
|
||||
[![assets/thumbs/options.png](assets/thumbs/options.png)](assets/options.png){: data-fancybox="group" data-caption="Admin options"}
|
||||
[![assets/thumbs/federation.png](assets/thumbs/federation.png)](assets/federation.png){: data-fancybox="group" data-caption="Admin federation"}
|
||||
[![assets/thumbs/follow.png](assets/thumbs/follow.png)](assets/follow.png){: data-fancybox="group" data-caption="Follow"}
|
|
@ -1,11 +0,0 @@
|
|||
---
|
||||
layout: default
|
||||
title: Usage
|
||||
permalink: /usage
|
||||
nav_order: 1
|
||||
has_children: true
|
||||
---
|
||||
|
||||
# Usage
|
||||
|
||||
ehmmm, help needed here :smile: feel free to send a PR => [here](https://framagit.org/les/gancio/tree/master/docs)
|
|
@ -1,43 +1,31 @@
|
|||
<template lang='pug'>
|
||||
el-container#main(:class='{dark: $route.name==="index" || $route.name==="announcement-id"}')
|
||||
el-dialog(:visible.sync='showFollowMe')
|
||||
h4(slot='title') {{$t('common.follow_me_title')}}
|
||||
FollowMe
|
||||
|
||||
el-backtop
|
||||
v-app
|
||||
Snackbar
|
||||
Confirm
|
||||
Nav
|
||||
#content
|
||||
nuxt
|
||||
el-footer.mt-1#footer
|
||||
#links
|
||||
a(href='https://gancio.org') Gancio {{settings.version}}</a>
|
||||
span ⇒
|
||||
a(v-if='settings.enable_federation' rel='me' :href='settings.baseurl' @click.prevent='showFollowMe=true') follow me
|
||||
nuxt-link(to='/about') about
|
||||
a(href='https://blog.gancio.org') blog
|
||||
a(href='https://framagit.org/les/gancio') source
|
||||
|
||||
v-main
|
||||
v-fade-transition(hide-on-leave)
|
||||
nuxt
|
||||
|
||||
Footer
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import Nav from '~/components/Nav.vue'
|
||||
import Snackbar from '../components/Snackbar'
|
||||
import Footer from '../components/Footer'
|
||||
import Confirm from '../components/Confirm'
|
||||
import { mapState } from 'vuex'
|
||||
import FollowMe from '../components/FollowMe'
|
||||
|
||||
export default {
|
||||
components: { Nav, FollowMe },
|
||||
data () {
|
||||
return { showFollowMe: false }
|
||||
},
|
||||
computed: mapState(['settings'])
|
||||
}
|
||||
</script>
|
||||
<style lang='less'>
|
||||
.el-backtop {
|
||||
color: orangered;
|
||||
}
|
||||
#footer {
|
||||
a {
|
||||
font-size: 1.1em;
|
||||
name: 'Default',
|
||||
components: { Nav, Snackbar, Footer, Confirm },
|
||||
computed: mapState(['settings']),
|
||||
created () {
|
||||
this.$vuetify.theme.dark = this.settings['theme.is_dark']
|
||||
this.$vuetify.theme.themes.dark.primary = this.settings['theme.primary']
|
||||
this.$vuetify.theme.themes.light.primary = this.settings['theme.primary']
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</script>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template lang='pug'>
|
||||
el-main.p-4.text-center#error
|
||||
h1(v-if="error.statusCode === 404") <i class='el-icon-warning'></i> {{error.message}}
|
||||
h1(v-else) <i name='el-icon-warning'></i> An error occurred: {{error.message}}
|
||||
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}}
|
||||
nuxt-link(to='/') Back to home
|
||||
</template>
|
||||
|
||||
|
@ -9,17 +9,10 @@
|
|||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
props: { error: { type: Object } },
|
||||
computed: mapState(['settings']),
|
||||
props: { error: { type: Object, default: () => ({ }) } },
|
||||
head () {
|
||||
return { title: `${this.settings.title} - Error` }
|
||||
}
|
||||
},
|
||||
computed: mapState(['settings'])
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
#error {
|
||||
margin-top: 20px;
|
||||
color: orange;
|
||||
i { color: orangered }
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<template>
|
||||
<nuxt />
|
||||
<template lang='pug'>
|
||||
v-app#iframe
|
||||
nuxt
|
||||
</template>
|
||||
<style lang='less'>
|
||||
html, body {
|
||||
background-color: transparent;
|
||||
#iframe.v-application {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,9 +1,24 @@
|
|||
<template lang='pug'>
|
||||
el-container#modal
|
||||
el-header
|
||||
.row.p-0.m-0
|
||||
.col.p-0
|
||||
.col-xl-5.col-lg-6.col-sm-10.col-xs-12.col-md-7.p-0
|
||||
v-app(app)
|
||||
Snackbar
|
||||
Confirm
|
||||
Nav
|
||||
|
||||
v-main(app)
|
||||
v-scroll-y-transition(hide-on-leave)
|
||||
nuxt
|
||||
.col.p-0
|
||||
|
||||
Footer
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import Nav from '~/components/Nav.vue'
|
||||
import Snackbar from '../components/Snackbar'
|
||||
import Footer from '../components/Footer'
|
||||
import Confirm from '../components/Confirm'
|
||||
|
||||
export default {
|
||||
name: 'Default',
|
||||
components: { Nav, Snackbar, Footer, Confirm }
|
||||
}
|
||||
</script>
|
||||
|
|
1
locales/ar.json
Normal file
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -48,7 +48,7 @@
|
|||
"enable": "Habilita",
|
||||
"disable": "Deshabilita",
|
||||
"me": "Tu",
|
||||
"password_updated": "S'ha actualitzat la contrasenya!",
|
||||
"password_updated": "S'ha actualitzat la contrasenya.",
|
||||
"resources": "Recursos",
|
||||
"n_resources": "cap recurs|un recurs|{n} recursos",
|
||||
"activate_user": "S'ha confirmat",
|
||||
|
@ -80,11 +80,15 @@
|
|||
"delete": "Esborra",
|
||||
"announcements": "Anuncis",
|
||||
"url": "Adreça URL",
|
||||
"place": "Lloc"
|
||||
"place": "Lloc",
|
||||
"import": "Importa",
|
||||
"reset": "Reinicia",
|
||||
"theme": "Tema",
|
||||
"tags": "Etiquetes"
|
||||
},
|
||||
"login": {
|
||||
"description": "Amb la sessió iniciada pots afegir activitats noves",
|
||||
"check_email": "Comprova al teu correu la safata d'entrada i la de brossa",
|
||||
"description": "Amb la sessió iniciada pots afegir activitats noves.",
|
||||
"check_email": "Comprova al teu correu la safata d'entrada i la de brossa.",
|
||||
"not_registered": "No estàs registrat/da?",
|
||||
"forgot_password": "Has oblidat la contrasenya?",
|
||||
"error": "Error en iniciar la sessió, comprova les credencials.",
|
||||
|
@ -97,20 +101,20 @@
|
|||
"export": {
|
||||
"intro": "A diferència de les plataformes capitalistes, que recopilen informació de tot el que hi fem les usuàries, nosaltres creiem que la informació, tal com les persones, ha de ser lliure. Per això, pots estar al corrent de les activitats que vols sense necessitat de passar per aquest web.",
|
||||
"email_description": "Pots rebre correus sobre les activitats que t'interessin.",
|
||||
"insert_your_address": "Introdueix la teva adreça de mail.",
|
||||
"feed_description": "\nPer tal de seguir les actualitzacions sense haver d'entrar periòdicament aquí, et recomanem fer servir el flux RSS</p>\n\n<p>Hi ha aplicacions que agrupen i llegeixen fluxos RSS de diverses webs, blogs i aplicacions web. És una bona manera de mantenir-te informada sense haver de crear comptes a tot arreu.</p>\n\n<li>Per a Android recomanem <a href=\"https://play.google.com/store/apps/details?id=net.frju.flym\"> Flym </a> o Feeder</li>\n<li>Per a iPhone / iPad pots fer servir <a href=\"https://itunes.apple.com/ua/app/feeds4u/id1038456442?mt=8\"> Feed4U </a></li>\n<li>Per a l'ordinador recomanem Feedbro, que és una extensió de navegador disponible per a <a href=\"https://addons.mozilla.org/en-GB/firefox/addon/feedbroreader/\"> Firefox </a> i per a <a href=\"https://chrome.google.com/webstore/detail/feedbro/mefgmmbdailogpfhfblcnnjfmnpnmdfa\"> Chrome i Chromium</a>. Funciona en els principals sistemes operatius.</li>\n<br/>\nAfegeix aquest enllaç al teu lector de fluxos per mantenir-te al dia sense esforç.",
|
||||
"insert_your_address": "Introdueix la teva adreça de mail",
|
||||
"feed_description": "Per tal de seguir les actualitzacions sense haver d'entrar periòdicament aquí, et recomanem fer servir el flux RSS</p>\n\n<p>Hi ha aplicacions que agrupen i llegeixen fluxos RSS de diverses webs, blogs i aplicacions web. És una bona manera de mantenir-te informada sense haver de crear comptes a tot arreu.</p>\n\n<li>Per a Android recomanem <a href=\"https://play.google.com/store/apps/details?id=net.frju.flym\"> Flym </a> o Feeder</li>\n<li>Per a iPhone / iPad pots fer servir <a href=\"https://itunes.apple.com/ua/app/feeds4u/id1038456442?mt=8\"> Feed4U </a></li>\n<li>Per a l'ordinador recomanem Feedbro, que és una extensió de navegador disponible per a <a href=\"https://addons.mozilla.org/en-GB/firefox/addon/feedbroreader/\"> Firefox </a> i per a <a href=\"https://chrome.google.com/webstore/detail/feedbro/mefgmmbdailogpfhfblcnnjfmnpnmdfa\"> Chrome i Chromium</a>. Funciona en els principals sistemes operatius.</li>\n<br/>\nAfegeix aquest enllaç al teu lector de fluxos per mantenir-te al dia sense esforç.",
|
||||
"ical_description": "Els sistemes operatius d'ordinadors i mòbils solen oferir aplicacions de calendari. Algunes permeten importar calendaris remots, que van comprovant periòdicament.",
|
||||
"list_description": "Si tens una web i vols encastar una llista d'activitats, pots fer servir el codi de sota."
|
||||
"list_description": "Si tens una web i vols encastar una llista d'activitats, pots fer servir el codi de sota"
|
||||
},
|
||||
"register": {
|
||||
"description": "\n 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\n 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. <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?",
|
||||
"error": "Error: ",
|
||||
"complete": "El registre ha de ser confirmat.",
|
||||
"first_user": "S'ha creat i activat un compte administrador"
|
||||
},
|
||||
"event": {
|
||||
"anon": "Anònim",
|
||||
"anon_description": "Pots afegir activitats sense haver de crear un compte o iniciar sessió, però a canvi hauràs d'esperar que algun/a admin la llegeixi i l'aprovi,\n confirmant que és adequada. Demana més feina humana i no podrà ser modificada.<br/><br/>\n Pots <a href='/login'>iniciar sessió</a> o <a href='/register'>crear un compte</a>, però si ho necessites, endavant! Procurarem trigar poc.",
|
||||
"anon_description": "Pots afegir activitats sense haver de crear un compte o iniciar sessió, però a canvi hauràs d'esperar que algun/a admin la llegeixi i l'aprovi,\n confirmant que és adequada. Demana més feina humana i no podrà ser modificada.<br/><br/>\n Pots <a href='/login'>iniciar sessió</a> o <a href='/register'>crear un compte</a>, però si ho necessites, endavant! Procurarem trigar poc. ",
|
||||
"same_day": "al mateix dia",
|
||||
"what_description": "Títol",
|
||||
"description_description": "Descripció",
|
||||
|
@ -144,12 +148,15 @@
|
|||
"from": "Des de les",
|
||||
"image_too_big": "La imatge és massa gran! Max 4 MB",
|
||||
"interact_with_me_at": "Interacciona amb mi a",
|
||||
"follow_me_description": "Entre les diverses maneres d'estar al dia de les activitats que es publiquen aquí a {title},\n pots seguir-nos al compte <u>{account}</u> des de Mastodon o altres, i afegir recursos des d'allà. <br/> <br/>\n Si no has sentit mai sobre «Mastodon» o «Fedivers», recomanem llegir <a href='https://www.savjee.be/videos/simply-explained/mastodon-and-fediverse-explained/'> aquest article (en anglès) </a>. <br/> <br/> Introdueix la teva instància a sota (ex: red.confederac.io o mastodont.cat)",
|
||||
"follow_me_description": "Entre les diverses maneres d'estar al dia de les activitats que es publiquen aquí a {title},\n pots seguir-nos al compte <u>{account}</u> des de Mastodon o altres, i afegir recursos des d'allà. <br/> <br/>\nSi no has sentit mai sobre «Mastodon» o «Fedivers», recomanem mirar <a href='https://peertube.social/videos/watch/d9bd2ee9-b7a4-44e3-8d65-61badd15c6e6'> aquest vídeo (subtitulat en català)</a>. <br/> <br/> Introdueix la teva instància a sota (ex: red.confederac.io o mastodont.cat)",
|
||||
"interact_with_me": "Segueix-nos al fedivers",
|
||||
"remove_recurrent_confirmation": "Estàs segur/a d'esborrar aquesta activitat periòdica?\n\nNo s'esborraran les ocurrències antigues, només es deixaran de crear les futures."
|
||||
"remove_recurrent_confirmation": "Estàs segur/a d'esborrar aquesta activitat periòdica?\nNo s'esborraran les ocurrències antigues, només es deixaran de crear les futures.",
|
||||
"ics": "ICS",
|
||||
"import_ICS": "Importa des d'un ICS",
|
||||
"import_URL": "Importa des d'una URL"
|
||||
},
|
||||
"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!",
|
||||
"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.",
|
||||
"event_confirm_description": "Des d'aquí pots confirmar les activitats creades anònimament",
|
||||
"delete_user": "Esborra",
|
||||
"remove_admin": "Esborra admin",
|
||||
|
@ -190,35 +197,41 @@
|
|||
"instance_timezone_description": "El Gancio està pensat per aglutinar les activitats d'un lloc específic, com una ciutat, i per tant, dins una sola zona horària. Les dates i hores de totes les activitats es mostraran segons el fus horari que escriguis i seleccionis.",
|
||||
"instance_locale_description": "La llengua amb què es mostren les pàgines és la preferida per la usuària. Ara bé, en alguns casos, hem de mostrar missatges per tothom en la mateixa llengua (per exemple, quan publiquem via ActivityPub o en alguns correus). En aquests casos, es farà servir la llengua predeterminada.",
|
||||
"instance_place": "Lloc indicatiu d'aquesta instància",
|
||||
"title_description": "Es fa servir al títol de la pàgina, a l'assumpte dels correus i en els formats exportats de feed i ics ",
|
||||
"title_description": "Es fa servir al títol de la pàgina, a l'assumpte dels correus i en els formats exportats de flux RSS i ICS.",
|
||||
"description_description": "Apareix a la capçalera al costat del títol",
|
||||
"instance_name_help": "Nom del compte ActivityPub per a seguir",
|
||||
"enable_trusted_instances": "Mostra les instàncies amigues",
|
||||
"trusted_instances_help": "Les instàncies amigues apareixen en la capçalera, a la barra de navegació superior",
|
||||
"add_trusted_instance": "Afegeix una instància amiga",
|
||||
"instance_place_help": "L'etiqueta per mostrar al menú de les altres instàncies amigues",
|
||||
"delete_trusted_instance_confirm": "Segur que vols eliminar aquest element del menú d'instàncies amigues?"
|
||||
"delete_trusted_instance_confirm": "Segur que vols eliminar aquest element del menú d'instàncies amigues?",
|
||||
"new_announcement": "Crea un anunci",
|
||||
"edit_place": "Modifica el lloc",
|
||||
"delete_footer_link_confirm": "Segur que vols esborrar aquest enllaç?",
|
||||
"footer_links": "Enllaços del peu",
|
||||
"add_link": "Afegeix un enllaç",
|
||||
"is_dark": "Tema fosc"
|
||||
},
|
||||
"auth": {
|
||||
"not_confirmed": "Encara no s'ha confirmat",
|
||||
"not_confirmed": "Encara no s'ha confirmat…",
|
||||
"fail": "Ha fallta l'autenticació! Pots comprovar la contrasenya?"
|
||||
},
|
||||
"settings": {
|
||||
"update_confirm": "Vols desar els canvis?",
|
||||
"change_password": "Canvia la contrasenya",
|
||||
"password_updated": "S'ha actualitzat la contrasenya",
|
||||
"password_updated": "S'ha actualitzat la contrasenya.",
|
||||
"danger_section": "Secció perillosa",
|
||||
"remove_account": "Si cliques el botó s'esborrarà el teu compte. Les activitats que hagis publicat es quedaran.",
|
||||
"remove_account_confirm": "Estàs a punt d'esborrar permanentment el teu compte!"
|
||||
"remove_account_confirm": "Estàs a punt d'esborrar permanentment el teu compte"
|
||||
},
|
||||
"error": {
|
||||
"nick_taken": "Aquest nom ja està agafat",
|
||||
"email_taken": "Aquest mail ja s'està fent servir"
|
||||
"nick_taken": "Aquest nom ja està agafat.",
|
||||
"email_taken": "Aquest mail ja s'està fent servir."
|
||||
},
|
||||
"confirm": {
|
||||
"title": "Confirmació d'usuàries",
|
||||
"not_valid": "Ermmmm hi ha algun error.",
|
||||
"valid": "S'ha confirmat el teu compte, ja pots <a href=\"/?ref=login\">iniciar sessió</a>."
|
||||
"valid": "S'ha confirmat el teu compte, ja pots <a href=\"/?ref=login\">iniciar sessió</a>"
|
||||
},
|
||||
"ordinal": {
|
||||
"1": "primer",
|
||||
|
@ -235,5 +248,9 @@
|
|||
"scopes": {
|
||||
"event:write": "Publica o modifica les teves activitats"
|
||||
}
|
||||
},
|
||||
"validators": {
|
||||
"email": "Escriu una adreça de correu vàlida",
|
||||
"required": "Cal omplir el camp {fieldName} és"
|
||||
}
|
||||
}
|
||||
|
|
166
locales/de.json
Normal file
|
@ -0,0 +1,166 @@
|
|||
{
|
||||
"common": {
|
||||
"title": "Titel",
|
||||
"user": "Nutzer:in",
|
||||
"moderation": "Moderation",
|
||||
"follow": "Folgen",
|
||||
"feed_url_copied": "Öffne die kopierte Feed-URL in deinem RSS-Reader",
|
||||
"feed": "RSS-Feed",
|
||||
"embed_help": "Kopiere den folgenden Code in deine Webseite und die Veranstaltung wird wie hier angezeigt",
|
||||
"embed_title": "Bette diese Veranstaltung in deine Webseite ein",
|
||||
"embed": "Einbetten",
|
||||
"copied": "Kopiert",
|
||||
"instances": "Instanzen",
|
||||
"add_to_calendar": "Zu einem Kalender hinzufügen",
|
||||
"send_via_mail": "E-Mail versenden",
|
||||
"copy_link": "Link kopieren",
|
||||
"set_password": "Passwort erstellen",
|
||||
"displayname": "Anzeigename",
|
||||
"activate_user": "Bestätigt",
|
||||
"password_updated": "Passwort geändert.",
|
||||
"me": "Du",
|
||||
"disable": "ausschalten",
|
||||
"enable": "einschalten",
|
||||
"cancel": "Abbrechen",
|
||||
"ok": "OK",
|
||||
"new_user": "Neue:r Nutzer:in",
|
||||
"new_password": "Neues Passwort",
|
||||
"recover_password": "Passwort wiederherstellen",
|
||||
"copy": "Kopieren",
|
||||
"logout_ok": "Abgemeldet",
|
||||
"add": "Hinzufügen",
|
||||
"edit_event": "Veranstaltung bearbeiten",
|
||||
"name": "Name",
|
||||
"share": "Teilen",
|
||||
"logout": "Abmelden",
|
||||
"preview": "Vorschau",
|
||||
"save": "Speichern",
|
||||
"activate": "Aktivieren",
|
||||
"remove_admin": "Admin entfernen",
|
||||
"deactivate": "Deaktivieren",
|
||||
"actions": "Aktionen",
|
||||
"settings": "Einstellungen",
|
||||
"places": "Orte",
|
||||
"events": "Veranstaltungen",
|
||||
"users": "Nutzer:innen",
|
||||
"admin": "Admin",
|
||||
"confirm": "Bestätigen",
|
||||
"edit": "Bearbeiten",
|
||||
"search": "Suchen",
|
||||
"hide": "Verstecken",
|
||||
"remove": "Entfernen",
|
||||
"description": "Beschreibung",
|
||||
"register": "Registrieren",
|
||||
"password": "Passwort",
|
||||
"email": "E-Mail",
|
||||
"login": "Anmelden",
|
||||
"media": "Dateien",
|
||||
"what": "Was",
|
||||
"when": "Wann",
|
||||
"address": "Adresse",
|
||||
"where": "Wo",
|
||||
"send": "Abschicken",
|
||||
"export": "Exportieren",
|
||||
"next": "Weiter",
|
||||
"add_event": "Veranstaltung hinzufügen",
|
||||
"import": "Importieren",
|
||||
"reset": "Zurücksetzen",
|
||||
"tags": "Markierung",
|
||||
"place": "Ort",
|
||||
"url": "URL",
|
||||
"announcements": "Ankündigungen",
|
||||
"delete": "Löschen",
|
||||
"skip": "Überspringen",
|
||||
"fediverse": "Fediverse",
|
||||
"start": "Starten",
|
||||
"pause": "Pausieren",
|
||||
"event": "Veranstaltung",
|
||||
"filter": "Filter",
|
||||
"authorize": "Autorisieren",
|
||||
"follow_me_title": "Folgen Sie den Aktualisierungen von Fediverse",
|
||||
"federation": "Föderation",
|
||||
"n_resources": "keine Ressource|eine Ressource|{n} Ressourcen",
|
||||
"resources": "Ressourcen",
|
||||
"info": "Infos",
|
||||
"theme": "Thema"
|
||||
},
|
||||
"admin": {
|
||||
"delete_footer_link_confirm": "Sicher, diesen Link zu entfernen?",
|
||||
"footer_links": "Fußnotenlinks",
|
||||
"add_link": "Link hinzufügen",
|
||||
"is_dark": "Dunkles Design",
|
||||
"delete_trusted_instance_confirm": "Wollen Sie dieses Element wirklich aus dem Menü der Freundinstanz löschen?",
|
||||
"new_announcement": "Neue Ankündigung",
|
||||
"edit_place": "Ort bearbeiten"
|
||||
},
|
||||
"settings": {
|
||||
"update_confirm": "Wollen Sie Ihre Änderung speichern?",
|
||||
"password_updated": "Passwort geändert.",
|
||||
"change_password": "Passwort ändern",
|
||||
"remove_account_confirm": "Sie sind dabei, Ihr Konto endgültig zu löschen",
|
||||
"remove_account": "Durch Drücken der folgenden Schaltfläche wird Ihr Benutzerkonto gelöscht. Von Ihnen veröffentlichte Ereignisse werden nicht veröffentlicht.",
|
||||
"danger_section": "Gefährlicher Abschnitt"
|
||||
},
|
||||
"auth": {
|
||||
"fail": "Konnte sich nicht anmelden. Sind Sie sicher, dass das Passwort korrekt ist?",
|
||||
"not_confirmed": "Noch nicht bestätigt …"
|
||||
},
|
||||
"error": {
|
||||
"email_taken": "Diese E-Mail-Adresse wird schon verwendet.",
|
||||
"nick_taken": "Dieser Benutzername wird bereits verwendet."
|
||||
},
|
||||
"confirm": {
|
||||
"title": "Benutzerbestätigung",
|
||||
"valid": "Ihr Konto ist bestätigt; Sie können sich jetzt <a href=\"/login\">anmelden</a>",
|
||||
"not_valid": "Etwas ist schief gelaufen."
|
||||
},
|
||||
"validators": {
|
||||
"required": "{fieldName} ist erforderlich",
|
||||
"email": "Fügen Sie eine gültige E-Mail ein"
|
||||
},
|
||||
"ordinal": {
|
||||
"-1": "letzte",
|
||||
"5": "fünfte",
|
||||
"4": "vierte",
|
||||
"3": "dritte",
|
||||
"2": "zweite",
|
||||
"1": "erste"
|
||||
},
|
||||
"oauth": {
|
||||
"scopes": {
|
||||
"event:write": "Fügen Sie Ihre Ereignisse hinzu und bearbeiten Sie sie"
|
||||
}
|
||||
},
|
||||
"event": {
|
||||
"tag_description": "Markierung",
|
||||
"description_description": "Beschreibung",
|
||||
"what_description": "Titel",
|
||||
"same_day": "am selben Tag",
|
||||
"anon": "Anonym"
|
||||
},
|
||||
"register": {
|
||||
"first_user": "Administrator erstellt",
|
||||
"complete": "Die Registrierung muss bestätigt werden.",
|
||||
"error": "Fehler: "
|
||||
},
|
||||
"export": {
|
||||
"list_description": "Wenn Sie eine Website haben und eine Liste von Ereignissen anzeigen möchten, verwenden Sie den folgenden Code",
|
||||
"ical_description": "Computer und Smartphones sind üblicherweise mit einer Kalenderanwendung ausgestattet, mit der ein Fernkalender importiert werden kann.",
|
||||
"insert_your_address": "Geben Sie Ihre E-Mail-Adresse ein",
|
||||
"email_description": "Sie können interessante Ereignisse per E-Mail erhalten.",
|
||||
"feed_description": "Um Updates auf deinem Computer oder Smartphone zu erhalten, ohne regelmäßig diese Seite zu öffnen, kannst du RSS-Feeds benutzen. </p>\n\n<p> Mit RSS-Feeds nutzt du eine spezielle App, um Updates von den Seiten, die dich interessieren, zu erhalten. Damit kannst du rasch vielen Seiten folgen, ohne einen\nAccount erstellen zu müssen und ohne sonstige Komplikationen. </p>\n\n<li> Für Android-Geräte empfehlen wir <a href=\"https://f-droid.org/en/packages/net.frju.flym/\">Flym</a> oder Feeder </li>\n<li> Auf iPhones / iPads kannst du <a href=\"https://itunes.apple.com/ua/app/feeds4u/id1038456442?mt=8\"> Feed4U </a> nutzen</li>\n<li> Auf Desktop-PCs oder Laptops empfehlen wir Feedbro, den du in <a href=\"https://addons.mozilla.org/en-GB/firefox/addon/feedbroreader/\"> Firefox </a> oder <a \nref=\"https://chrome.google.com/webstore/detail/feedbro/mefgmmbdailogpfhfblcnnjfmnpnmdfa\"> Chrome </a> installieren kannst. </li>\n<br/>\nIndem du diesen Link deinem RSS-Feed-Reader hinzufügst, wirst du auf dem Laufenden gehalten.",
|
||||
"intro": "Anders als unsoziale Netzwerke, die alles unternehmen, um Nutzer und deren Daten bei sich zu (be)halten, glauben wir, dass Informationen und Leute gleichermaßen frei sein müssen. Deshalb kannst du dich über Veranstaltungen auf dem Laufenden halten, ohne zwangsläufig über diese Seite zu gehen."
|
||||
},
|
||||
"recover": {
|
||||
"not_valid_code": "Etwas ist schief gelaufen."
|
||||
},
|
||||
"login": {
|
||||
"ok": "Angemeldet",
|
||||
"insert_email": "Geben Sie Ihre E-Mail-Adresse ein",
|
||||
"error": "Anmeldung unmöglich. Überprüfen Sie Ihre Anmeldeinformationen.",
|
||||
"forgot_password": "Passwort vergessen?",
|
||||
"not_registered": "Nicht registriert?",
|
||||
"check_email": "Überprüfen Sie Ihren E-Mail-Posteingang und Spam.",
|
||||
"description": "Durch Anmelden können Sie neue Veranstaltungen veröffentlichen."
|
||||
}
|
||||
}
|
1
locales/email/ar.json
Normal file
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -5,7 +5,7 @@
|
|||
},
|
||||
"confirm": {
|
||||
"subject": "Ja pots publicar activitats",
|
||||
"content": "Ei, el teu compte a <a href='{{config.baseurl}}'>{{config.title}}</a> ha estat confirmat. Escriu-nos a {{config.admin}} per qualsevol dubte."
|
||||
"content": "Ei, el teu compte a <a href='{{config.baseurl}}'>{{config.title}}</a> ha estat confirmat. Escriu-nos a {{config.admin_email}} per qualsevol dubte."
|
||||
},
|
||||
"user_confirm": {
|
||||
"subject": "Ja pots publicar activitats",
|
||||
|
@ -13,7 +13,7 @@
|
|||
},
|
||||
"recover": {
|
||||
"subject": "Recupera la contrasenya",
|
||||
"content": "Ep, has demanat restablir la contrasenya a {{config.title}}, oi? <a href='{{config.baseurl}}/recover/{{user.recover_code}}'>Clica aquí</a> per a continuar el procés"
|
||||
"content": "Ep, has demanat restablir la contrasenya a {{config.title}}, oi? <a href='{{config.baseurl}}/recover/{{user.recover_code}}'>Clica aquí</a> per a continuar el procés."
|
||||
},
|
||||
"admin_register": {
|
||||
"subject": "Registre nou",
|
||||
|
|
22
locales/email/de.json
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"admin_register": {
|
||||
"content": "{{user.email}} hat eine Registrierung auf {{config.title}} angefordert: <br/><pre>{{user.description}}</pre><br/> Bitte bestätigen <a href='{{config.baseurl}}/admin'>here</a>.",
|
||||
"subject": "Neue Registrierung"
|
||||
},
|
||||
"recover": {
|
||||
"content": "Hallo, du hast die Wiederherstellung deines Passworts angefordert {{config.title}}. <a href='{{config.baseurl}}/recover/{{user.recover_code}}'>Klicke hier</a>, um zu bestätigen.",
|
||||
"subject": "Passwortwiederherstellung"
|
||||
},
|
||||
"user_confirm": {
|
||||
"content": "Hallo, dein Account auf <a href='{{config.baseurl}}'>{{config.title}}</a> wurde erstellt. <a href='{{config.baseurl}}/user_confirm/{{user.recover_code}}'>Bestätige ihn und wähle ein Kennwort.</a>.",
|
||||
"subject": "Du kannst jetzt damit beginnen, Events zu veröffentlichen"
|
||||
},
|
||||
"confirm": {
|
||||
"content": "Hallo, dein Account auf <a href='{{config.baseurl}}'>{{config.title}}</a> wurde bestätigt. Anfragen kannst du an {{config.admin_email}} stellen.",
|
||||
"subject": "Du kannst jetzt damit beginnen, Events zu veröffentlichen"
|
||||
},
|
||||
"register": {
|
||||
"content": "Wir haben die Registrierungsanfrage erhalten. Wir werden sie so bald wie möglich bestätigen.",
|
||||
"subject": "Registrierungsanfrage erhalten"
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
},
|
||||
"recover": {
|
||||
"subject": "Password recovery",
|
||||
"content": "Hi, you requested a password recovery on {{config.title}}. <a href='{{config.baseurl}}/recover/{{user.recover_code}}'>Click here</a> to confirm"
|
||||
"content": "Hi, you requested a password recovery on {{config.title}}. <a href='{{config.baseurl}}/recover/{{user.recover_code}}'>Click here</a> to confirm."
|
||||
},
|
||||
"admin_register": {
|
||||
"subject": "New registration",
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
},
|
||||
"recover": {
|
||||
"subject": "Recuperar password",
|
||||
"content": "Hola, has pedido recuperar tu contraseña en {{config.title}}. <a href='{{config.baseurl}}/recover/{{user.recover_code}}'>Pulsa aquí</a> para confirmar"
|
||||
"content": "Hola, has pedido recuperar tu contraseña en {{config.title}}. <a href='{{config.baseurl}}/recover/{{user.recover_code}}'>Pulsa aquí</a> para confirmar."
|
||||
},
|
||||
"common": {
|
||||
"press_here": "Premi qui"
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
"content": "Kaixo, zure <a href='{{config.baseurl}}'>{{config.title}}</a>-ko kontua berria sortu da. <a href='{{config.baseurl}}/user_confirm/{{user.recover_code}}'>Baieztatu zu zarela eta aukeratu ezazu pasahitza</a>."
|
||||
},
|
||||
"recover": {
|
||||
"subject": "Pasahitza berreskuratu",
|
||||
"content": "Kaixo, zure pasahitza berreskuratzea eskatu duzu {{config.title}}-n. <a href='{{config.baseurl}}/recover/{{user.recover_code}}'>Sakatu hemen</a> baieztatzeko"
|
||||
"subject": "Pasahitza berreskuratzea",
|
||||
"content": "Kaixo, zure pasahitza berreskuratzea eskatu duzu {{config.title}}-n. <a href='{{config.baseurl}}/recover/{{user.recover_code}}'>Hemen sakatzen du</a> egiaztatzeko."
|
||||
},
|
||||
"admin_register": {
|
||||
"subject": "Izen-emate berria",
|
||||
|
|
22
locales/email/fr.json
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"admin_register": {
|
||||
"content": "{{user.email}} a demandé à s'inscrire sur {{config.title}} : <br/><pre>{{user.description}}</pre><br/> Confirmez la <a href='{{config.baseurl}}/admin'>ici</a>.",
|
||||
"subject": "Nouvelle inscription"
|
||||
},
|
||||
"recover": {
|
||||
"subject": "Réinitialisation du mot de passe",
|
||||
"content": "Bonjour, vous avez demandé une réinitialisation du mot de passe sur {{config.title}}. <a href='{{config.baseurl}}/recover/{{user.recover_code}}'>Cliquez ici</a> pour confirmer."
|
||||
},
|
||||
"user_confirm": {
|
||||
"content": "Bonjour, votre compte sur <a href='{{config.baseurl}}'>{{config.title}}</a> a été créé. <a href='{{config.baseurl}}/user_confirm/{{user.recover_code}}'>Confirmez-le et créez un mot de passe.</a>.",
|
||||
"subject": "Vous pouvez désormais commencer à publier des évènements"
|
||||
},
|
||||
"confirm": {
|
||||
"content": "Bonjour, votre compte sur <a href='{{config.baseurl}}'>{{config.title}}</a> a été confirmé. Écrivez-nous à {{config.admin_email}} pour tout renseignement.",
|
||||
"subject": "Vous pouvez désormais commencer à publier des évènements"
|
||||
},
|
||||
"register": {
|
||||
"content": "Nous avons reçu la demande d'inscription. Nous la confirmerons au plus vite.",
|
||||
"subject": "Demande d'inscription reçue"
|
||||
}
|
||||
}
|
|
@ -1,22 +1,22 @@
|
|||
{
|
||||
"register": {
|
||||
"subject": "Richiesta registrazione ricevuta",
|
||||
"content": "Abbiamo ricevuto la richiesta di registrazione. La confermeremo quanto prima.\nCiao"
|
||||
"content": "Abbiamo ricevuto la richiesta di registrazione, la confermeremo quanto prima.<br/>Ciao."
|
||||
},
|
||||
"confirm": {
|
||||
"subject": "Puoi iniziare a pubblicare eventi",
|
||||
"content": "Ciao, il tuo account su <a href='{{config.baseurl}}'>{{config.title}}</a> è stato confermato. Scrivici a {{config.admin_email}} per qualsiasi informazione e fai a modino."
|
||||
"content": "Ciao, il tuo account su <a href='{{config.baseurl}}'>{{config.title}}</a> è stato confermato.<br/> Scrivici a {{config.admin_email}} per qualsiasi informazione e fai a modino."
|
||||
},
|
||||
"user_confirm": {
|
||||
"subject": "Puoi iniziare a pubblicare eventi",
|
||||
"content": "Ciao, il tuo account su <a href='{{config.baseurl}}'>{{config.title}}</a> è stato creato. <a href='{{config.baseurl}}/user_confirm/{{user.recover_code}}'>Confermalo e scegli una password</a>."
|
||||
"content": "Ciao, il tuo account su <a href='{{config.baseurl}}'>{{config.title}}</a> è stato creato.<br/> <a href='{{config.baseurl}}/user_confirm/{{user.recover_code}}'>Confermalo e scegli una password</a>."
|
||||
},
|
||||
"recover": {
|
||||
"subject": "Recupero password",
|
||||
"content": "Ciao, hai richiesto un recupero della password su {{config.title}}. <a href='{{config.baseurl}}/recover/{{user.recover_code}}'>Premi qui</a> per confermare."
|
||||
"content": "Ciao, hai richiesto un recupero della password su {{config.title}}.<br/> <a href='{{config.baseurl}}/recover/{{user.recover_code}}'>Premi qui</a> per confermare."
|
||||
},
|
||||
"admin_register": {
|
||||
"subject": "Nuova registrazione",
|
||||
"content": "{{user.email}} si è registrato/a a {{config.title}} scrivendo:<br/><pre>{{user.description}}</pre><br/> Puoi confermarlo <a href='{{config.baseurl}}/admin'>qui</a>."
|
||||
"content": "{{user.email}} si è registratǝ a {{config.title}} scrivendo:<br/><pre>{{user.description}}</pre><br/> Puoi confermarlo <a href='{{config.baseurl}}/admin'>qui</a>."
|
||||
}
|
||||
}
|
||||
|
|