Merge pull request #10 from Accords-Library/develop

Better Markdawn and auto-generated tables of content
This commit is contained in:
DrMint 2022-03-12 14:04:12 +01:00 committed by GitHub
commit 56c07b715d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 837 additions and 259 deletions

50
package-lock.json generated
View File

@ -20,7 +20,6 @@
"turndown": "^7.1.1"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.2",
"@types/node": "17.0.21",
"@types/react": "17.0.40",
"@types/react-dom": "^17.0.13",
@ -455,20 +454,6 @@
"integrity": "sha512-JLo+Y592QzIE+q7Dl2pMUtt4q8SKYI5jDrZxrozEQxnGVOyYE+GWK9eLkwTaeN9DDctlaRAQ3TBmzZ1qdLE30A==",
"dev": true
},
"node_modules/@tailwindcss/typography": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.2.tgz",
"integrity": "sha512-coq8DBABRPFcVhVIk6IbKyyHUt7YTEC/C992tatFB+yEx5WGBQrCgsSFjxHUr8AWXphWckadVJbominEduYBqw==",
"dev": true,
"dependencies": {
"lodash.castarray": "^4.4.0",
"lodash.isplainobject": "^4.0.6",
"lodash.merge": "^4.6.2"
},
"peerDependencies": {
"tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || insiders"
}
},
"node_modules/@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@ -2405,18 +2390,6 @@
"node": ">=4"
}
},
"node_modules/lodash.castarray": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
"integrity": "sha1-wCUTUV4wna3dTCTGDP3c9ZdtkRU=",
"dev": true
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=",
"dev": true
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@ -3950,17 +3923,6 @@
"integrity": "sha512-JLo+Y592QzIE+q7Dl2pMUtt4q8SKYI5jDrZxrozEQxnGVOyYE+GWK9eLkwTaeN9DDctlaRAQ3TBmzZ1qdLE30A==",
"dev": true
},
"@tailwindcss/typography": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.2.tgz",
"integrity": "sha512-coq8DBABRPFcVhVIk6IbKyyHUt7YTEC/C992tatFB+yEx5WGBQrCgsSFjxHUr8AWXphWckadVJbominEduYBqw==",
"dev": true,
"requires": {
"lodash.castarray": "^4.4.0",
"lodash.isplainobject": "^4.0.6",
"lodash.merge": "^4.6.2"
}
},
"@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@ -5394,18 +5356,6 @@
"path-exists": "^3.0.0"
}
},
"lodash.castarray": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
"integrity": "sha1-wCUTUV4wna3dTCTGDP3c9ZdtkRU=",
"dev": true
},
"lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=",
"dev": true
},
"lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",

View File

@ -22,7 +22,6 @@
"turndown": "^7.1.1"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.2",
"@types/node": "17.0.21",
"@types/react": "17.0.40",
"@types/react-dom": "^17.0.13",

View File

@ -2,10 +2,11 @@ import {
GetContentQuery,
GetWebsiteInterfaceQuery,
} from "graphql/operations-types";
import { prettySlug } from "queries/helpers";
import { prettyinlineTitle, prettySlug, slugify } from "queries/helpers";
import Button from "components/Button";
import Img, { ImageQuality } from "components/Img";
import InsetBox from "components/InsetBox";
import Chip from "components/Chip";
export type ThumbnailHeaderProps = {
content: {
@ -39,7 +40,18 @@ export default function ThumbnailHeader(
<div className="w-full aspect-[4/3] bg-light rounded-xl"></div>
)}
</div>
<div className="grid place-items-center text-center">
<div
id={slugify(
content.titles.length > 0
? prettyinlineTitle(
content.titles[0].pre_title,
content.titles[0].title,
content.titles[0].subtitle
)
: prettySlug(content.slug)
)}
className="grid place-items-center text-center"
>
{content.titles.length > 0 ? (
<>
<p className="text-2xl">{content.titles[0].pre_title}</p>
@ -54,22 +66,26 @@ export default function ThumbnailHeader(
<div className="grid grid-flow-col gap-8">
{content.type && (
<div className="grid place-items-center place-content-start gap-2">
<div className="flex flex-col place-items-center gap-2">
<h3 className="text-xl">{langui.type}</h3>
<Button>
{content.type.data.attributes.titles.length > 0
? content.type.data.attributes.titles[0].title
: prettySlug(content.type.data.attributes.slug)}
</Button>
<div className="flex flex-row flex-wrap">
<Chip>
{content.type.data.attributes.titles.length > 0
? content.type.data.attributes.titles[0].title
: prettySlug(content.type.data.attributes.slug)}
</Chip>
</div>
</div>
)}
{content.categories.data.length > 0 && (
<div className="grid place-items-center place-content-start gap-2">
<div className="flex flex-col place-items-center gap-2">
<h3 className="text-xl">{langui.categories}</h3>
{content.categories.data.map((category) => (
<Button key={category.id}>{category.attributes.name}</Button>
))}
<div className="flex flex-row flex-wrap place-content-center gap-2">
{content.categories.data.map((category) => (
<Chip key={category.id}>{category.attributes.name}</Chip>
))}
</div>
</div>
)}
</div>

View File

@ -1,6 +1,9 @@
import HorizontalLine from "components/HorizontalLine";
import InsetBox from "components/InsetBox";
import { useAppLayout } from "contexts/AppLayoutContext";
import Markdown from "markdown-to-jsx";
import SceneBreak from "./SceneBreak";
import { slugify } from "queries/helpers";
import React from "react";
type ScenBreakProps = {
className?: string;
@ -9,15 +12,31 @@ type ScenBreakProps = {
export default function Markdawn(props: ScenBreakProps): JSX.Element {
const appLayout = useAppLayout();
const text = preprocessMarkDawn(props.text);
if (props.text) {
if (text) {
return (
<Markdown
className={`prose prose-p:text-justify text-black ${props.className}`}
className={`formatted ${props.className}`}
options={{
slugify: slugify,
overrides: {
hr: {
component: SceneBreak,
Sep: {
component: () => {
return <div className="my-24"></div>;
},
},
SceneBreak: {
component: (props: { id: string }) => {
return (
<div
id={props.id}
className={"h-0 text-center text-3xl text-dark mt-16 mb-20"}
>
* * *
</div>
);
},
},
player: {
component: () => {
@ -28,12 +47,80 @@ export default function Markdawn(props: ScenBreakProps): JSX.Element {
);
},
},
Transcript: {
component: (props) => {
return (
<div className="grid grid-cols-[auto_1fr] gap-x-6 gap-y-2">
{props.children}
</div>
);
},
},
Line: {
component: (props) => {
return (
<>
<strong className="text-dark opacity-60">
{props.name}
</strong>
<p className="whitespace-pre-line">{props.children}</p>
</>
);
},
},
InsetBox: {
component: (props) => {
return <InsetBox>{props.children}</InsetBox>;
},
},
li: {
component: (props: { children: React.ReactNode }) => {
return (
<li
className={
props.children && props.children?.toString().length > 100
? "my-4"
: ""
}
>
{props.children}
</li>
);
},
},
Highlight: {
component: (props: { children: React.ReactNode }) => {
return <mark>{props.children}</mark>;
},
},
footer: {
component: (props: { children: React.ReactNode }) => {
return (
<>
<HorizontalLine />
<div>{props.children}</div>
</>
);
},
},
},
}}
>
{props.text}
{text}
</Markdown>
);
}
return <></>;
}
export function preprocessMarkDawn(text: string): string {
let scenebreakIndex = 0;
const result = text.split("\n").map((line) => {
if (line === "* * *" || line === "---") {
scenebreakIndex++;
return `<SceneBreak id="scene-break-${scenebreakIndex}">`;
}
return line;
});
return result.join("\n");
}

View File

@ -1,15 +0,0 @@
type ScenBreakProps = {
className?: string;
};
export default function SceneBreak(props: ScenBreakProps): JSX.Element {
return (
<div
className={
"h-0 text-center text-3xl text-dark mt-16 mb-20" + " " + props.className
}
>
* * *
</div>
);
}

View File

@ -0,0 +1,153 @@
import { slugify } from "queries/helpers";
import { preprocessMarkDawn } from "./Markdawn";
type TOCProps = {
text: string;
title?: string;
};
export default function TOC(props: TOCProps): JSX.Element {
const toc = getTocFromMarkdawn(preprocessMarkDawn(props.text), props.title);
return (
<div>
<h3 className="text-xl">Table of content</h3>
<ol className="text-left">
<li className="my-2 overflow-x-hidden w-full text-ellipsis whitespace-nowrap">
<a className="" href={`#${toc.slug}`}>
{<abbr title={toc.title}>{toc.title}</abbr>}
</a>
</li>
{toc.children.map((h2, h2Index) => (
<>
<li
key={h2.slug}
className="my-2 overflow-x-hidden w-full text-ellipsis whitespace-nowrap"
>
<span className="text-dark">{`${h2Index + 1}. `}</span>
<a href={`#${h2.slug}`}>
{<abbr title={h2.title}>{h2.title}</abbr>}
</a>
</li>
<ol className="pl-4 text-left">
{h2.children.map((h3, h3Index) => (
<li
key={h3.slug}
className="my-2 overflow-x-hidden w-full text-ellipsis whitespace-nowrap"
>
<span className="text-dark">{`${h2Index + 1}.${
h3Index + 1
}. `}</span>
<a href={`#${h3.slug}`}>
{<abbr title={h3.title}>{h3.title}</abbr>}
</a>
</li>
))}
</ol>
</>
))}
</ol>
</div>
);
}
export type TOC = {
title: string;
slug: string;
children: TOC[];
};
export function getTocFromMarkdawn(text: string, title?: string): TOC {
if (!title) title = "Return to top";
let toc: TOC = { title: title, slug: slugify(title) || "", children: [] };
let h2 = -1;
let h3 = -1;
let h4 = -1;
let h5 = -1;
let scenebreak = 0;
let scenebreakIndex = 0;
text.split("\n").map((line) => {
if (line.startsWith("# ")) {
toc.slug = slugify(line);
} else if (line.startsWith("## ")) {
toc.children.push({
title: line.slice("## ".length),
slug: slugify(line),
children: [],
});
h2++;
h3 = -1;
h4 = -1;
h5 = -1;
scenebreak = 0;
} else if (line.startsWith("### ")) {
toc.children[h2].children.push({
title: line.slice("### ".length),
slug: slugify(line),
children: [],
});
h3++;
h4 = -1;
h5 = -1;
scenebreak = 0;
} else if (line.startsWith("#### ")) {
toc.children[h2].children[h3].children.push({
title: line.slice("#### ".length),
slug: slugify(line),
children: [],
});
h4++;
h5 = -1;
scenebreak = 0;
} else if (line.startsWith("##### ")) {
toc.children[h2].children[h3].children[h4].children.push({
title: line.slice("##### ".length),
slug: slugify(line),
children: [],
});
h5++;
scenebreak = 0;
} else if (line.startsWith("###### ")) {
toc.children[h2].children[h3].children[h4].children[h5].children.push({
title: line.slice("###### ".length),
slug: slugify(line),
children: [],
});
} else if (line.startsWith(`<SceneBreak`)) {
scenebreak++;
scenebreakIndex++;
if (h5 >= 0) {
toc.children[h2].children[h3].children[h4].children[h5].children.push({
title: `Scene break ${scenebreak}`,
slug: slugify(`scene-break-${scenebreakIndex}`),
children: [],
});
} else if (h4 >= 0) {
toc.children[h2].children[h3].children[h4].children.push({
title: `Scene break ${scenebreak}`,
slug: slugify(`scene-break-${scenebreakIndex}`),
children: [],
});
} else if (h3 >= 0) {
toc.children[h2].children[h3].children.push({
title: `Scene break ${scenebreak}`,
slug: slugify(`scene-break-${scenebreakIndex}`),
children: [],
});
} else if (h2 >= 0) {
toc.children[h2].children.push({
title: `Scene break ${scenebreak}`,
slug: slugify(`scene-break-${scenebreakIndex}`),
children: [],
});
} else {
toc.children.push({
title: `Scene break ${scenebreak}`,
slug: slugify(`scene-break-${scenebreakIndex}`),
children: [],
});
}
}
});
return toc;
}

View File

@ -12,12 +12,15 @@ export enum ContentPanelWidthSizes {
export default function ContentPanel(props: ContentPanelProps): JSX.Element {
const width = props.width ? props.width : ContentPanelWidthSizes.default;
const widthCSS =
width === ContentPanelWidthSizes.default ? "max-w-[45rem]" : "w-full";
const prose = props.autoformat ? "prose text-justify" : "";
width === ContentPanelWidthSizes.default ? "max-w-2xl" : "w-full";
return (
<div className={`grid pt-10 pb-20 px-6 desktop:py-20 desktop:px-10`}>
<main className={`${prose} ${widthCSS} place-self-center`}>
<main
className={`${
props.autoformat && "formatted"
} ${widthCSS} place-self-center`}
>
{props.children}
</main>
</div>

View File

@ -117,6 +117,8 @@ export default function MainPanel(props: MainPanelProps): JSX.Element {
onClick={() => appLayout.setMainPanelOpen(false)}
/>
{/*
<NavOption
url="/wiki"
icon="travel_explore"
@ -136,6 +138,8 @@ export default function MainPanel(props: MainPanelProps): JSX.Element {
reduced={appLayout.mainPanelReduced && isDesktop}
onClick={() => appLayout.setMainPanelOpen(false)}
/>
*/}
<HorizontalLine />
@ -147,7 +151,7 @@ export default function MainPanel(props: MainPanelProps): JSX.Element {
reduced={appLayout.mainPanelReduced && isDesktop}
onClick={() => appLayout.setMainPanelOpen(false)}
/>
{/*
<NavOption
url="/merch"
icon="store"
@ -156,6 +160,8 @@ export default function MainPanel(props: MainPanelProps): JSX.Element {
reduced={appLayout.mainPanelReduced && isDesktop}
onClick={() => appLayout.setMainPanelOpen(false)}
/>
*/}
<NavOption
url="/gallery"
@ -166,6 +172,8 @@ export default function MainPanel(props: MainPanelProps): JSX.Element {
onClick={() => appLayout.setMainPanelOpen(false)}
/>
{/*
<NavOption
url="/archives"
icon="inventory"
@ -175,6 +183,9 @@ export default function MainPanel(props: MainPanelProps): JSX.Element {
onClick={() => appLayout.setMainPanelOpen(false)}
/>
*/}
<NavOption
url="/about-us"
icon="info"

View File

@ -6,7 +6,7 @@ export type SwitchProps = {
className?: string;
};
export default function Select(props: SwitchProps): JSX.Element {
export default function Switch(props: SwitchProps): JSX.Element {
return (
<div
className={`h-6 w-12 rounded-full border-2 border-mid grid transition-colors relative cursor-pointer ${

View File

@ -109,6 +109,12 @@ query getWebsiteInterface($language_code: String) {
translation_notice
source_language
pronouns
no_category
item
items
content
result
results
}
}
}
@ -1142,3 +1148,77 @@ query getLanguages {
}
}
}
query getPost($slug: String, $language_code: String) {
posts(filters: { slug: { eq: $slug } }) {
data {
id
attributes {
slug
publishedAt
updatedAt
authors {
data {
id
attributes {
username
anonymize
anonymous_code
pronouns
bio(filters: { language: { code: { eq: $language_code } } }) {
bio
}
languages {
data {
attributes {
code
}
}
}
avatar {
data {
attributes {
name
alternativeText
caption
width
height
url
}
}
}
}
}
}
categories {
data {
id
attributes {
name
short
}
}
}
hidden
translations(filters: { language: { code: { eq: $language_code } } }) {
Status
title
excerpt
thumbnail {
data {
attributes {
name
alternativeText
caption
width
height
url
}
}
}
body
}
}
}
}
}

View File

@ -48,6 +48,13 @@ export enum Enum_Componentmetadatavideo_Resolution {
QuadHd_2160p = "QuadHD_2160p",
}
export enum Enum_Componenttranslationsposts_Status {
Incomplete = "Incomplete",
Draft = "Draft",
Review = "Review",
Done = "Done",
}
export enum Enum_Componenttranslationschronologyitem_Status {
Incomplete = "Incomplete",
Draft = "Draft",
@ -191,6 +198,12 @@ export type GetWebsiteInterfaceQuery = {
translation_notice: string;
source_language: string;
pronouns: string;
no_category: string;
item: string;
items: string;
content: string;
result: string;
results: string;
};
}>;
};
@ -1525,3 +1538,100 @@ export type GetLanguagesQuery = {
}>;
};
};
export type GetPostQueryVariables = Exact<{
slug: InputMaybe<Scalars["String"]>;
language_code: InputMaybe<Scalars["String"]>;
}>;
export type GetPostQuery = {
__typename: "Query";
posts: {
__typename: "PostEntityResponseCollection";
data: Array<{
__typename: "PostEntity";
id: string;
attributes: {
__typename: "Post";
slug: string;
publishedAt: any;
updatedAt: any;
hidden: boolean;
authors: {
__typename: "RecorderRelationResponseCollection";
data: Array<{
__typename: "RecorderEntity";
id: string;
attributes: {
__typename: "Recorder";
username: string;
anonymize: boolean;
anonymous_code: string;
pronouns: string;
bio: Array<{
__typename: "ComponentTranslationsBio";
bio: string;
}>;
languages: {
__typename: "LanguageRelationResponseCollection";
data: Array<{
__typename: "LanguageEntity";
attributes: { __typename: "Language"; code: string };
}>;
};
avatar: {
__typename: "UploadFileEntityResponse";
data: {
__typename: "UploadFileEntity";
attributes: {
__typename: "UploadFile";
name: string;
alternativeText: string;
caption: string;
width: number;
height: number;
url: string;
};
};
};
};
}>;
};
categories: {
__typename: "CategoryRelationResponseCollection";
data: Array<{
__typename: "CategoryEntity";
id: string;
attributes: {
__typename: "Category";
name: string;
short: string;
};
}>;
};
translations: Array<{
__typename: "ComponentTranslationsPosts";
Status: Enum_Componenttranslationsposts_Status;
title: string;
excerpt: string;
body: string;
thumbnail: {
__typename: "UploadFileEntityResponse";
data: {
__typename: "UploadFileEntity";
attributes: {
__typename: "UploadFile";
name: string;
alternativeText: string;
caption: string;
width: number;
height: number;
url: string;
};
};
};
}>;
};
}>;
};
};

View File

@ -23,6 +23,8 @@ import {
GetLibraryItemsPreviewQueryVariables,
GetLibraryItemsSlugsQuery,
GetLibraryItemsSlugsQueryVariables,
GetPostQuery,
GetPostQueryVariables,
GetWebsiteInterfaceQuery,
GetWebsiteInterfaceQueryVariables,
} from "graphql/operations-types";
@ -141,3 +143,10 @@ export async function getLanguages(
const query = getQueryFromOperations("getLanguages");
return await graphQL(query, JSON.stringify(variables));
}
export async function getPost(
variables: GetPostQueryVariables
): Promise<GetPostQuery> {
const query = getQueryFromOperations("getPost");
return await graphQL(query, JSON.stringify(variables));
}

View File

@ -1060,6 +1060,8 @@ input ComponentTranslationsPostsFiltersInput {
Status: StringFilterInput
title: StringFilterInput
excerpt: StringFilterInput
body: StringFilterInput
language: LanguageFiltersInput
and: [ComponentTranslationsPostsFiltersInput]
or: [ComponentTranslationsPostsFiltersInput]
not: ComponentTranslationsPostsFiltersInput
@ -1071,6 +1073,8 @@ input ComponentTranslationsPostsInput {
title: String
excerpt: String
thumbnail: ID
body: String
language: ID
}
type ComponentTranslationsPosts {
@ -1079,6 +1083,8 @@ type ComponentTranslationsPosts {
title: String!
excerpt: String
thumbnail: UploadFileEntityResponse
body: String
language: LanguageEntityResponse
}
enum ENUM_COMPONENTTRANSLATIONSSCANSET_STATUS {
@ -1648,6 +1654,7 @@ input CurrencyFiltersInput {
symbol: StringFilterInput
code: StringFilterInput
rate_to_usd: FloatFilterInput
display_decimals: BooleanFilterInput
createdAt: DateTimeFilterInput
updatedAt: DateTimeFilterInput
and: [CurrencyFiltersInput]
@ -1659,12 +1666,14 @@ input CurrencyInput {
symbol: String
code: String
rate_to_usd: Float
display_decimals: Boolean
}
type Currency {
symbol: String!
code: String!
rate_to_usd: Float
rate_to_usd: Float!
display_decimals: Boolean!
createdAt: DateTime
updatedAt: DateTime
}
@ -1916,6 +1925,7 @@ input LibraryItemFiltersInput {
digital: BooleanFilterInput
primary: BooleanFilterInput
submerchs: MerchItemFiltersInput
categories: CategoryFiltersInput
createdAt: DateTimeFilterInput
updatedAt: DateTimeFilterInput
and: [LibraryItemFiltersInput]
@ -1942,6 +1952,7 @@ input LibraryItemInput {
digital: Boolean
primary: Boolean
submerchs: [ID]
categories: [ID]
}
type LibraryItem {
@ -1987,6 +1998,11 @@ type LibraryItem {
pagination: PaginationArg = {}
sort: [String] = []
): MerchItemRelationResponseCollection
categories(
filters: CategoryFiltersInput
pagination: PaginationArg = {}
sort: [String] = []
): CategoryRelationResponseCollection
createdAt: DateTime
updatedAt: DateTime
}
@ -2107,6 +2123,7 @@ input PostFiltersInput {
authors: RecorderFiltersInput
slug: StringFilterInput
categories: CategoryFiltersInput
hidden: BooleanFilterInput
createdAt: DateTimeFilterInput
updatedAt: DateTimeFilterInput
publishedAt: DateTimeFilterInput
@ -2120,6 +2137,7 @@ input PostInput {
slug: String
categories: [ID]
translations: [ComponentTranslationsPostsInput]
hidden: Boolean
publishedAt: DateTime
}
@ -2140,6 +2158,7 @@ type Post {
pagination: PaginationArg = {}
sort: [String] = []
): [ComponentTranslationsPosts]
hidden: Boolean!
createdAt: DateTime
updatedAt: DateTime
publishedAt: DateTime
@ -2619,6 +2638,38 @@ input WebsiteInterfaceFiltersInput {
group_by: StringFilterInput
select_option_sidebar: StringFilterInput
group: StringFilterInput
settings: StringFilterInput
theme: StringFilterInput
light: StringFilterInput
auto: StringFilterInput
dark: StringFilterInput
font_size: StringFilterInput
player_name: StringFilterInput
currency: StringFilterInput
font: StringFilterInput
calculated: StringFilterInput
status_incomplete: StringFilterInput
status_draft: StringFilterInput
status_review: StringFilterInput
status_done: StringFilterInput
incomplete: StringFilterInput
draft: StringFilterInput
review: StringFilterInput
done: StringFilterInput
status: StringFilterInput
transcribers: StringFilterInput
translators: StringFilterInput
proofreaders: StringFilterInput
transcript_notice: StringFilterInput
translation_notice: StringFilterInput
source_language: StringFilterInput
pronouns: StringFilterInput
no_category: StringFilterInput
item: StringFilterInput
items: StringFilterInput
content: StringFilterInput
result: StringFilterInput
results: StringFilterInput
createdAt: DateTimeFilterInput
updatedAt: DateTimeFilterInput
and: [WebsiteInterfaceFiltersInput]
@ -2707,6 +2758,38 @@ input WebsiteInterfaceInput {
group_by: String
select_option_sidebar: String
group: String
settings: String
theme: String
light: String
auto: String
dark: String
font_size: String
player_name: String
currency: String
font: String
calculated: String
status_incomplete: String
status_draft: String
status_review: String
status_done: String
incomplete: String
draft: String
review: String
done: String
status: String
transcribers: String
translators: String
proofreaders: String
transcript_notice: String
translation_notice: String
source_language: String
pronouns: String
no_category: String
item: String
items: String
content: String
result: String
results: String
}
type WebsiteInterface {
@ -2790,6 +2873,38 @@ type WebsiteInterface {
group_by: String
select_option_sidebar: String
group: String
settings: String
theme: String
light: String
auto: String
dark: String
font_size: String
player_name: String
currency: String
font: String
calculated: String
status_incomplete: String
status_draft: String
status_review: String
status_done: String
incomplete: String
draft: String
review: String
done: String
status: String
transcribers: String
translators: String
proofreaders: String
transcript_notice: String
translation_notice: String
source_language: String
pronouns: String
no_category: String
item: String
items: String
content: String
result: String
results: String
createdAt: DateTime
updatedAt: DateTime
}

View File

@ -3,6 +3,7 @@ import PanelHeader from "components/PanelComponents/PanelHeader";
import { GetStaticProps } from "next";
import AppLayout from "components/AppLayout";
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
import NavOption from "components/PanelComponents/NavOption";
interface AboutUsProps extends AppStaticProps {}
@ -15,6 +16,15 @@ export default function AboutUs(props: AboutUsProps): JSX.Element {
title={langui.about_us}
description={langui.about_us_description}
/>
<NavOption title="Accords Handbook" url="/about-us/handbook" border />
<NavOption
title="Site information"
url="/about-us/site-information"
border
/>
<NavOption title="FAQ" url="/about-us/faq" border />
<NavOption title="Sharing Policy" url="/about-us/sharing-policy" border />
<NavOption title="Contact us" url="/about-us/contact" border />
</SubPanel>
);
return (

View File

@ -0,0 +1,60 @@
import AppLayout from "components/AppLayout";
import Markdawn from "components/Markdown/Markdawn";
import ReturnButton, {
ReturnButtonType,
} from "components/PanelComponents/ReturnButton";
import ContentPanel from "components/Panels/ContentPanel";
import { getPost } from "graphql/operations";
import { GetPostQuery } from "graphql/operations-types";
import { GetStaticProps } from "next";
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
import { prettySlug } from "queries/helpers";
interface SiteInfoProps extends AppStaticProps {
post: GetPostQuery["posts"]["data"][number]["attributes"];
}
export default function SiteInformation(props: SiteInfoProps): JSX.Element {
const { langui, post } = props;
const contentPanel = (
<ContentPanel>
<ReturnButton
href="/about-us"
displayOn={ReturnButtonType.Both}
langui={langui}
title={langui.about_us}
className="mb-10"
/>
{post.translations.length > 0 && (
<Markdawn text={post.translations[0].body} />
)}
</ContentPanel>
);
return (
<AppLayout
navTitle={
post.translations.length > 0
? post.translations[0].title
: prettySlug(post.slug)
}
contentPanel={contentPanel}
{...props}
/>
);
}
export const getStaticProps: GetStaticProps = async (context) => {
const props: SiteInfoProps = {
...(await getAppStaticProps(context)),
post: (
await getPost({
slug: "site-information",
language_code: context.locale || "en",
})
).posts.data[0].attributes,
};
return {
props: props,
};
};

View File

@ -26,6 +26,7 @@ import Chip from "components/Chip";
import ReactTooltip from "react-tooltip";
import RecorderChip from "components/RecorderChip";
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
import TOC from "components/Markdown/TOC";
interface ContentReadProps extends AppStaticProps {
content: GetContentTextQuery["contents"]["data"][number]["attributes"];
@ -141,6 +142,24 @@ export default function ContentRead(props: ContentReadProps): JSX.Element {
)}
</div>
)}
{content.text_set.length > 0 && content.text_set[0].text && (
<>
<HorizontalLine />
<TOC
text={content.text_set[0].text}
title={
content.titles.length > 0
? prettyinlineTitle(
content.titles[0].pre_title,
content.titles[0].title,
content.titles[0].subtitle
)
: prettySlug(content.slug)
}
/>
</>
)}
</SubPanel>
);
const contentPanel = (
@ -157,7 +176,7 @@ export default function ContentRead(props: ContentReadProps): JSX.Element {
<HorizontalLine />
{content.text_set.length > 0 && (
{content.text_set.length > 0 && content.text_set[0].text && (
<Markdawn text={content.text_set[0].text} />
)}
</div>

View File

@ -3,7 +3,10 @@ import SubPanel from "components/Panels/SubPanel";
import ContentPanel, {
ContentPanelWidthSizes,
} from "components/Panels/ContentPanel";
import { GetContentsQuery } from "graphql/operations-types";
import {
GetContentsQuery,
GetWebsiteInterfaceQuery,
} from "graphql/operations-types";
import { getContents } from "graphql/operations";
import PanelHeader from "components/PanelComponents/PanelHeader";
import AppLayout from "components/AppLayout";
@ -12,6 +15,7 @@ import { prettyinlineTitle, prettySlug } from "queries/helpers";
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
import Select from "components/Select";
import { useEffect, useState } from "react";
import Chip from "components/Chip";
interface ContentsProps extends AppStaticProps {
contents: GetContentsQuery["contents"]["data"];
@ -25,11 +29,11 @@ export default function Contents(props: ContentsProps): JSX.Element {
const [groupingMethod, setGroupingMethod] = useState<number>(-1);
const [groups, setGroups] = useState<GroupContentItems>(
getGroups(groupingMethod, contents)
getGroups(langui, groupingMethod, contents)
);
useEffect(() => {
setGroups(getGroups(groupingMethod, contents));
setGroups(getGroups(langui, groupingMethod, contents));
}, [langui, groupingMethod, contents]);
const subPanel = (
@ -61,9 +65,14 @@ export default function Contents(props: ContentsProps): JSX.Element {
{name && (
<h2
key={"h2" + name}
className="text-2xl pb-2 pt-10 first-of-type:pt-0"
className="text-2xl pb-2 pt-10 first-of-type:pt-0 flex flex-row place-items-center gap-2"
>
{name}
<Chip>{`${items.length} ${
items.length <= 1
? langui.result.toLowerCase()
: langui.results.toLowerCase()
}`}</Chip>
</h2>
)}
<div
@ -127,6 +136,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
};
function getGroups(
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"],
groupByType: number,
items: ContentsProps["contents"]
): GroupContentItems {
@ -150,11 +160,11 @@ function getGroups(
typeGroup.set("Bakuken", []);
typeGroup.set("YoRHa", []);
typeGroup.set("YoRHa Boys", []);
typeGroup.set("No category", []);
typeGroup.set(langui.no_category, []);
items.map((item) => {
if (item.attributes.categories.data.length === 0) {
typeGroup.get("No category")?.push(item);
typeGroup.get(langui.no_category)?.push(item);
} else {
item.attributes.categories.data.map((category) => {
typeGroup.get(category.attributes.name)?.push(item);

View File

@ -1,146 +1,48 @@
import AppLayout from "components/AppLayout";
import Markdawn from "components/Markdown/Markdawn";
import ContentPanel from "components/Panels/ContentPanel";
import { getPost } from "graphql/operations";
import { GetPostQuery } from "graphql/operations-types";
import { GetStaticProps } from "next";
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
import { prettySlug } from "queries/helpers";
interface HomeProps extends AppStaticProps {}
interface HomeProps extends AppStaticProps {
post: GetPostQuery["posts"]["data"][number]["attributes"];
}
export default function Home(props: HomeProps): JSX.Element {
const { post } = props;
const contentPanel = (
<ContentPanel autoformat>
<div className="grid place-items-center place-content-center w-full gap-5 text-center">
<div className="[mask:url('/icons/accords.svg')] [mask-size:contain] [mask-repeat:no-repeat] [mask-position:center] w-32 aspect-square mobile:w-[50vw] bg-black" />
<h1 className="text-5xl mb-0">Accord&rsquo;s Library</h1>
<h2 className="mt-0">Discover Analyse Translate Archive</h2>
</div>
<h2>What is this?</h2>
<p>
Accord&rsquo;s Library aims at gathering and archiving all of Yoko
Taro&rsquo;s work. Yoko Taro is a Japanese video game director and
scenario writer. He is best-known for his work on the NieR and
Drakengard (Drag-on Dragoon) franchises. To complement his games, Yoko
Taro likes to publish side materials in the form of books, novellas,
artbooks, stage plays, manga, drama CDs, and comics. Those side
materials can be very difficult to find. His work goes all the way back
to 2003, and most of them are out of print after having been released
solely in Japan, sometimes in limited quantities. Their prices on the
second hand market have skyrocketed, ranging all the way to hundreds if
not thousand of dollars for the rarest items.&nbsp;
</p>
<p>
This is where this library takes its meaning, in trying to help the
community grow by providing translators, writers, and wiki&rsquo;s
contributors a simple way to access these records filled with stories,
artworks, and knowledge.
</p>
<p>
We are a small group of Yoko Taro&rsquo;s fans that decided to join
forces and create a website and a community. Our motto is{" "}
<strong>Discover Analyze Translate Archive</strong> (D.A.T.A. for
short). We started with the goal of gathering and archiving as much
side-materials/merch as possible. But since then, our ambition grew and
we decided to create a full-fledged website that will also include news
articles, lore, summaries, translations, and transcriptions. Hopefully
one day, we will be up there in the list of notable resources for
Drakengard and NieR fans.
</p>
<h2>What&rsquo;s on this website?</h2>
<p>
<strong>
<a href="https://accords-library.com/compendium/">The Compendium</a>
</strong>
: This is where we will list every NieR/DOD/other Yoko Tato merch,
games, books, novel, stage play, CD... well everything! For each, we
will provide photos and/or scans of the content, information about what
it is, when and how it was released, size, initial price...
</p>
<p>
<strong>
<a href="https://accords-library.com/news/">News</a>
</strong>
: Yes because we also want to create our own content! So there you will
find translations, transcriptions, unboxing, news about future
merch/game releases, maybe some guides. We don&rsquo;t see this website
as being purely a showcase of our work, but also of the community, and
as such, we will be accepting applications for becoming contributors on
the website. For the applicant, there is no deadline or article quota,
it merely means that we will have access to the website Post Writing
tools and will be able to submit a draft that can be published once
verified by an editor. Anyway, that&rsquo;s at least the plan, we will
think more about this until the website&rsquo;s official launch.
</p>
<p>
<strong>
<a href="https://accords-library.com/data/">Data</a>
</strong>
: There we will publish lore/knowledge about the Yokoverse: Dictionary,
Timeline, Weapons Stories, Game summaries... We have not yet decided how
deep we want to go as they are already quite a few resources out there.{" "}
</p>
<p>
<strong>
<a
href="https://gallery.accords-library.com/posts"
target="_blank"
rel="noreferrer noopener"
>
Gallery
</a>
</strong>
: A fully tagged Danbooru-styled gallery with currently more than a
thousand unique artworks. If you are unfamiliar with this kind of
gallery, it comes with a powerful search function that allows you to
search for specific images: want to search for images with both Caim and
Inuart, just type{" "}
<kbd>
<a
href="https://gallery.accords-library.com/posts/query=Caim%20Inuart"
target="_blank"
rel="noreferrer noopener"
>
Caim Inuart
</a>
</kbd>
. If you want images of Devola OR Popola, you can use a comma{" "}
<kbd>
<a
href="https://gallery.accords-library.com/posts/query=Popola%2CDevola"
data-type="URL"
data-id="https://gallery.accords-library.com/posts/query=Popola%2CDevola"
target="_blank"
rel="noreferrer noopener"
>
Popola,Devola
</a>
</kbd>
. You can also negate a tag: i.e. images of 9S without any pods around,
search for{" "}
<kbd>
<a
href="https://gallery.accords-library.com/posts/query=9S%20-Pods"
target="_blank"
rel="noreferrer noopener"
>
9S -Pods
</a>
</kbd>
. Anyway, there is a lot more to it, you can click on &quot;Syntax
help&quot; next to the Search button for even neater functions. Btw, you
can create an account to favorite, upvote/downvote posts, or if you want
to help tagging them. There isn&rsquo;t currently a way for new users to
upload images, you&rsquo;ll have to contact us first and we can decide
to enable this function on your account.
</p>
<ContentPanel>
{post.translations.length > 0 && (
<Markdawn text={post.translations[0].body} />
)}
</ContentPanel>
);
return <AppLayout navTitle={"Home"} contentPanel={contentPanel} {...props} />;
return (
<AppLayout
navTitle={
post.translations.length > 0
? post.translations[0].title
: prettySlug(post.slug)
}
contentPanel={contentPanel}
{...props}
/>
);
}
export const getStaticProps: GetStaticProps = async (context) => {
const props: HomeProps = {
...(await getAppStaticProps(context)),
post: (
await getPost({
slug: "home",
language_code: context.locale || "en",
})
).posts.data[0].attributes,
};
return {
props: props,

View File

@ -218,13 +218,11 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
</div>
{item.categories.data.length > 0 && (
<div>
<div className="flex flex-col place-items-center gap-2">
<h3 className="text-xl">{langui.categories}</h3>
<div className="flex flex-row flex-wrap place-items-center place-content-start gap-2">
<div className="flex flex-row flex-wrap place-content-center gap-2">
{item.categories.data.map((category) => (
<Chip key={category.id}>
{category.attributes.short}
</Chip>
<Chip key={category.id}>{category.attributes.name}</Chip>
))}
</div>
</div>

View File

@ -17,6 +17,7 @@ import { useEffect, useState } from "react";
import { convertPrice, prettyDate, prettyinlineTitle } from "queries/helpers";
import Switch from "components/Switch";
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
import Chip from "components/Chip";
interface LibraryProps extends AppStaticProps {
items: GetLibraryItemsPreviewQuery["libraryItems"]["data"];
@ -116,9 +117,14 @@ export default function Library(props: LibraryProps): JSX.Element {
{name && (
<h2
key={"h2" + name}
className="text-2xl pb-2 pt-10 first-of-type:pt-0"
className="text-2xl pb-2 pt-10 first-of-type:pt-0 flex flex-row place-items-center gap-2"
>
{name}
<Chip>{`${items.length} ${
items.length <= 1
? langui.result.toLowerCase()
: langui.results.toLowerCase()
}`}</Chip>
</h2>
)}
<div
@ -188,11 +194,11 @@ function getGroups(
typeGroup.set("Bakuken", []);
typeGroup.set("YoRHa", []);
typeGroup.set("YoRHa Boys", []);
typeGroup.set("No category", []);
typeGroup.set(langui.no_category, []);
items.map((item) => {
if (item.attributes.categories.data.length === 0) {
typeGroup.get("No category")?.push(item);
typeGroup.get(langui.no_category)?.push(item);
} else {
item.attributes.categories.data.map((category) => {
typeGroup.get(category.attributes.name)?.push(item);

View File

@ -19,14 +19,12 @@ import { useRouter } from "next/router";
import ReactTooltip from "react-tooltip";
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
interface DataChronologyProps extends AppStaticProps {
interface ChronologyProps extends AppStaticProps {
chronologyItems: GetChronologyItemsQuery["chronologyItems"]["data"];
chronologyEras: GetErasQuery["chronologyEras"]["data"];
}
export default function DataChronology(
props: DataChronologyProps
): JSX.Element {
export default function Chronology(props: ChronologyProps): JSX.Element {
useTesting(props);
const { chronologyItems, chronologyEras } = props;
@ -133,7 +131,7 @@ export default function DataChronology(
}
export const getStaticProps: GetStaticProps = async (context) => {
const props: DataChronologyProps = {
const props: ChronologyProps = {
...(await getAppStaticProps(context)),
chronologyItems: (
await getChronologyItems({
@ -148,7 +146,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
};
};
function useTesting(props: DataChronologyProps) {
function useTesting(props: ChronologyProps) {
const router = useRouter();
const { chronologyItems, chronologyEras } = props;
chronologyEras.map((era) => {

View File

@ -6,7 +6,7 @@ import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
interface WikiProps extends AppStaticProps {}
export default function Hubs(props: WikiProps): JSX.Element {
export default function Wiki(props: WikiProps): JSX.Element {
const { langui } = props;
const subPanel = (
<SubPanel>

View File

@ -256,3 +256,20 @@ export function sortContent(
return 0;
});
}
export function slugify(str: string): string {
return str
.replace(/[ÀÁÂÃÄÅàáâãäåæÆ]/g, "a")
.replace(/[çÇ]/g, "c")
.replace(/[ðÐ]/g, "d")
.replace(/[ÈÉÊËéèêë]/g, "e")
.replace(/[ÏïÎîÍíÌì]/g, "i")
.replace(/[Ññ]/g, "n")
.replace(/[øØœŒÕõÔôÓóÒò]/g, "o")
.replace(/[ÜüÛûÚúÙù]/g, "u")
.replace(/[ŸÿÝý]/g, "y")
.replace(/[^a-z0-9- ]/gi, "")
.trim()
.replace(/ /gi, "-")
.toLowerCase();
}

View File

@ -24,6 +24,10 @@
@apply bg-dark text-light;
}
mark {
@apply bg-mid px-2
}
/* SCROLLBARS STYLING */
* {
@ -42,45 +46,82 @@
@apply bg-dark rounded-full border-[3px] border-solid border-light;
}
/* CHANGE PROSE DEFAULTS */
/* CHANGE FORMATTED DEFAULTS */
.prose,
.prose p,
.prose h1,
.prose h2,
.prose h3,
.prose h4,
.prose h5,
.prose h6,
.prose a,
.prose strong {
@apply text-black;
.formatted h1,
.formatted h2,
.formatted h3,
.formatted h4,
.formatted h5,
.formatted h6 {
@apply text-center;
}
.prose a {
@apply transition-colors underline-offset-2 decoration-dotted underline decoration-dark hover:text-dark;
.formatted h1 {
@apply text-4xl my-16;
}
.prose footer {
.formatted h1 + h2 {
@apply -mt-10;
}
.formatted h2 {
@apply text-3xl my-12;
}
.formatted h2 + h3 {
@apply -mt-8;
}
.formatted h3 {
@apply text-2xl my-8;
}
.formatted h3 + h4 {
@apply -mt-6;
}
.formatted h4 {
@apply text-xl my-6;
}
.formatted h5 {
@apply text-lg my-4;
}
.formatted p,
.formatted strong {
@apply my-2 text-justify;
}
.formatted footer {
@apply border-t-[3px] border-dotted pt-6;
}
.prose footer > div {
.formatted footer > div {
@apply my-2 px-6 py-4 rounded-xl;
}
.prose footer > div:target {
.formatted footer > div:target {
@apply bg-mid shadow-inner-sm shadow-shade;
}
.prose li::marker {
.formatted li::marker {
@apply text-dark;
}
.prose blockquote {
.formatted blockquote {
@apply border-l-dark;
}
.formatted ul {
@apply list-disc pl-4;
}
.formatted ol {
@apply list-decimal pl-4;
}
/* INPUT */
input {

View File

@ -72,7 +72,6 @@ module.exports = {
},
},
plugins: [
require("@tailwindcss/typography"),
plugin(function ({ addUtilities }) {
addUtilities({