Compare commits

...

5 Commits

Author SHA1 Message Date
c9310250e4 add: markdown deletion 2025-06-23 15:41:03 +01:00
a08164e914 add: route to stand along page 2025-06-23 12:18:26 +01:00
30a46d5064 add: markdown template to json schema 2025-05-12 09:59:23 +01:00
e5affe3465 improve: update css 2025-05-11 20:14:29 +01:00
101666d26d upgrade react-query to v5 2025-05-09 00:44:53 +01:00
29 changed files with 1029 additions and 581 deletions

View File

@@ -5,7 +5,7 @@ FRONTEND_HOST="${FRONTEND_HOST:-http://localhost:80}"
KC_CLIENT_ID="${KC_CLIENT_ID:-labdev}" KC_CLIENT_ID="${KC_CLIENT_ID:-labdev}"
KC_HOST="${KC_HOST:-https://login.hangman-lab.top}" KC_HOST="${KC_HOST:-https://login.hangman-lab.top}"
KC_REALM="${KC_REALM:-Hangman-Lab}" KC_REALM="${KC_REALM:-Hangman-Lab}"
DEBUG="${DEBUG:false}"
rm -f /usr/share/nginx/html/config.js rm -f /usr/share/nginx/html/config.js
@@ -24,7 +24,8 @@ cat <<EOL > /usr/share/nginx/html/config.json
"scope": "openid profile email roles", "scope": "openid profile email roles",
"popup_redirect_uri": "${FRONTEND_HOST}/popup_callback", "popup_redirect_uri": "${FRONTEND_HOST}/popup_callback",
"silent_redirect_uri": "${FRONTEND_HOST}/silent_callback" "silent_redirect_uri": "${FRONTEND_HOST}/silent_callback"
} },
"DEBUG": ${DEBUG}
} }
EOL EOL

57
package-lock.json generated
View File

@@ -10,7 +10,8 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@reduxjs/toolkit": "^2.7.0", "@reduxjs/toolkit": "^2.7.0",
"@tanstack/react-query": "^4.36.1", "@tanstack/react-query": "^5.75.5",
"@tanstack/react-query-devtools": "^5.75.5",
"assert": "^2.1.0", "assert": "^2.1.0",
"axios": "^1.7.9", "axios": "^1.7.9",
"bulma": "^1.0.2", "bulma": "^1.0.2",
@@ -2712,38 +2713,56 @@
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==" "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="
}, },
"node_modules/@tanstack/query-core": { "node_modules/@tanstack/query-core": {
"version": "4.36.1", "version": "5.75.5",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.36.1.tgz", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.75.5.tgz",
"integrity": "sha512-DJSilV5+ytBP1FbFcEJovv4rnnm/CokuVvrBEtW/Va9DvuJ3HksbXUJEpI0aV1KtuL4ZoO9AVE6PyNLzF7tLeA==", "integrity": "sha512-kPDOxtoMn2Ycycb76Givx2fi+2pzo98F9ifHL/NFiahEDpDwSVW6o12PRuQ0lQnBOunhRG5etatAhQij91M3MQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/query-devtools": {
"version": "5.74.7",
"resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.74.7.tgz",
"integrity": "sha512-nSNlfuGdnHf4yB0S+BoNYOE1o3oAH093weAYZolIHfS2stulyA/gWfSk/9H4ZFk5mAAHb5vNqAeJOmbdcGPEQw==",
"license": "MIT",
"funding": { "funding": {
"type": "github", "type": "github",
"url": "https://github.com/sponsors/tannerlinsley" "url": "https://github.com/sponsors/tannerlinsley"
} }
}, },
"node_modules/@tanstack/react-query": { "node_modules/@tanstack/react-query": {
"version": "4.36.1", "version": "5.75.5",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.36.1.tgz", "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.75.5.tgz",
"integrity": "sha512-y7ySVHFyyQblPl3J3eQBWpXZkliroki3ARnBKsdJchlgt7yJLRDUcf4B8soufgiYt3pEQIkBWBx1N9/ZPIeUWw==", "integrity": "sha512-QrLCJe40BgBVlWdAdf2ZEVJ0cISOuEy/HKupId1aTKU6gPJZVhSvZpH+Si7csRflCJphzlQ77Yx6gUxGW9o0XQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"@tanstack/query-core": "4.36.1", "@tanstack/query-core": "5.75.5"
"use-sync-external-store": "^1.2.0"
}, },
"funding": { "funding": {
"type": "github", "type": "github",
"url": "https://github.com/sponsors/tannerlinsley" "url": "https://github.com/sponsors/tannerlinsley"
}, },
"peerDependencies": { "peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react": "^18 || ^19"
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", }
"react-native": "*" },
"node_modules/@tanstack/react-query-devtools": {
"version": "5.75.5",
"resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.75.5.tgz",
"integrity": "sha512-S31U00nJOQIbxydRH1kOwdLRaLBrda8O5QjzmgkRg60UZzPGdbI6+873Qa0YGUfPeILDbR2ukgWyg7CJQPy4iA==",
"license": "MIT",
"dependencies": {
"@tanstack/query-devtools": "5.74.7"
}, },
"peerDependenciesMeta": { "funding": {
"react-dom": { "type": "github",
"optional": true "url": "https://github.com/sponsors/tannerlinsley"
}, },
"react-native": { "peerDependencies": {
"optional": true "@tanstack/react-query": "^5.75.5",
} "react": "^18 || ^19"
} }
}, },
"node_modules/@testing-library/dom": { "node_modules/@testing-library/dom": {

View File

@@ -13,7 +13,8 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@reduxjs/toolkit": "^2.7.0", "@reduxjs/toolkit": "^2.7.0",
"@tanstack/react-query": "^4.36.1", "@tanstack/react-query": "^5.75.5",
"@tanstack/react-query-devtools": "^5.75.5",
"assert": "^2.1.0", "assert": "^2.1.0",
"axios": "^1.7.9", "axios": "^1.7.9",
"bulma": "^1.0.2", "bulma": "^1.0.2",

View File

@@ -6,6 +6,7 @@ import MainNavigation from "./components/Navigations/MainNavigation";
import SideNavigation from "./components/Navigations/SideNavigation"; import SideNavigation from "./components/Navigations/SideNavigation";
import MarkdownContent from "./components/Markdowns/MarkdownContent"; import MarkdownContent from "./components/Markdowns/MarkdownContent";
import MarkdownEditor from "./components/Markdowns/MarkdownEditor"; import MarkdownEditor from "./components/Markdowns/MarkdownEditor";
import StandaloneMarkdownPage from "./components/Markdowns/StandaloneMarkdownPage";
import "./App.css"; import "./App.css";
import Callback from "./components/KeycloakCallbacks/Callback"; import Callback from "./components/KeycloakCallbacks/Callback";
import Footer from "./components/Footer"; import Footer from "./components/Footer";
@@ -18,30 +19,35 @@ const App = () => {
return ( return (
<Provider store={store}> <Provider store={store}>
<Router> <Router>
<div className="app-container"> <Routes>
<MainNavigation /> <Route path="/pg/*" element={<StandaloneMarkdownPage />} />
<div className="content-container"> <Route path="*" element={
<SideNavigation /> <div className="app-container">
<main className="main-content"> <MainNavigation />
<Routes> <div className="content-container">
<Route <SideNavigation />
path="/" <main className="main-content">
element={<Navigate to = "/markdown/1"/>} <Routes>
/> <Route
<Route path="/testx" element={<h2>test2</h2>}/> path="/"
<Route path="/markdown/:id" element={<MarkdownContent />} /> element={<Navigate to = "/markdown/1"/>}
<Route path="/callback" element={<Callback />} /> />
<Route path="/test" element={<h1>TEST</h1>}></Route> <Route path="/testx" element={<h2>test2</h2>}/>
<Route path="/markdown/create" element={<MarkdownEditor />} /> <Route path="/markdown/:strId" element={<MarkdownContent />} />
<Route path="/markdown/edit/:id" element={<MarkdownEditor />} /> <Route path="/callback" element={<Callback />} />
<Route path="/popup_callback" element={<PopupCallback />} /> <Route path="/test" element={<h1>TEST</h1>}></Route>
<Route path="/silent_callback" element={<SilentCallback />} /> <Route path="/markdown/create" element={<MarkdownEditor />} />
<Route path="/template/create" element={<MarkdownTemplateEditor />} /> <Route path="/markdown/edit/:strId" element={<MarkdownEditor />} />
<Route path="/template/edit/:id" element={<MarkdownTemplateEditor />} /> <Route path="/popup_callback" element={<PopupCallback />} />
</Routes> <Route path="/silent_callback" element={<SilentCallback />} />
</main> <Route path="/template/create" element={<MarkdownTemplateEditor />} />
</div> <Route path="/template/edit/:strId" element={<MarkdownTemplateEditor />} />
</div> </Routes>
</main>
</div>
</div>
} />
</Routes>
<Footer /> <Footer />
</Router> </Router>
</Provider> </Provider>

View File

@@ -0,0 +1,14 @@
import React from "react";
import {useConfig} from "../../ConfigProvider";
import {ReactQueryDevtools} from "@tanstack/react-query-devtools";
export const ControlledReactQueryDevtools = () => {
const config = useConfig();
if(config.DEBUG)
return (<ReactQueryDevtools />);
return (<></>);
};
export default ControlledReactQueryDevtools;

View File

@@ -5,6 +5,7 @@ const LayoutEditor = ({layout, onChange}) => {
return ( return (
<textarea <textarea
className="textarea" className="textarea"
style={{ height: "60vh" }}
value={_layout} value={_layout}
onChange={(e) => { onChange={(e) => {
setLayout(e.target.value); setLayout(e.target.value);

View File

@@ -10,7 +10,8 @@ const MarkdownTemplateEditor = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const { id } = useParams(); const { strId } = useParams();
const id = Number(strId);
const { data: template, isFetching: templateIsFetching } = useMarkdownTemplate(id); const { data: template, isFetching: templateIsFetching } = useMarkdownTemplate(id);
const saveMarkdownTemplate = useSaveMarkdownTemplate(); const saveMarkdownTemplate = useSaveMarkdownTemplate();
@@ -64,6 +65,7 @@ const MarkdownTemplateEditor = () => {
<div className="columns is-variable is-8"> <div className="columns is-variable is-8">
<div className="column"> <div className="column">
<h3 className="title is-5">Layout</h3> <h3 className="title is-5">Layout</h3>
<div className="box"> <div className="box">
<LayoutEditor <LayoutEditor
layout={layout} layout={layout}
@@ -71,15 +73,15 @@ const MarkdownTemplateEditor = () => {
onChange={(newLayout) => setLayout(newLayout)} onChange={(newLayout) => setLayout(newLayout)}
/> />
</div> </div>
</div> </div>
<div className="column"> <div className="column">
<h3 className="title is-5">Parameters</h3> <h3 className="title is-5">Parameters</h3>
<div className="box"> <ParametersManager
<ParametersManager parameters={parameters}
parameters={parameters} onChange={(newParameters) => setParameters(newParameters)}
onChange={(newParameters) => setParameters(newParameters)} />
/>
</div>
</div> </div>
</div> </div>
<div className="field is-grouped"> <div className="field is-grouped">

View File

@@ -3,18 +3,36 @@ import TypeEditor from "./TypeEditor";
const ParametersManager = ({ parameters, onChange }) => { const ParametersManager = ({ parameters, onChange }) => {
const [_parameters, setParameters] = useState(parameters || []); const [_parameters, setParameters] = useState(parameters || []);
const [expandedStates, setExpandedStates] = useState({});
const handleAdd = () => { const handleAdd = () => {
const updated = [ const updated = [
..._parameters, ..._parameters,
{ {
name: "", name: "",
type: { base_type: "string" } type: {
base_type: "string",
definition: {}
}
} }
]; ];
setParameters(updated); setParameters(updated);
onChange(updated); onChange(updated);
}; };
const handleTypeChange = (index, newType) => {
const updated = [..._parameters];
if (newType.base_type === "list" && !newType.extend_type) {
newType.extend_type = {
base_type: "string",
definition: {}
};
}
updated[index].type = newType;
setParameters(updated);
onChange(updated);
};
useEffect(() => { useEffect(() => {
setParameters(parameters); setParameters(parameters);
}, [parameters]); }, [parameters]);
@@ -33,6 +51,13 @@ const ParametersManager = ({ parameters, onChange }) => {
onChange(updated); onChange(updated);
}; };
const toggleExpand = (index) => {
setExpandedStates(prev => ({
...prev,
[index]: !prev[index]
}));
};
return ( return (
<div className="box"> <div className="box">
<div className="field"> <div className="field">
@@ -42,44 +67,53 @@ const ParametersManager = ({ parameters, onChange }) => {
</button> </button>
</div> </div>
</div> </div>
{_parameters.map((param, index) => ( <div style={{ maxHeight: "50vh", overflowY: "auto" }}>
<div key={index} className="box" style={{ marginBottom: "1rem" }}> {_parameters.map((param, index) => (
<div className="field is-grouped is-grouped-multiline"> <div key={index} className="box" style={{ marginBottom: "0.5rem" }}>
<div className="control is-expanded"> <div className="field is-grouped is-align-items-end">
<label className="label">Name:</label> <div className="control">
<input <label className="label">Name:</label>
type="text" </div>
className="input" <div className="control is-expanded">
value={param.name} <input
onChange={(e) => handleNameChange(index, e.target.value)} type="text"
placeholder="Parameter name" className="input"
/> value={param.name}
onChange={(e) => handleNameChange(index, e.target.value)}
placeholder="Parameter name"
/>
</div>
<div className="control">
<button
className="button is-danger"
onClick={() => handleDelete(index)}
>
Delete
</button>
</div>
</div> </div>
<div className="control"> <div className="field">
<button <div className="is-flex is-justify-content-space-between is-align-items-center mb-1">
className="button is-danger" <label className="label mb-0">Type:</label>
onClick={() => handleDelete(index)} <button
> className="button is-small"
Delete onClick={() => toggleExpand(index)}
</button> >
{expandedStates[index] ? "-" : "+"}
</button>
</div>
{expandedStates[index] && (
<div className="control">
<TypeEditor
type={param.type}
onChange={(newType) => handleTypeChange(index, newType)}
/>
</div>
)}
</div> </div>
</div> </div>
<div className="field"> ))}
<label className="label">Type:</label> </div>
<div className="control">
<TypeEditor
type={param.type}
onChange={(newType) => {
const updated = [..._parameters];
updated[index].type = newType;
setParameters(updated);
onChange(updated);
}}
/>
</div>
</div>
</div>
))}
</div> </div>
); );
}; };

View File

@@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useMarkdownTemplates } from "../../utils/queries/markdown-template-queries"; import { useMarkdownTemplates } from "../../utils/queries/markdown-template-queries";
const TemplateSelector = ({ template, onChange }) => { const TemplateSelector = ({ template, onChange, onCreate }) => {
const { data: templates, isFetching: templatesAreFetching } = useMarkdownTemplates(); const { data: templates, isFetching: templatesAreFetching } = useMarkdownTemplates();
const [_template, setTemplate] = useState( const [_template, setTemplate] = useState(
templates?.find((t) => t.id === template?.id) || { templates?.find((t) => t.id === template?.id) || {
@@ -34,13 +34,15 @@ const TemplateSelector = ({ template, onChange }) => {
value={template?.id || ""} value={template?.id || ""}
onChange={(e) => { onChange={(e) => {
const id = parseInt(e.target.value, 10); const id = parseInt(e.target.value, 10);
onChange( const selectedTemplate = templates.find((t) => t.id === id) || {
templates.find((t) => t.id === id) || { title: "",
title: "", parameters: [],
parameters: [], layout: "",
layout: "", };
} onChange(selectedTemplate);
); if (onCreate) {
onCreate(selectedTemplate);
}
}} }}
> >
<option value="">(None)</option> <option value="">(None)</option>

View File

@@ -69,7 +69,6 @@ const TypeEditor = ({ type, onChange }) => {
case 'template': case 'template':
return ( return (
<div className="field"> <div className="field">
<label className="label">Template</label>
<div className="control"> <div className="control">
<TemplateSelector <TemplateSelector
template={_type.definition.template} template={_type.definition.template}
@@ -95,7 +94,7 @@ const TypeEditor = ({ type, onChange }) => {
return ( return (
<div className="box"> <div className="box">
<div className="field"> <div className="field">
<label className="label">Type</label> {/*<label className="label">Type</label>*/}
<div className="control"> <div className="control">
<div className="select is-fullwidth"> <div className="select is-fullwidth">
<select <select

View File

@@ -9,10 +9,13 @@ import {usePath} from "../../utils/queries/path-queries";
import {useMarkdownSetting} from "../../utils/queries/markdown-setting-queries"; import {useMarkdownSetting} from "../../utils/queries/markdown-setting-queries";
import {useMarkdownTemplate} from "../../utils/queries/markdown-template-queries"; import {useMarkdownTemplate} from "../../utils/queries/markdown-template-queries";
import {useMarkdownTemplateSetting} from "../../utils/queries/markdown-template-setting-queries"; import {useMarkdownTemplateSetting} from "../../utils/queries/markdown-template-setting-queries";
import MarkdownSettingModal from "../Modals/MarkdownSettingModal";
const MarkdownContent = () => { const MarkdownContent = () => {
const { id } = useParams(); const { strId } = useParams();
const id = Number(strId);
const [indexTitle, setIndexTitle] = useState(null); const [indexTitle, setIndexTitle] = useState(null);
const [isSettingModalOpen, setSettingModalOpen] = useState(false);
const {data: markdown, isLoading, error} = useMarkdown(id); const {data: markdown, isLoading, error} = useMarkdown(id);
const {data: path, isFetching: isPathFetching} = usePath(markdown?.path_id); const {data: path, isFetching: isPathFetching} = usePath(markdown?.path_id);
const {data: setting, isFetching: isSettingFetching} = useMarkdownSetting(markdown?.setting_id); const {data: setting, isFetching: isSettingFetching} = useMarkdownSetting(markdown?.setting_id);
@@ -35,8 +38,8 @@ const MarkdownContent = () => {
if (error) { if (error) {
return <div>Error: {error.message || "Failed to load content"}</div>; return <div>Error: {error.message || "Failed to load content"}</div>;
} }
if (markdown.isMessage) { if (markdown.isMessage) {
return ( return (
<div className="markdown-content-container"> <div className="markdown-content-container">
<div className="notification is-info"> <div className="notification is-info">
@@ -49,15 +52,28 @@ const MarkdownContent = () => {
return ( return (
<div className="markdown-content-container"> <div className="markdown-content-container">
<div className="field has-addons markdown-content-container-header"> <div className="is-flex is-justify-content-space-between is-align-items-center markdown-content-container-header">
<h1 className="title control">{markdown.title === "index" ? indexTitle : markdown.title}</h1> <h1 className="title">{markdown.title === "index" ? indexTitle : markdown.title}</h1>
<PermissionGuard rolesRequired={['admin']}> <PermissionGuard rolesRequired={['admin']}>
<Link to={`/markdown/edit/${id}`} className="control button is-primary is-light"> <div className="field has-addons">
Edit <button
</Link> className="control button is-info is-light"
onClick={() => setSettingModalOpen(true)}
>
Settings
</button>
<Link to={`/markdown/edit/${id}`} className="control button is-primary is-light">
Edit
</Link>
</div>
</PermissionGuard> </PermissionGuard>
</div> </div>
<MarkdownView content={JSON.parse(markdown.content)} template={template}/> <MarkdownView content={JSON.parse(markdown.content)} template={template}/>
<MarkdownSettingModal
isOpen={isSettingModalOpen}
markdown={markdown}
onClose={() => setSettingModalOpen(false)}
/>
</div> </div>
); );
}; };

View File

@@ -11,11 +11,13 @@ import {useMarkdownTemplate, useMarkdownTemplates} from "../../utils/queries/mar
import TemplatedEditor from "./TemplatedEditor"; import TemplatedEditor from "./TemplatedEditor";
import {useMarkdownTemplateSetting, useUpdateMarkdownTemplateSetting, useCreateMarkdownTemplateSetting} from "../../utils/queries/markdown-template-setting-queries"; import {useMarkdownTemplateSetting, useUpdateMarkdownTemplateSetting, useCreateMarkdownTemplateSetting} from "../../utils/queries/markdown-template-setting-queries";
import TemplateSelector from "../MarkdownTemplate/TemplateSelector"; import TemplateSelector from "../MarkdownTemplate/TemplateSelector";
import {useCreateMarkdownSetting} from "../../utils/queries/markdown-setting-queries";
const MarkdownEditor = () => { const MarkdownEditor = () => {
const { roles } = useContext(AuthContext); const { roles } = useContext(AuthContext);
const navigate = useNavigate(); const navigate = useNavigate();
const { id } = useParams(); const { strId } = useParams();
const id = Number(strId);
const [title, setTitle] = useState(""); const [title, setTitle] = useState("");
const [content, setContent] = useState({}); const [content, setContent] = useState({});
const [shortcut, setShortcut] = useState(""); const [shortcut, setShortcut] = useState("");
@@ -23,6 +25,7 @@ const MarkdownEditor = () => {
const [isRawMode, setIsRawMode] = useState(false); const [isRawMode, setIsRawMode] = useState(false);
const [rawContent, setRawContent] = useState(""); const [rawContent, setRawContent] = useState("");
const [jsonError, setJsonError] = useState(""); const [jsonError, setJsonError] = useState("");
const [selectedTemplate, setSelectedTemplate] = useState(null);
const {data: markdown, isFetching: isMarkdownFetching, error} = useMarkdown(id); const {data: markdown, isFetching: isMarkdownFetching, error} = useMarkdown(id);
const saveMarkdown = useSaveMarkdown(); const saveMarkdown = useSaveMarkdown();
const {data: setting, isFetching: isSettingFetching} = useMarkdownSetting(markdown?.setting_id); const {data: setting, isFetching: isSettingFetching} = useMarkdownSetting(markdown?.setting_id);
@@ -32,10 +35,12 @@ const MarkdownEditor = () => {
const createTemplateSetting = useCreateMarkdownTemplateSetting(); const createTemplateSetting = useCreateMarkdownTemplateSetting();
const updateSetting = useUpdateMarkdownSetting(); const updateSetting = useUpdateMarkdownSetting();
const {data: templates, isFetching: templatesAreFetching} = useMarkdownTemplates(); const {data: templates, isFetching: templatesAreFetching} = useMarkdownTemplates();
const createMarkdownSetting = useCreateMarkdownSetting();
const notReady = isMarkdownFetching || isTemplateFetching || isSettingFetching || isTemplateSettingFetching || templatesAreFetching; const notReady = isMarkdownFetching || isTemplateFetching || isSettingFetching || isTemplateSettingFetching || templatesAreFetching;
useEffect(() => { useEffect(() => {
if(markdown){ if (markdown) {
setTitle(markdown.title); setTitle(markdown.title);
if (markdown.isMessage) { if (markdown.isMessage) {
navigate("/"); navigate("/");
@@ -56,22 +61,84 @@ const MarkdownEditor = () => {
} }
}, [markdown, navigate]); }, [markdown, navigate]);
useEffect(() => {
if (template) {
setSelectedTemplate(template);
}
}, [template]);
const handleSave = () => { const handleSave = () => {
if (isRawMode && jsonError) { if (isRawMode && jsonError) {
alert("Please fix the JSON errors before saving"); alert("Please fix the JSON errors before saving");
return; return;
} }
saveMarkdown.mutate( const saveData = {
{id, data: {title, content: JSON.stringify(content), path_id: pathId, shortcut}}, title,
{ content: JSON.stringify(content),
onSuccess: () => { path_id: pathId,
navigate("/"); shortcut
}, };
onError: () => { console.log("markdown", markdown);
alert("Error saving markdown file"); console.log(markdown?.id ? "update" : "create",)
if (!markdown?.id) {
saveMarkdown.mutate(
{data: saveData},
{
onSuccess: (newMarkdown) => {
createMarkdownSetting.mutate({}, {
onSuccess: (settingRes) => {
saveMarkdown.mutate({
id: newMarkdown.id,
data: {
setting_id: settingRes.id
}
}, {
onSuccess: () => {
if (selectedTemplate?.id) {
createTemplateSetting.mutate({
template_id: selectedTemplate.id
}, {
onSuccess: (templateSettingRes) => {
updateSetting.mutate({
id: settingRes.id,
data: {
template_setting_id: templateSettingRes.id
}
}, {
onSuccess: () => {
navigate("/");
}
});
}
});
} else {
navigate("/");
}
}
});
}
});
},
onError: () => {
alert("Error saving markdown file");
}
} }
}); );
} else {
console.log("try update");
saveMarkdown.mutate(
{id, data: saveData},
{
onSuccess: () => {
navigate("/markdown/" + id);
},
onError: () => {
alert("Error saving markdown file");
}
}
);
}
}; };
const toggleEditMode = () => { const toggleEditMode = () => {
@@ -104,54 +171,17 @@ const MarkdownEditor = () => {
}; };
const handleTemplateChange = (newTemplate) => { const handleTemplateChange = (newTemplate) => {
if (!newTemplate) return; setSelectedTemplate(newTemplate);
if (templateSetting) { if (templateSetting) {
updateTemplateSetting.mutate( updateTemplateSetting.mutate({
{ id: templateSetting.id,
id: templateSetting.id, data: {
data: { template_id: newTemplate.id } template_id: newTemplate.id
},
{
onSuccess: () => {
setContent({});
},
onError: () => {
alert("Error updating template");
}
} }
); });
} else if (setting) {
createTemplateSetting.mutate(
{ template_id: newTemplate.id },
{
onSuccess: (newTemplateSetting) => {
updateSetting.mutate(
{
id: setting.id,
data: { template_setting_id: newTemplateSetting.id }
},
{
onSuccess: () => {
setContent({});
},
onError: () => {
alert("Error updating markdown setting");
}
}
);
},
onError: () => {
alert("Error creating template setting");
}
}
);
} else {
alert("Cannot change template: No markdown setting found");
} }
}; };
const hasPermission = roles.includes("admin") || roles.includes("creator"); const hasPermission = roles.includes("admin") || roles.includes("creator");
if (!hasPermission) if (!hasPermission)
return <div className="notification is-danger">Permission Denied</div>; return <div className="notification is-danger">Permission Denied</div>;
@@ -211,7 +241,7 @@ const MarkdownEditor = () => {
<div className="field"> <div className="field">
<div className="control"> <div className="control">
<TemplateSelector <TemplateSelector
template={template} template={selectedTemplate || template}
onChange={handleTemplateChange} onChange={handleTemplateChange}
/> />
</div> </div>
@@ -247,9 +277,9 @@ const MarkdownEditor = () => {
</div> </div>
) : ( ) : (
<TemplatedEditor <TemplatedEditor
style={{height: "70vh"}} style={{height: "40vh"}}
content={content} content={content}
template={template} template={!markdown?.id ? selectedTemplate : template}
onContentChanged={(k, v) => setContent( onContentChanged={(k, v) => setContent(
prev => ({...prev, [k]: v}) prev => ({...prev, [k]: v})
)} )}
@@ -275,7 +305,11 @@ const MarkdownEditor = () => {
<div className="column is-half"> <div className="column is-half">
<h3 className="subtitle is-5">Preview</h3> <h3 className="subtitle is-5">Preview</h3>
<MarkdownView content={content} template={template} height='70vh'/> <MarkdownView
content={content}
template={!markdown?.id ? selectedTemplate : template}
height='70vh'
/>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,100 @@
import React, { useEffect, useState } from "react";
import { useLocation } from "react-router-dom";
import "katex/dist/katex.min.css";
import "./MarkdownContent.css";
import MarkdownView from "./MarkdownView";
import { useMarkdown } from "../../utils/queries/markdown-queries";
import { useMarkdownSetting } from "../../utils/queries/markdown-setting-queries";
import { useMarkdownTemplate } from "../../utils/queries/markdown-template-queries";
import { useMarkdownTemplateSetting } from "../../utils/queries/markdown-template-setting-queries";
import { useTree } from "../../utils/queries/tree-queries";
import { getMarkdownIdByPath } from "../../utils/pathUtils";
const StandaloneMarkdownPage = () => {
const location = useLocation();
const [indexTitle, setIndexTitle] = useState(null);
const [markdownId, setMarkdownId] = useState(null);
// Extract path from /pg/project/index -> project/index
const pathString = location.pathname.replace(/^\/pg\//, '');
const { data: tree, isLoading: isTreeLoading } = useTree();
const { data: markdown, isLoading: isMarkdownLoading, error } = useMarkdown(markdownId);
const { data: setting, isFetching: isSettingFetching } = useMarkdownSetting(markdown?.setting_id);
const { data: templateSetting, isFetching: isTemplateSettingFetching } = useMarkdownTemplateSetting(setting?.template_setting_id);
const { data: template, isFetching: isTemplateFetching } = useMarkdownTemplate(templateSetting?.template_id);
// Resolve markdown ID from path using tree
useEffect(() => {
if (tree && pathString) {
const resolvedId = getMarkdownIdByPath(tree, pathString);
setMarkdownId(resolvedId);
}
}, [tree, pathString]);
useEffect(() => {
if (markdown && markdown.title === "index" && pathString) {
const pathParts = pathString.split('/').filter(part => part.length > 0);
if (pathParts.length === 0) {
// Root index: /pg/ or /pg
setIndexTitle("Home");
} else {
// Directory index: /pg/Projects or /pg/Projects/project1
// Use the last directory name as title
const directoryName = pathParts[pathParts.length - 1];
setIndexTitle(directoryName);
}
}
}, [markdown, pathString]);
const notReady = isTreeLoading || isMarkdownLoading || isSettingFetching || isTemplateSettingFetching || isTemplateFetching;
if (notReady) {
return (
<div style={{ padding: "2rem", textAlign: "center" }}>
<div>Loading...</div>
</div>
);
}
if (error) {
return (
<div style={{ padding: "2rem", textAlign: "center" }}>
<div>Error: {error.message || "Failed to load content"}</div>
</div>
);
}
if (!notReady && !markdownId) {
return (
<div style={{ padding: "2rem", textAlign: "center" }}>
<div>Markdown not found for path: {pathString}</div>
</div>
);
}
if (markdown?.isMessage) {
return (
<div style={{ padding: "2rem" }}>
<div className="notification is-info">
<h4 className="title is-4">{markdown.title}</h4>
<p>{markdown.content}</p>
</div>
</div>
);
}
return (
<div style={{ padding: "2rem", maxWidth: "100%", margin: "0 auto" }}>
<div style={{ marginBottom: "2rem" }}>
<h1 className="title">{markdown?.title === "index" ? indexTitle : markdown?.title}</h1>
</div>
{markdown && (
<MarkdownView content={JSON.parse(markdown.content)} template={template} />
)}
</div>
);
};
export default StandaloneMarkdownPage;

View File

@@ -28,9 +28,10 @@ const TemplatedEditorComponent = ({ variable, value, namespace, onContentChanged
<label className="label">{__namespace}</label> <label className="label">{__namespace}</label>
<div className="control"> <div className="control">
<textarea <textarea
className="textarea" style={{maxHeight: "10vh"}}
value={value} className="textarea"
onChange={(e) => onContentChanged(variable.name, e.target.value)} value={value}
onChange={(e) => onContentChanged(variable.name, e.target.value)}
/> />
</div> </div>
</div> </div>
@@ -152,7 +153,7 @@ const TemplatedEditorComponent = ({ variable, value, namespace, onContentChanged
return <>{renderField()}</>; return <>{renderField()}</>;
}; };
const TemplatedEditor = ({ content, template, onContentChanged }) => { const TemplatedEditor = ({ content, template, onContentChanged, style }) => {
const tpl = template || { const tpl = template || {
parameters: [{ name: "markdown", type: { base_type: "markdown", definition: {} } }], parameters: [{ name: "markdown", type: { base_type: "markdown", definition: {} } }],
layout: "<markdown/>", layout: "<markdown/>",
@@ -160,16 +161,27 @@ const TemplatedEditor = ({ content, template, onContentChanged }) => {
}; };
return ( return (
<div className="box"> <div className="box" style={{
{tpl.parameters.map((variable, idx) => ( ...style,
<TemplatedEditorComponent display: "flex",
key={idx} flexDirection: "column",
variable={variable} overflow: "hidden"
value={content[variable.name]} }}>
namespace={tpl.title} <div style={{
onContentChanged={onContentChanged} flex: 1,
/> overflowY: "auto",
))} padding: "1rem"
}}>
{tpl.parameters.map((variable, idx) => (
<TemplatedEditorComponent
key={idx}
variable={variable}
value={content[variable.name]}
namespace={tpl.title}
onContentChanged={onContentChanged}
/>
))}
</div>
</div> </div>
); );
}; };

View File

@@ -0,0 +1,42 @@
import React from 'react';
const JsonSchemaModal = ({ isActive, onClose, schema }) => {
const handleCopy = () => {
navigator.clipboard.writeText(JSON.stringify(schema, null, 2));
};
return (
<div className={`modal ${isActive ? 'is-active' : ''}`}>
<div className="modal-background" onClick={onClose}></div>
<div className="modal-card">
<header className="modal-card-head">
<p className="modal-card-title">JSON Schema</p>
<button className="delete" aria-label="close" onClick={onClose}></button>
</header>
<section className="modal-card-body">
<div className="field">
<div className="control">
<textarea
className="textarea"
value={JSON.stringify(schema, null, 2)}
readOnly
style={{ height: "50vh" }}
/>
</div>
</div>
</section>
<footer className="modal-card-foot">
<button className="button is-primary" onClick={handleCopy}>
<span className="icon">
<i className="fas fa-copy"></i>
</span>
<span>copy</span>
</button>
<button className="button" onClick={onClose}>close</button>
</footer>
</div>
</div>
);
};
export default JsonSchemaModal;

View File

@@ -1,9 +1,30 @@
import {Link} from "react-router-dom"; import {Link, useNavigate} from "react-router-dom";
import PermissionGuard from "../PermissionGuard"; import PermissionGuard from "../PermissionGuard";
import React, {useState} from "react"; import React, {useState} from "react";
import MarkdownSettingModal from "../Modals/MarkdownSettingModal"; import MarkdownSettingModal from "../Modals/MarkdownSettingModal";
import {useDeleteMarkdown} from "../../utils/queries/markdown-queries";
import {useDeleteMarkdownSetting} from "../../utils/queries/markdown-setting-queries";
const MarkdownNode = ({markdown, handleMoveMarkdown}) => { const MarkdownNode = ({markdown, handleMoveMarkdown}) => {
const [isMarkdownSettingModalOpen, setIsMarkdownSettingModalOpen] = useState(false); const [isMarkdownSettingModalOpen, setIsMarkdownSettingModalOpen] = useState(false);
const navigate = useNavigate();
const deleteMarkdown = useDeleteMarkdown();
const deleteMarkdownSetting = useDeleteMarkdownSetting();
const handleDeleteMarkdown = async () => {
if (!window.confirm(`delete markdown "${markdown.title}" ? this action cannot be undone.`)) {
return;
}
try {
await deleteMarkdown.mutateAsync(markdown.id);
if (window.location.pathname === `/markdown/${markdown.id}`) {
navigate('/');
}
} catch (error) {
alert('failed: ' + (error.message || 'unknown error'));
}
};
return ( return (
<li key={markdown.id}> <li key={markdown.id}>
<div className="is-clickable field has-addons"> <div className="is-clickable field has-addons">
@@ -24,6 +45,18 @@ const MarkdownNode = ({markdown, handleMoveMarkdown}) => {
</span> </span>
</button> </button>
</p> </p>
<p className="control">
<button
className="button is-small is-danger"
onClick={handleDeleteMarkdown}
type="button"
disabled={deleteMarkdown.isLoading || deleteMarkdownSetting.isLoading}
>
<span className="icon">
<i className="fas fa-trash"/>
</span>
</button>
</p>
<div <div
className="control is-flex is-flex-direction-column is-align-items-center" className="control is-flex is-flex-direction-column is-align-items-center"
style={{marginLeft: "0.5rem"}} style={{marginLeft: "0.5rem"}}

View File

@@ -2,10 +2,12 @@ import React, { useState } from "react";
import { useMarkdownTemplates } from "../../../utils/queries/markdown-template-queries"; import { useMarkdownTemplates } from "../../../utils/queries/markdown-template-queries";
import PermissionGuard from "../../PermissionGuard"; import PermissionGuard from "../../PermissionGuard";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import JsonSchemaModal from "../../Modals/JsonSchemaModal";
const TemplateTab = () => { const TemplateTab = () => {
const { data: templates, isLoading, error } = useMarkdownTemplates(); const { data: templates, isLoading, error } = useMarkdownTemplates();
const [keyword, setKeyword] = useState(""); const [keyword, setKeyword] = useState("");
const [selectedSchema, setSelectedSchema] = useState(null);
const navigate = useNavigate(); const navigate = useNavigate();
const filteredTemplates = templates?.filter(template => const filteredTemplates = templates?.filter(template =>
@@ -16,6 +18,99 @@ const TemplateTab = () => {
navigate(`/template/edit/${templateId}`); navigate(`/template/edit/${templateId}`);
}; };
const generateJsonSchema = (template) => {
const schema = {
type: "object",
properties: {},
$defs: {}
};
const generateTypeSchema = (param, defName) => {
switch (param.type.base_type) {
case "string":
return {
type: "string"
};
case "markdown":
return {
type: "string",
description: "Markdown content"
};
case "enum":
return {
type: "string",
enum: param.type.definition.enums
};
case "list":
if (param.type.extend_type.base_type === "string") {
return {
type: "array",
items: {
type: "string"
}
};
} else if (param.type.extend_type.base_type === "list" ||
param.type.extend_type.base_type === "template") {
const itemsDefName = `${defName}_items`;
schema.$defs[itemsDefName] = generateTypeSchema(
{ type: param.type.extend_type },
itemsDefName
);
return {
type: "array",
items: {
$ref: `#/$defs/${itemsDefName}`
}
};
} else {
return {
type: "array",
items: generateTypeSchema(
{ type: param.type.extend_type },
`${defName}_items`
)
};
}
case "template":
const nestedTemplate = templates.find(t => t.id === param.type.definition.template.id);
if (nestedTemplate) {
const nestedSchema = {
type: "object",
properties: {}
};
nestedTemplate.parameters.forEach(nestedParam => {
nestedSchema.properties[nestedParam.name] = generateTypeSchema(
nestedParam,
`${defName}_${nestedParam.name}`
);
});
return nestedSchema;
} else {
return {
type: "object",
properties: {}
};
}
default:
return {
type: "object",
properties: {}
};
}
};
template.parameters.forEach(param => {
const defName = `param_${param.name}`;
schema.properties[param.name] = generateTypeSchema(param, defName);
});
if (Object.keys(schema.$defs).length === 0) {
delete schema.$defs;
}
return schema;
};
if (isLoading) return <p>Loading...</p>; if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error loading templates</p>; if (error) return <p>Error loading templates</p>;
@@ -38,23 +133,40 @@ const TemplateTab = () => {
Create New Template Create New Template
</a> </a>
</PermissionGuard> </PermissionGuard>
<ul className="menu-list">
{!filteredTemplates || filteredTemplates.length === 0 ? ( {filteredTemplates?.map((template) => (
<p>No templates found</p> <li key={template.id}>
) : ( <div className="is-flex is-justify-content-space-between is-align-items-center">
<div className="template-list"> <span>{template.title}</span>
{filteredTemplates.map(template => ( <div className="field has-addons is-justify-content-flex-end">
<button <button
key={template.id} className="button is-small control"
className="button is-light is-fullwidth template-button" onClick={() => handleTemplateClick(template.id)}
onClick={() => handleTemplateClick(template.id)} type="button"
style={{ marginBottom: "5px", textAlign: "left", justifyContent: "flex-start" }} >
> <span className="icon">
{template.title} <i className="fas fa-edit"></i>
</button> </span>
))} </button>
</div> <button
)} className="button is-small control"
onClick={() => setSelectedSchema(generateJsonSchema(template))}
type="button"
>
<span className="icon">
<i className="fas fa-code"></i>
</span>
</button>
</div>
</div>
</li>
))}
</ul>
<JsonSchemaModal
isActive={selectedSchema !== null}
onClose={() => setSelectedSchema(null)}
schema={selectedSchema}
/>
</aside> </aside>
); );
}; };

View File

@@ -24,10 +24,10 @@ const PathManager = ({ currentPathId = 1, onPathChange }) => {
let current_id = pathId; let current_id = pathId;
while (current_id) { while (current_id) {
try { try {
const pathData = await queryClient.fetchQuery( const pathData = await queryClient.fetchQuery({
["path", current_id], queryKey: ["path", current_id],
() => fetch_(`${config.BACKEND_HOST}/api/path/${current_id}`) queryFn: () => fetch_(`${config.BACKEND_HOST}/api/path/${current_id}`)
); });
if (!pathData) break; if (!pathData) break;
path.unshift({ name: pathData.name, id: pathData.id }); path.unshift({ name: pathData.name, id: pathData.id });
current_id = pathData.parent_id; current_id = pathData.parent_id;
@@ -71,8 +71,8 @@ const PathManager = ({ currentPathId = 1, onPathChange }) => {
{ name: searchTerm.trim(), parent_id: currentPathId }, { name: searchTerm.trim(), parent_id: currentPathId },
{ {
onSuccess: (newDir) => { onSuccess: (newDir) => {
queryClient.setQueryData(["path", newDir.id], newDir); queryClient.setQueryData({queryKey: ["path", newDir.id]}, newDir);
queryClient.invalidateQueries(["paths", currentPathId]); queryClient.invalidateQueries({queryKey: ["paths", currentPathId]});
setSearchTerm(""); setSearchTerm("");
alert("Directory created successfully."); alert("Directory created successfully.");
}, },

View File

@@ -5,6 +5,7 @@ import AuthProvider, {AuthContext} from "./AuthProvider";
import "bulma/css/bulma.min.css"; import "bulma/css/bulma.min.css";
import {QueryClient, QueryClientProvider} from "@tanstack/react-query" import {QueryClient, QueryClientProvider} from "@tanstack/react-query"
import ConfigProvider from "./ConfigProvider"; import ConfigProvider from "./ConfigProvider";
import ControlledReactQueryDevtools from "./components/Debug/ControlledReactQueryDevtools";
const queryClient = new QueryClient({ const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
@@ -52,6 +53,7 @@ root.render(
<AuthProvider> <AuthProvider>
<EnhancedAuthProvider> <EnhancedAuthProvider>
<App /> <App />
<ControlledReactQueryDevtools />
</EnhancedAuthProvider> </EnhancedAuthProvider>
</AuthProvider> </AuthProvider>
</QueryClientProvider> </QueryClientProvider>

45
src/utils/pathUtils.js Normal file
View File

@@ -0,0 +1,45 @@
export const findMarkdownByPath = (tree, pathString) => {
if (!tree || !pathString) return null;
const pathSegments = pathString.split('/').filter(segment => segment.length > 0);
if (pathSegments.length === 0) {
const rootIndex = tree.children?.find(
child => child.type === 'markdown' && child.title === 'index'
);
return rootIndex || null;
}
let currentNode = tree;
for (let i = 0; i < pathSegments.length; i++) {
const segment = pathSegments[i];
const childPath = currentNode.children?.find(
child => child.type === 'path' && child.name === segment
);
if (!childPath) {
if (i === pathSegments.length - 1) {
const markdownNode = currentNode.children?.find(
child => child.type === 'markdown' && child.title === segment
);
return markdownNode || null;
}
return null;
}
currentNode = childPath;
}
const indexMarkdown = currentNode.children?.find(
child => child.type === 'markdown' && child.title === 'index'
);
return indexMarkdown || null;
};
export const getMarkdownIdByPath = (tree, pathString) => {
const markdownNode = findMarkdownByPath(tree, pathString);
return markdownNode?.id || null;
};

View File

@@ -5,13 +5,13 @@ import {fetch_} from "../request-utils";
export const useMarkdownPermissionSettings = () => { export const useMarkdownPermissionSettings = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useQuery( return useQuery({
["markdown_permission_settings"], queryKey: ["markdown_permission_settings"],
() => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/permission/`), { queryFn: () => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/permission/`),
onSuccess: (data) => { onSuccess: async (data) => {
if(data){ if (data) {
for(const setting of data){ for (const setting of data) {
queryClient.invalidateQueries(["markdown_permission_setting", setting.id]); await queryClient.invalidateQueries(["markdown_permission_setting", setting.id]);
} }
} }
} }
@@ -21,9 +21,9 @@ export const useMarkdownPermissionSettings = () => {
export const useMarkdownPermissionSetting = (setting_id) => { export const useMarkdownPermissionSetting = (setting_id) => {
const config = useConfig(); const config = useConfig();
return useQuery( return useQuery({
["markdown_permission_setting", setting_id], queryKey: ["markdown_permission_setting", setting_id],
() => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/permission/${setting_id}/`), { queryFn: () => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/permission/${setting_id}`),
enabled: !!setting_id, enabled: !!setting_id,
} }
); );
@@ -32,15 +32,17 @@ export const useMarkdownPermissionSetting = (setting_id) => {
export const useCreateMarkdownPermissionSetting = () => { export const useCreateMarkdownPermissionSetting = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation((data) => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/permission/`, { return useMutation(
method: "POST", {
body: JSON.stringify(data), mutationFn: (data) => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/permission/`, {
}), { method: "POST",
onSuccess: (data) => { body: JSON.stringify(data),
queryClient.invalidateQueries(["markdown_permission_setting", data.id]); }),
queryClient.invalidateQueries(["markdown_permission_settings"]); onSuccess: async (data) => {
} await queryClient.invalidateQueries(["markdown_permission_setting", data.id]);
}); await queryClient.invalidateQueries(["markdown_permission_settings"]);
}
});
}; };
export const useUpdateMarkdownPermissionSetting = () => { export const useUpdateMarkdownPermissionSetting = () => {

View File

@@ -6,71 +6,73 @@ import {useConfig} from "../../ConfigProvider";
export const useMarkdown = (id) => { export const useMarkdown = (id) => {
const config = useConfig(); const config = useConfig();
return useQuery( return useQuery({
["markdown", id], queryKey: ["markdown", id],
() => fetch_(`${config.BACKEND_HOST}/api/markdown/${id}`), queryFn: () => fetch_(`${config.BACKEND_HOST}/api/markdown/${id}`),
{ enabled: !!id,
enabled: !!id, });
});
}; };
export const useIndexMarkdown = (path_id) => { export const useIndexMarkdown = (path_id) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const config = useConfig(); const config = useConfig();
return useQuery( return useQuery({
["index_markdown", path_id], queryKey: ["index_markdown", path_id],
() => fetch_(`${config.BACKEND_HOST}/api/markdown/get_index/${path_id}`),{ queryFn: () => fetch_(`${config.BACKEND_HOST}/api/markdown/get_index/${path_id}`),
enabled: !!path_id, enabled: !!path_id,
onSuccess: (data) => { onSuccess: (data) => {
if(data && data.id){ if(data && data.id){
queryClient.setQueryData(["markdown", data.id], data); queryClient.setQueryData({queryKey: ["markdown", data.id]}, data);
}
} }
}); }
});
}; };
export const useHomeMarkdown = () => { export const useHomeMarkdown = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const config = useConfig(); const config = useConfig();
return useQuery( return useQuery({
["home_markdown"], queryKey: ["home_markdown"],
() => fetch_(`${config.BACKEND_HOST}/api/markdown/get_home`), { queryFn: () => fetch_(`${config.BACKEND_HOST}/api/markdown/get_home`),
onSuccess: (data) => { onSuccess: (data) => {
if (data && data.id){ if (data && data.id){
queryClient.setQueryData(["markdown", data.id], data); queryClient.setQueryData({queryKey: ["markdown", data.id]}, data);
}
} }
}); }
});
}; };
export const useMarkdownsByPath = (pathId) => { export const useMarkdownsByPath = (pathId) => {
const config = useConfig(); const config = useConfig();
return useQuery( return useQuery({
["markdownsByPath", pathId], queryKey: ["markdownsByPath", pathId],
() => fetch_(`${config.BACKEND_HOST}/api/markdown/by_path/${pathId}`), queryFn: () => fetch_(`${config.BACKEND_HOST}/api/markdown/by_path/${pathId}`),
{ enabled: !!pathId
enabled: !!pathId });
});
}; };
export const useSaveMarkdown = () => { export const useSaveMarkdown = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const config = useConfig(); const config = useConfig();
return useMutation(({id, data}) => { return useMutation({
const url = id mutationFn: ({id, data}) => {
? `${config.BACKEND_HOST}/api/markdown/${id}` const url = id
: `${config.BACKEND_HOST}/api/markdown/`; ? `${config.BACKEND_HOST}/api/markdown/${id}`
const method = id ? "PATCH" : "POST"; : `${config.BACKEND_HOST}/api/markdown/`;
return fetch_(url, { const method = id ? "PATCH" : "POST";
method, return fetch_(url, {
body: JSON.stringify(data), method,
}) body: JSON.stringify(data),
},{ });
onSuccess: (res) => { },
queryClient.invalidateQueries(["markdownsByPath", res.path_id]); onSuccess: async (res) => {
queryClient.invalidateQueries(["markdown", res.id]); await queryClient.invalidateQueries({queryKey: ["markdownsByPath", res.path_id]});
queryClient.invalidateQueries(["tree"]); await queryClient.invalidateQueries({queryKey: ["markdown", res.id]});
await queryClient.invalidateQueries({queryKey: ["tree"]});
console.log("invalidateQueries: ", res.id, typeof res.id);
}, },
}); });
}; };
@@ -80,34 +82,52 @@ export const useMoveMarkdown = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const config = useConfig(); const config = useConfig();
return useMutation( return useMutation({
({markdown, direction}) => { mutationFn: ({markdown, direction}) => {
const apiEndpoint = `${config.BACKEND_HOST}/api/markdown/move_${direction}/${markdown.id}`; const apiEndpoint = `${config.BACKEND_HOST}/api/markdown/move_${direction}/${markdown.id}`;
return fetch_(apiEndpoint, {method: "PATCH"}); return fetch_(apiEndpoint, {method: "PATCH"});
}, },
{ onSuccess: () => {
onSuccess: () => { queryClient.invalidateQueries({queryKey: ["paths"]});
queryClient.invalidateQueries(["paths"]); queryClient.invalidateQueries({queryKey: ["tree"]});
queryClient.invalidateQueries(["tree"]);
}
} }
); });
};
export const useDeleteMarkdown = () => {
const queryClient = useQueryClient();
const config = useConfig();
return useMutation({
mutationFn: (markdownId) => {
return fetch_(`${config.BACKEND_HOST}/api/markdown/${markdownId}`, {
method: "DELETE"
});
},
onSuccess: (data, markdownId) => {
queryClient.invalidateQueries({queryKey: ["markdown", markdownId]});
queryClient.invalidateQueries({queryKey: ["tree"]});
queryClient.invalidateQueries({queryKey: ["markdownsByPath"]});
}
});
}; };
export const useSearchMarkdown = (keyword) => { export const useSearchMarkdown = (keyword) => {
const config = useConfig(); const config = useConfig();
return useQuery(["markdownsByKeyword", keyword], return useQuery({
() => fetch_( queryKey: ["markdownsByKeyword", keyword],
queryFn: () => fetch_(
`${config.BACKEND_HOST}/api/markdown/search/${encodeURIComponent(keyword)}`, `${config.BACKEND_HOST}/api/markdown/search/${encodeURIComponent(keyword)}`,
), ),
{ enabled: !!keyword,
enabled: !!keyword, });
}
);
}; };
export const useLinks = () => { export const useLinks = () => {
const config = useConfig(); const config = useConfig();
return useQuery(["links"], () => fetch_(`${config.BACKEND_HOST}/api/markdown/links`)); return useQuery({
queryKey: ["links"],
queryFn: () => fetch_(`${config.BACKEND_HOST}/api/markdown/links`)
});
} }

View File

@@ -5,71 +5,66 @@ import {fetch_} from "../request-utils";
export const useMarkdownSettings = () => { export const useMarkdownSettings = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useQuery( return useQuery({
["markdown_setting"], queryKey: ["markdown_setting"],
() => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/`), queryFn: () => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/`),
{ onSuccess: (data) => {
onSuccess: (data) => { if(data){
if(data){ for(const setting of data)
for(const setting of data) queryClient.setQueryData({queryKey: ["markdown_setting", setting.id]}, setting);
queryClient.invalidateQueries(["markdown_setting", setting.id]);
}
} }
} }
); });
}; };
export const useMarkdownSetting = (setting_id) => { export const useMarkdownSetting = (setting_id) => {
const config = useConfig(); const config = useConfig();
return useQuery( return useQuery({
["markdown_setting", setting_id], queryKey: ["markdown_setting", setting_id],
() => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/${setting_id}`, {}), { queryFn: () => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/${setting_id}`, {}),
enabled: !!setting_id, enabled: !!setting_id,
} });
);
}; };
export const useCreateMarkdownSetting = () => { export const useCreateMarkdownSetting = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation( return useMutation({
(data) => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/`, { mutationFn: (data) => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/`, {
method: "POST", method: "POST",
body: JSON.stringify(data) body: JSON.stringify(data)
}), { }),
onSuccess: (data) => { onSuccess: (data) => {
queryClient.invalidateQueries(["markdown_setting", data.id]); queryClient.invalidateQueries({queryKey: ["markdown_setting", data.id]});
}
} }
); });
}; };
export const useUpdateMarkdownSetting = () => { export const useUpdateMarkdownSetting = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation( return useMutation({
({id, data}) => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/${id}`, { mutationFn: ({id, data}) => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/${id}`, {
method: "PATCH", method: "PATCH",
body: JSON.stringify(data) body: JSON.stringify(data)
}),{ }),
onSuccess: (data, variables) => { onSuccess: (data, variables) => {
queryClient.invalidateQueries(["markdown_setting", variables.id]); queryClient.invalidateQueries({queryKey: ["markdown_setting", variables.id]});
} }
}); });
}; };
export const useDeleteMarkdownSetting = () => { export const useDeleteMarkdownSetting = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation( return useMutation({
(id) => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/${id}`, { mutationFn: (id) => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/${id}`, {
method: "DELETE", method: "DELETE",
}),{ }),
onSuccess: (data) => { onSuccess: (data) => {
queryClient.invalidateQueries(["markdown_setting", data.id]); queryClient.invalidateQueries({queryKey: ["markdown_setting", data.id]});
}
} }
); });
}; };

View File

@@ -7,95 +7,90 @@ import {data} from "react-router-dom";
export const useMarkdownTemplate = (template_id) => { export const useMarkdownTemplate = (template_id) => {
const config = useConfig(); const config = useConfig();
return useQuery( return useQuery({
["markdown_template", template_id], queryKey: ["markdown_template", template_id],
() => fetch_(`${config.BACKEND_HOST}/api/template/markdown/${template_id}`), { queryFn: () => fetch_(`${config.BACKEND_HOST}/api/template/markdown/${template_id}`),
enabled: !!template_id, enabled: !!template_id,
} });
);
}; };
export const useMarkdownTemplates = () => { export const useMarkdownTemplates = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useQuery( return useQuery({
["markdown_templates"], queryKey: ["markdown_templates"],
() => fetch_(`${config.BACKEND_HOST}/api/template/markdown/`), { queryFn: () => fetch_(`${config.BACKEND_HOST}/api/template/markdown/`),
onSuccess: (data) => { onSuccess: (data) => {
if(data){ if(data){
for(const template of data){ for(const template of data){
queryClient.setQueryData(["markdown_template", template.id], template); queryClient.setQueryData({queryKey: ["markdown_template", template.id]}, template);
}
} }
} }
} }
); });
}; };
export const useUpdateMarkdownTemplate = () => { export const useUpdateMarkdownTemplate = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation( return useMutation({
({id, data}) => fetch_(`${config.BACKEND_HOST}/api/template/markdown/${id}`, { mutationFn: ({id, data}) => fetch_(`${config.BACKEND_HOST}/api/template/markdown/${id}`, {
method: "PUT", method: "PUT",
body: JSON.stringify(data), body: JSON.stringify(data),
}), }),
{ onSuccess: (data) => {
onSuccess: (data) => { queryClient.invalidateQueries({queryKey: ["markdown_template", data.id]});
queryClient.invalidateQueries(["markdown_template", data.id]); queryClient.invalidateQueries({queryKey: ["markdown_templates"]});
queryClient.invalidateQueries(["markdown_templates"]);
}
} }
); });
} }
export const useCreateMarkdownTemplate = () => { export const useCreateMarkdownTemplate = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation( return useMutation({
(data) => fetch_(`${config.BACKEND_HOST}/api/template/markdown/`, { mutationFn: (data) => fetch_(`${config.BACKEND_HOST}/api/template/markdown/`, {
method: "POST", method: "POST",
body: JSON.stringify(data), body: JSON.stringify(data),
}),{ }),
onSuccess: (data) => { onSuccess: (data) => {
queryClient.invalidateQueries(["markdown_template", data.id]); queryClient.invalidateQueries({queryKey: ["markdown_template", data.id]});
queryClient.invalidateQueries(["markdown_templates"]); queryClient.invalidateQueries({queryKey: ["markdown_templates"]});
}
} }
); });
} }
export const useDeleteMarkdownTemplate = () => { export const useDeleteMarkdownTemplate = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation( return useMutation({
(id) => fetch_(`${config.BACKEND_HOST}/api/template/markdown/${id}`, { mutationFn: (id) => fetch_(`${config.BACKEND_HOST}/api/template/markdown/${id}`, {
method: "DELETE" method: "DELETE"
}), { }),
onSuccess: (res, variables) => { onSuccess: (res, variables) => {
queryClient.invalidateQueries(["markdown_template", variables]); queryClient.invalidateQueries({queryKey: ["markdown_template", variables]});
queryClient.invalidateQueries(["markdown_templates"]); queryClient.invalidateQueries({queryKey: ["markdown_templates"]});
}
} }
) });
} }
export const useSaveMarkdownTemplate = () => { export const useSaveMarkdownTemplate = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation(({id, data}) => { return useMutation({
const url = id mutationFn: ({id, data}) => {
? `${config.BACKEND_HOST}/api/template/markdown/${id}` const url = id
: `${config.BACKEND_HOST}/api/template/markdown/`; ? `${config.BACKEND_HOST}/api/template/markdown/${id}`
const method = id? "PUT": "POST"; : `${config.BACKEND_HOST}/api/template/markdown/`;
return fetch_(url, { const method = id? "PUT": "POST";
method, return fetch_(url, {
body: JSON.stringify(data), method,
}) body: JSON.stringify(data),
},{ });
},
onSuccess: (data) => { onSuccess: (data) => {
queryClient.invalidateQueries(["markdown_template", data.id]); queryClient.invalidateQueries({queryKey: ["markdown_template", data.id]});
queryClient.invalidateQueries(["markdown_templates"]); queryClient.invalidateQueries({queryKey: ["markdown_templates"]});
} }
}); });
} }

View File

@@ -5,40 +5,39 @@ import {fetch_} from "../request-utils";
export const useMarkdownTemplateSettings = () => { export const useMarkdownTemplateSettings = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useQuery( return useQuery({
["markdown_template_settings"], queryKey: ["markdown_template_settings"],
() => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/template/`), { queryFn: () => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/template/`),
onSuccess: (data) => { onSuccess: (data) => {
if(data){ if(data){
for(const setting of data){ for(const setting of data){
queryClient.invalidateQueries(["markdown_template_setting", settings.id]); queryClient.setQueryData({queryKey: ["markdown_template_setting", setting.id]}, setting);
}
} }
} }
} }
); });
}; };
export const useMarkdownTemplateSetting = (setting_id) => { export const useMarkdownTemplateSetting = (setting_id) => {
const config = useConfig(); const config = useConfig();
return useQuery( return useQuery({
["markdown_template_setting", setting_id], queryKey: ["markdown_template_setting", setting_id],
() => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/template/${setting_id}`), { queryFn: () => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/template/${setting_id}`),
enabled: !!setting_id, enabled: !!setting_id,
} });
);
}; };
export const useCreateMarkdownTemplateSetting = () => { export const useCreateMarkdownTemplateSetting = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation((data) => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/template/`, { return useMutation({
method: "POST", mutationFn: (data) => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/template/`, {
body: JSON.stringify(data), method: "POST",
}), { body: JSON.stringify(data),
}),
onSuccess: (data) => { onSuccess: (data) => {
queryClient.invalidateQueries(["markdown_template_setting", data.id]); queryClient.invalidateQueries({queryKey: ["markdown_template_setting", data.id]});
} }
}); });
}; };
@@ -46,29 +45,27 @@ export const useCreateMarkdownTemplateSetting = () => {
export const useUpdateMarkdownTemplateSetting = () => { export const useUpdateMarkdownTemplateSetting = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation( return useMutation({
({id, data}) => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/template/${id}`, { mutationFn: ({id, data}) => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/template/${id}`, {
method: "PATCH", method: "PATCH",
body: JSON.stringify(data), body: JSON.stringify(data),
}),{ }),
onSuccess: (res) => { onSuccess: (res) => {
queryClient.invalidateQueries(["markdown_template_setting", res.id]); queryClient.invalidateQueries({queryKey: ["markdown_template_setting", res.id]});
queryClient.invalidateQueries(["markdown_template_settings"]); queryClient.invalidateQueries({queryKey: ["markdown_template_settings"]});
}
} }
); });
}; };
export const useDeleteMarkdownTemplateSetting = () => { export const useDeleteMarkdownTemplateSetting = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation( return useMutation({
({id}) => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/template/${id}`, { mutationFn: ({id}) => fetch_(`${config.BACKEND_HOST}/api/setting/markdown/template/${id}`, {
method: "DELETE", method: "DELETE",
}), { }),
onSuccess: (res, variables) => { onSuccess: (res, variables) => {
queryClient.invalidateQueries(["markdown_template_setting", variables.id]); queryClient.invalidateQueries({queryKey: ["markdown_template_setting", variables.id]});
}
} }
); });
}; };

View File

@@ -6,87 +6,77 @@ import {useConfig} from "../../ConfigProvider";
export const usePaths = (parent_id) => { export const usePaths = (parent_id) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const config = useConfig(); const config = useConfig();
return useQuery( return useQuery({
["paths", parent_id], queryKey: ["paths", parent_id],
() => fetch_(`${config.BACKEND_HOST}/api/path/parent/${parent_id}`), queryFn: () => fetch_(`${config.BACKEND_HOST}/api/path/parent/${parent_id}`),
{ enabled: !!parent_id,
enabled: !!parent_id, onSuccess: (data) => {
onSuccess: (data) => { if(data) {
if(data) { for (const pth of data)
for (const pth of data) {
{ queryClient.setQueryData({queryKey: ["path", pth.id]}, pth);
queryClient.setQueryData(["path", pth.id], pth);
}
} }
} }
} }
); });
}; };
export const usePath = (id) => { export const usePath = (id) => {
const config = useConfig(); const config = useConfig();
return useQuery( return useQuery({
["path", id], queryKey: ["path", id],
() => fetch_(`${config.BACKEND_HOST}/api/path/${id}`), queryFn: () => fetch_(`${config.BACKEND_HOST}/api/path/${id}`),
{ enabled: !!id
enabled: !!id });
}
);
}; };
export const useCreatePath = () => { export const useCreatePath = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation( return useMutation({
(data) => fetch_(`${config.BACKEND_HOST}/api/path/`, { mutationFn: (data) => fetch_(`${config.BACKEND_HOST}/api/path/`, {
method: "POST", method: "POST",
body: JSON.stringify(data), body: JSON.stringify(data),
}), }),
{ onSuccess: (res) => {
onSuccess: (res) => { queryClient.invalidateQueries({queryKey: ["paths", res.parent_id]});
queryClient.invalidateQueries(["paths", res.parent_id]); queryClient.invalidateQueries({queryKey: ["tree"]});
queryClient.invalidateQueries(["tree"]); },
}, });
}
);
}; };
export const useUpdatePath = () => { export const useUpdatePath = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation( return useMutation({
({ id, data }) => fetch_(`${config.BACKEND_HOST}/api/path/${id}`, { mutationFn: ({ id, data }) => fetch_(`${config.BACKEND_HOST}/api/path/${id}`, {
method: "PATCH", method: "PATCH",
body: JSON.stringify(data), body: JSON.stringify(data),
}), }),
{ onSuccess: (res) => {
onSuccess: (res) => { queryClient.invalidateQueries({queryKey: ["paths", res.parent_id]});
queryClient.invalidateQueries(["paths", res.parent_id]); queryClient.invalidateQueries({queryKey: ["path", res.id]});
queryClient.invalidateQueries(["path", res.id]); queryClient.invalidateQueries({queryKey: ["tree"]});
queryClient.invalidateQueries(["tree"]); },
}, });
}
);
}; };
export const useDeletePath = () => { export const useDeletePath = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation( return useMutation({
(id) => fetch_(`${config.BACKEND_HOST}/api/path/${id}`, { mutationFn: (id) => fetch_(`${config.BACKEND_HOST}/api/path/${id}`, {
method: "DELETE", method: "DELETE",
}), }),
{ onSuccess: () => {
onSuccess: () => { queryClient.invalidateQueries({queryKey: ["paths"]});
queryClient.invalidateQueries(["paths"]); queryClient.invalidateQueries({queryKey: ["tree"]});
queryClient.invalidateQueries(["tree"]); },
}, });
}
);
}; };
@@ -94,16 +84,14 @@ export const useMovePath = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const config = useConfig(); const config = useConfig();
return useMutation( return useMutation({
({path, direction}) => { mutationFn: ({path, direction}) => {
const apiEndpoint = `${config.BACKEND_HOST}/api/path/move_${direction}/${path.id}`; const apiEndpoint = `${config.BACKEND_HOST}/api/path/move_${direction}/${path.id}`;
return fetch_(apiEndpoint, {method: "PATCH"}); return fetch_(apiEndpoint, {method: "PATCH"});
}, },
{ onSuccess: () => {
onSuccess: () => { queryClient.invalidateQueries({queryKey: ["paths"]});
queryClient.invalidateQueries(["paths"]); queryClient.invalidateQueries({queryKey: ["tree"]});
queryClient.invalidateQueries(["tree"]);
}
} }
); });
}; };

View File

@@ -5,76 +5,69 @@ import {fetch_} from "../request-utils";
export const usePathSettings = () => { export const usePathSettings = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useQuery( return useQuery({
"path_settings", queryKey: ["path_settings"],
() => fetch_(`${config.BACKEND_HOST}/api/setting/path/`), queryFn: () => fetch_(`${config.BACKEND_HOST}/api/setting/path/`),
{ onSuccess: (data) => {
onSuccess: (data) => { if(data){
if(data){ for(const setting of data)
for(const setting of data) queryClient.setQueryData({queryKey: ["path_setting", setting.id]}, setting);
queryClient.setQueryData(["path_setting", setting.id], setting);
}
} }
} }
); });
}; };
export const usePathSetting = (setting_id) => { export const usePathSetting = (setting_id) => {
const config = useConfig(); const config = useConfig();
return useQuery( return useQuery({
["path_setting", setting_id], queryKey: ["path_setting", setting_id],
() => fetch_(`${config.BACKEND_HOST}/api/setting/path/${setting_id}`), queryFn: () => fetch_(`${config.BACKEND_HOST}/api/setting/path/${setting_id}`),
{ enabled: !!setting_id,
enabled: !!setting_id, });
}
);
}; };
export const useCreatePathSetting = () => { export const useCreatePathSetting = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation( return useMutation({
(data) => fetch_(`${config.BACKEND_HOST}/api/setting/path/`, { mutationFn: (data) => fetch_(`${config.BACKEND_HOST}/api/setting/path/`, {
method: "POST", method: "POST",
body: JSON.stringify(data) body: JSON.stringify(data)
}), { }),
onSuccess: (data) => { onSuccess: (data) => {
queryClient.invalidateQueries(["path_setting", data.id]); queryClient.invalidateQueries({queryKey: ["path_setting", data.id]});
queryClient.invalidateQueries(["path_settings"]); queryClient.invalidateQueries({queryKey: ["path_settings"]});
}
} }
); });
}; };
export const useUpdatePathSetting = () => { export const useUpdatePathSetting = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation( return useMutation({
({id, data}) => fetch_(`${config.BACKEND_HOST}/api/setting/path/${id}`, { mutationFn: ({id, data}) => fetch_(`${config.BACKEND_HOST}/api/setting/path/${id}`, {
method: "PATCH", method: "PATCH",
body: JSON.stringify(data) body: JSON.stringify(data)
}), { }),
onSuccess: (data, variables) => { onSuccess: (data, variables) => {
queryClient.invalidateQueries(["path_setting", variables.id]); queryClient.invalidateQueries({queryKey: ["path_setting", variables.id]});
queryClient.invalidateQueries(["path_settings"]); queryClient.invalidateQueries({queryKey: ["path_settings"]});
}
} }
); });
}; };
export const useDeletePathSetting = () => { export const useDeletePathSetting = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation( return useMutation({
(id) => fetch_(`${config.BACKEND_HOST}/api/setting/path/${id}`, { mutationFn: (id) => fetch_(`${config.BACKEND_HOST}/api/setting/path/${id}`, {
method: "DELETE", method: "DELETE",
}),{ }),
onSuccess: (data, variables) => { onSuccess: (data, variables) => {
queryClient.invalidateQueries(["path_setting", variables.id]); queryClient.invalidateQueries({queryKey: ["path_setting", variables.id]});
queryClient.invalidateQueries(["path_settings"]); queryClient.invalidateQueries({queryKey: ["path_settings"]});
}
} }
); });
}; };

View File

@@ -6,14 +6,12 @@ import {useConfig} from "../../ConfigProvider";
export const useTree = () => { export const useTree = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const config = useConfig(); const config = useConfig();
return useQuery( return useQuery({
["tree"], queryKey: ["tree"],
() => fetch_(`${config.BACKEND_HOST}/api/tree/`), queryFn: () => fetch_(`${config.BACKEND_HOST}/api/tree/`),
{ onSuccess: data => {
onSuccess: data => { if(data)
if(data) queryClient.setQueryData({queryKey: ["tree"]}, data);
queryClient.setQueryData(["tree"], data);
}
} }
); });
} };

View File

@@ -5,147 +5,132 @@ import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query";
export const useWebhooks = () =>{ export const useWebhooks = () =>{
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const config = useConfig(); const config = useConfig();
return useQuery( return useQuery({
["webhooks"], queryKey: ["webhooks"],
() => fetch_(`${config.BACKEND_HOST}/api/webhook/`), queryFn: () => fetch_(`${config.BACKEND_HOST}/api/webhook/`),
{ onSuccess: (data) => {
onSuccess: (data) => { if(data){
if(data){ for(const webhook of data){
for(const webhook of data){ queryClient.setQueryData({queryKey: ["webhook", data.id]}, data);
queryClient.setQueryData(["webhook", data.id], data);
}
} }
} }
} }
); });
}; };
export const useCreateWebhook = () => { export const useCreateWebhook = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation( return useMutation({
(data) => fetch_(`${config.BACKEND_HOST}/api/webhook/`, { mutationFn: (data) => fetch_(`${config.BACKEND_HOST}/api/webhook/`, {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
"hook_url": data "hook_url": data
}), }),
}), }),
{ onSuccess: () => {
onSuccess: () => { queryClient.invalidateQueries({queryKey: ["webhooks"]});
queryClient.invalidateQueries(["webhooks"]);
}
} }
); });
}; };
export const useUpdateWebhook = () =>{ export const useUpdateWebhook = () =>{
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation( return useMutation({
({id, data}) => fetch_(`${config.BACKEND_HOST}/api/webhook/${id}`, { mutationFn: ({id, data}) => fetch_(`${config.BACKEND_HOST}/api/webhook/${id}`, {
method: "PATCH", method: "PATCH",
body: JSON.stringify(data) body: JSON.stringify(data)
}), }),
{ onSuccess: (res) => {
onSuccess: (res) => { queryClient.invalidateQueries({queryKey: ["webhook", res.id]});
queryClient.invalidateQueries(["webhook", res.id]); queryClient.invalidateQueries({queryKey: ["webhooks"]});
queryClient.invalidateQueries(["webhooks"]);
}
} }
); });
}; };
export const useDeleteWebhook = () => { export const useDeleteWebhook = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation( return useMutation({
(id) => fetch_(`${config.BACKEND_HOST}/api/webhook/${id}`, { mutationFn: (id) => fetch_(`${config.BACKEND_HOST}/api/webhook/${id}`, {
method: "DELETE", method: "DELETE",
}), }),
{ onSuccess: (res, variables) => {
onSuccess: (res, variables) => { queryClient.invalidateQueries({queryKey: ["webhook", variables.id]});
queryClient.invalidateQueries(["webhook", variables.id]); queryClient.invalidateQueries({queryKey: ["webhooks"]});
queryClient.invalidateQueries(["webhooks"]);
}
} }
) });
} }
export const useWebhookSettings = () => { export const useWebhookSettings = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useQuery( return useQuery({
["webhook_setting"], queryKey: ["webhook_setting"],
() => fetch_(`${config.BACKEND_HOST}/api/setting/path/webhook/`), queryFn: () => fetch_(`${config.BACKEND_HOST}/api/setting/path/webhook/`),
{ onSuccess: (data) => {
onSuccess: (data) => { if(data){
if(data){ for(const setting of data){
for(const setting of data){ queryClient.setQueryData({queryKey: ["webhook_setting", setting.id]}, setting);
queryClient.setQueryData(["webhook_setting", setting.id], setting);
}
} }
} }
} }
); });
}; };
export const useWebhookSetting = (setting_id) => { export const useWebhookSetting = (setting_id) => {
const config = useConfig(); const config = useConfig();
return useQuery( return useQuery({
["webhook_setting", setting_id], queryKey: ["webhook_setting", setting_id],
() => fetch_(`${config.BACKEND_HOST}/api/setting/path/webhook/${setting_id}`), queryFn: () => fetch_(`${config.BACKEND_HOST}/api/setting/path/webhook/${setting_id}`),
{ enabled: !!setting_id,
enabled: !!setting_id, });
});
}; };
export const useCreateWebhookSetting = () => { export const useCreateWebhookSetting = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation( return useMutation({
(data) => fetch_(`${config.BACKEND_HOST}/api/setting/path/webhook/`, { mutationFn: (data) => fetch_(`${config.BACKEND_HOST}/api/setting/path/webhook/`, {
method: "POST", method: "POST",
body: JSON.stringify(data) body: JSON.stringify(data)
}),{ }),
onSuccess: (res) => { onSuccess: (res) => {
queryClient.invalidateQueries(["webhook_setting", res.id]); queryClient.invalidateQueries({queryKey: ["webhook_setting", res.id]});
queryClient.invalidateQueries(["webhook_setting"]); queryClient.invalidateQueries({queryKey: ["webhook_setting"]});
}
} }
); });
}; };
export const useUpdateWebhookSetting = () => { export const useUpdateWebhookSetting = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation( return useMutation({
({id, data}) => fetch_(`${config.BACKEND_HOST}/api/setting/path/webhook/${id}`, { mutationFn: ({id, data}) => fetch_(`${config.BACKEND_HOST}/api/setting/path/webhook/${id}`, {
method: "PATCH", method: "PATCH",
body: JSON.stringify(data) body: JSON.stringify(data)
}),{ }),
onSuccess: (res, variables) => { onSuccess: (res, variables) => {
queryClient.invalidateQueries(["webhook_setting", variables.id]); queryClient.invalidateQueries({queryKey: ["webhook_setting", variables.id]});
queryClient.invalidateQueries(["webhook_setting"]); queryClient.invalidateQueries({queryKey: ["webhook_setting"]});
}
} }
); });
}; };
export const useDeleteWebhookSetting = () => { export const useDeleteWebhookSetting = () => {
const config = useConfig(); const config = useConfig();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation( return useMutation({
(id) => fetch_(`${config.BACKEND_HOST}/api/setting/path/webhook/${id}`, { mutationFn: (id) => fetch_(`${config.BACKEND_HOST}/api/setting/path/webhook/${id}`, {
method: "DELETE", method: "DELETE",
}), }),
{ onSuccess: (res, variables) => {
onSuccess: (res, variables) => { queryClient.invalidateQueries({queryKey: ["webhook_setting", variables.id]});
queryClient.invalidateQueries(["webhook_setting", variables.id]); queryClient.invalidateQueries({queryKey: ["webhook_setting"]});
queryClient.invalidateQueries(["webhook_setting"]);
}
} }
); });
}; };