Added relations pages to collectibles and pages
This commit is contained in:
parent
1a6e0b315b
commit
403f7e087d
6
TODO.md
6
TODO.md
|
@ -6,10 +6,14 @@
|
||||||
- [Bugs] Keziah reported some lag spikes when scrolling on the home page (Firefox on Windows)
|
- [Bugs] Keziah reported some lag spikes when scrolling on the home page (Firefox on Windows)
|
||||||
- [Feat] [Analytics] Add analytics
|
- [Feat] [Analytics] Add analytics
|
||||||
- [Bugs] [Tooltips] Tooltip in under next element (example in timeline)
|
- [Bugs] [Tooltips] Tooltip in under next element (example in timeline)
|
||||||
- [Bugs] [Language override] Maso actor is not focusable with keyboard nav
|
- [Bugs] [KeyboardNav]:
|
||||||
|
- Maso actor is not focusable with keyboard nav
|
||||||
|
- Parent pages not focusable
|
||||||
|
- Search button is double-focusable (once the link, and one the button)
|
||||||
|
|
||||||
## Short term
|
## Short term
|
||||||
|
|
||||||
|
- [Feat] Add links to all the timeline image and document on Timeline page
|
||||||
- [Bugs] Make sure uploads name are slug-like and with an extension.
|
- [Bugs] Make sure uploads name are slug-like and with an extension.
|
||||||
- [Bugs] Nyupun can't upload subtitles files
|
- [Bugs] Nyupun can't upload subtitles files
|
||||||
- [Bugs] https://v3.accords-library.com/en/collectibles/dod-original-soundtrack/scans obi is way too big
|
- [Bugs] https://v3.accords-library.com/en/collectibles/dod-original-soundtrack/scans obi is way too big
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
|
@ -19,20 +19,20 @@
|
||||||
"node": ">=19.7.0"
|
"node": ">=19.7.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/check": "^0.9.1",
|
"@astrojs/check": "^0.9.2",
|
||||||
"@astrojs/node": "^8.3.2",
|
"@astrojs/node": "^8.3.3",
|
||||||
"accept-language": "^3.0.18",
|
"accept-language": "^3.0.20",
|
||||||
"astro": "4.13.0",
|
"astro": "4.14.0",
|
||||||
"astro-icon": "^1.1.0",
|
"astro-icon": "^1.1.0",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
"ua-parser-js": "^1.0.38"
|
"ua-parser-js": "^1.0.38"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify-json/material-symbols": "^1.1.85",
|
"@iconify-json/material-symbols": "^1.1.87",
|
||||||
"@types/ua-parser-js": "^0.7.39",
|
"@types/ua-parser-js": "^0.7.39",
|
||||||
"astro-meta-tags": "^0.3.0",
|
"astro-meta-tags": "^0.3.0",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.20",
|
||||||
"postcss-preset-env": "^9.6.0",
|
"postcss-preset-env": "^10.0.1",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"prettier-plugin-astro": "^0.14.1",
|
"prettier-plugin-astro": "^0.14.1",
|
||||||
"typescript": "^5.5.4"
|
"typescript": "^5.5.4"
|
||||||
|
|
|
@ -195,22 +195,16 @@ export class PageCache {
|
||||||
return [`/folders/${change.slug}`];
|
return [`/folders/${change.slug}`];
|
||||||
|
|
||||||
case SDKEndpointNames.getCollectible:
|
case SDKEndpointNames.getCollectible:
|
||||||
return [`/collectibles/${change.slug}`];
|
return [`/collectibles/${change.slug}`, `/collectibles/${change.slug}/relations`];
|
||||||
|
|
||||||
case SDKEndpointNames.getCollectibleGallery:
|
case SDKEndpointNames.getCollectibleGallery:
|
||||||
return [`/collectibles/${change.slug}/gallery`];
|
return [`/collectibles/${change.slug}/gallery`];
|
||||||
|
|
||||||
// case SDKEndpointNames.getCollectibleGalleryImage:
|
|
||||||
// return [`/collectibles/${change.slug}/gallery/${change.index}`];
|
|
||||||
|
|
||||||
case SDKEndpointNames.getCollectibleScans:
|
case SDKEndpointNames.getCollectibleScans:
|
||||||
return [`/collectibles/${change.slug}/scans`];
|
return [`/collectibles/${change.slug}/scans`];
|
||||||
|
|
||||||
// case SDKEndpointNames.getCollectibleScanPage:
|
|
||||||
// return [`/collectibles/${change.slug}/scans/${change.index}`];
|
|
||||||
|
|
||||||
case SDKEndpointNames.getPage:
|
case SDKEndpointNames.getPage:
|
||||||
return [`/pages/${change.slug}`];
|
return [`/pages/${change.slug}`, `/pages/${change.slug}/relations`];
|
||||||
|
|
||||||
case SDKEndpointNames.getAudioByID:
|
case SDKEndpointNames.getAudioByID:
|
||||||
return [`/audios/${change.id}`];
|
return [`/audios/${change.id}`];
|
||||||
|
|
|
@ -4,25 +4,20 @@ import Topbar from "./components/Topbar/Topbar.astro";
|
||||||
import Footer from "./components/Footer.astro";
|
import Footer from "./components/Footer.astro";
|
||||||
import AppLayoutBackgroundImg from "./components/AppLayoutBackgroundImg.astro";
|
import AppLayoutBackgroundImg from "./components/AppLayoutBackgroundImg.astro";
|
||||||
import type { ComponentProps } from "astro/types";
|
import type { ComponentProps } from "astro/types";
|
||||||
import type { EndpointRelation } from "src/shared/payload/endpoint-types";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
openGraph?: ComponentProps<typeof Html>["openGraph"];
|
openGraph?: ComponentProps<typeof Html>["openGraph"];
|
||||||
backlinks?: EndpointRelation[];
|
|
||||||
backgroundImage?: ComponentProps<typeof AppLayoutBackgroundImg>["img"] | undefined;
|
backgroundImage?: ComponentProps<typeof AppLayoutBackgroundImg>["img"] | undefined;
|
||||||
hideFooterLinks?: boolean;
|
hideFooterLinks?: boolean;
|
||||||
hideHomeButton?: boolean;
|
|
||||||
hideSearchButton?: boolean;
|
|
||||||
class?: string | undefined;
|
class?: string | undefined;
|
||||||
|
topBar?: ComponentProps<typeof Topbar>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
openGraph,
|
openGraph,
|
||||||
backlinks,
|
|
||||||
backgroundImage,
|
backgroundImage,
|
||||||
hideFooterLinks = false,
|
hideFooterLinks = false,
|
||||||
hideHomeButton = false,
|
topBar = {},
|
||||||
hideSearchButton = false,
|
|
||||||
...otherProps
|
...otherProps
|
||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
---
|
---
|
||||||
|
@ -32,11 +27,7 @@ const {
|
||||||
<Html openGraph={openGraph}>
|
<Html openGraph={openGraph}>
|
||||||
{backgroundImage && <AppLayoutBackgroundImg img={backgroundImage} />}
|
{backgroundImage && <AppLayoutBackgroundImg img={backgroundImage} />}
|
||||||
<header>
|
<header>
|
||||||
<Topbar
|
<Topbar {...topBar} />
|
||||||
backlinks={backlinks}
|
|
||||||
hideHomeButton={hideHomeButton}
|
|
||||||
hideSearchButton={hideSearchButton}
|
|
||||||
/>
|
|
||||||
</header>
|
</header>
|
||||||
<main {...otherProps.class ? otherProps : {}}><slot /></main>
|
<main {...otherProps.class ? otherProps : {}}><slot /></main>
|
||||||
<Footer withLinks={!hideFooterLinks} />
|
<Footer withLinks={!hideFooterLinks} />
|
||||||
|
|
|
@ -105,6 +105,7 @@ const contactLabel = `${t("footer.socials.contact.title")} - ${t(
|
||||||
grid-template-columns: auto auto;
|
grid-template-columns: auto auto;
|
||||||
gap: 0.2em 1em;
|
gap: 0.2em 1em;
|
||||||
place-content: start;
|
place-content: start;
|
||||||
|
place-items: start;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
||||||
@media (min-width: 720.5px) {
|
@media (min-width: 720.5px) {
|
||||||
|
|
|
@ -674,10 +674,14 @@ const isIOS = parser.getOS().name === "iOS";
|
||||||
display: flex;
|
display: flex;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
gap: 0.4em;
|
gap: 0.4em;
|
||||||
padding: 0.7em 0.8em;
|
padding: 0.7em 1.1em;
|
||||||
border-radius: 9999px;
|
border-radius: 9999px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:has(svg) {
|
||||||
|
padding-left: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
transition: 150ms background-color;
|
transition: 150ms background-color;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
|
|
|
@ -4,17 +4,23 @@ import Button from "components/Button.astro";
|
||||||
import ThemeSelector from "./components/ThemeSelector.astro";
|
import ThemeSelector from "./components/ThemeSelector.astro";
|
||||||
import LanguageSelector from "./components/LanguageSelector.astro";
|
import LanguageSelector from "./components/LanguageSelector.astro";
|
||||||
import CurrencySelector from "./components/CurrencySelector.astro";
|
import CurrencySelector from "./components/CurrencySelector.astro";
|
||||||
import ParentPagesButton from "./components/ParentPagesButton.astro";
|
import RelationsButton from "./components/RelationsButton.astro";
|
||||||
import { getI18n } from "src/i18n/i18n";
|
import { getI18n } from "src/i18n/i18n";
|
||||||
import type { EndpointRelation } from "src/shared/payload/endpoint-types";
|
import type { EndpointRelation } from "src/shared/payload/endpoint-types";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
backlinks?: EndpointRelation[] | undefined;
|
relations?: EndpointRelation[] | undefined;
|
||||||
hideHomeButton?: boolean;
|
relationPageUrl?: string | undefined;
|
||||||
hideSearchButton?: boolean;
|
hideHomeButton?: boolean | undefined;
|
||||||
|
hideSearchButton?: boolean | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { backlinks = [], hideHomeButton = false, hideSearchButton = false } = Astro.props;
|
const {
|
||||||
|
relations = [],
|
||||||
|
relationPageUrl,
|
||||||
|
hideHomeButton = false,
|
||||||
|
hideSearchButton = false,
|
||||||
|
} = Astro.props;
|
||||||
|
|
||||||
const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||||
---
|
---
|
||||||
|
@ -23,14 +29,16 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||||
|
|
||||||
<nav id="topbar" class="when-no-print">
|
<nav id="topbar" class="when-no-print">
|
||||||
{
|
{
|
||||||
(!hideHomeButton || backlinks.length > 0) && (
|
(!hideHomeButton || relations.length > 0) && (
|
||||||
<div id="left" class="hide-scrollbar">
|
<div id="left" class="hide-scrollbar">
|
||||||
<a href={getLocalizedUrl("")} class="pressable-label">
|
<a href={getLocalizedUrl("")} class="pressable-label">
|
||||||
<Icon name="material-symbols:home" width={16} height={16} />
|
<Icon name="material-symbols:home" width={16} height={16} />
|
||||||
<p>{t("home.title")}</p>
|
<p>{t("home.title")}</p>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{backlinks.length > 0 && <ParentPagesButton backlinks={backlinks} />}
|
{relations.length > 0 && (
|
||||||
|
<RelationsButton relations={relations} relationPageUrl={relationPageUrl} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
---
|
|
||||||
import Tooltip from "components/Tooltip.astro";
|
|
||||||
import { Icon } from "astro-icon/components";
|
|
||||||
import { getI18n } from "src/i18n/i18n";
|
|
||||||
import ReturnToButton from "./ReturnToButton.astro";
|
|
||||||
import RelationRow from "components/RelationRow.astro";
|
|
||||||
import type { EndpointRelation } from "src/shared/payload/endpoint-types";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
backlinks: EndpointRelation[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const { backlinks } = Astro.props;
|
|
||||||
|
|
||||||
const { t } = await getI18n(Astro.locals.currentLocale);
|
|
||||||
---
|
|
||||||
|
|
||||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
|
||||||
|
|
||||||
{
|
|
||||||
backlinks.length === 1 && backlinks[0] ? (
|
|
||||||
<ReturnToButton relation={backlinks[0]} />
|
|
||||||
) : (
|
|
||||||
<Tooltip trigger="click" class="when-js">
|
|
||||||
<div id="tooltip-content" slot="tooltip-content">
|
|
||||||
<p>{t("header.nav.parentPages.tooltip")}</p>
|
|
||||||
<div>
|
|
||||||
{backlinks.map((relation) => (
|
|
||||||
<RelationRow relation={relation} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button class="pressable-label">
|
|
||||||
<Icon name="material-symbols:keyboard-return" />
|
|
||||||
<p>
|
|
||||||
{t("header.nav.parentPages.label", {
|
|
||||||
count: backlinks.length,
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
</button>
|
|
||||||
</Tooltip>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
{/* ------------------------------------------- CSS -------------------------------------------- */}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
#tooltip-content {
|
|
||||||
> p {
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
> div {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.8em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
---
|
||||||
|
import Tooltip from "components/Tooltip.astro";
|
||||||
|
import { Icon } from "astro-icon/components";
|
||||||
|
import { getI18n } from "src/i18n/i18n";
|
||||||
|
import ReturnToButton from "./ReturnToButton.astro";
|
||||||
|
import RelationRow from "components/RelationRow.astro";
|
||||||
|
import type { EndpointRelation } from "src/shared/payload/endpoint-types";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
relations: EndpointRelation[];
|
||||||
|
relationPageUrl?: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { relations, relationPageUrl } = Astro.props;
|
||||||
|
|
||||||
|
const { t } = await getI18n(Astro.locals.currentLocale);
|
||||||
|
|
||||||
|
const buttonLabel = t("header.nav.parentPages.label", {
|
||||||
|
count: relations.length,
|
||||||
|
});
|
||||||
|
---
|
||||||
|
|
||||||
|
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||||
|
|
||||||
|
{
|
||||||
|
relations.length === 1 && relations[0] ? (
|
||||||
|
<ReturnToButton relation={relations[0]} />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Tooltip trigger="click" class="when-js">
|
||||||
|
<div id="tooltip-content" slot="tooltip-content">
|
||||||
|
<p>{t("header.nav.parentPages.tooltip")}</p>
|
||||||
|
<div>
|
||||||
|
{relations.slice(0, 5).map((relation) => (
|
||||||
|
<RelationRow relation={relation} />
|
||||||
|
))}
|
||||||
|
{relationPageUrl && (
|
||||||
|
<>
|
||||||
|
<hr />
|
||||||
|
<a href={relationPageUrl} class="pressable-link">
|
||||||
|
{t("header.nav.parentPages.tooltip.viewAll")}
|
||||||
|
</a>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pressable-label">
|
||||||
|
<Icon name="material-symbols:keyboard-return" />
|
||||||
|
<p>{buttonLabel}</p>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
{relationPageUrl && (
|
||||||
|
<a class="pressable-label when-no-js" href={relationPageUrl}>
|
||||||
|
<Icon name="material-symbols:keyboard-return" />
|
||||||
|
<div>{buttonLabel}</div>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
{/* ------------------------------------------- CSS -------------------------------------------- */}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#tooltip-content {
|
||||||
|
> p {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.8em;
|
||||||
|
|
||||||
|
& > hr {
|
||||||
|
border: none;
|
||||||
|
border-top: 2px dotted var(--color-base-500);
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .pressable-link {
|
||||||
|
padding: 4px 10px;
|
||||||
|
margin: -4px -10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,37 @@
|
||||||
|
---
|
||||||
|
import Button from "components/Button.astro";
|
||||||
|
import Card from "components/Card.astro";
|
||||||
|
import { getI18n } from "src/i18n/i18n";
|
||||||
|
import type { EndpointRelation } from "src/shared/payload/endpoint-types";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
parent: EndpointRelation;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { parent } = Astro.props;
|
||||||
|
|
||||||
|
const { formatEndpointRelation, t } = await getI18n(Astro.locals.currentLocale);
|
||||||
|
const { href, target, label } = formatEndpointRelation(parent);
|
||||||
|
---
|
||||||
|
|
||||||
|
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||||
|
|
||||||
|
<Card class="card_subpage">
|
||||||
|
<p class="font-l">{t("global.subpageCard.message", { title: label })}</p>
|
||||||
|
<a href={href} target={target}>
|
||||||
|
<Button title={t("global.subpageCard.returnButton")} icon="material-symbols:keyboard-return" />
|
||||||
|
</a>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* ------------------------------------------- CSS -------------------------------------------- */}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.card_subpage {
|
||||||
|
padding: 2em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1em;
|
||||||
|
width: fit-content;
|
||||||
|
place-items: start;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -43,6 +43,6 @@ const getTruncatedText = () => {
|
||||||
title={getTruncatedText()}
|
title={getTruncatedText()}
|
||||||
href={getLocalizedUrl(`/timeline#${formatTimelineDateToId(date)}`)}
|
href={getLocalizedUrl(`/timeline#${formatTimelineDateToId(date)}`)}
|
||||||
icon="material-symbols:calendar-month"
|
icon="material-symbols:calendar-month"
|
||||||
iconHoverLabel={t("global.collections.chronologyEvents", { count: 1 })}
|
iconHoverLabel={t("global.collections.chronologyEvents")}
|
||||||
smallTitle
|
smallTitle
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -2,35 +2,38 @@
|
||||||
import GenericPreview from "components/Previews/GenericPreview.astro";
|
import GenericPreview from "components/Previews/GenericPreview.astro";
|
||||||
import { getI18n } from "src/i18n/i18n";
|
import { getI18n } from "src/i18n/i18n";
|
||||||
import { Collections } from "src/shared/payload/constants";
|
import { Collections } from "src/shared/payload/constants";
|
||||||
import type { EndpointFolder } from "src/shared/payload/endpoint-types";
|
import type { EndpointFolder, EndpointFolderPreview } from "src/shared/payload/endpoint-types";
|
||||||
import type { Attribute } from "src/utils/attributes";
|
import type { Attribute } from "src/utils/attributes";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
folder: EndpointFolder;
|
folder: EndpointFolder | EndpointFolderPreview;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { getLocalizedUrl, getLocalizedMatch, t } = await getI18n(Astro.locals.currentLocale);
|
const { getLocalizedUrl, getLocalizedMatch, t } = await getI18n(Astro.locals.currentLocale);
|
||||||
|
|
||||||
const {
|
const { folder } = Astro.props;
|
||||||
folder: { translations, slug, files, sections, backlinks },
|
|
||||||
} = Astro.props;
|
|
||||||
|
|
||||||
const { language, title } = getLocalizedMatch(translations);
|
const { language, title } = getLocalizedMatch(folder.translations);
|
||||||
|
|
||||||
const fileCount = files.length;
|
const attributes: Attribute[] = [];
|
||||||
|
|
||||||
const subfolderCount =
|
if ("files" in folder) {
|
||||||
|
const { backlinks, files, sections } = folder;
|
||||||
|
|
||||||
|
const fileCount = files.length;
|
||||||
|
|
||||||
|
const subfolderCount =
|
||||||
sections.type === "single"
|
sections.type === "single"
|
||||||
? sections.subfolders.length
|
? sections.subfolders.length
|
||||||
: sections.sections.reduce((acc, section) => acc + section.subfolders.length, 0);
|
: sections.sections.reduce((acc, section) => acc + section.subfolders.length, 0);
|
||||||
|
|
||||||
const attributes: Attribute[] = [
|
attributes.push({
|
||||||
{
|
|
||||||
icon: "material-symbols:box",
|
icon: "material-symbols:box",
|
||||||
title: t("global.folders.attributes.content.label"),
|
title: t("global.folders.attributes.content.label"),
|
||||||
values: [{ name: t("global.folders.attributes.content.value", { fileCount, subfolderCount }) }],
|
values: [{ name: t("global.folders.attributes.content.value", { fileCount, subfolderCount }) }],
|
||||||
},
|
});
|
||||||
{
|
|
||||||
|
attributes.push({
|
||||||
icon: "material-symbols:keyboard-return",
|
icon: "material-symbols:keyboard-return",
|
||||||
title: t("global.folders.attributes.parent"),
|
title: t("global.folders.attributes.parent"),
|
||||||
values: backlinks.flatMap((link) => {
|
values: backlinks.flatMap((link) => {
|
||||||
|
@ -38,8 +41,8 @@ const attributes: Attribute[] = [
|
||||||
const name = getLocalizedMatch(link.value.translations).title;
|
const name = getLocalizedMatch(link.value.translations).title;
|
||||||
return { name };
|
return { name };
|
||||||
}),
|
}),
|
||||||
},
|
});
|
||||||
];
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||||
|
@ -47,7 +50,7 @@ const attributes: Attribute[] = [
|
||||||
<GenericPreview
|
<GenericPreview
|
||||||
title={title}
|
title={title}
|
||||||
lang={language}
|
lang={language}
|
||||||
href={getLocalizedUrl(`/folders/${slug}`)}
|
href={getLocalizedUrl(`/folders/${folder.slug}`)}
|
||||||
attributes={attributes}
|
attributes={attributes}
|
||||||
icon="material-symbols:folder-open"
|
icon="material-symbols:folder-open"
|
||||||
iconHoverLabel={t("global.collections.folders", { count: 1 })}
|
iconHoverLabel={t("global.collections.folders", { count: 1 })}
|
||||||
|
|
|
@ -22,19 +22,25 @@ const {
|
||||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||||
|
|
||||||
<a href={href} target={target} rel={rel}>
|
<a href={href} target={target} rel={rel}>
|
||||||
<div class="font-xs">{typeLabel}</div><p lang={lang}>{label}</p>
|
<p>
|
||||||
|
<span id="type" class="font-xs">{typeLabel}</span><span id="label" lang={lang}>{label}</span>
|
||||||
|
</p>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{/* ------------------------------------------- CSS -------------------------------------------- */}
|
{/* ------------------------------------------- CSS -------------------------------------------- */}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
a {
|
a {
|
||||||
display: flex;
|
border-radius: 16px;
|
||||||
flex-wrap: wrap;
|
padding: 4px 10px;
|
||||||
place-items: center;
|
margin: -4px -10px;
|
||||||
gap: 0.1em 0.3em;
|
|
||||||
|
|
||||||
& > p {
|
& > p {
|
||||||
|
display: flex;
|
||||||
|
place-items: start;
|
||||||
|
gap: 0.1em 0.3em;
|
||||||
|
|
||||||
|
& > #label {
|
||||||
text-decoration: underline dotted 0.1em;
|
text-decoration: underline dotted 0.1em;
|
||||||
text-decoration-color: transparent;
|
text-decoration-color: transparent;
|
||||||
|
|
||||||
|
@ -44,12 +50,17 @@ const {
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
border-radius: 16px;
|
& > #type {
|
||||||
padding: 4px 10px;
|
background-color: var(--color-base-300);
|
||||||
margin: -4px -10px;
|
border-radius: 9999px;
|
||||||
|
padding: 0.3em 0.6em;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: -0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:hover > p,
|
&:hover > p > #label,
|
||||||
&:focus-visible > p {
|
&:focus-visible > p > #label {
|
||||||
color: var(--color-base-750);
|
color: var(--color-base-750);
|
||||||
text-decoration-color: var(--color-base-650);
|
text-decoration-color: var(--color-base-650);
|
||||||
}
|
}
|
||||||
|
@ -58,17 +69,9 @@ const {
|
||||||
outline-width: 1.5 px;
|
outline-width: 1.5 px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active > p {
|
&:active > p > #label {
|
||||||
color: var(--color-base-650);
|
color: var(--color-base-650);
|
||||||
text-decoration-color: var(--color-base-550);
|
text-decoration-color: var(--color-base-550);
|
||||||
}
|
}
|
||||||
|
|
||||||
& > div {
|
|
||||||
background-color: var(--color-base-300);
|
|
||||||
border-radius: 9999px;
|
|
||||||
padding: 0.3em 0.6em;
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-left: -0.5em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,140 @@
|
||||||
|
---
|
||||||
|
import ChronologyEventPreview from "components/Previews/ChronologyEventPreview.astro";
|
||||||
|
import CollectiblePreview from "components/Previews/CollectiblePreview.astro";
|
||||||
|
import FolderPreview from "components/Previews/FolderPreview.astro";
|
||||||
|
import { Collections } from "src/shared/payload/constants";
|
||||||
|
import type {
|
||||||
|
EndpointChronologyEvent,
|
||||||
|
EndpointCollectiblePreview,
|
||||||
|
EndpointFolderPreview,
|
||||||
|
EndpointRelation,
|
||||||
|
} from "src/shared/payload/endpoint-types";
|
||||||
|
import ReturnToParentCard from "./AppLayout/components/Topbar/components/ReturnToParentCard.astro";
|
||||||
|
import AppLayoutTitle from "./AppLayout/components/AppLayoutTitle.astro";
|
||||||
|
import { getI18n } from "src/i18n/i18n";
|
||||||
|
import AppLayout from "./AppLayout/AppLayout.astro";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
parentPage: EndpointRelation;
|
||||||
|
backlinks: EndpointRelation[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { backlinks, parentPage } = Astro.props;
|
||||||
|
const { formatEndpointRelation, t } = await getI18n(Astro.locals.currentLocale);
|
||||||
|
const { label } = formatEndpointRelation(parentPage);
|
||||||
|
|
||||||
|
const collectibles: EndpointCollectiblePreview[] = [];
|
||||||
|
const folders: EndpointFolderPreview[] = [];
|
||||||
|
const events: EndpointChronologyEvent[] = [];
|
||||||
|
|
||||||
|
backlinks.forEach((relation) => {
|
||||||
|
switch (relation.type) {
|
||||||
|
case Collections.Collectibles:
|
||||||
|
collectibles.push(relation.value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Collections.Folders:
|
||||||
|
folders.push(relation.value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Collections.ChronologyEvents:
|
||||||
|
events.push(relation.value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
---
|
||||||
|
|
||||||
|
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||||
|
|
||||||
|
<AppLayout class="app">
|
||||||
|
<AppLayoutTitle title={t("global.relationPage.title")} />
|
||||||
|
|
||||||
|
<ReturnToParentCard parent={parentPage} />
|
||||||
|
|
||||||
|
{
|
||||||
|
collectibles.length > 0 && (
|
||||||
|
<section>
|
||||||
|
<h2 class="font-3xl font-serif">
|
||||||
|
{t("global.collections.collectibles", { count: collectibles.length })}
|
||||||
|
</h2>
|
||||||
|
<p>{t("global.relationPage.collectibles", { title: label, count: folders.length })}</p>
|
||||||
|
<div class="grid">
|
||||||
|
{collectibles.map((collectible) => (
|
||||||
|
<CollectiblePreview collectible={collectible} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
folders.length > 0 && (
|
||||||
|
<section>
|
||||||
|
<h2 class="font-3xl font-serif">
|
||||||
|
{t("global.collections.folders", { count: folders.length })}
|
||||||
|
</h2>
|
||||||
|
<p>{t("global.relationPage.folders", { title: label, count: folders.length })}</p>
|
||||||
|
<div class="grid">
|
||||||
|
{folders.map((folder) => (
|
||||||
|
<FolderPreview folder={folder} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
events.length > 0 && (
|
||||||
|
<section>
|
||||||
|
<h2 class="font-3xl font-serif">{t("global.collections.chronologyEvents")}</h2>
|
||||||
|
<p>{t("global.relationPage.timelineEvents", { title: label, count: folders.length })}</p>
|
||||||
|
<div class="grid">
|
||||||
|
{events.map(({ date, events }) =>
|
||||||
|
events.map((event) => <ChronologyEventPreview date={date} event={event} />)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</AppLayout>
|
||||||
|
|
||||||
|
{/* ------------------------------------------- CSS -------------------------------------------- */}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.app {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||||
|
gap: clamp(6px, 2vmin, 16px);
|
||||||
|
align-items: start;
|
||||||
|
|
||||||
|
@media (max-width: 40rem) {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
row-gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 24rem) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
margin-block: 3em;
|
||||||
|
|
||||||
|
& > h2 {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > p {
|
||||||
|
margin-bottom: 3em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,6 +1,11 @@
|
||||||
import type { WordingKey } from "src/i18n/wordings-keys";
|
import type { WordingKey } from "src/i18n/wordings-keys";
|
||||||
import { contextCache } from "src/services";
|
import { contextCache } from "src/services";
|
||||||
import { capitalize, formatInlineTitle } from "src/utils/format";
|
import {
|
||||||
|
capitalize,
|
||||||
|
formatInlineTitle,
|
||||||
|
formatRichTextToString,
|
||||||
|
formatTimelineDateToId,
|
||||||
|
} from "src/utils/format";
|
||||||
import type { EndpointChronologyEvent, EndpointRelation } from "src/shared/payload/endpoint-types";
|
import type { EndpointChronologyEvent, EndpointRelation } from "src/shared/payload/endpoint-types";
|
||||||
import { Collections } from "src/shared/payload/constants";
|
import { Collections } from "src/shared/payload/constants";
|
||||||
|
|
||||||
|
@ -281,9 +286,16 @@ export const getI18n = async (locale: string) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const suffix =
|
||||||
|
relation.subpage === "scans"
|
||||||
|
? "/scans"
|
||||||
|
: relation.subpage === "gallery"
|
||||||
|
? "/gallery"
|
||||||
|
: "";
|
||||||
|
|
||||||
const translation = getLocalizedMatch(relation.value.translations);
|
const translation = getLocalizedMatch(relation.value.translations);
|
||||||
return {
|
return {
|
||||||
href: getLocalizedUrl(`/collectibles/${relation.value.slug}`),
|
href: getLocalizedUrl(`/collectibles/${relation.value.slug}${suffix}`),
|
||||||
typeLabel: t("global.sources.typeLabel.collectible"),
|
typeLabel: t("global.sources.typeLabel.collectible"),
|
||||||
label: formatInlineTitle(translation) + getRangeLabel(),
|
label: formatInlineTitle(translation) + getRangeLabel(),
|
||||||
lang: translation.language,
|
lang: translation.language,
|
||||||
|
@ -310,21 +322,37 @@ export const getI18n = async (locale: string) => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case Collections.ChronologyEvents: {
|
||||||
|
if (!relation.value.events[0]) break;
|
||||||
|
|
||||||
|
const translation = getLocalizedMatch(relation.value.events[0].translations);
|
||||||
|
let label =
|
||||||
|
translation.title ??
|
||||||
|
(translation.description && formatRichTextToString(translation.description));
|
||||||
|
if (!label) break;
|
||||||
|
|
||||||
|
return {
|
||||||
|
href: getLocalizedUrl(`/timeline#${formatTimelineDateToId(relation.value.date)}`),
|
||||||
|
typeLabel: t("global.sources.typeLabel.timeline"),
|
||||||
|
label,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/* TODO: Handle other types of relations */
|
/* TODO: Handle other types of relations */
|
||||||
case Collections.Audios:
|
case Collections.Audios:
|
||||||
case Collections.ChronologyEvents:
|
|
||||||
case Collections.Files:
|
case Collections.Files:
|
||||||
case Collections.Images:
|
case Collections.Images:
|
||||||
case Collections.Recorders:
|
case Collections.Recorders:
|
||||||
case Collections.Tags:
|
case Collections.Tags:
|
||||||
case Collections.Videos:
|
case Collections.Videos:
|
||||||
default:
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
href: "/404",
|
href: "/404",
|
||||||
label: `Invalid type ${relation["type"]}`,
|
label: `Invalid type ${relation["type"]}`,
|
||||||
typeLabel: "Error",
|
typeLabel: "Error",
|
||||||
};
|
};
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -176,4 +176,12 @@ export type WordingKey =
|
||||||
| "paginator.goLastPageButton"
|
| "paginator.goLastPageButton"
|
||||||
| "global.folders.attributes.content.label"
|
| "global.folders.attributes.content.label"
|
||||||
| "global.folders.attributes.content.value"
|
| "global.folders.attributes.content.value"
|
||||||
| "global.folders.attributes.parent";
|
| "global.folders.attributes.parent"
|
||||||
|
| "global.sources.typeLabel.timeline"
|
||||||
|
| "global.subpageCard.message"
|
||||||
|
| "global.subpageCard.returnButton"
|
||||||
|
| "header.nav.parentPages.tooltip.viewAll"
|
||||||
|
| "global.relationPage.folders"
|
||||||
|
| "global.relationPage.collectibles"
|
||||||
|
| "global.relationPage.timelineEvents"
|
||||||
|
| "global.relationPage.title";
|
||||||
|
|
|
@ -10,7 +10,7 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||||
|
|
||||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||||
|
|
||||||
<AppLayout class="app" hideHomeButton>
|
<AppLayout class="app" topBar={{ hideHomeButton: true }}>
|
||||||
<div id="text-container">
|
<div id="text-container">
|
||||||
<h1 class="font-serif font-5xl">404</h1>
|
<h1 class="font-serif font-5xl">404</h1>
|
||||||
<h2 class="font-4xl">Not found</h2>
|
<h2 class="font-4xl">Not found</h2>
|
||||||
|
|
|
@ -16,7 +16,7 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||||
|
|
||||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||||
|
|
||||||
<AppLayout class="app" hideHomeButton>
|
<AppLayout class="app" topBar={{ hideHomeButton: true }}>
|
||||||
<div class="top">
|
<div class="top">
|
||||||
<div id="text-container">
|
<div id="text-container">
|
||||||
<h1 class="font-serif font-5xl">500</h1>
|
<h1 class="font-serif font-5xl">500</h1>
|
||||||
|
|
|
@ -73,13 +73,13 @@ const metaAttributes = [
|
||||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||||
|
|
||||||
<AppLayout
|
<AppLayout
|
||||||
backlinks={backlinks}
|
|
||||||
openGraph={{
|
openGraph={{
|
||||||
title: formatInlineTitle({ pretitle, title, subtitle }),
|
title: formatInlineTitle({ pretitle, title, subtitle }),
|
||||||
description: description && formatRichTextToString(description),
|
description: description && formatRichTextToString(description),
|
||||||
thumbnail,
|
thumbnail,
|
||||||
audio,
|
audio,
|
||||||
}}>
|
}}
|
||||||
|
topBar={{ relations: backlinks }}>
|
||||||
<div id="container">
|
<div id="container">
|
||||||
<AudioPlayer audio={audio} class="audio_id-audio-player" />
|
<AudioPlayer audio={audio} class="audio_id-audio-player" />
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ const metaAttributes = [
|
||||||
description: description && formatRichTextToString(description),
|
description: description && formatRichTextToString(description),
|
||||||
thumbnail: image,
|
thumbnail: image,
|
||||||
}}
|
}}
|
||||||
backlinks={backlinks}>
|
topBar={{ relations: backlinks }}>
|
||||||
<Lightbox
|
<Lightbox
|
||||||
image={image}
|
image={image}
|
||||||
pretitle={pretitle}
|
pretitle={pretitle}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { payload } from "src/services";
|
||||||
import { formatInlineTitle, formatRichTextToString } from "src/utils/format";
|
import { formatInlineTitle, formatRichTextToString } from "src/utils/format";
|
||||||
import { fetchOr404 } from "src/utils/responses";
|
import { fetchOr404 } from "src/utils/responses";
|
||||||
import { sizesToSrcset } from "src/utils/img";
|
import { sizesToSrcset } from "src/utils/img";
|
||||||
import RichText from "components/RichText/RichText.astro";
|
import ReturnToParentCard from "components/AppLayout/components/Topbar/components/ReturnToParentCard.astro";
|
||||||
|
|
||||||
const slug = Astro.params.slug!;
|
const slug = Astro.params.slug!;
|
||||||
const { getLocalizedMatch, getLocalizedUrl, t } = await getI18n(Astro.locals.currentLocale);
|
const { getLocalizedMatch, getLocalizedUrl, t } = await getI18n(Astro.locals.currentLocale);
|
||||||
|
@ -15,7 +15,7 @@ const response = await fetchOr404(() => payload.getCollectibleGallery(slug));
|
||||||
if (response instanceof Response) {
|
if (response instanceof Response) {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
const { translations, backlinks, images, thumbnail } = response.data;
|
const { translations, images, thumbnail, backlinks } = response.data;
|
||||||
|
|
||||||
const translation = getLocalizedMatch(translations);
|
const translation = getLocalizedMatch(translations);
|
||||||
---
|
---
|
||||||
|
@ -28,20 +28,9 @@ const translation = getLocalizedMatch(translations);
|
||||||
description: translation.description && formatRichTextToString(translation.description),
|
description: translation.description && formatRichTextToString(translation.description),
|
||||||
thumbnail,
|
thumbnail,
|
||||||
}}
|
}}
|
||||||
backlinks={backlinks}
|
|
||||||
class="app">
|
class="app">
|
||||||
<AppLayoutTitle
|
<AppLayoutTitle title={t("collectibles.gallery.title")} />
|
||||||
title={translation.title}
|
{backlinks[0] && <ReturnToParentCard parent={backlinks[0]} />}
|
||||||
pretitle={translation.pretitle}
|
|
||||||
subtitle={translation.subtitle}
|
|
||||||
lang={translation.language}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{
|
|
||||||
translation.description && (
|
|
||||||
<RichText content={translation.description} context={{ lang: translation.language }} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,7 +19,8 @@ import { sizesToSrcset } from "src/utils/img";
|
||||||
import RichText from "components/RichText/RichText.astro";
|
import RichText from "components/RichText/RichText.astro";
|
||||||
import SubFilesSection from "./_components/SubFilesSection.astro";
|
import SubFilesSection from "./_components/SubFilesSection.astro";
|
||||||
import PriceInfo from "./_components/PriceInfo.astro";
|
import PriceInfo from "./_components/PriceInfo.astro";
|
||||||
import { CollectibleNature } from "src/shared/payload/constants";
|
import { CollectibleNature, Collections } from "src/shared/payload/constants";
|
||||||
|
import { sortBy } from "src/utils/array";
|
||||||
|
|
||||||
const slug = Astro.params.slug!;
|
const slug = Astro.params.slug!;
|
||||||
const { getLocalizedMatch, getLocalizedUrl, t, formatDate } = await getI18n(
|
const { getLocalizedMatch, getLocalizedUrl, t, formatDate } = await getI18n(
|
||||||
|
@ -117,6 +118,8 @@ if (languages.length > 0) {
|
||||||
withBorder: true,
|
withBorder: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sortBy(backlinks, ({ type }) => type, [Collections.Collectibles, Collections.Folders] as const);
|
||||||
---
|
---
|
||||||
|
|
||||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||||
|
@ -127,7 +130,10 @@ if (languages.length > 0) {
|
||||||
description: description && formatRichTextToString(description),
|
description: description && formatRichTextToString(description),
|
||||||
thumbnail,
|
thumbnail,
|
||||||
}}
|
}}
|
||||||
backlinks={backlinks}
|
topBar={{
|
||||||
|
relations: backlinks,
|
||||||
|
relationPageUrl: getLocalizedUrl(`/collectibles/${slug}/relations`),
|
||||||
|
}}
|
||||||
backgroundImage={backgroundImage ?? thumbnail}>
|
backgroundImage={backgroundImage ?? thumbnail}>
|
||||||
<AsideLayout reducedAsideWidth>
|
<AsideLayout reducedAsideWidth>
|
||||||
<Fragment slot="header">
|
<Fragment slot="header">
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
import RelationsPage from "components/RelationsPage.astro";
|
||||||
|
import { payload } from "src/services";
|
||||||
|
import { Collections } from "src/shared/payload/constants";
|
||||||
|
import { fetchOr404 } from "src/utils/responses";
|
||||||
|
|
||||||
|
const slug = Astro.params.slug!;
|
||||||
|
|
||||||
|
const response = await fetchOr404(() => payload.getCollectible(slug));
|
||||||
|
if (response instanceof Response) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
const collectible = response.data;
|
||||||
|
const { backlinks } = collectible;
|
||||||
|
---
|
||||||
|
|
||||||
|
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||||
|
|
||||||
|
<RelationsPage
|
||||||
|
parentPage={{ type: Collections.Collectibles, value: collectible }}
|
||||||
|
backlinks={backlinks}
|
||||||
|
/>
|
|
@ -30,7 +30,7 @@ const translation = getLocalizedMatch(translations);
|
||||||
title: `${formatInlineTitle(translation)} (${index})`,
|
title: `${formatInlineTitle(translation)} (${index})`,
|
||||||
description: translation.description && formatRichTextToString(translation.description),
|
description: translation.description && formatRichTextToString(translation.description),
|
||||||
}}
|
}}
|
||||||
backlinks={backlinks}>
|
topBar={{ relations: backlinks }}>
|
||||||
<Lightbox
|
<Lightbox
|
||||||
image={image}
|
image={image}
|
||||||
title={formatScanIndexShort(index)}
|
title={formatScanIndexShort(index)}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
---
|
---
|
||||||
import AppLayout from "components/AppLayout/AppLayout.astro";
|
import AppLayout from "components/AppLayout/AppLayout.astro";
|
||||||
import AppLayoutTitle from "components/AppLayout/components/AppLayoutTitle.astro";
|
|
||||||
import Credits from "components/Credits.astro";
|
import Credits from "components/Credits.astro";
|
||||||
import { getI18n } from "src/i18n/i18n";
|
import { getI18n } from "src/i18n/i18n";
|
||||||
import { payload } from "src/services";
|
import { payload } from "src/services";
|
||||||
import { fetchOr404 } from "src/utils/responses";
|
import { fetchOr404 } from "src/utils/responses";
|
||||||
import ScanPreview from "./_components/ScanPreview.astro";
|
import ScanPreview from "./_components/ScanPreview.astro";
|
||||||
import { formatInlineTitle, formatRichTextToString } from "src/utils/format";
|
import { formatInlineTitle, formatRichTextToString } from "src/utils/format";
|
||||||
import RichText from "components/RichText/RichText.astro";
|
import AppLayoutTitle from "components/AppLayout/components/AppLayoutTitle.astro";
|
||||||
|
import ReturnToParentCard from "components/AppLayout/components/Topbar/components/ReturnToParentCard.astro";
|
||||||
|
|
||||||
const slug = Astro.params.slug!;
|
const slug = Astro.params.slug!;
|
||||||
const { getLocalizedMatch, t } = await getI18n(Astro.locals.currentLocale);
|
const { getLocalizedMatch, t } = await getI18n(Astro.locals.currentLocale);
|
||||||
|
@ -16,7 +16,7 @@ const response = await fetchOr404(() => payload.getCollectibleScans(slug));
|
||||||
if (response instanceof Response) {
|
if (response instanceof Response) {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
const { translations, credits, cover, pages, dustjacket, obi, backlinks, thumbnail } =
|
const { translations, credits, cover, pages, dustjacket, obi, thumbnail, backlinks } =
|
||||||
response.data;
|
response.data;
|
||||||
|
|
||||||
const translation = getLocalizedMatch(translations);
|
const translation = getLocalizedMatch(translations);
|
||||||
|
@ -45,20 +45,9 @@ const hasOutsideObi = obi ? Object.keys(obi).some((value) => !value.includes("in
|
||||||
description: translation.description && formatRichTextToString(translation.description),
|
description: translation.description && formatRichTextToString(translation.description),
|
||||||
thumbnail,
|
thumbnail,
|
||||||
}}
|
}}
|
||||||
backlinks={backlinks}
|
|
||||||
class="app">
|
class="app">
|
||||||
<AppLayoutTitle
|
<AppLayoutTitle title={t("collectibles.scans.title")} />
|
||||||
title={translation.title}
|
{backlinks[0] && <ReturnToParentCard parent={backlinks[0]} />}
|
||||||
pretitle={translation.pretitle}
|
|
||||||
subtitle={translation.subtitle}
|
|
||||||
lang={translation.language}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{
|
|
||||||
translation.description && (
|
|
||||||
<RichText content={translation.description} context={{ lang: translation.language }} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
{credits.length > 0 && <Credits credits={credits} />}
|
{credits.length > 0 && <Credits credits={credits} />}
|
||||||
|
|
||||||
|
|
|
@ -84,12 +84,12 @@ const smallTitle = title === filename;
|
||||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||||
|
|
||||||
<AppLayout
|
<AppLayout
|
||||||
backlinks={backlinks}
|
|
||||||
openGraph={{
|
openGraph={{
|
||||||
title: formatInlineTitle({ pretitle, title, subtitle }),
|
title: formatInlineTitle({ pretitle, title, subtitle }),
|
||||||
description: description && formatRichTextToString(description),
|
description: description && formatRichTextToString(description),
|
||||||
thumbnail,
|
thumbnail,
|
||||||
}}>
|
}}
|
||||||
|
topBar={{ relations: backlinks }}>
|
||||||
<div id="container">
|
<div id="container">
|
||||||
{
|
{
|
||||||
thumbnail ? (
|
thumbnail ? (
|
||||||
|
|
|
@ -35,7 +35,7 @@ const { language, title, description } = getLocalizedMatch(translations);
|
||||||
title: title,
|
title: title,
|
||||||
description: description && formatRichTextToString(description),
|
description: description && formatRichTextToString(description),
|
||||||
}}
|
}}
|
||||||
backlinks={backlinks}
|
topBar={{ relations: backlinks }}
|
||||||
class="app">
|
class="app">
|
||||||
<AppLayoutTitle title={title} lang={language} />
|
<AppLayoutTitle title={title} lang={language} />
|
||||||
{description && <RichText content={description} context={{ lang: language }} />}
|
{description && <RichText content={description} context={{ lang: language }} />}
|
||||||
|
|
|
@ -83,12 +83,12 @@ const metaAttributes = [
|
||||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||||
|
|
||||||
<AppLayout
|
<AppLayout
|
||||||
backlinks={backlinks}
|
|
||||||
openGraph={{
|
openGraph={{
|
||||||
title: formatInlineTitle({ pretitle, title, subtitle }),
|
title: formatInlineTitle({ pretitle, title, subtitle }),
|
||||||
description: description && formatRichTextToString(description),
|
description: description && formatRichTextToString(description),
|
||||||
thumbnail: image,
|
thumbnail: image,
|
||||||
}}>
|
}}
|
||||||
|
topBar={{ relations: backlinks }}>
|
||||||
<Lightbox
|
<Lightbox
|
||||||
image={image}
|
image={image}
|
||||||
pretitle={pretitle}
|
pretitle={pretitle}
|
||||||
|
|
|
@ -16,7 +16,7 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||||
openGraph={{ title: t("home.title") }}
|
openGraph={{ title: t("home.title") }}
|
||||||
backgroundImage={contextCache.config.home.backgroundImage}
|
backgroundImage={contextCache.config.home.backgroundImage}
|
||||||
hideFooterLinks
|
hideFooterLinks
|
||||||
hideHomeButton
|
topBar={{ hideHomeButton: true }}
|
||||||
class="app">
|
class="app">
|
||||||
<HomeTitle />
|
<HomeTitle />
|
||||||
<p class="prose" set:html={t("home.description")} />
|
<p class="prose" set:html={t("home.description")} />
|
||||||
|
|
|
@ -15,7 +15,7 @@ if (response instanceof Response) {
|
||||||
const page = response.data;
|
const page = response.data;
|
||||||
const { backlinks, thumbnail, translations, backgroundImage } = page;
|
const { backlinks, thumbnail, translations, backgroundImage } = page;
|
||||||
|
|
||||||
const { getLocalizedMatch } = await getI18n(Astro.locals.currentLocale);
|
const { getLocalizedMatch, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||||
const meta = getLocalizedMatch(translations);
|
const meta = getLocalizedMatch(translations);
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ const meta = getLocalizedMatch(translations);
|
||||||
description: meta.summary && formatRichTextToString(meta.summary),
|
description: meta.summary && formatRichTextToString(meta.summary),
|
||||||
thumbnail: thumbnail,
|
thumbnail: thumbnail,
|
||||||
}}
|
}}
|
||||||
backlinks={backlinks}
|
topBar={{ relations: backlinks, relationPageUrl: getLocalizedUrl(`/pages/${slug}/relations`) }}
|
||||||
backgroundImage={backgroundImage ?? thumbnail}>
|
backgroundImage={backgroundImage ?? thumbnail}>
|
||||||
<Page slug={slug} lang={Astro.locals.currentLocale} page={page} />
|
<Page slug={slug} lang={Astro.locals.currentLocale} page={page} />
|
||||||
</AppLayout>
|
</AppLayout>
|
|
@ -0,0 +1,19 @@
|
||||||
|
---
|
||||||
|
import RelationsPage from "components/RelationsPage.astro";
|
||||||
|
import { payload } from "src/services";
|
||||||
|
import { Collections } from "src/shared/payload/constants";
|
||||||
|
import { fetchOr404 } from "src/utils/responses";
|
||||||
|
|
||||||
|
const slug = Astro.params.slug!;
|
||||||
|
|
||||||
|
const response = await fetchOr404(() => payload.getPage(slug));
|
||||||
|
if (response instanceof Response) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
const collectible = response.data;
|
||||||
|
const { backlinks } = collectible;
|
||||||
|
---
|
||||||
|
|
||||||
|
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||||
|
|
||||||
|
<RelationsPage parentPage={{ type: Collections.Pages, value: collectible }} backlinks={backlinks} />
|
|
@ -86,7 +86,7 @@ const getSearchUrl = (newState: State): string => {
|
||||||
|
|
||||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||||
|
|
||||||
<AppLayout hideSearchButton>
|
<AppLayout topBar={{ hideSearchButton: true }}>
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<HomeTitle />
|
<HomeTitle />
|
||||||
|
|
||||||
|
|
|
@ -73,13 +73,13 @@ const metaAttributes = [
|
||||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||||
|
|
||||||
<AppLayout
|
<AppLayout
|
||||||
backlinks={backlinks}
|
|
||||||
openGraph={{
|
openGraph={{
|
||||||
title: formatInlineTitle({ pretitle, title, subtitle }),
|
title: formatInlineTitle({ pretitle, title, subtitle }),
|
||||||
description: description && formatRichTextToString(description),
|
description: description && formatRichTextToString(description),
|
||||||
thumbnail,
|
thumbnail,
|
||||||
video,
|
video,
|
||||||
}}>
|
}}
|
||||||
|
topBar={{ relations: backlinks }}>
|
||||||
<div id="container">
|
<div id="container">
|
||||||
<VideoPlayer class="video_id-video-player" video={video} />
|
<VideoPlayer class="video_id-video-player" video={video} />
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit caa79dee9eca5b9b6959e6f5a721245202423612
|
Subproject commit 63d17331a17a5ab7874599d1df4ecf6b45ac89f3
|
|
@ -9,3 +9,14 @@ export const groupBy = <K, T>(array: T[], getKey: (item: T) => K): { key: K; val
|
||||||
|
|
||||||
return [...map.entries()].map(([key, values]) => ({ key, values }));
|
return [...map.entries()].map(([key, values]) => ({ key, values }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const sortBy = <T, K>(array: T[], getKey: (item: T) => K, sort: K[]) =>
|
||||||
|
array.sort((a, b) => {
|
||||||
|
const aKey = getKey(a);
|
||||||
|
const bKey = getKey(b);
|
||||||
|
let aKeyIndex = sort.indexOf(aKey);
|
||||||
|
let bKeyIndex = sort.indexOf(bKey);
|
||||||
|
if (aKeyIndex === -1) aKeyIndex = array.length;
|
||||||
|
if (bKeyIndex === -1) bKeyIndex = array.length;
|
||||||
|
return aKeyIndex - bKeyIndex;
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue