Merge branch 'master' into dev

This commit is contained in:
lesion 2022-09-12 17:14:27 +02:00
commit 1d0ba6f2c4
No known key found for this signature in database
GPG key ID: 352918250B012177
26 changed files with 2567 additions and 3944 deletions

1
.gitignore vendored
View file

@ -8,7 +8,6 @@
_*.js
config*.json
tests/seeds/testdb.sqlite
preso.md
gancio.sqlite
db.sqlite
releases

View file

@ -1,5 +1,14 @@
All notable changes to this project will be documented in this file.
### 1.5.4 - 6 set '22
- Update webcomponent deps
- Refactor datime display in webcomponent
- Force flyer download
- Restore range events on calendar
- Fix limit/max events for mariadb #183
- Fix endtime selection
- Fix microdata address
### 1.5.3 - 30 aug '22
- Fix end time selection when it's in the next day

View file

@ -1,31 +1,45 @@
import dayjs from 'dayjs'
export function attributesFromEvents (_events) {
export function attributesFromEvents(_events) {
// const colors = ['teal', 'green', 'yellow', 'teal', 'indigo', 'green', 'red', 'purple', 'pink', 'gray']
// merge events with same date
let attributes = []
const now = dayjs().unix()
for(let e of _events) {
for (let e of _events) {
const key = dayjs.unix(e.start_datetime).tz().format('YYYYMMDD')
const c = e.start_datetime < now ? 'vc-past' : ''
const c = (e.end_datetime || e.start_datetime) < now ? 'vc-past' : ''
if (e.multidate) {
attributes.push({
dates: { start: new Date(e.start_datetime * 1000), end: new Date(e.end_datetime * 1000) },
highlight: {
start: { fillMode: 'outline' },
base: { fillMode: 'light' },
end: { fillMode: 'outline' },
}
})
continue
}
const i = attributes.find(a => a.day === key)
if (!i) {
attributes.push({ day: key, key: e.id, n: 1, dates: new Date(e.start_datetime * 1000),
dot: { color: 'teal', class: c } })
attributes.push({
day: key, key: e.id, n: 1, dates: new Date(e.start_datetime * 1000),
dot: { color: 'teal', class: c }
})
continue
}
i.n++
if (i.n >= 20 ) {
if (i.n >= 20) {
i.dot = { color: 'purple', class: c }
} else if ( i.n >= 10 ) {
i.dot = { color: 'red', class: c}
} else if ( i.n >= 5 ) {
i.dot = { color: 'orange', class: c}
} else if ( i.n >= 3 ) {
i.dot = { color: 'yellow', class: c}
} else if (i.n >= 10) {
i.dot = { color: 'red', class: c }
} else if (i.n >= 5) {
i.dot = { color: 'orange', class: c }
} else if (i.n >= 3) {
i.dot = { color: 'yellow', class: c }
} else {
i.dot = { color: 'teal', class: c }
}

View file

@ -272,7 +272,7 @@ export default {
this.$emit('input', { ...this.value, from, due })
} else {
let from = value
let due = this.value.due || from
let due = this.value.due
if (this.fromHour) {
const [hour, minute] = this.fromHour.split(':')
from = dayjs.tz(value).hour(hour).minute(minute).second(0).toDate()

View file

@ -3,22 +3,22 @@ v-card.h-event.event.d-flex(itemscope itemtype="https://schema.org/Event")
nuxt-link(:to='`/event/${event.slug || event.id}`' itemprop="url")
MyPicture(:event='event' thumb :lazy='lazy')
v-icon.float-right.mr-1(v-if='event.parentId' color='success' v-text='mdiRepeat')
.title.p-name(itemprop="name") {{event.title}}
.title.p-name(itemprop="name") {{ event.title }}
v-card-text.body.pt-0.pb-0
time.dt-start.subtitle-1(:datetime='event.start_datetime|unixFormat("YYYY-MM-DD HH:mm")' itemprop="startDate" :content="event.start_datetime|unixFormat('YYYY-MM-DDTHH:mm')") <v-icon v-text='mdiCalendar'></v-icon> {{ event|when }}
.d-none.dt-end(itemprop="endDate" :content="event.end_datetime|unixFormat('YYYY-MM-DDTHH:mm')") {{event.end_datetime|unixFormat('YYYY-MM-DD HH:mm')}}
nuxt-link.place.d-block.p-location.pl-0(text color='primary' :to='`/place/${event.place.name}`' itemprop="location" :content="event.place.name") <v-icon v-text='mdiMapMarker'></v-icon> {{event.place.name}}
.d-none(itemprop='location.address') {{event.place.address}}
time.dt-start.subtitle-1(:datetime='event.start_datetime | unixFormat("YYYY-MM-DD HH:mm")' itemprop="startDate" :content="event.start_datetime | unixFormat('YYYY-MM-DDTHH:mm')") <v-icon v-text='mdiCalendar'></v-icon> {{ event | when }}
.d-none.dt-end(itemprop="endDate" :content="event.end_datetime | unixFormat('YYYY-MM-DDTHH:mm')") {{ event.end_datetime | unixFormat('YYYY-MM-DD HH:mm') }}
nuxt-link.place.d-block.p-location.pl-0(text color='primary' :to='`/place/${event.place.name}`' itemprop="location" itemscope itemtype="https://schema.org/Place") <v-icon v-text='mdiMapMarker'></v-icon> <span itemprop='name'>{{ event.place.name }}</span>
.d-none(itemprop='address') {{ event.place.address }}
v-card-actions.pt-0.actions.justify-space-between
.tags
v-chip.ml-1.mt-1(v-for='tag in event.tags.slice(0,6)' small :to='`/tag/${tag}`'
:key='tag' outlined color='primary') {{tag}}
v-chip.ml-1.mt-1(v-for='tag in event.tags.slice(0, 6)' small :to='`/tag/${tag}`'
:key='tag' outlined color='primary') {{ tag }}
client-only
v-menu(offset-y eager)
template(v-slot:activator="{on}")
template(v-slot:activator="{ on }")
v-btn.align-self-end(icon v-on='on' color='primary' title='more' aria-label='more')
v-icon(v-text='mdiDotsVertical')
v-list(dense)
@ -27,22 +27,22 @@ v-card.h-event.event.d-flex(itemscope itemtype="https://schema.org/Event")
v-list-item-icon
v-icon(v-text='mdiContentCopy')
v-list-item-content
v-list-item-title {{$t('common.copy_link')}}
v-list-item-title {{ $t('common.copy_link') }}
v-list-item(:href='`/api/event/${event.slug || event.id}.ics`')
v-list-item-icon
v-icon(v-text='mdiCalendarExport')
v-list-item-content
v-list-item-title {{$t('common.add_to_calendar')}}
v-list-item-title {{ $t('common.add_to_calendar') }}
v-list-item(v-if='is_mine' :to='`/add/${event.id}`')
v-list-item-icon
v-icon(v-text='mdiPencil')
v-list-item-content
v-list-item-title {{$t('common.edit')}}
v-list-item-title {{ $t('common.edit') }}
v-list-item(v-if='is_mine' @click='remove(false)')
v-list-item-icon
v-icon(color='error' v-text='mdiDeleteForever')
v-list-item-content
v-list-item-title {{$t('common.remove')}}
v-list-item-title {{ $t('common.remove') }}
template(#placeholder)
v-btn.align-self-end(icon color='primary' aria-label='more')
v-icon(v-text='mdiDotsVertical')
@ -51,13 +51,17 @@ v-card.h-event.event.d-flex(itemscope itemtype="https://schema.org/Event")
import { mapState } from 'vuex'
import clipboard from '../assets/clipboard'
import MyPicture from '~/components/MyPicture'
import { mdiRepeat, mdiPencil, mdiDotsVertical, mdiContentCopy,
mdiCalendarExport, mdiDeleteForever, mdiCalendar, mdiMapMarker } from '@mdi/js'
import {
mdiRepeat, mdiPencil, mdiDotsVertical, mdiContentCopy,
mdiCalendarExport, mdiDeleteForever, mdiCalendar, mdiMapMarker
} from '@mdi/js'
export default {
data () {
return { mdiRepeat, mdiPencil, mdiDotsVertical, mdiContentCopy, mdiCalendarExport,
mdiDeleteForever, mdiMapMarker, mdiCalendar }
data() {
return {
mdiRepeat, mdiPencil, mdiDotsVertical, mdiContentCopy, mdiCalendarExport,
mdiDeleteForever, mdiMapMarker, mdiCalendar
}
},
components: {
MyPicture
@ -69,7 +73,7 @@ export default {
mixins: [clipboard],
computed: {
...mapState(['settings']),
is_mine () {
is_mine() {
if (!this.$auth.user) {
return false
}
@ -79,13 +83,13 @@ export default {
}
},
methods: {
async remove () {
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)
this.$root.$message('admin.event_remove_ok')
}
}
}

View file

@ -1,10 +1,10 @@
<template lang='pug'>
v-container
v-card-title {{$t('common.announcements')}}
v-card-title {{ $t('common.announcements') }}
v-card-subtitle(v-html="$t('admin.announcement_description')")
v-dialog(v-model='dialog' width='800px' :fullscreen='$vuetify.breakpoint.xsOnly')
v-card
v-card-title {{$t('admin.new_announcement')}}
v-card-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'
@ -14,21 +14,21 @@ v-container
border no-save max-height='400px' :placeholder="$t('common.description')")
v-card-actions
v-spacer
v-btn(@click='dialog=false' color='error') {{$t('common.cancel')}}
v-btn(@click='save' color='primary' :disabled='!valid || loading' :loading='loading') {{$t(`common.${editing?'save':'send'}`)}}
v-btn(@click='dialog = false' color='error' outlined) {{ $t('common.cancel') }}
v-btn(@click='save' color='primary' :disabled='!valid || loading' :loading='loading' outlined) {{ $t(`common.${editing ? 'save' : 'send'}`) }}
v-btn(@click='openDialog' text color='primary') <v-icon v-text='mdiPlus'></v-icon> {{$t('common.add')}}
v-btn(@click='openDialog' text color='primary') <v-icon v-text='mdiPlus'></v-icon> {{ $t('common.add') }}
v-card-text
v-data-table(
v-if='announcements.length'
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
:headers='headers'
:items='announcements')
v-if='announcements.length'
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
:headers='headers'
:items='announcements')
template(v-slot:item.actions='{ item }')
v-btn(text small @click.stop='toggle(item)'
:color='item.visible?"warning":"success"') {{item.visible?$t('common.disable'):$t('common.enable')}}
v-btn(text small @click='edit(item)' color='primary') {{$t('common.edit')}}
v-btn(text small @click='remove(item)' color='error') {{$t('common.delete')}}
:color='item.visible ? "warning" : "success"') {{ item.visible ? $t('common.disable') : $t('common.enable') }}
v-btn(text small @click='edit(item)' color='primary') {{ $t('common.edit') }}
v-btn(text small @click='remove(item)' color='error') {{ $t('common.delete') }}
</template>
<script>
import { mapActions } from 'vuex'
@ -39,7 +39,7 @@ import { mdiPlus, mdiChevronRight, mdiChevronLeft } from '@mdi/js'
export default {
components: { Editor, Announcement },
data () {
data() {
return {
mdiPlus, mdiChevronRight, mdiChevronLeft,
valid: false,
@ -54,32 +54,32 @@ export default {
announcement: { title: '', announcement: '' }
}
},
async mounted () {
async mounted() {
this.announcements = await this.$axios.$get('/announcements')
},
methods: {
...mapActions(['setAnnouncements']),
edit (announcement) {
edit(announcement) {
this.announcement.title = announcement.title
this.announcement.announcement = announcement.announcement
this.announcement.id = announcement.id
this.editing = true
this.dialog = true
},
openDialog () {
openDialog() {
this.announcement = { title: '', announcement: '' }
this.dialog = true
this.$nextTick(() => this.$refs.announcement.reset())
},
async toggle (announcement) {
async toggle(announcement) {
try {
announcement.visible = !announcement.visible
await this.$axios.$put(`/announcements/${announcement.id}`, announcement)
this.announcements = this.announcements.map(a => a.id === announcement.id ? announcement : a)
this.setAnnouncements(cloneDeep(this.announcements.filter(a => a.visible)))
} catch (e) {}
} catch (e) { }
},
async remove (announcement) {
async remove(announcement) {
const ret = await this.$root.$confirm('admin.delete_announcement_confirm')
if (!ret) { return }
this.$axios.delete(`/announcements/${announcement.id}`)
@ -88,7 +88,7 @@ export default {
this.announcements = this.announcements.filter(a => a.id !== announcement.id)
})
},
async save () {
async save() {
if (!this.$refs.announcement.validate()) { return }
this.loading = true
try {

View file

@ -1,6 +1,6 @@
<template lang='pug'>
v-container
v-card-title {{$t('common.collections')}}
v-card-title {{ $t('common.collections') }}
v-spacer
v-text-field(v-model='search'
:append-icon='mdiMagnify' outlined rounded
@ -8,11 +8,11 @@ v-container
single-line hide-details)
v-card-subtitle(v-html="$t('admin.collections_description')")
v-btn(color='primary' text @click='newCollection') <v-icon v-text='mdiPlus'></v-icon> {{$t('admin.new_collection')}}
v-btn(color='primary' text @click='newCollection') <v-icon v-text='mdiPlus'></v-icon> {{ $t('admin.new_collection') }}
v-dialog(v-model='dialog' width='800' destroy-on-close :fullscreen='$vuetify.breakpoint.xsOnly')
v-card(color='secondary')
v-card-title {{$t('admin.edit_collection')}}
v-card
v-card-title {{ $t('admin.edit_collection') }}
v-card-text
v-form(v-model='valid' ref='form' @submit.prevent.native='saveCollection')
v-text-field(
@ -23,7 +23,7 @@ v-container
:placeholder='$t("common.name")')
template(v-slot:append-outer v-if='!collection.id')
v-btn(text @click='saveCollection' color='primary' :loading='loading'
:disabled='!valid || loading || !!collection.id') {{$t('common.save')}}
:disabled='!valid || loading || !!collection.id') {{ $t('common.save') }}
h3(v-else class='text-h5' v-text='collection.name')
v-row
@ -39,9 +39,9 @@ v-container
:delimiters="[',', ';']"
:items="tags"
:label="$t('common.tags')")
template(v-slot:selection="{ item, on, attrs, selected, parent}")
template(v-slot:selection="{ item, on, attrs, selected, parent }")
v-chip(v-bind="attrs" close :close-icon='mdiCloseCircle' @click:close='parent.selectItem(item)'
:input-value="selected" label small) {{item}}
:input-value="selected" label small) {{ item }}
v-col(cols=5)
v-autocomplete(v-model='filterPlaces'
@ -58,51 +58,51 @@ v-container
:delimiters="[',', ';']"
:items="places"
:label="$t('common.places')")
template(v-slot:selection="{ item, on, attrs, selected, parent}")
template(v-slot:selection="{ item, on, attrs, selected, parent }")
v-chip(v-bind="attrs" close :close-icon='mdiCloseCircle' @click:close='parent.selectItem(item)'
:input-value="selected" label small) {{item.name}}
:input-value="selected" label small) {{ item.name }}
//- template(v-slot:item="{ item, attrs, on }")
//- v-list-item(v-bind='attrs' v-on='on')
//- v-list-item-content(two-line)
//- v-list-item-title(v-text='item.name')
//- v-list-item-subtitle(v-text='item.address')
v-col(cols=2)
v-btn(color='primary' text @click='addFilter' :disabled='!collection.id || !filterPlaces.length && !filterTags.length') add <v-icon v-text='mdiPlus'></v-icon>
v-data-table(
:headers='filterHeaders'
:items='filters'
:hide-default-footer='filters.length<5'
:hide-default-footer='filters.length < 5'
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }')
template(v-slot:item.actions='{item}')
template(v-slot:item.actions='{ item }')
v-btn(@click='removeFilter(item)' color='error' icon)
v-icon(v-text='mdiDeleteForever')
template(v-slot:item.tags='{item}')
template(v-slot:item.tags='{ item }')
v-chip.ma-1(small v-for='tag in item.tags' v-text='tag' :key='tag')
template(v-slot:item.places='{item}')
template(v-slot:item.places='{ item }')
v-chip.ma-1(small v-for='place in item.places' v-text='place.name' :key='place.id' )
v-card-actions
v-spacer
v-btn(text @click='dialog=false' color='warning') {{$t('common.close')}}
v-spacer
v-btn(@click='dialog = false' outlined color='warning') {{ $t('common.close') }}
v-card-text
v-data-table(
:headers='collectionHeaders'
:items='collections'
:hide-default-footer='collections.length<5'
:hide-default-footer='collections.length < 5'
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
:search='search')
template(v-slot:item.filters='{item}')
span {{collectionFilters(item)}}
template(v-slot:item.actions='{item}')
v-btn(@click='editCollection(item)' color='primary' icon)
v-icon(v-text='mdiPencil')
v-btn(@click='removeCollection(item)' color='error' icon)
v-icon(v-text='mdiDeleteForever')
template(v-slot:item.filters='{ item }')
span {{ collectionFilters(item) }}
template(v-slot:item.actions='{ item }')
v-btn(@click='editCollection(item)' color='primary' icon)
v-icon(v-text='mdiPencil')
v-btn(@click='removeCollection(item)' color='error' icon)
v-icon(v-text='mdiDeleteForever')
</template>
<script>
@ -111,7 +111,7 @@ import debounce from 'lodash/debounce'
import { mdiPencil, mdiChevronLeft, mdiChevronRight, mdiMagnify, mdiPlus, mdiTagMultiple, mdiMapMarker, mdiDeleteForever, mdiCloseCircle } from '@mdi/js'
export default {
data () {
data() {
return {
mdiPencil, mdiChevronRight, mdiChevronLeft, mdiMagnify, mdiPlus, mdiTagMultiple, mdiMapMarker, mdiDeleteForever, mdiCloseCircle,
loading: false,
@ -139,7 +139,7 @@ export default {
]
}
},
async fetch () {
async fetch() {
this.collections = await this.$axios.$get('/collections?withFilters=true')
},
@ -150,14 +150,14 @@ export default {
searchPlaces: debounce(async function (ev) {
this.places = await this.$axios.$get(`/place?search=${ev.target.value}`)
}, 100),
collectionFilters (collection) {
collectionFilters(collection) {
return collection.filters.map(f => {
const tags = f.tags?.join(', ')
const tags = f.tags?.join(', ')
const places = f.places?.map(p => p.name).join(', ')
return '(' + (tags && places ? tags + ' - ' + places : tags + places) + ')'
}).join(' - ')
},
async addFilter () {
async addFilter() {
this.loading = true
const tags = this.filterTags
const places = this.filterPlaces.map(p => ({ id: p.id, name: p.name }))
@ -168,17 +168,17 @@ export default {
this.filterPlaces = []
this.loading = false
},
async editCollection (collection) {
async editCollection(collection) {
this.collection = { ...collection }
this.filters = await this.$axios.$get(`/filter/${collection.id}`)
this.dialog = true
},
newCollection () {
newCollection() {
this.collection = { name: '', id: null }
this.filters = []
this.dialog = true
},
async saveCollection () {
async saveCollection() {
if (!this.$refs.form.validate()) return
this.loading = true
this.collection.name = this.collection.name.trim()
@ -197,7 +197,7 @@ export default {
this.loading = false
}
},
async removeCollection (collection) {
async removeCollection(collection) {
const ret = await this.$root.$confirm('admin.delete_collection_confirm', { collection: collection.name })
if (!ret) { return }
try {
@ -211,4 +211,4 @@ export default {
}
}
}
</script>
</script>

View file

@ -1,6 +1,6 @@
<template lang='pug'>
v-container
v-card-title {{$t('common.places')}}
v-card-title {{ $t('common.places') }}
v-spacer
v-text-field(v-model='search'
:append-icon='mdiMagnify' outlined rounded
@ -9,8 +9,8 @@ v-container
v-card-subtitle(v-html="$t('admin.place_description')")
v-dialog(v-model='dialog' width='600' :fullscreen='$vuetify.breakpoint.xsOnly')
v-card(color='secondary')
v-card-title {{$t('admin.edit_place')}}
v-card
v-card-title {{ $t('admin.edit_place') }}
v-card-text
v-form(v-model='valid' ref='form' lazy-validation)
v-text-field(
@ -26,19 +26,19 @@ v-container
: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-spacer
v-btn(@click='dialog = false' outlined color='warning') {{ $t('common.cancel') }}
v-btn(@click='savePlace' color='primary' outlined :loading='loading'
:disable='!valid || loading') {{ $t('common.save') }}
v-card-text
v-data-table(
:headers='headers'
:items='places'
:hide-default-footer='places.length<5'
:hide-default-footer='places.length < 5'
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
:search='search')
template(v-slot:item.actions='{item}')
template(v-slot:item.actions='{ item }')
v-btn(@click='editPlace(item)' color='primary' icon)
v-icon(v-text='mdiPencil')
nuxt-link(:to='`/place/${item.name}`')
@ -49,7 +49,7 @@ v-container
import { mdiPencil, mdiChevronLeft, mdiChevronRight, mdiMagnify, mdiEye } from '@mdi/js'
export default {
data () {
data() {
return {
mdiPencil, mdiChevronRight, mdiChevronLeft, mdiMagnify, mdiEye,
loading: false,
@ -65,17 +65,17 @@ export default {
]
}
},
async fetch () {
async fetch() {
this.places = await this.$axios.$get('/place/all')
},
methods: {
editPlace (item) {
editPlace(item) {
this.place.name = item.name
this.place.address = item.address
this.place.id = item.id
this.dialog = true
},
async savePlace () {
async savePlace() {
if (!this.$refs.form.validate()) return
this.loading = true
await this.$axios.$put('/place', this.place)

File diff suppressed because it is too large Load diff

View file

@ -8,6 +8,16 @@ nav_order: 10
All notable changes to this project will be documented in this file.
### 1.5.4 - 6 set '22
- Update webcomponent deps
- Refactor datime display in webcomponent
- Force flyer download
- Restore range events on calendar
- Fix limit/max events for mariadb #183
- Fix endtime selection
- Fix microdata address
### 1.5.3 - 30 aug '22
- Fix end time selection when it's in the next day

View file

@ -1,6 +1,6 @@
{
"name": "gancio",
"version": "1.5.3",
"version": "1.5.4",
"description": "A shared agenda for local communities",
"author": "lesion",
"scripts": {

View file

@ -49,7 +49,7 @@ v-container#event.pa-0.pa-sm-2
:href='`/api/event/${event.slug || event.id}.ics`')
v-icon(v-text='mdiCalendarExport')
v-btn.ml-2(v-if='hasMedia' large icon :title="$t('event.download_flyer')" color='primary' :aria-label="$t('event.download_flyer')"
:href='event | mediaURL')
:href='event | mediaURL("download")')
v-icon(v-text='mdiFileDownloadOutline')
.p-description.text-body-1.pa-3.rounded(v-if='hasMedia && event.description' itemprop='description' v-html='event.description')
@ -331,4 +331,4 @@ export default {
}
}
}
</script>
</script>

View file

@ -42,14 +42,15 @@ export default ({ app, store }) => {
// shown in mobile homepage
Vue.filter('day', value => dayjs.unix(value).tz().locale(store.state.locale).format('dddd, D MMM'))
Vue.filter('mediaURL', (event, type, format = '.jpg') => {
const mediaPath = type === 'download' ? '/download/' : '/media/'
if (event.media && event.media.length) {
if (type === 'alt') {
return event.media[0].name
} else {
return store.state.settings.baseurl + '/media/' + (type === 'thumb' ? 'thumb/' : '') + event.media[0].url.replace(/.jpg$/, format)
return store.state.settings.baseurl + mediaPath + (type === 'thumb' ? 'thumb/' : '') + event.media[0].url.replace(/.jpg$/, format)
}
} else if (type !== 'alt') {
return store.state.settings.baseurl + '/media/' + (type === 'thumb' ? 'thumb/' : '') + 'logo.svg'
return store.state.settings.baseurl + mediaPath + (type === 'thumb' ? 'thumb/' : '') + 'logo.svg'
}
return ''
})

252
preso.md Normal file
View file

@ -0,0 +1,252 @@
# Gancio
_a shared agenda for local communities_
<small>
lesion / underscore hacklab / hackmeeting 0x19
</small>
--
- a brief history, where we come from
- where are we at
- where we are going
note: se qualcuno si sta chiedendo giustamente "ma ancora?"
--
## Intro
- is technology neutral? (hint: nope)
- there are choices based on values...
- ...and consequences
note: essendo uno dei primi talk rimarchero' un concetto che proprio la
comunita' di hackmeeting mi ha spiegato e non vorrei darlo per assodato in
questo contesto, ovvero che la tecnologia non e' neutrale ma facilita dei casi
d'uso, modifica l'ambito del possibile, quello che facilita e quello che
complica. gli strumenti sono formati dalla visione di chi li ha pensati,
progettati e costruiti e ne propagano i valori. nello sviluppo di strumenti ci
sono quindi scelte progettuali e ci sono delle conseguenze sulle scelte che
vengono fatte, questa e' la teoria. in pratica parliamo di quali sono le
impostazioni di default, quali sono le funzionalita' che scegliamo di
implementare o meno, quali sono i casi d'uso che vogliamo agevolare o meno.
sono domande importanti da farsi quando si sviluppa e quando si usa uno
strumento e cerchero' di spiegare un po' le scelte che sono state fatte su
gancio e perche'. ovviamente queste scelte sono ridiscutibili, siamo qui anche
per questo.
--
<blockquote><small>
... choices many of us in the social movements/left/activist scene make to be present in certain social networks, or to use certain technologies due to pragmatism - <strong>everybody is there</strong>, we need to reach 'common people', and so on. This is totally ok, but I feel we lack spaces to imagine which tools we need, which tech we would want to have if anything was possible? Do we want a FLOSS version of Instagram? Or do we want something completely different? Perhaps pragmatism allows the big tech tools to shape us and how we do our activism? What if we could shape the tools?
</small>
<span>absorto @ hackit_desiderata pad</span></blockquote>
note: tra le idee di tavole rotonde di quest'anno nel pad c'era questa serie di domande centrali.
questo non lo dico perche' penso che gancio sia chissa' che strumento
rivoluzionario, anzi. lo dico invece piu' che altro per spronarci tutti a farci
di questi ragionamenti e non solo per quanto riguarda gli strumenti tecnici.
dobbiamo chiederci cosa ci serve e perche'! non servono competenze per sognare e
desiderare, serve immaginarci dei modi altri, dei mondi altri. e questo sognare
lo ribadisco non si puo' lasciare ai nerd e basta.
---
### where we come from
- born from needs
note: carta canta, sgombero asilo 2019
--
## small & Local
- size matters
- small tech does not scale and it's ok
- local (no timezone)
note: progettando strumenti che devono scalare verso l'alto
costruiamo fondamentalmente centri di potere.
non e' solo una questione di software libero o della proprieta' del software...
se fb fosse nostro sarebbe comunque un problema, se il parlamento
fosse nostro sarebbe comunque un problema.
gancio non e' pensato per scalare, anzi, il caso d'uso facilitato
e' quello di un nodo legato ad un territorio e questa scelta
ha poi conseguenze sulla progettazione del sw e sulle conseguenze
nel suo utilizzo. ad es. una delle cons. di questa idea e' il fatto che
il fuso orario degli eventi e' uno per nodo, non per evento.
il caso d'uso poteva essere tematico ad esempio, nazionale, per posto....
conseguenze sull'uso > gli utenti sanno dove trovarti nella vita vera,
c'e' un rapporto, se domani gancio ha problemi c'e' un canale privilegiato
per comunicare.
--
## focus on content
nowhere on gancio appears the identity of who published the event, not even under a nickname, not even to administrators (except in the db).
This is not an ego-friendly platform, gamification is not aided.
note: altre scelte, non c'e' scritto da nessuna parte chi ha postato l'evento.
gamification non e' agevolata.
--
## random people first
We do not want logged user to get more features than random visitor.
People don't have to register to use it, not even to publish events.
note: eventi anonimi, gli eventi vanno confermati, possibilita' di modificare gli eventi?
--
## fuck walled garden
We are not interested in making hits, monitor user activities, sell data or ads: we export events in many ways, via RSS feeds, via global or individual ics calendar, embedding lists of events or single event via iframe or webcomponents on other websites, via h-event (microformat), via microdata, via ActivityPub, via API.
---
### 3 years later...
note: questo e' da dove siamo partiti...
--
### Status & Last Updates
- 25 known instances
- 11 languages
--
- https://gancio.cisti.org - Torino
- https://lapunta.org - Firenze
- https://sapratza.in - Sardegna
- https://ponente.rocks - Ponente Ligure
- https://bcn.convoca.la/ - Barcellona
- https://lubakiagenda.net/ - Bilbao
- https://bonn.jetzt/ - Bonn
- https://impending.events - Minneapolis
ma anche istanze tematiche:
- https://quest.livellosegreto.it - livello segreto
- https://events.osm.lat - OSM latino america
--
### Maintainance
> Another flaw in the human character
is that everybody wants to build
and nobody wants to do maintenance.<br/>
- Kurt Vonnegut
note: cosa ho fatto in questo tempo? principalmente c'e' un debito tecnico, la roba viene aggiornata, si scoprono bugs frequentemente,
aggiornando di scoprono altri bug! ogni feature si porta gatte da pelare notevoli.
--
Flyer download
note: non si poteva scaricare l'immagine associata ad un evento.
anche qui sono conseguenze indirette, [il componente](https://vuetifyjs.com/en/components/images/) della libreria che sto usando ha fatto altre scelte.
--
New time selection widget
--
Improve Recurrent events
--
Tag page
--
restrict new tag entropy
note: su questo c'e' ancora da fare per i po', debito tecnico
--
Place page
--
Redirect based on content-type
note: content-type cos'e' feed rss, ics, AP
--
Collection page
--
Add microdata support
--
sitemap
---
CLI
--
Add MariaDB supports
--
Improve SMTP configuration
--
footer links reordering
--
Unit Testing
--
Lot of fixes....
--
[API](https://gancio.org/dev/api)
--
### Webcomponent
<gancio-events baseurl='https://gancio.cisti.org' title='eventi' maxlength=4 theme='dark'/>
--
<gancio-events baseurl='https://gancio.cisti.org' title='eventi' maxlength=2 theme='dark' sidebar="false"/>
--
### WPGancio
---
## where are we going
--
### Plugins!
--
OSM integration
--
Generate
---
### Wanna help?
- let's think about what serves the community we want to build
- let's maintain the tools we already have
---
### References
- SITE: https://gancio.org
- DEMO: https://demo.gancio.org

4
reveal-md.json Normal file
View file

@ -0,0 +1,4 @@
{
"separator": "^---",
"verticalSeparator": "^--"
}

5
reveal.json Normal file
View file

@ -0,0 +1,5 @@
{
"controls": true,
"progress": true,
"transition": "slide"
}

View file

@ -23,18 +23,18 @@ const log = require('../../log')
const eventController = {
async searchMeta (req, res) {
async searchMeta(req, res) {
const search = req.query.search
const places = await Place.findAll({
order: [[Sequelize.col('w'), 'DESC']],
where: {
[Op.or]: [
Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), 'LIKE', '%' + search + '%' ),
Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), 'LIKE', '%' + search + '%'),
Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('address')), 'LIKE', '%' + search + '%')
]
},
attributes: [['name', 'label'], 'address', 'id', [Sequelize.cast(Sequelize.fn('COUNT', Sequelize.col('events.placeId')),'INTEGER'), 'w']],
attributes: [['name', 'label'], 'address', 'id', [Sequelize.cast(Sequelize.fn('COUNT', Sequelize.col('events.placeId')), 'INTEGER'), 'w']],
include: [{ model: Event, where: { is_visible: true }, required: true, attributes: [] }],
group: ['place.id'],
raw: true
@ -44,8 +44,8 @@ const eventController = {
order: [[Sequelize.col('w'), 'DESC']],
where: {
tag: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('tag')), 'LIKE', '%' + search + '%'),
},
attributes: [['tag','label'], [Sequelize.cast(Sequelize.fn('COUNT', Sequelize.col('tag.tag')), 'INTEGER'), 'w']],
},
attributes: [['tag', 'label'], [Sequelize.cast(Sequelize.fn('COUNT', Sequelize.col('tag.tag')), 'INTEGER'), 'w']],
include: [{ model: Event, where: { is_visible: true }, attributes: [], through: { attributes: [] }, required: true }],
group: ['tag.tag'],
raw: true
@ -57,13 +57,13 @@ const eventController = {
}).concat(tags.map(t => {
t.type = 'tag'
return t
})).sort( (a, b) => b.w - a.w).slice(0, 10)
})).sort((a, b) => b.w - a.w).slice(0, 10)
return res.json(ret)
},
async search (req, res) {
async search(req, res) {
const search = req.query.search.trim().toLocaleLowerCase()
const show_recurrent = req.query.show_recurrent || false
const end = req.query.end
@ -89,11 +89,11 @@ const eventController = {
if (search) {
replacements.push(search)
where[Op.or] =
[
{ title: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('title')), 'LIKE', '%' + search + '%') },
Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), 'LIKE', '%' + search + '%'),
Sequelize.fn('EXISTS', Sequelize.literal(`SELECT 1 FROM event_tags WHERE ${Col('event_tags.eventId')}=${Col('event.id')} AND LOWER(${Col('tagTag')}) = ?`))
]
[
{ title: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('title')), 'LIKE', '%' + search + '%') },
Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), 'LIKE', '%' + search + '%'),
Sequelize.fn('EXISTS', Sequelize.literal(`SELECT 1 FROM event_tags WHERE ${Col('event_tags.eventId')}=${Col('event.id')} AND LOWER(${Col('tagTag')}) = ?`))
]
}
@ -129,9 +129,9 @@ const eventController = {
},
async getNotifications (event, action) {
async getNotifications(event, action) {
log.debug(`getNotifications ${event.title} ${action}`)
function match (event, filters) {
function match(event, filters) {
// matches if no filter specified
if (!filters) { return true }
@ -170,7 +170,7 @@ const eventController = {
})
},
async get (req, res) {
async get(req, res) {
const format = req.params.format || 'json'
const is_admin = res.locals.user && res.locals.user.is_admin
const slug = req.params.event_slug
@ -222,7 +222,7 @@ const eventController = {
recurrent: null,
[Op.or]: [
{ start_datetime: { [Op.gt]: event.start_datetime } },
{
{
start_datetime: event.start_datetime,
id: { [Op.gt]: event.id }
}
@ -239,7 +239,7 @@ const eventController = {
recurrent: null,
[Op.or]: [
{ start_datetime: { [Op.lt]: event.start_datetime } },
{
{
start_datetime: event.start_datetime,
id: { [Op.lt]: event.id }
}
@ -270,7 +270,7 @@ const eventController = {
/** confirm an anonymous event
* and send related notifications
*/
async confirm (req, res) {
async confirm(req, res) {
const id = Number(req.params.event_id)
const event = await Event.findByPk(id, { include: [Place, Tag] })
if (!event) {
@ -299,7 +299,7 @@ const eventController = {
}
},
async unconfirm (req, res) {
async unconfirm(req, res) {
const id = Number(req.params.event_id)
const event = await Event.findByPk(id)
if (!event) { return req.sendStatus(404) }
@ -318,7 +318,7 @@ const eventController = {
},
/** get all unconfirmed events */
async getUnconfirmed (_req, res) {
async getUnconfirmed(_req, res) {
try {
const events = await Event.findAll({
where: {
@ -336,7 +336,7 @@ const eventController = {
}
},
async addNotification (req, res) {
async addNotification(req, res) {
try {
const notification = {
filters: { is_visible: true },
@ -351,7 +351,7 @@ const eventController = {
}
},
async delNotification (req, res) {
async delNotification(req, res) {
const remove_code = req.params.code
try {
const notification = await Notification.findOne({ where: { remove_code } })
@ -362,14 +362,14 @@ const eventController = {
res.sendStatus(200)
},
async isAnonEventAllowed (_req, res, next) {
async isAnonEventAllowed(_req, res, next) {
if (!res.locals.settings.allow_anon_event && !res.locals.user) {
return res.sendStatus(403)
}
next()
},
async add (req, res) {
async add(req, res) {
// req.err comes from multer streaming error
if (req.err) {
log.warn(req.err)
@ -380,7 +380,7 @@ const eventController = {
const body = req.body
const recurrent = body.recurrent ? JSON.parse(body.recurrent) : null
const required_fields = [ 'title', 'start_datetime']
const required_fields = ['title', 'start_datetime']
let missing_field = required_fields.find(required_field => !body[required_field])
if (missing_field) {
log.warn(`${missing_field} required`)
@ -398,7 +398,7 @@ const eventController = {
if (!body.place_name) {
return res.status(400).send(`Place not found`)
}
place = await Place.findOne({ where: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), Op.eq, body.place_name.trim().toLocaleLowerCase() )})
place = await Place.findOne({ where: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), Op.eq, body.place_name.trim().toLocaleLowerCase()) })
if (!place) {
if (!body.place_address || !body.place_name) {
return res.status(400).send(`place_id or place_name and place_address required`)
@ -479,7 +479,7 @@ const eventController = {
}
},
async update (req, res) {
async update(req, res) {
if (res.err) {
log.warn(req.err)
return res.status(400).json(req.err.toString())
@ -537,7 +537,7 @@ const eventController = {
} else if (body.image_focalpoint && event.media.length) {
let focalpoint = body.image_focalpoint ? body.image_focalpoint.split(',') : ['0', '0']
focalpoint = [parseFloat(parseFloat(focalpoint[0]).toFixed(2)), parseFloat(parseFloat(focalpoint[1]).toFixed(2))]
eventDetails.media = [ { ...event.media[0], focalpoint } ] // [0].focalpoint = focalpoint
eventDetails.media = [{ ...event.media[0], focalpoint }] // [0].focalpoint = focalpoint
}
await event.update(eventDetails)
@ -552,7 +552,7 @@ const eventController = {
if (!body.place_name) {
return res.status(400).send(`Place not found`)
}
place = await Place.findOne({ where: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), Op.eq, body.place_name.trim().toLocaleLowerCase() )})
place = await Place.findOne({ where: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), Op.eq, body.place_name.trim().toLocaleLowerCase()) })
if (!place) {
if (!body.place_address || !body.place_name) {
return res.status(400).send(`place_id or place_name and place_address required`)
@ -565,7 +565,7 @@ const eventController = {
}
await event.setPlace(place)
// create/assign tags
let tags = []
if (body.tags) {
@ -593,7 +593,7 @@ const eventController = {
}
},
async remove (req, res) {
async remove(req, res) {
const event = await Event.findByPk(req.params.id)
// check if event is mine (or user is admin)
if (event && (res.locals.user.is_admin || res.locals.user.id === event.userId)) {
@ -626,7 +626,7 @@ const eventController = {
* Method to search for events with pagination and filtering
* @returns
*/
async _select ({
async _select({
start = dayjs().unix(),
end,
tags,
@ -665,8 +665,8 @@ const eventController = {
const replacements = []
if (tags && places) {
where[Op.and] = [
{ placeId: places ? places.split(',') : []},
where[Op.and] = [
{ placeId: places ? places.split(',') : [] },
Sequelize.fn('EXISTS', Sequelize.literal(`SELECT 1 FROM event_tags WHERE ${Col('event_tags.eventId')}=${Col('event.id')} AND LOWER(${Col('tagTag')}) in (?)`))
]
replacements.push(tags)
@ -679,10 +679,10 @@ const eventController = {
let pagination = {}
if (limit) {
pagination = {
pagination = {
limit,
offset: limit * page,
}
}
}
const events = await Event.findAll({
@ -690,7 +690,7 @@ const eventController = {
attributes: {
exclude: ['likes', 'boost', 'userId', 'is_visible', 'createdAt', 'description', 'resources', 'recurrent', 'placeId', 'image_path']
},
order: [['start_datetime', older ? 'DESC' : 'ASC' ]],
order: [['start_datetime', older ? 'DESC' : 'ASC']],
include: [
{
model: Tag,
@ -717,14 +717,14 @@ const eventController = {
/**
* Select events based on params
*/
async select (req, res) {
async select(req, res) {
const settings = res.locals.settings
const start = req.query.start || dayjs().unix()
const end = req.query.end
const tags = req.query.tags
const places = req.query.places
const limit = req.query.max
const page = req.query.page = 0
const limit = Number(req.query.max) || 0
const page = Number(req.query.page) || 0
const older = req.query.older || false
const show_recurrent = settings.allow_recurrent_event &&
@ -738,7 +738,7 @@ const eventController = {
/**
* Ensure we have the next instance of a recurrent event
*/
async _createRecurrentOccurrence (e, startAt) {
async _createRecurrentOccurrence(e, startAt) {
log.debug(`Create recurrent event [${e.id}] ${e.title}"`)
const event = {
parentId: e.id,
@ -798,12 +798,12 @@ const eventController = {
/**
* Create instances of recurrent events
*/
async _createRecurrent (start_datetime = dayjs().unix()) {
async _createRecurrent(start_datetime = dayjs().unix()) {
// select recurrent events and its childs
const events = await Event.findAll({
where: { is_visible: true, recurrent: { [Op.ne]: null } },
include: [{ model: Tag, required: false },
{ model: Event, as: 'child', required: false, where: { start_datetime: { [Op.gte]: start_datetime } }}],
{ model: Event, as: 'child', required: false, where: { start_datetime: { [Op.gte]: start_datetime } } }],
order: [['child', 'start_datetime', 'DESC']]
})
@ -811,7 +811,7 @@ const eventController = {
const creations = events.map(e => {
if (e.child.length) {
if (e.child.find(c => c.is_visible)) return
return eventController._createRecurrentOccurrence(e, dayjs.unix(e.child[0].start_datetime+1))
return eventController._createRecurrentOccurrence(e, dayjs.unix(e.child[0].start_datetime + 1))
}
return eventController._createRecurrentOccurrence(e, dayjs())
})

View file

@ -108,6 +108,9 @@ module.exports = {
const router = express.Router()
// serve images/thumb
router.use('/media/', express.static(config.upload_path, { immutable: true, maxAge: '1y' }), (_req, res) => res.sendStatus(404))
router.use('/download/:filename', (req, res, next) => {
return res.download(req.params.filename, undefined, { root: config.upload_path }, err => res.status(404).send('Not found (but nice try 😊)'))
})
router.use('/noimg.svg', express.static('./static/noimg.svg'))
router.use('/logo.png', (req, res, next) => {

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,7 @@ let token
let app
let places = []
beforeAll( async () => {
beforeAll(async () => {
switch (process.env.DB) {
case 'mariadb':
process.env.config_path = path.resolve(__dirname, './seeds/config.mariadb.json')
@ -32,7 +32,7 @@ beforeAll( async () => {
await sequelize.query('DELETE FROM filters')
})
afterAll( async () => {
afterAll(async () => {
await require('../server/initialize.server.js').shutdown(false)
})
@ -77,7 +77,7 @@ describe('Authentication / Authorization', () => {
expect(response.body.token_type).toBe('Bearer')
token = response.body
})
test('should get user when authenticated', async () => {
const response = await request(app).get('/api/user')
.auth(token.access_token, { type: 'bearer' })
@ -105,16 +105,16 @@ describe('Settings', () => {
test('should retrieve stored array settings', async () => {
await request(app).post('/api/settings')
.auth(token.access_token, { type: 'bearer' })
.send({ key: 'test', value: [1,2,'test'] })
.send({ key: 'test', value: [1, 2, 'test'] })
.expect(200)
const response = await request(app)
.get('/api/settings')
.auth(token.access_token, { type: 'bearer' })
.expect(200)
expect(response.body.test.length).toBe(3)
expect(response.body.test).toStrictEqual([1,2,'test'])
expect(response.body.test).toStrictEqual([1, 2, 'test'])
})
@ -128,7 +128,7 @@ describe('Settings', () => {
.get('/api/settings')
.auth(token.access_token, { type: 'bearer' })
.expect(200)
expect(response.body.test.name).toBe('test object')
})
@ -143,9 +143,9 @@ describe('Settings', () => {
.get('/api/settings')
.auth(token.access_token, { type: 'bearer' })
.expect(200)
expect(response.body.test).toBe('test string')
})
})
})
@ -155,7 +155,7 @@ describe('Events', () => {
const required_fields = {
'title': {},
'start_datetime': { title: 'test title' },
'place_id or place_name and place_address': { title: 'test title', start_datetime: dayjs().unix()+1000, place_name: 'test place name'},
'place_id or place_name and place_address': { title: 'test title', start_datetime: dayjs().unix() + 1000, place_name: 'test place name' },
}
const promises = Object.keys(required_fields).map(async field => {
@ -171,15 +171,15 @@ describe('Events', () => {
test('should create anon event only when allowed', async () => {
await request(app).post('/api/settings')
.send({ key: 'allow_anon_event', value: false })
.auth(token.access_token, { type: 'bearer' })
.send({ key: 'allow_anon_event', value: false })
.auth(token.access_token, { type: 'bearer' })
.expect(200)
await request(app).post('/api/event')
.expect(403)
let response = await request(app).post('/api/event')
.send({ title: 'test title 2', place_name: 'place name', place_address: 'address', tags: ['test'], start_datetime: dayjs().unix()+1000 })
.send({ title: 'test title 2', place_name: 'place name', place_address: 'address', tags: ['test'], start_datetime: dayjs().unix() + 1000 })
.auth(token.access_token, { type: 'bearer' })
.expect(200)
@ -189,10 +189,10 @@ describe('Events', () => {
await request(app).post('/api/settings')
.send({ key: 'allow_anon_event', value: true })
.auth(token.access_token, { type: 'bearer' })
.expect(200)
.expect(200)
response = await request(app).post('/api/event')
.send({ title: 'test title 3', place_name: 'place name 2', place_address: 'address 2', tags: ['test'], start_datetime: dayjs().unix()+1000 })
.send({ title: 'test title 3', place_name: 'place name 2', place_address: 'address 2', tags: ['test'], start_datetime: dayjs().unix() + 1000 })
.expect(200)
expect(response.body.place.id).toBeDefined()
@ -204,7 +204,7 @@ describe('Events', () => {
const event = {
title: 'test title 4',
place_id: places[0],
start_datetime: dayjs().unix()+1000,
start_datetime: dayjs().unix() + 1000,
tags: [' test tag ']
}
@ -221,7 +221,7 @@ let event = {}
describe('Tags', () => {
test('should create event with tags', async () => {
event = await request(app).post('/api/event')
.send({ title: 'test tags', place_id: places[1], start_datetime: dayjs().unix()+1000 , tags: ['tag1', 'Tag2', 'tAg3'] })
.send({ title: 'test tags', place_id: places[1], start_datetime: dayjs().unix() + 1000, tags: ['tag1', 'Tag2', 'tAg3'] })
.auth(token.access_token, { type: 'bearer' })
.expect(200)
@ -231,7 +231,7 @@ describe('Tags', () => {
test('should create event trimming tags / ignore sensitiviness', async () => {
const ret = await request(app).post('/api/event')
.send({ title: 'test trimming tags', place_id: places[1], start_datetime: dayjs().unix()+1000, tags: ['Tag1', 'taG2 '] })
.send({ title: 'test trimming tags', place_id: places[1], start_datetime: dayjs().unix() + 1000, tags: ['Tag1', 'taG2 '] })
.auth(token.access_token, { type: 'bearer' })
.expect(200)
@ -243,9 +243,9 @@ describe('Tags', () => {
test('should modify event tags', async () => {
const ret = await request(app).put('/api/event')
.send({ id: event.body.id, tags: ['tag1', 'tag3', 'tag4'], place_id: places[1] })
.auth(token.access_token, { type: 'bearer' })
.expect(200)
.send({ id: event.body.id, tags: ['tag1', 'tag3', 'tag4'], place_id: places[1] })
.auth(token.access_token, { type: 'bearer' })
.expect(200)
expect(ret.body.tags).toStrictEqual(['tag1', 'tAg3', 'tag4'])
})
@ -253,18 +253,29 @@ describe('Tags', () => {
test('should return events searching for tags', async () => {
const response = await request(app).get('/api/events?tags=tAg3')
.expect(200)
expect(response.body.length).toBe(1)
// expect(response.body[0].title).toBe('test tags')
expect(response.body[0].tags.length).toBe(3)
})
test('should return limited events', async () => {
let response = await request(app).get('/api/events?max=1')
.expect(200)
expect(response.body.length).toBe(1)
response = await request(app).get('/api/events?max=2')
.expect(200)
expect(response.body.length).toBe(2)
})
})
describe('Place', () => {
test('should get events by place', async () => {
const response = await request(app).get('/api/place/place name 2')
.expect(200)
expect(response.body.place.name).toBe('place name 2')
expect(response.body.events.length).toBe(2)
expect(response.body.events[0].place.name).toBe('place name 2')
@ -277,7 +288,7 @@ describe('Place', () => {
const response = await request(app).get('/api/place/all')
.auth(token.access_token, { type: 'bearer' })
.expect(200)
expect(response.body.length).toBe(2)
})
@ -285,7 +296,7 @@ describe('Place', () => {
test('should search for a place', async () => {
const response = await request(app).get('/api/place?search=place')
.expect(200)
expect(response.body.length).toBe(2)
})
@ -293,7 +304,7 @@ describe('Place', () => {
let collections = []
let filters = []
describe ('Collection', () => {
describe('Collection', () => {
test('should not create a new collection if not allowed', () => {
return request(app).post('/api/collections')
.send({ name: 'test collection' })
@ -313,8 +324,8 @@ describe ('Collection', () => {
const response = await request(app).get('/api/collections/test collection')
.expect(200)
expect(response.body.length).toBe(0)
})
expect(response.body.length).toBe(0)
})
test('should add a new filter', async () => {
await request(app)
@ -384,7 +395,7 @@ describe ('Collection', () => {
// .expect(200)
// expect(response.body.length).toBe(1)
response = await request(app)
response = await request(app)
.get(`/api/collections/test collection`)
.expect(200)

View file

@ -9,8 +9,8 @@
"serve": "vite preview"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.34",
"svelte": "^3.46.3",
"vite": "^2.7.11"
"@sveltejs/vite-plugin-svelte": "^1.0.4",
"svelte": "^3.50.0",
"vite": "^3.0.9"
}
}

View file

@ -1,16 +1,19 @@
<svelte:options tag="gancio-event" />
<script>
import { onMount } from 'svelte'
import { when } from './helpers'
export let baseurl = 'https://demo.gancio.org'
export let id
let mounted = false
let event
function update (id, baseurl) {
function update(id, baseurl) {
if (mounted) {
fetch(`${baseurl}/api/event/${id}`)
.then(res => res.json())
.then(e => event = e)
.then((res) => res.json())
.then((e) => (event = e))
}
}
@ -20,85 +23,82 @@
})
$: update(id, baseurl)
function when (event) {
return new Date(event.start_datetime*1000)
.toLocaleDateString(undefined,
{
weekday: 'long',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
})
}
function thumbnail(event) {
return `${baseurl}/media/thumb/${event.media[0].url}`
}
function position(event) {
if (event.media[0].focalpoint) {
const focalpoint = event.media[0].focalpoint
return `${(focalpoint[0] + 1) * 50}% ${(focalpoint[1] + 1) * 50}%`
}
return 'center center'
const focalpoint = event.media[0].focalpoint
return `${(focalpoint[0] + 1) * 50}% ${(focalpoint[1] + 1) * 50}%`
}
return 'center center'
}
</script>
{#if event}
<a
href="{baseurl}/event/{event.slug || event.id}"
class="card"
target="_blank"
>
{#if event.media.length}
<img
src={thumbnail(event)}
alt={event.media[0].name}
style="object-position: {position(event)}; aspect-ratio=1.7778;"
/>
{/if}
<div class="container">
<strong>{event.title}</strong>
<div>{when(event)}</div>
<div class="place">@{event.place.name}</div>
</div>
</a>
{/if}
<style>
.card {
display: block;
font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS',
sans-serif;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
transition: 0.3s;
border-radius: 5px; /* 5px rounded corners */
max-width: 500px;
text-decoration: none;
color: white;
background-color: #1e1e1e;
overflow: hidden;
}
</script>
<svelte:options tag="gancio-event"/>
{#if event}
<a href='{baseurl}/event/{event.slug || event.id}' class='card' target='_blank'>
{#if event.media.length}
<img src="{thumbnail(event)}" alt="{event.media[0].name}" style="object-position: {position(event)}; aspect-ratio=1.7778;">
{/if}
<div class="container">
<strong>{event.title}</strong>
<div>{when(event)}</div>
<div class='place'>@{event.place.name}</div>
</div>
</a>
{/if}
<style>
.card {
display: block;
font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
transition: 0.3s;
border-radius: 5px; /* 5px rounded corners */
max-width: 500px;
text-decoration: none;
color: white;
background-color: #1e1e1e;
overflow: hidden;
}
/* Add rounded corners to the top left and the top right corner of the image */
img {
border-radius: 5px 5px 0 0;
max-height: 250px;
min-height: 160px;
width: 100%;
object-fit: cover;
object-position: top;
}
/* Add rounded corners to the top left and the top right corner of the image */
img {
border-radius: 5px 5px 0 0;
max-height: 250px;
min-height: 160px;
width: 100%;
object-fit: cover;
object-position: top;
}
.card:hover .container {
padding-left: 20px;
}
.card:hover .container {
padding-left: 20px;
}
/* On mouse-over, add a deeper shadow */
.card:hover {
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
}
/* On mouse-over, add a deeper shadow */
.card:hover {
box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);
}
/* Add some padding inside the card container */
.container {
transition: padding-left 0.2s;
padding: 16px;
}
/* Add some padding inside the card container */
.container {
transition: padding-left .2s;
padding: 16px;
}
.place {
font-weight: 600;
color: #ff6e40;
}
.place {
font-weight: 600;
color: #ff6e40;
}
</style>

View file

@ -1,13 +1,15 @@
<script>
<svelte:options tag="gancio-events" />
<script>
import { onMount } from 'svelte'
import { when } from './helpers'
export let baseurl = ''
export let title = ''
export let maxlength = false
export let tags = ''
export let places = ''
export let theme = 'light'
export let show_recurrent=false
export let show_recurrent = false
export let sidebar = 'true'
export let external_style = ''
@ -15,14 +17,14 @@
let mounted = false
let events = []
function update (v) {
function update(v) {
if (!mounted) return
const params = []
if (maxlength) {
params.push(`max=${maxlength}`)
}
if(tags) {
if (tags) {
params.push(`tags=${tags}`)
}
@ -30,246 +32,259 @@
params.push(`places=${places}`)
}
params.push(`show_recurrent=${show_recurrent?'true':'false'}`)
params.push(`show_recurrent=${show_recurrent ? 'true' : 'false'}`)
fetch(`${baseurl}/api/events?${params.join('&')}`)
.then(res => res.json())
.then(e => {
events = e
})
.catch(e => {
console.error('Error loading Gancio API -> ', e)
})
.then((res) => res.json())
.then((e) => {
events = e
})
.catch((e) => {
console.error('Error loading Gancio API -> ', e)
})
}
function position(event) {
if (event.media[0].focalpoint) {
const focalpoint = event.media[0].focalpoint
return `${(focalpoint[0] + 1) * 50}% ${(focalpoint[1] + 1) * 50}%`
}
return 'center center'
}
function when (timestamp) {
return new Date(timestamp*1000)
.toLocaleDateString(undefined,
{
weekday: 'long',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
})
if (event.media && event.media[0].focalpoint) {
const focalpoint = event.media[0].focalpoint
return `${(focalpoint[0] + 1) * 50}% ${(focalpoint[1] + 1) * 50}%`
}
return 'center center'
}
onMount(() => {
mounted = true
update()
})
$: update(maxlength && title && places && tags && theme && show_recurrent && sidebar)
$: update(
maxlength && title && places && tags && theme && show_recurrent && sidebar
)
</script>
<svelte:options tag="gancio-events"/>
{#if external_style}<link rel='stylesheet' href='{external_style}' />{/if}
{#if external_style}<link rel="stylesheet" href={external_style} />{/if}
{#if events.length}
<div id='gancioEvents'
class:dark="{theme === 'dark'}" class:light="{theme === 'light'}"
class:sidebar="{sidebar === 'true'}" class:nosidebar="{sidebar !== 'true'}">
{#if title && sidebar === 'true'}
<a href='{baseurl}' target='_blank' id='header'>
<div class='content'>
<div class='title'>{title}</div>
<img id='logo' alt='logo' src='{baseurl}/logo.png'/>
</div>
</a>
{/if}
{#each events as event}
<a href='{baseurl}/event/{event.slug || event.id}' class='event' title='{event.title}' target='_blank'>
{#if sidebar !== 'true'}
<div class='img'>
{#if event.media.length}
<img style="object-position: {position(event)}; aspect-ratio=1.7778;"
alt="{event.media[0].name}"
src="{baseurl + '/media/thumb/' + event.media[0].url}" loading='lazy'/>
{:else}
<img style="aspect-ratio=1.7778;"
alt="{event.title}"
src="{baseurl + '/noimg.svg'}" loading='lazy'/>
{/if}
</div>
{/if}
<div class='content'>
<div class='subtitle'>
{when(event.start_datetime)}
<div
id="gancioEvents"
class:dark={theme === 'dark'}
class:light={theme === 'light'}
class:sidebar={sidebar === 'true'}
class:nosidebar={sidebar !== 'true'}
>
{#if title && sidebar === 'true'}
<a href={baseurl} target="_blank" id="header">
<div class="content">
<div class="title">{title}</div>
<img id="logo" alt="logo" src="{baseurl}/logo.png" />
</div>
<div class='title'>
{event.title}
</div>
<span class='place'>@{event.place.name} <span class='subtitle'> {event.place.address}</span></span>
{#if event.tags.length}
<div class='tags'>
{#each event.tags as tag}
<span class='tag'>#{tag}</span>
{/each}
</a>
{/if}
{#each events as event}
<a
href="{baseurl}/event/{event.slug || event.id}"
class="event"
title={event.title}
target="_blank"
>
{#if sidebar !== 'true'}
<div class="img">
{#if event.media.length}
<img
style="object-position: {position(event)}; aspect-ratio=1.7778;"
alt={event.media[0].name}
src={baseurl + '/media/thumb/' + event.media[0].url}
loading="lazy"
/>
{:else}
<img
style="aspect-ratio=1.7778;"
alt={event.title}
src={baseurl + '/noimg.svg'}
loading="lazy"
/>
{/if}
</div>
{/if}
</div>
</a>
<div class="content">
<div class="subtitle">
{when(event)}
</div>
<div class="title">
{event.title}
</div>
<span class="place"
>@{event.place.name}
<span class="subtitle"> {event.place.address}</span></span
>
{#if event.tags.length}
<div class="tags">
{#each event.tags as tag}
<span class="tag">#{tag}</span>
{/each}
</div>
{/if}
</div>
</a>
{/each}
</div>
</div>
{/if}
<style>
#gancioEvents {
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
overflow-x: hidden;
width: 100%;
box-sizing: content-box;
margin: 0 auto;
font-size: 1rem;
}
.nosidebar {
max-width: 1200px;
}
#header{
padding: 1.2rem 1rem;
background-color: var(--bg-odd-color);
}
.sidebar {
max-width: 500px;
box-shadow: rgba(60, 64, 67, 0.4) 0px 1px 2px 0px, rgba(60, 64, 67, 0.25) 0px 1px 3px 1px;
border-radius: 5px;
font-size: 1rem;
}
.event .img {
width: 100%;
max-width: 450px;
max-height: 250px;
aspect-ratio: 1.7778;
flex: 1 0 auto;
/* height: 100%; */
}
@media screen and (max-width: 800px) {
.event {
flex-wrap: wrap;
#gancioEvents {
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
overflow-x: hidden;
width: 100%;
box-sizing: content-box;
margin: 0 auto;
font-size: 1rem;
text-align: left;
}
.nosidebar {
max-width: 1200px;
}
#header {
padding: 1.2rem 1rem;
background-color: var(--bg-odd-color);
}
.sidebar {
max-width: 500px;
box-shadow: rgba(60, 64, 67, 0.4) 0px 1px 2px 0px,
rgba(60, 64, 67, 0.25) 0px 1px 3px 1px;
border-radius: 5px;
font-size: 1rem;
}
.event .img {
max-width: 100%;
width: 100%;
max-width: 450px;
max-height: 250px;
aspect-ratio: 1.7778;
flex: 1 0 auto;
/* height: 100%; */
}
}
.event img {
object-fit: cover;
border-radius: 15px;
width: 100%;
height: 100%;
box-shadow: rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px;
}
.nosidebar .event {
margin-bottom: 2rem;
}
@media screen and (max-width: 800px) {
.event {
flex-wrap: wrap;
}
.event .img {
max-width: 100%;
}
}
.event img {
object-fit: cover;
border-radius: 15px;
width: 100%;
height: 100%;
box-shadow: rgba(50, 50, 93, 0.25) 0px 6px 12px -2px,
rgba(0, 0, 0, 0.3) 0px 3px 7px -3px;
}
.nosidebar .content {
margin-left: 1rem;
margin-top: 5px;
text-align: left;
}
.nosidebar .event {
margin-bottom: 2rem;
}
.tags {
margin-top: 2px;
}
.nosidebar .content {
margin-left: 1rem;
margin-top: 5px;
text-align: left;
}
#logo {
position: absolute;
top: 10px;
right: 10px;
height: 40px;
}
.tags {
margin-top: 2px;
}
a {
text-decoration: none;
color: var(--text-color);
display: flex;
padding: 8px 20px;
margin: 0;
line-height: 1.275rem;
font-weight: 400;
font-size: .875rem;
position: relative;
transition: background-color .3s cubic-bezier(.25,.8,.5,1), padding .3s;
box-sizing: content-box;
}
#logo {
position: absolute;
top: 10px;
right: 10px;
height: 40px;
}
a:hover .title,
a:focus .title,
a:active .title {
text-decoration:underline;
}
a {
text-decoration: none;
color: var(--text-color);
display: flex;
padding: 8px 20px;
margin: 0;
line-height: 1.275rem;
font-weight: 400;
font-size: 0.875rem;
position: relative;
transition: background-color 0.3s cubic-bezier(0.25, 0.8, 0.5, 1),
padding 0.3s;
box-sizing: content-box;
}
.dark {
--bg-odd-color: #161616;
--bg-even-color: #222;
--bg-hover-color: #333;
--text-color: white;
--title-color: white;
--line-color: rgba(120, 120, 120, 0.2);
}
a:hover .title,
a:focus .title,
a:active .title {
text-decoration: underline;
}
.light {
--bg-odd-color: #f5f5f5;
--bg-even-color: #FAFAFA;
--bg-hover-color: #EEE;
--text-color: #222;
--title-color: black;
--line-color: rgba(220, 220, 220, 0.9);
}
.sidebar a {
background-color: var(--bg-even-color);
border-bottom:1px solid var(--line-color);
}
.dark {
--bg-odd-color: #161616;
--bg-even-color: #222;
--bg-hover-color: #333;
--text-color: white;
--title-color: white;
--line-color: rgba(120, 120, 120, 0.2);
}
.sidebar a:hover,
.sidebar a:focus,
.sidebar a:active {
background-color: var(--bg-hover-color);
padding-left: 15px;
padding-right:25px;
}
.light {
--bg-odd-color: #f5f5f5;
--bg-even-color: #fafafa;
--bg-hover-color: #eee;
--text-color: #222;
--title-color: black;
--line-color: rgba(220, 220, 220, 0.9);
}
.sidebar a {
background-color: var(--bg-even-color);
border-bottom: 1px solid var(--line-color);
}
.place {
font-weight: 400;
font-size: 1.2rem;
line-height: 1.4rem;
color: orangered;
}
.sidebar a:hover,
.sidebar a:focus,
.sidebar a:active {
background-color: var(--bg-hover-color);
padding-left: 15px;
padding-right: 25px;
}
.title {
color: var(--title-color);
font-weight: bold;
font-size: 1.3rem;
line-height: 1.1em;
}
.place {
font-weight: 400;
font-size: 1.2rem;
line-height: 1.4rem;
color: orangered;
}
.nosidebar .title {
font-size: 1.9em;
line-height: 1.1em;
}
.title {
color: var(--title-color);
font-weight: bold;
font-size: 1.3rem;
line-height: 1.1em;
}
.subtitle {
font-size: 1rem;
line-height: 1.1em;
color: var(--title-color);
opacity: 0.9;
}
.nosidebar .title {
font-size: 1.9em;
line-height: 1.1em;
}
.tag {
margin-right: 10px;
display: inline-block;
}
.subtitle {
font-size: 1rem;
line-height: 1.1em;
color: var(--title-color);
opacity: 0.9;
}
.tag {
margin-right: 10px;
display: inline-block;
}
</style>

View file

@ -0,0 +1,24 @@
function formatDatetime(timestamp, type = 'long') {
const options =
type === 'long'
? {
weekday: 'long',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
}
: { hour: '2-digit', minute: '2-digit' }
return new Date(timestamp * 1000).toLocaleString(undefined, options)
}
export function when(event) {
if (event.multidate) {
return formatDatetime(event.start_datetime) + ' - ' + formatDatetime(event.end_datetime)
}
return (
formatDatetime(event.start_datetime) +
(event.end_datetime ? '-' + formatDatetime(event.end_datetime, 'short') : '')
)
}

View file

@ -2,157 +2,169 @@
# yarn lockfile v1
"@rollup/pluginutils@^4.1.2":
version "4.2.0"
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.0.tgz#a14bbd058fdbba0a5647143b16ed0d86fb60bd08"
integrity sha512-2WUyJNRkyH5p487pGnn4tWAsxhEFKN/pT8CMgHshd5H+IXkOnKvKZwsz5ZWz+YCXkleZRAU5kwbfgF8CPfDRqA==
"@esbuild/linux-loong64@0.14.54":
version "0.14.54"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz#de2a4be678bd4d0d1ffbb86e6de779cde5999028"
integrity sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==
"@rollup/pluginutils@^4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d"
integrity sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==
dependencies:
estree-walker "^2.0.1"
picomatch "^2.2.2"
"@sveltejs/vite-plugin-svelte@^1.0.0-next.34":
version "1.0.0-next.39"
resolved "https://registry.yarnpkg.com/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.0-next.39.tgz#b9437de18d13a475f76cf603511174cdf905b8d4"
integrity sha512-gnvvcAW2LK+KnUn8lKb2ypcXKwSp2K57mem5C4VNKfjxdRpM6+XwNavWwVf6otnDhz3qPYl/TKKW6/dRr6eeAw==
"@sveltejs/vite-plugin-svelte@^1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.4.tgz#363a0adeb9221c35abb65197c6db0754b9994a08"
integrity sha512-UZco2fdj0OVuRWC0SUJjEOftITc2IeHLFJNp00ym9MuQ9dShnlO4P29G8KUxRlcS7kSpzHuko6eCR9MOALj7lQ==
dependencies:
"@rollup/pluginutils" "^4.1.2"
debug "^4.3.3"
kleur "^4.1.4"
magic-string "^0.25.7"
svelte-hmr "^0.14.9"
"@rollup/pluginutils" "^4.2.1"
debug "^4.3.4"
deepmerge "^4.2.2"
kleur "^4.1.5"
magic-string "^0.26.2"
svelte-hmr "^0.14.12"
debug@^4.3.3:
version "4.3.3"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
debug@^4.3.4:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
dependencies:
ms "2.1.2"
esbuild-android-64@0.14.25:
version "0.14.25"
resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.25.tgz#d532d38cb5fe0ae45167ce35f4bbc784c636be40"
integrity sha512-L5vCUk7TzFbBnoESNoXjU3x9+/+7TDIE/1mTfy/erAfvZAqC+S3sp/Qa9wkypFMcFvN9FzvESkTlpeQDolREtQ==
deepmerge@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
esbuild-android-arm64@0.14.25:
version "0.14.25"
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.25.tgz#9c5bb3366aabfd14a1c726d36978b79441dfcb6e"
integrity sha512-4jv5xPjM/qNm27T5j3ZEck0PvjgQtoMHnz4FzwF5zNP56PvY2CT0WStcAIl6jNlsuDdN63rk2HRBIsO6xFbcFw==
esbuild-android-64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz#505f41832884313bbaffb27704b8bcaa2d8616be"
integrity sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==
esbuild-darwin-64@0.14.25:
version "0.14.25"
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.25.tgz#05dcdb6d884f427039ffee5e92ff97527e56c26d"
integrity sha512-TGp8tuudIxOyWd1+8aYPxQmC1ZQyvij/AfNBa35RubixD0zJ1vkKHVAzo0Zao1zcG6pNqiSyzfPto8vmg0s7oA==
esbuild-android-arm64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz#8ce69d7caba49646e009968fe5754a21a9871771"
integrity sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==
esbuild-darwin-arm64@0.14.25:
version "0.14.25"
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.25.tgz#28e080da4ea0cfe9498071e7f8060498caee1a95"
integrity sha512-oTcDgdm0MDVEmw2DWu8BV68pYuImpFgvWREPErBZmNA4MYKGuBRaCiJqq6jZmBR1x+3y1DWCjez+5uLtuAm6mw==
esbuild-darwin-64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz#24ba67b9a8cb890a3c08d9018f887cc221cdda25"
integrity sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==
esbuild-freebsd-64@0.14.25:
version "0.14.25"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.25.tgz#200d3664a3b945bc9fdcba73614b49a11ebd1cfa"
integrity sha512-ueAqbnMZ8arnuLH8tHwTCQYeptnHOUV7vA6px6j4zjjQwDx7TdP7kACPf3TLZLdJQ3CAD1XCvQ2sPhX+8tacvQ==
esbuild-darwin-arm64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz#3f7cdb78888ee05e488d250a2bdaab1fa671bf73"
integrity sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==
esbuild-freebsd-arm64@0.14.25:
version "0.14.25"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.25.tgz#624b08c5da6013bdc312aaa23c4ff409580f5c3c"
integrity sha512-+ZVWud2HKh+Ob6k/qiJWjBtUg4KmJGGmbvEXXW1SNKS7hW7HU+Zq2ZCcE1akFxOPkVB+EhOty/sSek30tkCYug==
esbuild-freebsd-64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz#09250f997a56ed4650f3e1979c905ffc40bbe94d"
integrity sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==
esbuild-linux-32@0.14.25:
version "0.14.25"
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.25.tgz#0238e597eb0b60aa06c7e98fccbbfd6bb9a0d6c5"
integrity sha512-3OP/lwV3kCzEz45tobH9nj+uE4ubhGsfx+tn0L26WAGtUbmmcRpqy7XRG/qK7h1mClZ+eguIANcQntYMdYklfw==
esbuild-freebsd-arm64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz#bafb46ed04fc5f97cbdb016d86947a79579f8e48"
integrity sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==
esbuild-linux-64@0.14.25:
version "0.14.25"
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.25.tgz#8a8b8cf47dfce127c858e71229d9a385a82c62e8"
integrity sha512-+aKHdHZmX9qwVlQmu5xYXh7GsBFf4TWrePgeJTalhXHOG7NNuUwoHmketGiZEoNsWyyqwH9rE5BC+iwcLY30Ug==
esbuild-linux-32@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz#e2a8c4a8efdc355405325033fcebeb941f781fe5"
integrity sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==
esbuild-linux-arm64@0.14.25:
version "0.14.25"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.25.tgz#7ac94371418a2640ba413bc1700aaedeb2794e52"
integrity sha512-UxfenPx/wSZx55gScCImPtXekvZQLI2GW3qe5dtlmU7luiqhp5GWPzGeQEbD3yN3xg/pHc671m5bma5Ns7lBHw==
esbuild-linux-64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz#de5fdba1c95666cf72369f52b40b03be71226652"
integrity sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==
esbuild-linux-arm@0.14.25:
version "0.14.25"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.25.tgz#034bd18e9310b9f010c89f90ef7f05706689600b"
integrity sha512-aTLcE2VBoLydL943REcAcgnDi3bHtmULSXWLbjtBdtykRatJVSxKMjK9YlBXUZC4/YcNQfH7AxwVeQr9fNxPhw==
esbuild-linux-arm64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz#dae4cd42ae9787468b6a5c158da4c84e83b0ce8b"
integrity sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==
esbuild-linux-mips64le@0.14.25:
version "0.14.25"
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.25.tgz#05f98a8cf6b578eab6b4e6b0ab094f37530934f4"
integrity sha512-wLWYyqVfYx9Ur6eU5RT92yJVsaBGi5RdkoWqRHOqcJ38Kn60QMlcghsKeWfe9jcYut8LangYZ98xO1LxIoSXrQ==
esbuild-linux-arm@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz#a2c1dff6d0f21dbe8fc6998a122675533ddfcd59"
integrity sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==
esbuild-linux-ppc64le@0.14.25:
version "0.14.25"
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.25.tgz#46fd0add8d8535678439d7a9c2876ad20042d952"
integrity sha512-0dR6Csl6Zas3g4p9ULckEl8Mo8IInJh33VCJ3eaV1hj9+MHGdmDOakYMN8MZP9/5nl+NU/0ygpd14cWgy8uqRw==
esbuild-linux-mips64le@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz#d9918e9e4cb972f8d6dae8e8655bf9ee131eda34"
integrity sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==
esbuild-linux-riscv64@0.14.25:
version "0.14.25"
resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.25.tgz#ea2e986f0f3e5df73c635135dd778051734fc605"
integrity sha512-J4d20HDmTrgvhR0bdkDhvvJGaikH3LzXQnNaseo8rcw9Yqby9A90gKUmWpfwqLVNRILvNnAmKLfBjCKU9ajg8w==
esbuild-linux-ppc64le@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz#3f9a0f6d41073fb1a640680845c7de52995f137e"
integrity sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==
esbuild-linux-s390x@0.14.25:
version "0.14.25"
resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.25.tgz#efe89486e9a1b1508925048076e3f3a6698aa6a3"
integrity sha512-YI2d5V6nTE73ZnhEKQD7MtsPs1EtUZJ3obS21oxQxGbbRw1G+PtJKjNyur+3t6nzHP9oTg6GHQ3S3hOLLmbDIQ==
esbuild-linux-riscv64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz#618853c028178a61837bc799d2013d4695e451c8"
integrity sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==
esbuild-netbsd-64@0.14.25:
version "0.14.25"
resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.25.tgz#439fe27d8ee3b5887501ee63988e85f920107db6"
integrity sha512-TKIVgNWLUOkr+Exrye70XTEE1lJjdQXdM4tAXRzfHE9iBA7LXWcNtVIuSnphTqpanPzTDFarF0yqq4kpbC6miA==
esbuild-linux-s390x@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz#d1885c4c5a76bbb5a0fe182e2c8c60eb9e29f2a6"
integrity sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==
esbuild-openbsd-64@0.14.25:
version "0.14.25"
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.25.tgz#31ebf616aadf6e60674469f2b92cec92280d9930"
integrity sha512-QgFJ37A15D7NIXBTYEqz29+uw3nNBOIyog+3kFidANn6kjw0GHZ0lEYQn+cwjyzu94WobR+fes7cTl/ZYlHb1A==
esbuild-netbsd-64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz#69ae917a2ff241b7df1dbf22baf04bd330349e81"
integrity sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==
esbuild-sunos-64@0.14.25:
version "0.14.25"
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.25.tgz#815e4f936d74970292a63ccfd5791fe5e3569f5f"
integrity sha512-rmWfjUItYIVlqr5EnTH1+GCxXiBOC42WBZ3w++qh7n2cS9Xo0lO5pGSG2N+huOU2fX5L+6YUuJ78/vOYvefeFw==
esbuild-openbsd-64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz#db4c8495287a350a6790de22edea247a57c5d47b"
integrity sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==
esbuild-windows-32@0.14.25:
version "0.14.25"
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.25.tgz#189e14df2478f2c193c86968ab1fb54e1ceaafd2"
integrity sha512-HGAxVUofl3iUIz9W10Y9XKtD0bNsK9fBXv1D55N/ljNvkrAYcGB8YCm0v7DjlwtyS6ws3dkdQyXadbxkbzaKOA==
esbuild-sunos-64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz#54287ee3da73d3844b721c21bc80c1dc7e1bf7da"
integrity sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==
esbuild-windows-64@0.14.25:
version "0.14.25"
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.25.tgz#3d5fbfdc3856850bb47439299e3b60dd18be111f"
integrity sha512-TirEohRkfWU9hXLgoDxzhMQD1g8I2mOqvdQF2RS9E/wbkORTAqJHyh7wqGRCQAwNzdNXdg3JAyhQ9/177AadWA==
esbuild-windows-32@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz#f8aaf9a5667630b40f0fb3aa37bf01bbd340ce31"
integrity sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==
esbuild-windows-arm64@0.14.25:
version "0.14.25"
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.25.tgz#8b243cbbad8a86cf98697da9ccb88c05df2ef458"
integrity sha512-4ype9ERiI45rSh+R8qUoBtaj6kJvUOI7oVLhKqPEpcF4Pa5PpT3hm/mXAyotJHREkHpM87PAJcA442mLnbtlNA==
esbuild-windows-64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz#bf54b51bd3e9b0f1886ffdb224a4176031ea0af4"
integrity sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==
esbuild@^0.14.14:
version "0.14.25"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.25.tgz#ddb9d47b91ca76abb7d850ce3dfed0bc3dc88d16"
integrity sha512-4JHEIOMNFvK09ziiL+iVmldIhLbn49V4NAVo888tcGFKedEZY/Y8YapfStJ6zSE23tzYPKxqKwQBnQoIO0BI/Q==
esbuild-windows-arm64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz#937d15675a15e4b0e4fafdbaa3a01a776a2be982"
integrity sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==
esbuild@^0.14.47:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.54.tgz#8b44dcf2b0f1a66fc22459943dccf477535e9aa2"
integrity sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==
optionalDependencies:
esbuild-android-64 "0.14.25"
esbuild-android-arm64 "0.14.25"
esbuild-darwin-64 "0.14.25"
esbuild-darwin-arm64 "0.14.25"
esbuild-freebsd-64 "0.14.25"
esbuild-freebsd-arm64 "0.14.25"
esbuild-linux-32 "0.14.25"
esbuild-linux-64 "0.14.25"
esbuild-linux-arm "0.14.25"
esbuild-linux-arm64 "0.14.25"
esbuild-linux-mips64le "0.14.25"
esbuild-linux-ppc64le "0.14.25"
esbuild-linux-riscv64 "0.14.25"
esbuild-linux-s390x "0.14.25"
esbuild-netbsd-64 "0.14.25"
esbuild-openbsd-64 "0.14.25"
esbuild-sunos-64 "0.14.25"
esbuild-windows-32 "0.14.25"
esbuild-windows-64 "0.14.25"
esbuild-windows-arm64 "0.14.25"
"@esbuild/linux-loong64" "0.14.54"
esbuild-android-64 "0.14.54"
esbuild-android-arm64 "0.14.54"
esbuild-darwin-64 "0.14.54"
esbuild-darwin-arm64 "0.14.54"
esbuild-freebsd-64 "0.14.54"
esbuild-freebsd-arm64 "0.14.54"
esbuild-linux-32 "0.14.54"
esbuild-linux-64 "0.14.54"
esbuild-linux-arm "0.14.54"
esbuild-linux-arm64 "0.14.54"
esbuild-linux-mips64le "0.14.54"
esbuild-linux-ppc64le "0.14.54"
esbuild-linux-riscv64 "0.14.54"
esbuild-linux-s390x "0.14.54"
esbuild-netbsd-64 "0.14.54"
esbuild-openbsd-64 "0.14.54"
esbuild-sunos-64 "0.14.54"
esbuild-windows-32 "0.14.54"
esbuild-windows-64 "0.14.54"
esbuild-windows-arm64 "0.14.54"
estree-walker@^2.0.1:
version "2.0.2"
@ -176,22 +188,22 @@ has@^1.0.3:
dependencies:
function-bind "^1.1.1"
is-core-module@^2.8.1:
version "2.8.1"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211"
integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==
is-core-module@^2.9.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed"
integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==
dependencies:
has "^1.0.3"
kleur@^4.1.4:
version "4.1.4"
resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.4.tgz#8c202987d7e577766d039a8cd461934c01cda04d"
integrity sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==
kleur@^4.1.5:
version "4.1.5"
resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780"
integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==
magic-string@^0.25.7:
version "0.25.9"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==
magic-string@^0.26.2:
version "0.26.3"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.26.3.tgz#25840b875140f7b4785ab06bddc384270b7dd452"
integrity sha512-u1Po0NDyFcwdg2nzHT88wSK0+Rih0N1M+Ph1Sp08k8yvFFU3KR72wryS7e1qMPJypt99WB7fIFVCA92mQrMjrg==
dependencies:
sourcemap-codec "^1.4.8"
@ -200,10 +212,10 @@ ms@2.1.2:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
nanoid@^3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35"
integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==
nanoid@^3.3.4:
version "3.3.4"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
path-parse@^1.0.7:
version "1.0.7"
@ -220,28 +232,28 @@ picomatch@^2.2.2:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
postcss@^8.4.6:
version "8.4.8"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.8.tgz#dad963a76e82c081a0657d3a2f3602ce10c2e032"
integrity sha512-2tXEqGxrjvAO6U+CJzDL2Fk2kPHTv1jQsYkSoMeOis2SsYaXRO2COxTdQp99cYvif9JTXaAk9lYGc3VhJt7JPQ==
postcss@^8.4.16:
version "8.4.16"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.16.tgz#33a1d675fac39941f5f445db0de4db2b6e01d43c"
integrity sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==
dependencies:
nanoid "^3.3.1"
nanoid "^3.3.4"
picocolors "^1.0.0"
source-map-js "^1.0.2"
resolve@^1.22.0:
version "1.22.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
resolve@^1.22.1:
version "1.22.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
dependencies:
is-core-module "^2.8.1"
is-core-module "^2.9.0"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
rollup@^2.59.0:
version "2.70.0"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.70.0.tgz#17a92e5938e92a251b962352e904c9f558230ec7"
integrity sha512-iEzYw+syFxQ0X9RefVwhr8BA2TNJsTaX8L8dhyeyMECDbmiba+8UQzcu+xZdji0+JQ+s7kouQnw+9Oz5M19XKA==
"rollup@>=2.75.6 <2.77.0 || ~2.77.0":
version "2.77.3"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.77.3.tgz#8f00418d3a2740036e15deb653bed1a90ee0cc12"
integrity sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==
optionalDependencies:
fsevents "~2.3.2"
@ -260,24 +272,24 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
svelte-hmr@^0.14.9:
version "0.14.11"
resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.14.11.tgz#63d532dc9c2c849ab708592f034765fa2502e568"
integrity sha512-R9CVfX6DXxW1Kn45Jtmx+yUe+sPhrbYSUp7TkzbW0jI5fVPn6lsNG9NEs5dFg5qRhFNAoVdRw5qQDLALNKhwbQ==
svelte-hmr@^0.14.12:
version "0.14.12"
resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.14.12.tgz#a127aec02f1896500b10148b2d4d21ddde39973f"
integrity sha512-4QSW/VvXuqVcFZ+RhxiR8/newmwOCTlbYIezvkeN6302YFRE8cXy0naamHcjz8Y9Ce3ITTZtrHrIL0AGfyo61w==
svelte@^3.46.3:
version "3.46.4"
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.46.4.tgz#0c46bc4a3e20a2617a1b7dc43a722f9d6c084a38"
integrity sha512-qKJzw6DpA33CIa+C/rGp4AUdSfii0DOTCzj/2YpSKKayw5WGSS624Et9L1nU1k2OVRS9vaENQXp2CVZNU+xvIg==
svelte@^3.50.0:
version "3.50.0"
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.50.0.tgz#d11a7a6bd1e084ec051d55104a9af8bccf54461f"
integrity sha512-zXeOUDS7+85i+RxLN+0iB6PMbGH7OhEgjETcD1fD8ZrhuhNFxYxYEHU41xuhkHIulJavcu3PKbPyuCrBxdxskQ==
vite@^2.7.11:
version "2.8.6"
resolved "https://registry.yarnpkg.com/vite/-/vite-2.8.6.tgz#32d50e23c99ca31b26b8ccdc78b1d72d4d7323d3"
integrity sha512-e4H0QpludOVKkmOsRyqQ7LTcMUDF3mcgyNU4lmi0B5JUbe0ZxeBBl8VoZ8Y6Rfn9eFKYtdXNPcYK97ZwH+K2ug==
vite@^3.0.9:
version "3.0.9"
resolved "https://registry.yarnpkg.com/vite/-/vite-3.0.9.tgz#45fac22c2a5290a970f23d66c1aef56a04be8a30"
integrity sha512-waYABTM+G6DBTCpYAxvevpG50UOlZuynR0ckTK5PawNVt7ebX6X7wNXHaGIO6wYYFXSM7/WcuFuO2QzhBB6aMw==
dependencies:
esbuild "^0.14.14"
postcss "^8.4.6"
resolve "^1.22.0"
rollup "^2.59.0"
esbuild "^0.14.47"
postcss "^8.4.16"
resolve "^1.22.1"
rollup ">=2.75.6 <2.77.0 || ~2.77.0"
optionalDependencies:
fsevents "~2.3.2"

File diff suppressed because it is too large Load diff