#266 [calendar] multiple years, day view

This commit is contained in:
Avris 2021-10-03 16:37:00 +02:00
parent 2b799b516c
commit 0b6feb406a
18 changed files with 234 additions and 77 deletions

View File

@ -1,43 +1,51 @@
<template>
<div class="calendar">
<div v-for="i in (startingDayOfWeek - 1)"></div>
<div v-for="d in iterateMonth(year, month)"
:class="['rounded-circle', getDayClass(d), markToday && d.equals(today) ? 'day-today' : '', d.equals(selectedDay) ? 'day-selected' : '']"
@click.stop="selectDay(d)"
:data-flag="getDayFlag(d)"
:style="getDayFlag(d) ? `background-image: url('${getDayFlag(d)}')` : ''"
<component :is="tooltips || getDayClass(d) === 'day' ? 'div' : 'nuxt-link'"
v-for="d in iterateMonth(year.year, month)" :key="d.toString()"
:to="`/${config.calendar.route}/${d}`"
:class="['rounded-circle', getDayClass(d), mark && d.equals(mark) ? 'day-today' : '', d.equals(selectedDay) ? 'day-selected' : '']"
@click.stop="selectDay(d)"
:data-flag="getDayFlag(d)"
:style="getDayFlag(d) ? `background-image: url('${getDayFlag(d)}')` : ''"
>
<div class="day-number">{{ d.day }}</div>
<div v-if="currentYear.eventsByDate[d.toString()] !== undefined && d.equals(selectedDay)" class="day-tooltip card text-dark shadow">
<div v-if="tooltips && year.eventsByDate[d.toString()] !== undefined && d.equals(selectedDay)" class="day-tooltip card text-dark shadow">
<div class="card-header d-flex justify-content-between">
<p class="h5 mb-0"><strong><T :params="{day: d.day}">calendar.dates.{{d.month}}</T></strong></p>
<button class="btn btn-sm py-0" @clik="selectedDay = null"><Icon v="times"/></button>
<span>
<nuxt-link :to="`/${config.calendar.route}/${d}`">
<Icon v="link"/>
<T>calendar.link</T>
</nuxt-link>
<button class="btn btn-sm py-0" @clik="selectedDay = null">
<Icon v="times"/>
</button>
</span>
</div>
<div class="card-body">
<ul class="list-unstyled mb-0">
<CalendarEvent v-for="event in currentYear.eventsByDate[d.toString()]" :event="event" :key="event.name"/>
<CalendarEvent v-for="event in year.eventsByDate[d.toString()]" :event="event" :key="event.name"/>
</ul>
</div>
</div>
</div>
</component>
</div>
</template>
<script>
import { Day, iterateMonth, EventLevel } from '../src/calendar/helpers';
import { currentYear } from '../src/calendar/calendar';
export default {
props: {
year: { required: true },
month: { required: true },
markToday: { type: Boolean },
mark: { },
tooltips: { type: Boolean },
},
data() {
return {
iterateMonth,
currentYear,
today: Day.today(),
selectedDay: null,
}
},
@ -60,17 +68,17 @@
},
computed: {
startingDayOfWeek() {
return new Day(this.year, this.month, 1).dayOfWeek;
return new Day(this.year.year, this.month, 1).dayOfWeek;
}
},
methods: {
getDayClass(d) {
if (this.currentYear.eventsByDate[d.toString()] === undefined) {
if (this.year.eventsByDate[d.toString()] === undefined) {
return 'day';
}
let maxLevel = 0;
for (let event of this.currentYear.eventsByDate[d.toString()]) {
for (let event of this.year.eventsByDate[d.toString()]) {
if (event.level > maxLevel) {
maxLevel = event.level;
}
@ -79,7 +87,7 @@
return `day day-event day-event-${maxLevel}`;
},
getDayFlag(d) {
for (let event of (this.currentYear.eventsByDate[d.toString()] || []).filter(e => e.level === EventLevel.Day && e.flag)) {
for (let event of (this.year.eventsByDate[d.toString()] || []).filter(e => e.level === EventLevel.Day && e.flag)) {
return `/flags/${event.flag}.png`;
}
return null;
@ -90,6 +98,9 @@
}
},
selectDay(d) {
if (!this.tooltips) {
return;
}
if (d.equals(this.selectedDay)) {
this.selectedDay = null;
} else {

View File

@ -15,16 +15,17 @@
</template>
<script>
import { currentYear } from '../src/calendar/calendar';
import { calendar } from '../src/calendar/calendar';
import { Day } from '../src/calendar/helpers';
export default {
props: {
day: { 'default': () => Day.today() },
link: { type: Boolean },
},
data() {
return {
events: currentYear.eventsByDate[Day.today().toString()],
events: calendar.getCurrentYear().eventsByDate[this.day.toString()],
}
}
}

View File

@ -0,0 +1,49 @@
<template>
<section>
<div class="alert alert-info d-flex flex-column flex-md-row justify-content-around">
<div v-if="day">
<p class="mb-0">
<T>calendar.full</T>:
</p>
<nuxt-link :to="`/${config.calendar.route}/${day.year}`" class="btn btn-outline-primary m-1">
<Icon v="calendar-star"/>
{{ day.year }}
</nuxt-link>
</div>
<div>
<p class="mb-0">
Twitter Bot:
</p>
<p class="mb-0">
<a href="https://twitter.com/CalendarQueer" target="_blank" rel="noopener" class="btn btn-outline-primary m-1">
<Icon v="twitter" set="b"/>
@CalendarQueer
</a>
</p>
</div>
<div>
<p class="mb-0">
<T>calendar.image.header</T>:
</p>
<p class="mb-0">
<a href="/calendar/overview.png" target="_blank" rel="noopener" class="btn btn-outline-primary m-1">
<Icon v="image"/>
<T>calendar.image.overview</T>
</a>
<a href="/calendar/labels.png" target="_blank" rel="noopener" class="btn btn-outline-primary m-1">
<Icon v="image"/>
<T>calendar.image.labels</T>
</a>
</p>
</div>
</div>
</section>
</template>
<script>
export default {
props: {
day: {},
}
}
</script>

View File

@ -1,12 +1,11 @@
<template>
<ul class="list-unstyled mb-0">
<CalendarEvent v-for="event in events" :event="event" :range="year" :key="event.name"/>
<CalendarEvent v-for="event in events" :event="event" :range="year.year" :key="event.name"/>
</ul>
</template>
<script>
import { iterateMonth } from '../src/calendar/helpers';
import { currentYear } from '../src/calendar/calendar';
export default {
props: {
@ -16,8 +15,8 @@
computed: {
events() {
let events = [];
for (let day of iterateMonth(this.year, this.month)) {
for (let event of currentYear.eventsByDate[day.toString()] || []) {
for (let day of iterateMonth(this.year.year, this.month)) {
for (let event of this.year.eventsByDate[day.toString()] || []) {
if (event.isFirstDay(day)) {
events.push(event);
}

View File

@ -36,7 +36,8 @@
if (current.includes('#')) {
current = current.substring(0, current.indexOf('#'));
}
return current === this.buildRoute(route).replace(/\/$/, '');
const expected = this.buildRoute(route).replace(/\/$/, '');
return current === expected || current.startsWith(expected + '/');
},
},
}

View File

@ -741,3 +741,5 @@ calendar:
header: 'Bild herunterladen'
overview: 'Überschau'
labels: 'Etiketten'
link: 'Link' # TODO
full: 'Full calendar' # TODO

View File

@ -720,3 +720,5 @@ calendar:
header: 'Download an image'
overview: 'Overview'
labels: 'Labels'
link: 'Link'
full: 'Full calendar'

View File

@ -755,3 +755,5 @@ calendar:
header: 'Descargar una imagen'
overview: 'Vista general'
labels: 'Etiquetas'
link: 'Link' # TODO
full: 'Full calendar' # TODO

View File

@ -219,7 +219,7 @@ faq:
Aangezien we al vragen hoe iemand heet, waarom dan ook niet wat hun voornaamwoorden zijn?
- >
(Formuleer het alsjeblieft alleen niet als "ben je een jongen of een meisje?"
Die vraag impliceert dat er alleen twee correcte antwoorden zijn
Die vraag impliceert dat er alleen twee correcte antwoorden zijn
en het suggereert dat je een ongezonde nieuwsgierigheid hebt wat betreft de genitaliën van die persoon.
In plaats daarvan zou je gewoon kunnen vragen: "Wat zijn je voornaamwoorden?" of "hoe wil je aangesproken worden?")
- >
@ -237,7 +237,7 @@ faq:
question: 'Worden deze voornaamwoorden überhaupt door iemand gebruikt?'
answer:
- >
Ja! Door miljoenen non-binaire personen over de hele wereld.
Ja! Door miljoenen non-binaire personen over de hele wereld.
Voor elk van de voornaamwoorden dat hier beschreven staat is er iemand die ze ook daadwerkelijk gebruikt in het dagelijks leven.
authority:
question: 'Are those nonbinary pronouns approved by some kind of authority?'
@ -252,7 +252,7 @@ faq:
Dictionaries take their time to start including those changes,
which doesn't make the change illegitimate in any way.
But eventually the new forms, if used often enough, get included in dictionaries.
The American dictionary {https://www.merriam-webster.com/words-at-play/singular-nonbinary-they=Merriam Webster},
The American dictionary {https://www.merriam-webster.com/words-at-play/singular-nonbinary-they=Merriam Webster},
for example, accepts the use of {/they=singular “they”} as a nonbinary pronoun.
- >
You can also read some {https://scholar.google.com/scholar?hl=en&q=neopronouns=academic papers}
@ -637,4 +637,6 @@ flags:
Trixic: 'Trixisch'
Two_Spirit: 'Bispiritueel'
Xenogender: 'Xenogender'
link: 'Link' # TODO
full: 'Full calendar' # TODO

View File

@ -1319,9 +1319,10 @@ calendar:
suicide_prevention_day: 'Światowy Dzień Zapobiegania Samobójstwom'
suicide_prevention_month: 'Miesiąc Zapobiegania Samobójstwom'
aids_awareness_month: 'Miesiąc Świadomości nt. AIDS'
banner: 'Dziś w kalendarzu'
image:
header: 'Ściągnij w formie obrazka'
overview: 'Przegląd'
labels: 'Etykietki'
link: 'Link'
full: 'Pełen kalendarz'

View File

@ -752,3 +752,5 @@ calendar:
header: 'Baixar uma imagem'
overview: 'Visão geral'
labels: 'Etiquetas'
link: 'Link' # TODO
full: 'Full calendar' # TODO

View File

@ -275,6 +275,8 @@ export default {
if (config.calendar && config.calendar.enabled) {
routes.push({ path: '/' + config.calendar.route, component: resolve(__dirname, 'routes/calendar.vue') });
routes.push({ path: '/' + config.calendar.route + '/:year(\\d\\d\\d\\d)', component: resolve(__dirname, 'routes/calendar.vue') });
routes.push({ path: '/' + config.calendar.route + '/:year(\\d\\d\\d\\d)-:month(\\d\\d)-:day(\\d\\d)', component: resolve(__dirname, 'routes/calendarDay.vue') });
routes.push({ path: '/calendar-wide', component: resolve(__dirname, 'routes/calendarWide.vue') });
}

View File

@ -1,51 +1,22 @@
<template>
<div>
<div v-if="year">
<CommunityNav/>
<h2>
<Icon v="calendar-star"/>
<T>calendar.headerLong</T> ({{year}})
<T>calendar.headerLong</T> <small class="text-muted">({{year.year}})</small>
</h2>
<CalendarBanner/>
<CalendarBanner v-if="year.isCurrent()"/>
<section class="row">
<div v-for="i in 12" class="col-12 col-sm-6 col-lg-4 py-3">
<h3 class="text-center"><T>calendar.months.{{i}}</T></h3>
<Calendar :year="year" :month="i" markToday/>
<Calendar :year="year" :month="i" :mark="today" tooltips/>
</div>
</section>
<section>
<div class="alert alert-info row">
<div class="col-12 col-lg-6">
<p class="mb-0">
Twitter Bot:
</p>
<p class="mb-0">
<a href="https://twitter.com/CalendarQueer" target="_blank" rel="noopener" class="btn btn-outline-primary m-1">
<Icon v="twitter" set="b"/>
@CalendarQueer
</a>
</p>
</div>
<div class="col-12 col-lg-6">
<p class="mb-0">
<T>calendar.image.header</T>:
</p>
<p class="mb-0">
<a href="/calendar/overview.png" target="_blank" rel="noopener" class="btn btn-outline-primary m-1">
<Icon v="image"/>
<T>calendar.image.overview</T>
</a>
<a href="/calendar/labels.png" target="_blank" rel="noopener" class="btn btn-outline-primary m-1">
<Icon v="image"/>
<T>calendar.image.labels</T>
</a>
</p>
</div>
</div>
</section>
<CalendarExtra/>
<Support/>
@ -53,15 +24,21 @@
<Share :title="$t('calendar.header')"/>
</section>
</div>
<NotFound v-else/>
</template>
<script>
import { head } from "../src/helpers";
import { calendar } from '../src/calendar/calendar';
import { Day } from '../src/calendar/helpers';
export default {
data() {
return {
year: new Date().getFullYear(),
year: this.$route.params.year
? calendar.getYear(this.$route.params.year)
: calendar.getCurrentYear(),
today: Day.today(),
}
},
head() {

70
routes/calendarDay.vue Normal file
View File

@ -0,0 +1,70 @@
<template>
<div v-if="year.eventsByDate[day.toString()]">
<CommunityNav/>
<h2>
<Icon v="calendar-star"/>
<T>calendar.headerLong</T> <small class="text-muted">({{day}})</small>
</h2>
<section>
<div class="d-flex justify-content-evenly flex-column-reverse flex-md-row align-items-center align-items-md-start">
<div class="calendar-month my-3">
<h3 class="text-center"><T>calendar.months.{{day.month}}</T></h3>
<Calendar :year="year" :month="day.month" :mark="day"/>
</div>
<div class="calendar-events my-3">
<h3><T :params="{day: day.day}">calendar.dates.{{day.month}}</T> {{day.year}}</h3>
<ul class="list-unstyled mb-0">
<CalendarEvent v-for="event in year.eventsByDate[day.toString()]" :event="event" :key="event.name"/>
</ul>
</div>
</div>
</section>
<CalendarExtra :day="day"/>
<Support/>
<section>
<Share :title="$t('calendar.header')"/>
</section>
</div>
<NotFound v-else/>
</template>
<script>
import { head } from "../src/helpers";
import { calendar } from '../src/calendar/calendar';
import { Day } from '../src/calendar/helpers';
export default {
data() {
const day = new Day(
this.$route.params.year,
this.$route.params.month,
this.$route.params.day,
);
return {
day,
year: calendar.getYear(day.year),
}
},
head() {
return head({
title: this.$t('calendar.headerLong'),
banner: `calendar/overview.png`,
});
},
};
</script>
<style lang="scss" scoped>
.calendar-month {
width: min(18rem, 100%);
}
.calendar-events {
width: min(20rem, 100%);
}
</style>

View File

@ -1,9 +1,9 @@
<template>
<div>
<div v-if="year">
<h2 class="d-flex justify-content-between">
<span>
<Icon v="calendar-star"/>
<T>calendar.headerLong</T> ({{year}})
<T>calendar.headerLong</T> <small class="text-muted">({{year.year}})</small>
</span>
<span class="h4 mt-2">
<nuxt-link to="/">
@ -21,16 +21,20 @@
</div>
</section>
</div>
<NotFound v-else/>
</template>
<script>
import { head } from "../src/helpers";
import {calendar} from "@/src/calendar/calendar";
export default {
layout: 'basic',
data() {
return {
year: new Date().getFullYear(),
year: this.$route.params.year
? calendar.getYear(this.$route.params.year)
: calendar.getCurrentYear(),
labels: this.$route.query.labels === 'true',
}
},

View File

@ -2,7 +2,7 @@ require('../src/dotenv')();
const Twitter = require('twitter');
const Suml = require('suml');
const fs = require('fs');
const { currentYear } = require('../src/calendar/calendar');
const { calendar } = require('../src/calendar/calendar');
const { Day } = require('../src/calendar/helpers');
const locales = require('../src/locales');
@ -27,7 +27,7 @@ const getEventName = (name) => {
(async () => {
const day = Day.today();
const events = currentYear.eventsByDate[day.toString()];
const events = calendar.getCurrentYear().eventsByDate[day.toString()];
console.log(events);
if (events === undefined || events.length === 0) {
@ -45,7 +45,7 @@ const getEventName = (name) => {
for (let event of events) {
tweet += ` - ${getEventName(event.name)}\n`;
}
tweet += `\n${domain}/${config.calendar.route}`;
tweet += `\n${domain}/${config.calendar.route}/${day}`;
console.log('------------');
console.log(tweet);

View File

@ -1,4 +1,4 @@
const { Day, Calendar, Event, EventLevel, day } = require('./helpers');
const { Calendar, Event, EventLevel, day } = require('./helpers');
const internationalEvents = require('../../locale/_/events');
const localEvents = require('../../data/events');
@ -13,7 +13,8 @@ for (let name in rawNamedays) {
}
}
module.exports.currentYear = new Calendar(
Day.today().year,
module.exports.calendar = new Calendar(
[...internationalEvents, ...localEvents], // TODO , ...namedays
2021,
2021,
);

View File

@ -1,9 +1,9 @@
class Day {
constructor(year, month, day, dayOfWeek) {
this.year = year;
this.month = month;
this.day = day;
this.dayOfWeek = dayOfWeek || new Date(year, month - 1, day).getDay() || 7;
this.year = parseInt(year);
this.month = parseInt(month);
this.day = parseInt(day);
this.dayOfWeek = dayOfWeek ? parseInt(dayOfWeek) : new Date(this.year, this.month - 1, this.day).getDay() || 7;
}
static fromDate(date) {
@ -121,7 +121,7 @@ module.exports.dayYear = function (day, year) {
return internal;
}
module.exports.Calendar = class {
class Year {
constructor(year, events) {
this.year = year;
this.events = events;
@ -139,4 +139,35 @@ module.exports.Calendar = class {
this.eventsByDate[date].sort((a, b) => b.level - a.level);
}
}
isCurrent() {
return this.year === Day.today().year;
}
}
module.exports.Year = Year;
module.exports.Calendar = class {
constructor(events, minYear = 0, maxYear = 9999) {
this._events = events;
this._minYear = minYear;
this._maxYear = maxYear;
this._years = {};
}
getYear(year) {
year = parseInt(year);
if (year < this._minYear || year > this._maxYear) {
return null;
}
if (this._years[year] === undefined) {
this._years[year] = new Year(year, this._events);
}
return this._years[year];
}
getCurrentYear() {
return this.getYear(Day.today().year);
}
}