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 "katex/dist/katex.min.css";
|
||||||
import "./MarkdownEditor.css";
|
import "./MarkdownEditor.css";
|
||||||
import config from "../../config";
|
import config from "../../config";
|
||||||
import {fetch_} from "../../utils/requestUtils";
|
import { fetch_ } from "../../utils/requestUtils";
|
||||||
|
import PathManager from "../PathManager";
|
||||||
|
|
||||||
const MarkdownEditor = () => {
|
const MarkdownEditor = () => {
|
||||||
const { roles } = useContext(AuthContext);
|
const { roles } = useContext(AuthContext);
|
||||||
@@ -18,7 +19,7 @@ const MarkdownEditor = () => {
|
|||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const [title, setTitle] = useState("");
|
const [title, setTitle] = useState("");
|
||||||
const [content, setContent] = useState("");
|
const [content, setContent] = useState("");
|
||||||
const [path, setPath] = useState("");
|
const [pathId, setPathId] = useState(1);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (id) {
|
if (id) {
|
||||||
@@ -29,10 +30,10 @@ const MarkdownEditor = () => {
|
|||||||
.then((data) => {
|
.then((data) => {
|
||||||
setTitle(data.title);
|
setTitle(data.title);
|
||||||
setContent(data.content);
|
setContent(data.content);
|
||||||
setPath(data.path);
|
setPathId(data.path_id);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error("failed to load markdown", err);
|
console.error("Failed to load markdown", err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [id]);
|
}, [id]);
|
||||||
@@ -42,26 +43,27 @@ const MarkdownEditor = () => {
|
|||||||
const method = id ? "PUT" : "POST";
|
const method = id ? "PUT" : "POST";
|
||||||
fetch_(url, {
|
fetch_(url, {
|
||||||
method,
|
method,
|
||||||
body: JSON.stringify({ title, content, path }),
|
body: JSON.stringify({ title, content, path_id: pathId }),
|
||||||
}, {
|
}, {
|
||||||
use_cache: false,
|
use_cache: false,
|
||||||
use_token: true,
|
use_token: true,
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
if(res.ok)
|
if (res.ok) {
|
||||||
navigate("/");
|
navigate("/");
|
||||||
else
|
} else {
|
||||||
return res.json().then((data) => {
|
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) => {
|
}).catch((err) => {
|
||||||
console.error("failed to load markdown", err);
|
console.error("Failed to save markdown", err);
|
||||||
})
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasPermission = roles.includes("admin") || roles.includes("creator");
|
const hasPermission = roles.includes("admin") || roles.includes("creator");
|
||||||
if (!hasPermission)
|
if (!hasPermission) {
|
||||||
return <div className="notification is-danger">Permission Denied</div>;
|
return <div className="notification is-danger">Permission Denied</div>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mt-5 markdown-editor-container">
|
<div className="container mt-5 markdown-editor-container">
|
||||||
@@ -84,18 +86,13 @@ const MarkdownEditor = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Path Field */}
|
{/* PathManager Field */}
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<label className="label">Path</label>
|
<label className="label">Path</label>
|
||||||
<div className="control">
|
<PathManager
|
||||||
<input
|
currentPathId={pathId}
|
||||||
className="input"
|
onPathChange={setPathId}
|
||||||
type="text"
|
/>
|
||||||
placeholder="Enter path"
|
|
||||||
value={path}
|
|
||||||
onChange={(e) => setPath(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content Field */}
|
{/* Content Field */}
|
||||||
|
|||||||
@@ -58,14 +58,22 @@ const MainNavigation = () => {
|
|||||||
<span className="button is-primary is-light">
|
<span className="button is-primary is-light">
|
||||||
{user.profile.name}
|
{user.profile.name}
|
||||||
</span>
|
</span>
|
||||||
<button className="button is-danger" onClick={logout}>
|
<button
|
||||||
|
className="button is-danger"
|
||||||
|
onClick={logout}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
Logout
|
Logout
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="navbar-item">
|
<div className="navbar-item">
|
||||||
<button className="button is-primary" onClick={login}>
|
<button
|
||||||
|
className="button is-primary"
|
||||||
|
onClick={login}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
Login
|
Login
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -118,12 +118,14 @@ const PathNode = ({ path, isRoot = false }) => {
|
|||||||
<button
|
<button
|
||||||
onClick={handleEdit}
|
onClick={handleEdit}
|
||||||
className="button is-small is-info is-light"
|
className="button is-small is-info is-light"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
className="button is-small is-danger is-light"
|
className="button is-small is-danger is-light"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</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));
|
const hasPermission = rolesRequired.some((role) => roles.includes(role));
|
||||||
|
|
||||||
if (!hasPermission) {
|
if (!hasPermission) {
|
||||||
console.log("F");
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return children;
|
return children;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import {data} from "react-router-dom";
|
||||||
|
|
||||||
const ongoingRequests = new Map();
|
const ongoingRequests = new Map();
|
||||||
|
|
||||||
|
|
||||||
@@ -40,9 +42,15 @@ export async function fetch_(url, init = {}, init_options = {}){
|
|||||||
headers: {
|
headers: {
|
||||||
...(init.headers || {}),
|
...(init.headers || {}),
|
||||||
...(token ? {Authorization: `Bearer ${token}`} : {}),
|
...(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 now = Date.now();
|
||||||
const cached_data = localStorage.getItem(options.cache_key);
|
const cached_data = localStorage.getItem(options.cache_key);
|
||||||
if(options.use_cache && cached_data){
|
if(options.use_cache && cached_data){
|
||||||
@@ -54,11 +62,13 @@ export async function fetch_(url, init = {}, init_options = {}){
|
|||||||
try {
|
try {
|
||||||
const fetchPromise = fetch(url, request_options)
|
const fetchPromise = fetch(url, request_options)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if(!response.ok)
|
if(!response.ok) {
|
||||||
throw new Error(`RESPONSE_ERROR: ${response.status}`);
|
throw new Error(`RESPONSE_ERROR: ${response.status}`);
|
||||||
|
}
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
|
|
||||||
if (options.use_cache)
|
if (options.use_cache)
|
||||||
{
|
{
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
@@ -79,9 +89,6 @@ export async function fetch_(url, init = {}, init_options = {}){
|
|||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user