add: tree / search
This commit is contained in:
@@ -2,7 +2,7 @@ import React, {useState} from "react";
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import PermissionGuard from "../PermissionGuard";
|
import PermissionGuard from "../PermissionGuard";
|
||||||
import "./PathNode.css";
|
import "./PathNode.css";
|
||||||
import {useDeletePath, useMovePath, usePaths, useUpdatePath} from "../../utils/path-queries";
|
import {useDeletePath, useMovePath, usePath, usePaths, useUpdatePath} from "../../utils/path-queries";
|
||||||
import {useIndexMarkdown, useMarkdownsByPath, useMoveMarkdown} from "../../utils/markdown-queries";
|
import {useIndexMarkdown, useMarkdownsByPath, useMoveMarkdown} from "../../utils/markdown-queries";
|
||||||
import MarkdownNode from "./MarkdownNode";
|
import MarkdownNode from "./MarkdownNode";
|
||||||
|
|
||||||
@@ -11,8 +11,8 @@ const PathNode = ({ path, isRoot = false }) => {
|
|||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
const [newName, setNewName] = useState(path.name);
|
const [newName, setNewName] = useState(path.name);
|
||||||
|
|
||||||
const { data: childPaths, isLoading: isChildLoading, error: childError } = usePaths(path.id);
|
// const { data: childPaths, isLoading: isChildLoading, error: childError } = usePaths(path.id);
|
||||||
const { data: markdowns, isLoading: isMarkdownLoading, error: markdownError } = useMarkdownsByPath(path.id);
|
// const { data: markdowns, isLoading: isMarkdownLoading, error: markdownError } = useMarkdownsByPath(path.id);
|
||||||
const deletePath = useDeletePath();
|
const deletePath = useDeletePath();
|
||||||
const updatePath = useUpdatePath();
|
const updatePath = useUpdatePath();
|
||||||
|
|
||||||
@@ -60,18 +60,40 @@ const PathNode = ({ path, isRoot = false }) => {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const childPaths = path.children.filter(x => x.type==="path");
|
||||||
const sortedPaths = childPaths
|
const sortedPaths = childPaths
|
||||||
? childPaths.slice().sort((a, b) => a.order.localeCompare(b.order))
|
? childPaths.slice().sort((a, b) => a.order.localeCompare(b.order))
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
|
const markdowns = path.children.filter(x => x.type==="markdown");
|
||||||
const sortedMarkdowns = markdowns
|
const sortedMarkdowns = markdowns
|
||||||
? markdowns.filter(md => md.title !== "index").sort((a, b) => a.order.localeCompare(b.order))
|
? markdowns.filter(md => md.title !== "index").sort((a, b) => a.order.localeCompare(b.order))
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
if(childError || markdownError){
|
/* if(childError || markdownError){
|
||||||
return <li>Error...</li>;
|
return <li>Error...</li>;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
|
if(isRoot)
|
||||||
|
return (
|
||||||
|
<ul className="menu-list">
|
||||||
|
{sortedPaths.map((path) => (
|
||||||
|
<PathNode
|
||||||
|
key={path.id}
|
||||||
|
path={path}
|
||||||
|
isRoot={false}
|
||||||
|
onSave={handleSave}
|
||||||
|
onDelete={handleDelete}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{sortedMarkdowns.filter(md => md.title !== "index").map((markdown) => (
|
||||||
|
<MarkdownNode
|
||||||
|
markdown={markdown}
|
||||||
|
handleMoveMarkdown={handleMoveMarkdown}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<li>
|
<li>
|
||||||
<div className="path-node-header field has-addons">
|
<div className="path-node-header field has-addons">
|
||||||
@@ -167,7 +189,6 @@ const PathNode = ({ path, isRoot = false }) => {
|
|||||||
|
|
||||||
{isExpanded && (
|
{isExpanded && (
|
||||||
<ul>
|
<ul>
|
||||||
{isChildLoading && <p>Loading...</p>}
|
|
||||||
{sortedPaths.map((child) => (
|
{sortedPaths.map((child) => (
|
||||||
<PathNode
|
<PathNode
|
||||||
key={child.id}
|
key={child.id}
|
||||||
|
|||||||
@@ -3,23 +3,15 @@ import PathNode from "./PathNode";
|
|||||||
import "./SideNavigation.css";
|
import "./SideNavigation.css";
|
||||||
import {useDeletePath, usePaths, useUpdatePath} from "../../utils/path-queries";
|
import {useDeletePath, usePaths, useUpdatePath} from "../../utils/path-queries";
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {useSearchMarkdown} from "../../utils/markdown-queries";
|
import {useTree} from "../../utils/tree-queries";
|
||||||
import MarkdownNode from "./MarkdownNode";
|
|
||||||
|
|
||||||
const SideNavigation = () => {
|
const SideNavigation = () => {
|
||||||
const {data: paths, isLoading, error } = usePaths(1);
|
const {data: tree, isLoading, error} = useTree();
|
||||||
const deletePath = useDeletePath();
|
const deletePath = useDeletePath();
|
||||||
const updatePath = useUpdatePath();
|
const updatePath = useUpdatePath();
|
||||||
const [searchTerm, setSearchTerm] = React.useState("");
|
const [searchTerm, setSearchTerm] = React.useState("");
|
||||||
const [keyword, setKeyword] = React.useState("");
|
const [keyword, setKeyword] = React.useState("");
|
||||||
const [searchMode, setSearchMode] = React.useState(false);
|
const [searchMode, setSearchMode] = React.useState(false);
|
||||||
|
|
||||||
const {data: searchResults, isLoading: isSearching} = useSearchMarkdown(keyword, {
|
|
||||||
enabled: searchMode && !!searchMode,
|
|
||||||
});
|
|
||||||
const sortedPaths = paths
|
|
||||||
? paths.slice().sort((a, b) => a.order.localeCompare(b.order))
|
|
||||||
: [];
|
|
||||||
const handleDelete = (id) => {
|
const handleDelete = (id) => {
|
||||||
if (window.confirm("Are you sure you want to delete this path?")){
|
if (window.confirm("Are you sure you want to delete this path?")){
|
||||||
deletePath.mutate(id, {
|
deletePath.mutate(id, {
|
||||||
@@ -30,13 +22,30 @@ const SideNavigation = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSearch = () => {
|
|
||||||
setSearchMode(true);
|
const filterTree = (t, k) => {
|
||||||
setKeyword(searchTerm);
|
if(t === undefined)
|
||||||
}
|
return undefined;
|
||||||
const exitSearch = () => {
|
if (t.type === "path") {
|
||||||
setSearchMode(false);
|
if (t.name.includes(k)) {
|
||||||
}
|
return { ...t };
|
||||||
|
}
|
||||||
|
const filteredChildren = (t.children || [])
|
||||||
|
.map(c => filterTree(c, k))
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
if (filteredChildren.length > 0) {
|
||||||
|
return { ...t, children: filteredChildren };
|
||||||
|
}
|
||||||
|
} else if (t.type === "markdown") {
|
||||||
|
if (t.title.includes(k)) {
|
||||||
|
return { ...t };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredTree = filterTree(tree, keyword);
|
||||||
|
|
||||||
const handleSave = (id, newName) => {
|
const handleSave = (id, newName) => {
|
||||||
updatePath.mutate({ id, data: {name: newName }} , {
|
updatePath.mutate({ id, data: {name: newName }} , {
|
||||||
@@ -45,55 +54,17 @@ const SideNavigation = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
if(!searchMode && isLoading){
|
if (isLoading) return <aside className="menu"><p>Loading...</p></aside>;
|
||||||
return <aside className="menu"><p>Loading...</p></aside>;
|
if (error) return <aside className="menu"><p>Error loading tree</p></aside>;
|
||||||
}
|
|
||||||
if(searchMode && isSearching){
|
|
||||||
return <aside className="menu"><p>Loading...</p></aside>;
|
|
||||||
}
|
|
||||||
if(error){
|
|
||||||
return <aside className="menu"><p>Error...</p></aside>;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="menu">
|
<aside className="menu">
|
||||||
<div className="field has-addons mb-2">
|
<div className="control is-expanded">
|
||||||
<div className="control is-expanded">
|
<input
|
||||||
<input
|
className="input is-small"
|
||||||
className="input is-small"
|
type="text"
|
||||||
type="text"
|
placeholder="Search..."
|
||||||
placeholder="Search..."
|
onChange={(e) => setKeyword(e.target.value)}
|
||||||
value={searchTerm}
|
/>
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="control">
|
|
||||||
<button
|
|
||||||
className="button is-small is-info"
|
|
||||||
onClick={handleSearch}
|
|
||||||
disabled={!searchTerm.trim()}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<span className="icon">
|
|
||||||
<i className="fa fa-search"></i>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{searchMode && (
|
|
||||||
<div className="control">
|
|
||||||
<button
|
|
||||||
className="button is-small is-danger"
|
|
||||||
onClick={exitSearch}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<span className="icon">
|
|
||||||
<i className={"fa fa-window-close"}></i>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<PermissionGuard rolesRequired={["admin", "creator"]}>
|
<PermissionGuard rolesRequired={["admin", "creator"]}>
|
||||||
<a
|
<a
|
||||||
@@ -103,29 +74,17 @@ const SideNavigation = () => {
|
|||||||
Create New Markdown
|
Create New Markdown
|
||||||
</a>
|
</a>
|
||||||
</PermissionGuard>
|
</PermissionGuard>
|
||||||
{searchMode ? (
|
{!filteredTree || filteredTree.length === 0 ?
|
||||||
<ul>
|
<p>No Result</p> :
|
||||||
{searchResults.map((markdown, i) => (
|
<PathNode
|
||||||
<MarkdownNode
|
key={1}
|
||||||
markdown={markdown}
|
path={filteredTree}
|
||||||
handleMoveMarkdown={() => {}}
|
isRoot={true}
|
||||||
/>
|
onSave={handleSave}
|
||||||
))}
|
onDelete={handleDelete}
|
||||||
</ul>
|
/>
|
||||||
) : (
|
}
|
||||||
<ul className="menu-list">
|
|
||||||
{isLoading && <p>Loading...</p>}
|
|
||||||
{sortedPaths.map((path) => (
|
|
||||||
<PathNode
|
|
||||||
key={path.id}
|
|
||||||
path={path}
|
|
||||||
isRoot={false}
|
|
||||||
onSave={handleSave}
|
|
||||||
onDelete={handleDelete}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export const useCreatePath = () => {
|
|||||||
onSuccess: (res, variables) => {
|
onSuccess: (res, variables) => {
|
||||||
console.log(JSON.stringify(variables));
|
console.log(JSON.stringify(variables));
|
||||||
queryClient.invalidateQueries(["paths", variables.parent_id]);
|
queryClient.invalidateQueries(["paths", variables.parent_id]);
|
||||||
|
queryClient.invalidateQueries("tree");
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -73,6 +74,7 @@ export const useUpdatePath = () => {
|
|||||||
onSuccess: (res, variables) => {
|
onSuccess: (res, variables) => {
|
||||||
queryClient.invalidateQueries(["paths", res.parent_id]);
|
queryClient.invalidateQueries(["paths", res.parent_id]);
|
||||||
queryClient.invalidateQueries(["path", variables.data.id]);
|
queryClient.invalidateQueries(["path", variables.data.id]);
|
||||||
|
queryClient.invalidateQueries("tree");
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -89,6 +91,7 @@ export const useDeletePath = () => {
|
|||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries("paths");
|
queryClient.invalidateQueries("paths");
|
||||||
|
queryClient.invalidateQueries("tree");
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -107,6 +110,7 @@ export const useMovePath = () => {
|
|||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries("paths");
|
queryClient.invalidateQueries("paths");
|
||||||
|
queryClient.invalidateQueries("tree");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
19
src/utils/tree-queries.js
Normal file
19
src/utils/tree-queries.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import {useQuery, useMutation, useQueryClient} from "react-query";
|
||||||
|
import {fetch_} from "./request-utils";
|
||||||
|
import {useConfig} from "../ConfigProvider";
|
||||||
|
|
||||||
|
|
||||||
|
export const useTree = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const config = useConfig();
|
||||||
|
return useQuery(
|
||||||
|
"tree",
|
||||||
|
() => fetch_(`${config.BACKEND_HOST}/api/tree/`),
|
||||||
|
{
|
||||||
|
onSuccess: data => {
|
||||||
|
if(data)
|
||||||
|
queryClient.setQueryData("tree", data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user