-
setKeyword(e.target.value)}
- />
+
);
};
diff --git a/src/components/Navigations/SideTabs/TemplateTab.js b/src/components/Navigations/SideTabs/TemplateTab.js
new file mode 100644
index 0000000..7313505
--- /dev/null
+++ b/src/components/Navigations/SideTabs/TemplateTab.js
@@ -0,0 +1,62 @@
+import React, { useState } from "react";
+import { useMarkdownTemplates } from "../../../utils/queries/markdown-template-queries";
+import PermissionGuard from "../../PermissionGuard";
+import { useNavigate } from "react-router-dom";
+
+const TemplateTab = () => {
+ const { data: templates, isLoading, error } = useMarkdownTemplates();
+ const [keyword, setKeyword] = useState("");
+ const navigate = useNavigate();
+
+ const filteredTemplates = templates?.filter(template =>
+ template.title.toLowerCase().includes(keyword.toLowerCase())
+ );
+
+ const handleTemplateClick = (templateId) => {
+ navigate(`/template/edit/${templateId}`);
+ };
+
+ if (isLoading) return
Loading...
;
+ if (error) return
Error loading templates
;
+
+ return (
+
+ );
+};
+
+export default TemplateTab;
diff --git a/src/components/Navigations/SideTabs/TreeTab.js b/src/components/Navigations/SideTabs/TreeTab.js
new file mode 100644
index 0000000..869f1f8
--- /dev/null
+++ b/src/components/Navigations/SideTabs/TreeTab.js
@@ -0,0 +1,88 @@
+import PermissionGuard from "../../PermissionGuard";
+import PathNode from "../PathNode";
+import React from "react";
+import {useTree} from "../../../utils/queries/tree-queries";
+import {useDeletePath, useUpdatePath} from "../../../utils/queries/path-queries";
+
+const TreeTab = () => {
+ const {data: tree, isLoading, error} = useTree();
+ const deletePath = useDeletePath();
+ const updatePath = useUpdatePath();
+ const [keyword, setKeyword] = React.useState("");
+ const handleDelete = (id) => {
+ if (window.confirm("Are you sure you want to delete this path?")){
+ deletePath.mutate(id, {
+ onError: (err) => {
+ alert("Failed to delete path");
+ },
+ });
+ }
+ };
+
+ const filterTree = (t, k) => {
+ if(t === undefined)
+ return undefined;
+ if (t.type === "path") {
+ 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) => {
+ updatePath.mutate({ id, data: {name: newName }} , {
+ onError: (err) => {
+ alert("Failed to update path");
+ }
+ });
+ };
+ if (isLoading) return
Loading...
;
+ if (error) return
Error loading tree
;
+ return (
+
+ );
+};
+
+export default TreeTab;
\ No newline at end of file
diff --git a/src/components/Settings/MarkdownSettings/MarkdownPermissionSettingPanel.js b/src/components/Settings/MarkdownSettings/MarkdownPermissionSettingPanel.js
new file mode 100644
index 0000000..f1d6315
--- /dev/null
+++ b/src/components/Settings/MarkdownSettings/MarkdownPermissionSettingPanel.js
@@ -0,0 +1,91 @@
+import {
+ useCreateMarkdownPermissionSetting,
+ useMarkdownPermissionSetting,
+ useUpdateMarkdownPermissionSetting
+} from "../../../utils/queries/markdown-permission-setting-queries";
+import React, {useEffect, useState} from "react";
+import {useUpdateMarkdownSetting} from "../../../utils/queries/markdown-setting-queries";
+
+const MarkdownPermissionSettingPanel = ({markdownSetting, onClose}) => {
+ const {data: setting, isFetching: settingIsFetching } = useMarkdownPermissionSetting(markdownSetting?.permission_setting_id);
+ const [permission, setPermission] = useState("");
+
+ const createMarkdownPermissionSetting = useCreateMarkdownPermissionSetting();
+ const updateMarkdownSetting = useUpdateMarkdownSetting();
+ const updateMarkdownPermissionSetting = useUpdateMarkdownPermissionSetting();
+
+ useEffect(() => {
+ if (setting && setting.permission !== undefined && setting.permission !== null) {
+ setPermission(setting.permission);
+ }
+ }, [setting]);
+
+ const handleCreatePermissionSetting = () => {
+ createMarkdownPermissionSetting.mutate({permission: null}, {
+ onSuccess: (data) => {
+ updateMarkdownSetting.mutate({
+ id: markdownSetting.id,
+ data: {
+ permission_setting_id: data.id,
+ }
+ });
+ }
+ });
+ };
+
+ const handleSaveMarkdownPermissionSetting = () => {
+ const permissionValue = permission === "" ? null : permission;
+
+ updateMarkdownPermissionSetting.mutate({
+ id: setting.id,
+ data: {
+ permission: permissionValue,
+ }
+ }, {
+ onSuccess: () => alert("Saved"),
+ onError: () => alert("Failed to save"),
+ });
+ onClose();
+ };
+
+ if (settingIsFetching) {
+ return (
Loading...
);
+ }
+
+ return setting ? (
+
+
Permission Setting
+
+
+
+
+
+
+
+
+ ) : (
+
+ );
+};
+
+export default MarkdownPermissionSettingPanel;
diff --git a/src/utils/queries/markdown-permission-setting-queries.js b/src/utils/queries/markdown-permission-setting-queries.js
new file mode 100644
index 0000000..a149b5e
--- /dev/null
+++ b/src/utils/queries/markdown-permission-setting-queries.js
@@ -0,0 +1,75 @@
+import {useConfig} from "../../ConfigProvider";
+import {useMutation, useQuery, useQueryClient} from "react-query";
+import {fetch_} from "../request-utils";
+
+export const useMarkdownPermissionSettings = () => {
+ const config = useConfig();
+ const queryClient = useQueryClient();
+ return useQuery(
+ "markdown_permission_settings",
+ () => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/permission/`), {
+ onSuccess: (data) => {
+ if(data){
+ for(const setting of data){
+ queryClient.invalidateQueries(["markdown_permission_setting", setting.id]);
+ }
+ }
+ }
+ }
+ );
+};
+
+export const useMarkdownPermissionSetting = (setting_id) => {
+ const config = useConfig();
+ return useQuery(
+ ["markdown_permission_setting", setting_id],
+ () => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/permission/${setting_id}/`), {
+ enabled: !!setting_id,
+ }
+ );
+};
+
+export const useCreateMarkdownPermissionSetting = () => {
+ const config = useConfig();
+ const queryClient = useQueryClient();
+ return useMutation((data) => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/permission/`, {
+ method: "POST",
+ body: JSON.stringify(data),
+ }), {
+ onSuccess: (data) => {
+ queryClient.invalidateQueries(["markdown_permission_setting", data.id]);
+ queryClient.invalidateQueries("markdown_permission_settings");
+ }
+ });
+};
+
+export const useUpdateMarkdownPermissionSetting = () => {
+ const config = useConfig();
+ const queryClient = useQueryClient();
+ return useMutation(
+ ({id, data}) => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/permission/${id}`, {
+ method: "PATCH",
+ body: JSON.stringify(data),
+ }),{
+ onSuccess: (res) => {
+ queryClient.invalidateQueries(["markdown_permission_setting", res.id]);
+ queryClient.invalidateQueries("markdown_permission_settings");
+ }
+ }
+ );
+};
+
+export const useDeleteMarkdownPermissionSetting = () => {
+ const config = useConfig();
+ const queryClient = useQueryClient();
+ return useMutation(
+ ({id}) => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/permission/${id}`, {
+ method: "DELETE",
+ }), {
+ onSuccess: (res, variables) => {
+ queryClient.invalidateQueries(["markdown_permission_setting", variables.id]);
+ queryClient.invalidateQueries("markdown_permission_settings");
+ }
+ }
+ );
+};
\ No newline at end of file
diff --git a/src/utils/request-utils.js b/src/utils/request-utils.js
index cacd2e2..26bf4dc 100644
--- a/src/utils/request-utils.js
+++ b/src/utils/request-utils.js
@@ -24,5 +24,16 @@ export async function fetch_(url, init = {}) {
return null;
}
+ if (response.status === 203) {
+ const data = await response.json();
+ return {
+ id: null,
+ content: data.msg || "Non-authoritative information received",
+ title: "Message",
+ isMessage: true,
+ ...data
+ };
+ }
+
return response.json();
-}
\ No newline at end of file
+}