Now using expression function style + some sections
This commit is contained in:
		
							parent
							
								
									8b80ec4ca3
								
							
						
					
					
						commit
						be1a32181e
					
				| @ -41,7 +41,7 @@ module.exports = { | ||||
|     eqeqeq: "error", | ||||
|     "func-name-matching": "warn", | ||||
|     "func-names": "warn", | ||||
|     "func-style": ["warn", "declaration"], | ||||
|     "func-style": ["warn", "expression"], | ||||
|     "grouped-accessor-pairs": "warn", | ||||
|     "guard-for-in": "warn", | ||||
|     "id-denylist": ["error", "data", "err", "e", "cb", "callback", "i"], | ||||
|  | ||||
							
								
								
									
										80
									
								
								CONTRIBUTING.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								CONTRIBUTING.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,80 @@ | ||||
| # CONTRIBUTING | ||||
| 
 | ||||
| ## Styling choices | ||||
| 
 | ||||
| ### Pages | ||||
| 
 | ||||
| ```tsx | ||||
| import ... | ||||
| 
 | ||||
| /* | ||||
|  *                                         ╭─────────────╮ | ||||
|  * ────────────────────────────────────────╯  CONSTANTS  ╰────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| const MY_CONSTANT = "value" | ||||
| const DEFAULT_FILTERS_STATE = {} | ||||
| 
 | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props {} | ||||
| 
 | ||||
| const PageName = () => {} | ||||
| export default PageName; | ||||
| 
 | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps: GetStaticProps = async (context) => {} | ||||
| 
 | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ | ||||
| 
 | ||||
| export const getStaticPaths: GetStaticPaths = async (context) => {} | ||||
| 
 | ||||
| /* | ||||
|  *                                      ╭───────────────────╮ | ||||
|  * ─────────────────────────────────────╯  PRIVATE METHODS  ╰─────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  PRIVATE COMPONENTS  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Component1Interface {} | ||||
| const Component1 = () => {} | ||||
| 
 | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ | ||||
| 
 | ||||
| interface Component2Interface {} | ||||
| const Component2 = () => {} | ||||
| 
 | ||||
| ``` | ||||
| 
 | ||||
| ### Components | ||||
| 
 | ||||
| ```tsx | ||||
| /* | ||||
|  *                                         ╭─────────────╮ | ||||
|  * ────────────────────────────────────────╯  CONSTANTS  ╰────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| const MY_CONSTANT = "value"; | ||||
| const DEFAULT_FILTERS_STATE = {}; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface ComponentProps {} | ||||
| 
 | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ | ||||
| 
 | ||||
| export const Component = () => {}; | ||||
| ``` | ||||
| @ -27,6 +27,19 @@ import { ContentPlaceholder } from "./PanelComponents/ContentPlaceholder"; | ||||
| import { MainPanel } from "./Panels/MainPanel"; | ||||
| import { Popup } from "./Popup"; | ||||
| 
 | ||||
| /* | ||||
|  *                                         ╭─────────────╮ | ||||
|  * ────────────────────────────────────────╯  CONSTANTS  ╰────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| const SENSIBILITY_SWIPE = 1.1; | ||||
| const TITLE_PREFIX = "Accord’s Library"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props extends AppStaticProps { | ||||
|   subPanel?: React.ReactNode; | ||||
|   subPanelIcon?: Icon; | ||||
| @ -38,24 +51,21 @@ interface Props extends AppStaticProps { | ||||
|   contentPanelScroolbar?: boolean; | ||||
| } | ||||
| 
 | ||||
| const SENSIBILITY_SWIPE = 1.1; | ||||
| const TITLE_PREFIX = "Accord’s Library"; | ||||
| 
 | ||||
| export function AppLayout(props: Props): JSX.Element { | ||||
|   const { | ||||
|     langui, | ||||
|     currencies, | ||||
|     languages, | ||||
|     subPanel, | ||||
|     contentPanel, | ||||
|     thumbnail, | ||||
|     title, | ||||
|     navTitle, | ||||
|     description, | ||||
|     subPanelIcon = Icon.Tune, | ||||
|     contentPanelScroolbar = true, | ||||
|   } = props; | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const AppLayout = ({ | ||||
|   langui, | ||||
|   currencies, | ||||
|   languages, | ||||
|   subPanel, | ||||
|   contentPanel, | ||||
|   thumbnail, | ||||
|   title, | ||||
|   navTitle, | ||||
|   description, | ||||
|   subPanelIcon = Icon.Tune, | ||||
|   contentPanelScroolbar = true, | ||||
| }: Props): JSX.Element => { | ||||
|   const { | ||||
|     configPanelOpen, | ||||
|     currency, | ||||
| @ -396,7 +406,7 @@ export function AppLayout(props: Props): JSX.Element { | ||||
|                     insertLabels={ | ||||
|                       new Map([ | ||||
|                         [0, langui.primary_language], | ||||
|                         [1, langui.secondary_language], | ||||
|                         [1, langui.secondary_language],  | ||||
|                       ]) | ||||
|                     } | ||||
|                     onChange={(items) => { | ||||
| @ -517,4 +527,4 @@ export function AppLayout(props: Props): JSX.Element { | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -1,21 +1,26 @@ | ||||
| import { cJoin } from "helpers/className"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   className?: string; | ||||
|   children: React.ReactNode; | ||||
| } | ||||
| 
 | ||||
| export function Chip(props: Props): JSX.Element { | ||||
|   return ( | ||||
|     <div | ||||
|       className={cJoin( | ||||
|         `grid place-content-center place-items-center whitespace-nowrap rounded-full
 | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const Chip = ({ className, children }: Props): JSX.Element => ( | ||||
|   <div | ||||
|     className={cJoin( | ||||
|       `grid place-content-center place-items-center whitespace-nowrap rounded-full
 | ||||
|         border-[1px] px-1.5 pb-[0.14rem] text-xs opacity-70 | ||||
|         transition-[color,_opacity,_border-color] hover:opacity-100`,
 | ||||
|         props.className | ||||
|       )} | ||||
|     > | ||||
|       {props.children} | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
|       className | ||||
|     )} | ||||
|   > | ||||
|     {children} | ||||
|   </div> | ||||
| ); | ||||
|  | ||||
| @ -1,17 +1,21 @@ | ||||
| import { cJoin } from "helpers/className"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   className?: string; | ||||
| } | ||||
| 
 | ||||
| export function HorizontalLine(props: Props): JSX.Element { | ||||
|   const { className } = props; | ||||
|   return ( | ||||
|     <div | ||||
|       className={cJoin( | ||||
|         "my-8 h-0 w-full border-t-[3px] border-dotted border-black", | ||||
|         className | ||||
|       )} | ||||
|     ></div> | ||||
|   ); | ||||
| } | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const HorizontalLine = ({ className }: Props): JSX.Element => ( | ||||
|   <div | ||||
|     className={cJoin( | ||||
|       "my-8 h-0 w-full border-t-[3px] border-dotted border-black", | ||||
|       className | ||||
|     )} | ||||
|   ></div> | ||||
| ); | ||||
|  | ||||
| @ -1,27 +1,35 @@ | ||||
| import { cJoin } from "helpers/className"; | ||||
| 
 | ||||
| import { MouseEventHandler } from "react"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   className?: string; | ||||
|   onClick?: MouseEventHandler<HTMLSpanElement> | undefined; | ||||
|   icon: Icon; | ||||
| } | ||||
| 
 | ||||
| export function Ico(props: Props): JSX.Element { | ||||
|   const { onClick, icon, className } = props; | ||||
|   return ( | ||||
|     <span | ||||
|       onClick={onClick} | ||||
|       className={cJoin( | ||||
|         "material-icons [font-size:inherit] [line-height:inherit]", | ||||
|         className | ||||
|       )} | ||||
|     > | ||||
|       {icon} | ||||
|     </span> | ||||
|   ); | ||||
| } | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const Ico = ({ onClick, icon, className }: Props): JSX.Element => ( | ||||
|   <span | ||||
|     onClick={onClick} | ||||
|     className={cJoin( | ||||
|       "material-icons [font-size:inherit] [line-height:inherit]", | ||||
|       className | ||||
|     )} | ||||
|   > | ||||
|     {icon} | ||||
|   </span> | ||||
| ); | ||||
| 
 | ||||
| /* | ||||
|  *                                          ╭─────────╮ | ||||
|  * ─────────────────────────────────────────╯  OTHER  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export enum Icon { | ||||
|   Onek = "1k", | ||||
|  | ||||
| @ -1,9 +1,13 @@ | ||||
| import { UploadImageFragment } from "graphql/generated"; | ||||
| import { getAssetURL, getImgSizesByQuality, ImageQuality } from "helpers/img"; | ||||
| 
 | ||||
| import { ImageProps } from "next/image"; | ||||
| import { MouseEventHandler } from "react"; | ||||
| 
 | ||||
| /* | ||||
|  *                                         ╭─────────────╮ | ||||
|  * ────────────────────────────────────────╯  CONSTANTS  ╰────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   className?: string; | ||||
|   image?: UploadImageFragment | string; | ||||
| @ -12,15 +16,15 @@ interface Props { | ||||
|   onClick?: MouseEventHandler<HTMLImageElement>; | ||||
| } | ||||
| 
 | ||||
| export function Img(props: Props): JSX.Element { | ||||
|   const { | ||||
|     className, | ||||
|     image, | ||||
|     quality = ImageQuality.Small, | ||||
|     alt, | ||||
|     onClick, | ||||
|   } = props; | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const Img = ({ | ||||
|   className, | ||||
|   image, | ||||
|   quality = ImageQuality.Small, | ||||
|   alt, | ||||
|   onClick, | ||||
| }: Props): JSX.Element => { | ||||
|   if (typeof image === "string") { | ||||
|     return ( | ||||
|       <img className={className} src={image} alt={alt ?? ""} loading="lazy" /> | ||||
| @ -40,4 +44,4 @@ export function Img(props: Props): JSX.Element { | ||||
|     ); | ||||
|   } | ||||
|   return <></>; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -1,11 +1,15 @@ | ||||
| import { Ico, Icon } from "components/Ico"; | ||||
| import { cIf, cJoin } from "helpers/className"; | ||||
| import { ConditionalWrapper, Wrapper } from "helpers/component"; | ||||
| 
 | ||||
| import { isDefined, isDefinedAndNotEmpty } from "helpers/others"; | ||||
| import { useRouter } from "next/router"; | ||||
| import React, { MouseEventHandler } from "react"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   id?: string; | ||||
|   className?: string; | ||||
| @ -20,21 +24,21 @@ interface Props { | ||||
|   badgeNumber?: number; | ||||
| } | ||||
| 
 | ||||
| export function Button(props: Props): JSX.Element { | ||||
|   const { | ||||
|     draggable, | ||||
|     id, | ||||
|     onClick, | ||||
|     active, | ||||
|     className, | ||||
|     icon, | ||||
|     text, | ||||
|     target, | ||||
|     href, | ||||
|     locale, | ||||
|     badgeNumber, | ||||
|   } = props; | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const Button = ({ | ||||
|   draggable, | ||||
|   id, | ||||
|   onClick, | ||||
|   active, | ||||
|   className, | ||||
|   icon, | ||||
|   text, | ||||
|   target, | ||||
|   href, | ||||
|   locale, | ||||
|   badgeNumber, | ||||
| }: Props): JSX.Element => { | ||||
|   const router = useRouter(); | ||||
| 
 | ||||
|   return ( | ||||
| @ -87,17 +91,19 @@ export function Button(props: Props): JSX.Element { | ||||
|       </div> | ||||
|     </ConditionalWrapper> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  PRIVATE COMPONENTS  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface LinkWrapperProps { | ||||
|   href?: string; | ||||
| } | ||||
| 
 | ||||
| function LinkWrapper(props: LinkWrapperProps & Wrapper) { | ||||
|   const { children, href } = props; | ||||
|   return ( | ||||
|     <a href={href} target="_blank" rel="noreferrer"> | ||||
|       {children} | ||||
|     </a> | ||||
|   ); | ||||
| } | ||||
| const LinkWrapper = ({ children, href }: LinkWrapperProps & Wrapper) => ( | ||||
|   <a href={href} target="_blank" rel="noreferrer"> | ||||
|     {children} | ||||
|   </a> | ||||
| ); | ||||
|  | ||||
| @ -4,6 +4,11 @@ import { ConditionalWrapper, Wrapper } from "helpers/component"; | ||||
| import { isDefinedAndNotEmpty } from "helpers/others"; | ||||
| import { Button } from "./Button"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   className?: string; | ||||
|   buttonsProps: (Parameters<typeof Button>[0] & { | ||||
| @ -11,43 +16,46 @@ interface Props { | ||||
|   })[]; | ||||
| } | ||||
| 
 | ||||
| export function ButtonGroup(props: Props): JSX.Element { | ||||
|   const { buttonsProps, className } = props; | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
|   return ( | ||||
|     <div className={cJoin("grid grid-flow-col", className)}> | ||||
|       {buttonsProps.map((buttonProps, index) => ( | ||||
|         <ConditionalWrapper | ||||
|           key={index} | ||||
|           isWrapping={isDefinedAndNotEmpty(buttonProps.tooltip)} | ||||
|           wrapper={ToolTipWrapper} | ||||
|           wrapperProps={{ text: buttonProps.tooltip ?? "" }} | ||||
|         > | ||||
|           <Button | ||||
|             {...buttonProps} | ||||
|             className={ | ||||
|               index === 0 | ||||
|                 ? "rounded-r-none border-r-0" | ||||
|                 : index === buttonsProps.length - 1 | ||||
|                 ? "rounded-l-none" | ||||
|                 : "rounded-none border-r-0" | ||||
|             } | ||||
|           /> | ||||
|         </ConditionalWrapper> | ||||
|       ))} | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| export const ButtonGroup = ({ | ||||
|   buttonsProps, | ||||
|   className, | ||||
| }: Props): JSX.Element => ( | ||||
|   <div className={cJoin("grid grid-flow-col", className)}> | ||||
|     {buttonsProps.map((buttonProps, index) => ( | ||||
|       <ConditionalWrapper | ||||
|         key={index} | ||||
|         isWrapping={isDefinedAndNotEmpty(buttonProps.tooltip)} | ||||
|         wrapper={ToolTipWrapper} | ||||
|         wrapperProps={{ text: buttonProps.tooltip ?? "" }} | ||||
|       > | ||||
|         <Button | ||||
|           {...buttonProps} | ||||
|           className={ | ||||
|             index === 0 | ||||
|               ? "rounded-r-none border-r-0" | ||||
|               : index === buttonsProps.length - 1 | ||||
|               ? "rounded-l-none" | ||||
|               : "rounded-none border-r-0" | ||||
|           } | ||||
|         /> | ||||
|       </ConditionalWrapper> | ||||
|     ))} | ||||
|   </div> | ||||
| ); | ||||
| 
 | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  PRIVATE COMPONENTS  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface ToolTipWrapperProps { | ||||
|   text: string; | ||||
| } | ||||
| 
 | ||||
| function ToolTipWrapper(props: ToolTipWrapperProps & Wrapper) { | ||||
|   const { text, children } = props; | ||||
|   return ( | ||||
|     <ToolTip content={text}> | ||||
|       <>{children}</> | ||||
|     </ToolTip> | ||||
|   ); | ||||
| } | ||||
| const ToolTipWrapper = ({ text, children }: ToolTipWrapperProps & Wrapper) => ( | ||||
|   <ToolTip content={text}> | ||||
|     <>{children}</> | ||||
|   </ToolTip> | ||||
| ); | ||||
|  | ||||
| @ -3,11 +3,15 @@ import { AppStaticProps } from "graphql/getAppStaticProps"; | ||||
| import { cJoin } from "helpers/className"; | ||||
| import { prettyLanguage } from "helpers/formatters"; | ||||
| import { iterateMap } from "helpers/others"; | ||||
| 
 | ||||
| import { Fragment } from "react"; | ||||
| import { ToolTip } from "../ToolTip"; | ||||
| import { Button } from "./Button"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   className?: string; | ||||
|   languages: AppStaticProps["languages"]; | ||||
| @ -16,29 +20,33 @@ interface Props { | ||||
|   onLanguageChanged: (index: number) => void; | ||||
| } | ||||
| 
 | ||||
| export function LanguageSwitcher(props: Props): JSX.Element { | ||||
|   const { locales, className, localesIndex, onLanguageChanged } = props; | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
|   return ( | ||||
|     <ToolTip | ||||
|       content={ | ||||
|         <div className={cJoin("flex flex-col gap-2", className)}> | ||||
|           {iterateMap(locales, (locale, value, index) => ( | ||||
|             <Fragment key={index}> | ||||
|               <Button | ||||
|                 active={value === localesIndex} | ||||
|                 onClick={() => onLanguageChanged(value)} | ||||
|                 text={prettyLanguage(locale, props.languages)} | ||||
|               /> | ||||
|             </Fragment> | ||||
|           ))} | ||||
|         </div> | ||||
|       } | ||||
|     > | ||||
|       <Button | ||||
|         badgeNumber={locales.size > 1 ? locales.size : undefined} | ||||
|         icon={Icon.Translate} | ||||
|       /> | ||||
|     </ToolTip> | ||||
|   ); | ||||
| } | ||||
| export const LanguageSwitcher = ({ | ||||
|   className, | ||||
|   locales, | ||||
|   localesIndex, | ||||
|   languages, | ||||
|   onLanguageChanged, | ||||
| }: Props): JSX.Element => ( | ||||
|   <ToolTip | ||||
|     content={ | ||||
|       <div className={cJoin("flex flex-col gap-2", className)}> | ||||
|         {iterateMap(locales, (locale, value, index) => ( | ||||
|           <Fragment key={index}> | ||||
|             <Button | ||||
|               active={value === localesIndex} | ||||
|               onClick={() => onLanguageChanged(value)} | ||||
|               text={prettyLanguage(locale, languages)} | ||||
|             /> | ||||
|           </Fragment> | ||||
|         ))} | ||||
|       </div> | ||||
|     } | ||||
|   > | ||||
|     <Button | ||||
|       badgeNumber={locales.size > 1 ? locales.size : undefined} | ||||
|       icon={Icon.Translate} | ||||
|     /> | ||||
|   </ToolTip> | ||||
| ); | ||||
|  | ||||
| @ -1,8 +1,12 @@ | ||||
| import { Ico, Icon } from "components/Ico"; | ||||
| import { isDefinedAndNotEmpty, iterateMap, mapMoveEntry } from "helpers/others"; | ||||
| 
 | ||||
| import { Fragment, useCallback, useState } from "react"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   className?: string; | ||||
|   items: Map<string, string>; | ||||
| @ -10,9 +14,14 @@ interface Props { | ||||
|   onChange?: (items: Map<string, string>) => void; | ||||
| } | ||||
| 
 | ||||
| export function OrderableList(props: Props): JSX.Element { | ||||
|   const { onChange } = props; | ||||
|   const [items, setItems] = useState<Map<string, string>>(props.items); | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const OrderableList = ({ | ||||
|   onChange, | ||||
|   items: propsItems, | ||||
|   insertLabels, | ||||
| }: Props): JSX.Element => { | ||||
|   const [items, setItems] = useState<Map<string, string>>(propsItems); | ||||
| 
 | ||||
|   const updateOrder = useCallback( | ||||
|     (sourceIndex: number, targetIndex: number) => { | ||||
| @ -27,10 +36,9 @@ export function OrderableList(props: Props): JSX.Element { | ||||
|     <div className="grid gap-2"> | ||||
|       {iterateMap(items, (key, value, index) => ( | ||||
|         <Fragment key={key}> | ||||
|           {props.insertLabels && | ||||
|             isDefinedAndNotEmpty(props.insertLabels.get(index)) && ( | ||||
|               <p>{props.insertLabels.get(index)}</p> | ||||
|             )} | ||||
|           {insertLabels && isDefinedAndNotEmpty(insertLabels.get(index)) && ( | ||||
|             <p>{insertLabels.get(index)}</p> | ||||
|           )} | ||||
|           <div | ||||
|             onDragStart={(event) => { | ||||
|               const source = event.target as HTMLElement; | ||||
| @ -89,4 +97,4 @@ export function OrderableList(props: Props): JSX.Element { | ||||
|       ))} | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -1,9 +1,13 @@ | ||||
| import { Icon } from "components/Ico"; | ||||
| import { cJoin } from "helpers/className"; | ||||
| 
 | ||||
| import { Dispatch, SetStateAction } from "react"; | ||||
| import { Button } from "./Button"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   className?: string; | ||||
|   maxPage: number; | ||||
| @ -11,27 +15,27 @@ interface Props { | ||||
|   setPage: Dispatch<SetStateAction<number>>; | ||||
| } | ||||
| 
 | ||||
| export function PageSelector(props: Props): JSX.Element { | ||||
|   const { page, setPage, maxPage, className } = props; | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
|   return ( | ||||
|     <div className={cJoin("flex flex-row place-content-center", className)}> | ||||
|       <Button | ||||
|         onClick={() => setPage((current) => (page > 0 ? current - 1 : current))} | ||||
|         className="rounded-r-none" | ||||
|         icon={Icon.NavigateBefore} | ||||
|       /> | ||||
|       <Button | ||||
|         className="rounded-none border-x-0" | ||||
|         text={(page + 1).toString()} | ||||
|       /> | ||||
|       <Button | ||||
|         onClick={() => | ||||
|           setPage((current) => (page < maxPage ? page + 1 : current)) | ||||
|         } | ||||
|         className="rounded-l-none" | ||||
|         icon={Icon.NavigateNext} | ||||
|       /> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| export const PageSelector = ({ | ||||
|   page, | ||||
|   setPage, | ||||
|   maxPage, | ||||
|   className, | ||||
| }: Props): JSX.Element => ( | ||||
|   <div className={cJoin("flex flex-row place-content-center", className)}> | ||||
|     <Button | ||||
|       onClick={() => setPage((current) => (page > 0 ? current - 1 : current))} | ||||
|       className="rounded-r-none" | ||||
|       icon={Icon.NavigateBefore} | ||||
|     /> | ||||
|     <Button className="rounded-none border-x-0" text={(page + 1).toString()} /> | ||||
|     <Button | ||||
|       onClick={() => | ||||
|         setPage((current) => (page < maxPage ? page + 1 : current)) | ||||
|       } | ||||
|       className="rounded-l-none" | ||||
|       icon={Icon.NavigateNext} | ||||
|     /> | ||||
|   </div> | ||||
| ); | ||||
|  | ||||
| @ -1,9 +1,13 @@ | ||||
| import { Ico, Icon } from "components/Ico"; | ||||
| import { cIf, cJoin } from "helpers/className"; | ||||
| 
 | ||||
| import { useToggle } from "hooks/useToggle"; | ||||
| import { Dispatch, Fragment, SetStateAction, useState } from "react"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   setState: Dispatch<SetStateAction<number>>; | ||||
|   state: number; | ||||
| @ -13,8 +17,15 @@ interface Props { | ||||
|   className?: string; | ||||
| } | ||||
| 
 | ||||
| export function Select(props: Props): JSX.Element { | ||||
|   const { className, state, options, allowEmpty, setState } = props; | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const Select = ({ | ||||
|   className, | ||||
|   state, | ||||
|   options, | ||||
|   allowEmpty, | ||||
|   setState, | ||||
| }: Props): JSX.Element => { | ||||
|   const [opened, setOpened] = useState(false); | ||||
|   const toggleOpened = useToggle(setOpened); | ||||
| 
 | ||||
| @ -80,4 +91,4 @@ export function Select(props: Props): JSX.Element { | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -1,8 +1,12 @@ | ||||
| import { cIf, cJoin } from "helpers/className"; | ||||
| 
 | ||||
| import { useToggle } from "hooks/useToggle"; | ||||
| import { Dispatch, SetStateAction } from "react"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   setState: Dispatch<SetStateAction<boolean>>; | ||||
|   state: boolean; | ||||
| @ -10,8 +14,14 @@ interface Props { | ||||
|   disabled?: boolean; | ||||
| } | ||||
| 
 | ||||
| export function Switch(props: Props): JSX.Element { | ||||
|   const { state, setState, className, disabled = false } = props; | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const Switch = ({ | ||||
|   state, | ||||
|   setState, | ||||
|   className, | ||||
|   disabled = false, | ||||
| }: Props): JSX.Element => { | ||||
|   const toggleState = useToggle(setState); | ||||
|   return ( | ||||
|     <div | ||||
| @ -41,4 +51,4 @@ export function Switch(props: Props): JSX.Element { | ||||
|       ></div> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -1,9 +1,13 @@ | ||||
| import { Ico, Icon } from "components/Ico"; | ||||
| import { cJoin } from "helpers/className"; | ||||
| import { isDefinedAndNotEmpty } from "helpers/others"; | ||||
| 
 | ||||
| import { Dispatch, SetStateAction } from "react"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   state: string | undefined; | ||||
|   setState: | ||||
| @ -14,32 +18,36 @@ interface Props { | ||||
|   placeholder?: string; | ||||
| } | ||||
| 
 | ||||
| export function TextInput(props: Props): JSX.Element { | ||||
|   const { state, setState, className, name, placeholder } = props; | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
|   return ( | ||||
|     <div className={cJoin("relative", className)}> | ||||
|       <input | ||||
|         className="w-full" | ||||
|         type="text" | ||||
|         name={name} | ||||
|         value={state} | ||||
|         placeholder={placeholder} | ||||
|         onChange={(event) => { | ||||
|           setState(event.target.value); | ||||
|         }} | ||||
|       /> | ||||
|       {isDefinedAndNotEmpty(state) && ( | ||||
|         <div className="absolute right-4 top-0 bottom-0 grid place-items-center"> | ||||
|           <Ico | ||||
|             className="cursor-pointer !text-xs" | ||||
|             icon={Icon.Close} | ||||
|             onClick={() => { | ||||
|               setState(""); | ||||
|             }} | ||||
|           /> | ||||
|         </div> | ||||
|       )} | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| export const TextInput = ({ | ||||
|   state, | ||||
|   setState, | ||||
|   className, | ||||
|   name, | ||||
|   placeholder, | ||||
| }: Props): JSX.Element => ( | ||||
|   <div className={cJoin("relative", className)}> | ||||
|     <input | ||||
|       className="w-full" | ||||
|       type="text" | ||||
|       name={name} | ||||
|       value={state} | ||||
|       placeholder={placeholder} | ||||
|       onChange={(event) => { | ||||
|         setState(event.target.value); | ||||
|       }} | ||||
|     /> | ||||
|     {isDefinedAndNotEmpty(state) && ( | ||||
|       <div className="absolute right-4 top-0 bottom-0 grid place-items-center"> | ||||
|         <Ico | ||||
|           className="cursor-pointer !text-xs" | ||||
|           icon={Icon.Close} | ||||
|           onClick={() => { | ||||
|             setState(""); | ||||
|           }} | ||||
|         /> | ||||
|       </div> | ||||
|     )} | ||||
|   </div> | ||||
| ); | ||||
|  | ||||
| @ -1,32 +1,33 @@ | ||||
| import { cIf, cJoin } from "helpers/className"; | ||||
| import { isDefinedAndNotEmpty } from "helpers/others"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   label: string | null | undefined; | ||||
|   input: JSX.Element; | ||||
|   disabled?: boolean; | ||||
| } | ||||
| 
 | ||||
| export function WithLabel(props: Props): JSX.Element { | ||||
|   const { label, input, disabled } = props; | ||||
|   return ( | ||||
|     <div | ||||
|       className={cJoin( | ||||
|         "flex flex-row place-content-between place-items-center gap-2", | ||||
|         cIf(disabled, "text-dark brightness-150 contrast-75 grayscale") | ||||
|       )} | ||||
|     > | ||||
|       {isDefinedAndNotEmpty(label) && ( | ||||
|         <p | ||||
|           className={cJoin( | ||||
|             "text-left", | ||||
|             cIf(label.length < 10, "flex-shrink-0") | ||||
|           )} | ||||
|         > | ||||
|           {label}: | ||||
|         </p> | ||||
|       )} | ||||
|       {input} | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const WithLabel = ({ label, input, disabled }: Props): JSX.Element => ( | ||||
|   <div | ||||
|     className={cJoin( | ||||
|       "flex flex-row place-content-between place-items-center gap-2", | ||||
|       cIf(disabled, "text-dark brightness-150 contrast-75 grayscale") | ||||
|     )} | ||||
|   > | ||||
|     {isDefinedAndNotEmpty(label) && ( | ||||
|       <p | ||||
|         className={cJoin("text-left", cIf(label.length < 10, "flex-shrink-0"))} | ||||
|       > | ||||
|         {label}: | ||||
|       </p> | ||||
|     )} | ||||
|     {input} | ||||
|   </div> | ||||
| ); | ||||
|  | ||||
| @ -1,21 +1,26 @@ | ||||
| import { cJoin } from "helpers/className"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   className?: string; | ||||
|   children: React.ReactNode; | ||||
|   id?: string; | ||||
| } | ||||
| 
 | ||||
| export function InsetBox(props: Props): JSX.Element { | ||||
|   return ( | ||||
|     <div | ||||
|       id={props.id} | ||||
|       className={cJoin( | ||||
|         "w-full rounded-xl bg-mid p-8 shadow-inner-sm shadow-shade", | ||||
|         props.className | ||||
|       )} | ||||
|     > | ||||
|       {props.children} | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const InsetBox = ({ id, className, children }: Props): JSX.Element => ( | ||||
|   <div | ||||
|     id={id} | ||||
|     className={cJoin( | ||||
|       "w-full rounded-xl bg-mid p-8 shadow-inner-sm shadow-shade", | ||||
|       className | ||||
|     )} | ||||
|   > | ||||
|     {children} | ||||
|   </div> | ||||
| ); | ||||
|  | ||||
| @ -5,73 +5,75 @@ import { useAppLayout } from "contexts/AppLayoutContext"; | ||||
| import { AppStaticProps } from "graphql/getAppStaticProps"; | ||||
| import { LibraryItemUserStatus } from "helpers/types"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   id: string | null | undefined; | ||||
|   displayCTAs: boolean; | ||||
|   id: string; | ||||
|   expand?: boolean; | ||||
|   langui: AppStaticProps["langui"]; | ||||
| } | ||||
| 
 | ||||
| export function PreviewCardCTAs(props: Props): JSX.Element { | ||||
|   const { id, displayCTAs, expand = false, langui } = props; | ||||
|   const appLayout = useAppLayout(); | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const PreviewCardCTAs = ({ | ||||
|   id, | ||||
|   expand = false, | ||||
|   langui, | ||||
| }: Props): JSX.Element => { | ||||
|   const appLayout = useAppLayout(); | ||||
|   return ( | ||||
|     <> | ||||
|       {displayCTAs && id && ( | ||||
|         <div | ||||
|           className={`flex flex-row place-content-center place-items-center ${ | ||||
|             expand ? "gap-4" : "gap-2" | ||||
|           }`}
 | ||||
|         > | ||||
|           <ToolTip content={langui.want_it} disabled={expand}> | ||||
|             <Button | ||||
|               icon={Icon.Favorite} | ||||
|               text={expand ? langui.want_it : undefined} | ||||
|               active={ | ||||
|                 appLayout.libraryItemUserStatus?.[id] === | ||||
|                 LibraryItemUserStatus.Want | ||||
|               } | ||||
|               onClick={(event) => { | ||||
|                 event.preventDefault(); | ||||
|                 appLayout.setLibraryItemUserStatus((current) => { | ||||
|                   const newLibraryItemUserStatus = current | ||||
|                     ? { ...current } | ||||
|                     : {}; | ||||
|                   newLibraryItemUserStatus[id] = | ||||
|                     newLibraryItemUserStatus[id] === LibraryItemUserStatus.Want | ||||
|                       ? LibraryItemUserStatus.None | ||||
|                       : LibraryItemUserStatus.Want; | ||||
|                   return newLibraryItemUserStatus; | ||||
|                 }); | ||||
|               }} | ||||
|             /> | ||||
|           </ToolTip> | ||||
|           <ToolTip content={langui.have_it} disabled={expand}> | ||||
|             <Button | ||||
|               icon={Icon.BackHand} | ||||
|               text={expand ? langui.have_it : undefined} | ||||
|               active={ | ||||
|                 appLayout.libraryItemUserStatus?.[id] === | ||||
|                 LibraryItemUserStatus.Have | ||||
|               } | ||||
|               onClick={(event) => { | ||||
|                 event.preventDefault(); | ||||
|                 appLayout.setLibraryItemUserStatus((current) => { | ||||
|                   const newLibraryItemUserStatus = current | ||||
|                     ? { ...current } | ||||
|                     : {}; | ||||
|                   newLibraryItemUserStatus[id] = | ||||
|                     newLibraryItemUserStatus[id] === LibraryItemUserStatus.Have | ||||
|                       ? LibraryItemUserStatus.None | ||||
|                       : LibraryItemUserStatus.Have; | ||||
|                   return newLibraryItemUserStatus; | ||||
|                 }); | ||||
|               }} | ||||
|             /> | ||||
|           </ToolTip> | ||||
|         </div> | ||||
|       )} | ||||
|       <div | ||||
|         className={`flex flex-row place-content-center place-items-center ${ | ||||
|           expand ? "gap-4" : "gap-2" | ||||
|         }`}
 | ||||
|       > | ||||
|         <ToolTip content={langui.want_it} disabled={expand}> | ||||
|           <Button | ||||
|             icon={Icon.Favorite} | ||||
|             text={expand ? langui.want_it : undefined} | ||||
|             active={ | ||||
|               appLayout.libraryItemUserStatus?.[id] === | ||||
|               LibraryItemUserStatus.Want | ||||
|             } | ||||
|             onClick={(event) => { | ||||
|               event.preventDefault(); | ||||
|               appLayout.setLibraryItemUserStatus((current) => { | ||||
|                 const newLibraryItemUserStatus = current ? { ...current } : {}; | ||||
|                 newLibraryItemUserStatus[id] = | ||||
|                   newLibraryItemUserStatus[id] === LibraryItemUserStatus.Want | ||||
|                     ? LibraryItemUserStatus.None | ||||
|                     : LibraryItemUserStatus.Want; | ||||
|                 return newLibraryItemUserStatus; | ||||
|               }); | ||||
|             }} | ||||
|           /> | ||||
|         </ToolTip> | ||||
|         <ToolTip content={langui.have_it} disabled={expand}> | ||||
|           <Button | ||||
|             icon={Icon.BackHand} | ||||
|             text={expand ? langui.have_it : undefined} | ||||
|             active={ | ||||
|               appLayout.libraryItemUserStatus?.[id] === | ||||
|               LibraryItemUserStatus.Have | ||||
|             } | ||||
|             onClick={(event) => { | ||||
|               event.preventDefault(); | ||||
|               appLayout.setLibraryItemUserStatus((current) => { | ||||
|                 const newLibraryItemUserStatus = current ? { ...current } : {}; | ||||
|                 newLibraryItemUserStatus[id] = | ||||
|                   newLibraryItemUserStatus[id] === LibraryItemUserStatus.Have | ||||
|                     ? LibraryItemUserStatus.None | ||||
|                     : LibraryItemUserStatus.Have; | ||||
|                 return newLibraryItemUserStatus; | ||||
|               }); | ||||
|             }} | ||||
|           /> | ||||
|         </ToolTip> | ||||
|       </div> | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -13,10 +13,14 @@ import { | ||||
|   isDefined, | ||||
|   isDefinedAndNotEmpty, | ||||
| } from "helpers/others"; | ||||
| 
 | ||||
| import { useSmartLanguage } from "hooks/useSmartLanguage"; | ||||
| import { Fragment, useCallback, useMemo } from "react"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   openLightBox: (images: string[], index?: number) => void; | ||||
|   scanSet: NonNullable< | ||||
| @ -45,10 +49,17 @@ interface Props { | ||||
|   >["content"]; | ||||
| } | ||||
| 
 | ||||
| export function ScanSet(props: Props): JSX.Element { | ||||
|   const { openLightBox, scanSet, slug, title, languages, langui, content } = | ||||
|     props; | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const ScanSet = ({ | ||||
|   openLightBox, | ||||
|   scanSet, | ||||
|   slug, | ||||
|   title, | ||||
|   languages, | ||||
|   langui, | ||||
|   content, | ||||
| }: Props): JSX.Element => { | ||||
|   const [selectedScan, LanguageSwitcher, languageSwitcherProps] = | ||||
|     useSmartLanguage({ | ||||
|       items: scanSet, | ||||
| @ -227,4 +238,4 @@ export function ScanSet(props: Props): JSX.Element { | ||||
|       )} | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -9,10 +9,14 @@ import { | ||||
| import { AppStaticProps } from "graphql/getAppStaticProps"; | ||||
| import { getAssetURL, ImageQuality } from "helpers/img"; | ||||
| import { filterHasAttributes, getStatusDescription } from "helpers/others"; | ||||
| 
 | ||||
| import { useSmartLanguage } from "hooks/useSmartLanguage"; | ||||
| import { Fragment, useCallback, useMemo } from "react"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   openLightBox: (images: string[], index?: number) => void; | ||||
|   images: NonNullable< | ||||
| @ -26,9 +30,14 @@ interface Props { | ||||
|   langui: AppStaticProps["langui"]; | ||||
| } | ||||
| 
 | ||||
| export function ScanSetCover(props: Props): JSX.Element { | ||||
|   const { openLightBox, images, languages, langui } = props; | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const ScanSetCover = ({ | ||||
|   openLightBox, | ||||
|   images, | ||||
|   languages, | ||||
|   langui, | ||||
| }: Props): JSX.Element => { | ||||
|   const [selectedScan, LanguageSwitcher, languageSwitcherProps] = | ||||
|     useSmartLanguage({ | ||||
|       items: images, | ||||
| @ -176,4 +185,4 @@ export function ScanSetCover(props: Props): JSX.Element { | ||||
|     ); | ||||
|   } | ||||
|   return <></>; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -6,6 +6,18 @@ import { Button } from "./Inputs/Button"; | ||||
| import { Popup } from "./Popup"; | ||||
| import { Icon } from "components/Ico"; | ||||
| 
 | ||||
| /* | ||||
|  *                                         ╭─────────────╮ | ||||
|  * ────────────────────────────────────────╯  CONSTANTS  ╰────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| const SENSIBILITY_SWIPE = 0.5; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   setState: | ||||
|     | Dispatch<SetStateAction<boolean | undefined>> | ||||
| @ -16,11 +28,15 @@ interface Props { | ||||
|   setIndex: Dispatch<SetStateAction<number>>; | ||||
| } | ||||
| 
 | ||||
| const SENSIBILITY_SWIPE = 0.5; | ||||
| 
 | ||||
| export function LightBox(props: Props): JSX.Element { | ||||
|   const { state, setState, images, index, setIndex } = props; | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const LightBox = ({ | ||||
|   state, | ||||
|   setState, | ||||
|   images, | ||||
|   index, | ||||
|   setIndex, | ||||
| }: Props): JSX.Element => { | ||||
|   const handlePrevious = useCallback(() => { | ||||
|     if (index > 0) setIndex(index - 1); | ||||
|   }, [index, setIndex]); | ||||
| @ -83,4 +99,4 @@ export function LightBox(props: Props): JSX.Element { | ||||
|       )} | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -4,24 +4,33 @@ import { Img } from "components/Img"; | ||||
| import { InsetBox } from "components/InsetBox"; | ||||
| import { ToolTip } from "components/ToolTip"; | ||||
| import { useAppLayout } from "contexts/AppLayoutContext"; | ||||
| import { AppStaticProps } from "graphql/getAppStaticProps"; | ||||
| import { cJoin } from "helpers/className"; | ||||
| import { slugify } from "helpers/formatters"; | ||||
| import { getAssetURL, ImageQuality } from "helpers/img"; | ||||
| import { isDefined, isDefinedAndNotEmpty } from "helpers/others"; | ||||
| 
 | ||||
| import { useLightBox } from "hooks/useLightBox"; | ||||
| import Markdown from "markdown-to-jsx"; | ||||
| import { useRouter } from "next/router"; | ||||
| import React, { useMemo } from "react"; | ||||
| import React, { Fragment, useMemo } from "react"; | ||||
| import ReactDOMServer from "react-dom/server"; | ||||
| 
 | ||||
| interface Props { | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface MarkdawnProps { | ||||
|   className?: string; | ||||
|   text: string; | ||||
| } | ||||
| 
 | ||||
| export function Markdawn(props: Props): JSX.Element { | ||||
|   const { className, text: rawText } = props; | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const Markdawn = ({ | ||||
|   className, | ||||
|   text: rawText, | ||||
| }: MarkdawnProps): JSX.Element => { | ||||
|   const appLayout = useAppLayout(); | ||||
|   const router = useRouter(); | ||||
|   const [openLightBox, LightBox] = useLightBox(); | ||||
| @ -299,33 +308,121 @@ export function Markdawn(props: Props): JSX.Element { | ||||
|     ); | ||||
|   } | ||||
|   return <></>; | ||||
| }; | ||||
| 
 | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| interface TableOfContentsProps { | ||||
|   text: string; | ||||
|   title?: string; | ||||
|   langui: AppStaticProps["langui"]; | ||||
| } | ||||
| 
 | ||||
| function HeaderToolTip(props: { id: string }) { | ||||
|   return ( | ||||
|     <ToolTip | ||||
|       content={"Copy anchor link"} | ||||
|       trigger="mouseenter" | ||||
|       className="text-sm" | ||||
|     > | ||||
|       <ToolTip content={"Copied! 👍"} trigger="click" className="text-sm"> | ||||
|         <Ico | ||||
|           icon={Icon.Link} | ||||
|           className="transition-color cursor-pointer hover:text-dark" | ||||
|           onClick={() => { | ||||
|             navigator.clipboard.writeText( | ||||
|               `${process.env.NEXT_PUBLIC_URL_SELF + window.location.pathname}#${ | ||||
|                 props.id | ||||
|               }` | ||||
|             ); | ||||
|           }} | ||||
|         /> | ||||
|       </ToolTip> | ||||
|     </ToolTip> | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const TableOfContents = ({ | ||||
|   text, | ||||
|   title, | ||||
|   langui, | ||||
| }: TableOfContentsProps): JSX.Element => { | ||||
|   const router = useRouter(); | ||||
|   const toc = useMemo( | ||||
|     () => getTocFromMarkdawn(preprocessMarkDawn(text), title), | ||||
|     [text, title] | ||||
|   ); | ||||
| 
 | ||||
|   return ( | ||||
|     <> | ||||
|       <h3 className="text-xl">{langui.table_of_contents}</h3> | ||||
|       <div className="max-w-[14.5rem] text-left"> | ||||
|         <p className="relative my-2 overflow-x-hidden text-ellipsis whitespace-nowrap text-left"> | ||||
|           <a onClick={async () => router.replace(`#${toc.slug}`)}> | ||||
|             {<abbr title={toc.title}>{toc.title}</abbr>} | ||||
|           </a> | ||||
|         </p> | ||||
|         <TocLevel tocchildren={toc.children} parentNumbering="" /> | ||||
|       </div> | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  PRIVATE COMPONENTS  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface TocInterface { | ||||
|   title: string; | ||||
|   slug: string; | ||||
|   children: TocInterface[]; | ||||
| } | ||||
| 
 | ||||
| function typographicRules(text: string): string { | ||||
| interface LevelProps { | ||||
|   tocchildren: TocInterface[]; | ||||
|   parentNumbering: string; | ||||
| } | ||||
| 
 | ||||
| const TocLevel = ({ | ||||
|   tocchildren, | ||||
|   parentNumbering, | ||||
| }: LevelProps): JSX.Element => { | ||||
|   const router = useRouter(); | ||||
| 
 | ||||
|   return ( | ||||
|     <ol className="pl-4 text-left"> | ||||
|       {tocchildren.map((child, childIndex) => ( | ||||
|         <Fragment key={child.slug}> | ||||
|           <li className="my-2 w-full overflow-x-hidden text-ellipsis whitespace-nowrap"> | ||||
|             <span className="text-dark">{`${parentNumbering}${ | ||||
|               childIndex + 1 | ||||
|             }.`}</span>{" "}
 | ||||
|             <a onClick={async () => router.replace(`#${child.slug}`)}> | ||||
|               {<abbr title={child.title}>{child.title}</abbr>} | ||||
|             </a> | ||||
|           </li> | ||||
|           <TocLevel | ||||
|             tocchildren={child.children} | ||||
|             parentNumbering={`${parentNumbering}${childIndex + 1}.`} | ||||
|           /> | ||||
|         </Fragment> | ||||
|       ))} | ||||
|     </ol> | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| /* | ||||
|  *                                      ╭───────────────────╮ | ||||
|  * ─────────────────────────────────────╯  PRIVATE METHODS  ╰─────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| const HeaderToolTip = (props: { id: string }): JSX.Element => ( | ||||
|   <ToolTip | ||||
|     content={"Copy anchor link"} | ||||
|     trigger="mouseenter" | ||||
|     className="text-sm" | ||||
|   > | ||||
|     <ToolTip content={"Copied! 👍"} trigger="click" className="text-sm"> | ||||
|       <Ico | ||||
|         icon={Icon.Link} | ||||
|         className="transition-color cursor-pointer hover:text-dark" | ||||
|         onClick={() => { | ||||
|           navigator.clipboard.writeText( | ||||
|             `${process.env.NEXT_PUBLIC_URL_SELF + window.location.pathname}#${ | ||||
|               props.id | ||||
|             }` | ||||
|           ); | ||||
|         }} | ||||
|       /> | ||||
|     </ToolTip> | ||||
|   </ToolTip> | ||||
| ); | ||||
| 
 | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  PRIVATE COMPONENTS  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| const typographicRules = (text: string): string => { | ||||
|   let newText = text; | ||||
|   newText = newText.replace(/--/gu, "—"); | ||||
|   /* | ||||
| @ -336,9 +433,20 @@ function typographicRules(text: string): string { | ||||
|    * newText = newText.replace(/'/gu, "’"); | ||||
|    */ | ||||
|   return newText; | ||||
| }; | ||||
| 
 | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| enum HeaderLevels { | ||||
|   H1 = 1, | ||||
|   H2 = 2, | ||||
|   H3 = 3, | ||||
|   H4 = 4, | ||||
|   H5 = 5, | ||||
|   H6 = 6, | ||||
| } | ||||
| 
 | ||||
| export function preprocessMarkDawn(text: string): string { | ||||
| const preprocessMarkDawn = (text: string): string => { | ||||
|   if (!text) return ""; | ||||
| 
 | ||||
|   let preprocessed = typographicRules(text); | ||||
| @ -383,22 +491,15 @@ export function preprocessMarkDawn(text: string): string { | ||||
|     .join("\n"); | ||||
| 
 | ||||
|   return preprocessed; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| enum HeaderLevels { | ||||
|   H1 = 1, | ||||
|   H2 = 2, | ||||
|   H3 = 3, | ||||
|   H4 = 4, | ||||
|   H5 = 5, | ||||
|   H6 = 6, | ||||
| } | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| function markdawnHeadersParser( | ||||
| const markdawnHeadersParser = ( | ||||
|   headerLevel: HeaderLevels, | ||||
|   line: string, | ||||
|   visitedSlugs: string[] | ||||
| ): string { | ||||
| ): string => { | ||||
|   const lineText = line.slice(headerLevel + 1); | ||||
|   const slug = slugify(lineText); | ||||
|   let newSlug = slug; | ||||
| @ -409,4 +510,102 @@ function markdawnHeadersParser( | ||||
|   } | ||||
|   visitedSlugs.push(newSlug); | ||||
|   return `<h${headerLevel} id="${newSlug}">${lineText}</h${headerLevel}>`; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| const getTocFromMarkdawn = (text: string, title?: string): TocInterface => { | ||||
|   const toc: TocInterface = { | ||||
|     title: title ?? "Return to top", | ||||
|     slug: slugify(title), | ||||
|     children: [], | ||||
|   }; | ||||
|   let h2 = -1; | ||||
|   let h3 = -1; | ||||
|   let h4 = -1; | ||||
|   let h5 = -1; | ||||
|   let scenebreak = 0; | ||||
|   let scenebreakIndex = 0; | ||||
| 
 | ||||
|   const getTitle = (line: string): string => | ||||
|     line.slice(line.indexOf(`">`) + 2, line.indexOf("</")); | ||||
| 
 | ||||
|   const getSlug = (line: string): string => | ||||
|     line.slice(line.indexOf(`id="`) + 4, line.indexOf(`">`)); | ||||
| 
 | ||||
|   text.split("\n").map((line) => { | ||||
|     if (line.startsWith("<h1 id=")) { | ||||
|       toc.title = getTitle(line); | ||||
|       toc.slug = getSlug(line); | ||||
|     } else if (line.startsWith("<h2 id=")) { | ||||
|       toc.children.push({ | ||||
|         title: getTitle(line), | ||||
|         slug: getSlug(line), | ||||
|         children: [], | ||||
|       }); | ||||
|       h2 += 1; | ||||
|       h3 = -1; | ||||
|       h4 = -1; | ||||
|       h5 = -1; | ||||
|       scenebreak = 0; | ||||
|     } else if (h2 >= 0 && line.startsWith("<h3 id=")) { | ||||
|       toc.children[h2].children.push({ | ||||
|         title: getTitle(line), | ||||
|         slug: getSlug(line), | ||||
|         children: [], | ||||
|       }); | ||||
|       h3 += 1; | ||||
|       h4 = -1; | ||||
|       h5 = -1; | ||||
|       scenebreak = 0; | ||||
|     } else if (h3 >= 0 && line.startsWith("<h4 id=")) { | ||||
|       toc.children[h2].children[h3].children.push({ | ||||
|         title: getTitle(line), | ||||
|         slug: getSlug(line), | ||||
|         children: [], | ||||
|       }); | ||||
|       h4 += 1; | ||||
|       h5 = -1; | ||||
|       scenebreak = 0; | ||||
|     } else if (h4 >= 0 && line.startsWith("<h5 id=")) { | ||||
|       toc.children[h2].children[h3].children[h4].children.push({ | ||||
|         title: getTitle(line), | ||||
|         slug: getSlug(line), | ||||
|         children: [], | ||||
|       }); | ||||
|       h5 += 1; | ||||
|       scenebreak = 0; | ||||
|     } else if (h5 >= 0 && line.startsWith("<h6 id=")) { | ||||
|       toc.children[h2].children[h3].children[h4].children[h5].children.push({ | ||||
|         title: getTitle(line), | ||||
|         slug: getSlug(line), | ||||
|         children: [], | ||||
|       }); | ||||
|     } else if (line.startsWith(`<SceneBreak`)) { | ||||
|       scenebreak += 1; | ||||
|       scenebreakIndex += 1; | ||||
| 
 | ||||
|       const child = { | ||||
|         title: `Scene break ${scenebreak}`, | ||||
|         slug: slugify(`scene-break-${scenebreakIndex}`), | ||||
|         children: [], | ||||
|       }; | ||||
| 
 | ||||
|       if (h5 >= 0) { | ||||
|         toc.children[h2].children[h3].children[h4].children[h5].children.push( | ||||
|           child | ||||
|         ); | ||||
|       } else if (h4 >= 0) { | ||||
|         toc.children[h2].children[h3].children[h4].children.push(child); | ||||
|       } else if (h3 >= 0) { | ||||
|         toc.children[h2].children[h3].children.push(child); | ||||
|       } else if (h2 >= 0) { | ||||
|         toc.children[h2].children.push(child); | ||||
|       } else { | ||||
|         toc.children.push(child); | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   return toc; | ||||
| }; | ||||
|  | ||||
| @ -1,168 +0,0 @@ | ||||
| import { slugify } from "helpers/formatters"; | ||||
| 
 | ||||
| import { useRouter } from "next/router"; | ||||
| import { Fragment, useMemo } from "react"; | ||||
| import { preprocessMarkDawn } from "./Markdawn"; | ||||
| 
 | ||||
| interface Props { | ||||
|   text: string; | ||||
|   title?: string; | ||||
| } | ||||
| 
 | ||||
| export function TOC(props: Props): JSX.Element { | ||||
|   const { text, title } = props; | ||||
|   const router = useRouter(); | ||||
|   const toc = useMemo( | ||||
|     () => getTocFromMarkdawn(preprocessMarkDawn(text), title), | ||||
|     [text, title] | ||||
|   ); | ||||
| 
 | ||||
|   return ( | ||||
|     <> | ||||
|       {/* TODO: add to LANGUI */} | ||||
|       <h3 className="text-xl">Table of content</h3> | ||||
|       <div className="max-w-[14.5rem] text-left"> | ||||
|         <p className="relative my-2 overflow-x-hidden text-ellipsis whitespace-nowrap text-left"> | ||||
|           <a onClick={async () => router.replace(`#${toc.slug}`)}> | ||||
|             {<abbr title={toc.title}>{toc.title}</abbr>} | ||||
|           </a> | ||||
|         </p> | ||||
|         <TOCLevel tocchildren={toc.children} parentNumbering="" /> | ||||
|       </div> | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| interface LevelProps { | ||||
|   tocchildren: TOCInterface[]; | ||||
|   parentNumbering: string; | ||||
| } | ||||
| 
 | ||||
| function TOCLevel(props: LevelProps): JSX.Element { | ||||
|   const router = useRouter(); | ||||
|   const { tocchildren, parentNumbering } = props; | ||||
|   return ( | ||||
|     <ol className="pl-4 text-left"> | ||||
|       {tocchildren.map((child, childIndex) => ( | ||||
|         <Fragment key={child.slug}> | ||||
|           <li className="my-2 w-full overflow-x-hidden text-ellipsis whitespace-nowrap"> | ||||
|             <span className="text-dark">{`${parentNumbering}${ | ||||
|               childIndex + 1 | ||||
|             }.`}</span>{" "}
 | ||||
|             <a onClick={async () => router.replace(`#${child.slug}`)}> | ||||
|               {<abbr title={child.title}>{child.title}</abbr>} | ||||
|             </a> | ||||
|           </li> | ||||
|           <TOCLevel | ||||
|             tocchildren={child.children} | ||||
|             parentNumbering={`${parentNumbering}${childIndex + 1}.`} | ||||
|           /> | ||||
|         </Fragment> | ||||
|       ))} | ||||
|     </ol> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| interface TOCInterface { | ||||
|   title: string; | ||||
|   slug: string; | ||||
|   children: TOCInterface[]; | ||||
| } | ||||
| 
 | ||||
| function getTocFromMarkdawn(text: string, title?: string): TOCInterface { | ||||
|   const toc: TOCInterface = { | ||||
|     title: title ?? "Return to top", | ||||
|     slug: slugify(title), | ||||
|     children: [], | ||||
|   }; | ||||
|   let h2 = -1; | ||||
|   let h3 = -1; | ||||
|   let h4 = -1; | ||||
|   let h5 = -1; | ||||
|   let scenebreak = 0; | ||||
|   let scenebreakIndex = 0; | ||||
| 
 | ||||
|   function getTitle(line: string): string { | ||||
|     return line.slice(line.indexOf(`">`) + 2, line.indexOf("</")); | ||||
|   } | ||||
| 
 | ||||
|   function getSlug(line: string): string { | ||||
|     return line.slice(line.indexOf(`id="`) + 4, line.indexOf(`">`)); | ||||
|   } | ||||
| 
 | ||||
|   text.split("\n").map((line) => { | ||||
|     if (line.startsWith("<h1 id=")) { | ||||
|       toc.title = getTitle(line); | ||||
|       toc.slug = getSlug(line); | ||||
|     } else if (line.startsWith("<h2 id=")) { | ||||
|       toc.children.push({ | ||||
|         title: getTitle(line), | ||||
|         slug: getSlug(line), | ||||
|         children: [], | ||||
|       }); | ||||
|       h2 += 1; | ||||
|       h3 = -1; | ||||
|       h4 = -1; | ||||
|       h5 = -1; | ||||
|       scenebreak = 0; | ||||
|     } else if (h2 >= 0 && line.startsWith("<h3 id=")) { | ||||
|       toc.children[h2].children.push({ | ||||
|         title: getTitle(line), | ||||
|         slug: getSlug(line), | ||||
|         children: [], | ||||
|       }); | ||||
|       h3 += 1; | ||||
|       h4 = -1; | ||||
|       h5 = -1; | ||||
|       scenebreak = 0; | ||||
|     } else if (h3 >= 0 && line.startsWith("<h4 id=")) { | ||||
|       toc.children[h2].children[h3].children.push({ | ||||
|         title: getTitle(line), | ||||
|         slug: getSlug(line), | ||||
|         children: [], | ||||
|       }); | ||||
|       h4 += 1; | ||||
|       h5 = -1; | ||||
|       scenebreak = 0; | ||||
|     } else if (h4 >= 0 && line.startsWith("<h5 id=")) { | ||||
|       toc.children[h2].children[h3].children[h4].children.push({ | ||||
|         title: getTitle(line), | ||||
|         slug: getSlug(line), | ||||
|         children: [], | ||||
|       }); | ||||
|       h5 += 1; | ||||
|       scenebreak = 0; | ||||
|     } else if (h5 >= 0 && line.startsWith("<h6 id=")) { | ||||
|       toc.children[h2].children[h3].children[h4].children[h5].children.push({ | ||||
|         title: getTitle(line), | ||||
|         slug: getSlug(line), | ||||
|         children: [], | ||||
|       }); | ||||
|     } else if (line.startsWith(`<SceneBreak`)) { | ||||
|       scenebreak += 1; | ||||
|       scenebreakIndex += 1; | ||||
| 
 | ||||
|       const child = { | ||||
|         title: `Scene break ${scenebreak}`, | ||||
|         slug: slugify(`scene-break-${scenebreakIndex}`), | ||||
|         children: [], | ||||
|       }; | ||||
| 
 | ||||
|       if (h5 >= 0) { | ||||
|         toc.children[h2].children[h3].children[h4].children[h5].children.push( | ||||
|           child | ||||
|         ); | ||||
|       } else if (h4 >= 0) { | ||||
|         toc.children[h2].children[h3].children[h4].children.push(child); | ||||
|       } else if (h3 >= 0) { | ||||
|         toc.children[h2].children[h3].children.push(child); | ||||
|       } else if (h2 >= 0) { | ||||
|         toc.children[h2].children.push(child); | ||||
|       } else { | ||||
|         toc.children.push(child); | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   return toc; | ||||
| } | ||||
| @ -2,29 +2,30 @@ import { Ico, Icon } from "components/Ico"; | ||||
| import { cIf, cJoin } from "helpers/className"; | ||||
| import { isDefined } from "helpers/others"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   message: string; | ||||
|   icon?: Icon; | ||||
| } | ||||
| 
 | ||||
| export function ContentPlaceholder(props: Props): JSX.Element { | ||||
|   const { message, icon } = props; | ||||
|   return ( | ||||
|     <div className="grid h-full place-content-center"> | ||||
|       <div | ||||
|         className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const ContentPlaceholder = ({ message, icon }: Props): JSX.Element => ( | ||||
|   <div className="grid h-full place-content-center"> | ||||
|     <div | ||||
|       className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted | ||||
|         border-dark p-8 text-dark opacity-40" | ||||
|     > | ||||
|       {isDefined(icon) && <Ico icon={icon} className="!text-[300%]" />} | ||||
|       <p | ||||
|         className={cJoin("w-64 text-2xl", cIf(!isDefined(icon), "text-center"))} | ||||
|       > | ||||
|         {isDefined(icon) && <Ico icon={icon} className="!text-[300%]" />} | ||||
|         <p | ||||
|           className={cJoin( | ||||
|             "w-64 text-2xl", | ||||
|             cIf(!isDefined(icon), "text-center") | ||||
|           )} | ||||
|         > | ||||
|           {message} | ||||
|         </p> | ||||
|       </div> | ||||
|         {message} | ||||
|       </p> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
|   </div> | ||||
| ); | ||||
|  | ||||
| @ -2,10 +2,14 @@ import { Ico, Icon } from "components/Ico"; | ||||
| import { ToolTip } from "components/ToolTip"; | ||||
| import { cJoin, cIf } from "helpers/className"; | ||||
| import { isDefinedAndNotEmpty } from "helpers/others"; | ||||
| 
 | ||||
| import { useRouter } from "next/router"; | ||||
| import { MouseEventHandler, useMemo } from "react"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   url: string; | ||||
|   icon?: Icon; | ||||
| @ -16,16 +20,17 @@ interface Props { | ||||
|   onClick?: MouseEventHandler<HTMLDivElement>; | ||||
| } | ||||
| 
 | ||||
| export function NavOption(props: Props): JSX.Element { | ||||
|   const { | ||||
|     url, | ||||
|     icon, | ||||
|     title, | ||||
|     subtitle, | ||||
|     border = false, | ||||
|     reduced = false, | ||||
|     onClick, | ||||
|   } = props; | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const NavOption = ({ | ||||
|   url, | ||||
|   icon, | ||||
|   title, | ||||
|   subtitle, | ||||
|   border = false, | ||||
|   reduced = false, | ||||
|   onClick, | ||||
| }: Props): JSX.Element => { | ||||
|   const router = useRouter(); | ||||
|   const isActive = useMemo( | ||||
|     () => router.asPath.startsWith(url), | ||||
| @ -82,4 +87,4 @@ export function NavOption(props: Props): JSX.Element { | ||||
|       </div> | ||||
|     </ToolTip> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -2,22 +2,30 @@ import { HorizontalLine } from "components/HorizontalLine"; | ||||
| import { Ico, Icon } from "components/Ico"; | ||||
| import { isDefinedAndNotEmpty } from "helpers/others"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   icon?: Icon; | ||||
|   title: string | null | undefined; | ||||
|   description?: string | null | undefined; | ||||
| } | ||||
| 
 | ||||
| export function PanelHeader(props: Props): JSX.Element { | ||||
|   const { icon, description, title } = props; | ||||
|   return ( | ||||
|     <> | ||||
|       <div className="grid w-full place-items-center"> | ||||
|         {icon && <Ico icon={icon} className="mb-3 !text-4xl" />} | ||||
|         <h2 className="text-2xl">{title}</h2> | ||||
|         {isDefinedAndNotEmpty(description) && <p>{description}</p>} | ||||
|       </div> | ||||
|       <HorizontalLine /> | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const PanelHeader = ({ | ||||
|   icon, | ||||
|   description, | ||||
|   title, | ||||
| }: Props): JSX.Element => ( | ||||
|   <> | ||||
|     <div className="grid w-full place-items-center"> | ||||
|       {icon && <Ico icon={icon} className="mb-3 !text-4xl" />} | ||||
|       <h2 className="text-2xl">{title}</h2> | ||||
|       {isDefinedAndNotEmpty(description) && <p>{description}</p>} | ||||
|     </div> | ||||
|     <HorizontalLine /> | ||||
|   </> | ||||
| ); | ||||
|  | ||||
| @ -5,6 +5,11 @@ import { useAppLayout } from "contexts/AppLayoutContext"; | ||||
| import { AppStaticProps } from "graphql/getAppStaticProps"; | ||||
| import { cJoin } from "helpers/className"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   href: string; | ||||
|   title: string | null | undefined; | ||||
| @ -20,8 +25,16 @@ export enum ReturnButtonType { | ||||
|   Both = "both", | ||||
| } | ||||
| 
 | ||||
| export function ReturnButton(props: Props): JSX.Element { | ||||
|   const { href, title, langui, displayOn, horizontalLine, className } = props; | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const ReturnButton = ({ | ||||
|   href, | ||||
|   title, | ||||
|   langui, | ||||
|   displayOn, | ||||
|   horizontalLine, | ||||
|   className, | ||||
| }: Props): JSX.Element => { | ||||
|   const appLayout = useAppLayout(); | ||||
| 
 | ||||
|   return ( | ||||
| @ -44,4 +57,4 @@ export function ReturnButton(props: Props): JSX.Element { | ||||
|       {horizontalLine === true && <HorizontalLine />} | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -1,5 +1,10 @@ | ||||
| import { cJoin } from "helpers/className"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   children: React.ReactNode; | ||||
|   width?: ContentPanelWidthSizes; | ||||
| @ -12,24 +17,26 @@ export enum ContentPanelWidthSizes { | ||||
|   Full = "full", | ||||
| } | ||||
| 
 | ||||
| export function ContentPanel(props: Props): JSX.Element { | ||||
|   const { width = ContentPanelWidthSizes.Default, children, className } = props; | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
|   return ( | ||||
|     <div className="grid h-full"> | ||||
|       <main | ||||
|         className={cJoin( | ||||
|           "justify-self-center px-4 pt-10 pb-20 desktop:px-10 desktop:pt-20 desktop:pb-32", | ||||
|           width === ContentPanelWidthSizes.Default | ||||
|             ? "max-w-2xl" | ||||
|             : width === ContentPanelWidthSizes.Large | ||||
|             ? "max-w-4xl" | ||||
|             : "w-full", | ||||
|           className | ||||
|         )} | ||||
|       > | ||||
|         {children} | ||||
|       </main> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| export const ContentPanel = ({ | ||||
|   width = ContentPanelWidthSizes.Default, | ||||
|   children, | ||||
|   className, | ||||
| }: Props): JSX.Element => ( | ||||
|   <div className="grid h-full"> | ||||
|     <main | ||||
|       className={cJoin( | ||||
|         "justify-self-center px-4 pt-10 pb-20 desktop:px-10 desktop:pt-20 desktop:pb-32", | ||||
|         width === ContentPanelWidthSizes.Default | ||||
|           ? "max-w-2xl" | ||||
|           : width === ContentPanelWidthSizes.Large | ||||
|           ? "max-w-4xl" | ||||
|           : "w-full", | ||||
|         className | ||||
|       )} | ||||
|     > | ||||
|       {children} | ||||
|     </main> | ||||
|   </div> | ||||
| ); | ||||
|  | ||||
| @ -12,12 +12,18 @@ import { Icon } from "components/Ico"; | ||||
| import { cIf, cJoin } from "helpers/className"; | ||||
| import { isDefinedAndNotEmpty } from "helpers/others"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   langui: AppStaticProps["langui"]; | ||||
| } | ||||
| 
 | ||||
| export function MainPanel(props: Props): JSX.Element { | ||||
|   const { langui } = props; | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const MainPanel = ({ langui }: Props): JSX.Element => { | ||||
|   const isDesktop = useMediaDesktop(); | ||||
|   const { | ||||
|     mainPanelReduced = false, | ||||
| @ -239,4 +245,4 @@ export function MainPanel(props: Props): JSX.Element { | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -1,11 +1,16 @@ | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   children: React.ReactNode; | ||||
| } | ||||
| 
 | ||||
| export function SubPanel(props: Props): JSX.Element { | ||||
|   return ( | ||||
|     <div className="grid gap-y-2 px-6 pt-10 pb-20 text-center desktop:py-8 desktop:px-10"> | ||||
|       {props.children} | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const SubPanel = ({ children }: Props): JSX.Element => ( | ||||
|   <div className="grid gap-y-2 px-6 pt-10 pb-20 text-center desktop:py-8 desktop:px-10"> | ||||
|     {children} | ||||
|   </div> | ||||
| ); | ||||
|  | ||||
| @ -1,9 +1,13 @@ | ||||
| import { useAppLayout } from "contexts/AppLayoutContext"; | ||||
| import { cIf, cJoin } from "helpers/className"; | ||||
| 
 | ||||
| import { Dispatch, SetStateAction, useEffect } from "react"; | ||||
| import Hotkeys from "react-hot-keys"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   setState: | ||||
|     | Dispatch<SetStateAction<boolean | undefined>> | ||||
| @ -15,16 +19,16 @@ interface Props { | ||||
|   padding?: boolean; | ||||
| } | ||||
| 
 | ||||
| export function Popup(props: Props): JSX.Element { | ||||
|   const { | ||||
|     setState, | ||||
|     state, | ||||
|     children, | ||||
|     fillViewport, | ||||
|     hideBackground = false, | ||||
|     padding = true, | ||||
|   } = props; | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const Popup = ({ | ||||
|   setState, | ||||
|   state, | ||||
|   children, | ||||
|   fillViewport, | ||||
|   hideBackground = false, | ||||
|   padding = true, | ||||
| }: Props): JSX.Element => { | ||||
|   const { setMenuGestures } = useAppLayout(); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
| @ -77,4 +81,4 @@ export function Popup(props: Props): JSX.Element { | ||||
|       </div> | ||||
|     </Hotkeys> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -8,8 +8,7 @@ import { Fragment, useCallback, useMemo } from "react"; | ||||
| import { AppLayout } from "./AppLayout"; | ||||
| import { Chip } from "./Chip"; | ||||
| import { HorizontalLine } from "./HorizontalLine"; | ||||
| import { Markdawn } from "./Markdown/Markdawn"; | ||||
| import { TOC } from "./Markdown/TOC"; | ||||
| import { Markdawn, TableOfContents } from "./Markdown/Markdawn"; | ||||
| import { ReturnButton, ReturnButtonType } from "./PanelComponents/ReturnButton"; | ||||
| import { ContentPanel } from "./Panels/ContentPanel"; | ||||
| import { SubPanel } from "./Panels/SubPanel"; | ||||
| @ -17,6 +16,11 @@ import { RecorderChip } from "./RecorderChip"; | ||||
| import { ThumbnailHeader } from "./ThumbnailHeader"; | ||||
| import { ToolTip } from "./ToolTip"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   post: PostWithTranslations; | ||||
|   langui: AppStaticProps["langui"]; | ||||
| @ -33,22 +37,23 @@ interface Props { | ||||
|   appendBody?: JSX.Element; | ||||
| } | ||||
| 
 | ||||
| export function PostPage(props: Props): JSX.Element { | ||||
|   const { | ||||
|     post, | ||||
|     langui, | ||||
|     languages, | ||||
|     returnHref, | ||||
|     returnTitle, | ||||
|     displayCredits, | ||||
|     displayToc, | ||||
|     displayThumbnailHeader, | ||||
|     displayLanguageSwitcher, | ||||
|     appendBody, | ||||
|     prependBody, | ||||
|   } = props; | ||||
|   const displayTitle = props.displayTitle ?? true; | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const PostPage = ({ | ||||
|   post, | ||||
|   langui, | ||||
|   languages, | ||||
|   returnHref, | ||||
|   returnTitle, | ||||
|   displayCredits, | ||||
|   displayToc, | ||||
|   displayThumbnailHeader, | ||||
|   displayLanguageSwitcher, | ||||
|   appendBody, | ||||
|   prependBody, | ||||
|   displayTitle = true, | ||||
|   currencies, | ||||
| }: Props): JSX.Element => { | ||||
|   const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = | ||||
|     useSmartLanguage({ | ||||
|       items: post.translations, | ||||
| @ -124,7 +129,9 @@ export function PostPage(props: Props): JSX.Element { | ||||
|             </> | ||||
|           )} | ||||
| 
 | ||||
|           {displayToc && <TOC text={body} title={title} />} | ||||
|           {displayToc && ( | ||||
|             <TableOfContents text={body} title={title} langui={langui} /> | ||||
|           )} | ||||
|         </SubPanel> | ||||
|       ) : undefined, | ||||
|     [ | ||||
| @ -216,7 +223,9 @@ export function PostPage(props: Props): JSX.Element { | ||||
|       contentPanel={contentPanel} | ||||
|       subPanel={subPanel} | ||||
|       thumbnail={thumbnail ?? undefined} | ||||
|       {...props} | ||||
|       currencies={currencies} | ||||
|       languages={languages} | ||||
|       langui={langui} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -14,14 +14,18 @@ import { | ||||
|   prettySlug, | ||||
| } from "helpers/formatters"; | ||||
| import { ImageQuality } from "helpers/img"; | ||||
| 
 | ||||
| import { useSmartLanguage } from "hooks/useSmartLanguage"; | ||||
| import Link from "next/link"; | ||||
| import { useCallback } from "react"; | ||||
| import { useCallback, useMemo } from "react"; | ||||
| import { Chip } from "./Chip"; | ||||
| import { Ico, Icon } from "./Ico"; | ||||
| import { Img } from "./Img"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   thumbnail?: UploadImageFragment | string | null | undefined; | ||||
|   thumbnailAspectRatio?: string; | ||||
| @ -53,75 +57,78 @@ interface Props { | ||||
|     | { __typename: "anotherHoverlayName" }; | ||||
| } | ||||
| 
 | ||||
| export function PreviewCard(props: Props): JSX.Element { | ||||
|   const { | ||||
|     href, | ||||
|     thumbnail, | ||||
|     thumbnailAspectRatio = "4/3", | ||||
|     thumbnailForceAspectRatio = false, | ||||
|     thumbnailRounded = true, | ||||
|     pre_title, | ||||
|     title, | ||||
|     subtitle, | ||||
|     description, | ||||
|     stackNumber = 0, | ||||
|     topChips, | ||||
|     bottomChips, | ||||
|     keepInfoVisible, | ||||
|     metadata, | ||||
|     hoverlay, | ||||
|     infoAppend, | ||||
|   } = props; | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const PreviewCard = ({ | ||||
|   href, | ||||
|   thumbnail, | ||||
|   thumbnailAspectRatio = "4/3", | ||||
|   thumbnailForceAspectRatio = false, | ||||
|   thumbnailRounded = true, | ||||
|   pre_title, | ||||
|   title, | ||||
|   subtitle, | ||||
|   description, | ||||
|   stackNumber = 0, | ||||
|   topChips, | ||||
|   bottomChips, | ||||
|   keepInfoVisible, | ||||
|   metadata, | ||||
|   hoverlay, | ||||
|   infoAppend, | ||||
| }: Props): JSX.Element => { | ||||
|   const appLayout = useAppLayout(); | ||||
| 
 | ||||
|   const metadataJSX = | ||||
|     metadata && (metadata.release_date || metadata.price) ? ( | ||||
|       <div className="flex w-full flex-row flex-wrap gap-x-3"> | ||||
|         {metadata.release_date && ( | ||||
|           <p className="text-sm mobile:text-xs"> | ||||
|             <Ico | ||||
|               icon={Icon.Event} | ||||
|               className="mr-1 translate-y-[.15em] !text-base" | ||||
|             /> | ||||
|             {prettyDate(metadata.release_date)} | ||||
|           </p> | ||||
|         )} | ||||
|         {metadata.price && metadata.currencies && ( | ||||
|           <p className="justify-self-end text-sm mobile:text-xs"> | ||||
|             <Ico | ||||
|               icon={Icon.ShoppingCart} | ||||
|               className="mr-1 translate-y-[.15em] !text-base" | ||||
|             /> | ||||
|             {prettyPrice( | ||||
|               metadata.price, | ||||
|               metadata.currencies, | ||||
|               appLayout.currency | ||||
|             )} | ||||
|           </p> | ||||
|         )} | ||||
|         {metadata.views && ( | ||||
|           <p className="text-sm mobile:text-xs"> | ||||
|             <Ico | ||||
|               icon={Icon.Visibility} | ||||
|               className="mr-1 translate-y-[.15em] !text-base" | ||||
|             /> | ||||
|             {prettyShortenNumber(metadata.views)} | ||||
|           </p> | ||||
|         )} | ||||
|         {metadata.author && ( | ||||
|           <p className="text-sm mobile:text-xs"> | ||||
|             <Ico | ||||
|               icon={Icon.Person} | ||||
|               className="mr-1 translate-y-[.15em] !text-base" | ||||
|             /> | ||||
|             {metadata.author} | ||||
|           </p> | ||||
|         )} | ||||
|       </div> | ||||
|     ) : ( | ||||
|       <></> | ||||
|     ); | ||||
|   const metadataJSX = useMemo( | ||||
|     () => | ||||
|       metadata && (metadata.release_date || metadata.price) ? ( | ||||
|         <div className="flex w-full flex-row flex-wrap gap-x-3"> | ||||
|           {metadata.release_date && ( | ||||
|             <p className="text-sm mobile:text-xs"> | ||||
|               <Ico | ||||
|                 icon={Icon.Event} | ||||
|                 className="mr-1 translate-y-[.15em] !text-base" | ||||
|               /> | ||||
|               {prettyDate(metadata.release_date)} | ||||
|             </p> | ||||
|           )} | ||||
|           {metadata.price && metadata.currencies && ( | ||||
|             <p className="justify-self-end text-sm mobile:text-xs"> | ||||
|               <Ico | ||||
|                 icon={Icon.ShoppingCart} | ||||
|                 className="mr-1 translate-y-[.15em] !text-base" | ||||
|               /> | ||||
|               {prettyPrice( | ||||
|                 metadata.price, | ||||
|                 metadata.currencies, | ||||
|                 appLayout.currency | ||||
|               )} | ||||
|             </p> | ||||
|           )} | ||||
|           {metadata.views && ( | ||||
|             <p className="text-sm mobile:text-xs"> | ||||
|               <Ico | ||||
|                 icon={Icon.Visibility} | ||||
|                 className="mr-1 translate-y-[.15em] !text-base" | ||||
|               /> | ||||
|               {prettyShortenNumber(metadata.views)} | ||||
|             </p> | ||||
|           )} | ||||
|           {metadata.author && ( | ||||
|             <p className="text-sm mobile:text-xs"> | ||||
|               <Ico | ||||
|                 icon={Icon.Person} | ||||
|                 className="mr-1 translate-y-[.15em] !text-base" | ||||
|               /> | ||||
|               {metadata.author} | ||||
|             </p> | ||||
|           )} | ||||
|         </div> | ||||
|       ) : ( | ||||
|         <></> | ||||
|       ), | ||||
|     [appLayout.currency, metadata] | ||||
|   ); | ||||
| 
 | ||||
|   return ( | ||||
|     <Link href={href} passHref> | ||||
| @ -241,12 +248,13 @@ export function PreviewCard(props: Props): JSX.Element { | ||||
|         )} | ||||
|         <div | ||||
|           className={cJoin( | ||||
|             "z-20 grid gap-2 p-4 transition-opacity linearbg-obi", | ||||
|             "grid gap-2 p-4 transition-opacity linearbg-obi", | ||||
|             cIf( | ||||
|               !keepInfoVisible, | ||||
|               `-inset-x-0.5 bottom-2 opacity-0 group-hover:opacity-100 hoverable:absolute
 | ||||
|               hoverable:drop-shadow-shade-lg notHoverable:rounded-b-md | ||||
|               notHoverable:opacity-100` | ||||
|               `-inset-x-0.5 bottom-2 opacity-0 [border-radius:10%_10%_10%_10%_/_1%_1%_3%_3%]
 | ||||
|               group-hover:opacity-100 hoverable:absolute hoverable:drop-shadow-shade-lg | ||||
|               notHoverable:rounded-b-md notHoverable:opacity-100`,
 | ||||
|               "[border-radius:0%_0%_10%_10%_/_0%_0%_3%_3%]" | ||||
|             ) | ||||
|           )} | ||||
|         > | ||||
| @ -287,7 +295,9 @@ export function PreviewCard(props: Props): JSX.Element { | ||||
|       </div> | ||||
|     </Link> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| interface TranslatedProps | ||||
|   extends Omit<Props, "description" | "pre_title" | "subtitle" | "title"> { | ||||
| @ -302,13 +312,14 @@ interface TranslatedProps | ||||
|   languages: AppStaticProps["languages"]; | ||||
| } | ||||
| 
 | ||||
| export function TranslatedPreviewCard(props: TranslatedProps): JSX.Element { | ||||
|   const { | ||||
|     translations = [{ title: props.slug, language: "default" }], | ||||
|     slug, | ||||
|     languages, | ||||
|   } = props; | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const TranslatedPreviewCard = ({ | ||||
|   slug, | ||||
|   translations = [{ title: slug, language: "default" }], | ||||
|   languages, | ||||
|   ...otherProps | ||||
| }: TranslatedProps): JSX.Element => { | ||||
|   const [selectedTranslation] = useSmartLanguage({ | ||||
|     items: translations, | ||||
|     languages: languages, | ||||
| @ -324,7 +335,7 @@ export function TranslatedPreviewCard(props: TranslatedProps): JSX.Element { | ||||
|       title={selectedTranslation?.title ?? prettySlug(slug)} | ||||
|       subtitle={selectedTranslation?.subtitle} | ||||
|       description={selectedTranslation?.description} | ||||
|       {...props} | ||||
|       {...otherProps} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -2,13 +2,17 @@ import { UploadImageFragment } from "graphql/generated"; | ||||
| import { AppStaticProps } from "graphql/getAppStaticProps"; | ||||
| import { prettySlug } from "helpers/formatters"; | ||||
| import { ImageQuality } from "helpers/img"; | ||||
| 
 | ||||
| import { useSmartLanguage } from "hooks/useSmartLanguage"; | ||||
| import Link from "next/link"; | ||||
| import { useCallback } from "react"; | ||||
| import { Chip } from "./Chip"; | ||||
| import { Img } from "./Img"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   thumbnail?: UploadImageFragment | string | null | undefined; | ||||
|   thumbnailAspectRatio?: string; | ||||
| @ -20,60 +24,60 @@ interface Props { | ||||
|   bottomChips?: string[]; | ||||
| } | ||||
| 
 | ||||
| function PreviewLine(props: Props): JSX.Element { | ||||
|   const { | ||||
|     href, | ||||
|     thumbnail, | ||||
|     pre_title, | ||||
|     title, | ||||
|     subtitle, | ||||
|     topChips, | ||||
|     bottomChips, | ||||
|     thumbnailAspectRatio, | ||||
|   } = props; | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
|   return ( | ||||
|     <Link href={href} passHref> | ||||
|       <div | ||||
|         className="flex h-36 w-full cursor-pointer flex-row place-items-center gap-4 overflow-hidden | ||||
| const PreviewLine = ({ | ||||
|   href, | ||||
|   thumbnail, | ||||
|   pre_title, | ||||
|   title, | ||||
|   subtitle, | ||||
|   topChips, | ||||
|   bottomChips, | ||||
|   thumbnailAspectRatio, | ||||
| }: Props): JSX.Element => ( | ||||
|   <Link href={href} passHref> | ||||
|     <div | ||||
|       className="flex h-36 w-full cursor-pointer flex-row place-items-center gap-4 overflow-hidden | ||||
|         rounded-md bg-light pr-4 transition-transform drop-shadow-shade-xl hover:scale-[1.02]" | ||||
|       > | ||||
|         {thumbnail ? ( | ||||
|           <div className="aspect-[3/2] h-full"> | ||||
|             <Img image={thumbnail} quality={ImageQuality.Medium} /> | ||||
|           </div> | ||||
|         ) : ( | ||||
|           <div style={{ aspectRatio: thumbnailAspectRatio }}></div> | ||||
|         )} | ||||
|         <div className="grid gap-2"> | ||||
|           {topChips && topChips.length > 0 && ( | ||||
|             <div className="grid grid-flow-col place-content-start gap-1 overflow-hidden"> | ||||
|               {topChips.map((text, index) => ( | ||||
|                 <Chip key={index}>{text}</Chip> | ||||
|               ))} | ||||
|             </div> | ||||
|           )} | ||||
|           <div className="my-1 flex flex-col"> | ||||
|             {pre_title && <p className="mb-1 leading-none">{pre_title}</p>} | ||||
|             {title && ( | ||||
|               <p className="font-headers text-lg leading-none">{title}</p> | ||||
|             )} | ||||
|             {subtitle && <p className="leading-none">{subtitle}</p>} | ||||
|           </div> | ||||
|           {bottomChips && bottomChips.length > 0 && ( | ||||
|             <div className="grid grid-flow-col place-content-start gap-1 overflow-hidden"> | ||||
|               {bottomChips.map((text, index) => ( | ||||
|                 <Chip key={index} className="text-sm"> | ||||
|                   {text} | ||||
|                 </Chip> | ||||
|               ))} | ||||
|             </div> | ||||
|           )} | ||||
|     > | ||||
|       {thumbnail ? ( | ||||
|         <div className="aspect-[3/2] h-full"> | ||||
|           <Img image={thumbnail} quality={ImageQuality.Medium} /> | ||||
|         </div> | ||||
|       ) : ( | ||||
|         <div style={{ aspectRatio: thumbnailAspectRatio }}></div> | ||||
|       )} | ||||
|       <div className="grid gap-2"> | ||||
|         {topChips && topChips.length > 0 && ( | ||||
|           <div className="grid grid-flow-col place-content-start gap-1 overflow-hidden"> | ||||
|             {topChips.map((text, index) => ( | ||||
|               <Chip key={index}>{text}</Chip> | ||||
|             ))} | ||||
|           </div> | ||||
|         )} | ||||
|         <div className="my-1 flex flex-col"> | ||||
|           {pre_title && <p className="mb-1 leading-none">{pre_title}</p>} | ||||
|           {title && ( | ||||
|             <p className="font-headers text-lg leading-none">{title}</p> | ||||
|           )} | ||||
|           {subtitle && <p className="leading-none">{subtitle}</p>} | ||||
|         </div> | ||||
|         {bottomChips && bottomChips.length > 0 && ( | ||||
|           <div className="grid grid-flow-col place-content-start gap-1 overflow-hidden"> | ||||
|             {bottomChips.map((text, index) => ( | ||||
|               <Chip key={index} className="text-sm"> | ||||
|                 {text} | ||||
|               </Chip> | ||||
|             ))} | ||||
|           </div> | ||||
|         )} | ||||
|       </div> | ||||
|     </Link> | ||||
|   ); | ||||
| } | ||||
|     </div> | ||||
|   </Link> | ||||
| ); | ||||
| 
 | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| interface TranslatedProps | ||||
|   extends Omit<Props, "pre_title" | "subtitle" | "title"> { | ||||
| @ -88,13 +92,14 @@ interface TranslatedProps | ||||
|   languages: AppStaticProps["languages"]; | ||||
| } | ||||
| 
 | ||||
| export function TranslatedPreviewLine(props: TranslatedProps): JSX.Element { | ||||
|   const { | ||||
|     translations = [{ title: props.slug, language: "default" }], | ||||
|     slug, | ||||
|     languages, | ||||
|   } = props; | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const TranslatedPreviewLine = ({ | ||||
|   slug, | ||||
|   translations = [{ title: slug, language: "default" }], | ||||
|   languages, | ||||
|   ...otherProps | ||||
| }: TranslatedProps): JSX.Element => { | ||||
|   const [selectedTranslation] = useSmartLanguage({ | ||||
|     items: translations, | ||||
|     languages: languages, | ||||
| @ -109,7 +114,7 @@ export function TranslatedPreviewLine(props: TranslatedProps): JSX.Element { | ||||
|       pre_title={selectedTranslation?.pre_title} | ||||
|       title={selectedTranslation?.title ?? prettySlug(slug)} | ||||
|       subtitle={selectedTranslation?.subtitle} | ||||
|       {...props} | ||||
|       {...otherProps} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -3,64 +3,67 @@ import { RecorderChipFragment } from "graphql/generated"; | ||||
| import { AppStaticProps } from "graphql/getAppStaticProps"; | ||||
| import { ImageQuality } from "helpers/img"; | ||||
| import { filterHasAttributes } from "helpers/others"; | ||||
| 
 | ||||
| import { Fragment } from "react"; | ||||
| import { Img } from "./Img"; | ||||
| import { Markdawn } from "./Markdown/Markdawn"; | ||||
| import { ToolTip } from "./ToolTip"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   className?: string; | ||||
|   recorder: RecorderChipFragment; | ||||
|   langui: AppStaticProps["langui"]; | ||||
| } | ||||
| 
 | ||||
| export function RecorderChip(props: Props): JSX.Element { | ||||
|   const { recorder, langui } = props; | ||||
|   return ( | ||||
|     <ToolTip | ||||
|       content={ | ||||
|         <div className="grid gap-8 p-2 py-5 text-left"> | ||||
|           <div className="grid grid-flow-col place-content-start place-items-center gap-6"> | ||||
|             {recorder.avatar?.data?.attributes && ( | ||||
|               <Img | ||||
|                 className="w-20 rounded-full border-4 border-mid" | ||||
|                 image={recorder.avatar.data.attributes} | ||||
|                 quality={ImageQuality.Small} | ||||
|               /> | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const RecorderChip = ({ recorder, langui }: Props): JSX.Element => ( | ||||
|   <ToolTip | ||||
|     content={ | ||||
|       <div className="grid gap-8 p-2 py-5 text-left"> | ||||
|         <div className="grid grid-flow-col place-content-start place-items-center gap-6"> | ||||
|           {recorder.avatar?.data?.attributes && ( | ||||
|             <Img | ||||
|               className="w-20 rounded-full border-4 border-mid" | ||||
|               image={recorder.avatar.data.attributes} | ||||
|               quality={ImageQuality.Small} | ||||
|             /> | ||||
|           )} | ||||
|           <div className="grid gap-2"> | ||||
|             <h3 className=" text-2xl">{recorder.username}</h3> | ||||
|             {recorder.languages?.data && recorder.languages.data.length > 0 && ( | ||||
|               <div className="flex flex-row flex-wrap gap-1"> | ||||
|                 <p>{langui.languages}:</p> | ||||
|                 {filterHasAttributes(recorder.languages.data).map( | ||||
|                   (language) => ( | ||||
|                     <Fragment key={language.attributes.code}> | ||||
|                       <Chip>{language.attributes.code.toUpperCase()}</Chip> | ||||
|                     </Fragment> | ||||
|                   ) | ||||
|                 )} | ||||
|               </div> | ||||
|             )} | ||||
|             {recorder.pronouns && ( | ||||
|               <div className="flex flex-row flex-wrap gap-1"> | ||||
|                 <p>{langui.pronouns}:</p> | ||||
|                 <Chip>{recorder.pronouns}</Chip> | ||||
|               </div> | ||||
|             )} | ||||
|             <div className="grid gap-2"> | ||||
|               <h3 className=" text-2xl">{recorder.username}</h3> | ||||
|               {recorder.languages?.data && recorder.languages.data.length > 0 && ( | ||||
|                 <div className="flex flex-row flex-wrap gap-1"> | ||||
|                   <p>{langui.languages}:</p> | ||||
|                   {filterHasAttributes(recorder.languages.data).map( | ||||
|                     (language) => ( | ||||
|                       <Fragment key={language.attributes.code}> | ||||
|                         <Chip>{language.attributes.code.toUpperCase()}</Chip> | ||||
|                       </Fragment> | ||||
|                     ) | ||||
|                   )} | ||||
|                 </div> | ||||
|               )} | ||||
|               {recorder.pronouns && ( | ||||
|                 <div className="flex flex-row flex-wrap gap-1"> | ||||
|                   <p>{langui.pronouns}:</p> | ||||
|                   <Chip>{recorder.pronouns}</Chip> | ||||
|                 </div> | ||||
|               )} | ||||
|             </div> | ||||
|           </div> | ||||
|           {recorder.bio?.[0] && <Markdawn text={recorder.bio[0].bio ?? ""} />} | ||||
|         </div> | ||||
|       } | ||||
|       placement="top" | ||||
|     > | ||||
|       <Chip key={recorder.anonymous_code}> | ||||
|         {recorder.anonymize | ||||
|           ? `Recorder#${recorder.anonymous_code}` | ||||
|           : recorder.username} | ||||
|       </Chip> | ||||
|     </ToolTip> | ||||
|   ); | ||||
| } | ||||
|         {recorder.bio?.[0] && <Markdawn text={recorder.bio[0].bio ?? ""} />} | ||||
|       </div> | ||||
|     } | ||||
|     placement="top" | ||||
|   > | ||||
|     <Chip key={recorder.anonymous_code}> | ||||
|       {recorder.anonymize | ||||
|         ? `Recorder#${recorder.anonymous_code}` | ||||
|         : recorder.username} | ||||
|     </Chip> | ||||
|   </ToolTip> | ||||
| ); | ||||
|  | ||||
| @ -7,9 +7,13 @@ import { AppStaticProps } from "graphql/getAppStaticProps"; | ||||
| import { prettyinlineTitle, prettySlug, slugify } from "helpers/formatters"; | ||||
| import { getAssetURL, ImageQuality } from "helpers/img"; | ||||
| import { filterHasAttributes } from "helpers/others"; | ||||
| 
 | ||||
| import { useLightBox } from "hooks/useLightBox"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   pre_title?: string | null | undefined; | ||||
|   title: string | null | undefined; | ||||
| @ -26,19 +30,19 @@ interface Props { | ||||
|   languageSwitcher?: JSX.Element; | ||||
| } | ||||
| 
 | ||||
| export function ThumbnailHeader(props: Props): JSX.Element { | ||||
|   const { | ||||
|     langui, | ||||
|     pre_title, | ||||
|     title, | ||||
|     subtitle, | ||||
|     thumbnail, | ||||
|     type, | ||||
|     categories, | ||||
|     description, | ||||
|     languageSwitcher, | ||||
|   } = props; | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const ThumbnailHeader = ({ | ||||
|   langui, | ||||
|   pre_title, | ||||
|   title, | ||||
|   subtitle, | ||||
|   thumbnail, | ||||
|   type, | ||||
|   categories, | ||||
|   description, | ||||
|   languageSwitcher, | ||||
| }: Props): JSX.Element => { | ||||
|   const [openLightBox, LightBox] = useLightBox(); | ||||
| 
 | ||||
|   return ( | ||||
| @ -105,4 +109,4 @@ export function ThumbnailHeader(props: Props): JSX.Element { | ||||
|       )} | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -2,20 +2,30 @@ import Tippy, { TippyProps } from "@tippyjs/react"; | ||||
| import { cJoin } from "helpers/className"; | ||||
| import "tippy.js/animations/scale-subtle.css"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props extends TippyProps {} | ||||
| 
 | ||||
| export function ToolTip(props: Props): JSX.Element { | ||||
|   const newProps: Props = { | ||||
|     className: cJoin("text-sm", props.className), | ||||
|     delay: [150, 0], | ||||
|     interactive: true, | ||||
|     animation: "scale-subtle", | ||||
|     ...props, | ||||
|   }; | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
|   return ( | ||||
|     <Tippy {...newProps}> | ||||
|       <div>{props.children}</div> | ||||
|     </Tippy> | ||||
|   ); | ||||
| } | ||||
| export const ToolTip = ({ | ||||
|   className, | ||||
|   delay = [150, 0], | ||||
|   interactive = true, | ||||
|   animation = "scale-subtle", | ||||
|   children, | ||||
|   ...otherProps | ||||
| }: Props): JSX.Element => ( | ||||
|   <Tippy | ||||
|     className={cJoin("text-sm", className)} | ||||
|     delay={delay} | ||||
|     interactive={interactive} | ||||
|     animation={animation} | ||||
|     {...otherProps} | ||||
|   > | ||||
|     <div>{children}</div> | ||||
|   </Tippy> | ||||
| ); | ||||
|  | ||||
| @ -20,36 +20,35 @@ interface Props { | ||||
|   langui: AppStaticProps["langui"]; | ||||
| } | ||||
| 
 | ||||
| export function ChronologyItemComponent(props: Props): JSX.Element { | ||||
|   const { langui } = props; | ||||
| 
 | ||||
|   if (props.item.attributes) { | ||||
| export const ChronologyItemComponent = ({ | ||||
|   langui, | ||||
|   item, | ||||
|   displayYear, | ||||
| }: Props): JSX.Element => { | ||||
|   if (item.attributes) { | ||||
|     return ( | ||||
|       <div | ||||
|         className="grid grid-cols-[4em] grid-rows-[auto_1fr] place-content-start | ||||
|         rounded-2xl py-4 px-8 target:my-4 target:bg-mid target:py-8" | ||||
|         id={generateAnchor( | ||||
|           props.item.attributes.year, | ||||
|           props.item.attributes.month, | ||||
|           props.item.attributes.day | ||||
|           item.attributes.year, | ||||
|           item.attributes.month, | ||||
|           item.attributes.day | ||||
|         )} | ||||
|       > | ||||
|         {props.displayYear && ( | ||||
|         {displayYear && ( | ||||
|           <p className="mt-[-.2em] text-lg font-bold"> | ||||
|             {generateYear( | ||||
|               props.item.attributes.displayed_date, | ||||
|               props.item.attributes.year | ||||
|             )} | ||||
|             {generateYear(item.attributes.displayed_date, item.attributes.year)} | ||||
|           </p> | ||||
|         )} | ||||
| 
 | ||||
|         <p className="col-start-1 text-sm text-dark"> | ||||
|           {generateDate(props.item.attributes.month, props.item.attributes.day)} | ||||
|           {generateDate(item.attributes.month, item.attributes.day)} | ||||
|         </p> | ||||
| 
 | ||||
|         <div className="col-start-2 row-span-2 row-start-1 grid gap-4"> | ||||
|           {props.item.attributes.events && | ||||
|             filterHasAttributes(props.item.attributes.events, [ | ||||
|           {item.attributes.events && | ||||
|             filterHasAttributes(item.attributes.events, [ | ||||
|               "id", | ||||
|               "translations", | ||||
|             ]).map((event) => ( | ||||
| @ -123,31 +122,29 @@ export function ChronologyItemComponent(props: Props): JSX.Element { | ||||
|   } | ||||
| 
 | ||||
|   return <></>; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| function generateAnchor( | ||||
| const generateAnchor = ( | ||||
|   year: number | undefined, | ||||
|   month: number | null | undefined, | ||||
|   day: number | null | undefined | ||||
| ): string { | ||||
| ): string => { | ||||
|   let result = ""; | ||||
|   if (year) result += year; | ||||
|   if (month) result += `- ${month.toString().padStart(2, "0")}`; | ||||
|   if (day) result += `- ${day.toString().padStart(2, "0")}`; | ||||
|   return result; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| function generateYear( | ||||
| const generateYear = ( | ||||
|   displayed_date: string | null | undefined, | ||||
|   year: number | undefined | ||||
| ): string { | ||||
|   return displayed_date ?? year?.toString() ?? ""; | ||||
| } | ||||
| ): string => displayed_date ?? year?.toString() ?? ""; | ||||
| 
 | ||||
| function generateDate( | ||||
| const generateDate = ( | ||||
|   month: number | null | undefined, | ||||
|   day: number | null | undefined | ||||
| ): string { | ||||
| ): string => { | ||||
|   const lut = [ | ||||
|     "Jan", | ||||
|     "Feb", | ||||
| @ -172,4 +169,4 @@ function generateDate( | ||||
|   } | ||||
| 
 | ||||
|   return result; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -10,22 +10,22 @@ interface Props { | ||||
|   langui: AppStaticProps["langui"]; | ||||
| } | ||||
| 
 | ||||
| export function ChronologyYearComponent(props: Props): JSX.Element { | ||||
|   const { langui } = props; | ||||
| 
 | ||||
|   return ( | ||||
|     <div | ||||
|       className="rounded-2xl target:my-4 target:bg-mid target:py-4" | ||||
|       id={props.items.length > 1 ? props.year.toString() : undefined} | ||||
|     > | ||||
|       {props.items.map((item, index) => ( | ||||
|         <ChronologyItemComponent | ||||
|           key={index} | ||||
|           item={item} | ||||
|           displayYear={index === 0} | ||||
|           langui={langui} | ||||
|         /> | ||||
|       ))} | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| export const ChronologyYearComponent = ({ | ||||
|   langui, | ||||
|   year, | ||||
|   items, | ||||
| }: Props): JSX.Element => ( | ||||
|   <div | ||||
|     className="rounded-2xl target:my-4 target:bg-mid target:py-4" | ||||
|     id={items.length > 1 ? year.toString() : undefined} | ||||
|   > | ||||
|     {items.map((item, index) => ( | ||||
|       <ChronologyItemComponent | ||||
|         key={index} | ||||
|         item={item} | ||||
|         displayYear={index === 0} | ||||
|         langui={langui} | ||||
|       /> | ||||
|     ))} | ||||
|   </div> | ||||
| ); | ||||
|  | ||||
| @ -17,9 +17,13 @@ interface Props { | ||||
|   index: number; | ||||
| } | ||||
| 
 | ||||
| export default function DefinitionCard(props: Props): JSX.Element { | ||||
|   const { source, translations = [], languages, langui, index } = props; | ||||
| 
 | ||||
| const DefinitionCard = ({ | ||||
|   source, | ||||
|   translations = [], | ||||
|   languages, | ||||
|   langui, | ||||
|   index, | ||||
| }: Props): JSX.Element => { | ||||
|   const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = | ||||
|     useSmartLanguage({ | ||||
|       items: translations, | ||||
| @ -33,8 +37,7 @@ export default function DefinitionCard(props: Props): JSX.Element { | ||||
|   return ( | ||||
|     <> | ||||
|       <div className="flex place-items-center gap-2"> | ||||
|         {/* TODO: Langui */} | ||||
|         <p className="font-headers text-lg">{`Definition ${index}`}</p> | ||||
|         <p className="font-headers text-lg">{`${langui.definition} ${index}`}</p> | ||||
|         {selectedTranslation?.status && ( | ||||
|           <ToolTip | ||||
|             content={getStatusDescription(selectedTranslation.status, langui)} | ||||
| @ -52,4 +55,5 @@ export default function DefinitionCard(props: Props): JSX.Element { | ||||
|       <p>{selectedTranslation?.definition}</p> | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
| export default DefinitionCard; | ||||
|  | ||||
| @ -106,15 +106,13 @@ const initialState: RequiredNonNullable<AppLayoutState> = { | ||||
| 
 | ||||
| const AppContext = React.createContext<AppLayoutState>(initialState); | ||||
| 
 | ||||
| export function useAppLayout(): AppLayoutState { | ||||
|   return useContext(AppContext); | ||||
| } | ||||
| export const useAppLayout = (): AppLayoutState => useContext(AppContext); | ||||
| 
 | ||||
| interface Props { | ||||
|   children: ReactNode; | ||||
| } | ||||
| 
 | ||||
| export function AppContextProvider(props: Props): JSX.Element { | ||||
| export const AppContextProvider = (props: Props): JSX.Element => { | ||||
|   const [subPanelOpen, setSubPanelOpen] = useStateWithLocalStorage( | ||||
|     "subPanelOpen", | ||||
|     initialState.subPanelOpen | ||||
| @ -176,43 +174,43 @@ export function AppContextProvider(props: Props): JSX.Element { | ||||
|       initialState.libraryItemUserStatus | ||||
|     ); | ||||
| 
 | ||||
|   function toggleSubPanelOpen() { | ||||
|   const toggleSubPanelOpen = () => { | ||||
|     setSubPanelOpen((current) => (isDefined(current) ? !current : current)); | ||||
|   } | ||||
|   }; | ||||
| 
 | ||||
|   function toggleConfigPanelOpen() { | ||||
|   const toggleConfigPanelOpen = () => { | ||||
|     setConfigPanelOpen((current) => (isDefined(current) ? !current : current)); | ||||
|   } | ||||
|   }; | ||||
| 
 | ||||
|   function toggleSearchPanelOpen() { | ||||
|   const toggleSearchPanelOpen = () => { | ||||
|     setSearchPanelOpen((current) => (isDefined(current) ? !current : current)); | ||||
|   } | ||||
|   }; | ||||
| 
 | ||||
|   function toggleMainPanelReduced() { | ||||
|   const toggleMainPanelReduced = () => { | ||||
|     setMainPanelReduced((current) => (isDefined(current) ? !current : current)); | ||||
|   } | ||||
|   }; | ||||
| 
 | ||||
|   function toggleMainPanelOpen() { | ||||
|   const toggleMainPanelOpen = () => { | ||||
|     setMainPanelOpen((current) => (isDefined(current) ? !current : current)); | ||||
|   } | ||||
|   }; | ||||
| 
 | ||||
|   function toggleDarkMode() { | ||||
|   const toggleDarkMode = () => { | ||||
|     setDarkMode((current) => (isDefined(current) ? !current : current)); | ||||
|   } | ||||
|   }; | ||||
| 
 | ||||
|   function toggleMenuGestures() { | ||||
|   const toggleMenuGestures = () => { | ||||
|     setMenuGestures((current) => !current); | ||||
|   } | ||||
|   }; | ||||
| 
 | ||||
|   function toggleSelectedThemeMode() { | ||||
|   const toggleSelectedThemeMode = () => { | ||||
|     setSelectedThemeMode((current) => | ||||
|       isDefined(current) ? !current : current | ||||
|     ); | ||||
|   } | ||||
|   }; | ||||
| 
 | ||||
|   function toggleDyslexic() { | ||||
|   const toggleDyslexic = () => { | ||||
|     setDyslexic((current) => (isDefined(current) ? !current : current)); | ||||
|   } | ||||
|   }; | ||||
| 
 | ||||
|   return ( | ||||
|     <AppContext.Provider | ||||
| @ -259,4 +257,4 @@ export function AppContextProvider(props: Props): JSX.Element { | ||||
|       {props.children} | ||||
|     </AppContext.Provider> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -17,9 +17,9 @@ export type AppStaticProps = { | ||||
|   languages: NonNullable<GetLanguagesQuery["languages"]>["data"]; | ||||
| }; | ||||
| 
 | ||||
| export async function getAppStaticProps( | ||||
| export const getAppStaticProps = async ( | ||||
|   context: GetStaticPropsContext | ||||
| ): Promise<AppStaticProps> { | ||||
| ): Promise<AppStaticProps> => { | ||||
|   const sdk = getReadySdk(); | ||||
|   const languages = (await sdk.getLanguages()).languages; | ||||
| 
 | ||||
| @ -53,4 +53,4 @@ export async function getAppStaticProps( | ||||
|   }; | ||||
| 
 | ||||
|   return appStaticProps; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| import { PostWithTranslations } from "helpers/types"; | ||||
| import { GetStaticPropsContext } from "next"; | ||||
| import { GetStaticProps, GetStaticPropsContext } from "next"; | ||||
| import { AppStaticProps, getAppStaticProps } from "./getAppStaticProps"; | ||||
| import { getReadySdk } from "./sdk"; | ||||
| 
 | ||||
| @ -7,12 +7,8 @@ export interface PostStaticProps extends AppStaticProps { | ||||
|   post: PostWithTranslations; | ||||
| } | ||||
| 
 | ||||
| export function getPostStaticProps( | ||||
|   slug: string | ||||
| ): ( | ||||
|   context: GetStaticPropsContext | ||||
| ) => Promise<{ notFound: boolean } | { props: PostStaticProps }> { | ||||
|   return async (context: GetStaticPropsContext) => { | ||||
| export const getPostStaticProps = (slug: string): GetStaticProps => { | ||||
|   return async (context) => { | ||||
|     const sdk = getReadySdk(); | ||||
|     const post = await sdk.getPost({ | ||||
|       slug: slug, | ||||
| @ -29,4 +25,4 @@ export function getPostStaticProps( | ||||
|     } | ||||
|     return { notFound: true }; | ||||
|   }; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -154,6 +154,11 @@ query getWebsiteInterface($language_code: String) { | ||||
|         only_display_items_i_want | ||||
|         only_display_unmarked_items | ||||
|         display_all_items | ||||
|         table_of_contents | ||||
|         definition | ||||
|         no_results_message | ||||
|         all | ||||
|         special_pages | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @ -1,9 +1,9 @@ | ||||
| import { GraphQLClient } from "graphql-request"; | ||||
| import { getSdk } from "graphql/generated"; | ||||
| 
 | ||||
| export function getReadySdk() { | ||||
| export const getReadySdk = () => { | ||||
|   const client = new GraphQLClient(process.env.URL_GRAPHQL ?? "", { | ||||
|     headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}` }, | ||||
|   }); | ||||
|   return getSdk(client); | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -1,11 +1,11 @@ | ||||
| export function cIf( | ||||
| export const cIf = ( | ||||
|   condition: boolean | null | undefined | string, | ||||
|   ifTrueCss: string, | ||||
|   ifFalseCss?: string | ||||
| ) { | ||||
| ) => { | ||||
|   return condition ? ifTrueCss : ifFalseCss ?? ""; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export function cJoin(...args: (string | undefined)[]): string { | ||||
| export const cJoin = (...args: (string | undefined)[]): string => { | ||||
|   return args.filter((elem) => elem).join(" "); | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -2,16 +2,21 @@ export interface Wrapper { | ||||
|   children: React.ReactNode; | ||||
| } | ||||
| 
 | ||||
| export function ConditionalWrapper<T>(props: { | ||||
| interface Props<T> { | ||||
|   isWrapping: boolean; | ||||
|   children: React.ReactNode; | ||||
|   wrapper: (wrapperProps: T & Wrapper) => JSX.Element; | ||||
|   wrapperProps: T; | ||||
| }): JSX.Element { | ||||
|   const { isWrapping, children, wrapper: Wrapper, wrapperProps } = props; | ||||
|   return isWrapping ? ( | ||||
| } | ||||
| 
 | ||||
| export const ConditionalWrapper = <T,>({ | ||||
|   isWrapping, | ||||
|   children, | ||||
|   wrapper: Wrapper, | ||||
|   wrapperProps, | ||||
| }: Props<T>): JSX.Element => | ||||
|   isWrapping ? ( | ||||
|     <Wrapper {...wrapperProps}>{children}</Wrapper> | ||||
|   ) : ( | ||||
|     <>{children}</> | ||||
|   ); | ||||
| } | ||||
|  | ||||
| @ -10,9 +10,12 @@ interface Description { | ||||
|   categories?: Content["categories"]; | ||||
| } | ||||
| 
 | ||||
| export function getDescription(props: Description): string { | ||||
|   const { langui, description: text, type, categories } = props; | ||||
| 
 | ||||
| export const getDescription = ({ | ||||
|   langui, | ||||
|   description: text, | ||||
|   type, | ||||
|   categories, | ||||
| }: Description): string => { | ||||
|   let description = ""; | ||||
| 
 | ||||
|   // TEXT
 | ||||
| @ -44,15 +47,15 @@ export function getDescription(props: Description): string { | ||||
|   } | ||||
| 
 | ||||
|   return description; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| function prettyMarkdown(markdown: string): string { | ||||
| const prettyMarkdown = (markdown: string): string => { | ||||
|   return markdown.replace(/[*]/gu, "").replace(/[_]/gu, ""); | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| function prettyChip(items: (string | undefined)[]): string { | ||||
| const prettyChip = (items: (string | undefined)[]): string => { | ||||
|   return items | ||||
|     .filter((item) => isDefined(item)) | ||||
|     .map((item) => `(${item})`) | ||||
|     .join(" "); | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -2,7 +2,7 @@ import { DatePickerFragment, PricePickerFragment } from "graphql/generated"; | ||||
| import { AppStaticProps } from "../graphql/getAppStaticProps"; | ||||
| import { convertPrice } from "./numbers"; | ||||
| 
 | ||||
| export function prettyDate(datePicker: DatePickerFragment): string { | ||||
| export const prettyDate = (datePicker: DatePickerFragment): string => { | ||||
|   let result = ""; | ||||
|   if (datePicker.year) result += datePicker.year.toString(); | ||||
|   if (datePicker.month) | ||||
| @ -10,13 +10,13 @@ export function prettyDate(datePicker: DatePickerFragment): string { | ||||
|   if (datePicker.day) | ||||
|     result += `/${datePicker.day.toString().padStart(2, "0")}`; | ||||
|   return result; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export function prettyPrice( | ||||
| export const prettyPrice = ( | ||||
|   pricePicker: PricePickerFragment, | ||||
|   currencies: AppStaticProps["currencies"], | ||||
|   targetCurrencyCode?: string | ||||
| ): string { | ||||
| ): string => { | ||||
|   if (!targetCurrencyCode) return ""; | ||||
|   let result = ""; | ||||
|   currencies.map((currency) => { | ||||
| @ -31,9 +31,9 @@ export function prettyPrice( | ||||
|     } | ||||
|   }); | ||||
|   return result; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export function prettySlug(slug?: string, parentSlug?: string): string { | ||||
| export const prettySlug = (slug?: string, parentSlug?: string): string => { | ||||
|   if (slug) { | ||||
|     if (parentSlug && slug.startsWith(parentSlug)) | ||||
|       slug = slug.substring(parentSlug.length + 1); | ||||
| @ -42,24 +42,24 @@ export function prettySlug(slug?: string, parentSlug?: string): string { | ||||
|     return capitalizeString(slug); | ||||
|   } | ||||
|   return ""; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export function prettyinlineTitle( | ||||
| export const prettyinlineTitle = ( | ||||
|   pretitle: string | undefined | null, | ||||
|   title: string | undefined | null, | ||||
|   subtitle: string | undefined | null | ||||
| ): string { | ||||
| ): string => { | ||||
|   let result = ""; | ||||
|   if (pretitle) result += `${pretitle}: `; | ||||
|   result += title; | ||||
|   if (subtitle) result += ` - ${subtitle}`; | ||||
|   return result; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export function prettyItemType( | ||||
| export const prettyItemType = ( | ||||
|   metadata: any, | ||||
|   langui: AppStaticProps["langui"] | ||||
| ): string | undefined | null { | ||||
| ): string | undefined | null => { | ||||
|   switch (metadata.__typename) { | ||||
|     case "ComponentMetadataAudio": | ||||
|       return langui.audio; | ||||
| @ -76,9 +76,9 @@ export function prettyItemType( | ||||
|     default: | ||||
|       return ""; | ||||
|   } | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export function prettyItemSubType( | ||||
| export const prettyItemSubType = ( | ||||
|   metadata: | ||||
|     | { | ||||
|         __typename: "ComponentMetadataAudio"; | ||||
| @ -156,7 +156,7 @@ export function prettyItemSubType( | ||||
|       } | ||||
|     | { __typename: "Error" } | ||||
|     | null | ||||
| ): string { | ||||
| ): string => { | ||||
|   if (metadata) { | ||||
|     switch (metadata.__typename) { | ||||
|       case "ComponentMetadataAudio": | ||||
| @ -195,9 +195,9 @@ export function prettyItemSubType( | ||||
|   } | ||||
|   return ""; | ||||
|   /* eslint-enable @typescript-eslint/no-explicit-any */ | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export function prettyShortenNumber(number: number): string { | ||||
| export const prettyShortenNumber = (number: number): string => { | ||||
|   if (number > 1000000) { | ||||
|     return number.toLocaleString(undefined, { | ||||
|       maximumSignificantDigits: 3, | ||||
| @ -210,9 +210,9 @@ export function prettyShortenNumber(number: number): string { | ||||
|     ); | ||||
|   } | ||||
|   return number.toLocaleString(); | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export function prettyDuration(seconds: number): string { | ||||
| export const prettyDuration = (seconds: number): string => { | ||||
|   let hours = 0; | ||||
|   let minutes = 0; | ||||
|   while (seconds > 60) { | ||||
| @ -228,36 +228,36 @@ export function prettyDuration(seconds: number): string { | ||||
|   result += minutes.toString().padStart(2, "0") + ":"; | ||||
|   result += seconds.toString().padStart(2, "0"); | ||||
|   return result; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export function prettyLanguage( | ||||
| export const prettyLanguage = ( | ||||
|   code: string, | ||||
|   languages: AppStaticProps["languages"] | ||||
| ): string { | ||||
| ): string => { | ||||
|   let result = code; | ||||
|   languages.forEach((language) => { | ||||
|     if (language?.attributes?.code === code) | ||||
|       result = language.attributes.localized_name; | ||||
|   }); | ||||
|   return result; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export function prettyURL(url: string): string { | ||||
| export const prettyURL = (url: string): string => { | ||||
|   let domain = new URL(url); | ||||
|   return domain.hostname.replace("www.", ""); | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| function capitalizeString(string: string): string { | ||||
|   function capitalizeWord(word: string): string { | ||||
| const capitalizeString = (string: string): string => { | ||||
|   const capitalizeWord = (word: string): string => { | ||||
|     return word.charAt(0).toUpperCase() + word.substring(1); | ||||
|   } | ||||
|   }; | ||||
| 
 | ||||
|   let words = string.split(" "); | ||||
|   words = words.map((word) => capitalizeWord(word)); | ||||
|   return words.join(" "); | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export function slugify(string: string | undefined): string { | ||||
| export const slugify = (string: string | undefined): string => { | ||||
|   if (!string) { | ||||
|     return ""; | ||||
|   } | ||||
| @ -275,4 +275,4 @@ export function slugify(string: string | undefined): string { | ||||
|     .replace(/[^a-z0-9- ]/gu, "") | ||||
|     .trim() | ||||
|     .replace(/ /gu, "-"); | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -14,7 +14,7 @@ interface OgImage { | ||||
|   alt: string; | ||||
| } | ||||
| 
 | ||||
| export function getAssetFilename(path: string): string { | ||||
| export const getAssetFilename = (path: string): string => { | ||||
|   let result = path.split("/"); | ||||
|   result = result[result.length - 1].split("."); | ||||
|   result = result | ||||
| @ -22,9 +22,9 @@ export function getAssetFilename(path: string): string { | ||||
|     .join(".") | ||||
|     .split("_"); | ||||
|   return result[0]; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export function getAssetURL(url: string, quality: ImageQuality): string { | ||||
| export const getAssetURL = (url: string, quality: ImageQuality): string => { | ||||
|   let newUrl = url; | ||||
|   newUrl = newUrl.replace(/^\/uploads/u, `/${quality}`); | ||||
|   newUrl = newUrl.replace(/.jpg$/u, ".webp"); | ||||
| @ -32,26 +32,26 @@ export function getAssetURL(url: string, quality: ImageQuality): string { | ||||
|   newUrl = newUrl.replace(/.png$/u, ".webp"); | ||||
|   if (quality === ImageQuality.Og) newUrl = newUrl.replace(/.webp$/u, ".jpg"); | ||||
|   return process.env.NEXT_PUBLIC_URL_IMG + newUrl; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| function getImgSizesByMaxSize( | ||||
| const getImgSizesByMaxSize = ( | ||||
|   width: number, | ||||
|   height: number, | ||||
|   maxSize: number | ||||
| ): { width: number; height: number } { | ||||
| ): { width: number; height: number } => { | ||||
|   if (width > height) { | ||||
|     if (width < maxSize) return { width: width, height: height }; | ||||
|     return { width: maxSize, height: (height / width) * maxSize }; | ||||
|   } | ||||
|   if (height < maxSize) return { width: width, height: height }; | ||||
|   return { width: (width / height) * maxSize, height: maxSize }; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export function getImgSizesByQuality( | ||||
| export const getImgSizesByQuality = ( | ||||
|   width: number, | ||||
|   height: number, | ||||
|   quality: ImageQuality | ||||
| ): { width: number; height: number } { | ||||
| ): { width: number; height: number } => { | ||||
|   switch (quality) { | ||||
|     case ImageQuality.Og: | ||||
|       return getImgSizesByMaxSize(width, height, 512); | ||||
| @ -64,12 +64,12 @@ export function getImgSizesByQuality( | ||||
|     default: | ||||
|       return { width: 0, height: 0 }; | ||||
|   } | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export function getOgImage( | ||||
| export const getOgImage = ( | ||||
|   quality: ImageQuality, | ||||
|   image: UploadImageFragment | ||||
| ): OgImage { | ||||
| ): OgImage => { | ||||
|   const imgSize = getImgSizesByQuality( | ||||
|     image.width ?? 0, | ||||
|     image.height ?? 0, | ||||
| @ -81,4 +81,4 @@ export function getOgImage( | ||||
|     height: imgSize.height, | ||||
|     alt: image.alternativeText || "", | ||||
|   }; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -10,11 +10,11 @@ import LibraryPage from "../pages/library/index"; | ||||
| type Items = Parameters<typeof LibraryPage>[0]["items"]; | ||||
| type GroupLibraryItems = Map<string, Items>; | ||||
| 
 | ||||
| export function getGroups( | ||||
| export const getGroups = ( | ||||
|   langui: AppStaticProps["langui"], | ||||
|   groupByType: number, | ||||
|   items: Items | ||||
| ): GroupLibraryItems { | ||||
| ): GroupLibraryItems => { | ||||
|   const groups: GroupLibraryItems = new Map(); | ||||
| 
 | ||||
|   switch (groupByType) { | ||||
| @ -145,9 +145,9 @@ export function getGroups( | ||||
|     } | ||||
|   } | ||||
|   return mapRemoveEmptyValues(groups); | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export function filterItems( | ||||
| export const filterItems = ( | ||||
|   appLayout: AppLayoutState, | ||||
|   items: Items, | ||||
|   searchName: string, | ||||
| @ -155,7 +155,7 @@ export function filterItems( | ||||
|   showPrimaryItems: boolean, | ||||
|   showSecondaryItems: boolean, | ||||
|   filterUserStatus: LibraryItemUserStatus | undefined | ||||
| ): Items { | ||||
| ): Items => { | ||||
|   return items.filter((item) => { | ||||
|     if (!showSubitems && !item.attributes?.root_item) return false; | ||||
|     if (showSubitems && isUntangibleGroupItem(item.attributes?.metadata?.[0])) { | ||||
| @ -194,23 +194,23 @@ export function filterItems( | ||||
| 
 | ||||
|     return true; | ||||
|   }); | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| // TODO: Properly type this shit
 | ||||
| export function isUntangibleGroupItem(metadata: any) { | ||||
| export const isUntangibleGroupItem = (metadata: any) => { | ||||
|   return ( | ||||
|     metadata && | ||||
|     metadata.__typename === "ComponentMetadataGroup" && | ||||
|     (metadata.subtype?.data?.attributes?.slug === "variant-set" || | ||||
|       metadata.subtype?.data?.attributes?.slug === "relation-set") | ||||
|   ); | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export function sortBy( | ||||
| export const sortBy = ( | ||||
|   orderByType: number, | ||||
|   items: Items, | ||||
|   currencies: AppStaticProps["currencies"] | ||||
| ) { | ||||
| ) => { | ||||
|   switch (orderByType) { | ||||
|     case 0: | ||||
|       return items.sort((a, b) => { | ||||
| @ -249,4 +249,4 @@ export function sortBy( | ||||
|     default: | ||||
|       return items; | ||||
|   } | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -1,9 +1,9 @@ | ||||
| import { GetCurrenciesQuery, PricePickerFragment } from "graphql/generated"; | ||||
| 
 | ||||
| export function convertPrice( | ||||
| export const convertPrice = ( | ||||
|   pricePicker: PricePickerFragment, | ||||
|   targetCurrency: NonNullable<GetCurrenciesQuery["currencies"]>["data"][number] | ||||
| ): number { | ||||
| ): number => { | ||||
|   if ( | ||||
|     pricePicker.amount && | ||||
|     pricePicker.currency?.data?.attributes && | ||||
| @ -14,17 +14,17 @@ export function convertPrice( | ||||
|       targetCurrency.attributes.rate_to_usd | ||||
|     ); | ||||
|   return 0; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export function convertMmToInch(mm: number | null | undefined): string { | ||||
| export const convertMmToInch = (mm: number | null | undefined): string => { | ||||
|   return mm ? (mm * 0.03937008).toPrecision(3) : ""; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export function randomInt(min: number, max: number) { | ||||
| export const randomInt = (min: number, max: number) => { | ||||
|   return Math.floor(Math.random() * (max - min)) + min; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export function isInteger(value: string): boolean { | ||||
| export const isInteger = (value: string): boolean => { | ||||
|   // eslint-disable-next-line require-unicode-regexp
 | ||||
|   return /^[+-]?[0-9]+$/.test(value); | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -18,7 +18,7 @@ type SortContentProps = | ||||
|       >["data"][number]["attributes"] | ||||
|     >["contents"]; | ||||
| 
 | ||||
| export function sortContent(contents: SortContentProps) { | ||||
| export const sortContent = (contents: SortContentProps) => { | ||||
|   contents?.data.sort((a, b) => { | ||||
|     if ( | ||||
|       a.attributes?.range[0]?.__typename === "ComponentRangePageRange" && | ||||
| @ -31,12 +31,12 @@ export function sortContent(contents: SortContentProps) { | ||||
|     } | ||||
|     return 0; | ||||
|   }); | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export function getStatusDescription( | ||||
| export const getStatusDescription = ( | ||||
|   status: string, | ||||
|   langui: AppStaticProps["langui"] | ||||
| ): string | null | undefined { | ||||
| ): string | null | undefined => { | ||||
|   switch (status) { | ||||
|     case Enum_Componentsetstextset_Status.Incomplete: | ||||
|       return langui.status_incomplete; | ||||
| @ -53,45 +53,44 @@ export function getStatusDescription( | ||||
|     default: | ||||
|       return ""; | ||||
|   } | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export function isDefined<T>(t: T): t is NonNullable<T> { | ||||
|   return t !== null && t !== undefined; | ||||
| } | ||||
| export const isDefined = <T>(t: T): t is NonNullable<T> => | ||||
|   t !== null && t !== undefined; | ||||
| 
 | ||||
| export function isUndefined<T>(t: T | undefined | null): t is undefined | null { | ||||
|   return t === null || t === undefined; | ||||
| } | ||||
| export const isUndefined = <T>( | ||||
|   t: T | undefined | null | ||||
| ): t is undefined | null => t === null || t === undefined; | ||||
| 
 | ||||
| export function isDefinedAndNotEmpty( | ||||
| export const isDefinedAndNotEmpty = ( | ||||
|   string: string | undefined | null | ||||
| ): string is string { | ||||
|   return isDefined(string) && string.length > 0; | ||||
| } | ||||
| ): string is string => isDefined(string) && string.length > 0; | ||||
| 
 | ||||
| export function filterDefined<T>(t: T[] | undefined | null): NonNullable<T>[] { | ||||
|   if (isUndefined(t)) return []; | ||||
|   return t.filter((item) => isDefined(item)) as NonNullable<T>[]; | ||||
| } | ||||
| export const filterDefined = <T>(t: T[] | undefined | null): NonNullable<T>[] => | ||||
|   isUndefined(t) | ||||
|     ? [] | ||||
|     : (t.filter((item) => isDefined(item)) as NonNullable<T>[]); | ||||
| 
 | ||||
| export function filterHasAttributes<T, P extends keyof NonNullable<T>>( | ||||
| export const filterHasAttributes = <T, P extends keyof NonNullable<T>>( | ||||
|   t: T[] | undefined | null, | ||||
|   attributes?: P[] | ||||
| ): SelectiveRequiredNonNullable<NonNullable<T>, P>[] { | ||||
|   if (isUndefined(t)) return []; | ||||
|   return t.filter((item) => { | ||||
|     if (isDefined(item)) { | ||||
|       const attributesToCheck = attributes ?? (Object.keys(item) as P[]); | ||||
|       return attributesToCheck.every((attribute) => isDefined(item[attribute])); | ||||
|     } | ||||
|     return false; | ||||
|   }) as unknown as SelectiveRequiredNonNullable<NonNullable<T>, P>[]; | ||||
| } | ||||
| ): SelectiveRequiredNonNullable<NonNullable<T>, P>[] => | ||||
|   isUndefined(t) | ||||
|     ? [] | ||||
|     : (t.filter((item) => { | ||||
|         if (isDefined(item)) { | ||||
|           const attributesToCheck = attributes ?? (Object.keys(item) as P[]); | ||||
|           return attributesToCheck.every((attribute) => | ||||
|             isDefined(item[attribute]) | ||||
|           ); | ||||
|         } | ||||
|         return false; | ||||
|       }) as unknown as SelectiveRequiredNonNullable<NonNullable<T>, P>[]); | ||||
| 
 | ||||
| export function iterateMap<K, V, U>( | ||||
| export const iterateMap = <K, V, U>( | ||||
|   map: Map<K, V>, | ||||
|   callbackfn: (key: K, value: V, index: number) => U | ||||
| ): U[] { | ||||
| ): U[] => { | ||||
|   const result: U[] = []; | ||||
|   let index = 0; | ||||
|   for (const [key, value] of map.entries()) { | ||||
| @ -99,21 +98,18 @@ export function iterateMap<K, V, U>( | ||||
|     index += 1; | ||||
|   } | ||||
|   return result; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export function mapMoveEntry<K, V>( | ||||
| export const mapMoveEntry = <K, V>( | ||||
|   map: Map<K, V>, | ||||
|   sourceIndex: number, | ||||
|   targetIndex: number | ||||
| ) { | ||||
|   return new Map(arrayMove([...map], sourceIndex, targetIndex)); | ||||
| } | ||||
| ) => new Map(arrayMove([...map], sourceIndex, targetIndex)); | ||||
| 
 | ||||
| function arrayMove<T>(arr: T[], sourceIndex: number, targetIndex: number) { | ||||
| const arrayMove = <T>(arr: T[], sourceIndex: number, targetIndex: number) => { | ||||
|   arr.splice(targetIndex, 0, arr.splice(sourceIndex, 1)[0]); | ||||
|   return arr; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export function mapRemoveEmptyValues<K, V>(groups: Map<K, V[]>): Map<K, V[]> { | ||||
|   return new Map([...groups].filter(([_, items]) => items.length > 0)); | ||||
| } | ||||
| export const mapRemoveEmptyValues = <K, V>(groups: Map<K, V[]>): Map<K, V[]> => | ||||
|   new Map([...groups].filter(([_, items]) => items.length > 0)); | ||||
|  | ||||
| @ -1,7 +1,5 @@ | ||||
| export function getVideoThumbnailURL(uid: string): string { | ||||
|   return `${process.env.NEXT_PUBLIC_URL_WATCH}/videos/${uid}.webp`; | ||||
| } | ||||
| export const getVideoThumbnailURL = (uid: string): string => | ||||
|   `${process.env.NEXT_PUBLIC_URL_WATCH}/videos/${uid}.webp`; | ||||
| 
 | ||||
| export function getVideoFile(uid: string): string { | ||||
|   return `${process.env.NEXT_PUBLIC_URL_WATCH}/videos/${uid}.mp4`; | ||||
| } | ||||
| export const getVideoFile = (uid: string): string => | ||||
|   `${process.env.NEXT_PUBLIC_URL_WATCH}/videos/${uid}.mp4`; | ||||
|  | ||||
| @ -2,7 +2,7 @@ import { useEffect } from "react"; | ||||
| import { usePrefersDarkMode } from "./useMediaQuery"; | ||||
| import { useStateWithLocalStorage } from "./useStateWithLocalStorage"; | ||||
| 
 | ||||
| export function useDarkMode( | ||||
| export const useDarkMode = ( | ||||
|   key: string, | ||||
|   initialValue: boolean | undefined | ||||
| ): [ | ||||
| @ -10,11 +10,9 @@ export function useDarkMode( | ||||
|   boolean | undefined, | ||||
|   React.Dispatch<React.SetStateAction<boolean | undefined>>, | ||||
|   React.Dispatch<React.SetStateAction<boolean | undefined>> | ||||
| ] { | ||||
| ] => { | ||||
|   const [darkMode, setDarkMode] = useStateWithLocalStorage(key, initialValue); | ||||
| 
 | ||||
|   const prefersDarkMode = usePrefersDarkMode(); | ||||
| 
 | ||||
|   const [selectedThemeMode, setSelectedThemeMode] = useStateWithLocalStorage( | ||||
|     "selectedThemeMode", | ||||
|     false | ||||
| @ -25,4 +23,4 @@ export function useDarkMode( | ||||
|   }, [selectedThemeMode, prefersDarkMode, setDarkMode]); | ||||
| 
 | ||||
|   return [darkMode, selectedThemeMode, setDarkMode, setSelectedThemeMode]; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -1,10 +1,10 @@ | ||||
| import { LightBox } from "components/LightBox"; | ||||
| import { useState } from "react"; | ||||
| 
 | ||||
| export function useLightBox(): [ | ||||
| export const useLightBox = (): [ | ||||
|   (images: string[], index?: number) => void, | ||||
|   () => JSX.Element | ||||
| ] { | ||||
| ] => { | ||||
|   const [lightboxOpen, setLightboxOpen] = useState(false); | ||||
|   const [lightboxImages, setLightboxImages] = useState([""]); | ||||
|   const [lightboxIndex, setLightboxIndex] = useState(0); | ||||
| @ -25,4 +25,4 @@ export function useLightBox(): [ | ||||
|       /> | ||||
|     ), | ||||
|   ]; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -1,21 +1,21 @@ | ||||
| import { useEffect, useState } from "react"; | ||||
| import { breaks } from "../../design.config"; | ||||
| 
 | ||||
| function useMediaQuery(query: string): boolean { | ||||
|   function getMatches(query: string): boolean { | ||||
| const useMediaQuery = (query: string): boolean => { | ||||
|   const getMatches = (query: string): boolean => { | ||||
|     // Prevents SSR issues
 | ||||
|     if (typeof window !== "undefined") { | ||||
|       return window.matchMedia(query).matches; | ||||
|     } | ||||
|     return false; | ||||
|   } | ||||
|   }; | ||||
| 
 | ||||
|   const [matches, setMatches] = useState<boolean>(getMatches(query)); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     function handleChange() { | ||||
|     const handleChange = () => { | ||||
|       setMatches(getMatches(query)); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     const matchMedia = window.matchMedia(query); | ||||
| 
 | ||||
| @ -31,25 +31,16 @@ function useMediaQuery(query: string): boolean { | ||||
|   }, [query]); | ||||
| 
 | ||||
|   return matches; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| // ts-unused-exports:disable-next-line
 | ||||
| export function useMediaThin() { | ||||
|   return useMediaQuery(breaks.thin.raw); | ||||
| } | ||||
| export const useMediaThin = () => useMediaQuery(breaks.thin.raw); | ||||
| 
 | ||||
| export function useMediaMobile() { | ||||
|   return useMediaQuery(breaks.mobile.raw); | ||||
| } | ||||
| export const useMediaMobile = () => useMediaQuery(breaks.mobile.raw); | ||||
| 
 | ||||
| export function useMediaDesktop() { | ||||
|   return useMediaQuery(breaks.desktop.raw); | ||||
| } | ||||
| export const useMediaDesktop = () => useMediaQuery(breaks.desktop.raw); | ||||
| 
 | ||||
| export function useMediaHoverable() { | ||||
|   return useMediaQuery("(hover: hover)"); | ||||
| } | ||||
| export const useMediaHoverable = () => useMediaQuery("(hover: hover)"); | ||||
| 
 | ||||
| export function usePrefersDarkMode() { | ||||
|   return useMediaQuery("(prefers-color-scheme: dark)"); | ||||
| } | ||||
| export const usePrefersDarkMode = () => | ||||
|   useMediaQuery("(prefers-color-scheme: dark)"); | ||||
|  | ||||
| @ -5,8 +5,8 @@ export enum AnchorIds { | ||||
| } | ||||
| 
 | ||||
| // Scroll to top of element "id" when "deps" update.
 | ||||
| export function useScrollTopOnChange(id: AnchorIds, deps: DependencyList) { | ||||
| export const useScrollTopOnChange = (id: AnchorIds, deps: DependencyList) => { | ||||
|   useEffect(() => { | ||||
|     document.querySelector(`#${id}`)?.scrollTo({ top: 0, behavior: "smooth" }); | ||||
|   }, deps); | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -13,31 +13,28 @@ interface Props<T> { | ||||
|   transform?: (item: NonNullable<T>) => NonNullable<T>; | ||||
| } | ||||
| 
 | ||||
| function getPreferredLanguage( | ||||
| const getPreferredLanguage = ( | ||||
|   preferredLanguages: (string | undefined)[], | ||||
|   availableLanguages: Map<string, number> | ||||
| ): number | undefined { | ||||
| ): number | undefined => { | ||||
|   for (const locale of preferredLanguages) { | ||||
|     if (isDefined(locale) && availableLanguages.has(locale)) { | ||||
|       return availableLanguages.get(locale); | ||||
|     } | ||||
|   } | ||||
|   return undefined; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export function useSmartLanguage<T>( | ||||
|   props: Props<T> | ||||
| ): [ | ||||
| export const useSmartLanguage = <T>({ | ||||
|   items, | ||||
|   languageExtractor, | ||||
|   languages, | ||||
|   transform = (item) => item, | ||||
| }: Props<T>): [ | ||||
|   T | undefined, | ||||
|   typeof LanguageSwitcher, | ||||
|   Parameters<typeof LanguageSwitcher>[0] | ||||
| ] { | ||||
|   const { | ||||
|     items, | ||||
|     languageExtractor, | ||||
|     languages, | ||||
|     transform = (item) => item, | ||||
|   } = props; | ||||
| ] => { | ||||
|   const { preferredLanguages } = useAppLayout(); | ||||
|   const router = useRouter(); | ||||
| 
 | ||||
| @ -81,4 +78,4 @@ export function useSmartLanguage<T>( | ||||
|   }; | ||||
| 
 | ||||
|   return [selectedTranslation, LanguageSwitcher, languageSwitcherProps]; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -1,84 +0,0 @@ | ||||
| import { LanguageSwitcher } from "components/Inputs/LanguageSwitcher"; | ||||
| import { useAppLayout } from "contexts/AppLayoutContext"; | ||||
| import { AppStaticProps } from "graphql/getAppStaticProps"; | ||||
| import { filterDefined, isDefined } from "helpers/others"; | ||||
| 
 | ||||
| import { useRouter } from "next/router"; | ||||
| import { useEffect, useMemo, useState } from "react"; | ||||
| 
 | ||||
| interface Props<T> { | ||||
|   items: T[]; | ||||
|   languages: AppStaticProps["languages"]; | ||||
|   languageExtractor: (item: NonNullable<T>) => string | undefined; | ||||
|   transform?: (item: NonNullable<T>) => NonNullable<T>; | ||||
| } | ||||
| 
 | ||||
| function getPreferredLanguage( | ||||
|   preferredLanguages: (string | undefined)[], | ||||
|   availableLanguages: Map<string, number> | ||||
| ): number | undefined { | ||||
|   for (const locale of preferredLanguages) { | ||||
|     if (isDefined(locale) && availableLanguages.has(locale)) { | ||||
|       return availableLanguages.get(locale); | ||||
|     } | ||||
|   } | ||||
|   return undefined; | ||||
| } | ||||
| 
 | ||||
| export function useSmartLanguage<T>( | ||||
|   props: Props<T> | ||||
| ): [ | ||||
|   T | undefined, | ||||
|   typeof LanguageSwitcher, | ||||
|   Parameters<typeof LanguageSwitcher>[0] | ||||
| ] { | ||||
|   const { | ||||
|     items, | ||||
|     languageExtractor, | ||||
|     languages, | ||||
|     transform = (item) => item, | ||||
|   } = props; | ||||
|   const { preferredLanguages } = useAppLayout(); | ||||
|   const router = useRouter(); | ||||
| 
 | ||||
|   const availableLocales = useMemo(() => { | ||||
|     const memo = new Map<string, number>(); | ||||
|     filterDefined(items).map((elem, index) => { | ||||
|       const result = languageExtractor(elem); | ||||
|       if (isDefined(result)) memo.set(result, index); | ||||
|     }); | ||||
|     return memo; | ||||
|   }, [items, languageExtractor]); | ||||
| 
 | ||||
|   const [selectedTranslationIndex, setSelectedTranslationIndex] = useState< | ||||
|     number | undefined | ||||
|   >(); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     setSelectedTranslationIndex( | ||||
|       getPreferredLanguage( | ||||
|         preferredLanguages ?? [router.locale], | ||||
|         availableLocales | ||||
|       ) | ||||
|     ); | ||||
|   }, [preferredLanguages, availableLocales, router.locale]); | ||||
| 
 | ||||
|   const selectedTranslation = useMemo(() => { | ||||
|     if (isDefined(selectedTranslationIndex)) { | ||||
|       const item = items[selectedTranslationIndex]; | ||||
|       if (isDefined(item)) { | ||||
|         return transform(item); | ||||
|       } | ||||
|     } | ||||
|     return undefined; | ||||
|   }, [items, selectedTranslationIndex, transform]); | ||||
| 
 | ||||
|   const languageSwitcherProps = { | ||||
|     languages: languages, | ||||
|     locales: availableLocales, | ||||
|     localesIndex: selectedTranslationIndex, | ||||
|     onLanguageChanged: setSelectedTranslationIndex, | ||||
|   }; | ||||
| 
 | ||||
|   return [selectedTranslation, LanguageSwitcher, languageSwitcherProps]; | ||||
| } | ||||
| @ -1,10 +1,10 @@ | ||||
| import { isDefined } from "helpers/others"; | ||||
| import { useEffect, useState } from "react"; | ||||
| 
 | ||||
| export function useStateWithLocalStorage<T>( | ||||
| export const useStateWithLocalStorage = <T>( | ||||
|   key: string, | ||||
|   initialValue: T | ||||
| ): [T | undefined, React.Dispatch<React.SetStateAction<T | undefined>>] { | ||||
| ): [T | undefined, React.Dispatch<React.SetStateAction<T | undefined>>] => { | ||||
|   const [value, setValue] = useState<T | undefined>(undefined); | ||||
|   const [, setFromLocaleStorage] = useState<boolean>(false); | ||||
| 
 | ||||
| @ -28,4 +28,4 @@ export function useStateWithLocalStorage<T>( | ||||
|   }, [value, key]); | ||||
| 
 | ||||
|   return [value, setValue]; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -1,7 +1,6 @@ | ||||
| import { Dispatch, SetStateAction, useCallback } from "react"; | ||||
| 
 | ||||
| export function useToggle(setState: Dispatch<SetStateAction<boolean>>) { | ||||
|   return useCallback(() => { | ||||
| export const useToggle = (setState: Dispatch<SetStateAction<boolean>>) => | ||||
|   useCallback(() => { | ||||
|     setState((current) => !current); | ||||
|   }, []); | ||||
| } | ||||
|  | ||||
| @ -5,16 +5,19 @@ import { | ||||
| } from "components/PanelComponents/ReturnButton"; | ||||
| import { ContentPanel } from "components/Panels/ContentPanel"; | ||||
| import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; | ||||
| import { GetStaticProps } from "next"; | ||||
| 
 | ||||
| import { GetStaticPropsContext } from "next"; | ||||
| import { useMemo } from "react"; | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props extends AppStaticProps {} | ||||
| 
 | ||||
| export default function FourOhFour(props: Props): JSX.Element { | ||||
|   const { langui } = props; | ||||
|   const contentPanel = useMemo( | ||||
|     () => ( | ||||
| const FourOhFour = ({ langui, ...otherProps }: Props): JSX.Element => ( | ||||
|   <AppLayout | ||||
|     navTitle="404" | ||||
|     contentPanel={ | ||||
|       <ContentPanel> | ||||
|         <h1>404 - {langui.page_not_found}</h1> | ||||
|         <ReturnButton | ||||
| @ -24,19 +27,23 @@ export default function FourOhFour(props: Props): JSX.Element { | ||||
|           displayOn={ReturnButtonType.Both} | ||||
|         /> | ||||
|       </ContentPanel> | ||||
|     ), | ||||
|     [langui] | ||||
|   ); | ||||
|   return <AppLayout navTitle="404" contentPanel={contentPanel} {...props} />; | ||||
| } | ||||
|     } | ||||
|     langui={langui} | ||||
|     {...otherProps} | ||||
|   /> | ||||
| ); | ||||
| export default FourOhFour; | ||||
| 
 | ||||
| export async function getStaticProps( | ||||
|   context: GetStaticPropsContext | ||||
| ): Promise<{ notFound: boolean } | { props: Props }> { | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps: GetStaticProps = async (context) => { | ||||
|   const props: Props = { | ||||
|     ...(await getAppStaticProps(context)), | ||||
|   }; | ||||
|   return { | ||||
|     props: props, | ||||
|   }; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -5,16 +5,19 @@ import { | ||||
| } from "components/PanelComponents/ReturnButton"; | ||||
| import { ContentPanel } from "components/Panels/ContentPanel"; | ||||
| import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; | ||||
| import { GetStaticProps } from "next"; | ||||
| 
 | ||||
| import { GetStaticPropsContext } from "next"; | ||||
| import { useMemo } from "react"; | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props extends AppStaticProps {} | ||||
| 
 | ||||
| export default function FiveHundred(props: Props): JSX.Element { | ||||
|   const { langui } = props; | ||||
|   const contentPanel = useMemo( | ||||
|     () => ( | ||||
| const FiveHundred = ({ langui, ...otherProps }: Props): JSX.Element => ( | ||||
|   <AppLayout | ||||
|     navTitle="500" | ||||
|     contentPanel={ | ||||
|       <ContentPanel> | ||||
|         <h1>500 - Internal Server Error</h1> | ||||
|         <ReturnButton | ||||
| @ -24,19 +27,23 @@ export default function FiveHundred(props: Props): JSX.Element { | ||||
|           displayOn={ReturnButtonType.Both} | ||||
|         /> | ||||
|       </ContentPanel> | ||||
|     ), | ||||
|     [langui] | ||||
|   ); | ||||
|   return <AppLayout navTitle="500" contentPanel={contentPanel} {...props} />; | ||||
| } | ||||
|     } | ||||
|     langui={langui} | ||||
|     {...otherProps} | ||||
|   /> | ||||
| ); | ||||
| export default FiveHundred; | ||||
| 
 | ||||
| export async function getStaticProps( | ||||
|   context: GetStaticPropsContext | ||||
| ): Promise<{ notFound: boolean } | { props: Props }> { | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps: GetStaticProps = async (context) => { | ||||
|   const props: Props = { | ||||
|     ...(await getAppStaticProps(context)), | ||||
|   }; | ||||
|   return { | ||||
|     props: props, | ||||
|   }; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -9,10 +9,9 @@ import { AppContextProvider } from "contexts/AppLayoutContext"; | ||||
| import type { AppProps } from "next/app"; | ||||
| import "tailwind.css"; | ||||
| 
 | ||||
| export default function AccordsLibraryApp(props: AppProps): JSX.Element { | ||||
|   return ( | ||||
|     <AppContextProvider> | ||||
|       <props.Component {...props.pageProps} /> | ||||
|     </AppContextProvider> | ||||
|   ); | ||||
| } | ||||
| const AccordsLibraryApp = (props: AppProps): JSX.Element => ( | ||||
|   <AppContextProvider> | ||||
|     <props.Component {...props.pageProps} /> | ||||
|   </AppContextProvider> | ||||
| ); | ||||
| export default AccordsLibraryApp; | ||||
|  | ||||
| @ -4,20 +4,33 @@ import { | ||||
|   PostStaticProps, | ||||
| } from "graphql/getPostStaticProps"; | ||||
| 
 | ||||
| export default function AccordsHandbook(props: PostStaticProps): JSX.Element { | ||||
|   const { post, langui, languages, currencies } = props; | ||||
|   return ( | ||||
|     <PostPage | ||||
|       currencies={currencies} | ||||
|       languages={languages} | ||||
|       langui={langui} | ||||
|       post={post} | ||||
|       returnHref="/about-us/" | ||||
|       returnTitle={langui.about_us} | ||||
|       displayToc | ||||
|       displayLanguageSwitcher | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| const AccordsHandbook = ({ | ||||
|   post, | ||||
|   langui, | ||||
|   languages, | ||||
|   currencies, | ||||
| }: PostStaticProps): JSX.Element => ( | ||||
|   <PostPage | ||||
|     currencies={currencies} | ||||
|     languages={languages} | ||||
|     langui={langui} | ||||
|     post={post} | ||||
|     returnHref="/about-us/" | ||||
|     returnTitle={langui.about_us} | ||||
|     displayToc | ||||
|     displayLanguageSwitcher | ||||
|   /> | ||||
| ); | ||||
| export default AccordsHandbook; | ||||
| 
 | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps = getPostStaticProps("accords-handbook"); | ||||
|  | ||||
| @ -11,9 +11,17 @@ import { useRouter } from "next/router"; | ||||
| import { RequestMailProps, ResponseMailProps } from "pages/api/mail"; | ||||
| import { useState } from "react"; | ||||
| 
 | ||||
| export default function AboutUs(props: PostStaticProps): JSX.Element { | ||||
|   const { post, langui, languages, currencies } = props; | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| const AboutUs = ({ | ||||
|   post, | ||||
|   langui, | ||||
|   languages, | ||||
|   currencies, | ||||
| }: PostStaticProps): JSX.Element => { | ||||
|   const router = useRouter(); | ||||
|   const [formResponse, setFormResponse] = useState(""); | ||||
|   const [formState, setFormState] = useState<"completed" | "ongoing" | "stale">( | ||||
| @ -181,6 +189,12 @@ export default function AboutUs(props: PostStaticProps): JSX.Element { | ||||
|       displayLanguageSwitcher | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
| export default AboutUs; | ||||
| 
 | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps = getPostStaticProps("contact"); | ||||
|  | ||||
| @ -4,16 +4,19 @@ import { NavOption } from "components/PanelComponents/NavOption"; | ||||
| import { PanelHeader } from "components/PanelComponents/PanelHeader"; | ||||
| import { SubPanel } from "components/Panels/SubPanel"; | ||||
| import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; | ||||
| import { GetStaticProps } from "next"; | ||||
| 
 | ||||
| import { GetStaticPropsContext } from "next"; | ||||
| import { useMemo } from "react"; | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props extends AppStaticProps {} | ||||
| 
 | ||||
| export default function AboutUs(props: Props): JSX.Element { | ||||
|   const { langui } = props; | ||||
|   const subPanel = useMemo( | ||||
|     () => ( | ||||
| const AboutUs = ({ langui, ...otherProps }: Props): JSX.Element => ( | ||||
|   <AppLayout | ||||
|     navTitle={langui.about_us} | ||||
|     subPanel={ | ||||
|       <SubPanel> | ||||
|         <PanelHeader | ||||
|           icon={Icon.Info} | ||||
| @ -33,21 +36,23 @@ export default function AboutUs(props: Props): JSX.Element { | ||||
|         /> | ||||
|         <NavOption title={langui.contact_us} url="/about-us/contact" border /> | ||||
|       </SubPanel> | ||||
|     ), | ||||
|     [langui] | ||||
|   ); | ||||
|   return ( | ||||
|     <AppLayout navTitle={langui.about_us} subPanel={subPanel} {...props} /> | ||||
|   ); | ||||
| } | ||||
|     } | ||||
|     langui={langui} | ||||
|     {...otherProps} | ||||
|   /> | ||||
| ); | ||||
| export default AboutUs; | ||||
| 
 | ||||
| export async function getStaticProps( | ||||
|   context: GetStaticPropsContext | ||||
| ): Promise<{ notFound: boolean } | { props: Props }> { | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps: GetStaticProps = async (context) => { | ||||
|   const props: Props = { | ||||
|     ...(await getAppStaticProps(context)), | ||||
|   }; | ||||
|   return { | ||||
|     props: props, | ||||
|   }; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -4,20 +4,33 @@ import { | ||||
|   PostStaticProps, | ||||
| } from "graphql/getPostStaticProps"; | ||||
| 
 | ||||
| export default function Legality(props: PostStaticProps): JSX.Element { | ||||
|   const { post, langui, languages, currencies } = props; | ||||
|   return ( | ||||
|     <PostPage | ||||
|       currencies={currencies} | ||||
|       languages={languages} | ||||
|       langui={langui} | ||||
|       post={post} | ||||
|       returnHref="/about-us/" | ||||
|       returnTitle={langui.about_us} | ||||
|       displayToc | ||||
|       displayLanguageSwitcher | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| const Legality = ({ | ||||
|   post, | ||||
|   langui, | ||||
|   languages, | ||||
|   currencies, | ||||
| }: PostStaticProps): JSX.Element => ( | ||||
|   <PostPage | ||||
|     currencies={currencies} | ||||
|     languages={languages} | ||||
|     langui={langui} | ||||
|     post={post} | ||||
|     returnHref="/about-us/" | ||||
|     returnTitle={langui.about_us} | ||||
|     displayToc | ||||
|     displayLanguageSwitcher | ||||
|   /> | ||||
| ); | ||||
| export default Legality; | ||||
| 
 | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps = getPostStaticProps("legality"); | ||||
|  | ||||
| @ -4,20 +4,33 @@ import { | ||||
|   PostStaticProps, | ||||
| } from "graphql/getPostStaticProps"; | ||||
| 
 | ||||
| export default function SharingPolicy(props: PostStaticProps): JSX.Element { | ||||
|   const { post, langui, languages, currencies } = props; | ||||
|   return ( | ||||
|     <PostPage | ||||
|       currencies={currencies} | ||||
|       languages={languages} | ||||
|       langui={langui} | ||||
|       post={post} | ||||
|       returnHref="/about-us/" | ||||
|       returnTitle={langui.about_us} | ||||
|       displayToc | ||||
|       displayLanguageSwitcher | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| const SharingPolicy = ({ | ||||
|   post, | ||||
|   langui, | ||||
|   languages, | ||||
|   currencies, | ||||
| }: PostStaticProps): JSX.Element => ( | ||||
|   <PostPage | ||||
|     currencies={currencies} | ||||
|     languages={languages} | ||||
|     langui={langui} | ||||
|     post={post} | ||||
|     returnHref="/about-us/" | ||||
|     returnTitle={langui.about_us} | ||||
|     displayToc | ||||
|     displayLanguageSwitcher | ||||
|   /> | ||||
| ); | ||||
| export default SharingPolicy; | ||||
| 
 | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps = getPostStaticProps("sharing-policy"); | ||||
|  | ||||
| @ -14,10 +14,10 @@ export interface RequestMailProps { | ||||
|   formName: string; | ||||
| } | ||||
| 
 | ||||
| export default async function Mail( | ||||
| const Mail = async ( | ||||
|   req: NextApiRequest, | ||||
|   res: NextApiResponse<ResponseMailProps> | ||||
| ) { | ||||
| ) => { | ||||
|   if (req.method === "POST") { | ||||
|     const body = req.body as RequestMailProps; | ||||
| 
 | ||||
| @ -48,4 +48,5 @@ export default async function Mail( | ||||
|   } | ||||
| 
 | ||||
|   res.status(200).json({ code: "OKAY" }); | ||||
| } | ||||
| }; | ||||
| export default Mail; | ||||
|  | ||||
| @ -81,10 +81,10 @@ type ResponseMailProps = { | ||||
|   revalidated: boolean; | ||||
| }; | ||||
| 
 | ||||
| export default async function Revalidate( | ||||
| const Revalidate = async ( | ||||
|   req: NextApiRequest, | ||||
|   res: NextApiResponse<ResponseMailProps> | ||||
| ) { | ||||
| ) => { | ||||
|   const body = req.body as RequestProps; | ||||
|   const { serverRuntimeConfig } = getConfig(); | ||||
| 
 | ||||
| @ -217,4 +217,5 @@ export default async function Revalidate( | ||||
|       .status(500) | ||||
|       .send({ message: "Error revalidating", revalidated: false }); | ||||
|   } | ||||
| } | ||||
| }; | ||||
| export default Revalidate; | ||||
|  | ||||
| @ -4,14 +4,18 @@ import { PanelHeader } from "components/PanelComponents/PanelHeader"; | ||||
| import { SubPanel } from "components/Panels/SubPanel"; | ||||
| import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; | ||||
| 
 | ||||
| import { GetStaticPropsContext } from "next"; | ||||
| import { GetStaticProps } from "next"; | ||||
| import { Icon } from "components/Ico"; | ||||
| import { useMemo } from "react"; | ||||
| 
 | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props extends AppStaticProps {} | ||||
| 
 | ||||
| export default function Archives(props: Props): JSX.Element { | ||||
|   const { langui } = props; | ||||
| const Archives = ({ langui, ...otherProps }: Props): JSX.Element => { | ||||
|   const subPanel = useMemo( | ||||
|     () => ( | ||||
|       <SubPanel> | ||||
| @ -26,17 +30,26 @@ export default function Archives(props: Props): JSX.Element { | ||||
|     [langui] | ||||
|   ); | ||||
|   return ( | ||||
|     <AppLayout navTitle={langui.archives} subPanel={subPanel} {...props} /> | ||||
|     <AppLayout | ||||
|       navTitle={langui.archives} | ||||
|       subPanel={subPanel} | ||||
|       langui={langui} | ||||
|       {...otherProps} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
| export default Archives; | ||||
| 
 | ||||
| export async function getStaticProps( | ||||
|   context: GetStaticPropsContext | ||||
| ): Promise<{ notFound: boolean } | { props: Props }> { | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps: GetStaticProps = async (context) => { | ||||
|   const props: Props = { | ||||
|     ...(await getAppStaticProps(context)), | ||||
|   }; | ||||
|   return { | ||||
|     props: props, | ||||
|   }; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -15,25 +15,25 @@ import { GetVideoChannelQuery } from "graphql/generated"; | ||||
| import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; | ||||
| import { getReadySdk } from "graphql/sdk"; | ||||
| import { getVideoThumbnailURL } from "helpers/videos"; | ||||
| import { | ||||
|   GetStaticPathsContext, | ||||
|   GetStaticPathsResult, | ||||
|   GetStaticPropsContext, | ||||
| } from "next"; | ||||
| import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; | ||||
| import { Fragment, useState, useMemo } from "react"; | ||||
| import { Icon } from "components/Ico"; | ||||
| import { useMediaHoverable } from "hooks/useMediaQuery"; | ||||
| import { WithLabel } from "components/Inputs/WithLabel"; | ||||
| import { filterHasAttributes, isDefined } from "helpers/others"; | ||||
| 
 | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props extends AppStaticProps { | ||||
|   channel: NonNullable< | ||||
|     GetVideoChannelQuery["videoChannels"] | ||||
|   >["data"][number]["attributes"]; | ||||
| } | ||||
| 
 | ||||
| export default function Channel(props: Props): JSX.Element { | ||||
|   const { langui, channel } = props; | ||||
| const Channel = ({ langui, channel, ...otherProps }: Props): JSX.Element => { | ||||
|   const [keepInfoVisible, setKeepInfoVisible] = useState(true); | ||||
|   const hoverable = useMediaHoverable(); | ||||
| 
 | ||||
| @ -115,14 +115,19 @@ export default function Channel(props: Props): JSX.Element { | ||||
|       navTitle={langui.archives} | ||||
|       subPanel={subPanel} | ||||
|       contentPanel={contentPanel} | ||||
|       {...props} | ||||
|       langui={langui} | ||||
|       {...otherProps} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
| export default Channel; | ||||
| 
 | ||||
| export async function getStaticProps( | ||||
|   context: GetStaticPropsContext | ||||
| ): Promise<{ notFound: boolean } | { props: Props }> { | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps: GetStaticProps = async (context) => { | ||||
|   const sdk = getReadySdk(); | ||||
|   const channel = await sdk.getVideoChannel({ | ||||
|     channel: | ||||
| @ -138,11 +143,11 @@ export async function getStaticProps( | ||||
|   return { | ||||
|     props: props, | ||||
|   }; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export async function getStaticPaths( | ||||
|   context: GetStaticPathsContext | ||||
| ): Promise<GetStaticPathsResult> { | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const getStaticPaths: GetStaticPaths = async (context) => { | ||||
|   const sdk = getReadySdk(); | ||||
|   const channels = await sdk.getVideoChannelsSlugs(); | ||||
|   const paths: GetStaticPathsResult["paths"] = []; | ||||
| @ -159,4 +164,4 @@ export async function getStaticPaths( | ||||
|     paths, | ||||
|     fallback: "blocking", | ||||
|   }; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -21,18 +21,29 @@ import { prettyDate } from "helpers/formatters"; | ||||
| import { filterHasAttributes } from "helpers/others"; | ||||
| import { getVideoThumbnailURL } from "helpers/videos"; | ||||
| import { useMediaHoverable } from "hooks/useMediaQuery"; | ||||
| import { GetStaticPropsContext } from "next"; | ||||
| import { GetStaticProps } from "next"; | ||||
| import { Fragment, useMemo, useState } from "react"; | ||||
| 
 | ||||
| /* | ||||
|  *                                         ╭─────────────╮ | ||||
|  * ────────────────────────────────────────╯  CONSTANTS  ╰────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| const ITEM_PER_PAGE = 50; | ||||
| 
 | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props extends AppStaticProps { | ||||
|   videos: NonNullable<GetVideosPreviewQuery["videos"]>["data"]; | ||||
| } | ||||
| 
 | ||||
| const ITEM_PER_PAGE = 50; | ||||
| 
 | ||||
| export default function Videos(props: Props): JSX.Element { | ||||
|   const { langui, videos } = props; | ||||
| const Videos = ({ langui, videos, ...otherProps }: Props): JSX.Element => { | ||||
|   const hoverable = useMediaHoverable(); | ||||
|   const [page, setPage] = useState(0); | ||||
|   const [keepInfoVisible, setKeepInfoVisible] = useState(true); | ||||
| 
 | ||||
|   const paginatedVideos = useMemo(() => { | ||||
|     const memo = []; | ||||
| @ -44,9 +55,6 @@ export default function Videos(props: Props): JSX.Element { | ||||
|     return memo; | ||||
|   }, [videos]); | ||||
| 
 | ||||
|   const [page, setPage] = useState(0); | ||||
|   const [keepInfoVisible, setKeepInfoVisible] = useState(true); | ||||
| 
 | ||||
|   const subPanel = useMemo( | ||||
|     () => ( | ||||
|       <SubPanel> | ||||
| @ -130,14 +138,19 @@ export default function Videos(props: Props): JSX.Element { | ||||
|       navTitle={langui.archives} | ||||
|       subPanel={subPanel} | ||||
|       contentPanel={contentPanel} | ||||
|       {...props} | ||||
|       langui={langui} | ||||
|       {...otherProps} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
| export default Videos; | ||||
| 
 | ||||
| export async function getStaticProps( | ||||
|   context: GetStaticPropsContext | ||||
| ): Promise<{ notFound: boolean } | { props: Props }> { | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps: GetStaticProps = async (context) => { | ||||
|   const sdk = getReadySdk(); | ||||
|   const videos = await sdk.getVideosPreview(); | ||||
|   if (!videos.videos) return { notFound: true }; | ||||
| @ -159,4 +172,4 @@ export async function getStaticProps( | ||||
|   return { | ||||
|     props: props, | ||||
|   }; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -21,21 +21,21 @@ import { prettyDate, prettyShortenNumber } from "helpers/formatters"; | ||||
| import { filterHasAttributes, isDefined } from "helpers/others"; | ||||
| import { getVideoFile } from "helpers/videos"; | ||||
| import { useMediaMobile } from "hooks/useMediaQuery"; | ||||
| import { | ||||
|   GetStaticPathsContext, | ||||
|   GetStaticPathsResult, | ||||
|   GetStaticPropsContext, | ||||
| } from "next"; | ||||
| import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; | ||||
| import { useMemo } from "react"; | ||||
| 
 | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props extends AppStaticProps { | ||||
|   video: NonNullable< | ||||
|     NonNullable<GetVideoQuery["videos"]>["data"][number]["attributes"] | ||||
|   >; | ||||
| } | ||||
| 
 | ||||
| export default function Video(props: Props): JSX.Element { | ||||
|   const { langui, video } = props; | ||||
| const Video = ({ langui, video, ...otherProps }: Props): JSX.Element => { | ||||
|   const isMobile = useMediaMobile(); | ||||
|   const appLayout = useAppLayout(); | ||||
|   const subPanel = useMemo( | ||||
| @ -201,14 +201,19 @@ export default function Video(props: Props): JSX.Element { | ||||
|       navTitle={langui.archives} | ||||
|       subPanel={subPanel} | ||||
|       contentPanel={contentPanel} | ||||
|       {...props} | ||||
|       langui={langui} | ||||
|       {...otherProps} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
| export default Video; | ||||
| 
 | ||||
| export async function getStaticProps( | ||||
|   context: GetStaticPropsContext | ||||
| ): Promise<{ notFound: boolean } | { props: Props }> { | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps: GetStaticProps = async (context) => { | ||||
|   const sdk = getReadySdk(); | ||||
|   const videos = await sdk.getVideo({ | ||||
|     uid: | ||||
| @ -224,11 +229,11 @@ export async function getStaticProps( | ||||
|   return { | ||||
|     props: props, | ||||
|   }; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export async function getStaticPaths( | ||||
|   context: GetStaticPathsContext | ||||
| ): Promise<GetStaticPathsResult> { | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const getStaticPaths: GetStaticPaths = async (context) => { | ||||
|   const sdk = getReadySdk(); | ||||
|   const videos = await sdk.getVideosSlugs(); | ||||
|   const paths: GetStaticPathsResult["paths"] = []; | ||||
| @ -242,4 +247,4 @@ export async function getStaticPaths( | ||||
|     paths, | ||||
|     fallback: "blocking", | ||||
|   }; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -3,14 +3,18 @@ import { PanelHeader } from "components/PanelComponents/PanelHeader"; | ||||
| import { SubPanel } from "components/Panels/SubPanel"; | ||||
| import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; | ||||
| 
 | ||||
| import { GetStaticPropsContext } from "next"; | ||||
| import { GetStaticProps } from "next"; | ||||
| import { Icon } from "components/Ico"; | ||||
| import { useMemo } from "react"; | ||||
| 
 | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props extends AppStaticProps {} | ||||
| 
 | ||||
| export default function Chronicles(props: Props): JSX.Element { | ||||
|   const { langui } = props; | ||||
| const Chronicles = ({ langui, ...otherProps }: Props): JSX.Element => { | ||||
|   const subPanel = useMemo( | ||||
|     () => ( | ||||
|       <SubPanel> | ||||
| @ -25,17 +29,26 @@ export default function Chronicles(props: Props): JSX.Element { | ||||
|   ); | ||||
| 
 | ||||
|   return ( | ||||
|     <AppLayout navTitle={langui.chronicles} subPanel={subPanel} {...props} /> | ||||
|     <AppLayout | ||||
|       navTitle={langui.chronicles} | ||||
|       subPanel={subPanel} | ||||
|       langui={langui} | ||||
|       {...otherProps} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
| export default Chronicles; | ||||
| 
 | ||||
| export async function getStaticProps( | ||||
|   context: GetStaticPropsContext | ||||
| ): Promise<{ notFound: boolean } | { props: Props }> { | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps: GetStaticProps = async (context) => { | ||||
|   const props: Props = { | ||||
|     ...(await getAppStaticProps(context)), | ||||
|   }; | ||||
|   return { | ||||
|     props: props, | ||||
|   }; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -2,8 +2,7 @@ import { AppLayout } from "components/AppLayout"; | ||||
| import { Chip } from "components/Chip"; | ||||
| import { HorizontalLine } from "components/HorizontalLine"; | ||||
| import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs"; | ||||
| import { Markdawn } from "components/Markdown/Markdawn"; | ||||
| import { TOC } from "components/Markdown/TOC"; | ||||
| import { Markdawn, TableOfContents } from "components/Markdown/Markdawn"; | ||||
| import { | ||||
|   ReturnButton, | ||||
|   ReturnButtonType, | ||||
| @ -35,27 +34,25 @@ import { ContentWithTranslations } from "helpers/types"; | ||||
| import { useMediaMobile } from "hooks/useMediaQuery"; | ||||
| import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange"; | ||||
| import { useSmartLanguage } from "hooks/useSmartLanguage"; | ||||
| import { | ||||
|   GetStaticPathsContext, | ||||
|   GetStaticPathsResult, | ||||
|   GetStaticPropsContext, | ||||
| } from "next"; | ||||
| import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; | ||||
| import { Fragment, useCallback, useMemo } from "react"; | ||||
| 
 | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props extends AppStaticProps { | ||||
|   content: ContentWithTranslations; | ||||
| } | ||||
| 
 | ||||
| type Group = NonNullable< | ||||
|   NonNullable< | ||||
|     NonNullable< | ||||
|       NonNullable<ContentWithTranslations["group"]>["data"] | ||||
|     >["attributes"] | ||||
|   >["contents"] | ||||
| >["data"]; | ||||
| 
 | ||||
| export default function Content(props: Props): JSX.Element { | ||||
|   const { langui, content, languages, currencies } = props; | ||||
| const Content = ({ | ||||
|   langui, | ||||
|   content, | ||||
|   languages, | ||||
|   currencies, | ||||
|   ...otherProps | ||||
| }: Props): JSX.Element => { | ||||
|   const isMobile = useMediaMobile(); | ||||
| 
 | ||||
|   const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = | ||||
| @ -253,15 +250,14 @@ export default function Content(props: Props): JSX.Element { | ||||
|                               position: "Bottom", | ||||
|                             }} | ||||
|                             infoAppend={ | ||||
|                               <PreviewCardCTAs | ||||
|                                 id={libraryItem.id} | ||||
|                                 displayCTAs={ | ||||
|                                   !isUntangibleGroupItem( | ||||
|                                     libraryItem.attributes.metadata?.[0] | ||||
|                                   ) | ||||
|                                 } | ||||
|                                 langui={langui} | ||||
|                               /> | ||||
|                               !isUntangibleGroupItem( | ||||
|                                 libraryItem.attributes.metadata?.[0] | ||||
|                               ) && ( | ||||
|                                 <PreviewCardCTAs | ||||
|                                   id={libraryItem.id} | ||||
|                                   langui={langui} | ||||
|                                 /> | ||||
|                               ) | ||||
|                             } | ||||
|                           /> | ||||
|                         </div> | ||||
| @ -277,13 +273,14 @@ export default function Content(props: Props): JSX.Element { | ||||
|         {selectedTranslation?.text_set?.text && ( | ||||
|           <> | ||||
|             <HorizontalLine /> | ||||
|             <TOC | ||||
|             <TableOfContents | ||||
|               text={selectedTranslation.text_set.text} | ||||
|               title={prettyinlineTitle( | ||||
|                 selectedTranslation.pre_title, | ||||
|                 selectedTranslation.title, | ||||
|                 selectedTranslation.subtitle | ||||
|               )} | ||||
|               langui={langui} | ||||
|             /> | ||||
|           </> | ||||
|         )} | ||||
| @ -460,14 +457,21 @@ export default function Content(props: Props): JSX.Element { | ||||
|       thumbnail={content.thumbnail?.data?.attributes ?? undefined} | ||||
|       contentPanel={contentPanel} | ||||
|       subPanel={subPanel} | ||||
|       {...props} | ||||
|       currencies={currencies} | ||||
|       languages={languages} | ||||
|       langui={langui} | ||||
|       {...otherProps} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
| export default Content; | ||||
| 
 | ||||
| export async function getStaticProps( | ||||
|   context: GetStaticPropsContext | ||||
| ): Promise<{ notFound: boolean } | { props: Props }> { | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps: GetStaticProps = async (context) => { | ||||
|   const sdk = getReadySdk(); | ||||
|   const slug = context.params?.slug ? context.params.slug.toString() : ""; | ||||
|   const content = await sdk.getContentText({ | ||||
| @ -485,11 +489,11 @@ export async function getStaticProps( | ||||
|   return { | ||||
|     props: props, | ||||
|   }; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export async function getStaticPaths( | ||||
|   context: GetStaticPathsContext | ||||
| ): Promise<GetStaticPathsResult> { | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const getStaticPaths: GetStaticPaths = async (context) => { | ||||
|   const sdk = getReadySdk(); | ||||
|   const contents = await sdk.getContentsSlugs(); | ||||
|   const paths: GetStaticPathsResult["paths"] = []; | ||||
| @ -505,9 +509,22 @@ export async function getStaticPaths( | ||||
|     paths, | ||||
|     fallback: "blocking", | ||||
|   }; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| function getPreviousContent(group: Group, currentSlug: string) { | ||||
| /* | ||||
|  *                                      ╭───────────────────╮ | ||||
|  * ─────────────────────────────────────╯  PRIVATE METHODS  ╰─────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| type Group = NonNullable< | ||||
|   NonNullable< | ||||
|     NonNullable< | ||||
|       NonNullable<ContentWithTranslations["group"]>["data"] | ||||
|     >["attributes"] | ||||
|   >["contents"] | ||||
| >["data"]; | ||||
| 
 | ||||
| const getPreviousContent = (group: Group, currentSlug: string) => { | ||||
|   for (let index = 0; index < group.length; index += 1) { | ||||
|     const content = group[index]; | ||||
|     if (content.attributes?.slug === currentSlug && index > 0) { | ||||
| @ -515,9 +532,11 @@ function getPreviousContent(group: Group, currentSlug: string) { | ||||
|     } | ||||
|   } | ||||
|   return undefined; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| function getNextContent(group: Group, currentSlug: string) { | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| const getNextContent = (group: Group, currentSlug: string) => { | ||||
|   for (let index = 0; index < group.length; index += 1) { | ||||
|     const content = group[index]; | ||||
|     if (content.attributes?.slug === currentSlug && index < group.length - 1) { | ||||
| @ -525,4 +544,4 @@ function getNextContent(group: Group, currentSlug: string) { | ||||
|     } | ||||
|   } | ||||
|   return undefined; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -9,12 +9,10 @@ import { | ||||
| } from "components/Panels/ContentPanel"; | ||||
| import { SubPanel } from "components/Panels/SubPanel"; | ||||
| import { TranslatedPreviewCard } from "components/PreviewCard"; | ||||
| 
 | ||||
| import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; | ||||
| import { getReadySdk } from "graphql/sdk"; | ||||
| import { prettyinlineTitle, prettySlug } from "helpers/formatters"; | ||||
| 
 | ||||
| import { GetStaticPropsContext } from "next"; | ||||
| import { GetStaticProps } from "next"; | ||||
| import { Fragment, useState, useMemo } from "react"; | ||||
| import { Icon } from "components/Ico"; | ||||
| import { WithLabel } from "components/Inputs/WithLabel"; | ||||
| @ -29,33 +27,47 @@ import { | ||||
| import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder"; | ||||
| import { GetContentsQuery } from "graphql/generated"; | ||||
| 
 | ||||
| interface Props extends AppStaticProps { | ||||
|   contents: NonNullable<GetContentsQuery["contents"]>["data"]; | ||||
| } | ||||
| /* | ||||
|  *                                         ╭─────────────╮ | ||||
|  * ────────────────────────────────────────╯  CONSTANTS  ╰────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| type GroupContentItems = Map<string, Props["contents"]>; | ||||
| 
 | ||||
| const defaultFiltersState = { | ||||
| const DEFAULT_FILTERS_STATE = { | ||||
|   groupingMethod: -1, | ||||
|   keepInfoVisible: false, | ||||
|   combineRelatedContent: true, | ||||
|   searchName: "", | ||||
| }; | ||||
| 
 | ||||
| export default function Contents(props: Props): JSX.Element { | ||||
|   const { langui, contents, languages } = props; | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props extends AppStaticProps { | ||||
|   contents: NonNullable<GetContentsQuery["contents"]>["data"]; | ||||
| } | ||||
| 
 | ||||
| const Contents = ({ | ||||
|   langui, | ||||
|   contents, | ||||
|   languages, | ||||
|   ...otherProps | ||||
| }: Props): JSX.Element => { | ||||
|   const hoverable = useMediaHoverable(); | ||||
| 
 | ||||
|   const [groupingMethod, setGroupingMethod] = useState<number>( | ||||
|     defaultFiltersState.groupingMethod | ||||
|     DEFAULT_FILTERS_STATE.groupingMethod | ||||
|   ); | ||||
|   const [keepInfoVisible, setKeepInfoVisible] = useState( | ||||
|     defaultFiltersState.keepInfoVisible | ||||
|     DEFAULT_FILTERS_STATE.keepInfoVisible | ||||
|   ); | ||||
|   const [combineRelatedContent, setCombineRelatedContent] = useState( | ||||
|     defaultFiltersState.combineRelatedContent | ||||
|     DEFAULT_FILTERS_STATE.combineRelatedContent | ||||
|   ); | ||||
|   const [searchName, setSearchName] = useState( | ||||
|     DEFAULT_FILTERS_STATE.searchName | ||||
|   ); | ||||
|   const [searchName, setSearchName] = useState(defaultFiltersState.searchName); | ||||
| 
 | ||||
|   const effectiveCombineRelatedContent = useMemo( | ||||
|     () => (searchName.length > 1 ? false : combineRelatedContent), | ||||
| @ -126,10 +138,12 @@ export default function Contents(props: Props): JSX.Element { | ||||
|           text={langui.reset_all_filters} | ||||
|           icon={Icon.Replay} | ||||
|           onClick={() => { | ||||
|             setSearchName(defaultFiltersState.searchName); | ||||
|             setGroupingMethod(defaultFiltersState.groupingMethod); | ||||
|             setKeepInfoVisible(defaultFiltersState.keepInfoVisible); | ||||
|             setCombineRelatedContent(defaultFiltersState.combineRelatedContent); | ||||
|             setSearchName(DEFAULT_FILTERS_STATE.searchName); | ||||
|             setGroupingMethod(DEFAULT_FILTERS_STATE.groupingMethod); | ||||
|             setKeepInfoVisible(DEFAULT_FILTERS_STATE.keepInfoVisible); | ||||
|             setCombineRelatedContent( | ||||
|               DEFAULT_FILTERS_STATE.combineRelatedContent | ||||
|             ); | ||||
|           }} | ||||
|         /> | ||||
|       </SubPanel> | ||||
| @ -147,12 +161,9 @@ export default function Contents(props: Props): JSX.Element { | ||||
|   const contentPanel = useMemo( | ||||
|     () => ( | ||||
|       <ContentPanel width={ContentPanelWidthSizes.Full}> | ||||
|         {/* TODO: Add to langui */} | ||||
|         {groups.size === 0 && ( | ||||
|           <ContentPlaceholder | ||||
|             message={ | ||||
|               "No results. You can try changing or resetting the search parameters." | ||||
|             } | ||||
|             message={langui.no_results_message ?? "No results"} | ||||
|             icon={Icon.ChevronLeft} | ||||
|           /> | ||||
|         )} | ||||
| @ -259,14 +270,20 @@ export default function Contents(props: Props): JSX.Element { | ||||
|       subPanel={subPanel} | ||||
|       contentPanel={contentPanel} | ||||
|       subPanelIcon={Icon.Search} | ||||
|       {...props} | ||||
|       languages={languages} | ||||
|       langui={langui} | ||||
|       {...otherProps} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
| export default Contents; | ||||
| 
 | ||||
| export async function getStaticProps( | ||||
|   context: GetStaticPropsContext | ||||
| ): Promise<{ notFound: boolean } | { props: Props }> { | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps: GetStaticProps = async (context) => { | ||||
|   const sdk = getReadySdk(); | ||||
|   const contents = await sdk.getContents({ | ||||
|     language_code: context.locale ?? "en", | ||||
| @ -285,13 +302,20 @@ export async function getStaticProps( | ||||
|   return { | ||||
|     props: props, | ||||
|   }; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export function getGroups( | ||||
| /* | ||||
|  *                                      ╭───────────────────╮ | ||||
|  * ─────────────────────────────────────╯  PRIVATE METHODS  ╰─────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| type GroupContentItems = Map<string, Props["contents"]>; | ||||
| 
 | ||||
| export const getGroups = ( | ||||
|   langui: AppStaticProps["langui"], | ||||
|   groupByType: number, | ||||
|   items: Props["contents"] | ||||
| ): GroupContentItems { | ||||
| ): GroupContentItems => { | ||||
|   const groups: GroupContentItems = new Map(); | ||||
| 
 | ||||
|   switch (groupByType) { | ||||
| @ -347,14 +371,16 @@ export function getGroups( | ||||
|     } | ||||
|   } | ||||
|   return mapRemoveEmptyValues(groups); | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export function filterContents( | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const filterContents = ( | ||||
|   contents: Props["contents"], | ||||
|   combineRelatedContent: boolean, | ||||
|   searchName: string | ||||
| ): Props["contents"] { | ||||
|   return contents.filter((content) => { | ||||
| ): Props["contents"] => | ||||
|   contents.filter((content) => { | ||||
|     if ( | ||||
|       combineRelatedContent && | ||||
|       content.attributes?.group?.data?.attributes?.combine === true && | ||||
| @ -381,4 +407,3 @@ export function filterContents( | ||||
|     } | ||||
|     return true; | ||||
|   }); | ||||
| } | ||||
|  | ||||
| @ -10,16 +10,19 @@ import { DevGetContentsQuery } from "graphql/generated"; | ||||
| import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; | ||||
| import { getReadySdk } from "graphql/sdk"; | ||||
| import { filterDefined, filterHasAttributes } from "helpers/others"; | ||||
| 
 | ||||
| import { GetStaticPropsContext } from "next"; | ||||
| import { GetStaticProps } from "next"; | ||||
| import { useMemo } from "react"; | ||||
| 
 | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props extends AppStaticProps { | ||||
|   contents: DevGetContentsQuery; | ||||
| } | ||||
| 
 | ||||
| export default function CheckupContents(props: Props): JSX.Element { | ||||
|   const { contents } = props; | ||||
| const CheckupContents = ({ contents, ...otherProps }: Props): JSX.Element => { | ||||
|   const testReport = testingContent(contents); | ||||
| 
 | ||||
|   const contentPanel = useMemo( | ||||
| @ -82,13 +85,21 @@ export default function CheckupContents(props: Props): JSX.Element { | ||||
|   ); | ||||
| 
 | ||||
|   return ( | ||||
|     <AppLayout navTitle={"Checkup"} contentPanel={contentPanel} {...props} /> | ||||
|     <AppLayout | ||||
|       navTitle={"Checkup"} | ||||
|       contentPanel={contentPanel} | ||||
|       {...otherProps} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
| export default CheckupContents; | ||||
| 
 | ||||
| export async function getStaticProps( | ||||
|   context: GetStaticPropsContext | ||||
| ): Promise<{ notFound: boolean } | { props: Props }> { | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps: GetStaticProps = async (context) => { | ||||
|   const sdk = getReadySdk(); | ||||
|   const contents = await sdk.devGetContents(); | ||||
|   const props: Props = { | ||||
| @ -98,7 +109,12 @@ export async function getStaticProps( | ||||
|   return { | ||||
|     props: props, | ||||
|   }; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| /* | ||||
|  *                                      ╭───────────────────╮ | ||||
|  * ─────────────────────────────────────╯  PRIVATE METHODS  ╰─────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| type Report = { | ||||
|   title: string; | ||||
| @ -116,7 +132,7 @@ type ReportLine = { | ||||
|   frontendUrl: string; | ||||
| }; | ||||
| 
 | ||||
| function testingContent(contents: Props["contents"]): Report { | ||||
| const testingContent = (contents: Props["contents"]): Report => { | ||||
|   const report: Report = { | ||||
|     title: "Contents", | ||||
|     lines: [], | ||||
| @ -438,4 +454,4 @@ function testingContent(contents: Props["contents"]): Report { | ||||
|     } | ||||
|   }); | ||||
|   return report; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -13,15 +13,22 @@ import { | ||||
| import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; | ||||
| import { getReadySdk } from "graphql/sdk"; | ||||
| 
 | ||||
| import { GetStaticPropsContext } from "next"; | ||||
| import { GetStaticProps } from "next"; | ||||
| import { useMemo } from "react"; | ||||
| 
 | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props extends AppStaticProps { | ||||
|   libraryItems: DevGetLibraryItemsQuery; | ||||
| } | ||||
| 
 | ||||
| export default function CheckupLibraryItems(props: Props): JSX.Element { | ||||
|   const { libraryItems } = props; | ||||
| const CheckupLibraryItems = ({ | ||||
|   libraryItems, | ||||
|   ...otherProps | ||||
| }: Props): JSX.Element => { | ||||
|   const testReport = testingLibraryItem(libraryItems); | ||||
| 
 | ||||
|   const contentPanel = useMemo( | ||||
| @ -84,13 +91,21 @@ export default function CheckupLibraryItems(props: Props): JSX.Element { | ||||
|   ); | ||||
| 
 | ||||
|   return ( | ||||
|     <AppLayout navTitle={"Checkup"} contentPanel={contentPanel} {...props} /> | ||||
|     <AppLayout | ||||
|       navTitle={"Checkup"} | ||||
|       contentPanel={contentPanel} | ||||
|       {...otherProps} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
| export default CheckupLibraryItems; | ||||
| 
 | ||||
| export async function getStaticProps( | ||||
|   context: GetStaticPropsContext | ||||
| ): Promise<{ notFound: boolean } | { props: Props }> { | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps: GetStaticProps = async (context) => { | ||||
|   const sdk = getReadySdk(); | ||||
|   const libraryItems = await sdk.devGetLibraryItems(); | ||||
|   const props: Props = { | ||||
| @ -100,7 +115,12 @@ export async function getStaticProps( | ||||
|   return { | ||||
|     props: props, | ||||
|   }; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| /* | ||||
|  *                                      ╭───────────────────╮ | ||||
|  * ─────────────────────────────────────╯  PRIVATE METHODS  ╰─────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| type Report = { | ||||
|   title: string; | ||||
| @ -118,7 +138,7 @@ type ReportLine = { | ||||
|   frontendUrl: string; | ||||
| }; | ||||
| 
 | ||||
| function testingLibraryItem(libraryItems: Props["libraryItems"]): Report { | ||||
| const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => { | ||||
|   const report: Report = { | ||||
|     title: "Contents", | ||||
|     lines: [], | ||||
| @ -757,4 +777,4 @@ function testingLibraryItem(libraryItems: Props["libraryItems"]): Report { | ||||
|   }); | ||||
| 
 | ||||
|   return report; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import { AppLayout } from "components/AppLayout"; | ||||
| import { Button } from "components/Inputs/Button"; | ||||
| import { Markdawn } from "components/Markdown/Markdawn"; | ||||
| import { Markdawn, TableOfContents } from "components/Markdown/Markdawn"; | ||||
| import { | ||||
|   ContentPanel, | ||||
|   ContentPanelWidthSizes, | ||||
| @ -8,16 +8,19 @@ import { | ||||
| import { Popup } from "components/Popup"; | ||||
| import { ToolTip } from "components/ToolTip"; | ||||
| import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; | ||||
| 
 | ||||
| import { GetStaticPropsContext } from "next"; | ||||
| import { GetStaticProps } from "next"; | ||||
| import { useCallback, useMemo, useRef, useState } from "react"; | ||||
| import TurndownService from "turndown"; | ||||
| import { Icon } from "components/Ico"; | ||||
| import { TOC } from "components/Markdown/TOC"; | ||||
| 
 | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props extends AppStaticProps {} | ||||
| 
 | ||||
| export default function Editor(props: Props): JSX.Element { | ||||
| const Editor = ({ langui, ...otherProps }: Props): JSX.Element => { | ||||
|   const handleInput = useCallback((text: string) => { | ||||
|     setMarkdown(text); | ||||
|   }, []); | ||||
| @ -444,7 +447,7 @@ export default function Editor(props: Props): JSX.Element { | ||||
|         </div> | ||||
| 
 | ||||
|         <div className="mt-8"> | ||||
|           <TOC text={markdown} /> | ||||
|           <TableOfContents text={markdown} langui={langui} /> | ||||
|         </div> | ||||
|       </ContentPanel> | ||||
|     ), | ||||
| @ -453,6 +456,7 @@ export default function Editor(props: Props): JSX.Element { | ||||
|       converterOpened, | ||||
|       handleInput, | ||||
|       insert, | ||||
|       langui, | ||||
|       markdown, | ||||
|       preline, | ||||
|       toggleWrap, | ||||
| @ -464,18 +468,23 @@ export default function Editor(props: Props): JSX.Element { | ||||
|     <AppLayout | ||||
|       navTitle="Markdawn Editor" | ||||
|       contentPanel={contentPanel} | ||||
|       {...props} | ||||
|       langui={langui} | ||||
|       {...otherProps} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
| export default Editor; | ||||
| 
 | ||||
| export async function getStaticProps( | ||||
|   context: GetStaticPropsContext | ||||
| ): Promise<{ notFound: boolean } | { props: Props }> { | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps: GetStaticProps = async (context) => { | ||||
|   const props: Props = { | ||||
|     ...(await getAppStaticProps(context)), | ||||
|   }; | ||||
|   return { | ||||
|     props: props, | ||||
|   }; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -1,5 +1,4 @@ | ||||
| import { AppLayout } from "components/AppLayout"; | ||||
| import { Icon } from "components/Ico"; | ||||
| import { Button } from "components/Inputs/Button"; | ||||
| import { ButtonGroup } from "components/Inputs/ButtonGroup"; | ||||
| import { | ||||
| @ -8,42 +7,43 @@ import { | ||||
| } from "components/Panels/ContentPanel"; | ||||
| import { ToolTip } from "components/ToolTip"; | ||||
| import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; | ||||
| import { GetStaticPropsContext } from "next"; | ||||
| import { GetStaticProps } from "next"; | ||||
| import { useCallback, useMemo, useRef, useState } from "react"; | ||||
| 
 | ||||
| interface Props extends AppStaticProps {} | ||||
| /* | ||||
|  *                                         ╭─────────────╮ | ||||
|  * ────────────────────────────────────────╯  CONSTANTS  ╰────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| const SIZE_MULTIPLIER = 1000; | ||||
| 
 | ||||
| function replaceSelection( | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props extends AppStaticProps {} | ||||
| 
 | ||||
| const replaceSelection = ( | ||||
|   text: string, | ||||
|   selectionStart: number, | ||||
|   selectionEnd: number, | ||||
|   newSelectedText: string | ||||
| ) { | ||||
|   return ( | ||||
|     text.substring(0, selectionStart) + | ||||
|     newSelectedText + | ||||
|     text.substring(selectionEnd) | ||||
|   ); | ||||
| } | ||||
| ) => | ||||
|   text.substring(0, selectionStart) + | ||||
|   newSelectedText + | ||||
|   text.substring(selectionEnd); | ||||
| 
 | ||||
| function swapChar(char: string, swaps: string[]): string { | ||||
| const swapChar = (char: string, swaps: string[]): string => { | ||||
|   for (let index = 0; index < swaps.length; index++) { | ||||
|     if (char === swaps[index]) { | ||||
|       console.log( | ||||
|         "found it", | ||||
|         char, | ||||
|         " returning", | ||||
|         swaps[(index + 1) % swaps.length] | ||||
|       ); | ||||
|       return swaps[(index + 1) % swaps.length]; | ||||
|     } | ||||
|   } | ||||
|   return char; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export default function Transcript(props: Props): JSX.Element { | ||||
| const Transcript = (props: Props): JSX.Element => { | ||||
|   const [text, setText] = useState(""); | ||||
|   const [fontSize, setFontSize] = useState(1); | ||||
|   const [xOffset, setXOffset] = useState(0); | ||||
| @ -67,6 +67,114 @@ export default function Transcript(props: Props): JSX.Element { | ||||
|     } | ||||
|   }, []); | ||||
| 
 | ||||
|   const convertFullWidth = useCallback(() => { | ||||
|     if (textAreaRef.current) { | ||||
|       textAreaRef.current.value = textAreaRef.current.value | ||||
|         // Numbers
 | ||||
|         .replaceAll("0", "0") | ||||
|         .replaceAll("1", "1") | ||||
|         .replaceAll("2", "2") | ||||
|         .replaceAll("3", "3") | ||||
|         .replaceAll("4", "4") | ||||
|         .replaceAll("5", "5") | ||||
|         .replaceAll("6", "6") | ||||
|         .replaceAll("7", "7") | ||||
|         .replaceAll("8", "8") | ||||
|         .replaceAll("9", "9") | ||||
|         // Uppercase letters
 | ||||
|         .replaceAll("A", "A") | ||||
|         .replaceAll("B", "B") | ||||
|         .replaceAll("C", "C") | ||||
|         .replaceAll("D", "D") | ||||
|         .replaceAll("E", "E") | ||||
|         .replaceAll("F", "F") | ||||
|         .replaceAll("G", "G") | ||||
|         .replaceAll("H", "H") | ||||
|         .replaceAll("I", "I") | ||||
|         .replaceAll("J", "J") | ||||
|         .replaceAll("K", "K") | ||||
|         .replaceAll("L", "L") | ||||
|         .replaceAll("M", "M") | ||||
|         .replaceAll("N", "N") | ||||
|         .replaceAll("O", "O") | ||||
|         .replaceAll("P", "P") | ||||
|         .replaceAll("Q", "Q") | ||||
|         .replaceAll("R", "R") | ||||
|         .replaceAll("S", "S") | ||||
|         .replaceAll("T", "T") | ||||
|         .replaceAll("U", "U") | ||||
|         .replaceAll("V", "V") | ||||
|         .replaceAll("W", "W") | ||||
|         .replaceAll("X", "X") | ||||
|         .replaceAll("Y", "Y") | ||||
|         .replaceAll("Z", "Z") | ||||
|         // Lowercase letters
 | ||||
|         .replaceAll("a", "a") | ||||
|         .replaceAll("b", "b") | ||||
|         .replaceAll("c", "c") | ||||
|         .replaceAll("d", "d") | ||||
|         .replaceAll("e", "e") | ||||
|         .replaceAll("f", "f") | ||||
|         .replaceAll("g", "g") | ||||
|         .replaceAll("h", "h") | ||||
|         .replaceAll("i", "i") | ||||
|         .replaceAll("j", "j") | ||||
|         .replaceAll("k", "k") | ||||
|         .replaceAll("l", "l") | ||||
|         .replaceAll("m", "m") | ||||
|         .replaceAll("n", "n") | ||||
|         .replaceAll("o", "o") | ||||
|         .replaceAll("p", "p") | ||||
|         .replaceAll("q", "q") | ||||
|         .replaceAll("r", "r") | ||||
|         .replaceAll("s", "s") | ||||
|         .replaceAll("t", "t") | ||||
|         .replaceAll("u", "u") | ||||
|         .replaceAll("v", "v") | ||||
|         .replaceAll("w", "w") | ||||
|         .replaceAll("x", "x") | ||||
|         .replaceAll("y", "y") | ||||
|         .replaceAll("z", "z") | ||||
|         // Others
 | ||||
|         .replaceAll(" ", " ") | ||||
|         .replaceAll(",", ",") | ||||
|         .replaceAll(".", ".") | ||||
|         .replaceAll(":", ":") | ||||
|         .replaceAll(";", ";") | ||||
|         .replaceAll("!", "!") | ||||
|         .replaceAll("?", "?") | ||||
|         .replaceAll('"', """) | ||||
|         .replaceAll("'", "'") | ||||
|         .replaceAll("`", "`") | ||||
|         .replaceAll("^", "^") | ||||
|         .replaceAll("~", "~") | ||||
|         .replaceAll("_", "_") | ||||
|         .replaceAll("&", "&") | ||||
|         .replaceAll("@", "@") | ||||
|         .replaceAll("#", "#") | ||||
|         .replaceAll("%", "%") | ||||
|         .replaceAll("+", "+") | ||||
|         .replaceAll("-", "-") | ||||
|         .replaceAll("*", "*") | ||||
|         .replaceAll("=", "=") | ||||
|         .replaceAll("<", "<") | ||||
|         .replaceAll(">", ">") | ||||
|         .replaceAll("(", "(") | ||||
|         .replaceAll(")", ")") | ||||
|         .replaceAll("[", "[") | ||||
|         .replaceAll("]", "]") | ||||
|         .replaceAll("{", "{") | ||||
|         .replaceAll("}", "}") | ||||
|         .replaceAll("|", "|") | ||||
|         .replaceAll("$", "$") | ||||
|         .replaceAll("£", "£") | ||||
|         .replaceAll("¢", "¢") | ||||
|         .replaceAll("₩", "₩") | ||||
|         .replaceAll("¥", "¥"); | ||||
|       updateDisplayedText(); | ||||
|     } | ||||
|   }, [updateDisplayedText]); | ||||
| 
 | ||||
|   const convertPunctuation = useCallback(() => { | ||||
|     if (textAreaRef.current) { | ||||
|       textAreaRef.current.value = textAreaRef.current.value | ||||
| @ -76,7 +184,9 @@ export default function Transcript(props: Props): JSX.Element { | ||||
|         .replaceAll(".", "。") | ||||
|         .replaceAll(",", "、") | ||||
|         .replaceAll("?", "?") | ||||
|         .replaceAll("!", "!"); | ||||
|         .replaceAll("!", "!") | ||||
|         .replaceAll(":", ":") | ||||
|         .replaceAll("~", "~"); | ||||
|       updateDisplayedText(); | ||||
|     } | ||||
|   }, [updateDisplayedText]); | ||||
| @ -320,14 +430,19 @@ export default function Transcript(props: Props): JSX.Element { | ||||
|               } | ||||
|             ></input> | ||||
|           </div> | ||||
|           <ToolTip content="Automatically convert punctuations"> | ||||
|             <Button icon={Icon.QuestionMark} onClick={convertPunctuation} /> | ||||
|           <ToolTip content="Automatically convert Western punctuations to Japanese ones."> | ||||
|             <Button text=". ⟹ 。" onClick={convertPunctuation} /> | ||||
|           </ToolTip> | ||||
|           <Button text={"か ⟺ が"} onClick={toggleDakuten} /> | ||||
|           <Button text={"つ ⟺ っ"} onClick={toggleSmallForm} /> | ||||
|           <Button text={"。"} onClick={() => insert("。")} /> | ||||
|           <Button text={"?"} onClick={() => insert("?")} /> | ||||
|           <Button text={"!"} onClick={() => insert("!")} /> | ||||
|           <ToolTip content="Swap a kana for one of its variant (different diacritics)."> | ||||
|             <Button text="か ⟺ が" onClick={toggleDakuten} /> | ||||
|           </ToolTip> | ||||
|           <ToolTip content="Toggle a kana's small form"> | ||||
|             <Button text="つ ⟺ っ" onClick={toggleSmallForm} /> | ||||
|           </ToolTip> | ||||
|           <ToolTip content="Convert standard characters to their full width variant."> | ||||
|             <Button text="123 ⟹ 123" onClick={convertFullWidth} /> | ||||
|           </ToolTip> | ||||
| 
 | ||||
|           <ToolTip | ||||
|             content={ | ||||
|               <div className="grid gap-2"> | ||||
| @ -351,8 +466,8 @@ export default function Transcript(props: Props): JSX.Element { | ||||
|                 /> | ||||
|                 <ButtonGroup | ||||
|                   buttonsProps={[ | ||||
|                     { text: "(", onClick: () => insert("(") }, | ||||
|                     { text: ")", onClick: () => insert(")") }, | ||||
|                     { text: "〖", onClick: () => insert("〖") }, | ||||
|                     { text: "〗", onClick: () => insert("〗") }, | ||||
|                   ]} | ||||
|                 /> | ||||
|                 <ButtonGroup | ||||
| @ -361,6 +476,18 @@ export default function Transcript(props: Props): JSX.Element { | ||||
|                     { text: "〟", onClick: () => insert("〟") }, | ||||
|                   ]} | ||||
|                 /> | ||||
|                 <ButtonGroup | ||||
|                   buttonsProps={[ | ||||
|                     { text: "(", onClick: () => insert("(") }, | ||||
|                     { text: ")", onClick: () => insert(")") }, | ||||
|                   ]} | ||||
|                 /> | ||||
|                 <ButtonGroup | ||||
|                   buttonsProps={[ | ||||
|                     { text: "⦅", onClick: () => insert("⦅") }, | ||||
|                     { text: "⦆", onClick: () => insert("⦆") }, | ||||
|                   ]} | ||||
|                 /> | ||||
|                 <ButtonGroup | ||||
|                   buttonsProps={[ | ||||
|                     { text: "〈", onClick: () => insert("〈") }, | ||||
| @ -373,19 +500,57 @@ export default function Transcript(props: Props): JSX.Element { | ||||
|                     { text: "》", onClick: () => insert("》") }, | ||||
|                   ]} | ||||
|                 /> | ||||
|                 <ButtonGroup | ||||
|                   buttonsProps={[ | ||||
|                     { text: "{", onClick: () => insert("{") }, | ||||
|                     { text: "}", onClick: () => insert("}") }, | ||||
|                   ]} | ||||
|                 /> | ||||
|                 <ButtonGroup | ||||
|                   buttonsProps={[ | ||||
|                     { text: "[", onClick: () => insert("[") }, | ||||
|                     { text: "]", onClick: () => insert("]") }, | ||||
|                   ]} | ||||
|                 /> | ||||
|                 <ButtonGroup | ||||
|                   buttonsProps={[ | ||||
|                     { text: "〔", onClick: () => insert("〔") }, | ||||
|                     { text: "〕", onClick: () => insert("〕") }, | ||||
|                   ]} | ||||
|                 /> | ||||
|                 <ButtonGroup | ||||
|                   buttonsProps={[ | ||||
|                     { text: "〘", onClick: () => insert("〘") }, | ||||
|                     { text: "〙", onClick: () => insert("〙") }, | ||||
|                   ]} | ||||
|                 /> | ||||
|               </div> | ||||
|             } | ||||
|           > | ||||
|             <Button text={"Quotations"} /> | ||||
|           </ToolTip> | ||||
| 
 | ||||
|           <Button text={"⋯"} onClick={() => insert("⋯")} /> | ||||
|           <Button text={"※"} onClick={() => insert("※")} /> | ||||
|           <Button text={'" "'} onClick={() => insert(" ")} /> | ||||
|           <ToolTip | ||||
|             content={ | ||||
|               <div className="grid gap-2"> | ||||
|                 <Button text={"。"} onClick={() => insert("。")} /> | ||||
|                 <Button text={"?"} onClick={() => insert("?")} /> | ||||
|                 <Button text={"!"} onClick={() => insert("!")} /> | ||||
|                 <Button text={"⋯"} onClick={() => insert("⋯")} /> | ||||
|                 <Button text={"※"} onClick={() => insert("※")} /> | ||||
|                 <Button text={"♪"} onClick={() => insert("♪")} /> | ||||
|                 <Button text={"・"} onClick={() => insert("・")} /> | ||||
|                 <Button text={"〇"} onClick={() => insert("〇")} /> | ||||
|                 <Button text={'" "'} onClick={() => insert(" ")} /> | ||||
|               </div> | ||||
|             } | ||||
|           > | ||||
|             <Button text="Insert" /> | ||||
|           </ToolTip> | ||||
|         </div> | ||||
|       </ContentPanel> | ||||
|     ), | ||||
|     [ | ||||
|       convertFullWidth, | ||||
|       convertPunctuation, | ||||
|       fontSize, | ||||
|       insert, | ||||
| @ -407,15 +572,19 @@ export default function Transcript(props: Props): JSX.Element { | ||||
|       contentPanelScroolbar={false} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
| export default Transcript; | ||||
| 
 | ||||
| export async function getStaticProps( | ||||
|   context: GetStaticPropsContext | ||||
| ): Promise<{ notFound: boolean } | { props: Props }> { | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps: GetStaticProps = async (context) => { | ||||
|   const props: Props = { | ||||
|     ...(await getAppStaticProps(context)), | ||||
|   }; | ||||
|   return { | ||||
|     props: props, | ||||
|   }; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -4,30 +4,44 @@ import { | ||||
|   PostStaticProps, | ||||
| } from "graphql/getPostStaticProps"; | ||||
| 
 | ||||
| export default function Home(props: PostStaticProps): JSX.Element { | ||||
|   const { post, langui, languages, currencies } = props; | ||||
|   return ( | ||||
|     <PostPage | ||||
|       currencies={currencies} | ||||
|       languages={languages} | ||||
|       langui={langui} | ||||
|       post={post} | ||||
|       prependBody={ | ||||
|         <div className="grid w-full place-content-center place-items-center gap-5 text-center"> | ||||
|           <div | ||||
|             className="aspect-square w-32 bg-black [mask:url('/icons/accords.svg')] | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| const Home = ({ | ||||
|   post, | ||||
|   langui, | ||||
|   languages, | ||||
|   currencies, | ||||
| }: PostStaticProps): JSX.Element => ( | ||||
|   <PostPage | ||||
|     currencies={currencies} | ||||
|     languages={languages} | ||||
|     langui={langui} | ||||
|     post={post} | ||||
|     prependBody={ | ||||
|       <div className="grid w-full place-content-center place-items-center gap-5 text-center"> | ||||
|         <div | ||||
|           className="aspect-square w-32 bg-black [mask:url('/icons/accords.svg')] | ||||
|             [mask-size:contain] [mask-repeat:no-repeat] [mask-position:center] mobile:w-[50vw]" | ||||
|           /> | ||||
|           <h1 className="mb-0 text-5xl">Accord’s Library</h1> | ||||
|           <h2 className="-mt-5 text-xl"> | ||||
|             Discover • Analyze • Translate • Archive | ||||
|           </h2> | ||||
|         </div> | ||||
|       } | ||||
|       displayTitle={false} | ||||
|       displayLanguageSwitcher | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
|         /> | ||||
|         <h1 className="mb-0 text-5xl">Accord’s Library</h1> | ||||
|         <h2 className="-mt-5 text-xl"> | ||||
|           Discover • Analyze • Translate • Archive | ||||
|         </h2> | ||||
|       </div> | ||||
|     } | ||||
|     displayTitle={false} | ||||
|     displayLanguageSwitcher | ||||
|   /> | ||||
| ); | ||||
| 
 | ||||
| export default Home; | ||||
| 
 | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps = getPostStaticProps("home"); | ||||
|  | ||||
| @ -42,14 +42,9 @@ import { | ||||
|   isDefinedAndNotEmpty, | ||||
|   sortContent, | ||||
| } from "helpers/others"; | ||||
| 
 | ||||
| import { useLightBox } from "hooks/useLightBox"; | ||||
| import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange"; | ||||
| import { | ||||
|   GetStaticPathsContext, | ||||
|   GetStaticPathsResult, | ||||
|   GetStaticPropsContext, | ||||
| } from "next"; | ||||
| import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; | ||||
| import { Fragment, useCallback, useMemo, useState } from "react"; | ||||
| import { isUntangibleGroupItem } from "helpers/libraryItem"; | ||||
| import { useMediaHoverable } from "hooks/useMediaQuery"; | ||||
| @ -58,6 +53,12 @@ import { useToggle } from "hooks/useToggle"; | ||||
| import { Ico, Icon } from "components/Ico"; | ||||
| import { cJoin, cIf } from "helpers/className"; | ||||
| import { useSmartLanguage } from "hooks/useSmartLanguage"; | ||||
| import { getDescription } from "helpers/description"; | ||||
| 
 | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props extends AppStaticProps { | ||||
|   item: NonNullable< | ||||
| @ -70,8 +71,14 @@ interface Props extends AppStaticProps { | ||||
|   >["data"][number]["id"]; | ||||
| } | ||||
| 
 | ||||
| export default function LibrarySlug(props: Props): JSX.Element { | ||||
|   const { item, itemId, langui, currencies, languages } = props; | ||||
| const LibrarySlug = ({ | ||||
|   item, | ||||
|   itemId, | ||||
|   langui, | ||||
|   currencies, | ||||
|   languages, | ||||
|   ...otherProps | ||||
| }: Props): JSX.Element => { | ||||
|   const appLayout = useAppLayout(); | ||||
|   const hoverable = useMediaHoverable(); | ||||
|   const [openLightBox, LightBox] = useLightBox(); | ||||
| @ -192,12 +199,11 @@ export default function LibrarySlug(props: Props): JSX.Element { | ||||
|                 )} | ||||
|               </div> | ||||
| 
 | ||||
|               <PreviewCardCTAs | ||||
|                 id={itemId} | ||||
|                 displayCTAs={!isUntangibleGroupItem(item.metadata?.[0])} | ||||
|                 langui={langui} | ||||
|                 expand | ||||
|               /> | ||||
|               {!isUntangibleGroupItem(item.metadata?.[0]) && | ||||
|                 isDefinedAndNotEmpty(itemId) && ( | ||||
|                   <PreviewCardCTAs id={itemId} langui={langui} expand /> | ||||
|                 )} | ||||
| 
 | ||||
|               {item.descriptions?.[0] && ( | ||||
|                 <p className="text-justify"> | ||||
|                   {item.descriptions[0].description} | ||||
| @ -479,15 +485,9 @@ export default function LibrarySlug(props: Props): JSX.Element { | ||||
|                         position: "Bottom", | ||||
|                       }} | ||||
|                       infoAppend={ | ||||
|                         <PreviewCardCTAs | ||||
|                           id={subitem.id} | ||||
|                           langui={langui} | ||||
|                           displayCTAs={ | ||||
|                             !isUntangibleGroupItem( | ||||
|                               subitem.attributes.metadata?.[0] | ||||
|                             ) | ||||
|                           } | ||||
|                         /> | ||||
|                         !isUntangibleGroupItem( | ||||
|                           subitem.attributes.metadata?.[0] | ||||
|                         ) && <PreviewCardCTAs id={subitem.id} langui={langui} /> | ||||
|                       } | ||||
|                     /> | ||||
|                   </Fragment> | ||||
| @ -598,15 +598,25 @@ export default function LibrarySlug(props: Props): JSX.Element { | ||||
|       contentPanel={contentPanel} | ||||
|       subPanel={subPanel} | ||||
|       thumbnail={item.thumbnail?.data?.attributes ?? undefined} | ||||
|       description={item.descriptions?.[0]?.description ?? undefined} | ||||
|       {...props} | ||||
|       description={getDescription({ | ||||
|         langui, | ||||
|         description: item.descriptions?.[0]?.description, | ||||
|       })} | ||||
|       currencies={currencies} | ||||
|       languages={languages} | ||||
|       langui={langui} | ||||
|       {...otherProps} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
| export default LibrarySlug; | ||||
| 
 | ||||
| export async function getStaticProps( | ||||
|   context: GetStaticPropsContext | ||||
| ): Promise<{ notFound: boolean } | { props: Props }> { | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps: GetStaticProps = async (context) => { | ||||
|   const sdk = getReadySdk(); | ||||
|   const item = await sdk.getLibraryItem({ | ||||
|     slug: | ||||
| @ -625,11 +635,11 @@ export async function getStaticProps( | ||||
|   return { | ||||
|     props: props, | ||||
|   }; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export async function getStaticPaths( | ||||
|   context: GetStaticPathsContext | ||||
| ): Promise<GetStaticPathsResult> { | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const getStaticPaths: GetStaticPaths = async (context) => { | ||||
|   const sdk = getReadySdk(); | ||||
|   const libraryItems = await sdk.getLibraryItemsSlugs(); | ||||
|   const paths: GetStaticPathsResult["paths"] = []; | ||||
| @ -638,12 +648,16 @@ export async function getStaticPaths( | ||||
|       paths.push({ params: { slug: item.attributes.slug }, locale: local }) | ||||
|     ); | ||||
|   }); | ||||
| 
 | ||||
|   return { | ||||
|     paths, | ||||
|     fallback: "blocking", | ||||
|   }; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  PRIVATE COMPONENTS  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface ContentLineProps { | ||||
|   content?: { | ||||
| @ -665,17 +679,15 @@ interface ContentLineProps { | ||||
|   hasScanSet: boolean; | ||||
| } | ||||
| 
 | ||||
| export function ContentLine(props: ContentLineProps): JSX.Element { | ||||
|   const { | ||||
|     rangeStart, | ||||
|     content, | ||||
|     langui, | ||||
|     languages, | ||||
|     hasScanSet, | ||||
|     slug, | ||||
|     parentSlug, | ||||
|   } = props; | ||||
| 
 | ||||
| const ContentLine = ({ | ||||
|   rangeStart, | ||||
|   content, | ||||
|   langui, | ||||
|   languages, | ||||
|   hasScanSet, | ||||
|   slug, | ||||
|   parentSlug, | ||||
| }: ContentLineProps): JSX.Element => { | ||||
|   const [opened, setOpened] = useState(false); | ||||
|   const toggleOpened = useToggle(setOpened); | ||||
| 
 | ||||
| @ -753,6 +765,4 @@ export function ContentLine(props: ContentLineProps): JSX.Element { | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| 
 | ||||
|   return <></>; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -18,13 +18,14 @@ import { prettyinlineTitle, prettySlug } from "helpers/formatters"; | ||||
| import { filterHasAttributes, isDefined, sortContent } from "helpers/others"; | ||||
| 
 | ||||
| import { useLightBox } from "hooks/useLightBox"; | ||||
| import { | ||||
|   GetStaticPathsContext, | ||||
|   GetStaticPathsResult, | ||||
|   GetStaticPropsContext, | ||||
| } from "next"; | ||||
| import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; | ||||
| import { Fragment, useMemo } from "react"; | ||||
| 
 | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props extends AppStaticProps { | ||||
|   item: NonNullable< | ||||
|     NonNullable< | ||||
| @ -36,8 +37,12 @@ interface Props extends AppStaticProps { | ||||
|   >; | ||||
| } | ||||
| 
 | ||||
| export default function LibrarySlug(props: Props): JSX.Element { | ||||
|   const { item, langui, languages } = props; | ||||
| const LibrarySlug = ({ | ||||
|   item, | ||||
|   langui, | ||||
|   languages, | ||||
|   ...otherProps | ||||
| }: Props): JSX.Element => { | ||||
|   const [openLightBox, LightBox] = useLightBox(); | ||||
|   sortContent(item.contents); | ||||
| 
 | ||||
| @ -129,14 +134,20 @@ export default function LibrarySlug(props: Props): JSX.Element { | ||||
|       contentPanel={contentPanel} | ||||
|       subPanel={subPanel} | ||||
|       thumbnail={item.thumbnail?.data?.attributes ?? undefined} | ||||
|       {...props} | ||||
|       languages={languages} | ||||
|       langui={langui} | ||||
|       {...otherProps} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
| export default LibrarySlug; | ||||
| 
 | ||||
| export async function getStaticProps( | ||||
|   context: GetStaticPropsContext | ||||
| ): Promise<{ notFound: boolean } | { props: Props }> { | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps: GetStaticProps = async (context) => { | ||||
|   const sdk = getReadySdk(); | ||||
|   const item = await sdk.getLibraryItemScans({ | ||||
|     slug: | ||||
| @ -155,11 +166,11 @@ export async function getStaticProps( | ||||
|   return { | ||||
|     props: props, | ||||
|   }; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export async function getStaticPaths( | ||||
|   context: GetStaticPathsContext | ||||
| ): Promise<GetStaticPathsResult> { | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const getStaticPaths: GetStaticPaths = async (context) => { | ||||
|   const sdk = getReadySdk(); | ||||
|   const libraryItems = await sdk.getLibraryItemsSlugs({}); | ||||
|   const paths: GetStaticPathsResult["paths"] = []; | ||||
| @ -173,4 +184,4 @@ export async function getStaticPaths( | ||||
|     paths, | ||||
|     fallback: "blocking", | ||||
|   }; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -13,7 +13,7 @@ import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; | ||||
| import { getReadySdk } from "graphql/sdk"; | ||||
| import { prettyItemSubType } from "helpers/formatters"; | ||||
| import { LibraryItemUserStatus } from "helpers/types"; | ||||
| import { GetStaticPropsContext } from "next"; | ||||
| import { GetStaticProps } from "next"; | ||||
| import { Fragment, useState, useMemo } from "react"; | ||||
| import { Icon } from "components/Ico"; | ||||
| import { WithLabel } from "components/Inputs/WithLabel"; | ||||
| @ -38,11 +38,12 @@ import { | ||||
| } from "helpers/others"; | ||||
| import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder"; | ||||
| 
 | ||||
| interface Props extends AppStaticProps { | ||||
|   items: NonNullable<GetLibraryItemsPreviewQuery["libraryItems"]>["data"]; | ||||
| } | ||||
| /* | ||||
|  *                                         ╭─────────────╮ | ||||
|  * ────────────────────────────────────────╯  CONSTANTS  ╰────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| const defaultFiltersState = { | ||||
| const DEFAULT_FILTERS_STATE = { | ||||
|   searchName: "", | ||||
|   showSubitems: false, | ||||
|   showPrimaryItems: true, | ||||
| @ -53,33 +54,48 @@ const defaultFiltersState = { | ||||
|   filterUserStatus: undefined, | ||||
| }; | ||||
| 
 | ||||
| export default function Library(props: Props): JSX.Element { | ||||
|   const { langui, items: libraryItems, currencies } = props; | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props extends AppStaticProps { | ||||
|   items: NonNullable<GetLibraryItemsPreviewQuery["libraryItems"]>["data"]; | ||||
| } | ||||
| 
 | ||||
| const Library = ({ | ||||
|   langui, | ||||
|   items: libraryItems, | ||||
|   currencies, | ||||
|   ...otherProps | ||||
| }: Props): JSX.Element => { | ||||
|   const appLayout = useAppLayout(); | ||||
|   const hoverable = useMediaHoverable(); | ||||
| 
 | ||||
|   const [searchName, setSearchName] = useState(defaultFiltersState.searchName); | ||||
|   const [searchName, setSearchName] = useState( | ||||
|     DEFAULT_FILTERS_STATE.searchName | ||||
|   ); | ||||
|   const [showSubitems, setShowSubitems] = useState<boolean>( | ||||
|     defaultFiltersState.showSubitems | ||||
|     DEFAULT_FILTERS_STATE.showSubitems | ||||
|   ); | ||||
|   const [showPrimaryItems, setShowPrimaryItems] = useState<boolean>( | ||||
|     defaultFiltersState.showPrimaryItems | ||||
|     DEFAULT_FILTERS_STATE.showPrimaryItems | ||||
|   ); | ||||
|   const [showSecondaryItems, setShowSecondaryItems] = useState<boolean>( | ||||
|     defaultFiltersState.showSecondaryItems | ||||
|     DEFAULT_FILTERS_STATE.showSecondaryItems | ||||
|   ); | ||||
|   const [sortingMethod, setSortingMethod] = useState<number>( | ||||
|     defaultFiltersState.sortingMethod | ||||
|     DEFAULT_FILTERS_STATE.sortingMethod | ||||
|   ); | ||||
|   const [groupingMethod, setGroupingMethod] = useState<number>( | ||||
|     defaultFiltersState.groupingMethod | ||||
|     DEFAULT_FILTERS_STATE.groupingMethod | ||||
|   ); | ||||
|   const [keepInfoVisible, setKeepInfoVisible] = useState( | ||||
|     defaultFiltersState.keepInfoVisible | ||||
|     DEFAULT_FILTERS_STATE.keepInfoVisible | ||||
|   ); | ||||
|   const [filterUserStatus, setFilterUserStatus] = useState< | ||||
|     LibraryItemUserStatus | undefined | ||||
|   >(defaultFiltersState.filterUserStatus); | ||||
|   >(DEFAULT_FILTERS_STATE.filterUserStatus); | ||||
| 
 | ||||
|   const filteredItems = useMemo( | ||||
|     () => | ||||
| @ -193,7 +209,6 @@ export default function Library(props: Props): JSX.Element { | ||||
|           /> | ||||
|         )} | ||||
| 
 | ||||
|         {/* TODO: Add "All" to langui */} | ||||
|         <ButtonGroup | ||||
|           className="mt-4" | ||||
|           buttonsProps={[ | ||||
| @ -217,7 +232,7 @@ export default function Library(props: Props): JSX.Element { | ||||
|             }, | ||||
|             { | ||||
|               tooltip: langui.only_display_unmarked_items, | ||||
|               text: "All", | ||||
|               text: langui.all, | ||||
|               onClick: () => setFilterUserStatus(undefined), | ||||
|               active: isUndefined(filterUserStatus), | ||||
|             }, | ||||
| @ -229,14 +244,14 @@ export default function Library(props: Props): JSX.Element { | ||||
|           text={langui.reset_all_filters} | ||||
|           icon={Icon.Replay} | ||||
|           onClick={() => { | ||||
|             setSearchName(defaultFiltersState.searchName); | ||||
|             setShowSubitems(defaultFiltersState.showSubitems); | ||||
|             setShowPrimaryItems(defaultFiltersState.showPrimaryItems); | ||||
|             setShowSecondaryItems(defaultFiltersState.showSecondaryItems); | ||||
|             setSortingMethod(defaultFiltersState.sortingMethod); | ||||
|             setGroupingMethod(defaultFiltersState.groupingMethod); | ||||
|             setKeepInfoVisible(defaultFiltersState.keepInfoVisible); | ||||
|             setFilterUserStatus(defaultFiltersState.filterUserStatus); | ||||
|             setSearchName(DEFAULT_FILTERS_STATE.searchName); | ||||
|             setShowSubitems(DEFAULT_FILTERS_STATE.showSubitems); | ||||
|             setShowPrimaryItems(DEFAULT_FILTERS_STATE.showPrimaryItems); | ||||
|             setShowSecondaryItems(DEFAULT_FILTERS_STATE.showSecondaryItems); | ||||
|             setSortingMethod(DEFAULT_FILTERS_STATE.sortingMethod); | ||||
|             setGroupingMethod(DEFAULT_FILTERS_STATE.groupingMethod); | ||||
|             setKeepInfoVisible(DEFAULT_FILTERS_STATE.keepInfoVisible); | ||||
|             setFilterUserStatus(DEFAULT_FILTERS_STATE.filterUserStatus); | ||||
|           }} | ||||
|         /> | ||||
|       </SubPanel> | ||||
| @ -258,12 +273,9 @@ export default function Library(props: Props): JSX.Element { | ||||
|   const contentPanel = useMemo( | ||||
|     () => ( | ||||
|       <ContentPanel width={ContentPanelWidthSizes.Full}> | ||||
|         {/* TODO: Add to langui */} | ||||
|         {groups.size === 0 && ( | ||||
|           <ContentPlaceholder | ||||
|             message={ | ||||
|               "No results. You can try changing or resetting the search parameters." | ||||
|             } | ||||
|             message={langui.no_results_message ?? "No results"} | ||||
|             icon={Icon.ChevronLeft} | ||||
|           /> | ||||
|         )} | ||||
| @ -314,13 +326,9 @@ export default function Library(props: Props): JSX.Element { | ||||
|                       position: "Bottom", | ||||
|                     }} | ||||
|                     infoAppend={ | ||||
|                       <PreviewCardCTAs | ||||
|                         id={item.id} | ||||
|                         displayCTAs={ | ||||
|                           !isUntangibleGroupItem(item.attributes.metadata?.[0]) | ||||
|                         } | ||||
|                         langui={langui} | ||||
|                       /> | ||||
|                       !isUntangibleGroupItem(item.attributes.metadata?.[0]) && ( | ||||
|                         <PreviewCardCTAs id={item.id} langui={langui} /> | ||||
|                       ) | ||||
|                     } | ||||
|                   /> | ||||
|                 </Fragment> | ||||
| @ -339,14 +347,20 @@ export default function Library(props: Props): JSX.Element { | ||||
|       subPanel={subPanel} | ||||
|       contentPanel={contentPanel} | ||||
|       subPanelIcon={Icon.Search} | ||||
|       {...props} | ||||
|       currencies={currencies} | ||||
|       langui={langui} | ||||
|       {...otherProps} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
| export default Library; | ||||
| 
 | ||||
| export async function getStaticProps( | ||||
|   context: GetStaticPropsContext | ||||
| ): Promise<{ notFound: boolean } | { props: Props }> { | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps: GetStaticProps = async (context) => { | ||||
|   const sdk = getReadySdk(); | ||||
|   const items = await sdk.getLibraryItemsPreview({ | ||||
|     language_code: context.locale ?? "en", | ||||
| @ -359,4 +373,4 @@ export async function getStaticProps( | ||||
|   return { | ||||
|     props: props, | ||||
|   }; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -3,15 +3,19 @@ import { PanelHeader } from "components/PanelComponents/PanelHeader"; | ||||
| import { SubPanel } from "components/Panels/SubPanel"; | ||||
| import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; | ||||
| 
 | ||||
| import { GetStaticPropsContext } from "next"; | ||||
| import { GetStaticProps } from "next"; | ||||
| import { Icon } from "components/Ico"; | ||||
| import { useMemo } from "react"; | ||||
| 
 | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props extends AppStaticProps {} | ||||
| export default function Merch(props: Props): JSX.Element { | ||||
|   const { langui } = props; | ||||
|   const subPanel = useMemo( | ||||
|     () => ( | ||||
| const Merch = ({ langui, ...otherProps }: Props): JSX.Element => ( | ||||
|   <AppLayout | ||||
|     navTitle={langui.merch} | ||||
|     subPanel={ | ||||
|       <SubPanel> | ||||
|         <PanelHeader | ||||
|           icon={Icon.Store} | ||||
| @ -19,20 +23,23 @@ export default function Merch(props: Props): JSX.Element { | ||||
|           description={langui.merch_description} | ||||
|         /> | ||||
|       </SubPanel> | ||||
|     ), | ||||
|     [langui] | ||||
|   ); | ||||
|     } | ||||
|     langui={langui} | ||||
|     {...otherProps} | ||||
|   /> | ||||
| ); | ||||
| export default Merch; | ||||
| 
 | ||||
|   return <AppLayout navTitle={langui.merch} subPanel={subPanel} {...props} />; | ||||
| } | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export async function getStaticProps( | ||||
|   context: GetStaticPropsContext | ||||
| ): Promise<{ notFound: boolean } | { props: Props }> { | ||||
| export const getStaticProps: GetStaticProps = async (context) => { | ||||
|   const props: Props = { | ||||
|     ...(await getAppStaticProps(context)), | ||||
|   }; | ||||
|   return { | ||||
|     props: props, | ||||
|   }; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -6,45 +6,43 @@ import { | ||||
| } from "graphql/getPostStaticProps"; | ||||
| import { getReadySdk } from "graphql/sdk"; | ||||
| import { filterHasAttributes, isDefined } from "helpers/others"; | ||||
| import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; | ||||
| 
 | ||||
| import { | ||||
|   GetStaticPathsContext, | ||||
|   GetStaticPathsResult, | ||||
|   GetStaticPropsContext, | ||||
| } from "next"; | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props extends AppStaticProps, PostStaticProps {} | ||||
| 
 | ||||
| export default function LibrarySlug(props: Props): JSX.Element { | ||||
|   const { post, langui, languages, currencies } = props; | ||||
|   return ( | ||||
|     <PostPage | ||||
|       currencies={currencies} | ||||
|       languages={languages} | ||||
|       langui={langui} | ||||
|       post={post} | ||||
|       returnHref="/news" | ||||
|       returnTitle={langui.news} | ||||
|       displayCredits | ||||
|       displayThumbnailHeader | ||||
|       displayToc | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| const LibrarySlug = (props: Props): JSX.Element => ( | ||||
|   <PostPage | ||||
|     returnHref="/news" | ||||
|     returnTitle={props.langui.news} | ||||
|     displayCredits | ||||
|     displayThumbnailHeader | ||||
|     displayToc | ||||
|     {...props} | ||||
|   /> | ||||
| ); | ||||
| export default LibrarySlug; | ||||
| 
 | ||||
| export async function getStaticProps( | ||||
|   context: GetStaticPropsContext | ||||
| ): Promise<{ notFound: boolean } | { props: Props }> { | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps: GetStaticProps = async (context) => { | ||||
|   const slug = | ||||
|     context.params && isDefined(context.params.slug) | ||||
|       ? context.params.slug.toString() | ||||
|       : ""; | ||||
|   return await getPostStaticProps(slug)(context); | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export async function getStaticPaths( | ||||
|   context: GetStaticPathsContext | ||||
| ): Promise<GetStaticPathsResult> { | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const getStaticPaths: GetStaticPaths = async (context) => { | ||||
|   const sdk = getReadySdk(); | ||||
|   const posts = await sdk.getPostsSlugs(); | ||||
|   const paths: GetStaticPathsResult["paths"] = []; | ||||
| @ -58,4 +56,4 @@ export async function getStaticPaths( | ||||
|     paths, | ||||
|     fallback: "blocking", | ||||
|   }; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -12,7 +12,7 @@ import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; | ||||
| import { getReadySdk } from "graphql/sdk"; | ||||
| import { prettyDate, prettySlug } from "helpers/formatters"; | ||||
| 
 | ||||
| import { GetStaticPropsContext } from "next"; | ||||
| import { GetStaticProps } from "next"; | ||||
| import { Fragment, useMemo, useState } from "react"; | ||||
| import { Icon } from "components/Ico"; | ||||
| import { WithLabel } from "components/Inputs/WithLabel"; | ||||
| @ -21,24 +21,32 @@ import { Button } from "components/Inputs/Button"; | ||||
| import { useMediaHoverable } from "hooks/useMediaQuery"; | ||||
| import { filterHasAttributes } from "helpers/others"; | ||||
| 
 | ||||
| interface Props extends AppStaticProps { | ||||
|   posts: NonNullable<GetPostsPreviewQuery["posts"]>["data"]; | ||||
| } | ||||
| /* | ||||
|  *                                         ╭─────────────╮ | ||||
|  * ────────────────────────────────────────╯  CONSTANTS  ╰────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| const defaultFiltersState = { | ||||
| const DEFAULT_FILTERS_STATE = { | ||||
|   searchName: "", | ||||
|   keepInfoVisible: true, | ||||
| }; | ||||
| 
 | ||||
| export default function News(props: Props): JSX.Element { | ||||
|   const { langui } = props; | ||||
|   const posts = sortPosts(props.posts); | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props extends AppStaticProps { | ||||
|   posts: NonNullable<GetPostsPreviewQuery["posts"]>["data"]; | ||||
| } | ||||
| 
 | ||||
| const News = ({ langui, posts, ...otherProps }: Props): JSX.Element => { | ||||
|   const hoverable = useMediaHoverable(); | ||||
| 
 | ||||
|   const [searchName, setSearchName] = useState(defaultFiltersState.searchName); | ||||
| 
 | ||||
|   const [searchName, setSearchName] = useState( | ||||
|     DEFAULT_FILTERS_STATE.searchName | ||||
|   ); | ||||
|   const [keepInfoVisible, setKeepInfoVisible] = useState( | ||||
|     defaultFiltersState.keepInfoVisible | ||||
|     DEFAULT_FILTERS_STATE.keepInfoVisible | ||||
|   ); | ||||
| 
 | ||||
|   const filteredItems = useMemo( | ||||
| @ -76,8 +84,8 @@ export default function News(props: Props): JSX.Element { | ||||
|           text={langui.reset_all_filters} | ||||
|           icon={Icon.Replay} | ||||
|           onClick={() => { | ||||
|             setSearchName(defaultFiltersState.searchName); | ||||
|             setKeepInfoVisible(defaultFiltersState.keepInfoVisible); | ||||
|             setSearchName(DEFAULT_FILTERS_STATE.searchName); | ||||
|             setKeepInfoVisible(DEFAULT_FILTERS_STATE.keepInfoVisible); | ||||
|           }} | ||||
|         /> | ||||
|       </SubPanel> | ||||
| @ -127,14 +135,19 @@ export default function News(props: Props): JSX.Element { | ||||
|       subPanel={subPanel} | ||||
|       contentPanel={contentPanel} | ||||
|       subPanelIcon={Icon.Search} | ||||
|       {...props} | ||||
|       langui={langui} | ||||
|       {...otherProps} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
| export default News; | ||||
| 
 | ||||
| export async function getStaticProps( | ||||
|   context: GetStaticPropsContext | ||||
| ): Promise<{ notFound: boolean } | { props: Props }> { | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps: GetStaticProps = async (context) => { | ||||
|   const sdk = getReadySdk(); | ||||
|   const posts = await sdk.getPostsPreview({ | ||||
|     language_code: context.locale ?? "en", | ||||
| @ -142,25 +155,31 @@ export async function getStaticProps( | ||||
|   if (!posts.posts) return { notFound: true }; | ||||
|   const props: Props = { | ||||
|     ...(await getAppStaticProps(context)), | ||||
|     posts: posts.posts.data, | ||||
|     posts: sortPosts(posts.posts.data), | ||||
|   }; | ||||
|   return { | ||||
|     props: props, | ||||
|   }; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| function sortPosts(posts: Props["posts"]): Props["posts"] { | ||||
|   return posts | ||||
| /* | ||||
|  *                                      ╭───────────────────╮ | ||||
|  * ─────────────────────────────────────╯  PRIVATE METHODS  ╰─────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| const sortPosts = (posts: Props["posts"]): Props["posts"] => | ||||
|   posts | ||||
|     .sort((a, b) => { | ||||
|       const dateA = a.attributes?.date ? prettyDate(a.attributes.date) : "9999"; | ||||
|       const dateB = b.attributes?.date ? prettyDate(b.attributes.date) : "9999"; | ||||
|       return dateA.localeCompare(dateB); | ||||
|     }) | ||||
|     .reverse(); | ||||
| } | ||||
| 
 | ||||
| function filterItems(posts: Props["posts"], searchName: string) { | ||||
|   return posts.filter((post) => { | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| const filterItems = (posts: Props["posts"], searchName: string) => | ||||
|   posts.filter((post) => { | ||||
|     if (searchName.length > 1) { | ||||
|       if ( | ||||
|         post.attributes?.translations?.[0]?.title | ||||
| @ -173,4 +192,3 @@ function filterItems(posts: Props["posts"], searchName: string) { | ||||
|     } | ||||
|     return true; | ||||
|   }); | ||||
| } | ||||
|  | ||||
| @ -21,20 +21,19 @@ import { | ||||
| } from "helpers/others"; | ||||
| import { WikiPageWithTranslations } from "helpers/types"; | ||||
| import { useSmartLanguage } from "hooks/useSmartLanguage"; | ||||
| import { | ||||
|   GetStaticPathsContext, | ||||
|   GetStaticPathsResult, | ||||
|   GetStaticPropsContext, | ||||
| } from "next"; | ||||
| import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; | ||||
| import { useCallback, useMemo } from "react"; | ||||
| 
 | ||||
| interface Props extends AppStaticProps { | ||||
|   page: WikiPageWithTranslations; | ||||
| } | ||||
| 
 | ||||
| export default function WikiPage(props: Props): JSX.Element { | ||||
|   const { page, langui, languages } = props; | ||||
| 
 | ||||
| const WikiPage = ({ | ||||
|   page, | ||||
|   langui, | ||||
|   languages, | ||||
|   ...otherProps | ||||
| }: Props): JSX.Element => { | ||||
|   const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = | ||||
|     useSmartLanguage({ | ||||
|       items: page.translations, | ||||
| @ -143,14 +142,15 @@ export default function WikiPage(props: Props): JSX.Element { | ||||
|       navTitle={langui.news} | ||||
|       subPanel={subPanel} | ||||
|       contentPanel={contentPanel} | ||||
|       {...props} | ||||
|       languages={languages} | ||||
|       langui={langui} | ||||
|       {...otherProps} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
| export default WikiPage; | ||||
| 
 | ||||
| export async function getStaticProps( | ||||
|   context: GetStaticPropsContext | ||||
| ): Promise<{ notFound: boolean } | { props: Props }> { | ||||
| export const getStaticProps: GetStaticProps = async (context) => { | ||||
|   const sdk = getReadySdk(); | ||||
|   const slug = | ||||
|     context.params && isDefined(context.params.slug) | ||||
| @ -169,11 +169,9 @@ export async function getStaticProps( | ||||
|   return { | ||||
|     props: props, | ||||
|   }; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| export async function getStaticPaths( | ||||
|   context: GetStaticPathsContext | ||||
| ): Promise<GetStaticPathsResult> { | ||||
| export const getStaticPaths: GetStaticPaths = async (context) => { | ||||
|   const sdk = getReadySdk(); | ||||
|   const contents = await sdk.getWikiPagesSlugs(); | ||||
|   const paths: GetStaticPathsResult["paths"] = []; | ||||
| @ -189,4 +187,4 @@ export async function getStaticPaths( | ||||
|     paths, | ||||
|     fallback: "blocking", | ||||
|   }; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -13,9 +13,14 @@ import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; | ||||
| import { getReadySdk } from "graphql/sdk"; | ||||
| import { prettySlug } from "helpers/formatters"; | ||||
| import { filterHasAttributes, isDefined } from "helpers/others"; | ||||
| import { GetStaticPropsContext } from "next"; | ||||
| import { GetStaticProps } from "next"; | ||||
| import { Fragment, useMemo } from "react"; | ||||
| 
 | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props extends AppStaticProps { | ||||
|   chronologyItems: NonNullable< | ||||
|     GetChronologyItemsQuery["chronologyItems"] | ||||
| @ -23,9 +28,12 @@ interface Props extends AppStaticProps { | ||||
|   chronologyEras: NonNullable<GetErasQuery["chronologyEras"]>["data"]; | ||||
| } | ||||
| 
 | ||||
| export default function Chronology(props: Props): JSX.Element { | ||||
|   const { chronologyItems, chronologyEras, langui } = props; | ||||
| 
 | ||||
| const Chronology = ({ | ||||
|   chronologyItems, | ||||
|   chronologyEras, | ||||
|   langui, | ||||
|   ...otherProps | ||||
| }: Props): JSX.Element => { | ||||
|   // Group by year the Chronology items
 | ||||
|   const chronologyItemYearGroups = useMemo(() => { | ||||
|     const memo: Props["chronologyItems"][number][][][] = []; | ||||
| @ -141,14 +149,19 @@ export default function Chronology(props: Props): JSX.Element { | ||||
|       navTitle={langui.chronology} | ||||
|       contentPanel={contentPanel} | ||||
|       subPanel={subPanel} | ||||
|       {...props} | ||||
|       langui={langui} | ||||
|       {...otherProps} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
| export default Chronology; | ||||
| 
 | ||||
| export async function getStaticProps( | ||||
|   context: GetStaticPropsContext | ||||
| ): Promise<{ notFound: boolean } | { props: Props }> { | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps: GetStaticProps = async (context) => { | ||||
|   const sdk = getReadySdk(); | ||||
|   const chronologyItems = await sdk.getChronologyItems({ | ||||
|     language_code: context.locale ?? "en", | ||||
| @ -166,4 +179,4 @@ export async function getStaticProps( | ||||
|   return { | ||||
|     props: props, | ||||
|   }; | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -4,7 +4,7 @@ import { PanelHeader } from "components/PanelComponents/PanelHeader"; | ||||
| import { SubPanel } from "components/Panels/SubPanel"; | ||||
| import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; | ||||
| 
 | ||||
| import { GetStaticPropsContext } from "next"; | ||||
| import { GetStaticProps } from "next"; | ||||
| import { Icon } from "components/Ico"; | ||||
| import { getReadySdk } from "graphql/sdk"; | ||||
| import { GetWikiPagesPreviewsQuery } from "graphql/generated"; | ||||
| @ -23,23 +23,38 @@ import { useMediaHoverable } from "hooks/useMediaQuery"; | ||||
| import { filterHasAttributes } from "helpers/others"; | ||||
| import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder"; | ||||
| 
 | ||||
| interface Props extends AppStaticProps { | ||||
|   pages: NonNullable<GetWikiPagesPreviewsQuery["wikiPages"]>["data"]; | ||||
| } | ||||
| /* | ||||
|  *                                         ╭─────────────╮ | ||||
|  * ────────────────────────────────────────╯  CONSTANTS  ╰────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| const defaultFiltersState = { | ||||
| const DEFAULT_FILTERS_STATE = { | ||||
|   searchName: "", | ||||
|   keepInfoVisible: true, | ||||
| }; | ||||
| 
 | ||||
| export default function Wiki(props: Props): JSX.Element { | ||||
|   const { langui, languages } = props; | ||||
|   const pages = sortPages(props.pages); | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
|  * ──────────────────────────────────────────╯  PAGE  ╰───────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props extends AppStaticProps { | ||||
|   pages: NonNullable<GetWikiPagesPreviewsQuery["wikiPages"]>["data"]; | ||||
| } | ||||
| 
 | ||||
| const Wiki = ({ | ||||
|   langui, | ||||
|   languages, | ||||
|   pages, | ||||
|   ...otherProps | ||||
| }: Props): JSX.Element => { | ||||
|   const hoverable = useMediaHoverable(); | ||||
| 
 | ||||
|   const [searchName, setSearchName] = useState(defaultFiltersState.searchName); | ||||
|   const [searchName, setSearchName] = useState( | ||||
|     DEFAULT_FILTERS_STATE.searchName | ||||
|   ); | ||||
|   const [keepInfoVisible, setKeepInfoVisible] = useState( | ||||
|     defaultFiltersState.keepInfoVisible | ||||
|     DEFAULT_FILTERS_STATE.keepInfoVisible | ||||
|   ); | ||||
| 
 | ||||
|   const filteredPages = useMemo( | ||||
| @ -77,14 +92,13 @@ export default function Wiki(props: Props): JSX.Element { | ||||
|           text={langui.reset_all_filters} | ||||
|           icon={Icon.Replay} | ||||
|           onClick={() => { | ||||
|             setSearchName(defaultFiltersState.searchName); | ||||
|             setKeepInfoVisible(defaultFiltersState.keepInfoVisible); | ||||
|             setSearchName(DEFAULT_FILTERS_STATE.searchName); | ||||
|             setKeepInfoVisible(DEFAULT_FILTERS_STATE.keepInfoVisible); | ||||
|           }} | ||||
|         /> | ||||
|         <HorizontalLine /> | ||||
| 
 | ||||
|         {/* TODO: Langui */} | ||||
|         <p className="mb-4 font-headers text-xl">Special Pages</p> | ||||
|         <p className="mb-4 font-headers text-xl">{langui.special_pages}</p> | ||||
| 
 | ||||
|         <NavOption title={langui.chronology} url="/wiki/chronology" border /> | ||||
|       </SubPanel> | ||||
| @ -99,12 +113,9 @@ export default function Wiki(props: Props): JSX.Element { | ||||
|           className="grid grid-cols-2 items-end gap-8 | ||||
|         desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))] mobile:gap-4" | ||||
|         > | ||||
|           {/* TODO: Add to langui */} | ||||
|           {filteredPages.length === 0 && ( | ||||
|             <ContentPlaceholder | ||||
|               message={ | ||||
|                 "No results. You can try changing or resetting the search parameters." | ||||
|               } | ||||
|               message={langui.no_results_message ?? "No results"} | ||||
|               icon={Icon.ChevronLeft} | ||||
|             /> | ||||
|           )} | ||||
| @ -137,7 +148,7 @@ export default function Wiki(props: Props): JSX.Element { | ||||
|         </div> | ||||
|       </ContentPanel> | ||||
|     ), | ||||
|     [filteredPages, keepInfoVisible, languages] | ||||
|     [filteredPages, keepInfoVisible, languages, langui] | ||||
|   ); | ||||
| 
 | ||||
|   return ( | ||||
| @ -146,36 +157,48 @@ export default function Wiki(props: Props): JSX.Element { | ||||
|       subPanel={subPanel} | ||||
|       contentPanel={contentPanel} | ||||
|       subPanelIcon={Icon.Search} | ||||
|       {...props} | ||||
|       languages={languages} | ||||
|       langui={langui} | ||||
|       {...otherProps} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| }; | ||||
| export default Wiki; | ||||
| 
 | ||||
| export async function getStaticProps( | ||||
|   context: GetStaticPropsContext | ||||
| ): Promise<{ notFound: boolean } | { props: Props }> { | ||||
| /* | ||||
|  *                                    ╭──────────────────────╮ | ||||
|  * ───────────────────────────────────╯  NEXT DATA FETCHING  ╰────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| export const getStaticProps: GetStaticProps = async (context) => { | ||||
|   const sdk = getReadySdk(); | ||||
|   const pages = await sdk.getWikiPagesPreviews({}); | ||||
|   if (!pages.wikiPages?.data) return { notFound: true }; | ||||
|   const props: Props = { | ||||
|     ...(await getAppStaticProps(context)), | ||||
|     pages: pages.wikiPages.data, | ||||
|     pages: sortPages(pages.wikiPages.data), | ||||
|   }; | ||||
|   return { | ||||
|     props: props, | ||||
|   }; | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| function sortPages(pages: Props["pages"]): Props["pages"] { | ||||
|   return pages.sort((a, b) => { | ||||
| /* | ||||
|  *                                      ╭───────────────────╮ | ||||
|  * ─────────────────────────────────────╯  PRIVATE METHODS  ╰─────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| const sortPages = (pages: Props["pages"]): Props["pages"] => | ||||
|   pages.sort((a, b) => { | ||||
|     const slugA = a.attributes?.slug ?? ""; | ||||
|     const slugB = b.attributes?.slug ?? ""; | ||||
|     return slugA.localeCompare(slugB); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| function filterPages(posts: Props["pages"], searchName: string) { | ||||
|   return posts.filter((post) => { | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| const filterPages = (posts: Props["pages"], searchName: string) => | ||||
|   posts.filter((post) => { | ||||
|     if (searchName.length > 1) { | ||||
|       if ( | ||||
|         post.attributes?.translations?.[0]?.title | ||||
| @ -188,4 +211,3 @@ function filterPages(posts: Props["pages"], searchName: string) { | ||||
|     } | ||||
|     return true; | ||||
|   }); | ||||
| } | ||||
|  | ||||
| @ -1,9 +1,7 @@ | ||||
| const plugin = require("tailwindcss/plugin"); | ||||
| const { breaks, colors, fonts, fontFamilies } = require("./design.config.js"); | ||||
| 
 | ||||
| function rgb(color) { | ||||
|   return [color.r, color.g, color.b].join(" "); | ||||
| } | ||||
| const rgb = (color) => [color.r, color.g, color.b].join(" "); | ||||
| 
 | ||||
| /** @type {import('tailwindcss').Config} */ | ||||
| module.exports = { | ||||
| @ -38,7 +36,7 @@ module.exports = { | ||||
|     }, | ||||
|   }, | ||||
|   plugins: [ | ||||
|     plugin(function ({ addUtilities }) { | ||||
|     plugin(({ addUtilities }) => { | ||||
|       addUtilities({ | ||||
|         ".set-theme-light": { | ||||
|           "--theme-color-highlight": rgb(colors.light.hightlight), | ||||
| @ -63,7 +61,7 @@ module.exports = { | ||||
|       }); | ||||
|     }), | ||||
| 
 | ||||
|     plugin(function ({ addUtilities }) { | ||||
|     plugin(({ addUtilities }) => { | ||||
|       addUtilities({ | ||||
|         ".set-theme-font-standard": { | ||||
|           "--theme-font-body": fontFamilies.standard.body, | ||||
| @ -78,7 +76,7 @@ module.exports = { | ||||
|       }); | ||||
|     }), | ||||
| 
 | ||||
|     plugin(function ({ addVariant, e }) { | ||||
|     plugin(({ addVariant, e }) => { | ||||
|       addVariant("webkit-scrollbar", ({ modifySelectors, separator }) => { | ||||
|         modifySelectors(({ className }) => { | ||||
|           return `.${e( | ||||
| @ -89,7 +87,7 @@ module.exports = { | ||||
|     }), | ||||
| 
 | ||||
|     // Colored Dropshadow
 | ||||
|     plugin(function ({ addUtilities }) { | ||||
|     plugin(({ addUtilities }) => { | ||||
|       addUtilities({ | ||||
|         ".drop-shadow-shade-md": { | ||||
|           filter: ` | ||||
| @ -131,7 +129,7 @@ module.exports = { | ||||
|       }); | ||||
|     }), | ||||
| 
 | ||||
|     plugin(function ({ addUtilities }) { | ||||
|     plugin(({ addUtilities }) => { | ||||
|       addUtilities({ | ||||
|         ".linearbg-obi": { | ||||
|           background: `linear-gradient(
 | ||||
| @ -145,7 +143,7 @@ module.exports = { | ||||
|       }); | ||||
|     }), | ||||
| 
 | ||||
|     plugin(function ({ addUtilities }) { | ||||
|     plugin(({ addUtilities }) => { | ||||
|       addUtilities({ | ||||
|         ".break-words": { | ||||
|           "word-break": "break-word", | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 DrMint
						DrMint