import { AppLayout } from "components/AppLayout"; import { Chip } from "components/Chip"; import { HorizontalLine } from "components/HorizontalLine"; import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs"; import { Markdawn, TableOfContents } from "components/Markdown/Markdawn"; import { ReturnButton, ReturnButtonType, } from "components/PanelComponents/ReturnButton"; import { ContentPanel } from "components/Panels/ContentPanel"; import { SubPanel } from "components/Panels/SubPanel"; import { PreviewCard } from "components/PreviewCard"; import { TranslatedPreviewLine } from "components/PreviewLine"; import { RecorderChip } from "components/RecorderChip"; import { ThumbnailHeader } from "components/ThumbnailHeader"; import { ToolTip } from "components/ToolTip"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { getReadySdk } from "graphql/sdk"; import { getDescription } from "helpers/description"; import { prettyinlineTitle, prettyLanguage, prettyItemSubType, prettySlug, } from "helpers/formatters"; import { isUntangibleGroupItem } from "helpers/libraryItem"; import { filterDefined, filterHasAttributes, getStatusDescription, isDefinedAndNotEmpty, } from "helpers/others"; import { ContentWithTranslations } from "helpers/types"; import { useMediaMobile } from "hooks/useMediaQuery"; import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange"; import { useSmartLanguage } from "hooks/useSmartLanguage"; import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; import { Fragment, useCallback, useMemo } from "react"; /* * ╭────────╮ * ──────────────────────────────────────────╯ PAGE ╰───────────────────────────────────────────── */ interface Props extends AppStaticProps { content: ContentWithTranslations; } const Content = ({ langui, content, languages, currencies, ...otherProps }: Props): JSX.Element => { const isMobile = useMediaMobile(); const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({ items: content.translations, languages: languages, languageExtractor: useCallback( (item: NonNullable) => item.language?.data?.attributes?.code, [] ), }); useScrollTopOnChange(AnchorIds.ContentPanel, [selectedTranslation]); const { previousContent, nextContent } = useMemo( () => ({ previousContent: content.group?.data?.attributes?.contents ? getPreviousContent( content.group.data.attributes.contents.data, content.slug ) : undefined, nextContent: content.group?.data?.attributes?.contents ? getNextContent( content.group.data.attributes.contents.data, content.slug ) : undefined, }), [content.group, content.slug] ); const subPanel = useMemo( () => ( {selectedTranslation?.text_set?.source_language?.data?.attributes ?.code !== undefined && (

{selectedTranslation.text_set.source_language.data.attributes .code === selectedTranslation.language?.data?.attributes?.code ? langui.transcript_notice : langui.translation_notice}

{selectedTranslation.text_set.source_language.data.attributes .code !== selectedTranslation.language?.data?.attributes?.code && (

{langui.source_language}:

{prettyLanguage( selectedTranslation.text_set.source_language.data.attributes .code, languages )}
)}

{langui.status}:

{selectedTranslation.text_set.status}
{selectedTranslation.text_set.transcribers && selectedTranslation.text_set.transcribers.data.length > 0 && (

{langui.transcribers}:

{filterHasAttributes( selectedTranslation.text_set.transcribers.data ).map((recorder) => ( ))}
)} {selectedTranslation.text_set.translators && selectedTranslation.text_set.translators.data.length > 0 && (

{langui.translators}:

{filterHasAttributes( selectedTranslation.text_set.translators.data ).map((recorder) => ( ))}
)} {selectedTranslation.text_set.proofreaders && selectedTranslation.text_set.proofreaders.data.length > 0 && (

{langui.proofreaders}:

{filterHasAttributes( selectedTranslation.text_set.proofreaders.data ).map((recorder) => ( ))}
)} {isDefinedAndNotEmpty(selectedTranslation.text_set.notes) && (

{"Notes"}:

)}
)} {content.ranged_contents?.data && content.ranged_contents.data.length > 0 && ( <>

{langui.source}

{content.ranged_contents.data.map((rangedContent) => { const libraryItem = rangedContent.attributes?.library_item?.data; if (libraryItem?.attributes && libraryItem.id) { return (
0 && libraryItem.attributes.metadata[0] ? [ prettyItemSubType( libraryItem.attributes.metadata[0] ), ] : [] } bottomChips={libraryItem.attributes.categories?.data.map( (category) => category.attributes?.short ?? "" )} metadata={{ currencies: currencies, release_date: libraryItem.attributes.release_date, price: libraryItem.attributes.price, position: "Bottom", }} infoAppend={ !isUntangibleGroupItem( libraryItem.attributes.metadata?.[0] ) && ( ) } />
); } return <>; })}
)} {selectedTranslation?.text_set?.text && ( <> )}
), [ content.ranged_contents?.data, currencies, languages, langui, selectedTranslation, ] ); const contentPanel = useMemo( () => (
} /> {previousContent?.attributes && (

{langui.previous_content}

({ pre_title: translation.pre_title, title: translation.title, subtitle: translation.subtitle, language: translation.language?.data?.attributes?.code, }))} slug={previousContent.attributes.slug} languages={languages} thumbnail={ previousContent.attributes.thumbnail?.data?.attributes } thumbnailAspectRatio="3/2" topChips={ isMobile ? undefined : previousContent.attributes.type?.data?.attributes ? [ previousContent.attributes.type.data.attributes .titles?.[0] ? previousContent.attributes.type.data.attributes .titles[0]?.title : prettySlug( previousContent.attributes.type.data.attributes .slug ), ] : undefined } bottomChips={ isMobile ? undefined : previousContent.attributes.categories?.data.map( (category) => category.attributes?.short ?? "" ) } />
)} {nextContent?.attributes && ( <>

{langui.followup_content}

({ pre_title: translation.pre_title, title: translation.title, subtitle: translation.subtitle, language: translation.language?.data?.attributes?.code, }))} slug={nextContent.attributes.slug} languages={languages} thumbnail={nextContent.attributes.thumbnail?.data?.attributes} thumbnailAspectRatio="3/2" topChips={ isMobile ? undefined : nextContent.attributes.type?.data?.attributes ? [ nextContent.attributes.type.data.attributes.titles?.[0] ? nextContent.attributes.type.data.attributes .titles[0]?.title : prettySlug( nextContent.attributes.type.data.attributes.slug ), ] : undefined } bottomChips={ isMobile ? undefined : nextContent.attributes.categories?.data.map( (category) => category.attributes?.short ?? "" ) } /> )}
), [ LanguageSwitcher, content.categories, content.thumbnail?.data?.attributes, content.type, isMobile, languageSwitcherProps, languages, langui, nextContent?.attributes, previousContent?.attributes, selectedTranslation?.description, selectedTranslation?.pre_title, selectedTranslation?.subtitle, selectedTranslation?.text_set?.text, selectedTranslation?.title, ] ); return ( ); }; export default Content; /* * ╭──────────────────────╮ * ───────────────────────────────────╯ NEXT DATA FETCHING ╰────────────────────────────────────── */ export const getStaticProps: GetStaticProps = async (context) => { const sdk = getReadySdk(); const slug = context.params?.slug ? context.params.slug.toString() : ""; const content = await sdk.getContentText({ slug: slug, language_code: context.locale ?? "en", }); if (!content.contents?.data[0]?.attributes?.translations) { return { notFound: true }; } const props: Props = { ...(await getAppStaticProps(context)), content: content.contents.data[0].attributes as ContentWithTranslations, }; return { props: props, }; }; // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ export const getStaticPaths: GetStaticPaths = async (context) => { const sdk = getReadySdk(); const contents = await sdk.getContentsSlugs(); const paths: GetStaticPathsResult["paths"] = []; filterHasAttributes(contents.contents?.data).map((item) => { context.locales?.map((local) => { paths.push({ params: { slug: item.attributes.slug }, locale: local, }); }); }); return { paths, fallback: "blocking", }; }; /* * ╭───────────────────╮ * ─────────────────────────────────────╯ PRIVATE METHODS ╰─────────────────────────────────────── */ type Group = NonNullable< NonNullable< NonNullable< NonNullable["data"] >["attributes"] >["contents"] >["data"]; const getPreviousContent = (group: Group, currentSlug: string) => { for (let index = 0; index < group.length; index += 1) { const content = group[index]; if (content.attributes?.slug === currentSlug && index > 0) { return group[index - 1]; } } return undefined; }; // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ const getNextContent = (group: Group, currentSlug: string) => { for (let index = 0; index < group.length; index += 1) { const content = group[index]; if (content.attributes?.slug === currentSlug && index < group.length - 1) { return group[index + 1]; } } return undefined; };