feat: Added new artist features (#235)

* Added artist information, notes & multiple covers
* Added Markdown for artist information, improved design of artist notes and fixed types.
* Added Markdown for anime synopsis and better error handling.
This commit is contained in:
Kyrch
2025-04-12 03:08:40 -03:00
committed by GitHub
parent 2023d85579
commit 49f64509ba
12 changed files with 304 additions and 54 deletions
+55 -1
View File
@@ -5,8 +5,12 @@ import gql from "graphql-tag";
import { LogoPlaceholder } from "@/components/image/LogoPlaceholder";
import { AspectRatio } from "@/components/utils/AspectRatio";
import type { MultiCoverImageResourceWithImagesFragment } from "@/generated/graphql";
import type {
MultiCoverImageResourceWithImagesFragment,
MultiLargeCoverImageResourceWithImagesFragment,
} from "@/generated/graphql";
import extractImages from "@/utils/extractImages";
import extractMultipleImages from "@/utils/extractMultipleImages";
function getTranslationX(item: number, itemCount: number) {
switch (itemCount) {
@@ -174,3 +178,53 @@ MultiCoverImage.fragments = {
}
`,
};
interface MultiLargeCoverImageProps extends ComponentPropsWithoutRef<typeof StyledCover> {
resourceWithImages: MultiLargeCoverImageResourceWithImagesFragment;
}
export function MultiLargeCoverImage({ resourceWithImages, ...props }: MultiLargeCoverImageProps) {
const images = extractMultipleImages(resourceWithImages).slice(0, 4);
return (
<AspectRatio $ratio={2 / 3}>
<StyledCoverContainer>
{images.length ? (
images.map(({ link }) => (
<StyledCoverItemContainer key={link} $itemCount={images.length}>
<StyledCover
loading="lazy"
src={link}
alt={`Cover image of ${resourceWithImages.name}`}
title={resourceWithImages.name}
style={{ backgroundImage: `url(${link})` }}
{...props}
/>
</StyledCoverItemContainer>
))
) : (
<LogoPlaceholder {...props} />
)}
</StyledCoverContainer>
</AspectRatio>
);
}
MultiLargeCoverImage.fragments = {
resourceWithImages: gql`
${extractMultipleImages.fragments.resourceWithImages}
fragment MultiLargeCoverImageResourceWithImages on ResourceWithImages {
... on Anime {
name
}
... on Artist {
name
}
... on Studio {
name
}
...extractMultipleImagesResourceWithImages
}
`,
};
+6
View File
@@ -83,6 +83,7 @@ export type Artist = ResourceWithImages & {
groups: Array<ArtistMembership>;
id: Scalars["Int"]["output"];
images: Array<Image>;
information?: Maybe<Scalars["String"]["output"]>;
members: Array<ArtistMembership>;
name: Scalars["String"]["output"];
performances: Array<Performance>;
@@ -96,6 +97,7 @@ export type ArtistMembership = {
as?: Maybe<Scalars["String"]["output"]>;
group: Artist;
member: Artist;
notes?: Maybe<Scalars["String"]["output"]>;
};
export type ArtistSearchResult = EntitySearchResult & {
@@ -204,6 +206,7 @@ export type GlobalSearchResult = {
export type Image = {
__typename?: "Image";
depth?: Maybe<Scalars["Int"]["output"]>;
facet?: Maybe<Scalars["String"]["output"]>;
id: Scalars["Int"]["output"];
link: Scalars["String"]["output"];
@@ -883,6 +886,7 @@ export type ArtistResolvers<
groups?: Resolver<Array<ResolversTypes["ArtistMembership"]>, ParentType, ContextType>;
id?: Resolver<ResolversTypes["Int"], ParentType, ContextType>;
images?: Resolver<Array<ResolversTypes["Image"]>, ParentType, ContextType>;
information?: Resolver<Maybe<ResolversTypes["String"]>, ParentType, ContextType>;
members?: Resolver<Array<ResolversTypes["ArtistMembership"]>, ParentType, ContextType>;
name?: Resolver<ResolversTypes["String"], ParentType, ContextType>;
performances?: Resolver<Array<ResolversTypes["Performance"]>, ParentType, ContextType>;
@@ -899,6 +903,7 @@ export type ArtistMembershipResolvers<
as?: Resolver<Maybe<ResolversTypes["String"]>, ParentType, ContextType>;
group?: Resolver<ResolversTypes["Artist"], ParentType, ContextType>;
member?: Resolver<ResolversTypes["Artist"], ParentType, ContextType>;
notes?: Resolver<Maybe<ResolversTypes["String"]>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
@@ -1048,6 +1053,7 @@ export type ImageResolvers<
ContextType = any,
ParentType extends ResolversParentTypes["Image"] = ResolversParentTypes["Image"],
> = {
depth?: Resolver<Maybe<ResolversTypes["Int"]>, ParentType, ContextType>;
facet?: Resolver<Maybe<ResolversTypes["String"]>, ParentType, ContextType>;
id?: Resolver<ResolversTypes["Int"], ParentType, ContextType>;
link?: Resolver<ResolversTypes["String"], ParentType, ContextType>;
+67 -6
View File
@@ -44,6 +44,7 @@ export type Artist = ResourceWithImages & {
groups: Array<ArtistMembership>;
id: Scalars["Int"]["output"];
images: Array<Image>;
information: Maybe<Scalars["String"]["output"]>;
members: Array<ArtistMembership>;
name: Scalars["String"]["output"];
performances: Array<Performance>;
@@ -56,6 +57,7 @@ export type ArtistMembership = {
as: Maybe<Scalars["String"]["output"]>;
group: Artist;
member: Artist;
notes: Maybe<Scalars["String"]["output"]>;
};
export type ArtistSearchResult = EntitySearchResult & {
@@ -153,6 +155,7 @@ export type GlobalSearchResult = {
};
export type Image = {
depth: Maybe<Scalars["Int"]["output"]>;
facet: Maybe<Scalars["String"]["output"]>;
id: Scalars["Int"]["output"];
link: Scalars["String"]["output"];
@@ -963,6 +966,26 @@ export type MultiCoverImageResourceWithImagesFragment =
| MultiCoverImageResourceWithImages_Artist_Fragment
| MultiCoverImageResourceWithImages_Studio_Fragment;
type MultiLargeCoverImageResourceWithImages_Anime_Fragment = {
name: string;
images: Array<{ link: string; depth: number | null; facet: string | null }>;
};
type MultiLargeCoverImageResourceWithImages_Artist_Fragment = {
name: string;
images: Array<{ link: string; depth: number | null; facet: string | null }>;
};
type MultiLargeCoverImageResourceWithImages_Studio_Fragment = {
name: string;
images: Array<{ link: string; depth: number | null; facet: string | null }>;
};
export type MultiLargeCoverImageResourceWithImagesFragment =
| MultiLargeCoverImageResourceWithImages_Anime_Fragment
| MultiLargeCoverImageResourceWithImages_Artist_Fragment
| MultiLargeCoverImageResourceWithImages_Studio_Fragment;
export type ProfileImageUserFragment = { name: string; email: string };
export type StudioCoverImageStudioFragment = {
@@ -1807,6 +1830,7 @@ export type RevalidateAnimeQuery = {
export type ArtistDetailPageArtistFragment = {
slug: string;
information: string | null;
name: string;
performances: Array<{
alias: string | null;
@@ -1858,10 +1882,16 @@ export type ArtistDetailPageArtistFragment = {
}>;
};
}>;
members: Array<{ alias: string | null; as: string | null; member: { slug: string; name: string } }>;
members: Array<{
alias: string | null;
as: string | null;
notes: string | null;
member: { slug: string; name: string; images: Array<{ link: string; facet: string | null }> };
}>;
groups: Array<{
alias: string | null;
as: string | null;
notes: string | null;
group: {
slug: string;
name: string;
@@ -1922,7 +1952,7 @@ export type ArtistDetailPageArtistFragment = {
};
}>;
resources: Array<{ link: string | null; site: string | null; as: string | null }>;
images: Array<{ facet: string | null; link: string }>;
images: Array<{ link: string; depth: number | null; facet: string | null }>;
};
export type ArtistDetailPageQueryVariables = Exact<{
@@ -1932,6 +1962,7 @@ export type ArtistDetailPageQueryVariables = Exact<{
export type ArtistDetailPageQuery = {
artist: {
slug: string;
information: string | null;
name: string;
performances: Array<{
alias: string | null;
@@ -1987,10 +2018,16 @@ export type ArtistDetailPageQuery = {
}>;
};
}>;
members: Array<{ alias: string | null; as: string | null; member: { slug: string; name: string } }>;
members: Array<{
alias: string | null;
as: string | null;
notes: string | null;
member: { slug: string; name: string; images: Array<{ link: string; facet: string | null }> };
}>;
groups: Array<{
alias: string | null;
as: string | null;
notes: string | null;
group: {
slug: string;
name: string;
@@ -2051,7 +2088,7 @@ export type ArtistDetailPageQuery = {
};
}>;
resources: Array<{ link: string | null; site: string | null; as: string | null }>;
images: Array<{ facet: string | null; link: string }>;
images: Array<{ link: string; depth: number | null; facet: string | null }>;
} | null;
};
@@ -2060,6 +2097,7 @@ export type ArtistDetailPageAllQueryVariables = Exact<{ [key: string]: never }>;
export type ArtistDetailPageAllQuery = {
artistAll: Array<{
slug: string;
information: string | null;
name: string;
performances: Array<{
alias: string | null;
@@ -2115,10 +2153,16 @@ export type ArtistDetailPageAllQuery = {
}>;
};
}>;
members: Array<{ alias: string | null; as: string | null; member: { slug: string; name: string } }>;
members: Array<{
alias: string | null;
as: string | null;
notes: string | null;
member: { slug: string; name: string; images: Array<{ link: string; facet: string | null }> };
}>;
groups: Array<{
alias: string | null;
as: string | null;
notes: string | null;
group: {
slug: string;
name: string;
@@ -2179,7 +2223,7 @@ export type ArtistDetailPageAllQuery = {
};
}>;
resources: Array<{ link: string | null; site: string | null; as: string | null }>;
images: Array<{ facet: string | null; link: string }>;
images: Array<{ link: string; depth: number | null; facet: string | null }>;
}>;
};
@@ -3045,3 +3089,20 @@ export type ExtractImagesResourceWithImagesFragment =
| ExtractImagesResourceWithImages_Anime_Fragment
| ExtractImagesResourceWithImages_Artist_Fragment
| ExtractImagesResourceWithImages_Studio_Fragment;
type ExtractMultipleImagesResourceWithImages_Anime_Fragment = {
images: Array<{ link: string; depth: number | null; facet: string | null }>;
};
type ExtractMultipleImagesResourceWithImages_Artist_Fragment = {
images: Array<{ link: string; depth: number | null; facet: string | null }>;
};
type ExtractMultipleImagesResourceWithImages_Studio_Fragment = {
images: Array<{ link: string; depth: number | null; facet: string | null }>;
};
export type ExtractMultipleImagesResourceWithImagesFragment =
| ExtractMultipleImagesResourceWithImages_Anime_Fragment
| ExtractMultipleImagesResourceWithImages_Artist_Fragment
| ExtractMultipleImagesResourceWithImages_Studio_Fragment;
+10 -7
View File
@@ -251,13 +251,16 @@ const resolvers: Resolvers = {
}),
(resources) => resources.map(({ artistresource, ...resource }) => ({ ...artistresource, ...resource })),
),
images: createApiResolverNotNull<ApiArtistShow<"images">>()({
extractFromParent: (artist) => artist.images,
endpoint: (artist) => `/artist/${artist.slug}`,
extractFromResponse: (response) => response.artist.images,
type: "Artist",
baseInclude: INCLUDES.Artist.images,
}),
images: transformedResolver(
createApiResolverNotNull<ApiArtistShow<"images">>()({
extractFromParent: (artist) => artist.images,
endpoint: (artist) => `/artist/${artist.slug}`,
extractFromResponse: (response) => response.artist.images,
type: "Artist",
baseInclude: INCLUDES.Artist.images,
}),
(images) => images.map(({ artistimage, ...image }) => ({ ...artistimage, ...image })),
),
groups: transformedResolver(
createApiResolverNotNull<ApiArtistShow<"groups">>()({
extractFromParent: (artist) => artist.groups,
+3
View File
@@ -165,6 +165,7 @@ const typeDefs = gql`
type Artist implements ResourceWithImages {
id: Int!
information: String
slug: String!
name: String!
performances: [Performance!]!
@@ -179,6 +180,7 @@ const typeDefs = gql`
member: Artist!
alias: String
as: String
notes: String
}
type Series {
@@ -201,6 +203,7 @@ const typeDefs = gql`
path: String!
size: Int!
mimetype: String!
depth: Int
facet: String
link: String!
}
+10 -1
View File
@@ -54,6 +54,7 @@ export interface ApiAnnouncement {
export interface ApiArtist {
id: number;
information: string | null;
name: string;
slug: string;
songs?: Array<
@@ -66,7 +67,11 @@ export interface ApiArtist {
artistresource: ApiArtistResource;
}
>;
images?: Array<ApiImage>;
images?: Array<
ApiImage & {
artistimage: ApiArtistImage;
}
>;
groups?: Array<
ApiArtist & {
artistmember: ApiArtistMember;
@@ -221,6 +226,10 @@ export interface ApiAnimeResource {
as: string | null;
}
export interface ApiArtistImage {
depth: number | null;
}
export interface ApiArtistMember {
alias: string | null;
as: string | null;
+2 -2
View File
@@ -19,7 +19,7 @@ import fetchStaticPaths from "@/utils/fetchStaticPaths";
import type { SharedPageProps } from "@/utils/getSharedPageProps";
import getSharedPageProps from "@/utils/getSharedPageProps";
import type { Heading } from "@/utils/rehypeExtractHeadings";
import serializeMarkdown from "@/utils/serializeMarkdown";
import { serializeMarkdownSafe } from "@/utils/serializeMarkdown";
import type { RequiredNonNullable } from "@/utils/types";
const StyledGrid = styled.div`
@@ -128,7 +128,7 @@ export const getStaticProps: GetStaticProps<DocumentPageProps, DocumentPageParam
...getSharedPageProps(apiRequests),
page: {
...data.page,
...(await serializeMarkdown(data.page.body)),
...(await serializeMarkdownSafe(data.page.body)),
},
},
// Revalidate after 1 hour (= 3600 seconds).
+15 -9
View File
@@ -1,7 +1,8 @@
import { useState } from "react";
import React, { useState } from "react";
import styled from "styled-components";
import type { GetStaticPaths, GetStaticProps } from "next";
import Link from "next/link";
import type { MDXRemoteSerializeResult } from "next-mdx-remote";
import gql from "graphql-tag";
import type { ParsedUrlQuery } from "querystring";
@@ -14,6 +15,7 @@ import { DescriptionList } from "@/components/description-list/DescriptionList";
import { ExternalLink } from "@/components/external-link/ExternalLink";
import { AnimeThemeFilter } from "@/components/filter/AnimeThemeFilter";
import { CoverImage } from "@/components/image/CoverImage";
import { Markdown } from "@/components/markdown/Markdown";
import { SEO } from "@/components/seo/SEO";
import { Text } from "@/components/text/Text";
import { HeightTransition } from "@/components/utils/HeightTransition";
@@ -30,6 +32,7 @@ import extractImages from "@/utils/extractImages";
import fetchStaticPaths from "@/utils/fetchStaticPaths";
import type { SharedPageProps } from "@/utils/getSharedPageProps";
import getSharedPageProps from "@/utils/getSharedPageProps";
import { serializeMarkdownSafe } from "@/utils/serializeMarkdown";
import type { RequiredNonNullable } from "@/utils/types";
const StyledList = styled.div`
@@ -40,13 +43,15 @@ const StyledList = styled.div`
text-align: center;
`;
interface AnimeDetailPageProps extends SharedPageProps, RequiredNonNullable<AnimeDetailPageQuery> {}
interface AnimeDetailPageProps extends SharedPageProps, RequiredNonNullable<AnimeDetailPageQuery> {
synopsisMarkdownSource: MDXRemoteSerializeResult | null;
}
interface AnimeDetailPageParams extends ParsedUrlQuery {
animeSlug: string;
}
export default function AnimeDetailPage({ anime }: AnimeDetailPageProps) {
export default function AnimeDetailPage({ anime, synopsisMarkdownSource }: AnimeDetailPageProps) {
const [collapseSynopsis, setCollapseSynopsis] = useState(true);
const { largeCover } = extractImages(anime);
@@ -118,16 +123,14 @@ export default function AnimeDetailPage({ anime }: AnimeDetailPageProps) {
</DescriptionList>
</Column>
<Column style={{ "--gap": "24px" }}>
{!!anime.synopsis && (
{!!synopsisMarkdownSource && (
<>
<Text variant="h2">Synopsis</Text>
<Card $hoverable onClick={() => setCollapseSynopsis(!collapseSynopsis)}>
<HeightTransition>
<Text
as="p"
maxLines={collapseSynopsis ? 2 : undefined}
dangerouslySetInnerHTML={{ __html: anime.synopsis }}
/>
<Text as="div" maxLines={collapseSynopsis ? 2 : undefined}>
<Markdown source={synopsisMarkdownSource} />
</Text>
</HeightTransition>
</Card>
</>
@@ -214,6 +217,9 @@ export const getStaticProps: GetStaticProps<AnimeDetailPageProps, AnimeDetailPag
props: {
...getSharedPageProps(apiRequests),
anime: data.anime,
synopsisMarkdownSource: data.anime.synopsis
? (await serializeMarkdownSafe(data.anime.synopsis)).source
: null,
},
// Revalidate after 1 hour (= 3600 seconds).
revalidate: 3600,
+69 -26
View File
@@ -1,9 +1,8 @@
import React, { useCallback } from "react";
import { memo, useMemo, useState } from "react";
import { useContext } from "react";
import React, { memo, useCallback, useContext, useMemo, useState } from "react";
import styled from "styled-components";
import type { GetStaticPaths, GetStaticProps } from "next";
import Link from "next/link";
import type { MDXRemoteSerializeResult } from "next-mdx-remote";
import gql from "graphql-tag";
import { uniq } from "lodash-es";
@@ -11,18 +10,22 @@ import type { ParsedUrlQuery } from "querystring";
import { Column } from "@/components/box/Flex";
import { FilterToggleButton } from "@/components/button/FilterToggleButton";
import { ArtistSummaryCard } from "@/components/card/ArtistSummaryCard";
import { Card } from "@/components/card/Card";
import { ThemeSummaryCard } from "@/components/card/ThemeSummaryCard";
import { SidebarContainer } from "@/components/container/SidebarContainer";
import { DescriptionList } from "@/components/description-list/DescriptionList";
import { ExternalLink } from "@/components/external-link/ExternalLink";
import { CoverImage } from "@/components/image/CoverImage";
import { MultiLargeCoverImage } from "@/components/image/MultiCoverImage";
import { Listbox, ListboxOption } from "@/components/listbox/Listbox";
import { Markdown } from "@/components/markdown/Markdown";
import { SearchFilter } from "@/components/search-filter/SearchFilter";
import { SearchFilterGroup } from "@/components/search-filter/SearchFilterGroup";
import { SearchFilterSortBy } from "@/components/search-filter/SearchFilterSortBy";
import { SEO } from "@/components/seo/SEO";
import { Text } from "@/components/text/Text";
import { Collapse } from "@/components/utils/Collapse";
import { HeightTransition } from "@/components/utils/HeightTransition";
import PlayerContext, { createWatchListItem } from "@/context/playerContext";
import type {
ArtistDetailPageAllQuery,
@@ -44,9 +47,10 @@ import {
SONG_Z_A,
SONG_Z_A_ANIME,
} from "@/utils/comparators";
import extractImages from "@/utils/extractImages";
import extractMultipleImages from "@/utils/extractMultipleImages";
import fetchStaticPaths from "@/utils/fetchStaticPaths";
import getSharedPageProps from "@/utils/getSharedPageProps";
import { serializeMarkdownSafe } from "@/utils/serializeMarkdown";
import type { RequiredNonNullable } from "@/utils/types";
const StyledList = styled.div`
@@ -76,14 +80,17 @@ function getPerformanceFilter(key: string | null): (performance: Performance) =>
}
}
type ArtistDetailPageProps = RequiredNonNullable<ArtistDetailPageQuery>;
type ArtistDetailPageProps = RequiredNonNullable<ArtistDetailPageQuery> & {
informationMarkdownSource: MDXRemoteSerializeResult | null;
};
interface ArtistDetailPageParams extends ParsedUrlQuery {
artistSlug: string;
}
export default function ArtistDetailPage({ artist }: ArtistDetailPageProps) {
const { largeCover } = extractImages(artist);
export default function ArtistDetailPage({ artist, informationMarkdownSource }: ArtistDetailPageProps) {
const images = extractMultipleImages(artist);
const [collapseInformation, setCollapseInformation] = useState(true);
const characters = useMemo(
() =>
@@ -183,11 +190,11 @@ export default function ArtistDetailPage({ artist }: ArtistDetailPageProps) {
return (
<>
<SEO title={artist.name} image={largeCover} />
<SEO title={artist.name} image={images[0]?.link} />
<Text variant="h1">{artist.name}</Text>
<SidebarContainer>
<Column style={{ "--gap": "24px" }}>
<CoverImage resourceWithImages={artist} alt={`Picture of ${artist.name}`} />
<MultiLargeCoverImage resourceWithImages={artist} />
<DescriptionList>
{aliases.length > 0 && (
<DescriptionList.Item title="Aliases">
@@ -203,12 +210,19 @@ export default function ArtistDetailPage({ artist }: ArtistDetailPageProps) {
{!!artist.members?.length && (
<DescriptionList.Item title="Members">
<StyledList>
{artist.members.map(({ member, alias, as }) => (
<Text key={member.slug} as={Link} href={`/artist/${member.slug}`} link>
{member.name}
{alias ? ` (as ${alias})` : null}
{as ? ` (as ${as})` : null}
</Text>
{artist.members.map(({ member, alias, as, notes }) => (
<Column key={member.slug}>
<Text as={Link} href={`/artist/${member.slug}`} link>
{alias ? alias : member.name}
</Text>
{as || notes ? (
<Text variant="small" color="text-muted">
{[as ? `As ${as}` : null, notes || null]
.filter(Boolean)
.join(" • ")}
</Text>
) : null}
</Column>
))}
</StyledList>
</DescriptionList.Item>
@@ -216,12 +230,23 @@ export default function ArtistDetailPage({ artist }: ArtistDetailPageProps) {
{!!artist.groups?.length && (
<DescriptionList.Item title="Member of">
<StyledList>
{artist.groups.map(({ group, alias, as }) => (
<Text key={group.slug} as={Link} href={`/artist/${group.slug}`} link>
{group.name}
{alias ? ` (as ${alias})` : null}
{as ? ` (as ${as})` : null}
</Text>
{artist.groups.map(({ group, alias, as, notes }) => (
<Column key={group.slug}>
<Text as={Link} href={`/artist/${group.slug}`} link>
{group.name}
</Text>
{alias || as || notes ? (
<Text variant="small" color="text-muted">
{[
alias ? `As ${alias}` : null,
as ? `As ${as}` : null,
notes || null,
]
.filter(Boolean)
.join(" • ")}
</Text>
) : null}
</Column>
))}
</StyledList>
</DescriptionList.Item>
@@ -243,6 +268,18 @@ export default function ArtistDetailPage({ artist }: ArtistDetailPageProps) {
</DescriptionList>
</Column>
<Column style={{ "--gap": "24px" }}>
{!!informationMarkdownSource && (
<>
<Text variant="h2">Information</Text>
<Card $hoverable onClick={() => setCollapseInformation(!collapseInformation)}>
<HeightTransition>
<Text as="div" maxLines={collapseInformation ? 2 : undefined}>
<Markdown source={informationMarkdownSource} />
</Text>
</HeightTransition>
</Card>
</>
)}
<StyledHeader>
<Text variant="h2">
Song Performances
@@ -386,10 +423,14 @@ ArtistDetailPage.fragements = {
${ThemeSummaryCard.fragments.theme}
${ThemeSummaryCard.fragments.artist}
${ThemeSummaryCard.fragments.expandable}
${ArtistSummaryCard.fragments.artist}
${MultiLargeCoverImage.fragments.resourceWithImages}
fragment ArtistDetailPageArtist on Artist {
...ThemeSummaryCardArtist
...MultiLargeCoverImageResourceWithImages
slug
information
name
performances {
alias
@@ -423,11 +464,13 @@ ArtistDetailPage.fragements = {
}
members {
member {
...ArtistSummaryCardArtist
slug
name
}
alias
as
notes
}
groups {
group {
@@ -466,16 +509,13 @@ ArtistDetailPage.fragements = {
}
alias
as
notes
}
resources {
link
site
as
}
images {
facet
link
}
}
`,
};
@@ -511,6 +551,9 @@ export const getStaticProps: GetStaticProps<ArtistDetailPageProps, ArtistDetailP
props: {
...getSharedPageProps(apiRequests),
artist: data.artist,
informationMarkdownSource: data.artist.information
? (await serializeMarkdownSafe(data.artist.information)).source
: null,
},
// Revalidate after 1 hour (= 3600 seconds).
revalidate: 3600,
+2 -2
View File
@@ -34,7 +34,7 @@ import useCurrentSeason from "@/hooks/useCurrentSeason";
import { fetchData } from "@/lib/server";
import theme from "@/theme";
import getSharedPageProps from "@/utils/getSharedPageProps";
import serializeMarkdown from "@/utils/serializeMarkdown";
import { serializeMarkdownSafe } from "@/utils/serializeMarkdown";
const BigButton = styled(Button)`
justify-content: flex-end;
@@ -246,7 +246,7 @@ export const getStaticProps: GetStaticProps<HomePageProps> = async () => {
featuredTheme: data?.featuredTheme?.entry?.theme ?? null,
announcementSources: await Promise.all(
data?.announcementAll.map(
async (announcement) => (await serializeMarkdown(announcement.content)).source,
async (announcement) => (await serializeMarkdownSafe(announcement.content)).source,
),
),
},
+52
View File
@@ -0,0 +1,52 @@
import type { ASTNode } from "graphql/index";
import gql from "graphql-tag";
import type { ExtractMultipleImagesResourceWithImagesFragment } from "@/generated/graphql";
interface ExtractMultiplesImagesResult {
link: string;
depth: number | null;
facet: string | null;
}
interface ExtractMultipleImages {
(resourceWithImages?: ExtractMultipleImagesResourceWithImagesFragment | null): Array<ExtractMultiplesImagesResult>;
fragments: {
resourceWithImages: ASTNode;
};
}
const extractMultipleImages: ExtractMultipleImages = (resourceWithImages) => {
if (resourceWithImages) {
const largeCovers = resourceWithImages.images.filter((image) => image.facet === "Large Cover");
return largeCovers.sort((a, b) => {
if (a.depth && b.depth) {
return a.depth - b.depth;
}
if (a.depth) {
return -1;
}
if (b.depth) {
return 1;
}
return 0;
});
}
return [];
};
export default extractMultipleImages;
extractMultipleImages.fragments = {
resourceWithImages: gql`
fragment extractMultipleImagesResourceWithImages on ResourceWithImages {
images {
link
depth
facet
}
}
`,
};
+13
View File
@@ -1,5 +1,6 @@
import { serialize } from "next-mdx-remote/serialize";
import { isObject } from "lodash-es";
import rehypePrettyCode from "rehype-pretty-code";
import rehypeSlug from "rehype-slug";
import remarkGfm from "remark-gfm";
@@ -28,3 +29,15 @@ export default async function serializeMarkdown(markdown: Compatible) {
headings,
};
}
export async function serializeMarkdownSafe(markdown: Compatible) {
try {
return await serializeMarkdown(markdown);
} catch (error) {
if (!isObject(error) || !("message" in error) || typeof error.message !== "string") {
throw error;
}
return await serializeMarkdown(`*Failed to parse Markdown!*\n\n\`\`\`\n${error.message}\n\`\`\``);
}
}