add: markdown search feature
This commit is contained in:
38
src/components/Navigations/MarkdownNode.js
Normal file
38
src/components/Navigations/MarkdownNode.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import {Link} from "react-router-dom";
|
||||
import PermissionGuard from "../PermissionGuard";
|
||||
import React from "react";
|
||||
|
||||
const MarkdownNode = ({markdown, handleMoveMarkdown}) => {
|
||||
return (
|
||||
<li key={markdown.id}>
|
||||
<div className="is-clickable field has-addons">
|
||||
<span className="markdown-name has-text-weight-bold control">
|
||||
<Link to={`/markdown/${markdown.id}`} className="is-link markdown-node">
|
||||
{markdown.title}
|
||||
</Link>
|
||||
</span>
|
||||
<PermissionGuard rolesRequired={['admin']}>
|
||||
<div className="control">
|
||||
<button
|
||||
className="button is-small mb-1 move-forward"
|
||||
style={{height: "1rem", padding: "0.25rem"}}
|
||||
onClick={() => handleMoveMarkdown(markdown, "forward")}
|
||||
type="button"
|
||||
>
|
||||
</button>
|
||||
<button
|
||||
className="button is-small mb-1 move-backward"
|
||||
style={{height: "1rem", padding: "0.25rem"}}
|
||||
onClick={() => handleMoveMarkdown(markdown, "backward")}
|
||||
type="button"
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
</PermissionGuard>
|
||||
</div>
|
||||
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
export default MarkdownNode;
|
||||
@@ -4,6 +4,7 @@ import PermissionGuard from "../PermissionGuard";
|
||||
import "./PathNode.css";
|
||||
import {useDeletePath, useMovePath, usePaths, useUpdatePath} from "../../utils/path-queries";
|
||||
import {useIndexMarkdown, useMarkdownsByPath, useMoveMarkdown} from "../../utils/markdown-queries";
|
||||
import MarkdownNode from "./MarkdownNode";
|
||||
|
||||
const PathNode = ({ path, isRoot = false }) => {
|
||||
const [isExpanded, setIsExpanded] = useState(isRoot);
|
||||
@@ -175,34 +176,10 @@ const PathNode = ({ path, isRoot = false }) => {
|
||||
))}
|
||||
|
||||
{sortedMarkdowns.filter(md => md.title !== "index").map((markdown) => (
|
||||
<li key={markdown.id}>
|
||||
<div className="is-clickable field has-addons">
|
||||
<span className="markdown-name has-text-weight-bold control">
|
||||
<Link to={`/markdown/${markdown.id}`} className="is-link markdown-node">
|
||||
{markdown.title}
|
||||
</Link>
|
||||
</span>
|
||||
<PermissionGuard rolesRequired={['admin']}>
|
||||
<div className="control">
|
||||
<button
|
||||
className="button is-small mb-1 move-forward"
|
||||
style={{ height: "1rem", padding: "0.25rem" }}
|
||||
onClick={() => handleMoveMarkdown(markdown, "forward")}
|
||||
type="button"
|
||||
>
|
||||
</button>
|
||||
<button
|
||||
className="button is-small mb-1 move-backward"
|
||||
style={{ height: "1rem", padding: "0.25rem" }}
|
||||
onClick={() => handleMoveMarkdown(markdown, "backward")}
|
||||
type="button"
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
</PermissionGuard>
|
||||
</div>
|
||||
|
||||
</li>
|
||||
<MarkdownNode
|
||||
markdown={markdown}
|
||||
handleMoveMarkdown={handleMoveMarkdown}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
@@ -3,12 +3,20 @@ import PathNode from "./PathNode";
|
||||
import "./SideNavigation.css";
|
||||
import {useDeletePath, usePaths, useUpdatePath} from "../../utils/path-queries";
|
||||
import React from 'react';
|
||||
import {useSearchMarkdown} from "../../utils/markdown-queries";
|
||||
import MarkdownNode from "./MarkdownNode";
|
||||
|
||||
const SideNavigation = () => {
|
||||
const {data: paths, isLoading, error } = usePaths(1);
|
||||
const deletePath = useDeletePath();
|
||||
const updatePath = useUpdatePath();
|
||||
const [searchTerm, setSearchTerm] = React.useState("");
|
||||
const [keyword, setKeyword] = React.useState("");
|
||||
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))
|
||||
: [];
|
||||
@@ -22,6 +30,14 @@ const SideNavigation = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearch = () => {
|
||||
setSearchMode(true);
|
||||
setKeyword(searchTerm);
|
||||
}
|
||||
const exitSearch = () => {
|
||||
setSearchMode(false);
|
||||
}
|
||||
|
||||
const handleSave = (id, newName) => {
|
||||
updatePath.mutate({ id, data: {name: newName }} , {
|
||||
onError: (err) => {
|
||||
@@ -29,17 +45,56 @@ const SideNavigation = () => {
|
||||
}
|
||||
});
|
||||
};
|
||||
if(isLoading){
|
||||
if(!searchMode && isLoading){
|
||||
return <aside className="menu"><p>Loading...</p></aside>;
|
||||
}
|
||||
if(searchMode && isSearching){
|
||||
return <aside className="menu"><p>Loading...</p></aside>;
|
||||
}
|
||||
|
||||
if(error){
|
||||
return <aside className="menu"><p>Error...</p></aside>;
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<aside className="menu">
|
||||
<p className="menu-label">---</p>
|
||||
<div className="field has-addons mb-2">
|
||||
<div className="control is-expanded">
|
||||
<input
|
||||
className="input is-small"
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
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>
|
||||
<PermissionGuard rolesRequired={["admin", "creator"]}>
|
||||
<a
|
||||
href="/markdown/create"
|
||||
@@ -48,18 +103,30 @@ const SideNavigation = () => {
|
||||
Create New Markdown
|
||||
</a>
|
||||
</PermissionGuard>
|
||||
<ul className="menu-list">
|
||||
{isLoading && <p>Loading...</p>}
|
||||
{sortedPaths.map((path) => (
|
||||
<PathNode
|
||||
key={path.id}
|
||||
path={path}
|
||||
isRoot={false}
|
||||
onSave={handleSave}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
{searchMode ? (
|
||||
<ul>
|
||||
{searchResults.map((markdown, i) => (
|
||||
<MarkdownNode
|
||||
markdown={markdown}
|
||||
handleMoveMarkdown={() => {}}
|
||||
/>
|
||||
))}
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -91,3 +91,16 @@ export const useMoveMarkdown = () => {
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const useSearchMarkdown = (keyword) => {
|
||||
const queryClient = useQueryClient();
|
||||
const config = useConfig();
|
||||
return useQuery(["markdownsByKeyword", keyword],
|
||||
() => fetch_(
|
||||
`${config.BACKEND_HOST}/api/markdown/search/${encodeURIComponent(keyword)}`,
|
||||
),
|
||||
{
|
||||
enabled: !!keyword,
|
||||
}
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user