(nuxt) dynamically load calendar events

This commit is contained in:
Valentyne Stigloher 2025-01-19 22:47:19 +01:00
parent a9abe47aa7
commit 6dcb92bdd8
15 changed files with 97 additions and 73 deletions

View File

@ -1,3 +1,21 @@
<script setup lang="ts">
import useConfig from '../composables/useConfig.ts';
import { Day } from '../src/calendar/helpers.ts';
import { loadCalendar } from '../src/data.ts';
const props = withDefaults(defineProps<{
day?: Day;
link?: boolean;
}>(), {
day: () => Day.today(),
});
const config = useConfig();
const calendar = await loadCalendar();
const events = calendar.getCurrentYear()!.eventsByDate[props.day.toString()];
</script>
<template>
<section
v-if="config.calendar && config.calendar.enabled && events !== undefined"
@ -23,26 +41,3 @@
</nuxt-link>
</section>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import useConfig from '../composables/useConfig.ts';
import { buildCalendar } from '../src/calendar/calendar.ts';
import { Day } from '../src/calendar/helpers.ts';
export default defineComponent({
props: {
day: { default: () => Day.today(), type: Day },
link: { type: Boolean },
},
setup(props) {
const runtimeConfig = useRuntimeConfig();
return {
config: useConfig(),
events: buildCalendar(runtimeConfig.public.baseUrl).getCurrentYear()!.eventsByDate[props.day.toString()],
};
},
});
</script>

View File

@ -25,8 +25,8 @@ import { storeToRefs } from 'pinia';
import { defineComponent } from 'vue';
import logoSvg from '../public/logo/logo.svg?raw';
import { buildCalendar } from '../src/calendar/calendar.ts';
import { Day } from '../src/calendar/helpers.ts';
import { loadCalendar } from '../src/data.ts';
import { ImmutableArray } from '../src/helpers.ts';
import { useMainStore } from '../store/index.ts';
@ -47,8 +47,7 @@ export default defineComponent({
day: { default: () => Day.today(), type: Day },
},
async setup() {
const runtimeConfig = useRuntimeConfig();
const calendarPromise = loadCalendar();
const { data: stats } = await useFetch<{ overall: { users: number } }>(
'/api/admin/stats-public',
{ lazy: true },
@ -57,7 +56,7 @@ export default defineComponent({
return {
stats,
selectedDay: storeToRefs(useMainStore()).selectedDay,
calendar: buildCalendar(runtimeConfig.public.baseUrl),
calendar: await calendarPromise,
};
},
data(): Data {

View File

@ -4,9 +4,8 @@ import { useNuxtApp } from 'nuxt/app';
import useConfig from '~/composables/useConfig.ts';
import useMainPronoun from '~/composables/useMainPronoun.ts';
import useSpelling from '~/composables/useSpelling.ts';
import { buildCalendar } from '~/src/calendar/calendar.ts';
import type { TermsEntryRaw } from '~/src/classes.ts';
import { loadPronounLibrary } from '~/src/data.ts';
import { loadCalendar, loadPronounLibrary } from '~/src/data.ts';
import { buildFlags } from '~/src/flags.ts';
import { buildImageUrl } from '~/src/helpers.ts';
import type { Profile, UserWithProfiles } from '~/src/profile.ts';
@ -24,7 +23,6 @@ const props = withDefaults(defineProps<{
const { $translator: translator } = useNuxtApp();
const { convertName } = useSpelling();
const config = useConfig();
const runtimeConfig = useRuntimeConfig();
const pronounLibrary = await loadPronounLibrary(config);
@ -40,7 +38,7 @@ const normaliseUrl = (url: string): string | null => {
const allFlags = buildFlags(config.locale);
const minAge = config.ageLimit || 13;
const year = buildCalendar(runtimeConfig.public.baseUrl).getCurrentYear();
const year = (await loadCalendar()).getCurrentYear();
const countFlags = computed(() => {
return (props.profile.flags?.length ?? 0) + (props.profile.customFlags?.length ?? 0);

View File

@ -3,9 +3,9 @@ import type { ComponentExposed } from 'vue-component-type-helpers';
import type Table from '~/components/Table.vue';
import type TermsSubmitForm from '~/components/TermsSubmitForm.vue';
import { buildCalendar } from '~/src/calendar/calendar.ts';
import { TermsEntry } from '~/src/classes.ts';
import type { TermsEntryRaw } from '~/src/classes.ts';
import { loadCalendar } from '~/src/data.ts';
import { buildDict, clearLinkedText } from '~/src/helpers.ts';
const props = defineProps<{
@ -13,7 +13,6 @@ const props = defineProps<{
}>();
const { $translator: translator } = useNuxtApp();
const runtimeConfig = useRuntimeConfig();
const config = useConfig();
const filter = useFilterWithCategory();
@ -108,7 +107,7 @@ const remove = async (entry: TermsEntry): Promise<void> => {
delete entries.value[entry.id];
};
const year = buildCalendar(runtimeConfig.public.baseUrl).getCurrentYear()!;
const year = (await loadCalendar()).getCurrentYear()!;
</script>
<template>

View File

@ -240,9 +240,18 @@ export default defineNuxtConfig({
}
},
async 'nitro:config'(nitroConfig) {
if (nitroConfig.publicAssets === undefined) {
nitroConfig.publicAssets = [];
}
nitroConfig.virtual ??= {};
// workaround to have a dynamic import with variables which currently does not work in Rollup
// https://github.com/rollup/rollup/issues/2097
nitroConfig.virtual['#virtual/calendar/events.ts'] = () => {
const imports = localeDescriptions.map((locale) => {
return `${locale.code}: async () =>
(await import('~/locale/${locale.code}/calendar/events.ts')).default`;
});
return `export default {${imports.join(', ')}};`;
};
nitroConfig.publicAssets ??= [];
nitroConfig.publicAssets.push(...(await Promise.all(codes.map(async (code) => [{
baseURL: `/docs/${code}`,
dir: await resolvePath(`~/locale/${code}/docs`),

View File

@ -4,8 +4,8 @@ import { useNuxtApp, useRoute } from 'nuxt/app';
import Page from '~/components/Page.vue';
import useConfig from '~/composables/useConfig.ts';
import useSimpleHead from '~/composables/useSimpleHead.ts';
import { buildCalendar } from '~/src/calendar/calendar.ts';
import { Day } from '~/src/calendar/helpers.ts';
import { loadCalendar } from '~/src/data.ts';
definePageMeta({
name: 'calendar',
@ -19,11 +19,10 @@ definePageMeta({
});
const { $translator: translator } = useNuxtApp();
const runtimeConfig = useRuntimeConfig();
const route = useRoute();
const config = useConfig();
const calendar = buildCalendar(runtimeConfig.public.baseUrl);
const calendar = await loadCalendar();
const year = route.params.year
? calendar.getYear(parseInt(route.params.year as string))
: calendar.getCurrentYear();

View File

@ -4,8 +4,8 @@ import { useNuxtApp, useRoute } from 'nuxt/app';
import Page from '~/components/Page.vue';
import useConfig from '~/composables/useConfig.ts';
import useSimpleHead from '~/composables/useSimpleHead.ts';
import { buildCalendar } from '~/src/calendar/calendar.ts';
import { Day } from '~/src/calendar/helpers.ts';
import { loadCalendar } from '~/src/data.ts';
definePageMeta({
name: 'calendarDay',
@ -19,7 +19,6 @@ definePageMeta({
});
const { $translator: translator } = useNuxtApp();
const runtimeConfig = useRuntimeConfig();
const route = useRoute();
const day = new Day(
@ -34,7 +33,7 @@ useSimpleHead({
}, translator);
const config = useConfig();
const year = buildCalendar(runtimeConfig.public.baseUrl).getYear(day.year);
const year = (await loadCalendar()).getYear(day.year);
const basic = route.query.layout === 'basic';
</script>

View File

@ -11,8 +11,7 @@ import useSimpleHead from '~/composables/useSimpleHead.ts';
import type { Config } from '~/locale/config.ts';
import { birthdateRange, formatDate, parseDate } from '~/src/birthdate.ts';
import { buildPronounUsage } from '~/src/buildPronoun.ts';
import { buildCalendar } from '~/src/calendar/calendar.ts';
import { loadPronounLibrary } from '~/src/data.ts';
import { loadCalendar, loadPronounLibrary } from '~/src/data.ts';
import { buildList, isValidLink } from '~/src/helpers.ts';
import { addPronounInjectionKey } from '~/src/injectionKeys.ts';
import opinions from '~/src/opinions.ts';
@ -224,7 +223,7 @@ const visibilityIcons = {
} as Record<ProfileVisibility, string[]>;
const runtimeConfig = useRuntimeConfig();
const year = buildCalendar(runtimeConfig.public.baseUrl).getCurrentYear();
const year = (await loadCalendar()).getCurrentYear();
const { mainPronoun } = useMainPronoun(pronounLibrary, formData, translator);

View File

@ -7,7 +7,7 @@ import type { MatchInfo, SearchResult, AsPlainObject, Options } from 'minisearch
import type { Config } from '~/locale/config.ts';
import { getPosts } from '~/server/blog.ts';
import { pronounLibrary, pronouns } from '~/server/data.ts';
import { loadCalendar, pronounLibrary, pronouns } from '~/server/data.ts';
import { getInclusiveEntries } from '~/server/express/inclusive.ts';
import { getNounEntries } from '~/server/express/nouns.ts';
import { getSourcesEntries } from '~/server/express/sources.ts';
@ -15,7 +15,6 @@ import { getTermsEntries } from '~/server/express/terms.ts';
import { loadSuml, loadSumlFromBase } from '~/server/loader.ts';
import { rootDir } from '~/server/paths.ts';
import { shortForVariant } from '~/src/buildPronoun.ts';
import { buildCalendar } from '~/src/calendar/calendar.ts';
import { Day } from '~/src/calendar/helpers.ts';
import forbidden from '~/src/forbidden.ts';
import { clearLinkedText, buildImageUrl } from '~/src/helpers.ts';
@ -810,9 +809,7 @@ const kinds: SearchKind[] = [
}
const base = config.calendar?.route;
const runtimeConfig = useRuntimeConfig();
const calendar = buildCalendar(runtimeConfig.public.baseUrl);
const calendar = await loadCalendar();
const year = Day.today().year;
return (calendar.getYear(year)?.events ?? [])

View File

@ -5,11 +5,11 @@ import fs from 'fs';
import Pageres from 'pageres';
import type { Config } from '../locale/config.ts';
import { buildCalendar } from '../src/calendar/calendar.ts';
import dbConnection from './db.ts';
import { loadSuml } from '~/server/loader.ts';
import { buildCalendar } from '~/src/calendar/calendar.ts';
const __dirname = new URL('.', import.meta.url).pathname;
@ -60,7 +60,8 @@ const dumpNameDays = async (): Promise<void> => {
const prevPath = `${__dirname}/../cache/calendar.json`;
const prev = fs.existsSync(prevPath) ? JSON.parse(fs.readFileSync(prevPath, 'utf-8')) : {};
const current = buildCalendar(process.env.BASE_URL!).buildSummary();
const localEvents = (await import(`../locale/${config.locale}/calendar/events.ts`)).default;
const current = buildCalendar(localEvents, process.env.BASE_URL!).buildSummary();
const changedYears = new Set();
for (const day in current) {
if (!Object.hasOwn(current, day)) {

View File

@ -1,6 +1,9 @@
import localeEventImports from '#virtual/calendar/events.ts';
import type { PronounExamplesData } from '~/locale/data.ts';
import { rootDir } from '~/server/paths.ts';
import { parsePronounGroups, parsePronouns } from '~/src/buildPronoun.ts';
import { buildCalendar } from '~/src/calendar/calendar.ts';
import type { Calendar } from '~/src/calendar/helpers.ts';
import { PronounLibrary } from '~/src/classes.ts';
import { loadTsv } from '~/src/tsv.ts';
@ -10,3 +13,14 @@ const pronounGroups = parsePronounGroups(loadTsv(`${dataDir}/pronouns/pronounGro
export const pronounLibrary = new PronounLibrary(global.config, pronounGroups, pronouns);
export const pronounExamples = loadTsv(`${dataDir}/pronouns/examples.tsv`) as PronounExamplesData[];
let calendar: Calendar | null = null;
export const loadCalendar = async () => {
if (calendar === null) {
const runtimeConfig = useRuntimeConfig();
const localEvents = await localeEventImports[config.locale]();
calendar = buildCalendar(localEvents, runtimeConfig.public.baseUrl);
}
return calendar;
};

View File

@ -6,7 +6,6 @@ import SQL from 'sql-template-strings';
import type { LocaleDescription } from '../../locale/locales.ts';
import type { Translations } from '../../locale/translations.ts';
import { buildCalendar } from '../../src/calendar/calendar.ts';
import { Calendar, Day } from '../../src/calendar/helpers.ts';
import type { Event } from '../../src/calendar/helpers.ts';
import { handleErrorAsync, clearLinkedText } from '../../src/helpers.ts';
@ -15,13 +14,13 @@ import { loadSuml, loadSumlFromBase } from '../loader.ts';
import { normalise } from './user.ts';
import { loadCalendar } from '~/server/data.ts';
// TODO caching? // import { caches } from "../../src/cache";
const translations = loadSuml('translations') as Translations;
const fallbackTranslations = loadSumlFromBase('locale/_base/translations') as Translations;
const calendar = buildCalendar(process.env.BASE_URL!);
const renderEvents = (yearEvents: Record<number, Event[]>, res: Response, onlyFirstDays: boolean = false, calNameExtra: string = '') => {
const events = [];
let i = 1;
@ -79,7 +78,8 @@ const buildMessage = (events: string[], locale: LocaleDescription, day: Day, lin
return message;
};
const eventsSummary = (day: Day, locale: LocaleDescription) => {
const eventsSummary = async (day: Day, locale: LocaleDescription) => {
const calendar = await loadCalendar();
const eventsRaw = calendar.getCurrentYear()!.eventsByDate[day.toString()];
const link = `${locale.url}/${encodeURIComponent(config.calendar!.route!)}/${day}`;
@ -126,14 +126,14 @@ interface ProfileRaw {
const router = Router();
router.get('/calendar/today', handleErrorAsync(async (req, res) => {
return res.json(eventsSummary(
return res.json(await eventsSummary(
Day.today(),
req.locales[config.locale],
));
}));
router.get('/calendar/:year-:month-:day', handleErrorAsync(async (req, res) => {
return res.json(eventsSummary(
return res.json(await eventsSummary(
new Day(parseInt(req.params.year), parseInt(req.params.month), parseInt(req.params.day)),
req.locales[config.locale],
));
@ -142,6 +142,7 @@ router.get('/calendar/:year-:month-:day', handleErrorAsync(async (req, res) => {
const routeBase = `/queer-calendar-${config.locale}`;
router.get(`${routeBase}.ics`, handleErrorAsync(async (req, res) => {
const calendar = await loadCalendar();
const events: Record<number, Event[]> = {};
for (const year of calendar.getAllYears()) {
events[year.year] = year.events;
@ -151,6 +152,7 @@ router.get(`${routeBase}.ics`, handleErrorAsync(async (req, res) => {
}));
router.get(generateURIEncodedPathAlternative(`${routeBase}-@:username.ics`), handleErrorAsync(async (req, res) => {
const calendar = await loadCalendar();
const profiles = await req.db.all<Pick<ProfileRaw, 'events' | 'customEvents'>>(SQL`
SELECT events, customEvents FROM profiles p
LEFT JOIN users u ON p.userId = u.id
@ -176,6 +178,7 @@ router.get(generateURIEncodedPathAlternative(`${routeBase}-@:username.ics`), han
}));
router.get(`${routeBase}-:year-:uuid.ics`, handleErrorAsync(async (req, res) => {
const calendar = await loadCalendar();
const year = calendar.getYear(parseInt(req.params.year));
if (!year) {
return res.status(404).json({ error: 'Not found' });
@ -185,6 +188,7 @@ router.get(`${routeBase}-:year-:uuid.ics`, handleErrorAsync(async (req, res) =>
}));
router.get(`${routeBase}-:year.ics`, handleErrorAsync(async (req, res) => {
const calendar = await loadCalendar();
const year = calendar.getYear(parseInt(req.params.year));
if (!year) {
return res.status(404).json({ error: 'Not found' });

View File

@ -1,18 +1,8 @@
import localEvents from '../../data/calendar/events.ts';
import rawNamedays from '../../data/names/namedays.json' with { type: 'json' };
import internationalEvents from '../../locale/_/calendar/events.ts';
import internationalEvents from '~/locale/_/calendar/events.ts';
import { Calendar } from '~/src/calendar/helpers.ts';
import type { Event } from '~/src/calendar/helpers.ts';
import { Calendar, Event, EventLevel, day } from './helpers.ts';
const _namedays = Object.entries(rawNamedays as Record<string, string[]>)
.flatMap(([name, namedays]) => {
return namedays.map((nameday) => {
const [m, d] = nameday.split('-');
return new Event(`nameday$${name}`, null, parseInt(m), day(parseInt(d)), EventLevel.Nameday);
});
});
export const buildCalendar = (baseUrl: string) => {
export const buildCalendar = (localEvents: Event[], baseUrl: string) => {
return new Calendar(
[...internationalEvents, ...localEvents], // TODO , ...namedays
baseUrl,

View File

@ -4,6 +4,7 @@ import { buildDict, buildList } from './helpers.ts';
import type { Config } from '~/locale/config.ts';
import type { PronounExamplesData } from '~/locale/data.ts';
import { buildCalendar } from '~/src/calendar/calendar.ts';
export const getLocale = () => {
return useRuntimeConfig().public.locale;
@ -97,3 +98,17 @@ export const loadNameCount = async () => {
return {};
}
};
export const loadCalendar = async () => {
const runtimeConfig = useRuntimeConfig();
const localEvents = (await import(`~/locale/${getLocale()}/calendar/events.ts`)).default;
/* const rawNamedays = (await import(`~/locale/${config.locale}/names/namedays.json`)).default;
const namedays = Object.entries(rawNamedays as Record<string, string[]>)
.flatMap(([name, namedays]) => {
return namedays.map((nameday) => {
const [m, d] = nameday.split('-');
return new Event(`nameday$${name}`, null, parseInt(m), day(parseInt(d)), EventLevel.Nameday);
});
}); */
return buildCalendar(localEvents, runtimeConfig.public.baseUrl);
};

6
types/virtual.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
declare module '#virtual/calendar/events.ts' {
import type { Event } from '~/src/calendar/helpers.ts';
declare const localEventsByLocale: Record<string, () => Promise<Event[]>>;
export default localEventsByLocale;
}