import { DateTime } from 'luxon'; import { defineNuxtPlugin, useCookie, useRouter } from 'nuxt/app'; import type { Pinia } from 'pinia'; import { decodeTime } from 'ulid'; import { LoadScriptError } from '../src/errors.ts'; import { useMainStore } from '../store/index.ts'; declare global { interface Window { fusetag: any; } } declare module '#app' { interface NuxtApp { $loadScript(name: string, src: string, nonce?: string, target?: string): Promise; $loadStylesheet(name: string, src: string): Promise; $datetime(timestamp: number): string; $date(timestamp: number): string; $ulidTime(ulid: string): number; } } declare module 'vue' { interface ComponentCustomProperties { $loadScript(name: string, src: string, nonce?: string, target?: string): Promise; $loadStylesheet(name: string, src: string): Promise; $datetime(timestamp: number): string; $date(timestamp: number): string; $ulidTime(ulid: string): number; } } export default defineNuxtPlugin((nuxtApp) => { const store = useMainStore(nuxtApp.$pinia as Pinia); const spellingCookie = useCookie('spelling'); store.setSpelling(spellingCookie.value ?? null); const translationsCookie = useCookie>('translations'); store.restoreTranslations(translationsCookie.value); const translationModeVisibleCookie = useCookie('translationModeVisible'); if (translationModeVisibleCookie.value) { store.showTranslationMode(); } const awaitLoadedClass = (node: Element): Promise => { return new Promise((resolve) => { if (node.classList.contains('loaded')) { resolve(); } else { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { const target = mutation.target as Element; if (target.classList.contains('loaded')) { observer.disconnect(); resolve(); } }); }); observer.observe(node, { attributes: true, attributeFilter: ['class'] }); } }); }; const loadScript = (name: string, src: string, nonce: string | undefined = undefined, target: string | undefined = undefined): Promise => { if (!import.meta.client) { return new Promise((resolve) => { resolve(); }); } const existingScriptNode = document.querySelector(`script.${name}-script`); if (existingScriptNode) { return awaitLoadedClass(existingScriptNode); } return new Promise((resolve, reject) => { const script = document.createElement('script'); script.setAttribute('src', src); if (nonce) { script.setAttribute('nonce', nonce); } script.classList.add(`${name}-script`); // script.crossOrigin = 'true'; TODO proper fix (adding it breaks publift) script.addEventListener('load', () => { script.classList.add('loaded'); resolve(); }); script.addEventListener('error', (event) => { script.classList.add('loaded'); // not really loaded, but let's stop other calls from waiting forever, see !493 reject(new LoadScriptError(name, src, typeof event === 'string' ? event : event.type)); }); let targetNode = target ? document.querySelector(target) : null; targetNode = targetNode || document.body; targetNode.appendChild(script); }); }; const loadStylesheet = (name: string, src: string): Promise => { if (!import.meta.client) { return new Promise((resolve) => { resolve(); }); } const existingLinkNode = document.querySelector(`link.${name}-stylesheet`); if (existingLinkNode) { return awaitLoadedClass(existingLinkNode); } return new Promise((resolve, reject) => { const link = document.createElement('link'); link.setAttribute('rel', 'stylesheet'); link.setAttribute('href', src); link.classList.add(`${name}-stylesheet`); link.crossOrigin = 'true'; link.addEventListener('load', () => { link.classList.add('loaded'); resolve(); }); link.addEventListener('error', (event) => { link.classList.add('loaded'); // not really loaded, but let's stop other calls from waiting forever, see !493 reject(new LoadScriptError(name, src, typeof event === 'string' ? event : event.type)); }); document.body.appendChild(link); }); }; const datetime = (timestamp: number): string => { const dt = DateTime.fromSeconds(timestamp); return dt.toFormat('y-MM-dd HH:mm'); }; const date = (timestamp: number): string => { const dt = DateTime.fromSeconds(timestamp); return dt.toFormat('y-MM-dd'); }; const ulidTime = (ulid: string): number => { return decodeTime(ulid) / 1000; }; const router = useRouter(); router.afterEach(() => { if (typeof window !== 'undefined' && window.fusetag && window.fusetag.pageInit) { window.fusetag.pageInit(); } }); return { provide: { loadScript, loadStylesheet, datetime, date, ulidTime, }, }; });