Verified Commit 9472d8d9 authored by les's avatar les
Browse files

Merge branch 'dev' into gh

parents f9601492 9e9643e6
......@@ -5,6 +5,9 @@ config/development.json
gancio_config.json
config.json
db.sqlite
thumb
docs/_site
.vscode
### Node template
# Logs
......
All notable changes to this project will be documented in this file.
### 0.24.0
- New Euskara language from Basque Country, thanks @hacklabkelo
- fix feed with filters
- cleaner homepage
- fix next/prev for recurrent events
- fix some history navigation issue
- fix blank lines in description
- upgrade deps
- better custom logo management
- fix settings update
### 0.23.0
- send AP Event Object instead of Note
- show only future unconfirmed events in admin panel
- new polish locale
- add friendly instances (an additional menu is shown)
- use user's logo not project's logo in federation
- start WPGancio plugin
- support media upload via url (API only)
- confirm before remove a resource
- confirm before remove a friendly instance
- event description supports some html tag
- fix redirect after login
### 0.22.0
- add admin announcement support (fix #74)
- each instance has a specific timezone you can choose from admin's panel
- refactoring language management (you can choose a default instance's language):
usually UX language is choosen looking at Accepted-Language header but there
are messages not generated from an http request (eg. sending events via AP).
In those cases a default instance's language is choosen (default 'en').
- use lazy loaded images
- better mobile experience for admins
- single day only recurring events
- fix user block in fediverse moderation
- filter and linkify content from fediverse
- add a lot of help strings in admin panel
- use html2text for event description in og: meta
- update deps
- fix a moment.js typo from/to issue
- fix #73
### 0.21.0
- a new recurring events logic (a la taskwarrior):
- each occurrence of a recurring event could be personalized.
- occurrence of recurring events are skippable.
- occurrence generation could be paused.
- support `h-event` microformats! in homepage and in any single event's page
- add a background task manager (email, recurring events creation)
- sanitize html content coming from fediverse and event description with dompurify
- also remove `fbclid` params in links
- front end search optimization
- use oauth2 for everything, password flow for webclient, this also fix
some issue with authentication
- clickable tags / places @ home (to add them as filters)
- use a cleaner menububble for the new editor
- add local smtp and sendmail options on configuration setup
- check permission for resource removal request from fediverse
- add resource from fediverse also when inReplyTo is another resource
- automatic API documentation inclusion in docs
- split moderation in another tab inside admin panel
- use axios instead of fetch everywhere
- lot of ux improvements & error handling
- search links in event description with linkifyjs while add/edit events
- remove text templates for emails and use html2text instead
- fix email unique index for users!
### 0.20.0
- New layout (navbar/footer/visible filters)
- two month calendar on large display
- admin could edit title, description, about, favicon & logo directly form admin panel
- new add event layout => https://demo.gancio.org/add
- new editor to describe events (bold, italic, link) supported also using copy/paste
- the editor also support live markdown (try using ### at line start)
- start oauth2 server implementation (documentation: https://gancio.org/dev/oauth)
- add fediverse moderation
- fix embedding an event via iframe
- images converted in .webp
- new catalan translation, thanks @fadelkon
### 0.17.14
- [locale] add catalan
- [fix] fedi outbox
### 0.17.12
- [ui] add admin section on event page in mobile #63
- [fix] remove username from users
### 0.17.11
- [refactor] s/fed_user/ap_user
......
.editor {
position: relative;
overflow-y: auto;
padding-top: 1.7em;
scrollbar-width: thin;
&.with-border {
border: 1px solid #ddd;
border-radius: 5px;
}
.content {
padding: 0px 5px 0px 5px;
flex: 1;
scrollbar-width: thin;
overflow-y: auto;
}
.menububble {
position: absolute;
display: flex;
overflow: hidden;
opacity: 0;
z-index: 1;
background: #dddddd;
transform: translateX(-50%);
border-radius: 3px;
padding: 0.07rem;
transition: opacity 0.2s, visibility 0.2s, left .2s, bottom .2s;
visibility: hidden;
&.is-active {
opacity: 1;
visibility: visible;
}
input {
padding: 0;
margin: 1px;
display: block;
border: 0;
color: #444;
font-size: .8em;
border-radius: 3px;
line-height: 100%;
transition: width .2s;
padding-left: 5px;
flex-grow: 1;
}
.fa-icon {
width: auto;
font-size: 10px;
height: 1.4em; /* or any other relative font sizes */
/* You would have to include the following two lines to make this work in Safari */
// max-width: 100%;
max-height: 100%;
}
}
}
// .event {
// width: 320px;
// max-width: 450px;
// flex-grow: 1;
// margin: .2em;
// background-color: #202020;
// overflow: hidden;
// a:hover {
// text-decoration: none;
// .title {
// border-bottom: 1px solid #888;
// color: white;
// }
// }
// .title {
// margin-left: 1rem;
// margin-top: 1rem;
// margin-right: 1rem;
// border-bottom: 1px solid #333;
// transition: border-color .5s;
// font-size: 1.2em;
// max-height: 3em;
// overflow: hidden;
// color: white;
// font-weight: bold;
// }
// .card-footer {
// max-height: 4.5em;
// overflow: hidden;
// padding: .25rem 0.5rem;
// line-height: 1.8rem;
// min-height: 2.2rem;
// }
// .card-body {
// overflow: hidden;
// }
// .description {
// color: #999;
// font-size: 0.8em;
// overflow: hidden;
// max-height: 100%;
// }
// .el-image { width: 100% }
// img {
// width: 100%;
// max-height: 250px;
// object-fit: cover;
// object-position: top;
// }
// }
import take from 'lodash/take'
import get from 'lodash/get'
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(), highlight: { color: 'green', fillMode: 'outline' } })
function getColor (event) {
const color = { class: 'vc-rounded-full', color: 'blue', fillMode: 'normal' }
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(_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: getColor(e),
dates: { start: new Date(e.start_datetime * 1000), end: new Date(e.end_datetime * 1000) }
})))
return attributes
}
@home_background: #222C32;
@background: white;
@success: #c7ffbc;
#__nuxt, #__layout {
height: 100%;
html, body {
scrollbar-width: thin;
overflow: auto !important;
scrollbar-color: #FF4511 #111;
font-family: sans-serif;
}
#home {
background-color: @home_background;
min-height: 100%;
li {
margin-left: 10px;
}
a, a:hover {
text-decoration: none;
.v-dialog .theme--dark.v-card {
background-color: #434343;
}
html, body {
margin: 0px;
background-color: @background;
width: 100%;
height: 100%;
box-sizing: border-box;
font-family: BlinkMacSystemFont,-apple-system,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,Helvetica,Arial,sans-serif !important;
font-size: 15px;
.v-application .p-description.text-body-1 {
letter-spacing: normal !important;
}
* {
box-sizing: border-box;
}
.el-form-item {
margin-bottom: 5px;
#home {
max-width: 1400px;
padding-right: 0px;
overflow: hidden;
}
.el-main {
max-width: 1000px;
border-radius: 0px;
margin: auto;
padding: 10px;
overflow: unset;
#events {
margin: 0 auto;
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.el-select-dropdown {
max-width: 100%;
left: 0px;
.container {
max-width: 1400px;
}
.page-enter-active, .page-leave-active {
transition: opacity .2s, transform .3s;
}
.page-enter, .page-leave-active {
transition: opacity .3s, transform .2s;
opacity: 0;
transform: translateX(30px);
.v-dialog {
width: 600px;
max-width: 800px;
}
pre {
font-family: BlinkMacSystemFont,-apple-system,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,Helvetica,Arial,sans-serif !important;
margin-bottom: 0px;
white-space: pre-line;
font-family: unset;
.theme--dark.v-list {
background-color: #333;
}
.el-popover {
word-break: normal;
.v-autocomplete__content.v-menu__content {
scrollbar-width: thin;
scrollbar-color: #FF4511 #111;
}
.el-dialog {
z-index: 100;
max-width: 550px;
.el-dialog__body {
word-break: unset;
}
.el-dialog__headerbtn {
font-size: 27px;
top: 9px;
right: 10px;
.el-dialog__close {
color: red;
// padding: 5px;
// background-color: #f0f0f0;
// border-radius: 22px;
// border: 1px solid #f0f0f0;
}
}
p {
font-size: 16px;
// EVENT
.event {
display: flex;
position: relative;
flex-direction: column;
width: 330px;
max-width: 500px;
flex-grow: 1;
margin-top: .4em;
margin-right: .4em;
overflow: hidden;
.title {
display: block;
max-height: 3.3em;
overflow: hidden;
margin: 0.5rem 1rem 0.5rem 1rem;
// color: white;
border-bottom: 1px solid rgba(4,4,4,0.2);
font-size: 1.2em !important;
line-height: 1.1em;
font-weight: 500;
}
}
.el-input__icon {
font-size: 20px;
}
.body {
flex: 1 1 auto;
}
@media only screen and (max-width: 768px) {
.el-dialog {
margin-top: 0px !important;
border-radius: 0px;
.img {
width: 100%;
}
html {
font-size: 13px;
max-height: 250px;
object-fit: cover;
object-position: top;
}
.el-main {
margin-top: 0px !important;
border-radius: 0px;
padding: 5px;
.place {
max-width: 100%;
span {
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.el-menu-item {
padding: 0px 17px;
a {
text-decoration: none;
}
}
}
\ No newline at end of file
<template lang="pug">
nuxt-link(:to='`/announcement/${announcement.id}`')
v-alert.mb-1(border='left' type='info' color="primary" show-icon) {{announcement.title}}
</template>
<script>
import { mapState } from 'vuex'
export default {
props: {
announcement: { type: Object, default: () => ({}) }
},
computed: {
...mapState(['announcements']),
description () {
return this.announcement.announcement.replace(/(<br>)+/g, '<br>')
}
}
}
</script>
<template lang="pug">
#calendar
v-calendar(
vc-calendar(
title-position='left'
:is-dark="settings['theme.is_dark']"
:columns="$screens({ sm: 2 }, 1)"
@update:from-page='updatePage'
:locale='$i18n.locale'
is-dark
:attributes='attributes'
:from-page.sync='page'
transition='fade'
is-expanded
is-inline
@dayclick='click')
</template>
<script>
import { mapState, mapActions, mapGetters } from 'vuex'
import moment from 'dayjs'
import { intersection, sample, 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 }
}
},
watch: {
// month selected
page () {
this.updateEvents(this.page)
computed: {
...mapState(['tags', 'filters', 'in_past', 'settings']),
attributes () {
return attributesFromEvents(this.events, this.tags)
}
},
methods: {
...mapActions(['updateEvents']),
...mapActions(['updateEvents', 'showPastEvents']),
updatePage (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
}
},
computed: {
...mapGetters(['filteredEventsWithPast']),
...mapState(['tags', 'filters', 'in_past']),
// TODO: could be better
attributes () {
const colors = ['green', 'orange', 'yellow', 'teal', 'indigo', 'blue', 'red', 'purple', 'pink', 'grey']
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 => {
const color = getColor(e)
return {
key: e.id,
dot: color,
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
this.$emit('dayclick', day)
}
}
}
</script>
<style>
#calendar {
margin: 0 auto;
max-width: 500px;
align-self: center;
}
.vc-opacity-0 {
opacity: 0.2 !important;
opacity: 0.3 !important;
}
/* .vc-highlight {
color: red;
height: 22px !important;
opacity: 0.4;
border-radius: 15px;
} */
.past-event {
opacity: 0.2;
opacity: 0.3;
}
</style>
<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
* }