kc token public key/token issue, path root set to 1
This commit is contained in:
@@ -10,7 +10,8 @@ import { okaidia } from "react-syntax-highlighter/dist/esm/styles/prism";
|
||||
import "katex/dist/katex.min.css";
|
||||
import "./MarkdownEditor.css";
|
||||
import config from "../../config";
|
||||
import {fetch_} from "../../utils/requestUtils";
|
||||
import { fetch_ } from "../../utils/requestUtils";
|
||||
import PathManager from "../PathManager";
|
||||
|
||||
const MarkdownEditor = () => {
|
||||
const { roles } = useContext(AuthContext);
|
||||
@@ -18,7 +19,7 @@ const MarkdownEditor = () => {
|
||||
const { id } = useParams();
|
||||
const [title, setTitle] = useState("");
|
||||
const [content, setContent] = useState("");
|
||||
const [path, setPath] = useState("");
|
||||
const [pathId, setPathId] = useState(1);
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
@@ -29,10 +30,10 @@ const MarkdownEditor = () => {
|
||||
.then((data) => {
|
||||
setTitle(data.title);
|
||||
setContent(data.content);
|
||||
setPath(data.path);
|
||||
setPathId(data.path_id);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("failed to load markdown", err);
|
||||
console.error("Failed to load markdown", err);
|
||||
});
|
||||
}
|
||||
}, [id]);
|
||||
@@ -42,26 +43,27 @@ const MarkdownEditor = () => {
|
||||
const method = id ? "PUT" : "POST";
|
||||
fetch_(url, {
|
||||
method,
|
||||
body: JSON.stringify({ title, content, path }),
|
||||
body: JSON.stringify({ title, content, path_id: pathId }),
|
||||
}, {
|
||||
use_cache: false,
|
||||
use_token: true,
|
||||
}).then((res) => {
|
||||
if(res.ok)
|
||||
if (res.ok) {
|
||||
navigate("/");
|
||||
else
|
||||
} else {
|
||||
return res.json().then((data) => {
|
||||
throw new Error(data.error || "Failed to load markdown");
|
||||
throw new Error(data.error || "Failed to save markdown");
|
||||
});
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.error("failed to load markdown", err);
|
||||
})
|
||||
|
||||
console.error("Failed to save markdown", err);
|
||||
});
|
||||
};
|
||||
|
||||
const hasPermission = roles.includes("admin") || roles.includes("creator");
|
||||
if (!hasPermission)
|
||||
if (!hasPermission) {
|
||||
return <div className="notification is-danger">Permission Denied</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mt-5 markdown-editor-container">
|
||||
@@ -84,18 +86,13 @@ const MarkdownEditor = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Path Field */}
|
||||
{/* PathManager Field */}
|
||||
<div className="field">
|
||||
<label className="label">Path</label>
|
||||
<div className="control">
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
placeholder="Enter path"
|
||||
value={path}
|
||||
onChange={(e) => setPath(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<PathManager
|
||||
currentPathId={pathId}
|
||||
onPathChange={setPathId}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Content Field */}
|
||||
|
||||
@@ -58,14 +58,22 @@ const MainNavigation = () => {
|
||||
<span className="button is-primary is-light">
|
||||
{user.profile.name}
|
||||
</span>
|
||||
<button className="button is-danger" onClick={logout}>
|
||||
<button
|
||||
className="button is-danger"
|
||||
onClick={logout}
|
||||
type="button"
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="navbar-item">
|
||||
<button className="button is-primary" onClick={login}>
|
||||
<button
|
||||
className="button is-primary"
|
||||
onClick={login}
|
||||
type="button"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -118,12 +118,14 @@ const PathNode = ({ path, isRoot = false }) => {
|
||||
<button
|
||||
onClick={handleEdit}
|
||||
className="button is-small is-info is-light"
|
||||
type="button"
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
onClick={handleDelete}
|
||||
className="button is-small is-danger is-light"
|
||||
type="button"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
|
||||
57
src/components/PathManager.css
Normal file
57
src/components/PathManager.css
Normal file
@@ -0,0 +1,57 @@
|
||||
.path-manager {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.path-manager-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.current-path {
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.sub-paths {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sub-path-item {
|
||||
padding: 0.5rem;
|
||||
font-size: 1rem;
|
||||
color: #4a4a4a;
|
||||
border-bottom: 1px solid #ddd;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.sub-path-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.sub-path-item:hover {
|
||||
background-color: #f0f0f0;
|
||||
color: #00d1b2;
|
||||
}
|
||||
|
||||
.path-manager-footer {
|
||||
margin-top: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.path-manager-footer .input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.path-manager-footer .button {
|
||||
white-space: nowrap;
|
||||
}
|
||||
116
src/components/PathManager.js
Normal file
116
src/components/PathManager.js
Normal file
@@ -0,0 +1,116 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { fetch_ } from "../utils/requestUtils";
|
||||
import config from "../config";
|
||||
import "./PathManager.css";
|
||||
|
||||
const PathManager = ({ currentPathId = 1, onPathChange }) => {
|
||||
const [currentPath, setCurrentPath] = useState("");
|
||||
const [currentId, setCurrentId] = useState(currentPathId);
|
||||
const [subPaths, setSubPaths] = useState([]);
|
||||
const [newDirName, setNewDirName] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchSubPaths(currentId);
|
||||
}, [currentId]);
|
||||
|
||||
const fetchSubPaths = (pathId) => {
|
||||
setLoading(true);
|
||||
fetch_(`${config.BACKEND_HOST}/api/path/parent/${pathId}`, {}, { use_cache: false, use_token: true })
|
||||
.then((data) => setSubPaths(data))
|
||||
.catch((error) => console.error("Failed to fetch subdirectories:", error))
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
|
||||
const handleSubPathClick = (subPath) => {
|
||||
setCurrentPath(`${currentPath}/${subPath.name}`);
|
||||
setCurrentId(subPath.id);
|
||||
onPathChange(subPath.id);
|
||||
};
|
||||
|
||||
const handleBackClick = () => {
|
||||
if (currentId === 1) return;
|
||||
const pathSegments = currentPath.split("/").filter(Boolean);
|
||||
pathSegments.pop();
|
||||
setCurrentPath(pathSegments.length > 0 ? `/${pathSegments.join("/")}` : "");
|
||||
const parentId = pathSegments.length > 0
|
||||
? subPaths.find((path) => path.name === pathSegments[pathSegments.length - 1])?.id || 0
|
||||
: 1;
|
||||
setCurrentId(parentId);
|
||||
onPathChange(parentId);
|
||||
};
|
||||
|
||||
const handleAddDirectory = () => {
|
||||
if (!newDirName.trim()) {
|
||||
alert("Directory name cannot be empty.");
|
||||
return;
|
||||
}
|
||||
fetch_(`${config.BACKEND_HOST}/api/path/`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ name: newDirName.trim(), parent_id: currentId }),
|
||||
}, { use_cache: false, use_token: true })
|
||||
.then((newDir) => {
|
||||
setSubPaths([...subPaths, newDir]);
|
||||
setNewDirName("");
|
||||
alert("Directory created successfully!");
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to create directory:", error);
|
||||
alert("Failed to create directory.");
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="path-manager">
|
||||
<div className="path-manager-header">
|
||||
<button
|
||||
className="button is-small is-danger"
|
||||
onClick={handleBackClick}
|
||||
disabled={currentId === 1}
|
||||
type="button"
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
<div className="current-path">Current Path: {currentPath || "/"}</div>
|
||||
</div>
|
||||
|
||||
<div className="path-manager-body">
|
||||
{loading ? (
|
||||
<p>Loading subdirectories...</p>
|
||||
) : (
|
||||
<ul className="sub-paths">
|
||||
{subPaths.map((subPath) => (
|
||||
<li
|
||||
key={subPath.id}
|
||||
className="sub-path-item is-clickable"
|
||||
onClick={() => handleSubPathClick(subPath)}
|
||||
>
|
||||
{subPath.name}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="path-manager-footer">
|
||||
<input
|
||||
className="input is-small"
|
||||
type="text"
|
||||
placeholder="New directory name"
|
||||
value={newDirName}
|
||||
onChange={(e) => setNewDirName(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
className="button is-small is-primary"
|
||||
onClick={handleAddDirectory}
|
||||
disabled={loading || !newDirName.trim()}
|
||||
type="button"
|
||||
>
|
||||
Add Directory
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PathManager;
|
||||
@@ -6,7 +6,6 @@ const PermissionGuard = ({rolesRequired, children}) => {
|
||||
const hasPermission = rolesRequired.some((role) => roles.includes(role));
|
||||
|
||||
if (!hasPermission) {
|
||||
console.log("F");
|
||||
return null;
|
||||
}
|
||||
return children;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import {data} from "react-router-dom";
|
||||
|
||||
const ongoingRequests = new Map();
|
||||
|
||||
|
||||
@@ -40,9 +42,15 @@ export async function fetch_(url, init = {}, init_options = {}){
|
||||
headers: {
|
||||
...(init.headers || {}),
|
||||
...(token ? {Authorization: `Bearer ${token}`} : {}),
|
||||
...(init.method && ['PUT', 'POST'].includes(init.method.toUpperCase())
|
||||
? {'Content-Type': 'application/json'}
|
||||
: {}),
|
||||
}
|
||||
};
|
||||
|
||||
if(options.use_cache && ongoingRequests.has(options.cache_key)){
|
||||
return ongoingRequests.get(options.cache_key);
|
||||
}
|
||||
const now = Date.now();
|
||||
const cached_data = localStorage.getItem(options.cache_key);
|
||||
if(options.use_cache && cached_data){
|
||||
@@ -54,11 +62,13 @@ export async function fetch_(url, init = {}, init_options = {}){
|
||||
try {
|
||||
const fetchPromise = fetch(url, request_options)
|
||||
.then((response) => {
|
||||
if(!response.ok)
|
||||
if(!response.ok) {
|
||||
throw new Error(`RESPONSE_ERROR: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
|
||||
if (options.use_cache)
|
||||
{
|
||||
localStorage.setItem(
|
||||
@@ -79,9 +89,6 @@ export async function fetch_(url, init = {}, init_options = {}){
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user