Display the news articles

This commit is contained in:
DrMint 2022-03-17 17:05:34 +01:00
parent 1f1c17a3a8
commit 151ae0b126
10 changed files with 517 additions and 27 deletions

View File

@ -167,7 +167,7 @@ export default function Markdawn(props: ScenBreakProps): JSX.Element {
}, },
InsetBox: { InsetBox: {
component: (props) => { component: (props) => {
return <InsetBox>{props.children}</InsetBox>; return <InsetBox className="my-12">{props.children}</InsetBox>;
}, },
}, },
li: { li: {

View File

@ -0,0 +1,64 @@
import Link from "next/link";
import { prettyDate, prettySlug } from "queries/helpers";
import Chip from "components/Chip";
import Img, { ImageQuality } from "components/Img";
import { GetPostsPreviewQuery } from "graphql/operations-types";
export type PostPreviewProps = {
post: {
slug: GetPostsPreviewQuery["posts"]["data"][number]["attributes"]["slug"];
thumbnail: GetPostsPreviewQuery["posts"]["data"][number]["attributes"]["thumbnail"];
translations: GetPostsPreviewQuery["posts"]["data"][number]["attributes"]["translations"];
categories: GetPostsPreviewQuery["posts"]["data"][number]["attributes"]["categories"];
date: GetPostsPreviewQuery["posts"]["data"][number]["attributes"]["date"];
};
};
export default function PostPreview(props: PostPreviewProps): JSX.Element {
const post = props.post;
return (
<Link href={"/news/" + post.slug} passHref>
<div className="drop-shadow-shade-xl cursor-pointer grid items-end hover:scale-[1.02] transition-transform">
{post.thumbnail.data ? (
<Img
className="rounded-md rounded-b-none"
image={post.thumbnail.data.attributes}
quality={ImageQuality.Medium}
/>
) : (
<div className="w-full aspect-[3/2] bg-light rounded-lg"></div>
)}
<div className="linearbg-obi fine:drop-shadow-shade-lg rounded-b-md top-full transition-opacity z-20 grid p-4 gap-2">
<div className="grid grid-flow-col w-full">
{post.date && (
<p className="mobile:text-xs text-sm">
<span className="material-icons !text-base translate-y-[.15em] mr-1">
event
</span>
{prettyDate(post.date)}
</p>
)}
</div>
<div>
{post.translations.length > 0 ? (
<>
<h1 className="text-xl">{post.translations[0].title}</h1>
<p>{post.translations[0].excerpt}</p>
</>
) : (
<h1 className="text-lg">{prettySlug(post.slug)}</h1>
)}
</div>
<div className="grid grid-flow-col gap-1 overflow-x-scroll webkit-scrollbar:w-0 [scrollbar-width:none] place-content-start">
{post.categories.data.map((category) => (
<Chip key={category.id} className="text-sm">
{category.attributes.short}
</Chip>
))}
</div>
</div>
</div>
</Link>
);
}

View File

@ -1155,8 +1155,12 @@ query getPost($slug: String, $language_code: String) {
id id
attributes { attributes {
slug slug
publishedAt
updatedAt updatedAt
date {
year
month
day
}
authors { authors {
data { data {
id id
@ -1200,8 +1204,20 @@ query getPost($slug: String, $language_code: String) {
} }
} }
hidden hidden
thumbnail {
data {
attributes {
name
alternativeText
caption
width
height
url
}
}
}
translations(filters: { language: { code: { eq: $language_code } } }) { translations(filters: { language: { code: { eq: $language_code } } }) {
Status status
title title
excerpt excerpt
thumbnail { thumbnail {
@ -1222,3 +1238,66 @@ query getPost($slug: String, $language_code: String) {
} }
} }
} }
query getPostsSlugs {
posts(filters: { hidden: { eq: false } }) {
data {
id
attributes {
slug
}
}
}
}
query getPostsPreview($language_code: String) {
posts(filters: { hidden: { eq: false } }) {
data {
id
attributes {
slug
date {
year
month
day
}
categories {
data {
id
attributes {
short
}
}
}
thumbnail {
data {
attributes {
name
alternativeText
caption
width
height
url
}
}
}
translations(filters: { language: { code: { eq: $language_code } } }) {
title
excerpt
thumbnail {
data {
attributes {
name
alternativeText
caption
width
height
url
}
}
}
}
}
}
}
}

View File

@ -1554,8 +1554,13 @@ export type GetPostQuery = {
attributes: { attributes: {
__typename: "Post"; __typename: "Post";
slug: string; slug: string;
publishedAt: any;
updatedAt: any; updatedAt: any;
date: {
__typename: "ComponentBasicsDatepicker";
year: number;
month: number;
day: number;
};
hidden: boolean; hidden: boolean;
authors: { authors: {
__typename: "RecorderRelationResponseCollection"; __typename: "RecorderRelationResponseCollection";
@ -1609,9 +1614,24 @@ export type GetPostQuery = {
}; };
}>; }>;
}; };
thumbnail: {
__typename: "UploadFileEntityResponse";
data: {
__typename: "UploadFileEntity";
attributes: {
__typename: "UploadFile";
name: string;
alternativeText: string;
caption: string;
width: number;
height: number;
url: string;
};
};
};
translations: Array<{ translations: Array<{
__typename: "ComponentTranslationsPosts"; __typename: "ComponentTranslationsPosts";
Status: Enum_Componenttranslationsposts_Status; status: Enum_Componenttranslationsposts_Status;
title: string; title: string;
excerpt: string; excerpt: string;
body: string; body: string;
@ -1635,3 +1655,85 @@ export type GetPostQuery = {
}>; }>;
}; };
}; };
export type GetPostsSlugsQueryVariables = Exact<{ [key: string]: never }>;
export type GetPostsSlugsQuery = {
__typename: "Query";
posts: {
__typename: "PostEntityResponseCollection";
data: Array<{
__typename: "PostEntity";
id: string;
attributes: { __typename: "Post"; slug: string };
}>;
};
};
export type GetPostsPreviewQueryVariables = Exact<{
language_code: InputMaybe<Scalars["String"]>;
}>;
export type GetPostsPreviewQuery = {
__typename: "Query";
posts: {
__typename: "PostEntityResponseCollection";
data: Array<{
__typename: "PostEntity";
id: string;
attributes: {
__typename: "Post";
slug: string;
date: {
__typename: "ComponentBasicsDatepicker";
year: number;
month: number;
day: number;
};
categories: {
__typename: "CategoryRelationResponseCollection";
data: Array<{
__typename: "CategoryEntity";
id: string;
attributes: { __typename: "Category"; short: string };
}>;
};
thumbnail: {
__typename: "UploadFileEntityResponse";
data: {
__typename: "UploadFileEntity";
attributes: {
__typename: "UploadFile";
name: string;
alternativeText: string;
caption: string;
width: number;
height: number;
url: string;
};
};
};
translations: Array<{
__typename: "ComponentTranslationsPosts";
title: string;
excerpt: string;
thumbnail: {
__typename: "UploadFileEntityResponse";
data: {
__typename: "UploadFileEntity";
attributes: {
__typename: "UploadFile";
name: string;
alternativeText: string;
caption: string;
width: number;
height: number;
url: string;
};
};
};
}>;
};
}>;
};
};

View File

@ -25,6 +25,10 @@ import {
GetLibraryItemsSlugsQueryVariables, GetLibraryItemsSlugsQueryVariables,
GetPostQuery, GetPostQuery,
GetPostQueryVariables, GetPostQueryVariables,
GetPostsPreviewQuery,
GetPostsPreviewQueryVariables,
GetPostsSlugsQuery,
GetPostsSlugsQueryVariables,
GetWebsiteInterfaceQuery, GetWebsiteInterfaceQuery,
GetWebsiteInterfaceQueryVariables, GetWebsiteInterfaceQueryVariables,
} from "graphql/operations-types"; } from "graphql/operations-types";
@ -150,3 +154,17 @@ export async function getPost(
const query = getQueryFromOperations("getPost"); const query = getQueryFromOperations("getPost");
return await graphQL(query, JSON.stringify(variables)); return await graphQL(query, JSON.stringify(variables));
} }
export async function getPostsSlugs(
variables: GetPostsSlugsQueryVariables
): Promise<GetPostsSlugsQuery> {
const query = getQueryFromOperations("getPostsSlugs");
return await graphQL(query, JSON.stringify(variables));
}
export async function getPostsPreview(
variables: GetPostsPreviewQueryVariables
): Promise<GetPostsPreviewQuery> {
const query = getQueryFromOperations("getPostsPreview");
return await graphQL(query, JSON.stringify(variables));
}

View File

@ -26,11 +26,6 @@ type ResponseCollectionMeta {
pagination: Pagination! pagination: Pagination!
} }
enum PublicationState {
LIVE
PREVIEW
}
input IDFilterInput { input IDFilterInput {
and: [ID] and: [ID]
or: [ID] or: [ID]
@ -387,6 +382,8 @@ input ComponentCollectionsComponentLibraryObiBeltInput {
back: ID back: ID
full: ID full: ID
inside_full: ID inside_full: ID
flap_front: ID
flap_back: ID
} }
type ComponentCollectionsComponentLibraryObiBelt { type ComponentCollectionsComponentLibraryObiBelt {
@ -396,6 +393,8 @@ type ComponentCollectionsComponentLibraryObiBelt {
back: UploadFileEntityResponse back: UploadFileEntityResponse
full: UploadFileEntityResponse full: UploadFileEntityResponse
inside_full: UploadFileEntityResponse inside_full: UploadFileEntityResponse
flap_front: UploadFileEntityResponse
flap_back: UploadFileEntityResponse
} }
input ComponentCollectionsComponentTitlesFiltersInput { input ComponentCollectionsComponentTitlesFiltersInput {
@ -1057,11 +1056,11 @@ enum ENUM_COMPONENTTRANSLATIONSPOSTS_STATUS {
} }
input ComponentTranslationsPostsFiltersInput { input ComponentTranslationsPostsFiltersInput {
Status: StringFilterInput
title: StringFilterInput title: StringFilterInput
excerpt: StringFilterInput excerpt: StringFilterInput
body: StringFilterInput body: StringFilterInput
language: LanguageFiltersInput language: LanguageFiltersInput
status: StringFilterInput
and: [ComponentTranslationsPostsFiltersInput] and: [ComponentTranslationsPostsFiltersInput]
or: [ComponentTranslationsPostsFiltersInput] or: [ComponentTranslationsPostsFiltersInput]
not: ComponentTranslationsPostsFiltersInput not: ComponentTranslationsPostsFiltersInput
@ -1069,22 +1068,22 @@ input ComponentTranslationsPostsFiltersInput {
input ComponentTranslationsPostsInput { input ComponentTranslationsPostsInput {
id: ID id: ID
Status: ENUM_COMPONENTTRANSLATIONSPOSTS_STATUS
title: String title: String
excerpt: String excerpt: String
thumbnail: ID thumbnail: ID
body: String body: String
language: ID language: ID
status: ENUM_COMPONENTTRANSLATIONSPOSTS_STATUS
} }
type ComponentTranslationsPosts { type ComponentTranslationsPosts {
id: ID! id: ID!
Status: ENUM_COMPONENTTRANSLATIONSPOSTS_STATUS!
title: String! title: String!
excerpt: String excerpt: String
thumbnail: UploadFileEntityResponse thumbnail: UploadFileEntityResponse
body: String body: String
language: LanguageEntityResponse language: LanguageEntityResponse
status: ENUM_COMPONENTTRANSLATIONSPOSTS_STATUS!
} }
enum ENUM_COMPONENTTRANSLATIONSSCANSET_STATUS { enum ENUM_COMPONENTTRANSLATIONSSCANSET_STATUS {
@ -2126,7 +2125,6 @@ input PostFiltersInput {
hidden: BooleanFilterInput hidden: BooleanFilterInput
createdAt: DateTimeFilterInput createdAt: DateTimeFilterInput
updatedAt: DateTimeFilterInput updatedAt: DateTimeFilterInput
publishedAt: DateTimeFilterInput
and: [PostFiltersInput] and: [PostFiltersInput]
or: [PostFiltersInput] or: [PostFiltersInput]
not: PostFiltersInput not: PostFiltersInput
@ -2138,7 +2136,7 @@ input PostInput {
categories: [ID] categories: [ID]
translations: [ComponentTranslationsPostsInput] translations: [ComponentTranslationsPostsInput]
hidden: Boolean hidden: Boolean
publishedAt: DateTime thumbnail: ID
} }
type Post { type Post {
@ -2159,9 +2157,9 @@ type Post {
sort: [String] = [] sort: [String] = []
): [ComponentTranslationsPosts] ): [ComponentTranslationsPosts]
hidden: Boolean! hidden: Boolean!
thumbnail: UploadFileEntityResponse
createdAt: DateTime createdAt: DateTime
updatedAt: DateTime updatedAt: DateTime
publishedAt: DateTime
} }
type PostEntity { type PostEntity {
@ -3206,7 +3204,6 @@ type Query {
filters: PostFiltersInput filters: PostFiltersInput
pagination: PaginationArg = {} pagination: PaginationArg = {}
sort: [String] = [] sort: [String] = []
publicationState: PublicationState = LIVE
): PostEntityResponseCollection ): PostEntityResponseCollection
rangedContent(id: ID): RangedContentEntityResponse rangedContent(id: ID): RangedContentEntityResponse
rangedContents( rangedContents(

View File

@ -40,7 +40,28 @@ export default function ContentIndex(props: ContentIndexProps): JSX.Element {
className="mb-10" className="mb-10"
/> />
<div className="grid place-items-center"> <div className="grid place-items-center">
<ThumbnailHeader content={content} langui={langui} /> <ThumbnailHeader
thumbnail={content.thumbnail}
pre_title={
content.titles.length > 0 ? content.titles[0].pre_title : undefined
}
title={
content.titles.length > 0
? content.titles[0].title
: prettySlug(content.slug)
}
subtitle={
content.titles.length > 0 ? content.titles[0].subtitle : undefined
}
description={
content.titles.length > 0
? content.titles[0].description
: undefined
}
type={content.type}
categories={content.categories}
langui={langui}
/>
<HorizontalLine /> <HorizontalLine />

View File

@ -1,9 +1,6 @@
import { GetStaticPaths, GetStaticProps } from "next"; import { GetStaticPaths, GetStaticProps } from "next";
import { getContentsSlugs, getContentText } from "graphql/operations"; import { getContentsSlugs, getContentText } from "graphql/operations";
import { import { GetContentTextQuery } from "graphql/operations-types";
Enum_Componentsetstextset_Status,
GetContentTextQuery,
} from "graphql/operations-types";
import ContentPanel from "components/Panels/ContentPanel"; import ContentPanel from "components/Panels/ContentPanel";
import HorizontalLine from "components/HorizontalLine"; import HorizontalLine from "components/HorizontalLine";
import SubPanel from "components/Panels/SubPanel"; import SubPanel from "components/Panels/SubPanel";
@ -158,13 +155,34 @@ export default function ContentRead(props: ContentReadProps): JSX.Element {
<ContentPanel> <ContentPanel>
<ReturnButton <ReturnButton
href={`/contents/${content.slug}`} href={`/contents/${content.slug}`}
title={"Content"} title={langui.content}
langui={langui} langui={langui}
displayOn={ReturnButtonType.Mobile} displayOn={ReturnButtonType.Mobile}
className="mb-10" className="mb-10"
/> />
<div className="grid place-items-center"> <div className="grid place-items-center">
<ThumbnailHeader content={content} langui={langui} /> <ThumbnailHeader
thumbnail={content.thumbnail}
pre_title={
content.titles.length > 0 ? content.titles[0].pre_title : undefined
}
title={
content.titles.length > 0
? content.titles[0].title
: prettySlug(content.slug)
}
subtitle={
content.titles.length > 0 ? content.titles[0].subtitle : undefined
}
description={
content.titles.length > 0
? content.titles[0].description
: undefined
}
type={content.type}
categories={content.categories}
langui={langui}
/>
<HorizontalLine /> <HorizontalLine />

165
src/pages/news/[slug].tsx Normal file
View File

@ -0,0 +1,165 @@
import AppLayout from "components/AppLayout";
import Chip from "components/Chip";
import ThumbnailHeader from "components/Content/ThumbnailHeader";
import HorizontalLine from "components/HorizontalLine";
import Markdawn from "components/Markdown/Markdawn";
import TOC from "components/Markdown/TOC";
import ReturnButton, {
ReturnButtonType,
} from "components/PanelComponents/ReturnButton";
import ContentPanel from "components/Panels/ContentPanel";
import SubPanel from "components/Panels/SubPanel";
import RecorderChip from "components/RecorderChip";
import ToolTip from "components/ToolTip";
import { getPost, getPostsSlugs } from "graphql/operations";
import { GetPostQuery } from "graphql/operations-types";
import { GetStaticPaths, GetStaticProps } from "next";
import { useRouter } from "next/router";
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
import { prettySlug, getStatusDescription } from "queries/helpers";
interface PostProps extends AppStaticProps {
post: GetPostQuery["posts"]["data"][number]["attributes"];
postId: GetPostQuery["posts"]["data"][number]["id"];
}
export default function LibrarySlug(props: PostProps): JSX.Element {
const { post, postId, langui } = props;
const router = useRouter();
const subPanel = (
<SubPanel>
<ReturnButton
href="/news"
title={langui.news}
langui={langui}
displayOn={ReturnButtonType.Desktop}
horizontalLine
/>
{post.translations.length > 0 && (
<div className="grid grid-flow-col place-items-center place-content-center gap-2">
<p className="font-headers">{langui.status}:</p>
<ToolTip
content={getStatusDescription(post.translations[0].status, langui)}
maxWidth={"20rem"}
>
<Chip>{post.translations[0].status}</Chip>
</ToolTip>
</div>
)}
{post.authors.data.length > 0 && (
<div>
<p className="font-headers">{"Authors"}:</p>
<div className="grid place-items-center place-content-center gap-2">
{post.authors.data.map((author) => (
<RecorderChip key={author.id} langui={langui} recorder={author} />
))}
</div>
</div>
)}
<HorizontalLine />
{post.translations.length > 0 && post.translations[0].body && (
<TOC
text={post.translations[0].body}
router={router}
title={post.translations[0].title}
/>
)}
</SubPanel>
);
const contentPanel = (
<ContentPanel>
<ReturnButton
href="/news"
title={langui.news}
langui={langui}
displayOn={ReturnButtonType.Mobile}
className="mb-10"
/>
<ThumbnailHeader
thumbnail={
post.translations.length > 0 && post.translations[0].thumbnail.data
? post.translations[0].thumbnail
: post.thumbnail
}
title={
post.translations.length > 0
? post.translations[0].title
: prettySlug(post.slug)
}
description={
post.translations.length > 0
? post.translations[0].excerpt
: undefined
}
langui={langui}
categories={post.categories}
/>
<HorizontalLine />
{post.translations.length > 0 && post.translations[0].body && (
<Markdawn text={post.translations[0].body} />
)}
</ContentPanel>
);
return (
<AppLayout
navTitle={langui.news}
title={
post.translations.length > 0
? post.translations[0].title
: prettySlug(post.slug)
}
contentPanel={contentPanel}
subPanel={subPanel}
thumbnail={post.translations[0].thumbnail.data?.attributes}
{...props}
/>
);
}
export const getStaticProps: GetStaticProps = async (context) => {
const post = (
await getPost({
slug: context.params?.slug?.toString() || "",
language_code: context.locale || "en",
})
).posts.data[0];
const props: PostProps = {
...(await getAppStaticProps(context)),
post: post.attributes,
postId: post.id,
};
return {
props: props,
};
};
export const getStaticPaths: GetStaticPaths = async (context) => {
type Path = {
params: {
slug: string;
};
locale: string;
};
const data = await getPostsSlugs({});
const paths: Path[] = [];
data.posts.data.map((item) => {
context.locales?.map((local) => {
paths.push({ params: { slug: item.attributes.slug }, locale: local });
});
});
return {
paths,
fallback: false,
};
};

View File

@ -3,11 +3,17 @@ import PanelHeader from "components/PanelComponents/PanelHeader";
import { GetStaticProps } from "next"; import { GetStaticProps } from "next";
import AppLayout from "components/AppLayout"; import AppLayout from "components/AppLayout";
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
import { GetPostsPreviewQuery } from "graphql/operations-types";
import { getPostsPreview } from "graphql/operations";
import ContentPanel, { ContentPanelWidthSizes } from "components/Panels/ContentPanel";
import PostsPreview from "components/News/PostsPreview";
interface NewsProps extends AppStaticProps {} interface NewsProps extends AppStaticProps {
posts: GetPostsPreviewQuery["posts"]["data"];
}
export default function News(props: NewsProps): JSX.Element { export default function News(props: NewsProps): JSX.Element {
const { langui } = props; const { langui, posts } = props;
const subPanel = ( const subPanel = (
<SubPanel> <SubPanel>
<PanelHeader <PanelHeader
@ -18,12 +24,32 @@ export default function News(props: NewsProps): JSX.Element {
</SubPanel> </SubPanel>
); );
return <AppLayout navTitle={langui.news} subPanel={subPanel} {...props} />; const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.large}>
<div className="grid gap-8 items-end grid-cols-2 desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))]">
{posts.map((post) => (
<PostsPreview key={post.id} post={post.attributes} />
))}
</div>
</ContentPanel>
);
return (
<AppLayout
navTitle={langui.news}
subPanel={subPanel}
contentPanel={contentPanel}
{...props}
/>
);
} }
export const getStaticProps: GetStaticProps = async (context) => { export const getStaticProps: GetStaticProps = async (context) => {
const props: NewsProps = { const props: NewsProps = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
posts: await (
await getPostsPreview({ language_code: context.locale || "en" })
).posts.data,
}; };
return { return {
props: props, props: props,