initial commit

This commit is contained in:
2024-10-13 14:02:02 +00:00
commit a3cabb6229
33 changed files with 18981 additions and 0 deletions

131
.gitignore vendored Normal file
View File

@@ -0,0 +1,131 @@
# ---> Node
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

10
Dockerfile Normal file
View File

@@ -0,0 +1,10 @@
FROM node:22-bookworm-slim
WORKDIR /work
COPY . /work
RUN npm ci
EXPOSE 3000
ENTRYPOINT ["/usr/bin/env"]
CMD ["npm", "start"]

View File

@@ -0,0 +1,29 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: amane-frontend
namespace: amane-tanikaze
labels:
app: amane-frontend
spec:
replicas: 1
selector:
matchLabels:
app: amane-frontend
template:
metadata:
labels:
app: amane-frontend
spec:
containers:
- name: dcbot
image: 'gitea.konchin.com/services/amane-tanikaze-frontend:latest'
ports:
- name: http
containerPort: 3000
env:
- name: WDS_SOCKET_PORT
value: 0
imagePullSecrets:
- name: regcred

View File

@@ -0,0 +1,16 @@
---
apiVersion: v1
kind: Service
metadata:
namespace: amane-tanikaze
name: amane-frontend
labels:
app: amane-frontend
spec:
type: ClusterIP
selector:
app: amane-frontend
ports:
- name: http
port: 80
targetPort: 3000

18042
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

45
package.json Normal file
View File

@@ -0,0 +1,45 @@
{
"name": "frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.70",
"@types/react": "^18.2.47",
"@types/react-dom": "^18.2.18",
"react": "^18.2.0",
"react-cookie": "^7.0.1",
"react-dom": "^18.2.0",
"react-router-dom": "^6.21.1",
"react-scripts": "5.0.1",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

43
public/index.html Normal file
View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/logo/poop.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo/poop.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Autoreact Image</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

BIN
public/logo/amane.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 KiB

BIN
public/logo/poop.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

15
public/manifest.json Normal file
View File

@@ -0,0 +1,15 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "poop.png",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/png"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

3
public/robots.txt Normal file
View File

@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

7
public/svg/info.svg Normal file
View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" height="160" width="160" version="1.0">
<g fill="#4b4b4b">
<path d="m80 15c-35.88 0-65 29.12-65 65s29.12 65 65 65 65-29.12 65-65-29.12-65-65-65zm0 10c30.36 0 55 24.64 55 55s-24.64 55-55 55-55-24.64-55-55 24.64-55 55-55z"/>
<path d="m57.373 18.231a9.3834 9.1153 0 1 1 -18.767 0 9.3834 9.1153 0 1 1 18.767 0z" transform="matrix(1.1989 0 0 1.2342 21.214 28.75)"/>
<path d="m90.665 110.96c-0.069 2.73 1.211 3.5 4.327 3.82l5.008 0.1v5.12h-39.073v-5.12l5.503-0.1c3.291-0.1 4.082-1.38 4.327-3.82v-30.813c0.035-4.879-6.296-4.113-10.757-3.968v-5.074l30.665-1.105"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 617 B

3
public/svg/search.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg id="tnb-google-search-icon" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.8153 10.3439C12.6061 9.2673 13.0732 7.9382 13.0732 6.5C13.0732 2.91015 10.163 0 6.57318 0C2.98333 0 0.0731812 2.91015 0.0731812 6.5C0.0731812 10.0899 2.98333 13 6.57318 13C8.01176 13 9.3412 12.5327 10.4179 11.7415L10.4171 11.7422C10.4466 11.7822 10.4794 11.8204 10.5156 11.8566L14.3661 15.7071C14.7566 16.0976 15.3898 16.0976 15.7803 15.7071C16.1708 15.3166 16.1708 14.6834 15.7803 14.2929L11.9298 10.4424C11.8936 10.4062 11.8553 10.3734 11.8153 10.3439ZM12.0732 6.5C12.0732 9.53757 9.61075 12 6.57318 12C3.53561 12 1.07318 9.53757 1.07318 6.5C1.07318 3.46243 3.53561 1 6.57318 1C9.61075 1 12.0732 3.46243 12.0732 6.5Z" fill="black"></path>
</svg>

After

Width:  |  Height:  |  Size: 764 B

3
src/config.ts Normal file
View File

@@ -0,0 +1,3 @@
export const config = {
aliasPerPage: 10
};

0
src/css/.Rhistory Normal file
View File

47
src/css/banner.css Normal file
View File

@@ -0,0 +1,47 @@
div#fixed-banner {
position: fixed;
top: 0;
left: 0;
height: 10%;
width: 100%;
background-color: white;
border-top: 0.8rem solid grey;
border-bottom: 0.8rem solid grey;
z-index: 1;
}
div.banner.container {
flex-flow: row nowrap;
width: 100%;
height: 100%;
}
div.banner.element {
height: 100%;
}
div.banner.element.left {
width: 10%;
min-width: 5rem;
flex-grow: 0;
flex-shrink: 0;
}
div.banner.element.right {
flex-grow: 1;
flex-shrink: 1;
}
div.left-banner.container {
flex-flow: row nowrap;
width: 100%;
height: 100%;
}
img.left-banner.element {
max-height: 80%;
max-width: 80%;
margin: 0 10px;
}
div.right-banner.container {
flex-flow: row-reverse nowrap;
width: 100%;
height: 100%;
}
div.right-banner.element {
width: 100%;
}

60
src/css/content.css Normal file
View File

@@ -0,0 +1,60 @@
div#image-col {
position: absolute;
top: calc(10% + 1.6rem);
left: min(15vw, 70px);
width: calc(100% - min(15vw, 70px));
height: calc(90% - 1.6rem);
}
div.image-row.container {
flex-flow: column nowrap;
align-items: start;
width: calc(100% - 1rem);
margin: 0.5rem;
border: 0.1rem solid #cccccc;
border-radius: 1rem;
}
span.anchor {
position: absolute;
margin-top: calc(-10% - 2rem);
}
div.image-row.container:nth-child(odd) {
background-color: #fefefe;
}
div.image-row.container:nth-child(even) {
background-color: #f0f0f0;
}
div.image-row.element {
height: 100%;
}
h3.image-row.element {
font-size: max(min(3vw, 1.2rem), 10px);
margin: 0.5rem 1rem;
}
div.image.container {
flex-flow: row wrap;
height: 100%;
}
img.image.element {
height: min(25vw, 7rem);
max-width: 16rem;
margin: 0 1rem;
padding: 1rem 0;
}
div.navigate.container {
justify-content: space-around;
width: 100%;
}
button.navigate.element {
height: 2rem;
min-width: 15vw;
font-size: 1rem;
padding: 0 12px;
border: 1px solid grey;
border-radius: 5px;
margin: 5px 10px;
background-color: #eeeeee;
}
button.navigate.element:hover {
background-color: #cccccc;
border: 1px solid grey;
}

26
src/css/general.css Normal file
View File

@@ -0,0 +1,26 @@
html {
font-size: 16px;
}
body {
background-color: white;
font-family: Arial, Helvetica, sans-serif;
color: black;
}
code {
font-family: consolas, monospace;
}
div.container {
display: flex;
align-items: center;
}
div.leftright.container {
height: 100%;
width: 100%;
}
div.leftright.element {
height: 100%;
}
div#image-col {
flex-grow: 1;
}

49
src/css/search-bar.css Normal file
View File

@@ -0,0 +1,49 @@
.search-bar.container {
flex-flow: row nowrap;
align-items: center;
width: calc(100% - 2 * min(4vw, 20px));
padding: 5px min(4vw, 20px);
height: 30px;
}
.search-bar.element {
height: 100%;
}
.search-bar.element:has(input) {
flex-grow: 1;
}
div#search-bar {
position: relative;
width: 65%;
height: 30px;
border: none;
outline: 0;
}
input[type="text"] {
border: 0.5px solid grey;
border-radius: 25px;
height: 100%;
width: 100%;
padding: 0;
overflow: hidden;
text-rendering: auto;
letter-spacing: normal;
word-spacing: normal;
text-transform: none;
text-indent: 12px;
text-shadow: none;
display: inline-block;
text-align: start;
appearance: auto;
}
input[type="text"]:focus {
border: 1px solid grey;
}
div.search-bar > img {
position: relative;
left: -30px;
height: 60%;
padding: 25% 0 15% 0;
}

59
src/css/sidebar.css Normal file
View File

@@ -0,0 +1,59 @@
div.fixed-sidebar {
position: fixed;
top: calc(10% + 1.6rem);
left: 0;
height: calc(90% - 1.6rem);
width: min(15vw, 70px);
background-color: #eeeeee;
z-index: 1;
}
div.fixed-sidebar:hover {
width: auto;
}
div.sidebar.container {
flex-flow: column nowrap;
align-items: start;
height: 100%;
}
div.sidebar.element {
width: 100%;
}
div.alias.container {
flex-flow: column nowrap;
width: 100%;
align-items: start;
}
div.sidebar.element:has(h2#alias-title) {
height: 2.5rem;
border-bottom: 0.5px solid black;
}
h2#alias-title {
margin: min(2vw, 10px);
font-size: min(5vw, 18px);
}
div.sidebar.element:has(div.alias.container) {
height: calc(100% - 2.5rem - 1px);
}
div.alias.container {
height: 100%;
overflow-x: hidden;
overflow-y: auto;
}
button.alias.element {
display: block;
width: 100%;
color: black;
white-space: nowrap;
font-size: min(4vw, 16px);
text-decoration: none;
text-align: start;
padding: min(1.5vw, 7.5px) min(2vw, 10px);
border: 0;
}
button.alias.element:hover {
color: white;
background-color: #333333;
}
button.alias.element.highlight {
background-color: #cccccc;
}

40
src/index.tsx Normal file
View File

@@ -0,0 +1,40 @@
import ReactDOM from 'react-dom/client';
import {BrowserRouter, Routes, Route} from 'react-router-dom';
import Index from './pages/root/Index';
import Error403 from './pages/error/Error403';
import Error404 from './pages/error/Error404';
import './css/general.css';
import './css/banner.css';
import './css/search-bar.css';
import './css/sidebar.css';
import './css/content.css';
function App(){
return <BrowserRouter>
<Routes>
<Route path='/'>
<Route index element={<Index />} />
<Route path='error403' element={<Error403 />} />
<Route path='error404' element={<Error404 />} />
<Route path='*' element={<Error404 />} />
</Route>
</Routes>
</BrowserRouter>
}
function main(){
const element: Element | null = document.getElementById('root');
if(!element) throw Error('element not exist');
const root = ReactDOM.createRoot(element);
if(!root) throw Error('root not exist');
root.render(<App />);
}
try{main();}catch(err: unknown){
if(err instanceof Error)
console.log(err.message);
else
console.log(String(err));
}

6
src/models/Alias.ts Normal file
View File

@@ -0,0 +1,6 @@
export interface Alias{
_id: string;
guild: string;
text: string;
images: string[];
}

9
src/models/AliasAPI.ts Normal file
View File

@@ -0,0 +1,9 @@
export interface ImageAPI{
id: string;
ext: string;
}
export interface AliasAPI{
text: string;
images: ImageAPI[];
}

6
src/models/Guild.ts Normal file
View File

@@ -0,0 +1,6 @@
export interface Guild{
_id: string;
guildId: string;
giveawayLogChannelId: string;
autoroleLogChannelId: string;
}

4
src/models/Image.ts Normal file
View File

@@ -0,0 +1,4 @@
export interface Image{
_id: string; // act as random generated filename
extension: string;
}

View File

@@ -0,0 +1,9 @@
function Error403(){
return <div>
<h1 style={{textAlign: 'center'}}>403 Forbidden</h1>
<p style={{textAlign: 'center'}}>You don't have the permission to access this page.</p>
<p style={{textAlign: 'center'}}>Or your token had been expired.</p>
</div>
}
export default Error403;

View File

@@ -0,0 +1,8 @@
function Error404(){
return <div>
<h1 style={{textAlign: 'center'}}>404 Not Found</h1>
<p style={{textAlign: 'center'}}>The required page does not exist.</p>
</div>
}
export default Error404;

24
src/pages/root/Banner.tsx Normal file
View File

@@ -0,0 +1,24 @@
import {Dispatch} from 'react';
import SearchBar, {SearchBarProps} from './SearchBar';
function Banner(props: SearchBarProps){
return <div id="fixed-banner" className="banner">
<div className="banner container">
<div className="banner element left">
<div className="left-banner container">
<img id="banner-icon" className="left-banner element" src="logo/amane.png"/>
</div>
</div>
<div className="banner element right">
<div className="right-banner container">
<div className="right-banner element">
<SearchBar setInput={props.setInput}/>
</div>
</div>
</div>
</div>
</div>;
}
export default Banner;

View File

@@ -0,0 +1,94 @@
import {useEffect, useState, useRef, Dispatch} from 'react';
import {AliasAPI, ImageAPI} from '../../models/AliasAPI';
import {Image} from '../../models/Image';
import {config} from '../../config';
interface NavigateBarProps{
aliases: AliasAPI[];
page: number;
setPage: Dispatch<number>;
}
interface ContentProps{
aliases: AliasAPI[];
page: number;
setPage: Dispatch<number>;
scroll: number;
setScroll: Dispatch<number>;
}
function NavigateBar(props: NavigateBarProps){
return <div className="navigate container">
<button className="navigate element"
onClick={() => {
props.setPage(Math.max(0, props.page-1));
}}>Prev</button>
<p>{props.page}</p>
<button className="navigate element"
onClick={() => {
props.setPage(Math.min(Math.ceil(
props.aliases.length / config.aliasPerPage) - 1,
props.page+1));
}}>Next</button>
</div>;
}
function Content(props: ContentProps){
const [content, setContent] = useState(Array<any>());
const [scroll, setScroll] = useState(-1);
const ref = useRef(new Array(props.aliases.length));
const handlePage = () => {
const newContent: Array<any> = [];
for(const i in props.aliases.slice(
props.page * config.aliasPerPage,
(props.page + 1) * config.aliasPerPage
)){
const j: number = Number(i) + props.page * config.aliasPerPage;
const a: AliasAPI = props.aliases[j];
newContent.push(
<div key={`${a.text}-image-row`}
className="image-row container">
<span ref={el => ref.current[j] = el} className="anchor"></span>
<h3 className="image-row element">{a.text}</h3>
<div className="image-row element">
<div className="image container">
{a.images.map((img: ImageAPI) =>
<img key={`${a.text}-${img.id}`}
className="image element"
src={`/img/${img.id}${img.ext}`}
title={img.id+img.ext}/>
)}
</div>
</div>
</div>
);
}
setContent(newContent);
};
useEffect(handlePage, [props.aliases, props.page]);
const handleScroll = () => {
if(props.scroll === -1){
if(ref.current[scroll])
ref.current[scroll].scrollIntoView({behavior: 'smooth'});
return;
}
if(ref.current[props.scroll])
ref.current[props.scroll].scrollIntoView({behavior: 'smooth'});
setScroll(props.scroll);
props.setScroll(-1);
};
useEffect(handleScroll, [content, props.scroll]);
return <div id="image-col" className="leftright element">
<NavigateBar aliases={props.aliases}
page={props.page} setPage={props.setPage}/>
{content}
<NavigateBar aliases={props.aliases}
page={props.page} setPage={props.setPage}/>
</div>;
}
export default Content;

74
src/pages/root/Index.tsx Normal file
View File

@@ -0,0 +1,74 @@
import {useEffect, useState} from 'react';
import {Navigate} from 'react-router-dom';
import {AliasAPI} from '../../models/AliasAPI';
import Banner from './Banner';
import Sidebar from './Sidebar';
import Content from './Content';
function Index(){
const [aliases, setAliases] =
useState(Array<AliasAPI>());
const [filtered, setFiltered] =
useState(Array<AliasAPI>());
const [input, setInput] = useState('');
const [status, setStatus] = useState(200);
const [page, setPage] = useState(0);
const [scroll, setScroll] = useState(-1);
const handleFetch = async () => {
try{
const res = await fetch('/api');
if(res.status !== 200)
setStatus(res.status);
setAliases(await res.json());
}catch(err: unknown){
if(err instanceof Error)
console.error(`Error, ${err.message}`);
else console.error(`Error, ${String(err)}`);
}
};
useEffect(() => {
handleFetch().catch(err=>console.error(err))
}, []);
const handleFilter = async () => {
try{
const res: Array<AliasAPI> = [];
for(const i of aliases)
if(i.text.startsWith(input))
res.push(i)
setFiltered(res);
setPage(0);
}catch(err: unknown){
if(err instanceof Error)
console.error(`Error, ${err.message}`);
else console.error(`Error, ${String(err)}`);
}
};
useEffect(() => {
handleFilter().catch(err=>console.error(err))
}, [aliases, input]);
if(status !== 200)
return <Navigate to={`/error${status}`} />;
return <>
<Banner setInput={setInput}/>
<div className="leftright container">
<Sidebar
aliases={filtered}
page={page} setPage={setPage}
setScroll={setScroll}
/>
<Content
aliases={filtered}
page={page} setPage={setPage}
scroll={scroll} setScroll={setScroll}
/>
</div>
</>;
}
export default Index;

View File

@@ -0,0 +1,33 @@
import {useEffect, useState, KeyboardEvent, Dispatch} from 'react';
export interface SearchBarProps{
setInput: Dispatch<string>;
};
function SearchBar(props: SearchBarProps){
const handleInput = (e: KeyboardEvent<HTMLInputElement>) => {
if(e.target instanceof Element && e.key === "Enter"){
props.setInput((e.target as HTMLInputElement).value);
}
};
// useEffect(()=>{}, [props.input]);
return <div className="search-bar container">
<div className="search-bar element">
<input id="search-bar" type="text"
placeholder="Search for aliases..."
autoComplete="off"
onKeyDown={handleInput}
/>
</div>
<div className="search-bar element">
<img
id="search-icon"
src="svg/search.svg"
alt="search-icon"
/>
</div>
</div>;
}
export default SearchBar;

View File

@@ -0,0 +1,60 @@
import {useEffect, useState, Dispatch, useRef} from 'react';
import {AliasAPI} from '../../models/AliasAPI';
import {config} from '../../config';
interface SidebarProps{
aliases: AliasAPI[];
page: number;
setPage: Dispatch<number>;
setScroll: Dispatch<number>;
}
function Sidebar(props: SidebarProps){
const [content, setContent] = useState(Array<any>());
const pageL: number = props.page * config.aliasPerPage;
const pageR: number = pageL + config.aliasPerPage;
const handleClick = (idx: number) => {
props.setPage(Math.floor(idx / config.aliasPerPage));
props.setScroll(idx);
};
const handlePage = async () => {
const newContent: Array<any> = [];
for(const i in props.aliases){
const alias: AliasAPI = props.aliases[i];
newContent.push(
<button key={`${alias.text}-sidebar`}
className={`alias element${
pageL <= Number(i) && Number(i) < pageR ?
' highlight' : ''}`
} title={alias.text}
onClick={() => handleClick(Number(i))}>
{alias.text}
</button>
);
}
setContent(newContent);
};
useEffect(() => {handlePage().catch(
err => console.error(err))}, [props.aliases, props.page]);
return <div id="sidbar-col" className="leftright element">
<div className="fixed-sidebar">
<div className="sidebar container">
<div className="sidebar element">
<h2 id="alias-title">Alias</h2>
</div>
<div className="sidebar element">
<div className="alias container">
{content}
</div>
</div>
</div>
</div>
</div>;
}
export default Sidebar;

26
tsconfig.json Normal file
View File

@@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}