feat: Reworked document pages (#194)

* Moved document pages from /wiki sub-path to root path.
* Added a blog index page.
* Added a page header to document pages.
* Added redirects for the moved pages.
* Removed wiki index page, because it will be a document page in the future.
* Removed terms of service and privacy policy pages, because these will be document pages in the future.
This commit is contained in:
Mani
2024-03-03 21:44:28 +01:00
parent 855443c6c8
commit 055bb7bea7
11 changed files with 129 additions and 111 deletions
+14
View File
@@ -65,6 +65,20 @@ const nextConfig = {
] ]
} }
]; ];
},
async redirects() {
return [
{
source: "/wiki/blog/:slug*",
destination: "/blog/:slug*",
permanent: true,
},
{
source: "/wiki/status/:slug*",
destination: "/blog/status_:slug*",
permanent: true,
},
];
} }
}; };
+5
View File
@@ -34,6 +34,7 @@ const StyledMarkdown = styled.div`
} }
& p { & p {
margin-top: 0;
margin-bottom: 16px; margin-bottom: 16px;
} }
@@ -100,6 +101,10 @@ const StyledMarkdown = styled.div`
& ${Card} { & ${Card} {
margin-bottom: 16px; margin-bottom: 16px;
} }
& img {
border-radius: ${theme.scalars.borderRadiusCard};
}
`; `;
interface MarkdownProps { interface MarkdownProps {
+18 -17
View File
@@ -156,6 +156,7 @@ export type Image = {
export type Page = { export type Page = {
body: Scalars['String']; body: Scalars['String'];
created_at: Scalars['String'];
id: Maybe<Scalars['Int']>; id: Maybe<Scalars['Int']>;
name: Scalars['String']; name: Scalars['String'];
slug: Scalars['String']; slug: Scalars['String'];
@@ -705,6 +706,18 @@ export type RandomThemeQueryVariables = Exact<{ [key: string]: never; }>;
export type RandomThemeQuery = { searchTheme: { data: Array<{ slug: string, type: string, sequence: number | null, group: string | null, id: number, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ basename: string, id: number, tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap, audio: { basename: string } }> }>, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null }> } }; export type RandomThemeQuery = { searchTheme: { data: Array<{ slug: string, type: string, sequence: number | null, group: string | null, id: number, entries: Array<{ version: number | null, episodes: string | null, spoiler: boolean, nsfw: boolean, videos: Array<{ basename: string, id: number, tags: string, resolution: number | null, nc: boolean, subbed: boolean, lyrics: boolean, uncen: boolean, source: VideoSource | null, overlap: VideoOverlap, audio: { basename: string } }> }>, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null }> } };
export type DocumentPageQueryVariables = Exact<{
pageSlug: Scalars['String'];
}>;
export type DocumentPageQuery = { page: { name: string, body: string, created_at: string } | null };
export type DocumentPageAllQueryVariables = Exact<{ [key: string]: never; }>;
export type DocumentPageAllQuery = { pageAll: Array<{ slug: string }> };
export type DumpIndexPageQueryVariables = Exact<{ [key: string]: never; }>; export type DumpIndexPageQueryVariables = Exact<{ [key: string]: never; }>;
@@ -769,6 +782,11 @@ export type ArtistIndexPageQueryVariables = Exact<{ [key: string]: never; }>;
export type ArtistIndexPageQuery = { artistAll: Array<{ slug: string, name: string }> }; export type ArtistIndexPageQuery = { artistAll: Array<{ slug: string, name: string }> };
export type DocumentIndexPageQueryVariables = Exact<{ [key: string]: never; }>;
export type DocumentIndexPageQuery = { pageAll: Array<{ slug: string, name: string, created_at: string }> };
export type CharacterFragmentFragment = { id: number, seed: number, name: string, source: string, image: string, theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null }; export type CharacterFragmentFragment = { id: number, seed: number, name: string, source: string, image: string, theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null };
export type RoundFragmentFragment = { tier: number, name: string | null, pairings: Array<{ order: number, group: number, votesA: number | null, votesB: number | null, characterA: { id: number, seed: number, name: string, source: string, image: string, theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null }, characterB: { id: number, seed: number, name: string, source: string, image: string, theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null } }> }; export type RoundFragmentFragment = { tier: number, name: string | null, pairings: Array<{ order: number, group: number, votesA: number | null, votesB: number | null, characterA: { id: number, seed: number, name: string, source: string, image: string, theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null }, characterB: { id: number, seed: number, name: string, source: string, image: string, theme: { slug: string, type: string, sequence: number | null, group: string | null, id: number, anime: { slug: string, name: string, images: Array<{ link: string, facet: string | null }> }, song: { title: string | null, performances: Array<{ as: string | null, artist: { slug: string, name: string } }> } | null, entries: Array<{ version: number | null, videos: Array<{ id: number, basename: string, tags: string, audio: { basename: string } }> }> } | null } }> };
@@ -901,23 +919,6 @@ export type StudioIndexPageQueryVariables = Exact<{ [key: string]: never; }>;
export type StudioIndexPageQuery = { studioAll: Array<{ slug: string, name: string }> }; export type StudioIndexPageQuery = { studioAll: Array<{ slug: string, name: string }> };
export type DocumentPageQueryVariables = Exact<{
pageSlug: Scalars['String'];
}>;
export type DocumentPageQuery = { page: { name: string, body: string } | null };
export type DocumentPageAllQueryVariables = Exact<{ [key: string]: never; }>;
export type DocumentPageAllQuery = { pageAll: Array<{ slug: string }> };
export type DocumentIndexPageQueryVariables = Exact<{ [key: string]: never; }>;
export type DocumentIndexPageQuery = { pageAll: Array<{ slug: string, name: string }> };
export type SeasonDetailPageQueryVariables = Exact<{ export type SeasonDetailPageQueryVariables = Exact<{
year?: InputMaybe<Scalars['Int']>; year?: InputMaybe<Scalars['Int']>;
season: Scalars['String']; season: Scalars['String'];
+2 -2
View File
@@ -65,11 +65,11 @@ const resolvers: IResolvers = {
transformer: (seasons, _, { year }) => seasons.map((season: string) => ({ value: season, year: { value: year } })) transformer: (seasons, _, { year }) => seasons.map((season: string) => ({ value: season, year: { value: year } }))
}), }),
page: apiResolver({ page: apiResolver({
endpoint: (_, { slug }) => `/page/${slug}?fields[page]=id,slug,name,body`, endpoint: (_, { slug }) => `/page/${slug}?fields[page]=id,slug,name,body,created_at`,
extractor: (result) => result.page extractor: (result) => result.page
}), }),
pageAll: apiResolver({ pageAll: apiResolver({
endpoint: () => `/page`, endpoint: () => `/page?fields[page]=id,slug,name,body,created_at`,
extractor: (result) => result.pages, extractor: (result) => result.pages,
pagination: true pagination: true
}), }),
+1
View File
@@ -216,6 +216,7 @@ const typeDefs = `
slug: String! slug: String!
name: String! name: String!
body: String! body: String!
created_at: String!
} }
type FeaturedTheme { type FeaturedTheme {
@@ -59,6 +59,7 @@ interface DocumentPageParams extends ParsedUrlQuery {
export default function DocumentPage({ page }: DocumentPageProps) { export default function DocumentPage({ page }: DocumentPageProps) {
return ( return (
<> <>
<PageHeader title={page.name} author={page.source.frontmatter?.author} createdAt={page.created_at} />
<StyledGrid> <StyledGrid>
<SEO title={page.name}/> <SEO title={page.name}/>
<Markdown source={page.source} /> <Markdown source={page.source} />
@@ -72,12 +73,29 @@ export default function DocumentPage({ page }: DocumentPageProps) {
); );
} }
interface PageHeaderProps {
title: string;
author?: string;
createdAt: string;
}
function PageHeader({ title, author, createdAt }: PageHeaderProps) {
return (
<div>
<Text variant="h1">{title}</Text>
{ !!author && <Text variant="small" color="text-muted">By: {author} &bull; </Text> }
<Text variant="small" color="text-muted">{new Date(createdAt).toLocaleDateString("en", { dateStyle: "long" })}</Text>
</div>
);
}
export const getStaticProps: GetStaticProps<DocumentPageProps, DocumentPageParams> = async ({ params }) => { export const getStaticProps: GetStaticProps<DocumentPageProps, DocumentPageParams> = async ({ params }) => {
const { data, apiRequests } = await fetchData<DocumentPageQuery, DocumentPageQueryVariables>(gql` const { data, apiRequests } = await fetchData<DocumentPageQuery, DocumentPageQueryVariables>(gql`
query DocumentPage($pageSlug: String!) { query DocumentPage($pageSlug: String!) {
page(slug: $pageSlug) { page(slug: $pageSlug) {
name name
body body
created_at
} }
} }
`, params && { `, params && {
-22
View File
@@ -1,22 +0,0 @@
import type { GetStaticProps } from "next";
import getSharedPageProps from "utils/getSharedPageProps";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import type { DocumentPageProps } from "pages/wiki/[...pageSlug]";
import serializeMarkdown from "utils/serializeMarkdown";
export { default } from "pages/wiki/[...pageSlug]/index";
export const getStaticProps: GetStaticProps<DocumentPageProps> = async () => {
const markdown = readFileSync(join(process.cwd(), "src/res/markdown/policy.md"), "utf-8");
return {
props: {
...getSharedPageProps(),
page: {
name: "Privacy Policy",
...await serializeMarkdown(markdown),
}
},
};
};
-22
View File
@@ -1,22 +0,0 @@
import type { GetStaticProps } from "next";
import getSharedPageProps from "utils/getSharedPageProps";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import type { DocumentPageProps } from "pages/wiki/[...pageSlug]";
import serializeMarkdown from "utils/serializeMarkdown";
export { default } from "pages/wiki/[...pageSlug]/index";
export const getStaticProps: GetStaticProps<DocumentPageProps> = async () => {
const markdown = readFileSync(join(process.cwd(), "src/res/markdown/terms.md"), "utf-8");
return {
props: {
...getSharedPageProps(),
page: {
name: "Terms of Service",
...await serializeMarkdown(markdown),
}
},
};
};
+70
View File
@@ -0,0 +1,70 @@
import { fetchData } from "lib/server";
import { SEO } from "components/seo";
import { Text } from "components/text";
import type { SharedPageProps } from "utils/getSharedPageProps";
import getSharedPageProps from "utils/getSharedPageProps";
import type { GetStaticProps } from "next";
import gql from "graphql-tag";
import type { DocumentIndexPageQuery } from "generated/graphql";
import { BackToTopButton, Button } from "components/button";
import { Card } from "components/card";
import Link from "next/link";
import { Column, Row } from "components/box";
import { groupBy } from "lodash-es";
import { Fragment } from "react";
interface DocumentIndexPageProps extends SharedPageProps, DocumentIndexPageQuery {}
export default function DocumentIndexPage({ pageAll }: DocumentIndexPageProps) {
const pageGroups = Object.entries(
groupBy(pageAll, (page) => new Date(page.created_at).getFullYear())
).sort((a, b) => parseInt(b[0]) - parseInt(a[0]));
return <>
<SEO title="Blog"/>
<BackToTopButton/>
<Text variant="h1">Blog</Text>
<Text>On our blog we share the latest updates to the AnimeThemes site as well as other interesting news surrounding the project.</Text>
{pageGroups.map(([year, pages], index) => (
<Fragment key={year}>
{index > 0 && <Text variant="h2">{year}</Text>}
{pages.map((page) => (
<Link key={page.slug} href={`/${page.slug}`}>
<Card>
<Row style={{ "--justify-content": "space-between", "--align-items": "center", "--gap": "16px" }}>
<Column style={{ "--gap": "8px" }}>
<Text color="text-primary" link>{page.name}</Text>
<Text variant="small" color="text-muted">Posted on: {new Date(page.created_at).toLocaleDateString("en", { dateStyle: "long" })}</Text>
</Column>
<Button variant="silent">Read more</Button>
</Row>
</Card>
</Link>
))}
</Fragment>
))}
</>;
}
export const getStaticProps: GetStaticProps<DocumentIndexPageProps> = async () => {
const { data, apiRequests } = await fetchData<DocumentIndexPageQuery>(gql`
query DocumentIndexPage {
pageAll {
slug
name
created_at
}
}
`);
return {
props: {
...getSharedPageProps(apiRequests),
pageAll: data.pageAll
.filter((page) => page.slug.startsWith("blog/") || page.slug.startsWith("status/"))
.sort((a, b) => Date.parse(b.created_at) - Date.parse(a.created_at))
},
// Revalidate after 3 hours (= 10800 seconds).
revalidate: 10800
};
};
-48
View File
@@ -1,48 +0,0 @@
import { fetchData } from "lib/server";
import { SEO } from "components/seo";
import { Text } from "components/text";
import type { SharedPageProps } from "utils/getSharedPageProps";
import getSharedPageProps from "utils/getSharedPageProps";
import type { GetStaticProps } from "next";
import gql from "graphql-tag";
import type { DocumentIndexPageQuery } from "generated/graphql";
import { TextLink } from "components/text/TextLink";
import { AlphabeticalIndex } from "components/index";
import { BackToTopButton } from "components/button";
interface DocumentIndexPageProps extends SharedPageProps, DocumentIndexPageQuery {}
export default function DocumentIndexPage({ pageAll }: DocumentIndexPageProps) {
return <>
<SEO title="Wiki"/>
<BackToTopButton/>
<Text variant="h1">Wiki</Text>
<AlphabeticalIndex items={pageAll}>
{(page) => (
<TextLink key={page.slug} href={`/wiki/${page.slug}`}>
{page.name}
</TextLink>
)}
</AlphabeticalIndex>
</>;
}
export const getStaticProps: GetStaticProps<DocumentIndexPageProps> = async () => {
const { data, apiRequests } = await fetchData<DocumentIndexPageQuery>(gql`
query DocumentIndexPage {
pageAll {
slug
name
}
}
`);
return {
props: {
...getSharedPageProps(apiRequests),
pageAll: data.pageAll
},
// Revalidate after 3 hours (= 10800 seconds).
revalidate: 10800
};
};
+1
View File
@@ -20,6 +20,7 @@ export default async function serializeMarkdown(markdown: Compatible) {
[rehypePrettyCode, { theme: "github-dark-dimmed" }], [rehypePrettyCode, { theme: "github-dark-dimmed" }],
], ],
}, },
parseFrontmatter: true,
}); });
return { return {