Compare commits

...

1 Commits

Author SHA1 Message Date
ede31f85b5 Save Markdowns 2024-12-06 15:13:20 +00:00
3 changed files with 115 additions and 112 deletions

View File

@@ -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);
});
};

View File

@@ -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;
}

View File

@@ -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;