Added terminal stuff
This commit is contained in:
		
							parent
							
								
									7b303f81ad
								
							
						
					
					
						commit
						1b347ad357
					
				
							
								
								
									
										326
									
								
								src/components/Cli/Terminal.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										326
									
								
								src/components/Cli/Terminal.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,326 @@ | ||||
| import { useCallback, useEffect, useMemo, useRef, useState } from "react"; | ||||
| import { useRouter } from "next/router"; | ||||
| import { useAppLayout } from "contexts/AppLayoutContext"; | ||||
| import { cJoin, cIf } from "helpers/className"; | ||||
| import { useTerminalContext } from "contexts/TerminalContext"; | ||||
| import { isDefined, isDefinedAndNotEmpty } from "helpers/others"; | ||||
| 
 | ||||
| /* | ||||
|  *                                         ╭─────────────╮ | ||||
|  * ────────────────────────────────────────╯  CONSTANTS  ╰────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| const LINE_PREFIX = "root@accords-library.com:"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  * ───────────────────────────────────────╯  COMPONENT  ╰─────────────────────────────────────────── | ||||
|  */ | ||||
| 
 | ||||
| interface Props { | ||||
|   childrenPaths: string[]; | ||||
|   parentPath: string; | ||||
|   content?: string; | ||||
| } | ||||
| 
 | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const Terminal = ({ | ||||
|   parentPath, | ||||
|   childrenPaths: propsChildrenPaths, | ||||
|   content, | ||||
| }: Props): JSX.Element => { | ||||
|   const [childrenPaths, setChildrenPaths] = useState(propsChildrenPaths); | ||||
|   const { darkMode } = useAppLayout(); | ||||
|   const { previousCommands, previousLines, setPreviousCommands, setPreviousLines } = | ||||
|     useTerminalContext(); | ||||
|   const [line, setLine] = useState(""); | ||||
|   const [displayCurrentLine, setDisplayCurrentLine] = useState(true); | ||||
|   const [previousCommandIndex, setPreviousCommandIndex] = useState(0); | ||||
|   const [carretPosition, setCarretPosition] = useState(0); | ||||
|   const router = useRouter(); | ||||
|   const { setPlayerName } = useAppLayout(); | ||||
|   const [isTextAreaFocused, setIsTextAreaFocused] = useState(false); | ||||
| 
 | ||||
|   const terminalInputRef = useRef<HTMLTextAreaElement>(null); | ||||
|   const terminalWindowRef = useRef<HTMLDivElement>(null); | ||||
| 
 | ||||
|   router.events.on("routeChangeComplete", () => { | ||||
|     terminalInputRef.current?.focus(); | ||||
|     setDisplayCurrentLine(true); | ||||
|   }); | ||||
| 
 | ||||
|   const onRouteChangeRequest = useCallback( | ||||
|     (newPath: string) => { | ||||
|       if (newPath !== router.asPath) { | ||||
|         setDisplayCurrentLine(false); | ||||
|         router.push(newPath); | ||||
|       } | ||||
|     }, | ||||
|     [router] | ||||
|   ); | ||||
| 
 | ||||
|   const prependLine = useCallback( | ||||
|     (text: string) => `${LINE_PREFIX}${router.asPath}# ${text}`, | ||||
|     [router.asPath] | ||||
|   ); | ||||
| 
 | ||||
|   type Command = { | ||||
|     key: string; | ||||
|     description: string; | ||||
|     handle: (currentLine: string, parameters: string) => string[]; | ||||
|   }; | ||||
|   const commands = useMemo<Command[]>(() => { | ||||
|     const result: Command[] = [ | ||||
|       { | ||||
|         key: "ls", | ||||
|         description: "List directory contents", | ||||
|         handle: (currentLine) => [ | ||||
|           ...previousLines, | ||||
|           prependLine(currentLine), | ||||
|           childrenPaths.join(" "), | ||||
|         ], | ||||
|       }, | ||||
| 
 | ||||
|       { | ||||
|         key: "clear", | ||||
|         description: "Clear the terminal screen", | ||||
|         handle: () => [], | ||||
|       }, | ||||
| 
 | ||||
|       { | ||||
|         key: "cat", | ||||
|         description: "Concatenate files and print on the standard output", | ||||
|         handle: (currentLine) => [ | ||||
|           ...previousLines, | ||||
|           prependLine(currentLine), | ||||
|           isDefinedAndNotEmpty(content) ? `\n${content}\n` : `-bash: cat: Nothing to display`, | ||||
|         ], | ||||
|       }, | ||||
| 
 | ||||
|       { | ||||
|         key: "reboot", | ||||
|         description: "Reboot the machine", | ||||
|         handle: () => { | ||||
|           setPlayerName(""); | ||||
|           return []; | ||||
|         }, | ||||
|       }, | ||||
| 
 | ||||
|       { | ||||
|         key: "rm", | ||||
|         description: "Remove files or directories", | ||||
|         handle: (currentLine, parameters) => { | ||||
|           console.log(parameters); | ||||
|           if (parameters.startsWith("-r ")) { | ||||
|             const folder = parameters.slice("-r ".length); | ||||
|             if (childrenPaths.includes(folder)) { | ||||
|               setChildrenPaths((current) => current.filter((path) => path !== folder)); | ||||
|               return [...previousLines, prependLine(currentLine)]; | ||||
|             } else if (folder === "*") { | ||||
|               setChildrenPaths([]); | ||||
|               return [...previousLines, prependLine(currentLine)]; | ||||
|             } else if (folder === "") { | ||||
|               return [ | ||||
|                 ...previousLines, | ||||
|                 prependLine(currentLine), | ||||
|                 `rm: missing operand\nTry 'rm -r <path>' to remove a folder`, | ||||
|               ]; | ||||
|             } | ||||
|             return [ | ||||
|               ...previousLines, | ||||
|               prependLine(currentLine), | ||||
|               `rm: cannot remove '${folder}': No such file or directory`, | ||||
|             ]; | ||||
|           } | ||||
|           return [ | ||||
|             ...previousLines, | ||||
|             prependLine(currentLine), | ||||
|             `rm: missing operand\nTry 'rm -r <path>' to remove a folder`, | ||||
|           ]; | ||||
|         }, | ||||
|       }, | ||||
| 
 | ||||
|       { | ||||
|         key: "help", | ||||
|         description: "Display this list", | ||||
|         handle: (currentLine) => [ | ||||
|           ...previousLines, | ||||
|           prependLine(currentLine), | ||||
|           ` | ||||
|           GNU bash, version 5.1.4(1)-release (x86_64-pc-linux-gnu) | ||||
|           These shell commands are defined internally.  Type 'help' to see this list. | ||||
| 
 | ||||
|           ${result.map((command) => `${command.key}: ${command.description}`).join("\n")} | ||||
| 
 | ||||
|           `,
 | ||||
|         ], | ||||
|       }, | ||||
| 
 | ||||
|       { | ||||
|         key: "cd", | ||||
|         description: "Change the shell working directory", | ||||
|         handle: (currentLine, parameters) => { | ||||
|           const newLines = []; | ||||
|           switch (parameters) { | ||||
|             case "..": { | ||||
|               onRouteChangeRequest(parentPath); | ||||
|               break; | ||||
|             } | ||||
|             case "/": { | ||||
|               onRouteChangeRequest("/"); | ||||
|               break; | ||||
|             } | ||||
|             case ".": { | ||||
|               break; | ||||
|             } | ||||
|             default: { | ||||
|               if (childrenPaths.includes(parameters)) { | ||||
|                 onRouteChangeRequest(`${router.asPath === "/" ? "" : router.asPath}/${parameters}`); | ||||
|               } else { | ||||
|                 newLines.push(`-bash: cd: ${parameters}: No such file or directory`); | ||||
|               } | ||||
|               break; | ||||
|             } | ||||
|           } | ||||
|           return [...previousLines, prependLine(currentLine), ...newLines]; | ||||
|         }, | ||||
|       }, | ||||
|     ]; | ||||
| 
 | ||||
|     return [ | ||||
|       ...result, | ||||
|       { | ||||
|         key: "", | ||||
|         description: "Unhandled command", | ||||
|         handle: (currentLine) => [ | ||||
|           ...previousLines, | ||||
|           prependLine(currentLine), | ||||
|           `-bash: ${currentLine}: command not found`, | ||||
|         ], | ||||
|       }, | ||||
|     ]; | ||||
|   }, [ | ||||
|     childrenPaths, | ||||
|     parentPath, | ||||
|     content, | ||||
|     onRouteChangeRequest, | ||||
|     prependLine, | ||||
|     previousLines, | ||||
|     router.asPath, | ||||
|     setPlayerName, | ||||
|   ]); | ||||
| 
 | ||||
|   const onNewLine = useCallback( | ||||
|     (newLine: string) => { | ||||
|       for (const command of commands) { | ||||
|         if (newLine.startsWith(command.key)) { | ||||
|           setPreviousLines(command.handle(newLine, newLine.slice(command.key.length + 1))); | ||||
|           setPreviousCommands([newLine, ...previousCommands]); | ||||
|           return; | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     [commands, previousCommands, setPreviousCommands, setPreviousLines] | ||||
|   ); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     if (terminalWindowRef.current) { | ||||
|       terminalWindowRef.current.scrollTo({ | ||||
|         top: terminalWindowRef.current.scrollHeight, | ||||
|       }); | ||||
|     } | ||||
|   }, [line]); | ||||
| 
 | ||||
|   return ( | ||||
|     <div | ||||
|       className={cJoin( | ||||
|         "h-screen overflow-hidden bg-light text-black set-theme-font-standard", | ||||
|         cIf(darkMode, "set-theme-dark", "set-theme-light") | ||||
|       )}> | ||||
|       <div | ||||
|         ref={terminalWindowRef} | ||||
|         className="h-full overflow-scroll scroll-auto p-6 | ||||
|         [scrollbar-width:none] webkit-scrollbar:w-0"> | ||||
|         {previousLines.map((previousLine, index) => ( | ||||
|           <p key={index} className="whitespace-pre-line font-realmono"> | ||||
|             {previousLine} | ||||
|           </p> | ||||
|         ))} | ||||
| 
 | ||||
|         <div className="relative"> | ||||
|           <textarea | ||||
|             className="absolute -top-1 -left-6 -right-6 w-screen rounded-none opacity-0" | ||||
|             spellCheck={false} | ||||
|             autoCapitalize="none" | ||||
|             autoCorrect="off" | ||||
|             placeholder="placeholder" | ||||
|             ref={terminalInputRef} | ||||
|             value={line} | ||||
|             onSelect={() => { | ||||
|               if (terminalInputRef.current) { | ||||
|                 setCarretPosition(terminalInputRef.current.selectionStart); | ||||
|                 terminalInputRef.current.selectionEnd = terminalInputRef.current.selectionStart; | ||||
|               } | ||||
|             }} | ||||
|             onBlur={() => setIsTextAreaFocused(false)} | ||||
|             onFocus={() => setIsTextAreaFocused(true)} | ||||
|             onKeyDown={(event) => { | ||||
|               if (event.key === "ArrowUp") { | ||||
|                 event.preventDefault(); | ||||
|                 let newPreviousCommandIndex = previousCommandIndex; | ||||
|                 if (previousCommandIndex < previousCommands.length - 1) { | ||||
|                   newPreviousCommandIndex += 1; | ||||
|                 } | ||||
|                 setPreviousCommandIndex(newPreviousCommandIndex); | ||||
|                 const previousCommand = previousCommands[newPreviousCommandIndex]; | ||||
|                 if (isDefined(previousCommand)) { | ||||
|                   setLine(previousCommand); | ||||
|                   setCarretPosition(previousCommand.length); | ||||
|                 } | ||||
|               } | ||||
|               if (event.key === "ArrowDown") { | ||||
|                 event.preventDefault(); | ||||
|                 let newPreviousCommandIndex = previousCommandIndex; | ||||
|                 if (previousCommandIndex > 0) { | ||||
|                   newPreviousCommandIndex -= 1; | ||||
|                 } | ||||
|                 setPreviousCommandIndex(newPreviousCommandIndex); | ||||
|                 const previousCommand = previousCommands[newPreviousCommandIndex]; | ||||
|                 if (isDefined(previousCommand)) { | ||||
|                   setLine(previousCommand); | ||||
|                   setCarretPosition(previousCommand.length); | ||||
|                 } | ||||
|               } | ||||
|             }} | ||||
|             onInput={() => { | ||||
|               if (terminalInputRef.current) { | ||||
|                 if (terminalInputRef.current.value.includes("\n")) { | ||||
|                   setLine(""); | ||||
|                   onNewLine(line); | ||||
|                 } else { | ||||
|                   setLine(terminalInputRef.current.value); | ||||
|                 } | ||||
|                 setCarretPosition(terminalInputRef.current.selectionStart); | ||||
|               } | ||||
|             }} | ||||
|           /> | ||||
|           {displayCurrentLine && ( | ||||
|             <p className="whitespace-normal font-realmono"> | ||||
|               {prependLine("")} | ||||
|               {line.slice(0, carretPosition)} | ||||
|               <span | ||||
|                 className={cJoin( | ||||
|                   "whitespace-pre font-realmono", | ||||
|                   cIf(isTextAreaFocused, "animation-carret border-b-2 border-black") | ||||
|                 )}> | ||||
|                 {line[carretPosition] ?? " "} | ||||
|               </span> | ||||
|               {line.slice(carretPosition + 1)} | ||||
|             </p> | ||||
|           )} | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
| @ -267,7 +267,10 @@ export const AppContextProvider = (props: Props): JSX.Element => { | ||||
|   }, [router.events, setConfigPanelOpen, setMainPanelOpen, setSubPanelOpen]); | ||||
| 
 | ||||
|   useLayoutEffect(() => { | ||||
|     document.getElementsByTagName("html")[0].style.fontSize = `${fontSize * 100}%`; | ||||
|     const html = document.getElementsByTagName("html")[0]; | ||||
|     if (isDefined(html)) { | ||||
|       html.style.fontSize = `${fontSize * 100}%`; | ||||
|     } | ||||
|   }, [fontSize]); | ||||
| 
 | ||||
|   useScrollIntoView(); | ||||
|  | ||||
							
								
								
									
										35
									
								
								src/contexts/TerminalContext.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/contexts/TerminalContext.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | ||||
| import React, { ReactNode, useContext, useState } from "react"; | ||||
| import { RequiredNonNullable } from "types/types"; | ||||
| 
 | ||||
| interface TerminalState { | ||||
|   previousLines: string[]; | ||||
|   previousCommands: string[]; | ||||
|   setPreviousLines: React.Dispatch<React.SetStateAction<TerminalState["previousLines"]>>; | ||||
|   setPreviousCommands: React.Dispatch<React.SetStateAction<TerminalState["previousCommands"]>>; | ||||
| } | ||||
| 
 | ||||
| const initialState: RequiredNonNullable<TerminalState> = { | ||||
|   previousLines: [], | ||||
|   previousCommands: [], | ||||
|   setPreviousLines: () => null, | ||||
|   setPreviousCommands: () => null, | ||||
| }; | ||||
| 
 | ||||
| const TerminalContext = React.createContext<TerminalState>(initialState); | ||||
| 
 | ||||
| export const useTerminalContext = (): TerminalState => useContext(TerminalContext); | ||||
| 
 | ||||
| interface Props { | ||||
|   children: ReactNode; | ||||
| } | ||||
| 
 | ||||
| export const TerminalContextProvider = ({ children }: Props): JSX.Element => { | ||||
|   const [previousLines, setPreviousLines] = useState(initialState.previousLines); | ||||
|   const [previousCommands, setPreviousCommands] = useState(initialState.previousCommands); | ||||
|   return ( | ||||
|     <TerminalContext.Provider | ||||
|       value={{ previousCommands, previousLines, setPreviousCommands, setPreviousLines }}> | ||||
|       {children} | ||||
|     </TerminalContext.Provider> | ||||
|   ); | ||||
| }; | ||||
| @ -25,7 +25,7 @@ export const getDescription = ( | ||||
|   return result; | ||||
| }; | ||||
| 
 | ||||
| const prettyMarkdown = (markdown: string): string => | ||||
| export const prettyMarkdown = (markdown: string): string => | ||||
|   markdown.replace(/[*]/gu, "").replace(/[_]/gu, ""); | ||||
| 
 | ||||
| const prettyChip = (items: (string | undefined)[]): string => | ||||
|  | ||||
							
								
								
									
										18
									
								
								src/helpers/terminal.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/helpers/terminal.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| import { isDefinedAndNotEmpty } from "./others"; | ||||
| 
 | ||||
| export const prettyTerminalUnderlinedTitle = (string: string | null | undefined): string => | ||||
|   isDefinedAndNotEmpty(string) | ||||
|     ? `\n\n${string} | ||||
| ${"‾".repeat(string.length)} | ||||
| ` | ||||
|     : ""; | ||||
| 
 | ||||
| export const prettyTerminalBoxedTitle = (string: string | null | undefined): string => | ||||
|   isDefinedAndNotEmpty(string) | ||||
|     ? `╭${"─".repeat(string.length + 2)}╮
 | ||||
|        │ ${string} │ | ||||
|        ╰${"─".repeat(string.length + 2)}╯` | ||||
|     : ""; | ||||
| 
 | ||||
| export const prettyTerminalTitle = (title: string | null | undefined): string => | ||||
|   `\n\n-= ${title?.toUpperCase()} =-`; | ||||
							
								
								
									
										6
									
								
								src/hooks/useIsTerminalMode.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/hooks/useIsTerminalMode.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| import { useAppLayout } from "contexts/AppLayoutContext"; | ||||
| 
 | ||||
| export const useIsTerminalMode = (): boolean => { | ||||
|   const { playerName } = useAppLayout(); | ||||
|   return playerName === "root"; | ||||
| }; | ||||
| @ -8,10 +8,13 @@ import "@fontsource/zen-maru-gothic/900.css"; | ||||
| import type { AppProps } from "next/app"; | ||||
| import { AppContextProvider } from "contexts/AppLayoutContext"; | ||||
| import "tailwind.css"; | ||||
| import { TerminalContextProvider } from "contexts/TerminalContext"; | ||||
| 
 | ||||
| const AccordsLibraryApp = (props: AppProps): JSX.Element => ( | ||||
|   <AppContextProvider> | ||||
|     <props.Component {...props.pageProps} /> | ||||
|     <TerminalContextProvider> | ||||
|       <props.Component {...props.pageProps} /> | ||||
|     </TerminalContextProvider> | ||||
|   </AppContextProvider> | ||||
| ); | ||||
| export default AccordsLibraryApp; | ||||
|  | ||||
| @ -381,7 +381,7 @@ const Editor = (props: Props): JSX.Element => { | ||||
|                 handleInput(textarea.value); | ||||
|               }} | ||||
|               className="h-[70vh] w-full rounded-xl bg-mid !bg-opacity-40 p-8 | ||||
|             font-mono text-black outline-none" | ||||
|               font-mono text-black outline-none" | ||||
|               value={markdown} | ||||
|               title="Input textarea" | ||||
|             /> | ||||
|  | ||||
| @ -2,6 +2,8 @@ import { PostPage } from "components/PostPage"; | ||||
| import { useAppLayout } from "contexts/AppLayoutContext"; | ||||
| import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps"; | ||||
| import { getOpenGraph } from "helpers/openGraph"; | ||||
| import { Terminal } from "components/Cli/Terminal"; | ||||
| import { useIsTerminalMode } from "hooks/useIsTerminalMode"; | ||||
| 
 | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
| @ -10,6 +12,26 @@ import { getOpenGraph } from "helpers/openGraph"; | ||||
| 
 | ||||
| const Home = ({ ...otherProps }: PostStaticProps): JSX.Element => { | ||||
|   const { langui } = useAppLayout(); | ||||
|   const isTerminalMode = useIsTerminalMode(); | ||||
| 
 | ||||
|   if (isTerminalMode) { | ||||
|     return ( | ||||
|       <Terminal | ||||
|         parentPath="/" | ||||
|         childrenPaths={[ | ||||
|           "library", | ||||
|           "contents", | ||||
|           "wiki", | ||||
|           "chronicles", | ||||
|           "news", | ||||
|           "gallery", | ||||
|           "archives", | ||||
|           "about-us", | ||||
|         ]} | ||||
|       /> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   return ( | ||||
|     <PostPage | ||||
|       {...otherProps} | ||||
|  | ||||
| @ -1,9 +1,16 @@ | ||||
| import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; | ||||
| import { NextRouter, useRouter } from "next/router"; | ||||
| import { PostPage } from "components/PostPage"; | ||||
| import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps"; | ||||
| import { getReadySdk } from "graphql/sdk"; | ||||
| import { filterHasAttributes, isDefined } from "helpers/others"; | ||||
| import { filterHasAttributes, isDefined, isDefinedAndNotEmpty } from "helpers/others"; | ||||
| import { useAppLayout } from "contexts/AppLayoutContext"; | ||||
| import { useIsTerminalMode } from "hooks/useIsTerminalMode"; | ||||
| import { Terminal } from "components/Cli/Terminal"; | ||||
| import { PostWithTranslations } from "types/types"; | ||||
| import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales"; | ||||
| import { prettyTerminalBoxedTitle } from "helpers/terminal"; | ||||
| import { prettyMarkdown } from "helpers/description"; | ||||
| 
 | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
| @ -14,6 +21,19 @@ interface Props extends PostStaticProps {} | ||||
| 
 | ||||
| const LibrarySlug = (props: Props): JSX.Element => { | ||||
|   const { langui } = useAppLayout(); | ||||
|   const isTerminalMode = useIsTerminalMode(); | ||||
|   const router = useRouter(); | ||||
| 
 | ||||
|   if (isTerminalMode) { | ||||
|     return ( | ||||
|       <Terminal | ||||
|         parentPath={"/news"} | ||||
|         childrenPaths={[]} | ||||
|         content={terminalPostPage(props.post, router)} | ||||
|       /> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   return ( | ||||
|     <PostPage | ||||
|       returnHref="/news" | ||||
| @ -55,3 +75,30 @@ export const getStaticPaths: GetStaticPaths = async (context) => { | ||||
|     fallback: "blocking", | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| const terminalPostPage = (post: PostWithTranslations, router: NextRouter): string => { | ||||
|   let result = ""; | ||||
|   if (router.locales && router.locale) { | ||||
|     const selectedTranslation = staticSmartLanguage({ | ||||
|       items: filterHasAttributes(post.translations, ["language.data.attributes.code"] as const), | ||||
|       languageExtractor: (item) => item.language.data.attributes.code, | ||||
|       preferredLanguages: getDefaultPreferredLanguages(router.locale, router.locales), | ||||
|     }); | ||||
| 
 | ||||
|     if (selectedTranslation) { | ||||
|       result += prettyTerminalBoxedTitle(selectedTranslation.title); | ||||
|       if (isDefinedAndNotEmpty(selectedTranslation.excerpt)) { | ||||
|         result += "\n\n"; | ||||
|         result += prettyMarkdown(selectedTranslation.excerpt); | ||||
|       } | ||||
|       if (isDefinedAndNotEmpty(selectedTranslation.body)) { | ||||
|         result += "\n\n"; | ||||
|         result += prettyMarkdown(selectedTranslation.body); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   result += "\n\n"; | ||||
| 
 | ||||
|   return result; | ||||
| }; | ||||
|  | ||||
| @ -25,6 +25,8 @@ import { useIsContentPanelAtLeast } from "hooks/useContainerQuery"; | ||||
| import { useAppLayout } from "contexts/AppLayoutContext"; | ||||
| import { getLangui } from "graphql/fetchLocalData"; | ||||
| import { sendAnalytics } from "helpers/analytics"; | ||||
| import { useIsTerminalMode } from "hooks/useIsTerminalMode"; | ||||
| import { Terminal } from "components/Cli/Terminal"; | ||||
| 
 | ||||
| /* | ||||
|  *                                         ╭─────────────╮ | ||||
| @ -55,6 +57,7 @@ const News = ({ posts, ...otherProps }: Props): JSX.Element => { | ||||
|     toggle: toggleKeepInfoVisible, | ||||
|     setValue: setKeepInfoVisible, | ||||
|   } = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible); | ||||
|   const isTerminalMode = useIsTerminalMode(); | ||||
| 
 | ||||
|   const subPanel = useMemo( | ||||
|     () => ( | ||||
| @ -153,6 +156,17 @@ const News = ({ posts, ...otherProps }: Props): JSX.Element => { | ||||
|     [keepInfoVisible, posts, searchName, isContentPanelAtLeast4xl] | ||||
|   ); | ||||
| 
 | ||||
|   if (isTerminalMode) { | ||||
|     return ( | ||||
|       <Terminal | ||||
|         parentPath="/" | ||||
|         childrenPaths={filterHasAttributes(posts, ["attributes"] as const).map( | ||||
|           (post) => post.attributes.slug | ||||
|         )} | ||||
|       /> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   return ( | ||||
|     <AppLayout | ||||
|       subPanel={subPanel} | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| import { useCallback, useMemo } from "react"; | ||||
| import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; | ||||
| import { useRouter } from "next/router"; | ||||
| import { AppLayout, AppLayoutRequired } from "components/AppLayout"; | ||||
| import { Chip } from "components/Chip"; | ||||
| import { HorizontalLine } from "components/HorizontalLine"; | ||||
| @ -22,6 +23,9 @@ import { cIf, cJoin } from "helpers/className"; | ||||
| import { useIs3ColumnsLayout } from "hooks/useContainerQuery"; | ||||
| import { useAppLayout } from "contexts/AppLayoutContext"; | ||||
| import { getLangui } from "graphql/fetchLocalData"; | ||||
| import { Terminal } from "components/Cli/Terminal"; | ||||
| import { prettyTerminalBoxedTitle, prettyTerminalUnderlinedTitle } from "helpers/terminal"; | ||||
| import { useIsTerminalMode } from "hooks/useIsTerminalMode"; | ||||
| 
 | ||||
| /* | ||||
|  *                                           ╭────────╮ | ||||
| @ -34,6 +38,8 @@ interface Props extends AppLayoutRequired { | ||||
| 
 | ||||
| const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => { | ||||
|   const { langui } = useAppLayout(); | ||||
|   const router = useRouter(); | ||||
|   const isTerminalMode = useIsTerminalMode(); | ||||
|   const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({ | ||||
|     items: page.translations, | ||||
|     languageExtractor: useCallback( | ||||
| @ -190,6 +196,48 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => { | ||||
|     ] | ||||
|   ); | ||||
| 
 | ||||
|   if (isTerminalMode) { | ||||
|     return ( | ||||
|       <Terminal | ||||
|         childrenPaths={[]} | ||||
|         parentPath={"/wiki"} | ||||
|         content={`${prettyTerminalBoxedTitle( | ||||
|           `${selectedTranslation?.title}${ | ||||
|             selectedTranslation?.aliases && selectedTranslation.aliases.length > 0 | ||||
|               ? ` (${selectedTranslation.aliases.map((alias) => alias?.alias).join(", ")})` | ||||
|               : "" | ||||
|           }` | ||||
|         )}${ | ||||
|           isDefinedAndNotEmpty(selectedTranslation?.summary) | ||||
|             ? `${prettyTerminalUnderlinedTitle(langui.summary)}${selectedTranslation?.summary}` | ||||
|             : "" | ||||
|         }${ | ||||
|           page.definitions && page.definitions.length > 0 | ||||
|             ? `${filterHasAttributes(page.definitions, ["translations"] as const).map( | ||||
|                 (definition, index) => | ||||
|                   `${prettyTerminalUnderlinedTitle(`${langui.definition} ${index + 1}`)}${ | ||||
|                     staticSmartLanguage({ | ||||
|                       items: filterHasAttributes(definition.translations, [ | ||||
|                         "language.data.attributes.code", | ||||
|                       ] as const), | ||||
|                       languageExtractor: (item) => item.language.data.attributes.code, | ||||
|                       preferredLanguages: getDefaultPreferredLanguages( | ||||
|                         router.locale ?? "en", | ||||
|                         router.locales ?? ["en"] | ||||
|                       ), | ||||
|                     })?.definition | ||||
|                   }` | ||||
|               )}` | ||||
|             : "" | ||||
|         }${ | ||||
|           isDefinedAndNotEmpty(selectedTranslation?.body?.body) | ||||
|             ? `\n\n${selectedTranslation?.body?.body}` | ||||
|             : "\n" | ||||
|         }`}
 | ||||
|       /> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   return <AppLayout subPanel={subPanel} contentPanel={contentPanel} {...otherProps} />; | ||||
| }; | ||||
| export default WikiPage; | ||||
| @ -208,7 +256,7 @@ export const getStaticProps: GetStaticProps = async (context) => { | ||||
|     language_code: context.locale ?? "en", | ||||
|     slug: slug, | ||||
|   }); | ||||
|   if (!page.wikiPages?.data[0].attributes?.translations) return { notFound: true }; | ||||
|   if (!page.wikiPages?.data[0]?.attributes?.translations) return { notFound: true }; | ||||
| 
 | ||||
|   const { title, description } = (() => { | ||||
|     const chipsGroups = { | ||||
|  | ||||
| @ -27,6 +27,8 @@ import { cIf } from "helpers/className"; | ||||
| import { useAppLayout } from "contexts/AppLayoutContext"; | ||||
| import { getLangui } from "graphql/fetchLocalData"; | ||||
| import { sendAnalytics } from "helpers/analytics"; | ||||
| import { Terminal } from "components/Cli/Terminal"; | ||||
| import { useIsTerminalMode } from "hooks/useIsTerminalMode"; | ||||
| 
 | ||||
| /* | ||||
|  *                                         ╭─────────────╮ | ||||
| @ -52,6 +54,7 @@ const Wiki = ({ pages, ...otherProps }: Props): JSX.Element => { | ||||
|   const hoverable = useDeviceSupportsHover(); | ||||
|   const { langui } = useAppLayout(); | ||||
|   const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl"); | ||||
|   const isTerminalMode = useIsTerminalMode(); | ||||
| 
 | ||||
|   const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName); | ||||
| 
 | ||||
| @ -230,6 +233,17 @@ const Wiki = ({ pages, ...otherProps }: Props): JSX.Element => { | ||||
|     [groupingFunction, keepInfoVisible, pages, searchName, isContentPanelAtLeast4xl] | ||||
|   ); | ||||
| 
 | ||||
|   if (isTerminalMode) { | ||||
|     return ( | ||||
|       <Terminal | ||||
|         parentPath="/" | ||||
|         childrenPaths={filterHasAttributes(pages, ["attributes"] as const).map( | ||||
|           (page) => page.attributes.slug | ||||
|         )} | ||||
|       /> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   return ( | ||||
|     <AppLayout | ||||
|       subPanel={subPanel} | ||||
|  | ||||
| @ -172,6 +172,22 @@ input[type="submit"] { | ||||
|   [background-blend-mode:var(--theme-texture-dots-blend)]; | ||||
| } | ||||
| 
 | ||||
| /* ANIMATION */ | ||||
| 
 | ||||
| .animation-carret { | ||||
|   animation: blink 1s step-end infinite; | ||||
| } | ||||
| 
 | ||||
| @keyframes blink { | ||||
|   from, | ||||
|   to { | ||||
|     border-bottom-style: solid; | ||||
|   } | ||||
|   50% { | ||||
|     border-bottom-style: none; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /* DEBUGGING */ | ||||
| .false { | ||||
|   @apply border-2 border-[red] text-[red] outline-dotted outline-2 outline-[red]; | ||||
|  | ||||
| @ -20,6 +20,7 @@ module.exports = { | ||||
|       body: "var(--theme-font-body)", | ||||
|       headers: "var(--theme-font-headers)", | ||||
|       mono: "var(--theme-font-mono)", | ||||
|       realmono: `ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace`, | ||||
|       ...fonts, | ||||
|     }, | ||||
|     screens: { | ||||
|  | ||||
| @ -15,7 +15,9 @@ | ||||
|     "isolatedModules": true, | ||||
|     "jsx": "preserve", | ||||
|     "incremental": true, | ||||
|     "baseUrl": "src" | ||||
|     "baseUrl": "src", | ||||
| 
 | ||||
|     "noUncheckedIndexedAccess": true | ||||
|   }, | ||||
|   "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], | ||||
|   "exclude": ["node_modules"] | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 DrMint
						DrMint