Muchos
This commit is contained in:
parent
88b9613abf
commit
f4cede5240
|
@ -20,6 +20,7 @@ export default defineConfig({
|
|||
},
|
||||
}),
|
||||
],
|
||||
devToolbar: { enabled: false },
|
||||
server: {
|
||||
port: 12499,
|
||||
host: true,
|
||||
|
|
19
package.json
19
package.json
|
@ -10,15 +10,20 @@
|
|||
"astro": "astro",
|
||||
"upgrade": "ncu",
|
||||
"script:download-payload-sdk": "bun run scripts/download-payload-sdk.ts",
|
||||
"script:download-currencies": "bun run scripts/download-currencies.ts"
|
||||
"script:download-currencies": "bun run scripts/download-currencies.ts",
|
||||
"script:download-wording-keys": "bun run scripts/download-wording-keys.ts"
|
||||
},
|
||||
"engines": {
|
||||
"npm": ">=10.0.0",
|
||||
"node": ">=19.7.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.5.4",
|
||||
"@astrojs/node": "^8.2.0",
|
||||
"@astrojs/check": "^0.5.6",
|
||||
"@astrojs/node": "^8.2.1",
|
||||
"@fontsource-variable/murecho": "^5.0.17",
|
||||
"@fontsource-variable/vollkorn": "^5.0.19",
|
||||
"accept-language": "^3.0.18",
|
||||
"astro": "4.3.7",
|
||||
"astro": "4.4.6",
|
||||
"astro-icon": "^1.1.0",
|
||||
"node-cache": "^5.1.2",
|
||||
"tippy.js": "^6.3.7",
|
||||
|
@ -26,13 +31,13 @@
|
|||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/material-symbols": "^1.1.72",
|
||||
"@iconify-json/material-symbols": "^1.1.73",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"astro-meta-tags": "^0.2.1",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"bun-types": "^1.0.26",
|
||||
"bun-types": "^1.0.29",
|
||||
"npm-check-updates": "^16.14.15",
|
||||
"postcss-preset-env": "^9.3.0",
|
||||
"postcss-preset-env": "^9.4.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
|
|
|
@ -1,16 +1,23 @@
|
|||
---
|
||||
import Html from "./components/Html.astro";
|
||||
import Topbar from "./components/Topbar.astro";
|
||||
import Topbar from "./components/Topbar/Topbar.astro";
|
||||
import Footer from "./components/Footer.astro";
|
||||
import type { ComponentProps } from "astro/types";
|
||||
import type { ParentPage } from "src/shared/payload/payload-sdk";
|
||||
import AppLayoutBackgroundImg from "./components/AppLayoutBackgroundImg.astro";
|
||||
|
||||
interface Props {
|
||||
parentPages?: ComponentProps<typeof Topbar>["parentPages"];
|
||||
parentPages?: ParentPage[];
|
||||
metaTitle?: string;
|
||||
hideFooterLinks?: boolean;
|
||||
backgroundIllustration?: string | undefined;
|
||||
}
|
||||
|
||||
const { metaTitle, hideFooterLinks = false, parentPages } = Astro.props;
|
||||
const {
|
||||
metaTitle,
|
||||
hideFooterLinks = false,
|
||||
parentPages,
|
||||
backgroundIllustration,
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
|
@ -19,6 +26,11 @@ const { metaTitle, hideFooterLinks = false, parentPages } = Astro.props;
|
|||
|
||||
<Html title={metaTitle}>
|
||||
<header>
|
||||
{
|
||||
backgroundIllustration && (
|
||||
<AppLayoutBackgroundImg src={backgroundIllustration} />
|
||||
)
|
||||
}
|
||||
<Topbar parentPages={parentPages} />
|
||||
</header>
|
||||
<main><slot /></main>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
import Html from "./components/Html.astro";
|
||||
import AppLayoutBackgroundImg from "./components/AppLayoutBackgroundImg.astro";
|
||||
import Topbar from "./components/Topbar.astro";
|
||||
import Topbar from "./components/Topbar/Topbar.astro";
|
||||
import Footer from "./components/Footer.astro";
|
||||
import AppLayoutTitle from "./components/AppLayoutTitle.astro";
|
||||
import type { ComponentProps } from "astro/types";
|
||||
|
|
|
@ -25,7 +25,6 @@ const styleNoScript = `
|
|||
}
|
||||
|
||||
<img id={uniqueId} src={src} alt={alt} class="when-no-print" />
|
||||
|
||||
<noscript set:html={styleNoScript} />
|
||||
|
||||
{
|
||||
|
@ -36,7 +35,6 @@ const styleNoScript = `
|
|||
img {
|
||||
opacity: 0;
|
||||
transition: 3s opacity;
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -54,6 +52,10 @@ const styleNoScript = `
|
|||
}
|
||||
</style>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- JS --------------------------------------------- */
|
||||
}
|
||||
|
||||
<script define:vars={{ uniqueId }}>
|
||||
const element = document.getElementById(uniqueId);
|
||||
|
||||
|
|
|
@ -8,12 +8,20 @@ interface Props {
|
|||
const { title, subtitle, pretitle } = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<h1 class="high-contrast-text">
|
||||
{pretitle && <span id="pretitle">{pretitle} </span>}
|
||||
<span id="title">{title} </span>
|
||||
{subtitle && <span id="subtitle">{subtitle}</span>}
|
||||
</h1>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||
}
|
||||
|
||||
<style>
|
||||
h1 {
|
||||
line-height: 0.8;
|
||||
|
|
|
@ -131,6 +131,10 @@ const contactLabel = `${t("footer.socials.contact.title")} - ${t(
|
|||
<div id="copyright" set:html={t("footer.disclaimer")} />
|
||||
</footer>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||
}
|
||||
|
||||
<style>
|
||||
footer {
|
||||
border-top: 0.1em solid var(--color-base-1000);
|
||||
|
@ -251,6 +255,7 @@ const contactLabel = `${t("footer.socials.contact.title")} - ${t(
|
|||
|
||||
& > #license-section {
|
||||
grid-area: license;
|
||||
line-height: 1.2;
|
||||
|
||||
& > #common-creative {
|
||||
display: flex;
|
||||
|
@ -288,6 +293,7 @@ const contactLabel = `${t("footer.socials.contact.title")} - ${t(
|
|||
|
||||
@media (max-width: 35rem) {
|
||||
place-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: clamp(24px, 8vw, 48px);
|
||||
|
||||
& > a > svg {
|
||||
|
@ -301,6 +307,7 @@ const contactLabel = `${t("footer.socials.contact.title")} - ${t(
|
|||
& > #copyright {
|
||||
border-left: 0.1em solid var(--color-base-1000);
|
||||
padding-left: 1em;
|
||||
line-height: 1.2;
|
||||
|
||||
@media (max-width: 35rem) {
|
||||
border: none;
|
||||
|
|
|
@ -16,7 +16,8 @@ const isIOS = parser.getOS().name === "iOS";
|
|||
|
||||
const { currentTheme } = Astro.locals;
|
||||
|
||||
/* -------------------------------------------- HTML -------------------------------------------- */
|
||||
/* Keep that separator here or else it breaks the HTML
|
||||
----------------------------------------------- HTML -------------------------------------------- */
|
||||
---
|
||||
|
||||
<html
|
||||
|
@ -46,8 +47,14 @@ const { currentTheme } = Astro.locals;
|
|||
/>
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
|
||||
<style is:global>
|
||||
.when-no-js {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<noscript>
|
||||
<style>
|
||||
<style is:global>
|
||||
.when-js {
|
||||
display: none !important;
|
||||
}
|
||||
|
@ -69,12 +76,6 @@ const { currentTheme } = Astro.locals;
|
|||
}
|
||||
|
||||
<style is:global>
|
||||
@media print {
|
||||
.when-no-print {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
--color-base-0: #ffffff;
|
||||
--color-base-50: #fffaf3;
|
||||
|
@ -107,6 +108,8 @@ const { currentTheme } = Astro.locals;
|
|||
--color-shadow-1: var(--color-base-350);
|
||||
--color-shadow-2: var(--color-base-300);
|
||||
|
||||
--color-critical-error: #940000;
|
||||
|
||||
--texture-dots: url(/img/paper-dots.webp);
|
||||
--texture-dots-blend: multiply;
|
||||
|
||||
|
@ -157,6 +160,8 @@ const { currentTheme } = Astro.locals;
|
|||
--color-shadow-1: var(--color-base-0);
|
||||
--color-shadow-2: var(--color-base-50);
|
||||
|
||||
--color-critical-error: red;
|
||||
|
||||
--texture-dots: url(/img/paper-dots-dark.webp);
|
||||
--texture-dots-blend: overlay;
|
||||
|
||||
|
@ -170,57 +175,6 @@ const { currentTheme } = Astro.locals;
|
|||
}
|
||||
|
||||
&:not(.manual-theme) {
|
||||
/* Get in between colors with https://colorkit.io/ */
|
||||
@media (prefers-color-scheme: light) {
|
||||
--color-base-0: #ffffff;
|
||||
--color-base-50: #fffaf3;
|
||||
--color-base-100: #fff4e6;
|
||||
--color-base-125: #fef0dd;
|
||||
--color-base-150: #fdebd4;
|
||||
--color-base-200: #f7ddc2;
|
||||
--color-base-250: #efcfb0;
|
||||
--color-base-300: #e5be9e;
|
||||
--color-base-350: #ddb08e;
|
||||
--color-base-400: #d3a07c;
|
||||
--color-base-450: #ca926c;
|
||||
--color-base-500: #c0835d;
|
||||
--color-base-550: #b3754f;
|
||||
--color-base-600: #a26a47;
|
||||
--color-base-650: #905e3f;
|
||||
--color-base-700: #805438;
|
||||
--color-base-750: #6e4a31;
|
||||
--color-base-800: #5e402b;
|
||||
--color-base-850: #4d3625;
|
||||
--color-base-900: #3c2d1e;
|
||||
--color-base-950: #2f2419;
|
||||
--color-base-1000: #1f1a13;
|
||||
|
||||
--color-elevation-2: var(--color-base-100);
|
||||
--color-elevation-1: var(--color-base-125);
|
||||
--color-elevation-0: var(--color-base-150);
|
||||
|
||||
--color-shadow: var(--color-base-500);
|
||||
--color-shadow-1: var(--color-base-350);
|
||||
--color-shadow-2: var(--color-base-300);
|
||||
|
||||
--texture-dots: url(/img/paper-dots.webp);
|
||||
--texture-dots-blend: multiply;
|
||||
|
||||
& .when-light-theme {
|
||||
display: initial !important;
|
||||
}
|
||||
|
||||
& .when-dark-theme {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
font-weight: 430;
|
||||
|
||||
strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
--color-base-1000: #ebeae7;
|
||||
--color-base-950: #eae5e0;
|
||||
|
@ -253,6 +207,8 @@ const { currentTheme } = Astro.locals;
|
|||
--color-shadow-1: var(--color-base-0);
|
||||
--color-shadow-2: var(--color-base-50);
|
||||
|
||||
--color-critical-error: red;
|
||||
|
||||
--texture-dots: url(/img/paper-dots-dark.webp);
|
||||
--texture-dots-blend: overlay;
|
||||
|
||||
|
@ -341,8 +297,21 @@ const { currentTheme } = Astro.locals;
|
|||
|
||||
p {
|
||||
& a {
|
||||
transition-duration: 150ms;
|
||||
transition-property: text-decoration-color, color;
|
||||
|
||||
color: var(--color-base-750);
|
||||
text-decoration: underline dotted 0.1em var(--color-base-650);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-base-850);
|
||||
text-decoration-color: var(--color-base-750);
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: var(--color-base-1000);
|
||||
text-decoration-color: var(--color-base-1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -369,6 +338,29 @@ const { currentTheme } = Astro.locals;
|
|||
}
|
||||
}
|
||||
|
||||
.pressable-label {
|
||||
text-decoration: none;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
gap: 0.4em;
|
||||
padding: 0.7em 0.8em;
|
||||
border-radius: 9999px;
|
||||
cursor: pointer;
|
||||
|
||||
backdrop-filter: blur(10px);
|
||||
|
||||
transition: 150ms background-color;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-base-250);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--color-base-300);
|
||||
}
|
||||
}
|
||||
|
||||
.pressable {
|
||||
--foreground-color: var(--color-base-650);
|
||||
color: var(--foreground-color);
|
||||
|
@ -396,17 +388,13 @@ const { currentTheme } = Astro.locals;
|
|||
}
|
||||
|
||||
.hide-scrollbar {
|
||||
scrollbar-width: none; /* Firefox */
|
||||
scrollbar-width: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.when-no-js {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.prose {
|
||||
font-size: 16px;
|
||||
line-height: 1.75;
|
||||
|
@ -425,6 +413,12 @@ const { currentTheme } = Astro.locals;
|
|||
> p {
|
||||
margin-top: 1.25em;
|
||||
margin-bottom: 1.25em;
|
||||
|
||||
> kbd {
|
||||
background-color: var(--color-shadow-2);
|
||||
padding: 0.15em 0.3em;
|
||||
border-radius: 0.3em;
|
||||
}
|
||||
}
|
||||
|
||||
> h2 {
|
||||
|
@ -443,6 +437,7 @@ const { currentTheme } = Astro.locals;
|
|||
margin-top: 2em;
|
||||
margin-bottom: 1em;
|
||||
line-height: 1;
|
||||
scroll-margin: 1em;
|
||||
}
|
||||
|
||||
> h2 + h3,
|
||||
|
@ -452,4 +447,16 @@ const { currentTheme } = Astro.locals;
|
|||
margin-top: -0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
scroll-behavior: smooth;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--color-base-650) transparent;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.when-no-print {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
---
|
||||
import { Icon } from "astro-icon/components";
|
||||
import Button from "components/Button.astro";
|
||||
import ThemeSelector from "components/AppLayout/components/ThemeSelector.astro";
|
||||
import LanguageSelector from "components/AppLayout/components/LanguageSelector.astro";
|
||||
import CurrencySelector from "components/AppLayout/components/CurrencySelector.astro";
|
||||
import ThemeSelector from "./components/ThemeSelector.astro";
|
||||
import LanguageSelector from "./components/LanguageSelector.astro";
|
||||
import CurrencySelector from "./components/CurrencySelector.astro";
|
||||
import { getI18n } from "translations/translations";
|
||||
import Tooltip from "components/Tooltip.astro";
|
||||
import type { ParentPage } from "src/shared/payload/payload-sdk";
|
||||
import ParentPagesButton from "./components/ParentPagesButton.astro";
|
||||
|
||||
interface Props {
|
||||
parentPages?: { name: string; slug: string; type: string }[] | undefined;
|
||||
parentPages?: ParentPage[] | undefined;
|
||||
hideHomeButton?: boolean;
|
||||
}
|
||||
|
||||
|
@ -24,31 +25,20 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
|||
<nav id="topbar" class="when-no-print">
|
||||
{
|
||||
(!hideHomeButton || parentPages.length > 0) && (
|
||||
<div id="breadcrumb" class="hide-scrollbar high-contrast-text">
|
||||
<a href="/">
|
||||
<div id="left" class="hide-scrollbar high-contrast-text">
|
||||
<a href="/" class="pressable-label">
|
||||
<Icon name="material-symbols:home" width={16} height={16} />
|
||||
<p>{t("home.title")}</p>
|
||||
</a>
|
||||
|
||||
{parentPages.length > 0 && (
|
||||
<Tooltip trigger="click">
|
||||
<div slot="tooltip-content">
|
||||
<p>This content is part of these pages:</p>
|
||||
<p>NieR / Concert</p>
|
||||
<p>NieR:Automata / Concert</p>
|
||||
<p>NieR:Theatrical Orchestra Concert 12020 Bluray</p>
|
||||
</div>
|
||||
<div>
|
||||
<Icon name="material-symbols:keyboard-return" />
|
||||
<p>4 parent pages</p>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<ParentPagesButton parentPages={parentPages} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<div id="toolbar">
|
||||
<div id="toolbar" class="hide-scrollbar">
|
||||
<a href={getLocalizedUrl("/search")}>
|
||||
<Button
|
||||
icon="material-symbols:search"
|
||||
|
@ -89,40 +79,16 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
|||
flex-wrap: wrap-reverse;
|
||||
gap: 32px 64px;
|
||||
|
||||
& > #breadcrumb {
|
||||
& > #left {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
overflow-x: scroll;
|
||||
gap: 8px;
|
||||
margin-left: -0.8em;
|
||||
|
||||
& > svg {
|
||||
& > :global(*) {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
& > a,
|
||||
& > :global(tippy-tooltip > div) {
|
||||
text-decoration: none;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
gap: 0.4em;
|
||||
padding: 0.7em 0.8em;
|
||||
border-radius: 9999px;
|
||||
cursor: pointer;
|
||||
|
||||
backdrop-filter: blur(10px);
|
||||
|
||||
transition: 150ms background-color;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-base-250);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--color-base-300);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& > #toolbar {
|
||||
|
@ -131,6 +97,7 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
|||
gap: 12px;
|
||||
place-items: center;
|
||||
justify-content: flex-end;
|
||||
overflow-x: scroll;
|
||||
|
||||
@media (max-width: 28rem) {
|
||||
justify-content: center;
|
||||
|
@ -138,7 +105,7 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
|||
|
||||
@media (max-width: 22rem) {
|
||||
justify-content: space-between;
|
||||
gap: 0;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
& > .separator {
|
||||
|
@ -172,11 +139,3 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
|||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script is:inline>
|
||||
const breadcrumbElem = document.querySelector("nav#topbar > #breadcrumb");
|
||||
breadcrumbElem?.scrollTo({
|
||||
left: breadcrumbElem.scrollWidth,
|
||||
behavior: "instant",
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,52 @@
|
|||
---
|
||||
import { Collections, type ParentPage } from "src/shared/payload/payload-sdk";
|
||||
import { getI18n } from "translations/translations";
|
||||
|
||||
interface Props {
|
||||
parentPage: ParentPage;
|
||||
}
|
||||
|
||||
const { parentPage } = Astro.props;
|
||||
const { getLocalizedMatch, getLocalizedUrl } = await getI18n(
|
||||
Astro.locals.currentLocale
|
||||
);
|
||||
|
||||
const translation = getLocalizedMatch(parentPage.translations, {
|
||||
name: parentPage.slug,
|
||||
});
|
||||
|
||||
let href = "";
|
||||
switch (parentPage.collection) {
|
||||
case Collections.Folders:
|
||||
href = getLocalizedUrl(`/folders/${parentPage.slug}`);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<a href={href}><span>{parentPage.tag}</span>{translation.name}</a>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||
}
|
||||
|
||||
<style>
|
||||
a {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
|
||||
& > span {
|
||||
background-color: var(--color-base-250);
|
||||
border-radius: 9999px;
|
||||
padding: 0.5em 0.6em;
|
||||
margin-right: 0.5em;
|
||||
font-size: 80%;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,51 @@
|
|||
---
|
||||
import Tooltip from "components/Tooltip.astro";
|
||||
import type { ParentPage } from "src/shared/payload/payload-sdk";
|
||||
import ParentPageLink from "./ParentPageLink.astro";
|
||||
import { Icon } from "astro-icon/components";
|
||||
import { getI18n } from "translations/translations";
|
||||
|
||||
interface Props {
|
||||
parentPages: ParentPage[];
|
||||
}
|
||||
|
||||
const { parentPages } = Astro.props;
|
||||
|
||||
const { t } = await getI18n(Astro.locals.currentLocale);
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<Tooltip trigger="click">
|
||||
<div id="tooltip-content" slot="tooltip-content">
|
||||
<p>This content is part of these pages:</p>
|
||||
{
|
||||
parentPages.map((parentPage) => (
|
||||
<ParentPageLink parentPage={parentPage} />
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<div class="pressable-label">
|
||||
<Icon name="material-symbols:keyboard-return" />
|
||||
<p>
|
||||
{
|
||||
t("header.nav.parentPages.label", {
|
||||
count: parentPages.length,
|
||||
})
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||
}
|
||||
|
||||
<style>
|
||||
#tooltip-content {
|
||||
display: grid;
|
||||
gap: 1em;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
import {
|
||||
isBlockLineBlock,
|
||||
type GenericBlock,
|
||||
isBlockCueBlock,
|
||||
isBlockSpacerBlock,
|
||||
} from "src/shared/payload/payload-sdk";
|
||||
|
||||
import LineBlock from "./components/LineBlock.astro";
|
||||
import CueBlock from "./components/CueBlock.astro";
|
||||
import ErrorMessage from "components/ErrorMessage.astro";
|
||||
import SpacerBlock from "./components/SpacerBlock.astro";
|
||||
|
||||
interface Props {
|
||||
block: GenericBlock;
|
||||
}
|
||||
|
||||
const { block } = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
{
|
||||
isBlockLineBlock(block) ? (
|
||||
<LineBlock block={block} />
|
||||
) : isBlockCueBlock(block) ? (
|
||||
<CueBlock block={block} />
|
||||
) : isBlockSpacerBlock(block) ? (
|
||||
<SpacerBlock block={block} />
|
||||
) : (
|
||||
<ErrorMessage
|
||||
title={`Unknown block type: ${block.blockType}`}
|
||||
description="Please contact website technical administrator."
|
||||
/>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
import RichText from "components/RichText/RichText.astro";
|
||||
import type { CueBlock } from "src/shared/payload/payload-sdk";
|
||||
|
||||
interface Props {
|
||||
block: CueBlock;
|
||||
}
|
||||
|
||||
const { block } = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<div>
|
||||
<RichText content={block.content} />
|
||||
</div>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||
}
|
||||
|
||||
<style>
|
||||
div {
|
||||
grid-column: span 2;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
import RichText from "components/RichText/RichText.astro";
|
||||
import type { LineBlock } from "src/shared/payload/payload-sdk";
|
||||
|
||||
interface Props {
|
||||
block: LineBlock;
|
||||
}
|
||||
|
||||
const { block } = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<div id="line">
|
||||
<p>{block.blockName}</p>
|
||||
<div>
|
||||
<RichText content={block.content} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||
}
|
||||
|
||||
<style>
|
||||
#line {
|
||||
display: grid;
|
||||
grid-column: span 2;
|
||||
grid-template-columns: subgrid;
|
||||
|
||||
p {
|
||||
color: var(--color-base-650);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
import type { SpacerBlock } from "src/shared/payload/payload-sdk";
|
||||
|
||||
interface Props {
|
||||
block: SpacerBlock;
|
||||
}
|
||||
|
||||
const { block } = Astro.props;
|
||||
|
||||
const spaceSizeToRem: Record<SpacerBlock["size"], number> = {
|
||||
Small: 1,
|
||||
Medium: 2,
|
||||
Large: 4,
|
||||
XLarge: 8,
|
||||
};
|
||||
---
|
||||
|
||||
<div style={`height: ${spaceSizeToRem[block.size]}rem`}></div>
|
|
@ -1,62 +0,0 @@
|
|||
---
|
||||
|
||||
---
|
||||
|
||||
<details>
|
||||
<summary><slot name="header" /></summary>
|
||||
<div class="content">
|
||||
<div class="content2">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<style>
|
||||
details {
|
||||
--foreground-color: var(--color-base-650);
|
||||
color: var(--foreground-color);
|
||||
border: 0.1em solid var(--foreground-color);
|
||||
background-color: var(--color-elevation-0);
|
||||
border-radius: 1.25em;
|
||||
|
||||
transition: all 1s;
|
||||
|
||||
&[open] {
|
||||
& > summary {
|
||||
border-bottom: 1px solid #aaa;
|
||||
}
|
||||
|
||||
& > .content {
|
||||
animation: animate 0.5s forwards;
|
||||
}
|
||||
}
|
||||
|
||||
& > summary {
|
||||
list-style: none;
|
||||
height: 2.5em;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
& > .content {
|
||||
padding: 1em;
|
||||
display: grid;
|
||||
|
||||
& > .content2 {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes animate {
|
||||
from {
|
||||
grid-template-rows: 0fr;
|
||||
}
|
||||
to {
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -7,6 +7,10 @@ interface Props {
|
|||
const { wrapper: Wrapper, condition } = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
{
|
||||
condition ? (
|
||||
<Wrapper>
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
import { getI18n } from "translations/translations";
|
||||
import Metadata from "./Metadata.astro";
|
||||
|
||||
interface Props {
|
||||
translators?: string[] | undefined;
|
||||
transcribers?: string[] | undefined;
|
||||
proofreaders?: string[] | undefined;
|
||||
}
|
||||
|
||||
const { translators = [], transcribers = [], proofreaders = [] } = Astro.props;
|
||||
const { formatRecorder } = await getI18n(Astro.locals.currentLocale);
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<Metadata
|
||||
icon="material-symbols:person-outline"
|
||||
title="Translators"
|
||||
values={translators.map((id) => formatRecorder(id))}
|
||||
/>
|
||||
|
||||
<Metadata
|
||||
icon="material-symbols:person-edit-outline"
|
||||
title="Transcribers"
|
||||
values={transcribers.map((id) => formatRecorder(id))}
|
||||
/>
|
||||
|
||||
<Metadata
|
||||
icon="material-symbols:person-check-outline"
|
||||
title="Proofreaders"
|
||||
values={proofreaders.map((id) => formatRecorder(id))}
|
||||
/>
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
import { Icon } from "astro-icon/components";
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
const { title, description } = Astro.props;
|
||||
---
|
||||
|
||||
<div>
|
||||
<Icon name="material-symbols:error-outline" width={32} height={32} />
|
||||
<p id="title">{title}</p>
|
||||
{description && <p>{description}</p>}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@keyframes flashingRed {
|
||||
from {
|
||||
background-color: #ff000022;
|
||||
}
|
||||
|
||||
to {
|
||||
background-color: #ff000033;
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
color: var(--color-critical-error) !important;
|
||||
padding: 2em 2em !important;
|
||||
margin-block: 4em !important;
|
||||
border-radius: 1em;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
|
||||
animation: flashingRed;
|
||||
animation-duration: 0.5s;
|
||||
animation-direction: alternate;
|
||||
animation-iteration-count: infinite;
|
||||
|
||||
& > #title {
|
||||
font-weight: 600;
|
||||
font-size: 120%;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,31 +0,0 @@
|
|||
---
|
||||
import { getRandomId } from "src/utils/random";
|
||||
|
||||
interface Props {
|
||||
src: string;
|
||||
alt?: string;
|
||||
id?: string;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { src, alt } = Astro.props;
|
||||
|
||||
const uniqueId = getRandomId();
|
||||
---
|
||||
|
||||
<img id={uniqueId} src={src} alt={alt} />
|
||||
|
||||
<script define:vars={{ uniqueId }}>
|
||||
const element = document.getElementById(uniqueId);
|
||||
|
||||
element.addEventListener("load", () => {
|
||||
element.style.opacity = 1;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
img {
|
||||
opacity: 0;
|
||||
transition: 3s opacity;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,66 @@
|
|||
---
|
||||
import MasoActor from "components/Maso/MasoActor.astro";
|
||||
import Tooltip from "components/Tooltip.astro";
|
||||
import Button from "components/Button.astro";
|
||||
import { getI18n } from "translations/translations";
|
||||
|
||||
interface Props {
|
||||
currentLang: string;
|
||||
getPartialUrl: (locale: string) => string;
|
||||
availableLanguages: string[];
|
||||
}
|
||||
|
||||
const { currentLang, getPartialUrl, availableLanguages } = Astro.props;
|
||||
const { formatLocale } = await getI18n(Astro.locals.currentLocale);
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<div id="lang-selector" class="when-js when-no-print">
|
||||
<Tooltip trigger="click">
|
||||
<Button
|
||||
icon="material-symbols:translate"
|
||||
title={currentLang.toUpperCase()}
|
||||
/>
|
||||
|
||||
<div id="tooltip-content" slot="tooltip-content">
|
||||
{
|
||||
availableLanguages.map((id) => (
|
||||
<MasoActor
|
||||
class:list={{ current: id === currentLang }}
|
||||
href={getPartialUrl(id)}
|
||||
>
|
||||
{formatLocale(id)}
|
||||
</MasoActor>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<p class="high-contrast-text">
|
||||
This content is available is {availableLanguages.length} languages.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||
}
|
||||
|
||||
<style>
|
||||
#lang-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1em;
|
||||
|
||||
#tooltip-content {
|
||||
display: grid;
|
||||
gap: 0.5em;
|
||||
|
||||
& > .current {
|
||||
color: var(--color-base-750);
|
||||
text-decoration: underline 0.08em var(--color-base-650);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -11,6 +11,6 @@
|
|||
}
|
||||
|
||||
<script>
|
||||
class MasoTarget extends HTMLElement {}
|
||||
customElements.define("maso-target", MasoTarget);
|
||||
import { customElement } from "src/utils/customElements";
|
||||
customElement("maso-target");
|
||||
</script>
|
||||
|
|
|
@ -12,6 +12,10 @@ const { icon, title, values } = Astro.props;
|
|||
if (values.length === 0) return;
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<div id="container">
|
||||
<div id="title">
|
||||
<Icon name={icon} width={24} height={24} />
|
||||
|
@ -22,13 +26,17 @@ if (values.length === 0) return;
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||
}
|
||||
|
||||
<style>
|
||||
#container {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 0.5em 1em;
|
||||
align-items: center;
|
||||
|
||||
|
||||
@media (max-width: 35em) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
|
@ -2,41 +2,34 @@
|
|||
import type { RichTextContext } from "src/utils/richText";
|
||||
import RTSection from "./components/RTSection.astro";
|
||||
import RTTranscript from "./components/RTTranscript.astro";
|
||||
import {
|
||||
isBlockNodeSectionBlock,
|
||||
isBlockNodeSpacerBlock,
|
||||
isBlockNodeTranscriptBlock,
|
||||
type RichTextBlockNode,
|
||||
} from "src/shared/payload/payload-sdk";
|
||||
import ErrorMessage from "components/ErrorMessage.astro";
|
||||
import RTSpacer from "./components/RTSpacer.astro";
|
||||
|
||||
interface Props {
|
||||
node: {
|
||||
type: string;
|
||||
version: number;
|
||||
format: number;
|
||||
text: string;
|
||||
[k: string]: unknown;
|
||||
fields: {
|
||||
id: string;
|
||||
blockName: string;
|
||||
blockType: string;
|
||||
};
|
||||
};
|
||||
node: RichTextBlockNode;
|
||||
context: RichTextContext;
|
||||
}
|
||||
|
||||
const { node, context } = Astro.props;
|
||||
|
||||
let NodeElement;
|
||||
switch (node.fields.blockType) {
|
||||
case "sectionBlock":
|
||||
NodeElement = RTSection;
|
||||
break;
|
||||
|
||||
case "transcriptBlock":
|
||||
NodeElement = RTTranscript;
|
||||
break;
|
||||
}
|
||||
---
|
||||
|
||||
{
|
||||
NodeElement ? (
|
||||
<NodeElement node={node} context={context} />
|
||||
isBlockNodeSectionBlock(node) ? (
|
||||
<RTSection node={node} context={context} />
|
||||
) : isBlockNodeTranscriptBlock(node) ? (
|
||||
<RTTranscript node={node} context={context} />
|
||||
) :isBlockNodeSpacerBlock(node) ? (
|
||||
<RTSpacer node={node} context={context} />
|
||||
) : (
|
||||
<p>{`Unknown block type: ${node.fields.blockType}. Please contact website technical administrator.`}</p>
|
||||
<ErrorMessage
|
||||
title={`Unknown block type: ${node.fields.blockType}`}
|
||||
description="Please contact website technical administrator."
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
---
|
||||
import RichText from "components/RichText/RichText.astro";
|
||||
import type { RichTextContent } from "src/shared/payload/payload-sdk";
|
||||
import type { RichTextContext } from "src/utils/richText";
|
||||
|
||||
interface Props {
|
||||
block: {
|
||||
id: string;
|
||||
content: RichTextContent;
|
||||
blockType: string;
|
||||
blockName: string;
|
||||
};
|
||||
context: RichTextContext;
|
||||
}
|
||||
|
||||
const { block, context } = Astro.props;
|
||||
---
|
||||
|
||||
<div>
|
||||
<RichText content={block.content} context={context} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div {
|
||||
grid-column: span 2;
|
||||
}
|
||||
</style>
|
|
@ -1,41 +0,0 @@
|
|||
---
|
||||
import RichText from "components/RichText/RichText.astro";
|
||||
import type { RichTextContent } from "src/shared/payload/payload-sdk";
|
||||
import type { RichTextContext } from "src/utils/richText";
|
||||
|
||||
interface Props {
|
||||
block: {
|
||||
id: string;
|
||||
content: RichTextContent;
|
||||
blockType: string;
|
||||
blockName: string;
|
||||
};
|
||||
context: RichTextContext;
|
||||
}
|
||||
|
||||
const { block, context } = Astro.props;
|
||||
---
|
||||
|
||||
<p>{block.blockName}</p>
|
||||
<div>
|
||||
<RichText content={block.content} context={context} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
p {
|
||||
color: var(--color-base-650);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@media (max-width: 35rem) {
|
||||
p {
|
||||
grid-column: 1;
|
||||
margin-bottom: -1em;
|
||||
}
|
||||
|
||||
div {
|
||||
grid-column: 1;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,22 +1,12 @@
|
|||
---
|
||||
import RichText from "components/RichText/RichText.astro";
|
||||
import type { RichTextContent } from "src/shared/payload/payload-sdk";
|
||||
import type {
|
||||
RichTextSectionBlock,
|
||||
} from "src/shared/payload/payload-sdk";
|
||||
import type { RichTextContext } from "src/utils/richText";
|
||||
|
||||
interface Props {
|
||||
node: {
|
||||
type: string;
|
||||
version: number;
|
||||
format: number;
|
||||
text: string;
|
||||
[k: string]: unknown;
|
||||
fields: {
|
||||
id: string;
|
||||
blockName: string;
|
||||
blockType: string;
|
||||
lines: RichTextContent;
|
||||
};
|
||||
};
|
||||
node: RichTextSectionBlock;
|
||||
context: RichTextContext;
|
||||
}
|
||||
|
||||
|
@ -25,19 +15,43 @@ const { node, context } = Astro.props;
|
|||
|
||||
{
|
||||
context.depth < 2 ? (
|
||||
<h2>{node.fields.blockName}</h2>
|
||||
<h2 id={node.fields.anchorHash}>
|
||||
<span>{`${node.fields.anchorHash} `}</span>
|
||||
{node.fields.blockName}
|
||||
</h2>
|
||||
) : context.depth === 2 ? (
|
||||
<h3>{node.fields.blockName}</h3>
|
||||
<h3 id={node.fields.anchorHash}>
|
||||
<span>{`${node.fields.anchorHash} `}</span>
|
||||
{node.fields.blockName}
|
||||
</h3>
|
||||
) : context.depth === 3 ? (
|
||||
<h4>{node.fields.blockName}</h4>
|
||||
<h4 id={node.fields.anchorHash}>
|
||||
<span>{`${node.fields.anchorHash} `}</span>
|
||||
{node.fields.blockName}
|
||||
</h4>
|
||||
) : context.depth === 4 ? (
|
||||
<h5>{node.fields.blockName}</h5>
|
||||
<h5 id={node.fields.anchorHash}>
|
||||
<span>{`${node.fields.anchorHash} `}</span>
|
||||
{node.fields.blockName}
|
||||
</h5>
|
||||
) : (
|
||||
<h6>{node.fields.blockName}</h6>
|
||||
<h6 id={node.fields.anchorHash}>
|
||||
<span>{`${node.fields.anchorHash} `}</span>
|
||||
{node.fields.blockName}
|
||||
</h6>
|
||||
)
|
||||
}
|
||||
|
||||
<RichText
|
||||
content={node.fields.lines}
|
||||
content={node.fields.content}
|
||||
context={{ ...context, depth: context.depth + 1 }}
|
||||
/>
|
||||
|
||||
<style>
|
||||
span {
|
||||
color: var(--color-base-650);
|
||||
font-weight: 500;
|
||||
font-size: 70%;
|
||||
margin-right: 0.3em;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
import type { RichTextContext } from "src/utils/richText";
|
||||
import type { RichTextSpacerBlock } from "src/shared/payload/payload-sdk";
|
||||
import SpacerBlock from "components/Blocks/components/SpacerBlock.astro";
|
||||
|
||||
interface Props {
|
||||
node: RichTextSpacerBlock;
|
||||
context: RichTextContext;
|
||||
}
|
||||
|
||||
const { node } = Astro.props;
|
||||
---
|
||||
|
||||
<SpacerBlock block={node.fields} />
|
|
@ -1,52 +1,25 @@
|
|||
---
|
||||
import type { RichTextContext } from "src/utils/richText";
|
||||
import RTLine from "./RTLine.astro";
|
||||
import RTCue from "./RTCue.astro";
|
||||
import type { RichTextTranscriptBlock } from "src/shared/payload/payload-sdk";
|
||||
import Block from "components/Blocks/Block.astro";
|
||||
|
||||
interface Props {
|
||||
node: {
|
||||
type: string;
|
||||
version: number;
|
||||
format: number;
|
||||
text: string;
|
||||
[k: string]: unknown;
|
||||
fields: {
|
||||
id: string;
|
||||
blockName: string;
|
||||
blockType: string;
|
||||
lines: { blockType: string }[];
|
||||
};
|
||||
};
|
||||
node: RichTextTranscriptBlock;
|
||||
context: RichTextContext;
|
||||
}
|
||||
|
||||
const { node, context } = Astro.props;
|
||||
const { node } = Astro.props;
|
||||
---
|
||||
|
||||
<div>
|
||||
{
|
||||
node.fields.lines.map((block) => {
|
||||
switch (block.blockType) {
|
||||
case "lineBlock":
|
||||
return <RTLine block={block} context={context} />;
|
||||
|
||||
case "cueBlock":
|
||||
return <RTCue block={block} context={context} />;
|
||||
|
||||
default:
|
||||
return (
|
||||
<p>{`Unknown block type: ${block.blockType}. Please contact website technical administrator.`}</p>
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
{node.fields.lines.map((block) => <Block block={block} />)}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div {
|
||||
padding-block: 1em;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-columns: [name] auto [text] 1fr;
|
||||
gap: 1.5em 2em;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -3,24 +3,15 @@ import type { RichTextContext } from "src/utils/richText";
|
|||
import RTNode from "../RTNode.astro";
|
||||
import RTCustomLink from "./components/RTCustomLink.astro";
|
||||
import RTInternalLink from "./components/RTInternalLink.astro";
|
||||
import {
|
||||
isLinkNodeCustomLinkNode,
|
||||
isLinkNodeInternalLinkNode,
|
||||
type RichTextLinkNode,
|
||||
} from "src/shared/payload/payload-sdk";
|
||||
import ErrorMessage from "components/ErrorMessage.astro";
|
||||
|
||||
interface Props {
|
||||
node: {
|
||||
type: string;
|
||||
children: {
|
||||
type: string;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
version: number;
|
||||
fields: {
|
||||
linkType: "internal" | "custom";
|
||||
doc: any;
|
||||
url: string;
|
||||
newTab: boolean;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
};
|
||||
node: RichTextLinkNode;
|
||||
context: RichTextContext;
|
||||
}
|
||||
|
||||
|
@ -28,19 +19,22 @@ const { node, context } = Astro.props;
|
|||
---
|
||||
|
||||
{
|
||||
node.fields.linkType === "custom" ? (
|
||||
isLinkNodeCustomLinkNode(node) ? (
|
||||
<RTCustomLink href={node.fields.url} newTab={node.fields.newTab}>
|
||||
{node.children.map((node) => (
|
||||
<RTNode node={node} context={context} />
|
||||
))}
|
||||
</RTCustomLink>
|
||||
) : node.fields.linkType === "internal" ? (
|
||||
) : isLinkNodeInternalLinkNode(node) ? (
|
||||
<RTInternalLink doc={node.fields.doc}>
|
||||
{node.children.map((node) => (
|
||||
<RTNode node={node} context={context} />
|
||||
))}
|
||||
</RTInternalLink>
|
||||
) : (
|
||||
<p>{`Unknown link type: ${node.fields.linkType}. Please contact website technical administrator.`}</p>
|
||||
<ErrorMessage
|
||||
title={`Unknown link type: ${node.fields.linkType}`}
|
||||
description="Please contact website technical administrator."
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
---
|
||||
import ErrorMessage from "components/ErrorMessage.astro";
|
||||
import { getI18n } from "translations/translations";
|
||||
|
||||
interface Props {
|
||||
|
@ -18,6 +19,9 @@ const { getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
|||
<slot />
|
||||
</a>
|
||||
) : (
|
||||
<p>{`Unknown internal link: ${doc.relationTo}. Please contact website technical administrator.`}</p>
|
||||
<ErrorMessage
|
||||
title={`Unknown internal link: ${doc.relationTo}`}
|
||||
description="Please contact website technical administrator."
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,24 +2,16 @@
|
|||
import type { RichTextContext } from "src/utils/richText";
|
||||
import RTBasicListItem from "./components/RTBasicListItem.astro";
|
||||
import RTCheckListItem from "./components/RTCheckListItem.astro";
|
||||
import {
|
||||
isListNodeBulletListNode,
|
||||
isListNodeCheckListNode,
|
||||
isListNodeNumberListNode,
|
||||
type RichTextListNode,
|
||||
} from "src/shared/payload/payload-sdk";
|
||||
import ErrorMessage from "components/ErrorMessage.astro";
|
||||
|
||||
interface Props {
|
||||
node: {
|
||||
type: string;
|
||||
version: number;
|
||||
listType: string;
|
||||
children: {
|
||||
type: string;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
children: {
|
||||
type: string;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
}[];
|
||||
[k: string]: unknown;
|
||||
};
|
||||
node: RichTextListNode;
|
||||
context: RichTextContext;
|
||||
}
|
||||
|
||||
|
@ -27,27 +19,28 @@ const { node, context } = Astro.props;
|
|||
---
|
||||
|
||||
{
|
||||
node.listType === "number" ? (
|
||||
isListNodeNumberListNode(node) ? (
|
||||
<ol>
|
||||
{node.children.map((node) => (
|
||||
<RTBasicListItem node={node} context={context} />
|
||||
))}
|
||||
</ol>
|
||||
) : node.listType === "bullet" ? (
|
||||
) : isListNodeBulletListNode(node) ? (
|
||||
<ul>
|
||||
{node.children.map((node) => (
|
||||
<RTBasicListItem node={node} context={context} />
|
||||
))}
|
||||
</ul>
|
||||
) : node.listType === "check" ? (
|
||||
) : isListNodeCheckListNode(node) ? (
|
||||
<ul>
|
||||
{node.children.map((node) => (
|
||||
<RTCheckListItem node={node} context={context} />
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p>
|
||||
{`Unknown list type: ${node.listType}. Please contact website technical administrator.`}
|
||||
</p>
|
||||
<ErrorMessage
|
||||
title={`Unknown list link: ${node.listType}`}
|
||||
description="Please contact website technical administrator."
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -5,46 +5,47 @@ import RTText from "./RTText/RTText.astro";
|
|||
import RTLink from "./RTLink/RTLink.astro";
|
||||
import RTBlock from "./RTBlock/RTBlock.astro";
|
||||
import type { RichTextContext } from "src/utils/richText";
|
||||
import {
|
||||
isNodeBlockNode,
|
||||
isNodeLinkNode,
|
||||
isNodeListNode,
|
||||
isNodeParagraphNode,
|
||||
isNodeTabNode,
|
||||
isNodeTextNode,
|
||||
type RichTextNode,
|
||||
} from "src/shared/payload/payload-sdk";
|
||||
import RTTab from "./RTTab.astro";
|
||||
import ErrorMessage from "components/ErrorMessage.astro";
|
||||
|
||||
interface Props {
|
||||
node: {
|
||||
type: string;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
};
|
||||
node: RichTextNode;
|
||||
context: RichTextContext;
|
||||
}
|
||||
|
||||
const { node, context } = Astro.props;
|
||||
|
||||
let NodeElement;
|
||||
switch (node.type) {
|
||||
case "paragraph":
|
||||
NodeElement = RTParagraph;
|
||||
break;
|
||||
|
||||
case "list":
|
||||
NodeElement = RTList;
|
||||
break;
|
||||
|
||||
case "text":
|
||||
NodeElement = RTText;
|
||||
break;
|
||||
|
||||
case "link":
|
||||
NodeElement = RTLink;
|
||||
break;
|
||||
|
||||
case "block":
|
||||
NodeElement = RTBlock;
|
||||
break;
|
||||
}
|
||||
---
|
||||
|
||||
{
|
||||
NodeElement ? (
|
||||
<NodeElement node={node} context={context} />
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
{
|
||||
isNodeParagraphNode(node) ? (
|
||||
<RTParagraph node={node} context={context} />
|
||||
) : isNodeListNode(node) ? (
|
||||
<RTList node={node} context={context} />
|
||||
) : isNodeTextNode(node) ? (
|
||||
<RTText node={node} context={context} />
|
||||
) : isNodeLinkNode(node) ? (
|
||||
<RTLink node={node} context={context} />
|
||||
) : isNodeBlockNode(node) ? (
|
||||
<RTBlock node={node} context={context} />
|
||||
) : isNodeTabNode(node) ? (
|
||||
<RTTab />
|
||||
) : (
|
||||
<p>{`Unknown node type: ${node.type}. Please contact website technical administrator.`}</p>
|
||||
<ErrorMessage
|
||||
title={`Unknown node type: ${node.type}`}
|
||||
description="Please contact website technical administrator."
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,18 +1,10 @@
|
|||
---
|
||||
import type { RichTextContext } from "src/utils/richText";
|
||||
import RTNode from "./RTNode.astro";
|
||||
import type { RichTextParagraphNode } from "src/shared/payload/payload-sdk";
|
||||
|
||||
interface Props {
|
||||
node: {
|
||||
type: string;
|
||||
version: number;
|
||||
children: {
|
||||
type: string;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
[k: string]: unknown;
|
||||
};
|
||||
node: RichTextParagraphNode;
|
||||
context: RichTextContext;
|
||||
}
|
||||
|
||||
|
@ -23,9 +15,12 @@ const { node, context } = Astro.props;
|
|||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<p>{node.children.map((node) => <RTNode node={node} context={context} />)}</p>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||
node.children.length > 0 && (
|
||||
<p style={`text-align: ${node.format};`}>
|
||||
{node.children.map((node) => (
|
||||
<RTNode node={node} context={context} />
|
||||
))}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
---
|
||||
|
||||
{" "}
|
|
@ -7,15 +7,12 @@ import RTLineThrough from "./components/RTLineThrough.astro";
|
|||
import RTSubscript from "./components/RTSubscript.astro";
|
||||
import RTSuperscript from "./components/RTSuperscript.astro";
|
||||
import RTInlineCode from "./components/RTInlineCode.astro";
|
||||
import type { RichTextContext } from "src/utils/richText";
|
||||
import type { RichTextTextNode } from "src/shared/payload/payload-sdk";
|
||||
|
||||
interface Props {
|
||||
node: {
|
||||
type: string;
|
||||
version: number;
|
||||
format: number;
|
||||
text: string;
|
||||
[k: string]: unknown;
|
||||
};
|
||||
node: RichTextTextNode;
|
||||
context: RichTextContext;
|
||||
}
|
||||
|
||||
const { node } = Astro.props;
|
||||
|
@ -23,8 +20,14 @@ const { node } = Astro.props;
|
|||
|
||||
<ConditionalWrapper wrapper={RTBold} condition={Boolean(node.format & 1)}>
|
||||
<ConditionalWrapper wrapper={RTItalic} condition={Boolean(node.format & 2)}>
|
||||
<ConditionalWrapper wrapper={RTLineThrough} condition={Boolean(node.format & 4)}>
|
||||
<ConditionalWrapper wrapper={RTUnderline} condition={Boolean(node.format & 8)}>
|
||||
<ConditionalWrapper
|
||||
wrapper={RTLineThrough}
|
||||
condition={Boolean(node.format & 4)}
|
||||
>
|
||||
<ConditionalWrapper
|
||||
wrapper={RTUnderline}
|
||||
condition={Boolean(node.format & 8)}
|
||||
>
|
||||
<ConditionalWrapper
|
||||
wrapper={RTInlineCode}
|
||||
condition={Boolean(node.format & 16)}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
import type { TableOfContentEntry } from "src/shared/payload/payload-sdk";
|
||||
import TableOfContentItem from "./components/TableOfContentItem.astro";
|
||||
import { Icon } from "astro-icon/components";
|
||||
|
||||
interface Props {
|
||||
toc: TableOfContentEntry[];
|
||||
}
|
||||
|
||||
const { toc } = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<div id="container">
|
||||
<div id="title">
|
||||
<Icon name="material-symbols:list-alt-outline" width={24} height={24} />
|
||||
<p>Table of Content</p>
|
||||
</div>
|
||||
<ol>
|
||||
{toc.map((entry) => <TableOfContentItem entry={entry} />)}
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||
}
|
||||
|
||||
<style>
|
||||
#container {
|
||||
& > #title {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 0.75em;
|
||||
|
||||
& > p {
|
||||
font-size: 1.5em;
|
||||
font-weight: 600;
|
||||
translate: 0px -0.1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
import type { TableOfContentEntry } from "src/shared/payload/payload-sdk";
|
||||
|
||||
interface Props {
|
||||
entry: TableOfContentEntry;
|
||||
}
|
||||
|
||||
const { entry } = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<li data-prefix={entry.prefix}>
|
||||
<a href={`#${entry.prefix}`}>{entry.title}</a>
|
||||
{
|
||||
entry.children.length > 0 && (
|
||||
<ol>
|
||||
{entry.children.map((entry) => (
|
||||
<Astro.self entry={entry} />
|
||||
))}
|
||||
</ol>
|
||||
)
|
||||
}
|
||||
</li>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||
}
|
||||
|
||||
<style>
|
||||
a {
|
||||
font-weight: 500;
|
||||
text-decoration: underline dotted 0.1em;
|
||||
text-decoration-color: transparent;
|
||||
|
||||
transition-duration: 150ms;
|
||||
transition-property: text-decoration-color, color;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-base-750);
|
||||
text-decoration-color: var(--color-base-650);
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: var(--color-base-650);
|
||||
text-decoration-color: var(--color-base-550);
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
margin-block: 0.5em;
|
||||
}
|
||||
|
||||
li {
|
||||
&::marker {
|
||||
content: attr(data-prefix) " ";
|
||||
color: var(--color-base-650);
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
line-height: 125%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
import Metadata from "components/Metadata.astro";
|
||||
import { getI18n } from "translations/translations";
|
||||
|
||||
interface Props {
|
||||
slug: string;
|
||||
icon: string;
|
||||
values: string[];
|
||||
}
|
||||
|
||||
const { icon, slug, values } = Astro.props;
|
||||
const { formatTag, formatTagsGroup } = await getI18n(
|
||||
Astro.locals.currentLocale
|
||||
);
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<Metadata
|
||||
icon={icon}
|
||||
title={formatTagsGroup(slug)}
|
||||
values={values.map(formatTag)}
|
||||
/>
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
import TagGroup from "./TagGroup.astro";
|
||||
|
||||
interface Props {
|
||||
tagGroups: { slug: string; icon: string; values: string[] }[];
|
||||
}
|
||||
|
||||
const { tagGroups } = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<div>{tagGroups.map((tag) => <TagGroup {...tag} />)}</div>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||
}
|
||||
|
||||
<style>
|
||||
div {
|
||||
@media (max-width: 35rem) {
|
||||
margin-block: 5em;
|
||||
gap: 2em;
|
||||
}
|
||||
|
||||
margin-block: 2em;
|
||||
display: grid;
|
||||
gap: 1em;
|
||||
}
|
||||
</style>
|
|
@ -38,7 +38,8 @@ const localeNegotiator = defineMiddleware(({ cookies, url, request }, next) => {
|
|||
const currentLocale = getCurrentLocale(url.pathname);
|
||||
const acceptedLocale = getBestAcceptedLanguage(request);
|
||||
const cookieLocale = getCookieLocale(cookies);
|
||||
const bestMatchingLocale = cookieLocale ?? acceptedLocale ?? defaultLocale;
|
||||
const bestMatchingLocale =
|
||||
cookieLocale ?? acceptedLocale ?? currentLocale ?? defaultLocale;
|
||||
|
||||
if (!currentLocale) {
|
||||
const redirectURL = getAbsoluteLocaleUrl(bestMatchingLocale, url.pathname);
|
||||
|
|
|
@ -8,6 +8,10 @@ interface Props {
|
|||
const { img, name, href } = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<a href={href} aria-label={name} class="pressable">
|
||||
{
|
||||
img ? (
|
||||
|
@ -16,11 +20,17 @@ const { img, name, href } = Astro.props;
|
|||
<img src={img.dark} class="when-dark-theme" alt={name} title={name} />
|
||||
</>
|
||||
) : (
|
||||
<div><p>{name}</p></div>
|
||||
<div>
|
||||
<p>{name}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</a>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||
}
|
||||
|
||||
<style>
|
||||
a {
|
||||
font-size: 24px;
|
||||
|
@ -46,6 +56,5 @@ const { img, name, href } = Astro.props;
|
|||
display: grid;
|
||||
place-content: center;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
|
@ -9,12 +9,20 @@ interface Props {
|
|||
const { pretitle, subtitle, title, href } = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<a href={href} class="pressable">
|
||||
<p class="pretitle">{pretitle}</p>
|
||||
<h3>{title}</h3>
|
||||
<p>{subtitle}</p>
|
||||
</a>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||
}
|
||||
|
||||
<style>
|
||||
a {
|
||||
display: flex;
|
|
@ -3,12 +3,16 @@ import { payload } from "src/shared/payload/payload-sdk";
|
|||
import { getI18n } from "translations/translations";
|
||||
import CategoryCard from "./CategoryCard.astro";
|
||||
|
||||
const folders = await payload.getRootFolders()
|
||||
const folders = await payload.getRootFolders();
|
||||
const { getLocalizedUrl, getLocalizedMatch } = await getI18n(
|
||||
Astro.locals.currentLocale
|
||||
);
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
{
|
||||
folders.map(({ slug, translations, darkThumbnail, lightThumbnail }) => (
|
||||
<CategoryCard
|
|
@ -10,6 +10,10 @@ interface Props {
|
|||
const { icon, subtitle, title, href } = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<a href={href} class="pressable">
|
||||
<Icon name={icon} />
|
||||
<div id="right">
|
||||
|
@ -18,6 +22,10 @@ const { icon, subtitle, title, href } = Astro.props;
|
|||
</div>
|
||||
</a>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||
}
|
||||
|
||||
<style>
|
||||
a {
|
||||
display: flex;
|
|
@ -1,62 +0,0 @@
|
|||
---
|
||||
import Button from "components/Button.astro";
|
||||
import Tooltip from "components/Tooltip.astro";
|
||||
import MasoActor from "components/Maso/MasoActor.astro";
|
||||
import MasoTarget from "components/Maso/MasoTarget.astro";
|
||||
import { getI18n } from "translations/translations";
|
||||
|
||||
export const partial = true;
|
||||
|
||||
interface Props {
|
||||
lang?: string;
|
||||
}
|
||||
|
||||
const reqUrl = new URL(Astro.request.url);
|
||||
const lang = Astro.props.lang ?? reqUrl.searchParams.get("lang")!;
|
||||
|
||||
const { t } = await getI18n(lang);
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<MasoTarget>
|
||||
<Tooltip trigger="click" class="when-js">
|
||||
<Button
|
||||
icon="material-symbols:translate"
|
||||
title={lang.toUpperCase()}
|
||||
ariaLabel={t("header.topbar.language.tooltip")}
|
||||
/>
|
||||
|
||||
<div id="content" slot="tooltip-content">
|
||||
{
|
||||
["en", "fr"].map((locale) => (
|
||||
<MasoActor
|
||||
class:list={{ current: locale === lang }}
|
||||
href={`/api/content?lang=${locale}`}
|
||||
>
|
||||
{locale.toString().toUpperCase()}
|
||||
</MasoActor>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div set:html={t("home.description")} />
|
||||
</MasoTarget>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||
}
|
||||
|
||||
<style>
|
||||
#content {
|
||||
display: grid;
|
||||
gap: 0.5em;
|
||||
|
||||
& > :global(.current) {
|
||||
color: var(--color-base-750);
|
||||
text-decoration: underline 0.08em var(--color-base-650);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,94 +0,0 @@
|
|||
---
|
||||
import MasoActor from "components/Maso/MasoActor.astro";
|
||||
import Tooltip from "components/Tooltip.astro";
|
||||
import Button from "components/Button.astro";
|
||||
import { getI18n } from "translations/translations";
|
||||
import Metadata from "pages/[locale]/api/contents/_components/Metadata.astro";
|
||||
|
||||
interface Props {
|
||||
currentLang: string;
|
||||
getPartialUrl: (locale: string) => string;
|
||||
availableLanguages: string[];
|
||||
translators?: string[] | undefined;
|
||||
transcribers?: string[] | undefined;
|
||||
proofreaders?: string[] | undefined;
|
||||
}
|
||||
|
||||
const {
|
||||
currentLang,
|
||||
getPartialUrl,
|
||||
availableLanguages,
|
||||
translators = [],
|
||||
transcribers = [],
|
||||
proofreaders = [],
|
||||
} = Astro.props;
|
||||
|
||||
const { formatLocale, formatRecorder } = await getI18n(
|
||||
Astro.locals.currentLocale
|
||||
);
|
||||
---
|
||||
|
||||
{
|
||||
availableLanguages.length > 1 && (
|
||||
<div id="lang-selector" class="when-js when-no-print">
|
||||
<Tooltip trigger="click">
|
||||
<Button
|
||||
icon="material-symbols:translate"
|
||||
title={currentLang.toUpperCase()}
|
||||
/>
|
||||
|
||||
<div id="tooltip-content" slot="tooltip-content">
|
||||
{availableLanguages.map((id) => (
|
||||
<MasoActor
|
||||
class:list={{ current: id === currentLang }}
|
||||
href={getPartialUrl(id)}
|
||||
>
|
||||
{formatLocale(id)}
|
||||
</MasoActor>
|
||||
))}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<p class="high-contrast-text">This content is available is {availableLanguages.length} languages.</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<Metadata
|
||||
icon="material-symbols:person-outline"
|
||||
title="Translators"
|
||||
values={translators.map((id) => formatRecorder(id))}
|
||||
/>
|
||||
|
||||
<Metadata
|
||||
icon="material-symbols:person-edit-outline"
|
||||
title="Transcribers"
|
||||
values={transcribers.map((id) => formatRecorder(id))}
|
||||
/>
|
||||
|
||||
<Metadata
|
||||
icon="material-symbols:person-check-outline"
|
||||
title="Proofreaders"
|
||||
values={proofreaders.map((id) => formatRecorder(id))}
|
||||
/>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||
}
|
||||
|
||||
<style>
|
||||
#lang-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1em;
|
||||
|
||||
#tooltip-content {
|
||||
display: grid;
|
||||
gap: 0.5em;
|
||||
|
||||
& > .current {
|
||||
color: var(--color-base-750);
|
||||
text-decoration: underline 0.08em var(--color-base-650);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,42 +1,38 @@
|
|||
---
|
||||
import RichText from "components/RichText/RichText.astro";
|
||||
import { payload } from "src/shared/payload/payload-sdk";
|
||||
import { payload, type EndpointPage } from "src/shared/payload/payload-sdk";
|
||||
import { getI18n } from "translations/translations";
|
||||
import AppLayoutTitle from "components/AppLayout/components/AppLayoutTitle.astro";
|
||||
import Metadata from "pages/[locale]/api/contents/_components/Metadata.astro";
|
||||
import MasoTarget from "components/Maso/MasoTarget.astro";
|
||||
|
||||
import AppLayoutBackgroundImg from "components/AppLayout/components/AppLayoutBackgroundImg.astro";
|
||||
import LangCredits from "./_components/LangCredits.astro";
|
||||
import TagGroups from "components/TagGroups.astro";
|
||||
import TableOfContent from "components/TableOfContent/TableOfContent.astro";
|
||||
import LanguageOverride from "components/LanguageOverride.astro";
|
||||
import Credits from "components/Credits.astro";
|
||||
|
||||
export const partial = true;
|
||||
|
||||
interface Props {
|
||||
lang?: string;
|
||||
slug?: string;
|
||||
page?: EndpointPage;
|
||||
}
|
||||
|
||||
const reqUrl = new URL(Astro.request.url);
|
||||
const lang = Astro.props.lang ?? reqUrl.searchParams.get("lang")!;
|
||||
const slug = Astro.props.slug ?? reqUrl.searchParams.get("slug")!;
|
||||
const page = Astro.props.page ?? (await payload.getPage(slug));
|
||||
|
||||
const { getLocalizedUrl, formatCategory, formatContentType } = await getI18n(
|
||||
Astro.locals.currentLocale
|
||||
);
|
||||
|
||||
const { getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||
const { getLocalizedMatch } = await getI18n(lang);
|
||||
|
||||
const content = await payload.getContent(slug);
|
||||
const translation = getLocalizedMatch(content.translations, {
|
||||
title: slug,
|
||||
format: {},
|
||||
sourceLanguage: "",
|
||||
});
|
||||
const translation = getLocalizedMatch(page.translations, { title: slug });
|
||||
---
|
||||
|
||||
<MasoTarget>
|
||||
{content.thumbnail && <AppLayoutBackgroundImg src={content.thumbnail.url} />}
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<MasoTarget>
|
||||
<div id="layout">
|
||||
<div id="left">
|
||||
<AppLayoutTitle
|
||||
|
@ -46,13 +42,13 @@ const translation = getLocalizedMatch(content.translations, {
|
|||
/>
|
||||
|
||||
{
|
||||
content.thumbnail && (
|
||||
page.thumbnail && (
|
||||
<img
|
||||
id="thumbnail"
|
||||
class="when-not-large"
|
||||
src={content.thumbnail.url}
|
||||
width={content.thumbnail.width}
|
||||
height={content.thumbnail.height}
|
||||
src={page.thumbnail.url}
|
||||
width={page.thumbnail.width}
|
||||
height={page.thumbnail.height}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -65,79 +61,71 @@ const translation = getLocalizedMatch(content.translations, {
|
|||
)
|
||||
}
|
||||
|
||||
{
|
||||
(content.type || content.categories.length > 0) && (
|
||||
<div class="meta-container">
|
||||
{content.type && (
|
||||
<Metadata
|
||||
icon="material-symbols:shape-line-outline"
|
||||
title="Type"
|
||||
values={[formatContentType(content.type)]}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Metadata
|
||||
icon="material-symbols:workspaces-outline"
|
||||
title="Categories"
|
||||
values={content.categories.map((id) =>
|
||||
formatCategory(id, "default")
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<TagGroups tagGroups={page.tagGroups} />
|
||||
|
||||
<div class="when-not-large meta-container">
|
||||
<LangCredits
|
||||
currentLang={lang}
|
||||
availableLanguages={content.translations.map(
|
||||
({ language }) => language
|
||||
)}
|
||||
getPartialUrl={(lang) =>
|
||||
getLocalizedUrl(`/api/contents/partial?lang=${lang}&slug=${slug}`)}
|
||||
translators={translation.format.text?.translators}
|
||||
transcribers={translation.format.text?.transcribers}
|
||||
proofreaders={translation.format.text?.proofreaders}
|
||||
{
|
||||
page.translations.length > 1 && (
|
||||
<LanguageOverride
|
||||
currentLang={lang}
|
||||
availableLanguages={page.translations.map(
|
||||
({ language }) => language
|
||||
)}
|
||||
getPartialUrl={(lang) =>
|
||||
getLocalizedUrl(`/api/pages/partial?lang=${lang}&slug=${slug}`)
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<Credits
|
||||
translators={translation.translators}
|
||||
proofreaders={translation.proofreaders}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{
|
||||
translation.format.text && (
|
||||
<>
|
||||
<hr />
|
||||
<div id="text">
|
||||
<RichText content={translation.format.text.content} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
<div class="when-not-large meta-container">
|
||||
<TableOfContent toc={translation.toc} />
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<div id="text">
|
||||
<RichText content={translation.content} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="right" class="when-large">
|
||||
{
|
||||
content.thumbnail && (
|
||||
page.thumbnail && (
|
||||
<img
|
||||
id="thumbnail"
|
||||
src={content.thumbnail.url}
|
||||
width={content.thumbnail.width}
|
||||
height={content.thumbnail.height}
|
||||
src={page.thumbnail.url}
|
||||
width={page.thumbnail.width}
|
||||
height={page.thumbnail.height}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
<div class="meta-container">
|
||||
<LangCredits
|
||||
currentLang={lang}
|
||||
availableLanguages={content.translations.map(
|
||||
({ language }) => language
|
||||
)}
|
||||
getPartialUrl={(lang) =>
|
||||
getLocalizedUrl(`/api/contents/partial?lang=${lang}&slug=${slug}`)}
|
||||
translators={translation.format.text?.translators}
|
||||
transcribers={translation.format.text?.transcribers}
|
||||
proofreaders={translation.format.text?.proofreaders}
|
||||
{
|
||||
page.translations.length > 1 && (
|
||||
<LanguageOverride
|
||||
currentLang={lang}
|
||||
availableLanguages={page.translations.map(
|
||||
({ language }) => language
|
||||
)}
|
||||
getPartialUrl={(lang) =>
|
||||
getLocalizedUrl(`/api/pages/partial?lang=${lang}&slug=${slug}`)
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<Credits
|
||||
translators={translation.translators}
|
||||
proofreaders={translation.proofreaders}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TableOfContent toc={translation.toc} />
|
||||
</div>
|
||||
</div>
|
||||
</MasoTarget>
|
|
@ -1,14 +0,0 @@
|
|||
---
|
||||
import AppEmptyLayout from "components/AppLayout/AppEmptyLayout.astro";
|
||||
import Content from "src/pages/[locale]/api/contents/partial.astro";
|
||||
|
||||
const { slug } = Astro.params;
|
||||
|
||||
if (!slug) {
|
||||
return Astro.redirect("/en/404");
|
||||
}
|
||||
---
|
||||
|
||||
<AppEmptyLayout>
|
||||
<Content slug={slug} lang={Astro.locals.currentLocale} />
|
||||
</AppLayout>
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
import AppLayout from "components/AppLayout/AppLayout.astro";
|
||||
---
|
||||
|
||||
<AppLayout pretitle="Guide to" title="Rich Text Editor" description="Having troubles using the Rich Text Editor? Looking for tips and advanced techniques? You've come to the right place!">
|
||||
<div class="prose">
|
||||
<h2>Add indentation / spaces between words</h2>
|
||||
|
||||
<p>
|
||||
By default, additional spaces are collapsed. This means that if multiple
|
||||
spaces are adjacent, only one space is preserved. To create spaces that
|
||||
will not be collapsed, you can use tabs instead. Simply press the <kbd
|
||||
>Tab</kbd
|
||||
> key a few times on your keyboard to create additional spaces between words.
|
||||
Be mindful of the use of these spaces.
|
||||
</p>
|
||||
</div>
|
||||
</AppLayout>
|
|
@ -4,21 +4,20 @@ 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";
|
||||
import { fetchOr404 } from "src/utils/responses";
|
||||
import ErrorMessage from "components/ErrorMessage.astro";
|
||||
|
||||
const { slug } = Astro.params;
|
||||
const { getLocalizedMatch, getLocalizedUrl } = await getI18n(
|
||||
Astro.locals.currentLocale
|
||||
);
|
||||
|
||||
if (!slug) {
|
||||
return Astro.redirect("/en/404");
|
||||
const folder = await fetchOr404(() => payload.getFolder(slug!));
|
||||
if (folder instanceof Response) {
|
||||
return folder;
|
||||
}
|
||||
|
||||
const folder = await payload.getFolder(slug);
|
||||
const meta = getLocalizedMatch(folder.translations, { name: slug });
|
||||
|
||||
// TODO: handle folder not found
|
||||
// TODO: send description as RichTextContent instead of string
|
||||
// TODO: handle light and dark illustration for applayout
|
||||
---
|
||||
|
||||
|
@ -30,7 +29,7 @@ const meta = getLocalizedMatch(folder.translations, { name: slug });
|
|||
{
|
||||
meta.description && (
|
||||
<div slot="header-description">
|
||||
<RichText content={JSON.parse(meta.description)} />
|
||||
<RichText content={meta.description} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -58,21 +57,38 @@ const meta = getLocalizedMatch(folder.translations, { name: slug });
|
|||
<div>
|
||||
{
|
||||
folder.files.map(({ relationTo, value }) => {
|
||||
if (relationTo === "contents") {
|
||||
return (
|
||||
<a
|
||||
class="pressable"
|
||||
href={getLocalizedUrl(`/contents/${value.slug}`)}
|
||||
>
|
||||
{value.slug}
|
||||
</a>
|
||||
);
|
||||
switch (relationTo) {
|
||||
case "contents":
|
||||
return (
|
||||
<a
|
||||
class="pressable"
|
||||
href={getLocalizedUrl(`/contents/${value.slug}`)}
|
||||
>
|
||||
{value.slug}
|
||||
</a>
|
||||
);
|
||||
|
||||
case "library-items":
|
||||
return <p>Library item not supported yet! {value.slug}</p>;
|
||||
|
||||
case "pages":
|
||||
return (
|
||||
<a
|
||||
class="pressable"
|
||||
href={getLocalizedUrl(`/pages/${value.slug}`)}
|
||||
>
|
||||
{value.slug}
|
||||
</a>
|
||||
);
|
||||
|
||||
default:
|
||||
return (
|
||||
<ErrorMessage
|
||||
title={`Unknown file type: ${relationTo}`}
|
||||
description="Please contact website technical administrator."
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<a href={getLocalizedUrl(`/library-item/${value.slug}`)}>
|
||||
{value.slug}
|
||||
</a>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
|
|
@ -9,6 +9,10 @@ interface Props {
|
|||
const { icon = "material-symbols:folder-outline", title, href } = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<a href={href} class="pressable">
|
||||
<Icon name={icon} />
|
||||
<div id="right">
|
||||
|
@ -16,6 +20,10 @@ const { icon = "material-symbols:folder-outline", title, href } = Astro.props;
|
|||
</div>
|
||||
</a>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||
}
|
||||
|
||||
<style>
|
||||
a {
|
||||
display: flex;
|
||||
|
|
|
@ -15,6 +15,10 @@ const { getLocalizedUrl, getLocalizedMatch } = await getI18n(
|
|||
);
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<div>
|
||||
{title && <h3>{title}</h3>}
|
||||
<section>
|
||||
|
|
|
@ -2,11 +2,10 @@
|
|||
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 { getI18n } from "../../../translations/translations";
|
||||
|
||||
import ChronicleCard from "pages/_components/ChronicleCard.astro";
|
||||
import LibraryGrid from "pages/_components/LibraryGrid.astro";
|
||||
import LibraryGrid from "./_components/LibraryGrid.astro";
|
||||
import ChronicleCard from "./_components/ChronicleCard.astro";
|
||||
import LinkCard from "./_components/LinkCard.astro";
|
||||
|
||||
const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||
---
|
||||
|
@ -46,7 +45,7 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
|||
<Button
|
||||
class="section-button"
|
||||
title={t("home.librarySection.button")}
|
||||
icon="material-symbols:browse-outline"
|
||||
icon="material-symbols:browse"
|
||||
/>
|
||||
</a>
|
||||
<div class="grid">
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
import AppEmptyLayout from "components/AppLayout/AppEmptyLayout.astro";
|
||||
import Page from "src/pages/[locale]/api/pages/partial.astro";
|
||||
import { payload } from "src/shared/payload/payload-sdk";
|
||||
import { fetchOr404 } from "src/utils/responses";
|
||||
|
||||
const { slug } = Astro.params;
|
||||
|
||||
const page = await fetchOr404(() => payload.getPage(slug!));
|
||||
if (page instanceof Response) {
|
||||
return page;
|
||||
}
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<AppEmptyLayout
|
||||
parentPages={page.parentPages}
|
||||
backgroundIllustration={page.thumbnail?.url}
|
||||
>
|
||||
<Page slug={page.slug} lang={Astro.locals.currentLocale} page={page} />
|
||||
</AppEmptyLayout>
|
|
@ -7,6 +7,10 @@ const { currentLocale, currentTheme, currentCurrency } = Astro.locals;
|
|||
const { t } = await getI18n(currentLocale);
|
||||
---
|
||||
|
||||
{
|
||||
/* ------------------------------------------- HTML ------------------------------------------- */
|
||||
}
|
||||
|
||||
<AppLayout title={t("settings.title")}>
|
||||
<div id="main">
|
||||
<div class="section">
|
||||
|
@ -69,6 +73,10 @@ const { t } = await getI18n(currentLocale);
|
|||
</div>
|
||||
</AppLayout>
|
||||
|
||||
{
|
||||
/* ------------------------------------------- CSS -------------------------------------------- */
|
||||
}
|
||||
|
||||
<style>
|
||||
.section {
|
||||
display: flex;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -3,6 +3,9 @@ import {
|
|||
type EndpointKey,
|
||||
type EndpointRecorder,
|
||||
type Language,
|
||||
type EndpointTag,
|
||||
type EndpointTagsGroup,
|
||||
type EndpointWording,
|
||||
} from "src/shared/payload/payload-sdk";
|
||||
|
||||
type Cache = {
|
||||
|
@ -10,13 +13,19 @@ type Cache = {
|
|||
currencies: string[];
|
||||
keys: EndpointKey[];
|
||||
recorders: EndpointRecorder[];
|
||||
tags: EndpointTag[];
|
||||
tagsGroups: EndpointTagsGroup[];
|
||||
wordings: EndpointWording[];
|
||||
};
|
||||
|
||||
const fetchNewData = async (): Promise<Cache> => ({
|
||||
locales: (await payload.getLanguages()),
|
||||
locales: await payload.getLanguages(),
|
||||
currencies: (await payload.getCurrencies()).map(({ id }) => id),
|
||||
keys: await payload.getKeys(),
|
||||
recorders: await payload.getRecorders(),
|
||||
tags: await payload.getTags(),
|
||||
tagsGroups: await payload.getTagsGroups(),
|
||||
wordings: await payload.getWordings(),
|
||||
});
|
||||
|
||||
export let cache = await fetchNewData();
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
export const fetchOr404 = async <T>(
|
||||
promise: () => Promise<T>
|
||||
): Promise<T | Response> => {
|
||||
try {
|
||||
return await promise();
|
||||
} catch {
|
||||
return new Response(null, {
|
||||
status: 404,
|
||||
statusText: "Not found",
|
||||
});
|
||||
}
|
||||
};
|
|
@ -1,30 +0,0 @@
|
|||
import { expect, test } from "bun:test";
|
||||
|
||||
const cases: [string, string, string[], string][] = [
|
||||
["", "", [], "/en/"],
|
||||
["", "", ["fr"], "/fr/"],
|
||||
["", "", ["en"], "/en/"],
|
||||
["", "", ["en", "fr"], "/en/"],
|
||||
["", "en", [], "/en/"],
|
||||
["", "fr", [], "/fr/"],
|
||||
["", "fr", ["en"], "/en/"],
|
||||
["", "fr", ["en", "fr"], "/en/"],
|
||||
["", "fr,en", ["en", "fr"], "/en/"],
|
||||
];
|
||||
|
||||
test.each(cases)(
|
||||
"Fetching url with prefix %p, with Accept-Language header %p, with cookie al_pref_languages %p, should redirect the user to %p",
|
||||
async (urlPrefix, acceptLanguage, cookie, expectedRedirection) => {
|
||||
const response = await fetch(`http://localhost:12498${urlPrefix}`, {
|
||||
redirect: "manual",
|
||||
headers: {
|
||||
...(acceptLanguage ? { "Accept-Language": acceptLanguage } : {}),
|
||||
...(cookie.length > 0
|
||||
? { Cookie: `al_pref_languages=${JSON.stringify(cookie)}` }
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
expect(response.status).toBe(302);
|
||||
expect(response.headers.get("Location")).toBe(expectedRedirection);
|
||||
}
|
||||
);
|
|
@ -5,7 +5,7 @@
|
|||
"home.description": "We aim at archiving and translating all of <strong>Yoko Taro</strong>’s works.<br />Yoko Taro is a Japanese video game director and scenario writer. He is best-known for his involvement with the <strong>NieR</strong > and <strong>Drakengard</strong> series. To complement his games, Yoko Taro likes to publish side materials in the form of books, anime, manga, audio books, novellas, even theater plays.<br />These media can be very difficult to find. His work goes all the way back to 2003. Most of it was released solely in Japanese, and sometimes in short supply. So this is what we do here: <strong>discover, archive, translate, and analyze</strong>.",
|
||||
"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.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. Alternatively you can also browse all content and use tags and filters to narrow your search. <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>",
|
||||
|
@ -55,5 +55,7 @@
|
|||
"footer.license.description": "This website’s content is made available under <a href=\"https://creativecommons.org/licenses/by-sa/4.0/\">CC-BY-SA</a> unless otherwise noted.",
|
||||
"footer.license.icons.tooltip": "CC-BY-SA 4.0 License",
|
||||
|
||||
"footer.disclaimer": "<strong>Accord’s Library</strong> is not affiliated with or endorsed by <strong>SQUARE ENIX CO. LTD</strong>. All game assets and promotional materials belongs to <strong>© SQUARE ENIX CO. LTD</strong>."
|
||||
"footer.disclaimer": "<strong>Accord’s Library</strong> is not affiliated with or endorsed by <strong>SQUARE ENIX CO. LTD</strong>. All game assets and promotional materials belongs to <strong>© SQUARE ENIX CO. LTD</strong>.",
|
||||
|
||||
"header.nav.parentPages.label": "{{ count }} parent page{{ count+,>1{s} }}"
|
||||
}
|
||||
|
|
|
@ -115,7 +115,8 @@ export const getI18n = async (locale: string) => {
|
|||
fallback: Omit<T, "language">
|
||||
): Omit<T, "language"> & { language?: string } =>
|
||||
options.find(({ language }) => language === locale) ??
|
||||
options.find(({ language }) => language === defaultLocale) ?? {
|
||||
options.find(({ language }) => language === defaultLocale) ??
|
||||
options[0] ?? {
|
||||
...fallback,
|
||||
};
|
||||
|
||||
|
@ -152,6 +153,16 @@ export const getI18n = async (locale: string) => {
|
|||
},
|
||||
getLocalizedUrl: (url: string): string => `/${locale}${url}`,
|
||||
getLocalizedMatch,
|
||||
formatTag: (id: string): string => {
|
||||
const tag = cache.tags.find(({ slug }) => slug === id);
|
||||
if (!tag) return "UNKNOWN";
|
||||
return getLocalizedMatch(tag.translations, { name: tag.slug }).name;
|
||||
},
|
||||
formatTagsGroup: (id: string): string => {
|
||||
const tag = cache.tagsGroups.find(({ slug }) => slug === id);
|
||||
if (!tag) return "UNKNOWN";
|
||||
return getLocalizedMatch(tag.translations, { name: tag.slug }).name;
|
||||
},
|
||||
formatCategory: (
|
||||
id: string,
|
||||
format: "short" | "default" = "default"
|
||||
|
@ -206,11 +217,12 @@ export const getCurrentLocale = (pathname: string): Locale | undefined => {
|
|||
export const getBestAcceptedLanguage = (
|
||||
request: Request
|
||||
): Locale | undefined => {
|
||||
const header = request.headers.get("Accept-Language");
|
||||
if (!header) return;
|
||||
|
||||
acceptLanguage.languages(cache.locales.map(({ id }) => id));
|
||||
|
||||
return (
|
||||
(acceptLanguage.get(
|
||||
request.headers.get("Accept-Language")
|
||||
) as Locale | null) ?? undefined
|
||||
acceptLanguage.get(request.headers.get("Accept-Language")) ?? undefined
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue