Compare commits
1 Commits
6d96b658f0
...
ede31f85b5
| Author | SHA1 | Date | |
|---|---|---|---|
| ede31f85b5 |
@@ -39,7 +39,7 @@ const MarkdownEditor = () => {
|
||||
}, [id]);
|
||||
|
||||
const handleSave = () => {
|
||||
const url = id ? `${config.BACKEND_HOST}/api/markdown/${id}` : `${config.BACKEND_HOST}/api/markdown`;
|
||||
const url = id ? `${config.BACKEND_HOST}/api/markdown/${id}` : `${config.BACKEND_HOST}/api/markdown/`;
|
||||
const method = id ? "PUT" : "POST";
|
||||
fetch_(url, {
|
||||
method,
|
||||
@@ -47,16 +47,12 @@ const MarkdownEditor = () => {
|
||||
}, {
|
||||
use_cache: false,
|
||||
use_token: true,
|
||||
}).then((res) => {
|
||||
if (res.ok) {
|
||||
navigate("/");
|
||||
} else {
|
||||
return res.json().then((data) => {
|
||||
throw new Error(data.error || "Failed to save markdown");
|
||||
});
|
||||
}
|
||||
}).then((data) => {
|
||||
if(data.error)
|
||||
throw new Error(data.error.message);
|
||||
navigate("/");
|
||||
}).catch((err) => {
|
||||
console.error("Failed to save markdown", err);
|
||||
console.error("Failed to load markdown", err);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,57 +1,39 @@
|
||||
.path-manager {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
background-color: #f9f9f9;
|
||||
.path-manager-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.path-manager-header {
|
||||
.dropdown {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.current-path {
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
.dropdown .dropdown-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sub-paths {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
.dropdown .dropdown-menu {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 0.25rem;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sub-path-item {
|
||||
.dropdown-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 {
|
||||
.dropdown-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;
|
||||
}
|
||||
@@ -1,14 +1,17 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import { fetch_ } from "../utils/requestUtils";
|
||||
import config from "../config";
|
||||
import "./PathManager.css";
|
||||
|
||||
const PathManager = ({ currentPathId = 1, onPathChange }) => {
|
||||
const [currentPath, setCurrentPath] = useState("");
|
||||
const [currentPath, setCurrentPath] = useState([{ name: "Root", id: 1 }]);
|
||||
const [currentId, setCurrentId] = useState(currentPathId);
|
||||
const [subPaths, setSubPaths] = useState([]);
|
||||
const [newDirName, setNewDirName] = useState("");
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [dropdownActive, setDropdownActive] = useState(false);
|
||||
|
||||
const inputRef = useRef();
|
||||
|
||||
useEffect(() => {
|
||||
fetchSubPaths(currentId);
|
||||
@@ -22,36 +25,34 @@ const PathManager = ({ currentPathId = 1, onPathChange }) => {
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
|
||||
const handleSubPathClick = (subPath) => {
|
||||
setCurrentPath(`${currentPath}/${subPath.name}`);
|
||||
setCurrentId(subPath.id);
|
||||
onPathChange(subPath.id);
|
||||
const handlePathClick = (pathId, pathIndex) => {
|
||||
const newPath = currentPath.slice(0, pathIndex + 1);
|
||||
setCurrentPath(newPath);
|
||||
setCurrentId(pathId);
|
||||
onPathChange(pathId);
|
||||
};
|
||||
|
||||
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 handleSubPathSelect = (subPath) => {
|
||||
const updatedPath = [...currentPath, { name: subPath.name, id: subPath.id }];
|
||||
setCurrentPath(updatedPath);
|
||||
setCurrentId(subPath.id);
|
||||
onPathChange(subPath.id);
|
||||
setSearchTerm("");
|
||||
setDropdownActive(false);
|
||||
};
|
||||
|
||||
const handleAddDirectory = () => {
|
||||
if (!newDirName.trim()) {
|
||||
if (!searchTerm.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 }),
|
||||
body: JSON.stringify({ name: searchTerm.trim(), parent_id: currentId }),
|
||||
}, { use_cache: false, use_token: true })
|
||||
.then((newDir) => {
|
||||
setSubPaths([...subPaths, newDir]);
|
||||
setNewDirName("");
|
||||
setSearchTerm("");
|
||||
alert("Directory created successfully!");
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -60,57 +61,81 @@ const PathManager = ({ currentPathId = 1, onPathChange }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleInputFocus = () => setDropdownActive(true);
|
||||
const handleInputBlur = () => {
|
||||
setTimeout(() => setDropdownActive(false), 150);
|
||||
};
|
||||
|
||||
const filteredSubPaths = subPaths.filter((path) =>
|
||||
path.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
|
||||
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 className="current-path">
|
||||
{currentPath.map((path, index) => (
|
||||
<span
|
||||
key={path.id}
|
||||
className="breadcrumb-item is-clickable"
|
||||
onClick={() => handlePathClick(path.id, index)}
|
||||
>
|
||||
{path.name}
|
||||
{index < currentPath.length - 1 && " / "}
|
||||
</span>
|
||||
))}
|
||||
</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 className="field has-addons">
|
||||
<div className="control">
|
||||
<input
|
||||
ref={inputRef}
|
||||
className="input is-small"
|
||||
type="text"
|
||||
placeholder="Search or create directory"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
onFocus={handleInputFocus}
|
||||
onBlur={handleInputBlur}
|
||||
/>
|
||||
</div>
|
||||
<div className="control">
|
||||
<button
|
||||
className="button is-small is-primary"
|
||||
onClick={handleAddDirectory}
|
||||
disabled={loading || !searchTerm.trim()}
|
||||
type="button"
|
||||
>
|
||||
Create "{searchTerm}"
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{dropdownActive && (
|
||||
<div className="dropdown is-active">
|
||||
<div className="dropdown-menu">
|
||||
<div className="dropdown-content">
|
||||
{filteredSubPaths.length > 0 ? (
|
||||
filteredSubPaths.map((subPath) => (
|
||||
<a
|
||||
key={subPath.id}
|
||||
className="dropdown-item"
|
||||
onClick={() => handleSubPathSelect(subPath)}
|
||||
>
|
||||
{subPath.name}
|
||||
</a>
|
||||
))
|
||||
) : (
|
||||
<div className="dropdown-item">No matches found</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</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;
|
||||
export default PathManager;
|
||||
|
||||
Reference in New Issue
Block a user