add: order paths & mds

This commit is contained in:
h z
2024-12-29 18:52:39 +00:00
parent 34ab63d0bf
commit 75d083f11f
6 changed files with 116 additions and 17 deletions

View File

@@ -32,7 +32,7 @@ const Footer = () => {
<a href="https://git.hangman-lab.top/hzhang/HangmanLab">git</a> <a href="https://git.hangman-lab.top/hzhang/HangmanLab">git</a>
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;
v0.0.6 v0.0.7
</span> </span>
)}</p> )}</p>
{ {

View File

@@ -2,8 +2,8 @@ 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, usePaths, useUpdatePath} from "../../utils/path-queries"; import {useDeletePath, useMovePath, usePaths, useUpdatePath} from "../../utils/path-queries";
import {useIndexMarkdown, useMarkdownsByPath} from "../../utils/markdown-queries"; import {useIndexMarkdown, useMarkdownsByPath, useMoveMarkdown} from "../../utils/markdown-queries";
const PathNode = ({ path, isRoot = false }) => { const PathNode = ({ path, isRoot = false }) => {
const [isExpanded, setIsExpanded] = useState(isRoot); const [isExpanded, setIsExpanded] = useState(isRoot);
@@ -17,6 +17,8 @@ const PathNode = ({ path, isRoot = false }) => {
const {data: indexMarkdown, isLoading: isIndexLoading, error: indexMarkdownError} = useIndexMarkdown(path.id); const {data: indexMarkdown, isLoading: isIndexLoading, error: indexMarkdownError} = useIndexMarkdown(path.id);
const movePath = useMovePath();
const moveMarkdown = useMoveMarkdown();
const toggleExpand = () => { const toggleExpand = () => {
setIsExpanded(!isExpanded); setIsExpanded(!isExpanded);
@@ -40,18 +42,34 @@ const PathNode = ({ path, isRoot = false }) => {
setIsEditing(true); setIsEditing(true);
}; };
const handleMovePath = (pth, direction) => {
movePath.mutate({path: pth, direction: direction}, {
onError: () => alert("failed to move this path"),
});
};
const handleMoveMarkdown = (md, direction) => {
moveMarkdown.mutate({markdown: md, direction: direction}, {
onError: () => alert("failed to move this markdown"),
})
};
const sortedPaths = childPaths
? childPaths.slice().sort((a, b) => a.order.localeCompare(b.order))
: [];
const sortedMarkdowns = markdowns
? 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>;
} }
return ( return (
<li> <li>
<div className="path-node-header is-clickable field has-addons" onClick={isRoot ? undefined : toggleExpand}> <div className="path-node-header field has-addons" >
{isEditing ? ( {isEditing ? (
<div className = "control has-icons-left"> <div className = "control has-icons-left">
<input <input
className="input is-small path-edit-input" className="input is-small path-edit-input"
@@ -61,7 +79,10 @@ const PathNode = ({ path, isRoot = false }) => {
</div> </div>
) : ( ) : (
<span className="path-name has-text-weight-bold control"> <span
className="path-name has-text-weight-bold control"
onClick={isRoot ? undefined : toggleExpand}
>
{ {
indexMarkdown ? ( indexMarkdown ? (
<Link to={`/markdown/${indexMarkdown.id}`} className="is-link"> <Link to={`/markdown/${indexMarkdown.id}`} className="is-link">
@@ -112,25 +133,67 @@ const PathNode = ({ path, isRoot = false }) => {
</span> </span>
</button> </button>
</p> </p>
<div
className="control is-flex is-flex-direction-column is-align-items-center"
style={{marginLeft: "0.5rem"}}
>
<button
className="button is-small is-primary mb-1"
style={{height: "1rem", padding: "0.25rem"}}
onClick={() => handleMovePath(path, "forward")}
type="button"
>
</button>
<button
className="button is-small is-primary mb-1"
style={{height: "1rem", padding: "0.25rem"}}
onClick={() => handleMovePath(path, "backward")}
type="button"
>
</button>
</div>
</div> </div>
</PermissionGuard> </PermissionGuard>
</div> </div>
{isExpanded && ( {isExpanded && (
<ul> <ul>
{ isChildLoading && <p>Loading...</p>} {isChildLoading && <p>Loading...</p>}
{ childPaths.map((child) => ( {sortedPaths.map((child) => (
<PathNode <PathNode
key={child.id} key={child.id}
path={child} path={child}
/> />
))} ))}
{ markdowns.filter(md => md.title !== "index").map((markdown) => ( {sortedMarkdowns.filter(md => md.title !== "index").map((markdown) => (
<li key={markdown.id}> <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"> <Link to={`/markdown/${markdown.id}`} className="is-link">
{markdown.title} {markdown.title}
</Link> </Link>
</span>
<PermissionGuard rolesRequired={['admin']}>
<div className="control">
<button
className="button is-small is-primary mb-1"
style={{ height: "1rem", padding: "0.25rem" }}
onClick={() => handleMoveMarkdown(markdown, "forward")}
type="button"
>
</button>
<button
className="button is-small is-primary mb-1"
style={{ height: "1rem", padding: "0.25rem" }}
onClick={() => handleMoveMarkdown(markdown, "backward")}
type="button"
>
</button>
</div>
</PermissionGuard>
</div>
</li> </li>
))} ))}
</ul> </ul>

View File

@@ -9,6 +9,9 @@ const SideNavigation = () => {
const deletePath = useDeletePath(); const deletePath = useDeletePath();
const updatePath = useUpdatePath(); const updatePath = useUpdatePath();
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, {
@@ -47,7 +50,7 @@ const SideNavigation = () => {
</PermissionGuard> </PermissionGuard>
<ul className="menu-list"> <ul className="menu-list">
{isLoading && <p>Loading...</p>} {isLoading && <p>Loading...</p>}
{paths.map((path) => ( {sortedPaths.map((path) => (
<PathNode <PathNode
key={path.id} key={path.id}
path={path} path={path}

View File

@@ -68,10 +68,26 @@ export const useSaveMarkdown = () => {
}) })
},{ },{
onSuccess: (res, variables) => { onSuccess: (res, variables) => {
queryClient.invalidateQueries(["markdownsByPath", variables.data.parent_id]); queryClient.invalidateQueries(["markdownsByPath", variables.data.path_id]);
queryClient.invalidateQueries(["markdown", variables.data.id]); queryClient.invalidateQueries(["markdown", variables.data.id]);
}, },
}); });
}; };
export const useMoveMarkdown = () => {
const queryClient = useQueryClient();
const config = useConfig();
return useMutation(
({markdown, direction}) => {
const apiEndpoint = `${config.BACKEND_HOST}/api/markdown/move_${direction}/${markdown.id}`;
return fetch_(apiEndpoint, {method: "PATCH"});
},
{
onSuccess: () => {
queryClient.invalidateQueries("paths");
}
}
);
};

View File

@@ -93,3 +93,21 @@ export const useDeletePath = () => {
} }
); );
}; };
export const useMovePath = () => {
const queryClient = useQueryClient();
const config = useConfig();
return useMutation(
({path, direction}) => {
const apiEndpoint = `${config.BACKEND_HOST}/api/path/move_${direction}/${path.id}`;
return fetch_(apiEndpoint, {method: "PATCH"});
},
{
onSuccess: () => {
queryClient.invalidateQueries("paths");
}
}
);
};

View File

@@ -1,6 +1,5 @@
const path = require('path'); const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin');
const Dotenv = require('dotenv-webpack');
module.exports = { module.exports = {
entry: './src/index.js', entry: './src/index.js',