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]); }, [id]);
const handleSave = () => { 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"; const method = id ? "PUT" : "POST";
fetch_(url, { fetch_(url, {
method, method,
@@ -47,16 +47,12 @@ const MarkdownEditor = () => {
}, { }, {
use_cache: false, use_cache: false,
use_token: true, use_token: true,
}).then((res) => { }).then((data) => {
if (res.ok) { if(data.error)
navigate("/"); throw new Error(data.error.message);
} else { navigate("/");
return res.json().then((data) => {
throw new Error(data.error || "Failed to save markdown");
});
}
}).catch((err) => { }).catch((err) => {
console.error("Failed to save markdown", err); console.error("Failed to load markdown", err);
}); });
}; };

View File

@@ -1,57 +1,39 @@
.path-manager { .path-manager-body {
border: 1px solid #ddd; display: flex;
border-radius: 8px; flex-direction: column;
padding: 1rem; gap: 0.5rem;
background-color: #f9f9f9;
} }
.path-manager-header { .dropdown {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; position: relative;
margin-bottom: 1rem;
} }
.current-path { .dropdown .dropdown-input {
font-weight: bold; flex: 1;
font-size: 1rem;
} }
.sub-paths { .dropdown .dropdown-menu {
list-style: none; position: absolute;
padding: 0; top: 100%;
margin: 0; 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; padding: 0.5rem;
font-size: 1rem;
color: #4a4a4a;
border-bottom: 1px solid #ddd;
cursor: pointer; cursor: pointer;
transition: background-color 0.2s; transition: background-color 0.2s;
} }
.sub-path-item:last-child { .dropdown-item:hover {
border-bottom: none;
}
.sub-path-item:hover {
background-color: #f0f0f0; 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 { fetch_ } from "../utils/requestUtils";
import config from "../config"; import config from "../config";
import "./PathManager.css"; import "./PathManager.css";
const PathManager = ({ currentPathId = 1, onPathChange }) => { const PathManager = ({ currentPathId = 1, onPathChange }) => {
const [currentPath, setCurrentPath] = useState(""); const [currentPath, setCurrentPath] = useState([{ name: "Root", id: 1 }]);
const [currentId, setCurrentId] = useState(currentPathId); const [currentId, setCurrentId] = useState(currentPathId);
const [subPaths, setSubPaths] = useState([]); const [subPaths, setSubPaths] = useState([]);
const [newDirName, setNewDirName] = useState(""); const [searchTerm, setSearchTerm] = useState("");
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [dropdownActive, setDropdownActive] = useState(false);
const inputRef = useRef();
useEffect(() => { useEffect(() => {
fetchSubPaths(currentId); fetchSubPaths(currentId);
@@ -22,36 +25,34 @@ const PathManager = ({ currentPathId = 1, onPathChange }) => {
.finally(() => setLoading(false)); .finally(() => setLoading(false));
}; };
const handleSubPathClick = (subPath) => { const handlePathClick = (pathId, pathIndex) => {
setCurrentPath(`${currentPath}/${subPath.name}`); const newPath = currentPath.slice(0, pathIndex + 1);
setCurrentId(subPath.id); setCurrentPath(newPath);
onPathChange(subPath.id); setCurrentId(pathId);
onPathChange(pathId);
}; };
const handleBackClick = () => { const handleSubPathSelect = (subPath) => {
if (currentId === 1) return; const updatedPath = [...currentPath, { name: subPath.name, id: subPath.id }];
const pathSegments = currentPath.split("/").filter(Boolean); setCurrentPath(updatedPath);
pathSegments.pop(); setCurrentId(subPath.id);
setCurrentPath(pathSegments.length > 0 ? `/${pathSegments.join("/")}` : ""); onPathChange(subPath.id);
const parentId = pathSegments.length > 0 setSearchTerm("");
? subPaths.find((path) => path.name === pathSegments[pathSegments.length - 1])?.id || 0 setDropdownActive(false);
: 1;
setCurrentId(parentId);
onPathChange(parentId);
}; };
const handleAddDirectory = () => { const handleAddDirectory = () => {
if (!newDirName.trim()) { if (!searchTerm.trim()) {
alert("Directory name cannot be empty."); alert("Directory name cannot be empty.");
return; return;
} }
fetch_(`${config.BACKEND_HOST}/api/path/`, { fetch_(`${config.BACKEND_HOST}/api/path/`, {
method: "POST", 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 }) }, { use_cache: false, use_token: true })
.then((newDir) => { .then((newDir) => {
setSubPaths([...subPaths, newDir]); setSubPaths([...subPaths, newDir]);
setNewDirName(""); setSearchTerm("");
alert("Directory created successfully!"); alert("Directory created successfully!");
}) })
.catch((error) => { .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 ( return (
<div className="path-manager"> <div className="path-manager">
<div className="path-manager-header"> <div className="path-manager-header">
<button <div className="current-path">
className="button is-small is-danger" {currentPath.map((path, index) => (
onClick={handleBackClick} <span
disabled={currentId === 1} key={path.id}
type="button" className="breadcrumb-item is-clickable"
> onClick={() => handlePathClick(path.id, index)}
Back >
</button> {path.name}
<div className="current-path">Current Path: {currentPath || "/"}</div> {index < currentPath.length - 1 && " / "}
</span>
))}
</div>
</div> </div>
<div className="path-manager-body"> <div className="path-manager-body">
{loading ? ( <div className="field has-addons">
<p>Loading subdirectories...</p> <div className="control">
) : ( <input
<ul className="sub-paths"> ref={inputRef}
{subPaths.map((subPath) => ( className="input is-small"
<li type="text"
key={subPath.id} placeholder="Search or create directory"
className="sub-path-item is-clickable" value={searchTerm}
onClick={() => handleSubPathClick(subPath)} onChange={(e) => setSearchTerm(e.target.value)}
> onFocus={handleInputFocus}
{subPath.name} onBlur={handleInputBlur}
</li> />
))} </div>
</ul> <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>
<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> </div>
); );
}; };
export default PathManager; export default PathManager;