mirror of
https://github.com/AnimeThemes/animethemes-web.git
synced 2026-07-11 01:24:31 +02:00
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:
@@ -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,
|
||||||
|
},
|
||||||
|
];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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'];
|
||||||
|
|||||||
@@ -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
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -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} • </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 && {
|
||||||
@@ -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),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -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),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -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
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -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
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user