import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; import { Fragment, useCallback } from "react"; import naturalCompare from "string-natural-compare"; import { AppLayout, AppLayoutRequired } 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 { TranslatedReturnButton } from "components/PanelComponents/ReturnButton"; import { ContentPanel } from "components/Containers/ContentPanel"; import { SubPanel } from "components/Containers/SubPanel"; import { PreviewCard } from "components/PreviewCard"; import { RecorderChip } from "components/RecorderChip"; import { ThumbnailHeader } from "components/ThumbnailHeader"; import { ToolTip } from "components/ToolTip"; import { getReadySdk } from "graphql/sdk"; import { prettyInlineTitle, prettyLanguage, prettyItemSubType, prettySlug, } from "helpers/formatters"; import { isUntangibleGroupItem } from "helpers/libraryItem"; import { filterHasAttributes, isDefinedAndNotEmpty } from "helpers/asserts"; import { ContentWithTranslations } from "types/types"; import { useScrollTopOnChange } from "hooks/useScrollTopOnChange"; import { useSmartLanguage } from "hooks/useSmartLanguage"; import { getOpenGraph } from "helpers/openGraph"; import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales"; import { getDescription } from "helpers/description"; import { TranslatedPreviewLine } from "components/PreviewLine"; import { cIf } from "helpers/className"; import { Ids } from "types/ids"; import { atoms } from "contexts/atoms"; import { useAtomGetter } from "helpers/atoms"; import { useFormat } from "hooks/useFormat"; import { getFormat } from "helpers/i18n"; /* * ╭────────╮ * ──────────────────────────────────────────╯ PAGE ╰───────────────────────────────────────────── */ interface Props extends AppLayoutRequired { content: ContentWithTranslations; } const Content = ({ content, ...otherProps }: Props): JSX.Element => { const isContentPanelAtLeast2xl = useAtomGetter(atoms.containerQueries.isContentPanelAtLeast2xl); const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout); const { format, formatStatusDescription } = useFormat(); const languages = useAtomGetter(atoms.localData.languages); const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({ items: content.translations, languageExtractor: useCallback( (item: NonNullable) => item.language?.data?.attributes?.code, [] ), }); useScrollTopOnChange(Ids.ContentPanel, [selectedTranslation]); const previousContent = content.folder?.data?.attributes?.contents && content.folder.data.attributes.sequence ? getPreviousContent(content.folder.data.attributes.contents.data, content.slug) : undefined; const nextContent = content.folder?.data?.attributes?.contents && content.folder.data.attributes.sequence ? getNextContent(content.folder.data.attributes.contents.data, content.slug) : undefined; const returnButtonProps = { href: content.folder?.data?.attributes ? `/contents/folder/${content.folder.data.attributes.slug}` : "/contents", translations: filterHasAttributes(content.folder?.data?.attributes?.titles, [ "language.data.attributes.code", ] as const).map((title) => ({ language: title.language.data.attributes.code, title: title.title, })), fallback: { title: content.folder?.data?.attributes ? prettySlug(content.folder.data.attributes.slug) : format("contents"), }, }; const subPanel = ( {selectedTranslation?.text_set?.source_language?.data?.attributes?.code !== undefined && ( <>

{selectedTranslation.text_set.source_language.data.attributes.code === selectedTranslation.language?.data?.attributes?.code ? format("transcript_notice") : format("translation_notice")}

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

{format("source_language")}:

)}

{format("status")}:

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

{format("transcribers")}:

{filterHasAttributes(selectedTranslation.text_set.transcribers.data, [ "attributes", "id", ] as const).map((recorder) => ( ))}
)} {selectedTranslation.text_set.translators && selectedTranslation.text_set.translators.data.length > 0 && (

{format("translators")}:

{filterHasAttributes(selectedTranslation.text_set.translators.data, [ "attributes", "id", ] as const).map((recorder) => ( ))}
)} {selectedTranslation.text_set.proofreaders && selectedTranslation.text_set.proofreaders.data.length > 0 && (

{format("proofreaders")}:

{filterHasAttributes(selectedTranslation.text_set.proofreaders.data, [ "attributes", "id", ] as const).map((recorder) => ( ))}
)} {isDefinedAndNotEmpty(selectedTranslation.text_set.notes) && (

{format("notes")}:

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

{format("source")}

{filterHasAttributes(content.ranged_contents.data, [ "attributes.library_item.data.attributes", "attributes.library_item.data.id", ] as const).map((rangedContent) => { const libraryItem = rangedContent.attributes.library_item.data; return (
0 && libraryItem.attributes.metadata[0] ? [prettyItemSubType(libraryItem.attributes.metadata[0])] : [] } bottomChips={filterHasAttributes(libraryItem.attributes.categories?.data, [ "attributes", ] as const).map((category) => category.attributes.short)} metadata={{ releaseDate: libraryItem.attributes.release_date, price: libraryItem.attributes.price, position: "Bottom", }} infoAppend={ !isUntangibleGroupItem(libraryItem.attributes.metadata?.[0]) && ( ) } />
); })}
)}
); const contentPanel = (
1 ? ( ) : undefined } /> {previousContent?.attributes && (

{format("previous_content")}

({ pre_title: translation.pre_title, title: translation.title, subtitle: translation.subtitle, language: translation.language.data.attributes.code, }))} fallback={{ title: prettySlug(previousContent.attributes.slug), }} thumbnail={previousContent.attributes.thumbnail?.data?.attributes} topChips={ isContentPanelAtLeast2xl && 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={ isContentPanelAtLeast2xl ? previousContent.attributes.categories?.data.map( (category) => category.attributes?.short ?? "" ) : undefined } />
)} {selectedTranslation?.text_set?.text && ( <> )} {nextContent?.attributes && ( <>

{format("followup_content")}

({ pre_title: translation.pre_title, title: translation.title, subtitle: translation.subtitle, language: translation.language.data.attributes.code, }))} fallback={{ title: nextContent.attributes.slug }} thumbnail={nextContent.attributes.thumbnail?.data?.attributes} topChips={ isContentPanelAtLeast2xl && 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={ isContentPanelAtLeast2xl ? nextContent.attributes.categories?.data.map( (category) => category.attributes?.short ?? "" ) : undefined } /> )}
); return ; }; export default Content; /* * ╭──────────────────────╮ * ───────────────────────────────────╯ NEXT DATA FETCHING ╰────────────────────────────────────── */ export const getStaticProps: GetStaticProps = async (context) => { const sdk = getReadySdk(); const { format } = getFormat(context.locale); 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 { title, description } = (() => { if (context.locale && context.locales) { const selectedTranslation = staticSmartLanguage({ items: content.contents.data[0].attributes.translations, languageExtractor: (item) => item.language?.data?.attributes?.code, preferredLanguages: getDefaultPreferredLanguages(context.locale, context.locales), }); if (selectedTranslation) { return { title: prettyInlineTitle( selectedTranslation.pre_title, selectedTranslation.title, selectedTranslation.subtitle ), description: getDescription(selectedTranslation.description, { [format("type", { count: Infinity })]: [ content.contents.data[0].attributes.type?.data?.attributes?.titles?.[0]?.title, ], [format("category", { count: Infinity })]: filterHasAttributes( content.contents.data[0].attributes.categories?.data, ["attributes"] as const ).map((category) => category.attributes.short), }), }; } } return { title: prettySlug(content.contents.data[0].attributes.slug), description: undefined, }; })(); const thumbnail = content.contents.data[0].attributes.thumbnail?.data?.attributes; content.contents.data[0].attributes.folder?.data?.attributes?.contents?.data.sort((a, b) => a.attributes && b.attributes ? naturalCompare(a.attributes.slug, b.attributes.slug) : 0 ); const props: Props = { content: content.contents.data[0].attributes as ContentWithTranslations, openGraph: getOpenGraph(format, title, description, thumbnail), }; 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, ["attributes"] as const).map((item) => { context.locales?.map((local) => { paths.push({ params: { slug: item.attributes.slug }, locale: local, }); }); }); return { paths, fallback: "blocking", }; }; /* * ╭───────────────────╮ * ─────────────────────────────────────╯ PRIVATE METHODS ╰─────────────────────────────────────── */ type FolderContents = NonNullable< NonNullable< NonNullable["data"]>["attributes"] >["contents"] >["data"]; const getPreviousContent = (contents: FolderContents, currentSlug: string) => { for (let index = 0; index < contents.length; index++) { const content = contents[index]; if (content?.attributes?.slug === currentSlug && index > 0) { return contents[index - 1]; } } return undefined; }; // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ const getNextContent = (contents: FolderContents, currentSlug: string) => { for (let index = 0; index < contents.length; index++) { const content = contents[index]; if (content?.attributes?.slug === currentSlug && index < contents.length - 1) { return contents[index + 1]; } } return undefined; };