chore: import upstream snapshot with attribution
@@ -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',
|
||||
},
|
||||
};
|
||||
@@ -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,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
|
||||
@@ -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,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).
|
||||
@@ -0,0 +1,7 @@
|
||||
# WeHub 来源说明
|
||||
|
||||
- 原始项目:`Cveinnt/LiveTerm`
|
||||
- 原始仓库:https://github.com/Cveinnt/LiveTerm
|
||||
- 导入方式:上游默认分支的最新快照
|
||||
- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
|
||||
- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 352 KiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 315 KiB |
|
After Width: | Height: | Size: 334 KiB |
|
After Width: | Height: | Size: 335 KiB |
|
After Width: | Height: | Size: 334 KiB |
|
After Width: | Height: | Size: 332 KiB |
|
After Width: | Height: | Size: 335 KiB |
|
After Width: | Height: | Size: 332 KiB |
|
After Width: | Height: | Size: 330 KiB |
|
After Width: | Height: | Size: 216 KiB |
@@ -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
|
||||
@@ -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
|
||||
@@ -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: {},
|
||||
},
|
||||
};
|
||||
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 22 KiB |
@@ -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>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
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
|
||||
}
|
||||
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
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,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;
|
||||
@@ -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,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;
|
||||
@@ -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(`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}`,
|
||||
};
|
||||
};
|
||||
@@ -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,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.
|
||||
`;
|
||||
};
|
||||
@@ -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.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;
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||