Tons more
This commit is contained in:
parent
47fa0d93a1
commit
07f6375b46
|
@ -1,31 +0,0 @@
|
|||
Bun.serve({
|
||||
port: 12498,
|
||||
fetch: async (req) => {
|
||||
const reqUrl = new URL(req.url);
|
||||
const rewriteUrl = new URL(reqUrl);
|
||||
rewriteUrl.hostname = "localhost";
|
||||
rewriteUrl.port = "12499";
|
||||
rewriteUrl.protocol = "http";
|
||||
const rewrite = new Request(rewriteUrl, req);
|
||||
const response = await fetch(rewrite, { redirect: "manual" });
|
||||
console.log(`[${response.status}] ${rewriteUrl.pathname}`);
|
||||
|
||||
if (response.status === 404 && response.headers.has("Location")) {
|
||||
|
||||
// Prevent redirection from a non locale-specific page to the en locale-specific page
|
||||
if (response.headers.get("location") === "/en" + rewriteUrl.pathname) {
|
||||
rewriteUrl.pathname = "/en/" + rewriteUrl.pathname;
|
||||
const rewrite = new Request(rewriteUrl, req);
|
||||
return await fetch(rewrite, { redirect: "manual" });
|
||||
}
|
||||
|
||||
return new Response(await response.blob(), {
|
||||
headers: response.headers,
|
||||
status: 302,
|
||||
statusText: "Found",
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
},
|
||||
});
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,14 @@
|
|||
import { writeFileSync } from "fs";
|
||||
|
||||
const PAYLOAD_FOLDER = `${process.cwd()}/src/shared/payload`;
|
||||
|
||||
const sdk = await fetch(`${import.meta.env.PAYLOAD_API_URL}/sdk`);
|
||||
|
||||
if (!sdk.ok) {
|
||||
console.error("Failed to get the sdk", sdk.status, sdk.statusText);
|
||||
} else {
|
||||
const sdkFile = await sdk.text();
|
||||
writeFileSync(`${PAYLOAD_FOLDER}/payload-sdk.ts`, sdkFile, {
|
||||
encoding: "utf-8",
|
||||
});
|
||||
}
|
|
@ -5,8 +5,8 @@ import Footer from "./components/Footer.astro";
|
|||
|
||||
interface Props {
|
||||
breadcrumb?: { name: string; slug: string }[];
|
||||
title?: string;
|
||||
description?: string;
|
||||
title?: string | undefined;
|
||||
description?: string | undefined;
|
||||
illustration?: string;
|
||||
illustrationSize?: string;
|
||||
illustrationPosition?: string;
|
||||
|
@ -34,14 +34,17 @@ const {
|
|||
<slot name="header-title">
|
||||
<h1>{title}</h1>
|
||||
</slot>
|
||||
|
||||
<div id="description">
|
||||
<slot name="header-description">
|
||||
<p>{description}</p>
|
||||
</slot>
|
||||
</div>
|
||||
<div id="image-container"></div>
|
||||
</div>
|
||||
{illustration && <div id="image-container" />}
|
||||
</div>
|
||||
</header>
|
||||
<main><slot name="main" /></main>
|
||||
<main><slot /></main>
|
||||
<Footer withLinks={breadcrumb.length > 0} />
|
||||
</Html>
|
||||
|
||||
|
@ -78,8 +81,12 @@ const {
|
|||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
& > p {
|
||||
& > #description {
|
||||
max-width: 35em;
|
||||
|
||||
& > p {
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
import Button from "components/Button.astro";
|
||||
import Tooltip from "components/Tooltip.astro";
|
||||
import { cache } from "src/utils/cachedPayload";
|
||||
import { getI18n } from "translations/translations";
|
||||
|
||||
interface Props {
|
||||
|
@ -20,14 +21,17 @@ const { currentCurrency } = Astro.locals;
|
|||
|
||||
<Tooltip trigger="click" class={className}>
|
||||
<div id="content" slot="tooltip-content">
|
||||
{
|
||||
cache.currencies.map((id) => (
|
||||
<a
|
||||
class:list={{ current: currentCurrency === "usd" }}
|
||||
href="?action-currency=usd">USD</a
|
||||
>
|
||||
<a
|
||||
class:list={{ current: currentCurrency === "eur" }}
|
||||
href="?action-currency=eur">EUR</a
|
||||
class:list={{ current: currentCurrency === id }}
|
||||
href={`?action-currency=${id}`}
|
||||
data-astro-prefetch="tap"
|
||||
>
|
||||
{id}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<Button
|
||||
icon="material-symbols:currency-exchange"
|
||||
|
|
|
@ -26,6 +26,10 @@ const contactLabel = `${t("footer.socials.contact.title")} - ${t(
|
|||
)}`;
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<footer>
|
||||
{
|
||||
withLinks && (
|
||||
|
@ -36,6 +40,18 @@ const contactLabel = `${t("footer.socials.contact.title")} - ${t(
|
|||
<Icon name="accords" />
|
||||
<p>{t("footer.links.home.title")}</p>
|
||||
</a>
|
||||
<a href="/archives">
|
||||
<Icon name="material-symbols:browse-outline" />
|
||||
<p>{"Contents"}</p>
|
||||
</a>
|
||||
<a href="/chronicles">
|
||||
<Icon name="material-symbols:book-2-outline" />
|
||||
<p>{"Chronicles"}</p>
|
||||
</a>
|
||||
<a href="/changelog">
|
||||
<Icon name="material-symbols:history" />
|
||||
<p>{"Changelog"}</p>
|
||||
</a>
|
||||
<a href="/timeline">
|
||||
<Icon name="material-symbols:calendar-month-outline" />
|
||||
<p>{t("footer.links.timeline.title")}</p>
|
||||
|
@ -126,10 +142,17 @@ const contactLabel = `${t("footer.socials.contact.title")} - ${t(
|
|||
|
||||
& > div {
|
||||
max-width: 20em;
|
||||
flex: 1;
|
||||
|
||||
@media (max-width: 35rem) {
|
||||
max-width: unset;
|
||||
}
|
||||
}
|
||||
|
||||
& > #nav {
|
||||
display: grid;
|
||||
place-content: start;
|
||||
flex: 1.5;
|
||||
|
||||
@media (max-width: 35rem) {
|
||||
place-items: center;
|
||||
|
@ -155,8 +178,6 @@ const contactLabel = `${t("footer.socials.contact.title")} - ${t(
|
|||
margin-left: -0.6em;
|
||||
|
||||
@media (max-width: 65rem) {
|
||||
grid-template-columns: 1fr;
|
||||
margin-top: 0.5em;
|
||||
gap: unset;
|
||||
}
|
||||
|
||||
|
|
|
@ -333,35 +333,27 @@ const { currentTheme } = Astro.locals;
|
|||
}
|
||||
|
||||
.keycap {
|
||||
transition-duration: 150ms;
|
||||
transition-property: translate, box-shadow, background-color;
|
||||
transition-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
|
||||
--foreground-color: var(--color-base-650);
|
||||
color: var(--foreground-color);
|
||||
border: 0.1rem solid var(--foreground-color);
|
||||
background-color: var(--color-elevation-0);
|
||||
|
||||
box-shadow:
|
||||
0 7px 15px -5px var(--color-shadow),
|
||||
inset 0 0 50px var(--color-elevation-2),
|
||||
inset 0 -7px 1px 5px var(--color-shadow-2);
|
||||
transition-duration: 250ms;
|
||||
transition-property: padding-top, box-shadow, background-color, color,
|
||||
border-color;
|
||||
transition-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
|
||||
&:hover {
|
||||
box-shadow:
|
||||
0 7px 15px -2px var(--color-shadow),
|
||||
inset 0 0 50px var(--color-elevation-2),
|
||||
inset 0 -7px 1px 5px var(--color-shadow-2);
|
||||
|
||||
--foreground-color: var(--color-base-1000);
|
||||
box-shadow: 0 2px 2px var(--color-shadow-2);
|
||||
background-color: var(--color-elevation-1);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transition-duration: 75ms;
|
||||
translate: 0 5px;
|
||||
--foreground-color: var(--color-base-1000);
|
||||
background-color: var(--color-elevation-2);
|
||||
|
||||
box-shadow:
|
||||
0 3px 5px -2px var(--color-shadow),
|
||||
inset 0 0 50px var(--color-elevation-2),
|
||||
inset 0 -6px 1px 6px var(--color-shadow-1);
|
||||
box-shadow: 0 6px 12px 2px var(--color-shadow-2);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -377,11 +369,3 @@ const { currentTheme } = Astro.locals;
|
|||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import htmx from "htmx.org";
|
||||
// On any page navigation, reprocess HTMX
|
||||
document.addEventListener("astro:page-load", () => {
|
||||
htmx.process(document.querySelector("main")!);
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
---
|
||||
import Button from "components/Button.astro";
|
||||
import Tooltip from "components/Tooltip.astro";
|
||||
import { getI18n, locales } from "translations/translations";
|
||||
import { cache } from "src/utils/cachedPayload";
|
||||
import { getI18n } from "translations/translations";
|
||||
|
||||
interface Props {
|
||||
withTitle?: boolean | undefined;
|
||||
|
@ -21,13 +22,13 @@ const { t } = await getI18n(currentLocale);
|
|||
<Tooltip trigger="click" class={className}>
|
||||
<div id="content" slot="tooltip-content">
|
||||
{
|
||||
locales.map((locale) => (
|
||||
cache.locales.map(id => (
|
||||
<a
|
||||
class:list={{ current: locale === currentLocale }}
|
||||
href={`?action-lang=${locale}`}
|
||||
class:list={{ current: currentLocale === id }}
|
||||
href={`?action-lang=${id}`}
|
||||
data-astro-prefetch="tap"
|
||||
>
|
||||
{locale.toString().toUpperCase()}
|
||||
{id}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
|
|
|
@ -11,7 +11,8 @@ interface Props {
|
|||
}
|
||||
|
||||
const { breadcrumb } = Astro.props;
|
||||
const { t } = await getI18n(Astro.locals.currentLocale);
|
||||
const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||
|
||||
---
|
||||
|
||||
{
|
||||
|
@ -44,10 +45,12 @@ const { t } = await getI18n(Astro.locals.currentLocale);
|
|||
)
|
||||
}
|
||||
<div id="toolbar">
|
||||
<a href={getLocalizedUrl("/search")}>
|
||||
<Button
|
||||
icon="material-symbols:search"
|
||||
ariaLabel={t("header.topbar.search.tooltip")}
|
||||
/>
|
||||
</a>
|
||||
<div class="separator"></div>
|
||||
|
||||
<div class="when-no-js">
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
interface Props {
|
||||
wrapper: (props?: any) => any;
|
||||
condition: boolean;
|
||||
}
|
||||
|
||||
const { wrapper: Wrapper, condition } = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
condition ? (
|
||||
<Wrapper>
|
||||
<slot />
|
||||
</Wrapper>
|
||||
) : (
|
||||
<slot />
|
||||
)
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
---
|
||||
interface Props {
|
||||
id?: string;
|
||||
class?: string;
|
||||
href: string;
|
||||
method?: "get" | "post" | "delete" | "put";
|
||||
}
|
||||
|
||||
const { href, method = "get", class: className, id } = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<maso-actor href={href} method={method} class={className} id={id}>
|
||||
<slot />
|
||||
</maso-actor>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||
}
|
||||
|
||||
<style>
|
||||
maso-actor {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- JS --------------------------------------------- */
|
||||
}
|
||||
|
||||
<script>
|
||||
import { customElement } from "src/utils/customElements";
|
||||
|
||||
customElement("maso-actor", (elem) => {
|
||||
const href = elem.getAttribute("href");
|
||||
const method = elem.getAttribute("method");
|
||||
|
||||
if (!href || !method) return;
|
||||
|
||||
elem.addEventListener("click", async () => {
|
||||
const elementToReplace = elem.closest("maso-target");
|
||||
if (!elementToReplace) return;
|
||||
const result = await fetch(href);
|
||||
if (!result.ok) return;
|
||||
const newContent = await result.text();
|
||||
elementToReplace.outerHTML = newContent;
|
||||
});
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
interface Props {
|
||||
id?: string;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { class:className, id } = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<maso-target class={className} id={id}>
|
||||
<slot />
|
||||
</maso-target>
|
||||
|
||||
|
||||
{
|
||||
/* ------------------------------------------- JS --------------------------------------------- */
|
||||
}
|
||||
|
||||
<script>
|
||||
class MasoTarget extends HTMLElement {}
|
||||
customElements.define("maso-target", MasoTarget);
|
||||
</script>
|
|
@ -4,33 +4,32 @@ interface Props {
|
|||
trigger?: string | undefined;
|
||||
}
|
||||
|
||||
const { class: className, trigger = "mouseenter focus" } = Astro.props;
|
||||
const { class: className, trigger } = Astro.props;
|
||||
---
|
||||
|
||||
<tippy-tooltip class={className} data-tippy-trigger={trigger}>
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<tippy-tooltip class={className} trigger={trigger}>
|
||||
<template><slot name="tooltip-content" /></template>
|
||||
<slot />
|
||||
</tippy-tooltip>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- JS --------------------------------------------- */
|
||||
}
|
||||
|
||||
<script>
|
||||
import tippy from "tippy.js";
|
||||
import htmx from "htmx.org";
|
||||
import { customElement } from "src/utils/customElements";
|
||||
|
||||
class TippyTooltip extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
tippy(this, {
|
||||
customElement("tippy-tooltip", (elem) => {
|
||||
tippy(elem, {
|
||||
allowHTML: true,
|
||||
content: (ref) =>
|
||||
ref.querySelector(":scope > template")?.innerHTML ?? "",
|
||||
content: (ref) => ref.querySelector(":scope > template")?.innerHTML ?? "",
|
||||
interactive: true,
|
||||
onMount: (instance) => {
|
||||
htmx.process(instance.popper);
|
||||
},
|
||||
trigger: elem.getAttribute("trigger") ?? "mouseenter focus",
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("tippy-tooltip", TippyTooltip);
|
||||
</script>
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
declare namespace App {
|
||||
interface Locals {
|
||||
currentLocale: import("translations/translations").Locale
|
||||
currentLocale: string
|
||||
currentTheme: "dark" | "auto" | "light"
|
||||
currentCurrency: "usd" | "eur"
|
||||
currentCurrency: string
|
||||
}
|
||||
}
|
|
@ -1,13 +1,18 @@
|
|||
import { defineMiddleware, sequence } from "astro:middleware";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
defaultLocale,
|
||||
getCookiePreferredLocale,
|
||||
getCurrentLocale,
|
||||
getPreferredLocale,
|
||||
getBestAcceptedLanguage,
|
||||
} from "translations/translations";
|
||||
|
||||
const cookieThemeSchema = z.enum(["dark", "light", "auto"]);
|
||||
import {
|
||||
CookieKeys,
|
||||
getCookieCurrency,
|
||||
getCookieLocale,
|
||||
getCookieTheme,
|
||||
isValidCurrency,
|
||||
isValidLocale,
|
||||
themeSchema,
|
||||
} from "src/utils/cookies";
|
||||
|
||||
const getAbsoluteLocaleUrl = (locale: string, url: string) =>
|
||||
`/${locale}${url}`;
|
||||
|
@ -23,91 +28,74 @@ const redirect = (
|
|||
});
|
||||
};
|
||||
|
||||
const localeNegotiator = defineMiddleware(
|
||||
({ cookies, url, request }, next) => {
|
||||
const currentLocale = getCurrentLocale(url.pathname);
|
||||
const preferredLocale = getPreferredLocale(request);
|
||||
const localeAgnosticPaths = ["/api/"];
|
||||
|
||||
if (url.pathname.startsWith("/api/")) {
|
||||
const localeNegotiator = defineMiddleware(({ cookies, url, request }, next) => {
|
||||
if (localeAgnosticPaths.some((prefix) => url.pathname.startsWith(prefix))) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const cookiePreferredLocale = getCookiePreferredLocale(cookies);
|
||||
const currentLocale = getCurrentLocale(url.pathname);
|
||||
const acceptedLocale = getBestAcceptedLanguage(request);
|
||||
const cookieLocale = getCookieLocale(cookies);
|
||||
const bestMatchingLocale = cookieLocale ?? acceptedLocale ?? defaultLocale;
|
||||
|
||||
if (!currentLocale) {
|
||||
const redirectURL = getAbsoluteLocaleUrl(
|
||||
cookiePreferredLocale ?? preferredLocale ?? defaultLocale,
|
||||
url.pathname
|
||||
);
|
||||
const redirectURL = getAbsoluteLocaleUrl(bestMatchingLocale, url.pathname);
|
||||
return redirect(redirectURL);
|
||||
}
|
||||
|
||||
if (cookiePreferredLocale) {
|
||||
if (cookiePreferredLocale !== currentLocale) {
|
||||
if (currentLocale !== bestMatchingLocale) {
|
||||
const pathnameWithoutLocale = url.pathname.substring(
|
||||
currentLocale.length + 1
|
||||
);
|
||||
const redirectURL = getAbsoluteLocaleUrl(
|
||||
cookiePreferredLocale,
|
||||
bestMatchingLocale,
|
||||
pathnameWithoutLocale
|
||||
);
|
||||
return redirect(redirectURL);
|
||||
}
|
||||
} else if (preferredLocale) {
|
||||
if (preferredLocale !== currentLocale) {
|
||||
const pathnameWithoutLocale = url.pathname.substring(
|
||||
currentLocale.length + 1
|
||||
);
|
||||
const redirectURL = getAbsoluteLocaleUrl(
|
||||
preferredLocale,
|
||||
pathnameWithoutLocale
|
||||
);
|
||||
return redirect(redirectURL);
|
||||
}
|
||||
}
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const handleActionsSearchParams = defineMiddleware(async ({ url }, next) => {
|
||||
// TODO: Verify locale typing
|
||||
const actionLang = url.searchParams.get("action-lang");
|
||||
if (actionLang) {
|
||||
if (isValidLocale(actionLang)) {
|
||||
const currentLocale = getCurrentLocale(url.pathname);
|
||||
const pathnameWithoutLocale = currentLocale
|
||||
? url.pathname.substring(currentLocale.length + 1)
|
||||
: url.pathname;
|
||||
const redirectURL = getAbsoluteLocaleUrl(actionLang, pathnameWithoutLocale);
|
||||
return redirect(redirectURL, {
|
||||
"Set-Cookie": `al_pref_languages=${JSON.stringify([actionLang])}; Path=/`,
|
||||
"Set-Cookie": `${CookieKeys.Languages}=${JSON.stringify([
|
||||
actionLang,
|
||||
])}; Path=/`,
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Verify currency typing
|
||||
const actionCurrency = url.searchParams.get("action-currency");
|
||||
if (actionCurrency) {
|
||||
if (isValidCurrency(actionCurrency)) {
|
||||
return redirect(url.pathname, {
|
||||
"Set-Cookie": `al_pref_currency=${JSON.stringify(
|
||||
"Set-Cookie": `${CookieKeys.Currency}=${JSON.stringify(
|
||||
actionCurrency
|
||||
)}; Path=/`,
|
||||
});
|
||||
}
|
||||
|
||||
const actionTheme = url.searchParams.get("action-theme");
|
||||
const verifiedActionTheme = cookieThemeSchema.safeParse(actionTheme);
|
||||
|
||||
const verifiedActionTheme = themeSchema.safeParse(actionTheme);
|
||||
if (verifiedActionTheme.success) {
|
||||
url.searchParams.delete("action-theme");
|
||||
if (verifiedActionTheme.data === "auto") {
|
||||
return redirect(url.pathname, {
|
||||
"Set-Cookie": `al_pref_theme=; Path=/; Expires=${new Date(
|
||||
"Set-Cookie": `${CookieKeys.Theme}=; Path=/; Expires=${new Date(
|
||||
0
|
||||
).toUTCString()}`,
|
||||
});
|
||||
}
|
||||
return redirect(url.pathname, {
|
||||
"Set-Cookie": `al_pref_theme=${verifiedActionTheme.data}; Path=/`,
|
||||
"Set-Cookie": `${CookieKeys.Theme}=${verifiedActionTheme.data}; Path=/`,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -126,12 +114,14 @@ const addContentLanguageResponseHeader = defineMiddleware(
|
|||
}
|
||||
);
|
||||
|
||||
const provideLocalsToRequest = defineMiddleware(async ({ url, locals, cookies }, next) => {
|
||||
const provideLocalsToRequest = defineMiddleware(
|
||||
async ({ url, locals, cookies }, next) => {
|
||||
locals.currentLocale = getCurrentLocale(url.pathname) ?? "en";
|
||||
locals.currentCurrency = cookies.get("al_pref_currency")?.value ?? "usd"
|
||||
locals.currentTheme = cookies.get("al_pref_theme")?.value ?? "auto"
|
||||
locals.currentCurrency = getCookieCurrency(cookies) ?? "USD";
|
||||
locals.currentTheme = getCookieTheme(cookies) ?? "auto";
|
||||
return next();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
export const onRequest = sequence(
|
||||
addContentLanguageResponseHeader,
|
||||
|
|
|
@ -1 +1,9 @@
|
|||
<h1>Oh nyo...</h1>
|
||||
---
|
||||
import AppLayout from "components/AppLayout/AppLayout.astro";
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<AppLayout title="Oh nyo..." />
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
import AppLayout from "components/AppLayout/AppLayout.astro";
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<AppLayout title="WIP" />
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
import AppLayout from "components/AppLayout/AppLayout.astro";
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<AppLayout title="WIP" />
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
import AppLayout from "components/AppLayout/AppLayout.astro";
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<AppLayout title="WIP" />
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
import AppLayout from "components/AppLayout/AppLayout.astro";
|
||||
import Content from "pages/api/content.astro";
|
||||
|
||||
const { currentLocale } = Astro.locals;
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<AppLayout
|
||||
breadcrumb={[{ name: "About us", slug: "about" }]}
|
||||
title="About us"
|
||||
description="This is a page to test the Temporary Language Override™ feature"
|
||||
>
|
||||
<Content lang={currentLocale} />
|
||||
</AppLayout>
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
import AppLayout from "components/AppLayout/AppLayout.astro";
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<AppLayout title="WIP" />
|
|
@ -1,95 +0,0 @@
|
|||
---
|
||||
import AppLayout from "components/AppLayout/AppLayout.astro";
|
||||
import FolderCard from "./_components/FolderCard.astro";
|
||||
import { getI18n } from "translations/translations";
|
||||
|
||||
const {getLocalizedUrl} = await getI18n(Astro.locals.currentLocale)
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<AppLayout
|
||||
breadcrumb={[
|
||||
{ name: "Drakengard", slug: "drakengard" },
|
||||
]}
|
||||
title="Drakengard"
|
||||
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||
illustration="/img/categories/light/dod.png"
|
||||
>
|
||||
<div id="main" slot="main">
|
||||
<FolderCard
|
||||
title="Games"
|
||||
icon="material-symbols:stadia-controller-outline"
|
||||
href={getLocalizedUrl("/drakengard/games")}
|
||||
/>
|
||||
<FolderCard
|
||||
title="Guidebooks"
|
||||
icon="material-symbols:menu-book-outline"
|
||||
href={getLocalizedUrl("/drakengard/guidebooks")}
|
||||
/>
|
||||
<FolderCard
|
||||
title="Manga"
|
||||
icon="material-symbols:menu-book-outline"
|
||||
href={getLocalizedUrl("/drakengard/manga")}
|
||||
/>
|
||||
<FolderCard
|
||||
title="Novels"
|
||||
icon="material-symbols:menu-book-outline"
|
||||
href={getLocalizedUrl("/drakengard/novels")}
|
||||
/>
|
||||
<FolderCard
|
||||
title="Novellas"
|
||||
icon="material-symbols:menu-book-outline"
|
||||
href={getLocalizedUrl("/drakengard/novellas")}
|
||||
/>
|
||||
<FolderCard
|
||||
title="Characters"
|
||||
icon="material-symbols:person-outline"
|
||||
href={getLocalizedUrl("/drakengard/characters")}
|
||||
/>
|
||||
<FolderCard
|
||||
title="Places"
|
||||
icon="material-symbols:location-on-outline"
|
||||
href={getLocalizedUrl("/drakengard/places")}
|
||||
/>
|
||||
<FolderCard
|
||||
title="Enemies"
|
||||
icon="material-symbols:sentiment-extremely-dissatisfied-outline"
|
||||
href={getLocalizedUrl("/drakengard/enemies")}
|
||||
/>
|
||||
<FolderCard
|
||||
title="Terms"
|
||||
icon="material-symbols:indeterminate-question-box"
|
||||
href={getLocalizedUrl("/drakengard/terms")}
|
||||
/>
|
||||
<FolderCard
|
||||
title="Weapons"
|
||||
icon="material-symbols:swords-outline"
|
||||
href={getLocalizedUrl("/drakengard/weapons")}
|
||||
/>
|
||||
<FolderCard
|
||||
title="Timeline"
|
||||
icon="material-symbols:calendar-month-outline"
|
||||
href={getLocalizedUrl("/drakengard/timeline")}
|
||||
/>
|
||||
<FolderCard
|
||||
title="News"
|
||||
icon="material-symbols:newspaper"
|
||||
href={getLocalizedUrl("/drakengard/news")}
|
||||
/>
|
||||
</div>
|
||||
</AppLayout>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||
}
|
||||
|
||||
<style>
|
||||
#main {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: 0.7rem 1rem;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,67 @@
|
|||
---
|
||||
import AppLayout from "components/AppLayout/AppLayout.astro";
|
||||
import { payload } from "src/shared/payload/payload-sdk";
|
||||
import { getI18n } from "translations/translations";
|
||||
import RichText from "components/RichText/RichText.astro";
|
||||
import FoldersSection from "./_components/FoldersSection.astro";
|
||||
|
||||
const { slug } = Astro.params;
|
||||
const { getLocalizedMatch } = await getI18n(Astro.locals.currentLocale);
|
||||
|
||||
if (!slug) {
|
||||
return Astro.redirect("/en/404");
|
||||
}
|
||||
|
||||
const folder = await payload.getFolder(slug);
|
||||
const meta = getLocalizedMatch(folder.translations, { name: slug });
|
||||
|
||||
// TODO: handle folder not found
|
||||
// TODO: handle rich text description
|
||||
// TODO: handle light and dark illustration for applayout
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<AppLayout title={meta.name}>
|
||||
|
||||
{
|
||||
meta.description && (
|
||||
<div slot="header-description">
|
||||
<RichText content={JSON.parse(meta.description)} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
folder.sections.type === "single" ? (
|
||||
<FoldersSection folders={folder.sections.subfolders} />
|
||||
) : (
|
||||
<div id="sections">
|
||||
{folder.sections.sections.map(({ subfolders, translations }) => (
|
||||
<FoldersSection
|
||||
folders={subfolders}
|
||||
title={
|
||||
getLocalizedMatch<{
|
||||
language: string;
|
||||
name: string | undefined;
|
||||
}>(translations, { name: undefined }).name
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</AppLayout>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||
}
|
||||
|
||||
<style>
|
||||
#sections {
|
||||
display: grid;
|
||||
gap: 2.5em;
|
||||
}
|
||||
</style>
|
|
@ -2,11 +2,11 @@
|
|||
import { Icon } from "astro-icon/components";
|
||||
interface Props {
|
||||
title: string;
|
||||
icon: string;
|
||||
icon?: string | undefined;
|
||||
href: string;
|
||||
}
|
||||
|
||||
const { icon, title, href } = Astro.props;
|
||||
const { icon = "material-symbols:folder-outline", title, href } = Astro.props;
|
||||
---
|
||||
|
||||
<a href={href} class="keycap">
|
||||
|
@ -22,8 +22,7 @@ const { icon, title, href } = Astro.props;
|
|||
place-items: center;
|
||||
gap: 1em;
|
||||
color: var(--color-base-1000);
|
||||
padding: 24px;
|
||||
padding-top: 12px;
|
||||
padding: 12px 24px;
|
||||
border-radius: 12px;
|
||||
text-decoration: none;
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
import type { EndpointFolderPreview } from "src/shared/payload/payload-sdk";
|
||||
import FolderCard from "./FolderCard.astro";
|
||||
import { getI18n } from "translations/translations";
|
||||
|
||||
interface Props {
|
||||
title?: string | undefined;
|
||||
folders: EndpointFolderPreview[];
|
||||
}
|
||||
|
||||
const { title, folders } = Astro.props;
|
||||
|
||||
const { getLocalizedUrl, getLocalizedMatch } = await getI18n(
|
||||
Astro.locals.currentLocale
|
||||
);
|
||||
---
|
||||
|
||||
<div>
|
||||
{title && <h3>{title}</h3>}
|
||||
<section>
|
||||
{
|
||||
folders.map(({ slug, translations, icon }) => (
|
||||
<FolderCard
|
||||
title={getLocalizedMatch(translations, { name: slug }).name}
|
||||
icon={icon ? `material-symbols:${icon}` : undefined}
|
||||
href={getLocalizedUrl(`/folders/${slug}`)}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||
}
|
||||
|
||||
<style>
|
||||
h3 {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
section {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: 0.7rem 1rem;
|
||||
}
|
||||
</style>
|
|
@ -3,9 +3,11 @@ import { Icon } from "astro-icon/components";
|
|||
import AppLayout from "components/AppLayout/AppLayout.astro";
|
||||
import Button from "components/Button.astro";
|
||||
import LinkCard from "../_components/LinkCard.astro";
|
||||
import CategoryCard from "../_components/CategoryCard.astro";
|
||||
import { getI18n } from "../../../translations/translations";
|
||||
|
||||
import ChronicleCard from "pages/_components/ChronicleCard.astro";
|
||||
import LibraryGrid from "pages/_components/LibraryGrid.astro";
|
||||
|
||||
const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||
---
|
||||
|
||||
|
@ -36,120 +38,77 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
|||
</a>
|
||||
</div>
|
||||
|
||||
<Fragment slot="main">
|
||||
<div id="main">
|
||||
<section id="library">
|
||||
<h2>{t("home.librarySection.title")}</h2>
|
||||
<p set:html={t("home.librarySection.description")} />
|
||||
<a href={getLocalizedUrl("/search")}>
|
||||
<Button
|
||||
class="section-button"
|
||||
title={t("home.librarySection.button")}
|
||||
icon="material-symbols:browse-outline"
|
||||
/>
|
||||
</a>
|
||||
<div class="grid">
|
||||
<CategoryCard
|
||||
img={{
|
||||
light: "/img/categories/light/dod.png",
|
||||
dark: "/img/categories/dark/dod.png",
|
||||
}}
|
||||
name="Drakengard"
|
||||
href={getLocalizedUrl("/drakengard")}
|
||||
/>
|
||||
<CategoryCard
|
||||
img={{
|
||||
light: "/img/categories/light/dod2.png",
|
||||
dark: "/img/categories/dark/dod2.png",
|
||||
}}
|
||||
name="Drakengard 2"
|
||||
href={getLocalizedUrl("/drakengard-2")}
|
||||
/>
|
||||
<CategoryCard
|
||||
img={{
|
||||
light: "/img/categories/light/dod3.png",
|
||||
dark: "/img/categories/dark/dod3.png",
|
||||
}}
|
||||
name="Drakengard 3"
|
||||
href={getLocalizedUrl("/drakengard-3")}
|
||||
/>
|
||||
<CategoryCard
|
||||
img={{
|
||||
light: "/img/categories/light/dod1.3.png",
|
||||
dark: "/img/categories/dark/dod1.3.png",
|
||||
}}
|
||||
name="Drakengard 1.3"
|
||||
href={getLocalizedUrl("/drakengard-1-3")}
|
||||
/>
|
||||
<CategoryCard
|
||||
img={{
|
||||
light: "/img/categories/light/nier.png",
|
||||
dark: "/img/categories/dark/nier.png",
|
||||
}}
|
||||
name="NieR"
|
||||
href={getLocalizedUrl("/nier")}
|
||||
/>
|
||||
<CategoryCard
|
||||
img={{
|
||||
light: "/img/categories/light/na.png",
|
||||
dark: "/img/categories/dark/na.png",
|
||||
}}
|
||||
name="NieR:Automata"
|
||||
href={getLocalizedUrl("/nier-automata")}
|
||||
/>
|
||||
<CategoryCard
|
||||
img={{
|
||||
light: "/img/categories/light/nier-rein.png",
|
||||
dark: "/img/categories/dark/nier-rein.png",
|
||||
}}
|
||||
name="NieR Re[in]carnation"
|
||||
href={getLocalizedUrl("/nier-rein")}
|
||||
/>
|
||||
<CategoryCard
|
||||
img={{
|
||||
light: "/img/categories/light/yorha.png",
|
||||
dark: "/img/categories/dark/yorha.png",
|
||||
}}
|
||||
name="YoRHa"
|
||||
href={getLocalizedUrl("/yorha")}
|
||||
/>
|
||||
<CategoryCard
|
||||
img={{
|
||||
light: "/img/categories/light/yorha-boys.png",
|
||||
dark: "/img/categories/dark/yorha-boys.png",
|
||||
}}
|
||||
name="YoRHa Boys"
|
||||
href={getLocalizedUrl("/yorha-boys")}
|
||||
/>
|
||||
<CategoryCard
|
||||
img={{
|
||||
light: "/img/categories/light/sino.png",
|
||||
dark: "/img/categories/dark/sino.png",
|
||||
}}
|
||||
name="SINoALICE"
|
||||
href={getLocalizedUrl("/sinoalice")}
|
||||
/>
|
||||
<CategoryCard
|
||||
img={{
|
||||
light: "/img/categories/light/404gamereset.png",
|
||||
dark: "/img/categories/dark/404gamereset.png",
|
||||
}}
|
||||
name="404 Game Re:Set"
|
||||
href={getLocalizedUrl("/404-game-reset")}
|
||||
/>
|
||||
<CategoryCard
|
||||
img={{
|
||||
light: "/img/categories/light/god-app.png",
|
||||
dark: "/img/categories/dark/god-app.png",
|
||||
}}
|
||||
name="God App"
|
||||
href={getLocalizedUrl("/god-app")}
|
||||
/>
|
||||
<CategoryCard
|
||||
img={{
|
||||
light: "/img/categories/light/voc.png",
|
||||
dark: "/img/categories/dark/voc.png",
|
||||
}}
|
||||
name="Voice of Cards"
|
||||
href={getLocalizedUrl("/voice-of-cards")}
|
||||
/>
|
||||
<CategoryCard name="Others..." href={getLocalizedUrl("/others")} />
|
||||
<LibraryGrid />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>{t("home.chroniclesSection.title")}</h2>
|
||||
<p set:html={t("home.chroniclesSection.description")} />
|
||||
<div class="flex">
|
||||
<ChronicleCard
|
||||
pretitle={"Preface"}
|
||||
title={"The Yokoverse"}
|
||||
subtitle={"Start reading the Chronicles here"}
|
||||
href={getLocalizedUrl("/chronicles/preface")}
|
||||
/>
|
||||
<ChronicleCard
|
||||
pretitle={"Chapter 1"}
|
||||
title={"Drakengard 3"}
|
||||
subtitle={"A cursed world, and the girl who hated it"}
|
||||
href={getLocalizedUrl("/chronicles/drakengard-3")}
|
||||
/>
|
||||
<ChronicleCard
|
||||
pretitle={"Chapter 2"}
|
||||
title={"Drakengard"}
|
||||
subtitle={"Humanity at its lowest"}
|
||||
href={getLocalizedUrl("/chronicles/drakengard")}
|
||||
/>
|
||||
<ChronicleCard
|
||||
pretitle={"Chapter 3"}
|
||||
title={"NieR"}
|
||||
subtitle={"The Glory of Mankind comes to an abrupt End"}
|
||||
href={getLocalizedUrl("/chronicles/nier")}
|
||||
/>
|
||||
<ChronicleCard
|
||||
pretitle={"Chapter 4"}
|
||||
title={"NieR:Automata"}
|
||||
subtitle={"Protagonists of Meaningless Stories"}
|
||||
href={getLocalizedUrl("/chronicles/nier-automata")}
|
||||
/>
|
||||
<ChronicleCard
|
||||
pretitle={"Epilogue"}
|
||||
title={"Appendices"}
|
||||
subtitle={"Protagonists of Meaningless Stories"}
|
||||
href={getLocalizedUrl("/chronicles/nier-automata")}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>{t("home.changesSection.title")}</h2>
|
||||
<p set:html={t("home.changesSection.description")} />
|
||||
<a href={getLocalizedUrl("/changelog")}>
|
||||
<Button
|
||||
class="section-button"
|
||||
title={t("home.changesSection.button")}
|
||||
icon="material-symbols:history"
|
||||
/>
|
||||
</a>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>{t("home.moreSection.title")}</h2>
|
||||
<p set:html={t("home.moreSection.description")} />
|
||||
|
@ -215,7 +174,6 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
|||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Fragment>
|
||||
</AppLayout>
|
||||
|
||||
{
|
||||
|
@ -230,11 +188,6 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
|||
align-items: flex-start;
|
||||
margin-bottom: 128px;
|
||||
|
||||
& > p {
|
||||
line-height: 1.4;
|
||||
max-width: 35em;
|
||||
}
|
||||
|
||||
@media (max-width: 35rem) {
|
||||
align-items: center;
|
||||
|
||||
|
@ -315,8 +268,12 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
|||
& > p {
|
||||
max-width: 35em;
|
||||
line-height: 1.4;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 24px;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
& > a > :global(.section-button) {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
&#library {
|
||||
|
@ -337,6 +294,18 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
|||
column-gap: clamp(6px, 2vmin, 16px);
|
||||
row-gap: clamp(6px + 6px, 2vmin + 6px, 16px);
|
||||
}
|
||||
|
||||
& > .flex {
|
||||
display: flex;
|
||||
column-gap: clamp(6px, 2vmin, 16px);
|
||||
row-gap: clamp(6px + 6px, 2vmin + 6px, 16px);
|
||||
flex-wrap: wrap;
|
||||
|
||||
@media (max-width: 35rem) {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
---
|
||||
import AppLayout from "components/AppLayout/AppLayout.astro";
|
||||
import Content from "pages/api/content.astro";
|
||||
|
||||
const { currentLocale } = Astro.locals;
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<AppLayout
|
||||
breadcrumb={[{ name: "Drakengard", slug: "drakengard" }]}
|
||||
title="Others"
|
||||
description="This is a page to test the Temporary Language Override™ feature"
|
||||
>
|
||||
<div id="main" slot="main">
|
||||
<Content lang={currentLocale} />
|
||||
</div>
|
||||
</AppLayout>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||
}
|
||||
|
||||
<style>
|
||||
#main {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: 0.7rem 1rem;
|
||||
}
|
||||
</style>
|
|
@ -1,35 +1,36 @@
|
|||
---
|
||||
import AppLayout from "components/AppLayout/AppLayout.astro";
|
||||
import { getI18n, locales } from "translations/translations";
|
||||
import { cache } from "src/utils/cachedPayload";
|
||||
import { getI18n } from "translations/translations";
|
||||
|
||||
const { currentLocale, currentTheme, currentCurrency } = Astro.locals;
|
||||
const { t } = await getI18n(currentLocale);
|
||||
---
|
||||
|
||||
<AppLayout
|
||||
title="Settings"
|
||||
breadcrumb={[{ name: "Settings", slug: "settings" }]}
|
||||
title={t("settings.title")}
|
||||
breadcrumb={[{ name: t("settings.title"), slug: "settings" }]}
|
||||
>
|
||||
<div id="main" slot="main">
|
||||
<div id="main">
|
||||
<div class="section">
|
||||
<h2>Language</h2>
|
||||
<p>{t("header.topbar.language.tooltip")}</p><br />
|
||||
<h2>{t("settings.language.title")}</h2>
|
||||
<p>{t("settings.language.description")}</p><br />
|
||||
{
|
||||
locales.map((locale) => (
|
||||
cache.locales.map((id) => (
|
||||
<a
|
||||
class:list={{ current: locale === currentLocale }}
|
||||
href={`?action-lang=${locale}`}
|
||||
class:list={{ current: currentLocale === id }}
|
||||
href={`?action-lang=${id}`}
|
||||
data-astro-prefetch="tap"
|
||||
>
|
||||
{locale.toString().toUpperCase()}
|
||||
{id}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Theme</h2>
|
||||
<p>{t("header.topbar.theme.tooltip")}</p><br />
|
||||
<h2>{t("settings.theme.title")}</h2>
|
||||
<p>{t("settings.theme.description")}</p><br />
|
||||
<a
|
||||
class:list={{ current: currentTheme === "dark" }}
|
||||
href="?action-theme=dark"
|
||||
|
@ -54,18 +55,19 @@ const { t } = await getI18n(currentLocale);
|
|||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Currency</h2>
|
||||
<p>{t("header.topbar.currency.tooltip")}</p><br />
|
||||
<h2>{t("settings.theme.title")}</h2>
|
||||
<p>{t("settings.theme.description")}</p><br />
|
||||
{
|
||||
cache.currencies.map((id) => (
|
||||
<a
|
||||
class:list={{ current: currentCurrency === "usd" }}
|
||||
href="?action-currency=usd"
|
||||
data-astro-prefetch="tap">USD</a
|
||||
>
|
||||
<a
|
||||
class:list={{ current: currentCurrency === "eur" }}
|
||||
href="?action-currency=eur"
|
||||
data-astro-prefetch="tap">EUR</a
|
||||
class:list={{ current: currentCurrency === id }}
|
||||
href={`?action-currency=${id}`}
|
||||
data-astro-prefetch="tap"
|
||||
>
|
||||
{id}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</AppLayout>
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
import AppLayout from "components/AppLayout/AppLayout.astro";
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<AppLayout title="WIP" />
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
import AppLayout from "components/AppLayout/AppLayout.astro";
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<AppLayout title="WIP" />
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
interface Props {
|
||||
img?: { light: string; dark: string };
|
||||
img?: { light: string; dark: string } | undefined;
|
||||
name: string;
|
||||
href: string;
|
||||
}
|
||||
|
@ -30,7 +30,6 @@ const { img, name, href } = Astro.props;
|
|||
place-items: center;
|
||||
|
||||
padding: 24px;
|
||||
padding-bottom: 32px;
|
||||
border-radius: 12px;
|
||||
|
||||
user-select: none;
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
---
|
||||
interface Props {
|
||||
pretitle: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
const { pretitle, subtitle, title, href } = Astro.props;
|
||||
---
|
||||
|
||||
<a href={href} class="keycap">
|
||||
<p class="pretitle">{pretitle}</p>
|
||||
<h3>{title}</h3>
|
||||
<p>{subtitle}</p>
|
||||
</a>
|
||||
|
||||
<style>
|
||||
a {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
gap: 1em;
|
||||
color: var(--color-base-1000);
|
||||
padding: 1.5em;
|
||||
border-radius: 0.75em;
|
||||
text-decoration: none;
|
||||
|
||||
& > svg {
|
||||
width: clamp(1.5em, 6vw + 0.8em, 3em);
|
||||
height: clamp(1.5em, 6vw + 0.8em, 3em);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.15em;
|
||||
|
||||
& > h3 {
|
||||
font-size: 1.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
& > .pretitle {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
& > p {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
import { payload } from "src/shared/payload/payload-sdk";
|
||||
import { getI18n } from "translations/translations";
|
||||
import CategoryCard from "./CategoryCard.astro";
|
||||
|
||||
const folders = await payload.getRootFolders()
|
||||
const { getLocalizedUrl, getLocalizedMatch } = await getI18n(
|
||||
Astro.locals.currentLocale
|
||||
);
|
||||
---
|
||||
|
||||
{
|
||||
folders.map(({ slug, translations, darkThumbnail, lightThumbnail }) => (
|
||||
<CategoryCard
|
||||
img={
|
||||
darkThumbnail && lightThumbnail
|
||||
? { dark: darkThumbnail.url, light: lightThumbnail.url }
|
||||
: undefined
|
||||
}
|
||||
name={getLocalizedMatch(translations, { name: slug }).name}
|
||||
href={getLocalizedUrl(`/folders/${slug}`)}
|
||||
/>
|
||||
))
|
||||
}
|
|
@ -25,7 +25,6 @@ const { icon, subtitle, title, href } = Astro.props;
|
|||
gap: 1em;
|
||||
color: var(--color-base-1000);
|
||||
padding: 1.5em;
|
||||
padding-top: 0.75em;
|
||||
border-radius: 0.75em;
|
||||
text-decoration: none;
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
---
|
||||
import Button from "components/Button.astro";
|
||||
import Tooltip from "components/Tooltip.astro";
|
||||
import { getI18n, locales } from "translations/translations";
|
||||
import MasoActor from "components/Maso/MasoActor.astro";
|
||||
import MasoTarget from "components/Maso/MasoTarget.astro";
|
||||
import { getI18n } from "translations/translations";
|
||||
|
||||
export const partial = true;
|
||||
|
||||
|
@ -15,7 +17,11 @@ const lang = Astro.props.lang ?? reqUrl.searchParams.get("lang")!;
|
|||
const { t } = await getI18n(lang);
|
||||
---
|
||||
|
||||
<div class="hx-swap-content">
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<MasoTarget>
|
||||
<Tooltip trigger="click" class="when-js">
|
||||
<Button
|
||||
icon="material-symbols:translate"
|
||||
|
@ -25,33 +31,30 @@ const { t } = await getI18n(lang);
|
|||
|
||||
<div id="content" slot="tooltip-content">
|
||||
{
|
||||
locales.map((locale) => (
|
||||
<a
|
||||
["en", "fr"].map((locale) => (
|
||||
<MasoActor
|
||||
class:list={{ current: locale === lang }}
|
||||
hx-get={`/api/content?lang=${locale}`}
|
||||
hx-trigger="click"
|
||||
hx-target="closest .hx-swap-content"
|
||||
hx-swap="outerHTML"
|
||||
href={`/api/content?lang=${locale}`}
|
||||
>
|
||||
{locale.toString().toUpperCase()}
|
||||
</a>
|
||||
</MasoActor>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div set:html={t("home.description")} />
|
||||
</div>
|
||||
</MasoTarget>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||
}
|
||||
|
||||
<style>
|
||||
#content {
|
||||
display: grid;
|
||||
gap: 0.5em;
|
||||
|
||||
& > a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
& > .current {
|
||||
& > :global(.current) {
|
||||
color: var(--color-base-750);
|
||||
text-decoration: underline 0.08em var(--color-base-650);
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,18 @@
|
|||
import { payload } from "src/shared/payload/payload-sdk";
|
||||
|
||||
type Cache = {
|
||||
locales: string[];
|
||||
currencies: string[];
|
||||
};
|
||||
|
||||
const fetchNewData = async (): Promise<Cache> => ({
|
||||
locales: (await payload.getLanguages()).map(({ id }) => id),
|
||||
currencies: (await payload.getCurrencies()).map(({ id }) => id),
|
||||
});
|
||||
|
||||
export let cache = await fetchNewData();
|
||||
|
||||
setInterval(async () => {
|
||||
console.log("Refreshing cached Payload data")
|
||||
cache = await fetchNewData();
|
||||
}, 1000_000);
|
|
@ -0,0 +1,56 @@
|
|||
import type { AstroCookies } from "astro";
|
||||
import { cache } from "src/utils/cachedPayload";
|
||||
import { z } from "zod";
|
||||
|
||||
export enum CookieKeys {
|
||||
Currency = "al_pref_currency",
|
||||
Theme = "al_pref_theme",
|
||||
Languages = "al_pref_languages",
|
||||
}
|
||||
|
||||
export const themeSchema = z.enum(["dark", "light", "auto"]);
|
||||
|
||||
export const getCookieLocale = (cookies: AstroCookies): string | undefined => {
|
||||
const cookie = cookies.get(CookieKeys.Languages);
|
||||
|
||||
try {
|
||||
const json = cookie?.json();
|
||||
const result = z.array(z.string()).nonempty().safeParse(json);
|
||||
if (result.success && isValidLocale(result.data[0])) {
|
||||
return result.data[0];
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const getCookieCurrency = (
|
||||
cookies: AstroCookies
|
||||
): string | undefined => {
|
||||
const cookieValue = cookies.get(CookieKeys.Currency)?.value;
|
||||
return isValidCurrency(cookieValue)
|
||||
? cookieValue
|
||||
: undefined;
|
||||
};
|
||||
|
||||
export const getCookieTheme = (
|
||||
cookies: AstroCookies
|
||||
): z.infer<typeof themeSchema> | undefined => {
|
||||
const cookieValue = cookies.get(CookieKeys.Theme)?.value;
|
||||
const result = themeSchema.safeParse(cookieValue);
|
||||
return result.success ? result.data : undefined;
|
||||
};
|
||||
|
||||
export const isValidCurrency = (
|
||||
currency: string | null | undefined
|
||||
): currency is string =>
|
||||
currency !== null &&
|
||||
currency != undefined &&
|
||||
cache.currencies.includes(currency);
|
||||
|
||||
export const isValidLocale = (
|
||||
locale: string | null | undefined
|
||||
): locale is string =>
|
||||
locale !== null && locale != undefined && cache.locales.includes(locale);
|
|
@ -0,0 +1,12 @@
|
|||
export const customElement = (
|
||||
name: string,
|
||||
constructor?: (elem: HTMLElement) => void
|
||||
) => {
|
||||
class CustomElementClass extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
constructor?.(this);
|
||||
}
|
||||
}
|
||||
customElements.define(name, CustomElementClass);
|
||||
};
|
|
@ -6,11 +6,25 @@
|
|||
"home.aboutUsButton": "Read more about us",
|
||||
"home.librarySection.title": "The Library",
|
||||
"home.librarySection.description": "Here you will find a list of IPs Yoko Taro worked on. Select one to discover all the media/content/articles that relates to this IP. <strong>Beware there can be spoilers.</strong>",
|
||||
"home.librarySection.button": "Browse all content",
|
||||
"home.chroniclesSection.title": "The Chronicles",
|
||||
"home.chroniclesSection.description": "Interested in exploring the Yokoverse lore? Experience all events and content in chronological order. <strong>Beware there can be spoilers.</strong>",
|
||||
"home.changesSection.title": "What’s new?",
|
||||
"home.changesSection.description": "Here are the 10 most recently added/updated content. You can open the changelog to see all past changes.",
|
||||
"home.changesSection.button": "Open the changelog",
|
||||
"home.moreSection.title": "More content",
|
||||
"home.moreSection.description": "The NieR and Drakengard series share a common timeline which you can explore it at the link bellow. Also we have gathered thousands of official artworks, videos, and notable web resources. <strong>Beware there can be spoilers.</strong>",
|
||||
"home.linksSection.title": "Links",
|
||||
"home.linksSection.description": "Do you have a <strong>question</strong>? Would like to share something with our <strong>community</strong>? Are you interested in <strong>contributing</strong> to this project? Whatever it is, you should find what you are looking for at the following links.",
|
||||
|
||||
"settings.title": "Settings",
|
||||
"settings.theme.title": "Theme",
|
||||
"settings.theme.description": "Switch between dark/light mode",
|
||||
"settings.language.title": "Language",
|
||||
"settings.language.description": "Select preferred language",
|
||||
"settings.currency.title": "Currency",
|
||||
"settings.currency.description": "Select preferred currency",
|
||||
|
||||
"header.topbar.search.tooltip": "Search on this website",
|
||||
"header.topbar.theme.tooltip": "Switch between dark/light mode",
|
||||
"header.topbar.theme.dark": "Dark",
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
import type { AstroCookies } from "astro";
|
||||
import { cache } from "src/utils/cachedPayload";
|
||||
import en from "./en.json";
|
||||
import fr from "./fr.json";
|
||||
import ja from "./ja.json"
|
||||
import ja from "./ja.json";
|
||||
|
||||
import acceptLanguage from 'accept-language';
|
||||
import { z } from "zod";
|
||||
import acceptLanguage from "accept-language";
|
||||
|
||||
type WordingKeys = keyof typeof en;
|
||||
const translationFiles: Record<string, Record<WordingKeys, string>> = {
|
||||
en,
|
||||
fr,
|
||||
ja
|
||||
ja,
|
||||
};
|
||||
|
||||
export const getI18n = async (locale: string) => {
|
||||
|
@ -118,6 +117,13 @@ export const getI18n = async (locale: string) => {
|
|||
return `«${key}»`;
|
||||
},
|
||||
getLocalizedUrl: (url: string): string => `/${locale}${url}`,
|
||||
getLocalizedMatch: <T extends { language: string }>(
|
||||
options: T[],
|
||||
fallback: Omit<T, "language">
|
||||
): Omit<T, "language"> =>
|
||||
options.find(({ language }) => language === locale) ??
|
||||
options.find(({ language }) => language === defaultLocale) ??
|
||||
fallback,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -140,15 +146,11 @@ const limitMatchToBalanceCurlyBraces = (
|
|||
return match.substring(0, index);
|
||||
};
|
||||
|
||||
export const locales = ["en", "es", "fr", "ja", "pt", "zh"] as const;
|
||||
acceptLanguage.languages([...locales]);
|
||||
|
||||
export type Locale = (typeof locales)[number];
|
||||
|
||||
export type Locale = string;
|
||||
export const defaultLocale: Locale = "en";
|
||||
|
||||
export const getCurrentLocale = (pathname: string): Locale | undefined => {
|
||||
for (const locale of locales) {
|
||||
for (const locale of cache.locales) {
|
||||
if (pathname.startsWith(`/${locale}`)) {
|
||||
return locale;
|
||||
}
|
||||
|
@ -156,29 +158,14 @@ export const getCurrentLocale = (pathname: string): Locale | undefined => {
|
|||
return undefined;
|
||||
};
|
||||
|
||||
export const getPreferredLocale = (request: Request): Locale | undefined => {
|
||||
return acceptLanguage.get(request.headers.get("Accept-Language")) as Locale | null ?? undefined;
|
||||
};
|
||||
|
||||
export const getCookiePreferredLocale = (
|
||||
cookies: AstroCookies
|
||||
): string | undefined => {
|
||||
const alPrefLanguages = cookies.get("al_pref_languages");
|
||||
|
||||
try {
|
||||
const json = alPrefLanguages?.json();
|
||||
const result = z.array(z.string()).nonempty().safeParse(json);
|
||||
if (result.success) {
|
||||
for (const value of result.data) {
|
||||
if (locales.includes(value as Locale)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
export const getBestAcceptedLanguage = (
|
||||
request: Request
|
||||
): Locale | undefined => {
|
||||
acceptLanguage.languages(cache.locales);
|
||||
|
||||
return (
|
||||
(acceptLanguage.get(
|
||||
request.headers.get("Accept-Language")
|
||||
) as Locale | null) ?? undefined
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue