first commit

pull/1/head
Cveinnt 2 years ago
parent ada2a566fa
commit d7eed87027

@ -0,0 +1,9 @@
module.exports = {
plugins: ['prettier'],
extends: ['next/core-web-vitals'],
rules: {
'no-console': 'error',
'prettier/prettier': 'warn',
'react-hooks/exhaustive-deps': 'off',
},
};

35
.gitignore vendored

@ -0,0 +1,35 @@
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
.gitattributes

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn lint

@ -0,0 +1,33 @@
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo

@ -0,0 +1,7 @@
{
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"bracketSpacing": true,
"tabWidth": 2
}

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Vincent Wu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,117 @@
# [LiveTerm - Make Terminal styled websites in minutes!](https://cveinnt.com)
Highly customizable, easy-to-use, and minimal terminal styled website template, written in Next.js.
# Table of Contents:
- [LiveTerm - Make Terminal styled websites in minutes!](#liveterm---make-terminal-styled-websites-in-minutes)
- [Table of Contents:](#table-of-contents)
- [Showcase](#showcase)
- [Quick Start](#quick-start)
- [Configuration](#configuration)
- [Basic Configuration](#basic-configuration)
- [Favicons](#favicons)
- [Banner](#banner)
- [Advanced Configuration](#advanced-configuration)
- [Deploy on Vercel](#deploy-on-vercel)
- [Credit](#credit)
## Showcase
<p align="center">
<img src="./demo/demo.png" width="800"><br>
<strong>Default LiveTerm</strong>
</p>
Live version [here](https://cveinnt.com)
<p align="center">
<img src="./demo/cveinnt.png" width="800"><br>
<strong>my personal website</strong>
</p>
Live version [here](https://cveinnt.com)
## Quick Start
First, clone this repository:
```bash
git clone https://github.com/Cveinnt/LiveTerm.git
```
Then, install dependencies:
```bash
yarn install
```
Now you can start development!
```bash
yarn dev
```
Or, you can build the project:
```bash
yarn build && yarn start
```
## Configuration
### Basic Configuration
Most of the configuration is done through the `config.json` file.
```javascript
{
"readmeUrl": //create a Github README and link it here!
"title": //title of the website
"name": //returned by the command of the same name
"social": {
"github": // your handle
"linkedin": // your handle
},
"email": // your email
"ps1_hostname": //hostname in prompt
"ps1_username": "guest", // username in prompt
"non_terminal_url": "W",
"colors": {
... // you can use existing templates in themes.json or use your own!
}
}
```
Feel free to change it as you see fit!
You can find several pre-configured themes in `themes.json`, and you can replace the colors in `config.json` with the theme color you like! The themes are based on the themes on [this website](https://glitchbone.github.io/vscode-base16-term/#/).
<p align="center">
<img src="./demo/themes.png" width="800"><br>
<strong>different LiveTerm themes</strong>
</p>
Just replace `"light"` or `"dark"` in the `"color"` part of the config file!
### Favicons
Favicons are located in `public/`, along with other files you may want to upload. I used this [website](https://www.favicon-generator.org/) to generate favicons.
### Banner
You may also want to change the output of `banner` command. To do that, simply paste your generated banner in `src/utils/bin/utils.ts`. I used this [website](https://manytools.org/hacker-tools/ascii-banner/) to generate my banner.
### Advanced Configuration
If you want to further customize your page, feel free to change the source code to your preference!
## Deploy on Vercel
The easiest way to deploy a Next.js app is to use the [Vercel Platform](https://vercel.com/) from the creators of Next.js.
Check out [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
## Credit
Based on M4TT72's awesome [Terminal](https://github.com/m4tt72/terminal).

@ -0,0 +1,39 @@
{
"readmeUrl": "https://raw.githubusercontent.com/cveinnt/cveinnt/master/README.md",
"title": "LiveTerm",
"name": "John Doe",
"ascii": "liveterm",
"social": {
"github": "",
"linkedin": ""
},
"email": "contact@johndoe.com",
"ps1_hostname": "liveterm",
"ps1_username": "visitor",
"non_terminal_url": "https://github.com/Cveinnt/LiveTerm",
"resume_url": "https://upload.wikimedia.org/wikipedia/commons/c/cc/Resume.pdf",
"donate_urls": {
"paypal": "https://paypal.me/cveinnt",
"patreon": "https://patreon.com/cveinnt"
},
"colors": {
"light": {
"background": "#FBF1C9",
"foreground": "#3C3836",
"yellow": "#D79921",
"green": "#98971A",
"gray": "#7C6F64",
"blue": "#458588",
"red": "#CA2124"
},
"dark": {
"background": "#2E3440",
"foreground": "#E5E9F0",
"yellow": "#5E81AC",
"green": "#A3BE8C",
"gray": "#88C0D0",
"blue": "#EBCB8B",
"red": "#BF616A"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

5
next-env.d.ts vendored

@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

@ -0,0 +1 @@
module.exports = {};

@ -0,0 +1,42 @@
{
"name": "liveterm",
"version": "0.1.0",
"license": "MIT",
"author": {
"name": "Vincent Wu",
"url": "https://cveinnt.com",
"email": "contact@wensenwu.com"
},
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"prepare": "husky install"
},
"dependencies": {
"axios": "^0.27.2",
"next": "12.1.6",
"react": "18.1.0",
"react-dom": "18.1.0",
"react-icons": "^4.3.1"
},
"devDependencies": {
"@types/node": "17.0.32",
"@types/react": "18.0.9",
"@types/react-dom": "18.0.3",
"@typescript-eslint/eslint-plugin": "^5.23.0",
"@typescript-eslint/parser": "^5.23.0",
"autoprefixer": "^10.4.7",
"eslint": "8.15.0",
"eslint-config-next": "^12.1.6",
"eslint-plugin-next": "^0.0.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.29.4",
"husky": "^8.0.1",
"postcss": "^8.4.13",
"prettier": "^2.6.2",
"tailwindcss": "^3.0.24",
"typescript": "^4.6.4"
}
}

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig><msapplication><tile><square70x70logo src="/ms-icon-70x70.png"/><square150x150logo src="/ms-icon-150x150.png"/><square310x310logo src="/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,49 @@
{
"name": "LiveTerm",
"short_name": "LiveTerm",
"theme_color": "#2E3440",
"background_color": "#2E3440",
"display": "fullscreen",
"orientation": "portrait",
"scope": "/",
"start_url": "/",
"icons": [
{
"src": "/android-icon-36x36.png",
"sizes": "36x36",
"type": "image/png",
"density": "0.75"
},
{
"src": "/android-icon-48x48.png",
"sizes": "48x48",
"type": "image/png",
"density": "1.0"
},
{
"src": "/android-icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"density": "1.5"
},
{
"src": "/android-icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"density": "2.0"
},
{
"src": "/android-icon-144x144.png",
"sizes": "144x144",
"type": "image/png",
"density": "3.0"
},
{
"src": "/android-icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"density": "4.0"
}
],
"splash_pages": null
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

@ -0,0 +1,3 @@
User-agent: *
Disallow:
Disallow: /cgi-bin/

@ -0,0 +1,19 @@
import React from 'react';
import config from '../../config.json';
export const Ps1 = () => {
return (
<div>
<span className="text-light-yellow dark:text-dark-yellow">
{config.ps1_username}
</span>
<span className="text-light-gray dark:text-dark-gray">@</span>
<span className="text-light-green dark:text-dark-green">
{config.ps1_hostname}
</span>
<span className="text-light-gray dark:text-dark-gray">:$ ~ </span>
</div>
);
};
export default Ps1;

@ -0,0 +1,31 @@
import React from 'react';
import { History as HistoryInterface } from './interface';
import { Ps1 } from '../Ps1';
export const History: React.FC<{ history: Array<HistoryInterface> }> = ({
history,
}) => {
return (
<>
{history.map((entry: HistoryInterface, index: number) => (
<div key={entry.command + index}>
<div className="flex flex-row space-x-2">
<div className="flex-shrink">
<Ps1 />
</div>
<div className="flex-grow">{entry.command}</div>
</div>
<p
className="whitespace-pre-wrap mb-2"
// style={{ lineHeight: 'normal' }}
dangerouslySetInnerHTML={{ __html: entry.output }}
/>
</div>
))}
</>
);
};
export default History;

@ -0,0 +1,27 @@
import React from 'react';
import { History } from './interface';
export const useHistory = (defaultValue: Array<History>) => {
const [history, setHistory] = React.useState<Array<History>>(defaultValue);
const [command, setCommand] = React.useState<string>('');
const [lastCommandIndex, setLastCommandIndex] = React.useState<number>(0);
return {
history,
command,
lastCommandIndex,
setHistory: (value: string) =>
setHistory([
...history,
{
id: history.length,
date: new Date(),
command,
output: value,
},
]),
setCommand,
setLastCommandIndex,
clearHistory: () => setHistory([]),
};
};

@ -0,0 +1,6 @@
export interface History {
id: number;
date: Date;
command: string;
output: string;
}

@ -0,0 +1,106 @@
import React from 'react';
import { commandExists } from '../utils/commandExists';
import { shell } from '../utils/shell';
import { handleTabCompletion } from '../utils/tabCompletion';
import { Ps1 } from './Ps1';
export const Input = ({
inputRef,
containerRef,
command,
history,
lastCommandIndex,
setCommand,
setHistory,
setLastCommandIndex,
clearHistory,
}) => {
const onSubmit = async (event: React.KeyboardEvent<HTMLInputElement>) => {
const commands: [string] = history
.map(({ command }) => command)
.filter((command: string) => command);
if (event.key === 'c' && event.ctrlKey) {
event.preventDefault();
setCommand('');
setHistory('');
setLastCommandIndex(0);
}
if (event.key === 'l' && event.ctrlKey) {
event.preventDefault();
clearHistory();
}
if (event.key === 'Tab') {
event.preventDefault();
handleTabCompletion(command, setCommand);
}
if (event.key === 'Enter' || event.code === '13') {
event.preventDefault();
setLastCommandIndex(0);
await shell(command, setHistory, clearHistory, setCommand);
containerRef.current.scrollTo(0, containerRef.current.scrollHeight);
}
if (event.key === 'ArrowUp') {
event.preventDefault();
if (!commands.length) {
return;
}
const index: number = lastCommandIndex + 1;
if (index <= commands.length) {
setLastCommandIndex(index);
setCommand(commands[commands.length - index]);
}
}
if (event.key === 'ArrowDown') {
event.preventDefault();
if (!commands.length) {
return;
}
const index: number = lastCommandIndex - 1;
if (index > 0) {
setLastCommandIndex(index);
setCommand(commands[commands.length - index]);
} else {
setLastCommandIndex(0);
setCommand('');
}
}
};
const onChange = ({
target: { value },
}: React.ChangeEvent<HTMLInputElement>) => {
setCommand(value);
};
return (
<div className="flex flex-row space-x-2">
<label htmlFor="prompt" className="flex-shrink">
<Ps1 />
</label>
<input
ref={inputRef}
id="prompt"
type="text"
className={`bg-light-background dark:bg-dark-background focus:outline-none flex-grow ${
commandExists(command) || command === ''
? 'text-dark-green'
: 'text-dark-red'
}`}
value={command}
onChange={onChange}
autoFocus
onKeyDown={onSubmit}
autoComplete="off"
/>
</div>
);
};
export default Input;

@ -0,0 +1,14 @@
import React from 'react';
import { useRouter } from 'next/router';
const NotFoundPage = () => {
const router = useRouter();
React.useEffect(() => {
router.replace('/');
});
return null;
};
export default NotFoundPage;

@ -0,0 +1,35 @@
import React from 'react';
import '../styles/global.css';
import Head from 'next/head';
const App = ({ Component, pageProps }) => {
const inputRef = React.useRef<HTMLInputElement>(null);
const onClickAnywhere = () => {
inputRef.current.focus();
};
return (
<>
<Head>
<meta
name="viewport"
content="initial-scale=1.0, width=device-width"
key="viewport"
maximum-scale="1"
/>
</Head>
<div
className="text-light-foreground dark:text-dark-foreground min-w-max text-xs md:min-w-full md:text-base"
onClick={onClickAnywhere}
>
<main className="bg-light-background dark:bg-dark-background w-full h-full p-2">
<Component {...pageProps} inputRef={inputRef} />
</main>
</div>
</>
);
};
export default App;

@ -0,0 +1,64 @@
import Head from 'next/head';
import React from 'react';
import config from '../../config.json';
import { Input } from '../components/input';
import { useHistory } from '../components/history/hook';
import { History } from '../components/history/History';
import { banner } from '../utils/bin';
interface IndexPageProps {
inputRef: React.MutableRefObject<HTMLInputElement>;
}
const IndexPage: React.FC<IndexPageProps> = ({ inputRef }) => {
const containerRef = React.useRef(null);
const {
history,
command,
lastCommandIndex,
setCommand,
setHistory,
clearHistory,
setLastCommandIndex,
} = useHistory([]);
const init = React.useCallback(() => setHistory(banner()), []);
React.useEffect(() => {
init();
}, [init]);
React.useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, [history]);
return (
<>
<Head>
<title>{config.title}</title>
</Head>
<div className="p-8 overflow-hidden h-full border-2 rounded border-light-yellow dark:border-dark-yellow">
<div ref={containerRef} className="overflow-y-auto h-full">
<History history={history} />
<Input
inputRef={inputRef}
containerRef={containerRef}
command={command}
history={history}
lastCommandIndex={lastCommandIndex}
setCommand={setCommand}
setHistory={setHistory}
setLastCommandIndex={setLastCommandIndex}
clearHistory={clearHistory}
/>
</div>
</div>
</>
);
};
export default IndexPage;

@ -0,0 +1,41 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@font-face {
font-family: 'Hack';
src: url(/assets/fonts/Hack-NF.ttf);
display: swap;
}
* {
font-family: 'Hack', monospace;
}
html,
body,
body > div:first-child,
div#__next,
div#__next > div {
height: 100%;
overflow: auto;
}
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: #1e252e;
border-radius: 5px;
}
::-webkit-scrollbar-thumb {
background: #ebdbb2;
border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
background: #ff8037;
}

@ -0,0 +1,30 @@
import axios from 'axios';
import config from '../../config.json';
export const getProjects = async () => {
const { data } = await axios.get(
`https://api.github.com/users/${config.social.github}/repos`,
);
return data;
};
export const getReadme = async () => {
const { data } = await axios.get(config.readmeUrl);
return data;
};
export const getWeather = async (city: string) => {
try {
const { data } = await axios.get(`/api/weather/${city}`);
return data;
} catch (error) {
return error;
}
};
export const getQuote = async () => {
const { data } = await axios.get('http://api.quotable.io/random');
return {
quote: `${data.content}” — ${data.author}`,
};
};

@ -0,0 +1,36 @@
// // List of commands that require API calls
import { getProjects } from '../api';
import { getQuote } from '../api';
import { getReadme } from '../api';
import { getWeather } from '../api';
export const projects = async (args: string[]): Promise<string> => {
const projects = await getProjects();
return projects
.map(
(repo) =>
`${repo.name} - <a class="text-light-blue dark:text-dark-blue underline" href="${repo.html_url}" target="_blank">${repo.html_url}</a>`,
)
.join('\n');
};
export const quote = async (args: string[]): Promise<string> => {
const data = await getQuote();
return data.quote;
};
export const readme = async (args: string[]): Promise<string> => {
const readme = await getReadme();
return `Opening GitHub README...\n
${readme}`;
};
export const weather = async (args: string[]): Promise<string> => {
const city = args.join('+');
if (!city) {
return 'Usage: weather [city]. Example: weather casablanca';
}
const weather = await getWeather(city);
return weather;
};

@ -0,0 +1,136 @@
// List of commands that do not require API calls
import * as bin from './index';
import config from '../../../config.json';
// Help
export const help = async (args: string[]): Promise<string> => {
const commands = Object.keys(bin).sort().join(', ');
var c = '';
for (let i = 1; i <= Object.keys(bin).sort().length; i++) {
if (i % 7 === 0) {
c += Object.keys(bin).sort()[i - 1] + '\n';
} else {
c += Object.keys(bin).sort()[i - 1] + ' ';
}
}
return `Welcome! Here are all the available commands:
\n${c}\n
[tab]: trigger completion.
[ctrl+l]/clear: clear terminal.\n
Type 'sumfetch' to display summary.
Type 'gui' or click <u><a href="${config.non_terminal_url}" target="_blank">here</a></u> for a simpler version.
`;
};
// Redirection
export const gui = async (args: string[]): Promise<string> => {
window.open(`${config.non_terminal_url}`);
return 'Opening GUI version...';
};
// About
export const about = async (args: string[]): Promise<string> => {
return `Hi, I am ${config.name}.
Welcome to my website!
More about me:
'sumfetch' - short summary.
'resume' - my latest resume.
'readme' - my github readme.`;
};
export const resume = async (args: string[]): Promise<string> => {
window.open(`${config.resume_url}`);
return 'Opening resume...';
};
// Donate
export const donate = async (args: string[]): Promise<string> => {
return `thank you for your interest.
here are the ways you can support my work:
- <u><a class="text-light-blue dark:text-dark-blue underline" href="${config.donate_urls.paypal}" target="_blank">paypal</a></u>
- <u><a class="text-light-blue dark:text-dark-blue underline" href="${config.donate_urls.patreon}" target="_blank">patreon</a></u>
`;
};
// Contact
export const email = async (args: string[]): Promise<string> => {
window.open(`mailto:${config.email}`);
return `Opening mailto:${config.email}...`;
};
export const github = async (args: string[]): Promise<string> => {
window.open(`https://github.com/${config.social.github}/`);
return 'Opening github...';
};
export const linkedin = async (args: string[]): Promise<string> => {
window.open(`https://www.linkedin.com/in/${config.social.linkedin}/`);
return 'Opening linkedin...';
};
// Typical linux commands
export const echo = async (args: string[]): Promise<string> => {
return args.join(' ');
};
export const whoami = async (args: string[]): Promise<string> => {
return `${config.ps1_username}`;
};
export const ls = async (args: string[]): Promise<string> => {
return `a
bunch
of
fake
directories`;
};
export const cd = async (args: string[]): Promise<string> => {
return `unfortunately, i cannot afford more directories.
if you want to help, you can type 'donate'.`;
};
export const date = async (args: string[]): Promise<string> => {
return new Date().toString();
};
export const vi = async (args: string[]): Promise<string> => {
return `woah, you still use 'vi'? just try 'vim'.`;
};
export const vim = async (args: string[]): Promise<string> => {
return `'vim' is so outdated. how about 'nvim'?`;
};
export const nvim = async (args: string[]): Promise<string> => {
return `'nvim'? too fancy. why not 'emacs'?`;
};
export const emacs = async (args?: string[]): Promise<string> => {
return `you know what? just use vscode.`;
};
export const sudo = async (args?: string[]): Promise<string> => {
window.open('https://www.youtube.com/watch?v=dQw4w9WgXcQ', '_blank'); // good ol' rick roll
return `Permission denied: with little power comes... no responsibility? `;
};
// Banner
export const banner = (args?: string[]): string => {
return `
Type 'help' to see the list of available commands.
Type 'sumfetch' to display summary.
Type 'gui' or click <u><a class="text-light-blue dark:text-dark-blue underline" href="${config.non_terminal_url}" target="_blank">here</a></u> for a simpler version.
`;
};

@ -0,0 +1,3 @@
export * from './commands';
export * from './api_commands';
export { default as sumfetch } from './sumfetch';

@ -0,0 +1,46 @@
import config from '../../../config.json';
const sumfetch = async (args: string[]): Promise<string> => {
if (config.ascii === 'cveinnt') {
return `
@@@@@@@@@@@@@ sumfetch: summary display
@@@@ @@@@ -----------
@@ @@ ABOUT
@@ @@ ${config.name}
@@ @@ ${config.ps1_hostname}
@@ @@@ @@ <u><a href="${config.resume_url}" target="_blank">resume</a></u>
@@ @@@ @@ <u><a href="${config.non_terminal_url}" target="_blank">alt website</a></u>
@@ @@ -----------
@@ .@@@@@@@@@@. @@ CONTACT
@@ @@ @@ @@ <u><a href="mailto:${config.email}" target="_blank">${config.email}</a></u>
@@ @@ @@ @@ <u><a href="https://github.com/${config.social.github}" target="_blank">github.com/${config.social.github}</a></u>
@@ @@@@@@ @@ <u><a href="https://linkedin.com/in/${config.social.linkedin}" target="_blank">linkedin.com/in/${config.social.linkedin}</a></u>
@@@ @@@ -----------
@@@ @@@ @@ DONATE
@| @@@@@@@@@@@@@@@@ @@ <u><a href="${config.donate_urls.paypal}" target="_blank">${config.donate_urls.paypal}</a></u>
@| @@ <u><a href="${config.donate_urls.patreon}" target="_blank">${config.donate_urls.patreon}</a></u>
`;
} else {
return `
  sumfetch
    -----------
      ABOUT
  ${config.name}
        <u><a href="${config.resume_url}" target="_blank">resume</a></u>
<u><a href="${config.non_terminal_url}" target="_blank">alt website</a></u>
  -----------
  > L I V E T E R M CONTACT
  <u><a href="mailto:${config.email}" target="_blank">${config.email}</a></u>
<u><a href="https://github.com/${config.social.github}" target="_blank">github.com/${config.social.github}</a></u>
        <u><a href="https://linkedin.com/in/${config.social.linkedin}" target="_blank">linkedin.com/in/${config.social.linkedin}</a></u>
  -----------
        DONATE
   <u><a href="${config.donate_urls.paypal}" target="_blank">${config.donate_urls.paypal}</a></u>
  <u><a href="${config.donate_urls.patreon}" target="_blank">${config.donate_urls.patreon}</a></u>
`;
}
};
export default sumfetch;

@ -0,0 +1,6 @@
import * as bin from './bin';
export const commandExists = (command: string) => {
const commands = ['clear', ...Object.keys(bin)];
return commands.indexOf(command.split(' ')[0].toLowerCase()) !== -1;
};

@ -0,0 +1,27 @@
import React from 'react';
import * as bin from './bin';
export const shell = async (
command: string,
setHistory: (value: string) => void,
clearHistory: () => void,
setCommand: React.Dispatch<React.SetStateAction<string>>,
) => {
const args = command.split(' ');
args[0] = args[0].toLowerCase();
if (args[0] === 'clear') {
clearHistory();
} else if (command === '') {
setHistory('');
} else if (Object.keys(bin).indexOf(args[0]) === -1) {
setHistory(
`shell: command not found: ${args[0]}. Try 'help' to get started.`,
);
} else {
const output = await bin[args[0]](args.slice(1));
setHistory(output);
}
setCommand('');
};

@ -0,0 +1,14 @@
import * as bin from './bin';
export const handleTabCompletion = (
command: string,
setCommand: React.Dispatch<React.SetStateAction<string>>,
) => {
const commands = Object.keys(bin).filter((entry) =>
entry.startsWith(command),
);
if (commands.length === 1) {
setCommand(commands[0]);
}
};

@ -0,0 +1,21 @@
const { colors } = require('./config.json');
module.exports = {
content: [
'./src/pages/**/*.{js,ts,jsx,tsx}',
'./src/components/**/*.{js,ts,jsx,tsx}',
],
darkMode: 'media', // or 'media' or 'class'
theme: {
colors: {
transparent: 'transparent',
current: 'currentColor',
...colors,
},
extend: {},
},
variants: {
extend: {},
},
plugins: [],
};

@ -0,0 +1,162 @@
{
"default": {
"light": {
"background": "#FBF1C9",
"foreground": "#3C3836",
"yellow": "#D79921",
"green": "#98971A",
"gray": "#7C6F64",
"blue": "#458588",
"red": "#CA2124"
},
"dark": {
"background": "#2E3440",
"foreground": "#E5E9F0",
"yellow": "#5E81AC",
"green": "#A3BE8C",
"gray": "#88C0D0",
"blue": "#EBCB8B",
"red": "#BF616A"
}
},
"gruvbox": {
"light": {
"background": "#FBF1C9",
"foreground": "#3C3836",
"yellow": "#D79921",
"green": "#98971A",
"gray": "#7C6F64",
"blue": "#458588",
"red": "#CA2124"
},
"dark": {
"background": "#1c1c1c",
"foreground": "#EBDBB2",
"yellow": "#D79921",
"green": "#98971A",
"gray": "#A89984",
"blue": "#458588",
"red": "#CA2124"
}
},
"dracula": {
"light": {
"background": "#FFFFDB",
"foreground": "#282a36",
"yellow": "#ffb86c",
"green": "#50fa7b",
"gray": "#8B6BB9",
"blue": "#67AFC0",
"red": "#ff5555"
},
"dark": {
"background": "#282a36",
"foreground": "#f8f8f2",
"yellow": "#ffb86c",
"green": "#50fa7b",
"gray": "#bd93f9",
"blue": "#8be9fd",
"red": "#ff5555"
}
},
"Nord": {
"light": {
"background": "#E5E9F0",
"foreground": "#2E3440",
"yellow": "#5E81AC",
"green": "#A3BE8C",
"gray": "#88C0D0",
"blue": "#EBCB8B",
"red": "#BF616A"
},
"dark": {
"background": "#2E3440",
"foreground": "#E5E9F0",
"yellow": "#5E81AC",
"green": "#A3BE8C",
"gray": "#88C0D0",
"blue": "#EBCB8B",
"red": "#BF616A"
}
},
"Monokai": {
"light": {
"background": "#F8F8F2",
"foreground": "#272822",
"yellow": "#F4BF75",
"green": "#A6E22E",
"gray": "#AE81FF",
"blue": "#66D9EF",
"red": "#F92672"
},
"dark": {
"background": "#272822",
"foreground": "#F8F8F2",
"yellow": "#F4BF75",
"green": "#A6E22E",
"gray": "#AE81FF",
"blue": "#66D9EF",
"red": "#F92672"
}
},
"Mocha": {
"light": {
"background": "#D0C8C6",
"foreground": "#3B3228",
"yellow": "#F4BC87",
"green": "#BEB55B",
"gray": "#A89BB9",
"blue": "#8AB3B5",
"red": "#CB6077"
},
"dark": {
"background": "#3B3228",
"foreground": "#D0C8C6",
"yellow": "#F4BC87",
"green": "#BEB55B",
"gray": "#A89BB9",
"blue": "#8AB3B5",
"red": "#CB6077"
}
},
"Solarized": {
"light": {
"background": "#FDF6E3",
"foreground": "#586E75",
"yellow": "#B58900",
"green": "#859900",
"gray": "#6C71C4",
"blue": "#268BD2",
"red": "#DC322F"
},
"dark": {
"background": "#002B36",
"foreground": "#93A1A1",
"yellow": "#B58900",
"green": "#859900",
"gray": "#6C71C4",
"blue": "#268BD2",
"red": "#DC322F"
}
},
"Paraiso": {
"light": {
"background": "#A39E9B",
"foreground": "#2F1E2E",
"yellow": "#FEC418",
"green": "#48B685",
"gray": "#815BA4",
"blue": "#06B6EF",
"red": "#EF6155"
},
"dark": {
"background": "#2F1E2E",
"foreground": "#A39E9B",
"yellow": "#FEC418",
"green": "#48B685",
"gray": "#815BA4",
"blue": "#06B6EF",
"red": "#EF6155"
}
}
}

@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"incremental": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}

@ -0,0 +1,5 @@
{
"github": {
"silent": true
}
}

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save