markdown editor

This commit is contained in:
h z
2024-12-04 16:53:35 +00:00
parent 55ddd17bf0
commit 413896c54b
9 changed files with 155 additions and 24 deletions

View File

@@ -1,8 +1,9 @@
import React from "react"; import React from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import MainNavigation from "./components/MainNavigation"; import MainNavigation from "./components/Navigations/MainNavigation";
import SideNavigation from "./components/SideNavigation"; import SideNavigation from "./components/Navigations/SideNavigation";
import MarkdownContent from "./components/MarkdownContent"; import MarkdownContent from "./components/MarkdownContent";
import MarkdownEditor from "./components/Markdowns/MarkdownEditor";
import "./App.css"; import "./App.css";
import Callback from "./Callback"; import Callback from "./Callback";
import {config} from "./confs/appConfig"; import {config} from "./confs/appConfig";
@@ -20,6 +21,8 @@ const App = () => {
<Route path="/markdown/:id" element={<MarkdownContent />} /> <Route path="/markdown/:id" element={<MarkdownContent />} />
<Route path="/callback" element={<Callback />} /> <Route path="/callback" element={<Callback />} />
<Route path="/test" element={<h1>TEST</h1>}></Route> <Route path="/test" element={<h1>TEST</h1>}></Route>
<Route path="/markdown/create" element={<MarkdownEditor />}></Route>
<Route path="/markdown/edit/:id" element={<MarkdownEditor />}></Route>
</Routes> </Routes>
</main> </main>
</div> </div>

View File

@@ -7,10 +7,12 @@ export const AuthContext = createContext({
user: null, user: null,
login: () => {}, login: () => {},
logout: () => {}, logout: () => {},
roles: [],
}); });
const AuthProvider = ({ children }) => { const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null); const [user, setUser] = useState(null);
const [roles, setRoles] = useState([]);
const userManager = new UserManager(appConfig.oidcConfig); const userManager = new UserManager(appConfig.oidcConfig);
useEffect(() => { useEffect(() => {
@@ -19,11 +21,16 @@ const AuthProvider = ({ children }) => {
if (user && !user.expired) { if (user && !user.expired) {
setUser(user); setUser(user);
localStorage.setItem("accessToken", user.access_token); localStorage.setItem("accessToken", user.access_token);
const clientRoles = user?.profile?.resource_access?.[appConfig.kc_client_id]?.roles || [];
setRoles(clientRoles);
} else if (user && user.expired) { } else if (user && user.expired) {
userManager.signinSilent() userManager.signinSilent()
.then((newUser) => { .then((newUser) => {
setUser(newUser); setUser(newUser);
localStorage.setItem("accessToken", newUser.access_token); localStorage.setItem("accessToken", newUser.access_token);
const clientRoles =
newUser?.profile?.resource_access?.[appConfig.kc_client_id]?.roles || [];
setRoles(clientRoles);
}) })
.catch((err) => { .catch((err) => {
console.error(err); console.error(err);
@@ -33,7 +40,10 @@ const AuthProvider = ({ children }) => {
}, [userManager]); }, [userManager]);
const login = () => { const login = () => {
userManager.signinRedirect().catch((err) => { userManager
.signinRedirect()
.catch(
(err) => {
console.log(appConfig); console.log(appConfig);
console.log(err); console.log(err);
}); });
@@ -41,7 +51,7 @@ const AuthProvider = ({ children }) => {
const logout = () => userManager.signoutRedirect(); const logout = () => userManager.signoutRedirect();
return ( return (
<AuthContext.Provider value={{ user, login, logout }}> <AuthContext.Provider value={{ user, roles, login, logout }}>
{children} {children}
</AuthContext.Provider> </AuthContext.Provider>
); );

View File

@@ -0,0 +1,97 @@
import React, {useContext, useEffect, useState} from "react";
import {AuthContext} from "../../AuthProvider";
import {useNavigate, useParams} from "react-router-dom";
import {fetchWithCache} from "../../utils/fetchWithCache";
const MarkdownEditor = () => {
const {roles} = useContext(AuthContext);
const navigate = useNavigate();
const {id} = useParams()
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [path, setPath] = useState("");
useEffect(() => {
if(id){
fetchWithCache(`/api/markdown/${id}`)
.then((data) => {
setTitle(data.title);
setContent(data.content);
setPath(data.path);
})
.catch((err) => {console.error("failed to load markdown", err)});
}
}, [id]);
const handleSave = () => {
const url = id ? `/api/markdown/${id}` : "/api/markdown";
const method = id ? "PUT" : "POST";
fetch (url, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${localStorage.getItem("accessToken")}`,
},
body: JSON.stringify({title, content, path}),
})
.then((res) => {
if (res.ok) {
navigate("/");
}else {
return res.json().then((data) => {
throw new Error(data.error || "Failed to save markdown");
})
}
})
.catch((err) => {console.error("failed to load markdown", err)});
};
const hasPermission = roles.includes("admin") || roles.includes("creator");
if(! hasPermission)
return (
<div> Can not crete(Permission Denied)</div>
);
return (
<div>
<h2>{id ? "Edit Markdown" : "Create Markdown"}</h2>
<form>
<div>
<label>
Title:
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
</label>
</div>
<div>
<label>
Path:
<input
type="text"
value={path}
onChange={(e) => setPath(e.target.value)}
/>
</label>
</div>
<div>
<label>
Content:
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
/>
</label>
</div>
<button type="button" onClick={handleSave}>
Save
</button>
</form>
</div>
);
}
export default MarkdownEditor;

View File

@@ -3,7 +3,7 @@
import React, {useContext} from "react"; import React, {useContext} from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import "./MainNavigation.css"; import "./MainNavigation.css";
import {AuthContext} from "../AuthProvider"; import {AuthContext} from "../../AuthProvider";
const MainNavigation = () => { const MainNavigation = () => {
const { user, login, logout } = useContext(AuthContext); const { user, login, logout } = useContext(AuthContext);

View File

@@ -3,7 +3,8 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import "./SideNavigation.css"; import "./SideNavigation.css";
import {fetchWithCache} from "../utils/fetchWithCache"; import {fetchWithCache} from "../../utils/fetchWithCache";
import PermissionGuard from "../PermissionGuard";
const SideNavigation = () => { const SideNavigation = () => {
const [markdowns, setMarkdowns] = useState([]); const [markdowns, setMarkdowns] = useState([]);
@@ -40,6 +41,12 @@ const SideNavigation = () => {
function renderTree(node, basePath = "") { function renderTree(node, basePath = "") {
return ( return (
<ul> <ul>
<li>
<PermissionGuard rolesRequired={["admin", "creator"]} >
<Link to="/markdown/create">Create New Markdown</Link>
</PermissionGuard>
</li>
{Object.entries(node).map(([key, value]) => { {Object.entries(node).map(([key, value]) => {
if (value.markdown) { if (value.markdown) {
return ( return (

View File

@@ -0,0 +1,15 @@
import {useContext} from "react";
import {AuthContext} from "../AuthProvider";
const PermissionGuard = ({rolesRequired, children}) => {
const { roles = [] } = useContext(AuthContext);
const hasPermission = rolesRequired.some((role) => roles.includes(role));
if (!hasPermission) {
console.log("F");
return null;
}
return children;
}
export default PermissionGuard;

View File

@@ -20,10 +20,9 @@ const config = async () => {
redirect_uri: `${appConfig.serverHost}/callback`, redirect_uri: `${appConfig.serverHost}/callback`,
post_logout_redirect_uri: appConfig.serverHost, post_logout_redirect_uri: appConfig.serverHost,
response_type: "code", response_type: "code",
scope: "openid profile email", scope: "openid profile email roles",
}; };
console.log(appConfig);
}; };