Added terminal stuff
This commit is contained in:
parent
7b303f81ad
commit
1b347ad357
|
@ -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]);
|
}, [router.events, setConfigPanelOpen, setMainPanelOpen, setSubPanelOpen]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
document.getElementsByTagName("html")[0].style.fontSize = `${fontSize * 100}%`;
|
const html = document.getElementsByTagName("html")[0];
|
||||||
|
if (isDefined(html)) {
|
||||||
|
html.style.fontSize = `${fontSize * 100}%`;
|
||||||
|
}
|
||||||
}, [fontSize]);
|
}, [fontSize]);
|
||||||
|
|
||||||
useScrollIntoView();
|
useScrollIntoView();
|
||||||
|
|
|
@ -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;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
const prettyMarkdown = (markdown: string): string =>
|
export const prettyMarkdown = (markdown: string): string =>
|
||||||
markdown.replace(/[*]/gu, "").replace(/[_]/gu, "");
|
markdown.replace(/[*]/gu, "").replace(/[_]/gu, "");
|
||||||
|
|
||||||
const prettyChip = (items: (string | undefined)[]): string =>
|
const prettyChip = (items: (string | undefined)[]): string =>
|
||||||
|
|
|
@ -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()} =-`;
|
|
@ -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 type { AppProps } from "next/app";
|
||||||
import { AppContextProvider } from "contexts/AppLayoutContext";
|
import { AppContextProvider } from "contexts/AppLayoutContext";
|
||||||
import "tailwind.css";
|
import "tailwind.css";
|
||||||
|
import { TerminalContextProvider } from "contexts/TerminalContext";
|
||||||
|
|
||||||
const AccordsLibraryApp = (props: AppProps): JSX.Element => (
|
const AccordsLibraryApp = (props: AppProps): JSX.Element => (
|
||||||
<AppContextProvider>
|
<AppContextProvider>
|
||||||
<props.Component {...props.pageProps} />
|
<TerminalContextProvider>
|
||||||
|
<props.Component {...props.pageProps} />
|
||||||
|
</TerminalContextProvider>
|
||||||
</AppContextProvider>
|
</AppContextProvider>
|
||||||
);
|
);
|
||||||
export default AccordsLibraryApp;
|
export default AccordsLibraryApp;
|
||||||
|
|
|
@ -381,7 +381,7 @@ const Editor = (props: Props): JSX.Element => {
|
||||||
handleInput(textarea.value);
|
handleInput(textarea.value);
|
||||||
}}
|
}}
|
||||||
className="h-[70vh] w-full rounded-xl bg-mid !bg-opacity-40 p-8
|
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}
|
value={markdown}
|
||||||
title="Input textarea"
|
title="Input textarea"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -2,6 +2,8 @@ import { PostPage } from "components/PostPage";
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
|
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
|
||||||
import { getOpenGraph } from "helpers/openGraph";
|
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 Home = ({ ...otherProps }: PostStaticProps): JSX.Element => {
|
||||||
const { langui } = useAppLayout();
|
const { langui } = useAppLayout();
|
||||||
|
const isTerminalMode = useIsTerminalMode();
|
||||||
|
|
||||||
|
if (isTerminalMode) {
|
||||||
|
return (
|
||||||
|
<Terminal
|
||||||
|
parentPath="/"
|
||||||
|
childrenPaths={[
|
||||||
|
"library",
|
||||||
|
"contents",
|
||||||
|
"wiki",
|
||||||
|
"chronicles",
|
||||||
|
"news",
|
||||||
|
"gallery",
|
||||||
|
"archives",
|
||||||
|
"about-us",
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PostPage
|
<PostPage
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||||
|
import { NextRouter, useRouter } from "next/router";
|
||||||
import { PostPage } from "components/PostPage";
|
import { PostPage } from "components/PostPage";
|
||||||
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
|
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
import { filterHasAttributes, isDefined } from "helpers/others";
|
import { filterHasAttributes, isDefined, isDefinedAndNotEmpty } from "helpers/others";
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
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 LibrarySlug = (props: Props): JSX.Element => {
|
||||||
const { langui } = useAppLayout();
|
const { langui } = useAppLayout();
|
||||||
|
const isTerminalMode = useIsTerminalMode();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
if (isTerminalMode) {
|
||||||
|
return (
|
||||||
|
<Terminal
|
||||||
|
parentPath={"/news"}
|
||||||
|
childrenPaths={[]}
|
||||||
|
content={terminalPostPage(props.post, router)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PostPage
|
<PostPage
|
||||||
returnHref="/news"
|
returnHref="/news"
|
||||||
|
@ -55,3 +75,30 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
|
||||||
fallback: "blocking",
|
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 { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
import { getLangui } from "graphql/fetchLocalData";
|
import { getLangui } from "graphql/fetchLocalData";
|
||||||
import { sendAnalytics } from "helpers/analytics";
|
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,
|
toggle: toggleKeepInfoVisible,
|
||||||
setValue: setKeepInfoVisible,
|
setValue: setKeepInfoVisible,
|
||||||
} = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
|
} = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
|
||||||
|
const isTerminalMode = useIsTerminalMode();
|
||||||
|
|
||||||
const subPanel = useMemo(
|
const subPanel = useMemo(
|
||||||
() => (
|
() => (
|
||||||
|
@ -153,6 +156,17 @@ const News = ({ posts, ...otherProps }: Props): JSX.Element => {
|
||||||
[keepInfoVisible, posts, searchName, isContentPanelAtLeast4xl]
|
[keepInfoVisible, posts, searchName, isContentPanelAtLeast4xl]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isTerminalMode) {
|
||||||
|
return (
|
||||||
|
<Terminal
|
||||||
|
parentPath="/"
|
||||||
|
childrenPaths={filterHasAttributes(posts, ["attributes"] as const).map(
|
||||||
|
(post) => post.attributes.slug
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout
|
<AppLayout
|
||||||
subPanel={subPanel}
|
subPanel={subPanel}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||||
import { Chip } from "components/Chip";
|
import { Chip } from "components/Chip";
|
||||||
import { HorizontalLine } from "components/HorizontalLine";
|
import { HorizontalLine } from "components/HorizontalLine";
|
||||||
|
@ -22,6 +23,9 @@ import { cIf, cJoin } from "helpers/className";
|
||||||
import { useIs3ColumnsLayout } from "hooks/useContainerQuery";
|
import { useIs3ColumnsLayout } from "hooks/useContainerQuery";
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
import { getLangui } from "graphql/fetchLocalData";
|
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 WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
||||||
const { langui } = useAppLayout();
|
const { langui } = useAppLayout();
|
||||||
|
const router = useRouter();
|
||||||
|
const isTerminalMode = useIsTerminalMode();
|
||||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
||||||
items: page.translations,
|
items: page.translations,
|
||||||
languageExtractor: useCallback(
|
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} />;
|
return <AppLayout subPanel={subPanel} contentPanel={contentPanel} {...otherProps} />;
|
||||||
};
|
};
|
||||||
export default WikiPage;
|
export default WikiPage;
|
||||||
|
@ -208,7 +256,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
||||||
language_code: context.locale ?? "en",
|
language_code: context.locale ?? "en",
|
||||||
slug: slug,
|
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 { title, description } = (() => {
|
||||||
const chipsGroups = {
|
const chipsGroups = {
|
||||||
|
|
|
@ -27,6 +27,8 @@ import { cIf } from "helpers/className";
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
import { getLangui } from "graphql/fetchLocalData";
|
import { getLangui } from "graphql/fetchLocalData";
|
||||||
import { sendAnalytics } from "helpers/analytics";
|
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 hoverable = useDeviceSupportsHover();
|
||||||
const { langui } = useAppLayout();
|
const { langui } = useAppLayout();
|
||||||
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
|
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
|
||||||
|
const isTerminalMode = useIsTerminalMode();
|
||||||
|
|
||||||
const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName);
|
const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName);
|
||||||
|
|
||||||
|
@ -230,6 +233,17 @@ const Wiki = ({ pages, ...otherProps }: Props): JSX.Element => {
|
||||||
[groupingFunction, keepInfoVisible, pages, searchName, isContentPanelAtLeast4xl]
|
[groupingFunction, keepInfoVisible, pages, searchName, isContentPanelAtLeast4xl]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isTerminalMode) {
|
||||||
|
return (
|
||||||
|
<Terminal
|
||||||
|
parentPath="/"
|
||||||
|
childrenPaths={filterHasAttributes(pages, ["attributes"] as const).map(
|
||||||
|
(page) => page.attributes.slug
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout
|
<AppLayout
|
||||||
subPanel={subPanel}
|
subPanel={subPanel}
|
||||||
|
|
|
@ -172,6 +172,22 @@ input[type="submit"] {
|
||||||
[background-blend-mode:var(--theme-texture-dots-blend)];
|
[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 */
|
/* DEBUGGING */
|
||||||
.false {
|
.false {
|
||||||
@apply border-2 border-[red] text-[red] outline-dotted outline-2 outline-[red];
|
@apply border-2 border-[red] text-[red] outline-dotted outline-2 outline-[red];
|
||||||
|
|
|
@ -20,6 +20,7 @@ module.exports = {
|
||||||
body: "var(--theme-font-body)",
|
body: "var(--theme-font-body)",
|
||||||
headers: "var(--theme-font-headers)",
|
headers: "var(--theme-font-headers)",
|
||||||
mono: "var(--theme-font-mono)",
|
mono: "var(--theme-font-mono)",
|
||||||
|
realmono: `ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace`,
|
||||||
...fonts,
|
...fonts,
|
||||||
},
|
},
|
||||||
screens: {
|
screens: {
|
||||||
|
|
|
@ -15,7 +15,9 @@
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"baseUrl": "src"
|
"baseUrl": "src",
|
||||||
|
|
||||||
|
"noUncheckedIndexedAccess": true
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
|
|
Loading…
Reference in New Issue