- {groupedList.size > 0 ? (
- iterateMap(
- groupedList,
- (name, groupItems) =>
- groupItems.length > 0 && (
-
- {name.length > 0 && (
+ {pages[page]?.length > 0 ? (
+ pages[page]?.map(
+ (group) =>
+ group.items.length > 0 && (
+
+ {group.name.length > 0 && (
- {name}
+ {group.name}
acc + groupCountingFunction(item),
- 0
- )} ${
- groupItems.length <= 1
+ text={`${group.totalCount} ${
+ group.items.length <= 1
? langui.result?.toLowerCase() ?? ""
: langui.results?.toLowerCase() ?? ""
}`}
@@ -179,13 +209,12 @@ export const SmartList = ({
className
)}
>
- {groupItems.map((item) => (
+ {group.items.map((item) => (
))}
- ),
- ([a], [b]) => groupSortingFunction(a, b)
+ )
)
) : isDefined(RenderWhenEmpty) ? (
({
)}
- {pageCount > 1 && paginationSelectorBottom && (
-
+ {pages.length > 1 && paginationSelectorBottom && (
+
)}
>
);
@@ -217,7 +251,7 @@ const DefaultRenderWhenEmpty = ({ langui }: DefaultRenderWhenEmptyProps) => (
border-dark p-8 text-dark opacity-40"
>
- {langui.no_results_message}
+ {langui.no_results_message}
diff --git a/src/components/Translated.tsx b/src/components/Translated.tsx
index 5d448c7..7a674d4 100644
--- a/src/components/Translated.tsx
+++ b/src/components/Translated.tsx
@@ -4,7 +4,9 @@ import { ScanSet } from "./Library/ScanSet";
import { NavOption } from "./PanelComponents/NavOption";
import { ChroniclePreview } from "./Chronicles/ChroniclePreview";
import { ChroniclesList } from "./Chronicles/ChroniclesList";
+import { Button } from "./Inputs/Button";
import { useSmartLanguage } from "hooks/useSmartLanguage";
+import { PreviewFolder } from "pages/contents/folder/[slug]";
export type TranslatedProps = Omit
& {
translations: (Pick
& { language: string })[];
@@ -158,3 +160,43 @@ export const TranslatedChroniclesList = ({
/>
);
};
+
+// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
+
+export const TranslatedButton = ({
+ translations,
+ fallback,
+ ...otherProps
+}: TranslatedProps[0], "text">): JSX.Element => {
+ const [selectedTranslation] = useSmartLanguage({
+ items: translations,
+ languageExtractor,
+ });
+
+ return (
+
+ );
+};
+
+// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
+
+export const TranslatedPreviewFolder = ({
+ translations,
+ fallback,
+ ...otherProps
+}: TranslatedProps<
+ Parameters[0],
+ "title"
+>): JSX.Element => {
+ const [selectedTranslation] = useSmartLanguage({
+ items: translations,
+ languageExtractor,
+ });
+
+ return (
+
+ );
+};
diff --git a/src/graphql/operations/getContentText.graphql b/src/graphql/operations/getContentText.graphql
index de192ba..f9258da 100644
--- a/src/graphql/operations/getContentText.graphql
+++ b/src/graphql/operations/getContentText.graphql
@@ -214,9 +214,22 @@ query getContentText($slug: String, $language_code: String) {
}
}
}
- group {
+ folders(pagination: { limit: -1 }) {
data {
+ id
attributes {
+ slug
+ titles(pagination: { limit: -1 }) {
+ language {
+ data {
+ attributes {
+ code
+ }
+ }
+ }
+ title
+ }
+ sequence
contents(pagination: { limit: -1 }) {
data {
attributes {
diff --git a/src/graphql/operations/getContents.graphql b/src/graphql/operations/getContents.graphql
index 46a7a6d..601f0dd 100644
--- a/src/graphql/operations/getContents.graphql
+++ b/src/graphql/operations/getContents.graphql
@@ -62,18 +62,6 @@ query getContents($language_code: String) {
}
}
}
- group {
- data {
- attributes {
- combine
- contents(pagination: { limit: -1 }) {
- data {
- id
- }
- }
- }
- }
- }
thumbnail {
data {
attributes {
diff --git a/src/graphql/operations/getContentsFolder.graphql b/src/graphql/operations/getContentsFolder.graphql
new file mode 100644
index 0000000..a2f467b
--- /dev/null
+++ b/src/graphql/operations/getContentsFolder.graphql
@@ -0,0 +1,132 @@
+query getContentsFolder($slug: String, $language_code: String) {
+ contentsFolders(filters: { slug: { eq: $slug } }) {
+ data {
+ attributes {
+ slug
+ titles(pagination: { limit: -1 }) {
+ id
+ language {
+ data {
+ attributes {
+ code
+ }
+ }
+ }
+ title
+ }
+ parent_folder {
+ data {
+ attributes {
+ slug
+ titles(pagination: { limit: -1 }) {
+ id
+ language {
+ data {
+ attributes {
+ code
+ }
+ }
+ }
+ title
+ }
+ }
+ }
+ }
+ contents(pagination: { limit: -1 }) {
+ data {
+ id
+ attributes {
+ slug
+ translations(pagination: { limit: -1 }) {
+ pre_title
+ title
+ subtitle
+ language {
+ data {
+ attributes {
+ code
+ }
+ }
+ }
+ }
+ categories(pagination: { limit: -1 }) {
+ data {
+ id
+ attributes {
+ name
+ short
+ }
+ }
+ }
+ type {
+ data {
+ attributes {
+ slug
+ titles(
+ filters: { language: { code: { eq: $language_code } } }
+ ) {
+ title
+ }
+ }
+ }
+ }
+ ranged_contents(pagination: { limit: -1 }) {
+ data {
+ id
+ attributes {
+ slug
+ scan_set {
+ id
+ }
+ library_item {
+ data {
+ attributes {
+ slug
+ title
+ subtitle
+ thumbnail {
+ data {
+ attributes {
+ ...uploadImage
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ thumbnail {
+ data {
+ attributes {
+ ...uploadImage
+ }
+ }
+ }
+ }
+ }
+ }
+ subfolders(pagination: { limit: -1 }) {
+ data {
+ id
+ attributes {
+ slug
+ titles(pagination: { limit: -1 }) {
+ id
+ language {
+ data {
+ attributes {
+ code
+ }
+ }
+ }
+ title
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/graphql/operations/getContentsFoldersSlugs.graphql b/src/graphql/operations/getContentsFoldersSlugs.graphql
new file mode 100644
index 0000000..b60437c
--- /dev/null
+++ b/src/graphql/operations/getContentsFoldersSlugs.graphql
@@ -0,0 +1,9 @@
+query getContentsFoldersSlugs {
+ contentsFolders(pagination: { limit: -1 }) {
+ data {
+ attributes {
+ slug
+ }
+ }
+ }
+}
diff --git a/src/graphql/operations/getWebsiteInterface.graphql b/src/graphql/operations/getWebsiteInterface.graphql
index da28efd..a144264 100644
--- a/src/graphql/operations/getWebsiteInterface.graphql
+++ b/src/graphql/operations/getWebsiteInterface.graphql
@@ -170,6 +170,8 @@ query getWebsiteInterface($language_code: String) {
no_source_warning
copy_anchor_link
anchor_link_copied
+ folders
+ empty_folder_message
}
}
}
diff --git a/src/helpers/openGraph.ts b/src/helpers/openGraph.ts
index 717ea1f..9fb4ad9 100644
--- a/src/helpers/openGraph.ts
+++ b/src/helpers/openGraph.ts
@@ -15,7 +15,7 @@ const DEFAULT_OG_THUMBNAIL = {
alt: "Accord's Library Logo",
};
-const TITLE_PREFIX = "Accord’s Library";
+export const TITLE_PREFIX = "Accord’s Library - ";
export interface OpenGraph {
title: string;
@@ -29,7 +29,7 @@ export const getOpenGraph = (
description?: string | null | undefined,
thumbnail?: UploadImageFragment | null | undefined
): OpenGraph => ({
- title: `${TITLE_PREFIX}${isDefinedAndNotEmpty(title) ? ` - ${title}` : ""}`,
+ title: `${TITLE_PREFIX}${isDefinedAndNotEmpty(title) ? `${title}` : ""}`,
description: isDefinedAndNotEmpty(description)
? description
: langui.default_description ?? "",
diff --git a/src/pages/archives/videos/index.tsx b/src/pages/archives/videos/index.tsx
index d008d65..a60f96a 100644
--- a/src/pages/archives/videos/index.tsx
+++ b/src/pages/archives/videos/index.tsx
@@ -99,33 +99,31 @@ const Videos = ({ langui, videos, ...otherProps }: Props): JSX.Element => {
items={filterHasAttributes(videos, ["id", "attributes"] as const)}
getItemId={(item) => item.id}
renderItem={({ item }) => (
- <>
-
- >
+
)}
+ langui={langui}
className="desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:grid-cols-2
thin:grid-cols-1"
- paginationItemPerPage={20}
+ paginationItemPerPage={25}
searchingTerm={searchName}
searchingBy={(item) => item.attributes.title}
- langui={langui}
/>
),
diff --git a/src/pages/contents/[slug]/index.tsx b/src/pages/contents/[slug].tsx
similarity index 87%
rename from src/pages/contents/[slug]/index.tsx
rename to src/pages/contents/[slug].tsx
index 4a39cd8..90d90fe 100644
--- a/src/pages/contents/[slug]/index.tsx
+++ b/src/pages/contents/[slug].tsx
@@ -33,7 +33,10 @@ import { ContentWithTranslations } from "helpers/types";
import { useMediaMobile } from "hooks/useMediaQuery";
import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange";
import { useSmartLanguage } from "hooks/useSmartLanguage";
-import { TranslatedPreviewLine } from "components/Translated";
+import {
+ TranslatedPreviewFolder,
+ TranslatedPreviewLine,
+} from "components/Translated";
import { getOpenGraph } from "helpers/openGraph";
import {
getDefaultPreferredLanguages,
@@ -74,32 +77,51 @@ const Content = ({
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,
+ previousContent:
+ content.folders?.data[0]?.attributes?.contents &&
+ content.folders.data[0].attributes.sequence
+ ? getPreviousContent(
+ content.folders.data[0].attributes.contents.data,
+ content.slug
+ )
+ : undefined,
+ nextContent:
+ content.folders?.data[0]?.attributes?.contents &&
+ content.folders.data[0].attributes.sequence
+ ? getNextContent(
+ content.folders.data[0].attributes.contents.data,
+ content.slug
+ )
+ : undefined,
}),
- [content.group, content.slug]
+ [content.folders, content.slug]
);
const subPanel = useMemo(
() => (
-
+ {content.folders?.data && content.folders.data.length > 0 && (
+ <>
+ {langui.folders}
+ {filterHasAttributes(content.folders.data, [
+ "attributes",
+ "id",
+ ] as const).map((folder) => (
+ ({
+ language: title.language.data.attributes.code,
+ title: title.title,
+ }))}
+ fallback={{ title: prettySlug(folder.attributes.slug) }}
+ />
+ ))}
+
+ >
+ )}
{selectedTranslation?.text_set?.source_language?.data?.attributes
?.code !== undefined && (
@@ -308,6 +330,7 @@ const Content = ({
),
[
+ content.folders?.data,
content.ranged_contents?.data,
currencies,
languages,
@@ -320,7 +343,7 @@ const Content = ({
() => (
{
* ─────────────────────────────────────╯ PRIVATE METHODS ╰───────────────────────────────────────
*/
-type Group = NonNullable<
+type FolderContents = NonNullable<
NonNullable<
NonNullable<
- NonNullable["data"]
+ NonNullable["data"][number]
>["attributes"]
>["contents"]
>["data"];
-const getPreviousContent = (group: Group, currentSlug: string) => {
- for (let index = 0; index < group.length; index++) {
- const content = group[index];
+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 group[index - 1];
+ return contents[index - 1];
}
}
return undefined;
@@ -599,11 +622,14 @@ const getPreviousContent = (group: Group, currentSlug: string) => {
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
-const getNextContent = (group: Group, currentSlug: string) => {
- for (let index = 0; index < group.length; index++) {
- const content = group[index];
- if (content.attributes?.slug === currentSlug && index < group.length - 1) {
- return group[index + 1];
+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;
diff --git a/src/pages/contents/all.tsx b/src/pages/contents/all.tsx
new file mode 100644
index 0000000..7cdb7de
--- /dev/null
+++ b/src/pages/contents/all.tsx
@@ -0,0 +1,315 @@
+import { GetStaticProps } from "next";
+import { useState, useMemo, useCallback } from "react";
+import { AppLayout, AppLayoutRequired } from "components/AppLayout";
+import { Select } from "components/Inputs/Select";
+import { Switch } from "components/Inputs/Switch";
+import { PanelHeader } from "components/PanelComponents/PanelHeader";
+import {
+ ContentPanel,
+ ContentPanelWidthSizes,
+} from "components/Panels/ContentPanel";
+import { SubPanel } from "components/Panels/SubPanel";
+import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
+import { getReadySdk } from "graphql/sdk";
+import { prettyInlineTitle, prettySlug } from "helpers/formatters";
+import { TextInput } from "components/Inputs/TextInput";
+import { WithLabel } from "components/Inputs/WithLabel";
+import { Button } from "components/Inputs/Button";
+import { useMediaHoverable } from "hooks/useMediaQuery";
+import { Icon } from "components/Ico";
+import { filterDefined, filterHasAttributes } from "helpers/others";
+import { GetContentsQuery } from "graphql/generated";
+import { SmartList } from "components/SmartList";
+import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable";
+import { useBoolean } from "hooks/useBoolean";
+import { TranslatedPreviewCard } from "components/Translated";
+import { getOpenGraph } from "helpers/openGraph";
+import { HorizontalLine } from "components/HorizontalLine";
+
+/*
+ * ╭─────────────╮
+ * ────────────────────────────────────────╯ CONSTANTS ╰──────────────────────────────────────────
+ */
+
+const DEFAULT_FILTERS_STATE = {
+ groupingMethod: -1,
+ keepInfoVisible: false,
+ searchName: "",
+};
+
+/*
+ * ╭────────╮
+ * ──────────────────────────────────────────╯ PAGE ╰─────────────────────────────────────────────
+ */
+
+interface Props extends AppStaticProps, AppLayoutRequired {
+ contents: NonNullable["data"];
+}
+
+const Contents = ({
+ langui,
+ contents,
+ languages,
+ ...otherProps
+}: Props): JSX.Element => {
+ const hoverable = useMediaHoverable();
+
+ const [groupingMethod, setGroupingMethod] = useState(
+ DEFAULT_FILTERS_STATE.groupingMethod
+ );
+ const {
+ state: keepInfoVisible,
+ toggleState: toggleKeepInfoVisible,
+ setState: setKeepInfoVisible,
+ } = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
+
+ const [searchName, setSearchName] = useState(
+ DEFAULT_FILTERS_STATE.searchName
+ );
+
+ const groupingFunction = useCallback(
+ (
+ item: SelectiveNonNullable<
+ NonNullable["data"][number],
+ "attributes" | "id"
+ >
+ ): string[] => {
+ switch (groupingMethod) {
+ case 0: {
+ const categories = filterHasAttributes(
+ item.attributes.categories?.data,
+ ["attributes"] as const
+ );
+ if (categories.length > 0) {
+ return categories.map((category) => category.attributes.name);
+ }
+ return [langui.no_category ?? "No category"];
+ }
+ case 1: {
+ return [
+ item.attributes.type?.data?.attributes?.titles?.[0]?.title ??
+ item.attributes.type?.data?.attributes?.slug
+ ? prettySlug(item.attributes.type.data.attributes.slug)
+ : langui.no_type ?? "No type",
+ ];
+ }
+ default: {
+ return [""];
+ }
+ }
+ },
+ [groupingMethod, langui]
+ );
+
+ const filteringFunction = useCallback(
+ (
+ item: SelectiveNonNullable
+ ) => {
+ if (searchName.length > 1) {
+ if (
+ filterDefined(item.attributes.translations).find((translation) =>
+ prettyInlineTitle(
+ translation.pre_title,
+ translation.title,
+ translation.subtitle
+ )
+ .toLowerCase()
+ .includes(searchName.toLowerCase())
+ )
+ ) {
+ return true;
+ }
+ return false;
+ }
+ return true;
+ },
+ [searchName]
+ );
+
+ const subPanel = useMemo(
+ () => (
+
+
+
+
+
+
+
+
+
+
+ }
+ />
+
+ {hoverable && (
+
+ }
+ />
+ )}
+
+
+ ),
+ [
+ groupingMethod,
+ hoverable,
+ keepInfoVisible,
+ langui.always_show_info,
+ langui.category,
+ langui.contents,
+ langui.contents_description,
+ langui.group_by,
+ langui.reset_all_filters,
+ langui.search_title,
+ langui.type,
+ searchName,
+ setKeepInfoVisible,
+ toggleKeepInfoVisible,
+ ]
+ );
+
+ const contentPanel = useMemo(
+ () => (
+
+ item.id}
+ renderItem={({ item }) => (
+ ({
+ pre_title: translation.pre_title,
+ title: translation.title,
+ subtitle: translation.subtitle,
+ language: translation.language.data.attributes.code,
+ }))}
+ fallback={{ title: prettySlug(item.attributes.slug) }}
+ thumbnail={item.attributes.thumbnail?.data?.attributes}
+ thumbnailAspectRatio="3/2"
+ thumbnailForceAspectRatio
+ topChips={
+ item.attributes.type?.data?.attributes
+ ? [
+ item.attributes.type.data.attributes.titles?.[0]
+ ? item.attributes.type.data.attributes.titles[0]?.title
+ : prettySlug(item.attributes.type.data.attributes.slug),
+ ]
+ : undefined
+ }
+ bottomChips={item.attributes.categories?.data.map(
+ (category) => category.attributes?.short ?? ""
+ )}
+ keepInfoVisible={keepInfoVisible}
+ />
+ )}
+ className="grid-cols-2 desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))]"
+ groupingFunction={groupingFunction}
+ filteringFunction={filteringFunction}
+ searchingTerm={searchName}
+ searchingBy={(item) =>
+ `
+ ${item.attributes.slug}
+ ${filterDefined(item.attributes.translations)
+ .map((translation) =>
+ prettyInlineTitle(
+ translation.pre_title,
+ translation.title,
+ translation.subtitle
+ )
+ )
+ .join(" ")}`
+ }
+ paginationItemPerPage={50}
+ langui={langui}
+ />
+
+ ),
+ [
+ contents,
+ filteringFunction,
+ groupingFunction,
+ keepInfoVisible,
+ langui,
+ searchName,
+ ]
+ );
+
+ return (
+
+ );
+};
+export default Contents;
+
+/*
+ * ╭──────────────────────╮
+ * ───────────────────────────────────╯ NEXT DATA FETCHING ╰──────────────────────────────────────
+ */
+
+export const getStaticProps: GetStaticProps = async (context) => {
+ const sdk = getReadySdk();
+ const contents = await sdk.getContents({
+ language_code: context.locale ?? "en",
+ });
+ if (!contents.contents) return { notFound: true };
+ contents.contents.data.sort((a, b) => {
+ const titleA = a.attributes?.slug ?? "";
+ const titleB = b.attributes?.slug ?? "";
+ return titleA.localeCompare(titleB);
+ });
+
+ const appStaticProps = await getAppStaticProps(context);
+ const props: Props = {
+ ...appStaticProps,
+ contents: contents.contents.data,
+ openGraph: getOpenGraph(
+ appStaticProps.langui,
+ appStaticProps.langui.contents ?? "Contents"
+ ),
+ };
+ return {
+ props: props,
+ };
+};
diff --git a/src/pages/contents/folder/[slug].tsx b/src/pages/contents/folder/[slug].tsx
new file mode 100644
index 0000000..c285f94
--- /dev/null
+++ b/src/pages/contents/folder/[slug].tsx
@@ -0,0 +1,322 @@
+import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
+import { useMemo } from "react";
+import { AppLayout, AppLayoutRequired } from "components/AppLayout";
+import {
+ ContentPanel,
+ ContentPanelWidthSizes,
+} from "components/Panels/ContentPanel";
+import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
+import { getOpenGraph } from "helpers/openGraph";
+import { getReadySdk } from "graphql/sdk";
+import { filterHasAttributes } from "helpers/others";
+import { GetContentsFolderQuery } from "graphql/generated";
+import {
+ getDefaultPreferredLanguages,
+ staticSmartLanguage,
+} from "helpers/locales";
+import { prettySlug } from "helpers/formatters";
+import { SmartList } from "components/SmartList";
+import {
+ TranslatedButton,
+ TranslatedPreviewCard,
+ TranslatedPreviewFolder,
+} from "components/Translated";
+import { Ico, Icon } from "components/Ico";
+import { Button } from "components/Inputs/Button";
+import { Link } from "components/Inputs/Link";
+import { PanelHeader } from "components/PanelComponents/PanelHeader";
+import { SubPanel } from "components/Panels/SubPanel";
+
+/*
+ * ╭────────╮
+ * ──────────────────────────────────────────╯ PAGE ╰─────────────────────────────────────────────
+ */
+
+interface Props extends AppStaticProps, AppLayoutRequired {
+ folder: NonNullable<
+ NonNullable<
+ GetContentsFolderQuery["contentsFolders"]
+ >["data"][number]["attributes"]
+ >;
+}
+
+const ContentsFolder = ({
+ langui,
+ openGraph,
+ folder,
+ ...otherProps
+}: Props): JSX.Element => {
+ const subPanel = useMemo(
+ () => (
+
+
+
+
+
+ ),
+ [langui.contents, langui.contents_description]
+ );
+
+ const contentPanel = useMemo(
+ () => (
+
+
+ {folder.parent_folder?.data?.attributes && (
+ <>
+ {folder.parent_folder.data.attributes.slug === "root" ? (
+
+ ) : (
+ ({
+ language: title.language.data.attributes.code,
+ text: title.title,
+ }))}
+ fallback={{
+ text: prettySlug(folder.parent_folder.data.attributes.slug),
+ }}
+ />
+ )}
+
+ >
+ )}
+
+ {folder.slug === "root" ? (
+
+ ) : (
+ ({
+ language: title.language.data.attributes.code,
+ text: title.title,
+ }))}
+ fallback={{
+ text: prettySlug(folder.slug),
+ }}
+ active
+ />
+ )}
+
+
+ item.id}
+ renderItem={({ item }) => (
+ ({
+ title: title.title,
+ language: title.language.data.attributes.code,
+ }))}
+ fallback={{ title: prettySlug(item.attributes.slug) }}
+ />
+ )}
+ className="grid-cols-2 items-stretch
+ desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))]"
+ renderWhenEmpty={() => <>>}
+ langui={langui}
+ groupingFunction={() => [langui.folders ?? "Folders"]}
+ />
+
+ item.id}
+ renderItem={({ item }) => (
+ ({
+ pre_title: translation.pre_title,
+ title: translation.title,
+ subtitle: translation.subtitle,
+ language: translation.language.data.attributes.code,
+ }))}
+ fallback={{ title: prettySlug(item.attributes.slug) }}
+ thumbnail={item.attributes.thumbnail?.data?.attributes}
+ thumbnailAspectRatio="3/2"
+ thumbnailForceAspectRatio
+ topChips={
+ item.attributes.type?.data?.attributes
+ ? [
+ item.attributes.type.data.attributes.titles?.[0]
+ ? item.attributes.type.data.attributes.titles[0]?.title
+ : prettySlug(item.attributes.type.data.attributes.slug),
+ ]
+ : undefined
+ }
+ bottomChips={item.attributes.categories?.data.map(
+ (category) => category.attributes?.short ?? ""
+ )}
+ keepInfoVisible
+ />
+ )}
+ className="grid-cols-2 desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))]"
+ renderWhenEmpty={() => <>>}
+ langui={langui}
+ groupingFunction={() => [langui.contents ?? "Contents"]}
+ />
+
+ {folder.contents?.data.length === 0 &&
+ folder.subfolders?.data.length === 0 && (
+
+ )}
+
+ ),
+ [
+ folder.contents,
+ folder.parent_folder?.data?.attributes,
+ folder.slug,
+ folder.subfolders,
+ folder.titles,
+ langui,
+ ]
+ );
+
+ return (
+
+ );
+};
+export default ContentsFolder;
+
+/*
+ * ╭──────────────────────╮
+ * ───────────────────────────────────╯ NEXT DATA FETCHING ╰──────────────────────────────────────
+ */
+
+export const getStaticProps: GetStaticProps = async (context) => {
+ const sdk = getReadySdk();
+ const slug = context.params?.slug ? context.params.slug.toString() : "";
+ const contentsFolder = await sdk.getContentsFolder({
+ slug: slug,
+ language_code: context.locale ?? "en",
+ });
+ if (!contentsFolder.contentsFolders?.data[0]?.attributes) {
+ return { notFound: true };
+ }
+
+ const folder = contentsFolder.contentsFolders.data[0].attributes;
+ const appStaticProps = await getAppStaticProps(context);
+
+ const title = (() => {
+ if (slug === "root") {
+ return appStaticProps.langui.contents ?? "Contents";
+ }
+ if (context.locale && context.locales) {
+ const selectedTranslation = staticSmartLanguage({
+ items: folder.titles,
+ languageExtractor: (item) => item.language?.data?.attributes?.code,
+ preferredLanguages: getDefaultPreferredLanguages(
+ context.locale,
+ context.locales
+ ),
+ });
+ if (selectedTranslation) {
+ return selectedTranslation.title;
+ }
+ }
+ return prettySlug(folder.slug);
+ })();
+
+ const props: Props = {
+ ...appStaticProps,
+ openGraph: getOpenGraph(appStaticProps.langui, title),
+ folder,
+ };
+ return {
+ props: props,
+ };
+};
+
+// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
+
+export const getStaticPaths: GetStaticPaths = async (context) => {
+ const sdk = getReadySdk();
+ const contents = await sdk.getContentsFoldersSlugs();
+ const paths: GetStaticPathsResult["paths"] = [];
+ filterHasAttributes(contents.contentsFolders?.data, [
+ "attributes",
+ ] as const).map((item) => {
+ context.locales?.map((local) => {
+ paths.push({
+ params: { slug: item.attributes.slug },
+ locale: local,
+ });
+ });
+ });
+ return {
+ paths,
+ fallback: "blocking",
+ };
+};
+
+/*
+ * ╭──────────────────────╮
+ * ───────────────────────────────────╯ PRIVATE COMPONENTS ╰──────────────────────────────────────
+ */
+
+interface PreviewFolderProps {
+ href: string;
+ title: string | null | undefined;
+}
+
+export const PreviewFolder = ({
+ href,
+ title,
+}: PreviewFolderProps): JSX.Element => (
+
+ {title && (
+
+ {title}
+
+ )}
+
+);
+
+interface NoContentNorFolderMessageProps {
+ langui: AppStaticProps["langui"];
+}
+
+const NoContentNorFolderMessage = ({
+ langui,
+}: NoContentNorFolderMessageProps) => (
+
+
+
{langui.empty_folder_message}
+
+
+);
diff --git a/src/pages/contents/index.tsx b/src/pages/contents/index.tsx
index 9b4639d..5c59528 100644
--- a/src/pages/contents/index.tsx
+++ b/src/pages/contents/index.tsx
@@ -1,332 +1,16 @@
import { GetStaticProps } from "next";
-import { useState, useMemo, useCallback } from "react";
-import { AppLayout, AppLayoutRequired } from "components/AppLayout";
-import { Select } from "components/Inputs/Select";
-import { Switch } from "components/Inputs/Switch";
-import { PanelHeader } from "components/PanelComponents/PanelHeader";
-import {
- ContentPanel,
- ContentPanelWidthSizes,
-} from "components/Panels/ContentPanel";
-import { SubPanel } from "components/Panels/SubPanel";
-import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
-import { getReadySdk } from "graphql/sdk";
-import { prettyInlineTitle, prettySlug } from "helpers/formatters";
-import { TextInput } from "components/Inputs/TextInput";
-import { WithLabel } from "components/Inputs/WithLabel";
-import { Button } from "components/Inputs/Button";
-import { useMediaHoverable } from "hooks/useMediaQuery";
-import { Icon } from "components/Ico";
-import { filterDefined, filterHasAttributes } from "helpers/others";
-import { GetContentsQuery } from "graphql/generated";
-import { SmartList } from "components/SmartList";
-import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable";
-import { useBoolean } from "hooks/useBoolean";
-import { TranslatedPreviewCard } from "components/Translated";
-import { getOpenGraph } from "helpers/openGraph";
-
-/*
- * ╭─────────────╮
- * ────────────────────────────────────────╯ CONSTANTS ╰──────────────────────────────────────────
- */
-
-const DEFAULT_FILTERS_STATE = {
- groupingMethod: -1,
- keepInfoVisible: false,
- combineRelatedContent: true,
- searchName: "",
-};
+import ContentsFolder, {
+ getStaticProps as folderGetStaticProps,
+} from "./folder/[slug]";
/*
* ╭────────╮
* ──────────────────────────────────────────╯ PAGE ╰─────────────────────────────────────────────
*/
-interface Props extends AppStaticProps, AppLayoutRequired {
- contents: NonNullable["data"];
-}
-
-const Contents = ({
- langui,
- contents,
- languages,
- ...otherProps
-}: Props): JSX.Element => {
- const hoverable = useMediaHoverable();
-
- const [groupingMethod, setGroupingMethod] = useState(
- DEFAULT_FILTERS_STATE.groupingMethod
- );
- const {
- state: keepInfoVisible,
- toggleState: toggleKeepInfoVisible,
- setState: setKeepInfoVisible,
- } = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
-
- const {
- state: combineRelatedContent,
- toggleState: toggleCombineRelatedContent,
- setState: setCombineRelatedContent,
- } = useBoolean(DEFAULT_FILTERS_STATE.combineRelatedContent);
- const [searchName, setSearchName] = useState(
- DEFAULT_FILTERS_STATE.searchName
- );
-
- const effectiveCombineRelatedContent = useMemo(
- () => (searchName.length > 1 ? false : combineRelatedContent),
- [combineRelatedContent, searchName.length]
- );
-
- const groupingFunction = useCallback(
- (
- item: SelectiveNonNullable<
- NonNullable["data"][number],
- "attributes" | "id"
- >
- ): string[] => {
- switch (groupingMethod) {
- case 0: {
- const categories = filterHasAttributes(
- item.attributes.categories?.data,
- ["attributes"] as const
- );
- if (categories.length > 0) {
- return categories.map((category) => category.attributes.name);
- }
- return [langui.no_category ?? "No category"];
- }
- case 1: {
- return [
- item.attributes.type?.data?.attributes?.titles?.[0]?.title ??
- item.attributes.type?.data?.attributes?.slug
- ? prettySlug(item.attributes.type.data.attributes.slug)
- : langui.no_type ?? "No type",
- ];
- }
- default: {
- return [""];
- }
- }
- },
- [groupingMethod, langui]
- );
-
- const filteringFunction = useCallback(
- (
- item: SelectiveNonNullable
- ) => {
- if (
- effectiveCombineRelatedContent &&
- item.attributes.group?.data?.attributes?.combine === true &&
- item.attributes.group.data.attributes.contents?.data[0].id !== item.id
- ) {
- return false;
- }
- if (searchName.length > 1) {
- if (
- filterDefined(item.attributes.translations).find((translation) =>
- prettyInlineTitle(
- translation.pre_title,
- translation.title,
- translation.subtitle
- )
- .toLowerCase()
- .includes(searchName.toLowerCase())
- )
- ) {
- return true;
- }
- return false;
- }
- return true;
- },
- [effectiveCombineRelatedContent, searchName]
- );
-
- const groupCountingFunction = useCallback(
- (
- item: SelectiveNonNullable
- ) =>
- item.attributes.group?.data?.attributes?.combine &&
- effectiveCombineRelatedContent
- ? item.attributes.group.data.attributes.contents?.data.length ?? 1
- : 1,
- [effectiveCombineRelatedContent]
- );
-
- const subPanel = useMemo(
- () => (
-
-
-
-
-
-
- }
- />
-
- 1}
- input={
-
- }
- />
-
- {hoverable && (
-
- }
- />
- )}
-
-
- ),
- [
- effectiveCombineRelatedContent,
- groupingMethod,
- hoverable,
- keepInfoVisible,
- langui.always_show_info,
- langui.category,
- langui.combine_related_contents,
- langui.contents,
- langui.contents_description,
- langui.group_by,
- langui.reset_all_filters,
- langui.search_title,
- langui.type,
- searchName,
- setCombineRelatedContent,
- setKeepInfoVisible,
- toggleCombineRelatedContent,
- toggleKeepInfoVisible,
- ]
- );
-
- const contentPanel = useMemo(
- () => (
-
- item.id}
- renderItem={({ item }) => (
- ({
- pre_title: translation.pre_title,
- title: translation.title,
- subtitle: translation.subtitle,
- language: translation.language.data.attributes.code,
- }))}
- fallback={{ title: prettySlug(item.attributes.slug) }}
- thumbnail={item.attributes.thumbnail?.data?.attributes}
- thumbnailAspectRatio="3/2"
- thumbnailForceAspectRatio
- stackNumber={
- effectiveCombineRelatedContent &&
- item.attributes.group?.data?.attributes?.combine === true
- ? item.attributes.group.data.attributes.contents?.data.length
- : 0
- }
- topChips={
- item.attributes.type?.data?.attributes
- ? [
- item.attributes.type.data.attributes.titles?.[0]
- ? item.attributes.type.data.attributes.titles[0]?.title
- : prettySlug(item.attributes.type.data.attributes.slug),
- ]
- : undefined
- }
- bottomChips={item.attributes.categories?.data.map(
- (category) => category.attributes?.short ?? ""
- )}
- keepInfoVisible={keepInfoVisible}
- />
- )}
- className="grid-cols-2 desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))]"
- groupingFunction={groupingFunction}
- groupCountingFunction={groupCountingFunction}
- filteringFunction={filteringFunction}
- searchingTerm={searchName}
- searchingBy={(item) =>
- `
- ${item.attributes.slug}
- ${filterDefined(item.attributes.translations)
- .map((translation) =>
- prettyInlineTitle(
- translation.pre_title,
- translation.title,
- translation.subtitle
- )
- )
- .join(" ")}`
- }
- langui={langui}
- />
-
- ),
- [
- contents,
- effectiveCombineRelatedContent,
- filteringFunction,
- groupCountingFunction,
- groupingFunction,
- keepInfoVisible,
- langui,
- searchName,
- ]
- );
-
- return (
-
- );
-};
+const Contents = (props: Parameters[0]): JSX.Element => (
+
+);
export default Contents;
/*
@@ -335,27 +19,6 @@ export default Contents;
*/
export const getStaticProps: GetStaticProps = async (context) => {
- const sdk = getReadySdk();
- const contents = await sdk.getContents({
- language_code: context.locale ?? "en",
- });
- if (!contents.contents) return { notFound: true };
- contents.contents.data.sort((a, b) => {
- const titleA = a.attributes?.slug ?? "";
- const titleB = b.attributes?.slug ?? "";
- return titleA.localeCompare(titleB);
- });
-
- const appStaticProps = await getAppStaticProps(context);
- const props: Props = {
- ...appStaticProps,
- contents: contents.contents.data,
- openGraph: getOpenGraph(
- appStaticProps.langui,
- appStaticProps.langui.contents ?? "Contents"
- ),
- };
- return {
- props: props,
- };
+ context.params = { slug: "root" };
+ return await folderGetStaticProps(context);
};
diff --git a/src/pages/library/index.tsx b/src/pages/library/index.tsx
index 25f52aa..9b4c31f 100644
--- a/src/pages/library/index.tsx
+++ b/src/pages/library/index.tsx
@@ -451,6 +451,7 @@ const Library = ({
)
}
filteringFunction={filteringFunction}
+ paginationItemPerPage={25}
langui={langui}
/>
diff --git a/src/pages/news/index.tsx b/src/pages/news/index.tsx
index 4abc165..c26c761 100644
--- a/src/pages/news/index.tsx
+++ b/src/pages/news/index.tsx
@@ -139,6 +139,7 @@ const News = ({ langui, posts, ...otherProps }: Props): JSX.Element => {
?.map((translation) => translation?.title)
.join(" ")}`
}
+ paginationItemPerPage={25}
/>
),
diff --git a/src/pages/wiki/index.tsx b/src/pages/wiki/index.tsx
index df59002..2a15b45 100644
--- a/src/pages/wiki/index.tsx
+++ b/src/pages/wiki/index.tsx
@@ -212,6 +212,7 @@ const Wiki = ({ langui, pages, ...otherProps }: Props): JSX.Element => {
.join(" ")
}
groupingFunction={groupingFunction}
+ paginationItemPerPage={25}
/>
),