PronounsPage/plugins/globals.ts
2025-04-19 15:04:06 +02:00

164 lines
5.7 KiB
TypeScript

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<void>;
$loadStylesheet(name: string, src: string): Promise<void>;
$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<void>;
$loadStylesheet(name: string, src: string): Promise<void>;
$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<Record<string, string>>('translations');
store.restoreTranslations(translationsCookie.value);
const translationModeVisibleCookie = useCookie('translationModeVisible');
if (translationModeVisibleCookie.value) {
store.showTranslationMode();
}
const awaitLoadedClass = (node: Element): Promise<void> => {
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<void> => {
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);
script.async = true;
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<void> => {
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,
},
};
});