Added timeline page
This commit is contained in:
parent
606c3cc53f
commit
0c4d5e4007
2
TODO.md
2
TODO.md
|
@ -12,7 +12,6 @@
|
|||
- [Collectibles] Create page for scans
|
||||
- When the tags overflow, the tag group name should be align start (see http://localhost:12499/en/pages/magnitude-negative-chapter-1)
|
||||
- [SDK] create a initPayload() that return a payload sdk (and stop hard wirring to ENV or node-cache)
|
||||
- [Payload] Compare current package.json with fresh install of create-payload-app
|
||||
|
||||
## Long term
|
||||
|
||||
|
@ -21,7 +20,6 @@
|
|||
- Grid view (all files)
|
||||
- Web archives
|
||||
- Videos
|
||||
- Timeline page
|
||||
- Contact page
|
||||
- About us page
|
||||
- Global search function
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 455 KiB |
|
@ -48,7 +48,7 @@ const contactLabel = `${t("footer.socials.contact.title")} - ${t(
|
|||
<Icon name="material-symbols:history" />
|
||||
<p>{"Changelog"}</p>
|
||||
</a>
|
||||
<a href="/timeline" class="DEV_TODO">
|
||||
<a href="/timeline">
|
||||
<Icon name="material-symbols:calendar-month" />
|
||||
<p>{t("footer.links.timeline.title")}</p>
|
||||
</a>
|
||||
|
|
|
@ -499,6 +499,10 @@ const { currentTheme } = Astro.locals;
|
|||
font-size: 24px;
|
||||
}
|
||||
|
||||
> h4 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
> h2,
|
||||
> h3,
|
||||
> h4,
|
||||
|
@ -548,3 +552,17 @@ const { currentTheme } = Astro.locals;
|
|||
Array.from(newDocument.querySelectorAll(".when-no-js")).forEach((node) => node.remove());
|
||||
});
|
||||
</script>
|
||||
|
||||
<script is:inline data-astro-rerun>
|
||||
document.querySelectorAll("a").forEach((element) => {
|
||||
const href = element.getAttribute("href");
|
||||
if (!href || !href.startsWith("#")) return;
|
||||
const heading = document.getElementById(href.substring(1));
|
||||
if (!heading) return;
|
||||
|
||||
element.addEventListener("click", (event) => {
|
||||
heading.scrollIntoView({ behavior: "smooth" });
|
||||
event.preventDefault();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
---
|
||||
import type { EndpointRecorder } from "src/shared/payload/payload-sdk";
|
||||
import { getI18n } from "src/i18n/i18n";
|
||||
|
||||
interface Props {
|
||||
translators?: EndpointRecorder[] | undefined;
|
||||
transcribers?: EndpointRecorder[] | undefined;
|
||||
proofreaders?: EndpointRecorder[] | undefined;
|
||||
}
|
||||
|
||||
const { translators = [], transcribers = [], proofreaders = [] } = Astro.props;
|
||||
const { t } = await getI18n(Astro.locals.currentLocale);
|
||||
|
||||
const tagGroups = [];
|
||||
|
||||
if (translators.length > 0) {
|
||||
tagGroups.push({
|
||||
name: t("global.credits.translators"),
|
||||
values: translators,
|
||||
});
|
||||
}
|
||||
|
||||
if (transcribers.length > 0) {
|
||||
tagGroups.push({
|
||||
name: t("global.credits.transcribers"),
|
||||
values: transcribers,
|
||||
});
|
||||
}
|
||||
|
||||
if (proofreaders.length > 0) {
|
||||
tagGroups.push({
|
||||
name: t("global.credits.proofreaders"),
|
||||
values: proofreaders,
|
||||
});
|
||||
}
|
||||
---
|
||||
|
||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||
|
||||
<div id="tags">
|
||||
{
|
||||
tagGroups.map(({ name, values }) => (
|
||||
<div>
|
||||
<p>{name}</p>
|
||||
{values.map(({ username }) => username).join(", ")}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
{/* ------------------------------------------- CSS -------------------------------------------- */}
|
||||
|
||||
<style>
|
||||
#tags {
|
||||
margin-top: 0.5em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5em 1.5em;
|
||||
|
||||
& > div {
|
||||
font-weight: 400;
|
||||
font-size: 80%;
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
|
||||
& > p {
|
||||
color: var(--color-base-750);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -28,7 +28,7 @@ const title = (() => {
|
|||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||
|
||||
<li data-prefix={entry.prefix}>
|
||||
<a href={`#${entry.prefix}`} class="pressable-link table-of-content-item">{title}</a>
|
||||
<a href={`#${entry.prefix}`} class="pressable-link">{title}</a>
|
||||
{
|
||||
entry.children.length > 0 && (
|
||||
<ol>
|
||||
|
@ -61,19 +61,3 @@ const title = (() => {
|
|||
line-height: 125%;
|
||||
}
|
||||
</style>
|
||||
|
||||
{/* ------------------------------------------- JS --------------------------------------------- */}
|
||||
|
||||
<script>
|
||||
document.querySelectorAll(".table-of-content-item").forEach((element) => {
|
||||
const href = element.getAttribute("href")?.substring(1);
|
||||
if (!href) return;
|
||||
const heading = document.getElementById(href);
|
||||
if (!heading) return;
|
||||
|
||||
element.addEventListener("click", (event) => {
|
||||
heading.scrollIntoView({ behavior: "smooth" });
|
||||
event.preventDefault();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import type { WordingKey } from "src/i18n/wordings-keys";
|
||||
|
||||
const timelineEras: { name: WordingKey; start: number; end: number }[] = [
|
||||
{ name: "timeline.eras.cataclysm", start: 856, end: 856 },
|
||||
{
|
||||
name: "timeline.eras.drakengard3",
|
||||
start: 997,
|
||||
end: 1000,
|
||||
},
|
||||
{
|
||||
name: "timeline.eras.drakengard",
|
||||
start: 1001,
|
||||
end: 1099,
|
||||
},
|
||||
{
|
||||
name: "timeline.eras.drakengard2",
|
||||
start: 1100,
|
||||
end: 1117,
|
||||
},
|
||||
{
|
||||
name: "timeline.eras.nier",
|
||||
start: 2003,
|
||||
end: 5012,
|
||||
},
|
||||
{
|
||||
name: "timeline.eras.nierAutomata",
|
||||
start: 5012,
|
||||
end: 12543,
|
||||
},
|
||||
];
|
||||
|
||||
export const dataConfig = {
|
||||
timeline: {
|
||||
yearsWithABreakBefore: [856, 997, 1001, 1118, 1957, 2003, 2049, 2050, 3361, 3463],
|
||||
eras: timelineEras,
|
||||
},
|
||||
};
|
|
@ -1,5 +1,7 @@
|
|||
import type { WordingKey } from "src/i18n/wordings-keys";
|
||||
import type { ChronologyEvent } from "src/shared/payload/payload-sdk";
|
||||
import { cache } from "src/utils/cachedPayload";
|
||||
import { capitalize } from "src/utils/format";
|
||||
|
||||
export const defaultLocale = "en";
|
||||
|
||||
|
@ -105,9 +107,7 @@ export const getI18n = async (locale: string) => {
|
|||
return template;
|
||||
};
|
||||
|
||||
const getLocalizedMatch = <T extends { language: string }>(
|
||||
options: T[]
|
||||
): Omit<T, "language"> & { language?: string } =>
|
||||
const getLocalizedMatch = <T extends { language: string }>(options: T[]): T =>
|
||||
options.find(({ language }) => language === locale) ??
|
||||
options.find(({ language }) => language === defaultLocale) ??
|
||||
options[0]!; // We will consider that there will always be at least one option.
|
||||
|
@ -129,8 +129,10 @@ export const getI18n = async (locale: string) => {
|
|||
const formatPrice = (price: { amount: number; currency: string }): string =>
|
||||
price.amount.toLocaleString(locale, { style: "currency", currency: price.currency });
|
||||
|
||||
const formatDate = (date: Date): string =>
|
||||
date.toLocaleDateString(locale, { dateStyle: "medium" });
|
||||
const formatDate = (
|
||||
date: Date,
|
||||
options: Intl.DateTimeFormatOptions | undefined = { dateStyle: "medium" }
|
||||
): string => date.toLocaleDateString(locale, options);
|
||||
|
||||
const formatInches = (sizeInMm: number): string => {
|
||||
return (
|
||||
|
@ -156,6 +158,21 @@ export const getI18n = async (locale: string) => {
|
|||
return number.toLocaleString(locale, options);
|
||||
};
|
||||
|
||||
const formatTimelineDate = ({ year, month, day }: ChronologyEvent["date"]): string => {
|
||||
const date = new Date();
|
||||
date.setFullYear(year);
|
||||
if (month) date.setMonth(month - 1);
|
||||
if (day) date.setDate(day);
|
||||
|
||||
return capitalize(
|
||||
formatDate(date, {
|
||||
year: "numeric",
|
||||
month: month ? "long" : undefined,
|
||||
day: day ? "numeric" : undefined,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
t,
|
||||
getLocalizedMatch,
|
||||
|
@ -167,5 +184,6 @@ export const getI18n = async (locale: string) => {
|
|||
formatGrams,
|
||||
formatMillimeters,
|
||||
formatNumber,
|
||||
formatTimelineDate,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -87,4 +87,21 @@ export type WordingKey =
|
|||
| "global.loading"
|
||||
| "pages.tableOfContent.sceneBreak"
|
||||
| "pages.tableOfContent.break"
|
||||
| "global.languageOverride.availableLanguages";
|
||||
| "global.languageOverride.availableLanguages"
|
||||
| "timeline.title"
|
||||
| "timeline.eras.drakengard3"
|
||||
| "timeline.eras.drakengard"
|
||||
| "timeline.eras.drakengard2"
|
||||
| "timeline.eras.nier"
|
||||
| "timeline.eras.nierAutomata"
|
||||
| "timeline.eras.cataclysm"
|
||||
| "timeline.description"
|
||||
| "timeline.notes.title"
|
||||
| "timeline.notes.content"
|
||||
| "timeline.priorCataclysmNote.title"
|
||||
| "timeline.priorCataclysmNote.content"
|
||||
| "timeline.jumpTo"
|
||||
| "timeline.year.during"
|
||||
| "timeline.eventFooter.languages"
|
||||
| "timeline.eventFooter.sources"
|
||||
| "timeline.eventFooter.note";
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
---
|
||||
import MasoTarget from "components/Maso/MasoTarget.astro";
|
||||
import TimelineEventTranslation from "pages/[locale]/timeline/_components/TimelineEventTranslation.astro";
|
||||
import TimelineLanguageOverride from "pages/[locale]/timeline/_components/TimelineLanguageOverride.astro";
|
||||
import TimelineNote from "pages/[locale]/timeline/_components/TimelineNote.astro";
|
||||
import TimelineSourcesButton from "pages/[locale]/timeline/_components/TimelineSourcesButton.astro";
|
||||
import { getI18n } from "src/i18n/i18n";
|
||||
import { payload, type EndpointChronologyEvent } from "src/shared/payload/payload-sdk";
|
||||
|
||||
export const partial = true;
|
||||
|
||||
interface Props {
|
||||
lang: string;
|
||||
event: EndpointChronologyEvent;
|
||||
id: string;
|
||||
index: number;
|
||||
}
|
||||
|
||||
const reqUrl = new URL(Astro.request.url);
|
||||
const lang = Astro.props.lang ?? reqUrl.searchParams.get("lang")!;
|
||||
const id = Astro.props.id ?? reqUrl.searchParams.get("id")!;
|
||||
const index = Astro.props.index ?? parseInt(reqUrl.searchParams.get("index")!);
|
||||
const event = Astro.props.event ?? (await payload.getChronologyEventByID(id));
|
||||
const { sources, translations } = event.events[index]!;
|
||||
|
||||
const { getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||
const { getLocalizedMatch } = await getI18n(lang);
|
||||
const { title, description, notes, proofreaders, transcribers, translators } =
|
||||
getLocalizedMatch(translations);
|
||||
---
|
||||
|
||||
<MasoTarget>
|
||||
<div class="event">
|
||||
<TimelineEventTranslation title={title} description={description} />
|
||||
<div id="bottom" class="when-js when-no-print">
|
||||
{sources.length > 0 && <TimelineSourcesButton sources={sources} />}
|
||||
{notes && <TimelineNote notes={notes} />}
|
||||
<TimelineLanguageOverride
|
||||
availableLanguages={translations.map(({ language }) => language)}
|
||||
currentLang={lang}
|
||||
proofreaders={proofreaders}
|
||||
transcribers={transcribers}
|
||||
translators={translators}
|
||||
getPartialUrl={(locale) =>
|
||||
getLocalizedUrl(`/api/timeline/partial?id=${id}&index=${index}&lang=${locale}`)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</MasoTarget>
|
||||
|
||||
<style>
|
||||
.event {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
|
||||
& > #bottom {
|
||||
display: flex;
|
||||
place-items: start;
|
||||
gap: 0.3em;
|
||||
font-size: 85%;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -6,6 +6,7 @@ import LibraryGrid from "./_components/LibraryGrid.astro";
|
|||
import ChronicleCard from "./_components/ChronicleCard.astro";
|
||||
import LinkCard from "./_components/LinkCard.astro";
|
||||
import { getI18n } from "src/i18n/i18n";
|
||||
import { dataConfig } from "src/dataConfig";
|
||||
|
||||
const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||
---
|
||||
|
@ -113,15 +114,6 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
|||
<p set:html={t("home.moreSection.description")} />
|
||||
<div class="grid">
|
||||
<div class="DEV_TODO">
|
||||
<LinkCard
|
||||
icon="material-symbols:calendar-month-outline"
|
||||
title={t("footer.links.timeline.title")}
|
||||
subtitle={t("footer.links.timeline.subtitle", {
|
||||
eraCount: 8,
|
||||
eventCount: 358,
|
||||
})}
|
||||
href={getLocalizedUrl("/timeline")}
|
||||
/>
|
||||
<LinkCard
|
||||
icon="material-symbols:movie-outline"
|
||||
title={t("footer.links.videos.title")}
|
||||
|
@ -136,6 +128,16 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
|||
/>
|
||||
</div>
|
||||
|
||||
<LinkCard
|
||||
icon="material-symbols:calendar-month-outline"
|
||||
title={t("footer.links.timeline.title")}
|
||||
subtitle={t("footer.links.timeline.subtitle", {
|
||||
eraCount: dataConfig.timeline.eras.length,
|
||||
eventCount: 358,
|
||||
})}
|
||||
href={getLocalizedUrl("/timeline")}
|
||||
/>
|
||||
|
||||
<LinkCard
|
||||
icon="material-symbols:perm-media-outline"
|
||||
title={t("footer.links.gallery.title")}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
---
|
||||
import { getI18n } from "src/i18n/i18n";
|
||||
import type { EndpointChronologyEvent } from "src/shared/payload/payload-sdk";
|
||||
import TimelineEventPartial from "../../api/timeline/partial.astro";
|
||||
|
||||
interface Props {
|
||||
event: EndpointChronologyEvent;
|
||||
displayDate: boolean;
|
||||
}
|
||||
|
||||
const { event, displayDate } = Astro.props;
|
||||
const { formatTimelineDate, t } = await getI18n(Astro.locals.currentLocale);
|
||||
|
||||
const displayedDate =
|
||||
!event.date.month && !event.date.day
|
||||
? t("timeline.year.during", { year: formatTimelineDate(event.date) })
|
||||
: formatTimelineDate(event.date);
|
||||
---
|
||||
|
||||
<div class="event-container">
|
||||
{displayDate && <h3>{displayedDate}</h3>}
|
||||
|
||||
<div>
|
||||
{
|
||||
event.events.map((_, index) => (
|
||||
<TimelineEventPartial
|
||||
event={event}
|
||||
index={index}
|
||||
id={event.id}
|
||||
lang={Astro.locals.currentLocale}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.event-container {
|
||||
&:has(h3) > div {
|
||||
border-left: 1px solid var(--color-base-600);
|
||||
padding-left: 1em;
|
||||
padding-block: 1em;
|
||||
}
|
||||
|
||||
& > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2em;
|
||||
}
|
||||
|
||||
& > h3 {
|
||||
padding-bottom: 0.3em;
|
||||
padding-inline: 0.2em;
|
||||
color: var(--color-base-700);
|
||||
border-bottom: 1px solid var(--color-base-600);
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
import RichText from "components/RichText/RichText.astro";
|
||||
import type { RichTextContent } from "src/shared/payload/payload-sdk";
|
||||
|
||||
interface Props {
|
||||
title?: string | undefined;
|
||||
description?: RichTextContent | undefined;
|
||||
}
|
||||
|
||||
const { title, description } = Astro.props;
|
||||
---
|
||||
|
||||
<div>
|
||||
{title && <h4>{title}</h4>}
|
||||
{description && <RichText content={description} />}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
|
||||
& > h4 {
|
||||
font-size: 120%;
|
||||
max-width: 35rem;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,66 @@
|
|||
---
|
||||
import { Icon } from "astro-icon/components";
|
||||
import InlineCredits from "components/InlineCredits.astro";
|
||||
import MasoActor from "components/Maso/MasoActor.astro";
|
||||
import Tooltip from "components/Tooltip.astro";
|
||||
import { getI18n } from "src/i18n/i18n";
|
||||
import type { EndpointRecorder } from "src/shared/payload/payload-sdk";
|
||||
import { formatLocale } from "src/utils/format";
|
||||
|
||||
interface Props {
|
||||
currentLang: string;
|
||||
availableLanguages: string[];
|
||||
getPartialUrl: (locale: string) => string;
|
||||
transcribers: EndpointRecorder[];
|
||||
translators: EndpointRecorder[];
|
||||
proofreaders: EndpointRecorder[];
|
||||
}
|
||||
|
||||
const { availableLanguages, transcribers, proofreaders, translators, getPartialUrl, currentLang } =
|
||||
Astro.props;
|
||||
|
||||
const { t } = await getI18n(Astro.locals.currentLocale);
|
||||
---
|
||||
|
||||
<Tooltip trigger="click">
|
||||
<div id="tooltip-content" slot="tooltip-content">
|
||||
{
|
||||
availableLanguages.map((id) => (
|
||||
<MasoActor href={getPartialUrl(id)}>
|
||||
<p class:list={{ current: id === currentLang, "pressable-link": true }}>
|
||||
{formatLocale(id)}
|
||||
</p>
|
||||
</MasoActor>
|
||||
))
|
||||
}
|
||||
|
||||
<InlineCredits
|
||||
translators={translators}
|
||||
transcribers={transcribers}
|
||||
proofreaders={proofreaders}
|
||||
/>
|
||||
</div>
|
||||
<div class="pressable-label">
|
||||
<Icon name="material-symbols:translate" />
|
||||
<p>
|
||||
{
|
||||
t("timeline.eventFooter.languages", {
|
||||
count: availableLanguages.length,
|
||||
})
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
<style>
|
||||
#tooltip-content {
|
||||
display: grid;
|
||||
gap: 0.5em;
|
||||
font-size: 1rem;
|
||||
|
||||
& .current {
|
||||
color: var(--color-base-750);
|
||||
text-decoration: underline 0.08em var(--color-base-650);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
import { Icon } from "astro-icon/components";
|
||||
import RichText from "components/RichText/RichText.astro";
|
||||
import Tooltip from "components/Tooltip.astro";
|
||||
import { getI18n } from "src/i18n/i18n";
|
||||
import type { RichTextContent } from "src/shared/payload/payload-sdk";
|
||||
|
||||
interface Props {
|
||||
notes: RichTextContent;
|
||||
}
|
||||
|
||||
const { notes } = Astro.props;
|
||||
|
||||
const { t } = await getI18n(Astro.locals.currentLocale);
|
||||
---
|
||||
|
||||
<Tooltip trigger="click">
|
||||
<div id="tooltip-content" slot="tooltip-content">
|
||||
<RichText content={notes} />
|
||||
</div>
|
||||
<div class="pressable-label">
|
||||
<Icon name="material-symbols:comment-outline" />
|
||||
<p>{t("timeline.eventFooter.note")}</p>
|
||||
</div>
|
||||
</Tooltip>
|
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
import { Icon } from "astro-icon/components";
|
||||
import Tooltip from "components/Tooltip.astro";
|
||||
import { getI18n } from "src/i18n/i18n";
|
||||
import type { EndpointSource } from "src/shared/payload/payload-sdk";
|
||||
import { formatInlineTitle } from "src/utils/format";
|
||||
|
||||
interface Props {
|
||||
sources: EndpointSource[];
|
||||
}
|
||||
|
||||
const { sources } = Astro.props;
|
||||
|
||||
const { getLocalizedUrl, getLocalizedMatch, t } = await getI18n(Astro.locals.currentLocale);
|
||||
---
|
||||
|
||||
<Tooltip trigger="click">
|
||||
<div id="tooltip-content" slot="tooltip-content">
|
||||
{
|
||||
sources.map((source) =>
|
||||
source.type === "url" ? (
|
||||
<a class="pressable-link" href={source.url} target={"_blank"} rel={"noopener noreferrer"}>
|
||||
{source.label}
|
||||
</a>
|
||||
) : source.type === "collectible" ? (
|
||||
<a
|
||||
class="pressable-link"
|
||||
href={getLocalizedUrl(`/collectibles/${source.collectible.slug}`)}>
|
||||
{formatInlineTitle(getLocalizedMatch(source.collectible.translations))}
|
||||
</a>
|
||||
) : (
|
||||
<a class="pressable-link" href={getLocalizedUrl(`/pages/${source.page.slug}`)}>
|
||||
{formatInlineTitle(getLocalizedMatch(source.page.translations))}
|
||||
</a>
|
||||
)
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div class="pressable-label">
|
||||
<Icon name="material-symbols:edit-note" />
|
||||
<p>
|
||||
{t("timeline.eventFooter.sources", { count: sources.length })}
|
||||
</p>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
<style>
|
||||
#tooltip-content {
|
||||
display: grid;
|
||||
gap: 0.5em;
|
||||
font-size: 1rem;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
import type { EndpointChronologyEvent } from "src/shared/payload/payload-sdk";
|
||||
import TimelineEvent from "./TimelineEvent.astro";
|
||||
import { getI18n } from "src/i18n/i18n";
|
||||
import { dataConfig } from "src/dataConfig";
|
||||
|
||||
interface Props {
|
||||
year: number;
|
||||
events: EndpointChronologyEvent[];
|
||||
}
|
||||
|
||||
const { year, events } = Astro.props;
|
||||
const { formatTimelineDate } = await getI18n(Astro.locals.currentLocale);
|
||||
---
|
||||
|
||||
{dataConfig.timeline.yearsWithABreakBefore.includes(year) && <hr id={`hr-${year}`} />}
|
||||
|
||||
<h2 id={year.toString()}>
|
||||
{formatTimelineDate(events.length === 1 ? events[0]!.date : { year })}
|
||||
</h2>
|
||||
|
||||
<div class="year-container">
|
||||
{events.map((event) => <TimelineEvent event={event} displayDate={events.length > 1} />)}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 3px dashed var(--color-base-500);
|
||||
margin-block: 5em;
|
||||
scroll-margin-block: 5em;
|
||||
}
|
||||
|
||||
.year-container {
|
||||
border-left: 1px solid var(--color-base-600);
|
||||
padding-left: 1em;
|
||||
padding-block: 1em;
|
||||
margin-bottom: 3em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
padding-bottom: 0.2em;
|
||||
padding-inline: 0.2em;
|
||||
color: var(--color-base-700);
|
||||
border-bottom: 1px solid var(--color-base-600);
|
||||
scroll-margin-block: 1em;
|
||||
width: fit-content;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,110 @@
|
|||
---
|
||||
import { payload } from "src/shared/payload/payload-sdk";
|
||||
import { groupBy } from "src/utils/array";
|
||||
import TimelineYear from "./_components/TimelineYear.astro";
|
||||
import AppEmptyLayout from "components/AppLayout/AppEmptyLayout.astro";
|
||||
import AppLayoutTitle from "components/AppLayout/components/AppLayoutTitle.astro";
|
||||
import Card from "components/Card.astro";
|
||||
import { getI18n } from "src/i18n/i18n";
|
||||
import AppLayoutBackgroundImg from "components/AppLayout/components/AppLayoutBackgroundImg.astro";
|
||||
import { dataConfig } from "src/dataConfig";
|
||||
|
||||
const events = await payload.getChronologyEvents();
|
||||
const groupedEvents = groupBy(events, (event) => event.date.year);
|
||||
const { getLocalizedUrl, t, formatTimelineDate } = await getI18n(Astro.locals.currentLocale);
|
||||
---
|
||||
|
||||
<AppEmptyLayout>
|
||||
<AppLayoutBackgroundImg
|
||||
img={{
|
||||
url: "/img/timeline-background.webp",
|
||||
filename: "timeline-background",
|
||||
width: 2478,
|
||||
height: 4110,
|
||||
mimeType: "image/webp",
|
||||
}}
|
||||
/>
|
||||
<AppLayoutTitle title={t("timeline.title")} />
|
||||
|
||||
<div id="summary" class="prose">
|
||||
<p>
|
||||
{t("timeline.description")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card-container">
|
||||
<Card>
|
||||
<div class="card-content prose">
|
||||
<h3>{t("timeline.notes.title")}</h3>
|
||||
|
||||
<p
|
||||
set:html={t("timeline.notes.content", {
|
||||
worldInside: `<a href="${getLocalizedUrl(
|
||||
"/collectibles/world-inside"
|
||||
)}">World Inside</a>`,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<div class="card-content prose">
|
||||
<h3>{t("timeline.priorCataclysmNote.title")}</h3>
|
||||
|
||||
<p
|
||||
set:html={t("timeline.priorCataclysmNote.content", {
|
||||
worldInside: `<a href="${getLocalizedUrl(
|
||||
"/collectibles/world-inside"
|
||||
)}">World Inside</a>`,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<div class="card-content prose jump-card">
|
||||
<h3>{t("timeline.jumpTo")}</h3>
|
||||
|
||||
{
|
||||
dataConfig.timeline.eras.map(({ name, start, end }) => (
|
||||
<p
|
||||
set:html={t(name, {
|
||||
start: `<a href="#${start}">${formatTimelineDate({ year: start })}</a>`,
|
||||
end: `<a href="#${end}">${formatTimelineDate({ year: end })}</a>`,
|
||||
})}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
{groupedEvents.map(({ key, values }) => <TimelineYear year={key} events={values} />)}
|
||||
</AppEmptyLayout>
|
||||
|
||||
<style>
|
||||
#summary {
|
||||
backdrop-filter: blur(5px);
|
||||
padding: 1.5em;
|
||||
margin: -1.5em;
|
||||
margin-block: 1em;
|
||||
border-radius: 3em;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: clamp(1em, 4vw, 3em);
|
||||
max-width: 35rem;
|
||||
|
||||
&.jump-card > p {
|
||||
margin-block: 0.2em;
|
||||
}
|
||||
}
|
||||
|
||||
.card-container {
|
||||
margin-top: 2em;
|
||||
margin-bottom: 4em;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
</style>
|
|
@ -20,8 +20,8 @@ export type RecorderBiographies =
|
|||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
direction: ("ltr" | "rtl") | null;
|
||||
format: "left" | "start" | "center" | "right" | "end" | "justify" | "";
|
||||
indent: number;
|
||||
type: string;
|
||||
version: number;
|
||||
|
@ -46,26 +46,25 @@ export interface Config {
|
|||
pages: Page;
|
||||
collectibles: Collectible;
|
||||
folders: Folder;
|
||||
'chronology-items': ChronologyItem;
|
||||
'chronology-eras': ChronologyEra;
|
||||
"chronology-events": ChronologyEvent;
|
||||
notes: Note;
|
||||
images: Image;
|
||||
'background-images': BackgroundImage;
|
||||
'recorders-thumbnails': RecordersThumbnail;
|
||||
"background-images": BackgroundImage;
|
||||
"recorders-thumbnails": RecordersThumbnail;
|
||||
videos: Video;
|
||||
'videos-channels': VideosChannel;
|
||||
"videos-channels": VideosChannel;
|
||||
tags: Tag;
|
||||
'tags-groups': TagsGroup;
|
||||
"tags-groups": TagsGroup;
|
||||
recorders: Recorder;
|
||||
languages: Language;
|
||||
currencies: Currency;
|
||||
wordings: Wording;
|
||||
'generic-contents': GenericContent;
|
||||
'payload-preferences': PayloadPreference;
|
||||
'payload-migrations': PayloadMigration;
|
||||
"generic-contents": GenericContent;
|
||||
"payload-preferences": PayloadPreference;
|
||||
"payload-migrations": PayloadMigration;
|
||||
};
|
||||
globals: {
|
||||
'home-folders': HomeFolder;
|
||||
"home-folders": HomeFolder;
|
||||
};
|
||||
}
|
||||
/**
|
||||
|
@ -75,7 +74,7 @@ export interface Config {
|
|||
export interface Page {
|
||||
id: string;
|
||||
slug: string;
|
||||
type: 'Content' | 'Post' | 'Generic';
|
||||
type: "Content" | "Post" | "Generic";
|
||||
thumbnail?: string | Image | null;
|
||||
backgroundImage?: string | BackgroundImage | null;
|
||||
tags?: (string | Tag)[] | null;
|
||||
|
@ -93,8 +92,8 @@ export interface Page {
|
|||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
direction: ("ltr" | "rtl") | null;
|
||||
format: "left" | "start" | "center" | "right" | "end" | "justify" | "";
|
||||
indent: number;
|
||||
type: string;
|
||||
version: number;
|
||||
|
@ -108,8 +107,8 @@ export interface Page {
|
|||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
direction: ("ltr" | "rtl") | null;
|
||||
format: "left" | "start" | "center" | "right" | "end" | "justify" | "";
|
||||
indent: number;
|
||||
type: string;
|
||||
version: number;
|
||||
|
@ -126,7 +125,7 @@ export interface Page {
|
|||
updatedBy: string | Recorder;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
_status?: ('draft' | 'published') | null;
|
||||
_status?: ("draft" | "published") | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
|
@ -237,7 +236,7 @@ export interface Recorder {
|
|||
avatar?: string | RecordersThumbnail | null;
|
||||
languages?: (string | Language)[] | null;
|
||||
biographies?: RecorderBiographies;
|
||||
role?: ('Admin' | 'Recorder' | 'Api')[] | null;
|
||||
role?: ("Admin" | "Recorder" | "Api")[] | null;
|
||||
anonymize: boolean;
|
||||
email: string;
|
||||
resetPasswordToken?: string | null;
|
||||
|
@ -301,8 +300,8 @@ export interface Folder {
|
|||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
direction: ("ltr" | "rtl") | null;
|
||||
format: "left" | "start" | "center" | "right" | "end" | "justify" | "";
|
||||
indent: number;
|
||||
type: string;
|
||||
version: number;
|
||||
|
@ -327,11 +326,11 @@ export interface Folder {
|
|||
files?:
|
||||
| (
|
||||
| {
|
||||
relationTo: 'collectibles';
|
||||
relationTo: "collectibles";
|
||||
value: string | Collectible;
|
||||
}
|
||||
| {
|
||||
relationTo: 'pages';
|
||||
relationTo: "pages";
|
||||
value: string | Page;
|
||||
}
|
||||
)[]
|
||||
|
@ -347,7 +346,7 @@ export interface Collectible {
|
|||
id: string;
|
||||
slug: string;
|
||||
thumbnail?: string | Image | null;
|
||||
nature: 'Physical' | 'Digital';
|
||||
nature: "Physical" | "Digital";
|
||||
languages?: (string | Language)[] | null;
|
||||
tags?: (string | Tag)[] | null;
|
||||
translations: {
|
||||
|
@ -362,8 +361,8 @@ export interface Collectible {
|
|||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
direction: ("ltr" | "rtl") | null;
|
||||
format: "left" | "start" | "center" | "right" | "end" | "justify" | "";
|
||||
indent: number;
|
||||
type: string;
|
||||
version: number;
|
||||
|
@ -455,8 +454,8 @@ export interface Collectible {
|
|||
pageInfoEnabled?: boolean | null;
|
||||
pageInfo?: {
|
||||
pageCount: number;
|
||||
bindingType?: ('Paperback' | 'Hardcover') | null;
|
||||
pageOrder?: ('Left to right' | 'Right to left') | null;
|
||||
bindingType?: ("Paperback" | "Hardcover") | null;
|
||||
pageOrder?: ("Left to right" | "Right to left") | null;
|
||||
};
|
||||
folders?: (string | Folder)[] | null;
|
||||
parentItems?: (string | Collectible)[] | null;
|
||||
|
@ -465,11 +464,11 @@ export interface Collectible {
|
|||
| {
|
||||
content:
|
||||
| {
|
||||
relationTo: 'pages';
|
||||
relationTo: "pages";
|
||||
value: string | Page;
|
||||
}
|
||||
| {
|
||||
relationTo: 'generic-contents';
|
||||
relationTo: "generic-contents";
|
||||
value: string | GenericContent;
|
||||
};
|
||||
range?:
|
||||
|
@ -479,14 +478,14 @@ export interface Collectible {
|
|||
end: number;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'pageRange';
|
||||
blockType: "pageRange";
|
||||
}
|
||||
| {
|
||||
start: string;
|
||||
end: string;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'timeRange';
|
||||
blockType: "timeRange";
|
||||
}
|
||||
| {
|
||||
translations?:
|
||||
|
@ -499,8 +498,8 @@ export interface Collectible {
|
|||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
direction: ("ltr" | "rtl") | null;
|
||||
format: "left" | "start" | "center" | "right" | "end" | "justify" | "";
|
||||
indent: number;
|
||||
type: string;
|
||||
version: number;
|
||||
|
@ -512,7 +511,7 @@ export interface Collectible {
|
|||
| null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'other';
|
||||
blockType: "other";
|
||||
}
|
||||
)[]
|
||||
| null;
|
||||
|
@ -522,7 +521,7 @@ export interface Collectible {
|
|||
updatedBy: string | Recorder;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
_status?: ('draft' | 'published') | null;
|
||||
_status?: ("draft" | "published") | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
|
@ -548,9 +547,9 @@ export interface GenericContent {
|
|||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "chronology-items".
|
||||
* via the `definition` "chronology-events".
|
||||
*/
|
||||
export interface ChronologyItem {
|
||||
export interface ChronologyEvent {
|
||||
id: string;
|
||||
name?: string | null;
|
||||
date: {
|
||||
|
@ -559,6 +558,7 @@ export interface ChronologyItem {
|
|||
day?: number | null;
|
||||
};
|
||||
events: {
|
||||
sources?: (UrlBlock | CollectibleBlock | PageBlock)[] | null;
|
||||
translations: {
|
||||
language: string | Language;
|
||||
sourceLanguage: string | Language;
|
||||
|
@ -570,8 +570,8 @@ export interface ChronologyItem {
|
|||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
direction: ("ltr" | "rtl") | null;
|
||||
format: "left" | "start" | "center" | "right" | "end" | "justify" | "";
|
||||
indent: number;
|
||||
type: string;
|
||||
version: number;
|
||||
|
@ -585,8 +585,8 @@ export interface ChronologyItem {
|
|||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
direction: ("ltr" | "rtl") | null;
|
||||
format: "left" | "start" | "center" | "right" | "end" | "justify" | "";
|
||||
indent: number;
|
||||
type: string;
|
||||
version: number;
|
||||
|
@ -603,42 +603,63 @@ export interface ChronologyItem {
|
|||
updatedBy: string | Recorder;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
_status?: ('draft' | 'published') | null;
|
||||
_status?: ("draft" | "published") | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "chronology-eras".
|
||||
* via the `definition` "UrlBlock".
|
||||
*/
|
||||
export interface ChronologyEra {
|
||||
id: string;
|
||||
slug: string;
|
||||
startingYear: number;
|
||||
endingYear: number;
|
||||
translations?:
|
||||
| {
|
||||
language: string | Language;
|
||||
title: string;
|
||||
description?: {
|
||||
root: {
|
||||
children: {
|
||||
type: string;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
export interface UrlBlock {
|
||||
url: string;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: "urlBlock";
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "CollectibleBlock".
|
||||
*/
|
||||
export interface CollectibleBlock {
|
||||
collectible: string | Collectible;
|
||||
range?:
|
||||
| (
|
||||
| {
|
||||
page: number;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: "page";
|
||||
}
|
||||
| {
|
||||
timestamp: string;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: "timestamp";
|
||||
}
|
||||
| {
|
||||
translations: {
|
||||
language: string | Language;
|
||||
note: string;
|
||||
id?: string | null;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
indent: number;
|
||||
type: string;
|
||||
version: number;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: "other";
|
||||
}
|
||||
)[]
|
||||
| null;
|
||||
events?: (string | ChronologyItem)[] | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: "collectibleBlock";
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "PageBlock".
|
||||
*/
|
||||
export interface PageBlock {
|
||||
page: string | Page;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: "pageBlock";
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
|
@ -653,8 +674,8 @@ export interface Note {
|
|||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
direction: ("ltr" | "rtl") | null;
|
||||
format: "left" | "start" | "center" | "right" | "end" | "justify" | "";
|
||||
indent: number;
|
||||
type: string;
|
||||
version: number;
|
||||
|
@ -672,7 +693,7 @@ export interface Video {
|
|||
id: string;
|
||||
uid: string;
|
||||
gone: boolean;
|
||||
source: 'YouTube' | 'NicoNico' | 'Tumblr';
|
||||
source: "YouTube" | "NicoNico" | "Tumblr";
|
||||
title: string;
|
||||
description?: string | null;
|
||||
likes?: number | null;
|
||||
|
@ -708,7 +729,7 @@ export interface Wording {
|
|||
export interface PayloadPreference {
|
||||
id: string;
|
||||
user: {
|
||||
relationTo: 'recorders';
|
||||
relationTo: "recorders";
|
||||
value: string | Recorder;
|
||||
};
|
||||
key?: string | null;
|
||||
|
@ -764,15 +785,15 @@ export interface LineBlock {
|
|||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
direction: ("ltr" | "rtl") | null;
|
||||
format: "left" | "start" | "center" | "right" | "end" | "justify" | "";
|
||||
indent: number;
|
||||
type: string;
|
||||
version: number;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
};
|
||||
blockType: 'lineBlock';
|
||||
blockType: "lineBlock";
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
|
@ -786,15 +807,15 @@ export interface CueBlock {
|
|||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
direction: ("ltr" | "rtl") | null;
|
||||
format: "left" | "start" | "center" | "right" | "end" | "justify" | "";
|
||||
indent: number;
|
||||
type: string;
|
||||
version: number;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
};
|
||||
blockType: 'cueBlock';
|
||||
blockType: "cueBlock";
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
|
@ -804,17 +825,17 @@ export interface TranscriptBlock {
|
|||
lines: (LineBlock | CueBlock)[];
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'transcriptBlock';
|
||||
blockType: "transcriptBlock";
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "BreakBlock".
|
||||
*/
|
||||
export interface BreakBlock {
|
||||
type: 'Scene break' | 'Empty space' | 'Solid line' | 'Dotted line';
|
||||
type: "Scene break" | "Empty space" | "Solid line" | "Dotted line";
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'breakBlock';
|
||||
blockType: "breakBlock";
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
|
@ -828,8 +849,8 @@ export interface SectionBlock {
|
|||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
direction: ("ltr" | "rtl") | null;
|
||||
format: "left" | "start" | "center" | "right" | "end" | "justify" | "";
|
||||
indent: number;
|
||||
type: string;
|
||||
version: number;
|
||||
|
@ -838,17 +859,18 @@ export interface SectionBlock {
|
|||
};
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'sectionBlock';
|
||||
blockType: "sectionBlock";
|
||||
}
|
||||
|
||||
|
||||
declare module "payload" {
|
||||
export interface GeneratedTypes extends Config {}
|
||||
}
|
||||
|
||||
/////////////// CONSTANTS ///////////////
|
||||
|
||||
|
||||
export enum Collections {
|
||||
ChronologyEras = "chronology-eras",
|
||||
ChronologyItems = "chronology-items",
|
||||
ChronologyEvents = "chronology-events",
|
||||
Currencies = "currencies",
|
||||
Files = "files",
|
||||
Languages = "languages",
|
||||
|
@ -1334,7 +1356,6 @@ export type EndpointPagePreview = {
|
|||
title: string;
|
||||
subtitle?: string;
|
||||
}[];
|
||||
status: "draft" | "published";
|
||||
};
|
||||
|
||||
export type EndpointPage = EndpointPagePreview & {
|
||||
|
@ -1368,7 +1389,6 @@ export type EndpointCollectiblePreview = {
|
|||
description?: RichTextContent;
|
||||
}[];
|
||||
tagGroups: EndpointTagsGroup[];
|
||||
status: "draft" | "published";
|
||||
releaseDate?: string;
|
||||
languages: string[];
|
||||
};
|
||||
|
@ -1441,6 +1461,40 @@ export type TableOfContentEntry = {
|
|||
children: TableOfContentEntry[];
|
||||
};
|
||||
|
||||
export type EndpointChronologyEvent = {
|
||||
id: string;
|
||||
date: {
|
||||
year: number;
|
||||
month?: number;
|
||||
day?: number;
|
||||
};
|
||||
events: {
|
||||
sources: EndpointSource[];
|
||||
translations: {
|
||||
language: string;
|
||||
sourceLanguage: string;
|
||||
title?: string;
|
||||
description?: RichTextContent;
|
||||
notes?: RichTextContent;
|
||||
transcribers: EndpointRecorder[];
|
||||
translators: EndpointRecorder[];
|
||||
proofreaders: EndpointRecorder[];
|
||||
}[];
|
||||
}[];
|
||||
};
|
||||
|
||||
export type EndpointSource =
|
||||
| { type: "url"; url: string; label: string }
|
||||
| {
|
||||
type: "collectible";
|
||||
collectible: EndpointCollectiblePreview;
|
||||
range?:
|
||||
| { type: "page"; page: number }
|
||||
| { type: "timestamp"; timestamp: string }
|
||||
| { type: "custom"; translations: { language: string; note: string }[] };
|
||||
}
|
||||
| { type: "page"; page: EndpointPagePreview };
|
||||
|
||||
export type PayloadImage = {
|
||||
url: string;
|
||||
width: number;
|
||||
|
@ -1450,8 +1504,6 @@ export type PayloadImage = {
|
|||
};
|
||||
|
||||
export const payload = {
|
||||
getEras: async (): Promise<EndpointEra[]> =>
|
||||
await (await request(payloadApiUrl(Collections.ChronologyEras, `all`))).json(),
|
||||
getHomeFolders: async (): Promise<EndpointHomeFolder[]> =>
|
||||
await (await request(payloadApiUrl(Collections.HomeFolders, `all`, true))).json(),
|
||||
getFolder: async (slug: string): Promise<EndpointFolder> =>
|
||||
|
@ -1468,4 +1520,8 @@ export const payload = {
|
|||
await (await request(payloadApiUrl(Collections.Pages, `slug/${slug}`))).json(),
|
||||
getCollectible: async (slug: string): Promise<EndpointCollectible> =>
|
||||
await (await request(payloadApiUrl(Collections.Collectibles, `slug/${slug}`))).json(),
|
||||
getChronologyEvents: async (): Promise<EndpointChronologyEvent[]> =>
|
||||
await (await request(payloadApiUrl(Collections.ChronologyEvents, `all`))).json(),
|
||||
getChronologyEventByID: async (id: string): Promise<EndpointChronologyEvent> =>
|
||||
await (await request(payloadApiUrl(Collections.ChronologyEvents, id))).json(),
|
||||
};
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
export const groupBy = <K, T>(array: T[], getKey: (item: T) => K): { key: K; values: T[] }[] => {
|
||||
const map = new Map<K, T[]>();
|
||||
array.forEach((item) => {
|
||||
const key = getKey(item);
|
||||
const currentValueInMap = map.get(key) ?? [];
|
||||
currentValueInMap.push(item);
|
||||
map.set(key, currentValueInMap);
|
||||
});
|
||||
|
||||
return [...map.entries()].map(([key, values]) => ({ key, values }));
|
||||
};
|
|
@ -47,3 +47,9 @@ export const formatRichTextToString = (content: RichTextContent): string => {
|
|||
|
||||
return content.root.children.map(formatNode).join("\n\n");
|
||||
};
|
||||
|
||||
export const capitalize = (string: string): string => {
|
||||
const [firstLetter, ...otherLetters] = string;
|
||||
if (firstLetter === undefined) return "";
|
||||
return [firstLetter.toUpperCase(), ...otherLetters].join("");
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue