Skip to content

Commit 47fda0f

Browse files
committed
feat: writeup editor v.0.1
1 parent b989765 commit 47fda0f

File tree

9 files changed

+2919
-14
lines changed

9 files changed

+2919
-14
lines changed

.env.development

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
# TDO
1313

14-
NEXT_PUBLIC_API_URL=https://node-api-qi6ms4rtoa-ue.a.run.app
14+
NEXT_PUBLIC_API_URL=http://localhost:3001
1515
NEXT_PUBLIC_FRONTEND_URL=http://localhost:3000
1616
NEXT_PUBLIC_APP_API_KEY=AIzaSyAHz1s-UuNhlZ6aKvqwzmzzidzWxBKw9hw
1717
NEXT_PUBLIC_APP_AUTH_DOMAIN=ctfguide-dev.firebaseapp.com

next.config.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
const removeImports = require("next-remove-imports")();
2+
13
/** @type {import('next').NextConfig} */
24
const nextConfig = {
35
webpack: (config, { dev }) => {
@@ -15,8 +17,10 @@ const nextConfig = {
1517
},
1618
experimental: {
1719
scrollRestoration: true,
20+
esmExternals: true, // Add this to ensure esmExternals is true
1821
},
19-
ignoreDuringBuilds: true
20-
}
22+
ignoreDuringBuilds: true,
23+
transpilePackages: ['react-md-editor']
24+
};
2125

22-
module.exports = nextConfig
26+
module.exports = removeImports(nextConfig);

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@
2121
"@heroicons/react": "^2.0.13",
2222
"@mui/material": "^5.15.2",
2323
"@mui/x-charts": "^6.18.4",
24+
"@react-oauth/google": "^0.12.1",
2425
"@stripe/stripe-js": "^2.1.7",
2526
"@tailwindcss/forms": "^0.5.3",
2627
"@tremor/react": "^1.8.1",
28+
"@uiw/react-markdown-editor": "^6.1.1",
29+
"@uiw/react-md-editor": "3.6.0",
2730
"asciinema-player": "3.6.3",
2831
"autoprefixer": "^10.4.12",
2932
"babel-plugin-macros": "^3.1.0",
30-
"@react-oauth/google": "^0.12.1",
31-
"jwt-decode": "^4.0.0",
3233
"chart.js": "^4.4.1",
3334
"clsx": "^1.2.1",
3435
"corepack": "^0.17.0",
@@ -38,9 +39,11 @@
3839
"focus-visible": "^5.2.0",
3940
"framer-motion": "^10.2.4",
4041
"heroicons": "^2.0.13",
42+
"jwt-decode": "^4.0.0",
4143
"marked": "^4.3.0",
4244
"material-ui-popup-state": "^5.0.5",
4345
"next": "^14.0.2",
46+
"next-remove-imports": "^1.0.12",
4447
"postcss-focus-visible": "^6.0.4",
4548
"puppeteer": "^22.0.0",
4649
"react": "18.2.0",

src/pages/_app.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import 'focus-visible';
22
import '@/styles/tailwind.css';
33
import '@tremor/react/dist/esm/tremor.css';
4+
import '@/styles/markdown.css';
5+
6+
47
import { GoogleOAuthProvider } from '@react-oauth/google';
58

69
export default function App({ Component, pageProps }) {

src/pages/_document.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export default function Document(props) {
66

77
return (
88
<Html
9+
data-color-mode="dark"
910
style={{ backgroundColor: '#161716' }}
1011
className="h-full scroll-smooth bg-white antialiased [font-feature-settings:'ss01']"
1112
lang="en"

src/pages/create/editor.jsx

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import Head from 'next/head';
2+
import { useState, useEffect } from 'react';
3+
import dynamic from 'next/dynamic';
4+
import { StandardNav } from '@/components/StandardNav';
5+
import { Footer } from '@/components/Footer';
6+
import request from "@/utils/request";
7+
import { MarkdownViewer } from '@/components/MarkdownViewer';
8+
9+
export default function Create() {
10+
const [isOpen, setIsOpen] = useState(false);
11+
const [contentPreview, setContentPreview] = useState('');
12+
const [title, setTitle] = useState('');
13+
useEffect(() => {
14+
try {
15+
request(`${process.env.NEXT_PUBLIC_API_URL}/account`, "GET", null)
16+
.then((data) => {
17+
// Handle data
18+
})
19+
.catch((err) => {
20+
console.log(err);
21+
});
22+
} catch (error) {
23+
console.error(error);
24+
}
25+
}, []);
26+
27+
const handleLoad = (event) => {
28+
// let cid = router.query.id; // Challenge ID
29+
30+
31+
}
32+
33+
const initWriteup = (event) => {
34+
// Fetch the data from the form
35+
try {
36+
request(`${process.env.NEXT_PUBLIC_API_URL}/writeups`, "POST", {
37+
title: "Untitled Writeup",
38+
content: "",
39+
})
40+
.then((data) => {
41+
// hide loader
42+
})
43+
.catch((err) => {
44+
console.log(err);
45+
});
46+
47+
} catch (error) {
48+
console.error(error);
49+
}
50+
51+
};
52+
53+
54+
const insertText = (text) => {
55+
const textarea = document.getElementById('content');
56+
const startPos = textarea.selectionStart;
57+
const endPos = textarea.selectionEnd;
58+
const newValue = textarea.value.substring(0, startPos) + text + textarea.value.substring(endPos, textarea.value.length);
59+
setContentPreview(newValue);
60+
textarea.focus();
61+
textarea.selectionEnd = startPos + text.length;
62+
};
63+
64+
return (
65+
<>
66+
<Head>
67+
<title>Create - CTFGuide</title>
68+
<style>
69+
@import url('https://fonts.googleapis.com/css2?family=Poppins&display=swap');
70+
</style>
71+
</Head>
72+
<StandardNav />
73+
<main className='flex items-center justify-center min-h-screen text-center'>
74+
<div className='center text-center mx-auto'>
75+
<h1><i className="fas fa-spinner text-white fa-spin text-4xl mb-5"></i></h1>
76+
<h1 className='text-white text-xl'>Setting up CTFGuide Editor</h1>
77+
</div>
78+
</main>
79+
<main className='hidden'>
80+
81+
82+
<div className='text-white text-xl px-6 py-4 w-full bg-neutral-800 flex'>
83+
<div>
84+
<h1>CTFGuide Editor <span className='bg-blue-600 px-2 rounded-lg text-sm'>BETA</span></h1>
85+
</div>
86+
87+
<div className='ml-auto text-sm '>
88+
<button className='bg-red-600 px-4 py-1 mr-3 rounded-lg'><i className="fas fa-trash fa-fw"></i> Delete</button>
89+
90+
<button className='bg-indigo-600 px-4 py-1 mr-3 rounded-lg'><i className="fas fa-save fa-fw"></i> Save</button>
91+
<button className='bg-green-600 px-4 py-1 mr-3 rounded-lg'><i className="fas fa-rocket fa-fw"></i> Publish</button>
92+
</div>
93+
</div>
94+
95+
<div className='px-6 mx-auto'>
96+
<input
97+
value={title}
98+
onChange={(event) => setTitle(event.target.value)}
99+
className='px-0 mt-2 bg-transparent border-none w-full text-white text-4xl placeholder-neutral-700 focus:outline-none focus:ring-0 focus:border-transparent focus:shadow-none'
100+
placeholder='Enter your title here'
101+
autoFocus
102+
/>
103+
<div className='grid grid-cols-2 gap-x-10 border-t border-neutral-800 '>
104+
<div className='mt-3'>
105+
<div className="toolbar py-1 ">
106+
<button onClick={() => insertText('**bold**')} className="toolbar-button text-white pr-2 mr-1">
107+
<i className="fas fa-bold"></i>
108+
</button>
109+
<button onClick={() => insertText('*italic*')} className="toolbar-button text-white px-2 mr-1" >
110+
<i className="fas fa-italic"></i>
111+
</button>
112+
<button onClick={() => insertText('# Heading')} className="toolbar-button text-white px-2 mr-1">
113+
<i className="fas fa-heading"></i>
114+
</button>
115+
<button onClick={() => insertText('[link](url)')} className="toolbar-button text-white px-2 mr-1">
116+
<i className="fas fa-link"></i>
117+
</button>
118+
<button onClick={() => insertText('```Code```')} className="toolbar-button text-white px-2 mr-1">
119+
<i className="fas fa-code"></i>
120+
</button>
121+
</div>
122+
<textarea
123+
value={contentPreview}
124+
id="content"
125+
placeholder="You can use Markdown here!"
126+
style={{ height: 'calc(100vh - 200px)' }}
127+
className="px-0 h-full w-full rounded-lg border-none placeholder-neutral-700 bg-neutral-900 px-5 py-4 text-white focus:outline-none focus:ring-0 focus:border-transparent focus:shadow-none"
128+
onChange={(event) => {
129+
setContentPreview(event.target.value);
130+
}}
131+
/>
132+
</div>
133+
<div className='border-l border-neutral-800'>
134+
<div contentEditable={false} className='text-white py-4 px-4'>
135+
<MarkdownViewer content={contentPreview} />
136+
</div>
137+
</div>
138+
</div>
139+
</div>
140+
</main>
141+
142+
143+
144+
<div className="hidden center fixed bottom-6 right-6 rounded-md bg-neutral-800/50 text-sm text-white p-2 opacity-40">
145+
Automatically saved at 12:22AM.
146+
</div>
147+
148+
149+
150+
151+
152+
153+
<div className='hidden flex w-full h-full grow basis-0'></div>
154+
<Footer />
155+
</>
156+
);
157+
}

src/styles/markdown.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@import "~@uiw/react-markdown-preview/esm/styles/markdown.css";
2+
@import "~@uiw/react-markdown-editor/markdown-editor.css";

src/styles/tailwind.css

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,30 @@
33
@tailwind components;
44
@tailwind utilities;
55

6+
/* HTML: <div class="loader"></div> */
7+
.loader {
8+
width: 50px;
9+
aspect-ratio: 1;
10+
position: relative;
11+
overflow: hidden;
12+
border: 2px solid;
13+
}
14+
.loader::before {
15+
content: "";
16+
position: absolute;
17+
inset: -48px;
18+
--c:conic-gradient(from 180deg at 50% calc(100% - 2px),#ffffff 90deg, #ffffff00 0);
19+
background: var(--c),var(--c);
20+
background-position: 0 0,8px 0;
21+
background-size: 16px 9.6px;
22+
animation: l16 2s infinite;
23+
}
24+
@keyframes l16 {
25+
50% {background-position: 0 -9.6px ,8px 9.6px; transform:rotate(90deg)}
26+
100% {background-position: 0 -19.2px,8px 19.2px;transform:rotate(180deg)}
27+
}
628
@import url('https://fonts.googleapis.com/css2?family=Poppins&display=swap');
29+
@import "~@uiw/react-markdown-editor/markdown-editor.css";
730

831
@layer utilities {
932
.markdown {

0 commit comments

Comments
 (0)