add: order paths & mds
This commit is contained in:
@@ -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>
|
||||||
|
|
||||||
|
|
||||||
v0.0.6
|
v0.0.7
|
||||||
</span>
|
</span>
|
||||||
)}</p>
|
)}</p>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
Reference in New Issue
Block a user