chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:29:18 +08:00
commit ed92a0c2cf
80 changed files with 3459 additions and 0 deletions
+9
View File
@@ -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
View File
@@ -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
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn lint
+33
View File
@@ -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
+7
View File
@@ -0,0 +1,7 @@
{
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"bracketSpacing": true,
"tabWidth": 2
}
+13
View File
@@ -0,0 +1,13 @@
FROM node:18-alpine as base
WORKDIR /home/node/app
COPY package.json ./
RUN npm install -g npm-check-updates
RUN ncu -u
RUN npm install
COPY . ./
FROM base as production
ENV NODE_PATH=./build
RUN npm run build
+21
View File
@@ -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.
+131
View File
@@ -0,0 +1,131 @@
# [💻 LiveTerm - build terminal styled websites in minutes!](https://liveterm.vercel.app)
Highly customizable, easy-to-use, and minimal terminal styled website template, powered by Next.js.
Building a simple website with LiveTerm only takes **minutes**, and you only need to work with **one** file: `config.json`. After you cloned this repository, simply run `yarn install && yarn dev` and start editing `config.json` to build your website!
LiveTerm can be used to build a variety of websites:
- [personal website](https://cveinnt.com)
- [browser startpage](https://livetermstart.vercel.app/)
- [project page](https://liveterm.vercel.app/)
- or maybe just a cool browser music player...be creative!
Feel free to play with the web demo above!
## 📸 Showcase
<p align="center">
<img src="./demo/demo.gif" width="600"><br>
<strong>LiveTerm with different themes</strong>
</p>
<p align="center">
<img src="./demo/cveinnt.png" width="600"><br>
<strong><a href="https://cveinnt.com" target=_blank>my personal website</a></strong>
</p>
## 🚀 Ship your LiveTerm site in less than 5 minutes
LiveTerm requires the `yarn` package manager. You can install `yarn` [here](https://classic.yarnpkg.com/lang/en/docs/install/).
Simply run the following commmand in your terminal:
```bash
sh -c "$(curl -fsSL https://raw.github.com/Cveinnt/LiveTerm/main/install/install.sh)"
```
This will install LiveTerm to the current directory. You can start building your website with:
```bash
cd LiveTerm && yarn dev
```
Start editing `config.json` and try saving and see the updated changes!
Alternatively, you can clone this repository to a location of your choosing
```bash
git clone https://github.com/Cveinnt/LiveTerm.git && cd LiveTerm
```
Then install dependencies and start developing there:
```bash
yarn install && yarn dev
```
### Docker Usage
First, clone the project and edit `config.json` to your liking. Then run the following to start the container in the background:
```shell
docker-compose up -d
```
If you **know** what you were doing, you can also try changing `Dockerfile` & `docker-compose.yml`!
Learn more about Docker [here](https://docs.docker.com/get-started/overview/ 'here').
## 📄 Configuration
### Basic Configuration
90% of LiveTerm's configurations are done through the `config.json` file.
```javascript
{
"readmeUrl": // create a Github README and link it here!
"title": // title of the website
"name": // your name, included in 'about' command
"ascii": // ascii art to display
"social": {
"github": // your handle
"linkedin": // your handle
},
"email": // your email
"ps1_hostname": "liveterm" // hostname in prompt
"ps1_username": "visitor", // username in prompt
"resume_url": "../resume.pdf", // path to your resume
"non_terminal_url": "W",
"colors": {
"light": {
...
},
"dark": {
... // you can use existing templates in themes.json or use your own!
}
}
}
```
Feel free to change it as you see fit!
### Themes
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/#/).
For a better preview of the themes, checkout the images in the `demo` folder.
### Favicons
Favicons are located in `public/`, along with the other files you may want to upload to your website. I used this [website](https://www.favicon-generator.org/) to generate favicons.
### Banner
You may also want to change the output of the `banner` command. To do that, simply paste your generated banner in `src/utils/bin/commands.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 liking!
## 🌐 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.
You can install `vercel` cli and follow the instruction [here](https://vercel.com/docs/concepts/deployments/overview).
You can also connect your github account to vercel and have vercel automatically deploy the github repository for you.
## Credit
Based on M4TT72's awesome [Terminal](https://github.com/m4tt72/terminal).
+7
View File
@@ -0,0 +1,7 @@
# WeHub 来源说明
- 原始项目:`Cveinnt/LiveTerm`
- 原始仓库:https://github.com/Cveinnt/LiveTerm
- 导入方式:上游默认分支的最新快照
- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
+39
View File
@@ -0,0 +1,39 @@
{
"readmeUrl": "https://raw.githubusercontent.com/cveinnt/cveinnt/master/README.md",
"title": "LiveTerm",
"name": "John Doe",
"ascii": "liveterm",
"social": {
"github": "github",
"linkedin": "linkedin"
},
"email": "contact@email.com",
"ps1_hostname": "liveterm",
"ps1_username": "visitor",
"repo": "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"
}
}
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

+14
View File
@@ -0,0 +1,14 @@
version: "3.7"
services:
liveterm:
build:
context: .
dockerfile: Dockerfile
target: base
expose:
- '3000'
ports:
- '3000:3000'
volumes:
- ./src/:/home/node/app/src
command: npm run dev
+59
View File
@@ -0,0 +1,59 @@
#!/bin/sh
# This script installs LiveTerm.
#
# Found a bug? Report it here: https://github.com/Cveinnt/LiveTerm/issues
set -e -u
eval "git clone 'https://github.com/Cveinnt/LiveTerm.git'"
cd LiveTerm
echo "LiveTerm cloned! Installing dependencies..."
if command -v yarn >/dev/null 2>&1 ; then
yarn install
else
cat 1>&2 << 'EOA'
/=====================================\\
| COMMAND NOT FOUND: 'yarn' |
\\=====================================/
Uh oh! We couldn't find 'yarn' installed on your system.
You should first install 'yarn' on your system.
For more information, visit yarn's installation page:
https://classic.yarnpkg.com/lang/en/docs/install/
EOA
exit 1
fi
cat <<-'EOM'
█████ ███ ███████████
░░███ ░░░ ░█░░░███░░░█
░███ ████ █████ █████ ██████ ░ ░███ ░ ██████ ████████ █████████████
░███ ░░███ ░░███ ░░███ ███░░███ ░███ ███░░███░░███░░███░░███░░███░░███
░███ ░███ ░███ ░███ ░███████ ░███ ░███████ ░███ ░░░ ░███ ░███ ░███
░███ █ ░███ ░░███ ███ ░███░░░ ░███ ░███░░░ ░███ ░███ ░███ ░███
███████████ █████ ░░█████ ░░██████ █████ ░░██████ █████ █████░███ █████
░░░░░░░░░░░ ░░░░░ ░░░░░ ░░░░░░ ░░░░░ ░░░░░░ ░░░░░ ░░░░░ ░░░ ░░░░░
LiveTerm has been downloaded to the current directory.
You can start developing it with:
cd LiveTerm && yarn dev
This will open a local port at http://localhost:3000,
where you can preview your website. To apply changes, edit:
config.json
And the changes should be applied upon saves!
EOM
+5
View File
@@ -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.
+1
View File
@@ -0,0 +1 @@
module.exports = {};
+42
View File
@@ -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"
}
}
+6
View File
@@ -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.
Binary file not shown.
Binary file not shown.
+2
View File
@@ -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

+49
View File
@@ -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

+3
View File
@@ -0,0 +1,3 @@
User-agent: *
Disallow:
Disallow: /cgi-bin/
+19
View File
@@ -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;
+31
View File
@@ -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;
+27
View File
@@ -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([]),
};
};
+6
View File
@@ -0,0 +1,6 @@
export interface History {
id: number;
date: Date;
command: string;
output: string;
}
+107
View File
@@ -0,0 +1,107 @@
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"
spellCheck="false"
/>
</div>
);
};
export default Input;
+14
View File
@@ -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;
+35
View File
@@ -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;
+65
View File
@@ -0,0 +1,65 @@
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.scrollIntoView();
inputRef.current.focus({ preventScroll: true });
}
}, [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;
+41
View File
@@ -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;
}
+30
View File
@@ -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(`https://wttr.in/${city}?ATm`);
return data;
} catch (error) {
return error;
}
};
export const getQuote = async () => {
const { data } = await axios.get('https://api.quotable.io/random');
return {
quote: `${data.content}” — ${data.author}`,
};
};
+36
View File
@@ -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;
};
+157
View File
@@ -0,0 +1,157 @@
// 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.
`;
};
// Redirection
export const repo = async (args: string[]): Promise<string> => {
window.open(`${config.repo}`);
return 'Opening Github repository...';
};
// 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...';
};
// Search
export const google = async (args: string[]): Promise<string> => {
window.open(`https://google.com/search?q=${args.join(' ')}`);
return `Searching google for ${args.join(' ')}...`;
};
export const duckduckgo = async (args: string[]): Promise<string> => {
window.open(`https://duckduckgo.com/?q=${args.join(' ')}`);
return `Searching duckduckgo for ${args.join(' ')}...`;
};
export const bing = async (args: string[]): Promise<string> => {
window.open(`https://bing.com/search?q=${args.join(' ')}`);
return `Wow, really? You are using bing for ${args.join(' ')}?`;
};
export const reddit = async (args: string[]): Promise<string> => {
window.open(`https://www.reddit.com/search/?q=${args.join(' ')}`);
return `Searching reddit for ${args.join(' ')}...`;
};
// 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'); // ...I'm sorry
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 'repo' or click <u><a class="text-light-blue dark:text-dark-blue underline" href="${config.repo}" target="_blank">here</a></u> for the Github repository.
`;
};
+3
View File
@@ -0,0 +1,3 @@
export * from './commands';
export * from './api_commands';
export { default as sumfetch } from './sumfetch';
+46
View File
@@ -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.repo}" target="_blank">Github repo</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.repo}" target="_blank">Github repo</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;
+6
View File
@@ -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;
};
+27
View File
@@ -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('');
};
+14
View File
@@ -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]);
}
};
+21
View File
@@ -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: [],
};
+162
View File
@@ -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"
}
}
}
+20
View File
@@ -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"]
}
+5
View File
@@ -0,0 +1,5 @@
{
"github": {
"silent": true
}
}
+2107
View File
File diff suppressed because it is too large Load Diff