Merge pull request #10 from Accords-Library/develop
Better Markdawn and auto-generated tables of content
This commit is contained in:
		
						commit
						56c07b715d
					
				
							
								
								
									
										50
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										50
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -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", | ||||
|  | ||||
| @ -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", | ||||
|  | ||||
| @ -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> | ||||
|  | ||||
| @ -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"); | ||||
| } | ||||
|  | ||||
| @ -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> | ||||
|   ); | ||||
| } | ||||
							
								
								
									
										153
									
								
								src/components/Markdown/TOC.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								src/components/Markdown/TOC.tsx
									
									
									
									
									
										Normal 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; | ||||
| } | ||||
| @ -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> | ||||
|  | ||||
| @ -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" | ||||
|  | ||||
| @ -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 ${ | ||||
|  | ||||
| @ -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 | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -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; | ||||
|               }; | ||||
|             }; | ||||
|           }; | ||||
|         }>; | ||||
|       }; | ||||
|     }>; | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| @ -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)); | ||||
| } | ||||
|  | ||||
| @ -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 | ||||
| } | ||||
|  | ||||
| @ -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="Accord’s 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 ( | ||||
|  | ||||
							
								
								
									
										60
									
								
								src/pages/about-us/site-information.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/pages/about-us/site-information.tsx
									
									
									
									
									
										Normal 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, | ||||
|   }; | ||||
| }; | ||||
| @ -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> | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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’s Library</h1> | ||||
|         <h2 className="mt-0">Discover • Analyse • Translate • Archive</h2> | ||||
|       </div> | ||||
| 
 | ||||
|       <h2>What is this?</h2> | ||||
|       <p> | ||||
|         Accord’s Library aims at gathering and archiving all of Yoko | ||||
|         Taro’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.  | ||||
|       </p> | ||||
|       <p> | ||||
|         This is where this library takes its meaning, in trying to help the | ||||
|         community grow by providing translators, writers, and wiki’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’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’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’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’s at least the plan, we will | ||||
|         think more about this until the website’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 "Syntax | ||||
|         help" 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’t currently a way for new users to | ||||
|         upload images, you’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, | ||||
|  | ||||
| @ -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> | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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) => { | ||||
|  | ||||
| @ -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> | ||||
|  | ||||
| @ -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(); | ||||
| } | ||||
|  | ||||
| @ -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 { | ||||
|  | ||||
| @ -72,7 +72,6 @@ module.exports = { | ||||
|     }, | ||||
|   }, | ||||
|   plugins: [ | ||||
|     require("@tailwindcss/typography"), | ||||
| 
 | ||||
|     plugin(function ({ addUtilities }) { | ||||
|       addUtilities({ | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 DrMint
						DrMint